Files
mama-box/mama-box.css

2645 lines
189 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.
/* ============ 妈妈盒子 · H5 ============ */
:root{
--brand:#F4593C;
--brand-lt:#FF7C5C;
--brand-dk:#E04A2E;
/* 设计稿 hero 是「越往下越浅」的暖橘(顶 #FC7A60 → 底 #FAAE9B),
之前写成了越往下越深,气泡插画一放上去边缘就发暗,这里按取样值改回来 */
--grad:linear-gradient(150deg,#E85D41 0%,#EC6A4A 45%,#EF7B55 75%,#F0906C 100%);
--bg:#F8F3EF;
--card:#fff;
--ink:#2B2320;
--ink-2:#6B615B;
--muted:#72665E;
--line:#EFE7E1;
--green:#6E9A56;
--purple:#8B6FE0;
--blue:#4E85C7;
/* ---- 「妈妈」这个身份的颜色 ----
本批从蓝(--blue #4E85C7)换成绿。用户原话:
「妈妈回信那个背景色,现在不是浅蓝吗?可以变成偏绿色一点。」
⚠️ 为什么另起一个变量、而不是把 --blue 的值直接改成绿:
--blue 是**调色板**里的颜色名,把叫 blue 的变量填成绿色,
以后每个读到它的人都会被骗一次。--mom 是**语义**变量(谁的颜色),
换色时只动它,调色板保持诚实。
⚠️ #3F7551 的深浅不是随手挑的:它要当小字压在 .lt-msg.mom 的浅绿气泡上,
实测对比度**起点 5.18 / 终点 4.73**(气泡是渐变,两头都得量,AA 全过)。
顺带一提,被它换掉的那个蓝压在旧浅蓝底 #EFF5FC 上只有 3.48:1 ——
本来就不达标,这次换色顺手把可读性一起修了。
再往浅调会立刻掉到 3 点几,改这个值之前先量对比度。 */
--mom:#3F7551;
/* 妈妈气泡的浅绿底(起 → 止)。做成渐变是用户要的那点「底纹」。
⚠️ 用户此前**两次**否掉过图案型底纹(四种图案散铺 →「太碎了」;
等距波点 →「小碎点看着还是太乱」)。所以这里只能是纯渐变,
别再往上加点、加纹理、加 repeating-linear-gradient。
⚠️ 和 --mom 是一组:改任意一个都得回来重量对比度。
⚠️ 深的那头放在**下方**(渐变 160deg)。.lm-who 那行绿色小字坐在气泡顶部,
让它待在浅的一头;正文 var(--ink-2) 走到 #E8F2EB 仍有 5.26。
把两个值对调会把唯一一处贴边的对比度推到最紧。 */
--mom-bg-1:#F6FBF7;
--mom-bg-2:#E8F2EB;
/* 宝宝气泡的暖底(起 → 止),和上面那组对称。
⚠️ 起点 #FFF8F4 比换掉的那块纯色 #FFF4EF **更浅**,是有意的:
.lm-who 在宝宝这边是 var(--brand),压在 #FFF4EF 上只有 3.07:1,
本来就没过 AA(11.5px 不算大字)。渐变要是从原色往深里走,
那行字会掉到 2.9。现在浅头在上、字也在上,实测 3.15 —— 比原来还好一点。
别把这两个值对调。 */
--baby-bg-1:#FFF8F4;
--baby-bg-2:#FFEDE3;
--r-lg:24px;
--r-md:18px;
--r-sm:12px;
--sh:0 8px 24px rgba(188,120,96,.10);
--sh-lg:0 14px 40px rgba(180,110,86,.16);
/* ---- 工具区背后的「妈妈」大字水印 ----
首页工具区(.tool-dock)的白底上躺着两个很大、很粗、很淡的「妈妈」,
压在内容底下。它不是纹理,是一枚印章:下半截整个沉在工具卡背后,
露出来的上面六成刚好够人一眼认出「哦,是妈妈两个字」。
⚠️ **全站只有这一枚。** 上一稿三块白底各放一枚,被否了 ——
三枚同样的字顺着首页排下来,本身又成了一种重复。一枚是印章,三枚是底纹。
别再往 .today-line / .shelf 上加回去;尺寸怎么定的见 .wm 那条规则。
⚠️ 这块地方已经推翻过两版底纹,两版都是被同一句话否掉的:
第一批「脚丫/星/云/爱心 四种图案散铺」→「太碎了,页面看着特别乱。」
第二批「等距波点,列距 36px」 →「那个小碎点看着还是太乱。」
两版的毛病是同一个:**满屏都是小东西**。小东西一多,眼睛就要挨个去认,
认的过程就是分心的过程。第三版索性反过来 —— 整块地方只放**一个**大东西,
大到不必去认,扫一眼就过去了。
**想再往这三块白底上撒任何细碎图案之前,先回头读这三行。**
⚠️ 为什么是 DOM 里一个 <span>,不是接着用 background-image:
前两版走的是 --pat 内联 SVG 背景(这一批已整个删掉)。换成大字之后,
SVG 背景那条路要靠 <text> 渲染中文 —— 而背景图里的 SVG 拿不到页面的
字体栈,只能听系统的;万一那台机器在这个渲染上下文里没有中文字体,
结果是**静悄悄什么都不显示**,页面看着和没做过一样。
这块地方已经被否两次了,再来一次「用户什么都没看见」太贵。
DOM 里的普通文字走的是 body 那套字体栈,不会有这种失败方式。
⚠️ 颜色别顺手调深。12px 的灰字(--muted #A79C95)在纯白上对比度是 2.68,
压到 #F9F3EE 上掉到 2.44,再深一档 #F7EFE8 就只剩 2.36。
水印和前两版底纹不是一回事 —— 它是一大片实心色,**正压在**
.dock-note 那行小灰字底下,不像波点只是零星擦过去。
另一头是:大面积本来就比小图案显眼得多,所以颜色可以、也应该
比波点那版更淡。深浅只改这一个变量。 */
--wm-color:#F9F3EE;
--wm-size:132px;
--tabh:78px; /* 悬浮药丸 58px + 底部留白 14px + 余量 */
--ease:cubic-bezier(.32,.72,0,1);
/* 宝宝来信正文用的「圆体」——信是小朋友写的,圆一点更像那么回事。
产物必须是单文件离线可用,所以不引 @font-face,只挑各平台自带的圆体:
Yuanti SC = iOS/macOS 圆体-简,Hiragino Maru Gothic = 日文丸ゴ(含部分汉字),
YouYuan/幼圆 = Windows 幼圆;都没有时退回默认无衬线,不会变形。 */
--font-round:"Yuanti SC","YuanTi SC",YuanTi,"Hiragino Maru Gothic ProN","Hiragino Maru Gothic Pro",
YouYuan,"幼圆","STYuanti-SC-Regular","PingFang SC","Microsoft YaHei",sans-serif;
/* 宝宝写的字:挑系统自带里最「歪歪扭扭」的手写/卡通体。
翩翩体(HanziPen SC)是 iOS/macOS 自带的儿童手写体,笔画有粗细起伏、
字面故意不方正,最像小朋友写的;手札体(Hannotate)次之;
Windows 退到华文彩云/幼圆;一路兜底到圆体,绝不掉进宋体。
另外 kidText() 会给每个字加一点随机旋转,没这些字体的机器也歪得起来。 */
--font-kid:"HanziPen SC","翩翩体-简","HanziPen TC","Hannotate SC","手札体-简",
"Yuanti SC",YouYuan,"幼圆","Hiragino Maru Gothic ProN",
"PingFang SC","Microsoft YaHei",sans-serif;
}
*{margin:0;padding:0;box-sizing:border-box;-webkit-tap-highlight-color:transparent}
html,body{height:100%}
body{
font-family:-apple-system,BlinkMacSystemFont,"PingFang SC","Hiragino Sans GB","Microsoft YaHei","Segoe UI",Roboto,sans-serif;
background:#E8DED6;color:var(--ink);-webkit-font-smoothing:antialiased;
display:flex;align-items:center;justify-content:center;overflow:hidden;
}
button{font:inherit;color:inherit;background:none;border:0;cursor:pointer;text-align:left}
svg{fill:currentColor}
b{font-weight:700}
em{font-style:normal}
.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}
/* ---------- 无障碍 / 触控 / 动效(WK 审计补齐) ---------- */
html{scroll-behavior:smooth;-webkit-text-size-adjust:100%;text-size-adjust:100%}
body,button,a,input,textarea,select{-webkit-touch-callout:none;touch-action:manipulation}
button,a,input,textarea,select,summary,[tabindex]{transition:box-shadow .15s ease}
:focus{outline:none}
:focus-visible{outline:3px solid rgba(244,89,60,.55);outline-offset:2px;border-radius:6px}
.modal,.sheet,.search-layer,.scrim{overscroll-behavior:contain}
a.skip{position:absolute;left:-9999px;top:auto;z-index:2000;background:var(--brand);color:#fff;padding:10px 16px;border-radius:10px;font-weight:700}
a.skip:focus{left:16px;top:16px}
/* 橙渐变上的白字:加深对比 + 轻微投影提可读性 */
.page-hero,.me-top,.home-hero,.hero-main,.week-num,.hd-card,.lc-title,.lc-prev{text-shadow:0 1px 3px rgba(120,30,10,.28)}
@media (prefers-reduced-motion:reduce){
*,*::before,*::after{animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important;scroll-behavior:auto!important}
}
/* ---------- 手机外框 ---------- */
.phone{
position:relative;width:430px;height:932px;max-height:100vh;
background:var(--bg);overflow:hidden;
border-radius:46px;box-shadow:0 40px 90px rgba(120,70,50,.28),0 0 0 11px #16110f,0 0 0 13px #2e2724;
}
@media (max-width:520px),(max-height:940px){
.phone{width:100vw;height:100vh;height:100dvh;max-height:none;border-radius:0;box-shadow:none}
}
/* ---------- 屏幕容器 ---------- */
.screens{position:absolute;inset:0}
.screen{
position:absolute;inset:0;overflow-y:auto;overflow-x:hidden;
-webkit-overflow-scrolling:touch;background:var(--bg);
padding-bottom:calc(var(--tabh) + env(safe-area-inset-bottom) + 24px);
opacity:0;visibility:hidden;transform:scale(.985);
transition:opacity .28s ease,transform .32s var(--ease),visibility .28s;
}
.screen::-webkit-scrollbar{display:none}
.screen.is-active{opacity:1;visibility:visible;transform:none;z-index:2}
/* ============ 通用 ============ */
.icon-btn{width:32px;height:32px;border-radius:50%;display:grid;place-items:center;position:relative;color:#fff;transition:transform .18s,background .2s}
.icon-btn.glass{background:rgba(255,255,255,.2);backdrop-filter:blur(8px)}
.icon-btn.plain{background:none;font-size:20px}
.icon-btn:active{transform:scale(.9)}
/* .icon-btn.has-dot 已删:全站唯一的使用者是首页右上角那个消息圆钮,钮没了规则也没人用 */
.chev{font-style:normal;color:#C9BFB8;font-size:19px;line-height:1}
/* 模块主标题:前面加一根竖色条做修饰,并把上间距从 26 拉到 36,
让相邻模块之间有明显的呼吸感,用户一眼能看出这是几个独立模块 */
.sec-title{
font-size:17.5px;font-weight:800;letter-spacing:-.2px;margin:36px 0 14px;
display:flex;align-items:center;gap:8px;
}
.sec-title::before{
content:"";flex:0 0 auto;width:4px;height:18px;border-radius:3px;
background:var(--brand);box-shadow:0 2px 6px rgba(244,89,60,.35);
}
.sec-title.c-green::before{background:var(--green);box-shadow:0 2px 6px rgba(110,154,86,.35)}
.sec-title.c-purple::before{background:var(--purple);box-shadow:0 2px 6px rgba(139,111,224,.35)}
.sec-title.c-blue::before{background:var(--mom);box-shadow:0 2px 6px rgba(63,117,81,.35)}
/* 标题后面挂的「(必填)」。目前只有「我的 → 孕期信息」一处在用。
⚠️ 用 --brand 不用 --muted:这是一句**要求**不是附注,灰掉就没人填了。
⚠️ letter-spacing 显式归 0 —— .sec-title 自己带 -.2px,那是给 17.5px 大字收紧用的,
12px 的小字再收会把括号和字挤在一起。
⚠️ 不设 margin-left:靠 .sec-title 的 gap:8 拉开;.sec-more 那条 margin-left:auto
会把「编辑 ›」推到最右,中间的空档全归它。 */
.sec-req{flex:0 0 auto;font-size:12px;font-weight:600;color:var(--brand);letter-spacing:0}
.sec-more{margin-left:auto;font-size:13px;color:var(--muted);font-weight:500;white-space:nowrap}
.sec-more:active{opacity:.5}
/* 这里原来还有一条 .sec-sub(挂在 .sec-title 底下的一句灰色说明)。
全站只有首页那四处在用,首页换骨架之后四句说明各自搬进了模块内部
(.dock-note / .tl-h-note / .ab-foot i / .sh-note),这条就成了死规则,删掉。
⚠️ .sec-title 本身不能删 —— 「我的」页还有两处在用。 */
.ripple{position:relative;overflow:hidden;transition:transform .16s var(--ease)}
.ripple:active{transform:scale(.975)}
.rp{position:absolute;border-radius:50%;background:rgba(244,89,60,.16);transform:translate(-50%,-50%) scale(0);animation:rp .55s ease-out forwards;pointer-events:none}
@keyframes rp{to{transform:translate(-50%,-50%) scale(1);opacity:0}}
.chips-row{display:flex;gap:9px;margin-bottom:14px;overflow-x:auto;padding-bottom:2px}
.chips-row.wrap{flex-wrap:wrap;overflow:visible}
.chips-row::-webkit-scrollbar{display:none}
.chip{
flex:0 0 auto;padding:8px 17px;border-radius:20px;background:#fff;
font-size:14px;font-weight:600;color:var(--ink-2);border:1px solid var(--line);
transition:background .22s var(--ease),color .22s var(--ease),border-color .22s var(--ease);
}
.chip.is-active{background:var(--brand);color:#fff;border-color:var(--brand);box-shadow:0 5px 14px rgba(244,89,60,.3)}
.chip:active{transform:scale(.94)}
/* 「收藏」不是分类,用金色星号和左边那几个分类 chip 拉开一点区别 */
.chip-fav{color:#B8912C}
.chip-fav.is-active{color:#fff;background:var(--brand);border-color:var(--brand)}
/* ============ 首页 · Hero ============
全部坐标取自设计稿 ______v25(857px 宽 = 428.5pt @2x),CSS px = 设计 px ÷ 2:
品牌「妈妈盒子」 x33 y27..58 → left 16.5 top 13.5 ≈21px
(设计稿右上角还有个消息圆钮 x762.7..827.3 y20.7..86,已删,见 index.html)
进度环 圆心(202,188) 外径158 描边8 → left 61.5 top 54.5 79×79 描边4
「+3天」 x293..338 y187..205 → left 146.5 top 93.5 ≈13px
天数文案 x67 y297..314 → left 33.5 top 148.5 ≈15px
气泡宝宝插画 x424..810 y46..348 → right 23 top 23 193×151
hero 橙色区底缘:中部 ≈y420(210) 两侧 ≈y340(170) → 椭圆下沿 44px
⚠️ 上面这些是**设计稿**的数,不是现在的实现值。设计稿里没有 slogan,
加进去之后品牌字以下整列都下移了,实际纵向排布以 .home-hero 里那笔账为准。 */
.home-hero{
position:relative;color:#fff;overflow:hidden;background:var(--grad);
padding:env(safe-area-inset-top) 15px 0 16px;
/* 设计稿橙区 210px。但设计稿里气泡下缘是被白卡盖住的,
现在气泡补全成整圆(高 189.5),橙区必须够高才不会被 overflow:hidden 切掉。
── 现在是 250:slogan 挪到品牌字右边之后重算的 ──
这一竖列是一笔连着的账,橙区高度是账的**结果**,不是可以随手调的数字:
hero-top 10 + 32 → 42 品牌字 + slogan 并排的那一排
hero-main 28 + 79 → 149 孕周环
hero-days 17 + 52 → 218 天数玻璃卡
letter-card 橙区底 −16 → 234 「宝宝来信」卡顶
卡顶 234 − 卡底 218 = 16px 安全间隙(旧版 13,同一个量级)。
⚠️ 橙区**没能**跟着 slogan 一起缩回 236,是被气泡拖住的:
slogan 从「独占两行」变成「和品牌字同一行」,本来省出 32px。
但同一行意味着字底落到 y=34,而气泡圆顶原来就在 y=26 ——
字会直接压在气泡上。(量过 hero-art.png 的 alpha:那是实心硬边整圆,
α≥8 和 α≥220 的左缘只差 1px,没有可以压上去的透明薄边。)
于是气泡整体下移 26→40,下缘跟着从 215.5 走到 229.5,
而下缘必须留在「宝宝来信」卡顶之上(旧版留 4.5px,这里同样留 4.5):
229.5 + 4.5 = 234 = 卡顶 = 橙区高 − 16 → 橙区至少 250。
省下的 32px 里只有 2px 能还给橙区(252→250),
另外 30px 退回给中间两处空隙(hero-main 10→28、hero-days 8→17),
也就是回到没有 slogan 那一版的疏密感。
⚠️ 动 slogan 的字号/行高、动气泡的 top、动上面任一个 margin,
都要把这四行连同上面这段气泡账一起重算。 */
height:calc(250px + env(safe-area-inset-top));
border-radius:0 0 50% 50% / 0 0 44px 44px;
}
.hero-blobs span{position:absolute;border-radius:50%;filter:blur(30px);opacity:.5}
.hero-blobs span:nth-child(1){width:190px;height:190px;background:#FFB59C;top:-50px;left:-40px}
.hero-blobs span:nth-child(2){width:150px;height:150px;background:#FF9A7B;bottom:20px;right:-30px}
.hero-blobs span:nth-child(3){width:120px;height:120px;background:#FFD9C9;top:120px;left:40%;opacity:.32}
.petals{position:absolute;inset:0;pointer-events:none;overflow:hidden}
.petals i{position:absolute;width:8px;height:8px;background:rgba(255,255,255,.5);border-radius:60% 0 60% 0;animation:petal linear infinite}
@keyframes petal{0%{transform:translateY(-20px) rotate(0);opacity:0}15%{opacity:.8}100%{transform:translateY(340px) rotate(320deg);opacity:0}}
.hero-top{position:relative;z-index:3;display:flex;align-items:center;justify-content:space-between;height:32px;margin-top:10px}
.hero-brand{font-size:21px;font-weight:800;letter-spacing:.5px;line-height:1}
/* 三个二级页的左上角标题已删除,这一排只剩右侧按钮。
.hero-top 是 space-between 且与首页共用(首页左边还有「妈妈盒子」),
所以不能改父级对齐,只能让按钮组自己靠右 */
.hero-actions{display:flex;gap:8px;margin-left:auto}
/* 品牌字**右边**的 slogan(以前是它下面的两行,这一批挪上来了)。
⚠️ margin-right:auto 是这条规则的关键,不是随手加的间距。
父级 .hero-top 是 space-between 且四页共用,不能动它的对齐;
auto 边距把那一排的富余空间全吃掉,第二个孩子就不会被甩到最右边,
而是紧贴着品牌字排。删了它,slogan 会跑到屏幕右上角、压在气泡上。
(同一个路子见上面的 .hero-actions{margin-left:auto}。)
⚠️ nowrap 必须留着。CJK 可以在任意两字之间换行,一旦允许浏览器自己挑,
10 个字会折成两行 —— 而这一排是写死的 height:32,第二行会直接溢出,
并且横着压到气泡上(气泡只让开了一行的高度,见 .home-hero 那笔账)。
⚠️ 字号 13 / 行高 16 都写死成 px,不是倍数:橙区那笔竖列账和
「字底 y=34 < 气泡顶 y=40」这条净空都是按这两个数算的。
字体走 --font-kid(翩翩体 / 手札体那套手写体)—— 用户要「不要那种规规矩矩的
字体」。这里**故意复用**宝宝来信正文那个变量,而不是新写一套几乎一样的栈:
两套栈只要有一套漏了某个平台,就会出现「同一台机器上两处手写体长得不一样」,
而这种漂移没人会去对。手写体笔画细,白字压在橙色渐变上容易发虚,
所以 font-weight 提到 700、opacity 给到 .92(比旧版 .88 实一点)。 */
.hero-slogan{position:relative;z-index:3;margin:0 auto 0 9px;
font-family:var(--font-kid);
font-size:13px;line-height:16px;font-weight:700;letter-spacing:.3px;
opacity:.92;white-space:nowrap}
/* margin-top 曾被 slogan 压到 10,slogan 挪到品牌字右边后退回 28
(比原始的 24 还多 4,是气泡下移之后腾出来要填的空)。
见 .home-hero 里那笔竖列账 —— 这个数不是独立的,改它要连着改橙区高度。 */
.hero-main{position:relative;z-index:3;display:flex;align-items:flex-start;gap:6px;margin:28px 0 0 45.5px}
.week-ring{position:relative;width:79px;height:79px;flex:0 0 auto;border-radius:50%;background:rgba(255,255,255,.07)}
.ring-svg{width:100%;height:100%;transform:rotate(-90deg);display:block}
.ring-bg{fill:none;stroke:rgba(255,255,255,.26);stroke-width:6}
.ring-fg{fill:none;stroke:#fff;stroke-width:6;stroke-linecap:round;stroke-dasharray:1000;stroke-dashoffset:1000;transition:stroke-dashoffset 1.4s var(--ease)}
.week-num{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center}
.week-num b{font-size:38px;font-weight:800;line-height:1;letter-spacing:-1px}
.week-num span{font-size:12px;opacity:.92;margin-top:3px}
.week-plus{font-size:13px;font-weight:600;opacity:.95;margin-top:37px}
/* 首页 hero 主插画:取自设计稿像素。设计稿里气泡下缘被白卡遮住,
已按拟合圆(设计坐标 圆心 620.1,246.3 半径 187.4)补全为整圆,
378px 图 → 189.5 CSS px。
⚠️ top 由 26 改成 40,是被 slogan 逼的,不是调构图。
slogan 挪到品牌字右边之后,字底落在 y=34;气泡圆顶原来就在 y=26,
白字会直接盖在气泡上。解过这张 PNG 的 alpha 通道确认过:
这幅画就是**实心整圆、边缘是硬的**(α≥8 与 α≥220 的左缘只差 1px),
没有可以让字压上去的透明薄边,所以只能整幅往下让。
下移之后圆心 (屏宽−25−94.75, 134.75)、下缘 40+189.5=229.5,
必须留在「宝宝来信」卡顶(234)之上 —— 这就是橙区高必须 ≥250 的来源。
改 top 前先读 .home-hero 里那笔竖列账。 */
/* 定位和浮动都挂在外壳上,img 只负责铺满 —— 这样嘴边的泡泡是
相对气泡本身定位的,会跟着整幅画一起上下浮,而不是自己飘在原地 */
.hero-baby{position:absolute;z-index:2;top:calc(40px + env(safe-area-inset-top));right:25px;width:189.5px;
pointer-events:none;user-select:none;animation:float 5.5s ease-in-out infinite}
.hero-art{width:100%;height:auto;display:block}
@keyframes float{0%,100%{transform:translateY(0)}50%{transform:translateY(-9px)}}
/* 嘴边的小气泡。起点定在图里嘴的位置(约 46%/52%),往左上飘 ——
宝宝头朝右上侧躺,左上正好是「水面」方向。
终点 translate(-16px,-46px) 距圆心约 48px,半径 94.75,绝不会飘出气泡壁。 */
.hb-bub{position:absolute;left:46%;top:52%;width:7px;height:7px;border-radius:50%;
background:rgba(255,255,255,.5);box-shadow:inset 0 0 0 1px rgba(255,255,255,.8);
opacity:0;animation:bub 4.2s ease-in-out infinite}
@keyframes bub{
0%{transform:translate(0,0) scale(.45);opacity:0}
14%{opacity:.9}
70%{opacity:.55}
100%{transform:translate(-16px,-46px) scale(1.15);opacity:0}
}
/* 三颗错开大小、时长和出发点,才不像在同一个节拍上打点 */
.hb-bub.b2{left:42.5%;top:54.5%;width:5px;height:5px;animation-duration:5.1s;animation-delay:1.4s}
.hb-bub.b3{left:48.5%;top:50.5%;width:9px;height:9px;animation-duration:4.7s;animation-delay:2.7s}
/* 系统开了「减少动态效果」就全部停下:这类装饰性循环动画是前庭不适的常见诱因 */
@media (prefers-reduced-motion:reduce){
.hero-baby{animation:none}
.hb-bub{animation:none;opacity:0}
}
/* 天数文案:一张毛玻璃小卡,不再是裸字浮在橙底上。
⚠️ 这块的高度是有硬预算的,改字号前先算一遍:
橙区固定 250px,而「宝宝来信」卡 margin-top:-16 → 卡顶落在 y=234,
且它横跨 x 75..324,和这张小卡(x 17.5..~158)左半边是重叠的。
往上推:hero-top 42 + hero-main(28+79) = 149,本块 margin-top 17 → 顶在 166。
所以卡身高度必须 ≤ 68px 才留得出安全间隙。
行高全部写死成 px 而不是倍数,就是为了让这笔账可算、不随字体变。
当前:padding 7+8 + who 16 + gap 1 + tail 20 = 52 → 底边 218,离 234 还有 16px。
(margin-top 曾为 slogan 压到 8,slogan 挪走后退回 17,见 .home-hero 里的竖列账) */
.hero-days{position:relative;z-index:3;margin:17px 0 0;padding-left:17.5px;max-width:196px}
.hd-card{display:inline-flex;flex-direction:column;gap:1px;padding:7px 13px 8px;border-radius:16px;
background:rgba(255,255,255,.17);border:1px solid rgba(255,255,255,.3);
box-shadow:0 6px 18px rgba(163,60,38,.13);
-webkit-backdrop-filter:blur(12px) saturate(1.25);backdrop-filter:blur(12px) saturate(1.25)}
/* 不支持 backdrop-filter 的机器上,半透明白会发灰、字浮不起来,
把底色调实一点把对比度补回来 */
@supports not ((backdrop-filter:blur(2px)) or (-webkit-backdrop-filter:blur(2px))){
.hd-card{background:rgba(255,255,255,.26)}
}
/* gap 从 5px 改成 0,那 5px 挪到 .hd-heart 的 margin-right 上:
现在这一行是「♥ 小名 + 在妈妈肚子里」,小名和后半句必须**贴着**,
留着 gap:5px 会在中间豁开一道缝,读起来像两个词。 */
.hd-who{display:flex;align-items:center;gap:0;font-size:13px;font-weight:700;opacity:.95;line-height:16px;min-width:0}
/* ⚠️ 小名那格是 maxlength="12"。12 字 ×14px = 168px,加上 ♥ 和「在妈妈肚子里」
远超 .hero-days 的 max-width:196px。这一行是 flex(默认不换行),
不给截断就会直接溢出到旁边的插画上。
截断的优先级放在**小名**上而不是后半句:小名被截成「小汤圆宝宝…」还认得出,
后半句被截掉则整句话不成话。 */
.hd-who b{font-size:14px;font-weight:800;letter-spacing:.2px;
min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* 后半句不参与压缩,永远完整 */
.hd-where{flex:0 0 auto}
.hd-heart{flex:0 0 auto;display:block;margin-right:5px}
/* 整体 nowrap:这张卡就是按这一行的宽度定的,断行会把数字甩下去 */
.hd-tail{white-space:nowrap;font-size:12.5px;opacity:.88;line-height:20px}
.hd-tail b{font-size:17px;font-weight:800;letter-spacing:-.3px;opacity:1;margin:0 1px}
/* 「宝宝来信」卡:设计稿 x150..648 → left 75 · 宽 249 · 高 80。
气泡补全成整圆后下缘到 215.5,卡顶必须 ≥215.5 才不会再压住宝宝,
故上移量由 34.5 改为 16(橙区 252 − 16 = 卡顶 236)。
橙区因 slogan 从 236 加到 252 之后,卡顶跟着从 220 落到 236,
离气泡下缘 215.5 更远了,这一条只会更安全,不用动。 */
.letter-card{
position:relative;z-index:6;display:flex;align-items:center;gap:9px;
width:249px;height:80px;margin:-16px 0 0 75px;
background:rgba(255,255,255,.92);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);
border-radius:20px;padding:0 12px 0 13.5px;box-shadow:0 10px 26px rgba(150,60,40,.16);
}
/* 没有未读、也没有新回信时整张收起来(pushLatestToHome 挂的类)。
这个库里没有全局 .hide,都是逐个元素声明的,跟 .lthread.hide 那些一个路子。 */
.letter-card.hide{display:none}
/* 信封外面包一层,只为了给「有新回信」的小红点一个定位参照 */
.lc-envwrap{position:relative;flex:0 0 auto;line-height:0}
.lc-env{width:64px;height:auto;display:block;filter:drop-shadow(0 4px 8px rgba(200,90,70,.22))}
.letter-card.has-new .lc-envwrap::after{
content:"";position:absolute;top:2px;right:-1px;width:10px;height:10px;border-radius:50%;
background:var(--brand);box-shadow:0 0 0 2.5px #fff;animation:newPulse 1.6s var(--ease) infinite;
}
@keyframes newPulse{0%,100%{transform:scale(1)}50%{transform:scale(1.28)}}
/* 卡内正文固定三行:标题 / 摘要 / 去看看。overflow:hidden 兜底,
哪行文案再长也不会挤出第四行 */
.lc-body{min-width:0;flex:1;overflow:hidden}
.lc-title{font-size:16px;font-weight:800;color:var(--ink);line-height:1.2;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.lc-desc{font-size:11.5px;color:var(--ink-2);margin:4px 0 5px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* 尖括号必须跟文字同行:宝宝回信后这行会变长,之前它会被挤到下一行单独站着 */
.lc-link{display:flex;align-items:center;gap:3px;font-size:12.5px;font-weight:700;color:var(--brand);white-space:nowrap}
.lc-link span{min-width:0;overflow:hidden;text-overflow:ellipsis}
.lc-link i{flex:0 0 auto;font-style:normal}
/* ============ 首页 · body ============ */
.home-body{padding:0 26px}
/* ⚠️ 这两条原来挂在 .sec-title 上。首页不再有 .sec-title 了,选择器必须跟着换到
新的第一块 .tool-dock —— 忘了换不会报错,只会静默失效(来信卡收起来时
头图底下那 64px 的洞补不回来),所以 smoke 里专门钉了一条。 */
.home-body>.tool-dock:first-child{margin-top:21px}
/* 来信卡收起来时,它原本占的 64px 流高(margin-top:-16 + height:80)没了,
只剩这 21px 顶着一大片橙色头图,太挤。补到 30px。
走类名而不是 :has(),老一点的移动端浏览器上 :has() 不一定有。 */
.home-body.no-letter>.tool-dock:first-child{margin-top:30px}
.page-body{padding:0 18px;margin-top:-46px;position:relative;z-index:5}
.page-body.no-top{margin-top:8px}
/* ============ 首页三大块的公共外壳 ============ */
/* 上一稿这三块各造了一种底:米色托盘 / 没有容器 / 白气泡 + 糖果卡,
四种表面语言堆在一屏里,结果就是「不规矩」。
这一稿收敛成一条规矩:**结构可以各不相同,表面必须一模一样**。
三块统一是「白色通栏色块」—— 左右各吃掉 .home-body 的 26px 内边距顶到屏幕边缘,
块与块之间露出米色页底当缝。结构上的差别(宫格 / 时间线 / 横向卡带)一点没动,
变化留在骨架里,表面不掺和,这样才是既不单调又不乱。
颜色只允许出现在小构件上:工具图标块、书封、时间线圆点、AI 气泡、各种胶囊。
⚠️ padding 左右必须正好等于 26,抵回负外边距,里面的内容才和页面其余部分对齐 ——
对齐的是内容,出血的是底色,这两件事要分开。
⚠️ 三块的圆角、底色、左右内边距改成在这里一处定义。以后要调,改这一条,
别再回到各自的规则里单独写,那正是上一稿乱掉的原因。 */
.tool-dock,.today-line,.shelf{
margin:0 -26px;padding:16px 26px 18px;border-radius:26px;
background-color:#fff;
/* ⚠️ 这两条是一对,缺一不可,而且必须一起看:
它们让每一块自己成为一个**层叠上下文**,好让块里那枚 .wm(大字水印)
用 z-index:-1 停在「白底之上、内容之下」这一层。
position:relative 单写不够 —— z-index 是 auto 时不建层叠上下文,
-1 就会一路掉到这块白底的**后面**去,水印整个消失。
所以这里的 z-index:0 看着像句废话,其实是水印能不能显示的开关。 */
position:relative;z-index:0;
}
/* 块间距。给后两块,第一块的上间距另有规则(来信卡收放要用)。
⚠️ 这是**块与块之间**的缝。某一块自己内部觉得挤,要调的是那块的 padding,
不是这里 —— 改这里会把三块的关系一起改掉。 */
.today-line,.shelf{margin-top:16px}
/* ===== 后两块的暖色渐变底(本批新增)=====
用户原话:「在那个宝宝的今日脚印那块,稍微给背景它加一点渐变」
「本周孕育指南的也是……但不用加那么重」
—— 所以两块同一套形状、不同强度;.tool-dock **保持纯白**,用户没提,
而且它背后压着那枚「妈妈」大字水印,底色一动水印的浓淡就得重调。
⚠️⚠️ 角度必须是 90deg,而且左边 32% 必须保持**纯白**。这不是审美选择,
是被「今日脚印」那条时间线的实现方式逼出来的硬约束:
时间线是一条 1.5px 的竖线(.today-line::before),线上那些圆点靠
**一圈不透明的外壳底色**把线「咬断」,一共四处,全部写死 #fff:
.tl-h-dot box-shadow 0 0 0 3.5px #fff … 0 0 0 8px #fff(还负责盖住线头)
.st-dot box-shadow 0 0 0 3px #fff
.ab-dot box-shadow 0 0 0 3px #fff
.ai-bubble::after background:#fff(把最后一个点以下那截线擦掉)
这四处全都钉在竖线那一列(--tl-x ≈ 63.5px,算上光晕最远到 x≈76px)。
底色一旦在那一列变成暖色,这四处就成了四块**白斑**压在暖底上 ——
而且是「看着像渲染没刷新」的那种,不是「颜色不好看」。
90deg 的渐变颜色**只跟 x 有关、与 y 和卡高无关**,才给得出一句
可验证的保证:x < 32% 之内恒为 #fff。斜着走(比如 150deg)就不行 ——
同一列的顶部和底部会落在渐变的不同位置,卡一变高约束就悄悄破了。
余量账(最窄的 320 屏,外壳出血后宽 336px):
白区 = 336 × 32% = 107.5px,光晕最远 76px,余 31.5px。
屏越宽白区越宽(390 屏 130px),320 是最紧的一档,够。
⚠️ 要改角度或起止位置,先把上面四处 #fff 一起解决掉,否则一定出白斑。
⚠️ .shelf 那块**没有**这个约束(它的书卡各自带不透明底色,叠压头像描的边
走 --sb-bg,都不依赖外壳)。这里仍写成同样的 32% 白区,纯粹是为了让上下
相邻的两块「渐变从同一个位置开始」—— 起点不齐会看着像没对齐的意外。
别把它当成 .shelf 自己的技术要求。
⚠️ 用 background-image 而不是 background 简写:简写会把上面那条公共规则里的
background-color:#fff 一起清掉,渐变要是哪天被覆盖或不支持,就直接透出
页面的米色底了。分开写,白底始终在最下面兜着。 */
.today-line{
background-image:
/* 右上角一点柔光,让它不只是「从左到右变暖」。放在 x=96%,
离竖线那一列(x≈63.5px)远得很,不参与上面那笔账。 */
radial-gradient(130px 90px at 96% 3%, rgba(255,206,178,.30), rgba(255,206,178,0) 72%),
linear-gradient(90deg,#fff 0%,#fff 32%,#FFF9F4 64%,#FFF2E7 100%);
}
/* 「不用加那么重」:同样的 90deg、同样的 32% 白区,终点浅一半
(#FFF8F3 之于 #FFF2E7),也不加那道柔光。 */
.shelf{
background-image:linear-gradient(90deg,#fff 0%,#fff 32%,#FFFCFA 64%,#FFF8F3 100%);
}
/* 大字水印「妈妈」。**全站只有一枚,在工具区(.tool-dock)。**
⚠️ 上一稿是三块各放一枚,被否了:「今日成长脚印和本周孕育指南那两个去掉,
只保留工具那个。」三枚同样的字顺着首页排下来,本身又成了一种重复 ——
一枚是印章,三枚是底纹,而底纹这个方向已经翻过两次车了。
**别再往 .today-line / .shelf 上加回去。**
⚠️ 它和标题是一对:标题写着「小汤圆的孕育工具」(跟着小名走),
水印压在后面写着「妈妈」。工具是给宝宝用的,动手的是妈妈。
所以水印这两个字**不跟小名变**,它本来就不是在说宝宝。
尺寸和位置是按「露出多少」倒推的,不是随手填的:
· 工具区从块顶到宫格之间只有约 75px 是透空的(16 内边距 + 抬头行 + 说明行),
往下整片都被不透明的工具卡铺满(.mini-card 是实心渐变底)。
· 132px 的字,字面高约 113px,于是**露出上面六成,下面四成沉到卡片背后** ——
「可以被数胎动、孕期体重那些卡挡住,只保留露出部分就行」,说的就是这个。
· 再往大调会好看,但露出的比例会跟着掉;掉到一半以下时,「妈妈」两个字
只剩上半截,就认不出来了。调 --wm-size 之前先想清楚这笔交换。
⚠️ letter-spacing 用 em 不用 px:跟着字号缩放,改 --wm-size 时不用再回来改这行。
负值是要的 —— 两个字挨紧才像一枚印章,松开就散成两个字了。
⚠️ pointer-events:none:工具卡整张都是可点区域,水印绝不能吃掉点击。
z-index:-1 本来也轮不到它接事件,但这行是白纸黑字的保险。 */
.wm{
position:absolute;top:0;right:16px;z-index:-1;
font-size:var(--wm-size);font-weight:900;line-height:1;letter-spacing:-.04em;
color:var(--wm-color);
pointer-events:none;user-select:none;-webkit-user-select:none;
}
/* 水印比抬头那条透空带高,得有人把它裁在圆角里,否则字面顶部会从块的
上边缘冒出去,糊在头图底下。裁在这块自己身上就够 —— 只有它有水印。
(宫格四周有 26px 内边距,工具卡的投影够不到边缘,裁掉不了任何东西。) */
.tool-dock{overflow:hidden}
/* 三块的抬头用同一套字号和间距。上一稿我给三个「更多」入口做了三种样子
(白胶囊 / 灰字 / 灰字),毫无理由地不一致 —— 同一层级的东西就该长一样。
⚠️ .dock-all 原来是白底胶囊,托盘变白之后白底压白底会直接消失,
顺手一起并进这条统一规则里了。 */
.dock-title,.tl-h-title,.sh-title{font-size:16.5px;font-weight:800;letter-spacing:-.2px}
.dock-all,.tl-h-all,.sh-all{
margin-left:auto;flex:0 0 auto;white-space:nowrap;
font-size:12px;font-weight:600;color:var(--muted);
}
.dock-all:active,.tl-h-all:active,.sh-all:active{opacity:.5}
/* 三块的说明也用同一套。上一稿 .dock-note 是半透明白 chip、另两条是灰字,
同样是没道理的三选一 —— chip 那个样子是为了在深色托盘上读得清,
底一变白,chip 的白底就没意义了,统一回灰字最干净。 */
.dock-note,.sh-note,.tl-h-note{font-size:12px;color:var(--muted);line-height:1.5}
/* ============ 模块一 · 工具 ============ */
.dock-head{display:flex;align-items:center;gap:8px;margin-bottom:7px}
/* 说明行仍是 inline-flex:前面那枚小图标要和文字同行居中,
但不能撑满整行(撑满的话图标会被推到左边很远)。 */
.dock-note{display:inline-flex;align-items:center;gap:5px;margin-bottom:11px}
.dock-note svg{flex:0 0 auto;color:var(--brand);opacity:.8}
/* Bento */
/* 卡进了白底容器,各自那圈阴影就没用了 —— 白底上叠浅阴影只会显脏,
五份糊在一起反而看不出是五张卡。圆角同步收一档,配小一号的画幅。 */
.tool-dock .mini-card{box-shadow:none;border-radius:18px}
/* 第三列从 .96fr 提到 1fr(第一列 1.3 补回来):这一列底下新添了「更多工具」,
四个字加一个 ›,按老比例在 320px 视口下这列只有 71px,减掉内边距剩 55px,
正好差一点点会顶出去。 */
.bento{display:grid;grid-template-columns:1.3fr 1fr 1fr;gap:10px}
.bento-col{display:flex;flex-direction:column;gap:10px}
.mini-card{position:relative;border-radius:20px;padding:14px;overflow:hidden;box-shadow:var(--sh)}
.mc-title{font-size:19px;font-weight:800;letter-spacing:-.3px}
.mc-title.sm{font-size:15px}
.mc-sub{font-size:12px;color:var(--ink-2);margin-top:3px}
.mc-btn{display:inline-block;margin-top:12px;background:var(--brand);color:#fff;font-size:13px;font-weight:700;padding:6px 15px;border-radius:14px;box-shadow:0 4px 10px rgba(244,89,60,.3)}
.mc-art{position:absolute;width:52px;height:auto;right:6px;bottom:2px;filter:drop-shadow(0 5px 10px rgba(160,90,70,.18))}
.mc-art.big{position:static;width:74px;display:block;margin:2px auto 6px}
.mc-big{font-size:22px;font-weight:800;margin-top:2px}
.mc-big small{font-size:13px;font-weight:700}
/* 四张工具卡的渐变跨度收窄到原来的一半左右:
渐变落差太大时整块颜色被拉花,模块边界反而看不清 */
.mini-card.kick{background:linear-gradient(150deg,#FFE2D6,#FFD7CA);height:158px}
.mini-card.kick .mc-art{width:74px;right:2px;bottom:0}
/* 胎儿估重(这张卡原来是「胎心记录」,本批换了内容;类名 .heart → .fweight,
下面那条 .mc-art 跟着改。除类名外,这里的数值一个都没动)。
版式是「名字 + 状态两行叠着,插画退到右下角」,和 .cal / .curve 同一套 ——
这一列窄,横排(图标 + 名字 + 右边状态)塞不下,标题会被挤成竖排。
底色浅蓝不是随手挑的:正上方「数胎动」是粉的,两块粉挨着分不出是两张卡;
插画又是珊瑚红的(原来是心,现在是表盘指针),压在粉底上糊成一片,浅蓝才托得住。
内边距上下收到 12(别处是 14):这一格只有 64px 高,
14 + 名字 20 + 间距 3 + 状态 16 + 14 = 67,会超三个像素。 */
.mini-card.fweight{background:linear-gradient(150deg,#E7EFFA,#DDE8F8);height:64px;padding:12px 14px}
.mini-card.weight{background:linear-gradient(160deg,#FFE9E1,#FFDDD3);display:flex;flex-direction:column;justify-content:center;text-align:center}
.mini-card.weight .mc-title{font-size:16px}
.mini-card.cal{background:linear-gradient(150deg,#ECF3DE,#E4F0D4);height:88px}
/* 生长曲线原来是 flex:1,把第三列剩下的高度全吃了(约 134px),
可它的内容和上面的孕检日历一样多,白撑着。压到 80px,
省下的 54px 给下面的「更多工具」。
⚠️ 这三张卡的高度是硬凑的:88 + 10 + 80 + 10 + 44 = 232,
左边那一列是 158 + 10 + 64 = 232,两列必须一样高,中间那张「孕期体重」
才不会比别人矮一截。动其中任何一个数,另一列要跟着补回来。
80 是这套竖排的下限:内边距 28 + 名字 20 + 间距 3 + 状态 16 = 67,再矮就贴边了。 */
.mini-card.curve{background:linear-gradient(150deg,#ECE5FA,#E5DCF9);height:80px}
.mini-card.cal .mc-art,.mini-card.curve .mc-art{width:42px;right:6px;bottom:4px}
/* 这张卡的插画得单独摆一份,不能跟着上面 .cal / .curve 那条走。
① 尺寸 38 而不是 42:日历、曲线那两张 png 画面里自带内部留白,
这里的表盘是满幅的圆,同样宽度看着会比它俩大一圈。
② 位置 right:11 / bottom:10:圆的最宽处顶在画布边缘,.mini-card 又是
overflow:hidden,按别处的 6 / 4 摆会被圆角啃掉一小块。这两个数当初是
为那颗同样满幅的心量出来的,换成圆表盘照样成立,所以原样留着。
③ 滤镜只留 drop-shadow:原来前面还挂着 saturate(.82) brightness(1.04),
那是专为 q-heart.png 那颗高饱和珊瑚红压色的 —— 它比 app-* 那几张低饱和的
奶油粉 / 淡紫 / 浅绿都要「喊」。现在这枚表盘是自己画的 SVG,配色一开始
就落在同一档里,不需要再压;继续挂着反而会把指针洗淡。
投影值照抄 .mc-art 的原值不动:打光要跟邻居一致。 */
.mini-card.fweight .mc-art{width:38px;right:11px;bottom:10px;
filter:drop-shadow(0 5px 10px rgba(160,90,70,.18))}
/* 「更多工具」:通往「记一下」弹层里那十几个工具。
故意不给糖果色 —— 它不是第六个工具,是个出口。素底 + 一行小字,
扫过去不会被当成工具卡数进去。
高度 44 是按手指来的(再矮一点更好看,但这是这一屏唯一通往其余工具的门,
点不准的代价太大),不是按内容 —— 一行 11.5px 的字只要 16px。 */
/* ⚠️ 底色必须跟着容器走:容器是白的就用灰米(现在这样),容器要是改成米色底,
这里得反过来提亮,否则浅底压浅底整张卡会直接消失。 */
.mini-card.more{background:#EFE7DA;height:44px;display:flex;align-items:center;justify-content:center;gap:3px;padding:0 6px}
.mc-more-t{font-size:11.5px;font-weight:700;color:#8A7A66;white-space:nowrap}
.mini-card.more .chev{font-style:normal;font-size:13px;font-weight:700;color:#A3937F;line-height:1}
/* 模块二 · 今天的动线 */
/* 上一版这里是两个独立模块:AI 小结一张渐变卡(左边 4px 色条 + 40px 大引号),
下面「今日成长脚印」一张白卡装的时间轴。两块都是「米色页面上摆一张卡」,
和上面的工具、下面的指南是同一种容器语言,四段叠起来才显得那么直筒。
这一版合成一条线,容器语言换成「没有容器,只有一条线」:
.tl-head 起点节点(今天 · 大圆点 · 今日成长脚印 · 查看全部)
.steps 四条记录(白卡撤了,只剩内容本身)
.ai-bubble 收尾节点(AI 徽标代替时间 · 圆点 · 一只气泡)
AI 那句话总结的就是它正上方那四条记录,本来就该挨着,不该隔着一整套抬头分开摆。
竖线的画法也换了。原来是每个 .step::before 各画一小段,
靠 :first-child{top:50%} / :last-child{bottom:50%} 拼出来 ——
那样只能在 .steps 内部拼,跨不到 .tl-head 和 .ai-bubble 上。
三条规则全删,改成这里一条从头贯到尾。
⚠️ 底色、圆角、左右内边距在上面的「公共外壳」里统一定义,这里只管线和节点。
——「咬线」的那几圈外圈颜色必须等于这块的底色(现在是 #fff),
跟着外壳走,外壳改底色这里就得一起改,见下面几处标注。 */
/* 这一块的几何全部由下面几个变量推出来,别再往下面的规则里写死像素。
三行(.tl-head / .step / .ai-bubble)必须是同构的三列:
[ 时间列 --tl-time ][ --tl-gap ][ 圆点 9px ][ --tl-gap ][ 正文 ]
只要有一列的宽度或间距在某一行上写得不一样,那一行的圆点就会离开竖线。
⚠️ 之前就是这么错的:.st-time 写的是 width:42px(可被 flex 压缩),
而 .tl-h-when / .ab-badge 写的是 flex:0 0 42px(不可压缩)。
320px 视口下 .step 一行内容总宽约 311 > 可用 264,flex 就去压唯一压得动的时间列,
把它压到约 36px,圆点跟着左移,看上去就是「时间贴到竖线上」。
现在三处统一 flex:0 0 var(--tl-time),谁都不许缩。 */
.today-line{
position:relative;
--tl-time:44px; /* 时间列宽:够放 "13:00" 还余一点 */
--tl-gap:13px; /* 三列之间的间距。从 10 提到 13,给时间和竖线之间让出空气 */
--tl-pad:2px; /* 每行自身的左右内边距 */
--tl-top:22px; /* 这一块的上留白 */
--tl-bot:24px; /* 这一块的下留白 */
/* 竖线中心 = 左内边距 + 时间列 + 间距 + 圆点半径(4.5) */
--tl-x:calc(var(--tl-pad) + var(--tl-time) + var(--tl-gap) + 4.5px);
/* 正文左缘 = 竖线中心 + 圆点半径 + 间距 */
--tl-text:calc(var(--tl-x) + 4.5px + var(--tl-gap));
padding-top:var(--tl-top);padding-bottom:var(--tl-bot);
}
/* 一整条,不掐头也不去尾:上端由 .tl-h-dot 的不透明外圈盖掉,
下端由 .ai-bubble::after 那块底色橡皮擦掉。
这两处都只依赖各自圆点的位置(我自己定死的),不依赖气泡里文字排了几行 ——
要是改成算 bottom 的像素值,文案一变或字体一换线就会拖长一截或者短一截。
⚠️ top/bottom 必须正好等于这一块的上下留白,**不能写 0**。
绝对定位是相对 padding box 算的,写 0 会让线从留白最顶端画起,
而 .tl-h-dot 的外圈只盖得住自己周围 12.5px —— 上下各会露出一截悬空的线头。
用同一组变量绑死,以后调留白线会跟着走。 */
.today-line::before{content:"";position:absolute;left:calc(var(--tl-x) - .75px);
top:var(--tl-top);bottom:var(--tl-bot);width:1.5px;background:#F0E2D9}
/* 起点节点。三列宽度全部走变量,圆点才落得进竖线。 */
.tl-head{display:flex;align-items:center;gap:var(--tl-gap);padding:0 var(--tl-pad);position:relative}
.tl-h-when{flex:0 0 var(--tl-time);font-size:13px;font-weight:700;color:var(--brand)}
/* 起点点得比途中的记录点更醒目,但**盒子仍然是 9px** —— 大小全靠 box-shadow 往外扩,
一改 width 就会把圆心推离 --tl-x,线和点就错位了。
三层 shadow 由外到内:底色 → 品牌色细圈 → 底色。⚠️ 最外层必须是不透明的容器底色(#fff),
它同时负责把 .tl-head 上方那截线头盖掉;写成半透明或写错色,线头就会露出来。 */
.tl-h-dot{flex:0 0 auto;width:9px;height:9px;border-radius:50%;background:var(--brand);
box-shadow:0 0 0 3.5px #fff,0 0 0 5.5px rgba(244,89,60,.30),0 0 0 8px #fff}
/* ⚠️ 两个标题现在都含用户填的小名,而小名输入框是 maxlength="12"。
12 个全角字 + 「的孕育工具」= 17 字 ≈ 277px,工具区那行留给标题的位置只有约
310px —— 塞得下,但只剩 30px 余量,字体或按钮文案一动就会把「所有工具 ›」挤出去。
与其赌那 30px,不如让标题自己先省略号收尾。.tl-h-title 本来就有这套,
.dock-title 是这次跟着一起挂上来的。 */
.tl-h-title,.dock-title{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* 说明缩进到 73px,和下面 .st-txt 的左边缘对齐(2+42+10+9+10),
看着像是挂在这条线右侧的一句注脚,不是又一行贴边灰字。 */
.tl-h-note{margin:5px 0 2px var(--tl-text)}
/* 它自己不再是一张卡了(背景/阴影/圆角/左右内边距都归外壳管),
.step 的左边缘因此和 .tl-head / .ai-bubble 对齐,三块共用同一条线。 */
.steps{padding:2px 0;cursor:pointer}
.step{display:flex;align-items:center;gap:var(--tl-gap);padding:11px var(--tl-pad);position:relative;font-size:14.5px}
/* ⚠️ 必须是 flex:0 0(不可压缩),只写 width 会被 flex 压缩 —— 见本块开头那段说明。 */
.st-time{flex:0 0 var(--tl-time);font-size:13px;color:var(--muted);font-variant-numeric:tabular-nums}
/* ⚠️ 外圈用来「咬断」竖线,所以它必须是**圆点背后那块底**的颜色 = 外壳的 #fff。 */
.st-dot{width:9px;height:9px;border-radius:50%;background:var(--brand);z-index:2;box-shadow:0 0 0 3px #fff}
.st-txt{font-weight:600;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* 数据来源标签:做成浅色小胶囊,一眼看出这条记录是哪个孕期工具同步过来的。
⚠️ 底色要和它贴着的那块底拉开差距 —— 而这一版**外壳底真的变了**,
上一版注释里假设的那件事发生了,所以这条跟着压深了一档:
胶囊 #F1E8E1 → #EADDD3,字 #9A8A7E → #8D7C6F
账(胶囊底 vs 它背后的外壳底,比值越大边界越清楚):
旧:#F1E8E1 压在纯白上 = 1.209
本批不改的话:#F1E8E1 压在 #FFF2E7 上 = 1.077 ← 几乎看不见,只剩一行浮着的字
改完:#EADDD3 压在 #FFF2E7 上 = 1.211 ← 回到旧版的清晰度
⚠️ 它 margin-left:auto 贴在行的最右端,正好落在渐变**最深的那一头**,
所以必须按终点色 #FFF2E7 算,按平均色算会算松。
⚠️ 字色一起压深是为了补回胶囊变深吃掉的那点对比(2.50 → 3.01)。
这两个值是一对,别只动一个。 */
.st-from{margin-left:auto;flex:0 0 auto;white-space:nowrap;font-size:11.5px;color:#8D7C6F;
background:#EADDD3;border-radius:9px;padding:3px 8px;line-height:1.35}
/* 收尾节点。三个直接子元素和 .step 同构(42 的左格 + 9 的点 + 剩下的),
圆点才落在同一条线上。align-items 用 flex-start 而不是 center ——
气泡是多行的,居中的话点会飘到气泡中腰,看着不像是从这个点说出来的。
徽标和点的 margin-top 是配着气泡第一行(标签行)算的:
气泡上内边距 11 + 标签行半高 7 = 18,加上 .ai-bubble 自己的 10 上内边距 = 28。 */
.ai-bubble{position:relative;display:flex;align-items:flex-start;gap:var(--tl-gap);
width:100%;text-align:left;padding:10px var(--tl-pad) 0;cursor:pointer}
.ab-badge{flex:0 0 var(--tl-time);margin-top:12.5px;
font-size:10.5px;font-weight:800;letter-spacing:.4px;color:var(--brand);line-height:1}
.ab-dot{flex:0 0 auto;width:9px;height:9px;margin-top:13.5px;border-radius:50%;
background:var(--brand);z-index:2;box-shadow:0 0 0 3px #fff}
.ab-body{flex:1;min-width:0}
/* 一只气泡,不是「又一张卡」:左上角削平 + 一个指回竖线的小尖角,
语气是「有人在说话」。原来那套左色条 + 40px 大引号不要了 ——
引号是在给一张卡补语气,有了尖角就不需要了。
⚠️ 底色沿用上一版 AI 小结卡那道暖渐变,不能用白:外壳已经是白的,
白气泡压白底就只剩一圈阴影,看不出是「在说话」。
这也是整块里唯一允许上色的地方 —— 时间线上其余部分都是白底黑字,
颜色留给「谁在说话」这一件事。 */
.ab-bubble{display:block;position:relative;background:linear-gradient(135deg,#FDF2E9,#FBE9DE);
border-radius:4px 16px 16px 16px;padding:11px 14px 12px}
/* ⚠️ 尖角的颜色要对上气泡渐变在**左上角**的那一端(#FDF2E9),不是取平均色 */
.ab-bubble::before{content:"";position:absolute;left:-7px;top:12px;width:0;height:0;
border:6px solid transparent;border-right:7px solid #FDF2E9;border-left:0}
/* 模块名降级成气泡里的一枚小标签。它和说明、「往期小结 ›」挤在同一行是塞不下的
(320px 下可用 197,三段要 212),所以标签进气泡、另两段留在落款行。 */
.ab-tag{display:block;font-size:11px;font-weight:800;letter-spacing:.2px;
color:var(--brand);opacity:.85;margin-bottom:5px}
.ab-say{display:block;font-size:15px;font-weight:600;line-height:1.6;color:#403430}
.ab-say b{color:var(--brand)}
.ab-foot{display:flex;align-items:center;gap:8px;margin-top:8px;padding-left:2px}
/* 这里原来还有一条 .ab-foot i(左边那句「AI自动汇总你每日记录」的样式)。
那句按用户要求删掉了,规则跟着删 —— 不留只被注释引用的死样式。
下面 em 的 margin-left:auto 是它现在还贴右的原因,别当成冗余清掉。 */
.ab-foot em{margin-left:auto;flex:0 0 auto;font-style:normal;white-space:nowrap;
font-size:11.5px;font-weight:700;color:var(--brand)}
/* 底色橡皮:把圆点以下那截线擦掉,线就停在这个点上,不会从气泡旁边一直拖到模块底。
top 只跟圆点走(圆心 28 + 半径 4.5 ≈ 32),和气泡有多高无关。
left/width 刚好盖住 58 那条线,够不着 x≥73 的气泡。
⚠️ 颜色 = 外壳底色 #fff,跟着外壳走。 */
.ai-bubble::after{content:"";position:absolute;left:calc(var(--tl-x) - 2.75px);
top:32px;bottom:0;width:5.5px;background:#fff}
/* ⚠️ 这两块的圆点是「长在」竖线上的,.ripple 默认的按下缩放会把点从线上拽下来
(线不跟着缩)。改成压透明度做反馈,涟漪照旧。 */
.steps,.ai-bubble{transition:opacity .16s var(--ease)}
.steps:active,.ai-bubble:active{transform:none;opacity:.6}
/* 模块三 · 书架卡带 */
/* 上一版这里是「1 张大卡 + 底下两张小卡」的九宫格式排布,卡都是竖着堆的,
和上面的工具区在视觉上是同一种东西 —— 一屏里第二次看到「大块 + 两个小块」。
这一版改成**横向卡带**:三张等宽的卡横着排,滑动查看,右边故意露出下一张的一角。
竖着滚的页面里插一段横着滑的内容,是打破直筒感最直接的办法,
而且资源本来就是「一排书」,横着摆比竖着堆更贴合它自己的样子。 */
/* 抬头和另两块一样:标题 + 右侧灰字入口 + 底下一句说明,字号间距全走公共规则。
上一稿这里额外加了一条 2px 的「书架横杆」压在标题底下 —— 另两块都没有,
纯粹是我给这一块单造的花样,正是「乱」的来源之一,去掉。 */
.shelf-bar{display:flex;align-items:center;gap:8px;margin-bottom:7px}
.sh-title{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.sh-note{margin-bottom:12px}
/* 卡带出血到屏幕边缘,和上面的工具托盘用同一招,理由不一样:
托盘是为了让底色成为一个「区」,这里是为了让卡能滑出屏幕、露出下一张。
要是卡带停在 26px 栏内,右边缘会硬生生切一刀,看着像没排版好而不是「可以滑」。
⚠️ padding 左右 = 26 抵回负外边距,第一张卡才和页面其余内容左对齐;
scroll-padding-left 必须跟着写,否则吸附会把卡吸到屏幕边缘(x=0)而不是 26。 */
.shelf-rail{
display:flex;gap:11px;margin:0 -26px;padding:2px 26px 6px;
overflow-x:auto;scroll-snap-type:x mandatory;scroll-padding-left:26px;
-webkit-overflow-scrolling:touch;
}
.shelf-rail::-webkit-scrollbar{display:none}
/* 78%:375 宽下卡是 292,右边正好剩 45px 露出下一张的边 —— 够让人知道还能滑,
又不至于露出半张让人以为是排版错位。用百分比而不是定宽,窄屏上会自己收。
等高不用写 min-height:flex 容器默认 align-items:stretch,三张自动拉齐到最高的那张,
再靠 .sb-row{margin-top:auto} 把底行压到卡底。 */
.shelf-book{
flex:0 0 78%;scroll-snap-align:start;
display:flex;flex-direction:column;text-align:left;
border-radius:20px;padding:14px;
}
/* 三张各自的糖果色沿用上一版那三张卡的配色,认得出是同一批东西。
⚠️ 不带阴影 —— 和 .tool-dock .mini-card 同一个道理:糖果色卡摆在白外壳上,
本身已经和底分得很开,再加阴影只会显脏。这一屏的规矩是
「白外壳负责分区,色块负责分内容,阴影一个都不要」。 */
/* --sb-bg 是这张卡**底部**的颜色(渐变的收尾色)。底行的叠压头像要用它描白边,
描的必须是所处位置的实际底色,写死 #fff 会在糖果色上留一圈亮边。 */
.shelf-book.sb-book{background:linear-gradient(150deg,#FFEEE2,#FCE3D3);--sb-bg:#FCE3D3}
.shelf-book.sb-food{background:linear-gradient(150deg,#EEF5E5,#E2EFD5);--sb-bg:#E2EFD5}
.shelf-book.sb-music{background:linear-gradient(150deg,#F0EAFB,#E5DBF8);--sb-bg:#E5DBF8}
/* 封面从「左边一张小图」改成「上面一张大图」,卡才像一本立在架上的书。
统一按高度 76 缩放:三张原图宽高比不一样(书是竖的,另两个是方图标),
锁高不锁宽,它们的视觉重量才差不多。margin 左右 auto 是在列向 flex 里做水平居中。 */
/* ⚠️ 封面必须占一个**固定的框**,而不是按图自身尺寸摆。
三张原图比例差很远(书是竖的窄长条,另两个是方图标),
上一稿只锁高度 76、宽度随意,于是三张卡的封面宽窄不一、视觉重量也不一样。
现在给统一的 width:100% + height:80px + object-fit:contain:
框一样大,图在框里等比缩放并居中,谁也不变形。 */
.sb-cover{width:100%;height:80px;display:block;object-fit:contain;margin:0 0 11px;
filter:drop-shadow(0 8px 15px rgba(180,120,80,.26))}
/* 标题一行封顶。三张的标题长短不一,放任换行会把下面的简介顶到不同高度。 */
.sb-title{font-size:15px;font-weight:800;line-height:1.4;letter-spacing:-.2px;
overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* 简介最多两行。现有三条文案在 320px 下都是一行,这里是防以后换了长文案
把某一张卡撑高、连累另外两张一起变高(卡带是等高的)。 */
.sb-desc{font-size:12px;color:var(--ink-2);line-height:1.5;margin-top:5px;
display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
/* margin-top:auto 把底行压到卡底,三张卡的底行因此始终在同一条水平线上,
哪怕上面的简介行数不一样。
gap:7px 是「头像 ↔ 人数」的距离,它俩是同一件事的两半,贴紧念着才连贯;
「免费」靠自己的 margin-left:auto 甩到最右,不吃这个 gap。 */
.sb-row{display:flex;align-items:center;gap:7px;margin-top:auto;padding-top:12px}
/* ⚠️ min-width:0 —— flex 子项默认 min-width:auto,光有 overflow/ellipsis 是不生效的,
窄屏(320)下这行会顶着「免费」把它挤出卡外。缩要缩在这儿,别缩头像。 */
.sb-got{font-style:normal;font-size:11px;color:var(--muted);white-space:nowrap;
overflow:hidden;text-overflow:ellipsis;min-width:0}
/* ---- 领取用户的叠压头像 ----
纯 CSS:assets/ 里没有人像图,为这点装饰新增 6 张图片不划算,
也会把单文件产物撑大。每个 <i> 是一个圆底,::before 画头、::after 画肩。
⚠️ 这一行的宽度非常紧,加大任何一个数之前先把账算一遍:
.shelf-book 是 flex-basis:78%,78% 算的是 .shelf-rail 的**内容盒**
(= 屏宽 - 52),不是屏宽。卡内可用宽度 = 0.78*(W-52) - 28:
W=320 → 181px W=375 → 224px W=390 → 236px
这一行要装:头像叠 + 7 + 人数 + 7 + 「免费」≈41。
⚠️ 人数那截这一版变长了:旧文案 6 个字 ≈69 → 「12.8 万妈妈已领取」≈88(+19)。
(这里不照抄旧文案原文:test/smoke.js 有一条「旧写法一处都不剩」的全文 grep,
跑在**构建产物**上,而 CSS 注释是会被 build.js 原样内联进去的 ——
在注释里写一遍旧文案,就会把那条 grep 打红。)
(11px 字号下,汉字按 11px、数字按 5.5px、点和空格按 2.75px 估。)
于是 = 头像叠 + 143:
→ 375 给头像的预算 224-143 = 81px,6 个(17px 叠 5px,含 padding-left:5)共 77px,仍放得下;
→ 320 预算只剩 181-143 = 38px。4 个要 53px、3 个要 46px,都装不下,只有 2 个(34px)行。
人数才是这行的主角,不能让它吃省略号 —— 按「要砍先砍装饰」,
窄屏这一版从收成 4 个改成收成 2 个,见文件下方那条媒体查询。 */
.sb-faces{display:flex;flex:0 0 auto;padding-left:5px}
.sb-faces i{
width:17px;height:17px;border-radius:50%;
margin-left:-5px; /* 一个压一个 */
position:relative;overflow:hidden;
/* 用 box-shadow 而不是 border 描白边:border 会吃掉 17px 里的内径,
把本来就只有 17px 的圆挤小;box-shadow 画在盒子外面,不影响布局。
白边的作用是把叠在一起的两颗头分开,颜色必须跟卡片底色一致。 */
box-shadow:0 0 0 1.5px var(--sb-bg,#fff);
}
/* 后面的盖住前面的,视觉上像一叠卡片从左往右码过来 */
.sb-faces i:nth-child(1){background:#F0A18C}
.sb-faces i:nth-child(2){background:#E9BE7C}
.sb-faces i:nth-child(3){background:#A9C79C}
.sb-faces i:nth-child(4){background:#8FB4D8}
.sb-faces i:nth-child(5){background:#BFA0D8}
.sb-faces i:nth-child(6){background:#EE9A9A}
/* 头 + 肩,最简剪影。半透明白而不是纯白:压在糖果色底上太扎眼。 */
.sb-faces i::before,.sb-faces i::after{
content:'';position:absolute;left:50%;transform:translateX(-50%);
background:rgba(255,255,255,.9);
}
.sb-faces i::before{top:3px;width:6px;height:6px;border-radius:50%}
.sb-faces i::after{bottom:-1px;width:11px;height:7px;border-radius:6px 6px 0 0}
/* 窄屏收成 2 个头像。见上面那笔账:320 宽下卡内只有 181px,
人数文案改长(+19px)之后,留给头像的预算从 57px 掉到 38px ——
原来收成 4 个(要 53px)已经不够了,只有 2 个(34px)塞得进去。
头像是装饰,人数是信息,要砍先砍装饰:宁可少两颗球,
也不能让「12.8 万妈妈已领取」吃省略号变成「12.8 万妈妈已…」。
360 是这一叠 6 个能完整放下的最小屏宽,所以断点还是 359。 */
@media (max-width:359px){
.sb-faces i:nth-child(n+3){display:none}
}
/* 「免费」甩到最右。文件末尾还有一条全局 .pill-free{margin-left:auto},
这里显式再写一次:靠一条几百行外的规则来决定这里的排版太脆,
而且上一版为了不让它靠右,正是在这个位置写了 margin-left:0。
⚠️ 底色必须写死成不透明白:文件末尾那条 .pill-free 用的是
rgba(110,154,86,.14)(半透明绿,为详情页的白底列表调的),
它在本文件里排在下面、同样是单类名,会盖掉上面那条 #E3F0D8。
半透明绿铺在三张糖果色卡上会各自被染成三种脏色 —— 挪到右边孤零零一颗之后
尤其明显。白底 + 绿字在粉/绿/紫三张卡上都干净,且三张长一个样。 */
.sb-row .pill-free{margin-left:auto;flex:0 0 auto;
background:#fff;color:var(--green);padding:3px 9px;border-radius:8px}
.pill-free{background:#E3F0D8;color:#5E8646;font-size:11.5px;font-weight:700;padding:3px 10px;border-radius:9px}
/* .pill-get(橙色「立即领取」胶囊)已删:它只在首页书架第一张卡上用过一次,
那颗假按钮连同这条规则一起去掉了。领取动作在资源详情页底部的 #rdGet。 */
.unlock{text-align:center;font-size:12.5px;color:var(--muted);margin:16px 0 4px}
.unlock a{color:var(--brand);font-weight:600}
/* ============ 通用页头(档案 / 来信) ============ */
.page-hero{padding:calc(env(safe-area-inset-top) + 16px) 20px 62px;color:#fff;background:var(--grad);position:relative;overflow:hidden}
.page-hero::after{content:"";position:absolute;width:230px;height:230px;border-radius:50%;background:rgba(255,255,255,.09);top:-70px;right:-60px}
.hero-info{display:flex;align-items:center;gap:14px;margin-top:20px;position:relative;z-index:2}
.hero-info>div:first-child{flex:1;min-width:0}
.hero-h1{font-size:27px;font-weight:800;letter-spacing:-.5px;margin-bottom:9px}
.hero-p{font-size:13.5px;opacity:.93;line-height:1.6}
/* 第二行是「查得到就行」的信息,压一档字号和透明度,
让标题和第一行先被读到,橙区不再是三行齐平的一堵字 */
.hero-p.sub{font-size:12px;opacity:.72;margin-top:1px}
/* 档案页那两行左边站着一个「这周宝宝多大了」的 emoji。
垫一层半透明白底圆盘,emoji 才不会直接糊在橘色上;
两行文字整体让开一个图标的位置 —— 就是「往右一点点」那个意思。
min-width:0 是给里面那个文字 div 的,不然长小名会把圆盘挤扁 */
.ar-lines{display:flex;align-items:center;gap:11px;margin-top:2px}
.ar-lines>div{min-width:0}
.ar-size{flex:0 0 auto;width:44px;height:44px;border-radius:50%;
background:rgba(255,255,255,.2);display:grid;place-items:center;font-size:25px;line-height:1}
/* ---------- 档案页 hero(重排过一次) ----------
.hero-info 是和来信页共用的,那边仍是「文字 + 右边信封」的横排;
档案页现在是自上而下四层,所以只在这里把 flex 关掉,不动共用那条。 */
.ar-hero{display:block}
/* 页面名。上一版是 .ar-eyebrow —— 13px、opacity .78 的一行小字,
为了把字号让给孕周。方向没错,但让过头了:这行是整页唯一出现宝宝小名的地方,
13px 的半透明白压在橙色渐变上,扫过去根本注意不到。
这一版不用字号抢(25px 还是归 .ar-week),改用「材质」抢:
半透明白底 + 一圈亮描边 + 毛玻璃,从橙色里浮起来一层 ——
字还是小的,但边界一有就跳出来了。
inline-flex 而不是 block:牌子宽度得跟着小名长短走,
撑满整行就成了色块,不像牌子。max-width 兜住超长小名。
gap 用 0 + 图标自己挂 margin:三个 flex item 统一 gap 会把
「小汤圆」和「的成长档案」也撑开 6px,读起来断成两截。
h1 默认的加粗和外边距在这里全部自己接管。 */
.ar-title{
display:inline-flex;align-items:center;gap:0;max-width:100%;
margin:0 0 12px;padding:5px 12px 5px 10px;
border-radius:99px;background:rgba(255,255,255,.18);
border:1px solid rgba(255,255,255,.3);
-webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px);
font-weight:600;letter-spacing:.2px;
}
.ar-t-ico{flex:0 0 auto;margin-right:6px;opacity:.9}
/* 小名是这枚牌子的重点,比后半句大两档半。省略号是给超长小名兜底的 ——
它是 flex item,不给 min-width:0 就不肯缩,会把牌子顶出屏幕。 */
.ar-t-name{font-size:15px;font-weight:800;letter-spacing:-.2px;line-height:1.3;
min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.ar-t-rest{flex:0 0 auto;font-size:12.5px;opacity:.8;white-space:nowrap}
/* 孕周接手了原来 .hero-h1 的位置。27px 太挤(右边不再有环,但左边多了 44px 圆盘),
收到 25px,正好和 .ar-size 的高度配平。 */
.ar-week{font-size:25px;font-weight:800;letter-spacing:-.5px;line-height:1.2}
/* 孕程细条。i 的宽度由 JS 现写(syncPregnancy),
过渡时间和进度环那条对齐,两页切过去动感是一样的。 */
.ar-prog{margin-top:18px;height:5px;border-radius:99px;background:rgba(255,255,255,.28);overflow:hidden}
.ar-prog>i{display:block;height:100%;width:0;border-radius:99px;background:#fff;
transition:width 1.4s var(--ease)}
.ar-foot{display:flex;justify-content:space-between;margin-top:8px;font-size:11.5px;opacity:.86}
.ar-foot b{font-weight:800;font-size:12.5px}
/* 四层竖排比原来「两行文字 + 右边一个环」高了约 34px。
橙区本来就不小,再长一截就压得下面没地方了,所以把底部留白从 62 收到 44。
.page-body 的负外边距要跟着改同样的数(-46 → -28),
这两个数的差值才是「白卡顶边离橙区内容的距离」,一直是 16px,
只改一边白卡就会爬上去盖住「已陪伴 N 天」那行。 */
#screen-archive .page-hero{padding-bottom:44px}
#screen-archive .page-body{margin-top:-28px}
/* 浮动信封改为扁平矢量(原本是 💌 emoji,各系统渲染不一致且偏写实) */
.env-art{flex:0 0 auto;margin-left:auto;display:block;filter:drop-shadow(0 8px 14px rgba(150,50,30,.28));animation:float 5s ease-in-out infinite}
/* 自动归档卡 */
.auto-card{background:#fff;border-radius:22px;padding:18px;box-shadow:var(--sh-lg)}
/* 标题行左边是「● 正在自动归档」,右边顶着当前周次角标 */
.ac-head{display:flex;align-items:center;justify-content:space-between;gap:12px;font-size:17px;font-weight:800}
.ac-h-l{display:flex;align-items:center;gap:9px;min-width:0}
.live-dot{flex:0 0 auto;width:9px;height:9px;border-radius:50%;background:#5EBE6B;position:relative}
.live-dot::after{content:"";position:absolute;inset:-4px;border-radius:50%;border:2px solid rgba(94,190,107,.5);animation:pulse 1.8s ease-out infinite}
@keyframes pulse{0%{transform:scale(.7);opacity:1}100%{transform:scale(1.5);opacity:0}}
/* 统计胶囊现在排在生成入口下面,所以间距从这儿给 */
.ac-chips{display:flex;gap:7px;flex-wrap:wrap;margin-top:14px}
.chip-stat{background:#F6F1EC;border-radius:10px;padding:6px 11px;font-size:12px;color:var(--ink-2)}
.chip-stat b{color:var(--brand)}
/* ---- 生成档案入口(周档案 / 完整档案):并入自动归档卡内 ---- */
.gen-badge{flex:0 0 auto;font-size:11.5px;font-weight:700;color:var(--brand);background:rgba(244,89,60,.1);padding:5px 10px;border-radius:9px}
/* 上面那句说明段落删掉了,这里自己把上间距补回来 */
.gen-btns{display:flex;gap:10px;margin-top:15px}
.gen-btn{
flex:1;min-width:0;display:flex;align-items:center;gap:10px;text-align:left;
background:#F7F1EB;border-radius:16px;padding:12px;color:var(--ink);
}
.gen-btn svg{flex:0 0 auto;color:var(--brand)}
.gen-btn span{display:flex;flex-direction:column;gap:3px;min-width:0}
.gen-btn b{font-size:14px;font-weight:800;line-height:1.35}
/* 副标题:原先卡片上那句笼统的说明拆到了这里,每个入口自己解释自己 */
.gen-btn em{font-size:10.5px;color:var(--muted);font-style:normal;font-weight:600;line-height:1.3;white-space:nowrap}
/* ---- 档案图片预览 ---- */
/* 抬头卡:暖色底 + 白底方形图标,先告诉你手里这张是什么 */
.pst-hero{display:flex;align-items:flex-start;gap:14px;border-radius:22px;padding:18px;margin-bottom:12px;
background:linear-gradient(140deg,#FFF3EA,#FAE3D6)}
.pst-ico{flex:0 0 auto;width:46px;height:46px;border-radius:15px;background:#fff;color:var(--brand);
display:grid;place-items:center;box-shadow:0 6px 14px rgba(196,110,80,.18)}
.pst-h-txt{min-width:0}
.pst-h-title{font-size:18px;font-weight:800;color:#3A2C24}
.pst-lead{font-size:13px;color:#8B6E5E;line-height:1.7;margin:6px 0 0}
/* 这张图里装了什么:条数、天数、成图尺寸 */
.pst-chips{display:flex;gap:7px;flex-wrap:wrap;margin-bottom:14px}
.pst-chip{background:#fff;border-radius:11px;padding:7px 11px;font-size:11.5px;color:var(--ink-2);box-shadow:var(--sh)}
.pst-chip b{color:var(--brand);font-weight:800;margin-left:3px}
/* 分享卡的三颗样式胶囊。用站内通用的 .chip,只在这儿管排版。
320 屏上 .detail-body 内宽 276:三颗 chip 各是 padding 34 + 文字
(信纸 28 / 成长足迹 56 / 比大小 42)= 62 + 90 + 76 = 228,
加两道 8px 间距共 244,比 276 富余 32px,不用横滚也不会折行。
⚠️ 加第四套样式前先按这个算式量一遍,超了就要给这行补 overflow-x:auto。 */
.pst-tpl{display:flex;gap:8px;margin-bottom:13px}
/* 完整档案是一页一张 A4,屏幕上就一张张纸往下摞,纸和纸之间留条缝 */
.pst-pages{display:flex;flex-direction:column;gap:14px}
/* 白色卡纸把图裱起来,再压一道虚线裁切位,像张待印的样张 */
.pst-frame{position:relative;padding:10px;border-radius:20px;overflow:hidden;box-shadow:var(--sh-lg);background:#fff;line-height:0}
.pst-frame::after{content:"";position:absolute;inset:5px;border:1.5px dashed rgba(150,110,88,.2);border-radius:16px;pointer-events:none}
/* canvas 用 2 倍像素画,这里按容器宽显示 */
.pst-frame canvas{width:100%;height:auto;display:block;border-radius:12px}
.pst-tip{font-size:11.5px;color:var(--muted);line-height:1.7;margin:13px 0 0}
/* 打印时只留这几张档案图,其它一概不出现在纸上 */
@media print{
/* 页边距已经画进画布里了(左边还留了装订边),浏览器不要再套一圈 */
@page{size:A4;margin:0}
body *{visibility:hidden}
#detailPoster,#detailPoster .detail-body,#detailPoster .pst-pages,#detailPoster .pst-frame,#detailPoster canvas{visibility:visible}
#detailPoster{position:absolute;inset:0;transform:none;overflow:visible;background:#fff}
#detailPoster .detail-bar,#detailPoster .pst-hero,#detailPoster .pst-chips,#detailPoster .pst-tip,#pstCta{display:none}
#detailPoster .detail-body{overflow:visible;padding:0}
/* 卡纸、裁切虚线、纸间缝隙都只是屏幕上的装饰,纸上不要 */
.pst-pages{display:block;gap:0}
.pst-frame{box-shadow:none;border-radius:0;padding:0;margin:0}
.pst-frame::after{display:none}
.pst-frame canvas{border-radius:0}
/* 一张画布 = 一张纸。最后一张后面不再断页,否则会多吐一张白纸 */
#detailPoster .pst-frame{page-break-after:always;break-after:page}
#detailPoster .pst-frame:last-child{page-break-after:auto;break-after:auto}
}
/* 分段控件 */
/* 三个分栏各自那行 sec-title 已删(和标签文字重复),
它原本提供的下间距改由这里给,否则内容会贴着分段控件 */
.segmented{position:relative;display:flex;background:#EFE7E0;border-radius:20px;padding:5px;margin:22px 0 16px}
/* 来信页那支现在住在橘色头部里,配色见「来信」一节的 #letterSeg */
.seg-thumb{position:absolute;top:5px;bottom:5px;left:5px;background:#fff;border-radius:16px;box-shadow:0 3px 10px rgba(150,100,80,.14);transition:transform .38s var(--ease),width .38s var(--ease);z-index:1}
.seg-btn{position:relative;z-index:2;flex:1;text-align:center;padding:11px 4px;font-size:15px;font-weight:600;color:var(--ink-2);transition:color .25s,font-weight .2s;white-space:nowrap}
.seg-btn.is-active{color:var(--ink);font-weight:800}
.seg-btn i{font-style:normal;font-size:12px;color:var(--brand)}
.seg-panel{display:none}
.seg-panel.is-active{display:block;animation:fadeUp .38s var(--ease)}
@keyframes fadeUp{from{opacity:0;transform:translateY(12px)}to{opacity:1;transform:none}}
/* 时光轴 */
.tl-card{background:#fff;border-radius:22px;padding:20px 18px 6px;box-shadow:var(--sh)}
.tl-item{position:relative;padding:0 0 24px 22px}
.tl-item::before{content:"";position:absolute;left:0;top:5px;width:11px;height:11px;border-radius:50%;background:var(--brand);z-index:2}
.tl-item::after{content:"";position:absolute;left:5px;top:14px;bottom:-2px;width:1.6px;background:#F6E1D8}
.tl-item:last-child::after{display:none}
.tl-date{font-size:12.5px;font-weight:700;color:var(--muted);margin-bottom:7px}
.tl-item:first-child .tl-date{color:var(--brand)}
.tl-text{font-size:15.5px;line-height:1.78;color:#39302C}
.tl-text b{color:var(--brand)}
.tl-from{display:inline-block;margin-top:9px;background:#F6F1EC;border-radius:8px;padding:5px 10px;font-size:11.5px;color:var(--muted)}
.tl-item.hide{display:none}
/* 成长数据 */
.stat-row{display:grid;grid-template-columns:repeat(3,1fr);gap:9px}
.stat-card{background:#fff;border-radius:16px;padding:13px 12px 8px;box-shadow:var(--sh)}
.sc-label{font-size:12px;color:var(--ink-2)}
.sc-value{font-size:24px;font-weight:800;letter-spacing:-.6px;margin:5px 0 3px}
.sc-value small{font-size:12px;font-weight:700}
.sc-trend{font-size:12px;font-weight:700;color:var(--brand)}
.sc-trend.ok{color:var(--green)}
.sc-trend.purple{color:var(--purple)}
.spark{width:100%;height:auto;margin-top:8px;display:block}
.spark polyline{stroke-dasharray:300;stroke-dashoffset:300;animation:draw 1.1s .2s var(--ease) forwards}
@keyframes draw{to{stroke-dashoffset:0}}
.chart-card{background:#fff;border-radius:22px;padding:18px;margin-top:12px;box-shadow:var(--sh)}
.cc-title{font-size:16px;font-weight:800;margin-bottom:14px}
.chart{width:100%;height:auto;display:block;overflow:visible}
.chart .grid{stroke:#F2ECE7;stroke-width:1}
.chart .band{fill:rgba(160,196,130,.24)}
.chart .area{fill:rgba(244,89,60,.08)}
.chart .line{fill:none;stroke:var(--brand);stroke-width:3;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:400;stroke-dashoffset:400;animation:draw 1.3s .15s var(--ease) forwards}
.chart .pt{fill:#fff;stroke:var(--brand);stroke-width:3}
.chart .pt.fill{fill:var(--brand);stroke:#fff}
.cc-x{display:flex;justify-content:space-between;font-size:11.5px;color:var(--muted);margin-top:9px;padding:0 2px}
.cc-legend{display:flex;gap:18px;margin-top:12px;padding-top:12px;border-top:1px solid var(--line)}
.lg{font-size:12.5px;color:var(--ink-2);display:flex;align-items:center;gap:6px}
.lg::before{content:"";width:16px;height:4px;border-radius:2px}
.lg-line::before{background:var(--brand)}
.lg-band::before{background:rgba(160,196,130,.5)}
.meas-card{background:#fff;border-radius:22px;padding:6px 18px;margin-top:12px;box-shadow:var(--sh)}
.meas{display:flex;align-items:center;gap:14px;padding:17px 0;border-bottom:1px solid var(--line)}
.meas:last-child{border:0}
.m-name{font-size:16px;font-weight:800;flex:0 0 62px}
.m-bar{flex:1;height:9px;border-radius:5px;background:#E9EFE1;position:relative}
.m-bar i{position:absolute;left:0;top:0;bottom:0;width:var(--p);background:linear-gradient(90deg,#DCE8CE,#C6DCB0);border-radius:5px}
.m-bar::after{content:"";position:absolute;left:var(--p,50%);top:50%;width:17px;height:17px;border-radius:50%;background:#fff;border:3px solid var(--brand);transform:translate(-50%,-50%);box-shadow:0 2px 6px rgba(0,0,0,.12)}
.m-val{text-align:right;flex:0 0 58px}
.m-val small{font-size:12px}
.m-val em{display:block;font-size:11.5px;color:var(--muted);font-weight:600;margin-top:2px}
.m-val{font-size:19px;font-weight:800}
/* 珍贵时刻 */
.moment-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px}
.moment{background:#fff;border-radius:18px;overflow:hidden;box-shadow:var(--sh);padding-bottom:12px}
.mo-art{height:104px;display:grid;place-items:center;position:relative}
/* 扁平图标统一规格:46px、线宽 2.4、圆角端点;
.f = 半透明白底(让图形从渐变里浮出来),.s = 描边,.d = 实心点 */
.mo-art svg{width:46px;height:46px;display:block;color:currentColor}
.mo-art svg .f{fill:#fff;opacity:.66}
.mo-art svg .s{fill:none;stroke:currentColor;stroke-width:2.4;stroke-linecap:round;stroke-linejoin:round}
.mo-art svg .d{fill:currentColor}
.mo-art.a1{background:linear-gradient(140deg,#EDE7F8,#E3D9F5);color:#6B54BE}
.mo-art.a2{background:linear-gradient(140deg,#FCE9E4,#FADCD3);color:#D0563F}
.mo-art.a3{background:linear-gradient(140deg,#E9F2E0,#DDEBD0);color:#5A8A41}
.mo-art.a4{background:linear-gradient(140deg,#FBEDE4,#F7E0D2);color:#CE6F35}
.mo-art.a5{background:linear-gradient(140deg,#E2EDF7,#D4E4F4);color:#3C6DA8}
.mo-art.a6{background:linear-gradient(140deg,#FDEAE0,#FADCCC);color:#C4632F}
.star{position:absolute;top:8px;right:9px;font-size:17px;color:rgba(255,255,255,.85);text-shadow:0 1px 3px rgba(0,0,0,.15);transition:transform .25s var(--ease),color .2s}
.star.is-on{color:#FFC93C}
.star:active{transform:scale(1.35)}
.mo-title{font-size:14.5px;font-weight:800;margin:11px 12px 5px}
.mo-meta{font-size:11.5px;color:var(--muted);margin:0 12px 7px}
.mo-from{display:inline-block;margin:0 12px;background:#F6F1EC;border-radius:7px;padding:4px 8px;font-size:11px;color:var(--muted)}
.moment.hide{display:none}
/* 纪念册 */
.album-card{display:flex;gap:15px;width:100%;background:linear-gradient(120deg,#FCEDE1,#F9E0D0);border-radius:22px;padding:18px;margin-top:22px;box-shadow:var(--sh)}
.al-book{flex:0 0 82px;height:110px;border-radius:5px 10px 10px 5px;background:linear-gradient(150deg,#F2795A,#E04E2E);display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px;color:#fff;box-shadow:0 10px 20px rgba(190,80,50,.32),inset 4px 0 0 rgba(255,255,255,.22)}
.al-leaf{font-size:28px}
.al-tag{font-size:11.5px;font-weight:800;text-align:center;line-height:1.35}
.al-title{font-size:17px;font-weight:800}
.al-title em{font-size:12.5px;color:var(--brand);margin-left:6px}
.al-desc{font-size:12.5px;color:var(--ink-2);line-height:1.7;margin:7px 0 11px}
.al-bar{height:8px;border-radius:4px;background:rgba(255,255,255,.75);overflow:hidden}
.al-bar i{display:block;height:100%;width:var(--p);background:linear-gradient(90deg,#FF8A68,#F0563A);border-radius:4px}
.al-meta{font-size:11.5px;color:var(--ink-2);margin-top:8px}
/* ============ 来信 ============ */
/* 页顶那张 .feature-letter「最新」大卡已删(画的就是列表第一条),
.fl-* / .badge-unread 一整套随之下线 */
/* 筛选标签搬进了橘色头部。
左右 -2px 是为了抵消 .page-hero(20px) 和 .page-body(18px) 的内边距差,
否则这排标签会比下面的信件列表往里缩 2px。
z-index 用来压住 .page-hero::after 那个装饰大圆(伪元素排在内容之后绘制)。
底色原来是白色半透明(照搬 .icon-btn.glass),但那是和白字同方向的,
等于没有底 —— 四格里只有选中那格(白底红字)跳得出来。
换成一层压暗的暖色,白字才立得住。 */
#letterSeg{background:rgba(120,42,26,.18);margin:18px -2px 0;z-index:2}
#letterSeg .seg-thumb{box-shadow:0 3px 10px rgba(150,60,40,.22)}
#letterSeg .seg-btn{color:#fff;font-weight:700}
#letterSeg .seg-btn.is-active{color:var(--brand);font-weight:800}
/* 未读数原来只是一个小号红字,太轻了。做成真的红点徽标:
红底白字的小胶囊,选中与否都是这一套 —— 红底压在橘衬和白衬上都够跳 */
#letterSeg .seg-btn i{
display:inline-block;min-width:16px;padding:0 4px;margin-left:3px;
background:#FF3B30;color:#fff;font-size:11px;font-weight:800;
line-height:16px;border-radius:8px;text-align:center;vertical-align:1px;
box-shadow:0 1px 3px rgba(0,0,0,.16)
}
/* 全读完了就别挂一个「0」在那儿。.seg-btn 是 flex:1,
徽标显隐不会让四格宽度变化,滑块也不用重测 */
#letterSeg .seg-btn i.zero{display:none}
/* .page-hero 的下内边距 + .page-body 的负上边距,本来是配合着让
.feature-letter 半浮在橘色下沿的。卡片没了,头部自己收边,
列表就从橘色下面老实开始。两个类都和档案页共用,只能定向覆盖 */
/* 上内边距:搜索图标那条 .hero-top 删掉后空出 42px(高 32 + margin-top 10),
全还回去等于白删,一点不还标题又贴着状态栏。补 18px,净收 24px。 */
#screen-letter .page-hero{padding:calc(env(safe-area-inset-top) + 34px) 20px 16px}
#screen-letter .page-body{margin-top:0}
/* ---------- 信封上盖(本批新增) ----------
⚠️ 先说清楚,免得被当成「用户否过的东西又回来了」:
被否掉的是 .lt-flap —— 列表里**每张信件卡**顶上那道折口
(原话「来信的那个列表的那个信纸的那个还是不要这种的,有点丑」),
那是 24 张卡各来一份、密度过头。这里是**整页头部一整只信封**,
全页只此一处,而且是用户本批点名要的(「做成类似一个信封的那种感觉」)。
两者只是名字像,别顺手一起删了。
形状和「为什么用 SVG 不用 clip-path」写在 index.html 那枚 <svg> 上面,
这里只管尺寸和层级。三件事都是踩过的坑,别简化:
⚠️ width:100% 不能省,也**不能**换成 left:0;right:0 那种写法。
SVG 是**替换元素**:绝对定位下 width 若为 auto,用的是它的固有宽度
(没写 width 属性的 SVG 固有宽度是 300px),right 会被直接忽略。
结果是盖子缩成左上角一块 300px 宽的三角,430px 的机器上右边空一截,
而且**不报错**、jsdom 也量不出来 —— 只能靠这条 CSS 断言钉住。
⚠️ 高度用百分比,不能写死 px。这个头部约 212px 高
(上内边距 34 + .hero-info 上外边距 20 + 文字块约 86
+ #letterSeg 上外边距 18 + 标签条约 38 + 下内边距 16),
72% ≈ 153px,尖端正好顶在 #letterSeg 的上沿。
但刘海机上 env(safe-area-inset-top) 会再撑高约 47px,
写死 153px 的话尖端就停在半空中、够不着那条封口了。
⚠️ 故意不写 z-index。.hero-info 和 #letterSeg 都是 z-index:2,
文字和标签一直压在盖子上面。别顺手给它补一个 z-index:
只要 ≥2 就会有一层半透明白蒙在标题上,白字压白纱,糊成一片。 */
.env-flap{position:absolute;left:0;top:0;width:100%;height:72%;
display:block;pointer-events:none}
.lgroup{margin-bottom:6px}
.lgroup.hide{display:none}
.lg-title{font-size:13px;font-weight:700;color:var(--muted);margin:16px 2px 10px}
/* ============ 来信列表:一条 = 宝宝发来的一条消息 ============
用户原话:「来信的那个列表的那个信纸的那个还是不要这种的,有点丑,
给我更换成别的形式的。」上一版是信封信纸风(卡顶折口 + 左边一枚歪着贴的邮票 +
摘要下面一条虚线横格)—— 三样装饰堆在一张 320 宽的小卡上,密度过头了,
而且横格那条虚线横穿整卡,视觉上把每条卡又切成两半,一屏下来全是横线。
这一版给了几个方案,用户选了**聊天气泡**:
[头像] ╭──────────────────╮
│ 标题 [未读] │
│ 摘要一行…… │
│ 12月5日 · 孕24周 │
╰──────────────────╯
为什么这个方向对:来信本来就是**宝宝在跟妈妈说话**,而下面 .lt-sub 里
妈妈的回信、宝宝的再回复早就是气泡了(.lt-msg,从第二版起就是)。
上一版等于一条会话里,第一句用信封画、后面几句用气泡画,两套语言。
现在整条线索从上到下都是同一套。
⚠️ 装饰全撤了,但**信息一条没少**:
邮票上的「12月 / 5」→ 挪进气泡底下的 .lt-meta(见 buildThread)
折口、虚线横格 → 纯装饰,直接删
改这块之前先确认要删的东西身上没挂着信息。 */
.lthread{
/* 三个尺寸放在 .lthread 上而不是 .lt-item 上,是因为下面 .lt-sub 也要读
(回信气泡要和上面这条来信气泡左对齐,见 --sub-in)。
.lt-sub 是 .lt-item 的**兄弟**,声明在 .lt-item 上它够不着。 */
--ava:38px; /* 头像直径 */
--gap:10px; /* 头像到气泡的间距 */
--tail:6px; /* 气泡左上那个小尖角的长度 */
/* 回信那串要缩进多少,才能和上面来信气泡的左缘对齐:
竖虚线左缘在 --ava/2 − 1px 处(2px 宽,正好压在头像中线上)
来信气泡左缘在 --ava + --gap 处
两者之差 = --ava/2 + --gap − 1px = 19 + 10 − 1 = 28px
⚠️ 别把它写死成 28px。头像一改,回信就整体错位,而且是那种
「看着好像本来就这样」的错位,没人会发现。 */
--sub-in:calc(var(--ava)/2 + var(--gap) - 1px);
margin-bottom:12px}
.lthread.hide{display:none}
/* ⚠️ --bub 只能声明在 .lt-item 上,不能往上提到 .lthread:
未读是 .lt-item.is-unread,只有声明在同一个元素上,那条覆盖才生效。
而 .lt-item::before(尖角)和 .lt-body(气泡)都是它的后代,继承得到。
⚠️ 本批把气泡从纯色改成渐变(用户要的「底纹」),于是变成**一对** ——
--bub 是起点、--bub-2 是终点。这两个名字不能拆开理解:
左上那个尖角(.lt-item::before)是 border 拼出来的三角,
**只吃得下一种纯色**,它取的是 --bub。所以 --bub 必须始终是
渐变里**贴着尖角那一头**的颜色(尖角在 top:11px 的左缘 ≈ 160deg 的起点)。
要是哪天把渐变角度翻过来、或者只改 --bub 不改 --bub-2,
屏幕上就会出现一个和气泡不同色的尖角 —— 看着像渲染坏了。 */
.lt-item{
--bub:#fff;
--bub-2:#FFF6F0;
position:relative; /* 尖角 ::before 靠它定位 */
display:flex;gap:var(--gap);width:100%;
/* ⚠️ flex-start,不是 center。气泡是变高的(标题可能换行、meta 可能多两段),
center 会让头像跟着往下飘;聊天列表里头像永远咬在第一行。 */
align-items:flex-start;
/* 这一行本身**是透明的**:没有背景、没有内边距、没有投影。
卡的那层壳整个搬进了 .lt-body。 */
background:none;padding:0}
.lt-item.hide{display:none}
/* 气泡左上那个小尖角,指向头像。
⚠️ 它画在 .lt-item 上而不是 .lt-body 上,这一条不能挪:
.lt-body 带 .ripple → overflow:hidden,画在它身上的尖角会被自己裁掉,
一个像素都露不出来。而 .lt-item 这一版正好把 .ripple 让了出来(见 buildThread),
伪元素没人抢。
⚠️ 三角形用 border 拼:宽高都是 0,只有右边框有颜色,于是右边框那块
梯形塌成一个尖朝左的三角。颜色取 var(--bub),未读那条自动跟着变。
⚠️ left 算出来是 38+10−6 = 42px,尖端 42、底边 48 —— 底边正好贴着气泡左缘。
**刻意不重叠**:.lt-body 是定位元素(.ripple 给的 position:relative),
在同一个层叠上下文里画得比 ::before 晚,真重叠了是气泡盖住尖角。 */
.lt-item::before{
content:"";position:absolute;left:calc(var(--ava) + var(--gap) - var(--tail));
top:11px;width:0;height:0;pointer-events:none;
border:var(--tail) solid transparent;border-left:0;border-right-color:var(--bub)}
/* 左边那颗:宝宝的头像。
⚠️ 里面原来是 js 的 babyFace() 画的一张 SVG 小脸,本批换成了一个「宝」字
(或用户自己选的照片)。理由见 app.js 的 babyAvatarInner()。
⚠️ 它现在是个 <button>(点一下换头像),不再是 <span>。
全局 button 重置里有 text-align:left,这颗圆盘里的字必须居中 ——
下面的 justify-content:center 管的是 flex 主轴,够了;
但 padding 得显式清 0,UA 默认给 button 的内边距会把 38px 的圆挤变形。
⚠️ flex:0 0 var(--ava) 里的 0 0 是不可省的:不写收缩因子,标题一长
弹性布局就先去压这颗圆头像,圆会被压成椭圆 —— 而且是渐变的椭圆,很难看。 */
.lt-ava{
flex:0 0 var(--ava);height:var(--ava);border-radius:50%;padding:0;
display:flex;align-items:center;justify-content:center;
background:linear-gradient(155deg,#FFE6D6,#FFC9B2);
color:#B75B38;
/* overflow:hidden 是给自定义头像那张 <img> 用的:图是方的,
靠这一层把它裁成圆。写在容器上而不是图上,是因为占位的「宝」字
也该被同一个圆约束(字号一旦被改大,不至于溢出圆盘)。 */
overflow:hidden;
box-shadow:0 2px 5px rgba(198,104,78,.16);
transition:transform .16s var(--ease)}
/* 点下去缩一下。这颗圆是这一行里唯一没有涟漪的可点区域
(涟漪归气泡,见上面 .lt-body),不给点按反馈的话,
用户点了它、系统相册又要一会儿才弹出来,中间那段会以为没点上。 */
.lt-ava:active{transform:scale(.9)}
/* 占位的「宝」字。字号 17 是配 38px 圆盘量出来的:再大就顶到圆边,
再小在满屏文字里显不出「这是个头像」。
⚠️ line-height:1 不能省 —— 中文字体的默认行高会把字压得偏下半个像素,
在圆盘里非常明显(看着像没对齐)。 */
.lt-ava-txt{font-size:17px;font-weight:800;line-height:1;letter-spacing:0}
/* 用户自己选的照片。object-fit:cover 而不是 contain:
contain 会在圆盘里留出上下两条底色边,看着像图没加载完。 */
.lt-ava-img{width:100%;height:100%;object-fit:cover;display:block}
/* 刚寄出的回信高亮两秒,让妈妈看清它落到列表哪一条了。
⚠️ 这里原先还有一条 .lt-item.just-added(同一段动画)。全项目只有 .lthread
会被加上 just-added(app.js 里那一处),.lt-item 上一次都没加过 ——
和之前清掉的 .lt-week.reply 是同一类死规则。删了。 */
.lthread.just-added{animation:justAdded 2.2s var(--ease)}
@keyframes justAdded{
/* ⚠️ 这圈光晕是「妈妈刚寄出的回信」的高亮,颜色必须跟着 --mom 走。
本批把妈妈从蓝换成绿,这两行原来是 rgba(77,138,199,…)(旧蓝 #4D8AC7),
漏改的话:气泡是绿的、闪的那圈是蓝的,只闪 2.2 秒,肉眼基本撞不上。
值是 --mom #3F7551 的 rgb。
⚠️ 这里只能手抄 rgb:keyframes 要的是带透明度的 rgba(),
CSS 没法从 #3F7551 现算出 63,117,81,var(--mom) 塞不进去。
所以 --mom 改值时**必须连这两行一起改**。
smoke 两头都钉着:反向扫「旧蓝 77,138,199 不许剩」+
正向钉「这两帧是 63,117,81」,改漏了会红。 */
0%{transform:scale(.96);box-shadow:0 0 0 0 rgba(63,117,81,.45)}
22%{transform:none;box-shadow:0 0 0 4px rgba(63,117,81,.28)}
100%{transform:none;box-shadow:var(--sh)}
}
/* 气泡本体。.ripple 从 .lt-item 挪到了这里(理由见 buildThread 里那段),
顺带把 position:relative 和 overflow:hidden 都带了过来 ——
overflow:hidden 正好让涟漪跟着下面这个圆角走,不用另外裁。
⚠️ 圆角写成 6/16/16/16:左上那个角**特意收小**,因为尖角就顶在那儿,
左上要是也 16px,尖角底边就悬在圆弧外面,看着像根断了的刺。 */
.lt-body{
flex:1;min-width:0;
background:linear-gradient(160deg,var(--bub) 0%,var(--bub-2) 100%);
border-radius:6px 16px 16px 16px;
padding:12px 13px;box-shadow:var(--sh)}
/* ⚠️ .lt-h 是弹性行,标题右边还并排着一枚「未读」标签。
在此之前 h4 **一条截断规则都没有**:标题短的时候看不出来,
可一旦加长(这一版给两条标题加了「妈妈,」三个字),弹性项默认
min-width:auto 会撑到内容全宽,把右边那枚标签直接顶出卡外 ——
不是省略号,是标签整个消失。所以 .lt-h 和 h4 两处都要 min-width:0。
量过的账(320 屏,改成气泡之后重算过一遍):
320 − 36(页边 18×2)− 38(头像)− 10(间距)− 26(气泡内边距 13×2)= 210px
比上一版信封卡的 202px **宽了 8px** —— 头像比那枚 42px 的邮票窄,
气泡内边距也比卡的 14px 小一点。所以上一版排得下的标题,这一版一定还排得下,
不用回头重算 LETTERS 里那几条。
⚠️ 上一句「最挤的一条是带『1 条新回复』≈65px,只剩 ~137px」是**上一版的账**,
那枚汇总标签本批已经删掉了(见 app.js buildThread 里的说明)。
现在标题右边最多只并排一枚「未读」≈ 2 字 × 10.5 + 内边 14 = 35px,
加 8px 间距,标题可用 210 − 43 = **167px**,比之前松了 30px。
别再照抄那个 137 —— 它只会让人以为标题行还紧,去砍不该砍的东西。 */
.lt-h{display:flex;align-items:center;gap:8px;min-width:0}
.lt-h h4{font-size:16px;font-weight:800;
min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* ⚠️ 这里原来还有一条 .lt-h h4.blue{color:var(--mom)},本批**删掉了**。
它是条死规则 —— 全项目搜遍,没有任何地方给列表里的 h4 加过 blue 这个 class
(js/app.js 里连 'blue' 这个字符串都不存在)。
留着它的坏处不是多几个字节,是**误导**:我自己这批就被它骗过一次,
看见它就以为「列表里还有一类标题是绿色的(妈妈发的信)」,
差点照着这个不存在的分支去做样式。实际上列表顶层每条都是宝宝写来的信,
妈妈的回信是缩进挂在下面的 .lt-msg.mom 子气泡,那些**没有 h4 标题**。
要给妈妈的条目做标题色,得先真的有那种条目,别再凭这条规则脑补。 */
/* ---- 标题下的手绘波浪线 ----
和详情页大标题下那条 .dl-wave 是同一条 path、同一个颜色,成对出现:
列表里看到的波浪线,点进去在详情页大标题下会再看到一条,视觉上接得上。
⚠️ 只会长在宝宝的信上,而且**不是靠判断实现的,是靠结构**:
波浪线跟着 .lt-h(标题行)走,而标题行只有顶层的宝宝来信才有。
妈妈的回信是 .lt-sub 里的 .lt-msg.mom,压根没有 .lt-h。
所以 js 里不需要任何 who === 'baby' 的分支 —— 别去加,加了是死代码。
⚠️ 宽度 58px,比详情页那条(96px)短得多,是因为容器差着一倍多:
详情页大标题是整屏宽,列表气泡 .lt-body 扣掉头像和内边距只剩 ~200px。
照搬 96px 会占掉气泡近一半宽,喧宾夺主。
⚠️ height 必须单独锁死。svg 带 preserveAspectRatio="none",
宽度一变高度会跟着缩放,线会随之变粗变细 —— 和 .dl-wave 那边同一个理由。 */
.lt-wave{display:block;width:58px;height:6px;margin:5px 0 1px}
/* ---- 未读 ----
原来只有一颗 8px 圆点挂在标题右端。「全部」这一页是一屏白卡往下堆,
那颗点又小又没字,卡本身还和已读的长得一模一样,从上往下扫就漏了。
现在三件事一起说:整卡换暖底 + 左边一道竖条 + 一枚写着「未读」的标签。
⚠️ 类名仍叫 .dot-unread(buildThread 里那处同名),它已经不是「点」了;
改名得同时动 JS、CSS、smoke 三处,收益不抵风险,先留着。 */
.dot-unread{
margin-left:auto;flex:0 0 auto;
background:var(--brand);color:#fff;
font-size:10.5px;font-weight:800;line-height:1.55;
padding:2px 7px;border-radius:8px;
}
/* ---- 未读 ----
上一版是「暖底 + 左边一道 inset 竖条 + 标签」三件事。这一版竖条**去掉了**:
气泡左缘上有个尖角顶着,一道 3.5px 的实心竖条压过去,尖角看起来就像
从竖条上长出来的一根刺。改用聊天里更自然的两样 ——
1. 气泡换更饱和的底色(--bub,尖角吃同一个变量,自动跟着变)
2. 头像加一圈品牌色的环
加上标题行那枚写着「未读」的标签,仍是三个信号,只是换了两个载体。
⚠️ #FFE7D8 比上一版的 #FFF2EA 深了一档,这是**可以**深的:
上一版卡住不能再深的原因是左边那枚邮票(#FDECE6)会和卡底融掉,
而邮票这一版已经不存在了。现在这块底色只需要跟正文
var(--ink-2) 和标签的 var(--brand) 拉开对比,两者都还很富余。 */
/* ⚠️ 未读也必须**成对**覆盖:只改 --bub 不改 --bub-2,未读气泡会从
#FFE7D8 渐到已读那头的 #FFF6F0,越往下越像已读,等于把信号做没了。 */
.lt-item.is-unread{--bub:#FFE7D8;--bub-2:#FFDCC8}
/* ⚠️ 第二段那层是 .lt-ava 原本的投影,必须写进同一条 box-shadow ——
box-shadow 是整条覆盖的,只写环会把投影顶掉,未读那颗头像反而比已读的还平。
环写在扩散位(第三个 0 后面那个 2.5px),画在圆外面,不吃头像本身的尺寸。 */
.lt-item.is-unread .lt-ava{
box-shadow:0 0 0 2.5px rgba(244,89,60,.45),0 2px 5px rgba(198,104,78,.16)}
/* 标题刻意**不**染成品牌色:气泡底色 + 头像环 + 标签三样已经说清楚了,
再把标题标红,整条就从「有封新信」变成「出事了」,而且标题更难读。 */
/* 来信摘要只留一行,收尾的六点省略号由 clampLine() 补,理由见 .lm-txt 那段。
⚠️ 这里原先还有 padding-bottom:7px + border-bottom:1px dashed —— 那是上一版
模仿信纸横格画的一条虚线。用户点名说这套「有点丑」,而它正是最扎眼的一样:
一条虚线横穿整卡、每卡一条,一屏扫下来全是横线。删掉,摘要和 .lt-meta
之间靠 margin 分开就够了(气泡里本来就不需要再切一刀)。
下边距顺势从 7 收回 6,和上边距对称。 */
.lt-body p{font-size:13px;color:var(--ink-2);line-height:1.65;margin:6px 0;
white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.lt-meta{font-size:11.5px;color:var(--muted)}
.star-txt{color:#E3A11B}
.empty-tip{display:none;text-align:center;color:var(--muted);font-size:13.5px;padding:50px 0}
.empty-tip.show{display:block}
/* ============ 会话式来信 ============
一封来信 = 一个 .lthread:上面是宝宝的信(.lt-item),
下面用一根虚线挂着妈妈的回信和宝宝的再回复(.lt-sub > .lt-msg)。
这样「这条回信是回给哪封信的」在列表里就是自明的,不再另开一页。
(.lthread 本体和它的三个尺寸变量声明在上面来信列表那段开头。)
⚠️ 上一版这里还有一条 .lthread.has-sub .lt-item{border-bottom-*-radius:9px},
作用是把主卡下缘收平、和下面那串连成一条。这一版删了:主卡已经不是
通栏的卡,而是一颗右侧的气泡,而下面那根竖虚线是从**头像**底下垂下来的,
两者根本不在一条竖线上 —— 收平气泡的下缘不会连上任何东西,
只会让气泡少一个角,看着像没画完。 */
.lt-sub{
/* 竖虚线压在头像中线上:线宽 2px,左缘放在 --ava/2 − 1,中心正好是 --ava/2。
于是「宝宝说了一句 → 底下挂着这轮对话」在视觉上是从这颗头像里长出来的。 */
margin-left:calc(var(--ava)/2 - 1px);
/* 缩进到和上面那只来信气泡的左缘齐平,推导见 .lthread 里 --sub-in 那段 */
padding-left:var(--sub-in);
border-left:2px dashed #EBDCD2}
.lt-msg{
display:block;width:100%;text-align:left;position:relative;
border-radius:14px;padding:11px 13px;margin-top:8px;box-shadow:var(--sh);
}
/* 短横线把气泡接到左边那根虚线上。
⚠️ 这两个值必须跟着 --sub-in 走,不能写死:left 正好回退整段缩进(贴到竖线上),
宽度再短 2px,让横线在离气泡 2px 处收住 —— 顶死在气泡边上反而糊成一团。 */
.lt-msg::before{content:"";position:absolute;
left:calc(var(--sub-in) * -1);top:19px;
width:calc(var(--sub-in) - 2px);height:2px;background:#EBDCD2}
/* 回信气泡的底:本批从两块纯色换成渐变(用户要的那点「底纹」),
妈妈那边同时从浅蓝改成浅绿。四个色标都在 :root,改之前先读那段。
⚠️ 160deg 不是随手写的:它让浅的一头落在气泡**顶部**,
而 .lm-who(妈妈绿 / 宝宝品牌橘)那行小字正好在顶部。
改成 20deg 之类把深头翻上来,宝宝那行橘字会从 3.15 掉到 2.9。 */
.lt-msg.mom{background:linear-gradient(160deg,var(--mom-bg-1) 0%,var(--mom-bg-2) 100%)}
.lt-msg.baby{background:linear-gradient(160deg,var(--baby-bg-1) 0%,var(--baby-bg-2) 100%)}
.lt-msg.is-new{box-shadow:0 0 0 2px rgba(244,89,60,.34),var(--sh)}
.lm-who{font-size:11.5px;font-weight:800;display:flex;align-items:center;gap:6px}
.lt-msg.mom .lm-who{color:var(--mom)}
.lt-msg.baby .lm-who{color:var(--brand)}
.lm-when{margin-left:auto;font-size:11px;color:var(--muted);font-weight:600}
/* 妈妈的回信 / 宝宝的再回复:只留一行。
⚠️ 不能再用 -webkit-line-clamp —— 它打的点是浏览器自己画的三点「…」,
CSS 改不了那个字符(text-overflow 的自定义字符串值只有 Firefox 支持,
webview 上一律不认)。要六点「……」只能自己截,见 js 里的 clampLine()。
这里只负责「压成一行、超出就裁」。text-overflow 留着当兜底:
万一量不到宽度(元素还没布局),至少是三点省略号,不会半个字硬断在边上。 */
.lm-txt{font-size:13px;color:var(--ink-2);line-height:1.66;margin-top:5px;
white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
/* 「新回复」角标 —— 需求里的「小红点/新消息」提示 */
.lt-new{background:var(--brand);color:#fff;font-size:10.5px;font-weight:800;padding:2px 7px;border-radius:8px;line-height:1.55}
/* 宝宝还在酝酿(回信要等 3~5 小时) */
.lt-wait{font-size:11.5px;color:var(--muted);margin-top:9px;display:flex;align-items:center;gap:7px}
.lt-wait::before{content:"";width:6px;height:6px;border-radius:50%;background:#D9C8BC;flex:0 0 auto;
animation:newPulse 1.6s var(--ease) infinite}
.fab-write{
position:absolute;right:18px;bottom:calc(var(--tabh) + env(safe-area-inset-bottom) + 16px);z-index:55;
background:var(--brand);color:#fff;font-size:15px;font-weight:700;
padding:14px 22px;border-radius:26px;box-shadow:0 12px 26px rgba(244,89,60,.42);
/* 图标 + 文字:和 .dl-btn.has-ico 同一套对齐 */
display:flex;align-items:center;gap:7px;
opacity:0;visibility:hidden;transform:translateY(14px);transition:opacity .3s var(--ease),visibility .3s var(--ease),transform .3s var(--ease);
}
.fab-write.show{opacity:1;visibility:visible;transform:none}
/* 按钮里的线性图标:不参与 flex 收缩,否则文字长一点就把它压扁 */
.btn-ico{flex:0 0 auto}
/* ============ 我的 ============ */
.me-top{padding:calc(env(safe-area-inset-top) + 18px) 18px 26px;background:var(--grad);border-radius:0 0 32px 32px;color:#fff;position:relative;overflow:hidden}
.me-top::after{content:"";position:absolute;width:220px;height:220px;border-radius:50%;background:rgba(255,255,255,.08);top:-80px;right:-60px}
/* 齿轮按钮直接挂在 .hero-top 上,没有 .hero-actions 包着,
够不到那条 margin-left:auto,所以这里单独把这一排改成靠右 */
.me-top .hero-top{margin-bottom:18px;justify-content:flex-end}
/* 半透明玻璃压在橘色渐变上永远发灰、发脏,文字也只能跟着用白的,读起来累。
改成一张实心暖白卡:橘色只做底衬,卡内回到正常的深色文字层级,
和页面下方的 .preg-card / .list-card 是同一种白卡语言。
── 底纹改成横向,这是第三版,前两版都被同一句话打回来 ──
⚠️ 用户为这张卡提过**两次**同样的意见(「加一点渐变,不要纯白色」
「现在纯白色不好看」)。两次我都只是把色标往深里调 ——
#FFF7F3 → 加个 52% 中间标 → #FFE9DB,两次都没解决。
**问题从来不在颜色深浅,在暖色铺到的面积。**
150deg 是斜的:暖色只落在右下那个角,而卡是 ~300×100 的扁盒子,
对角线方向上「右下角」小得可怜,正中间和左上恒定是白的。
色标再往深调,只会把那个小角调脏,卡整体照样读作白。
→ 所以这一版换成 90deg 横向,让暖色从右边**整片**压过来,
白区收在左边 20%(正好留给头像)。终点 #FFE4D4 其实只比上一版
#FFE9DB 深一点点,但观感差别很大 —— 差在面积,不在那点色差。
→ 下次若还嫌白,**别再动色标**,先想面积和方向。
⚠️ 横向渐变顺带解开了上一版那条「不能再往深里调」的限制:
当时的顾虑是终点会和 .me-avatar 的底(linear-gradient(140deg,#FFE6DA,#FBCFBF))
糊在一起。那是 150deg 才有的问题 —— 斜渐变把暖色送到了头像所在的左侧中部。
现在头像坐在左边 20% 的纯白区里,暖色在它右边,两者根本不相邻。
⚠️ 白区 20% 是给头像留的,别收窄:.me-avatar 是 66px 宽、左边距 18px,
在 ~300px 的卡上占到约 28%。白区若缩到 20% 以下,头像右半边会开始
压在暖色上,那圈 box-shadow 的白边就废了(它就是靠白底才划得出边界的)。
对比度复核(这次是实算的,见下面那笔更正):底色取渐变终点 #FFE4D4,
再叠上右上角那团柔光(rgba(255,196,160,.34))算最坏情况:
--ink #2B2320 11.70:1 ✓ 远超 AA
--ink-2 #6B615B 4.58:1 ✓ 刚过 AA 4.5(余量很薄,见下)
--brand #F4593C 2.52:1 ✗ 不过 AA
⚠️ 上一版这段注释写着「.me-info p b 用 --brand 约 3.3:1」—— **那个数是错的**,
3.31:1 是 --brand 压在**纯白**上的值,不是压在 #FFE9DB 上的。
实算:压 #FFE9DB 是 2.83:1,压现在的 #FFE4D4+柔光是 2.52:1。
也就是说「已连续记录 **42** 天」那个品牌色数字,**在这一版之前就已经不过 AA**,
不是这次改坏的,但这次又薄了 0.3。没有一并修,是因为改它要动全站品牌色的
用法(--brand 压浅底到处都是这个量级),不该夹在一次底纹调整里顺手做。
要修的话:给 .me-info p b 单独配一个压深版品牌色(约 #C9401F 起步才够 4.5)。
⚠️ --ink-2 那 4.58 只比 4.5 高 0.08,**别再加深终点或加大柔光**:
终点走到 #FFE0CC、或柔光提到 .42,它就掉到 4.5 以下了(实算 4.44 / 4.45)。 */
.me-card{display:flex;align-items:center;gap:14px;border-radius:26px;padding:17px 18px;
background-color:#fff;
/* 两层:底下一条横向暖色渐变(白区留给左边的头像),
上面右上角一团柔光,免得整块暖色看着像一条平铺的色带。
和首页 .today-line 是同一套手法(那边也是 radial 柔光 + 90deg 渐变)。 */
background-image:
radial-gradient(128px 92px at 94% 6%, rgba(255,196,160,.34), rgba(255,196,160,0) 72%),
linear-gradient(90deg,#fff 0%,#fff 20%,#FFF1E6 58%,#FFE4D4 100%);
color:var(--ink);
box-shadow:0 14px 32px rgba(140,56,34,.22),0 2px 6px rgba(140,56,34,.10);
position:relative;z-index:2;overflow:hidden;cursor:pointer;transition:transform .18s var(--ease)}
.me-card:active{transform:scale(.985)}
/* 尖括号跟全站 .chev 一个灰度,别再用白色 */
.me-chev{margin-left:auto;flex:0 0 auto;font-style:normal;font-size:24px;line-height:1;color:#C9BFB8}
/* 退出登录图标按钮:住 me-card **右上角**(绝对定位,不占 flex 流)。
⚠️ 只在已登录时显示(applyRemoteProfile 按 p.userName 控制显隐);
未登录显示"退出"没有意义。chev 恢复 margin-left:auto 占最右。 */
.me-card{position:relative}
.me-exit{position:absolute;top:10px;right:44px;color:#C9BFB8;flex:0 0 auto}
.me-exit:active{color:#C33A2E}
/* 头像底从「白蒙版 + 白描边」换成暖橘底 + 品牌色剪影(与 .pc-ico 同一套配色)。
⚠️ 外圈本批从 #FFF3EC 改回了纯 #fff。那圈浅橘白光晕本来是照着
「卡是白的」调的 —— 卡一变暖(终点 #FFE9DB),#FFF3EC 就夹在
卡色和头像底色中间,三层暖色叠在一起,那道圈等于消失了。
用纯白才重新和两边都拉开,头像才有一条清楚的边界。
别跟着卡的渐变把它也调暖,那就白改了。 */
.me-avatar{width:66px;height:66px;border-radius:50%;
background:linear-gradient(140deg,#FFE6DA,#FBCFBF);
display:grid;place-items:center;color:#EE7C5F;border:0;
box-shadow:0 0 0 3px #fff,0 6px 14px rgba(196,110,80,.20);
flex:0 0 auto;position:relative}
/* 上传头像后用 <img> 盖掉默认剪影;没图时 img 不占位 */
.me-avatar img{display:none;position:absolute;inset:0;width:100%;height:100%;object-fit:cover;border-radius:50%}
.me-avatar.has-img img{display:block}
.me-avatar.has-img>svg{visibility:hidden}
/* 白卡上再放白角标会糊掉,所以反过来:品牌色实心 + 白图标,
外面镶一圈白边,压在头像和卡面的交界处也读得出来 */
.av-edit{position:absolute;right:-2px;bottom:-2px;width:21px;height:21px;border-radius:50%;
background:var(--brand);color:#fff;display:grid;place-items:center;
box-shadow:0 0 0 2px #fff,0 3px 8px rgba(196,80,52,.38)}
.me-info{flex:1;min-width:0}
.me-name{font-size:21px;font-weight:800;color:var(--ink);letter-spacing:-.3px}
/* 原来靠 opacity 压层级,在白底上只会发灰,换成正经的次级文字色 */
.me-info p{font-size:13px;color:var(--ink-2);line-height:1.5;margin-top:5px}
.me-info p b{color:var(--brand);font-weight:800}
/* ⚠️ 这里原来有一条 .me-bio(一句话签名的单行截断)。签名整块已按用户要求删掉,
这条规则跟着删了 —— DOM 里再没有 .me-bio 这个元素。
留一条永远选不中东西的规则,下一个人读 CSS 会以为页面上还有这行内容,
然后跑去 JS 里找是谁把它藏起来了。
要加回签名,四处一起加:#pfBio 输入框 / #meBio 展示行 / syncBio() / 这条规则。 */
.sec-title.dark{color:var(--ink)}
/* ---- 孕期信息卡 ----
原来是纯白卡,夹在橘色 .me-top 和下面那张暖橘福利卡中间,自己反倒最没存在感。
⚠️ 这里**不能**也换成暖橘:一页上两张暖卡挨着,等于谁都没被突出,
而且福利卡(讲「白送你一件东西」)本就该是全页最跳的那张。
所以这张必须靠色系和邻居错开,两张卡才各自站得住。
配色改到第五版了,把前四版为什么被打回来写清楚,别再走一遍:
v1 纯白 —— 没存在感
v2 雾蓝 #F2F7FD→#E6EEF9 —— 打回,原话「颜色不是很好」
v3 薰衣草紫→藕荷粉 #E7DDFA→#F7E4F0 —— 又打回,原话「需要有设计感一些」
v4 深靛→紫→玫 #4E3FA8→#7C4FCB→#B0417F —— 打回,原话「那个紫太扎眼了」
⚠️ 复盘的关键在这里:v2 和 v3 是**同一个错**,我第二次才看出来。
v2 我归因成「两端色相拉得太近」,于是 v3 把色相从 2° 拉到 61°,
结果还是被打回 —— 说明色相差不是真正的病根。
**v2 和 v3 都是「浅色水洗」**:三通道全在 220 以上,
不管两端离多远,整张卡在手机上都只是一小片淡色,
而它上面就压着一整条饱和橘色的 .me-top。淡色卡贴在浓色条下面,
再怎么调色相也只会显得虚。所以 v4 不再在浅色里挑颜色,
直接换成**深色卡 + 白字**:靠明度对比站住,而不是靠色相。
v4 深靛→紫→玫 #4E3FA8→#7C4FCB→#B0417F —— 打回,原话「那个紫太扎眼了」
v4 的深色思路**是对的**(打回的是色相,不是明度),所以 v5 原样保留
「深色卡 + 白字」,只把紫换成绿:
v5:墨绿 #1E5A46 → 绿 #2C6B4C → 草绿 #3E6B30,白字。
· 色相 160.0° / 149.6° / 105.8°,仍全在暖橘带(10°–60°)之外,
和福利卡错开的原始目的没丢;
· 首尾跨 54.2°,比 v4 的 77.9° 窄,但**远远大于 v2 死掉时的 2°**,
渐变看得见就够了;
· 明度上和上下两张浅色卡彻底分开 —— v4 挣来的那一半没退。
⚠️ 换色时白字对比度**变好了,不是凑合过**(实测,白 #FFF 压在三个色标上):
v4 紫 8.08 / 5.47 / 5.37 → 最低 5.37
v5 绿 8.06 / 6.34 / 6.27 → 最低 6.27
.pc-sub / .pc-stage 那种 78% 白的副文同理:v4 最低 3.90 → v5 最低 4.54。
末档特意从更漂亮的 #4A7C3C(白字只有 4.95,**比紫还差**)压深到 #3E6B30,
就是为了不拿可读性换颜色。改这三个值之前先把这两组数重量一遍。
⚠️ 别把它调浅回去凑「温柔」。真要调,先想清楚怎么解决
「淡色卡压在饱和橘条下面显虚」这件事,那是 v2/v3 死掉的地方。
⚠️ 也别顺手把它调成 --mom 那支绿(#3F7551)。那支是「妈妈这个人」的语义色,
挪来当一张信息卡的底,等于宣布这张卡属于妈妈 —— 它讲的是宝宝的孕周。
⚠️ 换底色必须连带把卡里**所有**彩色件一起换掉,这张卡的坑比别的卡多,
因为它的零件散在三个地方:
· 这里(图标底、进度槽、进度条、三段文字、标题、副文)
· index.html 的 SVG presentation attribute(进度环 + 宝宝,共 4 处)
· 全局规则里两个**会被继承进来**的颜色 —— 见下面 .chev / .rp 两条
只改背景会得到「深紫底配浅紫环」,比原来的纯白更难看。 */
.preg-card{display:block;width:100%;
/* 两层背景:上层是右上角那点高光,下层才是主渐变。
⚠️ 用多重背景而不是 ::before 画高光,是为了躲开一个真实的层序坑:
这张卡带 .ripple(position:relative 但 z-index:auto,**不产生层叠上下文**),
绝对定位的 ::before 会画在普通流子元素**上面**,高光正好糊住标题;
改用 z-index:-1 又会穿到祖先背景后面去,还得再补 isolation。
高光本来就只是背景的一部分,写成背景层这些问题一个都不存在。 */
background:
radial-gradient(110px 80px at 86% 6%, rgba(255,255,255,.20), rgba(255,255,255,0) 72%),
linear-gradient(140deg,#1E5A46 0%,#2C6B4C 46%,#3E6B30 100%);
border-radius:22px;padding:18px;
/* 阴影跟着卡片自己的色相走,不用全局那个中性灰 var(--sh):
深色卡投一片灰影会显脏,投同色系的影才像这张卡「浮」起来。
⚠️ 这就是「跟着色相走」的代价:换底色时它**必须一起换**。
本批之前是 rgba(78,63,168,.30)(v4 紫的 rgb),底换成绿而影还是紫的话,
卡下面会渗出一圈淡紫 —— 在浅色页面上看得见,但很容易当成「本来就有的氛围」。
下面这个是 v5 首档 #1E5A46 的 rgb。 */
box-shadow:0 10px 24px rgba(30,90,70,.30)}
.pc-row{display:flex;align-items:center;gap:13px}
.pc-ico{width:52px;height:52px;border-radius:15px;background:rgba(255,255,255,.16);display:grid;place-items:center;flex:0 0 auto}
/* ⚠️ 标题这行原来不写 color,靠继承拿到深墨色。底一换成深色,
不显式写白就是「深紫底上的深墨字」—— 等于没有字。 */
.pc-title{font-size:21px;font-weight:800;letter-spacing:-.4px;color:#fff}
.pc-sub{font-size:12.5px;color:rgba(255,255,255,.78);margin-top:4px}
.pc-main{flex:1;min-width:0}
/* ⚠️ 这两条挡的是**继承进来的**全局颜色,是这次换色最容易漏的一处 ——
它们不在这个模块的代码里,grep「preg」「pc-」一个都搜不到:
· .chev 全局是 #C9BFB8(浅灰),画在深紫底上基本看不见;
· .rp(涟漪)全局是 rgba(244,89,60,.16),橘色 16% 铺在深紫上是一团脏影。
两条都只在这张卡里覆盖,不动全局 —— 别的卡还是浅底,全局值在那儿是对的。 */
.preg-card .chev{color:rgba(255,255,255,.75)}
.preg-card .rp{background:rgba(255,255,255,.22)}
/* 底槽和进度条都改成白的透明度分级:深色卡上再挑一个「更深的紫」当底槽,
在 9px 高的条子上根本分不出层次 */
.pc-bar{height:9px;border-radius:5px;background:rgba(255,255,255,.22);margin:16px 0 10px;overflow:hidden}
.pc-bar i{display:block;height:100%;width:var(--p);background:linear-gradient(90deg,rgba(255,255,255,.85),#fff);border-radius:5px}
.pc-stage{display:flex;justify-content:space-between;font-size:11.5px;color:rgba(255,255,255,.85)}
/* 当前段从「换个字色」升级成一颗白药丸:深色卡上只靠白色深浅分主次,
11.5px 的小字拉不开差距;反白一块才一眼看得出走到哪一段了。
⚠️ margin 上下各 -3px 抵掉 padding,保证这一行的高度和没药丸时一样 ——
不然这张卡会因为「当前段」比兄弟高 6px 而整体长高。
⚠️ .cur 是 syncPregnancy() 按孕周在三段之间**搬来搬去**的,
药丸可能落在第一段或第三段(贴着卡的左右内边)。space-between
三段都放得下:早/晚期 3 字≈34.5px、中期「孕中期 · 当前」7 字≈80px,
加内边距最宽 98px,三段合计 167px < 可用 248px。 */
/* ⚠️ 药丸里的字色是**卡底渐变的首档**,不是随便一个深色 ——
白药丸是从卡上「挖」出来的,字色取卡自己的深色才像同一张卡上的东西。
本批底色换绿,这里跟着从 #4E3FA8 换成 #1E5A46(白底上 8.06:1)。
漏改的话:绿卡上一颗白药丸、里面躺着一个紫字,这是全卡最后一处紫。 */
.pc-stage .cur{color:#1E5A46;background:#fff;font-weight:700;
padding:3px 9px;margin:-3px 0;border-radius:99px}
/* ---- 试用装福利卡(企业微信入口)----
和 .preg-card 同样的大圆角,是「我的」页上第二张真正的卡;
刻意不长得像下面 .list-card 里那些行 —— 它不是一条设置项。
底色从纯白换成暖渐变:这张卡现在讲的是「白送你一件东西」,
跟周围一水儿的白底卡拉开,福利感才立得住。 */
/* ⚠️ 这一版是**竖向三段**(不是四段,别照旧记忆数):
.wx-top 角标行(右对齐)/ .wx-body 图文行 / .wx-cta 通栏按钮。
标题行 .wx-head **不是**卡的直接子元素,它在 .wx-body 的右列 .wx-main 里,
和价格共用同一条左缘 —— 那是用户要的「放到到手价上边、跟到手价对齐」。
演化:左右两列 → 三段(标题独占一行)→ 四段(角标再提一行)
→ 又回到三段(标题行搬进右列)。每一步都是被账或被用户原话逼的。
⚠️ 两笔账的**分母不一样**,别混用:
角标行量的是**卡内净宽 248**(320 − 36 − 36);
标题行量的是**右列 160**(248 − 图 76 − gap 12)。
拿 248 去算标题行会得出「宽得很,都塞得下」的反结论。
角标为什么必须自己占一行(按现在的 160 算):
商品名「可心柔婴儿纸巾」7 × 16 = 112,
再塞 gap 6 + 「限量福利」角标 60 = 178 > 160 ✗ 差 18px。
而且这还是 1.0 倍;微信字体滑块最大档 1.35 倍下商品名自己就占 151.2,
160 里只剩 8.8px,角标连一个字都进不去。
所以角标只能自己占一行、右对齐 —— 那也正是用户要的「放到右上角」。
⚠️ .wx-card 的 flex-direction 是 column,**不是** row。
谁改回 row(或删掉 direction 退回默认),三段会并排挤成一条,
而且**不报错**,只是难看。smoke 钉了一条。
⚠️ 也别加 flex-wrap:竖排容器里 wrap 只在高度溢出时生效,在这儿是纯噪声,
还会让下一个人以为有换行逻辑。 */
.wx-card{display:flex;flex-direction:column;width:100%;
background:linear-gradient(135deg,#FFF4EC,#FFE6D9);
border-radius:22px;padding:16px 18px;box-shadow:var(--sh)}
/* ---- 第一段:角标行 ----
⚠️ 用 justify-content:flex-end 把角标推到右上角,**不是** position:absolute。
这是硬约束不是风格选择:.wx-card 挂着 .ripple,.ripple 带 overflow:hidden
(style.css:135),任何骑边(绝对定位 / top 负值)的角标都会被裁掉半截 ——
用户上一轮说的「限量福利被切了」正是这么来的。
修法也**不是**给卡加 overflow:visible:那会把涟漪放出圆角卡外。
走正常流 + 右对齐,物理上就不存在被裁的可能。
⚠️ 这一行只装角标一件,看着「浪费」了一行高度。别为了省它把角标并进别处:
角标 4×10.5 + 内边 18 = 60px,塞进现在只有 160px 的右列必然溢出,
而溢出的表现是商品名被省略号啃掉、且不报错。
18px 行高换商品名完整可见,这笔划算。 */
.wx-top{display:flex;justify-content:flex-end;margin-bottom:8px}
/* ---- 第二段:图文行(图 + 右列) ----
⚠️ align-items 必须是 **stretch**,不能是 center。这是用户「左边整个的位置
都是留给图片」那句话的**全部实现**:只有父容器 stretch,下面 .wx-fig 的
align-self:stretch 才有效,图片才会长满左列整个高度。
改回 center 的话图片缩回自己的内容高,左边空出一块,
而且**不报错、不溢出** —— 只有真机上看得出来。smoke 里有断言钉着这条。
gap 13 → 12:右列从 147px 加宽到 160px,这 1px 也是凑出来的(见 .wx-main)。 */
.wx-body{display:flex;align-items:stretch;gap:12px;margin-top:12px}
/* 商品占位图(现在是内联 SVG 抽纸,待换真图)。
⚠️ 宽度写死在容器上而不是交给里面的 svg/img 自己撑:换成真实商品图那天,
图的原始比例不会把这一格顶大、把右边标题挤没。
⚠️ 但**高度故意不写死**,用 align-self:stretch + min-height:92px:
stretch 让图跟着右列一起长 —— 这是用户「左边整个的位置都是留给图片」
那句要求的**实现本身**。父级 .wx-body 必须同时是 align-items:stretch,
缺一条图就缩回自己的内容高,而且不报错、只在真机上看得出来
(smoke 里两条都钉着)。
⚠️ min-height:92 本批起**已经不再起作用**,留着只是个地板。
标题行改竖排之后右列变高了:
.wx-head = 20.8(标题 16×1.3) + gap 3 + 14.95(限领 11.5×1.3) = 38.75
+ gap 5 + 26(价格) + gap 5 + 18.1(副文一行) ≈ **92.9**
(副文折两行时 ≈ 111)
92.9 恒大于 92,撑高度的活已经全归 stretch 了。别看到这个数就以为
它还在管事,也别为了「对齐」把它调成 93 —— 调了没有任何效果。
88 → 76:这是上一版**横排**标题行留下的数(那时右列要塞 152px 的商品名 + 限领)。
⚠️ 竖排之后这条约束没了:现在右列最宽的是副文 138px,图理论上能回到 88
(右列剩 148,仍 ≥ 138)。本批**没有改回去** —— 余量会从 22px 掉到 10px,
而用户这次并没有说图小了。真要动先把下面 .wx-main 那笔账重算一遍。
⚠️ background 不能省。里面是**内联 svg**,object-fit 对它**不生效** ——
生效的是 preserveAspectRatio(默认 xMidYMid meet = contain)。
64×64 的方图进 76×92 的框会上下各留 8px 空条,露出卡的橙色渐变。
#FFF1E6 取的是 svg 自己那张 <rect> 的颜色,接缝就看不见了。
别改成 slice(cover)—— 那会把抽纸盒裁掉头尾。 */
.wx-fig{flex:0 0 76px;width:76px;align-self:stretch;min-height:92px;
background:#FFF1E6;line-height:0;border-radius:16px;overflow:hidden}
.wx-fig>svg,.wx-fig>img{display:block;width:100%;height:100%;object-fit:cover}
/* 角标本体。⚠️ 它现在住在 .wx-top 里(卡的右上角),**不再**是 .wx-main 的
第一件。别再给它 align-self:flex-start —— 那是它待在右列时用来「缩成胶囊」的,
现在父容器是 justify-content:flex-end 的单件行,那条已经没有意义。
⚠️ 这里**永远**不要出现 position:absolute / top 负值,理由见 .wx-top 上面那段。
「电商化」这一版给它换成了品牌色斜向渐变 + 稍重的投影:促销角标本来就该比
正文更跳,但仍克制在一枚小胶囊里,不做成撕裂丝带那种廉价样式。 */
.wx-badge{font-size:10.5px;font-weight:800;
color:#fff;background:linear-gradient(135deg,#E86A3E,#D8452F);
border-radius:9px;padding:3px 9px;
letter-spacing:.3px;box-shadow:0 2px 6px rgba(216,95,56,.32)}
/* 图文行的右列:竖排 标题行 / 价格 / 副文。
⚠️ 本批把 .wx-head(商品名 + 限领)**搬进了这一列**,排在 .wx-price 之前。
用户的原话是「商品名放到到手价上边,跟到手价那个对齐」——
同属这一列 = 共用同一条左缘 = 「对齐」。别再把它提回卡的直接子元素。
⚠️ CTA 和角标仍然**不在**这一列(在卡的直接子元素 / .wx-top),别在这儿找。
⚠️ 320 屏上这一列是 **160px**:
320 − 页边距 36 − 卡内边距 36 − 图 76 − 一道 gap 12 = 160
标题行本批起**竖排**,两件各占一整行、互不抢宽度:
商品名「可心柔婴儿纸巾」7×16 = 112 ✓ 余 48px
「限领一份」 4×11.5 = 46 ✓ 余 114px
价格行「到手价」3×11.5 = 34.5 + gap 5 + 「¥0」≈ 26 ≈ 66 ✓ 余 94px
副文「加小汤圆的孕育顾问领取」11×12.5 ≈ 138 ✓ 余 22px(允许折两行)
⚠️ 全列最紧的一行现在是**副文**(138/160,余 22px),不再是标题行。
上一版标题行横排、只余 8px,微信那个全局字体滑块一调大就把商品名啃成
「可心柔婴儿纸…」—— 那正是用户本批报的 bug,别把它装回来。
justify-content:center 保留:右列自己现在就有 92.9px 高、和图基本齐平,
正常情况下居中不产生位移;它兜的是「以后图被调高」那种情形。 */
.wx-main{flex:1;min-width:0;display:flex;flex-direction:column;justify-content:center;gap:5px}
/* ---- 标题行(住在 .wx-main 里,右列的第一件) ----
⚠️ 对齐用的是 flex-end(底对齐),**不是** baseline,虽然 baseline 才是
两段不同字号文字并排的正解。原因是 .wx-title 带 overflow:hidden
(ellipsis 必须的)—— 按 CSS Inline / Flexbox 的规定,overflow 非 visible
的块容器**没有可用基线**,浏览器会拿它的 border box 底边合成一条,
结果就是「限领一份」被顶到标题上方去,看着像上标。
这个坑不会报错、只在真机上看得出来。
底对齐的残留误差(字号降了之后重算过):标题 15px/1.3 = 19.5 高,
基线离底边 (19.5−15)/2 + 0.12×15 ≈ 4.05px;限领 10.5px/1.3 = 13.65 高,
基线离底边 (13.65−10.5)/2 + 0.12×10.5 ≈ 2.84px。
两条基线差 ≈ 1.2px —— 肉眼看不出来,比 baseline 塌掉那种错法小一个量级。
gap 6 → 5:右列 160px 里要塞 105 + 42 = 147,gap 只能给 5 才留得下 8px 余量。
==== 本批改成竖排,上面这整段横排的账作废(留着是为了说明为什么不能改回去)====
用户报「商品名被切成『可心柔婴儿纸…』」。真因不是写错了,是那 8px 余量本身:
它是按 15px 字号**死算**出来的,而微信有一个全局字体大小滑块,用户一旦调大,
105px 的商品名当场涨到 130+,8px 余量瞬间蒸发,ellipsis 就啃掉了商品名。
横排方案的根子问题是「两件抢同一条 160px」,再怎么调字号都只是把临界点往后推。
竖排之后标题独占整行,商品名可用宽度从 113(160−5−42) 变成 160,
放大 1.5 倍都还装得下 —— 宽度争抢从根上消失了。
⚠️ 别改回 flex-direction:row 去「省一行高度」,那等于把这个 bug 装回来。
⚠️ align-items:flex-start 不能省:默认 stretch 会把两个 span 拉成满列宽,
「限领一份」的灰底/间距一旦以后加上去就会横贯整列。 */
.wx-head{display:flex;flex-direction:column;align-items:flex-start;gap:3px;min-width:0}
/* ---- 标题行的两件 ----
⚠️ 上一版这两件横排、抢同一条 160px,所以配过一组「伸缩性相反」的规则
(.wx-title flex:1 1 auto 可压缩,.wx-lim flex:0 0 auto 不可压缩,
意思是「真溢出时宁可啃商品名,也不让那句话消失」)。
**本批改竖排之后那组 flex 规则已经全部删掉**:两件各占一整行,
「谁先被压缩」这个问题根本不存在了。
⚠️ 别再照着那套旧描述往回加 flex:1 1 auto / flex:0 0 auto ——
在 column 容器里它们管的是**高度**,加上去只会让这两行开始互相挤压。
⚠️ min-width:0 保留,配合 max-width:100%。.wx-head 现在是 column flex,
这两个 span 在交叉轴上仍会被长内容撑出去;两条一起才能保证超长商品名
在本列内触发 ellipsis,而不是把整张卡顶宽。
⚠️ 字号 15 → 16。上一版压到 15 是横排时被 160px 挤的(16.5 会当场溢出);
竖排之后标题独占整行,7 字 × 16 = 112 ≤ 160,还剩 48px ——
够微信字体滑块放大到 1.42 倍仍不触发省略号。
没直接回到 16.5 是留一档安全余量:16.5 时只剩 1.38 倍。
⚠️ ellipsis 三件套(overflow/text-overflow/white-space)保留当兜底。
现在它基本不该再触发 —— 真触发了说明字号又被谁调大了,去看上面那笔账。 */
.wx-title{max-width:100%;min-width:0;font-size:16px;font-weight:700;color:var(--ink);
line-height:1.3;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* 「限领一份」。本批按用户要求移到商品名**下面独占一行**(原来并排在右侧)。
它是商品名的附注,不该和商品名一样重,所以仍是小字。
⚠️ 字号 10.5 → 11.5,和资源库 banner 的 .rlg-lim 对齐。
这是补一个真实的不一致:用户早先要求两张卡「一模一样」,但上一版
这边被 160px 逼到 10.5、那边是 11.5 —— 文案逐字相同、**字号却差一档**,
并排看得出来。竖排腾出宽度后没有理由再小一档了。
(smoke 那条断言只比对文案字符串,比不出字号差,所以它一直是绿的。)
⚠️ 文案和 .rlg-lim **必须逐字相同**,smoke 里有断言钉着。改这句就得同时改那句。
⚠️ 现在这行有整整 160px,「(每人限领一份)」8 字 × 11.5 = 92 也放得下 ——
但本批**不擅自改回长文案**:用户这次只要求挪位置,没说要改字。
⚠️ 别把这句合回 .wx-title 凑成一个字符串:合成一个字符串就只剩一个字号,
「限领」不再是商品名底下那行灰色小字,用户要的那个视觉层级整个没了。
颜色比 --ink-2(#6B615B) 再淡一档。这里写死不用变量:全站没有 --ink-3,
var(--ink-3,#…) 那种写法会让人误以为存在这么个变量。 */
/* ⚠️ 这里**没有** align-self:flex-start,是特意删掉的,别再加回来:
父级 .wx-head 已经写着 align-items:flex-start,子项上再写一遍是句空话。
变异测试把它删掉时一条断言都不红 —— 不是断言漏了,是它本来就不产生效果。
(.rlg-lim 那处一模一样,也一并删了。) */
.wx-lim{white-space:nowrap;font-size:11.5px;color:#8E827C;line-height:1.3}
/* ---- 价格行 ----
这一版「电商化」唯一新增的语义件:一个小号「到手价」+ 一个大号「¥0」。
⚠️ 只有到手价,**没有划掉的原价**。原价得编一个数字,而这是真实品牌的
真实商品,划线价属于价格宣称,编一个就是虚假标价。
「¥0」不是新承诺 —— 它就是这张卡从第一版起写着的「立即免费领取」
换成电商的排版语言再说一遍。
⚠️ <i> 必须显式 font-style:normal:浏览器默认样式会把它斜过来,
中文没有真正的斜体,浏览器只能几何倾斜,糊得厉害。
这里用 <i>/<b> 而不是两个 <span>,是为了在 HTML 里一眼能分出主次。 */
/* ⚠️ margin-top 去掉了:.wx-main 现在是 justify-content:center 的竖列,
价格和副文靠 gap:5px 拉开。留着 margin-top 会和 gap 叠加,把这一列顶偏。
这里 align-items:baseline 是**安全**的(和 .wx-head 那处相反):
这两件都是普通行盒,没有 overflow:hidden,基线是真实存在的。 */
.wx-price{display:flex;align-items:baseline;gap:5px}
.wx-price>i{font-style:normal;font-size:11.5px;color:var(--ink-2)}
.wx-price>b{font-size:26px;font-weight:800;color:var(--brand);letter-spacing:-.5px;line-height:1}
/* ⚠️ 这一行装的是「加<小名>的孕育顾问领取」,而小名那格是 maxlength="12",
整句能到 20 字上下。
所以必须是两行截断,不能沿用原来的单行省略 ——
单行会把后半截(含小名)整个吃掉,而 textContent 不受 CSS 截断影响,
测试里那条 /小汤圆/ 照样绿,屏幕上却什么都看不见。这是个静默失败。 */
/* ⚠️ margin-top 同样去掉了,理由和 .wx-price 一样:父列已经有 gap:5px。 */
.wx-sub{display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;
font-size:12.5px;color:var(--ink-2);line-height:1.45;overflow:hidden}
/* ---- 第四段:通栏 CTA ----
用实心按钮而不是一个 › :这张卡要的是「点我」,不是「还有下一层」。
现在它是 .wx-card 的**直接子元素**,靠 column 容器的 stretch 铺满整卡 248px
(上一版只占右列 159px)。整卡宽的实心按钮是电商卡的标准收尾。
⚠️ 别再给它 flex:0 0 100%。那是更早一版配合 .wx-card 的 flex-wrap 用的;
在 column 容器里 flex-basis 作用于**高度**,会把这条按钮拉成整卡高。
⚠️ text-align:center 不能省:全局 button{text-align:left}(style.css:80)
会一路继承下来,不写死的话文字贴在左边,看着像一条被拉长的标签。 */
.wx-cta{margin-top:13px;text-align:center;font-size:15.5px;font-weight:800;color:#fff;
background:linear-gradient(135deg,#E86A3E,#D8452F);
border-radius:14px;padding:12px;letter-spacing:.3px;
box-shadow:0 4px 12px rgba(216,95,56,.3)}
.list-card{background:#fff;border-radius:22px;padding:0 16px;box-shadow:var(--sh)}
.li{display:flex;align-items:center;gap:13px;width:100%;padding:16px 0;border-bottom:1px solid var(--line)}
.li:last-child{border:0}
/* 列表图标已全部换成扁平描边矢量,各自给一个与底色同系的描边色 */
.li-ico{width:38px;height:38px;border-radius:11px;display:grid;place-items:center;flex:0 0 auto}
.li-ico.y{background:#F4F0D8;color:#9A8A2E}
.li-ico.p{background:#EFE9FA;color:#7B5FD4}
.li-ico.o{background:#FBEDE3;color:#DE7443}
.li-ico.b{background:#E6EFF8;color:#4478BC}
.li-ico.r{background:#FBE9E7;color:#C33A2E}
.li-t{font-size:16.5px;font-weight:700}
.li-v{margin-left:auto;font-size:13px;color:var(--brand);font-weight:600}
.li .chev{margin-left:auto}
.li-v + .chev{margin-left:6px}
/* ============ 详情页 ============ */
.detail{
position:absolute;inset:0;z-index:80;background:var(--bg);
transform:translateX(100%);transition:transform .38s var(--ease);
display:flex;flex-direction:column;
}
.detail.show{transform:none;box-shadow:-10px 0 30px rgba(0,0,0,.1)}
.detail-bar{display:flex;align-items:center;gap:10px;padding:calc(env(safe-area-inset-top) + 14px) 18px 12px;background:rgba(248,243,239,.9);backdrop-filter:blur(14px);border-bottom:1px solid var(--line)}
.dt-back{font-size:32px;line-height:.7;color:var(--ink);width:34px}
.dt-title{font-size:17px;font-weight:800}
.dt-star{margin-left:auto;font-size:23px;color:var(--muted);transition:transform .25s var(--ease)}
.dt-star.on{color:#FFC93C}
.dt-star:active{transform:scale(1.3)}
.detail-body{flex:1;overflow-y:auto;padding:22px 22px 40px}
.detail-body::-webkit-scrollbar{display:none}
/* ---- 一整封信(抬头 + 语音 + 正文,同一张卡)----
整张卡就是一页白信纸:抬头和正文同底色、不分块。
层次全靠字号阶梯(11.5 → 27 → 16)和留白拉开。
在此之前试过「暖色抬头块 + 圆邮戳 + 齿孔撕口 + 大引号」,
装饰太多,每一样都在跟正文抢注意力,全部撤掉了。 */
.dl-letter{position:relative;background:#fff;border-radius:24px;overflow:hidden;box-shadow:var(--sh-lg)}
.dl-head{position:relative;padding:26px 24px 0}
/* 抬头小字:孕周 · 日期。字距拉开当作「眉批」,和大标题形成疏密对比 */
.dl-eyebrow{display:flex;align-items:center;gap:8px;font-size:11.5px;font-weight:700;
letter-spacing:1.1px;color:#B39C8E;margin:0}
.dl-eyebrow i{width:3px;height:3px;border-radius:50%;background:#E2CDBE;flex:0 0 auto}
/* 标题和正文一起用圆体,不然「小朋友的信」上下两截字形不搭。
不再留右侧 padding —— 邮戳已经去掉,标题可以用满整行 */
.dl-head h1{font-family:var(--font-round);font-size:27px;font-weight:800;line-height:1.34;
margin:9px 0 0;letter-spacing:0;color:#33261F}
/* 手绘波浪线:宽度按内容给个定值,preserveAspectRatio:none 会横向拉伸,
所以高度单独锁死,免得线随宽度变粗 */
.dl-wave{display:block;width:96px;height:9px;margin-top:11px}
/* 语音条紧跟标题。信纸是纯白的,语音条得有个淡暖底才浮得起来 */
.dl-head .voice-card{margin-top:18px;background:#FDF5F0;
box-shadow:0 6px 16px rgba(178,122,98,.1)}
/* ---- 信纸正文 ----
字号 17.5 → 16、行高 2.05 → 28px,都是往回收:原来一屏放不下几行,
读起来一直在滚。 */
.dl-body{position:relative;padding:20px 24px 26px}
/* 极淡的横格纹。关键是把它画在每个 <p> 自己身上而不是整块 .dl-text 上:
段间距(14px)不是行高的整数倍,画在外层的话,第二段起格线就和字错位了。
逐段绘制,每段的格线都从自己的第一行重新起算,永远对得上。
27→28px 那 1px 就是格线,压在每行文字的下缘。 */
.dl-text p{position:relative;font-family:var(--font-kid);font-size:16px;line-height:28px;
letter-spacing:.4px;color:#3A312C;margin:0 0 14px;
background-image:repeating-linear-gradient(to bottom,transparent 0,transparent 27px,#F4ECE4 27px,#F4ECE4 28px)}
.dl-text p:last-child{margin-bottom:0}
/* 落款靠右,走暖棕色的墨水调 —— 不用品牌红,免得像个可点的按钮。
原来名字上面有条横杠、下面还坠一行孕周,两样都删了:
孕周在页首的眉批里已经写过一次,落款只留一句话最像手写签名。 */
.dl-sign{position:relative;margin-top:22px;display:flex;justify-content:flex-end}
.ds-name{font-family:var(--font-kid);font-size:17px;font-weight:800;color:#7A6154;letter-spacing:.4px}
/* 每个字单独一个小方块,带一点点随机的旋转和上下错位 ——
没装翩翩体的机器(安卓 / Windows)也能有「小朋友写的」那种歪扭劲。
标点由 kidText() 黏在前一个字里,所以不会出现「。」跑到行首。 */
.kc{display:inline-block}
/* ---- 详情页里这封信下面的往来 ---- */
.dl-thread:not(:empty){margin-top:28px}
.dl-sec{font-size:13px;font-weight:800;color:var(--muted);display:flex;align-items:center;gap:9px;margin-bottom:13px}
.dl-sec::after{content:"";flex:1;height:1px;background:var(--line)}
/* 左侧一道竖色条,一眼分得清是妈妈写的还是宝宝回的 */
.dl-msg{position:relative;border-radius:18px;padding:14px 15px 14px 18px;margin-bottom:11px;overflow:hidden}
.dl-msg::before{content:"";position:absolute;left:0;top:0;bottom:0;width:3px}
/* ⚠️ 这两条底色必须和列表页的 .lt-msg.mom/.baby **同源**(同一组 :root 变量)。
同一条回信在列表里是一块底、点进详情又是另一块底,是那种
「说不出哪儿别扭」的不一致。以前是两处硬编码 #EFF5FC/#FFF4EF 各写一遍,
本批一起换成变量,以后改一处就够。 */
.dl-msg.mom{background:linear-gradient(160deg,var(--mom-bg-1) 0%,var(--mom-bg-2) 100%)}
.dl-msg.mom::before{background:var(--mom)}
.dl-msg.baby{background:linear-gradient(160deg,var(--baby-bg-1) 0%,var(--baby-bg-2) 100%)}
.dl-msg.baby::before{background:#E8A98F}
.dm-who{font-size:12.5px;font-weight:800;display:flex;align-items:center;gap:8px}
.dl-msg.mom .dm-who{color:var(--mom)}
.dl-msg.baby .dm-who{color:var(--brand)}
.dm-when{margin-left:auto;font-size:11px;color:var(--muted);font-weight:600}
/* white-space:pre-wrap 保住妈妈手写回信里的换行 */
.dm-txt{font-family:var(--font-round);font-size:15px;line-height:1.92;color:#3A312C;margin-top:8px;white-space:pre-wrap}
/* 只有宝宝的回复用卡通手写体,妈妈那条保持圆体 —— 两边字形不一样,
谁在说话不用看标签也认得出来 */
.dl-msg.baby .dm-txt{font-family:var(--font-kid);font-size:16px;line-height:2}
.dl-wait{font-size:12.5px;color:var(--muted);background:#F7F0EA;border-radius:16px;padding:13px 15px;line-height:1.75}
/* ---- 「肚子里的声音」----
宝宝偶尔发的语音不是宝宝在说话,而是它在羊水里听到的世界:
水声、心跳、外面爸爸妈妈的说话声。声音由 WebAudio 实时合成,
所以单文件产物里不需要任何音频资源。 */
.voice-card{background:linear-gradient(135deg,#FFF2EB,#FBE3D8);border-radius:20px;padding:16px;margin-top:24px}
.vc-lead{font-size:13.5px;color:var(--ink-2);line-height:1.75}
.vc-row{display:flex;align-items:center;gap:13px;margin-top:13px}
.vc-play{flex:0 0 auto;width:44px;height:44px;border-radius:50%;background:var(--brand);color:#fff;
font-size:14px;display:grid;place-items:center;box-shadow:0 8px 18px rgba(244,89,60,.34);transition:transform .16s}
.vc-play:active{transform:scale(.93)}
.vc-wave{flex:1;display:flex;align-items:center;gap:3px;height:30px}
.vc-wave i{flex:1;background:rgba(244,89,60,.4);border-radius:2px;height:24%}
.voice-card.playing .vc-wave i{animation:vcWave 1.15s var(--ease) infinite}
.vc-wave i:nth-child(2n){animation-delay:.16s}
.vc-wave i:nth-child(3n){animation-delay:.33s}
.vc-wave i:nth-child(5n){animation-delay:.52s}
@keyframes vcWave{0%,100%{height:24%}50%{height:100%}}
.vc-note{font-size:11.5px;color:var(--muted);margin-top:11px;line-height:1.65}
.dl-actions{display:flex;gap:11px;margin-top:26px}
.dl-btn{flex:1;text-align:center;padding:15px;border-radius:22px;font-size:15.5px;font-weight:700;transition:transform .16s}
.dl-btn:active{transform:scale(.96)}
/* 带图标的按钮转成 flex(.dl-btn 默认靠 text-align:center 居中,塞进 svg 后对不齐)。
用显式的 .has-ico 而不是 :has(.btn-ico),是为了不挑 webview 版本 */
.dl-btn.has-ico{display:flex;align-items:center;justify-content:center;gap:7px}
.dl-btn.ghost{background:#fff;color:var(--ink-2);box-shadow:var(--sh)}
.dl-btn.main{background:var(--brand);color:#fff;box-shadow:0 10px 22px rgba(244,89,60,.34)}
/* 「生成图片」这颗。以前是只有图标的 52×52 方钮,现在带上了四个字。
ico-txt = 不参与均分,宽度由内容自己撑(flex:0 0 auto),把剩下的全让给回信。
为什么不让两颗平分:这两颗的分量不一样,回信是主操作,
平分会让「生成图片」看起来和它一样重要。
320 屏上的宽度账(.detail-body 内宽 276,两颗之间 11):
#dlCard = padding 13×2 + 图标 17 + gap 7 + 「生成图片」4 字 ×15.5 ≈ 112
#dlReply = padding 15×2 + 图标 17 + gap 7 + 「给小汤圆回信」6 字 ×15.5 ≈ 147
112 + 147 + 11 = 270,富余 6px。
padding 从 15 收到 13 就是为了这 6px —— 按 15 算总宽 274,只剩 2px,
字体渲染差一点点就会挤到换行。
⚠️ 富余量是留给默认小名的。小名用户能自己填,超了由下面 #dlReplyTxt 兜。 */
.dl-btn.ico-txt{flex:0 0 auto;padding-left:13px;padding-right:13px;white-space:nowrap}
/* 小名是用户自己填的,长度没有上限。
min-width:0 让 flex 项允许被压缩,文字层自己省略号截断,
否则按钮会被文字撑破、整排换行。
⚠️ 这里只能用 CSS 的三点省略号,不是站内统一的六点 —— 见 clampLine() 那段注释,
text-overflow 的自定义字符串只有 Firefox 认。按钮里的字短,退一步可以接受。 */
#dlReply{flex:1;min-width:0}
#dlReplyTxt{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* 内容换成 SVG 之后要用 grid 居中,否则图标会贴着基线偏下 */
.dt-plain{margin-left:auto;line-height:1;color:var(--ink-2);width:30px;height:30px;display:grid;place-items:center}
.dt-plain:active{opacity:.5}
.d-sec{display:flex;align-items:center;font-size:16.5px;font-weight:800;color:var(--ink);margin:26px 0 13px}
.d-sec .sec-more{font-size:12.5px}
/* ============ 设置与服务 二级页 ============ */
.st-lead{font-size:13.5px;line-height:1.75;color:var(--ink-2);margin-bottom:4px}
.st-card{background:#fff;border-radius:22px;padding:0 16px;box-shadow:var(--sh)}
.st-card:first-child{margin-top:16px}
.st-row{display:flex;align-items:center;gap:12px;width:100%;padding:15px 0;border-bottom:1px solid var(--line);position:relative;overflow:hidden}
.st-row:last-child{border-bottom:0}
.st-rt{font-size:15.5px;font-weight:700;min-width:0}
/* 副标题挂在标题下面,em 撑成块级,行内不会把开关挤走 */
.st-rt em{display:block;font-size:12px;font-weight:500;font-style:normal;color:var(--muted);margin-top:3px;line-height:1.5}
.st-rv{margin-left:auto;font-size:12.5px;color:var(--muted);flex:0 0 auto;max-width:52%;text-align:right}
.st-row .chev{margin-left:auto;color:var(--muted)}
.st-rv + .chev{margin-left:6px}
/* 注意:不能叫 .st-txt —— 首页「今日成长脚印」的文字已经占了这个名字,
同名会把 padding 和分隔线漏到首页去,所以这里用 .st-para */
.st-para{font-size:14px;line-height:1.85;color:var(--ink-2);padding:15px 0;border-bottom:1px solid var(--line)}
.st-para:last-child{border-bottom:0}
/* 开关:轨道 40×24,圆点 18,开时右移 16px(40-18-2×3) */
.st-row.sw{cursor:pointer}
.sw-track{margin-left:auto;flex:0 0 auto;width:40px;height:24px;border-radius:12px;background:#E2DAD3;position:relative;transition:background .24s var(--ease)}
.sw-track b{position:absolute;left:3px;top:3px;width:18px;height:18px;border-radius:50%;background:#fff;box-shadow:0 1px 3px rgba(0,0,0,.2);transition:transform .24s var(--ease)}
.st-row.sw.on .sw-track{background:var(--brand)}
.st-row.sw.on .sw-track b{transform:translateX(16px)}
/* 关于页的品牌头 */
.st-about{text-align:center;padding:14px 0 22px}
.st-logo{width:78px;height:78px;margin:0 auto 14px;border-radius:22px;background:linear-gradient(140deg,#FDE3D6,#F9C9B4);display:grid;place-items:center;box-shadow:var(--sh)}
.st-logo svg{width:46px;height:46px}
.st-appname{font-size:20px;font-weight:800;letter-spacing:-.3px}
.st-ver{font-size:12.5px;color:var(--muted);margin-top:6px}
/* ============ AI 小结详情 ============
页面节奏:深 → 浅 → 线。
① 今日卡是全页唯一的深色块,一进来眼睛就落在它上面;
② 三条建议是白卡,靠左侧分类色条区分主题;
③ 往期是一条时间轴,弱化成背景信息。
原来三段都是同一种白卡罗列,谁也不比谁重要,所以看着散。 */
.ai-body{background:linear-gradient(180deg,#FFF6F1 0%,var(--bg) 240px)}
/* ---- ① 今日卡 ---- */
.ai-today{position:relative;overflow:hidden;border-radius:26px;padding:20px 20px 6px;
background:linear-gradient(145deg,#4A3A34 0%,#6B4A3C 55%,#8A5340 100%);
box-shadow:0 18px 34px rgba(90,58,44,.3)}
/* 两团柔光,让深色底不至于死板;纯 CSS,不额外加图 */
.ai-glow{position:absolute;width:190px;height:190px;border-radius:50%;pointer-events:none;
background:radial-gradient(circle,rgba(255,166,120,.5),transparent 70%);top:-96px;right:-54px}
.ai-glow.b{width:150px;height:150px;background:radial-gradient(circle,rgba(255,255,255,.16),transparent 70%);
top:auto;right:auto;bottom:-72px;left:-40px}
.ai-t-top{position:relative;display:flex;align-items:center;gap:8px}
.ai-week{font-size:12.5px;font-weight:800;color:#FFD9C4;background:rgba(255,255,255,.14);
padding:5px 10px;border-radius:8px}
.ai-date{margin-left:auto;font-size:12px;color:rgba(255,255,255,.62)}
/* 今日状态:一枚小圆点 + 两个字,替掉原来那个撑场面的大引号 */
.ai-mood{position:relative;display:inline-flex;align-items:center;gap:6px;margin-top:16px;
font-size:12.5px;font-weight:800;color:#FFC9A8;letter-spacing:1px}
.ai-mood::before{content:"";width:7px;height:7px;border-radius:50%;background:#7BD3A0;
box-shadow:0 0 0 4px rgba(123,211,160,.22)}
.ai-hero-txt{position:relative;font-size:19px;font-weight:700;line-height:1.75;color:#fff;
margin:9px 0 0;letter-spacing:.2px}
.ai-stats{position:relative;display:flex;margin-top:18px}
.ai-st{flex:1;text-align:center;padding:14px 4px 16px;position:relative}
/* 竖分隔线用伪元素,比 border 好控制:首列不画,避免左边多一道 */
.ai-st+.ai-st::before{content:"";position:absolute;left:0;top:16px;bottom:18px;width:1px;
background:rgba(255,255,255,.16)}
.ai-st b{display:block;font-size:20px;font-weight:800;color:#fff;letter-spacing:-.3px}
.ai-st span{display:block;font-size:11.5px;color:rgba(255,255,255,.6);margin-top:5px}
/* 和昨天比的涨跌,只是补充信息,做得很轻 */
.ai-st em{display:inline-block;font-style:normal;font-size:10.5px;font-weight:700;margin-left:4px;
color:#8FDCB0;vertical-align:2px}
.ai-st em.dn{color:#FFB59B}
.ai-st em.eq{color:rgba(255,255,255,.45)}
/* ---- 段落小标题:序号 + 标题 + 一条延伸到底的细线 ---- */
.ai-sec{display:flex;align-items:center;gap:9px;margin:26px 0 13px}
.ai-sec-n{font-size:11px;font-weight:800;color:var(--brand);background:rgba(244,89,60,.1);
padding:3px 7px;border-radius:6px;letter-spacing:.5px}
.ai-sec h3{font-size:16px;font-weight:800;color:var(--ink);letter-spacing:.2px}
.ai-sec-line{flex:1;height:1px;background:linear-gradient(90deg,rgba(0,0,0,.09),transparent)}
/* ---- ② 三条建议:每张卡一个主题色 ---- */
.ai-tips{display:flex;flex-direction:column;gap:11px}
.ai-tip{position:relative;overflow:hidden;background:#fff;border-radius:18px;
padding:15px 17px 16px 19px;box-shadow:var(--sh)}
/* 左边一道竖色条,四张卡换四种颜色,扫一眼就知道在讲哪方面 */
.ai-tip::before{content:"";position:absolute;left:0;top:0;bottom:0;width:4px;background:var(--c)}
/* 右上角一个巨大的浅色序号,纯装饰,撑住卡片右侧的空 */
.at-n{position:absolute;top:-8px;right:10px;font-size:52px;font-weight:800;line-height:1;
color:var(--c);opacity:.09;pointer-events:none;user-select:none}
.ai-tip.c-p{--c:#F2795A}
.ai-tip.c-g{--c:#5AB98C}
.ai-tip.c-b{--c:#5E93D8}
.ai-tip.c-y{--c:#E0A63C}
.ai-tip.c-r{--c:#D9739B}
.at-cat{display:inline-block;font-size:11px;font-weight:800;color:var(--c);
background:color-mix(in srgb,var(--c) 12%,#fff);padding:4px 9px;border-radius:7px;letter-spacing:.5px}
/* color-mix 不支持的老浏览器上,上一行整条声明会被丢弃、底色变透明,
所以先垫一层带透明度的同色底作为兜底 */
@supports not (background:color-mix(in srgb,red 10%,#fff)){
.at-cat{background:rgba(0,0,0,.05)}
}
.at-head{font-size:15.5px;font-weight:800;line-height:1.6;color:var(--ink);margin:10px 0 5px;
padding-right:24px}
.at-txt{font-size:13.5px;line-height:1.75;color:var(--ink-2)}
/* ---- ③ 往期:一条竖线串起来的时间轴 ---- */
.ai-logs{position:relative;display:flex;flex-direction:column;gap:8px;padding-left:22px}
.ai-logs::before{content:"";position:absolute;left:5px;top:16px;bottom:16px;width:2px;
border-radius:2px;background:linear-gradient(180deg,#F6D8C8,rgba(246,216,200,0))}
.ai-log{position:relative;display:block;width:100%;text-align:left;background:#fff;
border-radius:16px;padding:13px 15px;box-shadow:var(--sh)}
.al-dot{position:absolute;left:-21px;top:19px;width:9px;height:9px;border-radius:50%;
background:#fff;border:2px solid #EFC7B4;transition:.2s}
/* 原来点一条会把它标成 .is-on(当前正在看的那天)。现在点了是弹窗,
看完就关,不存在「停留在某一天」,高亮改成按下去的一瞬间 */
.ai-log:active{background:#FFF6F1;box-shadow:0 8px 18px rgba(214,120,86,.16)}
.ai-log:active .al-dot{border-color:var(--brand);background:var(--brand);
box-shadow:0 0 0 4px rgba(244,89,60,.15)}
.al-date{font-size:13.5px;font-weight:700;color:var(--ink)}
.al-week{font-size:11.5px;color:var(--brand);margin-left:8px;font-weight:600}
.al-txt{display:block;font-size:12.5px;color:var(--muted);margin-top:5px;line-height:1.5;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* ============ 资源列表 / 资源内容 ============ */
/* ---- 试用装福利 banner ----
和「我的」页那张 .wx-card 同一件事、同一套暖色、结构也一样
(标题行 / 图文行 / 通栏 CTA 三段竖排),但仍然**不复用**它的类:
两边的尺寸账不同(图 68×60 vs 88×88、字号各小一档、页边距也不一样),
共用一个类只会逼出一堆 .detail-body .wx-card{...} 的覆盖规则。
⚠️ 两套类名各自独立,所以**改一边要记得改另一边** —— 角标裁切这个 bug
当初就是只在这边被发现、那边照抄了同样的写法又犯一次。
⚠️ 布局同 .wx-card:**竖向三段** —— .rlg-head 标题行 / .rlg-body 图文行
/ .rlg-cta 通栏按钮。所以 flex-direction 是 column,不是 row。
⚠️ 320 屏上的宽度账(**这一版重算过**):
卡内净宽 = 320 − 页边距 44(.detail-body 左右各 22)− 卡内边距 28 = 248,
和「我的」页那张卡一样宽(那边是 320 − 36 − 36)。两边数字撞在一起是巧合,
别当成一个可以共享的常量。
标题行(本批起**竖排**,跟 .wx-card 同步,两件各占一整行):
「可心柔婴儿纸巾」7 × 16 = 112 ≤ 248 ✓ 余 136px
「限领一份」 4 × 11.5 = 46 ≤ 248 ✓ 余 202px
图文行右列 = 248 − 图 68 − gap 10 = 170px:
角标「福利」2 × 10 + 内边 16 = 36 ✓
价格行「到手价」3 × 11 = 33 + gap 5 + 「¥0」19px 两个半角 ≈ 21 → 59 ✓
副文「加小汤圆的孕育顾问领取」11 × 12 = 132 ✓(且允许折两行)
CTA 通栏 248px(上一版只有 170px)。
⚠️ 为什么当初要拆成「标题行升到卡的直接子元素」这个结构:更早那一版是
左右两列、标题挤在 170px 的右列里,横排的商品名 + 限领要 218px,放不下。
标题行升上来拿到通栏 248px 才排得开。
⚠️ 本批标题行又从横排改成了竖排(跟 .wx-card 同步,理由见那边),
于是 218px 那笔账作废了 —— 但**结构不要退回去**:退回右列之后
商品名只剩 170px,和「我的」页那张的 160px 一样会被字体滑块顶爆。
⚠️ 尺度刻意比 .wx-card 小一号(图不放大到 88、字号各降一档):
那边四周是空的,这条 banner 下面紧跟着 128 条资源列表,
照搬那边的尺寸会把资源列表整个顶出首屏。 */
.rl-gift{display:flex;flex-direction:column;width:100%;text-align:left;
background:linear-gradient(135deg,#FFF4EC,#FFE2D2);
border-radius:20px;padding:12px 14px;margin:0 0 14px;box-shadow:var(--sh)}
/* ⚠️ 用逐元素的 .rl-gift.hide,不引入全局 .hide,更不用 hidden 属性:
全项目零处使用 hidden,而 .letter-card.hide / .moment.hide 这套
逐元素写法是既有惯例,跟着走。
(测试里两条都断言:既查 .hide 类,也查算出来的 display —— jsdom
自带 [hidden]{display:none},光量 display 的话用 hidden 属性
也能蒙混过关。) */
.rl-gift.hide{display:none}
/* ⚠️ 和 .wx-badge 同一个故事:上一版是 position:absolute;top:-7px 骑在卡上边缘,
被 .rl-gift 身上 .ripple 的 overflow:hidden(style.css:135)裁掉一半,
「福利」两个字只露出下半截。这是**比 .wx-badge 更早**就存在的老问题。
改成流内标签之后不可能再被裁。别改回绝对定位。 */
.rlg-badge{align-self:flex-start;font-size:10px;font-weight:700;
color:#fff;background:var(--brand);border-radius:8px;padding:2px 8px;
letter-spacing:.5px;box-shadow:0 2px 6px rgba(216,95,56,.28)}
/* ---- 第一段:标题行 ----
⚠️ align-items 用 flex-end 不用 baseline,理由和 .wx-head 一模一样:
.rlg-title 带 overflow:hidden,按规范它没有可用基线,浏览器会拿
border box 底边合成一条,「(每人限领一份)」会被顶成上标。
改这条之前先读 .wx-head 上面那段注释。
==== 本批同样改成竖排,上面那段 flex-end 的理由随横排一起作废 ====
这一张其实排得下(248px 通栏,横排余 30px),改它纯粹是为了和「我的」页那张
保持一致 —— 用户早先要求两张卡「一模一样」,一张横排一张竖排并排看就是两个东西。
竖排之后也不再有「没有可用基线」那个坑:两件不在同一行,压根不需要对基线。
⚠️ 别只改一边。这两张卡的标题行是**成对**的,动一张就得动另一张。 */
.rlg-head{display:flex;flex-direction:column;align-items:flex-start;gap:3px;min-width:0}
/* ---- 第二段:图文行 ---- */
.rlg-body{display:flex;align-items:center;gap:10px;margin-top:9px}
/* 同时管占位的 <svg> 和以后换上的 <img>,换真图时这段不用动 */
.rlg-fig{flex:0 0 auto;width:68px;height:60px;display:block;border-radius:12px;
object-fit:cover;background:#FFF9F5}
/* 图文行的右列:竖向,默认 stretch,所以价格行/副文自动满列宽;
只有角标 align-self:flex-start 缩成胶囊。
⚠️ 标题和 CTA 已经不在这一列里了(升到 .rlg-head 和卡的直接子元素)。 */
.rlg-main{flex:1;min-width:0;display:flex;flex-direction:column}
/* ⚠️ 竖排之后两件不再抢同一条宽度,「谁先被压缩」这个问题整个消失了。
ellipsis 三件套留着当兜底,正常不该触发。字号 15 → 16,和 .wx-title 同步。 */
.rlg-title{max-width:100%;min-width:0;font-size:16px;font-weight:700;color:var(--ink);
line-height:1.3;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* 「限领一份」,和「我的」页 .wx-lim 同一句、同样是小字、
本批起同样排在商品名**下面独占一行**(原来在标题右侧)。
⚠️ 这两条规则现在字号也一致了(都 11.5)。上一版这边 11.5、那边被挤到 10.5,
文案逐字相同但字号差一档 —— 那种「几乎一样」比明显不同更难发现。 */
/* 同 .wx-lim:不写 align-self,父级 .rlg-head 的 align-items:flex-start 已经管了。 */
.rlg-lim{white-space:nowrap;font-size:11.5px;color:#8E827C;line-height:1.3}
/* 价格行,和 .wx-price 同构、字号各降一档。同样只有到手价、没有编出来的划线价。
⚠️ <i> 得显式 font-style:normal,否则浏览器默认样式把中文几何倾斜,糊得厉害。 */
.rlg-price{display:flex;align-items:baseline;gap:5px;margin-top:6px}
.rlg-price>i{font-style:normal;font-size:11px;color:var(--ink-2)}
.rlg-price>b{font-size:19px;font-weight:800;color:var(--brand);letter-spacing:-.5px;line-height:1}
/* 副标题只讲「怎么领」,**不再兼当 CTA** —— 下面有真按钮了。
⚠️ 从单行省略改成了两行截断。这行现在由 syncBabyWords() 写入
「加<小名>的孕育顾问领取」,小名 maxlength="12",整句能到 20 字 ≈ 240px,
而右列只有 170px —— 再用 nowrap+ellipsis 会把「领取」连同小名一起吃掉,
偏偏 textContent 查得到,测试还是绿的。和 .wx-sub 栽的是同一个坑。 */
.rlg-sub{display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;
font-size:12px;color:var(--ink-2);margin-top:5px;line-height:1.45;overflow:hidden}
/* ---- 第三段:通栏 CTA ----
⚠️ 和 .wx-cta 同款但字号小一号(这条 banner 整体比「我的」页那张卡矮)。
⚠️ 现在是 .rl-gift 的直接子元素,靠 column 容器的 stretch 铺满 248px。
别给它 flex:0 0 100% —— 在 column 容器里 flex-basis 作用于**高度**。
⚠️ 必须 text-align:center:全局 button{text-align:left} 会继承下来,
而且 .rl-gift 自己还写了一遍 text-align:left,不覆盖就贴左边。 */
.rlg-cta{margin-top:11px;text-align:center;font-size:14px;font-weight:700;color:#fff;
background:var(--brand);border-radius:12px;padding:9px 10px;
box-shadow:0 4px 10px rgba(216,95,56,.24)}
.rl-lead{font-size:13px;color:var(--ink-2);margin:0 0 14px}
.rl-lead b{color:var(--brand);font-size:15px}
.res-list{display:flex;flex-direction:column;gap:11px}
.res-item{display:flex;align-items:center;gap:12px;width:100%;text-align:left;background:#fff;border-radius:18px;padding:11px;box-shadow:var(--sh)}
.ri-thumb{flex:0 0 auto;width:62px;height:62px;border-radius:14px;display:grid;place-items:center;overflow:hidden}
.ri-thumb img{width:74%;height:74%;object-fit:contain;display:block}
.ri-thumb.o{background:#FDEBE2}.ri-thumb.g{background:#E8F1E2}
.ri-thumb.p{background:#EFE8F6}.ri-thumb.b{background:#E4EDF9}
.ri-body{min-width:0;flex:1}
.ri-title{display:block;font-size:15px;font-weight:800;color:var(--ink);line-height:1.3;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.ri-desc{display:block;font-size:12px;color:var(--ink-2);margin:4px 0 7px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.ri-row{display:flex;align-items:center;gap:6px}
.ri-row em{font-style:normal;font-size:11px;line-height:1}
/* 状态标顶到最右(原来 margin-left:auto 挂在「8 分钟读完」上)。
「免费」和「已领 ✓」是同一格的两种状态,必须共用这条,
否则领完之后标签会从行首跳到行尾。 */
.pill-free,.pill-got{margin-left:auto}
.pill-free{background:rgba(110,154,86,.14);color:var(--green);font-weight:700;padding:3.5px 7px;border-radius:6px}
.ri-cat{background:rgba(244,89,60,.1);color:var(--brand);font-weight:700;padding:3.5px 7px;border-radius:6px}
/* 适用孕周,顶掉了原来的「几分钟读完」。不给底色 —— 左边已经有一个实心分类标,
再来一个就成了两个胶囊抢注意力,而它只是补充说明。
nowrap:「孕24-28周」这种断在中间比溢出还难看 */
.ri-ga{color:var(--muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
/* ---- 已领的资源 ---- */
/* 顶掉同一位置上的「免费」。绿底是这套里表示「成了」的颜色(和领取按钮的 .got 一致),
但比 .pill-free 实一档 —— 「免费」是背景信息,「已领」是状态,该更跳一点 */
.pill-got{background:var(--green);color:#fff;font-weight:700;padding:3.5px 7px;border-radius:6px;white-space:nowrap}
/* 缩略图右下角的小勾。压在图片上,所以 .ri-thumb 要 relative;
它本来是 overflow:hidden 的方形圆角块,角标贴着内边缘不会被裁掉。
加一圈白描边,免得撞上深色缩略图看不清 */
.ri-thumb{position:relative}
.ri-got-tick{position:absolute;right:3px;bottom:3px;width:17px;height:17px;border-radius:50%;
background:var(--green);color:#fff;border:1.5px solid #fff;
font-style:normal;font-size:10px;font-weight:800;line-height:1;display:grid;place-items:center}
/* 整张卡不做灰化处理:领过不等于失效,用户还会回来拿链接。
只把标题的字重降半档,让没领过的在列表里更抢眼一点 */
.res-item.is-got .ri-title{font-weight:700}
/* chip 用绿色,和上面两个标记对齐;选中态复用 .chip-fav 那套翻转(白字 + 实底) */
.chip-got{color:var(--green)}
.chip-got.is-active{color:#fff;background:var(--green);border-color:var(--green)}
.res-list.slim .ri-thumb{width:50px;height:50px;border-radius:12px}
.res-list.slim .ri-title{font-size:14px}
.rd-cover{height:168px;border-radius:22px;display:grid;place-items:center;background:#FDEBE2;margin-bottom:16px;overflow:hidden}
.rd-cover.o{background:linear-gradient(135deg,#FDEEE5,#F9DCCC)}
.rd-cover.g{background:linear-gradient(135deg,#ECF4E6,#DBE9D0)}
.rd-cover.p{background:linear-gradient(135deg,#F2EBF8,#E4D9F1)}
.rd-cover.b{background:linear-gradient(135deg,#E8F0FA,#D6E4F5)}
.rd-cover img{width:132px;height:132px;object-fit:contain;display:block;filter:drop-shadow(0 8px 18px rgba(140,90,70,.2))}
.rd-cat{display:inline-block;font-size:11.5px;font-weight:700;color:var(--brand);background:rgba(244,89,60,.1);padding:5px 11px;border-radius:8px}
.rd-title{font-size:24px;font-weight:800;letter-spacing:-.5px;margin:11px 0 7px;line-height:1.3}
.rd-desc{font-size:13.5px;color:var(--ink-2);margin:0}
.rd-meta{display:flex;flex-wrap:wrap;gap:7px;margin:14px 0 6px;padding-bottom:18px;border-bottom:1px solid var(--line)}
.rd-meta span{font-size:11.5px;color:var(--muted);background:#fff;border:1px solid var(--line);padding:5px 10px;border-radius:8px}
.rd-body h4{font-size:16px;font-weight:800;color:var(--ink);margin:22px 0 9px}
.rd-body p{font-size:15px;line-height:1.95;color:#4A403A;margin:0}
/* 「收藏 / 立即领取」吸底浮动:它是 .detail 的直接子元素(不在滚动区里),
所以正文怎么滑,这条都停在底部。不用 position:fixed —— .detail 自身带
transform,fixed 会以它为包含块,反而错位。 */
.rd-cta{
flex:0 0 auto;display:flex;gap:11px;position:relative;
padding:12px 22px calc(env(safe-area-inset-bottom) + 14px);
background:rgba(248,243,239,.94);backdrop-filter:blur(14px);
border-top:1px solid var(--line);
}
/* 上沿渐隐,正文滑到底时不会被硬生生切断 */
.rd-cta::before{
content:"";position:absolute;left:0;right:0;bottom:100%;height:22px;pointer-events:none;
background:linear-gradient(rgba(248,243,239,0),rgba(248,243,239,.94));
}
.rd-cta .dl-btn.main.got{background:var(--green);box-shadow:0 10px 22px rgba(110,154,86,.32)}
/* 吸底条已占掉底部空间,滚动区不用再留 40px */
#detailRes .detail-body,#detailPoster .detail-body{padding-bottom:18px}
/* 档案预览有三个按钮,挤一点、字小一号 */
.pst-cta .dl-btn{padding-left:0;padding-right:0;font-size:14px}
/* ---- 百度网盘领取弹窗 ---- */
.pan-box{background:#F7F0EA;border-radius:16px;padding:2px 14px;margin-top:4px}
.pan-row{display:flex;align-items:center;gap:12px;padding:11px 0}
.pan-row + .pan-row{border-top:1px solid var(--line)}
.pan-k{flex:0 0 auto;font-size:12.5px;font-weight:700;color:var(--muted)}
/* 用 readonly input 而不是 <span>:长按能直接唤起「全选 / 拷贝」 */
.pan-v{
flex:1;min-width:0;background:none;border:0;padding:0;
font-size:13.5px;color:var(--ink);text-overflow:ellipsis;
font-family:ui-monospace,Menlo,Consolas,monospace;
}
.pan-v.code{font-size:16px;font-weight:800;letter-spacing:3px;color:var(--brand)}
.pan-note{font-size:11.5px;color:var(--muted);line-height:1.7;margin:11px 0 0}
/* ---- 加企业微信顾问弹窗 ----
紧挨着上面的网盘弹窗放:这两个弹窗是同一条领取动线的前后两步
(先扫码换码,再拿链接)。隔开几百行的话,改动线文案时几乎必然只改到一头。 */
.wx-qr-box{margin-top:16px;text-align:center}
/* 同时管占位的 <svg> 和以后换上的 <img>:换真图时这段 CSS 一个字都不用动。
⚠️ 别改成 .wx-qr img 或 svg.wx-qr —— 那样一换成 <img> 尺寸就全丢。 */
.wx-qr{display:block;width:168px;height:168px;margin:0 auto;border-radius:14px;background:#fff}
.wx-qr-cap{font-size:12px;color:var(--muted);margin-top:10px}
.wx-code{letter-spacing:2px;font-weight:700;text-transform:uppercase}
/* 红边只在这一格上加,不动全局 .fld input:focus 那套 */
.wx-code.is-err{border-color:#D9603F;box-shadow:0 0 0 3px rgba(217,96,63,.13)}
/* ⚠️ min-height 是留给一行红字的位置,空着也占着。
不留的话,红字冒出来的一瞬间整个 modal-foot 会往下跳一格 ——
用户手指正落向「解锁全部资料」,按钮却在他按下去之前挪走了。 */
.wx-err{min-height:18px;font-size:12.5px;color:#D9603F;font-weight:600;margin:8px 0 0;line-height:1.5}
/* 三种模式里有两种要收起兑换码那一格和「解锁全部资料」,只剩二维码:
is-done —— 已经解锁过的人再点福利入口,他来找顾问,不是再解锁一遍;
is-gift —— 福利入口本身。这儿只谈领试用装,码由顾问连同试用装一起发,
让人当场填码是给了他一个手上根本没有的东西。
(闸门 is-gate 里那一格照旧留着 —— 那才是填码的地方。)
⚠️ 这条只管「看不见」。JS 那边 #wxOk 开头还有一句
if (!wxModal.classList.contains('is-gate')) return;
—— display:none 的按钮 dispatchEvent 照样会触发监听,
少了那句,「兑换码不对」的红字会冒在一个根本没有输入框的弹窗上。
别看这儿收起来了就把那句 guard 当冗余删掉。 */
.wx-modal.is-done .wx-form,.wx-modal.is-done #wxOk,
.wx-modal.is-gift .wx-form,.wx-modal.is-gift #wxOk{display:none}
/* gift 模式:「先不领」按用户要求删了,而 #wxOk 上面刚被收起来 ——
.modal-foot 里就一个人都不剩了。不收起整条的话,弹窗底下会多出
一截带 padding 的空白,看着像没加载完。
⚠️ 只写 .is-gift,**不能带上 .is-done**:done 模式下「知道了」是那一屏
唯一的按钮,连坐隐藏就没有出口了。
⚠️ gift 的退路是右上角 .modal-x(modalWx 头部有这颗,删按钮前确认过)。
哪天有人想给 .modal-head 做「无按钮时隐藏」的优化,先回来读这一句。 */
.wx-modal.is-gift .modal-foot{display:none}
/* ============ 记一下 弹层 ============ */
.scrim{position:absolute;inset:0;background:rgba(50,30,22,.42);backdrop-filter:blur(2px);opacity:0;visibility:hidden;transition:opacity .32s,visibility .32s;z-index:90}
.scrim.show{opacity:1;visibility:visible}
.sheet{
position:absolute;left:0;right:0;bottom:0;z-index:95;max-height:92%;
background:#FAF5F1;border-radius:28px 28px 0 0;
display:flex;flex-direction:column;
transform:translateY(100%);transition:transform .42s var(--ease);
box-shadow:0 -10px 40px rgba(90,50,35,.2);
}
.sheet.show{transform:none}
.sheet.dragging{transition:none}
.sheet-grip{padding:10px 0 4px;display:grid;place-items:center;cursor:grab;touch-action:none}
.sheet-grip span{width:42px;height:5px;border-radius:3px;background:#DCD2CA}
.sheet-head{display:flex;align-items:flex-start;justify-content:space-between;padding:8px 22px 14px}
.sheet-head h2{font-size:26px;font-weight:800;letter-spacing:-.5px}
.sheet-head p{font-size:13px;color:var(--ink-2);margin-top:8px}
.sheet-close{width:38px;height:38px;border-radius:50%;background:#EEE7E1;display:grid;place-items:center;font-size:15px;color:var(--ink-2);flex:0 0 auto}
.sheet-close:active{transform:scale(.9)}
.sheet-scroll{flex:1;overflow-y:auto;padding:0 18px 8px}
.sheet-scroll::-webkit-scrollbar{display:none}
/* 「今日心情」已删除,只剩 3 个工具,所以是 3 列 */
.quick-row{display:grid;grid-template-columns:repeat(3,1fr);gap:9px}
.quick{border-radius:17px;padding:13px 8px;text-align:center;transition:transform .16s}
.quick:active{transform:scale(.95)}
.quick.q1{background:#FCE4DA}.quick.q2{background:#FBDDD2}.quick.q3{background:#FCE2DE}
.q-ico{display:block;width:34px;height:auto;margin:0 auto 7px;object-fit:contain}
.q-t{display:block;font-size:14px;font-weight:800}
.q-v{display:block;font-size:12.5px;font-weight:700;color:var(--brand);margin-top:4px}
.apps-head{display:flex;align-items:center;font-size:15.5px;font-weight:700;color:var(--ink-2);margin:22px 4px 14px}
.apps-head.first{margin-top:2px}
.apps-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:16px 8px}
.app{display:flex;flex-direction:column;align-items:center;gap:8px;font-size:12.5px;font-weight:600;color:var(--ink-2);transition:transform .16s}
.app:active{transform:scale(.9)}
/* 16 宫格磁贴:整块(含圆角底色 + 3D 图标)直接取自设计稿 */
.a-ico{width:54px;height:54px;display:block;object-fit:contain}
/* ============ 搜索层 ============ */
.search-layer{position:absolute;inset:0;z-index:85;background:var(--bg);opacity:0;visibility:hidden;transform:translateY(-8px);transition:opacity .28s var(--ease),visibility .28s var(--ease),transform .28s var(--ease)}
.search-layer.show{opacity:1;visibility:visible;transform:none}
.sl-bar{display:flex;align-items:center;gap:12px;padding:calc(env(safe-area-inset-top) + 16px) 18px 14px}
.sl-input{flex:1;display:flex;align-items:center;gap:9px;background:#fff;border-radius:20px;padding:11px 15px;box-shadow:var(--sh)}
.sl-input input{flex:1;border:0;outline:0;font:inherit;font-size:15px;background:none}
.sl-cancel{font-size:15px;color:var(--brand);font-weight:600}
.sl-body{padding:8px 18px}
.sl-sec{font-size:14px;font-weight:700;color:var(--ink-2);margin:18px 2px 12px}
.sl-hot .hot{display:flex;align-items:center;gap:12px;width:100%;padding:13px 2px;font-size:15px;border-bottom:1px solid var(--line)}
.sl-hot .hot i{width:20px;height:20px;border-radius:6px;background:#F0E8E2;color:var(--muted);font-style:normal;font-size:12px;font-weight:700;display:grid;place-items:center}
.sl-hot .hot:nth-child(1) i{background:var(--brand);color:#fff}
.sl-hot .hot:nth-child(2) i{background:#F79274;color:#fff}
.sl-hot .hot:nth-child(3) i{background:#F9B9A5;color:#fff}
/* ============ 底部导航 ============ */
/* 设计稿的导航是一枚「悬浮药丸」(x60..791 / y1902..1988 of 857 → 左30 右32 高43.5)。
药丸形态与位置保持设计稿,但图标与文字按可读性放大:
图标 24px · 文字 12.5px · 中心圆钮 44px,药丸高度相应提到 58px。 */
.tabbar{
position:absolute;left:26px;right:26px;bottom:calc(14px + env(safe-area-inset-bottom));z-index:60;
height:58px;border-radius:29px;
display:flex;align-items:center;padding:0 4px;
background:rgba(252,249,247,.95);backdrop-filter:blur(18px);-webkit-backdrop-filter:blur(18px);
box-shadow:0 10px 30px rgba(150,90,70,.16),0 2px 8px rgba(150,90,70,.08);
}
.tab{position:relative;flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:3px;height:100%;color:#A99D95;transition:color .22s}
.tab.is-active{color:var(--brand)}
/* 底部导航图标:设计稿原图转成蒙版,用 currentColor 染色,
未选中 #B3A79F / 选中 var(--brand),形状与设计稿逐像素一致 */
.t-ico{width:24px;height:24px;display:block;position:relative;transition:transform .28s var(--ease);
background-color:currentColor;
-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;
-webkit-mask-position:center;mask-position:center;
-webkit-mask-size:contain;mask-size:contain}
.t-ico.ic-home{-webkit-mask-image:url(assets/img/1afd85e578dd.png);mask-image:url(assets/img/1afd85e578dd.png)}
.t-ico.ic-archive{-webkit-mask-image:url(assets/img/fa7b793b47e8.png);mask-image:url(assets/img/fa7b793b47e8.png)}
.t-ico.ic-letter{-webkit-mask-image:url(assets/img/949338312a1f.png);mask-image:url(assets/img/949338312a1f.png)}
.t-ico.ic-me{-webkit-mask-image:url(assets/img/0ebc7631fea4.png);mask-image:url(assets/img/0ebc7631fea4.png)}
.tab.is-active .t-ico{transform:translateY(-2px) scale(1.06)}
/* nowrap:最长的那条是「宝宝来信」,四个汉字 12.5px ≈ 50px,
320px 视口下每格 tab ≈ 64px,塞得下。但字号一旦被系统放大就会折行,
而 tab 是固定高度的,折了会把图标顶出去,所以宁可让它溢出也不折。 */
.t-txt{font-size:12.5px;font-weight:600;line-height:1.1;letter-spacing:.2px;white-space:nowrap}
/* 角标移到 .tab 上定位——.t-ico 现在是 mask 元素,子元素会被一并遮罩 */
.t-badge{display:none;position:absolute;top:9px;left:calc(50% + 8px);width:9px;height:9px;background:#FF3B30;border-radius:50%;border:1.5px solid #FCF9F7}
.t-badge.show{display:block}
.tab-fab{position:relative}
/* 「记一下」中心按钮:整枚圆钮直接取自设计稿像素。
设计稿中它并不「凸出」药丸,而是与其它图标同处一行、只是更大。 */
.fab{
flex:0 0 auto;width:38px;height:38px;border-radius:50%;
display:grid;place-items:center;
box-shadow:0 6px 16px rgba(244,89,60,.42);
transition:transform .3s var(--ease);
}
.fab img{width:100%;height:100%;display:block;border-radius:50%;object-fit:cover}
.tab-fab .t-txt{color:var(--brand)}
.tab-fab:active .fab{transform:scale(.9) rotate(90deg)}
/* ⚠️ 这条以前写作 `.sheet.show ~ .tabbar .fab`。本批把 <nav class="tabbar"> 移到了
.phone 最前面(底部 tab 要尽早渲染,见 index.html 里 nav 上方那段),
而通用兄弟选择器 `~` 只能向**后**选 —— nav 排到 .sheet 前面之后那条规则
一次都不会再命中,圆钮就再也不转了。改挂在父级状态类上,与文档顺序无关。
.sheet-open 由 openSheet/closeSheet 和 .sheet 的 .show 成对切(js/app.js)。 */
.phone.sheet-open .tabbar .fab{transform:rotate(135deg)}
/* ============ 通用弹窗(回信 / 孕期信息) ============
z-index 要高于 detail(80)、search(85)、scrim(90)、sheet(95),
因为「给宝宝回信」是在来信详情页(detail)之上再叠一层输入框 */
.modal-scrim{
position:absolute;inset:0;z-index:120;background:rgba(50,30,22,.46);backdrop-filter:blur(3px);
opacity:0;visibility:hidden;transition:opacity .3s,visibility .3s;
}
.modal-scrim.show{opacity:1;visibility:visible}
.modal{
position:absolute;left:0;right:0;bottom:0;z-index:125;
background:#FAF5F1;border-radius:28px 28px 0 0;
padding:22px 22px calc(20px + env(safe-area-inset-bottom));
box-shadow:0 -10px 40px rgba(90,50,35,.22);
/* 和 .sheet 一样只靠 transform 移出屏幕,不用 visibility——
否则 visibility 会打断 transform 过渡,弹窗会「跳」出来 */
transform:translateY(102%);transition:transform .4s var(--ease);
max-height:92%;overflow-y:auto;
}
.modal::-webkit-scrollbar{display:none}
.modal.show{transform:none}
.modal-head{display:flex;align-items:center;justify-content:space-between;gap:12px}
.modal-head h3{font-size:20px;font-weight:800;letter-spacing:-.3px}
.modal-x{width:34px;height:34px;border-radius:50%;background:#EEE7E1;display:grid;place-items:center;color:var(--ink-2);flex:0 0 auto}
.modal-x:active{transform:scale(.9)}
/* ---- 往期小结弹窗 ----
叉挪到内容末尾、居中放大一点:这一屏是一路往下读完的,
手指最后停在底部,关闭按钮就该在那儿等着 */
.modal-x-btm{width:44px;height:44px;margin:24px auto 2px;background:#F3ECE6}
.alog-head{display:flex;align-items:center;gap:9px}
.alog-week{font-size:12px;font-weight:800;color:var(--brand);background:rgba(244,89,60,.1);border-radius:9px;padding:4px 9px}
.alog-mood{font-size:12px;font-weight:700;color:var(--ink-2);background:#F3ECE6;border-radius:9px;padding:4px 9px}
.alog-date{font-size:21px;font-weight:800;letter-spacing:-.3px;margin:12px 0 0}
.alog-txt{font-size:14.5px;color:var(--ink-2);line-height:1.8;margin:9px 0 0}
/* .alog-tips 已删(往期不再铺 1/2/3 建议卡),这里的 margin 跟着删。
.ai-stats 原本长在深色今日卡里,一整套都是白字。搬到浅底弹窗上得翻过来,
但只能在 .alog-stats 这一层改 —— 今日卡还在用原来那套。
上边距 14 → 18:它现在是弹窗里最后一块内容,前后都得撑开一点才不局促 */
.alog-stats{margin-top:18px;background:#FAF5F1;border-radius:18px;padding:2px 0}
.alog-stats .ai-st+.ai-st::before{background:rgba(120,90,70,.14)}
.alog-stats .ai-st b{color:var(--ink)}
.alog-stats .ai-st span{color:var(--muted)}
.alog-stats .ai-st em{color:#3E9B69}
.alog-stats .ai-st em.dn{color:#D9603F}
.alog-stats .ai-st em.eq{color:var(--muted)}
.modal-lead{font-size:13px;color:var(--ink-2);margin-top:8px;line-height:1.6}
.modal-lead b{color:var(--brand)}
/* 引导语里被挑出来的那四个字「全部解锁」(setLead 包的 <em class="lead-hi">)。
⚠️ 必须写 font-style:normal —— <em> 默认是斜体,而手写体几乎都没有斜体字重,
浏览器会拿正体去**算法伪斜**(整体切变),中文伪斜出来是歪的、发虚,
比不强调还难看。这一行不是可省的重置。
⚠️ 下划线用 text-decoration 而不是 border-bottom:border 会把行盒撑高,
这句话在 gate 弹窗里正好排到第三行,撑高会顶到下面那格输入框。
underline-offset 给 3px 是让线离汉字底再远一点,不然会贴着「锁」的竖钩。 */
.modal-lead .lead-hi{
font-family:var(--font-kid);font-style:normal;font-weight:800;
font-size:15px;color:var(--brand);
text-decoration:underline;text-decoration-thickness:1.5px;text-underline-offset:3px;
/* 线比字淡一档:--brand #F4593C 兑 .5 alpha。手写体本来笔画就细,
配一条同样浓的实线会显得字被压在线上面。改这里记得跟着 --brand 走。 */
text-decoration-color:rgba(244,89,60,.5)}
/* 输入框:必须显式给 font-family,否则 textarea/input 会退回成系统等宽字体 */
.modal-ta,.fld input{
font-family:inherit;font-size:15px;color:var(--ink);background:#fff;
border:1.5px solid #EBE1D9;border-radius:16px;width:100%;
transition:border-color .2s,box-shadow .2s;-webkit-appearance:none;appearance:none;
}
.modal-ta:focus,.fld input:focus{outline:none;border-color:var(--brand);box-shadow:0 0 0 3px rgba(244,89,60,.12)}
.modal-ta{margin-top:14px;padding:14px 15px;line-height:1.75;resize:none;min-height:132px}
.modal-tags{display:flex;flex-wrap:wrap;gap:8px;margin-top:12px}
.mtag{font-size:12.5px;font-weight:600;color:#A5683F;background:#F6EADF;border-radius:14px;padding:7px 12px;transition:transform .16s}
.mtag:active{transform:scale(.94)}
.fld{display:block;margin-top:14px}
.fld>span{display:block;font-size:12.5px;font-weight:700;color:var(--ink-2);margin-bottom:7px}
.fld input{padding:13px 15px}
.fld-row{display:flex;gap:12px}
.fld-row .fld{flex:1}
/* 没填预产期时孕周/额外天数不可编辑:置灰 + 禁用光标,一眼看出得先填上面那格 */
.fld input:disabled{background:#F0E9E3;color:#B4A79C;-webkit-text-fill-color:#B4A79C;
border-color:transparent;cursor:not-allowed;opacity:1}
.fld-hint{font-size:12px;color:var(--muted);margin-top:7px;line-height:1.5}
/* 性别三选一。用 button 而不是 radio:和弹窗里其它控件的圆角风格统一 */
.pick-row{display:flex;gap:9px}
/* 全局 button 被重置成了 text-align:left,这里得显式居中,
否则「还不知道 / 男孩 / 女孩」都贴在按钮左边 */
.pick{flex:1;padding:12px 4px;border-radius:15px;background:#fff;font-size:14px;font-weight:700;
text-align:center;color:var(--ink-2);box-shadow:inset 0 0 0 1.5px var(--line);transition:transform .16s,color .16s,box-shadow .16s}
.pick:active{transform:scale(.96)}
.pick.is-active{color:var(--brand);background:#FDECE7;box-shadow:inset 0 0 0 1.8px var(--brand)}
.modal-foot{display:flex;align-items:center;gap:10px;margin-top:20px}
.modal-count{font-size:12.5px;color:var(--ink-2);flex:0 0 auto;margin-right:2px}
.modal-count b{color:var(--ink);font-weight:700}
.modal-foot .dl-btn{padding:14px}
/* ---- 我的资料 · 换头像 ---- */
.av-pick{text-align:center;padding:20px 0 6px}
.av-big{width:92px;height:92px;border-radius:50%;margin:0 auto;display:grid;place-items:center;position:relative;
background:var(--grad);color:#fff;border:3px solid #fff;box-shadow:var(--sh);transition:transform .18s var(--ease)}
.av-big:active{transform:scale(.94)}
.av-big>svg{width:50px;height:50px}
.av-big img{display:none;position:absolute;inset:0;width:100%;height:100%;object-fit:cover;border-radius:50%}
.av-big.has-img img{display:block}
.av-big.has-img>svg{visibility:hidden}
.av-cam{position:absolute;right:-2px;bottom:-2px;width:30px;height:30px;border-radius:50%;background:#fff;color:var(--brand);display:grid;place-items:center;box-shadow:0 3px 10px rgba(0,0,0,.18)}
/* file input 不能 display:none —— 部分安卓 WebView 里隐藏元素的 .click() 不生效,
所以留在流里但压成 0 尺寸、完全透明 */
.av-file{position:absolute;width:0;height:0;opacity:0;overflow:hidden;pointer-events:none}
.av-tip{font-size:12px;color:var(--muted);margin-top:13px}
/* ⚠️ 「我的资料」这张弹窗本批并进了原「编辑孕期信息」的五个字段
(小名 / 性别 / 预产期 / 孕周 / 天数),一下长了约 276px。
.modal 是 max-height:92% + overflow-y:auto,撑爆不会破版 —— 但会把
「保存」按钮推到折线以下,用户得先滚一下才够得着,那已经是体验问题。
算下来最窄的 iPhone SE(667 × 92% = 613px)刚好贴线,所以这里把
**只有这张弹窗**的大头像从 92px 收到 76px,头像块整体省 ~26px。
收的是头像不是字段:头像在这张弹窗里是配角,92 和 76 都够点;
而任何一个字段的高度都直接关系到能不能填。
⚠️ 用 #modalProfile 前缀限定,别改全局 .av-big —— 那条别处也在用。
⚠️ 图片是 inset:0 铺满的,跟着容器缩,不用另外改 img。 */
#modalProfile .av-pick{padding:12px 0 4px}
#modalProfile .av-big{width:76px;height:76px}
#modalProfile .av-big>svg{width:42px;height:42px}
#modalProfile .av-tip{margin-top:10px}
/* ============ Toast ============ */
.toast{
position:absolute;left:50%;bottom:120px;z-index:200;transform:translate(-50%,14px);
background:rgba(40,26,20,.9);color:#fff;font-size:14px;font-weight:600;
padding:12px 20px;border-radius:22px;backdrop-filter:blur(10px);
opacity:0;visibility:hidden;transition:opacity .3s var(--ease),visibility .3s var(--ease);max-width:78%;text-align:center;
}
.toast.show{opacity:1;visibility:visible;transform:translate(-50%,0)}
/* 数字跳动 */
.bump{animation:bump .35s var(--ease)}
@keyframes bump{0%{transform:scale(1)}45%{transform:scale(1.32)}100%{transform:scale(1)}}
/* 弹层「全部孕期工具」:无 logo 应用的字母兜底块(与 .a-ico 同尺寸) */
.apps-grid .a-ico-letter{display:grid;place-items:center;width:100%;aspect-ratio:1;border-radius:22px;color:#fff;font-size:22px;font-weight:800}