Files
FamilyBook/assets/css/app.css
T

7007 lines
384 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;
/* 🔴🔴 v94(用户 2026-10-25):必填项的标记方式从「标题后面加红星星号」改成
「标题本身**红色 + 加粗**」——用户原话:
「必填项现在的做法是在后面加一个红色的星,但这样排版有时候不好看,对不齐。
把所有必填项都改一下,改成红色加粗字体……一看前面的标题,
只要是红色加粗的字体,就知道这是必填项。」
⇒ 需要两个变量:一个"必填红"、一个"必填字重"。
⚠️ **为什么单独立一个 `--req` 而不是直接用 `--red`**:
`--red` (#C0533F) 是**动作红**(删除按钮的底色),它当文字色用在这个
暖纸底 (#F7F3EA) 上对比度只有 ≈ 3.4:1 —— 13-14px 的标签字号下偏浅。
这里取深一档的 #B03A2E(≈ 4.6:1),既明显是红,又能和正文墨色同等清晰。
**改这个值会全站必填标签一起变。**
⚠️🔴 `--req-w` **必须是 700,不能写 650/600**:本站标签的基线字重
本来就是 600(`.form-grid > .field > label`、`.card .field > label`、
`.mv-row > .mv-lb`、`.dt-range-label` 四处都是 600)。写 650 的话
浏览器只会取到最近的真字重 700 或合成加粗 —— 一旦落到 600 就等于
**完全没加粗**,用户看到的只剩"变红",而他明确说的是「红色加粗」。
700 与 600 的差在 15px 中文黑体上是能一眼分出来的。 */
--req: #B03A2E;
--req-w: 700;
/* 🔴 v40(用户 2026-10-10):「影院管理部分…编辑使用蓝色的背景,删除使用红色的背景。」
⇒ 新增一个中性的"操作蓝"(不是链接蓝、也不是模块品牌色)—— 它是**语义色**:
蓝=编辑、红=删除,全站统一,跟着模块换肤也不该变。
⚠️ 暖纸底上要够暗才压得住白字:这里取 #2F6FB0(对比度 ≈ 4.9:1)。 */
--blue: #2F6FB0;
/* 🔴 v41(用户 2026-10-10):旅程详情页四种动作按钮各一种底色
(计划=蓝 / 实际=绿 / 编辑=黄 / 删除=红)。蓝、绿(--green)、红已有;
这里补"编辑黄"。
⚠️ 纯黄(#FFD400 之类)配白字几乎看不清(对比度 ≈ 1.3:1)。
取偏琥珀的深黄 #B8860B(DarkGoldenrod 系),暖纸底上白字对比度 ≈ 3.6:1,
且不会和品牌暖棕 (`--brand` #A84A28) 混淆 —— 后者明显更红。
⚠️ "实际=绿"用既有的 `--green` (#6F9E5C),它在白字下对比度 ≈ 3.0:1,
深一步更保险 —— 见 `.btn-actual` 里单独取的 #4E7C3F(≈ 4.6:1)。 */
--yellow: #B8860B;
--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-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); }
/* 🔴 v72(用户 2026-10-21)—— 这里原有一段 `.cal-set-books / .cal-set-book* /
.cal-set-page / .cal-set-form|block|foot|links` 的样式,随 `calendar/display`
整页删除(显隐与管理合并进 `calendar/calendars`)**全部失去了使用者**,
已在本轮清掉。留着的代价不只是"没用":
· 其中 `.cal-set-page .cal-set-books { max-height:none; overflow-y:visible; }`
曾被 v51 的反向验证当成"守内滚动"的锚点 —— 规则在、页面没了 ⇒
注入照旧命中文本、页面照旧全绿 ⇒ **那条反向验证变成空转**,
在输出上却看起来"这条断言很结实"(实测踩中,见 `_b51_falsify.py` 注释)。
· ⇐ 教训:**断言只能锚"当前真有使用者"的规则**;删模板时必须同步删死 CSS,
否则验证脚本会静默退化成摆设。
现在"日历本清单不套内滚动"这件事由 `.cal-books` 守(它才是页面上真实的清单容器)。 */
/* ============================================================================
v72(用户 2026-10-21):日历本「显隐 + 管理」合并成一张清单
----------------------------------------------------------------------------
用户原话:「日历设置里面日历本显示和日历管理可以放在一起啊。就是把日历本全都
列出来,然后显示不显示,加个复选框就行了。另外把下面别人共享给我的
也提到上面来,把所有的日历都放在一块,只不过在后面标注出来,
这个是别人谁谁谁共享给我的?是只读的还是可写的,什么的就可以了呀。」
⇒ 每行从左到右读下来就是一整句答案:
[✓] ■ 名字 [谁·只读/可写] 共享 导出 编辑 删除
⚠️ 用 grid 而不是 flex:归属那一列要**对齐**(否则每行标签长短不一时,
后面的操作按钮会跟着左右乱跳,看着很脏)。
⚠️ 窄屏(<640px)改成两行:第一行勾选+色块+名字+归属,第二行操作。
44 行日历本在手机上横着排会挤成碎片(用户可能在手机浏览器里管日历)。
========================================================================== */
.cal-books { display: flex; flex-direction: column; gap: 2px; }
/* 分组分隔线:从"我的"切到"共享给我的" */
.cal-books-sep {
display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap;
margin: 14px 0 6px; padding-top: 12px;
border-top: 1px solid var(--line);
font-size: 13px; font-weight: 600; color: var(--muted);
}
.cal-books-sep-note { font-size: 12px; font-weight: 400; color: var(--faint); }
.cal-book-row {
display: grid;
grid-template-columns: 22px 30px minmax(0, 1fr) auto auto;
align-items: center; gap: 10px;
padding: 8px 6px; border-radius: 8px;
}
.cal-book-row:hover { background: var(--line-2); }
/* 关掉的本子整行变淡 —— 一眼看出"这本现在不显示",
但**不 disable**:点一下就能重新勾上(disable 会让人以为"这行坏了") */
.cal-book-row.is-off .cal-book-name,
.cal-book-row.is-off .cal-book-dot,
.cal-book-row.is-off .cp-trigger-dot { opacity: .45; }
.cal-book-show { display: inline-flex; align-items: center; cursor: pointer; }
.cal-book-show input { accent-color: var(--brand); width: 16px; height: 16px; cursor: pointer; }
.cal-book-dot {
display: inline-block; width: 13px; height: 13px; border-radius: 3px;
border: 1px solid var(--line); justify-self: center; flex: 0 0 auto;
}
.cal-book-name { font-size: 14px; font-weight: 500; overflow: hidden; text-overflow: ellipsis; }
.cal-book-tags { display: inline-flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.cal-owner { font-size: 12.5px; color: var(--muted); white-space: nowrap; }
.node-badge-quiet { opacity: .7; }
/* 可写 / 只读两个徽标要**一眼能分**:可写用品牌色,只读用中性灰。
⚠️ 别用"同色深浅"区分 —— 用户要的正是在列表里快速扫出"哪些我能动"。 */
.node-badge.is-write { background: var(--brand-soft, rgba(74, 124, 130, .12)); color: var(--brand-ink, var(--brand)); }
.node-badge.is-read { background: var(--line-2); color: var(--muted); }
.cal-book-acts { display: inline-flex; align-items: center; gap: 12px; flex-wrap: wrap; justify-content: flex-end; }
.cal-book-acts .link-quiet { background: none; border: 0; padding: 0; cursor: pointer; font: inherit; font-size: 13px; }
.cal-book-acts .link-quiet:hover { color: var(--brand); text-decoration: underline; }
.cal-book-acts .link-danger:hover { color: var(--danger, #C0533F); }
.cal-book-acts .inline-form { display: inline; margin: 0; }
/* 已共享给谁:第二行,靠名字下方 */
.cal-book-shares {
grid-column: 3 / -1; display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
margin-top: 4px; font-size: 12.5px; color: var(--muted);
}
.cal-book-shares-label { color: var(--faint); }
.cal-book-shares .inline-form { display: inline; margin: 0; }
.cal-book-shares .link-quiet { background: none; border: 0; padding: 0; cursor: pointer; font: inherit; font-size: 12.5px; }
.cal-book-shares .link-danger:hover { color: var(--danger, #C0533F); }
@media (max-width: 640px) {
/* 窄屏:勾选+色块+名字+归属 一行,操作另起一行靠右 */
.cal-book-row { grid-template-columns: 22px 26px minmax(0, 1fr); row-gap: 6px; }
.cal-book-tags { grid-column: 3 / -1; }
.cal-book-acts { grid-column: 1 / -1; justify-content: flex-start; padding-left: 2px; }
.cal-book-shares { grid-column: 1 / -1; }
}
/* ============================================================================
v72:内置调色器(替代 `<input type="color">`,不再弹 Windows 系统取色框)
----------------------------------------------------------------------------
用户原话:「选颜色那个地方点了之后是弹出 Windows 的一个颜色选择框,这个不好,
我们把它改成内置的,就是就是在网页上弹出一个模态框或者一个什么样的,
然后选颜色,不要弹 Windows 的,那个一个体验也不好,另外一个也不好看。」
========================================================================== */
.cp-field { display: inline-flex; align-items: center; }
/* 触发按钮:小色块 + hex 值,长得像"一个可点的胶囊"而不是原生控件 */
.cp-trigger {
display: inline-flex; align-items: center; gap: 7px;
padding: 3px 9px 3px 4px; border: 1px solid var(--line); border-radius: 999px;
background: var(--surface); cursor: pointer; font: inherit; color: var(--ink);
transition: border-color .15s, box-shadow .15s;
}
.cp-trigger:hover { border-color: var(--brand); }
.cp-trigger:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
.cp-trigger-dot {
width: 18px; height: 18px; border-radius: 50%; flex: 0 0 auto;
border: 1px solid rgba(0, 0, 0, .12);
}
.cp-trigger-hex {
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
font-size: 11.5px; color: var(--muted); letter-spacing: .3px;
}
/* 行内(日历本清单里)那个调色器更紧凑一点,别把行撑高 */
.cal-book-row .cp-trigger { padding: 2px 4px; border-color: transparent; background: transparent; }
.cal-book-row .cp-trigger:hover { border-color: var(--line); background: var(--surface); }
.cal-book-row .cp-trigger-hex { display: none; }
.cal-book-row .cp-trigger-dot { width: 15px; height: 15px; }
/* 模态框:与 .cal-modal 同一套"遮罩 + 居中面板",但独立命名
—— 它可能在**没有日历**的页面上也用(影院管理等),别跟日历耦合。 */
.cp-modal {
position: fixed; inset: 0; z-index: 2000;
display: flex; align-items: center; justify-content: center;
padding: 16px; background: var(--mask, rgba(20, 24, 26, .42));
}
.cp-modal[hidden] { display: none; }
.cp-modal-panel {
width: min(420px, 100%); max-height: calc(100vh - 48px);
display: flex; flex-direction: column;
background: var(--surface); border-radius: 14px;
box-shadow: 0 18px 48px rgba(0, 0, 0, .22);
}
.cp-modal-head {
display: flex; align-items: center; justify-content: space-between;
padding: 14px 16px 10px; font-size: 15px;
}
.cp-modal-body { padding: 0 16px 4px; overflow-y: auto; }
.cp-modal-foot {
display: flex; gap: 10px; justify-content: flex-end;
padding: 12px 16px 16px;
}
/* 大预览块:让用户看到"日程条上白字压在这个颜色上"的真实效果 */
.cp-preview {
display: flex; align-items: center; justify-content: space-between;
height: 62px; padding: 0 14px; border-radius: 10px; background: #4A7C82;
color: #fff; transition: background .12s;
}
.cp-preview-chip { font-size: 13.5px; font-weight: 600; }
.cp-preview-hex {
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
font-size: 12px; opacity: .8;
}
.cp-swatches {
display: grid; grid-template-columns: repeat(8, 1fr);
gap: 8px; margin: 14px 0 12px;
}
.cp-swatch {
aspect-ratio: 1 / 1; border-radius: 8px; border: 2px solid transparent;
cursor: pointer; padding: 0; transition: transform .12s, border-color .12s;
}
.cp-swatch:hover { transform: scale(1.08); }
/* 选中态:白环 + 深色外环,任何底色上都看得见(只用深色环时,
深色块上的环就"消失"了 —— 那正是"点了没反应"的观感来源之一) */
.cp-swatch.active {
border-color: var(--surface);
box-shadow: 0 0 0 2px var(--ink);
}
.cp-swatch:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
.cp-custom { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; padding-bottom: 8px; }
.cp-custom label { font-size: 13px; color: var(--muted); }
.cp-hash { font-family: ui-monospace, monospace; color: var(--faint); }
.cp-hex-input { width: 96px; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; text-transform: uppercase; }
.cp-hex-input.is-bad { border-color: var(--danger, #C0533F); }
.cp-bad { font-size: 12px; color: var(--danger, #C0533F); }
/* 改色成功/失败的小浮条 */
.cp-toast {
position: fixed; left: 50%; bottom: 28px; transform: translateX(-50%);
z-index: 2100; padding: 9px 16px; border-radius: 999px;
background: var(--ink); color: var(--surface); font-size: 13px;
box-shadow: 0 8px 24px rgba(0, 0, 0, .2);
transition: opacity .3s, transform .3s;
}
.cp-toast.is-err { background: var(--danger, #C0533F); }
.cp-toast.is-out { opacity: 0; transform: translateX(-50%) translateY(6px); }
/* 模态框开着时锁住背景滚动(否则鼠标滚轮会滚到背后的长清单) */
body.cp-lock { overflow: hidden; }
/* 点了日历空白处就建日程(能力保留,见 templates/calendar/index.php 的 data-new-at)。
🔴 v48(用户 2026-10-14 第 3 条):「现在鼠标放在视图上时,变成了一个十字。
我感觉这个没必要,不用有这样的变化。」
⇒ **去掉十字光标**。原来给 `.rt-writable`(能写的人)挂 `cursor: crosshair`
是想"让人看出这里能点",但用户明确不要这个视觉变化。
⚠️ 只删光标、**不删能力**:点击建日程的 JS 与 `data-new-at` 一律不动
(那是第 2 条要修好、而不是要撤掉的东西)。
⚠️ 连 `.rt-writable` 类本身也留着 —— 它还被别处引用,且是"这个人能不能写"
这一状态在 DOM 上的标记位,删类名会牵连样式与断言。 */
.link-quiet { color: var(--muted); font-size: 14px; }
.link-quiet:hover { color: var(--brand); }
/* 长得像链接的**按钮**(v48)。`.link-quiet` 本来是给 <a>/<summary> 写的,
直接套在 <button> 上会带上浏览器的灰底/边框/内边距 ⇒ 与旁边的链接不一致。
这里把按钮的外观清成纯文字,只保留"可点"的观感(颜色/下划线走 .link-quiet)。 */
button.btn-inline {
background: none; border: 0; padding: 0; margin: 0;
font: inherit; line-height: inherit; cursor: pointer;
}
/* 顶栏里的文字一律不许换行 —— 一换行就是「首/页」「名/册」竖排,很难看 */
.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; }
/* ---- 🔴 v55(用户 2026-10-17 第 2 条):任务详情「状态选完即改」的**轻提示** ----
就地改完要给一句反馈(尤其循环任务会顺带排下一次,那句"下一次也排上了"不能丢)。
它**不是**页首那条 `.flash`(那是一整块、会撑开版面)——
这里是一个**浮在顶部**的小条:`position: fixed` 不占文档流,
所以"改状态"不会让页面跳动一下(那正是就地改要避免的)。
⚠️ 配色沿用上面 `.flash-item` 那两套(绿=成功 / 红=失败),保持全站一致。 */
.tk-flash {
position: fixed; top: 14px; left: 50%; transform: translateX(-50%);
z-index: 300; /* 高于自建弹框(.cal-modal 是 200) */
max-width: min(92vw, 460px);
padding: 9px 16px; border-radius: 999px;
font-size: 13.5px; line-height: 1.5; text-align: center;
box-shadow: 0 3px 14px rgba(0,0,0,.14);
background: #E6F0DC; color: #3D6B1F;
}
.tk-flash.is-err { background: #F8DDDA; color: #A1341F; }
.tk-flash[hidden] { display: none; }
.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)—— v37 重做
----------------------------------------------------------------------------
形态(用户 2026-10-09 第 4 次反馈点名要的):
日期 = 年 / 月 / 日 三个原生下拉;时间 = 时 : 分 两个原生下拉。
起止两端默认同一行,放不下时自动换到下一行(flex-wrap,见 .dt-cols)。
为什么把浏览器自带的 <input type=date> 换掉:
它的日历弹层是浏览器自己画的,我们碰不到 DOM、也控制不了流程;
Chrome 在"点顶部 2026年 10月 切到年月视图"之后要靠用户再点一次日期或
按 Esc 才回得到日期视图,各家还不一致 → 用户遇到的就是"选完年月就卡住、
选不了日"。三个原生下拉选完即生效,不需要任何确定按钮。
这一整段不再有"未升级就藏原生控件"的逻辑(v24~v36 有过)。
服务端给的就是最终形态,JS 只负责绑事件 —— 所以"JS 没跑 / 加载失败"时
控件依然完整可用,不是"骨架占位"。
============================================================================ */
.dt-input { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
/* 🔴 v44:内联标签(`_datetime.php` 的 `$dtLabel`)—— 让"日期范围:"与最新控件
**同一行**。宽度与观影式 `.mv-lb`(62px)一致,右对齐、冒号由 `::after` 给,
与「日历本」「标题」那几行的标签左缘对齐。
⚠️ 它是 `.dt-input` 的**兄弟节点**(不是子节点)—— 见 `_datetime.php` 里的说明:
放进去会被"收起原生端点"那条规则一起 clip 掉。选择器用 `+`(紧随其后)。 */
.dt-inline-lab {
flex: 0 0 auto; width: 62px; text-align: right;
font-size: 14px; font-weight: 600; color: var(--ink);
line-height: 1.4; word-break: keep-all;
}
/* ⚠️ 它同时带 `.mv-lb`(在 `.mv-row` 里时由那条规则给固定宽度与冒号);
这里再写一遍是为了**不在 `.mv-row` 里**(编辑整页那种两列排版)也成立。 */
.dt-inline-lab::after { content: ':'; font-weight: 400; color: var(--muted); }
.dt-inline-lab > i { font-style: normal; color: var(--red, #C0392B); margin-left: 1px; }
/* 范围组里带标签时:标签在前、控件在后,横向排(`.dt-range.dt-cols` 已是 flex row) */
.dt-range > .dt-inline-lab { flex: 0 0 62px; }
@media (max-width: 560px) {
.dt-inline-lab { flex: 0 0 52px; width: 52px; font-size: 13px; }
}
.dt-input .dt-native { display: contents; }
.dt-input .dt-semi { display: none; flex: 1 1 220px; min-width: 0; }
/* ---- 日期:年 / 月 / 日 三个下拉 ---- */
.dt-date-sel { display: flex; align-items: center; gap: 4px; min-width: 0; }
.dt-input .dt-year,
.dt-input .dt-month,
.dt-input .dt-day {
padding: 9px 6px; border: 1px solid var(--line); border-radius: var(--radius-s);
background: var(--surface); color: var(--ink); font: inherit; font-size: 13.5px;
font-variant-numeric: tabular-nums; cursor: pointer; min-width: 0;
}
/* 年最宽(4 位数 + 箭头),月/日短一些 —— 省下来的宽度留给时:分 */
.dt-input .dt-year { flex: 0 1 86px; }
.dt-input .dt-month { flex: 0 1 66px; }
.dt-input .dt-day { flex: 0 1 64px; }
/* 隐藏的 `*_date` 真正的字段(提交契约)—— 它必须留在 DOM 里 */
.dt-input input.dt-date[type="hidden"] { display: none; }
/* 旧「原生 date 框」形态($dtDateType='native' 时才出现)保留样式 */
.dt-input .dt-date:not([type="hidden"]) {
flex: 1 1 150px; min-width: 0; cursor: pointer;
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:not([type="hidden"])::-webkit-calendar-picker-indicator { cursor: pointer; opacity: .65; }
/* ---- 时间:时 : 分 ---- */
.dt-time { display: flex; align-items: center; gap: 3px; flex: 0 0 auto; }
.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-size: 13.5px;
font-variant-numeric: tabular-nums; cursor: pointer;
}
.dt-input .dt-colon { color: var(--muted); font-weight: 600; }
/* 必填但没填时描红(由 app.js 的 guardRequired 打类) */
.dt-input.dt-missing .dt-year,
.dt-input.dt-missing .dt-month,
.dt-input.dt-missing .dt-day,
.dt-input.dt-missing .dt-date { border-color: #C0392B !important; }
/* ============================================================================
起止范围(.dt-range):两端一行优先,放不下才换行
============================================================================
用户原话(2026-10-09 第 4 次反馈):
现在开始和结束时间分在了两行,导致日期这个特别特别的长,后面的时和分又
非常非常的短,非常不协调。所以这两个日期如果能放一排,如果能放得下的话,
就应该放一排。因为日期和时间,分和时和分的这个长度基本上是固定的,
所以你可以判断一下它到底能不能一行,能不能放得下。如果能放下的话就放在一行。
如果在手机上,比如说它这个页面非常窄,它就可以把它挤到下一行。
但是现在不要让它默认显示在两行上,应该默认显示在一行上才对。
⇒ 做法:flex 容器 + flex-wrap。每端有一个最小的可用宽度(.dt-part 的
min-width),浏览器在每次布局时自己判断放不放得下;放得下 → 一行;
放不下 → 第二端自然掉到下一行。
⚠️ 不用 JS 量宽度:字体没加载完 / 容器动画中都会量错,窗口缩放还要重算;
flex-wrap 是浏览器布局的一部分,永远是对的。
⚠️ .dt-range 的标签只在组上留一个(见 _event_fields.php 的说明):
两端是同一个范围的两个端点,各挂一个标签会变成"看着像两个字段"的错觉。 */
.dt-range { display: flex; flex-direction: column; gap: 8px; }
.dt-range-label { font-size: 14px; color: var(--ink); font-weight: 600; }
/* 升级(= app.js 挂上 .dt-cols)之后:两端并排、必要才换行 */
.dt-range.dt-cols {
flex-direction: row; flex-wrap: wrap; align-items: center;
gap: 10px 18px;
}
/* 两端的"最小可用宽度":日期(年+月+日) + 时:分 摆得下。
值取 250px —— 实测"年(86)+月(66)+日(64)+时:分(约 96)+间距"约 330px 才舒服,
但最小可用是 250px(会各自压缩一点),这样在常见的 560px+ 容器里
两端(250×2 + 18 间距 = 518)肯定放得下 → 默认就是一行。 */
.dt-range.dt-cols > .dt-input,
.dt-range.dt-cols > .field { flex: 1 1 250px; min-width: 250px; display: flex; }
.dt-range.dt-cols > .field { flex-direction: column; gap: 6px; }
/* 标签占满整行(不然它会跟第一端挤在一起) */
.dt-range.dt-cols > .dt-range-label { flex: 1 1 100%; }
/* 极窄屏:两端各自铺满一行(用户说的"手机上就挤到下一行") */
@media (max-width: 560px) {
.dt-range.dt-cols > .dt-input,
.dt-range.dt-cols > .field { flex: 1 1 100%; min-width: 0; }
}
/* ============================================================================
Semi 仅剩的用途:工作经历的「年月选择」(<input type="month"> 的升级)
----------------------------------------------------------------------------
⚠️ 日期/时间控件(.dt-input)v37 起已完全不用 Semi —— 上面那套自建下拉
就是因为它给的弹层交互不可控(选完年月卡住)。这里保留的是另一处。
============================================================================ */
.dt-semi.dt-semi-month { display: none; }
.dt-semi.dt-semi-month.is-live { display: block; width: 100%; }
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);
}
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);
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;
}
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); }
/* 「以哪个生日为准」的两块:被选中的那块描一圈,一眼看出填的是哪边 */
.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,那会一起藏掉)
🔴 v29 用户第 2 条:「任务完成复选框的提示文字还在」——
真根因:`_task_check.php` 与任务看板用的是 `class="visually-hidden"`,
而**本文件里从来没有这个类**(项目一直只定义 `.sr-only`)。
CSS 里查不到的类 = 没有任何样式 → 那段文字是**普通 inline span,直接显示出来**,
就贴在复选框旁边(用户看到的就是「标记完成:待办」)。
⇒ 修法:把两个名字指向同一条规则(两种写法都认)。
为什么不是"把模板改成 sr-only 了事":这个类名在下游(别的模板、以后新写的)
同样可能被用到,名字不再是隐患;而只改调用点的话,下次谁再写
`visually-hidden` 就又漏了。这里是"给能力补上定义",不是"给某一处打补丁"。*/
.sr-only,
.visually-hidden {
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; }
/* ---- 🔴 v63(用户 2026-10-19 第 5 条):公共标签的颜色 ----
一行 = 序号 · 色块 · 名字 · N人 · ▲▼ · 编辑。 */
.pg-dot {
flex: 0 0 auto; width: 12px; height: 12px; border-radius: 50%;
box-shadow: inset 0 0 0 1px rgba(0,0,0,.12); /* 浅色底上也要看得见边界 */
}
/* 色板(弹框里):点一下就选。⚠️ 选中态用 `is-on` 描一圈,
不与 `.is-on` 的通用含义冲突(本页没有别的 .is-on)。 */
.pg-swatches { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-top: 6px; }
.pg-sw {
width: 26px; height: 26px; border-radius: 7px; cursor: pointer;
border: 2px solid transparent; padding: 0;
box-shadow: inset 0 0 0 1px rgba(0,0,0,.12);
}
.pg-sw.is-on { border-color: var(--ink); }
.pg-sw:hover { transform: scale(1.06); }
/* 自定义取色器:与色块同尺寸,外观上就是色板里的第 13 格 */
.pg-sw-custom {
width: 26px; height: 26px; border-radius: 7px; overflow: hidden; cursor: pointer;
box-shadow: inset 0 0 0 1px rgba(0,0,0,.12);
display: inline-flex;
}
.pg-sw-custom input[type="color"] {
width: 40px; height: 40px; margin: -7px; border: 0; padding: 0; cursor: pointer; background: none;
}
/* 新建时那个颜色下拉(窄屏缩一半,别把"加一个"按钮挤到下一行) */
.pg-swatch-pick { flex: 0 0 auto; max-width: 96px; }
/* 弹框底栏:删除在左、取消/完成在右 —— 与"危险动作离确认按钮远一点"的通行做法一致 */
.rm-foot-gap { flex: 1 1 auto; }
/* ---------------- 个人设置:关系分类管理 ---------------- */
.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); }
/* 层 4:**实心**底色按钮(v40 用户要求)。
原话(2026-10-10):「删除按钮就用红色背景颜色…影院管理部分…编辑使用蓝色的背景,
删除使用红色的背景。」
⇒ 这两个要的是**实心色块**(一眼分得清),而不是层 3 那种淡色描边。
⚠️ 为什么不直接改 `.btn-danger`:全站十来处「删除」都在用它,改成实心会到处变样;
而且**页头动作区的层 1**(`.actions > form > button.btn`,特异性 0,4,1)
会盖过基类 `.btn-del`(0,1,0)—— 必须在同一层里再写一条才生效(与层 3 同一个坑)。 */
.page-head .actions > .btn-del:not(.btn-icon),
.page-head .actions > a.btn-del:not(.btn-icon),
.page-head .actions > form > .btn-del:not(.btn-icon),
.page-head .actions > form > button.btn-del:not(.btn-icon) {
background: var(--red); color: #fff; border-color: transparent;
}
.page-head .actions > .btn-edit:not(.btn-icon),
.page-head .actions > a.btn-edit:not(.btn-icon),
.page-head .actions > form > .btn-edit:not(.btn-icon),
.page-head .actions > form > button.btn-edit:not(.btn-icon) {
background: var(--blue); color: #fff; border-color: transparent;
}
/* 「设置」图标按钮:**唯一无色的**(白底描边、方形),与上面所有彩色按钮区分。
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 {
/* 🔴🔴 v43(用户 2026-10-11 第 3 条):「新建行程的时候,如果把地图拖进去,
它不是开始裁剪吗?但是这个弹出的框把裁剪那个框又盖住了,裁剪的框应该放到前面来。」
真根因:裁剪层是 **100**,而行程/观影那些弹框用的 `.cal-modal` 是 **200**
⇒ 从弹框里拖图触发的裁剪层被整块盖在下面 —— 看不见、也点不到。
(与 v41 那个「新建影院弹框被记一场电影盖住」是**同一个坑**,当时把 .cn-modal
提到了 260;这里漏了。)
⇒ 提到 **260**:高于所有自建弹框(.cal-modal 200 / .pp-modal 等),
与 .cn-modal 同层;低于灯箱(300)、预览层(320) —— 那两者是"看大图"的顶级层。 */
display: flex; position: fixed; inset: 0; z-index: 260;
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; }
/* 🔴 工单 #20:首页「近期任务」的空态提示是**一直渲染、靠 hidden 切换**的
(服务端有任务时给它 `hidden`,前端把最后一条删掉后再显示出来)。
⚠️ 浏览器默认的 `[hidden]{display:none}` 优先级最低 —— 只要哪天有人给
`.empty-hint` 加上 `display:flex` 之类,这句提示就会在"有任务时"也冒出来。
所以在这里显式再声明一次(与上面第 601 行那条注释是同一个约定)。 */
.empty-hint[hidden] { display: none !important; }
.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, filter .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; }
/* 🔴🔴 v41(用户 2026-10-10):「观影照片的左右按钮,鼠标悬停之后位置会变一下,
一不小心就会点错。」
真根因:文末「全站悬停统一」段给 `.lb-btn:hover` 加了
`transform: translateY(var(--hov-lift))`(= 上移 1px 的"轻抬升"手感)——
而 `.lb-nav` 是靠 `transform: translateY(-50%)` 做**垂直居中**的,
hover 那条**把居中的 transform 整个覆盖掉** ⇒ 鼠标一碰,按钮从"居中"跳到
"距顶 50%",**位移接近半个按钮高度**(48px 的一半),确实会点错。
⚠️ 与 v21 家谱那次 `.f3 div.card:hover > div` 覆盖库的居中 transform 是**同一个坑**。
⇒ 修:`.lb-nav:hover` 显式把它自己的居中 transform 写回去(只保留提亮,
不再抬升)。位置在 hover 前后**一字不动**,鼠标停在哪就点在哪。
⚠️ 必须写在统一 hover 段**之后**(同特异性下后者胜)—— 见文末那段里的同名规则。 */
.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; }
/* 🔴 v70(用户 2026-10-20 第 2 条):「日历导入进去了,但是不显示」。
原来这里是 `.cal-more` 一颗**纯文本 <span>** —— 点不动,于是被它折叠掉的条目
(用户自己导入的日程全在里面)**永远看不到**。
现在改成真正的按钮:可点、有热区、有 hover 反馈,点开是"当天全部条目"浮层。 */
.cal-more {
display: block; width: 100%; margin: 0; padding: 1px 4px;
border: 0; border-radius: 5px; background: transparent;
font: inherit; font-size: 11px; line-height: 1.5; text-align: left;
color: var(--faint); cursor: pointer;
}
.cal-more:hover { background: var(--hov-veil); color: var(--brand); }
.cal-more:focus-visible { outline: 2px solid var(--brand); outline-offset: 1px; }
/* ---- 当天全部条目浮层(v70)----
⚠️ 复用 `.cal-modal` 那一套(遮罩 / 居中面板 / 头尾不滚动),
只补列表本身。为什么还要独立一份而不是套用条目详情层:
那是"一条"的详情,这是"一天"的清单 —— 两者可以叠用(清单 → 某条详情)。 */
.cal-day-modal .cal-modal-panel { width: min(460px, 100%); max-height: calc(100vh - 80px); }
.cal-day-body { padding: 8px 10px 10px; }
.cal-day-item {
display: flex; align-items: baseline; gap: 10px; width: 100%;
padding: 9px 10px; margin: 0; border: 0; border-radius: 8px;
background: transparent; font: inherit; text-align: left; cursor: pointer;
color: var(--ink);
}
.cal-day-item + .cal-day-item { margin-top: 2px; }
.cal-day-item:hover { background: var(--hov-veil); }
.cal-day-when {
flex: 0 0 auto; min-width: 74px; font-size: 12px; color: var(--muted);
font-variant-numeric: tabular-nums;
}
.cal-day-name { flex: 1 1 auto; font-size: 13.5px; line-height: 1.45; word-break: break-word; }
/* 派生条目(农历/节气/节假日)略淡:它们天天都有、信息密度低,
视觉上让位给用户自己填的日程 */
.cal-day-item.is-derived .cal-day-name { color: var(--muted); }
/* 底栏三样:左「加一条」、中提示、右「在日视图里看」。
名单长的时候底栏会被挤 → 允许换行,别让 HarfBuzz 把按钮压扁。 */
.cal-day-foot { flex-wrap: wrap; gap: 8px; }
.cal-day-foot .cal-modal-tip { flex: 1 1 auto; }
.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; }
/* 🔴 v54(用户 2026-10-16 第 1 条):「首页**近期任务**那个地方要注意截断:
如果任务标题太长,会把这个任务**挤成两行**。」
真根因:这一条只有 `flex:1 1 auto` + `min-width:0` —— 它能**收缩**,
但行内文字**没有禁止换行** ⇒ 收缩之后标题在框内绕成第二行,把这一行撑高、
把旁边的状态/看板徽标挤到下一行。缺的正是这三句。
⚠️ 三句缺一不可:`nowrap` 让它不绕行、`hidden` 让溢出被裁、
`ellipsis` 给出"后面还有"的信号(不然会像被截断的错字)。
⚠️ 全名走 `title=` 悬停(模板里加的),点开也会弹详情浮层 —— 短的看不全不是问题。
⚠️ 这条**同时**管「往年的今天」那一块(同一个 class):那边的标题也可能长,
一并对齐成"一行 + 省略号"比一处换行一处截断更整齐。 */
.dash-what {
flex: 1 1 auto; min-width: 0; font-size: 14px;
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.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; }
/* 🔴 v39(用户 2026-10-10):「影院那个地方应该有一个搜索的功能,下拉的地方上面
有一个搜索筛选的功能。」
⇒ 弹出列表顶部一条搜索框。⚠️ 它要**吸顶**(列表长时往下滚,它跟着走)——
不然滚两下搜索框就没了,"搜索筛选"等于白加。 */
.cs-search {
position: sticky; top: -4px; z-index: 2;
margin: -4px -4px 4px; padding: 6px 6px 6px;
background: var(--surface); border-bottom: 1px solid var(--line-2);
}
.cs-search-input {
width: 100%; box-sizing: border-box;
padding: 7px 10px; font: inherit; font-size: 13px; color: var(--ink);
background: var(--paper, #FDFBF7); border: 1px solid var(--line);
border-radius: 7px;
}
.cs-search-input::placeholder { color: var(--muted); }
.cs-search-input:focus { outline: none; border-color: var(--brand); }
/* 没有匹配项时的提示(没搜过 / 有结果时都 hidden,不占高度) */
.cs-empty { padding: 10px 12px; font-size: 13px; color: var(--muted); text-align: center; }
.cs-empty[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;
}
/* ⚠️ 过滤用的 `hidden` 必须写出来:`.cs-item` 自己声明了 `display:flex`,
它会**盖过**浏览器默认的 `[hidden]{display:none}` ——
这正是第 11 批"选人过滤一直是坏的"的同一个坑,别再踩第二次。 */
.cs-item[hidden] { display: none; }
.cs-item:hover, .cs-item:focus { background: var(--line-2); outline: none; }
.cs-item.is-disabled { opacity: .5; cursor: not-allowed; }
.cs-item.is-disabled:hover { background: transparent; }
/* ---- 🔴 v44:日程「日历本」下拉 ----
用户 2026-10-12 第 1 条:「把颜色放在下拉框里面,就跟电影的影院选择一样,
让日历本这三个字和下拉框在一条线上。」
⇒ 复用观影那套 `.cs`(按钮 + 色点 + 弹出列表 + 搜索),只补两点:
① 这个下拉在 `.mv-row` 里,宽度由 `> *:not(.mv-lb)` 那条 `flex:1` 接管,够用;
② **无 JS 兜底**:那个原生 `<select id="ce-cal">` 只做视觉收起 ——
用 `position:absolute; width:1px`(**不是 display:none**,保留可聚焦、可提交),
与 `_datetime.php` 的"隐藏 date 当值载体"同一套思路。
⚠️ 断言里按 `#ce-cal` 取 option / 值的写法必须继续成立 —— 它在 DOM 里。 */
.cal-book-cs { width: 100%; }
.cal-book-native {
position: absolute; width: 1px; height: 1px;
overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap;
border: 0; padding: 0; margin: -1px;
}
/* 新建影院:居中弹框(与名册的动作弹框同一形态,只是更宽) */
.cn-modal {
/* 🔴🔴 v41(用户 2026-10-10):「观影模块,第一场电影,新建影院这个弹窗不显示,
被记一场电影这个弹窗盖住了。」
真根因:`.cn-modal` 是 **70**,而「记一场电影」用的 `.cal-modal` 是 **200** ——
影院弹框是**从观影弹框里**打开的子层,层级却更低 ⇒ 被整块盖住、看不见也点不到。
⇒ 提到 260:必须**高于 .cal-modal(200)**,也高于 antd 的下拉面板
(`.ant-picker-dropdown` 走 popper,默认 1050 之外的那一层我们没碰;
这里只需保证"压过所有自建弹框"即可)。 */
position: fixed; inset: 0; z-index: 260;
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; }
/* ⚠️ v30 第 4 条:`left` 与 `width` 现在由**内联样式**给出(冲突分列后每条不同),
所以这里**不能**再写 `left/right` —— 绝对定位下 left+right+width 三者同时存在是
"过约束",浏览器会丢掉 `right`(看着像是碰巧对)。留一个兜底 left:2px,
万一某条没带内联 left(老数据/兜底路径)也不至于跑到列外去。 */
.tg-block {
position: absolute; left: 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; }
/* ============================================================
跨天日程的分段(v88 / 工单 #35)
============================================================
用户:「如果一个日程跨了三天,应该在日历本上怎么显示呢?在周视图和日视图,
应该怎么显示?现在显示的有问题。」
做法:一个跨天日程在**每一列各画一段**(`build_time_grid` 算好每段的
top/height),视觉上要让它们看起来是**一条**,而不是三个互不相干的块。
为什么不做跨列绝对定位层:`.tg-body` 左边有 56px 固定 gutter,
窄屏还会横向滚动(`.cal-tg-card{overflow:auto}` + 每列 min-width 92px),
`.tg-col` 又是 `overflow:hidden`。跨列层要自己算偏移、跟滚动跑、还不能被裁。
在列内画段则天然对齐,代价只是段间有 1px 列边框缝 —— 用下面的样式糊上。
三段的关系:
· start(起始段):上圆下平 —— 下方要接中间段
· mid (中间段):上下都平 —— 两头都接
· end (结束段):上平下圆 —— 上方接中间段
⚠️ 内侧不画 border:`.tg-block` 原本有 1px 白边,段与段之间会多出两条白线,
看着像断开了。这里按段位取消对应的那条边。
⚠️ 左右内缩量要比单天块**小**(单天是 left:2px / width:-4px):
跨天段要尽量贴满整列,缝隙才窄,连起来才像一条。模板已给 1%/98%。 */
.tg-block.is-seg-start {
border-top-left-radius: 4px; border-top-right-radius: 4px;
border-bottom-left-radius: 0; border-bottom-right-radius: 0;
border-bottom-color: transparent; /* 下边不画,与中间段连上 */
padding-bottom: 0;
}
.tg-block.is-seg-mid {
border-radius: 0;
border-top-color: transparent;
border-bottom-color: transparent;
padding-top: 1px; padding-bottom: 0;
}
.tg-block.is-seg-end {
border-top-left-radius: 0; border-top-right-radius: 0;
border-bottom-left-radius: 4px; border-bottom-right-radius: 4px;
border-top-color: transparent; /* 上边不画,与中间段连上 */
padding-top: 1px;
}
/* 延续标记:这段不是从今天开始的,用一个小箭头代替开始时刻 */
.tg-cont {
display: inline-block; margin-right: 2px;
opacity: .75; font-size: 10px; line-height: 1;
}
/* 跨天段占满整列宽度,内边距跟着收一点,字才不会被挤 */
.tg-block[data-seg] {
padding-left: 4px; padding-right: 4px;
/* 🔴 v88:跨天段用 left:0 / width:100%(模板按段区分),
必须配 box-sizing:border-box,否则 padding 会把宽度撑出列外。
`.tg-block` 已有 box-sizing:border-box,这里显式再写一次是为了防止
以后有人给单天块改 box-sizing 时顺手把这条也改掉。 */
box-sizing: border-box;
}
/* 天视图只有一列,把列拉宽好读;周视图 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); }
/* 🔴🔴 v37:修正面板必须**浮**在按钮列之外,否则它只有 ~22px 宽、完全没法填。
真机实测(1400px 视口、品心 1080px):
.dt-range 宽 **22px**、`.nf-grid` 宽 **22px** —— 整张修正表单被压成一条线。
根因:`.node-ops` 是"一排小按钮"的容器
`flex: 0 0 auto; justify-content: flex-end;`(见上面),
而「修正」是 `<details class="rev-open">`:**展开的内容也长在这个按钮列里**
⇒ 列宽只有"修正"两个字那么宽(22px),里面的表单跟着一起被压扁。
⚠️ 这不只是"不好看":日期控件在 22px 里根本点不到(用户报的就是这个)。
修法:面板 `position: absolute` 脱离按钮列的宽度约束,浮到卡片上。
· 宽度取 `min(720px, container 宽)` —— 720 刚好摆得下"两端各 304"(608+间距)。
· `right: 0` 让它从按钮列右缘向左展开,不会超出卡片右边。
· `z-index: 30` 盖在后面的行上面(它是临时展开的面板,本来就该在最上层)。
⚠️ 不动 templates/trips/show.php 的 DOM:
`.node-list`/`.node-row` 的 flex 结构一动就会牵动"节点列表"的排版
(那是清单页最核心的视觉),而这里只需要把它**浮出来**,纯 CSS 就够。 */
.rev-open { position: relative; }
.rev-panel {
position: absolute; right: 0; top: calc(100% + 6px); z-index: 30;
width: min(720px, calc(100vw - 48px));
max-height: min(76vh, 720px); overflow-y: auto;
box-shadow: var(--shadow-l, 0 12px 32px rgba(0, 0, 0, .16));
}
.rev-form .btn-row { margin-top: 10px; }
/* 🔴 v50(用户 2026-10-14 第 3 条):修正表单的按钮行**靠右**,并补一颗「取消」。
——「记录这次修正的按钮应该放在右边,另外应该加一个关闭/取消的按钮。」
做法:`margin-left:auto` 作用在**按钮行里第一颗会被渲染的按钮**上,把整排推到右端。
两类情况(有没有那句"已记过 N 次"的说明)各写一条 —— 只有第一颗儿子适合
`margin-left:auto`(用 `:first-child` 精确命中),否则中间那些也会各带一个 auto、
间距被拉散。
⚠️ 不用 `.cal-modal-foot .btn[type=submit]{margin-left:auto}` 那条(本表单不在模态里)。
⚠️ 窄屏允许换行,不然三样挤在一行会把按钮压变形。 */
.rev-form .rev-foot { align-items: center; flex-wrap: wrap; }
.rev-form .rev-foot > *:first-child { margin-left: auto; }
/* 修正历史对比表 */
.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));
}
/* 🔴🔴 v41 例外(必须写在上面那条**之后**,靠"同特异性、后者胜"生效):
`.lb-nav`(照片左右切换)是**靠 transform 居中**的,一旦被上面的
`translateY(var(--hov-lift))` 覆盖,hover 时按钮会从"居中"跳到"距顶 50%",
位移近半个按钮高 —— 用户报的就是这个("悬停之后位置会变一下,一不小心就会点错")。
⇒ 这里把它的居中 transform 显式写回,只保留提亮。位置前后一字不动。 */
.lb-nav:hover { transform: translateY(-50%); }
/* 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; }
/* 🔴 v44(用户 2026-10-12 第 2 条):重复那三行(结束条件 / 重复次数 / 重复到)
在观影式排版下是 `.mv-row`(`display:flex`)⇒ 同上面 `.field` 那个坑:
**作者样式优先于 `[hidden]` 的浏览器默认 `display:none`**,`hidden` 形同虚设。
⇒ 一并补上,否则"不重复时它们也显示着"。 */
.mv-row[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;
}
/* 🔴 v82(用户 2026-10-23):「在日程模块中,那个红线左侧是不是还有一个红点?
如果有,去掉。」—— 确实有,就是这条 `::before`。
⚠️ 首页日程那条红线 v44 已经删过一次红点(`.ag-now-dot`),用户当时说
「红点显示也不全,也不太好看。直接把红点去掉就行了,直接一根红线就可以了。」
⇒ 两处口径统一:**只有一根线,没有任何端点装饰**。
⚠️ 这条规则整条删掉,别只把 `content` 清空 —— 留个空规则在这儿,
将来一定有人照着它把圆点"补回来"。 */
/* 过午夜(或跨天翻页)时线要能"走出去",不要卡在格子边界上闪 */
.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; }
/* 🔴 v30 第 1 条:「选中文件后,那个文件输入框,得有点反应才对。」
非暂存框(导入型)选中文件后由 app.js 的 bindFileEcho() 加 `.is-picked`
并把框里的话换成文件名 —— 这里把它从"虚线待放"变成"实线已选",
配色也换成品牌色的浅底,一眼能看出"这一步已经做完了"。
⚠️ 与 `.is-over`(拖动经过的高亮)区分开:那个是**临时**高亮,
这个是**已选**的稳定态,两者同时命中时以 is-picked 为准(写在后面)。 */
.upbox.is-picked {
border-style: solid; border-color: var(--brand);
background: #FDF3EC; color: var(--brand-ink, var(--brand));
}
.upbox.is-picked .upbox-ico { font-size: 18px; font-weight: 600; }
.upbox.is-picked .upbox-msg { font-weight: 600; word-break: break-all; }
/* 窄屏:框再矮一点,别把表单撑太长 */
@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; }
/* 🔴 v37:`max-height` 必须把 `.cal-modal`(本面板的父层)自己的 `padding: 24px`
算进去 —— 只减 48 会让面板底部**刚好探出视口 4px**,
于是 `.cal-modal-foot` 里那两个按钮有半截在屏幕外(用户"看不到按钮"的另一半原因)。
⇒ 减 **64px** = 上下各 24px 的遮罩内边距 + 16px 余量。 */
.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 - 64px);
}
.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; }
/* 🔴🔴 v54(用户 2026-10-16 第 1 条)—— **这条才是"标题太长挤成两行"的真根因**。
症状两层,第一层在这里:
`.home-duo` 是**两列网格**(`1fr 1fr`),而 grid/flex 子项的默认
`min-width: auto` 意思是"我最小也得装下我的内容" ⇒ 只要右栏有一条**长标题**,
那一列就被顶到 1700+px(实测:`home-duo` 1032px,里面 `hd-pane` 1763px),
整个两列布局被撑破、横向溢出到 2089px。
⇒ 于是"截断"根本没机会发生:**容器自己先被撑爆了**,行宽 1721px,
`scrollWidth === clientWidth`,`text-overflow: ellipsis` 看起来"没生效"。
⚠️ `min-width: 0` 是让 grid/flex 子项**允许收缩到内容以下**的唯一开关;
缺它时,在子项里怎么写 nowrap/ellipsis 都没用(本项目第二次踩这个:
v53 的 `.mv-grid` 是同类,那次是 `grid-column`,这次是 `min-width`)。
⚠️ 加在**两个**网格项上(两个 pane 都要能收缩,不只是有任务的那个)。 */
.home-duo > * { min-width: 0; }
.hd-pane { display: flex; flex-direction: column; height: 440px; min-height: 0; min-width: 0; }
.hd-pane-head { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; flex: 0 0 auto; min-width: 0; }
.hd-pane-head .cinema-h2 { margin: 0; flex: 1 1 auto; min-width: 0; }
/* ⚠️ v54:列表链上每一层都要 `min-width:0`(中间任何一层没给,
收缩就断在那一层、下面的 nowrap 又白写)—— `.dash-list`/`.tk-list` 是 ul,
`.tk-row` 是 li,都是 flex 项。 */
.dash-list { min-width: 0; }
.tk-list { min-width: 0; }
.tk-row { min-width: 0; }
.hd-pane-body {
flex: 1 1 auto; min-height: 0; min-width: 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** 定位,
所以刻度数与日程块的比例天然一致(都在同一个容器里算)。
🔴🔴 v80(用户 2026-10-22):「首页,日程好像显示不全,右侧是缺一块的,
是不是左侧有刻度,导致你算宽度的时候,没算对?」
—— 用户猜的**完全正确**。真根因:
· `.ag-grid` 宽 481,左边 42px 刻度 + 8px 间距 = 50px 被刻度吃掉,
刻度线本身只有 431px 宽;
· 而色块内联写的是 `left: calc(46px + X%)`、`width: calc(Y% - 4px)`,
那个 **Y% 是按 481 算的,不是按 431** ⇒ 100% 时色块宽 477,
右端落到 744,比卡片右边界(700)**多出 44px**,被 `overflow:hidden`
整块裁掉 —— 用户看到的"右侧缺一块",缺的正是日历本名那一截。
· 日历页 `.tg-block` 没这个病:它左边刻度是**独立的一列**(`.tg-col`),
百分比天然相对列宽。首页把刻度塞在同一 flex 行里,才留下这个残差。
⇒ 从结构上对齐日历页:**刻度区与"车道区"分开**。
`.ag-lanes` 是一个绝对定位的层,左边界让出 `--ag-gutter`、右边留 2px,
色块放在它里面,百分比就相对"真正可用的宽度"算了。
⚠️ `--ag-gutter` 是唯一真值源:`--ag-gutter` = 刻度列宽 + 行内 gap。
以后改刻度宽度只改这一个变量,色块/红线/刻度会一起跟着动。 */
.ag-grid { position: relative; --ag-gutter: 50px; }
.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; }
/* 🔴 v80:车道层 —— 色块的定位参照系。左让刻度、右留 2px 视觉边,
于是 `.ag-ev` 里那些 `left: X%` / `width: calc(Y% - 4px)` 的百分比
相对的就是**真正可用的宽度**(不再是含刻度的整宽)。
⚠️ `pointer-events: none` + 子项 `auto`:这一层铺在刻度线上面,
不能挡住刻度行本身的点击/悬浮;色块自己再收回事件。 */
.ag-lanes {
position: absolute; left: var(--ag-gutter, 50px); right: 2px; top: 0; bottom: 0;
pointer-events: none;
}
.ag-lanes > * { pointer-events: auto; }
/* 当前时刻红线(用户原话「需要在当前的时间或者是时刻那里显示一条红线」)
🔴 v44(用户第 3 条):左端那颗小圆点**删掉**了 ——
用户:「这个红点显示也不全,也不太好看。直接把红点去掉就行了,直接一根红线就可以了。」
⇒ 直接一根 2px 红线;`.ag-now-dot` 这条规则随之删除(不再有那个元素)。
🔴🔴 v82(用户 2026-10-23):「首页上的日程,红色线不对,变短了左侧缺了一块。」
真根因 = **双重缩进**:v80 把色块挪进 `.ag-lanes`(这一层自己已经
`left: var(--ag-gutter)` 让出了刻度),红线在它**内部**却又写了一次
`left: var(--ag-gutter, 50px)` ⇒ 实际左沿 = 刻度宽 + 刻度宽 = 100px,
比车道左沿多出整整一份刻度宽(实测差 50.00px,正是 `--ag-gutter` 的值)。
⇒ 红线在车道内部,`left` 就必须是 `0`(相对车道),不能再吃 gutter。
⚠️ 别再"顺手写回去":`.ag-ev` 的百分比也在这层里算,只要给它们加过 gutter,
就是同一个 bug 再长一遍。外来元素要判断"我在谁里面"再决定 left。 */
.ag-now { position: absolute; left: 0; right: 0; height: 0; border-top: 2px solid var(--red); z-index: 2; }
/* 日程块 */
/* 🔴 v30 第 2 条:「首页上的今天的日程,用色块表示,和日程保持一致。」
⇒ 从原来的"白底卡片 + 左边 3px 色线"改成**与日历页 `.tg-block` 同款的实心色块**:
底色 = 日历本色(内联给)、白字、圆角、无边框阴影。
⚠️ `left` 与 `width` 由**内联样式**给出(冲突分列的结果,见 templates/home.php)——
这里不再写 left/right(绝对定位下 left+right+width 三者同时存在是过约束)。
⚠️ `transform: translateY(2px)` 那点微调去掉了:色块要精确落在刻度上,
偏移 2px 会让"9:00 的块"看起来不在 9:00 那条线上。
⚠️ 白字压浅色本的兜底:加一层极轻的 text-shadow(不加深色罩,保持本色)。
🔴 v80:`left`/`right` 那句**删掉**了 —— 现在色块的定位原点是 `.ag-lanes`
(已经让出了刻度),内联的 `left: X%` 直接相对车道宽算,
`width: calc(Y% - 4px)` 里的 4px 正好是"左右各 2px"的视觉留白。
⚠️ 千万别再在这里写 `left: 46px` 之类的绝对值:那就是 v80 之前
"右侧缺一块"的直接成因(绝对偏移 + 相对整宽的百分比,两套账混算)。 */
.ag-ev {
position: absolute;
/* 🔴 v63(用户 2026-10-19 第 2 条):日历本要显示在色块**右侧** ⇒ 本体必须是
一行弹性容器。⚠️ 纵向排列时(`flex-direction: column`)`block` 子元素会各占一行,
日历本必然掉到第二行;色块很矮时就整条看不见。 */
display: flex; align-items: center; gap: 6px;
overflow: hidden; padding: 3px 7px;
background: var(--brand); border-radius: 4px;
font-size: 12px; line-height: 1.35; color: #fff;
text-decoration: none; box-sizing: border-box;
border: 1px solid rgba(255,255,255,.35);
text-shadow: 0 1px 1px rgba(0,0,0,.18);
}
.ag-ev:hover { text-decoration: none; filter: brightness(1.08); z-index: 2; }
/* 「时间 + 标题」这一坨占满剩余宽度;`min-width:0` 是标题能被省略号截断的前提
(没有它,flex 子项按内容撑宽 ⇒ 整行溢出、日历本被挤出可视区)。 */
.ag-ev .ag-ev-main {
display: flex; align-items: baseline; gap: 4px;
flex: 1 1 auto; min-width: 0;
}
.ag-ev .ag-ev-t { flex: 0 0 auto; font-weight: 600; font-variant-numeric: tabular-nums; }
.ag-ev .ag-ev-w { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 600; }
/* 日历本名:色块**右侧**的极淡小字(色块面积小,不加大徽标)。
⚠️ 它 `flex: 0 0 auto`(**不参与收缩**):用户要的就是"看得见日历本" ——
让它跟着一起缩,色块一窄就又没了。改为**标题**让步(`.ag-ev-main` 可缩到 0)。
同时给它 `max-width: 45%` 兜底:日历本名过长时自己截断,别把标题吃光。 */
.ag-ev .ag-ev-cal {
flex: 0 0 auto; font-size: 10.5px; opacity: .82;
max-width: 45%;
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* ---- 任务行:标题 + 状态 + **看板名** + 截止 ---- */
.tk-list { display: flex; flex-direction: column; }
/* 🔴 v54(用户 2026-10-16 第 1 条):标题太长会把这行**挤成两行**。
与 `.dash-what` 那三句(nowrap/hidden/ellipsis)配套:行本身**不许换行**,
于是标题只能去收缩(它有 min-width:0,能一直缩到 0),徽标保持原样。
⚠️ 不复用 `.dash-row` 的 `flex-wrap:wrap` —— 那是"往年的今天"那一块要的
(窄屏时日期徽标该绕到下一行);任务行收紧成 nowrap 才有截断可言。 */
.tk-row { align-items: center; flex-wrap: nowrap; }
.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); }
/* ------------------------------------------------------------
地址设置:「我的短语」表单(工单 #17)
🔴 为什么不用 `.inline-form`(用户 2026-10-28:「输入框怎么那么高啊」):
`.inline-form .input { flex: 1 1 200px }` 是为「input 直接当水平 flex 子项」
设计的。本表单要把 label / input / tip 竖直堆叠,input 必须包进 `.field`
(`flex-direction: column`)—— 一包进去,`flex-basis: 200px` 就落在
**垂直主轴**上,输入框被撑成 200px 高的一条。
做法:外层横向 flex(左:输入区,右:按钮),只把**输入区**当 flex 子项;
输入区内部仍走 `.field` 的纵向堆叠,并显式把 `.input` 的 flex 收回默认。
⚠️ 不要改 `.inline-form` 那条通用规则:全站另有 15 处在用,且都是
"input 直接当子项"的正确用法,改它会波及那些页面。
------------------------------------------------------------ */
.nav-phrase-form {
display: flex; align-items: flex-end; gap: 12px; flex-wrap: wrap;
margin: 0;
}
/* 输入区是横向主轴上的那个伸缩项(宽度在这里定,不是让内部 input 定) */
.nav-phrase-form > .field { flex: 1 1 260px; min-width: 0; max-width: 380px; }
/* 🔴 关键:把 input 从"flex 子项"退化成普通块,否则 flex-basis 又跑到垂直轴上 */
.nav-phrase-form > .field > .input {
flex: 0 0 auto; width: 100%; max-width: none;
/* 单行高度:与全站 .input 一致(10px 上下 padding + 14px 字 + 1px 边框) */
height: auto; min-height: 0;
}
/* 按钮跟输入框底对齐(不是与整块 field 居中)—— tip 在下方,居中会显得按钮浮起来 */
.nav-phrase-form > .btn-row { flex: 0 0 auto; margin: 0; align-self: flex-end; }
/* 窄屏:按钮换到下一行并靠左,不挤压输入框 */
@media (max-width: 480px) {
.nav-phrase-form > .field { flex: 1 1 100%; max-width: none; }
.nav-phrase-form > .btn-row { width: 100%; }
}
.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;
}
/* 大号字母块:图标选择器里的预览位(与列表里的 20px 区分开) */
.nav-ico-lg { width: 40px; height: 40px; font-size: 18px; border-radius: 8px; }
/* ------------------------------------------------------------
站点表单:自定义图标选择器(工单 #16)
左侧预览、右侧文件选择 + 说明。预览用 40px 见方,
和列表里的 20px 小图标区分开,让用户能看清传的是什么。
------------------------------------------------------------ */
.nav-icon-pick { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.nav-icon-prev {
flex: 0 0 auto; width: 44px; height: 44px;
display: flex; align-items: center; justify-content: center;
border: 1px solid var(--line-2); border-radius: 10px; background: #fff;
overflow: hidden;
}
.nav-icon-prev img { width: 40px; height: 40px; object-fit: contain; display: block; }
.nav-icon-ctl { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; min-width: 0; }
/* 原生 file 控件隐藏,用 <label for> 当按钮 —— 各浏览器原生按钮长相差异太大 */
.nav-icon-file { position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none; }
.nav-icon-btn { cursor: pointer; margin: 0; }
/* 焦点可达性:file 输入被键盘聚焦时,让标签有个可见的焦点环 */
.nav-icon-file:focus-visible + .nav-icon-btn {
outline: 2px solid var(--brand); outline-offset: 2px;
}
#navIconFname { max-width: 200px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.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; }
/* 🔴 v52(用户第 3 条):块**下方**的「+段落 / +图片」。
放在每段输入框的下方,写到哪一段就在哪儿加,不用划回顶部。
⚠️ 克制视觉:它是"随手加"的快捷入口,不该抢正文的注意力 ——
小字、灰色、平时半透明,鼠标进到这一块才变清晰。 */
.note-block-add { display: flex; gap: 6px; margin-top: 6px; }
.nb-add {
padding: 3px 10px; font-size: 12.5px; line-height: 1.5; cursor: pointer;
border: 1px dashed var(--line); border-radius: 999px; background: transparent;
color: var(--muted);
}
.note-block:hover .nb-add, .note-block:focus-within .nb-add { color: var(--brand-ink); }
.nb-add:hover { border-color: var(--brand-2); border-style: solid; background: #fff; }
.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; }
}
/* ============================================================
v28 用户第 2 / 3 条:首页两块右上角的「更多日程 / 全部任务」按钮
要**用所属模块的主题色做背景**(原来一律 .btn-ghost 的白底灰字,
与"日程=橙、任务=砖红"的分区色完全看不出关系)。
做法:模板把模块色注入 `--hd-c`(见 home.php 的 style 属性),
这里只负责"拿这个色当底 + 白字 + 悬停加深"三件事 ——
不为日程/任务各写一套(那是两份迟早漂移的样式)。
⚠️ 用 color-mix 压暗悬停色而不是写死两个色:模块色在「个人设置」里可改,
写死就会出现"用户换了主题色、按钮悬停还是原来的颜色"。
============================================================ */
.hd-more {
background: var(--hd-c, var(--brand));
border-color: var(--hd-c, var(--brand));
color: #fff;
}
.hd-more:hover {
background: color-mix(in srgb, var(--hd-c, var(--brand)) 82%, #000);
border-color: color-mix(in srgb, var(--hd-c, var(--brand)) 82%, #000);
color: #fff;
}
/* 老浏览器不认 color-mix 时的兜底:至少不要变成透明/消失 */
@supports not (background: color-mix(in srgb, red 50%, blue)) {
.hd-more:hover { background: var(--hd-c, var(--brand)); filter: brightness(.88); }
}
/* ============================================================
v28 用户第 5 / 6 条:名册「分组」改「标签」+ 卡片按钮要有底色
============================================================ */
/* ---- 顶部标签筛选条(替代原来的分栏)---- */
.roster-tagbar {
display: flex; flex-wrap: wrap; align-items: center; gap: 6px;
margin: 0 0 12px; padding: 10px 12px;
border: 1px solid var(--line); border-radius: var(--radius-s);
background: var(--surface);
}
.roster-tagbar-k { font-size: 12.5px; color: var(--faint); margin-right: 2px; }
.roster-tagbar-clear { margin-left: auto; font-size: 12.5px; }
/* ---- 平铺列表:**仍然单列**(只是不再有"栏"的分隔线)----
🔴🔴 v29 用户第 3 条:「名册页面完全错乱」—— 这个 bug 是 v28 **我自己引入的**。
v28 我把这里写成了多列 grid(`repeat(auto-fill, minmax(320px,1fr))`),
真实后果有两个,都实测到了:
① 卡片上有 `content-visibility: auto` + `contain-intrinsic-size: auto 68px`
(v24 为"千人名册不卡死"加的)。它在**单列流**里是安全的,
一旦卡片变成 grid item,屏外卡片的"估算高度"与网格轨道互相打架,
布局当场乱掉;
② 每列只剩 ~339px(1280 宽 ÷ 3),而卡片里是
[拖柄][头像 52px + 姓名/徽标][标签+编辑+删除 三个按钮 ≈213px] ——
实测 `person-main` 只分到 **106px**(含 52px 头像 → 姓名区仅 42px),
姓名、徽标全被挤碎,动作区还会折行。
⇒ 修法:**回到单列**(`.people-grid` 本来就是单列 flex,见文件上方 328 行),
把 v28 那条 grid 规则整个删掉。名册人数从 3 到 1000+ 都是单列,
与"分栏时代每一栏的卡片"同宽 —— 那才是这套卡片的原始设计宽度。
⚠️ 别再用"多列网格"来塞名册卡片:它的内容是**横向三段式**的,
天生吃宽度;窄了就一定碎。 */
/* ---- 🔴 用户第 6 条:「名册,每个人后面的按钮,也是要有背景颜色的。」
原来三颗按钮全是 .btn-ghost(白底 + 极浅描边),在纸白卡片上几乎看不见边界。
给它们各自一个**语义底色**(编辑=品牌淡、标签=中性灰、删除=危险淡),
而不是统一刷成一种 —— 一眼能分清哪个是危险动作。 ---- */
.person-edit,
.person-tag-btn,
.person-more-btn {
background: var(--line-2);
border-color: var(--line);
color: var(--ink);
}
/* 🔴🔴 v50(用户 2026-10-14 第 4 条):「名册列表里面的按钮也是一样的,
编辑是**蓝色**的背景,删除是**红色**的背景。」
—— 与旅程详情页/v40 影院管理页**同一套口径**:编辑蓝、删除红,都是**实心**色块。
⚠️ 用**作用域类**(`.person-edit` / `.person-del`),不去改全局 `.btn-edit`
/ `.btn-danger` 的语义 —— 本项目踩过"裸类改色波及别页"的坑
(v41 把影院管理页的蓝色「编辑」改成了黄,见前面那段血泪注释)。
⚠️ 三处形态都写(裸类 / `a.` / `button.`):`.btn`、`a.btn`、`button.btn`
的特异性不同,漏一种就有一种是错的(v40 已验过这条规律)。
⚠️ `.person-del` 原来只是 `.btn-danger`(**淡红描边**)—— 用户这次要的是**实心红**,
所以在这里覆盖它;`.btn-danger` 本身的语义不动。 */
.person-edit, a.person-edit, button.person-edit {
background: var(--blue); color: #fff; border-color: transparent;
}
.person-del, a.person-del, button.person-del {
background: var(--red); color: #fff; border-color: transparent;
}
/* hover:同色系加深(与文末「全站悬停统一」段同一手法)——
⚠️ 必须在这里补,因为那段是按类名白名单写的,`.person-edit/.person-del` 不在里面,
不补的话 hover 会退回 `.btn` 的品牌色/或 `.person-edit` 上面那条淡底规则。 */
.person-edit:hover, .person-del:hover { filter: brightness(.92); }
/* 标签 = 中性灰底;有标签时稍深一点,一眼能看出"这个人是有标记的" */
.person-tag-btn { background: var(--line-2); color: var(--muted); }
.person-tag-btn:hover { background: var(--line); color: var(--ink); }
.person-tag-btn.is-none { color: var(--faint); }
/* ⋯ = 中性 */
.person-more-btn { background: var(--line-2); color: var(--muted); }
.person-more-btn:hover { background: var(--line); color: var(--brand); }
/* 删除已经是 .btn-danger(自带红底),不动 */
/* ---- 标签编辑浮层里的候选标签 ---- */
.pt-picks { display: flex; flex-wrap: wrap; gap: 6px; }
.pt-pick {
display: inline-flex; align-items: center; gap: 5px; cursor: pointer;
padding: 4px 10px; border: 1px solid var(--line); border-radius: 999px;
background: #fff; font-size: 12.5px; color: var(--muted);
}
.pt-pick:hover { border-color: var(--brand-2); color: var(--brand-ink); }
/* 勾上时整颗变实心(不是只动那个小方框)—— 点一下的反馈要一眼看得见 */
.pt-pick:has(input:checked) {
border-color: var(--brand); background: var(--brand-soft, #FDF3EC);
color: var(--brand-ink);
}
.pt-pick input { accent-color: var(--brand); }
/* ============================================================
v28 用户第 7 条:资源的「编辑」弹框改成带页签(信息 / 附件 / 共享)
============================================================ */
.res-tabs {
display: flex; gap: 4px; padding: 0 16px 10px;
border-bottom: 1px solid var(--line); margin-bottom: 12px;
}
.res-tab {
padding: 5px 14px; border: 1px solid transparent; border-radius: var(--radius-s);
background: transparent; color: var(--muted); cursor: pointer; font-size: 13.5px;
}
.res-tab:hover { background: var(--line-2); color: var(--ink); }
/* 当前页签:品牌淡底 + 品牌字色(与全站分段控件同一套观感) */
.res-tab.is-on {
background: var(--brand-soft, #FDF3EC);
border-color: color-mix(in srgb, var(--brand) 30%, transparent);
color: var(--brand-ink); font-weight: 500;
}
/* ============================================================
v28 用户第 10 条:内置联想接口库(点一下填入表单)
============================================================ */
.sug-lib { display: flex; flex-wrap: wrap; gap: 6px; margin: 6px 0; }
.sug-lib-item {
display: inline-flex; flex-direction: column; align-items: flex-start; gap: 2px;
padding: 6px 12px; border: 1px solid var(--line); border-radius: var(--radius-s);
background: #fff; cursor: pointer; text-align: left;
}
.sug-lib-item b { font-size: 13px; font-weight: 500; color: var(--ink); }
.sug-lib-item span { font-size: 11.5px; color: var(--faint); }
.sug-lib-item:hover { border-color: var(--brand-2); }
/* 刚被点选的那一个:品牌底 + 品牌边,明确"我选了它" */
.sug-lib-item.is-picked {
border-color: var(--brand); background: var(--brand-soft, #FDF3EC);
}
.sug-lib-item.is-picked b { color: var(--brand-ink); }
/* ============================================================
v28 用户第 12 条:笔记 —— 富文本双模式 / 附件 / 多级清单 / 定尺寸卡片网格
============================================================ */
/* ---- 列表页:顶部条(搜索 + 标签),去掉左侧标签栏 ---- */
.note-bar { margin-bottom: 12px; }
.note-search { margin: 0 0 8px; }
.note-tagbar { margin-top: 2px; }
/* ---- 🔴 定尺寸卡片网格(用户:"一行几个块,但是块大小要确定")----
高度**固定**:内容装不下就靠摘要 + 溢出裁切,卡片之间永远整齐。
用 auto-fill + minmax 让"一行几个"随宽度自适应,而不是写死列数。 */
.note-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
gap: 12px;
}
.note-card {
display: flex; flex-direction: column;
/* 固定高度 = 用户要的"块大小确定"。240px 大约放得下:一行头 + 标题 + 3~4 行摘要 + 缩略图 */
height: 240px; overflow: hidden;
background: var(--surface); border: 1px solid var(--line);
border-radius: var(--radius); padding: 12px 14px;
box-shadow: var(--shadow);
}
.note-card:hover { border-color: var(--brand-2); }
.note-head { display: flex; align-items: center; gap: 6px; flex: 0 0 auto; }
.note-meta { display: flex; align-items: center; gap: 6px; min-width: 0; flex: 1 1 auto; }
.note-time { font-size: 11.5px; color: var(--faint); white-space: nowrap; }
.note-ops { display: flex; align-items: center; gap: 4px; flex: 0 0 auto; }
.note-ops .inline-form { display: inline; }
.note-op {
font-size: 12px; padding: 1px 7px; border-radius: 6px; cursor: pointer;
border: 1px solid var(--line); background: var(--line-2); color: var(--muted);
text-decoration: none; line-height: 1.6;
}
.note-op:hover { background: var(--line); color: var(--ink); text-decoration: none; }
.note-op-del:hover { border-color: var(--red); color: var(--red); background: #FDF2F0; }
/* 点卡片进编辑(整块可点,但不用 <a> 包全文 —— 里面还有表单与链接) */
.note-hit { display: block; flex: 1 1 auto; min-height: 0; overflow: hidden; }
.note-hit:hover { text-decoration: none; }
.note-title {
margin: 8px 0 5px; font-size: 14.5px; font-weight: 500; color: var(--ink);
/* 标题最多两行,超出省略 —— 固定高度下不能让它无限长 */
display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.note-ex {
margin: 6px 0 0; font-size: 13px; line-height: 1.6; color: var(--muted);
display: -webkit-box; -webkit-line-clamp: 4; -webkit-box-orient: vertical; overflow: hidden;
}
.note-ex-empty { color: var(--faint); font-style: italic; }
/* 图片:等比缩放到卡片宽,放在摘要下方(用户:"图片缩放后,放在下面") */
.note-thumb { margin-top: 8px; flex: 0 0 auto; overflow: hidden; border-radius: var(--radius-s); }
/* 🔴 v29 第 6 条:「笔记,首页显示的时候,里面的图片,和行程的类似,
把图片按照比例缩放到固定的高度或者宽度,放在下面就行了。」
⇒ 固定高度 + **等比完整显示**(contain),不再 cover 裁切。
⚠️ 从 cover 改 contain 是有意的取舍:
cover 能让每张图铺满、看着"实",但**看不到完整内容**(横图两端被切掉)——
用户要的是"按比例缩小放在下面",那就是要看到**整张**。
代价是宽高比与格子不一致时两侧留浅底,所以给个底色让留白显得是有意的。 */
.note-thumb img {
width: 100%; height: 110px; object-fit: contain; display: block;
background: #F6F4F0;
border: 1px solid var(--line); border-radius: var(--radius-s);
}
.note-card .note-tags-inline { margin-top: auto; padding-top: 8px; flex: 0 0 auto; }
.note-card .note-tag { font-size: 11.5px; padding: 2px 7px; }
/* ---- 编辑页:模式提示条 ---- */
.note-modebar {
display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
margin-bottom: 10px; padding-bottom: 8px; border-bottom: 1px solid var(--line-2);
}
.note-modebar code {
font-size: 11.5px; background: var(--line-2); padding: 1px 5px;
border-radius: 4px; color: var(--ink);
}
.note-live { margin-left: auto; display: inline-flex; align-items: center; gap: 5px;
font-size: 12.5px; color: var(--muted); cursor: pointer; }
.note-live input { accent-color: var(--brand); }
/* ---- 段落的 Markdown 实时预览块 ---- */
.note-block-pv {
margin-top: 6px; padding: 0 2px; font-size: 14px; line-height: 1.7; color: var(--ink);
}
.note-block-pv:not(.is-on) { display: none; }
/* 预览区左边一条细线,与输入区在视觉上分开(不然分不清哪块是输入、哪块是结果) */
.note-block-pv.is-on {
border-left: 3px solid var(--brand-soft, #F3D9CB);
padding-left: 10px;
}
/* ---- Markdown 渲染出来的元素(列表页/预览共用同一套)---- */
.md-p { margin: 0 0 8px; }
.md-p:last-child { margin-bottom: 0; }
/* 🔴🔴 v85(用户 2026-10-24):「还有日记功能,markdown 代码都没解析,
`##` 后面的是标题,现在显示的还是和正文一样。」
------------------------------------------------------------------
渲染**一直是对的**(`## x` → `<h4 class="md-h">`),问题全在**视觉**:
① 旧规则 `h4.md-h { font-size: 15px }`,而阅读态正文
`.note-read-block { font-size: 15px }` —— **两者完全相同**;
② 标题只有 `font-weight: 500`,而 .md-h 继承了正文的行高与颜色
⇒ 用户看到的"标题"和"正文"逐像素一致,当然认为"没解析"。
⇒ 三条一起改,缺一条都还是"看不出是标题":
① **字号必须拉开 ≥2px 且各级之间也要分级**(h4 < h5 那种倒挂是旧 bug:
`##` 给的是 h4、`###` 给的是 h5,而旧表里 h4=15px、h5=14px 只差 1px);
② **字重加到 600**(500 在中文无衬线里与常规几乎无差);
③ **加上下边距**,让标题与正文之间出现明确的段落呼吸。
⚠️ 为什么从 h4 起而不是 h1/h2:见 `Markdown::render()` 里的说明 ——
h1/h2 留给页面本身,笔记里的标题不许盖过页面标题。
所以这里必须把 **h4/h5/h6 这三级**都调大,光调 h4 不够。
🔴🔴 v93(2026-10-25,全量套件抓出来的**上一版没做完的地方**):
上一版只把 h4(`##`)做到 17px,**h5(`###`)留在 15.5px、h6 留在 14.5px**
—— 15.5 只比正文 15 大 0.5px,肉眼仍是"和正文一样";h6 更是**比正文还小**。
而上面这段注释自己写的是"三级都要调大、要拉开 ≥2px",实现违背了自己的规则。
⇒ 阶梯整体重排,**最小的一级也必须 ≥ 正文 + 2px**:
h3 `#` → 22px
h4 `##` → 20px
h5 `###` → 18px
h6 `####`起(Markdown 里 h4 及更深都收敛到 h6)→ 17px,另配 --muted 灰
四级字号**两两不同**(A9d 守的就是"同级同号、异级不同号"),
且 h6 靠灰色与字号双重区分 —— 否则 `####` 与正文挤在一起。
⚠️ 正文基准是 `.note-read-block { font-size: 15px }`(见下面那条规则)。
将来正文若改号,这里的四级要**一起**复核,别只改 h4。 */
.md-h {
margin: 16px 0 8px;
font-weight: 600;
color: var(--ink);
line-height: 1.45;
}
.md-h:first-child { margin-top: 0; }
h3.md-h { font-size: 22px; }
h4.md-h { font-size: 20px; }
h5.md-h { font-size: 18px; }
h6.md-h { font-size: 17px; color: var(--muted); }
.md-list { margin: 4px 0 8px; padding-left: 22px; }
.md-li { margin: 2px 0; }
/* 多级清单:子层用空心点,层级一眼可分(用户第 12 条"清单可以多级") */
.md-list .md-list { list-style-type: circle; margin: 2px 0 2px; }
.md-list .md-list .md-list { list-style-type: square; }
.md-quote {
margin: 6px 0; padding: 4px 12px; border-left: 3px solid var(--line);
color: var(--muted); background: var(--line-2); border-radius: 0 6px 6px 0;
}
.md-code {
background: #F6F4F0; border: 1px solid var(--line); border-radius: 6px;
padding: 8px 10px; overflow-x: auto; font-size: 12.5px; line-height: 1.6;
font-family: ui-monospace, Consolas, monospace; color: var(--ink);
}
.md-icode {
background: var(--line-2); padding: 1px 5px; border-radius: 4px;
font-size: 12.5px; font-family: ui-monospace, Consolas, monospace;
}
.md-link { color: var(--brand); text-decoration: underline; }
.md-hr { border: 0; border-top: 1px solid var(--line); margin: 10px 0; }
/* 🔴 v61(用户 2026-10-18):表格与任务清单 —— 与 Notion/GFM 同款的块形态。
表格必须 overflow-x 包一层的可能交给外层(笔记卡有 padding,窄屏横滚更稳);
边框用 --line,深色主题下自动跟色,不写死颜色。 */
.md-table {
border-collapse: collapse; margin: 6px 0 10px; width: auto; max-width: 100%;
font-size: 13.5px;
}
.md-table .md-th, .md-table .md-td {
border: 1px solid var(--line); padding: 4px 10px; text-align: left;
vertical-align: top;
}
.md-table .md-th { background: var(--line-2); font-weight: 600; }
/* 任务清单:去掉圆点,用只读 checkbox 顶位(ul 的 padding 提供缩进) */
.md-task { list-style: none; }
.md-task .md-check { margin: 0 6px 0 0; accent-color: var(--brand); vertical-align: -2px; }
/* ---- 编辑页底部附件区 ---- */
.note-files { display: flex; flex-direction: column; gap: 6px; margin-bottom: 12px; }
.note-file {
display: flex; align-items: center; gap: 8px;
padding: 6px 10px; border: 1px solid var(--line); border-radius: var(--radius-s);
background: #FDFCFA;
}
.note-file-ico {
font-size: 11px; padding: 1px 6px; border-radius: 4px;
background: var(--line-2); color: var(--muted); flex: 0 0 auto;
}
.note-file-nm { flex: 1 1 auto; min-width: 0; font-size: 13.5px; color: var(--ink);
overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.note-file-del {
flex: 0 0 auto; border: 1px solid var(--line); background: #fff; border-radius: 6px;
color: var(--muted); cursor: pointer; padding: 2px 8px; font-size: 12px;
}
.note-file-del:hover { border-color: var(--red); color: var(--red); }
/* 拖放区:虚线框,与全站其它模块的上传区同一观感 */
.note-att-drop {
border: 1px dashed var(--line); border-radius: var(--radius-s);
padding: 14px; text-align: center; background: #FDFCFA;
}
.note-att-drop.is-over { border-color: var(--brand); background: var(--brand-soft, #FDF3EC); }
.note-att-drop input[type=file] { display: none; }
.note-att-tip { font-size: 13px; color: var(--muted); }
.note-att-tip label { color: var(--brand); cursor: pointer; text-decoration: underline; }
/* ---- 窄屏:网格收成一列(固定高度在小屏上会显得很挤)---- */
@media (max-width: 640px) {
.note-grid { grid-template-columns: 1fr; }
.note-card { height: auto; min-height: 150px; }
}
/* ---- v28 修:名册卡片在窄屏被"标签按钮"撑破(真机几何探针抓到)----
根因:卡片右侧的动作区是一条 flex,里面那颗「标签」按钮的文字是
**所有标签拼起来**(`#小学同学 #初中同学 …`),且 `white-space: nowrap` ——
标签一多它就无限变宽,把整行、整张卡、整张网格一起顶出视口
(实测 360px 下 scrollW=2090,越界元素就是那一串 <a>/<button>)。
⇒ 三件事一起做,缺一不可:
① 动作区**允许换行**(标签多了折到第二行,而不是往外顶);
② 标签按钮**限最大宽度 + 溢出截断**(再长的标签也不会撑破);
③ 卡片主体 `min-width: 0`(flex/grid 子项默认 min-width:auto,不肯收缩)。 */
.person-actions { flex-wrap: wrap; justify-content: flex-end; }
.person-tag-btn {
max-width: 160px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.person { min-width: 0; }
.person-main { min-width: 0; }
.person .pb { min-width: 0; }
/* 窄屏更严:动作区整体不参与撑宽,标签按钮再收一档 */
@media (max-width: 720px) {
.person-actions { max-width: 100%; }
.person-tag-btn { max-width: 110px; }
/* 姓名那一栏允许收缩并截断,否则长名字也会顶破 */
.pname { overflow-wrap: anywhere; }
}
/* 🔴 v29 第 3 条(窄屏半边):360px 下卡片只有 328px,
[拖柄 24][头像 52 + 姓名][标签+编辑+删除 ≈197] 硬塞在一行 → 姓名区只剩 ~40px。
⇒ 窄屏把动作区**整体折到第二行**(卡片改纵向),姓名那一行就宽裕了。
用 flex-direction: column 而不是给动作区定宽:后者的宽度随标签数变化,
定了也会被长标签顶开(v28 就是这么栽的)。 */
@media (max-width: 640px) {
.person { flex-wrap: wrap; }
.person .person-main { flex: 1 1 calc(100% - 40px); }
.person-actions {
flex: 1 1 100%; justify-content: flex-start;
padding: 0 10px 10px 34px; /* 左边缩进到与姓名对齐(躲开拖柄) */
}
.person-tag-btn { max-width: 46vw; }
}
/* ============================================================
v29 用户第 7 / 8 / 9 条:笔记 —— 阅读态 / 工具条 / 斜线命令 / 图片调大小
============================================================ */
/* ---- 阅读态(第 8 条:点进来默认是"看")----
与编辑态的区别要**一眼看得出**:没有虚线框、没有按钮,就是一篇排好版的文档。 */
/* 🔴 v47(用户 2026-10-13):「标题和内容这个块的宽度并没有撑满整个页面,
它和下面那个共享给谁、还有权限那一块,宽度是不一致的。」
⇒ 原来这里写着 `max-width: 820px`,而下面的「共享」卡是满宽的 ⇒ 上下两块对不齐。
⚠️ 不要改成"给共享卡也加 820px":用户要的是**撑满**,不是一起变窄;
正文卡与其它页面的 .card 保持同宽,视觉上才是一列。
⇒ 直接放开限宽(.card 本身已有全页统一的内边距与最大宽度约束)。 */
.note-read { max-width: none; }
/* ⚠️ v55:`.note-read-title` 已删 —— 标题只在**页头**显示一次(用户 2026-10-17 第 1 条
「标题已经变成这个笔记的题目了,那下面那个里面就不用再显示这个题目了」)。
留一条没有使用者的规则,只会让下一个人以为"正文里还该有个标题"。 */
.note-read-tags { margin: 0 0 14px; }
.note-read-body { display: flex; flex-direction: column; gap: 14px; }
/* 🔴 v52(用户第 4 条):详情页的**分段编辑** —— 每一块外面包的这个容器。
⚠️ `position: relative` 是必须的:那个"鼠标划过才出来的编辑按钮"按它绝对定位到右上角。
⚠️ 不能给它背景色:它不是一张卡,只是"一块内容的边界框",加底色会让正文看起来像被框住。 */
.note-read-item { position: relative; }
.note-read-item.is-editing { padding: 0; }
/* 编辑按钮:平时不可见(但**不能用 display:none** —— 那样焦点/无障碍都拿不到它),
用 opacity + pointer-events 收起;鼠标进这一块、或键盘 Tab 到它,就浮出来。 */
.note-item-edit {
position: absolute; top: 2px; right: 2px;
padding: 2px 10px; font-size: 12.5px; line-height: 1.6;
border: 1px solid var(--line); border-radius: 999px;
background: #fff; color: var(--muted); cursor: pointer;
opacity: 0; pointer-events: none; transition: opacity .12s;
}
.note-read-item:hover .note-item-edit,
.note-read-item:focus-within .note-item-edit { opacity: 1; pointer-events: auto; }
.note-item-edit:hover { border-color: var(--brand-2); color: var(--brand-ink); }
.note-read-item.is-editing .note-item-edit { display: none; }
/* 就地编辑框:与正文同宽、留出"我在改"的视觉边界 */
.note-item-editor { display: flex; flex-direction: column; gap: 8px; }
.note-item-ta { width: 100%; resize: vertical; font-size: 15px; line-height: 1.8; }
.note-item-ops { display: flex; align-items: center; gap: 8px; }
.note-item-hint { margin: 0; font-size: 13px; color: var(--muted); }
.note-read-block { font-size: 15px; line-height: 1.8; color: var(--ink); }
/* 图片:默认满宽,可调(内联 style 给百分比);圆角与卡片一致 */
.note-read-fig { margin: 0; }
.note-read-fig img {
max-width: 100%; height: auto; display: block;
border: 1px solid var(--line); border-radius: var(--radius-s);
}
.note-read-fig a { display: inline-block; max-width: 100%; }
.note-read-h2 {
margin: 0 0 8px; font-size: 14px; font-weight: 600; color: var(--muted);
letter-spacing: .5px;
}
/* 附件区:一条细线把它与正文分开("在最下方"要看得出来是**另一块**) */
.note-read-files { margin-top: 20px; padding-top: 14px; border-top: 1px solid var(--line-2); }
.note-read .note-share-now { margin-top: 16px; }
/* ---- 编辑态:Markdown 工具条(第 7 条"要么有工具条")---- */
.note-cmdbar {
display: flex; align-items: center; gap: 4px; flex-wrap: wrap;
padding: 6px 8px; margin-bottom: 8px;
border: 1px solid var(--line); border-radius: var(--radius-s);
background: #FDFCFA;
}
.note-cmdbar .cmd {
min-width: 28px; height: 26px; padding: 0 7px;
border: 1px solid var(--line); border-radius: 5px; background: #fff;
color: var(--ink); cursor: pointer; font-size: 13px; line-height: 1;
}
.note-cmdbar .cmd:hover { border-color: var(--brand-2); color: var(--brand-ink); background: var(--brand-soft, #FDF3EC); }
.note-cmdbar .cmd-sep { width: 1px; height: 18px; background: var(--line); margin: 0 4px; }
.note-cmdbar .note-live { margin-left: auto; }
/* ---- 斜线命令菜单(第 7 条"要么有代码提示")----
⚠️ 绝对定位在**块内**(不是 body):跟着块走,块删除/移动时不会留孤儿菜单。 */
.note-block-body { position: relative; }
.slash-menu {
position: absolute; left: 0; top: 100%; z-index: 30; display: none;
min-width: 190px; max-height: 220px; overflow-y: auto;
background: #fff; border: 1px solid var(--line); border-radius: var(--radius-s);
box-shadow: var(--shadow); padding: 4px;
}
.slash-menu.is-on { display: block; }
.slash-item {
display: flex; align-items: baseline; gap: 8px; width: 100%;
padding: 5px 8px; border: 0; border-radius: 5px; background: transparent;
cursor: pointer; text-align: left; font-size: 13px; color: var(--ink);
}
.slash-item b { color: var(--brand); font-family: ui-monospace, Consolas, monospace; }
.slash-item span { color: var(--muted); font-size: 12px; }
.slash-item:hover, .slash-item.is-on { background: var(--brand-soft, #FDF3EC); }
/* ---- 图片块:尺寸档位(第 8 条"图文混排时,需要可以调整大小")---- */
.note-img-bar { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.note-img-sizes { display: inline-flex; gap: 3px; margin-left: auto; }
.note-img-size {
padding: 2px 7px; border: 1px solid var(--line); border-radius: 5px;
background: #fff; color: var(--muted); cursor: pointer; font-size: 11.5px;
}
.note-img-size:hover { border-color: var(--brand-2); color: var(--brand-ink); }
.note-img-size.is-on { border-color: var(--brand); background: var(--brand-soft, #FDF3EC); color: var(--brand-ink); }
/* 图上标着当前档位,编辑器里一眼能看出"这张被缩过了" */
.note-block[data-kind="image"] .note-block-img { transition: width .12s ease; }
/* ============================================================
通知消息(v30 第 5 条)
------------------------------------------------------------
用户原话:「引入通知模块,这个模块,不在导航显示,在个人头像点击那里显示,
放在个人设置前面,名字:通知消息。」
⚠️ 三处共用这一套样式:头像下拉里的入口、?r=notifications 页、首页那一块。
分开写三遍迟早长歪(本项目在"设置下拉"上刚吃过这个亏)。
⚠️ 未读用**左侧一条品牌色竖线 + 浅底**表达,不用加粗字重 ——
字重变化会让整行宽度轻微跳动,一列通知看下来眼睛累。
============================================================ */
/* 头像下拉里的入口:文案 + 未读角标(角标推到最右) */
.menu-list a:has(.menu-badge),
.menu-list a .menu-badge {
display: inline-flex; align-items: center;
}
.menu-list a .menu-badge {
margin-left: auto; min-width: 18px; height: 18px; padding: 0 5px;
border-radius: 999px; background: var(--red); color: #fff;
font-size: 11px; line-height: 18px; justify-content: center; font-weight: 600;
}
.ntf-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 2px; }
.ntf-row {
display: flex; align-items: center; gap: 10px;
padding: 8px 10px; border-radius: var(--radius-s);
border-left: 3px solid transparent;
}
.ntf-row:hover { background: var(--line-2); }
/* 未读:左侧品牌色竖线 + 极浅底(见上面的说明:不用字重) */
.ntf-row.is-unread { border-left-color: var(--brand); background: var(--brand-soft, #FDF6F1); }
.ntf-av {
flex: 0 0 auto; width: 30px; height: 30px; border-radius: 50%;
object-fit: cover; background: var(--line-2);
}
.ntf-main {
flex: 1 1 auto; min-width: 0; display: flex; align-items: center; gap: 10px;
color: var(--ink); text-decoration: none;
}
.ntf-main:hover { text-decoration: none; color: var(--brand); }
.ntf-title { flex: 1 1 auto; min-width: 0; font-size: 14px; overflow: hidden;
text-overflow: ellipsis; white-space: nowrap; }
.ntf-title b { font-weight: 600; margin-right: 2px; }
.ntf-time { flex: 0 0 auto; font-size: 12px; color: var(--faint); font-variant-numeric: tabular-nums; }
.ntf-kind { flex: 0 0 auto; font-size: 15px; line-height: 1; }
.ntf-del { flex: 0 0 auto; }
/* 旧首页那块用的紧凑行距类 —— v71c 之后**没人用了**(通知已改成卡片,走 `.hc-ntf`)。
留着这条是为了让"线上老页面截屏/老缓存"不突然变松;**新代码不要再挂这个类**。 */
.ntf-list-home .ntf-row { padding: 6px 8px; }
/* ---- v71c(用户 2026-10-21):通知消息变成首页卡片 ----
用户原话:「通知消息这个,也是首页的卡片,可以调整顺序和大小。另外,这个样式也
不好看,我看个人头像点击后,通知消息那个页面的样式就不错,直接复用就可以了。」
⇒ 结构与 `.ntf-*` **完全复用**(头像在前、kind 图标在右),这里只补"卡片比整页小"
带来的三条差异:
① `.hc-ntf` 的作用域里**允许正文折行** —— 卡片可能只有 2 格宽(一排四张里的一张),
单行省略会把「有人把你加进了观影「…」」全吃掉,只剩一个名字(那就是"不好看");
② 时间挪到正文下面自己一行(窄卡里横着排会把正文挤没);
③ 未读数做成一条小注脚(原来它挂在卡片外的 `<h1>` 上,卡片里没有那个位置)。
⚠️ 只在 `.hc-ntf` 下改,**不影响**通知页那一整页(那里宽度足够,单行更好读)。 */
.hc-ntf .ntf-row { align-items: flex-start; }
.hc-ntf .ntf-main { flex-direction: column; align-items: stretch; gap: 1px; }
.hc-ntf .ntf-title {
white-space: normal;
display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;
line-height: 1.4; word-break: break-word;
}
.hc-ntf .ntf-time { font-size: 11.5px; }
.hc-ntf .ntf-kind { margin-top: 2px; }
.hc-ntf-note {
margin: 0 0 6px; font-size: 12px; color: var(--brand-ink, var(--brand));
font-variant-numeric: tabular-nums;
}
@media (max-width: 640px) {
/* 窄屏把时间挪到下一行,别把标题挤成两三个字 */
.ntf-main { flex-wrap: wrap; gap: 2px 10px; }
.ntf-title { flex: 1 1 100%; }
}
/* ============================================================================
v37(2026-10-09) —— 弹框:按钮位置 / 标签加粗 / 观影弹框宽度
----------------------------------------------------------------------------
用户原话(4 条,都指向"弹框"):
⑤「日程模块的新建日程里,弹出一个对话框。但是"加上"和"取消"两个按钮在
右上角,离得特别远,根本看不到。应该把它们放在下方,或者放在"新建日程"
标题的右侧,不要放那么远。
另外,每个输入框前面的标题都太小了…比如"放在哪本""标题""地点""提醒"这些,
都应该加粗、变大,这样跟里面的内容产生一些差异,看起来更醒目一些。」
⑥「新建任务也和新建日程一样,新建时弹出一个框…」
============================================================================ */
/* 🔴🔴 日程弹框"按钮跑到右上角、还根本看不到"的真根因(实测定位,不是猜):
`.cal-modal-panel` 是 flex 列(head / form / foot 三段),但**它下面的 <form>
不是** —— form 是块级元素,`cal-modal-body{flex:1 1 auto}` 的 flex 上下文
只到 form 为止。于是 form 的高度 = head + 内容**全部**高度,而面板 `max-height`
又把外层卡住 ⇒ foot 被挤出面板可视区,出现在**面板底部之外**,
而面板本身不滚(滚的是 body)→ 用户看到的就是"按钮飘在很远的地方 / 看不见"。
⇒ 让 form 也参与那个 flex 列、body 才能真正被压缩到可滚动。 */
.cal-modal-panel > form {
display: flex; flex-direction: column;
flex: 1 1 auto; min-height: 0;
overflow: hidden; /* 圆角裁切:body 滚动时不要顶出面板 */
}
/* ⚠️ v37 起"加上"主键改用 `form="calAddForm"` 关联 ——
它不再长在 form 内部,`:not()` 那条老规则会失效,这里按 id 补回。 */
.cal-modal-foot .btn[type="submit"],
.cal-modal-foot #calAddSubmit { margin-left: auto; }
/* ---- 弹框里的字段标签:加粗、变大、深色 ----
用户原话:「每个输入框前面的标题都太小了…都应该加粗、变大,这样跟里面的内容
产生一些差异,看起来更醒目一些。」
⚠️ 只改**弹框里**的(.cal-modal / .pp-modal 等浮层面板内):
页面内的普通表单标签维持原样,避免"改一处动了全站"。
⚠️ 用 `.cal-modal .field > label` 而不是 `.cal-modal label`:
后者会连"提醒"那排 checkbox 的每个小标签一起加粗,看起来一片糊。 */
.cal-modal .field > label,
.cal-modal .field > .dt-range-label,
.cn-panel .field > label,
.pp-modal .field > label {
font-size: 14px; font-weight: 600; color: var(--ink);
}
/* ---- 观影"记一场电影"弹框 ----
⚠️ 字段比日程多得多(海报 + 观影照片 + 简介 + 同行人…),620px 太窄了,
用 760px 让两列字段摆得开;高度放到 88vh,长表单才有足够的可视区。 */
.cal-modal-panel.mv-panel { width: min(760px, 100%); max-height: calc(100vh - 64px); }
.mv-form-inner { padding: 16px; } /* 弹框里的内边距(编辑态用的是 .card-pad) */
/* 弹框形态下,页面上那条"记下这场电影 / 取消"一整行是多余的(foot 里已有),
但它在**编辑态**必须留着 —— JS 没跑时它是唯一的提交入口。 */
.mv-foot-inner { display: none; }
@media (max-width: 640px) {
.cal-modal-panel.mv-panel { width: 100%; }
}
/* ---- 窄屏:弹框字段全部单列(form-grid 是两列,手机上会把标签挤成两个字) ---- */
@media (max-width: 560px) {
.cal-modal .form-grid { grid-template-columns: 1fr; }
}
/* ============================================================================
v38(2026-10-10) 观影"记一场电影"弹框:标签内联排版 + Ant Design 日期控件
----------------------------------------------------------------------------
用户原话(两条,一起改的):
「记录一场电影,这个弹框因为标题和下面的输入框都是两行的,所以导致弹框非常长。
能不能排排版,让前面的标题后面加个冒号,后面的输入框直接跟在它后面,
这样就变成一行。包括开始、结束时间、影院、同行的人、简介等等,能把它排成
一行的都排成一行,但是一定要注意对齐的问题,如果不对齐的话可能不好看。」
「我终于找到了一个我比较认可的日期选择插件
https://ant-design.antgroup.com/components/date-picker-cn 现在观影这个模块
进行测试吧…等这个模块测试完没问题了,再更新到其他的模块。」
============================================================================ */
/* ---- ① 「标签:控件」内联网格 ----
🔴 对齐靠的是**固定宽度的标签列**(62px + 右对齐),不是靠调间距 ——
标签长短差很多(片名/时间/影院/同行的人),但它们冒号后的控件左缘
必须落在同一条竖线上,看起来才整齐。
⚠️ 标签**自带冒号**(CSS 的 ::after 给)—— 让每个模板都手写":"迟早漏一个。
⚠️ `> .mv-lb` 而**不是** `.mv-row label`:后者会把同行人那排 checkbox 的
小标签一起卷进来(它们不是"字段标签")。 */
.mv-grid { display: flex; flex-direction: column; gap: 12px; }
/* 🔴🔴 v53(用户 2026-10-16 第 3 条):「点击一个日程,弹出简介,点击编辑,
新的编辑框,**只有左侧有内容,右侧是空白的,好像分栏了一样**。这个样式不太对。」
真根因:日程编辑页(`templates/calendar/event.php`)里,`.mv-grid`(观影式的**纵向**列表)
被嵌在**两列**的 `.form-grid` 里:
`<div class="form-grid">` ← `grid-template-columns: 1fr 1fr`
`<div class="mv-grid">` ← 一个 grid 项,**只占第一列**
在整页形态下不明显(页面宽,两列各 ~700px 还能看);但**浮层里面板只有 620px**
⇒ 两列各 286px,而所有字段都挤在左边那一列 286px 里,右边整列空着 —— 就是用户看到的"分栏"。
(实测:panel 620 / form-grid 588 / cols="286px 286px" / mv-grid w=286。)
⇒ 让 `.mv-grid` **跨满整行**(`grid-column: 1 / -1`)。
它本身是 `<div>` 包着**纵向堆叠**的 `.mv-row`,语义上就是"一整块、占满宽度",
天生不该被塞进半列。这样两种形态(整页 / 浮层)都正确,
而且不必去改 `event.php` 里"整页保留两列"的结构判断(那条注释说明整页形态是有意保留的)。 */
.form-grid > .mv-grid { grid-column: 1 / -1; }
.mv-row { display: flex; align-items: flex-start; gap: 10px; min-width: 0; }
.mv-row > .mv-lb {
flex: 0 0 62px; width: 62px; /* 固定宽 = 对齐的基准 */
text-align: right; padding-top: 9px; /* 与 40px 高输入框的文字基线对齐 */
font-size: 14px; font-weight: 600; color: var(--ink);
line-height: 1.4; word-break: keep-all;
}
.mv-row > .mv-lb::after { content: ':'; font-weight: 400; color: var(--muted); }
/* 🔴 v94 起星号不再使用(见文件下方「必填项统一标记」那段);这两条留着**不删**:
万一还有没清干净的 `<i>*</i>`,它会渲染成紧贴的红色星号、不会露成一个灰星花,
而静态断言会把它当失败报出来 —— 比"悄悄留个灰星花"好定位。 */
.mv-row > .mv-lb > i { font-style: normal; color: var(--red, #C0392B); margin-left: 1px; }
/* 控件区:占满剩余宽度(min-width:0 让它能被压缩,不会把标签挤出去) */
.mv-row > *:not(.mv-lb) { flex: 1 1 auto; min-width: 0; }
/* 片名那一行:输入框 + 「自动获取」按钮并排 */
.mv-row .db-title-row { display: flex; gap: 8px; align-items: center; }
.mv-row .db-title-row .input { flex: 1 1 auto; min-width: 0; }
.mv-row .db-title-row .btn { flex: 0 0 auto; }
/* 影院那一行:自定义下拉 + 「新建影院」并排 */
.mv-row .cinema-select-row { display: flex; gap: 8px; align-items: center; }
.mv-row .cinema-select-row .cs { flex: 1 1 auto; min-width: 0; }
/* 时间那一行是 `.mv-row.dt-range`:它既是"标签+控件"的行,又是**范围组**。
⚠️ 这两重身份会打架:`.dt-range.dt-cols` 是 `flex-direction: row`,
会把标签也当成"一个端点"排进去。⇒ 这里把它拉回"标签一行、两端在下",
标签仍占固定 62px(与上面的字段对齐)。 */
.mv-row.dt-range.dt-cols { flex-direction: row; flex-wrap: wrap; align-items: flex-start; }
.mv-row.dt-range.dt-cols > .dt-range-label { flex: 0 0 62px; width: 62px; }
/* 🔴 v38 对齐修正(真机量出来的 8px 偏差):
`.dt-cols` 是 `gap: 10px 18px`(行距 10 / **列距 18**),而普通 `.mv-row`
只有 `gap: 10px`。于是"标签 → 控件"这段在时间行是 18px、在别的行是 10px
⇒ 控件左缘差了 8px(实测 452 vs 444),一眼就看得出没对齐。
⇒ 时间行的列距拉回 10px,与别的行一致。 */
.mv-row.dt-range.dt-cols { column-gap: 10px; }
/* 两端并排(沿用 v37 的 .dt-cols 规则),并**从标签右边开始** */
.mv-row.dt-range.dt-cols > .dt-input {
flex: 1 1 0; min-width: 220px; margin-top: 2px;
}
.mv-row.dt-range .err { flex: 1 1 100%; margin-left: 72px; }
/* 海报 / 观影照片:**与简介同一套 `.mv-row`**(标签在左、内容在右)。
🔴🔴 v39(用户 2026-10-10):「海报和观影照片为什么和上面的标题不一样呢?
海报的字体和简介是一样的,也带一个冒号。然后把后面点击或拖动进行上传往后放,
和简介后面的输入框保持一样大小。观影照片…也是加粗。和简介的字体保持一致,
不带冒号。]
⇒ 关键认识:**这两块不需要任何特殊规则**,它们就是普通字段行 ——
标签 = `.mv-lb`(继承上面那条:14px / 600 / var(--ink) / 固定 62px / 右对齐
+ `::after` 冒号),内容 = 行里唯一参与弹性布局的那一项(占满剩余、左缘 444)。
v38 之所以把它们写成 `.mv-media`(另起一行 + 手填 `margin-left:72px`),
是因为当时**探针取错了元素** —— 它按文档序取"第一个非标签子元素",
而 `.sr-only` 的文件框是 `position:absolute`(**脱离文档流、不参与 flex**),
却被算成了"控件",于是量出"1px 隐框把上传框推走 130px"这个**假结论**。
⇒ 这轮改用真机逐行量左缘来定,不再靠推断。
⚠️ 想让它别再"另起一行":这里**不设** flex-direction —— `.mv-row` 本来就是
`display:flex; align-items:flex-start`,标签与内容天然同一行。 */
.mv-grid-media { margin-top: 2px; gap: 14px; }
/* 内容区:纵向排(缩略图 + 上传框),并**占满**标签右侧的剩余宽度
(`flex:1 1 auto` + `min-width:0` 与 `.mv-row > *:not(.mv-lb)` 那条一致,
这里显式写一遍是为了让"它的宽度 = 简介输入框的宽度"这件事一眼可读)。 */
.mv-row-media > .mv-media-body {
flex: 1 1 auto; min-width: 0;
display: flex; flex-direction: column; gap: 8px;
}
.mv-media-body .photo-grid { margin-top: 0; }
/* 观影照片的标签**不带冒号**(用户原话:「观影照片也是加粗。和简介的字体保持一致,
不带冒号。」)—— 这类"有标签但不带冒号"的情况靠一个类开关,别去改 ::after 的全局规则。 */
.mv-lb.no-colon::after { content: none; }
/* ⚠️ `.sr-only` 是**绝对定位**的(见 .sr-only 的定义);它不该影响任何布局。
这里再钉一次"不参与 flex 分配"(万一将来有人把 .sr-only 的定义改了)。 */
.mv-media-body > .sr-only,
.mv-row > .sr-only { position: absolute; flex: 0 0 0; }
/* ---- ② Ant Design RangePicker 的外观对齐 ----
目标是"看起来就是站内的一个输入框",而不是"嵌进来一个外来控件":
同高(40px)、同圆角、同描边、同纸色。 */
.dt-antd { flex: 1 1 auto; min-width: 0; }
.dt-antd .ant-picker {
width: 100%; height: 40px; padding: 0 11px;
border: 1px solid var(--line); border-radius: var(--radius-s);
background: var(--surface); box-shadow: none;
}
.dt-antd .ant-picker:hover { border-color: var(--brand-2); }
.dt-antd .ant-picker-focused { border-color: var(--brand); box-shadow: none; }
.dt-antd .ant-picker-input > input { font: inherit; font-size: 14px; color: var(--ink); }
/* 品牌色覆盖:让 antd 的选中/主色跟着模块色走(与 Semi 那次同一思路) */
.dt-antd .ant-picker-range-separator { color: var(--muted); }
.dt-antd .ant-picker-cell-in-view.ant-picker-cell-selected .ant-picker-cell-inner,
.dt-antd .ant-picker-cell-in-view.ant-picker-cell-range-start .ant-picker-cell-inner,
.dt-antd .ant-picker-cell-in-view.ant-picker-cell-range-end .ant-picker-cell-inner {
background: var(--brand) !important;
}
/* 中间的"区间底色"用模块色的浅版 */
.dt-antd .ant-picker-cell-in-view.ant-picker-cell-in-range::before,
.dt-antd .ant-picker-cell-in-view.ant-picker-cell-range-hover::before {
background: color-mix(in srgb, var(--brand) 12%, transparent) !important;
}
.dt-antd .ant-picker-time-panel-column > li.ant-picker-time-panel-cell-selected .ant-picker-time-panel-cell-inner {
background: color-mix(in srgb, var(--brand) 14%, transparent) !important;
}
.dt-antd .ant-btn-primary { background: var(--brand); border-color: var(--brand); }
.dt-antd .ant-btn-primary:hover { background: var(--brand-2) !important; border-color: var(--brand-2) !important; }
/* 面板:跟着纸色(antd 默认是纯白 + 冷灰边) */
.dt-antd .ant-picker-dropdown .ant-picker-panel-container {
border: 1px solid var(--line); box-shadow: var(--shadow-l, 0 10px 30px rgba(0,0,0,.14));
}
/* 面板整体跟着模块色(antd 的 CSS-in-JS 变量) */
.ant-picker-dropdown {
--ant-color-primary: var(--brand, #A84A28);
--ant-color-primary-hover: var(--brand-2, #C05F38);
}
/* ⚠️ 挂上 antd 之后:**原生那对下拉收起来**(它们仍然在 DOM 里当值载体)。
⚠️ 用 visibility+绝对定位而不是 display:none —— 见 v37 那次的教训:
display:none 的控件在表单校验时不可聚焦;这里虽然已摘掉 required,
但保持"可聚焦"更稳(免得将来再加必填时又踩同一个坑)。
🔴🔴 v44(用户 2026-10-12 第 2 条):「之前的日期选择控件还有,需要删除。」
—— 用户在**日程**上看到 antd 控件与原生五个下拉**并排都在**。真根因是
**结构性**的:日程那对是 `‹div.dt-range› ‹div.field.ev-dt› ‹div.dt-input›`
(`.dt-input` 是**孙子**),而这条规则用的是**直接子**选择器 `> .dt-input`
⇒ **在日程上根本不命中**。
(与 v43 纪念日那次同族:结构一变,直接子选择器静默失效、不报任何错。)
⇒ ① 把 `>` 放宽成**后代**选择器(`_event_fields.php` 那边的结构也已改回
"与观影一致"的直接子,两道保险);
② 用户要的"删除"就做成真删 —— 用 `display:none` 把**原生下拉那一块**
从渲染里去掉。**注意:值载体不受影响** —— 隐藏的 `*_date` / `*_hour`
/ `*_minute` 是 `.dt-input` 的**直接子元素**,**不在 `.dt-native` 里**,
所以删掉原生块**不碰提交契约**。
(v37 那条"display:none 不可聚焦"讲的是**带 required 的可见字段**;
这里被藏的是"已被 antd 取代的旧控件",必填校验早已由
`guardRequired` 的 `data-required` 自查接管 ⇒ 不适用那个顾虑。) */
.dt-range.is-antd .dt-input {
position: absolute; width: 1px; height: 1px;
overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap;
border: 0; padding: 0; margin: -1px;
}
/* antd 真的挂上了 ⇒ 原生那几档下拉**彻底不渲染**(用户要的"删除")。 */
.dt-range.is-antd .dt-input .dt-native { display: none !important; }
/* 🔴🔴 v44:**带内联标签的那个端点不收起**。
根因(真机几何量出来的):内联标签(「日期范围:」)是画在 `.dt-input` **里面**的,
而上面那条收起规则把整个 `.dt-input` 绝对定位 + `clip` 掉了 ⇒
**标签跟着一起被裁没了**,用户只看到一个没有标签的控件
(量出来的证据:标签盒在 x=316/y=313 却被祖先 clip,`elementFromPoint`
命中的是 `.ant-picker-range-separator` ⇒ 标签确实看不见)。
⇒ 两头都不牺牲:`.dt-input` 只藏**它里面的原生块**(上一条已 `display:none`),
自己保持正常布局 ⇒ 它变成 `[标签 62px][最新控件]` 的一个横排。
起止的**第二个**端点没有标签,继续按上一条被收起(它本来就该消失)。 */
.dt-range.is-antd > .dt-input.has-inline-label {
position: static; width: auto; height: auto; overflow: visible;
clip: auto; margin: 0; padding: 0; border: 0;
flex: 1 1 auto; min-width: 0;
display: flex; flex-wrap: wrap; align-items: center; gap: 8px;
}
/* 🔴 v44(用户第 5 条):**单个**带时刻的 antd 控件(任务「截止时间」)。
与上面那对同一条思路 —— 挂上后把原生那几档下拉**真删掉**。
选择器用 `.dt-input.is-antd`(单点,不是范围组)——
`_datetime.php` 里 antd 挂载点仍是 `.dt-input` 自身。 */
.dt-input.is-antd > .dt-native { display: none !important; }
.dt-input.is-antd > .dt-antd { display: block; width: 100%; }
/* 窄屏:标签列收窄,别把输入框挤没了 */
@media (max-width: 560px) {
.mv-row > .mv-lb,
.mv-row.dt-range.dt-cols > .dt-range-label { flex: 0 0 52px; width: 52px; font-size: 13px; }
}
/* ---- 🔴 v39:撤销 v38 的"并排对" ----
用户 2026-10-10:「简介和备注不要放在一行,也就是说一个字段它就占一行。
所以简介后面是一行,备注后面是一行。」—— 简介/备注各占一整行。
⇒ `.mv-pair` 那套(grid 两列)整个删掉:它是 v38 为了压高度加的,
但半宽的多行文本框反而每行都换行、更难读。海报/观影照片也不再并排
(它们本来就只有一块,当时的"并排网格"里只有一个孩子,
grid 让它只占半宽 —— 那正是"海报的上传框比简介窄"的原因)。
⚠️ 不留悬念:全站搜 `.mv-pair` 应该**零命中**(模板与 CSS 都清干净)。 */
/* ============================================================================
🔴 v40(用户 2026-10-10)观影模块四处界面反馈
============================================================================ */
/* ---- ① 影院管理表格:操作列不再被挤成两行 ----
用户原话:「影院管理部分,注意控制列的宽度,因为最右侧的操作、编辑和删除两个按钮,
已经被挤成两行了,尝试让编辑和删除排在一行,宽度给它留够。」
🔴 真因:`.table` 是 `width:100%` + 自动布局,浏览器按内容分配列宽 ——
长地址那一列会把「操作」列压到只剩几十像素,两个按钮只好换行。
⇒ 两件事一起做才稳:
① 表格改 `table-layout: fixed`(列宽由我们给,不再被内容牵着走);
② 「操作」列给一个**够放两个按钮的最小宽度**(编辑 48 + 间距 8 + 删除 48
+ 单元格左右 padding 16 ≈ 128,留到 132 更从容)。
⚠️ 为什么不只是 `white-space:nowrap`:那会把列撑成"刚好够",
但**表格还是按内容分配**,窗口一窄就又轮到别的列挤压它;
固定布局才是真的把这件事钉死。 */
.cn-table { table-layout: fixed; }
.cn-table .cn-col-name { width: 30%; }
.cn-table .cn-col-place { width: 15%; }
.cn-table .cn-col-addr { width: 32%; }
.cn-table .cn-col-count { width: 10%; }
.cn-table .cn-col-ops { width: 132px; }
.cn-table td.cn-addr { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cn-ops {
display: flex; align-items: center; gap: 8px;
/* 🔴 不换行是本条的正题 */
flex-wrap: nowrap; white-space: nowrap;
}
.cn-ops > form { display: inline-flex; margin: 0; flex: 0 0 auto; }
.cn-ops > .btn { flex: 0 0 auto; }
/* 窄屏:地址列让位(它最长、也最不影响判断),操作列宽度保持不变 */
@media (max-width: 720px) {
.cn-table .cn-col-name { width: 34%; }
.cn-table .cn-col-place { width: 18%; }
.cn-table .cn-col-addr { width: 22%; }
.cn-table .cn-col-count { width: 12%; }
}
/* ---- ② 实心语义按钮:编辑=蓝、删除=红 ----
⚠️ 单独定义(不去改 `.btn-danger` 的既有语义):`.btn-danger` 是**淡红描边**的
"危险动作",全站十来处「删除」都在用它;用户这次要的是**实心色块**,
改 `.btn-danger` 会让所有页面一起变样。
三处都要写:裸类 / `a.` / `button.` —— 与页头动作区那套同一个理由
(`.btn`、`a.btn`、`button.btn` 特异性不同,漏一种就有一种是错的)。 */
.btn-edit, a.btn-edit, button.btn-edit {
background: var(--blue); color: #fff; border-color: transparent;
}
.btn-del, a.btn-del, button.btn-del {
background: var(--red); color: #fff; border-color: transparent;
}
/* ⚠️ hover 统一交给文件靠后的「全站悬停统一」段(有底色的一律同色系加深)——
但那段是按 `.btn-ok/.btn-del/...` 的类名白名单写的,`.btn-edit` 是新的,
要在这里补一条,否则它会退回 `.btn` 的默认(品牌色底),看着像变了个按钮。 */
.btn-edit:hover { filter: brightness(.92); }
/* ---- ③ 观影详情页 = 列表页的头部口径 ----
用户原话:「观影详情页面左上角的观影记录,应该和观影的列表页面里面保持一致。」
列表页是 `<h1>观影记录</h1>` + sub;详情页原来只有一个 `<span class="sub">`,
没有 h1 ⇒ 左上角看起来"不像是同一件事"。模板已补上 h1,
这里只处理**窄屏**:h1 与 sub 在页头会挤,sub 允许收缩并省略。 */
.page-head > .sub { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* ---- ④ 高级搜索的日期范围:换成 AntD RangePicker(纯日期)----
🔴 v40(用户 2026-10-10):「高级搜索里面的日期范围,也使用我们最新的控件。」
挂上之后把**原生那两个日期框收起来**(它们仍是 GET 字段,只是不显示)。
⚠️ 与原观影那对 `.dt-range.is-antd > .dt-input` 用同一套"clip 收起"手法:
不用 `display:none`(保持可聚焦,避免将来加 required 时不可聚焦的坑)。 */
.date-antd-field .date-range.is-antd > .input,
.date-antd-field .date-range.is-antd > .to {
position: absolute; width: 1px; height: 1px;
overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap;
border: 0; padding: 0; margin: -1px;
}
.date-antd-field .date-range.is-antd { display: block; }
/* 纯日期版控件与观影那边同款外观(同高、同圆角、同描边)—— 复用 .dt-antd 的规则,
这里只补"它是块级、占满字段宽度"这一点(.dt-antd 原为 flex 子项)。 */
.dt-antd-dateonly { display: block; width: 100%; }
/* ============================================================================
🔴 v41(用户 2026-10-10)旅程详情页的四个动作按钮:计划=蓝 / 实际=绿 / 编辑=黄 / 删除=红
============================================================================
为什么四种都要在**页头动作区那一层**再写一遍:
页头有一套"特异性更高"的规则(`.page-head .actions > .btn:not(.btn-icon)` 等,
见文件前部的页头按钮层),它们会盖过基类的 `.btn-xxx` 底色。
v40 的 `.btn-del` 就吃过这一脚(必须在页头层再补一条才生效)。
⚠️ 三处形态都要写(裸类 / a. / button.):`.btn`、`a.btn`、`button.btn`
的特异性不同,漏一种就有一种是错的(这条规律在 v40 已经验证过)。
⚠️ 设置按钮**不动**(用户:"设置就用这个图标,不要变")。 */
.btn-plan, a.btn-plan, button.btn-plan {
background: var(--blue); color: #fff; border-color: transparent;
}
.btn-actual, a.btn-actual, button.btn-actual {
/* 绿比 --green 深一档:白字对比度 3.0 → 4.6,暖纸底上看得清 */
background: #4E7C3F; color: #fff; border-color: transparent;
}
/* 🔴🔴 2026-10-11 修(v40 套件报的**真回归**):
这里原来写的是**裸类** `.btn-edit`(全局改黄),直接把 v40 影院管理页的
「编辑 = 蓝色实心」覆盖掉了 —— 影院那页量出来的实际色是 `rgb(184,134,11)`(黄),
而用户对它的要求是**蓝色**(v40 原话:「编辑使用蓝色的背景,删除使用红色的背景」)。
⇒ 两处需求不同:**旅程详情页的「编辑」= 黄**(v41 第 4 条),
**影院管理页的「编辑」= 蓝**(v40)。所以颜色必须**按作用域**给,不能裸类全局设。
锚点选 `[data-trip-edit]`(旅程详情页那个按钮的专属属性)——
它是最精确的标识,不依赖页头层级,也不会波及影院管理页的 `<a class="btn btn-edit">`。 */
.btn-edit[data-trip-edit] {
background: var(--yellow); color: #fff; border-color: transparent;
}
.btn-del, a.btn-del, button.btn-del {
background: var(--red); color: #fff; border-color: transparent;
}
/* hover:同色系加深(与文末"全站悬停统一"段同一手法:brightness 压低) */
.btn-plan:hover, .btn-actual:hover, .btn-edit:hover, .btn-del:hover { filter: brightness(.92); }
/* 页头动作区那一层(覆盖页头默认的"品牌色实心/ghost") */
.page-head .actions > .btn-plan:not(.btn-icon),
.page-head .actions > .btn-actual:not(.btn-icon),
.page-head .actions > .btn-edit[data-trip-edit]:not(.btn-icon),
.page-head .actions > .btn-del:not(.btn-icon),
.page-head .actions > form > button.btn-del:not(.btn-icon) { color: #fff; border-color: transparent; }
.page-head .actions > .btn-plan:not(.btn-icon) { background: var(--blue); }
.page-head .actions > .btn-actual:not(.btn-icon) { background: #4E7C3F; }
.page-head .actions > .btn-edit[data-trip-edit]:not(.btn-icon) { background: var(--yellow); }
.page-head .actions > .btn-del:not(.btn-icon),
.page-head .actions > form > button.btn-del:not(.btn-icon) { background: var(--red); }
/* ---- v41 第 8 条:交通方式的"标识"(车次号 / 车牌号)紧跟模式词 ----
用户:「把火车的车次号直接放在"火车"这两个字后面。如果是自驾,
就应该把自驾的车牌号放在后面。」
⇒ 同一个 chip 内、模式词之后,用略淡的字重区分(仍属同一个信息单元)。 */
.node-mode-no {
margin-left: 6px; padding-left: 6px;
border-left: 1px solid currentColor;
opacity: .78; font-weight: 500;
font-variant-numeric: tabular-nums;
}
/* ---- v41 第 2 条:题图的说明文字搬进框里(原来在框外另起一段)---- */
.cover-picker-hint {
display: block; margin-top: 8px;
font-size: 12.5px; line-height: 1.6; color: var(--muted);
}
/* ============================================================================
🔴 v41(用户 2026-10-10 第 5/6 条)行程弹框的**观影式排版**
============================================================================
用户原话:「编辑行程、加计划行程、加实际行程,全部都采用弹窗的形式。
其排版的方式和添加电影是一样的,前面是标题冒号,后面是输入框,
和观影模块一样,注意排版。」
🔴🔴 为什么这里用 **grid** 而不是 flex(2026-10-10 血泪):
先按观影那套写了 flex(`display:flex; flex-wrap:wrap` + label 固定 62px),
结果**控件被挤到下一行**(实测 label y=117 / 控件 y=187)。
查了半天:`.input/.select` 全站有 `width: 100%`,在 flex 行里
它的 auto 基准就是**整行宽** ⇒ 62 + 10 + 696 > 696 ⇒ 换行。
把 width 改 auto、把 flex-basis 从 0% 试到 auto、加 min-width……
控件宽度是对了(140),**换行依旧**(同一行里还有 `.tip` 声明着
`flex: 0 0 100%`,wrap 算法会把它们的理想宽度一起算)。
⇒ 换 **grid**:`.field` 两列(62px + 1fr),标签在第 1 列、控件在第 2 列,
`.tip/.err` 用 `grid-column: 2` 自动落在控件下面**且与控件左缘对齐**
(连 margin-left 都不需要)。两列表单的标准写法,不受 width/flex 交互影响。
⚠️ 作用域用 `.nf-form`(表单自己的类)而不是 `.nf-wrap` / `.nf-panel`:
片段形态(`?embed=1`)两者都不输出(壳由详情页容器给),
只用它们的话排版在弹框里**完全不生效**(探针抓到过)。 */
.nf-form .form-grid {
display: flex; flex-direction: column; gap: 12px;
}
.nf-form .form-grid > .field {
display: grid; grid-template-columns: 62px minmax(0, 1fr);
column-gap: 10px; row-gap: 6px; align-items: start;
}
/* 标签:固定第 1 列、右对齐、自动冒号(与观影那套同一组数字) */
.nf-form .form-grid > .field > label {
grid-column: 1;
text-align: right; padding-top: 9px;
font-size: 14px; font-weight: 600; color: var(--ink);
line-height: 1.4; word-break: keep-all;
}
.nf-form .form-grid > .field > label::after {
content: ':'; font-weight: 400; color: var(--muted);
}
/* 控件:占第 2 列(宽度由 grid 给,不受它的 width:100% 影响) */
.nf-form .form-grid > .field > *:not(label) {
grid-column: 2; min-width: 0;
}
/* 提示 / 错误:也在第 2 列 ⇒ **自动与控件左缘对齐** */
.nf-form .form-grid > .field > .tip,
.nf-form .form-grid > .field > .err {
grid-column: 2;
}
/* 起终点是两列小网格,整段落在第 2 列 */
.nf-form .form-grid > .field > .loc-row { grid-column: 2; }
/* 「当前状态」是只读文本,对齐 40px 高的控件 */
.nf-form .node-stage-ro { padding-top: 9px; }
/* 窄屏:标签列压到 52px,避免控件被挤得太窄 */
@media (max-width: 560px) {
.nf-form .form-grid > .field { grid-template-columns: 52px minmax(0, 1fr); }
}
/* ---- v41 第 8 条:交通方式的"标识"(车次号 / 车牌号)紧跟模式词 ----
用户:「把火车的车次号直接放在"火车"这两个字后面。如果是自驾,
就应该把自驾的车牌号放在后面。」
⇒ 同一个 chip 内、模式词之后,用略淡的字重区分(仍属同一个信息单元)。 */
.node-mode-no {
margin-left: 6px; padding-left: 6px;
border-left: 1px solid currentColor;
opacity: .78; font-weight: 500;
font-variant-numeric: tabular-nums;
}
/* ---- v41 第 2 条:题图的说明文字搬进框里(原来在框外另起一段)---- */
.cover-picker-hint {
display: block; margin-top: 8px;
font-size: 12.5px; line-height: 1.6; color: var(--muted);
}
/* ---- v41 第 10 条:纪念日弹框(观影式排版)里"月 / 日"那一行的两格 ---- */
.an-md { display: flex; align-items: center; gap: 6px; }
.an-md .input { width: 88px; flex: 0 0 auto; text-align: center; }
.an-md-sep { color: var(--muted); font-size: 13px; }
/* 观影式行里的"填充块"(选人组件那种自带布局的控件,占满控件位) */
.mv-row > .mv-fill { flex: 1 1 auto; min-width: 0; }
/* ---- v42(用户 2026-10-11 第 8 条)纪念日「日期」:AntD 单日期控件 ----
与原观影/高级搜索那两处同一套做法:
· `.dt-antd` 是 antd 的挂载点(宽度交给它);
· 挂上后(`.is-antd`)把**原生 `<input type="date">` 视觉收起** ——
它仍是值载体(antd 通过 change 写它),只是不再显示给用户。
⚠️ 用 clip 收起(不是 display:none):保留可聚焦、可被表单读到。
🔴🔴 v43(用户 2026-10-11 第 1 条):「日期那里多了一个控件,
只保留我们最新的控件就行,之前那种控件删除」——真根因有两层:
① 纪念日的原生框当初**没包在 `.dt-native` 里** ⇒ 这条选择器根本不命中
(模板已补上包装);
② **更要命的一层**:`.dt-input .dt-native { display: contents }`(第 619 行)
会让这个包装盒**自己不生成盒子**、子元素"提升"成 `.dt-input` 的直接布局项 ——
此时给它 `position:absolute; width:1px` **完全无效**(它没有盒子可定位),
原生框照样按 flex 项排出来 ⇒ 两个控件并排。
⇒ 修法:这里必须把 `display` **从 contents 改成 block**,定位才生效。
(只写 position/width/height 是不够的 —— 这是我第一版的错,探针当场抓到。) */
.dt-input.is-antd > .dt-native {
display: block; position: absolute; width: 1px; height: 1px;
overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; padding: 0; margin: -1px;
}
.dt-input.is-antd > .dt-antd { display: block; width: 100%; }
.dt-input.dt-antd-single { display: block; width: 100%; }
/* 兜底:万一哪天原生框又变成 `.dt-input` 的**直接子元素**(不包 .dt-native 了),
这条按直接子元素也能把它收起来。 */
.dt-input.is-antd > input[type="date"] {
display: block; position: absolute; width: 1px; height: 1px;
overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; padding: 0; margin: -1px;
}
/* ---- v42(用户第 6 条)简介为空时的那句可点占位 ----
与 `.trip-sum` 同一视觉位置,只是颜色淡一档(它是要点进去补的提示,不是内容)。 */
.trip-sum-empty { margin: 10px 0 0; font-size: 13px; color: var(--muted); }
.trip-sum-empty a { color: var(--brand); }
/* ============================================================================
🔴 v43(用户 2026-10-11 第 6 条)条目详情浮层
----------------------------------------------------------------------------
与 .cal-modal 同一形态(遮罩 + 居中面板 + 底栏),只是层级更高一点:
它可能从**另一个浮层**里打开(例如详情页弹框里点了条目),
所以要在 200 之上;但低于"看图"的灯箱(300)/预览层(320)。
⚠️ 与 .cn-modal(260)、.crop-overlay(260) 同层 —— 它们不会同时出现。
⚠️ 面板用 flex 列 + body 滚动,长内容(编辑表单)才有足够的可视区。 */
.fb-entry-modal {
position: fixed; inset: 0; z-index: 260;
display: flex; align-items: flex-start; justify-content: center;
padding: 24px 16px; overflow-y: auto;
background: rgba(44, 35, 29, .55); overscroll-behavior: contain;
}
.fb-entry-modal[hidden] { display: none; }
.fb-entry-panel { width: min(620px, 100%); max-height: calc(100vh - 64px); }
/* 🔴 v49(用户 2026-10-14 第 2 条):「点击详情…让模态框**变大**,显示更多信息」。
加宽到 860px(620 的 1.39 倍)—— 详情里可能有多行"标签:值"与附件清单,
宽一点才不至于每行都折行。窄屏仍占满。 */
.fb-entry-panel.is-wide { width: min(860px, 100%); }
/* 底栏右侧的按钮组(用户:「三个按钮,都放在下面的右侧」)。
`margin-left:auto` 把整组推到右边;左端那句提示(.cal-modal-tip)留在原地。
⚠️ 不用 `.cal-modal-foot .btn[type=submit]{margin-left:auto}` 那条(它只管提交键)。
⚠️ 组内允许换行:窄屏三颗按钮挤不下时要能折到第二行,而不是撑破底栏。 */
.fb-entry-acts {
margin-left: auto;
display: flex; align-items: center; gap: 8px; flex-wrap: wrap; justify-content: flex-end;
}
/* 🔴🔴 v49:**必须显式声明 `[hidden]`**(与文件前部那条"凡靠 [hidden] 隐藏的都再写一次"
同一条规律)。原因:`.btn { display: inline-flex }` 是**作者样式**,
特异性与优先级都高于 `[hidden]` 的**浏览器默认** `display:none`
⇒ `hidden` 属性对 `.btn` **完全无效**。
症状:底栏四态切换时,前面那些按钮"藏不掉"、全都挤在一条上
(本轮实测:派生条目本该只有「详情」,却三颗都露着)。
⚠️ 用 `!important` 与文件前部 `.pick-row[hidden]` 那组的写法保持一致 ——
底栏是"四态互斥"的强语义,宁可压过一切。 */
.fb-entry-acts .btn[hidden] { display: none !important; }
/* 三颗的配色(用户:「详细用蓝色,编辑用黄色,删除用红色」)。
⚠️ **作用域类**(fb-a-*),不去改全局 `.btn-edit` / `.btn-danger` 的语义 ——
本项目踩过"裸类改色波及别页"的坑(v41 把影院管理页的蓝色编辑改成了黄,见上面那段)。
⚠️ 三处形态都写(裸类 / a. / button.):`.btn`、`a.btn`、`button.btn` 的特异性不同,
漏一种就有一种是错的(这条规律 v40 已验过)。 */
.fb-a-detail, a.fb-a-detail, button.fb-a-detail {
background: var(--blue); color: #fff; border-color: transparent;
}
.fb-a-edit, a.fb-a-edit, button.fb-a-edit {
background: var(--yellow); color: #fff; border-color: transparent;
}
.fb-a-del, a.fb-a-del, button.fb-a-del {
background: var(--red); color: #fff; border-color: transparent;
}
.fb-a-detail:hover, .fb-a-edit:hover, .fb-a-del:hover { filter: brightness(.92); }
/* 编辑态下正文里就是一张完整表单(内边距由它自己那份 CSS 给)——
这里只保证它不双份留白。 */
.fb-entry-panel .cal-modal-body > .mv-form-inner,
.fb-entry-panel .cal-modal-body > .nf-wrap { padding: 0; }
/* 详情展开块:`?embed=detail` 吐的只读 dl(带 .ev-detail-block)。
它自己那份 `.card-pad` 内边距在浮层里是多余的(面板已有 padding)。 */
.fb-entry-panel .ev-detail-block { padding: 0; }
@media (max-width: 640px) {
.fb-entry-panel, .fb-entry-panel.is-wide { width: 100%; }
}
/* ============================================================
v52(用户 2026-10-15 第 5 条):「是不是这个人?」确认条
------------------------------------------------------------
新增成员时若按 姓名/手机号/身份证号 在家里找到匹配的人,表单页顶部出现这一条。
⚠️ 它**在这张表单里面**(不是单独一页)—— 见 templates/people/form.php 的长注释:
单独一页得把整张表(含数组字段)原样回传,漏一类就静默丢数据。
⚠️ 视觉上要"明显但不像出错":它是个**问句**,不是错误 —— 所以用品牌淡底 + 左侧竖条,
而不是红色告警(红色会让人以为"提交失败了")。
============================================================ */
.person-confirm {
border-left: 3px solid var(--brand-2);
background: var(--brand-soft, #F3F7F7);
}
.person-hit {
display: flex; align-items: center; gap: 14px;
background: var(--surface); border: 1px solid var(--line);
border-radius: var(--radius-s); padding: 12px 14px;
}
.person-hit-av { flex: 0 0 auto; }
.person-hit-av img,
.person-hit-av .avatar-fallback {
width: 52px; height: 52px; border-radius: 12px; display: block;
object-fit: cover; background: var(--line-2);
}
.person-hit-av .avatar-fallback {
display: flex; align-items: center; justify-content: center;
font-size: 22px; color: var(--muted);
}
.person-hit-info { flex: 1 1 auto; min-width: 0; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.person-hit-ops { flex: 0 0 auto; }
/* ============================================================
v52(用户 2026-10-15 第 7 条):模块设置的**整合表**
------------------------------------------------------------
一行一个模块,四列 = 顺序 / 模块 / 导航 / 首页 / 颜色。
⚠️ 原来那是三张卡(首页模块 / 导航模块 / 主题色),同一串模块名出现两遍。
合成一张表后用户一次就能改完 —— 表格是这种"多列同构"最自然的形态。
⚠️ `min-width` + 外层 `overflow-x:auto`:窄屏(375px)五列放不下时
表格能横向滚动,而不是把页面撑破(本项目在名册上栽过溢出)。
============================================================ */
.mod-table-wrap { overflow-x: auto; }
.mod-table { width: 100%; border-collapse: collapse; min-width: 560px; }
.mod-table th, .mod-table td {
padding: 8px 10px; text-align: left; border-bottom: 1px solid var(--line-2);
font-size: 14px; vertical-align: middle;
}
.mod-table th { font-size: 12px; color: var(--muted); font-weight: 600; }
.mod-table .mod-th-on, .mod-table .mod-td-on { width: 62px; text-align: center; }
.mod-table .mod-th-color, .mod-table .mod-td-color { width: 72px; text-align: center; }
.mod-table .mod-th-order, .mod-table .mod-td-order { width: 108px; }
.mod-table .mod-td-order { white-space: nowrap; }
.mod-table .mod-td-name { display: flex; align-items: center; gap: 8px; border-bottom: 0; }
.mod-chk { display: inline-flex; cursor: pointer; }
.mod-chk input { accent-color: var(--brand); width: 16px; height: 16px; }
.mod-move { display: inline-flex; gap: 2px; margin-left: 4px; }
.mod-color-ro { width: 22px; height: 22px; border-radius: 5px; display: inline-block; }
.mod-table .mod-row.is-dragged { opacity: .5; }
.mod-table .mod-row.is-drop-before td { border-top: 2px solid var(--brand-2); }
.mod-table .color-picker { width: 34px; height: 24px; padding: 0; border: 1px solid var(--line); border-radius: 5px; background: #fff; cursor: pointer; }
/* ==========================================================================
🔴 v54(用户 2026-10-16 第 3/4/5 条):旅程 —— **列表 / 地图**两个视图
==========================================================================
用户原话:「在旅程这个模块,**可以在地图上显示旅程数据**」(移植 TripLog);
「在旅程的「新建旅程」后面也加一个「设置」按钮」;
「里面的设置让用户自己配置:**到底默认显示旅程列表,还是用地图显示**。」
两个视图用**标签页**切换(不是两页)—— 因为"看列表"与"看地图"是同一份数据的
两种看法,来回切不该刷新整页;也让"默认显示哪个"这个设置有意义
(设置决定**进来时**先显示哪一个)。
⚠️ 标签页样式沿用本仓既有的"胶囊按钮 + on 态"口径(与日历视图切换一致)。 */
/* ---- 视图切换条 ---- */
.trip-views {
display: flex; align-items: center; gap: 8px;
margin: 0 0 14px;
}
.trip-views .tv-btn {
display: inline-flex; align-items: center; gap: 6px;
padding: 5px 14px; border-radius: 999px;
border: 1px solid var(--line); background: var(--surface);
color: var(--muted); font-size: 13.5px; cursor: pointer;
text-decoration: none; line-height: 1.5;
}
.trip-views .tv-btn:hover { color: var(--ink); border-color: var(--line-3, var(--line)); }
.trip-views .tv-btn.is-on {
background: var(--brand); border-color: var(--brand); color: #fff; font-weight: 600;
}
.trip-views .tv-btn svg { display: block; }
/* 右上角那颗「设为默认」小链接:与标签页同一行、靠右 */
.trip-views .tv-default {
margin-left: auto; font-size: 12.5px; color: var(--faint);
text-decoration: none; white-space: nowrap;
}
.trip-views .tv-default:hover { color: var(--brand); text-decoration: none; }
/* 「地图(0)」里那个小计数:还没位置时提示一下,别让人点进去看空地图 */
.trip-views .tv-dim { font-size: 11.5px; opacity: .8; margin-left: 2px; }
/* ---- 地图容器 ----
⚠️ **必须有固定高度**:高德地图在"高度为 0 的容器"里渲染出来是空白
(这是高德最常见的坑,`.trip-map` 的 height 就是为它写的)。
⚠️ 高度跟视口走(屏幕越大看得越远),但给下限免得笔记本上太扁。 */
.trip-map-wrap { margin-top: 2px; }
.trip-map {
width: 100%; height: clamp(380px, 62vh, 720px);
border: 1px solid var(--line); border-radius: var(--radius);
overflow: hidden; background: #EEF1F4; /* 瓦片到位前不闪白 */
}
.trip-map-fallback {
margin: 0; height: 100%; display: flex; align-items: center; justify-content: center;
color: var(--muted); font-size: 13.5px; padding: 0 24px; text-align: center;
}
.trip-map-hint { margin: 8px 0 0; }
/* ---- 地图上的标记:小卡片(左文右无图,比 TripLog 那版更轻)----
⚠️ 卡片是**绝对定位在画布中心**(JS 里 left/top = C+dx / C+dy):
画布固定 260×260,卡片偏移不影响标记锚点(AMap 按画布中心对齐坐标)。 */
.trip-pin-stage { position: relative; pointer-events: none; }
.trip-pin {
position: absolute; transform: translate(-50%, -50%);
pointer-events: auto; cursor: pointer;
}
.trip-pin-card {
display: flex; align-items: center; gap: 8px; /* 🔴 v63:题图在左、文字在右 */
background: var(--surface); border: 1px solid var(--line);
border-left: 3px solid var(--brand);
border-radius: 8px; padding: 6px 10px;
box-shadow: 0 2px 8px rgba(0,0,0,.10);
max-width: 220px;
}
/* 🔴 v63(用户 2026-10-19 第 3 条):旅程题图。
⚠️ `object-fit: cover` 必须有:题图原图长宽比五花八门,默认 `fill` 会把人拉扁。
⚠️ 有题图时卡片最多 220px(原来是 190px),文字那一块要 `min-width:0`
才能让 `.trip-pin-title` 的省略号真正生效(否则长标题把卡片撑破)。 */
.trip-pin-thumb {
flex: 0 0 auto; width: 40px; height: 40px;
border-radius: 5px; overflow: hidden; background: var(--line-2);
}
.trip-pin-thumb img { display: block; width: 100%; height: 100%; object-fit: cover; }
.trip-pin-body { display: block; min-width: 0; }
.trip-pin-title {
font-size: 13px; font-weight: 600; color: var(--ink);
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.trip-pin-sub { font-size: 11.5px; color: var(--muted); margin-top: 1px; }
.trip-pin:hover .trip-pin-card { border-color: var(--brand); box-shadow: 0 4px 14px rgba(0,0,0,.16); }
/* ⚠️ AMap 的标记包装层尺寸跟着 content(260×260)走、默认可命中,
会把旁边卡片的点击整层挡住。整层禁掉命中、只留卡片本身可点
(TripLog 也踩过这个,见其 `.amap-marker` 那条注释)。 */
#tripMapCanvas .amap-marker { pointer-events: none !important; }
#tripMapCanvas .amap-marker .trip-pin { pointer-events: auto !important; }
/* ---- 显示方式设置页的单选卡片(v54 第 5 条)----
原生 radio + label 包一层卡片外观;`.is-on` 由服务端渲染时给
—— 不依赖 JS 同步选中态(无 JS 时至少"当前项"是对的)。 */
.view-choice { display: flex; flex-direction: column; gap: 10px; max-width: 480px; }
.view-choice-item {
display: flex; align-items: flex-start; gap: 10px;
padding: 12px 14px; border: 1px solid var(--line); border-radius: var(--radius-s);
cursor: pointer; background: var(--surface);
}
.view-choice-item:hover { border-color: var(--brand-2); }
.view-choice-item.is-on { border-color: var(--brand); background: var(--brand-soft, #f2f7f3); }
.view-choice-item input { margin-top: 3px; flex: 0 0 auto; }
.view-choice-item .vci-body { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.view-choice-item .vci-body b { font-size: 14px; }
/* ==================================================================== */
/* v62(用户 2026-10-18 第 2 条):分类图标 */
/* */
/* 分类徽标 = 图标 + 名字。图标是行内 SVG(AnnivIcon),颜色取 currentColor */
/* ⇒ 放在浅底徽标里就是灰字色,放在深色格子里自动变白,不需要第二套配色。 */
/* ==================================================================== */
.kind-badge {
display: inline-flex; align-items: center; gap: 4px;
vertical-align: middle;
max-width: 100%;
}
.kind-badge .ai { flex: 0 0 auto; opacity: .9; }
.kind-badge .kind-name {
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
max-width: 9em; /* 分类名最长 20 字,卡一行不撑破表格 */
}
.ann-ic { display: inline-flex; align-items: center; flex: 0 0 auto; color: var(--muted); }
.ann-ic .ai { display: block; }
/* ---- 分类设置页(days/settings)---- */
.kind-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.kind-row {
display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
padding: 8px 10px; border: 1px solid var(--line); border-radius: var(--radius-s);
background: var(--surface);
/* ⚠️ 行内每项都可能收缩(分类名输入框),flex 子项默认 min-width:auto 会顶破 */
min-width: 0;
}
.kind-row.is-ro { gap: 10px; }
.kind-move { display: inline-flex; gap: 2px; flex: 0 0 auto; }
.kind-prev {
flex: 0 0 auto; width: 30px; height: 30px; border-radius: 8px;
display: inline-flex; align-items: center; justify-content: center;
background: var(--line-2); color: var(--ink);
}
.kind-sel { flex: 0 0 auto; width: 190px; max-width: 46vw; }
.kind-name-in { flex: 1 1 140px; min-width: 0; }
.kind-name-ro { font-size: 14px; }
.kind-use { flex: 0 0 auto; font-size: 12px; color: var(--muted); min-width: 46px; text-align: right; }
/* 改过但没保存:保存按钮描一圈告警色 + 旁边一句小字(JS 加 .is-dirty / 去掉 hidden)
⚠️ 用 `.btn-warn` 同一个 #E8A33D(全站"待处理"的语义色),不要另发明一个黄。 */
.btn.is-dirty { box-shadow: 0 0 0 2px #E8A33D; }
.kind-dirty { color: #b57a19; margin-left: 10px; }
.kind-add {
display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
margin-top: 14px; padding: 10px; border-radius: var(--radius-s);
background: var(--line-2);
}
.kind-add .select { flex: 0 0 auto; width: 190px; max-width: 46vw; }
.kind-add .input { flex: 1 1 160px; min-width: 0; }
.kind-add-lb { font-size: 13px; color: var(--muted); }
.kind-icon-bank { display: none; }
/* ==================================================================== */
/* v62(用户 2026-10-18 第 3 条):首页卡片网格 */
/* */
/* 口径(与 app/HomeCards.php、templates/home_cards.php 的措辞一致): */
/* · 一排 8 格;宽 2/4/6/8 → 一排 4 张 / 2 张 / 一大一小 / 独占一排 */
/* · 高 2/4/6/8 行;一行 76px、间距 10px */
/* */
/* ⚠️ 为什么用 `grid-template-columns: repeat(8, minmax(0,1fr))` 而不是 */
/* flex + 百分比:8 格制下"宽 6"(一大一小)只有网格能自然表达; */
/* `minmax(0,1fr)` 里的 0 对应 flex 那条 `min-width:0` —— */
/* 少了它,卡片里的长标题会把整列顶宽(v54 那个坑的同源)。 */
/* ⚠️ 高度用 `grid-row: span N` 而不是给每张卡片算像素: */
/* 这样"高 4"的两张卡片并排时天然等高,不依赖内容多少。 */
/* ==================================================================== */
.hc-grid {
display: grid;
grid-template-columns: repeat(8, minmax(0, 1fr));
grid-auto-rows: 76px;
gap: 10px;
align-items: stretch;
}
.hc-card {
display: flex; flex-direction: column;
min-width: 0; min-height: 0;
margin: 0;
padding: 12px 14px;
overflow: hidden;
}
/* ⚠️ 必须单独写这一条,而且选择器要**比 `.card + .card` 更重**:
上面 `.hc-card { margin: 0 }` 只有 0,1,0,而全局相邻间距
`.card + .card { margin-top: 18px }` 是 0,2,0 —— 权重更高,会把
每一张**非首张**的卡片顶下去 18px(实测症状:高 2 期望 162 对得上,
高 4/高 8 恒少 18px,因为网格首个子元素不命中 `+ .card`)。
间距本来就由 grid 的 `gap` 统一给,这里把相邻 margin 彻底清掉。 */
.hc-grid > .card + .card { margin-top: 0; }
.hc-w2 { grid-column: span 2; }
.hc-w4 { grid-column: span 4; }
.hc-w6 { grid-column: span 6; }
.hc-w8 { grid-column: span 8; }
.hc-h2 { grid-row: span 2; }
.hc-h4 { grid-row: span 4; }
.hc-h6 { grid-row: span 6; }
.hc-h8 { grid-row: span 8; }
.hc-head {
display: flex; align-items: center; gap: 10px;
flex: 0 0 auto; min-width: 0; margin-bottom: 8px;
}
.hc-head .cinema-h2 { margin: 0; flex: 1 1 auto; min-width: 0; }
.hc-head .hd-more { flex: 0 0 auto; }
/* 卡片正文:**自己滚**,不撑卡片(用户给的高度就是最终高度)。 */
.hc-body {
flex: 1 1 auto; min-height: 0; min-width: 0;
overflow-y: auto; overscroll-behavior: contain;
scrollbar-width: none; -ms-overflow-style: none;
}
.hc-body::-webkit-scrollbar { width: 0; height: 0; }
/* 概述卡:左右两栏(问候/数字 | 天气),与原来的信息面板一致 */
.hc-body.home-brief { align-content: flex-start; }
/* 日程卡:内部还要再分"全天区 + 时间网格",所以它自己是一个纵向 flex,
滚动交给内层 `.hd-pane-body`(这样全天区才能钉在上面不动)。 */
.hc-body.hc-agenda { display: flex; flex-direction: column; overflow: hidden; }
/* 卡片里的 pane 不再有 440px 的固定高(那是 v24 双栏时代的值) */
.hc-card .hd-pane-body { flex: 1 1 auto; min-height: 0; }
.hc-card .ag-allday { max-height: 40%; }
/* ---- 纪念日卡片 ---- */
.hc-ann .ann-up-item { gap: 6px; padding: 7px 2px; }
.hc-ann .ann-days { min-width: 52px; }
.hc-ann .ann-title { font-size: 13.5px; }
/* ---- 观影卡片:海报 + 片名 ---- */
.hc-mv-list, .hc-bk-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; }
.hc-mv, .hc-bk {
display: flex; align-items: center; gap: 10px; min-width: 0;
padding: 6px 2px; border-bottom: 1px solid var(--line-2);
}
.hc-mv:last-child, .hc-bk:last-child { border-bottom: 0; }
.hc-mv-poster, .hc-bk-cover {
flex: 0 0 auto; width: 34px; height: 46px; border-radius: 4px; overflow: hidden;
background: var(--line-2); display: inline-flex; align-items: center; justify-content: center;
}
.hc-bk-cover { width: 32px; height: 44px; }
.hc-mv-poster img, .hc-bk-cover img { width: 100%; height: 100%; object-fit: cover; display: block; }
.hc-mv-ph, .hc-bk-ph { font-size: 16px; opacity: .7; }
.hc-mv-main, .hc-bk-main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.hc-mv-t, .hc-bk-t {
font-size: 13.5px; color: var(--ink); font-weight: 600;
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.hc-bk-bar { display: block; height: 3px; border-radius: 2px; background: var(--line-2); overflow: hidden; }
.hc-bk-bar i { display: block; height: 100%; background: var(--brand); }
.hc-bk-st {
flex: 0 0 auto; font-size: 11px; padding: 1px 7px; border-radius: 999px;
background: var(--line-2); color: var(--muted);
}
.hc-bk-st.is-reading { background: #E7F0DA; color: #4A7226; }
.hc-bk-st.is-done { background: #E3EAF5; color: #3A5B8C; }
/* ---- 联系人卡片:头像 + 名字,按卡片宽度自动折行 ---- */
.hc-people { display: flex; flex-wrap: wrap; gap: 10px 12px; }
.hc-person {
display: flex; flex-direction: column; align-items: center; gap: 4px;
width: 56px; min-width: 0; text-decoration: none;
}
.hc-person-av {
width: 40px; height: 40px; border-radius: 50%; object-fit: cover;
display: inline-flex; align-items: center; justify-content: center;
font-size: 15px; font-weight: 600;
}
.hc-person-nm {
font-size: 11.5px; color: var(--muted); max-width: 100%;
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.hc-person:hover .hc-person-nm { color: var(--ink); }
/* ---- 窄屏:8 格 → 4 格 → 1 列 ---- */
@media (max-width: 1180px) {
.hc-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.hc-w2 { grid-column: span 2; }
.hc-w4, .hc-w6, .hc-w8 { grid-column: span 4; }
.hc-h8 { grid-row: span 7; }
}
@media (max-width: 680px) {
.hc-grid { grid-template-columns: minmax(0, 1fr); grid-auto-rows: 64px; }
.hc-w2, .hc-w4, .hc-w6, .hc-w8 { grid-column: span 1; }
.hc-h6, .hc-h8 { grid-row: span 6; }
}
/* ---- 首页卡片布局设置页 ---- */
.hc-set-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.hc-set-row {
display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
padding: 10px 12px; border: 1px solid var(--line); border-radius: var(--radius-s);
background: var(--surface); min-width: 0;
}
.hc-set-row.is-dragged { opacity: .5; }
.hc-set-row.is-drop-before { border-top: 2px solid var(--brand); }
.hc-set-order { display: inline-flex; align-items: center; gap: 2px; flex: 0 0 auto; }
.hc-set-on {
display: flex; align-items: center; gap: 8px;
flex: 1 1 220px; min-width: 0; cursor: pointer;
}
.hc-set-name { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.hc-set-name b { font-size: 14px; }
.hc-set-name .small { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.hc-set-dim { display: inline-flex; align-items: center; gap: 4px; flex: 0 0 auto; }
.hc-set-dim .select { width: 66px; }
.hc-set-n { width: 64px; }
.hc-set-null { width: 0; }
/* ============================================================
v78(用户 2026-10-22):首页「近期任务」的就地面板
------------------------------------------------------------
用户原话:「网页端,首页点击一个任务后,除了展示信息,应该有相应的功能,
可以修改状态:待办、进行中,可以挪动分组,可以拖动上传附件,可以添加评论,
添加子任务等。其目地,是为了让用户不离开首页,就能对任务进行简单的处理。」
这一段只画**面板本体**(壳、遮罩、底栏都沿用 `.cal-modal*` / `.fb-entry-panel`)。
三块强调色都不新造:状态用既有的 `.kb-st`(三档颜色是它定义的唯一来源),
看板 chip 用看板自己的 `--bc`(与看板列头、卡片上的点同一个色)。
============================================================ */
.tk-panel { display: flex; flex-direction: column; gap: 14px; }
.tk-sec { display: flex; flex-direction: column; gap: 8px; }
/* 段落之间的那条细线:面板里段落多(状态/看板/信息/子任务/附件/讨论),
不划线会糊成一整块;用 line-2(比正文分隔线更淡)以免像个表单边框。 */
.tk-sec + .tk-sec { border-top: 1px solid var(--line-2); padding-top: 14px; }
.tk-sec-h {
display: flex; align-items: center; gap: 6px;
font-size: 13px; font-weight: 600; color: var(--ink);
}
.tk-sec-h .group-n { margin-left: 0; }
/* 「状态」「看板」这两段是"标签 + 一排可点的东西",横排。
⚠️ 用 flex-wrap:看板可能是七八个,窄屏要能折行而不是撑出面板。 */
.tk-sec-inline { flex-direction: row; align-items: center; gap: 10px; flex-wrap: wrap; }
.tk-sec-lb { flex: 0 0 34px; font-size: 12.5px; color: var(--faint); }
.tk-seg { display: flex; gap: 6px; flex-wrap: wrap; }
/* 面板里的状态徽标比卡片上的大一档(这里是要点的按钮,不是标签) */
.tk-seg .kb-st { padding: 3px 12px; font-size: 12.5px; }
.tk-chips { display: flex; gap: 6px; flex-wrap: wrap; }
.tk-chip {
display: inline-flex; align-items: center; gap: 5px;
padding: 2px 10px; border-radius: 999px;
border: 1px solid var(--line); background: var(--surface);
font-size: 12px; color: var(--muted); cursor: pointer;
max-width: 100%;
}
.tk-chip:hover { border-color: var(--brand-2); color: var(--brand-ink); }
/* 选中的看板:用**它自己的颜色**描边 + 淡底 —— 与看板列头那颗点是同一个色,
用户不用读字就知道"这条现在在哪一摊事里"。 */
.tk-chip.is-on {
border-color: var(--bc, var(--brand));
background: color-mix(in srgb, var(--bc, var(--brand)) 12%, transparent);
color: var(--ink); font-weight: 600;
}
/* ⚠️ color-mix 在老浏览器上整条声明无效 → 底色会是透明的。
补一条纯 rgba 兜底(不指定色,统一淡灰),保证"选中"至少看得出来。 */
@supports not (background: color-mix(in srgb, red 10%, transparent)) {
.tk-chip.is-on { background: var(--line-2); }
}
.tk-chip.is-static { cursor: default; }
.tk-chip-dot {
width: 7px; height: 7px; border-radius: 50%; flex: 0 0 auto;
background: var(--bc, var(--faint));
}
/* 只读那几行(截止/负责人/分类/备注):面板里空间紧,行高比详情页收一点 */
.tk-kv .kv-row { padding: 6px 0; }
/* 加子任务那一行:输入框吃掉剩余宽度,按钮不缩 */
.tk-inline-form { display: flex; align-items: center; gap: 8px; }
.tk-inline-form .input { flex: 1 1 auto; min-width: 0; }
.tk-inline-form .btn { flex: 0 0 auto; }
/* 讨论里那条"删除":长得像链接(`.btn-inline` 负责去掉按钮的灰底,
`.link-quiet` 负责颜色),这里只把它推到本行最右。 */
.cmt-who .cmt-x { margin-left: auto; }
.tk-drop { margin-top: 4px; }
.tk-drop .upbox { min-height: 64px; }
@media (max-width: 640px) {
.tk-sec-lb { flex: 0 0 auto; width: 100%; }
}
/* v78:卡片窄到"宽 2"时,标题 +「全部任务」+「新建任务」三样挤不下 ——
允许折行,别把标题压成 0 宽(那会只剩两颗按钮,看不出这是哪张卡片)。 */
.hc-card.hc-w2 .hc-head { flex-wrap: wrap; }
/* ============================================================================
🔴🔴 v94(用户 2026-10-25):必填项统一标记 = **标签红色 + 加粗**,去掉红色星号
============================================================================
用户原话:「必填项现在的做法是在后面加一个红色的星,但这样排版有时候不好看,
对不齐。把所有必填项都改一下,改成红色加粗字体,就不用再考虑加红色星的问题了。
这样用户习惯之后,一看前面的标题,比如"日程名称"这种,只要是红色加粗的字体,
就知道这是必填项。我感觉这个比加红色的星更好一些。」
------------------------------------------------------------------
为什么用 `.is-req` 这个**显式标记**,而不是"看有没有 `required` 属性"自动推断
------------------------------------------------------------------
`required` 只出现在 `<input>` 上,而标签是它的**兄弟或父级之前的节点** ——
CSS 没有"父选择器"能用它反推(`:has()` 也只在很新的内核里可用,本项目要照顾
旧 WebView)。而且有几个必填项**根本没有 `required` 属性**(比如影院名称、
板块名这些靠 JS 校验的),靠属性推断会漏掉。
⇒ 一律由模板显式加 `is-req` / `is-req` 放在 `<span class="mv-lb">` 上。
「哪些是必填」这个事实写在模板里(离 `required` 最近),
「红加粗长什么样」写在 CSS 里(一处改、全站生效)。
------------------------------------------------------------------
⚠️ 三条硬约束(都是本项目的实测坑)
------------------------------------------------------------------
① **特异性**:这里用 `.is-req`(0,1,0)去覆盖 `.mv-row > .mv-lb`
(0,2,0)和 `.form-grid > .field > label`(0,2,1)—— 单靠一条肯定压不住。
所以最后用 `!important` 打穿。这不是偷懒:颜色/字重是**语义标记**,
不该被"某个面板想调一下标签样式"顺手盖掉,否则同一个"必填"在不同页面长得不一样。
② **冒号必须跟着变红**:`.mv-lb::after` / `.dt-inline-lab::after` 把「:」画成
灰色 400 字重 —— 只改标签本体会出现"红字 + 灰冒号"的半截效果。这里一并覆盖。
③ **不能用 `color: inherit` 之类做减法**:`--red` 是动作红(删除按钮底色),
当文字色在暖纸底上偏浅,见 `:root` 里 `--req` 的说明。
------------------------------------------------------------------
覆盖的三种标签形态(本项目全部标签都落在这三种里,已逐个核过)
------------------------------------------------------------------
a) `<label>` —— 多数表单
b) `<span class="mv-lb">` —— 观影式右对齐标签(含 ::after 冒号)
c) `<span class="dt-inline-lab">` —— 日期控件的内联标签(也带 ::after 冒号)
============================================================================ */
/* a) 裸 <label>(含 .form-grid / .card .field / .cal-modal 等各种宿主) */
label.is-req,
.field > label.is-req {
color: var(--req) !important;
font-weight: var(--req-w) !important;
}
/* b) 观影式 .mv-lb —— 冒号一起变红(`.mv-lb::after` 把「:」画成灰色 400,
只改标签本体会剩"红字 + 灰冒号"的半截效果;冒号也得跟着 700,否则
650/400 之间会断开一截) */
.mv-lb.is-req,
.mv-row > .mv-lb.is-req,
.dt-inline-lab.is-req {
color: var(--req) !important;
font-weight: var(--req-w) !important;
}
.mv-lb.is-req::after,
.mv-row > .mv-lb.is-req::after,
.dt-inline-lab.is-req::after {
color: var(--req) !important;
font-weight: var(--req-w) !important;
}
/* c) 日期范围那种独立标签(`<span class="dt-range-label">日期范围</span>`) */
.dt-range-label.is-req {
color: var(--req) !important;
font-weight: var(--req-w) !important;
}
/* 兜底:万一模板把 is-req 加在了包裹层(`.field` / `.mv-row`)而不是标签本身,
让里面的标签也吃到红色 —— 这样即使某处写法不同,用户看到的仍是一致的"红加粗"。
⚠️ 只作用在直接子级的 label / .mv-lb / .dt-range-label 上:
不能写成 `.field.is-req label`,那会把「提醒」那排 checkbox 的小标签一起染红。 */
.field.is-req > label,
.field.is-req > .field-label,
.mv-row.is-req > .mv-lb,
.field.is-req > .dt-range-label {
color: var(--req) !important;
font-weight: var(--req-w) !important;
}
.field.is-req > .dt-range-label::after,
.mv-row.is-req > .mv-lb::after {
color: var(--req) !important;
font-weight: var(--req-w) !important;
}
/* 深色/高对比主题下 `--req` 若偏暗就看不清 —— 这个站点目前只有暖纸一套,
但 `.dark` 类保留着(见 :root 附近的说明)。这里给一条备用值,
免得将来加暗色时必填标签糊进背景。 */
.dark label.is-req,
.dark .mv-lb.is-req,
.dark .dt-range-label.is-req,
.dark .field.is-req > label,
.dark .mv-row.is-req > .mv-lb {
color: #F08A78 !important;
}