@open330/kiwimu 0.4.0 → 0.7.1
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/bin/kiwimu +1 -1
- package/package.json +4 -1
- package/personas/namuwiki.json +6 -0
- package/src/build/renderer.ts +49 -2
- package/src/build/static/search.js +33 -2
- package/src/build/static/style.css +181 -44
- package/src/build/templates.ts +297 -167
- package/src/config.ts +35 -29
- package/src/demo/sample-data.ts +70 -0
- package/src/demo/setup.ts +31 -0
- package/src/expand/llm.ts +1 -1
- package/src/index.ts +208 -458
- package/src/ingest/docx.ts +0 -8
- package/src/ingest/legacy.ts +4 -4
- package/src/ingest/pdf.ts +1 -1
- package/src/ingest/pptx.ts +0 -1
- package/src/ingest/web.test.ts +41 -0
- package/src/ingest/web.ts +61 -62
- package/src/llm-client.ts +203 -126
- package/src/pipeline/chunker.test.ts +42 -0
- package/src/pipeline/chunker.ts +1 -48
- package/src/pipeline/llm-chunker.ts +133 -55
- package/src/server.ts +327 -0
- package/src/services/ingest.ts +100 -0
- package/src/store.test.ts +132 -0
- package/src/store.ts +102 -2
- package/src/pipeline/llm-linker.ts +0 -84
package/bin/kiwimu
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
|
-
|
|
2
|
+
import "../src/index.ts";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open330/kiwimu",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.1",
|
|
4
4
|
"description": "Turn textbooks, PDFs, and web content into your own interlinked learning wiki powered by LLM",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
"files": [
|
|
10
10
|
"bin/**/*",
|
|
11
11
|
"src/**/*",
|
|
12
|
+
"personas/**/*",
|
|
12
13
|
"assets/**/*",
|
|
13
14
|
"README.md",
|
|
14
15
|
"LICENSE"
|
|
@@ -48,6 +49,7 @@
|
|
|
48
49
|
"mammoth": "^1.12.0",
|
|
49
50
|
"marked": "^15.0.0",
|
|
50
51
|
"pdf-parse": "1.1.1",
|
|
52
|
+
"sanitize-html": "^2.17.2",
|
|
51
53
|
"smol-toml": "^1.3.1",
|
|
52
54
|
"turndown": "^7.2.0"
|
|
53
55
|
},
|
|
@@ -58,6 +60,7 @@
|
|
|
58
60
|
"devDependencies": {
|
|
59
61
|
"@types/bun": "latest",
|
|
60
62
|
"@types/pdf-parse": "^1.1.5",
|
|
63
|
+
"@types/sanitize-html": "^2.16.1",
|
|
61
64
|
"@types/turndown": "^5.0.5"
|
|
62
65
|
}
|
|
63
66
|
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "나무위키",
|
|
3
|
+
"description": "나무위키 특유의 문체와 스타일로 문서를 작성합니다",
|
|
4
|
+
"system_prompt": "당신은 나무위키 스타일의 위키 편집자입니다. 다음 특징을 반드시 지켜주세요:\n\n1. **문체**: 해요체(~입니다/~합니다)를 기본으로 하되, 가끔 반말(~이다/~한다)을 섞어 사용\n2. **유머**: 적절한 곳에 ~~취소선 드립~~, (괄호 안의 부연설명), [1] 각주 스타일의 코멘트를 삽입\n3. **강조**: 중요한 키워드는 **굵게** 처리하고, 핵심 개념은 반복 강조\n4. **서술 톤**: 백과사전적이면서도 친근한 톤. \"~라고 한다\", \"~라고 카더라\" 등의 표현 활용\n5. **구조**: 목차가 잘 정리된 체계적 구조. 소제목을 적극 활용\n6. **부가 정보**: \"여담으로~\", \"참고로~\", \"사실~\" 등의 표현으로 부가 정보 추가\n7. **링크**: 관련 개념에 적극적으로 [[위키 링크]]를 사용\n\n절대 딱딱한 교과서 문체로 쓰지 마세요. 읽는 사람이 재미있게 학습할 수 있도록 작성해주세요.",
|
|
5
|
+
"content_style": "Write in Korean 나무위키 style:\n- Use 해요체 with occasional 반말 mix\n- Add ~~strikethrough humor~~ and (parenthetical asides)\n- Bold **key terms** generously\n- Use phrases like \"~라고 한다\", \"여담으로~\", \"참고로~\"\n- Be encyclopedic yet friendly and entertaining\n- Structure with clear subsections\n- Use [[wiki links]] actively for related concepts"
|
|
6
|
+
}
|
package/src/build/renderer.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { mkdirSync, rmSync, cpSync, existsSync } from "fs";
|
|
2
2
|
import { join, dirname } from "path";
|
|
3
3
|
import { marked } from "marked";
|
|
4
|
+
import sanitizeHtml from "sanitize-html";
|
|
4
5
|
import type { KiwiConfig } from "../config";
|
|
5
6
|
import type { Store } from "../store";
|
|
6
7
|
import { buildGraphData } from "../pipeline/graph";
|
|
7
|
-
import { renderPage, renderIndex, renderGraph } from "./templates";
|
|
8
|
+
import { renderPage, renderIndex, renderGraph, renderQuizPage } from "./templates";
|
|
8
9
|
|
|
9
10
|
// Fix internal wiki links: /wiki/slug → /wiki/slug.html
|
|
10
11
|
function fixWikiLinks(html: string): string {
|
|
@@ -68,14 +69,28 @@ export async function buildSite(store: Store, config: KiwiConfig, projectRoot: s
|
|
|
68
69
|
const sourcePages = store.listSourcePages();
|
|
69
70
|
const conceptPages = store.listConceptPages();
|
|
70
71
|
const wikiName = config.project.name;
|
|
72
|
+
const backlinksMap = store.getAllBacklinksGrouped();
|
|
71
73
|
|
|
72
74
|
for (const page of pages) {
|
|
73
75
|
let htmlContent = await marked(page.content);
|
|
76
|
+
htmlContent = sanitizeHtml(htmlContent, {
|
|
77
|
+
allowedTags: sanitizeHtml.defaults.allowedTags.concat([
|
|
78
|
+
'img', 'details', 'summary', 'kbd', 'del', 's', 'sup', 'sub',
|
|
79
|
+
'span', 'div', 'section', 'figure', 'figcaption', 'mark'
|
|
80
|
+
]),
|
|
81
|
+
allowedAttributes: {
|
|
82
|
+
...sanitizeHtml.defaults.allowedAttributes,
|
|
83
|
+
'*': ['id', 'class', 'style'],
|
|
84
|
+
'img': ['src', 'alt', 'title', 'width', 'height'],
|
|
85
|
+
'a': ['href', 'title', 'target', 'rel'],
|
|
86
|
+
},
|
|
87
|
+
allowedSchemes: ['http', 'https', 'mailto'],
|
|
88
|
+
});
|
|
74
89
|
htmlContent = fixWikiLinks(htmlContent);
|
|
75
90
|
|
|
76
91
|
const { body, externalRefs } = extractExternalRefs(htmlContent);
|
|
77
92
|
const toc = generateToc(page.content);
|
|
78
|
-
const backlinks =
|
|
93
|
+
const backlinks = (backlinksMap.get(page.id) || []).map((bl) => ({
|
|
79
94
|
slug: bl.slug,
|
|
80
95
|
title: bl.title,
|
|
81
96
|
pageType: bl.page_type,
|
|
@@ -116,6 +131,38 @@ export async function buildSite(store: Store, config: KiwiConfig, projectRoot: s
|
|
|
116
131
|
})
|
|
117
132
|
);
|
|
118
133
|
|
|
134
|
+
// Quiz page
|
|
135
|
+
const quizzes = store.getAllQuizzes();
|
|
136
|
+
await Bun.write(
|
|
137
|
+
join(outputDir, "quiz.html"),
|
|
138
|
+
renderQuizPage({
|
|
139
|
+
wikiName,
|
|
140
|
+
quizzes: quizzes.map((q) => ({
|
|
141
|
+
id: q.id,
|
|
142
|
+
question: q.question,
|
|
143
|
+
answer: q.answer,
|
|
144
|
+
quiz_type: q.quiz_type,
|
|
145
|
+
page_title: q.page_title,
|
|
146
|
+
page_slug: q.page_slug,
|
|
147
|
+
})),
|
|
148
|
+
sourcePages: sourcePages.map((p) => ({ slug: p.slug, title: p.title })),
|
|
149
|
+
conceptPages: conceptPages.map((p) => ({ slug: p.slug, title: p.title })),
|
|
150
|
+
})
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
// Random page redirect
|
|
154
|
+
mkdirSync(join(wikiDir), { recursive: true });
|
|
155
|
+
await Bun.write(
|
|
156
|
+
join(wikiDir, "random.html"),
|
|
157
|
+
`<!DOCTYPE html><html><head><meta charset="UTF-8"><title>임의 문서</title></head><body><script>
|
|
158
|
+
fetch('/search-index.json').then(r=>r.json()).then(pages=>{
|
|
159
|
+
const p = pages[Math.floor(Math.random()*pages.length)];
|
|
160
|
+
if(p) location.href='/wiki/'+p.slug+'.html';
|
|
161
|
+
else location.href='/';
|
|
162
|
+
});
|
|
163
|
+
</script></body></html>`
|
|
164
|
+
);
|
|
165
|
+
|
|
119
166
|
const searchData = pages.map((p) => ({
|
|
120
167
|
slug: p.slug,
|
|
121
168
|
title: p.title,
|
|
@@ -5,6 +5,7 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|
|
5
5
|
if (!input || !dropdown) return;
|
|
6
6
|
|
|
7
7
|
let searchData = [];
|
|
8
|
+
let selectedIndex = -1;
|
|
8
9
|
try {
|
|
9
10
|
const resp = await fetch("/search-index.json");
|
|
10
11
|
searchData = await resp.json();
|
|
@@ -12,6 +13,12 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|
|
12
13
|
return;
|
|
13
14
|
}
|
|
14
15
|
|
|
16
|
+
function escapeHtml(text) {
|
|
17
|
+
const div = document.createElement('div');
|
|
18
|
+
div.textContent = text;
|
|
19
|
+
return div.innerHTML;
|
|
20
|
+
}
|
|
21
|
+
|
|
15
22
|
function fuzzyMatch(query, text) {
|
|
16
23
|
query = query.toLowerCase();
|
|
17
24
|
text = text.toLowerCase();
|
|
@@ -31,6 +38,7 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|
|
31
38
|
}
|
|
32
39
|
|
|
33
40
|
input.addEventListener("input", () => {
|
|
41
|
+
selectedIndex = -1;
|
|
34
42
|
const results = search(input.value);
|
|
35
43
|
if (results.length === 0) {
|
|
36
44
|
dropdown.classList.remove("active");
|
|
@@ -39,8 +47,8 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|
|
39
47
|
}
|
|
40
48
|
dropdown.innerHTML = results.map(r =>
|
|
41
49
|
`<a href="/wiki/${r.slug}.html">
|
|
42
|
-
<strong>${r.title}</strong>
|
|
43
|
-
<div style="font-size:12px;color:#6c757d;margin-top:2px;">${r.preview.slice(0, 80)}...</div>
|
|
50
|
+
<strong>${escapeHtml(r.title)}</strong>
|
|
51
|
+
<div style="font-size:12px;color:#6c757d;margin-top:2px;">${escapeHtml(r.preview.slice(0, 80))}...</div>
|
|
44
52
|
</a>`
|
|
45
53
|
).join("");
|
|
46
54
|
dropdown.classList.add("active");
|
|
@@ -61,6 +69,29 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|
|
61
69
|
if (e.key === "Escape") {
|
|
62
70
|
dropdown.classList.remove("active");
|
|
63
71
|
input.blur();
|
|
72
|
+
} else if (e.key === "ArrowDown") {
|
|
73
|
+
e.preventDefault();
|
|
74
|
+
const items = dropdown.querySelectorAll("a");
|
|
75
|
+
selectedIndex = Math.min(selectedIndex + 1, items.length - 1);
|
|
76
|
+
items.forEach((a, i) => a.classList.toggle("selected", i === selectedIndex));
|
|
77
|
+
items[selectedIndex]?.scrollIntoView({ block: "nearest" });
|
|
78
|
+
} else if (e.key === "ArrowUp") {
|
|
79
|
+
e.preventDefault();
|
|
80
|
+
const items = dropdown.querySelectorAll("a");
|
|
81
|
+
selectedIndex = Math.max(selectedIndex - 1, 0);
|
|
82
|
+
items.forEach((a, i) => a.classList.toggle("selected", i === selectedIndex));
|
|
83
|
+
} else if (e.key === "Enter" && selectedIndex >= 0) {
|
|
84
|
+
e.preventDefault();
|
|
85
|
+
dropdown.querySelectorAll("a")[selectedIndex]?.click();
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// Global "/" shortcut to focus search
|
|
90
|
+
document.addEventListener('keydown', (e) => {
|
|
91
|
+
if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return;
|
|
92
|
+
if (e.key === '/') {
|
|
93
|
+
e.preventDefault();
|
|
94
|
+
input.focus();
|
|
64
95
|
}
|
|
65
96
|
});
|
|
66
97
|
});
|
|
@@ -6,31 +6,41 @@
|
|
|
6
6
|
--bg-hover: #eaeaea;
|
|
7
7
|
--text: #1a1a1a;
|
|
8
8
|
--text-muted: #6b6b6b;
|
|
9
|
-
--link: #
|
|
10
|
-
--link-hover: #
|
|
9
|
+
--link: #0275d8; /* NamuWiki blue link */
|
|
10
|
+
--link-hover: #0050a0; /* Darker blue for hover */
|
|
11
11
|
--border: #dcdcdc;
|
|
12
|
-
--accent: #00a495;
|
|
12
|
+
--accent: #00a495; /* NamuWiki green */
|
|
13
13
|
--accent-dark: #008c7e;
|
|
14
|
-
--accent-light: #e0f5f3;
|
|
14
|
+
--accent-light: #e0f5f3; /* Light green for backgrounds */
|
|
15
15
|
--namu-green: #00a495;
|
|
16
16
|
--namu-green-dark: #007a6e;
|
|
17
17
|
--namu-heading-bg: #00a495;
|
|
18
18
|
--sidebar-width: 250px;
|
|
19
19
|
--topbar-height: 44px;
|
|
20
|
-
--radius
|
|
20
|
+
/* --radius variable removed as all corners are sharp */
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
* {
|
|
24
24
|
margin: 0;
|
|
25
25
|
padding: 0;
|
|
26
26
|
box-sizing: border-box;
|
|
27
|
+
box-shadow: none; /* Enforce no shadows */
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/* Targeted border-radius reset for kiwimu elements only */
|
|
31
|
+
.topbar, .sidebar, .content, .page-card, .toc-box,
|
|
32
|
+
.search-dropdown, .backlinks li a, .stat-card, .quick-link,
|
|
33
|
+
.file-drop, .add-form input, .add-form button,
|
|
34
|
+
.config-card, .badge, .page-type-badge, .page-body table,
|
|
35
|
+
.page-body blockquote, .page-body pre {
|
|
36
|
+
border-radius: 0;
|
|
27
37
|
}
|
|
28
38
|
|
|
29
39
|
body {
|
|
30
40
|
font-family: "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Malgun Gothic", sans-serif;
|
|
31
41
|
color: var(--text);
|
|
32
42
|
background: var(--bg);
|
|
33
|
-
line-height: 1.
|
|
43
|
+
line-height: 1.65; /* Updated line-height */
|
|
34
44
|
font-size: 15px;
|
|
35
45
|
}
|
|
36
46
|
|
|
@@ -57,7 +67,7 @@ a:hover {
|
|
|
57
67
|
padding: 0 16px;
|
|
58
68
|
z-index: 100;
|
|
59
69
|
gap: 12px;
|
|
60
|
-
box-shadow:
|
|
70
|
+
/* box-shadow: none; -> Handled by global reset */
|
|
61
71
|
}
|
|
62
72
|
|
|
63
73
|
.topbar-brand {
|
|
@@ -92,7 +102,7 @@ a:hover {
|
|
|
92
102
|
width: 100%;
|
|
93
103
|
padding: 5px 12px;
|
|
94
104
|
border: none;
|
|
95
|
-
border-radius: 3px;
|
|
105
|
+
/* border-radius: 3px; -> Handled by global reset */
|
|
96
106
|
font-size: 14px;
|
|
97
107
|
outline: none;
|
|
98
108
|
background: rgba(255,255,255,0.9);
|
|
@@ -101,7 +111,7 @@ a:hover {
|
|
|
101
111
|
|
|
102
112
|
.topbar-search input:focus {
|
|
103
113
|
background: #ffffff;
|
|
104
|
-
box-shadow:
|
|
114
|
+
/* box-shadow: none; -> Handled by global reset */
|
|
105
115
|
}
|
|
106
116
|
|
|
107
117
|
.search-dropdown {
|
|
@@ -112,8 +122,8 @@ a:hover {
|
|
|
112
122
|
right: 0;
|
|
113
123
|
background: var(--bg);
|
|
114
124
|
border: 1px solid var(--border);
|
|
115
|
-
border-radius: var(--radius);
|
|
116
|
-
box-shadow:
|
|
125
|
+
/* border-radius: var(--radius); -> Handled by global reset */
|
|
126
|
+
/* box-shadow: none; -> Handled by global reset */
|
|
117
127
|
max-height: 300px;
|
|
118
128
|
overflow-y: auto;
|
|
119
129
|
z-index: 200;
|
|
@@ -135,6 +145,10 @@ a:hover {
|
|
|
135
145
|
text-decoration: none;
|
|
136
146
|
}
|
|
137
147
|
|
|
148
|
+
.search-dropdown a.selected {
|
|
149
|
+
background: var(--accent-light);
|
|
150
|
+
}
|
|
151
|
+
|
|
138
152
|
.search-dropdown .search-highlight {
|
|
139
153
|
color: var(--namu-green);
|
|
140
154
|
font-weight: 600;
|
|
@@ -149,7 +163,7 @@ a:hover {
|
|
|
149
163
|
.btn-graph {
|
|
150
164
|
padding: 4px 10px;
|
|
151
165
|
border: 1px solid rgba(255,255,255,0.3);
|
|
152
|
-
border-radius: 3px;
|
|
166
|
+
/* border-radius: 3px; -> Handled by global reset */
|
|
153
167
|
font-size: 13px;
|
|
154
168
|
color: #ffffff;
|
|
155
169
|
white-space: nowrap;
|
|
@@ -238,7 +252,7 @@ a:hover {
|
|
|
238
252
|
}
|
|
239
253
|
|
|
240
254
|
.page-body {
|
|
241
|
-
line-height: 1.8;
|
|
255
|
+
/* line-height: 1.8; -> Removed to inherit from body's 1.65 */
|
|
242
256
|
}
|
|
243
257
|
|
|
244
258
|
.page-body h2 {
|
|
@@ -248,7 +262,7 @@ a:hover {
|
|
|
248
262
|
padding: 4px 10px;
|
|
249
263
|
background: var(--namu-green);
|
|
250
264
|
color: #ffffff;
|
|
251
|
-
border-radius: 2px;
|
|
265
|
+
/* border-radius: 2px; -> Handled by global reset */
|
|
252
266
|
}
|
|
253
267
|
|
|
254
268
|
.page-body h3 {
|
|
@@ -257,7 +271,7 @@ a:hover {
|
|
|
257
271
|
margin: 22px 0 8px;
|
|
258
272
|
padding: 3px 8px;
|
|
259
273
|
border-left: 4px solid var(--namu-green);
|
|
260
|
-
background:
|
|
274
|
+
background: none; /* Removed background */
|
|
261
275
|
color: var(--text);
|
|
262
276
|
}
|
|
263
277
|
|
|
@@ -293,14 +307,14 @@ a:hover {
|
|
|
293
307
|
padding: 8px 14px;
|
|
294
308
|
margin: 10px 0;
|
|
295
309
|
background: #f0faf9;
|
|
296
|
-
border-radius: 0 var(--radius) var(--radius) 0;
|
|
310
|
+
/* border-radius: 0 var(--radius) var(--radius) 0; -> Handled by global reset */
|
|
297
311
|
font-size: 14px;
|
|
298
312
|
}
|
|
299
313
|
|
|
300
314
|
.page-body code {
|
|
301
315
|
background: #f0f0f0;
|
|
302
316
|
padding: 2px 5px;
|
|
303
|
-
border-radius: 2px;
|
|
317
|
+
/* border-radius: 2px; -> Handled by global reset */
|
|
304
318
|
font-size: 13px;
|
|
305
319
|
font-family: "JetBrains Mono", "D2Coding", monospace;
|
|
306
320
|
}
|
|
@@ -309,7 +323,7 @@ a:hover {
|
|
|
309
323
|
background: #2b2b2b;
|
|
310
324
|
color: #e0e0e0;
|
|
311
325
|
padding: 14px;
|
|
312
|
-
border-radius: var(--radius);
|
|
326
|
+
/* border-radius: var(--radius); -> Handled by global reset */
|
|
313
327
|
overflow-x: auto;
|
|
314
328
|
margin: 10px 0;
|
|
315
329
|
}
|
|
@@ -322,7 +336,7 @@ a:hover {
|
|
|
322
336
|
|
|
323
337
|
.page-body img {
|
|
324
338
|
max-width: 100%;
|
|
325
|
-
border-radius: var(--radius);
|
|
339
|
+
/* border-radius: var(--radius); -> Handled by global reset */
|
|
326
340
|
margin: 10px 0;
|
|
327
341
|
}
|
|
328
342
|
|
|
@@ -351,11 +365,11 @@ a:hover {
|
|
|
351
365
|
font-weight: 800;
|
|
352
366
|
}
|
|
353
367
|
|
|
354
|
-
/* TOC - namuwiki style */
|
|
368
|
+
/* TOC - namuwiki style with counters */
|
|
355
369
|
.toc-box {
|
|
356
370
|
background: #f5f5f5;
|
|
357
371
|
border: 1px solid var(--border);
|
|
358
|
-
border-radius: var(--radius);
|
|
372
|
+
/* border-radius: var(--radius); -> Handled by global reset */
|
|
359
373
|
padding: 10px 14px;
|
|
360
374
|
margin-bottom: 20px;
|
|
361
375
|
}
|
|
@@ -367,29 +381,81 @@ a:hover {
|
|
|
367
381
|
color: var(--namu-green-dark);
|
|
368
382
|
}
|
|
369
383
|
|
|
370
|
-
.toc-box .toc
|
|
384
|
+
.toc-box .toc {
|
|
385
|
+
counter-reset: h2; /* Reset counter for top-level headings */
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
.toc-box .toc ul { /* Use ul as the base, then li for items */
|
|
371
389
|
list-style: none;
|
|
372
390
|
margin: 6px 0 0 0;
|
|
373
391
|
padding-left: 0;
|
|
374
392
|
}
|
|
375
393
|
|
|
376
|
-
.toc-box .toc ul ul {
|
|
377
|
-
padding-left:
|
|
394
|
+
.toc-box .toc ul ul { /* Nested lists */
|
|
395
|
+
padding-left: 18px; /* Indent for sub-levels */
|
|
378
396
|
}
|
|
379
397
|
|
|
380
398
|
.toc-box .toc li {
|
|
381
|
-
margin:
|
|
399
|
+
margin: 2px 0; /* Slightly more space */
|
|
400
|
+
position: relative; /* For counter positioning */
|
|
382
401
|
}
|
|
383
402
|
|
|
384
|
-
.toc-box .toc a {
|
|
403
|
+
.toc-box .toc li a {
|
|
385
404
|
font-size: 13px;
|
|
386
405
|
color: var(--link);
|
|
406
|
+
display: block; /* Make link clickable over full line */
|
|
407
|
+
padding-left: 20px; /* Space for counter */
|
|
387
408
|
}
|
|
388
409
|
|
|
389
|
-
.toc-box .toc a:hover {
|
|
410
|
+
.toc-box .toc li a:hover {
|
|
390
411
|
color: var(--link-hover);
|
|
391
412
|
}
|
|
392
413
|
|
|
414
|
+
/* Counter for h2 equivalent */
|
|
415
|
+
.toc-box .toc > ul > li {
|
|
416
|
+
counter-increment: h2;
|
|
417
|
+
counter-reset: h3; /* Reset h3 counter for each new h2 */
|
|
418
|
+
}
|
|
419
|
+
.toc-box .toc > ul > li > a::before {
|
|
420
|
+
content: counter(h2) ". ";
|
|
421
|
+
position: absolute;
|
|
422
|
+
left: 0;
|
|
423
|
+
color: var(--text-muted);
|
|
424
|
+
font-weight: 400;
|
|
425
|
+
width: 20px; /* Fixed width for alignment */
|
|
426
|
+
text-align: right;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/* Counter for h3 equivalent */
|
|
430
|
+
.toc-box .toc > ul > li > ul > li {
|
|
431
|
+
counter-increment: h3;
|
|
432
|
+
counter-reset: h4; /* Reset h4 counter for each new h3 */
|
|
433
|
+
}
|
|
434
|
+
.toc-box .toc > ul > li > ul > li > a::before {
|
|
435
|
+
content: counter(h2) "." counter(h3) ". ";
|
|
436
|
+
position: absolute;
|
|
437
|
+
left: 0;
|
|
438
|
+
color: var(--text-muted);
|
|
439
|
+
font-weight: 400;
|
|
440
|
+
width: 20px;
|
|
441
|
+
text-align: right;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/* Counter for h4 equivalent */
|
|
445
|
+
.toc-box .toc > ul > li > ul > li > ul > li {
|
|
446
|
+
counter-increment: h4;
|
|
447
|
+
}
|
|
448
|
+
.toc-box .toc > ul > li > ul > li > ul > li > a::before {
|
|
449
|
+
content: counter(h2) "." counter(h3) "." counter(h4) ". ";
|
|
450
|
+
position: absolute;
|
|
451
|
+
left: 0;
|
|
452
|
+
color: var(--text-muted);
|
|
453
|
+
font-weight: 400;
|
|
454
|
+
width: 20px;
|
|
455
|
+
text-align: right;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
|
|
393
459
|
/* Backlinks */
|
|
394
460
|
.backlinks {
|
|
395
461
|
margin-top: 36px;
|
|
@@ -416,7 +482,7 @@ a:hover {
|
|
|
416
482
|
padding: 3px 10px;
|
|
417
483
|
background: var(--accent-light);
|
|
418
484
|
border: 1px solid var(--namu-green);
|
|
419
|
-
border-radius: 3px;
|
|
485
|
+
/* border-radius: 3px; -> Handled by global reset */
|
|
420
486
|
font-size: 13px;
|
|
421
487
|
color: var(--namu-green-dark);
|
|
422
488
|
}
|
|
@@ -463,7 +529,7 @@ a:hover {
|
|
|
463
529
|
padding: 4px 10px;
|
|
464
530
|
background: var(--namu-green);
|
|
465
531
|
color: #ffffff;
|
|
466
|
-
border-radius: 2px;
|
|
532
|
+
/* border-radius: 2px; -> Handled by global reset */
|
|
467
533
|
}
|
|
468
534
|
|
|
469
535
|
.page-cards {
|
|
@@ -473,12 +539,22 @@ a:hover {
|
|
|
473
539
|
margin-bottom: 28px;
|
|
474
540
|
}
|
|
475
541
|
|
|
542
|
+
.empty-state {
|
|
543
|
+
grid-column: 1 / -1;
|
|
544
|
+
padding: 32px;
|
|
545
|
+
text-align: center;
|
|
546
|
+
color: var(--text-muted);
|
|
547
|
+
font-size: 14px;
|
|
548
|
+
border: 2px dashed var(--border);
|
|
549
|
+
margin: 8px 0;
|
|
550
|
+
}
|
|
551
|
+
|
|
476
552
|
.page-card {
|
|
477
553
|
display: block;
|
|
478
554
|
padding: 10px 14px;
|
|
479
555
|
background: var(--bg);
|
|
480
556
|
border: 1px solid var(--border);
|
|
481
|
-
border-radius: var(--radius);
|
|
557
|
+
/* border-radius: var(--radius); -> Handled by global reset */
|
|
482
558
|
color: var(--text);
|
|
483
559
|
transition: all 0.15s;
|
|
484
560
|
}
|
|
@@ -488,7 +564,7 @@ a:hover {
|
|
|
488
564
|
border-color: var(--namu-green);
|
|
489
565
|
text-decoration: none;
|
|
490
566
|
transform: translateY(-1px);
|
|
491
|
-
box-shadow:
|
|
567
|
+
/* box-shadow: none; -> Handled by global reset */
|
|
492
568
|
}
|
|
493
569
|
|
|
494
570
|
.card-title {
|
|
@@ -506,7 +582,7 @@ a:hover {
|
|
|
506
582
|
padding: 10px 18px;
|
|
507
583
|
background: var(--accent-light);
|
|
508
584
|
border: 1px solid var(--namu-green);
|
|
509
|
-
border-radius: var(--radius);
|
|
585
|
+
/* border-radius: var(--radius); -> Handled by global reset */
|
|
510
586
|
font-size: 14px;
|
|
511
587
|
color: var(--namu-green-dark);
|
|
512
588
|
font-weight: 600;
|
|
@@ -539,7 +615,7 @@ a:hover {
|
|
|
539
615
|
width: 100%;
|
|
540
616
|
height: calc(100vh - 200px);
|
|
541
617
|
border: 1px solid var(--border);
|
|
542
|
-
border-radius: var(--radius);
|
|
618
|
+
/* border-radius: var(--radius); -> Handled by global reset */
|
|
543
619
|
background: #fafafa;
|
|
544
620
|
}
|
|
545
621
|
|
|
@@ -577,7 +653,7 @@ h4 .headerlink {
|
|
|
577
653
|
font-size: 11px;
|
|
578
654
|
font-weight: 700;
|
|
579
655
|
padding: 2px 8px;
|
|
580
|
-
border-radius: 2px;
|
|
656
|
+
/* border-radius: 2px; -> Handled by global reset */
|
|
581
657
|
margin-bottom: 6px;
|
|
582
658
|
letter-spacing: 0.03em;
|
|
583
659
|
}
|
|
@@ -656,7 +732,7 @@ h4 .headerlink {
|
|
|
656
732
|
padding: 14px;
|
|
657
733
|
background: #f0f7ff;
|
|
658
734
|
border: 1px solid #bdd7ee;
|
|
659
|
-
border-radius: var(--radius);
|
|
735
|
+
/* border-radius: var(--radius); -> Handled by global reset */
|
|
660
736
|
}
|
|
661
737
|
|
|
662
738
|
.external-refs h3 {
|
|
@@ -678,7 +754,7 @@ h4 .headerlink {
|
|
|
678
754
|
|
|
679
755
|
.external-refs a {
|
|
680
756
|
font-size: 13px;
|
|
681
|
-
color: #1565c0;
|
|
757
|
+
color: #1565c0; /* Specific blue for external refs */
|
|
682
758
|
}
|
|
683
759
|
|
|
684
760
|
/* Backlink type indicators */
|
|
@@ -715,7 +791,7 @@ h4 .headerlink {
|
|
|
715
791
|
padding: 5px 14px;
|
|
716
792
|
border: 1px solid var(--border);
|
|
717
793
|
background: var(--bg-alt);
|
|
718
|
-
border-radius: var(--radius);
|
|
794
|
+
/* border-radius: var(--radius); -> Handled by global reset */
|
|
719
795
|
font-size: 13px;
|
|
720
796
|
cursor: pointer;
|
|
721
797
|
transition: all 0.15s;
|
|
@@ -744,7 +820,7 @@ h4 .headerlink {
|
|
|
744
820
|
justify-content: center;
|
|
745
821
|
padding: 18px;
|
|
746
822
|
border: 2px dashed var(--border);
|
|
747
|
-
border-radius: var(--radius);
|
|
823
|
+
/* border-radius: var(--radius); -> Handled by global reset */
|
|
748
824
|
cursor: pointer;
|
|
749
825
|
transition: all 0.15s;
|
|
750
826
|
text-align: center;
|
|
@@ -778,7 +854,7 @@ h4 .headerlink {
|
|
|
778
854
|
flex: 1;
|
|
779
855
|
padding: 8px 12px;
|
|
780
856
|
border: 1px solid var(--border);
|
|
781
|
-
border-radius: var(--radius);
|
|
857
|
+
/* border-radius: var(--radius); -> Handled by global reset */
|
|
782
858
|
font-size: 14px;
|
|
783
859
|
outline: none;
|
|
784
860
|
transition: border-color 0.2s;
|
|
@@ -786,7 +862,7 @@ h4 .headerlink {
|
|
|
786
862
|
|
|
787
863
|
.add-form input:focus {
|
|
788
864
|
border-color: var(--namu-green);
|
|
789
|
-
box-shadow:
|
|
865
|
+
/* box-shadow: none; -> Handled by global reset */
|
|
790
866
|
}
|
|
791
867
|
|
|
792
868
|
.add-form button {
|
|
@@ -794,7 +870,7 @@ h4 .headerlink {
|
|
|
794
870
|
background: var(--namu-green);
|
|
795
871
|
color: white;
|
|
796
872
|
border: none;
|
|
797
|
-
border-radius: var(--radius);
|
|
873
|
+
/* border-radius: var(--radius); -> Handled by global reset */
|
|
798
874
|
font-size: 14px;
|
|
799
875
|
font-weight: 600;
|
|
800
876
|
cursor: pointer;
|
|
@@ -813,7 +889,7 @@ h4 .headerlink {
|
|
|
813
889
|
|
|
814
890
|
.add-status {
|
|
815
891
|
padding: 8px 12px;
|
|
816
|
-
border-radius: var(--radius);
|
|
892
|
+
/* border-radius: var(--radius); -> Handled by global reset */
|
|
817
893
|
font-size: 14px;
|
|
818
894
|
}
|
|
819
895
|
|
|
@@ -849,7 +925,7 @@ h4 .headerlink {
|
|
|
849
925
|
padding: 10px 14px;
|
|
850
926
|
background: var(--bg-alt);
|
|
851
927
|
border: 1px solid var(--border);
|
|
852
|
-
border-radius: var(--radius);
|
|
928
|
+
/* border-radius: var(--radius); -> Handled by global reset */
|
|
853
929
|
text-align: center;
|
|
854
930
|
}
|
|
855
931
|
|
|
@@ -879,7 +955,7 @@ h4 .headerlink {
|
|
|
879
955
|
display: inline-block;
|
|
880
956
|
width: 12px;
|
|
881
957
|
height: 12px;
|
|
882
|
-
border-radius: 50%;
|
|
958
|
+
border-radius: 50%; /* Keep for circular dot */
|
|
883
959
|
vertical-align: middle;
|
|
884
960
|
margin-right: 4px;
|
|
885
961
|
}
|
|
@@ -892,11 +968,49 @@ h4 .headerlink {
|
|
|
892
968
|
background: var(--namu-green);
|
|
893
969
|
}
|
|
894
970
|
|
|
971
|
+
/* Hamburger menu button */
|
|
972
|
+
.topbar-menu-btn {
|
|
973
|
+
display: none;
|
|
974
|
+
background: none;
|
|
975
|
+
border: none;
|
|
976
|
+
color: white;
|
|
977
|
+
font-size: 22px;
|
|
978
|
+
cursor: pointer;
|
|
979
|
+
padding: 0 8px;
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
/* Sidebar overlay */
|
|
983
|
+
.sidebar-overlay {
|
|
984
|
+
display: none;
|
|
985
|
+
}
|
|
986
|
+
|
|
895
987
|
/* Responsive */
|
|
896
988
|
@media (max-width: 768px) {
|
|
989
|
+
.topbar-menu-btn { display: flex; align-items: center; }
|
|
990
|
+
|
|
897
991
|
.sidebar {
|
|
992
|
+
position: fixed;
|
|
993
|
+
left: -260px;
|
|
994
|
+
top: 46px;
|
|
995
|
+
bottom: 0;
|
|
996
|
+
width: 260px;
|
|
997
|
+
z-index: 99;
|
|
998
|
+
transition: left 0.25s ease;
|
|
999
|
+
background: var(--bg);
|
|
1000
|
+
display: block !important;
|
|
1001
|
+
}
|
|
1002
|
+
.sidebar.open { left: 0; }
|
|
1003
|
+
|
|
1004
|
+
.sidebar-overlay {
|
|
898
1005
|
display: none;
|
|
1006
|
+
position: fixed;
|
|
1007
|
+
inset: 0;
|
|
1008
|
+
top: 46px;
|
|
1009
|
+
background: rgba(0,0,0,0.4);
|
|
1010
|
+
z-index: 98;
|
|
899
1011
|
}
|
|
1012
|
+
.sidebar-overlay.active { display: block; }
|
|
1013
|
+
|
|
900
1014
|
.content {
|
|
901
1015
|
margin-left: 0;
|
|
902
1016
|
padding: 16px 14px;
|
|
@@ -905,3 +1019,26 @@ h4 .headerlink {
|
|
|
905
1019
|
max-width: 180px;
|
|
906
1020
|
}
|
|
907
1021
|
}
|
|
1022
|
+
|
|
1023
|
+
/* Dark mode */
|
|
1024
|
+
@media (prefers-color-scheme: dark) {
|
|
1025
|
+
:root {
|
|
1026
|
+
--bg: #1a1a2e;
|
|
1027
|
+
--bg-alt: #16213e;
|
|
1028
|
+
--bg-hover: #0f3460;
|
|
1029
|
+
--text: #e0e0e0;
|
|
1030
|
+
--text-muted: #8e8e8e;
|
|
1031
|
+
--text-dark: #ffffff;
|
|
1032
|
+
--border: #2a2a4a;
|
|
1033
|
+
--accent-light: #1a3a3a;
|
|
1034
|
+
--namu-green: #00b4a6;
|
|
1035
|
+
--namu-green-dark: #008c7e;
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
.topbar { background: #004d40; }
|
|
1039
|
+
.page-body h2 { background: #006050; }
|
|
1040
|
+
.page-card { background: var(--bg-alt); }
|
|
1041
|
+
.toc-box { background: var(--bg-alt); }
|
|
1042
|
+
.sidebar { background: var(--bg); border-right-color: var(--border); }
|
|
1043
|
+
img { opacity: 0.9; }
|
|
1044
|
+
}
|