concept-atlas-dense-explain 0.4.0 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/skill/SKILL.md +37 -27
- package/template/guides/atlas-guide.mdx +1 -1
- package/template/guides/scroll-guide.mdx +1 -4
- package/template/src/components/MDXComponents.jsx +12 -24
- package/template/src/model/validate-content.js +35 -0
- package/template/src/styles/concept-explain.css +50 -71
package/package.json
CHANGED
package/skill/SKILL.md
CHANGED
|
@@ -1,37 +1,39 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: concept-atlas-dense-explain
|
|
3
|
-
description:
|
|
3
|
+
description: Turn a topic or an existing document into an interactive Concept Atlas explainer. Use the concept-atlas-dense-explain npm CLI to generate AI-editable MDX, validate its structure, and compile standalone HTML with concept nodes, relation graphs, math (KaTeX), charts, figures, citations, and reading aids. Use when a technical explanation, concept map, layered knowledge page, dense explainer, or interactive teaching page is requested.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Concept Atlas Dense Explain
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
Drive everything through the `concept-atlas-dense-explain` npm CLI. This skill is intentionally lightweight: do not copy implementation files from the skill directory and do not recreate the React/Vite app. If a command name is unclear, run `npx concept-atlas-dense-explain help`.
|
|
9
|
+
|
|
10
|
+
If the user only wants the prompt/methodology and not files, still choose a shell and emit valid MDX; the CLI is needed only to compile.
|
|
9
11
|
|
|
10
12
|
## Workflow
|
|
11
13
|
|
|
12
|
-
1. Choose
|
|
13
|
-
2. **Learn the
|
|
14
|
+
1. Choose one page shell: `atlas` (concept graph with node navigation) or `scroll` (continuous document). Recommend `atlas` when the reader needs to drill into concepts or see relations; recommend `scroll` for linear argument, tutorials, and reports. The component library is shared. If the user already has an `.mdx` file, detect its shell and work with it.
|
|
15
|
+
2. **Learn the components from the canonical guide before authoring.** Generate the guide for the chosen shell into a temporary path and read it:
|
|
14
16
|
```bash
|
|
15
|
-
npx concept-atlas-dense-explain guide --mode atlas -o concept-atlas-atlas-guide.mdx
|
|
16
|
-
npx concept-atlas-dense-explain guide --mode scroll -o concept-atlas-scroll-guide.mdx
|
|
17
|
+
npx concept-atlas-dense-explain guide --mode atlas -o ./concept-atlas-atlas-guide.mdx
|
|
18
|
+
npx concept-atlas-dense-explain guide --mode scroll -o ./concept-atlas-scroll-guide.mdx
|
|
17
19
|
```
|
|
18
|
-
|
|
19
|
-
3.
|
|
20
|
-
4. Write semantic MDX
|
|
21
|
-
5. Validate
|
|
20
|
+
It is a real, compilable MDX file demonstrating every component and its exact props. Search it for a component name to copy the correct prop shape instead of guessing. Delete it when done.
|
|
21
|
+
3. Start from a skeleton when useful: `npx concept-atlas-dense-explain create <file>.mdx --mode atlas|scroll`. `create` and `guide` refuse to overwrite an existing file unless `--force` is passed.
|
|
22
|
+
4. Write the semantic MDX into the user's `.mdx` file (see Authoring rules).
|
|
23
|
+
5. Validate before rendering:
|
|
22
24
|
```bash
|
|
23
25
|
npx concept-atlas-dense-explain validate <file>.mdx --mode atlas|scroll
|
|
24
|
-
npx concept-atlas-dense-explain validate <file>.mdx --json
|
|
26
|
+
npx concept-atlas-dense-explain validate <file>.mdx --json
|
|
25
27
|
```
|
|
26
|
-
|
|
27
|
-
6. Compile
|
|
28
|
-
7. Report the
|
|
28
|
+
Every diagnostic is `CODE line:column message`. Fix all `error`s and re-run; warnings are quality signals you should also address when cheap.
|
|
29
|
+
6. Compile: `npx concept-atlas-dense-explain <file>.mdx --mode atlas|scroll [-o out.html]`. Output is a standalone HTML beside the MDX unless `-o` is given. Validation errors abort the build; use `--no-validate` only to force a knowingly broken build.
|
|
30
|
+
7. Report the shell, output path, validation result (errors/warnings), and limitations. Do not claim interactions you did not verify.
|
|
29
31
|
|
|
30
32
|
## Carriers
|
|
31
33
|
|
|
32
|
-
- `atlas`: `ExplainPage` → `ConceptGraph` → `ConceptNode
|
|
33
|
-
- `scroll`: `ScrollDocument` → `ScrollHeader` + `ScrollSection` (+ `ScrollProse`, `ScrollGrid`). Shared components
|
|
34
|
-
- Never
|
|
34
|
+
- `atlas`: `ExplainPage` → `ConceptGraph` → `ConceptNode`, plus `Children`/`ConceptRef` and cross-branch `Relation`s. Exactly one `L0` root, several `L1` branches, depth to `L3`/`L4`. Shared components live inside nodes.
|
|
35
|
+
- `scroll`: `ScrollDocument` → `ScrollHeader` + `ScrollSection` (+ `ScrollProse`, `ScrollGrid`). Shared components live inside sections. The shell auto-builds a a table of contents and reading progress from section titles — do not hand-build navigation.
|
|
36
|
+
- Never make one MDX file both shells. When switching shells, convert only the outer structure.
|
|
35
37
|
|
|
36
38
|
## Component families
|
|
37
39
|
|
|
@@ -43,18 +45,26 @@ Use the `concept-atlas-dense-explain` npm CLI. This skill is intentionally light
|
|
|
43
45
|
|
|
44
46
|
## Authoring rules
|
|
45
47
|
|
|
46
|
-
- Keep MDX semantic
|
|
47
|
-
- Give important
|
|
48
|
-
- Array props
|
|
49
|
-
-
|
|
50
|
-
- **
|
|
51
|
-
- **
|
|
52
|
-
- **
|
|
53
|
-
-
|
|
54
|
-
-
|
|
48
|
+
- Keep MDX semantic. Never write CSS, coordinates, SVG, or replacement application code; never invent component names or props.
|
|
49
|
+
- Give each important node a claim-like title, a one-sentence `summary`, and real substance (`Definition`, `Mechanism`, `Example`, `Boundary`, `Evidence`, a model, a chart, or math). Do not restate the same text across `Overview`, `Definition`, and `Insight`.
|
|
50
|
+
- Array props are arrays of objects: `Flow steps={[{title, description}]}`, `Timeline events={[{label, content}]}`, `MatrixModel cells={[{title, description, tone}]}`, `DecisionMatrix headers={[...]} rows={[[...]]}`, `Chart data={[{label, value}]}`, `References items={[{id, authors, year, title, url, source}]}`. The validator warns (`PROP_EXPECTS_ARRAY`) when an array prop gets a string or non-array.
|
|
51
|
+
- `Relation type` must be one of `prerequisite`, `causes`, `produces`, `uses`, `implements`, `contrasts`, `depends-on`, `exception-of`, `precedes`, and each `Relation` needs a `label`. Parent/child hierarchy is implicit (via `parent` and `Children`/`ConceptRef`) — do not express it with a `Relation`.
|
|
52
|
+
- **Math**: MDX parses `{ ... }` in children as expressions, so pass LaTeX with braces or backslashes through `formula`: `<Math formula="r_{\text{ann}} = (1 + r)^{12} - 1" />`, `<MathBlock formula="I(x) = -\log_2 p(x)" variables={[{symbol, description}]} />`. Brace-free children such as `<Math>\log_2 N</Math>` are fine. The validator warns (`MATH_CHILDREN_BRACES`).
|
|
53
|
+
- **Chart**: `type` is `bar` | `line` | `pie`; use `data` for bar/pie and `labels` + `series={[{name, values}]}` for line. Charts follow theme colors.
|
|
54
|
+
- **Figure**: a relative `src` (`./assets/diagram.png`) is inlined as base64 at build time so the HTML stays standalone; `http(s)` URLs stay links. Always set `alt`; add `label` and `caption` for a numbered caption. A missing relative file produces an `ASSET_MISSING` warning and a placeholder.
|
|
55
|
+
- **Cite/References**: `<Cite id="..." />` renders `[n]` from the matching item's position in `<References items={...} />`. In `scroll`, `References` can sit anywhere. In `atlas`, keep the cites and the `References` block in the same node, because node content only renders when that node is open.
|
|
56
|
+
- Continuous reading is configured on the shell, not with manual CSS: `spacing="compact|comfortable|airy"` for rhythm, `fontSize="compact|normal|large|xlarge"` (or numeric `scale`/`lineHeight`) for text size.
|
|
57
|
+
- Be brief about cost: KaTeX fonts and Mermaid roughly double the single-file output (~5 MB), which is normal for an offline explainer. Mention it if the user cares about file size.
|
|
55
58
|
|
|
56
59
|
## Validation diagnostics
|
|
57
60
|
|
|
58
|
-
`validate` and the build
|
|
61
|
+
`validate` and the build print `CODE line:column message`. Fix these `error`s before building: `UNKNOWN_COMPONENT`, `CARRIER_MISSING`, `CARRIER_CONFLICT`, `CARRIER_MODE_MISMATCH`, `NODE_MISSING_ID`, `DUPLICATE_NODE_ID`, `NODE_MISSING_TITLE`, `MISSING_PARENT`, `GRAPH_ROOT_UNRESOLVED`, `REF_UNRESOLVED`, `RELATION_FROM_UNRESOLVED`, `RELATION_TO_UNRESOLVED`. Warnings worth fixing: `NODE_MISSING_SUMMARY`, `NODE_NO_CORE_CONTENT`, `UNKNOWN_LEVEL`, `UNKNOWN_RELATION_TYPE`, `RELATION_MISSING_LABEL`, `PROP_EXPECTS_ARRAY`, `MATH_CHILDREN_BRACES`, `GRAPH_MISSING_ROOT`, `ASSET_MISSING`.
|
|
62
|
+
|
|
63
|
+
## Before you report
|
|
64
|
+
|
|
65
|
+
- All `error` diagnostics resolved (or `--no-validate` explicitly justified).
|
|
66
|
+
- All `ConceptRef`, `Relation` endpoints, and `ConceptGraph root` point at existing node ids.
|
|
67
|
+
- Every node has `title` + `summary`; every `Relation` has a `label`.
|
|
68
|
+
- The HTML file exists at the reported path.
|
|
59
69
|
|
|
60
70
|
If the CLI or npm registry is unavailable, report the blocker instead of copying the implementation into the skill.
|
|
@@ -125,7 +125,7 @@
|
|
|
125
125
|
</ConceptNode>
|
|
126
126
|
|
|
127
127
|
<ConceptNode id="chart-demo" title="Chart:用同一数据切换三种图形" level="L2" parent="extension-family" summary="bar、line、pie 三种类型共享数据形状,按比较、趋势或占比选择。">
|
|
128
|
-
<Definition>Chart 接收 data
|
|
128
|
+
<Definition>Chart 接收 data(`{label, value}` 数组)或 series + labels,在 SVG 中绘制并跟随主题色。</Definition>
|
|
129
129
|
<Chart title="各阶段耗时" type="bar" unit="小时" data={[{label:'收集',value:6},{label:'分析',value:14},{label:'验证',value:9},{label:'落地',value:4}]} />
|
|
130
130
|
<Chart title="留存趋势" type="line" labels={['第1周','第2周','第3周','第4周']} series={[{name:'留存率',values:[100,72,58,49]}]} />
|
|
131
131
|
</ConceptNode>
|
|
@@ -1,9 +1,6 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: scroll-guide
|
|
3
|
-
---
|
|
4
|
-
|
|
5
1
|
<ScrollDocument spacing="comfortable" fontSize="normal">
|
|
6
2
|
<ScrollHeader title="Concept Atlas 连续阅读指南" label="连续阅读 · 组件参考">同一套语义组件可以脱离图谱和节点面板,按传统文档流连续阅读。本页同时是一份可运行的组件参考:每一节演示一个组件家族,正文保持高信息密度,模型在需要比较、推导或收敛时占满可用宽度。</ScrollHeader>
|
|
3
|
+
|
|
7
4
|
<ScrollSection title="先识别推理结构">
|
|
8
5
|
<ScrollProse>模型不是内容的装饰。它们分别处理并列要素、二维定位、变量关系、论证层级和筛选过程。先确认关系形状,读者才能用最短路径验证结论。</ScrollProse>
|
|
9
6
|
<LearningObjectives items={['按关系形状选择组件', '把结论与证据分开表达', '让每个区块承担不同的认知任务']} />
|
|
@@ -563,32 +563,16 @@ export function ScrollDocument({ spacing = 'comfortable', fontSize = 'normal', s
|
|
|
563
563
|
'--reading-line-height': String(lineHeight ?? preset.lineHeight),
|
|
564
564
|
};
|
|
565
565
|
|
|
566
|
-
const
|
|
567
|
-
const previous = activeIndex > 0 ? items[activeIndex - 1] : null;
|
|
568
|
-
const next = activeIndex >= 0 && activeIndex < items.length - 1 ? items[activeIndex + 1] : null;
|
|
566
|
+
const showToc = toc && items.length >= 2;
|
|
569
567
|
|
|
570
568
|
return (
|
|
571
|
-
<article className={`continuous-document continuous-spacing-${safeSpacing}`} data-font-size={fontSize} style={style}>
|
|
569
|
+
<article className={`continuous-document continuous-spacing-${safeSpacing}${showToc ? ' has-toc' : ''}`} data-font-size={fontSize} style={style}>
|
|
572
570
|
{progress && <ReadingProgress />}
|
|
573
571
|
<ScrollOutlineContext.Provider value={outline}>
|
|
574
|
-
{
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
{previous ? (
|
|
579
|
-
<a className="pager-prev" href={`#${previous.id}`}>
|
|
580
|
-
<span>上一节</span>
|
|
581
|
-
<strong>{previous.title}</strong>
|
|
582
|
-
</a>
|
|
583
|
-
) : <span className="pager-spacer" />}
|
|
584
|
-
{next ? (
|
|
585
|
-
<a className="pager-next" href={`#${next.id}`}>
|
|
586
|
-
<span>下一节</span>
|
|
587
|
-
<strong>{next.title}</strong>
|
|
588
|
-
</a>
|
|
589
|
-
) : <span className="pager-spacer" />}
|
|
590
|
-
</nav>
|
|
591
|
-
)}
|
|
572
|
+
{showToc && <ScrollToc items={items} activeId={activeId} />}
|
|
573
|
+
<div className="continuous-content">
|
|
574
|
+
{children}
|
|
575
|
+
</div>
|
|
592
576
|
</ScrollOutlineContext.Provider>
|
|
593
577
|
</article>
|
|
594
578
|
);
|
|
@@ -768,12 +752,16 @@ NoteGrid.displayName = 'NoteGrid';
|
|
|
768
752
|
|
|
769
753
|
// Math ----------------------------------------------------------------------
|
|
770
754
|
|
|
771
|
-
|
|
755
|
+
// Named MathInline internally: exporting a function literally called `Math`
|
|
756
|
+
// would shadow the global Math object for the whole module and break Math.max
|
|
757
|
+
// and friends. It is exported as `Math` so MDX can use <Math>.
|
|
758
|
+
function MathInline({ formula, children }) {
|
|
772
759
|
const tex = (formula || childrenToText(children)).trim();
|
|
773
760
|
if (!tex) return null;
|
|
774
761
|
return <span className="semantic-math" dangerouslySetInnerHTML={{ __html: renderTex(tex, false) }} />;
|
|
775
762
|
}
|
|
776
|
-
|
|
763
|
+
MathInline.displayName = 'Math';
|
|
764
|
+
export { MathInline as Math };
|
|
777
765
|
|
|
778
766
|
export function MathBlock({ title = '公式', formula, variables = [], children }) {
|
|
779
767
|
const tex = (formula || childrenToText(children)).trim();
|
|
@@ -286,6 +286,23 @@ function tokenize(source) {
|
|
|
286
286
|
return tags;
|
|
287
287
|
}
|
|
288
288
|
|
|
289
|
+
/** Blank out tag ranges, preserving offsets and newlines, so only text remains. */
|
|
290
|
+
function textRegions(source, tags) {
|
|
291
|
+
const ranges = tags
|
|
292
|
+
.map(tag => [tag.start, tag.end])
|
|
293
|
+
.sort((a, b) => a[0] - b[0]);
|
|
294
|
+
const pieces = [];
|
|
295
|
+
let cursor = 0;
|
|
296
|
+
for (const [start, end] of ranges) {
|
|
297
|
+
if (start < cursor) continue;
|
|
298
|
+
pieces.push(source.slice(cursor, start));
|
|
299
|
+
pieces.push(source.slice(start, end).replace(/[^\n]/g, ' '));
|
|
300
|
+
cursor = end;
|
|
301
|
+
}
|
|
302
|
+
pieces.push(source.slice(cursor));
|
|
303
|
+
return pieces.join('');
|
|
304
|
+
}
|
|
305
|
+
|
|
289
306
|
function attrsToMap(attrs) {
|
|
290
307
|
const map = {};
|
|
291
308
|
for (const attr of attrs) map[attr.name] = attr;
|
|
@@ -421,6 +438,24 @@ export function validateMdxSource(source, options = {}) {
|
|
|
421
438
|
}
|
|
422
439
|
}
|
|
423
440
|
|
|
441
|
+
// MDX evaluates `{ ... }` in prose as a JavaScript expression. Authors often
|
|
442
|
+
// write data shapes like `{label, value}` in a sentence, which crashes the
|
|
443
|
+
// page with "label is not defined".
|
|
444
|
+
const text = textRegions(masked, tags);
|
|
445
|
+
for (const match of text.matchAll(/\{([^{}]*)\}/g)) {
|
|
446
|
+
const expression = match[1].trim();
|
|
447
|
+
if (!expression || expression.startsWith('/*')) continue;
|
|
448
|
+
if (/^[A-Za-z_$][\w$]*\s*(,[\s\S]*)?$/.test(expression)) {
|
|
449
|
+
add('warning', 'PROSE_EXPRESSION', `正文中的 {${expression}} 会被 MDX 当作表达式并导致运行时报错;请改成行内代码 \`{${expression}}\``, match.index, expression);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// MDX has no frontmatter support by default: a leading `---` block renders as
|
|
454
|
+
// a stray rule and text at the top of the page.
|
|
455
|
+
if (/^---\r?\n[\s\S]*?\r?\n---(\r?\n|$)/.test(source)) {
|
|
456
|
+
add('warning', 'FRONTMATTER_UNSUPPORTED', '文件以 --- 开头,但 MDX 不解析 frontmatter,它会被渲染成正文;请删除', 0, null);
|
|
457
|
+
}
|
|
458
|
+
|
|
424
459
|
const carrier = usedComponents.has('ScrollDocument') ? 'scroll'
|
|
425
460
|
: (usedComponents.has('ExplainPage') || usedComponents.has('ConceptGraph')) ? 'atlas'
|
|
426
461
|
: null;
|
|
@@ -3176,11 +3176,11 @@ html[data-carrier='scroll'] #root {
|
|
|
3176
3176
|
justify-content: space-between;
|
|
3177
3177
|
}
|
|
3178
3178
|
|
|
3179
|
-
.continuous-
|
|
3180
|
-
.continuous-
|
|
3181
|
-
.continuous-
|
|
3182
|
-
.continuous-
|
|
3183
|
-
.continuous-
|
|
3179
|
+
.continuous-content > .semantic-framework-model,
|
|
3180
|
+
.continuous-content > .semantic-model-matrix,
|
|
3181
|
+
.continuous-content > .semantic-model-formula,
|
|
3182
|
+
.continuous-content > .semantic-model-pyramid,
|
|
3183
|
+
.continuous-content > .semantic-model-funnel,
|
|
3184
3184
|
.continuous-section > .semantic-framework-model,
|
|
3185
3185
|
.continuous-section > .semantic-model-matrix,
|
|
3186
3186
|
.continuous-section > .semantic-model-formula,
|
|
@@ -3339,29 +3339,44 @@ html[data-carrier='scroll'] #root {
|
|
|
3339
3339
|
transition: transform 80ms linear;
|
|
3340
3340
|
}
|
|
3341
3341
|
|
|
3342
|
-
/* ---- Table of contents ---- */
|
|
3342
|
+
/* ---- Table of contents (sticky sidebar) ---- */
|
|
3343
|
+
|
|
3344
|
+
.continuous-content {
|
|
3345
|
+
min-width: 0;
|
|
3346
|
+
}
|
|
3343
3347
|
|
|
3344
3348
|
.scroll-toc {
|
|
3345
3349
|
display: none;
|
|
3346
3350
|
}
|
|
3347
3351
|
|
|
3348
|
-
@media (min-width:
|
|
3349
|
-
.
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3352
|
+
@media (min-width: 1080px) {
|
|
3353
|
+
.continuous-document.has-toc {
|
|
3354
|
+
display: grid;
|
|
3355
|
+
grid-template-columns: 208px minmax(0, 1fr);
|
|
3356
|
+
column-gap: 44px;
|
|
3357
|
+
align-items: start;
|
|
3358
|
+
}
|
|
3359
|
+
|
|
3360
|
+
.continuous-document.has-toc > .scroll-toc {
|
|
3361
|
+
position: sticky;
|
|
3362
|
+
top: 24px;
|
|
3354
3363
|
display: block;
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3364
|
+
grid-column: 1;
|
|
3365
|
+
align-self: start;
|
|
3366
|
+
max-height: calc(100vh - 48px);
|
|
3367
|
+
overflow-y: auto;
|
|
3368
|
+
padding-right: 6px;
|
|
3369
|
+
}
|
|
3370
|
+
|
|
3371
|
+
.continuous-document.has-toc > .continuous-content {
|
|
3372
|
+
grid-column: 2;
|
|
3360
3373
|
}
|
|
3361
3374
|
}
|
|
3362
3375
|
|
|
3363
3376
|
.scroll-toc-title {
|
|
3364
|
-
margin
|
|
3377
|
+
margin: 0 0 10px;
|
|
3378
|
+
padding-bottom: 8px;
|
|
3379
|
+
border-bottom: 1px solid var(--border-subtle);
|
|
3365
3380
|
color: var(--text-dim);
|
|
3366
3381
|
font-size: 11px;
|
|
3367
3382
|
font-weight: 700;
|
|
@@ -3373,90 +3388,54 @@ html[data-carrier='scroll'] #root {
|
|
|
3373
3388
|
margin: 0;
|
|
3374
3389
|
padding: 0;
|
|
3375
3390
|
list-style: none;
|
|
3391
|
+
border-left: 1px solid var(--border-subtle);
|
|
3376
3392
|
counter-reset: toc;
|
|
3377
3393
|
}
|
|
3378
3394
|
|
|
3379
3395
|
.scroll-toc-list li {
|
|
3380
|
-
margin: 0 0 2px;
|
|
3381
3396
|
counter-increment: toc;
|
|
3382
3397
|
}
|
|
3383
3398
|
|
|
3384
3399
|
.scroll-toc-list a {
|
|
3385
|
-
display:
|
|
3386
|
-
|
|
3400
|
+
display: grid;
|
|
3401
|
+
grid-template-columns: 24px minmax(0, 1fr);
|
|
3402
|
+
gap: 6px;
|
|
3403
|
+
align-items: baseline;
|
|
3404
|
+
margin-left: -1px;
|
|
3405
|
+
padding: 6px 8px 6px 0;
|
|
3387
3406
|
border-left: 2px solid transparent;
|
|
3388
|
-
border-radius: var(--radius-1);
|
|
3389
3407
|
color: var(--text-muted);
|
|
3390
3408
|
font-size: 12.5px;
|
|
3391
|
-
line-height: 1.
|
|
3409
|
+
line-height: 1.45;
|
|
3392
3410
|
text-decoration: none;
|
|
3393
|
-
|
|
3394
|
-
transition: color 0.15s ease, border-color 0.15s ease, background 0.15s ease;
|
|
3411
|
+
transition: color 0.15s ease, border-color 0.15s ease;
|
|
3395
3412
|
}
|
|
3396
3413
|
|
|
3397
3414
|
.scroll-toc-list a::before {
|
|
3398
3415
|
content: counter(toc, decimal-leading-zero);
|
|
3399
|
-
position: absolute;
|
|
3400
|
-
left: 4px;
|
|
3401
3416
|
color: var(--text-dim);
|
|
3402
3417
|
font-size: 10px;
|
|
3403
3418
|
font-variant-numeric: tabular-nums;
|
|
3419
|
+
text-align: center;
|
|
3420
|
+
transition: color 0.15s ease;
|
|
3404
3421
|
}
|
|
3405
3422
|
|
|
3406
3423
|
.scroll-toc-list a:hover {
|
|
3407
3424
|
color: var(--text-primary);
|
|
3408
|
-
background: var(--bg-highlight);
|
|
3409
3425
|
}
|
|
3410
3426
|
|
|
3411
|
-
.scroll-toc-list
|
|
3412
|
-
|
|
3413
|
-
color: var(--text-primary);
|
|
3414
|
-
font-weight: 600;
|
|
3415
|
-
}
|
|
3416
|
-
|
|
3417
|
-
/* ---- Section pager ---- */
|
|
3418
|
-
|
|
3419
|
-
.scroll-pager {
|
|
3420
|
-
display: grid;
|
|
3421
|
-
grid-template-columns: 1fr 1fr;
|
|
3422
|
-
gap: 12px;
|
|
3423
|
-
margin-top: 8px;
|
|
3424
|
-
padding-top: 16px;
|
|
3425
|
-
border-top: 1px solid var(--border-subtle);
|
|
3426
|
-
}
|
|
3427
|
-
|
|
3428
|
-
.scroll-pager a {
|
|
3429
|
-
display: flex;
|
|
3430
|
-
flex-direction: column;
|
|
3431
|
-
gap: 4px;
|
|
3432
|
-
padding: 10px 12px;
|
|
3433
|
-
border: 1px solid var(--border-medium);
|
|
3434
|
-
border-radius: var(--radius-2);
|
|
3435
|
-
background: var(--bg-surface);
|
|
3436
|
-
text-decoration: none;
|
|
3437
|
-
transition: border-color 0.15s ease, background 0.15s ease;
|
|
3438
|
-
}
|
|
3439
|
-
|
|
3440
|
-
.scroll-pager a:hover {
|
|
3441
|
-
border-color: var(--border-strong);
|
|
3442
|
-
background: var(--bg-card-hover);
|
|
3443
|
-
}
|
|
3444
|
-
|
|
3445
|
-
.scroll-pager a span {
|
|
3446
|
-
color: var(--text-dim);
|
|
3447
|
-
font-size: 11px;
|
|
3448
|
-
font-weight: 600;
|
|
3427
|
+
.scroll-toc-list a:hover::before {
|
|
3428
|
+
color: var(--text-secondary);
|
|
3449
3429
|
}
|
|
3450
3430
|
|
|
3451
|
-
.scroll-
|
|
3431
|
+
.scroll-toc-list li.active a {
|
|
3432
|
+
border-left-color: var(--primary);
|
|
3452
3433
|
color: var(--text-primary);
|
|
3453
|
-
font-size: 13px;
|
|
3454
3434
|
font-weight: 600;
|
|
3455
|
-
line-height: 1.4;
|
|
3456
3435
|
}
|
|
3457
3436
|
|
|
3458
|
-
.scroll-
|
|
3459
|
-
|
|
3437
|
+
.scroll-toc-list li.active a::before {
|
|
3438
|
+
color: var(--primary-light);
|
|
3460
3439
|
}
|
|
3461
3440
|
|
|
3462
3441
|
/* ===========================================================================
|