Files
FamilyBook/assets/css/app.css
T
laoyang c8839fa728 v26 第 26 批(5 条):财务多选按集合取图 / 首页全天置顶与红线定位 / 任务复选框去提示与看板颜色 / 导航搜索引擎切换与站点复用 / 详情页去复选框
- 财务:勾选多个标签时上面那张「总金额」图按**选中集合**统计(新增
  ledger/total_chart 路由 + 单一容器 data-ck-total-host,按集合现取并缓存;
  Ledger::timeline 第二参数放宽为 string|array)
- 首页日程:全天日程移出滚动容器固定置顶;进场把当前时刻红线居中
  (scrollTop 计算,不用 scrollIntoView),触发条件不绑 $showNow
- 任务:复选框去除 title 提示;看板可设颜色(Task::boardColor/updateBoard),
  首页任务行用该颜色显示所属看板
- 导航:展示页补回 id=engine-tabs(切换搜索引擎无效的根因);补内置引擎;
  新增站点输 URL 可复用他人已加过的站点(sharedByUrl/copyIconFile/adoptSite)
- 任务详情页页头去掉复选框(已有「标记完成」按钮)
2026-10-07 15:31:26 +08:00

4468 lines
234 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 { } /* 统一段处理 */
.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 { 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。 */
/* 🔴🔴 v24 第 25 批 用户第 3 条:**并排的两块不该再吃这条"相邻间距"**。
用户原话:「日程和任务看板,两个模块,现在有点错位,一个高,一个低,
应该完全一样才对。」
根因(实测,不是猜):`.home-duo` 里左右两块**都是 `.card`**、且在 DOM 里互为
紧邻兄弟 → `.card + .card` 命中**第二块** → 给它加了 `margin-top:18px`。
grid 的 `align-items: stretch` 让两块**高度**相等(那个数是对的),
但第二块被整体推下去 18px —— 于是"一样高、却一高一低"。
(我上一版只调了 `.home-duo` 的 align-items 与高度,没动这条,
真机探针才把 18px 这个偏移量量出来 —— 光看代码是看不出来的。)
⇒ 并排容器里的 `.card` 一律不吃相邻间距(间距由 `gap` 统一给)。
用 `:first-child` 之外的兄弟选择器把它们点掉,且**特异性要比 0,2,0 高**。 */
.home-duo > .card { margin-top: 0; }
.home-duo > .card + .card { margin-top: 0; }
/* 首页头图 */
.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: var(--hov-veil, rgba(44,35,29,.05));
}
.icon-btn svg { display: block; }
/* ---- 用户第 3 条:首页的统计信息块(问候 + 天气 + 几个数字) ---- */
/* ⚠️ v24 第 24 批:这里原有 6 条 `.home-brief*` 旧规则(纵向排列的问候/天气/数字),
本批把首页信息面板改成"左边内容 + 右边天气贴右端"的横排结构后,
那 6 条**全部成了死样式**(模板里已经没有任何元素用 home-brief-hello /
home-brief-weather / home-brief-stats / w-icon / w-temp / w-desc)。
⇒ 就地删掉,不留"两处定义同一个东西":
上批(第 23 批)就是因为"末尾追加、旧规则留着",漏了 flex-direction,
四个按钮竖排把标题行撑到 175px —— 那个坑的成因正是**旧规则没删**。
新结构见文件末尾「v24 第 24 批 …首页信息面板」那一段(.hb-* / .hb-weather)。 */
/* ---- 用户第 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;
}
/* ⚠️ 整行是**一条 flex**:[拖柄] [头像+信息(占满剩下的)] [右侧动作]。
原来右侧的「编辑 / ⋯」各自写死 right:46px / right:8px 绝对定位,
于是再往里加第三个按钮只能继续猜偏移、且「分组」按钮宽度随分组名变化必然重叠;
改 flex 之后按钮自然排开,姓名那边也不用靠 padding-right 让位了。 */
.person {
display: flex; align-items: stretch; 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;
/* v24 第 9 批 #5:千人名册不「卡死」的关键 —— 屏外的卡片浏览器直接跳过
布局/绘制(4 万+ DOM 节点的排版是一次性成本的大头)。
contain-intrinsic-size 给屏外卡片一个估算高度撑滚动条,
auto 前缀让浏览器记住真实渲染高度,滚过后就精确了。
⚠️ 拖动排序用的是实时 getBoundingClientRect:目标被自动滚进视口时
content-visibility 会立刻出真实布局,落点计算不受影响(drag probe 已验证)。 */
content-visibility: auto;
contain-intrinsic-size: auto 68px;
}
.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;
flex: 1 1 auto; min-width: 0; /* 剩下的宽度都给它,按钮那头不挤它 */
}
.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 { text-decoration: none; } /* 底色交给文末「全站悬停统一」段(同色系加深) */
.btn-ghost { background: #fff; color: var(--ink); border-color: var(--line); }
.btn-ghost:hover { } /* 同上:中性薄雾 + 边框微显,见文末统一段 */
.btn-danger { background: #fff; color: var(--red); border-color: rgba(192,83,63,.35); }
.btn-danger:hover { } /* 同上(危险类走薄雾带一点红) */
/* v24 第 19 批 用户第 7 条:行程行的动作按钮要**看得出颜色**——
「修改是黄色背景,完成绿色,未成行红色」。
⚠️ 这三个是**实心**色块(不是 .btn-danger 那种淡红描边):用户说的是背景色,
要的是"一眼分得清哪个是哪个"。所以单独定义,不去改 .btn-danger 的既有语义
(全站十来处「删除」都在用它,改成实心会到处变样)。 */
.btn-warn { background: #E8A33D; color: #fff; border-color: transparent; }
.btn-ok { background: #5E9E4E; color: #fff; border-color: transparent; }
.btn-del { background: #C0533F; color: #fff; border-color: transparent; }
/* ⚠️ 这三个的 hover 统一交给文件靠后的「第 20 批 全站悬停统一」那一段
(有底色的一律 `filter: brightness(.92)` 同色系加深,不再各写一个色值)。 */
.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; }
/* ---- v24 第 11 批 #3:详情页改「名片卡 + 全宽堆叠」----
⚠️ 旧的 .detail 是 260px + 1fr 两栏,头像卡比右栏矮得多 → 头像下面留一大片空白。
现在头像与基本信息并排成一张名片,其余内容全宽往下排。 */
.pcard {
display: flex; align-items: flex-start; gap: 18px;
background: var(--surface); border: 1px solid var(--line);
border-radius: var(--radius); box-shadow: var(--shadow);
padding: 18px 20px; margin-bottom: 18px;
}
.pcard-avatar { flex: 0 0 auto; }
.pcard-avatar img {
width: 108px; height: 108px; object-fit: cover;
border-radius: 12px; display: block; background: var(--line-2);
}
.pcard-main { flex: 1 1 auto; min-width: 0; padding-top: 2px; }
.pcard-name {
display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap;
font-size: 21px; font-weight: 600; margin-bottom: 8px;
}
.pcard-real { font-size: 13px; font-weight: 400; color: var(--muted); }
.pcard-meta { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.pcard-chip {
display: inline-block; padding: 3px 10px; border-radius: 999px;
background: var(--line-2); color: var(--ink); font-size: 12.5px;
}
.detail-stack { display: flex; flex-direction: column; gap: 18px; }
.detail-stack > div { min-width: 0; }
@media (max-width: 640px) {
.pcard { flex-direction: column; align-items: center; text-align: center; }
.pcard-name, .pcard-meta { justify-content: center; }
}
.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; }
/* ---- 工作经历(详情页,用户 2026-10-03 第 4 条)----
一个人可以有多段,逐条列出来;"现在还在那儿"的那段排最前并带徽标。 */
.job-history { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.job-history-item {
display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap;
word-break: normal; /* ⚠️ .kv-row dd 上有 break-all,这里要收回来,
否则"中国海洋大学"会被从中间断开 */
}
.job-history-item .job-org { font-weight: 600; }
.job-history-item .job-title { color: var(--brand-ink); }
.job-history-item .job-span { font-variant-numeric: tabular-nums; }
/* 「我对他的私人备注」那一块:淡一点,别和"公开资料"抢注意力 */
.my-note-card { background: #FCFAF6; }
/* ---------------- 提示 ---------------- */
.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; }
/* v24 第 9 批 #9:Semi DatePicker 升级态 —— .dt-native 是 display:contents(不干扰
flex 布局),升级成功后由 app.js 内联 display:none 藏掉;.dt-semi 顶上同一格。 */
.dt-input .dt-native { display: contents; }
/* ---- v24 第 12 批 #2:消除「先闪原生框、再变 Semi」----
html.js 由 <head> 里的内联脚本立刻加上(见 layout.php 的 no-js/js 二态)。
有 JS 时:原生控件**从一开始就不显示**,等 Semi 就绪(app.js 加 .is-semi)再露出。
没 JS 时(html 仍是 no-js):什么都不藏,原生控件照旧可用 —— 渐进增强的兜底不变。
⚠️ 用 visibility:hidden 而不是 display:none —— 保留元素占位高度,
否则升级完成的那一瞬表单会"跳"一下(高度从 0 变 40px)。 */
html.js .dt-input:not(.is-semi) .dt-native { visibility: hidden; }
/* ⚠️ v24 第 21 批 第 7 条:两端现在各自被 `.field` 包着,所以选择器要跟着下沉一层。
写成 `> .dt-input` 的话这条**永远不会命中** → 未升级时原生控件照旧可见,
与下面那块"占位骨架"叠在一起,会看到两套控件重影。 */
html.js .dt-range:not(.is-semi-range) .dt-input { visibility: hidden; }
html.js .dt-range:not(.is-semi-range) > .dt-range-label { visibility: hidden; }
/* Semi 未就绪前,先给一个同高的"占位骨架",让表单高度在升级前后保持一致 */
html.js .dt-input:not(.is-semi)::after {
content: ''; display: block; flex: 1 1 150px; height: 40px;
border: 1px solid var(--line); border-radius: var(--radius-s);
background: var(--surface); opacity: .55;
}
html.js .dt-range:not(.is-semi-range)::before {
content: ''; display: block; width: 100%; max-width: 460px; height: 40px;
border: 1px solid var(--line); border-radius: var(--radius-s);
background: var(--surface); opacity: .55;
}
html.js .dt-range.is-semi-range::before { display: none; }
.dt-input .dt-semi { display: none; flex: 1 1 220px; min-width: 0; }
.dt-input.is-semi .dt-semi { display: block; }
.dt-input.is-semi::after { display: none; }
/* 工作经历的月份选择器(type="month" 升级):占满原 .job-cell 格子 */
.dt-semi.dt-semi-month { display: none; }
.dt-semi.dt-semi-month.is-live { display: block; width: 100%; }
/* ---- v24 第 10 批 #7:起止范围用一个控件(Semi dateTimeRange)----
一对 .dt-input 被包在 .dt-range 里;升级后两端各自 display:none,
由**一个** .dt-semi-range 顶上来。未升级(JS 没跑/加载失败)时,
里面两个原生控件照旧各占一行 —— 也就是"功能没变,只是不好看"。 */
.dt-range { display: flex; flex-direction: column; gap: 8px; }
/* 范围控件自己的标签(只有一个,见 _event_fields.php 的说明) */
.dt-range-label { font-size: 13px; color: var(--muted); font-weight: 500; }
.dt-range.is-semi-range { display: block; }
.dt-range.is-semi-range > .dt-input { display: none; } /* 两端都让位给范围控件 */
/* 两端被 .field 包着时的同一条规则(见上面第 21 批的说明) */
.dt-range.is-semi-range > .field { display: none; }
.dt-semi-range { display: none; width: 100%; max-width: 460px; }
.dt-range.is-semi-range .dt-semi-range { display: block; }
/* Semi 范围输入框:与站内 .input 同高同圆角;宽度撑开一点(要放两个时间) */
body[class] .dt-semi-range .semi-input-wrapper {
height: 40px; border: 1px solid var(--line); border-radius: var(--radius-s);
background: var(--surface);
}
body[class] .dt-semi-range .semi-input-wrapper:hover { border-color: var(--brand-2); }
body[class] .dt-semi-range .semi-input { font: inherit; font-size: 13.5px; color: var(--ink); }
/* ---- v24 第 12 批 #2:范围控件的「灰底 + 不垂直居中」(实测定位)----
用真浏览器量出来两个毛病:
① 灰底:Semi 的 **外层** `.semi-datepicker-input` 自带 `rgba(46,50,56,.05)`
灰底、高只有 32px;而内层 `.semi-input-wrapper` 是白底、40px ——
两层套着,灰的那层露在外面(用户看到的"灰色背景色")。
② 不居中:外层 32px < 内层 40px,输入框上下都被挤出去
(实测 gapTop=1 / gapBottom=17 → 文字贴顶)。
修法:把**外层容器**改成透明 + 不限高 + 垂直居中,让内层 40px 的白底框
自己撑起来(与站内 .input 完全一致)。 */
body[class] .dt-semi-range .semi-datepicker-input {
background: transparent !important;
height: auto !important;
min-height: 0 !important;
align-items: center;
padding: 0;
border: 0;
}
body[class] .dt-semi-range .semi-datepicker-range-input-wrapper {
align-items: center;
background: transparent;
}
/* 两端的白底框就是"可见的输入框":撑满高度、文字垂直居中 */
body[class] .dt-semi-range .semi-input-wrapper {
display: flex; align-items: center;
}
body[class] .dt-semi-range .semi-input-wrapper .semi-input {
height: 100%; line-height: 40px; padding: 0 8px;
background: transparent;
}
@media (max-width: 640px) {
.dt-semi-range { max-width: none; }
}
.dt-input.dt-missing .semi-input-wrapper { border-color: #C0392B !important; box-shadow: 0 0 0 1px #C0392B inset; }
/* Semi 主题对齐宣纸色。semi.min.css 是懒加载的、必然排在 app.css 之后,
所以这里赢在**优先级**:body[class] 特异性(0,1,1) > semi 的 body(0,0,1)。 */
body[class] {
--semi-color-primary: var(--brand, #A84A28);
--semi-color-primary-hover: var(--brand-2, #C05F38);
--semi-color-primary-active: var(--brand-ink, #8D3C20);
--semi-color-focus-border: var(--brand, #A84A28);
}
/* 输入框本体:跟站内 .input / .dt-date 同一轮廓(描边、圆角、纸面底色、同高) */
body[class] .dt-semi .semi-input-wrapper {
width: 100%; height: 40px;
border: 1px solid var(--line); border-radius: var(--radius-s);
background: var(--surface); color: var(--ink);
/* v24 第 12 批 #2:单点控件也做同样处理 —— 外层半透明灰底去掉、文字垂直居中
(用户说"这种情况同样出现在其他的日期选择功能中")。 */
display: flex; align-items: center;
}
body[class] .dt-semi .semi-input-wrapper:hover { border-color: var(--brand-2); background: var(--surface); }
body[class] .dt-semi .semi-input { font: inherit; color: var(--ink); }
body[class] .dt-semi .semi-input::placeholder { color: var(--muted); }
body[class] .dt-semi .semi-datepicker-input,
body[class] .dt-semi .semi-datepicker {
background: transparent !important;
height: auto !important;
min-height: 0 !important;
border: 0;
padding: 0;
}
body[class] .dt-semi .semi-input-wrapper .semi-input {
height: 100%; line-height: 40px; padding: 0 11px; background: transparent;
}
/* 日历面板:白纸底色统一成卡片的纸色,选中/悬停色走上面的 brand 变量 */
body[class] .semi-popover .semi-datepicker { background: var(--surface, #fff); }
body[class] .semi-datepicker-day-selected { background-color: var(--brand, #A84A28); }
body[class] .semi-datepicker-day-hoverday,
body[class] .semi-datepicker-day-inhover { background-color: color-mix(in srgb, var(--brand, #A84A28) 12%, transparent); }
.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:「外面那个框,宽度和头像图片一致,
下面显示名字就行了」)。
🔴 头像槽位是 **1:1(正方形)**(见 App\ImageSlot::SLOTS['avatar'])
—— 所以"框宽 = 头像宽"其实要求框里的头像区**也是正方形**:
之前写成 96×72 的长方形 + `object-fit: contain`,正方形照片被**缩到 72 宽**
左右各留 ~12px 空白 → 看起来"框比头像宽很多"(用户第 8 条「还是比头像宽很多」)。
现在头像区取正方形,照片 `object-fit: cover` 铺满 —— 框就是头像本身那么大。
⚠️ 这个 90/128 必须与 JS 里 setCardDim({w,h}) **保持一致**:
库按它算卡片间距与连线落点,两边不一致连线就会歪。 */
.kin-card {
--kin-w: 90px;
--kin-h: 128px;
--kin-ph: 88px; /* 头像区(正方形):卡宽 − 左右各 1px 边框 */
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);
}
/* 头像区:**正方形、铺满**(见上面 --kin-ph 的说明)。
用户 2026-10-03 第 8 条:「宽度和头像图片一致,下面显示名字就行了」。 */
.kin-card .kin-ph {
width: var(--kin-ph); height: var(--kin-ph);
border-radius: 8px; overflow: hidden;
background: #F1E7D6; flex: 0 0 auto;
display: block;
}
/* 头像槽位是 1:1,所以这里的容器也是 1:1 —— cover 不会裁掉任何内容
(照片本来就是按 1:1 裁好的)。⚠️ 别改成 contain:那会在框里留出空白,
看起来就是"框比头像宽"。 */
.kin-card .kin-ph img {
width: 100%; height: 100%; object-fit: cover; 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; }
/* ---- 行右侧的动作区(分组 / 编辑 / ⋯)----
⚠️ 2026-10-04 之前这里有一条 `.person-more { display: none; }`,
而 `<details>` 默认 display 是 block,被它一压**整个 ⋯ 按钮都不见了**
—— 用户报的「名册没有地方可以更改分组了」就是这条(它把唯一的入口藏了)。
现在的做法是:动作区本身是 flex,三个按钮自然排开,谁都不用写死偏移。 */
.person-actions {
display: flex; align-items: center; gap: 6px;
padding: 8px 10px 8px 0; flex: 0 0 auto;
}
.person-edit {
padding: 3px 10px; font-size: 13px; white-space: nowrap;
}
.person-more-btn {
padding: 3px 9px; font-size: 15px; line-height: 1.3;
color: var(--muted); white-space: nowrap;
}
.person-more-btn:hover { color: var(--brand); }
/* ---------------- 名册卡片动作的**共享浮层**(v24 第 10 批 #6)----------------
「分组」「移出名册」两份表单全页只渲染一份(原来每卡各一份,1184 人 =
2369 个 form / 27234 个 input,密码管理器逐个扫描把页面拖死)。
这里给共享浮层一个定位容器,面板由 JS 贴合触发按钮摆放。 */
/* ---- v24 第 11 批 #2:名册卡片的动作**居中模态框** ----
(第 10 批是"贴按钮的浮层",用户反馈分组"点第二次失效"且要求"像选观影人一样弹框",
于是改成遮罩 + 居中面板,与 _person_picker 同一形态。) */
.rm-modal { position: fixed; inset: 0; z-index: 60; display: flex;
align-items: center; justify-content: center; }
.rm-modal[hidden] { display: none; }
.rm-mask { position: absolute; inset: 0; background: rgba(47, 36, 30, .34); }
.rm-panel {
position: relative; z-index: 1;
background: var(--surface); border: 1px solid var(--line);
border-radius: 14px; box-shadow: var(--shadow-l);
width: min(360px, 92vw); max-height: min(78vh, 620px);
display: flex; flex-direction: column;
}
.rm-panel[hidden] { display: none; }
.rm-head {
display: flex; align-items: center; gap: 10px;
padding: 12px 14px 10px; border-bottom: 1px solid var(--line-2);
}
.rm-head b { font-size: 14.5px; font-weight: 600; }
.rm-x {
margin-left: auto; border: 0; background: transparent; cursor: pointer;
font-size: 15px; line-height: 1; color: var(--muted); padding: 4px 6px; border-radius: 6px;
}
.rm-x:hover { background: var(--line-2); color: var(--ink); }
.rm-body { padding: 8px 6px; overflow-y: auto; }
.rm-foot {
display: flex; gap: 8px; justify-content: flex-end;
padding: 10px 14px; border-top: 1px solid var(--line-2);
}
.rm-warn {
margin: 0; padding: 10px 12px; font-size: 13px; line-height: 1.7;
background: #FDF2F0; border: 1px solid #F0D6D1; border-radius: 8px; color: #7A2A20;
}
.rm-modal .pg-menu { display: contents; }
.rm-modal .pd-form { display: contents; }
.rm-modal .person-move { display: block; padding: 6px 14px 10px; }
.rm-modal .person-move.is-kin { padding: 6px 14px 10px; }
/* ---- v24 第 11 批 #2:详情页底部「删除」= 大按钮(与「开通账号」同规格,红色底)---- */
.del-big { display: flex; flex-direction: column; gap: 12px; max-width: 420px; }
.del-big-tip { margin: 0; font-size: 13px; line-height: 1.75; color: var(--muted); }
.btn-block-lg {
display: flex; align-items: center; justify-content: center;
width: 100%; padding: 12px 16px; font-size: 14.5px;
}
.del-big-confirm {
display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
padding: 10px 12px; background: #FDF2F0;
border: 1px solid #F0D6D1; border-radius: 8px; font-size: 13px; color: #7A2A20;
}
.del-big-confirm[hidden] { display: none; }
/* ---- v24 第 12 批 #1:删除按钮紧挨「开通账号」放在同一列 ----
用户原话:「删除按钮,就放在开通账号按钮下面,紧挨着,长度大小,
和开通账号一样,就是颜色改成红色。」
所以这里只做两件事:① 与账号区的按钮**左对齐同宽**(都以账号卡片内容宽为准);
② 上方留一条细分隔线,让人一眼看出"这是同一个区块里的第二件事"。
⚠️ 尺寸一致性靠 .btn-block-lg(100% 宽 + 同 padding),不在这里写死宽度,
否则窄屏下会与「开通账号」对不齐。 */
.del-inline {
margin-top: 14px;
padding-top: 14px;
border-top: 1px dashed var(--line);
}
.del-inline-tip {
margin: 0 0 10px;
font-size: 12.5px;
line-height: 1.7;
color: var(--muted);
}
.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; }
/* ---- 「分组」按钮(用户 2026-10-04 第 1 条)----
按钮上**直接写着当前分组**,点开就是一张分组列表。
没分过组时写「分组」两个字并压暗(is-none),跟"已经归了组"区分开。 */
.person-group-btn {
white-space: nowrap; max-width: 130px; overflow: hidden; text-overflow: ellipsis;
list-style: none;
}
.person-group-btn::-webkit-details-marker { display: none; }
.person-group-btn.is-none { color: var(--faint); }
.person-group[open] {
position: absolute; right: 8px; top: 8px; z-index: 31;
}
.pg-menu {
background: var(--surface); border: 1px solid var(--line);
border-radius: 10px; box-shadow: var(--shadow-l);
min-width: 200px; max-height: 60vh; overflow: auto;
padding: 8px 0; margin-top: 2px;
}
.pg-title {
margin: 0 0 6px; padding: 0 12px; font-size: 12px; color: var(--faint);
}
.pg-item {
display: flex; align-items: center; gap: 8px; cursor: pointer;
padding: 7px 12px; font-size: 13.5px; color: var(--ink);
}
.pg-item:hover { background: var(--line-2); }
.pg-item input { flex: 0 0 auto; }
.pg-menu .pg-save { margin: 6px 12px 0; }
/* ---------------- 名册:拖动排序 / 拖到别栏改分组 ----------------
⚠️ 2026-10-04 起这一段是 **Pointer Events**(不再用 HTML5 拖放)——
原因写在 app.js 那段注释里(Chrome 下"只有第一组能拖"就是 HTML5 DnD 的锅)。
没有它,行上的「分组」按钮照样能改分组,功能不缺。 */
.roster.is-droppable .person { cursor: grab; }
/* ⚠️ 拖动中被拖的那张卡必须 pointer-events:none:落点是用 elementFromPoint
现算的,卡自己压在指针底下就只能算到自己,永远插不进别人中间。 */
.roster.is-droppable .person.is-dragged { opacity: .55; cursor: grabbing; pointer-events: none; }
.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);
/* 🔴 v24 第 25 批 用户第 5 条:手柄是**拖动名册的唯一入口**。
用户原话:「点击列表中的人,是直接查看这个人的信息,拖动的时候才是调整数序。」
→ 整行不再参与拖动,**只有按在这个 ⠿ 上才算**(鼠标与触摸同一判据)。
所以它必须一眼看得出来"这里是能抓的":
· `cursor: grab`(抓着时 JS 加 .is-dragged 的卡片会变 grabbing,见下)
· `touch-action: none` —— 不写它,手指在柄上一划会被浏览器当成滚页面,永远拖不起来
· 悬停加一点底色,别让它看着像一个装饰性的字符 */
touch-action: none; cursor: grab;
}
.drag-handle:hover { background: var(--brand-soft, rgba(168,74,40,.10)); color: var(--brand); }
.drag-handle-inline { color: var(--muted); }
/* 列表里每行要留出左侧拖柄的位置(否则⠿会压在头像上) */
.roster.is-droppable .person { padding-left: 18px; }
/* 正被拖的那张卡:手柄变抓握态(与 cursor:grab 配成一对) */
.person.is-dragged .drag-handle { cursor: grabbing; }
/* 空栏平时收起来(七个预置分组可能只用到三个,摊开会显得名册破破烂烂)——
但**拖动时必须露出来**,否则"想拖到一个现在没人的分组里"就没地方放。 */
.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; }
/* 窄屏:一行上「分组 + 编辑 + ⋯」三个按钮,分组名会吃掉姓名的空间 ——
把分组名的上限压一压,先保住姓名能读(分组点开照样看得到全名)。 */
@media (max-width: 640px) {
.person-group-btn { max-width: 76px; }
.person-actions { gap: 4px; padding-right: 6px; }
.person-edit { padding: 3px 7px; }
}
/* 拖完/没拖成的轻提示(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; }
/* 分享改用弹窗选人组件后,里面的 chips + 按钮要能在这个窄条里排开;
组件默认按"整页宽"设计(.pp-chips 自带 flex-wrap),这里给它一个合理上限,
免得点开"分享他的名片给…"时弹窗按钮被挤成半行。 */
.share-wrap .share-to { flex: 1 1 220px; min-width: 0; }
/* ---------------- 「从家里已有的人里挑」 ---------------- */
.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 { }
/* 层 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: var(--hov-veil, rgba(44,35,29,.05)); 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: rgba(192, 83, 63, .09); }
/* 「设置」图标按钮:**唯一无色的**(白底描边、方形),与上面所有彩色按钮区分。
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 {
border-color: var(--brand); color: var(--brand);
/* 底色交给文末统一段(中性薄雾),别在这里另糊一个 #FAF7F1 */
}
.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 { } /* 统一段处理 */
/* ---------------- 图片裁剪浮层 ---------------- */
/* 覆盖层默认不占空间;打开时铺满整屏,把底下的页面锁住(裁剪时不该能点到别处) */
/* ---- 图片裁剪(v24 第 11 批 #5:改用 Cropper.js)----
用户的四条硬要求(原话见 assets/js/app.js 那段注释)在这里逐条落地:
① 弹框**不要有任何滚动条** → 面板 overflow:hidden;画布尺寸固定,由 Cropper 缩放图片去适应。
② 图片无论多大都要缩放到框内 → 交给 Cropper 的 viewMode:1(container 自适应)。
③ **不要缩放条** → 原来那根 .crop-zoom-row 整块删掉了,改滚轮缩放。
④ 裁剪框要比弹框小、**所有触点都露出来** → 画布四周留出 22px 内边距,
Cropper 的四个角手柄画成实心白点,永远落在面板可见区内。 */
.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: 14px;
box-shadow: var(--shadow-l); padding: 16px;
max-width: calc(100vw - 24px); max-height: calc(100vh - 24px);
/* ① 不许出滚动条:面板自己 overflow:hidden,画布尺寸按视口算死在下面 */
overflow: hidden;
display: flex; flex-direction: column;
}
.crop-head {
display: flex; align-items: baseline; gap: 8px;
font-size: 15px; font-weight: 600; margin-bottom: 12px; flex: 0 0 auto;
}
.crop-head .crop-shape { font-size: 12px; font-weight: 400; color: var(--muted); }
.crop-x {
margin-left: auto; align-self: center; border: 0; background: transparent;
font-size: 15px; line-height: 1; color: var(--muted);
cursor: pointer; padding: 4px 6px; border-radius: 6px;
}
.crop-x:hover { background: var(--line-2); color: var(--ink); }
/* 画布:**固定尺寸**(不是 max-height),这样 Cropper 一定会把图片缩进来,
而不会让容器去滚动。宽高都换算成"视口内还能放下的最大方块"。
⚠️ 留 22px 内边距是给裁剪框四个角的手柄留位(要求④)。 */
.crop-canvas {
flex: 0 0 auto; position: relative;
width: min(560px, calc(100vw - 88px));
height: min(66vh, 460px);
border-radius: var(--radius-s); overflow: hidden;
background: #2C231D;
}
.crop-canvas img { display: block; max-width: 100%; }
.crop-tip { color: var(--muted); font-size: 12px; margin: 12px 0 0; flex: 0 0 auto; }
.crop-actions { display: flex; gap: 10px; justify-content: flex-end; margin-top: 14px; flex: 0 0 auto; }
/* ④ 触点全露出:Cropper 默认的手柄太细、颜色偏灰,在深色画布上不好抓。
这里加粗、改成白底品牌边,并**保证它们不被画布裁掉**(Cropper 自己会留 margin)。 */
.cropper-view-box,
.cropper-face { border-radius: 2px; }
.cropper-line { background-color: rgba(255, 255, 255, .9); }
.cropper-point {
width: 12px; height: 12px; opacity: 1;
background-color: #fff; border: 2px solid var(--brand);
border-radius: 50%;
}
.cropper-point.point-se { width: 14px; height: 14px; }
.cropper-bg { background-image: none; background-color: #2C231D; }
.cropper-modal { background-color: rgba(20, 14, 10, .55); }
@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 { } /* 统一段处理(同色系加深,不换成 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 { } /* 深底上不能用薄雾:走文末统一段的 brightness 提亮 */
.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; }
/* ---- v24 第 11 批 #7:账目行的两个弹框(全页各一份)----
原来每行内联一套(form + 选人组件),账目一多就是几千个 form
—— 用户报的"账目页也卡、还是 Bitwarden 冲突"就是这个。
现在行上只有按钮,表单集中到 #ledPanels。 */
.led-modal {
position: fixed; inset: 0; z-index: 75;
display: flex; align-items: center; justify-content: center;
}
.led-modal[hidden] { display: none; }
.led-mask { position: absolute; inset: 0; background: rgba(47, 36, 30, .34); }
.led-panel-lg {
position: relative; z-index: 1;
background: var(--surface); border: 1px solid var(--line);
border-radius: 14px; box-shadow: var(--shadow-l);
width: min(460px, 92vw); max-height: min(86vh, 660px);
display: flex; flex-direction: column;
}
.led-panel-lg[hidden] { display: none; }
.led-ph {
display: flex; align-items: center; gap: 10px;
padding: 12px 14px 10px; border-bottom: 1px solid var(--line-2);
}
.led-ph b { font-size: 14.5px; }
.led-x {
margin-left: auto; border: 0; background: transparent; cursor: pointer;
font-size: 15px; color: var(--muted); padding: 4px 6px; border-radius: 6px;
}
.led-x:hover { background: var(--line-2); color: var(--ink); }
.led-pb { padding: 12px 14px; overflow-y: auto; }
.led-pb .field { margin-bottom: 12px; }
.led-pb .field > label { display: block; font-size: 13px; color: var(--muted); margin-bottom: 5px; }
.led-pf {
display: flex; gap: 8px; justify-content: flex-end;
padding: 10px 14px; border-top: 1px solid var(--line-2);
}
/* 弹框里的选人组件别撑破面板 */
.led-pb .pp { max-width: none; }
.led-panel-lg .led-hist { margin-top: 4px; }
/* ---- v24 第 11 批 #7:图例(只显示"哪个颜色代表哪个账目")----
用户原话:「图下面的条目不用显示详情,只需要显示每个账目的颜色,
就是说哪个颜色代表哪个账目,就可以了。」
所以从"每账目一张小卡"改成一行色块图例。 */
.ck-legend {
display: flex; flex-wrap: wrap; gap: 6px 16px;
margin: 10px 2px 0; font-size: 13px; color: var(--ink);
}
.ck-lg-item { display: inline-flex; align-items: center; gap: 6px; }
.ck-lg-item[hidden] { display: none; }
.ck-lg-dot {
width: 11px; height: 11px; border-radius: 3px; flex: 0 0 auto;
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .12);
}
/* 多线图:比单条小图高一点,才看得出差异 */
.ck-multi { margin-top: 4px; }
/* ---- v24 第 10 批 #3:总金额图在最前(跨整行),下面才是逐条账目 ---- */
.ck-total { margin-bottom: 14px; }
.ck-total .ck-head b { font-size: 15px; }
/* 类别筛选条:图表一多就是一坨,把筛选权交给用户(而不是少画几条骗自己) */
.ck-filter { display: flex; flex-wrap: wrap; gap: 6px; margin: 0 0 12px; }
.ck-chip {
padding: 4px 11px; border: 1px solid var(--line); border-radius: 999px;
background: var(--surface); color: var(--ink); font: inherit; font-size: 13px;
cursor: pointer;
}
.ck-chip:hover { border-color: var(--brand-2); }
.ck-chip.is-on { background: var(--brand); border-color: var(--brand); color: #fff; }
.ck-chip.is-debt:not(.is-on) { color: #1F7A4D; }
/* ---- v24 第 10 批 #3:每行的「盘点 / 修改」两个按钮 + 就地展开的面板 ---- */
.led-acts { display: flex; gap: 6px; align-items: center; white-space: nowrap; }
.led-panel {
/* 作为 <tr> 的兄弟插进来(见模板里的 JS):表格列宽会把表单切碎,格子外才排得开 */
display: block; background: #FDFBF7;
border: 1px solid var(--line); border-radius: var(--radius-s);
padding: 12px 14px; margin: 0 0 8px;
}
.led-panel .field { margin-bottom: 10px; }
.led-panel .field > label { display: block; font-size: 13px; color: var(--muted); margin-bottom: 4px; }
.led-panel .input, .led-panel .select { width: 100%; max-width: 360px; }
.led-panel .btn-row { margin-top: 4px; }
.led-panel[hidden] { display: none; }
/* 「修改」面板里嵌了选人组件,给它一个不夸张的最大宽度 */
.led-panel .pp { max-width: 420px; }
/* 盘点面板与「修改」不再同处一个 details,分隔线也就不需要了 */
.led-recon { padding-bottom: 0; border-bottom: 0; margin-bottom: 0; }
.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; }
/* ---- 纪念日的「关联人」列(v24 第 25 批 用户第 2 条)----
用户原话:「再加一列,关联人。列表中,人的前面,都加头像。」
⚠️ 列里画的是全站统一的 person_chip(`.viewer-tag`,自带头像 + 底色),
所以这里只需要处理**排布**,不要另造一套人名样式(会与其它模块不一致)。
⚠️ 关联多个人时要能换行 —— `.viewer-tag` 是 `inline-flex`,
不写 flex-wrap 的话 5 个人会把表格撑宽(手机上直接横向溢出)。
⚠️ 「改/删」两个按钮要排成一行:`.res-inline` 那个 form 是 inline 的,
但两个按钮之间得靠 gap 分开,否则会贴在一起、看着像一个双色按钮。 */
.ann-people { display: flex; flex-wrap: wrap; gap: 4px; align-items: center; max-width: 260px; }
.ann-acts { display: flex; gap: 6px; align-items: center; }
.ann-acts .btn { flex: 0 0 auto; }
@media (max-width: 767px) {
/* 窄屏:关联人那一列不再限宽(表格本来就会横向滚动),
但两个按钮要保持不换行 —— 换行后「删除」会掉到「修改」下面,
在表格行里看着像两张卡片。 */
.ann-people { max-width: none; }
}
.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%; padding: 6px;
font-size: 12px; font-weight: 700; letter-spacing: .5px; color: #fff;
text-align: center; box-sizing: border-box;
}
.att-doc::before {
content: ""; width: 34px; height: 34px; border-radius: 6px; background: rgba(255, 255, 255, .22);
flex: 0 0 auto;
}
/* v24 第 19 批 用户第 6 条「如果是文件,则在图标上显示文件名」:
文件名压着色块显示 —— 文件就是**读名字**的,名字才是最该看清的东西。
⚠️ 两行封顶 + 省略号:文件名可以很长(「学位学历证书_博士学位证书_2022-07-08-17-05-53.jpeg」),
不截就会把图标撑变形、把相邻的格子挤歪。完整名字仍能从悬停 title / 下载看到。 */
.att-doc-name {
display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
overflow: hidden; word-break: break-all; line-height: 1.35;
max-width: 100%; font-size: 11px; font-weight: 600; letter-spacing: 0;
}
.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);
}
/* v24 第 19 批 用户第 6 条:缩略图下面**不再放文件名**(那串是上传时的原始名,
图片/视频是"看"的,一串哈希既认不出内容也不比缩略图本身多信息)。
`.att-fname` 的规则保留 —— 万一将来又要在某处显示一行名字,样式不用重写。 */
.att-fname {
flex: 1 1 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
color: var(--ink);
}
.att-fsize { flex: 0 0 auto; }
/* ---- 拖动上传(v24 第 19 批 用户第 1 条)----
用户原话:「为所有的图片视频附件上传处,提供拖动上传的功能,就是允许用户,
通过把文件拖动到那里进行上传。」
⚠️ 类名 `.dropzone` 由 app.js **自动给页面上每个 file 控件挂上**,
并且给「`data-pick-input` 指向该控件的预览图」也挂一份 —— 题图/头像那类
控件的 input 是 sr-only(看不见),往旁边那张图上拖才是最自然的动作。
全站十几处上传点一次性都获得这个能力(不改任何模板)。
⚠️ 落在哪儿由 `.is-over` 高亮体现;真正的落文件逻辑在 app.js 里
(DataTransfer → input.files → 手动派发 change)。
⚠️ 曾经想过再加一句常驻的「也可以把文件拖到这里」小字,**去掉了**:
十几个上传点各多一行灰字,页面会明显变吵,而"拖动"本身不是必须被教导的能力。 */
.dropzone { position: relative; }
.dropzone.is-over {
outline: 2px dashed var(--brand); outline-offset: 3px;
background: #FDF3EC; border-radius: var(--radius-s);
}
.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;
}
/* ---- 左右翻页箭头(v24 第 19 批 用户第 6 条)----
用户原话:「预览的时候,左右有箭头,可以直接查看下一张。」
⚠️ 左右各一个圆形按钮,垂直居中、压在内容两侧。禁用态(第一张/最后一张)
要**看得出来**(半透明 + 禁止光标),否则用户会以为"点了没反应"。 */
.pv-nav {
position: fixed; top: 50%; transform: translateY(-50%); z-index: 5;
width: 46px; height: 46px; border-radius: 50%; border: 0; cursor: pointer;
background: rgba(255, 255, 255, .2); color: #fff; font-size: 30px; line-height: 1;
display: flex; align-items: center; justify-content: center; padding: 0 0 4px;
}
.pv-nav:hover:not(:disabled) { background: rgba(255, 255, 255, .34); }
.pv-nav:disabled { opacity: .28; cursor: default; }
/* ⚠️ 与 .pv 同一个老坑:这里自己声明了 display:flex,不补 [hidden] 就藏不住 ——
只有一个可预览项时箭头必须消失,靠的就是这条。 */
.pv-nav[hidden] { display: none; }
.pv-prev { left: 14px; }
.pv-next { right: 14px; }
@media (max-width: 560px) {
.pv-nav { width: 40px; height: 40px; font-size: 26px; }
.pv-prev { left: 6px; }
.pv-next { right: 6px; }
}
@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; }
/* v24 第 19 批 用户第 8 条:「前面的复选框就没用了吧,去掉吧。」
⚠️ 多选时复选框**确实没用**了 —— 第 18 批起"选谁"靠点头像/名字(点击整行),
勾选与否只由暂存区的 chip 表达,一个小小的方框反而是干扰。
JS 给多选组件加 `.pp-compact` 才隐藏;**单选不隐藏**(单选的圆点是他唯一
能看见"现在选的是谁"的地方)。
⚠️ 只是 `display:none`,input **仍在 DOM 里** —— 表单提交全靠它。
千万别改成 remove()(那就变成"选了保存后没人")。 */
.pp-compact .pp-item input { display: none; }
.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 { 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; }
/* 老数据(只有 events.note、还没搬成 summary)时的一句提示 ——
见 templates/trips/show.php 里第 19 批 第 4 条的说明。 */
.trip-sum-legacy {
margin: 0; font-size: 13px; color: var(--muted);
background: #FDF3EC; border: 1px dashed rgba(168, 74, 40, .3);
border-radius: var(--radius-s); padding: 8px 10px;
}
.trip-sum-legacy a { font-weight: 600; }
.trip-people { display: flex; flex-wrap: wrap; gap: 4px 8px; }
.trip-hero { display: flex; gap: 20px; flex-wrap: wrap; }
/* ⚠️ v24 第 16 批 用户第 1 条:「题图应该按照 16:9 显示,因为添加的时候,
是按照这个比例裁剪的,现在的显示比例不对」—— 原来是 4:3 把 16:9 的图裁掉了两边。
这个数字必须与 ImageSlot 的 'cover' 槽位比例**一致**(部署断言专门比对两处)。 */
.trip-hero-cover {
width: 420px; max-width: 100%; aspect-ratio: 16 / 9; 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; }
/* v24 第 19 批 用户第 7 条:动作按钮**横着排列**、放在行程**右上角**。
「计划行程的三个按钮,修改、完成、未成行,横着排列,放在行程右上角」
「实际行程的编辑和删除按钮,也放在行程的右上角」
⚠️ 原来是 `flex-direction: column`(竖着一列、贴右下),用户要的是横排靠上。
改成 row + align-self:flex-start(贴着行的顶部对齐),窄屏再允许换行。 */
.node-ops {
flex: 0 0 auto; display: flex; flex-direction: row; flex-wrap: wrap;
gap: 6px; align-items: flex-start; justify-content: flex-end;
align-self: flex-start;
}
.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-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); }
/* 🔴 v24 第 23 批:这条规则**必须改而不是在后面再覆盖一条**。
旧值是 `flex-direction: column` —— 那是给「展开区里四个内联表单竖着排」用的布局,
而第 23 批把四个动作搬进了弹框,`.res-ops` 变成了**标题行右端那一排按钮**。
本轮实测:只在文件末尾追加 `display/gap/margin-left`,**没写 `flex-direction`**,
于是四个按钮仍然**竖着排**,把标题行从 40px 撑到 **175px 高**
(真机几何实测 ops.top=382.9 / title.top=445.6 —— 差 63px,一眼就看出不在同一排)。
⚠️ 这也是"两处定义同一个东西"的经典代价:覆盖式修法天生容易漏子属性,
而下一次改的人只会看到其中一处。**改类名语义时,旧规则要删/改,不要叠。** */
.res-ops { margin-left: auto; display: inline-flex; align-items: center; gap: 6px; flex-wrap: wrap; }
/* ---- 资源列表:搜索在上(用户 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); }
/* 🔴 v24 第 25 批 用户第 6 条:卡片加了「完成」复选框之后,它必须与标题**同一行**。
作者样式是 `flex-direction: column` —— 不处理的话复选框会独占一行、
标题被挤到第二行,一张卡凭空高一截(用户要的是"任务前面带一个复选框",
不是"任务上面带一个")。做法:把标题包成一行(.kb-top),
复选框 + 标题一起横排,剩下的 meta/foot 仍然竖着叠。 */
.kb-top { display: flex; align-items: flex-start; gap: 8px; }
.kb-top .kb-title { flex: 1 1 auto; min-width: 0; }
.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); }
/* ---- 状态徽标(v24 第 14 批 用户第 2 条:每张卡都有,点一下推进到下一档 ----
三档各有颜色:待办灰 / 进行中品牌色 / 已完成绿 —— 不靠位置也能一眼看出做到哪一步。
⚠️ .is-static(只读账号)长得一样但不可点:不给可点的错觉。 */
.kb-st {
display: inline-flex; align-items: center; gap: 4px;
padding: 1px 8px; border-radius: 999px; border: 1px solid transparent;
font-size: 11px; line-height: 1.7; white-space: nowrap; cursor: pointer;
}
.kb-st.is-todo { background: var(--line-2); color: var(--muted); border-color: var(--line); }
.kb-st.is-doing { background: rgba(168, 74, 40, .12); color: var(--brand-ink); border-color: rgba(168, 74, 40, .3); }
.kb-st.is-done { background: rgba(111, 158, 92, .14); color: #4C7038; border-color: rgba(111, 158, 92, .35); }
.kb-st.is-static { cursor: default; }
.kb-st.is-doing:hover { border-color: var(--brand); }
/* .is-on = "当前就是这一档"(读书详情页的四个状态按钮用它标选中):
加一圈内描边而不是换底色 —— 底色已经表达"这一档是什么颜色",
再换一次就会分不清"选中"和"这一档"。 */
.kb-st.is-on { box-shadow: inset 0 0 0 1px currentColor; font-weight: 600; }
.kb-due.is-late, .kb-late { color: var(--red); }
.kb-ico { white-space: nowrap; }
/* ---- 看板色点(v24 第 26 批 用户第 3 条)----
同一个色点两种用法:
· `.kb-dot` —— 看板页**列头**上(列本身就是一个看板);
· `.tk-board-dot` —— 首页任务行**看板徽标**里(名字前一个小圆点)。
都用 `--bc`(board color)传色:色值由服务端按「用户设过的色,否则按名字派生」算好,
CSS 不参与任何色值决策(否则"同一个看板在首页和看板页颜色不一样"迟早发生)。 */
.kb-dot,
.tk-board-dot {
display: inline-block; width: 9px; height: 9px; border-radius: 50%;
background: var(--bc, var(--line)); flex: 0 0 auto;
}
.kb-dot { width: 10px; height: 10px; }
/* 首页那个看板徽标:名字前带色点,整体仍是原来那颗浅色 pill */
.tk-board { display: inline-flex; align-items: center; gap: 5px; }
/* 看板设置页的颜色选择器:native color input 默认很宽,收窄到与输入框同高的一小块 */
.input-color {
width: 34px; height: 30px; padding: 2px; border: 1px solid var(--line);
border-radius: var(--radius-s); background: #fff; cursor: pointer; flex: 0 0 auto;
}
.input-color::-webkit-color-swatch-wrapper { padding: 0; }
.input-color::-webkit-color-swatch { border: 0; border-radius: 4px; }
.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`(旧的 ☐/☑ 字符按钮)已在 v24 第 25 批 用户第 6 条**删掉**:
子任务改用全站统一的 `.tk-check`(见上面那一段)。留一份没人用的样式
只会让下一个人以为还有第二条画法。 */
.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; }
/* ---- 「危险操作区」(成员详情页的删除,用户 2026-10-03 第 2 条)----
刻意做成**页面最下方一块低调的折叠区**:破坏性操作不该一眼看到、更不该一键完成。
视觉上只用一条虚线分隔 + 灰色摘要,不抢上面正常内容的注意力。 */
.danger-zone {
margin-top: 28px; padding-top: 14px;
border-top: 1px dashed var(--line);
}
.danger-zone > details > summary { cursor: pointer; font-size: 13px; }
.danger-zone .del-confirm {
margin-top: 10px; padding: 12px 14px;
border: 1px solid rgba(178, 45, 45, .25); border-radius: var(--radius-s);
background: #FDF6F4;
}
/* ---- 表单标签更醒目(用户 2026-10-03 第 6 条)----
「每一个输入的名字,都不够醒目,例如姓名、性别,这些字应该变大一点,加粗,
不能跟提示一样大小」—— 提醒(.tip)是 12px 的灰字,而标签原来是 13-14px,
两者太接近,扫一眼分不出"哪一行在哪"。这里把标签统一抬到 15px / 600 字重 /
深一点的墨色,与 .tip / .hint 明确拉开层级。 */
.form-grid > .field > label,
.form-grid > .field > .field-label {
font-weight: 600; font-size: 15px; color: var(--ink);
}
/* 「我的资料 / 编辑成员」那张表里也有不在 form-grid 直接子级的 label(嵌套块),
一并覆盖;用 :not(.job-lb) 排除工作经历行里的那些小标签(见下)。 */
.card .field > label:not(.job-lb):not(.check-item):not(.chk-inline) {
font-weight: 600; font-size: 15px; color: var(--ink);
}
/* ⚠️ 但 .tip / .hint / .small 这些"说明文字"必须保持小灰字,别被上面那条带大 */
.card .field > label.tip,
.card .field > label.hint,
.card .field > label.small { font-weight: 400; font-size: 12.5px; color: var(--muted); }
/* ---- 头像选择器(用户第 6 条:头像放最前 + 去掉「选择文件」按钮)----
点那张(默认)头像就是选文件 —— 图本身就是按钮,不再另起一行控件。
默认头像用 .avatar-default 叠一个"+"角标,提示这里可点。 */
.avatar-field { margin-bottom: 4px; }
.avatar-picker { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
.avatar-picker #avatar-preview {
width: 96px; height: 96px; border-radius: 12px; object-fit: cover;
border: 1px solid var(--line); background: #F1E7D6; cursor: pointer;
transition: border-color .15s ease, box-shadow .15s ease;
}
.avatar-picker #avatar-preview:hover { border-color: var(--brand); box-shadow: 0 0 0 3px rgba(168,74,40,.12); }
.avatar-picker #avatar-preview[data-has-avatar="0"] { border-style: dashed; }
.avatar-hint { line-height: 1.7; }
/* ---- 旅程题图(v24 第 14 批 用户第 3 条):与头像同一套裁剪,比例 16:9 ----
预览块本身就是"选文件"的点击区(与头像一样,省掉一整行重复的文件控件)。
⚠️ 比例写死成 16/9 是**故意**的:CSS 不读 ImageSlot,而槽位里的比例就是 16:9,
两边各写一份会漂移 —— 部署脚本有一条断言比对 app.js 与 CSS 里的比例数字。 */
.cover-picker { display: flex; align-items: flex-start; gap: 14px; flex-wrap: wrap; }
.cover-picker #cover-preview {
width: 240px; max-width: 100%; aspect-ratio: 16 / 9;
object-fit: cover; border-radius: 12px;
border: 1px solid var(--line); background: #F1E7D6; cursor: pointer;
transition: border-color .15s ease, box-shadow .15s ease;
}
.cover-picker #cover-preview:hover { border-color: var(--brand); box-shadow: 0 0 0 3px rgba(168,74,40,.12); }
.cover-picker #cover-preview[data-has-cover="0"] { border-style: dashed; }
.trip-people-block { display: flex; flex-direction: column; gap: 6px; margin: 8px 0; }
/* ---- 工作经历(用户第 4 条:可以有多个)---- */
.job-list { display: flex; flex-direction: column; gap: 8px; }
.job-row {
display: grid; gap: 8px; align-items: end;
/* 单位 / 职务 / 职称 各占一份,起止各一份窄的,最后是删除按钮 */
grid-template-columns: 1.4fr 1fr 1fr 130px 130px auto;
padding: 10px 12px; border: 1px solid var(--line-2);
border-radius: var(--radius-s); background: #FDFBF7;
}
.job-cell { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.job-lb { font-size: 12px; color: var(--muted); font-weight: 500; }
.job-cell .input { padding: 7px 9px; font-size: 14px; }
.job-del { align-self: end; }
@media (max-width: 860px) {
/* 窄屏:一段经历折成两列,别硬塞六列(会挤成没法填) */
.job-row { grid-template-columns: 1fr 1fr; }
.job-row .job-cell:first-child { grid-column: 1 / -1; }
.job-del { grid-column: 1 / -1; justify-self: 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); }
/* ---- v24 第 11 批 #4:影院下拉(带色点)+ 新建影院弹框 ----
原生 <option> 画不了色块,所以自己做一个"按钮 + 弹出列表":
值仍写进隐藏的 input[name=cinema_id],服务端与既有测试都不用改。 */
.cinema-select-row { display: flex; align-items: flex-start; gap: 8px; }
.cs { position: relative; flex: 1 1 auto; min-width: 0; }
.cs-btn {
display: flex; align-items: center; gap: 8px; 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);
cursor: pointer; text-align: left;
}
.cs-btn:hover { border-color: var(--brand-2); }
.cs-btn:focus { outline: none; border-color: var(--brand); }
.cs-text { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cs-caret { flex: 0 0 auto; color: var(--muted); font-size: 11px; }
.cs-dot {
flex: 0 0 auto; width: 12px; height: 12px; border-radius: 3px;
background: var(--line-2); box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .12);
}
.cs-dot.is-none { background: transparent; box-shadow: inset 0 0 0 1px var(--line); }
.cs-menu {
position: absolute; left: 0; right: 0; top: calc(100% + 4px); z-index: 40;
background: var(--surface); border: 1px solid var(--line);
border-radius: 10px; box-shadow: var(--shadow-l);
padding: 4px; max-height: 280px; overflow-y: auto;
}
.cs-menu[hidden] { display: none; }
.cs-item {
display: flex; align-items: center; gap: 8px; width: 100%;
padding: 8px 10px; border: 0; border-radius: 7px; background: transparent;
font: inherit; font-size: 13.5px; color: var(--ink); cursor: pointer; text-align: left;
}
.cs-item:hover, .cs-item:focus { background: var(--line-2); outline: none; }
/* 新建影院:居中弹框(与名册的动作弹框同一形态,只是更宽) */
.cn-modal {
position: fixed; inset: 0; z-index: 70;
display: flex; align-items: center; justify-content: center;
}
.cn-modal[hidden] { display: none; }
.cn-mask { position: absolute; inset: 0; background: rgba(47, 36, 30, .34); }
.cn-panel {
position: relative; z-index: 1;
background: var(--surface); border: 1px solid var(--line);
border-radius: 14px; box-shadow: var(--shadow-l);
width: min(420px, 92vw); max-height: min(84vh, 620px);
display: flex; flex-direction: column;
}
.cn-head {
display: flex; align-items: center; gap: 10px;
padding: 12px 14px 10px; border-bottom: 1px solid var(--line-2);
}
.cn-head b { font-size: 14.5px; }
.cn-x {
margin-left: auto; border: 0; background: transparent; cursor: pointer;
font-size: 15px; color: var(--muted); padding: 4px 6px; border-radius: 6px;
}
.cn-x:hover { background: var(--line-2); color: var(--ink); }
.cn-body { padding: 12px 14px; overflow-y: auto; }
.cn-body .field { margin-bottom: 12px; }
.cn-body .field > label { display: block; font-size: 13px; color: var(--muted); margin-bottom: 5px; }
.cn-foot {
display: flex; gap: 8px; justify-content: flex-end;
padding: 10px 14px; border-top: 1px solid var(--line-2);
}
.cn-presets { display: inline-flex; align-items: center; gap: 5px; flex-wrap: wrap; }
.cn-preset {
width: 20px; height: 20px; border-radius: 5px; border: 1px solid rgba(0, 0, 0, .12);
cursor: pointer; padding: 0;
}
.cn-preset:hover { transform: scale(1.08); }
/* ---- v24 第 11 批 #1:日历本改「实心色块」----
原来 .dash-tag 是 --faint 的 11px 灰字,压在宣纸底上几乎看不见(用户原话"看不清")。
现在:底色 = 该日历本的颜色(由模板内联),文字白色 —— 与左侧那个小圆点同一个颜色,
扫一眼就知道"这条是哪本日历的"。
⚠️ 颜色是用户自选的,浅色(如 #97A630)上白字会发虚 → 加一点文字阴影保底,
比"强制加深颜色"好:本子的颜色是用户定的,不该被我们改。 */
.dash-cal {
flex: 0 0 auto; align-self: center;
padding: 2px 9px; border-radius: 999px;
color: #fff; font-size: 11.5px; font-weight: 500; line-height: 1.7;
white-space: nowrap; max-width: 40%;
overflow: hidden; text-overflow: ellipsis;
text-shadow: 0 1px 1px rgba(0, 0, 0, .22);
}
@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 { text-decoration: none; } /* 原来那层 #FDFBF7 在米色底上像脏了一块 */
.seg-btn.is-on {
background: var(--brand); border-color: var(--brand); color: #fff; font-weight: 600;
}
/* ---- 日程页头的「跳到某一天」(v24 第 11 批 #6 重做)----
用户要求:「跳转日期那个地方,不要那么长,只需要和设置一样,一个按钮即可」,
且日期选择要用 Semi(与全站统一)。
所以现在是**一个方图标按钮**(与 ☰ 的 .btn-icon 同规格):
点开弹一个只含 Semi 日期选择器的轻浮层,选中即跳。 */
.cal-jump { display: inline-flex; margin: 0; }
.cal-jump-btn { display: inline-flex; align-items: center; justify-content: center; cursor: pointer; }
/* ---- v24 第 12 批 #3:跳转日期改成**按钮下方的下拉日历** ----
用户原话:「选择这个日期,不用弹窗,直接把日历选择,弹出在这个按钮下面就可以了。」
所以把原来"全屏遮罩 + 居中弹窗"(.cal-jump-host/.cal-jump-pop)换成
贴着按钮下方展开的下拉面板(.cal-jump-drop)。 */
.cal-jump { position: relative; }
.cal-jump-drop {
position: absolute; z-index: 70;
top: calc(100% + 8px); left: auto; right: 0; /* 贴右:与按钮同一列,不会跑出屏外 */
background: var(--surface);
border: 1px solid var(--line);
border-radius: 12px;
box-shadow: var(--shadow-l);
padding: 8px;
/* 下拉里放的是 Semi 的**内联**日历面板,去掉它自己的浮层阴影/描边避免双层框 */
}
.cal-jump-drop[hidden] { display: none; }
/* ---- v24 第 14 批 #1:下拉里是**自绘**的日历(不再挂 Semi 的浮层面板)----
「上一月/上一年」只重画面板、绝不提交(这正是不引第三方控件的原因:
它把"翻月"与"选中"混在同一个 onChange 里,于是翻月也会跳走)。 */
.cjd-head {
display: flex; align-items: center; gap: 4px; margin-bottom: 6px;
}
.cjd-title { flex: 1; text-align: center; font-weight: 600; font-size: 14px; }
.cjd-nav {
width: 28px; height: 28px; flex: 0 0 auto;
display: inline-flex; align-items: center; justify-content: center;
border: 1px solid var(--line); border-radius: 8px;
background: var(--surface); color: var(--ink); font-size: 14px; line-height: 1;
cursor: pointer;
}
.cjd-nav:hover { background: var(--line); }
.cjd-grid {
display: grid; grid-template-columns: repeat(7, 32px); gap: 2px;
}
.cjd-wd {
text-align: center; font-size: 11px; color: var(--muted); padding: 2px 0 4px;
}
.cjd-day {
width: 32px; height: 30px; padding: 0;
border: 0; border-radius: 8px; background: transparent; color: var(--ink);
font-size: 13px; cursor: pointer;
}
.cjd-day:hover { background: var(--line); }
.cjd-day.is-out { color: var(--muted); opacity: .55; }
.cjd-day.is-today { font-weight: 700; box-shadow: inset 0 0 0 1px var(--line); }
.cjd-day.is-on { background: var(--brand); color: #fff; }
.cjd-foot {
display: flex; align-items: center; gap: 8px;
margin-top: 8px; padding-top: 8px; border-top: 1px solid var(--line);
}
.cjd-foot .cjd-sel { flex: 1; }
/* ==================== 周 / 天:按小时铺的时间网格 ==================== */
/* 结构:左边一条"小时刻度"沟槽(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; }
}
/* ---- 地址五级(国家 / 省 / 市 / 区 / 详细地址,用户 2026-10-04 第 4 条)----
一行排不下五个控件,用**自动换行的网格**:宽屏上「国家+省+市」一行、
「区+详细」一行;窄屏自动每组一个。每个控件头上一行小标签(addr-k),
比把「省 / 市」两个字塞进 placeholder 里好认 —— placeholder 一填字就没了。 */
.addr-grid {
display: grid; gap: 8px 10px;
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
margin-top: 2px;
}
.addr-cell { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.addr-cell-wide { grid-column: span 2; }
.addr-cell .addr-k { font-size: 12px; color: var(--faint); }
.addr-cell .input,
.addr-cell .select { width: 100%; }
@media (max-width: 560px) {
.addr-cell-wide { grid-column: span 1; }
}
/* ==================== 旅程:行程状态机(v24 第 13 批)====================
分三组:计划 / 实际 / 未成行。未成行用灰调(它"不算数"但要看得到)。 */
.trip-stage-card { margin-bottom: 18px; }
.stage-hint { margin: -6px 0 12px; font-size: 12.5px; color: var(--muted); }
.stage-skipped { opacity: .82; }
.stage-skipped .node-row { background: var(--line-2); }
.node-badge.nb-plan { background: #EDE7DC; color: #6B5B45; }
.node-badge.nb-actual { background: #DCEFE2; color: #2C6B45; }
.node-badge.nb-skipped { background: #EFEDEA; color: #6B6560; }
.rev-badge {
display: inline-block; padding: 1px 7px; border-radius: 999px;
background: #F1E6F5; color: #6B3F7A; font-size: 11.5px;
}
.actual-tag {
display: inline-block; margin-left: 6px; padding: 1px 6px; border-radius: 999px;
background: #DCEFE2; color: #2C6B45; font-size: 11px;
}
/* 修正面板(在行程行里就地展开) */
.rev-open > summary { cursor: pointer; list-style: none; }
.rev-open > summary::-webkit-details-marker { display: none; }
.rev-panel {
margin-top: 10px; padding: 12px 14px;
background: var(--surface-2, #FAF7F1);
border: 1px solid var(--line); border-radius: 10px;
}
.rev-tip { margin: 0 0 10px; font-size: 12.5px; line-height: 1.7; color: var(--muted); }
.rev-form .btn-row { margin-top: 10px; }
/* 修正历史对比表 */
.rev-hist { margin-top: 8px; }
.rev-hist > summary { cursor: pointer; font-size: 12.5px; }
.rev-table { width: 100%; border-collapse: collapse; margin-top: 8px; font-size: 12.5px; }
.rev-table th, .rev-table td {
padding: 5px 8px; border-bottom: 1px solid var(--line-2); text-align: left;
}
.rev-table th { color: var(--muted); font-weight: 500; }
.rev-table tr:first-child td { color: var(--ink); } /* 原计划一行稍醒目 */
/* 行程表单里的"当前状态"只读展示 */
.node-stage-ro {
padding: 9px 11px; border: 1px dashed var(--line); border-radius: var(--radius-s);
background: var(--line-2); font-size: 13px;
}
/* ============================================================
v24 第 14 批:读书模块(用户第 5 条)
卡片骨架 / 统计图表一律复用现成的(.card.card-pad / .stats-grid /
.stat-card / .chart-card / .bar-chart / .vgrid),这里只写读书**特有**的部件。
图表仍然是纯 CSS:不引图表库,服务端画出来、没 JS 也看得见。
============================================================ */
/* ---- 书架:状态筛选条 ---- */
.book-filter { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 12px; }
.bf-btn {
display: inline-flex; align-items: center; gap: 5px;
padding: 5px 14px; border-radius: 999px;
border: 1px solid var(--line); background: var(--surface);
font-size: 13px; color: var(--ink); text-decoration: none;
transition: border-color .15s ease, background .15s ease;
}
.bf-btn:hover { border-color: var(--brand); text-decoration: none; }
.bf-btn.is-on { background: var(--brand); border-color: var(--brand); color: #fff; }
.bf-n { font-size: 11px; opacity: .72; font-variant-numeric: tabular-nums; }
/* ---- 书架:卡片网格 ----
封面按 2:3(书的形状),宽度自适应;窄屏自动减列。 */
.book-grid {
display: grid; gap: 14px;
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
}
.book-card {
display: flex; flex-direction: column; gap: 8px;
background: var(--surface); border: 1px solid var(--line);
border-radius: var(--radius); padding: 12px; box-shadow: var(--shadow);
}
/* ⚠️ 封面是 <a>(整块可点):居中靠 flex,图自己铺满 —— 只写一次 display,
写两遍后者会把前者悄悄改掉(这类"重复声明"最容易在改版时留成陷阱)。 */
.book-cover {
display: flex; align-items: center; justify-content: center;
aspect-ratio: 2 / 3; border-radius: var(--radius-s);
overflow: hidden; background: #F1E7D6;
}
.book-cover img { width: 100%; height: 100%; object-fit: cover; display: block; }
.book-cover-empty { font-size: 32px; opacity: .5; }
.book-body { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
.book-title {
font-size: 14px; font-weight: 600; color: var(--ink); line-height: 1.45;
text-decoration: none;
display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.book-title:hover { color: var(--brand); }
.book-author { font-size: 12px; color: var(--muted); }
.book-meta { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 6px; }
.book-rate { font-size: 11px; color: var(--brand-ink); }
.book-note-n { font-size: 11px; color: var(--muted); }
/* 读到第几章的细进度条 */
.bk-bar {
height: 4px; border-radius: 999px; background: var(--line-2); overflow: hidden; margin: 2px 0;
}
.bk-bar i { display: block; height: 100%; background: var(--brand); }
.book-foot { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-top: auto; }
/* ---- 一本书 ---- */
.book-hero { display: flex; gap: 20px; flex-wrap: wrap; }
.book-hero-cover {
width: 150px; max-width: 46%; flex: 0 0 auto;
aspect-ratio: 2 / 3; border-radius: var(--radius-s);
overflow: hidden; background: #F1E7D6;
display: flex; align-items: center; justify-content: center;
}
.book-hero-cover img { width: 100%; height: 100%; object-fit: cover; display: block; }
.book-hero-body { flex: 1 1 300px; min-width: 0; display: flex; flex-direction: column; gap: 10px; }
.book-sub { margin: 0; font-size: 14px; color: var(--muted); }
.book-states { display: flex; flex-wrap: wrap; gap: 6px; }
.book-sum { margin-top: 4px; }
.book-toc-box { margin-top: 4px; }
.book-toc { margin: 0; padding-left: 20px; display: flex; flex-direction: column; gap: 3px; }
.book-toc li { font-size: 13.5px; display: flex; align-items: baseline; gap: 8px; }
.book-toc li.is-cur > a { color: var(--brand); font-weight: 600; }
.bk-file-now { margin: 0 0 6px; font-size: 13px; color: var(--muted); }
/* ---- 笔记 ---- */
.book-note-form { display: flex; flex-direction: column; gap: 8px; margin-bottom: 14px; }
.bn-row { display: flex; gap: 8px; flex-wrap: wrap; }
.bn-row .select { flex: 0 0 auto; width: auto; min-width: 96px; }
.bn-row .input { flex: 1 1 160px; min-width: 0; }
.book-notes { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 10px; }
.book-note {
border: 1px solid var(--line-2); border-radius: var(--radius-s);
padding: 9px 12px; background: #FDFBF7;
}
.book-note.is-quote { border-left: 3px solid var(--brand-2); }
.book-note.is-review { border-left: 3px solid var(--green); }
.bn-head { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-bottom: 5px; }
.bn-kind {
font-size: 11px; padding: 1px 7px; border-radius: 999px;
background: var(--line-2); color: var(--brand-ink);
}
.bn-ch, .bn-src, .bn-who { font-size: 11.5px; color: var(--muted); }
.bn-del { margin-left: auto; }
.bn-body { font-size: 14px; line-height: 1.75; color: var(--ink); white-space: normal; }
/* ---- 阅读器 ----
目录在侧栏、正文在右边;窄屏侧栏折到上面(而不是横向挤成一条)。 */
.book-read { display: flex; gap: 18px; align-items: flex-start; }
.br-side {
flex: 0 0 210px; max-height: 72vh; overflow-y: auto;
background: var(--surface); border: 1px solid var(--line);
border-radius: var(--radius); padding: 12px 14px;
}
.br-side-h { font-size: 13px; font-weight: 600; color: var(--muted); margin-bottom: 8px; }
.br-toc { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 2px; }
.br-toc li { font-size: 13px; }
.br-toc li a {
display: block; padding: 4px 8px; border-radius: 6px; color: var(--ink); text-decoration: none;
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.br-toc li a:hover { background: var(--line-2); }
.br-toc li.is-cur a { background: rgba(168, 74, 40, .12); color: var(--brand-ink); font-weight: 600; }
.br-main {
flex: 1 1 auto; min-width: 0;
background: var(--surface); border: 1px solid var(--line);
border-radius: var(--radius); padding: 22px 26px; box-shadow: var(--shadow);
}
.br-ch-title { font-size: 19px; margin: 0 0 14px; }
.br-body { font-size: 16px; line-height: 1.95; color: var(--ink); }
.br-nav { display: flex; align-items: center; gap: 12px; margin-top: 22px; flex-wrap: wrap; }
.br-pos { margin-left: auto; }
.br-pdf object { width: 100%; height: 78vh; border: 0; display: block; }
@media (max-width: 860px) {
.book-read { flex-direction: column; }
.br-side { flex: 1 1 auto; width: 100%; max-height: 260px; }
.br-main { padding: 16px 18px; }
.br-body { font-size: 15.5px; line-height: 1.9; }
}
/* ---- 封面裁剪(书的封面是 2:3,与旅程题图的 16:9 不同)----
与 .cover-picker 同一套结构,只是预览框换成竖的。 */
.book-cover-picker #book-cover-preview {
width: 150px; aspect-ratio: 2 / 3;
}
/* ---- 导入读书笔记 ---- */
.imp-how { margin: 0 0 10px; padding-left: 20px; display: flex; flex-direction: column; gap: 5px; }
.imp-how li { font-size: 13.5px; line-height: 1.7; }
.imp-list { list-style: none; margin: 0 0 14px; padding: 0; display: flex; flex-direction: column; gap: 10px; }
.imp-book {
border: 1px solid var(--line-2); border-radius: var(--radius-s);
padding: 10px 12px; background: #FDFBF7;
}
.imp-bh { display: flex; flex-wrap: wrap; align-items: baseline; gap: 8px; }
.imp-t { font-size: 14.5px; font-weight: 600; }
.imp-n { margin-left: auto; font-size: 12px; color: var(--muted); font-variant-numeric: tabular-nums; }
.imp-notes { list-style: none; margin: 8px 0 0; padding: 0; display: flex; flex-direction: column; gap: 5px; }
.imp-notes li { display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px; font-size: 13px; }
.imp-notes li.is-quote .imp-txt { color: var(--muted); }
.imp-txt { flex: 1 1 240px; min-width: 0; line-height: 1.6; }
.imp-confirm { margin-top: 6px; }
/* ---- 柱状图:列数不定的(每年的本数 / 评分档位)----
观影那套 .vgrid 是写死 12 列的(12 个月),这里列数随数据变。 */
.vgrid-auto { grid-template-columns: repeat(auto-fit, minmax(26px, 1fr)); }
/* ============================================================
v24 第 15 批:活动(容器)/ 旅程(行程)两级语义
术语改了(用户第 1 条),活动表单砍到三项(用户第 4 条)。
============================================================ */
/* ---- 活动表单里的"说明一句"(时间/城市从哪来)----
用左边一条竖线而不是整块底色:它是**解释**、不是输入项,
染成和输入区一样的底色会让人以为这里要填东西。 */
.form-note {
margin: 0 0 14px; padding: 8px 0 8px 12px;
border-left: 3px solid var(--line);
font-size: 13px; line-height: 1.75; color: var(--muted);
}
.form-note b { color: var(--ink); font-weight: 600; }
/* ---- 「更多设置」折叠区:主流程只留三项,其余收在这里 ---- */
.more-set {
margin: 4px 0 14px; border: 1px solid var(--line);
border-radius: var(--radius-s); background: #FDFBF7;
}
.more-set > summary {
cursor: pointer; padding: 10px 14px;
font-size: 13.5px; color: var(--muted); font-weight: 500;
}
.more-set > summary:hover { color: var(--brand); }
.more-set[open] > summary { border-bottom: 1px solid var(--line-2); }
.more-set .form-grid { padding: 14px; margin: 0; }
/* 折叠区里的字段少一点内边距,看起来是"附带的"而不是主流程 */
.more-set .field { margin-bottom: 0; }
/* ============================================================
v24 第 16 批:旅程(容器)/ 行程(单元)两级
术语固定:旅程包含行程(用户第 9 条)。
新增:题图 16:9、五项统计、行程独立页、设置三页、花销。
============================================================ */
/* ---- 题图右侧的信息区:时间 + 城市 ---- */
.trip-when {
display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px 12px;
font-size: 14px; color: var(--muted);
}
.trip-when-date { font-weight: 600; color: var(--ink); }
.trip-when-cities { display: inline-flex; flex-wrap: wrap; align-items: baseline; gap: 5px; }
.city-chip {
padding: 1px 9px; border-radius: 999px;
background: var(--line-2); color: var(--brand-ink); font-size: 12.5px;
}
.city-sep { color: var(--faint); }
/* ---- 五项统计(用户第 2 条)---- */
.trip-stats {
display: flex; flex-wrap: wrap; gap: 10px 26px;
padding: 10px 0; border-top: 1px solid var(--line-2); border-bottom: 1px solid var(--line-2);
}
.ts-item { display: flex; flex-direction: column; align-items: flex-start; gap: 1px; }
.ts-n {
font-size: 19px; font-weight: 700; color: var(--brand);
font-variant-numeric: tabular-nums; line-height: 1.2;
}
.ts-l { font-size: 12px; color: var(--muted); }
.trip-sum { margin: 0; }
/* ---- 行程行上的新元素:出行方式 / 交通细节 / 起终点 / 花销 / 角色 ---- */
.node-mode { font-size: 12px; color: var(--muted); white-space: nowrap; }
.node-traffic { display: flex; flex-wrap: wrap; gap: 5px; }
.node-traffic .ntag {
font-size: 11.5px; padding: 1px 8px; border-radius: 5px;
background: var(--line-2); color: var(--muted);
}
.node-route-line { font-size: 12.5px; color: var(--muted); }
.node-route-line .arrow { color: var(--faint); margin: 0 3px; }
.node-spent { font-size: 12.5px; color: var(--brand-ink); }
.node-member { display: inline-flex; align-items: center; gap: 4px; }
.node-role {
font-size: 11px; padding: 0 6px; border-radius: 999px;
background: rgba(168, 74, 40, .12); color: var(--brand-ink);
}
/* ---- 行程表单:参与人角色 ---- */
.role-set { margin-top: 6px; }
.role-grid { display: grid; gap: 6px; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); margin-top: 8px; }
.role-row { display: flex; align-items: center; gap: 8px; }
.role-who { flex: 0 0 72px; font-size: 13px; color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.role-row .input { flex: 1 1 auto; min-width: 0; padding: 6px 9px; font-size: 13px; }
/* ---- 花销 ---- */
.exp-list { list-style: none; margin: 0 0 12px; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.exp-row {
display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
padding: 7px 11px; border: 1px solid var(--line-2); border-radius: var(--radius-s);
background: #FDFBF7; font-size: 13.5px;
}
.exp-cat {
font-size: 11.5px; padding: 1px 8px; border-radius: 999px;
background: var(--line-2); color: var(--brand-ink);
}
.exp-title { flex: 1 1 140px; min-width: 0; }
.exp-amt { font-weight: 600; font-variant-numeric: tabular-nums; }
.exp-total { margin: 0 0 12px; font-size: 14px; }
.exp-form-row { display: flex; flex-wrap: wrap; gap: 8px; }
.exp-form-row .input { flex: 1 1 130px; min-width: 0; }
.exp-form-row .select { flex: 0 0 auto; width: auto; min-width: 96px; }
/* ---- 设置三页:车辆 / 出行方式 / 节点类型 ---- */
.set-sub { font-size: 14px; font-weight: 600; margin: 16px 0 8px; }
.veh-list { list-style: none; margin: 0 0 10px; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.veh-row {
display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
padding: 8px 11px; border: 1px solid var(--line-2); border-radius: var(--radius-s);
background: #FDFBF7;
}
.veh-ro { background: var(--line-2); }
.veh-ico { font-size: 17px; }
.veh-name { font-size: 14px; font-weight: 600; }
.veh-tag { font-size: 11.5px; padding: 1px 8px; border-radius: 999px; background: var(--surface); color: var(--muted); border: 1px solid var(--line); }
.veh-form { flex: 1 1 auto; display: flex; flex-wrap: wrap; gap: 6px; align-items: center; min-width: 0; }
.veh-form .input { flex: 1 1 110px; min-width: 0; padding: 6px 9px; font-size: 13px; }
.veh-form .select { flex: 0 0 auto; width: auto; padding: 6px 9px; font-size: 13px; }
.veh-def { display: inline-flex; align-items: center; gap: 4px; font-size: 13px; color: var(--muted); white-space: nowrap; }
.veh-add { margin-top: 4px; }
.mode-grid { display: flex; flex-direction: column; gap: 8px; }
.mode-row {
display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
padding: 8px 11px; border: 1px solid var(--line-2); border-radius: var(--radius-s);
background: #FDFBF7;
}
.mode-ico { font-size: 17px; }
.mode-name { flex: 0 0 64px; font-size: 14px; }
.mode-row .input { flex: 1 1 220px; min-width: 0; padding: 6px 9px; font-size: 13px; }
.mode-none { flex: 1 1 auto; }
.type-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.type-row {
display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
padding: 8px 11px; border: 1px solid var(--line-2); border-radius: var(--radius-s);
background: #FDFBF7;
}
.type-form { flex: 1 1 auto; display: flex; flex-wrap: wrap; gap: 6px; align-items: center; min-width: 0; }
.type-form .input { flex: 1 1 140px; min-width: 0; padding: 6px 9px; font-size: 13px; }
.type-ico { flex: 0 0 52px !important; text-align: center; font-size: 16px !important; }
.type-tag { font-size: 11.5px; padding: 1px 8px; border-radius: 999px; background: var(--surface); color: var(--muted); border: 1px solid var(--line); }
.type-tag-custom { background: rgba(168, 74, 40, .1); color: var(--brand-ink); border-color: rgba(168, 74, 40, .25); }
.type-used { flex: 0 0 auto; }
.type-add { margin-top: 4px; }
/* ---- 窄屏 ---- */
@media (max-width: 860px) {
/* 题图在窄屏铺满一行(信息区折到下面)—— 16:9 的比例保持不变 */
.trip-hero-cover { width: 100%; }
.trip-stats { gap: 8px 18px; }
.ts-n { font-size: 17px; }
}
/* ============================================================
v24 第 17 批:城市的聚合 / 高德地址 / 表单精简
用户 10 条(城市聚合、类型照 TripLog、只有自驾选车、飞机铁路只记车次、
高德地址、弹框选人、去角色标注、图片右侧显示聚合城市、时间到时分秒)。
============================================================ */
/* ---- 时间:精确到时分的那一行(比原来只到日期的那行略宽)---- */
.node-time { font-variant-numeric: tabular-nums; }
/* ---- 出发地 → 到达地:带导航链接 ---- */
.node-route-line a {
color: var(--brand-ink); text-decoration: none;
border-bottom: 1px dashed rgba(168, 74, 40, .35);
}
.node-route-line a:hover { border-bottom-style: solid; }
/* ---- 地址输入(高德联想)----
可见文本框 + 隐藏的 addr/lng/lat/city;下面一行显示"详细地址"与导航链接。 */
.loc-field { display: flex; flex-direction: column; gap: 4px; }
.loc-label { font-size: 13px; color: var(--muted); display: inline-flex; align-items: center; gap: 5px; }
.loc-ico { font-size: 14px; }
.loc-input { width: 100%; }
.loc-extra { display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px 10px; min-height: 18px; }
.loc-detail {
font-size: 12px; color: var(--muted); flex: 1 1 140px; min-width: 0;
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.loc-detail:empty { display: none; }
.loc-nav {
flex: 0 0 auto; font-size: 12px; color: var(--brand-ink);
text-decoration: none; white-space: nowrap;
}
.loc-nav:hover { text-decoration: underline; }
.loc-nav[hidden] { display: none; }
/* ---- 修正表单:里面的字段少,用同一套 .field 网格 ---- */
.rev-form .field { margin-bottom: 0; }
/* ---- 窄屏:地址那两行不换行挤成一团 ---- */
@media (max-width: 620px) {
.loc-detail { flex: 1 1 100%; }
}
/* ============================================================
v24 第 18 批:选人弹框改「暂存 + 确定」两段式(用户第 18 条)
上半部分是暂存区(点了头像/名字但还没确定的人),下面是候选清单,
最下面「取消 / 确定」。⚠️ 只有多选(.pp-mode=multi)才渲染这两块。
============================================================ */
/* ---- 暂存区:在搜索框**下面**、候选清单**上面** ----
⚠️ 用户说的是"选择框的上半部分"。搜索框在最顶(要一直能打字搜下一个人),
所以暂存区放在搜索框之下、清单之上 —— 视觉上仍然属于"上半部分",
而且不会因为暂存区变高把搜索框挤走(搜索是要反复用的)。
⚠️ v24 第 19 批 用户第 8 条:「暂存区太小了,高度太低,看不见人」——
原来只有 148px 且一行能塞很多 chip,看着像一条空横条。
现在:① 高度提到 min 132 / max 46vh(跟着弹框走,不再写死一个矮数);
② chip 改成**大一点**(头像 28px、字号 13.5),并允许每行少放几个,
让人**看得见是谁**;③ 有内容时才占位(没人暂存时不显示,见 JS)。 */
.pp-stage {
border: 1px solid var(--line); border-radius: 8px;
background: #FDFBF7; padding: 10px 12px; margin-bottom: 8px;
min-height: 132px; max-height: 46vh; overflow-y: auto;
display: flex; flex-direction: column;
}
.pp-stage[hidden] { display: none; }
.pp-stage-label {
display: block; font-size: 12px; color: var(--muted); margin-bottom: 8px;
flex: 0 0 auto;
}
.pp-stage-label b { color: var(--brand); font-variant-numeric: tabular-nums; }
.pp-stage-chips {
display: flex; flex-wrap: wrap; gap: 8px; align-content: flex-start;
flex: 1 1 auto;
}
/* ---- 底部按钮:取消在左、确定在右(主操作靠右,与全站一致)---- */
.pp-actions {
display: flex; justify-content: flex-end; gap: 8px; margin-top: 10px;
padding-top: 10px; border-top: 1px solid var(--line-2);
}
/* ---- 暂存区里的 chip 比外面的紧凑一点(弹窗里空间有限)---- */
.pp-stage-chips .pp-chip { font-size: 13px; padding: 3px 8px 3px 4px; }
.pp-stage-chips .pp-chip img { width: 28px; height: 28px; }
/* ---- 窄屏:弹窗几乎占满,暂存区别太高(把清单挤没了就没法选了)---- */
@media (max-width: 480px) {
.pp-modal-box { max-height: 88vh; }
.pp-stage { min-height: 108px; max-height: 38vh; }
}
/* ============================================================
v24 第 20 批 用户第 9 条:**全站按钮悬停统一**
------------------------------------------------------------
用户原话:「很多按钮的悬停效果,非常奇怪,感觉纯粹是换了一个背景色,
这个背景色,跟这个模块完全不协调,应该调整所有按钮的悬停效果,
保持统一,但是不要单纯的换一个不协调的背景色。」
------------------------------------------------------------
⚠️ 改之前的问题(复现探针量出来 **15 种**互不相干的 hover 背景色):
#944022 / #FAF7F1 / #FBF7F1 / #F7EDE4 / #FDF1EE / #F7E4DF / #D79230 /
#518B43 / #AC4835 / var(--line-2) / var(--surface-2,#FDFBF7) / rgba(192,83,63,.08)
—— 同一行里「取消」和「保存」悬停变的是**两套不相干的色**;
`.seg-btn` 那种更是直接糊一层 `#FDFBF7`,在米色底上看着像"脏了一块"。
而且全是"只换背景"这一种表达 —— 这正是用户说的"纯粹是换了一个背景色"。
统一后的**一套手法**(所有按钮共用同一套手感,底色只跟着自己的色系走):
① **同色系加深**:有底色的一律 `filter: brightness(.92)` ——
深色按钮(品牌红 / 黄 / 绿 / 红)自然变深一点,**不再跳到另一个色**;
② **描边留白按钮**(.btn-ghost / .btn-danger / .btn-icon 及各处白底按钮):
统一用**中性薄雾** `rgba(44,35,29,.05)` —— 落在宣纸米底上只是"轻轻按了一下",
不会再出现 #F7EDE4 那种偏色的脏块;
③ **边框微显 + 极轻抬升**:`border-color: rgba(44,35,29,.16)` + `translateY(-1px)`
+ 一层很浅的投影 —— 给的是"这个能按"的触感,而不是换色;
④ 统一 `transition`(背景 / 边框 / 颜色 / 投影 / 位移一起过渡),避免"啪一下变色"。
⚠️ 实现上放在**文件最靠后**并带 `!important` 收口:
历史上有十几处散落的 hover(.btn:hover、.page-head 里那几层、.seg-btn:hover…)
优先级各不相同,逐个改既容易漏、又会被后加的规则盖掉。
收口之后**新加的按钮自动继承同一套手感**,作者不必再记一次。
⚠️ 深色浮层上的按钮(.lb-btn 在黑色遮罩上)不适用中性薄雾(深底上看不见),
由浮层自己的规则覆盖;.pv-nav / .pv-dl 同理,见各自定义处。
============================================================ */
:root {
/* 悬停手感三件套(改这里 = 全站按钮悬停一起变) */
--hov-lift: -1px;
--hov-ring: rgba(44, 35, 29, .16);
--hov-veil: rgba(44, 35, 29, .05);
--hov-dim: brightness(.92);
--hov-t: background-color .15s ease, border-color .15s ease,
color .15s ease, box-shadow .15s ease, transform .15s ease;
}
.btn, .btn-ghost, .btn-danger, .btn-warn, .btn-ok, .btn-del, .btn-sm, .btn-icon,
button.btn, a.btn, summary.btn, .seg-btn, .icon-btn, .rv-act-btn, .cs-btn, .bf-btn {
transition: var(--hov-t);
}
/* ① 有底色的按钮:同色系加深(不换色)+ 轻抬升
⚠️ 这里**只加 filter,绝不能写 `background-color: inherit`** ——
`inherit` 取的是**父元素**的背景(多半是透明),会把实心按钮变透明
(实测:黄/绿按钮悬停后变成 alpha 0.008 的鬼影)。
不改底色就"不写底色",让按钮自己的 background 留着。 */
.btn:not(.btn-ghost):not(.btn-danger):not(.btn-icon):not(.btn-warn):not(.btn-ok):not(.btn-del):hover,
.btn-warn:hover, .btn-ok:hover, .btn-del:hover,
.search-bar button:hover {
filter: var(--hov-dim);
transform: translateY(var(--hov-lift));
box-shadow: var(--shadow-l);
}
/* ② 描边留白按钮:中性薄雾 + 边框微显 + 轻抬升(不再各自糊一个偏色底) */
.btn-ghost:hover, .btn-icon:hover, .seg-btn:hover, .icon-btn:hover,
.who-menu > summary:hover,
.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: var(--hov-veil) !important;
border-color: var(--hov-ring) !important;
transform: translateY(var(--hov-lift));
box-shadow: var(--shadow);
}
.page-head .actions > .movie-set > summary.btn-icon:hover,
.page-head .actions > .movie-set > summary.btn:hover {
background: var(--hov-veil) !important;
border-color: var(--hov-ring) !important;
}
/* ③ 危险 / 删除类:同一套手法,薄雾里带一点红(语义不丢,但不刺眼) */
.btn-danger:hover, .rv-del-btn:hover, .btn.danger:hover,
.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: rgba(192, 83, 63, .09) !important;
border-color: rgba(192, 83, 63, .30) !important;
color: var(--red) !important;
transform: translateY(var(--hov-lift));
box-shadow: var(--shadow);
}
/* ⚠️ .btn-del 是**实心红**(第 19 批 行程行那种)—— 上面那条 ③ 会把它拉成淡红,
所以实心那一类要单独拉回来:不改底色(保住 #C0533F),只走"同色系加深"。 */
.btn-del:hover {
background: #C0533F !important;
color: #fff !important;
border-color: transparent !important;
}
/* ④ 纯图标 / 文字小按钮:不占背景,只做"轻抬升 + 轻微加深" */
.person-more-btn:hover, .rv-act-btn:hover, .bf-btn:hover, .cs-btn:hover,
.more-set > summary:hover, .rv-open-btn:hover {
filter: var(--hov-dim);
transform: translateY(var(--hov-lift));
text-decoration: none;
}
/* ⑤ 浮在深色遮罩上的按钮:深底上中性薄雾看不见 → 用"提亮"代替,
但**同一套手法**(变亮 + 轻抬升),保持全局手感一致。 */
.lb-btn:hover, .pv-close:hover, .crop-x:hover {
filter: brightness(1.18);
transform: translateY(var(--hov-lift));
}
/* v24 第 20 批 第 3 条:`form="id"` 关联用的**表单外壳**。
它们只承载字段、不出现在视觉里(按钮在原来的位置上,用 form= 指过来)。
⚠️ 必须 display:none —— 不藏的话页面底部会多出几块空的表单块。 */
.form-outside { display: none; }
/* ============================================================
v24 第 20 批 第 5 条:添加行程的三处版面调整
============================================================ */
/* ---- ① 出发地 / 到达地**同一行**(用户原话:"应该在同一行")----
⚠️ 必须显式开一个"一行两列"的容器,不能靠两个 .field 相邻 ——
.form-grid 是两列网格,每个 .field 各占一格;窄屏折行后顺序还会乱。 */
.loc-row { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
@media (max-width: 560px) {
/* 窄屏放不下并排两个地址框(中文地名 + 高德联想候选),改成上下 */
.loc-row { grid-template-columns: 1fr; }
}
/* ---- ② 「车辆」只在出行方式 = 自驾 时出现(第 17 批的**同一句原话**)----
🔴 v24 第 20 批 第 5 条修的是这里:用户又报了一次「车辆,只在出行方式选择自驾时
才出现,其他情况下,默认不出现」—— 说明**上一批的隐藏根本没生效**。
根因:模板里写的是 `<div class="field" data-mode-vehicle hidden>`,
而 `.field` 自己声明了 `display:flex` —— **作者样式优先于浏览器默认的
`[hidden]{display:none}`**,所以 `hidden` 形同虚设、车辆永远显示着。
(与 .pp-item / .pv 那几处是**同一个坑**,项目里已经栽过三次。)
修法:给自己声明过 display 的元素补一条 `[hidden]` 规则。 */
.field[hidden],
.field.full[hidden],
[data-mode-vehicle][hidden],
[data-mode-ticket][hidden] { display: none !important; }
/* ============================================================
v24 第 20 批 第 4 条:拖动即时上传的**暂存区** UI
------------------------------------------------------------
`.stage-drop` 是那个"可以往里拖"的落点;`[data-stage-list]` 装卡片。
卡片三态:.is-uploading(转圈感)/ 默认(缩略图)/ .is-failed(标红)。
============================================================ */
.stage-drop {
border: 1px dashed var(--line); border-radius: var(--radius-s);
background: #FDFBF7; padding: 10px 12px; margin-top: 6px;
}
.stage-drop.is-over { outline: 2px dashed var(--brand); outline-offset: 3px; background: #FDF3EC; }
.stage-drop-tip { display: block; font-size: 12px; color: var(--muted); margin-bottom: 8px; }
.stage-list { display: flex; flex-wrap: wrap; gap: 8px; }
/* ⚠️ .stage-list 自己声明了 display:flex → 要藏就得补 [hidden](本项目老坑) */
.stage-list[hidden] { display: none; }
.stage-cell {
position: relative; width: 84px; padding: 6px 4px; border-radius: 8px;
border: 1px solid var(--line); background: var(--surface);
display: flex; flex-direction: column; align-items: center; gap: 3px;
font-size: 11px; text-align: center; box-sizing: border-box;
}
.stage-cell img { width: 72px; height: 54px; object-fit: cover; border-radius: 5px; display: block; }
.stage-cell .stage-ico {
width: 72px; height: 54px; border-radius: 5px; background: #5F6672; color: #fff;
display: flex; align-items: center; justify-content: center; font-weight: 700;
}
.stage-cell .stage-name {
max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--ink);
}
.stage-cell .stage-meta { color: var(--muted); }
.stage-cell.is-uploading { opacity: .7; }
.stage-cell.is-uploading .stage-meta { color: var(--brand); }
.stage-cell.is-failed { border-color: var(--red); background: #FDF1EE; }
.stage-cell.is-failed .stage-meta { color: var(--red); }
.stage-cell .stage-x {
position: absolute; top: -6px; right: -6px; width: 20px; height: 20px;
border-radius: 50%; border: 1px solid var(--line); background: #fff; color: var(--muted);
font-size: 13px; line-height: 1; cursor: pointer; padding: 0;
}
.stage-cell .stage-x:hover { color: #fff; background: var(--red); border-color: var(--red); }
/* ============================================================
v24 第 20 批 第 6 条:周 / 日视图的「当前时间红线」
------------------------------------------------------------
用户原话:「应该在视图中间显示一条红线,用来表示当前进行的时间,
用一个不断从上到下的红线,来表示当前时间在视图上的刻度和位置。」
⚠️ 用**绝对定位**(top 由服务端/JS 给百分比)—— 与 .tg-block 同一套坐标,
所以线正好落在时间刻度的位置上,不会与日程块错开。
⚠️ 只在**今天那一列**出现(`.is-today`),由模板决定渲染不渲染。
⚠️ `pointer-events: none`:这条线是"刻度提示",不该挡住它下面的日程点击
(用户点线附近想点的是日程,不是这条线)。
============================================================ */
.tg-now {
position: absolute; left: 0; right: 0; height: 0;
border-top: 2px solid #E0453B; /* 红线(用户点名"红线") */
z-index: 4; /* 在日程块(z-index 2)之上,要看得见 */
pointer-events: none;
}
/* 左端那个小圆点:让"这条线是活的、代表此刻"更明显(也与日历 App 的通行做法一致) */
.tg-now::before {
content: ""; position: absolute; left: -4px; top: -5px;
width: 8px; height: 8px; border-radius: 50%; background: #E0453B;
box-shadow: 0 0 0 2px rgba(255, 255, 255, .9);
}
/* 过午夜(或跨天翻页)时线要能"走出去",不要卡在格子边界上闪 */
.tg-col { overflow: hidden; }
/* ============================================================
v24 第 20 批 第 7 条:重复日程删除的**范围选择**
(只删这一次 / 这一次及以后 / 全部)
⚠️ 三档要**竖着列**并带说明文字:这是不可逆操作,横排挤成一行会看不清后果。
============================================================ */
.ev-del-scope { display: flex; flex-direction: column; gap: 10px; align-items: flex-start; }
.ev-del-head { margin: 0; font-size: 14px; color: var(--ink); }
.ev-del-picks { display: flex; flex-direction: column; gap: 6px; }
.ev-del-picks .chk-inline { align-items: flex-start; }
/* v24 第 20 批 第 8 条:提醒多选的排版(勾选框多了要能换行,别挤成一长条) */
.rem-picks { display: flex; flex-wrap: wrap; gap: 6px 14px; }
.rem-pick { font-size: 13px; }
/* ============================================================
v24 第 21 批 第 2 条:观影编辑页的**已存图片**要缩成缩略图
------------------------------------------------------------
用户原话:「观影模块,海报和票根以及照片都带进来了,但是没有缩放,
把页面撑破了,应该缩放成缩略图。」
🔴 根因:`.mv-slot` 这个类**在样式表里根本不存在**(第 20 批加模板时只写了
结构没写样式)。于是里面的 <img> 按**原始像素**渲染 —— 海报是 2:3 竖图
(上传时按 600×900 裁的),就在一个 236px 宽的格子里撑出
600×900 + 942px 高,把表单顶破、还让整页横向溢出(实测 docW 1297 > vw 1280)。
「带进来了」这个功能是对的,缺的是"把它当成缩略图看"这一层样式。
现在的规格:左侧固定尺寸缩略图(海报 2:3 → 108×162;票根按同一高度裁切),
右侧「删除这张」按钮,整体是"看得见、不占地方"的一张缩略图。
⚠️ `object-fit: cover` 是必须的:票根那种横长条塞进竖框里,
不用它就会被压扁变形(看着像图坏了)。
⚠️ 缩略图高度**写死**、不能再由图片自身比例决定 —— 那正是撑破的原因。
============================================================ */
.mv-slot {
display: flex; align-items: flex-start; gap: 10px;
margin-bottom: 10px;
}
.mv-slot > a {
display: block; flex: 0 0 auto;
width: 108px; height: 162px; overflow: hidden;
border: 1px solid var(--line); border-radius: var(--radius-s);
background: #F1E7D6;
}
.mv-slot > a img {
width: 100%; height: 100%; object-fit: cover; display: block;
}
.mv-slot > .btn { flex: 0 0 auto; align-self: center; }
/* 窄屏:缩略图再小一点,别把"删除这张"挤出去 */
@media (max-width: 480px) {
.mv-slot > a { width: 84px; height: 126px; }
}
/* ============================================================
v24 第 21 批 第 7 条:「点击或拖动进行上传」的那个框
------------------------------------------------------------
用户原话:「所有上传图片等附件的地方,都不再放按钮,而是放一个框,
点击这个框实现点击上传按钮的功能,拖动图片进这个框里,
实现拖动多个上传功能。框里面写一句话:点击或拖动进行上传,
其他的功能和现在保持一致。」
⚠️ 这是**全新的统一上传入口**,取代了原来的 `<input class="input" type=file>`
(那个原生控件长得像输入框、写着"选择文件 未选择任何文件",
与旁边那个拖放虚线框是**两个**入口 —— 用户看到的就是"到底点哪个")。
现在原生框整个 `.sr-only` 藏起来,页面上只有这**一个**框。
⚠️ 落点高亮 `.is-over` 由 app.js 的 bindZone 加(拖动经过时)——
与既有的 `.dropzone.is-over` 是同一套动作,只是这个框自己有底色,
高亮用"描边+底色变亮"表达(不用 outline,避免与自带虚线边重叠成双线)。
============================================================ */
.stage-drop-tip { display: none; } /* 旧的提示位:文案已并进框里,这里留个空占位规则 */
.upbox {
display: flex; flex-direction: column; align-items: center; justify-content: center;
gap: 6px; width: 100%; min-height: 96px; box-sizing: border-box;
padding: 16px 14px; cursor: pointer; text-align: center;
border: 1.5px dashed var(--line); border-radius: var(--radius-s);
background: #FDFBF7; color: var(--muted);
transition: background-color .15s ease, border-color .15s ease, color .15s ease;
}
.upbox:hover { border-color: var(--brand-2); background: #FBF6F0; color: var(--brand); }
.upbox:focus-visible { outline: 2px solid var(--brand); outline-offset: 3px; }
/* 拖动经过时的高亮(app.js 的 bindZone 加 .is-over) */
.upbox.is-over {
border-color: var(--brand); border-style: solid;
background: #FDF3EC; color: var(--brand);
}
.upbox-ico { font-size: 22px; line-height: 1; font-weight: 300; }
.upbox-msg { font-size: 13.5px; }
/* 窄屏:框再矮一点,别把表单撑太长 */
@media (max-width: 480px) {
.upbox { min-height: 78px; padding: 12px; }
.upbox-ico { font-size: 18px; }
}
/* 空的提示行(暂存清单下面那句"已上传/失败"):没内容时不占地方 */
.stage-drop-tip:not([hidden]) { display: block; margin-top: 6px; font-size: 12px; color: var(--muted); }
/* ============================================================
v24 第 21 批 第 7 条:新建日程的**弹框**
------------------------------------------------------------
用户原话:「日历中新建日程,无论是点击按钮还是点击日历中间,都是采用弹框的形式
进行添加,这样不影响当前日历的显示,添加完成后,动态修改当前视图,
不要刷新。」
⚠️ 原来那张表单是**内联**在日历网格上方的卡片,一展开就把月视图推下去 ——
正是"影响当前日历的显示"。改成浮层后,底下的日历**一动不动**。
⚠️ 视觉沿用站内既有的浮层语言(遮罩 rgba(44,35,29,.55) + 圆角面板 + 阴影),
与选人弹窗 / 名册动作框 / 裁剪器是同一套,用户不用重新认。
============================================================ */
.cal-modal {
position: fixed; inset: 0; z-index: 200;
display: flex; align-items: flex-start; justify-content: center;
padding: 24px 16px; overflow-y: auto;
background: rgba(44, 35, 29, .55); overscroll-behavior: contain;
}
.cal-modal[hidden] { display: none; }
.cal-modal-panel {
width: min(680px, 100%); margin: auto;
background: var(--surface); border-radius: 14px;
box-shadow: var(--shadow-l); display: flex; flex-direction: column;
max-height: calc(100vh - 48px);
}
.cal-modal-head {
display: flex; align-items: center; gap: 10px;
padding: 14px 16px 12px; border-bottom: 1px solid var(--line-2);
flex: 0 0 auto;
}
.cal-modal-head b { font-size: 15.5px; font-weight: 600; }
.cal-modal-x {
margin-left: auto; border: 0; background: transparent; cursor: pointer;
font-size: 15px; line-height: 1; color: var(--muted);
padding: 4px 7px; border-radius: 6px;
}
.cal-modal-x:hover { background: var(--hov-veil); color: var(--ink); }
.cal-modal-body { padding: 16px; overflow-y: auto; flex: 1 1 auto; }
.cal-modal-foot {
display: flex; align-items: center; gap: 10px;
padding: 12px 16px 14px; border-top: 1px solid var(--line-2);
flex: 0 0 auto;
}
/* 提交键贴右(次要动作在左、主要动作在右,与全站一致) */
.cal-modal-foot .btn[type="submit"] { margin-left: auto; }
.cal-modal-tip { font-size: 12.5px; color: var(--muted); }
.cal-modal-tip.err { color: var(--red); }
/* 日历本:色块 + 下拉(原生 option 画不出色块,见模板里的说明) */
.cal-book-row { display: flex; align-items: center; gap: 8px; }
.cal-book-dot {
flex: 0 0 auto; width: 20px; height: 20px; border-radius: 6px;
border: 1px solid rgba(44, 35, 29, .12);
}
.cal-book-row .select { flex: 1 1 auto; }
/* ============================================================
v24 第 21 批 第 7 条:日程新建弹框的显隐(**必须放收尾**)
------------------------------------------------------------
⚠️ 为什么单独补这一条:本项目有一条全局兜底
`.field[hidden], .field.full[hidden], [data-mode-vehicle][hidden], … { display:none !important }`
(第 20 批为了修"车辆藏不住"加的)。而弹框的**面板里就有 `.field`** ——
面板一旦被隐藏,CSS 的 `display:flex` 与那条 `!important` 会打架,
加上 `hidden` 属性同时落在多个层级上,会出现"明明摘了 hidden 却还是不显示"。
这里用 `:not([hidden])` 把"打开态"显式钉死(后写者赢 + 特异性更高),
两种状态各归各的规则、互不牵连。
============================================================ */
.cal-modal:not([hidden]) { display: flex; }
.cal-modal[hidden] { display: none !important; }
/* ============================================================
v24 第 22 批:CardDAV 卡的窄屏收口
------------------------------------------------------------
⚠️ 为什么必须补这一段:`.node-badge` 是 `white-space: nowrap`(徽标本来就不该折行,
全站都在用),而这一卡的按钮行里放了一句说明性徽标 + 一个长地址输入框 ——
在 360/375/390px 上直接把整页**撑出横向滚动**
(真机几何实测:scrollW 412 > clientW 360,越界元素 = 这句徽标)。
手机上"整页能左右拖"是最刺眼的一类问题,所以:
· 窄屏让 `.btn-row` 能换行(否则按钮 + 徽标永远挤在一行);
· 徽标允许折行(它在这张卡里是**说明**,不是标签)。
只作用于这张卡(`.card[id="carddav"]`),不动全站的 `.node-badge` ——
改全站会把所有徽标都变成可折行,那些地方正是要靠 nowrap 保持一行。
============================================================ */
@media (max-width: 480px) {
#carddav .btn-row { flex-wrap: wrap; }
#carddav .node-badge { white-space: normal; }
}
/* ============================================================
v24 第 23 批 用户第 1 条:资源页列表瘦身 + 右上角四色按钮 + 四个弹框
============================================================ */
/* ---- ① 标题行:按钮组吸附到最右侧(与标题同一排)----
见上面 `.res-ops` 原位那条注释:**旧规则已经就地改掉了**,不是在这里再覆盖一遍。
(这正是本轮踩的坑:先前只在这里追加,`flex-direction: column` 漏改,按钮竖排。)
⚠️ 按钮组必须是 <span>(phrasing content):<summary> 里放 <div> 是非法 HTML。
这里给 `display:inline-flex` 才能有 flex 布局(span 默认 inline)。 */
.res-caret { color: var(--faint); font-size: 12px; flex: 0 0 auto; }
/* 展开时把箭头翻过来(纯装饰,不影响语义) */
.res-item[open] .res-caret { transform: rotate(180deg); }
.res-caret { transition: transform .15s ease; }
/* ---- ② 四个按钮的颜色(用户点名:编辑黄、共享绿、附件蓝、删除红)----
`.btn` 自带边框与底色,这里只覆盖这三样;文字色统一用白(黄底白字对比度偏低,
所以黄色的字色用深棕而不是纯白 —— 与 `.btn-warn` 的既有做法一致)。 */
.btn-info { background: #2C6FB5; color: #fff; border-color: transparent; } /* 附件:蓝 */
.res-op { font-size: 12.5px; }
/* ---- ③ 展开区:人员行(附件列完之后才显示)----
用户原话:「附件列完后,列出来人员,所有者、共享给谁了。然后其他的都不要。」 */
.res-people {
display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
padding-top: 10px; border-top: 1px solid var(--line-2);
}
/* ---- ④ 弹框外壳 ----
⚠️ `[hidden]` 必须**自己声明**:下面这两个类都写了 `display:flex`,
而 `[hidden]` 的浏览器默认 `display:none` 优先级更低 → 会被盖过 → 关不掉。
项目在 `.pv` 与 `.pp-item` 上各栽过一次,所以这两行是硬要求(有 e2e 断言盯着)。 */
.res-modal {
position: fixed; inset: 0; z-index: 78;
display: flex; align-items: center; justify-content: center;
}
.res-modal[hidden] { display: none; }
.res-mask { position: absolute; inset: 0; background: rgba(47, 36, 30, .34); }
.res-panel-lg, .res-panel-sm {
position: relative; z-index: 1;
background: var(--surface); border: 1px solid var(--line);
border-radius: 14px; box-shadow: var(--shadow-l);
width: min(460px, 92vw); max-height: min(88vh, 700px);
display: flex; flex-direction: column;
}
.res-panel-sm { width: min(400px, 92vw); }
.res-panel-lg[hidden], .res-panel-sm[hidden] { display: none; }
.res-ph {
display: flex; align-items: center; gap: 10px;
padding: 12px 14px 10px; border-bottom: 1px solid var(--line-2);
}
.res-ph b { font-size: 14.5px; }
.res-x {
margin-left: auto; border: 0; background: transparent; cursor: pointer;
font-size: 15px; color: var(--muted); padding: 4px 6px; border-radius: 6px;
}
.res-x:hover { background: var(--line-2); color: var(--ink); }
.res-pb { padding: 12px 14px; overflow-y: auto; }
.res-pb .field { margin-bottom: 12px; }
.res-pb .field > label { display: block; font-size: 13px; color: var(--muted); margin-bottom: 5px; }
.res-pf {
display: flex; gap: 8px; justify-content: flex-end;
padding: 10px 14px; border-top: 1px solid var(--line-2);
}
.res-pb .pp { max-width: none; }
.res-sub { font-size: 13px; color: var(--muted); margin: 14px 0 8px; font-weight: 600; }
.res-share-now { border-top: 1px solid var(--line-2); margin-top: 10px; }
/* ---- ⑤ "已共享给" / "已有附件" 两行(脚本按 data 属性现渲染)----
⚠️ 用 <span> + inline-flex 而不是 <div>/<table>:
这些行要能塞进任意容器,且不需要表格语义(就是"一行一行的清单")。 */
.rd-share-row, .rd-att-row {
display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
padding: 6px 0; border-bottom: 1px solid var(--line-2);
}
.rd-share-row:last-child, .rd-att-row:last-child { border-bottom: 0; }
.rd-share-name, .rd-att-name { flex: 1 1 auto; min-width: 0; word-break: break-all; font-size: 13.5px; }
/* 附件行里的缩略图:与展开区的附件墙同一个观感(小一号,弹框里放得下) */
.rd-att-thumb {
width: 34px; height: 34px; flex: 0 0 auto;
object-fit: cover; border-radius: 6px; border: 1px solid var(--line);
}
.rd-att-ico {
width: 34px; height: 34px; flex: 0 0 auto;
display: inline-flex; align-items: center; justify-content: center;
border-radius: 6px; background: #5F6672; color: #fff;
font-size: 10px; text-align: center; line-height: 1.1;
padding: 2px; overflow: hidden; word-break: break-all;
}
/* ---- ⑥ 窄屏 ----
四个按钮 + 标题 + 归属人 chip + 权限徽标挤在一行,360px 上必然溢出。
让按钮组整体落到第二行(`.res-head` 本来就能 wrap),并收紧内边距。
⚠️ 这里**不**动全站 `.node-badge` 的 nowrap —— 上一批(CardDAV 卡)已经证明
改全站会把所有徽标都变得可折行。 */
@media (max-width: 560px) {
.res-head { padding: 10px 12px; row-gap: 6px; }
.res-ops { margin-left: 0; width: 100%; justify-content: flex-start; }
.res-caret { display: none; }
.res-op { flex: 0 1 auto; }
}
/* ============================================================
v24 第 23 批 用户第 2 条:财务页类别标签可点 + 上下两图联动
============================================================ */
/* ---- ① 概览卡里的标签条 ----
原来是一排纯展示的 <span>(不可点),现在与图表卡那条 chip **并成一套筛选入口**。
⚠️ 元素从 <span> 换成了 <button>:它现在真的有行为了 ——
用 span 加 JS 点击,键盘用户和屏幕阅读器都认不出"这是个能按的东西"。
代价是浏览器会给 <button> 默认样式(灰底、系统字体、内边距),
`.ck-chip` 里已经把这些全部覆盖掉了(它本来就是给 button 写的)。
⚠️ 两条既有规则都在抢 margin:`.led-kinds`(margin-top:6px)在前、
`.ck-filter`(margin:0 0 12px)在后 → 后者赢,6px 那个上边距会被吃掉。
所以这一条写在**文件末尾**,用双类选择器把意图压回来。 */
.led-kinds.ck-filter { margin: 8px 0 0; }
/* 标签上的金额(原来就有,改成 button 后需要重新确认字重/数字对齐) */
.led-kind-chip b { font-weight: 600; }
/* ---- ② 选中类别后的高亮条 ----
回答用户那句"统计这个标签下的所有财务条目的总数":
看的是哪一类、合计多少、几笔 —— 并且给一个明确的出口(清除筛选)。
⚠️ 没有它的话,"图上少了几条线"会被读成"数据丢了",而不是"我在筛选"。 */
.led-focus {
display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
margin-top: 10px; padding: 8px 12px;
background: #FDF6EE; border: 1px solid #EBD9C4; border-radius: 8px;
font-size: 13px; color: #7A4A20;
}
.led-focus[hidden] { display: none; } /* 见 .res-modal 的同一提醒:自己声明了 display 就必须补这条 */
.led-focus-txt { flex: 1 1 auto; min-width: 0; }
/* ---- ③ 图下面的说明:整本账 / 只看某一类,两句话互斥显示 ---- */
.ck-note { margin: 8px 2px 0; }
.ck-note > span[hidden] { display: none; }
/* ============================================================
v24 第 24 批 用户第 2、3 条:首页信息面板 + 日程/任务左右各半
============================================================ */
/* ---- ① 信息面板:左(问候/日子/数字) + 右(天气,**贴右端**)----
⚠️ 旧的 `.home-brief` 是 `flex-direction: column`,天气自然就落到了下一行。
用户明确要求「天气放在这个信息面板的最右侧…靠右放」→ 改成横排 + margin-left:auto。
⚠️ 这里**必须**把旧规则的那几行一起改掉(v24 第 23 批:只在末尾追加、
漏了 flex-direction,四个按钮竖排撑高标题行 —— 同一个坑不再踩第二次)。 */
.home-brief { display: flex; flex-direction: row; align-items: flex-start; gap: 18px; flex-wrap: wrap; }
.hb-left { flex: 1 1 320px; min-width: 0; display: flex; flex-direction: column; gap: 8px; }
.hb-hello { font-size: 19px; line-height: 1.3; }
.hb-hello b { font-weight: 700; }
.hb-date { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; color: var(--muted); font-size: 13.5px; }
.hb-date .hb-d { color: var(--ink); font-size: 15px; font-weight: 600; }
.hb-date .hb-lunar { color: var(--brand-ink); }
/* 日子标记:节气 / 法定假日 / 传统节日 / 纪念日 / 生日,各给一个色 */
.hb-marks { display: flex; flex-wrap: wrap; gap: 6px; }
.hb-mark {
display: inline-flex; align-items: center; padding: 2px 9px; border-radius: 999px;
font-size: 12.5px; white-space: nowrap; border: 1px solid transparent;
}
.hb-mark.is-holiday { background: #FDECEC; color: #A8232B; border-color: #F3C9C9; }
.hb-mark.is-term { background: #EDF4E9; color: #3F6B34; border-color: #CFE2C6; }
.hb-mark.is-festival { background: #FDF2E3; color: #96551A; border-color: #F0D9B8; }
.hb-mark.is-anniv { background: #F2ECF7; color: #6B4A86; border-color: #DCCBE8; }
.hb-mark.is-birthday { background: #FDECF3; color: #A03060; border-color: #F3C9DB; }
.hb-stats { display: flex; flex-wrap: wrap; gap: 8px 20px; font-size: 13px; color: var(--muted); }
.hb-stats .hbs { display: inline-flex; align-items: baseline; gap: 4px; color: var(--muted); }
.hb-stats .hbs b { color: var(--ink); font-size: 16px; font-variant-numeric: tabular-nums; }
.hb-stats .hbs.is-late b { color: var(--red); }
/* ---- 天气卡:贴右端 + **彩色**图标(用户原话「彩色图标的形式」)----
⚠️ 颜色按天气**色调**(后端给的 tone)分档,而不是让 CSS 猜天气码 ——
同一份判断写两遍必然飘。晴=琥珀、云=灰蓝、雨=天蓝、雪=青、雷=紫、雾=灰。 */
.hb-weather {
margin-left: auto; flex: 0 0 auto;
display: grid; grid-template-columns: auto 1fr; grid-template-rows: auto auto;
align-items: center; gap: 2px 12px; min-width: 168px;
padding-left: 16px; border-left: 1px solid var(--line-2);
}
.hbw-icon {
grid-row: 1 / span 2; font-size: 40px; line-height: 1;
/* ⚠️ 不能靠 `-webkit-text-fill-color` 之类给**文字**上渐变(见 emoji-glyph 那课),
这里就是普通的彩色文字 + 一点投影,稳定且不挑系统字体。 */
filter: drop-shadow(0 1px 1px rgba(0, 0, 0, .10));
}
.hb-weather.is-sunny .hbw-icon { color: #E8A33D; }
.hb-weather.is-cloud .hbw-icon { color: #7C8AA0; }
.hb-weather.is-rain .hbw-icon { color: #3B7FBF; }
.hb-weather.is-snow .hbw-icon { color: #5AA6B8; }
.hb-weather.is-storm .hbw-icon { color: #7A5BB5; }
.hb-weather.is-fog .hbw-icon { color: #8C8C8C; }
.hbw-main { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.hbw-temp { font-size: 24px; font-weight: 700; font-variant-numeric: tabular-nums; line-height: 1.1; }
.hbw-text { font-size: 13px; color: var(--muted); }
.hbw-wind { font-size: 12px; color: var(--faint); }
.hbw-place { grid-column: 2; font-size: 12px; color: var(--faint); }
/* ---- ② 日程 / 任务 左右各半 ----
⚠️ 高度**固定**(用户原话「为了好看高度需要固定」),滚动条**藏起来**
(「鼠标放上去之后向下可以滚动,暂时不要出现滚动条太丑了」)。
藏滚动条要三套写法(Firefox / IE-Edge / WebKit 各一套),少一套就在某个浏览器上露出来。
🔴🔴 v24 第 25 批 用户第 3 条修的真 bug:「日程和任务看板,两个模块,
现在有点错位,一个高,一个低,应该完全一样才对。」
根因:这两块原来是 `align-items: start` + 各自内容撑高 ——
左边日程的**刻度行数**由"今天有几件事"决定(掐头去尾之后 3~9 行不等),
右边的任务条数也各不相同 ⇒ 两张卡片高度天然不等,一眼就是"一高一低"。
修法(三步,缺一不可):
① `align-items: stretch` —— 让两栏拉成同一行高(grid 的默认行为,
但上一版显式写成了 `start`,那等于主动放弃对齐);
② `.hd-pane` 给一个**固定总高**并 `display:flex; flex-direction:column`
(它本来就有 flex 列),head 不收缩、body 吃满剩余;
③ `.hd-pane-body` 用 `flex: 1 1 auto` + `min-height: 0`
取代原来的固定 `height: 360px`。
⚠️ `min-height: 0` 不能省:flex 子项的默认 `min-height:auto`
会让它**不肯缩到内容以下**,于是内容一多就把卡片顶高、
对齐又失效(这是"固定高度 + 内部滚动"这套写法的经典坑)。
这样两块的**外框高度恒等**,与各自内容多少无关 —— 对齐不再依赖数据。 */
.home-duo { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; align-items: stretch; }
.hd-pane { display: flex; flex-direction: column; height: 440px; min-height: 0; }
.hd-pane-head { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; flex: 0 0 auto; }
.hd-pane-head .cinema-h2 { margin: 0; flex: 1 1 auto; }
.hd-pane-body {
flex: 1 1 auto; min-height: 0; overflow-y: auto; overscroll-behavior: contain;
scrollbar-width: none; /* Firefox */
-ms-overflow-style: none; /* 旧 Edge / IE */
}
.hd-pane-body::-webkit-scrollbar { width: 0; height: 0; } /* Chrome / Safari */
/* ---- 日程:日视图形式的刻度网格 ----
⚠️ `.ag-row` 的高度就是"一小时"(44px);日程块用**百分比 top** 定位,
所以刻度数与日程块的比例天然一致(都在同一个容器里算)。 */
.ag-allday { display: flex; flex-direction: column; gap: 4px; margin-bottom: 10px; }
/* 🔴 v24 第 26 批 用户第 2 条:全天区现在是 `.hd-pane` 的**直接子元素**(不在滚动容器内),
必须显式声明"不参与伸缩、也不会被压扁":
· `flex: 0 0 auto` —— 它是固定高度的一块,绝不与下面的滚动区抢空间;
· `max-height: 33%` + 内部滚动 —— 全天日程可以有很多条(节假日、生日都在里面),
全铺开会把时段网格挤到看不见;给个上限,超了自己滚。
⚠️ 上限用**百分比**(相对 440px 的 pane)而不是固定 px:
窄屏时 pane 只有 320px,固定 140px 会占掉近一半(见下面的媒体查询)。
⚠️ 这里也要藏滚动条(与 `.hd-pane-body` 同一理由:用户嫌丑)。 */
.ag-allday {
flex: 0 0 auto; max-height: 33%; overflow-y: auto; overscroll-behavior: contain;
scrollbar-width: none; -ms-overflow-style: none;
}
.ag-allday::-webkit-scrollbar { width: 0; height: 0; }
.ag-allday-item {
display: flex; align-items: center; gap: 8px; padding: 5px 8px;
background: var(--line-2); border-radius: var(--radius-s); font-size: 13.5px; color: var(--ink);
}
.ag-allday-item .ag-ad-t { font-size: 11.5px; color: var(--muted); flex: 0 0 auto; }
.ag-allday-item .ag-ad-w { flex: 1 1 auto; min-width: 0; font-weight: 600; }
.ag-grid { position: relative; }
.ag-row { display: flex; align-items: flex-start; gap: 8px; height: 44px; }
.ag-hour { flex: 0 0 42px; font-size: 11.5px; color: var(--faint); font-variant-numeric: tabular-nums; transform: translateY(-6px); }
.ag-line { flex: 1 1 auto; height: 1px; background: var(--line-2); margin-top: 0; }
/* 当前时刻红线(用户原话「需要在当前的时间或者是时刻那里显示一条红线」) */
.ag-now { position: absolute; left: 46px; right: 0; height: 0; border-top: 2px solid var(--red); z-index: 2; }
.ag-now-dot {
position: absolute; left: -4px; top: -5px; width: 8px; height: 8px;
border-radius: 50%; background: var(--red);
}
/* 日程块 */
.ag-ev {
position: absolute; left: 46px; right: 4px; display: flex; align-items: center; gap: 8px;
padding: 5px 9px; min-height: 30px;
background: var(--surface); border: 1px solid var(--line);
border-left: 3px solid var(--brand); border-radius: var(--radius-s);
box-shadow: var(--shadow); font-size: 13.5px; color: var(--ink);
transform: translateY(2px);
}
.ag-ev:hover { box-shadow: var(--shadow-l); }
.ag-ev .ag-ev-t { flex: 0 0 auto; font-size: 11.5px; color: var(--muted); font-variant-numeric: tabular-nums; }
.ag-ev .ag-ev-w { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* ---- 任务行:标题 + 状态 + **看板名** + 截止 ---- */
.tk-list { display: flex; flex-direction: column; }
.tk-row { align-items: center; }
.tk-board {
flex: 0 0 auto; font-size: 11.5px; color: var(--muted);
background: var(--line-2); border-radius: 999px; padding: 1px 8px;
}
.tk-due { flex: 0 0 auto; font-size: 11.5px; color: var(--faint); }
/* ---- 任务的「完成」复选框(v24 第 25 批 用户第 6 条)----
用户原话:「所有的任务,前面都带一个复选框,在任何地方,点这个复选框,
这个任务就完成了。」
⚠️ 它是 `<button>` 不是 `<input type=checkbox>`(见 _task_check.php 的说明):
勾没勾**由服务端状态渲染的 class** 说了算,不是浏览器自己管 checked。
⚠️ 尺寸写死 18px 且 `flex: 0 0 auto`:卡片标题是长文本,
不写死的话它会被 flex 压扁(同一行里"文字优先")。
⚠️ 勾选态用**伪元素画对勾**而不是塞一个 ✓ 字符:
字符在不同字体里基线不同,18px 的方块里会歪。 */
.tk-check {
flex: 0 0 auto; width: 18px; height: 18px; padding: 0; margin: 0;
border: 1.5px solid var(--line); border-radius: 5px; background: #fff;
cursor: pointer; position: relative; transition: border-color .15s, background .15s;
}
.tk-check:hover { border-color: var(--brand); }
.tk-check::after {
content: ""; position: absolute; left: 5px; top: 1.5px;
width: 5px; height: 10px; border: solid #fff; border-width: 0 2px 2px 0;
transform: rotate(45deg) scale(0); transition: transform .13s ease;
}
.tk-check.is-done { background: var(--ok, #5E9E4E); border-color: var(--ok, #5E9E4E); }
.tk-check.is-done::after { transform: rotate(45deg) scale(1); }
/* 只读账号:一颗不可点的静态方块 */
.tk-check.is-static { cursor: default; }
.tk-check.is-static:hover { border-color: var(--line); }
/* 已完成的那张卡:标题压灰划掉(一眼看出"这件做完了") */
.kb-card.is-done .kb-title { color: var(--muted); text-decoration: line-through; }
/* ⚠️ 首页/详情那种"一行文字 + 一颗复选框"的场合:
`.dash-row` 是 `align-items: baseline`,而复选框是 18px 的方块 ——
按基线对齐会让它**沉到文字下面半截**(基线是文字的底边,方块没有基线)。
所以带 .tk-check 的行改成 `center`,文字与方块的**中线**对齐,
这才是"复选框在文字前面"该有的样子。
(`.sub-row` 本来就是 center,不用管它。) */
.dash-row:has(.tk-check) { align-items: center; }
/* ⚠️ v24 第 26 批 用户第 5 条:**详情页页头的复选框已去掉**(那颗由「标记完成」按钮取代),
所以这里原来那条 `.page-head h1:has(> .tk-check)` 一并删除 ——
留一条没有使用者的规则,只会让下一个人以为"页头还该有复选框"。 */
/* 看板页顶上的「三个状态到底什么意思」(用户第 6 条直接问出来的那句话) */
.tk-legend {
display: flex; flex-wrap: wrap; gap: 6px 18px; align-items: center;
margin: 0 0 12px; padding: 9px 12px;
background: var(--surface-2, rgba(0, 0, 0, .02));
border: 1px solid var(--line); border-radius: var(--radius-s);
}
.tk-legend .tk-lg-item { display: inline-flex; align-items: center; gap: 6px; font-size: 12.5px; }
.tk-legend .tk-lg-hint { color: var(--muted); }
/* ---- 窄屏:并排改成上下(各占一半宽度在手机上没法读)---- */
@media (max-width: 760px) {
.home-duo { grid-template-columns: 1fr; }
/* ⚠️ 窄屏仍要保持**固定高度**(用户要的是"高度固定、内部滚动"),
只是比桌面矮一点。不写成 auto:那样内容一多整页就长得没法用。 */
.hd-pane { height: 320px; }
.hb-weather { margin-left: 0; padding-left: 0; border-left: 0; padding-top: 10px; border-top: 1px solid var(--line-2); width: 100%; }
}
/* ============================================================
v24 第 24 批 用户第 4 条:个人导航**管理页**样式
(展示页样式在 assets/css/nav-show.css —— 那份是从 SimpleNav 整份搬来的,
这里只管登录后的管理界面,所以用家书自己的视觉语言。)
============================================================ */
.nav-grp-block { margin-bottom: 14px; }
.nav-sub { font-size: 13.5px; margin: 10px 0 6px; color: var(--muted); }
.nav-sortlist { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.nav-sortrow {
display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
padding: 7px 10px; border: 1px solid var(--line-2); border-radius: var(--radius-s);
background: #FDFBF7;
}
/* 隐藏中的条目淡一点,一眼能看出"它还在、只是不显示" */
.nav-sortrow.is-off { opacity: .55; }
.nav-sortrow.is-dragging { box-shadow: var(--shadow-l); border-color: var(--brand-2); }
.nav-drag {
flex: 0 0 auto; cursor: grab; color: var(--faint); font-size: 13px;
letter-spacing: -1px; user-select: none; padding: 0 2px;
}
.nav-drag:active { cursor: grabbing; }
.nav-ico {
flex: 0 0 auto; width: 20px; height: 20px; border-radius: 5px; object-fit: contain;
border: 1px solid var(--line-2); background: #fff;
}
.nav-ico-letter {
display: inline-flex; align-items: center; justify-content: center;
color: #fff; font-size: 11px; border: 0;
}
.nav-sname { flex: 0 0 auto; font-size: 13.5px; font-weight: 600; }
.nav-surl { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.nav-acts { margin-left: auto; display: inline-flex; gap: 6px; flex-wrap: wrap; }
@media (max-width: 560px) {
.nav-acts { margin-left: 0; width: 100%; }
.nav-surl { display: none; }
}
/* ============================================================
v24 第 24 批 用户第 5 条:微信读书导入页(书清单 + 样章)
============================================================ */
.wr-list { display: flex; flex-direction: column; gap: 8px; margin: 4px 0 14px; }
.wr-book {
display: flex; align-items: flex-start; gap: 10px;
padding: 9px 12px; border: 1px solid var(--line-2); border-radius: var(--radius-s);
background: #FDFBF7; cursor: pointer;
}
.wr-book:hover { border-color: var(--brand-2); }
.wr-book > input[type=checkbox] { margin-top: 3px; flex: 0 0 auto; accent-color: var(--brand); }
.wr-main { flex: 1 1 auto; min-width: 0; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.wr-title { font-size: 14px; font-weight: 600; }
/* 样章:原文用斜体、想法用普通体 —— 与读书详情页里笔记的排法一致 */
.wr-sample { flex: 1 1 100%; display: flex; flex-direction: column; gap: 2px; margin-left: 22px; }
.wr-q { font-size: 12.5px; color: var(--muted); line-height: 1.6; }
.wr-q i { font-style: normal; color: var(--ink); }
.wr-q em { font-style: normal; color: var(--brand-ink); }
.wr-q em::before { content: '💭 '; }
/* ---- 微信读书:封面(v24 第 25 批 用户第 8 条「封面图看不到」)----
⚠️ 尺寸必须写死(52×74 ≈ 2:3,与 bookcover 槽位的裁剪比例一致):
不写死的话,官方封面各尺寸不一,一行里高低参差(这正是"看起来乱"的原因)。
⚠️ `object-fit: cover` + 固定高宽:封面比例不一时居中裁,不拉伸变形。 */
.wr-cv {
flex: 0 0 auto; width: 52px; height: 74px; border-radius: 6px;
object-fit: cover; background: var(--line-2);
box-shadow: 0 1px 4px rgba(0, 0, 0, .10);
}
.wr-cv-ph {
display: flex; align-items: center; justify-content: center; font-size: 22px;
color: var(--faint); box-shadow: none; border: 1px dashed var(--line);
}
/* 外链封面(统计块里的「读得最多的书」,直接来自官方 CDN,本站不下载) */
.wr-top-cv {
flex: 0 0 auto; width: 34px; height: 48px; border-radius: 5px;
object-fit: cover; background: var(--line-2);
}
.wr-top-ph { display: flex; align-items: center; justify-content: center; color: var(--faint); }
/* ---- 自动同步(用户第 8 条)---- */
.wr-sync { display: flex; flex-wrap: wrap; gap: 8px 22px; font-size: 13px; }
.wr-sync-item { color: var(--muted); }
.wr-sync-item b { color: var(--ink); font-weight: 600; }
/* ---- 阅读统计(用户第 8 条)----
一排数字卡。⚠️ `flex: 0 1 auto` + `min-width` 而不是等宽:
指标个数(时长/天数/日均/读过/读完/笔记)随官方返回而变,
写死等宽会在某个指标缺失时留一块空。 */
.wr-stats { display: flex; flex-wrap: wrap; gap: 10px; margin: 2px 0 4px; }
.wr-stat {
flex: 0 1 auto; min-width: 108px; padding: 10px 14px;
background: var(--line-2); border-radius: var(--radius-s);
display: flex; flex-direction: column; gap: 2px;
}
.wr-stat b { font-size: 17px; color: var(--ink); font-weight: 600; }
.wr-stat span { font-size: 12px; color: var(--muted); }
.wr-sub { margin: 18px 0 8px; font-size: 14px; font-weight: 600; color: var(--ink); }
/* 读得最多的书 */
.wr-top { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.wr-top-item { display: flex; align-items: center; gap: 10px; }
.wr-top-b { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.wr-top-t { font-size: 13.5px; color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.wr-top-time { flex: 0 0 auto; font-size: 12px; color: var(--muted); font-variant-numeric: tabular-nums; }
/* 偏好分类:名称 + 条 + 数值 */
.wr-cats { display: flex; flex-direction: column; gap: 6px; }
.wr-cat { display: flex; align-items: center; gap: 10px; font-size: 13px; }
.wr-cat-n { flex: 0 0 84px; color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.wr-cat-bar { flex: 1 1 auto; height: 7px; background: var(--line-2); border-radius: 999px; overflow: hidden; }
.wr-cat-bar i { display: block; height: 100%; background: var(--brand); border-radius: 999px; }
.wr-cat-v { flex: 0 0 auto; font-size: 12px; }
/* ============================================================
v24 第 26 批 用户第 4 条:新增站点时的「可复用站点」提示块
(跨用户复用:别人加过同一个 URL,你就能直接搬过来)
============================================================ */
.nav-reuse {
margin: 0 0 12px; padding: 10px 12px;
border: 1px solid var(--line-2); border-left: 3px solid var(--brand-2);
border-radius: var(--radius-s); background: #FBF7F2;
}
.nav-reuse-h { font-size: 13px; color: var(--ink); margin-bottom: 8px; }
.nav-reuse-list { display: flex; flex-wrap: wrap; gap: 8px; }
.nav-reuse-item {
display: inline-flex; align-items: center; gap: 7px; cursor: pointer;
padding: 5px 10px; border: 1px solid var(--line); border-radius: var(--radius-s);
background: #fff; font-size: 13px; color: var(--ink);
}
.nav-reuse-item:hover { border-color: var(--brand-2); }
/* .is-on = 已选中(点一下即选中,再点别的会切换过去) */
.nav-reuse-item.is-on {
border-color: var(--brand); box-shadow: inset 0 0 0 1px var(--brand);
background: var(--brand-soft, #FDF3EC);
}
.nav-reuse-item .nav-ico { width: 20px; height: 20px; border-radius: 4px; object-fit: contain; }
.nav-reuse-nm { white-space: nowrap; }
.nav-reuse-f { font-size: 12px; color: var(--muted); margin-top: 8px; }
/* 空 img(没有图标可复用)别占位成一个破图框 */
.nav-reuse-item img:not([src]) { visibility: hidden; }