Transition map beads: connector-colored elongated pulses

Beads now take their connector's color — accent for internal links,
plain text color for external ones (matching the tconn-exit styling) —
and render as an elongated pulse along the direction of travel instead
of a plain dot: a rounded capsule with a gradient-faded tail, rotated
to the edge's angle (cached per flow). Flow objects carry an 'external'
tag from buildFlows; the gradients live in the svg defs with stop
colors resolved from CSS so theme variables apply.
This commit is contained in:
LeoVasanko committed 2026-09-25 20:23:20 +00:00
1 parent fb2914d853
commit 3c9ef04ffd
3 files changed
+52 -12

No files matched your search

+37 -5
View File
@@ -13,6 +13,7 @@ import {
TNODE_W,
TNODE_H,
BEAD_R,
BEAD_LEN,
buildTransitionGraph,
} from './analytics/transitions.js'
@@ -145,7 +146,17 @@ const tick = (t) => {
continue
}
const f = b.e.flow
out.push({ x: f.x1 + (f.x2 - f.x1) * b.p, y: f.y1 + (f.y2 - f.y1) * b.p })
// The pulse is elongated along the direction of travel (constant per
// flow) and colored like its connector (external edges use --text).
if (f.deg === undefined) {
f.deg = (Math.atan2(f.y2 - f.y1, f.x2 - f.x1) * 180) / Math.PI
}
out.push({
x: f.x1 + (f.x2 - f.x1) * b.p,
y: f.y1 + (f.y2 - f.y1) * b.p,
deg: f.deg,
ext: f.external,
})
}
beads.value = out
rafId = requestAnimationFrame(tick)
@@ -184,6 +195,21 @@ const countLabel = (n) =>
<section v-if="graph">
<svg class="tmap" :style="{ maxWidth: `${graph.bounds.x1 - graph.bounds.x0}px` }" :viewBox="`${graph.bounds.x0} ${graph.bounds.y0} ${graph.bounds.x1 - graph.bounds.x0} ${graph.bounds.y1 - graph.bounds.y0}`"
role="img" aria-label="map of transitions between pages">
<defs>
<!-- Pulse gradient: opaque head at the bead position (local origin),
fading tail trailing behind it (stop-color comes from CSS so
theme vars resolve). -->
<linearGradient id="tbead-grad">
<stop offset="0" class="tbead-stop" stop-opacity="0" />
<stop offset="0.55" class="tbead-stop" stop-opacity="0.5" />
<stop offset="1" class="tbead-stop" stop-opacity="1" />
</linearGradient>
<linearGradient id="tbead-grad-exit">
<stop offset="0" class="tbead-stop-exit" stop-opacity="0" />
<stop offset="0.55" class="tbead-stop-exit" stop-opacity="0.5" />
<stop offset="1" class="tbead-stop-exit" stop-opacity="1" />
</linearGradient>
</defs>
<path v-for="(a, i) in graph.arcs" :key="'a' + i"
:id="`tarc${i}`" :d="a.d" :class="['tarc', a.top && 'tarc-top']" />
<template v-for="(a, i) in graph.arcs" :key="'t' + i">
@@ -194,8 +220,11 @@ const countLabel = (n) =>
:d="e.d" :class="['tconn', e.external && 'tconn-exit']">
<title>{{ e.title }}</title>
</path>
<circle v-for="(b, i) in beads" :key="'b' + i"
:cx="b.x" :cy="b.y" :r="BEAD_R" class="tbead" />
<rect v-for="(b, i) in beads" :key="'b' + i"
:x="-BEAD_LEN" :y="-BEAD_R" :width="BEAD_LEN" :height="2 * BEAD_R" :rx="BEAD_R"
:fill="b.ext ? 'url(#tbead-grad-exit)' : 'url(#tbead-grad)'"
:class="['tbead', b.ext && 'tbead-exit']"
:transform="`translate(${b.x} ${b.y}) rotate(${b.deg})`" />
<g v-for="(x, i) in graph.extNodes" :key="'x' + i">
<clipPath :id="`xclip${i}`">
<rect :x="x.x - TNODE_W/2 + 6" :y="x.y - TNODE_H/2" :width="TNODE_W - 12"
@@ -259,10 +288,13 @@ const countLabel = (n) =>
fill: var(--text);
}
.tmap .tbead {
fill: var(--accent);
opacity: 0.85;
filter: drop-shadow(0 0 2.5px var(--accent));
}
.tmap .tbead-exit {
filter: drop-shadow(0 0 2.5px var(--text));
}
.tmap .tbead-stop { stop-color: var(--accent); }
.tmap .tbead-stop-exit { stop-color: var(--text); }
.tmap .txnode {
/* External source/exit pills: plain white on every theme, with a hairline
so the pill stays visible on a white page. */