三件事都由用户实测反馈,前两件先复现再动手。
一、日期框:敲 20260606 变成 202606/06(用户要「永远解决」)
- **实测复现**:写了个真实按键工具(CDP Input.dispatchKeyEvent 每字符带 text;
技能里的 `--press` 只给 Enter 带 text,数字敲不进去)。在 /stats 的「起始日期」上敲 20260606:
敲之前 `INPUT|date|`、attached=false;敲完显示 `202606/06`、`el.value` 是**空**(非法)—— 截图为证。
- **根因**:筛选区的起止日期是原生 `<input type="date">`,而挂载代码只写了
`querySelectorAll('input[type=datetime-local]')` ⇒ **type=date 从没人管**。
Chrome 的年段允许超过 4 位数字(年份上限 275760),8 位被切成 年=202606 + 月=06。
原生分段状态读不到、`setSelectionRange` 对 date 类输入直接抛 ⇒ 修不了,只能换控件
(用户也说了「如果当前控件无法实现就换别的」)。
- **做法**:`datetime-input.js` 加「只日期」模式(`SPEC.date`:年/月/日三段,输出 `Y-m-d`),
并提供**统一入口 `attachAll(root)`** 按 `el.type` 分派;`attach` 里先把控件换成 `text`
(引导会写 `2026-06-` 这种半成品,而原生控件对非法值一律置空)。
`datetime-input.js` 改为**无条件引入**(公共页筛选区也要用;flatpickr 仍按需加载 ——
没有它时只挂输入引导、不给日历,省 60 多 KB),挂载在 body 末尾**立刻**跑一次
(别只等 DOMContentLoaded,否则会先露出原生框再变文本框)。三个后台页改用 `attachAll`,
顺带覆盖行程筛选的 `mfrom/mto`(date 类走 `enableTime:false`,实测无时间选择器)。
- **验证**:真实按键敲 20260606 → `value="2026-06-06"`;端到端 `from=2025-08-01&to=2025-08-31`
筛出 **186 次**,与月度图峰值月(186)完全吻合(排除「解析成空导致恒 0」)。
门禁 `TestDateInputsGoThroughAttachAll`:模板里再出现按 type 挑的选择器即红(做过变异验证);
JS 单测补 11 条逐键模拟(45/45)。
二、逐月平均气温图在桌面端很小
- 量出来的:10 个月固定 36px 宽,只占 1042px 里的 **396px**,右边 600 多像素全空。
- 窄屏保持「固定 36px + 横滚」(挤在一起月份标签会糊),宽屏改 `flex: 1 1 36px; max-width: 96px`
并把轨道从 84px 加到 130px、字号提到 `--fs-sm`。
三、类型分布与一天中的分布同行高度不齐
- grid 默认 `align-items: stretch`,把 256px 的「一天中的分布」拉到与 1411px 的「类型分布」一样高,
卡片内部留一大片空白。改成 `.grid { align-items: start }`。
回归:./scripts/verify.sh **PASS=349 FAIL=0 SKIP=0**;移动端 390 / 桌面 1440 溢出与改前一致。
⚠️ 两个验证纪律(都写进 details 三十二):
- 验收跑着的时候别并发跑测试/变异实验(两次踩到,其中一次变异正好被验收抓到,那轮结果作废)。
- **变异验证「内嵌文件」(templates/static)之后必须 `go clean -cache`**:`//go:embed` 的内容
在「改坏→还原」后,构建缓存会继续给出变异期间编译的那份 —— 实测同一个测试
`go test ./internal/web/` 绿、`go test ./...` 红,`go clean -testcache` 不管用。
1293 lines
77 KiB
CSS
1293 lines
77 KiB
CSS
:root {
|
||
--accent: #2f7d6e;
|
||
--accent-soft: #e8f3f0;
|
||
--bg: #f6f7f8;
|
||
--panel: #ffffff;
|
||
--line: #e5e7ea;
|
||
--line-soft: #f0f1f3;
|
||
--text: #1f2328;
|
||
--text-2: #5b6169;
|
||
/* 次要文字。原来是 #8b9199,对白底只有 3.18:1、对浅灰底 2.81:1,
|
||
低于 WCAG AA 的 4.5:1(页面副标题、KPI 标签、筛选计数全用这个色阶)。
|
||
改成 #666d75 之后:白底 5.24 / 浅灰底 4.63 / 绿浅底 4.62,
|
||
既达标又仍比 --text-2(5.53)浅,层次没丢。 */
|
||
--text-3: #666d75;
|
||
/* 会随明暗切换的「面」色,抽出来暗色只需换这一组 */
|
||
--surface: #ffffff; /* 输入框 / 按钮 / 浮层的底 */
|
||
--glass: rgba(255, 255, 255, .92); /* 顶栏毛玻璃 */
|
||
--glass-strong: rgba(255, 255, 255, .97); /* 地图面板 / 图例 */
|
||
--row-hover: #fbfcfc;
|
||
--bg-2: #fafbfb; /* 比卡片略深一层的面(行内编辑表单、空态框) */
|
||
--on-accent: #ffffff; /* 主色底上的文字 */
|
||
|
||
/* ---------- 类型色(活动 / 交通方式)----------
|
||
色名是 rond 里的 Apple 日历色名(与 Go 侧 appleColors 的键一一对应,模板经
|
||
csscolor / csscolorsoft 取用)。地图那份调色板在 rond-map.js 里、**故意独立**:
|
||
地图瓦片在暗色下保持浅色,标记得用浅色底上那套值,不能跟着页面的暗色变量走。
|
||
⚠️ 这套色的**明度差异很大**:在白底上 yellow 1.87、mint 2.12、orange 2.20、green 2.22、
|
||
cyan 2.54、teal 2.57 —— 都远低于图形对象 3:1。所以它们只能当「身份标记」(圆点、柱条),
|
||
**别拿去当文字色或承载唯一信息的图形**,这也正是全站只用小圆点而不是彩色标签的原因。 */
|
||
--cat-red: #ff3b30; --cat-orange: #ff9500; --cat-yellow: #e8b700;
|
||
--cat-green: #34c759; --cat-mint: #00c7be; --cat-teal: #30b0c7;
|
||
--cat-cyan: #32ade6; --cat-blue: #007aff; --cat-indigo: #5856d6;
|
||
--cat-purple: #af52de; --cat-pink: #ff2d55; --cat-brown: #a2845e;
|
||
--cat-gray: #8e8e93;
|
||
/* 色名不认识时的兜底,与 Go 里 cssColor 的 var(--cat-none) 对齐 */
|
||
--cat-none: #5b6169;
|
||
/* 浅底(emoji 方块那种):半透明叠在卡片上,浅色暗色自适应。
|
||
原来是模板里给 hex 拼 "22" 当 alpha——换成 3 位色值或 var() 就散了,这里收口。 */
|
||
--cat-red-soft: rgba(255, 59, 48, .14); --cat-orange-soft: rgba(255, 149, 0, .14);
|
||
--cat-yellow-soft: rgba(232, 183, 0, .14); --cat-green-soft: rgba(52, 199, 89, .14);
|
||
--cat-mint-soft: rgba(0, 199, 190, .14); --cat-teal-soft: rgba(48, 176, 199, .14);
|
||
--cat-cyan-soft: rgba(50, 173, 230, .14); --cat-blue-soft: rgba(0, 122, 255, .14);
|
||
--cat-indigo-soft: rgba(88, 86, 214, .14); --cat-purple-soft: rgba(175, 82, 222, .14);
|
||
--cat-pink-soft: rgba(255, 45, 85, .14); --cat-brown-soft: rgba(162, 132, 94, .16);
|
||
--cat-gray-soft: rgba(142, 142, 147, .16);
|
||
--cat-none-soft: rgba(91, 97, 105, .14);
|
||
|
||
--ok-bg: #eef8f5; --ok-fg: #17604f; --ok-line: #cfe6e0;
|
||
--err-bg: #fdf3f2; --err-fg: #b42318; --err-line: #f3dcda;
|
||
--info-bg: #f2f6fd; --info-fg: #2c5282; --info-line: #dbe5f5;
|
||
/* 圆角 4 档。原来是 7 / 8 / 9 / 10 一堆近似值混着用,肉眼分不出、改一处要改一片。 */
|
||
--r-xs: 3px; /* 小方块(热力格、气温柱顶) */
|
||
--r-sm: 6px; /* 控件(按钮、输入框、导航链接、标签) */
|
||
--r-md: 8px; /* 中号面(缩略图、提示条) */
|
||
--r-lg: 12px; /* 卡片与浮层 */
|
||
/* 阴影 3 档,暗色模式整套覆盖(见文件末尾的 prefers-color-scheme) */
|
||
--sh-1: 0 1px 2px rgba(16, 24, 40, .04), 0 1px 3px rgba(16, 24, 40, .06);
|
||
--sh-2: 0 4px 14px rgba(16, 24, 40, .10), 0 1px 3px rgba(16, 24, 40, .06);
|
||
--sh-3: 0 10px 30px rgba(16, 24, 40, .12), 0 2px 6px rgba(16, 24, 40, .06);
|
||
/* 间距 5 档。本批只用在卡片内边距这类「主节奏」上,其余逐批替换。 */
|
||
--sp-1: 4px; --sp-2: 8px; --sp-3: 12px; --sp-4: 16px; --sp-5: 24px;
|
||
/* 卡片内边距单列一个变量:折叠卡片的标题行要用负 margin 顶到边(见 .filters-card),
|
||
两处必须永远相等,所以绑成同一个值。 */
|
||
--card-pad: 18px;
|
||
|
||
/* ---------- 字阶 ----------
|
||
文本只有 6 档。原来是 10/10.5/11/11.5/12/12.5/12.8/13/13.5/14/14.5/15/17/18/21px
|
||
十几档挤在一起,12~15px 之间就塞了 4 档 —— 层级挤不出来,只能靠加粗硬顶。 */
|
||
--fs-xs: 11px; /* 脚注 / 单位 / 轴标 / 图例 */
|
||
--fs-sm: 13px; /* 正文(默认) */
|
||
--fs-md: 15px; /* 卡片标题 / 强调文本 */
|
||
--fs-lg: 21px; /* 页面标题 */
|
||
--fs-xl: 26px; /* KPI 数值 */
|
||
--fs-2xl: 34px; /* 首屏主数 */
|
||
/* 表单控件字号单独一档:窄屏必须 ≥16px,否则 iOS 一聚焦就把整页放大。
|
||
它不属于文本层级,所以不并进上面的字阶。 */
|
||
--fs-control: 16px;
|
||
/* 字形尺寸(✕ / 箭头 / 空态图标 / 表情):按控件尺寸定,不是文字层级。
|
||
故意不进字阶 —— 把 22px 的 ✕ 塞进 26px 的字阶只会把按钮撑坏。 */
|
||
--glyph-xs: 12px;
|
||
--glyph-sm: 15px;
|
||
--glyph-md: 18px;
|
||
--glyph-lg: 22px;
|
||
--glyph-xl: 30px;
|
||
--glyph-2xl: 40px;
|
||
--mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
|
||
/* 顶栏高度:窄屏更矮。地图页的定位跟它对齐,避免出现几像素的缝。 */
|
||
--nav-h: 54px;
|
||
/* 地图页浮层的层级:Leaflet 自己的 z-index 封顶在 1000(.leaflet-top/.leaflet-bottom,
|
||
见 vendor/leaflet/leaflet.css),而 #map 那一层没有建立层叠上下文 ——
|
||
站点的浮层排不到 1000 之上,就会被左上角的缩放按钮、右下的比例尺压住。
|
||
分两档是为了保住原有次序:加载状态条要压在所有浮层之上。 */
|
||
--z-map-overlay: 1100;
|
||
--z-map-status: 1150;
|
||
/* 顶栏。必须压过地图页的浮层(--z-map-overlay = 1100):窄屏的导航菜单是从顶栏
|
||
展开的,而它属于 .nav 这个层叠上下文、自己抬不动 —— 只有把整条顶栏放到浮层之上,
|
||
展开后才不会被地图的筛选面板盖住(实测过 elementFromPoint)。 */
|
||
--z-nav: 1200;
|
||
/* 刘海屏 / 底部 Home 指示条的安全区。不支持 env() 的浏览器回落到 0。 */
|
||
--safe-t: env(safe-area-inset-top, 0px);
|
||
--safe-r: env(safe-area-inset-right, 0px);
|
||
--safe-b: env(safe-area-inset-bottom, 0px);
|
||
--safe-l: env(safe-area-inset-left, 0px);
|
||
}
|
||
|
||
* { box-sizing: border-box; }
|
||
|
||
html, body {
|
||
margin: 0;
|
||
padding: 0;
|
||
background: var(--bg);
|
||
color: var(--text);
|
||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Noto Sans SC", sans-serif;
|
||
font-size: var(--fs-sm);
|
||
line-height: 1.6;
|
||
-webkit-font-smoothing: antialiased;
|
||
}
|
||
|
||
a { color: var(--accent); text-decoration: none; }
|
||
a:hover { text-decoration: underline; }
|
||
h1, h2, h3, h4 { margin: 0; font-weight: 600; letter-spacing: -.01em; }
|
||
/* 显式给字号。这条规则以前只管字重和 margin,字号靠浏览器默认的 2em / 1.5em / 1.17em
|
||
—— 也就是跟着**根字号**漂:根从 14px 降到 13px 后,没被 `.card > h2` 命中的那些 h2
|
||
从 21px 悄悄变成 19.5px(/stats 的「我的结论」「访客点赞」两块),比同页卡片标题大一圈。 */
|
||
h1 { font-size: var(--fs-lg); }
|
||
h2 { font-size: var(--fs-md); }
|
||
h3, h4 { font-size: var(--fs-sm); }
|
||
/* <b>/<strong> 的浏览器默认是 bold(700),而全站层级只到 600 —— 不钉住的话
|
||
行内加粗会比标题还重。 */
|
||
b, strong { font-weight: 600; }
|
||
|
||
/* ---------- 顶栏 ---------- */
|
||
.nav {
|
||
position: sticky; top: 0; z-index: var(--z-nav);
|
||
background: var(--glass);
|
||
backdrop-filter: saturate(1.6) blur(10px);
|
||
border-bottom: 1px solid var(--line);
|
||
/* 横屏时刘海在左右,竖屏时状态栏在顶部 */
|
||
padding: var(--safe-t) var(--safe-r) 0 var(--safe-l);
|
||
}
|
||
.nav-inner {
|
||
max-width: 1120px; margin: 0 auto; padding: 0 20px;
|
||
/* 菜单是相对 .nav-inner 绝对定位的(要铺满整条),所以这里得有定位上下文 */
|
||
position: relative;
|
||
height: var(--nav-h); display: flex; align-items: center; gap: 22px;
|
||
}
|
||
.brand { display: flex; align-items: center; gap: 8px; font-weight: 600; color: var(--text); font-size: var(--fs-md); }
|
||
.brand:hover { text-decoration: none; }
|
||
.brand .avatar { font-size: var(--glyph-md); }
|
||
/* 链接行自带横向兜底:620px 以下整行会收进 .nav-menu,这条就只为「以后又加了入口、
|
||
宽屏也放不下」兜底 —— 宁可让它能滑,也别把最右边那个字切掉一半。 */
|
||
.nav-links { display: flex; gap: 4px; flex: 1; overflow-x: auto; scrollbar-width: none; }
|
||
.nav-links::-webkit-scrollbar { display: none; }
|
||
/* 还能往右滑时右缘渐隐(由 nav 的脚本按 scrollLeft 加/去这个类)。用 mask 而不是盖一层
|
||
渐变伪元素:伪元素会把链接文字一起遮住,mask 是让内容本身渐隐。 */
|
||
.nav-links.overflow-right {
|
||
-webkit-mask-image: linear-gradient(to right, #000 calc(100% - 28px), transparent);
|
||
mask-image: linear-gradient(to right, #000 calc(100% - 28px), transparent);
|
||
}
|
||
.nav-links a {
|
||
padding: 5px 11px; border-radius: var(--r-sm); color: var(--text-2); font-size: var(--fs-sm); white-space: nowrap;
|
||
}
|
||
.nav-links a:hover { background: var(--line-soft); color: var(--text); text-decoration: none; }
|
||
.nav-links a.on { background: var(--accent-soft); color: var(--accent); font-weight: 500; }
|
||
.nav-right { display: flex; align-items: center; gap: 10px; font-size: var(--fs-sm); color: var(--text-3); }
|
||
|
||
/* 窄屏导航菜单。620px 以下链接行一定放不下(390 溢 63px、320 溢 133px),整行收进这里。
|
||
用原生 <details>,不靠 JS 也能开合;JS 只补三件原生不给的事:点外面 / Esc / 点链接后收起。 */
|
||
.nav-menu { display: none; }
|
||
.nav-menu > summary {
|
||
list-style: none; cursor: pointer; user-select: none; white-space: nowrap;
|
||
display: inline-flex; align-items: center; gap: 4px;
|
||
min-height: 44px; padding: 0 10px; border-radius: var(--r-sm);
|
||
color: var(--text-2); font-size: var(--fs-sm);
|
||
}
|
||
.nav-menu > summary::-webkit-details-marker { display: none; }
|
||
.nav-menu > summary::after { content: "▾"; font-size: var(--glyph-xs); color: var(--text-3); }
|
||
.nav-menu[open] > summary { background: var(--accent-soft); color: var(--accent); }
|
||
.nav-menu[open] > summary::after { content: "▴"; }
|
||
.nav-menu-body {
|
||
position: absolute; left: 0; right: 0; top: 100%; z-index: 1;
|
||
display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 2px; padding: 6px;
|
||
background: var(--panel); border-bottom: 1px solid var(--line); box-shadow: var(--sh-2);
|
||
}
|
||
.nav-menu-body a {
|
||
display: flex; align-items: center; min-height: 44px; padding: 0 12px;
|
||
border-radius: var(--r-sm); color: var(--text-2); font-size: var(--fs-sm);
|
||
}
|
||
.nav-menu-body a:hover { background: var(--line-soft); color: var(--text); text-decoration: none; }
|
||
.nav-menu-body a.on { background: var(--accent-soft); color: var(--accent); font-weight: 500; }
|
||
|
||
/* ---------- 布局 ---------- */
|
||
.wrap { max-width: 1120px; margin: 0 auto;
|
||
padding: 22px calc(20px + var(--safe-r)) calc(60px + var(--safe-b)) calc(20px + var(--safe-l)); }
|
||
.page-head { margin-bottom: 18px; display: flex; align-items: flex-end; justify-content: space-between; gap: 16px; flex-wrap: wrap; }
|
||
.page-head h1 { font-size: var(--fs-lg); }
|
||
.page-head .sub { color: var(--text-3); font-size: var(--fs-sm); margin-top: 3px; }
|
||
|
||
.card {
|
||
background: var(--panel); border: 1px solid var(--line); border-radius: var(--r-lg);
|
||
box-shadow: var(--sh-1); padding: var(--card-pad); margin-bottom: var(--sp-4);
|
||
}
|
||
.card > h2 { font-size: var(--fs-md); margin-bottom: 2px; display: flex; align-items: center; gap: var(--sp-2); }
|
||
/* .card-body 是数据编辑页折叠用的容器,卡片内容被它包住后不再是 .card 的直接子元素,
|
||
所以下面两条用「后代」而不是「子」选择器——否则折叠脚本一加上,间距就全没了。 */
|
||
.card .card-sub { color: var(--text-3); font-size: var(--fs-sm); margin-bottom: var(--sp-3); }
|
||
.card.collapsed .card-body { display: none; }
|
||
|
||
/* 卡片三档。原来全站只有一种白盒(量下来 17 个盒子只有 2 种样式),KPI / 地图 / 图表 /
|
||
表格 / 筛选器全等重,扫视时没有落点。分档不是装饰,是为了「一眼知道先看哪」:
|
||
hero 只给首屏主数区与地图,quiet 只给纯说明性的内容。 */
|
||
.card--hero { border-color: transparent; box-shadow: var(--sh-3); padding: calc(var(--card-pad) + var(--sp-2)); }
|
||
/* quiet 用页面底色 + 细边 + 无阴影 = 贴在页面上的扁平面板。
|
||
注意别拿 --bg-2 (#fafbfb) 当底色:它和页面底 #f6f7f8 只差 4/255,
|
||
实测根本看不出边界,卡片会像「破了个洞」而不是「轻了一档」。 */
|
||
.card--quiet { background: var(--bg); border-color: var(--line); box-shadow: none; }
|
||
.card--quiet > h2 { font-size: var(--fs-sm); color: var(--text-2); }
|
||
|
||
/* 首屏主数:把最重要的那个数从 KPI 格里拎出来单独一行。
|
||
原来 8 个数字一样大一样重,扫过去不知道看哪个 —— 层级靠「只让一个特别大」,
|
||
而不是把所有的都调大。 */
|
||
.kpi-lead {
|
||
position: relative; overflow: hidden;
|
||
background: var(--panel); border: 1px solid var(--line); border-radius: var(--r-lg);
|
||
box-shadow: var(--sh-3);
|
||
padding: calc(var(--card-pad) + var(--sp-1)) var(--card-pad) var(--card-pad);
|
||
margin-bottom: var(--sp-3);
|
||
}
|
||
.kpi-lead::before {
|
||
content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 3px; background: var(--accent);
|
||
}
|
||
.kpi-lead .kl-v {
|
||
font-size: clamp(var(--fs-xl), 7.2vw, var(--fs-2xl)); font-weight: 600;
|
||
letter-spacing: -.02em; line-height: 1.1; font-variant-numeric: tabular-nums;
|
||
}
|
||
.kpi-lead .kl-v small { font-size: var(--fs-md); font-weight: 500; color: var(--text-3); margin-left: var(--sp-1); }
|
||
.kpi-lead .kl-k { color: var(--text-2); font-size: var(--fs-sm); margin-top: var(--sp-1); }
|
||
.kpi-lead .kl-x { color: var(--text-3); font-size: var(--fs-sm); margin-top: var(--sp-2); }
|
||
/* 只有带 data-card 的卡片(数据编辑页)能折叠:标题整行可点,右侧一个箭头 */
|
||
.card[data-card] > h2 { cursor: pointer; }
|
||
.card[data-card] > h2 .card-toggle {
|
||
margin-left: auto; flex: 0 0 auto; cursor: pointer;
|
||
background: none; border: 0; padding: 0 2px; color: var(--text-3);
|
||
font: inherit; font-size: var(--glyph-xs); line-height: 1;
|
||
}
|
||
.card[data-card] > h2 .card-toggle:hover { color: var(--text); }
|
||
/* align-items: start 是必须的:grid 默认 stretch,同一行里内容少的那张卡会被拉到与
|
||
最高的一样高,卡片内部留一大片空白(/stats 的「类型分布」60 多行 vs「一天中的分布」
|
||
24 根柱就是这么来的)。改成各按自然高度,短的那张下面露的是页面底色,而不是空白卡面。 */
|
||
.grid { display: grid; gap: 16px; align-items: start; }
|
||
/* grid 子项默认 min-width:auto(= 不小于自身 min-content),而卡片里常常放着
|
||
带 min-width 的表格——不加这条,卡片会拒绝收缩、把**整页**顶宽,出现横向滚动条
|
||
(/stats、/report 在手机上整页 692px 就是这么来的)。压到 0 之后由卡片内部的
|
||
.tw 自己横向滚动。 */
|
||
.grid > * { min-width: 0; }
|
||
.g2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
||
.g3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
|
||
.g4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
|
||
@media (max-width: 900px) { .g3, .g4 { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
|
||
@media (max-width: 620px) {
|
||
:root { --nav-h: 50px; }
|
||
.g2, .g3, .g4 { grid-template-columns: 1fr; }
|
||
.wrap { padding: 16px calc(14px + var(--safe-r)) calc(48px + var(--safe-b)) calc(14px + var(--safe-l)); }
|
||
.nav-inner { padding: 0 10px; gap: 6px; }
|
||
/* 窄屏只留头像 */
|
||
.brand .t { display: none; }
|
||
/* 链接行整行收进 .nav-menu(样式见上面那段),这里不再横向滚动 */
|
||
.nav-links { display: none; }
|
||
.nav-menu { display: block; flex: 1; }
|
||
.nav-right .who { display: none; }
|
||
.nav-right .btn.sm { padding: 4px 9px; font-size: var(--fs-sm); }
|
||
}
|
||
|
||
/* ---------- 窄屏触控与表单(移动端适配) ---------- */
|
||
/* 手指的落点比鼠标粗得多:导航链接原本只有 40×31、筛选标签 59×28、小按钮 45×30,
|
||
都低于 44×44 的通行下限,最容易点的就是它们。这里只放宽尺寸与内边距,不动结构。 */
|
||
@media (max-width: 620px) {
|
||
.nav-links a, .btn, .btn.sm, .chip { min-height: 44px; }
|
||
.nav-links a { display: inline-flex; align-items: center; padding: 8px 9px; }
|
||
/* 内边距与控件尺寸的窄屏覆盖在文件末尾(那几组选择器的基础规则在下面,
|
||
写在这里会被它们盖掉);这里只放基础规则里没有的属性。 */
|
||
/* iOS 聚焦字号小于 16px 的控件时会自动放大整页——用户点完输入框页面突然变大,
|
||
还要手动缩回去。窄屏统一提到 16px 规避(桌面端不受影响,保持紧凑)。 */
|
||
:root { --field-fs: 16px; }
|
||
/* 表格里的控件仍然紧凑:后台那张表列很多,16px 会把列撑得很难用。
|
||
表格里横向可滚,iOS 缩放的影响也小。 */
|
||
.photo-table, table.tb { --field-fs: 14px; }
|
||
}
|
||
|
||
.only-sm { display: none; }
|
||
@media (max-width: 620px) {
|
||
.hide-sm { display: none; }
|
||
.only-sm { display: inline; }
|
||
/* 窄屏用地势与手势缩放,把控件让给底部 */
|
||
.map-panel { top: 8px; }
|
||
}
|
||
|
||
/* ---------- 统计数字 ---------- */
|
||
/* grid-auto-rows: 1fr 让所有格子等高:否则某一格的值换行时,那一整行都比别行高 */
|
||
/* 用 flex + grow 而不是固定列数的网格:KPI 个数常常是 6、7 这种除不尽列数的,
|
||
网格会在最后一行留下空格子——而间隙是用底色画的,空格子就露成灰块,很难看。
|
||
flex 会让最后一行自己撑满,任何个数都不会有空格。 */
|
||
.kpis { display: flex; flex-wrap: wrap; gap: 1px; background: var(--line); border-radius: var(--r-lg); overflow: hidden; border: 1px solid var(--line); }
|
||
.kpi { flex: 1 1 158px; min-width: 0; background: var(--panel); padding: var(--sp-4); }
|
||
/* 字号跟着视口收:窄屏上「112天19小时」这种值放不下就会折成两行,把整行撑高 */
|
||
.kpi .v { font-size: clamp(var(--fs-md), 5.6vw, var(--fs-xl)); font-weight: 600; letter-spacing: -.02em; font-variant-numeric: tabular-nums; }
|
||
.kpi .v small { font-size: var(--fs-sm); font-weight: 500; color: var(--text-3); margin-left: 3px; }
|
||
.kpi .k { color: var(--text-3); font-size: var(--fs-sm); margin-top: 1px; }
|
||
|
||
/* ---------- 图表(纯 CSS/SVG) ---------- */
|
||
.bars { display: flex; flex-direction: column; gap: 9px; }
|
||
.bar-row { display: grid; grid-template-columns: 128px 1fr 104px; align-items: center; gap: 10px; font-size: var(--fs-sm); }
|
||
/* 整行可点(下钻为筛选):给一点悬停反馈,并让点击区域覆盖整行;
|
||
柱条本身只有 9px 高,靠「行」来接收点击才够手指点 */
|
||
a.bar-row { margin: 0 -6px; padding: 4px 6px; border-radius: var(--r-sm); color: inherit; text-decoration: none; }
|
||
a.bar-row:hover { background: var(--line-soft); text-decoration: none; }
|
||
a.col { display: block; text-decoration: none; }
|
||
a.col:hover { opacity: 1; filter: brightness(1.12); }
|
||
.bar-row .nm { color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||
.bar-track { background: var(--line-soft); border-radius: var(--r-sm); height: 9px; overflow: hidden; }
|
||
.bar-fill { height: 100%; border-radius: var(--r-sm); background: var(--accent); min-width: 2px; }
|
||
.bar-row .vv { text-align: right; color: var(--text-2); font-variant-numeric: tabular-nums; font-size: var(--fs-sm); white-space: nowrap; }
|
||
@media (max-width: 620px) {
|
||
.bar-row { grid-template-columns: 84px 1fr 92px; }
|
||
/* 图表行在手机上要能点中:整行 ~37px。再往上加(44px)会把「类型分布」这种
|
||
60 多行的图撑到两倍高,翻起来更难受,这里取个折中。 */
|
||
a.bar-row { padding: 8px 6px; }
|
||
}
|
||
|
||
/* 基线:柱子底下压一道浅线,让「高度从哪儿量起」有个参照(原来柱子直接悬在空白里,
|
||
高低只能靠互相比)。
|
||
padding-top 是给峰值柱顶上那个数字留的位置:height 走 border-box,内容区仍是 120px。 */
|
||
.cols { position: relative; display: flex; align-items: flex-end; gap: 3px; height: 136px; padding-top: 16px; border-bottom: 1px solid var(--line); }
|
||
.cols .col { position: relative; flex: 1; background: var(--accent); border-radius: var(--r-xs) var(--r-xs) 0 0; min-height: 2px; opacity: .8; }
|
||
.cols .col:hover { opacity: 1; }
|
||
/* 峰值柱:不透明拉满,并在顶上标出次数。数值本来写在副标题里,但它不告诉你「哪一根最高」,
|
||
标在柱子上才是能一眼找到的锚点。 */
|
||
.cols .col.peak { opacity: 1; }
|
||
.cols .col .pv {
|
||
position: absolute; left: 50%; bottom: calc(100% + 2px); transform: translateX(-50%);
|
||
font-size: var(--fs-xs); line-height: 1.2; color: var(--text-2);
|
||
font-variant-numeric: tabular-nums; white-space: nowrap;
|
||
}
|
||
.cols-x { display: flex; gap: 3px; font-size: var(--fs-xs); color: var(--text-3); margin-top: 5px; }
|
||
.cols-x span { flex: 1; text-align: center; overflow: hidden; white-space: nowrap; }
|
||
/* 一天 24 根柱,光看柱子分不出「上午 / 下午」。这条标尺用同一套 24 列网格(不是 flex:6),
|
||
所以每段边界和柱子严格对齐;每段直接写上时段和小时区间,就不用再单独留一行坐标轴。 */
|
||
.cols-band { display: grid; grid-template-columns: repeat(24, minmax(0, 1fr)); gap: 3px; margin-top: var(--sp-2); }
|
||
.cols-band span {
|
||
grid-column: span 6; text-align: center; white-space: nowrap;
|
||
font-size: var(--fs-xs); color: var(--text-3);
|
||
background: var(--line-soft); border-radius: var(--r-xs); padding: 2px 0;
|
||
}
|
||
|
||
/* ---------- 逐月气温 ---------- */
|
||
.temp-chart { margin-top: 16px; }
|
||
.temp-head { font-size: var(--fs-sm); color: var(--text-3); margin-bottom: 8px; }
|
||
/* 横向可滚动:柱子固定宽,十几二十个月挤在窄屏上也不会把标签压成一团 */
|
||
.temp-strip { display: flex; gap: 4px; overflow-x: auto; padding-bottom: 4px; }
|
||
.temp-col { flex: 0 0 36px; display: flex; flex-direction: column; align-items: center; gap: 3px; cursor: default; }
|
||
/* 宽屏反过来:柱子要跟着卡片长、轨道也要加高。原来固定 36×84,10 个月只占 1042px 里的
|
||
396px —— 右边 600 多像素全空着,整张图看着就「很小」。上限 96px 是防止只有两三个月时
|
||
柱子宽得不像柱状图。 */
|
||
@media (min-width: 720px) {
|
||
.temp-col { flex: 1 1 36px; max-width: 96px; }
|
||
.temp-col .tc-track { height: 130px; }
|
||
.temp-col .tc-v, .temp-col .tc-m { font-size: var(--fs-sm); }
|
||
}
|
||
.temp-col .tc-v { font-size: var(--fs-xs); color: var(--text-2); font-variant-numeric: tabular-nums; }
|
||
.temp-col .tc-track { width: 100%; height: 84px; display: flex; align-items: flex-end; }
|
||
.temp-col .tc-bar { width: 100%; border-radius: var(--r-xs) var(--r-xs) 0 0; min-height: 3px; }
|
||
.temp-col .tc-m { font-size: var(--fs-xs); color: var(--text-3); white-space: nowrap; }
|
||
.temp-col:hover .tc-bar { filter: brightness(1.08); }
|
||
.temp-col:hover .tc-v { color: var(--text); }
|
||
|
||
/* ---------- 日历热力 ---------- */
|
||
.heat { display: flex; gap: 3px; overflow-x: auto; padding-bottom: 4px; }
|
||
.heat .wk { display: flex; flex-direction: column; gap: 3px; }
|
||
.heat .d { width: 11px; height: 11px; border-radius: var(--r-xs); background: var(--line-soft); }
|
||
.heat .d.l1 { background: #cfe6e0; }
|
||
.heat .d.l2 { background: #9dcfc3; }
|
||
.heat .d.l3 { background: #5fae9c; }
|
||
.heat .d.l4 { background: var(--accent); }
|
||
.heat-legend { display: flex; align-items: center; gap: 5px; font-size: var(--fs-xs); color: var(--text-3); margin-top: 8px; }
|
||
.heat-legend .d { width: 11px; height: 11px; border-radius: var(--r-xs); }
|
||
|
||
/* ---------- 列表 ---------- */
|
||
.list { display: flex; flex-direction: column; }
|
||
.item {
|
||
display: grid; grid-template-columns: 40px 1fr auto; gap: 12px; align-items: center;
|
||
padding: 11px 2px; border-bottom: 1px solid var(--line-soft);
|
||
}
|
||
.item:last-child { border-bottom: 0; }
|
||
.item .ic {
|
||
width: 34px; height: 34px; border-radius: var(--r-md); display: flex; align-items: center; justify-content: center;
|
||
background: var(--line-soft); font-size: var(--glyph-md);
|
||
}
|
||
.item .t { font-weight: 500; }
|
||
.item .t a { color: var(--text); }
|
||
.item .m { color: var(--text-3); font-size: var(--fs-sm); margin-top: 1px; }
|
||
.item .r { text-align: right; color: var(--text-2); font-size: var(--fs-sm); white-space: nowrap; }
|
||
|
||
.chips { display: flex; flex-wrap: wrap; gap: 6px; }
|
||
.chip {
|
||
display: inline-flex; align-items: center; gap: 5px; padding: 3px 9px; border-radius: 999px;
|
||
background: var(--line-soft); color: var(--text-2); font-size: var(--fs-sm); border: 1px solid transparent;
|
||
}
|
||
.chip.on { background: var(--accent-soft); color: var(--accent); border-color: #cfe6e0; font-weight: 500; }
|
||
.chip .dot { width: 7px; height: 7px; border-radius: 50%; background: currentColor; }
|
||
.tag { display: inline-block; padding: 1px 7px; border-radius: var(--r-sm); background: var(--accent-soft); color: var(--accent); font-size: var(--fs-xs); }
|
||
|
||
/* ---------- 表单 ---------- */
|
||
label.f { display: block; margin-bottom: 13px; }
|
||
label.f > span { display: block; font-size: var(--fs-sm); color: var(--text-2); margin-bottom: 5px; }
|
||
/* 必填标记:字段名后的红色星号,以及表单顶部的统一说明 */
|
||
.req { color: #b42318; font-weight: 600; margin-left: 2px; }
|
||
.req-tip { font-size: var(--fs-sm); color: var(--text-2); margin: 0 0 10px; }
|
||
.req-tip .req { margin-right: 3px; }
|
||
|
||
/* flatpickr 日期时间选择器:贴合站点风格 */
|
||
.flatpickr-calendar { border-radius: var(--r-lg); box-shadow: var(--sh-2); font-family: inherit; }
|
||
.flatpickr-day.selected, .flatpickr-day.selected:hover { background: var(--accent); border-color: var(--accent); }
|
||
.flatpickr-day.today { border-color: var(--accent); }
|
||
.flatpickr-day.today:hover { background: var(--accent-soft); color: var(--text); }
|
||
.flatpickr-day:hover { background: var(--accent-soft); }
|
||
/* 控件字号走变量:窄屏要整体提到 16px(iOS 聚焦小于 16px 会自动放大整页),
|
||
而基础选择器 input[type=…], select, textarea 的特异性高于媒体查询里的单选择器,
|
||
直接在里面写 font-size 是压不过的——用可继承的自定义属性就没有这个问题。 */
|
||
/* 按「排除掉不该套文本框样式的类型」来写,而不是逐个枚举 text/number/date——
|
||
枚举漏了 datetime-local(后台编辑页有 160 多个),那些框就一直是浏览器默认外观:
|
||
没有圆角、没有内边距、13.33px 的字,跟旁边的输入框明显不是一套。 */
|
||
input:not([type=checkbox]):not([type=radio]):not([type=color]):not([type=range]):not([type=file]):not([type=submit]):not([type=button]):not([type=reset]):not([type=image]):not([type=hidden]),
|
||
select, textarea {
|
||
width: 100%; padding: 7px 10px; border: 1px solid var(--line); border-radius: var(--r-sm); font: inherit;
|
||
font-size: var(--field-fs, inherit);
|
||
background: var(--surface); color: var(--text); outline: none;
|
||
}
|
||
input:focus, select:focus, textarea:focus { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
|
||
.check { display: flex; align-items: center; gap: 7px; font-size: var(--fs-sm); color: var(--text-2); margin-bottom: 8px; word-break: keep-all; overflow-wrap: break-word; }
|
||
.check input { width: 15px; height: 15px; accent-color: var(--accent); }
|
||
.nobr { white-space: nowrap; }
|
||
.btn {
|
||
display: inline-flex; align-items: center; gap: 6px; padding: 7px 14px; border-radius: var(--r-sm);
|
||
border: 1px solid var(--line); background: var(--surface); color: var(--text); font: inherit; font-size: var(--fs-sm); cursor: pointer;
|
||
}
|
||
.btn:hover { background: var(--line-soft); text-decoration: none; }
|
||
.btn.primary { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
|
||
.btn.primary:hover { filter: brightness(1.06); }
|
||
.btn.sm { padding: 4px 10px; font-size: var(--fs-sm); }
|
||
.btn.danger { color: #b42318; border-color: #f0cecb; }
|
||
.btn.danger:hover { background: var(--err-bg); }
|
||
.row { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
|
||
|
||
/* ---------- 表格 ---------- */
|
||
table.tb { width: 100%; border-collapse: collapse; font-size: var(--fs-sm); }
|
||
table.tb th {
|
||
text-align: left; font-weight: 500; color: var(--text-3); font-size: var(--fs-sm);
|
||
padding: 7px 10px; border-bottom: 1px solid var(--line); white-space: nowrap;
|
||
}
|
||
table.tb td { padding: 9px 10px; border-bottom: 1px solid var(--line-soft); }
|
||
/* 操作列(桌面要横着一排按钮,不折行)。原来是写在模板上的 inline style,而 white-space 会
|
||
继承进行内编辑表单,堆叠态下「已选:…重选」那一行就顶破了卡片——inline 又压不过后面的覆盖,
|
||
所以挪成类,放不下宽表时在文件末尾统一放开。 */
|
||
table.tb td.ops { white-space: nowrap; }
|
||
table.tb tr:hover td { background: var(--row-hover); }
|
||
table.tb td.num, table.tb th.num { text-align: right; font-variant-numeric: tabular-nums; }
|
||
.tw { overflow-x: auto; }
|
||
/* 表格要给个内容下限:table 是 width:100%,而 CJK 允许逐字断行,没有下限时窄屏会把
|
||
每列压到一两个字宽(「一般公共场所」会排成 6 行、一行一个字)。有了下限,放不下就
|
||
交给上面的 .tw 横向滚动,列宽不再被榨干。 */
|
||
.tw > table.tb { min-width: 640px; }
|
||
/* 地点库这张有 8 列(多一列「图片」),640px 下「河池市」这种 3 字城市会被挤成两行
|
||
(实测 640 给到 54px、700 给到 59px,正好回到一行)。只给这张加下限,别的表不动 ——
|
||
列少的表跟着抬到 700 会平白多出一截横向滚动。 */
|
||
.tw > table.tb.places-table { min-width: 700px; }
|
||
/* 从地点详情页跳进来定位到的那一行(?open=):淡色底,长列表里一眼能认出目标。
|
||
用底色而不是动画——动画会被 prefers-reduced-motion 跳过,那时就完全看不出来了。 */
|
||
table.tb tr.located td, table.tb tr.located:hover td { background: var(--accent-soft); }
|
||
|
||
/* ---------- 地图 ---------- */
|
||
#map { height: 100%; width: 100%; background: #eef1f2; }
|
||
.map-page {
|
||
position: fixed;
|
||
inset: calc(var(--nav-h) + var(--safe-t)) var(--safe-r) var(--safe-b) var(--safe-l);
|
||
}
|
||
.map-panel {
|
||
position: absolute; top: 12px; left: 12px; z-index: var(--z-map-overlay); width: 286px; max-height: calc(100% - 24px);
|
||
overflow-y: auto; background: var(--glass-strong); backdrop-filter: blur(8px);
|
||
border: 1px solid var(--line); border-radius: var(--r-lg); box-shadow: var(--sh-2);
|
||
}
|
||
/* 内边距挂在表单上,不挂面板:窄屏的折叠条与操作行要 sticky 到面板的**内边缘**,
|
||
面板自己带 padding 的话它们只能粘在内容区边界上,边上会留一条缝让滚动内容穿过去
|
||
(实测:折叠条粘在内容区顶、上方 13px 仍能看到内容滚过)。 */
|
||
#filter-form { padding: 13px; }
|
||
/* 时间范围预设行:原来靠行内 style 加的下边距,挪到类上,好让窄屏媒体查询能一起改。 */
|
||
.map-panel .presets { margin-bottom: 10px; }
|
||
/* 「含家/工作」单独一行(不属于时间段预设)。窄屏预设行是单行横滑的,
|
||
它留在里面会被滑到屏幕外;单独一行上下各留 10px,读起来也像另一个开关。 */
|
||
.map-panel .hw-row { margin-bottom: 10px; }
|
||
.map-panel h3 { font-size: var(--fs-sm); margin-bottom: 9px; color: var(--text-2); }
|
||
/* 筛选面板的折叠开关:桌面上面板只占 286px、不碍事,按钮直接不显示,只在窄屏出现 */
|
||
.panel-fold { display: none; }
|
||
/* 筛选面板里可折叠的两块(地点类型 / 更多筛选):这两块在窄屏各占两百多像素,
|
||
是面板高度的大头。默认值在 map.html 的脚本里按视口定(桌面展开、窄屏收起);
|
||
收起时标题里显示已选/已生效的那几项,否则收起后就不知道当前选了什么。 */
|
||
.type-fold > summary, .adv-fold > summary {
|
||
list-style: none; display: flex; align-items: center; gap: 6px;
|
||
font-size: var(--fs-sm); color: var(--text-2); cursor: pointer; padding: 2px 0 8px;
|
||
}
|
||
.type-fold > summary::-webkit-details-marker, .adv-fold > summary::-webkit-details-marker { display: none; }
|
||
.type-fold > summary:hover, .adv-fold > summary:hover { color: var(--accent); }
|
||
.type-fold > summary .tf-sel, .adv-fold > summary .tf-sel {
|
||
flex: 0 1 auto; min-width: 0; color: var(--text-3);
|
||
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
||
}
|
||
.type-fold > summary .tf-arrow, .adv-fold > summary .tf-arrow { flex: 0 0 auto; margin-left: auto; font-size: var(--fs-xs); color: var(--text-3); }
|
||
.type-fold[open] > summary .tf-arrow, .adv-fold[open] > summary .tf-arrow { transform: rotate(90deg); }
|
||
/* 展开后标签行/控件自己就说明了当前值,标题里那份就不必再占一行 */
|
||
.type-fold[open] > summary .tf-sel, .adv-fold[open] > summary .tf-sel { display: none; }
|
||
/* 「更多筛选」里的 h3 是二级标题,别跟折叠标题撞成一样大 */
|
||
.adv-fold > h3 { font-size: var(--fs-sm); color: var(--text-3); margin-bottom: 8px; }
|
||
.map-legend {
|
||
/* bottom 让开右下角那行底图版权(约 15px 高),否则两者会叠在一起 */
|
||
position: absolute; bottom: 26px; right: 12px; z-index: var(--z-map-overlay); background: var(--glass-strong);
|
||
border: 1px solid var(--line); border-radius: var(--r-md); padding: 8px 11px; font-size: var(--fs-sm);
|
||
max-height: 40%; overflow-y: auto; box-shadow: var(--sh-2);
|
||
}
|
||
.map-legend .lg { display: flex; align-items: center; gap: 6px; margin-top: 4px; color: var(--text-2); }
|
||
.map-legend .lg i { width: 9px; height: 9px; border-radius: 50%; }
|
||
/* 迷雾浓度的样式从内联挪出来:窄屏要把它收成一个可展开的小标签——
|
||
不然「图层 / 迷雾浓度 / 缩放」三个控件竖着堆在右下角,占掉近 180px。 */
|
||
.fog-opacity { background: var(--surface); padding: 6px 8px; line-height: 1.2; }
|
||
.fog-opacity .fog-label {
|
||
display: block; text-align: left; border: 0; background: none; padding: 0;
|
||
font: inherit; font-size: var(--fs-xs); color: var(--text-2); cursor: default;
|
||
}
|
||
.fog-opacity .fog-slider { display: block; width: 96px; margin: 4px 0 0; accent-color: var(--accent); }
|
||
@media (max-width: 620px) {
|
||
.fog-opacity .fog-label { cursor: pointer; font-size: var(--fs-sm); }
|
||
.fog-opacity .fog-slider { display: none; }
|
||
.fog-opacity.open .fog-slider { display: block; }
|
||
}
|
||
|
||
/* 长列表划到底后的「回到顶部」。地图页不滚动,所以它不会出现。 */
|
||
.to-top {
|
||
position: fixed; right: calc(16px + var(--safe-r)); bottom: calc(20px + var(--safe-b)); z-index: var(--z-map-status);
|
||
width: 44px; height: 44px; border-radius: 50%; border: 1px solid var(--line);
|
||
background: var(--panel); color: var(--text-2); font-size: var(--glyph-lg); line-height: 1;
|
||
cursor: pointer; box-shadow: var(--sh-2);
|
||
}
|
||
.to-top[hidden] { display: none; }
|
||
.to-top:hover { color: var(--text); border-color: var(--text-3); }
|
||
|
||
/* 时间轴回放时新出现的点:冒出来 + 淡入(配合 JS 里的逐个延时,像"扫过来")。
|
||
点在 Leaflet 里是 overlay pane 里的 <path>,用 transform-box: fill-box 让缩放以自身
|
||
中心为原点,才不会跑到左上角去。 */
|
||
@media (prefers-reduced-motion: no-preference) {
|
||
@keyframes pt-in {
|
||
from { opacity: .1; transform: scale(.2); }
|
||
55% { opacity: 1; }
|
||
to { opacity: .9; transform: scale(1); }
|
||
}
|
||
.leaflet-overlay-pane path.pt-new {
|
||
animation: pt-in .55s cubic-bezier(.22, .9, .28, 1) both;
|
||
transform-box: fill-box;
|
||
transform-origin: center;
|
||
}
|
||
/* 聚合圆圈是 DOM 元素、而且 Leaflet 用 transform 给它定位,
|
||
所以只能动透明度 —— 再加 scale 会把它从原位挪走。 */
|
||
@keyframes pt-in-dom {
|
||
from { opacity: .1; }
|
||
60% { opacity: 1; }
|
||
to { opacity: .92; }
|
||
}
|
||
.leaflet-marker-pane .marker-cluster.pt-new {
|
||
animation: pt-in-dom .5s ease-out both;
|
||
}
|
||
/* 数字变了轻弹一下:弹内层 div(只放数字),外层带 Leaflet 的定位 transform 不能动 */
|
||
@keyframes pt-pulse {
|
||
0% { transform: scale(1); }
|
||
45% { transform: scale(1.3); }
|
||
100% { transform: scale(1); }
|
||
}
|
||
.leaflet-marker-pane .marker-cluster > div.pt-pulse {
|
||
animation: pt-pulse .32s ease-out;
|
||
}
|
||
}
|
||
|
||
/* 时间轴回放:地图底部中间的一条。窄屏上抬高一档,给右下角的底图版权让位。
|
||
和左侧的日期区间不是一回事——区间是「只看这段时间」,这里是「走到这一刻为止」。 */
|
||
.timeline-bar {
|
||
position: absolute; left: 50%; transform: translateX(-50%);
|
||
bottom: 12px; z-index: var(--z-map-overlay);
|
||
display: flex; align-items: center; gap: 10px;
|
||
max-width: min(560px, calc(100% - 24px));
|
||
padding: 7px 12px; border-radius: var(--r-lg);
|
||
background: var(--glass-strong); border: 1px solid var(--line); box-shadow: var(--sh-2);
|
||
backdrop-filter: blur(8px);
|
||
}
|
||
.timeline-bar[hidden] { display: none; }
|
||
.timeline-bar input[type=range] { flex: 1 1 auto; min-width: 90px; accent-color: var(--accent); margin: 0; }
|
||
.timeline-bar .tl-label { flex: 0 0 auto; font-size: var(--fs-sm); color: var(--text-3); white-space: nowrap; font-variant-numeric: tabular-nums; }
|
||
.timeline-bar button {
|
||
flex: 0 0 auto; border: 1px solid var(--line); background: var(--surface); color: var(--text);
|
||
border-radius: var(--r-sm); cursor: pointer; font: inherit; font-size: var(--fs-sm); line-height: 1;
|
||
}
|
||
.timeline-bar .tl-play { width: 30px; height: 30px; font-size: var(--fs-sm); padding: 0; }
|
||
.timeline-bar .tl-reset { padding: 7px 9px; }
|
||
.timeline-bar button:hover { background: var(--line-soft); }
|
||
@media (max-width: 620px) {
|
||
.timeline-bar { bottom: 26px; gap: 8px; padding: 6px 9px; }
|
||
.timeline-bar .tl-label { font-size: var(--fs-xs); }
|
||
}
|
||
|
||
/* 地图上的状态条(加载失败 + 重试)。放底部中间:左上角是筛选面板、右下是图例与版权。 */
|
||
.map-status {
|
||
position: absolute; left: 50%; transform: translateX(-50%);
|
||
bottom: 62px;
|
||
z-index: 600; display: flex; align-items: center; gap: 10px;
|
||
max-width: calc(100% - 24px); background: rgba(30,40,50,.92); color: #fff;
|
||
border-radius: var(--r-md); padding: 8px 12px; font-size: var(--fs-sm); box-shadow: var(--sh-2);
|
||
}
|
||
.map-status[hidden] { display: none; }
|
||
.map-status .btn { border-color: rgba(255,255,255,.35); background: rgba(255,255,255,.12); color: #fff; flex: 0 0 auto; }
|
||
.map-status .btn:hover { background: rgba(255,255,255,.22); }
|
||
@media (max-width: 620px) {
|
||
/* 上限 50%(原 44%):折叠条本身占 44px,不加这 6% 的话展开后能看到的筛选内容
|
||
反而比改动前更少。50% 刚好把那一条的高度补回来。 */
|
||
.map-panel { left: 8px; right: 8px; width: auto; max-height: 50%; top: 8px; }
|
||
.map-legend { display: none; }
|
||
/* 窄屏上面板铺满整行、最多能吃掉 50% 的屏幕,所以默认收起成一条:
|
||
只剩「筛选 + 数量 + 箭头」约 52px 高,想改条件再点开。
|
||
「筛选」两字与箭头之间的数量在展开时也保留——表单里那行重复的计数已去掉,
|
||
两块状态共用这一个数字,展开/收起都能看到当前筛出多少个点。
|
||
展开时它 sticky 在面板顶部:表单比可视窗高,滚到下面之后还得能点回来收起。
|
||
不透明底是因为滚动内容会从它下面穿过(面板本身已无内边距,它天然铺满整行)。 */
|
||
.panel-fold {
|
||
/* width:100% 不能省:这是 <button>,Chrome 下 display:flex + width:auto 仍是「收缩到内容宽」,
|
||
于是它只盖住左边一小条,右边的内容会从粘性条旁边穿过去。 */
|
||
display: flex; align-items: center; gap: 8px; width: 100%; min-height: 44px;
|
||
padding: 0 13px; border: 0; background: var(--glass-strong); color: var(--text-2);
|
||
font: inherit; font-size: var(--fs-sm); font-weight: 500; text-align: left; cursor: pointer;
|
||
position: sticky; top: 0; z-index: 2;
|
||
}
|
||
.map-panel:not(.collapsed) .panel-fold { border-bottom: 1px solid var(--line); }
|
||
.panel-fold .pf-n { color: var(--text-3); font-size: var(--fs-sm); font-weight: 400; }
|
||
.panel-fold .pf-c { margin-left: auto; padding-left: var(--sp-1); font-size: var(--glyph-xs); color: var(--text-3); }
|
||
.panel-fold:hover { color: var(--text); }
|
||
.panel-fold:hover .pf-c { color: var(--text); }
|
||
/* 「查询 / 重置」粘在面板底部:展开后表单 600+px、可视窗不到 400px,
|
||
不粘的话每改一次条件都要先滚到底才点得到查询。它必须是表单最后一个元素,
|
||
否则粘不住(实测后面还挂着内容时,底部会跟着内容一起往上跑)。 */
|
||
.filter-actions {
|
||
position: sticky; bottom: 0; z-index: 1;
|
||
margin: 0 -13px -13px; padding: 9px 13px;
|
||
background: var(--glass-strong); border-top: 1px solid var(--line);
|
||
}
|
||
.map-panel.collapsed { max-height: none; padding: 4px 0; }
|
||
.map-panel.collapsed #filter-form { display: none; }
|
||
/* 预设区间在窄屏会折成两三行(7 个预设 +「含家/工作」),单行横滑省下约 30~60px。 */
|
||
.map-panel .presets { flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none; }
|
||
.map-panel .presets::-webkit-scrollbar { display: none; }
|
||
.map-panel .presets > .chip { flex: 0 0 auto; }
|
||
.map-panel .map-count { display: none; }
|
||
}
|
||
/* Leaflet 把 .leaflet-container 的字号写成 0.75rem(见 vendor/leaflet/leaflet.css 的
|
||
「general typography」),于是它跟着**根字号**漂:根从 14px 降到 13px 后,
|
||
右下角的署名与比例尺缩到了 9.8px(掉到本站 11px 的下限之下),弹窗正文也跟着缩。
|
||
这里按自己的字阶钉死,分两档:容器 = 正文(弹窗正文是 1.08333em,跟着它走),
|
||
署名与比例尺 = 脚注。 */
|
||
.leaflet-container { font-size: var(--fs-sm); }
|
||
.leaflet-container .leaflet-control-attribution,
|
||
.leaflet-container .leaflet-control-scale-line { font-size: var(--fs-xs); }
|
||
|
||
.map-preview { height: 320px; border-radius: var(--r-md); overflow: hidden; border: 1px solid var(--line); }
|
||
|
||
/* ---------- 地点图片 ---------- */
|
||
/* 管理面板里的缩略图墙 */
|
||
.imgs-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); gap: 8px; }
|
||
.imgs-cell { position: relative; margin: 0; border-radius: var(--r-md); overflow: hidden; border: 1px solid var(--line); background: var(--line-soft); aspect-ratio: 4 / 3; }
|
||
.imgs-cell img { width: 100%; height: 100%; object-fit: cover; display: block; cursor: zoom-in; }
|
||
.imgs-del {
|
||
position: absolute; top: 5px; right: 5px; width: 22px; height: 22px; border-radius: 50%;
|
||
border: 0; background: rgba(0,0,0,.55); color: #fff; font-size: var(--glyph-sm); line-height: 1;
|
||
cursor: pointer; display: flex; align-items: center; justify-content: center;
|
||
}
|
||
.imgs-del:hover { background: #b42318; }
|
||
.imgs-busy { opacity: .45; pointer-events: none; }
|
||
.imgs-hint { font-size: var(--fs-sm); color: var(--text-3); margin-top: 8px; min-height: 18px; }
|
||
.imgs-hint.ok { color: #17604f; }
|
||
.imgs-hint.err { color: #b42318; }
|
||
/* 存储没配好时把「选择图片」变成不可点的样子,而不是留个能点但必失败的按钮 */
|
||
.btn.is-disabled { opacity: .5; cursor: not-allowed; }
|
||
|
||
/* 展示用:全屏画廊。深色底是为了不干扰照片本身,白底会把照片衬灰。
|
||
一次只看一张 —— 以前把图竖着堆起来,手机上既不能左右滑也不能放大。 */
|
||
.imgs-modal { position: fixed; inset: 0; z-index: 2000; background: rgba(12,14,16,.94); overflow: hidden; }
|
||
.imgs-modal[hidden] { display: none; }
|
||
.imgs-stage {
|
||
position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
|
||
overflow: auto; padding: calc(56px + var(--safe-t)) calc(14px + var(--safe-r)) calc(24px + var(--safe-b)) calc(14px + var(--safe-l));
|
||
/* 横滑留给我们做切换,纵向留给浏览器;pinch-zoom 让双指放大仍可用系统手势 */
|
||
touch-action: pan-y pinch-zoom;
|
||
}
|
||
.imgs-stage img { display: block; max-width: 100%; max-height: 100%; width: auto; height: auto; border-radius: var(--r-md); }
|
||
/* 双击放大:图放大到两倍、容器可拖动看局部(用尺寸而不是 transform,放大后不糊) */
|
||
.imgs-stage.zoomed { touch-action: pan-x pan-y; align-items: flex-start; justify-content: flex-start; }
|
||
.imgs-stage.zoomed img { max-width: none; max-height: none; width: 200%; border-radius: 0; }
|
||
.imgs-close {
|
||
position: fixed; top: calc(12px + var(--safe-t)); right: calc(14px + var(--safe-r)); z-index: 2001;
|
||
width: 44px; height: 44px; border-radius: 50%;
|
||
border: 1px solid rgba(255,255,255,.3); background: rgba(0,0,0,.4); color: #fff; font-size: var(--glyph-lg);
|
||
line-height: 1; cursor: pointer;
|
||
}
|
||
.imgs-close:hover { background: rgba(255,255,255,.18); }
|
||
.imgs-count {
|
||
position: fixed; top: calc(20px + var(--safe-t)); left: calc(18px + var(--safe-l)); z-index: 2001;
|
||
color: rgba(255,255,255,.78); font-size: var(--fs-sm);
|
||
}
|
||
/* 上一张 / 下一张:做成整条竖带,拇指够得着,不用瞄小按钮 */
|
||
.imgs-nav {
|
||
position: fixed; top: 50%; transform: translateY(-50%); z-index: 2001;
|
||
width: 44px; height: 76px; border: 0; border-radius: var(--r-lg); cursor: pointer;
|
||
background: rgba(255,255,255,.12); color: #fff; font-size: var(--glyph-xl); line-height: 1;
|
||
}
|
||
.imgs-nav:hover { background: rgba(255,255,255,.24); }
|
||
.imgs-prev { left: calc(6px + var(--safe-l)); }
|
||
.imgs-next { right: calc(6px + var(--safe-r)); }
|
||
|
||
/* ---------- 「停一下」提示页(内容对访客不开放之类) ---------- */
|
||
.notice-body {
|
||
min-height: 100vh; margin: 0; display: flex; align-items: center; justify-content: center;
|
||
padding: var(--sp-5) 18px; background: var(--bg);
|
||
}
|
||
.notice-card {
|
||
max-width: 420px; text-align: center; background: var(--panel); border: 1px solid var(--line);
|
||
border-radius: var(--r-lg); padding: 30px 26px; box-shadow: var(--sh-2);
|
||
}
|
||
.notice-card .notice-ico { font-size: var(--glyph-2xl); line-height: 1; margin-bottom: var(--sp-3); }
|
||
.notice-card h1 { font-size: var(--fs-lg); margin: 0 0 var(--sp-2); }
|
||
.notice-card p { color: var(--text-2); font-size: var(--fs-sm); margin: 0 0 18px; }
|
||
.notice-card .notice-tip { color: var(--text-3); font-size: var(--fs-sm); margin-top: 12px; }
|
||
|
||
/* 后台设置页的「标签 / 值」信息行(地图底图、图片存储状态)。
|
||
这个 class 一直没写样式,之前是两段光秃秃的 inline 文本;长一点的 URL
|
||
(比如对象存储的 endpoint)会把整页撑宽 —— 所以要 minmax(0,1fr) + 允许断词。 */
|
||
.kv { display: grid; grid-template-columns: 88px minmax(0, 1fr); gap: 4px 12px; font-size: var(--fs-sm); }
|
||
.kv .k { color: var(--text-3); }
|
||
.kv .v { color: var(--text); overflow-wrap: anywhere; }
|
||
@media (max-width: 620px) {
|
||
.kv { grid-template-columns: 1fr; gap: 2px; }
|
||
.kv .k { font-size: var(--fs-sm); }
|
||
}
|
||
/* URL 这类超长 token 不该把容器顶宽 */
|
||
.mono { overflow-wrap: anywhere; }
|
||
|
||
/* ---------- 暗色模式 ----------
|
||
跟随系统(prefers-color-scheme)。配色本来就是变量驱动的,所以这里主要换一套变量,
|
||
再覆盖几处硬编码的白色面。地图瓦片保持浅色——上游只有这一套免密钥瓦片,
|
||
所以地图页只暗掉面板与控件,瓦片不动。
|
||
色值是算过的:主文字 13.6:1、次要 8.0:1、弱化 5.4:1(都在 AA 之上)。 */
|
||
@media (prefers-color-scheme: dark) {
|
||
/* 用 html:root 而不是 :root:模板会在 CSS 之后注入一条 :root{--accent:…}(站长自定义主色),
|
||
同特异性下后者胜出,所以这里必须高一级才盖得住。 */
|
||
html:root {
|
||
/* 站长选的主色原值在深底上往往太暗(深绿只有 3.3:1),按比例掺白提亮 */
|
||
--accent: color-mix(in srgb, var(--accent-base, #2f7d6e) 58%, #ffffff);
|
||
--accent-soft: color-mix(in srgb, var(--accent-base, #2f7d6e) 24%, #1c2024);
|
||
--bg: #14171a;
|
||
--panel: #1c2024;
|
||
--line: #2b3137;
|
||
--line-soft: #23272c;
|
||
--text: #e8eaed;
|
||
--text-2: #b0b6bd;
|
||
--text-3: #99a0a8;
|
||
--surface: #22262b;
|
||
--glass: rgba(28, 32, 36, .92);
|
||
--glass-strong: rgba(28, 32, 36, .96);
|
||
--row-hover: #212529;
|
||
--bg-2: #191d21;
|
||
/* 浅绿底上用白字只有 2.4:1,反过来用深字才够 */
|
||
--on-accent: #0f1b18;
|
||
/* 类型色只有 indigo 在深底上不够:#5856d6 对 #1c2024 只有 2.90:1,其余都在 3.9 以上。
|
||
掺 10% 白提亮到 3.55:1。别的色一概不动 —— 它们本来就是浅亮色,深底上只会更清楚,
|
||
改了反而与手机端 / 地图上的颜色对不上。 */
|
||
--cat-indigo: #6967da;
|
||
--ok-bg: #16302a; --ok-fg: #7fd3bb; --ok-line: #245046;
|
||
--err-bg: #33201e; --err-fg: #f0a49b; --err-line: #5a3330;
|
||
--info-bg: #1b2534; --info-fg: #9dbdea; --info-line: #2f4055;
|
||
--sh-1: 0 1px 2px rgba(0, 0, 0, .45), 0 1px 3px rgba(0, 0, 0, .35);
|
||
--sh-2: 0 6px 18px rgba(0, 0, 0, .50);
|
||
--sh-3: 0 12px 34px rgba(0, 0, 0, .55);
|
||
}
|
||
/* 兜底:还有两处没走变量的白底 */
|
||
.flatpickr-calendar { background: var(--panel); border-color: var(--line); box-shadow: var(--sh-2); }
|
||
.flatpickr-day { color: var(--text); }
|
||
.flatpickr-day.prevMonthDay, .flatpickr-day.nextMonthDay { color: var(--text-3); }
|
||
.flatpickr-day:hover { background: var(--line-soft); color: var(--text); }
|
||
.flatpickr-months .flatpickr-month, .flatpickr-weekdays, .flatpickr-weekday { color: var(--text-2); fill: var(--text-2); background: transparent; }
|
||
.flatpickr-current-month .flatpickr-monthDropdown-months { background: var(--panel); color: var(--text); }
|
||
.flatpickr-time input, .flatpickr-time .flatpickr-am-pm { color: var(--text); }
|
||
/* Leaflet 自带的控件是白底,暗色下要跟着换,否则浅字压浅底看不清。
|
||
注意 leaflet.css 里角标的背景写的是 `.leaflet-container .leaflet-control-attribution`
|
||
(两级),所以这里必须带上 .leaflet-container 前缀,否则盖不住。 */
|
||
.leaflet-container .leaflet-bar a,
|
||
.leaflet-container .leaflet-bar a:hover,
|
||
.leaflet-container .leaflet-control-layers,
|
||
.leaflet-container .leaflet-control-layers-expanded,
|
||
.leaflet-container .leaflet-control-attribution,
|
||
.fog-opacity {
|
||
background: var(--surface); color: var(--text);
|
||
}
|
||
.leaflet-container .leaflet-bar a { border-bottom-color: var(--line); }
|
||
.leaflet-container .leaflet-bar a:hover { background: var(--line-soft); }
|
||
.leaflet-container .leaflet-control-attribution,
|
||
.leaflet-container .leaflet-control-attribution a { color: var(--text-2); }
|
||
.leaflet-control-scale-line {
|
||
background: rgba(28, 32, 36, .7); border-color: var(--text-3); color: var(--text-2);
|
||
}
|
||
.leaflet-container { background: #101315; }
|
||
/* 地图上的浅色瓦片在暗色 UI 里反差过大,整体压一点点亮度 */
|
||
#map .leaflet-tile-pane { filter: brightness(.86) saturate(.9); }
|
||
}
|
||
|
||
/* ---------- 键盘焦点 ----------
|
||
全站原来没有任何 :focus-visible 规则:用 Tab 走查时看不出焦点落在哪,
|
||
键盘用户(以及用读屏的)基本没法用。只在键盘操作时出现,鼠标点击不会看到这个圈。 */
|
||
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
|
||
.btn:focus-visible, .chip:focus-visible, .vote-btn:focus-visible,
|
||
.nav-links a:focus-visible, .pager a:focus-visible, .imgs-nav:focus-visible, .to-top:focus-visible {
|
||
outline: 2px solid var(--accent); outline-offset: 2px;
|
||
}
|
||
/* 输入框本来就有 focus 描边,这里只补键盘态的对比度 */
|
||
input:focus-visible, select:focus-visible, textarea:focus-visible, summary:focus-visible {
|
||
outline: 2px solid var(--accent); outline-offset: 1px;
|
||
}
|
||
|
||
/* ---------- 顶部加载进度条 ---------- */
|
||
.load-bar {
|
||
position: fixed; top: 0; left: 0; height: 2px; width: 0; z-index: 3000;
|
||
background: var(--accent); opacity: 0;
|
||
transition: width .18s ease-out, opacity .26s ease;
|
||
}
|
||
.load-bar[hidden] { display: none; }
|
||
|
||
/* ---------- 「已选条件」行 ---------- */
|
||
.active-filters { margin-bottom: 12px; align-items: center; }
|
||
.active-filters .af-label { font-size: var(--fs-sm); color: var(--text-3); }
|
||
.active-filters .chip { user-select: none; }
|
||
.active-filters .af-x { margin-left: 4px; opacity: .6; font-size: var(--fs-sm); }
|
||
.active-filters .chip:hover .af-x { opacity: 1; }
|
||
.active-filters .af-clear { background: transparent; color: var(--text-3); border-color: var(--line); }
|
||
|
||
/* ---------- 其他 ---------- */
|
||
.alert { padding: 9px 13px; border-radius: var(--r-md); font-size: var(--fs-sm); margin-bottom: 14px; }
|
||
.alert.err { background: var(--err-bg); color: var(--err-fg); border: 1px solid var(--err-line); }
|
||
.alert.ok { background: var(--ok-bg); color: var(--ok-fg); border: 1px solid var(--ok-line); }
|
||
.alert.info { background: var(--info-bg); color: var(--info-fg); border: 1px solid var(--info-line); }
|
||
.muted { color: var(--text-3); }
|
||
.mono { font-family: var(--mono); font-size: var(--fs-sm); }
|
||
.empty { text-align: center; padding: 40px 16px; color: var(--text-3); }
|
||
.empty .big { font-size: var(--glyph-xl); margin-bottom: var(--sp-2); }
|
||
.pager { display: flex; gap: 6px; align-items: center; justify-content: center; margin-top: 18px; font-size: var(--fs-sm); }
|
||
.pager a, .pager span { padding: 4px 10px; border-radius: var(--r-sm); border: 1px solid var(--line); color: var(--text-2); }
|
||
.pager a:hover { background: var(--line-soft); text-decoration: none; }
|
||
.pager .cur { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
|
||
footer.ft { max-width: 1120px; margin: 0 auto; padding: 20px; color: var(--text-3); font-size: var(--fs-sm); text-align: center; }
|
||
.timeline-day { margin-bottom: 20px; }
|
||
.timeline-day .dh {
|
||
position: sticky; top: var(--nav-h); background: var(--bg); z-index: 5; padding: 6px 0 7px;
|
||
font-size: var(--fs-sm); color: var(--text-2); font-weight: 500; display: flex; gap: 8px; align-items: baseline;
|
||
}
|
||
.timeline-day .dh .muted { font-size: var(--fs-sm); }
|
||
.tl-item { display: grid; grid-template-columns: 62px 20px 1fr; gap: 10px; align-items: start; padding: 7px 0; }
|
||
.tl-item .tm { color: var(--text-3); font-size: var(--fs-sm); font-variant-numeric: tabular-nums; padding-top: 1px; }
|
||
.tl-item .ln { display: flex; justify-content: center; position: relative; }
|
||
.tl-item .ln::before { content: ""; position: absolute; top: 12px; bottom: -12px; width: 1px; background: var(--line); }
|
||
.tl-item:last-child .ln::before { display: none; }
|
||
.tl-item .ln i { width: 9px; height: 9px; border-radius: 50%; background: var(--accent); margin-top: 5px; position: relative; z-index: 1; }
|
||
.tl-item .bd { font-size: var(--fs-sm); }
|
||
.tl-item .bd .meta { color: var(--text-3); font-size: var(--fs-sm); }
|
||
|
||
/* 聚合气泡改用主题色,默认的黄蓝配色和站点风格不一致 */
|
||
.marker-cluster-small,
|
||
.marker-cluster-medium,
|
||
.marker-cluster-large { background-color: color-mix(in srgb, var(--accent) 22%, transparent); }
|
||
.marker-cluster-small div,
|
||
.marker-cluster-medium div,
|
||
.marker-cluster-large div { background-color: var(--accent); color: var(--on-accent); font-weight: 500; }
|
||
|
||
/* 筛选面板占据左上角,把缩放控件挪到右上与图层切换并列 */
|
||
.map-panel { left: 12px; }
|
||
@media (max-width: 620px) { .map-panel { left: 8px; right: 8px; } }
|
||
|
||
/* 更多筛选类型 */
|
||
.more-types { margin: 2px 0 10px; }
|
||
.more-types summary { font-size: var(--fs-sm); color: var(--text-3); cursor: pointer; padding: 3px 0; }
|
||
.more-types summary:hover { color: var(--accent); }
|
||
.more-types .chips { margin-top: 8px; }
|
||
|
||
@media (max-width: 620px) {
|
||
.leaflet-bottom.leaflet-right .leaflet-control-zoom { margin-bottom: 34px; }
|
||
}
|
||
|
||
/* 推荐 / 踩雷 表态按钮 */
|
||
.vote-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 8px; }
|
||
.vote-label { color: var(--text-3); font-size: var(--fs-sm); min-width: 62px; }
|
||
/* 「推荐 / 踩雷」必须并排:把它们包成不可换行的组,窄屏时只让后面那句提示换行 */
|
||
.vote-btns { display: inline-flex; gap: 8px; flex-wrap: nowrap; }
|
||
.vote-btn {
|
||
display: inline-flex; align-items: center; gap: 5px;
|
||
padding: 6px 14px; border-radius: 999px; white-space: nowrap; flex: 0 0 auto;
|
||
border: 1px solid var(--line); background: var(--surface); color: var(--text);
|
||
font-size: var(--fs-sm); font-weight: 500; cursor: pointer; line-height: 1;
|
||
transition: background .12s, border-color .12s, color .12s;
|
||
}
|
||
.vote-btn:hover { border-color: var(--accent); color: var(--accent); }
|
||
.vote-btn.up.on { background: #e7f6ec; border-color: #34c759; color: #1f8f43; }
|
||
.vote-btn.down.on { background: #fdeceb; border-color: #ff3b30; color: #c62a20; }
|
||
@media (prefers-color-scheme: dark) {
|
||
/* 浅底深字在深色页里太扎眼,换成深底亮字(对比度都在 AA 之上) */
|
||
.vote-btn.up.on { background: #16302a; border-color: #34c759; color: #7fd3bb; }
|
||
.vote-btn.down.on { background: #33201e; border-color: #ff3b30; color: #f0a49b; }
|
||
.vote-tip.up { color: #7fd3bb; }
|
||
.vote-tip.down { color: #f0a49b; }
|
||
/* 深红字在深底上只有 2.3:1,换成亮红 */
|
||
.btn.danger { color: var(--err-fg); border-color: var(--err-line); }
|
||
}
|
||
.vote-btn:disabled { opacity: .55; cursor: default; }
|
||
.vote-tip { font-size: var(--fs-sm); color: var(--text-3); }
|
||
.vote-tip.up { color: #1f8f43; }
|
||
.vote-tip.down { color: #c62a20; }
|
||
/* 站长页面上访客票数只读展示:沿用「我的结论」同一套行结构,尺寸/行距完全一致,只弱化配色 */
|
||
.vote-btn.ro {
|
||
cursor: default; color: var(--text-2);
|
||
border-style: dashed; border-color: var(--line); background: transparent;
|
||
}
|
||
.vote-btn.ro:hover { border-color: var(--line); color: var(--text-2); }
|
||
/* 列表 / 气泡里的推荐踩雷小标签 */
|
||
/* 坐标疑似没做过 GCJ-02 偏移的提醒标 */
|
||
.tag.warn { background: #fdf3e3; color: #a15c00; }
|
||
.tag.vote-up { background: #e7f6ec; color: #1f8f43; }
|
||
.tag.vote-down { background: #fdeceb; color: #c62a20; }
|
||
|
||
/* 地图气泡:地点信息 + 只读备注 + 就地表决 */
|
||
.mp { min-width: 240px; max-width: 300px; }
|
||
.mp-n { font-weight: 600; margin-bottom: 2px; }
|
||
.mp-m { color: var(--text-3); font-size: var(--fs-sm); line-height: 1.6; }
|
||
.mp-note {
|
||
margin-top: 6px; padding: 6px 8px; border-radius: var(--r-sm);
|
||
background: var(--bg, #f5f6f8); color: var(--text);
|
||
font-size: var(--fs-sm); line-height: 1.55; white-space: pre-line;
|
||
max-height: 150px; overflow-y: auto;
|
||
}
|
||
.mp-foot { margin-top: 7px; font-size: var(--fs-sm); }
|
||
/* nowrap:气泡本来就窄,「👎 踩雷」被挤到第二行会很难看 */
|
||
.mp-vote { display: flex; align-items: center; gap: 6px; margin-top: 8px; flex-wrap: nowrap; }
|
||
.mp-vl { flex: 0 0 auto; color: var(--text-3); font-size: var(--fs-sm); white-space: nowrap; }
|
||
.mp-vb {
|
||
display: inline-flex; align-items: center; gap: 4px;
|
||
padding: 4px 10px; border-radius: 999px;
|
||
border: 1px solid var(--line); background: var(--surface); color: var(--text);
|
||
font-size: var(--fs-sm); font-weight: 500; line-height: 1; cursor: pointer;
|
||
flex: 0 0 auto; white-space: nowrap;
|
||
transition: background .12s, border-color .12s, color .12s;
|
||
}
|
||
.mp-vb b { font-weight: 600; }
|
||
.mp-vb:hover { border-color: var(--accent); color: var(--accent); }
|
||
.mp-vb.up.on { background: #e7f6ec; border-color: #34c759; color: #1f8f43; }
|
||
.mp-vb.down.on { background: #fdeceb; border-color: #ff3b30; color: #c62a20; }
|
||
.mp-vb:disabled { opacity: .55; cursor: default; }
|
||
|
||
/* 备注块:rond 回填的备注是按次一行的,必须保留换行 */
|
||
.note-block {
|
||
margin-top: 4px; padding: 7px 9px; border-radius: var(--r-md);
|
||
background: var(--bg, #f5f6f8); color: var(--text-2);
|
||
font-size: var(--fs-sm); line-height: 1.65; white-space: pre-line;
|
||
}
|
||
|
||
/* 报告导出为 PDF / 长图:隐藏导航、页脚与操作按钮,卡片避免跨页断裂 */
|
||
@media print {
|
||
.nav, .ft, .no-print { display: none !important; }
|
||
body { background: #fff; }
|
||
.wrap { max-width: 820px; margin: 0 auto; padding: 0 12px; }
|
||
.page-head h1 { margin-top: 8px; }
|
||
.card, .kpi, .kpis { break-inside: avoid; }
|
||
.grid.g2 { grid-template-columns: 1fr 1fr; }
|
||
a { color: inherit; text-decoration: none; }
|
||
.bar-fill { -webkit-print-color-adjust: exact; print-color-adjust: exact; }
|
||
.chip.on { background: var(--accent-soft); color: var(--accent); }
|
||
}
|
||
|
||
|
||
/* 数据编辑页:行内编辑用 details 折叠,summary 复用 .btn 外观 */
|
||
.inline-edit { display: inline-block; position: relative; }
|
||
.inline-edit summary { list-style: none; display: inline-block; }
|
||
.inline-edit summary::-webkit-details-marker { display: none; }
|
||
.inline-edit[open] { display: block; }
|
||
/* 行内编辑表单的宽度:桌面靠 --efw 撑到够填(各表不同,见模板里的 inline 变量),
|
||
放不下宽表时下面会放开这个下限——不然 320px 的表单会把 294px 的卡片顶破。 */
|
||
.inline-edit form { background: var(--bg-2, #fafbfb); border: 1px solid var(--line); border-radius: var(--r-lg); padding: 12px; min-width: var(--efw, 280px); }
|
||
.inline-edit form .f { margin-bottom: 8px; }
|
||
|
||
/* 「按名称搜坐标」输入框 + 按钮:必须始终同一行(含移动端),底边对齐。
|
||
* 不能直接复用 .row —— label.f 自带 margin-bottom,flex-end 按外边距盒对齐会把它顶高;
|
||
* 且 .row 会换行。这里显式 nowrap + 归零外边距,并让输入框可收缩。 */
|
||
.search-row { display: flex; flex-wrap: nowrap; align-items: flex-end; gap: 8px; margin-bottom: 12px; }
|
||
.search-row > label.f { flex: 1 1 auto; min-width: 0; margin-bottom: 0; }
|
||
.search-row .btn { flex: 0 0 auto; font-size: var(--fs-sm); }
|
||
.search-row > .act { display: flex; flex: 0 0 auto; gap: 8px; }
|
||
|
||
/* 按名称搜坐标的候选列表:整行可点,名称与地址分两行 */
|
||
.geo-list { display: flex; flex-direction: column; gap: 4px; margin: 0 0 12px; }
|
||
/* 到访选择器的候选列表有 max-height(200~240px),但 flex 子项不会因为上限而收缩、
|
||
overflow 又是 visible —— 30 条候选会实打实溢出上千像素,被后面的表单元素盖住。
|
||
必须给个 overflow 让它在自己的框里滚。 */
|
||
.picker-results { overflow-y: auto; }
|
||
.geo-list:empty { margin: 0; }
|
||
/* 行程列表的路线列:窄屏下每列只剩几十像素,城市名会把行高撑到到访列表的近两倍,
|
||
藏掉它(编号 + 地点名够认了,城市在紧邻的到访列表里就有),title 里仍留着全称。 */
|
||
@media (max-width: 620px) { .mv-city { display: none; } }
|
||
|
||
/* 筛选行:和 .row 一样窄屏会换行,但控件按底边对齐。
|
||
不能直接用 .row + align-items:flex-end——label.f 自带 margin-bottom,
|
||
flex-end 按外边距盒对齐会把按钮顶低几像素(就是那种「按钮有点歪」)。 */
|
||
.filter-row { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 8px; }
|
||
.filter-row > label.f { margin-bottom: 0; }
|
||
.filter-row > .act { display: flex; flex: 0 0 auto; gap: 8px; }
|
||
.geo-item {
|
||
display: block; width: 100%; text-align: left; cursor: pointer;
|
||
padding: 7px 10px; border: 1px solid var(--line); border-radius: var(--r-md);
|
||
background: var(--panel); color: var(--text); font: inherit;
|
||
}
|
||
.geo-item:hover { background: var(--line-soft); }
|
||
.geo-name { display: block; font-size: var(--fs-sm); }
|
||
.geo-addr { display: block; color: var(--text-3); font-size: var(--fs-sm); margin-top: 2px; }
|
||
|
||
/* ---------- 专题 ---------- */
|
||
/* 必须带上 #map:站点里 #map{height:100%} 是 ID 选择器,
|
||
单靠 .topic-map 这个类压不过它,容器会被算成 0 高(地图看不见)。 */
|
||
#map.topic-map { height: 520px; width: 100%; border-radius: var(--r-lg); overflow: hidden; }
|
||
@media (max-width: 620px) { #map.topic-map { height: 360px; } }
|
||
.topic-card { display: block; text-decoration: none; color: inherit; transition: box-shadow .15s, transform .15s; }
|
||
.topic-card:hover { box-shadow: var(--sh-2); transform: translateY(-1px); }
|
||
.topic-head { display: flex; align-items: center; gap: 8px; }
|
||
.topic-head h2 { margin: 0; }
|
||
|
||
/* 地图兜底提示:脚本没就绪时留在容器里,解释为什么没有地图 */
|
||
.map-fallback {
|
||
display: flex; align-items: center; justify-content: center; height: 100%;
|
||
padding: 20px; text-align: center; color: var(--text-3); font-size: var(--fs-sm);
|
||
}
|
||
|
||
/* 专题:时间范围独立成行(精确到分钟,塞进 KPI 网格会被挤成多行) */
|
||
.topic-range { margin-top: 5px; color: var(--text-2); font-size: var(--fs-sm); font-variant-numeric: tabular-nums; }
|
||
.topic-card .topic-range { margin: 7px 0 0; }
|
||
.topic-desc { color: var(--text-2); white-space: pre-wrap; line-height: 1.65; }
|
||
.kpis.kpis-3 .kpi { flex: 1 1 30%; }
|
||
@media (max-width: 620px) {
|
||
/* 三个数字都很短,窄屏保持三列比 2+1 更好看 */
|
||
.topic-card .kpis.kpis-3 .kpi { flex: 1 1 30%; }
|
||
.topic-card .kpi { padding: 11px 12px; }
|
||
.topic-range { font-size: var(--fs-sm); }
|
||
}
|
||
|
||
/* ---------- 表单里的「文字 + 内联按钮」组合 ---------- */
|
||
/* label 的文字行右侧放次要操作(活动改名/删除),不额外占高度,也就不会把整格撑高 */
|
||
label.f > span.span-actions { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
|
||
.mini-actions { display: flex; gap: 8px; font-size: var(--fs-sm); }
|
||
.mini-actions a { color: var(--text-3); }
|
||
.mini-actions a:hover { color: var(--accent); }
|
||
.mini-actions a.danger:hover { color: #b42318; }
|
||
/* 输入框 + 按钮同一行:input 自适应、按钮不压缩(不设 min-width:0 的话 input 会被按钮顶到下一行) */
|
||
.input-btn { display: flex; gap: 6px; }
|
||
.input-btn > input { flex: 1 1 auto; min-width: 0; }
|
||
.input-btn > .btn { flex: 0 0 auto; padding-left: 12px; padding-right: 12px; }
|
||
|
||
/* 到访列表里的收藏星标:点一下就切换,不用展开行内编辑 */
|
||
.star {
|
||
background: none; border: 0; padding: 0 3px; cursor: pointer; line-height: 1;
|
||
font-size: var(--glyph-sm); color: var(--text-3); vertical-align: -1px;
|
||
}
|
||
.star:hover { color: #e8b700; }
|
||
.star.on { color: #e8b700; }
|
||
|
||
/* 单个点位的地图弹窗 */
|
||
#point-modal {
|
||
position: fixed; inset: 0; z-index: 1200; display: flex; align-items: flex-start;
|
||
justify-content: center; padding: 40px 16px; overflow: auto;
|
||
background: rgba(0, 0, 0, .45);
|
||
}
|
||
.point-modal-box {
|
||
width: 100%; max-width: 660px; background: var(--panel); border-radius: var(--r-lg);
|
||
padding: 14px; box-shadow: var(--sh-3);
|
||
}
|
||
.point-modal-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 10px; }
|
||
.point-modal-map { height: 360px; border-radius: var(--r-lg); overflow: hidden; }
|
||
@media (max-width: 620px) {
|
||
#point-modal { padding: 16px 10px; }
|
||
.point-modal-map { height: 260px; }
|
||
}
|
||
|
||
/* 表格里的行内操作链接不许折行:中文默认能在任意字间断开,
|
||
「地图」「编辑」这类两字链接在窄列里会被拆成两行 */
|
||
.tb a { white-space: nowrap; }
|
||
|
||
/* 照片导入表:固定列宽比例,输入框跟着列宽走。
|
||
否则每个 input 自带 min-width,8 列加起来必然超出视口,最右边的「说明」列要横向拖动才看得到 */
|
||
.photo-table { table-layout: fixed; width: 100%; min-width: 880px; }
|
||
.photo-table th, .photo-table td { padding: 7px 8px; vertical-align: top; }
|
||
.photo-table input, .photo-table select { width: 100%; min-width: 0; box-sizing: border-box; }
|
||
.photo-table input[type=datetime-local] { font-size: var(--fs-sm); padding: 6px 6px; }
|
||
.photo-table input[type=checkbox] { width: auto; }
|
||
.photo-table .chip { white-space: nowrap; }
|
||
|
||
/* 窄屏:9 列在手机上放不下,把信息量最低的「文件」列去掉(地点名已经能认出是哪张),
|
||
同时把最小宽度调小,需要横向滑动时也能更快看到最右边的操作 */
|
||
@media (max-width: 700px) {
|
||
.photo-table { min-width: 640px; }
|
||
.photo-table col:nth-child(3),
|
||
.photo-table thead th:nth-child(3),
|
||
.photo-table tbody tr td:nth-child(3) { display: none; }
|
||
}
|
||
|
||
/* 照片导入:确认表上方的总览图。
|
||
做成「粘」的是为了让「鼠标划到某一行 → 图上那个点跳出来」始终看得见——
|
||
表格几十行,图一滚出屏幕这个联动就白搭了。粘住时必须自带不透明底,
|
||
否则下面滚过去的行会透上来。 */
|
||
#map-card {
|
||
position: sticky; top: calc(var(--nav-h) + 8px); z-index: 5;
|
||
background: var(--panel); padding: 2px 0 10px; margin-bottom: 2px;
|
||
border-bottom: 1px solid var(--line-soft);
|
||
}
|
||
.photo-map {
|
||
height: 200px; width: 100%; border-radius: var(--r-lg); overflow: hidden;
|
||
border: 1px solid var(--line); transition: height .18s ease;
|
||
}
|
||
/* 粘住以后收矮:200px 的浮层会挡住从下面滚过去的半个屏幕,收到 132px 还能看清
|
||
高亮出来的那个点,占的高度也只剩一条。说明文字这时候就没必要留着了。 */
|
||
#map-card.stuck .photo-map { height: 132px; }
|
||
#map-card.stuck .card-sub { display: none; }
|
||
#map-card.stuck { box-shadow: var(--sh-1); }
|
||
@media (max-width: 700px) {
|
||
.photo-map { height: 170px; }
|
||
#map-card.stuck .photo-map { height: 110px; }
|
||
}
|
||
@media (prefers-reduced-motion: reduce) {
|
||
.photo-map { transition: none; }
|
||
}
|
||
|
||
/* 照片导入的进度/结果提示:脚本直接往元素里写文本,空的时候自己收起 */
|
||
#parse-status { margin: 12px 0 0; }
|
||
#parse-status:empty { display: none; }
|
||
|
||
/* ---------- 窄屏触控与控件字号(必须放在最后) ----------
|
||
这几组选择器(.chip / .btn / .check input)的基础规则写在文件中部,
|
||
而 CSS 同特异性下靠后的赢 —— 把它们写在前面那份「窄屏触控」块里是被盖掉的
|
||
(实测 .check input 的 20px 一直没生效,量出来还是 15px)。所以统一放这里。
|
||
量法与判据见技能 rond-web-verify 的 audit/mobile.js:触控目标 ≥44px、
|
||
输入框字号 ≥16px(否则 iOS 聚焦时会自放大整页)。 */
|
||
@media (max-width: 620px) {
|
||
.chip { padding: 9px 12px; }
|
||
.btn, .btn.sm { padding: 10px 14px; }
|
||
/* 勾选框:原来 15px,手指很难点中(尤其后台设置页一屏几十个开关)。
|
||
有效目标仍是外面那圈 label,但控件本身大一点更好瞄。 */
|
||
.check input, input[type=checkbox], input[type=radio] { width: 20px; height: 20px; }
|
||
/* 下拉框 39px 差 5px 到 44px,补齐 */
|
||
select { min-height: 44px; }
|
||
/* 表格里的控件保持紧凑:那张表列多、行密,抬到 44px 会让整张表膨胀;
|
||
.photo-table input[type=checkbox]{width:auto} 是既有约定,别覆盖。 */
|
||
table.tb select, .photo-table select { min-height: 0; }
|
||
/* file / color 这两种被基础规则排除在外(不套文本框外观),也就没吃到
|
||
--field-fs,窄屏仍是 13.3px ⇒ iOS 一聚焦就放大。单独提上来。 */
|
||
input[type=file], input[type=color] { font-size: var(--fs-control); }
|
||
/* 11.5px 的标签在手机上偏小(/stats 一页 29 处、/ 15 处) */
|
||
.tag { font-size: var(--fs-sm); }
|
||
/* 导航左上角那个 logo 是回首页的入口,原来只有 25×29:给它撑到 44 高,
|
||
横向也补到 44(.brand .t 在窄屏被隐藏,加内边距就够了)。 */
|
||
.brand { min-width: 44px; min-height: 44px; justify-content: center; }
|
||
|
||
/* ---------- 地点库 / 时间线 / 统计三页共用的筛选卡:窄屏默认收成一条 ----------
|
||
与地图页那块 .panel-fold 同形——手机上它一上来就占掉大半屏(预设 + 已选 + 类型 + 六个
|
||
输入框),内容反而看不见。展开时折叠条用负外边距贴到卡片上沿(圆角照着卡片补,
|
||
卡片没有 overflow:hidden,否则折叠条就粘不住了);收起时卡片内边距归零、只留那一条。
|
||
top 取 --nav-h:导航是 sticky 的,写 0 会被压在它下面。
|
||
这些规则整条在媒体查询里 ⇒ 桌面端 .collapsed 不生效,等于没影响。 */
|
||
.filters-card > .panel-fold {
|
||
margin: calc(var(--card-pad) * -1) calc(var(--card-pad) * -1) 14px; border-radius: var(--r-lg) var(--r-lg) 0 0; top: var(--nav-h);
|
||
}
|
||
.filters-card.collapsed { padding: 0; }
|
||
.filters-card.collapsed > .panel-fold { margin: 0; border-radius: var(--r-lg); }
|
||
.filters-card.collapsed > :not(.panel-fold) { display: none; }
|
||
}
|
||
|
||
/* ---------- 放不下宽表时:一屏一行的「字段 + 值」 ----------
|
||
地点库(8 列)、统计、专题、后台这几张表在窄处要横滑 300+px 才看得全,滑着还对不上表头。
|
||
放不下时改成每行一块、每格「字段名 + 值」竖排:不用横滑,也不用记「第几列是什么」。
|
||
字段名取 td 的 data-label,每行的首格留空当小标题(如地点名),操作格给「操作」。
|
||
表格结构与脚本都没动(只是隐藏 thead),链接 / 表单 / 按钮照旧。
|
||
阈值 800 是从「卡片能不能放下表格」换算来的:卡片内容宽 ≈ 视口 − 93(页面 + 卡片的内边距),
|
||
这几张表的 min-width 最宽是 700 ⇒ 视口 793 以上才放得下,取 800 留几像素余量。
|
||
**必须用 @media,别改成 @container**:容器查询能按「卡片自己有多宽」判得更准,可它要
|
||
iOS 16 / Chrome 105 起才支持——旧手机整块规则直接失效,表格完全不堆叠、退化成横滑
|
||
328~388px(2026-09-19 真机上就是这么炸的,比改之前还糟)。现在宽表卡片已被要求不放 g2
|
||
(见 wide_table_layout_test.go),卡片宽度只由视口决定,两种判据在这些页面上等价,
|
||
那就用兼容性好的那个。若页面/卡片的内边距变了,这里的 800 要跟着重算。 */
|
||
/* ---------- 轻量输入弹层(替代 window.prompt) ----------
|
||
*
|
||
* 为什么不用 prompt():它会把提示文本与默认值写进地址栏(Chrome 会把
|
||
* 「?新的活动名称=吃饭」这类内容拼到 URL 上并留进历史),刷新即丢、
|
||
* 分享出去还带着原文。confirm/alert 不污染地址栏,只有 prompt 有这个毛病。
|
||
*
|
||
* 定位:原生 <dialog> 居中、带 ::backdrop 遮罩、按 Esc 关闭、焦点自动进输入框,
|
||
* 这几件事正好是 prompt 给的,手写一遍不划算;旧浏览器不支持时降级成页面顶部一条。
|
||
* showModal() 会让页面其余部分 inert,天然挡住「弹层开着还能点底层」。
|
||
*/
|
||
dialog.prompt-box {
|
||
border: 1px solid var(--line);
|
||
border-radius: var(--r-lg);
|
||
padding: 0;
|
||
min-width: 0; width: min(420px, calc(100vw - 32px));
|
||
background: var(--surface);
|
||
color: var(--text);
|
||
box-shadow: var(--sh-2);
|
||
}
|
||
dialog.prompt-box::backdrop { background: rgba(16, 24, 40, .38); }
|
||
.prompt-box .pb-body { padding: 14px 16px 12px; }
|
||
.prompt-box .pb-title { font-size: var(--fs-md); font-weight: 500; margin-bottom: 8px; }
|
||
/* 输入框字号必须 ≥16px:iOS Safari 聚焦时会自动放大页面,16px 以下会把布局顶歪 */
|
||
.prompt-box .pb-input {
|
||
width: 100%; font-size: var(--fs-control); font-family: inherit; color: var(--text);
|
||
background: var(--surface); border: 1px solid var(--line);
|
||
border-radius: var(--r-sm); padding: 7px 10px; min-height: 40px;
|
||
}
|
||
.prompt-box .pb-input:focus { outline: 2px solid var(--accent); outline-offset: -1px; border-color: var(--accent); }
|
||
.prompt-box .pb-acts { display: flex; gap: 8px; justify-content: flex-end; padding: 0 16px 14px; }
|
||
/* 触控目标下限 44px(窄屏见文件末尾的覆盖块) */
|
||
.prompt-box .pb-acts .btn { min-height: 38px; }
|
||
@media (max-width: 700px) {
|
||
.prompt-box .pb-acts .btn { min-height: 44px; }
|
||
}
|
||
|
||
/* 不支持 <dialog> 的旧浏览器:降级成页面顶部一条提示,
|
||
至少不丢内容、也不会把地址栏弄脏。 */
|
||
.prompt-fallback {
|
||
position: fixed; inset: 8px 8px auto 8px; z-index: 2000;
|
||
display: flex; gap: 8px; align-items: center;
|
||
padding: 10px 12px; border: 1px solid var(--line); border-radius: var(--r-lg);
|
||
background: var(--surface); box-shadow: var(--sh-2);
|
||
}
|
||
.prompt-fallback input {
|
||
flex: 1; min-width: 0; font-size: var(--fs-control); font-family: inherit;
|
||
border: 1px solid var(--line); border-radius: var(--r-sm); padding: 7px 10px; min-height: 40px;
|
||
}
|
||
|
||
@media (max-width: 800px) {
|
||
.tw > table.tb.tb-stack { min-width: 0; }
|
||
.tw > table.tb.tb-stack thead { display: none; }
|
||
.tw > table.tb.tb-stack,
|
||
.tw > table.tb.tb-stack > tbody,
|
||
.tw > table.tb.tb-stack > tbody > tr,
|
||
.tw > table.tb.tb-stack > tbody > tr > td { display: block; width: auto; }
|
||
.tw > table.tb.tb-stack > tbody > tr {
|
||
padding: 9px 11px; border-bottom: 1px solid var(--line-soft);
|
||
}
|
||
.tw > table.tb.tb-stack > tbody > tr:last-child { border-bottom: 0; }
|
||
/* 值用悬挂缩进:标签绝对定位在左边,值换行时对齐到值那一栏,而不是缩到标签下面 */
|
||
.tw > table.tb.tb-stack > tbody > tr > td {
|
||
position: relative; padding: 2px 0 2px 5.6em; border-bottom: 0;
|
||
}
|
||
.tw > table.tb.tb-stack > tbody > tr > td::before {
|
||
content: attr(data-label); position: absolute; left: 0; top: 2px;
|
||
width: 5em; color: var(--text-3); font-size: var(--fs-sm);
|
||
}
|
||
/* 首格是这一行的主角(地点名 / 天气 / 方式…):不缩进、字号抬一档当小标题;
|
||
操作格也是这一类(表头为空、整行铺开)。它们里面的内容是长名称或编辑表单,
|
||
而桌面模板给操作格挂了 nowrap(那一列要横向排按钮)——white-space 会继承到表单里,
|
||
于是「已选:#403 2026-09-13 19:53 · 某某地」这一行在窄处不换行、把卡片顶出 44px。
|
||
堆叠态每格本来就是一整行,nowrap 没有意义,放开。 */
|
||
.tw > table.tb.tb-stack > tbody > tr > td:not([data-label]) {
|
||
padding-left: 0; margin-bottom: 2px; font-size: var(--fs-sm); white-space: normal;
|
||
}
|
||
/* 操作格(有的带「操作」标签、有的表头本来就是空的)在堆叠态整行铺开,
|
||
桌面那条 nowrap 会让格里的编辑表单不换行、把卡片顶出几十像素 */
|
||
.tw > table.tb.tb-stack > tbody > tr > td.ops { white-space: normal; }
|
||
/* 数字列不必再右对齐(现在已经是「标签 + 值」两栏) */
|
||
.tw > table.tb.tb-stack td.num { text-align: left; }
|
||
/* 行内编辑表单的桌面宽度下限在这里放开:320px 的表单加上悬挂缩进会顶破卡片,
|
||
而表单字段本来就是 flex 自适应的(见 .row / label.f),窄处自然铺满即可。 */
|
||
.tw > table.tb.tb-stack .inline-edit form { min-width: 0; }
|
||
}
|
||
|