/*
 * 垂光台 · 令牌层
 *
 * ══ 世界：垂光 The Descending Light ══
 *
 * 背景做过三版，三版都被否掉。前两版的失败可以各写一段道理，
 * 但三版连起来看，错的是**同一件事**，而且那件事跟概念无关：
 *
 *   一版：磨光的黑石墙，天是石面上一层 alpha 0.10 的倒影 → 太空
 *   二版：开顶的深空，银河、星云、辉光星点、闪烁、流星     → 太俗
 *   三版：铜版星图，投影网格、大圆、星等符号、点画的银河   → 还是俗
 *
 * 三版都是"深色底 + 满屏程序化纹理"。
 * 概念一次比一次讲得通，结果一次比一次不像好设计。
 *
 * 去看了公认做得好的站之后，答案很直白，而且和我三次的做法正好相反：
 *
 *   **好的深色站，正文背后什么都没有。**
 *   Linear 的地是 rgb(8,9,10)，background-image: none —— 一整张纯色。
 *   basement.studio 是黑底加一个焦点物件，正文背后同样是空的。
 *   它们的高级感来自字、间距、留白和内容本身，
 *   **不来自背景里那一层东西**。
 *
 * 廉价感的来源不是"纹理画得不够好"，是**正文背后有纹理**这件事本身。
 * 三版都在往那一层加东西，所以三版都往同一个方向掉。
 *
 * 所以这一版把那一层**拿掉**，只留一件事 —— 项目自己的名字：
 *
 *   **垂光**：一束光从上面垂下来。
 *
 * 一束。极大、极软、极平滑，从顶上落到页面中部就散尽。
 * 没有第二个光源，没有星，没有网格，没有线，没有任何会动的东西。
 * 剩下的全部功夫花在两件看不见的事上：
 *   1) 渐变必须**平滑到不带条带**（大面积暗部渐变在 8 位色下必然断层），
 *      靠一层极细的颗粒去抖动它 —— 这是这一版唯一的纹理，
 *      而它的作用是**消除**瑕疵，不是增加花样。
 *   2) 光的落点要让出正文：最亮的一段在标题上方，读到正文时已经沉下去了。
 *
 * 三种材料没变：
 *
 *   地 —— 极深的中性，带一丝墨绿。它是"深"，不是"黑"。
 *   石 —— 立在地上的板。刻位、面板、读数条长在石上。
 *   纸 —— 你带走的那一份。把纸贴在名字上拓下来，那是拓片。
 *
 * 整套只有一种**动作色**：朱砂。
 *
 * ══ 硬约束（三版继承，没变过）══
 *
 * **背景不许把字吃掉。**
 * --lime-faint 必须在合成之后的背景上过 4.5:1，
 * 反推：背景任意一点的相对亮度 Y ≤ 0.0395。
 * 这一版只有一层光要管，所以这条第一次变得好守 —— 但**好守不等于可以放松**：
 * 光的 alpha 是从这个数倒推的，不是调出来的。
 */

/*
 * ══ 字面 ══
 *
 * 这三条以前**一条都不存在**。子集每次 `npm run font` 都在构建，
 * JetBrains Mono 也一直挂着 preload，但全站没有一处 @font-face ——
 * 于是中文标题一直落在 PingFang SC 上，等宽一直落在 SF Mono 上，
 * 而那个 preload 预载了一个谁也没声明的文件，纯浪费带宽。
 * 这一版把管线接上。
 */

/* 碑刻体。子集里只有站上真实出现过的字（见 fonts/subset-chars.txt）：
   新增中文文案后必须重跑 `npm run font`，否则漏掉的字会静默掉回系统字体，
   同一句话里出现两种字面。`npm run smoke` 有一项专门校验覆盖率。 */
@font-face {
  font-family: "Noto Serif SC Carve";
  src: url("fonts/noto-serif-sc-700-subset.woff2") format("woff2");
  font-weight: 700;
  font-style: normal;
  /* swap 而不是 block：217KB 的 CJK 子集不该挡住首屏。
     兜底是 Songti SC —— 同属明体，字形骨架对得上，换上来时不会跳版 */
  font-display: swap;
}

/* 等宽只声明**拉丁**的 unicode-range：
   中文永远不会去等它加载，也永远不会落进它的兜底字形里 */
@font-face {
  font-family: "JetBrains Mono";
  src: url("fonts/jetbrains-mono-400.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+2000-206F,
                 U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215;
}
@font-face {
  font-family: "JetBrains Mono";
  src: url("fonts/jetbrains-mono-700.woff2") format("woff2");
  font-weight: 700;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+2000-206F,
                 U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215;
}

:root {
  /* ══ 地 Ground ══
     整页的底。**一块纯色**，不是一张图 —— 这是这一版最重要的一条。
     三档只是同一块地的上中下，用来给页面一点纵深，
     它们之间的差极小（Y 0.0088 → 0.0021），**看不出是渐变**，只看得出上面比下面透气。
     不是纯黑：带一丝墨绿（色相 170），纯黑会让上面所有东西看着像贴纸。 */
  --ground-hi: oklch(0.205 0.010 170);  /* 页顶 */
  --ground:    oklch(0.165 0.008 170);  /* 中段。整页的基准色 */
  --ground-lo: oklch(0.128 0.007 172);  /* 页底 */

  /* ══ 垂光 The Shaft ══
     整页**唯一**的光源，也是整页唯一一处非纯色的地方。
     从顶上垂下来，落到页面中部就散尽 —— 落点是算过的：
     最亮的一段必须在标题**上方**，等读到正文，光已经沉下去了。

     alpha 0.20 是沿着光束**逐点**算出来的，不是取一个峰值拍脑袋：
     视口顶端（光最强、且导航栏没遮住的那一条）合成后 Y=0.032，
     --lime-faint 压上去 4.9:1；往下每一档都更宽松，到 45% 已经是 7.2:1。
     0.22 也过，但只剩 4.70:1 —— 4% 的余量不叫余量。
     这一层是整页**唯一**能变亮的东西，没有"那是点不是面"可以辩解，所以宁可欠一档。 */
  --shaft: oklch(0.800 0.055 212 / 0.20);

  /* 颗粒。这一版唯一的纹理，而它的职责是**消除瑕疵，不是增加花样**：
     大面积暗部渐变在 8 位色下必然出条带，抖动是唯一的解。
     所以它极细、极淡（3.5%），并且用 overlay 混合 —— 中灰不改变底色，
     只在正负两侧各抖一点点。它不该被看见，只该让渐变看不出断层。 */
  --grain-opacity: 0.035;

  /* ── 石 Stone：立在地上的板。读数条、面板、刻位都长在石上。
     它比地亮一点点 —— 抬起来的东西接到的光多一点，仅此而已。 */
  --stone:       oklch(0.176 0.020 186);  /* 板面。抬起来的那一层 */
  --stone-deep:  oklch(0.124 0.018 190);  /* 板缝、槽与槽之间的暗 */
  --stone-lit:   oklch(0.256 0.026 184);  /* 被光擦到的那一条边 */
  --stone-face:  oklch(0.206 0.022 186);  /* 读数条、面板 */

  /* 玻璃：少数需要让底下内容透过来的面板（顶栏、灯箱）。
     它**压暗**背后的东西，不是给背后加一层雾 —— 字要能站在它上面 */
  --glass:      oklch(0.120 0.020 190 / 0.74);
  --glass-thin: oklch(0.120 0.020 190 / 0.48);
  --glass-edge: oklch(0.900 0.014 96 / 0.13);
  --blur-glass: blur(12px) saturate(1.05);

  /* ── 刻 Carve：凹槽。光从左上来，所以上唇亮、槽底黑。
     这两个值是一对，永远一起用——只给亮不给暗，字就是浮在石头上的，不是刻进去的 */
  --cut-lip:   oklch(0.860 0.016 100);
  --cut-floor: oklch(0.092 0.018 192);

  /* ── 纸 Paper：拓片。你带走的那一份。
     **不是米黄**。拓片纸是冷的骨白，偏一点灰绿；
     暖奶油底 + 高对比衬线 + 陶土色强调是这一行最老的那个默认答案，
     这一版从纸的真实颜色上就把它避开 */
  --paper:       oklch(0.940 0.004 110);
  --paper-shade: oklch(0.884 0.005 110);  /* 纸的暗面、纸上的分隔 */
  --paper-edge:  oklch(0.812 0.006 110);  /* 纸的边缘、撕口 */

  /* 石墨。拓包擦上去的痕，不是纯黑——铅笔有冷的金属光 */
  --graphite:      oklch(0.270 0.006 250);  /* 纸上的正文 */
  --graphite-mid:  oklch(0.430 0.006 250);  /* 纸上的次级 */
  /* L 压到 0.505：纸（0.940）上的小字也要过 4.5:1。0.560 时只有 3.88 —— 拓片上的编号也是字 */
  --graphite-soft: oklch(0.505 0.006 250);  /* 纸上的刻度、单位 */

  /* ── 版面上的字。四级，靠尺寸与字重拉开层级，不靠压暗。
     色相从 250（冷蓝）转到 96（**暖象牙**）。这不是换个口味：
     深墨绿的版上压冷蓝白，两个冷色互相咬，整页发灰发脏；
     压暖象牙才是纸与墨的关系 —— 印张本来就是暖白的。
     L 值一个没动，上一版量出来的层级与对比度全部继承。
     最暗一级到 --lime-faint 为止，不许再往下 */
  --lime:       oklch(0.965 0.008 96);  /* 名字、标题、读数 */
  --lime-mid:   oklch(0.862 0.014 96);  /* 正文 */
  --lime-low:   oklch(0.775 0.018 96);  /* 次级说明 */
  /* 0.672 → 0.706 是前面几版买下来的余量：当时实测最紧的几处只剩 4.64:1，
     过线 3% 不叫过线叫碰巧。这一版背景干净得多，余量宽了很多，
     但这一档**不退回去** —— 那份余量是给光用的，不是拿来把字再压暗的 */
  --lime-faint: oklch(0.706 0.022 96);  /* 刻度、单位、禁用态——到底了 */

  /* ── 朱砂 Cinnabar：整套里唯一的饱和色。
     碑帖上唯一会出现的颜色就是印泥。它标记"已经被认领"和"能按下去"。
     一旦它变成标题色或装饰，这一页就塌成一张普通的深色落地页了 */
  /* L 压到 0.520：纸白（0.940）压在它上面要过 4.5:1，0.545 时只有 4.49 —— 差 0.01 也是不合格 */
  --seal:       oklch(0.520 0.172 32);
  --seal-deep:  oklch(0.430 0.150 30);   /* 印泥压实的那一圈边，只做形不做字 */
  --seal-light: oklch(0.660 0.165 34);   /* 要在深色底上承载小字时才提到这一级 */
  --seal-wash:  oklch(0.520 0.172 32 / 0.14);

  /* ── 天区三色：北天 / 南天 / 黄道，按刻位号轮着分（(seat-1) % 3）。
     首页的墙、观星台的穹顶、藏品元数据用同一组值——改一处要改三处，
     所以它只在这里定义一次。它是**分类色**，不做按钮、不做标题 */
  --region-n:     oklch(0.780 0.052 240);
  --region-s:     oklch(0.745 0.062 300);
  --region-e:     oklch(0.800 0.058 108);
  --region-n-dot: oklch(0.680 0.055 240);
  --region-s-dot: oklch(0.640 0.065 300);
  --region-e-dot: oklch(0.705 0.060 108);
  /* 龛底：同色相落到版面的亮度上。pit 是槽底，bed 是被光扫到的台面 */
  --region-n-pit: oklch(0.120 0.026 240);  --region-n-bed: oklch(0.270 0.050 240);
  --region-s-pit: oklch(0.118 0.030 300);  --region-s-bed: oklch(0.256 0.056 300);
  --region-e-pit: oklch(0.122 0.024 108);  --region-e-bed: oklch(0.268 0.046 108);

  /* ── 龛里那张星图。只有 17×20px：连线必须比星点暗一档，否则缩小后糊成一团麻 */
  --asterism-line:     oklch(0.800 0.016 100 / 0.62);
  --asterism-line-lit: oklch(0.975 0.010 100);
  --asterism-star:     oklch(0.960 0.014 100 / 0.94);

  /* ── 抬起来的板，上沿被光擦亮、下沿背光的那两道 */
  --edge-lit:  oklch(0.900 0.016 100 / 0.16);
  --edge-dim:  oklch(0.900 0.016 100 / 0.07);

  /* ── 凿进石面的底。面板、列表行、读数格、输入框都从这三档里取。
     它们是同一块石头的影子（同色相、同彩度），只有深浅不同 ——
     深浅表示"凿得多深"，不表示"换了一种材料"。 */
  --sunk-1: oklch(0.090 0.020 190 / 0.40);  /* 最浅：交替行、hover 底 */
  --sunk-2: oklch(0.090 0.020 190 / 0.62);  /* 常规面板与列表行 */
  --sunk-3: oklch(0.080 0.018 190 / 0.80);  /* 输入框、坑底、灯箱底板 */

  /* ── 线：板上的线是**凿出来的缝**，不是画上去的边。
     所以它们全部是低透明度的象牙色，从不使用实心边框色 */
  --rule:        oklch(0.900 0.014 96 / 0.14);
  --rule-strong: oklch(0.900 0.014 96 / 0.30);
  --rule-seal:   oklch(0.520 0.172 32 / 0.45);
  /* 纸上的线是纤维和折痕，比石头上的实 */
  --rule-paper:  oklch(0.270 0.006 250 / 0.18);

  /* ── 状态 */
  --thumb: oklch(0.50 0.020 190 / 0.55);  /* 滚动条拇指 */
  --ok:  oklch(0.680 0.105 155);
  --bad: var(--seal);

  /* ══ 排版 ══
   *
   * 展示字换了。上一版是黑体 900——那是仪器面板上的字。
   * 这一版是**明体**：宋/明体是刻工发明的字，横细竖粗、起收有棱，
   * 因为那是刀在木头和石头上最省力的走法。碑上的字本来就长这样。
   *
   * 等宽只给真实的测量值：编号、地址、计数、余量、区块号、时间。
   * 不拿它装技术感——任何一段散文落进等宽都是这条被破了。
   */
  --carve: "Noto Serif SC Carve", "Songti SC", "SimSun", "Source Han Serif SC", serif;
  --mono:  "JetBrains Mono", ui-monospace, "SF Mono", Menlo, monospace;
  --sans:  ui-sans-serif, -apple-system, "PingFang SC", "Helvetica Neue", sans-serif;

  /* 刻在石头上的中文**不收字距**：碑上的字是一格一格排的，
     负字距会让它读起来像海报标题，不像刻痕 */
  --ls-carve:  0.01em;
  --ls-normal: 0;
  --ls-wide:   0.02em;
  --ls-wider:  0.08em;
  --ls-widest: 0.18em;

  --t-3xs: 10px;
  --t-2xs: 11px;
  --t-xs:  12px;
  --t-sm:  13px;
  --t-md:  14px;
  --t-lg:  17px;
  --t-xl:  20px;
  --t-2xl: 26px;
  --t-3xl: 38px;
  --t-4xl: 56px;
  --t-5xl: 78px;

  --s1: 4px;  --s2: 8px;  --s3: 12px; --s4: 16px;
  --s5: 24px; --s6: 32px; --s7: 48px; --s8: 64px;

  /* ── 阴影词汇：**两条**。这一版把第三条删了。
     凹槽 Groove —— 唯一"凿进去"的配方。必须读起来是凹的：上唇亮、下沿暗、槽底最黑
     抬板 Slab   —— 从版面抬起来的板（读数条、拓片）。它有落影，因为它真的浮在版前面

     删掉的是 --halo（星晕）。它是上一版给"会发光的东西"用的 ——
     而这一版**没有会发光的东西**：铜版画的明暗只来自排线的疏密。
     留着它，早晚有人拿它去给某个元素加一圈辉光，那就是俗气回来的入口。 */
  --groove: inset 0 1.5px 0 oklch(0.965 0.008 96 / 0.24),
            inset 1.5px 0 0 oklch(0.965 0.008 96 / 0.11),
            inset 0 3px 4px -1px var(--cut-floor),
            inset 0 -2px 3px -1px oklch(0.860 0.016 100 / 0.15);
  --slab:   0 18px 40px -18px oklch(0.050 0.018 192 / 0.90);

  /* 圆角：石头上的东西是**切出来的**，不是揉圆的。
     纸有例外——纸会卷边，但也只卷 2px */
  --r-cut:  2px;
  --r-slab: 3px;
  /* 99px 只出现在滚动条拇指上 —— 它是浏览器的零件，不是墙上的东西 */
  --r-thumb: 99px;
}

/* ── 基础重置：所有页面共用 */
* { box-sizing: border-box; }
[hidden] { display: none !important; }

/*
 * 图和矢量图默认不许超出容器。
 * 藏品 SVG 的固有宽度是 500px —— 少写一条容器规则，它就会在窄屏上
 * 把整页撑宽，而 overflow 只是把溢出藏起来，正文照样被推到屏幕外。
 */
img, svg { max-width: 100%; }

/*
 * 浏览器自带的那几样也归这个世界管。
 * 选区、光标、滚动条、焦点圈 —— 不接管它们，页面就有几块地方
 * 属于"没有设计系统的默认值"。
 */
::selection { background: var(--seal); color: var(--paper); }
:root { accent-color: var(--seal); caret-color: var(--seal); color-scheme: dark; }

* { scrollbar-width: thin; scrollbar-color: var(--lime-faint) transparent; }
*::-webkit-scrollbar { width: 10px; height: 10px; }
*::-webkit-scrollbar-track { background: transparent; }
*::-webkit-scrollbar-thumb {
  background: var(--thumb);
  border: 3px solid transparent;
  background-clip: content-box;
  border-radius: var(--r-thumb);
}
*::-webkit-scrollbar-thumb:hover { background-color: var(--seal); }

:where(a, button, [tabindex], input, textarea, summary):focus-visible {
  outline: 2px solid var(--seal-light);
  outline-offset: 2px;
  border-radius: var(--r-cut);
}

/* 所有表格数字对齐：读数不该因为 1 比 8 窄而抖 */
:where(.n, .num, code, kbd, time) { font-variant-numeric: tabular-nums; }
