Files
FamilyBook/assets/css/app.css
T
laoyang 4d04490f7f 网页端任务面板按鸿蒙版结构重排 + 输入框占满整行 (#82)
用户 2026-11-05:「网页版编辑在首页编辑任务的时候,那个输入框怎么那么短呢?
所有输入框都应该跟弹出框一样宽。它里面的信息应该和鸿蒙版一样:
1.标题是"编辑任务" 2.下面分成两行:第一行是任务标题,第二行是状态
3.后续内容也和鸿蒙版保持一致,不需要额外信息。
布局顺序也和鸿蒙版一致:1.任务标题 2.状态 3.看板
4.截止日期、负责人、备注、协作人 5.子任务,一直到子任务都和鸿蒙版一致。
颜色也是用主题颜色。」

① 输入框为什么那么短 —— 是我上一轮修"对齐"时引入的
   #80 给每行加了 `max-width: var(--tk-attr-w)`(300px),
   目的是让各个控件右缘对齐。但代价是:面板 680 宽、控件只有 300,
   右边空着 300 多像素,看起来像没做完。
   ⇒ 改成上下两行之后,**每行只有一个控件、天然左缘对齐**,
     不再需要靠限宽来对齐 ⇒ 全面 `width: 100%; max-width: none`。
   实测(Playwright 量 getBoundingClientRect):
     五行控件宽全部 648 / 行宽 648 = **100%** ✅

② 排列改成上下两行
   `.tk-attr-row` 从 `flex-direction: row` 改成 `column`:
     ① 标签独占一行(主题色 + 加粗 + 字距 .6,与鸿蒙 fieldLb 同一口径)
     ② 控件独占一行
   顺带 `.tk-sec-inline`(状态/看板那两段)也改成纵向,
   与字段行同形态 —— 否则这里横着、那里竖着,看着像两套东西。

③ 顺序按鸿蒙版重排
   改前:状态 → 看板 → 更多信息{标题/截止/负责人/协作人/分类/备注}
   改后:任务标题 → 状态 → 看板 → 截止 → 负责人 → 备注 → 协作人 → 子任务 → 附件 → 讨论
   标题从「更多信息」里提出来,单独做第一个小节。

④ 去掉"额外信息":分类、循环
   用户明确说「不需要额外信息」,鸿蒙端面板里也没有这两项。
   ⚠️ 安全性确认:`tasks/panel_save` 用 `array_key_exists` 判"传没传",
      收不到 category_id 就**保留原值**,所以删掉字段不会把分类清空
      (这条在 #76 就是按这个原则写的)。

⑤ 颜色:小节标题 + 字段标签都用主题色
   `.tk-sec-h`(附件/讨论/子任务)从 var(--ink) 改 var(--tk-accent, var(--brand)),
   `.tk-attr-lb` 从 faint 灰改主题色 + 700 粗体。

⑥ 只读态也做成"框"
   新增 `.tk-attr-val`:浅底 + 圆角 + min-height 40,
   与可编辑态同宽同高。原来只读用户看到的是一行裸文字,
   和编辑态差太多(同一位置两种长相)。

实测渲染(family 1 最新任务):
  字段标签顺序:任务标题 → 截止 → 负责人 → 备注 → 协作人 ✅
  横排段顺序:状态 → 看板 ✅
  分类 / 循环:已删 ✅
2026-11-05 20:09:02 +08:00

7330 lines
398 KiB
CSS
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* ============================================================
家书 FamilyBook · 界面样式
配色取自图标:宣纸米 / 砖红封面 / 深褐书脊 / 纸白
原则:扁平、留白充足、字重克制(正文 400,标题 500-600)
============================================================ */
:root {
--paper: #F7F3EA;
--surface: #FFFFFF;
--ink: #2C231D;
--muted: #7E6B5A;
--faint: #A8988A;
--brand: #A84A28;
--brand-2: #C96F45;
--brand-ink:#6E3820;
--green: #6F9E5C;
--red: #C0533F;
/* 🔴🔴 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,所以把「最高祖先」排第一位以画出尽可能多的血亲旁支;
加载后再把镜头平移回「我」,兼顾"看到全树"与"以自己为中心"。
⚠️ 免费版 family-chart 的算法只画「树根的祖先链 + 后代 + 树根的兄弟姐妹 + 各人配偶」,
**不会从配偶继续向上/向下 walk**。这一点已用**官方示例自己的数据**验证过:
官方 data.json 里 13 人(含配偶 Andrea 的父母 Zadro/Zadra,格式完全按官方),
库只画出 11 人 —— 配偶方那两位长辈一个都没画。
所以「把数据按格式全塞进去就能画全」在姻亲这一项上不成立,是库的算法边界。 */
.kin-tools { display: flex; gap: 8px; justify-content: flex-end; margin: 0 0 10px; }
.kin-chart {
position: relative;
width: 100%;
height: clamp(420px, 62vh, 720px);
background: var(--surface);
border: 1px solid var(--line);
border-radius: var(--radius);
box-shadow: var(--shadow);
overflow: hidden;
cursor: grab;
/* 拖拽/缩放由库接管,别让浏览器插手滚动 */
touch-action: none;
}
.kin-chart:active { cursor: grabbing; }
/* 库的 `.f3` 会自带一套**深色主题**(--background-color: rgb(33,33,33) / --text-color: #fff)。
家书是宣纸浅色,必须在这里把库的主题变量改掉;否则卡片文字会变白、看不出内容。
只覆盖颜色变量,**不动**库的结构规则(那些是撑满/定位必需的)。 */
.kin-chart.f3 {
--male-color: #F1E7D6; /* 库的男卡底色,改成纸色(我们自己的 .kin-card 会再盖一层) */
--female-color: #F1E7D6;
--genderless-color: #F1E7D6;
--background-color: transparent;
--text-color: var(--ink);
font-family: inherit;
}
/* 隐藏 family-chart 自带的空导航栏(我们用自己的「看全部 / 回到我」按钮) */
.kin-chart .f3-nav-cont { display: none !important; }
/* 官方 CSS 已经给了 `.f3 svg.main_svg { width:100%; height:100% }`(撑满画布),
这里只补一个 display:block,避免 svg 作为 inline 元素多出基线空隙。 */
.kin-chart svg.main_svg { display: block; z-index: 1; }
/* family-chart 的 HTML 卡片层 #htmlSvg 与 SVG 连线层由库的 onZoom 同步 transform。
卡片容器 .card_cont 已经 absolute + translate(x,y);里面再放我们的 .kin-card。 */
.kin-chart #htmlSvg { z-index: 2; }
.kin-chart .card_cont { pointer-events: none; }
/* ⚠️ 库的性别底色规则是 `.f3 div.card-male .card-inner`(3 段),
比 `.kin-chart .card_cont .card-inner`(3 段)**同级**,靠先后顺序定胜负 ——
所以这份覆盖必须写在**引库 CSS 之后**(app.css 在 layout 里就是这么排的)。
把外层中转容器全部归零,只留我们自己的 .kin-card。 */
.kin-chart .card_cont > .card,
.kin-chart .card_cont .card-inner,
.kin-chart.f3 .card_cont .card-inner {
background: transparent;
padding: 0;
border-radius: 0;
min-height: 0;
width: auto;
height: auto;
outline: none;
}
.kin-chart .card_cont .card-inner { display: block; }
/* v21:卡片尺寸与 setCardDim({w:140, h:120}) 对齐 */
/* 用户 2026-10-03 第 1 条:
· 头像外框改**长方形**(宽 96 × 高 72),宽度基本就是头像显示宽度;
img 用 object-fit: contain —— 既不裁切,也不会把外框撑变形。
· 信息(姓名/称呼)在头像**下方**。
· 卡片不再整张是链接:只有 .kin-nm 是 <a>,右下角是独立的 <button>,
两者分开,避免"点按钮却进了成员页"的误触。
卡片尺寸与 JS 的 setCardDim({w,h}) 必须一致(库按它算间距与连线落点)。 */
/* 卡片尺寸:**框宽 = 头像宽**(用户 2026-10-03:「外面那个框,宽度和头像图片一致,
下面显示名字就行了」)。
🔴 头像槽位是 **1:1(正方形)**(见 App\ImageSlot::SLOTS['avatar'])
—— 所以"框宽 = 头像宽"其实要求框里的头像区**也是正方形**:
之前写成 96×72 的长方形 + `object-fit: contain`,正方形照片被**缩到 72 宽**
左右各留 ~12px 空白 → 看起来"框比头像宽很多"(用户第 8 条「还是比头像宽很多」)。
现在头像区取正方形,照片 `object-fit: cover` 铺满 —— 框就是头像本身那么大。
⚠️ 这个 90/128 必须与 JS 里 setCardDim({w,h}) **保持一致**:
库按它算卡片间距与连线落点,两边不一致连线就会歪。 */
.kin-card {
--kin-w: 90px;
--kin-h: 128px;
--kin-ph: 88px; /* 头像区(正方形):卡宽 − 左右各 1px 边框 */
position: absolute;
display: flex; flex-direction: column; align-items: center;
gap: 0;
width: var(--kin-w);
height: var(--kin-h);
box-sizing: border-box; padding: 5px 0 4px;
text-align: center;
background: var(--surface);
border: 1px solid var(--line);
border-radius: 10px;
box-shadow: 0 1px 3px rgba(43,38,32,.10);
color: var(--ink);
transition: border-color .15s ease, box-shadow .15s ease;
overflow: hidden;
pointer-events: auto; /* 父 .card_cont 是 none,这里恢复 */
}
/* ⚠️⚠️ 居中**绝不能**用 transform、也不能用 `inset:0 + margin:auto`。
两次踩坑记录(都是用户先发现的:一次"鼠标移上去框会动",一次"线又错位了"):
· 用 `transform: translate(-50%,-50%)`:库自带
`.f3 div.card:hover > div { transform: translate(0,-2px) }` 会把居中 transform
**整份覆盖** → 鼠标一进卡片整张卡跳 53×58px(半张卡)。
· 用 `inset: 0` + `margin: auto`:**在这里不成立** —— 含块是库的 `div.card`,
在 Cartesian 模式下它只有 **1.8px 宽**(宽度由内部一个空 div 撑出来),
auto 外边距在一个比卡片还窄的盒子里根本分不出空间,结果卡片整体**右移了 53.1px**
(实测每张卡的中心都偏在节点右边 53.1px)→ 连线与卡片错位。
正解:`left/top: 50%` + **半个自身尺寸的负外边距** —— 位置完全由布局决定,
不经过 transform,库那条 :hover 规则怎么覆盖都影响不到。
⚠️ 负外边距必须写 `calc(var(--kin-*) / -2)`,与上面的尺寸共用同一个变量;
写成死数字(-59/-66)迟早会和尺寸脱节。 */
.kin-card {
left: 50%; top: 50%;
margin-left: calc(var(--kin-w) / -2);
margin-top: calc(var(--kin-h) / -2);
}
/* ⚠️ 还要把库那条 `:hover` 位移**归零**(用户反馈「鼠标放上去框会动」的另一半)。
库的 family-chart.css:517 有:
`.f3 div.card:hover > div { transform: translate(0, -2px); }`
它除了覆盖居中,还会给卡片一个纯视觉的"上浮 2px"(实测 dy = -1.8px)。
我们自绘的宣纸卡不需要库那套悬停装饰,直接归零。
⚠️ 不去改 vendor 里那份文件(升级库会整份覆盖、改动静默消失):
在 app.css 覆盖即可 —— 它加载在 family-chart.css **之后**,
同特异性下后写的赢,**不需要 !important**。 */
.kin-chart.f3 div.card:hover > div { transform: none; }
.kin-chart.f3 div.card > div { transition: none; }
.kin-card:hover {
box-shadow: var(--shadow-l); border-color: var(--brand-2);
}
/* 头像区:**正方形、铺满**(见上面 --kin-ph 的说明)。
用户 2026-10-03 第 8 条:「宽度和头像图片一致,下面显示名字就行了」。 */
.kin-card .kin-ph {
width: var(--kin-ph); height: var(--kin-ph);
border-radius: 8px; overflow: hidden;
background: #F1E7D6; flex: 0 0 auto;
display: block;
}
/* 头像槽位是 1:1,所以这里的容器也是 1:1 —— cover 不会裁掉任何内容
(照片本来就是按 1:1 裁好的)。⚠️ 别改成 contain:那会在框里留出空白,
看起来就是"框比头像宽"。 */
.kin-card .kin-ph img {
width: 100%; height: 100%; object-fit: cover; display: block;
}
.kin-card .kin-ph:hover { box-shadow: inset 0 0 0 2px var(--brand-2); }
/* 姓名:用户 2026-10-03 第 7 条起,点姓名 = **以他为中心**重画家谱
(点头像才是进资料页)。
⚠️ 第 8 条明确要求**不要再显示"资料 / 以他为中心"那两行悬停提示**(没必要)——
所以 .kin-hint 这套已整个删掉,卡片上头像下面只有名字。 */
.kin-card .kin-nm {
display: block;
font-size: 12.5px; font-weight: 600; line-height: 1.25; max-width: 100%;
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
margin-top: 5px; color: var(--ink); text-decoration: none;
}
.kin-card .kin-nm:hover { color: var(--brand); text-decoration: underline; }
/* 称呼在姓名下面一行,用品牌色的浅字(参考实现是「(称呼)」) */
.kin-card .kin-lb {
font-size: 10.5px; color: var(--brand); line-height: 1.2; max-width: 100%;
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.kin-card .kin-mark { font-size: 10px; color: var(--faint); }
/* 「我」:品牌色描边 + 光圈 */
.kin-card.is-me {
border-color: var(--brand);
box-shadow: 0 0 0 3px rgba(168, 74, 40, .12), var(--shadow);
}
.kin-card.is-me .kin-lb { color: var(--brand-ink); font-weight: 600; }
/* 「待确认」(只填了称呼、没对应到真人):虚线 */
.kin-card.is-ph { border-style: dashed; background: #FCFAF6; }
/* family-chart 默认连线颜色改淡,与之前自研版一致。
库在 path 上写了 inline stroke="#fff",必须用 !important 覆盖。 */
.kin-chart svg.main_svg .links_view path.link {
fill: none;
stroke: #C9BDA9 !important;
stroke-width: 1.6;
}
.tree-legend { font-size: 12px; color: var(--muted); margin: 14px 2px 0; }
/* 详情页里的迷你家谱 */
.mini-tree { display: flex; flex-direction: column; gap: 8px; }
.mini-gen { display: flex; flex-wrap: wrap; gap: 8px; }
.mini-node {
display: flex; align-items: center; gap: 7px;
padding: 5px 12px 5px 5px; border-radius: 999px;
background: #FAF7F1; border: 1px solid var(--line);
color: var(--ink); font-size: 13px;
}
.mini-node:hover { text-decoration: none; border-color: var(--brand); }
.mini-node img { width: 26px; height: 26px; border-radius: 50%; object-fit: cover; }
.mini-node .mn-label { font-size: 11px; color: var(--brand); }
.mini-node.is-me { border-color: var(--brand); background: #F6E3D7; }
/* ---------------- 亲属关系编辑 ---------------- */
.rel-summary {
display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 12px; margin-bottom: 18px;
}
.rel-sum-item {
background: #FAF7F1; border: 1px solid var(--line-2);
border-radius: var(--radius-s); padding: 10px 14px;
}
.rel-sum-k { display: block; font-size: 12px; color: var(--muted); margin-bottom: 3px; }
.rel-sum-v { font-size: 14px; }
.rel-sum-v a { margin-right: 6px; }
.rel-add { border-top: 1px solid var(--line-2); padding-top: 16px; }
.rel-add-title { font-size: 14px; font-weight: 600; margin: 0 0 12px; }
.rel-form {
display: grid; grid-template-columns: 1fr 1fr 1.4fr auto;
gap: 12px; align-items: end;
}
.rel-list { margin-top: 16px; font-size: 13px; }
.rel-list summary { cursor: pointer; color: var(--muted); }
.rel-list ul { margin: 10px 0 0; padding-left: 18px; }
.rel-list li { margin-bottom: 6px; }
.rel-list .btn { margin-left: 6px; }
/* ---------------- 三角色 / 存储设置 ---------------- */
.role-cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 12px; }
.role-card { background: #FAF7F1; border: 1px solid var(--line-2); border-radius: var(--radius-s); padding: 14px 16px; }
.rc-name { font-weight: 600; margin-bottom: 5px; }
.rc-desc { margin: 0; font-size: 13px; color: var(--muted); line-height: 1.65; }
.radio-row { display: flex; flex-direction: column; gap: 10px; }
.radio-item {
display: flex; gap: 10px; align-items: flex-start;
background: #FAF7F1; border: 1px solid var(--line-2);
border-radius: var(--radius-s); padding: 12px 14px; cursor: pointer;
}
.radio-item input { margin-top: 3px; }
.check-item { display: flex; gap: 8px; align-items: flex-start; font-size: 13px; cursor: pointer; }
.check-item input { margin-top: 3px; }
.oss-box {
border: 1px solid var(--line-2); border-radius: var(--radius-s);
padding: 12px 16px; background: #FCFAF6;
}
.oss-box summary { cursor: pointer; font-size: 13px; font-weight: 600; color: var(--muted); }
.plain-list { margin: 0; padding-left: 18px; font-size: 14px; }
.plain-list li { margin-bottom: 7px; }
@media (max-width: 720px) {
.rel-form { grid-template-columns: 1fr; }
}
@media (max-width: 720px) {
/* 390px 宽度放不下整套顶栏:只留 图标 + 导航 + 头像 + 退出 */
.topbar-inner { gap: 6px; padding: 0 10px; height: 54px; }
.brand img { width: 26px; height: 26px; }
.nav { gap: 0; }
.nav a { padding: 6px 8px; font-size: 13px; }
.topbar-right { gap: 6px; }
.who-name, .who-pwd { display: none; } /* 名字与「改密码」收起,设置页里都有 */
.who img { width: 26px; height: 26px; }
.container, .container-narrow { padding: 20px 16px 48px; }
.form-grid { grid-template-columns: 1fr; }
.detail { grid-template-columns: 1fr; }
.hero { padding: 20px; gap: 14px; }
.hero h1 { font-size: 20px; }
.hero-stats { margin-left: 0; gap: 20px; }
.page-head { flex-wrap: wrap; align-items: center; }
.page-head .actions { margin-left: 0; width: 100%; }
/* ⚠️ 窄屏下动作区**必须允许换行**:
一行里可能有 6 个按钮(月/周/天 + 上一周/今天/下一周 + 新建 + ☰),
360px 根本放不下。不换行的后果不只是横向溢出(整页被拖走),
还会把按钮**挤扁**成高低不一(实测 91/68/44 三种高度,同排按钮参差不齐)——
而"页头按钮同高"是用户提过三次的明确要求。
换行之后每个按钮保持自己的尺寸,高度自然一致。 */
.page-head .actions { flex-wrap: wrap; }
.page-head .actions > .movie-set { margin-left: 0; }
.kv-row { grid-template-columns: 84px 1fr; }
.search { flex-wrap: wrap; }
.search .input { max-width: none; flex: 1 1 auto; }
}
/* ---------------- 「我怎么称呼家人」(视角属性,见 app/Nickname.php) ---------------- */
.nick-list { display: flex; flex-direction: column; gap: 8px; }
.nick-row {
display: grid; grid-template-columns: minmax(120px, auto) 1fr auto;
gap: 10px; align-items: center;
padding: 8px 10px; border-radius: var(--radius-s);
background: #FAF7F1; border: 1px solid var(--line-2);
}
.nick-who { display: flex; align-items: center; gap: 8px; font-size: 14px; min-width: 0; }
.nick-who img { width: 28px; height: 28px; border-radius: 50%; object-fit: cover; flex: 0 0 auto; }
.nick-who span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* 详情页上的单个"我叫他什么"输入行 */
.nick-inline { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
.nick-inline .input { flex: 1 1 220px; max-width: 340px; }
@media (max-width: 720px) {
.nick-row { grid-template-columns: 1fr; }
.nick-inline .input { max-width: none; }
}
/* ---------------- 连通性测试与诊断(设置页) ---------------- */
.test-actions { display: flex; flex-direction: column; gap: 10px; }
.test-actions form { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
.oss-diag {
margin-top: 16px; border-radius: var(--radius-s); overflow: hidden;
border: 1px solid var(--line);
}
.oss-diag-head {
display: flex; gap: 10px; align-items: baseline; flex-wrap: wrap;
padding: 10px 14px; font-size: 13px;
background: var(--line-2);
}
.oss-diag.is-ok .oss-diag-head { background: #E6F0DC; color: #3F6021; }
.oss-diag.is-bad .oss-diag-head { background: #F6E3D7; color: var(--brand-ink); }
.oss-diag-table { width: 100%; border-collapse: collapse; font-size: 13px; background: var(--surface); }
.oss-diag-table th,
.oss-diag-table td { padding: 8px 12px; text-align: left; vertical-align: top; border-top: 1px solid var(--line-2); }
.oss-diag-table th { font-weight: 500; color: var(--muted); font-size: 12px; }
.oss-diag-table td:nth-child(1) { white-space: nowrap; }
.oss-diag-table td:nth-child(3) { white-space: nowrap; }
.oss-diag-table tr.row-ok td:nth-child(2) { color: #4A7226; }
.oss-diag-table tr.row-bad td:nth-child(2) { color: var(--red); }
@media (max-width: 720px) {
.oss-diag-table th:nth-child(3),
.oss-diag-table td:nth-child(3) { display: none; }
}
/* ============================================================
以下都是 2026-09-30 那一批反馈加的东西
============================================================ */
/* 只给读屏用的标签:视觉上不占地,但无障碍名还在(别用 display:none,那会一起藏掉)
🔴 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;
}
/* 🔴 2026-11-05 修:视频打开后是黑的(用户:「点开之后是黑的,没办法播放」)。
原因:`max-height: 100%` 依赖父级的**确定高度**,而 `.pv-stage` 是
flex + `align-items:center`,百分比高度解析不出具体像素
⇒ `<video>` 按 0 高度渲染 ⇒ 只剩 background:#000 一块黑。
改成 `vh`(相对视口,永远是确定值)+ `object-fit: contain`(竖屏不变形)。 */
.pv-video {
max-width: 92vw;
max-height: 82vh;
width: auto;
height: auto;
object-fit: contain;
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);
}
/* 🔴 2026-11-05 重做(用户:「这个位都没对齐呀…直接用个标签,然后右边一个箭头,
到北京南站就得了,不用写从哪到哪」)。
原来只是一个普通 div,里面的 <a> 与 <span class="arrow"> 各按自己的基线排
⇒ 字号不同就"没对齐"。
现在:容器 flex + align-items:center,两个地名各自成胶囊标签,
箭头只作窄分隔。视觉上就是「[青岛北站] → [北京南站]」,
不再需要"从/到"这种字。 */
.node-route-line {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 6px;
font-size: 12.5px;
color: var(--muted);
line-height: 1;
margin-top: 2px;
}
/* 地名标签:浅底圆角,长名字省略 */
.node-route-line > a,
.node-route-line > span.route-stop {
display: inline-flex;
align-items: center;
max-width: 46%;
padding: 3px 8px;
border-radius: 999px;
background: var(--fill, #F1EAE1);
color: var(--ink, #2C231D);
font-size: 12px;
line-height: 1.2;
text-decoration: none;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.node-route-line > a:hover {
background: var(--brand-soft, #F5E3DC);
}
/* 箭头:窄、淡,只做分隔 */
.node-route-line .arrow {
color: var(--faint);
margin: 0 1px;
font-size: 12px;
line-height: 1;
flex: none;
}
.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;
/* #82:小节标题改 **模块主题色 + 加粗 + 字距** ——
与鸿蒙端 FbSectionTitle 一致(用户:「颜色也是用主题颜色」)。 */
font-size: 13px; font-weight: 700; letter-spacing: .6px;
color: var(--tk-accent, var(--brand));
}
.tk-sec-h .group-n { margin-left: 0; }
/* 「状态」「看板」这两段是"标签 + 一排可点的东西",横排。
⚠️ 用 flex-wrap:看板可能是七八个,窄屏要能折行而不是撑出面板。 */
.tk-sec-inline { flex-direction: row; align-items: center; gap: 10px; flex-wrap: wrap; }
/* 🔴 #74:面板里的「截止时间」AntD 控件。
`.dt-input` 是 `display:flex; flex-wrap:wrap`,放进 `.tk-sec-inline`
(也是 flex-wrap)里会被当成一个弹性项 —— 不给宽度约束时它会按内容撑,
在窄屏上可能把「截止」标签挤到上一行、或者控件自己溢出。
`flex: 1 1 auto` + `min-width: 0` 让它占满标签右侧的剩余宽度,
`max-width` 兜住极宽屏(AntD 的 picker 本身有固定宽度)。 */
.tk-sec-inline > .dt-input { flex: 1 1 auto; min-width: 0; max-width: 320px; }
/* 「还剩 3 天」那类小字:贴在控件右边,不与控件抢宽度 */
.tk-due-word { flex: 0 0 auto; font-size: 12.5px; color: var(--faint); white-space: nowrap; }
.tk-due-word.kb-late { color: var(--red); }
/* 🔴 #76:「更多信息」小节 —— 截止 / 负责人 / 分类 三行 + 保存。
用户 2026-11-05:「没有保存按钮…负责人和分类都没法修改。」
每行结构 = 固定宽标签 + 占满剩余宽度的控件,与 .mv-row 同一套观感
(但标签用 tk-attr-lb,因为面板里字号比整页表单小一号)。 */
.tk-attrs { display: flex; flex-direction: column; gap: 14px; }
/* 🔴🔴 #82(用户 2026-11-05):「网页版编辑在首页编辑任务的时候,那个输入框
怎么那么短呢?所有输入框都应该跟弹出框一样宽。」
「布局顺序也和鸿蒙版一致」「下面分成两行:第一行是任务标题,第二行是状态」
⇒ `.tk-attr-row` 从**横向**(标签左 / 控件右)改成**纵向两行**:
① 标签独占一行(主题色粗体)
② 控件独占一行,**占满整行宽度**(不再有 300px 上限)
为什么去掉 300px 上限:
那个上限是 #80 为"对齐"引入的,但它的代价是**输入框比面板窄一大截** ——
面板 680 宽,控件只有 300,右边空着 300 多像素,看起来像没做完。
现在改成上下两行之后,每行只有一个控件,天然左缘对齐,
**不再需要靠限宽来对齐** ⇒ 直接 width:100%。 */
.tk-attr-row {
display: flex; flex-direction: column; align-items: stretch; gap: 6px;
/* 🔴 #80:这里原来有 `flex-wrap: wrap` —— 窄屏下控件(300px)加标签(46px)
加间距(10px)放不进容器,控件就**掉到下一行**,左缘从 76 变成 0,
与标签右缘完全错开,整列看着像塌了。
⇒ 不换行:让控件按 `flex:1 1 auto` + `min-width:0` 收缩到可用宽度。
宽度不足时宁可控件变窄,也不要换行破坏对齐。
⚠️ 代价:极窄屏(< 200px)下控件会很窄 —— 但那时任何布局都放不下,
保持对齐比保持宽度更重要。 */
}
.tk-attr-lb {
/* #82:标签不再固定 46px(那是横排时的"对齐基准"),
现在独占一行,按内容宽即可;左对齐(横排时是右对齐贴控件)。
颜色从 faint 灰改成 **模块主题色 + 加粗** —— 与鸿蒙端的 fieldLb、
以及本页的小节标题同一口径。 */
font-size: 12.5px; font-weight: 700; letter-spacing: .6px;
color: var(--tk-accent, var(--brand)); text-align: left;
}
/* 控件区占满标签右侧(min-width:0 让它能被压缩,不会把标签挤出去) */
.tk-attr-row > *:not(.tk-attr-lb) { flex: 1 1 auto; min-width: 0; }
/* 🔴 #80(用户 2026-11-05):「标题和备注的输入框没有和其他输入框对齐。」
根因:各行的**宽度上限不一致** ——
· 截止(AntD picker) max-width: 300px
· 分类(select) max-width: 300px
· 负责人/协作人 max-width: 340px
· **标题(input)/ 备注(textarea)完全没设上限** ⇒ 撑满整行
于是标题那行比截止那行宽出一截,左右边缘都对不齐。
⇒ 统一成一个宽度变量。以后要调只改这一处,不会再有"某一行多一点"。
⚠️ `width: 100%` + `max-width` 而不是只给 `max-width`:
只给 max-width 时,块级 input 默认 width:auto 会按内容收缩,
反而比别的行窄。两个一起给才能"最多这么宽、但尽量占满"。
⚠️ 标签宽度(46px)+ gap(10px)= 56px,这与 `.tk-attr-actions` 的
`padding-left: 56px` 是同一个数 —— 改标签宽度时必须同步改那两处。 */
:root { --tk-attr-w: 300px; }
/* #82:所有控件一律占满整行 —— 不再有 `--tk-attr-w` 这个 300px 上限。
上下两行之后每行只有一个控件,宽度不一致反而不协调。 */
.tk-attr-row > .dt-input,
.tk-attr-row > select.select,
.tk-attr-row > .pp-root,
.tk-attr-row > input.input,
.tk-attr-row > textarea.input,
.tk-attr-row > .tk-attr-val { width: 100%; max-width: none; }
/* textarea 按行数撑高,别被 `.input` 的固定高度带偏 */
.tk-attr-row > textarea.input { resize: vertical; min-height: 46px; }
/* 保存按钮那一行:与上面的控件左缘对齐(= 46px 标签宽 + 10px gap) */
.tk-attr-actions {
/* #82:取消 padding-left(56px) —— 那是给"横排时对齐控件左缘"用的。
现在控件占满整行,按钮左缘就是容器左缘,再加 56 会缩进去一截。 */
display: flex; align-items: center; gap: 10px;
}
.tk-attr-tip { font-size: 12px; color: var(--green, #2E7D4F); }
/* #82:只读态的值也做成"框"的样子(浅底圆角),
与可编辑态同宽同高 —— 否则只读用户看到的是一行裸文字,和编辑态差太多。 */
.tk-attr-val {
display: block; min-height: 40px; box-sizing: border-box;
padding: 10px 12px; font-size: 14px; color: var(--ink);
background: var(--input-bg, #F7F4EF);
border-radius: var(--radius-s); word-break: break-word;
}
/* #82:面板里的"点一下就能改"的横排段(状态/看板)改成上下两行:
标签在上、chips 在下,与字段行同形态。 */
.tk-sec-inline { flex-direction: column; align-items: stretch; gap: 6px; }
.tk-sec-inline > .tk-sec-lb {
font-size: 12.5px; font-weight: 700; letter-spacing: .6px;
color: var(--tk-accent, var(--brand));
}
/* #78:面板里的选人组件(负责人单选 / 协作人多选)。
宽度已在上面的 `--tk-attr-w` 统一,这里只管 chips 的间距。
(原来这里单独写 max-width: 340px,比其他行宽 40px —— 正是 #80 对不齐的原因之一。) */
.tk-attr-row > .pp-root .pp-chips { margin-bottom: 6px; }
/* 标签在多行控件(备注、协作人)上方对齐,而不是垂直居中 */
.tk-attr-row-top { align-items: flex-start; }
.tk-attr-row-top > .tk-attr-lb { padding-top: 8px; }
/* 只读态的协作人 chips */
.tk-share-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.tk-share-chip {
font-size: 12.5px; padding: 3px 10px; border-radius: 999px;
background: var(--surface-2, #F2F0EC); color: var(--ink);
}
/* #78:评论里的「添加附件」。
用原生 file input(视觉隐藏),点 label 触发 —— 与站内其它上传入口同一手法。 */
/* 🔴 #81(用户 2026-11-05):「评论里面拖文件上传附件,那个地方太小了。」
原来是一个 `padding:4px 10px` 的小胶囊(高约 26px)+ 一行小字,
拖动落点太小:触屏上不好瞄,桌面上也容易拖偏。
⇒ 改用与「任务附件」同一套 `.upbox`(96px 高的虚线大框)。
这里只做评论特有的微调,不去覆盖 `.upbox` 本体 ——
那一套是站点级组件,改它会影响所有上传入口。 */
.cmt-attach-row {
display: flex; flex-direction: column; gap: 8px;
margin-top: 8px;
}
/* 评论里的上传框比「任务附件」矮一点:评论框本身已经在下方,
96px 会把整块讨论区推得太长;72px 在触屏上仍然好点。 */
.cmt-upbox { min-height: 72px; padding: 10px 14px; gap: 3px; }
.cmt-upbox .upbox-ico { font-size: 18px; }
.cmt-upbox .upbox-msg { font-size: 13px; }
.cmt-upbox-sub {
font-size: 11.5px; color: var(--faint); margin-top: 1px;
}
/* 拖拽经过时整框高亮(`.is-over` 由 task_panel.js 加在 `.cmt-attach-row` 上,
而 `.upbox.is-over` 是站点级规则 —— 这里补一条子选择器把它传导下去) */
.cmt-attach-row.is-over .cmt-upbox {
border-color: var(--brand); border-style: solid;
background: #FDF3EC; color: var(--brand);
}
/* 已选文件列表:一行一个,前面带个圆点;超长名截断 */
.cmt-file-list {
display: flex; flex-direction: column; gap: 3px;
padding: 6px 10px; border-radius: var(--radius-s);
background: var(--surface-2, #F2F0EC);
font-size: 12px; color: var(--muted);
}
.cmt-file-list span {
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.cmt-file-list span::before { content: '📎 '; }
.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;
}
/* ============================================================
#47(2026-11-05)行程状态动作按钮
------------------------------------------------------------
用户:「做成两个按钮,颜色也尽可能保持一致…
选中其中一个按钮之后…还加了一个那种椭圆的那种框那种背景,灰色的。」
实现:胶囊(border-radius:999px)+ 浅底;
`.is-on` 是"当前状态",用实心主题色表示"正在被选中",
其余动作是浅底 —— 一眼能看出现在是什么、能改成什么。
============================================================ */
.node-actions {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 6px;
margin: 4px 0 2px;
}
.node-act {
display: inline-flex;
align-items: center;
border: 0;
border-radius: 999px;
padding: 4px 10px;
font-size: 12px;
line-height: 1.35;
background: var(--fill, #F1EAE1);
color: var(--muted, #8A7A6D);
cursor: pointer;
font-family: inherit;
transition: background .12s ease, color .12s ease;
}
.node-act:hover { background: var(--brand-soft, #F5E3DC); }
/* 当前状态:实心 = 正在被选中 */
.node-act.is-on {
background: var(--muted, #8A7A6D);
color: #fff;
cursor: default;
}
.node-act-go { color: #2F6FA8; }
.node-act-go:hover { background: #EAF2F9; }
.node-act-ok { color: #3F7D5A; }
.node-act-ok:hover { background: #EAF4EE; }
.node-act-no { color: #C0533F; }
.node-act-no:hover { background: #FBEDEA; }
.node-act-gray { color: var(--muted, #8A7A6D); }
/* #48(2026-11-05)补充:取消按钮(琥珀色,与"删除"的砖红区分开)。
用户强调「取消和删除是不一样的」⇒ 视觉上也不能同色。 */
.node-act-warn { color: #B8862B; }
.node-act-warn:hover { background: #FDF6E7; }
/* 时间行:计划与实际并列时的区分 */
.node-time .t-plan { color: var(--muted, #8A7A6D); }
.node-time .t-actual {
color: var(--brand, #A8574F);
margin-left: 8px;
font-weight: 500;
}
/* ============================================================
#53(2026-11-05)视频解不了时的提示卡片
------------------------------------------------------------
用户传的视频是 H.265/HEVC,Chrome/Firefox 解不了 ⇒ 黑屏。
不给提示的话用户会以为"文件坏了/网站坏了"。
这里把原因和出路直接写在画面里。
============================================================ */
.pv-fallback {
max-width: 520px;
padding: 28px 24px;
border-radius: 12px;
background: rgba(255, 255, 255, .08);
border: 1px solid rgba(255, 255, 255, .18);
text-align: center;
color: #fff;
}
.pv-fallback-h {
margin: 0 0 10px;
font-size: 17px;
font-weight: 600;
}
.pv-fallback-p {
margin: 0 0 18px;
font-size: 13px;
line-height: 1.7;
opacity: .82;
}
.pv-fallback-dl {
display: inline-block;
padding: 9px 22px;
border-radius: 8px;
background: #fff;
color: #1a1a1a;
font-size: 14px;
font-weight: 600;
text-decoration: none;
}
.pv-fallback-dl:hover { background: #eee; }