dsh-visualkit 0.0.0-stage → 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +60 -0
- package/LICENSE +26 -0
- package/README.md +427 -2
- package/THIRD_PARTY.md +55 -0
- package/cordis.patch.yml +29 -0
- package/data/assets/README.md +38 -0
- package/data/assets/icons/brands/bilibili.svg +1 -0
- package/data/assets/icons/brands/discord.svg +1 -0
- package/data/assets/icons/brands/neteasecloudmusic.svg +1 -0
- package/data/assets/icons/brands/qq.svg +1 -0
- package/data/assets/icons/brands/sinaweibo.svg +1 -0
- package/data/assets/icons/brands/steam.svg +1 -0
- package/data/assets/icons/brands/telegram.svg +1 -0
- package/data/assets/icons/brands/tiktok.svg +1 -0
- package/data/assets/icons/brands/twitch.svg +1 -0
- package/data/assets/icons/brands/wechat.svg +1 -0
- package/data/assets/icons/brands/x.svg +1 -0
- package/data/assets/icons/brands/youtube.svg +1 -0
- package/data/assets/icons/lucide/bell.svg +1 -0
- package/data/assets/icons/lucide/calendar.svg +1 -0
- package/data/assets/icons/lucide/clock.svg +1 -0
- package/data/assets/icons/lucide/gamepad-2.svg +1 -0
- package/data/assets/icons/lucide/gift.svg +1 -0
- package/data/assets/icons/lucide/heart.svg +1 -0
- package/data/assets/icons/lucide/mic.svg +1 -0
- package/data/assets/icons/lucide/play.svg +1 -0
- package/data/assets/icons/lucide/star.svg +1 -0
- package/data/assets/icons/lucide/video.svg +1 -0
- package/data/assets/icons/phosphor/LICENSE +21 -0
- package/data/assets/icons/phosphor/atom-duotone.svg +1 -0
- package/data/assets/icons/phosphor/binary-duotone.svg +1 -0
- package/data/assets/icons/phosphor/blueprint-duotone.svg +1 -0
- package/data/assets/icons/phosphor/circuitry-duotone.svg +1 -0
- package/data/assets/icons/phosphor/cloud-arrow-up-duotone.svg +1 -0
- package/data/assets/icons/phosphor/code-duotone.svg +1 -0
- package/data/assets/icons/phosphor/compass-duotone.svg +1 -0
- package/data/assets/icons/phosphor/cpu-duotone.svg +1 -0
- package/data/assets/icons/phosphor/cube-duotone.svg +1 -0
- package/data/assets/icons/phosphor/database-duotone.svg +1 -0
- package/data/assets/icons/phosphor/flask-duotone.svg +1 -0
- package/data/assets/icons/phosphor/frame-corners-duotone.svg +1 -0
- package/data/assets/icons/phosphor/function-duotone.svg +1 -0
- package/data/assets/icons/phosphor/hard-drives-duotone.svg +1 -0
- package/data/assets/icons/phosphor/hexagon-duotone.svg +1 -0
- package/data/assets/icons/phosphor/infinity-duotone.svg +1 -0
- package/data/assets/icons/phosphor/lightning-duotone.svg +1 -0
- package/data/assets/icons/phosphor/magic-wand-duotone.svg +1 -0
- package/data/assets/icons/phosphor/moon-stars-duotone.svg +1 -0
- package/data/assets/icons/phosphor/paint-brush-duotone.svg +1 -0
- package/data/assets/icons/phosphor/pen-nib-duotone.svg +1 -0
- package/data/assets/icons/phosphor/planet-duotone.svg +1 -0
- package/data/assets/icons/phosphor/rocket-launch-duotone.svg +1 -0
- package/data/assets/icons/phosphor/ruler-duotone.svg +1 -0
- package/data/assets/icons/phosphor/selection-plus-duotone.svg +1 -0
- package/data/assets/icons/phosphor/shapes-duotone.svg +1 -0
- package/data/assets/icons/phosphor/sparkle-duotone.svg +1 -0
- package/data/assets/icons/phosphor/sun-horizon-duotone.svg +1 -0
- package/data/assets/icons/phosphor/terminal-window-duotone.svg +1 -0
- package/data/assets/icons/phosphor/triangle-duotone.svg +1 -0
- package/data/assets/icons/phosphor/waveform-duotone.svg +1 -0
- package/data/assets/icons/tabler/alarm.svg +1 -0
- package/data/assets/icons/tabler/badge-ad.svg +1 -0
- package/data/assets/icons/tabler/bell.svg +1 -0
- package/data/assets/icons/tabler/bolt.svg +1 -0
- package/data/assets/icons/tabler/brand-bilibili.svg +1 -0
- package/data/assets/icons/tabler/brand-discord.svg +1 -0
- package/data/assets/icons/tabler/brand-qq.svg +1 -0
- package/data/assets/icons/tabler/brand-twitch.svg +1 -0
- package/data/assets/icons/tabler/brand-wechat.svg +1 -0
- package/data/assets/icons/tabler/brand-weibo.svg +1 -0
- package/data/assets/icons/tabler/brand-youtube.svg +1 -0
- package/data/assets/icons/tabler/broadcast.svg +1 -0
- package/data/assets/icons/tabler/calendar-event.svg +1 -0
- package/data/assets/icons/tabler/calendar-week.svg +1 -0
- package/data/assets/icons/tabler/clock-hour-3.svg +1 -0
- package/data/assets/icons/tabler/coin.svg +1 -0
- package/data/assets/icons/tabler/crown.svg +1 -0
- package/data/assets/icons/tabler/currency-yen.svg +1 -0
- package/data/assets/icons/tabler/device-gamepad-2.svg +1 -0
- package/data/assets/icons/tabler/diamond.svg +1 -0
- package/data/assets/icons/tabler/flame.svg +1 -0
- package/data/assets/icons/tabler/gift.svg +1 -0
- package/data/assets/icons/tabler/headphones.svg +1 -0
- package/data/assets/icons/tabler/heart-filled.svg +1 -0
- package/data/assets/icons/tabler/heart.svg +1 -0
- package/data/assets/icons/tabler/live-view.svg +1 -0
- package/data/assets/icons/tabler/microphone.svg +1 -0
- package/data/assets/icons/tabler/mood-smile.svg +1 -0
- package/data/assets/icons/tabler/mood-tongue.svg +1 -0
- package/data/assets/icons/tabler/movie.svg +1 -0
- package/data/assets/icons/tabler/player-play.svg +1 -0
- package/data/assets/icons/tabler/playlist.svg +1 -0
- package/data/assets/icons/tabler/rocket.svg +1 -0
- package/data/assets/icons/tabler/sparkles.svg +1 -0
- package/data/assets/icons/tabler/star-filled.svg +1 -0
- package/data/assets/icons/tabler/star.svg +1 -0
- package/data/assets/icons/tabler/tag.svg +1 -0
- package/data/assets/icons/tabler/thumb-up.svg +1 -0
- package/data/assets/icons/tabler/ticket.svg +1 -0
- package/data/assets/icons/tabler/video.svg +1 -0
- package/data/assets/illustrations/openpeeps/LICENSE +8 -0
- package/data/assets/illustrations/openpeeps/peep-10.svg +29 -0
- package/data/assets/illustrations/openpeeps/peep-100.svg +32 -0
- package/data/assets/illustrations/openpeeps/peep-101.svg +25 -0
- package/data/assets/illustrations/openpeeps/peep-102.svg +28 -0
- package/data/assets/illustrations/openpeeps/peep-103.svg +32 -0
- package/data/assets/illustrations/openpeeps/peep-105.svg +25 -0
- package/data/assets/illustrations/openpeeps/peep-12.svg +29 -0
- package/data/assets/illustrations/openpeeps/peep-13.svg +27 -0
- package/data/assets/illustrations/openpeeps/peep-16.svg +27 -0
- package/data/assets/illustrations/openpeeps/peep-17.svg +31 -0
- package/data/assets/illustrations/openpeeps/peep-22.svg +29 -0
- package/data/assets/illustrations/openpeeps/peep-25.svg +29 -0
- package/data/assets/illustrations/openpeeps/peep-26.svg +29 -0
- package/data/assets/illustrations/openpeeps/peep-27.svg +29 -0
- package/data/assets/illustrations/openpeeps/peep-30.svg +29 -0
- package/data/assets/illustrations/openpeeps/peep-31.svg +27 -0
- package/data/assets/illustrations/openpeeps/peep-34.svg +29 -0
- package/data/assets/illustrations/openpeeps/peep-38.svg +29 -0
- package/data/assets/illustrations/openpeeps/peep-39.svg +31 -0
- package/data/assets/illustrations/openpeeps/peep-4.svg +29 -0
- package/data/assets/illustrations/openpeeps/peep-41.svg +29 -0
- package/data/assets/illustrations/openpeeps/peep-44.svg +29 -0
- package/data/assets/illustrations/openpeeps/peep-47.svg +26 -0
- package/data/assets/illustrations/openpeeps/peep-48.svg +29 -0
- package/data/assets/illustrations/openpeeps/peep-50.svg +31 -0
- package/data/assets/illustrations/openpeeps/peep-51.svg +29 -0
- package/data/assets/illustrations/openpeeps/peep-53.svg +27 -0
- package/data/assets/illustrations/openpeeps/peep-54.svg +26 -0
- package/data/assets/illustrations/openpeeps/peep-55.svg +29 -0
- package/data/assets/illustrations/openpeeps/peep-56.svg +27 -0
- package/data/assets/illustrations/openpeeps/peep-58.svg +29 -0
- package/data/assets/illustrations/openpeeps/peep-61.svg +27 -0
- package/data/assets/illustrations/openpeeps/peep-64.svg +27 -0
- package/data/assets/illustrations/openpeeps/peep-68.svg +27 -0
- package/data/assets/illustrations/openpeeps/peep-69.svg +27 -0
- package/data/assets/illustrations/openpeeps/peep-7.svg +31 -0
- package/data/assets/illustrations/openpeeps/peep-72.svg +29 -0
- package/data/assets/illustrations/openpeeps/peep-76.svg +31 -0
- package/data/assets/illustrations/openpeeps/peep-77.svg +27 -0
- package/data/assets/illustrations/openpeeps/peep-80.svg +27 -0
- package/data/assets/illustrations/openpeeps/peep-81.svg +29 -0
- package/data/assets/illustrations/openpeeps/peep-83.svg +29 -0
- package/data/assets/manifest.json +1 -0
- package/data/assets/parts//342/206/223/346/212/225/345/275/261.png +0 -0
- package/data/assets/parts//344/270/211/350/247/222/345/217/221/345/244/2711.png +0 -0
- package/data/assets/parts//344/270/211/350/247/222/345/217/221/345/244/2712.png +0 -0
- package/data/assets/parts//344/270/211/350/247/222/345/217/221/345/244/2713.png +0 -0
- package/data/assets/parts//344/270/211/350/247/222/345/217/221/345/244/2714.png +0 -0
- package/data/assets/parts//345/217/214/351/251/254/345/260/276.png +0 -0
- package/data/assets/parts//345/217/214/351/251/254/345/260/2761.png +0 -0
- package/data/assets/parts//345/217/2211.png +0 -0
- package/data/assets/parts//345/217/2212.png +0 -0
- package/data/assets/parts//345/217/2213.png +0 -0
- package/data/assets/parts//345/217/221/345/234/210.png +0 -0
- package/data/assets/parts//345/217/221/345/244/2711.png +0 -0
- package/data/assets/parts//345/217/221/345/244/2712.png +0 -0
- package/data/assets/parts//345/217/243/346/260/264/345/267/276/345/211/215.png +0 -0
- package/data/assets/parts//345/217/243/346/260/264/345/267/276/345/220/216.png +0 -0
- package/data/assets/parts//345/217/263/344/270/212/347/234/274/347/235/221.png +0 -0
- package/data/assets/parts//345/217/263/346/211/213.png +0 -0
- package/data/assets/parts//345/217/263/346/211/2131.png +0 -0
- package/data/assets/parts//345/217/263/347/234/274/347/220/203.png +0 -0
- package/data/assets/parts//345/217/263/347/234/274/347/231/275.png +0 -0
- package/data/assets/parts//345/217/263/350/205/277.png +0 -0
- package/data/assets/parts//345/221/206/346/257/233.png +0 -0
- package/data/assets/parts//345/230/264/345/267/264.png +0 -0
- package/data/assets/parts//345/260/276/345/267/264.png +0 -0
- package/data/assets/parts//345/267/246/344/270/212/347/234/274/347/235/221.png +0 -0
- package/data/assets/parts//345/267/246/345/217/214/347/234/274/347/232/256.png +0 -0
- package/data/assets/parts//345/267/246/346/211/213.png +0 -0
- package/data/assets/parts//345/267/246/346/211/2131.png +0 -0
- package/data/assets/parts//345/267/246/347/234/274/347/220/203.png +0 -0
- package/data/assets/parts//345/267/246/347/234/274/347/231/275.png +0 -0
- package/data/assets/parts//345/267/246/350/205/277.png +0 -0
- package/data/assets/parts//346/212/225/345/275/261.png +0 -0
- package/data/assets/parts//347/234/211/346/257/233.png +0 -0
- package/data/assets/parts//347/234/274/347/275/251.png +0 -0
- package/data/assets/parts//350/200/263/346/234/2651.png +0 -0
- package/data/assets/parts//350/200/263/346/234/2652.png +0 -0
- package/data/assets/parts//350/204/221/345/243/263.png +0 -0
- package/data/assets/parts//350/241/243/346/234/215/345/211/215.png +0 -0
- package/data/assets/parts//350/241/243/346/234/215/345/220/216.png +0 -0
- package/data/assets/parts//350/241/243/346/234/215/351/273/221.png +0 -0
- package/data/assets/parts//350/241/243/346/234/215/351/273/2211.png +0 -0
- package/data/assets/parts//350/272/253/344/275/223.png +0 -0
- package/data/assets/parts//351/223/201/346/235/277.png +0 -0
- package/data/assets/parts//351/273/221/347/234/274/345/234/210.png +0 -0
- package/data/assets/parts//351/273/221/347/234/274/345/234/2102.png +0 -0
- package/data/assets/patterns/checker.svg +1 -0
- package/data/assets/patterns/circles-overlap.svg +1 -0
- package/data/assets/patterns/crosshatch.svg +1 -0
- package/data/assets/patterns/diagonal.svg +1 -0
- package/data/assets/patterns/dots.svg +1 -0
- package/data/assets/patterns/grid.svg +1 -0
- package/data/assets/patterns/halftone.svg +1 -0
- package/data/assets/patterns/plus.svg +1 -0
- package/data/assets/patterns/scales.svg +1 -0
- package/data/assets/patterns/triangles.svg +1 -0
- package/data/assets/patterns/waves.svg +1 -0
- package/data/assets/patterns/zigzag.svg +1 -0
- package/data/assets/stickers/openmoji/1F375.svg +12 -0
- package/data/assets/stickers/openmoji/1F9E8.svg +21 -0
- package/data/assets/stickers/twemoji/1f389.svg +1 -0
- package/data/assets/stickers/twemoji/1f3a4.svg +1 -0
- package/data/assets/stickers/twemoji/1f3a7.svg +1 -0
- package/data/assets/stickers/twemoji/1f3ae.svg +1 -0
- package/data/assets/stickers/twemoji/1f44b.svg +1 -0
- package/data/assets/stickers/twemoji/1f44d.svg +1 -0
- package/data/assets/stickers/twemoji/1f494.svg +1 -0
- package/data/assets/stickers/twemoji/1f4a2.svg +1 -0
- package/data/assets/stickers/twemoji/1f4a8.svg +1 -0
- package/data/assets/stickers/twemoji/1f4b5.svg +1 -0
- package/data/assets/stickers/twemoji/1f4d6.svg +1 -0
- package/data/assets/stickers/twemoji/1f525.svg +1 -0
- package/data/assets/stickers/twemoji/1f600.svg +1 -0
- package/data/assets/stickers/twemoji/1f602.svg +1 -0
- package/data/assets/stickers/twemoji/1f60d.svg +1 -0
- package/data/assets/stickers/twemoji/1f62d.svg +1 -0
- package/data/assets/stickers/twemoji/1f680.svg +1 -0
- package/data/assets/stickers/twemoji/1f979.svg +1 -0
- package/data/assets/stickers/twemoji/1f97a.svg +1 -0
- package/data/assets/stickers/twemoji/26a1.svg +1 -0
- package/data/assets/stickers/twemoji/2705.svg +1 -0
- package/data/assets/stickers/twemoji/274c.svg +1 -0
- package/data/assets/stickers/twemoji/2764.svg +1 -0
- package/data/assets/stickers/twemoji/2b50.svg +1 -0
- package/data/assets/stickers/twemoji_1f525.svg +1 -0
- package/data/assets/stickers/twemoji_1f600.svg +1 -0
- package/data/assets/stickers/twemoji_1f979.svg +1 -0
- package/data/assets/stickers/twemoji_2764.svg +1 -0
- package/data/assets/textures/grain-dark.png +0 -0
- package/data/assets/textures/grain-light.png +0 -0
- package/data/assets/textures/grain-warm.png +0 -0
- package/data/lucide-icons.json +1 -0
- package/data/packs/tabler.icons.json +1 -0
- package/icon.svg +26 -0
- package/lib/assets.js +228 -0
- package/lib/audit.js +284 -0
- package/lib/contrast-render.js +213 -0
- package/lib/css-rewrite.js +379 -0
- package/lib/detect.js +701 -0
- package/lib/diff.js +153 -0
- package/lib/emit.js +557 -0
- package/lib/html-context.js +315 -0
- package/lib/icon-render.js +107 -0
- package/lib/icondb.js +402 -0
- package/lib/image-normalize.js +140 -0
- package/lib/index.js +1289 -0
- package/lib/layouts/common.js +48 -0
- package/lib/layouts/image.js +1010 -0
- package/lib/layouts/index.js +10 -0
- package/lib/layouts/intent.js +66 -0
- package/lib/layouts/metrics-text.js +91 -0
- package/lib/layouts/png.js +75 -0
- package/lib/layouts/rules-contrast.js +575 -0
- package/lib/layouts/rules-punct.js +486 -0
- package/lib/layouts/rules-structure.js +182 -0
- package/lib/layouts/source.js +1961 -0
- package/lib/layouts/svg.js +1204 -0
- package/lib/morphcore.js +139 -0
- package/lib/preview.js +225 -0
- package/lib/render-html.js +255 -0
- package/lib/render.js +486 -0
- package/lib/rules.js +267 -0
- package/lib/shadow.js +83 -0
- package/lib/skip.js +56 -0
- package/lib/style.js +688 -0
- package/lib/svg.js +729 -0
- package/lib/taste-image.js +323 -0
- package/lib/taste.js +219 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +92 -4
- package/vendor/morphicons-adapters.iife.js +903 -0
- package/vendor/morphicons.iife.js +2441 -0
- package/vendor/resvg/LICENSE +373 -0
- package/vendor/resvg/index.d.ts +98 -0
- package/vendor/resvg/index.js +23 -0
- package/vendor/resvg/js-binding.d.ts +52 -0
- package/vendor/resvg/js-binding.js +1 -0
- package/vendor/resvg/package.json +138 -0
- package/vendor/resvg/resvgjs.win32-x64-msvc.node +0 -0
package/lib/rules.js
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* rules.js — 「AI 味」规则表:约束(design_style 的禁用清单)与检测(ui_taste 的指标)
|
|
3
|
+
* 共用同一份定义。
|
|
4
|
+
*
|
|
5
|
+
* 为什么同源:设计的核心矛盾是「规则改了但检测没改」——两边各写一份必然漂移,
|
|
6
|
+
* 模型拿到的约束和验收标准就会打架。这里每条规则只有一处定义。
|
|
7
|
+
*
|
|
8
|
+
* ── scope 分层(2026-11-07 第二次修订)──────────────────────────
|
|
9
|
+
* 上一版把「通用质量」和「风格偏好」混成一层,风格规则占 41% 权重,导致约束本身
|
|
10
|
+
* 变成新的众数(四个预设只因强调色/字体不同才区分开)。现按证据分三层:
|
|
11
|
+
*
|
|
12
|
+
* quality 跨任何风格都成立的底线(字号要有层级、间距有节奏、交互态完整、
|
|
13
|
+
* 留白有分层、图标是真 SVG)。打破它不是"风格选择",是缺陷。
|
|
14
|
+
* style 仅代表一种审美取向(禁紫渐变 / 小圆角 / 禁毛玻璃 / 禁渐变文字 /
|
|
15
|
+
* 居中式首屏)。品牌或题材有理由时**应当允许越界**,故可降权、可声明豁免。
|
|
16
|
+
* content 内容真实度(假大数 / 占位文案)。
|
|
17
|
+
*
|
|
18
|
+
* profile 决定各层的计分系数,见 PROFILES——同一次检测在不同 profile 下
|
|
19
|
+
* 得到不同分数,但检测事实(hits / fix)完全一致。
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
const RULES = [
|
|
23
|
+
{
|
|
24
|
+
id: "emoji-icon",
|
|
25
|
+
// 参与「成组出现」判定:这一项属于风格默认选择,不是通用质量缺陷
|
|
26
|
+
fingerprint: true,
|
|
27
|
+
label: "emoji 当图标",
|
|
28
|
+
scope: "quality",
|
|
29
|
+
weight: 14,
|
|
30
|
+
ban: "不要用 emoji 做图标,用 icon (action=find|emit) 取真 SVG",
|
|
31
|
+
fix: "把 emoji 换成 SVG 图标:icon (action=find) 选型 → icon (action=emit, target=raw) 出码"
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
id: "type-flat",
|
|
35
|
+
label: "字号塌缩(有档位无跳变)",
|
|
36
|
+
scope: "quality",
|
|
37
|
+
weight: 12,
|
|
38
|
+
ban: "字号最多 4 档,且标题/正文之间必须有 ≥1.6× 跳变",
|
|
39
|
+
// 与 layout_check S1 同源判断:这里不重复实现,只把更细的查看入口写出来,
|
|
40
|
+
// 否则两个工具各报一次同一个问题,用户会困惑该听哪个。
|
|
41
|
+
fix: "把字号收敛到 4 档,并让最大档至少是正文的 1.6 倍(如正文 14 → 标题 24/28);逐档明细与 file:line 用 layout_check action=ui(S1)查看"
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
id: "spacing-offgrid",
|
|
45
|
+
label: "间距不在 4px 网格",
|
|
46
|
+
scope: "quality",
|
|
47
|
+
weight: 8,
|
|
48
|
+
ban: "间距落在 4px 网格(4/8/12/16/24/32;2px 仅用于对齐微调)",
|
|
49
|
+
fix: "把间距对齐到 4px 网格:4/8/12/16/24/32,删掉 5/6/7/10/14 这类手调值(2px 微调可保留)"
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
id: "no-states",
|
|
53
|
+
label: "交互态缺失",
|
|
54
|
+
scope: "quality",
|
|
55
|
+
weight: 6,
|
|
56
|
+
ban: "可点的元素必须有 hover / focus-visible / disabled 三态",
|
|
57
|
+
fix: "给按钮/链接/表格行补 :hover、:focus-visible、:disabled;键盘可达是底线"
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
id: "card-uniform",
|
|
61
|
+
// 参与「成组出现」判定:这一项属于风格默认选择,不是通用质量缺陷
|
|
62
|
+
fingerprint: true,
|
|
63
|
+
label: "同构三件套卡片",
|
|
64
|
+
scope: "quality",
|
|
65
|
+
weight: 4,
|
|
66
|
+
ban: "不要整页都是 icon+标题+描述的同构卡片网格",
|
|
67
|
+
fix: "让卡片承载不同信息量:主卡放结论,次卡放数据,边栏放记录;不要一律三件套"
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
id: "no-breath",
|
|
71
|
+
label: "留白无分层",
|
|
72
|
+
scope: "quality",
|
|
73
|
+
weight: 4,
|
|
74
|
+
ban: "页面级的留白要明显大于控件内部留白(至少 3:1)",
|
|
75
|
+
fix: "拉开两种 padding:页面级 32–48px,控件内部 6–10px;现在两者挤在同一量级"
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
id: "purple-gradient",
|
|
79
|
+
// 参与「成组出现」判定:这一项属于风格默认选择,不是通用质量缺陷
|
|
80
|
+
fingerprint: true,
|
|
81
|
+
label: "紫/蓝紫渐变",
|
|
82
|
+
scope: "style",
|
|
83
|
+
weight: 12,
|
|
84
|
+
ban: "默认避免紫/indigo/violet 渐变(AI 最强指纹);品牌主色确需时在 .design-intent.json 声明豁免",
|
|
85
|
+
fix: "渐变改成同色相或干脆去掉:单一强调色 + 中性底色;需要层次就用明度差,不要换色相"
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
id: "radius-blob",
|
|
89
|
+
// 参与「成组出现」判定:这一项属于风格默认选择,不是通用质量缺陷
|
|
90
|
+
fingerprint: true,
|
|
91
|
+
label: "大圆角滥用",
|
|
92
|
+
scope: "style",
|
|
93
|
+
weight: 10,
|
|
94
|
+
ban: "数据密集型界面圆角 ≤8px,不要全站 rounded-xl/2xl(消费类产品可放宽)",
|
|
95
|
+
fix: "圆角降到 ≤8px(数据密集型界面 2–4px);不要给所有容器同一个大圆角"
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
id: "hue-sprawl",
|
|
99
|
+
// 参与「成组出现」判定:这一项属于风格默认选择,不是通用质量缺陷
|
|
100
|
+
fingerprint: true,
|
|
101
|
+
label: "色相家族过多",
|
|
102
|
+
scope: "style",
|
|
103
|
+
weight: 8,
|
|
104
|
+
ban: "强调色只允许 1 个家族,语义色(成功/警告/危险)不与强调色混用",
|
|
105
|
+
fix: "把色相收到 1 个强调家族 + 1 套语义色;其余用中性灰阶拉开层次"
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
id: "shadow-glow",
|
|
109
|
+
// 参与「成组出现」判定:这一项属于风格默认选择,不是通用质量缺陷
|
|
110
|
+
fingerprint: true,
|
|
111
|
+
label: "发光/软阴影",
|
|
112
|
+
scope: "style",
|
|
113
|
+
weight: 8,
|
|
114
|
+
ban: "默认不要发光阴影和彩色投影,最多 1 层中性投影",
|
|
115
|
+
fix: "去掉 0 0 Npx 发光与彩色投影;需要浮起就用 1 层中性阴影或直接靠描边/底色区分"
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
id: "glassmorphism",
|
|
119
|
+
// 参与「成组出现」判定:这一项属于风格默认选择,不是通用质量缺陷
|
|
120
|
+
fingerprint: true,
|
|
121
|
+
label: "毛玻璃/半透明白",
|
|
122
|
+
scope: "style",
|
|
123
|
+
weight: 8,
|
|
124
|
+
ban: "默认不要毛玻璃(backdrop-filter)和大面积半透明白",
|
|
125
|
+
fix: "去掉 backdrop-filter;面板用实色 + 1px 描边,需要层次用底色明度差"
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
id: "gradient-text",
|
|
129
|
+
// 参与「成组出现」判定:这一项属于风格默认选择,不是通用质量缺陷
|
|
130
|
+
fingerprint: true,
|
|
131
|
+
label: "渐变文字",
|
|
132
|
+
scope: "style",
|
|
133
|
+
weight: 7,
|
|
134
|
+
ban: "不要渐变文字(background-clip:text)",
|
|
135
|
+
fix: "标题用实色 + 字重/字号制造层级;渐变文字在深色背景上对比度还会不达标"
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
id: "centered-hero",
|
|
139
|
+
// 参与「成组出现」判定:这一项属于风格默认选择,不是通用质量缺陷
|
|
140
|
+
fingerprint: true,
|
|
141
|
+
label: "居中 hero + CTA",
|
|
142
|
+
scope: "style",
|
|
143
|
+
weight: 3,
|
|
144
|
+
ban: "产品界面默认不要「居中大标题 + 副标题 + 按钮」的营销首屏(官网落地页可以)",
|
|
145
|
+
fix: "改成信息型首屏:一个真实结论句 + 关键指标 + 元数据(谁/何时/范围),而不是营销口号"
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
id: "fake-stat",
|
|
149
|
+
// 参与「成组出现」判定:这一项属于风格默认选择,不是通用质量缺陷
|
|
150
|
+
fingerprint: true,
|
|
151
|
+
label: "假大数/占位文案",
|
|
152
|
+
scope: "content",
|
|
153
|
+
weight: 6,
|
|
154
|
+
ban: "不要 128K+/99.9% 这类不可算的假大数,也不要 Lorem/敬请期待",
|
|
155
|
+
fix: "换成真能算的数字(18,432 行 / 47 分钟)或明确标注为示例数据,不要用 K+/99.9% 充场面"
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
id: "nest-boxes",
|
|
159
|
+
// 参与「成组出现」判定:这一项属于风格默认选择,不是通用质量缺陷
|
|
160
|
+
fingerprint: true,
|
|
161
|
+
label: "容器过度嵌套(框里再套框)",
|
|
162
|
+
scope: "style",
|
|
163
|
+
weight: 12,
|
|
164
|
+
ban: "卡片里不要再画一层带圆角的框:靠间距和底色区分层级,不要靠套娃",
|
|
165
|
+
fix: "把内层那圈 border-radius + 边框去掉,用 padding 与背景色差表达层级;确实要分组时用一条细分隔或留白,而不是再套一个框"
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
id: "divider-sprawl",
|
|
169
|
+
// 参与「成组出现」判定:这一项属于风格默认选择,不是通用质量缺陷
|
|
170
|
+
fingerprint: true,
|
|
171
|
+
label: "分割线泛滥",
|
|
172
|
+
scope: "style",
|
|
173
|
+
weight: 10,
|
|
174
|
+
ban: "不要每个区块之间都画一条线:先用间距分组,线只在间距做不到时用",
|
|
175
|
+
fix: "删掉多数 border-top/bottom 与 <hr>,把区块间距调到 24/32px 让留白完成分组;只保留真正的分组边界(如两张不同性质的表之间)"
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
id: "big-black-tight",
|
|
179
|
+
// 参与「成组出现」判定:这一项属于风格默认选择,不是通用质量缺陷
|
|
180
|
+
fingerprint: true,
|
|
181
|
+
label: "标题又大又黑又挤(≥30px / 700+ / 行高 ≤1.25)",
|
|
182
|
+
scope: "style",
|
|
183
|
+
weight: 14,
|
|
184
|
+
ban: "标题不要靠「更大更黑」硬撑层次:字号降下来、行高放开、用留白与字距做气口",
|
|
185
|
+
fix: "标题字号收到正文的 1.6–2×(正文 16 → 标题 28–32 已足够),font-weight 降到 600,line-height 提到 1.2–1.3"
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
id: "dead-button",
|
|
189
|
+
label: "按钮看着能点却没有结果",
|
|
190
|
+
scope: "quality",
|
|
191
|
+
weight: 16,
|
|
192
|
+
ban: "按钮必须有行为:submit、点击绑定、或明确的 data-action;摆着不响的按钮比没有按钮更伤",
|
|
193
|
+
fix: "给按钮接上真实行为(提交/跳转/打开面板),暂时做不到就渲染成 disabled 或去掉——先确认是不是挂在外层事件委托上,避免误判"
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
id: "ai-copy",
|
|
197
|
+
// 参与「成组出现」判定:这一项属于风格默认选择,不是通用质量缺陷
|
|
198
|
+
fingerprint: true,
|
|
199
|
+
label: "AI 套路文案",
|
|
200
|
+
scope: "content",
|
|
201
|
+
weight: 8,
|
|
202
|
+
ban: "CTA 不要用 → 箭头、“立即/一键/轻松/极致/解锁/赋能/助力”、“XX之旅”,也不要用无缝/闭环/抓手这类黑话",
|
|
203
|
+
// 实测来源:自己做的 7 种风格 UI 里给按钮写了“立即同步 →”,
|
|
204
|
+
// 而当时的规则一条都没抓到——工具在提倡反 AI 味,却查不出自己身上的 AI 味。
|
|
205
|
+
fix: "写成具体动作:把“立即同步 →”改成“同步”/“推送到远端”;删掉箭头与咏叹调形容词,动词+宾语就够"
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
id: "icon-tile-uniform",
|
|
209
|
+
// 参与「成组出现」判定:这一项属于风格默认选择,不是通用质量缺陷
|
|
210
|
+
fingerprint: true,
|
|
211
|
+
label: "图标色块一律",
|
|
212
|
+
scope: "style",
|
|
213
|
+
weight: 7,
|
|
214
|
+
ban: "不要给每个卡片的图标都套同尺寸同色的圆角块(渐变方块尤甚)——这是模板卡片的长相",
|
|
215
|
+
// 实测来源(2026-11-08):写一个当代 AI 首页样本,最显眼的 AI 感就是
|
|
216
|
+
// 三个特性卡里各一个 48px 同款渐变圆角块,而 19 条规则一条都没抓到。
|
|
217
|
+
fix: "选一种:图标直接放在留白里靠对齐建立秩序 / 只给主卡一个色块 / 色块随语义换色(成功·警示·中性)"
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
id: "eyebrow-spam",
|
|
221
|
+
// 参与「成组出现」判定:这一项属于风格默认选择,不是通用质量缺陷
|
|
222
|
+
fingerprint: true,
|
|
223
|
+
label: "小标签逐段开场",
|
|
224
|
+
scope: "style",
|
|
225
|
+
weight: 5,
|
|
226
|
+
ban: "不要每个区块都拿一个小号大字距标签(eyebrow/kicker)当开场,四段以上尤其明显",
|
|
227
|
+
// 保留 0–1 个是正常编辑手法(用来说明栏目归属),四段以上就是模板句式。
|
|
228
|
+
fix: "只留 1 个说明栏目归属,其余靠标题自身的层级与留白分段;小标签不宜承担分段责任"
|
|
229
|
+
}
|
|
230
|
+
];
|
|
231
|
+
|
|
232
|
+
/** 规则 id → 规则对象(ui_taste 的 ignore 参数与报告渲染共用)。 */
|
|
233
|
+
const RULE_BY_ID = new Map(RULES.map((rule) => [rule.id, rule]));
|
|
234
|
+
|
|
235
|
+
const TOTAL_WEIGHT = RULES.reduce((sum, rule) => sum + rule.weight, 0);
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* 检测档位:同一次检测,不同 profile 下各层的计分系数不同。
|
|
239
|
+
*
|
|
240
|
+
* strict 全规则满权重——适合"确认这就是 AI 默认产物"的诊断场景。
|
|
241
|
+
* balanced 默认。风格层降到 0.4:品牌/题材有理由的风格选择不应被判死刑,
|
|
242
|
+
* 但仍然能被看见。这是在"反同质化"与"仍能发现塌缩"之间的折中。
|
|
243
|
+
* structure 只查质量与内容(风格权重 0)——给已有明确视觉方向的团队用,
|
|
244
|
+
* 避免这套规则反过来把他们的品牌风格判成问题。
|
|
245
|
+
*/
|
|
246
|
+
const PROFILES = {
|
|
247
|
+
strict: { label: "全规则", quality: 1, style: 1, content: 1 },
|
|
248
|
+
balanced: { label: "默认(风格层降权)", quality: 1, style: 0.4, content: 0.8 },
|
|
249
|
+
structure: { label: "只查结构", quality: 1, style: 0, content: 0.5 }
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* 分数分级(balanced 口径)。
|
|
254
|
+
* 阈值按实测校准:模型无约束输出 75、第三方 AI 插件 UI 54、受约束手写 2-5。
|
|
255
|
+
*
|
|
256
|
+
* **口径纪律**:分数是诊断工具,不是 KPI。只在 ai-tinged / ai-default 区间
|
|
257
|
+
* 提示返工;clean 区间(≤15)不再"优化"——继续压分会把设计往安全区推,反而
|
|
258
|
+
* 制造同质化(Goodhart)。报告里会显式说明这一点。
|
|
259
|
+
*/
|
|
260
|
+
function gradeOf(score) {
|
|
261
|
+
if (score <= 15) return "clean";
|
|
262
|
+
if (score <= 30) return "acceptable";
|
|
263
|
+
if (score <= 50) return "ai-tinged";
|
|
264
|
+
return "ai-default";
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export { PROFILES, RULES, RULE_BY_ID, TOTAL_WEIGHT, gradeOf };
|
package/lib/shadow.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* shadow.js — CSS box-shadow 语法的解析与「阴影图元」。
|
|
3
|
+
*
|
|
4
|
+
* 为什么必须是**图元**而不是风格:高程体系、卡片、浮层、按奥态、浮雕、
|
|
5
|
+
* 玻璃底的柔和投影——全部只靠「偏移 + 模糊 + 颜色」三件事。
|
|
6
|
+
* 拆成一个可组合的参数,用户想做什么深度就做什么深度;
|
|
7
|
+
* 写成 `style: "neumorphic"` 就把用户锁在一种设计里了。
|
|
8
|
+
*
|
|
9
|
+
* 语法完全照 CSS box-shadow(模型对这套语法已经很熟):
|
|
10
|
+
* "0 4px 12px rgba(0,0,0,0.25)"
|
|
11
|
+
* "8px 8px 16px #bebebe, -8px -8px 16px #ffffff" ← 双光源(微拟物常用)
|
|
12
|
+
* "inset 0 2px 4px rgba(0,0,0,0.35)" ← 内阴影(被压入)
|
|
13
|
+
*
|
|
14
|
+
* 实测约束(2026-11-07,resvg):
|
|
15
|
+
* - `feDropShadow` **无效**(阴影区完全透明)→ 外层阴影用「模糊的偏移副本」实现;
|
|
16
|
+
* - `feMorphology`(spread)**无效** → 第 4 个数值目前忽略,并在 notes 里说明。
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/** 按顶层逗号切分(忽略 rgba(...) 里的逗号)。 */
|
|
20
|
+
function splitTopLevel(input) {
|
|
21
|
+
const out = [];
|
|
22
|
+
let depth = 0;
|
|
23
|
+
let cur = "";
|
|
24
|
+
for (const ch of input) {
|
|
25
|
+
if (ch === "(") depth += 1;
|
|
26
|
+
else if (ch === ")") depth = Math.max(0, depth - 1);
|
|
27
|
+
if (ch === "," && depth === 0) {
|
|
28
|
+
out.push(cur);
|
|
29
|
+
cur = "";
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
cur += ch;
|
|
33
|
+
}
|
|
34
|
+
if (cur.trim() !== "") out.push(cur);
|
|
35
|
+
return out;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** 单个阴影:`inset? dx dy blur? spread? color`。颜色 = 数值 token 之后的全部。 */
|
|
39
|
+
function parseOne(part) {
|
|
40
|
+
const text = part.trim();
|
|
41
|
+
if (text === "") return null;
|
|
42
|
+
const inset = /^inset\b/i.test(text);
|
|
43
|
+
const body = inset ? text.replace(/^inset\b/i, "").trim() : text;
|
|
44
|
+
const tokens = body.split(/\s+/);
|
|
45
|
+
const nums = [];
|
|
46
|
+
let i = 0;
|
|
47
|
+
while (i < tokens.length && nums.length < 4) {
|
|
48
|
+
const m = /^(-?\d*\.?\d+)(px)?$/.exec(tokens[i]);
|
|
49
|
+
if (m === null) break;
|
|
50
|
+
nums.push(Number(m[1]));
|
|
51
|
+
i += 1;
|
|
52
|
+
}
|
|
53
|
+
if (nums.length < 2) return null;
|
|
54
|
+
const color = tokens.slice(i).join(" ").trim();
|
|
55
|
+
return {
|
|
56
|
+
inset,
|
|
57
|
+
dx: nums[0],
|
|
58
|
+
dy: nums[1],
|
|
59
|
+
blur: nums[2] ?? 0,
|
|
60
|
+
spread: nums[3] ?? 0,
|
|
61
|
+
color: color === "" ? "rgba(0,0,0,0.25)" : color
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* 解析整串 box-shadow。非法片段跳过(不因一个写法错误就整次渲染失败)。
|
|
67
|
+
* @returns {{list: object[], droppedSpread: boolean}}
|
|
68
|
+
*/
|
|
69
|
+
export function parseShadows(spec) {
|
|
70
|
+
if (typeof spec !== "string" || spec.trim() === "") return { list: [], droppedSpread: false };
|
|
71
|
+
const list = [];
|
|
72
|
+
let droppedSpread = false;
|
|
73
|
+
for (const part of splitTopLevel(spec)) {
|
|
74
|
+
const one = parseOne(part);
|
|
75
|
+
if (one === null) continue;
|
|
76
|
+
if (one.spread !== 0) droppedSpread = true;
|
|
77
|
+
list.push(one);
|
|
78
|
+
}
|
|
79
|
+
return { list, droppedSpread };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** CSS 的 blur 半径 ≈ 2× Gaussian σ;内部统一用 σ。 */
|
|
83
|
+
export const cssBlurToSigma = (blur) => Math.max(0, blur / 2);
|
package/lib/skip.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/skip.js — 「哪些目录/文件不参与体检」的**单一来源**。
|
|
3
|
+
*
|
|
4
|
+
* 为什么必须共用:之前 taste.js 与 layouts/source.js 各写了一份 SKIP_DIRS,
|
|
5
|
+
* 修了一边另一边就漏。实测(2026-11-07,dsh-visualkit 自查):
|
|
6
|
+
* layout_check 已经跳过 test/ 了,但 taste 还在扫 test/fixtures/taste/ai-default/,
|
|
7
|
+
* 把那个「故意做成 AI 味」的样本算进自己的分数 → 自己的仓库被报 12 条 AI 味。
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/** 不看即跳过的目录名(精确匹配)。 */
|
|
11
|
+
export const SKIP_DIRS = new Set([
|
|
12
|
+
"node_modules",
|
|
13
|
+
".git",
|
|
14
|
+
"dist",
|
|
15
|
+
"build",
|
|
16
|
+
"vendor",
|
|
17
|
+
"coverage",
|
|
18
|
+
".next",
|
|
19
|
+
".nuxt",
|
|
20
|
+
"out",
|
|
21
|
+
".output",
|
|
22
|
+
"test",
|
|
23
|
+
"tests",
|
|
24
|
+
"__tests__",
|
|
25
|
+
"fixtures",
|
|
26
|
+
"spec",
|
|
27
|
+
// 开发/构建脚本不是界面代码:实测(Colibri)scripts/check.js、repolint.js、
|
|
28
|
+
// screenshot*.js 里的 ✓✗ 标记被算成“emoji 当图标”——那些是跑 CI 的脚本,
|
|
29
|
+
// 根本不会渲染到界面上。
|
|
30
|
+
"scripts",
|
|
31
|
+
"script",
|
|
32
|
+
"tools",
|
|
33
|
+
"ci",
|
|
34
|
+
// 临时/生成产物:实测(Colibri)tmp/viz/*.html 是脚本生成的报告
|
|
35
|
+
"tmp",
|
|
36
|
+
".tmp",
|
|
37
|
+
".temp",
|
|
38
|
+
"temp"
|
|
39
|
+
]);
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* 目录是否应跳过:除精确名外,还要盖住 `test_audio`、`test-e2e`、`*-test` 这类命名。
|
|
43
|
+
* 实测(Echo):`test_audio/bug-repro/*.html` 里的断言文本被报成标点问题。
|
|
44
|
+
*/
|
|
45
|
+
export function isSkippableDir(name) {
|
|
46
|
+
if (typeof name !== "string" || name === "") return false;
|
|
47
|
+
if (SKIP_DIRS.has(name)) return true;
|
|
48
|
+
const lower = name.toLowerCase();
|
|
49
|
+
return lower.startsWith("test_") || lower.startsWith("test-") || lower.endsWith("_test") || lower.endsWith("-test");
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* 测试文件本身:`foo.test.tsx` 里的字符串是**样本数据**(故意写错的文案、
|
|
54
|
+
* markdown fixture),拿它当界面文案判标点/混排是纯误报。
|
|
55
|
+
*/
|
|
56
|
+
export const TEST_FILE_RE = /(?:^|[\\/])[^\\/]*\.(?:test|spec)\.[jt]sx?$/i;
|