cardbuddy 0.16.0 → 0.17.0
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/dist/init.js +0 -2
- package/dist/preview-editor/assets/index-CXDf5tVg.css +1 -0
- package/dist/preview-editor/assets/index-DLQkCPLT.js +337 -0
- package/dist/preview-editor/index.html +2 -2
- package/dist/vendored/server/core/browser.js +17 -0
- package/dist/vendored/server/core/cardFiles.js +49 -1
- package/dist/vendored/server/core/cardImage.js +103 -2
- package/dist/vendored/server/core/screenshot.js +94 -0
- package/dist/vendored/skills/card-skill-trainer/SKILL.md +7 -7
- package/dist/vendored/skills/card-skill-trainer/scripts/plan_next_round.mjs +1 -1
- package/dist/vendored/skills/card-skill-trainer/scripts/run_round.mjs +7 -2
- package/dist/vendored/skills/dsl-card-generator/references/component_rules.md +14 -0
- package/dist/vendored/skills/dsl-card-generator/references/generation_pitfalls.md +48 -0
- package/dist/vendored/skills/dsl-card-generator/references/previewer_contract.md +21 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/scaffold.mjs +48 -1
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/v3_assemble.mjs +41 -4
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/v3_extract.mjs +57 -6
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/v3_shared.mjs +17 -3
- package/dist/vendored/skills/dsl-card-generator/scripts/v3_regression.mjs +8 -1
- package/dist/vendored/skills/finmall-card-sync/scripts/api_batch_sync.mjs +46 -2
- package/dist/vendored/skills/mbank-design-gen/SKILL.md +76 -0
- package/dist/vendored/skills/mbank-design-gen/assets/icons/icon-apps.png +0 -0
- package/dist/vendored/skills/mbank-design-gen/assets/icons/icon-arrow-right-blue.png +0 -0
- package/dist/vendored/skills/mbank-design-gen/assets/icons/icon-check.png +0 -0
- package/dist/vendored/skills/mbank-design-gen/assets/icons/icon-settings.png +0 -0
- package/dist/vendored/skills/mbank-design-gen/assets/icons/icon-sound.png +0 -0
- package/dist/vendored/skills/mbank-design-gen/assets/icons/icon-voice-switch.png +0 -0
- package/dist/vendored/skills/mbank-design-gen/assets/icons/progress-thumb.png +0 -0
- package/dist/vendored/skills/mbank-design-gen/assets/icons/tag-bg.png +0 -0
- package/dist/vendored/skills/mbank-design-gen/assets/icons/vs-badge.png +0 -0
- package/dist/vendored/skills/mbank-design-gen/assets/ref/ref-chat-options.png +0 -0
- package/dist/vendored/skills/mbank-design-gen/assets/ref/ref-home-scene.png +0 -0
- package/dist/vendored/skills/mbank-design-gen/assets/ref/ref-product-detail.png +0 -0
- package/dist/vendored/skills/mbank-design-gen/assets/ref/ref-rate-table.png +0 -0
- package/dist/vendored/skills/mbank-design-gen/assets/robot.png +0 -0
- package/dist/vendored/skills/mbank-design-gen/references/component-library.md +127 -0
- package/dist/vendored/skills/mbank-design-gen/references/design-spec.md +261 -0
- package/dist/vendored/skills/mbank-design-gen/templates/mockup-template.html +138 -0
- package/dist/vendored/skills/mbank-design-gen/tools/screenshot.mjs +34 -0
- package/dist/vendored/skills/shared/spec-contract.md +10 -1
- package/dist/vendored/skills/sketch-spec-to-card/SKILL.md +1 -1
- package/dist/vendored/skills/sketch-spec-to-card/scripts/parse_meaxure.py +30 -0
- package/dist/vendored/skills/sketch-spec-to-card/scripts/test_text_only_artboard.py +25 -3
- package/dist/vendored/skills/sketch-spec-to-card/scripts/vision.py +15 -3
- package/dist/vendored/templates/helloCard/schema.json +62 -1
- package/dist/vendored/tools/finmall-env.mjs +1 -18
- package/package.json +1 -1
- package/dist/preview-editor/assets/index-BkbBxDTT.css +0 -1
- package/dist/preview-editor/assets/index-Bro07DTa.js +0 -313
|
@@ -544,6 +544,18 @@ function buildSchemaFromSemantics(sem, id, name, opts = {}) {
|
|
|
544
544
|
for (const cluster of sem.chartClusters || []) {
|
|
545
545
|
addSemanticBound(cluster.y, cluster.y + cluster.height, true)
|
|
546
546
|
}
|
|
547
|
+
// 富文本段落块(多行段落按视觉行重组、逐片保留字重):占据真实纵向空间,必须登记边界——
|
|
548
|
+
// 否则下游行(辅助文案/徽章行/按钮)的顶距只能从更早的语义底沿起算,逐段累积下漂
|
|
549
|
+
// (r265 实测:首段之后所有内容整体 +88px,全卡溢出 ~400px)。
|
|
550
|
+
for (const rb of sem.richBlocks || []) {
|
|
551
|
+
const box = rect(rb.container)
|
|
552
|
+
const lineBottomOf = (rb.lines || []).reduce((maxBottom, line) => Math.max(
|
|
553
|
+
maxBottom,
|
|
554
|
+
(rect(line[0]).y || 0) + Math.max(...line.map((f) => rect(f).height || 0)),
|
|
555
|
+
), 0)
|
|
556
|
+
const bottom = Math.max((rb.y || 0) + (box.height || 0), lineBottomOf)
|
|
557
|
+
addSemanticBound(rb.y, bottom, true)
|
|
558
|
+
}
|
|
547
559
|
for (const aux of sem.auxTexts || []) {
|
|
548
560
|
addSemanticBound(aux.y, textBoundBottom(aux.y, aux.height, aux.fontSize))
|
|
549
561
|
// 主视觉领行(文本左侧大切片):流式行高取主视觉高度;主视觉溢出卡顶时流式
|
|
@@ -2819,7 +2831,10 @@ function buildSchemaFromSemantics(sem, id, name, opts = {}) {
|
|
|
2819
2831
|
// 拓扑驱动:先渲染「实时到账」类 mid 文本,表单行随后,footer 文本(温馨提示/最大金额)留到按钮之后。
|
|
2820
2832
|
const formTopY = sem.formRows && sem.formRows.length
|
|
2821
2833
|
? Math.min(...sem.formRows.map((f) => f.label.y))
|
|
2822
|
-
|
|
2834
|
+
// buttons[] 是归一化对象(几何挂在 x/y/width/height,没有 .rect),rect() 读不到会恒取 0 →
|
|
2835
|
+
// formTopY 塌成 0 时 midAux 全空、全部辅助文案并进 footAux(顶距退化成「同子列上一行估算底沿」)。
|
|
2836
|
+
// 归一化字段优先、spec 元素 rect 兜底。
|
|
2837
|
+
: (sem.buttons.length ? (Number.isFinite(sem.buttons[0].y) ? sem.buttons[0].y : (rect(sem.buttons[0]).y || 0))
|
|
2823
2838
|
: (scatterBand ? scatterBand.pillTop : 9999))
|
|
2824
2839
|
const midAux = (sem.postAmountTexts || []).filter((t) => (t.y || 0) < formTopY)
|
|
2825
2840
|
const footAux = (sem.postAmountTexts || []).filter((t) => (t.y || 0) >= formTopY)
|
|
@@ -2837,7 +2852,7 @@ function buildSchemaFromSemantics(sem, id, name, opts = {}) {
|
|
|
2837
2852
|
}
|
|
2838
2853
|
return bottom
|
|
2839
2854
|
}
|
|
2840
|
-
const lineBottom = (pat) => (pat.y || 0) + (
|
|
2855
|
+
const lineBottom = (pat) => (pat.y || 0) + estimatedTextBoxHeight(pat.height, pat.fontSize || 14)
|
|
2841
2856
|
// 上方元素底沿:前一项行底;第一项用显式传入的 beforeBottom(主金额底/按钮底/表单底),否则 null(回退固定值)
|
|
2842
2857
|
function auxRowPadding(pat, prevPat, isLast, beforeBottom) {
|
|
2843
2858
|
const gaps = {}
|
|
@@ -3447,6 +3462,17 @@ function buildSchemaFromSemantics(sem, id, name, opts = {}) {
|
|
|
3447
3462
|
})
|
|
3448
3463
|
previousFormBottom = rowVisualBottom
|
|
3449
3464
|
}
|
|
3465
|
+
// 表单行认领的图标(左侧 icon / 值前导媒体 / 行尾媒体 / 右侧 affordance)已在本行渲染,
|
|
3466
|
+
// 必须从孤立媒体兜底池回收——否则同一个切片渲染两遍(重影 + duplication 掉分),
|
|
3467
|
+
// 且兜底行会另起一段流式内容把按钮整体下推(r282 实测:卡图标在账户行与按钮行各出现一次,
|
|
3468
|
+
// 按钮行被顶出卡片底、y≈716 单带像素差异 88%)。
|
|
3469
|
+
if (Array.isArray(sem.isolatedBlocks)) {
|
|
3470
|
+
const _rowClaimed = new Set(sorted.flatMap((p2) => [p2.leftIconSlice, p2.valueLeadSlice,
|
|
3471
|
+
p2.trailingIconSlice, p2.rightAffordanceSlice]).filter(Boolean))
|
|
3472
|
+
if (_rowClaimed.size) {
|
|
3473
|
+
sem.isolatedBlocks = sem.isolatedBlocks.filter((bl) => !_rowClaimed.has(bl.element))
|
|
3474
|
+
}
|
|
3475
|
+
}
|
|
3450
3476
|
// Interleave dividers that fall between form rows into formHost
|
|
3451
3477
|
const formTopY = sorted[0].label.y
|
|
3452
3478
|
const formBottomY = Math.max(...sorted.map((fr) => Math.max(...[fr.value, ...(fr.subValues || []), fr.label]
|
|
@@ -3708,6 +3734,9 @@ if (subsActive.length && !sem._subAmountsConsumed) {
|
|
|
3708
3734
|
})()
|
|
3709
3735
|
// 单按钮行的水平内边距按 spec 按钮边缘推导(flex-end→右缘、flex-start→左缘),
|
|
3710
3736
|
// 通用卡 padding 会把按钮钉在错误内缩上(如壳内右锚按钮偏出 16px)。
|
|
3737
|
+
// 注意:只按断言侧给单边内缩。两侧同时写几何内缩会把 width:100% 的按钮挤窄
|
|
3738
|
+
// (右锚 195+16 内缩 → 内容盒只剩 132px → 文案换行溢出 textOverflow 门,
|
|
3739
|
+
// r276 于 33 例上实测回归)。按钮宽度由下面的「设计宽 vs 撑满行」分支决定。
|
|
3711
3740
|
const btnLeftPad = singleBtn && singleJustify === 'flex-start'
|
|
3712
3741
|
? Math.max(0, Math.round((singleBtn.x || buildCardLeft) - buildCardLeft))
|
|
3713
3742
|
: T.pad('card')
|
|
@@ -3733,7 +3762,12 @@ if (subsActive.length && !sem._subAmountsConsumed) {
|
|
|
3733
3762
|
if (sem.buttons.length > 1) {
|
|
3734
3763
|
setStyleChild(b, 'flex', 'flexShrink', '1')
|
|
3735
3764
|
setStyleChild(b, 'rect', 'width', '100%')
|
|
3736
|
-
|
|
3765
|
+
// 单按钮宽度:设计宽 < 卡宽 ⇒ 标注带显式设计宽(含「壳内按钮的内缩宽」),用设计宽 +
|
|
3766
|
+
// flexShrink:0;水平位置交给行内边距与 justify(见上,只给断言侧单边内缩)。
|
|
3767
|
+
// 旧判据用「卡宽 ×0.8」这条魔数比例,落在 80%~100% 区间的设计宽会被并进「撑满行」分支、
|
|
3768
|
+
// 宽度被抹成行内容盒宽(r273 实测 279/343 的按钮渲染成 311 满宽);
|
|
3769
|
+
// 而右锚按钮(flex-end)本就该用设计宽——撑满行会把按钮左缘推到卡片左缘。
|
|
3770
|
+
} else if (btn.width && btn.width < (sem.cardRect?.width || 375) - 2) {
|
|
3737
3771
|
setStyleChild(b, 'flex', 'flexShrink', '0')
|
|
3738
3772
|
setStyleChild(b, 'rect', 'width', String(Math.round(btn.width)))
|
|
3739
3773
|
} else {
|
|
@@ -3786,7 +3820,10 @@ if (subsActive.length && !sem._subAmountsConsumed) {
|
|
|
3786
3820
|
for (let i = 0; i < footAux.length; i++) {
|
|
3787
3821
|
const pat = footAux[i]
|
|
3788
3822
|
const isLast = i === footAux.length - 1
|
|
3789
|
-
|
|
3823
|
+
// 顶距锚点优先取「真实语义底沿」而非同子列上一行的估算底沿:两者之间可能夹着
|
|
3824
|
+
// 其它通道渲染的块(序号徽章行 / 富文本段落 / 分割线),用上一行估算底沿会把夹层
|
|
3825
|
+
// 高度重复计入顶距、逐段累积下漂(r265 实测每段 +34px)。取不到语义底沿才回退。
|
|
3826
|
+
const beforeBottom = previousSemanticBottom(pat.y) ?? (i > 0 ? lineBottom(footAux[i - 1]) : btnBottom)
|
|
3790
3827
|
const geom = auxRowPadding(pat, i > 0 ? footAux[i - 1] : null, isLast, beforeBottom)
|
|
3791
3828
|
// 水平位置同 midAux 纪律:左 padding 由 spec x 坐标驱动(几何锚定与对齐意图无关),
|
|
3792
3829
|
// 缺了会把左对齐页脚(金额说明/提示语)甩到卡片默认内缩处。
|
|
@@ -2511,6 +2511,10 @@ const floatDecos = []
|
|
|
2511
2511
|
if (formConsumed.has(t) || panelTextConsumed.has(t)) return false
|
|
2512
2512
|
if (subAmountEls.has(t)) return false
|
|
2513
2513
|
if (_panelGroupMemberEls.has(t)) return false
|
|
2514
|
+
// 序号徽章行成员(数字壳 + 右侧内容)已被 badgeRows 通道整体渲染:同一文本元素不得
|
|
2515
|
+
// 再被 listTitle 认领。否则徽章行内容二次渲染(重影),且 postAmountTexts 的 y 上界被
|
|
2516
|
+
// 这个假标题截断——假标题下方的段落/页脚文案整批静默丢失(r265 实测 5 段文案蒸发)。
|
|
2517
|
+
if (_badgeRowTextEls.has(t)) return false
|
|
2514
2518
|
// 复合行模式:行标题判定 = 候选下方 18-110px 有波段,且【另一文本】在相同相对距离(±4px)也有波段
|
|
2515
2519
|
// (行结构周期性重复)。卡标题(如「我的账户」距首波段 88px,无周期同伴)不受影响。
|
|
2516
2520
|
if (repeatRowMode) {
|
|
@@ -2918,6 +2922,9 @@ const floatDecos = []
|
|
|
2918
2922
|
}))
|
|
2919
2923
|
// 面板组成员:由面板列表模板整体接管
|
|
2920
2924
|
&& !_panelGroupMemberEls.has(t)
|
|
2925
|
+
// 序号徽章行成员(数字 + 右侧内容):行结构已由 badgeRows 通道还原,
|
|
2926
|
+
// 再当列表项标题会把同一行渲染两次(徽章行 + ListLayout 项重影)。
|
|
2927
|
+
&& !_badgeRowTextEls.has(t)
|
|
2921
2928
|
})
|
|
2922
2929
|
// 同 y(±10)的 first-col 文本聚合为一项;按 y 排序
|
|
2923
2930
|
// P0/P1 升级:列表项必须存在「标题下方 18-36px 的描述文本」(desc)——否则摘要区
|
|
@@ -3590,20 +3597,46 @@ const floatDecos = []
|
|
|
3590
3597
|
} : null
|
|
3591
3598
|
|
|
3592
3599
|
// —— 底部控制件:只用“按钮区下方 + 居中 + 小尺寸近似方形媒体”判定;低置信度的纯文本不建模。
|
|
3600
|
+
// 「折叠/展开控件」是**卡底**控件,判据必须锚在卡底区域,不能只锚「按钮区之下」:
|
|
3601
|
+
// 无按钮卡里 buttonShapes 为空会让 btnRegionBottom 塌成 0,于是卡中任何
|
|
3602
|
+
// 「小尺寸近方形 + 横向居中」的切片都被误认(如账户行左侧的圆形卡标,
|
|
3603
|
+
// r282 实测:同一图标在账户行与卡底各渲染一次,且卡底多出的那一行把
|
|
3604
|
+
// 底部按钮整体顶出内容区,单带像素差异 88%)。
|
|
3605
|
+
// 补一道真值门:切片必须落在卡内下缘带内(底沿不低于卡底 − 带高),
|
|
3606
|
+
// 且不得高于卡片内容区(文本/媒体)底沿——控件在内容之下,不在内容之中。
|
|
3593
3607
|
const collapseBtn = (() => {
|
|
3608
|
+
const cardRectR = rect(card)
|
|
3609
|
+
const cardBottom = (cardRectR.y || 0) + (cardRectR.height || 0)
|
|
3594
3610
|
const btnRegionBottom = buttonShapes.length
|
|
3595
3611
|
? Math.max(...buttonShapes.map((s) => (rect(s).y || 0) + (rect(s).height || 0)))
|
|
3596
3612
|
: 0
|
|
3613
|
+
// 卡内下缘带高:不超过卡高 22%,至少容得下控件自身高度
|
|
3614
|
+
const bottomBandHeight = Math.max(24, Math.round((cardRectR.height || 0) * 0.22))
|
|
3615
|
+
// 内容底沿:所有文本/媒体元素的最低底沿(归一化对象用 rect() 读不到时按 0 略过)
|
|
3616
|
+
let contentBottom = 0
|
|
3617
|
+
for (const t of texts) {
|
|
3618
|
+
const tr = rect(t)
|
|
3619
|
+
contentBottom = Math.max(contentBottom, (tr.y || 0) + (tr.height || 0))
|
|
3620
|
+
}
|
|
3621
|
+
for (const s of slices) {
|
|
3622
|
+
const sr = rect(s)
|
|
3623
|
+
contentBottom = Math.max(contentBottom, (sr.y || 0) + (sr.height || 0))
|
|
3624
|
+
}
|
|
3597
3625
|
const mediaSlice = slices.find((s) => {
|
|
3598
3626
|
const sr = rect(s)
|
|
3599
3627
|
const width = sr.width || 0
|
|
3600
3628
|
const height = sr.height || 0
|
|
3601
3629
|
const center = (sr.x || 0) + width / 2
|
|
3602
|
-
const cardCenter = (
|
|
3630
|
+
const cardCenter = (cardRectR.x || 0) + ((cardRectR.width || 0) / 2)
|
|
3631
|
+
const bottom = (sr.y || 0) + height
|
|
3603
3632
|
return width >= 12 && width <= 36 && height >= 12 && height <= 36
|
|
3604
3633
|
&& Math.abs(width - height) <= Math.max(10, width * 0.5)
|
|
3605
3634
|
&& (sr.y || 0) > btnRegionBottom
|
|
3606
|
-
|
|
3635
|
+
// 落在卡内下缘带内(真值门)
|
|
3636
|
+
&& bottom >= cardBottom - bottomBandHeight
|
|
3637
|
+
// 在内容之下而非内容之中:控件顶沿不得低于内容底沿减去自身高度容差
|
|
3638
|
+
&& (sr.y || 0) >= contentBottom - height - 4
|
|
3639
|
+
&& Math.abs(center - cardCenter) <= Math.max(24, (cardRectR.width || 0) * 0.12)
|
|
3607
3640
|
})
|
|
3608
3641
|
const textEl = null
|
|
3609
3642
|
if (!mediaSlice) return null
|
|
@@ -4018,8 +4051,9 @@ const orphanSlices = slices.filter((s) => {
|
|
|
4018
4051
|
}
|
|
4019
4052
|
|
|
4020
4053
|
// —— 壳内右列文本(两列拓扑)——
|
|
4021
|
-
// 内容壳(面板壳/渐变壳)右半区独立成列的文本:>=2
|
|
4022
|
-
// (x 差 <=8、分属不同行),壳内左侧另有 >=2
|
|
4054
|
+
// 内容壳(面板壳/渐变壳)右半区独立成列的文本:>=2 个文本左边界落在列界之后且互相成列
|
|
4055
|
+
// (x 差 <=8、分属不同行),壳内左侧另有 >=2 个文本、或存在前导媒体(整块图/大切片)→
|
|
4056
|
+
// 该壳是「左列 + 右列」拓扑而非竖排单列。列界取前导媒体右缘,无媒体才回退「壳宽 45%」。
|
|
4023
4057
|
// 此前右列文本或被压成竖排堆叠(panelRows 路径),或整段落进主视觉死区不渲染(渐变壳路径)。
|
|
4024
4058
|
const shellButtonTextEls = new Set(buttons.map((button) => button.sourceEl).filter(Boolean))
|
|
4025
4059
|
const shellRightColumns = []
|
|
@@ -4031,7 +4065,22 @@ const orphanSlices = slices.filter((s) => {
|
|
|
4031
4065
|
// 标题行(与壳内首个文本同 band)整体保留为整行:名称 + 紧邻短标签不是右列成员
|
|
4032
4066
|
const headerEls = inner.filter((t) => Math.abs((rect(t).y || 0) - topY) <= 8)
|
|
4033
4067
|
const headerSet = new Set(headerEls)
|
|
4034
|
-
|
|
4068
|
+
// 前导媒体(壳内左侧的整块图 / 大切片):高显著大于壳内行高、右缘落在壳左半区。
|
|
4069
|
+
// 右列左界此前固定取「壳宽 45%」——该比例隐含「前导媒体宽 ≈ 壳宽一半」的假设;
|
|
4070
|
+
// 媒体较窄时(80px 图 / 311px 壳)右列起点落在比例线左侧,右列文本整体被划到左列一侧
|
|
4071
|
+
// → left.length 恒 < 2 → 两列拓扑被整条判死,右列文本退化成全宽竖排行。
|
|
4072
|
+
// 有前导媒体时改用其右缘为列界(几何真值),无媒体才回退比例线。
|
|
4073
|
+
const innerHeights = inner.map((t) => rect(t).height || 0).sort((a, b) => a - b)
|
|
4074
|
+
const innerLineH = innerHeights[Math.floor(innerHeights.length / 2)] || 14
|
|
4075
|
+
const leadMedia = [...isolatedBlocks, ...slices]
|
|
4076
|
+
.filter((el) => thinInside(el, shape))
|
|
4077
|
+
.map((el) => rect(el))
|
|
4078
|
+
.filter((r) => (r.height || 0) >= Math.max(24, innerLineH * 1.8)
|
|
4079
|
+
&& (r.x || 0) + (r.width || 0) <= (sr.x || 0) + (sr.width || 0) * 0.5)
|
|
4080
|
+
.sort((a, b) => ((a.x || 0) + (a.width || 0)) - ((b.x || 0) + (b.width || 0)))[0] || null
|
|
4081
|
+
const splitX = leadMedia
|
|
4082
|
+
? Math.max((sr.x || 0) + 2, (leadMedia.x || 0) + (leadMedia.width || 0) + 2)
|
|
4083
|
+
: (sr.x || 0) + (sr.width || 0) * 0.45
|
|
4035
4084
|
const columns = []
|
|
4036
4085
|
for (const t of inner.filter((item) => !headerSet.has(item) && (rect(item).x || 0) >= splitX)
|
|
4037
4086
|
.slice().sort((a, b) => (rect(a).x || 0) - (rect(b).x || 0))) {
|
|
@@ -4047,7 +4096,9 @@ const orphanSlices = slices.filter((s) => {
|
|
|
4047
4096
|
if (!best) continue
|
|
4048
4097
|
const bestSet = new Set(best.items)
|
|
4049
4098
|
const left = inner.filter((t) => !headerSet.has(t) && !bestSet.has(t) && (rect(t).x || 0) < splitX)
|
|
4050
|
-
|
|
4099
|
+
// 左列成员可以是一张整块图而非文本(「产品图 + 右列信息」是常见形态):
|
|
4100
|
+
// 有前导媒体时左列无文本也成立,否则该形态永远进不了两列通道。
|
|
4101
|
+
if (left.length < 2 && !leadMedia) continue
|
|
4051
4102
|
shellRightColumns.push({
|
|
4052
4103
|
shapeEl: shape,
|
|
4053
4104
|
x: sr.x || 0, y: sr.y || 0, width: sr.width || 0, height: sr.height || 0,
|
|
@@ -179,8 +179,17 @@ export function fixFlexShrinkOverflow(root) {
|
|
|
179
179
|
const children = getChildren(node)
|
|
180
180
|
if (!isAutoSize && children.length >= 2) {
|
|
181
181
|
const allShrinkZero = children.every((child) => getStyleVal(child, 'flex', 'flexShrink') === '0')
|
|
182
|
-
|
|
183
|
-
|
|
182
|
+
// 「子节点全为固定 px 宽」= 这些宽度是设计稿逐个标注出来的(并排药丸/标签/统计块),
|
|
183
|
+
// 它们的和就是设计真值。此时**不得**为末位放开可缩点:药丸被压窄后内部文案换行、
|
|
184
|
+
// 行高撑高,连带 textOverflow 与 structure 一起掉(r284 实测:149+12+150 恰等于
|
|
185
|
+
// 组宽 311,却被压成两行,textOverflow 0.889 < 门 0.90)。
|
|
186
|
+
// 真正的「溢出兜底」场景是子节点里混有 auto/百分比宽(内容长度不可知),
|
|
187
|
+
// 那种才需要放开一个可缩点让内容有地方收缩。
|
|
188
|
+
const allFixedPx = children.every((child) => {
|
|
189
|
+
const cw = getStyleVal(child, 'rect', 'width')
|
|
190
|
+
return cw != null && /^\d+(\.\d+)?$/.test(String(cw))
|
|
191
|
+
})
|
|
192
|
+
if (allShrinkZero && !allFixedPx) {
|
|
184
193
|
setStyleVal(children[children.length - 1], 'flex', 'flexShrink', '1')
|
|
185
194
|
}
|
|
186
195
|
}
|
|
@@ -474,9 +483,14 @@ export function amountExpr(varName, srcText, opts = {}) {
|
|
|
474
483
|
const unit = JSON.stringify(unitMatch[2])
|
|
475
484
|
return `${sign} + ${abs} + ${unit}`
|
|
476
485
|
}
|
|
486
|
+
// 货币符号必须进表达式:spec 把「¥」与「5,638.00」拆成两层时,裸符号层会被合并段
|
|
487
|
+
// (mergeCurrencyLayer)吞掉,表达式是符号唯一的载体——不输出前缀 ⇒ 渲染只剩数字
|
|
488
|
+
// ("¥5,638.00" → "5,638.00")。前缀取自原文、运行时与负号拼接顺序固定(负号在前)。
|
|
489
|
+
// 纯数字原文("2000" 配相邻"万元")不写前缀,避免凭空加符号。
|
|
490
|
+
const cur = hasCurrency ? JSON.stringify(curPrefix) + ' + ' : ''
|
|
477
491
|
// 列表行模板共用一套表达式:货币前缀必须逐行数据驱动($amtCurN),否则首行符号套到全行
|
|
478
492
|
if (opts.prefixVar) return `${sign} + $${opts.prefixVar} + ${abs}`
|
|
479
|
-
return `${sign} + ${abs}`
|
|
493
|
+
return `${sign} + ${cur}${abs}`
|
|
480
494
|
}
|
|
481
495
|
|
|
482
496
|
export function amountValueExpr(varName, srcText, { currencyPrefix = true } = {}) {
|
|
@@ -63,6 +63,7 @@ if (argv.includes('--help') || argv.includes('-h')) {
|
|
|
63
63
|
|
|
64
64
|
const workRoot = path.join(REPO, '.cache', 'v3-regression')
|
|
65
65
|
const workCards = path.join(workRoot, 'src', 'cards')
|
|
66
|
+
const workSlices = path.join(workRoot, '__slices')
|
|
66
67
|
const baselineDir = path.join(REPO, 'training', 'pass1', 'baseline')
|
|
67
68
|
const node = process.execPath
|
|
68
69
|
// 同 gate.mjs:改为统一解析(此前 `process.env.PYTHON || 'python3'` 不认 DSL_CARD_PYTHON、不探测)
|
|
@@ -103,6 +104,10 @@ for (const c of cases) {
|
|
|
103
104
|
continue
|
|
104
105
|
}
|
|
105
106
|
// 1) 首轮生成(无 decisions)
|
|
107
|
+
// 切图落点必须一并密闭化:卡片产物已隔离到 .cache/v3-regression,但切图默认仍写 src/slices
|
|
108
|
+
// —— 金标 spec 来自 training/fixtures(含仓库未跟踪的切图名),跑一次回归就把它们混进
|
|
109
|
+
// 受跟踪资产目录(实测污染 src/slices 2 个未跟踪文件),并让 ci-gate 的源码污染守卫报错。
|
|
110
|
+
// 口径与 tools/regress-fidelity.mjs 一致。
|
|
106
111
|
const gen = run(node, [
|
|
107
112
|
path.join(SCRIPTS, 'spec_to_schema_v3.mjs'),
|
|
108
113
|
'--spec', specPath,
|
|
@@ -110,7 +115,9 @@ for (const c of cases) {
|
|
|
110
115
|
'--cardIndex', String(c.card),
|
|
111
116
|
'--out', workCards,
|
|
112
117
|
'--force',
|
|
113
|
-
]
|
|
118
|
+
], {
|
|
119
|
+
DSL_CARD_SLICES_DIR: workSlices,
|
|
120
|
+
})
|
|
114
121
|
if (!gen.ok) {
|
|
115
122
|
results.push({ regId: c.regId, pass: false, reason: 'generate 失败', detail: gen.output.slice(-600) })
|
|
116
123
|
continue
|
|
@@ -740,8 +740,52 @@ async function syncBlankCard(page, card, { cardDir, appId, workspaceId }) {
|
|
|
740
740
|
};
|
|
741
741
|
}
|
|
742
742
|
|
|
743
|
-
/**
|
|
743
|
+
/**
|
|
744
|
+
* 最小合法空白 schema:满足 validate_schema.py 的顶层必填(version/pageId/name/dictionary/view)。
|
|
745
|
+
* 根样式 margin / padding / gap 显式归零,压掉渲染器对无样式容器的默认注入
|
|
746
|
+
* (padding:2px / gap:4px / justify-content:center)——形状与存量卡根节点一致
|
|
747
|
+
* (style-meta.json:marginType / paddingType,'0' = 全边统一值)。
|
|
748
|
+
* 同一形状还有两份内联副本(禁跨包 import):server/core/cardFiles.js 的 blankResetStyle、
|
|
749
|
+
* skills/dsl-card-generator lib/scaffold.mjs —— 改这里必须同步改那两处。
|
|
750
|
+
*/
|
|
744
751
|
function buildBlankSchema(card) {
|
|
752
|
+
const blankResetStyle = () => ({
|
|
753
|
+
properties: [
|
|
754
|
+
{
|
|
755
|
+
itemId: '100',
|
|
756
|
+
itemKey: 'flex',
|
|
757
|
+
itemValueFrom: '0',
|
|
758
|
+
itemValue: {},
|
|
759
|
+
itemChildren: {
|
|
760
|
+
properties: [{ itemId: '003', itemKey: 'gap', itemValueFrom: '0', itemValue: '0' }],
|
|
761
|
+
},
|
|
762
|
+
},
|
|
763
|
+
{
|
|
764
|
+
itemId: '400',
|
|
765
|
+
itemKey: 'padding',
|
|
766
|
+
itemValueFrom: '0',
|
|
767
|
+
itemValue: {},
|
|
768
|
+
itemChildren: {
|
|
769
|
+
properties: [
|
|
770
|
+
{ itemId: '001', itemKey: 'paddingType', itemValueFrom: '0', itemValue: '0' },
|
|
771
|
+
{ itemId: '002', itemKey: 'padding', itemValueFrom: '0', itemValue: '0' },
|
|
772
|
+
],
|
|
773
|
+
},
|
|
774
|
+
},
|
|
775
|
+
{
|
|
776
|
+
itemId: '500',
|
|
777
|
+
itemKey: 'margin',
|
|
778
|
+
itemValueFrom: '0',
|
|
779
|
+
itemValue: {},
|
|
780
|
+
itemChildren: {
|
|
781
|
+
properties: [
|
|
782
|
+
{ itemId: '001', itemKey: 'marginType', itemValueFrom: '0', itemValue: '0' },
|
|
783
|
+
{ itemId: '002', itemKey: 'margin', itemValueFrom: '0', itemValue: '0' },
|
|
784
|
+
],
|
|
785
|
+
},
|
|
786
|
+
},
|
|
787
|
+
],
|
|
788
|
+
});
|
|
745
789
|
return {
|
|
746
790
|
version: '1.0.0',
|
|
747
791
|
pageId: String(card.cardId),
|
|
@@ -752,7 +796,7 @@ function buildBlankSchema(card) {
|
|
|
752
796
|
view: {
|
|
753
797
|
width: '375',
|
|
754
798
|
className: 'Root',
|
|
755
|
-
style:
|
|
799
|
+
style: blankResetStyle(),
|
|
756
800
|
properties: [],
|
|
757
801
|
eventactions: [],
|
|
758
802
|
components: [],
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: mbank-design-gen
|
|
3
|
+
description: MBank 手机银行 AI 助手风格的高保真效果图生成技能。以手绘草图/线框图为输入,按从两套银行 AI 助手设计稿(45 个画板)提炼的设计规范(卡片/尺寸/节奏/配色/层级/布局/风格化七类 token)产出风格统一的高保真 UI 效果图(HTML 精确渲染 + @2x PNG 截图,插画元素可选 ImageGen 补齐)。触发词:银行风格效果图, 高保真稿, 草图转效果图, 线框图上色, mbank 风格, 按设计规范出图, 设计稿风格生成, 效果图生成.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# MBank Design Gen — 草图 → 高保真效果图
|
|
7
|
+
|
|
8
|
+
以**手绘草图 / 线框图**为输入,套用本技能提炼的设计规范,产出与源设计稿风格一致的高保真手机 UI 效果图。
|
|
9
|
+
|
|
10
|
+
## 0. 铁律
|
|
11
|
+
|
|
12
|
+
1. **token 只认 `references/design-spec.md`**:所有色值/字号/圆角/间距/阴影必须来自该文件,禁止凭感觉造值;模板 `templates/mockup-template.html` 的 `:root` 是 CSS 侧唯一取值处。
|
|
13
|
+
2. **先读草图结构,再套组件**:用 Read 工具读草图图片 → 逐区块标注「这是什么组件」(对照 `references/component-library.md` A–J)→ 缺失形态才允许新建组件(新建时必须全部复用既有 token)。
|
|
14
|
+
3. **签名特征不可丢**:左冷蓝右暖奶油的氛围光背景、气泡非对称圆角(AI 左上直角 / 用户右上直角)、利率红 `#F3464A`、奶油促销卡、3D 金融插画溢出卡缘——这五项是风格一致性的底线,校验清单逐项查。
|
|
15
|
+
4. **布局精确优先于美感发挥**:草图画的区块顺序、层级、数量照搬,不做「顺手优化」;草图没画的固定框架(状态栏/导航/输入条/Home Indicator)按组件库 A 补齐。
|
|
16
|
+
5. **不硬编码业务文案**:效果图文案来自草图/用户输入;生成脚本类产物报错文案禁止写死业务实体名。
|
|
17
|
+
|
|
18
|
+
## 1. 文件地图
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
mbank-design-gen/
|
|
22
|
+
├── SKILL.md ← 本文件(工作流入口)
|
|
23
|
+
├── references/
|
|
24
|
+
│ ├── design-spec.md ← 权威 token 源(7 大规范 + 背景 CSS + 来源可信度)
|
|
25
|
+
│ └── component-library.md ← 组件拼装规范(A 全局框架 ~ J 徽标)
|
|
26
|
+
├── templates/
|
|
27
|
+
│ └── mockup-template.html ← 375×812 画布骨架,token 已内嵌 :root
|
|
28
|
+
├── tools/
|
|
29
|
+
│ └── screenshot.mjs ← HTML → @2x PNG(playwright,750×1624)
|
|
30
|
+
└── assets/
|
|
31
|
+
├── robot.png ← 吉祥物(直接引用,勿重绘)
|
|
32
|
+
├── icons/*.png ← 声音/设置/语音/箭头/对勾/九宫格/VS/标签底/进度 thumb
|
|
33
|
+
└── ref/*.png ← 4 张风格参考图(首页/对话/表格/长详情)
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## 2. 工作流
|
|
37
|
+
|
|
38
|
+
### Step 1 读草图,产出区块清单
|
|
39
|
+
Read 草图图片;对每个区块输出一行:`序号 | 区块类型(组件库编号,如 C 选项列表)| 内容摘要`。拿不准的区块对照 `assets/ref/` 四张参考图定形。
|
|
40
|
+
|
|
41
|
+
### Step 2 生成 HTML
|
|
42
|
+
复制 `templates/mockup-template.html` 到产物目录(如 `<工作目录>/mockup.html`),在 `.content` 的 SLOT 区按区块清单拼装组件;assets 引用改为相对/绝对路径指向本技能 `assets/`。规则:
|
|
43
|
+
- 只用模板 `:root` 的 token,不写裸 hex(模板外的例外仅限 design-spec 里给出的渐变组合)。
|
|
44
|
+
- 每个组件照 `component-library.md` 的结构/间距/字阶拼装。
|
|
45
|
+
- 文案用草图里的文字;草图字迹不清时按语境补全并在交付说明里列出。
|
|
46
|
+
|
|
47
|
+
### Step 3 截图导出
|
|
48
|
+
```bash
|
|
49
|
+
node <skill 路径>/tools/screenshot.mjs mockup.html mockup.png # 标准 375×812
|
|
50
|
+
node <skill 路径>/tools/screenshot.mjs mockup.html mockup.png --full # 长页整页
|
|
51
|
+
```
|
|
52
|
+
输出即 @2x 750 宽 PNG,与源设计稿同规格。
|
|
53
|
+
|
|
54
|
+
### Step 4 校验清单(交付前逐项打勾)
|
|
55
|
+
- [ ] 背景有左冷蓝 `#DBE3F5` / 右暖奶油 `#EEE6D9` 氛围光,向下渐隐至 `#F7F8FA+`
|
|
56
|
+
- [ ] AI 气泡 `#FEFBF7` 左上直角;用户气泡品牌渐变右上直角
|
|
57
|
+
- [ ] 圆角只用 4/8/12/20/22/pill 档位;卡片 radius 20
|
|
58
|
+
- [ ] 字阶不超出 24/20/18/17/16/15/14/13/12/10;金额数字用 DIN
|
|
59
|
+
- [ ] 利率/关键涨跌数字为红 `#F3464A`;链接/可点为 `#3B73FF`
|
|
60
|
+
- [ ] 间距全部落在 4 的倍数上
|
|
61
|
+
- [ ] 底部输入条 + Home Indicator 齐全,导航双 tab + 声音/设置齐全
|
|
62
|
+
- [ ] 3D 插画(如有)为暖金色系且溢出卡缘;吉祥物用 `assets/robot.png` 原图
|
|
63
|
+
- [ ] 截图 PNG 宽 750(@2x 生效)
|
|
64
|
+
|
|
65
|
+
### Step 5(可选)插画元素
|
|
66
|
+
草图含 3D 金融插画而手头无素材时,用 ImageGen 以 design-spec §7 关键词生成:`3D render, soft studio light, glossy financial objects, gold coins, warm orange palette, soft shadows, transparent background, C4D style`,落盘后 `<img>` 引入。**UI 面板永远不用 ImageGen 生成**(文字不可控),只用于装饰插画/人物照片。
|
|
67
|
+
|
|
68
|
+
## 3. 产物清单
|
|
69
|
+
|
|
70
|
+
| 文件 | 说明 |
|
|
71
|
+
|---|---|
|
|
72
|
+
| `mockup.html` | 高保真稿源(token 化,可继续手改) |
|
|
73
|
+
| `mockup.png` | @2x 效果图(750×1624 或长页等比) |
|
|
74
|
+
| (可选)`assets-*.png` | ImageGen 补生成的插画元素 |
|
|
75
|
+
|
|
76
|
+
交付时在回复中列出:区块清单 → 产物路径 → 校验清单结果;若草图有歧义取舍,逐条说明。
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# MBank 组件库规范(component-library)
|
|
2
|
+
|
|
3
|
+
> 从设计稿提炼的可复用部件。所有 token 定义见 `references/design-spec.md`;此处只写组件级拼装规则。
|
|
4
|
+
> 数值单位 pt(@1x)。每个组件给出:结构 → 样式 → 变体。
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## A. 全局框架(每屏必有)
|
|
9
|
+
|
|
10
|
+
### A1. 状态栏
|
|
11
|
+
- 左:时间 `9:41`,15pt Semibold `text-1`;右:信号/WiFi/电池(系统样式)。
|
|
12
|
+
|
|
13
|
+
### A2. 顶部导航
|
|
14
|
+
- 左:双 tab「此刻 / 对话」,17pt;激活 = Medium `text-1`,非激活 = Regular `text-2`,无下划线。
|
|
15
|
+
- 右:声音图标 + 设置图标(`assets/icons/`,24pt),间距 16pt。
|
|
16
|
+
- 背景:透明,融入页面氛围渐变,**不加底色不加分割线**。
|
|
17
|
+
|
|
18
|
+
### A3. 底部输入条(固定)
|
|
19
|
+
- 结构:`[麦克风图标 + 占位文字] 胶囊输入条` + 右侧 `[九宫格] 圆钮`。
|
|
20
|
+
- 输入条:高 36pt,radius-pill,白底,占位 15pt `text-3`(「欢迎向小易提问~」),麦克风 20pt 深灰,左 padding 16pt,**宽度撑满剩余空间**。
|
|
21
|
+
- 圆钮:44×44pt,白底 `shadow-float`,九宫格图标 20pt `text-1`。
|
|
22
|
+
- 下方 Home Indicator:34pt 高,黑色圆角横条 ~134pt 居中。
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## B. 对话气泡
|
|
27
|
+
|
|
28
|
+
### B1. 用户气泡(右)
|
|
29
|
+
- 背景渐变 `linear-gradient(90deg,#2F86F6,#005DFD)`,白字 15pt/24 Regular。
|
|
30
|
+
- radius:`12 0 12 12`(右上直角)。padding 12×16。max-width 75%。
|
|
31
|
+
- 与右边缘间距 12pt;多条相邻用户气泡间距 12pt。
|
|
32
|
+
|
|
33
|
+
### B2. AI 气泡(左)
|
|
34
|
+
- 背景 `#FEFBF7`(暖白),文字 15pt/24 `text-1`,radius `0 12 12 12`(左上直角)。
|
|
35
|
+
- padding 12×16;**无头像贴边**(头像仅首页问候区出现)。
|
|
36
|
+
- 变体 `ai-note`:气泡内底部追加「*温馨提示」13pt `text-2` + 说明文字 13pt/20 `text-2`,与正文间 8pt。
|
|
37
|
+
|
|
38
|
+
### B3. AI 气泡内富文本
|
|
39
|
+
- 关键词高亮:`color-primary-bright` 加粗。
|
|
40
|
+
- 序号列表:`1. 2. 3.` 起头行,悬挂对齐;书名号/【】内容加粗 `text-1`。
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## C. 选项列表卡(AI 给出可点选项)
|
|
45
|
+
|
|
46
|
+
- 容器:`rgba(255,255,255,0.7)`,radius 20pt,padding 12 16 16。
|
|
47
|
+
- 容器题:15pt `text-2`(「请选择你的借钱用途:」),下距 12pt。
|
|
48
|
+
- 条目:白底 radius 12pt,padding 14 16;题 16pt Medium `text-1` + 右侧 chevron(`icon-arrow-right-blue` 或灰 chevron 16pt);副文 14pt `text-2` 上距 4pt。条目间距 12pt。
|
|
49
|
+
- 变体 `selected`:条目题文字 `color-primary`,边框 1pt `color-primary`(用户刚点过的项)。
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## D. 建议气泡(「小易猜你想问」)
|
|
54
|
+
|
|
55
|
+
- 区块题:15pt `text-2`(「小易猜你想问」),下距 12pt,左对齐 16pt 边距。
|
|
56
|
+
- pill:白底、1pt `color-primary` 边框、蓝字 14pt,radius-pill,高 32pt,padding 0 16pt;纵向间距 12pt,左对齐流式换行。
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## E. 数据表格卡(利率表范式)
|
|
61
|
+
|
|
62
|
+
- 白底 radius 20pt padding 16;宽度 343pt。
|
|
63
|
+
- 标题:16pt Medium `text-1`,文字带 `#EDF5FF` 高亮衬底(padding 2 4)。
|
|
64
|
+
- 行 1(参数选择):左 15pt `text-1`,右「当前值 ˅」15pt `text-1` + chevron,行高 44pt,下加分隔线。
|
|
65
|
+
- 行 2(tabs):横排 tab,激活 = 16pt Medium `text-1` + 底部 2pt `text-1` 短下划线(宽≈字宽);非激活 15pt Regular `text-2`;间距 16pt。
|
|
66
|
+
- 数据行:高 44pt,分隔线 `#E9E9E9`;左标签 15pt `text-1`,右值 15pt Semibold `color-red`(利率类)或 `text-1`(普通数值)。
|
|
67
|
+
- 脚注:12pt/18 `text-3`(「*以上利率为我行标准利率…」),上距 8pt。
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## F. 产品资产卡(我的贷款/持仓范式)
|
|
72
|
+
|
|
73
|
+
- 白底 radius 20pt,padding 16。
|
|
74
|
+
- 头部:产品名 18pt Semibold `text-1`;下行编号 14pt `text-2`(「贷款编号:xxx」);头部区可带 `#F5F6F8` 灰底(padding 16,radius 12)。
|
|
75
|
+
- 汇总面板:奶油渐变 `linear-gradient(90deg,#FEF3E9,#FEFBF7)` radius 12pt padding 16;内部两列——标签 14pt `text-2` 上、金额 20pt DIN Bold `text-1-warm` 下(金额可 24pt)。
|
|
76
|
+
- 提醒气泡标签:`linear-gradient(90deg,#2F86F6,#005DFD)` 白字 13pt,radius 8 8 8 0,padding 6 10,悬浮于进度条起点上方。
|
|
77
|
+
- 进度条:轨道 4pt `#E9ECF2` radius-pill;已还段 `color-primary`;thumb = 白圆 16pt 带 `shadow-float`(素材 `progress-thumb.png`)。
|
|
78
|
+
- 刻度行:两端对齐——左「1期」/右「36期」13pt `text-1`。
|
|
79
|
+
- 日期行:两端「起始时间:…」/「结束时间:…」13pt `text-2`。
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## G. 对比卡(VS 范式)
|
|
84
|
+
|
|
85
|
+
- 白底 radius 20pt padding 16。
|
|
86
|
+
- 标题 18pt Semibold `text-1` 居左,带 `#EDF5FF` 衬底。
|
|
87
|
+
- 对比头部:左右两个圆角色块(左红 `#F3464A`、右蓝 `linear-gradient(90deg,#2F86F6,#3B73FF)`),radius 16pt,内白字居中:产品名 18pt Semibold + 副文 12pt 白 70% 透明度;中缝叠圆形 VS 徽章(白底 40pt 圆 + `vs-badge.png` 或描边字)。
|
|
88
|
+
- 对比行:三列——左值(红 `color-red` 15pt Semibold 居右)|中标签(`#F5F5F5` 底 radius 4pt padding 4 10,13pt `text-2`)|右值(蓝 `color-primary-bright` 15pt Semibold 居左);行距 16pt。
|
|
89
|
+
- 总结气泡:AI 气泡样式(`#FEFBF7` radius 12pt padding 12 16,15pt/24 `text-1`),置于卡外下方。
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## H. 要点列表(产品亮点范式)
|
|
94
|
+
|
|
95
|
+
- 区块题 18pt Semibold `text-1`。
|
|
96
|
+
- 条目:`color-primary` 实心圆 18pt 内白字序号(①②③,11pt)+ 条目题 16pt Medium `text-1` 同行;正文 15pt/24 `text-2` 悬挂对齐(缩进 = 圆点宽 + 8pt)。
|
|
97
|
+
- 免责脚注:12pt/18 `text-3`,前缀「*」。
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## I. 首页「此刻场景」卡片组
|
|
102
|
+
|
|
103
|
+
### I1. 问候区
|
|
104
|
+
- 左:吉祥物 `robot.png` 64pt 圆形(素材自带圆底可直接用);右:问候语 24pt Semibold `text-1-warm`(「早安,追梦人!」)+ 副文 15pt/24 `text-2` 两行,左间距 12pt。
|
|
105
|
+
|
|
106
|
+
### I2. 双列促销卡(2 列网格,gap 12pt,等宽 ~166pt)
|
|
107
|
+
- 基底:radius 20pt + `shadow-float`;两种底:
|
|
108
|
+
- `cream`:`linear-gradient(160deg,#FFF7E8,#FEF6ED)`
|
|
109
|
+
- `plain`:白底
|
|
110
|
+
- 结构:3D 插画(顶部,允许溢出卡缘 8–16pt)→ 标题 18pt Semibold `text-1-warm` → 副文 14pt/22 `text-2` → 胶囊按钮 32pt。
|
|
111
|
+
- 按钮:营销卡 = 橙红渐变 `linear-gradient(90deg,#F09404,#EE6723)` 白字 14pt Medium;功能卡 = `color-primary` 白字。
|
|
112
|
+
- 变体 `consultant`(顾问卡):顶部真人照片(radius 12pt,宽满卡)+ 直播徽标(红底白字 10pt pill)+ 题目 16pt Medium(关键词 `color-primary-bright`)+ 副文 14pt `text-2` + 底部「主讲人」描边 pill 12pt `text-2` + 右下黑色圆钮 28pt(白 chevron)。
|
|
113
|
+
|
|
114
|
+
### I3. 筛选条件列表(绿勾)
|
|
115
|
+
- 区块题 16pt Medium `text-1` 带下划线装饰 + 右侧「进一步筛选 >」14pt `color-primary-bright`。
|
|
116
|
+
- 条目:`icon-check.png` 18pt + 15pt `text-1`,行距 12pt。
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## J. 徽标与角标
|
|
121
|
+
|
|
122
|
+
| 徽标 | 样式 |
|
|
123
|
+
|---|---|
|
|
124
|
+
| 直播 live | `#F3464A` 底白字 10pt Medium pill,padding 2 8,左白点 6pt |
|
|
125
|
+
| 利率高亮 | `#EDF5FF` 底 `text-1` 16pt,padding 2 4 |
|
|
126
|
+
| 气泡提醒 | 品牌渐变底白字 13pt,radius `8 8 8 0` |
|
|
127
|
+
| 标签底 | `tag-bg.png` 奶油异形底 + 14pt `text-1-warm` |
|