/* ============================================================
   VIVI 设计系统 · atoms.css  ·  v1.0 (2026-08-27)
   ------------------------------------------------------------
   依赖 tokens.css，⛔ 这里不出现任何硬编码色号/尺寸。
   四个原子：section-head · stat-tile · chip/tag · card
   加上按钮、页面骨架、入场动画。

   为什么是这四个（审计实测，不是我挑的）：
   · stat-tile  全库 13 套各写一遍同一个东西
   · chip/tag   69 个目录命中，三种角色其实是同一个原子
   · card       画廊 6 版 + 18 处独立实现，47 个 .gal-* 类名三版完全一致
   · section-head 13 个 css 各写一遍

   命名前缀 v- ：⛔ 不和任何老项目的类名撞（.gal-* / .stat-card / .chip 全在用）
   ============================================================ */

/* ---------- 0. 基座 ---------- */
*, *::before, *::after { box-sizing: border-box; }

body.v-body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: var(--t-body);
  line-height: var(--lh-relaxed);
  -webkit-font-smoothing: antialiased;
}

.v-wrap { width: var(--width); margin: 0 auto; }
.v-wrap-narrow { width: var(--width-narrow); margin: 0 auto; }

/* 键盘焦点统一一处：⛔ 别在组件里各写一遍 outline */
.v-body :focus-visible {
  outline: 3px solid var(--teal);
  outline-offset: 2px;
  border-radius: var(--r-xs);
}

/* ============================================================
   1. section-head 三件套
   ------------------------------------------------------------
   全库 13 个 css 各写一遍（hackathon-website:115 / archive:201 / agent-worlds:68
   / toy-city-share:254 / company-collab:267 / ceo-dashboard:178 / demo-night:118…）
   结构：<div class="v-sect-head">
           <span class="v-sect-tag">03</span>
           <h2 class="v-sect-title">标题</h2>
           <p class="v-sect-sub">一句说明</p>
         </div>
   ============================================================ */
.v-sect { padding: var(--pad-sect) 0; }

.v-sect-head {
  display: flex; flex-direction: column; gap: var(--s-2);
  margin: 0 0 var(--s-10);
}
.v-sect-tag {
  align-self: flex-start;
  font-family: var(--font-mono);
  font-size: var(--t-xs); font-weight: 700;
  letter-spacing: var(--ls-wide); text-transform: uppercase;
  color: var(--muted);
  padding: var(--s-1) var(--s-4);
  border: var(--bw) solid var(--line);
  border-radius: var(--r-pill);
  background: var(--paper);
}
.v-sect-title {
  margin: 0;
  font-family: var(--font-display);
  font-size: var(--t-h2); font-weight: 900;
  line-height: var(--lh-tight); letter-spacing: var(--ls-tight);
}
.v-sect-sub {
  margin: 0; max-width: 56ch;
  font-size: var(--t-sm); font-weight: 600; color: var(--muted);
}
/* 居中变体 */
.v-sect-head.is-center { align-items: center; text-align: center; }
.v-sect-head.is-center .v-sect-tag { align-self: center; }

/* ============================================================
   2. stat-tile · 统计块（复用价值最高：全库 13 套实现同一个东西）
   ------------------------------------------------------------
   母本 = opening-market/app.js:544 renderMarketStats + styles.css:377-445
   结构：<div class="v-stat-grid">
           <div class="v-stat">
             <b class="v-stat-value">324<i>人</i></b>
             <span class="v-stat-label">有效参赛</span>
             <span class="v-stat-note">去重后口径</span>
           </div>
         </div>
   变体：.is-wide 占两列 · .is-main 大一号 · .is-accent 强调底
   ⛔ 数字用 <b> 不用 <div>：屏幕阅读器要先读数字
   ============================================================ */
.v-stat-grid {
  display: grid; gap: var(--s-5);
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
}
.v-stat {
  display: flex; flex-direction: column; gap: var(--s-2);
  padding: var(--pad-card);
  border: var(--bw) solid var(--line);
  border-radius: var(--r-md);
  background: var(--paper);
  box-shadow: var(--sh-1);
}
.v-stat-value {
  font-family: var(--font-mono);
  font-size: var(--t-h2); font-weight: 700;
  line-height: 1; letter-spacing: var(--ls-tight);
  font-variant-numeric: tabular-nums;   /* 数字等宽，一列数才对得齐 */
}
.v-stat-value i {          /* 单位，比数字小一档 */
  font-style: normal; font-size: 0.45em; font-weight: 700;
  margin-left: 2px; color: var(--muted);
}
.v-stat-label {
  font-size: var(--t-sm); font-weight: 800; color: var(--ink);
}
.v-stat-note {
  font-size: var(--t-xs); font-weight: 600; line-height: 1.5; color: var(--muted);
}

.v-stat.is-wide { grid-column: span 2; }
.v-stat.is-main { grid-column: span 2; }
.v-stat.is-main .v-stat-value { font-size: var(--t-h1); }
.v-stat.is-accent { background: var(--mint); border-color: var(--teal); }
.v-stat.is-accent .v-stat-value { color: var(--teal-ink); }

/* 条形版（opening-market:413 renderStatBars 同构）：数字旁边带占比条 */
.v-stat-bar { display: flex; flex-direction: column; gap: var(--s-2); }
.v-stat-bar-row {
  display: grid; grid-template-columns: 1fr auto; gap: var(--s-3);
  align-items: baseline;
  font-size: var(--t-sm); font-weight: 700;
}
.v-stat-bar-row b { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
.v-stat-bar-track {
  grid-column: 1 / -1; height: 6px;
  border-radius: var(--r-pill);
  background: var(--line);
  overflow: hidden;
}
.v-stat-bar-fill {
  height: 100%; border-radius: var(--r-pill);
  background: var(--teal);
  transition: width var(--dur-slow) var(--ease);
}

@media (max-width: 640px) {
  .v-stat.is-wide, .v-stat.is-main { grid-column: span 1; }
}

/* ============================================================
   3. chip / tag / badge · 同一个原子的三种角色（69 个目录命中）
   ------------------------------------------------------------
   母本 = gallery.js:269 renderFilters + gallery.css:103 .gal-chip
   三种角色，共用一套几何，只换语义标签：
   · v-chip  可点的筛选器 → <button> + aria-pressed，必须带计数 <b>
   · v-tag   不可点的分类 → <span>
   · v-badge 状态标 → <span> + data-state
   ⛔ 筛选 chip 必须是 <button> 且带 aria-pressed，不许用 <div onclick>
      （全库 6 个画廊版本里这个无障碍属性每次都得重写一遍，必漏）
   ============================================================ */
.v-chips { display: flex; flex-wrap: wrap; gap: var(--s-3); }

.v-chip {
  display: inline-flex; align-items: center; gap: var(--s-2);
  min-height: 38px; padding: 0 var(--s-6);
  border: var(--bw-mid) solid var(--line-strong);
  border-radius: var(--r-pill);
  background: var(--paper);
  color: var(--ink);
  font: inherit; font-size: var(--t-sm); font-weight: 800;
  cursor: pointer;
  transition: border-color var(--dur) var(--ease),
              background var(--dur) var(--ease),
              color var(--dur) var(--ease);
}
.v-chip b {                       /* 计数 */
  font-family: var(--font-mono); font-weight: 700; opacity: .55;
  font-variant-numeric: tabular-nums;
}
.v-chip:hover:not(:disabled) { border-color: var(--teal); }
.v-chip[aria-pressed="true"] {
  border-color: var(--teal-ink);
  background: var(--mint);
  color: var(--teal-ink);
}
.v-chip[aria-pressed="true"] b { opacity: .85; }
.v-chip:disabled { opacity: .4; cursor: not-allowed; }

/* 分类 tag：小、不可点、只做归类 */
.v-tag {
  display: inline-block;
  padding: 2px var(--s-3);
  border-radius: var(--r-pill);
  font-size: var(--t-xs); font-weight: 800;
  white-space: nowrap;
  background: var(--mint); color: var(--teal-ink);
}
/* 色位一律走 token 的 x + x-ink 配对，⛔ 别再自己配底色和字色 */
.v-tag.c-salmon { background: color-mix(in srgb, var(--salmon) 22%, var(--paper)); color: var(--salmon-ink); }
.v-tag.c-sky    { background: color-mix(in srgb, var(--sky) 22%, var(--paper));    color: var(--sky-ink); }
.v-tag.c-gold   { background: color-mix(in srgb, var(--gold) 28%, var(--paper));   color: var(--gold-ink); }
.v-tag.c-acid   { background: color-mix(in srgb, var(--acid) 30%, var(--paper));   color: var(--acid-ink); }
.v-tag.c-rose   { background: color-mix(in srgb, var(--rose) 30%, var(--paper));   color: var(--rose-ink); }
.v-tag.c-sand   { background: color-mix(in srgb, var(--sand) 34%, var(--paper));   color: var(--sand-ink); }
/* ⚠️ 中性 tag 的底不能直接用 --line（那是描边强度，12% 太重）：
   --muted 压上去只有 4.07 ✗。改成同色系压到 50% ⇒ 4.67 AA，深色下 5.33 AA。 */
.v-tag.is-mute  { background: color-mix(in srgb, var(--line) 50%, var(--paper)); color: var(--muted); }

/* 状态 badge：语义色，带小圆点。⛔ --bad 底上不放字（白字黑字都不达标）
   ⇒ 所以 badge 一律「浅底 + ink 版文字 + 原色圆点」 */
.v-badge {
  display: inline-flex; align-items: center; gap: var(--s-2);
  padding: 2px var(--s-3);
  border-radius: var(--r-pill);
  font-size: var(--t-xs); font-weight: 800; white-space: nowrap;
  background: var(--paper-soft);
  border: var(--bw) solid var(--line);
  color: var(--muted);
}
.v-badge::before {
  content: ""; width: 7px; height: 7px; border-radius: 50%;
  background: currentColor; flex: 0 0 auto;
}
.v-badge[data-state="ok"]   { color: var(--ok-ink); }
.v-badge[data-state="ok"]::before   { background: var(--ok); }
.v-badge[data-state="warn"] { color: var(--warn-ink); }
.v-badge[data-state="warn"]::before { background: var(--warn); }
.v-badge[data-state="bad"]  { color: var(--bad-ink); }
.v-badge[data-state="bad"]::before  { background: var(--bad); }
.v-badge[data-state="todo"] { color: var(--muted); border-style: dashed; border-color: var(--line-dashed); }

/* ============================================================
   4. card · 卡片壳（画廊 6 版 + 18 处独立实现）
   ------------------------------------------------------------
   母本 = gallery.js:255 cardHtml + gallery.css 的 47 个 .gal-* 类
          （47 个类名在 531/704/0801 三版完全一致 ⇒ 壳子已经冻结了）
   字段映射：{ no, title, by, intro, tags[], badges[], shot, href }
   结构：<a class="v-card" href="…">
           <div class="v-card-shot"><img …></div>
           <div class="v-card-body">
             <div class="v-card-top"><span class="v-card-no">#12</span><span class="v-badge">…</span></div>
             <h3 class="v-card-title">标题</h3>
             <p class="v-card-by">作者</p>
             <p class="v-card-intro">一句描述</p>
             <div class="v-chips"><span class="v-tag">…</span></div>
           </div>
         </a>
   ⛔ 视觉变体别往这儿加（profile-card-lab 拼贴风 / princess-rescue 纸片风
      是她的创作面，抽掉等于抹掉方差）
   ============================================================ */
.v-card-grid {
  display: grid; gap: var(--s-6);
  grid-template-columns: repeat(auto-fill, minmax(268px, 1fr));
}
.v-card-grid.is-list { grid-template-columns: 1fr; gap: var(--s-4); }

.v-card {
  display: flex; flex-direction: column; overflow: hidden;
  border: var(--bw) solid var(--line-strong);
  border-radius: var(--r-md);
  background: var(--paper);
  box-shadow: var(--sh-1);
  color: var(--ink);
  text-align: left; text-decoration: none;
  font: inherit; padding: 0;
  cursor: pointer;
  transition: border-color var(--dur) var(--ease),
              box-shadow var(--dur) var(--ease),
              transform var(--dur) var(--ease);
}
.v-card:hover { border-color: var(--teal); box-shadow: var(--sh-2); transform: translateY(-2px); }
.v-card:active { transform: translateY(1px); }

.v-card-shot { position: relative; aspect-ratio: 4 / 3; background: var(--line); overflow: hidden; }
.v-card-shot img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* ⚠️ 无图占位必须自带**不透明**底色：外层 .v-card-shot 是 --line（描边强度当填充，12% 偏深），
   --muted 直接压上去只有 4.22 ✗。
   ⛔ 这里不能拿 --line 去 color-mix：--line 本身带 alpha，混出来还是半透明，
      压到父层那块深底上反而更暗（试过，4.17 ✗，比不加还差）。
   ⇒ 用不透明的墨 6% 溶进纸 = 4.77 AA，深色主题下 5.33 AA（两个都实测过，不是估的）。
   ⛔ 别把 .v-card-shot 本体调亮 —— 真图加载时那层深底是有用的。 */
.v-card-shot-none {
  display: grid; place-items: center; height: 100%; gap: var(--s-1);
  padding: var(--s-4);
  background: color-mix(in srgb, var(--ink) 6%, var(--paper));
  color: var(--muted); font-size: var(--t-sm); font-weight: 800; text-align: center;
}
.v-card-shot-more {          /* 「还有 N 张」角标 */
  position: absolute; right: var(--s-3); bottom: var(--s-3);
  padding: 3px var(--s-3); border-radius: var(--r-pill);
  background: color-mix(in srgb, var(--ink) 78%, transparent);
  color: var(--ink-invert);
  font-family: var(--font-mono); font-size: var(--t-xs); font-weight: 700;
}

.v-card-body { display: flex; flex-direction: column; gap: var(--s-2); padding: var(--pad-card); }
.v-card-top { display: flex; align-items: center; gap: var(--s-2); flex-wrap: wrap; }
.v-card-no {
  font-family: var(--font-mono); font-size: var(--t-xs); font-weight: 700;
  color: var(--muted); font-variant-numeric: tabular-nums;
}
.v-card-title {
  margin: 0;
  font-family: var(--font-display);
  font-size: 16.5px; font-weight: 900; line-height: 1.35;
}
.v-card-title.is-todo { color: var(--muted); font-family: var(--font-body); font-size: var(--t-body); }
.v-card-by { margin: 0; font-size: var(--t-sm); font-weight: 800; color: var(--muted); }
.v-card-intro {
  margin: 0;
  font-size: var(--t-sm); font-weight: 600; line-height: var(--lh-relaxed);
  color: color-mix(in srgb, var(--ink) 78%, var(--paper));
  display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden;
}

/* 获奖态：只加描边和底色，⛔ 不动尺寸/布局（获奖卡变大一号会把网格挤歪，
   这是 0801 实战踩过的） */
.v-card.is-win {
  border-color: var(--gold-ink); border-width: var(--bw-bold);
  background: linear-gradient(180deg, color-mix(in srgb, var(--gold) 14%, var(--paper)), var(--paper) 42%);
}
.v-card.is-win:hover { border-color: var(--gold-ink); }

/* 列表视图：图挪到左边 */
.v-card-grid.is-list .v-card { flex-direction: row; align-items: stretch; }
.v-card-grid.is-list .v-card-shot { flex: 0 0 116px; aspect-ratio: auto; }
.v-card-grid.is-list .v-card-body { flex: 1 1 auto; gap: var(--s-1); }
.v-card-grid.is-list .v-card-intro { -webkit-line-clamp: 2; }

/* 素卡（无图、无链接）：拿它当通用容器 */
.v-panel {
  padding: var(--pad-card);
  border: var(--bw) solid var(--line);
  border-radius: var(--r-md);
  background: var(--paper);
  box-shadow: var(--sh-1);
}
.v-panel.is-dashed { border-style: dashed; border-color: var(--line-dashed); box-shadow: none; background: none; }

@media (prefers-reduced-motion: reduce) {
  .v-card, .v-card:hover, .v-card:active { transform: none; transition: none; }
}

/* ============================================================
   5. 按钮
   ------------------------------------------------------------
   ⛔ 主按钮不用强调色打底压白字（salmon 白字 2.76 / teal 2.86，全不合格）
      ⇒ 主按钮 = 墨底白字（14.31 AAA），强调色只做次按钮的描边和文字
   ============================================================ */
.v-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--s-2);
  min-height: 44px; padding: 0 var(--s-8);
  border: var(--bw-mid) solid var(--ink);
  border-radius: var(--r-sm);
  background: var(--ink); color: var(--ink-invert);
  font: inherit; font-size: var(--t-sm); font-weight: 800;
  text-decoration: none; cursor: pointer;
  transition: background var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
.v-btn:hover:not(:disabled) { background: color-mix(in srgb, var(--ink) 86%, var(--bg)); }
.v-btn:disabled { opacity: .45; cursor: not-allowed; }

.v-btn.is-ghost {          /* 次按钮 */
  background: var(--paper); color: var(--ink);
  border-color: var(--line-strong);
}
.v-btn.is-ghost:hover:not(:disabled) { border-color: var(--teal-ink); color: var(--teal-ink); background: var(--paper); }

.v-btn.is-accent {         /* 强调按钮＝强调色底压墨字，实测 salmon 5.37 AA */
  /* ⛔ 这里必须是 --on-accent，不是 --ink。深色主题下 --ink 会翻成浅色，
     浅字压 salmon 原底只有 2.35 ✗；--on-accent 固定深墨，两个主题都是 5.37 AA。 */
  background: var(--salmon); color: var(--on-accent); border-color: var(--salmon-ink);
}
/* ⛔ hover 不许往 --paper 混：深色主题下 --paper 是深色 ⇒ 越摸越暗，
   而压在上面的 --on-accent 是固定深墨，实测掉到 4.27 ✗（静态扫描扫不到 hover，
   我是把 hover 态强行变成静态才量出来的）。⇒ 走不翻的 --accent-lift = 6.21 AA，两个主题同一个数。 */
.v-btn.is-accent:hover:not(:disabled) { background: color-mix(in srgb, var(--salmon) 86%, var(--accent-lift)); }

.v-btn.is-link {           /* 纯文字链接态 */
  min-height: 0; padding: 0; border: 0; background: none;
  color: var(--teal-ink); text-decoration: underline; text-underline-offset: 3px;
}
.v-btn.is-sm { min-height: 36px; padding: 0 var(--s-5); font-size: var(--t-xs); }

/* ============================================================
   6. 页面骨架 · topbar / brand / back-to-top / 入场
   ------------------------------------------------------------
   topbar + brand-mark 在 10 个 html 里逐字相同 ⇒ 直接抽。
   sticky 全库 178 个文件、层级各写各的 ⇒ 一律走 --z-sticky。
   ============================================================ */
.v-topbar {
  position: sticky; top: 0; z-index: var(--z-sticky);
  display: flex; align-items: center; justify-content: space-between; gap: var(--s-5);
  padding: var(--s-4) 0;
  background: color-mix(in srgb, var(--paper-soft) 88%, transparent);
  backdrop-filter: blur(10px);
  border-bottom: var(--bw) solid var(--line);
}
.v-brand {
  display: inline-flex; align-items: center; gap: var(--s-3);
  text-decoration: none; color: var(--ink);
}
.v-brand-mark {
  display: grid; place-items: center;
  width: 30px; height: 30px;
  border-radius: var(--r-xs);
  background: var(--salmon); color: var(--on-accent);
  font-family: var(--font-display); font-size: 17px; font-weight: 900;
  /* ⚠️ 这里只是占位标记（一个字母 V）。厂牌 logo/字标尚未定稿，
     ⛔ 不要把它当 logo 用在对外印刷物料上。 */
}
.v-brand-copy { font-size: var(--t-sm); font-weight: 800; letter-spacing: var(--ls-tight); }
.v-topbar-nav { display: flex; align-items: center; gap: var(--s-5); }
.v-topbar-nav a { color: var(--muted); font-size: var(--t-sm); font-weight: 800; text-decoration: none; }
.v-topbar-nav a:hover, .v-topbar-nav a[aria-current="page"] { color: var(--ink); }

.v-backtop {
  position: fixed; right: var(--s-7); bottom: var(--s-7); z-index: var(--z-raised);
  width: 44px; height: 44px;
  border: var(--bw) solid var(--line-strong); border-radius: 50%;
  background: var(--paper-soft); color: var(--ink);
  font-size: var(--t-body); cursor: pointer;
  box-shadow: var(--sh-2);
  opacity: 0; pointer-events: none;
  transition: opacity var(--dur) var(--ease);
}
.v-backtop.is-on { opacity: 1; pointer-events: auto; }

.v-foot {
  padding: var(--s-11) 0 var(--s-12);
  border-top: var(--bw) solid var(--line);
  font-size: var(--t-sm); font-weight: 700; color: var(--muted);
}
/* 落款：全库 8 套格式、8 套类名 ⇒ 新页面一律 .v-foot + 「主办 Vivi」
   ⛔ 微信号不写进公共页面（view-source 可见） */

/* 入场动画：data-reveal + IntersectionObserver，全库 ~25 个 js 各写一遍 */
[data-reveal] {
  opacity: 0; transform: translateY(14px);
  transition: opacity var(--dur-enter) var(--ease), transform var(--dur-enter) var(--ease);
}
[data-reveal].is-in { opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce) {
  [data-reveal] { opacity: 1; transform: none; transition: none; }
}

/* 屏幕阅读器专用文本 */
.v-sr {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

