/* ============================================================
   VIVI 设计系统 · components.css  ·  v1.0 (2026-08-27)
   ------------------------------------------------------------
   依赖 tokens.css + atoms.css。组合件，比原子大一层。
   五件：控件条 · 模态 · 议程卡 · 表格 · 空态/加载

   每一件都是从「已经写过 6-7 遍」的那个里挑最成熟的一版抽出来的，
   ⛔ 不是新设计。出处写在各节开头。
   ============================================================ */

/* ============================================================
   1. 控件条 · 搜索 + 筛选 + 排序 + 视图切换 + 计数
   ------------------------------------------------------------
   母本 = vote-kit/public/gallery.html:79-108 + gallery.js:269
   六个画廊版本完全同构，交互细节（命中说明行、「已显示 N/M」）重写必漏。
   ============================================================ */
.v-controls { display: grid; gap: var(--s-5); }

.v-search { position: relative; display: flex; }
.v-search input {
  width: 100%; min-height: 46px; padding: 0 40px 0 var(--s-7);
  border: var(--bw-mid) solid var(--line-strong);
  border-radius: var(--r-sm);
  background: var(--paper); color: var(--ink);
  font: inherit; font-size: var(--t-body); font-weight: 600;
}
.v-search-clear {
  position: absolute; right: var(--s-2); top: 50%; transform: translateY(-50%);
  width: 32px; height: 32px; border: 0; border-radius: 50%;
  background: var(--line); color: var(--muted);
  font-size: var(--t-sm); font-weight: 900; cursor: pointer;
}
/* 搜数字/模糊命中时的说明行 —— 这一行是六版画廊里最容易漏抄的东西 */
.v-qnote {
  margin: 0; padding: var(--s-2) var(--s-5);
  border-radius: var(--r-xs);
  background: var(--mint);
  font-size: var(--t-sm); font-weight: 700; line-height: 1.5; color: var(--ink);
}

.v-viewbar {
  display: flex; flex-wrap: wrap; gap: var(--s-4);
  align-items: center; justify-content: space-between;
}
.v-sort { display: flex; align-items: center; gap: var(--s-3); font-size: var(--t-sm); font-weight: 800; color: var(--muted); }
.v-sort select {
  min-height: 38px; padding: 0 var(--s-4);
  border: var(--bw-mid) solid var(--line-strong); border-radius: var(--r-xs);
  background: var(--paper); color: var(--ink);
  font: inherit; font-size: var(--t-sm); font-weight: 700;
}
.v-modes { display: flex; gap: var(--s-2); }
.v-modes button {
  min-height: 38px; padding: 0 var(--s-5);
  border: var(--bw-mid) solid var(--line-strong); border-radius: var(--r-xs);
  background: var(--paper); color: var(--ink);
  font: inherit; font-size: var(--t-sm); font-weight: 800; cursor: pointer;
}
.v-modes button[aria-pressed="true"] { border-color: var(--teal-ink); background: var(--mint); color: var(--teal-ink); }

/* 计数行「已显示 N / M」：⛔ 别省，她的页面每次都要这行 */
.v-hint { margin: 0; font-size: var(--t-sm); font-weight: 700; line-height: 1.55; color: var(--muted); }
.v-hint b { color: var(--ink); font-family: var(--font-mono); font-variant-numeric: tabular-nums; }

/* ============================================================
   2. 模态 · backdrop + dialog + 关闭 + 左右翻
   ------------------------------------------------------------
   母本 = gallery.html:120-125 + gallery.js:403 detailHtml
   ⛔ 必须有：role="dialog" aria-modal="true" · Esc 关闭 · 焦点陷阱 · 点背板关闭
      （51 个目录各写一遍，每次手写必漏一项 ⇒ 交互逻辑在 kit.js 里，别再重写）
   ============================================================ */
.v-modal { position: fixed; inset: 0; z-index: var(--z-modal); display: none; }
.v-modal.is-open { display: block; }
.v-modal-back {
  position: absolute; inset: 0; z-index: var(--z-overlay);
  background: color-mix(in srgb, var(--ink) 55%, transparent);
  backdrop-filter: blur(3px);
  border: 0; width: 100%; cursor: pointer;
}
.v-modal-inner {
  position: relative; z-index: var(--z-modal);
  width: min(680px, calc(100vw - 28px));
  max-height: calc(100vh - 56px); overflow: auto;
  margin: var(--s-10) auto;
  padding: var(--s-10) var(--pad-card) var(--pad-card);
  border: var(--bw) solid var(--line-strong);
  border-radius: var(--r-lg);
  background: var(--paper-soft);
  box-shadow: var(--sh-3);
}
.v-modal-x {
  position: absolute; right: var(--s-4); top: var(--s-4);
  width: 36px; height: 36px;
  border: var(--bw) solid var(--line); border-radius: 50%;
  background: var(--paper); color: var(--ink);
  font-size: var(--t-body); font-weight: 900; cursor: pointer;
}
.v-modal-title { margin: 0 0 var(--s-3); font-family: var(--font-display); font-size: var(--t-h3); font-weight: 900; line-height: var(--lh-tight); }
.v-modal-nav { display: flex; justify-content: space-between; gap: var(--s-4); margin-top: var(--s-8); }

/* 两态 gate 变体（暗号验证后才看内容）——opening-market 独有能力，别的实现没有 */
.v-gate { display: grid; gap: var(--s-4); }
.v-gate-form { display: flex; flex-wrap: wrap; gap: var(--s-3); }
.v-gate-form input { flex: 1 1 200px; min-width: 0; min-height: 46px; padding: 0 var(--s-6);
  border: var(--bw-mid) solid var(--line-strong); border-radius: var(--r-sm);
  background: var(--paper); color: var(--ink); font: inherit; font-size: var(--t-body); font-weight: 600; }
.v-gate-msg { margin: 0; min-height: 1em; font-size: var(--t-sm); font-weight: 800; line-height: 1.55; }
.v-gate-msg.is-ok  { color: var(--ok-ink); }
.v-gate-msg.is-bad { color: var(--bad-ink); }

/* ============================================================
   3. 议程卡 / 时间线（7 版各写一遍，每期活动必有）
   ------------------------------------------------------------
   母本 = opening-market/app.js:667 renderAgendaCard + styles.css:1244
   单日/双日退化逻辑用 :has() 处理 —— 这段重写必错，照抄。
   ============================================================ */
.v-agenda { display: grid; gap: var(--s-7); grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); }
/* 只有一天时不要留出第二列的空档 */
.v-agenda:has(> .v-agenda-day:only-child) { grid-template-columns: minmax(0, 720px); justify-content: center; }

.v-agenda-day { display: grid; gap: var(--s-5); align-content: start; }
.v-agenda-daytitle {
  margin: 0; font-family: var(--font-mono);
  font-size: var(--t-sm); font-weight: 700; letter-spacing: var(--ls-wide);
  text-transform: uppercase; color: var(--muted);
}
.v-agenda-slot { display: grid; gap: var(--s-4); }

.v-agenda-note {
  display: grid; grid-template-columns: 64px 1fr; gap: var(--s-5);
  padding: var(--pad-card);
  border: var(--bw) solid var(--line);
  border-radius: var(--r-md);
  background: var(--paper);
}
.v-agenda-note.is-featured {
  border-color: var(--salmon-ink); border-width: var(--bw-mid);
  background: color-mix(in srgb, var(--salmon) 8%, var(--paper));
}
.v-agenda-time {
  font-family: var(--font-mono); font-size: var(--t-sm); font-weight: 700;
  line-height: 1.35; color: var(--muted); font-variant-numeric: tabular-nums;
}
.v-agenda-body { display: grid; gap: var(--s-2); }
.v-agenda-body h3 { margin: 0; font-family: var(--font-display); font-size: var(--t-body); font-weight: 900; line-height: 1.4; }
.v-agenda-body p  { margin: 0; font-size: var(--t-sm); font-weight: 600; color: var(--muted); line-height: var(--lh-relaxed); }
.v-agenda-live {
  display: inline-block; margin-left: var(--s-2);
  padding: 1px var(--s-2); border-radius: var(--r-pill);
  background: var(--bad-ink); color: var(--ink-invert);
  font-size: 10px; font-weight: 900; vertical-align: middle;
}
/* ⚠️ 08-27 修：这里原来是 --bad 底压 --paper，实测 3.78 ✗，
   当时用「大字号口径 3.0」给它开了绿灯 —— 那条口径是 ≥18.66px 粗体或 ≥24px，
   10px 的角标怎么算都不是大字，判据用错了。
   改成压 --bad-ink 打底 = 5.80 AA，深色主题下 8.07 AAA，两边都不用讲条件。
   ⛔ 别再把「它字少所以没关系」当成放宽口径的理由。 */

@media (max-width: 640px) {
  .v-agenda-note { grid-template-columns: 1fr; gap: var(--s-3); }
}

/* ============================================================
   4. 表格 / 名单（34 个目录命中）
   ------------------------------------------------------------
   母本 = demo-night 的 master-table（thead sticky + 首列冻结，最完整）
   ⛔ 短类名别抄 vote-kit 的 .wt/.wp/.wname（可读性差，改起来找不到）
   ============================================================ */
.v-table-scroll { overflow: auto; border: var(--bw) solid var(--line); border-radius: var(--r-md); background: var(--paper); }
.v-table { width: 100%; border-collapse: separate; border-spacing: 0; font-size: var(--t-sm); }
.v-table th, .v-table td {
  padding: var(--s-4) var(--s-5);
  border-bottom: var(--bw) solid var(--line);
  text-align: left; vertical-align: top;
}
.v-table thead th {
  position: sticky; top: 0; z-index: var(--z-base);
  background: var(--paper-soft);
  font-weight: 800; white-space: nowrap;
  border-bottom-width: var(--bw-bold);
}
.v-table tbody tr:last-child td { border-bottom: 0; }
.v-table tbody tr:hover td { background: color-mix(in srgb, var(--mint) 45%, var(--paper)); }
/* 数字列右对齐 + 等宽，⛔ 数字列左对齐一眼看不出大小 */
.v-table .is-num { text-align: right; font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
/* 首列冻结（宽表用） */
.v-table.is-frozen th:first-child, .v-table.is-frozen td:first-child {
  position: sticky; left: 0; z-index: var(--z-base);
  background: var(--paper-soft);
  border-right: var(--bw) solid var(--line);
}
.v-table.is-frozen thead th:first-child { z-index: var(--z-raised); }

/* ============================================================
   5. 空态 / 加载 / 提示条
   ------------------------------------------------------------
   ⛔ 空态永远不许显示成 0 或空白（「存在的东西不许显示成 0%」）
   ============================================================ */
.v-empty {
  display: grid; place-items: center; gap: var(--s-3);
  padding: var(--s-12) var(--s-7);
  border: var(--bw) dashed var(--line-dashed);
  border-radius: var(--r-md);
  text-align: center; color: var(--muted);
}
.v-empty b { color: var(--ink); font-size: var(--t-body); }
.v-empty span { font-size: var(--t-sm); font-weight: 600; }

.v-skeleton {
  border-radius: var(--r-xs);
  background: linear-gradient(90deg, var(--line) 25%, color-mix(in srgb, var(--line) 40%, var(--paper)) 50%, var(--line) 75%);
  background-size: 200% 100%;
  animation: v-shimmer 1.4s linear infinite;
}
@keyframes v-shimmer { from { background-position: 200% 0; } to { background-position: -200% 0; } }
@media (prefers-reduced-motion: reduce) { .v-skeleton { animation: none; } }

.v-note {
  display: flex; gap: var(--s-3); align-items: flex-start;
  padding: var(--s-5);
  border-left: var(--bw-heavy) solid var(--teal);
  border-radius: 0 var(--r-xs) var(--r-xs) 0;
  background: var(--mint);
  font-size: var(--t-sm); font-weight: 600; line-height: var(--lh-relaxed);
}
.v-note.is-warn { border-left-color: var(--warn); background: color-mix(in srgb, var(--gold) 18%, var(--paper)); }
.v-note.is-bad  { border-left-color: var(--bad);  background: color-mix(in srgb, var(--bad) 10%, var(--paper)); }

/* ============================================================
   6. 图表外壳（配 kit.js 的 cv / tip / legend / barRow）
   ------------------------------------------------------------
   canvas 本体的绘制在 js 里，这里只管容器、tooltip、图例。
   ============================================================ */
.v-chart { display: grid; gap: var(--s-4); }
.v-chart-holder { width: 100%; min-height: 120px; }
.v-chart-holder canvas { display: block; max-width: 100%; }

.v-tip {
  position: fixed; z-index: var(--z-toast);
  pointer-events: none;
  max-width: 260px;
  padding: var(--s-3) var(--s-4);
  border: var(--bw) solid var(--line-strong);
  border-radius: var(--r-xs);
  background: var(--paper-soft);
  box-shadow: var(--sh-2);
  font-size: var(--t-xs); line-height: 1.5; color: var(--ink);
  opacity: 0; transition: opacity var(--dur-fast) var(--ease);
}
.v-tip[data-on="1"] { opacity: 1; }
/* 值在前、标签在后 —— 光标已经在图元上了，先看到的应该是数字 */
.v-tip-v { font-family: var(--font-mono); font-size: var(--t-sm); font-weight: 700; font-variant-numeric: tabular-nums; }
.v-tip-k { color: var(--muted); font-weight: 600; }
.v-tip-row { display: flex; align-items: center; gap: var(--s-2); color: var(--muted); font-weight: 600; }
.v-tip-row i { width: 8px; height: 8px; border-radius: 2px; flex: 0 0 auto; }

.v-legend { display: flex; flex-wrap: wrap; gap: var(--s-3) var(--s-5); }
.v-legend-item { display: inline-flex; align-items: center; gap: var(--s-2); font-size: var(--t-xs); font-weight: 700; color: var(--muted); }
.v-legend-item i { width: 10px; height: 10px; border-radius: 3px; flex: 0 0 auto; }
.v-legend-item i.is-line { height: 3px; border-radius: var(--r-pill); }
