// Generated by `mise run animation-assets:generate`. Do not edit.
///|
let player_source : String = "(() => {\n 'use strict';\n const root = document.currentScript.closest('[data-diagram-viewer]');\n if (!root || root.dataset.motionReady) return;\n root.dataset.motionReady = 'true';\n const data = JSON.parse(root.querySelector('script[data-motion-data]').textContent);\n const svg = root.querySelector('svg');\n const control = name => root.querySelector(`[data-control=\"${name}\"]`);\n const play = control('play'), restart = control('restart'), step = control('step');\n const speedControl = control('speed'), seek = control('seek'), positionLabel = control('position');\n const status = control('status'), from = control('from'), to = control('to');\n const connectionControl=control('connection'), showConnection=control('show-connection');\n const undirectedKinds = control('undirected');\n const messageKinds = control('messages'), direction = control('direction');\n const reduced = matchMedia('(prefers-reduced-motion: reduce)');\n const ns = 'http://www.w3.org/2000/svg';\n const elements = new Map([...svg.querySelectorAll('path[id]')].map(path => [path.id, path]));\n const bindings = new Map(data.paths.map(binding => [binding.key, { element: elements.get(binding.id), length: null }]));\n const nodeElements = new Map();\n const bpmnElements = new Map([...svg.querySelectorAll('.bpmn-shape')].map(element => [element.getAttribute('data-id'), element]));\n const connectionElements = new Map([...svg.querySelectorAll('.swimlane-connection[data-swimlane-id]')].map(element => [element.getAttribute('data-swimlane-id'),element]));\n const swimlaneElements = new Map([...svg.querySelectorAll('.swimlane-node[data-swimlane-id], .swimlane-group[data-swimlane-id], .swimlane-lane[data-swimlane-id]')].map(element => [element.getAttribute('data-swimlane-id'), element]));\n const messageElements = new Map([...svg.querySelectorAll('.message[data-index]')].map(element => [Number(element.getAttribute('data-index')), element]));\n const frameElements = [...svg.querySelectorAll('rect.frame')];\n for (const binding of data.node_bindings) {\n const element = binding.kind === 'swimlane' ? swimlaneElements.get(binding.id) : binding.kind === 'bpmn' ? bpmnElements.get(binding.id) : binding.kind === 'message' ? messageElements.get(binding.index) : frameElements[binding.index];\n if (element) nodeElements.set(binding.endpoint, element);\n }\n function decode(plan) {\n return { mode: plan.mode, duration: Number(plan.duration_ms), steps: plan.steps.map(item => ({ ordinal: item.ordinal, key: item.key, start: Number(item.start_ms), duration: Number(item.duration_ms), groups: item.groups, reversed: Boolean(item.reversed) })) };\n }\n const authored = decode(data.plan);\n let plan = authored, position = 0, speed = 1, playing = false, previous = null, frame = null;\n const overlays = new Map(), highlights = new Set();\n function clearHighlights() { for (const element of highlights) element.classList.remove('motion-selected'); highlights.clear(); }\n function highlight(ids) { clearHighlights(); for (const id of ids) { const element = nodeElements.get(id); if (element) { element.classList.add('motion-selected'); highlights.add(element); } } }\n function clearOverlays() { for (const overlay of overlays.values()) overlay.group.remove(); overlays.clear(); }\n function normalize(time) {\n if (plan.duration === 0) return 0;\n return plan.mode === 'illustrative' ? Math.max(0, time) % plan.duration : Math.max(0, Math.min(time, plan.duration));\n }\n function stopFrame() { if (frame !== null) cancelAnimationFrame(frame); frame = null; previous = null; }\n function state(message) {\n root.dataset.playing = String(playing);\n play.textContent = playing ? 'Pause' : 'Play'; play.setAttribute('aria-pressed', String(playing));\n play.disabled = plan.duration === 0; restart.disabled = plan.duration === 0; step.disabled = plan.duration === 0; seek.disabled = plan.duration === 0;\n status.textContent = message || (playing ? (plan.mode === 'finite' ? 'Playing explicit scenario' : 'Playing illustrative flow') : plan.mode === 'finite' && position >= plan.duration ? 'Finished' : reduced.matches ? 'Paused. Reduced motion: use Step or Position.' : 'Paused');\n }\n function makeOverlay(item) {\n const binding = bindings.get(item.key);\n if (!binding || !binding.element) throw new Error('Missing validated motion binding');\n if (binding.length === null) binding.length = binding.element.getTotalLength();\n const group = document.createElementNS(ns, 'g');\n group.setAttribute('class', 'motion-overlay'); group.setAttribute('aria-hidden', 'true'); group.dataset.ordinal = String(item.ordinal); group.dataset.key = item.key;\n const path = document.createElementNS(ns, 'path'); path.setAttribute('d', binding.element.getAttribute('d'));\n path.setAttribute('fill', 'none'); path.setAttribute('stroke', data.accent); path.setAttribute('stroke-width', '3'); path.setAttribute('stroke-opacity', '.5'); path.setAttribute('stroke-dasharray', '12 8');\n const dot = document.createElementNS(ns, 'circle'); dot.setAttribute('r', '4'); dot.setAttribute('fill', data.accent);\n group.append(path, dot); binding.element.parentElement.append(group);\n const overlay = { key: item.key, group, path, dot, binding };\n overlays.set(item.ordinal, overlay); return overlay;\n }\n function draw() {\n const logical = Math.floor(normalize(position)); const active = new Set(); const keys = [];\n for (const item of plan.steps) {\n if (logical < item.start || logical >= item.start + item.duration) continue;\n active.add(item.ordinal); keys.push(item.key);\n const overlay = overlays.get(item.ordinal) || makeOverlay(item);\n const progress = Math.max(0, Math.min(1, (normalize(position) - item.start) / item.duration));\n const point = overlay.binding.element.getPointAtLength(overlay.binding.length * (item.reversed ? 1 - progress : progress));\n overlay.dot.setAttribute('cx', String(point.x)); overlay.dot.setAttribute('cy', String(point.y));\n overlay.path.setAttribute('stroke-dashoffset', String(-overlay.binding.length * progress));\n overlay.group.dataset.progress = String(progress); overlay.group.dataset.reversed = String(Boolean(item.reversed));\n }\n for (const [ordinal, overlay] of overlays) if (!active.has(ordinal)) { overlay.group.remove(); overlays.delete(ordinal); }\n // Still startup keeps the complete static diagram without an overlay.\n if (!playing && position === 0 && !root.dataset.inspected) clearOverlays();\n root.dataset.position = String(logical); root.dataset.active = JSON.stringify(keys); root.dataset.finished = String(plan.mode === 'finite' && logical >= plan.duration);\n seek.max = String(plan.duration); seek.value = String(logical);\n positionLabel.textContent = `${logical} / ${plan.duration} ms`;\n }\n function advance(now) {\n if (previous !== null) position = normalize(position + Math.max(0, now - previous) * speed);\n previous = now;\n if (plan.mode === 'finite' && position >= plan.duration) { playing = false; stopFrame(); state('Finished'); }\n }\n function tick(now) {\n frame = null;\n if (!playing || document.hidden || reduced.matches) { previous = null; return; }\n advance(now); draw();\n if (playing) frame = requestAnimationFrame(tick);\n }\n function schedule() { stopFrame(); if (playing && !document.hidden && !reduced.matches) frame = requestAnimationFrame(tick); }\n function inspect(time) {\n playing = false; stopFrame(); position = Math.max(0, Math.min(time, plan.duration)); root.dataset.inspected = 'true'; draw(); state();\n }\n function nextStep() {\n const logical = Math.floor(normalize(position));\n let next = plan.duration;\n for (const item of plan.steps) if (item.start > logical && item.start < next) next = item.start;\n if (plan.mode === 'illustrative' && next >= plan.duration) inspect(0); else inspect(next);\n }\n play.addEventListener('click', () => {\n if (reduced.matches) { nextStep(); return; }\n if (!playing && plan.mode === 'finite' && position >= plan.duration) position = 0;\n const requested = !playing;\n if (playing && previous !== null) advance(performance.now());\n playing = requested; root.dataset.inspected = 'true'; state(); draw(); schedule();\n });\n restart.addEventListener('click', () => inspect(0));\n step.addEventListener('click', nextStep);\n speedControl.addEventListener('change', () => {\n if (playing && previous !== null) advance(performance.now());\n const selected = Number(speedControl.value); if ([.25, .5, 1, 1.5, 2, 4].includes(selected)) speed = selected;\n root.dataset.speed = String(speed); previous = null; draw(); schedule();\n });\n seek.addEventListener('input', () => { position = Math.max(0, Math.min(Number(seek.value), plan.duration)); previous = null; root.dataset.inspected = 'true'; draw(); state(); });\n document.addEventListener('visibilitychange', () => { schedule(); });\n reduced.addEventListener('change', () => { if (reduced.matches) playing = false; state(); schedule(); });\n control('download').addEventListener('click', () => {\n const url = URL.createObjectURL(new Blob([data.canonical], { type: 'image/svg+xml;charset=utf-8' }));\n const link = document.createElement('a'); link.href = url; link.download = `${data.view_id.replace(/[^a-z0-9._-]/gi, '_') || 'diagram'}.svg`;\n root.append(link); link.click(); link.remove(); setTimeout(() => URL.revokeObjectURL(url), 0);\n });\n\n const outside = new Set(data.outside || []);\n const nodeIndex = new Map(data.graph.nodes.map((node, index) => [node.id, index]));\n const outgoing = new Map(), incoming = new Map();\n function add(table, node, kind, arc) { let kinds = table.get(node); if (!kinds) table.set(node, kinds = new Map()); let arcs = kinds.get(kind); if (!arcs) kinds.set(kind, arcs = []); arcs.push(arc); }\n data.graph.edges.forEach((edge, index) => { const a = nodeIndex.get(edge.source), b = nodeIndex.get(edge.target); add(outgoing, a, edge.kind, [b, index]); add(incoming, b, edge.kind, [a, index]); });\n function* arcs(node, kinds, selectedDirection) {\n const streams = [];\n for (const kind of kinds) {\n if (selectedDirection !== 'reverse') { const items = outgoing.get(node)?.get(kind); if (items) streams.push({ items, next: 0 }); }\n if (selectedDirection !== 'forward') { const items = incoming.get(node)?.get(kind); if (items) streams.push({ items, next: 0 }); }\n }\n let last = -1;\n for (;;) {\n let chosen = null;\n for (const stream of streams) if (stream.next < stream.items.length && (!chosen || stream.items[stream.next][1] < chosen.items[chosen.next][1])) chosen = stream;\n if (!chosen) return;\n const arc = chosen.items[chosen.next++]; if (arc[1] === last) continue; last = arc[1]; yield arc;\n }\n }\n function search(startId, targetId, selectedDirection, reach) {\n const limits = data.query_limits;\n const start = nodeIndex.get(startId), target = targetId === null ? null : nodeIndex.get(targetId);\n if (start === undefined || (!reach && target === undefined)) return { failure: outside.has(startId) || outside.has(targetId) ? 'Outside current view' : 'Invalid endpoint' };\n const kinds = ['sequence', 'order', 'swimlane']; if (messageKinds.checked) kinds.push('message'); if (undirectedKinds.checked) kinds.push('undirected');\n const queue = [start], seen = new Set(queue), previous = new Map(); let at = 0, count = 0, work = kinds.length;\n if (!reach && start === target) return { nodes: [start], edges: [] };\n while (at < queue.length) {\n if (++work > limits.work) return { failure: 'Search limit exceeded: work' };\n const current = queue[at++];\n for (const [next, edge] of arcs(current, kinds, selectedDirection)) {\n if (++work > limits.work || ++count > limits.edges) return { failure: 'Search limit exceeded' };\n if (seen.has(next)) continue;\n if (queue.length >= limits.nodes || (reach && queue.length >= limits.results)) return { failure: 'Search limit exceeded: nodes/results' };\n seen.add(next); previous.set(next, [current, edge]); queue.push(next);\n if (!reach && next === target) {\n const nodes = [target], edges = []; let cursor = target;\n while (cursor !== start) { if (nodes.length >= limits.results) return { failure: 'Search limit exceeded: results' }; const [parent, selected] = previous.get(cursor); nodes.push(parent); edges.push(selected); cursor = parent; }\n return { nodes: nodes.reverse(), edges: edges.reverse() };\n }\n }\n }\n return reach ? { nodes: queue, edges: [] } : { failure: 'Unreachable under the selected connection kinds' };\n }\n for (const node of data.graph.nodes) {\n if (node.kind !== 'bpmn' && node.kind !== 'message' && node.kind !== 'swimlane') continue;\n for (const select of [from, to]) { const option = document.createElement('option'); option.value = node.id; option.textContent = node.label || node.id; if (node.description) option.title = node.description; select.append(option); }\n }\n if (data.kind === 'swimlane') { const first = data.graph.nodes.find(node => node.primary); if (first) from.value = first.id; }\n if (to.options.length > 1) to.selectedIndex = to.options.length - 1;\n messageKinds.closest('label').hidden = data.kind !== 'bpmn';\n undirectedKinds.closest('label').hidden = data.kind !== 'swimlane';\n connectionControl.closest('label').hidden = data.kind !== 'swimlane'; showConnection.hidden = data.kind !== 'swimlane';\n for (const binding of data.paths) {\n if (binding.kind !== 'swimlane') continue;\n const option=document.createElement('option'); option.value=binding.key;\n option.textContent=binding.connection+(binding.label ? ': '+binding.label : ''); connectionControl.append(option);\n }\n showConnection.disabled=connectionControl.options.length===0;\n control('route').disabled = from.options.length === 0; control('reach').disabled = from.options.length === 0;\n function replace(next, message) { playing = false; stopFrame(); clearOverlays(); plan = next; position = 0; root.dataset.inspected = 'true'; draw(); state(message); }\n showConnection.addEventListener('click', () => {\n const binding=data.paths.find(binding => binding.key===connectionControl.value);\n if (!binding) return;\n replace({mode:'finite',duration:1000,steps:[{ordinal:0,key:binding.key,start:0,duration:1000,groups:[],reversed:Boolean(binding.reversed)}]},'Selected connection: '+binding.connection);\n clearHighlights(); const element=connectionElements.get(binding.connection);\n if(element) { element.classList.add('motion-selected'); highlights.add(element); }\n root.dataset.connection=binding.connection; root.dataset.route=JSON.stringify([binding.key]);\n });\n control('route').addEventListener('click', () => {\n delete root.dataset.connection;\n const result = search(from.value, to.value, data.kind === 'swimlane' ? direction.value : 'forward', false);\n if (result.failure) { replace({ mode: 'finite', duration: 0, steps: [] }, result.failure); clearHighlights(); root.dataset.route = '[]'; return; }\n const keys = data.kind !== 'sequence' ? result.edges.map(index => data.graph.edges[index].occurrence).filter(Boolean) : result.nodes.map(index => data.graph.nodes[index].occurrence).filter(Boolean);\n if (keys.length > data.step_limit) { replace({ mode: 'finite', duration: 0, steps: [] }, 'Scenario step limit exceeded'); clearHighlights(); root.dataset.route = '[]'; return; }\n const steps = keys.map((key, ordinal) => {\n const edge = data.graph.edges[result.edges[ordinal]];\n const reversed = data.kind === 'swimlane' && (Boolean(edge.reversed) !== (edge.source !== data.graph.nodes[result.nodes[ordinal]].id));\n return { ordinal, key, start: ordinal * 1000, duration: 1000, groups: [], reversed };\n });\n replace({ mode: 'finite', duration: steps.length * 1000, steps }, `Structural route: ${keys.length} connections. Conditions and event triggers are not evaluated.`);\n highlight(result.nodes.map(index => data.graph.nodes[index].id)); root.dataset.route = JSON.stringify(keys);\n });\n control('reach').addEventListener('click', () => {\n const result = search(from.value, null, direction.value, true);\n playing = false; stopFrame(); clearOverlays();\n if (result.failure) { clearHighlights(); state(result.failure); root.dataset.reachable = '[]'; return; }\n const ids = result.nodes.map(index => data.graph.nodes[index].id); highlight(ids); root.dataset.reachable = JSON.stringify(ids);\n const visible = new Set(ids.map(id => nodeElements.get(id)).filter(Boolean));\n state(`Structural reachability: ${visible.size} diagram elements. Conditions and event triggers are not evaluated.`);\n });\n control('clear').addEventListener('click', () => { clearHighlights(); delete root.dataset.route; delete root.dataset.connection; delete root.dataset.reachable; replace(authored, 'Paused'); delete root.dataset.inspected; draw(); });\n root.dataset.speed = '1'; draw(); state();\n})();\n"
///|
let player_style : String = "body { margin: 1.5rem; background: #f4f5f7; color: #0f172a; font: 15px/1.5 system-ui, sans-serif; }\n[data-diagram-viewer] { --viewer-bg: #f4f5f7; --viewer-ink: #0f172a; --viewer-border: #64748b; max-width: 90rem; margin: auto; }\n[data-diagram-viewer] .diagram-canvas { overflow: auto; border: 1px solid var(--viewer-border); border-radius: .4rem; }\n[data-diagram-viewer] .diagram-canvas > svg { display: block; max-width: 100%; height: auto; }\n[data-diagram-viewer] .motion-controls, [data-diagram-viewer] fieldset { display: flex; align-items: center; flex-wrap: wrap; gap: .6rem; margin: .8rem 0; }\n[data-diagram-viewer] fieldset { border: 1px solid var(--viewer-border); border-radius: .4rem; }\n[data-diagram-viewer] button, [data-diagram-viewer] select, [data-diagram-viewer] input { font: inherit; }\n[data-diagram-viewer] button, [data-diagram-viewer] select { padding: .35rem .6rem; border: 1px solid var(--viewer-border); border-radius: .3rem; background: var(--viewer-bg); color: var(--viewer-ink); }\n[data-diagram-viewer] [hidden] { display: none !important; }\n[data-diagram-viewer] label { display: inline-flex; align-items: center; gap: .35rem; }\n[data-diagram-viewer] select { max-width: 20rem; }\n[data-diagram-viewer] [data-control=\"seek\"] { width: min(22rem, 60vw); }\n[data-diagram-viewer] :focus-visible { outline: 3px solid #2563eb; outline-offset: 3px; }\n[data-diagram-viewer] [data-control=\"status\"] { min-height: 1.5em; }\n[data-diagram-viewer] .motion-overlay { pointer-events: none; }\n@media screen {\n [data-diagram-viewer] .motion-selected { filter: drop-shadow(0 0 3px #2563eb); }\n}\n@media (prefers-color-scheme: dark) {\n body { background: #10131a; color: #e2e8f0; }\n [data-diagram-viewer] { --viewer-bg: #10131a; --viewer-ink: #e2e8f0; --viewer-border: #94a3b8; }\n}\n@media print {\n body { margin: 0; background: white; }\n [data-diagram-viewer] .motion-controls, [data-diagram-viewer] fieldset, [data-diagram-viewer] [data-control=\"status\"], [data-diagram-viewer] .motion-overlay { display: none !important; }\n [data-diagram-viewer] .diagram-canvas { overflow: visible; border: none; }\n}\n"