diff --git a/.ua/visual-baseline.json b/.ua/visual-baseline.json index 922ee69..a5184d4 100644 --- a/.ua/visual-baseline.json +++ b/.ua/visual-baseline.json @@ -1,9 +1,9 @@ { "$schema": "视觉漂移基线:五类硬编码视觉字面量的允许上限,任一类超出即 ua audit:visual 退出码 1;迁移后用 --baseline 重写", "file": "packages/skin/ui/ui.css", - "commit": "320dac3", - "generatedAt": "2026-10-26T09:21:19.963Z", - "lines": 4758, + "commit": "089527f", + "generatedAt": "2026-10-26T09:36:10.619Z", + "lines": 4780, "total": 40, "counts": { "font-size-literal": 5, @@ -11,6 +11,9 @@ "shadow-literal": 9, "radius-literal": 0, "focus-ring-variant": 4, - "space-literal": 13 + "space-literal": 13, + "control-h-literal": 0, + "weight-literal": 0, + "leading-literal": 0 } } diff --git a/llms-full.txt b/llms-full.txt index 951a20b..6cac0f5 100644 --- a/llms-full.txt +++ b/llms-full.txt @@ -2172,6 +2172,8 @@ node packages/tools/cli/bin/ua.js |docs|tokens - --ua-weight-normal: 400 - --ua-weight-medium: 500 - --ua-weight-semibold: 600 +- --ua-weight-bold: 700 +- --ua-leading-none: 1 - --ua-leading-tight: 1.35 - --ua-leading-normal: 1.55 - --ua-border-subtle: color-mix(in srgb, var(--ua-color-border) 60%, transparent) diff --git a/packages/design/tokens/tokens.css b/packages/design/tokens/tokens.css index ccb3708..a074a98 100644 --- a/packages/design/tokens/tokens.css +++ b/packages/design/tokens/tokens.css @@ -40,11 +40,13 @@ --ua-font-size-2xl: 18px; /* 标题字号:页面区块标题 */ /* ---- 字重与行高:标题 / 正文 / 辅助三档 ---- */ - --ua-weight-normal: 400; /* 常规字重:正文与表单值 */ - --ua-weight-medium: 500; /* 中字重:控件文本、表头、强调条目 */ - --ua-weight-semibold: 600; /* 半粗字重:标题、关键数值 */ - --ua-leading-tight: 1.35; /* 紧凑行高:标题、按钮、单行控件 */ - --ua-leading-normal: 1.55; /* 常规行高:多行正文、说明文字 */ + --ua-weight-normal: 400; /* 正文档:正文与表单值 */ + --ua-weight-medium: 500; /* 辅助强调档:控件文本、表头、次要标题 */ + --ua-weight-semibold: 600; /* 标题档:区块标题、关键数值 */ + --ua-weight-bold: 700; /* 特重档:统计大数字等少数强调位 */ + --ua-leading-none: 1; /* 单行档:图标 / 数字 / 按钮文字的垂直居中 */ + --ua-leading-tight: 1.35; /* 紧凑档:标题、单行控件 */ + --ua-leading-normal: 1.55; /* 正文档:多行正文、说明文字 */ /* ---- 边框:细分(比基础边框更淡,用于分割线,避免 1px 线过重) ---- */ --ua-border-subtle: color-mix(in srgb, var(--ua-color-border) 60%, transparent); /* 细分边框色:表格分割线、卡片内分隔 */ diff --git a/packages/design/tokens/tokens.json b/packages/design/tokens/tokens.json index b7b7362..6b40168 100644 --- a/packages/design/tokens/tokens.json +++ b/packages/design/tokens/tokens.json @@ -68,11 +68,13 @@ "weight": { "normal": 400, "medium": 500, - "semibold": 600 + "semibold": 600, + "bold": 700 }, "leading": { "tight": 1.35, - "normal": 1.55 + "normal": 1.55, + "none": 1 }, "border": { "subtle": "color-mix(in srgb, var(--ua-color-border) 60%, transparent)" diff --git a/packages/skin/ui/ui.css b/packages/skin/ui/ui.css index 7b8fefc..7a5b743 100644 --- a/packages/skin/ui/ui.css +++ b/packages/skin/ui/ui.css @@ -89,7 +89,7 @@ .ua-card-title { margin: 0; font-size: var(--ua-font-size-lg); - font-weight: 500; + font-weight: var(--ua-weight-medium); } .ua-card-extra { display: inline-flex; @@ -132,7 +132,7 @@ top: 0; z-index: 1; background: var(--ua-color-surface); - font-weight: 500; + font-weight: var(--ua-weight-medium); color: var(--ua-color-text-muted); box-shadow: inset 0 -1px 0 var(--ua-color-border); } @@ -198,7 +198,7 @@ body.ua-col-resizing { } .ua-table tr.ua-row-selected td { color: var(--ua-color-text-muted); - font-weight: 400; + font-weight: var(--ua-weight-normal); } .ua-cell-check { width: 36px; @@ -432,7 +432,7 @@ body.ua-col-resizing { padding: var(--ua-space-15) var(--ua-space-25) var(--ua-space-05); color: var(--ua-color-text-muted); font-size: var(--ua-font-size-sm); - font-weight: 600; + font-weight: var(--ua-weight-semibold); letter-spacing: 0.02em; cursor: default; user-select: none; @@ -462,7 +462,7 @@ body.ua-col-resizing { .ua-option-selected { background: color-mix(in srgb, var(--ua-color-accent) 10%, transparent); color: var(--ua-color-text-muted); - font-weight: 400; + font-weight: var(--ua-weight-normal); } .ua-option-selected:hover { background: color-mix(in srgb, var(--ua-color-accent) 14%, transparent); @@ -648,12 +648,12 @@ body.ua-col-resizing { margin-bottom: var(--ua-space-2); color: var(--ua-color-text); font-size: var(--ua-font-size-lg); - font-weight: 600; + font-weight: var(--ua-weight-semibold); } .ua-pop-body { color: var(--ua-color-text-muted); font-size: var(--ua-font-size-md); - line-height: 1.6; + line-height: var(--ua-leading-normal); } /* —— 折叠面板 —— */ @@ -732,7 +732,7 @@ body.ua-col-resizing { background: color-mix(in srgb, var(--ua-color-accent) 14%, transparent); color: var(--ua-color-accent); font-size: var(--ua-font-size-md); - font-weight: 500; + font-weight: var(--ua-weight-medium); user-select: none; } .ua-avatar-img { @@ -764,7 +764,7 @@ body.ua-col-resizing { background: var(--ua-color-surface); color: var(--ua-color-text); font-size: var(--ua-font-size-sm); - line-height: 1.6; + line-height: var(--ua-leading-normal); transition: border-color var(--ua-duration-fast) var(--ua-ease), background var(--ua-duration-fast) var(--ua-ease); } .ua-tag-success { @@ -789,7 +789,7 @@ body.ua-col-resizing { background: none; color: var(--ua-color-text-muted); font-size: var(--ua-font-size-sm); - line-height: 1; + line-height: var(--ua-leading-none); cursor: pointer; transition: color var(--ua-duration-fast) var(--ua-ease), transform var(--ua-duration-fast) var(--ua-ease); } @@ -936,7 +936,7 @@ body.ua-col-resizing { .ua-input-number .ua-btn { height: auto; /* 步进器是图标按钮:不吃控件统一高度 */ padding: 5px var(--ua-space-3); /* 收窄为步进按钮 */ - line-height: 1.4; + line-height: var(--ua-leading-tight); } .ua-input-number-field { width: 76px; @@ -1045,12 +1045,12 @@ button.ua-step-dot:hover { } .ua-step-label { font-size: var(--ua-font-size-md); - line-height: 1.4; + line-height: var(--ua-leading-tight); color: var(--ua-color-text-muted); } .ua-step-current .ua-step-label { color: var(--ua-color-text); - font-weight: 600; + font-weight: var(--ua-weight-semibold); } .ua-step-desc { font-size: var(--ua-font-size-sm); @@ -1073,7 +1073,7 @@ button.ua-step-dot:hover { background: none; color: var(--ua-color-border); /* 未选中:弱化为边框色 */ font-size: 22px; - line-height: 1; + line-height: var(--ua-leading-none); cursor: pointer; transition: color var(--ua-duration-fast) var(--ua-ease), transform var(--ua-duration-fast) var(--ua-ease); } @@ -1112,12 +1112,12 @@ button.ua-step-dot:hover { } .ua-empty-icon { font-size: 34px; - line-height: 1; + line-height: var(--ua-leading-none); color: var(--ua-color-border); /* 装饰符号弱化为边框色 */ } .ua-empty-title { font-size: var(--ua-font-size-md); - font-weight: 600; + font-weight: var(--ua-weight-semibold); color: var(--ua-color-text); } .ua-empty-desc { @@ -1174,7 +1174,7 @@ button.ua-step-dot:hover { padding: 1px var(--ua-space-2); border-radius: 999px; font-size: var(--ua-font-size-sm); - line-height: 1.6; + line-height: var(--ua-leading-normal); } .ua-badge-count { min-width: 18px; @@ -1238,7 +1238,7 @@ button.ua-step-dot:hover { border: 1px solid currentColor; border-radius: 50%; font-size: var(--ua-font-size-xs); - line-height: 1; + line-height: var(--ua-leading-none); opacity: 0.85; } .ua-alert.is-banner { @@ -1253,7 +1253,7 @@ button.ua-step-dot:hover { } .ua-alert-title { margin-bottom: var(--ua-space-05); - font-weight: 600; + font-weight: var(--ua-weight-semibold); } .ua-alert-close { flex: none; @@ -1262,7 +1262,7 @@ button.ua-step-dot:hover { background: none; color: var(--ua-color-text-muted); font-size: var(--ua-font-size-lg); - line-height: 1.4; + line-height: var(--ua-leading-tight); cursor: pointer; transition: color var(--ua-duration-fast) var(--ua-ease); } @@ -1448,7 +1448,7 @@ button.ua-step-dot:hover { background: none; color: var(--ua-color-text-muted); font-size: var(--ua-font-size-lg); - line-height: 1; + line-height: var(--ua-leading-none); cursor: pointer; transition: color var(--ua-duration-fast) var(--ua-ease); } @@ -1599,7 +1599,7 @@ button.ua-step-dot:hover { } .ua-tab-active { color: var(--ua-color-accent); - font-weight: 500; + font-weight: var(--ua-weight-medium); } .ua-tab-item { position: relative; @@ -1619,7 +1619,7 @@ button.ua-step-dot:hover { background: none; color: var(--ua-color-text-muted); font-size: var(--ua-font-size-md); - line-height: 1; + line-height: var(--ua-leading-none); cursor: pointer; opacity: 0; transition: opacity var(--ua-duration-fast) var(--ua-ease), background-color var(--ua-duration-fast) var(--ua-ease), color var(--ua-duration-fast) var(--ua-ease); @@ -1798,14 +1798,14 @@ button.ua-step-dot:hover { .ua-drawer-title { margin: 0; font-size: var(--ua-font-size-lg); - font-weight: 500; + font-weight: var(--ua-weight-medium); } .ua-drawer-close { border: none; background: none; color: var(--ua-color-text-muted); font-size: var(--ua-font-size-2xl); - line-height: 1; + line-height: var(--ua-leading-none); cursor: pointer; } .ua-drawer-body { @@ -1885,7 +1885,7 @@ button.ua-step-dot:hover { .ua-modal-title { margin: 0; font-size: var(--ua-font-size-lg); - font-weight: 500; + font-weight: var(--ua-weight-medium); } .ua-modal-close { border: none; @@ -1893,7 +1893,7 @@ button.ua-step-dot:hover { color: var(--ua-color-text-muted); font-size: var(--ua-font-size-2xl); cursor: pointer; - line-height: 1; + line-height: var(--ua-leading-none); } .ua-modal-body { padding: var(--ua-space-4); @@ -1918,7 +1918,7 @@ button.ua-step-dot:hover { background: var(--ua-color-text); color: var(--ua-color-bg); font-size: var(--ua-font-size-sm); - line-height: 1.5; + line-height: var(--ua-leading-normal); pointer-events: none; opacity: 0; transition: opacity var(--ua-duration-fast) ease-out, transform var(--ua-duration-fast) ease-out; @@ -2133,7 +2133,7 @@ button.ua-step-dot:hover { padding: 0 var(--ua-space-05); color: var(--ua-color-text-muted); font-size: var(--ua-font-size-md); - line-height: 1; + line-height: var(--ua-leading-none); cursor: pointer; } .ua-input-clear:hover { @@ -2251,11 +2251,11 @@ button.ua-step-dot:hover { .ua-tl-title { color: var(--ua-color-text); font-size: var(--ua-font-size-md); - font-weight: 600; + font-weight: var(--ua-weight-semibold); } .ua-tl-item.is-pending .ua-tl-title { color: var(--ua-color-text-muted); - font-weight: 500; + font-weight: var(--ua-weight-medium); } .ua-tl-time { color: var(--ua-color-text-muted); @@ -2266,7 +2266,7 @@ button.ua-step-dot:hover { margin: var(--ua-space-05) 0 0; color: var(--ua-color-text-muted); font-size: var(--ua-font-size-sm); - line-height: 1.6; + line-height: var(--ua-leading-normal); } @keyframes ua-tl-in { from { opacity: 0; transform: translateY(-4px); } @@ -2327,7 +2327,7 @@ button.ua-step-dot:hover { } .ua-seg-btn.is-active { color: var(--ua-color-accent); - font-weight: 600; + font-weight: var(--ua-weight-semibold); } .ua-seg-btn:disabled { color: color-mix(in srgb, var(--ua-color-text-muted) 45%, transparent); @@ -2361,7 +2361,7 @@ button.ua-step-dot:hover { .ua-desc-title { color: var(--ua-color-text); font-size: var(--ua-font-size-md); - font-weight: 600; + font-weight: var(--ua-weight-semibold); } .ua-desc-head { display: flex; @@ -2452,8 +2452,8 @@ button.ua-step-dot:hover { .ua-stat-num { color: var(--ua-color-text); font-size: 26px; - font-weight: 600; - line-height: 1.2; + font-weight: var(--ua-weight-semibold); + line-height: var(--ua-leading-none); font-variant-numeric: tabular-nums; } .ua-stat-affix { @@ -2494,7 +2494,7 @@ button.ua-step-dot:hover { color: var(--ua-color-text-muted); } .ua-list-header { - font-weight: 600; + font-weight: var(--ua-weight-semibold); color: var(--ua-color-text); border-bottom: 1px solid var(--ua-color-border); } @@ -2582,7 +2582,7 @@ button.ua-step-dot:hover { background: var(--ua-color-bg); color: var(--ua-color-text); font-size: var(--ua-font-size-lg); - line-height: 1; + line-height: var(--ua-leading-none); cursor: pointer; opacity: 0; transition: opacity var(--ua-duration-fast) ease; @@ -2647,7 +2647,7 @@ button.ua-step-dot:hover { } .ua-tree-selected { background: color-mix(in srgb, var(--ua-color-accent) 12%, transparent); /* 选中低视觉比重 */ - font-weight: 600; + font-weight: var(--ua-weight-semibold); } .ua-tree-caret { display: inline-flex; @@ -2782,7 +2782,7 @@ button.ua-step-dot:hover { background: none; color: var(--ua-color-text-muted); cursor: pointer; - line-height: 1; + line-height: var(--ua-leading-none); padding: 0 var(--ua-space-05); } .ua-upload-remove:hover { @@ -2843,7 +2843,7 @@ button.ua-step-dot:hover { .ua-notice-title { flex: 1; font-size: var(--ua-font-size-md); - font-weight: 600; + font-weight: var(--ua-weight-semibold); color: var(--ua-color-text); } .ua-notice-close { @@ -2851,7 +2851,7 @@ button.ua-step-dot:hover { background: none; color: var(--ua-color-text-muted); cursor: pointer; - line-height: 1; + line-height: var(--ua-leading-none); padding: 0 var(--ua-space-05); } .ua-notice-close:hover { @@ -2861,7 +2861,7 @@ button.ua-step-dot:hover { margin-top: var(--ua-space-1); font-size: var(--ua-font-size-sm); color: var(--ua-color-text-muted); - line-height: 1.5; + line-height: var(--ua-leading-normal); } .ua-notice-action { margin-top: var(--ua-space-2); @@ -2894,7 +2894,7 @@ button.ua-step-dot:hover { height: var(--ua-control-h-lg); border-radius: 50%; font-size: var(--ua-font-size-lg); - font-weight: 700; + font-weight: var(--ua-weight-bold); color: var(--ua-color-accent-contrast); background: var(--ua-color-accent); } @@ -2903,14 +2903,14 @@ button.ua-step-dot:hover { .ua-result-danger .ua-result-icon { background: var(--ua-color-danger); } .ua-result-title { font-size: var(--ua-font-size-lg); - font-weight: 600; + font-weight: var(--ua-weight-semibold); color: var(--ua-color-text); } .ua-result-desc { max-width: 380px; font-size: var(--ua-font-size-sm); color: var(--ua-color-text-muted); - line-height: 1.6; + line-height: var(--ua-leading-normal); } .ua-result-extra { margin-top: var(--ua-space-1); @@ -2941,7 +2941,7 @@ button.ua-step-dot:hover { } .ua-transfer-title { font-size: var(--ua-font-size-sm); - font-weight: 600; + font-weight: var(--ua-weight-semibold); color: var(--ua-color-text); } .ua-transfer-search { @@ -3124,7 +3124,7 @@ button.ua-step-dot:hover { } .ua-cascader-opt.is-picked { color: var(--ua-color-accent); - font-weight: 600; + font-weight: var(--ua-weight-semibold); } .ua-cascader-opt:disabled { opacity: 0.45; @@ -3353,14 +3353,14 @@ button.ua-step-dot:hover { } .ua-datepicker-label { font-size: var(--ua-font-size-sm); - font-weight: 600; + font-weight: var(--ua-weight-semibold); color: var(--ua-color-text); } .ua-datepicker-nav { width: 24px; height: 24px; font-size: var(--ua-font-size-lg); - line-height: 1; + line-height: var(--ua-leading-none); color: var(--ua-color-text); background: transparent; border: 1px solid var(--ua-color-border); @@ -3407,7 +3407,7 @@ button.ua-step-dot:hover { .ua-datepicker-day.is-selected { color: var(--ua-color-accent-contrast); background: var(--ua-color-accent); - font-weight: 600; + font-weight: var(--ua-weight-semibold); } .ua-datepicker-day:disabled { opacity: 0.35; @@ -3508,7 +3508,7 @@ button.ua-step-dot:hover { } .ua-backtop-icon { font-size: var(--ua-font-size-lg); - line-height: 1; + line-height: var(--ua-leading-none); } @media (prefers-reduced-motion: reduce) { .ua-backtop { @@ -3679,13 +3679,13 @@ button.ua-step-dot:hover { } .ua-tour-title { font-size: var(--ua-font-size-sm); - font-weight: 600; + font-weight: var(--ua-weight-semibold); color: var(--ua-color-text); } .ua-tour-close { padding: 0 var(--ua-space-1); font-size: var(--ua-font-size-lg); - line-height: 1; + line-height: var(--ua-leading-none); color: var(--ua-color-text-muted); background: transparent; border: none; @@ -3697,7 +3697,7 @@ button.ua-step-dot:hover { .ua-tour-desc { margin-top: var(--ua-space-1); font-size: var(--ua-font-size-sm); - line-height: 1.6; + line-height: var(--ua-leading-normal); color: var(--ua-color-text-muted); } .ua-tour-foot { @@ -3812,7 +3812,7 @@ button.ua-step-dot:hover { .ua-multiselect-x { padding: 0; font-size: var(--ua-font-size-sm); - line-height: 1; + line-height: var(--ua-leading-none); color: var(--ua-color-text-muted); background: transparent; border: none; @@ -3863,7 +3863,7 @@ button.ua-step-dot:hover { padding: var(--ua-space-15) var(--ua-space-1) var(--ua-space-05); color: var(--ua-color-text-muted); font-size: var(--ua-font-size-sm); - font-weight: 600; + font-weight: var(--ua-weight-semibold); cursor: default; user-select: none; } @@ -3884,7 +3884,7 @@ button.ua-step-dot:hover { } .ua-multiselect-opt.is-selected { color: var(--ua-color-accent); - font-weight: 600; + font-weight: var(--ua-weight-semibold); } .ua-multiselect-opt.is-disabled { opacity: 0.45; @@ -4008,7 +4008,7 @@ button.ua-step-dot:hover { .ua-popconfirm-text { color: var(--ua-color-text-muted); font-size: var(--ua-font-size-md); - line-height: 1.5; + line-height: var(--ua-leading-normal); } .ua-popconfirm-actions { display: flex; @@ -4045,7 +4045,7 @@ button.ua-step-dot:hover { background: none; color: var(--ua-color-text-muted); font-size: var(--ua-font-size-md); - line-height: 1; + line-height: var(--ua-leading-none); cursor: pointer; transform: translateY(-50%); transition: color var(--ua-duration-fast) var(--ua-ease), background-color var(--ua-duration-fast) var(--ua-ease); @@ -4092,7 +4092,7 @@ button.ua-step-dot:hover { background: none; color: var(--ua-color-accent); font-size: var(--ua-font-size-md); - font-weight: 500; + font-weight: var(--ua-weight-medium); cursor: pointer; transition: color var(--ua-duration-fast) var(--ua-ease); } @@ -4122,7 +4122,7 @@ button.ua-step-dot:hover { .ua-calendar-label { color: var(--ua-color-text); font-size: var(--ua-font-size-md); - font-weight: 500; + font-weight: var(--ua-weight-medium); } .ua-calendar-nav { width: 24px; @@ -4179,7 +4179,7 @@ button.ua-step-dot:hover { .ua-calendar-day.is-selected { background: color-mix(in srgb, var(--ua-color-accent) 10%, transparent); color: var(--ua-color-accent); - font-weight: 500; + font-weight: var(--ua-weight-medium); transform: none; } .ua-calendar-day:focus-visible { @@ -4189,7 +4189,7 @@ button.ua-step-dot:hover { .ua-calendar-mark { color: var(--ua-color-text-muted); font-size: var(--ua-font-size-xs); - line-height: 1; + line-height: var(--ua-leading-none); } @media (prefers-reduced-motion: reduce) { .ua-calendar-day { @@ -4214,15 +4214,15 @@ button.ua-step-dot:hover { .ua-type { color: var(--ua-color-text); font-size: var(--ua-font-size-md); - line-height: 1.65; + line-height: var(--ua-leading-normal); margin: 0; } .ua-type-paragraph + .ua-type-paragraph { margin-top: var(--ua-space-2); /* 段落之间留呼吸,单段不额外撑高 */ } .ua-type-title { - font-weight: 600; - line-height: 1.35; + font-weight: var(--ua-weight-semibold); + line-height: var(--ua-leading-tight); letter-spacing: -0.01em; } h1.ua-type-title { font-size: calc(var(--ua-font-size-lg) * 1.6); } @@ -4234,7 +4234,7 @@ h5.ua-type-title { font-size: var(--ua-font-size-md); } .ua-type.is-success { color: var(--ua-color-success); } .ua-type.is-warning { color: var(--ua-color-warning); } .ua-type.is-danger { color: var(--ua-color-danger); } -.ua-type.is-strong { font-weight: 600; } +.ua-type.is-strong { font-weight: var(--ua-weight-semibold); } .ua-type.is-underline { text-decoration: underline; text-underline-offset: 2px; } .ua-type.is-delete { text-decoration: line-through; color: var(--ua-color-text-muted); } .ua-type.is-underline.is-delete { text-decoration: underline line-through; } @@ -4372,7 +4372,7 @@ h5.ua-type-title { font-size: var(--ua-font-size-md); } .ua-fab-icon { display: inline-flex; align-items: center; - line-height: 1; + line-height: var(--ua-leading-none); } .ua-fab-label { font-size: var(--ua-font-size-sm); @@ -4419,7 +4419,7 @@ h5.ua-type-title { font-size: var(--ua-font-size-md); } color: var(--ua-color-text); font: inherit; font-size: var(--ua-font-size-md); - line-height: 1.6; + line-height: var(--ua-leading-normal); resize: vertical; transition: border-color var(--ua-duration-fast) ease, box-shadow var(--ua-duration-fast) ease; } @@ -4593,7 +4593,7 @@ h5.ua-type-title { font-size: var(--ua-font-size-md); } } .ua-link-icon { font-size: 0.85em; - line-height: 1; + line-height: var(--ua-leading-none); } .ua-visually-hidden { position: absolute; @@ -4692,7 +4692,7 @@ h5.ua-type-title { font-size: var(--ua-font-size-md); } .ua-avatar-more { background: color-mix(in srgb, var(--ua-color-text-muted) 18%, transparent); color: var(--ua-color-text); - font-weight: 500; + font-weight: var(--ua-weight-medium); } /* —— 滑块轨道与刻度:轨道包一层,刻度按百分比定位在下方 —— */ diff --git a/packages/tools/cli/lib/visual-rules.mjs b/packages/tools/cli/lib/visual-rules.mjs index 806d5da..314026d 100644 --- a/packages/tools/cli/lib/visual-rules.mjs +++ b/packages/tools/cli/lib/visual-rules.mjs @@ -46,6 +46,16 @@ export const KIND_META = [ label: '控件高度字面值', hint: 'height 写死控件档位值(28/34/40px),应改用 --ua-control-h-*', }, + { + key: 'weight-literal', + label: '字重字面值', + hint: 'font-weight 写死数字,应改用 --ua-weight-*', + }, + { + key: 'leading-literal', + label: '行高字面值', + hint: 'line-height 写死数值,应改用 --ua-leading-*(px 行高是图形对齐技巧,不入此口径)', + }, ]; /*** 作用:需要扫描的 CSS 属性白名单(其余属性本轮不动;用白名单顺带避开伪类选择器误判) ***/ @@ -74,6 +84,8 @@ const SCAN_PROPS = new Set([ 'min-height', 'max-height', 'height', + 'font-weight', + 'line-height', ]); /*** 作用:内距类属性集合(迁移与统计共用同一判定) ***/ @@ -151,6 +163,26 @@ const CONTROL_H_TOKENS = new Map([ ['40px', 'var(--ua-control-h-lg)'], ]); +/*** 作用:字重字面值 → 令牌(normal/medium/semibold/bold 四档覆盖常见取值) ***/ +const WEIGHT_TOKENS = new Map([ + ['400', 'var(--ua-weight-normal)'], + ['500', 'var(--ua-weight-medium)'], + ['600', 'var(--ua-weight-semibold)'], + ['700', 'var(--ua-weight-bold)'], +]); + +/*** 作用:行高字面值 → 令牌(unitless 归 none/tight/normal 三档,就近收敛;px 行高是图形对齐不入表) ***/ +const LEADING_TOKENS = new Map([ + ['1', 'var(--ua-leading-none)'], + ['1.2', 'var(--ua-leading-none)'], + ['1.35', 'var(--ua-leading-tight)'], + ['1.4', 'var(--ua-leading-tight)'], + ['1.5', 'var(--ua-leading-normal)'], + ['1.55', 'var(--ua-leading-normal)'], + ['1.6', 'var(--ua-leading-normal)'], + ['1.65', 'var(--ua-leading-normal)'], +]); + /*** 作用:压缩空白,便于用稳定字符串比对(不改语义) ***/ export function squash(value) { return String(value).replace(/\s+/g, ' ').trim(); @@ -176,7 +208,7 @@ export function blankComments(src) { /*** 作用:逐条产出「属性 + 值」及其在原文中的偏移;只认白名单属性且必须以分号收尾(避开伪类选择器) ***/ export function eachDeclaration(code) { - const re = /(padding-top|padding-right|padding-bottom|padding-left|padding|margin-top|margin-right|margin-bottom|margin-left|margin|row-gap|column-gap|gap|min-height|max-height|height|transition-duration|animation-duration|transition|animation|font-size|box-shadow|border-radius|outline)\s*:\s*([^;{}]*);/gi; + const re = /(padding-top|padding-right|padding-bottom|padding-left|padding|margin-top|margin-right|margin-bottom|margin-left|margin|row-gap|column-gap|gap|font-weight|line-height|min-height|max-height|height|transition-duration|animation-duration|transition|animation|font-size|box-shadow|border-radius|outline)\s*:\s*([^;{}]*);/gi; const list = []; for (const m of code.matchAll(re)) { const prop = m[1].toLowerCase(); @@ -288,6 +320,20 @@ function migrateControlH(core) { return { kind: 'control-h-literal', value: token }; } +/*** 作用:迁移单条字重:整值命中四档即换令牌 ***/ +function migrateWeight(core) { + const token = WEIGHT_TOKENS.get(squash(core)); + if (!token) return null; + return { kind: 'weight-literal', value: token }; +} + +/*** 作用:迁移单条行高:unitless 数值命中映射表即换令牌(px 与表外值不动) ***/ +function migrateLeading(core) { + const token = LEADING_TOKENS.get(squash(core)); + if (!token) return null; + return { kind: 'leading-literal', value: token }; +} + /*** 作用:判定单条声明是否需要迁移,返回 { kind, value } 或 null(幂等:已是令牌就返回 null) ***/ export function migrateDeclaration(prop, rawValue) { const { core, tail } = splitImportant(rawValue); @@ -306,6 +352,10 @@ export function migrateDeclaration(prop, rawValue) { result = migrateSpace(core); } else if (prop === 'height') { result = migrateControlH(core); + } else if (prop === 'font-weight') { + result = migrateWeight(core); + } else if (prop === 'line-height') { + result = migrateLeading(core); } if (!result) return null; return { kind: result.kind, value: result.value + tail }; @@ -356,6 +406,17 @@ function detect(prop, rawValue, key) { if (prop !== 'height') return 0; // min/max-height 是约束不是控件档位,不入此口径 return CONTROL_H_TOKENS.has(squash(core)) ? 1 : 0; } + if (key === 'weight-literal') { + if (prop !== 'font-weight') return 0; + if (/^var\(/.test(value)) return 0; + return /\d+/.test(value) ? 1 : 0; // normal/bold 关键字与数值都算漂移,一律走令牌 + } + if (key === 'leading-literal') { + if (prop !== 'line-height') return 0; + if (/^var\(/.test(value)) return 0; + if (/px|em|rem|%|calc/.test(value)) return 0; // px 行高是图形对齐技巧(图标 / 角标),不入此口径 + return /^\d+(\.\d+)?$/.test(value) ? 1 : 0; // unitless 数值归三档令牌 + } return 0; }