: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); } /* / 的浏览器默认是 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),整行收进这里。 用原生
,不靠 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); } .grid { display: grid; gap: 16px; } /* 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; } .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 里的 ,用 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% 不能省:这是