This is an automated email from the ASF dual-hosted git repository.

davidzollo pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/seatunnel-website.git


The following commit(s) were added to refs/heads/main by this push:
     new d6a3e079d6a7 [Docs] Match homepage packet motion (#541)
d6a3e079d6a7 is described below

commit d6a3e079d6a785246689fc7e7a35923c28755d79
Author: Daniel <[email protected]>
AuthorDate: Tue Sep 22 08:17:36 2026 -0400

    [Docs] Match homepage packet motion (#541)
---
 src/components/home/MotionMechanismGrid.jsx | 318 ++++++++++++++++++++++------
 src/pages/home/index.less                   | 103 +++------
 2 files changed, 285 insertions(+), 136 deletions(-)

diff --git a/src/components/home/MotionMechanismGrid.jsx 
b/src/components/home/MotionMechanismGrid.jsx
index 86bfb35b7200..5c2025b2c5dd 100644
--- a/src/components/home/MotionMechanismGrid.jsx
+++ b/src/components/home/MotionMechanismGrid.jsx
@@ -17,82 +17,231 @@
 
 import React from 'react';
 
+const MOTION_CYCLE_SECONDS = 8;
+const FRAME_INTERVAL_MS = 1000 / 30;
+const PARALLEL_SPLITS = Array.from({length: 6}, (_, index) => ({
+    id: `S${index + 1}`,
+    owner: index % 3,
+    start: 0.35 + index * 0.62,
+}));
+const CDC_EVENTS = [
+    {start: 0.4, op: 'S', id: 41, value: 'NEW'},
+    {start: 1.45, op: 'S', id: 42, value: 'NEW'},
+    {start: 3, op: 'I', id: 43, value: 'NEW'},
+    {start: 4.2, op: 'U', id: 42, value: 'PAID'},
+    {start: 5.55, op: 'D', id: 41, value: null},
+];
+const TRANSFORMS = [
+    {id: 1, name: 'Ada', start: 0.45},
+    {id: 0, name: 'Temp', start: 2.35},
+    {id: 2, name: 'Lin', start: 4.25},
+];
+
+function cubicPoint(progress, path) {
+    const clampedProgress = Math.min(1, Math.max(0, progress));
+    const inverseProgress = 1 - clampedProgress;
+
+    return {
+        x:
+            inverseProgress ** 3 * path[0] +
+            3 * inverseProgress ** 2 * clampedProgress * path[2] +
+            3 * inverseProgress * clampedProgress ** 2 * path[4] +
+            clampedProgress ** 3 * path[6],
+        y:
+            inverseProgress ** 3 * path[1] +
+            3 * inverseProgress ** 2 * clampedProgress * path[3] +
+            3 * inverseProgress * clampedProgress ** 2 * path[5] +
+            clampedProgress ** 3 * path[7],
+    };
+}
+
+function usePrefersReducedMotion() {
+    const [prefersReducedMotion, setPrefersReducedMotion] = 
React.useState(false);
+
+    React.useEffect(() => {
+        const mediaQuery = window.matchMedia('(prefers-reduced-motion: 
reduce)');
+        const updatePreference = () => 
setPrefersReducedMotion(mediaQuery.matches);
+
+        updatePreference();
+        mediaQuery.addEventListener('change', updatePreference);
+        return () => mediaQuery.removeEventListener('change', 
updatePreference);
+    }, []);
+
+    return prefersReducedMotion;
+}
+
+function useMotionClock(isPaused) {
+    const [time, setTime] = React.useState(0);
+    const timeRef = React.useRef(0);
+
+    React.useEffect(() => {
+        timeRef.current = time;
+    }, [time]);
+
+    React.useEffect(() => {
+        if (isPaused) {
+            return undefined;
+        }
+
+        let animationFrame;
+        let previousFrame;
+        let lastRender = 0;
+
+        const tick = (now) => {
+            if (previousFrame === undefined) {
+                previousFrame = now;
+            }
+
+            timeRef.current = (timeRef.current + (now - previousFrame) / 1000) 
% MOTION_CYCLE_SECONDS;
+            previousFrame = now;
+
+            if (now - lastRender >= FRAME_INTERVAL_MS) {
+                setTime(timeRef.current);
+                lastRender = now;
+            }
+
+            animationFrame = window.requestAnimationFrame(tick);
+        };
+
+        animationFrame = window.requestAnimationFrame(tick);
+        return () => window.cancelAnimationFrame(animationFrame);
+    }, [isPaused]);
+
+    return time;
+}
+
 function Arrow({x, y}) {
     return <path d={`M${x - 6} ${y - 5}l6 5-6 5`} 
className="st-home-motion-diagram-arrow" fill="none" />;
 }
 
-function ParallelReadsDiagram() {
+function MovingPacket({label = '', path, progress, tone = 'blue', width = 30}) 
{
+    if (progress < 0 || progress > 1) {
+        return null;
+    }
+
+    const point = cubicPoint(progress, path);
+
+    if (!label) {
+        return (
+            <g transform={`translate(${point.x} ${point.y})`} 
className={`st-home-motion-diagram-moving-signal-${tone}`}>
+                <circle r="7" className="st-home-motion-diagram-moving-signal" 
/>
+                <circle r="3" 
className="st-home-motion-diagram-moving-signal-core" />
+            </g>
+        );
+    }
+
+    return (
+        <g transform={`translate(${point.x} ${point.y})`}>
+            <rect x={-width / 2} y="-12" width={width} height="24" rx="7" 
className={`st-home-motion-diagram-moving-packet-${tone}`} />
+            <text y="5" textAnchor="middle" 
className={`st-home-motion-diagram-moving-packet-copy-${tone}`}>{label}</text>
+        </g>
+    );
+}
+
+function cdcRows(time, isTarget) {
+    const rows = new Map(isTarget ? [] : [[41, 'NEW'], [42, 'NEW']]);
+
+    CDC_EVENTS.forEach((event) => {
+        const eventTime = isTarget ? event.start + 0.9 : event.start;
+        if ((isTarget || event.op !== 'S') && time >= eventTime) {
+            if (event.op === 'D') {
+                rows.delete(event.id);
+            } else {
+                rows.set(event.id, event.value);
+            }
+        }
+    });
+
+    return [...rows.entries()];
+}
+
+function CdcTable({x, rows}) {
+    return (
+        <>
+            <rect x={x} y="31" width="136" height="103" rx="9" 
className="st-home-motion-diagram-node" />
+            <rect x={x + 1} y="32" width="134" height="24" rx="7" 
className="st-home-motion-diagram-soft" />
+            <text x={x + 13} y="48" 
className="st-home-motion-diagram-label">id</text><text x={x + 60} y="48" 
className="st-home-motion-diagram-label">status</text>
+            {rows.length === 0 ? <text x={x + 13} y="77" 
className="st-home-motion-diagram-muted-copy">waiting…</text> : rows.map(([id, 
value], index) => <g key={id}><text x={x + 13} y={77 + index * 26} 
className="st-home-motion-diagram-copy">{id}</text><text x={x + 60} y={77 + 
index * 26} className="st-home-motion-diagram-copy">{value}</text></g>)}
+        </>
+    );
+}
+
+function transformedRows(time) {
+    return TRANSFORMS.filter((transform) => transform.id > 0 && time >= 
transform.start + 1.65);
+}
+
+function ParallelReadsDiagram({time}) {
+    const readerYPositions = [54, 87, 120];
+
     return (
         <svg viewBox="0 0 520 166" className="st-home-motion-diagram" 
aria-hidden="true">
             <text x="18" y="20" 
className="st-home-motion-diagram-label">SOURCE SPLITS</text>
             <text x="207" y="20" 
className="st-home-motion-diagram-label">READERS</text>
             <text x="438" y="20" 
className="st-home-motion-diagram-label">TARGET</text>
             <rect x="16" y="32" width="118" height="112" rx="10" 
className="st-home-motion-diagram-node" />
-            {['S1', 'S2', 'S3', 'S4', 'S5', 'S6'].map((split, index) => {
+            {PARALLEL_SPLITS.map((split, index) => {
                 const x = 28 + (index % 2) * 49;
                 const y = 45 + Math.floor(index / 2) * 31;
-                const active = index === 1 || index === 3;
+                const isWaiting = time < split.start;
                 return (
-                    <g key={split}>
-                        <rect x={x} y={y} width="39" height="22" rx="5" 
className={active ? 'st-home-motion-diagram-packet' : 
'st-home-motion-diagram-chip'} />
-                        <text
-                            x={x + 19.5}
-                            y={y + 15}
-                            textAnchor="middle"
-                            className={
-                                active
-                                    ? 'st-home-motion-diagram-on-blue'
-                                    : 'st-home-motion-diagram-chip-text'
-                            }
-                        >
-                            {split}
-                        </text>
+                    <g key={split.id}>
+                        <rect x={x} y={y} width="39" height="22" rx="5" 
className={isWaiting ? 'st-home-motion-diagram-chip' : 
'st-home-motion-diagram-soft'} />
+                        <text x={x + 19.5} y={y + 15} textAnchor="middle" 
className="st-home-motion-diagram-chip-text">{split.id}</text>
                     </g>
                 );
             })}
-            {[54, 87, 120].map((y, index) => (
+            {readerYPositions.map((y, index) => (
                 <g key={y}>
-                    <path d={`M134 88 C168 88 174 ${y} 204 ${y}`} 
className="st-home-motion-diagram-line st-home-motion-diagram-flow" fill="none" 
/>
+                    <path d={`M134 88 C168 88 174 ${y} 204 ${y}`} 
className="st-home-motion-diagram-line" fill="none" />
                     <rect x="205" y={y - 13} width="121" height="26" rx="7" 
className="st-home-motion-diagram-node" />
                     <circle cx="220" cy={y} r="3.5" className={index === 1 ? 
'st-home-motion-diagram-teal-fill' : 'st-home-motion-diagram-blue-fill'} />
                     <text x="232" y={y + 5} 
className="st-home-motion-diagram-copy">Reader {index + 1}</text>
-                    <path d={`M326 ${y} C364 ${y} 384 88 428 88`} 
className="st-home-motion-diagram-line st-home-motion-diagram-flow" fill="none" 
/>
+                    <path d={`M326 ${y} C364 ${y} 384 88 428 88`} 
className="st-home-motion-diagram-line" fill="none" />
                 </g>
             ))}
             <Arrow x="427" y="88" />
             <ellipse cx="466" cy="67" rx="27" ry="7" 
className="st-home-motion-diagram-node" />
             <path d="M439 67v43c0 9 54 9 54 0V67" 
className="st-home-motion-diagram-node" />
             <path d="M439 89c0 9 54 9 54 0M439 110c0 9 54 9 54 0" 
className="st-home-motion-diagram-line" fill="none" />
+            {PARALLEL_SPLITS.map((split) => {
+                const readerY = readerYPositions[split.owner];
+                const tone = split.owner === 1 ? 'teal' : 'blue';
+                return (
+                    <React.Fragment key={split.id}>
+                        <MovingPacket label={split.id} path={[134, 88, 168, 
88, 174, readerY, 204, readerY]} progress={(time - split.start) / 1.25} 
tone={tone} width={32} />
+                        <MovingPacket label={split.id} path={[326, readerY, 
364, readerY, 384, 88, 428, 88]} progress={(time - split.start - 1.25) / 1.25} 
tone={tone} width={32} />
+                    </React.Fragment>
+                );
+            })}
         </svg>
     );
 }
 
-function SnapshotCdcDiagram() {
+function SnapshotCdcDiagram({time}) {
+    const sourceRows = cdcRows(time, false);
+    const targetRows = cdcRows(time, true);
+
     return (
         <svg viewBox="0 0 520 166" className="st-home-motion-diagram" 
aria-hidden="true">
             <text x="18" y="20" className="st-home-motion-diagram-label">MYSQL 
/ ORDERS</text>
             <text x="373" y="20" 
className="st-home-motion-diagram-label">TARGET / ORDERS</text>
-            <rect x="16" y="31" width="136" height="103" rx="9" 
className="st-home-motion-diagram-node" />
-            <rect x="17" y="32" width="134" height="24" rx="7" 
className="st-home-motion-diagram-soft" />
-            <text x="29" y="48" 
className="st-home-motion-diagram-label">id</text><text x="76" y="48" 
className="st-home-motion-diagram-label">status</text>
-            <text x="29" y="77" 
className="st-home-motion-diagram-copy">41</text><text x="76" y="77" 
className="st-home-motion-diagram-copy">NEW</text>
-            <text x="29" y="104" 
className="st-home-motion-diagram-copy">42</text><text x="76" y="104" 
className="st-home-motion-diagram-copy">NEW</text>
+            <CdcTable x={16} rows={sourceRows} />
             <rect x="201" y="24" width="98" height="23" rx="12" 
className="st-home-motion-diagram-soft" />
-            <text x="250" y="40" textAnchor="middle" 
className="st-home-motion-diagram-accent-text">SNAPSHOT</text>
-            <path d="M153 82H367" className="st-home-motion-diagram-line 
st-home-motion-diagram-flow" fill="none" />
+            <text x="250" y="40" textAnchor="middle" 
className="st-home-motion-diagram-accent-text">{time < 2.8 ? 'SNAPSHOT' : 
'BINLOG'}</text>
+            <path d="M153 82H367" className="st-home-motion-diagram-line" 
fill="none" />
             <Arrow x="367" y="82" />
-            <rect x="235" y="70" width="48" height="23" rx="7" 
className="st-home-motion-diagram-teal" />
-            <text x="259" y="86" textAnchor="middle" 
className="st-home-motion-diagram-on-teal">S:42</text>
             <text x="260" y="116" textAnchor="middle" 
className="st-home-motion-diagram-copy">SeaTunnel</text>
-            <rect x="370" y="31" width="136" height="103" rx="9" 
className="st-home-motion-diagram-node" />
-            <rect x="371" y="32" width="134" height="24" rx="7" 
className="st-home-motion-diagram-soft" />
-            <text x="383" y="48" 
className="st-home-motion-diagram-label">id</text><text x="430" y="48" 
className="st-home-motion-diagram-label">status</text>
-            <text x="383" y="77" 
className="st-home-motion-diagram-copy">41</text><text x="430" y="77" 
className="st-home-motion-diagram-copy">NEW</text>
+            <CdcTable x={370} rows={targetRows} />
+            {CDC_EVENTS.map((event) => (
+                <MovingPacket key={`${event.op}-${event.id}-${event.start}`} 
label={`${event.op}:${event.id}`} path={[153, 82, 210, 82, 310, 82, 367, 82]} 
progress={(time - event.start) / 0.9} tone={event.op === 'S' || event.op === 
'I' ? 'teal' : 'blue'} width={43} />
+            ))}
         </svg>
     );
 }
 
-function TransformDiagram() {
+function TransformDiagram({time}) {
+    const outputRows = transformedRows(time);
+
     return (
         <svg viewBox="0 0 520 166" className="st-home-motion-diagram" 
aria-hidden="true">
             <text x="18" y="20" 
className="st-home-motion-diagram-label">INPUT</text>
@@ -102,25 +251,32 @@ function TransformDiagram() {
             <text x="28" y="57" 
className="st-home-motion-diagram-copy">1&nbsp;&nbsp; Ada</text>
             <text x="28" y="82" 
className="st-home-motion-diagram-copy">0&nbsp;&nbsp; Temp</text>
             <text x="28" y="107" 
className="st-home-motion-diagram-copy">2&nbsp;&nbsp; Lin</text>
-            <path d="M142 76H206M315 76h84" 
className="st-home-motion-diagram-line st-home-motion-diagram-flow" fill="none" 
/>
+            <path d="M142 76H206M315 76h84" 
className="st-home-motion-diagram-line" fill="none" />
             <Arrow x="399" y="76" />
             <rect x="207" y="48" width="108" height="56" rx="13" 
className="st-home-motion-diagram-blue" />
             <text x="261" y="82" textAnchor="middle" 
className="st-home-motion-diagram-on-blue st-home-motion-diagram-sql">SQL</text>
             <rect x="402" y="31" width="102" height="91" rx="9" 
className="st-home-motion-diagram-node" />
-            <rect x="365" y="64" width="24" height="24" rx="7" 
className="st-home-motion-diagram-teal" />
-            <text x="377" y="81" textAnchor="middle" 
className="st-home-motion-diagram-on-teal">1</text>
-            <text x="413" y="77" 
className="st-home-motion-diagram-muted-copy">waiting…</text>
+            {outputRows.length === 0 ? <text x="413" y="77" 
className="st-home-motion-diagram-muted-copy">waiting…</text> : 
outputRows.map((row, index) => <text key={row.id} x="413" y={60 + index * 27} 
className="st-home-motion-diagram-teal-copy">{row.id}&nbsp;&nbsp; 
{row.name}_</text>)}
             <text x="18" y="144" 
className="st-home-motion-diagram-code">CONCAT(name, '_')</text>
+            {TRANSFORMS.map((transform) => (
+                <React.Fragment key={transform.id}>
+                    <MovingPacket label={String(transform.id)} path={[142, 76, 
164, 76, 184, 76, 206, 76]} progress={(time - transform.start) / 0.65} 
width={22} />
+                    {transform.id > 0 ? <MovingPacket 
label={String(transform.id)} path={[315, 76, 344, 76, 371, 76, 399, 76]} 
progress={(time - transform.start - 0.85) / 0.8} tone="teal" width={22} /> : 
<MovingPacket label="0" path={[261, 105, 261, 117, 261, 126, 261, 136]} 
progress={(time - transform.start - 0.65) / 0.7} width={22} />}
+                </React.Fragment>
+            ))}
         </svg>
     );
 }
 
-function RoutingDiagram() {
+function RoutingDiagram({time}) {
     const rows = ['users', 'orders', 'items'];
     return (
         <svg viewBox="0 0 520 166" className="st-home-motion-diagram" 
aria-hidden="true">
             {rows.map((table, index) => {
                 const y = 39 + index * 43;
+                const tone = index === 1 ? 'teal' : 'blue';
+                const sourcePath = [144, y + 15, 179, y + 15, 189, 82, 221, 
82];
+                const targetPath = [299, 82, 333, 82, 343, y + 15, 376, y + 
15];
                 return (
                     <g key={table}>
                         <rect x="16" y={y} width="127" height="30" rx="7" 
className="st-home-motion-diagram-node" />
@@ -129,8 +285,18 @@ function RoutingDiagram() {
                         <rect x="389" y={y + 8} width="16" height="14" rx="2" 
className="st-home-motion-diagram-table" />
                         <text x="55" y={y + 20} 
className="st-home-motion-diagram-copy">{table}</text>
                         <text x="416" y={y + 20} 
className="st-home-motion-diagram-copy">{table}</text>
-                        <path d={`M144 ${y + 15} C179 ${y + 15} 189 82 221 
82M299 82 C333 82 343 ${y + 15} 376 ${y + 15}`} 
className="st-home-motion-diagram-line st-home-motion-diagram-flow" fill="none" 
/>
+                        <path d={`M144 ${y + 15} C179 ${y + 15} 189 82 221 
82M299 82 C333 82 343 ${y + 15} 376 ${y + 15}`} 
className="st-home-motion-diagram-line" fill="none" />
                         <Arrow x="375" y={y + 15} />
+                        {[0, 1].map((round) => {
+                            const start = 0.35 + index * 0.5 + round * 3;
+                            const label = table[0].toUpperCase();
+                            return (
+                                <React.Fragment key={start}>
+                                    <MovingPacket label={label} 
path={sourcePath} progress={(time - start) / 1.15} tone={tone} width={22} />
+                                    <MovingPacket label={label} 
path={targetPath} progress={(time - start - 1.15) / 1.15} tone={tone} 
width={22} />
+                                </React.Fragment>
+                            );
+                        })}
                     </g>
                 );
             })}
@@ -141,8 +307,11 @@ function RoutingDiagram() {
     );
 }
 
-function SchemaDiagram() {
+function SchemaDiagram({time}) {
     const rows = [['id', 'BIGINT'], ['name', 'VARCHAR'], ['region', 
'VARCHAR']];
+    const sourceColumnCount = time >= 1.1 ? 3 : 2;
+    const sinkColumnCount = time >= 3.8 ? 3 : 2;
+
     return (
         <svg viewBox="0 0 520 166" className="st-home-motion-diagram" 
aria-hidden="true">
             <text x="18" y="20" className="st-home-motion-diagram-label">MYSQL 
SOURCE</text>
@@ -153,46 +322,60 @@ function SchemaDiagram() {
                 const y = 76 + index * 24;
                 return (
                     <g key={name}>
-                        {index === 2 ? <rect x="22" y={y - 16} width="123" 
height="22" rx="4" className="st-home-motion-diagram-highlight" /> : null}
-                        <text x="29" y={y} className={index === 2 ? 
'st-home-motion-diagram-teal-copy' : 
'st-home-motion-diagram-copy'}>{name}</text>
-                        <text x="92" y={y} className={index === 2 ? 
'st-home-motion-diagram-teal-copy' : 
'st-home-motion-diagram-code'}>{type}</text>
-                        <text x="382" y={y} className={index === 2 ? 
'st-home-motion-diagram-teal-copy' : 
'st-home-motion-diagram-copy'}>{name}</text>
-                        <text x="445" y={y} className={index === 2 ? 
'st-home-motion-diagram-teal-copy' : 
'st-home-motion-diagram-code'}>{type}</text>
+                        {index === 2 && index < sourceColumnCount ? <rect 
x="22" y={y - 16} width="123" height="22" rx="4" 
className="st-home-motion-diagram-highlight" /> : null}
+                        {index < sourceColumnCount ? <><text x="29" y={y} 
className={index === 2 ? 'st-home-motion-diagram-teal-copy' : 
'st-home-motion-diagram-copy'}>{name}</text><text x="92" y={y} className={index 
=== 2 ? 'st-home-motion-diagram-teal-copy' : 
'st-home-motion-diagram-code'}>{type}</text></> : null}
+                        {index === 2 && index < sinkColumnCount ? <rect 
x="375" y={y - 16} width="123" height="22" rx="4" 
className="st-home-motion-diagram-highlight" /> : null}
+                        {index < sinkColumnCount ? <><text x="382" y={y} 
className={index === 2 ? 'st-home-motion-diagram-teal-copy' : 
'st-home-motion-diagram-copy'}>{name}</text><text x="445" y={y} 
className={index === 2 ? 'st-home-motion-diagram-teal-copy' : 
'st-home-motion-diagram-code'}>{type}</text></> : null}
                     </g>
                 );
             })}
-            <path d="M152 82H364" className="st-home-motion-diagram-line 
st-home-motion-diagram-flow" fill="none" /><Arrow x="364" y="82" />
+            <path d="M152 82H364" className="st-home-motion-diagram-line" 
fill="none" /><Arrow x="364" y="82" />
             <rect x="225" y="48" width="58" height="22" rx="11" 
className="st-home-motion-diagram-soft" /><text x="254" y="64" 
textAnchor="middle" className="st-home-motion-diagram-accent-text">DDL</text>
-            <rect x="217" y="74" width="42" height="23" rx="7" 
className="st-home-motion-diagram-blue" /><text x="238" y="90" 
textAnchor="middle" className="st-home-motion-diagram-on-blue">ADD</text>
+            <MovingPacket label="ADD" path={[152, 82, 220, 82, 300, 82, 364, 
82]} progress={(time - 1.4) / 2.4} width={36} />
+            <MovingPacket label="ROW" path={[152, 82, 220, 82, 300, 82, 364, 
82]} progress={(time - 5) / 1.1} tone="teal" width={38} />
         </svg>
     );
 }
 
-function CheckpointDiagram() {
+function CheckpointDiagram({time}) {
+    const isComplete = time >= 2.7;
+    const isRestoring = time >= 4.65 && time < 5.55;
+    const isWriterLost = time >= 3.65 && time < 4.65;
+    const signalProgress = time < 1.1 ? time / 0.9 : (time - 5.55) / 0.9;
+    const showSignals = time < 1.1 || (time >= 5.55 && time < 7.4);
+
     return (
         <svg viewBox="0 0 520 166" className="st-home-motion-diagram" 
aria-hidden="true">
-            <path d="M122 70h70M327 70h70" 
className="st-home-motion-diagram-line st-home-motion-diagram-flow" fill="none" 
/><Arrow x="192" y="70" /><Arrow x="397" y="70" />
+            <path d="M122 70h70M327 70h70" 
className="st-home-motion-diagram-line" fill="none" /><Arrow x="192" y="70" 
/><Arrow x="397" y="70" />
             {['Source', 'Writer', 'Sink'].map((label, index) => {
                 const x = [16, 198, 403][index];
-                return <g key={label}><rect x={x} y="47" width="104" 
height="43" rx="10" className="st-home-motion-diagram-node" /><text x={x + 52} 
y="74" textAnchor="middle" 
className="st-home-motion-diagram-strong">{label}</text></g>;
+                const isLostWriter = label === 'Writer' && isWriterLost;
+                return <g key={label}><rect x={x} y="47" width="104" 
height="43" rx="10" className={isLostWriter ? 'st-home-motion-diagram-blue' : 
'st-home-motion-diagram-node'} /><text x={x + 52} y="74" textAnchor="middle" 
className={isLostWriter ? 'st-home-motion-diagram-on-blue' : 
'st-home-motion-diagram-strong'}>{isLostWriter ? 'Worker lost' : 
label}</text></g>;
             })}
-            <rect x="232" y="20" width="70" height="21" rx="10" 
className="st-home-motion-diagram-soft" /><text x="267" y="35" 
textAnchor="middle" 
className="st-home-motion-diagram-accent-text">BARRIER</text>
-            <path d="M68 91C68 130 157 137 215 137M250 91v25" 
className="st-home-motion-diagram-line st-home-motion-diagram-flow 
st-home-motion-diagram-flow-teal" fill="none" />
-            <rect x="215" y="114" width="134" height="38" rx="9" 
className="st-home-motion-diagram-node" />
-            <text x="282" y="135" textAnchor="middle" 
className="st-home-motion-diagram-copy">Checkpoint state</text>
-            <text x="282" y="147" textAnchor="middle" 
className="st-home-motion-diagram-label">COMPLETED</text>
+            <path d="M68 91C68 130 157 137 215 137M250 91v25" 
className="st-home-motion-diagram-line" fill="none" />
+            <rect x="215" y="114" width="134" height="38" rx="9" 
className={isComplete ? 'st-home-motion-diagram-soft' : 
'st-home-motion-diagram-node'} />
+            <text x="282" y="135" textAnchor="middle" 
className="st-home-motion-diagram-copy">{isComplete ? 'Checkpoint #42' : 
'Checkpoint state'}</text>
+            <text x="282" y="147" textAnchor="middle" className={isComplete ? 
'st-home-motion-diagram-teal-copy' : 
'st-home-motion-diagram-label'}>{isComplete ? 'COMPLETED' : 'PENDING'}</text>
+            {showSignals ? <>
+                {[0, 1].map((offset) => <React.Fragment key={offset}>
+                    <MovingPacket path={[122, 70, 145, 70, 170, 70, 192, 70]} 
progress={signalProgress - offset * 0.48} tone="teal" />
+                    <MovingPacket path={[327, 70, 350, 70, 375, 70, 397, 70]} 
progress={signalProgress - offset * 0.48 + 0.2} tone="teal" />
+                </React.Fragment>)}
+            </> : null}
+            <MovingPacket label="BARRIER" path={[93, 30, 225, 30, 350, 30, 
488, 30]} progress={(time - 1.1) / 1.6} width={54} />
+            {isRestoring ? <MovingPacket path={[215, 137, 165, 137, 112, 121, 
68, 91]} progress={(time - 4.65) / 0.9} /> : null}
         </svg>
     );
 }
 
-function MotionDiagram({type}) {
+function MotionDiagram({type, time}) {
     const diagrams = {
-        parallel: <ParallelReadsDiagram />,
-        cdc: <SnapshotCdcDiagram />,
-        transform: <TransformDiagram />,
-        routing: <RoutingDiagram />,
-        schema: <SchemaDiagram />,
-        checkpoint: <CheckpointDiagram />,
+        parallel: <ParallelReadsDiagram time={time} />,
+        cdc: <SnapshotCdcDiagram time={time} />,
+        transform: <TransformDiagram time={time} />,
+        routing: <RoutingDiagram time={time} />,
+        schema: <SchemaDiagram time={time} />,
+        checkpoint: <CheckpointDiagram time={time} />,
     };
 
     return diagrams[type];
@@ -200,6 +383,8 @@ function MotionDiagram({type}) {
 
 export default function MotionMechanismGrid({caption, controls, gridLabel, 
mechanisms}) {
     const [isMotionPaused, setIsMotionPaused] = React.useState(false);
+    const prefersReducedMotion = usePrefersReducedMotion();
+    const time = useMotionClock(isMotionPaused || prefersReducedMotion);
 
     return (
         <>
@@ -208,12 +393,13 @@ export default function MotionMechanismGrid({caption, 
controls, gridLabel, mecha
                     type="button"
                     className="st-home-motion-control"
                     aria-pressed={isMotionPaused}
+                    disabled={prefersReducedMotion}
                     onClick={() => setIsMotionPaused((paused) => !paused)}
                 >
                     {isMotionPaused ? controls.play : controls.pause}
                 </button>
             </div>
-            <ul className={`st-home-motion-grid${isMotionPaused ? ' 
is-motion-paused' : ''}`} aria-label={gridLabel}>
+            <ul className="st-home-motion-grid" aria-label={gridLabel}>
                 {mechanisms.map((mechanism, index) => (
                     <li key={mechanism.title} className={`st-home-motion-card 
st-home-motion-card-${mechanism.diagram}`}>
                         <article>
@@ -224,7 +410,7 @@ export default function MotionMechanismGrid({caption, 
controls, gridLabel, mecha
                                     <p>{mechanism.description}</p>
                                 </div>
                             </div>
-                            <div 
className="st-home-motion-visual"><MotionDiagram type={mechanism.diagram} 
/></div>
+                            <div 
className="st-home-motion-visual"><MotionDiagram type={mechanism.diagram} 
time={time} /></div>
                             <p 
className="st-home-motion-card-detail">{mechanism.detail}</p>
                             <p 
className="st-home-motion-card-note">{mechanism.note}</p>
                         </article>
diff --git a/src/pages/home/index.less b/src/pages/home/index.less
index 7c8aa0daf96f..ee2c34124b7d 100644
--- a/src/pages/home/index.less
+++ b/src/pages/home/index.less
@@ -1047,6 +1047,11 @@ html[data-theme='light'] .st-home {
   outline-offset: 2px;
 }
 
+.st-home-motion-control:disabled {
+  cursor: default;
+  opacity: 0.58;
+}
+
 .st-home-motion-card {
   min-width: 0;
   border: 1px solid var(--motion-line);
@@ -1208,26 +1213,6 @@ html[data-theme='light'] .st-home {
   stroke-width: 1.6;
 }
 
-.st-home-motion-diagram-flow {
-  stroke: var(--motion-blue);
-  stroke-dasharray: 3 10;
-  stroke-dashoffset: 0;
-  animation: st-home-motion-flow 1.8s linear infinite;
-}
-
-.st-home-motion-diagram-flow-teal {
-  stroke: var(--motion-teal);
-  animation-delay: -0.6s;
-}
-
-.st-home-motion-card:nth-child(even) .st-home-motion-diagram-flow {
-  animation-delay: -0.9s;
-}
-
-.st-home-motion-card:nth-child(3n) .st-home-motion-diagram-flow {
-  animation-delay: -1.35s;
-}
-
 .st-home-motion-diagram-soft,
 .st-home-motion-diagram-chip {
   fill: var(--motion-soft);
@@ -1236,16 +1221,10 @@ html[data-theme='light'] .st-home {
 .st-home-motion-diagram-packet,
 .st-home-motion-diagram-blue {
   fill: var(--motion-blue);
-  transform-box: fill-box;
-  transform-origin: center;
-  animation: st-home-motion-pulse 2.4s ease-in-out infinite;
 }
 
 .st-home-motion-diagram-teal {
   fill: var(--motion-teal);
-  transform-box: fill-box;
-  transform-origin: center;
-  animation: st-home-motion-pulse 2.4s ease-in-out infinite -0.8s;
 }
 
 .st-home-motion-diagram-blue-fill {
@@ -1264,20 +1243,34 @@ html[data-theme='light'] .st-home {
 
 .st-home-motion-diagram-highlight {
   fill: var(--motion-highlight);
-  animation: st-home-motion-highlight 2.4s ease-in-out infinite;
-}
-
-.st-home-motion-card:hover .st-home-motion-diagram-flow,
-.st-home-motion-card:hover .st-home-motion-diagram-packet,
-.st-home-motion-card:hover .st-home-motion-diagram-blue,
-.st-home-motion-card:hover .st-home-motion-diagram-teal,
-.st-home-motion-card:hover .st-home-motion-diagram-highlight,
-.st-home-motion-grid.is-motion-paused .st-home-motion-diagram-flow,
-.st-home-motion-grid.is-motion-paused .st-home-motion-diagram-packet,
-.st-home-motion-grid.is-motion-paused .st-home-motion-diagram-blue,
-.st-home-motion-grid.is-motion-paused .st-home-motion-diagram-teal,
-.st-home-motion-grid.is-motion-paused .st-home-motion-diagram-highlight {
-  animation-play-state: paused;
+}
+
+.st-home-motion-diagram-moving-packet-blue,
+.st-home-motion-diagram-moving-signal-blue 
.st-home-motion-diagram-moving-signal {
+  fill: var(--motion-blue);
+}
+
+.st-home-motion-diagram-moving-packet-teal,
+.st-home-motion-diagram-moving-signal-teal 
.st-home-motion-diagram-moving-signal {
+  fill: var(--motion-teal);
+}
+
+.st-home-motion-diagram-moving-signal-core {
+  fill: var(--motion-node);
+}
+
+.st-home-motion-diagram-moving-packet-copy-blue,
+.st-home-motion-diagram-moving-packet-copy-teal {
+  font-size: 10px;
+  font-weight: 700;
+}
+
+.st-home-motion-diagram-moving-packet-copy-blue {
+  fill: var(--motion-on-blue);
+}
+
+.st-home-motion-diagram-moving-packet-copy-teal {
+  fill: var(--motion-on-teal);
 }
 
 .st-home-motion-card-detail {
@@ -3191,36 +3184,6 @@ html[data-theme='light'] .st-home .st-home-rv {
   }
 }
 
-@keyframes st-home-motion-flow {
-  to {
-    stroke-dashoffset: -52;
-  }
-}
-
-@keyframes st-home-motion-pulse {
-  0%,
-  100% {
-    opacity: 1;
-    transform: scale(0.96);
-  }
-
-  50% {
-    opacity: 1;
-    transform: scale(1);
-  }
-}
-
-@keyframes st-home-motion-highlight {
-  0%,
-  100% {
-    opacity: 0.68;
-  }
-
-  50% {
-    opacity: 1;
-  }
-}
-
 @keyframes st-home-flow {
   from {
     left: -60%;

Reply via email to