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 38ffb7ec9e4a [Docs] Match homepage motion states (#542)
38ffb7ec9e4a is described below

commit 38ffb7ec9e4a3ffe67135ccb6691e6f31608125e
Author: Daniel <[email protected]>
AuthorDate: Wed Sep 23 03:27:29 2026 -0400

    [Docs] Match homepage motion states (#542)
---
 src/components/home/MotionMechanismGrid.jsx | 357 +++++++++++++++++++++-------
 src/pages/home/index.jsx                    |  98 ++++++--
 src/pages/home/index.less                   | 143 ++++++++++-
 3 files changed, 485 insertions(+), 113 deletions(-)

diff --git a/src/components/home/MotionMechanismGrid.jsx 
b/src/components/home/MotionMechanismGrid.jsx
index 5c2025b2c5dd..a694967a111b 100644
--- a/src/components/home/MotionMechanismGrid.jsx
+++ b/src/components/home/MotionMechanismGrid.jsx
@@ -18,12 +18,16 @@
 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 FOCUS_CYCLE_SECONDS = 24;
+const LOOP_TRANSITION_START_SECONDS = 7.5;
+const PARALLEL_SPLITS = [
+    {id: 'S1', owner: 2, start: 0.35},
+    {id: 'S2', owner: 0, start: 0.97},
+    {id: 'S3', owner: 1, start: 1.59},
+    {id: 'S4', owner: 2, start: 2.21},
+    {id: 'S5', owner: 0, start: 2.83},
+    {id: 'S6', owner: 1, start: 3.45},
+];
 const CDC_EVENTS = [
     {start: 0.4, op: 'S', id: 41, value: 'NEW'},
     {start: 1.45, op: 'S', id: 42, value: 'NEW'},
@@ -36,6 +40,62 @@ const TRANSFORMS = [
     {id: 0, name: 'Temp', start: 2.35},
     {id: 2, name: 'Lin', start: 4.25},
 ];
+const DEFAULT_DIAGRAM_LABELS = {
+    barrier: 'BARRIER',
+    binlog: 'BINLOG',
+    checkpoint: 'Checkpoint',
+    checkpointState: 'Checkpoint state',
+    completed: 'COMPLETED',
+    customers: 'customers',
+    input: 'INPUT',
+    fieldId: 'id',
+    fieldStatus: 'status',
+    jdbcMysqlSink: 'JDBC MySQL sink',
+    mysqlOrders: 'MySQL / orders',
+    mysqlSource: 'MySQL source',
+    output: 'OUTPUT',
+    pending: 'PENDING',
+    reader: 'Reader',
+    readers: 'READERS',
+    route: 'route',
+    routeTables: ['users', 'orders', 'items'],
+    rowSql: 'ROW SQL',
+    sink: 'Sink',
+    snapshot: 'SNAPSHOT',
+    source: 'Source',
+    sourceSplits: 'SOURCE SPLITS',
+    table: 'Table',
+    target: 'TARGET',
+    targetOrders: 'Target / orders',
+    waiting: 'waiting…',
+    workerLost: 'Worker lost',
+    writer: 'Writer',
+    values: {
+        NEW: 'NEW',
+        PAID: 'PAID',
+    },
+    applied: 'applied',
+    filtered: 'id = 0 filtered',
+    region: 'region',
+    status: {
+        cdcDeleteArrived: 'DELETE · row 41 is removed.',
+        cdcDeleteStarted: 'DELETE · remove row 41',
+        cdcInsert: 'INSERT · new row 43',
+        cdcSnapshot: 'Read existing rows.',
+        cdcUpdate: 'UPDATE · row 42 becomes PAID',
+        checkpointBarrier: 'Capture state with a checkpoint barrier.',
+        checkpointComplete: 'Checkpoint #42 completed and retained.',
+        checkpointFailure: 'A worker fails after checkpoint #42.',
+        checkpointRestore: 'Restore saved source state from #42.',
+        checkpointRunning: 'Running · source and sink connected.',
+        checkpointResumed: 'Resumed · replay after the saved position.',
+        parallelComplete: '6 illustrated splits delivered.',
+        parallelRunning: 'Assigned splits move through 3 readers.',
+        routing: 'Table ID → matching destination writer.',
+        schema: 'schema-changes.enabled = true',
+        transform: "WHERE id > 0 · CONCAT(name, '_')",
+    },
+};
 
 function cubicPoint(progress, path) {
     const clampedProgress = Math.min(1, Math.max(0, progress));
@@ -71,49 +131,58 @@ function usePrefersReducedMotion() {
 }
 
 function useMotionClock(isPaused) {
-    const [time, setTime] = React.useState(0);
-    const timeRef = React.useRef(0);
+    const [clock, setClock] = React.useState({activeIndex: 0, activeProgress: 
0, time: 0});
+    const elapsedTimeRef = React.useRef(0);
+    const isPausedRef = React.useRef(isPaused);
 
     React.useEffect(() => {
-        timeRef.current = time;
-    }, [time]);
+        isPausedRef.current = isPaused;
+    }, [isPaused]);
 
     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;
+            if (!isPausedRef.current) {
+                elapsedTimeRef.current += (now - previousFrame) / 1000;
+                const focusTime = elapsedTimeRef.current % FOCUS_CYCLE_SECONDS;
+                setClock({
+                    activeIndex: Math.floor(focusTime / 4),
+                    activeProgress: (focusTime % 4) / 4,
+                    time: elapsedTimeRef.current % MOTION_CYCLE_SECONDS,
+                });
             }
+            previousFrame = now;
 
+            // Keep the frame reference current while paused so playback 
resumes without a time jump.
             animationFrame = window.requestAnimationFrame(tick);
         };
 
         animationFrame = window.requestAnimationFrame(tick);
         return () => window.cancelAnimationFrame(animationFrame);
-    }, [isPaused]);
+    }, []);
 
-    return time;
+    return clock;
+}
+
+function smooth(progress) {
+    const clampedProgress = Math.min(1, Math.max(0, progress));
+    return clampedProgress * clampedProgress * (3 - 2 * clampedProgress);
 }
 
 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 CheckMark({x, y}) {
+    return <path d={`M${x - 5} ${y}l4 4 8-9`} 
className="st-home-motion-diagram-check" fill="none" />;
+}
+
 function MovingPacket({label = '', path, progress, tone = 'blue', width = 30}) 
{
     if (progress < 0 || progress > 1) {
         return null;
@@ -155,13 +224,17 @@ function cdcRows(time, isTarget) {
     return [...rows.entries()];
 }
 
-function CdcTable({x, rows}) {
+function CdcTable({labels, 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>)}
+            <text x={x + 13} y="48" 
className="st-home-motion-diagram-label">{labels.fieldId}</text><text x={x + 
60} y="48" className="st-home-motion-diagram-label">{labels.fieldStatus}</text>
+            {rows.length === 0 ? <text x={x + 13} y="77" 
className="st-home-motion-diagram-muted-copy">{labels.waiting}</text> : 
rows.map(([id, value], index) => {
+                const y = 77 + index * 26;
+                const isPaid = value === 'PAID';
+                return <g key={id}>{isPaid ? <rect x={x + 5} y={y - 17} 
width="126" height="23" rx="5" className="st-home-motion-diagram-highlight" /> 
: null}<text x={x + 13} y={y} className={isPaid ? 
'st-home-motion-diagram-teal-copy' : 
'st-home-motion-diagram-copy'}>{id}</text><text x={x + 60} y={y} 
className={isPaid ? 'st-home-motion-diagram-teal-copy' : 
'st-home-motion-diagram-copy'}>{labels.values[value]}</text></g>;
+            })}
         </>
     );
 }
@@ -170,42 +243,47 @@ function transformedRows(time) {
     return TRANSFORMS.filter((transform) => transform.id > 0 && time >= 
transform.start + 1.65);
 }
 
-function ParallelReadsDiagram({time}) {
+function ParallelReadsDiagram({labels, time}) {
     const readerYPositions = [54, 87, 120];
+    const readerTones = ['blue', 'teal', 'dark'];
+    const completedSplitCount = PARALLEL_SPLITS.filter((split) => time >= 
split.start + 2.5).length;
 
     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>
+            <text x="18" y="20" 
className="st-home-motion-diagram-label">{labels.sourceSplits}</text>
+            <text x="207" y="20" 
className="st-home-motion-diagram-label">{labels.readers}</text>
+            <text x="438" y="20" 
className="st-home-motion-diagram-label">{labels.target}</text>
             <rect x="16" y="32" width="118" height="112" rx="10" 
className="st-home-motion-diagram-node" />
             {PARALLEL_SPLITS.map((split, index) => {
                 const x = 28 + (index % 2) * 49;
                 const y = 45 + Math.floor(index / 2) * 31;
                 const isWaiting = time < split.start;
+                const tone = readerTones[split.owner];
                 return (
                     <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>
+                        <rect x={x} y={y} width="39" height="22" rx="5" 
className={isWaiting ? 'st-home-motion-diagram-chip' : 
'st-home-motion-diagram-stage'} />
+                        <text x={x + 19.5} y={y + 15} textAnchor="middle" 
className={isWaiting ? `st-home-motion-diagram-chip-text-${tone}` : 
'st-home-motion-diagram-muted-copy'}>{split.id}</text>
                     </g>
                 );
             })}
             {readerYPositions.map((y, index) => (
                 <g key={y}>
                     <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>
+                    <rect x="205" y={y - 13} width="121" height="26" rx="7" 
className={`st-home-motion-diagram-node 
st-home-motion-diagram-reader-${readerTones[index]}`} />
+                    <circle cx="220" cy={y} r="3.5" 
className={`st-home-motion-diagram-${readerTones[index]}-fill`} />
+                    <text x="232" y={y + 5} 
className="st-home-motion-diagram-copy">{labels.reader} {index + 1}</text>
                     <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" />
+            <ellipse cx="466" cy="67" rx="27" ry="7" 
className="st-home-motion-diagram-database" />
+            <path d="M439 67v43c0 9 54 9 54 0V67" 
className="st-home-motion-diagram-database" />
             <path d="M439 89c0 9 54 9 54 0M439 110c0 9 54 9 54 0" 
className="st-home-motion-diagram-line" fill="none" />
+            <rect x="431" y="139" width="70" height="18" rx="9" 
className="st-home-motion-diagram-soft" />
+            <text x="466" y="152" textAnchor="middle" 
className="st-home-motion-diagram-accent-text">{completedSplitCount} / 6</text>
             {PARALLEL_SPLITS.map((split) => {
                 const readerY = readerYPositions[split.owner];
-                const tone = split.owner === 1 ? 'teal' : 'blue';
+                const tone = readerTones[split.owner];
                 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} />
@@ -217,21 +295,21 @@ function ParallelReadsDiagram({time}) {
     );
 }
 
-function SnapshotCdcDiagram({time}) {
+function SnapshotCdcDiagram({labels, 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>
-            <CdcTable x={16} rows={sourceRows} />
+            <text x="18" y="20" 
className="st-home-motion-diagram-label">{labels.mysqlOrders}</text>
+            <text x="373" y="20" 
className="st-home-motion-diagram-label">{labels.targetOrders}</text>
+            <CdcTable labels={labels} 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">{time < 2.8 ? 'SNAPSHOT' : 
'BINLOG'}</text>
+            <text x="250" y="40" textAnchor="middle" 
className="st-home-motion-diagram-accent-text">{time < 2.8 ? labels.snapshot : 
labels.binlog}</text>
             <path d="M153 82H367" className="st-home-motion-diagram-line" 
fill="none" />
             <Arrow x="367" y="82" />
             <text x="260" y="116" textAnchor="middle" 
className="st-home-motion-diagram-copy">SeaTunnel</text>
-            <CdcTable x={370} rows={targetRows} />
+            <CdcTable labels={labels} 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} />
             ))}
@@ -239,50 +317,52 @@ function SnapshotCdcDiagram({time}) {
     );
 }
 
-function TransformDiagram({time}) {
+function TransformDiagram({labels, time}) {
     const outputRows = transformedRows(time);
+    const isFiltered = time >= 3;
 
     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>
-            <text x="218" y="20" className="st-home-motion-diagram-label">ROW 
SQL</text>
-            <text x="421" y="20" 
className="st-home-motion-diagram-label">OUTPUT</text>
+            <text x="18" y="20" 
className="st-home-motion-diagram-label">{labels.input}</text>
+            <text x="218" y="20" 
className="st-home-motion-diagram-label">{labels.rowSql}</text>
+            <text x="421" y="20" 
className="st-home-motion-diagram-label">{labels.output}</text>
             <rect x="16" y="31" width="125" height="91" rx="9" 
className="st-home-motion-diagram-node" />
             <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="82" 
className="st-home-motion-diagram-muted-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" 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>
+            <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" />
-            {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>
+            {outputRows.length === 0 ? <text x="413" y="77" 
className="st-home-motion-diagram-muted-copy">{labels.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>)}
+            {isFiltered ? <><circle cx="261" cy="144" r="7" 
className="st-home-motion-diagram-filter-icon" /><path d="M257 148l8-8" 
className="st-home-motion-diagram-filter-mark" fill="none" /><text x="278" 
y="148" 
className="st-home-motion-diagram-muted-copy">{labels.filtered}</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} />}
+                    {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} tone="dark" width={22} />}
                 </React.Fragment>
             ))}
         </svg>
     );
 }
 
-function RoutingDiagram({time}) {
-    const rows = ['users', 'orders', 'items'];
+function RoutingDiagram({labels, time}) {
+    const rows = labels.routeTables;
+    const rowTones = ['blue', 'teal', 'dark'];
     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 tone = rowTones[index];
                 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" />
                         <rect x="377" y={y} width="127" height="30" rx="7" 
className="st-home-motion-diagram-node" />
-                        <rect x="28" y={y + 8} width="16" height="14" rx="2" 
className="st-home-motion-diagram-table" />
-                        <rect x="389" y={y + 8} width="16" height="14" rx="2" 
className="st-home-motion-diagram-table" />
+                        <rect x="28" y={y + 8} width="16" height="14" rx="2" 
className={`st-home-motion-diagram-table st-home-motion-diagram-table-${tone}`} 
/>
+                        <rect x="389" y={y + 8} width="16" height="14" rx="2" 
className={`st-home-motion-diagram-table st-home-motion-diagram-table-${tone}`} 
/>
                         <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" fill="none" />
@@ -301,23 +381,23 @@ function RoutingDiagram({time}) {
                 );
             })}
             <rect x="221" y="52" width="78" height="61" rx="12" 
className="st-home-motion-diagram-blue" />
-            <text x="260" y="78" textAnchor="middle" 
className="st-home-motion-diagram-on-blue">Table</text>
-            <text x="260" y="98" textAnchor="middle" 
className="st-home-motion-diagram-on-blue">route</text>
+            <text x="260" y="78" textAnchor="middle" 
className="st-home-motion-diagram-on-blue">{labels.table}</text>
+            <text x="260" y="98" textAnchor="middle" 
className="st-home-motion-diagram-on-blue">{labels.route}</text>
         </svg>
     );
 }
 
-function SchemaDiagram({time}) {
+function SchemaDiagram({labels, 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>
-            <text x="371" y="20" className="st-home-motion-diagram-label">JDBC 
MYSQL SINK</text>
+            <text x="18" y="20" 
className="st-home-motion-diagram-label">{labels.mysqlSource}</text>
+            <text x="371" y="20" 
className="st-home-motion-diagram-label">{labels.jdbcMysqlSink}</text>
             {[16, 369].map((x) => <rect key={x} x={x} y="31" width="135" 
height="104" rx="9" className="st-home-motion-diagram-node" />)}
-            {[16, 369].map((x) => <g key={x}><rect x={x + 1} y="32" 
width="133" height="24" rx="7" className="st-home-motion-diagram-soft" /><text 
x={x + 14} y="48" className="st-home-motion-diagram-copy">▦&nbsp; 
customers</text></g>)}
+            {[16, 369].map((x) => <g key={x}><rect x={x + 1} y="32" 
width="133" height="24" rx="7" className="st-home-motion-diagram-soft" /><text 
x={x + 14} y="48" className="st-home-motion-diagram-copy">▦&nbsp; 
{labels.customers}</text></g>)}
             {rows.map(([name, type], index) => {
                 const y = 76 + index * 24;
                 return (
@@ -333,11 +413,14 @@ function SchemaDiagram({time}) {
             <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>
             <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} />
+            {sourceColumnCount === 3 ? <CheckMark x="144" y="25" /> : null}
+            {sinkColumnCount === 3 ? <CheckMark x="497" y="25" /> : null}
+            <text x="260" y="151" textAnchor="middle" 
className={sinkColumnCount === 3 ? 'st-home-motion-diagram-teal-copy' : 
'st-home-motion-diagram-muted-copy'}>{sinkColumnCount === 3 ? labels.applied : 
labels.region}</text>
         </svg>
     );
 }
 
-function CheckpointDiagram({time}) {
+function CheckpointDiagram({labels, time}) {
     const isComplete = time >= 2.7;
     const isRestoring = time >= 4.65 && time < 5.55;
     const isWriterLost = time >= 3.65 && time < 4.65;
@@ -347,44 +430,145 @@ function CheckpointDiagram({time}) {
     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" fill="none" /><Arrow x="192" y="70" 
/><Arrow x="397" y="70" />
-            {['Source', 'Writer', 'Sink'].map((label, index) => {
+            {[labels.source, labels.writer, labels.sink].map((label, index) => 
{
                 const x = [16, 198, 403][index];
-                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>;
+                const isLostWriter = index === 1 && 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 ? labels.workerLost : 
label}</text></g>;
             })}
             <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>
+            <text x="282" y="135" textAnchor="middle" 
className="st-home-motion-diagram-copy">{isComplete ? `${labels.checkpoint} 
#42` : labels.checkpointState}</text>
+            <text x="282" y="147" textAnchor="middle" className={isComplete ? 
'st-home-motion-diagram-teal-copy' : 
'st-home-motion-diagram-label'}>{isComplete ? labels.completed : 
labels.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} />
+            {time >= 1.1 && time < 2.7 ? (() => {
+                const barrierX = 93 + (488 - 93) * ((time - 1.1) / 1.6);
+                return <g><path d={`M${barrierX} 20V112`} 
className="st-home-motion-diagram-barrier" fill="none" /><rect x={barrierX - 
29} y="4" width="58" height="18" rx="9" className="st-home-motion-diagram-soft" 
/><text x={barrierX} y="17" textAnchor="middle" 
className="st-home-motion-diagram-accent-text">{labels.barrier}</text></g>;
+            })() : null}
             {isRestoring ? <MovingPacket path={[215, 137, 165, 137, 112, 121, 
68, 91]} progress={(time - 4.65) / 0.9} /> : null}
+            {isComplete ? <CheckMark x="338" y="129" /> : null}
         </svg>
     );
 }
 
-function MotionDiagram({type, time}) {
+function renderMotionDiagram(labels, type, time) {
     const diagrams = {
-        parallel: <ParallelReadsDiagram time={time} />,
-        cdc: <SnapshotCdcDiagram time={time} />,
-        transform: <TransformDiagram time={time} />,
-        routing: <RoutingDiagram time={time} />,
-        schema: <SchemaDiagram time={time} />,
-        checkpoint: <CheckpointDiagram time={time} />,
+        parallel: <ParallelReadsDiagram labels={labels} time={time} />,
+        cdc: <SnapshotCdcDiagram labels={labels} time={time} />,
+        transform: <TransformDiagram labels={labels} time={time} />,
+        routing: <RoutingDiagram labels={labels} time={time} />,
+        schema: <SchemaDiagram labels={labels} time={time} />,
+        checkpoint: <CheckpointDiagram labels={labels} time={time} />,
     };
 
     return diagrams[type];
 }
 
-export default function MotionMechanismGrid({caption, controls, gridLabel, 
mechanisms}) {
+function MotionDiagram({labels, type, time}) {
+    if (time < LOOP_TRANSITION_START_SECONDS) {
+        return renderMotionDiagram(labels, type, time);
+    }
+
+    const blend = smooth((time - LOOP_TRANSITION_START_SECONDS) / 
(MOTION_CYCLE_SECONDS - LOOP_TRANSITION_START_SECONDS));
+    return (
+        <div className="st-home-motion-diagram-transition">
+            <div className="st-home-motion-diagram-transition-frame" 
style={{opacity: 1 - blend}}>{renderMotionDiagram(labels, type, 7.49)}</div>
+            <div className="st-home-motion-diagram-transition-frame" 
style={{opacity: blend}}>{renderMotionDiagram(labels, type, 0)}</div>
+        </div>
+    );
+}
+
+function getMotionStatus(labels, time, type) {
+    let status = labels.status[type];
+    let tone = 'blue';
+
+    if (type === 'parallel') {
+        const isComplete = PARALLEL_SPLITS.every((split) => time >= 
split.start + 2.5);
+        status = isComplete ? labels.status.parallelComplete : 
labels.status.parallelRunning;
+        tone = isComplete ? 'teal' : tone;
+    } else if (type === 'cdc') {
+        if (time < 2.8) {
+            status = labels.status.cdcSnapshot;
+        } else if (time < 4.1) {
+            status = labels.status.cdcInsert;
+        } else if (time < 5.55) {
+            status = labels.status.cdcUpdate;
+        } else if (time < 6.45) {
+            status = labels.status.cdcDeleteStarted;
+        } else {
+            status = labels.status.cdcDeleteArrived;
+            tone = 'teal';
+        }
+    } else if (type === 'routing' || type === 'schema' || type === 
'transform') {
+        status = labels.status[type];
+        tone = type === 'schema' && time >= 3.8 ? 'teal' : tone;
+    } else if (type === 'checkpoint') {
+        if (time < 1.1) {
+            status = labels.status.checkpointRunning;
+        } else if (time < 2.7) {
+            status = labels.status.checkpointBarrier;
+        } else if (time < 3.65) {
+            status = labels.status.checkpointComplete;
+        } else if (time < 4.65) {
+            status = labels.status.checkpointFailure;
+        } else if (time < 5.55) {
+            status = labels.status.checkpointRestore;
+        } else {
+            status = labels.status.checkpointResumed;
+            tone = 'teal';
+        }
+    }
+
+    return {status, tone};
+}
+
+function MotionStatus({labels, time, type}) {
+    const renderStatus = (frameTime) => {
+        const {status, tone} = getMotionStatus(labels, frameTime, type);
+        return <p className={`st-home-motion-card-detail 
st-home-motion-card-detail-${tone}`}>{status}</p>;
+    };
+
+    if (time < LOOP_TRANSITION_START_SECONDS) {
+        return renderStatus(time);
+    }
+
+    const blend = smooth((time - LOOP_TRANSITION_START_SECONDS) / 
(MOTION_CYCLE_SECONDS - LOOP_TRANSITION_START_SECONDS));
+    return (
+        <div className="st-home-motion-status-transition">
+            <div style={{opacity: 1 - blend}}>{renderStatus(7.49)}</div>
+            <div className="st-home-motion-status-transition-frame" 
style={{opacity: blend}}>{renderStatus(0)}</div>
+        </div>
+    );
+}
+
+export default function MotionMechanismGrid({caption, controls, gridLabel, 
labels = DEFAULT_DIAGRAM_LABELS, mechanisms}) {
     const [isMotionPaused, setIsMotionPaused] = React.useState(false);
     const prefersReducedMotion = usePrefersReducedMotion();
-    const time = useMotionClock(isMotionPaused || prefersReducedMotion);
+    const {activeIndex, activeProgress, time} = useMotionClock(isMotionPaused);
+
+    React.useEffect(() => {
+        if (prefersReducedMotion) {
+            setIsMotionPaused(true);
+        }
+    }, [prefersReducedMotion]);
+
+    React.useEffect(() => {
+        const onKeyDown = (event) => {
+            if (event.code !== 'Space' || ['INPUT', 'SELECT', 
'TEXTAREA'].includes(event.target.tagName)) {
+                return;
+            }
+
+            event.preventDefault();
+            setIsMotionPaused((paused) => !paused);
+        };
+
+        window.addEventListener('keydown', onKeyDown);
+        return () => window.removeEventListener('keydown', onKeyDown);
+    }, []);
 
     return (
         <>
@@ -393,7 +577,7 @@ export default function MotionMechanismGrid({caption, 
controls, gridLabel, mecha
                     type="button"
                     className="st-home-motion-control"
                     aria-pressed={isMotionPaused}
-                    disabled={prefersReducedMotion}
+                    aria-keyshortcuts="Space"
                     onClick={() => setIsMotionPaused((paused) => !paused)}
                 >
                     {isMotionPaused ? controls.play : controls.pause}
@@ -401,7 +585,11 @@ export default function MotionMechanismGrid({caption, 
controls, gridLabel, mecha
             </div>
             <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}`}>
+                    <li
+                        key={mechanism.title}
+                        className={`st-home-motion-card 
st-home-motion-card-${mechanism.diagram}${index === activeIndex ? ' is-active' 
: ''}`}
+                        style={index === activeIndex ? 
{'--motion-active-opacity': 0.48 + 0.42 * Math.sin(Math.PI * activeProgress), 
'--motion-active-progress': activeProgress} : undefined}
+                    >
                         <article>
                             <div className="st-home-motion-card-heading">
                                 <span>{String(index + 1).padStart(2, 
'0')}</span>
@@ -410,9 +598,10 @@ export default function MotionMechanismGrid({caption, 
controls, gridLabel, mecha
                                     <p>{mechanism.description}</p>
                                 </div>
                             </div>
-                            <div 
className="st-home-motion-visual"><MotionDiagram type={mechanism.diagram} 
time={time} /></div>
-                            <p 
className="st-home-motion-card-detail">{mechanism.detail}</p>
+                            <div 
className="st-home-motion-visual"><MotionDiagram labels={labels} 
type={mechanism.diagram} time={time} /></div>
+                            <MotionStatus labels={labels} time={time} 
type={mechanism.diagram} />
                             <p 
className="st-home-motion-card-note">{mechanism.note}</p>
+                            {index === activeIndex ? <span 
className="st-home-motion-card-progress" aria-hidden="true" /> : null}
                         </article>
                     </li>
                 ))}
diff --git a/src/pages/home/index.jsx b/src/pages/home/index.jsx
index 32868dfb5088..bf31abffc18e 100644
--- a/src/pages/home/index.jsx
+++ b/src/pages/home/index.jsx
@@ -262,7 +262,7 @@ const HOME_COPY = {
         },
         motion: {
             title: 'Data integration, in motion.',
-            lead: 'Six mechanisms that keep data moving through parallel 
workloads, continuous change, and recoverable failures.',
+            lead: 'Six mechanisms behind the data integration layer.',
             gridLabel: 'SeaTunnel data integration mechanisms',
             controls: {
                 label: 'Motion controls',
@@ -271,12 +271,12 @@ const HOME_COPY = {
             },
             caption: 'Illustrative flows—not a deployment blueprint. 
Production behavior depends on the selected connector, engine, and 
configuration.',
             mechanisms: [
-                {diagram: 'parallel', title: 'Parallel batch reads', 
description: 'Divide the work. Read independent splits in parallel.', detail: 
'Independent readers process assigned source splits.', note: 'JDBC example · 
Split assignment is source-specific.'},
-                {diagram: 'cdc', title: 'Snapshot + CDC', description: 
'Bootstrap a snapshot, then keep changes flowing.', detail: 'Capture existing 
rows before streaming changes.', note: 'MySQL-CDC example · startup.mode = 
initial.'},
-                {diagram: 'transform', title: 'Row-level transforms', 
description: 'Shape, filter, and enrich records before they land.', detail: 
'Apply row expressions and filters in the pipeline.', note: 'SQL example · 
expression + row filter.'},
-                {diagram: 'routing', title: 'Multi-table routing', 
description: 'Keep every record on its intended table path.', detail: 'Table 
identity selects the matching destination writer.', note: 'Logical routing · 
Physical connections may differ.'},
-                {diagram: 'schema', title: 'Schema evolution', description: 
'Apply supported table changes before new data moves downstream.', detail: 
'Propagate an added column through the compatible path.', note: 'Zeta example · 
MySQL-CDC → JDBC MySQL.'},
-                {diagram: 'checkpoint', title: 'Checkpoint recovery', 
description: 'Resume from a completed checkpoint after a failure.', detail: 
'Capture state with a checkpoint barrier.', note: 'Exactly-once requires 
compatible source and sink support.'},
+                {diagram: 'parallel', title: 'Parallel batch reads', 
description: 'Divide the work. Read splits in parallel.', detail: 'Independent 
readers process assigned source splits.', note: 'JDBC example · split 
assignment is source-specific.'},
+                {diagram: 'cdc', title: 'Snapshot + CDC', description: 
'Snapshot first. Then capture binlog changes.', detail: 'Capture existing rows 
before streaming changes.', note: 'MySQL-CDC example · startup.mode = initial'},
+                {diagram: 'transform', title: 'Row-level transforms', 
description: 'Compute fields and filter individual rows.', detail: 'Apply row 
expressions and filters in the pipeline.', note: 'SQL example · expression + 
row filter'},
+                {diagram: 'routing', title: 'Multi-table routing', 
description: 'Keep each record on the right table path.', detail: 'Table 
identity selects the matching destination writer.', note: 'Logical routing · 
physical connections may differ.'},
+                {diagram: 'schema', title: 'Schema evolution', description: 
'Propagate supported table changes.', detail: 'Propagate an added column 
through the compatible path.', note: 'Zeta example · MySQL-CDC → JDBC MySQL'},
+                {diagram: 'checkpoint', title: 'Checkpoint recovery', 
description: 'Restart from a completed checkpoint.', detail: 'Capture state 
with a checkpoint barrier.', note: 'Exactly-once requires compatible source / 
sink support.'},
             ],
         },
         connectors: {
@@ -527,22 +527,78 @@ const HOME_COPY = {
             ],
         },
         motion: {
-            title: 'Data integration, in motion.',
-            lead: 'Six mechanisms that keep data moving through parallel 
workloads, continuous change, and recoverable failures.',
-            gridLabel: 'SeaTunnel data integration mechanisms',
+            title: '数据集成,持续流动。',
+            lead: '数据集成层背后的六种机制。',
+            gridLabel: 'SeaTunnel 数据集成机制',
             controls: {
-                label: 'Motion controls',
-                pause: 'Pause motion',
-                play: 'Resume motion',
+                label: '动画控制',
+                pause: '暂停动画',
+                play: '继续动画',
             },
-            caption: 'Illustrative flows—not a deployment blueprint. 
Production behavior depends on the selected connector, engine, and 
configuration.',
+            labels: {
+                barrier: '屏障',
+                binlog: 'Binlog',
+                checkpoint: 'Checkpoint',
+                checkpointState: 'Checkpoint 状态',
+                completed: '已完成',
+                customers: '客户',
+                fieldId: '编号',
+                fieldStatus: '状态',
+                input: '输入',
+                jdbcMysqlSink: 'JDBC MySQL 目标端',
+                mysqlOrders: 'MySQL / 订单',
+                mysqlSource: 'MySQL 源端',
+                output: '输出',
+                pending: '等待中',
+                reader: '读取器',
+                readers: '读取器',
+                route: '路由',
+                routeTables: ['用户', '订单', '条目'],
+                rowSql: '行级 SQL',
+                sink: '目标端',
+                snapshot: '快照',
+                source: '源端',
+                sourceSplits: '源端分片',
+                table: '表',
+                target: '目标端',
+                targetOrders: '目标端 / 订单',
+                waiting: '等待中…',
+                workerLost: '写入器丢失',
+                writer: '写入器',
+                values: {
+                    NEW: '新建',
+                    PAID: '已支付',
+                },
+                applied: '已应用',
+                filtered: 'id = 0 已过滤',
+                region: 'region',
+                status: {
+                    cdcDeleteArrived: 'DELETE · 已移除第 41 行。',
+                    cdcDeleteStarted: 'DELETE · 正在移除第 41 行',
+                    cdcInsert: 'INSERT · 新增第 43 行',
+                    cdcSnapshot: '正在读取已有行。',
+                    cdcUpdate: 'UPDATE · 第 42 行变为 PAID',
+                    checkpointBarrier: '通过 checkpoint 屏障捕获状态。',
+                    checkpointComplete: 'Checkpoint #42 已完成并保留。',
+                    checkpointFailure: 'Checkpoint #42 完成后,一个写入器发生故障。',
+                    checkpointRestore: '正在从 #42 恢复已保存的 source 状态。',
+                    checkpointRunning: '运行中 · source 与 sink 已连接。',
+                    checkpointResumed: '已恢复 · 从已保存的位置继续回放。',
+                    parallelComplete: '6 个示例分片已全部送达。',
+                    parallelRunning: '已分配的分片正穿过 3 个读取器。',
+                    routing: '表标识 → 匹配的目标端写入器。',
+                    schema: 'schema-changes.enabled = true',
+                    transform: "WHERE id > 0 · CONCAT(name, '_')",
+                },
+            },
+            caption: '示意性流图,并非部署蓝图。生产行为取决于所选连接器、执行引擎和配置。',
             mechanisms: [
-                {diagram: 'parallel', title: 'Parallel batch reads', 
description: 'Divide the work. Read independent splits in parallel.', detail: 
'Independent readers process assigned source splits.', note: 'JDBC example · 
Split assignment is source-specific.'},
-                {diagram: 'cdc', title: 'Snapshot + CDC', description: 
'Bootstrap a snapshot, then keep changes flowing.', detail: 'Capture existing 
rows before streaming changes.', note: 'MySQL-CDC example · startup.mode = 
initial.'},
-                {diagram: 'transform', title: 'Row-level transforms', 
description: 'Shape, filter, and enrich records before they land.', detail: 
'Apply row expressions and filters in the pipeline.', note: 'SQL example · 
expression + row filter.'},
-                {diagram: 'routing', title: 'Multi-table routing', 
description: 'Keep every record on its intended table path.', detail: 'Table 
identity selects the matching destination writer.', note: 'Logical routing · 
Physical connections may differ.'},
-                {diagram: 'schema', title: 'Schema evolution', description: 
'Apply supported table changes before new data moves downstream.', detail: 
'Propagate an added column through the compatible path.', note: 'Zeta example · 
MySQL-CDC → JDBC MySQL.'},
-                {diagram: 'checkpoint', title: 'Checkpoint recovery', 
description: 'Resume from a completed checkpoint after a failure.', detail: 
'Capture state with a checkpoint barrier.', note: 'Exactly-once requires 
compatible source and sink support.'},
+                {diagram: 'parallel', title: '并行批读取', description: 
'拆分工作,让分片并行读取。', detail: '独立读取器处理已分配的源端分片。', note: 'JDBC 示例 · 分片分配取决于具体 
source。'},
+                {diagram: 'cdc', title: '快照 + CDC', description: '先完成快照,再捕获 
binlog 变更。', detail: '先读取已有行,再流式处理持续变更。', note: 'MySQL-CDC 示例 · startup.mode = 
initial'},
+                {diagram: 'transform', title: '行级转换', description: 
'计算字段并筛选单条记录。', detail: '在 pipeline 中应用行表达式和过滤条件。', note: 'SQL 示例 · 表达式 + 行过滤'},
+                {diagram: 'routing', title: '多表路由', description: 
'让每条记录走向正确的表路径。', detail: '表标识决定匹配的目标端写入器。', note: '逻辑路由 · 物理连接可能不同。'},
+                {diagram: 'schema', title: 'Schema 演进', description: 
'传播受支持的表结构变更。', detail: '沿兼容路径传播新增列。', note: 'Zeta 示例 · MySQL-CDC → JDBC 
MySQL'},
+                {diagram: 'checkpoint', title: 'Checkpoint 恢复', description: 
'从已完成的 checkpoint 重新开始。', detail: '通过 checkpoint 屏障捕获状态。', note: 'Exactly-once 
需要兼容的 source / sink 支持。'},
             ],
         },
         connectors: {
@@ -742,7 +798,7 @@ export default function Home() {
     const architectureTargetRefs = useRef([]);
 
     const language = isBrowser
-        ? (window.location.pathname.startsWith('/zh-CN/') ? 'zh-CN' : 'en')
+        ? (/^\/zh-CN(?:\/|$)/.test(window.location.pathname) ? 'zh-CN' : 'en')
         : (i18n.currentLocale === 'zh-CN' ? 'zh-CN' : 'en');
     const isChinese = language === 'zh-CN';
     const content = HOME_COPY[language] || HOME_COPY.en;
diff --git a/src/pages/home/index.less b/src/pages/home/index.less
index ee2c34124b7d..21c414674bc5 100644
--- a/src/pages/home/index.less
+++ b/src/pages/home/index.less
@@ -975,6 +975,7 @@ html[data-theme='light'] .st-home {
   --motion-line: rgba(166, 203, 232, 0.28);
   --motion-soft: rgba(93, 184, 226, 0.14);
   --motion-blue: #5db8e2;
+  --motion-dark: #2c6b96;
   --motion-teal: #52cbbf;
   --motion-on-blue: #082137;
   --motion-on-teal: #061f2a;
@@ -1047,12 +1048,9 @@ html[data-theme='light'] .st-home {
   outline-offset: 2px;
 }
 
-.st-home-motion-control:disabled {
-  cursor: default;
-  opacity: 0.58;
-}
-
 .st-home-motion-card {
+  position: relative;
+  overflow: hidden;
   min-width: 0;
   border: 1px solid var(--motion-line);
   border-radius: 1.2rem;
@@ -1060,8 +1058,11 @@ html[data-theme='light'] .st-home {
   box-shadow: 0 16px 32px rgba(0, 0, 0, 0.16);
 }
 
-.st-home-motion-card-schema {
+.st-home-motion-card.is-active {
   border-color: var(--motion-teal);
+  box-shadow:
+    0 16px 32px rgba(0, 0, 0, 0.16),
+    0 0 0 1px rgba(82, 203, 191, var(--motion-active-opacity));
 }
 
 .st-home-motion-card article {
@@ -1131,6 +1132,25 @@ html[data-theme='light'] .st-home {
   height: auto;
 }
 
+.st-home-motion-diagram-transition {
+  position: relative;
+  width: 100%;
+}
+
+.st-home-motion-diagram-transition-frame + 
.st-home-motion-diagram-transition-frame {
+  position: absolute;
+  inset: 0;
+}
+
+.st-home-motion-status-transition {
+  position: relative;
+}
+
+.st-home-motion-status-transition-frame {
+  position: absolute;
+  inset: 0;
+}
+
 .st-home-motion-diagram text {
   font-family: var(--ifm-font-family-base);
 }
@@ -1195,6 +1215,18 @@ html[data-theme='light'] .st-home {
   fill: var(--motion-blue);
 }
 
+.st-home-motion-diagram-chip-text-blue {
+  fill: var(--motion-blue);
+}
+
+.st-home-motion-diagram-chip-text-teal {
+  fill: var(--motion-teal);
+}
+
+.st-home-motion-diagram-chip-text-dark {
+  fill: #fff;
+}
+
 .st-home-motion-diagram-sql {
   font-size: 20px;
 }
@@ -1205,6 +1237,16 @@ html[data-theme='light'] .st-home {
   stroke-width: 1.2;
 }
 
+.st-home-motion-diagram-stage {
+  fill: var(--motion-stage);
+}
+
+.st-home-motion-diagram-database {
+  fill: var(--motion-node);
+  stroke: var(--motion-blue);
+  stroke-width: 1.6;
+}
+
 .st-home-motion-diagram-line,
 .st-home-motion-diagram-arrow {
   stroke: var(--motion-line);
@@ -1231,6 +1273,10 @@ html[data-theme='light'] .st-home {
   fill: var(--motion-blue);
 }
 
+.st-home-motion-diagram-dark-fill {
+  fill: var(--motion-dark);
+}
+
 .st-home-motion-diagram-teal-fill {
   fill: var(--motion-teal);
 }
@@ -1241,6 +1287,26 @@ html[data-theme='light'] .st-home {
   stroke-width: 1.4;
 }
 
+.st-home-motion-diagram-table-teal {
+  stroke: var(--motion-teal);
+}
+
+.st-home-motion-diagram-table-dark {
+  stroke: var(--motion-dark);
+}
+
+.st-home-motion-diagram-reader-blue {
+  stroke: var(--motion-blue);
+}
+
+.st-home-motion-diagram-reader-teal {
+  stroke: var(--motion-teal);
+}
+
+.st-home-motion-diagram-reader-dark {
+  stroke: var(--motion-dark);
+}
+
 .st-home-motion-diagram-highlight {
   fill: var(--motion-highlight);
 }
@@ -1255,12 +1321,18 @@ html[data-theme='light'] .st-home {
   fill: var(--motion-teal);
 }
 
+.st-home-motion-diagram-moving-packet-dark,
+.st-home-motion-diagram-moving-signal-dark 
.st-home-motion-diagram-moving-signal {
+  fill: var(--motion-dark);
+}
+
 .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 {
+.st-home-motion-diagram-moving-packet-copy-teal,
+.st-home-motion-diagram-moving-packet-copy-dark {
   font-size: 10px;
   font-weight: 700;
 }
@@ -1273,6 +1345,30 @@ html[data-theme='light'] .st-home {
   fill: var(--motion-on-teal);
 }
 
+.st-home-motion-diagram-moving-packet-copy-dark {
+  fill: #fff;
+}
+
+.st-home-motion-diagram-check,
+.st-home-motion-diagram-filter-mark {
+  stroke: var(--motion-teal);
+  stroke-linecap: round;
+  stroke-linejoin: round;
+  stroke-width: 2.2;
+}
+
+.st-home-motion-diagram-filter-icon {
+  fill: var(--motion-node);
+  stroke: var(--motion-blue);
+  stroke-width: 1.5;
+}
+
+.st-home-motion-diagram-barrier {
+  stroke: var(--motion-blue);
+  stroke-dasharray: 4 3;
+  stroke-width: 2.4;
+}
+
 .st-home-motion-card-detail {
   color: var(--motion-blue);
   font-size: 0.82rem;
@@ -1280,13 +1376,21 @@ html[data-theme='light'] .st-home {
   line-height: 1.45;
 }
 
+.st-home-motion-card-detail-teal {
+  color: var(--motion-teal-text);
+}
+
 .st-home-motion-card-detail::before {
   display: inline-block;
   margin-right: 0.45rem;
-  color: var(--motion-teal);
+  color: var(--motion-blue);
   content: '•';
 }
 
+.st-home-motion-card-detail-teal::before {
+  color: var(--motion-teal);
+}
+
 .st-home-motion-card-note {
   margin-top: 0.55rem;
   padding-top: 0.55rem;
@@ -1296,6 +1400,18 @@ html[data-theme='light'] .st-home {
   line-height: 1.4;
 }
 
+.st-home-motion-card-progress {
+  position: absolute;
+  right: 1.6rem;
+  bottom: 0;
+  left: 1.6rem;
+  height: 3px;
+  border-radius: 3px 3px 0 0;
+  background: var(--motion-teal);
+  transform: scaleX(var(--motion-active-progress));
+  transform-origin: left;
+}
+
 .st-home-motion-caption {
   max-width: 760px;
   margin-top: 1.25rem;
@@ -2785,6 +2901,7 @@ html[data-theme='light'] .st-home .st-home-motion-section 
{
   --motion-line: #d7e5eb;
   --motion-soft: #eaf6f7;
   --motion-blue: #2469a6;
+  --motion-dark: #215f91;
   --motion-teal: #30adb2;
   --motion-on-blue: #fff;
   --motion-on-teal: #0d3945;
@@ -2807,6 +2924,16 @@ html[data-theme='light'] .st-home .st-home-motion-card {
   box-shadow: 0 14px 30px rgba(87, 122, 177, 0.1);
 }
 
+html[data-theme='light'] .st-home .st-home-motion-card.is-active {
+  box-shadow:
+    0 14px 30px rgba(87, 122, 177, 0.1),
+    0 0 0 1px rgba(48, 173, 178, var(--motion-active-opacity));
+}
+
+html[data-theme='light'] .st-home .st-home-motion-diagram-chip-text-dark {
+  fill: var(--motion-dark);
+}
+
 html[data-theme='light'] .st-home .st-home-architecture {
   background: linear-gradient(180deg, #edf4fc 0%, #e6eef8 100%);
 }

Reply via email to