tink-harness 1.9.6 → 1.9.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,28 @@ All notable changes to Tink are tracked here.
|
|
|
6
6
|
|
|
7
7
|
No unreleased changes yet.
|
|
8
8
|
|
|
9
|
+
## [1.9.8] - 2026-06-11
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- Galaxy-styled harness map background: a slowly rotating 4-arm spiral of 760 particles (pink-to-cyan, screen-blended), 7 colored nebula glows, a breathing core glow, and a deeper space gradient — inspired by a Three.js galaxy reference while keeping the SVG map fully interactive.
|
|
14
|
+
- Neural signal pulses: glowing dots travel along every edge from source to target (3.2-7.4s, staggered), so connections read like firing synapses.
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
|
|
18
|
+
- Signal pulses follow the map state: they hide with core mode/filters, dim in focus mode, and stay bright only on edges related to the selected node. Clicking the galaxy background clears the selection. prefers-reduced-motion disables rotation and pulses.
|
|
19
|
+
|
|
20
|
+
## [1.9.7] - 2026-06-10
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
|
|
24
|
+
- The harness map now looks like a planetary system: usage/evidence/score satellites orbit their harness slowly (28-63s per revolution, alternating directions) along dashed orbit rings, top harnesses get a Saturn-style ring, and a starfield with twinkling stars sits behind the graph.
|
|
25
|
+
- Selecting a node now dims and blurs everything unrelated (focus mode); clicking the background clears the selection and restores the detail panel.
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
28
|
+
|
|
29
|
+
- Dragging the map no longer triggers text selection on labels and captions (user-select disabled on the map panel).
|
|
30
|
+
|
|
9
31
|
## [1.9.6] - 2026-06-10
|
|
10
32
|
|
|
11
33
|
### Added
|
package/VERSIONING.md
CHANGED
package/package.json
CHANGED
|
@@ -618,8 +618,7 @@ function buildGraphLayout(summary) {
|
|
|
618
618
|
glow: score >= 50 || radius >= 20
|
|
619
619
|
};
|
|
620
620
|
});
|
|
621
|
-
const
|
|
622
|
-
const virtualEdges = [];
|
|
621
|
+
const orbitSystems = [];
|
|
623
622
|
for (const node of positioned.filter((item) => item.type === 'harness')) {
|
|
624
623
|
const harnessId = shortLabel(node.id);
|
|
625
624
|
const harness = harnessById.get(harnessId);
|
|
@@ -627,38 +626,25 @@ function buildGraphLayout(summary) {
|
|
|
627
626
|
const uses = Number(harness.signals?.uses || 0);
|
|
628
627
|
const evidenceCount = (harness.evidence_handles || []).length;
|
|
629
628
|
const factorCount = (harness.candidate_score?.factors || []).length;
|
|
630
|
-
const
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
radius: satellite.radius,
|
|
647
|
-
color: TYPE_COLORS[satellite.kind] || TYPE_COLORS.unknown,
|
|
648
|
-
glow: satellite.kind === 'score'
|
|
649
|
-
};
|
|
650
|
-
augmented.push(child);
|
|
651
|
-
virtualEdges.push({
|
|
652
|
-
source: node.id,
|
|
653
|
-
target: child.id,
|
|
654
|
-
type: satellite.kind,
|
|
655
|
-
count: 1
|
|
656
|
-
});
|
|
657
|
-
});
|
|
629
|
+
const seed = hashString(node.id);
|
|
630
|
+
const rings = [
|
|
631
|
+
{ kind: 'signal', count: Math.min(10, Math.ceil(uses / 2)), distance: node.radius + 18, dotRadius: 2.4 },
|
|
632
|
+
{ kind: 'evidence', count: Math.min(6, evidenceCount), distance: node.radius + 29, dotRadius: 2.9 },
|
|
633
|
+
{ kind: 'score', count: Math.min(5, factorCount), distance: node.radius + 40, dotRadius: 3.2 }
|
|
634
|
+
]
|
|
635
|
+
.filter((ring) => ring.count > 0)
|
|
636
|
+
.map((ring, ringIndex) => ({
|
|
637
|
+
...ring,
|
|
638
|
+
duration: (28 + ((seed >> (ringIndex * 3)) % 36)).toFixed(0),
|
|
639
|
+
reverse: ringIndex % 2 === 1,
|
|
640
|
+
phase: ((seed >> (ringIndex * 5)) % 628) / 100
|
|
641
|
+
}));
|
|
642
|
+
if (rings.length) {
|
|
643
|
+
orbitSystems.push({ parentId: node.id, x: node.x, y: node.y, rings });
|
|
644
|
+
}
|
|
658
645
|
}
|
|
659
|
-
const byId = new Map(
|
|
660
|
-
const
|
|
661
|
-
const drawnEdges = [...filteredEdges, ...virtualEdges]
|
|
646
|
+
const byId = new Map(positioned.map((node) => [node.id, node]));
|
|
647
|
+
const drawnEdges = getRenderableEdges(edges)
|
|
662
648
|
.map((edge) => ({
|
|
663
649
|
...edge,
|
|
664
650
|
sourceNode: byId.get(edge.source),
|
|
@@ -667,15 +653,70 @@ function buildGraphLayout(summary) {
|
|
|
667
653
|
.filter((edge) => edge.sourceNode && edge.targetNode)
|
|
668
654
|
.slice(0, 240);
|
|
669
655
|
|
|
670
|
-
return { nodes:
|
|
656
|
+
return { nodes: positioned, edges: drawnEdges, orbitSystems };
|
|
671
657
|
}
|
|
672
658
|
|
|
673
659
|
function renderGraphCanvas(summary, copy) {
|
|
674
|
-
const { nodes, edges } = buildGraphLayout(summary);
|
|
660
|
+
const { nodes, edges, orbitSystems } = buildGraphLayout(summary);
|
|
675
661
|
const strongest = nodes
|
|
676
662
|
.filter((node) => node.type === 'harness')
|
|
677
663
|
.sort((a, b) => Number(b.weight || 0) - Number(a.weight || 0))
|
|
678
664
|
.slice(0, 8);
|
|
665
|
+
const stars = Array.from({ length: 110 }, (_, index) => {
|
|
666
|
+
const seed = hashString(`star:${index}`);
|
|
667
|
+
return {
|
|
668
|
+
x: seed % 1090,
|
|
669
|
+
y: (seed >> 4) % 680,
|
|
670
|
+
r: (0.5 + ((seed >> 8) % 12) / 10).toFixed(1),
|
|
671
|
+
opacity: (0.1 + ((seed >> 6) % 45) / 100).toFixed(2),
|
|
672
|
+
twinkle: index % 4 === 0,
|
|
673
|
+
delay: seed % 5000
|
|
674
|
+
};
|
|
675
|
+
});
|
|
676
|
+
const GALAXY = { count: 760, arms: 4, radius: 360, spin: 2.3, power: 2.4, cx: 545, cy: 340, flatten: 0.6 };
|
|
677
|
+
const lerpChannel = (from, to, t) => Math.round(from + (to - from) * t);
|
|
678
|
+
const galaxyDots = Array.from({ length: GALAXY.count }, (_, index) => {
|
|
679
|
+
const s1 = (hashString(`gal:${index}:a`) % 10000) / 10000;
|
|
680
|
+
const s2 = (hashString(`gal:${index}:b`) % 10000) / 10000;
|
|
681
|
+
const s3 = (hashString(`gal:${index}:c`) % 10000) / 10000;
|
|
682
|
+
const s4 = (hashString(`gal:${index}:d`) % 10000) / 10000;
|
|
683
|
+
const radius = Math.pow(s1, GALAXY.power) * GALAXY.radius;
|
|
684
|
+
const t = radius / GALAXY.radius;
|
|
685
|
+
const branchAngle = ((index % GALAXY.arms) / GALAXY.arms) * Math.PI * 2;
|
|
686
|
+
const spinAngle = t * GALAXY.spin;
|
|
687
|
+
const randomX = (s2 - 0.5) * 0.42 * radius;
|
|
688
|
+
const randomY = (s3 - 0.5) * 0.42 * radius;
|
|
689
|
+
const totalAngle = branchAngle + spinAngle;
|
|
690
|
+
// inside #FF66FF -> outside #66FFFF, like the reference galaxy
|
|
691
|
+
const color = `rgb(${lerpChannel(255, 102, t)}, ${lerpChannel(102, 255, t)}, 255)`;
|
|
692
|
+
return {
|
|
693
|
+
x: (GALAXY.cx + (Math.cos(totalAngle) * radius + randomX)).toFixed(1),
|
|
694
|
+
y: (GALAXY.cy + (Math.sin(totalAngle) * radius + randomY) * GALAXY.flatten).toFixed(1),
|
|
695
|
+
r: (0.5 + s4 * 1.1).toFixed(1),
|
|
696
|
+
color,
|
|
697
|
+
opacity: (0.12 + s4 * 0.34).toFixed(2)
|
|
698
|
+
};
|
|
699
|
+
});
|
|
700
|
+
const nebulae = Array.from({ length: 7 }, (_, index) => {
|
|
701
|
+
const seed = hashString(`nebula:${index}`);
|
|
702
|
+
return {
|
|
703
|
+
id: index,
|
|
704
|
+
hue: seed % 360,
|
|
705
|
+
x: 80 + (seed % 930),
|
|
706
|
+
y: 60 + ((seed >> 5) % 560),
|
|
707
|
+
r: 70 + ((seed >> 3) % 150),
|
|
708
|
+
opacity: (0.05 + ((seed >> 7) % 8) / 100).toFixed(2)
|
|
709
|
+
};
|
|
710
|
+
});
|
|
711
|
+
const pulses = edges.slice(0, 120).map((edge, index) => {
|
|
712
|
+
const seed = hashString(`pulse:${edge.source}:${edge.target}:${index}`);
|
|
713
|
+
return {
|
|
714
|
+
path: `M ${edge.sourceNode.x.toFixed(1)},${edge.sourceNode.y.toFixed(1)} L ${edge.targetNode.x.toFixed(1)},${edge.targetNode.y.toFixed(1)}`,
|
|
715
|
+
dur: (3.2 + (seed % 42) / 10).toFixed(1),
|
|
716
|
+
begin: -((seed >> 4) % 7000),
|
|
717
|
+
index
|
|
718
|
+
};
|
|
719
|
+
});
|
|
679
720
|
const mapTitle = copy.knowledgeGraph || copy.harnessMap || 'Harness map';
|
|
680
721
|
const mapEyebrow = copy.harnessMap && copy.harnessMap !== mapTitle ? copy.harnessMap : '';
|
|
681
722
|
return `
|
|
@@ -700,9 +741,26 @@ function renderGraphCanvas(summary, copy) {
|
|
|
700
741
|
</div>
|
|
701
742
|
<svg class="graph-canvas" viewBox="0 0 1090 680" role="img" aria-label="Harness health graph">
|
|
702
743
|
<defs>
|
|
703
|
-
<radialGradient id="graph-bg-grad" cx="50%" cy="42%" r="
|
|
704
|
-
<stop offset="0%" style="stop-color: #
|
|
705
|
-
<stop offset="
|
|
744
|
+
<radialGradient id="graph-bg-grad" cx="50%" cy="42%" r="80%">
|
|
745
|
+
<stop offset="0%" style="stop-color: #0B0E1A"/>
|
|
746
|
+
<stop offset="60%" style="stop-color: #05060F"/>
|
|
747
|
+
<stop offset="100%" style="stop-color: #000005"/>
|
|
748
|
+
</radialGradient>
|
|
749
|
+
<radialGradient id="galaxy-core-grad" cx="50%" cy="50%" r="50%">
|
|
750
|
+
<stop offset="0%" style="stop-color: #FFFFFF; stop-opacity: 0.55"/>
|
|
751
|
+
<stop offset="35%" style="stop-color: #C9B8FF; stop-opacity: 0.18"/>
|
|
752
|
+
<stop offset="100%" style="stop-color: #C9B8FF; stop-opacity: 0"/>
|
|
753
|
+
</radialGradient>
|
|
754
|
+
${nebulae.map((nebula) => `
|
|
755
|
+
<radialGradient id="nebula-grad-${nebula.id}" cx="50%" cy="50%" r="50%">
|
|
756
|
+
<stop offset="0%" style="stop-color: hsl(${nebula.hue}, 80%, 60%); stop-opacity: 0.6"/>
|
|
757
|
+
<stop offset="100%" style="stop-color: hsl(${nebula.hue}, 80%, 60%); stop-opacity: 0"/>
|
|
758
|
+
</radialGradient>
|
|
759
|
+
`).join('')}
|
|
760
|
+
<radialGradient id="pulse-grad" cx="50%" cy="50%" r="50%">
|
|
761
|
+
<stop offset="0%" style="stop-color: #FFFFFF; stop-opacity: 1"/>
|
|
762
|
+
<stop offset="45%" style="stop-color: #9DC4FF; stop-opacity: 0.85"/>
|
|
763
|
+
<stop offset="100%" style="stop-color: #5B8DEF; stop-opacity: 0"/>
|
|
706
764
|
</radialGradient>
|
|
707
765
|
${Object.entries(TYPE_COLORS).map(([type, color]) => `
|
|
708
766
|
<radialGradient id="node-grad-${escapeAttr(type)}" cx="32%" cy="28%" r="78%">
|
|
@@ -713,6 +771,20 @@ function renderGraphCanvas(summary, copy) {
|
|
|
713
771
|
`).join('')}
|
|
714
772
|
</defs>
|
|
715
773
|
<rect class="graph-bg" width="1090" height="680" fill="url(#graph-bg-grad)"/>
|
|
774
|
+
<g class="starfield" aria-hidden="true">
|
|
775
|
+
${stars.map((star) => `
|
|
776
|
+
<circle cx="${star.x}" cy="${star.y}" r="${star.r}" fill="#FFFFFF" opacity="${star.opacity}"${star.twinkle ? ` class="star-twinkle" style="--twinkle-delay: ${star.delay}ms"` : ''}/>
|
|
777
|
+
`).join('')}
|
|
778
|
+
</g>
|
|
779
|
+
<g class="galaxy-layer" aria-hidden="true">
|
|
780
|
+
${nebulae.map((nebula) => `
|
|
781
|
+
<circle class="nebula" cx="${nebula.x}" cy="${nebula.y}" r="${nebula.r}" fill="url(#nebula-grad-${nebula.id})" opacity="${nebula.opacity}"/>
|
|
782
|
+
`).join('')}
|
|
783
|
+
<g class="galaxy-spiral">
|
|
784
|
+
${galaxyDots.map((dot) => `<circle cx="${dot.x}" cy="${dot.y}" r="${dot.r}" fill="${dot.color}" opacity="${dot.opacity}"/>`).join('')}
|
|
785
|
+
</g>
|
|
786
|
+
<circle class="galaxy-core" cx="${GALAXY.cx}" cy="${GALAXY.cy}" r="120" fill="url(#galaxy-core-grad)"/>
|
|
787
|
+
</g>
|
|
716
788
|
<g id="graph-viewport">
|
|
717
789
|
<g class="edges">
|
|
718
790
|
${edges.map((edge, index) => `
|
|
@@ -731,6 +803,32 @@ function renderGraphCanvas(summary, copy) {
|
|
|
731
803
|
/>
|
|
732
804
|
`).join('')}
|
|
733
805
|
</g>
|
|
806
|
+
<g class="pulses" aria-hidden="true">
|
|
807
|
+
${pulses.map((pulse) => `
|
|
808
|
+
<g class="pulse-wrap" data-pulse-index="${pulse.index}">
|
|
809
|
+
<circle class="edge-pulse" r="2.6" fill="url(#pulse-grad)" opacity="0">
|
|
810
|
+
<animateMotion dur="${pulse.dur}s" begin="${pulse.begin}ms" repeatCount="indefinite" path="${escapeAttr(pulse.path)}"/>
|
|
811
|
+
<animate attributeName="opacity" values="0;0.95;0.95;0" keyTimes="0;0.12;0.85;1" dur="${pulse.dur}s" begin="${pulse.begin}ms" repeatCount="indefinite"/>
|
|
812
|
+
</circle>
|
|
813
|
+
</g>
|
|
814
|
+
`).join('')}
|
|
815
|
+
</g>
|
|
816
|
+
<g class="orbits" aria-hidden="true">
|
|
817
|
+
${orbitSystems.map((system) => `
|
|
818
|
+
<g class="orbit-system" data-parent="${escapeAttr(system.parentId)}" transform="translate(${system.x.toFixed(1)} ${system.y.toFixed(1)})">
|
|
819
|
+
${system.rings.map((ring) => {
|
|
820
|
+
const dots = Array.from({ length: ring.count }, (_, dotIndex) => {
|
|
821
|
+
const angle = ring.phase + (dotIndex * Math.PI * 2) / ring.count;
|
|
822
|
+
return `<circle class="orbit-dot" cx="${(Math.cos(angle) * ring.distance).toFixed(1)}" cy="${(Math.sin(angle) * ring.distance).toFixed(1)}" r="${ring.dotRadius}" fill="url(#node-grad-${escapeAttr(ring.kind)})"/>`;
|
|
823
|
+
}).join('');
|
|
824
|
+
return `
|
|
825
|
+
<circle class="orbit-ring" cx="0" cy="0" r="${ring.distance.toFixed(1)}"/>
|
|
826
|
+
<g class="orbit-spin${ring.reverse ? ' is-reverse' : ''}" style="--orbit-dur: ${ring.duration}s">${dots}</g>
|
|
827
|
+
`;
|
|
828
|
+
}).join('')}
|
|
829
|
+
</g>
|
|
830
|
+
`).join('')}
|
|
831
|
+
</g>
|
|
734
832
|
<g class="nodes">
|
|
735
833
|
${nodes.map((node, index) => {
|
|
736
834
|
const seed = hashString(node.id);
|
|
@@ -740,6 +838,9 @@ function renderGraphCanvas(summary, copy) {
|
|
|
740
838
|
const floatY = (((seed >> 3) % 7) - 3).toFixed(1);
|
|
741
839
|
return `
|
|
742
840
|
<g class="node-float" style="--float-dur: ${floatDuration}s; --float-delay: ${floatDelay}ms; --float-x: ${floatX}px; --float-y: ${floatY}px">
|
|
841
|
+
${node.type === 'harness' && node.radius >= 15 ? `
|
|
842
|
+
<ellipse class="planet-ring" cx="${node.x.toFixed(1)}" cy="${node.y.toFixed(1)}" rx="${(node.radius * 1.75).toFixed(1)}" ry="${(node.radius * 0.5).toFixed(1)}" transform="rotate(-16 ${node.x.toFixed(1)} ${node.y.toFixed(1)})"/>
|
|
843
|
+
` : ''}
|
|
743
844
|
<circle
|
|
744
845
|
class="graph-node ${node.type === 'harness' ? 'is-interactive' : ''}"
|
|
745
846
|
style="--enter-delay: ${Math.min(index * 9, 1100)}ms"
|
|
@@ -1358,14 +1459,42 @@ function renderScript(harnesses, copy) {
|
|
|
1358
1459
|
const setFilterStatus = (value) => {
|
|
1359
1460
|
if (filterStatus) filterStatus.textContent = value;
|
|
1360
1461
|
};
|
|
1462
|
+
const graphCanvas = document.querySelector('.graph-canvas');
|
|
1463
|
+
const orbitSystems = Array.from(document.querySelectorAll('.orbit-system'));
|
|
1464
|
+
const pulseWraps = Array.from(document.querySelectorAll('.pulse-wrap'));
|
|
1465
|
+
const defaultSelectedPanel = selectedPanel ? selectedPanel.innerHTML : '';
|
|
1466
|
+
function syncPulses() {
|
|
1467
|
+
const hasSelection = graphCanvas && graphCanvas.classList.contains('has-selection');
|
|
1468
|
+
pulseWraps.forEach((wrap) => {
|
|
1469
|
+
const edge = edges[Number(wrap.dataset.pulseIndex)];
|
|
1470
|
+
if (!edge) return;
|
|
1471
|
+
const hidden = edge.classList.contains('is-hidden') || edge.classList.contains('is-filtered-out');
|
|
1472
|
+
wrap.classList.toggle('is-hidden', hidden);
|
|
1473
|
+
wrap.classList.toggle('is-dimmed', Boolean(hasSelection && !edge.classList.contains('is-related')));
|
|
1474
|
+
});
|
|
1475
|
+
}
|
|
1476
|
+
function syncOrbits() {
|
|
1477
|
+
const hasSelection = graphCanvas && graphCanvas.classList.contains('has-selection');
|
|
1478
|
+
orbitSystems.forEach((system) => {
|
|
1479
|
+
const parent = nodeById(system.dataset.parent);
|
|
1480
|
+
const hidden = !parent || parent.classList.contains('is-hidden') || parent.classList.contains('is-filtered-out');
|
|
1481
|
+
system.classList.toggle('is-hidden', hidden);
|
|
1482
|
+
const related = parent && (parent.classList.contains('is-selected') || parent.classList.contains('is-related'));
|
|
1483
|
+
system.classList.toggle('is-dimmed', Boolean(hasSelection && !related));
|
|
1484
|
+
});
|
|
1485
|
+
syncPulses();
|
|
1486
|
+
}
|
|
1361
1487
|
function clearSelection() {
|
|
1362
1488
|
nodes.forEach((item) => item.classList.remove('is-selected', 'is-related'));
|
|
1363
1489
|
edges.forEach((item) => item.classList.remove('is-related'));
|
|
1364
1490
|
cards.forEach((item) => item.classList.remove('is-selected'));
|
|
1491
|
+
if (graphCanvas) graphCanvas.classList.remove('has-selection');
|
|
1492
|
+
syncOrbits();
|
|
1365
1493
|
}
|
|
1366
1494
|
function selectNode(node) {
|
|
1367
1495
|
clearSelection();
|
|
1368
1496
|
node.classList.add('is-selected');
|
|
1497
|
+
if (graphCanvas) graphCanvas.classList.add('has-selection');
|
|
1369
1498
|
const id = node.dataset.nodeId;
|
|
1370
1499
|
const item = byHarnessId.get(id);
|
|
1371
1500
|
edges.forEach((edge) => {
|
|
@@ -1393,6 +1522,7 @@ function renderScript(harnesses, copy) {
|
|
|
1393
1522
|
} else if (selectedPanel) {
|
|
1394
1523
|
selectedPanel.innerHTML = '<p class="eyebrow">' + esc(copy.selected) + '</p><h2>' + esc(node.dataset.nodeLabel) + '</h2><p>' + esc(id) + '</p><dl><div><dt>' + esc(copy.type) + '</dt><dd>' + esc(displayValue(node.dataset.nodeType)) + '</dd></div><div><dt>' + esc(copy.weight) + '</dt><dd>' + esc(node.dataset.nodeWeight) + '</dd></div></dl>';
|
|
1395
1524
|
}
|
|
1525
|
+
syncOrbits();
|
|
1396
1526
|
}
|
|
1397
1527
|
function selectHarness(id) {
|
|
1398
1528
|
const node = nodeById('harness:' + id);
|
|
@@ -1410,7 +1540,9 @@ function renderScript(harnesses, copy) {
|
|
|
1410
1540
|
nodes.forEach((node) => node.classList.toggle('is-hidden', mode === 'core' && node.dataset.core !== 'true'));
|
|
1411
1541
|
const visibleIds = new Set(nodes.filter((node) => !node.classList.contains('is-hidden')).map((node) => node.dataset.nodeId));
|
|
1412
1542
|
edges.forEach((edge) => edge.classList.toggle('is-hidden', mode === 'core' && (!visibleIds.has(edge.dataset.source) || !visibleIds.has(edge.dataset.target))));
|
|
1543
|
+
document.querySelectorAll('.orbit-system').forEach((system) => system.classList.toggle('is-mode-hidden', mode === 'core'));
|
|
1413
1544
|
setStatus(mode === 'core' ? copy.coreMode : copy.showingAll);
|
|
1545
|
+
syncOrbits();
|
|
1414
1546
|
}
|
|
1415
1547
|
function filterRecommendation(value, button) {
|
|
1416
1548
|
const alreadyActive = button.classList.contains('active-filter');
|
|
@@ -1424,6 +1556,7 @@ function renderScript(harnesses, copy) {
|
|
|
1424
1556
|
cards.forEach((card) => card.classList.remove('is-filtered-out'));
|
|
1425
1557
|
setStatus(copy.showingAll);
|
|
1426
1558
|
setFilterStatus(copy.showingAll);
|
|
1559
|
+
syncOrbits();
|
|
1427
1560
|
return;
|
|
1428
1561
|
}
|
|
1429
1562
|
button.classList.add('active-filter');
|
|
@@ -1438,6 +1571,7 @@ function renderScript(harnesses, copy) {
|
|
|
1438
1571
|
const label = recLabelByFilter[value] || value;
|
|
1439
1572
|
setStatus(copy.filteredTo + ': ' + label);
|
|
1440
1573
|
setFilterStatus(copy.filteredTo + ': ' + label);
|
|
1574
|
+
syncOrbits();
|
|
1441
1575
|
}
|
|
1442
1576
|
interactiveNodes.forEach((node) => {
|
|
1443
1577
|
node.addEventListener('click', () => selectNode(node));
|
|
@@ -1520,12 +1654,24 @@ function renderScript(harnesses, copy) {
|
|
|
1520
1654
|
panState.y = event.clientY;
|
|
1521
1655
|
applyView();
|
|
1522
1656
|
});
|
|
1657
|
+
let suppressClick = false;
|
|
1523
1658
|
const endPan = () => {
|
|
1659
|
+
suppressClick = Boolean(panState && panState.moved);
|
|
1524
1660
|
panState = null;
|
|
1525
1661
|
graphSvg.classList.remove('is-panning');
|
|
1526
1662
|
};
|
|
1527
1663
|
graphSvg.addEventListener('pointerup', endPan);
|
|
1528
1664
|
graphSvg.addEventListener('pointercancel', endPan);
|
|
1665
|
+
graphSvg.addEventListener('click', (event) => {
|
|
1666
|
+
if (suppressClick) {
|
|
1667
|
+
suppressClick = false;
|
|
1668
|
+
return;
|
|
1669
|
+
}
|
|
1670
|
+
if (event.target.classList.contains('graph-bg') || event.target.closest('.starfield') || event.target.closest('.galaxy-layer')) {
|
|
1671
|
+
clearSelection();
|
|
1672
|
+
if (selectedPanel && defaultSelectedPanel) selectedPanel.innerHTML = defaultSelectedPanel;
|
|
1673
|
+
}
|
|
1674
|
+
});
|
|
1529
1675
|
graphSvg.addEventListener('dblclick', (event) => {
|
|
1530
1676
|
event.preventDefault();
|
|
1531
1677
|
resetView();
|
|
@@ -2193,6 +2339,103 @@ function renderStyles() {
|
|
|
2193
2339
|
transition: opacity 160ms ease, transform 160ms ease;
|
|
2194
2340
|
}
|
|
2195
2341
|
|
|
2342
|
+
.map-panel,
|
|
2343
|
+
.graph-canvas,
|
|
2344
|
+
.graph-canvas text {
|
|
2345
|
+
user-select: none;
|
|
2346
|
+
-webkit-user-select: none;
|
|
2347
|
+
-webkit-user-drag: none;
|
|
2348
|
+
}
|
|
2349
|
+
|
|
2350
|
+
.star-twinkle {
|
|
2351
|
+
animation: star-twinkle 3.4s ease-in-out var(--twinkle-delay, 0ms) infinite alternate;
|
|
2352
|
+
}
|
|
2353
|
+
|
|
2354
|
+
@keyframes star-twinkle {
|
|
2355
|
+
from { opacity: 0.08; }
|
|
2356
|
+
to { opacity: 0.6; }
|
|
2357
|
+
}
|
|
2358
|
+
|
|
2359
|
+
.galaxy-layer { pointer-events: none; }
|
|
2360
|
+
|
|
2361
|
+
.nebula { mix-blend-mode: screen; }
|
|
2362
|
+
|
|
2363
|
+
.galaxy-spiral {
|
|
2364
|
+
transform-origin: 545px 340px;
|
|
2365
|
+
transform-box: view-box;
|
|
2366
|
+
animation: galaxy-rotate 420s linear infinite;
|
|
2367
|
+
mix-blend-mode: screen;
|
|
2368
|
+
}
|
|
2369
|
+
|
|
2370
|
+
@keyframes galaxy-rotate {
|
|
2371
|
+
to { transform: rotate(360deg); }
|
|
2372
|
+
}
|
|
2373
|
+
|
|
2374
|
+
.galaxy-core {
|
|
2375
|
+
mix-blend-mode: screen;
|
|
2376
|
+
animation: core-breathe 9s ease-in-out infinite alternate;
|
|
2377
|
+
}
|
|
2378
|
+
|
|
2379
|
+
@keyframes core-breathe {
|
|
2380
|
+
from { opacity: 0.5; }
|
|
2381
|
+
to { opacity: 0.9; }
|
|
2382
|
+
}
|
|
2383
|
+
|
|
2384
|
+
.pulse-wrap { transition: opacity 260ms ease; }
|
|
2385
|
+
.pulse-wrap.is-hidden { opacity: 0; }
|
|
2386
|
+
.pulse-wrap.is-dimmed { opacity: 0.08; }
|
|
2387
|
+
|
|
2388
|
+
.edge-pulse {
|
|
2389
|
+
filter: drop-shadow(0 0 4px rgba(157, 196, 255, 0.9));
|
|
2390
|
+
}
|
|
2391
|
+
|
|
2392
|
+
.orbit-ring {
|
|
2393
|
+
fill: none;
|
|
2394
|
+
stroke: var(--text-secondary);
|
|
2395
|
+
stroke-opacity: 0.14;
|
|
2396
|
+
stroke-width: 0.7;
|
|
2397
|
+
stroke-dasharray: 2 5;
|
|
2398
|
+
}
|
|
2399
|
+
|
|
2400
|
+
.orbit-spin {
|
|
2401
|
+
animation: orbit-rotate var(--orbit-dur, 40s) linear infinite;
|
|
2402
|
+
}
|
|
2403
|
+
|
|
2404
|
+
.orbit-spin.is-reverse { animation-direction: reverse; }
|
|
2405
|
+
|
|
2406
|
+
@keyframes orbit-rotate {
|
|
2407
|
+
to { transform: rotate(360deg); }
|
|
2408
|
+
}
|
|
2409
|
+
|
|
2410
|
+
.orbit-dot { opacity: 0.85; }
|
|
2411
|
+
|
|
2412
|
+
.orbit-system {
|
|
2413
|
+
transition: opacity 260ms ease;
|
|
2414
|
+
}
|
|
2415
|
+
|
|
2416
|
+
.orbit-system.is-hidden,
|
|
2417
|
+
.orbit-system.is-mode-hidden { opacity: 0; pointer-events: none; }
|
|
2418
|
+
.orbit-system.is-dimmed { opacity: 0.08; }
|
|
2419
|
+
|
|
2420
|
+
.planet-ring {
|
|
2421
|
+
fill: none;
|
|
2422
|
+
stroke: var(--text-secondary);
|
|
2423
|
+
stroke-opacity: 0.4;
|
|
2424
|
+
stroke-width: 1.4;
|
|
2425
|
+
pointer-events: none;
|
|
2426
|
+
}
|
|
2427
|
+
|
|
2428
|
+
.graph-canvas.has-selection .graph-node:not(.is-selected):not(.is-related) {
|
|
2429
|
+
opacity: 0.14;
|
|
2430
|
+
filter: blur(1.2px);
|
|
2431
|
+
}
|
|
2432
|
+
|
|
2433
|
+
.graph-canvas.has-selection .graph-edge:not(.is-related) { opacity: 0.05; }
|
|
2434
|
+
|
|
2435
|
+
.graph-canvas.has-selection .planet-ring { stroke-opacity: 0.1; }
|
|
2436
|
+
|
|
2437
|
+
.graph-canvas.has-selection .labels text { opacity: 0.18; transition: opacity 220ms ease; }
|
|
2438
|
+
|
|
2196
2439
|
.map-controls-row {
|
|
2197
2440
|
display: flex;
|
|
2198
2441
|
gap: var(--space-2);
|
|
@@ -2300,8 +2543,13 @@ function renderStyles() {
|
|
|
2300
2543
|
.graph-node,
|
|
2301
2544
|
.graph-edge,
|
|
2302
2545
|
.graph-canvas text,
|
|
2546
|
+
.orbit-spin,
|
|
2547
|
+
.star-twinkle,
|
|
2548
|
+
.galaxy-spiral,
|
|
2549
|
+
.galaxy-core,
|
|
2303
2550
|
.page.is-active { animation: none; }
|
|
2304
2551
|
.graph-node { transition: none; }
|
|
2552
|
+
.pulses { display: none; }
|
|
2305
2553
|
}
|
|
2306
2554
|
|
|
2307
2555
|
.map-caption {
|