/* 人类全景知识地图 — Phase 2 browser.
 *
 * Deliberately plain.  No framework, no build step, no CSS-in-JS.  The layout
 * is a knowledge tool: dense but readable, one accent colour per concern,
 * and nothing animates unless it is telling you something.
 */

:root {
  --bg: #fbfaf7;
  --panel: #ffffff;
  --ink: #1d1d1f;
  --dim: #6b6b70;
  --line: #e3e0da;
  --accent: #2f5d8a;
  --accent-soft: #eaf1f8;
  --ok: #2f7d4f;
  --cand: #8a6d1f;
  --warn: #a4452f;
  --manual: #6f4a9c;
  --hover: #f5f3ee;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font: 15px/1.65 -apple-system, "Segoe UI", "PingFang SC", "Hiragino Sans GB",
        "Microsoft YaHei", "Noto Sans CJK SC", sans-serif;
}

a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }
code {
  font-family: "SF Mono", Consolas, "Liberation Mono", monospace;
  font-size: 0.88em;
  background: #f2f0ec;
  padding: 0.1em 0.35em;
  border-radius: 3px;
}

/* ------------------------------------------------------------------ chrome */
.topbar {
  display: flex;
  align-items: center;
  gap: 1.2rem;
  padding: 0.7rem 1.4rem;
  background: var(--panel);
  border-bottom: 1px solid var(--line);
  position: sticky;
  top: 0;
  z-index: 10;
}
.brand { font-weight: 650; font-size: 1.25rem; color: var(--ink); white-space: nowrap; }
/* 标题旁边那句 slogan。它跟在 `.brand` 后面，顶栏的 `gap` 就是它和标题的间距。
   比标题小一号、浅一档，读起来是注解而不是第二个标题 —— 但**不能太浅**：
   原来的 `--dim`（#6b6b70）配 0.82rem 在纸色底上发灰，像没加载完的占位文字。
   现在这个灰在 #fbfaf7 上对比度约 9:1，是一句读得清的话。
   窄屏上先让位（和整页转单栏是同一个 900px 断点）—— 那时候搜索框比一句话重要。 */
.slogan { font-size: 0.95rem; color: #45454c; white-space: nowrap; }
@media (max-width: 900px) { .slogan { display: none; } }
.searchbox { display: flex; gap: 0.4rem; flex: 1; max-width: 640px; }
.searchbox input[type="search"] {
  flex: 1;
  padding: 0.45rem 0.7rem;
  border: 1px solid var(--line);
  border-radius: 6px;
  font-size: 0.95rem;
  background: #fff;
}
.searchbox button, .button {
  padding: 0.45rem 0.9rem;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: var(--panel);
  color: var(--ink);
  font-size: 0.9rem;
  cursor: pointer;
}
.searchbox button:hover, .button:hover { background: var(--accent-soft); text-decoration: none; }
.button-on { background: var(--accent-soft); border-color: var(--accent); }
.topnav { display: flex; gap: 0.9rem; font-size: 0.9rem; }

main { max-width: 1020px; margin: 0 auto; padding: 1.6rem 1.4rem 4rem; }

/* ----------------------------------------------------------------- panels */
.panel {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 1.1rem 1.3rem;
  margin: 1rem 0;
}
.panel h2 {
  margin: 0 0 0.6rem;
  font-size: 1rem;
  font-weight: 620;
  display: flex;
  align-items: center;
  gap: 0.5rem;
}
.panel.accent { border-left: 4px solid var(--accent); }
.count {
  font-size: 0.8rem;
  font-weight: 500;
  color: var(--dim);
  background: #f2f0ec;
  padding: 0.05rem 0.45rem;
  border-radius: 10px;
}
.hint { color: var(--dim); font-size: 0.86rem; margin: 0.4rem 0; }
.lead { font-size: 1rem; color: #3a3a3e; }
.dim { color: var(--dim); }
.small { font-size: 0.82rem; }
.empty { color: var(--dim); font-style: italic; }
.hero h1 { font-size: 1.5rem; margin: 0.4rem 0; }

/* ------------------------------------------------------------------- tags */
.tag {
  display: inline-block;
  font-size: 0.76rem;
  padding: 0.06rem 0.45rem;
  border-radius: 4px;
  background: #f2f0ec;
  color: var(--dim);
  white-space: nowrap;
}
.tag-source { background: var(--accent-soft); color: var(--accent); }
.tag-ok { background: #e6f3ea; color: var(--ok); }
.tag-cand { background: #f7f0dc; color: var(--cand); }
.tag-warn { background: #fbeae5; color: var(--warn); }
.tag-manual { background: #f0eafa; color: var(--manual); }

/* ------------------------------------------------------------------ links */
.pills { display: flex; flex-wrap: wrap; gap: 0.35rem; }
.pill {
  display: inline-block;
  padding: 0.15rem 0.6rem;
  border: 1px solid var(--line);
  border-radius: 14px;
  background: #fff;
  font-size: 0.88rem;
}
.pill:hover { background: var(--accent-soft); border-color: var(--accent); text-decoration: none; }
.pill-dirty { border-style: dashed; color: var(--dim); }
.big-link { font-size: 1.15rem; font-weight: 600; }

/* =======================================================================
   Phase 2.1 — the focused topic browser

   Three surfaces now share one shell (首页 / 搜索 / 主题页), and the topic
   page is the one that carries the browsing.  Its shape is the answer to
   what the audit found: a reader standing on 人工智能 needs to know where it
   sits, what is under it, and how much is under each of those — not a bigger
   picture.
   ======================================================================= */

/* -------------------------------------------------------------- landing */
.steps { margin: 0.4rem 0 0.6rem; padding-left: 1.3rem; font-size: 0.9rem; line-height: 1.55; }
.steps li { margin: 0.35rem 0; }

/* ---------------------------------------------------------- breadcrumb */
.breadcrumb { margin: 0.2rem 0 0.9rem; font-size: 0.92rem; color: var(--dim); }
.breadcrumb a { color: var(--accent); }
.breadcrumb .crumb-label { margin-right: 0.5rem; font-size: 0.82rem; }
.breadcrumb .here { color: var(--ink); font-weight: 600; }
.breadcrumb .sep { margin: 0 0.35rem; color: var(--dim); }
.breadcrumb .crumb-more { color: var(--dim); cursor: help; }
.level { margin: 0.3rem 0; display: flex; flex-wrap: wrap; gap: 0.35rem; align-items: center; }
.level .dim { min-width: 2rem; }

/* --------------------------------------------------------- topic page */
/* The picture sits beside the lists on a wide screen and under them on a
   narrow one.  It is a normal grid cell, never an overlay: the audit's P0
   was a drawer covering nodes that were visibly there and unclickable. */
.topic-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 380px;
  gap: 1.2rem;
  align-items: start;
}
.topic-main { min-width: 0; }
.topic-side { position: sticky; top: 4.5rem; }
@media (max-width: 980px) {
  .topic-layout { grid-template-columns: minmax(0, 1fr); }
  .topic-side { position: static; }
}
.graphpanel .cy-topic { width: 100%; height: 300px; border: 1px solid var(--line); border-radius: 8px; background: #fdfdfd; }

.summary {
  margin: 0.3rem 0 0.5rem;
  font-size: 1rem;
  color: #3a3a3e;
}

/* 子主题 as rows, because each one carries a number worth reading. */
.children { list-style: none; padding: 0; margin: 0.3rem 0; }
.children li { border-bottom: 1px solid var(--line); }
.children li:last-child { border-bottom: 0; }
.child {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 0.8rem; padding: 0.42rem 0.2rem; color: var(--ink);
}
.child:hover { background: var(--accent-soft); text-decoration: none; }
.child-label { font-weight: 550; }
.child-count {
  color: var(--dim); font-size: 0.82rem; font-variant-numeric: tabular-nums;
  min-width: 2.2ch; text-align: right;
}

/* ----------------------------------------------------- folded duplicates */
/* Nine Wiktionary senses matched 「明朝」 through a synonym and printed nine
   identical rows.  They are folded, not merged: the reader still reaches
   every one of them, and no two sources are ever in the same fold. */
.dupe { padding: 0.45rem 0; border-bottom: 1px solid var(--line); }
.dupe > details { border-top: 0; padding-top: 0; margin: 0; }
.dupe summary { display: flex; flex-wrap: wrap; gap: 0.45rem; align-items: baseline; }
.dupe .hits.nested { margin: 0.4rem 0 0 1.1rem; }
.hits.nested .hit { padding: 0.25rem 0; }

/* ------------------------------------------------------- technical folds */
.tech { margin: 0.5rem 0; border-top: 1px solid var(--line); padding-top: 0.5rem; }
.tech > summary { color: var(--dim); font-weight: 550; font-size: 0.88rem; }
.tech > summary:hover { color: var(--accent); }
/* ---------------------------------------------------------------- nodehead */
.nodehead { margin-bottom: 0.6rem; }
.nodehead h1 { margin: 0.2rem 0 0.4rem; font-size: 1.75rem; }
.nodehead.compact h1 { font-size: 1.35rem; }
.nodehead .meta { display: flex; flex-wrap: wrap; gap: 0.35rem; align-items: center; margin: 0.2rem 0; }
.actions { display: flex; gap: 0.5rem; margin-top: 0.7rem; }

/* ----------------------------------------------------------------- tables */
.table, .stats { width: 100%; border-collapse: collapse; font-size: 0.9rem; }
.table th, .table td { text-align: left; padding: 0.35rem 0.6rem; border-bottom: 1px solid var(--line); vertical-align: top; }
.table th { font-weight: 600; color: var(--dim); font-size: 0.82rem; }
.stats th { text-align: left; color: var(--dim); font-weight: 500; width: 22%; }
.stats td { font-variant-numeric: tabular-nums; width: 28%; }

/* Home page: where each of the six sources actually lives.  Three columns
   with the source name narrow, the size tabular, and the prose left wide. */
.sources { margin-top: 0.6rem; }
.sources thead th { border-bottom: 1px solid var(--line); }
.sources tbody th { width: 15%; font-size: 0.88rem; color: var(--fg); }
.sources tbody td:first-of-type { width: 17%; font-variant-numeric: tabular-nums; white-space: nowrap; }
.sources tbody td { line-height: 1.5; }

.hits { list-style: none; padding: 0; margin: 0.4rem 0; }
.hit { padding: 0.4rem 0; border-bottom: 1px solid var(--line); display: flex; flex-wrap: wrap; gap: 0.45rem; align-items: baseline; }
.hit-label { font-weight: 600; }

.roots { list-style: none; padding: 0; margin: 0.4rem 0; }
.root { display: flex; justify-content: space-between; padding: 0.55rem 0.8rem; border: 1px solid var(--line); border-radius: 8px; margin-bottom: 0.4rem; }
.root:hover { background: var(--accent-soft); border-color: var(--accent); text-decoration: none; }
.root-label { font-weight: 600; }
.root-count { color: var(--dim); font-size: 0.84rem; }

details { margin: 0.5rem 0; border-top: 1px solid var(--line); padding-top: 0.5rem; }
summary { cursor: pointer; font-weight: 550; color: var(--accent); }
.raw { background: #f7f6f3; padding: 0.7rem; border-radius: 6px; overflow-x: auto; font-size: 0.8rem; }
.gloss { margin: 0.4rem 0; }

/* ------------------------------------------------------------------ graph */
.legend { display: flex; flex-wrap: wrap; gap: 1rem; margin: 0.6rem 0; font-size: 0.84rem; }
.legend-item { display: flex; align-items: center; gap: 0.35rem; }
.legend-line { display: inline-block; width: 26px; }

.cy {
  width: 100%;
  height: 560px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: #fdfdfc;
}

/* =======================================================================
   Phase 2.2 — the Tree Explorer

   One page, two halves: search results on the left (independently
   scrollable), a tree on the right.  Nothing floats over anything — §26
   restates the audit's P0 as a layout rule, because the failure it found was
   a drawer covering nodes that were visibly there and unclickable.

   The tree is ordinary DOM in an <ol>: indentation is `--d` levels, and each
   row carries a short arm back to its parent plus a vertical for every
   ancestor column that still has something under it.  §27 asks for a tree that
   works, not a tree that is beautiful; the lines are here because a reader
   following an eight-level path needs to see which column a row belongs to,
   and the two animations are here because a branch arriving or leaving is the
   one thing on this page that is worth watching.
   ======================================================================= */

/* The explorer is the one page that is not a column of prose: §26 gives the
   tree the rest of the viewport, so `main`'s reading-width cap comes off and
   the two halves do their own padding. */
/* The explore page is an app shell: it fills the viewport, each half scrolls
   on its own, and the page itself never scrolls.  That is what keeps the left
   column still.  It used to be `position: sticky` inside a scrolling page,
   which cannot hold: a sticky box stops at the bottom of its containing
   block, so anything below the grid row — there was a page footer then —
   slid the column up by its own height at the end of the scroll.  Measured:
   the column sat at y=53 throughout and then jumped to y=-72. */
body.explore-page { display: flex; flex-direction: column; height: 100vh; overflow: hidden; }
.explore-page .topbar { flex: none; }
/* `margin: 0` is load-bearing: the base `main` rule carries `margin: 0 auto`
   for the reading-width pages, and an **auto cross-axis margin defeats
   `align-self: stretch` in a flex container** — the item shrinks to its
   content and centres instead of filling.  Measured before this: `main` was
   929px wide and centred inside every viewport. */
.explore-page main { flex: 1; min-height: 0; max-width: none; margin: 0; padding: 0; overflow: hidden; }

.explore {
  display: grid;
  grid-template-columns: 350px minmax(0, 1fr);
  /* One row, exactly the height of the shell.  Without this the row is
     `auto`, and `height: 100%` on a column would resolve against its own
     content — so the column would grow instead of scrolling. */
  grid-template-rows: minmax(0, 1fr);
  gap: 0;
  align-items: start;
  height: 100%;
}

/* The results column scrolls on its own and stays put while the tree grows.
   §20: the list never goes away when a root is chosen — that is what lets you
   compare 知乎's 人工智能 with Wikipedia's without losing your place. */
/* No left column on a page that has nothing to put in it — a node page has one
   hierarchy on screen and no choice to offer, so the tree takes the width. */
.explore.solo { grid-template-columns: minmax(0, 1fr); }
.rcol {
  /* Full height of the shell, split in two: `.rcol-body` scrolls, the usage
     hints under it do not.  No `sticky`: there is nothing to stick to when the
     page around it does not move. */
  display: flex;
  flex-direction: column;
  height: 100%;
  overflow: hidden;
  padding: 1rem 1rem 0;
  border-right: 1px solid var(--line);
  background: var(--panel);
}
/* 左栏里会滚动的那一段：来源卡 / 结果列表 / 详情卡。底部的 3rem 是给最后一项
   留的呼吸，原来挂在 `.rcol` 上（那时整个左栏自己滚）。 */
.rcol-body { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding-bottom: 3rem; }
.tcol {
  min-width: 0; height: 100%;
  /* A stack, not a scroller: the selected node's detail sits at the top and
     the tree takes the rest, scrolling inside its own box.  `flex` rather
     than a grid because the number of strips above the tree varies （a
     redirect banner comes and goes, the detail card is up here on a search
     page, the path bar only exists once there is a root）, and a fixed row
     list would have to know that.  Everything above the tree
     is `flex: none`, which is what keeps the detail on screen while the tree
     moves under it — without either one overlapping the other. */
  display: flex; flex-direction: column;
  overflow: hidden;
  padding: 1.2rem 1.4rem 0 2rem;
}
/* 树上方那条链路：一级主题 → … → 当前节点。一整行，长了就折行 —— 它标的是
   「你在哪」，横向滚出去一半就等于没有。 */
.pathbar {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.1rem 0.3rem;
  margin: 0 0 0.5rem;
  padding: 0.35rem 0.6rem;
  background: var(--accent-soft);
  border-radius: 6px;
  font-size: 0.85rem;
  line-height: 1.5;
}
.pathbar[hidden] { display: none; }
.pseg {
  color: var(--accent);
  /* 分类名可以很长（「Category:某国某年某月」），截断而不是把整行撑开。 */
  max-width: 22rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
a.pseg:hover { text-decoration: underline; }
/* 当前这一段不是链接：它就是「你在这儿」。 */
.pseg.cur { color: var(--ink); font-weight: 600; }
.psep { color: var(--dim); font-size: 0.75rem; }
/* 点左箭头上去的那一步。和树里的 ◀ 是同一个记号，所以同一套颜色。 */
.psep.up { color: var(--cand); }
/* The detail strip, when it is up here rather than in the left column.  Capped
   and scrollable so a long lead plus three senses cannot take the tree's whole
   height; measured, the ordinary case is ~350 px.
   Full width, the way the tree's own column is: a strip that stopped short of
   it read as a box parked in the corner with the rest of the row empty. */
.tcol > .card { flex: none; max-height: 46vh; overflow-y: auto; }

@media (max-width: 900px) {
  /* One column: there is nothing to hold side by side, so the shell goes away
     and the page scrolls again. */
  body.explore-page { display: block; height: auto; overflow: visible; }
  .explore-page main { overflow: visible; }
  .explore { grid-template-columns: minmax(0, 1fr); height: auto; }
  .rcol { height: auto; border-right: 0; border-bottom: 1px solid var(--line); }
  .tcol { height: auto; overflow: visible; }
}

/* --------------------------------------------------------- result column */
.rcol-head { display: flex; align-items: baseline; justify-content: space-between; gap: 0.5rem; }
.rcol-head h2 { font-size: 1rem; margin: 0 0 0.5rem; }

/* 左栏底部的使用提示。
 *
 * 位置：正常流里，跟在左栏内容后面，额外来一段上边距把它和上面那张卡片分开 ——
 * 首页的左栏本来很短，所以它落在左下角；内容长了它就跟在最后（不做 sticky，
 * 这个产品里没有东西浮在别的东西上面）。
 *
 * 形态：跟这个产品里其它「一条独立的东西」一样，用左边一道 accent 竖边而不是
 * 一圈边框 —— 来源卡片、详情条都是这个写法。 */
.hints {
  flex: none;                       /* 钉在栏底，不跟着上面滚 */
  margin: 1rem 0 0;
  padding: 0.1rem 0 1.25rem 0.8rem;
  border-left: 3px solid var(--accent-soft);
  font-size: 0.82rem;
  line-height: 1.6;
  color: var(--dim);
}
/* 「使用介绍」那一行：标题在左，**收起 / 展开那一头在右**。
   `<details>` 默认那个三角画在左边，而左边已经被标题占了。它是 `list-style`
   画的，所以下面关掉它两次（`list-style: none` 给标准浏览器，
   `::-webkit-details-marker` 给 Safari / 旧版 Chrome），换成自己的 `::after`。 */
.hints summary {
  font-size: 1rem;                  /* 用户要「大一点」：和左栏的 h2 同级 */
  font-weight: 650;
  color: var(--ink);
  cursor: pointer;
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 0.6rem;
  list-style: none;
}
.hints summary::-webkit-details-marker { display: none; }
/* 右端那两个字加一个三角。**要说出来**：只有一个三角，读者不知道点下去是
   「展开」还是「收起」。 */
.hints summary::after {
  content: "收起 ▾";
  flex: none;
  font-size: 0.82rem;
  font-weight: 400;
  color: var(--dim);
}
.hints:not([open]) summary::after { content: "展开 ▸"; }
.hints[open] summary { margin-bottom: 0.45rem; }
.hints ul { margin: 0; padding: 0; list-style: none; }
.hints li { margin: 0.35rem 0; }
.hints li + li { margin-top: 0.5rem; }
.hints strong { color: var(--ink); font-weight: 600; }
.hints code { font-size: 0.92em; padding: 0.05em 0.3em; }

/* 随机逛一个。一条链接，不是按钮：它去的是一个 URL。 */
.randline { margin: 0.15rem 0 0.9rem; }
/* 「随机逛一个」。它原来是一颗 `0.82rem` 的小胶囊，边框是几乎看不见的
   `--line`，读起来像一段套了个框的文字，而不像一个可以点的东西。现在：

   * 大一圈（`0.9rem`、`0.45rem × 1rem` 的留白）—— 它是这一栏里唯一的
     「玩一下」，值得占那点地方；
   * 边框换成 `--accent`、文字加重，让它一眼就是按钮。不会和上面选中的来源卡
     混起来：那是整宽的方卡，这是一颗胶囊，形状和大小都不同；
   * 左边一个 `✦`：这颗按钮说的是「随便给我一个」，那正是这个记号的意思，
     而且它给了下面那个「忙」的状态一个可以转的东西。 */
.randlink {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.45rem 1rem;
  border: 1px solid var(--accent);
  border-radius: 999px;
  font-size: 0.9rem;
  font-weight: 600;
  color: var(--accent);
  background: var(--panel);
  transition: background 120ms ease, color 120ms ease;
}
.randlink:hover { background: var(--accent-soft); text-decoration: none; }
.randlink-mark { font-size: 0.95em; line-height: 1; }
/* 请求在路上。就地换根那一下要等一个来回，没有反馈会让人以为没点中 ——
   原来是整颗调淡，看不出「在忙」和「坏了」的区别；现在 `✦` 转起来。 */
.randlink.busy { opacity: 0.65; pointer-events: none; }
.randlink.busy .randlink-mark { animation: randlink-spin 900ms linear infinite; }
@keyframes randlink-spin { to { transform: rotate(360deg); } }

/* The front door's source switcher.  Three published hierarchies, one of them
   drawn in the tree at a time.  Cards rather than tabs-with-an-underline: the
   reader is choosing which of three *trees* to be in, and a card is the shape
   that reads as "this is the whole column's subject", not as a filter strip.
   The 4px left edge is the accent's usual place in this product — the card
   above the tree carries it the same way. */
.srctabs { display: flex; flex-direction: column; gap: 0.45rem; margin: 0.2rem 0 0.7rem; }
.srctab {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 0.6rem;
  width: 100%;
  padding: 0.6rem 0.85rem;
  font: inherit;
  text-align: left;
  cursor: pointer;
  color: var(--ink);
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 8px;
  transition: background 120ms ease, border-color 120ms ease;
}
.srctab:hover { background: #f6f4f0; }
.srctab.on {
  background: var(--accent-soft);
  border-color: var(--accent);
}
.srctab-name { font-weight: 600; }
.srctab.on .srctab-name { color: var(--accent); }
.srctab-n {
  font-size: 0.8rem;
  color: var(--dim);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.srctab.on .srctab-n { color: var(--accent); }
.rsec { border-top: 1px solid var(--line); padding-top: 0.5rem; margin-top: 0.6rem; }
.rsec h3 { font-size: 0.86rem; margin: 0 0 0.4rem; color: var(--dim); font-weight: 600; }

.rlist { list-style: none; padding: 0; margin: 0; }
.rlist.compact .rrow { padding: 0.3rem 0; }
.rrow {
  padding: 0.6rem 0.5rem;
  border: 1px solid transparent;
  border-bottom: 1px solid var(--line);
  border-radius: 8px;
}
.rrow.on {
  background: var(--accent-soft);
  border-color: var(--accent);
  border-bottom-color: var(--accent);
}
/* The row is the link, so it has to look pressable as a whole. */
.rrow-link { display: block; color: inherit; text-decoration: none; }
/* The global `a:hover` underline has to be switched off here: the whole row
   lighting up is the affordance, and a rule drawn through the definition text
   just reads as damage.  Same reason `.pill`, `.child` and `.root` each say
   `text-decoration: none` on hover. */
.rrow-link:hover { text-decoration: none; }
.rrow:hover { background: var(--hover); border-color: var(--line); }
.rrow.on:hover { background: var(--accent-soft); border-color: var(--accent); }
.rrow-link:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 8px; }
.rrow-top { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.35rem; }
.rname { font-weight: 600; }
.rrow.on .rname { color: var(--accent); }
.rrow .alias { font-size: 0.82rem; color: var(--cand); margin-top: 0.2rem; }
.rmeta { font-size: 0.84rem; color: var(--dim); margin-top: 0.25rem; font-variant-numeric: tabular-nums; }
.rsum { font-size: 0.86rem; color: #3a3a3e; margin-top: 0.25rem; }
/* A state marker, not a button — the row itself is what you click. */
.rgo {
  display: inline-block; margin-top: 0.45rem; font-size: 0.78rem;
  padding: 0.15rem 0.6rem; border-radius: 999px;
  background: var(--accent); color: #fff;
}
.lex > summary { font-weight: 600; }
.dupe { border-top: 0; padding-top: 0; margin: 0.2rem 0; }
.dupe > summary { display: flex; flex-wrap: wrap; gap: 0.4rem; align-items: baseline; }

/* ------------------------------------------------------------ detail strip */
/* §22: name, source, type, a definition if the data has one, the other
   sources.  It sits above the tree in normal flow — opening a node's details
   must not mean leaving the tree or covering it. */
.card {
  background: var(--panel);
  border: 1px solid var(--line);
  border-left: 4px solid var(--accent);
  border-radius: 10px;
  padding: 0.8rem 1.1rem;
  margin-bottom: 0.8rem;
}
.card.loading { opacity: 0.55; }
.card-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.4rem; }
.card-head h2 { margin: 0; font-size: 1.25rem; }
.card-sum { margin: 0.4rem 0; }
/* The lead is several paragraphs and each is its own `<p>`; without this they
   sit 0.4rem apart, which is the gap between the two blocks rather than
   between paragraphs of the same one. */
.card-sum + .card-sum { margin-top: 0.7rem; }
/* The card's two prose blocks.  The heading is what keeps them apart: without
   it the article's lead and the dictionary's gloss read as one text that
   happens to change source halfway through. */
.card-block + .card-block { margin-top: 0.7rem; }
.card-block-h {
  margin: 0.2rem 0 0;
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  color: var(--dim);
}
.card-meta { margin: 0.2rem 0; font-variant-numeric: tabular-nums; }
.card .tech { margin: 0.5rem 0 0; }
.card-foot { margin-top: 0.5rem; }

/* ------------------------------------------------------------------- tree */
/* The tree's own box, and the only thing in the column that scrolls — both
   ways.  It used to be `overflow-x: auto` with the column scrolling
   vertically; now that the column is a fixed stack, the tree carries both
   axes, which is also what `tree.js`'s `verticalScroller()` looks for. */
.treewrap { flex: 1; min-height: 0; overflow: auto; padding-bottom: 4rem; }
/* Has to come *after* the rule above, not up in the layout's media block:
   same specificity, so the later rule wins, and the tree went on being its own
   scroll box on a narrow screen while the page refused to scroll at all. */
@media (max-width: 900px) {
  .treewrap { flex: none; overflow: visible; }
}
/* Holding the left button pushes the canvas around (see `wirePan`).  It moves
   in both directions, so the class goes on the root and `!important` overrides
   whatever the row under the pointer would otherwise show — the tree sets its
   own cursor on the controls, and a grab that turns back into a pointer
   halfway through a drag reads as the drag having ended.  The state lasts
   exactly as long as the button is held.  `user-select` matters as much as
   the cursor: a drag across four rows of text otherwise selects all of it. */
html.tree-panning, html.tree-panning * { cursor: grabbing !important; user-select: none; }
.tree { list-style: none; margin: 0; padding: 0; min-width: max-content; }

/* A group of roots, on the front door.  Two published hierarchies live in one
   tree, and this heading is what keeps them from reading as one odd set. */
/* A quiet label, not a section rule.  Two full-width hairlines down a page
   with half-empty rows read as a table with missing columns; the gap between
   groups is what separates them. */
.tgroup {
  margin: 1.4rem 0 0.4rem;
  font-size: 0.78rem;
  font-weight: 600;
  color: #9c978e;
  letter-spacing: 0.06em;
}
.tgroup:first-child { margin-top: 0; }

/* Folding lowers a branch's height to nothing instead of blinking it out.
   `prefers-reduced-motion` turns it off at the end of this block — the tree
   works without it. */
.trow { transition: height 190ms cubic-bezier(0.2, 0.7, 0.3, 1), opacity 190ms ease; }
.trow.t-leaving { pointer-events: none; }

/* Opening a level draws it; it does not perform it.  The rows used to fade and
   slide in one after another — 200ms each, on a 22ms stagger — so a level of
   fifty took four tenths of a second to finish arriving, and the reader was
   waiting on an animation to read rows that had already been fetched.  Folding
   keeps its transition: that one says *what just left*, which is worth 190ms;
   an entrance only delays what is already there. */

.tline {
  position: relative;
  /* inline-flex, not flex: a row is as wide as what is in it.  Stretched to the
     column it was 1045px for 250px of text, so hovering 「能量」 lit a bar four
     times the length of the row and the whole tree read as a table with three
     empty columns. */
  display: inline-flex;
  /* baseline, not center.  Everything in a row is text, and the row's parts are
     set at different sizes — a root's name is 1.05rem, the ◀ n / n ▶ controls
     0.78rem, the source·type tag 0.86rem.  Centring their boxes centres their
     *heights*, which leaves the smaller ones sitting low: on a root the count
     and its triangle were 1.7px below the name's baseline, close enough to
     look like a mistake and not close enough to look deliberate.  Aligning the
     baselines is what "on the same line" means for text. */
  align-items: baseline;
  gap: 0.4rem;
  min-height: 2rem;
  /* Indentation is set inline by tree.js, not computed here: a row starts
     where its parent's line ends, and only the browser knows how wide that
     line came out.  See `layout` there. */
  padding: 0.1rem 0.45rem 0.1rem 0;
  border-radius: 0 6px 6px 0;
}
.tline:hover { background: #f7f7f4; }

/* The connector: one short arm into the row, plus a vertical for every
   ancestor column that still has something below it.  Elements rather than
   `border-left`, because only the drawing knows which columns run on — the
   last child's column stops, its parent's does not.

   Both the position and the arm's length come from tree.js, for the same
   reason the indent does: they are measured, not stepped. */
.tg {
  position: absolute;
  width: 1.5px;
  background: #c4bbaa;
  border-radius: 1px;
  pointer-events: none;
}
.tg.v { top: 0; bottom: 0; }
.tg.v-top { top: 0; height: 50%; }
/* The stub a branching row drops between its own bottom and the first child's
   top.  `top: 50%` here was the line that ran alongside the row's `n ▼`. */
.tg.v-tail { top: 100%; bottom: 0; }
.tg.arm { top: 50%; height: 1.5px; }

/* A root carries no marker at all.  It has no corner above it, and the two
   things that were tried here both lied: a full-width wash made five roots
   into one tan rectangle, and a left hairline landed on exactly the x of the
   children's connectors, so 「root」 and 「child of a root」 looked the same.
   Weight does the job instead — a root is the bold row, and the heading above
   says which hierarchy it belongs to. */
.tree.multiroot .tname.root { font-size: 1rem; font-weight: 650; }
.trow.selected > .tline { outline: 2px solid var(--accent); outline-offset: -2px; }

/* ◀ expand / ▲ fold.  One control per direction, never a gesture: §5 bans
   click-vs-double-click entirely, and a reader should know what every control
   does before touching it. */
/* No border, no chip: at rest these are two small numbers either side of the
   name, and forty rows of outlined pills read as forty small buttons rather
   than as a tree.  The hover fill is what says they are controls, and the
   column stays fixed so the names still line up. */
.tgl {
  font: inherit;
  font-size: 0.78rem;
  font-variant-numeric: tabular-nums;
  min-width: 2.9rem;
  padding: 0.14rem 0.35rem;
  border: 0;
  border-radius: 6px;
  background: none;
  color: var(--dim);
  cursor: pointer;
  white-space: nowrap;
  /* Room on the left for the connector line that hangs past a row's name.
     The row's own flex gap leaves 6.4px between the name and this button, and
     a 1.5px line centred in that had 1.5px of clearance either side — close
     enough that it read as the line running into the pill.  Both controls get
     the margin, so a row's content shifts as a whole and the measured gap
     between a name and its children is unaffected. */
  margin-left: 0.35rem;
  transition: background 120ms ease, color 120ms ease;
}
.tgl:hover { background: #ebe8e2; color: var(--accent); }
/* An open branch is the one state worth colour — it is the one you can undo. */
.tgl.on { background: var(--accent-soft); color: var(--accent); font-weight: 600; }
.tgl.on:hover { background: #dae6f3; }
.tgl.up { text-align: right; }
.tgl.down { text-align: left; }

/* An empty direction still holds its column — that is what keeps a level of
   leaves lined up with the level above it — but it is a placeholder, not a
   control, so it is drawn quieter than the paper it sits on. */
.tgl.off {
  border-color: transparent;
  background: none;
  color: #e0dcd3;
  cursor: default;
  text-align: center;
}

.tname {
  font: inherit;
  font-weight: 550;
  color: var(--ink);
  background: none;
  border: 0;
  padding: 0.12rem 0.4rem;
  border-radius: 5px;
  cursor: pointer;
  text-align: left;
  white-space: nowrap;
  transition: background 120ms ease;
}
.tname:hover { background: #eceae4; }
.tname:focus-visible, .tgl:focus-visible, .tpage:focus-visible, .tpagesize:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}
.tname.root { font-weight: 700; font-size: 1.05rem; }
/* A ↻ row is not one more topic in the list — it says the branch has already
   been through here.  Greyed to the same colour as `.tmeta` beside it,
   deliberately the same value: the two are saying the same kind of thing. */
.tname.cycle { color: #9c978e; }
.tcycle { color: var(--cand); font-weight: 700; cursor: help; }
.tmeta { color: #9c978e; font-size: 0.76rem; white-space: nowrap; }

/* 一层的翻页条。它替掉了「显示更多（还有 4,120 个）」—— 那个按钮只能往前，
   点过四十次之后想回这一层的开头，只能把整条分支收起来再打开。
   页码说明你在哪，右边的框说明一页多少行，并且记住它。 */
.tpager { display: inline-flex; align-items: center; gap: 0.3rem; padding: 0.2rem 0; }
.tpage {
  font: inherit;
  font-size: 0.86rem;
  line-height: 1;
  padding: 0.18rem 0.5rem;
  border: 1px solid var(--line);
  border-radius: 5px;
  background: var(--panel);
  color: var(--ink);
  cursor: pointer;
}
.tpage:hover:not(:disabled) { background: var(--accent-soft); border-color: var(--accent); }
.tpage:disabled { color: #c8c3ba; cursor: default; }
.tpageno {
  font-size: 0.8rem;
  color: var(--dim);
  font-variant-numeric: tabular-nums;
  min-width: 4.5ch;
  text-align: center;
}
.tpagesize {
  font: inherit;
  font-size: 0.8rem;
  width: 4.2rem;
  padding: 0.16rem 0.3rem;
  border: 1px solid var(--line);
  border-radius: 5px;
  background: #fff;
  color: var(--ink);
}
/* 回到维基百科。跟着卡片表头那一排标签走，但它是能点的，所以要有别于它们：
   标签是灰底无边框的方块，这条是下划线 + 强调色，一眼看得出是出口。 */
/* Follows the tags instead of being pushed to the strip's right edge.  When the
   detail strip sat in the 350px left column, `margin-left: auto` cost nothing
   — there was no room to lose.  In the tree's column, which is as wide as the
   window, it stranded the link a thousand pixels from the title it belongs to
   （the reader drew an arrow across the gap）. */
.card-wiki {
  font-size: 0.78rem;
  color: var(--accent);
  text-decoration: none;
  border-bottom: 1px solid currentColor;
  white-space: nowrap;
}
.card-wiki:hover { opacity: 0.75; }


@media (prefers-reduced-motion: reduce) {
  .trow { transition: none; }
  /* 转圈的 `✦` 也停 —— 那颗按钮仍然会变淡，等待照样看得出来。 */
  .randlink.busy .randlink-mark { animation: none; }
}

/* =======================================================================
   The canvas (Phase 2 revision) — RETAINED, NO LONGER THE FRONT DOOR

   Phase 2.1 stopped routing map.html: an unbounded surface turned out to be
   a bad way to browse a hierarchy (the audit measured 158 nodes, no
   breadcrumb, no way back).  The canvas is not deleted — it is repositioned
   onto the topic page as 局部关系图 (.cy-topic above), where it is bounded,
   sits in normal flow, and every node is a link.  These rules stay because
   map.html and map.js are still in the tree.

   Three rules shaped the original:
   ======================================================================= */
   1. The canvas owns the viewport.  Nothing floats over it that the reader
      did not ask for, and nothing that floats over it is opaque enough to
      hide a node behind.
   2. Position is information.  Growing outwards from a node has to *mean*
      something, which is why the placement is radial and anchored rather
      than a force layout — a force layout re-arranges the whole map on every
      click, and a map that moves under your hand is a map you cannot read.
   3. The 网络 layout is a deliberate, reversible act.  It is the only thing
      that re-positions existing nodes, and switching back restores exactly
      where they were.
   ======================================================================= */

.canvas-page main { max-width: none; margin: 0; padding: 0; }

.canvas { position: relative; height: calc(100vh - 55px); overflow: hidden; }
/* Its own class rather than `.cy`: the Phase 2 graph page already owns `.cy`
   with a fixed 560px height, and sharing the name left the canvas a third of
   a viewport short of filling its parent. */
.cy-canvas {
  position: absolute;
  inset: 0;
  background: radial-gradient(circle at 50% 45%, #fffefb 0%, var(--bg) 70%);
}

.hud {
  position: absolute;
  z-index: 5;
  background: rgba(255, 255, 255, 0.95);
  border: 1px solid var(--line);
  border-radius: 10px;
  box-shadow: 0 2px 12px rgba(31, 29, 26, 0.07);
  backdrop-filter: blur(3px);
}

.hud .close {
  position: absolute;
  top: 0.3rem; right: 0.4rem;
  border: 0; background: none; cursor: pointer;
  font-size: 1.1rem; line-height: 1; color: var(--dim);
  padding: 0.15rem 0.35rem; border-radius: 5px;
}
.hud .close:hover { background: #f2f0ec; color: var(--ink); }

/* ---------------- launcher (empty state) ---------------- */
.launcher {
  top: 50%; left: 50%; transform: translate(-50%, -50%);
  width: min(520px, calc(100% - 3rem));
  padding: 1.2rem 1.4rem 1rem;
}
.launcher h2 { margin: 0 0 0.4rem; font-size: 1.05rem; font-weight: 620; }
.roots.compact { margin: 0.7rem 0 0.5rem; }
.roots.compact .root {
  width: 100%; text-align: left; cursor: pointer; font: inherit;
  display: flex; justify-content: space-between; align-items: baseline;
}
.tiny { font-size: 0.78rem; }

/* ---------------- controls ---------------- */
.controls {
  top: 0.8rem; right: 0.8rem;
  display: flex; align-items: center; gap: 0.45rem;
  padding: 0.35rem 0.45rem;
}
.seg { display: flex; border: 1px solid var(--line); border-radius: 7px; overflow: hidden; }
.seg button {
  border: 0; background: var(--panel); color: var(--dim);
  font: inherit; font-size: 0.84rem; padding: 0.25rem 0.6rem; cursor: pointer;
}
.seg button + button { border-left: 1px solid var(--line); }
.seg button:hover { background: var(--accent-soft); }
.seg button.on { background: var(--accent); color: #fff; }
.ctl {
  border: 1px solid var(--line); background: var(--panel); color: var(--ink);
  font: inherit; font-size: 0.84rem; padding: 0.25rem 0.6rem;
  border-radius: 7px; cursor: pointer;
}
.ctl:hover { background: var(--accent-soft); }

/* ---------------- status + legend ---------------- */
.status {
  bottom: 0.8rem; right: 0.8rem; max-width: 46ch;
  padding: 0.4rem 0.75rem; font-size: 0.8rem; color: var(--dim);
}
.status.warn { color: var(--warn); border-color: #e8c9c0; }
.legend {
  bottom: 0.8rem; left: 0.8rem;
  padding: 0.4rem 0.7rem; font-size: 0.76rem; color: var(--dim);
  display: flex; flex-wrap: wrap; gap: 0.15rem 0.85rem; max-width: 46ch;
}
.lg { display: inline-flex; align-items: center; gap: 0.35rem; white-space: nowrap; }
.ln { width: 20px; height: 0; border-top: 2px solid var(--c); display: inline-block; }
.ln.dashed { border-top-style: dashed; }
.ln.dotted { border-top-style: dotted; }

/* ---------------- live search ---------------- */
.searchbox.live { position: relative; max-width: 560px; }
.searchbox.live input[type="search"] { width: 100%; }
.results {
  position: absolute; top: calc(100% + 4px); left: 0; right: 0;
  max-height: 62vh; overflow-y: auto;
  background: var(--panel); border: 1px solid var(--line); border-radius: 9px;
  box-shadow: 0 6px 22px rgba(31, 29, 26, 0.12); z-index: 40; padding: 0.3rem;
}
.results .sec {
  font-size: 0.72rem; color: var(--dim); text-transform: none;
  padding: 0.35rem 0.55rem 0.2rem; letter-spacing: 0.02em;
}
.results .row {
  display: flex; align-items: baseline; gap: 0.5rem; width: 100%;
  border: 0; background: none; font: inherit; text-align: left;
  padding: 0.32rem 0.55rem; border-radius: 6px; cursor: pointer;
}
.results .row:hover, .results .row.sel { background: var(--accent-soft); }
.results .row .lbl { font-weight: 600; }
.results .row .src { color: var(--dim); font-size: 0.78rem; margin-left: auto; }
.results .none { padding: 0.5rem 0.6rem; color: var(--dim); font-size: 0.86rem; }
.tag {
  font-size: 0.68rem; padding: 0.02rem 0.32rem; border-radius: 4px;
  border: 1px solid var(--line); color: var(--dim); white-space: nowrap;
}
.tag.hant { border-color: #d8c9a8; color: var(--cand); }

/* ---------------- drawer ---------------- */
.drawer {
  top: 3.6rem; right: 0.8rem; bottom: 3rem; width: 330px;
  padding: 0.9rem 1rem; overflow-y: auto;
}
.drawer h2 { font-size: 1rem; margin: 0.1rem 0 0.15rem; font-weight: 620; line-height: 1.35; }
.drawer .meta { color: var(--dim); font-size: 0.78rem; margin-bottom: 0.6rem; }
.drawer .grp { border-top: 1px solid var(--line); padding-top: 0.55rem; margin-top: 0.55rem; }
.drawer .grp h3 {
  font-size: 0.8rem; margin: 0 0 0.4rem; color: var(--dim); font-weight: 600;
  display: flex; align-items: center; gap: 0.4rem;
}
.drawer .acts { display: flex; flex-wrap: wrap; gap: 0.35rem; }
.ex {
  border: 1px solid var(--line); background: #fff; color: var(--ink);
  font: inherit; font-size: 0.82rem; padding: 0.25rem 0.6rem;
  border-radius: 15px; cursor: pointer;
}
.ex:hover { background: var(--accent-soft); border-color: var(--accent); }
.ex[disabled] { opacity: 0.45; cursor: default; background: #f7f6f3; }
.ex.done { border-style: dashed; color: var(--dim); }
.drawer .rel { font-size: 0.84rem; padding: 0.28rem 0; border-bottom: 1px dotted var(--line); }
.drawer .rel:last-child { border-bottom: 0; }
.drawer .rel .st { font-size: 0.72rem; color: var(--dim); }
.drawer .rel .st.confirmed { color: var(--ok); }
.drawer .rel .st.candidate { color: var(--cand); }
.drawer .raw { font-size: 0.76rem; color: var(--dim); word-break: break-all; }
.drawer a.open { font-size: 0.8rem; }
