Files
TripLog/assets/style.css
T
laoyang 44ffee6c92 节点表单:时间框整块可点、起点终点一键导航、可直接传多张附件
三件事都是「界面上明明该有、但用起来不通」的地方:

1) 时间输入框只能点右侧那个小日历图标才弹选择器
   原生行为如此(而且各浏览器还不一致),用户会以为输入框是只读的。
   在全局脚本里给 datetime-local / date 挂 click → showPicker(),
   等于把整块输入区都当成那个图标;选择器已在显示时再调是空操作,
   不支持的浏览器静默跳过。顺带给可点光标。
   写在 layout.php 里,所以签到时间等其它时间框一并生效。

2) 起点/终点的精确位置此前只存在页面上,没有落库
   高德选点回调里拿到的 lng/lat 现在随节点一起存库,节点详情页和
   表单页都生成高德导航链接,点一下就能直接导航过去(手机端会尝试
   唤起高德 App)。链接只给 to 不给 from,让移动端自动用当前位置当起点。
   ⚠️ 高德 URI API 的坐标顺序是「经度,纬度」,写反了不报错、只会安静地
   导到地球另一边,所以单独写了单元测试守住顺序。
   手工改地名时会清掉旧坐标,退化成关键字搜索,避免「名字改了位置没改」。

3) 节点处不能上传图片附件
   节点表单改成 multipart,加拖放区 + 多选 + 本地预览 + 逐个移除,
   编辑态可勾选删除已有附件(逐条校验归属,防止改 id 删到别人家的文件)。
   上传逻辑从 AttachmentController 抽到 src/Uploads.php,两处共用一份:
   MIME 白名单、大小上限、孤儿回滚各抄一份迟早走偏。
   请求体超过 post_max_size 时 PHP 会静默丢掉 $_POST 与 $_FILES,紧随的
   CSRF 校验必然失败、报「请求已过期」——这是最难排查的一类问题。
   现在按 CONTENT_LENGTH 提前识别并给出人话提示,且该守卫必须排在
   csrf_check() 之前(只读长度、不改状态,所以顺序上是安全的)。

已验证:本地 php -l 全量、模板渲染 255 项、amap_nav_url 单测 18 项、
真实浏览器交互 47 项(时间框三处点击都弹、多选/移除后 input.files 真被
改写、超限被前端拦下、导航链接随选点刷新、勾选删除变红、三视口无溢出)、
打包 57 文件、远端 e2e 267 项,全部通过;部署后双 PHP 渲染逐字节一致。
2026-09-23 07:53:20 +08:00

1076 lines
49 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.
/* ============================================================================
家庭出行记录 · 样式表
----------------------------------------------------------------------------
组织方式:设计令牌 → 基础 → 布局 → 组件 → 页面 → 响应式
约定:
· 颜色/间距/圆角一律走 CSS 变量,改主题只动 :root
· 不写 !important;靠顺序与具体度控制
· 组件类名与模板一一对应,改名前先搜模板
========================================================================== */
/* ── 1. 设计令牌 ─────────────────────────────────────────────────────────── */
:root {
--bg: #f5f6f8;
--bg-soft: #eceef2;
--surface: #ffffff;
--surface-2: #fafbfc;
--border: #e3e6ec;
--border-strong: #cfd5df;
--text: #1c2029;
--text-soft: #5b6371;
--text-mute: #8b93a1;
--accent: #3d6bf5;
--accent-dark: #2f56cf;
--accent-soft: #eaf0ff;
--accent-ring: rgba(61, 107, 245, .18);
--success: #12855a;
--success-soft: #e4f6ee;
--warn: #b7791f;
--warn-soft: #fdf3e0;
--danger: #d63b3f;
--danger-soft: #fdecec;
--radius-sm: 8px;
--radius: 12px;
--radius-lg: 16px;
--shadow-xs: 0 1px 2px rgba(22, 28, 45, .06);
--shadow-sm: 0 2px 8px rgba(22, 28, 45, .07);
--shadow-md: 0 8px 24px rgba(22, 28, 45, .10);
--shadow-lg: 0 18px 48px rgba(22, 28, 45, .16);
/* ── 版心 ─────────────────────────────────────────────────────────────
顶栏与正文必须共用同一套「左右留白」,否则宽屏下两者左边缘会错开。 */
--container: 1080px; /* 正文列的目标宽度 */
--gutter: clamp(14px, 3.2vw, 24px); /* 窄屏时内容与视口边缘的最小留白 */
--topbar-h: 60px;
/* 版心左右留白:视口比 --container 宽时,把它推到「让正文列正好等于
--container 且居中」的位置;视口不够宽时退化为 --gutter。
⚠️ 顶栏与 .container 只共用这一个值,两边都不许再叠加
`max-width + margin auto` —— 那等于把内容推两次。曾经就是这么错的:
1920px 下正文列只剩 240px,2560px 下直接塌成 0,整页错位。
⚠️ 这里用 100% 而不是 100vw:100vw 含滚动条宽度,会让顶栏比正文
偏出半个滚动条。100% 相对各自包含块,两边算出来才严格相等。 */
--shell-pad: max(var(--gutter), calc((100% - var(--container)) / 2));
}
* { box-sizing: border-box; }
/* 图片一律不得宽过所在容器 —— 这是兜底,不是主要手段。
主因见下面 .avatar-preview / .cover-preview 的注释:尺寸要写在 img 上。
(这里只约束宽度,高度仍由各自的规则决定;单靠这条救不了「400×400 的方图
塞进 84px 圆框」那种高度溢出。) */
img { max-width: 100%; }
html { -webkit-text-size-adjust: 100%; }
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Hiragino Sans GB",
"Microsoft YaHei", "Segoe UI", Roboto, sans-serif;
font-size: 15px;
line-height: 1.62;
color: var(--text);
background: var(--bg);
-webkit-font-smoothing: antialiased;
}
a { color: var(--accent); text-decoration: none; }
a:hover { color: var(--accent-dark); text-decoration: underline; }
h1, h2, h3, h4 { margin: 0 0 .4em; line-height: 1.3; font-weight: 650; letter-spacing: -.01em; }
h1 { font-size: 22px; }
h2 { font-size: 17px; }
h3 { font-size: 15px; }
p { margin: 0 0 .8em; }
hr { border: 0; border-top: 1px solid var(--border); margin: 20px 0; }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px; }
/* 工具类 */
.muted { color: var(--text-mute); font-size: 13px; }
.danger { color: var(--danger); }
.meta { color: var(--text-mute); font-size: 12.5px; }
.two-col { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
.section-title {
font-size: 15px; font-weight: 650; margin: 28px 0 12px;
display: flex; align-items: center; gap: 8px;
}
.section-title::before {
content: ''; width: 3px; height: 15px; border-radius: 2px; background: var(--accent);
}
/* ── 2. 顶栏 ─────────────────────────────────────────────────────────────── */
.topbar {
position: sticky; top: 0; z-index: 40;
display: flex; align-items: center; justify-content: space-between;
gap: 12px;
min-height: var(--topbar-h);
padding: 8px var(--shell-pad); /* 与 .container 同一个版心留白 */
background: rgba(255, 255, 255, .88);
backdrop-filter: saturate(180%) blur(12px);
-webkit-backdrop-filter: saturate(180%) blur(12px);
border-bottom: 1px solid var(--border);
}
.brand {
display: inline-flex; align-items: center; gap: 8px;
min-width: 0; /* 允许品牌文字省略,别把顶栏撑破 */
font-weight: 680; font-size: 16px; color: var(--text); letter-spacing: -.01em;
}
.brand:hover { text-decoration: none; color: var(--text); }
.brand-mark { font-size: 18px; line-height: 1; }
.brand-text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* 右侧一整组(新建 + 菜单)整体不许被压缩:窄屏宁可省略名字,也别把菜单挤没 */
.topbar-right { display: flex; align-items: center; gap: 10px; flex: none; min-width: 0; }
/* ── 3. 右上角下拉菜单 ───────────────────────────────────────────────────── */
.menu { position: relative; flex: none; }
.menu-trigger {
list-style: none;
display: inline-flex; align-items: center; gap: 8px;
padding: 5px 10px 5px 5px;
border: 1px solid var(--border);
border-radius: 999px;
background: var(--surface);
cursor: pointer;
user-select: none;
transition: border-color .15s, box-shadow .15s, background .15s;
}
.menu-trigger::-webkit-details-marker { display: none; }
.menu-trigger:hover { border-color: var(--border-strong); box-shadow: var(--shadow-xs); }
.menu[open] > .menu-trigger {
border-color: var(--accent);
box-shadow: 0 0 0 3px var(--accent-ring);
background: var(--surface);
}
.menu-name {
font-size: 14px; font-weight: 550;
max-width: 7em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.menu-caret { font-size: 10px; color: var(--text-mute); transition: transform .18s; }
.menu[open] .menu-caret { transform: rotate(180deg); }
.menu-panel {
position: absolute; right: 0; top: calc(100% + 8px);
/* 宽:按最长菜单项撑开(不换行),但窄屏下不许宽过视口 */
width: max-content;
min-width: 224px;
max-width: min(320px, calc(100vw - var(--gutter) * 2));
/* 高:13 个菜单项接起来有 500 多 px,笔记本(768 高)会顶出屏幕底部。
给上限 + 滚动,菜单项再多也不会「乱七八糟」。 */
max-height: calc(100vh - var(--topbar-h) - 24px);
overflow-y: auto;
overscroll-behavior: contain;
padding: 6px;
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
box-shadow: var(--shadow-md);
animation: menu-in .14s ease-out;
z-index: 50;
}
@keyframes menu-in {
from { opacity: 0; transform: translateY(-6px); }
to { opacity: 1; transform: translateY(0); }
}
.menu-head { padding: 8px 10px 10px; border-bottom: 1px solid var(--border); margin-bottom: 4px; }
.menu-head-name { font-weight: 620; font-size: 14px; }
.menu-head-role { font-size: 12px; color: var(--text-mute); }
.menu-item {
display: flex; align-items: center; gap: 10px;
padding: 8px 10px;
border-radius: var(--radius-sm);
color: var(--text); font-size: 14px; text-decoration: none;
white-space: nowrap; /* 菜单项不折行,靠面板宽度自适应 */
transition: background .12s;
}
.menu-item:hover { background: var(--bg-soft); text-decoration: none; color: var(--text); }
.menu-icon { width: 18px; text-align: center; font-size: 15px; opacity: .9; }
.menu-sep {
font-size: 11px; color: var(--text-mute);
padding: 10px 10px 4px; letter-spacing: .06em;
}
.menu-item-danger { color: var(--danger); }
.menu-item-danger:hover { background: var(--danger-soft); color: var(--danger); }
/* ── 4. 容器与页头 ───────────────────────────────────────────────────────── */
.container {
/* 这里**故意不写 max-width**:正文列宽度由 --shell-pad 反推 ——
视口够宽时 padding 会把它挤到正好 var(--container) 宽且居中;
这与 .topbar 用的是同一个公式,所以两者左边缘必然对齐。
曾经这里把 max-width 也设成了 --container,再配 `margin: 0 auto` 和同一份
padding:等于把内容推两次。1920px 下正文列只剩 240px、2560px 下塌成 0,
是本次「整页错位」的直接原因。改这里前请先想清楚这一点。 */
margin: 0 auto;
padding: 24px var(--shell-pad) 64px;
}
.container-guest {
max-width: 420px;
display: flex; align-items: center; justify-content: center;
min-height: 100vh; padding: 24px 16px;
}
.page-header {
display: flex; align-items: flex-start; justify-content: space-between;
gap: 16px; flex-wrap: wrap;
margin-bottom: 20px;
}
.page-header h1 { margin-bottom: 3px; }
.page-header .muted { margin: 0; }
.page-header > div:first-child { min-width: 0; }
.footer {
text-align: center; color: var(--text-mute); font-size: 12.5px;
padding: 20px 16px 36px;
}
/* ── 5. 按钮 ─────────────────────────────────────────────────────────────── */
.btn, .btn-primary, .btn-danger, .btn-sm, .btn-primary-sm {
display: inline-flex; align-items: center; justify-content: center; gap: 6px;
padding: 8px 15px;
border: 1px solid var(--border);
border-radius: var(--radius-sm);
background: var(--surface);
color: var(--text);
font-size: 14px; font-family: inherit; font-weight: 550; line-height: 1.4;
cursor: pointer; text-decoration: none; white-space: nowrap;
transition: background .15s, border-color .15s, box-shadow .15s, transform .06s;
}
.btn:hover, .btn-sm:hover {
background: var(--surface-2); border-color: var(--border-strong);
text-decoration: none; color: var(--text);
}
.btn:active, .btn-primary:active, .btn-danger:active { transform: translateY(1px); }
.btn-primary, .btn-primary-sm {
background: var(--accent); border-color: var(--accent); color: #fff;
box-shadow: var(--shadow-xs);
}
.btn-primary:hover, .btn-primary-sm:hover {
background: var(--accent-dark); border-color: var(--accent-dark);
color: #fff; text-decoration: none;
}
.btn-danger { background: var(--danger); border-color: var(--danger); color: #fff; }
.btn-danger:hover { background: #bd3135; border-color: #bd3135; color: #fff; text-decoration: none; }
.btn-sm, .btn-primary-sm { padding: 5px 11px; font-size: 13px; }
.topbar-new { flex: none; }
/* ── 6. 提示 / 空态 / 标签 ───────────────────────────────────────────────── */
.alert {
display: flex; align-items: flex-start; gap: 8px;
padding: 11px 14px; margin-bottom: 18px;
background: var(--accent-soft);
border: 1px solid #cddbff;
border-left: 3px solid var(--accent);
border-radius: var(--radius-sm);
color: #1f3d8f; font-size: 14px;
}
.empty {
text-align: center; color: var(--text-mute);
padding: 40px 20px; font-size: 14px;
background: var(--surface);
border: 1px dashed var(--border-strong);
border-radius: var(--radius);
}
.empty p { margin: 0; }
.tag-default, .tag-self, .tag-temp, .status-tag, .mode-tag, .veh-tag, .role-tag, .node-type {
display: inline-flex; align-items: center; gap: 4px;
padding: 2px 8px; border-radius: 999px;
font-size: 12px; font-weight: 550; line-height: 1.7; white-space: nowrap;
}
.tag-default { background: var(--bg-soft); color: var(--text-soft); }
.tag-self { background: var(--success-soft); color: var(--success); }
.tag-temp { background: var(--warn-soft); color: var(--warn); }
.node-type { background: var(--accent-soft); color: var(--accent-dark); }
.status-planned { background: var(--bg-soft); color: var(--text-soft); }
.status-ongoing { background: var(--accent-soft); color: var(--accent-dark); }
.status-completed { background: var(--success-soft); color: var(--success); }
.status-cancelled { background: var(--danger-soft); color: var(--danger); }
/* 账号状态(用户列表用文字 + 颜色,不是胶囊标签) */
.status-active { color: var(--success); font-size: 13px; font-weight: 550; }
.status-inactive { color: var(--text-mute); font-size: 13px; }
.mode-tag { background: var(--bg-soft); color: var(--text-soft); }
.mode-drive_own, .mode-drive_rental { background: #e9f2ff; color: #1e5bb8; }
.mode-train { background: #eaf1ff; color: #2f56cf; }
.mode-plane { background: #e8f6fb; color: #17708f; }
.mode-ship { background: #e6f7f4; color: #106b60; }
.mode-walk, .mode-bike { background: var(--success-soft); color: var(--success); }
.mode-subway, .mode-bus, .mode-taxi { background: var(--warn-soft); color: var(--warn); }
.veh-own { background: var(--bg-soft); color: var(--text-soft); }
.veh-rental { background: var(--warn-soft); color: var(--warn); }
.role-admin { background: #f0e9ff; color: #6b3fc4; }
.role-user { background: var(--bg-soft); color: var(--text-soft); }
.money-over { color: var(--danger); }
.money-under { color: var(--success); }
/* ── 7. 卡片 ─────────────────────────────────────────────────────────────── */
.card-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(248px, 1fr));
gap: 16px;
}
.card {
display: flex; flex-direction: column;
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius-lg);
overflow: hidden;
transition: transform .16s, box-shadow .16s, border-color .16s;
}
.card:hover { transform: translateY(-2px); box-shadow: var(--shadow-sm); border-color: var(--border-strong); }
.card-cover { position: relative; aspect-ratio: 16 / 9; background: var(--bg-soft); overflow: hidden; }
.card-cover img { width: 100%; height: 100%; object-fit: cover; display: block; }
.card-cover-placeholder, .cover-placeholder {
display: flex; align-items: center; justify-content: center;
height: 100%; min-height: 96px;
background: linear-gradient(135deg, #eef2fb, #e6ebf7);
color: #9aa6bd; font-size: 30px;
}
.card-body { padding: 14px 16px 16px; display: flex; flex-direction: column; gap: 6px; flex: 1; }
.card-body h3 { margin: 0; font-size: 15.5px; }
.card-body h3 a { color: var(--text); }
.card-body h3 a:hover { color: var(--accent); text-decoration: none; }
.activity-cover { position: relative; border-radius: var(--radius-lg); overflow: hidden; margin-bottom: 20px; }
.activity-cover img { width: 100%; max-height: 320px; object-fit: cover; display: block; }
.activity-cover .cover-placeholder { min-height: 150px; }
/* ── 8. 时间轴与节点卡片 ─────────────────────────────────────────────────── */
.timeline-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; align-items: start; }
.timeline-col > h2 { margin-bottom: 12px; }
.node-card {
background: var(--surface);
border: 1px solid var(--border);
border-left: 3px solid var(--border-strong);
border-radius: var(--radius);
padding: 13px 15px;
margin-bottom: 12px;
transition: box-shadow .15s, border-color .15s;
}
.node-card:hover { box-shadow: var(--shadow-sm); }
.node-plan { border-left-color: #9db4f0; }
.node-reality { border-left-color: var(--success); }
.node-cancelled { opacity: .62; }
.node-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 10px; }
.node-title { font-weight: 620; font-size: 15px; display: flex; align-items: center; gap: 7px; flex-wrap: wrap; }
.node-icon { font-size: 15px; }
.node-time { color: var(--text-mute); font-size: 12.5px; margin-top: 2px; }
.node-loc {
display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
font-size: 13px; color: var(--text-soft); margin-top: 6px;
}
.loc-start, .loc-end { display: inline-flex; align-items: center; gap: 4px; }
.loc-arrow { color: var(--text-mute); }
.node-transport { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; margin-top: 7px; }
.node-cancel-reason { font-size: 12.5px; color: var(--danger); margin-top: 6px; }
.node-actions {
display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
margin-top: 10px; padding-top: 9px; border-top: 1px dashed var(--border);
}
.node-action-link, .node-action-strong {
font-size: 12.5px; color: var(--accent); padding: 3px 7px; border-radius: 6px;
}
.node-action-link:hover, .node-action-strong:hover { background: var(--accent-soft); text-decoration: none; }
.node-action-strong { font-weight: 600; }
.node-action-note { font-size: 12.5px; color: var(--text-mute); }
.node-action-btn, .node-action-form button {
font-size: 12.5px; padding: 3px 9px; border-radius: 6px;
border: 1px solid var(--border); background: var(--surface); color: var(--text-soft);
cursor: pointer; font-family: inherit;
}
.node-action-btn:hover, .node-action-form button:hover { border-color: var(--border-strong); color: var(--text); }
.node-action-form { display: inline; }
/* ── 9. 表单 ─────────────────────────────────────────────────────────────── */
.form { max-width: 720px; }
.form-plain { max-width: none; }
.form-row { margin-bottom: 15px; }
.form-row > label {
display: block; margin-bottom: 5px;
font-size: 13px; font-weight: 600; color: var(--text-soft);
}
.form-row .muted { margin-top: 4px; }
input[type="text"], input[type="password"], input[type="number"], input[type="datetime-local"],
input[type="date"], input[type="email"], input[type="tel"], input[type="url"], input[type="search"],
select, textarea {
width: 100%;
padding: 9px 11px;
border: 1px solid var(--border-strong);
border-radius: var(--radius-sm);
background: var(--surface);
color: var(--text);
font-size: 14px; font-family: inherit; line-height: 1.5;
transition: border-color .15s, box-shadow .15s;
}
input:focus, select:focus, textarea:focus {
outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-ring);
}
input:disabled, select:disabled, textarea:disabled { background: var(--bg-soft); color: var(--text-mute); }
input[type="file"] { font-size: 13px; padding: 6px 0; border: 0; background: none; width: auto; }
textarea { resize: vertical; min-height: 84px; }
select { cursor: pointer; }
/* 时间框:点框内任何一处都能弹选择器(由 layout.php 里的 showPicker 负责,
原生只有点右侧那个小图标才弹)。光标给个「整块可点」的暗示,别让人以为只有图标能点。 */
input[type="datetime-local"], input[type="date"] { cursor: pointer; }
input[type="datetime-local"]::-webkit-calendar-picker-indicator,
input[type="date"]::-webkit-calendar-picker-indicator { cursor: pointer; padding: 2px; }
.form-actions {
display: flex; align-items: center; gap: 10px;
margin-top: 22px; padding-top: 18px; border-top: 1px solid var(--border);
}
.fieldset {
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 15px 16px 4px;
margin: 0 0 16px;
background: var(--surface-2);
}
.fieldset > legend {
padding: 0 7px; font-size: 13px; font-weight: 620; color: var(--text-soft);
}
.inline-panel {
padding: 14px 16px; margin: 12px 0;
background: var(--surface-2);
border: 1px solid var(--border);
border-radius: var(--radius);
}
.panel {
padding: 18px;
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
margin-bottom: 18px;
}
.radio-group { display: flex; flex-wrap: wrap; gap: 8px; }
.radio-label {
display: inline-flex; align-items: center; gap: 6px;
padding: 6px 11px; border: 1px solid var(--border-strong); border-radius: 999px;
font-size: 13px; cursor: pointer; background: var(--surface);
}
.radio-label:hover { border-color: var(--accent); }
.radio-label input { margin: 0; }
.check-grid { display: flex; flex-wrap: wrap; gap: 8px; }
.checkbox-label {
display: inline-flex; align-items: center; gap: 6px;
padding: 6px 11px; border: 1px solid var(--border-strong); border-radius: var(--radius-sm);
font-size: 13px; cursor: pointer; background: var(--surface); max-width: 100%;
}
.checkbox-label:hover { border-color: var(--accent); background: var(--accent-soft); }
.checkbox-label input { margin: 0; }
.checkbox-label span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* 高德地址联想下拉 */
.amap-dropdown {
position: absolute; left: 0; right: 0; top: calc(100% + 4px);
max-height: 260px; overflow-y: auto;
background: var(--surface);
border: 1px solid var(--border-strong);
border-radius: var(--radius-sm);
box-shadow: var(--shadow-md);
z-index: 30;
}
.amap-dropdown-item { padding: 9px 12px; cursor: pointer; border-bottom: 1px solid var(--border); }
.amap-dropdown-item:last-child { border-bottom: 0; }
.amap-dropdown-item:hover, .amap-dropdown-item.active { background: var(--accent-soft); }
.amap-dropdown-name { font-size: 13.5px; }
.amap-dropdown-sub { font-size: 12px; color: var(--text-mute); }
/* 起点 / 终点的「导航到这里」。
有没有坐标决定它是什么链接,所以由服务端与 JS 共同决定显隐;
必须排在 .form-row .muted 之后(同权重,靠后者生效)。 */
.loc-nav { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-top: 8px; }
.loc-nav-link {
display: inline-flex; align-items: center; gap: 4px;
font-size: 13px; font-weight: 560; line-height: 1.4;
padding: 4px 11px; border-radius: 999px;
background: var(--accent-soft); color: var(--accent);
border: 1px solid transparent;
}
.loc-nav-link:hover { border-color: var(--accent); text-decoration: none; }
.loc-nav .muted { margin-top: 0; font-size: 12px; }
/* 详情页里跟在地址后面的那个链接(不是整块,只是行内追加一个胶囊) */
.loc-nav-inline { margin-left: 8px; vertical-align: middle; font-size: 12.5px; padding: 3px 9px; }
/* emoji 选择 */
.emoji-picker { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
.emoji-btn {
width: 34px; height: 34px; font-size: 17px; line-height: 1;
display: inline-flex; align-items: center; justify-content: center;
border: 1px solid var(--border); border-radius: var(--radius-sm);
background: var(--surface); cursor: pointer; padding: 0;
}
.emoji-btn:hover { border-color: var(--accent); background: var(--accent-soft); }
/* 表格 */
.data-table { width: 100%; border-collapse: collapse; font-size: 14px; background: var(--surface); }
.data-table th, .data-table td { padding: 10px 12px; text-align: left; border-bottom: 1px solid var(--border); }
.data-table th { font-size: 12.5px; font-weight: 620; color: var(--text-mute); background: var(--surface-2); white-space: nowrap; }
.data-table tr:last-child td { border-bottom: 0; }
.data-table tbody tr:hover { background: var(--surface-2); }
.data-table td.actions { white-space: nowrap; }
.data-table td.actions a, .data-table td.actions button { margin-right: 8px; }
/* ── 10. 详情页 ──────────────────────────────────────────────────────────── */
.detail-grid {
display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 14px 22px;
padding: 16px 18px;
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
}
.detail-item { min-width: 0; }
.detail-item-wide { grid-column: 1 / -1; }
.detail-label { font-size: 12px; color: var(--text-mute); margin-bottom: 2px; }
.detail-value { font-size: 14px; word-break: break-word; }
/* 花销汇总条 */
.activity-stats { display: flex; flex-wrap: wrap; gap: 10px; margin: 0 0 18px; }
.activity-stat {
flex: 1 1 140px;
padding: 11px 14px;
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
display: flex; flex-direction: column; gap: 1px;
}
.activity-stat .detail-label { margin: 0; }
.activity-stat strong { font-size: 16px; }
/* ── 11. 评论 ────────────────────────────────────────────────────────────── */
.comment-thread { margin-top: 8px; }
.comment-list { display: flex; flex-direction: column; gap: 14px; }
.comment-item {
padding: 12px 14px;
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
}
.comment-child {
margin-left: 22px;
background: var(--surface-2);
border-left: 2px solid var(--accent-soft);
}
.comment-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 5px; }
.comment-author { font-weight: 620; font-size: 13.5px; }
.comment-time { font-size: 12px; color: var(--text-mute); }
.comment-body { font-size: 14px; white-space: pre-wrap; word-break: break-word; }
.comment-replies { margin-top: 10px; display: flex; flex-direction: column; gap: 10px; }
.comment-del-form { display: inline; margin-left: auto; }
.comment-del {
border: 0; background: none; color: var(--text-mute);
font-size: 15px; line-height: 1; cursor: pointer; padding: 2px 6px; border-radius: 6px;
}
.comment-del:hover { background: var(--danger-soft); color: var(--danger); }
.comment-form { margin-top: 14px; }
.comment-form-inline { margin-top: 8px; }
.comment-form textarea, .comment-form-inline textarea { min-height: 68px; }
.comment-reply-box { margin-top: 10px; }
/* ── 12. 参与人 ──────────────────────────────────────────────────────────── */
.member-list { display: flex; flex-direction: column; gap: 10px; }
.member-row {
display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
padding: 10px 13px;
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
}
.member-name { font-weight: 600; font-size: 14px; }
.member-role { display: inline-flex; flex-wrap: wrap; gap: 5px; }
.member-emotion { font-size: 17px; line-height: 1; }
.member-checkin { font-size: 12.5px; color: var(--text-mute); }
.member-del-form { display: inline; margin-left: auto; }
.member-del-form button {
border: 0; background: none; color: var(--text-mute);
font-size: 15px; cursor: pointer; padding: 2px 6px; border-radius: 6px;
}
.member-del-form button:hover { background: var(--danger-soft); color: var(--danger); }
.emotion-grid { display: flex; flex-wrap: wrap; gap: 6px; }
.emotion-option {
display: inline-flex; align-items: center; gap: 5px;
padding: 5px 10px; border: 1px solid var(--border-strong); border-radius: 999px;
cursor: pointer; font-size: 14px; background: var(--surface);
}
.emotion-option:hover { border-color: var(--accent); }
.emotion-option input { margin: 0; }
.emotion-face { font-size: 16px; line-height: 1; }
.emotion-text { font-size: 12.5px; color: var(--text-soft); }
/* ── 13. 花销 ────────────────────────────────────────────────────────────── */
.expense-summary { display: flex; flex-wrap: wrap; gap: 10px; margin-bottom: 14px; }
.expense-stat {
flex: 1 1 130px;
padding: 11px 14px;
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
}
.expense-stat > div:first-child { font-size: 12px; color: var(--text-mute); }
.expense-stat strong { font-size: 16px; }
.expense-list { display: flex; flex-direction: column; gap: 10px; }
.expense-row {
display: flex; align-items: flex-start; justify-content: space-between; gap: 12px;
padding: 11px 14px;
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
}
.expense-main { min-width: 0; }
.expense-cat { font-weight: 600; font-size: 14px; display: flex; align-items: center; gap: 7px; flex-wrap: wrap; }
.expense-desc { font-size: 13px; color: var(--text-soft); margin-top: 2px; }
.expense-sub { font-size: 12.5px; color: var(--text-mute); margin-top: 3px; }
.expense-amount { font-size: 15px; font-weight: 650; white-space: nowrap; }
.expense-kind { font-size: 11.5px; padding: 1px 7px; border-radius: 999px; font-weight: 550; }
.expense-kind-budget { background: var(--bg-soft); color: var(--text-soft); }
.expense-kind-actual { background: var(--success-soft); color: var(--success); }
/* ── 14. 附件与照片 ──────────────────────────────────────────────────────── */
.drop-zone {
border: 2px dashed var(--border-strong);
border-radius: var(--radius);
background: var(--surface);
padding: 22px 16px;
text-align: center;
cursor: pointer;
transition: border-color .15s, background .15s;
}
.drop-zone:hover, .drop-zone.dragging { border-color: var(--accent); background: var(--accent-soft); }
.drop-zone-inner { color: var(--text-soft); }
.drop-zone-inner .muted { margin-top: 4px; }
.file-list { display: flex; flex-direction: column; gap: 6px; margin-top: 12px; }
.file-row {
font-size: 13px; color: var(--text-soft);
padding: 7px 11px; background: var(--surface-2);
border: 1px solid var(--border); border-radius: var(--radius-sm);
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* 待上传照片的暂存区 */
.photo-stage { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 14px; }
.photo-item {
width: 132px; background: var(--surface);
border: 1px solid var(--border); border-radius: var(--radius);
overflow: hidden; display: flex; flex-direction: column;
}
.photo-thumb { width: 100%; height: 96px; object-fit: cover; background: var(--bg-soft); display: block; }
.photo-info { padding: 6px 8px 0; font-size: 11.5px; color: var(--text-mute); }
.photo-name { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--text-soft); }
.photo-actions { display: flex; gap: 4px; padding: 6px 8px 8px; }
.photo-actions button {
flex: 1; font-size: 11.5px; padding: 4px; cursor: pointer;
border: 1px solid var(--border); border-radius: 6px;
background: var(--surface); color: var(--text-soft); font-family: inherit;
}
.photo-actions button:hover { border-color: var(--accent); color: var(--accent); }
.photo-badge {
margin: 6px 8px 0; align-self: flex-start;
font-size: 11px; padding: 1px 6px; border-radius: 999px;
background: var(--success-soft); color: var(--success);
}
/* 已上传的照片墙 */
.photo-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
gap: 12px;
}
.photo-grid-sm { grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); }
/* 已有附件的「勾选即删除」格子:整格可点,勾上后变灰 + 红勾。
原生 checkbox 藏起来但保留可聚焦,键盘也能用(勾选框上会有 focus 环)。 */
.photo-pick {
position: relative; display: block; cursor: pointer; margin: 0;
border: 1px solid var(--border); border-radius: var(--radius);
overflow: hidden; background: var(--surface);
transition: border-color .15s, box-shadow .15s;
}
.photo-pick:hover { border-color: var(--accent); }
.photo-pick img { width: 100%; aspect-ratio: 4 / 3; object-fit: cover; display: block; background: var(--bg-soft); }
.photo-pick-ext {
display: flex; align-items: center; justify-content: center;
aspect-ratio: 4 / 3; font-size: 12px; font-weight: 600;
color: var(--text-mute); background: var(--bg-soft);
}
.photo-pick input { position: absolute; width: 0; height: 0; opacity: 0; }
.photo-pick-mark {
position: absolute; top: 6px; right: 6px;
width: 22px; height: 22px; border-radius: 50%;
display: flex; align-items: center; justify-content: center;
font-size: 13px; line-height: 1; color: transparent;
background: rgba(20, 24, 34, .45);
border: 1px solid rgba(255, 255, 255, .85);
transition: background .15s, color .15s, border-color .15s;
}
.photo-pick input:checked ~ img,
.photo-pick input:checked ~ .photo-pick-ext { opacity: .35; filter: grayscale(1); }
.photo-pick input:checked ~ .photo-pick-mark { background: var(--danger); border-color: var(--danger); color: #fff; }
.photo-pick input:focus-visible ~ .photo-pick-mark { box-shadow: 0 0 0 3px var(--accent-ring); }
/* 表单里刚选中的待上传文件(复用 .photo-card 的样式,只是要一个能挂 × 按钮的位置) */
.photo-card > .photo-del { position: absolute; top: 6px; right: 6px; }
.photo-card {
position: relative; display: block;
border: 1px solid var(--border); border-radius: var(--radius);
overflow: hidden; background: var(--surface);
transition: box-shadow .15s, transform .15s;
}
.photo-card:hover { box-shadow: var(--shadow-sm); transform: translateY(-2px); }
.photo-card > a { display: block; color: inherit; }
.photo-card > a:hover { text-decoration: none; }
.photo-card img { width: 100%; aspect-ratio: 4 / 3; object-fit: cover; display: block; background: var(--bg-soft); }
/* 存储后端标记(本地 / OSS),压在缩略图左上角 */
.photo-tag {
position: absolute; top: 6px; left: 6px;
font-size: 10.5px; padding: 1px 6px; border-radius: 999px;
background: rgba(20, 24, 34, .6); color: #fff; letter-spacing: .3px;
}
.photo-tag-oss { background: rgba(232, 122, 45, .92); }
.photo-cap { padding: 8px 10px 10px; font-size: 12px; color: var(--text-mute); }
.photo-cap-name {
display: block; font-size: 12.5px; color: var(--text);
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.photo-cap-sub {
display: block; margin-top: 2px; font-size: 11.5px; color: var(--text-mute);
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.photo-cap-links { display: block; margin-top: 7px; }
.photo-cap-links a { font-size: 12px; margin-right: 12px; }
.photo-del-form { position: absolute; top: 6px; right: 6px; margin: 0; opacity: 0; transition: opacity .15s; }
.photo-card:hover .photo-del-form, .photo-del-form:focus-within { opacity: 1; }
.photo-del {
width: 26px; height: 26px; border-radius: 50%;
border: 0; cursor: pointer; line-height: 1;
background: rgba(20, 24, 34, .55); color: #fff; font-size: 15px;
display: flex; align-items: center; justify-content: center;
}
.photo-del:hover { background: var(--danger); }
/* 详情页上的小照片墙 */
.photo-more {
display: flex; flex-direction: column; align-items: center; justify-content: center;
aspect-ratio: 4 / 3; border: 1px dashed var(--border-strong);
border-radius: var(--radius); background: var(--surface-2);
color: var(--text-soft); font-size: 18px; font-weight: 620;
}
.photo-more span { font-size: 11.5px; font-weight: 400; color: var(--text-mute); }
.photo-more:hover { border-color: var(--accent); color: var(--accent); text-decoration: none; }
.photo-bar { display: flex; align-items: center; gap: 12px; margin-top: 12px; flex-wrap: wrap; }
/* 通用附件卡(非图片也用) */
.attachment-grid {
display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 12px;
}
.attachment-card {
border: 1px solid var(--border); border-radius: var(--radius);
overflow: hidden; background: var(--surface); display: flex; flex-direction: column;
}
.attachment-link { display: block; color: inherit; }
.attachment-link:hover { text-decoration: none; }
.attachment-thumb { width: 100%; aspect-ratio: 4 / 3; object-fit: cover; display: block; background: var(--bg-soft); }
.attachment-icon {
display: flex; align-items: center; justify-content: center;
height: 96px; font-size: 30px; background: var(--bg-soft);
}
.attachment-meta { padding: 9px 11px; display: flex; align-items: center; gap: 6px; }
.attachment-name {
font-size: 13px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1; min-width: 0;
}
.attachment-sub { font-size: 11.5px; color: var(--text-mute); }
.attachment-delete {
border: 0; background: none; color: var(--text-mute);
font-size: 16px; line-height: 1; cursor: pointer; padding: 2px 6px; border-radius: 6px; flex: none;
}
.attachment-delete:hover { background: var(--danger-soft); color: var(--danger); }
/* ── 15. 弹窗与裁剪 ──────────────────────────────────────────────────────── */
.modal {
position: fixed; inset: 0; z-index: 100;
display: none; align-items: center; justify-content: center;
padding: 20px;
}
.modal.open { display: flex; }
.modal-backdrop { position: absolute; inset: 0; background: rgba(18, 22, 32, .58); }
.modal-content {
position: relative; z-index: 1;
width: 100%; max-width: 460px;
max-height: calc(100vh - 40px); overflow: auto;
background: var(--surface);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-lg);
padding: 20px;
}
.modal-content-wide { max-width: 720px; }
.modal-actions {
display: flex; justify-content: flex-end; gap: 10px;
margin-top: 18px; padding-top: 16px; border-top: 1px solid var(--border);
}
.crop-container { width: 100%; max-height: 60vh; overflow: hidden; background: #111; border-radius: var(--radius-sm); }
.crop-container-wide { max-height: 66vh; }
/* Cropper 需要按原始尺寸接管这张图(自己算内联宽高),不能被 max-width 夹住;
容器自带 overflow:hidden,所以就算图超出也只会被裁掉,不会撑破页面。 */
.crop-container img { max-width: none; display: block; }
/* 预览容器只负责「框」:定尺寸、圆角、把图片裁进去。
⚠️ width / height / object-fit 必须写在 **img** 上,不能写在外层 div 上 ——
写错地方图片就完全不受约束,会按原始尺寸铺出来:头像 400×400、封面
1920×1080,直接把下面的表单盖住(这就是「头像非常大、整页错位」的根)。
对照写对了的 .card-cover img / .activity-cover img。 */
.avatar-preview {
position: relative; flex: none;
width: 84px; height: 84px; border-radius: 50%;
overflow: hidden; /* 把方图裁成圆,并兜住任何溢出 */
background: var(--bg-soft); border: 2px dashed var(--border-strong);
}
.cover-preview {
position: relative;
width: 100%; aspect-ratio: 16 / 9;
overflow: hidden;
border-radius: var(--radius); background: var(--bg-soft);
border: 2px dashed var(--border-strong);
}
/* 图和占位块都用绝对定位铺满容器:
百分比相对 padding box 解析,宽高一定确定。
⚠️ 不要用 `display: grid; place-items: center` + `width: 100%` ——
自动列宽会反过来依赖元素自身宽度,形成循环,浏览器只能把图算成 0 宽
(实测就是这么坏的:img 宽 0、只剩一个空框)。 */
.avatar-preview > img,
.cover-preview > img {
position: absolute; inset: 0;
display: block; width: 100%; height: 100%; object-fit: cover;
}
.avatar-preview > .avatar-placeholder,
.cover-preview > .cover-placeholder {
position: absolute; inset: 0;
width: 100%; height: 100%;
display: flex; align-items: center; justify-content: center;
}
/* 上传区只是「预览 + 操作按钮」的布局容器,不是那个虚线圆框本身。
⚠️ 这里曾经把 84×84 的圆形尺寸套在 .avatar-upload 上,而模板把预览、
按钮、提示文字都放在它**内部** → 三样东西全被塞进那个小圆里互相重叠
(编辑用户页的「选择图片/移除」就压在头像上)。圆框属于
.avatar-preview / .cover-preview,容器只负责排列。 */
.avatar-upload {
display: flex; align-items: center; gap: 16px;
width: auto; height: auto;
border: 0; border-radius: 0; background: none;
}
.cover-upload {
display: flex; flex-direction: column; gap: 12px;
width: 100%; height: auto;
border: 0; border-radius: 0; background: none;
}
/* "+" 占位块:尺寸已经被 `.avatar-preview > .avatar-placeholder` 铺满了,这里只管样子 */
.avatar-placeholder {
border-radius: 50%;
background: var(--bg-soft); color: var(--text-mute); font-size: 26px;
}
/* 悬停高亮落在预览框上(那才是"会被换掉的东西") */
.avatar-preview, .cover-preview { transition: border-color .15s, background .15s; }
.avatar-upload:hover .avatar-preview,
.cover-upload:hover .cover-preview { border-color: var(--accent); background: var(--accent-soft); }
.avatar-upload-actions, .cover-upload-actions {
display: flex; flex-wrap: wrap; align-items: center; gap: 8px 10px; margin-top: 0;
}
/* ── 16. 头像与用户 ──────────────────────────────────────────────────────── */
/* 这两类是直接写在 <img>(或首字母 <span>)上的,所以尺寸放这里是对的。
注意别把 .avatar-placeholder 的 84px 带进来:它常与 .avatar-sm 叠用
(用户列表里用首字母当头像),靠下面那条组合规则把字号压回 13px。 */
.avatar-xs, .avatar-sm {
border-radius: 50%; object-fit: cover; flex: none;
display: inline-flex; align-items: center; justify-content: center;
overflow: hidden;
}
.avatar-xs { width: 26px; height: 26px; }
.avatar-sm { width: 30px; height: 30px; }
.avatar-initial { background: var(--accent); color: #fff; font-size: 12px; font-weight: 620; }
.avatar-sm.avatar-placeholder,
.avatar-xs.avatar-placeholder {
font-size: 13px; font-weight: 620;
background: var(--accent-soft); color: var(--accent);
}
.user-cell { display: flex; align-items: center; gap: 9px; }
.user-info { display: inline-flex; align-items: center; gap: 8px; }
/* ── 17. 登录页 ──────────────────────────────────────────────────────────── */
.login-wrapper { width: 100%; }
.login-form {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-md);
padding: 28px 24px;
}
.login-form h1 { font-size: 19px; text-align: center; margin-bottom: 4px; }
.login-form > p { text-align: center; margin-bottom: 20px; }
/* ── 18. 存储设置页 ──────────────────────────────────────────────────────── */
.settings-status { display: flex; flex-wrap: wrap; gap: 10px; margin-bottom: 18px; }
.settings-chip {
flex: 1 1 190px;
padding: 11px 14px;
background: var(--surface);
border: 1px solid var(--border);
border-left: 3px solid var(--accent);
border-radius: var(--radius);
font-size: 13px;
}
.settings-chip.ok { border-left-color: var(--success); }
.settings-chip.warn { border-left-color: var(--warn); }
.settings-chip > div:first-child { font-size: 12px; color: var(--text-mute); }
.settings-chip strong { font-size: 14px; }
.settings-hint { font-size: 12.5px; color: var(--text-mute); margin-top: 5px; line-height: 1.6; }
.settings-hint code { background: var(--bg-soft); padding: 1px 5px; border-radius: 4px; font-size: 12px; word-break: break-all; }
.oss-fields { display: none; }
.oss-fields.show { display: block; }
.test-result {
margin-top: 12px; padding: 11px 13px; border-radius: var(--radius-sm);
font-size: 13px; display: none; white-space: pre-wrap; word-break: break-word;
}
.test-result.show { display: block; }
.test-result.ok { background: var(--success-soft); color: var(--success); border: 1px solid #b9e4d1; }
.test-result.bad { background: var(--danger-soft); color: var(--danger); border: 1px solid #f3c9ca; }
.role-chips { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 7px; }
.role-chip {
font-size: 12.5px; padding: 3px 10px; border-radius: 999px;
border: 1px solid var(--border-strong); background: var(--surface);
cursor: pointer; font-family: inherit; color: var(--text-soft);
}
.role-chip:hover { border-color: var(--accent); color: var(--accent); background: var(--accent-soft); }
/* ── 19. 形态开关(自有车 / 租车 等按需显示的字段) ─────────────────────── */
.own-only, .rental-only, .mode-field { display: none; }
/* ── 20. 附件页与表单补充 ───────────────────────────────────────────────── */
/* 表单里的小字说明 */
.form-hint { font-size: 12.5px; color: var(--text-mute); margin-top: 5px; line-height: 1.65; }
.form-hint-error { color: var(--danger); font-weight: 560; }
.form-hint code, .settings-hint code, .alert code {
background: var(--bg-soft); padding: 1px 5px; border-radius: 4px;
font-size: 12px; word-break: break-all;
}
.alert-warn { background: var(--warn-soft); color: var(--warn); border-color: #ecd9b4; }
.text-danger { color: var(--danger); }
.req { color: var(--danger); font-weight: 620; }
.inline-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.inline-row > input { flex: 1 1 220px; min-width: 0; }
/* 上传区 */
.drop-zone-icon { font-size: 30px; line-height: 1.1; }
.drop-zone-title { margin: 6px 0 0; font-size: 14.5px; color: var(--text); }
.drop-zone-hint { font-size: 12.5px; margin: 4px 0 0; }
/* 待上传暂存区 */
.stage-head {
display: flex; align-items: center; justify-content: space-between;
gap: 12px; margin-top: 18px; flex-wrap: wrap;
}
.stage-count { font-size: 13px; color: var(--text-soft); }
.stage-tools { display: flex; gap: 8px; }
.photo-thumb-icon {
display: flex; align-items: center; justify-content: center;
font-size: 28px; height: 96px; background: var(--bg-soft);
}
.photo-badge-plain { background: var(--bg-soft); color: var(--text-soft); }
.photo-actions button[disabled] { opacity: .45; cursor: not-allowed; }
/* 上传进度 */
.upload-progress { margin-top: 14px; }
.upload-track {
height: 8px; border-radius: 999px; background: var(--bg-soft); overflow: hidden;
}
.upload-bar {
height: 100%; width: 0; border-radius: 999px;
background: var(--accent); transition: width .2s ease;
}
.upload-text { margin-top: 6px; font-size: 12.5px; color: var(--text-mute); }
/* 弹窗标题 */
.modal-title { font-size: 16px; margin: 0 0 4px; }
.modal-hint { font-size: 12.5px; color: var(--text-mute); margin: 0 0 12px; }
/* ── 21. 响应式 ──────────────────────────────────────────────────────────── */
@media (max-width: 820px) {
.timeline-grid { grid-template-columns: 1fr; }
.card-grid { grid-template-columns: repeat(auto-fill, minmax(210px, 1fr)); }
}
@media (max-width: 640px) {
body { font-size: 14.5px; }
/* 留白仍走 --shell-pad(此时它等于 --gutter):断点里也不能改成固定值,
否则窄屏下顶栏与正文又会对不齐。 */
.container { padding: 16px var(--shell-pad) 52px; }
.container-guest { padding: 20px 14px; }
.topbar { padding: 8px var(--shell-pad); }
.brand-text { display: none; }
.menu-name { display: none; }
.menu-trigger { padding: 4px; gap: 4px; }
.menu-panel { min-width: 208px; }
.two-col { grid-template-columns: 1fr; }
.card-grid, .photo-grid, .attachment-grid {
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
}
.detail-grid { grid-template-columns: 1fr; }
.page-header { flex-direction: column; align-items: stretch; }
.page-header .btn, .page-header .btn-primary { align-self: flex-start; }
.comment-child { margin-left: 10px; }
.modal { padding: 12px; }
.modal-content { padding: 16px; }
.topbar-new { padding: 5px 9px; font-size: 12.5px; }
.expense-row { flex-direction: column; }
.expense-amount { align-self: flex-end; }
.photo-item { width: calc(50% - 6px); }
.settings-chip { flex: 1 1 100%; }
.inline-row { align-items: stretch; }
}