// 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"