codemini-cli 0.5.10 → 0.5.12

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.
Files changed (59) hide show
  1. package/OPERATIONS.md +242 -242
  2. package/README.md +588 -588
  3. package/codemini-web/dist/assets/{highlighted-body-OFNGDK62-7HL7yft8.js → highlighted-body-OFNGDK62-B-G99D0A.js} +1 -1
  4. package/codemini-web/dist/assets/{index-BK75hMb2.js → index-DIGUEzan.js} +108 -108
  5. package/codemini-web/dist/assets/index-Dkq1DdDX.css +2 -0
  6. package/codemini-web/dist/assets/mermaid-GHXKKRXX-va2Kl89u.js +1 -0
  7. package/codemini-web/dist/index.html +35 -23
  8. package/codemini-web/lib/approval-manager.js +32 -32
  9. package/codemini-web/lib/runtime-bridge.js +17 -11
  10. package/codemini-web/server.js +534 -205
  11. package/deployment.md +212 -212
  12. package/package.json +2 -2
  13. package/skills/brainstorm/SKILL.md +77 -77
  14. package/skills/codemini.skills.json +40 -40
  15. package/skills/grill-me/SKILL.md +30 -30
  16. package/skills/superpowers-lite/SKILL.md +82 -82
  17. package/src/cli.js +74 -74
  18. package/src/commands/chat.js +210 -210
  19. package/src/commands/run.js +313 -313
  20. package/src/commands/skill.js +438 -304
  21. package/src/commands/web.js +57 -57
  22. package/src/core/agent-loop.js +980 -980
  23. package/src/core/ast.js +309 -307
  24. package/src/core/chat-runtime.js +6261 -6253
  25. package/src/core/command-evaluator.js +72 -72
  26. package/src/core/command-loader.js +311 -311
  27. package/src/core/command-policy.js +301 -301
  28. package/src/core/command-risk.js +156 -156
  29. package/src/core/config-store.js +286 -285
  30. package/src/core/constants.js +18 -1
  31. package/src/core/context-compact.js +365 -365
  32. package/src/core/default-system-prompt.js +114 -107
  33. package/src/core/dream-audit.js +105 -105
  34. package/src/core/dream-consolidate.js +229 -229
  35. package/src/core/dream-evaluator.js +185 -185
  36. package/src/core/fff-adapter.js +383 -383
  37. package/src/core/memory-store.js +543 -543
  38. package/src/core/project-index.js +737 -548
  39. package/src/core/project-instructions.js +98 -98
  40. package/src/core/provider/anthropic.js +514 -514
  41. package/src/core/provider/openai-compatible.js +501 -501
  42. package/src/core/reflect-skill.js +178 -178
  43. package/src/core/reply-language.js +40 -40
  44. package/src/core/session-store.js +474 -474
  45. package/src/core/shell-profile.js +237 -237
  46. package/src/core/shell.js +323 -323
  47. package/src/core/soul.js +69 -69
  48. package/src/core/system-prompt-composer.js +52 -52
  49. package/src/core/tool-args.js +199 -154
  50. package/src/core/tool-output.js +184 -184
  51. package/src/core/tool-result-store.js +206 -206
  52. package/src/core/tools.js +3024 -2893
  53. package/src/core/version.js +11 -11
  54. package/src/tui/chat-app.js +5173 -5171
  55. package/src/tui/tool-activity/presenters/misc.js +30 -30
  56. package/src/tui/tool-activity/presenters/system.js +20 -20
  57. package/templates/project-requirements/report-shell.html +582 -582
  58. package/codemini-web/dist/assets/index-BSdIdn3L.css +0 -2
  59. package/codemini-web/dist/assets/mermaid-GHXKKRXX-Dg9qh8mg.js +0 -1
@@ -1,582 +1,582 @@
1
- <!doctype html>
2
- <html lang="{{html_lang}}">
3
- <head>
4
- <meta charset="utf-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1">
6
- <title>{{title}}</title>
7
- <style>
8
- :root {
9
- color-scheme: light;
10
- --bg: #ffffff;
11
- --bg-muted: #f7f7f5;
12
- --bg-hover: #f0f0ee;
13
- --panel: #ffffff;
14
- --text: #37352f;
15
- --text-secondary: #787774;
16
- --line: #e9e9e7;
17
- --line-strong: #dfdfde;
18
- --accent: #2383e2;
19
- --accent-hover: #1b6ec2;
20
- --accent-bg: #e8f0fe;
21
- --red: #eb5757;
22
- --red-bg: #ffefef;
23
- --orange: #e9730c;
24
- --orange-bg: #fff4e5;
25
- --green: #4dab6f;
26
- --green-bg: #edf7f0;
27
- --gray: #9b9a97;
28
- --gray-bg: #f1f1f0;
29
- --shadow-sm: 0 1px 2px rgba(0,0,0,0.04);
30
- --shadow-md: 0 2px 8px rgba(0,0,0,0.06);
31
- --radius: 6px;
32
- --radius-lg: 10px;
33
- --transition: 120ms ease;
34
- }
35
- * { box-sizing: border-box; margin: 0; }
36
- body {
37
- background: var(--bg);
38
- color: var(--text);
39
- font: 14.5px/1.6 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
40
- -webkit-font-smoothing: antialiased;
41
- -moz-osx-font-smoothing: grayscale;
42
- }
43
- /* ── Header ── */
44
- header {
45
- padding: 48px min(6vw, 80px) 28px;
46
- border-bottom: 1px solid var(--line);
47
- background: var(--bg);
48
- text-align: center;
49
- }
50
- h1, h2, h3, h4 { line-height: 1.25; color: var(--text); }
51
- h1 { margin: 0 0 8px; font-size: clamp(26px, 3.5vw, 38px); font-weight: 700; letter-spacing: -0.02em; }
52
- h2 { margin: 40px 0 16px; font-size: 22px; font-weight: 600; letter-spacing: -0.01em; }
53
- h3 { margin: 20px 0 8px; font-size: 16px; font-weight: 600; }
54
- a { color: var(--accent); text-decoration: none; transition: color var(--transition); }
55
- a:hover { color: var(--accent-hover); text-decoration: underline; }
56
- .meta {
57
- color: var(--text-secondary);
58
- font-size: 13px;
59
- display: flex;
60
- justify-content: center;
61
- gap: 16px;
62
- flex-wrap: wrap;
63
- margin-top: 4px;
64
- }
65
- .meta span::before { content: ""; display: inline-block; width: 3px; height: 3px; border-radius: 50%; background: var(--gray); vertical-align: middle; margin-right: 8px; }
66
- .meta span:first-child::before { display: none; }
67
- /* ── Layout ── */
68
- .layout {
69
- display: grid;
70
- grid-template-columns: minmax(200px, 260px) minmax(0, 1fr);
71
- gap: 0;
72
- min-height: calc(100vh - 140px);
73
- }
74
- /* ── Sidebar Nav ── */
75
- nav {
76
- position: sticky;
77
- top: 0;
78
- align-self: start;
79
- height: calc(100vh - 140px);
80
- overflow-y: auto;
81
- border-right: 1px solid var(--line);
82
- padding: 20px 12px 20px 12px;
83
- scrollbar-width: thin;
84
- }
85
- nav a {
86
- display: flex;
87
- align-items: center;
88
- gap: 8px;
89
- margin-bottom: 2px;
90
- padding: 6px 12px;
91
- border-radius: var(--radius);
92
- text-decoration: none;
93
- color: var(--text-secondary);
94
- font-size: 13.5px;
95
- font-weight: 500;
96
- transition: all var(--transition);
97
- border-bottom: none;
98
- }
99
- nav a:hover {
100
- background: var(--bg-hover);
101
- color: var(--text);
102
- text-decoration: none;
103
- }
104
- nav a.active {
105
- background: var(--bg-hover);
106
- color: var(--text);
107
- font-weight: 600;
108
- }
109
- nav a .nav-dot {
110
- width: 6px;
111
- height: 6px;
112
- border-radius: 50%;
113
- background: var(--line-strong);
114
- flex-shrink: 0;
115
- transition: background var(--transition);
116
- }
117
- nav a:hover .nav-dot,
118
- nav a.active .nav-dot { background: var(--accent); }
119
- /* ── Main Content ── */
120
- main {
121
- padding: 8px min(5vw, 80px) 80px;
122
- }
123
- section {
124
- padding: 4px 0 24px;
125
- border-bottom: 1px solid var(--line);
126
- }
127
- section:last-child { border-bottom: none; }
128
- /* ── Controls ── */
129
- .controls { margin: 8px 0 24px; }
130
- .controls-bar {
131
- display: flex;
132
- align-items: center;
133
- gap: 8px;
134
- flex-wrap: wrap;
135
- }
136
- input[type="search"] {
137
- width: min(400px, 100%);
138
- padding: 7px 12px 7px 32px;
139
- border: 1px solid var(--line);
140
- border-radius: var(--radius);
141
- background: var(--bg) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' fill='none' stroke='%239b9a97' stroke-width='2'%3E%3Ccircle cx='6' cy='6' r='4.5'/%3E%3Cpath d='M10 10l3 3'/%3E%3C/svg%3E") 10px center no-repeat;
142
- color: var(--text);
143
- font-size: 13.5px;
144
- transition: border-color var(--transition), box-shadow var(--transition);
145
- }
146
- input[type="search"]:focus {
147
- outline: none;
148
- border-color: var(--accent);
149
- box-shadow: 0 0 0 3px var(--accent-bg);
150
- }
151
- input[type="search"]::placeholder { color: var(--gray); }
152
- .controls-bar button {
153
- padding: 6px 14px;
154
- border: 1px solid var(--line);
155
- border-radius: var(--radius);
156
- background: var(--bg);
157
- color: var(--text-secondary);
158
- cursor: pointer;
159
- font-size: 12.5px;
160
- font-weight: 500;
161
- transition: all var(--transition);
162
- }
163
- .controls-bar button:hover {
164
- background: var(--bg-hover);
165
- color: var(--text);
166
- border-color: var(--line-strong);
167
- }
168
- .no-results { color: var(--gray); padding: 32px 0; font-size: 14px; display: none; }
169
- /* ── Cards ── */
170
- .card {
171
- border: 1px solid var(--line);
172
- border-radius: var(--radius-lg);
173
- background: var(--panel);
174
- padding: 20px 24px;
175
- margin: 16px 0;
176
- transition: box-shadow var(--transition);
177
- }
178
- .card:hover { box-shadow: var(--shadow-md); }
179
- /* ── Details (collapsible API cards) ── */
180
- details.card summary {
181
- cursor: pointer;
182
- font-size: 15px;
183
- font-weight: 600;
184
- padding: 2px 0;
185
- list-style: none;
186
- display: flex;
187
- align-items: center;
188
- gap: 8px;
189
- }
190
- details.card summary::-webkit-details-marker { display: none; }
191
- details.card summary::before {
192
- content: "";
193
- display: inline-block;
194
- width: 0; height: 0;
195
- border-left: 5px solid var(--text-secondary);
196
- border-top: 4px solid transparent;
197
- border-bottom: 4px solid transparent;
198
- transition: transform var(--transition);
199
- flex-shrink: 0;
200
- }
201
- details.card[open] summary::before { transform: rotate(90deg); }
202
- details.card summary:hover { color: var(--accent); }
203
- details.card[open] summary {
204
- padding-bottom: 16px;
205
- margin-bottom: 16px;
206
- border-bottom: 1px solid var(--line);
207
- }
208
- /* ── Badges / Tags ── */
209
- .tag, .badge {
210
- display: inline-flex;
211
- align-items: center;
212
- gap: 4px;
213
- border-radius: 4px;
214
- padding: 1px 7px;
215
- font-size: 11.5px;
216
- font-weight: 600;
217
- letter-spacing: 0.02em;
218
- line-height: 20px;
219
- white-space: nowrap;
220
- }
221
- .tag.extracted, .badge.extracted { color: var(--green); background: var(--green-bg); }
222
- .tag.inferred, .badge.inferred { color: var(--orange); background: var(--orange-bg); }
223
- .tag.unknown, .badge.unknown { color: var(--gray); background: var(--gray-bg); }
224
- .tag.warn, .badge.warn { color: var(--orange); background: var(--orange-bg); }
225
- .tag.danger, .badge.danger { color: var(--red); background: var(--red-bg); }
226
- .tag.ok, .badge.ok { color: var(--green); background: var(--green-bg); }
227
- /* ── Tables ── */
228
- .table-wrap { overflow-x: auto; margin: 12px 0; border-radius: var(--radius); }
229
- table, .spec-table, .sequence-table {
230
- width: 100%;
231
- border-collapse: collapse;
232
- margin: 12px 0;
233
- font-size: 13.5px;
234
- }
235
- th, td {
236
- text-align: left;
237
- vertical-align: top;
238
- padding: 8px 12px;
239
- border-bottom: 1px solid var(--line);
240
- }
241
- th {
242
- color: var(--text-secondary);
243
- font-size: 12px;
244
- font-weight: 600;
245
- text-transform: uppercase;
246
- letter-spacing: 0.04em;
247
- background: transparent;
248
- }
249
- tr:last-child td { border-bottom: none; }
250
- tr:hover td { background: var(--bg-muted); }
251
- /* ── Code ── */
252
- code, pre {
253
- font-family: "SFMono-Regular", "Menlo", "Consolas", "PT Mono", monospace;
254
- }
255
- code {
256
- background: var(--bg-muted);
257
- padding: 2px 5px;
258
- border-radius: 3px;
259
- font-size: 13px;
260
- color: #eb5757;
261
- }
262
- pre {
263
- overflow: auto;
264
- padding: 16px;
265
- border-radius: var(--radius);
266
- background: #fafaf8;
267
- border: 1px solid var(--line);
268
- font-size: 13px;
269
- line-height: 1.5;
270
- }
271
- /* ── Diagrams ── */
272
- .diagram, .diagram-card {
273
- overflow: auto;
274
- border: 1px solid var(--line);
275
- border-radius: var(--radius-lg);
276
- background: var(--bg);
277
- padding: 20px;
278
- margin: 16px 0;
279
- }
280
- .diagram-card figcaption {
281
- color: var(--text-secondary);
282
- font-weight: 600;
283
- font-size: 13px;
284
- margin: 0 0 16px;
285
- text-transform: uppercase;
286
- letter-spacing: 0.04em;
287
- }
288
- .diagram svg { max-width: 100%; height: auto; }
289
- .diagram-card svg { width: 100%; height: auto; }
290
- /* ── Flow Steps ── */
291
- .flow-steps {
292
- display: grid;
293
- gap: 2px;
294
- margin: 12px 0;
295
- padding: 0;
296
- list-style: none;
297
- counter-reset: step;
298
- }
299
- .flow-steps li {
300
- counter-increment: step;
301
- border: 1px solid var(--line);
302
- border-radius: var(--radius);
303
- background: var(--bg-muted);
304
- padding: 14px 16px 14px 48px;
305
- position: relative;
306
- transition: background var(--transition);
307
- }
308
- .flow-steps li:hover { background: var(--bg-hover); }
309
- .flow-steps li::before {
310
- content: counter(step);
311
- position: absolute;
312
- left: 16px;
313
- top: 14px;
314
- width: 22px;
315
- height: 22px;
316
- border-radius: 50%;
317
- background: var(--accent);
318
- color: #fff;
319
- font-size: 11px;
320
- font-weight: 700;
321
- display: flex;
322
- align-items: center;
323
- justify-content: center;
324
- }
325
- .flow-steps span { display: block; color: var(--text-secondary); margin-top: 4px; font-size: 13px; }
326
- /* ── Dependency Map ── */
327
- .dependency-map {
328
- display: flex;
329
- align-items: center;
330
- gap: 6px;
331
- flex-wrap: wrap;
332
- margin: 12px 0;
333
- }
334
- .dep-node {
335
- border: 1px solid var(--line);
336
- border-radius: var(--radius);
337
- background: var(--bg-muted);
338
- padding: 8px 14px;
339
- text-decoration: none;
340
- font-size: 13px;
341
- font-weight: 500;
342
- transition: all var(--transition);
343
- }
344
- .dep-node:hover {
345
- background: var(--bg-hover);
346
- border-color: var(--line-strong);
347
- text-decoration: none;
348
- color: var(--accent);
349
- }
350
- .dep-edge { color: var(--line-strong); font-weight: 400; font-size: 16px; }
351
- /* ── Misc ── */
352
- .diagram-status { color: var(--text-secondary); font-size: 13px; margin: 8px 0 0; }
353
- .placeholder { color: var(--gray); font-style: italic; }
354
- ul, ol { padding-left: 20px; }
355
- li { margin: 4px 0; }
356
- strong { font-weight: 600; }
357
- /* ── Back to Top ── */
358
- #back-to-top {
359
- position: fixed; bottom: 28px; right: 28px;
360
- padding: 8px 16px; border-radius: var(--radius);
361
- background: var(--panel); color: var(--accent);
362
- border: 1px solid var(--line); box-shadow: var(--shadow-md);
363
- text-decoration: none; font-size: 13px; font-weight: 500;
364
- opacity: 0; pointer-events: none;
365
- transition: opacity 0.2s, box-shadow 0.2s;
366
- }
367
- #back-to-top:hover { box-shadow: var(--shadow-sm); }
368
- #back-to-top.visible { opacity: 1; pointer-events: auto; }
369
- /* ── Responsive ── */
370
- @media (max-width: 820px) {
371
- .layout { grid-template-columns: 1fr; }
372
- nav {
373
- position: static;
374
- height: auto;
375
- border-right: none;
376
- border-bottom: 1px solid var(--line);
377
- padding: 12px;
378
- display: flex;
379
- gap: 4px;
380
- overflow-x: auto;
381
- scrollbar-width: none;
382
- }
383
- nav::-webkit-scrollbar { display: none; }
384
- nav a { white-space: nowrap; padding: 6px 10px; }
385
- nav a .nav-dot { display: none; }
386
- header { padding: 28px 20px 20px; }
387
- main { padding: 8px 20px 60px; }
388
- #back-to-top { bottom: 12px; right: 12px; }
389
- }
390
- /* ── Print ── */
391
- @media print {
392
- nav, .controls-bar, #back-to-top, .no-results { display: none !important; }
393
- .layout { grid-template-columns: 1fr; padding: 0; }
394
- .card { box-shadow: none; break-inside: avoid; border: 1px solid #ddd; }
395
- .diagram-card { box-shadow: none; }
396
- body { background: #fff; }
397
- header { background: #fff; padding: 20px 0; }
398
- main { padding: 0; max-width: 100%; }
399
- a { color: #333; }
400
- code { color: #333; }
401
- * { color-adjust: exact; -webkit-print-color-adjust: exact; }
402
- }
403
- </style>
404
- </head>
405
- <body>
406
- <header>
407
- <h1>{{title}}</h1>
408
- <div class="meta">
409
- <span>{{meta_workspace}}: {{workspace_name}}</span>
410
- <span>{{meta_date}}: {{date}}</span>
411
- <span>{{meta_generated}}: {{generated_at}}</span>
412
- </div>
413
- </header>
414
- <div class="layout">
415
- <nav aria-label="{{nav_label}}">
416
- <a href="#summary"><span class="nav-dot"></span>{{nav_summary}}</a>
417
- <a href="#architecture"><span class="nav-dot"></span>{{nav_architecture}}</a>
418
- <a href="#interfaces"><span class="nav-dot"></span>{{nav_interfaces}}</a>
419
- <a href="#requirements"><span class="nav-dot"></span>{{nav_requirements}}</a>
420
- <a href="#flows"><span class="nav-dot"></span>{{nav_flows}}</a>
421
- <a href="#domain"><span class="nav-dot"></span>{{nav_domain}}</a>
422
- <a href="#security"><span class="nav-dot"></span>{{nav_security}}</a>
423
- <a href="#errors"><span class="nav-dot"></span>{{nav_errors}}</a>
424
- <a href="#nonfunctional"><span class="nav-dot"></span>{{nav_nonfunctional}}</a>
425
- <a href="#questions"><span class="nav-dot"></span>{{nav_questions}}</a>
426
- <a href="#evidence"><span class="nav-dot"></span>{{nav_evidence}}</a>
427
- </nav>
428
- <main>
429
- <div class="controls">
430
- <div class="controls-bar">
431
- <input id="report-search" type="search" placeholder="{{search_placeholder}}">
432
- <button id="toggle-details" type="button">{{expand_all}}</button>
433
- <button id="show-questions" type="button">{{questions_only}}</button>
434
- </div>
435
- <p class="no-results" id="no-results">{{no_results}}</p>
436
- </div>
437
- <section id="summary">
438
- <h2>{{heading_summary}}</h2>
439
- <!-- REQUIREMENTS_SUMMARY -->
440
- <p class="placeholder">{{pending_summary}}</p>
441
- <!-- /REQUIREMENTS_SUMMARY -->
442
- </section>
443
- <section id="architecture">
444
- <h2>{{heading_architecture}}</h2>
445
- <!-- REQUIREMENTS_ARCHITECTURE -->
446
- <p class="placeholder">{{pending_architecture}}</p>
447
- <!-- /REQUIREMENTS_ARCHITECTURE -->
448
- </section>
449
- <section id="interfaces">
450
- <h2>{{heading_interfaces}}</h2>
451
- <!-- REQUIREMENTS_INTERFACE_INVENTORY -->
452
- <p class="placeholder">{{pending_interfaces}}</p>
453
- <!-- /REQUIREMENTS_INTERFACE_INVENTORY -->
454
- </section>
455
- <section id="requirements">
456
- <h2>{{heading_requirements}}</h2>
457
- <!-- REQUIREMENTS_API_CARDS -->
458
- <p class="placeholder">{{pending_requirements}}</p>
459
- <!-- /REQUIREMENTS_API_CARDS -->
460
- </section>
461
- <section id="flows">
462
- <h2>{{heading_flows}}</h2>
463
- <!-- REQUIREMENTS_FLOWS -->
464
- <p class="placeholder">{{pending_flows}}</p>
465
- <!-- /REQUIREMENTS_FLOWS -->
466
- </section>
467
- <section id="domain">
468
- <h2>{{heading_domain}}</h2>
469
- <!-- REQUIREMENTS_DOMAIN_MODEL -->
470
- <p class="placeholder">{{pending_domain}}</p>
471
- <!-- /REQUIREMENTS_DOMAIN_MODEL -->
472
- </section>
473
- <section id="security">
474
- <h2>{{heading_security}}</h2>
475
- <!-- REQUIREMENTS_SECURITY -->
476
- <p class="placeholder">{{pending_security}}</p>
477
- <!-- /REQUIREMENTS_SECURITY -->
478
- </section>
479
- <section id="errors">
480
- <h2>{{heading_errors}}</h2>
481
- <!-- REQUIREMENTS_ERROR_HANDLING -->
482
- <p class="placeholder">{{pending_errors}}</p>
483
- <!-- /REQUIREMENTS_ERROR_HANDLING -->
484
- </section>
485
- <section id="nonfunctional">
486
- <h2>{{heading_nonfunctional}}</h2>
487
- <!-- REQUIREMENTS_NONFUNCTIONAL -->
488
- <p class="placeholder">{{pending_nonfunctional}}</p>
489
- <!-- /REQUIREMENTS_NONFUNCTIONAL -->
490
- </section>
491
- <section id="questions">
492
- <h2>{{heading_questions}}</h2>
493
- <!-- REQUIREMENTS_OPEN_QUESTIONS -->
494
- <p class="placeholder">{{pending_questions}}</p>
495
- <!-- /REQUIREMENTS_OPEN_QUESTIONS -->
496
- </section>
497
- <section id="evidence">
498
- <h2>{{heading_evidence}}</h2>
499
- <!-- REQUIREMENTS_EVIDENCE_INDEX -->
500
- <p class="placeholder">{{pending_evidence}}</p>
501
- <!-- /REQUIREMENTS_EVIDENCE_INDEX -->
502
- </section>
503
- </main>
504
- </div>
505
- <a href="#" id="back-to-top">{{back_to_top}}</a>
506
- <script>
507
- (() => {
508
- const search = document.querySelector('#report-search');
509
- const noResults = document.querySelector('#no-results');
510
- const sections = document.querySelectorAll('section[id]');
511
- const cards = document.querySelectorAll('.card, details');
512
- const tables = document.querySelectorAll('table');
513
- const navLinks = document.querySelectorAll('nav a');
514
-
515
- /* ── Scroll spy ── */
516
- const observer = new IntersectionObserver(entries => {
517
- entries.forEach(e => {
518
- if (e.isIntersecting) {
519
- navLinks.forEach(l => l.classList.toggle('active', l.getAttribute('href') === '#' + e.target.id));
520
- }
521
- });
522
- }, { rootMargin: '-80px 0px -70% 0px', threshold: 0 });
523
- sections.forEach(s => observer.observe(s));
524
-
525
- /* ── Search ── */
526
- search?.addEventListener('input', () => {
527
- const query = search.value.trim().toLowerCase();
528
- let anyVisible = false;
529
- if (!query) {
530
- cards.forEach(c => c.style.display = '');
531
- tables.forEach(t => t.style.display = '');
532
- sections.forEach(s => s.style.display = '');
533
- noResults.style.display = 'none';
534
- return;
535
- }
536
- sections.forEach(s => {
537
- const inner = s.querySelectorAll('.card, details, table');
538
- let sectionVisible = false;
539
- inner.forEach(el => {
540
- const matched = el.textContent.toLowerCase().includes(query);
541
- el.style.display = matched ? '' : 'none';
542
- if (matched) sectionVisible = true;
543
- });
544
- s.style.display = sectionVisible ? '' : 'none';
545
- if (sectionVisible) anyVisible = true;
546
- });
547
- noResults.style.display = anyVisible ? 'none' : 'block';
548
- });
549
-
550
- /* ── Expand / Collapse all ── */
551
- const toggleBtn = document.querySelector('#toggle-details');
552
- let allOpen = false;
553
- toggleBtn?.addEventListener('click', () => {
554
- allOpen = !allOpen;
555
- document.querySelectorAll('details.card').forEach(d => d.open = allOpen);
556
- toggleBtn.textContent = allOpen ? '{{collapse_all}}' : '{{expand_all}}';
557
- });
558
-
559
- /* ── Show questions only ── */
560
- const questionsBtn = document.querySelector('#show-questions');
561
- let questionsOnly = false;
562
- questionsBtn?.addEventListener('click', () => {
563
- questionsOnly = !questionsOnly;
564
- const target = document.querySelector('#questions');
565
- if (questionsOnly) {
566
- sections.forEach(s => s.style.display = s === target ? '' : 'none');
567
- questionsBtn.textContent = '{{show_all_sections}}';
568
- } else {
569
- sections.forEach(s => s.style.display = '');
570
- questionsBtn.textContent = '{{questions_only}}';
571
- }
572
- });
573
-
574
- /* ── Back to top ── */
575
- const backToTop = document.querySelector('#back-to-top');
576
- window.addEventListener('scroll', () => {
577
- backToTop?.classList.toggle('visible', window.scrollY > 400);
578
- }, { passive: true });
579
- })();
580
- </script>
581
- </body>
582
- </html>
1
+ <!doctype html>
2
+ <html lang="{{html_lang}}">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>{{title}}</title>
7
+ <style>
8
+ :root {
9
+ color-scheme: light;
10
+ --bg: #ffffff;
11
+ --bg-muted: #f7f7f5;
12
+ --bg-hover: #f0f0ee;
13
+ --panel: #ffffff;
14
+ --text: #37352f;
15
+ --text-secondary: #787774;
16
+ --line: #e9e9e7;
17
+ --line-strong: #dfdfde;
18
+ --accent: #2383e2;
19
+ --accent-hover: #1b6ec2;
20
+ --accent-bg: #e8f0fe;
21
+ --red: #eb5757;
22
+ --red-bg: #ffefef;
23
+ --orange: #e9730c;
24
+ --orange-bg: #fff4e5;
25
+ --green: #4dab6f;
26
+ --green-bg: #edf7f0;
27
+ --gray: #9b9a97;
28
+ --gray-bg: #f1f1f0;
29
+ --shadow-sm: 0 1px 2px rgba(0,0,0,0.04);
30
+ --shadow-md: 0 2px 8px rgba(0,0,0,0.06);
31
+ --radius: 6px;
32
+ --radius-lg: 10px;
33
+ --transition: 120ms ease;
34
+ }
35
+ * { box-sizing: border-box; margin: 0; }
36
+ body {
37
+ background: var(--bg);
38
+ color: var(--text);
39
+ font: 14.5px/1.6 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
40
+ -webkit-font-smoothing: antialiased;
41
+ -moz-osx-font-smoothing: grayscale;
42
+ }
43
+ /* ── Header ── */
44
+ header {
45
+ padding: 48px min(6vw, 80px) 28px;
46
+ border-bottom: 1px solid var(--line);
47
+ background: var(--bg);
48
+ text-align: center;
49
+ }
50
+ h1, h2, h3, h4 { line-height: 1.25; color: var(--text); }
51
+ h1 { margin: 0 0 8px; font-size: clamp(26px, 3.5vw, 38px); font-weight: 700; letter-spacing: -0.02em; }
52
+ h2 { margin: 40px 0 16px; font-size: 22px; font-weight: 600; letter-spacing: -0.01em; }
53
+ h3 { margin: 20px 0 8px; font-size: 16px; font-weight: 600; }
54
+ a { color: var(--accent); text-decoration: none; transition: color var(--transition); }
55
+ a:hover { color: var(--accent-hover); text-decoration: underline; }
56
+ .meta {
57
+ color: var(--text-secondary);
58
+ font-size: 13px;
59
+ display: flex;
60
+ justify-content: center;
61
+ gap: 16px;
62
+ flex-wrap: wrap;
63
+ margin-top: 4px;
64
+ }
65
+ .meta span::before { content: ""; display: inline-block; width: 3px; height: 3px; border-radius: 50%; background: var(--gray); vertical-align: middle; margin-right: 8px; }
66
+ .meta span:first-child::before { display: none; }
67
+ /* ── Layout ── */
68
+ .layout {
69
+ display: grid;
70
+ grid-template-columns: minmax(160px, 190px) minmax(0, 1fr);
71
+ gap: 0;
72
+ min-height: calc(100vh - 140px);
73
+ }
74
+ /* ── Sidebar Nav ── */
75
+ nav {
76
+ position: sticky;
77
+ top: 0;
78
+ align-self: start;
79
+ height: calc(100vh - 140px);
80
+ overflow-y: auto;
81
+ border-right: 1px solid var(--line);
82
+ padding: 20px 12px 20px 12px;
83
+ scrollbar-width: thin;
84
+ }
85
+ nav a {
86
+ display: flex;
87
+ align-items: center;
88
+ gap: 8px;
89
+ margin-bottom: 2px;
90
+ padding: 6px 12px;
91
+ border-radius: var(--radius);
92
+ text-decoration: none;
93
+ color: var(--text-secondary);
94
+ font-size: 13.5px;
95
+ font-weight: 500;
96
+ transition: all var(--transition);
97
+ border-bottom: none;
98
+ }
99
+ nav a:hover {
100
+ background: var(--bg-hover);
101
+ color: var(--text);
102
+ text-decoration: none;
103
+ }
104
+ nav a.active {
105
+ background: var(--bg-hover);
106
+ color: var(--text);
107
+ font-weight: 600;
108
+ }
109
+ nav a .nav-dot {
110
+ width: 6px;
111
+ height: 6px;
112
+ border-radius: 50%;
113
+ background: var(--line-strong);
114
+ flex-shrink: 0;
115
+ transition: background var(--transition);
116
+ }
117
+ nav a:hover .nav-dot,
118
+ nav a.active .nav-dot { background: var(--accent); }
119
+ /* ── Main Content ── */
120
+ main {
121
+ padding: 8px min(5vw, 80px) 80px;
122
+ }
123
+ section {
124
+ padding: 4px 0 24px;
125
+ border-bottom: 1px solid var(--line);
126
+ }
127
+ section:last-child { border-bottom: none; }
128
+ /* ── Controls ── */
129
+ .controls { margin: 8px 0 24px; }
130
+ .controls-bar {
131
+ display: flex;
132
+ align-items: center;
133
+ gap: 8px;
134
+ flex-wrap: wrap;
135
+ }
136
+ input[type="search"] {
137
+ width: min(400px, 100%);
138
+ padding: 7px 12px 7px 32px;
139
+ border: 1px solid var(--line);
140
+ border-radius: var(--radius);
141
+ background: var(--bg) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' fill='none' stroke='%239b9a97' stroke-width='2'%3E%3Ccircle cx='6' cy='6' r='4.5'/%3E%3Cpath d='M10 10l3 3'/%3E%3C/svg%3E") 10px center no-repeat;
142
+ color: var(--text);
143
+ font-size: 13.5px;
144
+ transition: border-color var(--transition), box-shadow var(--transition);
145
+ }
146
+ input[type="search"]:focus {
147
+ outline: none;
148
+ border-color: var(--accent);
149
+ box-shadow: 0 0 0 3px var(--accent-bg);
150
+ }
151
+ input[type="search"]::placeholder { color: var(--gray); }
152
+ .controls-bar button {
153
+ padding: 6px 14px;
154
+ border: 1px solid var(--line);
155
+ border-radius: var(--radius);
156
+ background: var(--bg);
157
+ color: var(--text-secondary);
158
+ cursor: pointer;
159
+ font-size: 12.5px;
160
+ font-weight: 500;
161
+ transition: all var(--transition);
162
+ }
163
+ .controls-bar button:hover {
164
+ background: var(--bg-hover);
165
+ color: var(--text);
166
+ border-color: var(--line-strong);
167
+ }
168
+ .no-results { color: var(--gray); padding: 32px 0; font-size: 14px; display: none; }
169
+ /* ── Cards ── */
170
+ .card {
171
+ border: 1px solid var(--line);
172
+ border-radius: var(--radius-lg);
173
+ background: var(--panel);
174
+ padding: 20px 24px;
175
+ margin: 16px 0;
176
+ transition: box-shadow var(--transition);
177
+ }
178
+ .card:hover { box-shadow: var(--shadow-md); }
179
+ /* ── Details (collapsible API cards) ── */
180
+ details.card summary {
181
+ cursor: pointer;
182
+ font-size: 15px;
183
+ font-weight: 600;
184
+ padding: 2px 0;
185
+ list-style: none;
186
+ display: flex;
187
+ align-items: center;
188
+ gap: 8px;
189
+ }
190
+ details.card summary::-webkit-details-marker { display: none; }
191
+ details.card summary::before {
192
+ content: "";
193
+ display: inline-block;
194
+ width: 0; height: 0;
195
+ border-left: 5px solid var(--text-secondary);
196
+ border-top: 4px solid transparent;
197
+ border-bottom: 4px solid transparent;
198
+ transition: transform var(--transition);
199
+ flex-shrink: 0;
200
+ }
201
+ details.card[open] summary::before { transform: rotate(90deg); }
202
+ details.card summary:hover { color: var(--accent); }
203
+ details.card[open] summary {
204
+ padding-bottom: 16px;
205
+ margin-bottom: 16px;
206
+ border-bottom: 1px solid var(--line);
207
+ }
208
+ /* ── Badges / Tags ── */
209
+ .tag, .badge {
210
+ display: inline-flex;
211
+ align-items: center;
212
+ gap: 4px;
213
+ border-radius: 4px;
214
+ padding: 1px 7px;
215
+ font-size: 11.5px;
216
+ font-weight: 600;
217
+ letter-spacing: 0.02em;
218
+ line-height: 20px;
219
+ white-space: nowrap;
220
+ }
221
+ .tag.extracted, .badge.extracted { color: var(--green); background: var(--green-bg); }
222
+ .tag.inferred, .badge.inferred { color: var(--orange); background: var(--orange-bg); }
223
+ .tag.unknown, .badge.unknown { color: var(--gray); background: var(--gray-bg); }
224
+ .tag.warn, .badge.warn { color: var(--orange); background: var(--orange-bg); }
225
+ .tag.danger, .badge.danger { color: var(--red); background: var(--red-bg); }
226
+ .tag.ok, .badge.ok { color: var(--green); background: var(--green-bg); }
227
+ /* ── Tables ── */
228
+ .table-wrap { overflow-x: auto; margin: 12px 0; border-radius: var(--radius); }
229
+ table, .spec-table, .sequence-table {
230
+ width: 100%;
231
+ border-collapse: collapse;
232
+ margin: 12px 0;
233
+ font-size: 13.5px;
234
+ }
235
+ th, td {
236
+ text-align: left;
237
+ vertical-align: top;
238
+ padding: 8px 12px;
239
+ border-bottom: 1px solid var(--line);
240
+ }
241
+ th {
242
+ color: var(--text-secondary);
243
+ font-size: 12px;
244
+ font-weight: 600;
245
+ text-transform: uppercase;
246
+ letter-spacing: 0.04em;
247
+ background: transparent;
248
+ }
249
+ tr:last-child td { border-bottom: none; }
250
+ tr:hover td { background: var(--bg-muted); }
251
+ /* ── Code ── */
252
+ code, pre {
253
+ font-family: "SFMono-Regular", "Menlo", "Consolas", "PT Mono", monospace;
254
+ }
255
+ code {
256
+ background: var(--bg-muted);
257
+ padding: 2px 5px;
258
+ border-radius: 3px;
259
+ font-size: 13px;
260
+ color: #eb5757;
261
+ }
262
+ pre {
263
+ overflow: auto;
264
+ padding: 16px;
265
+ border-radius: var(--radius);
266
+ background: #fafaf8;
267
+ border: 1px solid var(--line);
268
+ font-size: 13px;
269
+ line-height: 1.5;
270
+ }
271
+ /* ── Diagrams ── */
272
+ .diagram, .diagram-card {
273
+ overflow: auto;
274
+ border: 1px solid var(--line);
275
+ border-radius: var(--radius-lg);
276
+ background: var(--bg);
277
+ padding: 20px;
278
+ margin: 16px 0;
279
+ }
280
+ .diagram-card figcaption {
281
+ color: var(--text-secondary);
282
+ font-weight: 600;
283
+ font-size: 13px;
284
+ margin: 0 0 16px;
285
+ text-transform: uppercase;
286
+ letter-spacing: 0.04em;
287
+ }
288
+ .diagram svg { max-width: 100%; height: auto; }
289
+ .diagram-card svg { width: 100%; height: auto; }
290
+ /* ── Flow Steps ── */
291
+ .flow-steps {
292
+ display: grid;
293
+ gap: 2px;
294
+ margin: 12px 0;
295
+ padding: 0;
296
+ list-style: none;
297
+ counter-reset: step;
298
+ }
299
+ .flow-steps li {
300
+ counter-increment: step;
301
+ border: 1px solid var(--line);
302
+ border-radius: var(--radius);
303
+ background: var(--bg-muted);
304
+ padding: 14px 16px 14px 48px;
305
+ position: relative;
306
+ transition: background var(--transition);
307
+ }
308
+ .flow-steps li:hover { background: var(--bg-hover); }
309
+ .flow-steps li::before {
310
+ content: counter(step);
311
+ position: absolute;
312
+ left: 16px;
313
+ top: 14px;
314
+ width: 22px;
315
+ height: 22px;
316
+ border-radius: 50%;
317
+ background: var(--accent);
318
+ color: #fff;
319
+ font-size: 11px;
320
+ font-weight: 700;
321
+ display: flex;
322
+ align-items: center;
323
+ justify-content: center;
324
+ }
325
+ .flow-steps span { display: block; color: var(--text-secondary); margin-top: 4px; font-size: 13px; }
326
+ /* ── Dependency Map ── */
327
+ .dependency-map {
328
+ display: flex;
329
+ align-items: center;
330
+ gap: 6px;
331
+ flex-wrap: wrap;
332
+ margin: 12px 0;
333
+ }
334
+ .dep-node {
335
+ border: 1px solid var(--line);
336
+ border-radius: var(--radius);
337
+ background: var(--bg-muted);
338
+ padding: 8px 14px;
339
+ text-decoration: none;
340
+ font-size: 13px;
341
+ font-weight: 500;
342
+ transition: all var(--transition);
343
+ }
344
+ .dep-node:hover {
345
+ background: var(--bg-hover);
346
+ border-color: var(--line-strong);
347
+ text-decoration: none;
348
+ color: var(--accent);
349
+ }
350
+ .dep-edge { color: var(--line-strong); font-weight: 400; font-size: 16px; }
351
+ /* ── Misc ── */
352
+ .diagram-status { color: var(--text-secondary); font-size: 13px; margin: 8px 0 0; }
353
+ .placeholder { color: var(--gray); font-style: italic; }
354
+ ul, ol { padding-left: 20px; }
355
+ li { margin: 4px 0; }
356
+ strong { font-weight: 600; }
357
+ /* ── Back to Top ── */
358
+ #back-to-top {
359
+ position: fixed; bottom: 28px; right: 28px;
360
+ padding: 8px 16px; border-radius: var(--radius);
361
+ background: var(--panel); color: var(--accent);
362
+ border: 1px solid var(--line); box-shadow: var(--shadow-md);
363
+ text-decoration: none; font-size: 13px; font-weight: 500;
364
+ opacity: 0; pointer-events: none;
365
+ transition: opacity 0.2s, box-shadow 0.2s;
366
+ }
367
+ #back-to-top:hover { box-shadow: var(--shadow-sm); }
368
+ #back-to-top.visible { opacity: 1; pointer-events: auto; }
369
+ /* ── Responsive ── */
370
+ @media (max-width: 820px) {
371
+ .layout { grid-template-columns: 1fr; }
372
+ nav {
373
+ position: static;
374
+ height: auto;
375
+ border-right: none;
376
+ border-bottom: 1px solid var(--line);
377
+ padding: 12px;
378
+ display: flex;
379
+ gap: 4px;
380
+ overflow-x: auto;
381
+ scrollbar-width: none;
382
+ }
383
+ nav::-webkit-scrollbar { display: none; }
384
+ nav a { white-space: nowrap; padding: 6px 10px; }
385
+ nav a .nav-dot { display: none; }
386
+ header { padding: 28px 20px 20px; }
387
+ main { padding: 8px 20px 60px; }
388
+ #back-to-top { bottom: 12px; right: 12px; }
389
+ }
390
+ /* ── Print ── */
391
+ @media print {
392
+ nav, .controls-bar, #back-to-top, .no-results { display: none !important; }
393
+ .layout { grid-template-columns: 1fr; padding: 0; }
394
+ .card { box-shadow: none; break-inside: avoid; border: 1px solid #ddd; }
395
+ .diagram-card { box-shadow: none; }
396
+ body { background: #fff; }
397
+ header { background: #fff; padding: 20px 0; }
398
+ main { padding: 0; max-width: 100%; }
399
+ a { color: #333; }
400
+ code { color: #333; }
401
+ * { color-adjust: exact; -webkit-print-color-adjust: exact; }
402
+ }
403
+ </style>
404
+ </head>
405
+ <body>
406
+ <header>
407
+ <h1>{{title}}</h1>
408
+ <div class="meta">
409
+ <span>{{meta_workspace}}: {{workspace_name}}</span>
410
+ <span>{{meta_date}}: {{date}}</span>
411
+ <span>{{meta_generated}}: {{generated_at}}</span>
412
+ </div>
413
+ </header>
414
+ <div class="layout">
415
+ <nav aria-label="{{nav_label}}">
416
+ <a href="#summary"><span class="nav-dot"></span>{{nav_summary}}</a>
417
+ <a href="#architecture"><span class="nav-dot"></span>{{nav_architecture}}</a>
418
+ <a href="#interfaces"><span class="nav-dot"></span>{{nav_interfaces}}</a>
419
+ <a href="#requirements"><span class="nav-dot"></span>{{nav_requirements}}</a>
420
+ <a href="#flows"><span class="nav-dot"></span>{{nav_flows}}</a>
421
+ <a href="#domain"><span class="nav-dot"></span>{{nav_domain}}</a>
422
+ <a href="#security"><span class="nav-dot"></span>{{nav_security}}</a>
423
+ <a href="#errors"><span class="nav-dot"></span>{{nav_errors}}</a>
424
+ <a href="#nonfunctional"><span class="nav-dot"></span>{{nav_nonfunctional}}</a>
425
+ <a href="#questions"><span class="nav-dot"></span>{{nav_questions}}</a>
426
+ <a href="#evidence"><span class="nav-dot"></span>{{nav_evidence}}</a>
427
+ </nav>
428
+ <main>
429
+ <div class="controls">
430
+ <div class="controls-bar">
431
+ <input id="report-search" type="search" placeholder="{{search_placeholder}}">
432
+ <button id="toggle-details" type="button">{{expand_all}}</button>
433
+ <button id="show-questions" type="button">{{questions_only}}</button>
434
+ </div>
435
+ <p class="no-results" id="no-results">{{no_results}}</p>
436
+ </div>
437
+ <section id="summary">
438
+ <h2>{{heading_summary}}</h2>
439
+ <!-- REQUIREMENTS_SUMMARY -->
440
+ <p class="placeholder">{{pending_summary}}</p>
441
+ <!-- /REQUIREMENTS_SUMMARY -->
442
+ </section>
443
+ <section id="architecture">
444
+ <h2>{{heading_architecture}}</h2>
445
+ <!-- REQUIREMENTS_ARCHITECTURE -->
446
+ <p class="placeholder">{{pending_architecture}}</p>
447
+ <!-- /REQUIREMENTS_ARCHITECTURE -->
448
+ </section>
449
+ <section id="interfaces">
450
+ <h2>{{heading_interfaces}}</h2>
451
+ <!-- REQUIREMENTS_INTERFACE_INVENTORY -->
452
+ <p class="placeholder">{{pending_interfaces}}</p>
453
+ <!-- /REQUIREMENTS_INTERFACE_INVENTORY -->
454
+ </section>
455
+ <section id="requirements">
456
+ <h2>{{heading_requirements}}</h2>
457
+ <!-- REQUIREMENTS_API_CARDS -->
458
+ <p class="placeholder">{{pending_requirements}}</p>
459
+ <!-- /REQUIREMENTS_API_CARDS -->
460
+ </section>
461
+ <section id="flows">
462
+ <h2>{{heading_flows}}</h2>
463
+ <!-- REQUIREMENTS_FLOWS -->
464
+ <p class="placeholder">{{pending_flows}}</p>
465
+ <!-- /REQUIREMENTS_FLOWS -->
466
+ </section>
467
+ <section id="domain">
468
+ <h2>{{heading_domain}}</h2>
469
+ <!-- REQUIREMENTS_DOMAIN_MODEL -->
470
+ <p class="placeholder">{{pending_domain}}</p>
471
+ <!-- /REQUIREMENTS_DOMAIN_MODEL -->
472
+ </section>
473
+ <section id="security">
474
+ <h2>{{heading_security}}</h2>
475
+ <!-- REQUIREMENTS_SECURITY -->
476
+ <p class="placeholder">{{pending_security}}</p>
477
+ <!-- /REQUIREMENTS_SECURITY -->
478
+ </section>
479
+ <section id="errors">
480
+ <h2>{{heading_errors}}</h2>
481
+ <!-- REQUIREMENTS_ERROR_HANDLING -->
482
+ <p class="placeholder">{{pending_errors}}</p>
483
+ <!-- /REQUIREMENTS_ERROR_HANDLING -->
484
+ </section>
485
+ <section id="nonfunctional">
486
+ <h2>{{heading_nonfunctional}}</h2>
487
+ <!-- REQUIREMENTS_NONFUNCTIONAL -->
488
+ <p class="placeholder">{{pending_nonfunctional}}</p>
489
+ <!-- /REQUIREMENTS_NONFUNCTIONAL -->
490
+ </section>
491
+ <section id="questions">
492
+ <h2>{{heading_questions}}</h2>
493
+ <!-- REQUIREMENTS_OPEN_QUESTIONS -->
494
+ <p class="placeholder">{{pending_questions}}</p>
495
+ <!-- /REQUIREMENTS_OPEN_QUESTIONS -->
496
+ </section>
497
+ <section id="evidence">
498
+ <h2>{{heading_evidence}}</h2>
499
+ <!-- REQUIREMENTS_EVIDENCE_INDEX -->
500
+ <p class="placeholder">{{pending_evidence}}</p>
501
+ <!-- /REQUIREMENTS_EVIDENCE_INDEX -->
502
+ </section>
503
+ </main>
504
+ </div>
505
+ <a href="#" id="back-to-top">{{back_to_top}}</a>
506
+ <script>
507
+ (() => {
508
+ const search = document.querySelector('#report-search');
509
+ const noResults = document.querySelector('#no-results');
510
+ const sections = document.querySelectorAll('section[id]');
511
+ const cards = document.querySelectorAll('.card, details');
512
+ const tables = document.querySelectorAll('table');
513
+ const navLinks = document.querySelectorAll('nav a');
514
+
515
+ /* ── Scroll spy ── */
516
+ const observer = new IntersectionObserver(entries => {
517
+ entries.forEach(e => {
518
+ if (e.isIntersecting) {
519
+ navLinks.forEach(l => l.classList.toggle('active', l.getAttribute('href') === '#' + e.target.id));
520
+ }
521
+ });
522
+ }, { rootMargin: '-80px 0px -70% 0px', threshold: 0 });
523
+ sections.forEach(s => observer.observe(s));
524
+
525
+ /* ── Search ── */
526
+ search?.addEventListener('input', () => {
527
+ const query = search.value.trim().toLowerCase();
528
+ let anyVisible = false;
529
+ if (!query) {
530
+ cards.forEach(c => c.style.display = '');
531
+ tables.forEach(t => t.style.display = '');
532
+ sections.forEach(s => s.style.display = '');
533
+ noResults.style.display = 'none';
534
+ return;
535
+ }
536
+ sections.forEach(s => {
537
+ const inner = s.querySelectorAll('.card, details, table');
538
+ let sectionVisible = false;
539
+ inner.forEach(el => {
540
+ const matched = el.textContent.toLowerCase().includes(query);
541
+ el.style.display = matched ? '' : 'none';
542
+ if (matched) sectionVisible = true;
543
+ });
544
+ s.style.display = sectionVisible ? '' : 'none';
545
+ if (sectionVisible) anyVisible = true;
546
+ });
547
+ noResults.style.display = anyVisible ? 'none' : 'block';
548
+ });
549
+
550
+ /* ── Expand / Collapse all ── */
551
+ const toggleBtn = document.querySelector('#toggle-details');
552
+ let allOpen = false;
553
+ toggleBtn?.addEventListener('click', () => {
554
+ allOpen = !allOpen;
555
+ document.querySelectorAll('details.card').forEach(d => d.open = allOpen);
556
+ toggleBtn.textContent = allOpen ? '{{collapse_all}}' : '{{expand_all}}';
557
+ });
558
+
559
+ /* ── Show questions only ── */
560
+ const questionsBtn = document.querySelector('#show-questions');
561
+ let questionsOnly = false;
562
+ questionsBtn?.addEventListener('click', () => {
563
+ questionsOnly = !questionsOnly;
564
+ const target = document.querySelector('#questions');
565
+ if (questionsOnly) {
566
+ sections.forEach(s => s.style.display = s === target ? '' : 'none');
567
+ questionsBtn.textContent = '{{show_all_sections}}';
568
+ } else {
569
+ sections.forEach(s => s.style.display = '');
570
+ questionsBtn.textContent = '{{questions_only}}';
571
+ }
572
+ });
573
+
574
+ /* ── Back to top ── */
575
+ const backToTop = document.querySelector('#back-to-top');
576
+ window.addEventListener('scroll', () => {
577
+ backToTop?.classList.toggle('visible', window.scrollY > 400);
578
+ }, { passive: true });
579
+ })();
580
+ </script>
581
+ </body>
582
+ </html>