mirror of
https://github.com/Kaelio/ktx.git
synced 2026-06-07 07:55:13 +02:00
feat(docs): animate ingestion flow with running dots
Replace static smoothstep edges in the introduction page's ingestion diagram with a custom animated edge that runs glowing cyan dots along each path, conveying the source → stage → output flow. Dot duration scales with path length and is hidden under prefers-reduced-motion.
This commit is contained in:
parent
78527fdf59
commit
d34c0a37f5
2 changed files with 80 additions and 2 deletions
|
|
@ -3,6 +3,9 @@
|
|||
import {
|
||||
Background,
|
||||
BackgroundVariant,
|
||||
BaseEdge,
|
||||
type EdgeProps,
|
||||
getSmoothStepPath,
|
||||
Handle,
|
||||
MarkerType,
|
||||
type Node,
|
||||
|
|
@ -182,7 +185,7 @@ const flowEdges = [
|
|||
})),
|
||||
].map((edge) => ({
|
||||
...edge,
|
||||
type: "smoothstep" as const,
|
||||
type: "animated" as const,
|
||||
style: { stroke: EDGE_STROKE, strokeWidth: 1.5 },
|
||||
markerEnd: {
|
||||
type: MarkerType.ArrowClosed,
|
||||
|
|
@ -337,12 +340,75 @@ function OutputNodeView({ data }: NodeProps<OutputNode>) {
|
|||
);
|
||||
}
|
||||
|
||||
const DOT_CORE_COLOR = "#67e8f9";
|
||||
const DOT_GLOW_COLOR = "#22d3ee";
|
||||
const DOT_SPEED_PX_PER_SEC = 110;
|
||||
const DOT_MIN_DURATION_SEC = 0.7;
|
||||
|
||||
function AnimatedSmoothStepEdge({
|
||||
id,
|
||||
sourceX,
|
||||
sourceY,
|
||||
sourcePosition,
|
||||
targetX,
|
||||
targetY,
|
||||
targetPosition,
|
||||
style,
|
||||
markerEnd,
|
||||
}: EdgeProps) {
|
||||
const [path] = getSmoothStepPath({
|
||||
sourceX,
|
||||
sourceY,
|
||||
sourcePosition,
|
||||
targetX,
|
||||
targetY,
|
||||
targetPosition,
|
||||
});
|
||||
const pathId = `mechanics-flow-${id}`;
|
||||
const approxLength =
|
||||
Math.abs(targetX - sourceX) + Math.abs(targetY - sourceY);
|
||||
const duration = Math.max(
|
||||
DOT_MIN_DURATION_SEC,
|
||||
approxLength / DOT_SPEED_PX_PER_SEC,
|
||||
);
|
||||
const durAttr = `${duration.toFixed(2)}s`;
|
||||
const beginAttr = `-${(duration / 2).toFixed(2)}s`;
|
||||
|
||||
return (
|
||||
<>
|
||||
<BaseEdge id={pathId} path={path} style={style} markerEnd={markerEnd} />
|
||||
<g className="mechanics-flow-dot">
|
||||
<circle r={6.5} fill={DOT_GLOW_COLOR} opacity={0.22} />
|
||||
<circle r={2.6} fill={DOT_CORE_COLOR} />
|
||||
<animateMotion dur={durAttr} repeatCount="indefinite">
|
||||
<mpath href={`#${pathId}`} />
|
||||
</animateMotion>
|
||||
</g>
|
||||
<g className="mechanics-flow-dot">
|
||||
<circle r={5} fill={DOT_GLOW_COLOR} opacity={0.14} />
|
||||
<circle r={2} fill={DOT_CORE_COLOR} opacity={0.7} />
|
||||
<animateMotion
|
||||
dur={durAttr}
|
||||
begin={beginAttr}
|
||||
repeatCount="indefinite"
|
||||
>
|
||||
<mpath href={`#${pathId}`} />
|
||||
</animateMotion>
|
||||
</g>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const nodeTypes = {
|
||||
source: SourceNodeView,
|
||||
stage: StageNodeView,
|
||||
output: OutputNodeView,
|
||||
};
|
||||
|
||||
const edgeTypes = {
|
||||
animated: AnimatedSmoothStepEdge,
|
||||
};
|
||||
|
||||
export function ProductMechanics() {
|
||||
return (
|
||||
<section
|
||||
|
|
@ -396,6 +462,7 @@ export function ProductMechanics() {
|
|||
nodes={nodes}
|
||||
edges={edges}
|
||||
nodeTypes={nodeTypes}
|
||||
edgeTypes={edgeTypes}
|
||||
fitView
|
||||
fitViewOptions={{ padding: 0.04 }}
|
||||
nodesDraggable={false}
|
||||
|
|
@ -459,6 +526,15 @@ export function ProductMechanics() {
|
|||
border: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
.mechanics-canvas .mechanics-flow-dot {
|
||||
pointer-events: none;
|
||||
filter: drop-shadow(0 0 6px rgba(34, 211, 238, 0.45));
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.mechanics-canvas .mechanics-flow-dot {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
`}</style>
|
||||
</section>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -86,7 +86,9 @@ test("product mechanics component explains ingestion outputs", async () => {
|
|||
'"use client"',
|
||||
"@xyflow/react",
|
||||
"<ReactFlow",
|
||||
"smoothstep",
|
||||
"getSmoothStepPath",
|
||||
"animateMotion",
|
||||
"mechanics-flow-dot",
|
||||
]) {
|
||||
assert.ok(
|
||||
component.includes(expectedText),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue