/* ============================================================
 * front.tool.bundle.css —— 由 .workbuddy/build_front_css.py 自动生成，请勿手改
 *
 * 内容 = 前台 7 个本地 CSS 按 front_css_links() 'tool' 栈的顺序拼接。
 * 重新生成：python .workbuddy/build_front_css.py --write
 *
 * 为什么不含 FontAwesome：它从不改动、带 1 年 immutable 缓存，
 * 合并进来会让「改一个本地 CSS」触发整包重下。详见脚本头部说明。
 * ============================================================ */

/* ---- assets/css/design-token.css ---- */
/**
 * 统一设计令牌（Design Tokens）—— 主题「晨曦 Aurora」
 *
 * @description 全站唯一的视觉源头：配色 / 圆角 / 阴影 / 字体 / 尺寸。
 *              改这里就能整体换肤；组件样式只准引用变量，不准写死颜色。
 *
 * 品牌色：青碧（Teal-Cyan）—— 主色 #0F9488，强调渐变 #14B8A6 → #0EA5E9
 * 点缀色：琥珀 #F59E0B —— 用于价格、促销等需要抓眼的位置
 *
 * 同时保留一套旧变量名（--bg-primary / --accent 等）以兼容历史代码。
 */

:root {
  /* 让浏览器原生控件（数字输入框的上下箭头、滚动条、日期选择器、下拉框）
     跟随当前主题配色。不设的话暗色模式下这些控件仍是亮色的，格外刺眼。
     注意这里是「跟随站点主题」而不是 system：主题由 data-theme 手动切换，
     下面深色块里会覆盖成 dark。 */
  color-scheme: light;

  /* ==================== 字体 ==================== */
  --font-sans: -apple-system, BlinkMacSystemFont, "PingFang SC", "HarmonyOS Sans SC",
               "Microsoft YaHei", "Segoe UI", Roboto, "Helvetica Neue", sans-serif;
  --font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;

  /* ==================== 表面（Surface） ==================== */
  --bg:        #F5F7F8;   /* 页面底色 */
  --bg-soft:   #EDF1F2;   /* 交替区块 / 悬浮态 */
  --card:      #FFFFFF;   /* 卡片 */
  --card-soft: #F8FAFA;   /* 卡片内嵌区 */

  /* ==================== 文字 ==================== */
  --text:      #12201E;   /* 主文字 */
  --text-2:    #5B6B68;   /* 次级文字 */
  --text-3:    #95A5A2;   /* 辅助 / 占位 */
  --text-inv:  #FFFFFF;   /* 反色文字（主色底上） */

  /* ==================== 品牌 ==================== */
  --primary:        #0F9488;
  --primary-hover:  #0B7369;
  --primary-soft:   #E4F5F2;
  --primary-ring:   rgba(15, 148, 136, .16);
  --primary-shadow: rgba(15, 148, 136, .28);

  --brand-400: #2DD4BF;
  --brand-500: #14B8A6;
  --brand-600: #0F9488;

  /* ==================== 功能色 ==================== */
  --success: #16A34A;
  --warning: #D97706;
  --danger:  #DC2626;
  --info:    #0EA5E9;
  --accent:  #F59E0B;   /* 点缀：价格 / 促销 */

  --success-soft: rgba(22, 163, 74, .12);
  --warning-soft: rgba(217, 119, 6, .12);
  --danger-soft:  rgba(220, 38, 38, .10);
  --info-soft:    rgba(14, 165, 233, .12);
  /* 语义边框：表单校验 / 提示条 / 状态卡描边 */
  --success-border: rgba(22, 163, 74, .34);
  --danger-border:  rgba(220, 38, 38, .30);
  --warning-border: rgba(217, 119, 6, .34);
  --info-border:    rgba(14, 165, 233, .30);

  /* ==================== 描边 ==================== */
  --border:       #E3E9E8;
  --border-hover: #CBD5D3;

  /* ==================== 圆角 ==================== */
  --radius:      18px;
  --radius-md:   14px;
  --radius-sm:   12px;
  --radius-xs:   8px;
  --radius-pill: 999px;

  /* ==================== 阴影（分层） ==================== */
  --shadow-xs: 0 1px 2px rgba(16, 32, 30, .04);
  --shadow-sm: 0 1px 2px rgba(16, 32, 30, .04), 0 1px 4px rgba(16, 32, 30, .04);
  --shadow:    0 1px 2px rgba(16, 32, 30, .04), 0 6px 18px rgba(16, 32, 30, .06);
  --shadow-hover: 0 10px 30px rgba(16, 32, 30, .12);
  --shadow-lg: 0 20px 52px rgba(16, 32, 30, .14);
  --shadow-brand: 0 8px 22px rgba(15, 148, 136, .26);

  /* ==================== 渐变 ==================== */
  --grad-brand: linear-gradient(135deg, #14B8A6 0%, #0EA5E9 100%);
  --grad-soft:  linear-gradient(160deg, #E4F5F2 0%, #EDF1F2 100%);
  --grad-warm:  linear-gradient(135deg, #F59E0B 0%, #EF4444 100%);

  /* ==================== 第三方品牌色 ==================== */
  /* 支付宝蓝、微信绿等属「品牌识别色」，不应随主题变，但要有名字可引用 */
  --alipay:        #1677FF;
  --alipay-hover:  #0958D9;
  --alipay-ring:   rgba(22, 119, 255, .28);
  --alipay-ring-2: rgba(22, 119, 255, .34);
  --wechat:        #22C55E;

  /* ==================== 语义扩展（组件一律引用这些，禁止写死） ==================== */
  --text-onbrand:  #FFFFFF;                 /* 品牌色 / 实色底上的文字 */
  --fill-white:    #FFFFFF;                 /* 纯白填充（如按钮高光） */
  --bg-sunken:     var(--bg);               /* 内凹区域（金额框、确认框） */
  --scrim:         rgba(16, 32, 30, .42);   /* 遮罩：弹窗 / 抽屉 */
  --scrim-strong:  rgba(16, 32, 30, .66);   /* 遮罩：强调层 */
  /* 随机背景图蒙层：拆成 rgb 三元组 + 独立 alpha，alpha 由后台「背景蒙版浓度」注入
     （templates/partials/head.php 把它写成 --bg-scrim-a）。
     拆开的原因：以前写死 rgba(...,.88) 时底图被盖得几乎看不见，等于没设背景。 */
  --bg-scrim-rgb:  245, 247, 248;           /* 浅色下的提亮蒙层色 */
  --bg-scrim-a:    .55;                     /* 默认浓度，可被后台配置覆盖 */
  --glass:         rgba(255, 255, 255, .06);/* 深色面板上的玻璃层 */
  --hairline:      rgba(16, 32, 30, .08);   /* 极细分隔线 */

  --grad-success-soft: linear-gradient(135deg, rgba(22, 163, 74, .18), rgba(22, 163, 74, .04));
  --grad-warning-soft: linear-gradient(135deg, rgba(217, 119, 6, .18), rgba(217, 119, 6, .04));
  --grad-alipay: linear-gradient(135deg, var(--alipay) 0%, var(--alipay-hover) 100%);

  /* ==================== 尺寸 / 节奏 ==================== */
  --nav-h: 64px;
  --container-max: 1200px;   /* 留白更足，观感更开阔 */
  --section-y: 72px;         /* 区块间距拉开，页面更有呼吸感 */
  --gutter: 24px;            /* 统一左右留白 */

  /* ==================== 动效 ==================== */
  --ease: cubic-bezier(.22, .61, .36, 1);
  --dur: .22s;
  --dur-fast: .14s;
  --dur-slow: .34s;

  /* ==================== 层级（z-index） ====================
   * 为什么要有这一层：
   *   以前全站散着 1 / 90 / 95 / 98 / 100 / 200 / 900 / 999 / 1000 / 1200 / 2000 / 3000
   *   十二种值，互相之间没有章法。新组件要盖住老组件，唯一的办法就是「猜一个更大的数」
   *   —— 于是数值一路膨胀到 3000，而「分享弹窗被底部 TabBar 挡住」这类问题照样反复出现。
   *
   *   更麻烦的是层叠上下文：给 body 的直接子元素加 z-index 会让它自成一个层叠上下文，
   *   里面的弹窗再高的 z-index 也只在「这一层」里有效，出不去（见 base.css 的说明）。
   *
   * 用法：**只准引用变量，不准写死数字**。要新增一层，先在这里定名字，
   * 而不是在某个组件里随手写 9999。
   */
  --z-behind:  -1;   /* 装饰层：页面背景图，压在所有内容之下 */
  --z-base:     1;   /* 内容抬高（如 hero 文字盖在背景图上） */
  --z-sticky:  90;   /* 页面内吸底条：商品详情页操作条、后台顶栏 */
  --z-fab:     95;   /* 悬浮操作按钮（FAB） */
  --z-tabbar:  98;   /* 移动端底部 TabBar */
  --z-nav:    100;   /* 顶部导航栏 */
  --z-dropdown: 200; /* 下拉菜单 */
  --z-overlay: 400;  /* 遮罩（弹窗/抽屉背后的暗层） */
  --z-drawer: 450;   /* 抽屉 / 侧栏（在遮罩之上） */
  --z-modal:  500;   /* 弹窗本体 */
  --z-popover: 600;  /* 气泡 / 提示层 */
  --z-toast:  700;   /* 全局吐司提示：永远在最上层 */

  /* ==================== 焦点环 ==================== */
  --focus-ring:        var(--primary);
  --focus-ring-width:  2px;
  --focus-ring-offset: 2px;

  /* ==================== 间距节奏 ====================
   * 统一用这套刻度，不要再随手写 13px / 17px 之类的散值 ——
   * 间距一散，页面看着就「不齐」，而这种不齐很难一眼看出原因。
   */
  --sp-1: 4px;
  --sp-2: 8px;
  --sp-3: 12px;
  --sp-4: 16px;
  --sp-5: 20px;
  --sp-6: 24px;
  --sp-8: 32px;
  --sp-10: 40px;
  --sp-12: 48px;

  /* ==================== 响应式断点（约定值） ====================
   * ⚠️ CSS 的 @media 不支持 var()，所以这里只是**约定**，靠人工遵守。
   *    全站统一用这四个值，不要再引入 780 / 820 / 900 这种散断点 ——
   *    断点一散，同一屏幕宽度下各组件的布局会在不同时刻跳变，观感是「散架」的。
   *
   *   --bp-sm  640px  手机横屏 / 小平板
   *   --bp-md  768px  平板竖屏（主流断点）
   *   --bp-lg 1024px  小桌面 / 平板横屏
   *   --bp-xl 1280px  常规桌面
   */
  --bp-sm: 640px;
  --bp-md: 768px;
  --bp-lg: 1024px;
  --bp-xl: 1280px;

  /* ==================== 旧变量名（兼容历史代码） ==================== */
  --bg-primary:   var(--bg);
  --bg-secondary: var(--bg-soft);
  --bg-card:      var(--card);
  --text-primary: var(--text);
  --text-secondary: var(--text-2);
  --accent-hover: var(--primary-hover);
  --pay-box-bg:   var(--primary-soft);
  --pay-box-hover: rgba(15, 148, 136, .08);
}

/* ==================== 深色主题 ==================== */
[data-theme="dark"] {
  /* 与 :root 的 color-scheme 成对：切到深色主题时原生控件一并变深 */
  color-scheme: dark;

  --bg:        #0C1114;
  --bg-soft:   #131A1E;
  --card:      #161E23;
  --card-soft: #1B242A;

  --text:      #E6EDF0;
  --text-2:    #9AAAB3;
  --text-3:    #6B7C86;
  --text-inv:  #06231F;

  --primary:        #2DD4BF;
  --primary-hover:  #5EEAD4;
  --primary-soft:   rgba(45, 212, 191, .14);
  --primary-ring:   rgba(45, 212, 191, .22);
  --primary-shadow: rgba(45, 212, 191, .30);

  --brand-400: #5EEAD4;
  --brand-500: #2DD4BF;
  --brand-600: #14B8A6;

  --success: #4ADE80;
  --warning: #FBBF24;
  --danger:  #F87171;
  --info:    #38BDF8;
  --accent:  #FBBF24;

  --success-soft: rgba(74, 222, 128, .14);
  --warning-soft: rgba(251, 191, 36, .14);
  --danger-soft:  rgba(248, 113, 113, .14);
  --info-soft:    rgba(56, 189, 248, .14);

  --success-border: rgba(74, 222, 128, .38);
  --danger-border:  rgba(248, 113, 113, .34);
  --warning-border: rgba(251, 191, 36, .38);
  --info-border:    rgba(56, 189, 248, .34);

  --border:       rgba(255, 255, 255, .10);
  --border-hover: rgba(255, 255, 255, .20);

  --shadow-xs: 0 1px 2px rgba(0, 0, 0, .30);
  --shadow-sm: 0 1px 3px rgba(0, 0, 0, .34);
  --shadow:    0 2px 6px rgba(0, 0, 0, .34), 0 10px 26px rgba(0, 0, 0, .26);
  --shadow-hover: 0 14px 38px rgba(0, 0, 0, .44);
  --shadow-lg: 0 24px 60px rgba(0, 0, 0, .52);
  --shadow-brand: 0 8px 24px rgba(45, 212, 191, .26);

  --grad-brand: linear-gradient(135deg, #2DD4BF 0%, #38BDF8 100%);
  --grad-soft:  linear-gradient(160deg, rgba(45, 212, 191, .10) 0%, #131A1E 100%);

  /* 深色下的语义覆盖 */
  --grad-success-soft: linear-gradient(135deg, rgba(74, 222, 128, .20), rgba(74, 222, 128, .05));
  --grad-warning-soft: linear-gradient(135deg, rgba(251, 191, 36, .20), rgba(251, 191, 36, .05));
  --scrim:        rgba(0, 0, 0, .62);
  --scrim-strong: rgba(0, 0, 0, .78);
  --bg-scrim-rgb:  12, 17, 20;             /* 深色下的压暗蒙层色 */
  --bg-scrim-a:    .72;
  --hairline:     rgba(255, 255, 255, .08);
  --glass:        rgba(255, 255, 255, .04);

  /* 暗色下主色/功能色（青、绿、蓝）都变成亮色，白字对比度不足，
     叠在这些亮底上的文字改用深青黑（与 --text-inv 暗色值一致） */
  --text-onbrand: #06231F;
  /* 暖色点缀在暗色下收一点，避免亮橙红在深背景上刺眼；配白字对比度也更好 */
  --grad-warm: linear-gradient(135deg, #B45309 0%, #B91C1C 100%);
}

/* ---- assets/css/base.css ---- */
/**
 * 基础样式重置 —— 主题「晨曦 Aurora」
 *
 * @description 全站基础层：重置、排版、容器、按钮、表单、卡片、工具类
 *              所有颜色 / 圆角 / 阴影一律取自 design-token.css
 */

* { margin: 0; padding: 0; box-sizing: border-box; }

html {
  scroll-behavior: smooth;
  -webkit-text-size-adjust: 100%;
  /* 锚点跳转时给固定导航栏让位。少了这一句，点目录/页内锚点跳过去，
     目标标题正好被顶栏压在底下 —— 表现为「跳过去了但看不见」。 */
  scroll-padding-top: calc(var(--nav-h) + 16px);
}

body {
  font-family: var(--font-sans);
  background: var(--bg);
  color: var(--text);
  line-height: 1.65;
  font-size: 15px;
  min-height: 100vh;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

/* 顶部一道极淡的品牌光晕，让浅色页面不至于太"平" */
body::before {
  content: '';
  position: fixed;
  inset: 0 0 auto 0;
  height: 320px;
  background: radial-gradient(120% 100% at 50% 0%, var(--primary-soft) 0%, transparent 68%);
  pointer-events: none;
  /* -1：压在所有内容之下。原来是 0，靠下面 body > * 的 z-index:1 才不盖住正文；
     现在下面那条不再给 z-index，这里必须改成 -1，否则会盖在内容之上。 */
  z-index: var(--z-behind);
}

/* ⚠️ 这里**不能**给 z-index。
   一旦 body 的直接子元素（<main> / <footer> / .tabbar …）带上 z-index，
   它自己就成了独立的层叠上下文：里面 z-index:1200 的弹窗会被「困」在这一层，
   对外只按父级的 1 参与比较 —— 结果底部 TabBar（98）反而盖在弹窗之上，
   工具页的分享海报弹窗就是这么被挡住的。
   去掉 z-index（保持 auto，不创建层叠上下文）后，弹窗的层级才在根上下文生效。 */
body > * { position: relative; }

::selection {
  background: var(--primary);
  color: var(--text-onbrand);
}

a {
  text-decoration: none;
  color: inherit;
  -webkit-tap-highlight-color: transparent;
}
a:hover { text-decoration: none; }

ul { list-style: none; }

/* height:auto 不能省：只写 max-width:100% 时，若 <img> 自带 width/height 属性
   或别处给它定了高，等比缩放会被破坏，图片会被压扁 / 拉长。
   （带具体类名的规则如 .good-thumb{height:54px} 优先级更高，不受这条影响。） */
img { display: block; max-width: 100%; height: auto; }

button, input, select, textarea { font-family: inherit; }

/* 焦点环 —— 鼠标要安静，键盘要清晰
   原写法把 :focus 和 :focus-visible 一起设成 none，理由只是「点击后残留一圈线不好看」。
   但这两个选择器的语义完全不同：
     · :focus        鼠标点击、脚本 focus() 都会命中  → 不显示 outline（满足观感）
     · :focus-visible 只在键盘导航（Tab）时命中       → **必须**显示
   一起干掉之后，键盘用户 Tab 到按钮、链接、自定义控件上完全看不出焦点在哪
   （输入框还有 border + box-shadow 兜着，按钮和链接则一点反馈都没有）。
   现在拆开：鼠标点击依旧安静，键盘导航有清晰的品牌色焦点环。 */
:focus { outline: none; }

:focus-visible {
  outline: var(--focus-ring-width) solid var(--focus-ring);
  outline-offset: var(--focus-ring-offset);
  border-radius: var(--radius-xs);
}

/* 滚动条：细而克制 */
::-webkit-scrollbar { width: 8px; height: 8px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
  background: var(--border-hover);
  border-radius: 999px;
}
::-webkit-scrollbar-thumb:hover { background: var(--text-3); }

/* ==================== 通用工具类 ==================== */
.hidden { display: none !important; }
/* 注意：这里**不要**再定义 .show { display: block !important }。
   全站有 33 个页面靠 classList.add('show') 做淡入（.toast.show / .report-modal.show /
   .modal-overlay.show …），一旦存在 !important 的全局 .show，这些容器会被强制
   display:block，淡入动画和 flex 居中布局全部失效。旧入口遗留的这条规则已删除。 */

/* ==================== Toast 提示 ====================
 * utils.js 的 showToast() 往 <body> 里塞一个 .toast 元素，
 * 但这条规则此前只定义在 admin.css（后台），前台 33 个工具页调 showToast
 * 其实什么都没显示。前台样式放到这里，补齐这个缺口。 */
.toast {
  position: fixed;
  left: 50%;
  bottom: 2rem;
  z-index: var(--z-toast);
  max-width: min(88vw, 420px);
  padding: .75rem 1.25rem;
  border-radius: var(--radius-sm);
  background: var(--card);
  color: var(--text);
  border: 1px solid var(--border);
  box-shadow: var(--shadow);
  font-size: .9rem;
  line-height: 1.5;
  text-align: center;
  pointer-events: none;
  opacity: 0;
  transform: translateX(-50%) translateY(100px);
  transition: opacity var(--dur) var(--ease), transform var(--dur) var(--ease);
}
.toast.show {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
}

/* ==================== 容器 ==================== */
.container {
  max-width: var(--container-max);
  margin: 0 auto;
  padding: 0 20px;
}

main {
  /* 内页顶部/底部预留呼吸空间：导航与内容、内容与页脚不再贴死。
     首页/关于（page-has-hero）的 hero 必须顶到导航栏，padding 归零。 */
  padding: 28px 0 56px;
}
body.page-has-hero main { padding: 0; }

/* ==================== 排版 ==================== */
h1, h2, h3, h4 {
  line-height: 1.3;
  letter-spacing: -.2px;
  font-weight: 700;
}

/* ==================== 内页页头（统一「大气」版式） ==================== */
/* 首页有 Hero，内页若只放一个 1.4rem 的小标题会显得局促。
   这里把内页页头也做成有呼吸感的「小 Hero」：更大的字号 + 品牌竖条 + 底部渐变细线。 */
.page-header {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 1.25rem;
  flex-wrap: wrap;
  margin-bottom: 2rem;
  padding-bottom: 1.4rem;
  position: relative;
}
.page-header::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 1px;
  background: linear-gradient(90deg, var(--primary) 0%, transparent 62%);
  opacity: .45;
}
.page-header h1 {
  font-size: clamp(1.6rem, 2.6vw, 2.05rem);
  font-weight: 780;
  letter-spacing: -.9px;
  display: flex;
  align-items: center;
  gap: .6rem;
  line-height: 1.2;
}
.page-header h1 .fas,
.page-header h1 .far,
.page-header h1 .fab {
  color: var(--primary);
  font-size: .82em;
}
.page-header p {
  font-size: .95rem;
  color: var(--text-3);
  margin-top: .5rem;
}
/* 页头右侧的说明/操作 */
.page-header .page-header-actions {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}

/* ==================== 按钮 ==================== */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  padding: 0.7rem 1.6rem;
  font-size: 0.92rem;
  font-weight: 600;
  border-radius: var(--radius-pill);
  border: 1px solid transparent;
  cursor: pointer;
  transition: all var(--dur) var(--ease);
  text-decoration: none;
  white-space: nowrap;
}

.btn-primary {
  background: var(--grad-brand);
  color: var(--text-inv);
  box-shadow: var(--shadow-brand);
}
.btn-primary:hover {
  color: var(--text-inv);
  transform: translateY(-2px);
  box-shadow: 0 12px 28px var(--primary-shadow);
}
.btn-primary:active { transform: translateY(0); }

.btn-secondary {
  background: var(--card);
  color: var(--text-2);
  border-color: var(--border);
  box-shadow: var(--shadow-xs);
}
.btn-secondary:hover {
  border-color: var(--primary);
  color: var(--primary);
  background: var(--primary-soft);
  transform: translateY(-1px);
}

.btn-ghost {
  background: transparent;
  color: var(--text-2);
  border: 1px solid var(--border);
}
.btn-ghost:hover {
  border-color: var(--primary);
  color: var(--primary);
  background: var(--primary-soft);
  transform: translateY(-1px);
}

.btn-danger {
  background: var(--danger-soft);
  color: var(--danger);
}
.btn-danger:hover { background: var(--danger); color: var(--text-onbrand); }

.btn-sm { padding: 0.4rem 1rem; font-size: 0.82rem; }
.btn-block { width: 100%; }

/* 让 .btn-* 单独使用（不带 .btn）时也有完整外观 */
.btn-primary,
.btn-secondary,
.btn-ghost,
.btn-danger {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  padding: 0.7rem 1.5rem;
  border-radius: var(--radius-pill);
  font-size: .92rem;
  font-weight: 600;
  cursor: pointer;
  border: 1px solid transparent;
  text-decoration: none;
  transition: all var(--dur) var(--ease);
  white-space: nowrap;
}
.btn-full { width: 100%; }

/* ==================== 表单 ==================== */
.form-group { margin-bottom: 16px; }

.form-label {
  display: block;
  font-size: 13px;
  font-weight: 600;
  color: var(--text-2);
  margin-bottom: 6px;
}
.form-label .req { color: var(--danger); }

.form-input,
.form-select,
.form-textarea,
.form-control {
  width: 100%;
  padding: 10px 14px;
  font-size: 14px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--card);
  color: var(--text);
  transition: border-color var(--dur), box-shadow var(--dur);
  font-family: inherit;
}

.form-input:focus,
.form-select:focus,
.form-textarea:focus,
.form-control:focus {
  outline: none;
  border-color: var(--primary);
  box-shadow: 0 0 0 3px var(--primary-ring);
}

/* ==================== 卡片 ==================== */
.card {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  overflow: hidden;
}

/* 相邻卡片留白。
   .card 本身没有外边距，两张卡上下相邻时会直接贴死
   （个人中心「每日签到」卡紧贴「账户充值」卡就是这么来的）。
   相邻 margin 会折叠，已经在别处设过 margin-bottom 的地方不会变成双倍。 */
.card + .card { margin-top: 1.25rem; }

.card-title {
  font-size: .98rem;
  font-weight: 650;
  padding: .9rem 1.1rem;
  border-bottom: 1px solid var(--border);
  display: flex;
  align-items: center;
  gap: .45rem;
}
.card-title .fas { color: var(--primary); }

/* ==================== 空状态 ==================== */
.empty-state {
  text-align: center;
  padding: 3.5rem 1.5rem;
  color: var(--text-3);
  background: var(--card);
  border: 1px dashed var(--border);
  border-radius: var(--radius);
}
.empty-state i {
  font-size: 2.6rem;
  opacity: .35;
  display: block;
  margin-bottom: .8rem;
}
.empty-state p { font-size: .9rem; }

/* ==================== 徽章 ==================== */
.badge {
  display: inline-flex;
  align-items: center;
  gap: .3rem;
  padding: .2rem .65rem;
  border-radius: var(--radius-pill);
  font-size: .74rem;
  font-weight: 600;
  background: var(--primary-soft);
  color: var(--primary);
}

/* ==================== 网格 ==================== */
.grid { display: grid; gap: 16px; }
.grid-2 { grid-template-columns: repeat(2, 1fr); }
.grid-3 { grid-template-columns: repeat(3, 1fr); }
.grid-4 { grid-template-columns: repeat(4, 1fr); }

/* ==================== 辅助类 ==================== */
.text-center { text-align: center; }
.flex-center { display: flex; align-items: center; justify-content: center; }
.flex-between { display: flex; align-items: center; justify-content: space-between; }
.gap-1 { gap: 8px; }
.gap-2 { gap: 16px; }
.mt-1 { margin-top: 8px; }
.mt-2 { margin-top: 16px; }
.mb-1 { margin-bottom: 8px; }
.mb-2 { margin-bottom: 16px; }
.p-1 { padding: 8px; }
.p-2 { padding: 16px; }

/* 数字（价格 / 统计）用等宽字形，避免跳动 */
.num, .tabular { font-variant-numeric: tabular-nums; }

/* ==================== 响应式 ==================== */
@media (max-width: 992px) {
  .grid-3, .grid-4 { grid-template-columns: repeat(2, 1fr); }
}

@media (max-width: 768px) {
  .container { padding: 0 16px; }
  main.container { padding: 16px 16px 32px; }
  .grid-2, .grid-3, .grid-4 { grid-template-columns: 1fr; }
  .page-header h1 { font-size: 1.2rem; }
  .btn-primary,
  .btn-secondary,
  .btn-ghost,
  .btn-danger { min-height: 44px; }
}

/* ==================== 无障碍：尊重系统的「减少动态效果」 ====================
 * 前庭功能障碍 / 晕动症用户会在系统里开「减少动态效果」。不响应这个设置，
 * 页面上的位移、缩放、轮播就会让他们不适。
 * 这里做全局兜底，把动画与过渡压到近乎为 0，而不是逐个组件去改一遍。
 * （homepage.css 里另有针对 hero 的细化处理，两者不冲突。） */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
}

/* ==================== 触摸设备：点击目标最小 44px ====================
 * 只在「视口 ≤768px」的断点里给 min-height 并不够 —— 平板和触屏笔记本
 * 同样靠手指操作，而它们的宽度往往大于 768px。
 * 用 (hover: none) 判断「没有精确指针」比猜宽度准得多。
 * 刻意不含 .btn-sm：那是表格行内、分页器里的紧凑按钮，撑到 44px 会把布局顶散。 */
@media (hover: none) {
  .btn,
  .btn-primary,
  .btn-secondary,
  .btn-ghost,
  .btn-danger {
    min-height: 44px;
  }
}

/* ==================== 按钮补齐（2026-10-13 从 content.css 并入） ====================
 * .btn-primary / .btn-secondary / .btn-ghost 单独使用（不带 .btn）时，
 * 只有上面 221-256 行的配色，缺 padding / 圆角 / display，会渲染成浏览器原生按钮。
 * 精修值（padding 1.6rem / 字重 550 / 圆角 999px）原本住在 content.css 1519-1548 行，
 * 随前台 CSS 拆分移到这里，放在上面那段同款兜底之后，继续赢。
 * full 与 tool 两个栈都加载 base.css，两边渲染与拆分前一致。
 * （1547 行那条 .modal-content 移动端内边距不属于按钮，已搬去 modal.css。） */
/* ==================== 按钮补齐 ====================
 * .btn-primary / .btn-secondary / .btn-ghost 此前只有配色，
 * 缺少 .btn 提供的 padding / 圆角 / display，单独使用时渲染成浏览器原生按钮。
 */
.btn-primary,
.btn-secondary,
.btn-ghost {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  padding: 0.7rem 1.6rem;
  font-size: 0.92rem;
  font-weight: 550;
  border-radius: 999px;
  border: 1px solid transparent;
  cursor: pointer;
  transition: all .2s;
  text-decoration: none;
  white-space: nowrap;
}

.btn-full { width: 100%; }

@media (max-width: 768px) {
  .btn-primary,
  .btn-secondary,
  .btn-ghost { min-height: 44px; }
}

/* ---- assets/css/navigation.css ---- */
/**
 * 导航与页脚 —— 主题「晨曦 Aurora」
 *
 * @description 顶栏（玻璃拟态）、页脚、移动端底部 TabBar、主题切换按钮
 */

/* ==================== 顶部导航 ==================== */
.navbar {
  position: sticky;
  top: 0;
  z-index: var(--z-nav);
  height: var(--nav-h);
  background: color-mix(in srgb, var(--card) 82%, transparent);
  backdrop-filter: saturate(180%) blur(16px);
  -webkit-backdrop-filter: saturate(180%) blur(16px);
  border-bottom: 1px solid var(--border);
  transition: box-shadow var(--dur) var(--ease), background var(--dur) var(--ease);
}

/* 不支持 color-mix 的浏览器回退 */
@supports not (background: color-mix(in srgb, red 50%, blue)) {
  .navbar { background: var(--card); }
}

.navbar.scrolled {
  box-shadow: var(--shadow-sm);
  border-bottom-color: var(--border-hover);
}

.nav-inner {
  height: 100%;
  display: flex;
  align-items: center;
  gap: 24px;
}

/* Logo */
.logo {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 17px;
  font-weight: 700;
  letter-spacing: .2px;
  flex-shrink: 0;
}

.logo-icon {
  width: 34px;
  height: 34px;
  border-radius: 11px;
  background: var(--grad-brand);
  color: var(--text-onbrand);
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: var(--shadow-brand);
  flex-shrink: 0;
}
.logo-icon svg { width: 18px; height: 18px; }

/* 导航链接 */
.nav-links {
  display: flex;
  gap: 4px;
  margin-left: auto;
}

.nav-links a {
  position: relative;
  font-size: 14.5px;
  color: var(--text-2);
  font-weight: 550;
  padding: 8px 14px;
  border-radius: var(--radius-pill);
  transition: color var(--dur), background var(--dur);
}

.nav-links a:hover {
  color: var(--text);
  background: var(--bg-soft);
}

.nav-links a.active {
  color: var(--primary);
  background: var(--primary-soft);
  font-weight: 650;
}

/* 顶栏右侧操作区 */
.nav-actions {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-shrink: 0;
}

/* 主题切换按钮 */
.theme-toggle {
  width: 38px;
  height: 38px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--border);
  border-radius: 50%;
  background: var(--card);
  color: var(--text-2);
  cursor: pointer;
  transition: all var(--dur) var(--ease);
  flex-shrink: 0;
}
.theme-toggle:hover {
  border-color: var(--primary);
  color: var(--primary);
  background: var(--primary-soft);
  transform: rotate(18deg);
}
.theme-toggle .fa-sun { display: none; }
[data-theme="dark"] .theme-toggle .fa-sun { display: inline-block; }
[data-theme="dark"] .theme-toggle .fa-moon { display: none; }

/* CTA 按钮 */
.nav-cta {
  flex-shrink: 0;
  padding: 9px 20px;
  border: none;              /* <button> 复用此类时吃掉浏览器默认边框（打赏按钮） */
  appearance: none;
  border-radius: var(--radius-pill);
  background: var(--grad-brand);
  color: var(--text-inv);
  font-size: 13.5px;
  font-weight: 600;
  line-height: 1.2;          /* button 与 a 的行高基线对齐 */
  box-shadow: var(--shadow-brand);
  transition: transform var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
  cursor: pointer;
}
.nav-cta:hover {
  color: var(--text-inv);
  transform: translateY(-2px);
  box-shadow: 0 12px 26px var(--primary-shadow);
}

/* ==================== 页脚 ==================== */
.footer {
  background: var(--bg-soft);
  border-top: 1px solid var(--border);
  padding: 52px 0 24px;
  /* 与内容的间距由 main 的 padding-bottom 统一供给（base.css），
     这里不再加 margin-top —— 双份间距会把页面撑出一大截 */
  margin-top: 0;
  position: relative;
}
.footer::before {
  content: '';
  position: absolute;
  top: -1px;
  left: 0;
  right: 0;
  height: 1px;
  background: var(--grad-brand);
  opacity: .5;
}

.footer-grid {
  display: grid;
  grid-template-columns: 2fr 1fr 1fr 1fr;
  gap: 36px;
}

.footer .logo { margin-bottom: 12px; }

.footer-desc {
  font-size: 13px;
  color: var(--text-2);
  line-height: 1.8;
  max-width: 320px;
}

.footer h4 {
  font-size: 13.5px;
  font-weight: 650;
  color: var(--text);
  margin-bottom: 14px;
}

.footer li { margin-bottom: 9px; }

.footer li a {
  font-size: 13px;
  color: var(--text-2);
  transition: color var(--dur);
}
.footer li a:hover { color: var(--primary); }

.footer-bottom {
  margin-top: 44px;
  padding-top: 20px;
  border-top: 1px solid var(--border);
  display: flex;
  justify-content: space-between;
  gap: 10px;
  flex-wrap: wrap;
  font-size: 12px;
  color: var(--text-3);
}
.footer-bottom a { color: inherit; }
.footer-bottom a:hover { color: var(--primary); }
.footer-version {
  font-variant-numeric: tabular-nums;
  opacity: .6;
}

/* ==================== 移动端底部 TabBar ==================== */
.tabbar {
  display: none;
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: var(--z-tabbar);
  background: color-mix(in srgb, var(--card) 92%, transparent);
  backdrop-filter: saturate(180%) blur(18px);
  -webkit-backdrop-filter: saturate(180%) blur(18px);
  border-top: 1px solid var(--border);
  box-shadow: 0 -6px 22px var(--hairline);
  padding: 6px 6px calc(6px + env(safe-area-inset-bottom));
}

@supports not (background: color-mix(in srgb, red 50%, blue)) {
  .tabbar { background: var(--card); }
}

.tabbar a {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
  padding: 5px 0 3px;
  font-size: 10.5px;
  color: var(--text-3);
  font-weight: 550;
  transition: color var(--dur);
  position: relative;
}

.tabbar a.active { color: var(--primary); font-weight: 650; }

/* 激活项顶部小圆点 */
.tabbar a.active::before {
  content: '';
  position: absolute;
  top: 0;
  width: 22px;
  height: 3px;
  border-radius: 0 0 4px 4px;
  background: var(--grad-brand);
}

.tabbar svg { width: 22px; height: 22px; }

/* ==================== 响应式 ==================== */
@media (max-width: 768px) {
  .tabbar { display: flex; }
  body { padding-bottom: calc(64px + env(safe-area-inset-bottom)); }

  /* 移动端顶部保留简化导航栏：左边 logo，右侧主题切换 + 打赏/用户中心按钮。
     中间导航链接在窄屏上没有空间，直接隐藏（用户通过底部 TabBar 切换主入口）。
     之前 .navbar { display: none; } 导致商品详情页等 H5 页面顶部光秃秃很难看。 */
  .navbar {
    position: sticky;
    top: 0;
  }
  .nav-links { display: none; }
  .nav-inner { gap: 12px; }
  .logo {
    font-size: 16px;
    gap: 8px;
  }
  .logo-icon {
    width: 32px;
    height: 32px;
    border-radius: 10px;
  }
  .logo-icon svg { width: 16px; height: 16px; }
  .nav-actions {
    gap: 8px;
    margin-left: auto;
  }
  .nav-cta {
    padding: 8px 16px;
    font-size: 13px;
  }
  .theme-toggle {
    width: 34px;
    height: 34px;
  }
  .footer { padding: 36px 0 20px;}
  .footer-grid {
    grid-template-columns: repeat(3, 1fr);
    gap: 14px;
    text-align: center;
  }
  .footer-grid > div:first-child { grid-column: 1 / -1; }
  .footer .logo { justify-content: center; }
  .footer-desc { max-width: none; }
  .footer h4 { font-size: 12.5px; margin-bottom: 10px; }
  .footer li { margin-bottom: 7px; }
  .footer-bottom {
    margin-top: 28px;
    justify-content: center;
    text-align: center;
  }
}

/* ---- assets/css/auth.css ---- */
/**
 * 认证页面（登录 / 注册 / 忘记密码 / 重置密码）—— 主题「晨曦 Aurora」
 */

.auth-page {
  min-height: calc(100vh - var(--nav-h) - 200px);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 48px 20px;
}

.auth-card {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 36px 32px;
  width: 100%;
  max-width: 420px;
  box-shadow: var(--shadow-hover);
  position: relative;
  overflow: hidden;
}

/* 顶部品牌渐变条 */
.auth-card::before {
  content: '';
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 3px;
  background: var(--grad-brand);
}

.auth-header {
  text-align: center;
  margin-bottom: 26px;
}

.auth-icon-wrapper {
  width: 66px;
  height: 66px;
  border-radius: 20px;
  background: var(--primary-soft);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 28px;
  margin: 0 auto 16px;
  border: 1px solid var(--primary-ring);
}
.auth-icon-wrapper i,
.auth-icon-wrapper .fas {
  background: var(--grad-brand);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  color: transparent;
}

.auth-title {
  font-size: 22px;
  font-weight: 750;
  color: var(--text);
  margin-bottom: 4px;
}

.auth-subtitle {
  font-size: 13px;
  color: var(--text-3);
}

.auth-error {
  background: var(--danger-soft);
  border: 1px solid var(--danger-border);
  color: var(--danger);
  padding: 10px 14px;
  border-radius: var(--radius-sm);
  font-size: 13px;
  margin-bottom: 16px;
}

.auth-success {
  background: var(--success-soft);
  border: 1px solid var(--success-border);
  color: var(--success);
  padding: 10px 14px;
  border-radius: var(--radius-sm);
  font-size: 13px;
  margin-bottom: 16px;
}

.auth-links {
  text-align: center;
  margin-top: 18px;
  font-size: 13px;
  color: var(--text-2);
}

.auth-links a {
  color: var(--primary);
  font-weight: 600;
}

.auth-links a:hover { text-decoration: underline; }

@media (max-width: 768px) {
  .auth-page { padding: 28px 16px; }
  .auth-card { padding: 28px 20px; }
}

/* ---- assets/css/poster.css ---- */
/**
 * 工具页分享海报弹窗（2026-10-13 从 content.css 拆出）
 *
 * 只被 tools/_footer.php 的海报功能使用；前台其余页面没有海报入口，
 * 因此只有 tool 栈（tools/*.php）加载本文件，full 栈不加载。
 * 合并包：assets/css/front.tool.bundle.css。
 *
 * 加载顺序（tool 栈）：design-token → base → navigation → auth → poster → modal → tools
 * ★ poster 必须在 modal 之前：本文件的单类 .poster-modal-content 赢不了
 *   modal.css 的 .modal-content（同权重、后加载的胜出）—— 与拆分前 content.css 里
 *   「海报段在前、弹窗段在后」的相对顺序一致，层叠结果不变。
 */

/* ==================== 海报分享弹窗 ==================== */
/* 下面这条单类选择器**赢不了** modal.css 里的 .modal-content（同权重、后加载的胜出）：
 * 真正生效的是 modal.css 里那条连写两个类的 `.modal-content.poster-modal-content`。
 * 这里保留只为表达意图，改数值请改 modal.css 那条。 */
.poster-modal-content { max-width: 420px; padding: 0; text-align: left; overflow: hidden; }
.poster-modal-body { padding: 16px; }

.poster-preview-wrapper {
  background: var(--bg-soft);
  border-radius: var(--radius-sm);
  padding: 12px;
  margin-bottom: 16px;
  min-height: 180px;
  display: flex;
  align-items: center;
  justify-content: center;
}

.poster-canvas { width: 100%; }
/* border-radius 与生成图保持一致（见 _footer.php 的 replaceWithImage：
   图片圆角现取 --radius），免得预览框和图片的圆角不一样大。 */
.poster-canvas img { width: 100%; border-radius: var(--radius); display: block; }

.poster-actions { display: flex; gap: 10px; }

.poster-btn {
  flex: 1;
  min-height: 44px;
  border: none;
  border-radius: var(--radius-sm);
  font-size: 0.9rem;
  font-weight: 600;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  transition: all .2s;
}

.poster-btn-primary { background: var(--primary); color: var(--text-onbrand); }
.poster-btn-primary:hover { background: var(--primary-hover); }

.poster-btn-secondary {
  background: var(--bg-soft);
  color: var(--text-2);
  border: 1px solid var(--border);
}

/* 海报卡片（html2canvas 渲染目标）
 *
 * 宽度是**设计稿尺寸**，固定 320px，内部布局不随屏幕重排 —— 海报最终是一张图片，
 * 在任何设备上长宽比和排版都必须一模一样。
 *
 * 踩过：移动端媒体查询里曾写 `.poster-card { width: 100% }`，窄屏把卡片压窄，
 * 于是「文字疯狂折行、整张卡被抻长」；更糟的是 html2canvas 捕获的就是被压窄后的布局，
 * 生成图再 width:100% 显示 —— 手机上永远是一张畸形海报。
 * 窄屏适配改走「整图等比缩小」（见 .poster-modal-content 与 _footer.php 的 fitPosterCard）。
 * flex:0 0 auto 保证它作为 flex 子项也绝不被压缩。 */
.poster-card {
  width: 320px;
  flex: 0 0 auto;
  background: var(--card);
  border-radius: var(--radius);
  overflow: hidden;
  font-size: 0.9rem;
}

/* 顶栏是整张海报**仅有的两个名字**：左边站名、右边工具名徽章。
 * 底色刻意不做成主色实心条 —— 一条高饱和色块压在最上面，海报立刻变「广告位」，
 * 中间真正想让人看的内容反被顶下去。
 *
 * 但也不再用 --bg-soft：预览框 .poster-preview-wrapper 的底色就是 --bg-soft，
 * 顶栏跟框**同色**，卡片顶上那条边整个消失，看上去像白卡片压住了名字的下半截
 * （用户「还是挡一半」就是这么读出来的）。现在顶栏 = 卡片自己的颜色 + 一条分隔线，
 * 卡片从上到下是完整的一块，两个名字明确落在卡片里。
 *
 * line-height 显式给 1.5：盒子永远比字高，汉字笔画不会溢出到内容区底下被盖住
 * （.poster-site-logo / .poster-tool-badge > span 都有 overflow:hidden 做省略号，
 *   行高给不够时溢出的下半截会被直接切掉 —— 这是「被挡」的第二种可能成因，一起堵死）。
 *
 * 左右 22px 与 .poster-body 对齐，整张卡的可视边界是一条直线；
 * 上下 18px 给两个名字留足呼吸位。 */
.poster-header {
  padding: 18px 22px;
  background: var(--card);
  color: var(--text-2);
  border-bottom: 1px solid var(--border);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  line-height: 1.5;
  /* 裁切只放在顶栏这一层，**不要**放到文字元素自己身上。
   *
   * 踩过（2026-10-06，用户第三次说「还是挡着」的最终根因）：
   * 两个名字的元素原本各自带着 overflow:hidden + text-overflow:ellipsis，
   * 浏览器 DOM 渲染下一切正常（所以本地怎么预览都没问题），
   * 但海报最终是 **html2canvas** 画到 canvas 上的 —— 它用自己那套字体度量定位文字
   * （渲染器把 ctx.textBaseline 固定成 bottom，基线算得比浏览器更低），
   * 再按元素盒 clip 一刀。拉丁字母看不出异常，**汉字的下半截会被整齐切掉**，
   * 弹窗里看着就像两个名字被什么压住了。
   * 这同时解释了前面几轮「调了内边距还是挡着」—— 问题从来不在内边距上。
   * 兜底截断因此改走 max-width + JS 字数截断，任何文字元素都不再带 overflow:hidden。 */
  overflow: hidden;
}

.poster-site-logo {
  font-size: 0.88rem;
  font-weight: 600;
  line-height: 1.5;
  min-width: 0;
  /* 这里**绝不能**写 overflow:hidden —— 汉字的下半截会被 html2canvas 切掉，
   * 原因见 .poster-header 里那段注释。超长截断改由 JS(posterTruncate) + max-width 负责。 */
  max-width: 58%;
  white-space: nowrap;
}

/* 顶栏右侧的工具名胶囊 —— 海报上「工具名」唯一的一次出现。
 * 做成主色小胶囊而不是白字，是为了在浅色顶栏上依然醒目，又不抢中间内容的戏。
 * （.poster-footer-info / .poster-site-name / .poster-slogan 是底部那次
 *   重复站名的残留，DOM 已不再生成，样式一并删除。） */
.poster-tool-badge {
  display: flex;
  align-items: center;
  gap: 5px;
  font-size: 0.82rem;
  font-weight: 600;
  line-height: 1.5;
  color: var(--primary);
  background: var(--primary-soft);
  padding: 3px 10px;
  border-radius: 999px;
  min-width: 0;
  flex: 0 1 auto;
}

/* 徽章左侧的图标不参与压缩 */
.poster-tool-badge > i { flex: none; }

/* 工具名过长时靠字数截断（JS）+ max-width 收住，不去挤站点名。
 * 同样**不能**用 overflow:hidden 截断 —— 见 .poster-header 的注释。 */
.poster-tool-badge > span {
  max-width: 9em;
  white-space: nowrap;
  line-height: 1.5;
}

/* --- html2canvas 出图补偿：**只作用于生成图片时的克隆文档** ---
 * 踩过（2026-10-06，用户第四次反馈「名字和图标还是下移一点点」）：
 * 顶栏是 `display:flex + align-items:center`。浏览器里两个名字正好居中，
 * 但 html2canvas 复现不了 flex 的垂直居中 —— 它把顶栏里的文字整体画低了约 0.64em（≈9px）。
 * 对照实测：同一张卡里，普通块流的文字它只偏 ≤2px，**只有 flex 居中里的文字偏 9px**，
 * 所以只补顶栏这一处，别去动其它区域。
 * 补偿量 0.64em 是量出来的：站名 ink 中心出图后是 39.5（DOM 是 30.5），
 * 减掉 9.0px 正好落回 30.5，与浏览器渲染完全一致。
 * 为什么挂在 .h2c-fix 上：DOM 渲染本来就是准的，补偿只能在出图的克隆里做
 * （_footer.php 的 onclone 给克隆卡片加这个类），否则页面里的海报会往上飘。
 * 只补**叶文字元素**：补在 .poster-tool-badge 这类容器上，里面的 i/span 会跟着再移一次，变成双倍偏移。 */
.poster-card.h2c-fix .poster-site-logo,
.poster-card.h2c-fix .poster-tool-badge > i,
.poster-card.h2c-fix .poster-tool-badge > span {
  transform: translateY(-0.64em);
}

/* 中间内容区：整张海报的舞台。
 * min-height + flex 垂直居中，让短内容（答案之书的「也许」二字）也不贴顶、不显扁。 */
.poster-body {
  padding: 30px 22px;
  min-height: 176px;
  text-align: center;
  color: var(--text);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}

/* 各版式容器占满整宽，内部的 max-width + margin:auto 才能稳定居中 */
.poster-default,
.poster-quote-main,
.poster-answer-main,
.poster-poem-main,
.poster-tab-main,
.poster-counter-main,
.poster-wheel-main,
.poster-kuolie { width: 100%; }

/* 扩列卡片海报：主角就是用户当前编辑的卡片本体（实时画布转的 dataURL 图）。
 * 撑满内容区宽（320 - 22×2 = 276px），高度按卡片原始比例自适应 ——
 * 卡片可长可短（长内容自动增高），压高度会把用户的资料截没。 */
.poster-kuolie-img {
  display: block;
  width: 100%;
  max-width: 276px;
  height: auto;
  margin: 0 auto;
}

/* 底部只剩一枚二维码（站名/口号那次重复已删除，见 tools/_footer.php）。
 * 竖排居中之后二维码也能放大到 92px，辨识度反而更高。
 * 左右同样 22px，与顶栏 / 内容区对齐，整张卡的可视边界是一条直线。 */
.poster-footer {
  padding: 18px 22px;
  border-top: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  align-items: center;
}

/* 二维码容器：绝不被压窄 */
.poster-qr-wrapper {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
}

/* 二维码必须永远是正方形。
 *
 * 踩过（本次问题）：base.css 有 `img { max-width:100%; height:auto }`，这里是 64×64。
 *   - `height:64px` 是类选择器，能盖过 base.css 的 `height:auto`；
 *   - 但**没人盖 `max-width:100%`**，而 `.poster-qr-wrapper` 原本没有任何样式，
 *     作为 flex 子项默认 flex-shrink:1 → 空间不足时是**容器先被压窄**，
 *     图片宽度被 max-width:100% 压到小于 64px，高度却仍锁在 64px
 *     → 二维码变成竖向长方形。
 * 三道保险：max-width:none 破掉 base.css、flex:none 不许压自己的容器、
 * aspect-ratio 兜底（即便外面哪天又把它压窄，也保持 1:1）。 */
.poster-qr-img {
  width: 92px;
  height: 92px;
  max-width: none;
  flex: none;
  aspect-ratio: 1 / 1;
  object-fit: contain;
  border-radius: 6px;
}

.poster-qr-text {
  font-size: 0.72rem;
  color: var(--text-3);
  text-align: center;
  margin-top: 6px;
  letter-spacing: .02em;
}

/* .poster-site-name / .poster-slogan 随底部那次重复站名一起删除，
 * 名字只保留顶栏那一次（说明见 .poster-header）。 */

.poster-default-icon,
.poster-quote-icon,
.poster-fortune-icon,
.poster-counter-icon,
.poster-wheel-icon,
.poster-answer-icon,
.poster-poem-icon,
.poster-tab-icon { font-size: 2.2rem; margin-bottom: 12px; }

/* 内容类版式的图标再收一档：它只是情绪的引子，主角是下面那段话 */
.poster-quote-icon,
.poster-answer-icon,
.poster-poem-icon { font-size: 1.75rem; margin-bottom: 16px; }

.poster-quote-title,
.poster-fortune-label,
.poster-counter-label,
.poster-wheel-label,
.poster-answer-label,
.poster-poem-title,
.poster-tab-title,
.poster-default-title {
  font-size: 1.05rem;
  font-weight: 700;
  margin-bottom: 8px;
}

.poster-quote-content,
.poster-fortune-detail,
.poster-counter-hint,
.poster-wheel-hint,
.poster-answer-hint,
.poster-default-desc {
  font-size: 0.88rem;
  color: var(--text-2);
  line-height: 1.7;
}

/* ===== 内容为王 =====
 * 用户是冲着这段话才点的分享，那它就是这张卡片的正文：
 * 提字号、放行高、颜色从 --text-2 提到 --text，上下留白给足。
 * 其余元素（名字 = 顶栏、二维码 = 底部）一律退成配角。 */
.poster-quote-content {
  font-size: 1.04rem;
  font-weight: 500;
  color: var(--text);
  line-height: 1.95;
  max-width: 15em;
  margin: 0 auto;
  word-break: break-word;
}

/* 答案之书的答案多是「也许」「放手去做」这类短句 —— 直接当主视觉放大 */
.poster-answer-text {
  font-size: 1.5rem;
  font-weight: 800;
  color: var(--primary);
  line-height: 1.5;
  max-width: 12em;
  margin: 0 auto 12px;
  word-break: break-word;
}

/* 藏头诗：一句一行，行距拉开，字号与语录齐平 */
.poster-poem-lines { display: flex; flex-direction: column; gap: 6px; }

.poster-poem-line {
  font-size: 1.04rem;
  font-weight: 500;
  color: var(--text);
  line-height: 1.9;
  letter-spacing: .08em;
}

/* 默认海报（无专属版式的工具）：大图标 + 该工具页自己的简介。
   标题 .poster-default-title 随「名字不重复」的口径一起删掉了 —— 顶栏徽章已有工具名，
   所以这里简介就是唯一主角，字号往上提一档、颜色用正文色。
   限宽居中，免得一句话拉成通栏。 */
.poster-default-desc {
  font-size: 0.96rem;
  color: var(--text);
  max-width: 17em;
  margin: 0 auto;
  word-break: break-word;
}

.poster-counter-num,
.poster-fortune-result,
.poster-wheel-result,
.poster-counter-time {
  font-size: 2rem;
  font-weight: 800;
  color: var(--primary);
  margin: 6px 0;
}

.poster-tab-features {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  justify-content: center;
  margin-top: 10px;
}

.poster-tab-feature {
  font-size: 0.75rem;
  padding: 3px 10px;
  border-radius: 999px;
  background: var(--primary-soft);
  color: var(--primary);
}

.poster-loading {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  color: var(--text-2);
  font-size: 0.88rem;
  padding: 40px 0;
}

/* 海报弹窗的移动端适配（原 content.css:1389 起）
 * 拆分前它和「工具页移动端适配」同属一个 @media 块；按归属分开：
 * 工具那四条已搬去 tools.css（必须排在工具页容器规则之后，否则被基础规则顶掉），
 * 海报这四条留在本文件末尾 = 仍在 poster 基础规则之后，同权重照样赢。 */
@media (max-width: 768px) {
  /* 海报卡片**不再** width:100%（压窄会毁掉排版，见 .poster-card 的说明）：
     它保持 320px 设计宽度，由生成出来的图片等比缩小来适配窄屏。
     这里只把弹窗自己的内边距收小，让海报在手机上尽量大。
     注意选择器要连写两个类：单类 .poster-modal-content 与 modal.css 的 .modal-content
     同权重，会被后加载的顶掉（原来那句 max-width:420px 就是这么变成死规则的）。 */
  .modal-content.poster-modal-content { max-width: 100%; }
  .poster-modal-body { padding: 12px; }
  .poster-preview-wrapper { padding: 10px; }
  .poster-actions { flex-direction: column; }
}

/* ---- assets/css/modal.css ---- */
/**
 * 前台弹窗组件（2026-10-13 从 content.css 拆出）
 *
 * 使用方：
 *   · templates/partials/site-footer.php 赞赏弹窗（全站页脚，full + tool 都加载）
 *   · templates/pages/pay.php 支付结果弹窗（full 栈）
 *   · tools/_footer.php 分享海报弹窗（tool 栈）
 *
 * 拆分前这些规则散在 content.css 四处：448（.modal-overlay，支付页面段里）、
 * 472（.modal-btn，支付方式段里）、1400-1517（弹窗组件段）、
 * 1547（按钮补齐段里的移动端 .modal-content 内边距）。合成一个文件后：
 *   full 栈：modal 插在 content 之前 —— content.css 里已无 .modal-* 规则，级联不变；
 *   tool 栈：poster → modal —— 与拆分前 content.css 内的相对顺序一致。
 */

/* ---- 原 content.css:448（支付页面段里的弹窗遮罩） ---- */
.modal-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: var(--scrim); display: none; align-items: center; justify-content: center; z-index: var(--z-overlay); }

/* ---- 原 content.css:472（支付方式选择段里的弹窗按钮） ---- */
.modal-btn { background: var(--primary); color: var(--text-onbrand); border: none; padding: 0.6rem 2rem; border-radius: var(--radius-xs); cursor: pointer; }

/* ==================== 前台弹窗组件 ====================
 * 此前 .modal-content / .modal-success / .modal-icon / .modal-title / .modal-message
 * 在前台 CSS 中均无定义（只有 admin.css 有同名类），
 * 导致 pay.php 支付成功弹窗渲染为半透明黑底上的裸文字。
 *
 * 同理 .modal 基类此前也只有 admin.css 有：templates/footer.php 的赞赏弹窗
 * 和 tools/_footer.php 的海报弹窗都写 class="modal hidden"，前台缺这条规则时
 * 弹窗内容会直接摊在页面流里。这里补齐整套弹窗壳。
 */
.modal {
  position: fixed;
  inset: 0;
  /* 1200：高于底部 TabBar(98) / 顶部导航(100) / 底部购买条(90)。
     前提是它所在的那条链路上没有别的层叠上下文 —— 见 base.css 里 body > * 的说明。 */
  z-index: var(--z-modal);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1.25rem;
  background: var(--scrim-strong);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
}
/* 默认隐藏靠 .hidden（base.css: display:none !important），语义与后台一致 */
.modal.hidden { display: none !important; }

.modal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: .75rem;
  padding: 1rem 1.25rem;
  border-bottom: 1px solid var(--border);
}
.modal-header .modal-title { margin: 0; text-align: left; }
.modal-header-actions { display: flex; align-items: center; gap: .5rem; }
.modal-close {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border: 1px solid var(--border);
  border-radius: var(--radius-xs);
  background: var(--card);
  color: var(--text-2);
  cursor: pointer;
  transition: all .2s;
}
.modal-close:hover { border-color: var(--danger); color: var(--danger); }

.modal-content {
  background: var(--card);
  border-radius: var(--radius);
  padding: 2rem 1.5rem;
  max-width: 380px;
  width: 90%;
  text-align: center;
  box-shadow: var(--shadow-hover);
}
/* 带 .modal-header 的弹窗（海报 / 赞赏）走左对齐版式，内边距交给内容容器 */
.modal-small { max-width: 360px; padding: 0; text-align: left; overflow: hidden; }

/* 海报弹窗宽度与内边距。
 * 必须连写两个类：单类 `.poster-modal-content` 与后面的 `.modal-content` 同权重，
 * 后写的胜出 —— 原来那句 `max-width: 420px` 一直是死规则（实际生效 380px）。
 * 380px 正好等于「320px 卡片 + 内容内边距」，预览区不被拉大，生成图几乎不放大。
 *
 * padding 是同一个坑的**第二次发作**，而且这次是肉眼可见的：
 * `.poster-modal-content{padding:0}` 同样被后面的 `.modal-content{padding:2rem 1.5rem}`
 * 顶掉，海报弹窗一直白吃 32px 上下 / 24px 左右内边距。预览区可用宽从 348 掉到 300，
 * 320px 的卡片放不下 -> fitPosterCard 把整张卡等比缩到 86%（320 -> 276）。
 * 后果：顶栏那两个名字只有 9px 高（13px 字号 x 0.86），汉字笔画在 9px 上糊成一团，
 * 看起来就是「名字被卡片挡掉一半」（用户连续两次反馈，第一次改左右内边距无效，
 * 因为病根在竖直方向被整体压小）。
 * 连写两个类拿到 (0,2,0)，无论写在前面还是后面都赢。 */
.modal-content.poster-modal-content { width: 100%; max-width: 380px; padding: 0; }

.modal-icon {
  font-size: 3rem;
  margin-bottom: 1rem;
  line-height: 1;
}

.modal-success .modal-icon { color: var(--success); }
.modal-error .modal-icon { color: var(--danger); }

.modal-title {
  font-size: 1.2rem;
  font-weight: 700;
  color: var(--text);
  margin-bottom: .5rem;
}

.modal-message {
  font-size: .92rem;
  color: var(--text-2);
  line-height: 1.7;
  margin-bottom: 1.5rem;
}

/* 赞赏弹窗（templates/partials/site-footer.php 输出） */
.donate-content { padding: 1.25rem 1.5rem 1.5rem; text-align: center; }
.donate-icon { color: var(--danger); }
.donate-message {
  font-size: .9rem;
  color: var(--text-2);
  line-height: 1.7;
  margin-bottom: 1rem;
}
.donate-methods { display: flex; gap: 1rem; justify-content: center; flex-wrap: wrap; }
.donate-method { flex: 1 1 140px; max-width: 180px; }
.donate-method-title {
  font-size: .82rem;
  color: var(--text-3);
  margin-bottom: .5rem;
}
.donate-qr { width: 100%; height: auto; border-radius: var(--radius-sm); }

/* ---- 原 content.css:1547（按钮补齐段里的移动端内边距） ---- */
@media (max-width: 768px) {
  .modal-content { padding: 1.5rem 1.25rem; }
}

/* ---- assets/css/tools.css ---- */
/**
 * 工具页共用组件样式 —— 供 tools/ 下 60+ 个独立工具页面使用
 *
 * @description 这些组件（结果卡片 / 标签页 / 上传区 / 状态文字…）在工具页里
 *              被反复使用了几百次，但历史上只有「作者顺手写了内联样式的工具页」
 *              才有样子，其余页面全是浏览器默认外观（裸 input、没边距的裸按钮）。
 *              本文件把它们收拢为一套统一组件，让所有工具页外观一致。
 *
 * 颜色 / 圆角 / 阴影一律取自 design-token.css，因此深色模式自动跟随。
 * 只做「补齐缺失样式」，不改动任何页面的 DOM 结构。
 */

/* ==================== 结果卡片（result-grid / result-card / result-item） ==================== */
/* 用法：<div class="result-grid"> <div class="result-card"><span class="result-label">..</span><span class="result-value">..</span></div> ... </div> */
.result-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: 10px;
  margin: 12px 0;
}

.result-card {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 12px 14px;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-xs);
  min-width: 0;
  transition: border-color var(--dur) var(--ease), transform var(--dur) var(--ease);
}
.result-card:hover {
  border-color: var(--primary);
  transform: translateY(-1px);
}

.result-label {
  font-size: .76rem;
  font-weight: 600;
  letter-spacing: .02em;
  color: var(--text-3);
  text-transform: none;
}

.result-value {
  font-size: 1.05rem;
  font-weight: 700;
  color: var(--text);
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  word-break: break-all;
  line-height: 1.4;
}

/* 单行结果条：<div class="result-item"><span>标签</span><code>值</code><button…></div> */
.result-item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 14px;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-xs);
  flex-wrap: wrap;
}
.result-item > span:first-child {
  font-size: .8rem;
  font-weight: 600;
  color: var(--text-3);
  flex: 0 0 auto;
}
.result-item > code {
  flex: 1 1 auto;
  min-width: 0;
  font-family: var(--font-mono);
  font-size: .84rem;
  color: var(--text);
  background: var(--bg-soft);
  border-radius: var(--radius-xs);
  padding: 4px 8px;
  word-break: break-all;
}

/* ==================== 二级标签页（工具箱里嵌套一层） ==================== */
/* 一个工具里有「一级分类 → 二级功能」时用：外层照常 .u-tabs 选一级，
   一级面板里再放一个 .u-tabs.u-tabs-sub 选二级，二级面板用 .u-sub-panel。
   JS 侧的坑：一级切换**只能**查 #容器 > .u-tab-btn，不能全局查 .u-tab-btn，
   否则会把二级标签的 active 一起摘掉（切走再切回就白屏）。
   参考实现：tools/formatconvert.php */
.u-tabs.u-tabs-sub {
  margin-bottom: 14px;
  padding-bottom: 8px;
}
.u-tabs.u-tabs-sub .u-tab-btn {
  min-height: 34px;
  padding: 6px 13px;
  font-size: .85rem;
}

.u-sub-panel { display: none; }
.u-sub-panel.active { display: block; }

/* ==================== 遗留前缀标签页（别名，保持旧页面外观一致） ==================== */
/* img-/cl-/bi-/ck-/pr-/rs-/te- 这些前缀的选项卡历史上没有样式，只有 btn-primary/
   btn-secondary 在撑；这里补齐选中态与按钮排布，视觉与 .u-tab-btn 一致。
   注意：这些页面的面板显隐靠各面板自己的内联 style="display:none"（由 JS 切换），
   所以这里**不能**给 -panel 加 display:none，否则会把默认面板也一起隐藏。
   新写的工具不要再加前缀，直接用 .u-tab-btn / .u-tab-panel。
   （fc- 已随「格式转换」改用统一类而退役；.tool-tab-* 那套也随之删掉了。） */
.img-tab, .cl-tab, .bi-tab, .ck-tab,
.pr-tab, .rs-tab, .te-tab {
  font-weight: 600;
}

.img-tab.active, .cl-tab.active, .bi-tab.active, .ck-tab.active,
.pr-tab.active, .rs-tab.active, .te-tab.active {
  box-shadow: var(--shadow-brand);
}

/* ==================== 统一标签页容器 ==================== */
/* 历史上每个工具各写一套前缀（img-/cl-/te-/pr-/rs-/bi-/ck-/texttool- 的 tool-tab-*、
   color-tab-content…），类名不同、功能完全一样。
   现在统一成这一套：一级 .u-tabs/.u-tab-btn/.u-tab-panel，二级 .u-tabs-sub/.u-sub-panel。
   新写的工具直接用这些，别再起新前缀。 */
.u-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  padding-bottom: 10px;
  margin-bottom: 16px;
  border-bottom: 1px solid var(--border);
}

.u-tab-btn {
  appearance: none;
  border: 1px solid transparent;
  background: transparent;
  color: var(--text-2);
  font-size: .9rem;
  font-weight: 600;
  font-family: inherit;
  padding: 8px 16px;
  border-radius: var(--radius-pill);
  cursor: pointer;
  transition: all var(--dur) var(--ease);
  white-space: nowrap;
  min-height: 40px;
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.u-tab-btn:hover {
  color: var(--primary);
  background: var(--primary-soft);
}
.u-tab-btn.active {
  background: var(--grad-brand);
  color: var(--text-inv);
  box-shadow: var(--shadow-brand);
}

.u-tab-panel { display: none; }
.u-tab-panel.active { display: block; }

/* ==================== 上传区（upload-area） ==================== */
.upload-area {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 32px 16px;
  border: 2px dashed var(--border);
  border-radius: var(--radius-sm);
  background: var(--card-soft);
  color: var(--text-3);
  cursor: pointer;
  text-align: center;
  transition: all var(--dur) var(--ease);
}
.upload-area:hover,
.upload-area.dragover {
  border-color: var(--primary);
  background: var(--primary-soft);
  color: var(--primary);
}
.upload-area > i {
  font-size: 1.9rem;
  opacity: .75;
}
.upload-area > p {
  font-size: .88rem;
  margin: 0;
}

/* ==================== 状态文字 ==================== */
.success-text,
.error-text,
.info-text,
.warning-text {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: .88rem;
  line-height: 1.6;
  margin: 8px 0;
}
.success-text > i, .error-text > i, .info-text > i, .warning-text > i { flex: 0 0 auto; }

.success-text { color: var(--success); }
.error-text   { color: var(--danger); }
.info-text    { color: var(--text-3); }
.warning-text { color: var(--warning); }

.info-text {
  background: var(--info-soft);
  border: 1px solid var(--info-border);
  border-radius: var(--radius-xs);
  padding: 8px 12px;
  display: block;
}

/* ==================== 通用内容分区 ==================== */
.tool-section {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-xs);
  padding: 18px;
  margin-bottom: 16px;
}
.tool-section > h3,
.tool-section > h4 {
  font-size: .98rem;
  font-weight: 700;
  margin-bottom: 12px;
  display: flex;
  align-items: center;
  gap: 6px;
}
.tool-section > h3 > i,
.tool-section > h4 > i { color: var(--primary); }

/* ==================== 输入补充 ==================== */
/* 工具页里大量 <select> 没带 class，浏览器默认外观与已样式化的 .form-input 不一致 */
.tool-page-body select,
.tool-section select {
  appearance: none;
  -webkit-appearance: none;
  padding: 10px 34px 10px 14px;
  font-size: 14px;
  font-family: inherit;
  color: var(--text);
  background-color: var(--card);
  background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2395A5A2' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 10px center;
  background-size: 15px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.tool-page-body select:focus,
.tool-section select:focus {
  outline: none;
  border-color: var(--primary);
  box-shadow: 0 0 0 3px var(--primary-ring);
}

/* 工具页里的 label 默认无间距，和输入框贴在一起 */
.tool-page-body label:not(.form-switch):not(.tool-item) {
  display: inline-block;
  font-size: 13px;
  font-weight: 600;
  color: var(--text-2);
}

/* textarea 默认不能竖向拉伸到乱七八糟的样子，但保留可调整 */
.tool-page-body textarea.form-input { resize: vertical; line-height: 1.6; }
.tool-page-body textarea.form-input[readonly] {
  background: var(--bg-soft);
  color: var(--text-2);
}

/* 等宽输出框（代码 / 哈希 / JSON 结果） */
.tool-page-body .mono,
.tool-page-body code { font-family: var(--font-mono); }

/* ==================== 图标按钮 / 附加按钮 ==================== */
.btn-search,
.btn-locate {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 42px;
  min-height: 42px;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--card);
  color: var(--text-2);
  font-size: .95rem;
  cursor: pointer;
  transition: all var(--dur) var(--ease);
}
.btn-search:hover,
.btn-locate:hover {
  border-color: var(--primary);
  color: var(--primary);
  background: var(--primary-soft);
}

.btn-cancel {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  padding: .7rem 1.5rem;
  font-size: .92rem;
  font-weight: 600;
  border-radius: var(--radius-pill);
  border: 1px solid var(--border);
  background: var(--card);
  color: var(--text-2);
  cursor: pointer;
  transition: all var(--dur) var(--ease);
}
.btn-cancel:hover { border-color: var(--primary); color: var(--primary); }

/* ==================== 工具页专属小元件 ==================== */
/* 符号面板按钮（tools/symbols.php） */
.sy-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.5rem;
  height: 2.5rem;
  font-size: 1.2rem;
  background: var(--card-soft);
  border: 1px solid var(--border);
  border-radius: var(--radius-xs);
  color: var(--text);
  cursor: pointer;
  transition: all var(--dur) var(--ease);
}
.sy-btn:hover {
  background: var(--primary-soft);
  border-color: var(--primary);
  color: var(--primary);
}

/* ==================== 工具保存失败提示 ==================== */
/* 位于工具页标题下方（tools/_header.php 输出）。
   只在保存失败时显示：默认隐藏（没有 data-state 属性），
   由 assets/js/tool-content-save.js 在失败时打上 data-state="error"。
   保存成功是后台自动行为，不向用户提示，因此没有 ok / idle 态。 */
.tool-save-state {
  display: none;
  align-items: center;
  gap: 6px;
  margin-top: 6px;
  padding: 4px 10px;
  font-size: .78rem;
  line-height: 1.4;
  border-radius: var(--radius-pill);
  max-width: 100%;
}
.tool-save-state > i { flex: 0 0 auto; font-size: .8rem; }
.tool-save-state .tool-save-text {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.tool-save-state[data-state="error"] {
  display: inline-flex;
  background: var(--danger-soft);
  color: var(--danger);
}

@media (max-width: 768px) {
  .tool-save-state { font-size: .74rem; padding: 3px 9px; }
  .tool-save-state .tool-save-text { white-space: normal; }
}
/* ==================== 响应式 ==================== */
@media (max-width: 768px) {
  .result-grid { grid-template-columns: repeat(auto-fill, minmax(130px, 1fr)); gap: 8px; }
  .result-card { padding: 10px 12px; }
  .result-value { font-size: .95rem; }

  .u-tabs { gap: 4px; }
  .u-tab-btn {
    font-size: .84rem;
    padding: 7px 13px;
    min-height: 38px;
  }

  .tool-section { padding: 14px; }
  .upload-area { padding: 24px 12px; }
}


/* ==================== 裸控件兜底（只管没写 class 的） ==================== */
/*
 * 为什么需要这一段：
 *   60+ 个工具页里大量 <input> / <button> 是纯裸标签 —— 只有写了
 *   class="form-input" / "btn" 的才吃得到样式，其余全是浏览器默认外观
 *   （灰底方块 input、没圆角没阴影的 button），这就是「很多工具页看着没有层次感」的根源。
 *
 * 为什么用 :not([class])：
 *   只兜底**一个 class 都没有**的控件。凡是页面自己用了组件类
 *   （.form-input / .btn / .tool-* / 自定义类）的一律不碰，不会覆盖既有设计。
 *   想让某个控件保持原样，给它加任意 class 即可。
 */
.tool-page-body input:not([class]),
.tool-page-body textarea:not([class]),
.tool-section input:not([class]),
.tool-section textarea:not([class]) {
  width: 100%;
  padding: 10px 14px;
  font-size: 14px;
  font-family: inherit;
  color: var(--text);
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-xs);
  transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.tool-page-body input:not([class])::placeholder,
.tool-page-body textarea:not([class])::placeholder,
.tool-section input:not([class])::placeholder,
.tool-section textarea:not([class])::placeholder { color: var(--text-3); }

.tool-page-body input:not([class]):focus,
.tool-page-body textarea:not([class]):focus,
.tool-section input:not([class]):focus,
.tool-section textarea:not([class]):focus {
  outline: none;
  border-color: var(--primary);
  box-shadow: 0 0 0 3px var(--primary-ring);
}

/* 勾选项/单选项不该被拉成整行 */
.tool-page-body input[type="checkbox"]:not([class]),
.tool-page-body input[type="radio"]:not([class]),
.tool-section input[type="checkbox"]:not([class]),
.tool-section input[type="radio"]:not([class]) {
  width: auto;
  box-shadow: none;
  padding: 0;
}

.tool-page-body button:not([class]),
.tool-section button:not([class]) {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  min-height: 40px;
  padding: 9px 18px;
  font-size: 14px;
  font-family: inherit;
  font-weight: 600;
  color: var(--text-onbrand);
  background: var(--grad-brand);
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-xs);
  cursor: pointer;
  transition: transform var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.tool-page-body button:not([class]):hover,
.tool-section button:not([class]):hover {
  transform: translateY(-1px);
  box-shadow: var(--shadow-sm);
}
.tool-page-body button:not([class]):active,
.tool-section button:not([class]):active { transform: translateY(0); }

/* 裸标题（工具页里直接写 <h3> 的分区标题）给一点层级 */
.tool-page-body h2:not([class]),
.tool-page-body h3:not([class]),
.tool-page-body h4:not([class]) {
  display: flex;
  align-items: center;
  gap: 6px;
  margin: 18px 0 10px;
  font-size: .98rem;
  font-weight: 700;
  color: var(--text);
}
.tool-page-body h2:not([class]) > i,
.tool-page-body h3:not([class]) > i,
.tool-page-body h4:not([class]) > i { color: var(--primary); }

/* ==================== 以下四段 2026-10-13 从 content.css / homepage.css 并入 ====================
 * 原因：前台 CSS 拆成 full / tool 两个合并包（见 includes/config.php 的 front_css_links），
 * tool 栈（tools/*.php）不再加载 homepage.css 与 content.css 整包
 * （商城/购物车/个人中心/订单/商品/地址等约 60KB 与工具页无关），
 * 但工具页仍需要下面四段，故并入两个栈都会加载的本文件。
 *   1) 工具页容器（原 content.css:949）—— .tool-page*，60+ 个工具页共用
 *   2) 空状态（原 content.css:730）—— browserinfo 等工具在用
 *   3) 工具中心（原 content.css:2446）—— tools.php 工具中心页
 *   4) 日历组件（原 homepage.css:669）—— tools/calendar.php 与首页侧栏卡共用
 * 级联安全性论证见 .workbuddy/tools/css_split_analyze.py。 */

/* ==================== 工具页容器（tools/ 63 个页面共用） ====================
 * 工具页是直接访问的独立页（tools/xxx.php），不经过单入口 layout 的 <main>。
 * 这里自己承担 .container 的职责（限宽 + 左右留白 + 上下间距），
 * 不要再套一层 .container，否则限宽和留白会被叠加两次。 */
.tool-page {
  max-width: var(--container-max);
  margin: 0 auto;
  padding: 24px 20px 48px;
}

.tool-page-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  margin-bottom: 16px;
}

.tool-header-left { min-width: 0; }

.tool-page-title {
  font-size: 1.35rem;
  font-weight: 700;
  color: var(--text);
  display: flex;
  align-items: center;
  gap: 10px;
}

.tool-page-title i { color: var(--primary); }

.tool-header-actions {
  display: flex;
  align-items: center;
  gap: 8px;
}

.tool-action-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: 44px;
  padding: 0 14px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  background: var(--card);
  color: var(--text-2);
  font-size: 0.88rem;
  cursor: pointer;
  transition: all .2s;
}

.tool-action-btn:hover {
  border-color: var(--primary);
  color: var(--primary);
  background: var(--primary-soft);
}

.tool-share-btn { color: var(--primary); border-color: var(--primary); }

.tool-page-body { min-width: 0; }

/* 工具页移动端适配（原 content.css:1383-1388）
 * ★ 必须紧跟「工具页容器」：同权重选择器靠源码顺序决胜。
 *   2026-10-13 回归对比抓到：这几条若留在 poster.css，会被后加载的
 *   tools.css 基础规则顶掉，手机上 .tool-page 一直吃桌面内边距。 */
@media (max-width: 768px) {
  .tool-page { padding: 16px 16px 32px; }
  .tool-page-title { font-size: 1.15rem; }
  .tool-action-btn .btn-text { display: none; }
  .tool-action-btn { padding: 0 12px; }
}

/* ==================== 空状态 ==================== */
.empty-state {
  text-align: center;
  padding: 4rem 1rem;
  color: var(--text-3);
}

.empty-state i {
  font-size: 3rem;
  margin-bottom: 1rem;
  opacity: 0.5;
}

/* ==================== 工具中心（tools.php） ==================== */
.tool-search {
  position: relative;
  display: flex;
  align-items: center;
  margin-bottom: 1rem;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  padding: 0 .4rem 0 0;
  box-shadow: var(--shadow-xs);
  transition: border-color var(--dur), box-shadow var(--dur);
}
.tool-search:focus-within {
  border-color: var(--primary);
  box-shadow: 0 0 0 3px var(--primary-ring);
}
.tool-search > .fas {
  padding: 0 .2rem 0 1rem;
  color: var(--text-3);
  font-size: .85rem;
}
.tool-search input {
  flex: 1;
  min-width: 0;
  border: none;
  outline: none;
  background: transparent;
  color: var(--text);
  font-size: .92rem;
  padding: .68rem .3rem;
  font-family: inherit;
}
.tool-search button {
  flex-shrink: 0;
  border: none;
  cursor: pointer;
  background: var(--grad-brand);
  color: var(--text-inv);
  font-size: .85rem;
  font-weight: 600;
  padding: .48rem 1.2rem;
  border-radius: var(--radius-pill);
  transition: opacity var(--dur);
}
.tool-search button:hover { opacity: .88; }

.tool-cats {
  display: flex;
  gap: .5rem;
  overflow-x: auto;
  padding-bottom: .5rem;
  margin-bottom: 1.25rem;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}
.tool-cats::-webkit-scrollbar { display: none; }

.tool-cat {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  gap: .35rem;
  padding: .45rem .95rem;
  min-height: 38px;
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  background: var(--card);
  color: var(--text-2);
  font-size: .85rem;
  white-space: nowrap;
  transition: all var(--dur) var(--ease);
}
.tool-cat:hover { border-color: var(--primary); color: var(--primary); }
.tool-cat.active {
  background: var(--grad-brand);
  border-color: transparent;
  color: var(--text-inv);
  font-weight: 650;
  box-shadow: var(--shadow-brand);
}
.tool-cat span {
  font-size: .72rem;
  opacity: .7;
  font-variant-numeric: tabular-nums;
}

/* 「推荐工具」区块：后台指定、独立于分类列表，
   故视觉上要能和下面的普通分类区块区分开（暖色标题 + 强调边框）。
   注意它只在「纯浏览态」（无搜索词、无分类筛选）出现，
   且里面的工具已从各自分类中剔除，两边不会重复。 */
.recommended-tools-group {
  padding: 1.1rem 1.15rem .35rem;
  border: 1px solid color-mix(in srgb, var(--warning, #f59e0b) 26%, transparent);
  border-radius: var(--radius-md, 12px);
  background: linear-gradient(
    180deg,
    color-mix(in srgb, var(--warning, #f59e0b) 7%, transparent),
    transparent 70%
  );
}

.recommended-tools-group .tool-group-title .fas { color: var(--warning, #f59e0b); }

.tool-group { margin-bottom: 2rem; }

.tool-group-title {
  font-size: 1rem;
  font-weight: 700;
  display: flex;
  align-items: center;
  gap: .45rem;
  margin-bottom: 1rem;
}
.tool-group-title .fas { color: var(--primary); }
.tool-group-count {
  font-size: .72rem;
  font-weight: 600;
  color: var(--text-3);
  background: var(--bg-soft);
  border-radius: var(--radius-pill);
  padding: .1rem .5rem;
}

/* 工具中心：横版卡片（左图标 + 右上工具名 / 右下描述）。
   与首页那套竖版 .tool-card 分开 —— 首页是窄侧栏里的两列小格，
   横排在那么窄的列里会被挤成一条，所以这里用 .tool-grid-wide 做作用域，
   只改工具中心，首页不受影响。
   列数：桌面 auto-fill 自适应，1200px 容器下落在 5 列（4~6 之间）；
   768px 以下强制两列（移动端需求）。 */
.tool-grid-wide {
  grid-template-columns: repeat(auto-fill, minmax(210px, 1fr));
  gap: 12px;
}

/* 卡片尺寸统一：名称恒 1 行 + 描述恒 2 行（见下方 min-height），
   内容高度本身就固定了；这里再补一个 min-height 兜底，字体渲染有差异也不会高低不一。 */
.tool-grid-wide .tool-card {
  display: flex;
  align-items: center;
  gap: .7rem;
  padding: .7rem .85rem;
  min-height: 76px;
  text-align: left;
}

.tool-grid-wide .tool-card .tool-icon {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 42px;
  height: 42px;
  margin: 0;
  border-radius: var(--radius-sm, 12px);
  background: var(--primary-soft);
  font-size: 1.15rem;
  transition: background var(--dur);
}
/* 悬停时卡片底色变成 --primary-soft，图标块再保持同色就糊成一团，
   翻成实底把它「顶」出来，既保住对比又像一次轻微反馈。 */
.tool-grid-wide .tool-card:hover .tool-icon { background: var(--card); }

.tool-grid-wide .tool-card .tool-body {
  flex: 1;
  min-width: 0;
}

.tool-grid-wide .tool-card .tool-name {
  font-size: 13.5px;
  line-height: 1.3;
  min-height: 1.3em;   /* 名称恒占一行（空名称也不塌），卡片高度才不会飘 */
  font-weight: 650;
  color: var(--text);
  margin-bottom: 2px;
}

/* 描述固定占「两行」的位置 —— 这是卡片尺寸统一的关键。
   只写 line-clamp 只是「最多两行」，1 行的描述会让卡片矮一截，
   同屏里出现 68px / 86px 两种高度，一眼就看出没对齐。
   用 min-height 把两行的高度先占住（line-height 1.45 × 2 = 2.9em），
   超出的部分再交给 line-clamp 截断。 */
.tool-grid-wide .tool-card .tool-desc {
  font-size: 11.5px;
  color: var(--text-3);
  display: -webkit-box;
  -webkit-line-clamp: 1;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

@media (max-width: 768px) {
  .tool-grid-wide {
    grid-template-columns: repeat(2, 1fr);
    gap: 8px;
  }
  .tool-grid-wide .tool-card {gap: .55rem;padding: .6rem .65rem;min-height: 70px;}
  .tool-grid-wide .tool-card .tool-icon { width: 36px; height: 36px; font-size: 1rem; }
  .tool-grid-wide .tool-card .tool-name { font-size: 12.5px; }
  .tool-grid-wide .tool-card .tool-desc {font-size: 11px;}
  .tool-search input { font-size: 16px; }
}

@media (max-width: 420px) {
  .tool-grid-wide .tool-card { gap: .5rem; padding: .55rem .6rem; min-height: 68px; }
  .tool-grid-wide .tool-card .tool-icon { width: 32px; height: 32px; font-size: .95rem; }
}


/* ==================== 日历组件（首页侧栏卡 + 工具页共用） ====================
 * tools/calendar.php 与 templates/pages/index.php 共用 assets/js/calendar.js 渲染
 * .calendar-day markup。拆分前工具页的桌面基础样式一直靠 homepage.css 的这段
 * （calendar.php 自己的 <style> 只有媒体查询里的尺寸微调）。
 * 放到两个栈都会加载的 tools.css，tool 栈不加载 homepage.css 也不会缺样式。
 * 注意：calendar.php 还通过 $extraHead 单独加载 calendar.css，它排在这些 <link> 之后，
 * 冲突属性以 calendar.css 为准 —— 与拆分前的层叠顺序完全一致。 */
.calendar-day {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1px;
  padding: 4px 0;
  border-radius: var(--radius-xs);
  cursor: pointer;
  transition: background var(--dur), color var(--dur);
  min-height: 34px;
}
.calendar-day:hover { background: var(--bg-soft); }

.calendar-day .day-num {
  font-size: 13px;
  font-weight: 650;
  color: var(--text);
  line-height: 1.2;
}
.calendar-day .day-lunar {
  font-size: 9px;
  color: var(--text-3);
  line-height: 1.2;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 100%;
}
.calendar-day .day-lunar.special { color: var(--primary); }

.calendar-day.today {
  background: var(--grad-brand);
  box-shadow: var(--shadow-brand);
}
.calendar-day.today .day-num,
.calendar-day.today .day-lunar { color: var(--text-onbrand); }
.calendar-day.today .day-lunar.special { color: var(--text-onbrand); }

.calendar-day.selected { box-shadow: inset 0 0 0 2px var(--primary); }

@media (max-width: 768px) {
  .calendar-day { min-height: 32px; padding: 3px 0; }
  .calendar-day .day-num { font-size: 12px; }
  .calendar-day .day-lunar { font-size: 8px; }
}
