/*
 * blog-site.css — 博客站视觉层
 *
 * 本站专属资产，手写，不进 _tools/sync_blog_site.js 同步清单（进了会被真源覆盖）。
 * 设计约束：
 * - 渐变只出现在 hero 薄雾与主按钮，其余纯色；彩色投影一律不用。
 * - 圆角只留两档：--v-r 12px 卡片、--v-r-sm 8px 控件。
 * - 字重只用 400/500/600/700：中文字体栈里 750/650 没有对应字面，会被静默回退到
 *   700 或 600，写了等于没写，还造成粗细不匀。中文字体栈须显式锁定。
 * - 层级靠字号差、字重差、色阶差承担，不靠描边，不靠底色。
 * - 深色不是反色而是重算：阴影几乎消失、描边更亮、正文对比更高。
 *
 * 装饰总量全站最低：docs / product / support / download / search 的视觉重心是检索
 * 与分流，可以用卡片与色块；博客读者已经决定读，需要的是长时间阅读的舒适度。
 * 唯一主动增加的装饰是确定性封面（.v-cover-gen）：按 slug 哈希生成几何占位图，
 * 同一篇文章每次构建得到同一张、不同文章不同图，它承担识别功能。
 */

/* ═══════════════════════ 设计令牌 ═══════════════════════ */
:root {
  /* 品牌色：只用于主按钮、链接、标签。不做大面积铺色 */
  --v-brand: #2B5CFF;
  --v-brand-hover: #1E48E0;
  --v-accent: #E11D48;

  /*
   * 中性阶：承担 95% 的界面。刻意偏冷灰（与品牌蓝同色温），
   * 冷灰比纯灰更「贵」——纯灰容易显脏，冷灰显通透
   */
  --v-ink: #0A0F1C;
  --v-ink-2: #2A3547;
  --v-ink-3: #5A6478;
  --v-ink-4: #8A94A6;
  --v-ink-5: #B4BCC9;

  /* 表面：三层。页面底 / 卡片 / 悬浮 */
  --v-bg: #FFFFFF;
  --v-bg-alt: #F7F9FB;
  --v-bg-sunken: #F1F4F7;
  --v-surface: #FFFFFF;
  --v-surface-hover: #F7F9FB;

  /* 描边：只用两档。v1 的四档 hairline 在深色下全部糊成一片 */
  --v-line: #E4E8EE;
  --v-line-2: #D0D7E0;

  /* 阴影：中性黑 + 低透明。层级越高越扩散越淡 */
  --v-sh-1: 0 1px 2px rgba(10, 15, 28, .05);
  --v-sh-2: 0 2px 4px rgba(10, 15, 28, .04), 0 8px 20px -12px rgba(10, 15, 28, .14);
  --v-sh-3: 0 4px 8px rgba(10, 15, 28, .05), 0 18px 44px -18px rgba(10, 15, 28, .20);

  /* 圆角：两档，收紧 */
  --v-r: 12px;
  --v-r-sm: 8px;

  /* 排版尺度：clamp 让同一规则在 390px 与 1440px 都成立，不需要断点堆砌 */
  --v-measure: 68ch;      /* 正文单行上限——超过眼睛就要回扫 */
  --v-body-size: 17px;
  --v-body-lh: 1.85;
  --v-gap: clamp(48px, 7vw, 88px);   /* 大区块间距 */

  /*
   * 字体栈：中文字体必须显式列出并在前，否则不同系统渲染差异巨大。
   * -apple-system 保证 mac 用 SF，中文回退 PingFang；Windows 走 Segoe + 微软雅黑
   */
  --v-font: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC",
             "Hiragino Sans GB", "Microsoft YaHei", "Source Han Sans SC",
             "Noto Sans CJK SC", sans-serif;
  --v-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Monaco,
            Consolas, "Liberation Mono", monospace;
}

[data-theme="dark"] {
  /* 深色下品牌色提亮：#2B5CFF 在深底上对比度不足，链接会看不清 */
  --v-brand: #6D93FF;
  --v-brand-hover: #8FACFF;
  --v-accent: #FF6B8A;

  --v-ink: #F2F5F9;
  --v-ink-2: #C8D2E0;
  --v-ink-3: #97A3B5;
  --v-ink-4: #6E7A8C;
  --v-ink-5: #4A5464;

  --v-bg: #0B0F16;
  --v-bg-alt: #111722;
  --v-bg-sunken: #070A10;
  --v-surface: #121822;
  --v-surface-hover: #18202C;

  /* 深色下描边必须更亮才看得见边界——但不能亮到变成「描边风格」 */
  --v-line: #232C3A;
  --v-line-2: #303B4C;

  /* 深色下阴影几乎不可见（没有光源），所以靠描边与表面差承担层级 */
  --v-sh-1: 0 1px 2px rgba(0, 0, 0, .40);
  --v-sh-2: 0 2px 4px rgba(0, 0, 0, .30), 0 8px 20px -12px rgba(0, 0, 0, .55);
  --v-sh-3: 0 4px 8px rgba(0, 0, 0, .35), 0 18px 44px -18px rgba(0, 0, 0, .70);
}

/* ═══════════════════════ 基础接管 ═══════════════════════ */
/*
 * 必须接管 body 与 html 的背景/字号。
 *
 * 本站的配色令牌是 --v-*（纯白底），而主站 site.css 给 body 的是
 * var(--bg)（主站偏灰底）—— 两套令牌并存时，body 是灰的、
 * .v-hero 与卡片是白的，页面出现"灰底上摆一块白板"的割裂感。
 *
 * 只接管背景、字号、字体栈与滚动条：布局与 chrome（导航/页脚）
 * 仍归主站管，blog 站不重写公共 chrome —— 这是子站的第一条纪律。
 */
html { background: var(--v-bg); }
body {
  background: var(--v-bg);
  color: var(--v-ink);
  font-family: var(--v-font);
}
[data-theme="dark"] body { background: var(--v-bg); color: var(--v-ink); }

/*
 * 滚动条跟随主题。
 * 原先读 --v-line-2（#d0d7e0），对白底 1.45:1；暗色 1.70:1 —— 都不达 3:1，
 * 而 blog 是全站唯一的第四套滚动条色。现在统一读 tokens.css 的 --qf-scrollbar-*，
 * blog 自己不再维护一套滚动条颜色。track 也交给令牌（transparent），
 * 否则深色下会把 --v-bg 画成一条实心槽，和别站不一致。
 */
html { scrollbar-color: var(--qf-scrollbar-thumb) var(--qf-scrollbar-track); }

/* ═══════════════════════ 容器 ═══════════════════════ */
.v-wrap { width: min(1120px, 100% - 48px); margin-inline: auto; }
/*
 * 正文容器：比卡片区窄一档，让「读」与「逛」有物理分界。
 * 740px → 72ch（16px 下实测 621px），与 docs 的 --docs-prose-maxw
 * 同一个判据。740px 在 16px 字号下是 46 个汉字/行（中文排版的常见上限是 40），
 * 而 docs 正文 621px = 38.8 个 —— 同是正文，「文档」比「文章」密一档没有道理。
 * 用 ch 而不是写死 621px：字号档一变，行宽跟着走，不会再各自漂移。
 */
.v-wrap-narrow { width: min(72ch, 100% - 48px); margin-inline: auto; }

/* ═══════════════════════ 主视觉 ═══════════════════════ */
/*
 * 纪律①的落点：全站唯一的渐变区域。
 * v1 是三层 radial + 双向细网格；v2 只留一层极淡的色雾 + 一层网格质地，
 * 且网格透明度降到几乎看不见——它存在的意义是「让大面积纯色不死」，
 * 不是「被看见」。
 */
.v-hero {
  position: relative;
  overflow: hidden;
  padding: clamp(56px, 8vw, 104px) 0 clamp(40px, 5vw, 64px);
  background:
    radial-gradient(1200px 480px at 18% -10%, rgba(43, 92, 255, .09), transparent 60%),
    radial-gradient(900px 400px at 88% 8%, rgba(225, 29, 72, .05), transparent 62%),
    var(--v-bg);
  border-bottom: 1px solid var(--v-line);
}
[data-theme="dark"] .v-hero {
  background:
    radial-gradient(1200px 480px at 18% -10%, rgba(109, 147, 255, .13), transparent 60%),
    radial-gradient(900px 400px at 88% 8%, rgba(255, 107, 138, .07), transparent 62%),
    var(--v-bg);
}

/* 细网格：透明度 .02，作用是给纯色一点质地。深色下更弱 */
.v-hero::before {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background-image:
    linear-gradient(var(--v-line) 1px, transparent 1px),
    linear-gradient(90deg, var(--v-line) 1px, transparent 1px);
  background-size: 64px 64px;
  opacity: .35;
  mask-image: linear-gradient(to bottom, rgba(0, 0, 0, .5), transparent 72%);
  -webkit-mask-image: linear-gradient(to bottom, rgba(0, 0, 0, .5), transparent 72%);
}

/*
 * 主视觉：居中刊头（index / archive 共用）
 *
 * 纯文字 hero 没有右侧配图，左对齐时文字只占容器左侧 45%，右半屏整片空着，
 * 读起来像「图没加载出来」。收成居中阅读栏，空白变成对称的版心留白。
 *
 * 居中必须写在基础规则里：先前放在 min-width:1024 的媒体块，而 h1/lead 的
 * 基础规则在本文件更后面、同特异度 → margin:0 0 18px 反压 margin-inline:auto，
 * 结果标题和导语贴在 820px 版心左缘、只有 kicker 和按钮居中（1440 下标题中心
 * 偏离视口中心 223px）。同一条版式不能靠源码顺序决定成败，故统一落到基础层。
 */
.v-hero-inner {
  position: relative;
  z-index: 1;
  max-width: 820px;
  margin-inline: auto;
  text-align: center;
}

.v-kicker {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin: 0 0 20px;
  font-size: 13px;
  font-weight: 500;
  letter-spacing: .02em;
  color: var(--v-ink-3);
}
/* kicker 前的小方块：形状而非圆点——方块比圆点更像「标记」 */
.v-kicker::before {
  content: "";
  width: 8px;
  height: 8px;
  border-radius: var(--r-sm, 8px);
  background: var(--v-brand);
}

.v-hero h1 {
  margin: 0 auto 18px;
  font-family: var(--v-font);
  font-size: 36px;
  line-height: 1.08;
  letter-spacing: -.028em;
  font-weight: 700;
  color: var(--v-ink);
  max-width: 22ch;
  text-wrap: balance;      /* 中文标题避免出现孤字行 */
}

.v-hero-lead {
  margin: 0 auto 28px;
  max-width: 58ch;
  font-size: 16px;
  line-height: 1.75;
  color: var(--v-ink-3);
}

.v-hero-cta { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; justify-content: center; }

/* ═══════════════════════ 控件 ═══════════════════════ */
/*
 * 纪律：控件只有两档视觉——实心（主）与描边（次）。
 * v1 的第三档「玻璃幽灵按钮」在浅色底上几乎不可见，等于没有。
 */
.v-btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  /*
   * 归一到共享按钮刻度：中档 = 44px。
   * 原 padding:10px 18px + lh1.4 + 1px 边框 = 41.6px，比共享 .btn 的 44 矮 2.4px，
   * 两者并排（404 页就有）会看出来。这里同时给 min-height，因为 11+11+19.6+2=43.6
   * 仍差 0.4px —— 只改 padding 到不了 44，必须靠 min-height 兜底（也是触控下限）。
   */
  min-height: 44px;
  padding: 11px 24px;
  border-radius: var(--r-md, 12px);
  border: 1px solid transparent;
  font-family: var(--v-font);
  font-size: 14px;
  font-weight: 500;
  line-height: 1.4;
  text-decoration: none;
  cursor: pointer;
  transition: background-color .16s ease, border-color .16s ease, color .16s ease;
}
.v-btn svg { width: 16px; height: 16px; flex: 0 0 auto; }

.v-btn-primary { background: var(--v-brand); color: #fff; }
.v-btn-primary:hover { background: var(--v-brand-hover); }
[data-theme="dark"] .v-btn-primary { color: #0B0F16; }

.v-btn-ghost { background: transparent; border-color: var(--v-line-2); color: var(--v-ink-2); }
.v-btn-ghost:hover { border-color: var(--v-ink-4); color: var(--v-ink); }

.v-btn:focus-visible,
.v-chip:focus-visible,
.v-tag:focus-visible {
  outline: 2px solid var(--v-brand);
  outline-offset: 3px;
  border-radius: var(--r-sm, 8px);
}

/* ═══════════════════════ 筛选隐藏 ═══════════════════════ */
/*
 * 必须显式声明 display，否则 hidden 属性无效：
 * .v-row 用了 display:grid，而 [hidden] 的 UA 样式是 display:none，
 * 作者样式的优先级更高 → 筛选时 hidden=true 的行照样显示。
 * 表现是「点标签，列表没变化」，而控制台没有报错——纯视觉层静默失效。
 * （这条是 v1 在 jsdom 验证中抓出来的，换类名后必须一并迁移。）
 */
.v-row[hidden], .v-lead[hidden] { display: none !important; }

/*
 * 整块可点击（stretched link）。
 *
 * 为什么需要它：卡片是 <article>（内含 <a> 标签链接），但 UX 上希望整块可点。
 * 做法是让标题里的 <a> 用 ::after 铺满整卡，交互区就是整块。
 *
 * 关键约束：标签链接必须 z-index 更高，否则会被这层透明覆盖层盖住、
 * 点不动。所以 .v-tag / .v-row-foot > span 的 position:relative + z-index 是
 * 硬要求，不是锦上添花——去掉任何一个，标签就会"点不动"，
 * 而页面上看不出任何异常。
 */
.v-stretch { color: inherit; text-decoration: none; }
.v-stretch::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
}
/* .v-lead / .v-row 必须是定位祖先，否则 ::after 会相对更外层定位 */
.v-lead, .v-row { position: relative; }
/* 覆盖层之上的元素：可点且优先 */
.v-lead .v-tag, .v-row .v-tag,
.v-lead .v-row-foot, .v-row .v-row-foot { position: relative; z-index: 2; }

.v-lead:focus-within, .v-row:focus-within { border-color: var(--v-line-2); }
.v-lead .v-stretch:focus-visible, .v-row .v-stretch:focus-visible {
  outline: 2px solid var(--v-brand);
  outline-offset: 3px;
  border-radius: var(--r-sm, 8px);
}

/* ═══════════════════════ 头条（精选文章）═══════════════════════ */
/*
 * 纪律②的落点：首页唯一一处「打破栅格」的元素。
 * v1 用一个 span-2 的网格卡；v2 改成整宽横幅——因为头条和列表项
 * 应该是两种不同的东西，如果只是宽度不同，用户会读成「这张卡比较大」，
 * 而不会读成「这是头条」。版式差异必须大到不需要文字解释。
 */
/*
 * 高度策略（实测连踩三次，这是最终可行解）：
 * ① 封面列设 aspect-ratio → 按列宽算高，573×4/3=764px，撑满整页
 * ② 只给封面列 min-height → 仍被 grid 拉到 560px
 * ③ 横幅设 aspect-ratio + max-height → 与内容高度打架，文档反而变高
 *
 * 结论：高度由文字列决定，封面只是跟随。
 * 所以 .v-lead 不设任何 aspect-ratio / min-height，
 * 让它等于 max(文字列高度, 封面列的 min-height)；
 * 封面列用 position:relative + height:100% 填满，
 * 它的 min-height 只作为"文字太少时"的兜底（不会反向撑高）。
 *
 * 同时把封面容器从 absolute 方案改成 flex 流内方案 ——
 * .v-cover-gen 用绝对定位需要一层定位祖先，在 grid 里容易失控，
 * 改成 display:grid + place-items 让它自己撑满，行为可预测。
 */
.v-lead {
  display: grid;
  /*
   * 同 .v-row：第二列必须显式固定，且两列都允许收缩。
   * 否则文字列里的长摘要会把 1fr 顶到整行宽，右侧文字区被挤到下一行
   * （实测 .v-lead-body 拿到 1120px = 整行宽）。
   */
  grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr);
  gap: 0;
  align-items: stretch;
  border: 1px solid var(--v-line);
  border-radius: var(--v-r);
  background: var(--v-surface);
  overflow: hidden;
  text-decoration: none;
  color: inherit;
  transition: border-color .2s ease;
}
.v-lead:hover { border-color: var(--v-line-2); }

.v-lead-cover {
  /*
   * 定位祖先不可省（.v-cover-date 是 absolute），但不给 aspect-ratio：
   * 在 grid stretch 下它会按列宽算高并把整块横幅撑满（实测踩过）。
   */
  position: relative;
  display: grid;
  overflow: hidden;
  background: var(--v-bg-sunken);
  /*
   * min-height 是唯一的高度来源，且只在"文字比它矮"时起作用，
   * 不会反向把整行拉高 —— 因为它比文字列矮（~280px）。
   */
  min-height: 280px;
}
.v-lead-cover img { width: 100%; height: 100%; object-fit: cover; display: block; }
.v-lead:hover .v-lead-cover img { transform: scale(1.02); }
.v-lead-cover { transition: transform .4s cubic-bezier(.2, .7, .3, 1); }

.v-lead-body {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 14px;
  padding: clamp(28px, 4vw, 48px);
}

.v-lead-badge {
  align-self: flex-start;
  padding: 4px 10px;
  border-radius: var(--r-sm, 8px);
  background: var(--v-ink);
  color: var(--v-bg);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: .04em;
}
[data-theme="dark"] .v-lead-badge { background: var(--v-ink); color: var(--v-bg); }

.v-lead-title {
  margin: 0;
  font-size: 36px;
  line-height: 1.18;
  letter-spacing: -.024em;
  font-weight: 700;
  color: var(--v-ink);
}
.v-lead:hover .v-lead-title { color: var(--v-brand); }

.v-lead-sum {
  margin: 0;
  max-width: 46ch;
  font-size: 16px;
  line-height: 1.72;
  color: var(--v-ink-3);
}

@media (max-width: 1023px) {
  .v-lead { grid-template-columns: minmax(0, 1fr); }
  .v-lead-cover { aspect-ratio: 16 / 9; }
}

/* ═══════════════════════ 列表行（不是卡片）═══════════════════════ */
/*
 * 纪律③的落点：v1 的 bl-grid 是三列卡片墙。卡片墙适合「逛」，
 * 博客列表是「读」的入口，用户要的是扫标题和摘要，不是欣赏边框。
 * v2 改成全宽列表行：一行一篇文章，行间一条 1px 线。
 * 这也顺带解决了卡片墙的真实问题：卡片墙在 320px 起跳，
 * 1440px 下会挤成 4 列，每列只有 320px，中文标题必然折成 3 行。
 */
/*
 * 杂志风：列表区从「单列行」改为「响应式卡片网格」。
 * auto-fill + minmax 让列数随容器自适应（1120px 下约 3 列），
 * 列宽下限 290px 保证中文标题不被压成三行。
 */
.v-list {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(290px, 1fr));
  gap: clamp(18px, 2.2vw, 26px);
  margin-top: 10px;
}

/*
 * 杂志风：卡片（图上文下）。
 * 仍保留 position:relative —— .v-stretch::after 要相对它铺满整卡。
 * 封面在 HTML 里是第二个子节点，用 order:-1 提到顶部（图片先、文字后）。
 */
.v-row {
  display: flex;
  flex-direction: column;
  border: 1px solid var(--v-line);
  border-radius: var(--v-r);
  background: var(--v-surface);
  overflow: hidden;
  text-decoration: none;
  color: inherit;
  /*
   * 静息层次从 polish-layer 第 20 节的 :where 兜底搬回站内（同 search 的
   * .srch-how-card）。卡片墙不该只在 hover 时才存在。
   */
  box-shadow: var(--shadow-sm);
  transition: border-color .18s ease, box-shadow .18s ease, transform .18s ease;
}
.v-row:hover {
  border-color: var(--v-line-2);
  box-shadow: var(--shadow-md);
  transform: translateY(-2px);
}

.v-row-main {
  min-width: 0;            /* 允许收缩，避免长标题顶开网格列 */
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: clamp(16px, 1.8vw, 22px);
}

.v-row-title {
  margin: 0 0 8px;
  font-size: 18px;
  line-height: 1.34;
  letter-spacing: -.016em;
  font-weight: 600;
  color: var(--v-ink);
  transition: color .16s ease;
}
.v-row:hover .v-row-title { color: var(--v-brand); }

.v-row-sum {
  margin: 0 0 12px;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  max-width: 60ch;
  font-size: 14px;
  line-height: 1.7;
  color: var(--v-ink-3);
}

.v-row-foot {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 14px;
  align-items: center;
  font-size: 13px;
  color: var(--v-ink-4);
  font-variant-numeric: tabular-nums;
}
.v-row-foot .v-dot { width: 3px; height: 3px; border-radius: 50%; background: currentColor; opacity: .6; }

.v-row-cover {
  order: -1;               /* 卡片图上文下：封面提到顶部 */
  position: relative;
  display: grid;
  aspect-ratio: 16 / 9;
  min-height: 0;
  max-height: none;
  border-radius: 0;
  overflow: hidden;
  background: var(--v-bg-sunken);
}
.v-row-cover img { width: 100%; height: 100%; object-fit: cover; display: block; transition: transform .4s cubic-bezier(.2, .7, .3, 1); }
.v-row:hover .v-row-cover img { transform: scale(1.03); }

@media (max-width: 767px) {
  /* 单列卡片：封面已在默认态是顶部 16:9，无需额外处理 */
  .v-row { gap: 0; }
}

/* ═══════════════════════ 确定性生成封面 ═══════════════════════ */
/*
 * 替代 v1 的「QF 字母 + 斜纹」。
 * 图案由 slug 哈希决定：同一篇永远同一张图（确定性构建的前提），
 * 不同篇图案不同（识别功能）。
 * 三种构图（同心弧 / 斜切块 / 点阵）按哈希取模，覆盖率够用且都好看。
 * 全部走 CSS，不引入图片依赖，也不需要构建期生成二进制。
 */
.v-cover-gen {
  /*
   * 用 grid + place-items 而不是 position:absolute; inset:0。
   * 绝对定位需要一层准确的定位祖先，在 grid 拉伸场景里很容易失控
   * （实测：inset:0 的封面一度把整个横幅撑到 560px）。
   * 流内方案让它自己占满父格，行为可预测，也少一层定位关系。
   */
  display: block;
  width: 100%;
  height: 100%;
  min-height: inherit;
}

/* 构图一：同心弧——用 repeating-radial-gradient 画等距圆环 */
.v-cover-gen[data-art="1"] {
  background:
    repeating-radial-gradient(circle at 30% 30%, rgba(255,255,255,.20) 0 1.5px, transparent 1.5px 18px),
    linear-gradient(135deg, var(--v-ink-4), var(--v-ink-3));
}
/* 构图二：斜切块——45° 硬边色块，像印刷品的套印偏移 */
.v-cover-gen[data-art="2"] {
  background:
    linear-gradient(118deg, transparent 42%, rgba(255,255,255,.16) 42% 58%, transparent 58%),
    linear-gradient(118deg, transparent 66%, rgba(255,255,255,.10) 66% 74%, transparent 74%),
    linear-gradient(135deg, var(--v-ink-3), var(--v-ink-5));
}
/* 构图三：点阵——两组错位的点制造纵深 */
.v-cover-gen[data-art="3"] {
  background:
    radial-gradient(rgba(255,255,255,.26) 1.2px, transparent 1.2px) 0 0 / 16px 16px,
    radial-gradient(rgba(255,255,255,.14) 1.2px, transparent 1.2px) 8px 8px / 16px 16px,
    linear-gradient(135deg, var(--v-ink-5), var(--v-ink-3));
}
/* 兜底：哈希落在 0 或超出范围时用构图一，不允许出现纯色块 */
.v-cover-gen[data-art="0"], .v-cover-gen:not([data-art]) {
  background: linear-gradient(135deg, var(--v-ink-4), var(--v-ink-3));
}
/* 深色下压暗：这些图案是浅色叠加，在深底上会过亮 */
[data-theme="dark"] .v-cover-gen { filter: brightness(.62) saturate(.8); }

/*
 * 封面日期：改成一个纯文字角标，不加底色胶囊——少一层装饰。
 * 依赖封面容器 position:relative（上面已钉死），所以这里 absolute 稳定
 */
.v-cover-date {
  position: absolute;
  left: 12px;
  bottom: 10px;
  z-index: 1;
  color: #fff;
  font-size: 12px;
  font-weight: 500;
  letter-spacing: .04em;
  font-variant-numeric: tabular-nums;
  text-shadow: 0 1px 3px rgba(0, 0, 0, .5);
}
/*
 * 深色下生成封面被压暗（见上面 .v-cover-gen 的 filter），白字会看不清。
 * 不用 display:none 藏掉日期——那是信息损失；
 * 改为给日期自身加一层深色底，让它在任何底色上都可读。
 */
[data-theme="dark"] .v-cover-date {
  text-shadow: none;
  background: rgba(0, 0, 0, .55);
  padding: 2px 7px;
  border-radius: var(--r-sm, 8px);
}

/* ═══════════════════════ 标签 ═══════════════════════ */
.v-tag {
  padding: 2px 8px;
  border-radius: var(--r-sm, 8px);
  background: var(--v-bg-sunken);
  color: var(--v-ink-3);
  font-size: 12px;
  font-weight: 500;
  line-height: 1.6;
  text-decoration: none;
  transition: background-color .16s ease, color .16s ease;
}
.v-tag:hover { background: var(--v-bg-alt); color: var(--v-ink); }

/* ═══════════════════════ 筛选条 ═══════════════════════ */
/*
 * v1 用了玻璃拟态底 + 描边 + 圆角，等于给筛选条加了一个盒子。
 * 博客站只需要一行 chip，不值得一个盒子
 */
/*
 * 杂志风：顶部分类导航（吸顶）。
 * 背景用 var(--v-bg) + 轻微毛玻璃，盖住下方滚过的卡片；
 * 窄屏改为横向滚动，避免换行把导航顶高。
 */
.v-filters {
  position: sticky;
  top: 0;
  z-index: 30;
  display: flex;
  flex-wrap: nowrap;
  gap: 8px;
  align-items: center;
  margin: 0 0 28px;
  padding: 14px 0;
  background: color-mix(in srgb, var(--v-bg) 88%, transparent);
  -webkit-backdrop-filter: saturate(140%) blur(10px);
  backdrop-filter: saturate(140%) blur(10px);
  border-bottom: 1px solid var(--v-line);
  overflow-x: auto;
  scrollbar-width: none;
}
.v-filters::-webkit-scrollbar { display: none; }
.v-filters .v-chip { flex: 0 0 auto; white-space: nowrap; }
.v-filters-label {
  margin-right: 2px;
  font-size: 13px;
  font-weight: 500;
  color: var(--v-ink-3);
}

.v-chip {
  /*
   * 触控地板：实测 65×34（首页分类筛选）。chips 是页面上
   * 排得最密的一组可点元素，34px 在触屏上极易点到隔壁；44 之后整行只高 10px。
   */
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 6px 12px;
  border: 1px solid transparent;
  border-radius: var(--v-r-sm);
  background: transparent;
  color: var(--v-ink-3);
  font-family: var(--v-font);
  font-size: 13px;
  font-weight: 500;
  line-height: 1.5;
  text-decoration: none;
  cursor: pointer;
  transition: background-color .16s ease, color .16s ease;
}
.v-chip:hover { background: var(--v-bg-alt); color: var(--v-ink); }
.v-chip.is-on, .v-chip[aria-pressed="true"] {
  background: var(--v-bg-sunken);
  color: var(--v-ink);
}
.v-chip-count { margin-left: 6px; color: var(--v-ink-4); font-variant-numeric: tabular-nums; }
.v-chip.is-on .v-chip-count { color: var(--v-ink-3); }

/* ═══════════════════════ 空态 ═══════════════════════ */
/*
 * blog_posts 目前 0 行，空态是这个站的常态，必须体面。
 * v1 给了虚线框 + 渐变图标 + 居中按钮组（三个装饰）。
 * v2 收成一个纯文字块：说明这个站是干什么的 + 两个出口。虚线框去掉。
 */
.v-empty { padding: clamp(48px, 7vw, 80px) 0; text-align: left; max-width: 56ch; }
.v-empty h2 {
  margin: 0 0 10px;
  font-size: 22px;
  font-weight: 600;
  letter-spacing: -.014em;
  color: var(--v-ink);
}
.v-empty p { margin: 0 0 24px; font-size: 14px; line-height: 1.75; color: var(--v-ink-3); }
.v-empty-actions { display: flex; flex-wrap: wrap; gap: 10px; }

/* ═══════════════════════ 文章页 ═══════════════════════ */
.v-article { padding: clamp(28px, 4vw, 44px) 0 clamp(64px, 8vw, 104px); }

.v-crumb {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: center;
  margin: 0 0 28px;
  font-size: 13px;
  color: var(--v-ink-4);
}
.v-crumb a { color: var(--v-ink-3); text-decoration: none; }
.v-crumb a:hover { color: var(--v-ink); }
.v-crumb-sep { opacity: .5; }

/*
 * 文章头：v1 用渐变竖线（border-image）。
 * 渐变竖线是装饰，且 border-image 在部分浏览器对圆角/多条边框表现不一致。
 * v2 换成更安静的方案：eyebrow（小标签）+ 标题 + 摘要，靠字重与留白分层。
 */
.v-article-head { margin: 0 0 32px; }

.v-eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  margin: 0 0 14px;
  font-size: 13px;
  font-weight: 500;
  color: var(--v-ink-4);
}
.v-eyebrow::before {
  content: "";
  width: 18px;
  height: 1px;
  background: var(--v-ink-5);
}

.v-article-head h1 {
  margin: 0 0 16px;
  font-family: var(--v-font);
  font-size: 36px;
  line-height: 1.18;
  letter-spacing: -.026em;
  font-weight: 700;
  color: var(--v-ink);
  max-width: 26ch;
  text-wrap: balance;
}

.v-article-sum {
  margin: 0 0 20px;
  max-width: 56ch;
  font-size: 16px;
  line-height: 1.72;
  color: var(--v-ink-3);
}

.v-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 16px;
  align-items: center;
  font-size: 13px;
  color: var(--v-ink-4);
  font-variant-numeric: tabular-nums;
}
.v-meta-item { display: inline-flex; align-items: center; gap: 6px; }
.v-meta-item svg { width: 14px; height: 14px; opacity: .75; }

.v-tags-row { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 16px; }

.v-article-cover {
  margin: 0 0 40px;
  border-radius: var(--v-r);
  overflow: hidden;
  aspect-ratio: 21 / 9;
  background: var(--v-bg-sunken);
  position: relative;
}
.v-article-cover img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* ═══════════════════════ 正文排版 ═══════════════════════ */
/* 全站最重要的规则集。博客站的品质 90% 由这 18 行决定 */
.v-body {
  max-width: var(--v-measure);
  font-family: var(--v-font);
  font-size: var(--v-body-size);
  line-height: var(--v-body-lh);
  color: var(--v-ink);
  overflow-wrap: break-word;
}
.v-body > *:first-child { margin-top: 0; }
.v-body p { margin: 0 0 1.4em; }

/* 中文正文不需要首行缩进（现代排版惯例），但需要段间距。1.4em 是可读性与密度的平衡点 */
.v-body h2, .v-body h3, .v-body h4 {
  margin: 2.2em 0 .7em;
  line-height: 1.3;
  letter-spacing: -.018em;
  font-weight: 700;
  color: var(--v-ink);
  scroll-margin-top: 92px;   /* 锚点跳转不被吸顶导航盖住 */
}
.v-body h2 { font-size: 1.38em; }
.v-body h3 { font-size: 1.16em; }
.v-body h4 { font-size: 1.04em; }

.v-body ul, .v-body ol { margin: 0 0 1.4em; padding-left: 1.4em; }
.v-body li { margin: .45em 0; }
.v-body li::marker { color: var(--v-ink-4); }
.v-body a:not(.btn) { color: var(--v-brand); text-underline-offset: 3px; text-decoration-thickness: 1px; }

.v-body strong { font-weight: 600; }

/*
 * 引用块：v1 是紫色左边框。改为中性左边框 + 稍轻的字重，
 * 引用是「别人的话」，不该比正文更抢眼
 */
.v-body blockquote {
  margin: 1.8em 0;
  padding: 2px 0 2px 20px;
  border-left: 2px solid var(--v-line-2);
  color: var(--v-ink-2);
}
.v-body blockquote p:last-child { margin-bottom: 0; }

.v-body img { max-width: 100%; height: auto; border-radius: var(--v-r-sm); display: block; margin: 1.6em 0; }
.v-body hr { margin: 2.6em 0; border: 0; border-top: 1px solid var(--v-line); }

.v-body code {
  padding: .14em .4em;
  border-radius: var(--r-sm, 8px);
  background: var(--v-bg-sunken);
  font-family: var(--v-mono);
  font-size: .86em;
  color: var(--v-ink);
}

/*
 * 代码块：博客里的代码是「证据」，用深底 + 浅字让它像一段被引用的材料。
 * 这是全站第二处允许「强对比」的地方（第一处是正文/背景的反差）
 */
.v-body pre {
  position: relative;
  margin: 1.8em 0;
  padding: 20px 22px;
  overflow-x: auto;
  border-radius: var(--v-r-sm);
  /*
   * 底色改用共享令牌 --qf-code-bg（= #0D1B24），与 docs 的代码块同一口"墨井"。
   * 原值 #0D1220 与之只差一点色相，但两站并排看会像两家公司。
   */
  background: var(--qf-code-bg, #0D1B24);
  color: #E3E9F2;
  font-family: var(--v-mono);
  font-size: 14px;
  line-height: 1.7;
}
.v-body pre code { padding: 0; background: none; color: inherit; font-size: inherit; }

.v-copy {
  position: absolute;
  top: 10px;
  right: 10px;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 10px;
  border: 1px solid rgba(255, 255, 255, .16);
  border-radius: var(--r-sm, 8px);
  background: rgba(255, 255, 255, .06);
  color: #A8B4C6;
  font-family: var(--v-font);
  font-size: 12px;
  font-weight: 500;
  line-height: 1.5;
  cursor: pointer;
  transition: background-color .16s ease, color .16s ease;
}
.v-copy:hover { background: rgba(255, 255, 255, .12); color: #fff; }
.v-copy.is-done { border-color: rgba(52, 211, 153, .5); color: #6EE7B7; }

.v-body table {
  width: 100%;
  margin: 1.8em 0;
  border-collapse: collapse;
  font-size: 14px;
  display: block;
  overflow-x: auto;
}
.v-body th, .v-body td { padding: 10px 14px; border-bottom: 1px solid var(--v-line); text-align: left; }
.v-body th { font-weight: 600; color: var(--v-ink); }
/* 表格用横线不用网格：网格线在长文里会持续干扰阅读 */
.v-body tr:last-child td { border-bottom: 1px solid var(--v-line-2); }

/* ═══════════════════════ 目录 ═══════════════════════ */
/*
 * v1 用 margin-left: calc(50% + 400px) 做绝对定位——容器一变就错位。
 * v2 保持同样的定位思路但改用 CSS 变量计算宽度，且默认隐藏到真正有空间才显示
 */
.v-toc {
  position: absolute;
  top: 100px;
  width: 200px;
  margin-left: calc(50% + 396px);
  font-size: 13px;
  line-height: 1.6;
}
.v-toc-title {
  margin: 0 0 10px;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--v-ink-4);
}
.v-toc ol { margin: 0; padding: 0; list-style: none; }
.v-toc li { margin: 0; }
/* 当前节的高亮用左侧竖线而不是变色：变色在长列表里会闪，变竖线不会 */
.v-toc a {
  display: block;
  padding: 5px 0 5px 12px;
  border-left: 2px solid var(--v-line);
  color: var(--v-ink-4);
  text-decoration: none;
  transition: color .16s ease, border-color .16s ease;
}
.v-toc a:hover { color: var(--v-ink); }
.v-toc a.is-active { color: var(--v-ink); border-color: var(--v-brand); font-weight: 500; }

@media (max-width: 1279px) { .v-toc { display: none; } }

/* ═══════════════════════ 相关 / 上下篇 ═══════════════════════ */
.v-related {
  margin-top: clamp(48px, 6vw, 72px);
  padding-top: clamp(32px, 4vw, 44px);
  border-top: 1px solid var(--v-line);
}
.v-sect-title {
  margin: 0 0 18px;
  font-size: 13px;
  font-weight: 600;
  letter-spacing: .01em;
  color: var(--v-ink);
}
.v-sect + .v-sect { margin-top: 40px; }
.v-relver-note { margin: 0 0 24px; font-size: 14px; color: var(--v-ink-3); }
.v-relver-note a { color: var(--v-brand); }

.v-pager { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 20px; }
.v-pager-item {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 4px 0;
  text-decoration: none;
  transition: color .16s ease;
}
.v-pager-item.is-next { text-align: right; align-items: flex-end; }
/* 上下篇不加边框盒子——它们是导航链接，不是内容块。加框会和上面的正文卡片打架 */
.v-pager-k { font-size: 12px; font-weight: 500; color: var(--v-ink-4); }
.v-pager-t { font-size: 16px; font-weight: 500; line-height: 1.45; color: var(--v-ink); }
.v-pager-item:hover .v-pager-t { color: var(--v-brand); }
@media (max-width: 639px) { .v-pager { grid-template-columns: minmax(0, 1fr); } }

/* ═══════════════════════ 订阅区（Newsletter）═══════════════════════ */
/*
 * 底部订阅带：静态站没有邮件后端，订阅只有 RSS 一条真路径。
 * 左侧说明、右侧订阅源地址 + 复制按钮（与 hero 共用 data-bl-rss 处理）。
 * 地址必须可见：旧版放了个邮箱框，提交也只是复制链接，用户看到的是「填了没反应」。
 */
.v-news {
  margin-top: clamp(40px, 5vw, 64px);
  padding: clamp(26px, 4vw, 44px);
  border: 1px solid var(--v-line);
  border-radius: calc(var(--v-r) + 4px);
  background:
    radial-gradient(800px 320px at 12% -20%, rgba(43, 92, 255, .08), transparent 60%),
    var(--v-surface);
  display: flex;
  flex-wrap: wrap;
  gap: 20px 32px;
  align-items: center;
  justify-content: space-between;
}
.v-news-copy { min-width: 240px; max-width: 52ch; }
.v-news-t { margin: 0 0 8px; font-size: 22px; font-weight: 700; letter-spacing: -.02em; color: var(--v-ink); }
.v-news-d { margin: 0; font-size: 14px; line-height: 1.7; color: var(--v-ink-3); }
/* min-width:0 是必需的：nowrap 的订阅源地址把这里的 min-content 撑到全文宽度，
   320px 下整页横向溢出 7px。放开下限，由 .v-news-url 自己内部滚动。 */
.v-news-actions { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; min-width: 0; max-width: 100%; }
/* 订阅源地址直接印出来：用户要能看见自己复制的是什么，而不是只收到一句「已复制」 */
.v-news-url {
  min-width: 0;
  max-width: 100%;
  padding: 10px 14px;
  border: 1px solid var(--v-line-2);
  border-radius: var(--v-r-sm);
  background: var(--v-bg);
  color: var(--v-ink-2);
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 13px;
  line-height: 1.4;
  overflow-x: auto;
  white-space: nowrap;
}

/* ═══════════════════════ 跨站交叉区 ═══════════════════════ */
/*
 * v1 是四张带图标的卡片。图标是第四层装饰（已有标题/描述/描边/底色），
 * 去掉图标后靠一条分隔线分组，信息密度反而更高
 */
.v-cross {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 0;
  margin-top: clamp(40px, 5vw, 64px);
  border-top: 1px solid var(--v-line);
}
.v-cross-item {
  display: block;
  padding: 20px 20px 20px 0;
  border-bottom: 1px solid var(--v-line);
  text-decoration: none;
  transition: color .16s ease;
}
.v-cross-t { display: block; margin: 0 0 5px; font-size: 14px; font-weight: 500; color: var(--v-ink); }
.v-cross-d { display: block; margin: 0; font-size: 13px; line-height: 1.6; color: var(--v-ink-3); }
.v-cross-item:hover .v-cross-t { color: var(--v-brand); }
.v-cross-item:hover .v-cross-d { color: var(--v-ink-3); }

/* ═══════════════════════ 归档时间轴 ═══════════════════════ */
.v-arch-group { margin-bottom: 40px; }
.v-arch-y {
  display: flex;
  align-items: baseline;
  gap: 10px;
  margin: 0 0 6px;
  padding-bottom: 10px;
  border-bottom: 1px solid var(--v-line);
  font-size: 18px;
  font-weight: 600;
  letter-spacing: -.01em;
  color: var(--v-ink);
  font-variant-numeric: tabular-nums;
}
.v-arch-n { font-size: 13px; font-weight: 400; color: var(--v-ink-4); }
.v-arch-list { margin: 0; padding: 0; list-style: none; }
.v-arch-list li { padding: 11px 0; border-bottom: 1px solid var(--v-line); }
.v-arch-list li:last-child { border-bottom: 0; }
.v-arch-list a { color: var(--v-ink-2); text-decoration: none; font-size: 16px; font-weight: 400; }
.v-arch-list a:hover { color: var(--v-brand); }
.v-arch-d { margin-left: 10px; font-size: 13px; color: var(--v-ink-4); font-variant-numeric: tabular-nums; }

/* ═══════════════════════ 滚动渐入 ═══════════════════════ */
/*
 * 用 IntersectionObserver 加类，不用 CSS animation：
 * animation 会在元素进入视口前就播放完（首屏看不到效果），
 * 且 prefers-reduced-motion 下仍在动（无障碍问题）。
 * .v-js 由 JS 加在 <html> 上；没有它时默认可见，JS 挂了也不白屏。
 */
.v-reveal { opacity: 1; transform: none; }
.v-js .v-reveal { opacity: 0; transform: translateY(12px); transition: opacity .5s ease, transform .5s cubic-bezier(.2, .7, .3, 1); }
.v-js .v-reveal.is-in { opacity: 1; transform: none; }

/*
 * 兜底：JS 给 <html> 加了 .v-js 后，元素初始是 opacity:0，
 * 只有 IntersectionObserver 回调加上 .is-in 才可见。
 * 一旦 IO 没触发（元素在首屏外、脚本中途抛错、或浏览器策略禁了 IO），
 * 整块内容就永久不可见 —— 而且控制台不会有任何报错。
 * v1 已经因为同类问题踩过一次（.bl-card[hidden] 失效）。
 *
 * 这里用 @media print 兜住"所有 reveal 都应该可见"这个不变量：
 * 打印页永远需要完整内容，顺带保证无障碍与截图工具拿得到正文。
 * （另有一层保险在 blog-app.js：IO 不可用时立即全部置可见。）
 */
@media print {
  .v-js .v-reveal { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: reduce) {
  .v-js .v-reveal { opacity: 1; transform: none; transition: none; }
  .v-lead:hover .v-lead-cover img,
  .v-row:hover .v-row-cover img { transform: none; }
}

/* ═══════════════════════ 提示条 ═══════════════════════ */
.v-toast {
  position: fixed;
  left: 50%;
  bottom: 28px;
  z-index: 90;
  transform: translate(-50%, 12px);
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 11px 18px;
  border: 1px solid var(--v-line);
  border-radius: var(--v-r-sm);
  background: var(--v-surface);
  box-shadow: var(--shadow-md);
  color: var(--v-ink);
  font-size: 14px;
  opacity: 0;
  pointer-events: none;
  transition: opacity .22s ease, transform .22s cubic-bezier(.2, .7, .3, 1);
}
.v-toast.is-on { opacity: 1; transform: translate(-50%, 0); }
.v-toast button {
  border: 0;
  background: none;
  color: var(--v-brand);
  font: inherit;
  font-weight: 500;
  cursor: pointer;
  padding: 0;
}

.v-warn {
  display: flex;
  gap: 10px;
  align-items: flex-start;
  margin: 0 0 24px;
  padding: 12px 16px;
  border: 1px solid #E8C88A;
  border-radius: var(--v-r-sm);
  background: #FDF8EE;
  color: #7A5A16;
  font-size: 14px;
  line-height: 1.65;
}
[data-theme="dark"] .v-warn { border-color: #5C4718; background: #1E1908; color: #E3C77A; }
.v-warn svg { width: 17px; height: 17px; flex: 0 0 auto; margin-top: 1px; }

/* ═══════════════════════ 移动端微调 ═══════════════════════ */
@media (max-width: 639px) {
  .v-body { font-size: 16px; }
  .v-article-cover { aspect-ratio: 16 / 10; }
  .v-wrap, .v-wrap-narrow { width: calc(100% - 40px); }
}


/* ═══════════════════════ 大厂化增强 ═══════════════════════ */
.v-card {
  transition: transform .22s cubic-bezier(.4,0,.2,1), box-shadow .22s ease, border-color .22s ease;
}
.v-card:hover {
  transform: translateY(-3px);
  box-shadow: var(--shadow-md);
}
.v-chip {
  transition: background .18s ease, color .18s ease, border-color .18s ease, transform .15s ease;
}
.v-chip:hover {
  transform: translateY(-1px);
}
.v-btn {
  transition: transform .16s ease, box-shadow .2s ease, background .2s ease, color .2s ease;
}
.v-btn:hover {
  transform: translateY(-1.5px);
}
.v-btn:active {
  transform: translateY(0.5px);
}

/*
 * 内联样式外提：原先写在生成器模板的 style="" 里。
 * 取值逐字一致，视觉零漂移由 _tmp/snapshot_pages.js 的全元素计算样式对比证明。
 * .v-pager 在本站同时承载两种形态（文章底部上一篇/下一篇的两列网格、
 * 归档底部的翻页按钮组），这里用 is-flex-pager 把后者显式区分出来——
 * 此前它靠内联 display:flex 覆盖 .v-pager 的 grid，属于同类别名分歧。
 */
.qf-hero-archive { padding-block: 44px 34px; }
h1.qf-h1-full { max-width: none; }
.qf-chip-gap { margin-left: 6px; }
.v-sect.qf-mt-40 { margin-top: 40px; }
.v-btn.is-disabled { opacity: .45; pointer-events: none; }
.v-pager.is-flex-pager {
  display: flex;
  gap: 8px;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  margin-top: 26px;
}

/* 首屏密度：空态是常态，按「有文章」调的纵向留白会把空态标题挤出 1024×800 首屏 */
body[data-page="blog"] .v-hero { padding-bottom: clamp(20px, 2.4vw, 28px); }
body[data-page="blog"] .section { padding-top: clamp(16px, 2vw, 24px); }
body[data-page="blog"] .v-warn { margin-bottom: 12px; }
body[data-page="blog"] .v-filters { padding: 10px 0; margin-bottom: 18px; }
/* 空列表仍占一格网格，留着只贡献间距 */
body[data-page="blog"] .v-list:empty { display: none; }

/* 空态零篇时是页面主角：给它边界与层级，别散成左对齐的一段小字。
   宽度与居中跟随刊头 .v-hero-inner（820）：先前 720 且不居中，
   在 1440 下卡片左右各空出 200/480，和居中的标题对不上。 */
body[data-page="blog"] .v-empty {
  max-width: 820px;
  margin-inline: auto;
  padding: clamp(28px, 3.2vw, 40px);
  border: 1px solid var(--v-line);
  border-radius: var(--v-r);
  background: var(--v-surface);
}
body[data-page="blog"] .v-empty p { max-width: 58ch; }
body[data-page="blog"] .v-empty + .v-news { margin-top: clamp(24px, 3vw, 32px); }
