|
4 | 4 | <meta charset="utf-8"> |
5 | 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> |
6 | 6 | <title>codedocent — {{ root.name }}</title> |
| 7 | +<script> |
| 8 | +if (localStorage.getItem('cd-theme') === 'light') |
| 9 | + document.documentElement.classList.add('light-mode'); |
| 10 | +</script> |
7 | 11 | <style> |
| 12 | +:root { |
| 13 | + --bg-body: #1a1a2e; |
| 14 | + --bg-header: #12122a; |
| 15 | + --bg-node: #222240; |
| 16 | + --bg-imports: #1e1e38; |
| 17 | + --bg-pseudocode: #1e2a38; |
| 18 | + --bg-source: #0d0d1a; |
| 19 | + --bg-badge: #2e2e50; |
| 20 | + --bg-btn: #2e2e50; |
| 21 | + --bg-btn-hover: #3a3a5a; |
| 22 | + --bg-btn-active: #4466aa; |
| 23 | + --bg-breadcrumb: #1e1e38; |
| 24 | + --bg-header-hover: #2a2a48; |
| 25 | + |
| 26 | + --text-primary: #e0e0e0; |
| 27 | + --text-body: #e0e0e0; |
| 28 | + --text-name: #e0e0f0; |
| 29 | + --text-badge: #aaaacc; |
| 30 | + --text-lines: #8888aa; |
| 31 | + --text-range: #8888aa; |
| 32 | + --text-warning: #FFD700; |
| 33 | + --text-imports-label: #8888aa; |
| 34 | + --text-imports-item: #bbbbcc; |
| 35 | + --text-summary: #bbbbcc; |
| 36 | + --text-placeholder: #666688; |
| 37 | + --text-pseudocode-label: #8888aa; |
| 38 | + --text-pseudocode: #ccccdd; |
| 39 | + --text-btn: #bbbbcc; |
| 40 | + --text-link: #7799ee; |
| 41 | + --text-toggle: #8888aa; |
| 42 | + --text-analyzing: #8888aa; |
| 43 | + --text-breadcrumb-sep: #666688; |
| 44 | + --text-breadcrumb-current: #e0e0e0; |
| 45 | + --text-source: #D4D4D4; |
| 46 | + |
| 47 | + --border-node: #3a3a5a; |
| 48 | + --border-imports: #3a3a5a; |
| 49 | + --border-pseudocode: #3a4a5a; |
| 50 | + --border-btn: #4a4a6a; |
| 51 | + --border-breadcrumb: #3a3a5a; |
| 52 | + --border-textarea: #555; |
| 53 | + |
| 54 | + --shadow-node: rgba(0, 0, 0, 0.3); |
| 55 | +} |
| 56 | + |
| 57 | +:root.light-mode { |
| 58 | + --bg-body: #F5F5F5; |
| 59 | + --bg-header: #1A1A2E; |
| 60 | + --bg-node: #FFFFFF; |
| 61 | + --bg-imports: #F9F9F9; |
| 62 | + --bg-pseudocode: #F0F4F8; |
| 63 | + --bg-source: #1E1E2E; |
| 64 | + --bg-badge: #EEEEEE; |
| 65 | + --bg-btn: #F5F5F5; |
| 66 | + --bg-btn-hover: #E8E8E8; |
| 67 | + --bg-btn-active: #1A1A2E; |
| 68 | + --bg-breadcrumb: #EDEDF0; |
| 69 | + --bg-header-hover: #FAFAFA; |
| 70 | + |
| 71 | + --text-primary: #333; |
| 72 | + --text-body: #333; |
| 73 | + --text-name: #1A1A2E; |
| 74 | + --text-badge: #666666; |
| 75 | + --text-lines: #888888; |
| 76 | + --text-range: #AAAAAA; |
| 77 | + --text-warning: #B8860B; |
| 78 | + --text-imports-label: #999999; |
| 79 | + --text-imports-item: #555555; |
| 80 | + --text-summary: #555555; |
| 81 | + --text-placeholder: #AAAAAA; |
| 82 | + --text-pseudocode-label: #999999; |
| 83 | + --text-pseudocode: #444444; |
| 84 | + --text-btn: #555; |
| 85 | + --text-link: #5566DD; |
| 86 | + --text-toggle: #888; |
| 87 | + --text-analyzing: #888; |
| 88 | + --text-breadcrumb-sep: #999; |
| 89 | + --text-breadcrumb-current: #333; |
| 90 | + --text-source: #D4D4D4; |
| 91 | + |
| 92 | + --border-node: #E0E0E0; |
| 93 | + --border-imports: #E8E8E8; |
| 94 | + --border-pseudocode: #D0D8E0; |
| 95 | + --border-btn: #D0D0D0; |
| 96 | + --border-breadcrumb: #D8D8DC; |
| 97 | + --border-textarea: #555; |
| 98 | + |
| 99 | + --shadow-node: rgba(0, 0, 0, 0.06); |
| 100 | +} |
| 101 | + |
8 | 102 | *, *::before, *::after { |
9 | 103 | box-sizing: border-box; |
10 | 104 | } |
|
13 | 107 | margin: 0; |
14 | 108 | padding: 0; |
15 | 109 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; |
16 | | - background: #F5F5F5; |
17 | | - color: #333; |
| 110 | + background: var(--bg-body); |
| 111 | + color: var(--text-body); |
18 | 112 | line-height: 1.5; |
19 | 113 | } |
20 | 114 |
|
21 | 115 | .cd-header { |
22 | | - background: #1A1A2E; |
| 116 | + background: var(--bg-header); |
23 | 117 | color: #FFFFFF; |
24 | 118 | padding: 16px 24px; |
25 | 119 | margin-bottom: 24px; |
| 120 | + display: flex; |
| 121 | + justify-content: space-between; |
| 122 | + align-items: center; |
26 | 123 | } |
27 | 124 |
|
28 | 125 | .cd-header__title { |
|
39 | 136 | font-family: "SF Mono", "Fira Code", "Consolas", monospace; |
40 | 137 | } |
41 | 138 |
|
| 139 | +.cd-theme-toggle { |
| 140 | + background: none; |
| 141 | + border: 1px solid rgba(255,255,255,0.3); |
| 142 | + border-radius: 8px; |
| 143 | + font-size: 20px; |
| 144 | + cursor: pointer; |
| 145 | + padding: 4px 8px; |
| 146 | + color: #FFF; |
| 147 | + line-height: 1; |
| 148 | +} |
| 149 | + |
| 150 | +.cd-theme-toggle:hover { |
| 151 | + border-color: rgba(255,255,255,0.6); |
| 152 | +} |
| 153 | + |
42 | 154 | .cd-main { |
43 | 155 | max-width: 960px; |
44 | 156 | margin: 0 auto; |
45 | 157 | padding: 0 16px 48px; |
46 | 158 | } |
47 | 159 |
|
48 | 160 | .cd-node { |
49 | | - background: #FFFFFF; |
50 | | - border: 1px solid #E0E0E0; |
| 161 | + background: var(--bg-node); |
| 162 | + border: 1px solid var(--border-node); |
51 | 163 | border-radius: 8px; |
52 | | - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06); |
| 164 | + box-shadow: 0 1px 3px var(--shadow-node); |
53 | 165 | margin-bottom: 12px; |
54 | 166 | border-left-width: 5px; |
55 | 167 | border-left-style: solid; |
| 168 | + border-left-color: var(--node-color, #CCCCCC); |
56 | 169 | } |
57 | 170 |
|
58 | 171 | .cd-node__header { |
|
72 | 185 | font-family: "SF Mono", "Fira Code", "Consolas", monospace; |
73 | 186 | font-weight: 700; |
74 | 187 | font-size: 14px; |
75 | | - color: #1A1A2E; |
| 188 | + color: var(--text-name); |
76 | 189 | } |
77 | 190 |
|
78 | 191 | .cd-node__badge { |
|
83 | 196 | letter-spacing: 0.05em; |
84 | 197 | padding: 2px 8px; |
85 | 198 | border-radius: 10px; |
86 | | - background: #EEEEEE; |
87 | | - color: #666666; |
| 199 | + background: var(--bg-badge); |
| 200 | + color: var(--text-badge); |
88 | 201 | } |
89 | 202 |
|
90 | 203 | .cd-node__lines { |
91 | 204 | font-size: 12px; |
92 | | - color: #888888; |
| 205 | + color: var(--text-lines); |
93 | 206 | } |
94 | 207 |
|
95 | 208 | .cd-node__range { |
96 | 209 | font-size: 12px; |
97 | | - color: #AAAAAA; |
| 210 | + color: var(--text-range); |
98 | 211 | font-family: "SF Mono", "Fira Code", "Consolas", monospace; |
99 | 212 | } |
100 | 213 |
|
|
105 | 218 |
|
106 | 219 | .cd-node__warning-text { |
107 | 220 | font-size: 11px; |
108 | | - color: #B8860B; |
| 221 | + color: var(--text-warning); |
109 | 222 | font-style: italic; |
110 | 223 | } |
111 | 224 |
|
|
114 | 227 | } |
115 | 228 |
|
116 | 229 | .cd-imports { |
117 | | - background: #F9F9F9; |
118 | | - border: 1px solid #E8E8E8; |
| 230 | + background: var(--bg-imports); |
| 231 | + border: 1px solid var(--border-imports); |
119 | 232 | border-radius: 6px; |
120 | 233 | padding: 8px 12px; |
121 | 234 | margin-bottom: 8px; |
|
126 | 239 | font-weight: 700; |
127 | 240 | text-transform: uppercase; |
128 | 241 | letter-spacing: 0.08em; |
129 | | - color: #999999; |
| 242 | + color: var(--text-imports-label); |
130 | 243 | margin-bottom: 4px; |
131 | 244 | } |
132 | 245 |
|
133 | 246 | .cd-imports__item { |
134 | 247 | font-family: "SF Mono", "Fira Code", "Consolas", monospace; |
135 | 248 | font-size: 13px; |
136 | | - color: #555555; |
| 249 | + color: var(--text-imports-item); |
137 | 250 | padding: 1px 0; |
138 | 251 | } |
139 | 252 |
|
140 | 253 | .cd-summary { |
141 | | - color: #555555; |
| 254 | + color: var(--text-summary); |
142 | 255 | font-size: 13px; |
143 | 256 | margin-bottom: 8px; |
144 | 257 | } |
145 | 258 |
|
146 | 259 | .cd-summary--placeholder { |
147 | 260 | font-style: italic; |
148 | | - color: #AAAAAA; |
| 261 | + color: var(--text-placeholder); |
149 | 262 | } |
150 | 263 |
|
151 | 264 | .cd-pseudocode { |
152 | | - background: #F0F4F8; |
153 | | - border: 1px solid #D0D8E0; |
| 265 | + background: var(--bg-pseudocode); |
| 266 | + border: 1px solid var(--border-pseudocode); |
154 | 267 | border-radius: 6px; |
155 | 268 | padding: 8px 12px; |
156 | 269 | margin-bottom: 8px; |
|
161 | 274 | font-weight: 700; |
162 | 275 | text-transform: uppercase; |
163 | 276 | letter-spacing: 0.08em; |
164 | | - color: #999999; |
| 277 | + color: var(--text-pseudocode-label); |
165 | 278 | margin-bottom: 4px; |
166 | 279 | } |
167 | 280 |
|
168 | 281 | .cd-pseudocode__code { |
169 | 282 | font-family: "SF Mono", "Fira Code", "Consolas", monospace; |
170 | 283 | font-size: 13px; |
171 | | - color: #444444; |
| 284 | + color: var(--text-pseudocode); |
172 | 285 | margin: 0; |
173 | 286 | white-space: pre-wrap; |
174 | 287 | } |
|
179 | 292 |
|
180 | 293 | .cd-warnings__item { |
181 | 294 | font-size: 13px; |
182 | | - color: #B8860B; |
| 295 | + color: var(--text-warning); |
183 | 296 | padding: 2px 0; |
184 | 297 | } |
185 | 298 |
|
|
199 | 312 | font-weight: 600; |
200 | 313 | padding: 4px 12px; |
201 | 314 | border-radius: 14px; |
202 | | - border: 1px solid #D0D0D0; |
203 | | - background: #F5F5F5; |
204 | | - color: #555; |
| 315 | + border: 1px solid var(--border-btn); |
| 316 | + background: var(--bg-btn); |
| 317 | + color: var(--text-btn); |
205 | 318 | cursor: pointer; |
206 | 319 | transition: background 0.15s, color 0.15s; |
207 | 320 | } |
208 | 321 |
|
209 | 322 | .cd-code-btn:hover { |
210 | | - background: #E8E8E8; |
| 323 | + background: var(--bg-btn-hover); |
211 | 324 | } |
212 | 325 |
|
213 | 326 | .cd-code-btn--active { |
214 | | - background: #1A1A2E; |
| 327 | + background: var(--bg-btn-active); |
215 | 328 | color: #FFF; |
216 | | - border-color: #1A1A2E; |
| 329 | + border-color: var(--bg-btn-active); |
217 | 330 | } |
218 | 331 |
|
219 | 332 | .cd-code-btn--copied { |
|
224 | 337 |
|
225 | 338 | .cd-source-display { |
226 | 339 | display: none; |
227 | | - background: #1E1E2E; |
228 | | - color: #D4D4D4; |
| 340 | + background: var(--bg-source); |
| 341 | + color: var(--text-source); |
229 | 342 | border-radius: 6px; |
230 | 343 | padding: 12px; |
231 | 344 | margin-bottom: 8px; |
|
263 | 376 | <body> |
264 | 377 |
|
265 | 378 | {% macro render_node(node) %} |
266 | | -<div class="cd-node cd-node--{{ node.node_type }}" style="border-left-color: {{ get_color(node) }};"> |
| 379 | +<div class="cd-node cd-node--{{ node.node_type }}" style="--node-color: {{ get_color(node) }};"> |
267 | 380 | <div class="cd-node__header"> |
268 | 381 | <span class="cd-node__icon">{{ NODE_ICONS.get(node.node_type, '') }}</span> |
269 | 382 | <span class="cd-node__name">{{ node.name }}</span> |
|
334 | 447 | {% endmacro %} |
335 | 448 |
|
336 | 449 | <header class="cd-header"> |
337 | | - <h1 class="cd-header__title">codedocent</h1> |
338 | | - <p class="cd-header__path">{{ root.filepath or root.name }}</p> |
| 450 | + <div class="cd-header__text"> |
| 451 | + <h1 class="cd-header__title">codedocent</h1> |
| 452 | + <p class="cd-header__path">{{ root.filepath or root.name }}</p> |
| 453 | + </div> |
| 454 | + <button class="cd-theme-toggle" id="cd-theme-toggle" |
| 455 | + onclick="cdToggleTheme()" aria-label="Toggle theme"></button> |
339 | 456 | </header> |
340 | 457 |
|
341 | 458 | <main class="cd-main"> |
342 | 459 | {{ render_node(root) }} |
343 | 460 | </main> |
344 | 461 |
|
345 | 462 | <script> |
| 463 | +function cdToggleTheme() { |
| 464 | + var isLight = document.documentElement.classList.toggle('light-mode'); |
| 465 | + localStorage.setItem('cd-theme', isLight ? 'light' : 'dark'); |
| 466 | + document.getElementById('cd-theme-toggle').textContent = isLight ? '\u{1F319}' : '\u2600\uFE0F'; |
| 467 | +} |
| 468 | +(function() { |
| 469 | + var btn = document.getElementById('cd-theme-toggle'); |
| 470 | + if (btn) btn.textContent = document.documentElement.classList.contains('light-mode') ? '\u{1F319}' : '\u2600\uFE0F'; |
| 471 | +})(); |
| 472 | + |
346 | 473 | function cdToggleSource(btn) { |
347 | 474 | var pre = btn.closest('.cd-node__body').querySelector('.cd-source-display'); |
348 | 475 | var isOpen = pre.classList.contains('cd-source-display--open'); |
|
0 commit comments