Files
FamilyBook/assets/css/app.css
T

4574 lines
240 KiB
CSS
Raw Normal View History

/* ============================================================
家书 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,所以把「最高祖先」排第一位以画出尽可能多的血亲旁支;
2026-10-02 19:24:08 +08:00
加载后再把镜头平移回「我」,兼顾"看到全树"与"以自己为中心"。
⚠️ 免费版 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;
2026-10-02 19:24:08 +08:00
/* 拖拽/缩放由库接管,别让浏览器插手滚动 */
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;
}
2026-10-02 19:24:08 +08:00
/* 隐藏 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; }
2026-10-02 19:24:08 +08:00
/* 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。 */
2026-10-02 19:24:08 +08:00
.kin-chart .card_cont > .card,
.kin-chart .card_cont .card-inner,
.kin-chart.f3 .card_cont .card-inner {
2026-10-02 19:24:08 +08:00
background: transparent;
padding: 0;
border-radius: 0;
min-height: 0;
width: auto;
height: auto;
outline: none;
2026-10-02 19:24:08 +08:00
}
.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;
2026-10-02 19:24:08 +08:00
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; }
2026-10-02 19:24:08 +08:00
/* 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; }
/* ============================================================
v27 个人笔记(用户 2026-10-07)
参考 flomo(时间流卡片 + 标签)/ Obsidian(图文混排的直觉)/ 思源(分块),
但一律取**轻**的那一层:块只有 段落/图片/清单,不做块引用、不做富文本。
配色沿用宣纸米 + 砖红,不引入第三套色。
============================================================ */
/* ---- 列表页:左标签云 + 右卡片流 ---- */
.note-layout { display: grid; grid-template-columns: 220px 1fr; gap: 16px; align-items: start; }
.note-side { position: sticky; top: 16px; }
.note-main { min-width: 0; }
.note-search-card { margin-bottom: 12px; }
.note-search-card .search { margin: 0; }
.note-tags { display: flex; flex-wrap: wrap; gap: 6px; }
.note-tag {
display: inline-flex; align-items: center; gap: 5px;
padding: 4px 9px; border-radius: 999px; cursor: pointer;
border: 1px solid var(--line); background: #fff;
font-size: 12.5px; color: var(--muted); text-decoration: none;
}
.note-tag:hover { border-color: var(--brand-2); color: var(--brand-ink); text-decoration: none; }
.note-tag.is-on { border-color: var(--brand); color: var(--brand-ink); background: var(--brand-soft, #FDF3EC); }
.note-tag b { font-weight: 600; color: var(--faint); font-variant-numeric: tabular-nums; }
.note-tag.is-on b { color: var(--brand-ink); }
.note-tags-inline { margin-top: 10px; }
.note-tags-suggest { margin-top: 8px; align-items: center; }
/* ---- 卡片 ---- */
.note-card { margin-bottom: 12px; }
.note-head { display: flex; align-items: center; gap: 8px; }
.note-meta { display: flex; align-items: center; gap: 8px; flex: 1 1 auto; min-width: 0; }
.note-ava { width: 22px; height: 22px; border-radius: 50%; object-fit: cover; flex: 0 0 auto; }
.note-who { font-size: 13px; color: var(--ink); font-weight: 500; }
.note-time { font-size: 12px; color: var(--faint); margin-left: auto; padding-right: 4px; }
/* 共享/私有标记:一眼分清"这是我写给自己看的"还是"全家都能看的" */
.note-share, .note-private {
font-size: 11px; padding: 1px 7px; border-radius: 999px; flex: 0 0 auto;
}
.note-share { color: #2F6B4F; background: #E7F2EC; }
.note-private { color: var(--faint); background: var(--line-2); }
.note-ops { display: flex; align-items: center; gap: 6px; flex: 0 0 auto; }
.note-ops .inline-form { display: inline; }
.note-title { margin: 10px 0 6px; font-size: 16px; font-weight: 600; color: var(--ink); }
.note-body { display: flex; flex-direction: column; gap: 8px; }
.note-text { margin: 0; font-size: 14.5px; line-height: 1.7; color: var(--ink); white-space: pre-wrap; }
.note-list { margin: 0; padding-left: 20px; font-size: 14.5px; line-height: 1.7; color: var(--ink); }
.note-list li { margin: 2px 0; }
/* 图片:等比缩到卡片宽,圆角与卡片一致;点开走全站预览层(data-prev) */
.note-img { display: block; }
.note-img img {
max-width: 100%; max-height: 420px; border-radius: var(--radius-s);
border: 1px solid var(--line); display: block;
}
.note-more { text-align: center; margin: 16px 0 8px; }
/* ---- 编辑页:分块 ---- */
.note-form .card { margin-bottom: 12px; }
.note-block-head { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; }
.note-block-head .cinema-h2 { margin: 0; flex: 1 1 auto; }
.note-add { display: flex; gap: 6px; flex: 0 0 auto; }
.note-blocks { display: flex; flex-direction: column; gap: 8px; }
.note-block {
display: flex; gap: 8px; align-items: flex-start;
border: 1px dashed var(--line); border-radius: var(--radius-s);
padding: 8px; background: #FDFCFA;
}
.note-block:focus-within { border-color: var(--brand-2); border-style: solid; }
.note-block-ops { display: flex; flex-direction: column; gap: 4px; flex: 0 0 auto; }
.nb-btn {
width: 24px; height: 22px; line-height: 1; padding: 0;
border: 1px solid var(--line); border-radius: 5px; background: #fff;
color: var(--muted); cursor: pointer; font-size: 12px;
}
.nb-btn:hover { border-color: var(--brand-2); color: var(--brand-ink); }
.nb-del:hover { border-color: var(--red); color: var(--red); }
.note-block-body { flex: 1 1 auto; min-width: 0; }
.note-block-ta { width: 100%; resize: vertical; font-size: 14.5px; line-height: 1.7; }
.note-block-img {
max-width: 100%; max-height: 320px; border-radius: var(--radius-s);
border: 1px solid var(--line); display: block; margin-bottom: 6px;
}
/* 还没选图时:不要显示破图框(那是 <img src=""> 的默认表现,很难看) */
.note-block-img.is-empty { display: none; }
.note-img-pick {
padding: 4px 10px; border: 1px solid var(--line); border-radius: var(--radius-s);
background: #fff; color: var(--muted); cursor: pointer; font-size: 12.5px;
}
.note-img-pick:hover { border-color: var(--brand-2); color: var(--brand-ink); }
.note-share-toggle {
display: flex; align-items: center; gap: 8px; margin-top: 12px;
font-size: 13.5px; color: var(--muted); cursor: pointer;
}
.note-share-toggle input { width: 16px; height: 16px; accent-color: var(--brand); }
.note-actions { display: flex; gap: 8px; margin: 4px 0 20px; }
/* ---- 窄屏:标签云折到上面,卡片满宽 ---- */
@media (max-width: 760px) {
.note-layout { grid-template-columns: 1fr; }
.note-side { position: static; }
.note-side .card { margin-bottom: 12px; }
/* 长英文/URL 不要把卡片撑出横向滚动(全站反复栽在这条上) */
.note-text, .note-list, .note-title { overflow-wrap: anywhere; }
}