/* ============================================================
   bazicn 门户 v2 · 浅色宣纸
   ------------------------------------------------------------
   配色真源：/srv/bazicn-design/  →  node sync-tokens.mjs
             →  dist/tokens.portal.css  →  逐字节拷到本目录的 tokens.css
                (sha256 9f453ce18484…，与 dist 必须一致)

   ★ 本文件里没有任何颜色字面量，一律 var(--color-*) / var(--glass-*)。
     半透明一律走 color-mix(in oklab, <token> N%, transparent) 或
     「实色 + opacity」，不写 rgb(r g b / a)。
     唯一的例外是 favicon.svg —— 独立 .svg 文件拿不到页面的 CSS 变量，
     只能字面同步，绑定关系写在那个文件的注释里。

   要改配色：改 /srv/bazicn-design/ 的真源 → node sync-tokens.mjs
             → 重新 cp dist/tokens.portal.css 到本目录 tokens.css。
   直接手改 tokens.css 会与真源静默分叉，而门户**不在 check-tokens.mjs
   的扫描范围内** —— 没有守卫会替你抓到，纪律只能靠这段注释。

   命名：本文件自建的组合变量一律 --portal-* 前缀。
        绝不复用/覆盖 token 名 —— 覆盖 token 名会让 tokens.css 变成谎言。
   ============================================================ */

/* ── 标题字体：独立 family 名 ──────────────────────────────
   叫 "Bazicn Display"（而不是 Noto Serif SC）是刻意的。
   门户零 JS，无法在运行时探测缺字；独立 family 名把「子集外字符掉回退」
   从概率问题变成结构上确定的行为 —— 不在 65 字子集里的字符一定落到
   --font-serif，也绝不会与本机装的同名全量字体混用。

   ⚠ 「独立」只在 **CSS 这一层**成立。woff2 二进制里的 name[1]/name[4]
     仍是 "Noto Serif CJK SC"、name[6] 仍是 "NotoSerifCJKsc-Regular"，
     刻意没改名（保留上游名与商标声明，字体文件自证来源，服务于 OFL 第 2 条
     的署名要求；该字体未声明任何 Reserved Font Name，故不涉第 3 条）。
     别把这两层混为一谈 —— 详见 /v2/fonts/OFL.txt。

   字集真源与构建脚本：/srv/bazicn-portal/tools/{display-text,build-font}.mjs
   守卫（字集 ↔ 页面双向比对）：  /srv/bazicn-portal/tools/check-display-text.mjs
   许可（OFL 1.1，必须随字体分发）：/v2/fonts/OFL.txt
   ------------------------------------------------------------ */
@font-face {
  font-family: 'Bazicn Display';
  /* 相对 URL：CSS 里按**本样式表自己的地址**解析，不是按 HTML 的。
     /v2/styles.css → /v2/fonts/…；收口后 /styles.css → /fonts/… ——
     平移不用改这一行。 */
  src: url('fonts/bazicn-display.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

:root {
  color-scheme: light;

  --portal-display: 'Bazicn Display', var(--font-serif);
  --portal-page: min(72rem, calc(100% - 2.75rem));

  --portal-shadow:
    0 1px 2px color-mix(in oklab, var(--color-ink) 5%, transparent),
    0 12px 32px color-mix(in oklab, var(--color-ink) 7%, transparent);
  --portal-shadow-lift:
    0 2px 4px color-mix(in oklab, var(--color-ink) 6%, transparent),
    0 20px 48px color-mix(in oklab, var(--color-ink) 12%, transparent);

  --portal-ease: 0.18s cubic-bezier(0.4, 0, 0.2, 1);
}

/* ── .zf：标题字体的**唯一**施加点 ─────────────────────────
   本文件里 var(--portal-display) 只在这一条规则里被消费。
   这不是洁癖，是守卫 check-display-text.mjs 的结构前提：
   它靠扫 HTML 里 class="zf" 的子树来推断「哪些字会用显示体渲染」，
   再与字体子集双向比对（既不许缺字、也不许有永远用不上的冗余字形）。
   一旦有第二处选择器施加 --portal-display，那个推断就不成立，
   守卫会变成假通过 —— 所以 [3] 直接硬编码「选择器必须是 .zf」。

   要给新元素上标题字体：给它加 class="zf"，然后
     1) 把文案登记进 /srv/bazicn-portal/tools/display-text.mjs
     2) node /srv/bazicn-portal/tools/build-font.mjs   （重裁子集）
     3) node /srv/bazicn-portal/tools/check-display-text.mjs
   别在这里加第二条 font-family。
   ------------------------------------------------------------ */
.zf {
  font-family: var(--portal-display);
}

*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  scroll-behavior: smooth;
}

body {
  margin: 0;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  background: var(--color-night);
  color: var(--color-text);
  font-family: var(--font-sans);
  line-height: 1.7;
  -webkit-font-smoothing: antialiased;
}

/* 顶端金丝线。装饰线是「身份色位置纪律」允许的六处之一，
   但门户用的是鎏金 —— 鎏金是 L1 共享点缀，不是任何一个应用的身份色，
   所以门户可以用得比两个子应用重。 */
body::before {
  content: '';
  position: fixed;
  inset: 0 0 auto;
  height: 2px;
  z-index: 20;
  background: linear-gradient(
    90deg,
    var(--color-gold-dark),
    var(--color-gold-light) 28%,
    var(--color-gold) 52%,
    var(--color-gold-light) 74%,
    var(--color-gold-dark)
  );
}

/* ── 纸上晕染 ─────────────────────────────────────────────
   朱砂（命理先生）× 帝紫（星衍紫微）在纸上洇开，底部收一层鎏金。
   两块颜色同时出现是有意的语义：暗示「这里通向两处」。

   为什么用三个带 data-app 的空元素而不是 body 的多层 background：
   --color-brand 只在 [data-app] 作用域里有定义，根作用域拿不到。
   把 data-app 挂在装饰元素自己身上，颜色就完全由 tokens.css 决定，
   styles.css 里一个色值字面量都不用写。
   浓度靠 opacity 而不是 rgba()，同样是为了不写字面量。
   ------------------------------------------------------------ */
.paper {
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  overflow: hidden;
}

.paper > span {
  position: absolute;
  display: block;
}

.paper-cinnabar {
  top: -20rem;
  left: -16rem;
  width: 58rem;
  height: 44rem;
  opacity: 0.09;
  background: radial-gradient(closest-side, var(--color-brand), transparent);
}

.paper-violet {
  top: -16rem;
  right: -18rem;
  width: 54rem;
  height: 40rem;
  opacity: 0.085;
  background: radial-gradient(closest-side, var(--color-brand), transparent);
}

.paper-gold {
  bottom: -22rem;
  left: 50%;
  translate: -50% 0;
  width: 70rem;
  height: 34rem;
  opacity: 0.1;
  background: radial-gradient(closest-side, var(--color-gold), transparent);
}

h1,
h2,
h3 {
  margin: 0;
  font-weight: 600;
  line-height: 1.2;
  text-wrap: balance;
}

p {
  margin: 0;
}

a {
  color: inherit;
}

ul {
  margin: 0;
  padding: 0;
  list-style: none;
}

/* ── 焦点可见 ──────────────────────────────────────────────
   用 :focus-visible 而不是 :focus —— 鼠标点击不该留下焦点环，
   否则用户会学会忽略它，键盘用户就失去了这个信号。
   --color-brand 只在 [data-app] 作用域有定义，根作用域回退到鎏金墨色，
   所以同一条规则既覆盖门户自有元素，也覆盖两个应用的入口。 */
:focus-visible {
  outline: 2px solid var(--color-brand, var(--color-gold-ink));
  outline-offset: 3px;
  border-radius: 3px;
}

.band-dark :focus-visible {
  outline-color: var(--color-gold-light);
}

/* 跳转链接 —— Tab 的第一站 */
.skip {
  position: absolute;
  left: 0.75rem;
  top: -5rem;
  z-index: 40;
  padding: 0.7rem 1.1rem;
  border-radius: var(--radius-control);
  background: var(--color-surface);
  color: var(--color-text);
  border: 1px solid var(--color-stroke-strong);
  box-shadow: var(--portal-shadow-lift);
  font-size: 0.95rem;
  font-weight: 600;
  text-decoration: none;
  transition: top var(--portal-ease);
}

.skip:focus-visible {
  top: 0.75rem;
}

.page {
  width: var(--portal-page);
  margin-inline: auto;
}

/* ── 篆意方印 ─────────────────────────────────────────────
   inline SVG，不外链图片。inline 才吃得到 CSS 变量 ——
   独立 .svg 文件拿不到页面变量（favicon 就是因此只能写字面量）。
   印用鎏金而非朱砂：朱砂是命理先生的身份色，
   按身份色位置纪律不能拿来当门户自己的 logo。 */
.seal-ground {
  fill: var(--color-gold);
}

.seal-frame {
  fill: none;
  stroke: var(--color-night);
  stroke-width: 1;
  opacity: 0.45;
}

.seal-stroke {
  fill: none;
  stroke: var(--color-night);
  stroke-width: 4.4;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* ── 页眉 ──────────────────────────────────────────────── */
.site-header {
  position: sticky;
  top: 0;
  z-index: 10;
  background: var(--glass-surface);
  backdrop-filter: blur(var(--glass-blur));
  -webkit-backdrop-filter: blur(var(--glass-blur));
  border-bottom: 1px solid var(--glass-hairline);
}

.header-inner {
  width: var(--portal-page);
  margin-inline: auto;
  min-height: 4.25rem;
  display: flex;
  align-items: center;
  gap: 0.75rem 1.5rem;
  padding-block: 0.7rem;
}

.brand {
  display: inline-flex;
  align-items: center;
  gap: 0.7rem;
  text-decoration: none;
  margin-right: auto;
}

.brand .seal {
  width: 2.1rem;
  height: 2.1rem;
  flex: none;
}

.brand-text {
  display: flex;
  flex-direction: column;
  line-height: 1.3;
}

.wordmark {
  font-size: 1.15rem;
  letter-spacing: 0.06em;
  color: var(--color-text);
}

.brand-sub {
  font-size: 0.72rem;
  letter-spacing: 0.16em;
  color: var(--color-text-muted-ink);
}

.nav {
  display: flex;
  align-items: center;
  gap: 0.25rem;
}

.nav a {
  font-size: 0.95rem;
  letter-spacing: 0.04em;
  padding: 0.45rem 0.7rem;
  border-radius: var(--radius-pill);
  color: var(--color-text-secondary);
  text-decoration: none;
  transition: color var(--portal-ease), background var(--portal-ease);
}

.nav a:hover {
  color: var(--color-brand-ink, var(--color-brand, var(--color-text)));
  background: var(--color-brand-wash, var(--color-gold-wash));
}

/* ── W3 登录态挂载点 ──────────────────────────────────────
   本波只留位置与样式；auth.js 去掉 [hidden] 即成形。
   用法约定见 index.html 里那段注释（含 XSS 警示）。 */
.auth-slot {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  font-size: 0.88rem;
  color: var(--color-text-secondary);
  white-space: nowrap;
}

.auth-slot[hidden] {
  display: none;
}

/* ── Hero ─────────────────────────────────────────────────
   深色天然有仪式感，浅色没有，最容易丢的就是「入口感」。
   这里补回来的方式是纵向张力而不是堆装饰：
   上下留白拉到接近视口两成、标题吃满两行、
   然后用一条金丝把节奏收住，再进内容。 */
main {
  flex: 1;
}

.hero {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: clamp(2rem, 5vw, 4.5rem);
  padding-block: clamp(3.5rem, 13vh, 8rem) clamp(2.5rem, 8vh, 5rem);
}

.eyebrow {
  font-size: 0.8rem;
  letter-spacing: 0.34em;
  color: var(--color-gold-ink);
  margin-bottom: clamp(1.25rem, 3vh, 2rem);
}

.hero h1 {
  display: flex;
  flex-direction: column;
  gap: clamp(0.35rem, 1.4vh, 0.9rem);
  font-size: clamp(2.7rem, 8.5vw, 4.75rem);
  font-weight: 400;
  line-height: 1.04;
  letter-spacing: 0.02em;
}

.hero h1 .line-2 {
  font-size: 0.44em;
  letter-spacing: 0.5em;
  /* 大字距会在行尾多留一个字距的空白，减回来才是真正的居左对齐 */
  margin-inline-end: -0.5em;
  color: var(--color-gold-ink);
}

.lede {
  max-width: 34rem;
  margin-top: clamp(1.5rem, 3.5vh, 2.25rem);
  font-size: 1.06rem;
  color: var(--color-text-secondary);
}

/* ── 主次 CTA ──────────────────────────────────────────────
   全站唯一让朱砂与帝紫同框的地方。两颗尺寸相同，
   分量差别只由「填充 vs 描边」表达 —— 不用大小差，
   因为两个应用是并列关系，主次只反映「多数人先去哪」。 */
.cta-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.85rem;
  margin-top: clamp(1.75rem, 4vh, 2.5rem);
}

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 3rem;
  padding: 0.75rem 1.5rem;
  border-radius: var(--radius-control);
  border: 1.5px solid transparent;
  font-size: 1.02rem;
  letter-spacing: 0.08em;
  text-decoration: none;
  transition: background var(--portal-ease), border-color var(--portal-ease),
    transform var(--portal-ease), box-shadow var(--portal-ease);
}

/* 朱砂填充 + 白字 = 7.09:1 */
.btn-primary {
  background: var(--color-brand);
  border-color: var(--color-brand);
  color: var(--color-surface);
  box-shadow: var(--portal-shadow);
}

.btn-primary:hover {
  background: var(--color-brand-dark);
  border-color: var(--color-brand-dark);
  transform: translateY(-1px);
  box-shadow: var(--portal-shadow-lift);
}

/* 帝紫描边：字用 -ink（5.61:1 on 白），边框用本色（装饰，不承载文字） */
.btn-secondary {
  background: var(--color-surface);
  border-color: var(--color-brand);
  color: var(--color-brand-ink, var(--color-brand));
  box-shadow: var(--portal-shadow);
}

.btn-secondary:hover {
  background: var(--color-brand-wash);
  transform: translateY(-1px);
  box-shadow: var(--portal-shadow-lift);
}

.hero-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 1.4rem;
  margin-top: clamp(1.5rem, 3vh, 2rem);
  font-size: 0.88rem;
  color: var(--color-text-muted-ink);
}

.hero-meta li {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
}

.dot {
  width: 0.45rem;
  height: 0.45rem;
  border-radius: 50%;
  background: var(--color-gold);
  flex: none;
}

/* 闲章 + 边款：印在右、竖排小字在旁，是书画的题款关系。
   「纵向张力」在版面上的落点就是这一竖。 */
.hero-aside {
  display: flex;
  align-items: center;
  gap: 1.15rem;
}

.hero-aside .seal {
  width: clamp(6rem, 11vw, 9.5rem);
  height: clamp(6rem, 11vw, 9.5rem);
  flex: none;
  filter: drop-shadow(0 10px 26px color-mix(in oklab, var(--color-gold) 25%, transparent));
}

.vseal {
  writing-mode: vertical-rl;
  /* upright 让 CJK 与「·」一律直立，也就绕开了 vert 字形替换 ——
     子集是用 noLayoutClosure 裁的，本来就没带替换字形。 */
  text-orientation: upright;
  font-size: 0.98rem;
  letter-spacing: 0.42em;
  color: var(--color-gold-ink);
  white-space: nowrap;
}

/* ── 金丝分隔 ──────────────────────────────────────────── */
.rule {
  display: flex;
  align-items: center;
  gap: 0.9rem;
  color: var(--color-gold);
}

.rule::before,
.rule::after {
  content: '';
  height: 1px;
  flex: 1;
  background: linear-gradient(90deg, transparent, currentColor 22%, currentColor 78%, transparent);
  opacity: 0.6;
}

.rule i {
  width: 0.42rem;
  height: 0.42rem;
  background: currentColor;
  rotate: 45deg;
  flex: none;
}

/* ── 分节 ─────────────────────────────────────────────── */
.section {
  padding-block: clamp(3rem, 9vh, 5.5rem);
}

.section-head {
  max-width: 40rem;
  margin-bottom: clamp(1.75rem, 4vh, 2.75rem);
}

.section-head h2 {
  font-weight: 400;
  font-size: clamp(1.7rem, 4.2vw, 2.35rem);
  letter-spacing: 0.06em;
}

.section-head p {
  margin-top: 0.7rem;
  color: var(--color-text-secondary);
}

/* ── 模块卡 ───────────────────────────────────────────── */
.module-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 1.15rem;
}

.soon-grid {
  margin-top: 1.15rem;
}

.card {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 0.7rem;
  padding: 1.7rem 1.6rem 1.5rem;
  border: 1px solid var(--color-stroke);
  border-radius: var(--radius-card);
  background: var(--color-surface);
  box-shadow: var(--portal-shadow);
  text-decoration: none;
  overflow: hidden;
  transition: transform var(--portal-ease), box-shadow var(--portal-ease),
    border-color var(--portal-ease);
}

/* 顶端色带 = 目的地的身份色。这是卡片与应用之间唯一的颜色契约：
   点进去之后 app 里的主色，就是这条带子的颜色。 */
.card::before {
  content: '';
  position: absolute;
  inset: 0 0 auto;
  height: 3px;
  background: var(--color-brand, var(--color-stroke-strong));
}

a.card:hover {
  transform: translateY(-3px);
  border-color: var(--color-brand, var(--color-stroke-strong));
  box-shadow: var(--portal-shadow-lift);
}

.tag {
  align-self: flex-start;
  font-size: 0.75rem;
  letter-spacing: 0.13em;
  padding: 0.25rem 0.6rem;
  border-radius: var(--radius-chip);
  background: var(--color-brand-wash, var(--color-night-lighter));
  color: var(--color-brand-ink, var(--color-brand, var(--color-text-secondary)));
}

.card h3 {
  font-weight: 400;
  font-size: 1.6rem;
  letter-spacing: 0.05em;
  color: var(--color-text);
}

.card p {
  color: var(--color-text-secondary);
  font-size: 0.97rem;
}

.card-points {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem 0.5rem;
  margin-top: 0.15rem;
  font-size: 0.82rem;
  color: var(--color-text-muted-ink);
}

.card-points li {
  padding: 0.15rem 0.5rem;
  border: 1px solid var(--color-stroke);
  border-radius: var(--radius-chip);
  background: var(--color-night);
}

.go {
  margin-top: auto;
  padding-top: 0.9rem;
  font-size: 0.92rem;
  font-weight: 600;
  color: var(--color-brand-ink, var(--color-brand, var(--color-text-muted-ink)));
}

.go::after {
  content: ' →';
  transition: margin var(--portal-ease);
}

a.card:hover .go::after {
  margin-left: 0.25rem;
}

.card.soon {
  background: color-mix(in oklab, var(--color-surface) 55%, transparent);
  box-shadow: none;
  gap: 0.5rem;
  padding: 1.3rem 1.4rem 1.2rem;
}

.card.soon h3 {
  font-size: 1.3rem;
  color: var(--color-text-secondary);
}

.card.soon .go {
  color: var(--color-text-muted-ink);
  font-weight: 500;
}

.card.soon .go::after {
  content: '';
}

/* ── 深色免责 band ─────────────────────────────────────────
   保留一段深色，是为了让仪式感来自**对比**而不是整页发暗。
   底色用的是 --color-ink（正文墨色）—— 不是新配色，
   是把中性层反过来当底用，所以不需要给 L1 长一层深色映射。

   顺带一个白捡的好处：鎏金在宣纸上只有 3.01:1（只能装饰），
   在这块墨底上是 4.68:1 —— 全站唯一能让鎏金承载文字的地方。

   全出血靠「.band-dark 是 <main> 的直接子元素、宽度 100%」实现，
   不用 100vw + overflow-x:hidden：那个写法一是遇到滚动条会溢出，
   二是 html 上的 overflow-x 会让页眉的 position:sticky 失效。 */
.band-dark {
  padding-block: clamp(3rem, 9vh, 5rem);
  background: var(--color-ink);
  color: var(--color-night);
  border-block: 1px solid var(--color-gold-dark);
}

.band-eyebrow {
  font-size: 0.76rem;
  letter-spacing: 0.3em;
  color: var(--color-gold);
  margin-bottom: 0.8rem;
}

.band-dark h2 {
  font-weight: 400;
  font-size: clamp(1.55rem, 4vw, 2.1rem);
  letter-spacing: 0.08em;
  color: var(--color-night);
}

.band-body {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 1rem 2.5rem;
  margin-top: clamp(1.4rem, 3.5vh, 2rem);
  font-size: 0.94rem;
  color: var(--color-night-lighter);
}

.band-body strong {
  color: var(--color-gold-light);
  font-weight: 600;
}

/* ── 页脚 ─────────────────────────────────────────────── */
.site-footer {
  padding-block: clamp(2.5rem, 6vh, 3.5rem);
  font-size: 0.9rem;
}

.footer-top {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 1rem 2rem;
}

.footer-brand {
  display: inline-flex;
  align-items: center;
  gap: 0.65rem;
  margin-right: auto;
  color: var(--color-text-secondary);
}

.footer-brand .seal {
  width: 1.6rem;
  height: 1.6rem;
  flex: none;
}

.footer-nav {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem 1.5rem;
}

.footer-nav a {
  color: var(--color-text-secondary);
  text-decoration-color: var(--color-stroke-strong);
  text-underline-offset: 0.25em;
}

.footer-nav a:hover {
  color: var(--color-text);
  text-decoration-color: currentColor;
}

.footer-fine {
  margin-top: 1.5rem;
  padding-top: 1.25rem;
  border-top: 1px solid var(--color-stroke);
  display: grid;
  gap: 0.55rem;
  font-size: 0.82rem;
  color: var(--color-text-muted-ink);
}

.footer-fine a {
  color: inherit;
  text-underline-offset: 0.25em;
}

.footer-fine a:hover {
  color: var(--color-text-secondary);
}

/* ── 响应式 ───────────────────────────────────────────── */
@media (max-width: 62rem) {
  .hero {
    grid-template-columns: minmax(0, 1fr);
  }

  /* 闲章是版面留白的产物；挤进单栏就只剩噪声，收掉。 */
  .hero-aside {
    display: none;
  }

  .band-body {
    grid-template-columns: minmax(0, 1fr);
  }
}

@media (max-width: 48rem) {
  .module-grid {
    grid-template-columns: minmax(0, 1fr);
  }

  .header-inner {
    flex-wrap: wrap;
    row-gap: 0.4rem;
  }

  .nav {
    margin-left: -0.7rem;
  }
}

@media (max-width: 30rem) {
  .cta-row .btn {
    flex: 1 1 100%;
  }
}

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }

  *,
  *::before,
  *::after {
    transition-duration: 0.01ms !important;
    animation-duration: 0.01ms !important;
  }

  a.card:hover,
  .btn:hover {
    transform: none;
  }
}

/* 高对比 / 强制配色下，把「靠阴影分层」换成实线边框：
   forced-colors 会覆盖掉我们所有的背景色和阴影。 */
@media (forced-colors: active) {
  .card,
  .btn {
    border: 1px solid CanvasText;
  }

  .paper {
    display: none;
  }
}

@media print {
  .site-header,
  .hero-aside,
  .paper,
  .skip {
    display: none;
  }

  body::before {
    display: none;
  }
}
