Browse Source

优化布局

robert 2 ngày trước cách đây
mục cha
commit
abf8b3d842

+ 46 - 4
doc/animatable_properties/test_css_props.html

@@ -31,6 +31,7 @@
                 <div class="tabs">
                     <div class="tab active" onclick="switchTab('js')">JavaScript</div>
                     <div class="tab" onclick="switchTab('html')">HTML</div>
+                    <div class="tab" onclick="switchTab('css')">CSS</div>
                 </div>
             </div>
 
@@ -46,29 +47,57 @@
 <span class="tag">&lt;div</span> <span class="attr">class</span>=<span class="val">"prop-box"</span><span class="tag">&gt;&lt;/div&gt;</span>
             </div>
 
+            <pre id="css-code" class="css-view">.prop-box {
+  width: 50px;
+  height: 50px;
+  background-color: var(--highlight-color);
+  border-radius: 4px;
+  margin-bottom: 20px;
+}
+.demo-visual {
+  padding: 40px;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+}</pre>
+
+            <div class="feature-desc">
+                <strong>功能说明:</strong>直接动画任意 CSS 属性(如宽度、颜色、圆角等),适合做组件状态变化与视觉过渡。
+            </div>
+
             <div class="demo-visual">
                 <div class="prop-box"></div>
             </div>
             
             <div class="action-bar">
                 <button class="play-btn" onclick="runDemo()">REPLAY</button>
+                <button class="play-btn secondary" onclick="pauseDemo()">PAUSE</button>
+                <button class="play-btn secondary" onclick="resumeDemo()">RESUME</button>
             </div>
         </div>
 
     </div>
 
+    <script src="../highlight_css.js"></script>
     <script src="../../xjs.js"></script>
     <script>
         function switchTab(tab) {
             document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
-            document.querySelectorAll('.code-view, .html-view').forEach(v => v.classList.remove('active'));
+            document.querySelectorAll('.code-view, .html-view, .css-view').forEach(v => v.classList.remove('active'));
             if (tab === 'js') {
+                document.querySelector('.tabs .tab:nth-child(1)')?.classList.add('active');
                 document.getElementById('js-code').classList.add('active');
-            } else {
+            } else if (tab === 'html') {
+                document.querySelector('.tabs .tab:nth-child(2)')?.classList.add('active');
                 document.getElementById('html-code').classList.add('active');
+            } else {
+                document.querySelector('.tabs .tab:nth-child(3)')?.classList.add('active');
+                document.getElementById('css-code').classList.add('active');
             }
         }
 
+        let __demoControls = [];
+
         function runDemo() {
             // Reset
             const el = document.querySelector('.prop-box');
@@ -76,13 +105,26 @@
             el.style.backgroundColor = ''; // Reset to CSS var
             el.style.borderRadius = '4px';
             
-                        
-            xjs('.prop-box').animate({ 
+            __demoControls = [];
+            __demoControls.push(xjs('.prop-box').animate({ 
                 width: '100px',
                 backgroundColor: '#FFF', 
                 borderRadius: '50%',
                 duration: 1000,
                 easing: 'ease-in-out'
+            }));
+        }
+
+        function pauseDemo() {
+            __demoControls.forEach(c => { try { c?.pause?.(); } catch {} });
+        }
+
+        function resumeDemo() {
+            __demoControls.forEach(c => {
+                try {
+                    if (typeof c?.resume === 'function') c.resume();
+                    else if (typeof c?.play === 'function') c.play();
+                } catch {}
             });
         }
         

+ 66 - 4
doc/animatable_properties/test_css_vars.html

@@ -26,7 +26,11 @@
         <div class="box-container">
             <div class="box-header">
                 <div class="box-title">CSS Var example</div>
-                <div class="tabs"><div class="tab active">JavaScript</div></div>
+                <div class="tabs">
+                    <div class="tab active" onclick="switchTab('js')">JavaScript</div>
+                    <div class="tab" onclick="switchTab('html')">HTML</div>
+                    <div class="tab" onclick="switchTab('css')">CSS</div>
+                </div>
             </div>
             <div id="js-code" class="code-view active">
 <span class="fun">xjs</span><span class="punc">(</span><span class="str">'.box'</span><span class="punc">)</span>.<span class="fun">animate</span><span class="punc">(</span><span class="punc">{</span>
@@ -35,22 +39,80 @@
   easing<span class="punc">:</span> <span class="str">'ease-in-out'</span>
 <span class="punc">}</span><span class="punc">)</span><span class="punc">;</span>
             </div>
+            <div id="html-code" class="html-view">
+<span class="tag">&lt;div</span> <span class="attr">class</span>=<span class="val">"box"</span><span class="tag">&gt;&lt;/div&gt;</span>
+            </div>
+            <pre id="css-code" class="css-view">:root {
+  --box-width: 50px;
+}
+.box {
+  width: var(--box-width);
+  height: 50px;
+  background-color: var(--highlight-color);
+  border-radius: 4px;
+}
+.demo-visual {
+  padding: 40px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}</pre>
+
+            <div class="feature-desc">
+                <strong>功能说明:</strong>把 CSS 自定义属性(变量)当作可动画属性,适合主题色、尺寸、间距等可配置样式的过渡。
+            </div>
             <div class="demo-visual">
                 <div class="box"></div>
             </div>
-            <div class="action-bar"><button class="play-btn" onclick="runDemo()">REPLAY</button></div>
+            <div class="action-bar">
+                <button class="play-btn" onclick="runDemo()">REPLAY</button>
+                <button class="play-btn secondary" onclick="pauseDemo()">PAUSE</button>
+                <button class="play-btn secondary" onclick="resumeDemo()">RESUME</button>
+            </div>
         </div>
     </div>
+    <script src="../highlight_css.js"></script>
     <script src="../../xjs.js"></script>
     <script>
+        function switchTab(tab) {
+            document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
+            document.querySelectorAll('.code-view, .html-view, .css-view').forEach(v => v.classList.remove('active'));
+            if (tab === 'js') {
+                document.querySelector('.tabs .tab:nth-child(1)')?.classList.add('active');
+                document.getElementById('js-code').classList.add('active');
+            } else if (tab === 'html') {
+                document.querySelector('.tabs .tab:nth-child(2)')?.classList.add('active');
+                document.getElementById('html-code').classList.add('active');
+            } else {
+                document.querySelector('.tabs .tab:nth-child(3)')?.classList.add('active');
+                document.getElementById('css-code').classList.add('active');
+            }
+        }
+
+        let __demoControls = [];
+
         function runDemo() {
-                        const box = document.querySelector('.box');
+            const box = document.querySelector('.box');
             box.style.setProperty('--box-width', '50px');
 
-            xjs('.box').animate({
+            __demoControls = [];
+            __demoControls.push(xjs('.box').animate({
                 '--box-width': '200px',
                 duration: 1000,
                 easing: 'ease-in-out'
+            }));
+        }
+
+        function pauseDemo() {
+            __demoControls.forEach(c => { try { c?.pause?.(); } catch {} });
+        }
+
+        function resumeDemo() {
+            __demoControls.forEach(c => {
+                try {
+                    if (typeof c?.resume === 'function') c.resume();
+                    else if (typeof c?.play === 'function') c.play();
+                } catch {}
             });
         }
         setTimeout(runDemo, 500);

+ 66 - 3
doc/animatable_properties/test_js_props.html

@@ -18,7 +18,11 @@
         <div class="box-container">
             <div class="box-header">
                 <div class="box-title">JS Object example</div>
-                <div class="tabs"><div class="tab active">JavaScript</div></div>
+                <div class="tabs">
+                    <div class="tab active" onclick="switchTab('js')">JavaScript</div>
+                    <div class="tab" onclick="switchTab('html')">HTML</div>
+                    <div class="tab" onclick="switchTab('css')">CSS</div>
+                </div>
             </div>
             <div id="js-code" class="code-view active">
 <span class="kwd">const</span> $ <span class="punc">=</span> <span class="fun">xjs</span><span class="punc">;</span>
@@ -28,22 +32,81 @@
   update<span class="punc">:</span> <span class="punc">()</span> <span class="punc">=></span> updateUI(Math.round(obj.score))
 <span class="punc">}</span><span class="punc">)</span><span class="punc">;</span>
             </div>
+            <div id="html-code" class="html-view">
+<span class="tag">&lt;div</span> <span class="attr">class</span>=<span class="val">"log"</span><span class="tag">&gt;</span>Score: <span class="tag">&lt;span</span> <span class="attr">id</span>=<span class="val">"score"</span><span class="tag">&gt;</span>0<span class="tag">&lt;/span&gt;&lt;/div&gt;</span>
+            </div>
+            <pre id="css-code" class="css-view">.demo-visual {
+  padding: 40px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+.log {
+  font-family: monospace;
+  font-size: 20px;
+  color: #fff;
+}</pre>
+
+            <div class="feature-desc">
+                <strong>功能说明:</strong>动画普通对象的属性值,并在 update 中把数值渲染到 UI;常用于数值滚动、计数器、进度等非 DOM 属性动画。
+            </div>
             <div class="demo-visual">
                 <div class="log">Score: <span id="score">0</span></div>
             </div>
-            <div class="action-bar"><button class="play-btn" onclick="runDemo()">REPLAY</button></div>
+            <div class="action-bar">
+                <button class="play-btn" onclick="runDemo()">REPLAY</button>
+                <button class="play-btn secondary" onclick="pauseDemo()">PAUSE</button>
+                <button class="play-btn secondary" onclick="resumeDemo()">RESUME</button>
+            </div>
         </div>
     </div>
+    <script src="../highlight_css.js"></script>
     <script src="../../xjs.js"></script>
     <script>
+        function switchTab(tab) {
+            document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
+            document.querySelectorAll('.code-view, .html-view, .css-view').forEach(v => v.classList.remove('active'));
+            if (tab === 'js') {
+                document.querySelector('.tabs .tab:nth-child(1)')?.classList.add('active');
+                document.getElementById('js-code').classList.add('active');
+            } else if (tab === 'html') {
+                document.querySelector('.tabs .tab:nth-child(2)')?.classList.add('active');
+                document.getElementById('html-code').classList.add('active');
+            } else {
+                document.querySelector('.tabs .tab:nth-child(3)')?.classList.add('active');
+                document.getElementById('css-code').classList.add('active');
+            }
+        }
+
+        let __demoControls = [];
+
         function runDemo() {
             const obj = { score: 0 };
             const el = document.getElementById('score');
-            animal.animate(obj, { 
+            __demoControls = [];
+            // Use xjs if available; fall back to animal.animate if present.
+            const animator = (typeof xjs === 'function') ? xjs(obj) : (typeof animal !== 'undefined' ? animal : null);
+            const ctl = (animator && typeof animator.animate === 'function')
+                ? animator.animate({ 
                 score: 1000, 
                 duration: 1500, 
                 easing: 'linear',
                 update: () => el.textContent = Math.round(obj.score)
+            })
+                : null;
+            if (ctl) __demoControls.push(ctl);
+        }
+
+        function pauseDemo() {
+            __demoControls.forEach(c => { try { c?.pause?.(); } catch {} });
+        }
+
+        function resumeDemo() {
+            __demoControls.forEach(c => {
+                try {
+                    if (typeof c?.resume === 'function') c.resume();
+                    else if (typeof c?.play === 'function') c.play();
+                } catch {}
             });
         }
         setTimeout(runDemo, 500);

+ 63 - 3
doc/animatable_properties/test_transforms.html

@@ -23,7 +23,11 @@
         <div class="box-container">
             <div class="box-header">
                 <div class="box-title">Transform example</div>
-                <div class="tabs"><div class="tab active">JavaScript</div></div>
+                <div class="tabs">
+                    <div class="tab active" onclick="switchTab('js')">JavaScript</div>
+                    <div class="tab" onclick="switchTab('html')">HTML</div>
+                    <div class="tab" onclick="switchTab('css')">CSS</div>
+                </div>
             </div>
             <div id="js-code" class="code-view active">
 <span class="fun">xjs</span><span class="punc">(</span><span class="str">'.box'</span><span class="punc">)</span>.<span class="fun">animate</span><span class="punc">({</span>
@@ -32,17 +36,73 @@
   scale<span class="punc">:</span> <span class="num">0.5</span>
 <span class="punc">}</span><span class="punc">)</span><span class="punc">;</span>
             </div>
+            <div id="html-code" class="html-view">
+<span class="tag">&lt;div</span> <span class="attr">class</span>=<span class="val">"box"</span><span class="tag">&gt;&lt;/div&gt;</span>
+            </div>
+            <pre id="css-code" class="css-view">.box {
+  width: 50px;
+  height: 50px;
+  background-color: var(--highlight-color);
+  border-radius: 4px;
+}
+.demo-visual {
+  padding: 40px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  height: 150px;
+}</pre>
+
+            <div class="feature-desc">
+                <strong>功能说明:</strong>把 transform 拆成独立属性(translate/rotate/scale 等)进行动画,组合时会自动合并到最终 transform。
+            </div>
             <div class="demo-visual">
                 <div class="box"></div>
             </div>
-            <div class="action-bar"><button class="play-btn" onclick="runDemo()">REPLAY</button></div>
+            <div class="action-bar">
+                <button class="play-btn" onclick="runDemo()">REPLAY</button>
+                <button class="play-btn secondary" onclick="pauseDemo()">PAUSE</button>
+                <button class="play-btn secondary" onclick="resumeDemo()">RESUME</button>
+            </div>
         </div>
     </div>
+    <script src="../highlight_css.js"></script>
     <script src="../../xjs.js"></script>
     <script>
+        function switchTab(tab) {
+            document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
+            document.querySelectorAll('.code-view, .html-view, .css-view').forEach(v => v.classList.remove('active'));
+            if (tab === 'js') {
+                document.querySelector('.tabs .tab:nth-child(1)')?.classList.add('active');
+                document.getElementById('js-code').classList.add('active');
+            } else if (tab === 'html') {
+                document.querySelector('.tabs .tab:nth-child(2)')?.classList.add('active');
+                document.getElementById('html-code').classList.add('active');
+            } else {
+                document.querySelector('.tabs .tab:nth-child(3)')?.classList.add('active');
+                document.getElementById('css-code').classList.add('active');
+            }
+        }
+
+        let __demoControls = [];
+
         function runDemo() {
             document.querySelector('.box').style.transform = 'none';
-                        xjs('.box').animate({ translateX: 100, rotate: '1turn', scale: 0.5, duration: 1000 });
+            __demoControls = [];
+            __demoControls.push(xjs('.box').animate({ translateX: 100, rotate: '1turn', scale: 0.5, duration: 1000 }));
+        }
+
+        function pauseDemo() {
+            __demoControls.forEach(c => { try { c?.pause?.(); } catch {} });
+        }
+
+        function resumeDemo() {
+            __demoControls.forEach(c => {
+                try {
+                    if (typeof c?.resume === 'function') c.resume();
+                    else if (typeof c?.play === 'function') c.play();
+                } catch {}
+            });
         }
         setTimeout(runDemo, 500);
     </script>

+ 94 - 2
doc/demo.css

@@ -26,6 +26,69 @@ body {
     overflow-y: auto;
 }
 
+/* Auto-hide scrollbar (shown only on scroll/hover via parent) */
+html, body {
+    scrollbar-width: none; /* Firefox (hidden by default) */
+    -ms-overflow-style: none; /* IE/old Edge */
+}
+
+html::-webkit-scrollbar,
+body::-webkit-scrollbar {
+    width: 0 !important;
+    height: 0 !important;
+}
+
+/* Some pages may scroll a nested container; hide vertical scrollbars by default */
+*::-webkit-scrollbar:vertical {
+    width: 0 !important;
+}
+
+/* Shown when scrolling (class is toggled by doc/index.html) */
+html.scrolling,
+body.scrolling {
+    scrollbar-width: thin; /* Firefox */
+    scrollbar-color: rgba(0, 0, 0, 0.9) transparent;
+}
+
+html.scrolling::-webkit-scrollbar,
+body.scrolling::-webkit-scrollbar {
+    width: 6px !important;
+    height: 6px !important;
+}
+
+html.scrolling::-webkit-scrollbar-track,
+body.scrolling::-webkit-scrollbar-track {
+    background: transparent;
+}
+
+html.scrolling::-webkit-scrollbar-thumb,
+body.scrolling::-webkit-scrollbar-thumb {
+    background: rgba(0, 0, 0, 0.95) !important;
+    border-radius: 999px;
+}
+
+html.scrolling::-webkit-scrollbar-thumb:hover,
+body.scrolling::-webkit-scrollbar-thumb:hover {
+    background: rgba(0, 0, 0, 1);
+}
+
+/* Show vertical scrollbars (narrow + black) for any scrolled container while scrolling */
+html.scrolling *::-webkit-scrollbar:vertical,
+body.scrolling *::-webkit-scrollbar:vertical {
+    width: 6px !important;
+}
+
+html.scrolling *::-webkit-scrollbar-thumb,
+body.scrolling *::-webkit-scrollbar-thumb {
+    background: rgba(0, 0, 0, 0.95) !important;
+    border-radius: 999px;
+}
+
+html.scrolling *::-webkit-scrollbar-track,
+body.scrolling *::-webkit-scrollbar-track {
+    background: transparent !important;
+}
+
 .container {
     max-width: 980px;
     width: 100%;
@@ -152,7 +215,7 @@ code.inline {
 }
 
 /* Code Views */
-.code-view, .html-view {
+.code-view, .html-view, .css-view {
     background: var(--code-bg);
     padding: 20px;
     font-family: 'Roboto Mono', 'Monaco', monospace;
@@ -165,7 +228,7 @@ code.inline {
     margin: 0;
 }
 
-.code-view.active, .html-view.active {
+.code-view.active, .html-view.active, .css-view.active {
     display: block;
 }
 
@@ -228,6 +291,8 @@ code.inline {
     padding: 15px 30px;
     display: flex;
     justify-content: flex-end;
+    align-items: center;
+    gap: 10px;
     border-top: 1px solid var(--border-color);
 }
 
@@ -248,6 +313,33 @@ code.inline {
     color: var(--orange);
 }
 
+/* Secondary action button (pause/resume) */
+.play-btn.secondary {
+    border-color: #2f2f2f;
+    color: #c8c8c8;
+    background: rgba(255, 255, 255, 0.02);
+}
+
+.play-btn.secondary:hover {
+    border-color: #5a5a5a;
+    color: #e6e6e6;
+}
+
+/* Small per-demo note block (feature description) */
+.feature-desc {
+    padding: 14px 20px;
+    border-top: 1px solid var(--border-color);
+    background: rgba(255, 255, 255, 0.01);
+    color: #9a9a9a;
+    font-size: 12px;
+    line-height: 1.6;
+}
+
+.feature-desc strong {
+    color: #ddd;
+    font-weight: 700;
+}
+
 /* Toast */
 .toast {
     position: fixed;

+ 59 - 4
doc/examples/controls.html

@@ -65,6 +65,7 @@
                 <div class="tabs">
                     <div class="tab active" onclick="switchTab('js')">JavaScript</div>
                     <div class="tab" onclick="switchTab('html')">HTML</div>
+                    <div class="tab" onclick="switchTab('css')">CSS</div>
                 </div>
             </div>
 
@@ -95,6 +96,37 @@ xjs.scroll(progress);</pre>
   &lt;div class="ex-bar"&gt;&lt;/div&gt;
 &lt;/div&gt;</pre>
 
+            <pre id="css-code" class="css-view">.demo-visual { gap: 18px; }
+.row {
+  width: 100%;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  gap: 14px;
+}
+.box {
+  width: 48px;
+  height: 48px;
+  border-radius: 10px;
+  background: var(--highlight-color);
+  transform: translateX(0px);
+}
+.bar-wrap {
+  width: 100%;
+  background: rgba(255,255,255,0.05);
+  border: 1px solid rgba(255,255,255,0.08);
+  border-radius: 999px;
+  overflow: hidden;
+  height: 10px;
+}
+.bar { height: 100%; width: 0%; background: var(--orange); }
+.hint { font-size: 12px; color: #888; }
+.spacer { height: 80vh; }</pre>
+
+            <div class="feature-desc">
+                <strong>功能说明:</strong>演示 controls 对象的 <code class="inline">play/pause/seek</code> 与滚动联动:点击 REPLAY 后,在面板内滚动会驱动进度条动画。
+            </div>
+
             <div class="demo-visual">
                 <div class="row">
                     <div class="box ex-box" aria-label="demo box"></div>
@@ -109,25 +141,32 @@ xjs.scroll(progress);</pre>
 
             <div class="action-bar">
                 <button class="play-btn" onclick="runDemo()">REPLAY</button>
+                <button class="play-btn secondary" onclick="pauseDemo()">PAUSE</button>
+                <button class="play-btn secondary" onclick="resumeDemo()">RESUME</button>
             </div>
         </div>
     </div>
 
+    <script src="../highlight_css.js"></script>
     <script src="../../xjs.js"></script>
     <script>
         function switchTab(tab) {
             document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
-            document.querySelectorAll('.code-view, .html-view').forEach(v => v.classList.remove('active'));
+            document.querySelectorAll('.code-view, .html-view, .css-view').forEach(v => v.classList.remove('active'));
             if (tab === 'js') {
-                document.querySelector('.tabs .tab:nth-child(1)').classList.add('active');
+                document.querySelector('.tabs .tab:nth-child(1)')?.classList.add('active');
                 document.getElementById('js-code').classList.add('active');
-            } else {
-                document.querySelector('.tabs .tab:nth-child(2)').classList.add('active');
+            } else if (tab === 'html') {
+                document.querySelector('.tabs .tab:nth-child(2)')?.classList.add('active');
                 document.getElementById('html-code').classList.add('active');
+            } else {
+                document.querySelector('.tabs .tab:nth-child(3)')?.classList.add('active');
+                document.getElementById('css-code')?.classList.add('active');
             }
         }
 
         let cleanup = null;
+        let __demoControls = [];
         function runDemo() {
                         // reset
             const box = document.querySelector('.ex-box');
@@ -139,6 +178,7 @@ xjs.scroll(progress);</pre>
             // Remove previous scroll listener if any
             if (cleanup) cleanup();
             cleanup = null;
+            __demoControls = [];
 
             const ctl = xjs('.ex-box').animate({
                 x: 220,
@@ -147,6 +187,7 @@ xjs.scroll(progress);</pre>
                 duration: 800,
                 autoplay: false
             });
+            __demoControls.push(ctl);
             ctl.seek(0.25).play();
 
             const progress = xjs('.ex-bar').animate({
@@ -154,9 +195,23 @@ xjs.scroll(progress);</pre>
                 autoplay: false,
                 duration: 1000
             });
+            __demoControls.push(progress);
             cleanup = xjs.scroll(progress);
         }
 
+        function pauseDemo() {
+            __demoControls.forEach(c => { try { c?.pause?.(); } catch {} });
+        }
+
+        function resumeDemo() {
+            __demoControls.forEach(c => {
+                try {
+                    if (typeof c?.resume === 'function') c.resume();
+                    else if (typeof c?.play === 'function') c.play();
+                } catch {}
+            });
+        }
+
         setTimeout(runDemo, 300);
     </script>
 </body>

+ 55 - 2
doc/examples/layer.html

@@ -80,6 +80,7 @@
         <div class="tabs">
           <div class="tab active" onclick="switchTab('js')">JavaScript</div>
           <div class="tab" onclick="switchTab('html')">HTML</div>
+          <div class="tab" onclick="switchTab('css')">CSS</div>
         </div>
       </div>
 
@@ -110,6 +111,54 @@ document.querySelector('.btn-direct').addEventListener('click', () =&gt; {
 
 &lt;button class="btn btn-direct"&gt;Open (xjs.layer)&lt;/button&gt;</pre>
 
+      <pre id="css-code" class="css-view">.demo-visual {
+  gap: 14px;
+  align-items: stretch;
+}
+.row {
+  display: flex;
+  gap: 12px;
+  flex-wrap: wrap;
+  align-items: center;
+}
+.btn {
+  appearance: none;
+  border: 1px solid rgba(255,255,255,0.12);
+  background: rgba(255,255,255,0.04);
+  color: #fff;
+  border-radius: 999px;
+  padding: 10px 14px;
+  font-weight: 650;
+  cursor: pointer;
+  transition: border-color 0.15s ease, background 0.15s ease;
+}
+.btn:hover {
+  border-color: rgba(255,255,255,0.22);
+  background: rgba(255,255,255,0.06);
+}
+.btn.danger { border-color: rgba(255,75,75,0.5); background: rgba(255,75,75,0.12); }
+.btn.danger:hover { border-color: rgba(255,75,75,0.7); background: rgba(255,75,75,0.18); }
+.hint { font-size: 12px; color: #8c8c8c; line-height: 1.5; }
+.pill {
+  display: inline-flex;
+  align-items: center;
+  gap: 8px;
+  border: 1px solid rgba(255,255,255,0.08);
+  background: rgba(255,255,255,0.03);
+  border-radius: 999px;
+  padding: 8px 12px;
+  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
+  font-size: 12px;
+  color: #b7b7b7;
+  overflow-x: auto;
+  max-width: 100%;
+  white-space: nowrap;
+}</pre>
+
+      <div class="feature-desc">
+        <strong>功能说明:</strong>演示 <code class="inline">Selection.layer()</code> 的三种用法:选择器绑定弹窗、data-* 配置、以及直接调用 <code class="inline">xjs.layer()</code>。
+      </div>
+
       <div class="demo-visual">
         <div class="row">
           <button class="btn danger btn-delete">Delete (bind .layer)</button>
@@ -135,17 +184,21 @@ document.querySelector('.btn-direct').addEventListener('click', () =&gt; {
     </div>
   </div>
 
+  <script src="../highlight_css.js"></script>
   <script src="../../xjs.js"></script>
   <script>
     function switchTab(tab) {
       document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
-      document.querySelectorAll('.code-view, .html-view').forEach(v => v.classList.remove('active'));
+      document.querySelectorAll('.code-view, .html-view, .css-view').forEach(v => v.classList.remove('active'));
       if (tab === 'js') {
         document.querySelector('.tabs .tab:nth-child(1)').classList.add('active');
         document.getElementById('js-code').classList.add('active');
-      } else {
+      } else if (tab === 'html') {
         document.querySelector('.tabs .tab:nth-child(2)').classList.add('active');
         document.getElementById('html-code').classList.add('active');
+      } else {
+        document.querySelector('.tabs .tab:nth-child(3)').classList.add('active');
+        document.getElementById('css-code').classList.add('active');
       }
     }
 

+ 115 - 0
doc/highlight_css.js

@@ -0,0 +1,115 @@
+// Minimal CSS syntax highlighter for the docs (shared).
+// It converts plain-text CSS inside `.css-view` into highlighted HTML using
+// the existing demo.css token classes: .kwd .str .num .punc .com .attr .val .fun
+
+(function () {
+  function escapeHtml(s) {
+    return String(s)
+      .replace(/&/g, '&amp;')
+      .replace(/</g, '&lt;')
+      .replace(/>/g, '&gt;');
+  }
+
+  function highlightCSS(input) {
+    let s = escapeHtml(input);
+
+    // Comments
+    s = s.replace(/\/\*[\s\S]*?\*\//g, (m) => `<span class="com">${m}</span>`);
+
+    // Strings
+    s = s.replace(/("(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*')/g, (m) => `<span class="str">${m}</span>`);
+
+    // @rules
+    s = s.replace(/(^|\s)(@[\w-]+)/g, (m, p1, p2) => `${p1}<span class="kwd">${p2}</span>`);
+
+    // Hex colors
+    s = s.replace(/(#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8}))/g, (m) => `<span class="num">${m}</span>`);
+
+    // Numbers with units (very rough, but good enough visually)
+    s = s.replace(/(\b\d+(?:\.\d+)?)(%|px|rem|em|vh|vw|vmin|vmax|deg|turn|s|ms)?\b/g, (m, n, unit) => {
+      return `<span class="num">${n}</span>${unit || ''}`;
+    });
+
+    // Property names: "prop: value"
+    s = s.replace(/(^|\n)(\s*)([a-zA-Z_-][\w-]*)(\s*):/g, (m, p1, ws, prop, ws2) => {
+      return `${p1}${ws}<span class="attr">${prop}</span>${ws2}<span class="punc">:</span>`;
+    });
+
+    // Highlight braces / punctuation
+    s = s.replace(/([{}();,])/g, (m) => `<span class="punc">${m}</span>`);
+
+    // Selectors (line before "{"), skip @rules
+    s = s.replace(/(^|\n)([^\n{]+?)(\s*)(<span class="punc">\{<\/span>)/g, (m, p1, sel, ws, brace) => {
+      const trimmed = sel.trimStart();
+      if (trimmed.startsWith('@')) return `${p1}${sel}${ws}${brace}`;
+      return `${p1}<span class="fun">${sel}</span>${ws}${brace}`;
+    });
+
+    // Values: after ":" until ";" or "}" (best-effort, runs after punctuation)
+    s = s.replace(/(<span class="punc">:<\/span>)([^;\n}]+)(?=(<span class="punc">;|<span class="punc">\}))/g, (m, colon, val) => {
+      // avoid re-highlighting spans
+      if (val.includes('<span')) return `${colon}${val}`;
+      return `${colon}<span class="val">${val}</span>`;
+    });
+
+    return s;
+  }
+
+  function enhance() {
+    const blocks = document.querySelectorAll('.css-view');
+    blocks.forEach((el) => {
+      try {
+        if (el.dataset.highlighted === '1') return;
+        const text = el.textContent || '';
+        // If already contains spans, assume it's already highlighted.
+        if (el.innerHTML.includes('<span')) {
+          el.dataset.highlighted = '1';
+          return;
+        }
+        el.innerHTML = highlightCSS(text);
+        el.dataset.highlighted = '1';
+      } catch (_) {
+        // Never break the page; just skip highlighting if anything goes wrong.
+      }
+    });
+  }
+
+  // Expose for pages that want to re-run manually
+  window.__highlightCssViews = enhance;
+
+  if (document.readyState === 'loading') {
+    document.addEventListener('DOMContentLoaded', enhance, { once: true });
+  } else {
+    enhance();
+  }
+
+  // Re-run when switching to the CSS tab (covers cases where the page
+  // injects/replaces code blocks after load or during navigation).
+  document.addEventListener(
+    'click',
+    (e) => {
+      const tab = e.target && e.target.closest ? e.target.closest('.tab') : null;
+      if (!tab) return;
+      const label = (tab.textContent || '').trim().toLowerCase();
+      if (label === 'css') {
+        // Defer so the tab switch can toggle DOM/classes first
+        setTimeout(enhance, 0);
+      }
+    },
+    true
+  );
+
+  // Observe DOM changes to catch dynamically added `.css-view` blocks.
+  try {
+    const mo = new MutationObserver(() => {
+      // Cheap debounce via microtask
+      Promise.resolve().then(enhance);
+    });
+    mo.observe(document.documentElement, {
+      subtree: true,
+      childList: true,
+      characterData: true
+    });
+  } catch (_) {}
+})();
+

+ 242 - 2
doc/index.html

@@ -15,6 +15,7 @@
             --header-color: #666;
             --middle-col-bg: #161616;
             --tree-line-color: #333;
+            --accent-color: #ff9f43;
         }
         
         body {
@@ -33,7 +34,7 @@
         .sidebar-left {
             width: 260px;
             background: var(--sidebar-bg);
-            border-right: 1px solid var(--border-color);
+          
             display: flex;
             flex-direction: column;
             overflow-y: auto;
@@ -147,7 +148,7 @@
         .sidebar-middle {
             width: 320px;
             background: var(--middle-col-bg);
-            border-right: 1px solid var(--border-color);
+           
             display: flex;
             flex-direction: column;
             overflow: hidden;
@@ -175,6 +176,32 @@
             height: 100%;
         }
 
+        /* Connection line between middle and right columns */
+        .column-connection-line {
+            position: fixed;
+            inset: 0;
+            width: 100vw;
+            height: 100vh;
+            pointer-events: none;
+            z-index: 20;
+            opacity: 0.85;
+        }
+
+        .column-connection-path {
+            stroke: var(--accent-color);
+            stroke-width: 2;
+            stroke-dasharray: 6 6;
+            stroke-linecap: round;
+            fill: none;
+            opacity: 0.35;
+            animation: dashFlow 1.1s linear infinite;
+            filter: drop-shadow(0 0 2px rgba(255, 159, 67, 0.25));
+        }
+
+        @keyframes dashFlow {
+            to { stroke-dashoffset: -12; }
+        }
+
     </style>
 </head>
 <body>
@@ -259,6 +286,11 @@
         <iframe id="content-frame" src="targets/test_css_selector.html"></iframe>
     </div>
 
+    <!-- SVG overlay for the middle→right connection line -->
+    <svg id="column-connection-line" class="column-connection-line" aria-hidden="true">
+        <path id="column-connection-path" class="column-connection-path" d=""></path>
+    </svg>
+
     <script>
         function toggleTree(header) {
             const children = header.nextElementSibling;
@@ -295,6 +327,9 @@
             try {
                 setMiddleActive(url);
             } catch {}
+
+            // Update connection line after navigation
+            scheduleConnectionLineUpdate();
         }
 
         // Called by content iframe pages (and also internally after navigation)
@@ -305,6 +340,198 @@
             if (win && typeof win.setActiveByContentUrl === 'function') {
                 win.setActiveByContentUrl(contentUrl);
             }
+
+            // Update connection line when active item changes
+            scheduleConnectionLineUpdate();
+        }
+
+        // =============================
+        // Middle ↔ Right connection line
+        // =============================
+        const connectionSvg = document.getElementById('column-connection-line');
+        const connectionPath = document.getElementById('column-connection-path');
+        let _connRaf = 0;
+
+        function scheduleConnectionLineUpdate() {
+            if (_connRaf) return;
+            _connRaf = requestAnimationFrame(() => {
+                _connRaf = 0;
+                updateConnectionLine();
+            });
+        }
+
+        function getActiveElementInFrame(frameEl) {
+            try {
+                const doc = frameEl?.contentDocument;
+                if (!doc) return null;
+                // Middle list pages use .card.active / .header-card.active; fall back to .active
+                return (
+                    doc.querySelector('.card.active') ||
+                    doc.querySelector('.header-card.active') ||
+                    doc.querySelector('.nav-item.active') ||
+                    doc.querySelector('.active')
+                );
+            } catch {
+                return null;
+            }
+        }
+
+        function getAnchorElementInContentFrame(frameEl) {
+            try {
+                const doc = frameEl?.contentDocument;
+                if (!doc) return null;
+                // Prefer a *visible* code area anchor (in-viewport), then fall back
+                const candidates = [
+                    '.box-container .box-header',
+                    '.code-view.active, .html-view.active, .css-view.active',
+                    '.box-container',
+                    'h1',
+                    '.container',
+                    'body'
+                ];
+                for (const sel of candidates) {
+                    const el = doc.querySelector(sel);
+                    if (!el) continue;
+                    // If it's visible in the iframe viewport, use it; else keep looking
+                    const r = el.getBoundingClientRect();
+                    const vh = frameEl?.clientHeight || 0;
+                    if (!vh) return el;
+                    const visibleTop = Math.max(0, r.top);
+                    const visibleBottom = Math.min(vh, r.bottom);
+                    if (visibleBottom - visibleTop > 8) return el;
+                }
+                return doc.body;
+            } catch {
+                return null;
+            }
+        }
+
+        function clamp(n, min, max) {
+            return Math.min(Math.max(n, min), max);
+        }
+
+        function getVisibleCenterInFrame(frameEl, elRect) {
+            const w = frameEl?.clientWidth || 0;
+            const h = frameEl?.clientHeight || 0;
+            if (!w || !h) return null;
+
+            const visibleLeft = Math.max(0, elRect.left);
+            const visibleRight = Math.min(w, elRect.right);
+            const visibleTop = Math.max(0, elRect.top);
+            const visibleBottom = Math.min(h, elRect.bottom);
+
+            if (visibleRight - visibleLeft <= 1 || visibleBottom - visibleTop <= 1) {
+                return null;
+            }
+
+            return {
+                x: (visibleLeft + visibleRight) / 2,
+                y: (visibleTop + visibleBottom) / 2
+            };
+        }
+
+        function updateConnectionLine() {
+            if (!connectionSvg || !connectionPath) return;
+
+            const middleFrame = document.getElementById('middle-frame');
+            const contentFrame = document.getElementById('content-frame');
+            if (!middleFrame || !contentFrame) return;
+
+            const midRect = middleFrame.getBoundingClientRect();
+            const rightRect = contentFrame.getBoundingClientRect();
+
+            const active = getActiveElementInFrame(middleFrame);
+            const anchor = getAnchorElementInContentFrame(contentFrame);
+            if (!active || !anchor) {
+                connectionPath.setAttribute('d', '');
+                connectionSvg.style.opacity = '0';
+                return;
+            }
+
+            const activeRect = active.getBoundingClientRect();
+            const anchorRect = anchor.getBoundingClientRect();
+
+            // Use the *visible* part of the element within its iframe viewport
+            const activeCenter = getVisibleCenterInFrame(middleFrame, activeRect);
+            const anchorCenter = getVisibleCenterInFrame(contentFrame, anchorRect);
+            if (!activeCenter || !anchorCenter) {
+                // If either side isn't visible in its iframe, hide the line (prevents weird drifting)
+                connectionPath.setAttribute('d', '');
+                connectionSvg.style.opacity = '0';
+                return;
+            }
+
+            // Convert iframe-viewport coordinates to parent viewport coordinates
+            const startX = midRect.left + clamp(activeRect.right, 0, middleFrame.clientWidth);
+            const startY = midRect.top + activeCenter.y;
+
+            // Point into the code area a bit (not exactly at left edge)
+            const endX = rightRect.left + clamp(anchorRect.left + 18, 12, contentFrame.clientWidth - 12);
+            const endY = rightRect.top + anchorCenter.y;
+
+            // Draw an L-shaped polyline.
+            // Keep the vertical segment aligned with the column divider (middle ↔ right).
+            const dividerX = rightRect.left + 0.5; // half-pixel for crisper stroke alignment
+            const x1 = Math.max(startX + 12, dividerX);
+            const d = [
+                `M ${startX} ${startY}`,
+                `L ${x1} ${startY}`,
+                `L ${x1} ${endY}`,
+                `L ${endX} ${endY}`
+            ].join(' ');
+
+            connectionPath.setAttribute('d', d);
+            connectionSvg.style.opacity = '0.85';
+        }
+
+        function attachFrameScrollListeners(frameEl) {
+            if (!frameEl) return;
+            const onScroll = () => {
+                markFrameScrolling(frameEl);
+                scheduleConnectionLineUpdate();
+            };
+            try {
+                // Window scroll inside iframe
+                frameEl.contentWindow?.addEventListener('scroll', onScroll, { passive: true });
+                // Some pages scroll on document/body; capture to be safe
+                frameEl.contentDocument?.addEventListener('scroll', onScroll, true);
+                frameEl.contentDocument?.documentElement?.addEventListener?.('scroll', onScroll, { passive: true });
+                frameEl.contentDocument?.body?.addEventListener?.('scroll', onScroll, { passive: true });
+            } catch {}
+        }
+
+        const _scrollHideTimers = new WeakMap();
+        function markFrameScrolling(frameEl) {
+            try {
+                const doc = frameEl?.contentDocument;
+                if (!doc) return;
+                doc.documentElement?.classList.add('scrolling');
+                doc.body?.classList.add('scrolling');
+                const prev = _scrollHideTimers.get(frameEl);
+                if (prev) clearTimeout(prev);
+                _scrollHideTimers.set(frameEl, setTimeout(() => {
+                    try {
+                        doc.documentElement?.classList.remove('scrolling');
+                        doc.body?.classList.remove('scrolling');
+                    } catch {}
+                }, 500));
+            } catch {}
+        }
+
+        // Always-on lightweight loop to keep the line aligned even
+        // if iframe scroll events were missed due to timing.
+        let _connLoopStarted = false;
+        function startConnectionLineLoop() {
+            if (_connLoopStarted) return;
+            _connLoopStarted = true;
+            const loop = () => {
+                // Only bother when we have a path (avoids work when hidden)
+                if (connectionPath?.getAttribute('d')) {
+                    scheduleConnectionLineUpdate();
+                }
+                requestAnimationFrame(loop);
+            };
+            requestAnimationFrame(loop);
         }
 
         // Make initial active state robust against iframe load ordering
@@ -318,6 +545,8 @@
                 middleFrame.addEventListener('load', () => {
                     const current = document.getElementById('content-frame')?.getAttribute('src');
                     if (current) setMiddleActive(current);
+                    attachFrameScrollListeners(middleFrame);
+                    scheduleConnectionLineUpdate();
                 });
             }
 
@@ -325,8 +554,19 @@
                 contentFrame.addEventListener('load', () => {
                     const current = contentFrame.getAttribute('src');
                     if (current) setMiddleActive(current);
+                    attachFrameScrollListeners(contentFrame);
+                    scheduleConnectionLineUpdate();
                 });
             }
+
+            // If the iframes were already loaded before we attached 'load' listeners,
+            // still attach scroll listeners immediately.
+            attachFrameScrollListeners(middleFrame);
+            attachFrameScrollListeners(contentFrame);
+
+            window.addEventListener('resize', scheduleConnectionLineUpdate, { passive: true });
+            scheduleConnectionLineUpdate();
+            startConnectionLineLoop();
         });
     </script>
 </body>

+ 90 - 10
doc/list.css

@@ -1,5 +1,68 @@
 /* Common styles for List Pages (Middle Column) */
 
+/* Auto-hide scrollbar (shown only on scroll/hover via parent) */
+html, body {
+    scrollbar-width: none; /* Firefox (hidden by default) */
+    -ms-overflow-style: none; /* IE/old Edge */
+}
+
+html::-webkit-scrollbar,
+body::-webkit-scrollbar {
+    width: 0 !important;
+    height: 0 !important;
+}
+
+/* Some list pages may scroll a nested container; hide vertical scrollbars by default */
+*::-webkit-scrollbar:vertical {
+    width: 0 !important;
+}
+
+/* Shown when scrolling (class is toggled by doc/index.html) */
+html.scrolling,
+body.scrolling {
+    scrollbar-width: thin; /* Firefox */
+    scrollbar-color: rgba(0, 0, 0, 0.9) transparent;
+}
+
+html.scrolling::-webkit-scrollbar,
+body.scrolling::-webkit-scrollbar {
+    width: 6px !important;
+    height: 6px !important;
+}
+
+html.scrolling::-webkit-scrollbar-track,
+body.scrolling::-webkit-scrollbar-track {
+    background: transparent;
+}
+
+html.scrolling::-webkit-scrollbar-thumb,
+body.scrolling::-webkit-scrollbar-thumb {
+    background: rgba(0, 0, 0, 0.95) !important;
+    border-radius: 999px;
+}
+
+html.scrolling::-webkit-scrollbar-thumb:hover,
+body.scrolling::-webkit-scrollbar-thumb:hover {
+    background: rgba(0, 0, 0, 1);
+}
+
+/* Show vertical scrollbars (narrow + black) for any scrolled container while scrolling */
+html.scrolling *::-webkit-scrollbar:vertical,
+body.scrolling *::-webkit-scrollbar:vertical {
+    width: 6px !important;
+}
+
+html.scrolling *::-webkit-scrollbar-thumb,
+body.scrolling *::-webkit-scrollbar-thumb {
+    background: rgba(0, 0, 0, 0.95) !important;
+    border-radius: 999px;
+}
+
+html.scrolling *::-webkit-scrollbar-track,
+body.scrolling *::-webkit-scrollbar-track {
+    background: transparent !important;
+}
+
 body {
     padding: 20px;
     display: block;
@@ -86,27 +149,42 @@ body {
 
 /* Cards */
 .card {
-    background: #1a1a1a;
+    background: rgba(26, 26, 26, 0.78);
     border-radius: 8px;
     padding: 20px;
     display: flex;
     flex-direction: column;
     cursor: pointer;
-    transition: background 0.2s, border-color 0.2s;
+    transition: background 0.2s, border-color 0.2s, box-shadow 0.2s;
     position: relative;
     min-height: 80px;
     justify-content: space-between;
-    border: 1px solid transparent;
+    border: 1px solid #303033;
+    overflow: hidden;
+}
+
+.card::before {
+    content: '';
+    position: absolute;
+    left: 0;
+    right: 0;
+    top: 0;
+    height: 1px;
+    background: linear-gradient(90deg, transparent, rgba(13, 255, 0, 0.22), transparent);
+    opacity: 0.55;
+    pointer-events: none;
 }
 
 .card:hover {
-    background: #222;
-    border-color: #333;
+    background: rgba(34, 34, 34, 0.82);
+    border-color: rgba(255, 159, 67, 0.28);
+    box-shadow: 0 6px 18px rgba(0, 0, 0, 0.35);
 }
 
 .card.active {
     border-color: var(--orange, #FF8F42);
-    background: #222;
+    background: linear-gradient(45deg, rgba(255, 159, 67, 0.08), rgba(34, 34, 34, 0.88), rgba(255, 159, 67, 0.06));
+    box-shadow: 0 0 0 1px rgba(255, 159, 67, 0.06), 0 10px 26px rgba(0, 0, 0, 0.35);
 }
 
 .card-footer {
@@ -184,19 +262,21 @@ body {
 /* Header Card Specifics */
 .header-card {
     height: 100px;
-    background-color: #2a251c; /* Dark orange/brown tint */
-    border: 1px solid #4a3b2a;
+    background: linear-gradient(135deg, rgba(42, 37, 28, 0.92), rgba(22, 22, 22, 0.92));
+    border: 1px solid rgba(255, 159, 67, 0.18);
     border-radius: 8px;
     position: relative;
     cursor: pointer;
     overflow: hidden;
-    transition: border-color 0.2s;
+    transition: border-color 0.2s, box-shadow 0.2s, background 0.2s;
 }
 .header-card.active {
     border-color: var(--orange, #FF8F42);
+    box-shadow: 0 0 0 1px rgba(255, 159, 67, 0.08), 0 14px 30px rgba(0, 0, 0, 0.35);
 }
 .header-card:hover {
-    border-color: #665;
+    border-color: rgba(255, 159, 67, 0.35);
+    box-shadow: 0 10px 26px rgba(0, 0, 0, 0.35);
 }
 
 .header-dots {

+ 39 - 3
doc/targets/test_array_targets.html

@@ -34,6 +34,7 @@
                 <div class="tabs">
                     <div class="tab active" onclick="switchTab('js')">JavaScript</div>
                     <div class="tab" onclick="switchTab('html')">HTML</div>
+                    <div class="tab" onclick="switchTab('css')">CSS</div>
                 </div>
             </div>
 
@@ -51,6 +52,19 @@
 <span class="tag">&lt;div</span> <span class="attr">class</span>=<span class="val">"el-2"</span><span class="tag">&gt;&lt;/div&gt;</span>
             </div>
 
+            <pre id="css-code" class="css-view">.el {
+  width: 28px;
+  height: 28px;
+  background-color: var(--highlight-color);
+  border-radius: 50%;
+  margin: 10px 0;
+}
+.demo-visual { padding: 40px; }</pre>
+
+            <div class="feature-desc">
+                <strong>功能说明:</strong>通过数组一次性指定多个目标(DOM/NodeList/对象混合也可以),适合手动组合 targets,或把多个来源的元素合并后统一动画。
+            </div>
+
             <!-- Live Demo -->
             <div class="demo-visual">
                 <div class="el el-1"></div>
@@ -59,26 +73,34 @@
             
             <div class="action-bar">
                 <button class="play-btn" onclick="runDemo()">REPLAY</button>
+                <button class="play-btn secondary" onclick="pauseDemo()">PAUSE</button>
+                <button class="play-btn secondary" onclick="resumeDemo()">RESUME</button>
             </div>
         </div>
 
     </div>
 
+    <script src="../highlight_css.js"></script>
     <script src="../../xjs.js"></script>
     <script>
         function switchTab(tab) {
             document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
-            document.querySelectorAll('.code-view, .html-view').forEach(v => v.classList.remove('active'));
+            document.querySelectorAll('.code-view, .html-view, .css-view').forEach(v => v.classList.remove('active'));
             
             if (tab === 'js') {
                 document.querySelector('.tabs .tab:nth-child(1)').classList.add('active');
                 document.getElementById('js-code').classList.add('active');
-            } else {
+            } else if (tab === 'html') {
                 document.querySelector('.tabs .tab:nth-child(2)').classList.add('active');
                 document.getElementById('html-code').classList.add('active');
+            } else {
+                document.querySelector('.tabs .tab:nth-child(3)').classList.add('active');
+                document.getElementById('css-code').classList.add('active');
             }
         }
 
+        let __demoControls = [];
+
         function runDemo() {
             // Reset
             document.querySelectorAll('.el').forEach(el => {
@@ -88,10 +110,24 @@
                         const el1 = document.querySelector('.el-1');
             const el2 = document.querySelector('.el-2');
             
-            xjs([el1, el2]).animate({ 
+            __demoControls = [];
+            __demoControls.push(xjs([el1, el2]).animate({ 
                 x: 200, 
                 duration: 1000,
                 easing: 'ease-out'
+            }));
+        }
+
+        function pauseDemo() {
+            __demoControls.forEach(c => { try { c?.pause?.(); } catch {} });
+        }
+
+        function resumeDemo() {
+            __demoControls.forEach(c => {
+                try {
+                    if (typeof c?.resume === 'function') c.resume();
+                    else if (typeof c?.play === 'function') c.play();
+                } catch {}
             });
         }
         

+ 121 - 25
doc/targets/test_css_selector.html

@@ -85,6 +85,7 @@
                     <div class="tabs" role="tablist" aria-label="Code tabs">
                         <div class="tab active" role="tab" aria-selected="true" tabindex="0" onclick="switchTab('js')">JavaScript</div>
                         <div class="tab" role="tab" aria-selected="false" tabindex="-1" onclick="switchTab('html')">HTML</div>
+                        <div class="tab" role="tab" aria-selected="false" tabindex="-1" onclick="switchTab('css')">CSS</div>
                     </div>
                 </div>
             </div>
@@ -111,6 +112,50 @@
 <span class="tag">&lt;/div&gt;</span>
             </div>
 
+            <!-- CSS Code View -->
+            <pre id="css-code" class="css-view">.row {
+  position: relative;
+  padding: 18px 0;
+  border-bottom: 1px solid #222;
+  display: flex;
+  align-items: center;
+  min-height: 48px;
+}
+.row:last-child { border-bottom: none; }
+
+.row::before {
+  content: '';
+  position: absolute;
+  left: 0;
+  right: 0;
+  top: 50%;
+  height: 1px;
+  background: #262626;
+  transform: translateY(-0.5px);
+}
+
+.square {
+  position: relative;
+  z-index: 1;
+  width: 18px;
+  height: 18px;
+  background-color: var(--highlight-color);
+  border-radius: 3px;
+  will-change: transform;
+}
+
+.sq-lg { width: 28px; height: 28px; border-radius: 4px; }
+.sq-md { width: 20px; height: 20px; border-radius: 3px; }
+.sq-sm { width: 14px; height: 14px; border-radius: 3px; }
+
+#css-selector-id { background-color: var(--accent-color); }
+
+.demo-visual { padding: 26px 30px; background: #151515; }</pre>
+
+            <div class="feature-desc">
+                <strong>功能说明:</strong>通过 CSS 选择器(字符串)批量选择目标元素,针对不同 selector 可以分别创建动画;适合对一组 DOM 元素做统一或分层动画。
+            </div>
+
             <!-- Live Demo -->
             <div class="demo-visual">
                 <!-- Row 1 -->
@@ -129,6 +174,8 @@
             
             <div class="action-bar">
                 <button class="play-btn" onclick="runDemo()">REPLAY</button>
+                <button class="play-btn secondary" onclick="pauseDemo()">PAUSE</button>
+                <button class="play-btn secondary" onclick="resumeDemo()">RESUME</button>
             </div>
         </div>
 
@@ -154,26 +201,40 @@
 
     <div id="toast" class="toast" role="status" aria-live="polite"></div>
 
+    <script src="../highlight_css.js"></script>
     <script src="../../xjs.js"></script>
     <script>
         function switchTab(tab) {
             document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
-            document.querySelectorAll('.code-view, .html-view').forEach(v => v.classList.remove('active'));
+            document.querySelectorAll('.code-view, .html-view, .css-view').forEach(v => v.classList.remove('active'));
             
             if (tab === 'js') {
-                document.querySelector('.tabs .tab:nth-child(1)').classList.add('active');
+                document.querySelector('.tabs .tab:nth-child(1)')?.classList.add('active');
                 document.getElementById('js-code').classList.add('active');
-                document.querySelector('.tabs .tab:nth-child(1)').setAttribute('aria-selected', 'true');
-                document.querySelector('.tabs .tab:nth-child(2)').setAttribute('aria-selected', 'false');
-                document.querySelector('.tabs .tab:nth-child(1)').tabIndex = 0;
-                document.querySelector('.tabs .tab:nth-child(2)').tabIndex = -1;
-            } else {
-                document.querySelector('.tabs .tab:nth-child(2)').classList.add('active');
+                document.querySelector('.tabs .tab:nth-child(1)')?.setAttribute('aria-selected', 'true');
+                document.querySelector('.tabs .tab:nth-child(2)')?.setAttribute('aria-selected', 'false');
+                document.querySelector('.tabs .tab:nth-child(3)')?.setAttribute('aria-selected', 'false');
+                document.querySelector('.tabs .tab:nth-child(1)') && (document.querySelector('.tabs .tab:nth-child(1)').tabIndex = 0);
+                document.querySelector('.tabs .tab:nth-child(2)') && (document.querySelector('.tabs .tab:nth-child(2)').tabIndex = -1);
+                document.querySelector('.tabs .tab:nth-child(3)') && (document.querySelector('.tabs .tab:nth-child(3)').tabIndex = -1);
+            } else if (tab === 'html') {
+                document.querySelector('.tabs .tab:nth-child(2)')?.classList.add('active');
                 document.getElementById('html-code').classList.add('active');
-                document.querySelector('.tabs .tab:nth-child(1)').setAttribute('aria-selected', 'false');
-                document.querySelector('.tabs .tab:nth-child(2)').setAttribute('aria-selected', 'true');
-                document.querySelector('.tabs .tab:nth-child(1)').tabIndex = -1;
-                document.querySelector('.tabs .tab:nth-child(2)').tabIndex = 0;
+                document.querySelector('.tabs .tab:nth-child(1)')?.setAttribute('aria-selected', 'false');
+                document.querySelector('.tabs .tab:nth-child(2)')?.setAttribute('aria-selected', 'true');
+                document.querySelector('.tabs .tab:nth-child(3)')?.setAttribute('aria-selected', 'false');
+                document.querySelector('.tabs .tab:nth-child(1)') && (document.querySelector('.tabs .tab:nth-child(1)').tabIndex = -1);
+                document.querySelector('.tabs .tab:nth-child(2)') && (document.querySelector('.tabs .tab:nth-child(2)').tabIndex = 0);
+                document.querySelector('.tabs .tab:nth-child(3)') && (document.querySelector('.tabs .tab:nth-child(3)').tabIndex = -1);
+            } else {
+                document.querySelector('.tabs .tab:nth-child(3)')?.classList.add('active');
+                document.getElementById('css-code')?.classList.add('active');
+                document.querySelector('.tabs .tab:nth-child(1)')?.setAttribute('aria-selected', 'false');
+                document.querySelector('.tabs .tab:nth-child(2)')?.setAttribute('aria-selected', 'false');
+                document.querySelector('.tabs .tab:nth-child(3)')?.setAttribute('aria-selected', 'true');
+                document.querySelector('.tabs .tab:nth-child(1)') && (document.querySelector('.tabs .tab:nth-child(1)').tabIndex = -1);
+                document.querySelector('.tabs .tab:nth-child(2)') && (document.querySelector('.tabs .tab:nth-child(2)').tabIndex = -1);
+                document.querySelector('.tabs .tab:nth-child(3)') && (document.querySelector('.tabs .tab:nth-child(3)').tabIndex = 0);
             }
         }
 
@@ -187,7 +248,7 @@
         }
 
         async function copyActiveCode() {
-            const active = document.querySelector('.code-view.active, .html-view.active');
+            const active = document.querySelector('.code-view.active, .html-view.active, .css-view.active');
             const text = active ? active.innerText.trimEnd() : '';
             if (!text) return;
 
@@ -212,29 +273,55 @@
             document.querySelectorAll('.square').forEach(el => {
                 el.style.transform = 'none';
             });
-            
-                        xjs('.square').animate({ 
+
+            const controls = [];
+
+            controls.push(xjs('.square').animate({ 
                 x: '17rem', 
                 duration: 1000,
                 easing: 'ease-out'
-            });
+            }));
 
-            xjs('#css-selector-id').animate({ 
+            controls.push(xjs('#css-selector-id').animate({ 
                 rotate: '1turn', 
                 duration: 1000,
                 easing: 'ease-out'
-            });
+            }));
 
-            xjs('.medium.row:nth-child(3) .square').animate({
+            const ctl3 = xjs('.medium.row:nth-child(3) .square').animate({
                 scale: [1, .5],
                 duration: 500,
                 easing: 'ease-in-out'
-            }).then(() => {
-                xjs('.medium.row:nth-child(3) .square').animate({
+            });
+            controls.push(ctl3);
+
+            // Keep the old demo behavior (second phase) but preserve a pause/resume handle.
+            ctl3.then(() => {
+                const ctl4 = xjs('.medium.row:nth-child(3) .square').animate({
                     scale: [.5, 1],
                     duration: 500,
                     easing: 'ease-in-out'
                 });
+                controls.push(ctl4);
+            });
+
+            window.__demoControls = controls;
+        }
+
+        function pauseDemo() {
+            const controls = window.__demoControls || [];
+            controls.forEach(c => {
+                try { c?.pause?.(); } catch {}
+            });
+        }
+
+        function resumeDemo() {
+            const controls = window.__demoControls || [];
+            controls.forEach(c => {
+                try {
+                    if (typeof c?.resume === 'function') c.resume();
+                    else if (typeof c?.play === 'function') c.play();
+                } catch {}
             });
         }
 
@@ -256,10 +343,19 @@
             if (!focused || !focused.classList.contains('tab')) return;
             if (e.key !== 'ArrowLeft' && e.key !== 'ArrowRight') return;
             e.preventDefault();
-            const isJs = focused.textContent.trim().toLowerCase().includes('javascript');
-            switchTab(isJs ? 'html' : 'js');
-            const next = document.querySelector('.tab.active');
-            next?.focus();
+            const tabs = Array.from(document.querySelectorAll('.tabs .tab'));
+            const idx = Math.max(0, tabs.indexOf(focused));
+            const nextIdx = e.key === 'ArrowRight'
+                ? (idx + 1) % tabs.length
+                : (idx - 1 + tabs.length) % tabs.length;
+            const next = tabs[nextIdx];
+            const lang = next?.textContent?.trim().toLowerCase().includes('html')
+                ? 'html'
+                : next?.textContent?.trim().toLowerCase().includes('css')
+                    ? 'css'
+                    : 'js';
+            switchTab(lang);
+            document.querySelector('.tab.active')?.focus();
         });
 
         // Sync middle list selection when loaded inside doc/index.html

+ 41 - 5
doc/targets/test_dom_elements.html

@@ -34,6 +34,7 @@
                 <div class="tabs">
                     <div class="tab active" onclick="switchTab('js')">JavaScript</div>
                     <div class="tab" onclick="switchTab('html')">HTML</div>
+                    <div class="tab" onclick="switchTab('css')">CSS</div>
                 </div>
             </div>
 
@@ -58,6 +59,19 @@
 <span class="tag">&lt;div</span> <span class="attr">class</span>=<span class="val">"dom-node-list"</span><span class="tag">&gt;&lt;/div&gt;</span>
             </div>
 
+            <pre id="css-code" class="css-view">.el {
+  width: 28px;
+  height: 28px;
+  background-color: var(--highlight-color);
+  border-radius: 2px;
+  margin: 10px 0;
+}
+.demo-visual { padding: 40px; }</pre>
+
+            <div class="feature-desc">
+                <strong>功能说明:</strong>直接把 DOM 节点或 NodeList 作为 targets 传入,适合已经拿到元素引用(不依赖选择器)的场景。
+            </div>
+
             <!-- Live Demo -->
             <div class="demo-visual">
                 <div class="el dom-node" style="background: #ff9f43"></div>
@@ -67,26 +81,34 @@
             
             <div class="action-bar">
                 <button class="play-btn" onclick="runDemo()">REPLAY</button>
+                <button class="play-btn secondary" onclick="pauseDemo()">PAUSE</button>
+                <button class="play-btn secondary" onclick="resumeDemo()">RESUME</button>
             </div>
         </div>
 
     </div>
 
+    <script src="../highlight_css.js"></script>
     <script src="../../xjs.js"></script>
     <script>
         function switchTab(tab) {
             document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
-            document.querySelectorAll('.code-view, .html-view').forEach(v => v.classList.remove('active'));
+            document.querySelectorAll('.code-view, .html-view, .css-view').forEach(v => v.classList.remove('active'));
             
             if (tab === 'js') {
                 document.querySelector('.tabs .tab:nth-child(1)').classList.add('active');
                 document.getElementById('js-code').classList.add('active');
-            } else {
+            } else if (tab === 'html') {
                 document.querySelector('.tabs .tab:nth-child(2)').classList.add('active');
                 document.getElementById('html-code').classList.add('active');
+            } else {
+                document.querySelector('.tabs .tab:nth-child(3)').classList.add('active');
+                document.getElementById('css-code').classList.add('active');
             }
         }
 
+        let __demoControls = [];
+
         function runDemo() {
             document.querySelectorAll('.el').forEach(el => {
                 el.style.transform = 'none';
@@ -95,17 +117,31 @@
                         const element = document.querySelector('.dom-node');
             const elements = document.querySelectorAll('.dom-node-list');
             
-            xjs(element).animate({ 
+            __demoControls = [];
+            __demoControls.push(xjs(element).animate({ 
                 x: 200, 
                 duration: 1000,
                 easing: 'ease-out'
-            });
+            }));
 
-            xjs(elements).animate({ 
+            __demoControls.push(xjs(elements).animate({ 
                 x: 200, 
                 duration: 1000,
                 delay: 200,
                 easing: 'ease-out'
+            }));
+        }
+
+        function pauseDemo() {
+            __demoControls.forEach(c => { try { c?.pause?.(); } catch {} });
+        }
+
+        function resumeDemo() {
+            __demoControls.forEach(c => {
+                try {
+                    if (typeof c?.resume === 'function') c.resume();
+                    else if (typeof c?.play === 'function') c.play();
+                } catch {}
             });
         }
         

+ 44 - 3
doc/targets/test_js_objects.html

@@ -28,6 +28,7 @@
                 <div class="tabs">
                     <div class="tab active" onclick="switchTab('js')">JavaScript</div>
                     <div class="tab" onclick="switchTab('html')">HTML</div>
+                    <div class="tab" onclick="switchTab('css')">CSS</div>
                 </div>
             </div>
 
@@ -46,6 +47,24 @@
 <span class="tag">&lt;div</span> <span class="attr">id</span>=<span class="val">"log"</span><span class="tag">&gt;&lt;/div&gt;</span>
             </div>
 
+            <pre id="css-code" class="css-view">.demo-visual {
+  padding: 40px;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+}
+
+.log-display {
+  font-family: monospace;
+  color: #fff;
+  font-size: 24px;
+}</pre>
+
+            <div class="feature-desc">
+                <strong>功能说明:</strong>以普通 JS 对象作为目标,动画会逐帧更新对象上的数值字段;适合把动画数值“驱动”到任意渲染逻辑中(canvas / webgl / 自定义组件)。
+            </div>
+
             <!-- Live Demo -->
             <div class="demo-visual">
                 <div class="log-display">
@@ -55,37 +74,59 @@
             
             <div class="action-bar">
                 <button class="play-btn" onclick="runDemo()">REPLAY</button>
+                <button class="play-btn secondary" onclick="pauseDemo()">PAUSE</button>
+                <button class="play-btn secondary" onclick="resumeDemo()">RESUME</button>
             </div>
         </div>
 
     </div>
 
+    <script src="../highlight_css.js"></script>
     <script src="../../xjs.js"></script>
     <script>
         function switchTab(tab) {
             document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
-            document.querySelectorAll('.code-view, .html-view').forEach(v => v.classList.remove('active'));
+            document.querySelectorAll('.code-view, .html-view, .css-view').forEach(v => v.classList.remove('active'));
             
             if (tab === 'js') {
                 document.querySelector('.tabs .tab:nth-child(1)').classList.add('active');
                 document.getElementById('js-code').classList.add('active');
-            } else {
+            } else if (tab === 'html') {
                 document.querySelector('.tabs .tab:nth-child(2)').classList.add('active');
                 document.getElementById('html-code').classList.add('active');
+            } else {
+                document.querySelector('.tabs .tab:nth-child(3)').classList.add('active');
+                document.getElementById('css-code').classList.add('active');
             }
         }
 
+        let __demoControls = [];
+
         function runDemo() {
             const logEl = document.getElementById('log-val');
             const obj = { prop: 0 };
                         
-            xjs(obj).animate({ 
+            __demoControls = [];
+            __demoControls.push(xjs(obj).animate({ 
                 prop: 100, 
                 duration: 2000,
                 easing: 'linear',
                 update: () => {
                     logEl.textContent = Math.round(obj.prop);
                 }
+            }));
+        }
+
+        function pauseDemo() {
+            __demoControls.forEach(c => { try { c?.pause?.(); } catch {} });
+        }
+
+        function resumeDemo() {
+            __demoControls.forEach(c => {
+                try {
+                    if (typeof c?.resume === 'function') c.resume();
+                    else if (typeof c?.play === 'function') c.play();
+                } catch {}
             });
         }
         

+ 51 - 3
doc/test_on_update.html

@@ -63,6 +63,7 @@ xjs('.box').animate({
                 <div class="tabs">
                     <div class="tab active" onclick="switchTab('js')">JavaScript</div>
                     <div class="tab" onclick="switchTab('html')">HTML</div>
+                    <div class="tab" onclick="switchTab('css')">CSS</div>
                 </div>
             </div>
 
@@ -87,6 +88,31 @@ xjs('.box').animate({
 <span class="tag">&lt;div</span> <span class="attr">class</span>=<span class="val">"circle"</span><span class="tag">&gt;&lt;/div&gt;</span>
             </div>
 
+            <pre id="css-code" class="css-view">.circle {
+  width: 50px;
+  height: 50px;
+  background-color: var(--highlight-color);
+  border-radius: 50%;
+}
+.value {
+  font-size: 20px;
+  font-weight: bold;
+  color: #fff;
+  margin-bottom: 20px;
+  font-family: monospace;
+}
+.demo-content {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  width: 100%;
+}</pre>
+
+            <div class="feature-desc">
+                <strong>功能说明:</strong>通过 <code class="inline">begin/update/complete</code> 回调拿到动画进度与时间,适合做进度条、数字跟随、状态同步等“联动”效果。
+            </div>
+
             <!-- Live Demo -->
             <div class="demo-visual">
                 <div class="demo-content">
@@ -97,11 +123,14 @@ xjs('.box').animate({
             
             <div class="action-bar">
                 <button class="play-btn" onclick="runDemo()">REPLAY</button>
+                <button class="play-btn secondary" onclick="pauseDemo()">PAUSE</button>
+                <button class="play-btn secondary" onclick="resumeDemo()">RESUME</button>
             </div>
         </div>
 
     </div>
 
+    <script src="highlight_css.js"></script>
     <script src="../xjs.js"></script>
     <script>
         const utils = {
@@ -110,24 +139,30 @@ xjs('.box').animate({
 
         function switchTab(tab) {
             document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
-            document.querySelectorAll('.code-view, .html-view').forEach(v => v.classList.remove('active'));
+            document.querySelectorAll('.code-view, .html-view, .css-view').forEach(v => v.classList.remove('active'));
             
             if (tab === 'js') {
                 document.querySelector('.tabs .tab:nth-child(1)').classList.add('active');
                 document.getElementById('js-code').classList.add('active');
-            } else {
+            } else if (tab === 'html') {
                 document.querySelector('.tabs .tab:nth-child(2)').classList.add('active');
                 document.getElementById('html-code').classList.add('active');
+            } else {
+                document.querySelector('.tabs .tab:nth-child(3)').classList.add('active');
+                document.getElementById('css-code').classList.add('active');
             }
         }
 
+        let __demoControls = [];
+
         function runDemo() {
                         const $value = document.querySelector('.value');
             const $circle = document.querySelector('.circle');
             $circle.style.transform = 'none';
             $value.textContent = '0';
 
-            xjs('.circle').animate({
+            __demoControls = [];
+            __demoControls.push(xjs('.circle').animate({
                 x: '16rem',
                 duration: 1000,
                 loop: Infinity, 
@@ -135,6 +170,19 @@ xjs('.box').animate({
                 begin: () => { $value.textContent = '0'; },
                 update: ({ progress }) => { $value.textContent = Math.round(progress * 100) + '%'; },
                 complete: () => { /* not reached when loop is Infinity */ }
+            }));
+        }
+
+        function pauseDemo() {
+            __demoControls.forEach(c => { try { c?.pause?.(); } catch {} });
+        }
+
+        function resumeDemo() {
+            __demoControls.forEach(c => {
+                try {
+                    if (typeof c?.resume === 'function') c.resume();
+                    else if (typeof c?.play === 'function') c.play();
+                } catch {}
             });
         }
         

+ 62 - 3
doc/tween_value_types/test_colors.html

@@ -23,7 +23,11 @@
         <div class="box-container">
             <div class="box-header">
                 <div class="box-title">Color example</div>
-                <div class="tabs"><div class="tab active">JavaScript</div></div>
+                <div class="tabs">
+                    <div class="tab active" onclick="switchTab('js')">JavaScript</div>
+                    <div class="tab" onclick="switchTab('html')">HTML</div>
+                    <div class="tab" onclick="switchTab('css')">CSS</div>
+                </div>
             </div>
             <div id="js-code" class="code-view active">
 <span class="fun">xjs</span><span class="punc">(</span><span class="str">'.box'</span><span class="punc">)</span>.<span class="fun">animate</span><span class="punc">(</span><span class="punc">{</span>
@@ -33,23 +37,78 @@
   loop<span class="punc">:</span> <span class="num">2</span>
 <span class="punc">}</span><span class="punc">)</span><span class="punc">;</span>
             </div>
+            <div id="html-code" class="html-view">
+<span class="tag">&lt;div</span> <span class="attr">class</span>=<span class="val">"box"</span><span class="tag">&gt;&lt;/div&gt;</span>
+            </div>
+            <pre id="css-code" class="css-view">.box {
+  width: 50px;
+  height: 50px;
+  background-color: var(--highlight-color);
+  border-radius: 50%;
+}
+.demo-visual {
+  padding: 40px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}</pre>
+
+            <div class="feature-desc">
+                <strong>功能说明:</strong>支持 Hex / RGB / HSL 等颜色格式的插值动画;适合主题色过渡、状态反馈与强调效果。
+            </div>
             <div class="demo-visual">
                 <div class="box"></div>
             </div>
-            <div class="action-bar"><button class="play-btn" onclick="runDemo()">REPLAY</button></div>
+            <div class="action-bar">
+                <button class="play-btn" onclick="runDemo()">REPLAY</button>
+                <button class="play-btn secondary" onclick="pauseDemo()">PAUSE</button>
+                <button class="play-btn secondary" onclick="resumeDemo()">RESUME</button>
+            </div>
         </div>
     </div>
+    <script src="../highlight_css.js"></script>
     <script src="../../xjs.js"></script>
     <script>
+        function switchTab(tab) {
+            document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
+            document.querySelectorAll('.code-view, .html-view, .css-view').forEach(v => v.classList.remove('active'));
+            if (tab === 'js') {
+                document.querySelector('.tabs .tab:nth-child(1)')?.classList.add('active');
+                document.getElementById('js-code').classList.add('active');
+            } else if (tab === 'html') {
+                document.querySelector('.tabs .tab:nth-child(2)')?.classList.add('active');
+                document.getElementById('html-code').classList.add('active');
+            } else {
+                document.querySelector('.tabs .tab:nth-child(3)')?.classList.add('active');
+                document.getElementById('css-code').classList.add('active');
+            }
+        }
+
+        let __demoControls = [];
+
         function runDemo() {
             const box = document.querySelector('.box');
             box.style.backgroundColor = ''; // Reset
             
-                        xjs('.box').animate({
+            __demoControls = [];
+            __demoControls.push(xjs('.box').animate({
                 backgroundColor: '#FFF',
                 duration: 1000,
                 direction: 'alternate',
                 loop: 2
+            }));
+        }
+
+        function pauseDemo() {
+            __demoControls.forEach(c => { try { c?.pause?.(); } catch {} });
+        }
+
+        function resumeDemo() {
+            __demoControls.forEach(c => {
+                try {
+                    if (typeof c?.resume === 'function') c.resume();
+                    else if (typeof c?.play === 'function') c.play();
+                } catch {}
             });
         }
         setTimeout(runDemo, 500);

+ 62 - 3
doc/tween_value_types/test_numerical.html

@@ -23,7 +23,11 @@
         <div class="box-container">
             <div class="box-header">
                 <div class="box-title">Numerical example</div>
-                <div class="tabs"><div class="tab active">JavaScript</div></div>
+                <div class="tabs">
+                    <div class="tab active" onclick="switchTab('js')">JavaScript</div>
+                    <div class="tab" onclick="switchTab('html')">HTML</div>
+                    <div class="tab" onclick="switchTab('css')">CSS</div>
+                </div>
             </div>
             <div id="js-code" class="code-view active">
 <span class="fun">xjs</span><span class="punc">(</span><span class="str">'.box'</span><span class="punc">)</span>.<span class="fun">animate</span><span class="punc">(</span><span class="punc">{</span>
@@ -33,17 +37,72 @@
   easing<span class="punc">:</span> <span class="str">'ease-out'</span>
 <span class="punc">}</span><span class="punc">)</span><span class="punc">;</span>
             </div>
+            <div id="html-code" class="html-view">
+<span class="tag">&lt;div</span> <span class="attr">class</span>=<span class="val">"box"</span><span class="tag">&gt;&lt;/div&gt;</span>
+            </div>
+            <pre id="css-code" class="css-view">.box {
+  width: 50px;
+  height: 50px;
+  background-color: var(--highlight-color);
+  border-radius: 4px;
+}
+.demo-visual {
+  padding: 40px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}</pre>
+
+            <div class="feature-desc">
+                <strong>功能说明:</strong>单位为纯数字的属性值(如 translateX、scale 等)会直接进行数值插值,适合做基础位移/缩放等动画。
+            </div>
             <div class="demo-visual">
                 <div class="box"></div>
             </div>
-            <div class="action-bar"><button class="play-btn" onclick="runDemo()">REPLAY</button></div>
+            <div class="action-bar">
+                <button class="play-btn" onclick="runDemo()">REPLAY</button>
+                <button class="play-btn secondary" onclick="pauseDemo()">PAUSE</button>
+                <button class="play-btn secondary" onclick="resumeDemo()">RESUME</button>
+            </div>
         </div>
     </div>
+    <script src="../highlight_css.js"></script>
     <script src="../../xjs.js"></script>
     <script>
+        function switchTab(tab) {
+            document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
+            document.querySelectorAll('.code-view, .html-view, .css-view').forEach(v => v.classList.remove('active'));
+            if (tab === 'js') {
+                document.querySelector('.tabs .tab:nth-child(1)')?.classList.add('active');
+                document.getElementById('js-code').classList.add('active');
+            } else if (tab === 'html') {
+                document.querySelector('.tabs .tab:nth-child(2)')?.classList.add('active');
+                document.getElementById('html-code').classList.add('active');
+            } else {
+                document.querySelector('.tabs .tab:nth-child(3)')?.classList.add('active');
+                document.getElementById('css-code').classList.add('active');
+            }
+        }
+
+        let __demoControls = [];
+
         function runDemo() {
             document.querySelector('.box').style.transform = 'none';
-                        xjs('.box').animate({ x: 200, scale: 2, duration: 1000, easing: 'ease-out' });
+            __demoControls = [];
+            __demoControls.push(xjs('.box').animate({ x: 200, scale: 2, duration: 1000, easing: 'ease-out' }));
+        }
+
+        function pauseDemo() {
+            __demoControls.forEach(c => { try { c?.pause?.(); } catch {} });
+        }
+
+        function resumeDemo() {
+            __demoControls.forEach(c => {
+                try {
+                    if (typeof c?.resume === 'function') c.resume();
+                    else if (typeof c?.play === 'function') c.play();
+                } catch {}
+            });
         }
         setTimeout(runDemo, 500);
     </script>

+ 62 - 3
doc/tween_value_types/test_relative.html

@@ -26,7 +26,11 @@
         <div class="box-container">
             <div class="box-header">
                 <div class="box-title">Relative value example</div>
-                <div class="tabs"><div class="tab active">JavaScript</div></div>
+                <div class="tabs">
+                    <div class="tab active" onclick="switchTab('js')">JavaScript</div>
+                    <div class="tab" onclick="switchTab('html')">HTML</div>
+                    <div class="tab" onclick="switchTab('css')">CSS</div>
+                </div>
             </div>
             <div id="js-code" class="code-view active">
 <span class="kwd">const</span> box <span class="punc">=</span> document.querySelector<span class="punc">(</span><span class="str">'.box'</span><span class="punc">);</span>
@@ -38,14 +42,55 @@
   duration<span class="punc">:</span> <span class="num">1000</span>
 <span class="punc">}</span><span class="punc">)</span><span class="punc">;</span>
             </div>
+            <div id="html-code" class="html-view">
+<span class="tag">&lt;div</span> <span class="attr">class</span>=<span class="val">"box"</span><span class="tag">&gt;&lt;/div&gt;</span>
+            </div>
+            <pre id="css-code" class="css-view">.box {
+  width: 50px;
+  height: 50px;
+  background-color: var(--highlight-color);
+  border-radius: 4px;
+}
+.demo-visual {
+  padding: 40px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}</pre>
+
+            <div class="feature-desc">
+                <strong>功能说明:</strong>不支持 “+=” 这种相对字符串时,可以先读取当前值,再把目标值计算出来传给动画(本例演示 width 的相对变化)。
+            </div>
             <div class="demo-visual">
                 <div class="box"></div>
             </div>
-            <div class="action-bar"><button class="play-btn" onclick="runDemo()">REPLAY</button></div>
+            <div class="action-bar">
+                <button class="play-btn" onclick="runDemo()">REPLAY</button>
+                <button class="play-btn secondary" onclick="pauseDemo()">PAUSE</button>
+                <button class="play-btn secondary" onclick="resumeDemo()">RESUME</button>
+            </div>
         </div>
     </div>
+    <script src="../highlight_css.js"></script>
     <script src="../../xjs.js"></script>
     <script>
+        function switchTab(tab) {
+            document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
+            document.querySelectorAll('.code-view, .html-view, .css-view').forEach(v => v.classList.remove('active'));
+            if (tab === 'js') {
+                document.querySelector('.tabs .tab:nth-child(1)')?.classList.add('active');
+                document.getElementById('js-code').classList.add('active');
+            } else if (tab === 'html') {
+                document.querySelector('.tabs .tab:nth-child(2)')?.classList.add('active');
+                document.getElementById('html-code').classList.add('active');
+            } else {
+                document.querySelector('.tabs .tab:nth-child(3)')?.classList.add('active');
+                document.getElementById('css-code').classList.add('active');
+            }
+        }
+
+        let __demoControls = [];
+
         function runDemo() {
             const box = document.querySelector('.box');
             box.style.transform = 'none';
@@ -53,11 +98,25 @@
 
                         const w = parseFloat(getComputedStyle(box).width) || 50;
 
-            xjs(box).animate({
+            __demoControls = [];
+            __demoControls.push(xjs(box).animate({
                 x: 100,
                 width: [w + 'px', (w - 10) + 'px'],
                 duration: 1000,
                 easing: 'ease-out'
+            }));
+        }
+
+        function pauseDemo() {
+            __demoControls.forEach(c => { try { c?.pause?.(); } catch {} });
+        }
+
+        function resumeDemo() {
+            __demoControls.forEach(c => {
+                try {
+                    if (typeof c?.resume === 'function') c.resume();
+                    else if (typeof c?.play === 'function') c.play();
+                } catch {}
             });
         }
         setTimeout(runDemo, 500);

+ 56 - 3
doc/tween_value_types/test_unit.html

@@ -23,7 +23,11 @@
         <div class="box-container">
             <div class="box-header">
                 <div class="box-title">Unit example</div>
-                <div class="tabs"><div class="tab active">JavaScript</div></div>
+                <div class="tabs">
+                    <div class="tab active" onclick="switchTab('js')">JavaScript</div>
+                    <div class="tab" onclick="switchTab('html')">HTML</div>
+                    <div class="tab" onclick="switchTab('css')">CSS</div>
+                </div>
             </div>
             <div id="js-code" class="code-view active">
 <span class="fun">xjs</span><span class="punc">(</span><span class="str">'.box'</span><span class="punc">)</span>.<span class="fun">animate</span><span class="punc">(</span><span class="punc">{</span>
@@ -32,22 +36,71 @@
   easing<span class="punc">:</span> <span class="str">'ease-in-out'</span>
 <span class="punc">}</span><span class="punc">)</span><span class="punc">;</span>
             </div>
+            <div id="html-code" class="html-view">
+<span class="tag">&lt;div</span> <span class="attr">class</span>=<span class="val">"box"</span><span class="tag">&gt;&lt;/div&gt;</span>
+            </div>
+            <pre id="css-code" class="css-view">.box {
+  width: 50px;
+  height: 50px;
+  background-color: var(--highlight-color);
+  border-radius: 4px;
+}</pre>
+
+            <div class="feature-desc">
+                <strong>功能说明:</strong>支持不同单位之间的动画(如 px ⇄ %),会在运行时做单位换算并进行插值。
+            </div>
             <div class="demo-visual" style="display: block; padding: 40px 0;">
                 <div class="box"></div>
             </div>
-            <div class="action-bar"><button class="play-btn" onclick="runDemo()">REPLAY</button></div>
+            <div class="action-bar">
+                <button class="play-btn" onclick="runDemo()">REPLAY</button>
+                <button class="play-btn secondary" onclick="pauseDemo()">PAUSE</button>
+                <button class="play-btn secondary" onclick="resumeDemo()">RESUME</button>
+            </div>
         </div>
     </div>
+    <script src="../highlight_css.js"></script>
     <script src="../../xjs.js"></script>
     <script>
+        function switchTab(tab) {
+            document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
+            document.querySelectorAll('.code-view, .html-view, .css-view').forEach(v => v.classList.remove('active'));
+            if (tab === 'js') {
+                document.querySelector('.tabs .tab:nth-child(1)')?.classList.add('active');
+                document.getElementById('js-code').classList.add('active');
+            } else if (tab === 'html') {
+                document.querySelector('.tabs .tab:nth-child(2)')?.classList.add('active');
+                document.getElementById('html-code').classList.add('active');
+            } else {
+                document.querySelector('.tabs .tab:nth-child(3)')?.classList.add('active');
+                document.getElementById('css-code').classList.add('active');
+            }
+        }
+
+        let __demoControls = [];
+
         function runDemo() {
             const box = document.querySelector('.box');
             box.style.width = '50px'; // Reset
             
-                        xjs('.box').animate({
+            __demoControls = [];
+            __demoControls.push(xjs('.box').animate({
                 width: '100%',
                 duration: 1000,
                 easing: 'ease-in-out'
+            }));
+        }
+
+        function pauseDemo() {
+            __demoControls.forEach(c => { try { c?.pause?.(); } catch {} });
+        }
+
+        function resumeDemo() {
+            __demoControls.forEach(c => {
+                try {
+                    if (typeof c?.resume === 'function') c.resume();
+                    else if (typeof c?.play === 'function') c.play();
+                } catch {}
             });
         }
         setTimeout(runDemo, 500);