diff --git a/.ua/visual-baseline.json b/.ua/visual-baseline.json index 58b1e16..0a067bf 100644 --- a/.ua/visual-baseline.json +++ b/.ua/visual-baseline.json @@ -1,13 +1,13 @@ { "$schema": "视觉漂移基线:各类硬编码视觉字面量的允许上限,任一类超出即 ua audit:visual 退出码 1;迁移后用 --baseline 重写", "file": "packages/skin/ui/ui.css", - "commit": "654a9dd", - "generatedAt": "2026-10-26T09:53:34.712Z", + "commit": "0e996e4", + "generatedAt": "2026-10-26T09:54:55.315Z", "lines": 4874, - "total": 27, + "total": 18, "counts": { "font-size-literal": 0, - "duration-literal": 9, + "duration-literal": 0, "shadow-literal": 5, "radius-literal": 0, "focus-ring-variant": 0, diff --git a/llms-full.txt b/llms-full.txt index 0dd276f..102873d 100644 --- a/llms-full.txt +++ b/llms-full.txt @@ -2220,6 +2220,11 @@ node packages/tools/cli/bin/ua.js |docs|tokens - --ua-duration-fast: 0.15s - --ua-duration-base: 0.2s - --ua-duration-slow: 0.25s +- --ua-duration-slower: 0.35s +- --ua-duration-loop-fast: 0.6s +- --ua-duration-loop-mid: 0.8s +- --ua-duration-loop-slow: 1.2s +- --ua-duration-loop-long: 1.6s - --ua-ease: cubic-bezier(0.2, 0.8, 0.2, 1) - --ua-ease-out: cubic-bezier(0.16, 1, 0.3, 1) - --ua-focus-ring: 0 0 0 3px color-mix(in srgb, var(--ua-color-accent) 22%, transparent)(暗色 0 0 0 3px color-mix(in srgb, var(--ua-color-accent) 34%, transparent)) diff --git a/packages/design/tokens/tokens.css b/packages/design/tokens/tokens.css index be667e5..9869ed7 100644 --- a/packages/design/tokens/tokens.css +++ b/packages/design/tokens/tokens.css @@ -64,10 +64,15 @@ --ua-shadow-accent: 0 3px 10px color-mix(in srgb, var(--ua-color-accent) 25%, transparent); /* 主题色投影:主按钮 / 强调控件悬停浮起 */ --ua-shadow-accent-strong: 0 8px 20px color-mix(in srgb, var(--ua-color-accent) 35%, transparent); /* 主题色强投影:返回顶部等悬浮强调件 */ - /* ---- 动效:三档时长 + 两条主缓动曲线 ---- */ + /* ---- 动效:四档交互时长 + 四档循环时长 + 两条主缓动曲线 ---- */ --ua-duration-fast: 0.15s; /* 快:hover / 聚焦 / 颜色与边框变化(既有样式的主流时长) */ --ua-duration-base: 0.2s; /* 中:展开收起 / 位移 / 透明度过渡 */ --ua-duration-slow: 0.25s; /* 慢:浮层出入场 / 较大面积变化 */ + --ua-duration-slower: 0.35s; /* 更慢:进度条宽度推进 / 描边长度等大跨度形变(比浮层出场再长一档) */ + --ua-duration-loop-fast: 0.6s; /* 循环快档:条纹滚动等细微循环 */ + --ua-duration-loop-mid: 0.8s; /* 循环中档:加载转圈 / 开关内转圈(0.7 与 0.8 收敛到此档) */ + --ua-duration-loop-slow: 1.2s; /* 循环慢档:骨架扫光 / 不确定进度条(1.4 收敛到此档) */ + --ua-duration-loop-long: 1.6s; /* 循环长档:时间线呼吸等低频脉冲,别再用在加载态上 */ --ua-ease: cubic-bezier(0.2, 0.8, 0.2, 1); /* 标准缓动:先快后缓,控件交互默认用这条 */ --ua-ease-out: cubic-bezier(0.16, 1, 0.3, 1); /* 出场缓动:元素进入视口 / 弹层出现时使用 */ diff --git a/packages/design/tokens/tokens.json b/packages/design/tokens/tokens.json index 6420ce6..99dc90a 100644 --- a/packages/design/tokens/tokens.json +++ b/packages/design/tokens/tokens.json @@ -114,7 +114,12 @@ "duration": { "fast": "0.15s", "base": "0.2s", - "slow": "0.25s" + "slow": "0.25s", + "slower": "0.35s", + "loopFast": "0.6s", + "loopMid": "0.8s", + "loopSlow": "1.2s", + "loopLong": "1.6s" }, "ease": "cubic-bezier(0.2, 0.8, 0.2, 1)", "easeOut": "cubic-bezier(0.16, 1, 0.3, 1)" diff --git a/packages/skin/ui/ui.css b/packages/skin/ui/ui.css index b430dbd..f225d5e 100644 --- a/packages/skin/ui/ui.css +++ b/packages/skin/ui/ui.css @@ -322,7 +322,7 @@ body.ua-col-resizing { background: transparent; border: 2px solid color-mix(in srgb, var(--ua-color-bg) 40%, transparent); border-top-color: var(--ua-color-bg); - animation: ua-switch-spin 0.7s linear infinite; + animation: ua-switch-spin var(--ua-duration-loop-mid) linear infinite; } @keyframes ua-switch-spin { to { transform: rotate(360deg); } @@ -1380,7 +1380,7 @@ button.ua-step-dot:hover { inset: 0; transform: translateX(-100%); background: linear-gradient(90deg, transparent, color-mix(in srgb, var(--ua-color-bg) 65%, transparent), transparent); - animation: ua-shimmer 1.4s ease-in-out infinite; + animation: ua-shimmer var(--ua-duration-loop-slow) ease-in-out infinite; } @keyframes ua-shimmer { to { @@ -1478,7 +1478,7 @@ button.ua-step-dot:hover { border: 2px solid var(--ua-color-border); border-top-color: var(--ua-color-accent); border-radius: 50%; - animation: ua-spin 0.8s linear infinite; + animation: ua-spin var(--ua-duration-loop-mid) linear infinite; } @keyframes ua-spin { to { @@ -1506,7 +1506,7 @@ button.ua-step-dot:hover { width: 0; border-radius: inherit; background: linear-gradient(90deg, color-mix(in srgb, var(--ua-color-accent) 65%, transparent), var(--ua-color-accent)); - transition: width 0.35s cubic-bezier(0.4, 0, 0.2, 1); + transition: width var(--ua-duration-slower) cubic-bezier(0.4, 0, 0.2, 1); } .ua-progress-fill::after { content: ''; @@ -1514,7 +1514,7 @@ button.ua-step-dot:hover { inset: 0; background-image: repeating-linear-gradient(45deg, rgba(255, 255, 255, 0.18) 0 6px, transparent 6px 12px); background-size: 17px 100%; - animation: ua-stripe 0.6s linear infinite; + animation: ua-stripe var(--ua-duration-loop-fast) linear infinite; } .ua-progress-fill.ua-done { background: var(--ua-color-success); @@ -1537,7 +1537,7 @@ button.ua-step-dot:hover { .ua-progress-fill.ua-indeterminate { width: 35%; transition: none; - animation: ua-indeterminate 1.2s ease-in-out infinite; + animation: ua-indeterminate var(--ua-duration-loop-slow) ease-in-out infinite; } .ua-progress-fill.ua-indeterminate::after { animation: none; @@ -2238,7 +2238,7 @@ button.ua-step-dot:hover { .ua-tl-dot-pending { background: var(--ua-color-bg); border: 1px dashed var(--ua-color-accent); - animation: ua-tl-pulse 1.6s ease-in-out infinite; + animation: ua-tl-pulse var(--ua-duration-loop-long) ease-in-out infinite; } .ua-tl-head { display: flex; @@ -2565,7 +2565,7 @@ button.ua-step-dot:hover { .ua-carousel-track { display: flex; height: 100%; - transition: transform 0.32s ease; + transition: transform var(--ua-duration-slower) ease; } .ua-carousel-slide { flex: 0 0 100%; @@ -4730,7 +4730,7 @@ h5.ua-type-title { font-size: var(--ua-font-size-md); } .ua-progress-ring-arc { stroke: var(--ua-color-accent); stroke-linecap: round; - transition: stroke-dashoffset 0.3s ease; + transition: stroke-dashoffset var(--ua-duration-slower) ease; } .ua-progress-ring.is-success .ua-progress-ring-arc { stroke: var(--ua-color-success); } .ua-progress-ring.is-warning .ua-progress-ring-arc { stroke: var(--ua-color-warning); } diff --git a/packages/tools/cli/lib/visual-rules.mjs b/packages/tools/cli/lib/visual-rules.mjs index a6106a3..2a18bdb 100644 --- a/packages/tools/cli/lib/visual-rules.mjs +++ b/packages/tools/cli/lib/visual-rules.mjs @@ -194,8 +194,19 @@ const DURATION_STEPS = [ { max: 0.17, token: 'var(--ua-duration-fast)' }, { max: 0.23, token: 'var(--ua-duration-base)' }, { max: 0.2501, token: 'var(--ua-duration-slow)' }, + { max: 0.36, token: 'var(--ua-duration-slower)' }, ]; +/*** 作用:循环动效(infinite)的时长 → 循环四档;0.7/0.8 收敛到中档、1.2/1.4 收敛到慢档,表外保留 ***/ +const LOOP_DURATIONS = new Map([ + ['0.6s', 'var(--ua-duration-loop-fast)'], + ['0.7s', 'var(--ua-duration-loop-mid)'], + ['0.8s', 'var(--ua-duration-loop-mid)'], + ['1.2s', 'var(--ua-duration-loop-slow)'], + ['1.4s', 'var(--ua-duration-loop-slow)'], + ['1.6s', 'var(--ua-duration-loop-long)'], +]); + /*** 作用:控件高度三档的字面值 → 令牌(28/34/40px 三个值保留给控件高度档位,其余尺寸不入此口径) ***/ const CONTROL_H_TOKENS = new Map([ ['28px', 'var(--ua-control-h-sm)'], @@ -305,12 +316,13 @@ function hasTiming(value) { /*** 作用:迁移单个动效声明值(逐段替换时长,缺缓动时补 --ua-ease);返回 null 表示无需改动 ***/ function migrateMotion(prop, core) { const shorthand = prop === 'transition' || prop === 'animation'; // 只有简写能补缓动,-duration 属性补了是非法的 + const loop = /infinite/.test(core) && (prop === 'animation' || prop === 'animation-duration'); // 无限循环走循环四档 const parts = splitTopLevel(core); let hits = 0; const next = parts.map((segment) => { let replaced = 0; let out = segment.replace(/(\d*\.?\d+)(ms|s)\b/gi, (token) => { - const mapped = durationToken(token); + const mapped = loop ? LOOP_DURATIONS.get(token.toLowerCase()) ?? null : durationToken(token); if (!mapped) return token; // 表外时长保留,交给后续视觉条目单独评估 replaced++; hits++;