Files
FamilyBook/assets/css/app.css
T
laoyang 218cdf74fc 家谱:修「线又错位」(我上一版的居中写法在库里不成立)+ 框宽收成头像宽 + 去掉悬停提示
用户 2026-10-03 反馈三条。**第一条是我上一版引入的回归,先说清楚**。

一、「画线又有点错位了」—— 上一版把居中从 transform 换成 `inset:0 + margin:auto`,
    这一步在**这个库里不成立**:
      含块是库的 `div.card`,它在 Cartesian 模式下只有 **1.8px 宽**
      (宽度由内部一个空 div 撑出来,卡片是溢出绘制的)。
      `inset:0` + `margin:auto` 的"居中"依赖"容器比元素宽" —— 容器只有 1.8px 时
      auto 外边距分不出任何空间,卡片直接被摆在容器左上角、整体**右移 53.1px**。
    实测(1600×1000、库缩放 0.9):**每一张**卡的中心都偏在节点中心右边 53.1px
    (是固定的布局偏差,不是随机抖动)→ 连线仍然画在节点上,于是全部错位。
    正解:`left/top: 50%` + **半个自身尺寸的负外边距**
    (`margin-left: calc(var(--kin-w) / -2)`)—— 位置完全由布局决定,
    **不经过 transform**,所以库那条 `:hover { transform: … }` 也顶不掉它。
    ⚠️ 负外边距用 calc 与尺寸共用变量,不写死 -59/-66(迟早脱节)。
    ⚠️ 这条路上已经栽过两次,两种错写法都别再回去:
       · `transform: translate(-50%,-50%)` → hover 时被库覆盖,卡片跳半张(53×58px);
       · `inset:0 + margin:auto` → 容器 1.8px 宽,卡片整体右移 53px、连线错位。

二、「外面那个框又变宽了;宽度和头像图片一致,下面显示名字就行了」:
    卡片 118 → **98px**(= 96 头像 + 左右各 1px 边框),横向 padding 归零,
    头像改 `width: 100%` 占满卡片内宽 —— 于是**边框刚好贴着头像**,
    框里不再有多余的横向空白;头像下面就是名字(+ 与我的称呼)。
    ⚠️ 98×116 与 JS 的 `setCardDim` 必须同源(库按它算间距与连线落点),
       所以两边都改用常量:CSS 的 `--kin-w/--kin-h` ↔ JS 的 `KIN_W/KIN_H`。

三、「鼠标放在名字上后显示了一行字:资料、以他为中心 —— 没必要,不要显示」:
    那两行是我上一版加的悬停提示(`.kin-hint`),**整个删掉**(模板 + CSS)。

验证:
  · 家谱可视化检查 29 → **34** 条全过,新增五条正是这次的守卫:
    ⑮ 每张卡正对节点中心(改坏时实测 dx=53.1)· ⑯ 框宽 = 头像宽(≤2px)
    · ⑯b 尺寸不是退化的 0(防止"一起变 0"也算过)· ⑰ 无 .kin-hint
    · ⑱ 头像下面是名字。
  · 反向验证:把居中改回 `inset:0 + margin:auto` → ⑮ 立刻红(dx=44.1),
    还原后复绿(断言不是永远绿的)。
  · 连线端点独立核对:12 个端点里 9 个落在卡片附近,另 3 个是
    **夫妻之间那段连接线**(画在两卡之间,本来就该在两卡之间)。
  · e2e 773/0 · 第 5 轮专项 91/0 · 几何 428/0 · 移动端 684/0 · 交互 97/0。
  · 部署脚本更新/新增静态断言:允许 transform 之外的居中方式但**禁止那两种错写法**
    (transform 居中 / inset:0)、必须有 `margin-left: calc(var(--kin-w) / -2)`、
    JS 与 CSS 的宽度必须一致、`kin-hint` 不许回来。
  · 线上两个文件 md5 与本地一致;kin-hint=0、--kin-w: 98px 在位。
2026-10-03 17:49:10 +08:00

2317 lines
116 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.
/* ============================================================
家书 FamilyBook · 界面样式
配色取自图标:宣纸米 / 砖红封面 / 深褐书脊 / 纸白
原则:扁平、留白充足、字重克制(正文 400,标题 500-600)
============================================================ */
:root {
--paper: #F7F3EA;
--surface: #FFFFFF;
--ink: #2C231D;
--muted: #7E6B5A;
--faint: #A8988A;
--brand: #A84A28;
--brand-2: #C96F45;
--brand-ink:#6E3820;
--green: #6F9E5C;
--red: #C0533F;
--line: rgba(44, 35, 29, .10);
--line-2: rgba(44, 35, 29, .06);
--radius: 16px;
--radius-s: 10px;
--shadow: 0 1px 2px rgba(44, 35, 29, .05);
--shadow-l: 0 4px 16px rgba(44, 35, 29, .08);
/* ---- 动作按钮的统一度量 ----
用户提过三次「设置按钮和记一场电影按钮高度要一致」。为杜绝再犯,
这里把按钮高度拆成 4 个变量,并算出唯一的高度:
--act-h = 上下内边距 + 行高 + 上下边框 = 9*2 + 14*1.7 + 1*2 = 43.8px
文字按钮靠 padding + line-height 自然撑到这个高度;
**只有图标的方形按钮**(.btn-icon)显式写高度,但取的是同一个 --act-h,
这样两边永远相等,改一处就同步。 */
--act-pad-y: 9px;
--act-pad-x: 16px;
--act-font: 14px;
--act-line: 1.7;
--act-h: calc(var(--act-pad-y) * 2 + var(--act-font) * var(--act-line) + 1px * 2);
}
* { box-sizing: border-box; }
html, body { height: 100%; }
body {
margin: 0;
background: var(--paper);
color: var(--ink);
font: 15px/1.7 -apple-system, BlinkMacSystemFont, "PingFang SC",
"Microsoft YaHei", "Hiragino Sans GB", sans-serif;
-webkit-font-smoothing: antialiased;
}
a { color: var(--brand); text-decoration: none; }
a:hover { text-decoration: underline; }
/* ---------------- 顶栏 ---------------- */
.topbar {
position: sticky; top: 0; z-index: 20;
background: var(--surface);
border-bottom: 1px solid var(--line);
}
.topbar-inner {
max-width: 1080px; margin: 0 auto; padding: 0 24px;
height: 60px; display: flex; align-items: center; gap: 24px;
}
.brand { display: flex; align-items: center; gap: 10px; color: var(--ink); }
.brand:hover { text-decoration: none; }
.brand img { width: 30px; height: 30px; border-radius: 8px; display: block; }
.brand span { font-size: 16px; font-weight: 600; letter-spacing: .5px; }
.nav { display: flex; gap: 4px; margin-left: 8px; }
.nav a {
padding: 6px 12px; border-radius: 999px; color: var(--muted);
font-size: 14px;
}
.nav a:hover { background: var(--line-2); text-decoration: none; }
.nav a.on { background: var(--brand); color: #fff; }
.topbar-right { margin-left: auto; display: flex; align-items: center; gap: 12px; }
.who { display: flex; align-items: center; gap: 8px; color: var(--muted); font-size: 14px; }
/* 姓名 + 头像是个下拉菜单的「把手」——hover 时给点反馈,让人知道它可点 */
.who-menu { position: relative; }
.who-menu > summary { list-style: none; cursor: pointer; padding: 4px 6px; border-radius: 999px; }
.who-menu > summary::-webkit-details-marker { display: none; }
.who-menu > summary:hover { background: var(--line-2); }
.who-menu > summary:hover .who-name { color: var(--brand); }
.who-menu[open] > summary { background: var(--line-2); }
.who-menu[open] > summary .caret { transform: rotate(180deg); }
.who img { width: 30px; height: 30px; border-radius: 50%; object-fit: cover; }
.who img { transition: box-shadow .15s ease; }
.who-menu > summary:hover img { box-shadow: 0 0 0 2px var(--brand-2); }
/* 那个小三角:没有它,用户不知道"名字能点开" */
.caret {
width: 0; height: 0; margin-left: -2px;
border-left: 4px solid transparent; border-right: 4px solid transparent;
border-top: 5px solid var(--faint);
transition: transform .15s ease;
}
.menu-list {
position: absolute; right: 0; top: calc(100% + 8px); z-index: 30;
min-width: 148px; padding: 6px;
background: var(--surface); border: 1px solid var(--line);
border-radius: var(--radius-s); box-shadow: var(--shadow-l);
display: flex; flex-direction: column;
}
.menu-list a {
padding: 8px 12px; border-radius: 8px; color: var(--ink);
font-size: 14px; white-space: nowrap;
}
.menu-list a:hover { background: var(--line-2); text-decoration: none; color: var(--brand); }
/* 退出放在最下面并隔一条线:它是"离开",和上面几个"改点什么"不是一类动作 */
.menu-list .menu-exit {
margin-top: 4px; padding-top: 9px; border-top: 1px solid var(--line-2);
border-radius: 0 0 8px 8px; color: var(--muted);
}
.menu-list .menu-exit:hover { color: var(--brand); }
/* ---- 观影页的「设置」下拉:图标按钮 + 永远贴右 ----
用户明确要求(2026-10-01):
· 去掉「设置」二字,只留**三条横线**图标(☰);
· 在**所有页面都排在最右侧**(位置统一);
· 点开后的每条条目,**文字前都要有图标**;
· 除设置外,其它按钮都要**有颜色**,跟设置区分开。
贴右的做法:`margin-left:auto` 把它推到动作区最右端 —— 这样无论各页把片段
require 在 actions 的哪个位置(有的写在最前、有的写在最后),它都贴右,
不必逐页调顺序,也就不会有"某个页面漏了"的情况。 */
.movie-set { position: relative; margin-left: auto; }
.movie-set > summary {
list-style: none; cursor: pointer; display: inline-flex; align-items: center;
justify-content: center; gap: 6px;
}
.movie-set > summary::-webkit-details-marker { display: none; }
.movie-set[open] > summary { border-color: var(--brand); color: var(--brand); }
/* 方形图标按钮:只有图标、无文字。高度**必须**取 --act-h,
与「记一场电影」等实心按钮同高(那个高度由 --act-pad-y/--act-font/--act-line 推出)。
⚠️ 别在这里另写一个数字(曾写死 40px,比文字按钮的 43.8px 矮了 3.8px,被用户连续投诉)。 */
.btn-icon {
background: #fff; color: var(--ink); border: 1px solid var(--line);
padding: 0 !important; width: var(--act-h); height: var(--act-h); min-width: var(--act-h);
display: inline-flex; align-items: center; justify-content: center;
line-height: 1;
}
.btn-icon:hover { background: #FAF7F1; color: var(--brand); border-color: var(--brand); }
.btn-icon svg { display: block; }
/* 观影设置菜单:从右上角改成**按钮下方右对齐**(按钮贴右,菜单也贴右才不会跑出屏外) */
.movie-set .menu-list { left: auto; right: 0; top: calc(100% + 8px); }
.movie-set .menu-list a { display: flex; align-items: center; gap: 10px; }
.movie-set .menu-list a .mi { flex: 0 0 16px; color: var(--muted); }
.movie-set .menu-list a:hover .mi,
.movie-set .menu-list a.on .mi { color: currentColor; }
.movie-set .menu-list a.on { color: var(--brand); background: var(--line-2); }
/* ---- 日程页的设置下拉(v24 用户 2026-10-03 第 4、5 条)----
它比观影/任务那个 ☰ 内容多:里面有个**表单**(默认视图 + 显示哪些本子)。
本子清单会随共享/新建变长,所以给列表一个最大高度 + 滚动,
否则菜单会顶出屏幕、底下的「保存」按钮够不着。 */
.cal-set .menu-list { min-width: 260px; max-width: min(320px, 92vw); }
.cal-set-list { padding: 10px; gap: 0; }
.cal-set-form { display: block; }
.cal-set-block { padding: 6px 2px; }
.cal-set-block + .cal-set-block { border-top: 1px solid var(--line-2); margin-top: 4px; }
.cal-set-h { font-size: 12px; color: var(--muted); margin-bottom: 6px; }
.cal-set-views { display: flex; gap: 12px; flex-wrap: wrap; }
.cal-set-view { display: inline-flex; align-items: center; gap: 5px; font-size: 13px; cursor: pointer; }
.cal-set-view input { accent-color: var(--brand); }
/* 本子清单:最多占 42vh,超了内部滚动 —— 菜单本身不滚,否则保存按钮会被推出可视区 */
.cal-set-books { max-height: 42vh; overflow-y: auto; }
.cal-set-book {
display: flex; align-items: center; gap: 7px; padding: 5px 4px;
font-size: 13px; cursor: pointer; border-radius: 6px; white-space: normal;
}
.cal-set-book:hover { background: var(--line-2); }
.cal-set-book input { accent-color: var(--brand); flex: 0 0 auto; }
.cal-set-book .color-preview { width: 11px; height: 11px; border-radius: 3px; flex: 0 0 auto; }
.cal-set-book .cal-name { overflow: hidden; text-overflow: ellipsis; }
.cal-set-book .node-badge { margin-left: auto; flex: 0 0 auto; }
.cal-set-foot { margin-top: 10px; }
.cal-set-foot .btn { width: 100%; justify-content: center; }
.cal-set-links { margin-top: 8px; padding-top: 8px; border-top: 1px solid var(--line-2); }
.cal-set-links a { display: flex; align-items: center; gap: 10px; white-space: normal; }
.cal-set-links a .mi { flex: 0 0 16px; color: var(--muted); }
.cal-set-links a:hover .mi { color: currentColor; }
/* 点了日历空白处就建日程 —— 鼠标要能看出那里可点(用户第 5 条)。
⚠️ 只给「能写」的人光标:只读账号点上去弹不出表单,给个十字光标是骗人。 */
.tg-col.rt-writable, .cal-cell.rt-writable { cursor: crosshair; }
.link-quiet { color: var(--muted); font-size: 14px; }
.link-quiet:hover { color: var(--brand); }
/* 顶栏里的文字一律不许换行 —— 一换行就是「首/页」「名/册」竖排,很难看 */
.nav a, .topbar-right a, .who-name { white-space: nowrap; }
/* ---------------- 布局 ---------------- */
.container { max-width: 1080px; margin: 0 auto; padding: 28px 24px 64px; }
.container-narrow { max-width: 560px; margin: 0 auto; padding: 28px 24px 64px; }
.page-head { display: flex; align-items: flex-end; gap: 16px; margin-bottom: 20px; }
.page-head h1 { font-size: 22px; font-weight: 600; margin: 0; }
.page-head .sub { color: var(--muted); font-size: 14px; }
.page-head .actions { margin-left: auto; display: flex; gap: 8px; align-items: center; }
/* ---- 页头动作区的完整规格见**文件靠后**的「页头动作区」一节 ----
刻意放后面:.btn / .btn-ghost / .btn-icon 等基类定义在文件中部,
CSS 同特异性「后写的赢」——放这里会被它们覆盖(踩过:设置按钮被染成品牌底、
btn-ghost 仍是白的)。此处只留布局,样式集中在后面一处,便于对照。 */
.section { margin-top: 28px; }
.section-title {
display: flex; align-items: center; gap: 10px;
font-size: 15px; font-weight: 600; margin: 0 0 12px;
}
.section-title .hint { font-weight: 400; color: var(--muted); font-size: 13px; }
/* ---------------- 卡片 ---------------- */
.card {
background: var(--surface);
border: 1px solid var(--line);
border-radius: var(--radius);
box-shadow: var(--shadow);
}
.card-pad { padding: 20px; }
/* 相邻的两块之间一定要有缝 —— 影院管理页「添加影院」与「影院列表」、
年度报告的几张卡,曾经紧贴成一个整块,看不出是两个东西。
统一在这里给间距,而不是每个页面各写一次(写一次就会漏一处)。 */
.card + .card { margin-top: 18px; }
/* ⚠️ 上面**只写 .card + .card**:.section 自己有 margin-top:28px,
若在这里再写 `.card + .section { margin-top:18px }`,特异性 0,2,0 会压过
`.section` 的 0,1,0,反而把原本 28px 的间距改小 —— 修 A 不能顺手弄坏 B。 */
/* 首页头图 */
.hero {
background: var(--surface); border: 1px solid var(--line);
border-radius: var(--radius); box-shadow: var(--shadow);
padding: 26px 28px;
display: flex; align-items: center; gap: 20px; flex-wrap: wrap;
}
.hero-logo { width: 64px; height: 64px; border-radius: 16px; flex: 0 0 auto; }
.hero-main { flex: 1 1 240px; min-width: 0; }
.hero h1 { font-size: 24px; font-weight: 600; margin: 0 0 4px; }
.hero .motto { color: var(--muted); margin: 0; font-size: 14px; }
/* 那句小字到底是谁的:自己写的那句要标一下,否则用户会以为这是系统统一发的 */
.motto-from {
display: inline-block; margin-left: 6px; padding: 0 7px;
border-radius: 999px; background: var(--line-2);
font-size: 11px; color: var(--faint);
}
.motto-edit { margin-left: 8px; font-size: 12px; color: var(--brand-2); white-space: nowrap; }
/* 详情页顶部引用本人的一句话 */
.motto-line {
margin: -8px 0 16px; padding-left: 12px;
border-left: 3px solid var(--brand-2);
color: var(--muted); font-size: 14px;
}
/* ⚠️ .hero-stats / .stat 已随用户 2026-10-03 第 3 条撤掉首页那三个统计,
这里保留规则本身(其它页面可能还在用 .stat 这个类名),不再由首页引用。 */
.hero-stats { margin-left: auto; display: flex; gap: 28px; }
.stat { text-align: center; }
.stat b { display: block; font-size: 24px; font-weight: 600; line-height: 1.2; }
.stat span { font-size: 12px; color: var(--muted); }
/* 用户第 3 条:标题下那句个人 slogan */
.hero-slogan { color: var(--muted); margin: 4px 0 0; font-size: 14px; }
/* 用户第 3 条:「我的资料 / 我的家谱」换成**图标按钮**(原来是一排文字按钮)。
margin-left:auto 把它推到最右,与原来统计块的位置一致。 */
.hero-actions { margin-left: auto; display: flex; gap: 8px; align-items: center; }
.icon-btn {
display: inline-flex; align-items: center; justify-content: center;
width: 40px; height: 40px; border-radius: 12px;
border: 1px solid var(--line); background: var(--surface); color: var(--muted);
transition: border-color .15s ease, color .15s ease, background .15s ease;
}
.icon-btn:hover {
color: var(--brand); border-color: var(--brand-2); background: #FBF7F1;
}
.icon-btn svg { display: block; }
/* ---- 用户第 3 条:首页的统计信息块(问候 + 天气 + 几个数字) ---- */
.home-brief { display: flex; flex-direction: column; gap: 10px; }
.home-brief-hello { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.home-brief-hello b { font-size: 17px; }
.home-brief-weather {
display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap;
padding: 8px 12px; border-radius: 10px; background: var(--line-2);
}
.home-brief-weather .w-icon { font-size: 20px; line-height: 1; }
.home-brief-weather .w-temp { font-size: 20px; font-weight: 600; font-variant-numeric: tabular-nums; }
.home-brief-weather .w-desc { font-size: 14px; }
.home-brief-stats { display: flex; flex-wrap: wrap; gap: 10px 22px; color: var(--muted); font-size: 13px; }
.home-brief-stats .hbs b { color: var(--ink); font-size: 16px; margin-right: 3px; }
/* ---- 用户第 3 条:首页的模块小结卡 ---- */
.mod-cards {
display: grid; gap: 12px;
grid-template-columns: repeat(auto-fill, minmax(210px, 1fr));
}
.mod-card {
display: flex; flex-direction: column; gap: 6px;
padding: 14px 16px; border-radius: var(--radius);
border: 1px solid var(--line); background: var(--surface);
border-left: 3px solid var(--mc, var(--brand));
color: var(--ink); text-decoration: none;
transition: box-shadow .15s ease, border-color .15s ease;
}
.mod-card:hover { box-shadow: var(--shadow); text-decoration: none; }
.mod-card-head { display: flex; align-items: center; gap: 8px; color: var(--mc, var(--brand)); }
.mod-card-icon { display: inline-flex; flex: 0 0 auto; }
.mod-card-name { font-size: 13px; font-weight: 600; }
.mod-card-big { font-size: 20px; font-weight: 600; color: var(--ink); }
.mod-card-lines { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 2px; }
.mod-card-lines li { font-size: 12.5px; color: var(--muted); }
/* ---------------- 成员卡 ---------------- */
/* 用户 2026-10-03 第 4 条:名册从「卡片网格」改成**竖向列表**——
一行一个人,左边头像、中间姓名+关系+手机号/生日、右边操作。
列表形态下拖动排序也更直观(上下移动,不会像网格那样左右跳)。 */
.people-grid {
display: flex; flex-direction: column; gap: 8px; margin-top: 4px;
}
.person {
display: block; color: var(--ink); position: relative;
background: var(--surface); border: 1px solid var(--line);
border-radius: var(--radius-s);
transition: border-color .15s ease, box-shadow .15s ease;
}
.person:hover { border-color: var(--brand-2); box-shadow: var(--shadow); }
/* 头像:列表里用小方图(正方形、cover 裁切),不再是整块大图 */
.person .ph {
background: #F1E7D6; overflow: hidden;
width: 52px; height: 52px; border-radius: 8px; flex: 0 0 auto;
}
.person .ph img { width: 100%; height: 100%; object-fit: cover; display: block; }
.person .person-main {
display: flex; align-items: center; gap: 12px;
padding: 10px 12px; color: var(--ink); text-decoration: none;
}
.person .pb { flex: 1 1 auto; min-width: 0; }
.person .pname { font-weight: 600; font-size: 15px; display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.person .pmeta { color: var(--muted); font-size: 12.5px; margin-top: 2px; }
/* 中间那几个"重要信息":手机 / 生日 */
.person .pfacts { display: flex; flex-wrap: wrap; gap: 6px 14px; margin-top: 4px; font-size: 13px; }
.person .pfact { color: var(--ink); display: inline-flex; align-items: baseline; gap: 5px; }
.person .pfact-k {
color: var(--muted); font-size: 11px; padding: 1px 5px; border-radius: 4px;
background: var(--line-2);
}
/* ⚠️⚠️ 过滤用的行必须能真的藏起来。
`[hidden]` 的 display:none 是**浏览器默认样式**,优先级最低 —— 只要作者样式里
给同一个元素写了 display(.person 是 display:block、.pp-item 是 flex、
.att-item 是 inline-flex…),就会把它盖掉:元素"标注了 hidden 却照样显示",
于是「输入即筛选」看着完全没反应(列表一条都不少)。
这个坑本项目已经踩过两次(.att-item、.pp-item),所以统一写成一条通则:
凡是靠 [hidden] 隐藏的列表行,都必须在作者样式里显式再声明一次。 */
[data-search][hidden],
.pick-row[hidden],
.person[hidden] { display: none !important; }
.badge {
display: inline-block; padding: 1px 8px; border-radius: 999px;
font-size: 11px; font-weight: 500; line-height: 1.7;
background: var(--line-2); color: var(--muted);
}
.badge-admin { background: #F6E3D7; color: var(--brand-ink); }
.badge-open { background: #E6F0DC; color: #4A7226; }
.badge-me { background: var(--brand); color: #fff; }
/* 生日条 —— 一个人可能同时出两条(农历与阳历不在同一天),
落在同一天时并成一条、kinds 里挂多个标签,正好对应"要分别提醒"。 */
.bdays { display: flex; gap: 12px; flex-wrap: wrap; }
.bday {
display: flex; align-items: center; gap: 12px;
background: var(--surface); border: 1px solid var(--line);
border-radius: var(--radius); padding: 10px 18px 10px 10px;
box-shadow: var(--shadow);
color: var(--ink); text-decoration: none;
transition: transform .15s ease, box-shadow .15s ease;
}
.bday:hover { transform: translateY(-2px); box-shadow: var(--shadow-l); text-decoration: none; }
.bday img { width: 40px; height: 40px; border-radius: 50%; object-fit: cover; flex: 0 0 auto; }
.bday b { font-weight: 500; }
.bday .d { color: var(--brand); font-size: 13px; }
.bday-meta { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; margin-top: 3px; }
.bday-date { font-size: 12px; color: var(--faint); font-variant-numeric: tabular-nums; }
/* 「身份证生日 / 阳历生日 / 农历生日」的标签,首页与详情页共用 */
.bd-kind {
display: inline-block; padding: 0 7px; border-radius: 999px;
font-size: 11px; line-height: 1.8;
background: var(--line-2); color: var(--muted);
}
.bd-kind-id { background: #E6F0DC; color: #4A7226; }
.bd-kind-solar { background: #F6E3D7; color: var(--brand-ink); }
.bd-kind-lunar { background: #E8E4F2; color: #55477E; }
/* 详情页的三个生日 */
.bd-list { list-style: none; margin: 0; padding: 0; }
.bd-list li { display: flex; align-items: baseline; gap: 8px; margin-bottom: 3px; flex-wrap: wrap; }
.bd-list .bd-val { font-variant-numeric: tabular-nums; }
/* ---------------- 表单 ---------------- */
.form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
/* ⚠️ min-width:0 是必需的(v11 实测踩到):.field 是 grid 项,默认 min-width:auto
会被内部 <select> 的**最长选项文字**撑开 —— 高级搜索页在 360/375px 下整页横向溢出
(scrollW 390 > clientW 360),而「影院」下拉正是长选项。给了 0 之后列才能收进版心,
下拉自身由 width:100% 跟着收。与 .hero-main / .person-move .select 同一套做法。 */
.field { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.field.full { grid-column: 1 / -1; }
.field label { font-size: 13px; color: var(--muted); font-weight: 500; }
.field .tip { font-size: 12px; color: var(--muted); }
.input, .select, textarea.input {
width: 100%; padding: 10px 12px; font: inherit; font-size: 14px;
color: var(--ink); background: #fff;
border: 1px solid var(--line); border-radius: var(--radius-s);
transition: border-color .15s ease, box-shadow .15s ease;
}
.input:focus, .select:focus, textarea.input:focus {
outline: none; border-color: var(--brand);
box-shadow: 0 0 0 3px rgba(168, 74, 40, .12);
}
textarea.input { min-height: 76px; resize: vertical; }
.err { color: var(--red); font-size: 12px; }
.btn {
display: inline-flex; align-items: center; justify-content: center; gap: 6px;
padding: var(--act-pad-y) var(--act-pad-x);
font: inherit; font-size: var(--act-font); line-height: var(--act-line); font-weight: 500;
border-radius: var(--radius-s); border: 1px solid transparent; cursor: pointer;
background: var(--brand); color: #fff; transition: background .15s ease;
}
.btn:hover { background: #944022; text-decoration: none; }
.btn-ghost { background: #fff; color: var(--ink); border-color: var(--line); }
.btn-ghost:hover { background: #FAF7F1; }
.btn-danger { background: #fff; color: var(--red); border-color: rgba(192,83,63,.35); }
.btn-danger:hover { background: #FDF1EE; }
.btn-sm { padding: 5px 12px; font-size: 13px; }
.btn-row { display: flex; gap: 10px; margin-top: 20px; }
/* ---------------- 详情 ---------------- */
.detail { display: grid; grid-template-columns: 260px 1fr; gap: 24px; }
.detail-photo {
background: var(--surface); border: 1px solid var(--line);
border-radius: var(--radius); padding: 16px; text-align: center;
box-shadow: var(--shadow);
}
.detail-photo img {
width: 100%; aspect-ratio: 1/1; object-fit: cover;
border-radius: 12px; display: block;
}
.detail-photo .name { font-size: 18px; font-weight: 600; margin: 14px 0 4px; }
.detail-photo .sub { color: var(--muted); font-size: 13px; }
.kv { display: flex; flex-direction: column; }
.kv-row {
display: grid; grid-template-columns: 96px 1fr; gap: 12px;
padding: 12px 0; border-bottom: 1px solid var(--line-2);
}
.kv-row:last-child { border-bottom: 0; }
.kv-row dt { color: var(--muted); font-size: 13px; }
.kv-row dd { margin: 0; font-size: 14px; word-break: break-all; }
.mono { font-family: ui-monospace, "SFMono-Regular", Consolas, monospace; letter-spacing: .5px; }
/* ---------------- 提示 ---------------- */
.flash { display: flex; flex-direction: column; gap: 8px; margin-bottom: 18px; }
.flash-item {
padding: 10px 14px; border-radius: var(--radius-s); font-size: 14px;
background: #E6F0DC; color: #3D6B1F;
}
.flash-item.err { background: #F8DDDA; color: #A1341F; }
.empty {
background: var(--surface); border: 1px dashed var(--line);
border-radius: var(--radius); padding: 40px 20px; text-align: center;
color: var(--muted);
}
.empty b { display: block; color: var(--ink); font-weight: 500; margin-bottom: 4px; }
/* ---------------- 登录 / 安装 ---------------- */
.auth-wrap {
min-height: 100vh; display: flex; align-items: center; justify-content: center;
padding: 40px 20px;
}
.auth-card {
width: 100%; max-width: 400px; background: var(--surface);
border: 1px solid var(--line); border-radius: 20px; box-shadow: var(--shadow-l);
padding: 32px;
}
/* 图标 + 「用手机号登录你的家书」+ 家庭名,整块水平居中。
⚠️ 只有这一块居中:下面的表单仍然左对齐(label 左对齐才好读),
「忘记密码」那行本来就是居中,所以视觉上不会歪。 */
.auth-head { text-align: center; }
.auth-card .logo {
display: block; margin: 0 auto 14px;
width: 56px; height: 56px; border-radius: 14px;
}
.auth-card h1 { font-size: 20px; font-weight: 600; margin: 0 0 4px; }
.auth-card .lead { color: var(--muted); font-size: 14px; margin: 0 0 24px; }
.auth-card .btn { width: 100%; margin-top: 20px; }
.auth-foot { margin-top: 18px; font-size: 13px; color: var(--muted); text-align: center; }
/* ---------------- 其他 ---------------- */
.muted { color: var(--muted); }
.small { font-size: 13px; }
.nowrap { white-space: nowrap; }
.search { display: flex; gap: 8px; margin-bottom: 16px; }
.search .input { max-width: 260px; }
/* ---------------- 日期 + 时:分:秒 组合控件(templates/_datetime.php) ----------------
原生 datetime-local 的显示格式由浏览器 UI 语言/系统时间格式决定,
中文环境渲染成「下午 03:30」且没有秒,所以自己拼一个。
日期部分仍是原生 date 控件 → app.js 的 showPicker 让它整框可点。 */
.dt-input { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.dt-input .dt-date {
flex: 1 1 150px; min-width: 0;
padding: 9px 11px; border: 1px solid var(--line); border-radius: var(--radius-s);
background: var(--surface); color: var(--ink); font: inherit;
accent-color: var(--brand);
}
.dt-input .dt-date { cursor: pointer; } /* 整框可点,光标也要说明这一点 */
.dt-input .dt-date::-webkit-calendar-picker-indicator { cursor: pointer; opacity: .65; }
.dt-time { display: flex; align-items: center; gap: 3px; }
.dt-input .dt-hour,
.dt-input .dt-minute,
.dt-input .dt-second {
padding: 9px 6px; border: 1px solid var(--line); border-radius: var(--radius-s);
background: var(--surface); color: var(--ink); font: inherit;
font-variant-numeric: tabular-nums; cursor: pointer;
}
.dt-input .dt-colon { color: var(--muted); font-weight: 600; }
/* 「以哪个生日为准」的两块:被选中的那块描一圈,一眼看出填的是哪边 */
.bd-source { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 6px; }
.bd-pane { border: 1px solid transparent; border-radius: var(--radius); padding: 10px 12px 2px; }
.bd-pane.is-primary { border-color: var(--brand-2); background: #FDF6F1; }
.bd-pane .bd-pane-k { font-size: 13px; font-weight: 600; margin: 0 0 8px; }
.bd-readonly {
display: inline-block; padding: 6px 10px; border-radius: var(--radius-s);
background: var(--line-2); color: var(--muted); font-size: 14px;
font-variant-numeric: tabular-nums;
}
/* ---------------- 称谓标签 ---------------- */
.rel-label {
display: inline-block; padding: 1px 9px; border-radius: 999px;
background: #F6E3D7; color: var(--brand-ink);
font-size: 11px; font-weight: 500; line-height: 1.8;
margin-right: 2px;
}
/* ---------------- 家谱(以我为中心) ----------------
v22:按 family-chart **官方用法**接(容器 class="f3" + 引 family-chart.css),
库把 data[0].id 当 main_id,所以把「最高祖先」排第一位以画出尽可能多的血亲旁支;
加载后再把镜头平移回「我」,兼顾"看到全树"与"以自己为中心"。
⚠️ 免费版 family-chart 的算法只画「树根的祖先链 + 后代 + 树根的兄弟姐妹 + 各人配偶」,
**不会从配偶继续向上/向下 walk**。这一点已用**官方示例自己的数据**验证过:
官方 data.json 里 13 人(含配偶 Andrea 的父母 Zadro/Zadra,格式完全按官方),
库只画出 11 人 —— 配偶方那两位长辈一个都没画。
所以「把数据按格式全塞进去就能画全」在姻亲这一项上不成立,是库的算法边界。 */
.kin-tools { display: flex; gap: 8px; justify-content: flex-end; margin: 0 0 10px; }
.kin-chart {
position: relative;
width: 100%;
height: clamp(420px, 62vh, 720px);
background: var(--surface);
border: 1px solid var(--line);
border-radius: var(--radius);
box-shadow: var(--shadow);
overflow: hidden;
cursor: grab;
/* 拖拽/缩放由库接管,别让浏览器插手滚动 */
touch-action: none;
}
.kin-chart:active { cursor: grabbing; }
/* 库的 `.f3` 会自带一套**深色主题**(--background-color: rgb(33,33,33) / --text-color: #fff)。
家书是宣纸浅色,必须在这里把库的主题变量改掉;否则卡片文字会变白、看不出内容。
只覆盖颜色变量,**不动**库的结构规则(那些是撑满/定位必需的)。 */
.kin-chart.f3 {
--male-color: #F1E7D6; /* 库的男卡底色,改成纸色(我们自己的 .kin-card 会再盖一层) */
--female-color: #F1E7D6;
--genderless-color: #F1E7D6;
--background-color: transparent;
--text-color: var(--ink);
font-family: inherit;
}
/* 隐藏 family-chart 自带的空导航栏(我们用自己的「看全部 / 回到我」按钮) */
.kin-chart .f3-nav-cont { display: none !important; }
/* 官方 CSS 已经给了 `.f3 svg.main_svg { width:100%; height:100% }`(撑满画布),
这里只补一个 display:block,避免 svg 作为 inline 元素多出基线空隙。 */
.kin-chart svg.main_svg { display: block; z-index: 1; }
/* family-chart 的 HTML 卡片层 #htmlSvg 与 SVG 连线层由库的 onZoom 同步 transform。
卡片容器 .card_cont 已经 absolute + translate(x,y);里面再放我们的 .kin-card。 */
.kin-chart #htmlSvg { z-index: 2; }
.kin-chart .card_cont { pointer-events: none; }
/* ⚠️ 库的性别底色规则是 `.f3 div.card-male .card-inner`(3 段),
比 `.kin-chart .card_cont .card-inner`(3 段)**同级**,靠先后顺序定胜负 ——
所以这份覆盖必须写在**引库 CSS 之后**(app.css 在 layout 里就是这么排的)。
把外层中转容器全部归零,只留我们自己的 .kin-card。 */
.kin-chart .card_cont > .card,
.kin-chart .card_cont .card-inner,
.kin-chart.f3 .card_cont .card-inner {
background: transparent;
padding: 0;
border-radius: 0;
min-height: 0;
width: auto;
height: auto;
outline: none;
}
.kin-chart .card_cont .card-inner { display: block; }
/* v21:卡片尺寸与 setCardDim({w:140, h:120}) 对齐 */
/* 用户 2026-10-03 第 1 条:
· 头像外框改**长方形**(宽 96 × 高 72),宽度基本就是头像显示宽度;
img 用 object-fit: contain —— 既不裁切,也不会把外框撑变形。
· 信息(姓名/称呼)在头像**下方**。
· 卡片不再整张是链接:只有 .kin-nm 是 <a>,右下角是独立的 <button>,
两者分开,避免"点按钮却进了成员页"的误触。
卡片尺寸与 JS 的 setCardDim({w,h}) 必须一致(库按它算间距与连线落点)。 */
/* 卡片尺寸:**框宽 = 头像宽**(用户 2026-10-03 第 8 条:「外面那个框,宽度和头像图片一致,
下面显示名字就行了」)。
98 = 96(头像实际宽度,见 .kin-ph)+ 左右各 1px 边框 —— 即"边框刚好贴着头像",
框里不再有多余的横向空白(原来是 118 宽、比头像宽出 22px)。
⚠️ 横向 padding 必须是 0,否则头像(width:100%)会被压窄。
⚠️ 这个 98×116 必须与 JS 里 setCardDim({w,h}) **保持一致**:
库按它算卡片间距与连线落点,两边不一致连线就会歪。 */
.kin-card {
--kin-w: 98px;
--kin-h: 116px;
position: absolute;
display: flex; flex-direction: column; align-items: center;
gap: 0;
width: var(--kin-w);
height: var(--kin-h);
box-sizing: border-box; padding: 5px 0 4px;
text-align: center;
background: var(--surface);
border: 1px solid var(--line);
border-radius: 10px;
box-shadow: 0 1px 3px rgba(43,38,32,.10);
color: var(--ink);
transition: border-color .15s ease, box-shadow .15s ease;
overflow: hidden;
pointer-events: auto; /* 父 .card_cont 是 none,这里恢复 */
}
/* ⚠️⚠️ 居中**绝不能**用 transform、也不能用 `inset:0 + margin:auto`。
两次踩坑记录(都是用户先发现的:一次"鼠标移上去框会动",一次"线又错位了"):
· 用 `transform: translate(-50%,-50%)`:库自带
`.f3 div.card:hover > div { transform: translate(0,-2px) }` 会把居中 transform
**整份覆盖** → 鼠标一进卡片整张卡跳 53×58px(半张卡)。
· 用 `inset: 0` + `margin: auto`:**在这里不成立** —— 含块是库的 `div.card`,
在 Cartesian 模式下它只有 **1.8px 宽**(宽度由内部一个空 div 撑出来),
auto 外边距在一个比卡片还窄的盒子里根本分不出空间,结果卡片整体**右移了 53.1px**
(实测每张卡的中心都偏在节点右边 53.1px)→ 连线与卡片错位。
正解:`left/top: 50%` + **半个自身尺寸的负外边距** —— 位置完全由布局决定,
不经过 transform,库那条 :hover 规则怎么覆盖都影响不到。
⚠️ 负外边距必须写 `calc(var(--kin-*) / -2)`,与上面的尺寸共用同一个变量;
写成死数字(-59/-66)迟早会和尺寸脱节。 */
.kin-card {
left: 50%; top: 50%;
margin-left: calc(var(--kin-w) / -2);
margin-top: calc(var(--kin-h) / -2);
}
/* ⚠️ 还要把库那条 `:hover` 位移**归零**(用户反馈「鼠标放上去框会动」的另一半)。
库的 family-chart.css:517 有:
`.f3 div.card:hover > div { transform: translate(0, -2px); }`
它除了覆盖居中,还会给卡片一个纯视觉的"上浮 2px"(实测 dy = -1.8px)。
我们自绘的宣纸卡不需要库那套悬停装饰,直接归零。
⚠️ 不去改 vendor 里那份文件(升级库会整份覆盖、改动静默消失):
在 app.css 覆盖即可 —— 它加载在 family-chart.css **之后**,
同特异性下后写的赢,**不需要 !important**。 */
.kin-chart.f3 div.card:hover > div { transform: none; }
.kin-chart.f3 div.card > div { transition: none; }
.kin-card:hover {
box-shadow: var(--shadow-l); border-color: var(--brand-2);
}
/* 头像外框:**占满卡片内宽**(卡片 padding 横向为 0,所以这里 100% = 卡片内宽),
长方形、object-fit: contain 不裁切。
用户 2026-10-03 第 8 条:「外面那个框,宽度和头像图片一致,下面显示名字就行了」
—— 框宽 == 头像宽,头像下面只有名字。 */
.kin-card .kin-ph {
width: 100%; height: 72px; border-radius: 8px 8px 0 0; overflow: hidden;
background: #F1E7D6; flex: 0 0 auto;
display: block;
}
.kin-card .kin-ph img {
width: 100%; height: 100%; object-fit: contain; display: block;
}
.kin-card .kin-ph:hover { box-shadow: inset 0 0 0 2px var(--brand-2); }
/* 姓名:用户 2026-10-03 第 7 条起,点姓名 = **以他为中心**重画家谱
(点头像才是进资料页)。
⚠️ 第 8 条明确要求**不要再显示"资料 / 以他为中心"那两行悬停提示**(没必要)——
所以 .kin-hint 这套已整个删掉,卡片上头像下面只有名字。 */
.kin-card .kin-nm {
display: block;
font-size: 12.5px; font-weight: 600; line-height: 1.25; max-width: 100%;
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
margin-top: 5px; color: var(--ink); text-decoration: none;
}
.kin-card .kin-nm:hover { color: var(--brand); text-decoration: underline; }
/* 称呼在姓名下面一行,用品牌色的浅字(参考实现是「(称呼)」) */
.kin-card .kin-lb {
font-size: 10.5px; color: var(--brand); line-height: 1.2; max-width: 100%;
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.kin-card .kin-mark { font-size: 10px; color: var(--faint); }
/* 「我」:品牌色描边 + 光圈 */
.kin-card.is-me {
border-color: var(--brand);
box-shadow: 0 0 0 3px rgba(168, 74, 40, .12), var(--shadow);
}
.kin-card.is-me .kin-lb { color: var(--brand-ink); font-weight: 600; }
/* 「待确认」(只填了称呼、没对应到真人):虚线 */
.kin-card.is-ph { border-style: dashed; background: #FCFAF6; }
/* family-chart 默认连线颜色改淡,与之前自研版一致。
库在 path 上写了 inline stroke="#fff",必须用 !important 覆盖。 */
.kin-chart svg.main_svg .links_view path.link {
fill: none;
stroke: #C9BDA9 !important;
stroke-width: 1.6;
}
.tree-legend { font-size: 12px; color: var(--muted); margin: 14px 2px 0; }
/* 详情页里的迷你家谱 */
.mini-tree { display: flex; flex-direction: column; gap: 8px; }
.mini-gen { display: flex; flex-wrap: wrap; gap: 8px; }
.mini-node {
display: flex; align-items: center; gap: 7px;
padding: 5px 12px 5px 5px; border-radius: 999px;
background: #FAF7F1; border: 1px solid var(--line);
color: var(--ink); font-size: 13px;
}
.mini-node:hover { text-decoration: none; border-color: var(--brand); }
.mini-node img { width: 26px; height: 26px; border-radius: 50%; object-fit: cover; }
.mini-node .mn-label { font-size: 11px; color: var(--brand); }
.mini-node.is-me { border-color: var(--brand); background: #F6E3D7; }
/* ---------------- 亲属关系编辑 ---------------- */
.rel-summary {
display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 12px; margin-bottom: 18px;
}
.rel-sum-item {
background: #FAF7F1; border: 1px solid var(--line-2);
border-radius: var(--radius-s); padding: 10px 14px;
}
.rel-sum-k { display: block; font-size: 12px; color: var(--muted); margin-bottom: 3px; }
.rel-sum-v { font-size: 14px; }
.rel-sum-v a { margin-right: 6px; }
.rel-add { border-top: 1px solid var(--line-2); padding-top: 16px; }
.rel-add-title { font-size: 14px; font-weight: 600; margin: 0 0 12px; }
.rel-form {
display: grid; grid-template-columns: 1fr 1fr 1.4fr auto;
gap: 12px; align-items: end;
}
.rel-list { margin-top: 16px; font-size: 13px; }
.rel-list summary { cursor: pointer; color: var(--muted); }
.rel-list ul { margin: 10px 0 0; padding-left: 18px; }
.rel-list li { margin-bottom: 6px; }
.rel-list .btn { margin-left: 6px; }
/* ---------------- 三角色 / 存储设置 ---------------- */
.role-cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 12px; }
.role-card { background: #FAF7F1; border: 1px solid var(--line-2); border-radius: var(--radius-s); padding: 14px 16px; }
.rc-name { font-weight: 600; margin-bottom: 5px; }
.rc-desc { margin: 0; font-size: 13px; color: var(--muted); line-height: 1.65; }
.radio-row { display: flex; flex-direction: column; gap: 10px; }
.radio-item {
display: flex; gap: 10px; align-items: flex-start;
background: #FAF7F1; border: 1px solid var(--line-2);
border-radius: var(--radius-s); padding: 12px 14px; cursor: pointer;
}
.radio-item input { margin-top: 3px; }
.check-item { display: flex; gap: 8px; align-items: flex-start; font-size: 13px; cursor: pointer; }
.check-item input { margin-top: 3px; }
.oss-box {
border: 1px solid var(--line-2); border-radius: var(--radius-s);
padding: 12px 16px; background: #FCFAF6;
}
.oss-box summary { cursor: pointer; font-size: 13px; font-weight: 600; color: var(--muted); }
.plain-list { margin: 0; padding-left: 18px; font-size: 14px; }
.plain-list li { margin-bottom: 7px; }
@media (max-width: 720px) {
.rel-form { grid-template-columns: 1fr; }
}
@media (max-width: 720px) {
/* 390px 宽度放不下整套顶栏:只留 图标 + 导航 + 头像 + 退出 */
.topbar-inner { gap: 6px; padding: 0 10px; height: 54px; }
.brand img { width: 26px; height: 26px; }
.nav { gap: 0; }
.nav a { padding: 6px 8px; font-size: 13px; }
.topbar-right { gap: 6px; }
.who-name, .who-pwd { display: none; } /* 名字与「改密码」收起,设置页里都有 */
.who img { width: 26px; height: 26px; }
.container, .container-narrow { padding: 20px 16px 48px; }
.form-grid { grid-template-columns: 1fr; }
.detail { grid-template-columns: 1fr; }
.hero { padding: 20px; gap: 14px; }
.hero h1 { font-size: 20px; }
.hero-stats { margin-left: 0; gap: 20px; }
.page-head { flex-wrap: wrap; align-items: center; }
.page-head .actions { margin-left: 0; width: 100%; }
/* ⚠️ 窄屏下动作区**必须允许换行**:
一行里可能有 6 个按钮(月/周/天 + 上一周/今天/下一周 + 新建 + ☰),
360px 根本放不下。不换行的后果不只是横向溢出(整页被拖走),
还会把按钮**挤扁**成高低不一(实测 91/68/44 三种高度,同排按钮参差不齐)——
而"页头按钮同高"是用户提过三次的明确要求。
换行之后每个按钮保持自己的尺寸,高度自然一致。 */
.page-head .actions { flex-wrap: wrap; }
.page-head .actions > .movie-set { margin-left: 0; }
.kv-row { grid-template-columns: 84px 1fr; }
.search { flex-wrap: wrap; }
.search .input { max-width: none; flex: 1 1 auto; }
}
/* ---------------- 「我怎么称呼家人」(视角属性,见 app/Nickname.php) ---------------- */
.nick-list { display: flex; flex-direction: column; gap: 8px; }
.nick-row {
display: grid; grid-template-columns: minmax(120px, auto) 1fr auto;
gap: 10px; align-items: center;
padding: 8px 10px; border-radius: var(--radius-s);
background: #FAF7F1; border: 1px solid var(--line-2);
}
.nick-who { display: flex; align-items: center; gap: 8px; font-size: 14px; min-width: 0; }
.nick-who img { width: 28px; height: 28px; border-radius: 50%; object-fit: cover; flex: 0 0 auto; }
.nick-who span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* 详情页上的单个"我叫他什么"输入行 */
.nick-inline { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
.nick-inline .input { flex: 1 1 220px; max-width: 340px; }
@media (max-width: 720px) {
.nick-row { grid-template-columns: 1fr; }
.nick-inline .input { max-width: none; }
}
/* ---------------- 连通性测试与诊断(设置页) ---------------- */
.test-actions { display: flex; flex-direction: column; gap: 10px; }
.test-actions form { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
.oss-diag {
margin-top: 16px; border-radius: var(--radius-s); overflow: hidden;
border: 1px solid var(--line);
}
.oss-diag-head {
display: flex; gap: 10px; align-items: baseline; flex-wrap: wrap;
padding: 10px 14px; font-size: 13px;
background: var(--line-2);
}
.oss-diag.is-ok .oss-diag-head { background: #E6F0DC; color: #3F6021; }
.oss-diag.is-bad .oss-diag-head { background: #F6E3D7; color: var(--brand-ink); }
.oss-diag-table { width: 100%; border-collapse: collapse; font-size: 13px; background: var(--surface); }
.oss-diag-table th,
.oss-diag-table td { padding: 8px 12px; text-align: left; vertical-align: top; border-top: 1px solid var(--line-2); }
.oss-diag-table th { font-weight: 500; color: var(--muted); font-size: 12px; }
.oss-diag-table td:nth-child(1) { white-space: nowrap; }
.oss-diag-table td:nth-child(3) { white-space: nowrap; }
.oss-diag-table tr.row-ok td:nth-child(2) { color: #4A7226; }
.oss-diag-table tr.row-bad td:nth-child(2) { color: var(--red); }
@media (max-width: 720px) {
.oss-diag-table th:nth-child(3),
.oss-diag-table td:nth-child(3) { display: none; }
}
/* ============================================================
以下都是 2026-09-30 那一批反馈加的东西
============================================================ */
/* 只给读屏用的标签:视觉上不占地,但无障碍名还在(别用 display:none,那会一起藏掉) */
.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;
}
/* 区块之间的小间隔(比写 style="margin-top:20px" 好读) */
.stack-gap { height: 20px; }
/* ---------------- 名册:按关系分组分栏 ---------------- */
.group-block { margin-top: 24px; }
.group-head {
display: flex; align-items: baseline; gap: 8px;
margin: 0 0 10px; padding-bottom: 8px;
border-bottom: 1px solid var(--line);
font-size: 15px; font-weight: 600;
}
.group-head .group-title { color: var(--ink); }
/* 分组名前面那道砖红短线,让分栏在长页面上更容易一眼定位 */
.group-head .group-title::before {
content: ''; display: inline-block; vertical-align: middle;
width: 3px; height: 13px; margin-right: 8px; border-radius: 2px;
background: var(--brand);
}
.group-head .group-n { margin-left: auto; font-weight: 400; font-size: 12px; color: var(--faint); }
/* 卡片改成 div > a + details:表单不能套在链接里(非法 HTML,浏览器会把 DOM 拆开) */
.person-main { display: block; color: var(--ink); }
.person-main:hover { text-decoration: none; }
/* 列表形态下「⋯ 调整分组」改到行的**右侧**(原来在卡片下方,列表里会撑高一行) */
.person-more {
position: absolute; right: 8px; top: 50%; transform: translateY(-50%);
border: 0; background: none;
}
.person-more > summary {
list-style: none; cursor: pointer; padding: 4px 10px;
font-size: 16px; color: var(--muted); border-radius: 8px;
}
.person-more > summary:hover { background: var(--line-2); color: var(--brand); }
.person-more > summary::-webkit-details-marker { display: none; }
.person-more > summary::after { content: ' ▾'; }
.person-more[open] > summary::after { content: ' ▴'; }
.person-more > summary:hover { color: var(--brand); }
.person-more[open] {
position: absolute; right: 8px; top: 8px; transform: none;
background: var(--surface); border: 1px solid var(--line);
border-radius: 10px; box-shadow: var(--shadow-l); z-index: 30;
min-width: 232px; padding-bottom: 4px;
}
.person-move { display: flex; gap: 6px; padding: 8px 12px; flex-wrap: wrap; }
.person-move .select { flex: 1 1 auto; min-width: 0; padding: 6px 8px; font-size: 13px; }
.person-move .btn { flex: 0 0 auto; }
/* ---------------- 名册:拖动排序 / 拖到别栏改分组 ----------------
拖动是"锦上添花":没有它,卡片上的「⋯ → 调整分组」表单照样能改分组,
手机上 HTML5 拖放本来也不生效,那套表单就是窄屏的正路。 */
.roster.is-droppable .person { cursor: grab; }
.roster.is-droppable .person.is-dragged { opacity: .45; cursor: grabbing; }
.drag-handle {
position: absolute; left: 3px; top: 50%; transform: translateY(-50%); z-index: 2;
width: 22px; height: 22px; line-height: 22px; text-align: center;
border-radius: 6px; font-size: 14px;
color: var(--muted); background: rgba(255, 255, 255, .82);
border: 1px solid var(--line-2);
pointer-events: none; /* 手柄只是个记号,抓取要落在整张卡上 */
}
.drag-handle-inline { color: var(--muted); }
/* 列表里每行要留出左侧拖柄的位置(否则⠿会压在头像上) */
.roster.is-droppable .person { padding-left: 18px; }
/* 空栏平时收起来(七个预置分组可能只用到三个,摊开会显得名册破破烂烂)——
但**拖动时必须露出来**,否则"想拖到一个现在没人的分组里"就没地方放。 */
.group-block.is-empty { display: none; }
.roster.is-dragging .group-block.is-empty {
display: block; opacity: .9;
border: 1px dashed var(--line);
border-radius: var(--radius); padding: 10px; margin-top: 18px;
background: rgba(250, 247, 241, .6);
}
.group-drop-hint { display: none; margin-left: auto; font-weight: 400; font-size: 12px; color: var(--brand); }
.roster.is-dragging .group-block .group-drop-hint { display: inline; }
/* 拖动时把"有多少人"让出位置给落点提示,两者都挤在右上角会很乱 */
.roster.is-dragging .group-block .group-n { display: none; }
.roster.is-dragging .people-grid { min-height: 24px; }
/* 拖完/没拖成的轻提示(2 秒自己消失,不打断操作) */
.roster-note {
position: fixed; left: 50%; bottom: 28px; transform: translate(-50%, 12px);
z-index: 200; padding: 8px 16px; border-radius: 999px;
background: rgba(44, 35, 29, .88); color: #fff; font-size: 13px;
opacity: 0; pointer-events: none; transition: opacity .18s ease, transform .18s ease;
}
.roster-note.is-show { opacity: 1; transform: translate(-50%, 0); }
.roster-note.is-err { background: rgba(178, 45, 45, .92); }
/* ---------------- 分享名片(成员详情页) ---------------- */
.share-wrap { display: inline-block; }
.share-wrap > summary { list-style: none; cursor: pointer; display: inline-block; }
.share-wrap > summary::-webkit-details-marker { display: none; }
.share-wrap .inline-form { margin-top: 10px; }
/* ---------------- 「从家里已有的人里挑」 ---------------- */
.pick-list { list-style: none; margin: 0 0 4px; padding: 0; display: grid; gap: 8px; }
.pick-row { margin: 0; }
.pick-item {
display: flex; align-items: center; gap: 10px; cursor: pointer;
padding: 8px 12px; border-radius: var(--radius-s);
background: #FAF7F1; border: 1px solid var(--line-2);
}
.pick-item:hover { border-color: var(--brand); }
.pick-item input[type="checkbox"] { width: 16px; height: 16px; flex: 0 0 auto; accent-color: var(--brand); }
.pick-ph { width: 34px; height: 34px; border-radius: 50%; object-fit: cover; flex: 0 0 auto; background: #F1E7D6; }
.pick-name { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
/* ---------------- 个人设置:分组顺序 ---------------- */
.order-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.order-row {
display: flex; align-items: center; gap: 10px;
padding: 8px 12px; border-radius: var(--radius-s);
background: #FAF7F1; border: 1px solid var(--line-2);
}
.order-no {
flex: 0 0 auto; width: 22px; height: 22px; border-radius: 50%;
background: var(--brand); color: #fff;
font-size: 12px; line-height: 22px; text-align: center;
}
.order-name { flex: 1 1 auto; min-width: 0; font-size: 14px; }
.order-count { flex: 0 0 auto; font-size: 12px; color: var(--faint); }
.order-move { flex: 0 0 auto; display: flex; gap: 4px; }
.order-move .btn { padding: 2px 8px; line-height: 1.4; }
.order-move .btn[disabled] { opacity: .35; cursor: default; }
/* ---------------- 个人设置:关系分类管理 ---------------- */
.inline-form { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.inline-form .input { flex: 1 1 200px; max-width: 320px; }
.group-manage { list-style: none; margin: 16px 0 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.group-manage-row {
display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
padding: 8px 12px; border-radius: var(--radius-s);
background: #FAF7F1; border: 1px solid var(--line-2);
}
.group-rename { display: flex; gap: 8px; align-items: center; flex: 1 1 240px; min-width: 0; }
.group-rename .input { flex: 1 1 auto; min-width: 0; max-width: 220px; padding: 6px 10px; font-size: 13px; }
.group-manage-row .group-count { flex: 0 0 auto; }
.del-wrap > summary { list-style: none; cursor: pointer; }
.del-wrap[open] { flex: 1 1 100%; }
.del-confirm { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; padding: 8px 0 2px; }
/* ---- 页头动作区:统一尺寸 + 除设置外都要「有颜色」 ----
⚠️ 本节刻意放在**文件靠后**:.btn / .btn-ghost / .btn-danger / .btn-icon
的定义都在前面,CSS 同特异性时「后写的赢」——放前面会被它们覆盖
(实测过:放前面时 .btn-icon 白底被 .btn 的品牌底盖掉、.btn-ghost 也还是白的)。
用户明确提过三次:
·「设置按钮和记一场电影按钮高度要一致」
·「返回等按钮和记一场电影等按钮的大小高度的统一问题」
·「设置按钮…只保留图标,图标换成三条横线;除设置,其他按钮都要有颜色」 */
.page-head .actions > .btn,
.page-head .actions > .movie-set > summary.btn,
.page-head .actions > form > .btn,
.page-head .actions > form > button.btn {
padding: var(--act-pad-y) var(--act-pad-x); font-size: var(--act-font);
}
/* 层 1:主按钮(裸 .btn,无修饰类)—— 品牌实心。
⚠️ `.btn` 同时出现在 `<a class="btn">` 和 `<form><button class="btn">`,
两条路径的选择器都要写出来,且**特异性必须相等**,否则后面层 2/3
按类名匹配的规则压不住它(下层靠"同特异性 + 后写"取胜)。 */
.page-head .actions > .btn:not(.btn-icon),
.page-head .actions > form > .btn:not(.btn-icon),
.page-head .actions > form > button.btn:not(.btn-icon) {
background: var(--brand); color: #fff; border-color: transparent;
}
.page-head .actions > .btn:not(.btn-icon):hover,
.page-head .actions > form > button.btn:not(.btn-icon):hover { background: #944022; }
/* 层 2:次要按钮(.btn-ghost)—— 品牌描边 + 极浅品牌底,比主按钮轻一档。
a / button 两种形态各写一条、特异性对齐(见层 1 的警告)。 */
.page-head .actions > .btn-ghost:not(.btn-icon),
.page-head .actions > a.btn-ghost:not(.btn-icon),
.page-head .actions > form > .btn-ghost:not(.btn-icon),
.page-head .actions > form > button.btn-ghost:not(.btn-icon) {
background: #FDF7F2; color: var(--brand); border-color: rgba(168, 74, 40, .45);
}
.page-head .actions > .btn-ghost:not(.btn-icon):hover,
.page-head .actions > a.btn-ghost:not(.btn-icon):hover,
.page-head .actions > form > .btn-ghost:not(.btn-icon):hover,
.page-head .actions > form > button.btn-ghost:not(.btn-icon):hover {
background: #F7EDE4; border-color: var(--brand);
}
/* 层 3:危险按钮(.btn-danger)—— 淡红底 + 红字,同样要有颜色、但不能看着像主按钮。
⚠️ 实测踩过:只写 `.actions > form > .btn-danger`(0,4,0)会被层 1 的
`.actions > form > button.btn`(0,4,1)压掉 —— 详情页的「删除」按钮
在浏览器里显示成品牌砖红实心,跟「编辑」几乎分不出来。补上 `button.`
形态后特异性升到 (0,4,2),才真正生效。 */
.page-head .actions > .btn-danger:not(.btn-icon),
.page-head .actions > a.btn-danger:not(.btn-icon),
.page-head .actions > form > .btn-danger:not(.btn-icon),
.page-head .actions > form > button.btn-danger:not(.btn-icon) {
background: #FDF1EE; color: var(--red); border-color: rgba(192, 83, 63, .45);
}
.page-head .actions > .btn-danger:not(.btn-icon):hover,
.page-head .actions > a.btn-danger:not(.btn-icon):hover,
.page-head .actions > form > .btn-danger:not(.btn-icon):hover,
.page-head .actions > form > button.btn-danger:not(.btn-icon):hover { background: #F7E4DF; }
/* 「设置」图标按钮:**唯一无色的**(白底描边、方形),与上面所有彩色按钮区分。
padding 归零、做成正方形,但高度必须仍是 --act-h(= 文字按钮的 43.8px)——
写死过 40px,比旁边按钮矮 3.8px,是用户反复反馈的那个问题。 */
.page-head .actions > .movie-set > summary.btn-icon,
.page-head .actions > .movie-set > summary.btn {
padding: 0;
width: var(--act-h); height: var(--act-h); min-width: var(--act-h); line-height: 1;
background: #fff; color: var(--ink); border: 1px solid var(--line);
}
.page-head .actions > .movie-set > summary.btn-icon:hover,
.page-head .actions > .movie-set > summary.btn:hover {
background: #FAF7F1; border-color: var(--brand); color: var(--brand);
}
.movie-set[open] > summary.btn-icon,
.movie-set[open] > summary.btn { border-color: var(--brand); color: var(--brand); }
.btn.danger { color: var(--red); border-color: rgba(192, 83, 63, .35); }
.btn.danger:hover { background: rgba(192, 83, 63, .08); }
/* ---------------- 图片裁剪浮层 ---------------- */
/* 覆盖层默认不占空间;打开时铺满整屏,把底下的页面锁住(裁剪时不该能点到别处) */
.crop-overlay { display: none; }
.crop-overlay.is-open {
display: flex; position: fixed; inset: 0; z-index: 100;
align-items: center; justify-content: center;
padding: 20px; background: rgba(44, 35, 29, .55);
}
.crop-panel {
background: var(--surface); border-radius: var(--radius);
box-shadow: var(--shadow-l); padding: 16px;
max-width: calc(100vw - 24px); max-height: calc(100vh - 24px); overflow: auto;
}
.crop-head {
display: flex; align-items: baseline; gap: 8px;
font-size: 15px; font-weight: 600; margin-bottom: 12px;
}
.crop-head .crop-shape { font-size: 12px; font-weight: 400; color: var(--muted); }
.crop-stage {
position: relative; margin: 0 auto; overflow: auto;
background: #F1E7D6; border-radius: var(--radius-s);
touch-action: none; /* 手机上拖框时别把页面一起滚走 */
user-select: none;
}
/* 图片的宽高由 JS 按倍率写死(放大后比舞台大,靠舞台自身滚动看别处)——
所以这里**不能**写 width/height:100%,否则缩放就永远没效果。 */
.crop-img { display: block; max-width: none; }
/* 缩放条 —— 用户明确要求"裁剪的时候要能缩放" */
.crop-zoom-row {
display: flex; align-items: center; gap: 10px;
margin-top: 12px; font-size: 13px; color: var(--muted);
}
.crop-zoom { flex: 1; min-width: 120px; accent-color: var(--brand); }
.crop-zoom-val { font-variant-numeric: tabular-nums; min-width: 42px; text-align: right; }
/* 框外的部分压暗 —— 这是"你只留下框里这块"最直观的表达 */
.crop-box {
position: absolute; cursor: move;
border: 2px solid #fff;
box-shadow: 0 0 0 9999px rgba(44, 35, 29, .5);
}
.crop-box::before {
content: ''; position: absolute; inset: 0;
border: 1px dashed rgba(255, 255, 255, .75); pointer-events: none;
}
.crop-handle {
position: absolute; right: -8px; bottom: -8px;
width: 20px; height: 20px; border-radius: 50%;
background: #fff; border: 2px solid var(--brand);
cursor: nwse-resize;
}
.crop-tip { color: var(--muted); font-size: 12px; margin: 12px 0 0; }
.crop-actions { display: flex; gap: 10px; justify-content: flex-end; margin-top: 14px; }
@media (max-width: 720px) {
.order-move .btn { padding: 4px 10px; }
.group-manage-row { align-items: flex-start; }
.crop-panel { padding: 12px; }
}
/* ===== 补充联系方式(可多条):标注 + 内容 一行两组,窄屏摞起来 ===== */
.ct-row { display: flex; gap: 8px; margin-bottom: 8px; }
.ct-row .ct-label { flex: 0 0 140px; }
.ct-row .input { flex: 1; }
@media (max-width: 720px) {
.ct-row { flex-direction: column; gap: 6px; }
.ct-row .ct-label { flex: none; }
}
/* ===== 观影 v9:瀑布流海报墙 + MovieLog 式详情/评价/影院 ===== */
/* ---- 搜索栏 ---- */
.search-bar {
display: flex; gap: 10px; align-items: center;
background: var(--surface); padding: 8px 16px;
border-radius: 12px; box-shadow: var(--shadow);
border: 1px solid var(--line); max-width: 500px; margin-bottom: 20px;
}
.search-bar input[type="text"] {
flex: 1; border: none; outline: none; padding: 8px 10px;
font-size: 15px; background: transparent; color: var(--ink);
}
.search-bar button {
background: var(--brand); color: #fff; border: none;
padding: 8px 20px; border-radius: 8px; font-weight: 600;
cursor: pointer; white-space: nowrap;
}
.search-bar button:hover { background: var(--brand-2); }
.search-clear { color: var(--muted); font-size: 14px; padding: 8px 4px; white-space: nowrap; }
/* ---- 瀑布流海报墙 ---- */
.waterfall {
display: grid; gap: 20px; padding: 4px 0;
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
}
.movie-card {
background: var(--surface); border-radius: var(--radius);
overflow: hidden; box-shadow: var(--shadow); cursor: pointer;
border: 1px solid var(--line); display: flex; flex-direction: column;
text-decoration: none; color: var(--ink);
}
.movie-card:hover {
transform: translateY(-6px); box-shadow: var(--shadow-l);
border-color: var(--brand-2); text-decoration: none;
}
.card-image {
width: 100%; aspect-ratio: 2 / 3; background: var(--line-2);
overflow: hidden; display: flex; align-items: center; justify-content: center;
position: relative; flex-shrink: 0;
}
.card-image img { width: 100%; height: 100%; object-fit: cover; display: block; transition: transform .4s ease; }
.movie-card:hover .card-image img { transform: scale(1.03); }
.placeholder-poster {
width: 100%; height: 100%; display: flex; flex-direction: column;
align-items: center; justify-content: center; gap: 6px;
background: linear-gradient(135deg, #4a3a30, #2C231D);
color: rgba(255, 255, 255, .55); font-size: 42px; font-weight: 600;
}
.placeholder-poster .placeholder-title {
font-size: 14px; text-align: center; padding: 0 12px;
color: rgba(255, 255, 255, .75); font-weight: 500;
}
.card-body { padding: 14px 16px 16px; flex: 1; display: flex; flex-direction: column; gap: 6px; }
.card-title {
font-size: 16px; font-weight: 600; color: var(--ink);
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.card-meta {
display: flex; flex-wrap: wrap; gap: 6px 12px;
font-size: 13px; color: var(--muted);
}
.card-meta span { display: inline-flex; align-items: center; gap: 3px; }
.meta-rating { color: var(--brand); font-weight: 600; }
.meta-rating-max { color: var(--muted); font-weight: 400; }
.card-viewers { display: flex; flex-wrap: wrap; gap: 4px 8px; margin-top: auto; }
.viewer-tag {
display: inline-flex; align-items: center; gap: 4px; font-size: 12px;
padding: 2px 8px; border-radius: 12px; background: var(--line-2);
color: var(--ink); text-decoration: none;
/* 底色/字色/边线由 person_chip() 写成行内样式(每人一个颜色,来自他的头像主色);
这里只给**没有行内样式时的兜底**(旧的灰底标签):1px 透明边,
这样有边线和没边线的两种标签高度完全一致,不会一行高一行矮。 */
border: 1px solid transparent;
}
.viewer-tag:hover { text-decoration: none; filter: brightness(.96); }
.viewer-tag .chip-av { width: 18px; height: 18px; border-radius: 50%; object-fit: cover; flex: 0 0 auto; }
.viewer-tag .chip-av-text {
background: var(--faint); color: #fff;
display: inline-flex; align-items: center; justify-content: center;
font-size: 10px; line-height: 1;
}
.viewer-tag .chip-name { white-space: nowrap; }
/* ---- 详情页 ---- */
.detail-hero {
display: grid; grid-template-columns: 300px 1fr; gap: 32px;
background: var(--surface); border-radius: var(--radius);
padding: 28px; box-shadow: var(--shadow); margin-bottom: 20px;
border: 1px solid var(--line);
}
.detail-poster {
border-radius: 12px; overflow: hidden; background: var(--line-2);
aspect-ratio: 2 / 3; display: flex; align-items: center; justify-content: center;
}
.detail-poster img { width: 100%; height: 100%; object-fit: cover; }
.detail-poster .placeholder-poster { font-size: 48px; }
.detail-poster .placeholder-poster .placeholder-title { font-size: 16px; }
.detail-info { display: flex; flex-direction: column; gap: 12px; min-width: 0; }
.detail-info h1 { font-size: 26px; font-weight: 700; margin: 0 0 4px; color: var(--ink); }
.detail-info .meta-row { display: flex; flex-wrap: wrap; gap: 8px 16px; font-size: 14px; color: var(--muted); }
.detail-info .meta-row span { display: inline-flex; align-items: center; gap: 4px; }
.info-text { color: var(--ink); line-height: 1.7; font-size: 15px; }
.remark-text {
background: var(--line-2); padding: 12px 16px; border-radius: 8px;
color: var(--ink); border-left: 4px solid var(--brand); font-size: 14px;
}
.detail-section {
background: var(--surface); border-radius: var(--radius);
padding: 24px 28px; box-shadow: var(--shadow); margin-bottom: 20px;
border: 1px solid var(--line);
}
.detail-section h3 {
font-size: 17px; font-weight: 600; margin: 0 0 16px; color: var(--ink);
display: flex; align-items: center; gap: 10px;
}
.detail-section h3 .badge {
font-size: 13px; font-weight: 400; background: var(--line-2);
padding: 2px 12px; border-radius: 20px; color: var(--muted);
}
.empty-hint { color: var(--muted); font-size: 14px; margin: 0; }
.viewers-list { display: flex; flex-wrap: wrap; gap: 12px; }
.viewers-list .viewer-tag { padding: 4px 12px 4px 6px; font-size: 14px; }
.viewers-list .viewer-tag .chip-av { width: 28px; height: 28px; }
.viewers-list .viewer-tag .chip-av-text { font-size: 13px; }
.cinema-info {
display: grid; grid-template-columns: 1fr 1fr; gap: 8px 24px; font-size: 15px;
}
.cinema-info .label { color: var(--muted); }
.cinema-info .value { color: var(--ink); font-weight: 500; }
/* ---- 影像记录 ---- */
.gallery-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 16px; }
.gallery-item {
border-radius: 10px; overflow: hidden; background: var(--line-2);
aspect-ratio: 4 / 3; display: flex; align-items: center; justify-content: center;
cursor: pointer; transition: transform .2s; position: relative;
border: 1px solid var(--line);
}
.gallery-item:hover { transform: scale(1.02); }
.gallery-item img { width: 100%; height: 100%; object-fit: cover; display: block; }
.gallery-item .label-badge {
position: absolute; bottom: 8px; left: 8px;
background: rgba(0, 0, 0, .7); color: #fff; font-size: 11px;
padding: 2px 12px; border-radius: 12px;
}
/* ---- 图片灯箱 ---- */
.lb-openable { cursor: zoom-in; }
.lb-openable:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
.lightbox {
position: fixed; inset: 0; z-index: 300; display: none;
align-items: center; justify-content: center;
background: rgba(0, 0, 0, .88); overscroll-behavior: contain;
}
.lightbox.open { display: flex; }
.lb-stage { display: flex; align-items: center; justify-content: center; max-width: 92vw; max-height: 82vh; }
.lb-img {
max-width: 92vw; max-height: 78vh; width: auto; height: auto;
object-fit: contain; border-radius: 8px; box-shadow: 0 20px 60px rgba(0, 0, 0, .5);
user-select: none; -webkit-user-drag: none;
}
.lb-btn {
position: absolute; border: none; cursor: pointer; color: #fff;
background: rgba(255, 255, 255, .12); display: flex;
align-items: center; justify-content: center;
transition: background-color .2s ease; font-family: inherit;
}
.lb-btn:hover { background: rgba(255, 255, 255, .26); }
.lb-btn[hidden] { display: none; }
.lb-close { top: 14px; right: 16px; width: 40px; height: 40px; border-radius: 50%; font-size: 18px; }
/* 下载按钮在关闭按钮左边,同高、同一套视觉(v24 第 3 条:预览层右上角要有下载) */
.lb-dl {
top: 14px; right: 66px; height: 40px; padding: 0 16px; border-radius: 20px;
display: inline-flex; align-items: center; font-size: 13px; font-weight: 600;
background: #fff; color: #1a1a1a; text-decoration: none;
}
.lb-dl:hover { background: #eee; }
.lb-nav { top: 50%; transform: translateY(-50%); width: 48px; height: 48px; border-radius: 50%; font-size: 28px; padding-bottom: 4px; }
.lb-prev { left: 14px; }
.lb-next { right: 14px; }
.lb-bar {
position: absolute; left: 0; right: 0; bottom: 0; padding: 14px 20px;
display: flex; align-items: center; justify-content: space-between; gap: 12px;
color: #fff; font-size: 14px;
background: linear-gradient(to top, rgba(0, 0, 0, .6), rgba(0, 0, 0, 0));
}
.lb-caption { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.lb-counter { flex: 0 0 auto; opacity: .75; font-variant-numeric: tabular-nums; }
/* ---- 评价与评论 ---- */
.rv-composer > summary { list-style: none; cursor: pointer; }
.rv-composer > summary::-webkit-details-marker { display: none; }
.rv-open-btn {
display: inline-block; padding: 8px 18px; border-radius: 10px;
background: linear-gradient(135deg, var(--brand), var(--brand-2));
color: #fff; font-size: 14px; font-weight: 600; user-select: none;
}
.rv-open-btn:hover { text-decoration: none; box-shadow: var(--shadow-l); }
.rv-composer[open] > .rv-open-btn { background: var(--line-2); color: var(--muted); }
.rv-act-btn {
display: inline-block; padding: 4px 12px; border: 1px solid var(--line);
border-radius: 20px; background: var(--surface); color: var(--muted);
font-size: 12.5px; user-select: none; cursor: pointer;
}
.rv-act-btn:hover { border-color: var(--brand); color: var(--brand); text-decoration: none; }
.rv-composer[open] > .rv-act-btn { border-color: var(--brand); background: rgba(168, 74, 40, .08); color: var(--brand); font-weight: 600; }
.rv-form { background: var(--line-2); border-radius: 12px; padding: 16px 18px; margin-top: 10px; }
.rv-composer > .rv-form { margin-bottom: 0; }
.rv-form-inline { padding: 12px 14px; }
.rv-rating-row { display: flex; align-items: center; gap: 8px 14px; flex-wrap: wrap; margin-bottom: 12px; }
.rv-rating-label { font-size: 14px; font-weight: 600; color: var(--ink); }
/* ---- 10 星评分(v10):空心 ☆ 灰、点亮 ★ 金黄。
DOM 按 10..1 倒序渲染 + row-reverse,视觉即 1..10 从左到右;
「点亮的星星」由 JS 加 .lit(含悬停预览),.no-js 兜底用 :has() 只亮已选中那一档。 ---- */
.rv-scale { display: flex; gap: 2px; }
.rv-stars { flex-direction: row-reverse; justify-content: flex-end; }
.rv-star { position: relative; cursor: pointer; line-height: 1; }
.rv-star input { position: absolute; width: 1px; height: 1px; opacity: 0; }
.rv-star .st-glyph { font-size: 28px; line-height: 1; }
.rv-star .st-off { color: #c9cdd4; }
.rv-star .st-on { display: none; color: #f5b301; }
.rv-star.lit .st-on { display: inline; }
.rv-star.lit .st-off { display: none; }
.rv-star:hover .st-off { color: #aeb4be; }
.rv-star:focus-within { outline: 2px solid var(--brand); outline-offset: 1px; border-radius: 4px; }
/* 无 JS 兜底:至少选中的那一档及其左边(视觉)会亮 */
.rv-stars.no-js .rv-star:has(input:checked) .st-on,
.rv-stars.no-js .rv-star:has(input:checked) ~ .rv-star .st-on { display: inline; }
.rv-stars.no-js .rv-star:has(input:checked) .st-off,
.rv-stars.no-js .rv-star:has(input:checked) ~ .rv-star .st-off { display: none; }
/* 评价列表里的静态星级展示 */
.stars-disp { display: inline-flex; gap: 1px; font-size: 17px; line-height: 1; vertical-align: -2px; }
.stars-disp .d-on { color: #f5b301; }
.stars-disp .d-off { color: #d3d7de; }
/* ---- 财务 / 账本(v24 第 15 条) ---- */
/* 金额列右对齐 + 等宽数字(表格里数字不齐会很难扫) */
.table .num, .table th.num { text-align: right; font-variant-numeric: tabular-nums; }
.table tfoot th { border-top: 2px solid var(--line); border-bottom: 0; color: var(--ink); }
.led-sum-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: 10px; }
.led-total { display: flex; flex-wrap: wrap; gap: 18px; margin: 12px 0 4px; }
.led-total-item { display: flex; flex-direction: column; gap: 2px; }
.led-total-lb { font-size: 12px; color: var(--muted); }
.led-total-num {
font-size: 22px; font-weight: 700; font-variant-numeric: tabular-nums;
color: var(--ink);
}
.led-total-item.led-net .led-total-num { color: var(--brand); }
/* 数字颜色:**收入/资产用红、负债用绿**(与 A 股涨红跌绿同一套约定,不要反) */
.led-asset { color: #B0342C; }
.led-debt { color: #1F7A4D; }
.led-kinds { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 6px; }
.led-kind-chip {
display: inline-flex; align-items: center; gap: 5px;
padding: 3px 9px; border-radius: 999px; font-size: 12px;
background: var(--line-2); color: var(--ink); white-space: nowrap;
}
.led-kind-chip b { font-variant-numeric: tabular-nums; }
.led-kind-chip.is-debt { background: #E7F2EC; }
.led-shares { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-bottom: 10px; }
.led-share-chip {
display: inline-flex; align-items: center; gap: 6px;
padding: 3px 8px; border: 1px solid var(--line); border-radius: 999px;
}
.led-ops { display: flex; flex-wrap: wrap; align-items: flex-start; gap: 12px; }
.led-ops .inline-form { flex-wrap: wrap; align-items: center; }
.led-ops .input { min-width: 140px; }
.led-edit summary { cursor: pointer; }
.led-edit .node-form { min-width: min(520px, 86vw); }
/* ---- 财务:变化折线图 + 设置下拉(v24 用户 2026-10-03 第 7、8 条)---- */
.led-charts { margin-bottom: 0; }
.ck-grid {
display: grid; gap: 14px;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
}
.ck-card {
border: 1px solid var(--line-2); border-radius: var(--radius-s);
background: #FDFBF7; padding: 10px 12px;
color: var(--ink); /* SVG 用 currentColor 画基线与日期,靠这里继承 */
}
.ck-head { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; margin-bottom: 4px; }
.ck-head b { font-size: 14px; }
.ck-svg { display: block; margin-top: 2px; }
/* 盘点面板:与「更正」并列在一个 <details> 里,中间给一条分隔线才分得清两件事 */
.led-recon { padding-bottom: 10px; border-bottom: 1px dashed var(--line-2); margin-bottom: 10px; }
.led-form-h { font-size: 12.5px; color: var(--muted); font-weight: 500; margin: 0 0 8px; }
.led-hist { margin-top: 6px; }
.led-hist summary { cursor: pointer; }
.led-hist-list { list-style: none; margin: 6px 0 0; padding: 0; font-size: 13px; }
.led-hist-list li {
display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap;
padding: 5px 0; border-top: 1px solid var(--line-2);
}
.led-hist-list li b { font-variant-numeric: tabular-nums; }
/* 账本设置下拉:比观影那个内容多,给个上限 + 内部滚动 */
.led-set .menu-list { min-width: 280px; max-width: min(340px, 94vw); }
.led-set-list { padding: 10px; gap: 0; max-height: 70vh; overflow-y: auto; }
.led-set-block { padding: 8px 2px; }
.led-set-block + .led-set-block { border-top: 1px solid var(--line-2); margin-top: 4px; }
.led-set-shares { list-style: none; margin: 0 0 8px; padding: 0; }
.led-set-shares li {
display: flex; align-items: center; gap: 6px; flex-wrap: wrap; padding: 4px 0;
}
.led-set-shares li .node-badge { flex: 0 0 auto; }
.led-set-form-in { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.led-set-form-in .input { flex: 1 1 120px; min-width: 0; }
.led-set-form-in .pp { flex: 1 1 100%; }
.led-add { margin-bottom: 18px; }
/* ---- 纪念日(v24 第 13 条) ---- */
.ann-up { list-style: none; margin: 0; padding: 0; }
.ann-up-item {
display: flex; flex-wrap: wrap; align-items: baseline; gap: 8px;
padding: 8px 2px; border-bottom: 1px solid var(--line-2); font-size: 14px;
}
.ann-up-item:last-child { border-bottom: 0; }
.ann-days {
flex: 0 0 auto; min-width: 62px; font-weight: 600; color: var(--brand);
font-variant-numeric: tabular-nums;
}
.ann-date { flex: 0 0 auto; color: var(--muted); font-variant-numeric: tabular-nums; }
.ann-title { flex: 1 1 auto; color: var(--ink); min-width: 0; }
.chk-inline { display: flex; align-items: center; gap: 6px; font-size: 14px; }
/* ---- 任务设置(v24 第 9、10 条):看板/分类的增删改列表 ---- */
.kb-boards { margin-bottom: 12px; }
.set-list { display: flex; flex-direction: column; gap: 8px; margin: 4px 0 0; }
/* 一行 = 一个「改名」表单 + 一个「删除」表单,靠 flex 排成一条 */
.set-row { flex: 1 1 auto; display: flex; align-items: center; gap: 8px; margin: 0; }
.set-row .input { flex: 1 1 auto; min-width: 0; }
.set-tail { flex: 0 0 auto; }
/* 「已完成」归档列表 */
.done-list { list-style: none; margin: 0; padding: 0; }
.done-item {
display: flex; flex-wrap: wrap; align-items: center; gap: 8px;
padding: 9px 2px; border-bottom: 1px solid var(--line-2); font-size: 14px;
}
.done-item:last-child { border-bottom: 0; }
.done-title { color: var(--ink); text-decoration: none; }
.done-title:hover { color: var(--brand); text-decoration: underline; }
/* ---- 附件缩略图墙 + 全局预览(v24 第 3 条) ----
规则:图片/视频给缩略图,PDF/Word/Excel 给**同尺寸**图标;
可预览的点开在本页这一层里看,不可预览的点了直接下载。 */
.att-wall {
display: flex; flex-wrap: wrap; gap: 12px; margin: 8px 0 2px;
}
.att-cell { width: 132px; }
/* ⚠️ 缩略图与文档图标**必须是同一个盒子**:用户要的是"图标跟预览图一样大小",
所以尺寸只在这里写一次,图标靠 flex 撑满,不另给一套尺寸。 */
.att-thumb {
display: flex; align-items: center; justify-content: center;
width: 132px; height: 96px; border-radius: 8px; overflow: hidden;
border: 1px solid var(--line); background: var(--line-2); position: relative;
}
.att-thumb:hover { border-color: var(--brand); }
.att-thumb img, .att-thumb video {
width: 100%; height: 100%; object-fit: cover; display: block;
}
.att-thumb video { object-fit: cover; pointer-events: none; }
.att-play {
position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
color: #fff; font-size: 22px; text-shadow: 0 2px 8px rgba(0, 0, 0, .6); pointer-events: none;
}
.att-doc {
display: flex; flex-direction: column; align-items: center; justify-content: center;
gap: 4px; width: 100%; height: 100%;
font-size: 12px; font-weight: 700; letter-spacing: .5px; color: #fff;
}
.att-doc::before {
content: ""; width: 34px; height: 34px; border-radius: 6px; background: rgba(255, 255, 255, .22);
}
.att-doc-pdf { background: #B0342C; }
.att-doc-doc { background: #2C5AA0; }
.att-doc-xls { background: #1F7A4D; }
.att-doc-ppt { background: #B4622A; }
.att-doc-text { background: #5F6672; }
.att-doc-file { background: #5F6672; }
.att-meta {
display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px 6px;
margin-top: 4px; font-size: 12px; color: var(--muted);
}
.att-fname {
flex: 1 1 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
color: var(--ink);
}
.att-fsize { flex: 0 0 auto; }
.att-meta form { display: inline; margin: 0; }
/* ---- 全局预览层 ---- */
.pv {
position: fixed; inset: 0; z-index: 320; display: flex; flex-direction: column;
background: rgba(0, 0, 0, .9); overscroll-behavior: contain;
}
/* ⚠️ .pv 自己声明了 display:flex,必须补这条,否则 preview.js 里的
box.hidden = true 藏不住它(与 .pp-item 同一个坑)。 */
.pv[hidden] { display: none; }
.pv-head {
flex: 0 0 auto; display: flex; align-items: center; gap: 10px;
padding: 12px 16px; color: #fff;
}
.pv-name {
min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
font-size: 14px;
}
.pv-meta { flex: 0 0 auto; font-size: 12px; opacity: .7; }
/* 下载按钮恒在右上角(用户第 3 条点名要求) */
.pv-dl {
margin-left: auto; flex: 0 0 auto; padding: 6px 14px; border-radius: 8px;
background: #fff; color: #1a1a1a; font-size: 13px; font-weight: 600; text-decoration: none;
}
.pv-dl:hover { background: #eee; }
.pv-close {
flex: 0 0 auto; border: 0; background: rgba(255, 255, 255, .16); color: #fff;
width: 34px; height: 34px; border-radius: 50%; font-size: 16px; cursor: pointer;
}
.pv-close:hover { background: rgba(255, 255, 255, .28); }
.pv-stage {
flex: 1 1 auto; min-height: 0; display: flex; align-items: center; justify-content: center;
padding: 0 16px 16px;
}
.pv-img {
max-width: 92vw; max-height: 100%; width: auto; height: auto;
object-fit: contain; border-radius: 8px;
}
.pv-video { max-width: 92vw; max-height: 100%; width: auto; height: auto; background: #000; }
.pv-pdf {
width: 92vw; height: 100%; border: 0; border-radius: 8px; background: #fff;
}
@media (max-width: 560px) {
.att-cell, .att-thumb { width: 108px; }
.att-thumb { height: 80px; }
}
/* ---- 全局人员选择器(v24 第 11 条):chips + 弹窗 + 拼音首字母联想 ----
⚠️ .att-* 是这套组件早年在观影页的版本,已由 .pp-* 取代;
保留只为历史样式,新页面一律用 _person_picker.php。 */
.pp-chips { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 8px; }
.pp-chip {
display: inline-flex; align-items: center; gap: 6px; padding: 3px 6px 3px 4px;
border: 1px solid var(--line); border-radius: 999px; background: var(--surface);
font-size: 13px;
}
.pp-chip img {
width: 22px; height: 22px; border-radius: 50%; object-fit: cover; flex: 0 0 auto;
background: #F1E7D6;
}
.pp-chip .pp-x {
border: 0; background: none; color: var(--muted); font-size: 15px;
line-height: 1; padding: 0 2px; cursor: pointer;
}
.pp-chip .pp-x:hover { color: #c0392b; }
.pp-chip-empty { color: var(--muted); border-style: dashed; padding: 3px 10px; }
.pp-modal {
position: fixed; inset: 0; z-index: 60; display: flex; align-items: center;
justify-content: center; background: rgba(0, 0, 0, .35); padding: 16px;
}
.pp-modal[hidden] { display: none; }
.pp-modal-box {
background: var(--surface); border-radius: 12px; box-shadow: 0 12px 40px rgba(0, 0, 0, .2);
width: min(420px, 100%); max-height: 70vh; display: flex; flex-direction: column;
padding: 14px 16px; box-sizing: border-box;
}
.pp-modal-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px; }
.pp-close {
border: 0; background: none; font-size: 20px; line-height: 1;
color: var(--muted); cursor: pointer; padding: 2px 6px;
}
.pp-modal-box .input { margin-bottom: 8px; }
.pp-list { overflow-y: auto; border: 1px solid var(--line); border-radius: 8px; }
.pp-item {
display: flex; align-items: center; gap: 8px; padding: 8px 12px; cursor: pointer;
border-bottom: 1px solid var(--line-2); font-size: 14px;
}
/* ⚠️ 这条不能省:.pp-item 自己写了 display:flex,作者样式压过浏览器
[hidden] { display:none },缺了它「过滤」就成了摆设 —— 名字和拼音都搜不动。
弹窗 .pp-modal 同理(上面已有),凡是**自己声明了 display** 的元素要藏都得补一条。 */
.pp-item[hidden] { display: none; }
.pp-item:last-child { border-bottom: 0; }
.pp-item:hover { background: var(--line-2); }
.pp-item input { accent-color: var(--brand); flex: 0 0 auto; }
.pp-item .pp-av {
width: 30px; height: 30px; border-radius: 50%; object-fit: cover; flex: 0 0 auto;
background: #F1E7D6;
}
.pp-item .pp-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pp-item .pp-initials { margin-left: auto; color: var(--muted); font-size: 12px; }
.pp-item-none .pp-name { color: var(--muted); }
.pp-empty { padding: 10px 12px; color: var(--muted); font-size: 13px; }
.pp-empty[hidden] { display: none; }
/* ---- 豆瓣自动补充(v10):片名行 + 下拉候选 + 海报预览 ---- */
.db-title-row { display: flex; gap: 8px; align-items: center; }
.db-title-row .input { flex: 1 1 auto; }
.db-title-row .btn { flex: 0 0 auto; white-space: nowrap; }
.db-suggest {
margin-top: 6px; border: 1px solid var(--line); border-radius: 10px;
background: var(--surface); overflow: hidden; max-height: 300px; overflow-y: auto;
}
.db-suggest[hidden] { display: none; }
.db-item {
display: flex; align-items: center; gap: 10px; width: 100%; padding: 8px 12px;
border: 0; border-bottom: 1px solid var(--line-2); background: none;
text-align: left; cursor: pointer; font: inherit; color: var(--ink);
}
.db-item:last-child { border-bottom: 0; }
.db-item:hover { background: var(--line-2); }
.db-item img { width: 32px; height: 45px; object-fit: cover; border-radius: 4px; background: var(--line-2); flex: 0 0 auto; }
.db-item-tx { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.db-item-tx b { font-size: 14px; }
.db-item-tx small { color: var(--muted); font-size: 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.db-poster-preview { display: flex; align-items: center; gap: 10px; margin-top: 8px; }
.db-poster-preview[hidden] { display: none; }
.db-poster-preview img {
width: 60px; height: 90px; object-fit: cover; border-radius: 6px;
border: 1px solid var(--line); background: var(--line-2);
}
/* 详情页英雄区里的豆瓣信息块 */
.db-meta {
display: flex; flex-wrap: wrap; gap: 4px 14px; align-items: baseline;
margin: 8px 0 2px; padding: 8px 12px; border-radius: 10px;
background: var(--line-2); font-size: 13px; color: var(--ink);
}
.db-meta .db-rating { color: #f5b301; font-weight: 600; }
.db-meta .db-rating b { font-size: 16px; }
.db-meta .db-line { display: inline-flex; gap: 6px; align-items: baseline; min-width: 0; }
.db-meta .db-label { color: var(--muted); flex: 0 0 auto; }
.db-meta .db-link { margin-left: auto; color: var(--brand); text-decoration: none; white-space: nowrap; }
.db-meta .db-link:hover { text-decoration: underline; }
.rv-word { font-size: 13px; color: var(--muted); }
.rv-word.is-set { color: var(--brand); font-weight: 600; }
.rv-textarea {
width: 100%; min-height: 92px; resize: vertical; padding: 12px 14px;
border: 1px solid var(--line); border-radius: 10px; font-size: 15px;
font-family: inherit; line-height: 1.7; background: var(--surface);
color: var(--ink); box-sizing: border-box;
}
.rv-textarea:focus { outline: none; border-color: var(--brand); }
.rv-textarea-sm { min-height: 56px; font-size: 14px; }
.rv-form-foot { display: flex; align-items: center; justify-content: flex-end; gap: 12px; margin-top: 10px; flex-wrap: wrap; }
.rv-form-foot .rv-hint { margin-right: auto; }
.rv-hint { font-size: 12px; color: var(--muted); }
.rv-pick-label { display: block; font-size: 12px; color: var(--muted); margin-bottom: 6px; }
.rv-empty { color: var(--muted); font-size: 14px; }
.rv-list { display: flex; flex-direction: column; gap: 18px; }
.rv-item { border-top: 1px solid var(--line-2); padding-top: 16px; }
.rv-item:first-child { border-top: none; padding-top: 0; }
.rv-head { display: flex; align-items: center; gap: 10px; }
.rv-avatar { width: 34px; height: 34px; border-radius: 50%; object-fit: cover; flex: 0 0 auto; }
.rv-avatar-sm { width: 26px; height: 26px; }
.rv-avatar-text {
display: inline-flex; align-items: center; justify-content: center;
background: var(--line-2); color: var(--muted); font-weight: 600; font-size: 15px;
}
.rv-who { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.rv-name { font-size: 14px; font-weight: 600; color: var(--ink); }
/* ⚠️ 写着「我」的那个徽标(类名是「评价」区块 + 「我的」后缀)v11 删掉了:
它在 .rv-who(flex column)里被 align-items:stretch 拉成整行宽的色带,
星星旁边显出一条丑长的品牌色条。删除按钮已经表达了「这是我写的」,
不需要徽标。别再把它加回来。 */
.rv-time { display: block; font-size: 12px; color: var(--muted); }
.rv-score { flex: 0 0 auto; display: inline-flex; align-items: center; gap: 5px; }
.rv-score .stars-disp { font-size: 16px; }
.rv-score-max { font-size: 12px; font-weight: 500; color: var(--muted); font-variant-numeric: tabular-nums; }
.rv-del { margin-left: auto; }
.rv-del-btn {
background: none; border: none; cursor: pointer; color: var(--muted);
font-size: 14px; line-height: 1; padding: 4px 6px; border-radius: 6px;
}
.rv-del-btn:hover { background: rgba(192, 83, 63, .12); color: var(--red); }
.rv-content {
margin: 8px 0 2px 44px; font-size: 15px; line-height: 1.75;
color: var(--ink); word-break: break-word;
}
.rv-updated { margin-left: 44px; font-size: 12px; color: var(--muted); }
.rv-comments { margin: 10px 0 10px 44px; display: flex; flex-direction: column; gap: 8px; }
.rv-comment { display: flex; gap: 8px; background: var(--line-2); border-radius: 10px; padding: 8px 12px; }
.rv-c-body { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.rv-c-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.rv-c-head .rv-name { font-size: 13px; }
.rv-c-text { font-size: 14px; line-height: 1.7; color: var(--ink); word-break: break-word; }
.rv-c-acts { margin-top: 6px; }
.rv-thread { display: flex; flex-direction: column; gap: 6px; }
.rv-comment-reply { margin-left: 22px; }
.rv-at { font-size: 12.5px; color: var(--muted); margin-bottom: 2px; }
.rv-at span { color: var(--brand); font-weight: 600; }
.rv-actions { display: flex; align-items: flex-start; gap: 8px 10px; flex-wrap: wrap; margin: 12px 0 0 44px; }
.rv-feelings { display: flex; flex-wrap: wrap; gap: 6px; margin: 4px 0 2px; }
.rv-item > .rv-feelings { margin-left: 44px; }
.rv-feeling-pick { margin-top: 8px; }
.feel-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.feel-chip { display: inline-flex; }
.feel-pick { position: relative; }
.feel-pick input { position: absolute; width: 1px; height: 1px; opacity: 0; }
.feel-inner {
display: inline-flex; align-items: center; gap: 4px; padding: 4px 10px;
border: 1px solid var(--line); border-radius: 20px; background: var(--surface);
font-size: 13px; color: var(--muted); cursor: pointer; user-select: none;
}
.feel-pick:hover .feel-inner { border-color: var(--brand); }
.feel-pick input:checked + .feel-inner { border-color: var(--brand); background: rgba(168, 74, 40, .10); color: var(--brand); font-weight: 600; }
.feel-pick:focus-within .feel-inner { outline: 2px solid var(--brand); outline-offset: 1px; }
.feel-emoji { font-size: 14px; }
.feel-chip.is-static {
align-items: center; gap: 4px; padding: 2px 10px; border-radius: 20px;
background: rgba(168, 74, 40, .10); color: var(--brand); font-size: 12px;
}
/* ---- 影院管理页 ---- */
.cinema-h2 { font-size: 17px; font-weight: 600; margin: 0 0 14px; }
.cinema-select-row { display: flex; gap: 10px; align-items: center; }
.cinema-select-row .select { flex: 1; }
.color-row { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.color-picker { width: 60px; height: 40px; padding: 2px; border: 1px solid var(--line); border-radius: 8px; background: var(--surface); cursor: pointer; }
.color-hex { font-size: 14px; color: var(--muted); font-family: monospace; }
.preset-palette { display: flex; flex-wrap: wrap; gap: 6px; }
.preset-color {
display: inline-block; width: 28px; height: 28px; border-radius: 50%;
cursor: pointer; border: 2px solid transparent; transition: transform .2s, border-color .2s;
}
.preset-color:hover { transform: scale(1.1); }
.preset-color.active { border-color: var(--ink); }
.color-preview {
display: inline-block; width: 14px; height: 14px; border-radius: 3px;
margin-right: 6px; vertical-align: middle; border: 1px solid var(--line);
}
.table-wrap { overflow-x: auto; }
.table { width: 100%; border-collapse: collapse; font-size: 14px; }
.table th { text-align: left; padding: 10px 8px; border-bottom: 2px solid var(--line); color: var(--muted); font-weight: 600; }
.table td { padding: 10px 8px; border-bottom: 1px solid var(--line-2); }
.table tr:hover td { background: var(--line-2); }
/* ---- 观影响应式 ---- */
@media (max-width: 720px) {
.waterfall { grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 12px; }
.card-body { padding: 10px 12px 12px; }
.card-title { font-size: 14px; }
.card-meta { font-size: 11px; gap: 4px 10px; }
.detail-hero { grid-template-columns: 1fr; padding: 20px; }
.detail-poster { max-width: 240px; margin: 0 auto; }
.cinema-info { grid-template-columns: 1fr; }
.gallery-grid { grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); }
.detail-section { padding: 16px 18px; }
.detail-info h1 { font-size: 22px; }
.rv-content, .rv-updated, .rv-comments, .rv-actions, .rv-item > .rv-feelings { margin-left: 0; }
.rv-comment-reply { margin-left: 10px; }
.lb-nav { width: 40px; height: 40px; font-size: 24px; }
.lb-prev { left: 6px; }
.lb-next { right: 6px; }
.lb-img { max-width: 88vw; max-height: 70vh; }
}
/* ============================================================
v11:观影统计 / 高级搜索 / 年度报告(从 MovieLog 迁入)
版式沿用家书的暖纸配色;图表全用 CSS(不引 CDN 图表库)。
============================================================ */
/* ---- 统计仪表板 ---- */
.stats-grid {
display: grid; gap: 14px; margin-bottom: 16px;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
}
.stat-card {
background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
padding: 16px 18px; text-align: center; box-shadow: var(--shadow);
}
.stat-card .number { font-size: 30px; font-weight: 700; color: var(--brand); line-height: 1.2; font-variant-numeric: tabular-nums; }
.stat-card .label { font-size: 13px; color: var(--muted); margin-top: 2px; }
.stats-span {
display: flex; align-items: baseline; gap: 12px; margin-bottom: 16px;
}
.stats-span .label { font-size: 13px; color: var(--muted); }
.stats-span .value { font-size: 16px; font-weight: 600; }
.chart-card {
background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
padding: 18px 22px; box-shadow: var(--shadow); margin-bottom: 16px;
}
.chart-card h3 { font-size: 16px; font-weight: 600; margin: 0 0 14px; display: flex; align-items: baseline; gap: 8px; }
.chart-card h3 .hint { font-size: 12px; color: var(--faint); font-weight: 400; }
.two-col { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
@media (max-width: 720px) { .two-col { grid-template-columns: 1fr; } }
.bar-chart { display: flex; flex-direction: column; gap: 8px; }
.bar-item { display: flex; align-items: center; gap: 12px; }
.bar-item .bar-label {
width: 150px; flex: 0 0 auto; font-size: 13px; color: var(--ink);
display: flex; align-items: center; gap: 6px;
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.bar-item .bar-label .bar-sub { color: var(--faint); font-size: 11px; }
.bar-item .bar-avatar { width: 22px; height: 22px; border-radius: 50%; object-fit: cover; flex: 0 0 auto; }
.bar-item .color-dot { display: inline-block; width: 11px; height: 11px; border-radius: 3px; flex: 0 0 auto; }
.bar-item .bar-track {
flex: 1; height: 20px; background: var(--line-2); border-radius: 6px; overflow: hidden;
}
.bar-item .bar-fill {
display: block; height: 100%; border-radius: 6px; background: var(--brand);
}
.bar-item .bar-fill-alt { background: var(--brand-2); }
.bar-item .bar-fill-green { background: var(--green); }
.bar-item .bar-count {
width: 40px; flex: 0 0 auto; text-align: right; font-size: 13px; font-weight: 600;
font-variant-numeric: tabular-nums;
}
@media (max-width: 720px) {
.bar-item .bar-label { width: 92px; font-size: 12px; }
.bar-item .bar-count { width: 30px; }
.chart-card { padding: 14px 16px; }
}
/* ---- 高级搜索 ---- */
.adv-form { margin-bottom: 18px; }
.date-range { display: flex; align-items: center; gap: 8px; }
.date-range .to { color: var(--muted); font-size: 13px; flex: 0 0 auto; }
/* ⚠️ 原生 <input type="date"> 的 min-width:auto 约 150px(带日历图标 + 占位数字)——
两个日期框 + 「至」在 360px 下会横向溢出(实测 scrollW 390 > clientW 360)。
给 0 并让它们等比收缩;这是继 .field(select)之后同一页的第二个溢出源。 */
.date-range .input { flex: 1 1 0; min-width: 0; }
.results-head {
display: flex; align-items: baseline; justify-content: space-between;
gap: 10px; margin: 0 2px 12px;
}
.results-head h3 { margin: 0; font-size: 17px; }
.results-head .sub { color: var(--muted); font-size: 13px; }
/* ---- 年度报告 ---- */
.year-pick { display: inline-block; }
.report-grid {
display: grid; gap: 14px; margin-bottom: 16px;
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
}
.report-grid.two { grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); }
.report-card {
background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
padding: 18px 20px; box-shadow: var(--shadow);
}
.report-card .n { font-size: 32px; font-weight: 700; color: var(--brand); line-height: 1.15; font-variant-numeric: tabular-nums; }
.report-card .l { font-size: 13px; color: var(--muted); }
.report-card .v { font-size: 19px; font-weight: 600; margin-top: 2px; }
.report-card .s { font-size: 12px; color: var(--faint); margin-top: 4px; }
/* 月份竖柱图(纯 CSS) */
.vgrid {
display: grid; grid-template-columns: repeat(12, 1fr); gap: 6px;
align-items: end; height: 160px; margin-top: 4px;
}
.vcol { display: flex; flex-direction: column; align-items: center; justify-content: flex-end; height: 100%; gap: 3px; }
.vcol .vbar {
width: 68%; min-height: 2px; background: linear-gradient(var(--brand-2), var(--brand));
border-radius: 5px 5px 2px 2px; transition: height .4s ease;
}
.vcol .vlab { font-size: 11px; color: var(--muted); }
.vcol .vnum { font-size: 11px; color: var(--faint); font-variant-numeric: tabular-nums; }
.kw-cloud { display: flex; flex-wrap: wrap; gap: 8px; }
.kw-cloud .kw {
font-size: 14px; padding: 4px 12px; border-radius: 999px;
background: var(--line-2); color: var(--brand-ink); border: 1px solid var(--line);
}
/* ==================== v12:旅程 / 日程 / 资源 / 任务 ==================== */
/* 四个新模块共用一套"卡片骨架"(.card + .card-pad + .cinema-h2 的小标题),
下面只写各模块**特有**的部件,避免又造一套并行的卡片。 */
/* ---- 旅程 ---- */
.trip-list { display: flex; flex-direction: column; gap: 12px; }
.trip-card {
display: flex; gap: 16px; background: var(--surface); border: 1px solid var(--line);
border-radius: var(--radius); padding: 14px; text-decoration: none; color: inherit;
transition: transform .18s ease, box-shadow .18s ease;
}
.trip-card:hover { transform: translateY(-2px); box-shadow: var(--shadow-l); text-decoration: none; }
.trip-cover {
width: 132px; height: 96px; flex: 0 0 auto; border-radius: var(--radius-s);
overflow: hidden; background: #F1E7D6; display: flex; align-items: center; justify-content: center;
}
.trip-cover img { width: 100%; height: 100%; object-fit: cover; display: block; }
.trip-cover-empty { font-size: 34px; opacity: .55; }
.trip-main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 6px; }
.trip-title { font-size: 17px; font-weight: 600; margin: 0; }
.trip-meta { display: flex; flex-wrap: wrap; gap: 4px 14px; font-size: 13px; color: var(--muted); }
.trip-sum { margin: 0; font-size: 14px; color: var(--muted); line-height: 1.6; }
.trip-people { display: flex; flex-wrap: wrap; gap: 4px 8px; }
.trip-hero { display: flex; gap: 20px; flex-wrap: wrap; }
.trip-hero-cover {
width: 260px; max-width: 100%; aspect-ratio: 4 / 3; flex: 0 0 auto; border-radius: var(--radius-s);
overflow: hidden; background: #F1E7D6; display: flex; align-items: center; justify-content: center;
}
.trip-hero-cover img { width: 100%; height: 100%; object-fit: cover; display: block; }
.trip-hero-body { flex: 1 1 320px; min-width: 0; display: flex; flex-direction: column; gap: 10px; }
.trip-loc { margin: 0; font-size: 14px; color: var(--muted); }
.cover-now { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; }
.cover-now img { width: 96px; height: 64px; object-fit: cover; border-radius: 8px; display: block; }
.photo-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); gap: 10px; }
.photo-cell { margin: 0; background: #F1E7D6; border-radius: var(--radius-s); overflow: hidden; }
.photo-cell a { display: block; }
.photo-cell img { width: 100%; height: 120px; object-fit: cover; display: block; }
.photo-del { padding: 6px; text-align: center; }
/* ---- 行程节点 ---- */
.node-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 10px; }
.node-row {
display: flex; gap: 12px; align-items: flex-start; padding: 12px 14px;
border: 1px solid var(--line-2); border-radius: var(--radius-s); background: #FDFBF7;
}
.node-row.status-cancelled { opacity: .62; }
.node-row.status-done .node-head b { color: var(--muted); }
.node-ico { font-size: 20px; flex: 0 0 auto; line-height: 1.4; }
.node-body { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 5px; }
.node-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.node-head b { font-size: 15px; }
.node-badge {
font-size: 11px; padding: 1px 8px; border-radius: 999px;
background: var(--line-2); color: var(--muted); white-space: nowrap;
}
.node-row.status-done .node-badge { background: #E6F0DC; color: #4A7226; }
.node-place, .node-time { font-size: 12px; color: var(--muted); }
.node-detail { margin: 0; font-size: 14px; line-height: 1.65; }
.node-ops { flex: 0 0 auto; display: flex; flex-direction: column; gap: 6px; align-items: flex-end; }
.node-edit > summary { cursor: pointer; }
.node-add { margin-top: 14px; border-top: 1px solid var(--line-2); padding-top: 12px; }
.node-add > summary { cursor: pointer; font-size: 14px; }
.node-form { display: flex; flex-direction: column; gap: 10px; margin-top: 10px; }
.nf-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.nf-grid .field.full { grid-column: 1 / -1; }
.nf-members { display: flex; flex-wrap: wrap; gap: 6px 16px; }
/* ---- 附件行(旅程节点 / 资源 / 任务共用) ---- */
.attach-row { display: flex; flex-wrap: wrap; gap: 8px; }
.attach-item {
display: inline-flex; align-items: center; gap: 5px; font-size: 13px;
padding: 4px 10px; border-radius: 999px; border: 1px solid var(--line);
background: var(--surface); color: var(--brand-ink); text-decoration: none;
}
.attach-item:hover { border-color: var(--brand); text-decoration: none; }
/* ---- 日程(月视图,群晖 Calendar 的外观) ---- */
.cal-wrap { display: grid; grid-template-columns: 230px 1fr; gap: 18px; align-items: start; }
/* 用户 2026-10-03 第 4 条:左侧「我的日历本」那一栏撤了,日历占满整宽。
⚠️ 用**单列**改写而不是删掉 .cal-wrap:`.cal-wrap` 还被别处(窄屏媒体查询)
引用着,删掉会连带把那条声明变空。单列 = 视觉上就是"充满整个宽度"。 */
.cal-wrap-full { grid-template-columns: 1fr; }
.cal-side { background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); padding: 16px 18px; }
.cal-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.cal-item { display: flex; flex-direction: column; gap: 4px; }
.cal-item > .node-badge { align-self: flex-start; }
.cal-toggle { display: flex; align-items: center; gap: 8px; font-size: 14px; cursor: pointer; }
.cal-toggle input { accent-color: var(--brand); }
.cal-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cal-note { font-size: 12px; color: var(--faint); line-height: 1.6; margin: 12px 0 0; }
.cal-main { min-width: 0; }
.cal-add { margin-bottom: 18px; }
.cal-grid-card { padding: 0; overflow: hidden; }
.cal-head { display: grid; grid-template-columns: repeat(7, 1fr); border-bottom: 1px solid var(--line); }
.cal-wd { padding: 8px 0; text-align: center; font-size: 12px; color: var(--muted); }
.cal-grid { display: grid; grid-template-columns: repeat(7, 1fr); }
.cal-cell {
min-height: 104px; padding: 6px 6px 8px; border-right: 1px solid var(--line-2);
border-bottom: 1px solid var(--line-2); display: flex; flex-direction: column; gap: 4px;
}
.cal-cell:nth-child(7n) { border-right: 0; }
.cal-cell:nth-last-child(-n+7) { border-bottom: 0; }
.cal-cell.is-out { background: #FCFAF6; }
.cal-cell.is-out .cal-dnum { color: var(--faint); }
.cal-cell.is-today { background: #FDF6F1; }
.cal-dnum { font-size: 12px; color: var(--muted); font-variant-numeric: tabular-nums; padding-left: 2px; }
.cal-cell.is-today .cal-dnum {
color: #fff; background: var(--brand); border-radius: 999px;
width: 20px; height: 20px; display: flex; align-items: center; justify-content: center;
}
.cal-evs { display: flex; flex-direction: column; gap: 3px; }
.cal-ev {
display: block; font-size: 12px; line-height: 1.35; color: #fff;
padding: 2px 6px; border-radius: 5px; text-decoration: none;
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.cal-ev:hover { text-decoration: none; filter: brightness(1.08); }
.cal-ev.is-allday { font-weight: 500; }
.cal-more { font-size: 11px; color: var(--faint); padding-left: 2px; }
.ev-del { margin-top: 16px; }
.cal-share-name { font-size: 13px; color: var(--muted); }
@media (max-width: 860px) {
.cal-wrap { grid-template-columns: 1fr; }
.cal-cell { min-height: 78px; }
}
/* ---- 资源 ---- */
.res-list { display: flex; flex-direction: column; gap: 8px; }
.res-item { border: 1px solid var(--line-2); border-radius: var(--radius-s); background: #FDFBF7; }
.res-head {
display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
padding: 12px 14px; cursor: pointer; list-style: none;
}
.res-head::-webkit-details-marker { display: none; }
.res-head:hover { background: #FAF7F1; }
.res-title { font-size: 15px; font-weight: 600; }
.res-n { font-size: 12px; color: var(--faint); }
.res-body { padding: 0 14px 14px; display: flex; flex-direction: column; gap: 10px; }
.res-size { font-size: 11px; color: var(--faint); }
.res-inline { display: inline; margin: 0; }
.res-shares { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.res-label { font-size: 12px; color: var(--muted); }
.res-ops { display: flex; flex-direction: column; gap: 8px; align-items: flex-start; }
.res-share-add > summary { cursor: pointer; }
/* ---- 资源列表:搜索在上(用户 2026-10-03 第 6 条)、排序与截断提示(第 10 条)---- */
.res-search-card { display: flex; flex-direction: column; gap: 10px; }
.res-search { margin-bottom: 0; }
/* 搜索框给宽一点:资源标题长(「不动产证 / 学区房合同」),窄了看不全 */
.res-search .input { max-width: none; flex: 1 1 auto; min-width: 0; }
.res-sortbar { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.res-sortbar .seg { margin: 0; }
.res-showall { margin-left: auto; }
.res-more {
display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--line-2);
}
/* ---- 任务看板 ----
⚠️ 列数不再固定为 3:v24(用户 2026-10-03 第 7 条)起**一列 = 用户自己的一个看板**,
看板能加能删,写死 repeat(3, 1fr) 的话加第四个看板就直接掉到第二行去
(看起来像"加了个看板结果没显示")。
改成"横向自动排 + 可横向滚动":几列都放得下,列多了也不挤。 */
.kanban {
display: grid; grid-auto-flow: column;
grid-auto-columns: minmax(230px, 1fr);
gap: 14px; align-items: start; overflow-x: auto; padding-bottom: 2px;
}
.kb-col { background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden; }
.kb-head {
display: flex; align-items: center; gap: 8px; padding: 12px 16px;
border-bottom: 1px solid var(--line-2); font-size: 14px;
}
.kb-head .kb-n { margin-left: auto; font-size: 12px; color: var(--faint); }
.kb-body { padding: 12px; display: flex; flex-direction: column; gap: 10px; min-height: 90px; }
.kb-body.is-over { background: #FDF6F1; outline: 2px dashed var(--brand-2); outline-offset: -4px; }
.kb-card {
border: 1px solid var(--line-2); border-radius: var(--radius-s); background: #FDFBF7;
padding: 10px 12px; display: flex; flex-direction: column; gap: 6px;
}
.kb-card.is-dragging { opacity: .45; }
.kb-card.is-overdue { border-color: rgba(192, 83, 63, .4); }
.kb-title { font-size: 14px; font-weight: 500; color: var(--ink); }
.kb-title:hover { color: var(--brand); }
.kb-meta { display: flex; flex-wrap: wrap; gap: 4px 8px; font-size: 12px; color: var(--muted); }
.kb-due.is-late, .kb-late { color: var(--red); }
.kb-ico { white-space: nowrap; }
.kb-sub { display: flex; align-items: center; gap: 8px; }
.kb-bar { flex: 1 1 auto; height: 5px; border-radius: 999px; background: var(--line-2); overflow: hidden; }
.kb-bar i { display: block; height: 100%; background: var(--green); }
.kb-sub-n { font-size: 11px; color: var(--faint); font-variant-numeric: tabular-nums; }
.kb-foot { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.kb-move { margin-left: auto; display: flex; gap: 4px; }
.kb-empty { padding: 6px 2px; }
.hidden-form { display: none; }
/* ---- 任务详情 ---- */
.sub-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.sub-row {
display: flex; align-items: center; gap: 10px; padding: 7px 10px;
border: 1px solid var(--line-2); border-radius: var(--radius-s); background: #FDFBF7;
}
.sub-row.is-done .sub-title { color: var(--muted); text-decoration: line-through; }
.sub-check {
background: none; border: 0; cursor: pointer; font-size: 17px; line-height: 1;
padding: 0 2px; color: var(--brand);
}
.sub-check.is-static { cursor: default; color: var(--muted); }
.sub-title { flex: 1 1 auto; min-width: 0; font-size: 14px; }
.cmt-list { list-style: none; margin: 0 0 12px; padding: 0; display: flex; flex-direction: column; gap: 10px; }
.cmt-row { border-top: 1px solid var(--line-2); padding-top: 10px; }
.cmt-who { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.cmt-time { font-size: 11px; color: var(--faint); }
.cmt-body { font-size: 14px; line-height: 1.7; margin: 6px 0; }
.cmt-body .mention { color: var(--brand-ink); background: #F6E3D7; border-radius: 4px; padding: 0 3px; }
.cmt-form { margin-top: 6px; }
.task-danger { margin-top: 18px; display: flex; justify-content: flex-end; }
@media (max-width: 860px) {
.kanban { grid-template-columns: 1fr; }
.nf-grid { grid-template-columns: 1fr; }
}
/* ---- 个人设置里的「模块」两块 ---- */
.mod-order .mod-row { cursor: grab; }
.mod-order .mod-row.is-dragged { opacity: .45; cursor: grabbing; }
.mod-order .mod-row.is-drop-before { box-shadow: inset 0 3px 0 var(--brand); }
.mod-order .mod-row.is-drop-after { box-shadow: inset 0 -3px 0 var(--brand); }
.mod-pick { display: flex; align-items: center; gap: 8px; flex: 1 1 auto; min-width: 0; cursor: pointer; }
.mod-pick input { accent-color: var(--brand); }
.mod-swatches { display: flex; flex-wrap: wrap; gap: 8px 18px; }
.mod-swatch { display: inline-flex; align-items: center; gap: 6px; font-size: 13px; color: var(--muted); }
.mod-colors { display: flex; flex-direction: column; gap: 10px; }
.mod-color-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.mod-color-name { flex: 0 0 76px; font-size: 14px; }
.mod-preset { display: flex; flex-wrap: wrap; gap: 4px; }
.mod-preset .preset-color { cursor: pointer; }
/* ---- 纪念日设置下拉(v24 用户 2026-10-03 第 9 条)---- */
.ann-set .menu-list { min-width: 260px; max-width: min(320px, 92vw); }
.ann-set-list { padding: 10px; gap: 0; }
.ann-kind-list { list-style: none; margin: 0; padding: 0; max-height: 46vh; overflow-y: auto; }
.ann-kind-list li {
display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
padding: 5px 2px; border-bottom: 1px solid var(--line-2);
}
.ann-kind-list li:last-child { border-bottom: 0; }
.ann-kind-name { font-size: 13.5px; font-weight: 500; }
.ann-kind-list li .small { margin-left: auto; }
/* ---- 首页 dashboard ---- */
.hero-actions { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
.dash-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 2px; }
.dash-row {
display: flex; align-items: baseline; gap: 10px; padding: 8px 2px;
border-top: 1px solid var(--line-2); flex-wrap: wrap;
}
.dash-row:first-child { border-top: 0; }
.dash-dot { width: 8px; height: 8px; border-radius: 50%; flex: 0 0 auto; }
.dash-when { flex: 0 0 96px; font-size: 12px; color: var(--muted); font-variant-numeric: tabular-nums; }
.dash-what { flex: 1 1 auto; min-width: 0; font-size: 14px; }
.dash-tag { flex: 0 0 auto; font-size: 11px; color: var(--faint); }
@media (max-width: 620px) {
.dash-when { flex: 0 0 auto; }
}
/* ---- v14:导航条从 4 个模块变成最多 8 个,窄屏必须能横向滑动 ----
⚠️ 这是本轮**实测出来的回归**:模块从 4 个加到 8 个之后,
375px 视口下 scrollWidth=472 > clientWidth=375,**整页横向溢出**
(顶栏把 body 撑宽了)。以前 4 个模块刚好塞得下,所以从没有人给 .nav 写过窄屏规则。
修法用「导航自己横向滚动」而不是「换行」:
换行会把顶栏从 60px 撑成两行,sticky 顶栏一高一矮很难看,而且滚动时页面会跳。
⚠️ 断点必须是 **720px**(站点既有的"窄屏留白 16px"断点),不能用 860:
746px 宽时若只改顶栏留白,就会出现「顶栏 x=16、正文 x=24」错位 8px
(几何检查会直接报出来)。窄屏规则只在站点真正切到小留白的那一档生效。 */
/* ==================== 月 / 周 / 天 三选一的分段控件 ==================== */
/* 三个按钮连成一条(相邻边各留一半边框,拼起来才是一整条外框),
当前那个用模块主题色填底 —— 一眼看出自己在哪种看法上。 */
.seg { display: inline-flex; }
.seg-btn {
padding: 6px 14px; font-size: 13px; line-height: 1;
color: var(--muted); background: var(--surface);
border: 1px solid var(--line); text-decoration: none;
}
.seg-btn + .seg-btn { border-left: 0; }
.seg-btn:first-child { border-radius: var(--radius-s) 0 0 var(--radius-s); }
.seg-btn:last-child { border-radius: 0 var(--radius-s) var(--radius-s) 0; }
.seg-btn:hover { background: var(--surface-2, #FDFBF7); text-decoration: none; }
.seg-btn.is-on {
background: var(--brand); border-color: var(--brand); color: #fff; font-weight: 600;
}
/* ==================== 周 / 天:按小时铺的时间网格 ==================== */
/* 结构:左边一条"小时刻度"沟槽(tg-gutter),右边 N 个日期列(tg-col)。
列高固定 = 24 小时 × 每小时 44px,日程块按 top/height 百分比**绝对定位**,
所以"09:15 的 20 分钟"就是一小条,不会跟 9 点整的会挤成一样高。 */
.cal-tg-card { padding: 0; overflow: auto; }
.tg-head { display: flex; position: sticky; top: 0; z-index: 3;
background: var(--surface); border-bottom: 1px solid var(--line); }
.tg-gutter {
flex: 0 0 56px; width: 56px; border-right: 1px solid var(--line);
display: flex; flex-direction: column; align-items: flex-end;
}
.tg-dayname {
flex: 1 1 0; min-width: 0; padding: 8px 6px; text-align: center;
border-left: 1px solid var(--line-2);
}
.tg-dayname:first-of-type { border-left: 0; }
.tg-dayname.is-today { background: #FDF6F1; }
.tg-wd { display: block; font-size: 11px; color: var(--faint); }
.tg-dd { display: block; font-size: 16px; font-weight: 600; font-variant-numeric: tabular-nums; }
.tg-dayname.is-today .tg-dd { color: var(--brand); }
.tg-allday { display: flex; border-bottom: 1px solid var(--line); background: #FDFBF7; }
.tg-allday .tg-gutter { padding: 6px 6px; }
.tg-tag { font-size: 11px; color: var(--faint); }
.tg-adcell {
flex: 1 1 0; min-width: 0; padding: 4px 4px 6px; border-left: 1px solid var(--line-2);
display: flex; flex-direction: column; gap: 3px;
}
.tg-adcell:first-of-type { border-left: 0; }
.tg-body { display: flex; position: relative; }
.tg-hours { padding: 0; }
.tg-hr {
height: 44px; padding-right: 6px; padding-top: 2px;
font-size: 11px; color: var(--faint); font-variant-numeric: tabular-nums;
border-top: 1px solid var(--line-2); box-sizing: border-box;
}
.tg-hr:first-child { border-top: 0; }
.tg-col {
flex: 1 1 0; min-width: 0; position: relative;
height: 1056px; /* 24 × 44px,与 .tg-hr 的高度必须一致 */
border-left: 1px solid var(--line-2); box-sizing: border-box;
}
.tg-col:first-of-type { border-left: 0; }
.tg-col.is-today { background: #FDF6F1; }
.tg-line { display: block; height: 44px; border-top: 1px solid var(--line-2); box-sizing: border-box; }
.tg-line:first-child { border-top: 0; }
.tg-block {
position: absolute; left: 2px; right: 2px;
display: block; overflow: hidden; padding: 2px 5px;
border-radius: 4px; color: #fff; font-size: 11px; line-height: 1.35;
text-decoration: none; box-sizing: border-box;
border: 1px solid rgba(255,255,255,.35);
}
.tg-block:hover { text-decoration: none; filter: brightness(1.1); z-index: 2; }
.tg-block b { font-weight: 600; margin-right: 3px; }
/* 天视图只有一列,把列拉宽好读;周视图 7 列时列宽由 flex 平分 */
@media (max-width: 720px) {
/* 窄屏留不住 56px 的刻度沟槽 + 7 列,改成整体可横向滚动(.cal-tg-card 已 overflow:auto),
给每列一个最小宽度,滚动着看而不是把字挤成一团。 */
.tg-col, .tg-dayname, .tg-adcell { min-width: 92px; }
.cal-tg-card { -webkit-overflow-scrolling: touch; }
}
/* ⚠️ v24:导航从 7 个模块加到 9 个(纪念日 / 财务)之后,**746px 就放不下了**,
而"放不下"的表现不是换行、是顶栏被撑宽 20.5px:
`.nav` 内部产生横向滚动条(哪怕已用 scrollbar-width:none 隐藏),
`.topbar-inner` 的可用宽度就比 `.container` 少一档,
几何检查直接报「顶栏右 742.5 / 正文右 722.0」。
所以**不能**只在 720px 断点里给 .nav 加快收缩 ——
那样 721~1080 这一段区间没人管,正是出问题的地方。
正确做法:把「导航可收缩 + 横向滚动」提到**无媒体查询的基态**,
只在真的窄到需要缩留白时才改 padding(下面 720px 那段)。 */
.nav {
/* flex:1 1 auto + min-width:0 才真的会"被压缩";只写 min-width 时它仍是 auto 宽度,
内容一多就把顶栏撑出去(这就是 746px 那 20.5px 的来源)。 */
flex: 1 1 auto; min-width: 0;
overflow-x: auto; overscroll-behavior-x: contain; scrollbar-width: none;
}
.nav::-webkit-scrollbar { display: none; }
.nav a { flex: 0 0 auto; }
@media (max-width: 720px) {
.topbar-inner { gap: 12px; padding: 0 16px; }
/* min-width:0 是关键:flex 子项默认 min-width:auto,会拒绝被压缩,
于是内部再 overflow-x 也撑不出滚动条,而是把父级一起顶宽。 */
.nav {
flex: 1 1 auto;
margin-left: 0;
}
.nav a { padding: 6px 10px; }
.topbar-right { margin-left: 0; gap: 6px; }
/* 窄屏把「家书」两个字收起来,只留图标 —— 名字占的宽度正好够多放两个模块 */
.brand span { display: none; }
.who .who-name { display: none; }
}