@@ -8691,6 +8749,24 @@
Tool boundary
count: function () { return payload ? Object.keys(payload).length : 0; }
};
})();
+ /* Authored node task checklists: passport-only, like explanations. Their task
+ sources are verified and read through Archify.sourceEvidence.task(). */
+ Archify.taskChecklists = (function () {
+ var element = document.getElementById('archify-task-checklists-data');
+ var payload = null;
+ if (element) {
+ try {
+ var parsed = JSON.parse(element.textContent || 'null');
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) payload = parsed;
+ } catch (_) {}
+ }
+ return {
+ node: function (id) {
+ var tasks = payload && Object.prototype.hasOwnProperty.call(payload, id) ? payload[id] : null;
+ return Array.isArray(tasks) ? tasks.slice() : [];
+ }
+ };
+ })();
/* Verified repository evidence is emitted only after the renderer checks
the authored GitHub revision, source blobs, and optional line ranges
against the explicit --repo-root. It remains outside the SVG so every
@@ -8730,6 +8806,11 @@
Tool boundary
node: function (id) {
var sources = payload && payload.nodes ? payload.nodes[id] : null;
return Array.isArray(sources) ? sources.slice() : [];
+ },
+ task: function (id, index) {
+ var tasks = payload && payload.taskChecklists ? payload.taskChecklists[id] : null;
+ var sources = Array.isArray(tasks) ? tasks[index] : null;
+ return Array.isArray(sources) ? sources.slice() : [];
}
};
})();
@@ -8905,6 +8986,9 @@
Tool boundary
var parentBackBtn = document.getElementById('btn-parent-back');
var drilldownsSection = document.getElementById('focus-drilldowns');
var drilldownsList = document.getElementById('focus-drilldowns-list');
+ var taskChecklistSection = document.getElementById('focus-task-checklist');
+ var taskChecklistCount = document.getElementById('focus-task-checklist-count');
+ var taskChecklistTasks = document.getElementById('focus-task-checklist-tasks');
var moveBtn = document.getElementById('btn-focus-move');
var activeIds = [];
var hoveredRelationship = null;
@@ -9226,30 +9310,71 @@
Tool boundary
repositoryLink.setAttribute('aria-label', viewerText('viewer.passport.repository.open', { revision: repository.revision }));
}
evidence.title = viewerText('viewer.passport.verificationScope');
- sources.forEach(function (source) {
- var link = document.createElement(source.href ? 'a' : 'div');
- link.className = 'semantic-passport-source';
- if (source.href) {
- link.href = source.href;
- link.target = '_blank';
- link.rel = 'noopener noreferrer';
- link.referrerPolicy = 'no-referrer';
- link.setAttribute('aria-label', viewerText('viewer.passport.source.open', { path: source.path, revision: repository.shortRevision }));
+ sources.forEach(function (source) { evidenceLinks.appendChild(sourceLink(source, repository)); });
+ evidence.hidden = false;
+ }
+ function sourceLink(source, repository) {
+ var link = document.createElement(source.href ? 'a' : 'div');
+ link.className = 'semantic-passport-source';
+ if (source.href) {
+ link.href = source.href;
+ link.target = '_blank';
+ link.rel = 'noopener noreferrer';
+ link.referrerPolicy = 'no-referrer';
+ link.setAttribute('aria-label', viewerText('viewer.passport.source.open', { path: source.path, revision: repository.shortRevision }));
+ }
+ var name = document.createElement('strong');
+ name.textContent = source.label || source.path.split('/').pop() || source.path;
+ var location = document.createElement('code');
+ location.textContent = source.line
+ ? 'L' + source.line + (source.endLine && source.endLine !== source.line ? '–' + source.endLine : '') + (source.href ? ' ↗' : '')
+ : source.href ? viewerText('viewer.passport.source.openLink') : '';
+ var sourcePath = document.createElement('small');
+ sourcePath.textContent = source.path;
+ link.appendChild(name);
+ link.appendChild(location);
+ link.appendChild(sourcePath);
+ return link;
+ }
+ var TASK_CHECKLIST_STATUS = {
+ implemented: ['✓', 'viewer.passport.taskChecklist.status.implemented'],
+ 'needs-review': ['?', 'viewer.passport.taskChecklist.status.needsReview'],
+ pending: ['○', 'viewer.passport.taskChecklist.status.pending']
+ };
+ // Read-only: authored statuses and verified task sources, never edited here.
+ function renderTaskChecklist(id) {
+ if (!taskChecklistSection) return;
+ taskChecklistSection.open = false;
+ taskChecklistTasks.textContent = '';
+ var tasks = Archify.taskChecklists ? Archify.taskChecklists.node(id) : [];
+ var repository = Archify.sourceEvidence.repository();
+ taskChecklistSection.hidden = !tasks.length;
+ tasks.forEach(function (task, index) {
+ var item = document.createElement('li');
+ item.className = 'semantic-passport-task-checklist-task';
+ item.setAttribute('data-status', task.status);
+ var status = TASK_CHECKLIST_STATUS[task.status] || TASK_CHECKLIST_STATUS.pending;
+ var mark = document.createElement('span');
+ mark.className = 'semantic-passport-task-checklist-mark';
+ mark.setAttribute('role', 'img');
+ mark.setAttribute('aria-label', viewerText(status[1]));
+ mark.textContent = status[0];
+ var title = document.createElement('strong');
+ title.textContent = task.title;
+ item.appendChild(mark);
+ item.appendChild(title);
+ if (task.criteria) {
+ var criteria = document.createElement('p');
+ criteria.textContent = task.criteria;
+ item.appendChild(criteria);
}
- var name = document.createElement('strong');
- name.textContent = source.label || source.path.split('/').pop() || source.path;
- var location = document.createElement('code');
- location.textContent = source.line
- ? 'L' + source.line + (source.endLine && source.endLine !== source.line ? '–' + source.endLine : '') + (source.href ? ' ↗' : '')
- : source.href ? viewerText('viewer.passport.source.openLink') : '';
- var sourcePath = document.createElement('small');
- sourcePath.textContent = source.path;
- link.appendChild(name);
- link.appendChild(location);
- link.appendChild(sourcePath);
- evidenceLinks.appendChild(link);
+ Archify.sourceEvidence.task(id, index).forEach(function (source) { item.appendChild(sourceLink(source, repository)); });
+ taskChecklistTasks.appendChild(item);
+ });
+ taskChecklistCount.textContent = viewerText('viewer.passport.taskChecklist.count', {
+ implemented: tasks.filter(function (task) { return task.status === 'implemented'; }).length,
+ total: tasks.length
});
- evidence.hidden = false;
}
function explanationFor(id) {
return Archify.explanations && typeof Archify.explanations.node === 'function' ? Archify.explanations.node(id) : null;
@@ -9332,6 +9457,7 @@
Tool boundary
semanticId.hidden = false;
renderDrilldowns(node);
renderSourceEvidence(id);
+ renderTaskChecklist(id);
}
/**
* 仅允许同目录文件名.html(可选 ?query / #fragment)。
@@ -10390,6 +10516,10 @@
Tool boundary
evidenceLinks.textContent = '';
repositoryLink.removeAttribute('href');
repositoryLink.textContent = '';
+ if (taskChecklistSection) {
+ taskChecklistSection.hidden = true;
+ taskChecklistTasks.textContent = '';
+ }
summary.textContent = '';
reachSection.hidden = true;
upstreamCount.textContent = '0';
@@ -10572,6 +10702,7 @@
Tool boundary
clearBtn.addEventListener('click', function () { clear({ restoreFocus: true }); });
copyBtn.addEventListener('click', copyFocusLink);
if (explainBtn) explainBtn.addEventListener('click', function (event) { event.stopPropagation(); toggleExplanation(); });
+ if (taskChecklistSection) taskChecklistSection.addEventListener('toggle', function () { if (!chip.hidden) requestLensPlacement(); });
if (explanationBack) {
explanationBack.addEventListener('click', function (event) { event.stopPropagation(); closeExplanation(); });
}
diff --git a/examples/checkout-platform-delta.html b/examples/checkout-platform-delta.html
index fe40b814e..dfd1db60b 100644
--- a/examples/checkout-platform-delta.html
+++ b/examples/checkout-platform-delta.html
@@ -1914,6 +1914,55 @@
color: var(--text-faint);
font-size: 0.5625rem;
}
+ /* Authored task checklist: read-only, collapsed to one counted line. */
+ .semantic-passport-task-checklist {
+ margin-top: 0.52rem;
+ padding-top: 0.48rem;
+ border-top: 1px solid color-mix(in srgb, var(--toolbar-border) 76%, transparent);
+ }
+ .semantic-passport-task-checklist > summary { cursor: pointer; }
+ .semantic-passport-task-checklist-count {
+ color: var(--text-muted);
+ font-size: 0.5625rem;
+ }
+ .semantic-passport-task-checklist-tasks {
+ display: grid;
+ gap: 0.32rem;
+ max-height: min(16rem, 40vh);
+ margin: 0.45rem 0 0;
+ padding: 0.15rem;
+ overflow: auto;
+ overscroll-behavior: contain;
+ scrollbar-width: thin;
+ scrollbar-color: var(--frontend-stroke) transparent;
+ list-style: none;
+ }
+ .semantic-passport-task-checklist-task {
+ display: grid;
+ grid-template-columns: 0.9rem minmax(0, 1fr);
+ gap: 0.12rem 0.3rem;
+ }
+ .semantic-passport-task-checklist-mark {
+ color: var(--text-faint);
+ font-size: 0.625rem;
+ font-weight: 800;
+ text-align: center;
+ }
+ .semantic-passport-task-checklist-task[data-status="implemented"] .semantic-passport-task-checklist-mark { color: var(--frontend-stroke); }
+ .semantic-passport-task-checklist-task[data-status="needs-review"] .semantic-passport-task-checklist-mark { color: var(--text-muted); }
+ .semantic-passport-task-checklist-task > strong {
+ color: var(--text-muted);
+ font-size: 0.6875rem;
+ overflow-wrap: anywhere;
+ }
+ .semantic-passport-task-checklist-task p,
+ .semantic-passport-task-checklist-task .semantic-passport-source { grid-column: 2; }
+ .semantic-passport-task-checklist-task p {
+ margin: 0;
+ color: var(--text-faint);
+ font-size: 0.5625rem;
+ overflow-wrap: anywhere;
+ }
.semantic-passport-reach {
display: grid;
gap: 0.28rem;
@@ -1922,7 +1971,8 @@
border-top: 1px solid color-mix(in srgb, var(--toolbar-border) 76%, transparent);
}
.semantic-passport-reach[hidden] { display: none; }
- .semantic-passport-reach-label {
+ .semantic-passport-reach-label,
+ .semantic-passport-task-checklist-label {
color: var(--text-faint);
font-size: 0.5rem;
font-weight: 800;
@@ -1990,8 +2040,10 @@
scrollbar-width: thin;
scrollbar-color: var(--frontend-stroke) transparent;
}
- .relationship-lens-list::-webkit-scrollbar { width: 4px; }
- .relationship-lens-list::-webkit-scrollbar-thumb {
+ .relationship-lens-list::-webkit-scrollbar,
+ .semantic-passport-task-checklist-tasks::-webkit-scrollbar { width: 4px; }
+ .relationship-lens-list::-webkit-scrollbar-thumb,
+ .semantic-passport-task-checklist-tasks::-webkit-scrollbar-thumb {
border-radius: 999px;
background: color-mix(in srgb, var(--frontend-stroke) 68%, transparent);
}
@@ -2493,8 +2545,9 @@
as are dark themes and presets that already pass. Zero counts stay faint
on purpose, so they are excluded from the darker rule. Route-target
finders keep their --security-stroke status color, so they are excluded
- too. */
+ too. Task checklist done marks are the same kind of text. */
html[data-theme="light"] .semantic-passport-meta [data-passport="kind"],
+ html[data-theme="light"] .semantic-passport-task-checklist-task[data-status="implemented"] .semantic-passport-task-checklist-mark,
html[data-theme="light"] .node-finder:not([data-context="route-target"]) .node-finder-result em:not([data-zero]) {
color: var(--reader-accent, var(--frontend-stroke));
}
@@ -7214,6 +7267,55 @@
color: var(--text-faint);
font-size: 0.5625rem;
}
+ /* Authored task checklist: read-only, collapsed to one counted line. */
+ .semantic-passport-task-checklist {
+ margin-top: 0.52rem;
+ padding-top: 0.48rem;
+ border-top: 1px solid color-mix(in srgb, var(--toolbar-border) 76%, transparent);
+ }
+ .semantic-passport-task-checklist > summary { cursor: pointer; }
+ .semantic-passport-task-checklist-count {
+ color: var(--text-muted);
+ font-size: 0.5625rem;
+ }
+ .semantic-passport-task-checklist-tasks {
+ display: grid;
+ gap: 0.32rem;
+ max-height: min(16rem, 40vh);
+ margin: 0.45rem 0 0;
+ padding: 0.15rem;
+ overflow: auto;
+ overscroll-behavior: contain;
+ scrollbar-width: thin;
+ scrollbar-color: var(--frontend-stroke) transparent;
+ list-style: none;
+ }
+ .semantic-passport-task-checklist-task {
+ display: grid;
+ grid-template-columns: 0.9rem minmax(0, 1fr);
+ gap: 0.12rem 0.3rem;
+ }
+ .semantic-passport-task-checklist-mark {
+ color: var(--text-faint);
+ font-size: 0.625rem;
+ font-weight: 800;
+ text-align: center;
+ }
+ .semantic-passport-task-checklist-task[data-status="implemented"] .semantic-passport-task-checklist-mark { color: var(--frontend-stroke); }
+ .semantic-passport-task-checklist-task[data-status="needs-review"] .semantic-passport-task-checklist-mark { color: var(--text-muted); }
+ .semantic-passport-task-checklist-task > strong {
+ color: var(--text-muted);
+ font-size: 0.6875rem;
+ overflow-wrap: anywhere;
+ }
+ .semantic-passport-task-checklist-task p,
+ .semantic-passport-task-checklist-task .semantic-passport-source { grid-column: 2; }
+ .semantic-passport-task-checklist-task p {
+ margin: 0;
+ color: var(--text-faint);
+ font-size: 0.5625rem;
+ overflow-wrap: anywhere;
+ }
.semantic-passport-reach {
display: grid;
gap: 0.28rem;
@@ -7222,7 +7324,8 @@
border-top: 1px solid color-mix(in srgb, var(--toolbar-border) 76%, transparent);
}
.semantic-passport-reach[hidden] { display: none; }
- .semantic-passport-reach-label {
+ .semantic-passport-reach-label,
+ .semantic-passport-task-checklist-label {
color: var(--text-faint);
font-size: 0.5rem;
font-weight: 800;
@@ -7290,8 +7393,10 @@
scrollbar-width: thin;
scrollbar-color: var(--frontend-stroke) transparent;
}
- .relationship-lens-list::-webkit-scrollbar { width: 4px; }
- .relationship-lens-list::-webkit-scrollbar-thumb {
+ .relationship-lens-list::-webkit-scrollbar,
+ .semantic-passport-task-checklist-tasks::-webkit-scrollbar { width: 4px; }
+ .relationship-lens-list::-webkit-scrollbar-thumb,
+ .semantic-passport-task-checklist-tasks::-webkit-scrollbar-thumb {
border-radius: 999px;
background: color-mix(in srgb, var(--frontend-stroke) 68%, transparent);
}
@@ -7793,8 +7898,9 @@
as are dark themes and presets that already pass. Zero counts stay faint
on purpose, so they are excluded from the darker rule. Route-target
finders keep their --security-stroke status color, so they are excluded
- too. */
+ too. Task checklist done marks are the same kind of text. */
html[data-theme="light"] .semantic-passport-meta [data-passport="kind"],
+ html[data-theme="light"] .semantic-passport-task-checklist-task[data-status="implemented"] .semantic-passport-task-checklist-mark,
html[data-theme="light"] .node-finder:not([data-context="route-target"]) .node-finder-result em:not([data-zero]) {
color: var(--reader-accent, var(--frontend-stroke));
}
@@ -10542,7 +10648,8 @@
- <script id="archify-i18n-data" type="application/json">{"locale":"en","messages":{"viewer.kind.frontend":"Frontend","viewer.kind.backend":"Backend","viewer.kind.database":"Database","viewer.kind.cloud":"Cloud","viewer.kind.security":"Security","viewer.kind.messagebus":"Message bus","viewer.kind.external":"External","viewer.kind.neutral":"Neutral","viewer.kind.node":"Node","viewer.kind.start":"Start","viewer.kind.active":"Active","viewer.kind.waiting":"Waiting","viewer.kind.decision":"Decision","viewer.kind.success":"Success","viewer.kind.failure":"Failure","viewer.toolbar.actions":"Diagram actions","viewer.theme.toggle.title":"Toggle theme (T)","viewer.theme.toggle":"Toggle color theme","viewer.theme.dark":"Dark","viewer.theme.light":"Light","viewer.preset.choose.title":"Choose visual style (S cycles)","viewer.preset.choose":"Choose visual style","viewer.preset.style":"Style","viewer.preset.menu":"Visual style","viewer.preset.identity":"Visual identity","viewer.preset.cycles":"to cycle","viewer.preset.classic":"Classic","viewer.preset.classic.short":"Classic","viewer.preset.classic.hint":"Stable technical default","viewer.preset.flow":"Signal Flow","viewer.preset.flow.short":"Flow","viewer.preset.flow.hint":"Motion-forward presentation","viewer.preset.blueprint":"Blueprint","viewer.preset.blueprint.hint":"Engineering review","viewer.preset.editorial":"Editorial","viewer.preset.editorial.hint":"Publication and launch notes","viewer.preset.badge.signalFlow":"SIGNAL FLOW","viewer.preset.badge.blueprint":"BLUEPRINT / REV 01","viewer.preset.badge.editorial":"EDITORIAL / FIELD NOTE","viewer.preset.badge.editorialPlate":"ARCHIFY / PLATE 04","viewer.preset.current":"Visual style: {style}. Choose visual style","viewer.motion.live":"Live","viewer.motion.still":"Still","viewer.motion.pause":"Pause motion","viewer.motion.resume":"Resume motion","viewer.motion.reduced":"Motion paused by reduced-motion preference","viewer.motion.hidden":"Motion paused while this page is hidden","viewer.motion.yielding":"Pause motion; currently yielding to {owner}","viewer.motion.yielding.title":"Live preview enabled · yielding to {owner}","viewer.owner.route":"Route Probe","viewer.owner.lens":"Semantic Lens","viewer.owner.relationship":"Relationship Preview","viewer.owner.intent":"Intent Trace","viewer.owner.focus":"semantic focus","viewer.owner.legend":"legend preview","viewer.owner.reader":"reader interaction","viewer.present.enter":"Enter presentation stage","viewer.present.enter.title":"Presentation stage (F)","viewer.present.exit":"Exit presentation stage","viewer.present.exit.title":"Exit presentation stage (F or Escape)","viewer.present.present":"Present","viewer.present.exit.label":"Exit","viewer.export.button":"Export","viewer.export.button.title":"Export diagram (E)","viewer.export.diagram":"Export diagram","viewer.export.menu":"Export","viewer.export.subtitle":"Portable, clean outputs","viewer.export.share":"Share","viewer.export.shareCard":"Share Card","viewer.export.routeShareCard":"Route Share Card","viewer.export.reachShareCard":"Reach Share Card","viewer.export.copyDiagram":"Copy diagram","viewer.export.clipboardPng":"PNG to clipboard","viewer.export.raster":"Raster images","viewer.export.image":"Image","viewer.export.lossless":"Lossless image","viewer.export.compact":"Compact image","viewer.export.modern":"Modern image","viewer.export.vectorMotion":"Vector and motion","viewer.export.vectorMotion.heading":"Vector \u0026 motion","viewer.export.editable":"Editable vector","viewer.export.svg.auto":"SVG · Auto","viewer.export.svg.auto.hint":"Matches host theme","viewer.export.svg.light":"SVG · Light","viewer.export.svg.light.hint":"Always light","viewer.export.svg.dark":"SVG · Dark","viewer.export.svg.dark.hint":"Always dark","viewer.export.motion6s":"6s motion","viewer.export.unsupported":"Not supported by this browser","viewer.export.clipboardUnsupported":"Clipboard image write not supported by this browser","viewer.export.clipboardUnsupported.short":"Clipboard image write not supported in this browser.","viewer.export.motionUnavailable":"Motion capture unavailable in this browser","viewer.export.webmUnavailable":"WebM unavailable in this browser","viewer.export.failed":"Export failed: {message}","viewer.export.unknownVariant":"Unknown Share Card variant: {variant}","viewer.export.routeRequired":"Trace a route before exporting a Route Share Card","viewer.export.reachRequired":"Trace authored reach before exporting a Reach Share Card","viewer.export.unknown":"unknown","viewer.export.routeFailed":"Route Share Card export failed: {message}","viewer.export.reachFailed":"Reach Share Card export failed: {message}","viewer.export.copyFailed":"Copy failed: {message}","viewer.export.copiedPng":"Copied PNG to clipboard","viewer.export.downloadedRoute":"Downloaded Route Share Card","viewer.export.downloadedReach":"Downloaded Reach Share Card","viewer.export.downloadedWebm":"Downloaded WebM","viewer.export.recording":"Recording 6 seconds of motion…","viewer.export.card.routeSummary.one":"Path: {source} → {target} · {count} step","viewer.export.card.routeSummary.other":"Path: {source} → {target} · {count} steps","viewer.export.card.reachSummary":"Authored {direction} from {origin} · {nodes} · {links} · max {hops}","viewer.export.card.node.one":"{count} node","viewer.export.card.node.other":"{count} nodes","viewer.export.card.link.one":"{count} link","viewer.export.card.link.other":"{count} links","viewer.export.card.hop.one":"{count} hop","viewer.export.card.hop.other":"{count} hops","viewer.export.card.routeBadge":"ARCHIFY · ROUTE · {hops}","viewer.export.card.reachBadge":"ARCHIFY · {direction} REACH","viewer.export.direction.upstream":"Upstream","viewer.export.direction.downstream":"Downstream","viewer.export.error.canvasUnavailable":"Canvas unavailable for {label}","viewer.export.error.contextUnavailable":"2D canvas context unavailable for {label}","viewer.export.error.toBlobUnavailable":"canvas.toBlob unavailable for {label}","viewer.export.error.toBlobNull":"canvas.toBlob returned no data for {label}","viewer.export.error.variantsCombined":"Share Card variants cannot be combined","viewer.export.error.viewerState":"Share Card export could not remove temporary viewer state","viewer.export.error.routeState":"Route Card export could not preserve the resolved route safely","viewer.export.error.reachState":"Reach Card export could not preserve authored reach safely","viewer.export.error.webmRequirements":"WebM motion export requires a trace animation and browser MediaRecorder support","viewer.export.error.mediaRecorder":"MediaRecorder failed","viewer.export.error.emptyWebm":"MediaRecorder produced an empty WebM","viewer.export.error.webmBackground":"SVG background could not be loaded for WebM export","viewer.guide.title":"Explore this system","viewer.focus.selectedNodes":"{count} selected nodes","viewer.guide.eyebrow":"Diagram guide","viewer.guide.close":"Close diagram guide","viewer.guide.inspecting":"Inspecting compiled semantics","viewer.guide.actions":"Diagram exploration actions","viewer.guide.find":"Find any node","viewer.guide.find.hint":"Search labels, responsibilities, kinds, and stable IDs.","viewer.guide.route":"Trace a route","viewer.guide.route.aria":"Trace a directed route","viewer.guide.route.hint":"Ask how two semantic nodes connect in authored direction.","viewer.guide.map":"See the whole system","viewer.guide.map.hint":"Open Semantic Radar with a live viewport and stable nodes.","viewer.guide.lens":"Compare semantic kinds","viewer.guide.lens.hint":"Count roles, reveal their traffic, and compare direct authored links.","viewer.guide.present":"Enter Presentation Stage","viewer.guide.present.hint":"Give the live diagram the viewport without changing export.","viewer.guide.shortcuts":"Additional keyboard shortcuts","viewer.guide.shortcut.export":"Export","viewer.guide.shortcut.theme":"Theme","viewer.guide.shortcut.style":"Style","viewer.guide.shortcut.reset":"Reset","viewer.guide.shortcut.zoomIn":"Zoom in","viewer.guide.shortcut.zoomOut":"Zoom out","viewer.guide.shortcut.close":"Close","viewer.guide.credit":"Find Archify useful? A star on GitHub helps","viewer.guide.star":"Star","viewer.guide.facts":"{nodes} · {relationships}","viewer.guide.fact.node.one":"{count} semantic node","viewer.guide.fact.node.other":"{count} semantic nodes","viewer.guide.fact.relationship.one":"{count} relationship","viewer.guide.fact.relationship.other":"{count} relationships","viewer.guide.open":"Open diagram guide","viewer.finder.title":"Find a node","viewer.finder.close":"Close node finder","viewer.finder.placeholder":"Search labels or IDs","viewer.finder.search":"Search diagram nodes","viewer.finder.results":"Diagram nodes","viewer.finder.empty":"No matching nodes","viewer.finder.result.focus":"Focus {label}","viewer.finder.result.routeStart":"Choose {label} as route start","viewer.finder.result.routeTarget":"Choose {label} as route destination, {links}","viewer.finder.status.empty":"No matching nodes","viewer.finder.status.count.one":"{count} matching node","viewer.finder.status.count.other":"{count} matching nodes","viewer.finder.noun.nodes":"nodes","viewer.finder.link.one":"{count} link","viewer.finder.link.other":"{count} links","viewer.finder.result.focus.one":"Focus {label}, {count} related connection","viewer.finder.result.focus.other":"Focus {label}, {count} related connections","viewer.finder.status.filtered":"{visible} of {available} {noun}","viewer.finder.status.all":"{available} {noun}","viewer.passport.eyebrow":"Semantic passport","viewer.passport.metadata":"Node metadata","viewer.passport.evidence":"Verified source evidence","viewer.passport.verified":"Verified source","viewer.passport.verificationScope":"Verified against local Git at the pinned revision. Remote access has not been checked.","viewer.passport.reach":"Authored reach","viewer.passport.reach.trace":"Trace authored reachability","viewer.passport.upstream":"Upstream","viewer.passport.downstream":"Downstream","viewer.passport.upstream.trace":"Trace upstream authored reachability","viewer.passport.downstream.trace":"Trace downstream authored reachability","viewer.passport.close":"Close semantic passport","viewer.passport.move":"Move semantic passport. Drag, use arrow keys, or press Home to reset.","viewer.passport.copy":"Copy link","viewer.passport.copy.focus":"Copy link to focused node","viewer.passport.relations":"Relations","viewer.passport.relations.show":"Show connected relationships","viewer.passport.relations.hide":"Hide connected relationships","viewer.passport.relations.list":"Connected relationships","viewer.passport.copyRelation":"Copy relation","viewer.passport.copyNode":"Copy node","viewer.passport.copyPinned":"Copy link to pinned relationship","viewer.passport.copySource":"Copy link to source node","viewer.passport.copy.focused.success":"Focused node link copied","viewer.passport.copy.pinned.success":"Pinned relationship link copied","viewer.passport.copy.focused.failed":"Could not copy focused node link","viewer.passport.copy.pinned.failed":"Could not copy pinned relationship link","viewer.passport.relationship.none":"No connected relationships","viewer.passport.relationship.count.one":"{count} relation","viewer.passport.relationship.count.other":"{count} relations","viewer.passport.relationship.show.one":"Show {count} connected relationship","viewer.passport.relationship.show.other":"Show {count} connected relationships","viewer.passport.relationship.summary":"{out} outgoing · {in} incoming{loops}","viewer.passport.relationship.loops":" · {count} loop","viewer.passport.relationship.explorer":"Direct relationship explorer","viewer.passport.relationship.help":"Use arrow keys to explore relationships. Press Enter or Space to pin details; Escape clears.","viewer.passport.relationship.loopsBack":"loops back","viewer.passport.relationship.connectsTo":"connects to","viewer.passport.relationship.connectsFrom":"connects from","viewer.passport.relationship.pinned":"Pinned relationship · {from} → {to} · {label}","viewer.passport.relationship.inspect":"Inspect relationship {index} of {total}: {from} to {to}, {label}. Press Enter for details.","viewer.passport.relationship.group.out":"Outgoing","viewer.passport.relationship.group.in":"Incoming","viewer.passport.relationship.group.loop":"Self loops","viewer.passport.relationship.row":"{group}: {relationship}, {neighbor}","viewer.passport.relationship.direction.out":"OUT →","viewer.passport.relationship.direction.in":"← IN","viewer.passport.relationship.direction.loop":"LOOP","viewer.passport.sourceCount.one":"{count} verified source reference","viewer.passport.sourceCount.other":"{count} verified source references","viewer.passport.repository.open":"Open verified repository revision {revision}","viewer.passport.source.open":"Open verified source {path} at revision {revision}","viewer.passport.source.openLink":"Open ↗","viewer.passport.reach.upstream.one":"Trace {count} upstream authored node","viewer.passport.reach.upstream.other":"Trace {count} upstream authored nodes","viewer.passport.reach.downstream.one":"Trace {count} downstream authored node","viewer.passport.reach.downstream.other":"Trace {count} downstream authored nodes","viewer.passport.reach.noUpstream":"No upstream authored nodes","viewer.passport.reach.noDownstream":"No downstream authored nodes","viewer.passport.reach.status":"{direction} · {nodes} nodes · {links} links · max {hops} hops","viewer.route.eyebrow":"Path","viewer.route.start":"Click where the path starts","viewer.route.start.find":"Find start","viewer.route.start.find.aria":"Find a route start","viewer.route.copy":"Copy link","viewer.route.copy.aria":"Copy link to traced route","viewer.route.clear":"Clear","viewer.route.clear.aria":"Clear route probe","viewer.route.traced":"Path","viewer.route.pickTwo":"Click two nodes on the diagram","viewer.route.pickOne":"Click a node on the diagram","viewer.route.controls":"Route journey controls","viewer.route.previous":"Previous route position","viewer.route.play":"Play route journey","viewer.route.pause":"Pause route journey","viewer.route.replay":"Replay route journey","viewer.route.next":"Next route position","viewer.route.journey":"Journey","viewer.route.pause.label":"Pause","viewer.route.replay.label":"Replay","viewer.route.overview":"Overview","viewer.route.overview.aria":"Show complete route overview","viewer.route.instructions":"Click a start, then an end. Paths follow the arrows.","viewer.route.destination":"Where does the path from {label} end?","viewer.route.destination.find":"Find target","viewer.route.destination.find.aria":"Find a reachable route destination","viewer.route.differentDestination":"Choose a different destination","viewer.route.distinct":"Pick two different nodes.","viewer.route.unreachable":"Cannot reach {label} from here","viewer.route.unreachable.detail":"{target} cannot be reached from {source} along the arrows. Pick a highlighted node.","viewer.route.start.instructions":"Next, pick the end. Only nodes you can reach will light up.","viewer.route.copy.success":"Traced route link copied","viewer.route.copy.failed":"Could not copy traced route link","viewer.route.position":"Route position {index} of {total}: {label}","viewer.route.step":"Step {index} of {total} · {phase} · {label}","viewer.route.motionRequired":"Automatic journey requires Live motion","viewer.route.trigger.clear":"Clear traced route","viewer.route.overview.status":"{nodes} · {hops} · shortest path","viewer.route.overview.node.one":"{count} node","viewer.route.overview.node.other":"{count} nodes","viewer.route.overview.hop.one":"{count} step","viewer.route.overview.hop.other":"{count} steps","viewer.route.phase.playing":"Playing","viewer.route.phase.complete":"Complete","viewer.route.phase.inspecting":"Inspecting","viewer.route.destination.count.one":"{count} reachable node is highlighted. Click it.","viewer.route.destination.count.other":"{count} reachable nodes are highlighted. Click one.","viewer.route.noOutgoing":"No arrows lead out of this node. Clear it and pick another start.","viewer.route.result.title":"{source} to {target}","viewer.route.finder.source.title":"Choose route start","viewer.route.finder.source.placeholder":"Search route sources","viewer.route.finder.source.empty":"No matching route sources","viewer.route.finder.source.results":"Nodes that can start a route","viewer.route.finder.source.noun":"route sources","viewer.route.finder.source.badge":"start","viewer.route.finder.target.title":"Destination from {label}","viewer.route.finder.target.placeholder":"Search reachable destinations","viewer.route.finder.target.empty":"No matching reachable destinations","viewer.route.finder.target.results":"Reachable route destinations","viewer.route.finder.target.noun":"reachable destinations","viewer.route.hop.one":"{count} hop","viewer.route.hop.other":"{count} hops","viewer.lens.eyebrow":"Semantic lens","viewer.lens.title":"Compare system roles","viewer.lens.close":"Close semantic lens","viewer.lens.instruction":"Choose up to two semantic kinds. One reveals its real traffic; two compare only direct authored relationships.","viewer.lens.kinds":"Semantic kinds","viewer.lens.choose":"Choose a kind to inspect its nodes and touching relationships.","viewer.lens.copy":"Copy link to semantic lens","viewer.lens.clear":"Clear semantic lens","viewer.lens.open":"Open semantic lens","viewer.lens.openActive":"Open active semantic lens","viewer.lens.legend":"Semantic legend","viewer.lens.legend.inspect.one":"Inspect {label}, {count} node","viewer.lens.legend.inspect.other":"Inspect {label}, {count} nodes","viewer.lens.kind.count.one":"{label}, {count} node","viewer.lens.kind.count.other":"{label}, {count} nodes","viewer.lens.compare.one":"{first} → {second}: {forward} · {second} → {first}: {reverse} · {count} direct relationship","viewer.lens.compare.other":"{first} → {second}: {forward} · {second} → {first}: {reverse} · {count} direct relationships","viewer.lens.single":"{nodes} · {relationships} · connected peers remain visible","viewer.lens.node.one":"{count} {label} node","viewer.lens.node.other":"{count} {label} nodes","viewer.lens.relationship.one":"{count} touching relationship","viewer.lens.relationship.other":"{count} touching relationships","viewer.radar.title":"Semantic radar","viewer.radar.building":"Building overview","viewer.radar.openFull":"Open full semantic radar","viewer.radar.open":"Open radar","viewer.radar.close":"Close semantic radar","viewer.radar.surface":"Diagram overview. Click a node to focus it, or use arrow keys to pan.","viewer.radar.click":"Click node","viewer.radar.drag":"Drag to pan","viewer.radar.space":"Semantic radar needs more MAP space.","viewer.radar.nodes":"Semantic diagram radar nodes","viewer.radar.focus":"Focus {label} from Semantic Radar","viewer.radar.status":"{count} nodes · {viewport}","viewer.radar.fullMap":"{count} nodes · full map","viewer.radar.compacted":"Radar compacted to avoid covering the Semantic Passport or MAP controls.","viewer.radar.cancelWaiting":"Cancel semantic radar waiting for more MAP space","viewer.radar.needsSpace":"Semantic radar needs more visible MAP space","viewer.radar.viewport.full":"full map","viewer.radar.viewport.width":"{percent}% width","viewer.radar.viewport.scale":"{percent}% viewport","viewer.nav.controls":"Diagram view controls","viewer.nav.route":"Trace a directed route","viewer.nav.route.title":"Trace route (R)","viewer.nav.route.short":"ROUTE","viewer.nav.radar":"Open semantic radar","viewer.nav.radar.title":"Semantic radar (M)","viewer.nav.radar.short":"MAP","viewer.nav.lens":"Open semantic lens","viewer.nav.lens.title":"Semantic lens (L)","viewer.nav.lens.short":"LENS","viewer.nav.find":"Find a node","viewer.nav.find.title":"Find a node (/)","viewer.outline.title":"Node index","viewer.rail.show":"Notes \u0026 index","viewer.rail.controls":"Side panel","viewer.rail.collapse":"Collapse side panel","viewer.rail.bottom":"Move panel below the diagram","viewer.rail.right":"Move panel beside the diagram","viewer.nav.guide":"Open diagram guide","viewer.nav.guide.title":"Diagram guide (?)","viewer.nav.zoomOut":"Zoom out","viewer.nav.zoomOut.title":"Zoom out (-)","viewer.nav.reset":"Reset diagram view","viewer.nav.reset.title":"Reset view (0)","viewer.nav.read":"READ","viewer.nav.zoomIn":"Zoom in","viewer.nav.zoomIn.title":"Zoom in (+)","viewer.nav.camera":"{hint}. Reset diagram view","viewer.nav.camera.title":"{semantic}{hint} · reset view (0)","viewer.nav.camera.semantic":"Semantic camera active · ","viewer.nav.level.map":"MAP","viewer.nav.level.read":"READ","viewer.nav.level.full":"FULL","viewer.nav.level.auto":"AUTO","viewer.nav.detail.map":"Zoom in to reveal relationship labels and node context","viewer.nav.detail.read":"Zoom in again to reveal tags and annotations","viewer.nav.detail.full":"Full diagram detail","viewer.intent.summary":"{label}. {out} outgoing, {in} incoming{loops}. {total} connections. Press Enter for details.","viewer.intent.loops":", {count} self loop","viewer.common.copied":"Copied","viewer.common.copyFailed":"Copy failed","viewer.common.copyLink":"Copy link","viewer.common.clear":"Clear","viewer.common.close":"Close","viewer.passport.explain":"Explain","viewer.passport.explain.show":"Show the explanation for this node","viewer.passport.explanation":"Explanation","viewer.passport.explanation.back":"{label} explanation. Activate to return to the passport","viewer.passport.explanation.backLink":"Back to passport","viewer.passport.drilldowns":"Child diagrams","viewer.passport.drilldowns.list":"Open a child diagram","viewer.passport.drilldowns.hint":"Ctrl+click or double-click opens the first entry","viewer.passport.drilldowns.open":"Open {label}","viewer.passport.back":"Back to parent","viewer.passport.back.aria":"Return to the parent diagram","viewer.nav.back":"Back","viewer.nav.back.title":"Return to the parent diagram"}}</script>
+
+ <script id="archify-i18n-data" type="application/json">{"locale":"en","messages":{"viewer.kind.frontend":"Frontend","viewer.kind.backend":"Backend","viewer.kind.database":"Database","viewer.kind.cloud":"Cloud","viewer.kind.security":"Security","viewer.kind.messagebus":"Message bus","viewer.kind.external":"External","viewer.kind.neutral":"Neutral","viewer.kind.node":"Node","viewer.kind.start":"Start","viewer.kind.active":"Active","viewer.kind.waiting":"Waiting","viewer.kind.decision":"Decision","viewer.kind.success":"Success","viewer.kind.failure":"Failure","viewer.toolbar.actions":"Diagram actions","viewer.theme.toggle.title":"Toggle theme (T)","viewer.theme.toggle":"Toggle color theme","viewer.theme.dark":"Dark","viewer.theme.light":"Light","viewer.preset.choose.title":"Choose visual style (S cycles)","viewer.preset.choose":"Choose visual style","viewer.preset.style":"Style","viewer.preset.menu":"Visual style","viewer.preset.identity":"Visual identity","viewer.preset.cycles":"to cycle","viewer.preset.classic":"Classic","viewer.preset.classic.short":"Classic","viewer.preset.classic.hint":"Stable technical default","viewer.preset.flow":"Signal Flow","viewer.preset.flow.short":"Flow","viewer.preset.flow.hint":"Motion-forward presentation","viewer.preset.blueprint":"Blueprint","viewer.preset.blueprint.hint":"Engineering review","viewer.preset.editorial":"Editorial","viewer.preset.editorial.hint":"Publication and launch notes","viewer.preset.badge.signalFlow":"SIGNAL FLOW","viewer.preset.badge.blueprint":"BLUEPRINT / REV 01","viewer.preset.badge.editorial":"EDITORIAL / FIELD NOTE","viewer.preset.badge.editorialPlate":"ARCHIFY / PLATE 04","viewer.preset.current":"Visual style: {style}. Choose visual style","viewer.motion.live":"Live","viewer.motion.still":"Still","viewer.motion.pause":"Pause motion","viewer.motion.resume":"Resume motion","viewer.motion.reduced":"Motion paused by reduced-motion preference","viewer.motion.hidden":"Motion paused while this page is hidden","viewer.motion.yielding":"Pause motion; currently yielding to {owner}","viewer.motion.yielding.title":"Live preview enabled · yielding to {owner}","viewer.owner.route":"Route Probe","viewer.owner.lens":"Semantic Lens","viewer.owner.relationship":"Relationship Preview","viewer.owner.intent":"Intent Trace","viewer.owner.focus":"semantic focus","viewer.owner.legend":"legend preview","viewer.owner.reader":"reader interaction","viewer.present.enter":"Enter presentation stage","viewer.present.enter.title":"Presentation stage (F)","viewer.present.exit":"Exit presentation stage","viewer.present.exit.title":"Exit presentation stage (F or Escape)","viewer.present.present":"Present","viewer.present.exit.label":"Exit","viewer.export.button":"Export","viewer.export.button.title":"Export diagram (E)","viewer.export.diagram":"Export diagram","viewer.export.menu":"Export","viewer.export.subtitle":"Portable, clean outputs","viewer.export.share":"Share","viewer.export.shareCard":"Share Card","viewer.export.routeShareCard":"Route Share Card","viewer.export.reachShareCard":"Reach Share Card","viewer.export.copyDiagram":"Copy diagram","viewer.export.clipboardPng":"PNG to clipboard","viewer.export.raster":"Raster images","viewer.export.image":"Image","viewer.export.lossless":"Lossless image","viewer.export.compact":"Compact image","viewer.export.modern":"Modern image","viewer.export.vectorMotion":"Vector and motion","viewer.export.vectorMotion.heading":"Vector \u0026 motion","viewer.export.editable":"Editable vector","viewer.export.svg.auto":"SVG · Auto","viewer.export.svg.auto.hint":"Matches host theme","viewer.export.svg.light":"SVG · Light","viewer.export.svg.light.hint":"Always light","viewer.export.svg.dark":"SVG · Dark","viewer.export.svg.dark.hint":"Always dark","viewer.export.motion6s":"6s motion","viewer.export.unsupported":"Not supported by this browser","viewer.export.clipboardUnsupported":"Clipboard image write not supported by this browser","viewer.export.clipboardUnsupported.short":"Clipboard image write not supported in this browser.","viewer.export.motionUnavailable":"Motion capture unavailable in this browser","viewer.export.webmUnavailable":"WebM unavailable in this browser","viewer.export.failed":"Export failed: {message}","viewer.export.unknownVariant":"Unknown Share Card variant: {variant}","viewer.export.routeRequired":"Trace a route before exporting a Route Share Card","viewer.export.reachRequired":"Trace authored reach before exporting a Reach Share Card","viewer.export.unknown":"unknown","viewer.export.routeFailed":"Route Share Card export failed: {message}","viewer.export.reachFailed":"Reach Share Card export failed: {message}","viewer.export.copyFailed":"Copy failed: {message}","viewer.export.copiedPng":"Copied PNG to clipboard","viewer.export.downloadedRoute":"Downloaded Route Share Card","viewer.export.downloadedReach":"Downloaded Reach Share Card","viewer.export.downloadedWebm":"Downloaded WebM","viewer.export.recording":"Recording 6 seconds of motion…","viewer.export.card.routeSummary.one":"Path: {source} → {target} · {count} step","viewer.export.card.routeSummary.other":"Path: {source} → {target} · {count} steps","viewer.export.card.reachSummary":"Authored {direction} from {origin} · {nodes} · {links} · max {hops}","viewer.export.card.node.one":"{count} node","viewer.export.card.node.other":"{count} nodes","viewer.export.card.link.one":"{count} link","viewer.export.card.link.other":"{count} links","viewer.export.card.hop.one":"{count} hop","viewer.export.card.hop.other":"{count} hops","viewer.export.card.routeBadge":"ARCHIFY · ROUTE · {hops}","viewer.export.card.reachBadge":"ARCHIFY · {direction} REACH","viewer.export.direction.upstream":"Upstream","viewer.export.direction.downstream":"Downstream","viewer.export.error.canvasUnavailable":"Canvas unavailable for {label}","viewer.export.error.contextUnavailable":"2D canvas context unavailable for {label}","viewer.export.error.toBlobUnavailable":"canvas.toBlob unavailable for {label}","viewer.export.error.toBlobNull":"canvas.toBlob returned no data for {label}","viewer.export.error.variantsCombined":"Share Card variants cannot be combined","viewer.export.error.viewerState":"Share Card export could not remove temporary viewer state","viewer.export.error.routeState":"Route Card export could not preserve the resolved route safely","viewer.export.error.reachState":"Reach Card export could not preserve authored reach safely","viewer.export.error.webmRequirements":"WebM motion export requires a trace animation and browser MediaRecorder support","viewer.export.error.mediaRecorder":"MediaRecorder failed","viewer.export.error.emptyWebm":"MediaRecorder produced an empty WebM","viewer.export.error.webmBackground":"SVG background could not be loaded for WebM export","viewer.guide.title":"Explore this system","viewer.focus.selectedNodes":"{count} selected nodes","viewer.guide.eyebrow":"Diagram guide","viewer.guide.close":"Close diagram guide","viewer.guide.inspecting":"Inspecting compiled semantics","viewer.guide.actions":"Diagram exploration actions","viewer.guide.find":"Find any node","viewer.guide.find.hint":"Search labels, responsibilities, kinds, and stable IDs.","viewer.guide.route":"Trace a route","viewer.guide.route.aria":"Trace a directed route","viewer.guide.route.hint":"Ask how two semantic nodes connect in authored direction.","viewer.guide.map":"See the whole system","viewer.guide.map.hint":"Open Semantic Radar with a live viewport and stable nodes.","viewer.guide.lens":"Compare semantic kinds","viewer.guide.lens.hint":"Count roles, reveal their traffic, and compare direct authored links.","viewer.guide.present":"Enter Presentation Stage","viewer.guide.present.hint":"Give the live diagram the viewport without changing export.","viewer.guide.shortcuts":"Additional keyboard shortcuts","viewer.guide.shortcut.export":"Export","viewer.guide.shortcut.theme":"Theme","viewer.guide.shortcut.style":"Style","viewer.guide.shortcut.reset":"Reset","viewer.guide.shortcut.zoomIn":"Zoom in","viewer.guide.shortcut.zoomOut":"Zoom out","viewer.guide.shortcut.close":"Close","viewer.guide.credit":"Find Archify useful? A star on GitHub helps","viewer.guide.star":"Star","viewer.guide.facts":"{nodes} · {relationships}","viewer.guide.fact.node.one":"{count} semantic node","viewer.guide.fact.node.other":"{count} semantic nodes","viewer.guide.fact.relationship.one":"{count} relationship","viewer.guide.fact.relationship.other":"{count} relationships","viewer.guide.open":"Open diagram guide","viewer.finder.title":"Find a node","viewer.finder.close":"Close node finder","viewer.finder.placeholder":"Search labels or IDs","viewer.finder.search":"Search diagram nodes","viewer.finder.results":"Diagram nodes","viewer.finder.empty":"No matching nodes","viewer.finder.result.focus":"Focus {label}","viewer.finder.result.routeStart":"Choose {label} as route start","viewer.finder.result.routeTarget":"Choose {label} as route destination, {links}","viewer.finder.status.empty":"No matching nodes","viewer.finder.status.count.one":"{count} matching node","viewer.finder.status.count.other":"{count} matching nodes","viewer.finder.noun.nodes":"nodes","viewer.finder.link.one":"{count} link","viewer.finder.link.other":"{count} links","viewer.finder.result.focus.one":"Focus {label}, {count} related connection","viewer.finder.result.focus.other":"Focus {label}, {count} related connections","viewer.finder.status.filtered":"{visible} of {available} {noun}","viewer.finder.status.all":"{available} {noun}","viewer.passport.eyebrow":"Semantic passport","viewer.passport.metadata":"Node metadata","viewer.passport.evidence":"Verified source evidence","viewer.passport.verified":"Verified source","viewer.passport.verificationScope":"Verified against local Git at the pinned revision. Remote access has not been checked.","viewer.passport.reach":"Authored reach","viewer.passport.reach.trace":"Trace authored reachability","viewer.passport.upstream":"Upstream","viewer.passport.downstream":"Downstream","viewer.passport.upstream.trace":"Trace upstream authored reachability","viewer.passport.downstream.trace":"Trace downstream authored reachability","viewer.passport.close":"Close semantic passport","viewer.passport.move":"Move semantic passport. Drag, use arrow keys, or press Home to reset.","viewer.passport.copy":"Copy link","viewer.passport.copy.focus":"Copy link to focused node","viewer.passport.relations":"Relations","viewer.passport.relations.show":"Show connected relationships","viewer.passport.relations.hide":"Hide connected relationships","viewer.passport.relations.list":"Connected relationships","viewer.passport.copyRelation":"Copy relation","viewer.passport.copyNode":"Copy node","viewer.passport.copyPinned":"Copy link to pinned relationship","viewer.passport.copySource":"Copy link to source node","viewer.passport.copy.focused.success":"Focused node link copied","viewer.passport.copy.pinned.success":"Pinned relationship link copied","viewer.passport.copy.focused.failed":"Could not copy focused node link","viewer.passport.copy.pinned.failed":"Could not copy pinned relationship link","viewer.passport.relationship.none":"No connected relationships","viewer.passport.relationship.count.one":"{count} relation","viewer.passport.relationship.count.other":"{count} relations","viewer.passport.relationship.show.one":"Show {count} connected relationship","viewer.passport.relationship.show.other":"Show {count} connected relationships","viewer.passport.relationship.summary":"{out} outgoing · {in} incoming{loops}","viewer.passport.relationship.loops":" · {count} loop","viewer.passport.relationship.explorer":"Direct relationship explorer","viewer.passport.relationship.help":"Use arrow keys to explore relationships. Press Enter or Space to pin details; Escape clears.","viewer.passport.relationship.loopsBack":"loops back","viewer.passport.relationship.connectsTo":"connects to","viewer.passport.relationship.connectsFrom":"connects from","viewer.passport.relationship.pinned":"Pinned relationship · {from} → {to} · {label}","viewer.passport.relationship.inspect":"Inspect relationship {index} of {total}: {from} to {to}, {label}. Press Enter for details.","viewer.passport.relationship.group.out":"Outgoing","viewer.passport.relationship.group.in":"Incoming","viewer.passport.relationship.group.loop":"Self loops","viewer.passport.relationship.row":"{group}: {relationship}, {neighbor}","viewer.passport.relationship.direction.out":"OUT →","viewer.passport.relationship.direction.in":"← IN","viewer.passport.relationship.direction.loop":"LOOP","viewer.passport.sourceCount.one":"{count} verified source reference","viewer.passport.sourceCount.other":"{count} verified source references","viewer.passport.repository.open":"Open verified repository revision {revision}","viewer.passport.source.open":"Open verified source {path} at revision {revision}","viewer.passport.source.openLink":"Open ↗","viewer.passport.reach.upstream.one":"Trace {count} upstream authored node","viewer.passport.reach.upstream.other":"Trace {count} upstream authored nodes","viewer.passport.reach.downstream.one":"Trace {count} downstream authored node","viewer.passport.reach.downstream.other":"Trace {count} downstream authored nodes","viewer.passport.reach.noUpstream":"No upstream authored nodes","viewer.passport.reach.noDownstream":"No downstream authored nodes","viewer.passport.reach.status":"{direction} · {nodes} nodes · {links} links · max {hops} hops","viewer.route.eyebrow":"Path","viewer.route.start":"Click where the path starts","viewer.route.start.find":"Find start","viewer.route.start.find.aria":"Find a route start","viewer.route.copy":"Copy link","viewer.route.copy.aria":"Copy link to traced route","viewer.route.clear":"Clear","viewer.route.clear.aria":"Clear route probe","viewer.route.traced":"Path","viewer.route.pickTwo":"Click two nodes on the diagram","viewer.route.pickOne":"Click a node on the diagram","viewer.route.controls":"Route journey controls","viewer.route.previous":"Previous route position","viewer.route.play":"Play route journey","viewer.route.pause":"Pause route journey","viewer.route.replay":"Replay route journey","viewer.route.next":"Next route position","viewer.route.journey":"Journey","viewer.route.pause.label":"Pause","viewer.route.replay.label":"Replay","viewer.route.overview":"Overview","viewer.route.overview.aria":"Show complete route overview","viewer.route.instructions":"Click a start, then an end. Paths follow the arrows.","viewer.route.destination":"Where does the path from {label} end?","viewer.route.destination.find":"Find target","viewer.route.destination.find.aria":"Find a reachable route destination","viewer.route.differentDestination":"Choose a different destination","viewer.route.distinct":"Pick two different nodes.","viewer.route.unreachable":"Cannot reach {label} from here","viewer.route.unreachable.detail":"{target} cannot be reached from {source} along the arrows. Pick a highlighted node.","viewer.route.start.instructions":"Next, pick the end. Only nodes you can reach will light up.","viewer.route.copy.success":"Traced route link copied","viewer.route.copy.failed":"Could not copy traced route link","viewer.route.position":"Route position {index} of {total}: {label}","viewer.route.step":"Step {index} of {total} · {phase} · {label}","viewer.route.motionRequired":"Automatic journey requires Live motion","viewer.route.trigger.clear":"Clear traced route","viewer.route.overview.status":"{nodes} · {hops} · shortest path","viewer.route.overview.node.one":"{count} node","viewer.route.overview.node.other":"{count} nodes","viewer.route.overview.hop.one":"{count} step","viewer.route.overview.hop.other":"{count} steps","viewer.route.phase.playing":"Playing","viewer.route.phase.complete":"Complete","viewer.route.phase.inspecting":"Inspecting","viewer.route.destination.count.one":"{count} reachable node is highlighted. Click it.","viewer.route.destination.count.other":"{count} reachable nodes are highlighted. Click one.","viewer.route.noOutgoing":"No arrows lead out of this node. Clear it and pick another start.","viewer.route.result.title":"{source} to {target}","viewer.route.finder.source.title":"Choose route start","viewer.route.finder.source.placeholder":"Search route sources","viewer.route.finder.source.empty":"No matching route sources","viewer.route.finder.source.results":"Nodes that can start a route","viewer.route.finder.source.noun":"route sources","viewer.route.finder.source.badge":"start","viewer.route.finder.target.title":"Destination from {label}","viewer.route.finder.target.placeholder":"Search reachable destinations","viewer.route.finder.target.empty":"No matching reachable destinations","viewer.route.finder.target.results":"Reachable route destinations","viewer.route.finder.target.noun":"reachable destinations","viewer.route.hop.one":"{count} hop","viewer.route.hop.other":"{count} hops","viewer.lens.eyebrow":"Semantic lens","viewer.lens.title":"Compare system roles","viewer.lens.close":"Close semantic lens","viewer.lens.instruction":"Choose up to two semantic kinds. One reveals its real traffic; two compare only direct authored relationships.","viewer.lens.kinds":"Semantic kinds","viewer.lens.choose":"Choose a kind to inspect its nodes and touching relationships.","viewer.lens.copy":"Copy link to semantic lens","viewer.lens.clear":"Clear semantic lens","viewer.lens.open":"Open semantic lens","viewer.lens.openActive":"Open active semantic lens","viewer.lens.legend":"Semantic legend","viewer.lens.legend.inspect.one":"Inspect {label}, {count} node","viewer.lens.legend.inspect.other":"Inspect {label}, {count} nodes","viewer.lens.kind.count.one":"{label}, {count} node","viewer.lens.kind.count.other":"{label}, {count} nodes","viewer.lens.compare.one":"{first} → {second}: {forward} · {second} → {first}: {reverse} · {count} direct relationship","viewer.lens.compare.other":"{first} → {second}: {forward} · {second} → {first}: {reverse} · {count} direct relationships","viewer.lens.single":"{nodes} · {relationships} · connected peers remain visible","viewer.lens.node.one":"{count} {label} node","viewer.lens.node.other":"{count} {label} nodes","viewer.lens.relationship.one":"{count} touching relationship","viewer.lens.relationship.other":"{count} touching relationships","viewer.radar.title":"Semantic radar","viewer.radar.building":"Building overview","viewer.radar.openFull":"Open full semantic radar","viewer.radar.open":"Open radar","viewer.radar.close":"Close semantic radar","viewer.radar.surface":"Diagram overview. Click a node to focus it, or use arrow keys to pan.","viewer.radar.click":"Click node","viewer.radar.drag":"Drag to pan","viewer.radar.space":"Semantic radar needs more MAP space.","viewer.radar.nodes":"Semantic diagram radar nodes","viewer.radar.focus":"Focus {label} from Semantic Radar","viewer.radar.status":"{count} nodes · {viewport}","viewer.radar.fullMap":"{count} nodes · full map","viewer.radar.compacted":"Radar compacted to avoid covering the Semantic Passport or MAP controls.","viewer.radar.cancelWaiting":"Cancel semantic radar waiting for more MAP space","viewer.radar.needsSpace":"Semantic radar needs more visible MAP space","viewer.radar.viewport.full":"full map","viewer.radar.viewport.width":"{percent}% width","viewer.radar.viewport.scale":"{percent}% viewport","viewer.nav.controls":"Diagram view controls","viewer.nav.route":"Trace a directed route","viewer.nav.route.title":"Trace route (R)","viewer.nav.route.short":"ROUTE","viewer.nav.radar":"Open semantic radar","viewer.nav.radar.title":"Semantic radar (M)","viewer.nav.radar.short":"MAP","viewer.nav.lens":"Open semantic lens","viewer.nav.lens.title":"Semantic lens (L)","viewer.nav.lens.short":"LENS","viewer.nav.find":"Find a node","viewer.nav.find.title":"Find a node (/)","viewer.outline.title":"Node index","viewer.rail.show":"Notes \u0026 index","viewer.rail.controls":"Side panel","viewer.rail.collapse":"Collapse side panel","viewer.rail.bottom":"Move panel below the diagram","viewer.rail.right":"Move panel beside the diagram","viewer.nav.guide":"Open diagram guide","viewer.nav.guide.title":"Diagram guide (?)","viewer.nav.zoomOut":"Zoom out","viewer.nav.zoomOut.title":"Zoom out (-)","viewer.nav.reset":"Reset diagram view","viewer.nav.reset.title":"Reset view (0)","viewer.nav.read":"READ","viewer.nav.zoomIn":"Zoom in","viewer.nav.zoomIn.title":"Zoom in (+)","viewer.nav.camera":"{hint}. Reset diagram view","viewer.nav.camera.title":"{semantic}{hint} · reset view (0)","viewer.nav.camera.semantic":"Semantic camera active · ","viewer.nav.level.map":"MAP","viewer.nav.level.read":"READ","viewer.nav.level.full":"FULL","viewer.nav.level.auto":"AUTO","viewer.nav.detail.map":"Zoom in to reveal relationship labels and node context","viewer.nav.detail.read":"Zoom in again to reveal tags and annotations","viewer.nav.detail.full":"Full diagram detail","viewer.intent.summary":"{label}. {out} outgoing, {in} incoming{loops}. {total} connections. Press Enter for details.","viewer.intent.loops":", {count} self loop","viewer.common.copied":"Copied","viewer.common.copyFailed":"Copy failed","viewer.common.copyLink":"Copy link","viewer.common.clear":"Clear","viewer.common.close":"Close","viewer.passport.explain":"Explain","viewer.passport.explain.show":"Show the explanation for this node","viewer.passport.explanation":"Explanation","viewer.passport.explanation.back":"{label} explanation. Activate to return to the passport","viewer.passport.explanation.backLink":"Back to passport","viewer.passport.taskChecklist":"Task checklist","viewer.passport.taskChecklist.count":"{implemented}/{total} implemented","viewer.passport.taskChecklist.status.implemented":"Implemented","viewer.passport.taskChecklist.status.needsReview":"Needs review","viewer.passport.taskChecklist.status.pending":"Pending","viewer.passport.drilldowns":"Child diagrams","viewer.passport.drilldowns.list":"Open a child diagram","viewer.passport.drilldowns.hint":"Ctrl+click or double-click opens the first entry","viewer.passport.drilldowns.open":"Open {label}","viewer.passport.back":"Back to parent","viewer.passport.back.aria":"Return to the parent diagram","viewer.nav.back":"Back","viewer.nav.back.title":"Return to the parent diagram"}}</script>
<!-- Main Diagram -->
<div class="diagram-container" data-detail-level="read"
data-preset-badge-editorial-plate="ARCHIFY / PLATE 04">
@@ -10867,6 +10974,10 @@
<small class="semantic-passport-drilldowns-hint">Ctrl+click or double-click opens the first entry</small>
</div>
<span class="relationship-lens-summary" id="focus-summary" aria-live="polite"></span>
+ <details class="semantic-passport-task-checklist" id="focus-task-checklist" hidden>
+ <summary><span class="semantic-passport-task-checklist-label">Task checklist</span> <span class="semantic-passport-task-checklist-count" id="focus-task-checklist-count"></span></summary>
+ <ul class="semantic-passport-task-checklist-tasks" id="focus-task-checklist-tasks"></ul>
+ </details>
<div class="semantic-passport-reach" id="focus-reach" hidden>
<span class="semantic-passport-reach-label">Authored reach</span>
<div class="semantic-passport-reach-actions" role="group" aria-label="Trace authored reachability">
@@ -13646,6 +13757,24 @@
count: function () { return payload ? Object.keys(payload).length : 0; }
};
})();
+ /* Authored node task checklists: passport-only, like explanations. Their task
+ sources are verified and read through Archify.sourceEvidence.task(). */
+ Archify.taskChecklists = (function () {
+ var element = document.getElementById('archify-task-checklists-data');
+ var payload = null;
+ if (element) {
+ try {
+ var parsed = JSON.parse(element.textContent || 'null');
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) payload = parsed;
+ } catch (_) {}
+ }
+ return {
+ node: function (id) {
+ var tasks = payload && Object.prototype.hasOwnProperty.call(payload, id) ? payload[id] : null;
+ return Array.isArray(tasks) ? tasks.slice() : [];
+ }
+ };
+ })();
/* Verified repository evidence is emitted only after the renderer checks
the authored GitHub revision, source blobs, and optional line ranges
against the explicit --repo-root. It remains outside the SVG so every
@@ -13685,6 +13814,11 @@
node: function (id) {
var sources = payload && payload.nodes ? payload.nodes[id] : null;
return Array.isArray(sources) ? sources.slice() : [];
+ },
+ task: function (id, index) {
+ var tasks = payload && payload.taskChecklists ? payload.taskChecklists[id] : null;
+ var sources = Array.isArray(tasks) ? tasks[index] : null;
+ return Array.isArray(sources) ? sources.slice() : [];
}
};
})();
@@ -13860,6 +13994,9 @@
var parentBackBtn = document.getElementById('btn-parent-back');
var drilldownsSection = document.getElementById('focus-drilldowns');
var drilldownsList = document.getElementById('focus-drilldowns-list');
+ var taskChecklistSection = document.getElementById('focus-task-checklist');
+ var taskChecklistCount = document.getElementById('focus-task-checklist-count');
+ var taskChecklistTasks = document.getElementById('focus-task-checklist-tasks');
var moveBtn = document.getElementById('btn-focus-move');
var activeIds = [];
var hoveredRelationship = null;
@@ -14181,30 +14318,71 @@
repositoryLink.setAttribute('aria-label', viewerText('viewer.passport.repository.open', { revision: repository.revision }));
}
evidence.title = viewerText('viewer.passport.verificationScope');
- sources.forEach(function (source) {
- var link = document.createElement(source.href ? 'a' : 'div');
- link.className = 'semantic-passport-source';
- if (source.href) {
- link.href = source.href;
- link.target = '_blank';
- link.rel = 'noopener noreferrer';
- link.referrerPolicy = 'no-referrer';
- link.setAttribute('aria-label', viewerText('viewer.passport.source.open', { path: source.path, revision: repository.shortRevision }));
+ sources.forEach(function (source) { evidenceLinks.appendChild(sourceLink(source, repository)); });
+ evidence.hidden = false;
+ }
+ function sourceLink(source, repository) {
+ var link = document.createElement(source.href ? 'a' : 'div');
+ link.className = 'semantic-passport-source';
+ if (source.href) {
+ link.href = source.href;
+ link.target = '_blank';
+ link.rel = 'noopener noreferrer';
+ link.referrerPolicy = 'no-referrer';
+ link.setAttribute('aria-label', viewerText('viewer.passport.source.open', { path: source.path, revision: repository.shortRevision }));
+ }
+ var name = document.createElement('strong');
+ name.textContent = source.label || source.path.split('/').pop() || source.path;
+ var location = document.createElement('code');
+ location.textContent = source.line
+ ? 'L' + source.line + (source.endLine && source.endLine !== source.line ? '–' + source.endLine : '') + (source.href ? ' ↗' : '')
+ : source.href ? viewerText('viewer.passport.source.openLink') : '';
+ var sourcePath = document.createElement('small');
+ sourcePath.textContent = source.path;
+ link.appendChild(name);
+ link.appendChild(location);
+ link.appendChild(sourcePath);
+ return link;
+ }
+ var TASK_CHECKLIST_STATUS = {
+ implemented: ['✓', 'viewer.passport.taskChecklist.status.implemented'],
+ 'needs-review': ['?', 'viewer.passport.taskChecklist.status.needsReview'],
+ pending: ['○', 'viewer.passport.taskChecklist.status.pending']
+ };
+ // Read-only: authored statuses and verified task sources, never edited here.
+ function renderTaskChecklist(id) {
+ if (!taskChecklistSection) return;
+ taskChecklistSection.open = false;
+ taskChecklistTasks.textContent = '';
+ var tasks = Archify.taskChecklists ? Archify.taskChecklists.node(id) : [];
+ var repository = Archify.sourceEvidence.repository();
+ taskChecklistSection.hidden = !tasks.length;
+ tasks.forEach(function (task, index) {
+ var item = document.createElement('li');
+ item.className = 'semantic-passport-task-checklist-task';
+ item.setAttribute('data-status', task.status);
+ var status = TASK_CHECKLIST_STATUS[task.status] || TASK_CHECKLIST_STATUS.pending;
+ var mark = document.createElement('span');
+ mark.className = 'semantic-passport-task-checklist-mark';
+ mark.setAttribute('role', 'img');
+ mark.setAttribute('aria-label', viewerText(status[1]));
+ mark.textContent = status[0];
+ var title = document.createElement('strong');
+ title.textContent = task.title;
+ item.appendChild(mark);
+ item.appendChild(title);
+ if (task.criteria) {
+ var criteria = document.createElement('p');
+ criteria.textContent = task.criteria;
+ item.appendChild(criteria);
}
- var name = document.createElement('strong');
- name.textContent = source.label || source.path.split('/').pop() || source.path;
- var location = document.createElement('code');
- location.textContent = source.line
- ? 'L' + source.line + (source.endLine && source.endLine !== source.line ? '–' + source.endLine : '') + (source.href ? ' ↗' : '')
- : source.href ? viewerText('viewer.passport.source.openLink') : '';
- var sourcePath = document.createElement('small');
- sourcePath.textContent = source.path;
- link.appendChild(name);
- link.appendChild(location);
- link.appendChild(sourcePath);
- evidenceLinks.appendChild(link);
+ Archify.sourceEvidence.task(id, index).forEach(function (source) { item.appendChild(sourceLink(source, repository)); });
+ taskChecklistTasks.appendChild(item);
+ });
+ taskChecklistCount.textContent = viewerText('viewer.passport.taskChecklist.count', {
+ implemented: tasks.filter(function (task) { return task.status === 'implemented'; }).length,
+ total: tasks.length
});
- evidence.hidden = false;
}
function explanationFor(id) {
return Archify.explanations && typeof Archify.explanations.node === 'function' ? Archify.explanations.node(id) : null;
@@ -14287,6 +14465,7 @@
semanticId.hidden = false;
renderDrilldowns(node);
renderSourceEvidence(id);
+ renderTaskChecklist(id);
}
/**
* 仅允许同目录文件名.html(可选 ?query / #fragment)。
@@ -15345,6 +15524,10 @@
evidenceLinks.textContent = '';
repositoryLink.removeAttribute('href');
repositoryLink.textContent = '';
+ if (taskChecklistSection) {
+ taskChecklistSection.hidden = true;
+ taskChecklistTasks.textContent = '';
+ }
summary.textContent = '';
reachSection.hidden = true;
upstreamCount.textContent = '0';
@@ -15527,6 +15710,7 @@
clearBtn.addEventListener('click', function () { clear({ restoreFocus: true }); });
copyBtn.addEventListener('click', copyFocusLink);
if (explainBtn) explainBtn.addEventListener('click', function (event) { event.stopPropagation(); toggleExplanation(); });
+ if (taskChecklistSection) taskChecklistSection.addEventListener('toggle', function () { if (!chip.hidden) requestLensPlacement(); });
if (explanationBack) {
explanationBack.addEventListener('click', function (event) { event.stopPropagation(); closeExplanation(); });
}
@@ -22954,6 +23138,55 @@
color: var(--text-faint);
font-size: 0.5625rem;
}
+ /* Authored task checklist: read-only, collapsed to one counted line. */
+ .semantic-passport-task-checklist {
+ margin-top: 0.52rem;
+ padding-top: 0.48rem;
+ border-top: 1px solid color-mix(in srgb, var(--toolbar-border) 76%, transparent);
+ }
+ .semantic-passport-task-checklist > summary { cursor: pointer; }
+ .semantic-passport-task-checklist-count {
+ color: var(--text-muted);
+ font-size: 0.5625rem;
+ }
+ .semantic-passport-task-checklist-tasks {
+ display: grid;
+ gap: 0.32rem;
+ max-height: min(16rem, 40vh);
+ margin: 0.45rem 0 0;
+ padding: 0.15rem;
+ overflow: auto;
+ overscroll-behavior: contain;
+ scrollbar-width: thin;
+ scrollbar-color: var(--frontend-stroke) transparent;
+ list-style: none;
+ }
+ .semantic-passport-task-checklist-task {
+ display: grid;
+ grid-template-columns: 0.9rem minmax(0, 1fr);
+ gap: 0.12rem 0.3rem;
+ }
+ .semantic-passport-task-checklist-mark {
+ color: var(--text-faint);
+ font-size: 0.625rem;
+ font-weight: 800;
+ text-align: center;
+ }
+ .semantic-passport-task-checklist-task[data-status="implemented"] .semantic-passport-task-checklist-mark { color: var(--frontend-stroke); }
+ .semantic-passport-task-checklist-task[data-status="needs-review"] .semantic-passport-task-checklist-mark { color: var(--text-muted); }
+ .semantic-passport-task-checklist-task > strong {
+ color: var(--text-muted);
+ font-size: 0.6875rem;
+ overflow-wrap: anywhere;
+ }
+ .semantic-passport-task-checklist-task p,
+ .semantic-passport-task-checklist-task .semantic-passport-source { grid-column: 2; }
+ .semantic-passport-task-checklist-task p {
+ margin: 0;
+ color: var(--text-faint);
+ font-size: 0.5625rem;
+ overflow-wrap: anywhere;
+ }
.semantic-passport-reach {
display: grid;
gap: 0.28rem;
@@ -22962,7 +23195,8 @@
border-top: 1px solid color-mix(in srgb, var(--toolbar-border) 76%, transparent);
}
.semantic-passport-reach[hidden] { display: none; }
- .semantic-passport-reach-label {
+ .semantic-passport-reach-label,
+ .semantic-passport-task-checklist-label {
color: var(--text-faint);
font-size: 0.5rem;
font-weight: 800;
@@ -23030,8 +23264,10 @@
scrollbar-width: thin;
scrollbar-color: var(--frontend-stroke) transparent;
}
- .relationship-lens-list::-webkit-scrollbar { width: 4px; }
- .relationship-lens-list::-webkit-scrollbar-thumb {
+ .relationship-lens-list::-webkit-scrollbar,
+ .semantic-passport-task-checklist-tasks::-webkit-scrollbar { width: 4px; }
+ .relationship-lens-list::-webkit-scrollbar-thumb,
+ .semantic-passport-task-checklist-tasks::-webkit-scrollbar-thumb {
border-radius: 999px;
background: color-mix(in srgb, var(--frontend-stroke) 68%, transparent);
}
@@ -23533,8 +23769,9 @@
as are dark themes and presets that already pass. Zero counts stay faint
on purpose, so they are excluded from the darker rule. Route-target
finders keep their --security-stroke status color, so they are excluded
- too. */
+ too. Task checklist done marks are the same kind of text. */
html[data-theme="light"] .semantic-passport-meta [data-passport="kind"],
+ html[data-theme="light"] .semantic-passport-task-checklist-task[data-status="implemented"] .semantic-passport-task-checklist-mark,
html[data-theme="light"] .node-finder:not([data-context="route-target"]) .node-finder-result em:not([data-zero]) {
color: var(--reader-accent, var(--frontend-stroke));
}
@@ -26282,7 +26519,8 @@
- <script id="archify-i18n-data" type="application/json">{"locale":"en","messages":{"viewer.kind.frontend":"Frontend","viewer.kind.backend":"Backend","viewer.kind.database":"Database","viewer.kind.cloud":"Cloud","viewer.kind.security":"Security","viewer.kind.messagebus":"Message bus","viewer.kind.external":"External","viewer.kind.neutral":"Neutral","viewer.kind.node":"Node","viewer.kind.start":"Start","viewer.kind.active":"Active","viewer.kind.waiting":"Waiting","viewer.kind.decision":"Decision","viewer.kind.success":"Success","viewer.kind.failure":"Failure","viewer.toolbar.actions":"Diagram actions","viewer.theme.toggle.title":"Toggle theme (T)","viewer.theme.toggle":"Toggle color theme","viewer.theme.dark":"Dark","viewer.theme.light":"Light","viewer.preset.choose.title":"Choose visual style (S cycles)","viewer.preset.choose":"Choose visual style","viewer.preset.style":"Style","viewer.preset.menu":"Visual style","viewer.preset.identity":"Visual identity","viewer.preset.cycles":"to cycle","viewer.preset.classic":"Classic","viewer.preset.classic.short":"Classic","viewer.preset.classic.hint":"Stable technical default","viewer.preset.flow":"Signal Flow","viewer.preset.flow.short":"Flow","viewer.preset.flow.hint":"Motion-forward presentation","viewer.preset.blueprint":"Blueprint","viewer.preset.blueprint.hint":"Engineering review","viewer.preset.editorial":"Editorial","viewer.preset.editorial.hint":"Publication and launch notes","viewer.preset.badge.signalFlow":"SIGNAL FLOW","viewer.preset.badge.blueprint":"BLUEPRINT / REV 01","viewer.preset.badge.editorial":"EDITORIAL / FIELD NOTE","viewer.preset.badge.editorialPlate":"ARCHIFY / PLATE 04","viewer.preset.current":"Visual style: {style}. Choose visual style","viewer.motion.live":"Live","viewer.motion.still":"Still","viewer.motion.pause":"Pause motion","viewer.motion.resume":"Resume motion","viewer.motion.reduced":"Motion paused by reduced-motion preference","viewer.motion.hidden":"Motion paused while this page is hidden","viewer.motion.yielding":"Pause motion; currently yielding to {owner}","viewer.motion.yielding.title":"Live preview enabled · yielding to {owner}","viewer.owner.route":"Route Probe","viewer.owner.lens":"Semantic Lens","viewer.owner.relationship":"Relationship Preview","viewer.owner.intent":"Intent Trace","viewer.owner.focus":"semantic focus","viewer.owner.legend":"legend preview","viewer.owner.reader":"reader interaction","viewer.present.enter":"Enter presentation stage","viewer.present.enter.title":"Presentation stage (F)","viewer.present.exit":"Exit presentation stage","viewer.present.exit.title":"Exit presentation stage (F or Escape)","viewer.present.present":"Present","viewer.present.exit.label":"Exit","viewer.export.button":"Export","viewer.export.button.title":"Export diagram (E)","viewer.export.diagram":"Export diagram","viewer.export.menu":"Export","viewer.export.subtitle":"Portable, clean outputs","viewer.export.share":"Share","viewer.export.shareCard":"Share Card","viewer.export.routeShareCard":"Route Share Card","viewer.export.reachShareCard":"Reach Share Card","viewer.export.copyDiagram":"Copy diagram","viewer.export.clipboardPng":"PNG to clipboard","viewer.export.raster":"Raster images","viewer.export.image":"Image","viewer.export.lossless":"Lossless image","viewer.export.compact":"Compact image","viewer.export.modern":"Modern image","viewer.export.vectorMotion":"Vector and motion","viewer.export.vectorMotion.heading":"Vector \u0026 motion","viewer.export.editable":"Editable vector","viewer.export.svg.auto":"SVG · Auto","viewer.export.svg.auto.hint":"Matches host theme","viewer.export.svg.light":"SVG · Light","viewer.export.svg.light.hint":"Always light","viewer.export.svg.dark":"SVG · Dark","viewer.export.svg.dark.hint":"Always dark","viewer.export.motion6s":"6s motion","viewer.export.unsupported":"Not supported by this browser","viewer.export.clipboardUnsupported":"Clipboard image write not supported by this browser","viewer.export.clipboardUnsupported.short":"Clipboard image write not supported in this browser.","viewer.export.motionUnavailable":"Motion capture unavailable in this browser","viewer.export.webmUnavailable":"WebM unavailable in this browser","viewer.export.failed":"Export failed: {message}","viewer.export.unknownVariant":"Unknown Share Card variant: {variant}","viewer.export.routeRequired":"Trace a route before exporting a Route Share Card","viewer.export.reachRequired":"Trace authored reach before exporting a Reach Share Card","viewer.export.unknown":"unknown","viewer.export.routeFailed":"Route Share Card export failed: {message}","viewer.export.reachFailed":"Reach Share Card export failed: {message}","viewer.export.copyFailed":"Copy failed: {message}","viewer.export.copiedPng":"Copied PNG to clipboard","viewer.export.downloadedRoute":"Downloaded Route Share Card","viewer.export.downloadedReach":"Downloaded Reach Share Card","viewer.export.downloadedWebm":"Downloaded WebM","viewer.export.recording":"Recording 6 seconds of motion…","viewer.export.card.routeSummary.one":"Path: {source} → {target} · {count} step","viewer.export.card.routeSummary.other":"Path: {source} → {target} · {count} steps","viewer.export.card.reachSummary":"Authored {direction} from {origin} · {nodes} · {links} · max {hops}","viewer.export.card.node.one":"{count} node","viewer.export.card.node.other":"{count} nodes","viewer.export.card.link.one":"{count} link","viewer.export.card.link.other":"{count} links","viewer.export.card.hop.one":"{count} hop","viewer.export.card.hop.other":"{count} hops","viewer.export.card.routeBadge":"ARCHIFY · ROUTE · {hops}","viewer.export.card.reachBadge":"ARCHIFY · {direction} REACH","viewer.export.direction.upstream":"Upstream","viewer.export.direction.downstream":"Downstream","viewer.export.error.canvasUnavailable":"Canvas unavailable for {label}","viewer.export.error.contextUnavailable":"2D canvas context unavailable for {label}","viewer.export.error.toBlobUnavailable":"canvas.toBlob unavailable for {label}","viewer.export.error.toBlobNull":"canvas.toBlob returned no data for {label}","viewer.export.error.variantsCombined":"Share Card variants cannot be combined","viewer.export.error.viewerState":"Share Card export could not remove temporary viewer state","viewer.export.error.routeState":"Route Card export could not preserve the resolved route safely","viewer.export.error.reachState":"Reach Card export could not preserve authored reach safely","viewer.export.error.webmRequirements":"WebM motion export requires a trace animation and browser MediaRecorder support","viewer.export.error.mediaRecorder":"MediaRecorder failed","viewer.export.error.emptyWebm":"MediaRecorder produced an empty WebM","viewer.export.error.webmBackground":"SVG background could not be loaded for WebM export","viewer.guide.title":"Explore this system","viewer.focus.selectedNodes":"{count} selected nodes","viewer.guide.eyebrow":"Diagram guide","viewer.guide.close":"Close diagram guide","viewer.guide.inspecting":"Inspecting compiled semantics","viewer.guide.actions":"Diagram exploration actions","viewer.guide.find":"Find any node","viewer.guide.find.hint":"Search labels, responsibilities, kinds, and stable IDs.","viewer.guide.route":"Trace a route","viewer.guide.route.aria":"Trace a directed route","viewer.guide.route.hint":"Ask how two semantic nodes connect in authored direction.","viewer.guide.map":"See the whole system","viewer.guide.map.hint":"Open Semantic Radar with a live viewport and stable nodes.","viewer.guide.lens":"Compare semantic kinds","viewer.guide.lens.hint":"Count roles, reveal their traffic, and compare direct authored links.","viewer.guide.present":"Enter Presentation Stage","viewer.guide.present.hint":"Give the live diagram the viewport without changing export.","viewer.guide.shortcuts":"Additional keyboard shortcuts","viewer.guide.shortcut.export":"Export","viewer.guide.shortcut.theme":"Theme","viewer.guide.shortcut.style":"Style","viewer.guide.shortcut.reset":"Reset","viewer.guide.shortcut.zoomIn":"Zoom in","viewer.guide.shortcut.zoomOut":"Zoom out","viewer.guide.shortcut.close":"Close","viewer.guide.credit":"Find Archify useful? A star on GitHub helps","viewer.guide.star":"Star","viewer.guide.facts":"{nodes} · {relationships}","viewer.guide.fact.node.one":"{count} semantic node","viewer.guide.fact.node.other":"{count} semantic nodes","viewer.guide.fact.relationship.one":"{count} relationship","viewer.guide.fact.relationship.other":"{count} relationships","viewer.guide.open":"Open diagram guide","viewer.finder.title":"Find a node","viewer.finder.close":"Close node finder","viewer.finder.placeholder":"Search labels or IDs","viewer.finder.search":"Search diagram nodes","viewer.finder.results":"Diagram nodes","viewer.finder.empty":"No matching nodes","viewer.finder.result.focus":"Focus {label}","viewer.finder.result.routeStart":"Choose {label} as route start","viewer.finder.result.routeTarget":"Choose {label} as route destination, {links}","viewer.finder.status.empty":"No matching nodes","viewer.finder.status.count.one":"{count} matching node","viewer.finder.status.count.other":"{count} matching nodes","viewer.finder.noun.nodes":"nodes","viewer.finder.link.one":"{count} link","viewer.finder.link.other":"{count} links","viewer.finder.result.focus.one":"Focus {label}, {count} related connection","viewer.finder.result.focus.other":"Focus {label}, {count} related connections","viewer.finder.status.filtered":"{visible} of {available} {noun}","viewer.finder.status.all":"{available} {noun}","viewer.passport.eyebrow":"Semantic passport","viewer.passport.metadata":"Node metadata","viewer.passport.evidence":"Verified source evidence","viewer.passport.verified":"Verified source","viewer.passport.verificationScope":"Verified against local Git at the pinned revision. Remote access has not been checked.","viewer.passport.reach":"Authored reach","viewer.passport.reach.trace":"Trace authored reachability","viewer.passport.upstream":"Upstream","viewer.passport.downstream":"Downstream","viewer.passport.upstream.trace":"Trace upstream authored reachability","viewer.passport.downstream.trace":"Trace downstream authored reachability","viewer.passport.close":"Close semantic passport","viewer.passport.move":"Move semantic passport. Drag, use arrow keys, or press Home to reset.","viewer.passport.copy":"Copy link","viewer.passport.copy.focus":"Copy link to focused node","viewer.passport.relations":"Relations","viewer.passport.relations.show":"Show connected relationships","viewer.passport.relations.hide":"Hide connected relationships","viewer.passport.relations.list":"Connected relationships","viewer.passport.copyRelation":"Copy relation","viewer.passport.copyNode":"Copy node","viewer.passport.copyPinned":"Copy link to pinned relationship","viewer.passport.copySource":"Copy link to source node","viewer.passport.copy.focused.success":"Focused node link copied","viewer.passport.copy.pinned.success":"Pinned relationship link copied","viewer.passport.copy.focused.failed":"Could not copy focused node link","viewer.passport.copy.pinned.failed":"Could not copy pinned relationship link","viewer.passport.relationship.none":"No connected relationships","viewer.passport.relationship.count.one":"{count} relation","viewer.passport.relationship.count.other":"{count} relations","viewer.passport.relationship.show.one":"Show {count} connected relationship","viewer.passport.relationship.show.other":"Show {count} connected relationships","viewer.passport.relationship.summary":"{out} outgoing · {in} incoming{loops}","viewer.passport.relationship.loops":" · {count} loop","viewer.passport.relationship.explorer":"Direct relationship explorer","viewer.passport.relationship.help":"Use arrow keys to explore relationships. Press Enter or Space to pin details; Escape clears.","viewer.passport.relationship.loopsBack":"loops back","viewer.passport.relationship.connectsTo":"connects to","viewer.passport.relationship.connectsFrom":"connects from","viewer.passport.relationship.pinned":"Pinned relationship · {from} → {to} · {label}","viewer.passport.relationship.inspect":"Inspect relationship {index} of {total}: {from} to {to}, {label}. Press Enter for details.","viewer.passport.relationship.group.out":"Outgoing","viewer.passport.relationship.group.in":"Incoming","viewer.passport.relationship.group.loop":"Self loops","viewer.passport.relationship.row":"{group}: {relationship}, {neighbor}","viewer.passport.relationship.direction.out":"OUT →","viewer.passport.relationship.direction.in":"← IN","viewer.passport.relationship.direction.loop":"LOOP","viewer.passport.sourceCount.one":"{count} verified source reference","viewer.passport.sourceCount.other":"{count} verified source references","viewer.passport.repository.open":"Open verified repository revision {revision}","viewer.passport.source.open":"Open verified source {path} at revision {revision}","viewer.passport.source.openLink":"Open ↗","viewer.passport.reach.upstream.one":"Trace {count} upstream authored node","viewer.passport.reach.upstream.other":"Trace {count} upstream authored nodes","viewer.passport.reach.downstream.one":"Trace {count} downstream authored node","viewer.passport.reach.downstream.other":"Trace {count} downstream authored nodes","viewer.passport.reach.noUpstream":"No upstream authored nodes","viewer.passport.reach.noDownstream":"No downstream authored nodes","viewer.passport.reach.status":"{direction} · {nodes} nodes · {links} links · max {hops} hops","viewer.route.eyebrow":"Path","viewer.route.start":"Click where the path starts","viewer.route.start.find":"Find start","viewer.route.start.find.aria":"Find a route start","viewer.route.copy":"Copy link","viewer.route.copy.aria":"Copy link to traced route","viewer.route.clear":"Clear","viewer.route.clear.aria":"Clear route probe","viewer.route.traced":"Path","viewer.route.pickTwo":"Click two nodes on the diagram","viewer.route.pickOne":"Click a node on the diagram","viewer.route.controls":"Route journey controls","viewer.route.previous":"Previous route position","viewer.route.play":"Play route journey","viewer.route.pause":"Pause route journey","viewer.route.replay":"Replay route journey","viewer.route.next":"Next route position","viewer.route.journey":"Journey","viewer.route.pause.label":"Pause","viewer.route.replay.label":"Replay","viewer.route.overview":"Overview","viewer.route.overview.aria":"Show complete route overview","viewer.route.instructions":"Click a start, then an end. Paths follow the arrows.","viewer.route.destination":"Where does the path from {label} end?","viewer.route.destination.find":"Find target","viewer.route.destination.find.aria":"Find a reachable route destination","viewer.route.differentDestination":"Choose a different destination","viewer.route.distinct":"Pick two different nodes.","viewer.route.unreachable":"Cannot reach {label} from here","viewer.route.unreachable.detail":"{target} cannot be reached from {source} along the arrows. Pick a highlighted node.","viewer.route.start.instructions":"Next, pick the end. Only nodes you can reach will light up.","viewer.route.copy.success":"Traced route link copied","viewer.route.copy.failed":"Could not copy traced route link","viewer.route.position":"Route position {index} of {total}: {label}","viewer.route.step":"Step {index} of {total} · {phase} · {label}","viewer.route.motionRequired":"Automatic journey requires Live motion","viewer.route.trigger.clear":"Clear traced route","viewer.route.overview.status":"{nodes} · {hops} · shortest path","viewer.route.overview.node.one":"{count} node","viewer.route.overview.node.other":"{count} nodes","viewer.route.overview.hop.one":"{count} step","viewer.route.overview.hop.other":"{count} steps","viewer.route.phase.playing":"Playing","viewer.route.phase.complete":"Complete","viewer.route.phase.inspecting":"Inspecting","viewer.route.destination.count.one":"{count} reachable node is highlighted. Click it.","viewer.route.destination.count.other":"{count} reachable nodes are highlighted. Click one.","viewer.route.noOutgoing":"No arrows lead out of this node. Clear it and pick another start.","viewer.route.result.title":"{source} to {target}","viewer.route.finder.source.title":"Choose route start","viewer.route.finder.source.placeholder":"Search route sources","viewer.route.finder.source.empty":"No matching route sources","viewer.route.finder.source.results":"Nodes that can start a route","viewer.route.finder.source.noun":"route sources","viewer.route.finder.source.badge":"start","viewer.route.finder.target.title":"Destination from {label}","viewer.route.finder.target.placeholder":"Search reachable destinations","viewer.route.finder.target.empty":"No matching reachable destinations","viewer.route.finder.target.results":"Reachable route destinations","viewer.route.finder.target.noun":"reachable destinations","viewer.route.hop.one":"{count} hop","viewer.route.hop.other":"{count} hops","viewer.lens.eyebrow":"Semantic lens","viewer.lens.title":"Compare system roles","viewer.lens.close":"Close semantic lens","viewer.lens.instruction":"Choose up to two semantic kinds. One reveals its real traffic; two compare only direct authored relationships.","viewer.lens.kinds":"Semantic kinds","viewer.lens.choose":"Choose a kind to inspect its nodes and touching relationships.","viewer.lens.copy":"Copy link to semantic lens","viewer.lens.clear":"Clear semantic lens","viewer.lens.open":"Open semantic lens","viewer.lens.openActive":"Open active semantic lens","viewer.lens.legend":"Semantic legend","viewer.lens.legend.inspect.one":"Inspect {label}, {count} node","viewer.lens.legend.inspect.other":"Inspect {label}, {count} nodes","viewer.lens.kind.count.one":"{label}, {count} node","viewer.lens.kind.count.other":"{label}, {count} nodes","viewer.lens.compare.one":"{first} → {second}: {forward} · {second} → {first}: {reverse} · {count} direct relationship","viewer.lens.compare.other":"{first} → {second}: {forward} · {second} → {first}: {reverse} · {count} direct relationships","viewer.lens.single":"{nodes} · {relationships} · connected peers remain visible","viewer.lens.node.one":"{count} {label} node","viewer.lens.node.other":"{count} {label} nodes","viewer.lens.relationship.one":"{count} touching relationship","viewer.lens.relationship.other":"{count} touching relationships","viewer.radar.title":"Semantic radar","viewer.radar.building":"Building overview","viewer.radar.openFull":"Open full semantic radar","viewer.radar.open":"Open radar","viewer.radar.close":"Close semantic radar","viewer.radar.surface":"Diagram overview. Click a node to focus it, or use arrow keys to pan.","viewer.radar.click":"Click node","viewer.radar.drag":"Drag to pan","viewer.radar.space":"Semantic radar needs more MAP space.","viewer.radar.nodes":"Semantic diagram radar nodes","viewer.radar.focus":"Focus {label} from Semantic Radar","viewer.radar.status":"{count} nodes · {viewport}","viewer.radar.fullMap":"{count} nodes · full map","viewer.radar.compacted":"Radar compacted to avoid covering the Semantic Passport or MAP controls.","viewer.radar.cancelWaiting":"Cancel semantic radar waiting for more MAP space","viewer.radar.needsSpace":"Semantic radar needs more visible MAP space","viewer.radar.viewport.full":"full map","viewer.radar.viewport.width":"{percent}% width","viewer.radar.viewport.scale":"{percent}% viewport","viewer.nav.controls":"Diagram view controls","viewer.nav.route":"Trace a directed route","viewer.nav.route.title":"Trace route (R)","viewer.nav.route.short":"ROUTE","viewer.nav.radar":"Open semantic radar","viewer.nav.radar.title":"Semantic radar (M)","viewer.nav.radar.short":"MAP","viewer.nav.lens":"Open semantic lens","viewer.nav.lens.title":"Semantic lens (L)","viewer.nav.lens.short":"LENS","viewer.nav.find":"Find a node","viewer.nav.find.title":"Find a node (/)","viewer.outline.title":"Node index","viewer.rail.show":"Notes \u0026 index","viewer.rail.controls":"Side panel","viewer.rail.collapse":"Collapse side panel","viewer.rail.bottom":"Move panel below the diagram","viewer.rail.right":"Move panel beside the diagram","viewer.nav.guide":"Open diagram guide","viewer.nav.guide.title":"Diagram guide (?)","viewer.nav.zoomOut":"Zoom out","viewer.nav.zoomOut.title":"Zoom out (-)","viewer.nav.reset":"Reset diagram view","viewer.nav.reset.title":"Reset view (0)","viewer.nav.read":"READ","viewer.nav.zoomIn":"Zoom in","viewer.nav.zoomIn.title":"Zoom in (+)","viewer.nav.camera":"{hint}. Reset diagram view","viewer.nav.camera.title":"{semantic}{hint} · reset view (0)","viewer.nav.camera.semantic":"Semantic camera active · ","viewer.nav.level.map":"MAP","viewer.nav.level.read":"READ","viewer.nav.level.full":"FULL","viewer.nav.level.auto":"AUTO","viewer.nav.detail.map":"Zoom in to reveal relationship labels and node context","viewer.nav.detail.read":"Zoom in again to reveal tags and annotations","viewer.nav.detail.full":"Full diagram detail","viewer.intent.summary":"{label}. {out} outgoing, {in} incoming{loops}. {total} connections. Press Enter for details.","viewer.intent.loops":", {count} self loop","viewer.common.copied":"Copied","viewer.common.copyFailed":"Copy failed","viewer.common.copyLink":"Copy link","viewer.common.clear":"Clear","viewer.common.close":"Close","viewer.passport.explain":"Explain","viewer.passport.explain.show":"Show the explanation for this node","viewer.passport.explanation":"Explanation","viewer.passport.explanation.back":"{label} explanation. Activate to return to the passport","viewer.passport.explanation.backLink":"Back to passport","viewer.passport.drilldowns":"Child diagrams","viewer.passport.drilldowns.list":"Open a child diagram","viewer.passport.drilldowns.hint":"Ctrl+click or double-click opens the first entry","viewer.passport.drilldowns.open":"Open {label}","viewer.passport.back":"Back to parent","viewer.passport.back.aria":"Return to the parent diagram","viewer.nav.back":"Back","viewer.nav.back.title":"Return to the parent diagram"}}</script>
+
+ <script id="archify-i18n-data" type="application/json">{"locale":"en","messages":{"viewer.kind.frontend":"Frontend","viewer.kind.backend":"Backend","viewer.kind.database":"Database","viewer.kind.cloud":"Cloud","viewer.kind.security":"Security","viewer.kind.messagebus":"Message bus","viewer.kind.external":"External","viewer.kind.neutral":"Neutral","viewer.kind.node":"Node","viewer.kind.start":"Start","viewer.kind.active":"Active","viewer.kind.waiting":"Waiting","viewer.kind.decision":"Decision","viewer.kind.success":"Success","viewer.kind.failure":"Failure","viewer.toolbar.actions":"Diagram actions","viewer.theme.toggle.title":"Toggle theme (T)","viewer.theme.toggle":"Toggle color theme","viewer.theme.dark":"Dark","viewer.theme.light":"Light","viewer.preset.choose.title":"Choose visual style (S cycles)","viewer.preset.choose":"Choose visual style","viewer.preset.style":"Style","viewer.preset.menu":"Visual style","viewer.preset.identity":"Visual identity","viewer.preset.cycles":"to cycle","viewer.preset.classic":"Classic","viewer.preset.classic.short":"Classic","viewer.preset.classic.hint":"Stable technical default","viewer.preset.flow":"Signal Flow","viewer.preset.flow.short":"Flow","viewer.preset.flow.hint":"Motion-forward presentation","viewer.preset.blueprint":"Blueprint","viewer.preset.blueprint.hint":"Engineering review","viewer.preset.editorial":"Editorial","viewer.preset.editorial.hint":"Publication and launch notes","viewer.preset.badge.signalFlow":"SIGNAL FLOW","viewer.preset.badge.blueprint":"BLUEPRINT / REV 01","viewer.preset.badge.editorial":"EDITORIAL / FIELD NOTE","viewer.preset.badge.editorialPlate":"ARCHIFY / PLATE 04","viewer.preset.current":"Visual style: {style}. Choose visual style","viewer.motion.live":"Live","viewer.motion.still":"Still","viewer.motion.pause":"Pause motion","viewer.motion.resume":"Resume motion","viewer.motion.reduced":"Motion paused by reduced-motion preference","viewer.motion.hidden":"Motion paused while this page is hidden","viewer.motion.yielding":"Pause motion; currently yielding to {owner}","viewer.motion.yielding.title":"Live preview enabled · yielding to {owner}","viewer.owner.route":"Route Probe","viewer.owner.lens":"Semantic Lens","viewer.owner.relationship":"Relationship Preview","viewer.owner.intent":"Intent Trace","viewer.owner.focus":"semantic focus","viewer.owner.legend":"legend preview","viewer.owner.reader":"reader interaction","viewer.present.enter":"Enter presentation stage","viewer.present.enter.title":"Presentation stage (F)","viewer.present.exit":"Exit presentation stage","viewer.present.exit.title":"Exit presentation stage (F or Escape)","viewer.present.present":"Present","viewer.present.exit.label":"Exit","viewer.export.button":"Export","viewer.export.button.title":"Export diagram (E)","viewer.export.diagram":"Export diagram","viewer.export.menu":"Export","viewer.export.subtitle":"Portable, clean outputs","viewer.export.share":"Share","viewer.export.shareCard":"Share Card","viewer.export.routeShareCard":"Route Share Card","viewer.export.reachShareCard":"Reach Share Card","viewer.export.copyDiagram":"Copy diagram","viewer.export.clipboardPng":"PNG to clipboard","viewer.export.raster":"Raster images","viewer.export.image":"Image","viewer.export.lossless":"Lossless image","viewer.export.compact":"Compact image","viewer.export.modern":"Modern image","viewer.export.vectorMotion":"Vector and motion","viewer.export.vectorMotion.heading":"Vector \u0026 motion","viewer.export.editable":"Editable vector","viewer.export.svg.auto":"SVG · Auto","viewer.export.svg.auto.hint":"Matches host theme","viewer.export.svg.light":"SVG · Light","viewer.export.svg.light.hint":"Always light","viewer.export.svg.dark":"SVG · Dark","viewer.export.svg.dark.hint":"Always dark","viewer.export.motion6s":"6s motion","viewer.export.unsupported":"Not supported by this browser","viewer.export.clipboardUnsupported":"Clipboard image write not supported by this browser","viewer.export.clipboardUnsupported.short":"Clipboard image write not supported in this browser.","viewer.export.motionUnavailable":"Motion capture unavailable in this browser","viewer.export.webmUnavailable":"WebM unavailable in this browser","viewer.export.failed":"Export failed: {message}","viewer.export.unknownVariant":"Unknown Share Card variant: {variant}","viewer.export.routeRequired":"Trace a route before exporting a Route Share Card","viewer.export.reachRequired":"Trace authored reach before exporting a Reach Share Card","viewer.export.unknown":"unknown","viewer.export.routeFailed":"Route Share Card export failed: {message}","viewer.export.reachFailed":"Reach Share Card export failed: {message}","viewer.export.copyFailed":"Copy failed: {message}","viewer.export.copiedPng":"Copied PNG to clipboard","viewer.export.downloadedRoute":"Downloaded Route Share Card","viewer.export.downloadedReach":"Downloaded Reach Share Card","viewer.export.downloadedWebm":"Downloaded WebM","viewer.export.recording":"Recording 6 seconds of motion…","viewer.export.card.routeSummary.one":"Path: {source} → {target} · {count} step","viewer.export.card.routeSummary.other":"Path: {source} → {target} · {count} steps","viewer.export.card.reachSummary":"Authored {direction} from {origin} · {nodes} · {links} · max {hops}","viewer.export.card.node.one":"{count} node","viewer.export.card.node.other":"{count} nodes","viewer.export.card.link.one":"{count} link","viewer.export.card.link.other":"{count} links","viewer.export.card.hop.one":"{count} hop","viewer.export.card.hop.other":"{count} hops","viewer.export.card.routeBadge":"ARCHIFY · ROUTE · {hops}","viewer.export.card.reachBadge":"ARCHIFY · {direction} REACH","viewer.export.direction.upstream":"Upstream","viewer.export.direction.downstream":"Downstream","viewer.export.error.canvasUnavailable":"Canvas unavailable for {label}","viewer.export.error.contextUnavailable":"2D canvas context unavailable for {label}","viewer.export.error.toBlobUnavailable":"canvas.toBlob unavailable for {label}","viewer.export.error.toBlobNull":"canvas.toBlob returned no data for {label}","viewer.export.error.variantsCombined":"Share Card variants cannot be combined","viewer.export.error.viewerState":"Share Card export could not remove temporary viewer state","viewer.export.error.routeState":"Route Card export could not preserve the resolved route safely","viewer.export.error.reachState":"Reach Card export could not preserve authored reach safely","viewer.export.error.webmRequirements":"WebM motion export requires a trace animation and browser MediaRecorder support","viewer.export.error.mediaRecorder":"MediaRecorder failed","viewer.export.error.emptyWebm":"MediaRecorder produced an empty WebM","viewer.export.error.webmBackground":"SVG background could not be loaded for WebM export","viewer.guide.title":"Explore this system","viewer.focus.selectedNodes":"{count} selected nodes","viewer.guide.eyebrow":"Diagram guide","viewer.guide.close":"Close diagram guide","viewer.guide.inspecting":"Inspecting compiled semantics","viewer.guide.actions":"Diagram exploration actions","viewer.guide.find":"Find any node","viewer.guide.find.hint":"Search labels, responsibilities, kinds, and stable IDs.","viewer.guide.route":"Trace a route","viewer.guide.route.aria":"Trace a directed route","viewer.guide.route.hint":"Ask how two semantic nodes connect in authored direction.","viewer.guide.map":"See the whole system","viewer.guide.map.hint":"Open Semantic Radar with a live viewport and stable nodes.","viewer.guide.lens":"Compare semantic kinds","viewer.guide.lens.hint":"Count roles, reveal their traffic, and compare direct authored links.","viewer.guide.present":"Enter Presentation Stage","viewer.guide.present.hint":"Give the live diagram the viewport without changing export.","viewer.guide.shortcuts":"Additional keyboard shortcuts","viewer.guide.shortcut.export":"Export","viewer.guide.shortcut.theme":"Theme","viewer.guide.shortcut.style":"Style","viewer.guide.shortcut.reset":"Reset","viewer.guide.shortcut.zoomIn":"Zoom in","viewer.guide.shortcut.zoomOut":"Zoom out","viewer.guide.shortcut.close":"Close","viewer.guide.credit":"Find Archify useful? A star on GitHub helps","viewer.guide.star":"Star","viewer.guide.facts":"{nodes} · {relationships}","viewer.guide.fact.node.one":"{count} semantic node","viewer.guide.fact.node.other":"{count} semantic nodes","viewer.guide.fact.relationship.one":"{count} relationship","viewer.guide.fact.relationship.other":"{count} relationships","viewer.guide.open":"Open diagram guide","viewer.finder.title":"Find a node","viewer.finder.close":"Close node finder","viewer.finder.placeholder":"Search labels or IDs","viewer.finder.search":"Search diagram nodes","viewer.finder.results":"Diagram nodes","viewer.finder.empty":"No matching nodes","viewer.finder.result.focus":"Focus {label}","viewer.finder.result.routeStart":"Choose {label} as route start","viewer.finder.result.routeTarget":"Choose {label} as route destination, {links}","viewer.finder.status.empty":"No matching nodes","viewer.finder.status.count.one":"{count} matching node","viewer.finder.status.count.other":"{count} matching nodes","viewer.finder.noun.nodes":"nodes","viewer.finder.link.one":"{count} link","viewer.finder.link.other":"{count} links","viewer.finder.result.focus.one":"Focus {label}, {count} related connection","viewer.finder.result.focus.other":"Focus {label}, {count} related connections","viewer.finder.status.filtered":"{visible} of {available} {noun}","viewer.finder.status.all":"{available} {noun}","viewer.passport.eyebrow":"Semantic passport","viewer.passport.metadata":"Node metadata","viewer.passport.evidence":"Verified source evidence","viewer.passport.verified":"Verified source","viewer.passport.verificationScope":"Verified against local Git at the pinned revision. Remote access has not been checked.","viewer.passport.reach":"Authored reach","viewer.passport.reach.trace":"Trace authored reachability","viewer.passport.upstream":"Upstream","viewer.passport.downstream":"Downstream","viewer.passport.upstream.trace":"Trace upstream authored reachability","viewer.passport.downstream.trace":"Trace downstream authored reachability","viewer.passport.close":"Close semantic passport","viewer.passport.move":"Move semantic passport. Drag, use arrow keys, or press Home to reset.","viewer.passport.copy":"Copy link","viewer.passport.copy.focus":"Copy link to focused node","viewer.passport.relations":"Relations","viewer.passport.relations.show":"Show connected relationships","viewer.passport.relations.hide":"Hide connected relationships","viewer.passport.relations.list":"Connected relationships","viewer.passport.copyRelation":"Copy relation","viewer.passport.copyNode":"Copy node","viewer.passport.copyPinned":"Copy link to pinned relationship","viewer.passport.copySource":"Copy link to source node","viewer.passport.copy.focused.success":"Focused node link copied","viewer.passport.copy.pinned.success":"Pinned relationship link copied","viewer.passport.copy.focused.failed":"Could not copy focused node link","viewer.passport.copy.pinned.failed":"Could not copy pinned relationship link","viewer.passport.relationship.none":"No connected relationships","viewer.passport.relationship.count.one":"{count} relation","viewer.passport.relationship.count.other":"{count} relations","viewer.passport.relationship.show.one":"Show {count} connected relationship","viewer.passport.relationship.show.other":"Show {count} connected relationships","viewer.passport.relationship.summary":"{out} outgoing · {in} incoming{loops}","viewer.passport.relationship.loops":" · {count} loop","viewer.passport.relationship.explorer":"Direct relationship explorer","viewer.passport.relationship.help":"Use arrow keys to explore relationships. Press Enter or Space to pin details; Escape clears.","viewer.passport.relationship.loopsBack":"loops back","viewer.passport.relationship.connectsTo":"connects to","viewer.passport.relationship.connectsFrom":"connects from","viewer.passport.relationship.pinned":"Pinned relationship · {from} → {to} · {label}","viewer.passport.relationship.inspect":"Inspect relationship {index} of {total}: {from} to {to}, {label}. Press Enter for details.","viewer.passport.relationship.group.out":"Outgoing","viewer.passport.relationship.group.in":"Incoming","viewer.passport.relationship.group.loop":"Self loops","viewer.passport.relationship.row":"{group}: {relationship}, {neighbor}","viewer.passport.relationship.direction.out":"OUT →","viewer.passport.relationship.direction.in":"← IN","viewer.passport.relationship.direction.loop":"LOOP","viewer.passport.sourceCount.one":"{count} verified source reference","viewer.passport.sourceCount.other":"{count} verified source references","viewer.passport.repository.open":"Open verified repository revision {revision}","viewer.passport.source.open":"Open verified source {path} at revision {revision}","viewer.passport.source.openLink":"Open ↗","viewer.passport.reach.upstream.one":"Trace {count} upstream authored node","viewer.passport.reach.upstream.other":"Trace {count} upstream authored nodes","viewer.passport.reach.downstream.one":"Trace {count} downstream authored node","viewer.passport.reach.downstream.other":"Trace {count} downstream authored nodes","viewer.passport.reach.noUpstream":"No upstream authored nodes","viewer.passport.reach.noDownstream":"No downstream authored nodes","viewer.passport.reach.status":"{direction} · {nodes} nodes · {links} links · max {hops} hops","viewer.route.eyebrow":"Path","viewer.route.start":"Click where the path starts","viewer.route.start.find":"Find start","viewer.route.start.find.aria":"Find a route start","viewer.route.copy":"Copy link","viewer.route.copy.aria":"Copy link to traced route","viewer.route.clear":"Clear","viewer.route.clear.aria":"Clear route probe","viewer.route.traced":"Path","viewer.route.pickTwo":"Click two nodes on the diagram","viewer.route.pickOne":"Click a node on the diagram","viewer.route.controls":"Route journey controls","viewer.route.previous":"Previous route position","viewer.route.play":"Play route journey","viewer.route.pause":"Pause route journey","viewer.route.replay":"Replay route journey","viewer.route.next":"Next route position","viewer.route.journey":"Journey","viewer.route.pause.label":"Pause","viewer.route.replay.label":"Replay","viewer.route.overview":"Overview","viewer.route.overview.aria":"Show complete route overview","viewer.route.instructions":"Click a start, then an end. Paths follow the arrows.","viewer.route.destination":"Where does the path from {label} end?","viewer.route.destination.find":"Find target","viewer.route.destination.find.aria":"Find a reachable route destination","viewer.route.differentDestination":"Choose a different destination","viewer.route.distinct":"Pick two different nodes.","viewer.route.unreachable":"Cannot reach {label} from here","viewer.route.unreachable.detail":"{target} cannot be reached from {source} along the arrows. Pick a highlighted node.","viewer.route.start.instructions":"Next, pick the end. Only nodes you can reach will light up.","viewer.route.copy.success":"Traced route link copied","viewer.route.copy.failed":"Could not copy traced route link","viewer.route.position":"Route position {index} of {total}: {label}","viewer.route.step":"Step {index} of {total} · {phase} · {label}","viewer.route.motionRequired":"Automatic journey requires Live motion","viewer.route.trigger.clear":"Clear traced route","viewer.route.overview.status":"{nodes} · {hops} · shortest path","viewer.route.overview.node.one":"{count} node","viewer.route.overview.node.other":"{count} nodes","viewer.route.overview.hop.one":"{count} step","viewer.route.overview.hop.other":"{count} steps","viewer.route.phase.playing":"Playing","viewer.route.phase.complete":"Complete","viewer.route.phase.inspecting":"Inspecting","viewer.route.destination.count.one":"{count} reachable node is highlighted. Click it.","viewer.route.destination.count.other":"{count} reachable nodes are highlighted. Click one.","viewer.route.noOutgoing":"No arrows lead out of this node. Clear it and pick another start.","viewer.route.result.title":"{source} to {target}","viewer.route.finder.source.title":"Choose route start","viewer.route.finder.source.placeholder":"Search route sources","viewer.route.finder.source.empty":"No matching route sources","viewer.route.finder.source.results":"Nodes that can start a route","viewer.route.finder.source.noun":"route sources","viewer.route.finder.source.badge":"start","viewer.route.finder.target.title":"Destination from {label}","viewer.route.finder.target.placeholder":"Search reachable destinations","viewer.route.finder.target.empty":"No matching reachable destinations","viewer.route.finder.target.results":"Reachable route destinations","viewer.route.finder.target.noun":"reachable destinations","viewer.route.hop.one":"{count} hop","viewer.route.hop.other":"{count} hops","viewer.lens.eyebrow":"Semantic lens","viewer.lens.title":"Compare system roles","viewer.lens.close":"Close semantic lens","viewer.lens.instruction":"Choose up to two semantic kinds. One reveals its real traffic; two compare only direct authored relationships.","viewer.lens.kinds":"Semantic kinds","viewer.lens.choose":"Choose a kind to inspect its nodes and touching relationships.","viewer.lens.copy":"Copy link to semantic lens","viewer.lens.clear":"Clear semantic lens","viewer.lens.open":"Open semantic lens","viewer.lens.openActive":"Open active semantic lens","viewer.lens.legend":"Semantic legend","viewer.lens.legend.inspect.one":"Inspect {label}, {count} node","viewer.lens.legend.inspect.other":"Inspect {label}, {count} nodes","viewer.lens.kind.count.one":"{label}, {count} node","viewer.lens.kind.count.other":"{label}, {count} nodes","viewer.lens.compare.one":"{first} → {second}: {forward} · {second} → {first}: {reverse} · {count} direct relationship","viewer.lens.compare.other":"{first} → {second}: {forward} · {second} → {first}: {reverse} · {count} direct relationships","viewer.lens.single":"{nodes} · {relationships} · connected peers remain visible","viewer.lens.node.one":"{count} {label} node","viewer.lens.node.other":"{count} {label} nodes","viewer.lens.relationship.one":"{count} touching relationship","viewer.lens.relationship.other":"{count} touching relationships","viewer.radar.title":"Semantic radar","viewer.radar.building":"Building overview","viewer.radar.openFull":"Open full semantic radar","viewer.radar.open":"Open radar","viewer.radar.close":"Close semantic radar","viewer.radar.surface":"Diagram overview. Click a node to focus it, or use arrow keys to pan.","viewer.radar.click":"Click node","viewer.radar.drag":"Drag to pan","viewer.radar.space":"Semantic radar needs more MAP space.","viewer.radar.nodes":"Semantic diagram radar nodes","viewer.radar.focus":"Focus {label} from Semantic Radar","viewer.radar.status":"{count} nodes · {viewport}","viewer.radar.fullMap":"{count} nodes · full map","viewer.radar.compacted":"Radar compacted to avoid covering the Semantic Passport or MAP controls.","viewer.radar.cancelWaiting":"Cancel semantic radar waiting for more MAP space","viewer.radar.needsSpace":"Semantic radar needs more visible MAP space","viewer.radar.viewport.full":"full map","viewer.radar.viewport.width":"{percent}% width","viewer.radar.viewport.scale":"{percent}% viewport","viewer.nav.controls":"Diagram view controls","viewer.nav.route":"Trace a directed route","viewer.nav.route.title":"Trace route (R)","viewer.nav.route.short":"ROUTE","viewer.nav.radar":"Open semantic radar","viewer.nav.radar.title":"Semantic radar (M)","viewer.nav.radar.short":"MAP","viewer.nav.lens":"Open semantic lens","viewer.nav.lens.title":"Semantic lens (L)","viewer.nav.lens.short":"LENS","viewer.nav.find":"Find a node","viewer.nav.find.title":"Find a node (/)","viewer.outline.title":"Node index","viewer.rail.show":"Notes \u0026 index","viewer.rail.controls":"Side panel","viewer.rail.collapse":"Collapse side panel","viewer.rail.bottom":"Move panel below the diagram","viewer.rail.right":"Move panel beside the diagram","viewer.nav.guide":"Open diagram guide","viewer.nav.guide.title":"Diagram guide (?)","viewer.nav.zoomOut":"Zoom out","viewer.nav.zoomOut.title":"Zoom out (-)","viewer.nav.reset":"Reset diagram view","viewer.nav.reset.title":"Reset view (0)","viewer.nav.read":"READ","viewer.nav.zoomIn":"Zoom in","viewer.nav.zoomIn.title":"Zoom in (+)","viewer.nav.camera":"{hint}. Reset diagram view","viewer.nav.camera.title":"{semantic}{hint} · reset view (0)","viewer.nav.camera.semantic":"Semantic camera active · ","viewer.nav.level.map":"MAP","viewer.nav.level.read":"READ","viewer.nav.level.full":"FULL","viewer.nav.level.auto":"AUTO","viewer.nav.detail.map":"Zoom in to reveal relationship labels and node context","viewer.nav.detail.read":"Zoom in again to reveal tags and annotations","viewer.nav.detail.full":"Full diagram detail","viewer.intent.summary":"{label}. {out} outgoing, {in} incoming{loops}. {total} connections. Press Enter for details.","viewer.intent.loops":", {count} self loop","viewer.common.copied":"Copied","viewer.common.copyFailed":"Copy failed","viewer.common.copyLink":"Copy link","viewer.common.clear":"Clear","viewer.common.close":"Close","viewer.passport.explain":"Explain","viewer.passport.explain.show":"Show the explanation for this node","viewer.passport.explanation":"Explanation","viewer.passport.explanation.back":"{label} explanation. Activate to return to the passport","viewer.passport.explanation.backLink":"Back to passport","viewer.passport.taskChecklist":"Task checklist","viewer.passport.taskChecklist.count":"{implemented}/{total} implemented","viewer.passport.taskChecklist.status.implemented":"Implemented","viewer.passport.taskChecklist.status.needsReview":"Needs review","viewer.passport.taskChecklist.status.pending":"Pending","viewer.passport.drilldowns":"Child diagrams","viewer.passport.drilldowns.list":"Open a child diagram","viewer.passport.drilldowns.hint":"Ctrl+click or double-click opens the first entry","viewer.passport.drilldowns.open":"Open {label}","viewer.passport.back":"Back to parent","viewer.passport.back.aria":"Return to the parent diagram","viewer.nav.back":"Back","viewer.nav.back.title":"Return to the parent diagram"}}</script>
<!-- Main Diagram -->
<div class="diagram-container" data-detail-level="read"
data-preset-badge-editorial-plate="ARCHIFY / PLATE 04">
@@ -26609,6 +26847,10 @@
<small class="semantic-passport-drilldowns-hint">Ctrl+click or double-click opens the first entry</small>
</div>
<span class="relationship-lens-summary" id="focus-summary" aria-live="polite"></span>
+ <details class="semantic-passport-task-checklist" id="focus-task-checklist" hidden>
+ <summary><span class="semantic-passport-task-checklist-label">Task checklist</span> <span class="semantic-passport-task-checklist-count" id="focus-task-checklist-count"></span></summary>
+ <ul class="semantic-passport-task-checklist-tasks" id="focus-task-checklist-tasks"></ul>
+ </details>
<div class="semantic-passport-reach" id="focus-reach" hidden>
<span class="semantic-passport-reach-label">Authored reach</span>
<div class="semantic-passport-reach-actions" role="group" aria-label="Trace authored reachability">
@@ -29388,6 +29630,24 @@
count: function () { return payload ? Object.keys(payload).length : 0; }
};
})();
+ /* Authored node task checklists: passport-only, like explanations. Their task
+ sources are verified and read through Archify.sourceEvidence.task(). */
+ Archify.taskChecklists = (function () {
+ var element = document.getElementById('archify-task-checklists-data');
+ var payload = null;
+ if (element) {
+ try {
+ var parsed = JSON.parse(element.textContent || 'null');
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) payload = parsed;
+ } catch (_) {}
+ }
+ return {
+ node: function (id) {
+ var tasks = payload && Object.prototype.hasOwnProperty.call(payload, id) ? payload[id] : null;
+ return Array.isArray(tasks) ? tasks.slice() : [];
+ }
+ };
+ })();
/* Verified repository evidence is emitted only after the renderer checks
the authored GitHub revision, source blobs, and optional line ranges
against the explicit --repo-root. It remains outside the SVG so every
@@ -29427,6 +29687,11 @@
node: function (id) {
var sources = payload && payload.nodes ? payload.nodes[id] : null;
return Array.isArray(sources) ? sources.slice() : [];
+ },
+ task: function (id, index) {
+ var tasks = payload && payload.taskChecklists ? payload.taskChecklists[id] : null;
+ var sources = Array.isArray(tasks) ? tasks[index] : null;
+ return Array.isArray(sources) ? sources.slice() : [];
}
};
})();
@@ -29602,6 +29867,9 @@
var parentBackBtn = document.getElementById('btn-parent-back');
var drilldownsSection = document.getElementById('focus-drilldowns');
var drilldownsList = document.getElementById('focus-drilldowns-list');
+ var taskChecklistSection = document.getElementById('focus-task-checklist');
+ var taskChecklistCount = document.getElementById('focus-task-checklist-count');
+ var taskChecklistTasks = document.getElementById('focus-task-checklist-tasks');
var moveBtn = document.getElementById('btn-focus-move');
var activeIds = [];
var hoveredRelationship = null;
@@ -29923,30 +30191,71 @@
repositoryLink.setAttribute('aria-label', viewerText('viewer.passport.repository.open', { revision: repository.revision }));
}
evidence.title = viewerText('viewer.passport.verificationScope');
- sources.forEach(function (source) {
- var link = document.createElement(source.href ? 'a' : 'div');
- link.className = 'semantic-passport-source';
- if (source.href) {
- link.href = source.href;
- link.target = '_blank';
- link.rel = 'noopener noreferrer';
- link.referrerPolicy = 'no-referrer';
- link.setAttribute('aria-label', viewerText('viewer.passport.source.open', { path: source.path, revision: repository.shortRevision }));
+ sources.forEach(function (source) { evidenceLinks.appendChild(sourceLink(source, repository)); });
+ evidence.hidden = false;
+ }
+ function sourceLink(source, repository) {
+ var link = document.createElement(source.href ? 'a' : 'div');
+ link.className = 'semantic-passport-source';
+ if (source.href) {
+ link.href = source.href;
+ link.target = '_blank';
+ link.rel = 'noopener noreferrer';
+ link.referrerPolicy = 'no-referrer';
+ link.setAttribute('aria-label', viewerText('viewer.passport.source.open', { path: source.path, revision: repository.shortRevision }));
+ }
+ var name = document.createElement('strong');
+ name.textContent = source.label || source.path.split('/').pop() || source.path;
+ var location = document.createElement('code');
+ location.textContent = source.line
+ ? 'L' + source.line + (source.endLine && source.endLine !== source.line ? '–' + source.endLine : '') + (source.href ? ' ↗' : '')
+ : source.href ? viewerText('viewer.passport.source.openLink') : '';
+ var sourcePath = document.createElement('small');
+ sourcePath.textContent = source.path;
+ link.appendChild(name);
+ link.appendChild(location);
+ link.appendChild(sourcePath);
+ return link;
+ }
+ var TASK_CHECKLIST_STATUS = {
+ implemented: ['✓', 'viewer.passport.taskChecklist.status.implemented'],
+ 'needs-review': ['?', 'viewer.passport.taskChecklist.status.needsReview'],
+ pending: ['○', 'viewer.passport.taskChecklist.status.pending']
+ };
+ // Read-only: authored statuses and verified task sources, never edited here.
+ function renderTaskChecklist(id) {
+ if (!taskChecklistSection) return;
+ taskChecklistSection.open = false;
+ taskChecklistTasks.textContent = '';
+ var tasks = Archify.taskChecklists ? Archify.taskChecklists.node(id) : [];
+ var repository = Archify.sourceEvidence.repository();
+ taskChecklistSection.hidden = !tasks.length;
+ tasks.forEach(function (task, index) {
+ var item = document.createElement('li');
+ item.className = 'semantic-passport-task-checklist-task';
+ item.setAttribute('data-status', task.status);
+ var status = TASK_CHECKLIST_STATUS[task.status] || TASK_CHECKLIST_STATUS.pending;
+ var mark = document.createElement('span');
+ mark.className = 'semantic-passport-task-checklist-mark';
+ mark.setAttribute('role', 'img');
+ mark.setAttribute('aria-label', viewerText(status[1]));
+ mark.textContent = status[0];
+ var title = document.createElement('strong');
+ title.textContent = task.title;
+ item.appendChild(mark);
+ item.appendChild(title);
+ if (task.criteria) {
+ var criteria = document.createElement('p');
+ criteria.textContent = task.criteria;
+ item.appendChild(criteria);
}
- var name = document.createElement('strong');
- name.textContent = source.label || source.path.split('/').pop() || source.path;
- var location = document.createElement('code');
- location.textContent = source.line
- ? 'L' + source.line + (source.endLine && source.endLine !== source.line ? '–' + source.endLine : '') + (source.href ? ' ↗' : '')
- : source.href ? viewerText('viewer.passport.source.openLink') : '';
- var sourcePath = document.createElement('small');
- sourcePath.textContent = source.path;
- link.appendChild(name);
- link.appendChild(location);
- link.appendChild(sourcePath);
- evidenceLinks.appendChild(link);
+ Archify.sourceEvidence.task(id, index).forEach(function (source) { item.appendChild(sourceLink(source, repository)); });
+ taskChecklistTasks.appendChild(item);
+ });
+ taskChecklistCount.textContent = viewerText('viewer.passport.taskChecklist.count', {
+ implemented: tasks.filter(function (task) { return task.status === 'implemented'; }).length,
+ total: tasks.length
});
- evidence.hidden = false;
}
function explanationFor(id) {
return Archify.explanations && typeof Archify.explanations.node === 'function' ? Archify.explanations.node(id) : null;
@@ -30029,6 +30338,7 @@
semanticId.hidden = false;
renderDrilldowns(node);
renderSourceEvidence(id);
+ renderTaskChecklist(id);
}
/**
* 仅允许同目录文件名.html(可选 ?query / #fragment)。
@@ -31087,6 +31397,10 @@
evidenceLinks.textContent = '';
repositoryLink.removeAttribute('href');
repositoryLink.textContent = '';
+ if (taskChecklistSection) {
+ taskChecklistSection.hidden = true;
+ taskChecklistTasks.textContent = '';
+ }
summary.textContent = '';
reachSection.hidden = true;
upstreamCount.textContent = '0';
@@ -31269,6 +31583,7 @@
clearBtn.addEventListener('click', function () { clear({ restoreFocus: true }); });
copyBtn.addEventListener('click', copyFocusLink);
if (explainBtn) explainBtn.addEventListener('click', function (event) { event.stopPropagation(); toggleExplanation(); });
+ if (taskChecklistSection) taskChecklistSection.addEventListener('toggle', function () { if (!chip.hidden) requestLensPlacement(); });
if (explanationBack) {
explanationBack.addEventListener('click', function (event) { event.stopPropagation(); closeExplanation(); });
}
@@ -36524,7 +36839,7 @@
const status = document.querySelector('#review-status');
const receiptNode = document.querySelector('#archify-compare-receipt');
const reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)');
- const exportCss = "\n /* ==========================================================\n THEME VARIABLES — switch by toggling [data-theme] on \u003chtml\u003e\n ========================================================== */\n :root,\n [data-theme=\"dark\"] {\n --bg: #020617;\n --grid: #1e293b;\n --canvas-dot: rgba(148, 163, 184, 0.16);\n --text: #ffffff;\n --text-muted: #94a3b8;\n --text-dim: #475569;\n /* UI chrome and menu hints need more contrast than SVG .t-dim accents */\n --text-faint: #7d8da1;\n\n --panel: rgba(15, 23, 42, 0.5);\n --panel-border: #1e293b;\n --lane-fill: rgba(15, 23, 42, 0.22);\n --lane-stroke: #334155;\n\n --arrow: #94a3b8;\n --arrow-emphasis: #34d399;\n\n /* Opaque mask color used behind semi-transparent component fills\n so the arrows drawn underneath are properly hidden. */\n --mask: #0f172a;\n\n --frontend-fill: rgba(8, 51, 68, 0.4);\n --frontend-stroke: #22d3ee;\n --backend-fill: rgba(6, 78, 59, 0.4);\n --backend-stroke: #34d399;\n --database-fill: rgba(76, 29, 149, 0.4);\n --database-stroke: #a78bfa;\n --cloud-fill: rgba(120, 53, 15, 0.3);\n --cloud-stroke: #fbbf24;\n --security-fill: rgba(136, 19, 55, 0.4);\n --security-stroke: #fb7185;\n --messagebus-fill: rgba(251, 146, 60, 0.3);\n --messagebus-stroke:#fb923c;\n --external-fill: rgba(30, 41, 59, 0.5);\n --external-stroke: #94a3b8;\n\n --toolbar-bg: rgba(15, 23, 42, 0.8);\n --toolbar-border: #334155;\n --toolbar-text: #e2e8f0;\n --toolbar-hover: rgba(15, 23, 42, 0.95);\n --toolbar-menu-bg: #0f172a;\n }\n\n [data-theme=\"light\"] {\n --bg: #f4f5f7;\n --grid: #e9edf2;\n --canvas-dot: #e2e7ec;\n --text: #111827;\n --text-muted: #5b6474;\n --text-dim: #9aa3b2;\n --text-faint: #6b7280;\n\n --panel: #ffffff;\n --panel-border: #e5e7eb;\n --lane-fill: rgba(248, 250, 252, 0.65);\n --lane-stroke: #cbd5e1;\n\n --arrow: #64748b;\n --arrow-emphasis: #059669;\n\n --mask: #ffffff;\n\n --frontend-fill: rgba(34, 211, 238, 0.15);\n --frontend-stroke: #0891b2;\n /* #1040: darker accent for small bold reader text; the diagram stroke above is untouched. */\n --reader-accent: #077f9c;\n --backend-fill: rgba(52, 211, 153, 0.18);\n --backend-stroke: #059669;\n --database-fill: rgba(167, 139, 250, 0.2);\n --database-stroke: #7c3aed;\n --cloud-fill: rgba(251, 191, 36, 0.18);\n --cloud-stroke: #d97706;\n --security-fill: rgba(251, 113, 133, 0.15);\n --security-stroke: #e11d48;\n --messagebus-fill: rgba(251, 146, 60, 0.15);\n --messagebus-stroke: #ea580c;\n --external-fill: rgba(148, 163, 184, 0.18);\n --external-stroke: #64748b;\n\n --toolbar-bg: rgba(255, 255, 255, 0.92);\n --toolbar-border: #cbd5e1;\n --toolbar-text: #334155;\n --toolbar-hover: #ffffff;\n --toolbar-menu-bg: #ffffff;\n }\n\n /* ==========================================================\n SIGNAL FLOW — an opt-in, motion-forward visual preset.\n The variables also target a standalone exported SVG, whose\n root carries data-preset and data-theme directly.\n ========================================================== */\n [data-preset=\"signal-flow\"][data-theme=\"dark\"] {\n --bg: #030711;\n --grid: #15233a;\n --panel: rgba(6, 14, 28, 0.78);\n --panel-border: #1d3350;\n --lane-fill: rgba(9, 22, 40, 0.5);\n --lane-stroke: #2c4564;\n --text: #f5fbff;\n --text-muted: #9eb0c7;\n --text-dim: #52667f;\n --text-faint: #7890ad;\n --mask: #07101e;\n --frontend-fill: rgba(6, 182, 212, 0.14);\n --frontend-stroke: #67e8f9;\n --backend-fill: rgba(16, 185, 129, 0.14);\n --backend-stroke: #5eead4;\n --database-fill: rgba(139, 92, 246, 0.16);\n --database-stroke: #c4b5fd;\n --cloud-fill: rgba(245, 158, 11, 0.13);\n --cloud-stroke: #fcd34d;\n --security-fill: rgba(244, 63, 94, 0.13);\n --security-stroke: #fda4af;\n --messagebus-fill: rgba(249, 115, 22, 0.13);\n --messagebus-stroke: #fdba74;\n --external-fill: rgba(71, 85, 105, 0.24);\n --external-stroke: #a5b4c7;\n --arrow: #7890ad;\n --arrow-emphasis: #2dd4bf;\n --toolbar-bg: rgba(7, 16, 30, 0.86);\n --toolbar-border: #29415f;\n --toolbar-menu-bg: #07101e;\n }\n\n [data-preset=\"signal-flow\"][data-theme=\"light\"] {\n --bg: #f4f9fc;\n --grid: #d4e5ee;\n --panel: rgba(255, 255, 255, 0.82);\n --panel-border: #bfd5e2;\n --lane-fill: rgba(232, 243, 248, 0.62);\n --lane-stroke: #a9c5d5;\n --text: #102638;\n --text-muted: #587287;\n --text-dim: #8aa2b4;\n --text-faint: #5d778a; /* #1040: was #668397 at 3.99:1 on reader surfaces */\n --mask: #ffffff;\n --frontend-fill: rgba(6, 182, 212, 0.09);\n --frontend-stroke: #0789a1;\n /* #1040: darker accent for small bold reader text; the diagram stroke above is untouched. */\n --reader-accent: #067e95;\n --backend-fill: rgba(5, 150, 105, 0.09);\n --backend-stroke: #087f69;\n --database-fill: rgba(124, 58, 237, 0.09);\n --database-stroke: #7254c7;\n --cloud-fill: rgba(217, 119, 6, 0.08);\n --cloud-stroke: #b9670b;\n --security-fill: rgba(225, 29, 72, 0.08);\n --security-stroke: #c53a59;\n --messagebus-fill: rgba(234, 88, 12, 0.08);\n --messagebus-stroke: #c65f27;\n --external-fill: rgba(100, 116, 139, 0.1);\n --external-stroke: #607a8c;\n --arrow: #7b97aa;\n --arrow-emphasis: #0d9488;\n }\n\n /* ==========================================================\n BLUEPRINT — a review-first drafting preset for deployment,\n infrastructure, and technical design documentation. It keeps\n the same semantic palette and geometry while trading glow for\n precise grid contrast, squared materials, and drafting marks.\n ========================================================== */\n [data-preset=\"blueprint\"][data-theme=\"dark\"] {\n --bg: #06131f;\n --grid: #17425a;\n --panel: rgba(7, 27, 43, 0.9);\n --panel-border: #27627f;\n --lane-fill: rgba(10, 43, 66, 0.36);\n --lane-stroke: #34799a;\n --text: #e3f6ff;\n --text-muted: #91b8ca;\n --text-dim: #557e92;\n --text-faint: #739caf;\n --mask: #0a2031;\n --frontend-fill: rgba(26, 157, 193, 0.14);\n --frontend-stroke: #66d9ef;\n --backend-fill: rgba(31, 155, 124, 0.13);\n --backend-stroke: #69dfbd;\n --database-fill: rgba(120, 105, 196, 0.14);\n --database-stroke: #b4a8ff;\n --cloud-fill: rgba(197, 145, 43, 0.13);\n --cloud-stroke: #ffd166;\n --security-fill: rgba(199, 76, 104, 0.13);\n --security-stroke: #ff8da1;\n --messagebus-fill: rgba(207, 112, 49, 0.13);\n --messagebus-stroke: #ffad66;\n --external-fill: rgba(84, 119, 139, 0.18);\n --external-stroke: #a7cad9;\n --arrow: #78a3b7;\n --arrow-emphasis: #64dfc1;\n --toolbar-bg: rgba(7, 28, 45, 0.92);\n --toolbar-border: #34799a;\n --toolbar-text: #d8f3ff;\n --toolbar-hover: rgba(18, 58, 83, 0.95);\n --toolbar-menu-bg: #0a2031;\n }\n\n [data-preset=\"blueprint\"][data-theme=\"light\"] {\n --bg: #edf7fa;\n --grid: #b5d5e1;\n --panel: rgba(249, 253, 255, 0.94);\n --panel-border: #78aabd;\n --lane-fill: rgba(210, 232, 240, 0.42);\n --lane-stroke: #83b2c4;\n --text: #123344;\n --text-muted: #4e7486;\n --text-dim: #86a6b4;\n --text-faint: #577786; /* #1040: was #668c9d at 3.54:1 on reader surfaces */\n --mask: #f9fdff;\n --frontend-fill: rgba(8, 145, 178, 0.08);\n --frontend-stroke: #087f9c;\n --backend-fill: rgba(5, 128, 101, 0.08);\n --backend-stroke: #08755f;\n --database-fill: rgba(100, 75, 180, 0.08);\n --database-stroke: #6757a8;\n --cloud-fill: rgba(181, 120, 15, 0.08);\n --cloud-stroke: #a86609;\n --security-fill: rgba(190, 48, 80, 0.07);\n --security-stroke: #b32f50;\n --messagebus-fill: rgba(196, 81, 22, 0.07);\n --messagebus-stroke: #b65120;\n --external-fill: rgba(79, 112, 128, 0.08);\n --external-stroke: #506f7e;\n --arrow: #6d93a5;\n --arrow-emphasis: #087f69;\n --toolbar-bg: rgba(247, 252, 254, 0.94);\n --toolbar-border: #8ab5c5;\n --toolbar-text: #294f61;\n --toolbar-hover: #ffffff;\n --toolbar-menu-bg: #f9fdff;\n }\n\n /* ==========================================================\n EDITORIAL — a warm, publication-minded preset for design\n reviews, launch notes, and documentation. The diagram keeps\n the same semantic palette and exact geometry, but trades\n software chrome for paper, ink, ruled structure, and one\n restrained vermilion accent.\n ========================================================== */\n [data-preset=\"editorial\"][data-theme=\"dark\"] {\n --bg: #181611;\n --grid: #39342a;\n --panel: rgba(35, 31, 24, 0.96);\n --panel-border: #625a4a;\n --lane-fill: rgba(52, 46, 35, 0.46);\n --lane-stroke: #726957;\n --text: #f4eddf;\n --text-muted: #b9ae9b;\n --text-dim: #776e60;\n --text-faint: #9d917e;\n --mask: #231f18;\n --frontend-fill: rgba(43, 133, 142, 0.16);\n --frontend-stroke: #7fc6c7;\n --backend-fill: rgba(63, 132, 92, 0.16);\n --backend-stroke: #8fc29e;\n --database-fill: rgba(117, 91, 141, 0.17);\n --database-stroke: #c0a4d0;\n --cloud-fill: rgba(170, 119, 49, 0.16);\n --cloud-stroke: #d8ad68;\n --security-fill: rgba(157, 69, 65, 0.17);\n --security-stroke: #df9085;\n --messagebus-fill: rgba(174, 79, 42, 0.16);\n --messagebus-stroke: #df946f;\n --external-fill: rgba(126, 115, 96, 0.18);\n --external-stroke: #b8ad99;\n --arrow: #948978;\n --arrow-emphasis: #dd6b3d;\n --toolbar-bg: rgba(35, 31, 24, 0.92);\n --toolbar-border: #625a4a;\n --toolbar-text: #eee5d5;\n --toolbar-hover: #302a20;\n --toolbar-menu-bg: #231f18;\n }\n\n [data-preset=\"editorial\"][data-theme=\"light\"] {\n --bg: #f2eee5;\n --grid: #d8d0c2;\n --panel: rgba(251, 248, 241, 0.97);\n --panel-border: #c4b9a6;\n --lane-fill: rgba(229, 221, 207, 0.42);\n --lane-stroke: #b8aa94;\n --text: #242018;\n --text-muted: #6f6658;\n --text-dim: #a09788;\n --text-faint: #796f60; /* #1040: was #817767 at 4.15:1 on reader surfaces */\n --mask: #fbf8f1;\n --frontend-fill: rgba(31, 117, 126, 0.09);\n --frontend-stroke: #287e84;\n /* #1040: darker accent for small bold reader text; the diagram stroke above is untouched. */\n --reader-accent: #267a80;\n --backend-fill: rgba(42, 119, 75, 0.09);\n --backend-stroke: #397b53;\n --database-fill: rgba(105, 75, 130, 0.09);\n --database-stroke: #765d86;\n --cloud-fill: rgba(157, 103, 31, 0.1);\n --cloud-stroke: #9a671f;\n --security-fill: rgba(153, 58, 52, 0.09);\n --security-stroke: #9e463f;\n --messagebus-fill: rgba(182, 70, 27, 0.09);\n --messagebus-stroke: #ad4b25;\n --external-fill: rgba(101, 91, 75, 0.09);\n --external-stroke: #746b5e;\n --arrow: #8a806f;\n --arrow-emphasis: #bb4c23;\n --toolbar-bg: rgba(251, 248, 241, 0.94);\n --toolbar-border: #c4b9a6;\n --toolbar-text: #40392f;\n --toolbar-hover: #fffdf8;\n --toolbar-menu-bg: #fbf8f1;\n }\n\n * { margin: 0; padding: 0; box-sizing: border-box; }\n\n body {\n --archify-page-top: 2rem;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', 'Noto Sans CJK SC', 'Noto Sans', Roboto, 'Helvetica Neue', Arial, sans-serif;\n -webkit-font-smoothing: antialiased;\n background: var(--bg);\n min-height: 100vh;\n padding: var(--archify-page-top) 2rem 2rem;\n color: var(--text);\n /* Theme surfaces change together; animating the page background leaves it\n visibly out of sync with the SVG and panel for the first 200ms. */\n }\n\n html[data-preset=\"signal-flow\"] body {\n background-image:\n radial-gradient(circle at 18% -8%, rgba(34, 211, 238, 0.14), transparent 34rem),\n radial-gradient(circle at 92% 18%, rgba(139, 92, 246, 0.12), transparent 30rem);\n background-attachment: fixed;\n }\n html[data-preset=\"signal-flow\"][data-theme=\"light\"] body {\n background-image:\n radial-gradient(circle at 18% -8%, rgba(6, 182, 212, 0.12), transparent 34rem),\n radial-gradient(circle at 92% 18%, rgba(139, 92, 246, 0.08), transparent 30rem);\n }\n html[data-preset=\"blueprint\"] body {\n background-image:\n linear-gradient(var(--grid) 1px, transparent 1px),\n linear-gradient(90deg, var(--grid) 1px, transparent 1px);\n background-size: 32px 32px;\n background-position: -1px -1px;\n background-attachment: fixed;\n }\n html[data-preset=\"editorial\"] body {\n background-image:\n linear-gradient(90deg, transparent 0, transparent 5.4rem, color-mix(in srgb, var(--security-stroke) 18%, transparent) 5.4rem, color-mix(in srgb, var(--security-stroke) 18%, transparent) calc(5.4rem + 1px), transparent calc(5.4rem + 1px)),\n repeating-linear-gradient(0deg, transparent 0, transparent 31px, color-mix(in srgb, var(--grid) 36%, transparent) 31px, color-mix(in srgb, var(--grid) 36%, transparent) 32px);\n background-attachment: fixed;\n }\n\n /* Gallery/embed mode keeps the generated artifact as the source of truth\n while presenting only its diagram surface inside a proof card. */\n html[data-embed=\"true\"] body {\n min-height: 0;\n padding: 0;\n overflow: hidden;\n background: var(--bg);\n background-image: none;\n }\n html[data-embed=\"true\"] .toolbar,\n html[data-embed=\"true\"] .header,\n html[data-embed=\"true\"] .cards,\n html[data-embed=\"true\"] .diagram-nav,\n html[data-embed=\"true\"] .overview-map,\n html[data-embed=\"true\"] .focus-chip,\n html[data-embed=\"true\"] .route-probe,\n html[data-embed=\"true\"] .semantic-lens,\n html[data-embed=\"true\"] .diagram-guide,\n html[data-embed=\"true\"] .node-finder,\n html[data-embed=\"true\"] .node-outline { display: none !important; }\n html[data-embed=\"true\"] .container { max-width: none; }\n html[data-embed=\"true\"] .diagram-container {\n padding: 0.5rem;\n border: 0;\n border-radius: 0;\n box-shadow: none;\n }\n html[data-embed=\"true\"] .diagram-container svg { min-width: 0; }\n\n /* Presentation Stage is a viewer-only layer: it gives the live diagram\n the viewport while keeping guided views, theme, focus, and exports real.\n Embed mode wins when both query parameters are present. */\n html[data-present=\"true\"]:not([data-embed=\"true\"]) body {\n --archify-page-top: 1rem;\n height: 100dvh;\n min-height: 0;\n padding: 1rem;\n overflow: hidden;\n }\n html[data-present=\"true\"]:not([data-embed=\"true\"]) .container {\n display: flex;\n flex-direction: column;\n gap: 0.75rem;\n width: 100%;\n max-width: none;\n height: calc(100dvh - 2rem);\n }\n html[data-present=\"true\"]:not([data-embed=\"true\"]) .header {\n flex: none;\n min-width: 0;\n margin: 0;\n padding-right: 23rem;\n }\n html[data-present=\"true\"]:not([data-embed=\"true\"]) .header-row { margin-bottom: 0.25rem; }\n html[data-present=\"true\"]:not([data-embed=\"true\"]) .subtitle {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n html[data-present=\"true\"]:not([data-embed=\"true\"]) .diagram-container {\n display: flex;\n flex: 1 1 auto;\n align-items: center;\n justify-content: center;\n min-height: 0;\n padding: 0.75rem;\n padding-bottom: calc(0.75rem + var(--archify-nav-reserve));\n }\n html[data-present=\"true\"]:not([data-embed=\"true\"]) .diagram-container \u003e svg {\n flex: 1 1 auto;\n height: 100%;\n min-height: 0;\n width: 100%;\n min-width: 0;\n }\n html[data-present=\"true\"]:not([data-embed=\"true\"]) .cards { display: none; }\n html[data-present=\"true\"]:not([data-embed=\"true\"]) #btn-present {\n border-color: var(--frontend-stroke);\n color: var(--frontend-stroke);\n }\n\n /* The reader owns only the outer scale. Compact automatic sequences fit at\n their authored text size or larger. Long sequences and waterfall rows\n favor reading width and page scroll; other diagrams keep height fitting.\n Small automatic canvases retain a modest scale inside a usable shell.\n Every strategy preserves the authored SVG/viewBox and semantic geometry. */\n .container {\n width: 100%;\n max-width: var(--archify-reader-width, 1440px);\n margin: 0 auto;\n }\n html[data-reader-area=\"true\"] .diagram-container {\n min-height: var(--archify-diagram-min-height);\n display: flex;\n align-items: center;\n }\n html[data-reader-area=\"true\"] .diagram-container \u003e svg { flex-shrink: 0; }\n /* The original SVG legend can use the outer corner while the capped or fitted\n diagram stays centered. The container still clips all outer paint. */\n .diagram-container:has([data-reader-legend-corner]) \u003e svg { overflow: visible; }\n .diagram-container [data-reader-legend-corner] {\n transform-origin: 0 0;\n transform-box: view-box;\n transform: var(--archify-reader-legend-transform);\n }\n html[data-reader-narrow=\"true\"]:not([data-present=\"true\"]) .diagram-container \u003e svg {\n min-width: 0;\n max-width: var(--archify-diagram-max-width);\n margin-inline: auto;\n }\n @media (min-width: 768px) and (max-height: 920px) {\n .header { margin-bottom: 1rem; }\n .diagram-container { padding: 1rem; }\n .cards { margin-top: 1rem; }\n .card { padding: 1rem; }\n }\n\n .header { margin-bottom: 1.5rem; padding-right: 29.5rem; }\n\n html[data-preset=\"signal-flow\"] .header,\n html[data-preset=\"blueprint\"] .header,\n html[data-preset=\"editorial\"] .header { padding-right: 29.5rem; }\n\n .header-row {\n display: flex;\n align-items: center;\n gap: 0.75rem;\n min-height: 2.75rem;\n margin-bottom: 0.25rem;\n }\n\n .pulse-dot {\n flex: none;\n width: 10px;\n height: 10px;\n background: var(--frontend-stroke);\n border-radius: 50%;\n box-shadow: 0 0 0 4px color-mix(in srgb, var(--frontend-stroke) 16%, transparent);\n animation: none;\n }\n\n html[data-motion-capable=\"true\"] .pulse-dot { animation: pulse 2s infinite; }\n\n @keyframes pulse {\n 0%, 100% { opacity: 1; }\n 50% { opacity: 0.5; }\n }\n\n h1 {\n font-size: 1.5rem;\n font-weight: 650;\n line-height: 1.25;\n letter-spacing: -0.015em;\n }\n\n .subtitle {\n color: var(--text-muted);\n font-size: 0.875rem;\n line-height: 1.55;\n margin-left: 1.375rem;\n }\n\n html[data-preset=\"signal-flow\"] .header-row::after {\n content: attr(data-preset-badge-signal-flow);\n flex: none;\n margin-left: 0.25rem;\n color: var(--frontend-stroke);\n border: 1px solid var(--frontend-stroke);\n border-radius: 999px;\n padding: 0.28rem 0.6rem;\n font-size: 0.625rem;\n letter-spacing: 0.12em;\n opacity: 0.82;\n box-shadow: 0 0 20px rgba(34, 211, 238, 0.12);\n white-space: nowrap;\n }\n\n html[data-preset=\"blueprint\"] .pulse-dot {\n width: 10px;\n height: 10px;\n border: 1px solid var(--frontend-stroke);\n border-radius: 1px;\n background: transparent;\n box-shadow: inset 0 0 0 2px var(--bg);\n transform: rotate(45deg);\n animation: none;\n }\n html[data-preset=\"blueprint\"] .header-row::after {\n content: attr(data-preset-badge-blueprint);\n flex: none;\n margin-left: 0.25rem;\n padding: 0.28rem 0.55rem;\n border-top: 1px solid var(--frontend-stroke);\n border-bottom: 1px solid var(--frontend-stroke);\n color: var(--frontend-stroke);\n font-size: 0.5625rem;\n font-weight: 700;\n letter-spacing: 0.14em;\n white-space: nowrap;\n }\n\n html[data-preset=\"editorial\"] h1,\n html[data-preset=\"editorial\"] .card h3 {\n font-family: Georgia, 'Times New Roman', 'Songti SC', STSong, serif;\n letter-spacing: -0.02em;\n }\n html[data-preset=\"editorial\"] h1 { font-size: 1.72rem; font-weight: 600; }\n html[data-preset=\"editorial\"] .pulse-dot {\n width: 9px;\n height: 9px;\n border: 1px solid var(--arrow-emphasis);\n border-radius: 0;\n background: var(--arrow-emphasis);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--arrow-emphasis) 14%, transparent);\n transform: rotate(45deg);\n animation: none;\n }\n html[data-preset=\"editorial\"] .header-row::after {\n content: attr(data-preset-badge-editorial);\n flex: none;\n margin-left: 0.25rem;\n padding: 0.32rem 0 0.24rem;\n border-bottom: 2px solid var(--arrow-emphasis);\n color: var(--arrow-emphasis);\n font-family: Georgia, 'Times New Roman', serif;\n font-size: 0.625rem;\n font-style: italic;\n font-weight: 600;\n letter-spacing: 0.08em;\n white-space: nowrap;\n }\n\n .diagram-container {\n --archify-nav-reserve: 0px;\n background: var(--panel);\n border-radius: 1rem;\n border: 1px solid var(--panel-border);\n box-shadow: 0 1px 2px rgba(15, 23, 42, 0.04), 0 16px 40px -20px rgba(15, 23, 42, 0.18);\n padding: 1.5rem;\n padding-bottom: calc(1.5rem + var(--archify-nav-reserve));\n overflow: hidden;\n position: relative;\n }\n\n html[data-preset=\"signal-flow\"] .diagram-container {\n position: relative;\n overflow: hidden;\n background: linear-gradient(180deg, rgba(8, 20, 38, 0.82), rgba(3, 8, 18, 0.94));\n box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.04), 0 28px 80px rgba(0, 0, 0, 0.34);\n }\n html[data-preset=\"signal-flow\"][data-theme=\"light\"] .diagram-container {\n background: linear-gradient(180deg, rgba(255, 255, 255, 0.9), rgba(237, 246, 250, 0.94));\n box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.72), 0 24px 60px rgba(45, 78, 99, 0.12);\n }\n html[data-preset=\"blueprint\"] .diagram-container {\n border-color: var(--panel-border);\n border-radius: 0.35rem;\n background: var(--panel);\n box-shadow:\n inset 0 0 0 1px color-mix(in srgb, var(--panel-border) 34%, transparent),\n 0 18px 48px rgba(0, 0, 0, 0.16);\n }\n html[data-preset=\"blueprint\"] .diagram-container::after {\n content: \"\";\n position: absolute;\n z-index: 10;\n inset: 0.7rem;\n pointer-events: none;\n background:\n linear-gradient(var(--frontend-stroke), var(--frontend-stroke)) left top / 1.35rem 1px no-repeat,\n linear-gradient(var(--frontend-stroke), var(--frontend-stroke)) left top / 1px 1.35rem no-repeat,\n linear-gradient(var(--frontend-stroke), var(--frontend-stroke)) right top / 1.35rem 1px no-repeat,\n linear-gradient(var(--frontend-stroke), var(--frontend-stroke)) right top / 1px 1.35rem no-repeat,\n linear-gradient(var(--frontend-stroke), var(--frontend-stroke)) left bottom / 1.35rem 1px no-repeat,\n linear-gradient(var(--frontend-stroke), var(--frontend-stroke)) left bottom / 1px 1.35rem no-repeat,\n linear-gradient(var(--frontend-stroke), var(--frontend-stroke)) right bottom / 1.35rem 1px no-repeat,\n linear-gradient(var(--frontend-stroke), var(--frontend-stroke)) right bottom / 1px 1.35rem no-repeat;\n opacity: 0.58;\n }\n html[data-preset=\"editorial\"] .diagram-container {\n overflow: hidden;\n border-color: var(--panel-border);\n border-radius: 0.38rem;\n background:\n linear-gradient(90deg, color-mix(in srgb, var(--security-fill) 58%, transparent), transparent 12%),\n var(--panel);\n box-shadow:\n 0 1px 0 color-mix(in srgb, var(--text) 6%, transparent),\n 0 18px 48px color-mix(in srgb, #000 19%, transparent);\n }\n html[data-preset=\"editorial\"] .diagram-container::after {\n position: absolute;\n z-index: 10;\n top: 0.72rem;\n right: 1.25rem;\n padding: 0.2rem 0.52rem;\n border: 1px solid var(--panel-border);\n background: var(--panel);\n color: var(--arrow-emphasis);\n content: attr(data-preset-badge-editorial-plate);\n font-family: Georgia, 'Times New Roman', serif;\n font-size: 0.52rem;\n font-style: italic;\n font-weight: 600;\n letter-spacing: 0.09em;\n pointer-events: none;\n }\n html[data-embed=\"true\"][data-preset=\"editorial\"] .diagram-container::after { display: none; }\n html[data-motion-capable=\"true\"][data-preset=\"signal-flow\"][data-ambient-entry=\"running\"] .diagram-container::before {\n content: \"\";\n position: absolute;\n inset: 0;\n pointer-events: none;\n background: linear-gradient(115deg, transparent 28%, rgba(103, 232, 249, 0.035) 48%, transparent 68%);\n transform: translateX(-70%);\n animation: archify-signal-scan 4.8s ease-in-out 1 both;\n }\n\n /* Motion Governor gives the reader a real static state and gives the\n strongest semantic action the only motion budget. Atmosphere resets to\n its authored base instead of freezing mid-cycle. Static artifacts never\n start a decorative pulse or scan. */\n html:not([data-motion-capable=\"true\"]) svg[data-animation=\"trace\"] [data-animate] {\n animation: none !important;\n }\n html:not([data-motion-capable=\"true\"]) svg[data-animation=\"trace\"] [data-graph-role=\"automatic-crossover\"]:has(\u003e [data-animate=\"edge\"]) \u003e [data-graph-role=\"automatic-crossover-underlay\"] {\n animation: none !important;\n }\n html[data-motion=\"still\"] .pulse-dot,\n html[data-motion=\"still\"] .diagram-container::before,\n html[data-motion=\"still\"] svg[data-animation=\"trace\"] [data-animate],\n html[data-motion-owner] .pulse-dot,\n html[data-motion-owner] .diagram-container::before,\n html[data-motion-owner] svg[data-animation=\"trace\"] [data-animate],\n html[data-embed=\"true\"] .pulse-dot,\n html[data-embed=\"true\"] .diagram-container::before,\n html[data-share-playback=\"true\"] .pulse-dot,\n html[data-share-playback=\"true\"] .diagram-container::before,\n html[data-document-hidden=\"true\"] .pulse-dot,\n html[data-document-hidden=\"true\"] .diagram-container::before,\n html[data-document-hidden=\"true\"] svg[data-animation=\"trace\"] [data-animate],\n html[data-motion=\"still\"] svg[data-animation=\"trace\"] [data-graph-role=\"automatic-crossover\"]:has(\u003e [data-animate=\"edge\"]) \u003e [data-graph-role=\"automatic-crossover-underlay\"],\n html[data-motion-owner] svg[data-animation=\"trace\"] [data-graph-role=\"automatic-crossover\"]:has(\u003e [data-animate=\"edge\"]) \u003e [data-graph-role=\"automatic-crossover-underlay\"],\n html[data-embed=\"true\"] svg[data-animation=\"trace\"] [data-graph-role=\"automatic-crossover\"]:has(\u003e [data-animate=\"edge\"]) \u003e [data-graph-role=\"automatic-crossover-underlay\"],\n html[data-share-playback=\"true\"] svg[data-animation=\"trace\"] [data-graph-role=\"automatic-crossover\"]:has(\u003e [data-animate=\"edge\"]) \u003e [data-graph-role=\"automatic-crossover-underlay\"],\n html[data-document-hidden=\"true\"] svg[data-animation=\"trace\"] [data-graph-role=\"automatic-crossover\"]:has(\u003e [data-animate=\"edge\"]) \u003e [data-graph-role=\"automatic-crossover-underlay\"] {\n animation: none !important;\n }\n html[data-motion=\"still\"] .diagram-container::before,\n html[data-motion-owner] .diagram-container::before,\n html[data-embed=\"true\"] .diagram-container::before,\n html[data-share-playback=\"true\"] .diagram-container::before {\n opacity: 0;\n }\n html[data-motion=\"still\"] .intent-trace-flow,\n html[data-motion=\"still\"] .route-probe-flow,\n html[data-motion=\"still\"] .route-journey-flow,\n html[data-motion=\"still\"] .semantic-lens-flow,\n html[data-motion=\"still\"] .diagram-guide,\n html[data-motion=\"still\"] .overview-map-live,\n html[data-document-hidden=\"true\"] .intent-trace-flow,\n html[data-document-hidden=\"true\"] .route-probe-flow,\n html[data-document-hidden=\"true\"] .route-journey-flow,\n html[data-document-hidden=\"true\"] .semantic-lens-flow,\n html[data-document-hidden=\"true\"] .diagram-guide,\n html[data-document-hidden=\"true\"] .overview-map-live {\n animation: none !important;\n }\n\n html[data-motion=\"still\"] .intent-trace-flow,\n html[data-motion=\"still\"] .route-probe-flow {\n stroke-dasharray: none;\n stroke-dashoffset: 0;\n opacity: 0.72;\n }\n html[data-motion=\"still\"] .route-journey-overlay,\n html[data-document-hidden=\"true\"] .route-journey-overlay { display: none !important; }\n html[data-motion=\"still\"] .relationship-pulse-overlay { display: none !important; }\n\n html[data-motion=\"still\"] svg [data-node-id],\n html[data-motion=\"still\"] svg [data-edge-from],\n html[data-motion=\"still\"] svg [data-detail],\n html[data-motion=\"still\"] svg [data-detail-anchor],\n html[data-motion=\"still\"] svg [data-legend-hit],\n html[data-motion=\"still\"] svg {\n transition: none !important;\n }\n\n svg {\n font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, 'DejaVu Sans Mono', 'Liberation Mono', 'Noto Sans Mono CJK SC', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', monospace;\n width: 100%;\n min-width: min(900px, 100%);\n display: block;\n transform-origin: 0 0;\n transition: transform 0.18s ease;\n }\n\n .diagram-container.is-camera-moving svg {\n transition: transform 0.42s cubic-bezier(0.22, 1, 0.36, 1);\n }\n .diagram-container.is-camera-transaction svg { transition: none; }\n .diagram-container.is-pannable { cursor: grab; }\n .diagram-container.is-panning { cursor: grabbing; user-select: none; }\n .diagram-container.is-panning svg { transition: none; }\n\n .diagram-nav {\n position: absolute;\n right: 1rem;\n bottom: 1rem;\n z-index: 12;\n translate: 0 calc(-1 * var(--archify-dock-lift, 0px));\n display: inline-flex;\n align-items: center;\n max-width: calc(100% - 2rem);\n padding: 0.25rem;\n gap: 0.0625rem;\n overflow-x: auto;\n border: 1px solid color-mix(in srgb, var(--panel-border) 80%, transparent);\n border-radius: 0.75rem;\n background: color-mix(in srgb, var(--panel) 84%, transparent);\n box-shadow: 0 1px 2px rgba(15, 23, 42, 0.05), 0 10px 28px -14px rgba(15, 23, 42, 0.28);\n backdrop-filter: blur(14px) saturate(1.2);\n scrollbar-width: none;\n }\n .diagram-nav::-webkit-scrollbar { display: none; }\n .diagram-nav button,\n .focus-chip button {\n border: 0;\n background: transparent;\n color: var(--toolbar-text);\n font: inherit;\n cursor: pointer;\n }\n .diagram-nav button {\n min-width: 2rem;\n height: 2rem;\n padding: 0 0.4rem;\n border-radius: 0.5rem;\n color: var(--text-muted);\n font-size: 0.75rem;\n font-weight: 500;\n letter-spacing: 0;\n transition: background 0.15s, color 0.15s;\n }\n .diagram-nav button:hover,\n .diagram-nav button:focus-visible {\n color: var(--text);\n background: color-mix(in srgb, var(--text) 6%, transparent);\n }\n .diagram-nav button:hover,\n .diagram-nav button:focus-visible,\n .focus-chip button:hover,\n .focus-chip button:focus-visible { background: var(--toolbar-hover); }\n .diagram-nav button:focus-visible,\n .focus-chip button:focus-visible {\n outline: 2px solid var(--frontend-stroke);\n outline-offset: 1px;\n }\n .diagram-nav button:disabled { opacity: 0.38; cursor: default; }\n /* The Guide stays on the ? shortcut; Map only earns dock space once the\n reader has zoomed past the full diagram (or opened it from the keyboard). */\n .diagram-nav button[hidden] { display: none; }\n .diagram-container:not(.is-pannable) #btn-overview-map:not([data-radar-requested]):not(:focus) { display: none; }\n .diagram-nav button:disabled:hover { background: transparent; }\n .diagram-nav #btn-route-probe,\n .diagram-nav #btn-overview-map,\n .diagram-nav #btn-semantic-lens {\n min-width: auto;\n padding-inline: 0.6rem;\n color: var(--text-muted);\n font-weight: 500;\n }\n .diagram-nav #btn-node-finder,\n .diagram-nav [data-view=\"out\"] {\n position: relative;\n margin-left: 0.375rem;\n }\n .diagram-nav #btn-node-finder::before,\n .diagram-nav [data-view=\"out\"]::before {\n position: absolute;\n top: 0.55rem;\n bottom: 0.55rem;\n left: -0.19rem;\n width: 1px;\n background: color-mix(in srgb, var(--panel-border) 90%, transparent);\n content: \"\";\n pointer-events: none;\n }\n .diagram-nav-icon {\n display: block;\n width: 0.95rem;\n height: 0.95rem;\n margin: auto;\n background: currentColor;\n -webkit-mask-position: center;\n -webkit-mask-repeat: no-repeat;\n -webkit-mask-size: contain;\n mask-position: center;\n mask-repeat: no-repeat;\n mask-size: contain;\n }\n .diagram-nav-icon.find {\n -webkit-mask-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Ccircle cx='8.5' cy='8.5' r='4.5' fill='none' stroke='black' stroke-width='1.8'/%3E%3Cpath d='m12 12 4 4' fill='none' stroke='black' stroke-width='1.8' stroke-linecap='round'/%3E%3C/svg%3E\");\n mask-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Ccircle cx='8.5' cy='8.5' r='4.5' fill='none' stroke='black' stroke-width='1.8'/%3E%3Cpath d='m12 12 4 4' fill='none' stroke='black' stroke-width='1.8' stroke-linecap='round'/%3E%3C/svg%3E\");\n }\n .diagram-nav-icon.guide {\n -webkit-mask-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Ccircle cx='10' cy='10' r='7' fill='none' stroke='black' stroke-width='1.7'/%3E%3Cpath d='M7.8 7.3a2.4 2.4 0 014.6.9c0 1.8-2.4 2-2.4 3.7M10 14.9h.01' fill='none' stroke='black' stroke-width='1.7' stroke-linecap='round'/%3E%3C/svg%3E\");\n mask-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Ccircle cx='10' cy='10' r='7' fill='none' stroke='black' stroke-width='1.7'/%3E%3Cpath d='M7.8 7.3a2.4 2.4 0 014.6.9c0 1.8-2.4 2-2.4 3.7M10 14.9h.01' fill='none' stroke='black' stroke-width='1.7' stroke-linecap='round'/%3E%3C/svg%3E\");\n }\n .diagram-nav-icon.minus {\n -webkit-mask-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath d='M5 10h10' fill='none' stroke='black' stroke-width='1.8' stroke-linecap='round'/%3E%3C/svg%3E\");\n mask-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath d='M5 10h10' fill='none' stroke='black' stroke-width='1.8' stroke-linecap='round'/%3E%3C/svg%3E\");\n }\n .diagram-nav-icon.plus {\n -webkit-mask-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath d='M10 5v10M5 10h10' fill='none' stroke='black' stroke-width='1.8' stroke-linecap='round'/%3E%3C/svg%3E\");\n mask-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath d='M10 5v10M5 10h10' fill='none' stroke='black' stroke-width='1.8' stroke-linecap='round'/%3E%3C/svg%3E\");\n }\n .diagram-nav [data-view=\"reset\"] {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 0.2rem;\n min-width: 3.1rem;\n padding-inline: 0.35rem;\n }\n .diagram-nav [data-view=\"reset\"][data-detail-visible] {\n min-width: 3.8rem;\n }\n .diagram-nav-detail {\n color: color-mix(in srgb, var(--toolbar-text) 52%, transparent);\n font-size: 0.44rem;\n font-weight: 700;\n line-height: 1;\n letter-spacing: 0.07em;\n }\n .diagram-nav-percent {\n color: var(--text-muted);\n font-size: 0.75rem;\n font-weight: 500;\n font-variant-numeric: tabular-nums;\n line-height: 1;\n }\n /* Mode buttons carry a small glyph so the dock reads as one icon language. */\n .diagram-nav #btn-route-probe,\n .diagram-nav #btn-overview-map,\n .diagram-nav #btn-semantic-lens {\n display: inline-flex;\n align-items: center;\n gap: 0.375rem;\n padding-inline: 0.55rem 0.65rem;\n }\n .diagram-nav #btn-route-probe::before,\n .diagram-nav #btn-overview-map::before,\n .diagram-nav #btn-semantic-lens::before {\n flex: none;\n width: 0.95rem;\n height: 0.95rem;\n background: currentColor;\n content: \"\";\n opacity: 0.85;\n -webkit-mask: var(--nav-glyph) center / contain no-repeat;\n mask: var(--nav-glyph) center / contain no-repeat;\n }\n .diagram-nav #btn-route-probe { --nav-glyph: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cg fill='none' stroke='black' stroke-width='1.7' stroke-linecap='round'%3E%3Ccircle cx='4.8' cy='14.8' r='2.2'/%3E%3Ccircle cx='15.2' cy='5.2' r='2.2'/%3E%3Cpath d='M6.9 13.9c3.8-1.4 2.6-6.3 6.2-7.8'/%3E%3C/g%3E%3C/svg%3E\"); }\n .diagram-nav #btn-overview-map { --nav-glyph: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cg fill='none' stroke='black' stroke-width='1.7' stroke-linejoin='round'%3E%3Cpath d='M2.8 5.4 7.4 3.5l5.2 2 4.6-1.9v11l-4.6 1.9-5.2-2-4.6 1.9z'/%3E%3Cpath d='M7.4 3.5v11M12.6 5.5v11'/%3E%3C/g%3E%3C/svg%3E\"); }\n .diagram-nav #btn-semantic-lens { --nav-glyph: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cg fill='none' stroke='black' stroke-width='1.7'%3E%3Ccircle cx='10' cy='10' r='6.8'/%3E%3Ccircle cx='10' cy='10' r='2.6'/%3E%3C/g%3E%3C/svg%3E\"); }\n /* Zoom out / level / zoom in join into one stepper well. */\n .diagram-nav [data-view=\"out\"],\n .diagram-nav [data-view=\"reset\"],\n .diagram-nav [data-view=\"in\"] {\n position: relative;\n border-radius: 0;\n background: color-mix(in srgb, var(--panel-border) 38%, transparent);\n }\n .diagram-nav [data-view=\"out\"] { margin-left: 0.375rem; border-radius: 0.5rem 0 0 0.5rem; }\n .diagram-nav [data-view=\"out\"]::before { display: none; }\n .diagram-nav [data-view=\"reset\"],\n .diagram-nav [data-view=\"in\"] { margin-left: -0.0625rem; }\n .diagram-nav [data-view=\"in\"] { border-radius: 0 0.5rem 0.5rem 0; }\n .diagram-nav [data-view=\"reset\"]::after,\n .diagram-nav [data-view=\"in\"]::after {\n position: absolute;\n top: 0.55rem;\n bottom: 0.55rem;\n left: 0;\n width: 1px;\n background: color-mix(in srgb, var(--panel-border) 75%, transparent);\n content: \"\";\n pointer-events: none;\n }\n .diagram-nav [data-view=\"out\"]:hover,\n .diagram-nav [data-view=\"reset\"]:hover,\n .diagram-nav [data-view=\"in\"]:hover { background: color-mix(in srgb, var(--panel-border) 60%, transparent); }\n .diagram-nav [data-view=\"out\"]:disabled,\n .diagram-nav [data-view=\"in\"]:disabled { opacity: 1; color: color-mix(in srgb, var(--text-muted) 40%, transparent); }\n .diagram-nav [data-view=\"out\"]:disabled:hover,\n .diagram-nav [data-view=\"in\"]:disabled:hover { background: color-mix(in srgb, var(--panel-border) 38%, transparent); }\n .diagram-nav [data-view=\"reset\"] { min-width: 3.25rem; }\n .diagram-container[data-camera-indicator=\"true\"] .diagram-nav-detail,\n .diagram-container[data-camera-indicator=\"true\"] .diagram-nav-percent { color: var(--frontend-stroke); }\n .diagram-nav #btn-diagram-guide[aria-expanded=\"true\"] {\n color: var(--cloud-stroke);\n background: var(--toolbar-hover);\n }\n\n /* Semantic Radar is a deliberately simplified, viewer-only overview. It\n uses authored node bounds and the live camera viewport rather than\n cloning the canonical SVG, so IDs, filters, animation, and export state\n stay singular and deterministic. */\n .overview-map {\n position: absolute;\n right: 1rem;\n bottom: calc(4.15rem + var(--archify-nav-reserve));\n z-index: 12;\n width: 11.25rem;\n overflow: hidden;\n border: 1px solid color-mix(in srgb, var(--frontend-stroke) 52%, var(--toolbar-border));\n border-radius: 0.78rem;\n background: var(--toolbar-menu-bg);\n color: var(--toolbar-text);\n box-shadow: 0 16px 42px rgba(0, 0, 0, 0.3);\n backdrop-filter: blur(16px);\n font-size: 0.625rem;\n }\n .overview-map[hidden] { display: none; }\n .overview-map[data-docked=\"true\"] {\n position: fixed;\n right: var(--archify-radar-right, 1rem);\n top: var(--archify-radar-top, 1rem);\n bottom: auto;\n }\n .overview-map[data-docked=\"true\"][data-dock-side=\"left\"] {\n right: auto;\n left: var(--archify-radar-left, 1rem);\n }\n html[data-preset=\"blueprint\"] .overview-map { border-radius: 0.28rem; }\n .overview-map-head {\n display: grid;\n grid-template-columns: auto minmax(0, 1fr) auto;\n align-items: center;\n gap: 0.45rem;\n min-height: 2.25rem;\n padding: 0.42rem 0.48rem 0.38rem 0.55rem;\n border-bottom: 1px solid var(--toolbar-border);\n background: linear-gradient(110deg, color-mix(in srgb, var(--frontend-fill) 45%, transparent), transparent 68%);\n cursor: grab;\n touch-action: none;\n user-select: none;\n }\n .overview-map[data-panel-dragging=\"true\"] .overview-map-head { cursor: grabbing; }\n .overview-map[data-placement-invalid=\"true\"] {\n border-color: var(--security-stroke);\n box-shadow: 0 0 0 2px color-mix(in srgb, var(--security-stroke) 34%, transparent), 0 16px 42px rgba(0, 0, 0, 0.3);\n }\n .overview-map[data-compact=\"true\"] {\n width: 7.4rem;\n }\n .overview-map[data-compact=\"true\"] .overview-map-head {\n grid-template-columns: minmax(0, 1fr) auto;\n gap: 0.24rem;\n min-height: 1.5rem;\n padding: 0.05rem 0.16rem 0.05rem 0.32rem;\n border-bottom: 0;\n }\n .overview-map[data-compact=\"true\"] .overview-map-live,\n .overview-map[data-compact=\"true\"] .overview-map-copy,\n .overview-map[data-compact=\"true\"] .overview-map-surface,\n .overview-map[data-compact=\"true\"] .overview-map-hint {\n display: none;\n }\n .overview-map[data-compact=\"true\"] .overview-map-expand { display: inline-flex; }\n .overview-map[data-compact=\"true\"] .overview-map-close {\n width: 1.15rem;\n height: 1.15rem;\n font-size: 0.72rem;\n }\n .overview-map-live {\n width: 0.42rem;\n height: 0.42rem;\n border-radius: 50%;\n background: var(--backend-stroke);\n box-shadow: 0 0 0 0 color-mix(in srgb, var(--backend-stroke) 52%, transparent);\n animation: archify-radar-live 2.4s ease-out infinite;\n }\n .overview-map-copy { min-width: 0; line-height: 1.1; }\n .overview-map-copy strong {\n display: block;\n overflow: hidden;\n color: var(--frontend-stroke);\n font-size: 0.59rem;\n letter-spacing: 0.12em;\n text-transform: uppercase;\n white-space: nowrap;\n text-overflow: ellipsis;\n }\n .overview-map-copy small {\n display: block;\n margin-top: 0.18rem;\n overflow: hidden;\n color: var(--text-faint);\n font-size: 0.52rem;\n letter-spacing: 0.04em;\n white-space: nowrap;\n text-overflow: ellipsis;\n }\n .overview-map-expand {\n display: none;\n align-items: center;\n justify-content: center;\n min-width: 0;\n height: 1.2rem;\n padding: 0 0.34rem;\n overflow: hidden;\n border: 0;\n border-radius: 0.3rem;\n background: transparent;\n color: var(--frontend-stroke);\n font: inherit;\n font-size: 0.49rem;\n font-weight: 700;\n letter-spacing: 0.06em;\n text-transform: uppercase;\n white-space: nowrap;\n cursor: pointer;\n }\n .overview-map-close {\n width: 1.55rem;\n height: 1.55rem;\n padding: 0;\n border: 0;\n border-radius: 0.34rem;\n background: transparent;\n color: var(--toolbar-text);\n font: inherit;\n font-size: 0.9rem;\n cursor: pointer;\n }\n .overview-map-expand:hover,\n .overview-map-expand:focus-visible,\n .overview-map-close:hover,\n .overview-map-close:focus-visible { background: var(--toolbar-hover); }\n .overview-map-expand:focus-visible,\n .overview-map-close:focus-visible,\n .overview-map-surface:focus-visible,\n .overview-map-node:focus-visible {\n outline: 2px solid var(--frontend-stroke);\n outline-offset: 1px;\n }\n .overview-map-surface {\n position: relative;\n height: 7rem;\n margin: 0.42rem;\n overflow: hidden;\n border: 1px solid color-mix(in srgb, var(--panel-border) 72%, transparent);\n border-radius: 0.42rem;\n background:\n linear-gradient(color-mix(in srgb, var(--panel-border) 26%, transparent) 1px, transparent 1px),\n linear-gradient(90deg, color-mix(in srgb, var(--panel-border) 26%, transparent) 1px, transparent 1px),\n color-mix(in srgb, var(--bg) 86%, transparent);\n background-size: 12px 12px;\n cursor: crosshair;\n touch-action: none;\n }\n html[data-preset=\"blueprint\"] .overview-map-surface { border-radius: 0.1rem; }\n .overview-map-surface \u003e svg {\n width: 100%;\n height: 100%;\n min-width: 0;\n transform: none !important;\n transition: none !important;\n display: block;\n overflow: visible;\n }\n .overview-map-node {\n fill: var(--backend-stroke);\n fill-opacity: 0.52;\n stroke: var(--backend-stroke);\n stroke-opacity: 0.86;\n vector-effect: non-scaling-stroke;\n cursor: pointer;\n transition: fill-opacity 140ms ease, stroke-width 140ms ease, filter 140ms ease;\n }\n .overview-map-node[data-kind=\"frontend\"],\n .overview-map-node[data-kind=\"start\"] { fill: var(--frontend-stroke); stroke: var(--frontend-stroke); }\n .overview-map-node[data-kind=\"database\"],\n .overview-map-node[data-kind=\"success\"] { fill: var(--database-stroke); stroke: var(--database-stroke); }\n .overview-map-node[data-kind=\"cloud\"],\n .overview-map-node[data-kind=\"waiting\"] { fill: var(--cloud-stroke); stroke: var(--cloud-stroke); }\n .overview-map-node[data-kind=\"messagebus\"] { fill: var(--messagebus-stroke); stroke: var(--messagebus-stroke); }\n .overview-map-node[data-kind=\"security\"],\n .overview-map-node[data-kind=\"decision\"],\n .overview-map-node[data-kind=\"failure\"] { fill: var(--security-stroke); stroke: var(--security-stroke); }\n .overview-map-node[data-kind=\"external\"],\n .overview-map-node[data-kind=\"neutral\"] { fill: var(--external-stroke); stroke: var(--external-stroke); }\n .overview-map-node[data-radar-active=\"true\"],\n .overview-map-node:hover,\n .overview-map-node:focus-visible {\n fill-opacity: 0.92;\n stroke-width: 2.2;\n filter: drop-shadow(0 0 3px currentColor);\n }\n .overview-map-viewport {\n fill: color-mix(in srgb, var(--frontend-fill) 56%, transparent);\n stroke: var(--frontend-stroke);\n stroke-width: 2;\n vector-effect: non-scaling-stroke;\n pointer-events: none;\n filter: drop-shadow(0 0 3px color-mix(in srgb, var(--frontend-stroke) 54%, transparent));\n }\n .overview-map-hint {\n display: flex;\n justify-content: space-between;\n gap: 0.5rem;\n padding: 0 0.52rem 0.46rem;\n color: var(--text-faint);\n font-size: 0.49rem;\n letter-spacing: 0.06em;\n text-transform: uppercase;\n }\n .diagram-nav #btn-overview-map[aria-expanded=\"true\"] {\n color: var(--frontend-stroke);\n background: color-mix(in srgb, var(--frontend-stroke) 11%, transparent);\n }\n .overview-map-feedback {\n position: absolute;\n right: 1rem;\n bottom: 4.15rem;\n z-index: 13;\n max-width: min(16rem, calc(100% - 2rem));\n padding: 0.38rem 0.55rem;\n border: 1px solid color-mix(in srgb, var(--frontend-stroke) 52%, var(--toolbar-border));\n border-radius: 0.5rem;\n background: var(--toolbar-menu-bg);\n color: var(--toolbar-text);\n box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2);\n font-size: 0.52rem;\n line-height: 1.35;\n pointer-events: none;\n }\n .overview-map-feedback[hidden] { display: none; }\n .focus-chip[data-radar-yielded=\"true\"] { display: none !important; }\n .diagram-nav #btn-route-probe[aria-pressed=\"true\"] {\n color: var(--backend-stroke);\n background: color-mix(in srgb, var(--backend-stroke) 11%, transparent);\n }\n .diagram-nav #btn-semantic-lens[aria-expanded=\"true\"],\n .diagram-nav #btn-semantic-lens[aria-pressed=\"true\"] {\n color: var(--database-stroke);\n background: color-mix(in srgb, var(--database-stroke) 11%, transparent);\n }\n\n /* Semantic Lens turns the compiled node-kind palette into an analytical\n legend. One kind reveals its real traffic; two kinds compare only their\n direct authored relationships. Geometry and the canonical SVG stay put. */\n .semantic-lens {\n position: absolute;\n right: 1rem;\n bottom: calc(4.15rem + var(--archify-nav-reserve));\n z-index: 24;\n width: min(20rem, calc(100% - 2rem));\n overflow: hidden;\n border: 1px solid color-mix(in srgb, var(--database-stroke) 58%, var(--toolbar-border));\n border-radius: 0.82rem;\n background:\n linear-gradient(135deg, color-mix(in srgb, var(--database-fill) 34%, transparent), transparent 48%),\n var(--toolbar-menu-bg);\n color: var(--toolbar-text);\n box-shadow: 0 18px 52px rgba(0, 0, 0, 0.36);\n backdrop-filter: blur(18px);\n animation: archify-lens-in 180ms cubic-bezier(0.22, 1, 0.36, 1);\n }\n .semantic-lens[hidden] { display: none; }\n .semantic-lens[data-dock-side=\"left\"] { left: 1rem; right: auto; }\n .semantic-lens[data-dock-side=\"right\"] { left: auto; right: 1rem; }\n html[data-preset=\"blueprint\"] .semantic-lens { border-radius: 0.28rem; }\n .semantic-lens-head {\n display: grid;\n grid-template-columns: minmax(0, 1fr) auto;\n align-items: start;\n gap: 0.75rem;\n padding: 0.72rem 0.78rem 0.62rem;\n border-bottom: 1px solid var(--toolbar-border);\n }\n .semantic-lens-eyebrow {\n display: block;\n color: var(--database-stroke);\n font-size: 0.5rem;\n font-weight: 800;\n letter-spacing: 0.16em;\n text-transform: uppercase;\n }\n .semantic-lens-title {\n display: block;\n margin-top: 0.16rem;\n color: var(--text);\n font-size: 0.82rem;\n letter-spacing: -0.02em;\n }\n .semantic-lens-close {\n width: 1.75rem;\n height: 1.75rem;\n padding: 0;\n border: 0;\n border-radius: 0.35rem;\n background: transparent;\n color: var(--toolbar-text);\n font: inherit;\n font-size: 1rem;\n cursor: pointer;\n }\n .semantic-lens-close:hover,\n .semantic-lens-close:focus-visible { background: var(--toolbar-hover); }\n .semantic-lens-instruction {\n margin: 0;\n padding: 0.52rem 0.78rem 0.48rem;\n color: var(--text-faint);\n font-size: 0.55rem;\n line-height: 1.45;\n }\n .semantic-lens-kinds {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 0.3rem;\n max-height: min(15rem, 40vh);\n padding: 0 0.5rem 0.5rem;\n overflow-y: auto;\n scrollbar-width: thin;\n }\n .semantic-lens-kind {\n --lens-color: var(--external-stroke);\n display: grid;\n grid-template-columns: auto minmax(0, 1fr) auto;\n align-items: center;\n gap: 0.42rem;\n min-width: 0;\n min-height: 2.15rem;\n padding: 0.35rem 0.42rem;\n border: 1px solid color-mix(in srgb, var(--lens-color) 24%, var(--toolbar-border));\n border-radius: 0.45rem;\n background: color-mix(in srgb, var(--panel) 76%, transparent);\n color: var(--toolbar-text);\n font: inherit;\n text-align: left;\n cursor: pointer;\n transition: border-color 140ms ease, background 140ms ease, opacity 140ms ease;\n }\n html[data-preset=\"blueprint\"] .semantic-lens-kind { border-radius: 0.12rem; }\n .semantic-lens-kind[data-kind=\"frontend\"],\n .semantic-lens-kind[data-kind=\"start\"] { --lens-color: var(--frontend-stroke); }\n .semantic-lens-kind[data-kind=\"backend\"],\n .semantic-lens-kind[data-kind=\"active\"] { --lens-color: var(--backend-stroke); }\n .semantic-lens-kind[data-kind=\"database\"],\n .semantic-lens-kind[data-kind=\"success\"] { --lens-color: var(--database-stroke); }\n .semantic-lens-kind[data-kind=\"cloud\"],\n .semantic-lens-kind[data-kind=\"waiting\"] { --lens-color: var(--cloud-stroke); }\n .semantic-lens-kind[data-kind=\"messagebus\"] { --lens-color: var(--messagebus-stroke); }\n .semantic-lens-kind[data-kind=\"security\"],\n .semantic-lens-kind[data-kind=\"decision\"],\n .semantic-lens-kind[data-kind=\"failure\"] { --lens-color: var(--security-stroke); }\n .semantic-lens-kind:hover,\n .semantic-lens-kind:focus-visible,\n .semantic-lens-kind[aria-pressed=\"true\"] {\n border-color: color-mix(in srgb, var(--lens-color) 78%, var(--toolbar-border));\n background: color-mix(in srgb, var(--lens-color) 11%, var(--panel));\n }\n .semantic-lens-kind:focus-visible,\n .semantic-lens-close:focus-visible,\n .semantic-lens-actions button:focus-visible {\n outline: 2px solid var(--database-stroke);\n outline-offset: 1px;\n }\n .semantic-lens-kind:disabled { opacity: 0.34; cursor: default; }\n .semantic-lens-swatch {\n width: 0.48rem;\n height: 0.48rem;\n border-radius: 50%;\n background: var(--lens-color);\n box-shadow: 0 0 8px color-mix(in srgb, var(--lens-color) 58%, transparent);\n }\n .semantic-lens-kind strong {\n overflow: hidden;\n color: var(--text);\n font-size: 0.61rem;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n .semantic-lens-kind em {\n color: var(--lens-color);\n font-size: 0.54rem;\n font-style: normal;\n font-weight: 800;\n }\n .semantic-lens-status {\n min-height: 2.5rem;\n margin: 0;\n padding: 0.55rem 0.78rem;\n border-top: 1px solid var(--toolbar-border);\n color: var(--text-faint);\n font-size: 0.55rem;\n line-height: 1.45;\n }\n .semantic-lens-status strong { color: var(--database-stroke); font-weight: 800; }\n .semantic-lens-actions {\n display: flex;\n justify-content: flex-end;\n gap: 0.28rem;\n padding: 0 0.62rem 0.62rem;\n }\n .semantic-lens-actions button {\n min-height: 1.8rem;\n padding: 0.28rem 0.48rem;\n border: 0;\n border-radius: 0.35rem;\n background: transparent;\n color: var(--toolbar-text);\n font: inherit;\n font-size: 0.56rem;\n cursor: pointer;\n }\n .semantic-lens-actions button:hover { background: var(--toolbar-hover); }\n .semantic-lens-actions #semantic-lens-copy { color: var(--database-stroke); }\n @keyframes archify-lens-in {\n from { opacity: 0; transform: translateY(5px) scale(0.985); }\n to { opacity: 1; transform: translateY(0) scale(1); }\n }\n\n /* Route Probe answers a reader question over the compiled semantic graph:\n choose two stable nodes and reveal the shortest authored directed route.\n The receipt and signal live entirely in the viewer layer. */\n .route-probe {\n position: absolute;\n left: 1rem;\n bottom: calc(1rem + var(--archify-nav-reserve));\n z-index: 12;\n width: min(46rem, calc(100% - 25rem));\n min-width: 18rem;\n overflow: hidden;\n border: 1px solid color-mix(in srgb, var(--backend-stroke) 62%, var(--toolbar-border));\n border-radius: 0.82rem;\n background:\n linear-gradient(115deg, color-mix(in srgb, var(--backend-fill) 50%, transparent), transparent 62%),\n var(--toolbar-menu-bg);\n color: var(--toolbar-text);\n box-shadow: 0 18px 48px rgba(0, 0, 0, 0.3);\n backdrop-filter: blur(16px);\n font-size: 0.75rem;\n }\n .route-probe[hidden] { display: none; }\n /* Picking needs one line of guidance, not a wide empty card; the panel\n widens only when there is a traced route to show. */\n .route-probe:not([data-state=\"result\"]) { width: min(22rem, calc(100% - 2rem)); min-width: 0; }\n .route-probe-path:has(\u003e .route-probe-placeholder:only-child) { display: none; }\n .route-probe[data-route-dock=\"top\"] {\n top: 1rem;\n bottom: auto;\n }\n html[data-preset=\"blueprint\"] .route-probe { border-radius: 0.25rem; }\n .route-probe-head {\n display: grid;\n grid-template-columns: minmax(0, 1fr) auto;\n align-items: start;\n gap: 0.65rem;\n padding: 0.625rem 0.75rem 0.5rem;\n border-bottom: 1px solid color-mix(in srgb, var(--toolbar-border) 80%, transparent);\n }\n .route-probe-copy { min-width: 0; }\n .route-probe-eyebrow {\n display: block;\n color: var(--backend-stroke);\n font-size: 0.6875rem;\n font-weight: 600;\n letter-spacing: 0.02em;\n }\n .route-probe-title {\n display: block;\n margin-top: 0.14rem;\n overflow: hidden;\n color: var(--toolbar-text);\n font-size: 0.875rem;\n font-weight: 600;\n line-height: 1.3;\n white-space: nowrap;\n text-overflow: ellipsis;\n }\n .route-probe-actions { display: flex; gap: 0.2rem; }\n .route-probe button {\n min-height: 1.55rem;\n padding: 0.25rem 0.42rem;\n border: 0;\n border-radius: 0.36rem;\n background: transparent;\n color: var(--toolbar-text);\n font: inherit;\n cursor: pointer;\n }\n .route-probe button:hover,\n .route-probe button:focus-visible { background: var(--toolbar-hover); }\n .route-probe button:focus-visible {\n outline: 2px solid var(--backend-stroke);\n outline-offset: 1px;\n }\n .route-probe-copy-link { color: var(--frontend-stroke) !important; }\n .route-probe-find { color: var(--backend-stroke) !important; }\n .route-probe-copy-link[hidden] { display: none; }\n .route-probe-find[hidden] { display: none; }\n .route-probe-path {\n display: flex;\n align-items: center;\n min-height: 2.2rem;\n padding: 0.42rem 0.58rem 0.24rem;\n overflow-x: auto;\n scrollbar-width: thin;\n white-space: nowrap;\n }\n .route-probe-node,\n .route-probe button.route-probe-node {\n flex: none;\n max-width: 9.5rem;\n overflow: hidden;\n min-height: 1.75rem !important;\n padding: 0.22rem 0.38rem;\n border: 1px solid color-mix(in srgb, var(--backend-stroke) 46%, var(--toolbar-border));\n border-radius: 999px;\n background: color-mix(in srgb, var(--backend-fill) 52%, transparent);\n color: var(--text);\n font-size: 0.56rem;\n font-weight: 700;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n .route-probe-node[data-endpoint=\"start\"] {\n border-color: var(--frontend-stroke);\n color: var(--frontend-stroke);\n }\n .route-probe-node[data-endpoint=\"end\"] {\n border-color: var(--security-stroke);\n color: var(--security-stroke);\n }\n .route-probe-node[data-route-journey-state=\"past\"] { opacity: 0.66; }\n .route-probe-node[data-route-journey-state=\"future\"] { opacity: 0.48; }\n .route-probe-node[aria-current=\"step\"] {\n border-color: var(--backend-stroke);\n background: color-mix(in srgb, var(--backend-fill) 82%, var(--toolbar-menu-bg));\n color: var(--text);\n box-shadow: 0 0 0 1px color-mix(in srgb, var(--backend-stroke) 42%, transparent), 0 0 14px color-mix(in srgb, var(--backend-stroke) 34%, transparent);\n opacity: 1;\n }\n html[data-preset=\"blueprint\"] .route-probe-node[aria-current=\"step\"] {\n border-style: double;\n border-width: 3px;\n border-radius: 0.2rem;\n box-shadow: none;\n }\n .route-probe-arrow {\n flex: none;\n padding: 0 0.28rem;\n color: var(--text-faint);\n font-size: 0.65rem;\n }\n .route-probe-placeholder {\n color: var(--text-faint);\n font-size: 0.75rem;\n }\n .route-probe-status {\n display: block;\n min-height: 1.45rem;\n padding: 0.5rem 0.75rem 0.625rem;\n color: var(--text-muted, var(--text-faint));\n font-size: 0.75rem;\n line-height: 1.45;\n }\n .route-journey-controls {\n display: grid;\n grid-template-columns: 2rem minmax(5.8rem, auto) 2rem minmax(4.8rem, auto);\n justify-content: start;\n gap: 0.22rem;\n padding: 0.08rem 0.58rem 0.28rem;\n }\n .route-journey-controls[hidden] { display: none; }\n .route-journey-controls button {\n min-height: 1.8rem;\n border: 1px solid color-mix(in srgb, var(--toolbar-border) 86%, transparent);\n background: color-mix(in srgb, var(--toolbar-bg) 72%, transparent);\n font-size: 0.54rem;\n font-weight: 700;\n }\n .route-journey-controls button:disabled {\n cursor: default;\n opacity: 0.34;\n }\n .route-journey-play { color: var(--backend-stroke) !important; }\n .route-journey-overview { color: var(--frontend-stroke) !important; }\n .route-journey-controls[data-playing=\"true\"] .route-journey-play {\n border-color: color-mix(in srgb, var(--backend-stroke) 64%, var(--toolbar-border));\n background: color-mix(in srgb, var(--backend-fill) 62%, transparent);\n }\n .route-probe[data-state=\"error\"] {\n border-color: color-mix(in srgb, var(--security-stroke) 72%, var(--toolbar-border));\n }\n .route-probe[data-state=\"error\"] .route-probe-status { color: var(--security-stroke); }\n\n .focus-chip {\n position: absolute;\n left: 1rem;\n top: 1rem;\n z-index: 12;\n display: block;\n width: min(22rem, calc(100% - 13rem));\n max-width: calc(100% - 13rem);\n overflow: hidden;\n border: 1px solid var(--frontend-stroke);\n border-radius: 0.85rem;\n background: var(--toolbar-menu-bg);\n color: var(--toolbar-text);\n box-shadow: 0 18px 48px rgba(0, 0, 0, 0.3);\n backdrop-filter: blur(16px);\n font-size: 0.6875rem;\n }\n .focus-chip[hidden] { display: none; }\n html[data-preset=\"blueprint\"] .focus-chip { border-radius: 0.3rem; }\n .focus-chip button {\n flex: none;\n padding: 0.3rem 0.45rem;\n border-radius: 0.35rem;\n font-size: 0.625rem;\n }\n .relationship-lens-head {\n position: relative;\n display: grid;\n grid-template-columns: minmax(0, 1fr) auto;\n align-items: start;\n gap: 0.75rem;\n padding: 0.9rem 0.75rem 0.65rem;\n border-bottom: 1px solid var(--toolbar-border);\n background: linear-gradient(120deg, color-mix(in srgb, var(--frontend-fill) 34%, transparent), transparent 72%);\n }\n .relationship-lens-drag-handle {\n position: absolute;\n top: 0.12rem;\n left: 50%;\n width: 2.5rem;\n height: 0.78rem;\n padding: 0 !important;\n transform: translateX(-50%);\n color: var(--text-faint) !important;\n cursor: grab;\n touch-action: none;\n user-select: none;\n }\n .relationship-lens-drag-handle::before {\n content: \"\";\n display: block;\n width: 1.25rem;\n margin: auto;\n border-top: 2px dotted currentColor;\n opacity: 0.72;\n }\n .focus-chip[data-panel-dragging=\"true\"] .relationship-lens-drag-handle { cursor: grabbing; }\n @media (hover: none), (pointer: coarse) {\n .relationship-lens-drag-handle { display: none; }\n }\n .relationship-lens-copy { min-width: 0; }\n .relationship-lens-actions {\n display: flex;\n flex-direction: column;\n align-items: flex-end;\n gap: 0.18rem;\n }\n .relationship-lens-actions button { white-space: nowrap; }\n .relationship-lens-actions #btn-focus-clear {\n display: grid;\n width: 1.75rem;\n height: 1.75rem;\n padding: 0;\n place-items: center;\n color: var(--text-muted);\n font-size: 1rem;\n line-height: 1;\n }\n .relationship-lens-actions #btn-focus-clear:hover,\n .relationship-lens-actions #btn-focus-clear:focus-visible {\n background: var(--toolbar-hover);\n color: var(--text);\n }\n .relationship-lens-actions #btn-focus-copy { color: var(--frontend-stroke); }\n .relationship-lens-actions #btn-focus-relations { display: none; }\n .relationship-lens-eyebrow {\n display: block;\n margin-bottom: 0.18rem;\n color: var(--frontend-stroke);\n font-size: 0.5rem;\n font-weight: 800;\n letter-spacing: 0.14em;\n text-transform: uppercase;\n }\n .relationship-lens-title {\n display: block;\n overflow: hidden;\n color: var(--text);\n font-size: 0.8rem;\n line-height: 1.35;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n .semantic-passport-detail {\n display: block;\n margin-top: 0.12rem;\n overflow: hidden;\n color: var(--text-muted);\n font-size: 0.625rem;\n line-height: 1.4;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n .semantic-passport-detail[hidden] { display: none; }\n .semantic-passport-meta {\n display: flex;\n flex-wrap: wrap;\n gap: 0.24rem;\n margin-top: 0.42rem;\n }\n .semantic-passport-meta span,\n .semantic-passport-meta code {\n max-width: 100%;\n overflow: hidden;\n padding: 0.14rem 0.34rem;\n border: 1px solid color-mix(in srgb, var(--toolbar-border) 82%, transparent);\n border-radius: 999px;\n background: color-mix(in srgb, var(--panel) 68%, transparent);\n color: var(--text-faint);\n font: inherit;\n font-size: 0.5rem;\n letter-spacing: 0.04em;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n .semantic-passport-meta [data-passport=\"kind\"] {\n border-color: color-mix(in srgb, var(--frontend-stroke) 50%, var(--toolbar-border));\n color: var(--frontend-stroke);\n font-weight: 800;\n text-transform: uppercase;\n }\n .semantic-passport-meta [data-passport=\"tag\"] { color: var(--cloud-stroke); }\n .semantic-passport-meta [data-passport=\"brand\"] {\n border-color: color-mix(in srgb, var(--messagebus-stroke) 52%, var(--toolbar-border));\n color: var(--messagebus-stroke);\n font-weight: 700;\n }\n .semantic-passport-meta [hidden] { display: none; }\n html[data-preset=\"blueprint\"] .semantic-passport-meta span,\n html[data-preset=\"blueprint\"] .semantic-passport-meta code { border-radius: 0.1rem; }\n /* 跨图下钻:Passport 列出全部子图;首项即 Ctrl/双击默认目标 */\n .semantic-passport-drilldowns {\n display: grid;\n gap: 0.28rem;\n margin-top: 0.52rem;\n padding-top: 0.48rem;\n border-top: 1px solid color-mix(in srgb, var(--toolbar-border) 76%, transparent);\n }\n .semantic-passport-drilldowns[hidden] { display: none; }\n .semantic-passport-drilldowns-label {\n color: var(--text-faint);\n font-size: 0.5rem;\n font-weight: 800;\n letter-spacing: 0.1em;\n text-transform: uppercase;\n }\n .semantic-passport-drilldowns-list {\n display: grid;\n gap: 0.22rem;\n }\n .semantic-passport-drilldown {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 0.5rem;\n min-height: 2rem;\n padding: 0.32rem 0.5rem;\n border: 1px solid color-mix(in srgb, var(--toolbar-border) 86%, transparent);\n border-radius: 0.35rem;\n background: color-mix(in srgb, var(--panel) 70%, transparent);\n color: var(--text-muted);\n text-decoration: none;\n font-size: 0.6875rem;\n font-weight: 600;\n }\n .semantic-passport-drilldown:hover,\n .semantic-passport-drilldown:focus-visible {\n color: var(--backend-stroke);\n border-color: color-mix(in srgb, var(--backend-stroke) 42%, var(--toolbar-border));\n }\n .semantic-passport-drilldown:focus-visible {\n outline: 2px solid color-mix(in srgb, var(--backend-stroke) 55%, transparent);\n outline-offset: 1px;\n }\n .semantic-passport-drilldown small {\n color: var(--text-faint);\n font-size: 0.5625rem;\n font-weight: 700;\n letter-spacing: 0.04em;\n text-transform: uppercase;\n }\n .semantic-passport-drilldowns-hint {\n color: var(--text-faint);\n font-size: 0.5625rem;\n line-height: 1.35;\n }\n /* 返回上一级:放在页眉显眼位置,不进 Passport */\n .parent-back-bar {\n display: flex;\n align-items: center;\n gap: 0.65rem;\n margin: 0 0 0.85rem;\n }\n .parent-back-bar[hidden] { display: none !important; }\n .parent-back-bar button {\n display: inline-flex;\n align-items: center;\n gap: 0.45rem;\n min-height: 2.35rem;\n padding: 0.4rem 0.9rem 0.4rem 0.7rem;\n border: 1px solid color-mix(in srgb, var(--backend-stroke) 48%, var(--toolbar-border));\n border-radius: 0.55rem;\n background: color-mix(in srgb, var(--backend-stroke) 14%, var(--panel));\n color: var(--backend-stroke);\n font-size: 0.8125rem;\n font-weight: 700;\n letter-spacing: 0.01em;\n cursor: pointer;\n box-shadow: 0 0 0 1px color-mix(in srgb, var(--backend-stroke) 12%, transparent);\n }\n .parent-back-bar button:hover,\n .parent-back-bar button:focus-visible {\n background: color-mix(in srgb, var(--backend-stroke) 22%, var(--panel));\n border-color: color-mix(in srgb, var(--backend-stroke) 68%, var(--toolbar-border));\n color: var(--text);\n }\n .parent-back-bar button:focus-visible {\n outline: 2px solid color-mix(in srgb, var(--backend-stroke) 55%, transparent);\n outline-offset: 2px;\n }\n .parent-back-bar button .parent-back-arrow {\n font-size: 1rem;\n line-height: 1;\n opacity: 0.92;\n }\n html[data-preset=\"blueprint\"] .parent-back-bar button { border-radius: 0.15rem; }\n svg [data-node-id][data-node-drilldowns] { cursor: pointer; }\n /* 单目标下钻:原生 SVG \u003ca\u003e 不画下划线;角标必须跟 SVG 一起导出 */\n svg a.archify-drilldown-link {\n text-decoration: none;\n color: inherit;\n cursor: pointer;\n }\n svg .archify-drilldown-mark-disc {\n fill: color-mix(in srgb, var(--backend-stroke) 88%, var(--panel));\n stroke: color-mix(in srgb, var(--backend-stroke) 55%, var(--toolbar-border));\n stroke-width: 1;\n }\n svg .archify-drilldown-mark-arrow {\n stroke: var(--panel);\n stroke-width: 1.6;\n stroke-linecap: round;\n stroke-linejoin: round;\n }\n\n .semantic-passport-evidence {\n margin-top: 0.52rem;\n padding: 0.5rem;\n border: 1px solid color-mix(in srgb, var(--backend-stroke) 32%, var(--toolbar-border));\n border-radius: 0.55rem;\n background: color-mix(in srgb, var(--backend-fill) 18%, var(--panel));\n }\n .semantic-passport-evidence[hidden] { display: none; }\n html[data-preset=\"blueprint\"] .semantic-passport-evidence { border-radius: 0.18rem; }\n .semantic-passport-evidence-head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 0.55rem;\n margin-bottom: 0.35rem;\n }\n .semantic-passport-evidence-status {\n display: inline-flex;\n align-items: center;\n gap: 0.28rem;\n color: var(--backend-stroke);\n font-size: 0.5rem;\n font-weight: 800;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n }\n .semantic-passport-evidence-status::before {\n content: '';\n width: 0.38rem;\n height: 0.38rem;\n border-radius: 50%;\n background: var(--backend-stroke);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--backend-stroke) 14%, transparent);\n }\n .semantic-passport-repository {\n min-width: 0;\n overflow: hidden;\n color: var(--text-faint);\n font-size: 0.5rem;\n text-decoration: none;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n .semantic-passport-repository:hover,\n .semantic-passport-repository:focus-visible { color: var(--backend-stroke); }\n .semantic-passport-evidence-links { display: grid; gap: 0.22rem; }\n .semantic-passport-source {\n display: grid;\n grid-template-columns: minmax(0, 1fr) auto;\n gap: 0.15rem 0.45rem;\n align-items: center;\n padding: 0.34rem 0.4rem;\n border: 1px solid transparent;\n border-radius: 0.38rem;\n color: var(--text-muted);\n text-decoration: none;\n }\n a.semantic-passport-source:hover,\n a.semantic-passport-source:focus-visible {\n border-color: color-mix(in srgb, var(--backend-stroke) 42%, transparent);\n background: color-mix(in srgb, var(--backend-fill) 32%, transparent);\n color: var(--text);\n }\n .semantic-passport-source strong {\n overflow: hidden;\n font-size: 0.5625rem;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n .semantic-passport-source code {\n color: var(--backend-stroke);\n font: inherit;\n font-size: 0.5rem;\n }\n .semantic-passport-source small {\n grid-column: 1 / -1;\n overflow: hidden;\n color: var(--text-faint);\n font-size: 0.5rem;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n .relationship-lens-summary {\n display: block;\n margin-top: 0.12rem;\n color: var(--text-faint);\n font-size: 0.5625rem;\n }\n .semantic-passport-reach {\n display: grid;\n gap: 0.28rem;\n margin-top: 0.52rem;\n padding-top: 0.48rem;\n border-top: 1px solid color-mix(in srgb, var(--toolbar-border) 76%, transparent);\n }\n .semantic-passport-reach[hidden] { display: none; }\n .semantic-passport-reach-label {\n color: var(--text-faint);\n font-size: 0.5rem;\n font-weight: 800;\n letter-spacing: 0.1em;\n text-transform: uppercase;\n }\n .semantic-passport-reach-actions {\n display: grid;\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: 0.25rem;\n }\n .semantic-passport-reach-actions button {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 0.5rem;\n min-width: 0;\n min-height: 2rem;\n padding: 0.32rem 0.42rem;\n border: 1px solid color-mix(in srgb, var(--toolbar-border) 86%, transparent);\n background: color-mix(in srgb, var(--panel) 70%, transparent);\n color: var(--text-muted);\n text-align: left;\n }\n .semantic-passport-reach-actions button:hover:not(:disabled),\n .semantic-passport-reach-actions button:focus-visible {\n border-color: color-mix(in srgb, var(--frontend-stroke) 62%, var(--toolbar-border));\n color: var(--text);\n }\n .semantic-passport-reach-actions button:focus-visible {\n outline: 2px solid var(--frontend-stroke);\n outline-offset: 1px;\n }\n .semantic-passport-reach-actions button:disabled {\n cursor: default;\n opacity: 0.5;\n }\n .semantic-passport-reach-actions button[aria-pressed=\"true\"] {\n border-color: color-mix(in srgb, var(--reach-color, var(--frontend-stroke)) 72%, var(--toolbar-border));\n background: color-mix(in srgb, var(--reach-fill, var(--frontend-fill)) 48%, transparent);\n color: var(--text);\n }\n #btn-reach-upstream {\n --reach-color: var(--database-stroke);\n --reach-fill: var(--database-fill);\n }\n #btn-reach-downstream {\n --reach-color: var(--backend-stroke);\n --reach-fill: var(--backend-fill);\n }\n .semantic-passport-reach-actions strong {\n color: var(--reach-color, var(--frontend-stroke));\n font-size: 0.625rem;\n }\n .semantic-passport-reach-status {\n color: var(--text-faint);\n font-size: 0.525rem;\n line-height: 1.4;\n }\n .semantic-passport-reach-status[hidden] { display: none; }\n .relationship-lens-list {\n max-height: min(18rem, 46vh);\n overflow: auto;\n padding: 0.4rem;\n scrollbar-width: thin;\n scrollbar-color: var(--frontend-stroke) transparent;\n }\n .relationship-lens-list::-webkit-scrollbar { width: 4px; }\n .relationship-lens-list::-webkit-scrollbar-thumb {\n border-radius: 999px;\n background: color-mix(in srgb, var(--frontend-stroke) 68%, transparent);\n }\n .relationship-lens-group + .relationship-lens-group { margin-top: 0.35rem; }\n .relationship-lens-group-title {\n display: block;\n padding: 0.24rem 0.38rem 0.16rem;\n color: var(--text-faint);\n font-size: 0.5rem;\n font-weight: 800;\n letter-spacing: 0.12em;\n text-transform: uppercase;\n }\n .relationship-lens-row {\n display: grid;\n grid-template-columns: 2.1rem minmax(0, 1fr);\n gap: 0.06rem 0.45rem;\n width: 100%;\n padding: 0.45rem 0.5rem !important;\n text-align: left;\n }\n .relationship-lens-row:hover,\n .relationship-lens-row:focus-visible,\n .relationship-lens-row[data-preview-active=\"true\"] {\n background: var(--toolbar-hover);\n box-shadow: inset 2px 0 0 var(--frontend-stroke);\n }\n .relationship-lens-row:focus-visible {\n outline: 2px solid var(--frontend-stroke);\n outline-offset: -2px;\n }\n .relationship-lens-direction {\n grid-row: 1 / 3;\n align-self: center;\n color: var(--frontend-stroke);\n font-size: 0.5rem;\n font-weight: 800;\n letter-spacing: 0.08em;\n }\n .relationship-lens-row[data-direction=\"in\"] .relationship-lens-direction { color: var(--database-stroke); }\n .relationship-lens-row[data-direction=\"loop\"] .relationship-lens-direction { color: var(--security-stroke); }\n .relationship-lens-row strong {\n overflow: hidden;\n color: var(--text);\n font-size: 0.6875rem;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n .relationship-lens-row small {\n overflow: hidden;\n color: var(--text-faint);\n font-size: 0.5625rem;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n .relationship-lens-empty {\n margin: 0;\n padding: 0.75rem;\n color: var(--text-faint);\n font-size: 0.625rem;\n text-align: center;\n }\n\n /* Diagram Guide keeps the artifact's growing reader vocabulary\n discoverable without widening the permanent control bar. It derives\n honest counts at runtime and delegates every action to an existing\n production interaction instead of creating a second command system. */\n .diagram-guide {\n position: absolute;\n top: 1rem;\n right: 1rem;\n z-index: 26;\n width: min(32rem, calc(100% - 2rem));\n max-height: min(42rem, calc(100% - 2rem));\n overflow: auto;\n border: 1px solid color-mix(in srgb, var(--cloud-stroke) 58%, var(--toolbar-border));\n border-radius: 0.82rem;\n background:\n linear-gradient(135deg, color-mix(in srgb, var(--cloud-fill) 32%, transparent), transparent 42%),\n var(--toolbar-menu-bg);\n color: var(--toolbar-text);\n box-shadow: 0 22px 64px rgba(0, 0, 0, 0.4);\n backdrop-filter: blur(18px);\n animation: archify-guide-in 180ms cubic-bezier(0.22, 1, 0.36, 1);\n }\n .diagram-guide[hidden] { display: none; }\n html[data-preset=\"blueprint\"] .diagram-guide { border-radius: 0.28rem; }\n .diagram-guide-head {\n display: grid;\n grid-template-columns: minmax(0, 1fr) auto;\n align-items: start;\n gap: 1rem;\n padding: 0.82rem 0.9rem 0.68rem;\n border-bottom: 1px solid var(--toolbar-border);\n }\n .diagram-guide-eyebrow {\n display: block;\n color: var(--cloud-stroke);\n font-size: 0.5rem;\n font-weight: 800;\n letter-spacing: 0.16em;\n text-transform: uppercase;\n }\n .diagram-guide-title {\n display: block;\n margin-top: 0.16rem;\n color: var(--text);\n font-size: 0.86rem;\n letter-spacing: -0.02em;\n }\n .diagram-guide-close {\n width: 1.8rem;\n height: 1.8rem;\n border: 0;\n border-radius: 0.35rem;\n background: transparent;\n color: var(--toolbar-text);\n font: inherit;\n font-size: 1rem;\n cursor: pointer;\n }\n .diagram-guide-close:hover,\n .diagram-guide-close:focus-visible { background: var(--toolbar-hover); }\n .diagram-guide-stats {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n margin: 0;\n padding: 0.54rem 0.9rem;\n border-bottom: 1px solid color-mix(in srgb, var(--toolbar-border) 74%, transparent);\n color: var(--text-faint);\n font-size: 0.56rem;\n letter-spacing: 0.025em;\n }\n .diagram-guide-stats::before {\n content: '';\n width: 0.42rem;\n height: 0.42rem;\n flex: none;\n border-radius: 50%;\n background: var(--backend-stroke);\n box-shadow: 0 0 10px color-mix(in srgb, var(--backend-stroke) 72%, transparent);\n }\n .diagram-guide-actions {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 0.38rem;\n padding: 0.62rem;\n }\n .diagram-guide-action {\n --guide-accent: var(--frontend-stroke);\n display: grid;\n grid-template-columns: auto minmax(0, 1fr) auto;\n align-items: center;\n gap: 0.55rem;\n min-height: 4.15rem;\n padding: 0.55rem 0.58rem;\n border: 1px solid color-mix(in srgb, var(--guide-accent) 22%, var(--toolbar-border));\n border-radius: 0.55rem;\n background: color-mix(in srgb, var(--panel) 78%, transparent);\n color: var(--toolbar-text);\n font: inherit;\n text-align: left;\n cursor: pointer;\n transition: border-color 140ms ease, background 140ms ease, transform 140ms ease;\n }\n .diagram-guide-action[data-guide-action=\"route\"] { --guide-accent: var(--backend-stroke); }\n .diagram-guide-action[data-guide-action=\"map\"] { --guide-accent: var(--database-stroke); }\n .diagram-guide-action[data-guide-action=\"lens\"] { --guide-accent: var(--messagebus-stroke); }\n\n .diagram-guide-action[data-guide-action=\"present\"] {\n --guide-accent: var(--cloud-stroke);\n }\n .diagram-guide-action:hover,\n .diagram-guide-action:focus-visible {\n border-color: color-mix(in srgb, var(--guide-accent) 76%, var(--toolbar-border));\n background: color-mix(in srgb, var(--guide-accent) 9%, var(--panel));\n transform: translateY(-1px);\n }\n .diagram-guide-action:focus-visible,\n .diagram-guide-close:focus-visible {\n outline: 2px solid var(--cloud-stroke);\n outline-offset: 1px;\n }\n .diagram-guide-action:disabled {\n opacity: 0.42;\n cursor: default;\n transform: none;\n }\n .diagram-guide-index {\n display: grid;\n place-items: center;\n width: 1.65rem;\n height: 1.65rem;\n border: 1px solid color-mix(in srgb, var(--guide-accent) 64%, var(--toolbar-border));\n border-radius: 0.35rem;\n color: var(--guide-accent);\n font-size: 0.5rem;\n font-weight: 800;\n letter-spacing: 0.06em;\n }\n .diagram-guide-action-copy { min-width: 0; }\n .diagram-guide-action strong {\n display: block;\n overflow: hidden;\n color: var(--text);\n font-size: 0.66rem;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n .diagram-guide-action small {\n display: -webkit-box;\n margin-top: 0.18rem;\n overflow: hidden;\n color: var(--text-faint);\n font-size: 0.5rem;\n line-height: 1.35;\n -webkit-box-orient: vertical;\n -webkit-line-clamp: 2;\n }\n .diagram-guide kbd {\n align-self: start;\n min-width: 1.45rem;\n padding: 0.12rem 0.3rem;\n border: 1px solid var(--toolbar-border);\n border-radius: 0.25rem;\n color: var(--text-faint);\n font: inherit;\n font-size: 0.5rem;\n text-align: center;\n }\n .diagram-guide-shortcuts {\n display: flex;\n flex-wrap: wrap;\n gap: 0.34rem 0.62rem;\n padding: 0.58rem 0.9rem 0.68rem;\n border-top: 1px solid var(--toolbar-border);\n color: var(--text-faint);\n font-size: 0.5rem;\n }\n .diagram-guide-shortcuts span {\n display: inline-flex;\n align-items: center;\n gap: 0.28rem;\n white-space: nowrap;\n }\n .diagram-guide-feedback {\n min-height: 1rem;\n margin: -0.18rem 0.9rem 0.62rem;\n color: var(--security-stroke);\n font-size: 0.5rem;\n }\n .diagram-guide-feedback:empty { display: none; }\n .diagram-guide-credit {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 0.6rem;\n padding: 0.46rem 0.9rem 0.56rem;\n border-top: 1px solid color-mix(in srgb, var(--toolbar-border) 74%, transparent);\n color: var(--text-faint);\n font-size: 0.5rem;\n letter-spacing: 0.02em;\n text-decoration: none;\n }\n .diagram-guide-credit-link {\n display: inline-flex;\n align-items: center;\n gap: 0.28rem;\n flex: none;\n opacity: 0.72;\n transition: opacity 120ms ease, color 120ms ease;\n }\n .diagram-guide-star {\n width: 0.62rem;\n height: 0.62rem;\n background: currentColor;\n -webkit-mask: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M8 1.6l1.9 3.9 4.3.6-3.1 3 .7 4.3L8 11.4l-3.8 2 .7-4.3-3.1-3 4.3-.6z' fill='none' stroke='black' stroke-width='1.3' stroke-linejoin='round'/%3E%3C/svg%3E\") center / contain no-repeat;\n mask: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M8 1.6l1.9 3.9 4.3.6-3.1 3 .7 4.3L8 11.4l-3.8 2 .7-4.3-3.1-3 4.3-.6z' fill='none' stroke='black' stroke-width='1.3' stroke-linejoin='round'/%3E%3C/svg%3E\") center / contain no-repeat;\n }\n .diagram-guide-credit:hover .diagram-guide-star,\n .diagram-guide-credit:focus-visible .diagram-guide-star {\n -webkit-mask-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M8 1.6l1.9 3.9 4.3.6-3.1 3 .7 4.3L8 11.4l-3.8 2 .7-4.3-3.1-3 4.3-.6z' stroke='black' stroke-width='1.3' stroke-linejoin='round'/%3E%3C/svg%3E\");\n mask-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M8 1.6l1.9 3.9 4.3.6-3.1 3 .7 4.3L8 11.4l-3.8 2 .7-4.3-3.1-3 4.3-.6z' stroke='black' stroke-width='1.3' stroke-linejoin='round'/%3E%3C/svg%3E\");\n }\n .diagram-guide-credit:hover .diagram-guide-credit-link,\n .diagram-guide-credit:focus-visible .diagram-guide-credit-link { opacity: 1; color: var(--cloud-stroke); }\n .diagram-guide-credit:focus-visible { outline: 2px solid var(--cloud-stroke); outline-offset: -2px; }\n @keyframes archify-guide-in {\n from { opacity: 0; transform: translateY(-5px) scale(0.985); }\n to { opacity: 1; transform: translateY(0) scale(1); }\n }\n\n .node-finder {\n position: absolute;\n top: 1rem;\n left: 1rem;\n z-index: 24;\n display: flex;\n flex-direction: column;\n width: min(22rem, calc(100% - 2rem));\n max-height: calc(100% - 2rem);\n overflow: hidden;\n border: 1px solid var(--toolbar-border);\n border-radius: 0.85rem;\n background: var(--toolbar-menu-bg);\n color: var(--toolbar-text);\n box-shadow: 0 6px 8px rgba(0, 0, 0, 0.22);\n backdrop-filter: blur(16px);\n }\n .node-finder[hidden] { display: none; }\n .node-finder[data-context=\"route-source\"] {\n border-color: color-mix(in srgb, var(--frontend-stroke) 68%, var(--toolbar-border));\n }\n .node-finder[data-context=\"route-target\"] {\n border-color: color-mix(in srgb, var(--security-stroke) 68%, var(--toolbar-border));\n }\n html[data-preset=\"blueprint\"] .node-finder { border-radius: 0.3rem; }\n .node-finder-head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 1rem;\n padding: 0.7rem 0.8rem 0.45rem;\n }\n .node-finder-head strong {\n color: var(--text);\n font-size: 0.6875rem;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n }\n .node-finder[data-context=\"route-source\"] .node-finder-head strong { color: var(--frontend-stroke); }\n .node-finder[data-context=\"route-target\"] .node-finder-head strong { color: var(--security-stroke); }\n .node-finder-close {\n width: 1.75rem;\n height: 1.75rem;\n border: 0;\n border-radius: 0.35rem;\n background: transparent;\n color: var(--toolbar-text);\n font: inherit;\n font-size: 1rem;\n cursor: pointer;\n }\n .node-finder-close:hover,\n .node-finder-close:focus-visible { background: var(--toolbar-hover); }\n .node-finder-close:focus-visible,\n .node-finder-result:focus-visible {\n outline: 2px solid var(--frontend-stroke);\n outline-offset: -2px;\n }\n .node-finder-search {\n display: grid;\n grid-template-columns: auto minmax(0, 1fr) auto;\n align-items: center;\n gap: 0.55rem;\n margin: 0 0.55rem 0.55rem;\n padding: 0 0.7rem;\n min-height: 2.35rem;\n border: 1px solid var(--toolbar-border);\n border-radius: 0.55rem;\n background: color-mix(in srgb, var(--panel) 78%, transparent);\n transition: border-color 160ms ease-out, box-shadow 160ms ease-out;\n }\n html[data-preset=\"blueprint\"] .node-finder-search { border-radius: 0.18rem; }\n .node-finder-search \u003e span { color: var(--frontend-stroke); font-size: 0.875rem; }\n .node-finder-search:focus-within {\n border-color: var(--frontend-stroke);\n box-shadow: 0 0 0 2px color-mix(in srgb, var(--frontend-stroke) 14%, transparent);\n }\n .node-finder-input {\n appearance: none;\n width: 100%;\n min-width: 0;\n border: 0;\n outline: 0;\n background: transparent;\n color: var(--text);\n font: inherit;\n font-size: 0.75rem;\n }\n .node-finder-input:focus-visible { outline: none; }\n .node-finder-input::placeholder { color: var(--text-faint); }\n .node-finder kbd {\n padding: 0.1rem 0.32rem;\n border: 1px solid var(--toolbar-border);\n border-radius: 0.25rem;\n color: var(--text-faint);\n font: inherit;\n font-size: 0.5625rem;\n }\n .node-finder-results {\n display: grid;\n flex: 1 1 auto;\n gap: 0;\n min-height: 0;\n max-height: min(18rem, 48vh);\n margin: 0 0.55rem 0.45rem;\n padding: 0;\n overflow-y: auto;\n overscroll-behavior: contain;\n border: 1px solid color-mix(in srgb, var(--toolbar-border) 78%, transparent);\n border-radius: 0.55rem;\n background: color-mix(in srgb, var(--panel) 54%, transparent);\n scroll-padding-block: 0.25rem;\n scrollbar-width: thin;\n scrollbar-color: color-mix(in srgb, var(--panel-border) 90%, transparent) transparent;\n }\n /* Slim overlay-style thumb instead of the chunky platform default. */\n .node-finder-results::-webkit-scrollbar { width: 10px; }\n .node-finder-results::-webkit-scrollbar-track { background: transparent; }\n .node-finder-results::-webkit-scrollbar-thumb {\n background: color-mix(in srgb, var(--panel-border) 85%, transparent);\n border: 3px solid transparent;\n border-radius: 999px;\n background-clip: padding-box;\n }\n .node-finder-results::-webkit-scrollbar-thumb:hover {\n background: color-mix(in srgb, var(--text-muted) 65%, transparent);\n border: 3px solid transparent;\n background-clip: padding-box;\n }\n html[data-preset=\"blueprint\"] .node-finder-results { border-radius: 0.18rem; }\n .node-finder-result {\n display: grid;\n grid-template-columns: minmax(0, 1fr) auto;\n gap: 0.2rem 0.75rem;\n width: 100%;\n min-height: 3rem;\n padding: 0.52rem 0.65rem;\n border: 0;\n border-radius: 0;\n background: transparent;\n color: var(--text);\n font: inherit;\n text-align: left;\n cursor: pointer;\n transition: background-color 160ms ease-out;\n }\n .node-finder-result:not(:last-child) {\n border-bottom: 1px solid color-mix(in srgb, var(--toolbar-border) 62%, transparent);\n }\n .node-finder-result:hover,\n .node-finder-result:focus-visible {\n position: relative;\n z-index: 1;\n background: color-mix(in srgb, var(--frontend-fill) 48%, var(--toolbar-hover));\n }\n .node-finder-result strong {\n overflow: hidden;\n font-size: 0.75rem;\n line-height: 1.25;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n .node-finder-result small {\n grid-column: 1 / -1;\n overflow: hidden;\n color: var(--text-faint);\n font-size: 0.5625rem;\n letter-spacing: 0.015em;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n .node-finder-result em {\n align-self: center;\n color: var(--frontend-stroke);\n font-size: 0.625rem;\n font-style: normal;\n }\n /* A zero count is absence, not information — let it recede. */\n .node-finder-result em[data-zero] { color: var(--text-faint); }\n .node-finder[data-context^=\"route-\"] .node-finder-result em {\n padding: 0.1rem 0.3rem;\n border: 1px solid color-mix(in srgb, currentColor 52%, transparent);\n border-radius: 999px;\n font-size: 0.5rem;\n font-weight: 800;\n letter-spacing: 0.05em;\n text-transform: uppercase;\n }\n .node-finder[data-context=\"route-target\"] .node-finder-result em { color: var(--security-stroke); }\n .node-finder-empty {\n padding: 1.1rem 0.75rem 1.25rem;\n color: var(--text-faint);\n font-size: 0.6875rem;\n text-align: center;\n }\n .node-finder-status {\n margin: 0;\n padding: 0.1rem 0.8rem 0.65rem;\n color: var(--text-faint);\n font-size: 0.5625rem;\n }\n /* #1040: small bold reader text in the semantic accent color (Passport\n direction chip, Finder result count) falls below the 4.5:1 normal-text\n floor in some light themes. Resolve a darker --reader-accent where the\n preset defines one; the diagram's --frontend-stroke itself is untouched,\n as are dark themes and presets that already pass. Zero counts stay faint\n on purpose, so they are excluded from the darker rule. Route-target\n finders keep their --security-stroke status color, so they are excluded\n too. */\n html[data-theme=\"light\"] .semantic-passport-meta [data-passport=\"kind\"],\n html[data-theme=\"light\"] .node-finder:not([data-context=\"route-target\"]) .node-finder-result em:not([data-zero]) {\n color: var(--reader-accent, var(--frontend-stroke));\n }\n\n .cards {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(min(15rem, 100%), 1fr));\n gap: 1rem;\n margin-top: 2rem;\n }\n\n .card {\n background: var(--panel);\n border-radius: 0.75rem;\n border: 1px solid var(--panel-border);\n padding: 1.125rem 1.25rem;\n /* Card notes are authored prose and may carry an unbreakable run such as a\n slash-joined pipeline or a long identifier. Without this, that run sets the\n card's min-content width, spills past its track in every rail placement,\n and pushes the whole page into horizontal overflow at desktop widths.\n `anywhere` rather than `break-word` because only `anywhere` shrinks the\n intrinsic minimum the layout is sized from. */\n overflow-wrap: anywhere;\n }\n\n html[data-preset=\"signal-flow\"] .card {\n background: linear-gradient(145deg, rgba(10, 24, 43, 0.86), rgba(5, 12, 24, 0.92));\n box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.035);\n }\n html[data-preset=\"signal-flow\"][data-theme=\"light\"] .card {\n background: linear-gradient(145deg, rgba(255, 255, 255, 0.92), rgba(237, 246, 250, 0.9));\n box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.82), 0 12px 32px rgba(45, 78, 99, 0.08);\n }\n\n /* Preserve the one-screen guarantee on ordinary laptop displays. The\n high-screen expansion above must not trade a fuller canvas for page\n scrolling at 900–1080px heights. */\n @media (min-width: 768px) and (max-height: 1100px) {\n .header { margin-bottom: 1rem; }\n .diagram-container {\n padding: 0.875rem;\n padding-bottom: calc(0.875rem + var(--archify-nav-reserve));\n }\n .cards { margin-top: 1rem; }\n .card { padding: 1rem; }\n }\n @media (min-width: 768px) and (max-height: 920px) {\n body { padding-block: 1.25rem; }\n body { --archify-page-top: 1.25rem; }\n .header { margin-bottom: 0.875rem; }\n .cards { margin-top: 0.75rem; }\n html[data-nav-stage-rail=\"true\"] body { padding-block: 0.375rem; }\n /* Align the fixed toolbar with the compact title row. Reserve its hit area\n above the guide without taking extra height from a fitted diagram. */\n html[data-nav-stage-rail=\"true\"] .toolbar { top: 0.25rem; }\n html[data-nav-stage-rail=\"true\"] .header { min-height: 2.625rem; margin-bottom: 0.5rem; }\n html[data-nav-stage-rail=\"true\"] .diagram-container[data-nav-stage-rail=\"true\"] { padding-top: 0.5rem; }\n html[data-nav-stage-rail=\"true\"] .cards { margin-top: 0.375rem; }\n }\n\n html[data-preset=\"blueprint\"] .card {\n position: relative;\n border-color: var(--panel-border);\n border-radius: 0.3rem;\n background: var(--panel);\n box-shadow: none;\n }\n html[data-preset=\"blueprint\"] .card::before {\n content: \"\";\n position: absolute;\n top: -1px;\n left: 1rem;\n width: 2.5rem;\n border-top: 2px solid var(--frontend-stroke);\n }\n\n html[data-preset=\"editorial\"] .card {\n position: relative;\n border-radius: 0.28rem;\n background: var(--panel);\n box-shadow: 0 10px 28px color-mix(in srgb, #000 10%, transparent);\n }\n html[data-preset=\"editorial\"] .card::before {\n position: absolute;\n top: -1px;\n left: 1.25rem;\n width: 3.25rem;\n border-top: 3px solid var(--arrow-emphasis);\n content: \"\";\n }\n html[data-preset=\"editorial\"] .card-dot { border-radius: 0; transform: rotate(45deg) scale(0.78); }\n\n /* Summary rail: the reader measures the viewport and enables it only when the\n diagram keeps its readable width beside the notes. */\n .reader-rail { display: contents; }\n html[data-reader-rail=\"true\"] .container {\n display: grid;\n grid-template-columns: minmax(0, 1fr) var(--archify-rail-width);\n grid-auto-flow: row dense;\n column-gap: var(--archify-rail-gap);\n align-items: start;\n }\n html[data-reader-rail=\"true\"] .container \u003e .header { grid-column: 1 / -1; }\n html[data-reader-rail=\"true\"] .container \u003e .diagram-container { grid-column: 1; }\n /* The rail is at least the diagram's height and may run on to the viewport\n floor (--archify-rail-max, set by the reader); past that, notes scroll and\n the index keeps a usable share instead of collapsing to its header. */\n html[data-reader-rail=\"true\"] .reader-rail {\n display: flex;\n flex-direction: column;\n grid-column: 2;\n align-self: stretch;\n min-height: 0;\n max-height: var(--archify-rail-max, none);\n }\n html:is([data-reader-rail=\"true\"], [data-reader-rail=\"overlay\"]) .cards:empty { display: none; }\n html:is([data-reader-rail=\"true\"], [data-reader-rail=\"overlay\"]) .cards {\n display: flex;\n flex: 0 1 auto;\n flex-direction: column;\n gap: 0;\n min-height: 0;\n margin-top: 0;\n padding: 0.125rem 0 0;\n overflow-y: auto;\n scrollbar-width: none;\n -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 1.25rem), transparent);\n mask-image: linear-gradient(to bottom, #000 calc(100% - 1.25rem), transparent);\n }\n html:is([data-reader-rail=\"true\"], [data-reader-rail=\"overlay\"]) .cards::-webkit-scrollbar { display: none; }\n html:is([data-reader-rail=\"true\"], [data-reader-rail=\"overlay\"]) .cards .card { flex: none; }\n html:is([data-reader-rail=\"true\"], [data-reader-rail=\"overlay\"]) .cards .card {\n padding: 0 0.25rem 1.125rem;\n border: 0;\n border-radius: 0;\n background: transparent;\n box-shadow: none;\n }\n html:is([data-reader-rail=\"true\"], [data-reader-rail=\"overlay\"]) .cards .card + .card {\n padding-top: 1.125rem;\n border-top: 1px solid var(--panel-border);\n }\n html:is([data-reader-rail=\"true\"], [data-reader-rail=\"overlay\"]) .cards .card::before { display: none; }\n html:is([data-reader-rail=\"true\"], [data-reader-rail=\"overlay\"]) .card-header { gap: 0.55rem; margin-bottom: 0.45rem; }\n html:is([data-reader-rail=\"true\"], [data-reader-rail=\"overlay\"]) .cards .card h3 { font-size: 0.875rem; letter-spacing: -0.005em; }\n html:is([data-reader-rail=\"true\"], [data-reader-rail=\"overlay\"]) .cards .card ul { font-size: 0.8125rem; line-height: 1.6; }\n\n .node-outline { display: none; }\n html:is([data-reader-rail=\"true\"], [data-reader-rail=\"overlay\"]) .node-outline:not([hidden]) {\n display: flex;\n flex: 1 0 min(13rem, 55%);\n flex-direction: column;\n min-height: 0;\n margin-top: 0.25rem;\n overflow: hidden;\n border-top: 1px solid var(--panel-border);\n }\n html:is([data-reader-rail=\"true\"], [data-reader-rail=\"overlay\"]) .cards:empty + .node-outline:not([hidden]) { margin-top: 0; border-top: 0; }\n /* Collapsed: only the reveal control remains, so the diagram takes the width. */\n html[data-reader-rail=\"collapsed\"] .reader-rail { display: none; }\n /* Overlay: a drawer for diagrams whose readable floor leaves no room to dock. */\n html[data-reader-rail=\"overlay\"] .reader-rail {\n position: fixed;\n z-index: 150;\n top: 1rem;\n right: 1rem;\n bottom: 1rem;\n display: flex;\n flex-direction: column;\n width: var(--archify-rail-width);\n padding: 0.75rem 1rem 0.5rem;\n border: 1px solid color-mix(in srgb, var(--panel-border) 90%, transparent);\n border-radius: 1rem;\n background: var(--toolbar-menu-bg);\n box-shadow: 0 1px 2px rgba(15, 23, 42, 0.06), 0 28px 64px -18px rgba(15, 23, 42, 0.38);\n animation: rail-drawer-in 0.2s cubic-bezier(0.22, 1, 0.36, 1);\n }\n @keyframes rail-drawer-in {\n from { opacity: 0; transform: translateX(0.75rem); }\n to { opacity: 1; transform: translateX(0); }\n }\n /* Bottom (default): the notes keep their full-width card row and the index\n follows as one panel whose groups flow into columns, so nothing scrolls\n inside it. The placement toggle sits in the index header. */\n html[data-reader-rail=\"bottom\"] .reader-rail {\n display: grid;\n grid-template-areas: \"cards\" \"index\";\n grid-template-columns: minmax(0, 1fr);\n row-gap: 1rem;\n margin-top: 1rem;\n }\n html[data-reader-rail=\"bottom\"] .cards { grid-area: cards; margin-top: 0; }\n html[data-reader-rail=\"bottom\"] .cards:empty { display: none; }\n html[data-reader-rail=\"bottom\"] .node-outline:not([hidden]) {\n display: block;\n grid-area: index;\n padding: 0 1.25rem 0.75rem;\n border: 1px solid var(--panel-border);\n border-radius: 0.75rem;\n background: var(--panel);\n }\n html[data-reader-rail=\"bottom\"] .reader-rail \u003e .rail-controls {\n z-index: 1;\n grid-area: index;\n align-self: start;\n justify-self: end;\n margin: 0.625rem 0.625rem 0 0;\n }\n html[data-reader-rail=\"bottom\"] .node-outline-head { padding: 1rem 2.5rem 0.375rem 0.25rem; }\n html[data-reader-rail=\"bottom\"] .node-outline-list {\n display: grid;\n gap: 0.5rem;\n padding: 0;\n overflow: visible;\n -webkit-mask-image: none;\n mask-image: none;\n }\n html[data-reader-rail=\"bottom\"] .node-outline-group + .node-outline-group { margin-top: 0; }\n html[data-reader-rail=\"bottom\"] .node-outline-group:has(\u003e .node-outline-heading) { padding-bottom: 0.25rem; }\n html[data-reader-rail=\"bottom\"] .node-outline-group \u003e ul {\n display: flex;\n flex-wrap: wrap;\n gap: 0.25rem 0.5rem;\n }\n html[data-reader-rail=\"bottom\"] .node-outline-group \u003e ul::before { display: none; }\n html[data-reader-rail=\"bottom\"] .node-outline-group li {\n flex: 0 1 auto;\n min-width: min(100%, 12rem);\n max-width: min(100%, 32rem);\n }\n html[data-reader-rail=\"bottom\"] .node-outline-heading {\n justify-content: flex-start;\n gap: 0.5rem;\n }\n html[data-reader-rail=\"bottom\"] .node-outline-heading \u003e span { overflow-wrap: anywhere; }\n .rail-controls { display: none; }\n html:is([data-reader-rail=\"true\"], [data-reader-rail=\"overlay\"], [data-reader-rail=\"bottom\"]) .rail-controls {\n display: flex;\n flex: none;\n justify-content: flex-end;\n gap: 0.125rem;\n margin: -0.25rem -0.25rem 0.25rem 0;\n }\n .rail-control {\n display: inline-grid;\n place-items: center;\n width: 1.875rem;\n height: 1.875rem;\n border: 0;\n border-radius: 0.5rem;\n background: transparent;\n color: var(--text-faint);\n cursor: pointer;\n transition: background 0.14s ease, color 0.14s ease;\n }\n .rail-control[hidden] { display: none; }\n .rail-control:hover,\n .rail-control:focus-visible { color: var(--text); background: color-mix(in srgb, var(--text) 6%, transparent); }\n .rail-control:focus-visible { outline: 1.5px solid color-mix(in srgb, var(--frontend-stroke) 60%, transparent); outline-offset: -1.5px; }\n .rail-control-icon,\n .rail-reveal-icon {\n width: 1rem;\n height: 1rem;\n background: currentColor;\n -webkit-mask: var(--rail-glyph) center / contain no-repeat;\n mask: var(--rail-glyph) center / contain no-repeat;\n }\n /* Panel glyphs: a frame with the panel drawn on the side it moves to. */\n .rail-control-icon.placement,\n #rail-placement[data-placement=\"right\"] .rail-control-icon { --rail-glyph: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cg fill='none' stroke='black' stroke-width='1.6'%3E%3Crect x='2.8' y='3.8' width='14.4' height='12.4' rx='2'/%3E%3Cpath d='M2.8 12h14.4'/%3E%3C/g%3E%3C/svg%3E\"); }\n #rail-placement[data-placement=\"bottom\"] .rail-control-icon { --rail-glyph: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cg fill='none' stroke='black' stroke-width='1.6'%3E%3Crect x='2.8' y='3.8' width='14.4' height='12.4' rx='2'/%3E%3Cpath d='M12.6 3.8v12.4'/%3E%3C/g%3E%3C/svg%3E\"); }\n .rail-control-icon.collapse { --rail-glyph: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath d='m8 5.5 4.5 4.5L8 14.5' fill='none' stroke='black' stroke-width='1.7' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E\"); }\n .rail-reveal-icon { --rail-glyph: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cg fill='none' stroke='black' stroke-width='1.6'%3E%3Crect x='2.8' y='3.8' width='14.4' height='12.4' rx='2'/%3E%3Cpath d='M12.6 3.8v12.4M14.4 7.2h1M14.4 9.6h1M14.4 12h1'/%3E%3C/g%3E%3C/svg%3E\"); }\n .rail-reveal[hidden] { display: none; }\n /* The collapsed rail leaves one quiet pill in the canvas corner. */\n .rail-reveal {\n position: absolute;\n z-index: 13;\n top: 0.75rem;\n right: 0.75rem;\n display: inline-flex;\n align-items: center;\n gap: 0.45rem;\n min-height: 2rem;\n padding: 0 0.75rem 0 0.625rem;\n border: 1px solid color-mix(in srgb, var(--panel-border) 85%, transparent);\n border-radius: 999px;\n background: color-mix(in srgb, var(--panel) 86%, transparent);\n color: var(--text-muted);\n box-shadow: 0 1px 2px rgba(15, 23, 42, 0.05), 0 8px 20px -12px rgba(15, 23, 42, 0.28);\n backdrop-filter: blur(12px);\n font: inherit;\n font-size: 0.75rem;\n font-weight: 500;\n cursor: pointer;\n transition: color 0.14s ease, background 0.14s ease;\n }\n .rail-reveal:hover,\n .rail-reveal:focus-visible { color: var(--text); background: var(--panel); }\n .rail-reveal:focus-visible { outline: 1.5px solid color-mix(in srgb, var(--frontend-stroke) 60%, transparent); outline-offset: 2px; }\n html[data-motion=\"still\"] .reader-rail { animation: none !important; }\n .node-outline-head {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n padding: 1.125rem 0.25rem 0.375rem;\n }\n .node-outline-head strong {\n color: var(--text);\n font-size: 0.875rem;\n font-weight: 600;\n letter-spacing: -0.005em;\n }\n .node-outline-count {\n min-width: 1.35rem;\n padding: 0.1rem 0.4rem;\n border-radius: 999px;\n background: color-mix(in srgb, var(--text) 6%, transparent);\n color: var(--text-muted);\n font-size: 0.6875rem;\n font-weight: 600;\n line-height: 1.3;\n text-align: center;\n font-variant-numeric: tabular-nums;\n }\n .node-outline-list {\n flex: 1 1 auto;\n min-height: 0;\n margin: 0 -0.375rem;\n padding: 0 0 1.25rem;\n overflow-y: auto;\n list-style: none;\n scrollbar-width: none;\n -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 2rem), transparent);\n mask-image: linear-gradient(to bottom, #000 calc(100% - 2rem), transparent);\n }\n .node-outline-list::-webkit-scrollbar { display: none; }\n .node-outline-group + .node-outline-group { margin-top: 0.25rem; }\n .node-outline-group \u003e ul {\n position: relative;\n list-style: none;\n }\n /* A quiet spine ties each group's swatches together like a lane. */\n .node-outline-group \u003e ul::before {\n position: absolute;\n top: 0.95rem;\n bottom: 0.95rem;\n left: calc(0.625rem + 0.3125rem - 0.5px);\n width: 1px;\n background: var(--panel-border);\n content: \"\";\n }\n .node-outline-heading {\n display: flex;\n align-items: baseline;\n justify-content: space-between;\n padding: 0.7rem 0.625rem 0.25rem;\n color: var(--text-faint);\n font-size: 0.6875rem;\n font-weight: 600;\n letter-spacing: 0.02em;\n }\n .node-outline-heading small {\n color: var(--text-dim);\n font-size: 0.6875rem;\n font-weight: 500;\n font-variant-numeric: tabular-nums;\n }\n /* The rail frees the vertical budget, so compact viewports keep the normal\n title spacing instead of the stacked layout's tight stage rail. */\n html[data-reader-rail]:not([data-reader-rail=\"bottom\"]) body { --archify-page-top: 1.5rem; padding-block: 1.5rem; }\n html[data-reader-rail]:not([data-reader-rail=\"bottom\"]) .toolbar { top: calc(var(--archify-page-top) - 0.1875rem); }\n html[data-reader-rail]:not([data-reader-rail=\"bottom\"]) .header { min-height: 0; margin-bottom: 1.125rem; }\n .node-outline-item {\n position: relative;\n display: grid;\n grid-template-columns: 0.625rem minmax(0, auto) minmax(0, 1fr) 0.75rem;\n align-items: center;\n column-gap: 0.625rem;\n width: 100%;\n min-height: 1.95rem;\n padding: 0.28rem 0.5rem 0.28rem 0.625rem;\n border: 0;\n border-radius: 0.5rem;\n background: transparent;\n color: var(--text);\n font: inherit;\n text-align: left;\n cursor: pointer;\n transition: background 0.16s ease;\n }\n /* Trailing chevron hints that a click focuses the node. */\n .node-outline-item::after {\n grid-column: 4;\n width: 0.75rem;\n height: 0.75rem;\n background: currentColor;\n color: var(--text-muted);\n content: \"\";\n opacity: 0;\n transform: translateX(-3px);\n transition: opacity 0.16s ease, transform 0.16s ease;\n -webkit-mask: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='m4.5 3 3 3-3 3' fill='none' stroke='black' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E\") center / contain no-repeat;\n mask: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='m4.5 3 3 3-3 3' fill='none' stroke='black' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E\") center / contain no-repeat;\n }\n .node-outline-item:hover,\n .node-outline-item:focus-visible,\n .node-outline-item[data-previewing] { background: color-mix(in srgb, var(--text) 4.5%, transparent); }\n .node-outline-item:hover::after,\n .node-outline-item:focus-visible::after { opacity: 0.7; transform: translateX(0); }\n .node-outline-item:focus-visible { outline: 1.5px solid color-mix(in srgb, var(--outline-tone) 60%, transparent); outline-offset: -1.5px; }\n .node-outline-item[aria-current=\"true\"] { background: color-mix(in srgb, var(--outline-tone) 10%, transparent); }\n .node-outline-item[aria-current=\"true\"]::after { opacity: 0; }\n /* Swatches echo the diagram's node cards: tinted fill, hairline stroke. */\n .node-outline-dot {\n width: 0.625rem;\n height: 0.5rem;\n border: 1.25px solid var(--outline-tone);\n border-radius: 0.15rem;\n background: color-mix(in srgb, var(--outline-tone) 16%, var(--bg));\n transition: background 0.16s ease, transform 0.16s ease, box-shadow 0.16s ease;\n }\n .node-outline-item:hover .node-outline-dot,\n .node-outline-item:focus-visible .node-outline-dot,\n .node-outline-item[data-previewing] .node-outline-dot,\n .node-outline-item[aria-current=\"true\"] .node-outline-dot {\n background: var(--outline-tone);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--outline-tone) 18%, transparent);\n transform: scale(1.08);\n }\n .node-outline-item strong {\n overflow: hidden;\n font-size: 0.8125rem;\n font-weight: 500;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n .node-outline-item[aria-current=\"true\"] strong { font-weight: 600; }\n .node-outline-item small {\n overflow: hidden;\n color: var(--text-faint);\n font-size: 0.6875rem;\n text-align: right;\n text-overflow: ellipsis;\n white-space: nowrap;\n transition: color 0.16s ease;\n }\n .node-outline-item:hover small,\n .node-outline-item[data-previewing] small,\n .node-outline-item[aria-current=\"true\"] small { color: var(--text-muted); }\n\n /* The desktop bottom rail is an index: compact rows keep related nodes\n together while allowing full names and descriptions to wrap naturally. */\n html[data-reader-rail=\"bottom\"] .node-outline-item {\n grid-template-columns: 0.625rem minmax(0, 1fr) 0.75rem;\n align-items: first baseline;\n column-gap: 0.375rem;\n min-height: 0;\n padding-block: 0.375rem;\n }\n html[data-reader-rail=\"bottom\"] .node-outline-item strong,\n html[data-reader-rail=\"bottom\"] .node-outline-item small {\n overflow: visible;\n text-overflow: clip;\n white-space: normal;\n overflow-wrap: anywhere;\n }\n html[data-reader-rail=\"bottom\"] .node-outline-item strong {\n grid-column: 2;\n grid-row: 1;\n }\n html[data-reader-rail=\"bottom\"] .node-outline-item small {\n grid-column: 2;\n grid-row: 2;\n text-align: left;\n }\n html[data-reader-rail=\"bottom\"] .node-outline-item::after {\n grid-column: 3;\n grid-row: 1;\n align-self: center;\n }\n\n .card-header {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n margin-bottom: 0.625rem;\n }\n\n .card-dot {\n flex: none;\n width: 8px;\n height: 8px;\n border-radius: 50%;\n }\n\n .card-dot.cyan { background: var(--frontend-stroke); }\n .card-dot.emerald { background: var(--backend-stroke); }\n .card-dot.violet { background: var(--database-stroke); }\n .card-dot.amber { background: var(--cloud-stroke); }\n .card-dot.rose { background: var(--security-stroke); }\n .card-dot.orange { background: var(--messagebus-stroke); }\n .card-dot.slate { background: var(--external-stroke); }\n\n .card h3 { font-size: 0.9375rem; font-weight: 600; line-height: 1.4; color: var(--text); }\n\n .card ul {\n list-style: none;\n color: var(--text-muted);\n font-size: 0.8125rem;\n line-height: 1.6;\n }\n\n .card li {\n position: relative;\n margin-bottom: 0.3rem;\n padding-left: 0.9rem;\n }\n .card li:last-child { margin-bottom: 0; }\n /* Hanging markers take the card's tone so wrapped lines align under text. */\n .card li::before {\n position: absolute;\n top: calc(0.8em - 2px);\n left: 0.2rem;\n width: 4px;\n height: 4px;\n border-radius: 50%;\n background: color-mix(in srgb, var(--card-tone, var(--text-muted)) 70%, transparent);\n content: \"\";\n }\n .card:has(.card-dot.cyan) { --card-tone: var(--frontend-stroke); }\n .card:has(.card-dot.emerald) { --card-tone: var(--backend-stroke); }\n .card:has(.card-dot.violet) { --card-tone: var(--database-stroke); }\n .card:has(.card-dot.amber) { --card-tone: var(--cloud-stroke); }\n .card:has(.card-dot.rose) { --card-tone: var(--security-stroke); }\n .card:has(.card-dot.orange) { --card-tone: var(--messagebus-stroke); }\n .card:has(.card-dot.slate) { --card-tone: var(--external-stroke); }\n .card-dot { box-shadow: 0 0 0 3px color-mix(in srgb, var(--card-tone, transparent) 16%, transparent); }\n\n /* ==========================================================\n Print stylesheet — hide interactive controls, force light,\n drop the grid so the diagram doesn't waste ink, use landscape\n so wide diagrams fit, pack summary cards into 2 columns to\n avoid an orphan card on a trailing page.\n ========================================================== */\n @page { size: landscape; margin: 1.5cm; }\n\n @media print {\n /* Force the FULL light palette — including component fills/strokes and\n lane/arrow colors — so printing from dark theme doesn't put neon\n strokes and translucent dark fills on white paper. */\n :root, [data-theme=\"dark\"], [data-theme=\"light\"] {\n --bg: #ffffff;\n --grid: transparent;\n --panel: #ffffff;\n --panel-border: #e2e8f0;\n --text: #0f172a;\n --text-muted: #475569;\n --text-dim: #94a3b8;\n --text-faint: #64748b;\n --mask: #ffffff;\n --lane-fill: rgba(248, 250, 252, 0.65);\n --lane-stroke: #cbd5e1;\n --arrow: #94a3b8;\n --arrow-emphasis: #059669;\n --frontend-fill: rgba(34, 211, 238, 0.15);\n --frontend-stroke: #0891b2;\n --backend-fill: rgba(52, 211, 153, 0.18);\n --backend-stroke: #059669;\n --database-fill: rgba(167, 139, 250, 0.2);\n --database-stroke: #7c3aed;\n --cloud-fill: rgba(251, 191, 36, 0.18);\n --cloud-stroke: #d97706;\n --security-fill: rgba(251, 113, 133, 0.15);\n --security-stroke: #e11d48;\n --messagebus-fill: rgba(251, 146, 60, 0.15);\n --messagebus-stroke:#ea580c;\n --external-fill: rgba(148, 163, 184, 0.18);\n --external-stroke: #64748b;\n }\n body { background: #ffffff !important; padding: 0; }\n .container { max-width: none; }\n .toolbar, .diagram-nav, .focus-chip, .archify-toast, .no-print { display: none !important; }\n \n .diagram-container, .card { box-shadow: none; border-color: #e2e8f0; }\n .diagram-container { --archify-nav-reserve: 0px !important; }\n html[data-preset=\"signal-flow\"] .diagram-container,\n html[data-preset=\"signal-flow\"] .card { background: #ffffff; }\n .cards { grid-template-columns: 1fr 1fr; }\n /* Ensure elements break sensibly across pages */\n .card { break-inside: avoid; page-break-inside: avoid; }\n .diagram-container { break-inside: avoid; page-break-inside: avoid; }\n .diagram-container svg [data-detail] {\n opacity: 1 !important;\n pointer-events: auto !important;\n }\n .diagram-container svg [data-detail-anchor] { transform: none !important; }\n .diagram-container svg[data-lens-active] [data-node-id],\n .diagram-container svg[data-lens-active] [data-edge-from],\n .diagram-container svg[data-legend-preview-active] [data-node-id],\n .diagram-container svg[data-legend-preview-active] [data-edge-from] {\n opacity: 1 !important;\n filter: none !important;\n }\n [data-legend-bridge-runtime] { display: none !important; }\n .semantic-lens-overlay { display: none !important; }\n .relationship-hit-overlay { display: none !important; }\n .relationship-pulse-overlay { display: none !important; }\n .route-probe-overlay, .route-journey-overlay { display: none !important; }\n .diagram-container svg[data-focus-active] [data-node-id],\n .diagram-container svg[data-focus-active] [data-edge-from],\n .diagram-container svg[data-focus-active] [data-graph-role=\"automatic-crossover-underlay\"],\n .diagram-container svg[data-reach-active] [data-node-id],\n .diagram-container svg[data-reach-active] [data-edge-from],\n .diagram-container svg[data-reach-active] [data-graph-role=\"automatic-crossover-underlay\"],\n .diagram-container svg[data-route-active] [data-node-id],\n .diagram-container svg[data-route-active] [data-edge-from],\n .diagram-container svg[data-route-active] [data-graph-role=\"automatic-crossover-underlay\"] {\n opacity: 1 !important;\n filter: none !important;\n animation: none !important;\n }\n h1, .subtitle { color: #0f172a; }\n }\n\n /* ==========================================================\n TOOLBAR (theme toggle + export menu)\n ========================================================== */\n /* The toolbar tracks the centered reader column and the title row, so\n an adaptive narrow reader does not leave its controls stranded at the\n viewport edge. */\n .toolbar {\n position: absolute;\n top: var(--archify-page-top);\n right: max(1rem, calc((100vw - min(100vw - 4rem, var(--archify-reader-width, 1440px))) / 2));\n display: flex;\n align-items: center;\n gap: 0.125rem;\n padding: 0;\n border: 0;\n background: transparent;\n box-shadow: none;\n z-index: 100;\n }\n /* Ghost controls keep a 44px hit area while the visible pill stays compact;\n Export is the one primary action. */\n .toolbar button {\n position: relative;\n isolation: isolate;\n background: transparent;\n color: var(--text-muted);\n border: 0;\n padding: 0.5rem 0.75rem;\n border-radius: 0.5625rem;\n font-family: inherit;\n font-size: 0.8125rem;\n font-weight: 500;\n cursor: pointer;\n white-space: nowrap;\n box-shadow: none;\n transition: color 0.15s;\n display: inline-flex;\n align-items: center;\n gap: 0.4rem;\n min-height: 2.75rem;\n }\n .toolbar button::before {\n position: absolute;\n z-index: -1;\n inset: 0.375rem 0;\n border-radius: 0.5rem;\n background: transparent;\n content: \"\";\n transition: background 0.15s, box-shadow 0.15s;\n }\n .toolbar button:hover { color: var(--text); }\n .toolbar button:hover::before { background: color-mix(in srgb, var(--text) 6%, transparent); }\n .toolbar button[aria-expanded=\"true\"],\n .toolbar button[aria-pressed=\"true\"]:not(#btn-theme) { color: var(--text); }\n .toolbar button[aria-expanded=\"true\"]::before,\n .toolbar button[aria-pressed=\"true\"]:not(#btn-theme)::before {\n background: color-mix(in srgb, var(--text) 8%, transparent);\n }\n .toolbar #btn-export {\n margin-left: 0.375rem;\n padding-right: 0.95rem;\n padding-left: 1rem;\n color: var(--bg);\n font-weight: 550;\n }\n .toolbar #btn-export::before {\n background: var(--text);\n box-shadow: 0 1px 2px rgba(15, 23, 42, 0.12), 0 6px 16px -8px rgba(15, 23, 42, 0.4);\n }\n .toolbar #btn-export:hover,\n .toolbar #btn-export[aria-expanded=\"true\"] { color: var(--bg); }\n .toolbar #btn-export:hover::before,\n .toolbar #btn-export[aria-expanded=\"true\"]::before { background: color-mix(in srgb, var(--text) 86%, var(--bg)); }\n .toolbar button:focus-visible {\n outline: 2px solid var(--frontend-stroke);\n outline-offset: -4px;\n }\n .toolbar { gap: 0.5rem; }\n .toolbar #btn-export { margin-left: 0; }\n .toolbar #btn-export::before { inset: 0.25rem 0; border-radius: 0.6875rem; }\n /* Tools share one quiet capsule; the capsule is drawn inside the 44px row\n so the controls keep their hit area without a tall visible box. */\n .toolbar-group {\n position: relative;\n isolation: isolate;\n display: flex;\n align-items: center;\n gap: 0.0625rem;\n padding: 0 0.25rem;\n }\n .toolbar-group::before {\n position: absolute;\n z-index: -1;\n inset: 0.25rem 0;\n border: 1px solid color-mix(in srgb, var(--panel-border) 85%, transparent);\n border-radius: 0.6875rem;\n background: color-mix(in srgb, var(--panel) 78%, transparent);\n box-shadow: 0 1px 2px rgba(15, 23, 42, 0.04), 0 8px 20px -14px rgba(15, 23, 42, 0.22);\n backdrop-filter: blur(12px);\n content: \"\";\n }\n .toolbar-group button { padding-inline: 0.625rem; }\n .toolbar-group button::before { inset: 0.5rem 0; border-radius: 0.5rem; }\n /* Theme becomes a sliding switch; its label stays for assistive tech. */\n .toolbar #theme-label,\n .toolbar #present-label {\n position: absolute;\n width: 1px;\n height: 1px;\n overflow: hidden;\n clip: rect(0 0 0 0);\n white-space: nowrap;\n }\n .toolbar #btn-theme {\n width: 3.5rem;\n padding: 0;\n justify-content: center;\n }\n .toolbar #btn-theme::before {\n inset: 0.75rem 0.4375rem;\n border-radius: 999px;\n background: color-mix(in srgb, var(--text) 9%, transparent);\n box-shadow: inset 0 1px 2px rgba(15, 23, 42, 0.08);\n }\n .toolbar #btn-theme:hover::before { background: color-mix(in srgb, var(--text) 13%, transparent); }\n .toolbar #btn-theme::after {\n position: absolute;\n z-index: 0;\n top: 50%;\n left: 0.5625rem;\n width: 1.125rem;\n height: 1.125rem;\n border-radius: 50%;\n background: var(--panel);\n box-shadow: 0 1px 2px rgba(15, 23, 42, 0.18), 0 2px 6px rgba(15, 23, 42, 0.12);\n content: \"\";\n transform: translateY(-50%);\n transition: left 0.22s cubic-bezier(0.22, 1, 0.36, 1);\n }\n .toolbar #theme-icon {\n position: absolute;\n z-index: 1;\n top: 50%;\n left: calc(0.5625rem + 0.1875rem);\n width: 0.75rem;\n height: 0.75rem;\n color: var(--cloud-stroke);\n transform: translateY(-50%);\n transition: left 0.22s cubic-bezier(0.22, 1, 0.36, 1);\n }\n [data-theme=\"dark\"] .toolbar #btn-theme::before { background: rgba(148, 163, 184, 0.16); }\n [data-theme=\"dark\"] .toolbar #btn-theme::after { left: calc(100% - 0.5625rem - 1.125rem); background: #3b4a63; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4); }\n [data-theme=\"dark\"] .toolbar #theme-icon { left: calc(100% - 0.5625rem - 1.125rem + 0.1875rem); color: #c7d2fe; }\n .toolbar #btn-present { width: 2.5rem; padding: 0; justify-content: center; }\n .toolbar #btn-present .toolbar-icon { width: 1rem; height: 1rem; }\n html[data-motion=\"still\"] .toolbar #btn-theme::after,\n html[data-motion=\"still\"] .toolbar #theme-icon { transition: none; }\n html[data-present=\"true\"]:not([data-embed=\"true\"]) .toolbar { right: 1rem; }\n html[data-preset=\"editorial\"] .toolbar { border-radius: 0.45rem; }\n .toolbar #btn-motion[hidden] { display: none !important; }\n .toolbar #btn-motion { min-width: 4.4rem; }\n .toolbar #btn-motion:disabled { cursor: default; opacity: 0.58; }\n .toolbar .preset-wrap,\n .toolbar .export-wrap {\n position: relative;\n }\n .toolbar-icon {\n display: inline-block;\n width: 0.95rem;\n height: 0.95rem;\n flex: 0 0 auto;\n background: currentColor;\n -webkit-mask-position: center;\n -webkit-mask-repeat: no-repeat;\n -webkit-mask-size: contain;\n mask-position: center;\n mask-repeat: no-repeat;\n mask-size: contain;\n }\n .toolbar-chevron {\n width: 0.65rem;\n height: 0.65rem;\n transition: transform 0.15s ease;\n }\n [aria-expanded=\"true\"] \u003e .toolbar-chevron { transform: rotate(180deg); }\n [data-theme=\"light\"] #theme-icon {\n -webkit-mask-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cg fill='none' stroke='black' stroke-width='1.7' stroke-linecap='round'%3E%3Ccircle cx='10' cy='10' r='3.1'/%3E%3Cpath d='M10 2.2v1.4M10 16.4v1.4M2.2 10h1.4M16.4 10h1.4M4.5 4.5l1 1M14.5 14.5l1 1M15.5 4.5l-1 1M5.5 14.5l-1 1'/%3E%3C/g%3E%3C/svg%3E\");\n mask-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cg fill='none' stroke='black' stroke-width='1.7' stroke-linecap='round'%3E%3Ccircle cx='10' cy='10' r='3.1'/%3E%3Cpath d='M10 2.2v1.4M10 16.4v1.4M2.2 10h1.4M16.4 10h1.4M4.5 4.5l1 1M14.5 14.5l1 1M15.5 4.5l-1 1M5.5 14.5l-1 1'/%3E%3C/g%3E%3C/svg%3E\");\n }\n [data-theme=\"dark\"] #theme-icon {\n -webkit-mask-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath d='M15.2 12.7A6.3 6.3 0 017.3 4.8a6.3 6.3 0 107.9 7.9Z' fill='none' stroke='black' stroke-width='1.7' stroke-linejoin='round'/%3E%3C/svg%3E\");\n mask-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath d='M15.2 12.7A6.3 6.3 0 017.3 4.8a6.3 6.3 0 107.9 7.9Z' fill='none' stroke='black' stroke-width='1.7' stroke-linejoin='round'/%3E%3C/svg%3E\");\n }\n #btn-present[aria-pressed=\"false\"] #present-icon {\n -webkit-mask-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath d='M7.2 3.2h-4v4M12.8 3.2h4v4M7.2 16.8h-4v-4M12.8 16.8h4v-4' fill='none' stroke='black' stroke-width='1.7' stroke-linecap='round'/%3E%3C/svg%3E\");\n mask-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath d='M7.2 3.2h-4v4M12.8 3.2h4v4M7.2 16.8h-4v-4M12.8 16.8h4v-4' fill='none' stroke='black' stroke-width='1.7' stroke-linecap='round'/%3E%3C/svg%3E\");\n }\n #btn-present[aria-pressed=\"true\"] #present-icon {\n -webkit-mask-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath d='M7.2 7.2h-4v-4M12.8 7.2h4v-4M7.2 12.8h-4v4M12.8 12.8h4v4' fill='none' stroke='black' stroke-width='1.7' stroke-linecap='round'/%3E%3C/svg%3E\");\n mask-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath d='M7.2 7.2h-4v-4M12.8 7.2h4v-4M7.2 12.8h-4v4M12.8 12.8h4v4' fill='none' stroke='black' stroke-width='1.7' stroke-linecap='round'/%3E%3C/svg%3E\");\n }\n .toolbar-chevron {\n -webkit-mask-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='m3 4.5 3 3 3-3' fill='none' stroke='black' stroke-width='1.7' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E\");\n mask-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='m3 4.5 3 3 3-3' fill='none' stroke='black' stroke-width='1.7' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E\");\n }\n .toolbar #btn-preset { min-width: 5.4rem; }\n .preset-control-mark {\n width: 0.58rem;\n height: 0.58rem;\n flex: 0 0 auto;\n border: 1px solid var(--frontend-stroke);\n border-radius: 0.2rem;\n background: color-mix(in srgb, var(--frontend-stroke) 28%, transparent);\n transition: border-radius 0.15s, box-shadow 0.15s, transform 0.15s;\n }\n #btn-preset[data-preset-option=\"signal-flow\"] .preset-control-mark {\n border-radius: 50%;\n box-shadow: 0 0 10px color-mix(in srgb, var(--frontend-stroke) 78%, transparent);\n }\n #btn-preset[data-preset-option=\"blueprint\"] .preset-control-mark {\n border-radius: 0.06rem;\n background: transparent;\n box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--frontend-stroke) 32%, transparent);\n transform: rotate(45deg) scale(0.84);\n }\n #btn-preset[data-preset-option=\"editorial\"] .preset-control-mark {\n border-color: var(--arrow-emphasis);\n border-radius: 0;\n background: var(--arrow-emphasis);\n box-shadow: 0 0 0 2px color-mix(in srgb, var(--arrow-emphasis) 14%, transparent);\n transform: rotate(45deg) scale(0.72);\n }\n /* Menus anchor to the tools capsule so they open flush under it. */\n .toolbar-group .preset-wrap { position: static; }\n .toolbar .preset-menu {\n position: absolute;\n top: calc(100% + 0.125rem);\n right: 0;\n display: none;\n width: 17.5rem;\n max-width: calc(100vw - 2rem);\n padding: 0.375rem;\n border: 1px solid color-mix(in srgb, var(--panel-border) 90%, transparent);\n border-radius: 0.875rem;\n background: var(--toolbar-menu-bg);\n box-shadow: 0 1px 2px rgba(15, 23, 42, 0.06), 0 18px 44px -14px rgba(15, 23, 42, 0.3);\n }\n [data-theme=\"dark\"] .toolbar .preset-menu,\n [data-theme=\"dark\"] .toolbar .export-menu {\n border-color: rgba(148, 163, 184, 0.16);\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3), 0 18px 44px -12px rgba(0, 0, 0, 0.6);\n }\n /* Menu rows are not toolbar pills: drop the ghost-button pseudo surface. */\n .toolbar .preset-menu button,\n .toolbar .export-menu button { isolation: auto; min-height: 0; }\n .toolbar .preset-menu button::before { display: none; }\n .toolbar .preset-menu.open { display: block; }\n .toolbar .preset-menu.open,\n .toolbar .export-menu.open {\n animation: toolbar-menu-in 0.16s cubic-bezier(0.2, 0.8, 0.2, 1);\n transform-origin: top right;\n }\n @keyframes toolbar-menu-in {\n from { opacity: 0; transform: translateY(-0.25rem) scale(0.985); }\n to { opacity: 1; transform: translateY(0) scale(1); }\n }\n .preset-menu-heading {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 1rem;\n padding: 0.45rem 0.55rem 0.4rem;\n color: var(--text-faint);\n font-size: 0.6875rem;\n font-weight: 600;\n letter-spacing: 0.01em;\n }\n .preset-menu-shortcut {\n display: inline-flex;\n align-items: center;\n gap: 0.35rem;\n font-weight: 500;\n }\n .preset-menu-shortcut kbd {\n min-width: 1.125rem;\n padding: 0.05rem 0.3rem;\n border: 1px solid var(--panel-border);\n border-bottom-width: 2px;\n border-radius: 0.3rem;\n background: color-mix(in srgb, var(--text) 3%, var(--toolbar-menu-bg));\n color: var(--text-muted);\n font-family: inherit;\n font-size: 0.625rem;\n font-weight: 600;\n line-height: 1.3;\n text-align: center;\n }\n .toolbar .preset-option {\n display: grid;\n grid-template-columns: 2.375rem minmax(0, 1fr) 1rem;\n align-items: center;\n gap: 0.75rem;\n width: 100%;\n min-height: 3.125rem;\n margin-top: 0.125rem;\n padding: 0.45rem 0.6rem 0.45rem 0.5rem;\n border: 0;\n border-radius: 0.625rem;\n background: transparent;\n color: var(--text);\n box-shadow: none;\n backdrop-filter: none;\n text-align: left;\n transition: background 0.14s ease;\n }\n .toolbar .preset-option:hover,\n .toolbar .preset-option:focus-visible { background: color-mix(in srgb, var(--text) 5%, transparent); }\n .toolbar .preset-option[aria-checked=\"true\"] { background: color-mix(in srgb, var(--frontend-stroke) 8%, transparent); }\n .toolbar .preset-option:focus-visible {\n outline: 1.5px solid color-mix(in srgb, var(--frontend-stroke) 55%, transparent);\n outline-offset: -1.5px;\n }\n .preset-option-swatch {\n position: relative;\n width: 2.375rem;\n height: 1.75rem;\n overflow: hidden;\n border: 0;\n border-radius: 0.4375rem;\n background: #0f172a;\n box-shadow: inset 0 0 0 1px rgba(148, 163, 184, 0.28), 0 1px 2px rgba(15, 23, 42, 0.12);\n transition: box-shadow 0.14s ease, transform 0.14s ease;\n }\n .toolbar .preset-option:hover .preset-option-swatch { transform: translateY(-1px); }\n .toolbar .preset-option[aria-checked=\"true\"] .preset-option-swatch {\n box-shadow: 0 0 0 2px var(--toolbar-menu-bg), 0 0 0 3.5px var(--frontend-stroke);\n }\n .preset-option-swatch::before,\n .preset-option-swatch::after { position: absolute; content: \"\"; }\n .preset-option-swatch.classic::before {\n top: 0.42rem;\n right: 0.35rem;\n bottom: 0.42rem;\n left: 0.35rem;\n border: 1px solid #22d3ee;\n border-radius: 0.16rem;\n background: rgba(34, 211, 238, 0.14);\n }\n /* Classic previews the canvas it will actually produce in this theme. */\n [data-theme=\"light\"] .preset-option-swatch.classic {\n background:\n radial-gradient(circle at 1px 1px, #d9dee5 0.6px, transparent 0.9px) 0 0 / 5px 5px,\n #ffffff;\n }\n [data-theme=\"light\"] .preset-option-swatch.classic::before {\n border-color: #1b87a3;\n background: rgba(14, 116, 144, 0.08);\n box-shadow: 0 2px 4px rgba(17, 24, 39, 0.12);\n }\n .preset-option-swatch.signal-flow {\n border-color: #29415f;\n background: linear-gradient(135deg, #07101e 10%, #10243c 100%);\n }\n .preset-option-swatch.signal-flow::before {\n top: 50%;\n right: 0.2rem;\n left: 0.2rem;\n height: 1px;\n background: linear-gradient(90deg, #67e8f9, #c4b5fd);\n box-shadow: 0 0 8px #67e8f9;\n }\n .preset-option-swatch.signal-flow::after {\n top: calc(50% - 0.13rem);\n right: 0.28rem;\n width: 0.26rem;\n height: 0.26rem;\n border-radius: 50%;\n background: #67e8f9;\n box-shadow: 0 0 7px #67e8f9;\n }\n .preset-option-swatch.blueprint {\n border-color: #34799a;\n border-radius: 0.08rem;\n background-color: #0a2031;\n background-image:\n linear-gradient(rgba(102, 217, 239, 0.18) 1px, transparent 1px),\n linear-gradient(90deg, rgba(102, 217, 239, 0.18) 1px, transparent 1px);\n background-size: 0.42rem 0.42rem;\n }\n .preset-option-swatch.blueprint::before {\n top: 0.34rem;\n right: 0.38rem;\n bottom: 0.34rem;\n left: 0.38rem;\n border: 1px solid #66d9ef;\n }\n .preset-option-swatch.editorial {\n border-color: #b8aa94;\n border-radius: 0.08rem;\n background: repeating-linear-gradient(0deg, #f2eee5 0 0.34rem, #d8d0c2 0.36rem, #f2eee5 0.38rem);\n }\n .preset-option-swatch.editorial::before {\n top: 0;\n bottom: 0;\n left: 0.42rem;\n width: 1px;\n background: #c9502d;\n opacity: 0.72;\n }\n .preset-option-swatch.editorial::after {\n top: 0.52rem;\n right: 0.45rem;\n width: 0.38rem;\n height: 0.38rem;\n background: #c9502d;\n transform: rotate(45deg);\n }\n .preset-option-copy { min-width: 0; }\n .preset-option-copy strong,\n .preset-option-copy small { display: block; }\n .preset-option-copy strong {\n color: var(--text);\n font-size: 0.8125rem;\n font-weight: 550;\n letter-spacing: -0.005em;\n }\n .toolbar .preset-option[aria-checked=\"true\"] .preset-option-copy strong { font-weight: 650; }\n .preset-option-copy small {\n margin-top: 0.125rem;\n overflow: hidden;\n color: var(--text-faint);\n font-size: 0.6875rem;\n font-weight: 400;\n white-space: nowrap;\n text-overflow: ellipsis;\n }\n .preset-option-check {\n display: inline-block;\n width: 0.9rem;\n height: 0.9rem;\n color: var(--frontend-stroke);\n background: currentColor;\n -webkit-mask-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='m3 8.5 3 3 7-7' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E\");\n -webkit-mask-position: center;\n -webkit-mask-repeat: no-repeat;\n -webkit-mask-size: contain;\n mask-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='m3 8.5 3 3 7-7' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E\");\n mask-position: center;\n mask-repeat: no-repeat;\n mask-size: contain;\n opacity: 0;\n }\n .preset-option[aria-checked=\"true\"] .preset-option-check { opacity: 1; }\n html[data-preset=\"editorial\"] .toolbar button,\n html[data-preset=\"editorial\"] .preset-menu,\n html[data-preset=\"editorial\"] .export-menu,\n html[data-preset=\"editorial\"] .overview-map,\n html[data-preset=\"editorial\"] .semantic-lens,\n html[data-preset=\"editorial\"] .route-probe,\n html[data-preset=\"editorial\"] .focus-chip,\n html[data-preset=\"editorial\"] .diagram-guide,\n html[data-preset=\"editorial\"] .node-finder {\n border-radius: 0.3rem;\n }\n .motion-control-dot {\n width: 0.46rem;\n height: 0.46rem;\n flex: 0 0 auto;\n border: 1px solid var(--frontend-stroke);\n border-radius: 50%;\n background: var(--frontend-stroke);\n box-shadow: 0 0 9px color-mix(in srgb, var(--frontend-stroke) 72%, transparent);\n }\n #btn-motion[aria-pressed=\"false\"] .motion-control-dot {\n background: transparent;\n box-shadow: none;\n }\n /* The motion button breathes on the relay cycle, so the chrome keeps time\n with the wave it controls. Motion Governor mirrors the planned cycle from\n the diagram SVG onto the document. */\n html[data-ambient-motion=\"running\"][data-motion=\"live\"]:not([data-motion-owner]):not([data-embed]):not([data-share-playback]):not([data-document-hidden]) #btn-motion .motion-control-dot {\n animation: archify-motion-dot-breathe var(--flow-cycle-chrome, 6.4s) ease-in-out infinite;\n }\n @keyframes archify-motion-dot-breathe {\n 0%, 100% { opacity: 1; transform: scale(1); }\n 46% { opacity: 0.55; transform: scale(0.72); }\n }\n .toolbar .export-menu {\n position: absolute;\n top: calc(100% + 0.125rem);\n right: 0;\n background: var(--toolbar-menu-bg);\n border: 1px solid color-mix(in srgb, var(--panel-border) 90%, transparent);\n width: 19rem;\n max-width: calc(100vw - 2rem);\n max-height: calc(100vh - 5.5rem);\n overflow-y: auto;\n border-radius: 0.875rem;\n padding: 0.375rem;\n display: none;\n flex-direction: column;\n box-shadow: 0 1px 2px rgba(15, 23, 42, 0.06), 0 18px 44px -14px rgba(15, 23, 42, 0.3);\n scrollbar-width: thin;\n }\n .toolbar .export-menu.open { display: flex; }\n .export-menu-header {\n display: flex;\n align-items: flex-start;\n justify-content: space-between;\n gap: 1rem;\n padding: 0.55rem 0.6rem 0.6rem;\n }\n .export-menu-header strong,\n .export-menu-header span { display: block; }\n .export-menu-header strong {\n color: var(--text);\n font-size: 0.875rem;\n font-weight: 600;\n letter-spacing: -0.005em;\n }\n .export-menu-header span {\n margin-top: 0.15rem;\n color: var(--text-faint);\n font-size: 0.75rem;\n }\n .export-menu-header kbd {\n min-width: 1.125rem;\n padding: 0.05rem 0.3rem;\n border: 1px solid var(--panel-border);\n border-bottom-width: 2px;\n border-radius: 0.3rem;\n background: color-mix(in srgb, var(--text) 3%, var(--toolbar-menu-bg));\n color: var(--text-muted);\n font-family: inherit;\n font-size: 0.625rem;\n font-weight: 600;\n line-height: 1.3;\n text-align: center;\n }\n .export-menu-section {\n display: grid;\n grid-template-columns: minmax(0, 1fr);\n gap: 0.0625rem;\n }\n .export-menu-section + .export-menu-section {\n margin-top: 0.3rem;\n padding-top: 0.3rem;\n border-top: 1px solid color-mix(in srgb, var(--panel-border) 80%, transparent);\n }\n .export-menu-heading {\n grid-column: 1 / -1;\n display: block;\n padding: 0.4rem 0.6rem 0.25rem;\n color: var(--text-faint);\n font-size: 0.6875rem;\n font-weight: 600;\n letter-spacing: 0.01em;\n }\n .toolbar .export-menu button {\n display: grid;\n grid-template-columns: 1rem minmax(0, 1fr);\n align-items: center;\n column-gap: 0.7rem;\n background: transparent;\n border: 0;\n box-shadow: none;\n backdrop-filter: none;\n width: 100%;\n min-height: 2.5rem;\n text-align: left;\n padding: 0.4rem 0.6rem;\n border-radius: 0.5625rem;\n color: var(--text);\n white-space: nowrap;\n transition: background 0.14s ease;\n }\n .toolbar .export-menu button:hover,\n .toolbar .export-menu button:focus-visible { background: color-mix(in srgb, var(--text) 5%, transparent); color: var(--text); }\n .toolbar .export-menu button:focus-visible {\n outline: 1.5px solid color-mix(in srgb, var(--frontend-stroke) 55%, transparent);\n outline-offset: -1.5px;\n }\n /* Name and format hint share one line, so each row stays compact. */\n .export-item-copy {\n display: flex;\n align-items: baseline;\n justify-content: space-between;\n gap: 0.75rem;\n min-width: 0;\n }\n .export-item-copy strong {\n overflow: hidden;\n color: inherit;\n font-size: 0.8125rem;\n font-weight: 500;\n text-overflow: ellipsis;\n }\n .export-item-copy small {\n flex: none;\n color: var(--text-faint);\n font-size: 0.6875rem;\n font-weight: 450;\n }\n .toolbar .export-menu button::before {\n position: static;\n z-index: auto;\n inset: auto;\n border-radius: 0;\n display: block;\n width: 1rem;\n height: 1rem;\n background: currentColor;\n color: var(--text-muted);\n content: \"\";\n opacity: 0.8;\n -webkit-mask-position: center;\n -webkit-mask-repeat: no-repeat;\n -webkit-mask-size: contain;\n mask-position: center;\n mask-repeat: no-repeat;\n mask-size: contain;\n }\n .toolbar .export-menu button[data-action=\"route-share-card\"]::before,\n .toolbar .export-menu button[data-action=\"reach-share-card\"]::before {\n -webkit-mask-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath fill='none' stroke='black' stroke-width='1.7' d='M3 4.5h14v11H3zM6 12l2.5-2.5 2 2 1.5-1.5 2 2'/%3E%3Ccircle cx='13.8' cy='7.3' r='1.2' fill='black'/%3E%3C/svg%3E\");\n mask-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath fill='none' stroke='black' stroke-width='1.7' d='M3 4.5h14v11H3zM6 12l2.5-2.5 2 2 1.5-1.5 2 2'/%3E%3Ccircle cx='13.8' cy='7.3' r='1.2' fill='black'/%3E%3C/svg%3E\");\n }\n .toolbar .export-menu button[data-action^=\"copy\"]::before {\n -webkit-mask-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Crect x='6' y='6' width='10' height='10' rx='1.5' fill='none' stroke='black' stroke-width='1.7'/%3E%3Cpath d='M4 13H3.5A1.5 1.5 0 012 11.5v-8A1.5 1.5 0 013.5 2h8A1.5 1.5 0 0113 3.5V4' fill='none' stroke='black' stroke-width='1.7'/%3E%3C/svg%3E\");\n mask-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Crect x='6' y='6' width='10' height='10' rx='1.5' fill='none' stroke='black' stroke-width='1.7'/%3E%3Cpath d='M4 13H3.5A1.5 1.5 0 012 11.5v-8A1.5 1.5 0 013.5 2h8A1.5 1.5 0 0113 3.5V4' fill='none' stroke='black' stroke-width='1.7'/%3E%3C/svg%3E\");\n }\n .toolbar .export-menu button[data-format=\"png\"]::before,\n .toolbar .export-menu button[data-format=\"jpeg\"]::before,\n .toolbar .export-menu button[data-format=\"webp\"]::before {\n -webkit-mask-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath d='M5 2.5h7l3 3v12H5zM12 2.5v3h3' fill='none' stroke='black' stroke-width='1.7'/%3E%3Cpath d='M7.5 14l2-2 1.5 1.5 1.5-1.5' fill='none' stroke='black' stroke-width='1.7'/%3E%3C/svg%3E\");\n mask-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath d='M5 2.5h7l3 3v12H5zM12 2.5v3h3' fill='none' stroke='black' stroke-width='1.7'/%3E%3Cpath d='M7.5 14l2-2 1.5 1.5 1.5-1.5' fill='none' stroke='black' stroke-width='1.7'/%3E%3C/svg%3E\");\n }\n .toolbar .export-menu button[data-format^=\"svg\"]::before,\n .toolbar .export-menu button[data-format=\"webm\"]::before {\n -webkit-mask-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath d='M10 2.5l7 4v7l-7 4-7-4v-7zM3 6.5l7 4 7-4M10 10.5v7' fill='none' stroke='black' stroke-width='1.7'/%3E%3C/svg%3E\");\n mask-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath d='M10 2.5l7 4v7l-7 4-7-4v-7zM3 6.5l7 4 7-4M10 10.5v7' fill='none' stroke='black' stroke-width='1.7'/%3E%3C/svg%3E\");\n }\n .toolbar .export-menu button[data-action=\"route-share-card\"],\n .toolbar .export-menu button[data-action=\"reach-share-card\"] {\n color: var(--text);\n background: color-mix(in srgb, var(--frontend-stroke) 7%, transparent);\n }\n .toolbar .export-menu button[data-action=\"route-share-card\"]::before,\n .toolbar .export-menu button[data-action=\"reach-share-card\"]::before { color: var(--frontend-stroke); opacity: 1; }\n .toolbar .export-menu button[hidden] { display: none; }\n .toolbar .export-menu button[data-action=\"route-share-card\"]:hover,\n .toolbar .export-menu button[data-action=\"reach-share-card\"]:hover {\n background: color-mix(in srgb, var(--frontend-stroke) 12%, transparent);\n }\n .toolbar .export-menu button:disabled {\n color: var(--text-faint);\n background: transparent;\n opacity: 0.55;\n cursor: not-allowed;\n }\n .toolbar .export-menu .hint {\n color: var(--text-faint);\n font-size: 0.6875rem;\n }\n\n /* Toast — brief feedback for actions like \"copied\" */\n .archify-toast {\n position: fixed;\n top: 1rem;\n left: 50%;\n transform: translateX(-50%) translateY(-8px);\n background: var(--toolbar-menu-bg);\n color: var(--text);\n border: 1px solid var(--toolbar-border);\n padding: 0.5rem 1rem;\n border-radius: 0.5rem;\n font-family: inherit;\n font-size: 0.75rem;\n opacity: 0;\n transition: opacity 0.2s, transform 0.2s;\n z-index: 200;\n pointer-events: none;\n box-shadow: 0 4px 20px rgba(0,0,0,0.25);\n }\n .archify-toast.show {\n opacity: 1;\n transform: translateX(-50%) translateY(0);\n }\n\n @media (max-width: 720px) {\n body { padding: 1rem; }\n .toolbar .preset-menu,\n .toolbar .export-menu {\n position: fixed;\n top: 4.5rem;\n right: 1rem;\n left: 1rem;\n width: auto;\n max-width: none;\n }\n .toolbar {\n position: relative;\n top: auto;\n right: auto;\n justify-content: flex-end;\n width: max-content;\n max-width: 100%;\n margin-left: auto;\n margin-bottom: 1rem;\n }\n .header { margin-bottom: 1.25rem; padding-right: 0; }\n html[data-preset=\"signal-flow\"] .header,\n html[data-preset=\"blueprint\"] .header,\n html[data-preset=\"editorial\"] .header { padding-right: 0; }\n .header-row {\n gap: 0.75rem;\n align-items: flex-start;\n }\n html[data-preset=\"signal-flow\"] .header-row,\n html[data-preset=\"blueprint\"] .header-row,\n html[data-preset=\"editorial\"] .header-row { flex-wrap: wrap; }\n html[data-preset=\"signal-flow\"] .header-row::after {\n margin-left: 0;\n order: 3;\n }\n html[data-preset=\"blueprint\"] .header-row::after {\n margin-left: 0;\n order: 3;\n }\n html[data-preset=\"editorial\"] .header-row::after {\n margin-left: 0;\n order: 3;\n }\n h1 {\n font-size: 1.25rem;\n line-height: 1.25;\n }\n .subtitle {\n margin-left: 0;\n font-size: 0.8125rem;\n line-height: 1.55;\n }\n .diagram-container {\n padding: 0.75rem;\n border-radius: 0.75rem;\n }\n html:not([data-embed=\"true\"]) .diagram-container {\n padding: 0.75rem 0.75rem 4.25rem;\n }\n html:not([data-embed=\"true\"]) .diagram-container[data-wide-diagram=\"true\"] {\n overflow-x: auto;\n overflow-y: hidden;\n overscroll-behavior-x: contain;\n scrollbar-width: thin;\n scroll-behavior: smooth;\n }\n html:not([data-embed=\"true\"]) .diagram-container[data-wide-diagram=\"true\"] \u003e svg {\n min-width: 720px;\n }\n html:not([data-embed=\"true\"]) .diagram-container[data-wide-diagram=\"true\"] .diagram-nav,\n html:not([data-embed=\"true\"]) .diagram-container[data-wide-diagram=\"true\"] .overview-map,\n html:not([data-embed=\"true\"]) .diagram-container[data-wide-diagram=\"true\"] .focus-chip,\n html:not([data-embed=\"true\"]) .diagram-container[data-wide-diagram=\"true\"] .route-probe,\n html:not([data-embed=\"true\"]) .diagram-container[data-wide-diagram=\"true\"] .semantic-lens,\n html:not([data-embed=\"true\"]) .diagram-container[data-wide-diagram=\"true\"] .diagram-guide,\n html:not([data-embed=\"true\"]) .diagram-container[data-wide-diagram=\"true\"] .node-finder {\n transform: translateX(var(--archify-scroll-x, 0px));\n }\n .diagram-nav { right: 0.5rem; bottom: 0.5rem; }\n .diagram-nav button {\n min-width: 2.75rem;\n height: 2.75rem;\n }\n .diagram-nav [data-view=\"reset\"] { min-width: 3.75rem; }\n .diagram-nav [data-view=\"reset\"][data-detail-visible] { min-width: 4.35rem; }\n .overview-map {\n right: 0.5rem;\n bottom: 3.7rem;\n width: min(10.25rem, calc(100% - 1rem));\n }\n .overview-map[data-docked=\"true\"] {\n right: var(--archify-radar-right, 0.5rem);\n top: var(--archify-radar-top, 0.5rem);\n bottom: auto;\n transform: none !important;\n }\n .overview-map[data-docked=\"true\"][data-dock-side=\"left\"] {\n right: auto;\n left: var(--archify-radar-left, 0.5rem);\n }\n .overview-map-surface { height: 4rem; }\n .overview-map-feedback { right: 0.5rem; bottom: 3.7rem; }\n .semantic-lens {\n right: 0.5rem;\n bottom: 3.7rem;\n width: calc(100% - 1rem);\n max-height: calc(100% - 4.2rem);\n }\n .semantic-lens[data-dock-side] { left: auto; right: 0.5rem; }\n .semantic-lens-kinds { max-height: min(12rem, 34vh); }\n .focus-chip {\n left: 0.5rem;\n top: 0.5rem;\n width: calc(100% - 1rem);\n max-width: none;\n }\n .relationship-lens-head { padding-top: 0.7rem; }\n .relationship-lens-drag-handle { display: none; }\n .relationship-lens-list { max-height: min(12rem, 38vh); }\n .relationship-lens-actions #btn-focus-relations { display: block; color: var(--database-stroke); }\n .focus-chip:not([data-relations-expanded=\"true\"]) .relationship-lens-head { border-bottom-color: transparent; }\n .focus-chip:not([data-relations-expanded=\"true\"]) .relationship-lens-list { display: none; }\n /* While a mobile reader previews one relationship, turn the full Lens\n into a compact peek card. This keeps the exact source and target on\n canvas instead of covering them with a long, scrollable list. */\n .focus-chip[data-relationship-previewing=\"true\"] .relationship-lens-list {\n max-height: none;\n padding: 0.35rem;\n overflow: hidden;\n }\n .focus-chip[data-relationship-previewing=\"true\"] .relationship-lens-group { margin: 0; }\n .focus-chip[data-relationship-previewing=\"true\"] .relationship-lens-group-title,\n .focus-chip[data-relationship-previewing=\"true\"] .relationship-lens-row:not([data-preview-active=\"true\"]) {\n display: none;\n }\n .node-finder {\n top: 0.5rem;\n left: 0.5rem;\n width: min(22rem, calc(100% - 1rem));\n max-height: calc(100% - 1rem);\n }\n .diagram-guide {\n top: 0.5rem;\n right: 0.5rem;\n left: 0.5rem;\n width: auto;\n max-height: calc(100% - 1rem);\n }\n .diagram-guide-head { padding: 0.62rem 0.7rem 0.52rem; }\n .diagram-guide-stats { padding: 0.42rem 0.7rem; }\n .diagram-guide-actions {\n grid-template-columns: 1fr;\n gap: 0.28rem;\n padding: 0.42rem;\n }\n .diagram-guide-action {\n min-height: 3.1rem;\n padding: 0.42rem 0.48rem;\n }\n .diagram-guide-action small {\n display: block;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n .diagram-guide-index {\n width: 1.5rem;\n height: 1.5rem;\n }\n .diagram-guide-shortcuts { padding: 0.46rem 0.7rem 0.52rem; }\n .diagram-guide-action[data-guide-action=\"present\"] { grid-column: auto; }\n .route-probe[data-guide-open=\"true\"] {\n visibility: hidden;\n pointer-events: none;\n }\n .node-finder-results { max-height: min(16rem, 38vh); }\n .route-probe {\n left: 0.5rem;\n bottom: 3.7rem;\n width: calc(100% - 1rem);\n min-width: 0;\n }\n .route-probe[data-route-dock=\"top\"] {\n top: 0.5rem;\n bottom: auto;\n }\n .route-probe[data-finder-open=\"true\"] {\n visibility: hidden;\n pointer-events: none;\n }\n .route-probe-head { padding: 0.45rem 0.48rem 0.38rem; }\n .route-probe-path { padding: 0.35rem 0.48rem 0.2rem; }\n .route-probe-node { min-height: 2.75rem !important; }\n .route-journey-controls {\n grid-template-columns: 2.75rem minmax(0, 1fr) 2.75rem minmax(0, 1fr);\n padding: 0.08rem 0.48rem 0.32rem;\n }\n .route-journey-controls button { min-height: 2.75rem; }\n .route-probe-status { padding: 0.05rem 0.48rem 0.38rem; }\n .route-probe-node { max-width: 7.5rem; }\n \n \n \n \n \n \n \n \n \n \n \n html[data-embed=\"true\"][data-share-playback=\"true\"] .diagram-container {\n padding-top: 4.25rem;\n }\n html[data-embed=\"true\"][data-share-moment=\"true\"] .diagram-container {\n padding-top: 4.25rem;\n }\n \n .cards {\n grid-template-columns: 1fr;\n margin-top: 1rem;\n }\n html[data-present=\"true\"]:not([data-embed=\"true\"]) body { padding: 0.5rem; }\n html[data-present=\"true\"]:not([data-embed=\"true\"]) .container {\n gap: 0.5rem;\n height: calc(100dvh - 1rem);\n }\n html[data-present=\"true\"]:not([data-embed=\"true\"]) .toolbar {\n position: fixed;\n top: 0.5rem;\n right: 0.5rem;\n margin: 0;\n gap: 0.25rem;\n }\n html[data-present=\"true\"]:not([data-embed=\"true\"]) .toolbar button {\n min-height: 2.75rem;\n padding: 0.42rem 0.58rem;\n }\n html[data-present=\"true\"]:not([data-embed=\"true\"]) .header {\n min-height: 2.25rem;\n padding: 3.5rem 0 0;\n }\n html[data-present=\"true\"]:not([data-embed=\"true\"]) .header-row {\n min-height: 2.25rem;\n margin: 0;\n align-items: center;\n flex-wrap: nowrap;\n }\n html[data-present=\"true\"]:not([data-embed=\"true\"]) .header-row::after,\n html[data-present=\"true\"]:not([data-embed=\"true\"]) .pulse-dot,\n html[data-present=\"true\"]:not([data-embed=\"true\"]) .subtitle { display: none; }\n html[data-present=\"true\"]:not([data-embed=\"true\"]) h1 {\n flex: 1 1 auto;\n min-width: 0;\n max-width: 100%;\n overflow: hidden;\n font-size: 0.875rem;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n \n html[data-present=\"true\"]:not([data-embed=\"true\"]) .diagram-container { padding: 0.5rem 0.5rem 3.75rem; }\n html[data-present=\"true\"]:not([data-embed=\"true\"]) .diagram-container[data-wide-diagram=\"true\"] \u003e svg {\n width: 720px;\n height: auto;\n min-width: 720px;\n }\n }\n\n @media (max-width: 420px) {\n .export-menu-section { grid-template-columns: 1fr; }\n .export-menu-heading,\n .toolbar .export-menu button[data-action=\"route-share-card\"],\n .toolbar .export-menu button[data-action=\"reach-share-card\"] { grid-column: 1; }\n }\n\n @media (prefers-reduced-motion: reduce) {\n .toolbar .preset-menu.open,\n .toolbar .export-menu.open { animation: none; }\n .toolbar-chevron { transition: none; }\n }\n\n @media (max-width: 360px) {\n .toolbar { gap: 0.25rem; }\n .toolbar button { padding-right: 0.58rem; padding-left: 0.58rem; }\n .toolbar #btn-motion { min-width: 4.4rem; padding-right: 0.58rem; padding-left: 0.58rem; }\n #theme-label, #preset-label, #present-label { display: none; }\n }\n\n /* ==========================================================\n SVG SEMANTIC CLASSES — use these instead of inline fill/stroke\n ========================================================== */\n .c-grid { stroke: var(--grid); fill: none; }\n .c-mask { fill: var(--mask); stroke: none; }\n .c-lane-rule { stroke: var(--lane-stroke); fill: none; opacity: 0.7; }\n .c-lane-wash { fill: var(--lane-fill); stroke: none; }\n\n .c-frontend { fill: var(--frontend-fill); stroke: var(--frontend-stroke); }\n .c-backend { fill: var(--backend-fill); stroke: var(--backend-stroke); }\n .c-database { fill: var(--database-fill); stroke: var(--database-stroke); }\n .c-cloud { fill: var(--cloud-fill); stroke: var(--cloud-stroke); }\n .c-security { fill: var(--security-fill); stroke: var(--security-stroke); }\n .c-messagebus { fill: var(--messagebus-fill); stroke: var(--messagebus-stroke); }\n .c-external { fill: var(--external-fill); stroke: var(--external-stroke); }\n\n /* Text helpers */\n .t-primary { fill: var(--text); }\n .t-muted { fill: var(--text-muted); }\n .t-dim { fill: var(--text-dim); }\n .t-frontend { fill: var(--frontend-stroke); }\n .t-backend { fill: var(--backend-stroke); }\n .t-database { fill: var(--database-stroke); }\n .t-cloud { fill: var(--cloud-stroke); }\n .t-security { fill: var(--security-stroke); }\n .t-messagebus { fill: var(--messagebus-stroke); }\n .t-external { fill: var(--external-stroke); }\n\n /* Relationship labels share the exact color token used by their path. */\n .t-edge-default { fill: var(--arrow); }\n .t-edge-emphasis { fill: var(--arrow-emphasis); }\n .t-edge-security { fill: var(--security-stroke); }\n .t-edge-dashed { fill: var(--database-stroke); }\n\n /* Classic canvas: a quiet dot field replaces drafting grid lines, and nodes\n read as lifted cards with hairline strokes. Paint only; authored geometry,\n hit areas and exports keep their canonical shapes. */\n html[data-preset=\"classic\"] .diagram-container {\n background:\n radial-gradient(circle at 1px 1px, var(--canvas-dot) 1px, transparent 1.4px) 0 0 / 20px 20px,\n var(--panel);\n }\n html[data-preset=\"classic\"] .diagram-container \u003e svg .c-grid { stroke: transparent; }\n html[data-preset=\"classic\"][data-theme=\"light\"] .diagram-container \u003e svg [data-node-id] \u003e rect:not(.c-mask) {\n --ambient-node-base-filter: drop-shadow(0 1px 1px rgba(17, 24, 39, 0.05)) drop-shadow(0 6px 12px rgba(17, 24, 39, 0.06));\n filter: var(--ambient-node-base-filter);\n }\n html[data-preset=\"classic\"][data-theme=\"dark\"] .diagram-container \u003e svg [data-node-id] \u003e rect:not(.c-mask) {\n --ambient-node-base-filter: drop-shadow(0 8px 16px rgba(0, 0, 0, 0.35));\n filter: var(--ambient-node-base-filter);\n }\n html[data-preset=\"classic\"] .diagram-container \u003e svg [data-edge-from] {\n stroke-linecap: round;\n stroke-linejoin: round;\n }\n html[data-preset=\"classic\"] .diagram-container \u003e svg .c-lane-wash { fill: color-mix(in srgb, var(--text) 2.6%, transparent); }\n html[data-preset=\"classic\"] .diagram-container \u003e svg .c-lane-rule { stroke: var(--panel-border); opacity: 1; }\n html[data-preset=\"classic\"][data-theme=\"dark\"] .diagram-container \u003e svg .c-lane-rule { stroke: rgba(148, 163, 184, 0.16); }\n html[data-preset=\"classic\"] .diagram-container \u003e svg [data-node-id] {\n transition: transform 0.18s cubic-bezier(0.22, 1, 0.36, 1);\n }\n html[data-preset=\"classic\"]:not([data-motion=\"still\"]) .diagram-container \u003e svg [data-node-id]:hover {\n transform: translateY(-1.5px);\n }\n\n /* Semantic Sigils are small authored role stamps, not icons from a brand\n pack and not viewer controls. Prefix selectors with svg so the canonical\n export's SVG-only stylesheet collector keeps them automatically. */\n svg .semantic-sigil {\n fill: none;\n stroke: currentColor;\n stroke-width: 1.35;\n stroke-linecap: round;\n stroke-linejoin: round;\n opacity: 0.76;\n pointer-events: none;\n }\n svg .semantic-sigil \u003e * { vector-effect: non-scaling-stroke; }\n svg .semantic-sigil .sigil-fill { fill: currentColor; stroke: none; }\n svg .s-frontend { color: var(--frontend-stroke); }\n svg .s-backend { color: var(--backend-stroke); }\n svg .s-database { color: var(--database-stroke); }\n svg .s-cloud { color: var(--cloud-stroke); }\n svg .s-security { color: var(--security-stroke); }\n svg .s-messagebus { color: var(--messagebus-stroke); }\n svg .s-external { color: var(--external-stroke); }\n\n /* Brand Marks are authored identity badges. Their color is contained\n inside one neutral plate so vendor color never replaces Archify's\n semantic node and relationship vocabulary. */\n svg .brand-mark { pointer-events: none; }\n svg .brand-mark-badge { fill: #fff; stroke: none; }\n svg .brand-mark-frame {\n fill: none;\n stroke: #cbd5e1;\n stroke-width: 0.8;\n vector-effect: non-scaling-stroke;\n }\n svg .brand-mark-fallback {\n fill: none;\n stroke: #475569;\n stroke-width: 1.15;\n stroke-linecap: round;\n stroke-linejoin: round;\n }\n svg[data-preset=\"blueprint\"] .brand-mark-badge,\n svg[data-preset=\"blueprint\"] .brand-mark-frame { rx: 1px; }\n\n /* Reading Depth is a viewer-only level-of-detail layer. MAP preserves the\n authored structure and primary labels, READ restores relationship labels\n and node context, and FULL restores tags and fine annotations. Semantic\n intent can reveal the relevant detail without moving any geometry. */\n svg [data-detail] { transition: opacity 160ms ease; }\n svg [data-detail-anchor] {\n transform-box: fill-box;\n transform-origin: center;\n transition: transform 160ms ease;\n }\n .diagram-container[data-detail-level=\"map\"] svg [data-detail=\"context\"],\n .diagram-container[data-detail-level=\"map\"] svg [data-detail=\"fine\"],\n .diagram-container[data-detail-level=\"read\"] svg [data-detail=\"fine\"] {\n opacity: 0;\n pointer-events: none;\n }\n .diagram-container[data-detail-level=\"map\"] svg [data-detail-anchor] {\n transform: translateY(8px);\n }\n\n /* Focus, lens, preview, route, and story states are stronger reader intent than\n the global zoom level. Reveal only their exact semantic matches. */\n svg[data-focus-active] [data-focus-match][data-detail],\n svg[data-focus-active] [data-focus-match] [data-detail],\n svg[data-reach-active] [data-reach-match][data-detail],\n svg[data-reach-active] [data-reach-match] [data-detail],\n svg[data-lens-active] [data-lens-match][data-detail],\n svg[data-lens-active] [data-lens-match] [data-detail],\n svg[data-legend-preview-active] [data-legend-preview-match][data-detail],\n svg[data-legend-preview-active] [data-legend-preview-match] [data-detail],\n svg[data-intent-trace-active] [data-intent-trace-match][data-detail],\n svg[data-intent-trace-active] [data-intent-trace-match] [data-detail],\n svg[data-route-active] [data-route-match][data-detail],\n svg[data-route-active] [data-route-match] [data-detail],\n svg[data-relationship-preview-active] [data-relationship-preview][data-detail],\n svg[data-relationship-preview-active] [data-relationship-preview] [data-detail],\n svg [data-node-id]:hover [data-detail],\n svg [data-node-id]:focus-visible [data-detail] {\n opacity: 1;\n pointer-events: auto;\n }\n svg[data-focus-active] [data-focus-match] [data-detail-anchor],\n svg[data-reach-active] [data-reach-match] [data-detail-anchor],\n svg[data-lens-active] [data-lens-match] [data-detail-anchor],\n svg[data-legend-preview-active] [data-legend-preview-match] [data-detail-anchor],\n svg[data-intent-trace-active] [data-intent-trace-match] [data-detail-anchor],\n svg[data-route-active] [data-route-match] [data-detail-anchor],\n svg [data-node-id]:hover [data-detail-anchor],\n svg [data-node-id]:focus-visible [data-detail-anchor] { transform: none; }\n\n /* Arrows */\n .a-default { stroke: var(--arrow); fill: none; }\n .a-emphasis { stroke: var(--arrow-emphasis); fill: none; }\n .a-security { stroke: var(--security-stroke); fill: none; stroke-dasharray: 5,5; }\n .a-dashed { stroke: var(--database-stroke); fill: none; stroke-dasharray: 4,4; }\n\n /* Arrowhead markers reference these */\n .m-default { fill: var(--arrow); }\n .m-emphasis { fill: var(--arrow-emphasis); }\n .m-security { fill: var(--security-stroke); }\n .m-dashed { fill: var(--database-stroke); }\n\n /* Stable semantic exploration hooks emitted by every renderer. */\n svg [data-node-id] {\n cursor: pointer;\n transition: opacity 0.18s ease, fill-opacity 0.18s ease, stroke-opacity 0.18s ease, filter 0.18s ease;\n }\n svg [data-edge-from],\n svg [data-graph-role=\"automatic-crossover-underlay\"] { transition: opacity 0.18s ease, filter 0.18s ease; }\n svg [data-node-id]:hover,\n svg [data-node-id]:focus-visible {\n filter: drop-shadow(0 0 7px var(--frontend-stroke));\n outline: none;\n }\n svg [data-legend-kind][role=\"button\"] { cursor: pointer; }\n svg [data-legend-kind]:focus { outline: none; }\n svg [data-legend-hit] {\n fill: transparent;\n stroke: transparent;\n stroke-width: 1.4;\n pointer-events: all;\n vector-effect: non-scaling-stroke;\n transition: stroke 150ms ease, stroke-width 150ms ease, filter 150ms ease;\n }\n svg [data-legend-count-badge] { pointer-events: none; }\n svg [data-legend-count-badge] rect {\n fill: var(--mask);\n stroke: var(--panel-border);\n stroke-width: 1;\n vector-effect: non-scaling-stroke;\n }\n svg [data-legend-count-badge] text {\n fill: var(--text-muted);\n font-size: 8px;\n font-weight: 700;\n text-anchor: middle;\n }\n svg [data-legend-kind][role=\"button\"]:hover [data-legend-hit] {\n stroke: color-mix(in srgb, var(--database-stroke) 58%, transparent);\n }\n svg [data-legend-kind][role=\"button\"]:focus-visible [data-legend-hit] {\n stroke: var(--frontend-stroke);\n stroke-width: 1.8;\n filter: drop-shadow(0 0 3px var(--frontend-stroke));\n }\n svg [data-legend-kind][data-legend-selected] [data-legend-hit] {\n stroke: var(--database-stroke);\n stroke-width: 2.2;\n stroke-dasharray: 3 2;\n }\n svg [data-legend-kind][data-legend-selected] [data-legend-count-badge] rect {\n stroke: var(--database-stroke);\n stroke-width: 1.8;\n }\n svg [data-legend-kind][data-legend-zero] { opacity: 0.44; }\n svg[data-preset=\"signal-flow\"] [data-legend-kind][data-legend-selected] [data-legend-hit] {\n filter: drop-shadow(0 0 4px var(--database-stroke));\n }\n svg[data-preset=\"blueprint\"] [data-legend-hit],\n svg[data-preset=\"blueprint\"] [data-legend-count-badge] rect { rx: 0; filter: none; }\n svg[data-legend-preview-active] [data-node-id],\n svg[data-legend-preview-active] [data-edge-from] { opacity: 0.11; }\n svg[data-legend-preview-active] [data-legend-preview-match] { opacity: 1; }\n svg[data-legend-preview-active] [data-legend-preview-peer] { opacity: 0.62; }\n svg[data-legend-preview-active] [data-legend-preview-selected] {\n filter: drop-shadow(0 0 8px var(--database-stroke));\n }\n svg[data-lens-active] [data-node-id],\n svg[data-lens-active] [data-edge-from] { opacity: 0.11; }\n svg[data-lens-active] [data-lens-match] { opacity: 1; }\n svg[data-lens-active] [data-lens-peer] { opacity: 0.62; }\n svg[data-lens-active] [data-lens-selected] {\n filter: drop-shadow(0 0 10px var(--database-stroke));\n }\n .semantic-lens-overlay { pointer-events: none; }\n .semantic-lens-flow {\n fill: none;\n stroke-width: 3.05;\n stroke-linecap: round;\n stroke-dasharray: 0.075 0.925;\n stroke-dashoffset: 0;\n vector-effect: non-scaling-stroke;\n opacity: 0.9;\n animation: archify-semantic-lens-flow 1.35s linear 1 both;\n animation-delay: var(--lens-flow-delay, 0s);\n }\n .semantic-lens-flow[data-direction=\"out\"],\n .semantic-lens-flow[data-direction=\"forward\"] {\n stroke: var(--frontend-stroke);\n filter: drop-shadow(0 0 3px var(--frontend-stroke));\n }\n .semantic-lens-flow[data-direction=\"in\"],\n .semantic-lens-flow[data-direction=\"reverse\"] {\n stroke: var(--database-stroke);\n filter: drop-shadow(0 0 3px var(--database-stroke));\n }\n .semantic-lens-flow[data-direction=\"within\"] {\n stroke: var(--messagebus-stroke);\n filter: drop-shadow(0 0 3px var(--messagebus-stroke));\n }\n svg[data-preset=\"signal-flow\"] .semantic-lens-flow {\n stroke-width: 3.7;\n opacity: 1;\n }\n svg[data-preset=\"signal-flow\"] .semantic-lens-flow[data-direction=\"out\"],\n svg[data-preset=\"signal-flow\"] .semantic-lens-flow[data-direction=\"forward\"] {\n filter: drop-shadow(0 0 6px var(--frontend-stroke));\n }\n svg[data-preset=\"signal-flow\"] .semantic-lens-flow[data-direction=\"in\"],\n svg[data-preset=\"signal-flow\"] .semantic-lens-flow[data-direction=\"reverse\"] {\n filter: drop-shadow(0 0 6px var(--database-stroke));\n }\n svg[data-preset=\"signal-flow\"] .semantic-lens-flow[data-direction=\"within\"] {\n filter: drop-shadow(0 0 6px var(--messagebus-stroke));\n }\n svg[data-preset=\"blueprint\"] .semantic-lens-flow {\n stroke-width: 2.45;\n stroke-linecap: square;\n filter: none;\n opacity: 0.78;\n }\n html[data-embed=\"true\"] .semantic-lens-overlay { display: none; }\n /* Automatic crossover masks are visual companions of their one semantic\n edge. Keep the mask free of data-edge-* identity so graph traversal and\n export still observe exactly one edge, then mirror reader emphasis from\n the paired foreground path in this presentation-only wrapper. */\n svg[data-legend-preview-active] [data-graph-role=\"automatic-crossover\"] \u003e [data-graph-role=\"automatic-crossover-underlay\"],\n svg[data-lens-active] [data-graph-role=\"automatic-crossover\"] \u003e [data-graph-role=\"automatic-crossover-underlay\"] { opacity: 0.11; }\n svg[data-legend-preview-active] [data-graph-role=\"automatic-crossover\"]:has(\u003e [data-legend-preview-match]) \u003e [data-graph-role=\"automatic-crossover-underlay\"],\n svg[data-lens-active] [data-graph-role=\"automatic-crossover\"]:has(\u003e [data-lens-match]) \u003e [data-graph-role=\"automatic-crossover-underlay\"] { opacity: 1; }\n svg[data-legend-preview-active] [data-graph-role=\"automatic-crossover\"]:has(\u003e [data-legend-preview-peer]) \u003e [data-graph-role=\"automatic-crossover-underlay\"],\n svg[data-lens-active] [data-graph-role=\"automatic-crossover\"]:has(\u003e [data-lens-peer]) \u003e [data-graph-role=\"automatic-crossover-underlay\"] { opacity: 0.62; }\n svg[data-focus-active] [data-node-id],\n svg[data-focus-active] [data-edge-from] { opacity: 0.13; }\n svg[data-focus-active] [data-graph-role=\"automatic-crossover\"] \u003e [data-graph-role=\"automatic-crossover-underlay\"] { opacity: 0.13; }\n svg[data-focus-active] [data-lifecycle-rail] { opacity: 0; }\n svg[data-focus-active] [data-focus-match] { opacity: 1; }\n svg[data-focus-active] [data-graph-role=\"automatic-crossover\"]:has(\u003e [data-focus-match]) \u003e [data-graph-role=\"automatic-crossover-underlay\"] { opacity: 1; }\n svg[data-focus-active] [data-focus-selected] {\n filter: drop-shadow(0 0 10px var(--frontend-stroke));\n }\n\n /* Authored Reachability is a bounded graph query over the relationships\n already present in the artifact. Upstream walks incoming edges;\n downstream walks outgoing edges. It never claims runtime causality and\n changes only viewer emphasis, not canonical geometry or export bytes. */\n svg[data-reach-active] [data-node-id],\n svg[data-reach-active] [data-edge-from] { opacity: 0.09; }\n svg[data-reach-active] [data-graph-role=\"automatic-crossover\"] \u003e [data-graph-role=\"automatic-crossover-underlay\"] { opacity: 0.09; }\n svg[data-reach-active] [data-reach-match] { opacity: 1; }\n svg[data-reach-active] [data-graph-role=\"automatic-crossover\"]:has(\u003e [data-reach-match]) \u003e [data-graph-role=\"automatic-crossover-underlay\"] { opacity: 1; }\n svg[data-reach-active=\"upstream\"] [data-reach-origin] {\n filter: drop-shadow(0 0 11px var(--database-stroke));\n }\n svg[data-reach-active=\"downstream\"] [data-reach-origin] {\n filter: drop-shadow(0 0 11px var(--backend-stroke));\n }\n svg[data-reach-active=\"upstream\"] [data-edge-from][data-reach-match] {\n filter: drop-shadow(0 0 4px color-mix(in srgb, var(--database-stroke) 72%, transparent));\n }\n svg[data-reach-active=\"downstream\"] [data-edge-from][data-reach-match] {\n filter: drop-shadow(0 0 4px color-mix(in srgb, var(--backend-stroke) 72%, transparent));\n }\n svg[data-preset=\"blueprint\"][data-reach-active] [data-reach-origin],\n svg[data-preset=\"blueprint\"][data-reach-active] [data-edge-from][data-reach-match] {\n filter: none;\n }\n\n /* Direct Relationship Explorer makes authored paths a real input surface\n without changing their visible stroke or canonical SVG. A transparent\n runtime rail supplies forgiving pointer geometry; the exact authored\n path and endpoints continue to carry every visible state. */\n .relationship-hit-overlay { pointer-events: none; }\n .relationship-hit-target { outline: none; }\n .relationship-hit-rail {\n fill: none;\n stroke: transparent;\n stroke-width: 24;\n stroke-linecap: round;\n stroke-linejoin: round;\n vector-effect: non-scaling-stroke;\n pointer-events: stroke;\n cursor: pointer;\n }\n .relationship-focus-rail {\n fill: none;\n stroke: color-mix(in srgb, var(--frontend-stroke) 34%, transparent);\n stroke-width: 6;\n stroke-dasharray: 2 4;\n stroke-linecap: round;\n stroke-linejoin: round;\n vector-effect: non-scaling-stroke;\n pointer-events: none;\n opacity: 0;\n }\n .relationship-hit-target:focus-visible .relationship-focus-rail {\n opacity: 1;\n }\n svg[data-preset=\"blueprint\"] .relationship-hit-target:focus-visible .relationship-focus-rail {\n stroke-linecap: square;\n filter: none;\n }\n svg[data-relationship-direct-active] [data-node-id],\n svg[data-relationship-direct-active] [data-edge-from] { opacity: 0.16; }\n svg[data-relationship-direct-active] [data-graph-role=\"automatic-crossover\"] \u003e [data-graph-role=\"automatic-crossover-underlay\"] { opacity: 0.16; }\n svg[data-relationship-direct-active] [data-relationship-preview],\n svg[data-relationship-direct-active] [data-relationship-preview-node] { opacity: 1; }\n svg[data-relationship-direct-active] [data-graph-role=\"automatic-crossover\"]:has(\u003e [data-relationship-preview]) \u003e [data-graph-role=\"automatic-crossover-underlay\"] { opacity: 1; }\n svg[data-focus-active]:not([data-relationship-pin-active]) .relationship-hit-rail,\n svg[data-route-picking] .relationship-hit-rail,\n svg[data-route-active] .relationship-hit-rail,\n svg[data-lens-active] .relationship-hit-rail { pointer-events: none; }\n html[data-embed=\"true\"] .relationship-hit-overlay { display: none; }\n @media (hover: none), (pointer: coarse) {\n .relationship-hit-rail { stroke-width: 24; }\n }\n\n /* Relationship Preview is temporary exploration state layered on top of\n neighborhood focus. It changes emphasis only; exported geometry and the\n relationship's original solid/dashed visual language stay untouched. */\n svg[data-relationship-preview-active] [data-focus-match] { opacity: 0.18; }\n svg[data-relationship-preview-active] [data-graph-role=\"automatic-crossover\"]:has(\u003e [data-focus-match]) \u003e [data-graph-role=\"automatic-crossover-underlay\"] { opacity: 0.18; }\n svg[data-relationship-preview-active] [data-lifecycle-rail] { opacity: 0; }\n svg[data-relationship-preview-active] [data-relationship-preview],\n svg[data-relationship-preview-active] [data-relationship-preview-node] { opacity: 1; }\n svg[data-relationship-preview-active] [data-graph-role=\"automatic-crossover\"]:has(\u003e [data-relationship-preview]) \u003e [data-graph-role=\"automatic-crossover-underlay\"] { opacity: 1; }\n svg[data-relationship-preview-active] [data-relationship-preview] {\n filter: drop-shadow(0 0 5px var(--arrow-emphasis));\n }\n svg[data-relationship-preview-active] [data-relationship-preview] path,\n svg[data-relationship-preview-active] path[data-relationship-preview],\n svg[data-relationship-preview-active] [data-relationship-preview] line,\n svg[data-relationship-preview-active] line[data-relationship-preview],\n svg[data-relationship-preview-active] [data-relationship-preview] polyline,\n svg[data-relationship-preview-active] polyline[data-relationship-preview] {\n stroke-width: 2.75;\n }\n svg[data-relationship-preview-active] [data-relationship-preview-source] {\n filter: drop-shadow(0 0 7px var(--frontend-stroke));\n }\n svg[data-relationship-preview-active] [data-relationship-preview-target] {\n filter: drop-shadow(0 0 7px var(--database-stroke));\n }\n .relationship-pulse-overlay { pointer-events: none; }\n .relationship-flow-pulse {\n fill: none;\n stroke: var(--arrow-emphasis);\n stroke-width: 3.35;\n stroke-linecap: round;\n stroke-dasharray: 0.085 0.915;\n stroke-dashoffset: 0;\n vector-effect: non-scaling-stroke;\n opacity: 0;\n filter: drop-shadow(0 0 4px var(--arrow-emphasis));\n animation: archify-relationship-pulse 1.2s linear 1 both;\n }\n svg[data-preset=\"signal-flow\"] .relationship-flow-pulse {\n stroke: var(--frontend-stroke);\n stroke-width: 3.85;\n filter: drop-shadow(0 0 7px var(--frontend-stroke));\n }\n svg[data-preset=\"blueprint\"] .relationship-flow-pulse {\n stroke-width: 2.5;\n stroke-linecap: square;\n filter: none;\n }\n svg[data-preset=\"editorial\"] .relationship-flow-pulse {\n stroke: var(--arrow-emphasis);\n stroke-width: 2.75;\n filter: none;\n }\n .semantic-flow-token {\n color: var(--frontend-stroke);\n opacity: 0;\n pointer-events: none;\n }\n .relationship-flow-token {\n animation: archify-relationship-token-life 1.2s linear 1 both;\n }\n\n .semantic-flow-token-halo {\n fill: var(--mask);\n stroke: currentColor;\n stroke-width: 1.1;\n vector-effect: non-scaling-stroke;\n opacity: 0.96;\n }\n .relationship-flow-token-shape {\n fill: var(--mask);\n stroke: currentColor;\n stroke-width: 1.35;\n stroke-linecap: round;\n stroke-linejoin: round;\n vector-effect: non-scaling-stroke;\n }\n .relationship-flow-token-ink {\n fill: none;\n stroke: currentColor;\n stroke-width: 1.05;\n stroke-linecap: round;\n vector-effect: non-scaling-stroke;\n }\n .relationship-flow-token-dot { fill: currentColor; stroke: none; }\n .semantic-flow-token[data-token-kind=\"data\"] { color: var(--database-stroke); }\n .semantic-flow-token[data-token-kind=\"event\"] { color: var(--messagebus-stroke); }\n .semantic-flow-token[data-token-kind=\"security\"] { color: var(--security-stroke); }\n .semantic-flow-token[data-token-kind=\"state\"] { color: var(--cloud-stroke); }\n svg[data-preset=\"signal-flow\"] .semantic-flow-token {\n filter: drop-shadow(0 0 5px currentColor);\n }\n svg[data-preset=\"blueprint\"] .semantic-flow-token { filter: none; }\n svg[data-preset=\"editorial\"] .semantic-flow-token { filter: none; }\n html[data-embed=\"true\"] .relationship-pulse-overlay { display: none; }\n\n /* Intent Trace is a temporary, pre-click exploration layer. Related\n topology stays readable while a viewer-only signal follows the active trace over\n cloned edge geometry; the authored edge remains untouched underneath. */\n /* Keep the node's opaque backing mask intact while its visible ink dims.\n Fading the whole group also fades that mask and briefly exposes arrows\n routed underneath the node when the pointer leaves. */\n svg[data-intent-trace-active] [data-node-id] { fill-opacity: 0.2; stroke-opacity: 0.2; }\n svg [data-node-id] \u003e .c-mask { fill-opacity: 1; }\n svg[data-intent-trace-active] [data-node-id][data-intent-trace-match] { fill-opacity: 1; stroke-opacity: 1; }\n svg[data-intent-trace-active] [data-edge-from] { opacity: 0.2; }\n svg[data-intent-trace-active] [data-graph-role=\"automatic-crossover\"] \u003e [data-graph-role=\"automatic-crossover-underlay\"] { opacity: 0.2; }\n svg[data-intent-trace-active] [data-lifecycle-rail] { opacity: 0; }\n svg[data-intent-trace-active] [data-intent-trace-match] { opacity: 1; }\n svg[data-intent-trace-active] [data-graph-role=\"automatic-crossover\"]:has(\u003e [data-intent-trace-match]) \u003e [data-graph-role=\"automatic-crossover-underlay\"] { opacity: 1; }\n svg[data-intent-trace-active] [data-intent-trace-selected] {\n filter: drop-shadow(0 0 10px var(--frontend-stroke));\n }\n .intent-trace-overlay { pointer-events: none; }\n .intent-trace-flow {\n fill: none;\n stroke-width: 3.1;\n stroke-linecap: round;\n stroke-dasharray: 0.1 0.9;\n stroke-dashoffset: 0;\n vector-effect: non-scaling-stroke;\n opacity: 0.94;\n animation: archify-intent-trace-flow 1.15s linear infinite;\n }\n /* Direction names describe each edge relative to the focused node only.\n The cloned geometry already runs from the authored source to target, so\n incoming and outgoing signals must share the same playback direction. */\n .intent-trace-flow[data-direction=\"out\"] {\n stroke: var(--frontend-stroke);\n filter: drop-shadow(0 0 4px var(--frontend-stroke));\n }\n .intent-trace-flow[data-direction=\"in\"] {\n stroke: var(--database-stroke);\n filter: drop-shadow(0 0 4px var(--database-stroke));\n animation-direction: normal;\n }\n .intent-trace-flow[data-direction=\"loop\"] {\n stroke: var(--security-stroke);\n filter: drop-shadow(0 0 4px var(--security-stroke));\n }\n svg[data-preset=\"blueprint\"] [data-intent-trace-selected],\n svg[data-preset=\"blueprint\"] .intent-trace-flow {\n filter: none;\n stroke-linecap: square;\n }\n svg[data-preset=\"editorial\"] [data-intent-trace-selected],\n svg[data-preset=\"editorial\"] .intent-trace-flow { filter: none; }\n .intent-trace-status {\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n margin: -1px;\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n clip-path: inset(50%);\n white-space: nowrap;\n border: 0;\n }\n\n /* Route Probe separates a durable two-point query from selection and\n one-hop hover. Only the chosen shortest directed path stays prominent;\n the traveling signal is cloned viewer geometry and never authored SVG. */\n svg[data-route-picking=\"target\"] [data-node-id] { opacity: 0.24; }\n svg[data-route-picking=\"target\"] [data-route-candidate],\n svg[data-route-picking=\"target\"] [data-route-start] { opacity: 1; }\n svg[data-route-picking] [data-node-id] { cursor: crosshair; }\n svg[data-route-picking] [data-route-start] {\n filter: drop-shadow(0 0 10px var(--frontend-stroke));\n }\n svg[data-route-active] [data-node-id],\n svg[data-route-active] [data-edge-from] { opacity: 0.11; }\n svg[data-route-active] [data-graph-role=\"automatic-crossover\"] \u003e [data-graph-role=\"automatic-crossover-underlay\"] { opacity: 0.11; }\n svg[data-route-active] [data-route-match] { opacity: 1; }\n svg[data-route-active] [data-graph-role=\"automatic-crossover\"]:has(\u003e [data-route-match]) \u003e [data-graph-role=\"automatic-crossover-underlay\"] { opacity: 1; }\n svg[data-route-active] [data-route-start] {\n filter: drop-shadow(0 0 11px var(--frontend-stroke));\n }\n svg[data-route-active] [data-route-end] {\n filter: drop-shadow(0 0 11px var(--security-stroke));\n }\n svg[data-route-active] [data-route-step]:not([data-route-start]):not([data-route-end]) {\n filter: drop-shadow(0 0 7px var(--backend-stroke));\n }\n svg[data-route-journey] [data-route-match][data-route-journey-state=\"past\"] { opacity: 0.62; }\n svg[data-route-journey] [data-route-match][data-route-journey-state=\"future\"] { opacity: 0.34; }\n svg[data-route-journey] [data-route-match][data-route-journey-state=\"current\"] { opacity: 1; }\n svg[data-route-journey] [data-graph-role=\"automatic-crossover\"]:has(\u003e [data-route-match][data-route-journey-state=\"past\"]) \u003e [data-graph-role=\"automatic-crossover-underlay\"] { opacity: 0.62; }\n svg[data-route-journey] [data-graph-role=\"automatic-crossover\"]:has(\u003e [data-route-match][data-route-journey-state=\"future\"]) \u003e [data-graph-role=\"automatic-crossover-underlay\"] { opacity: 0.34; }\n svg[data-route-journey] [data-graph-role=\"automatic-crossover\"]:has(\u003e [data-route-match][data-route-journey-state=\"current\"]) \u003e [data-graph-role=\"automatic-crossover-underlay\"] { opacity: 1; }\n svg[data-route-journey] [data-node-id][data-route-journey-current] {\n filter: drop-shadow(0 0 10px var(--backend-stroke));\n }\n svg[data-route-journey] [data-edge-from][data-route-journey-current] {\n opacity: 1;\n filter: drop-shadow(0 0 6px var(--backend-stroke));\n }\n .route-probe-overlay { pointer-events: none; }\n .route-journey-overlay { pointer-events: none; }\n .route-probe-flow {\n fill: none;\n stroke: var(--backend-stroke);\n stroke-width: 3.25;\n stroke-linecap: round;\n stroke-dasharray: 0.085 0.915;\n stroke-dashoffset: 0;\n vector-effect: non-scaling-stroke;\n opacity: 0.96;\n filter: drop-shadow(0 0 5px var(--backend-stroke));\n animation: archify-route-probe-flow 1.1s cubic-bezier(0.22, 1, 0.36, 1) 1 both;\n animation-delay: calc(var(--route-step, 0) * 0.16s);\n }\n .route-journey-flow {\n fill: none;\n stroke: var(--backend-stroke);\n stroke-width: 3.5;\n stroke-linecap: round;\n stroke-dasharray: 0.1 0.9;\n stroke-dashoffset: 0;\n vector-effect: non-scaling-stroke;\n opacity: 0;\n filter: drop-shadow(0 0 6px var(--backend-stroke));\n animation: archify-route-journey-flow 780ms cubic-bezier(0.22, 1, 0.36, 1) 1 both;\n }\n svg[data-preset=\"blueprint\"] [data-route-start],\n svg[data-preset=\"blueprint\"] [data-route-end],\n svg[data-preset=\"blueprint\"] [data-route-step],\n svg[data-preset=\"blueprint\"] .route-probe-flow {\n filter: none;\n stroke-linecap: square;\n }\n svg[data-preset=\"blueprint\"] .route-journey-flow {\n filter: none;\n stroke-linecap: square;\n }\n svg[data-preset=\"editorial\"] [data-route-start],\n svg[data-preset=\"editorial\"] [data-route-end],\n svg[data-preset=\"editorial\"] [data-route-step],\n svg[data-preset=\"editorial\"] .route-probe-flow,\n svg[data-preset=\"editorial\"] .route-journey-flow { filter: none; }\n\n svg[data-preset=\"editorial\"] .c-grid {\n stroke-dasharray: 1 4;\n opacity: 0.48;\n }\n svg[data-preset=\"editorial\"] .c-region {\n fill: color-mix(in srgb, var(--cloud-fill) 48%, transparent);\n stroke-dasharray: 9 4;\n }\n svg[data-preset=\"editorial\"] [data-node-id]:hover,\n svg[data-preset=\"editorial\"] [data-node-id]:focus-visible,\n svg[data-preset=\"editorial\"][data-focus-active] [data-focus-selected] {\n filter: drop-shadow(0 2px 2px color-mix(in srgb, var(--text) 18%, transparent));\n }\n\n svg[data-preset=\"blueprint\"] .c-grid {\n stroke-dasharray: 1 3;\n opacity: 0.78;\n }\n svg[data-preset=\"blueprint\"] .c-region {\n fill: color-mix(in srgb, var(--cloud-fill) 58%, transparent);\n stroke-dasharray: 12 4 2 4;\n }\n svg[data-preset=\"blueprint\"] .c-security-group,\n svg[data-preset=\"blueprint\"] .c-lane { stroke-dasharray: 7 3; }\n svg[data-preset=\"blueprint\"] [data-node-id]:hover,\n svg[data-preset=\"blueprint\"] [data-node-id]:focus-visible {\n filter: drop-shadow(0 0 3px var(--frontend-stroke));\n }\n svg[data-preset=\"blueprint\"][data-focus-active] [data-focus-selected] {\n filter: drop-shadow(0 0 3px var(--frontend-stroke));\n }\n svg[data-preset=\"blueprint\"][data-focus-active][data-reach-active] [data-focus-selected][data-reach-origin] {\n filter: none;\n }\n svg[data-preset=\"blueprint\"][data-relationship-preview-active] [data-relationship-preview],\n svg[data-preset=\"blueprint\"][data-relationship-preview-active] [data-relationship-preview-source],\n svg[data-preset=\"blueprint\"][data-relationship-preview-active] [data-relationship-preview-target] {\n filter: drop-shadow(0 0 3px var(--frontend-stroke));\n }\n\n /* Dashed boundary variants */\n .c-security-group { fill: transparent; stroke: var(--security-stroke); stroke-dasharray: 4,4; }\n .c-region { fill: rgba(251, 191, 36, 0.05); stroke: var(--cloud-stroke); stroke-dasharray: 8,4; }\n .c-lane { fill: var(--lane-fill); stroke: var(--lane-stroke); stroke-dasharray: 6,6; }\n\n /* Live plays one relay per cycle: a comet (halo, tail, head) leaves each\n source when the signal reaches it, travels at one shared speed, leaves a\n soft wake, and lands as a ripple while its target glows. Downstream edges\n depart only after their source is reached, so the reader sees the path the\n diagram describes rather than every edge shimmering at once. Sequence\n messages relay in authored chronological order. Motion Governor plans the\n timeline and its per-edge keyframes; this stylesheet owns appearance.\n Original line language and markers remain static. Node entry is bounded to\n one pass per document. */\n svg[data-animation=\"trace\"] {\n --flow-cycle: 6.4s;\n --flow-halo-peak: 0.17;\n --flow-glow-peak: 0.5;\n --flow-tail-peak: 0.42;\n --flow-head-peak: 0.95;\n --flow-wake-peak: 0.3;\n --flow-ripple-peak: 0.65;\n }\n .ambient-edge-flow {\n fill: none;\n stroke: var(--arrow-emphasis);\n stroke-linecap: round;\n pointer-events: none;\n display: none;\n }\n .ambient-flow-wake {\n --flow-peak: var(--flow-wake-peak, 0.3);\n stroke-width: 3.2;\n stroke-dasharray: var(--flow-dasharray, none);\n }\n .ambient-flow-halo {\n --flow-peak: var(--flow-halo-peak, 0.17);\n stroke-width: 6;\n stroke-dasharray: var(--flow-dash, 12px) calc(var(--flow-len, 0px) - var(--flow-dash, 12px));\n }\n /* A wide blurred streak centered on the head: this is the bloom that makes\n the comet read as a traveling orb of light rather than a bright dash. */\n .ambient-flow-glow {\n --flow-peak: var(--flow-glow-peak, 0.5);\n stroke-width: 9;\n stroke-dasharray: var(--flow-dash, 12px) calc(var(--flow-len, 0px) - var(--flow-dash, 12px));\n filter: blur(2.5px);\n }\n .ambient-flow-tail {\n --flow-peak: var(--flow-tail-peak, 0.42);\n stroke-width: 2.4;\n stroke-dasharray: var(--flow-dasharray, var(--flow-dash, 20px) calc(var(--flow-len, 0px) - var(--flow-dash, 20px)));\n }\n .ambient-flow-head {\n --flow-peak: var(--flow-head-peak, 0.95);\n stroke: color-mix(in srgb, var(--arrow-emphasis) 40%, white);\n stroke-width: 2.6;\n stroke-dasharray: var(--flow-dash, 6px) calc(var(--flow-len, 0px) - var(--flow-dash, 6px));\n }\n .ambient-flow-ripple {\n --flow-peak: var(--flow-ripple-peak, 0.65);\n stroke: color-mix(in srgb, var(--arrow-emphasis) 55%, white);\n stroke-width: 2;\n }\n html[data-ambient-motion=\"running\"][data-motion=\"live\"]:not([data-motion-owner]):not([data-embed]):not([data-share-playback]):not([data-document-hidden]) .ambient-edge-flow {\n display: inline;\n animation-name: var(--flow-name, none);\n animation-duration: var(--flow-cycle, 6.4s);\n animation-timing-function: linear;\n animation-iteration-count: infinite;\n animation-delay: var(--flow-delay, 0s);\n /* Without backwards fill a delayed layer would sit at its natural opacity\n (fully lit) until its departure arrives. */\n animation-fill-mode: backwards;\n }\n /* Stream packets trail the comet head as smaller, dimmer beads. */\n .ambient-flow-head.packet {\n --flow-peak: calc(var(--flow-head-peak, 0.95) * 0.62);\n stroke-width: 3.4;\n }\n /* Timeline and waterfall playhead: a crisp rule over a soft bloom. */\n .ambient-flow-playhead {\n --flow-peak: 0.9;\n stroke: color-mix(in srgb, var(--arrow-emphasis) 55%, white);\n stroke-width: 1.6;\n }\n .ambient-flow-playhead-glow {\n --flow-peak: 0.32;\n stroke-width: 10;\n filter: blur(4px);\n }\n /* Waterfall spans fill in proportion to their measured time, then settle. */\n .ambient-flow-fill {\n --flow-peak: 0.5;\n fill: color-mix(in srgb, var(--flow-fill, var(--arrow-emphasis)) 55%, white);\n stroke: none;\n transform-box: fill-box;\n transform-origin: left center;\n }\n .ambient-flow-fill-failed { fill: var(--security-stroke); --flow-peak: 0.5; }\n html[data-theme=\"light\"] .ambient-flow-playhead { stroke: color-mix(in srgb, var(--arrow-emphasis) 70%, black); }\n html[data-theme=\"light\"] .ambient-flow-fill { --flow-peak: 0.42; fill: color-mix(in srgb, var(--flow-fill, var(--arrow-emphasis)) 70%, black); }\n svg[data-preset=\"editorial\"] .ambient-flow-fill { --flow-peak: 0.2; }\n svg[data-preset=\"editorial\"] .ambient-flow-playhead-glow { --flow-peak: 0.14; }\n /* Each decorative flow follows its owning edge through tree visibility. */\n .ambient-edge-flow[data-tree-hidden] { display: none !important; animation: none !important; }\n svg[data-preset=\"blueprint\"] .ambient-edge-flow { stroke-linecap: square; }\n svg[data-preset=\"editorial\"] { --flow-halo-peak: 0.07; --flow-glow-peak: 0.28; --flow-tail-peak: 0.2; --flow-head-peak: 0.7; --flow-wake-peak: 0.14; --flow-ripple-peak: 0.3; }\n /* On light canvases the relay reads by saturation instead of bloom: the head\n and ripple mix toward ink instead of white, and every layer's peak rises so\n the wave survives the bright background. */\n html[data-theme=\"light\"] svg[data-animation=\"trace\"] {\n --flow-halo-peak: 0.22;\n --flow-glow-peak: 0.4;\n --flow-tail-peak: 0.5;\n --flow-wake-peak: 0.34;\n }\n html[data-theme=\"light\"] .ambient-flow-head {\n stroke: color-mix(in srgb, var(--arrow-emphasis) 55%, black);\n stroke-width: 2.8;\n }\n html[data-theme=\"light\"] .ambient-flow-ripple {\n stroke: color-mix(in srgb, var(--arrow-emphasis) 65%, black);\n }\n html[data-ambient-entry=\"running\"] svg[data-animation=\"trace\"] [data-animate=\"node\"] {\n transform-box: fill-box;\n transform-origin: center;\n animation: archify-node-pulse 3.6s ease-in-out 1 calc(var(--step, 0) * 160ms),\n archify-node-enter 0.9s cubic-bezier(0.22, 1, 0.36, 1) backwards calc(var(--step, 0) * 160ms);\n }\n html[data-ambient-entry=\"running\"] svg[data-preset=\"signal-flow\"][data-animation=\"trace\"] [data-animate=\"node\"] {\n animation: archify-node-pulse 3.1s ease-in-out 1 calc(var(--step, 0) * 160ms),\n archify-node-enter 0.9s cubic-bezier(0.22, 1, 0.36, 1) backwards calc(var(--step, 0) * 160ms);\n }\n html[data-ambient-entry=\"running\"] svg[data-preset=\"blueprint\"][data-animation=\"trace\"] [data-animate=\"node\"] {\n animation: archify-blueprint-node-pulse 3.8s ease-in-out 1 calc(var(--step, 0) * 160ms),\n archify-node-enter 0.9s cubic-bezier(0.22, 1, 0.36, 1) backwards calc(var(--step, 0) * 160ms);\n }\n html[data-ambient-entry=\"running\"] svg[data-preset=\"editorial\"][data-animation=\"trace\"] [data-animate=\"node\"] {\n animation: archify-editorial-node-pulse 3.5s ease-in-out 1 calc(var(--step, 0) * 160ms),\n archify-node-enter 0.9s cubic-bezier(0.22, 1, 0.36, 1) backwards calc(var(--step, 0) * 160ms);\n }\n /* Each node glows in its planned windows: when the relay reaches it, while a\n walk token rests on it, as a message lands, or as the playhead passes.\n During the bounded entrance all three animations run; the entrance pulse is\n listed after the repeating glow and wins on filter until it completes. */\n html[data-ambient-motion=\"running\"][data-motion=\"live\"]:not([data-motion-owner]):not([data-embed]):not([data-share-playback]):not([data-document-hidden]) svg[data-animation=\"trace\"] [data-animate=\"node\"] {\n animation: var(--flow-node-name, none) var(--flow-cycle, 6.4s) linear infinite;\n }\n html[data-ambient-motion=\"running\"][data-motion=\"live\"]:not([data-motion-owner]):not([data-embed]):not([data-share-playback]):not([data-document-hidden])[data-ambient-entry=\"running\"] svg[data-animation=\"trace\"] [data-animate=\"node\"] {\n animation: var(--flow-node-name, none) var(--flow-cycle, 6.4s) linear infinite,\n archify-node-pulse 3.6s ease-in-out 1 calc(var(--step, 0) * 160ms),\n archify-node-enter 0.9s cubic-bezier(0.22, 1, 0.36, 1) backwards calc(var(--step, 0) * 160ms);\n }\n html[data-ambient-motion=\"running\"][data-motion=\"live\"]:not([data-motion-owner]):not([data-embed]):not([data-share-playback]):not([data-document-hidden])[data-ambient-entry=\"running\"] svg[data-preset=\"signal-flow\"][data-animation=\"trace\"] [data-animate=\"node\"] {\n animation: var(--flow-node-name, none) var(--flow-cycle, 6.4s) linear infinite,\n archify-node-pulse 3.1s ease-in-out 1 calc(var(--step, 0) * 160ms),\n archify-node-enter 0.9s cubic-bezier(0.22, 1, 0.36, 1) backwards calc(var(--step, 0) * 160ms);\n }\n html[data-ambient-motion=\"running\"][data-motion=\"live\"]:not([data-motion-owner]):not([data-embed]):not([data-share-playback]):not([data-document-hidden])[data-ambient-entry=\"running\"] svg[data-preset=\"blueprint\"][data-animation=\"trace\"] [data-animate=\"node\"] {\n animation: var(--flow-node-name, none) var(--flow-cycle, 6.4s) linear infinite,\n archify-blueprint-node-pulse 3.8s ease-in-out 1 calc(var(--step, 0) * 160ms),\n archify-node-enter 0.9s cubic-bezier(0.22, 1, 0.36, 1) backwards calc(var(--step, 0) * 160ms);\n }\n html[data-ambient-motion=\"running\"][data-motion=\"live\"]:not([data-motion-owner]):not([data-embed]):not([data-share-playback]):not([data-document-hidden])[data-ambient-entry=\"running\"] svg[data-preset=\"editorial\"][data-animation=\"trace\"] [data-animate=\"node\"] {\n animation: var(--flow-node-name, none) var(--flow-cycle, 6.4s) linear infinite,\n archify-editorial-node-pulse 3.5s ease-in-out 1 calc(var(--step, 0) * 160ms),\n archify-node-enter 0.9s cubic-bezier(0.22, 1, 0.36, 1) backwards calc(var(--step, 0) * 160ms);\n }\n @keyframes archify-node-enter {\n from { opacity: 0; }\n to { opacity: 1; }\n }\n @media print {\n .ambient-edge-flow { display: none !important; animation: none !important; }\n svg[data-animation=\"trace\"] [data-animate=\"node\"] { animation: none !important; }\n }\n @keyframes archify-node-pulse {\n 0%, 72%, 100% { filter: none; stroke-width: 1.5; }\n 18%, 36% {\n filter: drop-shadow(0 0 8px var(--arrow-emphasis));\n stroke-width: 2.4;\n }\n }\n @keyframes archify-blueprint-node-pulse {\n 0%, 72%, 100% { opacity: 1; filter: none; }\n 18%, 36% { opacity: 0.72; filter: drop-shadow(0 0 2px var(--frontend-stroke)); }\n }\n @keyframes archify-editorial-node-pulse {\n 0%, 72%, 100% { opacity: 1; filter: none; }\n 18%, 36% { opacity: 0.78; filter: drop-shadow(0 2px 2px color-mix(in srgb, var(--text) 18%, transparent)); }\n }\n @keyframes archify-signal-scan {\n 0%, 18% { opacity: 1; transform: translateX(-70%); }\n 70% { opacity: 1; transform: translateX(70%); }\n 100% { opacity: 0; transform: translateX(70%); }\n }\n @keyframes archify-radar-live {\n 0% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--backend-stroke) 52%, transparent); }\n 55%, 100% { box-shadow: 0 0 0 0.32rem transparent; }\n }\n\n @keyframes archify-intent-trace-flow {\n 0% { opacity: 0.94; stroke-dashoffset: 0; }\n 100% { opacity: 0.94; stroke-dashoffset: -1; }\n }\n @keyframes archify-route-probe-flow {\n 0% { stroke-dashoffset: 0; opacity: 0.96; }\n 78% { stroke-dashoffset: -1; opacity: 0.96; }\n 100% { stroke-dasharray: none; stroke-dashoffset: 0; opacity: 0.58; }\n }\n @keyframes archify-route-journey-flow {\n 0% { opacity: 0.16; stroke-dashoffset: 0; }\n 14%, 76% { opacity: 1; }\n 100% { opacity: 0; stroke-dashoffset: -1; }\n }\n @keyframes archify-semantic-lens-flow {\n 0% { opacity: 0.2; stroke-dashoffset: 0; }\n 12%, 78% { opacity: 0.9; }\n 100% { opacity: 0; stroke-dashoffset: -1; }\n }\n @keyframes archify-relationship-pulse {\n 0% { opacity: 0.18; stroke-dashoffset: 0; }\n 10%, 76% { opacity: 0.98; }\n 100% { opacity: 0; stroke-dashoffset: -1; }\n }\n @keyframes archify-relationship-token-life {\n 0%, 7% { opacity: 0; }\n 13%, 82% { opacity: 1; }\n 100% { opacity: 0; }\n }\n\n @keyframes archify-share-cue-enter {\n from { opacity: 0; transform: translate(-50%, -0.45rem); }\n to { opacity: 1; transform: translate(-50%, 0); }\n }\n @keyframes archify-share-cue-pulse {\n 0%, 100% { opacity: 1; transform: scale(1); }\n 50% { opacity: 0.48; transform: scale(0.78); }\n }\n @media (prefers-reduced-motion: reduce) {\n .ambient-edge-flow { display: none !important; animation: none !important; }\n html[data-preset=\"signal-flow\"] .diagram-container::before {\n animation: none !important;\n }\n .pulse-dot { animation: none !important; }\n .motion-control-dot { animation: none !important; }\n svg[data-animation=\"trace\"] [data-animate] {\n animation: none !important;\n stroke-dashoffset: 0;\n }\n svg[data-animation=\"trace\"] [data-graph-role=\"automatic-crossover\"]:has(\u003e [data-animate=\"edge\"]) \u003e [data-graph-role=\"automatic-crossover-underlay\"] {\n animation: none !important;\n stroke-dashoffset: 0;\n }\n .intent-trace-flow,\n .route-probe-flow,\n .semantic-lens,\n .diagram-guide,\n .overview-map-live { animation: none !important; }\n \n .intent-trace-flow {\n animation: none !important;\n stroke-dasharray: none;\n stroke-dashoffset: 0;\n opacity: 0.72;\n }\n .route-probe-flow {\n animation: none !important;\n stroke-dasharray: none;\n stroke-dashoffset: 0;\n opacity: 0.78;\n }\n .route-journey-overlay { display: none !important; }\n .semantic-lens-flow {\n animation: none !important;\n stroke-dasharray: none;\n stroke-width: 2.2;\n opacity: 0.34;\n filter: none;\n }\n .relationship-pulse-overlay { display: none !important; }\n svg [data-node-id], svg [data-edge-from], svg [data-detail], svg [data-detail-anchor], svg [data-legend-hit], svg { transition: none !important; }\n }\n\n /* Authored explanation face. It overlays the passport's own footprint so\n the placement beside the focused node is untouched; the passport may\n grow within the visible stage while its prose scrolls independently. */\n .focus-chip[data-explaining=\"true\"] \u003e :not(.semantic-passport-explanation) { visibility: hidden; }\n .semantic-passport-explanation {\n position: absolute;\n inset: 0;\n display: none;\n flex-direction: column;\n gap: 0.35rem;\n padding: 0.7rem 0.75rem;\n overflow: hidden;\n scrollbar-width: thin;\n color: var(--text);\n }\n .focus-chip[data-explaining=\"true\"] .semantic-passport-explanation {\n display: flex;\n animation: passport-explanation-in 240ms cubic-bezier(0.16, 1, 0.3, 1);\n }\n .semantic-passport-explanation \u003e :not(.semantic-passport-explanation-body) { flex: none; }\n .semantic-passport-explanation-eyebrow {\n display: block;\n color: var(--reader-accent, var(--text));\n font-size: 0.5rem;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n }\n .semantic-passport-explanation-title { display: block; font-size: 0.8rem; line-height: 1.35; }\n .semantic-passport-explanation-body { display: grid; align-content: start; flex: 1 1 auto; min-height: 0; gap: 0.35rem; overflow: auto; overscroll-behavior: contain; scrollbar-width: thin; }\n .semantic-passport-explanation-body:focus-visible { outline: 2px solid var(--reader-accent, var(--text)); outline-offset: 2px; }\n .semantic-passport-explanation-body p { margin: 0; color: var(--text-muted); font-size: 0.6875rem; line-height: 1.5; overflow-wrap: anywhere; }\n .semantic-passport-explanation-back {\n align-self: flex-start;\n margin-top: auto;\n padding: 0.15rem 0;\n border: 0;\n background: none;\n color: var(--reader-accent, var(--text));\n font: inherit;\n font-size: 0.625rem;\n text-decoration: underline;\n cursor: pointer;\n }\n .semantic-passport-explanation-back:hover { color: var(--text); }\n .semantic-passport-explanation-back:focus-visible { outline: 2px solid var(--frontend-stroke); outline-offset: 2px; }\n @keyframes passport-explanation-in {\n from { transform: rotateY(-90deg); opacity: 0; }\n to { transform: rotateY(0); opacity: 1; }\n }\n @media (prefers-reduced-motion: reduce) {\n .focus-chip[data-explaining=\"true\"] .semantic-passport-explanation { animation: none; }\n }\n\n /* A short mobile viewport cannot fit the Passport below the page header.\n Keep its controls in the viewport without shrinking the drawing or prose. */\n @media (max-width: 720px) and (max-height: 480px) {\n .focus-chip {\n position: fixed;\n left: 8px !important;\n top: var(--passport-viewport-top, 8px) !important;\n width: min(22rem, calc(100vw - 16px));\n max-width: calc(100vw - 16px);\n max-height: calc(100vh - var(--passport-viewport-top, 8px) - 8px);\n overflow-y: auto;\n }\n .focus-chip[data-explaining=\"true\"] { overflow: hidden; }\n }\n ";
+ const exportCss = "\n /* ==========================================================\n THEME VARIABLES — switch by toggling [data-theme] on \u003chtml\u003e\n ========================================================== */\n :root,\n [data-theme=\"dark\"] {\n --bg: #020617;\n --grid: #1e293b;\n --canvas-dot: rgba(148, 163, 184, 0.16);\n --text: #ffffff;\n --text-muted: #94a3b8;\n --text-dim: #475569;\n /* UI chrome and menu hints need more contrast than SVG .t-dim accents */\n --text-faint: #7d8da1;\n\n --panel: rgba(15, 23, 42, 0.5);\n --panel-border: #1e293b;\n --lane-fill: rgba(15, 23, 42, 0.22);\n --lane-stroke: #334155;\n\n --arrow: #94a3b8;\n --arrow-emphasis: #34d399;\n\n /* Opaque mask color used behind semi-transparent component fills\n so the arrows drawn underneath are properly hidden. */\n --mask: #0f172a;\n\n --frontend-fill: rgba(8, 51, 68, 0.4);\n --frontend-stroke: #22d3ee;\n --backend-fill: rgba(6, 78, 59, 0.4);\n --backend-stroke: #34d399;\n --database-fill: rgba(76, 29, 149, 0.4);\n --database-stroke: #a78bfa;\n --cloud-fill: rgba(120, 53, 15, 0.3);\n --cloud-stroke: #fbbf24;\n --security-fill: rgba(136, 19, 55, 0.4);\n --security-stroke: #fb7185;\n --messagebus-fill: rgba(251, 146, 60, 0.3);\n --messagebus-stroke:#fb923c;\n --external-fill: rgba(30, 41, 59, 0.5);\n --external-stroke: #94a3b8;\n\n --toolbar-bg: rgba(15, 23, 42, 0.8);\n --toolbar-border: #334155;\n --toolbar-text: #e2e8f0;\n --toolbar-hover: rgba(15, 23, 42, 0.95);\n --toolbar-menu-bg: #0f172a;\n }\n\n [data-theme=\"light\"] {\n --bg: #f4f5f7;\n --grid: #e9edf2;\n --canvas-dot: #e2e7ec;\n --text: #111827;\n --text-muted: #5b6474;\n --text-dim: #9aa3b2;\n --text-faint: #6b7280;\n\n --panel: #ffffff;\n --panel-border: #e5e7eb;\n --lane-fill: rgba(248, 250, 252, 0.65);\n --lane-stroke: #cbd5e1;\n\n --arrow: #64748b;\n --arrow-emphasis: #059669;\n\n --mask: #ffffff;\n\n --frontend-fill: rgba(34, 211, 238, 0.15);\n --frontend-stroke: #0891b2;\n /* #1040: darker accent for small bold reader text; the diagram stroke above is untouched. */\n --reader-accent: #077f9c;\n --backend-fill: rgba(52, 211, 153, 0.18);\n --backend-stroke: #059669;\n --database-fill: rgba(167, 139, 250, 0.2);\n --database-stroke: #7c3aed;\n --cloud-fill: rgba(251, 191, 36, 0.18);\n --cloud-stroke: #d97706;\n --security-fill: rgba(251, 113, 133, 0.15);\n --security-stroke: #e11d48;\n --messagebus-fill: rgba(251, 146, 60, 0.15);\n --messagebus-stroke: #ea580c;\n --external-fill: rgba(148, 163, 184, 0.18);\n --external-stroke: #64748b;\n\n --toolbar-bg: rgba(255, 255, 255, 0.92);\n --toolbar-border: #cbd5e1;\n --toolbar-text: #334155;\n --toolbar-hover: #ffffff;\n --toolbar-menu-bg: #ffffff;\n }\n\n /* ==========================================================\n SIGNAL FLOW — an opt-in, motion-forward visual preset.\n The variables also target a standalone exported SVG, whose\n root carries data-preset and data-theme directly.\n ========================================================== */\n [data-preset=\"signal-flow\"][data-theme=\"dark\"] {\n --bg: #030711;\n --grid: #15233a;\n --panel: rgba(6, 14, 28, 0.78);\n --panel-border: #1d3350;\n --lane-fill: rgba(9, 22, 40, 0.5);\n --lane-stroke: #2c4564;\n --text: #f5fbff;\n --text-muted: #9eb0c7;\n --text-dim: #52667f;\n --text-faint: #7890ad;\n --mask: #07101e;\n --frontend-fill: rgba(6, 182, 212, 0.14);\n --frontend-stroke: #67e8f9;\n --backend-fill: rgba(16, 185, 129, 0.14);\n --backend-stroke: #5eead4;\n --database-fill: rgba(139, 92, 246, 0.16);\n --database-stroke: #c4b5fd;\n --cloud-fill: rgba(245, 158, 11, 0.13);\n --cloud-stroke: #fcd34d;\n --security-fill: rgba(244, 63, 94, 0.13);\n --security-stroke: #fda4af;\n --messagebus-fill: rgba(249, 115, 22, 0.13);\n --messagebus-stroke: #fdba74;\n --external-fill: rgba(71, 85, 105, 0.24);\n --external-stroke: #a5b4c7;\n --arrow: #7890ad;\n --arrow-emphasis: #2dd4bf;\n --toolbar-bg: rgba(7, 16, 30, 0.86);\n --toolbar-border: #29415f;\n --toolbar-menu-bg: #07101e;\n }\n\n [data-preset=\"signal-flow\"][data-theme=\"light\"] {\n --bg: #f4f9fc;\n --grid: #d4e5ee;\n --panel: rgba(255, 255, 255, 0.82);\n --panel-border: #bfd5e2;\n --lane-fill: rgba(232, 243, 248, 0.62);\n --lane-stroke: #a9c5d5;\n --text: #102638;\n --text-muted: #587287;\n --text-dim: #8aa2b4;\n --text-faint: #5d778a; /* #1040: was #668397 at 3.99:1 on reader surfaces */\n --mask: #ffffff;\n --frontend-fill: rgba(6, 182, 212, 0.09);\n --frontend-stroke: #0789a1;\n /* #1040: darker accent for small bold reader text; the diagram stroke above is untouched. */\n --reader-accent: #067e95;\n --backend-fill: rgba(5, 150, 105, 0.09);\n --backend-stroke: #087f69;\n --database-fill: rgba(124, 58, 237, 0.09);\n --database-stroke: #7254c7;\n --cloud-fill: rgba(217, 119, 6, 0.08);\n --cloud-stroke: #b9670b;\n --security-fill: rgba(225, 29, 72, 0.08);\n --security-stroke: #c53a59;\n --messagebus-fill: rgba(234, 88, 12, 0.08);\n --messagebus-stroke: #c65f27;\n --external-fill: rgba(100, 116, 139, 0.1);\n --external-stroke: #607a8c;\n --arrow: #7b97aa;\n --arrow-emphasis: #0d9488;\n }\n\n /* ==========================================================\n BLUEPRINT — a review-first drafting preset for deployment,\n infrastructure, and technical design documentation. It keeps\n the same semantic palette and geometry while trading glow for\n precise grid contrast, squared materials, and drafting marks.\n ========================================================== */\n [data-preset=\"blueprint\"][data-theme=\"dark\"] {\n --bg: #06131f;\n --grid: #17425a;\n --panel: rgba(7, 27, 43, 0.9);\n --panel-border: #27627f;\n --lane-fill: rgba(10, 43, 66, 0.36);\n --lane-stroke: #34799a;\n --text: #e3f6ff;\n --text-muted: #91b8ca;\n --text-dim: #557e92;\n --text-faint: #739caf;\n --mask: #0a2031;\n --frontend-fill: rgba(26, 157, 193, 0.14);\n --frontend-stroke: #66d9ef;\n --backend-fill: rgba(31, 155, 124, 0.13);\n --backend-stroke: #69dfbd;\n --database-fill: rgba(120, 105, 196, 0.14);\n --database-stroke: #b4a8ff;\n --cloud-fill: rgba(197, 145, 43, 0.13);\n --cloud-stroke: #ffd166;\n --security-fill: rgba(199, 76, 104, 0.13);\n --security-stroke: #ff8da1;\n --messagebus-fill: rgba(207, 112, 49, 0.13);\n --messagebus-stroke: #ffad66;\n --external-fill: rgba(84, 119, 139, 0.18);\n --external-stroke: #a7cad9;\n --arrow: #78a3b7;\n --arrow-emphasis: #64dfc1;\n --toolbar-bg: rgba(7, 28, 45, 0.92);\n --toolbar-border: #34799a;\n --toolbar-text: #d8f3ff;\n --toolbar-hover: rgba(18, 58, 83, 0.95);\n --toolbar-menu-bg: #0a2031;\n }\n\n [data-preset=\"blueprint\"][data-theme=\"light\"] {\n --bg: #edf7fa;\n --grid: #b5d5e1;\n --panel: rgba(249, 253, 255, 0.94);\n --panel-border: #78aabd;\n --lane-fill: rgba(210, 232, 240, 0.42);\n --lane-stroke: #83b2c4;\n --text: #123344;\n --text-muted: #4e7486;\n --text-dim: #86a6b4;\n --text-faint: #577786; /* #1040: was #668c9d at 3.54:1 on reader surfaces */\n --mask: #f9fdff;\n --frontend-fill: rgba(8, 145, 178, 0.08);\n --frontend-stroke: #087f9c;\n --backend-fill: rgba(5, 128, 101, 0.08);\n --backend-stroke: #08755f;\n --database-fill: rgba(100, 75, 180, 0.08);\n --database-stroke: #6757a8;\n --cloud-fill: rgba(181, 120, 15, 0.08);\n --cloud-stroke: #a86609;\n --security-fill: rgba(190, 48, 80, 0.07);\n --security-stroke: #b32f50;\n --messagebus-fill: rgba(196, 81, 22, 0.07);\n --messagebus-stroke: #b65120;\n --external-fill: rgba(79, 112, 128, 0.08);\n --external-stroke: #506f7e;\n --arrow: #6d93a5;\n --arrow-emphasis: #087f69;\n --toolbar-bg: rgba(247, 252, 254, 0.94);\n --toolbar-border: #8ab5c5;\n --toolbar-text: #294f61;\n --toolbar-hover: #ffffff;\n --toolbar-menu-bg: #f9fdff;\n }\n\n /* ==========================================================\n EDITORIAL — a warm, publication-minded preset for design\n reviews, launch notes, and documentation. The diagram keeps\n the same semantic palette and exact geometry, but trades\n software chrome for paper, ink, ruled structure, and one\n restrained vermilion accent.\n ========================================================== */\n [data-preset=\"editorial\"][data-theme=\"dark\"] {\n --bg: #181611;\n --grid: #39342a;\n --panel: rgba(35, 31, 24, 0.96);\n --panel-border: #625a4a;\n --lane-fill: rgba(52, 46, 35, 0.46);\n --lane-stroke: #726957;\n --text: #f4eddf;\n --text-muted: #b9ae9b;\n --text-dim: #776e60;\n --text-faint: #9d917e;\n --mask: #231f18;\n --frontend-fill: rgba(43, 133, 142, 0.16);\n --frontend-stroke: #7fc6c7;\n --backend-fill: rgba(63, 132, 92, 0.16);\n --backend-stroke: #8fc29e;\n --database-fill: rgba(117, 91, 141, 0.17);\n --database-stroke: #c0a4d0;\n --cloud-fill: rgba(170, 119, 49, 0.16);\n --cloud-stroke: #d8ad68;\n --security-fill: rgba(157, 69, 65, 0.17);\n --security-stroke: #df9085;\n --messagebus-fill: rgba(174, 79, 42, 0.16);\n --messagebus-stroke: #df946f;\n --external-fill: rgba(126, 115, 96, 0.18);\n --external-stroke: #b8ad99;\n --arrow: #948978;\n --arrow-emphasis: #dd6b3d;\n --toolbar-bg: rgba(35, 31, 24, 0.92);\n --toolbar-border: #625a4a;\n --toolbar-text: #eee5d5;\n --toolbar-hover: #302a20;\n --toolbar-menu-bg: #231f18;\n }\n\n [data-preset=\"editorial\"][data-theme=\"light\"] {\n --bg: #f2eee5;\n --grid: #d8d0c2;\n --panel: rgba(251, 248, 241, 0.97);\n --panel-border: #c4b9a6;\n --lane-fill: rgba(229, 221, 207, 0.42);\n --lane-stroke: #b8aa94;\n --text: #242018;\n --text-muted: #6f6658;\n --text-dim: #a09788;\n --text-faint: #796f60; /* #1040: was #817767 at 4.15:1 on reader surfaces */\n --mask: #fbf8f1;\n --frontend-fill: rgba(31, 117, 126, 0.09);\n --frontend-stroke: #287e84;\n /* #1040: darker accent for small bold reader text; the diagram stroke above is untouched. */\n --reader-accent: #267a80;\n --backend-fill: rgba(42, 119, 75, 0.09);\n --backend-stroke: #397b53;\n --database-fill: rgba(105, 75, 130, 0.09);\n --database-stroke: #765d86;\n --cloud-fill: rgba(157, 103, 31, 0.1);\n --cloud-stroke: #9a671f;\n --security-fill: rgba(153, 58, 52, 0.09);\n --security-stroke: #9e463f;\n --messagebus-fill: rgba(182, 70, 27, 0.09);\n --messagebus-stroke: #ad4b25;\n --external-fill: rgba(101, 91, 75, 0.09);\n --external-stroke: #746b5e;\n --arrow: #8a806f;\n --arrow-emphasis: #bb4c23;\n --toolbar-bg: rgba(251, 248, 241, 0.94);\n --toolbar-border: #c4b9a6;\n --toolbar-text: #40392f;\n --toolbar-hover: #fffdf8;\n --toolbar-menu-bg: #fbf8f1;\n }\n\n * { margin: 0; padding: 0; box-sizing: border-box; }\n\n body {\n --archify-page-top: 2rem;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', 'Noto Sans CJK SC', 'Noto Sans', Roboto, 'Helvetica Neue', Arial, sans-serif;\n -webkit-font-smoothing: antialiased;\n background: var(--bg);\n min-height: 100vh;\n padding: var(--archify-page-top) 2rem 2rem;\n color: var(--text);\n /* Theme surfaces change together; animating the page background leaves it\n visibly out of sync with the SVG and panel for the first 200ms. */\n }\n\n html[data-preset=\"signal-flow\"] body {\n background-image:\n radial-gradient(circle at 18% -8%, rgba(34, 211, 238, 0.14), transparent 34rem),\n radial-gradient(circle at 92% 18%, rgba(139, 92, 246, 0.12), transparent 30rem);\n background-attachment: fixed;\n }\n html[data-preset=\"signal-flow\"][data-theme=\"light\"] body {\n background-image:\n radial-gradient(circle at 18% -8%, rgba(6, 182, 212, 0.12), transparent 34rem),\n radial-gradient(circle at 92% 18%, rgba(139, 92, 246, 0.08), transparent 30rem);\n }\n html[data-preset=\"blueprint\"] body {\n background-image:\n linear-gradient(var(--grid) 1px, transparent 1px),\n linear-gradient(90deg, var(--grid) 1px, transparent 1px);\n background-size: 32px 32px;\n background-position: -1px -1px;\n background-attachment: fixed;\n }\n html[data-preset=\"editorial\"] body {\n background-image:\n linear-gradient(90deg, transparent 0, transparent 5.4rem, color-mix(in srgb, var(--security-stroke) 18%, transparent) 5.4rem, color-mix(in srgb, var(--security-stroke) 18%, transparent) calc(5.4rem + 1px), transparent calc(5.4rem + 1px)),\n repeating-linear-gradient(0deg, transparent 0, transparent 31px, color-mix(in srgb, var(--grid) 36%, transparent) 31px, color-mix(in srgb, var(--grid) 36%, transparent) 32px);\n background-attachment: fixed;\n }\n\n /* Gallery/embed mode keeps the generated artifact as the source of truth\n while presenting only its diagram surface inside a proof card. */\n html[data-embed=\"true\"] body {\n min-height: 0;\n padding: 0;\n overflow: hidden;\n background: var(--bg);\n background-image: none;\n }\n html[data-embed=\"true\"] .toolbar,\n html[data-embed=\"true\"] .header,\n html[data-embed=\"true\"] .cards,\n html[data-embed=\"true\"] .diagram-nav,\n html[data-embed=\"true\"] .overview-map,\n html[data-embed=\"true\"] .focus-chip,\n html[data-embed=\"true\"] .route-probe,\n html[data-embed=\"true\"] .semantic-lens,\n html[data-embed=\"true\"] .diagram-guide,\n html[data-embed=\"true\"] .node-finder,\n html[data-embed=\"true\"] .node-outline { display: none !important; }\n html[data-embed=\"true\"] .container { max-width: none; }\n html[data-embed=\"true\"] .diagram-container {\n padding: 0.5rem;\n border: 0;\n border-radius: 0;\n box-shadow: none;\n }\n html[data-embed=\"true\"] .diagram-container svg { min-width: 0; }\n\n /* Presentation Stage is a viewer-only layer: it gives the live diagram\n the viewport while keeping guided views, theme, focus, and exports real.\n Embed mode wins when both query parameters are present. */\n html[data-present=\"true\"]:not([data-embed=\"true\"]) body {\n --archify-page-top: 1rem;\n height: 100dvh;\n min-height: 0;\n padding: 1rem;\n overflow: hidden;\n }\n html[data-present=\"true\"]:not([data-embed=\"true\"]) .container {\n display: flex;\n flex-direction: column;\n gap: 0.75rem;\n width: 100%;\n max-width: none;\n height: calc(100dvh - 2rem);\n }\n html[data-present=\"true\"]:not([data-embed=\"true\"]) .header {\n flex: none;\n min-width: 0;\n margin: 0;\n padding-right: 23rem;\n }\n html[data-present=\"true\"]:not([data-embed=\"true\"]) .header-row { margin-bottom: 0.25rem; }\n html[data-present=\"true\"]:not([data-embed=\"true\"]) .subtitle {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n html[data-present=\"true\"]:not([data-embed=\"true\"]) .diagram-container {\n display: flex;\n flex: 1 1 auto;\n align-items: center;\n justify-content: center;\n min-height: 0;\n padding: 0.75rem;\n padding-bottom: calc(0.75rem + var(--archify-nav-reserve));\n }\n html[data-present=\"true\"]:not([data-embed=\"true\"]) .diagram-container \u003e svg {\n flex: 1 1 auto;\n height: 100%;\n min-height: 0;\n width: 100%;\n min-width: 0;\n }\n html[data-present=\"true\"]:not([data-embed=\"true\"]) .cards { display: none; }\n html[data-present=\"true\"]:not([data-embed=\"true\"]) #btn-present {\n border-color: var(--frontend-stroke);\n color: var(--frontend-stroke);\n }\n\n /* The reader owns only the outer scale. Compact automatic sequences fit at\n their authored text size or larger. Long sequences and waterfall rows\n favor reading width and page scroll; other diagrams keep height fitting.\n Small automatic canvases retain a modest scale inside a usable shell.\n Every strategy preserves the authored SVG/viewBox and semantic geometry. */\n .container {\n width: 100%;\n max-width: var(--archify-reader-width, 1440px);\n margin: 0 auto;\n }\n html[data-reader-area=\"true\"] .diagram-container {\n min-height: var(--archify-diagram-min-height);\n display: flex;\n align-items: center;\n }\n html[data-reader-area=\"true\"] .diagram-container \u003e svg { flex-shrink: 0; }\n /* The original SVG legend can use the outer corner while the capped or fitted\n diagram stays centered. The container still clips all outer paint. */\n .diagram-container:has([data-reader-legend-corner]) \u003e svg { overflow: visible; }\n .diagram-container [data-reader-legend-corner] {\n transform-origin: 0 0;\n transform-box: view-box;\n transform: var(--archify-reader-legend-transform);\n }\n html[data-reader-narrow=\"true\"]:not([data-present=\"true\"]) .diagram-container \u003e svg {\n min-width: 0;\n max-width: var(--archify-diagram-max-width);\n margin-inline: auto;\n }\n @media (min-width: 768px) and (max-height: 920px) {\n .header { margin-bottom: 1rem; }\n .diagram-container { padding: 1rem; }\n .cards { margin-top: 1rem; }\n .card { padding: 1rem; }\n }\n\n .header { margin-bottom: 1.5rem; padding-right: 29.5rem; }\n\n html[data-preset=\"signal-flow\"] .header,\n html[data-preset=\"blueprint\"] .header,\n html[data-preset=\"editorial\"] .header { padding-right: 29.5rem; }\n\n .header-row {\n display: flex;\n align-items: center;\n gap: 0.75rem;\n min-height: 2.75rem;\n margin-bottom: 0.25rem;\n }\n\n .pulse-dot {\n flex: none;\n width: 10px;\n height: 10px;\n background: var(--frontend-stroke);\n border-radius: 50%;\n box-shadow: 0 0 0 4px color-mix(in srgb, var(--frontend-stroke) 16%, transparent);\n animation: none;\n }\n\n html[data-motion-capable=\"true\"] .pulse-dot { animation: pulse 2s infinite; }\n\n @keyframes pulse {\n 0%, 100% { opacity: 1; }\n 50% { opacity: 0.5; }\n }\n\n h1 {\n font-size: 1.5rem;\n font-weight: 650;\n line-height: 1.25;\n letter-spacing: -0.015em;\n }\n\n .subtitle {\n color: var(--text-muted);\n font-size: 0.875rem;\n line-height: 1.55;\n margin-left: 1.375rem;\n }\n\n html[data-preset=\"signal-flow\"] .header-row::after {\n content: attr(data-preset-badge-signal-flow);\n flex: none;\n margin-left: 0.25rem;\n color: var(--frontend-stroke);\n border: 1px solid var(--frontend-stroke);\n border-radius: 999px;\n padding: 0.28rem 0.6rem;\n font-size: 0.625rem;\n letter-spacing: 0.12em;\n opacity: 0.82;\n box-shadow: 0 0 20px rgba(34, 211, 238, 0.12);\n white-space: nowrap;\n }\n\n html[data-preset=\"blueprint\"] .pulse-dot {\n width: 10px;\n height: 10px;\n border: 1px solid var(--frontend-stroke);\n border-radius: 1px;\n background: transparent;\n box-shadow: inset 0 0 0 2px var(--bg);\n transform: rotate(45deg);\n animation: none;\n }\n html[data-preset=\"blueprint\"] .header-row::after {\n content: attr(data-preset-badge-blueprint);\n flex: none;\n margin-left: 0.25rem;\n padding: 0.28rem 0.55rem;\n border-top: 1px solid var(--frontend-stroke);\n border-bottom: 1px solid var(--frontend-stroke);\n color: var(--frontend-stroke);\n font-size: 0.5625rem;\n font-weight: 700;\n letter-spacing: 0.14em;\n white-space: nowrap;\n }\n\n html[data-preset=\"editorial\"] h1,\n html[data-preset=\"editorial\"] .card h3 {\n font-family: Georgia, 'Times New Roman', 'Songti SC', STSong, serif;\n letter-spacing: -0.02em;\n }\n html[data-preset=\"editorial\"] h1 { font-size: 1.72rem; font-weight: 600; }\n html[data-preset=\"editorial\"] .pulse-dot {\n width: 9px;\n height: 9px;\n border: 1px solid var(--arrow-emphasis);\n border-radius: 0;\n background: var(--arrow-emphasis);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--arrow-emphasis) 14%, transparent);\n transform: rotate(45deg);\n animation: none;\n }\n html[data-preset=\"editorial\"] .header-row::after {\n content: attr(data-preset-badge-editorial);\n flex: none;\n margin-left: 0.25rem;\n padding: 0.32rem 0 0.24rem;\n border-bottom: 2px solid var(--arrow-emphasis);\n color: var(--arrow-emphasis);\n font-family: Georgia, 'Times New Roman', serif;\n font-size: 0.625rem;\n font-style: italic;\n font-weight: 600;\n letter-spacing: 0.08em;\n white-space: nowrap;\n }\n\n .diagram-container {\n --archify-nav-reserve: 0px;\n background: var(--panel);\n border-radius: 1rem;\n border: 1px solid var(--panel-border);\n box-shadow: 0 1px 2px rgba(15, 23, 42, 0.04), 0 16px 40px -20px rgba(15, 23, 42, 0.18);\n padding: 1.5rem;\n padding-bottom: calc(1.5rem + var(--archify-nav-reserve));\n overflow: hidden;\n position: relative;\n }\n\n html[data-preset=\"signal-flow\"] .diagram-container {\n position: relative;\n overflow: hidden;\n background: linear-gradient(180deg, rgba(8, 20, 38, 0.82), rgba(3, 8, 18, 0.94));\n box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.04), 0 28px 80px rgba(0, 0, 0, 0.34);\n }\n html[data-preset=\"signal-flow\"][data-theme=\"light\"] .diagram-container {\n background: linear-gradient(180deg, rgba(255, 255, 255, 0.9), rgba(237, 246, 250, 0.94));\n box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.72), 0 24px 60px rgba(45, 78, 99, 0.12);\n }\n html[data-preset=\"blueprint\"] .diagram-container {\n border-color: var(--panel-border);\n border-radius: 0.35rem;\n background: var(--panel);\n box-shadow:\n inset 0 0 0 1px color-mix(in srgb, var(--panel-border) 34%, transparent),\n 0 18px 48px rgba(0, 0, 0, 0.16);\n }\n html[data-preset=\"blueprint\"] .diagram-container::after {\n content: \"\";\n position: absolute;\n z-index: 10;\n inset: 0.7rem;\n pointer-events: none;\n background:\n linear-gradient(var(--frontend-stroke), var(--frontend-stroke)) left top / 1.35rem 1px no-repeat,\n linear-gradient(var(--frontend-stroke), var(--frontend-stroke)) left top / 1px 1.35rem no-repeat,\n linear-gradient(var(--frontend-stroke), var(--frontend-stroke)) right top / 1.35rem 1px no-repeat,\n linear-gradient(var(--frontend-stroke), var(--frontend-stroke)) right top / 1px 1.35rem no-repeat,\n linear-gradient(var(--frontend-stroke), var(--frontend-stroke)) left bottom / 1.35rem 1px no-repeat,\n linear-gradient(var(--frontend-stroke), var(--frontend-stroke)) left bottom / 1px 1.35rem no-repeat,\n linear-gradient(var(--frontend-stroke), var(--frontend-stroke)) right bottom / 1.35rem 1px no-repeat,\n linear-gradient(var(--frontend-stroke), var(--frontend-stroke)) right bottom / 1px 1.35rem no-repeat;\n opacity: 0.58;\n }\n html[data-preset=\"editorial\"] .diagram-container {\n overflow: hidden;\n border-color: var(--panel-border);\n border-radius: 0.38rem;\n background:\n linear-gradient(90deg, color-mix(in srgb, var(--security-fill) 58%, transparent), transparent 12%),\n var(--panel);\n box-shadow:\n 0 1px 0 color-mix(in srgb, var(--text) 6%, transparent),\n 0 18px 48px color-mix(in srgb, #000 19%, transparent);\n }\n html[data-preset=\"editorial\"] .diagram-container::after {\n position: absolute;\n z-index: 10;\n top: 0.72rem;\n right: 1.25rem;\n padding: 0.2rem 0.52rem;\n border: 1px solid var(--panel-border);\n background: var(--panel);\n color: var(--arrow-emphasis);\n content: attr(data-preset-badge-editorial-plate);\n font-family: Georgia, 'Times New Roman', serif;\n font-size: 0.52rem;\n font-style: italic;\n font-weight: 600;\n letter-spacing: 0.09em;\n pointer-events: none;\n }\n html[data-embed=\"true\"][data-preset=\"editorial\"] .diagram-container::after { display: none; }\n html[data-motion-capable=\"true\"][data-preset=\"signal-flow\"][data-ambient-entry=\"running\"] .diagram-container::before {\n content: \"\";\n position: absolute;\n inset: 0;\n pointer-events: none;\n background: linear-gradient(115deg, transparent 28%, rgba(103, 232, 249, 0.035) 48%, transparent 68%);\n transform: translateX(-70%);\n animation: archify-signal-scan 4.8s ease-in-out 1 both;\n }\n\n /* Motion Governor gives the reader a real static state and gives the\n strongest semantic action the only motion budget. Atmosphere resets to\n its authored base instead of freezing mid-cycle. Static artifacts never\n start a decorative pulse or scan. */\n html:not([data-motion-capable=\"true\"]) svg[data-animation=\"trace\"] [data-animate] {\n animation: none !important;\n }\n html:not([data-motion-capable=\"true\"]) svg[data-animation=\"trace\"] [data-graph-role=\"automatic-crossover\"]:has(\u003e [data-animate=\"edge\"]) \u003e [data-graph-role=\"automatic-crossover-underlay\"] {\n animation: none !important;\n }\n html[data-motion=\"still\"] .pulse-dot,\n html[data-motion=\"still\"] .diagram-container::before,\n html[data-motion=\"still\"] svg[data-animation=\"trace\"] [data-animate],\n html[data-motion-owner] .pulse-dot,\n html[data-motion-owner] .diagram-container::before,\n html[data-motion-owner] svg[data-animation=\"trace\"] [data-animate],\n html[data-embed=\"true\"] .pulse-dot,\n html[data-embed=\"true\"] .diagram-container::before,\n html[data-share-playback=\"true\"] .pulse-dot,\n html[data-share-playback=\"true\"] .diagram-container::before,\n html[data-document-hidden=\"true\"] .pulse-dot,\n html[data-document-hidden=\"true\"] .diagram-container::before,\n html[data-document-hidden=\"true\"] svg[data-animation=\"trace\"] [data-animate],\n html[data-motion=\"still\"] svg[data-animation=\"trace\"] [data-graph-role=\"automatic-crossover\"]:has(\u003e [data-animate=\"edge\"]) \u003e [data-graph-role=\"automatic-crossover-underlay\"],\n html[data-motion-owner] svg[data-animation=\"trace\"] [data-graph-role=\"automatic-crossover\"]:has(\u003e [data-animate=\"edge\"]) \u003e [data-graph-role=\"automatic-crossover-underlay\"],\n html[data-embed=\"true\"] svg[data-animation=\"trace\"] [data-graph-role=\"automatic-crossover\"]:has(\u003e [data-animate=\"edge\"]) \u003e [data-graph-role=\"automatic-crossover-underlay\"],\n html[data-share-playback=\"true\"] svg[data-animation=\"trace\"] [data-graph-role=\"automatic-crossover\"]:has(\u003e [data-animate=\"edge\"]) \u003e [data-graph-role=\"automatic-crossover-underlay\"],\n html[data-document-hidden=\"true\"] svg[data-animation=\"trace\"] [data-graph-role=\"automatic-crossover\"]:has(\u003e [data-animate=\"edge\"]) \u003e [data-graph-role=\"automatic-crossover-underlay\"] {\n animation: none !important;\n }\n html[data-motion=\"still\"] .diagram-container::before,\n html[data-motion-owner] .diagram-container::before,\n html[data-embed=\"true\"] .diagram-container::before,\n html[data-share-playback=\"true\"] .diagram-container::before {\n opacity: 0;\n }\n html[data-motion=\"still\"] .intent-trace-flow,\n html[data-motion=\"still\"] .route-probe-flow,\n html[data-motion=\"still\"] .route-journey-flow,\n html[data-motion=\"still\"] .semantic-lens-flow,\n html[data-motion=\"still\"] .diagram-guide,\n html[data-motion=\"still\"] .overview-map-live,\n html[data-document-hidden=\"true\"] .intent-trace-flow,\n html[data-document-hidden=\"true\"] .route-probe-flow,\n html[data-document-hidden=\"true\"] .route-journey-flow,\n html[data-document-hidden=\"true\"] .semantic-lens-flow,\n html[data-document-hidden=\"true\"] .diagram-guide,\n html[data-document-hidden=\"true\"] .overview-map-live {\n animation: none !important;\n }\n\n html[data-motion=\"still\"] .intent-trace-flow,\n html[data-motion=\"still\"] .route-probe-flow {\n stroke-dasharray: none;\n stroke-dashoffset: 0;\n opacity: 0.72;\n }\n html[data-motion=\"still\"] .route-journey-overlay,\n html[data-document-hidden=\"true\"] .route-journey-overlay { display: none !important; }\n html[data-motion=\"still\"] .relationship-pulse-overlay { display: none !important; }\n\n html[data-motion=\"still\"] svg [data-node-id],\n html[data-motion=\"still\"] svg [data-edge-from],\n html[data-motion=\"still\"] svg [data-detail],\n html[data-motion=\"still\"] svg [data-detail-anchor],\n html[data-motion=\"still\"] svg [data-legend-hit],\n html[data-motion=\"still\"] svg {\n transition: none !important;\n }\n\n svg {\n font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, 'DejaVu Sans Mono', 'Liberation Mono', 'Noto Sans Mono CJK SC', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', monospace;\n width: 100%;\n min-width: min(900px, 100%);\n display: block;\n transform-origin: 0 0;\n transition: transform 0.18s ease;\n }\n\n .diagram-container.is-camera-moving svg {\n transition: transform 0.42s cubic-bezier(0.22, 1, 0.36, 1);\n }\n .diagram-container.is-camera-transaction svg { transition: none; }\n .diagram-container.is-pannable { cursor: grab; }\n .diagram-container.is-panning { cursor: grabbing; user-select: none; }\n .diagram-container.is-panning svg { transition: none; }\n\n .diagram-nav {\n position: absolute;\n right: 1rem;\n bottom: 1rem;\n z-index: 12;\n translate: 0 calc(-1 * var(--archify-dock-lift, 0px));\n display: inline-flex;\n align-items: center;\n max-width: calc(100% - 2rem);\n padding: 0.25rem;\n gap: 0.0625rem;\n overflow-x: auto;\n border: 1px solid color-mix(in srgb, var(--panel-border) 80%, transparent);\n border-radius: 0.75rem;\n background: color-mix(in srgb, var(--panel) 84%, transparent);\n box-shadow: 0 1px 2px rgba(15, 23, 42, 0.05), 0 10px 28px -14px rgba(15, 23, 42, 0.28);\n backdrop-filter: blur(14px) saturate(1.2);\n scrollbar-width: none;\n }\n .diagram-nav::-webkit-scrollbar { display: none; }\n .diagram-nav button,\n .focus-chip button {\n border: 0;\n background: transparent;\n color: var(--toolbar-text);\n font: inherit;\n cursor: pointer;\n }\n .diagram-nav button {\n min-width: 2rem;\n height: 2rem;\n padding: 0 0.4rem;\n border-radius: 0.5rem;\n color: var(--text-muted);\n font-size: 0.75rem;\n font-weight: 500;\n letter-spacing: 0;\n transition: background 0.15s, color 0.15s;\n }\n .diagram-nav button:hover,\n .diagram-nav button:focus-visible {\n color: var(--text);\n background: color-mix(in srgb, var(--text) 6%, transparent);\n }\n .diagram-nav button:hover,\n .diagram-nav button:focus-visible,\n .focus-chip button:hover,\n .focus-chip button:focus-visible { background: var(--toolbar-hover); }\n .diagram-nav button:focus-visible,\n .focus-chip button:focus-visible {\n outline: 2px solid var(--frontend-stroke);\n outline-offset: 1px;\n }\n .diagram-nav button:disabled { opacity: 0.38; cursor: default; }\n /* The Guide stays on the ? shortcut; Map only earns dock space once the\n reader has zoomed past the full diagram (or opened it from the keyboard). */\n .diagram-nav button[hidden] { display: none; }\n .diagram-container:not(.is-pannable) #btn-overview-map:not([data-radar-requested]):not(:focus) { display: none; }\n .diagram-nav button:disabled:hover { background: transparent; }\n .diagram-nav #btn-route-probe,\n .diagram-nav #btn-overview-map,\n .diagram-nav #btn-semantic-lens {\n min-width: auto;\n padding-inline: 0.6rem;\n color: var(--text-muted);\n font-weight: 500;\n }\n .diagram-nav #btn-node-finder,\n .diagram-nav [data-view=\"out\"] {\n position: relative;\n margin-left: 0.375rem;\n }\n .diagram-nav #btn-node-finder::before,\n .diagram-nav [data-view=\"out\"]::before {\n position: absolute;\n top: 0.55rem;\n bottom: 0.55rem;\n left: -0.19rem;\n width: 1px;\n background: color-mix(in srgb, var(--panel-border) 90%, transparent);\n content: \"\";\n pointer-events: none;\n }\n .diagram-nav-icon {\n display: block;\n width: 0.95rem;\n height: 0.95rem;\n margin: auto;\n background: currentColor;\n -webkit-mask-position: center;\n -webkit-mask-repeat: no-repeat;\n -webkit-mask-size: contain;\n mask-position: center;\n mask-repeat: no-repeat;\n mask-size: contain;\n }\n .diagram-nav-icon.find {\n -webkit-mask-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Ccircle cx='8.5' cy='8.5' r='4.5' fill='none' stroke='black' stroke-width='1.8'/%3E%3Cpath d='m12 12 4 4' fill='none' stroke='black' stroke-width='1.8' stroke-linecap='round'/%3E%3C/svg%3E\");\n mask-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Ccircle cx='8.5' cy='8.5' r='4.5' fill='none' stroke='black' stroke-width='1.8'/%3E%3Cpath d='m12 12 4 4' fill='none' stroke='black' stroke-width='1.8' stroke-linecap='round'/%3E%3C/svg%3E\");\n }\n .diagram-nav-icon.guide {\n -webkit-mask-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Ccircle cx='10' cy='10' r='7' fill='none' stroke='black' stroke-width='1.7'/%3E%3Cpath d='M7.8 7.3a2.4 2.4 0 014.6.9c0 1.8-2.4 2-2.4 3.7M10 14.9h.01' fill='none' stroke='black' stroke-width='1.7' stroke-linecap='round'/%3E%3C/svg%3E\");\n mask-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Ccircle cx='10' cy='10' r='7' fill='none' stroke='black' stroke-width='1.7'/%3E%3Cpath d='M7.8 7.3a2.4 2.4 0 014.6.9c0 1.8-2.4 2-2.4 3.7M10 14.9h.01' fill='none' stroke='black' stroke-width='1.7' stroke-linecap='round'/%3E%3C/svg%3E\");\n }\n .diagram-nav-icon.minus {\n -webkit-mask-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath d='M5 10h10' fill='none' stroke='black' stroke-width='1.8' stroke-linecap='round'/%3E%3C/svg%3E\");\n mask-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath d='M5 10h10' fill='none' stroke='black' stroke-width='1.8' stroke-linecap='round'/%3E%3C/svg%3E\");\n }\n .diagram-nav-icon.plus {\n -webkit-mask-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath d='M10 5v10M5 10h10' fill='none' stroke='black' stroke-width='1.8' stroke-linecap='round'/%3E%3C/svg%3E\");\n mask-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath d='M10 5v10M5 10h10' fill='none' stroke='black' stroke-width='1.8' stroke-linecap='round'/%3E%3C/svg%3E\");\n }\n .diagram-nav [data-view=\"reset\"] {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 0.2rem;\n min-width: 3.1rem;\n padding-inline: 0.35rem;\n }\n .diagram-nav [data-view=\"reset\"][data-detail-visible] {\n min-width: 3.8rem;\n }\n .diagram-nav-detail {\n color: color-mix(in srgb, var(--toolbar-text) 52%, transparent);\n font-size: 0.44rem;\n font-weight: 700;\n line-height: 1;\n letter-spacing: 0.07em;\n }\n .diagram-nav-percent {\n color: var(--text-muted);\n font-size: 0.75rem;\n font-weight: 500;\n font-variant-numeric: tabular-nums;\n line-height: 1;\n }\n /* Mode buttons carry a small glyph so the dock reads as one icon language. */\n .diagram-nav #btn-route-probe,\n .diagram-nav #btn-overview-map,\n .diagram-nav #btn-semantic-lens {\n display: inline-flex;\n align-items: center;\n gap: 0.375rem;\n padding-inline: 0.55rem 0.65rem;\n }\n .diagram-nav #btn-route-probe::before,\n .diagram-nav #btn-overview-map::before,\n .diagram-nav #btn-semantic-lens::before {\n flex: none;\n width: 0.95rem;\n height: 0.95rem;\n background: currentColor;\n content: \"\";\n opacity: 0.85;\n -webkit-mask: var(--nav-glyph) center / contain no-repeat;\n mask: var(--nav-glyph) center / contain no-repeat;\n }\n .diagram-nav #btn-route-probe { --nav-glyph: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cg fill='none' stroke='black' stroke-width='1.7' stroke-linecap='round'%3E%3Ccircle cx='4.8' cy='14.8' r='2.2'/%3E%3Ccircle cx='15.2' cy='5.2' r='2.2'/%3E%3Cpath d='M6.9 13.9c3.8-1.4 2.6-6.3 6.2-7.8'/%3E%3C/g%3E%3C/svg%3E\"); }\n .diagram-nav #btn-overview-map { --nav-glyph: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cg fill='none' stroke='black' stroke-width='1.7' stroke-linejoin='round'%3E%3Cpath d='M2.8 5.4 7.4 3.5l5.2 2 4.6-1.9v11l-4.6 1.9-5.2-2-4.6 1.9z'/%3E%3Cpath d='M7.4 3.5v11M12.6 5.5v11'/%3E%3C/g%3E%3C/svg%3E\"); }\n .diagram-nav #btn-semantic-lens { --nav-glyph: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cg fill='none' stroke='black' stroke-width='1.7'%3E%3Ccircle cx='10' cy='10' r='6.8'/%3E%3Ccircle cx='10' cy='10' r='2.6'/%3E%3C/g%3E%3C/svg%3E\"); }\n /* Zoom out / level / zoom in join into one stepper well. */\n .diagram-nav [data-view=\"out\"],\n .diagram-nav [data-view=\"reset\"],\n .diagram-nav [data-view=\"in\"] {\n position: relative;\n border-radius: 0;\n background: color-mix(in srgb, var(--panel-border) 38%, transparent);\n }\n .diagram-nav [data-view=\"out\"] { margin-left: 0.375rem; border-radius: 0.5rem 0 0 0.5rem; }\n .diagram-nav [data-view=\"out\"]::before { display: none; }\n .diagram-nav [data-view=\"reset\"],\n .diagram-nav [data-view=\"in\"] { margin-left: -0.0625rem; }\n .diagram-nav [data-view=\"in\"] { border-radius: 0 0.5rem 0.5rem 0; }\n .diagram-nav [data-view=\"reset\"]::after,\n .diagram-nav [data-view=\"in\"]::after {\n position: absolute;\n top: 0.55rem;\n bottom: 0.55rem;\n left: 0;\n width: 1px;\n background: color-mix(in srgb, var(--panel-border) 75%, transparent);\n content: \"\";\n pointer-events: none;\n }\n .diagram-nav [data-view=\"out\"]:hover,\n .diagram-nav [data-view=\"reset\"]:hover,\n .diagram-nav [data-view=\"in\"]:hover { background: color-mix(in srgb, var(--panel-border) 60%, transparent); }\n .diagram-nav [data-view=\"out\"]:disabled,\n .diagram-nav [data-view=\"in\"]:disabled { opacity: 1; color: color-mix(in srgb, var(--text-muted) 40%, transparent); }\n .diagram-nav [data-view=\"out\"]:disabled:hover,\n .diagram-nav [data-view=\"in\"]:disabled:hover { background: color-mix(in srgb, var(--panel-border) 38%, transparent); }\n .diagram-nav [data-view=\"reset\"] { min-width: 3.25rem; }\n .diagram-container[data-camera-indicator=\"true\"] .diagram-nav-detail,\n .diagram-container[data-camera-indicator=\"true\"] .diagram-nav-percent { color: var(--frontend-stroke); }\n .diagram-nav #btn-diagram-guide[aria-expanded=\"true\"] {\n color: var(--cloud-stroke);\n background: var(--toolbar-hover);\n }\n\n /* Semantic Radar is a deliberately simplified, viewer-only overview. It\n uses authored node bounds and the live camera viewport rather than\n cloning the canonical SVG, so IDs, filters, animation, and export state\n stay singular and deterministic. */\n .overview-map {\n position: absolute;\n right: 1rem;\n bottom: calc(4.15rem + var(--archify-nav-reserve));\n z-index: 12;\n width: 11.25rem;\n overflow: hidden;\n border: 1px solid color-mix(in srgb, var(--frontend-stroke) 52%, var(--toolbar-border));\n border-radius: 0.78rem;\n background: var(--toolbar-menu-bg);\n color: var(--toolbar-text);\n box-shadow: 0 16px 42px rgba(0, 0, 0, 0.3);\n backdrop-filter: blur(16px);\n font-size: 0.625rem;\n }\n .overview-map[hidden] { display: none; }\n .overview-map[data-docked=\"true\"] {\n position: fixed;\n right: var(--archify-radar-right, 1rem);\n top: var(--archify-radar-top, 1rem);\n bottom: auto;\n }\n .overview-map[data-docked=\"true\"][data-dock-side=\"left\"] {\n right: auto;\n left: var(--archify-radar-left, 1rem);\n }\n html[data-preset=\"blueprint\"] .overview-map { border-radius: 0.28rem; }\n .overview-map-head {\n display: grid;\n grid-template-columns: auto minmax(0, 1fr) auto;\n align-items: center;\n gap: 0.45rem;\n min-height: 2.25rem;\n padding: 0.42rem 0.48rem 0.38rem 0.55rem;\n border-bottom: 1px solid var(--toolbar-border);\n background: linear-gradient(110deg, color-mix(in srgb, var(--frontend-fill) 45%, transparent), transparent 68%);\n cursor: grab;\n touch-action: none;\n user-select: none;\n }\n .overview-map[data-panel-dragging=\"true\"] .overview-map-head { cursor: grabbing; }\n .overview-map[data-placement-invalid=\"true\"] {\n border-color: var(--security-stroke);\n box-shadow: 0 0 0 2px color-mix(in srgb, var(--security-stroke) 34%, transparent), 0 16px 42px rgba(0, 0, 0, 0.3);\n }\n .overview-map[data-compact=\"true\"] {\n width: 7.4rem;\n }\n .overview-map[data-compact=\"true\"] .overview-map-head {\n grid-template-columns: minmax(0, 1fr) auto;\n gap: 0.24rem;\n min-height: 1.5rem;\n padding: 0.05rem 0.16rem 0.05rem 0.32rem;\n border-bottom: 0;\n }\n .overview-map[data-compact=\"true\"] .overview-map-live,\n .overview-map[data-compact=\"true\"] .overview-map-copy,\n .overview-map[data-compact=\"true\"] .overview-map-surface,\n .overview-map[data-compact=\"true\"] .overview-map-hint {\n display: none;\n }\n .overview-map[data-compact=\"true\"] .overview-map-expand { display: inline-flex; }\n .overview-map[data-compact=\"true\"] .overview-map-close {\n width: 1.15rem;\n height: 1.15rem;\n font-size: 0.72rem;\n }\n .overview-map-live {\n width: 0.42rem;\n height: 0.42rem;\n border-radius: 50%;\n background: var(--backend-stroke);\n box-shadow: 0 0 0 0 color-mix(in srgb, var(--backend-stroke) 52%, transparent);\n animation: archify-radar-live 2.4s ease-out infinite;\n }\n .overview-map-copy { min-width: 0; line-height: 1.1; }\n .overview-map-copy strong {\n display: block;\n overflow: hidden;\n color: var(--frontend-stroke);\n font-size: 0.59rem;\n letter-spacing: 0.12em;\n text-transform: uppercase;\n white-space: nowrap;\n text-overflow: ellipsis;\n }\n .overview-map-copy small {\n display: block;\n margin-top: 0.18rem;\n overflow: hidden;\n color: var(--text-faint);\n font-size: 0.52rem;\n letter-spacing: 0.04em;\n white-space: nowrap;\n text-overflow: ellipsis;\n }\n .overview-map-expand {\n display: none;\n align-items: center;\n justify-content: center;\n min-width: 0;\n height: 1.2rem;\n padding: 0 0.34rem;\n overflow: hidden;\n border: 0;\n border-radius: 0.3rem;\n background: transparent;\n color: var(--frontend-stroke);\n font: inherit;\n font-size: 0.49rem;\n font-weight: 700;\n letter-spacing: 0.06em;\n text-transform: uppercase;\n white-space: nowrap;\n cursor: pointer;\n }\n .overview-map-close {\n width: 1.55rem;\n height: 1.55rem;\n padding: 0;\n border: 0;\n border-radius: 0.34rem;\n background: transparent;\n color: var(--toolbar-text);\n font: inherit;\n font-size: 0.9rem;\n cursor: pointer;\n }\n .overview-map-expand:hover,\n .overview-map-expand:focus-visible,\n .overview-map-close:hover,\n .overview-map-close:focus-visible { background: var(--toolbar-hover); }\n .overview-map-expand:focus-visible,\n .overview-map-close:focus-visible,\n .overview-map-surface:focus-visible,\n .overview-map-node:focus-visible {\n outline: 2px solid var(--frontend-stroke);\n outline-offset: 1px;\n }\n .overview-map-surface {\n position: relative;\n height: 7rem;\n margin: 0.42rem;\n overflow: hidden;\n border: 1px solid color-mix(in srgb, var(--panel-border) 72%, transparent);\n border-radius: 0.42rem;\n background:\n linear-gradient(color-mix(in srgb, var(--panel-border) 26%, transparent) 1px, transparent 1px),\n linear-gradient(90deg, color-mix(in srgb, var(--panel-border) 26%, transparent) 1px, transparent 1px),\n color-mix(in srgb, var(--bg) 86%, transparent);\n background-size: 12px 12px;\n cursor: crosshair;\n touch-action: none;\n }\n html[data-preset=\"blueprint\"] .overview-map-surface { border-radius: 0.1rem; }\n .overview-map-surface \u003e svg {\n width: 100%;\n height: 100%;\n min-width: 0;\n transform: none !important;\n transition: none !important;\n display: block;\n overflow: visible;\n }\n .overview-map-node {\n fill: var(--backend-stroke);\n fill-opacity: 0.52;\n stroke: var(--backend-stroke);\n stroke-opacity: 0.86;\n vector-effect: non-scaling-stroke;\n cursor: pointer;\n transition: fill-opacity 140ms ease, stroke-width 140ms ease, filter 140ms ease;\n }\n .overview-map-node[data-kind=\"frontend\"],\n .overview-map-node[data-kind=\"start\"] { fill: var(--frontend-stroke); stroke: var(--frontend-stroke); }\n .overview-map-node[data-kind=\"database\"],\n .overview-map-node[data-kind=\"success\"] { fill: var(--database-stroke); stroke: var(--database-stroke); }\n .overview-map-node[data-kind=\"cloud\"],\n .overview-map-node[data-kind=\"waiting\"] { fill: var(--cloud-stroke); stroke: var(--cloud-stroke); }\n .overview-map-node[data-kind=\"messagebus\"] { fill: var(--messagebus-stroke); stroke: var(--messagebus-stroke); }\n .overview-map-node[data-kind=\"security\"],\n .overview-map-node[data-kind=\"decision\"],\n .overview-map-node[data-kind=\"failure\"] { fill: var(--security-stroke); stroke: var(--security-stroke); }\n .overview-map-node[data-kind=\"external\"],\n .overview-map-node[data-kind=\"neutral\"] { fill: var(--external-stroke); stroke: var(--external-stroke); }\n .overview-map-node[data-radar-active=\"true\"],\n .overview-map-node:hover,\n .overview-map-node:focus-visible {\n fill-opacity: 0.92;\n stroke-width: 2.2;\n filter: drop-shadow(0 0 3px currentColor);\n }\n .overview-map-viewport {\n fill: color-mix(in srgb, var(--frontend-fill) 56%, transparent);\n stroke: var(--frontend-stroke);\n stroke-width: 2;\n vector-effect: non-scaling-stroke;\n pointer-events: none;\n filter: drop-shadow(0 0 3px color-mix(in srgb, var(--frontend-stroke) 54%, transparent));\n }\n .overview-map-hint {\n display: flex;\n justify-content: space-between;\n gap: 0.5rem;\n padding: 0 0.52rem 0.46rem;\n color: var(--text-faint);\n font-size: 0.49rem;\n letter-spacing: 0.06em;\n text-transform: uppercase;\n }\n .diagram-nav #btn-overview-map[aria-expanded=\"true\"] {\n color: var(--frontend-stroke);\n background: color-mix(in srgb, var(--frontend-stroke) 11%, transparent);\n }\n .overview-map-feedback {\n position: absolute;\n right: 1rem;\n bottom: 4.15rem;\n z-index: 13;\n max-width: min(16rem, calc(100% - 2rem));\n padding: 0.38rem 0.55rem;\n border: 1px solid color-mix(in srgb, var(--frontend-stroke) 52%, var(--toolbar-border));\n border-radius: 0.5rem;\n background: var(--toolbar-menu-bg);\n color: var(--toolbar-text);\n box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2);\n font-size: 0.52rem;\n line-height: 1.35;\n pointer-events: none;\n }\n .overview-map-feedback[hidden] { display: none; }\n .focus-chip[data-radar-yielded=\"true\"] { display: none !important; }\n .diagram-nav #btn-route-probe[aria-pressed=\"true\"] {\n color: var(--backend-stroke);\n background: color-mix(in srgb, var(--backend-stroke) 11%, transparent);\n }\n .diagram-nav #btn-semantic-lens[aria-expanded=\"true\"],\n .diagram-nav #btn-semantic-lens[aria-pressed=\"true\"] {\n color: var(--database-stroke);\n background: color-mix(in srgb, var(--database-stroke) 11%, transparent);\n }\n\n /* Semantic Lens turns the compiled node-kind palette into an analytical\n legend. One kind reveals its real traffic; two kinds compare only their\n direct authored relationships. Geometry and the canonical SVG stay put. */\n .semantic-lens {\n position: absolute;\n right: 1rem;\n bottom: calc(4.15rem + var(--archify-nav-reserve));\n z-index: 24;\n width: min(20rem, calc(100% - 2rem));\n overflow: hidden;\n border: 1px solid color-mix(in srgb, var(--database-stroke) 58%, var(--toolbar-border));\n border-radius: 0.82rem;\n background:\n linear-gradient(135deg, color-mix(in srgb, var(--database-fill) 34%, transparent), transparent 48%),\n var(--toolbar-menu-bg);\n color: var(--toolbar-text);\n box-shadow: 0 18px 52px rgba(0, 0, 0, 0.36);\n backdrop-filter: blur(18px);\n animation: archify-lens-in 180ms cubic-bezier(0.22, 1, 0.36, 1);\n }\n .semantic-lens[hidden] { display: none; }\n .semantic-lens[data-dock-side=\"left\"] { left: 1rem; right: auto; }\n .semantic-lens[data-dock-side=\"right\"] { left: auto; right: 1rem; }\n html[data-preset=\"blueprint\"] .semantic-lens { border-radius: 0.28rem; }\n .semantic-lens-head {\n display: grid;\n grid-template-columns: minmax(0, 1fr) auto;\n align-items: start;\n gap: 0.75rem;\n padding: 0.72rem 0.78rem 0.62rem;\n border-bottom: 1px solid var(--toolbar-border);\n }\n .semantic-lens-eyebrow {\n display: block;\n color: var(--database-stroke);\n font-size: 0.5rem;\n font-weight: 800;\n letter-spacing: 0.16em;\n text-transform: uppercase;\n }\n .semantic-lens-title {\n display: block;\n margin-top: 0.16rem;\n color: var(--text);\n font-size: 0.82rem;\n letter-spacing: -0.02em;\n }\n .semantic-lens-close {\n width: 1.75rem;\n height: 1.75rem;\n padding: 0;\n border: 0;\n border-radius: 0.35rem;\n background: transparent;\n color: var(--toolbar-text);\n font: inherit;\n font-size: 1rem;\n cursor: pointer;\n }\n .semantic-lens-close:hover,\n .semantic-lens-close:focus-visible { background: var(--toolbar-hover); }\n .semantic-lens-instruction {\n margin: 0;\n padding: 0.52rem 0.78rem 0.48rem;\n color: var(--text-faint);\n font-size: 0.55rem;\n line-height: 1.45;\n }\n .semantic-lens-kinds {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 0.3rem;\n max-height: min(15rem, 40vh);\n padding: 0 0.5rem 0.5rem;\n overflow-y: auto;\n scrollbar-width: thin;\n }\n .semantic-lens-kind {\n --lens-color: var(--external-stroke);\n display: grid;\n grid-template-columns: auto minmax(0, 1fr) auto;\n align-items: center;\n gap: 0.42rem;\n min-width: 0;\n min-height: 2.15rem;\n padding: 0.35rem 0.42rem;\n border: 1px solid color-mix(in srgb, var(--lens-color) 24%, var(--toolbar-border));\n border-radius: 0.45rem;\n background: color-mix(in srgb, var(--panel) 76%, transparent);\n color: var(--toolbar-text);\n font: inherit;\n text-align: left;\n cursor: pointer;\n transition: border-color 140ms ease, background 140ms ease, opacity 140ms ease;\n }\n html[data-preset=\"blueprint\"] .semantic-lens-kind { border-radius: 0.12rem; }\n .semantic-lens-kind[data-kind=\"frontend\"],\n .semantic-lens-kind[data-kind=\"start\"] { --lens-color: var(--frontend-stroke); }\n .semantic-lens-kind[data-kind=\"backend\"],\n .semantic-lens-kind[data-kind=\"active\"] { --lens-color: var(--backend-stroke); }\n .semantic-lens-kind[data-kind=\"database\"],\n .semantic-lens-kind[data-kind=\"success\"] { --lens-color: var(--database-stroke); }\n .semantic-lens-kind[data-kind=\"cloud\"],\n .semantic-lens-kind[data-kind=\"waiting\"] { --lens-color: var(--cloud-stroke); }\n .semantic-lens-kind[data-kind=\"messagebus\"] { --lens-color: var(--messagebus-stroke); }\n .semantic-lens-kind[data-kind=\"security\"],\n .semantic-lens-kind[data-kind=\"decision\"],\n .semantic-lens-kind[data-kind=\"failure\"] { --lens-color: var(--security-stroke); }\n .semantic-lens-kind:hover,\n .semantic-lens-kind:focus-visible,\n .semantic-lens-kind[aria-pressed=\"true\"] {\n border-color: color-mix(in srgb, var(--lens-color) 78%, var(--toolbar-border));\n background: color-mix(in srgb, var(--lens-color) 11%, var(--panel));\n }\n .semantic-lens-kind:focus-visible,\n .semantic-lens-close:focus-visible,\n .semantic-lens-actions button:focus-visible {\n outline: 2px solid var(--database-stroke);\n outline-offset: 1px;\n }\n .semantic-lens-kind:disabled { opacity: 0.34; cursor: default; }\n .semantic-lens-swatch {\n width: 0.48rem;\n height: 0.48rem;\n border-radius: 50%;\n background: var(--lens-color);\n box-shadow: 0 0 8px color-mix(in srgb, var(--lens-color) 58%, transparent);\n }\n .semantic-lens-kind strong {\n overflow: hidden;\n color: var(--text);\n font-size: 0.61rem;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n .semantic-lens-kind em {\n color: var(--lens-color);\n font-size: 0.54rem;\n font-style: normal;\n font-weight: 800;\n }\n .semantic-lens-status {\n min-height: 2.5rem;\n margin: 0;\n padding: 0.55rem 0.78rem;\n border-top: 1px solid var(--toolbar-border);\n color: var(--text-faint);\n font-size: 0.55rem;\n line-height: 1.45;\n }\n .semantic-lens-status strong { color: var(--database-stroke); font-weight: 800; }\n .semantic-lens-actions {\n display: flex;\n justify-content: flex-end;\n gap: 0.28rem;\n padding: 0 0.62rem 0.62rem;\n }\n .semantic-lens-actions button {\n min-height: 1.8rem;\n padding: 0.28rem 0.48rem;\n border: 0;\n border-radius: 0.35rem;\n background: transparent;\n color: var(--toolbar-text);\n font: inherit;\n font-size: 0.56rem;\n cursor: pointer;\n }\n .semantic-lens-actions button:hover { background: var(--toolbar-hover); }\n .semantic-lens-actions #semantic-lens-copy { color: var(--database-stroke); }\n @keyframes archify-lens-in {\n from { opacity: 0; transform: translateY(5px) scale(0.985); }\n to { opacity: 1; transform: translateY(0) scale(1); }\n }\n\n /* Route Probe answers a reader question over the compiled semantic graph:\n choose two stable nodes and reveal the shortest authored directed route.\n The receipt and signal live entirely in the viewer layer. */\n .route-probe {\n position: absolute;\n left: 1rem;\n bottom: calc(1rem + var(--archify-nav-reserve));\n z-index: 12;\n width: min(46rem, calc(100% - 25rem));\n min-width: 18rem;\n overflow: hidden;\n border: 1px solid color-mix(in srgb, var(--backend-stroke) 62%, var(--toolbar-border));\n border-radius: 0.82rem;\n background:\n linear-gradient(115deg, color-mix(in srgb, var(--backend-fill) 50%, transparent), transparent 62%),\n var(--toolbar-menu-bg);\n color: var(--toolbar-text);\n box-shadow: 0 18px 48px rgba(0, 0, 0, 0.3);\n backdrop-filter: blur(16px);\n font-size: 0.75rem;\n }\n .route-probe[hidden] { display: none; }\n /* Picking needs one line of guidance, not a wide empty card; the panel\n widens only when there is a traced route to show. */\n .route-probe:not([data-state=\"result\"]) { width: min(22rem, calc(100% - 2rem)); min-width: 0; }\n .route-probe-path:has(\u003e .route-probe-placeholder:only-child) { display: none; }\n .route-probe[data-route-dock=\"top\"] {\n top: 1rem;\n bottom: auto;\n }\n html[data-preset=\"blueprint\"] .route-probe { border-radius: 0.25rem; }\n .route-probe-head {\n display: grid;\n grid-template-columns: minmax(0, 1fr) auto;\n align-items: start;\n gap: 0.65rem;\n padding: 0.625rem 0.75rem 0.5rem;\n border-bottom: 1px solid color-mix(in srgb, var(--toolbar-border) 80%, transparent);\n }\n .route-probe-copy { min-width: 0; }\n .route-probe-eyebrow {\n display: block;\n color: var(--backend-stroke);\n font-size: 0.6875rem;\n font-weight: 600;\n letter-spacing: 0.02em;\n }\n .route-probe-title {\n display: block;\n margin-top: 0.14rem;\n overflow: hidden;\n color: var(--toolbar-text);\n font-size: 0.875rem;\n font-weight: 600;\n line-height: 1.3;\n white-space: nowrap;\n text-overflow: ellipsis;\n }\n .route-probe-actions { display: flex; gap: 0.2rem; }\n .route-probe button {\n min-height: 1.55rem;\n padding: 0.25rem 0.42rem;\n border: 0;\n border-radius: 0.36rem;\n background: transparent;\n color: var(--toolbar-text);\n font: inherit;\n cursor: pointer;\n }\n .route-probe button:hover,\n .route-probe button:focus-visible { background: var(--toolbar-hover); }\n .route-probe button:focus-visible {\n outline: 2px solid var(--backend-stroke);\n outline-offset: 1px;\n }\n .route-probe-copy-link { color: var(--frontend-stroke) !important; }\n .route-probe-find { color: var(--backend-stroke) !important; }\n .route-probe-copy-link[hidden] { display: none; }\n .route-probe-find[hidden] { display: none; }\n .route-probe-path {\n display: flex;\n align-items: center;\n min-height: 2.2rem;\n padding: 0.42rem 0.58rem 0.24rem;\n overflow-x: auto;\n scrollbar-width: thin;\n white-space: nowrap;\n }\n .route-probe-node,\n .route-probe button.route-probe-node {\n flex: none;\n max-width: 9.5rem;\n overflow: hidden;\n min-height: 1.75rem !important;\n padding: 0.22rem 0.38rem;\n border: 1px solid color-mix(in srgb, var(--backend-stroke) 46%, var(--toolbar-border));\n border-radius: 999px;\n background: color-mix(in srgb, var(--backend-fill) 52%, transparent);\n color: var(--text);\n font-size: 0.56rem;\n font-weight: 700;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n .route-probe-node[data-endpoint=\"start\"] {\n border-color: var(--frontend-stroke);\n color: var(--frontend-stroke);\n }\n .route-probe-node[data-endpoint=\"end\"] {\n border-color: var(--security-stroke);\n color: var(--security-stroke);\n }\n .route-probe-node[data-route-journey-state=\"past\"] { opacity: 0.66; }\n .route-probe-node[data-route-journey-state=\"future\"] { opacity: 0.48; }\n .route-probe-node[aria-current=\"step\"] {\n border-color: var(--backend-stroke);\n background: color-mix(in srgb, var(--backend-fill) 82%, var(--toolbar-menu-bg));\n color: var(--text);\n box-shadow: 0 0 0 1px color-mix(in srgb, var(--backend-stroke) 42%, transparent), 0 0 14px color-mix(in srgb, var(--backend-stroke) 34%, transparent);\n opacity: 1;\n }\n html[data-preset=\"blueprint\"] .route-probe-node[aria-current=\"step\"] {\n border-style: double;\n border-width: 3px;\n border-radius: 0.2rem;\n box-shadow: none;\n }\n .route-probe-arrow {\n flex: none;\n padding: 0 0.28rem;\n color: var(--text-faint);\n font-size: 0.65rem;\n }\n .route-probe-placeholder {\n color: var(--text-faint);\n font-size: 0.75rem;\n }\n .route-probe-status {\n display: block;\n min-height: 1.45rem;\n padding: 0.5rem 0.75rem 0.625rem;\n color: var(--text-muted, var(--text-faint));\n font-size: 0.75rem;\n line-height: 1.45;\n }\n .route-journey-controls {\n display: grid;\n grid-template-columns: 2rem minmax(5.8rem, auto) 2rem minmax(4.8rem, auto);\n justify-content: start;\n gap: 0.22rem;\n padding: 0.08rem 0.58rem 0.28rem;\n }\n .route-journey-controls[hidden] { display: none; }\n .route-journey-controls button {\n min-height: 1.8rem;\n border: 1px solid color-mix(in srgb, var(--toolbar-border) 86%, transparent);\n background: color-mix(in srgb, var(--toolbar-bg) 72%, transparent);\n font-size: 0.54rem;\n font-weight: 700;\n }\n .route-journey-controls button:disabled {\n cursor: default;\n opacity: 0.34;\n }\n .route-journey-play { color: var(--backend-stroke) !important; }\n .route-journey-overview { color: var(--frontend-stroke) !important; }\n .route-journey-controls[data-playing=\"true\"] .route-journey-play {\n border-color: color-mix(in srgb, var(--backend-stroke) 64%, var(--toolbar-border));\n background: color-mix(in srgb, var(--backend-fill) 62%, transparent);\n }\n .route-probe[data-state=\"error\"] {\n border-color: color-mix(in srgb, var(--security-stroke) 72%, var(--toolbar-border));\n }\n .route-probe[data-state=\"error\"] .route-probe-status { color: var(--security-stroke); }\n\n .focus-chip {\n position: absolute;\n left: 1rem;\n top: 1rem;\n z-index: 12;\n display: block;\n width: min(22rem, calc(100% - 13rem));\n max-width: calc(100% - 13rem);\n overflow: hidden;\n border: 1px solid var(--frontend-stroke);\n border-radius: 0.85rem;\n background: var(--toolbar-menu-bg);\n color: var(--toolbar-text);\n box-shadow: 0 18px 48px rgba(0, 0, 0, 0.3);\n backdrop-filter: blur(16px);\n font-size: 0.6875rem;\n }\n .focus-chip[hidden] { display: none; }\n html[data-preset=\"blueprint\"] .focus-chip { border-radius: 0.3rem; }\n .focus-chip button {\n flex: none;\n padding: 0.3rem 0.45rem;\n border-radius: 0.35rem;\n font-size: 0.625rem;\n }\n .relationship-lens-head {\n position: relative;\n display: grid;\n grid-template-columns: minmax(0, 1fr) auto;\n align-items: start;\n gap: 0.75rem;\n padding: 0.9rem 0.75rem 0.65rem;\n border-bottom: 1px solid var(--toolbar-border);\n background: linear-gradient(120deg, color-mix(in srgb, var(--frontend-fill) 34%, transparent), transparent 72%);\n }\n .relationship-lens-drag-handle {\n position: absolute;\n top: 0.12rem;\n left: 50%;\n width: 2.5rem;\n height: 0.78rem;\n padding: 0 !important;\n transform: translateX(-50%);\n color: var(--text-faint) !important;\n cursor: grab;\n touch-action: none;\n user-select: none;\n }\n .relationship-lens-drag-handle::before {\n content: \"\";\n display: block;\n width: 1.25rem;\n margin: auto;\n border-top: 2px dotted currentColor;\n opacity: 0.72;\n }\n .focus-chip[data-panel-dragging=\"true\"] .relationship-lens-drag-handle { cursor: grabbing; }\n @media (hover: none), (pointer: coarse) {\n .relationship-lens-drag-handle { display: none; }\n }\n .relationship-lens-copy { min-width: 0; }\n .relationship-lens-actions {\n display: flex;\n flex-direction: column;\n align-items: flex-end;\n gap: 0.18rem;\n }\n .relationship-lens-actions button { white-space: nowrap; }\n .relationship-lens-actions #btn-focus-clear {\n display: grid;\n width: 1.75rem;\n height: 1.75rem;\n padding: 0;\n place-items: center;\n color: var(--text-muted);\n font-size: 1rem;\n line-height: 1;\n }\n .relationship-lens-actions #btn-focus-clear:hover,\n .relationship-lens-actions #btn-focus-clear:focus-visible {\n background: var(--toolbar-hover);\n color: var(--text);\n }\n .relationship-lens-actions #btn-focus-copy { color: var(--frontend-stroke); }\n .relationship-lens-actions #btn-focus-relations { display: none; }\n .relationship-lens-eyebrow {\n display: block;\n margin-bottom: 0.18rem;\n color: var(--frontend-stroke);\n font-size: 0.5rem;\n font-weight: 800;\n letter-spacing: 0.14em;\n text-transform: uppercase;\n }\n .relationship-lens-title {\n display: block;\n overflow: hidden;\n color: var(--text);\n font-size: 0.8rem;\n line-height: 1.35;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n .semantic-passport-detail {\n display: block;\n margin-top: 0.12rem;\n overflow: hidden;\n color: var(--text-muted);\n font-size: 0.625rem;\n line-height: 1.4;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n .semantic-passport-detail[hidden] { display: none; }\n .semantic-passport-meta {\n display: flex;\n flex-wrap: wrap;\n gap: 0.24rem;\n margin-top: 0.42rem;\n }\n .semantic-passport-meta span,\n .semantic-passport-meta code {\n max-width: 100%;\n overflow: hidden;\n padding: 0.14rem 0.34rem;\n border: 1px solid color-mix(in srgb, var(--toolbar-border) 82%, transparent);\n border-radius: 999px;\n background: color-mix(in srgb, var(--panel) 68%, transparent);\n color: var(--text-faint);\n font: inherit;\n font-size: 0.5rem;\n letter-spacing: 0.04em;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n .semantic-passport-meta [data-passport=\"kind\"] {\n border-color: color-mix(in srgb, var(--frontend-stroke) 50%, var(--toolbar-border));\n color: var(--frontend-stroke);\n font-weight: 800;\n text-transform: uppercase;\n }\n .semantic-passport-meta [data-passport=\"tag\"] { color: var(--cloud-stroke); }\n .semantic-passport-meta [data-passport=\"brand\"] {\n border-color: color-mix(in srgb, var(--messagebus-stroke) 52%, var(--toolbar-border));\n color: var(--messagebus-stroke);\n font-weight: 700;\n }\n .semantic-passport-meta [hidden] { display: none; }\n html[data-preset=\"blueprint\"] .semantic-passport-meta span,\n html[data-preset=\"blueprint\"] .semantic-passport-meta code { border-radius: 0.1rem; }\n /* 跨图下钻:Passport 列出全部子图;首项即 Ctrl/双击默认目标 */\n .semantic-passport-drilldowns {\n display: grid;\n gap: 0.28rem;\n margin-top: 0.52rem;\n padding-top: 0.48rem;\n border-top: 1px solid color-mix(in srgb, var(--toolbar-border) 76%, transparent);\n }\n .semantic-passport-drilldowns[hidden] { display: none; }\n .semantic-passport-drilldowns-label {\n color: var(--text-faint);\n font-size: 0.5rem;\n font-weight: 800;\n letter-spacing: 0.1em;\n text-transform: uppercase;\n }\n .semantic-passport-drilldowns-list {\n display: grid;\n gap: 0.22rem;\n }\n .semantic-passport-drilldown {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 0.5rem;\n min-height: 2rem;\n padding: 0.32rem 0.5rem;\n border: 1px solid color-mix(in srgb, var(--toolbar-border) 86%, transparent);\n border-radius: 0.35rem;\n background: color-mix(in srgb, var(--panel) 70%, transparent);\n color: var(--text-muted);\n text-decoration: none;\n font-size: 0.6875rem;\n font-weight: 600;\n }\n .semantic-passport-drilldown:hover,\n .semantic-passport-drilldown:focus-visible {\n color: var(--backend-stroke);\n border-color: color-mix(in srgb, var(--backend-stroke) 42%, var(--toolbar-border));\n }\n .semantic-passport-drilldown:focus-visible {\n outline: 2px solid color-mix(in srgb, var(--backend-stroke) 55%, transparent);\n outline-offset: 1px;\n }\n .semantic-passport-drilldown small {\n color: var(--text-faint);\n font-size: 0.5625rem;\n font-weight: 700;\n letter-spacing: 0.04em;\n text-transform: uppercase;\n }\n .semantic-passport-drilldowns-hint {\n color: var(--text-faint);\n font-size: 0.5625rem;\n line-height: 1.35;\n }\n /* 返回上一级:放在页眉显眼位置,不进 Passport */\n .parent-back-bar {\n display: flex;\n align-items: center;\n gap: 0.65rem;\n margin: 0 0 0.85rem;\n }\n .parent-back-bar[hidden] { display: none !important; }\n .parent-back-bar button {\n display: inline-flex;\n align-items: center;\n gap: 0.45rem;\n min-height: 2.35rem;\n padding: 0.4rem 0.9rem 0.4rem 0.7rem;\n border: 1px solid color-mix(in srgb, var(--backend-stroke) 48%, var(--toolbar-border));\n border-radius: 0.55rem;\n background: color-mix(in srgb, var(--backend-stroke) 14%, var(--panel));\n color: var(--backend-stroke);\n font-size: 0.8125rem;\n font-weight: 700;\n letter-spacing: 0.01em;\n cursor: pointer;\n box-shadow: 0 0 0 1px color-mix(in srgb, var(--backend-stroke) 12%, transparent);\n }\n .parent-back-bar button:hover,\n .parent-back-bar button:focus-visible {\n background: color-mix(in srgb, var(--backend-stroke) 22%, var(--panel));\n border-color: color-mix(in srgb, var(--backend-stroke) 68%, var(--toolbar-border));\n color: var(--text);\n }\n .parent-back-bar button:focus-visible {\n outline: 2px solid color-mix(in srgb, var(--backend-stroke) 55%, transparent);\n outline-offset: 2px;\n }\n .parent-back-bar button .parent-back-arrow {\n font-size: 1rem;\n line-height: 1;\n opacity: 0.92;\n }\n html[data-preset=\"blueprint\"] .parent-back-bar button { border-radius: 0.15rem; }\n svg [data-node-id][data-node-drilldowns] { cursor: pointer; }\n /* 单目标下钻:原生 SVG \u003ca\u003e 不画下划线;角标必须跟 SVG 一起导出 */\n svg a.archify-drilldown-link {\n text-decoration: none;\n color: inherit;\n cursor: pointer;\n }\n svg .archify-drilldown-mark-disc {\n fill: color-mix(in srgb, var(--backend-stroke) 88%, var(--panel));\n stroke: color-mix(in srgb, var(--backend-stroke) 55%, var(--toolbar-border));\n stroke-width: 1;\n }\n svg .archify-drilldown-mark-arrow {\n stroke: var(--panel);\n stroke-width: 1.6;\n stroke-linecap: round;\n stroke-linejoin: round;\n }\n\n .semantic-passport-evidence {\n margin-top: 0.52rem;\n padding: 0.5rem;\n border: 1px solid color-mix(in srgb, var(--backend-stroke) 32%, var(--toolbar-border));\n border-radius: 0.55rem;\n background: color-mix(in srgb, var(--backend-fill) 18%, var(--panel));\n }\n .semantic-passport-evidence[hidden] { display: none; }\n html[data-preset=\"blueprint\"] .semantic-passport-evidence { border-radius: 0.18rem; }\n .semantic-passport-evidence-head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 0.55rem;\n margin-bottom: 0.35rem;\n }\n .semantic-passport-evidence-status {\n display: inline-flex;\n align-items: center;\n gap: 0.28rem;\n color: var(--backend-stroke);\n font-size: 0.5rem;\n font-weight: 800;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n }\n .semantic-passport-evidence-status::before {\n content: '';\n width: 0.38rem;\n height: 0.38rem;\n border-radius: 50%;\n background: var(--backend-stroke);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--backend-stroke) 14%, transparent);\n }\n .semantic-passport-repository {\n min-width: 0;\n overflow: hidden;\n color: var(--text-faint);\n font-size: 0.5rem;\n text-decoration: none;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n .semantic-passport-repository:hover,\n .semantic-passport-repository:focus-visible { color: var(--backend-stroke); }\n .semantic-passport-evidence-links { display: grid; gap: 0.22rem; }\n .semantic-passport-source {\n display: grid;\n grid-template-columns: minmax(0, 1fr) auto;\n gap: 0.15rem 0.45rem;\n align-items: center;\n padding: 0.34rem 0.4rem;\n border: 1px solid transparent;\n border-radius: 0.38rem;\n color: var(--text-muted);\n text-decoration: none;\n }\n a.semantic-passport-source:hover,\n a.semantic-passport-source:focus-visible {\n border-color: color-mix(in srgb, var(--backend-stroke) 42%, transparent);\n background: color-mix(in srgb, var(--backend-fill) 32%, transparent);\n color: var(--text);\n }\n .semantic-passport-source strong {\n overflow: hidden;\n font-size: 0.5625rem;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n .semantic-passport-source code {\n color: var(--backend-stroke);\n font: inherit;\n font-size: 0.5rem;\n }\n .semantic-passport-source small {\n grid-column: 1 / -1;\n overflow: hidden;\n color: var(--text-faint);\n font-size: 0.5rem;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n .relationship-lens-summary {\n display: block;\n margin-top: 0.12rem;\n color: var(--text-faint);\n font-size: 0.5625rem;\n }\n /* Authored task checklist: read-only, collapsed to one counted line. */\n .semantic-passport-task-checklist {\n margin-top: 0.52rem;\n padding-top: 0.48rem;\n border-top: 1px solid color-mix(in srgb, var(--toolbar-border) 76%, transparent);\n }\n .semantic-passport-task-checklist \u003e summary { cursor: pointer; }\n .semantic-passport-task-checklist-count {\n color: var(--text-muted);\n font-size: 0.5625rem;\n }\n .semantic-passport-task-checklist-tasks {\n display: grid;\n gap: 0.32rem;\n max-height: min(16rem, 40vh);\n margin: 0.45rem 0 0;\n padding: 0.15rem;\n overflow: auto;\n overscroll-behavior: contain;\n scrollbar-width: thin;\n scrollbar-color: var(--frontend-stroke) transparent;\n list-style: none;\n }\n .semantic-passport-task-checklist-task {\n display: grid;\n grid-template-columns: 0.9rem minmax(0, 1fr);\n gap: 0.12rem 0.3rem;\n }\n .semantic-passport-task-checklist-mark {\n color: var(--text-faint);\n font-size: 0.625rem;\n font-weight: 800;\n text-align: center;\n }\n .semantic-passport-task-checklist-task[data-status=\"implemented\"] .semantic-passport-task-checklist-mark { color: var(--frontend-stroke); }\n .semantic-passport-task-checklist-task[data-status=\"needs-review\"] .semantic-passport-task-checklist-mark { color: var(--text-muted); }\n .semantic-passport-task-checklist-task \u003e strong {\n color: var(--text-muted);\n font-size: 0.6875rem;\n overflow-wrap: anywhere;\n }\n .semantic-passport-task-checklist-task p,\n .semantic-passport-task-checklist-task .semantic-passport-source { grid-column: 2; }\n .semantic-passport-task-checklist-task p {\n margin: 0;\n color: var(--text-faint);\n font-size: 0.5625rem;\n overflow-wrap: anywhere;\n }\n .semantic-passport-reach {\n display: grid;\n gap: 0.28rem;\n margin-top: 0.52rem;\n padding-top: 0.48rem;\n border-top: 1px solid color-mix(in srgb, var(--toolbar-border) 76%, transparent);\n }\n .semantic-passport-reach[hidden] { display: none; }\n .semantic-passport-reach-label,\n .semantic-passport-task-checklist-label {\n color: var(--text-faint);\n font-size: 0.5rem;\n font-weight: 800;\n letter-spacing: 0.1em;\n text-transform: uppercase;\n }\n .semantic-passport-reach-actions {\n display: grid;\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: 0.25rem;\n }\n .semantic-passport-reach-actions button {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 0.5rem;\n min-width: 0;\n min-height: 2rem;\n padding: 0.32rem 0.42rem;\n border: 1px solid color-mix(in srgb, var(--toolbar-border) 86%, transparent);\n background: color-mix(in srgb, var(--panel) 70%, transparent);\n color: var(--text-muted);\n text-align: left;\n }\n .semantic-passport-reach-actions button:hover:not(:disabled),\n .semantic-passport-reach-actions button:focus-visible {\n border-color: color-mix(in srgb, var(--frontend-stroke) 62%, var(--toolbar-border));\n color: var(--text);\n }\n .semantic-passport-reach-actions button:focus-visible {\n outline: 2px solid var(--frontend-stroke);\n outline-offset: 1px;\n }\n .semantic-passport-reach-actions button:disabled {\n cursor: default;\n opacity: 0.5;\n }\n .semantic-passport-reach-actions button[aria-pressed=\"true\"] {\n border-color: color-mix(in srgb, var(--reach-color, var(--frontend-stroke)) 72%, var(--toolbar-border));\n background: color-mix(in srgb, var(--reach-fill, var(--frontend-fill)) 48%, transparent);\n color: var(--text);\n }\n #btn-reach-upstream {\n --reach-color: var(--database-stroke);\n --reach-fill: var(--database-fill);\n }\n #btn-reach-downstream {\n --reach-color: var(--backend-stroke);\n --reach-fill: var(--backend-fill);\n }\n .semantic-passport-reach-actions strong {\n color: var(--reach-color, var(--frontend-stroke));\n font-size: 0.625rem;\n }\n .semantic-passport-reach-status {\n color: var(--text-faint);\n font-size: 0.525rem;\n line-height: 1.4;\n }\n .semantic-passport-reach-status[hidden] { display: none; }\n .relationship-lens-list {\n max-height: min(18rem, 46vh);\n overflow: auto;\n padding: 0.4rem;\n scrollbar-width: thin;\n scrollbar-color: var(--frontend-stroke) transparent;\n }\n .relationship-lens-list::-webkit-scrollbar,\n .semantic-passport-task-checklist-tasks::-webkit-scrollbar { width: 4px; }\n .relationship-lens-list::-webkit-scrollbar-thumb,\n .semantic-passport-task-checklist-tasks::-webkit-scrollbar-thumb {\n border-radius: 999px;\n background: color-mix(in srgb, var(--frontend-stroke) 68%, transparent);\n }\n .relationship-lens-group + .relationship-lens-group { margin-top: 0.35rem; }\n .relationship-lens-group-title {\n display: block;\n padding: 0.24rem 0.38rem 0.16rem;\n color: var(--text-faint);\n font-size: 0.5rem;\n font-weight: 800;\n letter-spacing: 0.12em;\n text-transform: uppercase;\n }\n .relationship-lens-row {\n display: grid;\n grid-template-columns: 2.1rem minmax(0, 1fr);\n gap: 0.06rem 0.45rem;\n width: 100%;\n padding: 0.45rem 0.5rem !important;\n text-align: left;\n }\n .relationship-lens-row:hover,\n .relationship-lens-row:focus-visible,\n .relationship-lens-row[data-preview-active=\"true\"] {\n background: var(--toolbar-hover);\n box-shadow: inset 2px 0 0 var(--frontend-stroke);\n }\n .relationship-lens-row:focus-visible {\n outline: 2px solid var(--frontend-stroke);\n outline-offset: -2px;\n }\n .relationship-lens-direction {\n grid-row: 1 / 3;\n align-self: center;\n color: var(--frontend-stroke);\n font-size: 0.5rem;\n font-weight: 800;\n letter-spacing: 0.08em;\n }\n .relationship-lens-row[data-direction=\"in\"] .relationship-lens-direction { color: var(--database-stroke); }\n .relationship-lens-row[data-direction=\"loop\"] .relationship-lens-direction { color: var(--security-stroke); }\n .relationship-lens-row strong {\n overflow: hidden;\n color: var(--text);\n font-size: 0.6875rem;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n .relationship-lens-row small {\n overflow: hidden;\n color: var(--text-faint);\n font-size: 0.5625rem;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n .relationship-lens-empty {\n margin: 0;\n padding: 0.75rem;\n color: var(--text-faint);\n font-size: 0.625rem;\n text-align: center;\n }\n\n /* Diagram Guide keeps the artifact's growing reader vocabulary\n discoverable without widening the permanent control bar. It derives\n honest counts at runtime and delegates every action to an existing\n production interaction instead of creating a second command system. */\n .diagram-guide {\n position: absolute;\n top: 1rem;\n right: 1rem;\n z-index: 26;\n width: min(32rem, calc(100% - 2rem));\n max-height: min(42rem, calc(100% - 2rem));\n overflow: auto;\n border: 1px solid color-mix(in srgb, var(--cloud-stroke) 58%, var(--toolbar-border));\n border-radius: 0.82rem;\n background:\n linear-gradient(135deg, color-mix(in srgb, var(--cloud-fill) 32%, transparent), transparent 42%),\n var(--toolbar-menu-bg);\n color: var(--toolbar-text);\n box-shadow: 0 22px 64px rgba(0, 0, 0, 0.4);\n backdrop-filter: blur(18px);\n animation: archify-guide-in 180ms cubic-bezier(0.22, 1, 0.36, 1);\n }\n .diagram-guide[hidden] { display: none; }\n html[data-preset=\"blueprint\"] .diagram-guide { border-radius: 0.28rem; }\n .diagram-guide-head {\n display: grid;\n grid-template-columns: minmax(0, 1fr) auto;\n align-items: start;\n gap: 1rem;\n padding: 0.82rem 0.9rem 0.68rem;\n border-bottom: 1px solid var(--toolbar-border);\n }\n .diagram-guide-eyebrow {\n display: block;\n color: var(--cloud-stroke);\n font-size: 0.5rem;\n font-weight: 800;\n letter-spacing: 0.16em;\n text-transform: uppercase;\n }\n .diagram-guide-title {\n display: block;\n margin-top: 0.16rem;\n color: var(--text);\n font-size: 0.86rem;\n letter-spacing: -0.02em;\n }\n .diagram-guide-close {\n width: 1.8rem;\n height: 1.8rem;\n border: 0;\n border-radius: 0.35rem;\n background: transparent;\n color: var(--toolbar-text);\n font: inherit;\n font-size: 1rem;\n cursor: pointer;\n }\n .diagram-guide-close:hover,\n .diagram-guide-close:focus-visible { background: var(--toolbar-hover); }\n .diagram-guide-stats {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n margin: 0;\n padding: 0.54rem 0.9rem;\n border-bottom: 1px solid color-mix(in srgb, var(--toolbar-border) 74%, transparent);\n color: var(--text-faint);\n font-size: 0.56rem;\n letter-spacing: 0.025em;\n }\n .diagram-guide-stats::before {\n content: '';\n width: 0.42rem;\n height: 0.42rem;\n flex: none;\n border-radius: 50%;\n background: var(--backend-stroke);\n box-shadow: 0 0 10px color-mix(in srgb, var(--backend-stroke) 72%, transparent);\n }\n .diagram-guide-actions {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 0.38rem;\n padding: 0.62rem;\n }\n .diagram-guide-action {\n --guide-accent: var(--frontend-stroke);\n display: grid;\n grid-template-columns: auto minmax(0, 1fr) auto;\n align-items: center;\n gap: 0.55rem;\n min-height: 4.15rem;\n padding: 0.55rem 0.58rem;\n border: 1px solid color-mix(in srgb, var(--guide-accent) 22%, var(--toolbar-border));\n border-radius: 0.55rem;\n background: color-mix(in srgb, var(--panel) 78%, transparent);\n color: var(--toolbar-text);\n font: inherit;\n text-align: left;\n cursor: pointer;\n transition: border-color 140ms ease, background 140ms ease, transform 140ms ease;\n }\n .diagram-guide-action[data-guide-action=\"route\"] { --guide-accent: var(--backend-stroke); }\n .diagram-guide-action[data-guide-action=\"map\"] { --guide-accent: var(--database-stroke); }\n .diagram-guide-action[data-guide-action=\"lens\"] { --guide-accent: var(--messagebus-stroke); }\n\n .diagram-guide-action[data-guide-action=\"present\"] {\n --guide-accent: var(--cloud-stroke);\n }\n .diagram-guide-action:hover,\n .diagram-guide-action:focus-visible {\n border-color: color-mix(in srgb, var(--guide-accent) 76%, var(--toolbar-border));\n background: color-mix(in srgb, var(--guide-accent) 9%, var(--panel));\n transform: translateY(-1px);\n }\n .diagram-guide-action:focus-visible,\n .diagram-guide-close:focus-visible {\n outline: 2px solid var(--cloud-stroke);\n outline-offset: 1px;\n }\n .diagram-guide-action:disabled {\n opacity: 0.42;\n cursor: default;\n transform: none;\n }\n .diagram-guide-index {\n display: grid;\n place-items: center;\n width: 1.65rem;\n height: 1.65rem;\n border: 1px solid color-mix(in srgb, var(--guide-accent) 64%, var(--toolbar-border));\n border-radius: 0.35rem;\n color: var(--guide-accent);\n font-size: 0.5rem;\n font-weight: 800;\n letter-spacing: 0.06em;\n }\n .diagram-guide-action-copy { min-width: 0; }\n .diagram-guide-action strong {\n display: block;\n overflow: hidden;\n color: var(--text);\n font-size: 0.66rem;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n .diagram-guide-action small {\n display: -webkit-box;\n margin-top: 0.18rem;\n overflow: hidden;\n color: var(--text-faint);\n font-size: 0.5rem;\n line-height: 1.35;\n -webkit-box-orient: vertical;\n -webkit-line-clamp: 2;\n }\n .diagram-guide kbd {\n align-self: start;\n min-width: 1.45rem;\n padding: 0.12rem 0.3rem;\n border: 1px solid var(--toolbar-border);\n border-radius: 0.25rem;\n color: var(--text-faint);\n font: inherit;\n font-size: 0.5rem;\n text-align: center;\n }\n .diagram-guide-shortcuts {\n display: flex;\n flex-wrap: wrap;\n gap: 0.34rem 0.62rem;\n padding: 0.58rem 0.9rem 0.68rem;\n border-top: 1px solid var(--toolbar-border);\n color: var(--text-faint);\n font-size: 0.5rem;\n }\n .diagram-guide-shortcuts span {\n display: inline-flex;\n align-items: center;\n gap: 0.28rem;\n white-space: nowrap;\n }\n .diagram-guide-feedback {\n min-height: 1rem;\n margin: -0.18rem 0.9rem 0.62rem;\n color: var(--security-stroke);\n font-size: 0.5rem;\n }\n .diagram-guide-feedback:empty { display: none; }\n .diagram-guide-credit {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 0.6rem;\n padding: 0.46rem 0.9rem 0.56rem;\n border-top: 1px solid color-mix(in srgb, var(--toolbar-border) 74%, transparent);\n color: var(--text-faint);\n font-size: 0.5rem;\n letter-spacing: 0.02em;\n text-decoration: none;\n }\n .diagram-guide-credit-link {\n display: inline-flex;\n align-items: center;\n gap: 0.28rem;\n flex: none;\n opacity: 0.72;\n transition: opacity 120ms ease, color 120ms ease;\n }\n .diagram-guide-star {\n width: 0.62rem;\n height: 0.62rem;\n background: currentColor;\n -webkit-mask: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M8 1.6l1.9 3.9 4.3.6-3.1 3 .7 4.3L8 11.4l-3.8 2 .7-4.3-3.1-3 4.3-.6z' fill='none' stroke='black' stroke-width='1.3' stroke-linejoin='round'/%3E%3C/svg%3E\") center / contain no-repeat;\n mask: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M8 1.6l1.9 3.9 4.3.6-3.1 3 .7 4.3L8 11.4l-3.8 2 .7-4.3-3.1-3 4.3-.6z' fill='none' stroke='black' stroke-width='1.3' stroke-linejoin='round'/%3E%3C/svg%3E\") center / contain no-repeat;\n }\n .diagram-guide-credit:hover .diagram-guide-star,\n .diagram-guide-credit:focus-visible .diagram-guide-star {\n -webkit-mask-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M8 1.6l1.9 3.9 4.3.6-3.1 3 .7 4.3L8 11.4l-3.8 2 .7-4.3-3.1-3 4.3-.6z' stroke='black' stroke-width='1.3' stroke-linejoin='round'/%3E%3C/svg%3E\");\n mask-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M8 1.6l1.9 3.9 4.3.6-3.1 3 .7 4.3L8 11.4l-3.8 2 .7-4.3-3.1-3 4.3-.6z' stroke='black' stroke-width='1.3' stroke-linejoin='round'/%3E%3C/svg%3E\");\n }\n .diagram-guide-credit:hover .diagram-guide-credit-link,\n .diagram-guide-credit:focus-visible .diagram-guide-credit-link { opacity: 1; color: var(--cloud-stroke); }\n .diagram-guide-credit:focus-visible { outline: 2px solid var(--cloud-stroke); outline-offset: -2px; }\n @keyframes archify-guide-in {\n from { opacity: 0; transform: translateY(-5px) scale(0.985); }\n to { opacity: 1; transform: translateY(0) scale(1); }\n }\n\n .node-finder {\n position: absolute;\n top: 1rem;\n left: 1rem;\n z-index: 24;\n display: flex;\n flex-direction: column;\n width: min(22rem, calc(100% - 2rem));\n max-height: calc(100% - 2rem);\n overflow: hidden;\n border: 1px solid var(--toolbar-border);\n border-radius: 0.85rem;\n background: var(--toolbar-menu-bg);\n color: var(--toolbar-text);\n box-shadow: 0 6px 8px rgba(0, 0, 0, 0.22);\n backdrop-filter: blur(16px);\n }\n .node-finder[hidden] { display: none; }\n .node-finder[data-context=\"route-source\"] {\n border-color: color-mix(in srgb, var(--frontend-stroke) 68%, var(--toolbar-border));\n }\n .node-finder[data-context=\"route-target\"] {\n border-color: color-mix(in srgb, var(--security-stroke) 68%, var(--toolbar-border));\n }\n html[data-preset=\"blueprint\"] .node-finder { border-radius: 0.3rem; }\n .node-finder-head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 1rem;\n padding: 0.7rem 0.8rem 0.45rem;\n }\n .node-finder-head strong {\n color: var(--text);\n font-size: 0.6875rem;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n }\n .node-finder[data-context=\"route-source\"] .node-finder-head strong { color: var(--frontend-stroke); }\n .node-finder[data-context=\"route-target\"] .node-finder-head strong { color: var(--security-stroke); }\n .node-finder-close {\n width: 1.75rem;\n height: 1.75rem;\n border: 0;\n border-radius: 0.35rem;\n background: transparent;\n color: var(--toolbar-text);\n font: inherit;\n font-size: 1rem;\n cursor: pointer;\n }\n .node-finder-close:hover,\n .node-finder-close:focus-visible { background: var(--toolbar-hover); }\n .node-finder-close:focus-visible,\n .node-finder-result:focus-visible {\n outline: 2px solid var(--frontend-stroke);\n outline-offset: -2px;\n }\n .node-finder-search {\n display: grid;\n grid-template-columns: auto minmax(0, 1fr) auto;\n align-items: center;\n gap: 0.55rem;\n margin: 0 0.55rem 0.55rem;\n padding: 0 0.7rem;\n min-height: 2.35rem;\n border: 1px solid var(--toolbar-border);\n border-radius: 0.55rem;\n background: color-mix(in srgb, var(--panel) 78%, transparent);\n transition: border-color 160ms ease-out, box-shadow 160ms ease-out;\n }\n html[data-preset=\"blueprint\"] .node-finder-search { border-radius: 0.18rem; }\n .node-finder-search \u003e span { color: var(--frontend-stroke); font-size: 0.875rem; }\n .node-finder-search:focus-within {\n border-color: var(--frontend-stroke);\n box-shadow: 0 0 0 2px color-mix(in srgb, var(--frontend-stroke) 14%, transparent);\n }\n .node-finder-input {\n appearance: none;\n width: 100%;\n min-width: 0;\n border: 0;\n outline: 0;\n background: transparent;\n color: var(--text);\n font: inherit;\n font-size: 0.75rem;\n }\n .node-finder-input:focus-visible { outline: none; }\n .node-finder-input::placeholder { color: var(--text-faint); }\n .node-finder kbd {\n padding: 0.1rem 0.32rem;\n border: 1px solid var(--toolbar-border);\n border-radius: 0.25rem;\n color: var(--text-faint);\n font: inherit;\n font-size: 0.5625rem;\n }\n .node-finder-results {\n display: grid;\n flex: 1 1 auto;\n gap: 0;\n min-height: 0;\n max-height: min(18rem, 48vh);\n margin: 0 0.55rem 0.45rem;\n padding: 0;\n overflow-y: auto;\n overscroll-behavior: contain;\n border: 1px solid color-mix(in srgb, var(--toolbar-border) 78%, transparent);\n border-radius: 0.55rem;\n background: color-mix(in srgb, var(--panel) 54%, transparent);\n scroll-padding-block: 0.25rem;\n scrollbar-width: thin;\n scrollbar-color: color-mix(in srgb, var(--panel-border) 90%, transparent) transparent;\n }\n /* Slim overlay-style thumb instead of the chunky platform default. */\n .node-finder-results::-webkit-scrollbar { width: 10px; }\n .node-finder-results::-webkit-scrollbar-track { background: transparent; }\n .node-finder-results::-webkit-scrollbar-thumb {\n background: color-mix(in srgb, var(--panel-border) 85%, transparent);\n border: 3px solid transparent;\n border-radius: 999px;\n background-clip: padding-box;\n }\n .node-finder-results::-webkit-scrollbar-thumb:hover {\n background: color-mix(in srgb, var(--text-muted) 65%, transparent);\n border: 3px solid transparent;\n background-clip: padding-box;\n }\n html[data-preset=\"blueprint\"] .node-finder-results { border-radius: 0.18rem; }\n .node-finder-result {\n display: grid;\n grid-template-columns: minmax(0, 1fr) auto;\n gap: 0.2rem 0.75rem;\n width: 100%;\n min-height: 3rem;\n padding: 0.52rem 0.65rem;\n border: 0;\n border-radius: 0;\n background: transparent;\n color: var(--text);\n font: inherit;\n text-align: left;\n cursor: pointer;\n transition: background-color 160ms ease-out;\n }\n .node-finder-result:not(:last-child) {\n border-bottom: 1px solid color-mix(in srgb, var(--toolbar-border) 62%, transparent);\n }\n .node-finder-result:hover,\n .node-finder-result:focus-visible {\n position: relative;\n z-index: 1;\n background: color-mix(in srgb, var(--frontend-fill) 48%, var(--toolbar-hover));\n }\n .node-finder-result strong {\n overflow: hidden;\n font-size: 0.75rem;\n line-height: 1.25;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n .node-finder-result small {\n grid-column: 1 / -1;\n overflow: hidden;\n color: var(--text-faint);\n font-size: 0.5625rem;\n letter-spacing: 0.015em;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n .node-finder-result em {\n align-self: center;\n color: var(--frontend-stroke);\n font-size: 0.625rem;\n font-style: normal;\n }\n /* A zero count is absence, not information — let it recede. */\n .node-finder-result em[data-zero] { color: var(--text-faint); }\n .node-finder[data-context^=\"route-\"] .node-finder-result em {\n padding: 0.1rem 0.3rem;\n border: 1px solid color-mix(in srgb, currentColor 52%, transparent);\n border-radius: 999px;\n font-size: 0.5rem;\n font-weight: 800;\n letter-spacing: 0.05em;\n text-transform: uppercase;\n }\n .node-finder[data-context=\"route-target\"] .node-finder-result em { color: var(--security-stroke); }\n .node-finder-empty {\n padding: 1.1rem 0.75rem 1.25rem;\n color: var(--text-faint);\n font-size: 0.6875rem;\n text-align: center;\n }\n .node-finder-status {\n margin: 0;\n padding: 0.1rem 0.8rem 0.65rem;\n color: var(--text-faint);\n font-size: 0.5625rem;\n }\n /* #1040: small bold reader text in the semantic accent color (Passport\n direction chip, Finder result count) falls below the 4.5:1 normal-text\n floor in some light themes. Resolve a darker --reader-accent where the\n preset defines one; the diagram's --frontend-stroke itself is untouched,\n as are dark themes and presets that already pass. Zero counts stay faint\n on purpose, so they are excluded from the darker rule. Route-target\n finders keep their --security-stroke status color, so they are excluded\n too. Task checklist done marks are the same kind of text. */\n html[data-theme=\"light\"] .semantic-passport-meta [data-passport=\"kind\"],\n html[data-theme=\"light\"] .semantic-passport-task-checklist-task[data-status=\"implemented\"] .semantic-passport-task-checklist-mark,\n html[data-theme=\"light\"] .node-finder:not([data-context=\"route-target\"]) .node-finder-result em:not([data-zero]) {\n color: var(--reader-accent, var(--frontend-stroke));\n }\n\n .cards {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(min(15rem, 100%), 1fr));\n gap: 1rem;\n margin-top: 2rem;\n }\n\n .card {\n background: var(--panel);\n border-radius: 0.75rem;\n border: 1px solid var(--panel-border);\n padding: 1.125rem 1.25rem;\n /* Card notes are authored prose and may carry an unbreakable run such as a\n slash-joined pipeline or a long identifier. Without this, that run sets the\n card's min-content width, spills past its track in every rail placement,\n and pushes the whole page into horizontal overflow at desktop widths.\n `anywhere` rather than `break-word` because only `anywhere` shrinks the\n intrinsic minimum the layout is sized from. */\n overflow-wrap: anywhere;\n }\n\n html[data-preset=\"signal-flow\"] .card {\n background: linear-gradient(145deg, rgba(10, 24, 43, 0.86), rgba(5, 12, 24, 0.92));\n box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.035);\n }\n html[data-preset=\"signal-flow\"][data-theme=\"light\"] .card {\n background: linear-gradient(145deg, rgba(255, 255, 255, 0.92), rgba(237, 246, 250, 0.9));\n box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.82), 0 12px 32px rgba(45, 78, 99, 0.08);\n }\n\n /* Preserve the one-screen guarantee on ordinary laptop displays. The\n high-screen expansion above must not trade a fuller canvas for page\n scrolling at 900–1080px heights. */\n @media (min-width: 768px) and (max-height: 1100px) {\n .header { margin-bottom: 1rem; }\n .diagram-container {\n padding: 0.875rem;\n padding-bottom: calc(0.875rem + var(--archify-nav-reserve));\n }\n .cards { margin-top: 1rem; }\n .card { padding: 1rem; }\n }\n @media (min-width: 768px) and (max-height: 920px) {\n body { padding-block: 1.25rem; }\n body { --archify-page-top: 1.25rem; }\n .header { margin-bottom: 0.875rem; }\n .cards { margin-top: 0.75rem; }\n html[data-nav-stage-rail=\"true\"] body { padding-block: 0.375rem; }\n /* Align the fixed toolbar with the compact title row. Reserve its hit area\n above the guide without taking extra height from a fitted diagram. */\n html[data-nav-stage-rail=\"true\"] .toolbar { top: 0.25rem; }\n html[data-nav-stage-rail=\"true\"] .header { min-height: 2.625rem; margin-bottom: 0.5rem; }\n html[data-nav-stage-rail=\"true\"] .diagram-container[data-nav-stage-rail=\"true\"] { padding-top: 0.5rem; }\n html[data-nav-stage-rail=\"true\"] .cards { margin-top: 0.375rem; }\n }\n\n html[data-preset=\"blueprint\"] .card {\n position: relative;\n border-color: var(--panel-border);\n border-radius: 0.3rem;\n background: var(--panel);\n box-shadow: none;\n }\n html[data-preset=\"blueprint\"] .card::before {\n content: \"\";\n position: absolute;\n top: -1px;\n left: 1rem;\n width: 2.5rem;\n border-top: 2px solid var(--frontend-stroke);\n }\n\n html[data-preset=\"editorial\"] .card {\n position: relative;\n border-radius: 0.28rem;\n background: var(--panel);\n box-shadow: 0 10px 28px color-mix(in srgb, #000 10%, transparent);\n }\n html[data-preset=\"editorial\"] .card::before {\n position: absolute;\n top: -1px;\n left: 1.25rem;\n width: 3.25rem;\n border-top: 3px solid var(--arrow-emphasis);\n content: \"\";\n }\n html[data-preset=\"editorial\"] .card-dot { border-radius: 0; transform: rotate(45deg) scale(0.78); }\n\n /* Summary rail: the reader measures the viewport and enables it only when the\n diagram keeps its readable width beside the notes. */\n .reader-rail { display: contents; }\n html[data-reader-rail=\"true\"] .container {\n display: grid;\n grid-template-columns: minmax(0, 1fr) var(--archify-rail-width);\n grid-auto-flow: row dense;\n column-gap: var(--archify-rail-gap);\n align-items: start;\n }\n html[data-reader-rail=\"true\"] .container \u003e .header { grid-column: 1 / -1; }\n html[data-reader-rail=\"true\"] .container \u003e .diagram-container { grid-column: 1; }\n /* The rail is at least the diagram's height and may run on to the viewport\n floor (--archify-rail-max, set by the reader); past that, notes scroll and\n the index keeps a usable share instead of collapsing to its header. */\n html[data-reader-rail=\"true\"] .reader-rail {\n display: flex;\n flex-direction: column;\n grid-column: 2;\n align-self: stretch;\n min-height: 0;\n max-height: var(--archify-rail-max, none);\n }\n html:is([data-reader-rail=\"true\"], [data-reader-rail=\"overlay\"]) .cards:empty { display: none; }\n html:is([data-reader-rail=\"true\"], [data-reader-rail=\"overlay\"]) .cards {\n display: flex;\n flex: 0 1 auto;\n flex-direction: column;\n gap: 0;\n min-height: 0;\n margin-top: 0;\n padding: 0.125rem 0 0;\n overflow-y: auto;\n scrollbar-width: none;\n -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 1.25rem), transparent);\n mask-image: linear-gradient(to bottom, #000 calc(100% - 1.25rem), transparent);\n }\n html:is([data-reader-rail=\"true\"], [data-reader-rail=\"overlay\"]) .cards::-webkit-scrollbar { display: none; }\n html:is([data-reader-rail=\"true\"], [data-reader-rail=\"overlay\"]) .cards .card { flex: none; }\n html:is([data-reader-rail=\"true\"], [data-reader-rail=\"overlay\"]) .cards .card {\n padding: 0 0.25rem 1.125rem;\n border: 0;\n border-radius: 0;\n background: transparent;\n box-shadow: none;\n }\n html:is([data-reader-rail=\"true\"], [data-reader-rail=\"overlay\"]) .cards .card + .card {\n padding-top: 1.125rem;\n border-top: 1px solid var(--panel-border);\n }\n html:is([data-reader-rail=\"true\"], [data-reader-rail=\"overlay\"]) .cards .card::before { display: none; }\n html:is([data-reader-rail=\"true\"], [data-reader-rail=\"overlay\"]) .card-header { gap: 0.55rem; margin-bottom: 0.45rem; }\n html:is([data-reader-rail=\"true\"], [data-reader-rail=\"overlay\"]) .cards .card h3 { font-size: 0.875rem; letter-spacing: -0.005em; }\n html:is([data-reader-rail=\"true\"], [data-reader-rail=\"overlay\"]) .cards .card ul { font-size: 0.8125rem; line-height: 1.6; }\n\n .node-outline { display: none; }\n html:is([data-reader-rail=\"true\"], [data-reader-rail=\"overlay\"]) .node-outline:not([hidden]) {\n display: flex;\n flex: 1 0 min(13rem, 55%);\n flex-direction: column;\n min-height: 0;\n margin-top: 0.25rem;\n overflow: hidden;\n border-top: 1px solid var(--panel-border);\n }\n html:is([data-reader-rail=\"true\"], [data-reader-rail=\"overlay\"]) .cards:empty + .node-outline:not([hidden]) { margin-top: 0; border-top: 0; }\n /* Collapsed: only the reveal control remains, so the diagram takes the width. */\n html[data-reader-rail=\"collapsed\"] .reader-rail { display: none; }\n /* Overlay: a drawer for diagrams whose readable floor leaves no room to dock. */\n html[data-reader-rail=\"overlay\"] .reader-rail {\n position: fixed;\n z-index: 150;\n top: 1rem;\n right: 1rem;\n bottom: 1rem;\n display: flex;\n flex-direction: column;\n width: var(--archify-rail-width);\n padding: 0.75rem 1rem 0.5rem;\n border: 1px solid color-mix(in srgb, var(--panel-border) 90%, transparent);\n border-radius: 1rem;\n background: var(--toolbar-menu-bg);\n box-shadow: 0 1px 2px rgba(15, 23, 42, 0.06), 0 28px 64px -18px rgba(15, 23, 42, 0.38);\n animation: rail-drawer-in 0.2s cubic-bezier(0.22, 1, 0.36, 1);\n }\n @keyframes rail-drawer-in {\n from { opacity: 0; transform: translateX(0.75rem); }\n to { opacity: 1; transform: translateX(0); }\n }\n /* Bottom (default): the notes keep their full-width card row and the index\n follows as one panel whose groups flow into columns, so nothing scrolls\n inside it. The placement toggle sits in the index header. */\n html[data-reader-rail=\"bottom\"] .reader-rail {\n display: grid;\n grid-template-areas: \"cards\" \"index\";\n grid-template-columns: minmax(0, 1fr);\n row-gap: 1rem;\n margin-top: 1rem;\n }\n html[data-reader-rail=\"bottom\"] .cards { grid-area: cards; margin-top: 0; }\n html[data-reader-rail=\"bottom\"] .cards:empty { display: none; }\n html[data-reader-rail=\"bottom\"] .node-outline:not([hidden]) {\n display: block;\n grid-area: index;\n padding: 0 1.25rem 0.75rem;\n border: 1px solid var(--panel-border);\n border-radius: 0.75rem;\n background: var(--panel);\n }\n html[data-reader-rail=\"bottom\"] .reader-rail \u003e .rail-controls {\n z-index: 1;\n grid-area: index;\n align-self: start;\n justify-self: end;\n margin: 0.625rem 0.625rem 0 0;\n }\n html[data-reader-rail=\"bottom\"] .node-outline-head { padding: 1rem 2.5rem 0.375rem 0.25rem; }\n html[data-reader-rail=\"bottom\"] .node-outline-list {\n display: grid;\n gap: 0.5rem;\n padding: 0;\n overflow: visible;\n -webkit-mask-image: none;\n mask-image: none;\n }\n html[data-reader-rail=\"bottom\"] .node-outline-group + .node-outline-group { margin-top: 0; }\n html[data-reader-rail=\"bottom\"] .node-outline-group:has(\u003e .node-outline-heading) { padding-bottom: 0.25rem; }\n html[data-reader-rail=\"bottom\"] .node-outline-group \u003e ul {\n display: flex;\n flex-wrap: wrap;\n gap: 0.25rem 0.5rem;\n }\n html[data-reader-rail=\"bottom\"] .node-outline-group \u003e ul::before { display: none; }\n html[data-reader-rail=\"bottom\"] .node-outline-group li {\n flex: 0 1 auto;\n min-width: min(100%, 12rem);\n max-width: min(100%, 32rem);\n }\n html[data-reader-rail=\"bottom\"] .node-outline-heading {\n justify-content: flex-start;\n gap: 0.5rem;\n }\n html[data-reader-rail=\"bottom\"] .node-outline-heading \u003e span { overflow-wrap: anywhere; }\n .rail-controls { display: none; }\n html:is([data-reader-rail=\"true\"], [data-reader-rail=\"overlay\"], [data-reader-rail=\"bottom\"]) .rail-controls {\n display: flex;\n flex: none;\n justify-content: flex-end;\n gap: 0.125rem;\n margin: -0.25rem -0.25rem 0.25rem 0;\n }\n .rail-control {\n display: inline-grid;\n place-items: center;\n width: 1.875rem;\n height: 1.875rem;\n border: 0;\n border-radius: 0.5rem;\n background: transparent;\n color: var(--text-faint);\n cursor: pointer;\n transition: background 0.14s ease, color 0.14s ease;\n }\n .rail-control[hidden] { display: none; }\n .rail-control:hover,\n .rail-control:focus-visible { color: var(--text); background: color-mix(in srgb, var(--text) 6%, transparent); }\n .rail-control:focus-visible { outline: 1.5px solid color-mix(in srgb, var(--frontend-stroke) 60%, transparent); outline-offset: -1.5px; }\n .rail-control-icon,\n .rail-reveal-icon {\n width: 1rem;\n height: 1rem;\n background: currentColor;\n -webkit-mask: var(--rail-glyph) center / contain no-repeat;\n mask: var(--rail-glyph) center / contain no-repeat;\n }\n /* Panel glyphs: a frame with the panel drawn on the side it moves to. */\n .rail-control-icon.placement,\n #rail-placement[data-placement=\"right\"] .rail-control-icon { --rail-glyph: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cg fill='none' stroke='black' stroke-width='1.6'%3E%3Crect x='2.8' y='3.8' width='14.4' height='12.4' rx='2'/%3E%3Cpath d='M2.8 12h14.4'/%3E%3C/g%3E%3C/svg%3E\"); }\n #rail-placement[data-placement=\"bottom\"] .rail-control-icon { --rail-glyph: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cg fill='none' stroke='black' stroke-width='1.6'%3E%3Crect x='2.8' y='3.8' width='14.4' height='12.4' rx='2'/%3E%3Cpath d='M12.6 3.8v12.4'/%3E%3C/g%3E%3C/svg%3E\"); }\n .rail-control-icon.collapse { --rail-glyph: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath d='m8 5.5 4.5 4.5L8 14.5' fill='none' stroke='black' stroke-width='1.7' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E\"); }\n .rail-reveal-icon { --rail-glyph: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cg fill='none' stroke='black' stroke-width='1.6'%3E%3Crect x='2.8' y='3.8' width='14.4' height='12.4' rx='2'/%3E%3Cpath d='M12.6 3.8v12.4M14.4 7.2h1M14.4 9.6h1M14.4 12h1'/%3E%3C/g%3E%3C/svg%3E\"); }\n .rail-reveal[hidden] { display: none; }\n /* The collapsed rail leaves one quiet pill in the canvas corner. */\n .rail-reveal {\n position: absolute;\n z-index: 13;\n top: 0.75rem;\n right: 0.75rem;\n display: inline-flex;\n align-items: center;\n gap: 0.45rem;\n min-height: 2rem;\n padding: 0 0.75rem 0 0.625rem;\n border: 1px solid color-mix(in srgb, var(--panel-border) 85%, transparent);\n border-radius: 999px;\n background: color-mix(in srgb, var(--panel) 86%, transparent);\n color: var(--text-muted);\n box-shadow: 0 1px 2px rgba(15, 23, 42, 0.05), 0 8px 20px -12px rgba(15, 23, 42, 0.28);\n backdrop-filter: blur(12px);\n font: inherit;\n font-size: 0.75rem;\n font-weight: 500;\n cursor: pointer;\n transition: color 0.14s ease, background 0.14s ease;\n }\n .rail-reveal:hover,\n .rail-reveal:focus-visible { color: var(--text); background: var(--panel); }\n .rail-reveal:focus-visible { outline: 1.5px solid color-mix(in srgb, var(--frontend-stroke) 60%, transparent); outline-offset: 2px; }\n html[data-motion=\"still\"] .reader-rail { animation: none !important; }\n .node-outline-head {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n padding: 1.125rem 0.25rem 0.375rem;\n }\n .node-outline-head strong {\n color: var(--text);\n font-size: 0.875rem;\n font-weight: 600;\n letter-spacing: -0.005em;\n }\n .node-outline-count {\n min-width: 1.35rem;\n padding: 0.1rem 0.4rem;\n border-radius: 999px;\n background: color-mix(in srgb, var(--text) 6%, transparent);\n color: var(--text-muted);\n font-size: 0.6875rem;\n font-weight: 600;\n line-height: 1.3;\n text-align: center;\n font-variant-numeric: tabular-nums;\n }\n .node-outline-list {\n flex: 1 1 auto;\n min-height: 0;\n margin: 0 -0.375rem;\n padding: 0 0 1.25rem;\n overflow-y: auto;\n list-style: none;\n scrollbar-width: none;\n -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 2rem), transparent);\n mask-image: linear-gradient(to bottom, #000 calc(100% - 2rem), transparent);\n }\n .node-outline-list::-webkit-scrollbar { display: none; }\n .node-outline-group + .node-outline-group { margin-top: 0.25rem; }\n .node-outline-group \u003e ul {\n position: relative;\n list-style: none;\n }\n /* A quiet spine ties each group's swatches together like a lane. */\n .node-outline-group \u003e ul::before {\n position: absolute;\n top: 0.95rem;\n bottom: 0.95rem;\n left: calc(0.625rem + 0.3125rem - 0.5px);\n width: 1px;\n background: var(--panel-border);\n content: \"\";\n }\n .node-outline-heading {\n display: flex;\n align-items: baseline;\n justify-content: space-between;\n padding: 0.7rem 0.625rem 0.25rem;\n color: var(--text-faint);\n font-size: 0.6875rem;\n font-weight: 600;\n letter-spacing: 0.02em;\n }\n .node-outline-heading small {\n color: var(--text-dim);\n font-size: 0.6875rem;\n font-weight: 500;\n font-variant-numeric: tabular-nums;\n }\n /* The rail frees the vertical budget, so compact viewports keep the normal\n title spacing instead of the stacked layout's tight stage rail. */\n html[data-reader-rail]:not([data-reader-rail=\"bottom\"]) body { --archify-page-top: 1.5rem; padding-block: 1.5rem; }\n html[data-reader-rail]:not([data-reader-rail=\"bottom\"]) .toolbar { top: calc(var(--archify-page-top) - 0.1875rem); }\n html[data-reader-rail]:not([data-reader-rail=\"bottom\"]) .header { min-height: 0; margin-bottom: 1.125rem; }\n .node-outline-item {\n position: relative;\n display: grid;\n grid-template-columns: 0.625rem minmax(0, auto) minmax(0, 1fr) 0.75rem;\n align-items: center;\n column-gap: 0.625rem;\n width: 100%;\n min-height: 1.95rem;\n padding: 0.28rem 0.5rem 0.28rem 0.625rem;\n border: 0;\n border-radius: 0.5rem;\n background: transparent;\n color: var(--text);\n font: inherit;\n text-align: left;\n cursor: pointer;\n transition: background 0.16s ease;\n }\n /* Trailing chevron hints that a click focuses the node. */\n .node-outline-item::after {\n grid-column: 4;\n width: 0.75rem;\n height: 0.75rem;\n background: currentColor;\n color: var(--text-muted);\n content: \"\";\n opacity: 0;\n transform: translateX(-3px);\n transition: opacity 0.16s ease, transform 0.16s ease;\n -webkit-mask: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='m4.5 3 3 3-3 3' fill='none' stroke='black' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E\") center / contain no-repeat;\n mask: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='m4.5 3 3 3-3 3' fill='none' stroke='black' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E\") center / contain no-repeat;\n }\n .node-outline-item:hover,\n .node-outline-item:focus-visible,\n .node-outline-item[data-previewing] { background: color-mix(in srgb, var(--text) 4.5%, transparent); }\n .node-outline-item:hover::after,\n .node-outline-item:focus-visible::after { opacity: 0.7; transform: translateX(0); }\n .node-outline-item:focus-visible { outline: 1.5px solid color-mix(in srgb, var(--outline-tone) 60%, transparent); outline-offset: -1.5px; }\n .node-outline-item[aria-current=\"true\"] { background: color-mix(in srgb, var(--outline-tone) 10%, transparent); }\n .node-outline-item[aria-current=\"true\"]::after { opacity: 0; }\n /* Swatches echo the diagram's node cards: tinted fill, hairline stroke. */\n .node-outline-dot {\n width: 0.625rem;\n height: 0.5rem;\n border: 1.25px solid var(--outline-tone);\n border-radius: 0.15rem;\n background: color-mix(in srgb, var(--outline-tone) 16%, var(--bg));\n transition: background 0.16s ease, transform 0.16s ease, box-shadow 0.16s ease;\n }\n .node-outline-item:hover .node-outline-dot,\n .node-outline-item:focus-visible .node-outline-dot,\n .node-outline-item[data-previewing] .node-outline-dot,\n .node-outline-item[aria-current=\"true\"] .node-outline-dot {\n background: var(--outline-tone);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--outline-tone) 18%, transparent);\n transform: scale(1.08);\n }\n .node-outline-item strong {\n overflow: hidden;\n font-size: 0.8125rem;\n font-weight: 500;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n .node-outline-item[aria-current=\"true\"] strong { font-weight: 600; }\n .node-outline-item small {\n overflow: hidden;\n color: var(--text-faint);\n font-size: 0.6875rem;\n text-align: right;\n text-overflow: ellipsis;\n white-space: nowrap;\n transition: color 0.16s ease;\n }\n .node-outline-item:hover small,\n .node-outline-item[data-previewing] small,\n .node-outline-item[aria-current=\"true\"] small { color: var(--text-muted); }\n\n /* The desktop bottom rail is an index: compact rows keep related nodes\n together while allowing full names and descriptions to wrap naturally. */\n html[data-reader-rail=\"bottom\"] .node-outline-item {\n grid-template-columns: 0.625rem minmax(0, 1fr) 0.75rem;\n align-items: first baseline;\n column-gap: 0.375rem;\n min-height: 0;\n padding-block: 0.375rem;\n }\n html[data-reader-rail=\"bottom\"] .node-outline-item strong,\n html[data-reader-rail=\"bottom\"] .node-outline-item small {\n overflow: visible;\n text-overflow: clip;\n white-space: normal;\n overflow-wrap: anywhere;\n }\n html[data-reader-rail=\"bottom\"] .node-outline-item strong {\n grid-column: 2;\n grid-row: 1;\n }\n html[data-reader-rail=\"bottom\"] .node-outline-item small {\n grid-column: 2;\n grid-row: 2;\n text-align: left;\n }\n html[data-reader-rail=\"bottom\"] .node-outline-item::after {\n grid-column: 3;\n grid-row: 1;\n align-self: center;\n }\n\n .card-header {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n margin-bottom: 0.625rem;\n }\n\n .card-dot {\n flex: none;\n width: 8px;\n height: 8px;\n border-radius: 50%;\n }\n\n .card-dot.cyan { background: var(--frontend-stroke); }\n .card-dot.emerald { background: var(--backend-stroke); }\n .card-dot.violet { background: var(--database-stroke); }\n .card-dot.amber { background: var(--cloud-stroke); }\n .card-dot.rose { background: var(--security-stroke); }\n .card-dot.orange { background: var(--messagebus-stroke); }\n .card-dot.slate { background: var(--external-stroke); }\n\n .card h3 { font-size: 0.9375rem; font-weight: 600; line-height: 1.4; color: var(--text); }\n\n .card ul {\n list-style: none;\n color: var(--text-muted);\n font-size: 0.8125rem;\n line-height: 1.6;\n }\n\n .card li {\n position: relative;\n margin-bottom: 0.3rem;\n padding-left: 0.9rem;\n }\n .card li:last-child { margin-bottom: 0; }\n /* Hanging markers take the card's tone so wrapped lines align under text. */\n .card li::before {\n position: absolute;\n top: calc(0.8em - 2px);\n left: 0.2rem;\n width: 4px;\n height: 4px;\n border-radius: 50%;\n background: color-mix(in srgb, var(--card-tone, var(--text-muted)) 70%, transparent);\n content: \"\";\n }\n .card:has(.card-dot.cyan) { --card-tone: var(--frontend-stroke); }\n .card:has(.card-dot.emerald) { --card-tone: var(--backend-stroke); }\n .card:has(.card-dot.violet) { --card-tone: var(--database-stroke); }\n .card:has(.card-dot.amber) { --card-tone: var(--cloud-stroke); }\n .card:has(.card-dot.rose) { --card-tone: var(--security-stroke); }\n .card:has(.card-dot.orange) { --card-tone: var(--messagebus-stroke); }\n .card:has(.card-dot.slate) { --card-tone: var(--external-stroke); }\n .card-dot { box-shadow: 0 0 0 3px color-mix(in srgb, var(--card-tone, transparent) 16%, transparent); }\n\n /* ==========================================================\n Print stylesheet — hide interactive controls, force light,\n drop the grid so the diagram doesn't waste ink, use landscape\n so wide diagrams fit, pack summary cards into 2 columns to\n avoid an orphan card on a trailing page.\n ========================================================== */\n @page { size: landscape; margin: 1.5cm; }\n\n @media print {\n /* Force the FULL light palette — including component fills/strokes and\n lane/arrow colors — so printing from dark theme doesn't put neon\n strokes and translucent dark fills on white paper. */\n :root, [data-theme=\"dark\"], [data-theme=\"light\"] {\n --bg: #ffffff;\n --grid: transparent;\n --panel: #ffffff;\n --panel-border: #e2e8f0;\n --text: #0f172a;\n --text-muted: #475569;\n --text-dim: #94a3b8;\n --text-faint: #64748b;\n --mask: #ffffff;\n --lane-fill: rgba(248, 250, 252, 0.65);\n --lane-stroke: #cbd5e1;\n --arrow: #94a3b8;\n --arrow-emphasis: #059669;\n --frontend-fill: rgba(34, 211, 238, 0.15);\n --frontend-stroke: #0891b2;\n --backend-fill: rgba(52, 211, 153, 0.18);\n --backend-stroke: #059669;\n --database-fill: rgba(167, 139, 250, 0.2);\n --database-stroke: #7c3aed;\n --cloud-fill: rgba(251, 191, 36, 0.18);\n --cloud-stroke: #d97706;\n --security-fill: rgba(251, 113, 133, 0.15);\n --security-stroke: #e11d48;\n --messagebus-fill: rgba(251, 146, 60, 0.15);\n --messagebus-stroke:#ea580c;\n --external-fill: rgba(148, 163, 184, 0.18);\n --external-stroke: #64748b;\n }\n body { background: #ffffff !important; padding: 0; }\n .container { max-width: none; }\n .toolbar, .diagram-nav, .focus-chip, .archify-toast, .no-print { display: none !important; }\n \n .diagram-container, .card { box-shadow: none; border-color: #e2e8f0; }\n .diagram-container { --archify-nav-reserve: 0px !important; }\n html[data-preset=\"signal-flow\"] .diagram-container,\n html[data-preset=\"signal-flow\"] .card { background: #ffffff; }\n .cards { grid-template-columns: 1fr 1fr; }\n /* Ensure elements break sensibly across pages */\n .card { break-inside: avoid; page-break-inside: avoid; }\n .diagram-container { break-inside: avoid; page-break-inside: avoid; }\n .diagram-container svg [data-detail] {\n opacity: 1 !important;\n pointer-events: auto !important;\n }\n .diagram-container svg [data-detail-anchor] { transform: none !important; }\n .diagram-container svg[data-lens-active] [data-node-id],\n .diagram-container svg[data-lens-active] [data-edge-from],\n .diagram-container svg[data-legend-preview-active] [data-node-id],\n .diagram-container svg[data-legend-preview-active] [data-edge-from] {\n opacity: 1 !important;\n filter: none !important;\n }\n [data-legend-bridge-runtime] { display: none !important; }\n .semantic-lens-overlay { display: none !important; }\n .relationship-hit-overlay { display: none !important; }\n .relationship-pulse-overlay { display: none !important; }\n .route-probe-overlay, .route-journey-overlay { display: none !important; }\n .diagram-container svg[data-focus-active] [data-node-id],\n .diagram-container svg[data-focus-active] [data-edge-from],\n .diagram-container svg[data-focus-active] [data-graph-role=\"automatic-crossover-underlay\"],\n .diagram-container svg[data-reach-active] [data-node-id],\n .diagram-container svg[data-reach-active] [data-edge-from],\n .diagram-container svg[data-reach-active] [data-graph-role=\"automatic-crossover-underlay\"],\n .diagram-container svg[data-route-active] [data-node-id],\n .diagram-container svg[data-route-active] [data-edge-from],\n .diagram-container svg[data-route-active] [data-graph-role=\"automatic-crossover-underlay\"] {\n opacity: 1 !important;\n filter: none !important;\n animation: none !important;\n }\n h1, .subtitle { color: #0f172a; }\n }\n\n /* ==========================================================\n TOOLBAR (theme toggle + export menu)\n ========================================================== */\n /* The toolbar tracks the centered reader column and the title row, so\n an adaptive narrow reader does not leave its controls stranded at the\n viewport edge. */\n .toolbar {\n position: absolute;\n top: var(--archify-page-top);\n right: max(1rem, calc((100vw - min(100vw - 4rem, var(--archify-reader-width, 1440px))) / 2));\n display: flex;\n align-items: center;\n gap: 0.125rem;\n padding: 0;\n border: 0;\n background: transparent;\n box-shadow: none;\n z-index: 100;\n }\n /* Ghost controls keep a 44px hit area while the visible pill stays compact;\n Export is the one primary action. */\n .toolbar button {\n position: relative;\n isolation: isolate;\n background: transparent;\n color: var(--text-muted);\n border: 0;\n padding: 0.5rem 0.75rem;\n border-radius: 0.5625rem;\n font-family: inherit;\n font-size: 0.8125rem;\n font-weight: 500;\n cursor: pointer;\n white-space: nowrap;\n box-shadow: none;\n transition: color 0.15s;\n display: inline-flex;\n align-items: center;\n gap: 0.4rem;\n min-height: 2.75rem;\n }\n .toolbar button::before {\n position: absolute;\n z-index: -1;\n inset: 0.375rem 0;\n border-radius: 0.5rem;\n background: transparent;\n content: \"\";\n transition: background 0.15s, box-shadow 0.15s;\n }\n .toolbar button:hover { color: var(--text); }\n .toolbar button:hover::before { background: color-mix(in srgb, var(--text) 6%, transparent); }\n .toolbar button[aria-expanded=\"true\"],\n .toolbar button[aria-pressed=\"true\"]:not(#btn-theme) { color: var(--text); }\n .toolbar button[aria-expanded=\"true\"]::before,\n .toolbar button[aria-pressed=\"true\"]:not(#btn-theme)::before {\n background: color-mix(in srgb, var(--text) 8%, transparent);\n }\n .toolbar #btn-export {\n margin-left: 0.375rem;\n padding-right: 0.95rem;\n padding-left: 1rem;\n color: var(--bg);\n font-weight: 550;\n }\n .toolbar #btn-export::before {\n background: var(--text);\n box-shadow: 0 1px 2px rgba(15, 23, 42, 0.12), 0 6px 16px -8px rgba(15, 23, 42, 0.4);\n }\n .toolbar #btn-export:hover,\n .toolbar #btn-export[aria-expanded=\"true\"] { color: var(--bg); }\n .toolbar #btn-export:hover::before,\n .toolbar #btn-export[aria-expanded=\"true\"]::before { background: color-mix(in srgb, var(--text) 86%, var(--bg)); }\n .toolbar button:focus-visible {\n outline: 2px solid var(--frontend-stroke);\n outline-offset: -4px;\n }\n .toolbar { gap: 0.5rem; }\n .toolbar #btn-export { margin-left: 0; }\n .toolbar #btn-export::before { inset: 0.25rem 0; border-radius: 0.6875rem; }\n /* Tools share one quiet capsule; the capsule is drawn inside the 44px row\n so the controls keep their hit area without a tall visible box. */\n .toolbar-group {\n position: relative;\n isolation: isolate;\n display: flex;\n align-items: center;\n gap: 0.0625rem;\n padding: 0 0.25rem;\n }\n .toolbar-group::before {\n position: absolute;\n z-index: -1;\n inset: 0.25rem 0;\n border: 1px solid color-mix(in srgb, var(--panel-border) 85%, transparent);\n border-radius: 0.6875rem;\n background: color-mix(in srgb, var(--panel) 78%, transparent);\n box-shadow: 0 1px 2px rgba(15, 23, 42, 0.04), 0 8px 20px -14px rgba(15, 23, 42, 0.22);\n backdrop-filter: blur(12px);\n content: \"\";\n }\n .toolbar-group button { padding-inline: 0.625rem; }\n .toolbar-group button::before { inset: 0.5rem 0; border-radius: 0.5rem; }\n /* Theme becomes a sliding switch; its label stays for assistive tech. */\n .toolbar #theme-label,\n .toolbar #present-label {\n position: absolute;\n width: 1px;\n height: 1px;\n overflow: hidden;\n clip: rect(0 0 0 0);\n white-space: nowrap;\n }\n .toolbar #btn-theme {\n width: 3.5rem;\n padding: 0;\n justify-content: center;\n }\n .toolbar #btn-theme::before {\n inset: 0.75rem 0.4375rem;\n border-radius: 999px;\n background: color-mix(in srgb, var(--text) 9%, transparent);\n box-shadow: inset 0 1px 2px rgba(15, 23, 42, 0.08);\n }\n .toolbar #btn-theme:hover::before { background: color-mix(in srgb, var(--text) 13%, transparent); }\n .toolbar #btn-theme::after {\n position: absolute;\n z-index: 0;\n top: 50%;\n left: 0.5625rem;\n width: 1.125rem;\n height: 1.125rem;\n border-radius: 50%;\n background: var(--panel);\n box-shadow: 0 1px 2px rgba(15, 23, 42, 0.18), 0 2px 6px rgba(15, 23, 42, 0.12);\n content: \"\";\n transform: translateY(-50%);\n transition: left 0.22s cubic-bezier(0.22, 1, 0.36, 1);\n }\n .toolbar #theme-icon {\n position: absolute;\n z-index: 1;\n top: 50%;\n left: calc(0.5625rem + 0.1875rem);\n width: 0.75rem;\n height: 0.75rem;\n color: var(--cloud-stroke);\n transform: translateY(-50%);\n transition: left 0.22s cubic-bezier(0.22, 1, 0.36, 1);\n }\n [data-theme=\"dark\"] .toolbar #btn-theme::before { background: rgba(148, 163, 184, 0.16); }\n [data-theme=\"dark\"] .toolbar #btn-theme::after { left: calc(100% - 0.5625rem - 1.125rem); background: #3b4a63; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4); }\n [data-theme=\"dark\"] .toolbar #theme-icon { left: calc(100% - 0.5625rem - 1.125rem + 0.1875rem); color: #c7d2fe; }\n .toolbar #btn-present { width: 2.5rem; padding: 0; justify-content: center; }\n .toolbar #btn-present .toolbar-icon { width: 1rem; height: 1rem; }\n html[data-motion=\"still\"] .toolbar #btn-theme::after,\n html[data-motion=\"still\"] .toolbar #theme-icon { transition: none; }\n html[data-present=\"true\"]:not([data-embed=\"true\"]) .toolbar { right: 1rem; }\n html[data-preset=\"editorial\"] .toolbar { border-radius: 0.45rem; }\n .toolbar #btn-motion[hidden] { display: none !important; }\n .toolbar #btn-motion { min-width: 4.4rem; }\n .toolbar #btn-motion:disabled { cursor: default; opacity: 0.58; }\n .toolbar .preset-wrap,\n .toolbar .export-wrap {\n position: relative;\n }\n .toolbar-icon {\n display: inline-block;\n width: 0.95rem;\n height: 0.95rem;\n flex: 0 0 auto;\n background: currentColor;\n -webkit-mask-position: center;\n -webkit-mask-repeat: no-repeat;\n -webkit-mask-size: contain;\n mask-position: center;\n mask-repeat: no-repeat;\n mask-size: contain;\n }\n .toolbar-chevron {\n width: 0.65rem;\n height: 0.65rem;\n transition: transform 0.15s ease;\n }\n [aria-expanded=\"true\"] \u003e .toolbar-chevron { transform: rotate(180deg); }\n [data-theme=\"light\"] #theme-icon {\n -webkit-mask-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cg fill='none' stroke='black' stroke-width='1.7' stroke-linecap='round'%3E%3Ccircle cx='10' cy='10' r='3.1'/%3E%3Cpath d='M10 2.2v1.4M10 16.4v1.4M2.2 10h1.4M16.4 10h1.4M4.5 4.5l1 1M14.5 14.5l1 1M15.5 4.5l-1 1M5.5 14.5l-1 1'/%3E%3C/g%3E%3C/svg%3E\");\n mask-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cg fill='none' stroke='black' stroke-width='1.7' stroke-linecap='round'%3E%3Ccircle cx='10' cy='10' r='3.1'/%3E%3Cpath d='M10 2.2v1.4M10 16.4v1.4M2.2 10h1.4M16.4 10h1.4M4.5 4.5l1 1M14.5 14.5l1 1M15.5 4.5l-1 1M5.5 14.5l-1 1'/%3E%3C/g%3E%3C/svg%3E\");\n }\n [data-theme=\"dark\"] #theme-icon {\n -webkit-mask-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath d='M15.2 12.7A6.3 6.3 0 017.3 4.8a6.3 6.3 0 107.9 7.9Z' fill='none' stroke='black' stroke-width='1.7' stroke-linejoin='round'/%3E%3C/svg%3E\");\n mask-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath d='M15.2 12.7A6.3 6.3 0 017.3 4.8a6.3 6.3 0 107.9 7.9Z' fill='none' stroke='black' stroke-width='1.7' stroke-linejoin='round'/%3E%3C/svg%3E\");\n }\n #btn-present[aria-pressed=\"false\"] #present-icon {\n -webkit-mask-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath d='M7.2 3.2h-4v4M12.8 3.2h4v4M7.2 16.8h-4v-4M12.8 16.8h4v-4' fill='none' stroke='black' stroke-width='1.7' stroke-linecap='round'/%3E%3C/svg%3E\");\n mask-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath d='M7.2 3.2h-4v4M12.8 3.2h4v4M7.2 16.8h-4v-4M12.8 16.8h4v-4' fill='none' stroke='black' stroke-width='1.7' stroke-linecap='round'/%3E%3C/svg%3E\");\n }\n #btn-present[aria-pressed=\"true\"] #present-icon {\n -webkit-mask-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath d='M7.2 7.2h-4v-4M12.8 7.2h4v-4M7.2 12.8h-4v4M12.8 12.8h4v4' fill='none' stroke='black' stroke-width='1.7' stroke-linecap='round'/%3E%3C/svg%3E\");\n mask-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath d='M7.2 7.2h-4v-4M12.8 7.2h4v-4M7.2 12.8h-4v4M12.8 12.8h4v4' fill='none' stroke='black' stroke-width='1.7' stroke-linecap='round'/%3E%3C/svg%3E\");\n }\n .toolbar-chevron {\n -webkit-mask-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='m3 4.5 3 3 3-3' fill='none' stroke='black' stroke-width='1.7' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E\");\n mask-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='m3 4.5 3 3 3-3' fill='none' stroke='black' stroke-width='1.7' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E\");\n }\n .toolbar #btn-preset { min-width: 5.4rem; }\n .preset-control-mark {\n width: 0.58rem;\n height: 0.58rem;\n flex: 0 0 auto;\n border: 1px solid var(--frontend-stroke);\n border-radius: 0.2rem;\n background: color-mix(in srgb, var(--frontend-stroke) 28%, transparent);\n transition: border-radius 0.15s, box-shadow 0.15s, transform 0.15s;\n }\n #btn-preset[data-preset-option=\"signal-flow\"] .preset-control-mark {\n border-radius: 50%;\n box-shadow: 0 0 10px color-mix(in srgb, var(--frontend-stroke) 78%, transparent);\n }\n #btn-preset[data-preset-option=\"blueprint\"] .preset-control-mark {\n border-radius: 0.06rem;\n background: transparent;\n box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--frontend-stroke) 32%, transparent);\n transform: rotate(45deg) scale(0.84);\n }\n #btn-preset[data-preset-option=\"editorial\"] .preset-control-mark {\n border-color: var(--arrow-emphasis);\n border-radius: 0;\n background: var(--arrow-emphasis);\n box-shadow: 0 0 0 2px color-mix(in srgb, var(--arrow-emphasis) 14%, transparent);\n transform: rotate(45deg) scale(0.72);\n }\n /* Menus anchor to the tools capsule so they open flush under it. */\n .toolbar-group .preset-wrap { position: static; }\n .toolbar .preset-menu {\n position: absolute;\n top: calc(100% + 0.125rem);\n right: 0;\n display: none;\n width: 17.5rem;\n max-width: calc(100vw - 2rem);\n padding: 0.375rem;\n border: 1px solid color-mix(in srgb, var(--panel-border) 90%, transparent);\n border-radius: 0.875rem;\n background: var(--toolbar-menu-bg);\n box-shadow: 0 1px 2px rgba(15, 23, 42, 0.06), 0 18px 44px -14px rgba(15, 23, 42, 0.3);\n }\n [data-theme=\"dark\"] .toolbar .preset-menu,\n [data-theme=\"dark\"] .toolbar .export-menu {\n border-color: rgba(148, 163, 184, 0.16);\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3), 0 18px 44px -12px rgba(0, 0, 0, 0.6);\n }\n /* Menu rows are not toolbar pills: drop the ghost-button pseudo surface. */\n .toolbar .preset-menu button,\n .toolbar .export-menu button { isolation: auto; min-height: 0; }\n .toolbar .preset-menu button::before { display: none; }\n .toolbar .preset-menu.open { display: block; }\n .toolbar .preset-menu.open,\n .toolbar .export-menu.open {\n animation: toolbar-menu-in 0.16s cubic-bezier(0.2, 0.8, 0.2, 1);\n transform-origin: top right;\n }\n @keyframes toolbar-menu-in {\n from { opacity: 0; transform: translateY(-0.25rem) scale(0.985); }\n to { opacity: 1; transform: translateY(0) scale(1); }\n }\n .preset-menu-heading {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 1rem;\n padding: 0.45rem 0.55rem 0.4rem;\n color: var(--text-faint);\n font-size: 0.6875rem;\n font-weight: 600;\n letter-spacing: 0.01em;\n }\n .preset-menu-shortcut {\n display: inline-flex;\n align-items: center;\n gap: 0.35rem;\n font-weight: 500;\n }\n .preset-menu-shortcut kbd {\n min-width: 1.125rem;\n padding: 0.05rem 0.3rem;\n border: 1px solid var(--panel-border);\n border-bottom-width: 2px;\n border-radius: 0.3rem;\n background: color-mix(in srgb, var(--text) 3%, var(--toolbar-menu-bg));\n color: var(--text-muted);\n font-family: inherit;\n font-size: 0.625rem;\n font-weight: 600;\n line-height: 1.3;\n text-align: center;\n }\n .toolbar .preset-option {\n display: grid;\n grid-template-columns: 2.375rem minmax(0, 1fr) 1rem;\n align-items: center;\n gap: 0.75rem;\n width: 100%;\n min-height: 3.125rem;\n margin-top: 0.125rem;\n padding: 0.45rem 0.6rem 0.45rem 0.5rem;\n border: 0;\n border-radius: 0.625rem;\n background: transparent;\n color: var(--text);\n box-shadow: none;\n backdrop-filter: none;\n text-align: left;\n transition: background 0.14s ease;\n }\n .toolbar .preset-option:hover,\n .toolbar .preset-option:focus-visible { background: color-mix(in srgb, var(--text) 5%, transparent); }\n .toolbar .preset-option[aria-checked=\"true\"] { background: color-mix(in srgb, var(--frontend-stroke) 8%, transparent); }\n .toolbar .preset-option:focus-visible {\n outline: 1.5px solid color-mix(in srgb, var(--frontend-stroke) 55%, transparent);\n outline-offset: -1.5px;\n }\n .preset-option-swatch {\n position: relative;\n width: 2.375rem;\n height: 1.75rem;\n overflow: hidden;\n border: 0;\n border-radius: 0.4375rem;\n background: #0f172a;\n box-shadow: inset 0 0 0 1px rgba(148, 163, 184, 0.28), 0 1px 2px rgba(15, 23, 42, 0.12);\n transition: box-shadow 0.14s ease, transform 0.14s ease;\n }\n .toolbar .preset-option:hover .preset-option-swatch { transform: translateY(-1px); }\n .toolbar .preset-option[aria-checked=\"true\"] .preset-option-swatch {\n box-shadow: 0 0 0 2px var(--toolbar-menu-bg), 0 0 0 3.5px var(--frontend-stroke);\n }\n .preset-option-swatch::before,\n .preset-option-swatch::after { position: absolute; content: \"\"; }\n .preset-option-swatch.classic::before {\n top: 0.42rem;\n right: 0.35rem;\n bottom: 0.42rem;\n left: 0.35rem;\n border: 1px solid #22d3ee;\n border-radius: 0.16rem;\n background: rgba(34, 211, 238, 0.14);\n }\n /* Classic previews the canvas it will actually produce in this theme. */\n [data-theme=\"light\"] .preset-option-swatch.classic {\n background:\n radial-gradient(circle at 1px 1px, #d9dee5 0.6px, transparent 0.9px) 0 0 / 5px 5px,\n #ffffff;\n }\n [data-theme=\"light\"] .preset-option-swatch.classic::before {\n border-color: #1b87a3;\n background: rgba(14, 116, 144, 0.08);\n box-shadow: 0 2px 4px rgba(17, 24, 39, 0.12);\n }\n .preset-option-swatch.signal-flow {\n border-color: #29415f;\n background: linear-gradient(135deg, #07101e 10%, #10243c 100%);\n }\n .preset-option-swatch.signal-flow::before {\n top: 50%;\n right: 0.2rem;\n left: 0.2rem;\n height: 1px;\n background: linear-gradient(90deg, #67e8f9, #c4b5fd);\n box-shadow: 0 0 8px #67e8f9;\n }\n .preset-option-swatch.signal-flow::after {\n top: calc(50% - 0.13rem);\n right: 0.28rem;\n width: 0.26rem;\n height: 0.26rem;\n border-radius: 50%;\n background: #67e8f9;\n box-shadow: 0 0 7px #67e8f9;\n }\n .preset-option-swatch.blueprint {\n border-color: #34799a;\n border-radius: 0.08rem;\n background-color: #0a2031;\n background-image:\n linear-gradient(rgba(102, 217, 239, 0.18) 1px, transparent 1px),\n linear-gradient(90deg, rgba(102, 217, 239, 0.18) 1px, transparent 1px);\n background-size: 0.42rem 0.42rem;\n }\n .preset-option-swatch.blueprint::before {\n top: 0.34rem;\n right: 0.38rem;\n bottom: 0.34rem;\n left: 0.38rem;\n border: 1px solid #66d9ef;\n }\n .preset-option-swatch.editorial {\n border-color: #b8aa94;\n border-radius: 0.08rem;\n background: repeating-linear-gradient(0deg, #f2eee5 0 0.34rem, #d8d0c2 0.36rem, #f2eee5 0.38rem);\n }\n .preset-option-swatch.editorial::before {\n top: 0;\n bottom: 0;\n left: 0.42rem;\n width: 1px;\n background: #c9502d;\n opacity: 0.72;\n }\n .preset-option-swatch.editorial::after {\n top: 0.52rem;\n right: 0.45rem;\n width: 0.38rem;\n height: 0.38rem;\n background: #c9502d;\n transform: rotate(45deg);\n }\n .preset-option-copy { min-width: 0; }\n .preset-option-copy strong,\n .preset-option-copy small { display: block; }\n .preset-option-copy strong {\n color: var(--text);\n font-size: 0.8125rem;\n font-weight: 550;\n letter-spacing: -0.005em;\n }\n .toolbar .preset-option[aria-checked=\"true\"] .preset-option-copy strong { font-weight: 650; }\n .preset-option-copy small {\n margin-top: 0.125rem;\n overflow: hidden;\n color: var(--text-faint);\n font-size: 0.6875rem;\n font-weight: 400;\n white-space: nowrap;\n text-overflow: ellipsis;\n }\n .preset-option-check {\n display: inline-block;\n width: 0.9rem;\n height: 0.9rem;\n color: var(--frontend-stroke);\n background: currentColor;\n -webkit-mask-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='m3 8.5 3 3 7-7' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E\");\n -webkit-mask-position: center;\n -webkit-mask-repeat: no-repeat;\n -webkit-mask-size: contain;\n mask-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='m3 8.5 3 3 7-7' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E\");\n mask-position: center;\n mask-repeat: no-repeat;\n mask-size: contain;\n opacity: 0;\n }\n .preset-option[aria-checked=\"true\"] .preset-option-check { opacity: 1; }\n html[data-preset=\"editorial\"] .toolbar button,\n html[data-preset=\"editorial\"] .preset-menu,\n html[data-preset=\"editorial\"] .export-menu,\n html[data-preset=\"editorial\"] .overview-map,\n html[data-preset=\"editorial\"] .semantic-lens,\n html[data-preset=\"editorial\"] .route-probe,\n html[data-preset=\"editorial\"] .focus-chip,\n html[data-preset=\"editorial\"] .diagram-guide,\n html[data-preset=\"editorial\"] .node-finder {\n border-radius: 0.3rem;\n }\n .motion-control-dot {\n width: 0.46rem;\n height: 0.46rem;\n flex: 0 0 auto;\n border: 1px solid var(--frontend-stroke);\n border-radius: 50%;\n background: var(--frontend-stroke);\n box-shadow: 0 0 9px color-mix(in srgb, var(--frontend-stroke) 72%, transparent);\n }\n #btn-motion[aria-pressed=\"false\"] .motion-control-dot {\n background: transparent;\n box-shadow: none;\n }\n /* The motion button breathes on the relay cycle, so the chrome keeps time\n with the wave it controls. Motion Governor mirrors the planned cycle from\n the diagram SVG onto the document. */\n html[data-ambient-motion=\"running\"][data-motion=\"live\"]:not([data-motion-owner]):not([data-embed]):not([data-share-playback]):not([data-document-hidden]) #btn-motion .motion-control-dot {\n animation: archify-motion-dot-breathe var(--flow-cycle-chrome, 6.4s) ease-in-out infinite;\n }\n @keyframes archify-motion-dot-breathe {\n 0%, 100% { opacity: 1; transform: scale(1); }\n 46% { opacity: 0.55; transform: scale(0.72); }\n }\n .toolbar .export-menu {\n position: absolute;\n top: calc(100% + 0.125rem);\n right: 0;\n background: var(--toolbar-menu-bg);\n border: 1px solid color-mix(in srgb, var(--panel-border) 90%, transparent);\n width: 19rem;\n max-width: calc(100vw - 2rem);\n max-height: calc(100vh - 5.5rem);\n overflow-y: auto;\n border-radius: 0.875rem;\n padding: 0.375rem;\n display: none;\n flex-direction: column;\n box-shadow: 0 1px 2px rgba(15, 23, 42, 0.06), 0 18px 44px -14px rgba(15, 23, 42, 0.3);\n scrollbar-width: thin;\n }\n .toolbar .export-menu.open { display: flex; }\n .export-menu-header {\n display: flex;\n align-items: flex-start;\n justify-content: space-between;\n gap: 1rem;\n padding: 0.55rem 0.6rem 0.6rem;\n }\n .export-menu-header strong,\n .export-menu-header span { display: block; }\n .export-menu-header strong {\n color: var(--text);\n font-size: 0.875rem;\n font-weight: 600;\n letter-spacing: -0.005em;\n }\n .export-menu-header span {\n margin-top: 0.15rem;\n color: var(--text-faint);\n font-size: 0.75rem;\n }\n .export-menu-header kbd {\n min-width: 1.125rem;\n padding: 0.05rem 0.3rem;\n border: 1px solid var(--panel-border);\n border-bottom-width: 2px;\n border-radius: 0.3rem;\n background: color-mix(in srgb, var(--text) 3%, var(--toolbar-menu-bg));\n color: var(--text-muted);\n font-family: inherit;\n font-size: 0.625rem;\n font-weight: 600;\n line-height: 1.3;\n text-align: center;\n }\n .export-menu-section {\n display: grid;\n grid-template-columns: minmax(0, 1fr);\n gap: 0.0625rem;\n }\n .export-menu-section + .export-menu-section {\n margin-top: 0.3rem;\n padding-top: 0.3rem;\n border-top: 1px solid color-mix(in srgb, var(--panel-border) 80%, transparent);\n }\n .export-menu-heading {\n grid-column: 1 / -1;\n display: block;\n padding: 0.4rem 0.6rem 0.25rem;\n color: var(--text-faint);\n font-size: 0.6875rem;\n font-weight: 600;\n letter-spacing: 0.01em;\n }\n .toolbar .export-menu button {\n display: grid;\n grid-template-columns: 1rem minmax(0, 1fr);\n align-items: center;\n column-gap: 0.7rem;\n background: transparent;\n border: 0;\n box-shadow: none;\n backdrop-filter: none;\n width: 100%;\n min-height: 2.5rem;\n text-align: left;\n padding: 0.4rem 0.6rem;\n border-radius: 0.5625rem;\n color: var(--text);\n white-space: nowrap;\n transition: background 0.14s ease;\n }\n .toolbar .export-menu button:hover,\n .toolbar .export-menu button:focus-visible { background: color-mix(in srgb, var(--text) 5%, transparent); color: var(--text); }\n .toolbar .export-menu button:focus-visible {\n outline: 1.5px solid color-mix(in srgb, var(--frontend-stroke) 55%, transparent);\n outline-offset: -1.5px;\n }\n /* Name and format hint share one line, so each row stays compact. */\n .export-item-copy {\n display: flex;\n align-items: baseline;\n justify-content: space-between;\n gap: 0.75rem;\n min-width: 0;\n }\n .export-item-copy strong {\n overflow: hidden;\n color: inherit;\n font-size: 0.8125rem;\n font-weight: 500;\n text-overflow: ellipsis;\n }\n .export-item-copy small {\n flex: none;\n color: var(--text-faint);\n font-size: 0.6875rem;\n font-weight: 450;\n }\n .toolbar .export-menu button::before {\n position: static;\n z-index: auto;\n inset: auto;\n border-radius: 0;\n display: block;\n width: 1rem;\n height: 1rem;\n background: currentColor;\n color: var(--text-muted);\n content: \"\";\n opacity: 0.8;\n -webkit-mask-position: center;\n -webkit-mask-repeat: no-repeat;\n -webkit-mask-size: contain;\n mask-position: center;\n mask-repeat: no-repeat;\n mask-size: contain;\n }\n .toolbar .export-menu button[data-action=\"route-share-card\"]::before,\n .toolbar .export-menu button[data-action=\"reach-share-card\"]::before {\n -webkit-mask-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath fill='none' stroke='black' stroke-width='1.7' d='M3 4.5h14v11H3zM6 12l2.5-2.5 2 2 1.5-1.5 2 2'/%3E%3Ccircle cx='13.8' cy='7.3' r='1.2' fill='black'/%3E%3C/svg%3E\");\n mask-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath fill='none' stroke='black' stroke-width='1.7' d='M3 4.5h14v11H3zM6 12l2.5-2.5 2 2 1.5-1.5 2 2'/%3E%3Ccircle cx='13.8' cy='7.3' r='1.2' fill='black'/%3E%3C/svg%3E\");\n }\n .toolbar .export-menu button[data-action^=\"copy\"]::before {\n -webkit-mask-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Crect x='6' y='6' width='10' height='10' rx='1.5' fill='none' stroke='black' stroke-width='1.7'/%3E%3Cpath d='M4 13H3.5A1.5 1.5 0 012 11.5v-8A1.5 1.5 0 013.5 2h8A1.5 1.5 0 0113 3.5V4' fill='none' stroke='black' stroke-width='1.7'/%3E%3C/svg%3E\");\n mask-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Crect x='6' y='6' width='10' height='10' rx='1.5' fill='none' stroke='black' stroke-width='1.7'/%3E%3Cpath d='M4 13H3.5A1.5 1.5 0 012 11.5v-8A1.5 1.5 0 013.5 2h8A1.5 1.5 0 0113 3.5V4' fill='none' stroke='black' stroke-width='1.7'/%3E%3C/svg%3E\");\n }\n .toolbar .export-menu button[data-format=\"png\"]::before,\n .toolbar .export-menu button[data-format=\"jpeg\"]::before,\n .toolbar .export-menu button[data-format=\"webp\"]::before {\n -webkit-mask-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath d='M5 2.5h7l3 3v12H5zM12 2.5v3h3' fill='none' stroke='black' stroke-width='1.7'/%3E%3Cpath d='M7.5 14l2-2 1.5 1.5 1.5-1.5' fill='none' stroke='black' stroke-width='1.7'/%3E%3C/svg%3E\");\n mask-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath d='M5 2.5h7l3 3v12H5zM12 2.5v3h3' fill='none' stroke='black' stroke-width='1.7'/%3E%3Cpath d='M7.5 14l2-2 1.5 1.5 1.5-1.5' fill='none' stroke='black' stroke-width='1.7'/%3E%3C/svg%3E\");\n }\n .toolbar .export-menu button[data-format^=\"svg\"]::before,\n .toolbar .export-menu button[data-format=\"webm\"]::before {\n -webkit-mask-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath d='M10 2.5l7 4v7l-7 4-7-4v-7zM3 6.5l7 4 7-4M10 10.5v7' fill='none' stroke='black' stroke-width='1.7'/%3E%3C/svg%3E\");\n mask-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath d='M10 2.5l7 4v7l-7 4-7-4v-7zM3 6.5l7 4 7-4M10 10.5v7' fill='none' stroke='black' stroke-width='1.7'/%3E%3C/svg%3E\");\n }\n .toolbar .export-menu button[data-action=\"route-share-card\"],\n .toolbar .export-menu button[data-action=\"reach-share-card\"] {\n color: var(--text);\n background: color-mix(in srgb, var(--frontend-stroke) 7%, transparent);\n }\n .toolbar .export-menu button[data-action=\"route-share-card\"]::before,\n .toolbar .export-menu button[data-action=\"reach-share-card\"]::before { color: var(--frontend-stroke); opacity: 1; }\n .toolbar .export-menu button[hidden] { display: none; }\n .toolbar .export-menu button[data-action=\"route-share-card\"]:hover,\n .toolbar .export-menu button[data-action=\"reach-share-card\"]:hover {\n background: color-mix(in srgb, var(--frontend-stroke) 12%, transparent);\n }\n .toolbar .export-menu button:disabled {\n color: var(--text-faint);\n background: transparent;\n opacity: 0.55;\n cursor: not-allowed;\n }\n .toolbar .export-menu .hint {\n color: var(--text-faint);\n font-size: 0.6875rem;\n }\n\n /* Toast — brief feedback for actions like \"copied\" */\n .archify-toast {\n position: fixed;\n top: 1rem;\n left: 50%;\n transform: translateX(-50%) translateY(-8px);\n background: var(--toolbar-menu-bg);\n color: var(--text);\n border: 1px solid var(--toolbar-border);\n padding: 0.5rem 1rem;\n border-radius: 0.5rem;\n font-family: inherit;\n font-size: 0.75rem;\n opacity: 0;\n transition: opacity 0.2s, transform 0.2s;\n z-index: 200;\n pointer-events: none;\n box-shadow: 0 4px 20px rgba(0,0,0,0.25);\n }\n .archify-toast.show {\n opacity: 1;\n transform: translateX(-50%) translateY(0);\n }\n\n @media (max-width: 720px) {\n body { padding: 1rem; }\n .toolbar .preset-menu,\n .toolbar .export-menu {\n position: fixed;\n top: 4.5rem;\n right: 1rem;\n left: 1rem;\n width: auto;\n max-width: none;\n }\n .toolbar {\n position: relative;\n top: auto;\n right: auto;\n justify-content: flex-end;\n width: max-content;\n max-width: 100%;\n margin-left: auto;\n margin-bottom: 1rem;\n }\n .header { margin-bottom: 1.25rem; padding-right: 0; }\n html[data-preset=\"signal-flow\"] .header,\n html[data-preset=\"blueprint\"] .header,\n html[data-preset=\"editorial\"] .header { padding-right: 0; }\n .header-row {\n gap: 0.75rem;\n align-items: flex-start;\n }\n html[data-preset=\"signal-flow\"] .header-row,\n html[data-preset=\"blueprint\"] .header-row,\n html[data-preset=\"editorial\"] .header-row { flex-wrap: wrap; }\n html[data-preset=\"signal-flow\"] .header-row::after {\n margin-left: 0;\n order: 3;\n }\n html[data-preset=\"blueprint\"] .header-row::after {\n margin-left: 0;\n order: 3;\n }\n html[data-preset=\"editorial\"] .header-row::after {\n margin-left: 0;\n order: 3;\n }\n h1 {\n font-size: 1.25rem;\n line-height: 1.25;\n }\n .subtitle {\n margin-left: 0;\n font-size: 0.8125rem;\n line-height: 1.55;\n }\n .diagram-container {\n padding: 0.75rem;\n border-radius: 0.75rem;\n }\n html:not([data-embed=\"true\"]) .diagram-container {\n padding: 0.75rem 0.75rem 4.25rem;\n }\n html:not([data-embed=\"true\"]) .diagram-container[data-wide-diagram=\"true\"] {\n overflow-x: auto;\n overflow-y: hidden;\n overscroll-behavior-x: contain;\n scrollbar-width: thin;\n scroll-behavior: smooth;\n }\n html:not([data-embed=\"true\"]) .diagram-container[data-wide-diagram=\"true\"] \u003e svg {\n min-width: 720px;\n }\n html:not([data-embed=\"true\"]) .diagram-container[data-wide-diagram=\"true\"] .diagram-nav,\n html:not([data-embed=\"true\"]) .diagram-container[data-wide-diagram=\"true\"] .overview-map,\n html:not([data-embed=\"true\"]) .diagram-container[data-wide-diagram=\"true\"] .focus-chip,\n html:not([data-embed=\"true\"]) .diagram-container[data-wide-diagram=\"true\"] .route-probe,\n html:not([data-embed=\"true\"]) .diagram-container[data-wide-diagram=\"true\"] .semantic-lens,\n html:not([data-embed=\"true\"]) .diagram-container[data-wide-diagram=\"true\"] .diagram-guide,\n html:not([data-embed=\"true\"]) .diagram-container[data-wide-diagram=\"true\"] .node-finder {\n transform: translateX(var(--archify-scroll-x, 0px));\n }\n .diagram-nav { right: 0.5rem; bottom: 0.5rem; }\n .diagram-nav button {\n min-width: 2.75rem;\n height: 2.75rem;\n }\n .diagram-nav [data-view=\"reset\"] { min-width: 3.75rem; }\n .diagram-nav [data-view=\"reset\"][data-detail-visible] { min-width: 4.35rem; }\n .overview-map {\n right: 0.5rem;\n bottom: 3.7rem;\n width: min(10.25rem, calc(100% - 1rem));\n }\n .overview-map[data-docked=\"true\"] {\n right: var(--archify-radar-right, 0.5rem);\n top: var(--archify-radar-top, 0.5rem);\n bottom: auto;\n transform: none !important;\n }\n .overview-map[data-docked=\"true\"][data-dock-side=\"left\"] {\n right: auto;\n left: var(--archify-radar-left, 0.5rem);\n }\n .overview-map-surface { height: 4rem; }\n .overview-map-feedback { right: 0.5rem; bottom: 3.7rem; }\n .semantic-lens {\n right: 0.5rem;\n bottom: 3.7rem;\n width: calc(100% - 1rem);\n max-height: calc(100% - 4.2rem);\n }\n .semantic-lens[data-dock-side] { left: auto; right: 0.5rem; }\n .semantic-lens-kinds { max-height: min(12rem, 34vh); }\n .focus-chip {\n left: 0.5rem;\n top: 0.5rem;\n width: calc(100% - 1rem);\n max-width: none;\n }\n .relationship-lens-head { padding-top: 0.7rem; }\n .relationship-lens-drag-handle { display: none; }\n .relationship-lens-list { max-height: min(12rem, 38vh); }\n .relationship-lens-actions #btn-focus-relations { display: block; color: var(--database-stroke); }\n .focus-chip:not([data-relations-expanded=\"true\"]) .relationship-lens-head { border-bottom-color: transparent; }\n .focus-chip:not([data-relations-expanded=\"true\"]) .relationship-lens-list { display: none; }\n /* While a mobile reader previews one relationship, turn the full Lens\n into a compact peek card. This keeps the exact source and target on\n canvas instead of covering them with a long, scrollable list. */\n .focus-chip[data-relationship-previewing=\"true\"] .relationship-lens-list {\n max-height: none;\n padding: 0.35rem;\n overflow: hidden;\n }\n .focus-chip[data-relationship-previewing=\"true\"] .relationship-lens-group { margin: 0; }\n .focus-chip[data-relationship-previewing=\"true\"] .relationship-lens-group-title,\n .focus-chip[data-relationship-previewing=\"true\"] .relationship-lens-row:not([data-preview-active=\"true\"]) {\n display: none;\n }\n .node-finder {\n top: 0.5rem;\n left: 0.5rem;\n width: min(22rem, calc(100% - 1rem));\n max-height: calc(100% - 1rem);\n }\n .diagram-guide {\n top: 0.5rem;\n right: 0.5rem;\n left: 0.5rem;\n width: auto;\n max-height: calc(100% - 1rem);\n }\n .diagram-guide-head { padding: 0.62rem 0.7rem 0.52rem; }\n .diagram-guide-stats { padding: 0.42rem 0.7rem; }\n .diagram-guide-actions {\n grid-template-columns: 1fr;\n gap: 0.28rem;\n padding: 0.42rem;\n }\n .diagram-guide-action {\n min-height: 3.1rem;\n padding: 0.42rem 0.48rem;\n }\n .diagram-guide-action small {\n display: block;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n .diagram-guide-index {\n width: 1.5rem;\n height: 1.5rem;\n }\n .diagram-guide-shortcuts { padding: 0.46rem 0.7rem 0.52rem; }\n .diagram-guide-action[data-guide-action=\"present\"] { grid-column: auto; }\n .route-probe[data-guide-open=\"true\"] {\n visibility: hidden;\n pointer-events: none;\n }\n .node-finder-results { max-height: min(16rem, 38vh); }\n .route-probe {\n left: 0.5rem;\n bottom: 3.7rem;\n width: calc(100% - 1rem);\n min-width: 0;\n }\n .route-probe[data-route-dock=\"top\"] {\n top: 0.5rem;\n bottom: auto;\n }\n .route-probe[data-finder-open=\"true\"] {\n visibility: hidden;\n pointer-events: none;\n }\n .route-probe-head { padding: 0.45rem 0.48rem 0.38rem; }\n .route-probe-path { padding: 0.35rem 0.48rem 0.2rem; }\n .route-probe-node { min-height: 2.75rem !important; }\n .route-journey-controls {\n grid-template-columns: 2.75rem minmax(0, 1fr) 2.75rem minmax(0, 1fr);\n padding: 0.08rem 0.48rem 0.32rem;\n }\n .route-journey-controls button { min-height: 2.75rem; }\n .route-probe-status { padding: 0.05rem 0.48rem 0.38rem; }\n .route-probe-node { max-width: 7.5rem; }\n \n \n \n \n \n \n \n \n \n \n \n html[data-embed=\"true\"][data-share-playback=\"true\"] .diagram-container {\n padding-top: 4.25rem;\n }\n html[data-embed=\"true\"][data-share-moment=\"true\"] .diagram-container {\n padding-top: 4.25rem;\n }\n \n .cards {\n grid-template-columns: 1fr;\n margin-top: 1rem;\n }\n html[data-present=\"true\"]:not([data-embed=\"true\"]) body { padding: 0.5rem; }\n html[data-present=\"true\"]:not([data-embed=\"true\"]) .container {\n gap: 0.5rem;\n height: calc(100dvh - 1rem);\n }\n html[data-present=\"true\"]:not([data-embed=\"true\"]) .toolbar {\n position: fixed;\n top: 0.5rem;\n right: 0.5rem;\n margin: 0;\n gap: 0.25rem;\n }\n html[data-present=\"true\"]:not([data-embed=\"true\"]) .toolbar button {\n min-height: 2.75rem;\n padding: 0.42rem 0.58rem;\n }\n html[data-present=\"true\"]:not([data-embed=\"true\"]) .header {\n min-height: 2.25rem;\n padding: 3.5rem 0 0;\n }\n html[data-present=\"true\"]:not([data-embed=\"true\"]) .header-row {\n min-height: 2.25rem;\n margin: 0;\n align-items: center;\n flex-wrap: nowrap;\n }\n html[data-present=\"true\"]:not([data-embed=\"true\"]) .header-row::after,\n html[data-present=\"true\"]:not([data-embed=\"true\"]) .pulse-dot,\n html[data-present=\"true\"]:not([data-embed=\"true\"]) .subtitle { display: none; }\n html[data-present=\"true\"]:not([data-embed=\"true\"]) h1 {\n flex: 1 1 auto;\n min-width: 0;\n max-width: 100%;\n overflow: hidden;\n font-size: 0.875rem;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n \n html[data-present=\"true\"]:not([data-embed=\"true\"]) .diagram-container { padding: 0.5rem 0.5rem 3.75rem; }\n html[data-present=\"true\"]:not([data-embed=\"true\"]) .diagram-container[data-wide-diagram=\"true\"] \u003e svg {\n width: 720px;\n height: auto;\n min-width: 720px;\n }\n }\n\n @media (max-width: 420px) {\n .export-menu-section { grid-template-columns: 1fr; }\n .export-menu-heading,\n .toolbar .export-menu button[data-action=\"route-share-card\"],\n .toolbar .export-menu button[data-action=\"reach-share-card\"] { grid-column: 1; }\n }\n\n @media (prefers-reduced-motion: reduce) {\n .toolbar .preset-menu.open,\n .toolbar .export-menu.open { animation: none; }\n .toolbar-chevron { transition: none; }\n }\n\n @media (max-width: 360px) {\n .toolbar { gap: 0.25rem; }\n .toolbar button { padding-right: 0.58rem; padding-left: 0.58rem; }\n .toolbar #btn-motion { min-width: 4.4rem; padding-right: 0.58rem; padding-left: 0.58rem; }\n #theme-label, #preset-label, #present-label { display: none; }\n }\n\n /* ==========================================================\n SVG SEMANTIC CLASSES — use these instead of inline fill/stroke\n ========================================================== */\n .c-grid { stroke: var(--grid); fill: none; }\n .c-mask { fill: var(--mask); stroke: none; }\n .c-lane-rule { stroke: var(--lane-stroke); fill: none; opacity: 0.7; }\n .c-lane-wash { fill: var(--lane-fill); stroke: none; }\n\n .c-frontend { fill: var(--frontend-fill); stroke: var(--frontend-stroke); }\n .c-backend { fill: var(--backend-fill); stroke: var(--backend-stroke); }\n .c-database { fill: var(--database-fill); stroke: var(--database-stroke); }\n .c-cloud { fill: var(--cloud-fill); stroke: var(--cloud-stroke); }\n .c-security { fill: var(--security-fill); stroke: var(--security-stroke); }\n .c-messagebus { fill: var(--messagebus-fill); stroke: var(--messagebus-stroke); }\n .c-external { fill: var(--external-fill); stroke: var(--external-stroke); }\n\n /* Text helpers */\n .t-primary { fill: var(--text); }\n .t-muted { fill: var(--text-muted); }\n .t-dim { fill: var(--text-dim); }\n .t-frontend { fill: var(--frontend-stroke); }\n .t-backend { fill: var(--backend-stroke); }\n .t-database { fill: var(--database-stroke); }\n .t-cloud { fill: var(--cloud-stroke); }\n .t-security { fill: var(--security-stroke); }\n .t-messagebus { fill: var(--messagebus-stroke); }\n .t-external { fill: var(--external-stroke); }\n\n /* Relationship labels share the exact color token used by their path. */\n .t-edge-default { fill: var(--arrow); }\n .t-edge-emphasis { fill: var(--arrow-emphasis); }\n .t-edge-security { fill: var(--security-stroke); }\n .t-edge-dashed { fill: var(--database-stroke); }\n\n /* Classic canvas: a quiet dot field replaces drafting grid lines, and nodes\n read as lifted cards with hairline strokes. Paint only; authored geometry,\n hit areas and exports keep their canonical shapes. */\n html[data-preset=\"classic\"] .diagram-container {\n background:\n radial-gradient(circle at 1px 1px, var(--canvas-dot) 1px, transparent 1.4px) 0 0 / 20px 20px,\n var(--panel);\n }\n html[data-preset=\"classic\"] .diagram-container \u003e svg .c-grid { stroke: transparent; }\n html[data-preset=\"classic\"][data-theme=\"light\"] .diagram-container \u003e svg [data-node-id] \u003e rect:not(.c-mask) {\n --ambient-node-base-filter: drop-shadow(0 1px 1px rgba(17, 24, 39, 0.05)) drop-shadow(0 6px 12px rgba(17, 24, 39, 0.06));\n filter: var(--ambient-node-base-filter);\n }\n html[data-preset=\"classic\"][data-theme=\"dark\"] .diagram-container \u003e svg [data-node-id] \u003e rect:not(.c-mask) {\n --ambient-node-base-filter: drop-shadow(0 8px 16px rgba(0, 0, 0, 0.35));\n filter: var(--ambient-node-base-filter);\n }\n html[data-preset=\"classic\"] .diagram-container \u003e svg [data-edge-from] {\n stroke-linecap: round;\n stroke-linejoin: round;\n }\n html[data-preset=\"classic\"] .diagram-container \u003e svg .c-lane-wash { fill: color-mix(in srgb, var(--text) 2.6%, transparent); }\n html[data-preset=\"classic\"] .diagram-container \u003e svg .c-lane-rule { stroke: var(--panel-border); opacity: 1; }\n html[data-preset=\"classic\"][data-theme=\"dark\"] .diagram-container \u003e svg .c-lane-rule { stroke: rgba(148, 163, 184, 0.16); }\n html[data-preset=\"classic\"] .diagram-container \u003e svg [data-node-id] {\n transition: transform 0.18s cubic-bezier(0.22, 1, 0.36, 1);\n }\n html[data-preset=\"classic\"]:not([data-motion=\"still\"]) .diagram-container \u003e svg [data-node-id]:hover {\n transform: translateY(-1.5px);\n }\n\n /* Semantic Sigils are small authored role stamps, not icons from a brand\n pack and not viewer controls. Prefix selectors with svg so the canonical\n export's SVG-only stylesheet collector keeps them automatically. */\n svg .semantic-sigil {\n fill: none;\n stroke: currentColor;\n stroke-width: 1.35;\n stroke-linecap: round;\n stroke-linejoin: round;\n opacity: 0.76;\n pointer-events: none;\n }\n svg .semantic-sigil \u003e * { vector-effect: non-scaling-stroke; }\n svg .semantic-sigil .sigil-fill { fill: currentColor; stroke: none; }\n svg .s-frontend { color: var(--frontend-stroke); }\n svg .s-backend { color: var(--backend-stroke); }\n svg .s-database { color: var(--database-stroke); }\n svg .s-cloud { color: var(--cloud-stroke); }\n svg .s-security { color: var(--security-stroke); }\n svg .s-messagebus { color: var(--messagebus-stroke); }\n svg .s-external { color: var(--external-stroke); }\n\n /* Brand Marks are authored identity badges. Their color is contained\n inside one neutral plate so vendor color never replaces Archify's\n semantic node and relationship vocabulary. */\n svg .brand-mark { pointer-events: none; }\n svg .brand-mark-badge { fill: #fff; stroke: none; }\n svg .brand-mark-frame {\n fill: none;\n stroke: #cbd5e1;\n stroke-width: 0.8;\n vector-effect: non-scaling-stroke;\n }\n svg .brand-mark-fallback {\n fill: none;\n stroke: #475569;\n stroke-width: 1.15;\n stroke-linecap: round;\n stroke-linejoin: round;\n }\n svg[data-preset=\"blueprint\"] .brand-mark-badge,\n svg[data-preset=\"blueprint\"] .brand-mark-frame { rx: 1px; }\n\n /* Reading Depth is a viewer-only level-of-detail layer. MAP preserves the\n authored structure and primary labels, READ restores relationship labels\n and node context, and FULL restores tags and fine annotations. Semantic\n intent can reveal the relevant detail without moving any geometry. */\n svg [data-detail] { transition: opacity 160ms ease; }\n svg [data-detail-anchor] {\n transform-box: fill-box;\n transform-origin: center;\n transition: transform 160ms ease;\n }\n .diagram-container[data-detail-level=\"map\"] svg [data-detail=\"context\"],\n .diagram-container[data-detail-level=\"map\"] svg [data-detail=\"fine\"],\n .diagram-container[data-detail-level=\"read\"] svg [data-detail=\"fine\"] {\n opacity: 0;\n pointer-events: none;\n }\n .diagram-container[data-detail-level=\"map\"] svg [data-detail-anchor] {\n transform: translateY(8px);\n }\n\n /* Focus, lens, preview, route, and story states are stronger reader intent than\n the global zoom level. Reveal only their exact semantic matches. */\n svg[data-focus-active] [data-focus-match][data-detail],\n svg[data-focus-active] [data-focus-match] [data-detail],\n svg[data-reach-active] [data-reach-match][data-detail],\n svg[data-reach-active] [data-reach-match] [data-detail],\n svg[data-lens-active] [data-lens-match][data-detail],\n svg[data-lens-active] [data-lens-match] [data-detail],\n svg[data-legend-preview-active] [data-legend-preview-match][data-detail],\n svg[data-legend-preview-active] [data-legend-preview-match] [data-detail],\n svg[data-intent-trace-active] [data-intent-trace-match][data-detail],\n svg[data-intent-trace-active] [data-intent-trace-match] [data-detail],\n svg[data-route-active] [data-route-match][data-detail],\n svg[data-route-active] [data-route-match] [data-detail],\n svg[data-relationship-preview-active] [data-relationship-preview][data-detail],\n svg[data-relationship-preview-active] [data-relationship-preview] [data-detail],\n svg [data-node-id]:hover [data-detail],\n svg [data-node-id]:focus-visible [data-detail] {\n opacity: 1;\n pointer-events: auto;\n }\n svg[data-focus-active] [data-focus-match] [data-detail-anchor],\n svg[data-reach-active] [data-reach-match] [data-detail-anchor],\n svg[data-lens-active] [data-lens-match] [data-detail-anchor],\n svg[data-legend-preview-active] [data-legend-preview-match] [data-detail-anchor],\n svg[data-intent-trace-active] [data-intent-trace-match] [data-detail-anchor],\n svg[data-route-active] [data-route-match] [data-detail-anchor],\n svg [data-node-id]:hover [data-detail-anchor],\n svg [data-node-id]:focus-visible [data-detail-anchor] { transform: none; }\n\n /* Arrows */\n .a-default { stroke: var(--arrow); fill: none; }\n .a-emphasis { stroke: var(--arrow-emphasis); fill: none; }\n .a-security { stroke: var(--security-stroke); fill: none; stroke-dasharray: 5,5; }\n .a-dashed { stroke: var(--database-stroke); fill: none; stroke-dasharray: 4,4; }\n\n /* Arrowhead markers reference these */\n .m-default { fill: var(--arrow); }\n .m-emphasis { fill: var(--arrow-emphasis); }\n .m-security { fill: var(--security-stroke); }\n .m-dashed { fill: var(--database-stroke); }\n\n /* Stable semantic exploration hooks emitted by every renderer. */\n svg [data-node-id] {\n cursor: pointer;\n transition: opacity 0.18s ease, fill-opacity 0.18s ease, stroke-opacity 0.18s ease, filter 0.18s ease;\n }\n svg [data-edge-from],\n svg [data-graph-role=\"automatic-crossover-underlay\"] { transition: opacity 0.18s ease, filter 0.18s ease; }\n svg [data-node-id]:hover,\n svg [data-node-id]:focus-visible {\n filter: drop-shadow(0 0 7px var(--frontend-stroke));\n outline: none;\n }\n svg [data-legend-kind][role=\"button\"] { cursor: pointer; }\n svg [data-legend-kind]:focus { outline: none; }\n svg [data-legend-hit] {\n fill: transparent;\n stroke: transparent;\n stroke-width: 1.4;\n pointer-events: all;\n vector-effect: non-scaling-stroke;\n transition: stroke 150ms ease, stroke-width 150ms ease, filter 150ms ease;\n }\n svg [data-legend-count-badge] { pointer-events: none; }\n svg [data-legend-count-badge] rect {\n fill: var(--mask);\n stroke: var(--panel-border);\n stroke-width: 1;\n vector-effect: non-scaling-stroke;\n }\n svg [data-legend-count-badge] text {\n fill: var(--text-muted);\n font-size: 8px;\n font-weight: 700;\n text-anchor: middle;\n }\n svg [data-legend-kind][role=\"button\"]:hover [data-legend-hit] {\n stroke: color-mix(in srgb, var(--database-stroke) 58%, transparent);\n }\n svg [data-legend-kind][role=\"button\"]:focus-visible [data-legend-hit] {\n stroke: var(--frontend-stroke);\n stroke-width: 1.8;\n filter: drop-shadow(0 0 3px var(--frontend-stroke));\n }\n svg [data-legend-kind][data-legend-selected] [data-legend-hit] {\n stroke: var(--database-stroke);\n stroke-width: 2.2;\n stroke-dasharray: 3 2;\n }\n svg [data-legend-kind][data-legend-selected] [data-legend-count-badge] rect {\n stroke: var(--database-stroke);\n stroke-width: 1.8;\n }\n svg [data-legend-kind][data-legend-zero] { opacity: 0.44; }\n svg[data-preset=\"signal-flow\"] [data-legend-kind][data-legend-selected] [data-legend-hit] {\n filter: drop-shadow(0 0 4px var(--database-stroke));\n }\n svg[data-preset=\"blueprint\"] [data-legend-hit],\n svg[data-preset=\"blueprint\"] [data-legend-count-badge] rect { rx: 0; filter: none; }\n svg[data-legend-preview-active] [data-node-id],\n svg[data-legend-preview-active] [data-edge-from] { opacity: 0.11; }\n svg[data-legend-preview-active] [data-legend-preview-match] { opacity: 1; }\n svg[data-legend-preview-active] [data-legend-preview-peer] { opacity: 0.62; }\n svg[data-legend-preview-active] [data-legend-preview-selected] {\n filter: drop-shadow(0 0 8px var(--database-stroke));\n }\n svg[data-lens-active] [data-node-id],\n svg[data-lens-active] [data-edge-from] { opacity: 0.11; }\n svg[data-lens-active] [data-lens-match] { opacity: 1; }\n svg[data-lens-active] [data-lens-peer] { opacity: 0.62; }\n svg[data-lens-active] [data-lens-selected] {\n filter: drop-shadow(0 0 10px var(--database-stroke));\n }\n .semantic-lens-overlay { pointer-events: none; }\n .semantic-lens-flow {\n fill: none;\n stroke-width: 3.05;\n stroke-linecap: round;\n stroke-dasharray: 0.075 0.925;\n stroke-dashoffset: 0;\n vector-effect: non-scaling-stroke;\n opacity: 0.9;\n animation: archify-semantic-lens-flow 1.35s linear 1 both;\n animation-delay: var(--lens-flow-delay, 0s);\n }\n .semantic-lens-flow[data-direction=\"out\"],\n .semantic-lens-flow[data-direction=\"forward\"] {\n stroke: var(--frontend-stroke);\n filter: drop-shadow(0 0 3px var(--frontend-stroke));\n }\n .semantic-lens-flow[data-direction=\"in\"],\n .semantic-lens-flow[data-direction=\"reverse\"] {\n stroke: var(--database-stroke);\n filter: drop-shadow(0 0 3px var(--database-stroke));\n }\n .semantic-lens-flow[data-direction=\"within\"] {\n stroke: var(--messagebus-stroke);\n filter: drop-shadow(0 0 3px var(--messagebus-stroke));\n }\n svg[data-preset=\"signal-flow\"] .semantic-lens-flow {\n stroke-width: 3.7;\n opacity: 1;\n }\n svg[data-preset=\"signal-flow\"] .semantic-lens-flow[data-direction=\"out\"],\n svg[data-preset=\"signal-flow\"] .semantic-lens-flow[data-direction=\"forward\"] {\n filter: drop-shadow(0 0 6px var(--frontend-stroke));\n }\n svg[data-preset=\"signal-flow\"] .semantic-lens-flow[data-direction=\"in\"],\n svg[data-preset=\"signal-flow\"] .semantic-lens-flow[data-direction=\"reverse\"] {\n filter: drop-shadow(0 0 6px var(--database-stroke));\n }\n svg[data-preset=\"signal-flow\"] .semantic-lens-flow[data-direction=\"within\"] {\n filter: drop-shadow(0 0 6px var(--messagebus-stroke));\n }\n svg[data-preset=\"blueprint\"] .semantic-lens-flow {\n stroke-width: 2.45;\n stroke-linecap: square;\n filter: none;\n opacity: 0.78;\n }\n html[data-embed=\"true\"] .semantic-lens-overlay { display: none; }\n /* Automatic crossover masks are visual companions of their one semantic\n edge. Keep the mask free of data-edge-* identity so graph traversal and\n export still observe exactly one edge, then mirror reader emphasis from\n the paired foreground path in this presentation-only wrapper. */\n svg[data-legend-preview-active] [data-graph-role=\"automatic-crossover\"] \u003e [data-graph-role=\"automatic-crossover-underlay\"],\n svg[data-lens-active] [data-graph-role=\"automatic-crossover\"] \u003e [data-graph-role=\"automatic-crossover-underlay\"] { opacity: 0.11; }\n svg[data-legend-preview-active] [data-graph-role=\"automatic-crossover\"]:has(\u003e [data-legend-preview-match]) \u003e [data-graph-role=\"automatic-crossover-underlay\"],\n svg[data-lens-active] [data-graph-role=\"automatic-crossover\"]:has(\u003e [data-lens-match]) \u003e [data-graph-role=\"automatic-crossover-underlay\"] { opacity: 1; }\n svg[data-legend-preview-active] [data-graph-role=\"automatic-crossover\"]:has(\u003e [data-legend-preview-peer]) \u003e [data-graph-role=\"automatic-crossover-underlay\"],\n svg[data-lens-active] [data-graph-role=\"automatic-crossover\"]:has(\u003e [data-lens-peer]) \u003e [data-graph-role=\"automatic-crossover-underlay\"] { opacity: 0.62; }\n svg[data-focus-active] [data-node-id],\n svg[data-focus-active] [data-edge-from] { opacity: 0.13; }\n svg[data-focus-active] [data-graph-role=\"automatic-crossover\"] \u003e [data-graph-role=\"automatic-crossover-underlay\"] { opacity: 0.13; }\n svg[data-focus-active] [data-lifecycle-rail] { opacity: 0; }\n svg[data-focus-active] [data-focus-match] { opacity: 1; }\n svg[data-focus-active] [data-graph-role=\"automatic-crossover\"]:has(\u003e [data-focus-match]) \u003e [data-graph-role=\"automatic-crossover-underlay\"] { opacity: 1; }\n svg[data-focus-active] [data-focus-selected] {\n filter: drop-shadow(0 0 10px var(--frontend-stroke));\n }\n\n /* Authored Reachability is a bounded graph query over the relationships\n already present in the artifact. Upstream walks incoming edges;\n downstream walks outgoing edges. It never claims runtime causality and\n changes only viewer emphasis, not canonical geometry or export bytes. */\n svg[data-reach-active] [data-node-id],\n svg[data-reach-active] [data-edge-from] { opacity: 0.09; }\n svg[data-reach-active] [data-graph-role=\"automatic-crossover\"] \u003e [data-graph-role=\"automatic-crossover-underlay\"] { opacity: 0.09; }\n svg[data-reach-active] [data-reach-match] { opacity: 1; }\n svg[data-reach-active] [data-graph-role=\"automatic-crossover\"]:has(\u003e [data-reach-match]) \u003e [data-graph-role=\"automatic-crossover-underlay\"] { opacity: 1; }\n svg[data-reach-active=\"upstream\"] [data-reach-origin] {\n filter: drop-shadow(0 0 11px var(--database-stroke));\n }\n svg[data-reach-active=\"downstream\"] [data-reach-origin] {\n filter: drop-shadow(0 0 11px var(--backend-stroke));\n }\n svg[data-reach-active=\"upstream\"] [data-edge-from][data-reach-match] {\n filter: drop-shadow(0 0 4px color-mix(in srgb, var(--database-stroke) 72%, transparent));\n }\n svg[data-reach-active=\"downstream\"] [data-edge-from][data-reach-match] {\n filter: drop-shadow(0 0 4px color-mix(in srgb, var(--backend-stroke) 72%, transparent));\n }\n svg[data-preset=\"blueprint\"][data-reach-active] [data-reach-origin],\n svg[data-preset=\"blueprint\"][data-reach-active] [data-edge-from][data-reach-match] {\n filter: none;\n }\n\n /* Direct Relationship Explorer makes authored paths a real input surface\n without changing their visible stroke or canonical SVG. A transparent\n runtime rail supplies forgiving pointer geometry; the exact authored\n path and endpoints continue to carry every visible state. */\n .relationship-hit-overlay { pointer-events: none; }\n .relationship-hit-target { outline: none; }\n .relationship-hit-rail {\n fill: none;\n stroke: transparent;\n stroke-width: 24;\n stroke-linecap: round;\n stroke-linejoin: round;\n vector-effect: non-scaling-stroke;\n pointer-events: stroke;\n cursor: pointer;\n }\n .relationship-focus-rail {\n fill: none;\n stroke: color-mix(in srgb, var(--frontend-stroke) 34%, transparent);\n stroke-width: 6;\n stroke-dasharray: 2 4;\n stroke-linecap: round;\n stroke-linejoin: round;\n vector-effect: non-scaling-stroke;\n pointer-events: none;\n opacity: 0;\n }\n .relationship-hit-target:focus-visible .relationship-focus-rail {\n opacity: 1;\n }\n svg[data-preset=\"blueprint\"] .relationship-hit-target:focus-visible .relationship-focus-rail {\n stroke-linecap: square;\n filter: none;\n }\n svg[data-relationship-direct-active] [data-node-id],\n svg[data-relationship-direct-active] [data-edge-from] { opacity: 0.16; }\n svg[data-relationship-direct-active] [data-graph-role=\"automatic-crossover\"] \u003e [data-graph-role=\"automatic-crossover-underlay\"] { opacity: 0.16; }\n svg[data-relationship-direct-active] [data-relationship-preview],\n svg[data-relationship-direct-active] [data-relationship-preview-node] { opacity: 1; }\n svg[data-relationship-direct-active] [data-graph-role=\"automatic-crossover\"]:has(\u003e [data-relationship-preview]) \u003e [data-graph-role=\"automatic-crossover-underlay\"] { opacity: 1; }\n svg[data-focus-active]:not([data-relationship-pin-active]) .relationship-hit-rail,\n svg[data-route-picking] .relationship-hit-rail,\n svg[data-route-active] .relationship-hit-rail,\n svg[data-lens-active] .relationship-hit-rail { pointer-events: none; }\n html[data-embed=\"true\"] .relationship-hit-overlay { display: none; }\n @media (hover: none), (pointer: coarse) {\n .relationship-hit-rail { stroke-width: 24; }\n }\n\n /* Relationship Preview is temporary exploration state layered on top of\n neighborhood focus. It changes emphasis only; exported geometry and the\n relationship's original solid/dashed visual language stay untouched. */\n svg[data-relationship-preview-active] [data-focus-match] { opacity: 0.18; }\n svg[data-relationship-preview-active] [data-graph-role=\"automatic-crossover\"]:has(\u003e [data-focus-match]) \u003e [data-graph-role=\"automatic-crossover-underlay\"] { opacity: 0.18; }\n svg[data-relationship-preview-active] [data-lifecycle-rail] { opacity: 0; }\n svg[data-relationship-preview-active] [data-relationship-preview],\n svg[data-relationship-preview-active] [data-relationship-preview-node] { opacity: 1; }\n svg[data-relationship-preview-active] [data-graph-role=\"automatic-crossover\"]:has(\u003e [data-relationship-preview]) \u003e [data-graph-role=\"automatic-crossover-underlay\"] { opacity: 1; }\n svg[data-relationship-preview-active] [data-relationship-preview] {\n filter: drop-shadow(0 0 5px var(--arrow-emphasis));\n }\n svg[data-relationship-preview-active] [data-relationship-preview] path,\n svg[data-relationship-preview-active] path[data-relationship-preview],\n svg[data-relationship-preview-active] [data-relationship-preview] line,\n svg[data-relationship-preview-active] line[data-relationship-preview],\n svg[data-relationship-preview-active] [data-relationship-preview] polyline,\n svg[data-relationship-preview-active] polyline[data-relationship-preview] {\n stroke-width: 2.75;\n }\n svg[data-relationship-preview-active] [data-relationship-preview-source] {\n filter: drop-shadow(0 0 7px var(--frontend-stroke));\n }\n svg[data-relationship-preview-active] [data-relationship-preview-target] {\n filter: drop-shadow(0 0 7px var(--database-stroke));\n }\n .relationship-pulse-overlay { pointer-events: none; }\n .relationship-flow-pulse {\n fill: none;\n stroke: var(--arrow-emphasis);\n stroke-width: 3.35;\n stroke-linecap: round;\n stroke-dasharray: 0.085 0.915;\n stroke-dashoffset: 0;\n vector-effect: non-scaling-stroke;\n opacity: 0;\n filter: drop-shadow(0 0 4px var(--arrow-emphasis));\n animation: archify-relationship-pulse 1.2s linear 1 both;\n }\n svg[data-preset=\"signal-flow\"] .relationship-flow-pulse {\n stroke: var(--frontend-stroke);\n stroke-width: 3.85;\n filter: drop-shadow(0 0 7px var(--frontend-stroke));\n }\n svg[data-preset=\"blueprint\"] .relationship-flow-pulse {\n stroke-width: 2.5;\n stroke-linecap: square;\n filter: none;\n }\n svg[data-preset=\"editorial\"] .relationship-flow-pulse {\n stroke: var(--arrow-emphasis);\n stroke-width: 2.75;\n filter: none;\n }\n .semantic-flow-token {\n color: var(--frontend-stroke);\n opacity: 0;\n pointer-events: none;\n }\n .relationship-flow-token {\n animation: archify-relationship-token-life 1.2s linear 1 both;\n }\n\n .semantic-flow-token-halo {\n fill: var(--mask);\n stroke: currentColor;\n stroke-width: 1.1;\n vector-effect: non-scaling-stroke;\n opacity: 0.96;\n }\n .relationship-flow-token-shape {\n fill: var(--mask);\n stroke: currentColor;\n stroke-width: 1.35;\n stroke-linecap: round;\n stroke-linejoin: round;\n vector-effect: non-scaling-stroke;\n }\n .relationship-flow-token-ink {\n fill: none;\n stroke: currentColor;\n stroke-width: 1.05;\n stroke-linecap: round;\n vector-effect: non-scaling-stroke;\n }\n .relationship-flow-token-dot { fill: currentColor; stroke: none; }\n .semantic-flow-token[data-token-kind=\"data\"] { color: var(--database-stroke); }\n .semantic-flow-token[data-token-kind=\"event\"] { color: var(--messagebus-stroke); }\n .semantic-flow-token[data-token-kind=\"security\"] { color: var(--security-stroke); }\n .semantic-flow-token[data-token-kind=\"state\"] { color: var(--cloud-stroke); }\n svg[data-preset=\"signal-flow\"] .semantic-flow-token {\n filter: drop-shadow(0 0 5px currentColor);\n }\n svg[data-preset=\"blueprint\"] .semantic-flow-token { filter: none; }\n svg[data-preset=\"editorial\"] .semantic-flow-token { filter: none; }\n html[data-embed=\"true\"] .relationship-pulse-overlay { display: none; }\n\n /* Intent Trace is a temporary, pre-click exploration layer. Related\n topology stays readable while a viewer-only signal follows the active trace over\n cloned edge geometry; the authored edge remains untouched underneath. */\n /* Keep the node's opaque backing mask intact while its visible ink dims.\n Fading the whole group also fades that mask and briefly exposes arrows\n routed underneath the node when the pointer leaves. */\n svg[data-intent-trace-active] [data-node-id] { fill-opacity: 0.2; stroke-opacity: 0.2; }\n svg [data-node-id] \u003e .c-mask { fill-opacity: 1; }\n svg[data-intent-trace-active] [data-node-id][data-intent-trace-match] { fill-opacity: 1; stroke-opacity: 1; }\n svg[data-intent-trace-active] [data-edge-from] { opacity: 0.2; }\n svg[data-intent-trace-active] [data-graph-role=\"automatic-crossover\"] \u003e [data-graph-role=\"automatic-crossover-underlay\"] { opacity: 0.2; }\n svg[data-intent-trace-active] [data-lifecycle-rail] { opacity: 0; }\n svg[data-intent-trace-active] [data-intent-trace-match] { opacity: 1; }\n svg[data-intent-trace-active] [data-graph-role=\"automatic-crossover\"]:has(\u003e [data-intent-trace-match]) \u003e [data-graph-role=\"automatic-crossover-underlay\"] { opacity: 1; }\n svg[data-intent-trace-active] [data-intent-trace-selected] {\n filter: drop-shadow(0 0 10px var(--frontend-stroke));\n }\n .intent-trace-overlay { pointer-events: none; }\n .intent-trace-flow {\n fill: none;\n stroke-width: 3.1;\n stroke-linecap: round;\n stroke-dasharray: 0.1 0.9;\n stroke-dashoffset: 0;\n vector-effect: non-scaling-stroke;\n opacity: 0.94;\n animation: archify-intent-trace-flow 1.15s linear infinite;\n }\n /* Direction names describe each edge relative to the focused node only.\n The cloned geometry already runs from the authored source to target, so\n incoming and outgoing signals must share the same playback direction. */\n .intent-trace-flow[data-direction=\"out\"] {\n stroke: var(--frontend-stroke);\n filter: drop-shadow(0 0 4px var(--frontend-stroke));\n }\n .intent-trace-flow[data-direction=\"in\"] {\n stroke: var(--database-stroke);\n filter: drop-shadow(0 0 4px var(--database-stroke));\n animation-direction: normal;\n }\n .intent-trace-flow[data-direction=\"loop\"] {\n stroke: var(--security-stroke);\n filter: drop-shadow(0 0 4px var(--security-stroke));\n }\n svg[data-preset=\"blueprint\"] [data-intent-trace-selected],\n svg[data-preset=\"blueprint\"] .intent-trace-flow {\n filter: none;\n stroke-linecap: square;\n }\n svg[data-preset=\"editorial\"] [data-intent-trace-selected],\n svg[data-preset=\"editorial\"] .intent-trace-flow { filter: none; }\n .intent-trace-status {\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n margin: -1px;\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n clip-path: inset(50%);\n white-space: nowrap;\n border: 0;\n }\n\n /* Route Probe separates a durable two-point query from selection and\n one-hop hover. Only the chosen shortest directed path stays prominent;\n the traveling signal is cloned viewer geometry and never authored SVG. */\n svg[data-route-picking=\"target\"] [data-node-id] { opacity: 0.24; }\n svg[data-route-picking=\"target\"] [data-route-candidate],\n svg[data-route-picking=\"target\"] [data-route-start] { opacity: 1; }\n svg[data-route-picking] [data-node-id] { cursor: crosshair; }\n svg[data-route-picking] [data-route-start] {\n filter: drop-shadow(0 0 10px var(--frontend-stroke));\n }\n svg[data-route-active] [data-node-id],\n svg[data-route-active] [data-edge-from] { opacity: 0.11; }\n svg[data-route-active] [data-graph-role=\"automatic-crossover\"] \u003e [data-graph-role=\"automatic-crossover-underlay\"] { opacity: 0.11; }\n svg[data-route-active] [data-route-match] { opacity: 1; }\n svg[data-route-active] [data-graph-role=\"automatic-crossover\"]:has(\u003e [data-route-match]) \u003e [data-graph-role=\"automatic-crossover-underlay\"] { opacity: 1; }\n svg[data-route-active] [data-route-start] {\n filter: drop-shadow(0 0 11px var(--frontend-stroke));\n }\n svg[data-route-active] [data-route-end] {\n filter: drop-shadow(0 0 11px var(--security-stroke));\n }\n svg[data-route-active] [data-route-step]:not([data-route-start]):not([data-route-end]) {\n filter: drop-shadow(0 0 7px var(--backend-stroke));\n }\n svg[data-route-journey] [data-route-match][data-route-journey-state=\"past\"] { opacity: 0.62; }\n svg[data-route-journey] [data-route-match][data-route-journey-state=\"future\"] { opacity: 0.34; }\n svg[data-route-journey] [data-route-match][data-route-journey-state=\"current\"] { opacity: 1; }\n svg[data-route-journey] [data-graph-role=\"automatic-crossover\"]:has(\u003e [data-route-match][data-route-journey-state=\"past\"]) \u003e [data-graph-role=\"automatic-crossover-underlay\"] { opacity: 0.62; }\n svg[data-route-journey] [data-graph-role=\"automatic-crossover\"]:has(\u003e [data-route-match][data-route-journey-state=\"future\"]) \u003e [data-graph-role=\"automatic-crossover-underlay\"] { opacity: 0.34; }\n svg[data-route-journey] [data-graph-role=\"automatic-crossover\"]:has(\u003e [data-route-match][data-route-journey-state=\"current\"]) \u003e [data-graph-role=\"automatic-crossover-underlay\"] { opacity: 1; }\n svg[data-route-journey] [data-node-id][data-route-journey-current] {\n filter: drop-shadow(0 0 10px var(--backend-stroke));\n }\n svg[data-route-journey] [data-edge-from][data-route-journey-current] {\n opacity: 1;\n filter: drop-shadow(0 0 6px var(--backend-stroke));\n }\n .route-probe-overlay { pointer-events: none; }\n .route-journey-overlay { pointer-events: none; }\n .route-probe-flow {\n fill: none;\n stroke: var(--backend-stroke);\n stroke-width: 3.25;\n stroke-linecap: round;\n stroke-dasharray: 0.085 0.915;\n stroke-dashoffset: 0;\n vector-effect: non-scaling-stroke;\n opacity: 0.96;\n filter: drop-shadow(0 0 5px var(--backend-stroke));\n animation: archify-route-probe-flow 1.1s cubic-bezier(0.22, 1, 0.36, 1) 1 both;\n animation-delay: calc(var(--route-step, 0) * 0.16s);\n }\n .route-journey-flow {\n fill: none;\n stroke: var(--backend-stroke);\n stroke-width: 3.5;\n stroke-linecap: round;\n stroke-dasharray: 0.1 0.9;\n stroke-dashoffset: 0;\n vector-effect: non-scaling-stroke;\n opacity: 0;\n filter: drop-shadow(0 0 6px var(--backend-stroke));\n animation: archify-route-journey-flow 780ms cubic-bezier(0.22, 1, 0.36, 1) 1 both;\n }\n svg[data-preset=\"blueprint\"] [data-route-start],\n svg[data-preset=\"blueprint\"] [data-route-end],\n svg[data-preset=\"blueprint\"] [data-route-step],\n svg[data-preset=\"blueprint\"] .route-probe-flow {\n filter: none;\n stroke-linecap: square;\n }\n svg[data-preset=\"blueprint\"] .route-journey-flow {\n filter: none;\n stroke-linecap: square;\n }\n svg[data-preset=\"editorial\"] [data-route-start],\n svg[data-preset=\"editorial\"] [data-route-end],\n svg[data-preset=\"editorial\"] [data-route-step],\n svg[data-preset=\"editorial\"] .route-probe-flow,\n svg[data-preset=\"editorial\"] .route-journey-flow { filter: none; }\n\n svg[data-preset=\"editorial\"] .c-grid {\n stroke-dasharray: 1 4;\n opacity: 0.48;\n }\n svg[data-preset=\"editorial\"] .c-region {\n fill: color-mix(in srgb, var(--cloud-fill) 48%, transparent);\n stroke-dasharray: 9 4;\n }\n svg[data-preset=\"editorial\"] [data-node-id]:hover,\n svg[data-preset=\"editorial\"] [data-node-id]:focus-visible,\n svg[data-preset=\"editorial\"][data-focus-active] [data-focus-selected] {\n filter: drop-shadow(0 2px 2px color-mix(in srgb, var(--text) 18%, transparent));\n }\n\n svg[data-preset=\"blueprint\"] .c-grid {\n stroke-dasharray: 1 3;\n opacity: 0.78;\n }\n svg[data-preset=\"blueprint\"] .c-region {\n fill: color-mix(in srgb, var(--cloud-fill) 58%, transparent);\n stroke-dasharray: 12 4 2 4;\n }\n svg[data-preset=\"blueprint\"] .c-security-group,\n svg[data-preset=\"blueprint\"] .c-lane { stroke-dasharray: 7 3; }\n svg[data-preset=\"blueprint\"] [data-node-id]:hover,\n svg[data-preset=\"blueprint\"] [data-node-id]:focus-visible {\n filter: drop-shadow(0 0 3px var(--frontend-stroke));\n }\n svg[data-preset=\"blueprint\"][data-focus-active] [data-focus-selected] {\n filter: drop-shadow(0 0 3px var(--frontend-stroke));\n }\n svg[data-preset=\"blueprint\"][data-focus-active][data-reach-active] [data-focus-selected][data-reach-origin] {\n filter: none;\n }\n svg[data-preset=\"blueprint\"][data-relationship-preview-active] [data-relationship-preview],\n svg[data-preset=\"blueprint\"][data-relationship-preview-active] [data-relationship-preview-source],\n svg[data-preset=\"blueprint\"][data-relationship-preview-active] [data-relationship-preview-target] {\n filter: drop-shadow(0 0 3px var(--frontend-stroke));\n }\n\n /* Dashed boundary variants */\n .c-security-group { fill: transparent; stroke: var(--security-stroke); stroke-dasharray: 4,4; }\n .c-region { fill: rgba(251, 191, 36, 0.05); stroke: var(--cloud-stroke); stroke-dasharray: 8,4; }\n .c-lane { fill: var(--lane-fill); stroke: var(--lane-stroke); stroke-dasharray: 6,6; }\n\n /* Live plays one relay per cycle: a comet (halo, tail, head) leaves each\n source when the signal reaches it, travels at one shared speed, leaves a\n soft wake, and lands as a ripple while its target glows. Downstream edges\n depart only after their source is reached, so the reader sees the path the\n diagram describes rather than every edge shimmering at once. Sequence\n messages relay in authored chronological order. Motion Governor plans the\n timeline and its per-edge keyframes; this stylesheet owns appearance.\n Original line language and markers remain static. Node entry is bounded to\n one pass per document. */\n svg[data-animation=\"trace\"] {\n --flow-cycle: 6.4s;\n --flow-halo-peak: 0.17;\n --flow-glow-peak: 0.5;\n --flow-tail-peak: 0.42;\n --flow-head-peak: 0.95;\n --flow-wake-peak: 0.3;\n --flow-ripple-peak: 0.65;\n }\n .ambient-edge-flow {\n fill: none;\n stroke: var(--arrow-emphasis);\n stroke-linecap: round;\n pointer-events: none;\n display: none;\n }\n .ambient-flow-wake {\n --flow-peak: var(--flow-wake-peak, 0.3);\n stroke-width: 3.2;\n stroke-dasharray: var(--flow-dasharray, none);\n }\n .ambient-flow-halo {\n --flow-peak: var(--flow-halo-peak, 0.17);\n stroke-width: 6;\n stroke-dasharray: var(--flow-dash, 12px) calc(var(--flow-len, 0px) - var(--flow-dash, 12px));\n }\n /* A wide blurred streak centered on the head: this is the bloom that makes\n the comet read as a traveling orb of light rather than a bright dash. */\n .ambient-flow-glow {\n --flow-peak: var(--flow-glow-peak, 0.5);\n stroke-width: 9;\n stroke-dasharray: var(--flow-dash, 12px) calc(var(--flow-len, 0px) - var(--flow-dash, 12px));\n filter: blur(2.5px);\n }\n .ambient-flow-tail {\n --flow-peak: var(--flow-tail-peak, 0.42);\n stroke-width: 2.4;\n stroke-dasharray: var(--flow-dasharray, var(--flow-dash, 20px) calc(var(--flow-len, 0px) - var(--flow-dash, 20px)));\n }\n .ambient-flow-head {\n --flow-peak: var(--flow-head-peak, 0.95);\n stroke: color-mix(in srgb, var(--arrow-emphasis) 40%, white);\n stroke-width: 2.6;\n stroke-dasharray: var(--flow-dash, 6px) calc(var(--flow-len, 0px) - var(--flow-dash, 6px));\n }\n .ambient-flow-ripple {\n --flow-peak: var(--flow-ripple-peak, 0.65);\n stroke: color-mix(in srgb, var(--arrow-emphasis) 55%, white);\n stroke-width: 2;\n }\n html[data-ambient-motion=\"running\"][data-motion=\"live\"]:not([data-motion-owner]):not([data-embed]):not([data-share-playback]):not([data-document-hidden]) .ambient-edge-flow {\n display: inline;\n animation-name: var(--flow-name, none);\n animation-duration: var(--flow-cycle, 6.4s);\n animation-timing-function: linear;\n animation-iteration-count: infinite;\n animation-delay: var(--flow-delay, 0s);\n /* Without backwards fill a delayed layer would sit at its natural opacity\n (fully lit) until its departure arrives. */\n animation-fill-mode: backwards;\n }\n /* Stream packets trail the comet head as smaller, dimmer beads. */\n .ambient-flow-head.packet {\n --flow-peak: calc(var(--flow-head-peak, 0.95) * 0.62);\n stroke-width: 3.4;\n }\n /* Timeline and waterfall playhead: a crisp rule over a soft bloom. */\n .ambient-flow-playhead {\n --flow-peak: 0.9;\n stroke: color-mix(in srgb, var(--arrow-emphasis) 55%, white);\n stroke-width: 1.6;\n }\n .ambient-flow-playhead-glow {\n --flow-peak: 0.32;\n stroke-width: 10;\n filter: blur(4px);\n }\n /* Waterfall spans fill in proportion to their measured time, then settle. */\n .ambient-flow-fill {\n --flow-peak: 0.5;\n fill: color-mix(in srgb, var(--flow-fill, var(--arrow-emphasis)) 55%, white);\n stroke: none;\n transform-box: fill-box;\n transform-origin: left center;\n }\n .ambient-flow-fill-failed { fill: var(--security-stroke); --flow-peak: 0.5; }\n html[data-theme=\"light\"] .ambient-flow-playhead { stroke: color-mix(in srgb, var(--arrow-emphasis) 70%, black); }\n html[data-theme=\"light\"] .ambient-flow-fill { --flow-peak: 0.42; fill: color-mix(in srgb, var(--flow-fill, var(--arrow-emphasis)) 70%, black); }\n svg[data-preset=\"editorial\"] .ambient-flow-fill { --flow-peak: 0.2; }\n svg[data-preset=\"editorial\"] .ambient-flow-playhead-glow { --flow-peak: 0.14; }\n /* Each decorative flow follows its owning edge through tree visibility. */\n .ambient-edge-flow[data-tree-hidden] { display: none !important; animation: none !important; }\n svg[data-preset=\"blueprint\"] .ambient-edge-flow { stroke-linecap: square; }\n svg[data-preset=\"editorial\"] { --flow-halo-peak: 0.07; --flow-glow-peak: 0.28; --flow-tail-peak: 0.2; --flow-head-peak: 0.7; --flow-wake-peak: 0.14; --flow-ripple-peak: 0.3; }\n /* On light canvases the relay reads by saturation instead of bloom: the head\n and ripple mix toward ink instead of white, and every layer's peak rises so\n the wave survives the bright background. */\n html[data-theme=\"light\"] svg[data-animation=\"trace\"] {\n --flow-halo-peak: 0.22;\n --flow-glow-peak: 0.4;\n --flow-tail-peak: 0.5;\n --flow-wake-peak: 0.34;\n }\n html[data-theme=\"light\"] .ambient-flow-head {\n stroke: color-mix(in srgb, var(--arrow-emphasis) 55%, black);\n stroke-width: 2.8;\n }\n html[data-theme=\"light\"] .ambient-flow-ripple {\n stroke: color-mix(in srgb, var(--arrow-emphasis) 65%, black);\n }\n html[data-ambient-entry=\"running\"] svg[data-animation=\"trace\"] [data-animate=\"node\"] {\n transform-box: fill-box;\n transform-origin: center;\n animation: archify-node-pulse 3.6s ease-in-out 1 calc(var(--step, 0) * 160ms),\n archify-node-enter 0.9s cubic-bezier(0.22, 1, 0.36, 1) backwards calc(var(--step, 0) * 160ms);\n }\n html[data-ambient-entry=\"running\"] svg[data-preset=\"signal-flow\"][data-animation=\"trace\"] [data-animate=\"node\"] {\n animation: archify-node-pulse 3.1s ease-in-out 1 calc(var(--step, 0) * 160ms),\n archify-node-enter 0.9s cubic-bezier(0.22, 1, 0.36, 1) backwards calc(var(--step, 0) * 160ms);\n }\n html[data-ambient-entry=\"running\"] svg[data-preset=\"blueprint\"][data-animation=\"trace\"] [data-animate=\"node\"] {\n animation: archify-blueprint-node-pulse 3.8s ease-in-out 1 calc(var(--step, 0) * 160ms),\n archify-node-enter 0.9s cubic-bezier(0.22, 1, 0.36, 1) backwards calc(var(--step, 0) * 160ms);\n }\n html[data-ambient-entry=\"running\"] svg[data-preset=\"editorial\"][data-animation=\"trace\"] [data-animate=\"node\"] {\n animation: archify-editorial-node-pulse 3.5s ease-in-out 1 calc(var(--step, 0) * 160ms),\n archify-node-enter 0.9s cubic-bezier(0.22, 1, 0.36, 1) backwards calc(var(--step, 0) * 160ms);\n }\n /* Each node glows in its planned windows: when the relay reaches it, while a\n walk token rests on it, as a message lands, or as the playhead passes.\n During the bounded entrance all three animations run; the entrance pulse is\n listed after the repeating glow and wins on filter until it completes. */\n html[data-ambient-motion=\"running\"][data-motion=\"live\"]:not([data-motion-owner]):not([data-embed]):not([data-share-playback]):not([data-document-hidden]) svg[data-animation=\"trace\"] [data-animate=\"node\"] {\n animation: var(--flow-node-name, none) var(--flow-cycle, 6.4s) linear infinite;\n }\n html[data-ambient-motion=\"running\"][data-motion=\"live\"]:not([data-motion-owner]):not([data-embed]):not([data-share-playback]):not([data-document-hidden])[data-ambient-entry=\"running\"] svg[data-animation=\"trace\"] [data-animate=\"node\"] {\n animation: var(--flow-node-name, none) var(--flow-cycle, 6.4s) linear infinite,\n archify-node-pulse 3.6s ease-in-out 1 calc(var(--step, 0) * 160ms),\n archify-node-enter 0.9s cubic-bezier(0.22, 1, 0.36, 1) backwards calc(var(--step, 0) * 160ms);\n }\n html[data-ambient-motion=\"running\"][data-motion=\"live\"]:not([data-motion-owner]):not([data-embed]):not([data-share-playback]):not([data-document-hidden])[data-ambient-entry=\"running\"] svg[data-preset=\"signal-flow\"][data-animation=\"trace\"] [data-animate=\"node\"] {\n animation: var(--flow-node-name, none) var(--flow-cycle, 6.4s) linear infinite,\n archify-node-pulse 3.1s ease-in-out 1 calc(var(--step, 0) * 160ms),\n archify-node-enter 0.9s cubic-bezier(0.22, 1, 0.36, 1) backwards calc(var(--step, 0) * 160ms);\n }\n html[data-ambient-motion=\"running\"][data-motion=\"live\"]:not([data-motion-owner]):not([data-embed]):not([data-share-playback]):not([data-document-hidden])[data-ambient-entry=\"running\"] svg[data-preset=\"blueprint\"][data-animation=\"trace\"] [data-animate=\"node\"] {\n animation: var(--flow-node-name, none) var(--flow-cycle, 6.4s) linear infinite,\n archify-blueprint-node-pulse 3.8s ease-in-out 1 calc(var(--step, 0) * 160ms),\n archify-node-enter 0.9s cubic-bezier(0.22, 1, 0.36, 1) backwards calc(var(--step, 0) * 160ms);\n }\n html[data-ambient-motion=\"running\"][data-motion=\"live\"]:not([data-motion-owner]):not([data-embed]):not([data-share-playback]):not([data-document-hidden])[data-ambient-entry=\"running\"] svg[data-preset=\"editorial\"][data-animation=\"trace\"] [data-animate=\"node\"] {\n animation: var(--flow-node-name, none) var(--flow-cycle, 6.4s) linear infinite,\n archify-editorial-node-pulse 3.5s ease-in-out 1 calc(var(--step, 0) * 160ms),\n archify-node-enter 0.9s cubic-bezier(0.22, 1, 0.36, 1) backwards calc(var(--step, 0) * 160ms);\n }\n @keyframes archify-node-enter {\n from { opacity: 0; }\n to { opacity: 1; }\n }\n @media print {\n .ambient-edge-flow { display: none !important; animation: none !important; }\n svg[data-animation=\"trace\"] [data-animate=\"node\"] { animation: none !important; }\n }\n @keyframes archify-node-pulse {\n 0%, 72%, 100% { filter: none; stroke-width: 1.5; }\n 18%, 36% {\n filter: drop-shadow(0 0 8px var(--arrow-emphasis));\n stroke-width: 2.4;\n }\n }\n @keyframes archify-blueprint-node-pulse {\n 0%, 72%, 100% { opacity: 1; filter: none; }\n 18%, 36% { opacity: 0.72; filter: drop-shadow(0 0 2px var(--frontend-stroke)); }\n }\n @keyframes archify-editorial-node-pulse {\n 0%, 72%, 100% { opacity: 1; filter: none; }\n 18%, 36% { opacity: 0.78; filter: drop-shadow(0 2px 2px color-mix(in srgb, var(--text) 18%, transparent)); }\n }\n @keyframes archify-signal-scan {\n 0%, 18% { opacity: 1; transform: translateX(-70%); }\n 70% { opacity: 1; transform: translateX(70%); }\n 100% { opacity: 0; transform: translateX(70%); }\n }\n @keyframes archify-radar-live {\n 0% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--backend-stroke) 52%, transparent); }\n 55%, 100% { box-shadow: 0 0 0 0.32rem transparent; }\n }\n\n @keyframes archify-intent-trace-flow {\n 0% { opacity: 0.94; stroke-dashoffset: 0; }\n 100% { opacity: 0.94; stroke-dashoffset: -1; }\n }\n @keyframes archify-route-probe-flow {\n 0% { stroke-dashoffset: 0; opacity: 0.96; }\n 78% { stroke-dashoffset: -1; opacity: 0.96; }\n 100% { stroke-dasharray: none; stroke-dashoffset: 0; opacity: 0.58; }\n }\n @keyframes archify-route-journey-flow {\n 0% { opacity: 0.16; stroke-dashoffset: 0; }\n 14%, 76% { opacity: 1; }\n 100% { opacity: 0; stroke-dashoffset: -1; }\n }\n @keyframes archify-semantic-lens-flow {\n 0% { opacity: 0.2; stroke-dashoffset: 0; }\n 12%, 78% { opacity: 0.9; }\n 100% { opacity: 0; stroke-dashoffset: -1; }\n }\n @keyframes archify-relationship-pulse {\n 0% { opacity: 0.18; stroke-dashoffset: 0; }\n 10%, 76% { opacity: 0.98; }\n 100% { opacity: 0; stroke-dashoffset: -1; }\n }\n @keyframes archify-relationship-token-life {\n 0%, 7% { opacity: 0; }\n 13%, 82% { opacity: 1; }\n 100% { opacity: 0; }\n }\n\n @keyframes archify-share-cue-enter {\n from { opacity: 0; transform: translate(-50%, -0.45rem); }\n to { opacity: 1; transform: translate(-50%, 0); }\n }\n @keyframes archify-share-cue-pulse {\n 0%, 100% { opacity: 1; transform: scale(1); }\n 50% { opacity: 0.48; transform: scale(0.78); }\n }\n @media (prefers-reduced-motion: reduce) {\n .ambient-edge-flow { display: none !important; animation: none !important; }\n html[data-preset=\"signal-flow\"] .diagram-container::before {\n animation: none !important;\n }\n .pulse-dot { animation: none !important; }\n .motion-control-dot { animation: none !important; }\n svg[data-animation=\"trace\"] [data-animate] {\n animation: none !important;\n stroke-dashoffset: 0;\n }\n svg[data-animation=\"trace\"] [data-graph-role=\"automatic-crossover\"]:has(\u003e [data-animate=\"edge\"]) \u003e [data-graph-role=\"automatic-crossover-underlay\"] {\n animation: none !important;\n stroke-dashoffset: 0;\n }\n .intent-trace-flow,\n .route-probe-flow,\n .semantic-lens,\n .diagram-guide,\n .overview-map-live { animation: none !important; }\n \n .intent-trace-flow {\n animation: none !important;\n stroke-dasharray: none;\n stroke-dashoffset: 0;\n opacity: 0.72;\n }\n .route-probe-flow {\n animation: none !important;\n stroke-dasharray: none;\n stroke-dashoffset: 0;\n opacity: 0.78;\n }\n .route-journey-overlay { display: none !important; }\n .semantic-lens-flow {\n animation: none !important;\n stroke-dasharray: none;\n stroke-width: 2.2;\n opacity: 0.34;\n filter: none;\n }\n .relationship-pulse-overlay { display: none !important; }\n svg [data-node-id], svg [data-edge-from], svg [data-detail], svg [data-detail-anchor], svg [data-legend-hit], svg { transition: none !important; }\n }\n\n /* Authored explanation face. It overlays the passport's own footprint so\n the placement beside the focused node is untouched; the passport may\n grow within the visible stage while its prose scrolls independently. */\n .focus-chip[data-explaining=\"true\"] \u003e :not(.semantic-passport-explanation) { visibility: hidden; }\n .semantic-passport-explanation {\n position: absolute;\n inset: 0;\n display: none;\n flex-direction: column;\n gap: 0.35rem;\n padding: 0.7rem 0.75rem;\n overflow: hidden;\n scrollbar-width: thin;\n color: var(--text);\n }\n .focus-chip[data-explaining=\"true\"] .semantic-passport-explanation {\n display: flex;\n animation: passport-explanation-in 240ms cubic-bezier(0.16, 1, 0.3, 1);\n }\n .semantic-passport-explanation \u003e :not(.semantic-passport-explanation-body) { flex: none; }\n .semantic-passport-explanation-eyebrow {\n display: block;\n color: var(--reader-accent, var(--text));\n font-size: 0.5rem;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n }\n .semantic-passport-explanation-title { display: block; font-size: 0.8rem; line-height: 1.35; }\n .semantic-passport-explanation-body { display: grid; align-content: start; flex: 1 1 auto; min-height: 0; gap: 0.35rem; overflow: auto; overscroll-behavior: contain; scrollbar-width: thin; }\n .semantic-passport-explanation-body:focus-visible { outline: 2px solid var(--reader-accent, var(--text)); outline-offset: 2px; }\n .semantic-passport-explanation-body p { margin: 0; color: var(--text-muted); font-size: 0.6875rem; line-height: 1.5; overflow-wrap: anywhere; }\n .semantic-passport-explanation-back {\n align-self: flex-start;\n margin-top: auto;\n padding: 0.15rem 0;\n border: 0;\n background: none;\n color: var(--reader-accent, var(--text));\n font: inherit;\n font-size: 0.625rem;\n text-decoration: underline;\n cursor: pointer;\n }\n .semantic-passport-explanation-back:hover { color: var(--text); }\n .semantic-passport-explanation-back:focus-visible { outline: 2px solid var(--frontend-stroke); outline-offset: 2px; }\n @keyframes passport-explanation-in {\n from { transform: rotateY(-90deg); opacity: 0; }\n to { transform: rotateY(0); opacity: 1; }\n }\n @media (prefers-reduced-motion: reduce) {\n .focus-chip[data-explaining=\"true\"] .semantic-passport-explanation { animation: none; }\n }\n\n /* A short mobile viewport cannot fit the Passport below the page header.\n Keep its controls in the viewport without shrinking the drawing or prose. */\n @media (max-width: 720px) and (max-height: 480px) {\n .focus-chip {\n position: fixed;\n left: 8px !important;\n top: var(--passport-viewport-top, 8px) !important;\n width: min(22rem, calc(100vw - 16px));\n max-width: calc(100vw - 16px);\n max-height: calc(100vh - var(--passport-viewport-top, 8px) - 8px);\n overflow-y: auto;\n }\n .focus-chip[data-explaining=\"true\"] { overflow: hidden; }\n }\n ";
let activeIndex = -1;
let playbackToken = 0;
let playbackTimer = 0;
diff --git a/examples/checkout-platform-delta.receipt.json b/examples/checkout-platform-delta.receipt.json
index cd55ce333..abcd78ae9 100644
--- a/examples/checkout-platform-delta.receipt.json
+++ b/examples/checkout-platform-delta.receipt.json
@@ -219,8 +219,8 @@
"Boundary identity is conservatively derived from kind + label."
],
"artifact": {
- "sha256": "089761b59f8215901ebb2dbb46e82f9bf8ecce0458160c5eec43374e915882f8",
- "bytes": 2429667
+ "sha256": "f3b93fec12e1bca3e4523e94fa3d82a3236f43b43fb2cc2efc09d0e57b66610b",
+ "bytes": 2448648
},
"validation": {
"checksPassed": 28,
diff --git a/examples/class-payment-processors-rendered.html b/examples/class-payment-processors-rendered.html
index 96e5fed30..e59b00418 100644
--- a/examples/class-payment-processors-rendered.html
+++ b/examples/class-payment-processors-rendered.html
@@ -2078,6 +2078,55 @@
color: var(--text-faint);
font-size: 0.5625rem;
}
+ /* Authored task checklist: read-only, collapsed to one counted line. */
+ .semantic-passport-task-checklist {
+ margin-top: 0.52rem;
+ padding-top: 0.48rem;
+ border-top: 1px solid color-mix(in srgb, var(--toolbar-border) 76%, transparent);
+ }
+ .semantic-passport-task-checklist > summary { cursor: pointer; }
+ .semantic-passport-task-checklist-count {
+ color: var(--text-muted);
+ font-size: 0.5625rem;
+ }
+ .semantic-passport-task-checklist-tasks {
+ display: grid;
+ gap: 0.32rem;
+ max-height: min(16rem, 40vh);
+ margin: 0.45rem 0 0;
+ padding: 0.15rem;
+ overflow: auto;
+ overscroll-behavior: contain;
+ scrollbar-width: thin;
+ scrollbar-color: var(--frontend-stroke) transparent;
+ list-style: none;
+ }
+ .semantic-passport-task-checklist-task {
+ display: grid;
+ grid-template-columns: 0.9rem minmax(0, 1fr);
+ gap: 0.12rem 0.3rem;
+ }
+ .semantic-passport-task-checklist-mark {
+ color: var(--text-faint);
+ font-size: 0.625rem;
+ font-weight: 800;
+ text-align: center;
+ }
+ .semantic-passport-task-checklist-task[data-status="implemented"] .semantic-passport-task-checklist-mark { color: var(--frontend-stroke); }
+ .semantic-passport-task-checklist-task[data-status="needs-review"] .semantic-passport-task-checklist-mark { color: var(--text-muted); }
+ .semantic-passport-task-checklist-task > strong {
+ color: var(--text-muted);
+ font-size: 0.6875rem;
+ overflow-wrap: anywhere;
+ }
+ .semantic-passport-task-checklist-task p,
+ .semantic-passport-task-checklist-task .semantic-passport-source { grid-column: 2; }
+ .semantic-passport-task-checklist-task p {
+ margin: 0;
+ color: var(--text-faint);
+ font-size: 0.5625rem;
+ overflow-wrap: anywhere;
+ }
.semantic-passport-reach {
display: grid;
gap: 0.28rem;
@@ -2086,7 +2135,8 @@
border-top: 1px solid color-mix(in srgb, var(--toolbar-border) 76%, transparent);
}
.semantic-passport-reach[hidden] { display: none; }
- .semantic-passport-reach-label {
+ .semantic-passport-reach-label,
+ .semantic-passport-task-checklist-label {
color: var(--text-faint);
font-size: 0.5rem;
font-weight: 800;
@@ -2154,8 +2204,10 @@
scrollbar-width: thin;
scrollbar-color: var(--frontend-stroke) transparent;
}
- .relationship-lens-list::-webkit-scrollbar { width: 4px; }
- .relationship-lens-list::-webkit-scrollbar-thumb {
+ .relationship-lens-list::-webkit-scrollbar,
+ .semantic-passport-task-checklist-tasks::-webkit-scrollbar { width: 4px; }
+ .relationship-lens-list::-webkit-scrollbar-thumb,
+ .semantic-passport-task-checklist-tasks::-webkit-scrollbar-thumb {
border-radius: 999px;
background: color-mix(in srgb, var(--frontend-stroke) 68%, transparent);
}
@@ -2657,8 +2709,9 @@
as are dark themes and presets that already pass. Zero counts stay faint
on purpose, so they are excluded from the darker rule. Route-target
finders keep their --security-stroke status color, so they are excluded
- too. */
+ too. Task checklist done marks are the same kind of text. */
html[data-theme="light"] .semantic-passport-meta [data-passport="kind"],
+ html[data-theme="light"] .semantic-passport-task-checklist-task[data-status="implemented"] .semantic-passport-task-checklist-mark,
html[data-theme="light"] .node-finder:not([data-context="route-target"]) .node-finder-result em:not([data-zero]) {
color: var(--reader-accent, var(--frontend-stroke));
}
@@ -5407,7 +5460,8 @@
Payment processor hierarchy
-
+
+
@@ -5899,6 +5953,10 @@
Payment processor hierarchy
Ctrl+click or double-click opens the first entry
+
+ Task checklist
+
+
Authored reach
@@ -8678,6 +8736,24 @@
Payment processor hierarchy
count: function () { return payload ? Object.keys(payload).length : 0; }
};
})();
+ /* Authored node task checklists: passport-only, like explanations. Their task
+ sources are verified and read through Archify.sourceEvidence.task(). */
+ Archify.taskChecklists = (function () {
+ var element = document.getElementById('archify-task-checklists-data');
+ var payload = null;
+ if (element) {
+ try {
+ var parsed = JSON.parse(element.textContent || 'null');
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) payload = parsed;
+ } catch (_) {}
+ }
+ return {
+ node: function (id) {
+ var tasks = payload && Object.prototype.hasOwnProperty.call(payload, id) ? payload[id] : null;
+ return Array.isArray(tasks) ? tasks.slice() : [];
+ }
+ };
+ })();
/* Verified repository evidence is emitted only after the renderer checks
the authored GitHub revision, source blobs, and optional line ranges
against the explicit --repo-root. It remains outside the SVG so every
@@ -8717,6 +8793,11 @@
Payment processor hierarchy
node: function (id) {
var sources = payload && payload.nodes ? payload.nodes[id] : null;
return Array.isArray(sources) ? sources.slice() : [];
+ },
+ task: function (id, index) {
+ var tasks = payload && payload.taskChecklists ? payload.taskChecklists[id] : null;
+ var sources = Array.isArray(tasks) ? tasks[index] : null;
+ return Array.isArray(sources) ? sources.slice() : [];
}
};
})();
@@ -8892,6 +8973,9 @@
Payment processor hierarchy
var parentBackBtn = document.getElementById('btn-parent-back');
var drilldownsSection = document.getElementById('focus-drilldowns');
var drilldownsList = document.getElementById('focus-drilldowns-list');
+ var taskChecklistSection = document.getElementById('focus-task-checklist');
+ var taskChecklistCount = document.getElementById('focus-task-checklist-count');
+ var taskChecklistTasks = document.getElementById('focus-task-checklist-tasks');
var moveBtn = document.getElementById('btn-focus-move');
var activeIds = [];
var hoveredRelationship = null;
@@ -9213,30 +9297,71 @@
Payment processor hierarchy
repositoryLink.setAttribute('aria-label', viewerText('viewer.passport.repository.open', { revision: repository.revision }));
}
evidence.title = viewerText('viewer.passport.verificationScope');
- sources.forEach(function (source) {
- var link = document.createElement(source.href ? 'a' : 'div');
- link.className = 'semantic-passport-source';
- if (source.href) {
- link.href = source.href;
- link.target = '_blank';
- link.rel = 'noopener noreferrer';
- link.referrerPolicy = 'no-referrer';
- link.setAttribute('aria-label', viewerText('viewer.passport.source.open', { path: source.path, revision: repository.shortRevision }));
+ sources.forEach(function (source) { evidenceLinks.appendChild(sourceLink(source, repository)); });
+ evidence.hidden = false;
+ }
+ function sourceLink(source, repository) {
+ var link = document.createElement(source.href ? 'a' : 'div');
+ link.className = 'semantic-passport-source';
+ if (source.href) {
+ link.href = source.href;
+ link.target = '_blank';
+ link.rel = 'noopener noreferrer';
+ link.referrerPolicy = 'no-referrer';
+ link.setAttribute('aria-label', viewerText('viewer.passport.source.open', { path: source.path, revision: repository.shortRevision }));
+ }
+ var name = document.createElement('strong');
+ name.textContent = source.label || source.path.split('/').pop() || source.path;
+ var location = document.createElement('code');
+ location.textContent = source.line
+ ? 'L' + source.line + (source.endLine && source.endLine !== source.line ? '–' + source.endLine : '') + (source.href ? ' ↗' : '')
+ : source.href ? viewerText('viewer.passport.source.openLink') : '';
+ var sourcePath = document.createElement('small');
+ sourcePath.textContent = source.path;
+ link.appendChild(name);
+ link.appendChild(location);
+ link.appendChild(sourcePath);
+ return link;
+ }
+ var TASK_CHECKLIST_STATUS = {
+ implemented: ['✓', 'viewer.passport.taskChecklist.status.implemented'],
+ 'needs-review': ['?', 'viewer.passport.taskChecklist.status.needsReview'],
+ pending: ['○', 'viewer.passport.taskChecklist.status.pending']
+ };
+ // Read-only: authored statuses and verified task sources, never edited here.
+ function renderTaskChecklist(id) {
+ if (!taskChecklistSection) return;
+ taskChecklistSection.open = false;
+ taskChecklistTasks.textContent = '';
+ var tasks = Archify.taskChecklists ? Archify.taskChecklists.node(id) : [];
+ var repository = Archify.sourceEvidence.repository();
+ taskChecklistSection.hidden = !tasks.length;
+ tasks.forEach(function (task, index) {
+ var item = document.createElement('li');
+ item.className = 'semantic-passport-task-checklist-task';
+ item.setAttribute('data-status', task.status);
+ var status = TASK_CHECKLIST_STATUS[task.status] || TASK_CHECKLIST_STATUS.pending;
+ var mark = document.createElement('span');
+ mark.className = 'semantic-passport-task-checklist-mark';
+ mark.setAttribute('role', 'img');
+ mark.setAttribute('aria-label', viewerText(status[1]));
+ mark.textContent = status[0];
+ var title = document.createElement('strong');
+ title.textContent = task.title;
+ item.appendChild(mark);
+ item.appendChild(title);
+ if (task.criteria) {
+ var criteria = document.createElement('p');
+ criteria.textContent = task.criteria;
+ item.appendChild(criteria);
}
- var name = document.createElement('strong');
- name.textContent = source.label || source.path.split('/').pop() || source.path;
- var location = document.createElement('code');
- location.textContent = source.line
- ? 'L' + source.line + (source.endLine && source.endLine !== source.line ? '–' + source.endLine : '') + (source.href ? ' ↗' : '')
- : source.href ? viewerText('viewer.passport.source.openLink') : '';
- var sourcePath = document.createElement('small');
- sourcePath.textContent = source.path;
- link.appendChild(name);
- link.appendChild(location);
- link.appendChild(sourcePath);
- evidenceLinks.appendChild(link);
+ Archify.sourceEvidence.task(id, index).forEach(function (source) { item.appendChild(sourceLink(source, repository)); });
+ taskChecklistTasks.appendChild(item);
+ });
+ taskChecklistCount.textContent = viewerText('viewer.passport.taskChecklist.count', {
+ implemented: tasks.filter(function (task) { return task.status === 'implemented'; }).length,
+ total: tasks.length
});
- evidence.hidden = false;
}
function explanationFor(id) {
return Archify.explanations && typeof Archify.explanations.node === 'function' ? Archify.explanations.node(id) : null;
@@ -9319,6 +9444,7 @@
Payment processor hierarchy
semanticId.hidden = false;
renderDrilldowns(node);
renderSourceEvidence(id);
+ renderTaskChecklist(id);
}
/**
* 仅允许同目录文件名.html(可选 ?query / #fragment)。
@@ -10377,6 +10503,10 @@
Payment processor hierarchy
evidenceLinks.textContent = '';
repositoryLink.removeAttribute('href');
repositoryLink.textContent = '';
+ if (taskChecklistSection) {
+ taskChecklistSection.hidden = true;
+ taskChecklistTasks.textContent = '';
+ }
summary.textContent = '';
reachSection.hidden = true;
upstreamCount.textContent = '0';
@@ -10559,6 +10689,7 @@
Payment processor hierarchy
clearBtn.addEventListener('click', function () { clear({ restoreFocus: true }); });
copyBtn.addEventListener('click', copyFocusLink);
if (explainBtn) explainBtn.addEventListener('click', function (event) { event.stopPropagation(); toggleExplanation(); });
+ if (taskChecklistSection) taskChecklistSection.addEventListener('toggle', function () { if (!chip.hidden) requestLensPlacement(); });
if (explanationBack) {
explanationBack.addEventListener('click', function (event) { event.stopPropagation(); closeExplanation(); });
}
diff --git a/examples/class-payments-rendered.html b/examples/class-payments-rendered.html
index 806d48dc6..9634eb8e1 100644
--- a/examples/class-payments-rendered.html
+++ b/examples/class-payments-rendered.html
@@ -2078,6 +2078,55 @@
color: var(--text-faint);
font-size: 0.5625rem;
}
+ /* Authored task checklist: read-only, collapsed to one counted line. */
+ .semantic-passport-task-checklist {
+ margin-top: 0.52rem;
+ padding-top: 0.48rem;
+ border-top: 1px solid color-mix(in srgb, var(--toolbar-border) 76%, transparent);
+ }
+ .semantic-passport-task-checklist > summary { cursor: pointer; }
+ .semantic-passport-task-checklist-count {
+ color: var(--text-muted);
+ font-size: 0.5625rem;
+ }
+ .semantic-passport-task-checklist-tasks {
+ display: grid;
+ gap: 0.32rem;
+ max-height: min(16rem, 40vh);
+ margin: 0.45rem 0 0;
+ padding: 0.15rem;
+ overflow: auto;
+ overscroll-behavior: contain;
+ scrollbar-width: thin;
+ scrollbar-color: var(--frontend-stroke) transparent;
+ list-style: none;
+ }
+ .semantic-passport-task-checklist-task {
+ display: grid;
+ grid-template-columns: 0.9rem minmax(0, 1fr);
+ gap: 0.12rem 0.3rem;
+ }
+ .semantic-passport-task-checklist-mark {
+ color: var(--text-faint);
+ font-size: 0.625rem;
+ font-weight: 800;
+ text-align: center;
+ }
+ .semantic-passport-task-checklist-task[data-status="implemented"] .semantic-passport-task-checklist-mark { color: var(--frontend-stroke); }
+ .semantic-passport-task-checklist-task[data-status="needs-review"] .semantic-passport-task-checklist-mark { color: var(--text-muted); }
+ .semantic-passport-task-checklist-task > strong {
+ color: var(--text-muted);
+ font-size: 0.6875rem;
+ overflow-wrap: anywhere;
+ }
+ .semantic-passport-task-checklist-task p,
+ .semantic-passport-task-checklist-task .semantic-passport-source { grid-column: 2; }
+ .semantic-passport-task-checklist-task p {
+ margin: 0;
+ color: var(--text-faint);
+ font-size: 0.5625rem;
+ overflow-wrap: anywhere;
+ }
.semantic-passport-reach {
display: grid;
gap: 0.28rem;
@@ -2086,7 +2135,8 @@
border-top: 1px solid color-mix(in srgb, var(--toolbar-border) 76%, transparent);
}
.semantic-passport-reach[hidden] { display: none; }
- .semantic-passport-reach-label {
+ .semantic-passport-reach-label,
+ .semantic-passport-task-checklist-label {
color: var(--text-faint);
font-size: 0.5rem;
font-weight: 800;
@@ -2154,8 +2204,10 @@
scrollbar-width: thin;
scrollbar-color: var(--frontend-stroke) transparent;
}
- .relationship-lens-list::-webkit-scrollbar { width: 4px; }
- .relationship-lens-list::-webkit-scrollbar-thumb {
+ .relationship-lens-list::-webkit-scrollbar,
+ .semantic-passport-task-checklist-tasks::-webkit-scrollbar { width: 4px; }
+ .relationship-lens-list::-webkit-scrollbar-thumb,
+ .semantic-passport-task-checklist-tasks::-webkit-scrollbar-thumb {
border-radius: 999px;
background: color-mix(in srgb, var(--frontend-stroke) 68%, transparent);
}
@@ -2657,8 +2709,9 @@
as are dark themes and presets that already pass. Zero counts stay faint
on purpose, so they are excluded from the darker rule. Route-target
finders keep their --security-stroke status color, so they are excluded
- too. */
+ too. Task checklist done marks are the same kind of text. */
html[data-theme="light"] .semantic-passport-meta [data-passport="kind"],
+ html[data-theme="light"] .semantic-passport-task-checklist-task[data-status="implemented"] .semantic-passport-task-checklist-mark,
html[data-theme="light"] .node-finder:not([data-context="route-target"]) .node-finder-result em:not([data-zero]) {
color: var(--reader-accent, var(--frontend-stroke));
}
@@ -5407,7 +5460,8 @@
Payment module types
-
+
+
@@ -5699,6 +5753,10 @@
Payment module types
Ctrl+click or double-click opens the first entry
+
+ Task checklist
+
+
Authored reach
@@ -8478,6 +8536,24 @@
Payment module types
count: function () { return payload ? Object.keys(payload).length : 0; }
};
})();
+ /* Authored node task checklists: passport-only, like explanations. Their task
+ sources are verified and read through Archify.sourceEvidence.task(). */
+ Archify.taskChecklists = (function () {
+ var element = document.getElementById('archify-task-checklists-data');
+ var payload = null;
+ if (element) {
+ try {
+ var parsed = JSON.parse(element.textContent || 'null');
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) payload = parsed;
+ } catch (_) {}
+ }
+ return {
+ node: function (id) {
+ var tasks = payload && Object.prototype.hasOwnProperty.call(payload, id) ? payload[id] : null;
+ return Array.isArray(tasks) ? tasks.slice() : [];
+ }
+ };
+ })();
/* Verified repository evidence is emitted only after the renderer checks
the authored GitHub revision, source blobs, and optional line ranges
against the explicit --repo-root. It remains outside the SVG so every
@@ -8517,6 +8593,11 @@
Payment module types
node: function (id) {
var sources = payload && payload.nodes ? payload.nodes[id] : null;
return Array.isArray(sources) ? sources.slice() : [];
+ },
+ task: function (id, index) {
+ var tasks = payload && payload.taskChecklists ? payload.taskChecklists[id] : null;
+ var sources = Array.isArray(tasks) ? tasks[index] : null;
+ return Array.isArray(sources) ? sources.slice() : [];
}
};
})();
@@ -8692,6 +8773,9 @@
Payment module types
var parentBackBtn = document.getElementById('btn-parent-back');
var drilldownsSection = document.getElementById('focus-drilldowns');
var drilldownsList = document.getElementById('focus-drilldowns-list');
+ var taskChecklistSection = document.getElementById('focus-task-checklist');
+ var taskChecklistCount = document.getElementById('focus-task-checklist-count');
+ var taskChecklistTasks = document.getElementById('focus-task-checklist-tasks');
var moveBtn = document.getElementById('btn-focus-move');
var activeIds = [];
var hoveredRelationship = null;
@@ -9013,30 +9097,71 @@
Payment module types
repositoryLink.setAttribute('aria-label', viewerText('viewer.passport.repository.open', { revision: repository.revision }));
}
evidence.title = viewerText('viewer.passport.verificationScope');
- sources.forEach(function (source) {
- var link = document.createElement(source.href ? 'a' : 'div');
- link.className = 'semantic-passport-source';
- if (source.href) {
- link.href = source.href;
- link.target = '_blank';
- link.rel = 'noopener noreferrer';
- link.referrerPolicy = 'no-referrer';
- link.setAttribute('aria-label', viewerText('viewer.passport.source.open', { path: source.path, revision: repository.shortRevision }));
+ sources.forEach(function (source) { evidenceLinks.appendChild(sourceLink(source, repository)); });
+ evidence.hidden = false;
+ }
+ function sourceLink(source, repository) {
+ var link = document.createElement(source.href ? 'a' : 'div');
+ link.className = 'semantic-passport-source';
+ if (source.href) {
+ link.href = source.href;
+ link.target = '_blank';
+ link.rel = 'noopener noreferrer';
+ link.referrerPolicy = 'no-referrer';
+ link.setAttribute('aria-label', viewerText('viewer.passport.source.open', { path: source.path, revision: repository.shortRevision }));
+ }
+ var name = document.createElement('strong');
+ name.textContent = source.label || source.path.split('/').pop() || source.path;
+ var location = document.createElement('code');
+ location.textContent = source.line
+ ? 'L' + source.line + (source.endLine && source.endLine !== source.line ? '–' + source.endLine : '') + (source.href ? ' ↗' : '')
+ : source.href ? viewerText('viewer.passport.source.openLink') : '';
+ var sourcePath = document.createElement('small');
+ sourcePath.textContent = source.path;
+ link.appendChild(name);
+ link.appendChild(location);
+ link.appendChild(sourcePath);
+ return link;
+ }
+ var TASK_CHECKLIST_STATUS = {
+ implemented: ['✓', 'viewer.passport.taskChecklist.status.implemented'],
+ 'needs-review': ['?', 'viewer.passport.taskChecklist.status.needsReview'],
+ pending: ['○', 'viewer.passport.taskChecklist.status.pending']
+ };
+ // Read-only: authored statuses and verified task sources, never edited here.
+ function renderTaskChecklist(id) {
+ if (!taskChecklistSection) return;
+ taskChecklistSection.open = false;
+ taskChecklistTasks.textContent = '';
+ var tasks = Archify.taskChecklists ? Archify.taskChecklists.node(id) : [];
+ var repository = Archify.sourceEvidence.repository();
+ taskChecklistSection.hidden = !tasks.length;
+ tasks.forEach(function (task, index) {
+ var item = document.createElement('li');
+ item.className = 'semantic-passport-task-checklist-task';
+ item.setAttribute('data-status', task.status);
+ var status = TASK_CHECKLIST_STATUS[task.status] || TASK_CHECKLIST_STATUS.pending;
+ var mark = document.createElement('span');
+ mark.className = 'semantic-passport-task-checklist-mark';
+ mark.setAttribute('role', 'img');
+ mark.setAttribute('aria-label', viewerText(status[1]));
+ mark.textContent = status[0];
+ var title = document.createElement('strong');
+ title.textContent = task.title;
+ item.appendChild(mark);
+ item.appendChild(title);
+ if (task.criteria) {
+ var criteria = document.createElement('p');
+ criteria.textContent = task.criteria;
+ item.appendChild(criteria);
}
- var name = document.createElement('strong');
- name.textContent = source.label || source.path.split('/').pop() || source.path;
- var location = document.createElement('code');
- location.textContent = source.line
- ? 'L' + source.line + (source.endLine && source.endLine !== source.line ? '–' + source.endLine : '') + (source.href ? ' ↗' : '')
- : source.href ? viewerText('viewer.passport.source.openLink') : '';
- var sourcePath = document.createElement('small');
- sourcePath.textContent = source.path;
- link.appendChild(name);
- link.appendChild(location);
- link.appendChild(sourcePath);
- evidenceLinks.appendChild(link);
+ Archify.sourceEvidence.task(id, index).forEach(function (source) { item.appendChild(sourceLink(source, repository)); });
+ taskChecklistTasks.appendChild(item);
+ });
+ taskChecklistCount.textContent = viewerText('viewer.passport.taskChecklist.count', {
+ implemented: tasks.filter(function (task) { return task.status === 'implemented'; }).length,
+ total: tasks.length
});
- evidence.hidden = false;
}
function explanationFor(id) {
return Archify.explanations && typeof Archify.explanations.node === 'function' ? Archify.explanations.node(id) : null;
@@ -9119,6 +9244,7 @@
Payment module types
semanticId.hidden = false;
renderDrilldowns(node);
renderSourceEvidence(id);
+ renderTaskChecklist(id);
}
/**
* 仅允许同目录文件名.html(可选 ?query / #fragment)。
@@ -10177,6 +10303,10 @@
Payment module types
evidenceLinks.textContent = '';
repositoryLink.removeAttribute('href');
repositoryLink.textContent = '';
+ if (taskChecklistSection) {
+ taskChecklistSection.hidden = true;
+ taskChecklistTasks.textContent = '';
+ }
summary.textContent = '';
reachSection.hidden = true;
upstreamCount.textContent = '0';
@@ -10359,6 +10489,7 @@
Payment module types
clearBtn.addEventListener('click', function () { clear({ restoreFocus: true }); });
copyBtn.addEventListener('click', copyFocusLink);
if (explainBtn) explainBtn.addEventListener('click', function (event) { event.stopPropagation(); toggleExplanation(); });
+ if (taskChecklistSection) taskChecklistSection.addEventListener('toggle', function () { if (!chip.hidden) requestLensPlacement(); });
if (explanationBack) {
explanationBack.addEventListener('click', function (event) { event.stopPropagation(); closeExplanation(); });
}
diff --git a/examples/dataflow-product-analytics.html b/examples/dataflow-product-analytics.html
index a256fce9f..8f59f94fb 100644
--- a/examples/dataflow-product-analytics.html
+++ b/examples/dataflow-product-analytics.html
@@ -2078,6 +2078,55 @@
color: var(--text-faint);
font-size: 0.5625rem;
}
+ /* Authored task checklist: read-only, collapsed to one counted line. */
+ .semantic-passport-task-checklist {
+ margin-top: 0.52rem;
+ padding-top: 0.48rem;
+ border-top: 1px solid color-mix(in srgb, var(--toolbar-border) 76%, transparent);
+ }
+ .semantic-passport-task-checklist > summary { cursor: pointer; }
+ .semantic-passport-task-checklist-count {
+ color: var(--text-muted);
+ font-size: 0.5625rem;
+ }
+ .semantic-passport-task-checklist-tasks {
+ display: grid;
+ gap: 0.32rem;
+ max-height: min(16rem, 40vh);
+ margin: 0.45rem 0 0;
+ padding: 0.15rem;
+ overflow: auto;
+ overscroll-behavior: contain;
+ scrollbar-width: thin;
+ scrollbar-color: var(--frontend-stroke) transparent;
+ list-style: none;
+ }
+ .semantic-passport-task-checklist-task {
+ display: grid;
+ grid-template-columns: 0.9rem minmax(0, 1fr);
+ gap: 0.12rem 0.3rem;
+ }
+ .semantic-passport-task-checklist-mark {
+ color: var(--text-faint);
+ font-size: 0.625rem;
+ font-weight: 800;
+ text-align: center;
+ }
+ .semantic-passport-task-checklist-task[data-status="implemented"] .semantic-passport-task-checklist-mark { color: var(--frontend-stroke); }
+ .semantic-passport-task-checklist-task[data-status="needs-review"] .semantic-passport-task-checklist-mark { color: var(--text-muted); }
+ .semantic-passport-task-checklist-task > strong {
+ color: var(--text-muted);
+ font-size: 0.6875rem;
+ overflow-wrap: anywhere;
+ }
+ .semantic-passport-task-checklist-task p,
+ .semantic-passport-task-checklist-task .semantic-passport-source { grid-column: 2; }
+ .semantic-passport-task-checklist-task p {
+ margin: 0;
+ color: var(--text-faint);
+ font-size: 0.5625rem;
+ overflow-wrap: anywhere;
+ }
.semantic-passport-reach {
display: grid;
gap: 0.28rem;
@@ -2086,7 +2135,8 @@
border-top: 1px solid color-mix(in srgb, var(--toolbar-border) 76%, transparent);
}
.semantic-passport-reach[hidden] { display: none; }
- .semantic-passport-reach-label {
+ .semantic-passport-reach-label,
+ .semantic-passport-task-checklist-label {
color: var(--text-faint);
font-size: 0.5rem;
font-weight: 800;
@@ -2154,8 +2204,10 @@
scrollbar-width: thin;
scrollbar-color: var(--frontend-stroke) transparent;
}
- .relationship-lens-list::-webkit-scrollbar { width: 4px; }
- .relationship-lens-list::-webkit-scrollbar-thumb {
+ .relationship-lens-list::-webkit-scrollbar,
+ .semantic-passport-task-checklist-tasks::-webkit-scrollbar { width: 4px; }
+ .relationship-lens-list::-webkit-scrollbar-thumb,
+ .semantic-passport-task-checklist-tasks::-webkit-scrollbar-thumb {
border-radius: 999px;
background: color-mix(in srgb, var(--frontend-stroke) 68%, transparent);
}
@@ -2657,8 +2709,9 @@
as are dark themes and presets that already pass. Zero counts stay faint
on purpose, so they are excluded from the darker rule. Route-target
finders keep their --security-stroke status color, so they are excluded
- too. */
+ too. Task checklist done marks are the same kind of text. */
html[data-theme="light"] .semantic-passport-meta [data-passport="kind"],
+ html[data-theme="light"] .semantic-passport-task-checklist-task[data-status="implemented"] .semantic-passport-task-checklist-mark,
html[data-theme="light"] .node-finder:not([data-context="route-target"]) .node-finder-result em:not([data-zero]) {
color: var(--reader-accent, var(--frontend-stroke));
}
@@ -5406,7 +5459,8 @@
Product Analytics Data Flow
-
+
+
@@ -5758,6 +5812,10 @@
Product Analytics Data Flow
Ctrl+click or double-click opens the first entry
+
+ Task checklist
+
+
Authored reach
@@ -8571,6 +8629,24 @@
Derived Consumers
count: function () { return payload ? Object.keys(payload).length : 0; }
};
})();
+ /* Authored node task checklists: passport-only, like explanations. Their task
+ sources are verified and read through Archify.sourceEvidence.task(). */
+ Archify.taskChecklists = (function () {
+ var element = document.getElementById('archify-task-checklists-data');
+ var payload = null;
+ if (element) {
+ try {
+ var parsed = JSON.parse(element.textContent || 'null');
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) payload = parsed;
+ } catch (_) {}
+ }
+ return {
+ node: function (id) {
+ var tasks = payload && Object.prototype.hasOwnProperty.call(payload, id) ? payload[id] : null;
+ return Array.isArray(tasks) ? tasks.slice() : [];
+ }
+ };
+ })();
/* Verified repository evidence is emitted only after the renderer checks
the authored GitHub revision, source blobs, and optional line ranges
against the explicit --repo-root. It remains outside the SVG so every
@@ -8610,6 +8686,11 @@
Derived Consumers
node: function (id) {
var sources = payload && payload.nodes ? payload.nodes[id] : null;
return Array.isArray(sources) ? sources.slice() : [];
+ },
+ task: function (id, index) {
+ var tasks = payload && payload.taskChecklists ? payload.taskChecklists[id] : null;
+ var sources = Array.isArray(tasks) ? tasks[index] : null;
+ return Array.isArray(sources) ? sources.slice() : [];
}
};
})();
@@ -8785,6 +8866,9 @@
Derived Consumers
var parentBackBtn = document.getElementById('btn-parent-back');
var drilldownsSection = document.getElementById('focus-drilldowns');
var drilldownsList = document.getElementById('focus-drilldowns-list');
+ var taskChecklistSection = document.getElementById('focus-task-checklist');
+ var taskChecklistCount = document.getElementById('focus-task-checklist-count');
+ var taskChecklistTasks = document.getElementById('focus-task-checklist-tasks');
var moveBtn = document.getElementById('btn-focus-move');
var activeIds = [];
var hoveredRelationship = null;
@@ -9106,30 +9190,71 @@
Derived Consumers
repositoryLink.setAttribute('aria-label', viewerText('viewer.passport.repository.open', { revision: repository.revision }));
}
evidence.title = viewerText('viewer.passport.verificationScope');
- sources.forEach(function (source) {
- var link = document.createElement(source.href ? 'a' : 'div');
- link.className = 'semantic-passport-source';
- if (source.href) {
- link.href = source.href;
- link.target = '_blank';
- link.rel = 'noopener noreferrer';
- link.referrerPolicy = 'no-referrer';
- link.setAttribute('aria-label', viewerText('viewer.passport.source.open', { path: source.path, revision: repository.shortRevision }));
+ sources.forEach(function (source) { evidenceLinks.appendChild(sourceLink(source, repository)); });
+ evidence.hidden = false;
+ }
+ function sourceLink(source, repository) {
+ var link = document.createElement(source.href ? 'a' : 'div');
+ link.className = 'semantic-passport-source';
+ if (source.href) {
+ link.href = source.href;
+ link.target = '_blank';
+ link.rel = 'noopener noreferrer';
+ link.referrerPolicy = 'no-referrer';
+ link.setAttribute('aria-label', viewerText('viewer.passport.source.open', { path: source.path, revision: repository.shortRevision }));
+ }
+ var name = document.createElement('strong');
+ name.textContent = source.label || source.path.split('/').pop() || source.path;
+ var location = document.createElement('code');
+ location.textContent = source.line
+ ? 'L' + source.line + (source.endLine && source.endLine !== source.line ? '–' + source.endLine : '') + (source.href ? ' ↗' : '')
+ : source.href ? viewerText('viewer.passport.source.openLink') : '';
+ var sourcePath = document.createElement('small');
+ sourcePath.textContent = source.path;
+ link.appendChild(name);
+ link.appendChild(location);
+ link.appendChild(sourcePath);
+ return link;
+ }
+ var TASK_CHECKLIST_STATUS = {
+ implemented: ['✓', 'viewer.passport.taskChecklist.status.implemented'],
+ 'needs-review': ['?', 'viewer.passport.taskChecklist.status.needsReview'],
+ pending: ['○', 'viewer.passport.taskChecklist.status.pending']
+ };
+ // Read-only: authored statuses and verified task sources, never edited here.
+ function renderTaskChecklist(id) {
+ if (!taskChecklistSection) return;
+ taskChecklistSection.open = false;
+ taskChecklistTasks.textContent = '';
+ var tasks = Archify.taskChecklists ? Archify.taskChecklists.node(id) : [];
+ var repository = Archify.sourceEvidence.repository();
+ taskChecklistSection.hidden = !tasks.length;
+ tasks.forEach(function (task, index) {
+ var item = document.createElement('li');
+ item.className = 'semantic-passport-task-checklist-task';
+ item.setAttribute('data-status', task.status);
+ var status = TASK_CHECKLIST_STATUS[task.status] || TASK_CHECKLIST_STATUS.pending;
+ var mark = document.createElement('span');
+ mark.className = 'semantic-passport-task-checklist-mark';
+ mark.setAttribute('role', 'img');
+ mark.setAttribute('aria-label', viewerText(status[1]));
+ mark.textContent = status[0];
+ var title = document.createElement('strong');
+ title.textContent = task.title;
+ item.appendChild(mark);
+ item.appendChild(title);
+ if (task.criteria) {
+ var criteria = document.createElement('p');
+ criteria.textContent = task.criteria;
+ item.appendChild(criteria);
}
- var name = document.createElement('strong');
- name.textContent = source.label || source.path.split('/').pop() || source.path;
- var location = document.createElement('code');
- location.textContent = source.line
- ? 'L' + source.line + (source.endLine && source.endLine !== source.line ? '–' + source.endLine : '') + (source.href ? ' ↗' : '')
- : source.href ? viewerText('viewer.passport.source.openLink') : '';
- var sourcePath = document.createElement('small');
- sourcePath.textContent = source.path;
- link.appendChild(name);
- link.appendChild(location);
- link.appendChild(sourcePath);
- evidenceLinks.appendChild(link);
+ Archify.sourceEvidence.task(id, index).forEach(function (source) { item.appendChild(sourceLink(source, repository)); });
+ taskChecklistTasks.appendChild(item);
+ });
+ taskChecklistCount.textContent = viewerText('viewer.passport.taskChecklist.count', {
+ implemented: tasks.filter(function (task) { return task.status === 'implemented'; }).length,
+ total: tasks.length
});
- evidence.hidden = false;
}
function explanationFor(id) {
return Archify.explanations && typeof Archify.explanations.node === 'function' ? Archify.explanations.node(id) : null;
@@ -9212,6 +9337,7 @@
Derived Consumers
semanticId.hidden = false;
renderDrilldowns(node);
renderSourceEvidence(id);
+ renderTaskChecklist(id);
}
/**
* 仅允许同目录文件名.html(可选 ?query / #fragment)。
@@ -10270,6 +10396,10 @@
Derived Consumers
evidenceLinks.textContent = '';
repositoryLink.removeAttribute('href');
repositoryLink.textContent = '';
+ if (taskChecklistSection) {
+ taskChecklistSection.hidden = true;
+ taskChecklistTasks.textContent = '';
+ }
summary.textContent = '';
reachSection.hidden = true;
upstreamCount.textContent = '0';
@@ -10452,6 +10582,7 @@
Derived Consumers
clearBtn.addEventListener('click', function () { clear({ restoreFocus: true }); });
copyBtn.addEventListener('click', copyFocusLink);
if (explainBtn) explainBtn.addEventListener('click', function (event) { event.stopPropagation(); toggleExplanation(); });
+ if (taskChecklistSection) taskChecklistSection.addEventListener('toggle', function () { if (!chip.hidden) requestLensPlacement(); });
if (explanationBack) {
explanationBack.addEventListener('click', function (event) { event.stopPropagation(); closeExplanation(); });
}
diff --git a/examples/drilldown-api-dataflow.html b/examples/drilldown-api-dataflow.html
index fed860d20..962951ecd 100644
--- a/examples/drilldown-api-dataflow.html
+++ b/examples/drilldown-api-dataflow.html
@@ -2078,6 +2078,55 @@
color: var(--text-faint);
font-size: 0.5625rem;
}
+ /* Authored task checklist: read-only, collapsed to one counted line. */
+ .semantic-passport-task-checklist {
+ margin-top: 0.52rem;
+ padding-top: 0.48rem;
+ border-top: 1px solid color-mix(in srgb, var(--toolbar-border) 76%, transparent);
+ }
+ .semantic-passport-task-checklist > summary { cursor: pointer; }
+ .semantic-passport-task-checklist-count {
+ color: var(--text-muted);
+ font-size: 0.5625rem;
+ }
+ .semantic-passport-task-checklist-tasks {
+ display: grid;
+ gap: 0.32rem;
+ max-height: min(16rem, 40vh);
+ margin: 0.45rem 0 0;
+ padding: 0.15rem;
+ overflow: auto;
+ overscroll-behavior: contain;
+ scrollbar-width: thin;
+ scrollbar-color: var(--frontend-stroke) transparent;
+ list-style: none;
+ }
+ .semantic-passport-task-checklist-task {
+ display: grid;
+ grid-template-columns: 0.9rem minmax(0, 1fr);
+ gap: 0.12rem 0.3rem;
+ }
+ .semantic-passport-task-checklist-mark {
+ color: var(--text-faint);
+ font-size: 0.625rem;
+ font-weight: 800;
+ text-align: center;
+ }
+ .semantic-passport-task-checklist-task[data-status="implemented"] .semantic-passport-task-checklist-mark { color: var(--frontend-stroke); }
+ .semantic-passport-task-checklist-task[data-status="needs-review"] .semantic-passport-task-checklist-mark { color: var(--text-muted); }
+ .semantic-passport-task-checklist-task > strong {
+ color: var(--text-muted);
+ font-size: 0.6875rem;
+ overflow-wrap: anywhere;
+ }
+ .semantic-passport-task-checklist-task p,
+ .semantic-passport-task-checklist-task .semantic-passport-source { grid-column: 2; }
+ .semantic-passport-task-checklist-task p {
+ margin: 0;
+ color: var(--text-faint);
+ font-size: 0.5625rem;
+ overflow-wrap: anywhere;
+ }
.semantic-passport-reach {
display: grid;
gap: 0.28rem;
@@ -2086,7 +2135,8 @@
border-top: 1px solid color-mix(in srgb, var(--toolbar-border) 76%, transparent);
}
.semantic-passport-reach[hidden] { display: none; }
- .semantic-passport-reach-label {
+ .semantic-passport-reach-label,
+ .semantic-passport-task-checklist-label {
color: var(--text-faint);
font-size: 0.5rem;
font-weight: 800;
@@ -2154,8 +2204,10 @@
scrollbar-width: thin;
scrollbar-color: var(--frontend-stroke) transparent;
}
- .relationship-lens-list::-webkit-scrollbar { width: 4px; }
- .relationship-lens-list::-webkit-scrollbar-thumb {
+ .relationship-lens-list::-webkit-scrollbar,
+ .semantic-passport-task-checklist-tasks::-webkit-scrollbar { width: 4px; }
+ .relationship-lens-list::-webkit-scrollbar-thumb,
+ .semantic-passport-task-checklist-tasks::-webkit-scrollbar-thumb {
border-radius: 999px;
background: color-mix(in srgb, var(--frontend-stroke) 68%, transparent);
}
@@ -2657,8 +2709,9 @@
as are dark themes and presets that already pass. Zero counts stay faint
on purpose, so they are excluded from the darker rule. Route-target
finders keep their --security-stroke status color, so they are excluded
- too. */
+ too. Task checklist done marks are the same kind of text. */
html[data-theme="light"] .semantic-passport-meta [data-passport="kind"],
+ html[data-theme="light"] .semantic-passport-task-checklist-task[data-status="implemented"] .semantic-passport-task-checklist-mark,
html[data-theme="light"] .node-finder:not([data-context="route-target"]) .node-finder-result em:not([data-zero]) {
color: var(--reader-accent, var(--frontend-stroke));
}
@@ -5407,7 +5460,8 @@
API Request Dataflow
-
+
+
@@ -5640,6 +5694,10 @@
API Request Dataflow
Ctrl+click or double-click opens the first entry
+
+ Task checklist
+
+
Authored reach
@@ -8428,6 +8486,24 @@
Back navigation
count: function () { return payload ? Object.keys(payload).length : 0; }
};
})();
+ /* Authored node task checklists: passport-only, like explanations. Their task
+ sources are verified and read through Archify.sourceEvidence.task(). */
+ Archify.taskChecklists = (function () {
+ var element = document.getElementById('archify-task-checklists-data');
+ var payload = null;
+ if (element) {
+ try {
+ var parsed = JSON.parse(element.textContent || 'null');
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) payload = parsed;
+ } catch (_) {}
+ }
+ return {
+ node: function (id) {
+ var tasks = payload && Object.prototype.hasOwnProperty.call(payload, id) ? payload[id] : null;
+ return Array.isArray(tasks) ? tasks.slice() : [];
+ }
+ };
+ })();
/* Verified repository evidence is emitted only after the renderer checks
the authored GitHub revision, source blobs, and optional line ranges
against the explicit --repo-root. It remains outside the SVG so every
@@ -8467,6 +8543,11 @@
Back navigation
node: function (id) {
var sources = payload && payload.nodes ? payload.nodes[id] : null;
return Array.isArray(sources) ? sources.slice() : [];
+ },
+ task: function (id, index) {
+ var tasks = payload && payload.taskChecklists ? payload.taskChecklists[id] : null;
+ var sources = Array.isArray(tasks) ? tasks[index] : null;
+ return Array.isArray(sources) ? sources.slice() : [];
}
};
})();
@@ -8642,6 +8723,9 @@
Back navigation
var parentBackBtn = document.getElementById('btn-parent-back');
var drilldownsSection = document.getElementById('focus-drilldowns');
var drilldownsList = document.getElementById('focus-drilldowns-list');
+ var taskChecklistSection = document.getElementById('focus-task-checklist');
+ var taskChecklistCount = document.getElementById('focus-task-checklist-count');
+ var taskChecklistTasks = document.getElementById('focus-task-checklist-tasks');
var moveBtn = document.getElementById('btn-focus-move');
var activeIds = [];
var hoveredRelationship = null;
@@ -8963,30 +9047,71 @@
Back navigation
repositoryLink.setAttribute('aria-label', viewerText('viewer.passport.repository.open', { revision: repository.revision }));
}
evidence.title = viewerText('viewer.passport.verificationScope');
- sources.forEach(function (source) {
- var link = document.createElement(source.href ? 'a' : 'div');
- link.className = 'semantic-passport-source';
- if (source.href) {
- link.href = source.href;
- link.target = '_blank';
- link.rel = 'noopener noreferrer';
- link.referrerPolicy = 'no-referrer';
- link.setAttribute('aria-label', viewerText('viewer.passport.source.open', { path: source.path, revision: repository.shortRevision }));
+ sources.forEach(function (source) { evidenceLinks.appendChild(sourceLink(source, repository)); });
+ evidence.hidden = false;
+ }
+ function sourceLink(source, repository) {
+ var link = document.createElement(source.href ? 'a' : 'div');
+ link.className = 'semantic-passport-source';
+ if (source.href) {
+ link.href = source.href;
+ link.target = '_blank';
+ link.rel = 'noopener noreferrer';
+ link.referrerPolicy = 'no-referrer';
+ link.setAttribute('aria-label', viewerText('viewer.passport.source.open', { path: source.path, revision: repository.shortRevision }));
+ }
+ var name = document.createElement('strong');
+ name.textContent = source.label || source.path.split('/').pop() || source.path;
+ var location = document.createElement('code');
+ location.textContent = source.line
+ ? 'L' + source.line + (source.endLine && source.endLine !== source.line ? '–' + source.endLine : '') + (source.href ? ' ↗' : '')
+ : source.href ? viewerText('viewer.passport.source.openLink') : '';
+ var sourcePath = document.createElement('small');
+ sourcePath.textContent = source.path;
+ link.appendChild(name);
+ link.appendChild(location);
+ link.appendChild(sourcePath);
+ return link;
+ }
+ var TASK_CHECKLIST_STATUS = {
+ implemented: ['✓', 'viewer.passport.taskChecklist.status.implemented'],
+ 'needs-review': ['?', 'viewer.passport.taskChecklist.status.needsReview'],
+ pending: ['○', 'viewer.passport.taskChecklist.status.pending']
+ };
+ // Read-only: authored statuses and verified task sources, never edited here.
+ function renderTaskChecklist(id) {
+ if (!taskChecklistSection) return;
+ taskChecklistSection.open = false;
+ taskChecklistTasks.textContent = '';
+ var tasks = Archify.taskChecklists ? Archify.taskChecklists.node(id) : [];
+ var repository = Archify.sourceEvidence.repository();
+ taskChecklistSection.hidden = !tasks.length;
+ tasks.forEach(function (task, index) {
+ var item = document.createElement('li');
+ item.className = 'semantic-passport-task-checklist-task';
+ item.setAttribute('data-status', task.status);
+ var status = TASK_CHECKLIST_STATUS[task.status] || TASK_CHECKLIST_STATUS.pending;
+ var mark = document.createElement('span');
+ mark.className = 'semantic-passport-task-checklist-mark';
+ mark.setAttribute('role', 'img');
+ mark.setAttribute('aria-label', viewerText(status[1]));
+ mark.textContent = status[0];
+ var title = document.createElement('strong');
+ title.textContent = task.title;
+ item.appendChild(mark);
+ item.appendChild(title);
+ if (task.criteria) {
+ var criteria = document.createElement('p');
+ criteria.textContent = task.criteria;
+ item.appendChild(criteria);
}
- var name = document.createElement('strong');
- name.textContent = source.label || source.path.split('/').pop() || source.path;
- var location = document.createElement('code');
- location.textContent = source.line
- ? 'L' + source.line + (source.endLine && source.endLine !== source.line ? '–' + source.endLine : '') + (source.href ? ' ↗' : '')
- : source.href ? viewerText('viewer.passport.source.openLink') : '';
- var sourcePath = document.createElement('small');
- sourcePath.textContent = source.path;
- link.appendChild(name);
- link.appendChild(location);
- link.appendChild(sourcePath);
- evidenceLinks.appendChild(link);
+ Archify.sourceEvidence.task(id, index).forEach(function (source) { item.appendChild(sourceLink(source, repository)); });
+ taskChecklistTasks.appendChild(item);
+ });
+ taskChecklistCount.textContent = viewerText('viewer.passport.taskChecklist.count', {
+ implemented: tasks.filter(function (task) { return task.status === 'implemented'; }).length,
+ total: tasks.length
});
- evidence.hidden = false;
}
function explanationFor(id) {
return Archify.explanations && typeof Archify.explanations.node === 'function' ? Archify.explanations.node(id) : null;
@@ -9069,6 +9194,7 @@
Back navigation
semanticId.hidden = false;
renderDrilldowns(node);
renderSourceEvidence(id);
+ renderTaskChecklist(id);
}
/**
* 仅允许同目录文件名.html(可选 ?query / #fragment)。
@@ -10127,6 +10253,10 @@
Back navigation
evidenceLinks.textContent = '';
repositoryLink.removeAttribute('href');
repositoryLink.textContent = '';
+ if (taskChecklistSection) {
+ taskChecklistSection.hidden = true;
+ taskChecklistTasks.textContent = '';
+ }
summary.textContent = '';
reachSection.hidden = true;
upstreamCount.textContent = '0';
@@ -10309,6 +10439,7 @@
Back navigation
clearBtn.addEventListener('click', function () { clear({ restoreFocus: true }); });
copyBtn.addEventListener('click', copyFocusLink);
if (explainBtn) explainBtn.addEventListener('click', function (event) { event.stopPropagation(); toggleExplanation(); });
+ if (taskChecklistSection) taskChecklistSection.addEventListener('toggle', function () { if (!chip.hidden) requestLensPlacement(); });
if (explanationBack) {
explanationBack.addEventListener('click', function (event) { event.stopPropagation(); closeExplanation(); });
}
diff --git a/examples/drilldown-api-sequence.html b/examples/drilldown-api-sequence.html
index f909c36ba..f314a6ad4 100644
--- a/examples/drilldown-api-sequence.html
+++ b/examples/drilldown-api-sequence.html
@@ -2078,6 +2078,55 @@
color: var(--text-faint);
font-size: 0.5625rem;
}
+ /* Authored task checklist: read-only, collapsed to one counted line. */
+ .semantic-passport-task-checklist {
+ margin-top: 0.52rem;
+ padding-top: 0.48rem;
+ border-top: 1px solid color-mix(in srgb, var(--toolbar-border) 76%, transparent);
+ }
+ .semantic-passport-task-checklist > summary { cursor: pointer; }
+ .semantic-passport-task-checklist-count {
+ color: var(--text-muted);
+ font-size: 0.5625rem;
+ }
+ .semantic-passport-task-checklist-tasks {
+ display: grid;
+ gap: 0.32rem;
+ max-height: min(16rem, 40vh);
+ margin: 0.45rem 0 0;
+ padding: 0.15rem;
+ overflow: auto;
+ overscroll-behavior: contain;
+ scrollbar-width: thin;
+ scrollbar-color: var(--frontend-stroke) transparent;
+ list-style: none;
+ }
+ .semantic-passport-task-checklist-task {
+ display: grid;
+ grid-template-columns: 0.9rem minmax(0, 1fr);
+ gap: 0.12rem 0.3rem;
+ }
+ .semantic-passport-task-checklist-mark {
+ color: var(--text-faint);
+ font-size: 0.625rem;
+ font-weight: 800;
+ text-align: center;
+ }
+ .semantic-passport-task-checklist-task[data-status="implemented"] .semantic-passport-task-checklist-mark { color: var(--frontend-stroke); }
+ .semantic-passport-task-checklist-task[data-status="needs-review"] .semantic-passport-task-checklist-mark { color: var(--text-muted); }
+ .semantic-passport-task-checklist-task > strong {
+ color: var(--text-muted);
+ font-size: 0.6875rem;
+ overflow-wrap: anywhere;
+ }
+ .semantic-passport-task-checklist-task p,
+ .semantic-passport-task-checklist-task .semantic-passport-source { grid-column: 2; }
+ .semantic-passport-task-checklist-task p {
+ margin: 0;
+ color: var(--text-faint);
+ font-size: 0.5625rem;
+ overflow-wrap: anywhere;
+ }
.semantic-passport-reach {
display: grid;
gap: 0.28rem;
@@ -2086,7 +2135,8 @@
border-top: 1px solid color-mix(in srgb, var(--toolbar-border) 76%, transparent);
}
.semantic-passport-reach[hidden] { display: none; }
- .semantic-passport-reach-label {
+ .semantic-passport-reach-label,
+ .semantic-passport-task-checklist-label {
color: var(--text-faint);
font-size: 0.5rem;
font-weight: 800;
@@ -2154,8 +2204,10 @@
scrollbar-width: thin;
scrollbar-color: var(--frontend-stroke) transparent;
}
- .relationship-lens-list::-webkit-scrollbar { width: 4px; }
- .relationship-lens-list::-webkit-scrollbar-thumb {
+ .relationship-lens-list::-webkit-scrollbar,
+ .semantic-passport-task-checklist-tasks::-webkit-scrollbar { width: 4px; }
+ .relationship-lens-list::-webkit-scrollbar-thumb,
+ .semantic-passport-task-checklist-tasks::-webkit-scrollbar-thumb {
border-radius: 999px;
background: color-mix(in srgb, var(--frontend-stroke) 68%, transparent);
}
@@ -2657,8 +2709,9 @@
as are dark themes and presets that already pass. Zero counts stay faint
on purpose, so they are excluded from the darker rule. Route-target
finders keep their --security-stroke status color, so they are excluded
- too. */
+ too. Task checklist done marks are the same kind of text. */
html[data-theme="light"] .semantic-passport-meta [data-passport="kind"],
+ html[data-theme="light"] .semantic-passport-task-checklist-task[data-status="implemented"] .semantic-passport-task-checklist-mark,
html[data-theme="light"] .node-finder:not([data-context="route-target"]) .node-finder-result em:not([data-zero]) {
color: var(--reader-accent, var(--frontend-stroke));
}
@@ -5407,7 +5460,8 @@
API Cache Miss Sequence
-
+
+
@@ -5651,6 +5705,10 @@
API Cache Miss Sequence
Ctrl+click or double-click opens the first entry
+
+ Task checklist
+
+
Authored reach
@@ -8439,6 +8497,24 @@
Multi-target
count: function () { return payload ? Object.keys(payload).length : 0; }
};
})();
+ /* Authored node task checklists: passport-only, like explanations. Their task
+ sources are verified and read through Archify.sourceEvidence.task(). */
+ Archify.taskChecklists = (function () {
+ var element = document.getElementById('archify-task-checklists-data');
+ var payload = null;
+ if (element) {
+ try {
+ var parsed = JSON.parse(element.textContent || 'null');
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) payload = parsed;
+ } catch (_) {}
+ }
+ return {
+ node: function (id) {
+ var tasks = payload && Object.prototype.hasOwnProperty.call(payload, id) ? payload[id] : null;
+ return Array.isArray(tasks) ? tasks.slice() : [];
+ }
+ };
+ })();
/* Verified repository evidence is emitted only after the renderer checks
the authored GitHub revision, source blobs, and optional line ranges
against the explicit --repo-root. It remains outside the SVG so every
@@ -8478,6 +8554,11 @@
Multi-target
node: function (id) {
var sources = payload && payload.nodes ? payload.nodes[id] : null;
return Array.isArray(sources) ? sources.slice() : [];
+ },
+ task: function (id, index) {
+ var tasks = payload && payload.taskChecklists ? payload.taskChecklists[id] : null;
+ var sources = Array.isArray(tasks) ? tasks[index] : null;
+ return Array.isArray(sources) ? sources.slice() : [];
}
};
})();
@@ -8653,6 +8734,9 @@
Multi-target
var parentBackBtn = document.getElementById('btn-parent-back');
var drilldownsSection = document.getElementById('focus-drilldowns');
var drilldownsList = document.getElementById('focus-drilldowns-list');
+ var taskChecklistSection = document.getElementById('focus-task-checklist');
+ var taskChecklistCount = document.getElementById('focus-task-checklist-count');
+ var taskChecklistTasks = document.getElementById('focus-task-checklist-tasks');
var moveBtn = document.getElementById('btn-focus-move');
var activeIds = [];
var hoveredRelationship = null;
@@ -8974,30 +9058,71 @@
Multi-target
repositoryLink.setAttribute('aria-label', viewerText('viewer.passport.repository.open', { revision: repository.revision }));
}
evidence.title = viewerText('viewer.passport.verificationScope');
- sources.forEach(function (source) {
- var link = document.createElement(source.href ? 'a' : 'div');
- link.className = 'semantic-passport-source';
- if (source.href) {
- link.href = source.href;
- link.target = '_blank';
- link.rel = 'noopener noreferrer';
- link.referrerPolicy = 'no-referrer';
- link.setAttribute('aria-label', viewerText('viewer.passport.source.open', { path: source.path, revision: repository.shortRevision }));
+ sources.forEach(function (source) { evidenceLinks.appendChild(sourceLink(source, repository)); });
+ evidence.hidden = false;
+ }
+ function sourceLink(source, repository) {
+ var link = document.createElement(source.href ? 'a' : 'div');
+ link.className = 'semantic-passport-source';
+ if (source.href) {
+ link.href = source.href;
+ link.target = '_blank';
+ link.rel = 'noopener noreferrer';
+ link.referrerPolicy = 'no-referrer';
+ link.setAttribute('aria-label', viewerText('viewer.passport.source.open', { path: source.path, revision: repository.shortRevision }));
+ }
+ var name = document.createElement('strong');
+ name.textContent = source.label || source.path.split('/').pop() || source.path;
+ var location = document.createElement('code');
+ location.textContent = source.line
+ ? 'L' + source.line + (source.endLine && source.endLine !== source.line ? '–' + source.endLine : '') + (source.href ? ' ↗' : '')
+ : source.href ? viewerText('viewer.passport.source.openLink') : '';
+ var sourcePath = document.createElement('small');
+ sourcePath.textContent = source.path;
+ link.appendChild(name);
+ link.appendChild(location);
+ link.appendChild(sourcePath);
+ return link;
+ }
+ var TASK_CHECKLIST_STATUS = {
+ implemented: ['✓', 'viewer.passport.taskChecklist.status.implemented'],
+ 'needs-review': ['?', 'viewer.passport.taskChecklist.status.needsReview'],
+ pending: ['○', 'viewer.passport.taskChecklist.status.pending']
+ };
+ // Read-only: authored statuses and verified task sources, never edited here.
+ function renderTaskChecklist(id) {
+ if (!taskChecklistSection) return;
+ taskChecklistSection.open = false;
+ taskChecklistTasks.textContent = '';
+ var tasks = Archify.taskChecklists ? Archify.taskChecklists.node(id) : [];
+ var repository = Archify.sourceEvidence.repository();
+ taskChecklistSection.hidden = !tasks.length;
+ tasks.forEach(function (task, index) {
+ var item = document.createElement('li');
+ item.className = 'semantic-passport-task-checklist-task';
+ item.setAttribute('data-status', task.status);
+ var status = TASK_CHECKLIST_STATUS[task.status] || TASK_CHECKLIST_STATUS.pending;
+ var mark = document.createElement('span');
+ mark.className = 'semantic-passport-task-checklist-mark';
+ mark.setAttribute('role', 'img');
+ mark.setAttribute('aria-label', viewerText(status[1]));
+ mark.textContent = status[0];
+ var title = document.createElement('strong');
+ title.textContent = task.title;
+ item.appendChild(mark);
+ item.appendChild(title);
+ if (task.criteria) {
+ var criteria = document.createElement('p');
+ criteria.textContent = task.criteria;
+ item.appendChild(criteria);
}
- var name = document.createElement('strong');
- name.textContent = source.label || source.path.split('/').pop() || source.path;
- var location = document.createElement('code');
- location.textContent = source.line
- ? 'L' + source.line + (source.endLine && source.endLine !== source.line ? '–' + source.endLine : '') + (source.href ? ' ↗' : '')
- : source.href ? viewerText('viewer.passport.source.openLink') : '';
- var sourcePath = document.createElement('small');
- sourcePath.textContent = source.path;
- link.appendChild(name);
- link.appendChild(location);
- link.appendChild(sourcePath);
- evidenceLinks.appendChild(link);
+ Archify.sourceEvidence.task(id, index).forEach(function (source) { item.appendChild(sourceLink(source, repository)); });
+ taskChecklistTasks.appendChild(item);
+ });
+ taskChecklistCount.textContent = viewerText('viewer.passport.taskChecklist.count', {
+ implemented: tasks.filter(function (task) { return task.status === 'implemented'; }).length,
+ total: tasks.length
});
- evidence.hidden = false;
}
function explanationFor(id) {
return Archify.explanations && typeof Archify.explanations.node === 'function' ? Archify.explanations.node(id) : null;
@@ -9080,6 +9205,7 @@
Multi-target
semanticId.hidden = false;
renderDrilldowns(node);
renderSourceEvidence(id);
+ renderTaskChecklist(id);
}
/**
* 仅允许同目录文件名.html(可选 ?query / #fragment)。
@@ -10138,6 +10264,10 @@
Multi-target
evidenceLinks.textContent = '';
repositoryLink.removeAttribute('href');
repositoryLink.textContent = '';
+ if (taskChecklistSection) {
+ taskChecklistSection.hidden = true;
+ taskChecklistTasks.textContent = '';
+ }
summary.textContent = '';
reachSection.hidden = true;
upstreamCount.textContent = '0';
@@ -10320,6 +10450,7 @@
Multi-target
clearBtn.addEventListener('click', function () { clear({ restoreFocus: true }); });
copyBtn.addEventListener('click', copyFocusLink);
if (explainBtn) explainBtn.addEventListener('click', function (event) { event.stopPropagation(); toggleExplanation(); });
+ if (taskChecklistSection) taskChecklistSection.addEventListener('toggle', function () { if (!chip.hidden) requestLensPlacement(); });
if (explanationBack) {
explanationBack.addEventListener('click', function (event) { event.stopPropagation(); closeExplanation(); });
}
diff --git a/examples/drilldown-integrations.html b/examples/drilldown-integrations.html
index 73ab952a2..30784f942 100644
--- a/examples/drilldown-integrations.html
+++ b/examples/drilldown-integrations.html
@@ -2078,6 +2078,55 @@
color: var(--text-faint);
font-size: 0.5625rem;
}
+ /* Authored task checklist: read-only, collapsed to one counted line. */
+ .semantic-passport-task-checklist {
+ margin-top: 0.52rem;
+ padding-top: 0.48rem;
+ border-top: 1px solid color-mix(in srgb, var(--toolbar-border) 76%, transparent);
+ }
+ .semantic-passport-task-checklist > summary { cursor: pointer; }
+ .semantic-passport-task-checklist-count {
+ color: var(--text-muted);
+ font-size: 0.5625rem;
+ }
+ .semantic-passport-task-checklist-tasks {
+ display: grid;
+ gap: 0.32rem;
+ max-height: min(16rem, 40vh);
+ margin: 0.45rem 0 0;
+ padding: 0.15rem;
+ overflow: auto;
+ overscroll-behavior: contain;
+ scrollbar-width: thin;
+ scrollbar-color: var(--frontend-stroke) transparent;
+ list-style: none;
+ }
+ .semantic-passport-task-checklist-task {
+ display: grid;
+ grid-template-columns: 0.9rem minmax(0, 1fr);
+ gap: 0.12rem 0.3rem;
+ }
+ .semantic-passport-task-checklist-mark {
+ color: var(--text-faint);
+ font-size: 0.625rem;
+ font-weight: 800;
+ text-align: center;
+ }
+ .semantic-passport-task-checklist-task[data-status="implemented"] .semantic-passport-task-checklist-mark { color: var(--frontend-stroke); }
+ .semantic-passport-task-checklist-task[data-status="needs-review"] .semantic-passport-task-checklist-mark { color: var(--text-muted); }
+ .semantic-passport-task-checklist-task > strong {
+ color: var(--text-muted);
+ font-size: 0.6875rem;
+ overflow-wrap: anywhere;
+ }
+ .semantic-passport-task-checklist-task p,
+ .semantic-passport-task-checklist-task .semantic-passport-source { grid-column: 2; }
+ .semantic-passport-task-checklist-task p {
+ margin: 0;
+ color: var(--text-faint);
+ font-size: 0.5625rem;
+ overflow-wrap: anywhere;
+ }
.semantic-passport-reach {
display: grid;
gap: 0.28rem;
@@ -2086,7 +2135,8 @@
border-top: 1px solid color-mix(in srgb, var(--toolbar-border) 76%, transparent);
}
.semantic-passport-reach[hidden] { display: none; }
- .semantic-passport-reach-label {
+ .semantic-passport-reach-label,
+ .semantic-passport-task-checklist-label {
color: var(--text-faint);
font-size: 0.5rem;
font-weight: 800;
@@ -2154,8 +2204,10 @@
scrollbar-width: thin;
scrollbar-color: var(--frontend-stroke) transparent;
}
- .relationship-lens-list::-webkit-scrollbar { width: 4px; }
- .relationship-lens-list::-webkit-scrollbar-thumb {
+ .relationship-lens-list::-webkit-scrollbar,
+ .semantic-passport-task-checklist-tasks::-webkit-scrollbar { width: 4px; }
+ .relationship-lens-list::-webkit-scrollbar-thumb,
+ .semantic-passport-task-checklist-tasks::-webkit-scrollbar-thumb {
border-radius: 999px;
background: color-mix(in srgb, var(--frontend-stroke) 68%, transparent);
}
@@ -2657,8 +2709,9 @@
as are dark themes and presets that already pass. Zero counts stay faint
on purpose, so they are excluded from the darker rule. Route-target
finders keep their --security-stroke status color, so they are excluded
- too. */
+ too. Task checklist done marks are the same kind of text. */
html[data-theme="light"] .semantic-passport-meta [data-passport="kind"],
+ html[data-theme="light"] .semantic-passport-task-checklist-task[data-status="implemented"] .semantic-passport-task-checklist-mark,
html[data-theme="light"] .node-finder:not([data-context="route-target"]) .node-finder-result em:not([data-zero]) {
color: var(--reader-accent, var(--frontend-stroke));
}
@@ -5407,7 +5460,8 @@
Integrations Map
-
+
+
@@ -5649,6 +5703,10 @@
Integrations Map
Ctrl+click or double-click opens the first entry
+
+ Task checklist
+
+
Authored reach
@@ -8438,6 +8496,24 @@
Sibling pair
count: function () { return payload ? Object.keys(payload).length : 0; }
};
})();
+ /* Authored node task checklists: passport-only, like explanations. Their task
+ sources are verified and read through Archify.sourceEvidence.task(). */
+ Archify.taskChecklists = (function () {
+ var element = document.getElementById('archify-task-checklists-data');
+ var payload = null;
+ if (element) {
+ try {
+ var parsed = JSON.parse(element.textContent || 'null');
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) payload = parsed;
+ } catch (_) {}
+ }
+ return {
+ node: function (id) {
+ var tasks = payload && Object.prototype.hasOwnProperty.call(payload, id) ? payload[id] : null;
+ return Array.isArray(tasks) ? tasks.slice() : [];
+ }
+ };
+ })();
/* Verified repository evidence is emitted only after the renderer checks
the authored GitHub revision, source blobs, and optional line ranges
against the explicit --repo-root. It remains outside the SVG so every
@@ -8477,6 +8553,11 @@
Sibling pair
node: function (id) {
var sources = payload && payload.nodes ? payload.nodes[id] : null;
return Array.isArray(sources) ? sources.slice() : [];
+ },
+ task: function (id, index) {
+ var tasks = payload && payload.taskChecklists ? payload.taskChecklists[id] : null;
+ var sources = Array.isArray(tasks) ? tasks[index] : null;
+ return Array.isArray(sources) ? sources.slice() : [];
}
};
})();
@@ -8652,6 +8733,9 @@
Sibling pair
var parentBackBtn = document.getElementById('btn-parent-back');
var drilldownsSection = document.getElementById('focus-drilldowns');
var drilldownsList = document.getElementById('focus-drilldowns-list');
+ var taskChecklistSection = document.getElementById('focus-task-checklist');
+ var taskChecklistCount = document.getElementById('focus-task-checklist-count');
+ var taskChecklistTasks = document.getElementById('focus-task-checklist-tasks');
var moveBtn = document.getElementById('btn-focus-move');
var activeIds = [];
var hoveredRelationship = null;
@@ -8973,30 +9057,71 @@
Sibling pair
repositoryLink.setAttribute('aria-label', viewerText('viewer.passport.repository.open', { revision: repository.revision }));
}
evidence.title = viewerText('viewer.passport.verificationScope');
- sources.forEach(function (source) {
- var link = document.createElement(source.href ? 'a' : 'div');
- link.className = 'semantic-passport-source';
- if (source.href) {
- link.href = source.href;
- link.target = '_blank';
- link.rel = 'noopener noreferrer';
- link.referrerPolicy = 'no-referrer';
- link.setAttribute('aria-label', viewerText('viewer.passport.source.open', { path: source.path, revision: repository.shortRevision }));
+ sources.forEach(function (source) { evidenceLinks.appendChild(sourceLink(source, repository)); });
+ evidence.hidden = false;
+ }
+ function sourceLink(source, repository) {
+ var link = document.createElement(source.href ? 'a' : 'div');
+ link.className = 'semantic-passport-source';
+ if (source.href) {
+ link.href = source.href;
+ link.target = '_blank';
+ link.rel = 'noopener noreferrer';
+ link.referrerPolicy = 'no-referrer';
+ link.setAttribute('aria-label', viewerText('viewer.passport.source.open', { path: source.path, revision: repository.shortRevision }));
+ }
+ var name = document.createElement('strong');
+ name.textContent = source.label || source.path.split('/').pop() || source.path;
+ var location = document.createElement('code');
+ location.textContent = source.line
+ ? 'L' + source.line + (source.endLine && source.endLine !== source.line ? '–' + source.endLine : '') + (source.href ? ' ↗' : '')
+ : source.href ? viewerText('viewer.passport.source.openLink') : '';
+ var sourcePath = document.createElement('small');
+ sourcePath.textContent = source.path;
+ link.appendChild(name);
+ link.appendChild(location);
+ link.appendChild(sourcePath);
+ return link;
+ }
+ var TASK_CHECKLIST_STATUS = {
+ implemented: ['✓', 'viewer.passport.taskChecklist.status.implemented'],
+ 'needs-review': ['?', 'viewer.passport.taskChecklist.status.needsReview'],
+ pending: ['○', 'viewer.passport.taskChecklist.status.pending']
+ };
+ // Read-only: authored statuses and verified task sources, never edited here.
+ function renderTaskChecklist(id) {
+ if (!taskChecklistSection) return;
+ taskChecklistSection.open = false;
+ taskChecklistTasks.textContent = '';
+ var tasks = Archify.taskChecklists ? Archify.taskChecklists.node(id) : [];
+ var repository = Archify.sourceEvidence.repository();
+ taskChecklistSection.hidden = !tasks.length;
+ tasks.forEach(function (task, index) {
+ var item = document.createElement('li');
+ item.className = 'semantic-passport-task-checklist-task';
+ item.setAttribute('data-status', task.status);
+ var status = TASK_CHECKLIST_STATUS[task.status] || TASK_CHECKLIST_STATUS.pending;
+ var mark = document.createElement('span');
+ mark.className = 'semantic-passport-task-checklist-mark';
+ mark.setAttribute('role', 'img');
+ mark.setAttribute('aria-label', viewerText(status[1]));
+ mark.textContent = status[0];
+ var title = document.createElement('strong');
+ title.textContent = task.title;
+ item.appendChild(mark);
+ item.appendChild(title);
+ if (task.criteria) {
+ var criteria = document.createElement('p');
+ criteria.textContent = task.criteria;
+ item.appendChild(criteria);
}
- var name = document.createElement('strong');
- name.textContent = source.label || source.path.split('/').pop() || source.path;
- var location = document.createElement('code');
- location.textContent = source.line
- ? 'L' + source.line + (source.endLine && source.endLine !== source.line ? '–' + source.endLine : '') + (source.href ? ' ↗' : '')
- : source.href ? viewerText('viewer.passport.source.openLink') : '';
- var sourcePath = document.createElement('small');
- sourcePath.textContent = source.path;
- link.appendChild(name);
- link.appendChild(location);
- link.appendChild(sourcePath);
- evidenceLinks.appendChild(link);
+ Archify.sourceEvidence.task(id, index).forEach(function (source) { item.appendChild(sourceLink(source, repository)); });
+ taskChecklistTasks.appendChild(item);
+ });
+ taskChecklistCount.textContent = viewerText('viewer.passport.taskChecklist.count', {
+ implemented: tasks.filter(function (task) { return task.status === 'implemented'; }).length,
+ total: tasks.length
});
- evidence.hidden = false;
}
function explanationFor(id) {
return Archify.explanations && typeof Archify.explanations.node === 'function' ? Archify.explanations.node(id) : null;
@@ -9079,6 +9204,7 @@
Sibling pair
semanticId.hidden = false;
renderDrilldowns(node);
renderSourceEvidence(id);
+ renderTaskChecklist(id);
}
/**
* 仅允许同目录文件名.html(可选 ?query / #fragment)。
@@ -10137,6 +10263,10 @@
Sibling pair
evidenceLinks.textContent = '';
repositoryLink.removeAttribute('href');
repositoryLink.textContent = '';
+ if (taskChecklistSection) {
+ taskChecklistSection.hidden = true;
+ taskChecklistTasks.textContent = '';
+ }
summary.textContent = '';
reachSection.hidden = true;
upstreamCount.textContent = '0';
@@ -10319,6 +10449,7 @@
Sibling pair
clearBtn.addEventListener('click', function () { clear({ restoreFocus: true }); });
copyBtn.addEventListener('click', copyFocusLink);
if (explainBtn) explainBtn.addEventListener('click', function (event) { event.stopPropagation(); toggleExplanation(); });
+ if (taskChecklistSection) taskChecklistSection.addEventListener('toggle', function () { if (!chip.hidden) requestLensPlacement(); });
if (explanationBack) {
explanationBack.addEventListener('click', function (event) { event.stopPropagation(); closeExplanation(); });
}
diff --git a/examples/drilldown-overview.html b/examples/drilldown-overview.html
index 166661023..8c50c0a70 100644
--- a/examples/drilldown-overview.html
+++ b/examples/drilldown-overview.html
@@ -2078,6 +2078,55 @@
color: var(--text-faint);
font-size: 0.5625rem;
}
+ /* Authored task checklist: read-only, collapsed to one counted line. */
+ .semantic-passport-task-checklist {
+ margin-top: 0.52rem;
+ padding-top: 0.48rem;
+ border-top: 1px solid color-mix(in srgb, var(--toolbar-border) 76%, transparent);
+ }
+ .semantic-passport-task-checklist > summary { cursor: pointer; }
+ .semantic-passport-task-checklist-count {
+ color: var(--text-muted);
+ font-size: 0.5625rem;
+ }
+ .semantic-passport-task-checklist-tasks {
+ display: grid;
+ gap: 0.32rem;
+ max-height: min(16rem, 40vh);
+ margin: 0.45rem 0 0;
+ padding: 0.15rem;
+ overflow: auto;
+ overscroll-behavior: contain;
+ scrollbar-width: thin;
+ scrollbar-color: var(--frontend-stroke) transparent;
+ list-style: none;
+ }
+ .semantic-passport-task-checklist-task {
+ display: grid;
+ grid-template-columns: 0.9rem minmax(0, 1fr);
+ gap: 0.12rem 0.3rem;
+ }
+ .semantic-passport-task-checklist-mark {
+ color: var(--text-faint);
+ font-size: 0.625rem;
+ font-weight: 800;
+ text-align: center;
+ }
+ .semantic-passport-task-checklist-task[data-status="implemented"] .semantic-passport-task-checklist-mark { color: var(--frontend-stroke); }
+ .semantic-passport-task-checklist-task[data-status="needs-review"] .semantic-passport-task-checklist-mark { color: var(--text-muted); }
+ .semantic-passport-task-checklist-task > strong {
+ color: var(--text-muted);
+ font-size: 0.6875rem;
+ overflow-wrap: anywhere;
+ }
+ .semantic-passport-task-checklist-task p,
+ .semantic-passport-task-checklist-task .semantic-passport-source { grid-column: 2; }
+ .semantic-passport-task-checklist-task p {
+ margin: 0;
+ color: var(--text-faint);
+ font-size: 0.5625rem;
+ overflow-wrap: anywhere;
+ }
.semantic-passport-reach {
display: grid;
gap: 0.28rem;
@@ -2086,7 +2135,8 @@
border-top: 1px solid color-mix(in srgb, var(--toolbar-border) 76%, transparent);
}
.semantic-passport-reach[hidden] { display: none; }
- .semantic-passport-reach-label {
+ .semantic-passport-reach-label,
+ .semantic-passport-task-checklist-label {
color: var(--text-faint);
font-size: 0.5rem;
font-weight: 800;
@@ -2154,8 +2204,10 @@
scrollbar-width: thin;
scrollbar-color: var(--frontend-stroke) transparent;
}
- .relationship-lens-list::-webkit-scrollbar { width: 4px; }
- .relationship-lens-list::-webkit-scrollbar-thumb {
+ .relationship-lens-list::-webkit-scrollbar,
+ .semantic-passport-task-checklist-tasks::-webkit-scrollbar { width: 4px; }
+ .relationship-lens-list::-webkit-scrollbar-thumb,
+ .semantic-passport-task-checklist-tasks::-webkit-scrollbar-thumb {
border-radius: 999px;
background: color-mix(in srgb, var(--frontend-stroke) 68%, transparent);
}
@@ -2657,8 +2709,9 @@
as are dark themes and presets that already pass. Zero counts stay faint
on purpose, so they are excluded from the darker rule. Route-target
finders keep their --security-stroke status color, so they are excluded
- too. */
+ too. Task checklist done marks are the same kind of text. */
html[data-theme="light"] .semantic-passport-meta [data-passport="kind"],
+ html[data-theme="light"] .semantic-passport-task-checklist-task[data-status="implemented"] .semantic-passport-task-checklist-mark,
html[data-theme="light"] .node-finder:not([data-context="route-target"]) .node-finder-result em:not([data-zero]) {
color: var(--reader-accent, var(--frontend-stroke));
}
@@ -5407,7 +5460,8 @@
Drilldown Overview
-
+
+
@@ -5624,6 +5678,10 @@
Drilldown Overview
Ctrl+click or double-click opens the first entry
+
+ Task checklist
+
+
Authored reach
@@ -8413,6 +8471,24 @@
Drilldown contract
count: function () { return payload ? Object.keys(payload).length : 0; }
};
})();
+ /* Authored node task checklists: passport-only, like explanations. Their task
+ sources are verified and read through Archify.sourceEvidence.task(). */
+ Archify.taskChecklists = (function () {
+ var element = document.getElementById('archify-task-checklists-data');
+ var payload = null;
+ if (element) {
+ try {
+ var parsed = JSON.parse(element.textContent || 'null');
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) payload = parsed;
+ } catch (_) {}
+ }
+ return {
+ node: function (id) {
+ var tasks = payload && Object.prototype.hasOwnProperty.call(payload, id) ? payload[id] : null;
+ return Array.isArray(tasks) ? tasks.slice() : [];
+ }
+ };
+ })();
/* Verified repository evidence is emitted only after the renderer checks
the authored GitHub revision, source blobs, and optional line ranges
against the explicit --repo-root. It remains outside the SVG so every
@@ -8452,6 +8528,11 @@
Drilldown contract
node: function (id) {
var sources = payload && payload.nodes ? payload.nodes[id] : null;
return Array.isArray(sources) ? sources.slice() : [];
+ },
+ task: function (id, index) {
+ var tasks = payload && payload.taskChecklists ? payload.taskChecklists[id] : null;
+ var sources = Array.isArray(tasks) ? tasks[index] : null;
+ return Array.isArray(sources) ? sources.slice() : [];
}
};
})();
@@ -8627,6 +8708,9 @@
Drilldown contract
var parentBackBtn = document.getElementById('btn-parent-back');
var drilldownsSection = document.getElementById('focus-drilldowns');
var drilldownsList = document.getElementById('focus-drilldowns-list');
+ var taskChecklistSection = document.getElementById('focus-task-checklist');
+ var taskChecklistCount = document.getElementById('focus-task-checklist-count');
+ var taskChecklistTasks = document.getElementById('focus-task-checklist-tasks');
var moveBtn = document.getElementById('btn-focus-move');
var activeIds = [];
var hoveredRelationship = null;
@@ -8948,30 +9032,71 @@
Drilldown contract
repositoryLink.setAttribute('aria-label', viewerText('viewer.passport.repository.open', { revision: repository.revision }));
}
evidence.title = viewerText('viewer.passport.verificationScope');
- sources.forEach(function (source) {
- var link = document.createElement(source.href ? 'a' : 'div');
- link.className = 'semantic-passport-source';
- if (source.href) {
- link.href = source.href;
- link.target = '_blank';
- link.rel = 'noopener noreferrer';
- link.referrerPolicy = 'no-referrer';
- link.setAttribute('aria-label', viewerText('viewer.passport.source.open', { path: source.path, revision: repository.shortRevision }));
+ sources.forEach(function (source) { evidenceLinks.appendChild(sourceLink(source, repository)); });
+ evidence.hidden = false;
+ }
+ function sourceLink(source, repository) {
+ var link = document.createElement(source.href ? 'a' : 'div');
+ link.className = 'semantic-passport-source';
+ if (source.href) {
+ link.href = source.href;
+ link.target = '_blank';
+ link.rel = 'noopener noreferrer';
+ link.referrerPolicy = 'no-referrer';
+ link.setAttribute('aria-label', viewerText('viewer.passport.source.open', { path: source.path, revision: repository.shortRevision }));
+ }
+ var name = document.createElement('strong');
+ name.textContent = source.label || source.path.split('/').pop() || source.path;
+ var location = document.createElement('code');
+ location.textContent = source.line
+ ? 'L' + source.line + (source.endLine && source.endLine !== source.line ? '–' + source.endLine : '') + (source.href ? ' ↗' : '')
+ : source.href ? viewerText('viewer.passport.source.openLink') : '';
+ var sourcePath = document.createElement('small');
+ sourcePath.textContent = source.path;
+ link.appendChild(name);
+ link.appendChild(location);
+ link.appendChild(sourcePath);
+ return link;
+ }
+ var TASK_CHECKLIST_STATUS = {
+ implemented: ['✓', 'viewer.passport.taskChecklist.status.implemented'],
+ 'needs-review': ['?', 'viewer.passport.taskChecklist.status.needsReview'],
+ pending: ['○', 'viewer.passport.taskChecklist.status.pending']
+ };
+ // Read-only: authored statuses and verified task sources, never edited here.
+ function renderTaskChecklist(id) {
+ if (!taskChecklistSection) return;
+ taskChecklistSection.open = false;
+ taskChecklistTasks.textContent = '';
+ var tasks = Archify.taskChecklists ? Archify.taskChecklists.node(id) : [];
+ var repository = Archify.sourceEvidence.repository();
+ taskChecklistSection.hidden = !tasks.length;
+ tasks.forEach(function (task, index) {
+ var item = document.createElement('li');
+ item.className = 'semantic-passport-task-checklist-task';
+ item.setAttribute('data-status', task.status);
+ var status = TASK_CHECKLIST_STATUS[task.status] || TASK_CHECKLIST_STATUS.pending;
+ var mark = document.createElement('span');
+ mark.className = 'semantic-passport-task-checklist-mark';
+ mark.setAttribute('role', 'img');
+ mark.setAttribute('aria-label', viewerText(status[1]));
+ mark.textContent = status[0];
+ var title = document.createElement('strong');
+ title.textContent = task.title;
+ item.appendChild(mark);
+ item.appendChild(title);
+ if (task.criteria) {
+ var criteria = document.createElement('p');
+ criteria.textContent = task.criteria;
+ item.appendChild(criteria);
}
- var name = document.createElement('strong');
- name.textContent = source.label || source.path.split('/').pop() || source.path;
- var location = document.createElement('code');
- location.textContent = source.line
- ? 'L' + source.line + (source.endLine && source.endLine !== source.line ? '–' + source.endLine : '') + (source.href ? ' ↗' : '')
- : source.href ? viewerText('viewer.passport.source.openLink') : '';
- var sourcePath = document.createElement('small');
- sourcePath.textContent = source.path;
- link.appendChild(name);
- link.appendChild(location);
- link.appendChild(sourcePath);
- evidenceLinks.appendChild(link);
+ Archify.sourceEvidence.task(id, index).forEach(function (source) { item.appendChild(sourceLink(source, repository)); });
+ taskChecklistTasks.appendChild(item);
+ });
+ taskChecklistCount.textContent = viewerText('viewer.passport.taskChecklist.count', {
+ implemented: tasks.filter(function (task) { return task.status === 'implemented'; }).length,
+ total: tasks.length
});
- evidence.hidden = false;
}
function explanationFor(id) {
return Archify.explanations && typeof Archify.explanations.node === 'function' ? Archify.explanations.node(id) : null;
@@ -9054,6 +9179,7 @@
Drilldown contract
semanticId.hidden = false;
renderDrilldowns(node);
renderSourceEvidence(id);
+ renderTaskChecklist(id);
}
/**
* 仅允许同目录文件名.html(可选 ?query / #fragment)。
@@ -10112,6 +10238,10 @@
Drilldown contract
evidenceLinks.textContent = '';
repositoryLink.removeAttribute('href');
repositoryLink.textContent = '';
+ if (taskChecklistSection) {
+ taskChecklistSection.hidden = true;
+ taskChecklistTasks.textContent = '';
+ }
summary.textContent = '';
reachSection.hidden = true;
upstreamCount.textContent = '0';
@@ -10294,6 +10424,7 @@
Drilldown contract
clearBtn.addEventListener('click', function () { clear({ restoreFocus: true }); });
copyBtn.addEventListener('click', copyFocusLink);
if (explainBtn) explainBtn.addEventListener('click', function (event) { event.stopPropagation(); toggleExplanation(); });
+ if (taskChecklistSection) taskChecklistSection.addEventListener('toggle', function () { if (!chip.hidden) requestLensPlacement(); });
if (explanationBack) {
explanationBack.addEventListener('click', function (event) { event.stopPropagation(); closeExplanation(); });
}
diff --git a/examples/drilldown-platform.html b/examples/drilldown-platform.html
index fdcfc98c3..f6aadcace 100644
--- a/examples/drilldown-platform.html
+++ b/examples/drilldown-platform.html
@@ -2078,6 +2078,55 @@
color: var(--text-faint);
font-size: 0.5625rem;
}
+ /* Authored task checklist: read-only, collapsed to one counted line. */
+ .semantic-passport-task-checklist {
+ margin-top: 0.52rem;
+ padding-top: 0.48rem;
+ border-top: 1px solid color-mix(in srgb, var(--toolbar-border) 76%, transparent);
+ }
+ .semantic-passport-task-checklist > summary { cursor: pointer; }
+ .semantic-passport-task-checklist-count {
+ color: var(--text-muted);
+ font-size: 0.5625rem;
+ }
+ .semantic-passport-task-checklist-tasks {
+ display: grid;
+ gap: 0.32rem;
+ max-height: min(16rem, 40vh);
+ margin: 0.45rem 0 0;
+ padding: 0.15rem;
+ overflow: auto;
+ overscroll-behavior: contain;
+ scrollbar-width: thin;
+ scrollbar-color: var(--frontend-stroke) transparent;
+ list-style: none;
+ }
+ .semantic-passport-task-checklist-task {
+ display: grid;
+ grid-template-columns: 0.9rem minmax(0, 1fr);
+ gap: 0.12rem 0.3rem;
+ }
+ .semantic-passport-task-checklist-mark {
+ color: var(--text-faint);
+ font-size: 0.625rem;
+ font-weight: 800;
+ text-align: center;
+ }
+ .semantic-passport-task-checklist-task[data-status="implemented"] .semantic-passport-task-checklist-mark { color: var(--frontend-stroke); }
+ .semantic-passport-task-checklist-task[data-status="needs-review"] .semantic-passport-task-checklist-mark { color: var(--text-muted); }
+ .semantic-passport-task-checklist-task > strong {
+ color: var(--text-muted);
+ font-size: 0.6875rem;
+ overflow-wrap: anywhere;
+ }
+ .semantic-passport-task-checklist-task p,
+ .semantic-passport-task-checklist-task .semantic-passport-source { grid-column: 2; }
+ .semantic-passport-task-checklist-task p {
+ margin: 0;
+ color: var(--text-faint);
+ font-size: 0.5625rem;
+ overflow-wrap: anywhere;
+ }
.semantic-passport-reach {
display: grid;
gap: 0.28rem;
@@ -2086,7 +2135,8 @@
border-top: 1px solid color-mix(in srgb, var(--toolbar-border) 76%, transparent);
}
.semantic-passport-reach[hidden] { display: none; }
- .semantic-passport-reach-label {
+ .semantic-passport-reach-label,
+ .semantic-passport-task-checklist-label {
color: var(--text-faint);
font-size: 0.5rem;
font-weight: 800;
@@ -2154,8 +2204,10 @@
scrollbar-width: thin;
scrollbar-color: var(--frontend-stroke) transparent;
}
- .relationship-lens-list::-webkit-scrollbar { width: 4px; }
- .relationship-lens-list::-webkit-scrollbar-thumb {
+ .relationship-lens-list::-webkit-scrollbar,
+ .semantic-passport-task-checklist-tasks::-webkit-scrollbar { width: 4px; }
+ .relationship-lens-list::-webkit-scrollbar-thumb,
+ .semantic-passport-task-checklist-tasks::-webkit-scrollbar-thumb {
border-radius: 999px;
background: color-mix(in srgb, var(--frontend-stroke) 68%, transparent);
}
@@ -2657,8 +2709,9 @@
as are dark themes and presets that already pass. Zero counts stay faint
on purpose, so they are excluded from the darker rule. Route-target
finders keep their --security-stroke status color, so they are excluded
- too. */
+ too. Task checklist done marks are the same kind of text. */
html[data-theme="light"] .semantic-passport-meta [data-passport="kind"],
+ html[data-theme="light"] .semantic-passport-task-checklist-task[data-status="implemented"] .semantic-passport-task-checklist-mark,
html[data-theme="light"] .node-finder:not([data-context="route-target"]) .node-finder-result em:not([data-zero]) {
color: var(--reader-accent, var(--frontend-stroke));
}
@@ -5407,7 +5460,8 @@
Platform Overview
-
+
+
@@ -5635,6 +5689,10 @@
Platform Overview
Ctrl+click or double-click opens the first entry
+
+ Task checklist
+
+
Authored reach
@@ -8424,6 +8482,24 @@
Single-child href
count: function () { return payload ? Object.keys(payload).length : 0; }
};
})();
+ /* Authored node task checklists: passport-only, like explanations. Their task
+ sources are verified and read through Archify.sourceEvidence.task(). */
+ Archify.taskChecklists = (function () {
+ var element = document.getElementById('archify-task-checklists-data');
+ var payload = null;
+ if (element) {
+ try {
+ var parsed = JSON.parse(element.textContent || 'null');
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) payload = parsed;
+ } catch (_) {}
+ }
+ return {
+ node: function (id) {
+ var tasks = payload && Object.prototype.hasOwnProperty.call(payload, id) ? payload[id] : null;
+ return Array.isArray(tasks) ? tasks.slice() : [];
+ }
+ };
+ })();
/* Verified repository evidence is emitted only after the renderer checks
the authored GitHub revision, source blobs, and optional line ranges
against the explicit --repo-root. It remains outside the SVG so every
@@ -8463,6 +8539,11 @@
Single-child href
node: function (id) {
var sources = payload && payload.nodes ? payload.nodes[id] : null;
return Array.isArray(sources) ? sources.slice() : [];
+ },
+ task: function (id, index) {
+ var tasks = payload && payload.taskChecklists ? payload.taskChecklists[id] : null;
+ var sources = Array.isArray(tasks) ? tasks[index] : null;
+ return Array.isArray(sources) ? sources.slice() : [];
}
};
})();
@@ -8638,6 +8719,9 @@
Single-child href
var parentBackBtn = document.getElementById('btn-parent-back');
var drilldownsSection = document.getElementById('focus-drilldowns');
var drilldownsList = document.getElementById('focus-drilldowns-list');
+ var taskChecklistSection = document.getElementById('focus-task-checklist');
+ var taskChecklistCount = document.getElementById('focus-task-checklist-count');
+ var taskChecklistTasks = document.getElementById('focus-task-checklist-tasks');
var moveBtn = document.getElementById('btn-focus-move');
var activeIds = [];
var hoveredRelationship = null;
@@ -8959,30 +9043,71 @@
Single-child href
repositoryLink.setAttribute('aria-label', viewerText('viewer.passport.repository.open', { revision: repository.revision }));
}
evidence.title = viewerText('viewer.passport.verificationScope');
- sources.forEach(function (source) {
- var link = document.createElement(source.href ? 'a' : 'div');
- link.className = 'semantic-passport-source';
- if (source.href) {
- link.href = source.href;
- link.target = '_blank';
- link.rel = 'noopener noreferrer';
- link.referrerPolicy = 'no-referrer';
- link.setAttribute('aria-label', viewerText('viewer.passport.source.open', { path: source.path, revision: repository.shortRevision }));
+ sources.forEach(function (source) { evidenceLinks.appendChild(sourceLink(source, repository)); });
+ evidence.hidden = false;
+ }
+ function sourceLink(source, repository) {
+ var link = document.createElement(source.href ? 'a' : 'div');
+ link.className = 'semantic-passport-source';
+ if (source.href) {
+ link.href = source.href;
+ link.target = '_blank';
+ link.rel = 'noopener noreferrer';
+ link.referrerPolicy = 'no-referrer';
+ link.setAttribute('aria-label', viewerText('viewer.passport.source.open', { path: source.path, revision: repository.shortRevision }));
+ }
+ var name = document.createElement('strong');
+ name.textContent = source.label || source.path.split('/').pop() || source.path;
+ var location = document.createElement('code');
+ location.textContent = source.line
+ ? 'L' + source.line + (source.endLine && source.endLine !== source.line ? '–' + source.endLine : '') + (source.href ? ' ↗' : '')
+ : source.href ? viewerText('viewer.passport.source.openLink') : '';
+ var sourcePath = document.createElement('small');
+ sourcePath.textContent = source.path;
+ link.appendChild(name);
+ link.appendChild(location);
+ link.appendChild(sourcePath);
+ return link;
+ }
+ var TASK_CHECKLIST_STATUS = {
+ implemented: ['✓', 'viewer.passport.taskChecklist.status.implemented'],
+ 'needs-review': ['?', 'viewer.passport.taskChecklist.status.needsReview'],
+ pending: ['○', 'viewer.passport.taskChecklist.status.pending']
+ };
+ // Read-only: authored statuses and verified task sources, never edited here.
+ function renderTaskChecklist(id) {
+ if (!taskChecklistSection) return;
+ taskChecklistSection.open = false;
+ taskChecklistTasks.textContent = '';
+ var tasks = Archify.taskChecklists ? Archify.taskChecklists.node(id) : [];
+ var repository = Archify.sourceEvidence.repository();
+ taskChecklistSection.hidden = !tasks.length;
+ tasks.forEach(function (task, index) {
+ var item = document.createElement('li');
+ item.className = 'semantic-passport-task-checklist-task';
+ item.setAttribute('data-status', task.status);
+ var status = TASK_CHECKLIST_STATUS[task.status] || TASK_CHECKLIST_STATUS.pending;
+ var mark = document.createElement('span');
+ mark.className = 'semantic-passport-task-checklist-mark';
+ mark.setAttribute('role', 'img');
+ mark.setAttribute('aria-label', viewerText(status[1]));
+ mark.textContent = status[0];
+ var title = document.createElement('strong');
+ title.textContent = task.title;
+ item.appendChild(mark);
+ item.appendChild(title);
+ if (task.criteria) {
+ var criteria = document.createElement('p');
+ criteria.textContent = task.criteria;
+ item.appendChild(criteria);
}
- var name = document.createElement('strong');
- name.textContent = source.label || source.path.split('/').pop() || source.path;
- var location = document.createElement('code');
- location.textContent = source.line
- ? 'L' + source.line + (source.endLine && source.endLine !== source.line ? '–' + source.endLine : '') + (source.href ? ' ↗' : '')
- : source.href ? viewerText('viewer.passport.source.openLink') : '';
- var sourcePath = document.createElement('small');
- sourcePath.textContent = source.path;
- link.appendChild(name);
- link.appendChild(location);
- link.appendChild(sourcePath);
- evidenceLinks.appendChild(link);
+ Archify.sourceEvidence.task(id, index).forEach(function (source) { item.appendChild(sourceLink(source, repository)); });
+ taskChecklistTasks.appendChild(item);
+ });
+ taskChecklistCount.textContent = viewerText('viewer.passport.taskChecklist.count', {
+ implemented: tasks.filter(function (task) { return task.status === 'implemented'; }).length,
+ total: tasks.length
});
- evidence.hidden = false;
}
function explanationFor(id) {
return Archify.explanations && typeof Archify.explanations.node === 'function' ? Archify.explanations.node(id) : null;
@@ -9065,6 +9190,7 @@
Single-child href
semanticId.hidden = false;
renderDrilldowns(node);
renderSourceEvidence(id);
+ renderTaskChecklist(id);
}
/**
* 仅允许同目录文件名.html(可选 ?query / #fragment)。
@@ -10123,6 +10249,10 @@
Single-child href
evidenceLinks.textContent = '';
repositoryLink.removeAttribute('href');
repositoryLink.textContent = '';
+ if (taskChecklistSection) {
+ taskChecklistSection.hidden = true;
+ taskChecklistTasks.textContent = '';
+ }
summary.textContent = '';
reachSection.hidden = true;
upstreamCount.textContent = '0';
@@ -10305,6 +10435,7 @@
Single-child href
clearBtn.addEventListener('click', function () { clear({ restoreFocus: true }); });
copyBtn.addEventListener('click', copyFocusLink);
if (explainBtn) explainBtn.addEventListener('click', function (event) { event.stopPropagation(); toggleExplanation(); });
+ if (taskChecklistSection) taskChecklistSection.addEventListener('toggle', function () { if (!chip.hidden) requestLensPlacement(); });
if (explanationBack) {
explanationBack.addEventListener('click', function (event) { event.stopPropagation(); closeExplanation(); });
}
diff --git a/examples/erd-orders-rendered.html b/examples/erd-orders-rendered.html
index 29c209430..06fae6751 100644
--- a/examples/erd-orders-rendered.html
+++ b/examples/erd-orders-rendered.html
@@ -2078,6 +2078,55 @@
color: var(--text-faint);
font-size: 0.5625rem;
}
+ /* Authored task checklist: read-only, collapsed to one counted line. */
+ .semantic-passport-task-checklist {
+ margin-top: 0.52rem;
+ padding-top: 0.48rem;
+ border-top: 1px solid color-mix(in srgb, var(--toolbar-border) 76%, transparent);
+ }
+ .semantic-passport-task-checklist > summary { cursor: pointer; }
+ .semantic-passport-task-checklist-count {
+ color: var(--text-muted);
+ font-size: 0.5625rem;
+ }
+ .semantic-passport-task-checklist-tasks {
+ display: grid;
+ gap: 0.32rem;
+ max-height: min(16rem, 40vh);
+ margin: 0.45rem 0 0;
+ padding: 0.15rem;
+ overflow: auto;
+ overscroll-behavior: contain;
+ scrollbar-width: thin;
+ scrollbar-color: var(--frontend-stroke) transparent;
+ list-style: none;
+ }
+ .semantic-passport-task-checklist-task {
+ display: grid;
+ grid-template-columns: 0.9rem minmax(0, 1fr);
+ gap: 0.12rem 0.3rem;
+ }
+ .semantic-passport-task-checklist-mark {
+ color: var(--text-faint);
+ font-size: 0.625rem;
+ font-weight: 800;
+ text-align: center;
+ }
+ .semantic-passport-task-checklist-task[data-status="implemented"] .semantic-passport-task-checklist-mark { color: var(--frontend-stroke); }
+ .semantic-passport-task-checklist-task[data-status="needs-review"] .semantic-passport-task-checklist-mark { color: var(--text-muted); }
+ .semantic-passport-task-checklist-task > strong {
+ color: var(--text-muted);
+ font-size: 0.6875rem;
+ overflow-wrap: anywhere;
+ }
+ .semantic-passport-task-checklist-task p,
+ .semantic-passport-task-checklist-task .semantic-passport-source { grid-column: 2; }
+ .semantic-passport-task-checklist-task p {
+ margin: 0;
+ color: var(--text-faint);
+ font-size: 0.5625rem;
+ overflow-wrap: anywhere;
+ }
.semantic-passport-reach {
display: grid;
gap: 0.28rem;
@@ -2086,7 +2135,8 @@
border-top: 1px solid color-mix(in srgb, var(--toolbar-border) 76%, transparent);
}
.semantic-passport-reach[hidden] { display: none; }
- .semantic-passport-reach-label {
+ .semantic-passport-reach-label,
+ .semantic-passport-task-checklist-label {
color: var(--text-faint);
font-size: 0.5rem;
font-weight: 800;
@@ -2154,8 +2204,10 @@
scrollbar-width: thin;
scrollbar-color: var(--frontend-stroke) transparent;
}
- .relationship-lens-list::-webkit-scrollbar { width: 4px; }
- .relationship-lens-list::-webkit-scrollbar-thumb {
+ .relationship-lens-list::-webkit-scrollbar,
+ .semantic-passport-task-checklist-tasks::-webkit-scrollbar { width: 4px; }
+ .relationship-lens-list::-webkit-scrollbar-thumb,
+ .semantic-passport-task-checklist-tasks::-webkit-scrollbar-thumb {
border-radius: 999px;
background: color-mix(in srgb, var(--frontend-stroke) 68%, transparent);
}
@@ -2657,8 +2709,9 @@
as are dark themes and presets that already pass. Zero counts stay faint
on purpose, so they are excluded from the darker rule. Route-target
finders keep their --security-stroke status color, so they are excluded
- too. */
+ too. Task checklist done marks are the same kind of text. */
html[data-theme="light"] .semantic-passport-meta [data-passport="kind"],
+ html[data-theme="light"] .semantic-passport-task-checklist-task[data-status="implemented"] .semantic-passport-task-checklist-mark,
html[data-theme="light"] .node-finder:not([data-context="route-target"]) .node-finder-result em:not([data-zero]) {
color: var(--reader-accent, var(--frontend-stroke));
}
@@ -5406,7 +5459,8 @@
Order management schema
-
+
+
@@ -5990,6 +6044,10 @@
Order management schema
Ctrl+click or double-click opens the first entry
+
+ Task checklist
+
+
Authored reach
@@ -8803,6 +8861,24 @@
How the bands read
count: function () { return payload ? Object.keys(payload).length : 0; }
};
})();
+ /* Authored node task checklists: passport-only, like explanations. Their task
+ sources are verified and read through Archify.sourceEvidence.task(). */
+ Archify.taskChecklists = (function () {
+ var element = document.getElementById('archify-task-checklists-data');
+ var payload = null;
+ if (element) {
+ try {
+ var parsed = JSON.parse(element.textContent || 'null');
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) payload = parsed;
+ } catch (_) {}
+ }
+ return {
+ node: function (id) {
+ var tasks = payload && Object.prototype.hasOwnProperty.call(payload, id) ? payload[id] : null;
+ return Array.isArray(tasks) ? tasks.slice() : [];
+ }
+ };
+ })();
/* Verified repository evidence is emitted only after the renderer checks
the authored GitHub revision, source blobs, and optional line ranges
against the explicit --repo-root. It remains outside the SVG so every
@@ -8842,6 +8918,11 @@
How the bands read
node: function (id) {
var sources = payload && payload.nodes ? payload.nodes[id] : null;
return Array.isArray(sources) ? sources.slice() : [];
+ },
+ task: function (id, index) {
+ var tasks = payload && payload.taskChecklists ? payload.taskChecklists[id] : null;
+ var sources = Array.isArray(tasks) ? tasks[index] : null;
+ return Array.isArray(sources) ? sources.slice() : [];
}
};
})();
@@ -9017,6 +9098,9 @@
How the bands read
var parentBackBtn = document.getElementById('btn-parent-back');
var drilldownsSection = document.getElementById('focus-drilldowns');
var drilldownsList = document.getElementById('focus-drilldowns-list');
+ var taskChecklistSection = document.getElementById('focus-task-checklist');
+ var taskChecklistCount = document.getElementById('focus-task-checklist-count');
+ var taskChecklistTasks = document.getElementById('focus-task-checklist-tasks');
var moveBtn = document.getElementById('btn-focus-move');
var activeIds = [];
var hoveredRelationship = null;
@@ -9338,30 +9422,71 @@
How the bands read
repositoryLink.setAttribute('aria-label', viewerText('viewer.passport.repository.open', { revision: repository.revision }));
}
evidence.title = viewerText('viewer.passport.verificationScope');
- sources.forEach(function (source) {
- var link = document.createElement(source.href ? 'a' : 'div');
- link.className = 'semantic-passport-source';
- if (source.href) {
- link.href = source.href;
- link.target = '_blank';
- link.rel = 'noopener noreferrer';
- link.referrerPolicy = 'no-referrer';
- link.setAttribute('aria-label', viewerText('viewer.passport.source.open', { path: source.path, revision: repository.shortRevision }));
+ sources.forEach(function (source) { evidenceLinks.appendChild(sourceLink(source, repository)); });
+ evidence.hidden = false;
+ }
+ function sourceLink(source, repository) {
+ var link = document.createElement(source.href ? 'a' : 'div');
+ link.className = 'semantic-passport-source';
+ if (source.href) {
+ link.href = source.href;
+ link.target = '_blank';
+ link.rel = 'noopener noreferrer';
+ link.referrerPolicy = 'no-referrer';
+ link.setAttribute('aria-label', viewerText('viewer.passport.source.open', { path: source.path, revision: repository.shortRevision }));
+ }
+ var name = document.createElement('strong');
+ name.textContent = source.label || source.path.split('/').pop() || source.path;
+ var location = document.createElement('code');
+ location.textContent = source.line
+ ? 'L' + source.line + (source.endLine && source.endLine !== source.line ? '–' + source.endLine : '') + (source.href ? ' ↗' : '')
+ : source.href ? viewerText('viewer.passport.source.openLink') : '';
+ var sourcePath = document.createElement('small');
+ sourcePath.textContent = source.path;
+ link.appendChild(name);
+ link.appendChild(location);
+ link.appendChild(sourcePath);
+ return link;
+ }
+ var TASK_CHECKLIST_STATUS = {
+ implemented: ['✓', 'viewer.passport.taskChecklist.status.implemented'],
+ 'needs-review': ['?', 'viewer.passport.taskChecklist.status.needsReview'],
+ pending: ['○', 'viewer.passport.taskChecklist.status.pending']
+ };
+ // Read-only: authored statuses and verified task sources, never edited here.
+ function renderTaskChecklist(id) {
+ if (!taskChecklistSection) return;
+ taskChecklistSection.open = false;
+ taskChecklistTasks.textContent = '';
+ var tasks = Archify.taskChecklists ? Archify.taskChecklists.node(id) : [];
+ var repository = Archify.sourceEvidence.repository();
+ taskChecklistSection.hidden = !tasks.length;
+ tasks.forEach(function (task, index) {
+ var item = document.createElement('li');
+ item.className = 'semantic-passport-task-checklist-task';
+ item.setAttribute('data-status', task.status);
+ var status = TASK_CHECKLIST_STATUS[task.status] || TASK_CHECKLIST_STATUS.pending;
+ var mark = document.createElement('span');
+ mark.className = 'semantic-passport-task-checklist-mark';
+ mark.setAttribute('role', 'img');
+ mark.setAttribute('aria-label', viewerText(status[1]));
+ mark.textContent = status[0];
+ var title = document.createElement('strong');
+ title.textContent = task.title;
+ item.appendChild(mark);
+ item.appendChild(title);
+ if (task.criteria) {
+ var criteria = document.createElement('p');
+ criteria.textContent = task.criteria;
+ item.appendChild(criteria);
}
- var name = document.createElement('strong');
- name.textContent = source.label || source.path.split('/').pop() || source.path;
- var location = document.createElement('code');
- location.textContent = source.line
- ? 'L' + source.line + (source.endLine && source.endLine !== source.line ? '–' + source.endLine : '') + (source.href ? ' ↗' : '')
- : source.href ? viewerText('viewer.passport.source.openLink') : '';
- var sourcePath = document.createElement('small');
- sourcePath.textContent = source.path;
- link.appendChild(name);
- link.appendChild(location);
- link.appendChild(sourcePath);
- evidenceLinks.appendChild(link);
+ Archify.sourceEvidence.task(id, index).forEach(function (source) { item.appendChild(sourceLink(source, repository)); });
+ taskChecklistTasks.appendChild(item);
+ });
+ taskChecklistCount.textContent = viewerText('viewer.passport.taskChecklist.count', {
+ implemented: tasks.filter(function (task) { return task.status === 'implemented'; }).length,
+ total: tasks.length
});
- evidence.hidden = false;
}
function explanationFor(id) {
return Archify.explanations && typeof Archify.explanations.node === 'function' ? Archify.explanations.node(id) : null;
@@ -9444,6 +9569,7 @@
How the bands read
semanticId.hidden = false;
renderDrilldowns(node);
renderSourceEvidence(id);
+ renderTaskChecklist(id);
}
/**
* 仅允许同目录文件名.html(可选 ?query / #fragment)。
@@ -10502,6 +10628,10 @@
How the bands read
evidenceLinks.textContent = '';
repositoryLink.removeAttribute('href');
repositoryLink.textContent = '';
+ if (taskChecklistSection) {
+ taskChecklistSection.hidden = true;
+ taskChecklistTasks.textContent = '';
+ }
summary.textContent = '';
reachSection.hidden = true;
upstreamCount.textContent = '0';
@@ -10684,6 +10814,7 @@
How the bands read
clearBtn.addEventListener('click', function () { clear({ restoreFocus: true }); });
copyBtn.addEventListener('click', copyFocusLink);
if (explainBtn) explainBtn.addEventListener('click', function (event) { event.stopPropagation(); toggleExplanation(); });
+ if (taskChecklistSection) taskChecklistSection.addEventListener('toggle', function () { if (!chip.hidden) requestLensPlacement(); });
if (explanationBack) {
explanationBack.addEventListener('click', function (event) { event.stopPropagation(); closeExplanation(); });
}
diff --git a/examples/lifecycle-agent-run.html b/examples/lifecycle-agent-run.html
index 8c99b9048..5cb12fb42 100644
--- a/examples/lifecycle-agent-run.html
+++ b/examples/lifecycle-agent-run.html
@@ -2078,6 +2078,55 @@
color: var(--text-faint);
font-size: 0.5625rem;
}
+ /* Authored task checklist: read-only, collapsed to one counted line. */
+ .semantic-passport-task-checklist {
+ margin-top: 0.52rem;
+ padding-top: 0.48rem;
+ border-top: 1px solid color-mix(in srgb, var(--toolbar-border) 76%, transparent);
+ }
+ .semantic-passport-task-checklist > summary { cursor: pointer; }
+ .semantic-passport-task-checklist-count {
+ color: var(--text-muted);
+ font-size: 0.5625rem;
+ }
+ .semantic-passport-task-checklist-tasks {
+ display: grid;
+ gap: 0.32rem;
+ max-height: min(16rem, 40vh);
+ margin: 0.45rem 0 0;
+ padding: 0.15rem;
+ overflow: auto;
+ overscroll-behavior: contain;
+ scrollbar-width: thin;
+ scrollbar-color: var(--frontend-stroke) transparent;
+ list-style: none;
+ }
+ .semantic-passport-task-checklist-task {
+ display: grid;
+ grid-template-columns: 0.9rem minmax(0, 1fr);
+ gap: 0.12rem 0.3rem;
+ }
+ .semantic-passport-task-checklist-mark {
+ color: var(--text-faint);
+ font-size: 0.625rem;
+ font-weight: 800;
+ text-align: center;
+ }
+ .semantic-passport-task-checklist-task[data-status="implemented"] .semantic-passport-task-checklist-mark { color: var(--frontend-stroke); }
+ .semantic-passport-task-checklist-task[data-status="needs-review"] .semantic-passport-task-checklist-mark { color: var(--text-muted); }
+ .semantic-passport-task-checklist-task > strong {
+ color: var(--text-muted);
+ font-size: 0.6875rem;
+ overflow-wrap: anywhere;
+ }
+ .semantic-passport-task-checklist-task p,
+ .semantic-passport-task-checklist-task .semantic-passport-source { grid-column: 2; }
+ .semantic-passport-task-checklist-task p {
+ margin: 0;
+ color: var(--text-faint);
+ font-size: 0.5625rem;
+ overflow-wrap: anywhere;
+ }
.semantic-passport-reach {
display: grid;
gap: 0.28rem;
@@ -2086,7 +2135,8 @@
border-top: 1px solid color-mix(in srgb, var(--toolbar-border) 76%, transparent);
}
.semantic-passport-reach[hidden] { display: none; }
- .semantic-passport-reach-label {
+ .semantic-passport-reach-label,
+ .semantic-passport-task-checklist-label {
color: var(--text-faint);
font-size: 0.5rem;
font-weight: 800;
@@ -2154,8 +2204,10 @@
scrollbar-width: thin;
scrollbar-color: var(--frontend-stroke) transparent;
}
- .relationship-lens-list::-webkit-scrollbar { width: 4px; }
- .relationship-lens-list::-webkit-scrollbar-thumb {
+ .relationship-lens-list::-webkit-scrollbar,
+ .semantic-passport-task-checklist-tasks::-webkit-scrollbar { width: 4px; }
+ .relationship-lens-list::-webkit-scrollbar-thumb,
+ .semantic-passport-task-checklist-tasks::-webkit-scrollbar-thumb {
border-radius: 999px;
background: color-mix(in srgb, var(--frontend-stroke) 68%, transparent);
}
@@ -2657,8 +2709,9 @@
as are dark themes and presets that already pass. Zero counts stay faint
on purpose, so they are excluded from the darker rule. Route-target
finders keep their --security-stroke status color, so they are excluded
- too. */
+ too. Task checklist done marks are the same kind of text. */
html[data-theme="light"] .semantic-passport-meta [data-passport="kind"],
+ html[data-theme="light"] .semantic-passport-task-checklist-task[data-status="implemented"] .semantic-passport-task-checklist-mark,
html[data-theme="light"] .node-finder:not([data-context="route-target"]) .node-finder-result em:not([data-zero]) {
color: var(--reader-accent, var(--frontend-stroke));
}
@@ -5406,7 +5459,8 @@
Agent Run Lifecycle
-
+
+
@@ -5784,6 +5838,10 @@
Agent Run Lifecycle
Ctrl+click or double-click opens the first entry
+
+ Task checklist
+
+
Authored reach
@@ -8583,6 +8641,24 @@
Recovery + Terminal Exits
count: function () { return payload ? Object.keys(payload).length : 0; }
};
})();
+ /* Authored node task checklists: passport-only, like explanations. Their task
+ sources are verified and read through Archify.sourceEvidence.task(). */
+ Archify.taskChecklists = (function () {
+ var element = document.getElementById('archify-task-checklists-data');
+ var payload = null;
+ if (element) {
+ try {
+ var parsed = JSON.parse(element.textContent || 'null');
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) payload = parsed;
+ } catch (_) {}
+ }
+ return {
+ node: function (id) {
+ var tasks = payload && Object.prototype.hasOwnProperty.call(payload, id) ? payload[id] : null;
+ return Array.isArray(tasks) ? tasks.slice() : [];
+ }
+ };
+ })();
/* Verified repository evidence is emitted only after the renderer checks
the authored GitHub revision, source blobs, and optional line ranges
against the explicit --repo-root. It remains outside the SVG so every
@@ -8622,6 +8698,11 @@
Recovery + Terminal Exits
node: function (id) {
var sources = payload && payload.nodes ? payload.nodes[id] : null;
return Array.isArray(sources) ? sources.slice() : [];
+ },
+ task: function (id, index) {
+ var tasks = payload && payload.taskChecklists ? payload.taskChecklists[id] : null;
+ var sources = Array.isArray(tasks) ? tasks[index] : null;
+ return Array.isArray(sources) ? sources.slice() : [];
}
};
})();
@@ -8797,6 +8878,9 @@
Recovery + Terminal Exits
var parentBackBtn = document.getElementById('btn-parent-back');
var drilldownsSection = document.getElementById('focus-drilldowns');
var drilldownsList = document.getElementById('focus-drilldowns-list');
+ var taskChecklistSection = document.getElementById('focus-task-checklist');
+ var taskChecklistCount = document.getElementById('focus-task-checklist-count');
+ var taskChecklistTasks = document.getElementById('focus-task-checklist-tasks');
var moveBtn = document.getElementById('btn-focus-move');
var activeIds = [];
var hoveredRelationship = null;
@@ -9118,30 +9202,71 @@
Recovery + Terminal Exits
repositoryLink.setAttribute('aria-label', viewerText('viewer.passport.repository.open', { revision: repository.revision }));
}
evidence.title = viewerText('viewer.passport.verificationScope');
- sources.forEach(function (source) {
- var link = document.createElement(source.href ? 'a' : 'div');
- link.className = 'semantic-passport-source';
- if (source.href) {
- link.href = source.href;
- link.target = '_blank';
- link.rel = 'noopener noreferrer';
- link.referrerPolicy = 'no-referrer';
- link.setAttribute('aria-label', viewerText('viewer.passport.source.open', { path: source.path, revision: repository.shortRevision }));
+ sources.forEach(function (source) { evidenceLinks.appendChild(sourceLink(source, repository)); });
+ evidence.hidden = false;
+ }
+ function sourceLink(source, repository) {
+ var link = document.createElement(source.href ? 'a' : 'div');
+ link.className = 'semantic-passport-source';
+ if (source.href) {
+ link.href = source.href;
+ link.target = '_blank';
+ link.rel = 'noopener noreferrer';
+ link.referrerPolicy = 'no-referrer';
+ link.setAttribute('aria-label', viewerText('viewer.passport.source.open', { path: source.path, revision: repository.shortRevision }));
+ }
+ var name = document.createElement('strong');
+ name.textContent = source.label || source.path.split('/').pop() || source.path;
+ var location = document.createElement('code');
+ location.textContent = source.line
+ ? 'L' + source.line + (source.endLine && source.endLine !== source.line ? '–' + source.endLine : '') + (source.href ? ' ↗' : '')
+ : source.href ? viewerText('viewer.passport.source.openLink') : '';
+ var sourcePath = document.createElement('small');
+ sourcePath.textContent = source.path;
+ link.appendChild(name);
+ link.appendChild(location);
+ link.appendChild(sourcePath);
+ return link;
+ }
+ var TASK_CHECKLIST_STATUS = {
+ implemented: ['✓', 'viewer.passport.taskChecklist.status.implemented'],
+ 'needs-review': ['?', 'viewer.passport.taskChecklist.status.needsReview'],
+ pending: ['○', 'viewer.passport.taskChecklist.status.pending']
+ };
+ // Read-only: authored statuses and verified task sources, never edited here.
+ function renderTaskChecklist(id) {
+ if (!taskChecklistSection) return;
+ taskChecklistSection.open = false;
+ taskChecklistTasks.textContent = '';
+ var tasks = Archify.taskChecklists ? Archify.taskChecklists.node(id) : [];
+ var repository = Archify.sourceEvidence.repository();
+ taskChecklistSection.hidden = !tasks.length;
+ tasks.forEach(function (task, index) {
+ var item = document.createElement('li');
+ item.className = 'semantic-passport-task-checklist-task';
+ item.setAttribute('data-status', task.status);
+ var status = TASK_CHECKLIST_STATUS[task.status] || TASK_CHECKLIST_STATUS.pending;
+ var mark = document.createElement('span');
+ mark.className = 'semantic-passport-task-checklist-mark';
+ mark.setAttribute('role', 'img');
+ mark.setAttribute('aria-label', viewerText(status[1]));
+ mark.textContent = status[0];
+ var title = document.createElement('strong');
+ title.textContent = task.title;
+ item.appendChild(mark);
+ item.appendChild(title);
+ if (task.criteria) {
+ var criteria = document.createElement('p');
+ criteria.textContent = task.criteria;
+ item.appendChild(criteria);
}
- var name = document.createElement('strong');
- name.textContent = source.label || source.path.split('/').pop() || source.path;
- var location = document.createElement('code');
- location.textContent = source.line
- ? 'L' + source.line + (source.endLine && source.endLine !== source.line ? '–' + source.endLine : '') + (source.href ? ' ↗' : '')
- : source.href ? viewerText('viewer.passport.source.openLink') : '';
- var sourcePath = document.createElement('small');
- sourcePath.textContent = source.path;
- link.appendChild(name);
- link.appendChild(location);
- link.appendChild(sourcePath);
- evidenceLinks.appendChild(link);
+ Archify.sourceEvidence.task(id, index).forEach(function (source) { item.appendChild(sourceLink(source, repository)); });
+ taskChecklistTasks.appendChild(item);
+ });
+ taskChecklistCount.textContent = viewerText('viewer.passport.taskChecklist.count', {
+ implemented: tasks.filter(function (task) { return task.status === 'implemented'; }).length,
+ total: tasks.length
});
- evidence.hidden = false;
}
function explanationFor(id) {
return Archify.explanations && typeof Archify.explanations.node === 'function' ? Archify.explanations.node(id) : null;
@@ -9224,6 +9349,7 @@
Recovery + Terminal Exits
semanticId.hidden = false;
renderDrilldowns(node);
renderSourceEvidence(id);
+ renderTaskChecklist(id);
}
/**
* 仅允许同目录文件名.html(可选 ?query / #fragment)。
@@ -10282,6 +10408,10 @@
Recovery + Terminal Exits
evidenceLinks.textContent = '';
repositoryLink.removeAttribute('href');
repositoryLink.textContent = '';
+ if (taskChecklistSection) {
+ taskChecklistSection.hidden = true;
+ taskChecklistTasks.textContent = '';
+ }
summary.textContent = '';
reachSection.hidden = true;
upstreamCount.textContent = '0';
@@ -10464,6 +10594,7 @@
Recovery + Terminal Exits
clearBtn.addEventListener('click', function () { clear({ restoreFocus: true }); });
copyBtn.addEventListener('click', copyFocusLink);
if (explainBtn) explainBtn.addEventListener('click', function (event) { event.stopPropagation(); toggleExplanation(); });
+ if (taskChecklistSection) taskChecklistSection.addEventListener('toggle', function () { if (!chip.hidden) requestLensPlacement(); });
if (explanationBack) {
explanationBack.addEventListener('click', function (event) { event.stopPropagation(); closeExplanation(); });
}
diff --git a/examples/sequence-cache-miss-request.html b/examples/sequence-cache-miss-request.html
index 9efa34fb8..ba9f94a4a 100644
--- a/examples/sequence-cache-miss-request.html
+++ b/examples/sequence-cache-miss-request.html
@@ -2078,6 +2078,55 @@
color: var(--text-faint);
font-size: 0.5625rem;
}
+ /* Authored task checklist: read-only, collapsed to one counted line. */
+ .semantic-passport-task-checklist {
+ margin-top: 0.52rem;
+ padding-top: 0.48rem;
+ border-top: 1px solid color-mix(in srgb, var(--toolbar-border) 76%, transparent);
+ }
+ .semantic-passport-task-checklist > summary { cursor: pointer; }
+ .semantic-passport-task-checklist-count {
+ color: var(--text-muted);
+ font-size: 0.5625rem;
+ }
+ .semantic-passport-task-checklist-tasks {
+ display: grid;
+ gap: 0.32rem;
+ max-height: min(16rem, 40vh);
+ margin: 0.45rem 0 0;
+ padding: 0.15rem;
+ overflow: auto;
+ overscroll-behavior: contain;
+ scrollbar-width: thin;
+ scrollbar-color: var(--frontend-stroke) transparent;
+ list-style: none;
+ }
+ .semantic-passport-task-checklist-task {
+ display: grid;
+ grid-template-columns: 0.9rem minmax(0, 1fr);
+ gap: 0.12rem 0.3rem;
+ }
+ .semantic-passport-task-checklist-mark {
+ color: var(--text-faint);
+ font-size: 0.625rem;
+ font-weight: 800;
+ text-align: center;
+ }
+ .semantic-passport-task-checklist-task[data-status="implemented"] .semantic-passport-task-checklist-mark { color: var(--frontend-stroke); }
+ .semantic-passport-task-checklist-task[data-status="needs-review"] .semantic-passport-task-checklist-mark { color: var(--text-muted); }
+ .semantic-passport-task-checklist-task > strong {
+ color: var(--text-muted);
+ font-size: 0.6875rem;
+ overflow-wrap: anywhere;
+ }
+ .semantic-passport-task-checklist-task p,
+ .semantic-passport-task-checklist-task .semantic-passport-source { grid-column: 2; }
+ .semantic-passport-task-checklist-task p {
+ margin: 0;
+ color: var(--text-faint);
+ font-size: 0.5625rem;
+ overflow-wrap: anywhere;
+ }
.semantic-passport-reach {
display: grid;
gap: 0.28rem;
@@ -2086,7 +2135,8 @@
border-top: 1px solid color-mix(in srgb, var(--toolbar-border) 76%, transparent);
}
.semantic-passport-reach[hidden] { display: none; }
- .semantic-passport-reach-label {
+ .semantic-passport-reach-label,
+ .semantic-passport-task-checklist-label {
color: var(--text-faint);
font-size: 0.5rem;
font-weight: 800;
@@ -2154,8 +2204,10 @@
scrollbar-width: thin;
scrollbar-color: var(--frontend-stroke) transparent;
}
- .relationship-lens-list::-webkit-scrollbar { width: 4px; }
- .relationship-lens-list::-webkit-scrollbar-thumb {
+ .relationship-lens-list::-webkit-scrollbar,
+ .semantic-passport-task-checklist-tasks::-webkit-scrollbar { width: 4px; }
+ .relationship-lens-list::-webkit-scrollbar-thumb,
+ .semantic-passport-task-checklist-tasks::-webkit-scrollbar-thumb {
border-radius: 999px;
background: color-mix(in srgb, var(--frontend-stroke) 68%, transparent);
}
@@ -2657,8 +2709,9 @@
as are dark themes and presets that already pass. Zero counts stay faint
on purpose, so they are excluded from the darker rule. Route-target
finders keep their --security-stroke status color, so they are excluded
- too. */
+ too. Task checklist done marks are the same kind of text. */
html[data-theme="light"] .semantic-passport-meta [data-passport="kind"],
+ html[data-theme="light"] .semantic-passport-task-checklist-task[data-status="implemented"] .semantic-passport-task-checklist-mark,
html[data-theme="light"] .node-finder:not([data-context="route-target"]) .node-finder-result em:not([data-zero]) {
color: var(--reader-accent, var(--frontend-stroke));
}
@@ -5406,7 +5459,8 @@
Cache Miss Request Sequence
-
+
+
@@ -5773,6 +5827,10 @@
Cache Miss Request Sequence
Ctrl+click or double-click opens the first entry
+
+ Task checklist
+
+
Authored reach
@@ -8586,6 +8644,24 @@
Async Trace
count: function () { return payload ? Object.keys(payload).length : 0; }
};
})();
+ /* Authored node task checklists: passport-only, like explanations. Their task
+ sources are verified and read through Archify.sourceEvidence.task(). */
+ Archify.taskChecklists = (function () {
+ var element = document.getElementById('archify-task-checklists-data');
+ var payload = null;
+ if (element) {
+ try {
+ var parsed = JSON.parse(element.textContent || 'null');
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) payload = parsed;
+ } catch (_) {}
+ }
+ return {
+ node: function (id) {
+ var tasks = payload && Object.prototype.hasOwnProperty.call(payload, id) ? payload[id] : null;
+ return Array.isArray(tasks) ? tasks.slice() : [];
+ }
+ };
+ })();
/* Verified repository evidence is emitted only after the renderer checks
the authored GitHub revision, source blobs, and optional line ranges
against the explicit --repo-root. It remains outside the SVG so every
@@ -8625,6 +8701,11 @@
Async Trace
node: function (id) {
var sources = payload && payload.nodes ? payload.nodes[id] : null;
return Array.isArray(sources) ? sources.slice() : [];
+ },
+ task: function (id, index) {
+ var tasks = payload && payload.taskChecklists ? payload.taskChecklists[id] : null;
+ var sources = Array.isArray(tasks) ? tasks[index] : null;
+ return Array.isArray(sources) ? sources.slice() : [];
}
};
})();
@@ -8800,6 +8881,9 @@
Async Trace
var parentBackBtn = document.getElementById('btn-parent-back');
var drilldownsSection = document.getElementById('focus-drilldowns');
var drilldownsList = document.getElementById('focus-drilldowns-list');
+ var taskChecklistSection = document.getElementById('focus-task-checklist');
+ var taskChecklistCount = document.getElementById('focus-task-checklist-count');
+ var taskChecklistTasks = document.getElementById('focus-task-checklist-tasks');
var moveBtn = document.getElementById('btn-focus-move');
var activeIds = [];
var hoveredRelationship = null;
@@ -9121,30 +9205,71 @@
Async Trace
repositoryLink.setAttribute('aria-label', viewerText('viewer.passport.repository.open', { revision: repository.revision }));
}
evidence.title = viewerText('viewer.passport.verificationScope');
- sources.forEach(function (source) {
- var link = document.createElement(source.href ? 'a' : 'div');
- link.className = 'semantic-passport-source';
- if (source.href) {
- link.href = source.href;
- link.target = '_blank';
- link.rel = 'noopener noreferrer';
- link.referrerPolicy = 'no-referrer';
- link.setAttribute('aria-label', viewerText('viewer.passport.source.open', { path: source.path, revision: repository.shortRevision }));
+ sources.forEach(function (source) { evidenceLinks.appendChild(sourceLink(source, repository)); });
+ evidence.hidden = false;
+ }
+ function sourceLink(source, repository) {
+ var link = document.createElement(source.href ? 'a' : 'div');
+ link.className = 'semantic-passport-source';
+ if (source.href) {
+ link.href = source.href;
+ link.target = '_blank';
+ link.rel = 'noopener noreferrer';
+ link.referrerPolicy = 'no-referrer';
+ link.setAttribute('aria-label', viewerText('viewer.passport.source.open', { path: source.path, revision: repository.shortRevision }));
+ }
+ var name = document.createElement('strong');
+ name.textContent = source.label || source.path.split('/').pop() || source.path;
+ var location = document.createElement('code');
+ location.textContent = source.line
+ ? 'L' + source.line + (source.endLine && source.endLine !== source.line ? '–' + source.endLine : '') + (source.href ? ' ↗' : '')
+ : source.href ? viewerText('viewer.passport.source.openLink') : '';
+ var sourcePath = document.createElement('small');
+ sourcePath.textContent = source.path;
+ link.appendChild(name);
+ link.appendChild(location);
+ link.appendChild(sourcePath);
+ return link;
+ }
+ var TASK_CHECKLIST_STATUS = {
+ implemented: ['✓', 'viewer.passport.taskChecklist.status.implemented'],
+ 'needs-review': ['?', 'viewer.passport.taskChecklist.status.needsReview'],
+ pending: ['○', 'viewer.passport.taskChecklist.status.pending']
+ };
+ // Read-only: authored statuses and verified task sources, never edited here.
+ function renderTaskChecklist(id) {
+ if (!taskChecklistSection) return;
+ taskChecklistSection.open = false;
+ taskChecklistTasks.textContent = '';
+ var tasks = Archify.taskChecklists ? Archify.taskChecklists.node(id) : [];
+ var repository = Archify.sourceEvidence.repository();
+ taskChecklistSection.hidden = !tasks.length;
+ tasks.forEach(function (task, index) {
+ var item = document.createElement('li');
+ item.className = 'semantic-passport-task-checklist-task';
+ item.setAttribute('data-status', task.status);
+ var status = TASK_CHECKLIST_STATUS[task.status] || TASK_CHECKLIST_STATUS.pending;
+ var mark = document.createElement('span');
+ mark.className = 'semantic-passport-task-checklist-mark';
+ mark.setAttribute('role', 'img');
+ mark.setAttribute('aria-label', viewerText(status[1]));
+ mark.textContent = status[0];
+ var title = document.createElement('strong');
+ title.textContent = task.title;
+ item.appendChild(mark);
+ item.appendChild(title);
+ if (task.criteria) {
+ var criteria = document.createElement('p');
+ criteria.textContent = task.criteria;
+ item.appendChild(criteria);
}
- var name = document.createElement('strong');
- name.textContent = source.label || source.path.split('/').pop() || source.path;
- var location = document.createElement('code');
- location.textContent = source.line
- ? 'L' + source.line + (source.endLine && source.endLine !== source.line ? '–' + source.endLine : '') + (source.href ? ' ↗' : '')
- : source.href ? viewerText('viewer.passport.source.openLink') : '';
- var sourcePath = document.createElement('small');
- sourcePath.textContent = source.path;
- link.appendChild(name);
- link.appendChild(location);
- link.appendChild(sourcePath);
- evidenceLinks.appendChild(link);
+ Archify.sourceEvidence.task(id, index).forEach(function (source) { item.appendChild(sourceLink(source, repository)); });
+ taskChecklistTasks.appendChild(item);
+ });
+ taskChecklistCount.textContent = viewerText('viewer.passport.taskChecklist.count', {
+ implemented: tasks.filter(function (task) { return task.status === 'implemented'; }).length,
+ total: tasks.length
});
- evidence.hidden = false;
}
function explanationFor(id) {
return Archify.explanations && typeof Archify.explanations.node === 'function' ? Archify.explanations.node(id) : null;
@@ -9227,6 +9352,7 @@
Async Trace
semanticId.hidden = false;
renderDrilldowns(node);
renderSourceEvidence(id);
+ renderTaskChecklist(id);
}
/**
* 仅允许同目录文件名.html(可选 ?query / #fragment)。
@@ -10285,6 +10411,10 @@
Async Trace
evidenceLinks.textContent = '';
repositoryLink.removeAttribute('href');
repositoryLink.textContent = '';
+ if (taskChecklistSection) {
+ taskChecklistSection.hidden = true;
+ taskChecklistTasks.textContent = '';
+ }
summary.textContent = '';
reachSection.hidden = true;
upstreamCount.textContent = '0';
@@ -10467,6 +10597,7 @@
Async Trace
clearBtn.addEventListener('click', function () { clear({ restoreFocus: true }); });
copyBtn.addEventListener('click', copyFocusLink);
if (explainBtn) explainBtn.addEventListener('click', function (event) { event.stopPropagation(); toggleExplanation(); });
+ if (taskChecklistSection) taskChecklistSection.addEventListener('toggle', function () { if (!chip.hidden) requestLensPlacement(); });
if (explanationBack) {
explanationBack.addEventListener('click', function (event) { event.stopPropagation(); closeExplanation(); });
}
diff --git a/examples/subscription-billing-rendered.html b/examples/subscription-billing-rendered.html
index 5ba5434da..24c0ec632 100644
--- a/examples/subscription-billing-rendered.html
+++ b/examples/subscription-billing-rendered.html
@@ -2078,6 +2078,55 @@
color: var(--text-faint);
font-size: 0.5625rem;
}
+ /* Authored task checklist: read-only, collapsed to one counted line. */
+ .semantic-passport-task-checklist {
+ margin-top: 0.52rem;
+ padding-top: 0.48rem;
+ border-top: 1px solid color-mix(in srgb, var(--toolbar-border) 76%, transparent);
+ }
+ .semantic-passport-task-checklist > summary { cursor: pointer; }
+ .semantic-passport-task-checklist-count {
+ color: var(--text-muted);
+ font-size: 0.5625rem;
+ }
+ .semantic-passport-task-checklist-tasks {
+ display: grid;
+ gap: 0.32rem;
+ max-height: min(16rem, 40vh);
+ margin: 0.45rem 0 0;
+ padding: 0.15rem;
+ overflow: auto;
+ overscroll-behavior: contain;
+ scrollbar-width: thin;
+ scrollbar-color: var(--frontend-stroke) transparent;
+ list-style: none;
+ }
+ .semantic-passport-task-checklist-task {
+ display: grid;
+ grid-template-columns: 0.9rem minmax(0, 1fr);
+ gap: 0.12rem 0.3rem;
+ }
+ .semantic-passport-task-checklist-mark {
+ color: var(--text-faint);
+ font-size: 0.625rem;
+ font-weight: 800;
+ text-align: center;
+ }
+ .semantic-passport-task-checklist-task[data-status="implemented"] .semantic-passport-task-checklist-mark { color: var(--frontend-stroke); }
+ .semantic-passport-task-checklist-task[data-status="needs-review"] .semantic-passport-task-checklist-mark { color: var(--text-muted); }
+ .semantic-passport-task-checklist-task > strong {
+ color: var(--text-muted);
+ font-size: 0.6875rem;
+ overflow-wrap: anywhere;
+ }
+ .semantic-passport-task-checklist-task p,
+ .semantic-passport-task-checklist-task .semantic-passport-source { grid-column: 2; }
+ .semantic-passport-task-checklist-task p {
+ margin: 0;
+ color: var(--text-faint);
+ font-size: 0.5625rem;
+ overflow-wrap: anywhere;
+ }
.semantic-passport-reach {
display: grid;
gap: 0.28rem;
@@ -2086,7 +2135,8 @@
border-top: 1px solid color-mix(in srgb, var(--toolbar-border) 76%, transparent);
}
.semantic-passport-reach[hidden] { display: none; }
- .semantic-passport-reach-label {
+ .semantic-passport-reach-label,
+ .semantic-passport-task-checklist-label {
color: var(--text-faint);
font-size: 0.5rem;
font-weight: 800;
@@ -2154,8 +2204,10 @@
scrollbar-width: thin;
scrollbar-color: var(--frontend-stroke) transparent;
}
- .relationship-lens-list::-webkit-scrollbar { width: 4px; }
- .relationship-lens-list::-webkit-scrollbar-thumb {
+ .relationship-lens-list::-webkit-scrollbar,
+ .semantic-passport-task-checklist-tasks::-webkit-scrollbar { width: 4px; }
+ .relationship-lens-list::-webkit-scrollbar-thumb,
+ .semantic-passport-task-checklist-tasks::-webkit-scrollbar-thumb {
border-radius: 999px;
background: color-mix(in srgb, var(--frontend-stroke) 68%, transparent);
}
@@ -2657,8 +2709,9 @@
as are dark themes and presets that already pass. Zero counts stay faint
on purpose, so they are excluded from the darker rule. Route-target
finders keep their --security-stroke status color, so they are excluded
- too. */
+ too. Task checklist done marks are the same kind of text. */
html[data-theme="light"] .semantic-passport-meta [data-passport="kind"],
+ html[data-theme="light"] .semantic-passport-task-checklist-task[data-status="implemented"] .semantic-passport-task-checklist-mark,
html[data-theme="light"] .node-finder:not([data-context="route-target"]) .node-finder-result em:not([data-zero]) {
color: var(--reader-accent, var(--frontend-stroke));
}
@@ -5406,7 +5459,8 @@
Subscription billing schema
-
+
+
@@ -6259,6 +6313,10 @@
Subscription billing schema
Ctrl+click or double-click opens the first entry
+
+ Task checklist
+
+
Authored reach
@@ -9060,6 +9118,24 @@
Optionality
count: function () { return payload ? Object.keys(payload).length : 0; }
};
})();
+ /* Authored node task checklists: passport-only, like explanations. Their task
+ sources are verified and read through Archify.sourceEvidence.task(). */
+ Archify.taskChecklists = (function () {
+ var element = document.getElementById('archify-task-checklists-data');
+ var payload = null;
+ if (element) {
+ try {
+ var parsed = JSON.parse(element.textContent || 'null');
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) payload = parsed;
+ } catch (_) {}
+ }
+ return {
+ node: function (id) {
+ var tasks = payload && Object.prototype.hasOwnProperty.call(payload, id) ? payload[id] : null;
+ return Array.isArray(tasks) ? tasks.slice() : [];
+ }
+ };
+ })();
/* Verified repository evidence is emitted only after the renderer checks
the authored GitHub revision, source blobs, and optional line ranges
against the explicit --repo-root. It remains outside the SVG so every
@@ -9099,6 +9175,11 @@
Optionality
node: function (id) {
var sources = payload && payload.nodes ? payload.nodes[id] : null;
return Array.isArray(sources) ? sources.slice() : [];
+ },
+ task: function (id, index) {
+ var tasks = payload && payload.taskChecklists ? payload.taskChecklists[id] : null;
+ var sources = Array.isArray(tasks) ? tasks[index] : null;
+ return Array.isArray(sources) ? sources.slice() : [];
}
};
})();
@@ -9274,6 +9355,9 @@
Optionality
var parentBackBtn = document.getElementById('btn-parent-back');
var drilldownsSection = document.getElementById('focus-drilldowns');
var drilldownsList = document.getElementById('focus-drilldowns-list');
+ var taskChecklistSection = document.getElementById('focus-task-checklist');
+ var taskChecklistCount = document.getElementById('focus-task-checklist-count');
+ var taskChecklistTasks = document.getElementById('focus-task-checklist-tasks');
var moveBtn = document.getElementById('btn-focus-move');
var activeIds = [];
var hoveredRelationship = null;
@@ -9595,30 +9679,71 @@
Optionality
repositoryLink.setAttribute('aria-label', viewerText('viewer.passport.repository.open', { revision: repository.revision }));
}
evidence.title = viewerText('viewer.passport.verificationScope');
- sources.forEach(function (source) {
- var link = document.createElement(source.href ? 'a' : 'div');
- link.className = 'semantic-passport-source';
- if (source.href) {
- link.href = source.href;
- link.target = '_blank';
- link.rel = 'noopener noreferrer';
- link.referrerPolicy = 'no-referrer';
- link.setAttribute('aria-label', viewerText('viewer.passport.source.open', { path: source.path, revision: repository.shortRevision }));
+ sources.forEach(function (source) { evidenceLinks.appendChild(sourceLink(source, repository)); });
+ evidence.hidden = false;
+ }
+ function sourceLink(source, repository) {
+ var link = document.createElement(source.href ? 'a' : 'div');
+ link.className = 'semantic-passport-source';
+ if (source.href) {
+ link.href = source.href;
+ link.target = '_blank';
+ link.rel = 'noopener noreferrer';
+ link.referrerPolicy = 'no-referrer';
+ link.setAttribute('aria-label', viewerText('viewer.passport.source.open', { path: source.path, revision: repository.shortRevision }));
+ }
+ var name = document.createElement('strong');
+ name.textContent = source.label || source.path.split('/').pop() || source.path;
+ var location = document.createElement('code');
+ location.textContent = source.line
+ ? 'L' + source.line + (source.endLine && source.endLine !== source.line ? '–' + source.endLine : '') + (source.href ? ' ↗' : '')
+ : source.href ? viewerText('viewer.passport.source.openLink') : '';
+ var sourcePath = document.createElement('small');
+ sourcePath.textContent = source.path;
+ link.appendChild(name);
+ link.appendChild(location);
+ link.appendChild(sourcePath);
+ return link;
+ }
+ var TASK_CHECKLIST_STATUS = {
+ implemented: ['✓', 'viewer.passport.taskChecklist.status.implemented'],
+ 'needs-review': ['?', 'viewer.passport.taskChecklist.status.needsReview'],
+ pending: ['○', 'viewer.passport.taskChecklist.status.pending']
+ };
+ // Read-only: authored statuses and verified task sources, never edited here.
+ function renderTaskChecklist(id) {
+ if (!taskChecklistSection) return;
+ taskChecklistSection.open = false;
+ taskChecklistTasks.textContent = '';
+ var tasks = Archify.taskChecklists ? Archify.taskChecklists.node(id) : [];
+ var repository = Archify.sourceEvidence.repository();
+ taskChecklistSection.hidden = !tasks.length;
+ tasks.forEach(function (task, index) {
+ var item = document.createElement('li');
+ item.className = 'semantic-passport-task-checklist-task';
+ item.setAttribute('data-status', task.status);
+ var status = TASK_CHECKLIST_STATUS[task.status] || TASK_CHECKLIST_STATUS.pending;
+ var mark = document.createElement('span');
+ mark.className = 'semantic-passport-task-checklist-mark';
+ mark.setAttribute('role', 'img');
+ mark.setAttribute('aria-label', viewerText(status[1]));
+ mark.textContent = status[0];
+ var title = document.createElement('strong');
+ title.textContent = task.title;
+ item.appendChild(mark);
+ item.appendChild(title);
+ if (task.criteria) {
+ var criteria = document.createElement('p');
+ criteria.textContent = task.criteria;
+ item.appendChild(criteria);
}
- var name = document.createElement('strong');
- name.textContent = source.label || source.path.split('/').pop() || source.path;
- var location = document.createElement('code');
- location.textContent = source.line
- ? 'L' + source.line + (source.endLine && source.endLine !== source.line ? '–' + source.endLine : '') + (source.href ? ' ↗' : '')
- : source.href ? viewerText('viewer.passport.source.openLink') : '';
- var sourcePath = document.createElement('small');
- sourcePath.textContent = source.path;
- link.appendChild(name);
- link.appendChild(location);
- link.appendChild(sourcePath);
- evidenceLinks.appendChild(link);
+ Archify.sourceEvidence.task(id, index).forEach(function (source) { item.appendChild(sourceLink(source, repository)); });
+ taskChecklistTasks.appendChild(item);
+ });
+ taskChecklistCount.textContent = viewerText('viewer.passport.taskChecklist.count', {
+ implemented: tasks.filter(function (task) { return task.status === 'implemented'; }).length,
+ total: tasks.length
});
- evidence.hidden = false;
}
function explanationFor(id) {
return Archify.explanations && typeof Archify.explanations.node === 'function' ? Archify.explanations.node(id) : null;
@@ -9701,6 +9826,7 @@
Optionality
semanticId.hidden = false;
renderDrilldowns(node);
renderSourceEvidence(id);
+ renderTaskChecklist(id);
}
/**
* 仅允许同目录文件名.html(可选 ?query / #fragment)。
@@ -10759,6 +10885,10 @@
Optionality
evidenceLinks.textContent = '';
repositoryLink.removeAttribute('href');
repositoryLink.textContent = '';
+ if (taskChecklistSection) {
+ taskChecklistSection.hidden = true;
+ taskChecklistTasks.textContent = '';
+ }
summary.textContent = '';
reachSection.hidden = true;
upstreamCount.textContent = '0';
@@ -10941,6 +11071,7 @@
Optionality
clearBtn.addEventListener('click', function () { clear({ restoreFocus: true }); });
copyBtn.addEventListener('click', copyFocusLink);
if (explainBtn) explainBtn.addEventListener('click', function (event) { event.stopPropagation(); toggleExplanation(); });
+ if (taskChecklistSection) taskChecklistSection.addEventListener('toggle', function () { if (!chip.hidden) requestLensPlacement(); });
if (explanationBack) {
explanationBack.addEventListener('click', function (event) { event.stopPropagation(); closeExplanation(); });
}
diff --git a/examples/timeline-archify-dev-activity-rendered.html b/examples/timeline-archify-dev-activity-rendered.html
index f25e1c038..8d1332036 100644
--- a/examples/timeline-archify-dev-activity-rendered.html
+++ b/examples/timeline-archify-dev-activity-rendered.html
@@ -2078,6 +2078,55 @@
color: var(--text-faint);
font-size: 0.5625rem;
}
+ /* Authored task checklist: read-only, collapsed to one counted line. */
+ .semantic-passport-task-checklist {
+ margin-top: 0.52rem;
+ padding-top: 0.48rem;
+ border-top: 1px solid color-mix(in srgb, var(--toolbar-border) 76%, transparent);
+ }
+ .semantic-passport-task-checklist > summary { cursor: pointer; }
+ .semantic-passport-task-checklist-count {
+ color: var(--text-muted);
+ font-size: 0.5625rem;
+ }
+ .semantic-passport-task-checklist-tasks {
+ display: grid;
+ gap: 0.32rem;
+ max-height: min(16rem, 40vh);
+ margin: 0.45rem 0 0;
+ padding: 0.15rem;
+ overflow: auto;
+ overscroll-behavior: contain;
+ scrollbar-width: thin;
+ scrollbar-color: var(--frontend-stroke) transparent;
+ list-style: none;
+ }
+ .semantic-passport-task-checklist-task {
+ display: grid;
+ grid-template-columns: 0.9rem minmax(0, 1fr);
+ gap: 0.12rem 0.3rem;
+ }
+ .semantic-passport-task-checklist-mark {
+ color: var(--text-faint);
+ font-size: 0.625rem;
+ font-weight: 800;
+ text-align: center;
+ }
+ .semantic-passport-task-checklist-task[data-status="implemented"] .semantic-passport-task-checklist-mark { color: var(--frontend-stroke); }
+ .semantic-passport-task-checklist-task[data-status="needs-review"] .semantic-passport-task-checklist-mark { color: var(--text-muted); }
+ .semantic-passport-task-checklist-task > strong {
+ color: var(--text-muted);
+ font-size: 0.6875rem;
+ overflow-wrap: anywhere;
+ }
+ .semantic-passport-task-checklist-task p,
+ .semantic-passport-task-checklist-task .semantic-passport-source { grid-column: 2; }
+ .semantic-passport-task-checklist-task p {
+ margin: 0;
+ color: var(--text-faint);
+ font-size: 0.5625rem;
+ overflow-wrap: anywhere;
+ }
.semantic-passport-reach {
display: grid;
gap: 0.28rem;
@@ -2086,7 +2135,8 @@
border-top: 1px solid color-mix(in srgb, var(--toolbar-border) 76%, transparent);
}
.semantic-passport-reach[hidden] { display: none; }
- .semantic-passport-reach-label {
+ .semantic-passport-reach-label,
+ .semantic-passport-task-checklist-label {
color: var(--text-faint);
font-size: 0.5rem;
font-weight: 800;
@@ -2154,8 +2204,10 @@
scrollbar-width: thin;
scrollbar-color: var(--frontend-stroke) transparent;
}
- .relationship-lens-list::-webkit-scrollbar { width: 4px; }
- .relationship-lens-list::-webkit-scrollbar-thumb {
+ .relationship-lens-list::-webkit-scrollbar,
+ .semantic-passport-task-checklist-tasks::-webkit-scrollbar { width: 4px; }
+ .relationship-lens-list::-webkit-scrollbar-thumb,
+ .semantic-passport-task-checklist-tasks::-webkit-scrollbar-thumb {
border-radius: 999px;
background: color-mix(in srgb, var(--frontend-stroke) 68%, transparent);
}
@@ -2657,8 +2709,9 @@
as are dark themes and presets that already pass. Zero counts stay faint
on purpose, so they are excluded from the darker rule. Route-target
finders keep their --security-stroke status color, so they are excluded
- too. */
+ too. Task checklist done marks are the same kind of text. */
html[data-theme="light"] .semantic-passport-meta [data-passport="kind"],
+ html[data-theme="light"] .semantic-passport-task-checklist-task[data-status="implemented"] .semantic-passport-task-checklist-mark,
html[data-theme="light"] .node-finder:not([data-context="route-target"]) .node-finder-result em:not([data-zero]) {
color: var(--reader-accent, var(--frontend-stroke));
}
@@ -5407,7 +5460,8 @@
Archify dev branch activity, 28 Sep – 4 Oct 2026
-
+
+
@@ -5914,6 +5968,10 @@
Archify dev branch activity, 28 Sep – 4 Oct 2026
Ctrl+click or double-click opens the first entry
+
+ Task checklist
+
+
Authored reach
@@ -8693,6 +8751,24 @@
Archify dev branch activity, 28 Sep – 4 Oct 2026
count: function () { return payload ? Object.keys(payload).length : 0; }
};
})();
+ /* Authored node task checklists: passport-only, like explanations. Their task
+ sources are verified and read through Archify.sourceEvidence.task(). */
+ Archify.taskChecklists = (function () {
+ var element = document.getElementById('archify-task-checklists-data');
+ var payload = null;
+ if (element) {
+ try {
+ var parsed = JSON.parse(element.textContent || 'null');
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) payload = parsed;
+ } catch (_) {}
+ }
+ return {
+ node: function (id) {
+ var tasks = payload && Object.prototype.hasOwnProperty.call(payload, id) ? payload[id] : null;
+ return Array.isArray(tasks) ? tasks.slice() : [];
+ }
+ };
+ })();
/* Verified repository evidence is emitted only after the renderer checks
the authored GitHub revision, source blobs, and optional line ranges
against the explicit --repo-root. It remains outside the SVG so every
@@ -8732,6 +8808,11 @@
Archify dev branch activity, 28 Sep – 4 Oct 2026
node: function (id) {
var sources = payload && payload.nodes ? payload.nodes[id] : null;
return Array.isArray(sources) ? sources.slice() : [];
+ },
+ task: function (id, index) {
+ var tasks = payload && payload.taskChecklists ? payload.taskChecklists[id] : null;
+ var sources = Array.isArray(tasks) ? tasks[index] : null;
+ return Array.isArray(sources) ? sources.slice() : [];
}
};
})();
@@ -8907,6 +8988,9 @@
Archify dev branch activity, 28 Sep – 4 Oct 2026
var parentBackBtn = document.getElementById('btn-parent-back');
var drilldownsSection = document.getElementById('focus-drilldowns');
var drilldownsList = document.getElementById('focus-drilldowns-list');
+ var taskChecklistSection = document.getElementById('focus-task-checklist');
+ var taskChecklistCount = document.getElementById('focus-task-checklist-count');
+ var taskChecklistTasks = document.getElementById('focus-task-checklist-tasks');
var moveBtn = document.getElementById('btn-focus-move');
var activeIds = [];
var hoveredRelationship = null;
@@ -9228,30 +9312,71 @@
Archify dev branch activity, 28 Sep – 4 Oct 2026
repositoryLink.setAttribute('aria-label', viewerText('viewer.passport.repository.open', { revision: repository.revision }));
}
evidence.title = viewerText('viewer.passport.verificationScope');
- sources.forEach(function (source) {
- var link = document.createElement(source.href ? 'a' : 'div');
- link.className = 'semantic-passport-source';
- if (source.href) {
- link.href = source.href;
- link.target = '_blank';
- link.rel = 'noopener noreferrer';
- link.referrerPolicy = 'no-referrer';
- link.setAttribute('aria-label', viewerText('viewer.passport.source.open', { path: source.path, revision: repository.shortRevision }));
+ sources.forEach(function (source) { evidenceLinks.appendChild(sourceLink(source, repository)); });
+ evidence.hidden = false;
+ }
+ function sourceLink(source, repository) {
+ var link = document.createElement(source.href ? 'a' : 'div');
+ link.className = 'semantic-passport-source';
+ if (source.href) {
+ link.href = source.href;
+ link.target = '_blank';
+ link.rel = 'noopener noreferrer';
+ link.referrerPolicy = 'no-referrer';
+ link.setAttribute('aria-label', viewerText('viewer.passport.source.open', { path: source.path, revision: repository.shortRevision }));
+ }
+ var name = document.createElement('strong');
+ name.textContent = source.label || source.path.split('/').pop() || source.path;
+ var location = document.createElement('code');
+ location.textContent = source.line
+ ? 'L' + source.line + (source.endLine && source.endLine !== source.line ? '–' + source.endLine : '') + (source.href ? ' ↗' : '')
+ : source.href ? viewerText('viewer.passport.source.openLink') : '';
+ var sourcePath = document.createElement('small');
+ sourcePath.textContent = source.path;
+ link.appendChild(name);
+ link.appendChild(location);
+ link.appendChild(sourcePath);
+ return link;
+ }
+ var TASK_CHECKLIST_STATUS = {
+ implemented: ['✓', 'viewer.passport.taskChecklist.status.implemented'],
+ 'needs-review': ['?', 'viewer.passport.taskChecklist.status.needsReview'],
+ pending: ['○', 'viewer.passport.taskChecklist.status.pending']
+ };
+ // Read-only: authored statuses and verified task sources, never edited here.
+ function renderTaskChecklist(id) {
+ if (!taskChecklistSection) return;
+ taskChecklistSection.open = false;
+ taskChecklistTasks.textContent = '';
+ var tasks = Archify.taskChecklists ? Archify.taskChecklists.node(id) : [];
+ var repository = Archify.sourceEvidence.repository();
+ taskChecklistSection.hidden = !tasks.length;
+ tasks.forEach(function (task, index) {
+ var item = document.createElement('li');
+ item.className = 'semantic-passport-task-checklist-task';
+ item.setAttribute('data-status', task.status);
+ var status = TASK_CHECKLIST_STATUS[task.status] || TASK_CHECKLIST_STATUS.pending;
+ var mark = document.createElement('span');
+ mark.className = 'semantic-passport-task-checklist-mark';
+ mark.setAttribute('role', 'img');
+ mark.setAttribute('aria-label', viewerText(status[1]));
+ mark.textContent = status[0];
+ var title = document.createElement('strong');
+ title.textContent = task.title;
+ item.appendChild(mark);
+ item.appendChild(title);
+ if (task.criteria) {
+ var criteria = document.createElement('p');
+ criteria.textContent = task.criteria;
+ item.appendChild(criteria);
}
- var name = document.createElement('strong');
- name.textContent = source.label || source.path.split('/').pop() || source.path;
- var location = document.createElement('code');
- location.textContent = source.line
- ? 'L' + source.line + (source.endLine && source.endLine !== source.line ? '–' + source.endLine : '') + (source.href ? ' ↗' : '')
- : source.href ? viewerText('viewer.passport.source.openLink') : '';
- var sourcePath = document.createElement('small');
- sourcePath.textContent = source.path;
- link.appendChild(name);
- link.appendChild(location);
- link.appendChild(sourcePath);
- evidenceLinks.appendChild(link);
+ Archify.sourceEvidence.task(id, index).forEach(function (source) { item.appendChild(sourceLink(source, repository)); });
+ taskChecklistTasks.appendChild(item);
+ });
+ taskChecklistCount.textContent = viewerText('viewer.passport.taskChecklist.count', {
+ implemented: tasks.filter(function (task) { return task.status === 'implemented'; }).length,
+ total: tasks.length
});
- evidence.hidden = false;
}
function explanationFor(id) {
return Archify.explanations && typeof Archify.explanations.node === 'function' ? Archify.explanations.node(id) : null;
@@ -9334,6 +9459,7 @@
Archify dev branch activity, 28 Sep – 4 Oct 2026
semanticId.hidden = false;
renderDrilldowns(node);
renderSourceEvidence(id);
+ renderTaskChecklist(id);
}
/**
* 仅允许同目录文件名.html(可选 ?query / #fragment)。
@@ -10392,6 +10518,10 @@
Archify dev branch activity, 28 Sep – 4 Oct 2026
evidenceLinks.textContent = '';
repositoryLink.removeAttribute('href');
repositoryLink.textContent = '';
+ if (taskChecklistSection) {
+ taskChecklistSection.hidden = true;
+ taskChecklistTasks.textContent = '';
+ }
summary.textContent = '';
reachSection.hidden = true;
upstreamCount.textContent = '0';
@@ -10574,6 +10704,7 @@
Archify dev branch activity, 28 Sep – 4 Oct 2026
clearBtn.addEventListener('click', function () { clear({ restoreFocus: true }); });
copyBtn.addEventListener('click', copyFocusLink);
if (explainBtn) explainBtn.addEventListener('click', function (event) { event.stopPropagation(); toggleExplanation(); });
+ if (taskChecklistSection) taskChecklistSection.addEventListener('toggle', function () { if (!chip.hidden) requestLensPlacement(); });
if (explanationBack) {
explanationBack.addEventListener('click', function (event) { event.stopPropagation(); closeExplanation(); });
}
diff --git a/examples/timeline-payment-incident-rendered.html b/examples/timeline-payment-incident-rendered.html
index 964deb1dd..f38f964cc 100644
--- a/examples/timeline-payment-incident-rendered.html
+++ b/examples/timeline-payment-incident-rendered.html
@@ -2078,6 +2078,55 @@
color: var(--text-faint);
font-size: 0.5625rem;
}
+ /* Authored task checklist: read-only, collapsed to one counted line. */
+ .semantic-passport-task-checklist {
+ margin-top: 0.52rem;
+ padding-top: 0.48rem;
+ border-top: 1px solid color-mix(in srgb, var(--toolbar-border) 76%, transparent);
+ }
+ .semantic-passport-task-checklist > summary { cursor: pointer; }
+ .semantic-passport-task-checklist-count {
+ color: var(--text-muted);
+ font-size: 0.5625rem;
+ }
+ .semantic-passport-task-checklist-tasks {
+ display: grid;
+ gap: 0.32rem;
+ max-height: min(16rem, 40vh);
+ margin: 0.45rem 0 0;
+ padding: 0.15rem;
+ overflow: auto;
+ overscroll-behavior: contain;
+ scrollbar-width: thin;
+ scrollbar-color: var(--frontend-stroke) transparent;
+ list-style: none;
+ }
+ .semantic-passport-task-checklist-task {
+ display: grid;
+ grid-template-columns: 0.9rem minmax(0, 1fr);
+ gap: 0.12rem 0.3rem;
+ }
+ .semantic-passport-task-checklist-mark {
+ color: var(--text-faint);
+ font-size: 0.625rem;
+ font-weight: 800;
+ text-align: center;
+ }
+ .semantic-passport-task-checklist-task[data-status="implemented"] .semantic-passport-task-checklist-mark { color: var(--frontend-stroke); }
+ .semantic-passport-task-checklist-task[data-status="needs-review"] .semantic-passport-task-checklist-mark { color: var(--text-muted); }
+ .semantic-passport-task-checklist-task > strong {
+ color: var(--text-muted);
+ font-size: 0.6875rem;
+ overflow-wrap: anywhere;
+ }
+ .semantic-passport-task-checklist-task p,
+ .semantic-passport-task-checklist-task .semantic-passport-source { grid-column: 2; }
+ .semantic-passport-task-checklist-task p {
+ margin: 0;
+ color: var(--text-faint);
+ font-size: 0.5625rem;
+ overflow-wrap: anywhere;
+ }
.semantic-passport-reach {
display: grid;
gap: 0.28rem;
@@ -2086,7 +2135,8 @@
border-top: 1px solid color-mix(in srgb, var(--toolbar-border) 76%, transparent);
}
.semantic-passport-reach[hidden] { display: none; }
- .semantic-passport-reach-label {
+ .semantic-passport-reach-label,
+ .semantic-passport-task-checklist-label {
color: var(--text-faint);
font-size: 0.5rem;
font-weight: 800;
@@ -2154,8 +2204,10 @@
scrollbar-width: thin;
scrollbar-color: var(--frontend-stroke) transparent;
}
- .relationship-lens-list::-webkit-scrollbar { width: 4px; }
- .relationship-lens-list::-webkit-scrollbar-thumb {
+ .relationship-lens-list::-webkit-scrollbar,
+ .semantic-passport-task-checklist-tasks::-webkit-scrollbar { width: 4px; }
+ .relationship-lens-list::-webkit-scrollbar-thumb,
+ .semantic-passport-task-checklist-tasks::-webkit-scrollbar-thumb {
border-radius: 999px;
background: color-mix(in srgb, var(--frontend-stroke) 68%, transparent);
}
@@ -2657,8 +2709,9 @@
as are dark themes and presets that already pass. Zero counts stay faint
on purpose, so they are excluded from the darker rule. Route-target
finders keep their --security-stroke status color, so they are excluded
- too. */
+ too. Task checklist done marks are the same kind of text. */
html[data-theme="light"] .semantic-passport-meta [data-passport="kind"],
+ html[data-theme="light"] .semantic-passport-task-checklist-task[data-status="implemented"] .semantic-passport-task-checklist-mark,
html[data-theme="light"] .node-finder:not([data-context="route-target"]) .node-finder-result em:not([data-zero]) {
color: var(--reader-accent, var(--frontend-stroke));
}
@@ -5407,7 +5460,8 @@
Payment failure incident
-
+
+
@@ -5684,6 +5738,10 @@
Payment failure incident
Ctrl+click or double-click opens the first entry
+
+ Task checklist
+
+
Authored reach
@@ -8463,6 +8521,24 @@
Payment failure incident
count: function () { return payload ? Object.keys(payload).length : 0; }
};
})();
+ /* Authored node task checklists: passport-only, like explanations. Their task
+ sources are verified and read through Archify.sourceEvidence.task(). */
+ Archify.taskChecklists = (function () {
+ var element = document.getElementById('archify-task-checklists-data');
+ var payload = null;
+ if (element) {
+ try {
+ var parsed = JSON.parse(element.textContent || 'null');
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) payload = parsed;
+ } catch (_) {}
+ }
+ return {
+ node: function (id) {
+ var tasks = payload && Object.prototype.hasOwnProperty.call(payload, id) ? payload[id] : null;
+ return Array.isArray(tasks) ? tasks.slice() : [];
+ }
+ };
+ })();
/* Verified repository evidence is emitted only after the renderer checks
the authored GitHub revision, source blobs, and optional line ranges
against the explicit --repo-root. It remains outside the SVG so every
@@ -8502,6 +8578,11 @@
Payment failure incident
node: function (id) {
var sources = payload && payload.nodes ? payload.nodes[id] : null;
return Array.isArray(sources) ? sources.slice() : [];
+ },
+ task: function (id, index) {
+ var tasks = payload && payload.taskChecklists ? payload.taskChecklists[id] : null;
+ var sources = Array.isArray(tasks) ? tasks[index] : null;
+ return Array.isArray(sources) ? sources.slice() : [];
}
};
})();
@@ -8677,6 +8758,9 @@
Payment failure incident
var parentBackBtn = document.getElementById('btn-parent-back');
var drilldownsSection = document.getElementById('focus-drilldowns');
var drilldownsList = document.getElementById('focus-drilldowns-list');
+ var taskChecklistSection = document.getElementById('focus-task-checklist');
+ var taskChecklistCount = document.getElementById('focus-task-checklist-count');
+ var taskChecklistTasks = document.getElementById('focus-task-checklist-tasks');
var moveBtn = document.getElementById('btn-focus-move');
var activeIds = [];
var hoveredRelationship = null;
@@ -8998,30 +9082,71 @@
Payment failure incident
repositoryLink.setAttribute('aria-label', viewerText('viewer.passport.repository.open', { revision: repository.revision }));
}
evidence.title = viewerText('viewer.passport.verificationScope');
- sources.forEach(function (source) {
- var link = document.createElement(source.href ? 'a' : 'div');
- link.className = 'semantic-passport-source';
- if (source.href) {
- link.href = source.href;
- link.target = '_blank';
- link.rel = 'noopener noreferrer';
- link.referrerPolicy = 'no-referrer';
- link.setAttribute('aria-label', viewerText('viewer.passport.source.open', { path: source.path, revision: repository.shortRevision }));
+ sources.forEach(function (source) { evidenceLinks.appendChild(sourceLink(source, repository)); });
+ evidence.hidden = false;
+ }
+ function sourceLink(source, repository) {
+ var link = document.createElement(source.href ? 'a' : 'div');
+ link.className = 'semantic-passport-source';
+ if (source.href) {
+ link.href = source.href;
+ link.target = '_blank';
+ link.rel = 'noopener noreferrer';
+ link.referrerPolicy = 'no-referrer';
+ link.setAttribute('aria-label', viewerText('viewer.passport.source.open', { path: source.path, revision: repository.shortRevision }));
+ }
+ var name = document.createElement('strong');
+ name.textContent = source.label || source.path.split('/').pop() || source.path;
+ var location = document.createElement('code');
+ location.textContent = source.line
+ ? 'L' + source.line + (source.endLine && source.endLine !== source.line ? '–' + source.endLine : '') + (source.href ? ' ↗' : '')
+ : source.href ? viewerText('viewer.passport.source.openLink') : '';
+ var sourcePath = document.createElement('small');
+ sourcePath.textContent = source.path;
+ link.appendChild(name);
+ link.appendChild(location);
+ link.appendChild(sourcePath);
+ return link;
+ }
+ var TASK_CHECKLIST_STATUS = {
+ implemented: ['✓', 'viewer.passport.taskChecklist.status.implemented'],
+ 'needs-review': ['?', 'viewer.passport.taskChecklist.status.needsReview'],
+ pending: ['○', 'viewer.passport.taskChecklist.status.pending']
+ };
+ // Read-only: authored statuses and verified task sources, never edited here.
+ function renderTaskChecklist(id) {
+ if (!taskChecklistSection) return;
+ taskChecklistSection.open = false;
+ taskChecklistTasks.textContent = '';
+ var tasks = Archify.taskChecklists ? Archify.taskChecklists.node(id) : [];
+ var repository = Archify.sourceEvidence.repository();
+ taskChecklistSection.hidden = !tasks.length;
+ tasks.forEach(function (task, index) {
+ var item = document.createElement('li');
+ item.className = 'semantic-passport-task-checklist-task';
+ item.setAttribute('data-status', task.status);
+ var status = TASK_CHECKLIST_STATUS[task.status] || TASK_CHECKLIST_STATUS.pending;
+ var mark = document.createElement('span');
+ mark.className = 'semantic-passport-task-checklist-mark';
+ mark.setAttribute('role', 'img');
+ mark.setAttribute('aria-label', viewerText(status[1]));
+ mark.textContent = status[0];
+ var title = document.createElement('strong');
+ title.textContent = task.title;
+ item.appendChild(mark);
+ item.appendChild(title);
+ if (task.criteria) {
+ var criteria = document.createElement('p');
+ criteria.textContent = task.criteria;
+ item.appendChild(criteria);
}
- var name = document.createElement('strong');
- name.textContent = source.label || source.path.split('/').pop() || source.path;
- var location = document.createElement('code');
- location.textContent = source.line
- ? 'L' + source.line + (source.endLine && source.endLine !== source.line ? '–' + source.endLine : '') + (source.href ? ' ↗' : '')
- : source.href ? viewerText('viewer.passport.source.openLink') : '';
- var sourcePath = document.createElement('small');
- sourcePath.textContent = source.path;
- link.appendChild(name);
- link.appendChild(location);
- link.appendChild(sourcePath);
- evidenceLinks.appendChild(link);
+ Archify.sourceEvidence.task(id, index).forEach(function (source) { item.appendChild(sourceLink(source, repository)); });
+ taskChecklistTasks.appendChild(item);
+ });
+ taskChecklistCount.textContent = viewerText('viewer.passport.taskChecklist.count', {
+ implemented: tasks.filter(function (task) { return task.status === 'implemented'; }).length,
+ total: tasks.length
});
- evidence.hidden = false;
}
function explanationFor(id) {
return Archify.explanations && typeof Archify.explanations.node === 'function' ? Archify.explanations.node(id) : null;
@@ -9104,6 +9229,7 @@
Payment failure incident
semanticId.hidden = false;
renderDrilldowns(node);
renderSourceEvidence(id);
+ renderTaskChecklist(id);
}
/**
* 仅允许同目录文件名.html(可选 ?query / #fragment)。
@@ -10162,6 +10288,10 @@
Payment failure incident
evidenceLinks.textContent = '';
repositoryLink.removeAttribute('href');
repositoryLink.textContent = '';
+ if (taskChecklistSection) {
+ taskChecklistSection.hidden = true;
+ taskChecklistTasks.textContent = '';
+ }
summary.textContent = '';
reachSection.hidden = true;
upstreamCount.textContent = '0';
@@ -10344,6 +10474,7 @@
Payment failure incident
clearBtn.addEventListener('click', function () { clear({ restoreFocus: true }); });
copyBtn.addEventListener('click', copyFocusLink);
if (explainBtn) explainBtn.addEventListener('click', function (event) { event.stopPropagation(); toggleExplanation(); });
+ if (taskChecklistSection) taskChecklistSection.addEventListener('toggle', function () { if (!chip.hidden) requestLensPlacement(); });
if (explanationBack) {
explanationBack.addEventListener('click', function (event) { event.stopPropagation(); closeExplanation(); });
}
diff --git a/examples/tree-archify-repository-rendered.html b/examples/tree-archify-repository-rendered.html
index aa4978ed3..c8c13fd75 100644
--- a/examples/tree-archify-repository-rendered.html
+++ b/examples/tree-archify-repository-rendered.html
@@ -2078,6 +2078,55 @@
color: var(--text-faint);
font-size: 0.5625rem;
}
+ /* Authored task checklist: read-only, collapsed to one counted line. */
+ .semantic-passport-task-checklist {
+ margin-top: 0.52rem;
+ padding-top: 0.48rem;
+ border-top: 1px solid color-mix(in srgb, var(--toolbar-border) 76%, transparent);
+ }
+ .semantic-passport-task-checklist > summary { cursor: pointer; }
+ .semantic-passport-task-checklist-count {
+ color: var(--text-muted);
+ font-size: 0.5625rem;
+ }
+ .semantic-passport-task-checklist-tasks {
+ display: grid;
+ gap: 0.32rem;
+ max-height: min(16rem, 40vh);
+ margin: 0.45rem 0 0;
+ padding: 0.15rem;
+ overflow: auto;
+ overscroll-behavior: contain;
+ scrollbar-width: thin;
+ scrollbar-color: var(--frontend-stroke) transparent;
+ list-style: none;
+ }
+ .semantic-passport-task-checklist-task {
+ display: grid;
+ grid-template-columns: 0.9rem minmax(0, 1fr);
+ gap: 0.12rem 0.3rem;
+ }
+ .semantic-passport-task-checklist-mark {
+ color: var(--text-faint);
+ font-size: 0.625rem;
+ font-weight: 800;
+ text-align: center;
+ }
+ .semantic-passport-task-checklist-task[data-status="implemented"] .semantic-passport-task-checklist-mark { color: var(--frontend-stroke); }
+ .semantic-passport-task-checklist-task[data-status="needs-review"] .semantic-passport-task-checklist-mark { color: var(--text-muted); }
+ .semantic-passport-task-checklist-task > strong {
+ color: var(--text-muted);
+ font-size: 0.6875rem;
+ overflow-wrap: anywhere;
+ }
+ .semantic-passport-task-checklist-task p,
+ .semantic-passport-task-checklist-task .semantic-passport-source { grid-column: 2; }
+ .semantic-passport-task-checklist-task p {
+ margin: 0;
+ color: var(--text-faint);
+ font-size: 0.5625rem;
+ overflow-wrap: anywhere;
+ }
.semantic-passport-reach {
display: grid;
gap: 0.28rem;
@@ -2086,7 +2135,8 @@
border-top: 1px solid color-mix(in srgb, var(--toolbar-border) 76%, transparent);
}
.semantic-passport-reach[hidden] { display: none; }
- .semantic-passport-reach-label {
+ .semantic-passport-reach-label,
+ .semantic-passport-task-checklist-label {
color: var(--text-faint);
font-size: 0.5rem;
font-weight: 800;
@@ -2154,8 +2204,10 @@
scrollbar-width: thin;
scrollbar-color: var(--frontend-stroke) transparent;
}
- .relationship-lens-list::-webkit-scrollbar { width: 4px; }
- .relationship-lens-list::-webkit-scrollbar-thumb {
+ .relationship-lens-list::-webkit-scrollbar,
+ .semantic-passport-task-checklist-tasks::-webkit-scrollbar { width: 4px; }
+ .relationship-lens-list::-webkit-scrollbar-thumb,
+ .semantic-passport-task-checklist-tasks::-webkit-scrollbar-thumb {
border-radius: 999px;
background: color-mix(in srgb, var(--frontend-stroke) 68%, transparent);
}
@@ -2657,8 +2709,9 @@
as are dark themes and presets that already pass. Zero counts stay faint
on purpose, so they are excluded from the darker rule. Route-target
finders keep their --security-stroke status color, so they are excluded
- too. */
+ too. Task checklist done marks are the same kind of text. */
html[data-theme="light"] .semantic-passport-meta [data-passport="kind"],
+ html[data-theme="light"] .semantic-passport-task-checklist-task[data-status="implemented"] .semantic-passport-task-checklist-mark,
html[data-theme="light"] .node-finder:not([data-context="route-target"]) .node-finder-result em:not([data-zero]) {
color: var(--reader-accent, var(--frontend-stroke));
}
@@ -5407,7 +5460,8 @@
Archify repository layout
-
+
+
@@ -5983,6 +6037,10 @@
Archify repository layout
Ctrl+click or double-click opens the first entry
+
+ Task checklist
+
+
Authored reach
@@ -8762,6 +8820,24 @@
Archify repository layout
count: function () { return payload ? Object.keys(payload).length : 0; }
};
})();
+ /* Authored node task checklists: passport-only, like explanations. Their task
+ sources are verified and read through Archify.sourceEvidence.task(). */
+ Archify.taskChecklists = (function () {
+ var element = document.getElementById('archify-task-checklists-data');
+ var payload = null;
+ if (element) {
+ try {
+ var parsed = JSON.parse(element.textContent || 'null');
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) payload = parsed;
+ } catch (_) {}
+ }
+ return {
+ node: function (id) {
+ var tasks = payload && Object.prototype.hasOwnProperty.call(payload, id) ? payload[id] : null;
+ return Array.isArray(tasks) ? tasks.slice() : [];
+ }
+ };
+ })();
/* Verified repository evidence is emitted only after the renderer checks
the authored GitHub revision, source blobs, and optional line ranges
against the explicit --repo-root. It remains outside the SVG so every
@@ -8801,6 +8877,11 @@
Archify repository layout
node: function (id) {
var sources = payload && payload.nodes ? payload.nodes[id] : null;
return Array.isArray(sources) ? sources.slice() : [];
+ },
+ task: function (id, index) {
+ var tasks = payload && payload.taskChecklists ? payload.taskChecklists[id] : null;
+ var sources = Array.isArray(tasks) ? tasks[index] : null;
+ return Array.isArray(sources) ? sources.slice() : [];
}
};
})();
@@ -8976,6 +9057,9 @@
Archify repository layout
var parentBackBtn = document.getElementById('btn-parent-back');
var drilldownsSection = document.getElementById('focus-drilldowns');
var drilldownsList = document.getElementById('focus-drilldowns-list');
+ var taskChecklistSection = document.getElementById('focus-task-checklist');
+ var taskChecklistCount = document.getElementById('focus-task-checklist-count');
+ var taskChecklistTasks = document.getElementById('focus-task-checklist-tasks');
var moveBtn = document.getElementById('btn-focus-move');
var activeIds = [];
var hoveredRelationship = null;
@@ -9297,30 +9381,71 @@
Archify repository layout
repositoryLink.setAttribute('aria-label', viewerText('viewer.passport.repository.open', { revision: repository.revision }));
}
evidence.title = viewerText('viewer.passport.verificationScope');
- sources.forEach(function (source) {
- var link = document.createElement(source.href ? 'a' : 'div');
- link.className = 'semantic-passport-source';
- if (source.href) {
- link.href = source.href;
- link.target = '_blank';
- link.rel = 'noopener noreferrer';
- link.referrerPolicy = 'no-referrer';
- link.setAttribute('aria-label', viewerText('viewer.passport.source.open', { path: source.path, revision: repository.shortRevision }));
+ sources.forEach(function (source) { evidenceLinks.appendChild(sourceLink(source, repository)); });
+ evidence.hidden = false;
+ }
+ function sourceLink(source, repository) {
+ var link = document.createElement(source.href ? 'a' : 'div');
+ link.className = 'semantic-passport-source';
+ if (source.href) {
+ link.href = source.href;
+ link.target = '_blank';
+ link.rel = 'noopener noreferrer';
+ link.referrerPolicy = 'no-referrer';
+ link.setAttribute('aria-label', viewerText('viewer.passport.source.open', { path: source.path, revision: repository.shortRevision }));
+ }
+ var name = document.createElement('strong');
+ name.textContent = source.label || source.path.split('/').pop() || source.path;
+ var location = document.createElement('code');
+ location.textContent = source.line
+ ? 'L' + source.line + (source.endLine && source.endLine !== source.line ? '–' + source.endLine : '') + (source.href ? ' ↗' : '')
+ : source.href ? viewerText('viewer.passport.source.openLink') : '';
+ var sourcePath = document.createElement('small');
+ sourcePath.textContent = source.path;
+ link.appendChild(name);
+ link.appendChild(location);
+ link.appendChild(sourcePath);
+ return link;
+ }
+ var TASK_CHECKLIST_STATUS = {
+ implemented: ['✓', 'viewer.passport.taskChecklist.status.implemented'],
+ 'needs-review': ['?', 'viewer.passport.taskChecklist.status.needsReview'],
+ pending: ['○', 'viewer.passport.taskChecklist.status.pending']
+ };
+ // Read-only: authored statuses and verified task sources, never edited here.
+ function renderTaskChecklist(id) {
+ if (!taskChecklistSection) return;
+ taskChecklistSection.open = false;
+ taskChecklistTasks.textContent = '';
+ var tasks = Archify.taskChecklists ? Archify.taskChecklists.node(id) : [];
+ var repository = Archify.sourceEvidence.repository();
+ taskChecklistSection.hidden = !tasks.length;
+ tasks.forEach(function (task, index) {
+ var item = document.createElement('li');
+ item.className = 'semantic-passport-task-checklist-task';
+ item.setAttribute('data-status', task.status);
+ var status = TASK_CHECKLIST_STATUS[task.status] || TASK_CHECKLIST_STATUS.pending;
+ var mark = document.createElement('span');
+ mark.className = 'semantic-passport-task-checklist-mark';
+ mark.setAttribute('role', 'img');
+ mark.setAttribute('aria-label', viewerText(status[1]));
+ mark.textContent = status[0];
+ var title = document.createElement('strong');
+ title.textContent = task.title;
+ item.appendChild(mark);
+ item.appendChild(title);
+ if (task.criteria) {
+ var criteria = document.createElement('p');
+ criteria.textContent = task.criteria;
+ item.appendChild(criteria);
}
- var name = document.createElement('strong');
- name.textContent = source.label || source.path.split('/').pop() || source.path;
- var location = document.createElement('code');
- location.textContent = source.line
- ? 'L' + source.line + (source.endLine && source.endLine !== source.line ? '–' + source.endLine : '') + (source.href ? ' ↗' : '')
- : source.href ? viewerText('viewer.passport.source.openLink') : '';
- var sourcePath = document.createElement('small');
- sourcePath.textContent = source.path;
- link.appendChild(name);
- link.appendChild(location);
- link.appendChild(sourcePath);
- evidenceLinks.appendChild(link);
+ Archify.sourceEvidence.task(id, index).forEach(function (source) { item.appendChild(sourceLink(source, repository)); });
+ taskChecklistTasks.appendChild(item);
+ });
+ taskChecklistCount.textContent = viewerText('viewer.passport.taskChecklist.count', {
+ implemented: tasks.filter(function (task) { return task.status === 'implemented'; }).length,
+ total: tasks.length
});
- evidence.hidden = false;
}
function explanationFor(id) {
return Archify.explanations && typeof Archify.explanations.node === 'function' ? Archify.explanations.node(id) : null;
@@ -9403,6 +9528,7 @@
Archify repository layout
semanticId.hidden = false;
renderDrilldowns(node);
renderSourceEvidence(id);
+ renderTaskChecklist(id);
}
/**
* 仅允许同目录文件名.html(可选 ?query / #fragment)。
@@ -10461,6 +10587,10 @@
Archify repository layout
evidenceLinks.textContent = '';
repositoryLink.removeAttribute('href');
repositoryLink.textContent = '';
+ if (taskChecklistSection) {
+ taskChecklistSection.hidden = true;
+ taskChecklistTasks.textContent = '';
+ }
summary.textContent = '';
reachSection.hidden = true;
upstreamCount.textContent = '0';
@@ -10643,6 +10773,7 @@
Archify repository layout
clearBtn.addEventListener('click', function () { clear({ restoreFocus: true }); });
copyBtn.addEventListener('click', copyFocusLink);
if (explainBtn) explainBtn.addEventListener('click', function (event) { event.stopPropagation(); toggleExplanation(); });
+ if (taskChecklistSection) taskChecklistSection.addEventListener('toggle', function () { if (!chip.hidden) requestLensPlacement(); });
if (explanationBack) {
explanationBack.addEventListener('click', function (event) { event.stopPropagation(); closeExplanation(); });
}
diff --git a/examples/tree-payment-platform-rendered.html b/examples/tree-payment-platform-rendered.html
index 6d95f32ba..a87832225 100644
--- a/examples/tree-payment-platform-rendered.html
+++ b/examples/tree-payment-platform-rendered.html
@@ -2078,6 +2078,55 @@
color: var(--text-faint);
font-size: 0.5625rem;
}
+ /* Authored task checklist: read-only, collapsed to one counted line. */
+ .semantic-passport-task-checklist {
+ margin-top: 0.52rem;
+ padding-top: 0.48rem;
+ border-top: 1px solid color-mix(in srgb, var(--toolbar-border) 76%, transparent);
+ }
+ .semantic-passport-task-checklist > summary { cursor: pointer; }
+ .semantic-passport-task-checklist-count {
+ color: var(--text-muted);
+ font-size: 0.5625rem;
+ }
+ .semantic-passport-task-checklist-tasks {
+ display: grid;
+ gap: 0.32rem;
+ max-height: min(16rem, 40vh);
+ margin: 0.45rem 0 0;
+ padding: 0.15rem;
+ overflow: auto;
+ overscroll-behavior: contain;
+ scrollbar-width: thin;
+ scrollbar-color: var(--frontend-stroke) transparent;
+ list-style: none;
+ }
+ .semantic-passport-task-checklist-task {
+ display: grid;
+ grid-template-columns: 0.9rem minmax(0, 1fr);
+ gap: 0.12rem 0.3rem;
+ }
+ .semantic-passport-task-checklist-mark {
+ color: var(--text-faint);
+ font-size: 0.625rem;
+ font-weight: 800;
+ text-align: center;
+ }
+ .semantic-passport-task-checklist-task[data-status="implemented"] .semantic-passport-task-checklist-mark { color: var(--frontend-stroke); }
+ .semantic-passport-task-checklist-task[data-status="needs-review"] .semantic-passport-task-checklist-mark { color: var(--text-muted); }
+ .semantic-passport-task-checklist-task > strong {
+ color: var(--text-muted);
+ font-size: 0.6875rem;
+ overflow-wrap: anywhere;
+ }
+ .semantic-passport-task-checklist-task p,
+ .semantic-passport-task-checklist-task .semantic-passport-source { grid-column: 2; }
+ .semantic-passport-task-checklist-task p {
+ margin: 0;
+ color: var(--text-faint);
+ font-size: 0.5625rem;
+ overflow-wrap: anywhere;
+ }
.semantic-passport-reach {
display: grid;
gap: 0.28rem;
@@ -2086,7 +2135,8 @@
border-top: 1px solid color-mix(in srgb, var(--toolbar-border) 76%, transparent);
}
.semantic-passport-reach[hidden] { display: none; }
- .semantic-passport-reach-label {
+ .semantic-passport-reach-label,
+ .semantic-passport-task-checklist-label {
color: var(--text-faint);
font-size: 0.5rem;
font-weight: 800;
@@ -2154,8 +2204,10 @@
scrollbar-width: thin;
scrollbar-color: var(--frontend-stroke) transparent;
}
- .relationship-lens-list::-webkit-scrollbar { width: 4px; }
- .relationship-lens-list::-webkit-scrollbar-thumb {
+ .relationship-lens-list::-webkit-scrollbar,
+ .semantic-passport-task-checklist-tasks::-webkit-scrollbar { width: 4px; }
+ .relationship-lens-list::-webkit-scrollbar-thumb,
+ .semantic-passport-task-checklist-tasks::-webkit-scrollbar-thumb {
border-radius: 999px;
background: color-mix(in srgb, var(--frontend-stroke) 68%, transparent);
}
@@ -2657,8 +2709,9 @@
as are dark themes and presets that already pass. Zero counts stay faint
on purpose, so they are excluded from the darker rule. Route-target
finders keep their --security-stroke status color, so they are excluded
- too. */
+ too. Task checklist done marks are the same kind of text. */
html[data-theme="light"] .semantic-passport-meta [data-passport="kind"],
+ html[data-theme="light"] .semantic-passport-task-checklist-task[data-status="implemented"] .semantic-passport-task-checklist-mark,
html[data-theme="light"] .node-finder:not([data-context="route-target"]) .node-finder-result em:not([data-zero]) {
color: var(--reader-accent, var(--frontend-stroke));
}
@@ -5407,7 +5460,8 @@
Payment platform capabilities
-
+
+
@@ -5722,6 +5776,10 @@
Payment platform capabilities
Ctrl+click or double-click opens the first entry
+
+ Task checklist
+
+
Authored reach
@@ -8501,6 +8559,24 @@
Payment platform capabilities
count: function () { return payload ? Object.keys(payload).length : 0; }
};
})();
+ /* Authored node task checklists: passport-only, like explanations. Their task
+ sources are verified and read through Archify.sourceEvidence.task(). */
+ Archify.taskChecklists = (function () {
+ var element = document.getElementById('archify-task-checklists-data');
+ var payload = null;
+ if (element) {
+ try {
+ var parsed = JSON.parse(element.textContent || 'null');
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) payload = parsed;
+ } catch (_) {}
+ }
+ return {
+ node: function (id) {
+ var tasks = payload && Object.prototype.hasOwnProperty.call(payload, id) ? payload[id] : null;
+ return Array.isArray(tasks) ? tasks.slice() : [];
+ }
+ };
+ })();
/* Verified repository evidence is emitted only after the renderer checks
the authored GitHub revision, source blobs, and optional line ranges
against the explicit --repo-root. It remains outside the SVG so every
@@ -8540,6 +8616,11 @@
Payment platform capabilities
node: function (id) {
var sources = payload && payload.nodes ? payload.nodes[id] : null;
return Array.isArray(sources) ? sources.slice() : [];
+ },
+ task: function (id, index) {
+ var tasks = payload && payload.taskChecklists ? payload.taskChecklists[id] : null;
+ var sources = Array.isArray(tasks) ? tasks[index] : null;
+ return Array.isArray(sources) ? sources.slice() : [];
}
};
})();
@@ -8715,6 +8796,9 @@
Payment platform capabilities
var parentBackBtn = document.getElementById('btn-parent-back');
var drilldownsSection = document.getElementById('focus-drilldowns');
var drilldownsList = document.getElementById('focus-drilldowns-list');
+ var taskChecklistSection = document.getElementById('focus-task-checklist');
+ var taskChecklistCount = document.getElementById('focus-task-checklist-count');
+ var taskChecklistTasks = document.getElementById('focus-task-checklist-tasks');
var moveBtn = document.getElementById('btn-focus-move');
var activeIds = [];
var hoveredRelationship = null;
@@ -9036,30 +9120,71 @@
Payment platform capabilities
repositoryLink.setAttribute('aria-label', viewerText('viewer.passport.repository.open', { revision: repository.revision }));
}
evidence.title = viewerText('viewer.passport.verificationScope');
- sources.forEach(function (source) {
- var link = document.createElement(source.href ? 'a' : 'div');
- link.className = 'semantic-passport-source';
- if (source.href) {
- link.href = source.href;
- link.target = '_blank';
- link.rel = 'noopener noreferrer';
- link.referrerPolicy = 'no-referrer';
- link.setAttribute('aria-label', viewerText('viewer.passport.source.open', { path: source.path, revision: repository.shortRevision }));
+ sources.forEach(function (source) { evidenceLinks.appendChild(sourceLink(source, repository)); });
+ evidence.hidden = false;
+ }
+ function sourceLink(source, repository) {
+ var link = document.createElement(source.href ? 'a' : 'div');
+ link.className = 'semantic-passport-source';
+ if (source.href) {
+ link.href = source.href;
+ link.target = '_blank';
+ link.rel = 'noopener noreferrer';
+ link.referrerPolicy = 'no-referrer';
+ link.setAttribute('aria-label', viewerText('viewer.passport.source.open', { path: source.path, revision: repository.shortRevision }));
+ }
+ var name = document.createElement('strong');
+ name.textContent = source.label || source.path.split('/').pop() || source.path;
+ var location = document.createElement('code');
+ location.textContent = source.line
+ ? 'L' + source.line + (source.endLine && source.endLine !== source.line ? '–' + source.endLine : '') + (source.href ? ' ↗' : '')
+ : source.href ? viewerText('viewer.passport.source.openLink') : '';
+ var sourcePath = document.createElement('small');
+ sourcePath.textContent = source.path;
+ link.appendChild(name);
+ link.appendChild(location);
+ link.appendChild(sourcePath);
+ return link;
+ }
+ var TASK_CHECKLIST_STATUS = {
+ implemented: ['✓', 'viewer.passport.taskChecklist.status.implemented'],
+ 'needs-review': ['?', 'viewer.passport.taskChecklist.status.needsReview'],
+ pending: ['○', 'viewer.passport.taskChecklist.status.pending']
+ };
+ // Read-only: authored statuses and verified task sources, never edited here.
+ function renderTaskChecklist(id) {
+ if (!taskChecklistSection) return;
+ taskChecklistSection.open = false;
+ taskChecklistTasks.textContent = '';
+ var tasks = Archify.taskChecklists ? Archify.taskChecklists.node(id) : [];
+ var repository = Archify.sourceEvidence.repository();
+ taskChecklistSection.hidden = !tasks.length;
+ tasks.forEach(function (task, index) {
+ var item = document.createElement('li');
+ item.className = 'semantic-passport-task-checklist-task';
+ item.setAttribute('data-status', task.status);
+ var status = TASK_CHECKLIST_STATUS[task.status] || TASK_CHECKLIST_STATUS.pending;
+ var mark = document.createElement('span');
+ mark.className = 'semantic-passport-task-checklist-mark';
+ mark.setAttribute('role', 'img');
+ mark.setAttribute('aria-label', viewerText(status[1]));
+ mark.textContent = status[0];
+ var title = document.createElement('strong');
+ title.textContent = task.title;
+ item.appendChild(mark);
+ item.appendChild(title);
+ if (task.criteria) {
+ var criteria = document.createElement('p');
+ criteria.textContent = task.criteria;
+ item.appendChild(criteria);
}
- var name = document.createElement('strong');
- name.textContent = source.label || source.path.split('/').pop() || source.path;
- var location = document.createElement('code');
- location.textContent = source.line
- ? 'L' + source.line + (source.endLine && source.endLine !== source.line ? '–' + source.endLine : '') + (source.href ? ' ↗' : '')
- : source.href ? viewerText('viewer.passport.source.openLink') : '';
- var sourcePath = document.createElement('small');
- sourcePath.textContent = source.path;
- link.appendChild(name);
- link.appendChild(location);
- link.appendChild(sourcePath);
- evidenceLinks.appendChild(link);
+ Archify.sourceEvidence.task(id, index).forEach(function (source) { item.appendChild(sourceLink(source, repository)); });
+ taskChecklistTasks.appendChild(item);
+ });
+ taskChecklistCount.textContent = viewerText('viewer.passport.taskChecklist.count', {
+ implemented: tasks.filter(function (task) { return task.status === 'implemented'; }).length,
+ total: tasks.length
});
- evidence.hidden = false;
}
function explanationFor(id) {
return Archify.explanations && typeof Archify.explanations.node === 'function' ? Archify.explanations.node(id) : null;
@@ -9142,6 +9267,7 @@
Payment platform capabilities
semanticId.hidden = false;
renderDrilldowns(node);
renderSourceEvidence(id);
+ renderTaskChecklist(id);
}
/**
* 仅允许同目录文件名.html(可选 ?query / #fragment)。
@@ -10200,6 +10326,10 @@
Payment platform capabilities
evidenceLinks.textContent = '';
repositoryLink.removeAttribute('href');
repositoryLink.textContent = '';
+ if (taskChecklistSection) {
+ taskChecklistSection.hidden = true;
+ taskChecklistTasks.textContent = '';
+ }
summary.textContent = '';
reachSection.hidden = true;
upstreamCount.textContent = '0';
@@ -10382,6 +10512,7 @@
Payment platform capabilities
clearBtn.addEventListener('click', function () { clear({ restoreFocus: true }); });
copyBtn.addEventListener('click', copyFocusLink);
if (explainBtn) explainBtn.addEventListener('click', function (event) { event.stopPropagation(); toggleExplanation(); });
+ if (taskChecklistSection) taskChecklistSection.addEventListener('toggle', function () { if (!chip.hidden) requestLensPlacement(); });
if (explanationBack) {
explanationBack.addEventListener('click', function (event) { event.stopPropagation(); closeExplanation(); });
}
diff --git a/examples/waterfall-checkout-request-rendered.html b/examples/waterfall-checkout-request-rendered.html
index e2d4a16b4..1978be924 100644
--- a/examples/waterfall-checkout-request-rendered.html
+++ b/examples/waterfall-checkout-request-rendered.html
@@ -2078,6 +2078,55 @@
color: var(--text-faint);
font-size: 0.5625rem;
}
+ /* Authored task checklist: read-only, collapsed to one counted line. */
+ .semantic-passport-task-checklist {
+ margin-top: 0.52rem;
+ padding-top: 0.48rem;
+ border-top: 1px solid color-mix(in srgb, var(--toolbar-border) 76%, transparent);
+ }
+ .semantic-passport-task-checklist > summary { cursor: pointer; }
+ .semantic-passport-task-checklist-count {
+ color: var(--text-muted);
+ font-size: 0.5625rem;
+ }
+ .semantic-passport-task-checklist-tasks {
+ display: grid;
+ gap: 0.32rem;
+ max-height: min(16rem, 40vh);
+ margin: 0.45rem 0 0;
+ padding: 0.15rem;
+ overflow: auto;
+ overscroll-behavior: contain;
+ scrollbar-width: thin;
+ scrollbar-color: var(--frontend-stroke) transparent;
+ list-style: none;
+ }
+ .semantic-passport-task-checklist-task {
+ display: grid;
+ grid-template-columns: 0.9rem minmax(0, 1fr);
+ gap: 0.12rem 0.3rem;
+ }
+ .semantic-passport-task-checklist-mark {
+ color: var(--text-faint);
+ font-size: 0.625rem;
+ font-weight: 800;
+ text-align: center;
+ }
+ .semantic-passport-task-checklist-task[data-status="implemented"] .semantic-passport-task-checklist-mark { color: var(--frontend-stroke); }
+ .semantic-passport-task-checklist-task[data-status="needs-review"] .semantic-passport-task-checklist-mark { color: var(--text-muted); }
+ .semantic-passport-task-checklist-task > strong {
+ color: var(--text-muted);
+ font-size: 0.6875rem;
+ overflow-wrap: anywhere;
+ }
+ .semantic-passport-task-checklist-task p,
+ .semantic-passport-task-checklist-task .semantic-passport-source { grid-column: 2; }
+ .semantic-passport-task-checklist-task p {
+ margin: 0;
+ color: var(--text-faint);
+ font-size: 0.5625rem;
+ overflow-wrap: anywhere;
+ }
.semantic-passport-reach {
display: grid;
gap: 0.28rem;
@@ -2086,7 +2135,8 @@
border-top: 1px solid color-mix(in srgb, var(--toolbar-border) 76%, transparent);
}
.semantic-passport-reach[hidden] { display: none; }
- .semantic-passport-reach-label {
+ .semantic-passport-reach-label,
+ .semantic-passport-task-checklist-label {
color: var(--text-faint);
font-size: 0.5rem;
font-weight: 800;
@@ -2154,8 +2204,10 @@
scrollbar-width: thin;
scrollbar-color: var(--frontend-stroke) transparent;
}
- .relationship-lens-list::-webkit-scrollbar { width: 4px; }
- .relationship-lens-list::-webkit-scrollbar-thumb {
+ .relationship-lens-list::-webkit-scrollbar,
+ .semantic-passport-task-checklist-tasks::-webkit-scrollbar { width: 4px; }
+ .relationship-lens-list::-webkit-scrollbar-thumb,
+ .semantic-passport-task-checklist-tasks::-webkit-scrollbar-thumb {
border-radius: 999px;
background: color-mix(in srgb, var(--frontend-stroke) 68%, transparent);
}
@@ -2657,8 +2709,9 @@
as are dark themes and presets that already pass. Zero counts stay faint
on purpose, so they are excluded from the darker rule. Route-target
finders keep their --security-stroke status color, so they are excluded
- too. */
+ too. Task checklist done marks are the same kind of text. */
html[data-theme="light"] .semantic-passport-meta [data-passport="kind"],
+ html[data-theme="light"] .semantic-passport-task-checklist-task[data-status="implemented"] .semantic-passport-task-checklist-mark,
html[data-theme="light"] .node-finder:not([data-context="route-target"]) .node-finder-result em:not([data-zero]) {
color: var(--reader-accent, var(--frontend-stroke));
}
@@ -5407,7 +5460,8 @@
Checkout request execution
-
+
+
@@ -5666,6 +5720,10 @@
Checkout request execution
Ctrl+click or double-click opens the first entry
+
+ Task checklist
+
+
Authored reach
@@ -8445,6 +8503,24 @@
Checkout request execution
count: function () { return payload ? Object.keys(payload).length : 0; }
};
})();
+ /* Authored node task checklists: passport-only, like explanations. Their task
+ sources are verified and read through Archify.sourceEvidence.task(). */
+ Archify.taskChecklists = (function () {
+ var element = document.getElementById('archify-task-checklists-data');
+ var payload = null;
+ if (element) {
+ try {
+ var parsed = JSON.parse(element.textContent || 'null');
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) payload = parsed;
+ } catch (_) {}
+ }
+ return {
+ node: function (id) {
+ var tasks = payload && Object.prototype.hasOwnProperty.call(payload, id) ? payload[id] : null;
+ return Array.isArray(tasks) ? tasks.slice() : [];
+ }
+ };
+ })();
/* Verified repository evidence is emitted only after the renderer checks
the authored GitHub revision, source blobs, and optional line ranges
against the explicit --repo-root. It remains outside the SVG so every
@@ -8484,6 +8560,11 @@
Checkout request execution
node: function (id) {
var sources = payload && payload.nodes ? payload.nodes[id] : null;
return Array.isArray(sources) ? sources.slice() : [];
+ },
+ task: function (id, index) {
+ var tasks = payload && payload.taskChecklists ? payload.taskChecklists[id] : null;
+ var sources = Array.isArray(tasks) ? tasks[index] : null;
+ return Array.isArray(sources) ? sources.slice() : [];
}
};
})();
@@ -8659,6 +8740,9 @@
Checkout request execution
var parentBackBtn = document.getElementById('btn-parent-back');
var drilldownsSection = document.getElementById('focus-drilldowns');
var drilldownsList = document.getElementById('focus-drilldowns-list');
+ var taskChecklistSection = document.getElementById('focus-task-checklist');
+ var taskChecklistCount = document.getElementById('focus-task-checklist-count');
+ var taskChecklistTasks = document.getElementById('focus-task-checklist-tasks');
var moveBtn = document.getElementById('btn-focus-move');
var activeIds = [];
var hoveredRelationship = null;
@@ -8980,30 +9064,71 @@
Checkout request execution
repositoryLink.setAttribute('aria-label', viewerText('viewer.passport.repository.open', { revision: repository.revision }));
}
evidence.title = viewerText('viewer.passport.verificationScope');
- sources.forEach(function (source) {
- var link = document.createElement(source.href ? 'a' : 'div');
- link.className = 'semantic-passport-source';
- if (source.href) {
- link.href = source.href;
- link.target = '_blank';
- link.rel = 'noopener noreferrer';
- link.referrerPolicy = 'no-referrer';
- link.setAttribute('aria-label', viewerText('viewer.passport.source.open', { path: source.path, revision: repository.shortRevision }));
+ sources.forEach(function (source) { evidenceLinks.appendChild(sourceLink(source, repository)); });
+ evidence.hidden = false;
+ }
+ function sourceLink(source, repository) {
+ var link = document.createElement(source.href ? 'a' : 'div');
+ link.className = 'semantic-passport-source';
+ if (source.href) {
+ link.href = source.href;
+ link.target = '_blank';
+ link.rel = 'noopener noreferrer';
+ link.referrerPolicy = 'no-referrer';
+ link.setAttribute('aria-label', viewerText('viewer.passport.source.open', { path: source.path, revision: repository.shortRevision }));
+ }
+ var name = document.createElement('strong');
+ name.textContent = source.label || source.path.split('/').pop() || source.path;
+ var location = document.createElement('code');
+ location.textContent = source.line
+ ? 'L' + source.line + (source.endLine && source.endLine !== source.line ? '–' + source.endLine : '') + (source.href ? ' ↗' : '')
+ : source.href ? viewerText('viewer.passport.source.openLink') : '';
+ var sourcePath = document.createElement('small');
+ sourcePath.textContent = source.path;
+ link.appendChild(name);
+ link.appendChild(location);
+ link.appendChild(sourcePath);
+ return link;
+ }
+ var TASK_CHECKLIST_STATUS = {
+ implemented: ['✓', 'viewer.passport.taskChecklist.status.implemented'],
+ 'needs-review': ['?', 'viewer.passport.taskChecklist.status.needsReview'],
+ pending: ['○', 'viewer.passport.taskChecklist.status.pending']
+ };
+ // Read-only: authored statuses and verified task sources, never edited here.
+ function renderTaskChecklist(id) {
+ if (!taskChecklistSection) return;
+ taskChecklistSection.open = false;
+ taskChecklistTasks.textContent = '';
+ var tasks = Archify.taskChecklists ? Archify.taskChecklists.node(id) : [];
+ var repository = Archify.sourceEvidence.repository();
+ taskChecklistSection.hidden = !tasks.length;
+ tasks.forEach(function (task, index) {
+ var item = document.createElement('li');
+ item.className = 'semantic-passport-task-checklist-task';
+ item.setAttribute('data-status', task.status);
+ var status = TASK_CHECKLIST_STATUS[task.status] || TASK_CHECKLIST_STATUS.pending;
+ var mark = document.createElement('span');
+ mark.className = 'semantic-passport-task-checklist-mark';
+ mark.setAttribute('role', 'img');
+ mark.setAttribute('aria-label', viewerText(status[1]));
+ mark.textContent = status[0];
+ var title = document.createElement('strong');
+ title.textContent = task.title;
+ item.appendChild(mark);
+ item.appendChild(title);
+ if (task.criteria) {
+ var criteria = document.createElement('p');
+ criteria.textContent = task.criteria;
+ item.appendChild(criteria);
}
- var name = document.createElement('strong');
- name.textContent = source.label || source.path.split('/').pop() || source.path;
- var location = document.createElement('code');
- location.textContent = source.line
- ? 'L' + source.line + (source.endLine && source.endLine !== source.line ? '–' + source.endLine : '') + (source.href ? ' ↗' : '')
- : source.href ? viewerText('viewer.passport.source.openLink') : '';
- var sourcePath = document.createElement('small');
- sourcePath.textContent = source.path;
- link.appendChild(name);
- link.appendChild(location);
- link.appendChild(sourcePath);
- evidenceLinks.appendChild(link);
+ Archify.sourceEvidence.task(id, index).forEach(function (source) { item.appendChild(sourceLink(source, repository)); });
+ taskChecklistTasks.appendChild(item);
+ });
+ taskChecklistCount.textContent = viewerText('viewer.passport.taskChecklist.count', {
+ implemented: tasks.filter(function (task) { return task.status === 'implemented'; }).length,
+ total: tasks.length
});
- evidence.hidden = false;
}
function explanationFor(id) {
return Archify.explanations && typeof Archify.explanations.node === 'function' ? Archify.explanations.node(id) : null;
@@ -9086,6 +9211,7 @@
Checkout request execution
semanticId.hidden = false;
renderDrilldowns(node);
renderSourceEvidence(id);
+ renderTaskChecklist(id);
}
/**
* 仅允许同目录文件名.html(可选 ?query / #fragment)。
@@ -10144,6 +10270,10 @@
Checkout request execution
evidenceLinks.textContent = '';
repositoryLink.removeAttribute('href');
repositoryLink.textContent = '';
+ if (taskChecklistSection) {
+ taskChecklistSection.hidden = true;
+ taskChecklistTasks.textContent = '';
+ }
summary.textContent = '';
reachSection.hidden = true;
upstreamCount.textContent = '0';
@@ -10326,6 +10456,7 @@
Checkout request execution
clearBtn.addEventListener('click', function () { clear({ restoreFocus: true }); });
copyBtn.addEventListener('click', copyFocusLink);
if (explainBtn) explainBtn.addEventListener('click', function (event) { event.stopPropagation(); toggleExplanation(); });
+ if (taskChecklistSection) taskChecklistSection.addEventListener('toggle', function () { if (!chip.hidden) requestLensPlacement(); });
if (explanationBack) {
explanationBack.addEventListener('click', function (event) { event.stopPropagation(); closeExplanation(); });
}
diff --git a/examples/waterfall-example-rebuild-rendered.html b/examples/waterfall-example-rebuild-rendered.html
index c15bd96a0..aa43005cd 100644
--- a/examples/waterfall-example-rebuild-rendered.html
+++ b/examples/waterfall-example-rebuild-rendered.html
@@ -2078,6 +2078,55 @@
color: var(--text-faint);
font-size: 0.5625rem;
}
+ /* Authored task checklist: read-only, collapsed to one counted line. */
+ .semantic-passport-task-checklist {
+ margin-top: 0.52rem;
+ padding-top: 0.48rem;
+ border-top: 1px solid color-mix(in srgb, var(--toolbar-border) 76%, transparent);
+ }
+ .semantic-passport-task-checklist > summary { cursor: pointer; }
+ .semantic-passport-task-checklist-count {
+ color: var(--text-muted);
+ font-size: 0.5625rem;
+ }
+ .semantic-passport-task-checklist-tasks {
+ display: grid;
+ gap: 0.32rem;
+ max-height: min(16rem, 40vh);
+ margin: 0.45rem 0 0;
+ padding: 0.15rem;
+ overflow: auto;
+ overscroll-behavior: contain;
+ scrollbar-width: thin;
+ scrollbar-color: var(--frontend-stroke) transparent;
+ list-style: none;
+ }
+ .semantic-passport-task-checklist-task {
+ display: grid;
+ grid-template-columns: 0.9rem minmax(0, 1fr);
+ gap: 0.12rem 0.3rem;
+ }
+ .semantic-passport-task-checklist-mark {
+ color: var(--text-faint);
+ font-size: 0.625rem;
+ font-weight: 800;
+ text-align: center;
+ }
+ .semantic-passport-task-checklist-task[data-status="implemented"] .semantic-passport-task-checklist-mark { color: var(--frontend-stroke); }
+ .semantic-passport-task-checklist-task[data-status="needs-review"] .semantic-passport-task-checklist-mark { color: var(--text-muted); }
+ .semantic-passport-task-checklist-task > strong {
+ color: var(--text-muted);
+ font-size: 0.6875rem;
+ overflow-wrap: anywhere;
+ }
+ .semantic-passport-task-checklist-task p,
+ .semantic-passport-task-checklist-task .semantic-passport-source { grid-column: 2; }
+ .semantic-passport-task-checklist-task p {
+ margin: 0;
+ color: var(--text-faint);
+ font-size: 0.5625rem;
+ overflow-wrap: anywhere;
+ }
.semantic-passport-reach {
display: grid;
gap: 0.28rem;
@@ -2086,7 +2135,8 @@
border-top: 1px solid color-mix(in srgb, var(--toolbar-border) 76%, transparent);
}
.semantic-passport-reach[hidden] { display: none; }
- .semantic-passport-reach-label {
+ .semantic-passport-reach-label,
+ .semantic-passport-task-checklist-label {
color: var(--text-faint);
font-size: 0.5rem;
font-weight: 800;
@@ -2154,8 +2204,10 @@
scrollbar-width: thin;
scrollbar-color: var(--frontend-stroke) transparent;
}
- .relationship-lens-list::-webkit-scrollbar { width: 4px; }
- .relationship-lens-list::-webkit-scrollbar-thumb {
+ .relationship-lens-list::-webkit-scrollbar,
+ .semantic-passport-task-checklist-tasks::-webkit-scrollbar { width: 4px; }
+ .relationship-lens-list::-webkit-scrollbar-thumb,
+ .semantic-passport-task-checklist-tasks::-webkit-scrollbar-thumb {
border-radius: 999px;
background: color-mix(in srgb, var(--frontend-stroke) 68%, transparent);
}
@@ -2657,8 +2709,9 @@
as are dark themes and presets that already pass. Zero counts stay faint
on purpose, so they are excluded from the darker rule. Route-target
finders keep their --security-stroke status color, so they are excluded
- too. */
+ too. Task checklist done marks are the same kind of text. */
html[data-theme="light"] .semantic-passport-meta [data-passport="kind"],
+ html[data-theme="light"] .semantic-passport-task-checklist-task[data-status="implemented"] .semantic-passport-task-checklist-mark,
html[data-theme="light"] .node-finder:not([data-context="route-target"]) .node-finder-result em:not([data-zero]) {
color: var(--reader-accent, var(--frontend-stroke));
}
@@ -5407,7 +5460,8 @@
Rebuilding the bundled examples
-
+
+
@@ -5852,6 +5906,10 @@
Rebuilding the bundled examples
Ctrl+click or double-click opens the first entry
+
+ Task checklist
+
+
Authored reach
@@ -8631,6 +8689,24 @@
Rebuilding the bundled examples
count: function () { return payload ? Object.keys(payload).length : 0; }
};
})();
+ /* Authored node task checklists: passport-only, like explanations. Their task
+ sources are verified and read through Archify.sourceEvidence.task(). */
+ Archify.taskChecklists = (function () {
+ var element = document.getElementById('archify-task-checklists-data');
+ var payload = null;
+ if (element) {
+ try {
+ var parsed = JSON.parse(element.textContent || 'null');
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) payload = parsed;
+ } catch (_) {}
+ }
+ return {
+ node: function (id) {
+ var tasks = payload && Object.prototype.hasOwnProperty.call(payload, id) ? payload[id] : null;
+ return Array.isArray(tasks) ? tasks.slice() : [];
+ }
+ };
+ })();
/* Verified repository evidence is emitted only after the renderer checks
the authored GitHub revision, source blobs, and optional line ranges
against the explicit --repo-root. It remains outside the SVG so every
@@ -8670,6 +8746,11 @@
Rebuilding the bundled examples
node: function (id) {
var sources = payload && payload.nodes ? payload.nodes[id] : null;
return Array.isArray(sources) ? sources.slice() : [];
+ },
+ task: function (id, index) {
+ var tasks = payload && payload.taskChecklists ? payload.taskChecklists[id] : null;
+ var sources = Array.isArray(tasks) ? tasks[index] : null;
+ return Array.isArray(sources) ? sources.slice() : [];
}
};
})();
@@ -8845,6 +8926,9 @@
Rebuilding the bundled examples
var parentBackBtn = document.getElementById('btn-parent-back');
var drilldownsSection = document.getElementById('focus-drilldowns');
var drilldownsList = document.getElementById('focus-drilldowns-list');
+ var taskChecklistSection = document.getElementById('focus-task-checklist');
+ var taskChecklistCount = document.getElementById('focus-task-checklist-count');
+ var taskChecklistTasks = document.getElementById('focus-task-checklist-tasks');
var moveBtn = document.getElementById('btn-focus-move');
var activeIds = [];
var hoveredRelationship = null;
@@ -9166,30 +9250,71 @@
Rebuilding the bundled examples
repositoryLink.setAttribute('aria-label', viewerText('viewer.passport.repository.open', { revision: repository.revision }));
}
evidence.title = viewerText('viewer.passport.verificationScope');
- sources.forEach(function (source) {
- var link = document.createElement(source.href ? 'a' : 'div');
- link.className = 'semantic-passport-source';
- if (source.href) {
- link.href = source.href;
- link.target = '_blank';
- link.rel = 'noopener noreferrer';
- link.referrerPolicy = 'no-referrer';
- link.setAttribute('aria-label', viewerText('viewer.passport.source.open', { path: source.path, revision: repository.shortRevision }));
+ sources.forEach(function (source) { evidenceLinks.appendChild(sourceLink(source, repository)); });
+ evidence.hidden = false;
+ }
+ function sourceLink(source, repository) {
+ var link = document.createElement(source.href ? 'a' : 'div');
+ link.className = 'semantic-passport-source';
+ if (source.href) {
+ link.href = source.href;
+ link.target = '_blank';
+ link.rel = 'noopener noreferrer';
+ link.referrerPolicy = 'no-referrer';
+ link.setAttribute('aria-label', viewerText('viewer.passport.source.open', { path: source.path, revision: repository.shortRevision }));
+ }
+ var name = document.createElement('strong');
+ name.textContent = source.label || source.path.split('/').pop() || source.path;
+ var location = document.createElement('code');
+ location.textContent = source.line
+ ? 'L' + source.line + (source.endLine && source.endLine !== source.line ? '–' + source.endLine : '') + (source.href ? ' ↗' : '')
+ : source.href ? viewerText('viewer.passport.source.openLink') : '';
+ var sourcePath = document.createElement('small');
+ sourcePath.textContent = source.path;
+ link.appendChild(name);
+ link.appendChild(location);
+ link.appendChild(sourcePath);
+ return link;
+ }
+ var TASK_CHECKLIST_STATUS = {
+ implemented: ['✓', 'viewer.passport.taskChecklist.status.implemented'],
+ 'needs-review': ['?', 'viewer.passport.taskChecklist.status.needsReview'],
+ pending: ['○', 'viewer.passport.taskChecklist.status.pending']
+ };
+ // Read-only: authored statuses and verified task sources, never edited here.
+ function renderTaskChecklist(id) {
+ if (!taskChecklistSection) return;
+ taskChecklistSection.open = false;
+ taskChecklistTasks.textContent = '';
+ var tasks = Archify.taskChecklists ? Archify.taskChecklists.node(id) : [];
+ var repository = Archify.sourceEvidence.repository();
+ taskChecklistSection.hidden = !tasks.length;
+ tasks.forEach(function (task, index) {
+ var item = document.createElement('li');
+ item.className = 'semantic-passport-task-checklist-task';
+ item.setAttribute('data-status', task.status);
+ var status = TASK_CHECKLIST_STATUS[task.status] || TASK_CHECKLIST_STATUS.pending;
+ var mark = document.createElement('span');
+ mark.className = 'semantic-passport-task-checklist-mark';
+ mark.setAttribute('role', 'img');
+ mark.setAttribute('aria-label', viewerText(status[1]));
+ mark.textContent = status[0];
+ var title = document.createElement('strong');
+ title.textContent = task.title;
+ item.appendChild(mark);
+ item.appendChild(title);
+ if (task.criteria) {
+ var criteria = document.createElement('p');
+ criteria.textContent = task.criteria;
+ item.appendChild(criteria);
}
- var name = document.createElement('strong');
- name.textContent = source.label || source.path.split('/').pop() || source.path;
- var location = document.createElement('code');
- location.textContent = source.line
- ? 'L' + source.line + (source.endLine && source.endLine !== source.line ? '–' + source.endLine : '') + (source.href ? ' ↗' : '')
- : source.href ? viewerText('viewer.passport.source.openLink') : '';
- var sourcePath = document.createElement('small');
- sourcePath.textContent = source.path;
- link.appendChild(name);
- link.appendChild(location);
- link.appendChild(sourcePath);
- evidenceLinks.appendChild(link);
+ Archify.sourceEvidence.task(id, index).forEach(function (source) { item.appendChild(sourceLink(source, repository)); });
+ taskChecklistTasks.appendChild(item);
+ });
+ taskChecklistCount.textContent = viewerText('viewer.passport.taskChecklist.count', {
+ implemented: tasks.filter(function (task) { return task.status === 'implemented'; }).length,
+ total: tasks.length
});
- evidence.hidden = false;
}
function explanationFor(id) {
return Archify.explanations && typeof Archify.explanations.node === 'function' ? Archify.explanations.node(id) : null;
@@ -9272,6 +9397,7 @@
Rebuilding the bundled examples
semanticId.hidden = false;
renderDrilldowns(node);
renderSourceEvidence(id);
+ renderTaskChecklist(id);
}
/**
* 仅允许同目录文件名.html(可选 ?query / #fragment)。
@@ -10330,6 +10456,10 @@
Rebuilding the bundled examples
evidenceLinks.textContent = '';
repositoryLink.removeAttribute('href');
repositoryLink.textContent = '';
+ if (taskChecklistSection) {
+ taskChecklistSection.hidden = true;
+ taskChecklistTasks.textContent = '';
+ }
summary.textContent = '';
reachSection.hidden = true;
upstreamCount.textContent = '0';
@@ -10512,6 +10642,7 @@
Rebuilding the bundled examples
clearBtn.addEventListener('click', function () { clear({ restoreFocus: true }); });
copyBtn.addEventListener('click', copyFocusLink);
if (explainBtn) explainBtn.addEventListener('click', function (event) { event.stopPropagation(); toggleExplanation(); });
+ if (taskChecklistSection) taskChecklistSection.addEventListener('toggle', function () { if (!chip.hidden) requestLensPlacement(); });
if (explanationBack) {
explanationBack.addEventListener('click', function (event) { event.stopPropagation(); closeExplanation(); });
}
diff --git a/examples/web-app-rendered.html b/examples/web-app-rendered.html
index 5a59859e8..917f77cec 100644
--- a/examples/web-app-rendered.html
+++ b/examples/web-app-rendered.html
@@ -2078,6 +2078,55 @@
color: var(--text-faint);
font-size: 0.5625rem;
}
+ /* Authored task checklist: read-only, collapsed to one counted line. */
+ .semantic-passport-task-checklist {
+ margin-top: 0.52rem;
+ padding-top: 0.48rem;
+ border-top: 1px solid color-mix(in srgb, var(--toolbar-border) 76%, transparent);
+ }
+ .semantic-passport-task-checklist > summary { cursor: pointer; }
+ .semantic-passport-task-checklist-count {
+ color: var(--text-muted);
+ font-size: 0.5625rem;
+ }
+ .semantic-passport-task-checklist-tasks {
+ display: grid;
+ gap: 0.32rem;
+ max-height: min(16rem, 40vh);
+ margin: 0.45rem 0 0;
+ padding: 0.15rem;
+ overflow: auto;
+ overscroll-behavior: contain;
+ scrollbar-width: thin;
+ scrollbar-color: var(--frontend-stroke) transparent;
+ list-style: none;
+ }
+ .semantic-passport-task-checklist-task {
+ display: grid;
+ grid-template-columns: 0.9rem minmax(0, 1fr);
+ gap: 0.12rem 0.3rem;
+ }
+ .semantic-passport-task-checklist-mark {
+ color: var(--text-faint);
+ font-size: 0.625rem;
+ font-weight: 800;
+ text-align: center;
+ }
+ .semantic-passport-task-checklist-task[data-status="implemented"] .semantic-passport-task-checklist-mark { color: var(--frontend-stroke); }
+ .semantic-passport-task-checklist-task[data-status="needs-review"] .semantic-passport-task-checklist-mark { color: var(--text-muted); }
+ .semantic-passport-task-checklist-task > strong {
+ color: var(--text-muted);
+ font-size: 0.6875rem;
+ overflow-wrap: anywhere;
+ }
+ .semantic-passport-task-checklist-task p,
+ .semantic-passport-task-checklist-task .semantic-passport-source { grid-column: 2; }
+ .semantic-passport-task-checklist-task p {
+ margin: 0;
+ color: var(--text-faint);
+ font-size: 0.5625rem;
+ overflow-wrap: anywhere;
+ }
.semantic-passport-reach {
display: grid;
gap: 0.28rem;
@@ -2086,7 +2135,8 @@
border-top: 1px solid color-mix(in srgb, var(--toolbar-border) 76%, transparent);
}
.semantic-passport-reach[hidden] { display: none; }
- .semantic-passport-reach-label {
+ .semantic-passport-reach-label,
+ .semantic-passport-task-checklist-label {
color: var(--text-faint);
font-size: 0.5rem;
font-weight: 800;
@@ -2154,8 +2204,10 @@
scrollbar-width: thin;
scrollbar-color: var(--frontend-stroke) transparent;
}
- .relationship-lens-list::-webkit-scrollbar { width: 4px; }
- .relationship-lens-list::-webkit-scrollbar-thumb {
+ .relationship-lens-list::-webkit-scrollbar,
+ .semantic-passport-task-checklist-tasks::-webkit-scrollbar { width: 4px; }
+ .relationship-lens-list::-webkit-scrollbar-thumb,
+ .semantic-passport-task-checklist-tasks::-webkit-scrollbar-thumb {
border-radius: 999px;
background: color-mix(in srgb, var(--frontend-stroke) 68%, transparent);
}
@@ -2657,8 +2709,9 @@
as are dark themes and presets that already pass. Zero counts stay faint
on purpose, so they are excluded from the darker rule. Route-target
finders keep their --security-stroke status color, so they are excluded
- too. */
+ too. Task checklist done marks are the same kind of text. */
html[data-theme="light"] .semantic-passport-meta [data-passport="kind"],
+ html[data-theme="light"] .semantic-passport-task-checklist-task[data-status="implemented"] .semantic-passport-task-checklist-mark,
html[data-theme="light"] .node-finder:not([data-context="route-target"]) .node-finder-result em:not([data-zero]) {
color: var(--reader-accent, var(--frontend-stroke));
}
@@ -5406,7 +5459,8 @@
Sample Web App
-
+
+
@@ -5769,6 +5823,10 @@
Sample Web App
Ctrl+click or double-click opens the first entry
+
+ Task checklist
+
+
Authored reach
@@ -8580,6 +8638,24 @@
Security
count: function () { return payload ? Object.keys(payload).length : 0; }
};
})();
+ /* Authored node task checklists: passport-only, like explanations. Their task
+ sources are verified and read through Archify.sourceEvidence.task(). */
+ Archify.taskChecklists = (function () {
+ var element = document.getElementById('archify-task-checklists-data');
+ var payload = null;
+ if (element) {
+ try {
+ var parsed = JSON.parse(element.textContent || 'null');
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) payload = parsed;
+ } catch (_) {}
+ }
+ return {
+ node: function (id) {
+ var tasks = payload && Object.prototype.hasOwnProperty.call(payload, id) ? payload[id] : null;
+ return Array.isArray(tasks) ? tasks.slice() : [];
+ }
+ };
+ })();
/* Verified repository evidence is emitted only after the renderer checks
the authored GitHub revision, source blobs, and optional line ranges
against the explicit --repo-root. It remains outside the SVG so every
@@ -8619,6 +8695,11 @@
Security
node: function (id) {
var sources = payload && payload.nodes ? payload.nodes[id] : null;
return Array.isArray(sources) ? sources.slice() : [];
+ },
+ task: function (id, index) {
+ var tasks = payload && payload.taskChecklists ? payload.taskChecklists[id] : null;
+ var sources = Array.isArray(tasks) ? tasks[index] : null;
+ return Array.isArray(sources) ? sources.slice() : [];
}
};
})();
@@ -8794,6 +8875,9 @@
Security
var parentBackBtn = document.getElementById('btn-parent-back');
var drilldownsSection = document.getElementById('focus-drilldowns');
var drilldownsList = document.getElementById('focus-drilldowns-list');
+ var taskChecklistSection = document.getElementById('focus-task-checklist');
+ var taskChecklistCount = document.getElementById('focus-task-checklist-count');
+ var taskChecklistTasks = document.getElementById('focus-task-checklist-tasks');
var moveBtn = document.getElementById('btn-focus-move');
var activeIds = [];
var hoveredRelationship = null;
@@ -9115,30 +9199,71 @@
Security
repositoryLink.setAttribute('aria-label', viewerText('viewer.passport.repository.open', { revision: repository.revision }));
}
evidence.title = viewerText('viewer.passport.verificationScope');
- sources.forEach(function (source) {
- var link = document.createElement(source.href ? 'a' : 'div');
- link.className = 'semantic-passport-source';
- if (source.href) {
- link.href = source.href;
- link.target = '_blank';
- link.rel = 'noopener noreferrer';
- link.referrerPolicy = 'no-referrer';
- link.setAttribute('aria-label', viewerText('viewer.passport.source.open', { path: source.path, revision: repository.shortRevision }));
+ sources.forEach(function (source) { evidenceLinks.appendChild(sourceLink(source, repository)); });
+ evidence.hidden = false;
+ }
+ function sourceLink(source, repository) {
+ var link = document.createElement(source.href ? 'a' : 'div');
+ link.className = 'semantic-passport-source';
+ if (source.href) {
+ link.href = source.href;
+ link.target = '_blank';
+ link.rel = 'noopener noreferrer';
+ link.referrerPolicy = 'no-referrer';
+ link.setAttribute('aria-label', viewerText('viewer.passport.source.open', { path: source.path, revision: repository.shortRevision }));
+ }
+ var name = document.createElement('strong');
+ name.textContent = source.label || source.path.split('/').pop() || source.path;
+ var location = document.createElement('code');
+ location.textContent = source.line
+ ? 'L' + source.line + (source.endLine && source.endLine !== source.line ? '–' + source.endLine : '') + (source.href ? ' ↗' : '')
+ : source.href ? viewerText('viewer.passport.source.openLink') : '';
+ var sourcePath = document.createElement('small');
+ sourcePath.textContent = source.path;
+ link.appendChild(name);
+ link.appendChild(location);
+ link.appendChild(sourcePath);
+ return link;
+ }
+ var TASK_CHECKLIST_STATUS = {
+ implemented: ['✓', 'viewer.passport.taskChecklist.status.implemented'],
+ 'needs-review': ['?', 'viewer.passport.taskChecklist.status.needsReview'],
+ pending: ['○', 'viewer.passport.taskChecklist.status.pending']
+ };
+ // Read-only: authored statuses and verified task sources, never edited here.
+ function renderTaskChecklist(id) {
+ if (!taskChecklistSection) return;
+ taskChecklistSection.open = false;
+ taskChecklistTasks.textContent = '';
+ var tasks = Archify.taskChecklists ? Archify.taskChecklists.node(id) : [];
+ var repository = Archify.sourceEvidence.repository();
+ taskChecklistSection.hidden = !tasks.length;
+ tasks.forEach(function (task, index) {
+ var item = document.createElement('li');
+ item.className = 'semantic-passport-task-checklist-task';
+ item.setAttribute('data-status', task.status);
+ var status = TASK_CHECKLIST_STATUS[task.status] || TASK_CHECKLIST_STATUS.pending;
+ var mark = document.createElement('span');
+ mark.className = 'semantic-passport-task-checklist-mark';
+ mark.setAttribute('role', 'img');
+ mark.setAttribute('aria-label', viewerText(status[1]));
+ mark.textContent = status[0];
+ var title = document.createElement('strong');
+ title.textContent = task.title;
+ item.appendChild(mark);
+ item.appendChild(title);
+ if (task.criteria) {
+ var criteria = document.createElement('p');
+ criteria.textContent = task.criteria;
+ item.appendChild(criteria);
}
- var name = document.createElement('strong');
- name.textContent = source.label || source.path.split('/').pop() || source.path;
- var location = document.createElement('code');
- location.textContent = source.line
- ? 'L' + source.line + (source.endLine && source.endLine !== source.line ? '–' + source.endLine : '') + (source.href ? ' ↗' : '')
- : source.href ? viewerText('viewer.passport.source.openLink') : '';
- var sourcePath = document.createElement('small');
- sourcePath.textContent = source.path;
- link.appendChild(name);
- link.appendChild(location);
- link.appendChild(sourcePath);
- evidenceLinks.appendChild(link);
+ Archify.sourceEvidence.task(id, index).forEach(function (source) { item.appendChild(sourceLink(source, repository)); });
+ taskChecklistTasks.appendChild(item);
+ });
+ taskChecklistCount.textContent = viewerText('viewer.passport.taskChecklist.count', {
+ implemented: tasks.filter(function (task) { return task.status === 'implemented'; }).length,
+ total: tasks.length
});
- evidence.hidden = false;
}
function explanationFor(id) {
return Archify.explanations && typeof Archify.explanations.node === 'function' ? Archify.explanations.node(id) : null;
@@ -9221,6 +9346,7 @@
Security
semanticId.hidden = false;
renderDrilldowns(node);
renderSourceEvidence(id);
+ renderTaskChecklist(id);
}
/**
* 仅允许同目录文件名.html(可选 ?query / #fragment)。
@@ -10279,6 +10405,10 @@
Security
evidenceLinks.textContent = '';
repositoryLink.removeAttribute('href');
repositoryLink.textContent = '';
+ if (taskChecklistSection) {
+ taskChecklistSection.hidden = true;
+ taskChecklistTasks.textContent = '';
+ }
summary.textContent = '';
reachSection.hidden = true;
upstreamCount.textContent = '0';
@@ -10461,6 +10591,7 @@
Security
clearBtn.addEventListener('click', function () { clear({ restoreFocus: true }); });
copyBtn.addEventListener('click', copyFocusLink);
if (explainBtn) explainBtn.addEventListener('click', function (event) { event.stopPropagation(); toggleExplanation(); });
+ if (taskChecklistSection) taskChecklistSection.addEventListener('toggle', function () { if (!chip.hidden) requestLensPlacement(); });
if (explanationBack) {
explanationBack.addEventListener('click', function (event) { event.stopPropagation(); closeExplanation(); });
}
diff --git a/examples/web-app.html b/examples/web-app.html
index 5a59859e8..13179e350 100644
--- a/examples/web-app.html
+++ b/examples/web-app.html
@@ -2078,6 +2078,55 @@
color: var(--text-faint);
font-size: 0.5625rem;
}
+ /* Authored task checklist: read-only, collapsed to one counted line. */
+ .semantic-passport-task-checklist {
+ margin-top: 0.52rem;
+ padding-top: 0.48rem;
+ border-top: 1px solid color-mix(in srgb, var(--toolbar-border) 76%, transparent);
+ }
+ .semantic-passport-task-checklist > summary { cursor: pointer; }
+ .semantic-passport-task-checklist-count {
+ color: var(--text-muted);
+ font-size: 0.5625rem;
+ }
+ .semantic-passport-task-checklist-tasks {
+ display: grid;
+ gap: 0.32rem;
+ max-height: min(16rem, 40vh);
+ margin: 0.45rem 0 0;
+ padding: 0.15rem;
+ overflow: auto;
+ overscroll-behavior: contain;
+ scrollbar-width: thin;
+ scrollbar-color: var(--frontend-stroke) transparent;
+ list-style: none;
+ }
+ .semantic-passport-task-checklist-task {
+ display: grid;
+ grid-template-columns: 0.9rem minmax(0, 1fr);
+ gap: 0.12rem 0.3rem;
+ }
+ .semantic-passport-task-checklist-mark {
+ color: var(--text-faint);
+ font-size: 0.625rem;
+ font-weight: 800;
+ text-align: center;
+ }
+ .semantic-passport-task-checklist-task[data-status="implemented"] .semantic-passport-task-checklist-mark { color: var(--frontend-stroke); }
+ .semantic-passport-task-checklist-task[data-status="needs-review"] .semantic-passport-task-checklist-mark { color: var(--text-muted); }
+ .semantic-passport-task-checklist-task > strong {
+ color: var(--text-muted);
+ font-size: 0.6875rem;
+ overflow-wrap: anywhere;
+ }
+ .semantic-passport-task-checklist-task p,
+ .semantic-passport-task-checklist-task .semantic-passport-source { grid-column: 2; }
+ .semantic-passport-task-checklist-task p {
+ margin: 0;
+ color: var(--text-faint);
+ font-size: 0.5625rem;
+ overflow-wrap: anywhere;
+ }
.semantic-passport-reach {
display: grid;
gap: 0.28rem;
@@ -2086,7 +2135,8 @@
border-top: 1px solid color-mix(in srgb, var(--toolbar-border) 76%, transparent);
}
.semantic-passport-reach[hidden] { display: none; }
- .semantic-passport-reach-label {
+ .semantic-passport-reach-label,
+ .semantic-passport-task-checklist-label {
color: var(--text-faint);
font-size: 0.5rem;
font-weight: 800;
@@ -2154,8 +2204,10 @@
scrollbar-width: thin;
scrollbar-color: var(--frontend-stroke) transparent;
}
- .relationship-lens-list::-webkit-scrollbar { width: 4px; }
- .relationship-lens-list::-webkit-scrollbar-thumb {
+ .relationship-lens-list::-webkit-scrollbar,
+ .semantic-passport-task-checklist-tasks::-webkit-scrollbar { width: 4px; }
+ .relationship-lens-list::-webkit-scrollbar-thumb,
+ .semantic-passport-task-checklist-tasks::-webkit-scrollbar-thumb {
border-radius: 999px;
background: color-mix(in srgb, var(--frontend-stroke) 68%, transparent);
}
@@ -2657,8 +2709,9 @@
as are dark themes and presets that already pass. Zero counts stay faint
on purpose, so they are excluded from the darker rule. Route-target
finders keep their --security-stroke status color, so they are excluded
- too. */
+ too. Task checklist done marks are the same kind of text. */
html[data-theme="light"] .semantic-passport-meta [data-passport="kind"],
+ html[data-theme="light"] .semantic-passport-task-checklist-task[data-status="implemented"] .semantic-passport-task-checklist-mark,
html[data-theme="light"] .node-finder:not([data-context="route-target"]) .node-finder-result em:not([data-zero]) {
color: var(--reader-accent, var(--frontend-stroke));
}
@@ -5406,7 +5459,7 @@
Sample Web App
-
+
@@ -5769,6 +5822,10 @@
Sample Web App
Ctrl+click or double-click opens the first entry
+
+ Task checklist
+
+
Authored reach
@@ -8580,6 +8637,24 @@
Security
count: function () { return payload ? Object.keys(payload).length : 0; }
};
})();
+ /* Authored node task checklists: passport-only, like explanations. Their task
+ sources are verified and read through Archify.sourceEvidence.task(). */
+ Archify.taskChecklists = (function () {
+ var element = document.getElementById('archify-task-checklists-data');
+ var payload = null;
+ if (element) {
+ try {
+ var parsed = JSON.parse(element.textContent || 'null');
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) payload = parsed;
+ } catch (_) {}
+ }
+ return {
+ node: function (id) {
+ var tasks = payload && Object.prototype.hasOwnProperty.call(payload, id) ? payload[id] : null;
+ return Array.isArray(tasks) ? tasks.slice() : [];
+ }
+ };
+ })();
/* Verified repository evidence is emitted only after the renderer checks
the authored GitHub revision, source blobs, and optional line ranges
against the explicit --repo-root. It remains outside the SVG so every
@@ -8619,6 +8694,11 @@
Security
node: function (id) {
var sources = payload && payload.nodes ? payload.nodes[id] : null;
return Array.isArray(sources) ? sources.slice() : [];
+ },
+ task: function (id, index) {
+ var tasks = payload && payload.taskChecklists ? payload.taskChecklists[id] : null;
+ var sources = Array.isArray(tasks) ? tasks[index] : null;
+ return Array.isArray(sources) ? sources.slice() : [];
}
};
})();
@@ -8794,6 +8874,9 @@
Security
var parentBackBtn = document.getElementById('btn-parent-back');
var drilldownsSection = document.getElementById('focus-drilldowns');
var drilldownsList = document.getElementById('focus-drilldowns-list');
+ var taskChecklistSection = document.getElementById('focus-task-checklist');
+ var taskChecklistCount = document.getElementById('focus-task-checklist-count');
+ var taskChecklistTasks = document.getElementById('focus-task-checklist-tasks');
var moveBtn = document.getElementById('btn-focus-move');
var activeIds = [];
var hoveredRelationship = null;
@@ -9115,30 +9198,71 @@
Security
repositoryLink.setAttribute('aria-label', viewerText('viewer.passport.repository.open', { revision: repository.revision }));
}
evidence.title = viewerText('viewer.passport.verificationScope');
- sources.forEach(function (source) {
- var link = document.createElement(source.href ? 'a' : 'div');
- link.className = 'semantic-passport-source';
- if (source.href) {
- link.href = source.href;
- link.target = '_blank';
- link.rel = 'noopener noreferrer';
- link.referrerPolicy = 'no-referrer';
- link.setAttribute('aria-label', viewerText('viewer.passport.source.open', { path: source.path, revision: repository.shortRevision }));
+ sources.forEach(function (source) { evidenceLinks.appendChild(sourceLink(source, repository)); });
+ evidence.hidden = false;
+ }
+ function sourceLink(source, repository) {
+ var link = document.createElement(source.href ? 'a' : 'div');
+ link.className = 'semantic-passport-source';
+ if (source.href) {
+ link.href = source.href;
+ link.target = '_blank';
+ link.rel = 'noopener noreferrer';
+ link.referrerPolicy = 'no-referrer';
+ link.setAttribute('aria-label', viewerText('viewer.passport.source.open', { path: source.path, revision: repository.shortRevision }));
+ }
+ var name = document.createElement('strong');
+ name.textContent = source.label || source.path.split('/').pop() || source.path;
+ var location = document.createElement('code');
+ location.textContent = source.line
+ ? 'L' + source.line + (source.endLine && source.endLine !== source.line ? '–' + source.endLine : '') + (source.href ? ' ↗' : '')
+ : source.href ? viewerText('viewer.passport.source.openLink') : '';
+ var sourcePath = document.createElement('small');
+ sourcePath.textContent = source.path;
+ link.appendChild(name);
+ link.appendChild(location);
+ link.appendChild(sourcePath);
+ return link;
+ }
+ var TASK_CHECKLIST_STATUS = {
+ implemented: ['✓', 'viewer.passport.taskChecklist.status.implemented'],
+ 'needs-review': ['?', 'viewer.passport.taskChecklist.status.needsReview'],
+ pending: ['○', 'viewer.passport.taskChecklist.status.pending']
+ };
+ // Read-only: authored statuses and verified task sources, never edited here.
+ function renderTaskChecklist(id) {
+ if (!taskChecklistSection) return;
+ taskChecklistSection.open = false;
+ taskChecklistTasks.textContent = '';
+ var tasks = Archify.taskChecklists ? Archify.taskChecklists.node(id) : [];
+ var repository = Archify.sourceEvidence.repository();
+ taskChecklistSection.hidden = !tasks.length;
+ tasks.forEach(function (task, index) {
+ var item = document.createElement('li');
+ item.className = 'semantic-passport-task-checklist-task';
+ item.setAttribute('data-status', task.status);
+ var status = TASK_CHECKLIST_STATUS[task.status] || TASK_CHECKLIST_STATUS.pending;
+ var mark = document.createElement('span');
+ mark.className = 'semantic-passport-task-checklist-mark';
+ mark.setAttribute('role', 'img');
+ mark.setAttribute('aria-label', viewerText(status[1]));
+ mark.textContent = status[0];
+ var title = document.createElement('strong');
+ title.textContent = task.title;
+ item.appendChild(mark);
+ item.appendChild(title);
+ if (task.criteria) {
+ var criteria = document.createElement('p');
+ criteria.textContent = task.criteria;
+ item.appendChild(criteria);
}
- var name = document.createElement('strong');
- name.textContent = source.label || source.path.split('/').pop() || source.path;
- var location = document.createElement('code');
- location.textContent = source.line
- ? 'L' + source.line + (source.endLine && source.endLine !== source.line ? '–' + source.endLine : '') + (source.href ? ' ↗' : '')
- : source.href ? viewerText('viewer.passport.source.openLink') : '';
- var sourcePath = document.createElement('small');
- sourcePath.textContent = source.path;
- link.appendChild(name);
- link.appendChild(location);
- link.appendChild(sourcePath);
- evidenceLinks.appendChild(link);
+ Archify.sourceEvidence.task(id, index).forEach(function (source) { item.appendChild(sourceLink(source, repository)); });
+ taskChecklistTasks.appendChild(item);
+ });
+ taskChecklistCount.textContent = viewerText('viewer.passport.taskChecklist.count', {
+ implemented: tasks.filter(function (task) { return task.status === 'implemented'; }).length,
+ total: tasks.length
});
- evidence.hidden = false;
}
function explanationFor(id) {
return Archify.explanations && typeof Archify.explanations.node === 'function' ? Archify.explanations.node(id) : null;
@@ -9221,6 +9345,7 @@
Security
semanticId.hidden = false;
renderDrilldowns(node);
renderSourceEvidence(id);
+ renderTaskChecklist(id);
}
/**
* 仅允许同目录文件名.html(可选 ?query / #fragment)。
@@ -10279,6 +10404,10 @@
Security
evidenceLinks.textContent = '';
repositoryLink.removeAttribute('href');
repositoryLink.textContent = '';
+ if (taskChecklistSection) {
+ taskChecklistSection.hidden = true;
+ taskChecklistTasks.textContent = '';
+ }
summary.textContent = '';
reachSection.hidden = true;
upstreamCount.textContent = '0';
@@ -10461,6 +10590,7 @@
Security
clearBtn.addEventListener('click', function () { clear({ restoreFocus: true }); });
copyBtn.addEventListener('click', copyFocusLink);
if (explainBtn) explainBtn.addEventListener('click', function (event) { event.stopPropagation(); toggleExplanation(); });
+ if (taskChecklistSection) taskChecklistSection.addEventListener('toggle', function () { if (!chip.hidden) requestLensPlacement(); });
if (explanationBack) {
explanationBack.addEventListener('click', function (event) { event.stopPropagation(); closeExplanation(); });
}
diff --git a/examples/workflow-agent-tool-call-rendered.html b/examples/workflow-agent-tool-call-rendered.html
index 697b89906..8d8907091 100644
--- a/examples/workflow-agent-tool-call-rendered.html
+++ b/examples/workflow-agent-tool-call-rendered.html
@@ -2078,6 +2078,55 @@
color: var(--text-faint);
font-size: 0.5625rem;
}
+ /* Authored task checklist: read-only, collapsed to one counted line. */
+ .semantic-passport-task-checklist {
+ margin-top: 0.52rem;
+ padding-top: 0.48rem;
+ border-top: 1px solid color-mix(in srgb, var(--toolbar-border) 76%, transparent);
+ }
+ .semantic-passport-task-checklist > summary { cursor: pointer; }
+ .semantic-passport-task-checklist-count {
+ color: var(--text-muted);
+ font-size: 0.5625rem;
+ }
+ .semantic-passport-task-checklist-tasks {
+ display: grid;
+ gap: 0.32rem;
+ max-height: min(16rem, 40vh);
+ margin: 0.45rem 0 0;
+ padding: 0.15rem;
+ overflow: auto;
+ overscroll-behavior: contain;
+ scrollbar-width: thin;
+ scrollbar-color: var(--frontend-stroke) transparent;
+ list-style: none;
+ }
+ .semantic-passport-task-checklist-task {
+ display: grid;
+ grid-template-columns: 0.9rem minmax(0, 1fr);
+ gap: 0.12rem 0.3rem;
+ }
+ .semantic-passport-task-checklist-mark {
+ color: var(--text-faint);
+ font-size: 0.625rem;
+ font-weight: 800;
+ text-align: center;
+ }
+ .semantic-passport-task-checklist-task[data-status="implemented"] .semantic-passport-task-checklist-mark { color: var(--frontend-stroke); }
+ .semantic-passport-task-checklist-task[data-status="needs-review"] .semantic-passport-task-checklist-mark { color: var(--text-muted); }
+ .semantic-passport-task-checklist-task > strong {
+ color: var(--text-muted);
+ font-size: 0.6875rem;
+ overflow-wrap: anywhere;
+ }
+ .semantic-passport-task-checklist-task p,
+ .semantic-passport-task-checklist-task .semantic-passport-source { grid-column: 2; }
+ .semantic-passport-task-checklist-task p {
+ margin: 0;
+ color: var(--text-faint);
+ font-size: 0.5625rem;
+ overflow-wrap: anywhere;
+ }
.semantic-passport-reach {
display: grid;
gap: 0.28rem;
@@ -2086,7 +2135,8 @@
border-top: 1px solid color-mix(in srgb, var(--toolbar-border) 76%, transparent);
}
.semantic-passport-reach[hidden] { display: none; }
- .semantic-passport-reach-label {
+ .semantic-passport-reach-label,
+ .semantic-passport-task-checklist-label {
color: var(--text-faint);
font-size: 0.5rem;
font-weight: 800;
@@ -2154,8 +2204,10 @@
scrollbar-width: thin;
scrollbar-color: var(--frontend-stroke) transparent;
}
- .relationship-lens-list::-webkit-scrollbar { width: 4px; }
- .relationship-lens-list::-webkit-scrollbar-thumb {
+ .relationship-lens-list::-webkit-scrollbar,
+ .semantic-passport-task-checklist-tasks::-webkit-scrollbar { width: 4px; }
+ .relationship-lens-list::-webkit-scrollbar-thumb,
+ .semantic-passport-task-checklist-tasks::-webkit-scrollbar-thumb {
border-radius: 999px;
background: color-mix(in srgb, var(--frontend-stroke) 68%, transparent);
}
@@ -2657,8 +2709,9 @@
as are dark themes and presets that already pass. Zero counts stay faint
on purpose, so they are excluded from the darker rule. Route-target
finders keep their --security-stroke status color, so they are excluded
- too. */
+ too. Task checklist done marks are the same kind of text. */
html[data-theme="light"] .semantic-passport-meta [data-passport="kind"],
+ html[data-theme="light"] .semantic-passport-task-checklist-task[data-status="implemented"] .semantic-passport-task-checklist-mark,
html[data-theme="light"] .node-finder:not([data-context="route-target"]) .node-finder-result em:not([data-zero]) {
color: var(--reader-accent, var(--frontend-stroke));
}
@@ -5406,7 +5459,8 @@
Agent Tool Call Workflow
-
+
+
@@ -5799,6 +5853,10 @@
Agent Tool Call Workflow
Ctrl+click or double-click opens the first entry
+
+ Task checklist
+
+
Authored reach
@@ -8598,6 +8656,24 @@
Runtime Semantics
count: function () { return payload ? Object.keys(payload).length : 0; }
};
})();
+ /* Authored node task checklists: passport-only, like explanations. Their task
+ sources are verified and read through Archify.sourceEvidence.task(). */
+ Archify.taskChecklists = (function () {
+ var element = document.getElementById('archify-task-checklists-data');
+ var payload = null;
+ if (element) {
+ try {
+ var parsed = JSON.parse(element.textContent || 'null');
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) payload = parsed;
+ } catch (_) {}
+ }
+ return {
+ node: function (id) {
+ var tasks = payload && Object.prototype.hasOwnProperty.call(payload, id) ? payload[id] : null;
+ return Array.isArray(tasks) ? tasks.slice() : [];
+ }
+ };
+ })();
/* Verified repository evidence is emitted only after the renderer checks
the authored GitHub revision, source blobs, and optional line ranges
against the explicit --repo-root. It remains outside the SVG so every
@@ -8637,6 +8713,11 @@ Runtime Semantics
node: function (id) {
var sources = payload && payload.nodes ? payload.nodes[id] : null;
return Array.isArray(sources) ? sources.slice() : [];
+ },
+ task: function (id, index) {
+ var tasks = payload && payload.taskChecklists ? payload.taskChecklists[id] : null;
+ var sources = Array.isArray(tasks) ? tasks[index] : null;
+ return Array.isArray(sources) ? sources.slice() : [];
}
};
})();
@@ -8812,6 +8893,9 @@ Runtime Semantics
var parentBackBtn = document.getElementById('btn-parent-back');
var drilldownsSection = document.getElementById('focus-drilldowns');
var drilldownsList = document.getElementById('focus-drilldowns-list');
+ var taskChecklistSection = document.getElementById('focus-task-checklist');
+ var taskChecklistCount = document.getElementById('focus-task-checklist-count');
+ var taskChecklistTasks = document.getElementById('focus-task-checklist-tasks');
var moveBtn = document.getElementById('btn-focus-move');
var activeIds = [];
var hoveredRelationship = null;
@@ -9133,30 +9217,71 @@ Runtime Semantics
repositoryLink.setAttribute('aria-label', viewerText('viewer.passport.repository.open', { revision: repository.revision }));
}
evidence.title = viewerText('viewer.passport.verificationScope');
- sources.forEach(function (source) {
- var link = document.createElement(source.href ? 'a' : 'div');
- link.className = 'semantic-passport-source';
- if (source.href) {
- link.href = source.href;
- link.target = '_blank';
- link.rel = 'noopener noreferrer';
- link.referrerPolicy = 'no-referrer';
- link.setAttribute('aria-label', viewerText('viewer.passport.source.open', { path: source.path, revision: repository.shortRevision }));
+ sources.forEach(function (source) { evidenceLinks.appendChild(sourceLink(source, repository)); });
+ evidence.hidden = false;
+ }
+ function sourceLink(source, repository) {
+ var link = document.createElement(source.href ? 'a' : 'div');
+ link.className = 'semantic-passport-source';
+ if (source.href) {
+ link.href = source.href;
+ link.target = '_blank';
+ link.rel = 'noopener noreferrer';
+ link.referrerPolicy = 'no-referrer';
+ link.setAttribute('aria-label', viewerText('viewer.passport.source.open', { path: source.path, revision: repository.shortRevision }));
+ }
+ var name = document.createElement('strong');
+ name.textContent = source.label || source.path.split('/').pop() || source.path;
+ var location = document.createElement('code');
+ location.textContent = source.line
+ ? 'L' + source.line + (source.endLine && source.endLine !== source.line ? '–' + source.endLine : '') + (source.href ? ' ↗' : '')
+ : source.href ? viewerText('viewer.passport.source.openLink') : '';
+ var sourcePath = document.createElement('small');
+ sourcePath.textContent = source.path;
+ link.appendChild(name);
+ link.appendChild(location);
+ link.appendChild(sourcePath);
+ return link;
+ }
+ var TASK_CHECKLIST_STATUS = {
+ implemented: ['✓', 'viewer.passport.taskChecklist.status.implemented'],
+ 'needs-review': ['?', 'viewer.passport.taskChecklist.status.needsReview'],
+ pending: ['○', 'viewer.passport.taskChecklist.status.pending']
+ };
+ // Read-only: authored statuses and verified task sources, never edited here.
+ function renderTaskChecklist(id) {
+ if (!taskChecklistSection) return;
+ taskChecklistSection.open = false;
+ taskChecklistTasks.textContent = '';
+ var tasks = Archify.taskChecklists ? Archify.taskChecklists.node(id) : [];
+ var repository = Archify.sourceEvidence.repository();
+ taskChecklistSection.hidden = !tasks.length;
+ tasks.forEach(function (task, index) {
+ var item = document.createElement('li');
+ item.className = 'semantic-passport-task-checklist-task';
+ item.setAttribute('data-status', task.status);
+ var status = TASK_CHECKLIST_STATUS[task.status] || TASK_CHECKLIST_STATUS.pending;
+ var mark = document.createElement('span');
+ mark.className = 'semantic-passport-task-checklist-mark';
+ mark.setAttribute('role', 'img');
+ mark.setAttribute('aria-label', viewerText(status[1]));
+ mark.textContent = status[0];
+ var title = document.createElement('strong');
+ title.textContent = task.title;
+ item.appendChild(mark);
+ item.appendChild(title);
+ if (task.criteria) {
+ var criteria = document.createElement('p');
+ criteria.textContent = task.criteria;
+ item.appendChild(criteria);
}
- var name = document.createElement('strong');
- name.textContent = source.label || source.path.split('/').pop() || source.path;
- var location = document.createElement('code');
- location.textContent = source.line
- ? 'L' + source.line + (source.endLine && source.endLine !== source.line ? '–' + source.endLine : '') + (source.href ? ' ↗' : '')
- : source.href ? viewerText('viewer.passport.source.openLink') : '';
- var sourcePath = document.createElement('small');
- sourcePath.textContent = source.path;
- link.appendChild(name);
- link.appendChild(location);
- link.appendChild(sourcePath);
- evidenceLinks.appendChild(link);
+ Archify.sourceEvidence.task(id, index).forEach(function (source) { item.appendChild(sourceLink(source, repository)); });
+ taskChecklistTasks.appendChild(item);
+ });
+ taskChecklistCount.textContent = viewerText('viewer.passport.taskChecklist.count', {
+ implemented: tasks.filter(function (task) { return task.status === 'implemented'; }).length,
+ total: tasks.length
});
- evidence.hidden = false;
}
function explanationFor(id) {
return Archify.explanations && typeof Archify.explanations.node === 'function' ? Archify.explanations.node(id) : null;
@@ -9239,6 +9364,7 @@ Runtime Semantics
semanticId.hidden = false;
renderDrilldowns(node);
renderSourceEvidence(id);
+ renderTaskChecklist(id);
}
/**
* 仅允许同目录文件名.html(可选 ?query / #fragment)。
@@ -10297,6 +10423,10 @@ Runtime Semantics
evidenceLinks.textContent = '';
repositoryLink.removeAttribute('href');
repositoryLink.textContent = '';
+ if (taskChecklistSection) {
+ taskChecklistSection.hidden = true;
+ taskChecklistTasks.textContent = '';
+ }
summary.textContent = '';
reachSection.hidden = true;
upstreamCount.textContent = '0';
@@ -10479,6 +10609,7 @@ Runtime Semantics
clearBtn.addEventListener('click', function () { clear({ restoreFocus: true }); });
copyBtn.addEventListener('click', copyFocusLink);
if (explainBtn) explainBtn.addEventListener('click', function (event) { event.stopPropagation(); toggleExplanation(); });
+ if (taskChecklistSection) taskChecklistSection.addEventListener('toggle', function () { if (!chip.hidden) requestLensPlacement(); });
if (explanationBack) {
explanationBack.addEventListener('click', function (event) { event.stopPropagation(); closeExplanation(); });
}
diff --git a/experiments/mco-showcase/mco-runtime.html b/experiments/mco-showcase/mco-runtime.html
index edd742ef1..7cc4995a2 100644
--- a/experiments/mco-showcase/mco-runtime.html
+++ b/experiments/mco-showcase/mco-runtime.html
@@ -2078,6 +2078,55 @@
color: var(--text-faint);
font-size: 0.5625rem;
}
+ /* Authored task checklist: read-only, collapsed to one counted line. */
+ .semantic-passport-task-checklist {
+ margin-top: 0.52rem;
+ padding-top: 0.48rem;
+ border-top: 1px solid color-mix(in srgb, var(--toolbar-border) 76%, transparent);
+ }
+ .semantic-passport-task-checklist > summary { cursor: pointer; }
+ .semantic-passport-task-checklist-count {
+ color: var(--text-muted);
+ font-size: 0.5625rem;
+ }
+ .semantic-passport-task-checklist-tasks {
+ display: grid;
+ gap: 0.32rem;
+ max-height: min(16rem, 40vh);
+ margin: 0.45rem 0 0;
+ padding: 0.15rem;
+ overflow: auto;
+ overscroll-behavior: contain;
+ scrollbar-width: thin;
+ scrollbar-color: var(--frontend-stroke) transparent;
+ list-style: none;
+ }
+ .semantic-passport-task-checklist-task {
+ display: grid;
+ grid-template-columns: 0.9rem minmax(0, 1fr);
+ gap: 0.12rem 0.3rem;
+ }
+ .semantic-passport-task-checklist-mark {
+ color: var(--text-faint);
+ font-size: 0.625rem;
+ font-weight: 800;
+ text-align: center;
+ }
+ .semantic-passport-task-checklist-task[data-status="implemented"] .semantic-passport-task-checklist-mark { color: var(--frontend-stroke); }
+ .semantic-passport-task-checklist-task[data-status="needs-review"] .semantic-passport-task-checklist-mark { color: var(--text-muted); }
+ .semantic-passport-task-checklist-task > strong {
+ color: var(--text-muted);
+ font-size: 0.6875rem;
+ overflow-wrap: anywhere;
+ }
+ .semantic-passport-task-checklist-task p,
+ .semantic-passport-task-checklist-task .semantic-passport-source { grid-column: 2; }
+ .semantic-passport-task-checklist-task p {
+ margin: 0;
+ color: var(--text-faint);
+ font-size: 0.5625rem;
+ overflow-wrap: anywhere;
+ }
.semantic-passport-reach {
display: grid;
gap: 0.28rem;
@@ -2086,7 +2135,8 @@
border-top: 1px solid color-mix(in srgb, var(--toolbar-border) 76%, transparent);
}
.semantic-passport-reach[hidden] { display: none; }
- .semantic-passport-reach-label {
+ .semantic-passport-reach-label,
+ .semantic-passport-task-checklist-label {
color: var(--text-faint);
font-size: 0.5rem;
font-weight: 800;
@@ -2154,8 +2204,10 @@
scrollbar-width: thin;
scrollbar-color: var(--frontend-stroke) transparent;
}
- .relationship-lens-list::-webkit-scrollbar { width: 4px; }
- .relationship-lens-list::-webkit-scrollbar-thumb {
+ .relationship-lens-list::-webkit-scrollbar,
+ .semantic-passport-task-checklist-tasks::-webkit-scrollbar { width: 4px; }
+ .relationship-lens-list::-webkit-scrollbar-thumb,
+ .semantic-passport-task-checklist-tasks::-webkit-scrollbar-thumb {
border-radius: 999px;
background: color-mix(in srgb, var(--frontend-stroke) 68%, transparent);
}
@@ -2657,8 +2709,9 @@
as are dark themes and presets that already pass. Zero counts stay faint
on purpose, so they are excluded from the darker rule. Route-target
finders keep their --security-stroke status color, so they are excluded
- too. */
+ too. Task checklist done marks are the same kind of text. */
html[data-theme="light"] .semantic-passport-meta [data-passport="kind"],
+ html[data-theme="light"] .semantic-passport-task-checklist-task[data-status="implemented"] .semantic-passport-task-checklist-mark,
html[data-theme="light"] .node-finder:not([data-context="route-target"]) .node-finder-result em:not([data-zero]) {
color: var(--reader-accent, var(--frontend-stroke));
}
@@ -5406,8 +5459,9 @@ MCO Runtime Architecture
+
-
+
@@ -5792,6 +5846,10 @@
MCO Runtime Architecture
Ctrl+click or double-click opens the first entry
+
+ Task checklist
+
+
Authored reach
@@ -8605,6 +8663,24 @@
Opaque Answers, Durable Evidence
count: function () { return payload ? Object.keys(payload).length : 0; }
};
})();
+ /* Authored node task checklists: passport-only, like explanations. Their task
+ sources are verified and read through Archify.sourceEvidence.task(). */
+ Archify.taskChecklists = (function () {
+ var element = document.getElementById('archify-task-checklists-data');
+ var payload = null;
+ if (element) {
+ try {
+ var parsed = JSON.parse(element.textContent || 'null');
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) payload = parsed;
+ } catch (_) {}
+ }
+ return {
+ node: function (id) {
+ var tasks = payload && Object.prototype.hasOwnProperty.call(payload, id) ? payload[id] : null;
+ return Array.isArray(tasks) ? tasks.slice() : [];
+ }
+ };
+ })();
/* Verified repository evidence is emitted only after the renderer checks
the authored GitHub revision, source blobs, and optional line ranges
against the explicit --repo-root. It remains outside the SVG so every
@@ -8644,6 +8720,11 @@ Opaque Answers, Durable Evidence
node: function (id) {
var sources = payload && payload.nodes ? payload.nodes[id] : null;
return Array.isArray(sources) ? sources.slice() : [];
+ },
+ task: function (id, index) {
+ var tasks = payload && payload.taskChecklists ? payload.taskChecklists[id] : null;
+ var sources = Array.isArray(tasks) ? tasks[index] : null;
+ return Array.isArray(sources) ? sources.slice() : [];
}
};
})();
@@ -8819,6 +8900,9 @@ Opaque Answers, Durable Evidence
var parentBackBtn = document.getElementById('btn-parent-back');
var drilldownsSection = document.getElementById('focus-drilldowns');
var drilldownsList = document.getElementById('focus-drilldowns-list');
+ var taskChecklistSection = document.getElementById('focus-task-checklist');
+ var taskChecklistCount = document.getElementById('focus-task-checklist-count');
+ var taskChecklistTasks = document.getElementById('focus-task-checklist-tasks');
var moveBtn = document.getElementById('btn-focus-move');
var activeIds = [];
var hoveredRelationship = null;
@@ -9140,30 +9224,71 @@ Opaque Answers, Durable Evidence
repositoryLink.setAttribute('aria-label', viewerText('viewer.passport.repository.open', { revision: repository.revision }));
}
evidence.title = viewerText('viewer.passport.verificationScope');
- sources.forEach(function (source) {
- var link = document.createElement(source.href ? 'a' : 'div');
- link.className = 'semantic-passport-source';
- if (source.href) {
- link.href = source.href;
- link.target = '_blank';
- link.rel = 'noopener noreferrer';
- link.referrerPolicy = 'no-referrer';
- link.setAttribute('aria-label', viewerText('viewer.passport.source.open', { path: source.path, revision: repository.shortRevision }));
+ sources.forEach(function (source) { evidenceLinks.appendChild(sourceLink(source, repository)); });
+ evidence.hidden = false;
+ }
+ function sourceLink(source, repository) {
+ var link = document.createElement(source.href ? 'a' : 'div');
+ link.className = 'semantic-passport-source';
+ if (source.href) {
+ link.href = source.href;
+ link.target = '_blank';
+ link.rel = 'noopener noreferrer';
+ link.referrerPolicy = 'no-referrer';
+ link.setAttribute('aria-label', viewerText('viewer.passport.source.open', { path: source.path, revision: repository.shortRevision }));
+ }
+ var name = document.createElement('strong');
+ name.textContent = source.label || source.path.split('/').pop() || source.path;
+ var location = document.createElement('code');
+ location.textContent = source.line
+ ? 'L' + source.line + (source.endLine && source.endLine !== source.line ? '–' + source.endLine : '') + (source.href ? ' ↗' : '')
+ : source.href ? viewerText('viewer.passport.source.openLink') : '';
+ var sourcePath = document.createElement('small');
+ sourcePath.textContent = source.path;
+ link.appendChild(name);
+ link.appendChild(location);
+ link.appendChild(sourcePath);
+ return link;
+ }
+ var TASK_CHECKLIST_STATUS = {
+ implemented: ['✓', 'viewer.passport.taskChecklist.status.implemented'],
+ 'needs-review': ['?', 'viewer.passport.taskChecklist.status.needsReview'],
+ pending: ['○', 'viewer.passport.taskChecklist.status.pending']
+ };
+ // Read-only: authored statuses and verified task sources, never edited here.
+ function renderTaskChecklist(id) {
+ if (!taskChecklistSection) return;
+ taskChecklistSection.open = false;
+ taskChecklistTasks.textContent = '';
+ var tasks = Archify.taskChecklists ? Archify.taskChecklists.node(id) : [];
+ var repository = Archify.sourceEvidence.repository();
+ taskChecklistSection.hidden = !tasks.length;
+ tasks.forEach(function (task, index) {
+ var item = document.createElement('li');
+ item.className = 'semantic-passport-task-checklist-task';
+ item.setAttribute('data-status', task.status);
+ var status = TASK_CHECKLIST_STATUS[task.status] || TASK_CHECKLIST_STATUS.pending;
+ var mark = document.createElement('span');
+ mark.className = 'semantic-passport-task-checklist-mark';
+ mark.setAttribute('role', 'img');
+ mark.setAttribute('aria-label', viewerText(status[1]));
+ mark.textContent = status[0];
+ var title = document.createElement('strong');
+ title.textContent = task.title;
+ item.appendChild(mark);
+ item.appendChild(title);
+ if (task.criteria) {
+ var criteria = document.createElement('p');
+ criteria.textContent = task.criteria;
+ item.appendChild(criteria);
}
- var name = document.createElement('strong');
- name.textContent = source.label || source.path.split('/').pop() || source.path;
- var location = document.createElement('code');
- location.textContent = source.line
- ? 'L' + source.line + (source.endLine && source.endLine !== source.line ? '–' + source.endLine : '') + (source.href ? ' ↗' : '')
- : source.href ? viewerText('viewer.passport.source.openLink') : '';
- var sourcePath = document.createElement('small');
- sourcePath.textContent = source.path;
- link.appendChild(name);
- link.appendChild(location);
- link.appendChild(sourcePath);
- evidenceLinks.appendChild(link);
+ Archify.sourceEvidence.task(id, index).forEach(function (source) { item.appendChild(sourceLink(source, repository)); });
+ taskChecklistTasks.appendChild(item);
+ });
+ taskChecklistCount.textContent = viewerText('viewer.passport.taskChecklist.count', {
+ implemented: tasks.filter(function (task) { return task.status === 'implemented'; }).length,
+ total: tasks.length
});
- evidence.hidden = false;
}
function explanationFor(id) {
return Archify.explanations && typeof Archify.explanations.node === 'function' ? Archify.explanations.node(id) : null;
@@ -9246,6 +9371,7 @@ Opaque Answers, Durable Evidence
semanticId.hidden = false;
renderDrilldowns(node);
renderSourceEvidence(id);
+ renderTaskChecklist(id);
}
/**
* 仅允许同目录文件名.html(可选 ?query / #fragment)。
@@ -10304,6 +10430,10 @@ Opaque Answers, Durable Evidence
evidenceLinks.textContent = '';
repositoryLink.removeAttribute('href');
repositoryLink.textContent = '';
+ if (taskChecklistSection) {
+ taskChecklistSection.hidden = true;
+ taskChecklistTasks.textContent = '';
+ }
summary.textContent = '';
reachSection.hidden = true;
upstreamCount.textContent = '0';
@@ -10486,6 +10616,7 @@ Opaque Answers, Durable Evidence
clearBtn.addEventListener('click', function () { clear({ restoreFocus: true }); });
copyBtn.addEventListener('click', copyFocusLink);
if (explainBtn) explainBtn.addEventListener('click', function (event) { event.stopPropagation(); toggleExplanation(); });
+ if (taskChecklistSection) taskChecklistSection.addEventListener('toggle', function () { if (!chip.hidden) requestLensPlacement(); });
if (explanationBack) {
explanationBack.addEventListener('click', function (event) { event.stopPropagation(); closeExplanation(); });
}
diff --git a/scripts/browser-test-inventory.mjs b/scripts/browser-test-inventory.mjs
index 2b8cf97bc..66e20410b 100644
--- a/scripts/browser-test-inventory.mjs
+++ b/scripts/browser-test-inventory.mjs
@@ -27,6 +27,7 @@ export const browserTestFiles = [
'route-probe-browser.test.mjs',
'focus-browser.test.mjs',
'passport-explanation-browser.test.mjs',
+ 'passport-task-checklist-browser.test.mjs',
'drilldown-context-browser.test.mjs',
'crossover-state-browser.test.mjs',
'semantic-passport-move-browser.test.mjs',
diff --git a/test/passport-task-checklist-browser.test.mjs b/test/passport-task-checklist-browser.test.mjs
new file mode 100644
index 000000000..be7aa6b93
--- /dev/null
+++ b/test/passport-task-checklist-browser.test.mjs
@@ -0,0 +1,251 @@
+import { test } from 'node:test';
+import assert from 'node:assert/strict';
+import fs from 'node:fs';
+import path from 'node:path';
+import { pathToFileURL } from 'node:url';
+import { findChrome } from '../archify/bin/visual-check.mjs';
+import { desktopBrowser, desktopPointerCheck } from './helpers/desktop-browser.mjs';
+import { fixture, run as cli } from './helpers/repository-evidence-fixture.mjs';
+import { createViewerClick } from './helpers/viewer-click.mjs';
+
+const chrome = process.env.ARCHIFY_CHROME ? findChrome() : null;
+
+test('Semantic Passport lists an authored task checklist read-only with verified task sources', {
+ skip: chrome ? false : 'Set ARCHIFY_CHROME to run real-browser passport task checklist checks.',
+}, async (t) => {
+ const data = fixture();
+ data.diagram.components[0].task_checklist = [
+ { title: 'Route requests by kind', status: 'implemented', criteria: 'Every request reaches exactly one handler.', sources: [{ path: 'src/router.js', line: 1, end_line: 3, label: 'Request router' }] },
+ { title: 'Keep sessions in a store', status: 'needs-review', sources: [{ path: 'src/store.js', line: 1 }] },
+ { title: 'Expire idle sessions', status: 'pending' },
+ ];
+ // A long list on a bottom-row node checks placement and scrolling on the same page.
+ data.diagram.components.find((component) => component.id === 'worker').task_checklist = Array.from({ length: 12 }, (_, index) => ({
+ title: `Task ${index + 1} with a title long enough to wrap in the passport`,
+ status: index % 2 ? 'pending' : 'needs-review',
+ criteria: 'A full-sentence acceptance criterion that wraps onto a second line.',
+ }));
+ fs.writeFileSync(data.input, JSON.stringify(data.diagram));
+ const output = path.join(data.root, 'task-checklist.html');
+ const rendered = cli(['render', 'architecture', data.input, output, '--repo-root', data.root]);
+ assert.equal(rendered.status, 0, rendered.stderr || rendered.stdout);
+
+ const browser = desktopBrowser(chrome);
+ t.after(() => browser.close());
+ const session = await browser.sessionPromise;
+ const checkPointer = await desktopPointerCheck(browser, session);
+ const send = (method, params = {}) => browser.cdp.send(method, params, session);
+ await browser.cdp.send('Browser.setDownloadBehavior', { behavior: 'deny' });
+ await send('Emulation.setFocusEmulationEnabled', { enabled: true });
+ async function run(expression) {
+ const r = await send('Runtime.evaluate', { expression, awaitPromise: true, returnByValue: true });
+ assert.equal(r.exceptionDetails, undefined, r.exceptionDetails?.exception?.description);
+ return r.result?.value;
+ }
+ const click = await createViewerClick({ send, run, timeout: 10000 });
+ await send('Page.addScriptToEvaluateOnNewDocument', { source: `window.pageErrors=[];addEventListener('error',e=>pageErrors.push(e.message));` });
+ await send('Emulation.setDeviceMetricsOverride', { width: 1440, height: 900, deviceScaleFactor: 1, mobile: false });
+ await send('Emulation.setEmulatedMedia', { features: [{ name: 'prefers-reduced-motion', value: 'reduce' }] });
+ const loaded = browser.cdp.waitFor('Page.loadEventFired', session);
+ await send('Page.navigate', { url: pathToFileURL(output).href + '?theme=dark' });
+ await loaded;
+ await checkPointer();
+ await run('document.fonts.ready');
+ await run('Archify.viewerChromeLayout.whenStable()');
+
+ const settle = () => run('new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve)))');
+ const state = () => run(`(() => { const section = document.getElementById('focus-task-checklist');
+ return { hidden: section.hidden, open: section.open, count: document.getElementById('focus-task-checklist-count').textContent,
+ tasks: Array.from(section.querySelectorAll('.semantic-passport-task-checklist-task')).map(task => ({
+ status: task.getAttribute('data-status'), mark: task.querySelector('[role="img"]').getAttribute('aria-label'),
+ title: task.querySelector('strong').textContent, criteria: task.querySelector('p') && task.querySelector('p').textContent,
+ links: Array.from(task.querySelectorAll('a.semantic-passport-source')).map(link => link.getAttribute('href')) })),
+ focused: document.activeElement && document.activeElement.tagName, errors: pageErrors }; })()`);
+
+ assert.equal(await run(`Archify.focus.set('users')`), true);
+ await settle();
+ const closed = await state();
+ assert.equal(closed.hidden, false, 'a node with a task checklist shows the section');
+ assert.equal(closed.open, false, 'the task checklist starts as one counted line');
+ assert.equal(closed.count, '1/3 implemented');
+
+ await click('#focus-task-checklist > summary');
+ await settle();
+ const opened = await state();
+ assert.equal(opened.open, true, 'clicking the counted line expands the tasks');
+ assert.deepEqual(opened.tasks.map(({ status, mark }) => [status, mark]), [
+ ['implemented', 'Implemented'], ['needs-review', 'Needs review'], ['pending', 'Pending'],
+ ]);
+ assert.equal(opened.tasks[0].criteria, 'Every request reaches exactly one handler.');
+ assert.equal(opened.tasks[0].links.length, 1);
+ assert.match(opened.tasks[0].links[0], new RegExp(`/blob/${data.revision}/src/router\\.js#L1-L3$`));
+ assert.equal(opened.tasks[1].links.length, 1);
+ assert.deepEqual(opened.tasks[2].links, [], 'a pending task shows no evidence');
+ if (process.env.ARCHIFY_TEST_EVIDENCE_DIR) {
+ fs.mkdirSync(process.env.ARCHIFY_TEST_EVIDENCE_DIR, { recursive: true });
+ const capture = await send('Page.captureScreenshot', { format: 'png', captureBeyondViewport: false });
+ fs.writeFileSync(path.join(process.env.ARCHIFY_TEST_EVIDENCE_DIR, 'task-checklist-1440x900.png'), Buffer.from(capture.data, 'base64'));
+ }
+
+ // `text` makes Chromium emit the character event that natively toggles .
+ await send('Input.dispatchKeyEvent', { type: 'keyDown', key: 'Enter', code: 'Enter', windowsVirtualKeyCode: 13, text: '\r' });
+ await send('Input.dispatchKeyEvent', { type: 'keyUp', key: 'Enter', code: 'Enter', windowsVirtualKeyCode: 13 });
+ await settle();
+ const keyboard = await state();
+ assert.equal(keyboard.focused, 'SUMMARY', 'the counted line takes keyboard focus');
+ assert.equal(keyboard.open, false, 'Enter on the counted line collapses the tasks');
+
+ const canonical = await run(`document.querySelector('.diagram-container > svg').outerHTML`);
+ assert.doesNotMatch(canonical, /Route requests by kind|Expire idle sessions/, 'tasks stay outside the canonical SVG');
+
+ assert.equal(await run(`Archify.focus.set('cdn')`), true);
+ await settle();
+ assert.equal((await state()).hidden, true, 'nodes without a task checklist show no section');
+ await run(`Archify.focus.clear()`);
+ await settle();
+ const cleared = await state();
+ assert.equal(cleared.hidden, true);
+ assert.deepEqual(cleared.tasks, []);
+ assert.deepEqual(cleared.errors, []);
+
+ // A long task checklist expanded near the bottom of the stage must stay inside
+ // it and scroll rather than clip, and the next node starts collapsed.
+ assert.equal(await run(`Archify.focus.set('worker')`), true);
+ await settle();
+ await click('#focus-task-checklist > summary');
+ await run('new Promise(resolve => setTimeout(resolve, 100))');
+ await settle();
+ const expanded = await run(`(() => { const chip = document.getElementById('focus-chip').getBoundingClientRect();
+ const stage = document.querySelector('.diagram-container').getBoundingClientRect();
+ const list = document.getElementById('focus-task-checklist-tasks');
+ list.scrollTop = list.scrollHeight;
+ const last = list.lastElementChild.getBoundingClientRect();
+ return { open: document.getElementById('focus-task-checklist').open, chipBottom: chip.bottom, stageBottom: Math.min(stage.bottom, innerHeight),
+ scrolls: list.scrollHeight > list.clientHeight, lastVisible: last.bottom <= list.getBoundingClientRect().bottom + 1 }; })()`);
+ assert.equal(expanded.open, true);
+ assert.ok(expanded.chipBottom <= expanded.stageBottom + 1, `expanded passport ends at ${expanded.chipBottom}, stage at ${expanded.stageBottom}`);
+ assert.equal(expanded.scrolls, true, 'a long task checklist scrolls inside the passport');
+ assert.equal(expanded.lastVisible, true, 'the last task can be scrolled into view');
+ assert.equal(await run(`Archify.focus.set('users')`), true);
+ await settle();
+ assert.equal(await run(`document.getElementById('focus-task-checklist').open`), false, 'the next node starts collapsed');
+ assert.deepEqual(await run('pageErrors'), []);
+});
+
+test('a twelve-task checklist stays reachable beside a parent link and long explanation at 320×300', {
+ skip: chrome ? false : 'Set ARCHIFY_CHROME to run real-browser passport task checklist checks.',
+}, async (t) => {
+ const data = fixture();
+ data.diagram.meta.parentHref = 'grandparent.html';
+ const users = data.diagram.components[0];
+ users.explanation = ('A reader needs the complete explanation, with every paragraph available.\n\n').repeat(20).slice(0, 1190);
+ users.task_checklist = Array.from({ length: 12 }, (_, index) => ({
+ title: `Task ${index + 1} with a title long enough to wrap in a narrow passport`,
+ status: index % 2 ? 'needs-review' : 'implemented',
+ criteria: 'A full-sentence acceptance criterion that wraps onto a second line.',
+ sources: [{ path: index % 2 ? 'src/store.js' : 'src/router.js', line: 1 }],
+ }));
+ data.diagram.components.find((component) => component.id === 'api').task_checklist = [{ title: 'Serve requests', status: 'pending' }];
+ fs.writeFileSync(data.input, JSON.stringify(data.diagram));
+ const output = path.join(data.root, 'short-viewport.html');
+ const rendered = cli(['render', 'architecture', data.input, output, '--repo-root', data.root]);
+ assert.equal(rendered.status, 0, rendered.stderr || rendered.stdout);
+
+ const browser = desktopBrowser(chrome);
+ t.after(() => browser.close());
+ const session = await browser.sessionPromise;
+ const send = (method, params = {}) => browser.cdp.send(method, params, session);
+ async function run(expression) {
+ const r = await send('Runtime.evaluate', { expression, awaitPromise: true, returnByValue: true });
+ assert.equal(r.exceptionDetails, undefined, r.exceptionDetails?.exception?.description);
+ return r.result?.value;
+ }
+ const settle = () => run('new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve)))');
+ const key = async (name, code, windowsVirtualKeyCode, modifiers = 0) => {
+ await send('Input.dispatchKeyEvent', { type: 'keyDown', key: name, code, windowsVirtualKeyCode, modifiers, ...(name === 'Enter' ? { text: '\r' } : {}) });
+ await send('Input.dispatchKeyEvent', { type: 'keyUp', key: name, code, windowsVirtualKeyCode, modifiers });
+ await settle();
+ };
+ const click = await createViewerClick({ send, run, timeout: 10000 });
+ await send('Page.addScriptToEvaluateOnNewDocument', { source: `window.pageErrors=[];addEventListener('error',e=>pageErrors.push(e.message));` });
+ await send('Emulation.setFocusEmulationEnabled', { enabled: true });
+ await send('Emulation.setDeviceMetricsOverride', { width: 320, height: 300, deviceScaleFactor: 1, mobile: false });
+ await send('Emulation.setEmulatedMedia', { features: [{ name: 'prefers-reduced-motion', value: 'reduce' }] });
+ const loaded = browser.cdp.waitFor('Page.loadEventFired', session);
+ await send('Page.navigate', { url: pathToFileURL(output).href + '?theme=light' });
+ await loaded;
+ await run('document.fonts.ready');
+ await run('Archify.readerLayout.whenStable()');
+
+ // "Visible" means inside the viewport and inside every scrolling ancestor.
+ const state = () => run(`(() => { const chip = document.getElementById('focus-chip'), section = document.getElementById('focus-task-checklist'), active = document.activeElement;
+ const visible = (el) => { const r = el.getBoundingClientRect(); if (r.top < 0 || r.bottom > innerHeight + 1) return false;
+ for (let p = el.parentElement; p && p !== document.body; p = p.parentElement) {
+ if (p.scrollHeight > p.clientHeight && /auto|scroll/.test(getComputedStyle(p).overflowY)) { const b = p.getBoundingClientRect(); if (r.top < b.top - 1 || r.bottom > b.bottom + 1) return false; }
+ }
+ return true; };
+ const back = document.getElementById('btn-parent-back').getBoundingClientRect(), hit = document.elementFromPoint(back.left + back.width / 2, back.top + back.height / 2);
+ return { chipShown: !chip.hidden, chipFits: !chip.hidden && chip.getBoundingClientRect().top >= 0 && chip.getBoundingClientRect().bottom <= innerHeight + 1,
+ hidden: section.hidden, open: section.open, explaining: Archify.focus.explaining(), focused: active.id || active.tagName, focusedVisible: visible(active),
+ links: section.querySelectorAll('a.semantic-passport-source').length, linkIndex: [...section.querySelectorAll('a.semantic-passport-source')].indexOf(active),
+ parentBackShown: !document.querySelector('.parent-back-bar').hidden, parentBackClickable: !!hit && document.getElementById('btn-parent-back').contains(hit), errors: pageErrors }; })()`);
+
+ assert.equal(await run(`Archify.focus.set('users')`), true);
+ await settle();
+ let s = await state();
+ assert.equal(s.parentBackShown, true, 'the page keeps its parent return link');
+ assert.equal(s.chipFits, true, 'the passport fits the short viewport');
+ assert.equal(s.open, false, 'the task checklist starts collapsed');
+
+ await click('#focus-task-checklist > summary');
+ s = await state();
+ assert.equal(s.open, true, 'clicking the counted line expands the tasks');
+ assert.equal(s.links, 12);
+ for (let index = 0; index < s.links; index += 1) {
+ await key('Tab', 'Tab', 9);
+ const step = await state();
+ assert.equal(step.linkIndex, index, `Tab reaches task link ${index + 1}`);
+ assert.equal(step.focusedVisible, true, `task link ${index + 1} is scrolled into view`);
+ }
+ if (process.env.ARCHIFY_TEST_EVIDENCE_DIR) {
+ fs.mkdirSync(process.env.ARCHIFY_TEST_EVIDENCE_DIR, { recursive: true });
+ const capture = await send('Page.captureScreenshot', { format: 'png', captureBeyondViewport: false });
+ fs.writeFileSync(path.join(process.env.ARCHIFY_TEST_EVIDENCE_DIR, 'task-checklist-320x300.png'), Buffer.from(capture.data, 'base64'));
+ }
+ for (let index = 0; index < s.links; index += 1) await key('Tab', 'Tab', 9, 8);
+ s = await state();
+ assert.equal(s.focused, 'SUMMARY', 'Shift+Tab walks back to the counted line');
+ assert.equal(s.focusedVisible, true);
+ await key('Enter', 'Enter', 13);
+ assert.equal((await state()).open, false, 'Enter collapses the tasks');
+ await key('Enter', 'Enter', 13);
+ assert.equal((await state()).open, true, 'Enter expands them again');
+
+ await click('#btn-focus-explain');
+ s = await state();
+ assert.equal(s.explaining, true);
+ assert.equal(s.focused, 'focus-explanation-back');
+ assert.equal(s.focusedVisible, true, 'Back to passport is visible');
+ await key('Escape', 'Escape', 27);
+ s = await state();
+ assert.equal(s.explaining, false);
+ assert.equal(s.focused, 'btn-focus-explain');
+ assert.equal(s.focusedVisible, true);
+ assert.equal(s.open, true, 'returning from the explanation keeps the expanded tasks');
+
+ await click('#btn-focus-clear');
+ s = await state();
+ assert.equal(s.chipShown, false, 'the close control hides the passport');
+ assert.equal(s.parentBackClickable, true, 'the parent return link is clickable once the passport closes');
+
+ assert.equal(await run(`Archify.focus.set('users')`), true);
+ await settle();
+ assert.equal((await state()).open, false, 'refocusing the node starts collapsed');
+ await click('#focus-task-checklist > summary');
+ assert.equal(await run(`Archify.focus.set('api')`), true);
+ await settle();
+ s = await state();
+ assert.equal(s.hidden, false);
+ assert.equal(s.open, false, 'switching nodes starts collapsed');
+ assert.deepEqual(s.errors, []);
+});
diff --git a/test/passport-task-checklist.test.mjs b/test/passport-task-checklist.test.mjs
new file mode 100644
index 000000000..0f8b49840
--- /dev/null
+++ b/test/passport-task-checklist.test.mjs
@@ -0,0 +1,139 @@
+import { test } from 'node:test';
+import assert from 'node:assert/strict';
+import { execFileSync } from 'node:child_process';
+import fs from 'node:fs';
+import os from 'node:os';
+import path from 'node:path';
+import { fileURLToPath } from 'node:url';
+import { validateSchema } from '../archify/renderers/shared/validator.mjs';
+import { verifyRepositoryEvidence } from '../archify/renderers/shared/repository-evidence.mjs';
+import { fixture, run, evidencePayload } from './helpers/repository-evidence-fixture.mjs';
+
+const __dirname = path.dirname(fileURLToPath(import.meta.url));
+const skillRoot = path.resolve(__dirname, '..', 'archify');
+const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-passport-task-checklist-'));
+
+const CASES = {
+ architecture: ['web-app.architecture.json', 'components'],
+ workflow: ['agent-tool-call.workflow.json', 'nodes'],
+ sequence: ['cache-miss-request.sequence.json', 'participants'],
+ dataflow: ['product-analytics.dataflow.json', 'nodes'],
+ lifecycle: ['agent-run.lifecycle.json', 'states'],
+};
+
+const TASKS = [
+ { title: 'Zebra-quartz review task', status: 'needs-review', criteria: 'Sentinel criteria that must stay out of the SVG.' },
+ { title: 'Expire idle sessions', status: 'pending' },
+];
+
+function example(mode) {
+ return JSON.parse(fs.readFileSync(path.join(skillRoot, 'examples', CASES[mode][0]), 'utf8'));
+}
+
+function withTaskChecklist(mode, tasks = TASKS) {
+ const diagram = example(mode);
+ const collection = diagram[CASES[mode][1]];
+ collection[0].task_checklist = tasks;
+ return { diagram, id: collection[0].id };
+}
+
+function write(mode, diagram, name) {
+ const input = path.join(tmp, `${mode}-${name}.json`);
+ fs.writeFileSync(input, JSON.stringify(diagram));
+ return input;
+}
+
+function render(mode, input) {
+ const output = path.join(tmp, `${path.basename(input, '.json')}.html`);
+ execFileSync(process.execPath, [path.join(skillRoot, `renderers/${mode}/render-${mode}.mjs`), input, output]);
+ return fs.readFileSync(output, 'utf8');
+}
+
+function svg(html) {
+ return html.match(//)?.[0] || '';
+}
+
+function taskChecklistPayload(html) {
+ const block = html.match(/