Files
juzi/h5/css/style.css
T
xsl 5957ef04fc feat: 接入 Face++ 真实测肤 + 三轮多角色迭代修复
Face++ 集成(高阶版 skinanalyze_advanced):
- h5/server.js:零依赖 Node 后端,静态托管 + /api/skin-analyze 中转,
  api_secret 只存服务端(从 apikey.bash/env 读取,.gitignore 已排除)
- 实时相机:getUserMedia 自拍取景,不可用时降级系统相机;
  相机令牌防授权回调泄漏摄像头;离开拍照页即释放
- 检测流:三连拍仅送检正面照(压至 1280px JPEG),错误码→友好提示
- 结果映射:肌肤年龄=skin_age 真实值;紧塑力/活力感/稳定力由真实
  维度合成;具体描述按检出项动态生成;检测图用用户实拍
- ?mock=1 内置样本供离线/自动化验证;README 补充部署与 HTTPS 说明

三轮迭代修复(QA+美术并行审计→开发统一修复,共 ~28 项):
- 功能:P1 鼠标拖拽轮播(Pointer Events+setPointerCapture)、
  快门/返回竞态(camSeq 令牌)、报告页状态重置、弹窗 ESC/遮罩关闭
- 视觉:还原度 80%→94%,档案页布局/弹窗比例/报告页产品卡等
  结构性偏差修正,字号/间距逐项实测核销
- 工程化:首屏 701KB→386KB(懒加载+preload+清理 212KB 无用资源)、
  favicon/SEO meta/隐私协议占位层/README

Review 修复:server.js 路径穿越前缀绕过(ROOT+sep 校验)、
getUserMedia 迟到回调释放相机(camSeqCam 令牌)
2026-09-22 21:06:33 +08:00

440 lines
16 KiB
CSS
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* ═══════════════════════════════════════════
巨子测肤 · 1'me 在线肌肤测试 — H5 样式
设计稿 1080px 基准 → 此处 375px(÷2.88)
主色 #F5F078 · 文字 #282828 · 底 #EFEFEF
═══════════════════════════════════════════ */
:root {
--yellow: #F5F078;
--ink: #282828;
--ink-2: #2C2C2C;
--grey: #868686;
--grey-2: #757575;
--grey-3: #707070;
--para: #4A4A4A;
--bg: #EFEFEF;
--line: #E5E5E5;
--dry: #EAE6E1;
--neutral: #DBF6D6;
--bad: #D30C0C;
--nav-h: 44px;
--font: "Noto Sans SC","PingFang SC","Hiragino Sans GB","Microsoft YaHei",system-ui,sans-serif;
}
* { margin: 0; padding: 0; box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
html, body { height: 100%; }
body {
font-family: var(--font);
color: var(--ink);
background: #000;
overflow: hidden;
-webkit-font-smoothing: antialiased;
user-select: none;
max-width: 480px; /* 桌面预览时居中显示 */
margin: 0 auto;
position: relative;
}
img { display: block; }
button { font-family: inherit; border: 0; background: none; cursor: pointer; }
/* ─── 屏幕容器 ─── */
.screen {
position: absolute; inset: 0;
display: none;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
background: var(--bg);
}
.screen.active { display: block; animation: screenIn .35s ease both; }
@keyframes screenIn { from { opacity: 0; transform: translateX(14px); } to { opacity: 1; transform: none; } }
/* ─── 通用按钮 ─── */
.btn-primary {
display: block;
width: 182px; height: 39px;
line-height: 39px;
background: var(--yellow);
border-radius: 20px;
color: var(--ink);
font-size: 14px; font-weight: 500;
text-align: center;
transition: transform .12s ease;
}
.btn-primary:active { transform: scale(.96); }
/* ─── 折线箭头(返回/下拉) ─── */
.chev {
width: 9px; height: 9px;
border: solid var(--ink-2); border-width: 0 2px 2px 0;
display: inline-block; padding: 2px;
}
.chev.left { transform: rotate(135deg); }
.chev.down { transform: rotate(45deg); }
/* ═══════════ 01 启动页 ═══════════ */
#s-splash { background: #101823; }
.splash-bg {
position: absolute; inset: 0;
background: url(../assets/img/bg-splash.jpg) center/cover no-repeat;
}
.splash-slogan {
position: absolute; left: 0; right: 0; top: 20%;
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);
}
.splash-bottom {
position: absolute; left: 0; right: 0; bottom: 11%;
display: flex; flex-direction: column; align-items: center; gap: 18px;
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);
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;
}
/* ═══════════ 03 档案填写 ═══════════ */
#s-profile { background: #0e0f18; }
.profile-bg {
position: absolute; inset: 0;
/* 设计稿:启动页照片 + 50% 正片叠底黑遮罩(矩形3) */
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;
}
.page-title {
color: #fff; font-size: 21px; font-weight: 700;
letter-spacing: 1px; margin-bottom: 2px;
}
.field-label {
display: block;
color: #fff; font-size: 14px; font-weight: 400;
margin-bottom: 6px; letter-spacing: .5px;
}
.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; }
/* 肤质卡片 */
.skin-card {
background: #fff; border-radius: 6px;
padding: 24px 10px 26px;
}
.skin-axis {
display: flex; justify-content: center; gap: 8px;
margin: 0 0 14px;
}
.axis-item {
display: inline-flex; align-items: center; gap: 4px;
font-size: 9px; color: var(--ink);
}
.chip { width: 8px; height: 8px; border-radius: 2px; display: inline-block; }
.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-desc {
font-size: 10px; color: var(--ink); font-weight: 400;
line-height: 1.5; text-align: center;
margin-top: 5px; padding: 0 26px;
}
.skin-dots { display: flex; justify-content: center; gap: 7px; margin-top: 16px; }
.skin-dots i {
width: 8px; height: 8px; 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; }
/* ═══════════ 05-07 拍照页 ═══════════ */
#s-cam { background: #000; }
.cam-bg {
position: absolute; inset: 0;
background-size: cover; background-position: center;
}
/* 实时相机画面(getUserMedia 成功后显示,盖住静态示意图) */
#cam-video {
position: absolute; inset: 0;
width: 100%; height: 100%;
object-fit: cover;
transform: scaleX(-1); /* 自拍镜面预览 */
display: none; z-index: 0;
}
#s-cam.cam-live #cam-video { display: block; }
#s-cam.cam-live .cam-bg { display: none; }
/* 15% 黑遮罩,压住画面保证白色文案可读(静态示意图与实时画面共用) */
#s-cam::before { content: ""; position: absolute; inset: 0; background: rgba(0,0,0,.15); z-index: 1; }
#s-cam .nav { position: fixed; left: 0; right: 0; }
.cam-step {
position: absolute; left: 0; right: 0; top: 16.5%;
text-align: center;
color: #fff; font-size: 21px; font-weight: 500;
line-height: 1.5; letter-spacing: 1px;
text-shadow: 0 1px 10px rgba(0,0,0,.4);
z-index: 2;
}
.cam-guide {
position: absolute; left: 0; right: 0; bottom: 20%;
display: flex; justify-content: center; align-items: center; gap: 18px;
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-controls {
position: absolute; left: 0; right: 0; bottom: 5.2%;
display: flex; align-items: center; justify-content: space-between;
padding: 0 55px;
padding-bottom: env(safe-area-inset-bottom);
z-index: 2;
}
.cam-side img { width: 31px; height: 31px; }
.cam-shutter img { width: 62px; height: 62px; }
.cam-shutter:active { transform: scale(.92); }
.cam-flash {
position: absolute; inset: 0; background: #fff;
opacity: 0; pointer-events: none; z-index: 30; /* 盖住导航(z 20) */
transition: opacity .18s ease;
}
.cam-flash.on { opacity: .92; }
/* ═══════════ 08 报告页 ═══════════ */
.report-body { padding: 12px 38px 34px; }
.report-title {
font-size: 20px; font-weight: 700; color: var(--ink);
text-align: center; margin: 10px 0 16px;
letter-spacing: .5px;
}
.card {
background: #fff; border-radius: 6px;
padding: 14px 15px;
margin-bottom: 9px;
}
/* 总分 */
.card-stats { display: flex; padding: 11px 15px; }
.stat { flex: 1; display: flex; flex-direction: column; align-items: center; gap: 2px; }
.stat-label { font-size: 9px; font-weight: 500; color: var(--grey); }
/* label 下短横线(34×1 居中) */
.stat-label::after {
content: ""; display: block;
width: 34px; height: 1px; background: #EFEFEF;
margin: 4px auto 0;
}
.stat-value { font-size: 29px; font-weight: 400; color: var(--ink); letter-spacing: 1px; line-height: 34px; }
/* 维度(灰底独立文字,置于总分卡与维度卡之间) */
.dims-intro { font-size: 10px; font-weight: 500; color: #464646; line-height: 1.6; margin: 0 2px 9px; }
.dims-row { display: flex; }
.dim { flex: 1; display: flex; flex-direction: column; align-items: center; gap: 2px; }
.dim + .dim { border-left: 1px solid #EFEFEF; }
.dim-name { font-size: 9px; font-weight: 500; color: var(--grey); }
.dim-score { font-size: 29px; font-weight: 500; color: var(--ink); }
.dim-bad { color: var(--bad); }
.dims-note { font-size: 9px; font-weight: 500; color: var(--grey); line-height: 1.55; margin-top: 9px; text-align: center; }
.dims-disclaimer { font-size: 7px; font-weight: 400; color: var(--grey-3); margin: 0 2px 9px; line-height: 1.5; }
/* 具体描述 */
.detail-tabs {
display: flex;
border-bottom: 1px solid #F0F0F0;
margin-bottom: 12px;
}
.dtab {
flex: 1; padding: 8px 0 9px;
font-size: 10px; font-weight: 500; color: var(--ink);
position: relative;
}
.dtab.active { font-weight: 700; }
.dtab.active::after {
content: ""; position: absolute;
left: 50%; transform: translateX(-50%); bottom: -1px;
width: 78px; height: 7px; border-radius: 3.5px;
background: var(--yellow);
}
.dpanel { display: none; }
.dpanel.active { display: block; animation: fade .25s ease; }
@keyframes fade { from { opacity: 0; } to { opacity: 1; } }
.ditem { display: flex; gap: 11px; align-items: flex-start; }
.ditem + .ditem { margin-top: 13px; padding-top: 13px; border-top: 1px solid #F0F0F0; }
.ditem-img { width: 92px; height: 96px; object-fit: cover; border-radius: 4px; flex-shrink: 0; }
.ditem-text { flex: 1; }
/* 检测条目标题:竖排,位于图左(设计稿如此) */
.ditem-title {
writing-mode: vertical-rl;
letter-spacing: 4px;
font-size: 11.5px; font-weight: 500; color: var(--ink);
margin-right: 12px; flex-shrink: 0;
}
.ditem-para { font-size: 10px; font-weight: 400; color: var(--para); line-height: 1.45; overflow-wrap: anywhere; }
.dempty {
padding: 26px 0; text-align: center;
font-size: 10px; color: var(--grey-2);
}
/* 护理建议 */
.card-heading { font-size: 13px; font-weight: 700; color: #000; margin-bottom: 8px; }
.care-item { font-size: 10px; color: var(--para); line-height: 1.6; margin-bottom: 8px; overflow-wrap: anywhere; }
.care-summary { font-size: 11px; font-weight: 500; color: #000; margin-top: 10px; }
/* 产品推荐:白底圆角卡,左侧文案 + 右侧单图(图高撑卡) */
.prod-title { font-size: 21px; font-weight: 700; color: var(--ink); line-height: 1.45; text-align: center; margin: 4px 0 12px; }
.card-product { padding: 0; overflow: hidden; }
.prod-card { display: flex; align-items: stretch; }
.prod-info {
flex: 1; min-width: 0;
padding: 18px 12px 18px 16px;
display: flex; flex-direction: column; justify-content: center; gap: 7px;
}
.prod-badge { font-size: 8px; font-weight: 500; letter-spacing: 3px; color: #B9B9B9; }
.prod-name { font-size: 13px; font-weight: 700; color: var(--ink); line-height: 1.4; overflow-wrap: anywhere; }
.prod-sub { font-size: 7.5px; font-weight: 500; color: var(--grey-3); }
.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: 168px; height: 225px;
object-fit: cover; flex-shrink: 0;
border-radius: 0 6px 6px 0;
}
.btn-more { margin: 16px auto 0; }
.report-disclaimer {
font-size: 7px; font-weight: 300; color: var(--grey-3);
line-height: 1.7; text-align: center;
margin: 5px 2px 9px; /* 灰底独立文字,位于描述卡与护理建议卡之间 */
}
/* ═══════════ 弹窗 ═══════════ */
.overlay {
position: absolute; inset: 0; z-index: 50;
display: none;
align-items: center; justify-content: center;
background: rgba(0,0,0,.5);
}
.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; } }
.popup {
position: relative;
width: 307px;
background: #fff; border-radius: 10px;
padding: 34px 26px 33px;
text-align: left;
}
.popup-close {
position: absolute; top: 12px; right: 12px;
width: 31px; height: 31px;
display: flex; align-items: center; justify-content: center;
}
.popup-close img { width: 17px; height: 17px; opacity: .8; }
.popup-title {
font-size: 23px; font-weight: 500; color: var(--ink);
line-height: 1.35; letter-spacing: .5px;
margin-bottom: 15px;
}
.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; }
.checkbox {
width: 11px; height: 11px; border-radius: 50%;
border: 1px solid #282828; background: #fff;
flex-shrink: 0; position: relative;
}
.checkbox.checked::after {
content: ""; position: absolute;
inset: 2px; border-radius: 50%;
background: var(--ink-2);
}
.privacy-text { font-size: 10px; color: var(--grey-2); }
.popup-btn { margin: 0 auto; }
/* 分析中 */
.overlay-dark { background: rgba(16,16,16,.82); }
.analyzing {
color: #fff; text-align: center;
font-size: 13px; letter-spacing: 1px;
display: flex; flex-direction: column; align-items: center; gap: 16px;
}
.spinner {
width: 34px; height: 34px;
border: 3px solid rgba(255,255,255,.25);
border-top-color: var(--yellow);
border-radius: 50%;
animation: spin .8s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
/* Toast */
.toast {
position: absolute; left: 50%; bottom: 15%;
transform: translateX(-50%) translateY(8px);
background: rgba(20,20,20,.85); color: #fff;
font-size: 12px;
padding: 9px 18px; border-radius: 18px;
opacity: 0; pointer-events: none;
transition: all .25s ease;
z-index: 99; white-space: nowrap;
}
.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; }