From 658b232a39afe008471e8bc1e50651567d2bad76 Mon Sep 17 00:00:00 2001 From: root Date: Fri, 9 Oct 2026 23:22:28 +0800 Subject: [PATCH] save --- h5/README.md | 39 +- h5/css/style.css | 709 ++++++++---------- h5/index.html | 202 ++--- h5/js/app.js | 444 ++++++----- h5/js/head-pose.js | 35 +- h5/js/region-data.js | 39 + h5/port-forward-host.bat | 26 + h5/start-local.sh | 11 + h5/test/e2e-browser.mjs | 23 +- h5/vendor/mobile-select/LICENSE | 21 + h5/vendor/mobile-select/README.md | 4 + h5/vendor/mobile-select/mobile-select.css | 164 ++++ .../mobile-select/mobile-select.iife.min.js | 7 + 13 files changed, 1019 insertions(+), 705 deletions(-) create mode 100644 h5/js/region-data.js create mode 100644 h5/port-forward-host.bat create mode 100644 h5/start-local.sh create mode 100644 h5/vendor/mobile-select/LICENSE create mode 100644 h5/vendor/mobile-select/README.md create mode 100644 h5/vendor/mobile-select/mobile-select.css create mode 100644 h5/vendor/mobile-select/mobile-select.iife.min.js diff --git a/h5/README.md b/h5/README.md index c390ac7..727a6f0 100644 --- a/h5/README.md +++ b/h5/README.md @@ -54,25 +54,30 @@ node server.js # 默认 8080,可用 PORT=xxx 修改 调试: -- `http://127.0.0.1:8080/?mock=1` 使用内置样本(`SkinScore.MOCK_RESULT`/`MOCK_SIDE`)跳过真实检测;不加载 face-api、不跑姿态检测,快门手动逐步推进(离线/自动化验证用)。 +- `http://127.0.0.1:8080/?mock=1` 使用内置样本(`SkinScore.MOCK_RESULT`/`MOCK_SIDE`)跳过真实检测;不加载 face-api、不跑姿态检测,显示快门手动逐步推进(离线/自动化验证用)。 - `http://127.0.0.1:8080/?mock=1&screen=report` 直渲 mock 报告页(**仅调试用**,供无头浏览器截图验证布局;档案缺省按 28 岁/干性肌肤);mock 数据源为 `assets/mock/report-fixture.js`(真实案例1三视图检测返回,含 face_maps 图层 data URL 与 marks 坐标),改该文件即可换样本。 -## 拍照交互(引导式自动三连拍) +## 拍照交互(动态捕捉自动三连拍) -三步(顺序对齐 Figma,左右一律以用户本人为准):1/3 正脸(target `front`)→ 2/3 右脸出镜(用户头向自己的左侧转,target `right`,产物作 `right_image`)→ 3/3 左脸出镜(头向自己的右侧转,target `left`,产物作 `left_image`)。送检前 `photosByView` 按 target 重排为 [正, 左, 右]。 +三步严格按顺序,本步拍到合格照片才进入下一步(左右一律以用户本人为准):1/3 正对屏幕(target `front`)→ 2/3 右脸出镜(用户头向自己的左侧转,target `right`,产物作 `right_image`)→ 3/3 左脸出镜(头向自己的右侧转,target `left`,产物作 `left_image`)。送检前 `photosByView` 按 target 重排为 [正, 左, 右]。页面内没有返回按钮,微信/浏览器返回键经 History API 回到档案页并释放摄像头。 -- **步骤圆点**:顶部三点,当前步高亮、完成步绿色打勾。 -- **取景蒙版**:内联 SVG(`#cam-mask`)按步骤切换轮廓——正脸步为正脸剪影(含耳部),侧脸步为对应方向的侧脸剪影(鼻朝向与转头方向一致);**轮廓路径由 Figma 拍照示意图(`design/assets/images/*-拍照示意.png` 的 alpha 镂空区)逐像素描摹生成**,坐标为 Figma 375×812 整帧,viewBox 只取 y 86–812(帧顶 86px 是微信导航栏,H5 视口从其下方开始;步骤文案/引导等纵向百分比也以 726 高为基准);框外 85% 黑(SVG mask 镂空,对齐 Figma 图层不透明度),轮廓线 2.5px 半透明白。未检测到人脸时轮廓呼吸动画(1.8s);姿态 ok 时轮廓变绿并停止动效。 -- **正脸步提示口径**:正脸步角度不合格一律只提示「请正对屏幕」(不再提示左右转头/抬头低头),阈值 |yaw|≤15°、|pitch|≤18°(2026-09-28 产品要求放宽);侧脸步仍按方向提示「请向左/右转头」。 -- **实时检测**:face-api 从本地 `vendor/faceapi/` 加载(`tinyFaceDetector` + `faceLandmark68Net`)。进入拍照页即开始加载模型(状态提示"检测引擎加载中…"),加载完成前快门为手动模式。检测循环 ~150ms 一帧,`detectSingleFace(video, TinyFaceDetectorOptions({inputSize:416, scoreThreshold:0.3})).withFaceLandmarks()` 后拿 68 特征点调 `HeadPose.check(pos, target, mirrored)`——特征点取自原始帧(预览 CSS 镜像不影响),侧脸步 mirrored 恒 false(个别前置帧已镜像的机型由侧脸自校准翻转 `sideYawFlip`,只动判定不动画面),正脸步只看 |yaw|;连续漏检 ≥3 帧才报"未检测到人脸"(容忍单帧抖动);人脸框高度占比 <26% 提示"请靠近一点"、>58% 提示"请远一点"(以取景框≈42% 屏高标定)。提示一次一条,优先级:无人脸 > 距离 > 角度。 -- **倒计时自动拍**:姿态 ok 连续维持 ≥600ms 后,先等「保持不动」等在播提示音自然收尾(画面保持绿框+保持不动),再进 3s 倒计时(中央大数字,轮廓保持绿色,数字与「三、二、一」语音同步起播,不再拦腰切断);倒计时中任一帧不 ok 立即取消并回到对应提示。倒计时结束进入白屏补光。 -- **镜像策略**:前置镜头三步**预览**都镜像(照镜子习惯;Figma 侧脸轮廓即按镜像画面绘制:右脸出镜耳在画面右、左脸出镜耳在画面左);后置镜头预览不镜像,侧脸轮廓随之水平翻转(`.cam-mask.flip`)。**抓帧一律不镜像**:三张照片都是摄像头原始视角,Face++ 左右分区文案(左嘴角纹/左右脸颊)对应用户真实左右,与真值样本同口径。 -- **白屏补光**:全屏纯白覆盖层瞬间点亮,~300ms 后抓全分辨率帧(原始视角 + 最长边 1280 JPEG),保持 ~300ms 再淡出(总 600–900ms),同步 `navigator.vibrate(30)`。 -- **手动兜底**:快门任何时刻可拍当前步(同样走白屏补光),不依赖检测;face-api/模型加载失败 → toast"自动识别不可用,已切换手动拍摄",全程手动。系统相机降级模式(getUserMedia 不可用/被拒)维持原样:无检测、无补光。 -- **相册选图**:相机页「相册」按钮可选当前步的图片(任何模式下可用),与实拍同管线(压 1280 → 推进流程)。 -- **语音播报**:步骤引导/姿态提示/倒计时/拍摄完成/分析中/失败共 14 条,文案与文件名契约见 `../docs/语音播报内容设计文档_V1.0.md`。WebAudio 单通道、姿态提示同条 4s 去重、解锁后预加载全部条目;流程音(步骤/拍摄完成/倒计时/分析中)排队顺序播放互不截断,姿态提示不截断流程音(期间只挂起最新一条,收尾后补播),失败提示可打断「分析中」。`assets/voice/manifest.json` 清单制(14 条已生成),清单缺失的条目静默跳过。 -- **无进展提示**:单步 12s 未触发倒计时,底部浮出"识别不到?可点下方快门手动拍摄"(不阻塞流程)。 -- **资源管理**:离开拍照页(cam-back/完成)停止检测循环、清全部定时器、释放摄像头;`camSeq`(流程)/`camSeqCam`(相机)双令牌防竞态;face-api 模型只加载一次(缓存 promise,失败后下次进入重试)。 +- **取景蒙版**:内联 SVG(`#cam-mask`)按步骤切换轮廓——正脸步为正脸剪影(含耳部),侧脸步为对应方向的侧脸剪影;**轮廓路径由 Figma 拍照示意图(`design/assets/images/*-拍照示意.png` 的 alpha 镂空区)逐像素描摹生成**,坐标为 Figma 375×812 整帧,viewBox 只取 y 86–812;框外 85% 黑,轮廓线 2.5px 半透明白。检测运行时轮廓内有自上而下的扫描带(`#cam-clip` 由 app.js 复制轮廓生成);未检测到人脸时轮廓呼吸动画;条件合格时轮廓变绿。 +- **预览缩小 15%**:`#cam-video` 按 `scale(.85)` 显示,手机离脸近一些也能把脸装进轮廓;距离阈值随之按 1/0.85 换算(人脸框高/视频高 <0.30 提示「请靠近一点」、>0.68 提示「请远一点」)。 +- **动态提示**(一次一条,优先级:无人脸 > 距离 > 居中 > 角度 > 光线):「未检测到人脸」「请靠近一点 / 请远一点」「请将脸部移入取景框」(人脸中心偏离中线 >18% 屏宽)、角度类「请向左转头 / 请向右转头 / 请略微抬头 / 请略微低头」(正脸阈值 |yaw|≤15°、|pitch|≤18°,超出时按超出比例更大的一项给方向;侧脸 yaw 28°–75°、|pitch|≤20°)、光线类「请到光线充足处 / 光线过强,请避开强光」(脸部平均亮度 <65 或 >235)。 +- **合格即拍**:上述条件全部合格并连续维持 450ms(滤掉一闪而过的合格帧,防运动模糊)立即白屏补光抓拍,**无倒计时、无手动快门**。 +- **实时检测**:face-api 从本地 `vendor/faceapi/` 加载(`tinyFaceDetector` + `faceLandmark68Net`),进入拍照页开始加载(状态提示「检测引擎加载中…」)。检测循环 ~150ms 一帧,特征点取自原始帧(预览 CSS 镜像不影响),个别前置帧已镜像的机型由侧脸自校准翻转 `sideYawFlip`;连续漏检 ≥3 帧才报「未检测到人脸」。 +- **镜像策略**:前置镜头预览镜像(照镜子习惯);后置预览不镜像,侧脸轮廓随之翻转(`.cam-mask.flip`)。**抓帧一律不镜像**,三张照片都是摄像头原始视角。 +- **白屏补光**:全屏纯白覆盖层瞬间点亮,~300ms 后抓全分辨率帧(原始视角 + 最长边 1280 JPEG),保持 ~300ms 再淡出,同步 `navigator.vibrate(30)`。 +- **快门只在无法实时识别时出现**:系统相机降级(getUserMedia 不可用/被拒,如手机走局域网 HTTP)、face-api/模型加载失败、`?mock=1` 调试。降级与相册选图拿到的照片会先用 face-api 校验(有人脸、正脸/侧脸转头幅度对;系统相机是否镜像因机型而异,侧脸不校验左右),不合格提示重拍、停在本步;模型不可用时放行,由服务端兜底。 +- **相册选图**:相机页「相册」按钮可选当前步的图片,与降级拍照同管线(压 1280 → 校验 → 推进)。 +- **语音播报**:步骤引导/姿态提示/拍摄完成/分析中/失败,文案与文件名契约见 `../docs/语音播报内容设计文档_V1.0.md`。新增的提示(居中、抬头/低头、光线)暂无音频,清单缺失的条目静默跳过,只显示文字。 +- **无进展提示**:单步 12s 仍未拍到,底部浮出「识别不到?请正对镜头,并到光线充足处」(不阻塞流程)。 +- **资源管理**:离开拍照页停止检测循环、清全部定时器、释放摄像头;`camSeq`(流程)/`camSeqCam`(相机)双令牌防竞态;face-api 模型只加载一次。 + +## 档案页与报告页 + +- **档案页**:出生年月 / 性别 / 所在地区三个滚轮选择器同一风格(`vendor/mobile-select`,MIT),出生年月自动换算年龄;地区为省–市两级联动,数据为 `js/region-data.js`(由 china-division 2.7.0 `pc.json` 生成,另含港澳台与「海外」)。 +- **报告页**:无返回按钮与页头标题;总分环形图 + 肌肤年龄与「比实际年龄年轻/大 N 岁」;三维度分数 + 进度条(最低分标红并挂「优先关注」);紧塑力/活力感/稳定力各一张卡,卡内子 Tab 切换;点检测图全屏放大,再点或按返回键回到报告。 ## 检测流程与评分渲染 @@ -154,7 +159,7 @@ schtasks /create /tn "JuziSkinH5" /tr "\"C:\Windows\System32\cmd.exe\" /c \"D:\j :: 移除: schtasks /delete /tn "JuziSkinH5" /f ``` -局域网提测访问 `http://<本机局域网IP>:8088/`。注意:① 首次监听若弹 Windows 防火墙提示需允许 node.exe;② **手机走局域网 HTTP 时 getUserMedia 不可用**,会自动降级为系统相机手动三连拍(分析链路完整),要测引导式自动拍照需在本机浏览器访问 `http://127.0.0.1:8088`,或上 HTTPS。 +局域网提测访问 `http://<本机局域网IP>:8088/`。注意:① 首次监听若弹 Windows 防火墙提示需允许 node.exe;② **手机走局域网 HTTP 时 getUserMedia 不可用**,会自动降级为系统相机三连拍(照片仍按步校验,分析链路完整),要测动态捕捉自动拍照需在本机浏览器访问 `http://127.0.0.1:8088`,或上 HTTPS。 nginx 反代示例(静态与 API 都交给 server.js,nginx 只做 HTTPS 终结): @@ -180,7 +185,7 @@ server { - 语音 14 条已由 `tools/gen_voice.py` 生成;改文案后重跑脚本并同步 `manifest.json`。 - 「了解更多」为开发中占位(Toast 提示);拍照页右下按钮为切换前后镜头(系统相机降级模式下提示在系统相机内切换)。 - 产品推荐卡(COREⅡ 双仓精华液)为设计稿固定素材,不随精华推荐档位(25+/35+/50+)变化,待产品提供三档对应素材。 -- **姿态与距离参数已用模拟摄像头校准**(2026-09-27):以 C3 三视图(+45° 侧脸)生成 Y4M 模拟摄像头视频,闭环修正了 yaw 增益(2.2→3.8)、pitch 基线(0.32→0.45)、check() 双重镜像符号 bug、距离阈值(0.35/0.8→0.26/0.58)、检测参数(320/0.4→416/0.3)与漏检 3 帧容忍;自动三连拍在无头浏览器全程通过(e2e E7)。**真机仍建议复核一次**:不同机型前置镜像策略/取景比例可能微调体验(校准接口:调用方 `mirrored` 标志与 `HeadPose.config`)。 +- **姿态与距离参数已用模拟摄像头校准**(2026-09-27):以 C3 三视图(+45° 侧脸)生成 Y4M 模拟摄像头视频,闭环修正了 yaw 增益(2.2→3.8)、pitch 基线(0.32→0.45)、check() 双重镜像符号 bug、距离阈值(0.35/0.8→0.26/0.58;2026-10 预览缩小 15% 后按比例换算为 0.30/0.68)、检测参数(320/0.4→416/0.3)与漏检 3 帧容忍;自动三连拍在无头浏览器全程通过(e2e E7)。**真机仍建议复核一次**(尤其 2026-10 新增的居中/亮度阈值):不同机型前置镜像策略/取景比例可能微调体验(校准接口:调用方 `mirrored` 标志与 `HeadPose.config`)。 ## 技术说明 diff --git a/h5/css/style.css b/h5/css/style.css index 9ecd019..cfe6977 100644 --- a/h5/css/style.css +++ b/h5/css/style.css @@ -1,6 +1,6 @@ /* ═══════════════════════════════════════════ 巨子测肤 · 1'me 在线肌肤测试 — H5 样式 - 设计稿 1080px 基准 → 此处 375px(÷2.88) + 设计稿 1080px 基准 → 375px(÷2.88);尺寸写成 N × --u,不同宽度手机等比缩放、视觉一致 主色 #F5F078 · 文字 #282828 · 底 #EFEFEF ═══════════════════════════════════════════ */ :root { @@ -13,10 +13,9 @@ --para: #4A4A4A; --bg: #EFEFEF; --line: #E5E5E5; - --dry: #EAE6E1; - --neutral: #DBF6D6; --bad: #D30C0C; --nav-h: 44px; + --u: calc(min(100vw, 480px) / 375); /* 1 设计像素(375 宽基准) */ --font: "Noto Sans SC","PingFang SC","Hiragino Sans GB","Microsoft YaHei",system-ui,sans-serif; } @@ -34,7 +33,7 @@ body { position: relative; } img { display: block; } -button { font-family: inherit; border: 0; background: none; cursor: pointer; } +button { font-family: inherit; border: 0; background: none; cursor: pointer; color: inherit; } /* ─── 屏幕容器 ─── */ .screen { @@ -47,15 +46,15 @@ button { font-family: inherit; border: 0; background: none; cursor: pointer; } .screen.active { display: block; animation: screenIn .35s ease both; } @keyframes screenIn { from { opacity: 0; transform: translateX(14px); } to { opacity: 1; transform: none; } } -/* ─── 通用按钮 ─── */ +/* ─── 主按钮:长方形 + 小倒角(设计稿 181×37) ─── */ .btn-primary { display: block; - width: 182px; height: 39px; - line-height: 39px; + width: calc(181 * var(--u)); height: calc(37 * var(--u)); + line-height: calc(37 * var(--u)); background: var(--yellow); - border-radius: 20px; + border-radius: calc(5 * var(--u)); color: var(--ink); - font-size: 14px; font-weight: 500; + font-size: calc(14.5 * var(--u)); font-weight: 500; letter-spacing: calc(.5 * var(--u)); text-align: center; transition: transform .12s ease; } @@ -68,8 +67,16 @@ button { font-family: inherit; border: 0; background: none; cursor: pointer; } display: inline-block; padding: 2px; } .chev.left { transform: rotate(135deg); } +.chev.right { transform: rotate(-45deg); } .chev.down { transform: rotate(45deg); } +.nav-back { + width: 27px; height: 27px; + display: flex; align-items: center; justify-content: center; + border: 1px solid var(--line); border-radius: 50%; + background: #fff; +} + /* ═══════════ 01 启动页 ═══════════ */ #s-splash { background: #101823; } .splash-bg { @@ -77,194 +84,175 @@ button { font-family: inherit; border: 0; background: none; cursor: pointer; } background: url(../assets/img/bg-splash.jpg) center/cover no-repeat; } .splash-slogan { - position: absolute; left: 0; right: 0; top: 20%; + position: absolute; left: 0; right: 0; top: 10.7%; text-align: center; - color: #fff; font-size: 23px; font-weight: 500; - line-height: 1.38; - letter-spacing: 1px; - text-shadow: 0 1px 8px rgba(0,0,0,.18); + color: #fff; font-size: calc(23 * var(--u)); font-weight: 500; + line-height: 1.38; letter-spacing: calc(.6 * var(--u)); + text-shadow: 0 1px 8px rgba(0,0,0,.15); } .splash-bottom { - position: absolute; left: 0; right: 0; bottom: 11%; - display: flex; flex-direction: column; align-items: center; gap: 18px; + position: absolute; left: 0; right: 0; bottom: 12.4%; + display: flex; flex-direction: column; align-items: center; gap: calc(16 * var(--u)); padding-bottom: env(safe-area-inset-bottom); } -.brand-row { display: flex; align-items: center; gap: 7px; } -.brand-logo { width: 46px; height: auto; filter: brightness(0) invert(1); } -.brand-text { color: #fff; font-size: 21px; font-weight: 500; letter-spacing: 1px; } - -/* ═══════════ 通用导航 ═══════════ */ -.nav { - position: sticky; top: 0; z-index: 20; - height: var(--nav-h); - display: flex; align-items: center; - padding: 0 6px; - background: rgba(255,255,255,.94); - -webkit-backdrop-filter: blur(8px); /* iOS<15.4 需要前缀 */ - backdrop-filter: blur(8px); -} -.nav-dark { background: rgba(20,20,20,.55); } -.nav-dark .nav-title { color: #fff; } -.nav-dark .chev { border-color: #fff; } -.nav-back { - width: 27px; height: 27px; - display: flex; align-items: center; justify-content: center; - border: 1px solid #E5E5E5; border-radius: 50%; - background: #fff; -} -/* 深色导航(拍照页):半透明白底盘 + 浅白描边 */ -.nav-dark .nav-back { background: rgba(255,255,255,.18); border-color: rgba(255,255,255,.4); } -.nav-title { - position: absolute; left: 50%; transform: translateX(-50%); - font-size: 14px; font-weight: 500; color: #000; - max-width: 55%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; -} +.brand-row { display: flex; align-items: center; gap: calc(7 * var(--u)); } +.brand-logo { width: calc(46 * var(--u)); height: auto; filter: brightness(0) invert(1); } +.brand-text { color: #fff; font-size: calc(21.5 * var(--u)); font-weight: 500; letter-spacing: calc(1 * var(--u)); } /* ═══════════ 03 档案填写 ═══════════ */ #s-profile { background: #0e0f18; } .profile-bg { position: absolute; inset: 0; - /* 设计稿:启动页照片 + 50% 正片叠底黑遮罩(矩形3) */ + /* 启动页照片 + 55% 黑遮罩 */ background: linear-gradient(rgba(0,0,0,.55), rgba(0,0,0,.55)), url(../assets/img/bg-splash.jpg) center/cover no-repeat; } -#s-profile .nav { position: fixed; left: 0; right: 0; } .profile-body { position: relative; z-index: 1; min-height: 100%; - /* 设计:标题在导航下 23px;按钮随内容流(卡底→按钮间距按设计节奏约 22px) */ - padding: calc(var(--nav-h) + 23px) 64px clamp(48px, 8vh, 121px); - display: flex; flex-direction: column; justify-content: flex-start; - gap: 16px; + padding: calc(34 * var(--u)) calc(64 * var(--u)) calc(40 * var(--u)); } .page-title { - color: #fff; font-size: 21px; font-weight: 700; - letter-spacing: 1px; margin-bottom: 2px; + color: #fff; font-size: calc(21.5 * var(--u)); font-weight: 700; + letter-spacing: calc(1 * var(--u)); text-align: center; + margin-bottom: calc(22 * var(--u)); } +.field { margin-bottom: calc(19 * var(--u)); } .field-label { - display: block; - color: #fff; font-size: 14px; font-weight: 400; - margin-bottom: 6px; letter-spacing: .5px; + display: block; text-align: center; + color: #fff; font-size: calc(14.5 * var(--u)); font-weight: 400; + margin-bottom: calc(9 * var(--u)); letter-spacing: calc(.5 * var(--u)); } -.form-row.two { display: flex; gap: 22px; } -.form-row.two .field { flex: 1; } -.field-input { - display: block; width: 100%; height: 38px; - background: #fff; border-radius: 6px; border: 0; outline: none; - padding: 0 12px; - font-family: inherit; font-size: 13px; color: var(--ink); - appearance: none; -webkit-appearance: none; -} -.field-input::placeholder { color: #bbb; } -.field-select-wrap { position: relative; } -.field-select-wrap .chev { position: absolute; right: 12px; top: 15px; width: 7px; height: 7px; pointer-events: none; } +.form-row.two { display: flex; gap: calc(23 * var(--u)); } +.form-row.two .field { flex: 1; min-width: 0; } -/* 肤质卡片 */ +/* 选择控件(出生年月/性别/地区同一样式) */ +.pick { + display: flex; align-items: center; justify-content: space-between; gap: 6px; + width: 100%; height: calc(37 * var(--u)); + padding: 0 calc(13 * var(--u)) 0 calc(12 * var(--u)); + background: #fff; border-radius: calc(5 * var(--u)); + font-size: calc(13.5 * var(--u)); color: var(--ink); text-align: left; +} +.pick-value { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +.pick .chev { flex: none; width: calc(8 * var(--u)); height: calc(8 * var(--u)); margin-top: calc(-4 * var(--u)); border-width: 0 1.5px 1.5px 0; } +.pick:active { background: #F7F7F7; } + +/* 肤质卡片(设计稿 246×278) */ .skin-card { - background: #fff; border-radius: 6px; - padding: 24px 10px 26px; + background: #fff; border-radius: calc(5 * var(--u)); + padding: calc(21 * var(--u)) 0 calc(24 * var(--u)); } .skin-axis { - display: flex; justify-content: center; gap: 8px; - margin: 0 0 14px; + display: flex; justify-content: center; gap: calc(12 * var(--u)); + margin-bottom: calc(18 * var(--u)); } .axis-item { - display: inline-flex; align-items: center; gap: 4px; - font-size: 9px; color: var(--ink); + display: inline-flex; align-items: center; gap: calc(5 * var(--u)); + font-size: calc(9 * var(--u)); color: var(--ink); } -.chip { width: 8px; height: 8px; border-radius: 2px; display: inline-block; } +.chip { width: calc(8 * var(--u)); height: calc(8 * var(--u)); border-radius: 50%; display: inline-block; } +.skin-stage { position: relative; padding: 0 calc(28 * var(--u)); } +.skin-arrow { + position: absolute; top: calc(42 * var(--u)); z-index: 1; + width: calc(26 * var(--u)); height: calc(26 * var(--u)); + display: flex; align-items: center; justify-content: center; +} +.skin-arrow.prev { left: calc(5 * var(--u)); } +.skin-arrow.next { right: calc(5 * var(--u)); } +.skin-arrow .chev { width: calc(9 * var(--u)); height: calc(9 * var(--u)); border-width: 0 1.5px 1.5px 0; } +.skin-arrow[disabled] { opacity: .2; } .skin-carousel { overflow: hidden; touch-action: pan-y; /* 纵向滚动交给浏览器,横向留给拖拽 */ } .skin-track { display: flex; transition: transform .32s cubic-bezier(.22,.61,.36,1); } .skin-item { flex: 0 0 100%; display: flex; flex-direction: column; align-items: center; } -.skin-item img { width: 109px; height: 109px; } -.skin-name { font-size: 13px; font-weight: 500; color: #000; margin-top: 12px; } +.skin-item img { width: calc(109 * var(--u)); height: calc(109 * var(--u)); } +.skin-name { font-size: calc(12.5 * var(--u)); font-weight: 700; color: #000; margin-top: calc(16 * var(--u)); } .skin-desc { - font-size: 10px; color: var(--ink); font-weight: 400; - line-height: 1.5; text-align: center; - margin-top: 5px; padding: 0 26px; + font-size: calc(10.3 * var(--u)); color: var(--ink); + line-height: 1.55; text-align: center; + margin-top: calc(5 * var(--u)); max-width: calc(136 * var(--u)); } -.skin-dots { display: flex; justify-content: center; gap: 7px; margin-top: 16px; } +.skin-dots { display: flex; justify-content: center; gap: calc(5 * var(--u)); margin-top: calc(22 * var(--u)); } .skin-dots i { - width: 8px; height: 8px; border-radius: 50%; + width: calc(8 * var(--u)); height: calc(8 * var(--u)); border-radius: 50%; background: #D6D6D6; transition: background .25s; } .skin-dots i.on { background: #000; } -/* 按钮随内容流:卡底→按钮间距 22px(= flex gap 16 + 此处 6),两侧 auto 居中 */ -.btn-submit { margin: 6px auto 0; } +.btn-submit { margin: calc(25 * var(--u)) auto 0; } + +/* ─── 底部滚轮选择器(vendor/mobile-select)外观统一 ─── */ +.ms-mobile-select .ms-gray-layer { background: rgba(0,0,0,.55); } +.ms-mobile-select .ms-content { + bottom: -400px; + border-radius: 12px 12px 0 0; overflow: hidden; + padding-bottom: env(safe-area-inset-bottom); + font-family: var(--font); +} +.ms-mobile-select .ms-btn-bar { height: 50px; line-height: 50px; font-size: 15px; border-bottom-color: #F0F0F0; } +.ms-mobile-select .ms-cancel, .ms-mobile-select .ms-ensure { height: 50px; width: 64px; } +.ms-mobile-select .ms-cancel { color: var(--grey-2) !important; } +.ms-mobile-select .ms-ensure { color: var(--ink) !important; font-weight: 600; } +.ms-mobile-select .ms-title { font-size: 15px; font-weight: 600; color: var(--ink) !important; } +.ms-mobile-select .ms-wheels, .ms-mobile-select .ms-wheel, .ms-mobile-select .ms-shadow-mask { height: 220px; } +.ms-mobile-select .ms-select-container li { font-size: 16px; color: var(--ink); height: 44px; line-height: 44px; } +.ms-mobile-select .ms-select-line { height: 44px; top: 88px; border-color: #EFEFEF; background: rgba(245,240,120,.18); } /* ═══════════ 05-07 拍照页 ═══════════ */ #s-cam { background: #000; } -/* 相机未就绪/降级时的静态态:白底经蒙版 85% 压暗 → 框外深灰、轮廓内白,即 Figma 05-07 静态稿 */ +/* 相机未就绪/降级时的静态态:白底经蒙版 85% 压暗 → 框外深灰、轮廓内白 */ .cam-bg { position: absolute; inset: 0; background: #fff; } -/* 实时相机画面(getUserMedia 成功后显示,盖住静态示意图) */ +/* 实时画面整体缩小 15%:脸离镜头近一点也能装进取景轮廓(检测距离阈值按同一比例放宽) */ #cam-video { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; - transform: scaleX(-1); /* 自拍镜面预览 */ + transform: scale(.85); display: none; z-index: 0; } +#cam-video.mirror { transform: scale(-.85, .85); } /* 前置镜头镜像预览 */ #s-cam.cam-live #cam-video { display: block; } #s-cam.cam-live .cam-bg { display: none; } -/* 设计稿(Figma 05-07):压暗只在取景蒙版框外(.cam-dim-rect),不再整屏压暗 */ -#s-cam::before { content: none; } -#s-cam .nav { position: fixed; left: 0; right: 0; } -/* 拍照页纵向定位按 WebView 口径:Figma 帧 812 高扣掉顶部 86px 微信导航栏 = 726 高视口,百分比均以 726 为基准 */ +/* 纵向定位按 WebView 口径:Figma 帧 812 高扣掉顶部 86px 微信导航栏 = 726 高视口 */ .cam-step { - position: absolute; left: 0; right: 0; top: 6.4%; + position: absolute; left: 0; right: 0; top: 7.1%; text-align: center; - color: #fff; font-size: 21px; font-weight: 500; - line-height: 1.5; letter-spacing: 1px; + color: #fff; font-size: calc(21.5 * var(--u)); font-weight: 500; + line-height: 1.5; letter-spacing: calc(1 * var(--u)); text-shadow: 0 1px 10px rgba(0,0,0,.4); z-index: 2; } .cam-guide { position: absolute; left: 0; right: 0; bottom: 22.4%; - display: flex; justify-content: center; align-items: center; gap: 18px; + display: flex; justify-content: center; align-items: center; gap: calc(26 * var(--u)); z-index: 2; } -.cam-guide span { color: #fff; font-size: 14px; font-weight: 400; opacity: .92; } -.cam-guide i { width: 0; height: 15px; border-left: 1px solid #fff; } +.cam-guide span { color: #fff; font-size: calc(14.5 * var(--u)); font-weight: 400; } +.cam-guide i { width: 0; height: calc(15 * var(--u)); border-left: 1px solid rgba(255,255,255,.7); } .cam-controls { position: absolute; left: 0; right: 0; bottom: 5.2%; + height: calc(62 * var(--u)); display: flex; align-items: center; justify-content: space-between; - padding: 0 55px; - padding-bottom: env(safe-area-inset-bottom); + padding: 0 calc(56 * var(--u)); + margin-bottom: env(safe-area-inset-bottom); z-index: 2; } -.cam-side img { width: 31px; height: 31px; } -.cam-shutter img { width: 62px; height: 62px; } +.cam-side img { width: calc(30 * var(--u)); height: calc(30 * var(--u)); } +.cam-shutter { visibility: hidden; } +.cam-shutter img { width: calc(62 * var(--u)); height: calc(62 * var(--u)); } .cam-shutter:active { transform: scale(.92); } -/* 白屏补光:瞬间点亮(.on 无过渡),移除后淡出(总时长 600–900ms) */ +#s-cam.cam-manual .cam-shutter { visibility: visible; } +/* 白屏补光:瞬间点亮(.on 无过渡),移除后淡出 */ .cam-fill { position: absolute; inset: 0; background: #fff; - opacity: 0; pointer-events: none; z-index: 30; /* 盖住导航(z 20) */ + opacity: 0; pointer-events: none; z-index: 30; transition: opacity .25s ease; } .cam-fill.on { opacity: 1; transition: none; } -/* 步骤圆点:当前步高亮、完成步绿色打勾(Figma 无此元素,放在步骤文案与取景轮廓之间的空隙) */ -.cam-dots { - position: absolute; top: 16.2%; left: 0; right: 0; - display: flex; justify-content: center; gap: 12px; - z-index: 2; -} -.cam-dots i { - width: 9px; height: 9px; border-radius: 50%; - background: rgba(255,255,255,.35); - position: relative; transition: background .25s; -} -.cam-dots i.on { background: #fff; box-shadow: 0 0 0 3px rgba(255,255,255,.25); } -.cam-dots i.done { background: #34C759; } -.cam-dots i.done::after { - content: "✓"; position: absolute; left: 0; right: 0; top: -1px; - text-align: center; font-size: 8px; line-height: 10px; color: #fff; -} - /* 取景蒙版:SVG mask 镂空 + 轮廓线框,按 data-step 切换轮廓;框外 85% 黑 */ .cam-mask { position: absolute; inset: 0; @@ -276,53 +264,46 @@ button { font-family: inherit; border: 0; background: none; cursor: pointer; } .cam-mask[data-step="1"] [data-shape="1"], .cam-mask[data-step="2"] [data-shape="2"] { display: block; } .cam-mask.flip { transform: scaleX(-1); } /* 后置镜头预览不镜像:侧脸轮廓随之翻转 */ -.cam-dim-rect { fill: #000; fill-opacity: .85; } /* Figma 05-07 框外为 85% 黑(alpha 217):环境隐约可见,人脸只在轮廓内清晰 */ +.cam-dim-rect { fill: #000; fill-opacity: .85; } .cam-line path { fill: none; stroke: rgba(255,255,255,.85); stroke-width: 2.5; } -/* 未检测到人脸:轮廓呼吸动画(~1.8s);姿态 ok:品牌绿定格 */ @keyframes maskBreath { 0%,100% { stroke-opacity: .3; } 50% { stroke-opacity: .95; } } .cam-mask.no-face .cam-line path { animation: maskBreath 1.8s ease-in-out infinite; } .cam-mask.ok .cam-line path { stroke: #34C759; stroke-opacity: 1; animation: none; } +/* 动态捕捉扫描带:自上而下扫过轮廓内 */ +.cam-scan { display: none; } +.cam-mask.scanning .cam-scan { display: block; } +.cam-scan-band { animation: camScan 2.2s cubic-bezier(.45,.05,.55,.95) infinite; } +@keyframes camScan { from { transform: translateY(0); } to { transform: translateY(440px); } } /* 检测引擎加载状态(小字胶囊) */ .cam-status { - position: absolute; top: 17px; left: 50%; /* 与悬浮返回键同一行,不压步骤文案 */ + position: absolute; top: 17px; left: 50%; transform: translateX(-50%); - font-size: 10px; color: rgba(255,255,255,.85); + font-size: calc(11 * var(--u)); color: rgba(255,255,255,.85); background: rgba(0,0,0,.35); padding: 4px 12px; border-radius: 11px; z-index: 2; opacity: 0; transition: opacity .25s; white-space: nowrap; } .cam-status.show { opacity: 1; } -/* 引导提示:一次只显示一条,触发式出现/消失 */ +/* 动态提示:取景框下方、引导短句上方,一次一条 */ .cam-hint { - position: absolute; left: 50%; bottom: 29%; + position: absolute; left: 50%; bottom: 27%; transform: translateX(-50%) translateY(6px); - font-size: 13px; color: #fff; - background: rgba(0,0,0,.45); - padding: 7px 16px; border-radius: 17px; + font-size: calc(17 * var(--u)); font-weight: 500; color: #fff; + text-shadow: 0 1px 8px rgba(0,0,0,.65); z-index: 2; opacity: 0; pointer-events: none; transition: opacity .2s ease, transform .2s ease; white-space: nowrap; } .cam-hint.show { opacity: 1; transform: translateX(-50%); } -.cam-hint.ok { background: rgba(52,199,89,.82); } - -/* 倒计时大数字(取景中央 3→2→1) */ -.cam-count { - position: absolute; left: 0; right: 0; top: 40%; - text-align: center; - font-size: 76px; font-weight: 600; color: #fff; - text-shadow: 0 2px 18px rgba(0,0,0,.55); - z-index: 3; opacity: 0; transition: opacity .15s; pointer-events: none; -} -.cam-count.show { opacity: 1; } +.cam-hint.ok { color: #34C759; } /* 长时间无进展轻提示(不阻塞流程) */ .cam-nudge { - position: absolute; left: 50%; bottom: 13%; + position: absolute; left: 50%; bottom: 16.5%; transform: translateX(-50%) translateY(6px); - font-size: 10px; color: #fff; + font-size: calc(11.5 * var(--u)); color: #fff; background: rgba(0,0,0,.55); padding: 6px 14px; border-radius: 14px; z-index: 2; opacity: 0; pointer-events: none; @@ -331,131 +312,173 @@ button { font-family: inherit; border: 0; background: none; cursor: pointer; } } .cam-nudge.show { opacity: 1; transform: translateX(-50%); } -/* ═══════════ 08 报告页(Figma 2:9 严格还原:设计稿 1080 基准 ÷2.88) ═══════════ */ -.report-body { padding: 0 38px 20px; } -/* 顶栏:白底通栏横带,左返回圆钮 + 居中黑标题(设计 255px 高通栏含手机状态栏,H5 只保留标题栏) */ -.report-nav { - position: relative; - margin: 0 -38px; /* 通栏:抵消 body 侧 padding */ - height: calc(44px + env(safe-area-inset-top)); - padding-top: env(safe-area-inset-top); - background: #fff; - display: flex; align-items: center; justify-content: center; -} -.report-nav .nav-back { - position: absolute; left: 8px; - top: calc(50% + env(safe-area-inset-top) / 2); transform: translateY(-50%); -} -.report-nav-title { font-size: 14px; font-weight: 500; color: #000; letter-spacing: .5px; } +/* ═══════════ 08 报告页 ═══════════ */ +#s-report { background: var(--bg); } +.report-body { padding: 0 calc(18 * var(--u)) calc(28 * var(--u)); } .report-title { - font-size: 20.6px; font-weight: 700; color: var(--ink); - text-align: center; margin: 20px 0 13px; - letter-spacing: .5px; + font-size: calc(23 * var(--u)); font-weight: 700; color: var(--ink); + text-align: center; letter-spacing: calc(.5 * var(--u)); + padding: calc(32 * var(--u)) 0 calc(20 * var(--u)); } .card { - background: #fff; border-radius: 10px; - padding: 14px 18px; - margin-bottom: 10px; + background: #fff; border-radius: calc(8 * var(--u)); + margin-bottom: calc(12 * var(--u)); } -/* 总分:两张独立白卡(设计 420+420,间距 17px→6px) */ -.stats-row { display: flex; gap: 6px; margin-bottom: 10px; } -.stats-row .card-stat { flex: 1; margin-bottom: 0; } -.card-stat { - display: flex; flex-direction: column; align-items: center; - padding: 12px 10px 13px; +.stat-label { font-size: calc(12 * var(--u)); color: var(--ink); } +.stat-value { color: var(--ink); line-height: 1; white-space: nowrap; } +.stat-value b { font-weight: 500; letter-spacing: calc(.5 * var(--u)); } +.stat-value i { font-style: normal; font-weight: 500; } + +/* 总分卡:环形综合评分 | 肌肤年龄 */ +.card-summary { display: flex; align-items: center; padding: calc(16 * var(--u)) 0; } +.sum-score { + position: relative; flex: 0 0 52%; + display: flex; justify-content: center; + border-right: 1px solid #EFEFEF; } -.stat-label { font-size: 8.7px; font-weight: 500; color: var(--grey); } -/* label 下短横线(设计 99px→34px 居中) */ -.stat-label::after { - content: ""; display: block; - width: 34px; height: 1px; background: #EFEFEF; - margin: 4px auto 0; +.ring { width: calc(108 * var(--u)); height: calc(108 * var(--u)); transform: rotate(-90deg); } +.ring circle { fill: none; stroke-width: 9; } +.ring-bg { stroke: #EFEFEF; } +.ring-fg { stroke: var(--yellow); stroke-linecap: round; stroke-dasharray: 326.73; stroke-dashoffset: 326.73; transition: stroke-dashoffset .9s ease; } +.ring-center { + position: absolute; inset: 0; + display: flex; flex-direction: column; align-items: center; justify-content: center; } -/* 大数字 30px/400 + 单位 15px/500(设计 87px 与 43.7px) */ -.stat-value { margin-top: 2px; line-height: 40px; color: var(--ink); } -.stat-value b { font-size: 30px; font-weight: 400; letter-spacing: 1px; } -.stat-value i { font-style: normal; font-size: 15px; font-weight: 500; } -/* 三维度卡(设计:标签 8.7px 灰 / 分数 28.9px lh50 / 竖向短分隔线 21px / 最低分标红) */ -.card-dims { padding: 17px 19px 15px; } +.ring-center .stat-value b { font-size: calc(40 * var(--u)); } +.ring-center .stat-value i { font-size: calc(14 * var(--u)); } +.ring-center .stat-label { margin-top: calc(3 * var(--u)); font-size: calc(11.5 * var(--u)); color: var(--para); } +.sum-age { + flex: 1; min-width: 0; + display: flex; flex-direction: column; align-items: flex-start; + padding-left: calc(26 * var(--u)); +} +.stat-age { margin-top: calc(6 * var(--u)); } +.stat-age b { font-size: calc(58 * var(--u)); } +.stat-age i { font-size: calc(19 * var(--u)); margin-left: 2px; } +.age-pill { + margin-top: calc(7 * var(--u)); + font-size: calc(9.5 * var(--u)); color: var(--grey-2); + background: #F2F2F2; border-radius: calc(9 * var(--u)); + padding: calc(2 * var(--u)) calc(10 * var(--u)); + white-space: nowrap; +} +.age-pill[hidden] { display: none; } + +/* 三维度卡:分数 + 进度条;最低分标红并挂「优先关注」 */ +.card-dims { padding: calc(18 * var(--u)) calc(7 * var(--u)) calc(7 * var(--u)); } .dims-row { display: flex; } -.dim { flex: 1; position: relative; display: flex; flex-direction: column; align-items: center; } -.dim + .dim::before { - content: ""; position: absolute; left: 0; top: 37px; - width: 1px; height: 21px; margin-top: -10.5px; background: #EFEFEF; +.dim { + flex: 1; min-width: 0; position: relative; + display: flex; flex-direction: column; align-items: flex-start; + padding: 0 0 calc(4 * var(--u)) calc(14 * var(--u)); +} +.dim + .dim { border-left: 1px solid #EFEFEF; } +.dim-name { font-size: calc(12 * var(--u)); color: var(--ink); } +.dim-score { margin-top: calc(4 * var(--u)); font-size: calc(30 * var(--u)); font-weight: 500; color: var(--ink); line-height: 1.1; } +.dim-score b { font-size: calc(42 * var(--u)); font-weight: 500; } +.dim-score.dim-bad { color: var(--bad); } +.dim-bar { + display: block; margin-top: calc(6 * var(--u)); + width: calc(69 * var(--u)); height: calc(6 * var(--u)); + border-radius: calc(3 * var(--u)); background: #EFEFEF; overflow: hidden; +} +.dim-bar i { display: block; height: 100%; width: 0; border-radius: inherit; background: var(--yellow); transition: width .8s ease; } +.dim.best .dim-bar i { background: #D7EE6A; } +.dim.worst .dim-bar i { background: #F4B04F; } +.dim-flag { + visibility: hidden; + margin-top: calc(6 * var(--u)); + font-style: normal; font-size: calc(9 * var(--u)); color: var(--ink); + background: #F8F4A6; border-radius: calc(8 * var(--u)); + padding: calc(1 * var(--u)) calc(8 * var(--u)); +} +.dim.worst .dim-flag { visibility: visible; } +.dims-note { + position: relative; margin-top: calc(12 * var(--u)); + background: #F2F2F2; border-radius: calc(6 * var(--u)); + padding: calc(11 * var(--u)) calc(14 * var(--u)) calc(11 * var(--u)) calc(26 * var(--u)); +} +.dims-note::before { + content: ""; position: absolute; left: calc(14 * var(--u)); top: calc(12 * var(--u)); bottom: calc(12 * var(--u)); + width: calc(3 * var(--u)); border-radius: 2px; background: var(--yellow); +} +.dims-note-l1 { font-size: calc(13 * var(--u)); color: var(--para); } +.dims-note-l1 b { font-weight: 500; color: var(--ink); text-decoration: underline; text-underline-offset: 3px; } +.dims-note-l2 { margin-top: calc(4 * var(--u)); font-size: calc(12 * var(--u)); color: var(--grey); } + +/* 维度区块:大标题 + 卡内子 Tab + 大图 + 说明 */ +.sec-title { + font-size: calc(22 * var(--u)); font-weight: 700; color: var(--ink); + text-align: center; letter-spacing: calc(1 * var(--u)); + margin: calc(26 * var(--u)) 0 calc(16 * var(--u)); +} +.card-detail { padding: calc(14 * var(--u)) calc(30 * var(--u)) calc(22 * var(--u)); } +.detail-tabs { display: flex; justify-content: space-between; border-bottom: 1px solid #EFEFEF; } +.dtab { + position: relative; + min-width: calc(80 * var(--u)); + padding: calc(6 * var(--u)) calc(4 * var(--u)) calc(11 * var(--u)); + font-size: calc(14.5 * var(--u)); font-weight: 500; color: var(--ink); } -.dim-name { font-size: 8.7px; font-weight: 500; color: var(--grey); } -.dim-score { font-size: 28.9px; font-weight: 500; color: var(--ink); line-height: 50px; } -.dim-score.dim-bad { color: var(--bad); } /* 最低分标红 */ -/* 关注行(设计 fs25→8.7px:前缀灰 + 维度名深色大一号;下行深色口号) */ -.dims-note { margin-top: 2px; text-align: center; line-height: 1.5; } -.dims-note-l1 { font-size: 8.7px; font-weight: 500; color: #858585; } -.dims-note-l1 b { font-size: 10.1px; font-weight: 500; color: var(--ink); } -.dims-note-l2 { font-size: 8.7px; font-weight: 500; color: var(--ink); } -/* 具体描述(Tab 10.1px,选中 700 + 78×7 黄条;竖排条目标题在图左;图文垂直居中) */ -.card-detail { padding: 0 19px 19px; } -.detail-tabs { display: flex; border-bottom: 1px solid #F0F0F0; } -.dtab { flex: 1; padding: 13px 0 0; font-weight: 500; color: var(--ink); } -.dtab-tx { position: relative; display: inline-block; font-size: 10.1px; padding: 0 5px 7px; } .dtab.active { font-weight: 700; } -.dtab.active .dtab-tx::after { - content: ""; position: absolute; left: 50%; transform: translateX(-50%); bottom: -1px; - width: 78px; height: 7px; border-radius: 3.5px; background: var(--yellow); +.dtab.active::after { + content: ""; position: absolute; left: 0; right: 0; bottom: calc(-3 * var(--u)); + height: calc(6 * var(--u)); border-radius: calc(3 * var(--u)); background: var(--yellow); } .dpanel { display: none; } -.dpanel.active { display: block; animation: fade .25s ease; padding-top: 19px; } +.dpanel.active { display: block; animation: fade .25s ease; padding-top: calc(14 * var(--u)); } @keyframes fade { from { opacity: 0; } to { opacity: 1; } } -.ditem { display: flex; gap: 9px; align-items: center; } -.ditem + .ditem { margin-top: 18px; padding-top: 18px; border-top: 1px solid #F0F0F0; } -.ditem-img { width: 92px; height: 96px; object-fit: cover; border-radius: 6px; flex-shrink: 0; } -.ditem-text { flex: 1; min-width: 0; } -/* 检测条目标题:竖排,位于图左(设计稿如此) */ -.ditem-title { - writing-mode: vertical-rl; - letter-spacing: 4px; - font-size: 11.5px; font-weight: 500; color: var(--ink); - margin-right: 3.5px; flex-shrink: 0; +.ditem-img { + width: calc(213 * var(--u)); height: calc(277 * var(--u)); + margin: 0 auto calc(14 * var(--u)); + object-fit: cover; border-radius: calc(6 * var(--u)); + background: #F5F5F5; cursor: zoom-in; } -.ditem-para { font-size: 10.1px; font-weight: 400; color: var(--para); line-height: 1.41; overflow-wrap: anywhere; } -/* 护理建议(标题 13px/700/#000;条目 10.1px #4A4A4A lh1.43;收尾句 11.1px/500/#000) */ -.card-care, .card-essence { padding: 18px 20px 16px 19px; } -.card-heading { font-size: 13px; font-weight: 700; color: #000; margin-bottom: 4px; } -.care-item { font-size: 10.1px; color: var(--para); line-height: 1.43; margin-bottom: 7px; overflow-wrap: anywhere; } -.care-summary { font-size: 11.1px; font-weight: 500; color: #000; margin-top: 5px; } -/* 精华推荐(Figma 无此卡,按业务文档 V2.3 §6.4 保留,样式沿用护理建议卡) */ -.essence-name { font-size: 12px; font-weight: 700; color: var(--ink); margin-bottom: 5px; } -.essence-triggers { font-size: 7px; color: var(--grey-3); margin-top: 6px; } -/* 产品推荐:大标题独立落在灰底;白卡左文案右图(图 151×174 撑满卡高,COREⅡ 为 Figma 导出字标 SVG) */ +.ditem-img[hidden] { display: none; } +.ditem-para { + padding-top: calc(12 * var(--u)); border-top: 1px solid #EFEFEF; + font-size: calc(13 * var(--u)); color: var(--para); line-height: 1.72; + text-align: justify; overflow-wrap: anywhere; +} + +.vm-notes { font-size: calc(10 * var(--u)); color: var(--grey-3); line-height: 1.7; margin: calc(14 * var(--u)) calc(6 * var(--u)) 0; } +.report-disclaimer { + font-size: calc(10 * var(--u)); color: var(--para); + line-height: 1.7; margin: calc(18 * var(--u)) calc(6 * var(--u)) calc(14 * var(--u)); +} + +/* 护理建议 / 精华推荐 */ +.card-care, .card-essence { padding: calc(20 * var(--u)) calc(22 * var(--u)) calc(18 * var(--u)); } +.card-heading { font-size: calc(17 * var(--u)); font-weight: 700; color: #000; margin-bottom: calc(10 * var(--u)); } +.care-item { font-size: calc(13.5 * var(--u)); color: var(--para); line-height: 1.7; margin-bottom: calc(10 * var(--u)); overflow-wrap: anywhere; } +.care-summary { font-size: calc(13.5 * var(--u)); font-weight: 500; color: #000; margin-top: calc(4 * var(--u)); } +.essence-name { font-size: calc(16 * var(--u)); font-weight: 700; color: var(--ink); } +.essence-triggers { font-size: calc(11 * var(--u)); color: var(--grey-3); margin-top: calc(6 * var(--u)); line-height: 1.5; } + +/* 产品推荐:大标题落在灰底;白卡左文案右图(各占一半) */ .prod-title { - font-size: 21.6px; font-weight: 700; color: var(--ink); - line-height: 1.5; text-align: center; margin: 15px 0 16px; + font-size: calc(23 * var(--u)); font-weight: 700; color: var(--ink); + line-height: 1.45; text-align: center; letter-spacing: calc(1 * var(--u)); + margin: calc(30 * var(--u)) 0 calc(18 * var(--u)); } -.card-product { padding: 0; overflow: hidden; } -.prod-card { display: flex; align-items: stretch; } +.card-product { overflow: hidden; } +.prod-card { display: flex; align-items: stretch; min-height: calc(176 * var(--u)); } .prod-info { flex: 1; min-width: 0; - padding: 44px 10px 16px 16px; + padding: calc(40 * var(--u)) calc(10 * var(--u)) calc(16 * var(--u)) calc(18 * var(--u)); display: flex; flex-direction: column; align-items: flex-start; } -.prod-badge { width: 39px; height: auto; } -.prod-name { font-size: 13px; font-weight: 700; color: var(--ink); line-height: 1.34; margin-top: 9px; overflow-wrap: anywhere; } -.prod-sub { font-size: 7.3px; font-weight: 500; color: var(--grey-3); margin-top: auto; padding-top: 10px; } -.prod-scroll { - flex-shrink: 0; margin: 0; padding: 0; - display: flex; overflow: hidden; - scrollbar-width: none; -} -.prod-scroll::-webkit-scrollbar { display: none; } -.prod-scroll img { - width: 151px; height: 174px; - object-fit: cover; flex-shrink: 0; -} -.btn-more { margin: 20px auto 0; } -/* 免责小字:灰底独立文字,左对齐到卡片文案位(设计 x=166→57.6px;fw300 #000) */ -.report-disclaimer { - font-size: 7.3px; font-weight: 300; color: #000; - line-height: 1.44; text-align: left; - margin: 13px 19px 11px; -} +.prod-badge { width: calc(44 * var(--u)); height: auto; } +.prod-name { font-size: calc(14.5 * var(--u)); font-weight: 700; color: var(--ink); line-height: 1.45; margin-top: calc(10 * var(--u)); } +.prod-sub { font-size: calc(9.5 * var(--u)); color: var(--grey-3); margin-top: auto; padding-top: calc(10 * var(--u)); } +.prod-scroll { flex: 0 0 51%; display: flex; overflow: hidden; } +.prod-scroll img { width: 100%; height: 100%; object-fit: cover; } +.btn-more { margin: calc(26 * var(--u)) auto 0; } + +/* 档位配色(引擎档位 → 文字色) */ +.lv-good { color: #2FA84F; } +.lv-warn { color: #E8A13D; } +.lv-bad { color: var(--bad); } /* ═══════════ 弹窗 ═══════════ */ .overlay { @@ -467,37 +490,31 @@ button { font-family: inherit; border: 0; background: none; cursor: pointer; } .overlay.show { display: flex; } .overlay .popup { animation: popIn .3s cubic-bezier(.34,1.4,.64,1) both; } @keyframes popIn { from { opacity: 0; transform: translateY(26px) scale(.96); } to { opacity: 1; transform: none; } } +/* 弹窗 308×312(设计稿 02/04) */ .popup { position: relative; - width: 307px; - background: #fff; border-radius: 10px; - padding: 34px 26px 33px; + width: calc(308 * var(--u)); min-height: calc(312 * var(--u)); + background: #fff; border-radius: calc(10 * var(--u)); + padding: calc(40 * var(--u)) calc(32 * var(--u)) calc(35 * var(--u)); text-align: left; + display: flex; flex-direction: column; } .popup-close { - position: absolute; top: 12px; right: 12px; - width: 31px; height: 31px; + position: absolute; top: calc(10 * var(--u)); right: calc(10 * var(--u)); + width: calc(32 * var(--u)); height: calc(32 * var(--u)); display: flex; align-items: center; justify-content: center; } -.popup-close img { width: 17px; height: 17px; opacity: .8; } +.popup-close img { width: calc(19 * var(--u)); height: calc(19 * var(--u)); } .popup-title { - font-size: 23px; font-weight: 500; color: var(--ink); - line-height: 1.35; letter-spacing: .5px; - margin-bottom: 15px; + font-size: calc(23 * var(--u)); font-weight: 500; color: var(--ink); + line-height: 1.38; letter-spacing: calc(.5 * var(--u)); + margin-bottom: calc(14 * var(--u)); } -.popup-title-center { text-align: center; margin: 18px 0 24px; } -/* 拍照引导弹窗:近方形比例,标题垂直居中偏上,按钮贴底 */ -#p-photoguide .popup { - min-height: 312px; - display: flex; flex-direction: column; - padding: 34px 26px 52px; -} -#p-photoguide .popup-title-center { margin: 56px 0 auto; } -.popup-body { font-size: 11.5px; color: var(--ink); line-height: 1.55; margin-bottom: 20px; } -.privacy-row { display: flex; align-items: center; gap: 8px; margin-bottom: 40px; cursor: pointer; } +.popup-body { font-size: calc(11.6 * var(--u)); color: var(--ink); line-height: 1.6; margin-bottom: calc(26 * var(--u)); } +.privacy-row { display: flex; align-items: center; gap: calc(9 * var(--u)); cursor: pointer; } .checkbox { - width: 11px; height: 11px; border-radius: 50%; - border: 1px solid #282828; background: #fff; + width: calc(12 * var(--u)); height: calc(12 * var(--u)); border-radius: 50%; + border: 1px solid var(--ink); background: #fff; flex-shrink: 0; position: relative; } .checkbox.checked::after { @@ -505,17 +522,32 @@ button { font-family: inherit; border: 0; background: none; cursor: pointer; } inset: 2px; border-radius: 50%; background: var(--ink-2); } -.privacy-text { font-size: 10px; color: var(--grey-2); } -.popup-btn { margin: 0 auto; } +.privacy-text { font-size: calc(10.3 * var(--u)); color: var(--grey-2); } +.privacy-link { color: var(--ink); font-weight: 500; text-decoration: underline; cursor: pointer; } +.privacy-link:active { opacity: .7; } +.popup-btn { margin: auto auto 0; } +#p-privacy .popup-btn { margin-top: calc(40 * var(--u)); } + +/* 拍照引导:背景只留启动页底图 + Slogan,标题居中 */ +.overlay-guide { background: #101823; } +.guide-bg { position: absolute; inset: 0; background: url(../assets/img/bg-splash.jpg) center/cover no-repeat; } +.guide-bg::after { content: ""; position: absolute; inset: 0; background: rgba(0,0,0,.5); } +#p-photoguide .popup { padding: 0 calc(24 * var(--u)) calc(55 * var(--u)); } +.popup-title-center { text-align: center; margin: calc(88 * var(--u)) 0 calc(40 * var(--u)); } + +/* 报告检测图放大 */ +.overlay-zoom { background: rgba(0,0,0,.92); z-index: 80; cursor: zoom-out; } +.overlay-zoom img { max-width: 100%; max-height: 100%; object-fit: contain; animation: zoomIn .22s ease both; } +@keyframes zoomIn { from { opacity: 0; transform: scale(.88); } to { opacity: 1; transform: none; } } /* 分析中 */ .overlay-dark { background: rgba(16,16,16,.82); } .analyzing { color: #fff; text-align: center; - font-size: 13px; letter-spacing: 1px; + font-size: calc(14 * var(--u)); letter-spacing: 1px; display: flex; flex-direction: column; align-items: center; gap: 16px; } -.analyzing-tip { font-size: 10px; color: rgba(255,255,255,.45); letter-spacing: 0; } +.analyzing-tip { font-size: calc(11 * var(--u)); color: rgba(255,255,255,.45); letter-spacing: 0; } .spinner { width: 34px; height: 34px; border: 3px solid rgba(255,255,255,.25); @@ -529,59 +561,32 @@ button { font-family: inherit; border: 0; background: none; cursor: pointer; } .toast { position: absolute; left: 50%; bottom: 15%; transform: translateX(-50%) translateY(8px); + max-width: 86%; background: rgba(20,20,20,.85); color: #fff; - font-size: 12px; + font-size: calc(13 * var(--u)); line-height: 1.5; text-align: center; padding: 9px 18px; border-radius: 18px; opacity: 0; pointer-events: none; transition: all .25s ease; - z-index: 99; white-space: nowrap; + z-index: 99; } .toast.show { opacity: 1; transform: translateX(-50%) translateY(0); } -/* ═══════════ 第3轮上线打磨新增(以下均为新增规则,不动以上已验收样式) ═══════════ */ -/* 隐私协议链接:下划线、色值不变,点击开占位弹层 */ -.privacy-link { color: var(--grey-2); text-decoration: underline; cursor: pointer; } -.privacy-link:active { opacity: .7; } -/* 隐私协议占位弹层(轻量,复用 .overlay/.popup 骨架) */ -#p-policy .popup { padding: 30px 26px 34px; } -.popup-title-policy { font-size: 18px; margin-bottom: 12px; } - -/* ═══════════ 第4轮:引导式自动三连拍 + SkinScore 报告接入(新增,不动以上已验收样式) ═══════════ */ -/* 档位配色:良好=绿 / 改善=琥珀 / 预警=红(文字场景直接上色) */ -.lv-good { color: #2FA84F; } -.lv-warn { color: #E8A13D; } -.lv-bad { color: var(--bad); } -/* 肤龄钳制留痕(小字灰) */ -.stat-sub { font-size: 8px; color: var(--grey-3); min-height: 10px; line-height: 10px; } -/* 分类条目:S 值 + 档位徽章 */ -.cat-head { display: flex; align-items: center; gap: 6px; margin-bottom: 4px; } -.cat-s { font-size: 15px; font-weight: 600; color: var(--ink); } -.cat-s.cat-na { font-size: 9px; font-weight: 400; color: var(--grey-2); } -.cat-badge { font-style: normal; font-size: 8px; font-weight: 500; color: #fff; padding: 2px 7px; border-radius: 8px; } -.cat-badge.lv-good { background: #2FA84F; color: #fff; } -.cat-badge.lv-warn { background: #E8A13D; color: #fff; } -.cat-badge.lv-bad { background: var(--bad); color: #fff; } -/* 降级标注(下颌线未参与评分等,小字灰) */ -.vm-notes { font-size: 7px; font-weight: 400; color: var(--grey-3); line-height: 1.7; margin: 0 2px 9px; } -/* 精华推荐卡 */ -.essence-name { font-size: 15px; font-weight: 700; color: var(--ink); margin-bottom: 6px; } -.essence-triggers { font-size: 8.7px; color: var(--grey-3); margin-top: 6px; } /* 设计规范 Caption 级:验收要求展示命中判据,需可读 */ - -/* ═══════════ 第5轮:报告页检测可视化(face_maps 图层 / marks 叠加,新增,不动以上已验收样式) ═══════════ */ -/* 分类卡检测图复用 .ditem-img(左图右文、92×96 裁切);无检测图时(hidden)卡片退回文字占位 */ -.ditem-img[hidden] { display: none; } - /* ═══════════ 隐私政策与服务协议(全文页) ═══════════ */ .overlay-policy { background: #F5F5F5; } -.policy-page { width: 100%; height: 100%; display: flex; flex-direction: column; background: #fff; } +.policy-page { position: relative; width: 100%; height: 100%; display: flex; flex-direction: column; background: #fff; } +.policy-float-back { + position: absolute; top: 12px; left: 12px; z-index: 2; + background: #fff; box-shadow: 0 1px 6px rgba(0,0,0,.12); +} .policy-header { position: relative; flex: none; height: var(--nav-h); display: flex; align-items: center; justify-content: center; background: #fff; border-bottom: 1px solid var(--line); } -.policy-header .nav-back { position: absolute; left: 4px; top: 0; width: 40px; height: 100%; display: flex; align-items: center; justify-content: center; } +.policy-header .nav-back { position: absolute; left: 4px; top: 0; width: 40px; height: 100%; border: 0; } .policy-header-title { font-size: 14px; font-weight: 500; color: #000; } .policy-body { flex: 1; overflow-y: auto; -webkit-overflow-scrolling: touch; padding: 22px 20px 48px; } +#p-policy .policy-body { padding-top: 52px; } .policy-title { font-size: 19px; font-weight: 700; color: var(--ink); line-height: 1.4; margin-bottom: 16px; } .policy-h2 { font-size: 15px; font-weight: 600; color: var(--ink); @@ -598,10 +603,6 @@ button { font-family: inherit; border: 0; background: none; cursor: pointer; } .policy-end { text-align: center; color: var(--grey); margin-top: 28px; } /* ═══════════ 历史报告 ═══════════ */ -.nav-history { - position: absolute; right: 10px; top: 50%; transform: translateY(-50%); - font-size: 12px; color: var(--grey-2); padding: 4px 8px; -} .policy-header .history-clear { position: absolute; right: 10px; top: 50%; transform: translateY(-50%); font-size: 12px; color: var(--grey-2); padding: 4px 8px; @@ -620,77 +621,3 @@ button { font-family: inherit; border: 0; background: none; cursor: pointer; } .his-score b { font-size: 22px; font-weight: 500; color: var(--ink); } .his-score span { font-size: 11px; color: var(--grey); margin-left: 2px; } .his-del { flex: none; font-size: 11px; color: var(--grey-2); padding: 6px 4px; } - -/* ═══════════ 第6轮:真机反馈 13 项 ═══════════ */ -/* #2 姿态提示浮层:移到取景框下方(原 29% 与脸部轮廓/下巴重叠) */ -.cam-hint { bottom: 21%; } -/* #10 无进展提示与快门重叠:上移 */ -.cam-nudge { bottom: 16%; } -/* #4/#10 拍照页顶栏取消,返回键改悬浮白圆(对齐设计稿) */ -.cam-back-float { - position: absolute; top: 12px; left: 12px; z-index: 3; - background: rgba(255,255,255,.9); border-color: transparent; -} -/* #12 预览镜像改为类切换(前置镜像/后置不镜像) */ -#cam-video { transform: none; } -#cam-video.mirror { transform: scaleX(-1); } - -/* #1 城市自定义下拉(替代原生 select) */ -.rs { position: relative; } -.rs-current { - width: 100%; display: flex; align-items: center; justify-content: space-between; - background: #fff; border: 1px solid var(--line); border-radius: 8px; - padding: 12px 14px; font-size: 13px; color: var(--ink); text-align: left; -} -.rs-current .chev { transition: transform .2s; } -.rs.open .rs-current .chev { transform: rotate(225deg); } -.rs-value.rs-placeholder { color: #B3B3B3; } -.rs-list { - position: absolute; left: 0; right: 0; top: calc(100% + 6px); z-index: 30; - max-height: 264px; overflow-y: auto; -webkit-overflow-scrolling: touch; - background: #fff; border: 1px solid var(--line); border-radius: 8px; - box-shadow: 0 8px 24px rgba(0,0,0,.08); -} -.rs-list li { padding: 12px 14px; font-size: 13px; color: var(--ink); } -.rs-list li + li { border-top: 1px solid #F3F3F3; } -.rs-list li:active { background: #F7F7F7; } - -/* #5 隐私页去头部条,返回键悬浮 */ -.policy-float-back { - position: absolute; top: 12px; left: 12px; z-index: 2; - background: #fff; box-shadow: 0 1px 6px rgba(0,0,0,.12); -} -.overlay-policy .policy-page { position: relative; } - -/* ═══════════ 第7轮:用户测试 agent 反馈 ═══════════ */ -/* P1-1 隐私页标题被悬浮返回键遮挡:正文上留返回键位 */ -#p-policy .policy-body { padding-top: 52px; } -/* P1-2 姿态提示气泡压住底部引导文字:改到步骤文案下方、取景框上方 */ -.cam-hint { bottom: auto; top: 26%; } - -/* ═══════════ 第8轮:真机反馈 11 项 ═══════════ */ -/* #1 城市下拉浮层层级高于肤质轮播圆点 */ -.rs { z-index: 50; } -.rs-list { z-index: 50; } -/* #4 无进展提示再上移(取景/拍摄区下方,引导 chips 上方) */ -.cam-nudge { bottom: 17.5%; } /* 引导 chips(bottom 22.4%) 与快门之间 */ -/* #6 报告页头部:按 Figma 08 还原为内联白底顶栏(见报告页主区块),不再悬浮 */ - -/* ═══════════ 第9轮:真机反馈 ═══════════ */ -/* #1 城市下拉浮层再提级,并加实体阴影边界(部分端 stacking 表现不同) */ -.rs, .rs-list { z-index: 999; } -/* #3 姿态提示浮层改设计语言:无气泡底,纯白文字+投影,置于引导 chips 上方 */ -.cam-hint { - top: auto; bottom: 26%; - background: none; padding: 0; border-radius: 0; - font-size: 15px; font-weight: 500; text-shadow: 0 1px 8px rgba(0,0,0,.65); -} -.cam-hint.ok { background: none; color: #34C759; } -/* #9 档案页顶栏移除,返回键悬浮(白圆带影,压暗背景上可读) */ -.profile-float-back { - position: fixed; top: 12px; left: 12px; z-index: 21; - background: rgba(255,255,255,.92); border-color: transparent; - box-shadow: 0 1px 6px rgba(0,0,0,.18); -} -/* 下拉浮层与肤质圆点之间留明确间隙 */ -.rs-list { max-height: 236px; } diff --git a/h5/index.html b/h5/index.html index 2a79ab6..9314227 100644 --- a/h5/index.html +++ b/h5/index.html @@ -12,6 +12,7 @@ 1'me 在线肌肤测试 + @@ -32,49 +33,44 @@
-

您的肌肤状态

+
- - + +
-
- - -
+
-
- - -
- +
+ + + +
偏干 - 水油平衡 偏油 + 水油平衡
- @@ -87,15 +83,18 @@
- -
-

1/3
请单人正脸出镜

+

1/3
请正对屏幕

检测引擎加载中…

-
-

识别不到?可点下方快门手动拍摄

+

识别不到?请正对镜头,并到光线充足处

@@ -124,6 +126,7 @@ + @@ -137,90 +140,82 @@
- -
- - 1'me在线肌肤测试 -

您的肌肤状态表现

- -
-
- 综合评分 - --分 + +
+
+ +
+ --分 + 综合评分 +
-
+
肌肤年龄 - --岁 - + --岁 +
-
紧塑力-.-
-
活力感-.-
-
稳定力-.-
+
紧塑力-.-优先关注
+
活力感-.-优先关注
+
稳定力-.-优先关注
-

近期优先关注:
---

+

近期优先关注:---

+ +

紧塑力

-
- - - +
+ +
-
-
-
-

轮廓

- 轮廓检测图 -
-

检测完成后显示分析结果。

-
-
-
-

皱纹

- 皱纹检测图 -
-

检测完成后显示分析结果。

-
-
-
-
-
-

光滑

- -
-

检测完成后显示分析结果。

-
-
-
-

肤色水润

- -
-

检测完成后显示分析结果。

-
-
-
-
-
-

敏感

- -
-

检测完成后显示分析结果。

-
-
-
-

痘痘

- -
-

检测完成后显示分析结果。

-
-
-
+
+ 轮廓检测图 +

检测完成后显示分析结果。

+
+
+ 皱纹检测图 +

检测完成后显示分析结果。

+
+
+ +

活力感

+
+
+ + +
+
+ +

检测完成后显示分析结果。

+
+
+ +

检测完成后显示分析结果。

+
+
+ +

稳定力

+
+
+ + +
+
+ +

检测完成后显示分析结果。

+
+
+ +

检测完成后显示分析结果。

@@ -248,7 +243,7 @@
CORE Ⅱ -

以我自在有势挺括双仓精华液

+

以我自在有势挺括
双仓精华液

参透皮下根源 28天校准皮肤状态

@@ -278,16 +273,23 @@
-
+ +
+

Life in Full
为更好的生命表现

+ +
+ 检测图放大 +
+
@@ -449,6 +451,8 @@ + +