:root {
  --bg: #f5f7fa;
  --panel: #ffffff;
  --text: #1f2933;
  --muted: #7b8794;
  --primary: #2f6fed;
  /* 主色的加深档。给「需要压得住背景」的实心面用（返回顶部圆钮的渐变落点就是它）。
     单靠 --primary 做纯色底，在白底页面上偏飘、不够醒目。 */
  --primary-deep: #1a4fbf;
  --primary-soft: #e8f0fe;
  --border: #e4e9f0;
  --shadow: 0 2px 12px rgba(31, 41, 51, 0.06);
  --radius: 14px;
  --side-w: 232px;
  --top-h: 60px;   /* 顶栏高度（含搜索引擎搜索框）：侧栏 sticky top、公告栏 sticky top 基于它 */
  /* 吸顶总高度 = 顶栏 + 公告栏（公告栏显示时那一截）。**内容区的锚点偏移必须用它**：
     公告栏在 main.content 内、不覆盖侧栏，所以侧栏仍用 --top-h；
     但「滚动到分类」时不吃掉公告栏这一截，标题就会被压住（2026-10-18 实测 28px）。
     由 app.js 的 syncTopHeight() 写入行内值，这里只是首屏兜底。 */
  --stick-h: 60px;
  /* 可在后台「首页设置 → 字体」调整，由 JS 写入行内样式覆盖 */
  --font-size: 14px;
  /* 顶栏左上角「站点名称 / 站点描述」两个独立字号（各自一个滑块）。
     与 --font-size（页面基准字号）分开：这里只作用于 .brand 里的两行文字。 */
  --title-size: 18px;
  --subtitle-size: 12px;
  /* 主页中心模块（正文顶部的 .hero 大标题 + 副标题）的独立字号，同样各一个滑块。
     与顶栏那两个分开：改这里不会动顶栏，改顶栏也不会动中心。 */
  --hero-title-size: 24px;
  --hero-sub-size: 14px;
  --font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC",
    "Microsoft YaHei", Roboto, Helvetica, Arial, sans-serif;
  /* 开启壁纸时顶栏 / 侧栏改用半透明面板，让壁纸能全屏透出（不透明面板会挡住壁纸） */
  --panel-translucent: rgba(255, 255, 255, .78);

  /* ===== 毛玻璃（glassmorphism）设计令牌 =====
   * 页面底层是一层柔和的多点渐变（--page-bg），顶栏 / 侧栏 / 卡片浮在它上面：
   * 半透明填补 + backdrop-filter 模糊身后内容，滚过长内容时玻璃感最明显。
   * --bg / --panel 保持不透明，供后台等需要高可读性的区域继续使用。
   *
   * 色彩浓度经过实测标定：底色太淡（如 #dce8ff 一档）时，58% 白的玻璃层叠上去
   * 与纯白几乎无差，毛玻璃在浅色主题下「看不见」；这里刻意把色斑压深一档，
   * 让玻璃面板透出可见的冷/暖色调，同时仍保持深色文字的高可读性。 */
  --page-bg:
    radial-gradient(1100px 620px at 8% -8%, #c3d7ff 0%, rgba(195, 215, 255, 0) 62%),
    radial-gradient(900px 520px at 102% 2%, #ffcfdf 0%, rgba(255, 207, 223, 0) 58%),
    radial-gradient(1000px 700px at 48% 112%, #b7eeda 0%, rgba(183, 238, 218, 0) 62%),
    radial-gradient(760px 460px at 78% 46%, #ffebc9 0%, rgba(255, 235, 201, 0) 60%),
    linear-gradient(180deg, #ebf0fa 0%, #e6ecf7 100%);
  --glass-1: rgba(255, 255, 255, .58);   /* 顶栏 / 侧栏 / 顶部导航：面积大、透得多 */
  --glass-2: rgba(255, 255, 255, .55);   /* 卡片 / 小卡片 */
  --glass-3: rgba(255, 255, 255, .82);   /* 弹窗 / 下拉：可读性优先 */
  --glass-hover: rgba(255, 255, 255, .88);
  --glass-brd: rgba(255, 255, 255, .75);        /* 玻璃上沿高光边 */
  --glass-brd-soft: rgba(163, 182, 210, .38);   /* 玻璃之间的分隔线 */
  --glass-blur: 20px;
  --glass-saturate: 180%;
  --glass-shadow: 0 4px 18px rgba(31, 41, 51, .08);
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  color: var(--text);
  font-family: var(--font-family);
  font-size: var(--font-size);
  line-height: 1.5;
  /* 禁止 iOS 横竖屏切换时自动放大字号 */
  -webkit-text-size-adjust: 100%;
}

/* 页面底层：柔和的多点渐变，作为毛玻璃「身后被模糊的东西」。
   必须挂在 html 上而不是 body 上 —— body 要保持透明，
   否则负 z-index 的壁纸层（body.wallpaper::before）会被 body 自己的背景整块盖住
   （见下方「壁纸：固定背景层」的说明）。 */
html {
  min-height: 100%;
  background: var(--page-bg);
  background-attachment: fixed;
  background-repeat: no-repeat;
  background-size: cover;
}
body { background: transparent; }

a { color: inherit; text-decoration: none; }

/* 顶栏（毛玻璃：吸顶滚动时正文从它身后透过并被模糊） */
.topbar {
  position: sticky;
  top: 0;
  z-index: 30;
  min-height: var(--top-h);
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 10px 14px;
  padding: 8px 18px;
  background: var(--glass-1);
  -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
  backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
  border-bottom: 1px solid var(--glass-brd-soft);
  box-shadow: var(--glass-shadow);
}
/* 顶栏左上角品牌块：站点名称（上）+ 站点描述（下），两行字号各自独立可调。
   注意 .brand 本身不要再写 font-size —— 会同时影响两行，让两个滑块互相打架。
   顶栏高度因此会随字号变化，app.js 的 syncTopHeight() 会把实测高度回写给
   --top-h（侧栏 sticky top 与锚点 scroll-margin 都依赖它）。 */
.brand {
  display: flex;
  align-items: center;
  gap: 9px;
  min-width: 0;
}
/* 图标跟着「站点名称字号」一起缩放：否则把名称拖到 40px 时，固定 22px 的
   emoji 会显得极小、比例失衡。默认 18px × 1.2 ≈ 21.6px，与改版前基本一致。
   站内图标（光伏类矢量图）由后台「站点图标」选择器更换，见 js/logos.js；
   这里统一按 1em 排版 —— 换什么图标都不用改尺寸，字号滑块一拖图标同步变大。 */
.brand #siteLogo {
  font-size: calc(var(--title-size) * 1.2);
  line-height: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;              /* 名称过长时别把图标挤扁（省略号由 .brand-text 负责） */
}
.brand #siteLogo svg { width: 1em; height: 1em; display: block; }
/* 自定义图片可能不是正方形：锁高度、宽度按比例自适应，避免被拉变形。
   min-width 是兜底：图片地址失效时（naturalWidth=0）宽度会塌成 0，
   图标凭空消失、后面的名称跟着左移一下；给个最小占位就不会有这种跳动。
   配合 object-fit: contain，竖长图落在方形盒子里也不会被拉伸。 */
.brand #siteLogo img {
  height: 1em; min-width: 1em; width: auto; max-width: 1.6em;
  object-fit: contain; display: block;
}
.brand #siteLogo .logo-text { display: block; }
.brand-text {
  display: flex;
  flex-direction: column;
  min-width: 0;
  line-height: 1.25;
}
.brand #siteTitle {
  font-weight: 700;
  font-size: var(--title-size);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.brand #siteDesc {
  color: var(--muted);
  font-size: var(--subtitle-size);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* 顶栏搜索区：网页搜索（左） + 站内站点搜索（右）；窄屏整体换到第二行 */
.tb-searches {
  display: flex;
  align-items: center;
  gap: 10px;
  flex: 1 1 auto;
  min-width: 0;
}
.ws-wrap { position: relative; flex: 1 1 340px; max-width: 480px; min-width: 0; }
/* 搜索表单必须是 flex 行：此前没有任何 .ws-form 规则，<form> 按块级排版把
   「输入框」和「搜索按钮」竖着摞成两行，顶栏实测被撑到 91px。
   而 --top-h 固定按 60px 算（侧栏 sticky top、锚点 scroll-margin 都基于它），
   于是滚动时侧栏会被顶栏挡住、锚点跳转也会把标题压在顶栏下面。 */
.ws-form { display: flex; align-items: center; gap: 8px; }
/* 顶栏内的尺寸收紧，避免把顶栏撑高 */
.tb-searches .ws-input-wrap input { height: 36px; font-size: 13px; }
.tb-searches .ws-go { height: 36px; padding: 0 16px; font-size: 13px; }
.tb-searches .ws-badge { width: 22px; height: 22px; font-size: 11px; cursor: pointer; }

.search-box {
  flex: 0 1 230px;
  max-width: 260px;
  min-width: 0;
  position: relative;
  display: flex;
  align-items: center;
}
.search-box input {
  width: 100%;
  height: 38px;
  padding: 0 36px 0 14px;
  border: 1px solid var(--glass-brd);
  border-radius: 20px;
  background: var(--glass-2);
  font-size: 14px;
  /* 表单控件的文字色不随 body 继承（UA 样式给的是 fieldtext，无 color-scheme 时即纯黑），
     必须显式声明，否则深色主题下「黑字 + 半透明深底」等于看不见。 */
  color: var(--text);
  outline: none;
  transition: border-color .15s, background .15s;
}
.search-box input::placeholder { color: var(--muted); }
.search-box input:focus { border-color: var(--primary); background: var(--glass-hover); }

/* 搜索框刻意用 type="search" 来躲开浏览器密码管理器的自动填充（见 index.html 注释）。
   代价是浏览器会塞进一个原生清除按钮 / 圆角装饰，和自绘的 ✕ 重叠，这里抹掉。 */
.search-box input[type="search"],
.ws-input-wrap input[type="search"] {
  -webkit-appearance: none;
  appearance: none;
}
.search-box input[type="search"]::-webkit-search-cancel-button,
.search-box input[type="search"]::-webkit-search-decoration,
.search-box input[type="search"]::-webkit-search-results-button,
.ws-input-wrap input[type="search"]::-webkit-search-cancel-button,
.ws-input-wrap input[type="search"]::-webkit-search-decoration,
.ws-input-wrap input[type="search"]::-webkit-search-results-button {
  -webkit-appearance: none;
  appearance: none;
  display: none;
}
.search-clear {
  position: absolute;
  right: 12px;
  color: var(--muted);
  cursor: pointer;
  display: none;
  font-size: 12px;
}
.search-clear.show { display: block; }
.admin-link {
  white-space: nowrap;
  padding: 8px 14px;
  background: var(--primary);
  color: #fff;
  border-radius: 20px;
  font-weight: 600;
  font-size: 13px;
  transition: opacity .15s;
}
.admin-link:hover { opacity: .9; }
.menu-toggle {
  display: none;
  border: none;
  background: transparent;
  font-size: 22px;
  cursor: pointer;
  color: var(--text);
}

/* 布局 */
.layout { display: flex; align-items: flex-start; }

/* 左侧导航栏 */
.sidebar {
  position: sticky;
  top: var(--top-h);
  width: var(--side-w);
  height: calc(100vh - var(--top-h));
  flex-shrink: 0;
  background: var(--glass-1);
  -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
  backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
  border-right: 1px solid var(--glass-brd-soft);
  overflow-y: auto;
  /* 底部不留内边距：侧栏最后一个元素是吸底的 .side-foot，底部留白交给它自己，
     否则吸底条下方会漏出 14px 侧栏背景、露出半个分类，像没贴到底。 */
  padding: 14px 10px 0;
}
.side-nav { display: flex; flex-direction: column; gap: 2px; }
.side-item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 12px;
  border-radius: 10px;
  color: var(--text);
  font-size: 14px;
  cursor: pointer;
  transition: background .12s, color .12s;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.side-item:hover { background: var(--glass-hover); }
.side-item.active { background: var(--primary-soft); color: var(--primary); font-weight: 600; }
/* 侧栏底部说明（页面左下角）：站点副标题 + 数据来源 + 版本号。
 * ⚠️ 这三行由 JS 逐行插入 <div>（见 app.js 的 renderSideFoot），不要再改成
 *    textContent + '\n' —— 那在 HTML 里是**无效**的：默认 white-space 会把
 *    换行折成空格，行能不能分开全看宽度巧合（窄了碰巧折行、宽了挤成一坨）。
 * 版本号行 .sf-ver 的字号与上面两行**严格一致**（同为 12px）：
 *    它只是脚注，不该比导航说明更抢眼；靠浅一点的透明度和上间距做层次，
 *    不用另设字号（另设字号会让它在真机上看着"跟旁边不是一套字"）。 */
.side-foot {
  /* 贴住侧栏可视区的底部：分类少时它就静静待在最下面（与从前一致），
     分类多、侧栏要滚动时它会**吸底常驻**，不会被滚出屏幕 ——
     左下角那行版本号必须扫一眼就看得到，藏在滚动区外等于没加
     （真机实测：默认 seed 数据的 26 个分类就把侧栏撑到 1491px，
      版本号落在视口外，1024 高度下根本看不见）。 */
  position: sticky;
  bottom: 0;
  /* 左右负 margin 抵消侧栏自身的 10px 内边距 → 吸底条**通栏**铺满整个侧栏宽度；
     padding 左侧 22px = 10(侧栏) - 10(负 margin) + 22，与上面分类项的文字左边界对齐。 */
  margin: 14px -10px 0;
  padding: 10px 22px 16px;
  font-size: 12px;
  line-height: 1.55;
  color: var(--muted);
  border-top: 1px solid var(--glass-brd-soft);
  /* 吸底时下方会有分类项从底下滚过，这里必须有**实色**底把它挡住。
     ⚠️ 别想着"侧栏是毛玻璃，这里也来一层 glass"：侧栏自己是 backdrop-filter
        元素，后代再写 backdrop-filter 不会对侧栏背后的内容生效 —— 实测结果是
        半透明（--glass-1 只有 .58 白）背后分类的文字清清楚楚地叠在版本号上，
        看着像重影。所以直接用不透明的面板色（浅色 #fff / 深色 #1a212b，
        两套主题都跟随）。 */
  background: var(--panel);
}
.side-foot .sf-ver {
  margin-top: 5px;
  font-size: 12px;
  opacity: .88;
  cursor: help;
}
/* 侧栏底部的三行说明同样是用户可填的文本（站点副标题里常见长网址），
   长词不折行会把侧栏撑出横向滚动条 —— 侧栏 overflow-y:auto 会连带
   把 overflow-x 算成 auto，于是分类栏底下多出一条横向滚动条。 */
.side-foot, .side-foot .sf-ver { overflow-wrap: anywhere; }
.sidebar-mask {
  display: none;
  position: fixed;
  inset: var(--top-h) 0 0 0;
  background: rgba(0,0,0,.35);
  z-index: 20;
}

/* 主内容区 */
.content {
  flex: 1;
  min-width: 0;
  padding: 22px 26px 40px;
}
/* 主页中心模块：站点名称（大标题）+ 站点描述（副标题），位于正文最上方。
   ⚠️ 2026-09-23 当天曾把这块整体搬进顶栏、删掉 .hero；同日应需求**恢复为两块并存**：
   顶栏一份、中心一份，文字与字号完全独立（后台分开设置），谁都不影响谁。
   字号只用变量、不在任何 @media 里再写死 —— 否则滑块调大后在窄屏会被断点压回去，
   表现为"后台改了字没变大"，非常难查。 */
.hero { margin-bottom: 18px; }
.hero h1 { margin: 0 0 4px; font-size: var(--hero-title-size); }
.hero p { margin: 0; color: var(--muted); font-size: var(--hero-sub-size); }
/* 站点名称 / 描述由用户在后台自由填写，可能是一长串没有空格的英文 ——
   这种词默认不在中间断开，会直接从盒子里溢出去、把整页撑出横向滚动条。
   允许任意位置换行，长词才会老实折到下一行。页脚同理（也常被填成一串网址）。 */
.hero h1, .hero p, .footer { overflow-wrap: anywhere; }

/* 锚点偏移用 --stick-h（顶栏 + 公告栏），不是 --top-h：公告栏显示时它同样吸在顶部，
   只让位顶栏的话分类标题会被公告栏压住。--stick-h 未定义时回落到 --top-h。 */
.section { margin-bottom: 30px; scroll-margin-top: calc(var(--stick-h, var(--top-h)) + 12px); }
/* 2/3 级小分类（.subsection）同样带 sec- 前缀 id、同样会被跳转与 scrollspy 命中，
   以前漏了 scroll-margin-top，跳到子分类时标题直接被顶栏吃掉。 */
.subsection { scroll-margin-top: calc(var(--stick-h, var(--top-h)) + 12px); }
.section-head {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0 0 14px;
  font-size: 17px;
  font-weight: 700;
  /* 同 .card 的道理：flex 子项默认 min-width:auto，长分类名会把标题行顶宽 */
  min-width: 0;
}
.section-head .sec-icon { font-size: 18px; }
.section-head .sec-count {
  font-size: 12px;
  font-weight: 500;
  color: var(--muted);
  background: rgba(123,135,148,.16);
  padding: 2px 8px;
  border-radius: 10px;
}
/* 分类名（图标与数量徽标之外的中间那一段）：
 * ① min-width:0 —— 允许被压缩到内容宽度以下，否则无空格的长英文分类名
 *    （如 "PhotovoltaicPowerStationSafetyRiskMonitoring"）不参与收缩，
 *    把整行顶宽 → 整页横向溢出、手机上右侧留白（2026-10-04 修的同类问题）。
 * ② overflow-wrap:anywhere —— 压缩后还得有地方落字：无空格长词默认不在中间断开，
 *    会直接从盒子里溢出去。允许任意位置断行，长名才能老实换到第二行。
 * ③ 图标与数量徽标不许被压：让名子去承担收缩，它们保持原样。 */
.section-head > .sec-name, .sub-head > .sec-name { min-width: 0; overflow-wrap: anywhere; }
.section-head > .sec-icon, .section-head > .sec-count,
.sub-head > .sec-icon, .sub-head > .sec-count { flex-shrink: 0; }

.cards {
  display: grid;
  /* 卡片密度随「卡片大小」档位走：小档位每行塞下更多张，整体观感才真的"更小"。
     各断点只给「基准最小宽度」，档位通过 --card-scale 倍率统一缩放，
     这样窄屏的响应式密度阶梯仍然保留（见下方 1440 / 1024 / 640 三个断点）。 */
  --card-scale: 1;
  --card-gap: 14px;
  grid-template-columns: repeat(auto-fill, minmax(calc(220px * var(--card-scale)), 1fr));
  gap: var(--card-gap);
}
.card {
  position: relative;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 14px;
  /* ⚠️ min-width: 0 不可删。grid 子项的默认值是 auto，等于「不得小于自身 min-content」，
     而下面 .name / .desc 是 white-space: nowrap，min-content 就是**整个站名的完整宽度**。
     于是一个长英文站名（如 "Nielsen Norman Group"）会把所在列顶宽，
     而 grid 的同一列所有轨道是等宽的 —— 一列变宽，整张网格跟着变宽，
     直接把文档撑过视口：手机上表现为「右侧一大块空白 + 底部横向滚动条」，
     用户往右一划，内容整体左移、左边被切掉。
     归零后轨道能正常收到 1fr 以内，长名走省略号（.name 已有 overflow/ellipsis）。 */
  min-width: 0;
  /* 毛玻璃卡片：半透明 + 上沿高光边。刻意不加 backdrop-filter ——
     卡片数量多（一屏几十张），逐张实时模糊会明显掉帧；
     背后是柔和渐变 + 半透明填充，肉眼看同样是玻璃质感。 */
  background: var(--glass-2);
  border: 1px solid var(--glass-brd);
  border-radius: var(--radius);
  box-shadow: var(--glass-shadow);
  transition: transform .12s, box-shadow .12s, border-color .12s, background .12s;
}
.card:hover {
  transform: translateY(-2px);
  background: var(--glass-hover);
  border-color: var(--primary);
  box-shadow: 0 8px 22px rgba(47,111,237,.16);
}
.card .thumb {
  width: 40px;
  height: 40px;
  border-radius: 10px;
  flex-shrink: 0;
  background: rgba(255, 255, 255, .55) center/cover no-repeat;
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 700;
  color: #fff;
  font-size: 16px;
  overflow: hidden;
}
.card .thumb img { width: 100%; height: 100%; object-fit: contain; }
.card .meta { min-width: 0; }
.card .name {
  font-weight: 600;
  font-size: 14px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.card .desc {
  color: var(--muted);
  font-size: 12px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* 收藏按钮 */
.fav-star {
  position: absolute;
  top: 5px;
  right: 7px;
  width: 24px;
  height: 24px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  cursor: pointer;
  font-size: 17px;
  line-height: 1;
  color: var(--muted);
  background: transparent;
  user-select: none;
  transition: color .12s, background .12s;
}
.fav-star:hover { background: var(--bg); color: #f0a020; }
.fav-star.on { color: #f0a020; }

/* 常用收藏入口与提示 */
/* 与下方「全部 / 分类」留出间距，降低误点概率（误点会切进收藏视图） */
.fav-entry { font-weight: 600; margin-bottom: 8px; }
.fav-entry.active { background: rgba(240,160,32,.18); color: #f0a020; }
/* 顶栏里的那一份（分类在顶部时侧栏整个不显示，顶栏是唯一入口，见 app.js buildTopNav）：
   · 清掉侧栏竖排才需要的下间距，否则它比同排的分类项矮 8px、对不齐；
   · 角标不要 margin-left:auto —— 顶栏项是 inline-flex，宽度由内容决定，
     那个 auto 把角标连同整项撑成怪异的间距。 */
.layout-top .top-nav a.fav-entry { margin-bottom: 0; }
.layout-top .top-nav a.fav-entry .fav-count { margin-left: 0; }
.fav-count {
  margin-left: auto;
  font-size: 12px;
  color: var(--muted);
  background: rgba(123,135,148,.16);
  border-radius: 10px;
  padding: 1px 8px;
  /* 默认隐藏，有收藏时由 JS 显式改成 inline-block（清成 '' 会回落到这里的 none） */
  display: none;
}
.fav-hint {
  margin-left: auto;
  font-size: 12px;
  color: var(--muted);
  font-weight: 400;
}
/* 收藏视图的出口按钮：收藏视图会整块替换正文，必须给用户一个显式的返回入口 */
.fav-back {
  flex: none;
  padding: 4px 12px;
  border: 1px solid var(--glass-brd);
  border-radius: 14px;
  background: var(--glass-2);
  color: var(--text);
  font-size: 12px;
  font-weight: 500;
  font-family: var(--font-family);
  cursor: pointer;
  transition: border-color .15s, color .15s, background .15s;
}
.fav-back:hover { border-color: var(--primary); color: var(--primary); background: var(--glass-hover); }
.empty-actions { margin-top: 16px; }
.empty-actions .btn { display: inline-block; }
.card.dragging { opacity: .4; }
.card.drop-over { box-shadow: inset 0 2px 0 var(--primary); }

/* ===== 悬浮滚动公告栏 =====
 * 位置：顶栏正下方、正文之上，随页面吸顶（sticky + top = --top-h，即顶栏实测高度，
 *       由 app.js 的 syncTopHeight() 维护 —— 顶栏在 search-above / 换行时会变高，
 *       写死 px 会与公告栏重叠）。
 * 内容：后台「首页设置 → 站点信息 → 滚动公告」改，一个字都不限长。
 *   单条文字超宽  → 横向无缝滚动（两份内容 + translateX(-50%)）
 *   多条消息      → 纵向轮播，每条 3.6 秒
 *   都够短        → 静态展示，不滚（避免一个短语来回晃）
 * 没有公告或用户本次会话关过 → 整条 hidden。 */
.announce-bar {
  position: sticky; top: var(--top-h, 0px); z-index: 29;
  display: flex; align-items: center; gap: 10px;
  margin-bottom: 16px; padding: 8px 12px;
  border: 1px solid var(--glass-brd); border-radius: 12px;
  background: var(--glass-2); color: var(--text);
  box-shadow: var(--glass-shadow);
  -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
  backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
  font-size: 13px; line-height: 1.6;
  overflow: hidden;
}
.announce-bar[hidden] { display: none; }
.announce-bar .ab-icon { flex: none; font-size: 15px; line-height: 1; }
.announce-bar .ab-viewport { flex: 1; min-width: 0; overflow: hidden; }
.announce-bar .ab-track { white-space: nowrap; }
.announce-bar .ab-text { display: inline-block; }
/* 横向滚动：两份内容，每份右侧留间距；动画走容器一半宽度实现无缝 */
.announce-bar .ab-track.marquee { display: inline-block; }
.announce-bar .ab-track.marquee .ab-text { padding-right: 4em; }
.announce-bar .ab-track.marquee.play { animation: ab-marquee linear infinite; }
.announce-bar .ab-track.marquee.play:hover, .announce-bar:hover .ab-track.marquee.play { animation-play-state: paused; }
@keyframes ab-marquee {
  from { transform: translateX(0); }
  to   { transform: translateX(-50%); }
}
/* 纵向轮播：切换时淡入上移 */
.announce-bar .ab-track.rotator .ab-text { animation: ab-rise .45s ease; }
@keyframes ab-rise {
  from { opacity: 0; transform: translateY(60%); }
  to   { opacity: 1; transform: translateY(0); }
}
/* 关闭钮：暗色 / 浅色都保持可读 */
.announce-bar .ab-close {
  flex: none; width: 22px; height: 22px; padding: 0; border: 0; border-radius: 6px;
  background: transparent; color: var(--muted); cursor: pointer; font-size: 12px; line-height: 1;
}
.announce-bar .ab-close:hover { color: var(--text); background: var(--glass-hover); }
/* 窄屏：图标收缩、字号略小，公告栏只占一行 */
@media (max-width: 640px) {
  .announce-bar { font-size: 12.5px; padding: 7px 10px; margin-bottom: 12px; }
}

/* 顶部「常用收藏」区块（常驻在 hero 与第一个分类之间）。
   结构与分类区块完全同款（.section / .section-head / .cards 全复用），
   这里只补三处：与下方分类的分隔、空态说明、以及「查看全部」按钮的位置。 */
/* 撤掉区块时用的是 hidden 属性。hidden 只是 UA 样式表里的 [hidden]{display:none}，
   作者样式里任何一条 display 都能盖掉它（本项目被这种反向覆盖坑过：@supports 降级块
   曾把新配色改回旧色）。这里显式再写一遍兜住。 */
.fav-top[hidden] { display: none; }
.fav-top-sec {
  margin-bottom: 22px;
  padding-bottom: 16px;
  border-bottom: 1px dashed var(--border);
}
.fav-top-more { margin-left: 10px; }
.fav-top-empty {
  margin: 0;
  padding: 12px 14px;
  border: 1px dashed var(--border);
  border-radius: var(--radius);
  background: var(--glass-2);
  color: var(--muted);
  font-size: 13px;
  line-height: 1.7;
}

/* ===== 系统设置：布局变体 ===== */
/* 分类位置：顶部 */
.layout-top .sidebar { display: none; }
/* ⚠️ 这一条不能删：顶部导航是 JS 用 layout.insertBefore(nav, .content) 插进 .layout 的，
   而 .layout 是 display:flex 的**横排**容器 —— 不加这条，导航与正文会并排抢宽度：
   导航按 max-content 撑开（26 个分类 ≈ 1357px），正文被压到 68px，
   于是整站内容挤成右侧一条竖线、每行一个字，还把文档撑出横向滚动条。
   （2026-10-04 由新增的横向溢出测试发现，属长期潜伏、只在「分类在顶部」时才现形的布局崩坏。）
   改成纵向堆叠后，导航占满一行、正文回到它下面，也就是这个布局本来的设计意图。 */
.layout-top .layout { flex-direction: column; align-items: stretch; }
.layout-top .top-nav {
  display: flex; padding: 12px 26px; gap: 8px;
  background: var(--glass-1);
  -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
  backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
  border-bottom: 1px solid var(--glass-brd-soft);
}
.layout-top .top-nav a {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 7px 14px; border-radius: 18px; font-size: 13px; color: var(--text);
  transition: background .12s, color .12s; flex-shrink: 0;
}
.layout-top .top-nav a:hover { background: var(--glass-hover); }
.layout-top .top-nav a.active { background: var(--primary-soft); color: var(--primary); font-weight: 600; }
/* 单行模式：横向滚动 */
.layout-top .top-nav { flex-wrap: nowrap; overflow-x: auto; scrollbar-width: thin; }
.layout-top .top-nav.multi { flex-wrap: wrap; overflow-x: visible; }

/* 搜索框在 logo 上方 */
.search-above .topbar { flex-wrap: wrap; }
.search-above .search-box { order: -1; flex-basis: 100%; max-width: 100%; margin: 0 0 8px; }

/* 卡片大小/列数 */
.cards.cols-2 { grid-template-columns: repeat(2, 1fr); }
.cards.cols-3 { grid-template-columns: repeat(3, 1fr); }
.cards.cols-4 { grid-template-columns: repeat(4, 1fr); }
.cards.cols-5 { grid-template-columns: repeat(5, 1fr); }
.cards.cols-6 { grid-template-columns: repeat(6, 1fr); }
/* ===== 卡片大小：5 档，由后台的滑块调节（1 极小 / 2 小号 / 3 中号 / 4 大号 / 5 超大） =====
   前后台约定：`site.cardSize` 存数字 1~5，前台 cardClasses() 输出 size-1..size-5。
   历史数据里是 small/medium/large，两端都做同一套兼容映射
   （small → 3 中号，即原来的「紧凑」；medium → 4；large → 5）。
   每档改两样东西：
     · --card-scale：卡片网格最小宽度的倍率（各断点的基准值见 1440/1024/640 三个媒体查询），
       让"更小的卡片"不只是内边距变窄，而是一行真的能多放几张；
     · 卡片内部尺寸（内边距 / 图标 / 标题字号 / 收藏星 / 访问量角标）。
   特异度提醒：`.cards.size-N .card .thumb`(0,4,0) 高于窄屏断点里的 `.card .thumb`(0,2,0)，
   所以档位在手机上同样生效 —— 这是刻意的。 */
.cards.size-1 { --card-scale: 0.78; --card-gap: 10px; }
.cards.size-1 .card { padding: 6px; gap: 5px; }
.cards.size-1 .card .thumb { width: 22px; height: 22px; border-radius: 6px; font-size: 11px; }
.cards.size-1 .card .name { font-size: 11.5px; }
.cards.size-1 .card .desc { font-size: 10px; }
.cards.size-1 .card .fav-star { top: 2px; right: 3px; width: 16px; height: 16px; font-size: 12px; }
.cards.size-1 .card .visits-tag { font-size: 9px; bottom: 2px; right: 3px; }

.cards.size-2 { --card-scale: 0.88; --card-gap: 12px; }
.cards.size-2 .card { padding: 8px; gap: 6px; }
.cards.size-2 .card .thumb { width: 27px; height: 27px; border-radius: 7px; font-size: 13px; }
.cards.size-2 .card .name { font-size: 12.5px; }
.cards.size-2 .card .desc { font-size: 10.5px; }
.cards.size-2 .card .fav-star { top: 3px; right: 4px; width: 19px; height: 19px; font-size: 13px; }
.cards.size-2 .card .visits-tag { font-size: 9.5px; bottom: 3px; right: 4px; }

/* 中号：与原「紧凑」完全一致（老数据里的 small 映射到这一档） */
.cards.size-3 { --card-scale: 1; --card-gap: 14px; }
.cards.size-3 .card { padding: 10px; gap: 8px; }
.cards.size-3 .card .thumb { width: 32px; height: 32px; border-radius: 8px; font-size: 14px; }
.cards.size-3 .card .name { font-size: 13px; }
.cards.size-3 .card .desc { font-size: 11px; }
.cards.size-3 .card .fav-star { top: 3px; right: 4px; width: 22px; height: 22px; font-size: 15px; }
.cards.size-3 .card .visits-tag { font-size: 10px; bottom: 3px; right: 6px; }

/* 大号：等于改动前的默认外观（老数据里的 medium 映射到这一档） */
.cards.size-4 { --card-scale: 1.08; --card-gap: 14px; }
.cards.size-4 .card { padding: 14px; gap: 12px; }
.cards.size-4 .card .thumb { width: 40px; height: 40px; border-radius: 10px; font-size: 16px; }
.cards.size-4 .card .name { font-size: 14px; }
.cards.size-4 .card .desc { font-size: 12px; }
.cards.size-4 .card .fav-star { top: 5px; right: 7px; width: 24px; height: 24px; font-size: 17px; }
.cards.size-4 .card .visits-tag { font-size: 11px; bottom: 5px; right: 8px; }

/* 超大：与原「宽松」一致（老数据里的 large 映射到这一档） */
.cards.size-5 { --card-scale: 1.18; --card-gap: 16px; }
.cards.size-5 .card { padding: 18px; gap: 14px; }
.cards.size-5 .card .thumb { width: 48px; height: 48px; border-radius: 12px; font-size: 18px; }
.cards.size-5 .card .name { font-size: 15px; }
.cards.size-5 .card .desc { font-size: 13px; }
.cards.size-5 .card .fav-star { top: 7px; right: 9px; width: 26px; height: 26px; font-size: 19px; }
.cards.size-5 .card .visits-tag { font-size: 11.5px; bottom: 6px; right: 9px; }
.cards.no-shadow .card { box-shadow: none; }
.card .visits-tag {
  position: absolute; bottom: 5px; right: 8px;
  font-size: 11px; color: var(--muted); font-variant-numeric: tabular-nums;
}

/* 壁纸：固定背景层 */
/* 关键：html, body 默认有「不透明」背景（见顶部 html, body 规则）。
   负 z-index 的定位元素绘制在父元素背景「之下」，若不给 body 去掉背景色，
   壁纸层与遮罩层会被 body 自己的背景整个盖住 —— 表现为后台设了壁纸、主页毫无变化。
   底层仍由 html 的 --bg 兜底，壁纸透明或未加载时视觉与原来一致。 */
body.wallpaper { position: relative; background: transparent; }
body.wallpaper::before {
  content: ''; position: fixed; inset: 0; z-index: -1;
  background: var(--wp-bg, none); background-size: cover; background-position: center;
  filter: blur(var(--wp-blur, 0px));
}
body.wallpaper::after {
  content: ''; position: fixed; inset: 0; z-index: -1;
  background: rgba(255,255,255,var(--wp-opacity, 0.08));
}
[data-theme="dark"] body.wallpaper::after { background: rgba(0,0,0,var(--wp-opacity, 0.08)); }
/* 顶栏 / 侧栏 / 顶部导航本身就是毛玻璃层（见 --glass-1）：半透明 + backdrop-filter，
   壁纸或正文都能从背后透出并被模糊，所以这里不再需要单独的「壁纸模式」覆盖。 */

.empty {
  color: var(--muted);
  text-align: center;
  padding: 60px 0;
  font-size: 15px;
}

/* 加载失败 / 超时的说明面板（.empty 的加强版，由 app.js 的 loadingPanel() 渲染）。
   存在的意义很实在：以前接口不响应时页面就是一片空白，用户分不清是「坏了」
   还是「本来就没数据」。宁可难看一点，也不能让人对着空白页猜。 */
.load-fail {
  max-width: 580px;
  margin: 0 auto;
  text-align: left;
  line-height: 1.8;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 20px 22px;
}
.load-fail .lf-title { display: block; font-size: 16px; color: var(--text); margin-bottom: 8px; }
.load-fail .lf-line { font-size: 13.5px; color: var(--muted); word-break: break-word; }
.load-fail .lf-retry { margin-top: 14px; }

.footer {
  margin-top: 30px;
  padding-top: 18px;
  border-top: 1px solid var(--border);
  color: var(--muted);
  font-size: 12px;
  text-align: center;
}

/* ===== 搜索引擎搜索（已上移至顶栏内） ===== */
.ws-input-wrap {
  position: relative; flex: 1; min-width: 0;
  display: flex; align-items: center; gap: 8px;
  padding: 0 12px;
  background: var(--glass-2);
  border: 1px solid var(--glass-brd);
  border-radius: 24px;
  transition: border-color .15s, box-shadow .15s;
}
.ws-input-wrap:focus-within {
  border-color: var(--primary);
  box-shadow: 0 0 0 3px var(--primary-soft);
}
.ws-badge {
  width: 24px; height: 24px; border-radius: 8px; flex-shrink: 0;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 12px; font-weight: 700; color: #fff;
}
.ws-input-wrap input {
  flex: 1; min-width: 0; height: 44px;
  border: none; outline: none; background: transparent;
  font-size: 14px; color: var(--text); font-family: inherit;
}
.ws-input-wrap input::placeholder { color: var(--muted); }
.ws-clear {
  border: none; background: transparent; color: var(--muted);
  cursor: pointer; font-size: 12px; padding: 4px 2px; display: none; flex-shrink: 0;
}
.ws-clear.show { display: block; }
.ws-go {
  flex-shrink: 0; height: 46px; padding: 0 22px;
  border: none; border-radius: 24px;
  background: var(--primary); color: #fff;
  font-weight: 600; font-size: 14px; cursor: pointer;
  transition: opacity .15s;
}
.ws-go:hover { opacity: .9; }
/* 引擎列表：顶栏内的下拉面板，点左侧徽标展开 / 收起 */
.tb-searches .ws-engines {
  position: absolute;
  top: calc(100% + 8px);
  left: 0;
  right: 0;
  z-index: 40;
  display: none;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 0;
  padding: 8px;
  background: var(--glass-3);
  -webkit-backdrop-filter: blur(22px) saturate(var(--glass-saturate));
  backdrop-filter: blur(22px) saturate(var(--glass-saturate));
  border: 1px solid var(--glass-brd);
  border-radius: 12px;
  box-shadow: var(--glass-shadow);
  max-height: 240px;
  overflow-y: auto;
}
.tb-searches .ws-engines.open { display: flex; }
.ws-engine {
  border: 1px solid var(--glass-brd); background: var(--glass-2); color: var(--text);
  padding: 5px 12px; border-radius: 16px; font-size: 12px; cursor: pointer;
  white-space: nowrap; transition: background .12s, color .12s, border-color .12s;
}
.ws-engine:hover { border-color: var(--primary); color: var(--primary); }
.ws-engine.on {
  background: var(--primary); border-color: var(--primary);
  color: #fff; font-weight: 600;
}

/* ===== 响应式：大屏 / 平板 / 手机 / 小屏手机 ===== */

/* 大屏（≥1440px） */
@media (min-width: 1440px) {
  .content { padding: 26px 34px 48px; }
  .cards { grid-template-columns: repeat(auto-fill, minmax(calc(240px * var(--card-scale)), 1fr)); }
}

/* 平板（≤1024px）：侧栏收进抽屉，卡片列变密 */
@media (max-width: 1024px) {
  .menu-toggle { display: block; }
  .sidebar {
    position: fixed;
    left: 0;
    top: var(--top-h);
    height: calc(100vh - var(--top-h));
    z-index: 25;
    transform: translateX(-100%);
    transition: transform .2s;
    box-shadow: var(--shadow);
  }
  .sidebar.open { transform: translateX(0); }
  .sidebar-mask.show { display: block; }
  .content { padding: 20px 20px 36px; }
  .cards { grid-template-columns: repeat(auto-fill, minmax(calc(180px * var(--card-scale)), 1fr)); }
  /* 平板下解除后台设置的固定多列，交给自动填充 */
  .cards.cols-4, .cards.cols-5, .cards.cols-6 {
    grid-template-columns: repeat(auto-fill, minmax(calc(180px * var(--card-scale)), 1fr));
  }
  /* 平板：顶栏收窄两个搜索框，按钮只留图标 */
  .ws-wrap { flex: 1 1 220px; max-width: 320px; }
  .search-box { flex: 0 1 160px; max-width: 190px; }
  .ws-go-txt { display: none; }
  .tb-searches .ws-go { padding: 0 12px; }
}

/* 手机（≤640px） */
@media (max-width: 640px) {
  /* 顶栏两行：第一行品牌 + 主题 + 收录，第二行网页搜索 + 站内搜索 */
  :root { --top-h: 104px; }
  .topbar { gap: 8px; padding: 8px 12px; }
  /* 站名 / 描述字号由后台滑块控制，这里不再降档 —— 用户设了多大就是多大，
     只保证不撑破顶栏：超出部分走省略号（见上面 .brand #siteTitle 的 overflow）。
     图标也不再单独降档，它按 --title-size 等比缩放（见 .brand #siteLogo）。 */
  .brand { gap: 6px; min-width: 0; }
  /* 省略号已由 .brand #siteTitle 统一提供，这里不再重复声明 */
  .tb-searches { flex: 1 1 100%; order: 10; gap: 8px; }
  .ws-wrap { flex: 1 1 auto; max-width: none; }
  .search-box { flex: 1 1 auto; max-width: none; min-width: 0; }
  .search-box input { height: 34px; font-size: 13px; padding: 0 32px 0 12px; }
  .search-clear { right: 10px; }
  .theme-toggle { width: 34px; height: 34px; font-size: 15px; }
  .admin-link { padding: 6px 10px; font-size: 12px; }
  .al-txt { display: none; }

  /* 手机：中心模块居中（与改版前一致）。
     ⚠️ 只恢复居中，**不要再写 font-size** —— 中心字号由后台滑块控制，
     这里若降档，用户把中心名称拖到 40px 后在手机上会被压回 20px，
     表现为「后台改了字没变大」，是极难查的一类问题。 */
  .hero { text-align: center; }

  .content { padding: 14px 12px 30px; }

  .ws-input-wrap { padding: 0 10px; }
  .tb-searches .ws-input-wrap input { height: 36px; font-size: 16px; } /* 16px 防 iOS 聚焦缩放 */
  .tb-searches .ws-go { height: 36px; padding: 0 12px; }
  .ws-go-txt { display: none; }
  /* 引擎列表收进下拉，点徽标展开 */
  .tb-searches .ws-badge { width: 30px; height: 30px; border-radius: 9px; font-size: 13px; }
  .ws-badge::after { content: '▾'; font-size: 9px; margin-left: 2px; opacity: .85; }

  .cards { grid-template-columns: repeat(auto-fill, minmax(calc(140px * var(--card-scale)), 1fr)); gap: 10px; }
  .cards.cols-3, .cards.cols-4, .cards.cols-5, .cards.cols-6 {
    grid-template-columns: repeat(auto-fill, minmax(calc(140px * var(--card-scale)), 1fr));
  }
  .card { padding: 10px; gap: 8px; }
  .card .thumb { width: 34px; height: 34px; border-radius: 8px; font-size: 14px; }
  .card .name { font-size: 13px; }
  .card .desc { font-size: 11px; }
  .fav-star { top: 3px; right: 4px; width: 22px; height: 22px; font-size: 15px; }
  .card .visits-tag { font-size: 10px; bottom: 3px; right: 6px; }

  .section { margin-bottom: 22px; }
  .section-head { font-size: 15px; margin-bottom: 10px; }
  .layout-top .top-nav { padding: 10px 12px; }
  .footer { font-size: 11px; margin-top: 22px; }
  .empty { padding: 40px 0; font-size: 14px; }
}

/* 小屏手机（≤400px）：进一步收敛，保证双列卡片与顶栏不溢出 */
@media (max-width: 400px) {
  /* 顶栏极窄时收起「站点描述」，保住「站点名称」本身。
     2026-09-23 之前这里藏的是 #siteTitle —— 那时名称只是顶栏一行小字、描述在
     正文中间，藏名称无伤大雅；现在名称与描述都在顶栏（见 .brand），
     再藏名称就等于把品牌整个抹掉，所以改成让次要的描述先让位。 */
  #siteDesc { display: none; }
  .cards { grid-template-columns: 1fr 1fr; }
  .cards.cols-2, .cards.cols-3, .cards.cols-4, .cards.cols-5, .cards.cols-6 {
    grid-template-columns: 1fr 1fr;
  }
  .card .desc { display: none; }
  .theme-toggle { width: 32px; height: 32px; }
}

/* 主题切换按钮 */
.theme-toggle {
  border: 1px solid var(--glass-brd);
  background: var(--glass-2);
  color: var(--text);
  width: 38px; height: 38px;
  border-radius: 50%;
  cursor: pointer;
  font-size: 16px;
  display: inline-flex; align-items: center; justify-content: center;
  transition: background .15s, border-color .15s;
}
.theme-toggle:hover { background: var(--glass-hover); border-color: var(--primary); }

/* 深色主题 */
[data-theme="dark"] {
  --bg: #0f1419;
  --panel: #1a212b;
  --text: #e6edf3;
  --muted: #8b98a5;
  --primary: #4f8cff;
  --primary-deep: #2f6fed;
  --primary-soft: #1f2d44;
  --border: #2a3340;
  --shadow: 0 2px 12px rgba(0,0,0,.4);
  --panel-translucent: rgba(26, 33, 43, .78);

  /* 深色毛玻璃：底色压暗，玻璃仍然透出背后的渐变光斑 */
  --page-bg:
    radial-gradient(1100px 620px at 8% -8%, #17273f 0%, rgba(23, 39, 63, 0) 62%),
    radial-gradient(900px 520px at 102% 2%, #2b1c36 0%, rgba(43, 28, 54, 0) 58%),
    radial-gradient(1000px 700px at 48% 112%, #0f2e2c 0%, rgba(15, 46, 44, 0) 62%),
    linear-gradient(180deg, #10161d 0%, #0c1015 100%);
  --glass-1: rgba(26, 33, 43, .62);
  --glass-2: rgba(30, 38, 50, .58);
  --glass-3: rgba(26, 33, 43, .86);
  --glass-hover: rgba(46, 58, 76, .78);
  --glass-brd: rgba(255, 255, 255, .10);
  --glass-brd-soft: rgba(255, 255, 255, .08);
  --glass-shadow: 0 4px 18px rgba(0, 0, 0, .38);
}

/* 后台登录遮罩 */
.login-mask {
  position: fixed; inset: 0; z-index: 100;
  display: flex; align-items: center; justify-content: center;
  background: rgba(0,0,0,.5);
}
.login-box {
  background: var(--glass-3);
  -webkit-backdrop-filter: blur(24px) saturate(var(--glass-saturate));
  backdrop-filter: blur(24px) saturate(var(--glass-saturate));
  border: 1px solid var(--glass-brd);
  border-radius: 14px; padding: 24px; width: 300px; box-shadow: 0 16px 44px rgba(0,0,0,.22);
}
.login-box h3 { margin: 0 0 6px; font-size: 16px; }
.login-box p { margin: 0 0 14px; color: var(--muted); font-size: 12px; }
.login-box input { margin-bottom: 10px; }
.login-box .err { color: #e8543f; font-size: 12px; margin: 6px 0 0; min-height: 14px; }

/* 拖拽排序 */
.cat-item, table.grid tbody tr { cursor: default; }
.cat-item[draggable="true"] { cursor: grab; }
table.grid tbody tr[draggable="true"] { cursor: grab; }
.cat-item.dragging, table.grid tbody tr.dragging { opacity: .4; }
.cat-item.drop-over, table.grid tbody tr.drop-over { box-shadow: inset 0 2px 0 var(--primary); }
.drag-handle { color: var(--muted); cursor: grab; user-select: none; font-size: 13px; }

/* 图标单元格：输入框 + 上传/自动匹配按钮 + 预览 */
.icon-cell { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.icon-cell .icon-row { display: flex; gap: 4px; align-items: center; }
.icon-cell input { padding: 5px 7px; font-size: 12px; width: 100%; min-width: 0; box-sizing: border-box; }
.icon-btns { display: flex; gap: 3px; }
.icon-btns .mini {
  border: 1px solid var(--border); background: var(--bg); color: var(--text);
  border-radius: 6px; cursor: pointer; font-size: 12px; padding: 2px 6px; line-height: 1.4;
}
.icon-btns .mini:hover { border-color: var(--primary); color: var(--primary); }
.col-visits { text-align: center; color: var(--muted); font-variant-numeric: tabular-nums; }
#statHint b { color: var(--primary); }

/* 分类编辑模态框 */
.modal-mask {
  position: fixed; inset: 0; z-index: 90;
  display: flex; align-items: center; justify-content: center;
  background: rgba(0,0,0,.5);
}
.modal-mask .modal {
  background: var(--panel); border: 1px solid var(--border); border-radius: 14px;
  padding: 22px; width: 360px; max-width: 92vw; box-shadow: var(--shadow);
}
.modal-mask .modal h3 { margin: 0 0 14px; font-size: 16px; }
.modal-mask .modal label.hint { display: block; margin-bottom: 4px; }
.modal-mask .modal input[type=text] { margin-bottom: 6px; }
.modal-mask .modal .thumb-sm { width: 28px; height: 28px; border-radius: 7px; object-fit: contain; border: 1px solid var(--border); }

/* ===== 侧栏小功能卡片：天气 / 万年历 ===== */
.mini-panel { display: flex; flex-direction: column; gap: 8px; padding: 10px 12px 4px; }
.mini-card { background: var(--glass-2); border: 1px solid var(--glass-brd); border-radius: 12px; padding: 10px 12px; }
.mini-loading { font-size: 13px; color: var(--text); opacity: .7; }
.mini-weather .mw-row { display: flex; align-items: center; gap: 8px; }
.mini-weather .mw-icon { font-size: 24px; line-height: 1; }
.mini-weather .mw-temp { font-size: 22px; font-weight: 700; color: var(--text); }
.mini-weather .mw-meta { display: flex; justify-content: space-between; gap: 8px; margin-top: 2px; font-size: 12px; color: var(--text); opacity: .8; }
.mini-weather .mw-city { font-weight: 600; }
.mini-calendar .mc-solar { font-size: 14px; font-weight: 600; color: var(--text); }
.mini-calendar .mc-sub { font-size: 12px; color: var(--text); opacity: .8; margin-top: 2px; }
.mini-calendar .mc-greet { font-size: 12px; color: var(--primary); margin-top: 4px; font-weight: 600; }

/* ===== 主页分类层级（1~3 级）；后台分类树样式见 admin.html ===== */
/* 主页侧栏：同一级严格左对齐成竖直线（级差 18px → 26 / 44 / 62 / 80）。
   折叠箭头改为绝对定位、脱离 flex 流 —— 否则"有子分类的项"会被 arrow(14px)+gap(10px)
   推右 24px，同级之间就对不齐了（这是此前错位的根因）。 */
#sideNav .side-item { position: relative; padding-left: 26px; }
#sideNav .side-item.cat.lv2 { padding-left: 44px; font-size: 13.5px; }
#sideNav .side-item.cat.lv3 { padding-left: 62px; font-size: 13px; }
#sideNav .side-item.cat.lv4 { padding-left: 80px; font-size: 12.5px; }

/* 主页侧栏：折叠箭头（绝对定位到图标左侧的固定槽位，不占文档流宽度） */
#sideNav .nav-arrow {
  position: absolute;
  top: 50%;
  left: 6px;
  transform: translateY(-50%);
  width: 14px;
  text-align: center;
  color: var(--muted);
  font-size: 10px;
  line-height: 1;
  transition: transform .15s ease;
}
#sideNav .side-item.cat.lv2 .nav-arrow { left: 24px; }
#sideNav .side-item.cat.lv3 .nav-arrow { left: 42px; }
#sideNav .side-item.cat.lv4 .nav-arrow { left: 60px; }
#sideNav .nav-arrow:hover { color: var(--primary); }
/* 旋转时保留垂直居中，否则绝对定位后箭头会上下跳动 */
#sideNav .side-item.collapsed .nav-arrow { transform: translateY(-50%) rotate(-90deg); }

/* 主区：2/3 级子分组 */
.subsection { margin-top: 20px; }
.subsection.lv3 { margin-left: 8px; padding-left: 14px; border-left: 2px solid var(--border); }
.sub-head {
  display: flex; align-items: center; gap: 8px;
  margin: 0 0 10px;
  font-size: 15px; font-weight: 600; color: var(--text);
}
.sub-head.lv3 { font-size: 14px; color: var(--muted); }
.sub-head .sec-icon { flex: none; display: inline-flex; align-items: center; }
.sub-head .sec-count {
  font-size: 11px; font-weight: 500; color: var(--muted);
  background: rgba(123,135,148,.16); padding: 1px 7px; border-radius: 10px;
}
.subsection .cards { margin-bottom: 4px; }

/* 顶部导航：子级略缩 */
.top-nav-item.lv2 { font-size: 12.5px; opacity: .92; }
.top-nav-item.lv3 { font-size: 11.5px; opacity: .82; }

/* ===== 账户体系：顶栏入口 + 登录/注册/用户中心弹窗 ===== */
/* 放在文件末尾，注意自带响应式规则，避免被上方 @media 覆盖 */

.acct-btn {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 6px 12px; border: 1px solid var(--glass-brd); border-radius: 18px;
  background: var(--glass-2); color: var(--text); cursor: pointer;
  font-size: 12.5px; font-family: var(--font-family); white-space: nowrap;
  transition: border-color .15s, color .15s, background .15s;
}
.acct-btn:hover { border-color: var(--primary); color: var(--primary); background: var(--glass-hover); }
.acct-btn.logged { border-color: var(--primary); color: var(--primary); background: var(--primary-soft); }

/* 顶栏「＋ 申请收录」：与登录按钮同尺寸并排，但用主色描边 + 主色文字，
   让它看起来是「可以做一件事」而不是又一个栏目链接。 */
.apply-btn {
  border-color: var(--primary);
  color: var(--primary);
  background: var(--primary-soft);
}
.apply-btn:hover { background: var(--glass-hover); }

/* 弹窗里的文字按钮（登录弹窗的申请入口 / 申请弹窗的「去登录」）：
   不像按钮、也不抢 tab 的视觉层级，但 hover 有下划线，看得出可点。 */
.apply-link {
  display: inline-block; width: 100%; margin: 2px 0 0; padding: 4px 2px;
  border: 0; background: none; cursor: pointer; text-align: left;
  color: var(--primary); font-size: 12.5px; font-family: var(--font-family);
}
.apply-link:hover { text-decoration: underline; }

/* 申请弹窗底部的登录态说明：未登录 = 灰色说明，已登录 = 绿色一行 */
.apply-hint { margin: 8px 0 0; font-size: 12px; line-height: 1.6; color: var(--muted); }
.apply-hint.ok { color: #1a9d5a; }

/* 弹窗骨架 */
[hidden] { display: none !important; }
.modal-mask {
  position: fixed; inset: 0; z-index: 1200;
  display: flex; align-items: center; justify-content: center;
  padding: 18px; background: rgba(15, 23, 32, .45);
  -webkit-backdrop-filter: blur(2px); backdrop-filter: blur(2px);
}
.modal {
  position: relative; width: 100%; max-width: 380px; max-height: 88vh; overflow: auto;
  background: var(--glass-3); color: var(--text);
  -webkit-backdrop-filter: blur(24px) saturate(var(--glass-saturate));
  backdrop-filter: blur(24px) saturate(var(--glass-saturate));
  border: 1px solid var(--glass-brd); border-radius: var(--radius);
  box-shadow: 0 16px 44px rgba(0, 0, 0, .22);
  padding: 20px; font-family: var(--font-family); font-size: 14px;
}
.modal.wide { max-width: 520px; }
.modal-x {
  position: absolute; top: 10px; right: 10px;
  width: 28px; height: 28px; line-height: 1;
  border: none; border-radius: 50%; background: transparent;
  color: var(--muted); font-size: 15px; cursor: pointer;
}
.modal-x:hover { background: var(--bg); color: var(--text); }
.modal-title { margin: 0 0 6px; font-size: 16px; }
.modal-title .who { font-size: 12.5px; font-weight: 400; color: var(--muted); margin-left: 6px; }
.modal-actions { display: flex; gap: 8px; margin: 10px 0 4px; flex-wrap: wrap; }
.sub { margin: 18px 0 8px; font-size: 13.5px; color: var(--text); opacity: .9; }

/* 选项卡 */
.modal-tabs { display: flex; gap: 6px; margin-bottom: 14px; }
.mtab {
  flex: 1; padding: 7px 0; border: 1px solid var(--border); border-radius: 9px;
  background: var(--bg); color: var(--muted); cursor: pointer;
  font-size: 13.5px; font-family: var(--font-family);
}
.mtab.active { background: var(--primary-soft); border-color: var(--primary); color: var(--primary); font-weight: 600; }

/* 表单 */
.mpane.hidden { display: none; }
.fl { display: block; margin: 10px 0 4px; font-size: 12.5px; color: var(--muted); }
.modal input[type="text"], .modal input[type="password"], .modal select {
  width: 100%; box-sizing: border-box;
  padding: 9px 11px; border: 1px solid var(--border); border-radius: 9px;
  background: var(--bg); color: var(--text); font-size: 13.5px; font-family: var(--font-family);
}
.modal input:focus, .modal select:focus { outline: none; border-color: var(--primary); }
.chk { display: flex; align-items: center; gap: 6px; margin: 12px 0; font-size: 13px; color: var(--muted); }
.hint { font-size: 12px; color: var(--muted); margin: 8px 0 0; line-height: 1.6; }
.modal-msg { min-height: 18px; margin: 8px 0 0; font-size: 12.5px; color: var(--muted); line-height: 1.5; }
/* 用户中心里的「收藏同步状态」一行（登录后才有，未登录整行 hidden） */
.fav-sync-note {
  margin: 4px 0 10px;
  padding: 8px 10px;
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--muted);
  background: var(--primary-soft);
  border-radius: 8px;
}
.modal-msg.err { color: #d93025; }
.modal-msg.ok { color: #1a9d5a; }

/* 按钮 */
.btn {
  padding: 9px 14px; border: 1px solid var(--border); border-radius: 9px;
  background: var(--panel); color: var(--text); cursor: pointer;
  font-size: 13.5px; font-family: var(--font-family);
}
.btn:hover { border-color: var(--primary); color: var(--primary); }
.btn.primary { background: var(--primary); border-color: var(--primary); color: #fff; }
.btn.primary:hover { opacity: .92; color: #fff; }
.btn.block { display: block; width: 100%; margin-top: 12px; }
.btn.sm { padding: 6px 11px; font-size: 12.5px; }
.btn.ghost { background: transparent; }
.btn[disabled] { opacity: .55; cursor: not-allowed; }

/* 申请表单与列表 */
.apply-form { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.apply-form input, .apply-form select { grid-column: span 1; }
.apply-form #appUrl { grid-column: span 2; }
.apply-form .btn { grid-column: span 2; }
.apply-list { display: flex; flex-direction: column; gap: 8px; }
.apply-item {
  display: flex; align-items: flex-start; gap: 10px;
  padding: 10px 12px; border: 1px solid var(--border); border-radius: 10px;
  background: var(--bg);
}
.apply-item .ai-main { flex: 1; min-width: 0; }
.apply-item .ai-name { font-size: 13.5px; font-weight: 600; }
.apply-item .ai-url { font-size: 12px; color: var(--muted); word-break: break-all; margin-top: 2px; }
.apply-item .ai-note { font-size: 12px; color: var(--muted); margin-top: 4px; }
.badge {
  flex: none; padding: 3px 9px; border-radius: 11px; font-size: 11.5px;
  border: 1px solid var(--border); color: var(--muted); background: var(--panel); white-space: nowrap;
}
.badge.pending { border-color: #e0a800; color: #b8860b; background: rgba(224, 168, 0, .1); }
.badge.active { border-color: #1a9d5a; color: #1a9d5a; background: rgba(26, 157, 90, .1); }
.badge.rejected { border-color: #d93025; color: #d93025; background: rgba(217, 48, 37, .08); }
.badge.disabled { border-color: var(--muted); }

@media (max-width: 640px) {
  .acct-btn .acct-txt { display: none; }        /* 窄屏只留图标 */
  .apply-form { grid-template-columns: 1fr; }
  .apply-form #appUrl, .apply-form .btn { grid-column: span 1; }
  .modal { padding: 16px; max-height: 92vh; }
  .fav-hint { display: none; }                  /* 触屏不能拖拽排序，提示无意义且挤占标题行 */
}

/* ===== 返回顶部：右下角浮动圆钮（主页 / 后台共用） =====
   判据统一用 window.scrollY —— 两页的纵向滚动容器都是 window
   （.topbar 是 sticky 吸顶，.layout / .admin 都是普通流），所以不需要按页面分支。
   逻辑在 js/totop.js，两页各引一次。

   z-index 取 40：高于顶栏(30)与侧栏遮罩(20)，低于后台登录遮罩(100)与各弹窗(90 / 1200)——
   弹窗打开时按钮要沉在遮罩之下，不能浮上来抢焦点。

   隐藏用 visibility 而不是 display:none：一是淡出能有过渡，
   二是隐藏期间它不会被 Tab 键聚焦到（display:none 与 visibility:hidden 都可以，
   但 opacity:0 单独用不行 —— 那样的"看不见的按钮"仍可被键盘点到）。 */
.to-top {
  position: fixed;
  right: 24px;
  bottom: 24px;
  z-index: 40;
  width: 46px;
  height: 46px;
  padding: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  /* 光晕色 / 聚焦环色。放在这儿而不是两个主题令牌块里 ——
     它只服务这一个组件，塞进全局表会把主题表撑乱。深色主题在下面单独覆盖。 */
  --tt-glow: rgba(47, 111, 237, .40);
  --tt-ring: rgba(47, 111, 237, .30);
  /* 实心主色渐变，不再用毛玻璃：花哨壁纸下玻璃会糊成一块、反而没有存在感，
     与「更醒目」正相反；实心底还顺带省掉一层 backdrop-filter 的合成开销。
     渐变从 --primary 落到 --primary-deep，圆钮才显得"立"得起来，而不是一片平色。 */
  border: 1px solid rgba(255, 255, 255, .30);
  background:
    linear-gradient(180deg, rgba(255, 255, 255, .22), rgba(255, 255, 255, 0) 56%),
    linear-gradient(160deg, var(--primary), var(--primary-deep));
  /* 两层投影：一层同色光晕让它从页面浮起来，一层冷色实投影压出厚度 */
  box-shadow: 0 6px 18px var(--tt-glow), 0 2px 6px rgba(15, 23, 42, .22);
  color: #fff;
  cursor: pointer;
  opacity: 0;
  visibility: hidden;
  transform: translateY(10px) scale(.9);
  transition: opacity .2s ease, transform .2s ease, visibility .2s, box-shadow .2s ease;
}
[data-theme="dark"] .to-top {
  --tt-glow: rgba(79, 140, 255, .46);
  --tt-ring: rgba(79, 140, 255, .34);
}
/* 箭头用内联 SVG，而不是 ↑ 这个字符：字符的笔画粗细归字体管，压根改不动，
   想加粗只能靠 font-weight 去赌系统字体有没有对应字形（很多时候并没有）。 */
.to-top svg {
  width: 23px;
  height: 23px;
  display: block;
  transition: transform .2s ease;
}
.to-top.on { opacity: 1; visibility: visible; transform: none; }
.to-top:hover {
  transform: translateY(-3px) scale(1.04);
  box-shadow: 0 12px 28px var(--tt-glow), 0 0 0 6px var(--tt-ring),
              0 3px 8px rgba(15, 23, 42, .24);
}
/* 箭头自己也再向上挪一点，暗示"往上走"的方向 */
.to-top:hover svg { transform: translateY(-1.5px); }
.to-top:active { transform: translateY(-1px) scale(.94); }
.to-top:focus-visible { outline: 2px solid var(--primary); outline-offset: 3px; }

/* 窄屏往角上收一点，别压住卡片；仍保持 40px 以上的触控热区 */
@media (max-width: 640px) {
  .to-top { right: 14px; bottom: 14px; width: 42px; height: 42px; }
  .to-top svg { width: 21px; height: 21px; }
}

/* 系统开启「减弱动态效果」时去掉位移（平滑滚动由 js/totop.js 同步判断） */
@media (prefers-reduced-motion: reduce) {
  .to-top { transition: opacity .12s ease, visibility .12s; transform: none; }
  .to-top:hover { transform: none; }
  .to-top:hover svg { transform: none; }
  .to-top:active { transform: none; }
}

/* ===== 毛玻璃降级：不支持 backdrop-filter 时回落到不透明面板，保证可读性 ===== */
@supports not ((backdrop-filter: blur(2px)) or (-webkit-backdrop-filter: blur(2px))) {
  .topbar, .sidebar, .layout-top .top-nav { background: var(--panel); }
  .card, .mini-card { background: var(--glass-hover); }
  .modal, .login-box, .tb-searches .ws-engines { background: var(--panel); }
  /* 返回顶部圆钮**不在**这里降级：它是实心主色底，本来就不依赖 backdrop-filter。
     早先那条 `.to-top { background: var(--panel) }` 必须删掉 ——
     留着会把主色底替换成白色面板，越降级越浅，与「更醒目」背道而驰。 */
}
@supports not (background: radial-gradient(#fff 0%, rgba(255,255,255,0) 60%)) {
  html { background: var(--bg); }
}
