living-ai-documentation 1.0.0

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 (203) hide show
  1. package/LICENSE +661 -0
  2. package/README.fr.md +344 -0
  3. package/README.md +344 -0
  4. package/dist/bin/cli.d.ts +3 -0
  5. package/dist/bin/cli.d.ts.map +1 -0
  6. package/dist/bin/cli.js +262 -0
  7. package/dist/bin/cli.js.map +1 -0
  8. package/dist/src/frontend/accuracy-gauge.js +70 -0
  9. package/dist/src/frontend/admin.html +1532 -0
  10. package/dist/src/frontend/annotations.js +585 -0
  11. package/dist/src/frontend/boot.js +101 -0
  12. package/dist/src/frontend/config.js +29 -0
  13. package/dist/src/frontend/confirm-modal.js +82 -0
  14. package/dist/src/frontend/context.html +1252 -0
  15. package/dist/src/frontend/dark-mode.js +20 -0
  16. package/dist/src/frontend/diagram/alignment.js +161 -0
  17. package/dist/src/frontend/diagram/clipboard.js +187 -0
  18. package/dist/src/frontend/diagram/constants.js +109 -0
  19. package/dist/src/frontend/diagram/custom-shapes.js +104 -0
  20. package/dist/src/frontend/diagram/debug.js +43 -0
  21. package/dist/src/frontend/diagram/drawio-export.js +649 -0
  22. package/dist/src/frontend/diagram/edge-panel.js +293 -0
  23. package/dist/src/frontend/diagram/edge-rendering.js +12 -0
  24. package/dist/src/frontend/diagram/evidence.js +146 -0
  25. package/dist/src/frontend/diagram/grid.js +78 -0
  26. package/dist/src/frontend/diagram/groups.js +102 -0
  27. package/dist/src/frontend/diagram/history.js +157 -0
  28. package/dist/src/frontend/diagram/image-name-modal.js +48 -0
  29. package/dist/src/frontend/diagram/image-upload.js +36 -0
  30. package/dist/src/frontend/diagram/label-editor.js +115 -0
  31. package/dist/src/frontend/diagram/link-panel.js +144 -0
  32. package/dist/src/frontend/diagram/main.js +364 -0
  33. package/dist/src/frontend/diagram/network.js +2214 -0
  34. package/dist/src/frontend/diagram/node-panel.js +389 -0
  35. package/dist/src/frontend/diagram/node-rendering.js +964 -0
  36. package/dist/src/frontend/diagram/persistence.js +168 -0
  37. package/dist/src/frontend/diagram/ports.js +421 -0
  38. package/dist/src/frontend/diagram/selection-overlay.js +387 -0
  39. package/dist/src/frontend/diagram/state.js +43 -0
  40. package/dist/src/frontend/diagram/t.js +3 -0
  41. package/dist/src/frontend/diagram/toast.js +21 -0
  42. package/dist/src/frontend/diagram/unlock-hold.js +206 -0
  43. package/dist/src/frontend/diagram/zoom.js +20 -0
  44. package/dist/src/frontend/diagram-link-modal.js +137 -0
  45. package/dist/src/frontend/diagram.html +1494 -0
  46. package/dist/src/frontend/documents.js +479 -0
  47. package/dist/src/frontend/export.js +338 -0
  48. package/dist/src/frontend/file-attach.js +178 -0
  49. package/dist/src/frontend/files-modal.js +243 -0
  50. package/dist/src/frontend/i18n/en.json +624 -0
  51. package/dist/src/frontend/i18n/fr.json +624 -0
  52. package/dist/src/frontend/i18n.js +32 -0
  53. package/dist/src/frontend/image-paste.js +126 -0
  54. package/dist/src/frontend/index.html +2806 -0
  55. package/dist/src/frontend/local-search.js +476 -0
  56. package/dist/src/frontend/metadata.js +318 -0
  57. package/dist/src/frontend/misc.js +92 -0
  58. package/dist/src/frontend/new-doc-modal.js +285 -0
  59. package/dist/src/frontend/new-folder-modal.js +169 -0
  60. package/dist/src/frontend/search.js +194 -0
  61. package/dist/src/frontend/shape-editor.html +685 -0
  62. package/dist/src/frontend/sidebar-helpers.js +96 -0
  63. package/dist/src/frontend/sidebar-resize.js +98 -0
  64. package/dist/src/frontend/sidebar.js +351 -0
  65. package/dist/src/frontend/snippet-detect.js +25 -0
  66. package/dist/src/frontend/snippet-table.js +85 -0
  67. package/dist/src/frontend/snippet-tree.js +94 -0
  68. package/dist/src/frontend/snippets.js +1146 -0
  69. package/dist/src/frontend/state.js +46 -0
  70. package/dist/src/frontend/utils.js +21 -0
  71. package/dist/src/frontend/validate.js +107 -0
  72. package/dist/src/frontend/vendor/wordcloud2.js +1187 -0
  73. package/dist/src/frontend/wordcloud.js +693 -0
  74. package/dist/src/lib/config.d.ts +26 -0
  75. package/dist/src/lib/config.d.ts.map +1 -0
  76. package/dist/src/lib/config.js +195 -0
  77. package/dist/src/lib/config.js.map +1 -0
  78. package/dist/src/lib/hash.d.ts +2 -0
  79. package/dist/src/lib/hash.d.ts.map +1 -0
  80. package/dist/src/lib/hash.js +18 -0
  81. package/dist/src/lib/hash.js.map +1 -0
  82. package/dist/src/lib/metadata.d.ts +31 -0
  83. package/dist/src/lib/metadata.d.ts.map +1 -0
  84. package/dist/src/lib/metadata.js +128 -0
  85. package/dist/src/lib/metadata.js.map +1 -0
  86. package/dist/src/lib/parser.d.ts +11 -0
  87. package/dist/src/lib/parser.d.ts.map +1 -0
  88. package/dist/src/lib/parser.js +111 -0
  89. package/dist/src/lib/parser.js.map +1 -0
  90. package/dist/src/lib/status.d.ts +9 -0
  91. package/dist/src/lib/status.d.ts.map +1 -0
  92. package/dist/src/lib/status.js +72 -0
  93. package/dist/src/lib/status.js.map +1 -0
  94. package/dist/src/mcp/server.d.ts +3 -0
  95. package/dist/src/mcp/server.d.ts.map +1 -0
  96. package/dist/src/mcp/server.js +2046 -0
  97. package/dist/src/mcp/server.js.map +1 -0
  98. package/dist/src/mcp/tools/diagrams.d.ts +82 -0
  99. package/dist/src/mcp/tools/diagrams.d.ts.map +1 -0
  100. package/dist/src/mcp/tools/diagrams.js +594 -0
  101. package/dist/src/mcp/tools/diagrams.js.map +1 -0
  102. package/dist/src/mcp/tools/documents.d.ts +44 -0
  103. package/dist/src/mcp/tools/documents.d.ts.map +1 -0
  104. package/dist/src/mcp/tools/documents.js +186 -0
  105. package/dist/src/mcp/tools/documents.js.map +1 -0
  106. package/dist/src/mcp/tools/git.d.ts +10 -0
  107. package/dist/src/mcp/tools/git.d.ts.map +1 -0
  108. package/dist/src/mcp/tools/git.js +217 -0
  109. package/dist/src/mcp/tools/git.js.map +1 -0
  110. package/dist/src/mcp/tools/metadata.d.ts +57 -0
  111. package/dist/src/mcp/tools/metadata.d.ts.map +1 -0
  112. package/dist/src/mcp/tools/metadata.js +222 -0
  113. package/dist/src/mcp/tools/metadata.js.map +1 -0
  114. package/dist/src/mcp/tools/source.d.ts +29 -0
  115. package/dist/src/mcp/tools/source.d.ts.map +1 -0
  116. package/dist/src/mcp/tools/source.js +196 -0
  117. package/dist/src/mcp/tools/source.js.map +1 -0
  118. package/dist/src/routes/annotations.d.ts +3 -0
  119. package/dist/src/routes/annotations.d.ts.map +1 -0
  120. package/dist/src/routes/annotations.js +83 -0
  121. package/dist/src/routes/annotations.js.map +1 -0
  122. package/dist/src/routes/browse-source.d.ts +3 -0
  123. package/dist/src/routes/browse-source.d.ts.map +1 -0
  124. package/dist/src/routes/browse-source.js +79 -0
  125. package/dist/src/routes/browse-source.js.map +1 -0
  126. package/dist/src/routes/browse.d.ts +3 -0
  127. package/dist/src/routes/browse.d.ts.map +1 -0
  128. package/dist/src/routes/browse.js +91 -0
  129. package/dist/src/routes/browse.js.map +1 -0
  130. package/dist/src/routes/config.d.ts +3 -0
  131. package/dist/src/routes/config.d.ts.map +1 -0
  132. package/dist/src/routes/config.js +145 -0
  133. package/dist/src/routes/config.js.map +1 -0
  134. package/dist/src/routes/context.d.ts +3 -0
  135. package/dist/src/routes/context.d.ts.map +1 -0
  136. package/dist/src/routes/context.js +287 -0
  137. package/dist/src/routes/context.js.map +1 -0
  138. package/dist/src/routes/diagrams.d.ts +3 -0
  139. package/dist/src/routes/diagrams.d.ts.map +1 -0
  140. package/dist/src/routes/diagrams.js +69 -0
  141. package/dist/src/routes/diagrams.js.map +1 -0
  142. package/dist/src/routes/documents.d.ts +11 -0
  143. package/dist/src/routes/documents.d.ts.map +1 -0
  144. package/dist/src/routes/documents.js +450 -0
  145. package/dist/src/routes/documents.js.map +1 -0
  146. package/dist/src/routes/export.d.ts +3 -0
  147. package/dist/src/routes/export.d.ts.map +1 -0
  148. package/dist/src/routes/export.js +280 -0
  149. package/dist/src/routes/export.js.map +1 -0
  150. package/dist/src/routes/files.d.ts +3 -0
  151. package/dist/src/routes/files.d.ts.map +1 -0
  152. package/dist/src/routes/files.js +180 -0
  153. package/dist/src/routes/files.js.map +1 -0
  154. package/dist/src/routes/images.d.ts +3 -0
  155. package/dist/src/routes/images.d.ts.map +1 -0
  156. package/dist/src/routes/images.js +49 -0
  157. package/dist/src/routes/images.js.map +1 -0
  158. package/dist/src/routes/metadata.d.ts +3 -0
  159. package/dist/src/routes/metadata.d.ts.map +1 -0
  160. package/dist/src/routes/metadata.js +131 -0
  161. package/dist/src/routes/metadata.js.map +1 -0
  162. package/dist/src/routes/shape-libraries.d.ts +3 -0
  163. package/dist/src/routes/shape-libraries.d.ts.map +1 -0
  164. package/dist/src/routes/shape-libraries.js +118 -0
  165. package/dist/src/routes/shape-libraries.js.map +1 -0
  166. package/dist/src/routes/wordcloud.d.ts +3 -0
  167. package/dist/src/routes/wordcloud.d.ts.map +1 -0
  168. package/dist/src/routes/wordcloud.js +95 -0
  169. package/dist/src/routes/wordcloud.js.map +1 -0
  170. package/dist/src/server.d.ts +7 -0
  171. package/dist/src/server.d.ts.map +1 -0
  172. package/dist/src/server.js +93 -0
  173. package/dist/src/server.js.map +1 -0
  174. package/dist/starter-doc/.living-doc.json +52 -0
  175. package/dist/starter-doc/ADRS/2026_01_01_[ADR]_example_architecture_decision.md +59 -0
  176. package/dist/starter-doc/AI/2026_01_01_how_to.md +112 -0
  177. package/dist/starter-doc/AI/PROJECT-INSTRUCTIONS.md +172 -0
  178. package/dist/starter-doc/AI/PROJECT-STACK.md +77 -0
  179. package/dist/starter-doc/AI/PROJECT-USEFUL-COMMANDS.md +80 -0
  180. package/dist/starter-doc/AI/default/AGENTS.md +31 -0
  181. package/dist/starter-doc/AI/default/CLAUDE.md +31 -0
  182. package/dist/starter-doc/AI/default/MEMORY.md +24 -0
  183. package/dist/starter-doc/AI/rules/no-magic-numbers.md +18 -0
  184. package/dist/starter-doc/AI/rules/track-current-work.md +23 -0
  185. package/dist/starter-doc/WORKLOG/current-task.md +57 -0
  186. package/dist/starter-doc-fr/.living-doc.json +52 -0
  187. package/dist/starter-doc-fr/ADRS/2026_01_01_[ADR]_example_architecture_decision.md +59 -0
  188. package/dist/starter-doc-fr/AI/2026_01_01_how_to.md +100 -0
  189. package/dist/starter-doc-fr/AI/PROJECT-INSTRUCTIONS.md +172 -0
  190. package/dist/starter-doc-fr/AI/PROJECT-STACK.md +77 -0
  191. package/dist/starter-doc-fr/AI/PROJECT-USEFUL-COMMANDS.md +80 -0
  192. package/dist/starter-doc-fr/AI/default/AGENTS.md +31 -0
  193. package/dist/starter-doc-fr/AI/default/CLAUDE.md +31 -0
  194. package/dist/starter-doc-fr/AI/default/MEMORY.md +24 -0
  195. package/dist/starter-doc-fr/AI/rules/no-magic-numbers.md +18 -0
  196. package/dist/starter-doc-fr/AI/rules/track-current-work.md +23 -0
  197. package/dist/starter-doc-fr/WORKLOG/current-task.md +57 -0
  198. package/images/living_documentation.jpg +0 -0
  199. package/images/readme-extra-files.png +0 -0
  200. package/images/readme-filename-pattern.png +0 -0
  201. package/images/readme-intelligent-search-demo.jpg +0 -0
  202. package/images/readme-sidebar.png +0 -0
  203. package/package.json +72 -0
@@ -0,0 +1,2806 @@
1
+ <!doctype html>
2
+ <html lang="en" class="h-full">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Living Documentation</title>
7
+
8
+ <!-- i18n -->
9
+ <script src="/i18n.js"></script>
10
+
11
+ <!-- Tailwind CSS + Typography plugin via CDN -->
12
+ <script src="https://cdn.tailwindcss.com?plugins=typography"></script>
13
+
14
+ <!-- Highlight.js — syntax highlighting (always dark) -->
15
+ <link
16
+ rel="stylesheet"
17
+ href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css"
18
+ />
19
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
20
+
21
+ <!-- Font Awesome — icons -->
22
+ <link
23
+ rel="stylesheet"
24
+ href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css"
25
+ />
26
+
27
+ <!-- WordCloud2.js — vendored from npm, no CDN dependency -->
28
+ <script src="/vendor/wordcloud2.js"></script>
29
+ <!-- Word Cloud logic — stop words, browser, rendering -->
30
+ <script src="/wordcloud.js"></script>
31
+
32
+ <!-- Extracted modules (classic scripts, global symbols).
33
+ Use `defer` so they execute in order AFTER the DOM is parsed. -->
34
+ <script defer src="/utils.js"></script>
35
+ <script defer src="/state.js"></script>
36
+ <script defer src="/sidebar-helpers.js"></script>
37
+ <script defer src="/snippet-detect.js"></script>
38
+ <script defer src="/snippet-table.js"></script>
39
+ <script defer src="/snippet-tree.js"></script>
40
+ <script defer src="/dark-mode.js"></script>
41
+ <script defer src="/config.js"></script>
42
+ <script defer src="/sidebar.js"></script>
43
+ <script defer src="/sidebar-resize.js"></script>
44
+ <script defer src="/search.js"></script>
45
+ <script defer src="/local-search.js"></script>
46
+ <script defer src="/image-paste.js"></script>
47
+ <script defer src="/file-attach.js"></script>
48
+ <script defer src="/documents.js"></script>
49
+ <script defer src="/misc.js"></script>
50
+ <script defer src="/snippets.js"></script>
51
+ <script defer src="/annotations.js"></script>
52
+ <script defer src="/metadata.js"></script>
53
+ <script defer src="/accuracy-gauge.js"></script>
54
+ <script defer src="/validate.js"></script>
55
+ <script defer src="/export.js"></script>
56
+ <script defer src="/diagram-link-modal.js"></script>
57
+ <script defer src="/new-folder-modal.js"></script>
58
+ <script defer src="/new-doc-modal.js"></script>
59
+ <script defer src="/confirm-modal.js"></script>
60
+ <script defer src="/files-modal.js"></script>
61
+ <script defer src="/boot.js"></script>
62
+
63
+ <script>
64
+ tailwind.config = {
65
+ darkMode: "class",
66
+ theme: { extend: {} },
67
+ };
68
+ </script>
69
+
70
+ <style>
71
+ /* Smooth sidebar transitions */
72
+ .category-docs {
73
+ transition:
74
+ max-height 0.2s ease,
75
+ opacity 0.2s ease;
76
+ }
77
+ .category-docs.collapsed {
78
+ max-height: 0;
79
+ opacity: 0;
80
+ overflow: hidden;
81
+ }
82
+ .category-docs.expanded {
83
+ max-height: 9999px;
84
+ opacity: 1;
85
+ }
86
+ .category-docs.no-transition {
87
+ transition: none !important;
88
+ }
89
+
90
+ /* Code block decorations (copy + collapse) */
91
+ .prose pre {
92
+ position: relative;
93
+ }
94
+ .ld-code-copy {
95
+ position: absolute;
96
+ top: 0.4rem;
97
+ right: 0.5rem;
98
+ z-index: 2;
99
+ width: 28px;
100
+ height: 28px;
101
+ display: inline-flex;
102
+ align-items: center;
103
+ justify-content: center;
104
+ border-radius: 0.375rem;
105
+ background: rgba(255, 255, 255, 0.08);
106
+ color: #d1d5db;
107
+ border: 1px solid rgba(255, 255, 255, 0.15);
108
+ cursor: pointer;
109
+ opacity: 0;
110
+ transition:
111
+ opacity 0.15s ease,
112
+ background 0.15s ease;
113
+ font-size: 0.8rem;
114
+ line-height: 1;
115
+ }
116
+ .prose pre:hover .ld-code-copy,
117
+ .ld-code-copy:focus-visible,
118
+ .ld-code-copy.ld-copied {
119
+ opacity: 1;
120
+ }
121
+ .ld-code-copy:hover {
122
+ background: rgba(255, 255, 255, 0.2);
123
+ }
124
+ .ld-code-copy.ld-copied {
125
+ background: rgba(34, 197, 94, 0.25);
126
+ color: #86efac;
127
+ border-color: rgba(34, 197, 94, 0.4);
128
+ }
129
+
130
+ /* Collapsible code blocks (set via --ld-code-max-h from .living-doc.json) */
131
+ pre.ld-collapsible {
132
+ max-height: var(--ld-code-max-h, 400px);
133
+ overflow: hidden;
134
+ position: relative;
135
+ }
136
+ pre.ld-collapsible.ld-expanded {
137
+ max-height: none;
138
+ overflow: auto;
139
+ }
140
+ pre.ld-collapsible:not(.ld-expanded)::after {
141
+ content: "";
142
+ position: absolute;
143
+ left: 0;
144
+ right: 0;
145
+ bottom: 0;
146
+ height: 64px;
147
+ pointer-events: none;
148
+ background: linear-gradient(
149
+ to bottom,
150
+ rgba(40, 44, 52, 0) 0%,
151
+ rgba(40, 44, 52, 0.85) 100%
152
+ );
153
+ }
154
+ .ld-code-toggle {
155
+ position: absolute;
156
+ right: 0.5rem;
157
+ bottom: 0.4rem;
158
+ z-index: 1;
159
+ padding: 0.15rem 0.6rem;
160
+ border-radius: 0.375rem;
161
+ background: rgba(255, 255, 255, 0.12);
162
+ color: #e5e7eb;
163
+ font-size: 0.75rem;
164
+ font-weight: 500;
165
+ border: 1px solid rgba(255, 255, 255, 0.18);
166
+ cursor: pointer;
167
+ transition: background 0.15s ease;
168
+ }
169
+ .ld-code-toggle:hover {
170
+ background: rgba(255, 255, 255, 0.22);
171
+ }
172
+
173
+ /* Color swatch selection ring */
174
+ .color-swatch-btn.selected-swatch,
175
+ .color-text-swatch-btn.selected-text-swatch {
176
+ outline: 2px solid currentColor;
177
+ outline-offset: 2px;
178
+ box-shadow: 0 0 0 3px rgba(0,0,0,0.15);
179
+ }
180
+
181
+ /* Active doc highlight */
182
+ .doc-item.active {
183
+ background-color: rgba(59, 130, 246, 0.12);
184
+ color: rgb(59, 130, 246);
185
+ font-weight: 600;
186
+ }
187
+ .dark .doc-item.active {
188
+ background-color: rgba(96, 165, 250, 0.15);
189
+ color: rgb(96, 165, 250);
190
+ }
191
+
192
+ /* Prose overrides for correct dark mode */
193
+ .dark .prose {
194
+ color: #d1d5db;
195
+ }
196
+ .dark .prose h1,
197
+ .dark .prose h2,
198
+ .dark .prose h3,
199
+ .dark .prose h4,
200
+ .dark .prose h5,
201
+ .dark .prose h6 {
202
+ color: #f9fafb;
203
+ }
204
+ .dark .prose a {
205
+ color: #60a5fa;
206
+ }
207
+ .dark .prose strong {
208
+ color: #f3f4f6;
209
+ }
210
+ .prose code:not(pre code) {
211
+ background: #e5e7eb;
212
+ color: #111827;
213
+ padding: 0.1em 0.35em;
214
+ border-radius: 0.25em;
215
+ font-size: 0.875em;
216
+ font-weight: 400;
217
+ }
218
+ .dark .prose code:not(pre code) {
219
+ background: #4b5563;
220
+ color: #f87171;
221
+ padding: 0.1em 0.35em;
222
+ border-radius: 0.25em;
223
+ font-size: 0.875em;
224
+ font-weight: 400;
225
+ }
226
+
227
+ .dark .prose blockquote {
228
+ border-color: #4b5563;
229
+ color: #9ca3af;
230
+ }
231
+
232
+ /* Image margins — inline images (in lists, after <br>, mixed with text)
233
+ collapse to zero vertical margin; only images that sit alone in their
234
+ own paragraph keep a visual breathing room. */
235
+ .prose img {
236
+ margin-top: 0;
237
+ margin-bottom: 0;
238
+ }
239
+ .prose p > img:only-child,
240
+ .prose p > a:only-child > img {
241
+ margin-top: 1em;
242
+ margin-bottom: 1em;
243
+ }
244
+
245
+ /* Horizontal rules — tame Tailwind Typography's 3em default + bump contrast */
246
+ .prose hr {
247
+ margin-top: 2em;
248
+ margin-bottom: 2em;
249
+ border-color: #d1d5db;
250
+ }
251
+
252
+ /* Local search highlight — distinct from the global yellow search */
253
+ mark.ld-local-mark {
254
+ background: #bae6fd;
255
+ color: inherit;
256
+ padding: 0 1px;
257
+ border-radius: 2px;
258
+ }
259
+ mark.ld-local-mark.ld-local-active {
260
+ background: #0284c7;
261
+ color: #fff;
262
+ }
263
+ .dark mark.ld-local-mark {
264
+ background: #075985;
265
+ color: #e0f2fe;
266
+ }
267
+ .dark mark.ld-local-mark.ld-local-active {
268
+ background: #0ea5e9;
269
+ color: #082f49;
270
+ }
271
+ .ld-local-item-active {
272
+ background: rgba(14, 165, 233, 0.12);
273
+ }
274
+
275
+ /* File attachment pills — links pointing to /files/ */
276
+ .prose a.ld-file-attachment,
277
+ a.ld-file-attachment {
278
+ display: inline-flex;
279
+ align-items: center;
280
+ gap: 0.35em;
281
+ padding: 0.1em 0.55em;
282
+ border-radius: 0.375rem;
283
+ background: #ede9fe;
284
+ color: #5b21b6;
285
+ text-decoration: none;
286
+ font-weight: 500;
287
+ border: 1px solid #ddd6fe;
288
+ transition: background-color 0.15s, border-color 0.15s;
289
+ }
290
+ .prose a.ld-file-attachment:hover,
291
+ a.ld-file-attachment:hover {
292
+ background: #ddd6fe;
293
+ border-color: #c4b5fd;
294
+ }
295
+ .dark .prose a.ld-file-attachment,
296
+ .dark a.ld-file-attachment {
297
+ background: rgba(139, 92, 246, 0.18);
298
+ color: #c4b5fd;
299
+ border-color: rgba(139, 92, 246, 0.3);
300
+ }
301
+ .dark .prose a.ld-file-attachment:hover,
302
+ .dark a.ld-file-attachment:hover {
303
+ background: rgba(139, 92, 246, 0.28);
304
+ border-color: rgba(139, 92, 246, 0.45);
305
+ }
306
+ a.ld-file-attachment .ld-file-icon {
307
+ font-size: 0.85em;
308
+ opacity: 0.85;
309
+ }
310
+ .dark .prose hr {
311
+ border-color: #374151;
312
+ }
313
+ .dark .prose table th {
314
+ background: #1f2937;
315
+ color: #f9fafb;
316
+ }
317
+ .dark .prose table td {
318
+ border-color: #374151;
319
+ }
320
+ .dark .prose table tr:nth-child(even) {
321
+ background: #111827;
322
+ }
323
+
324
+ /* Print / Export PDF */
325
+ @media print {
326
+ #sidebar,
327
+ #header {
328
+ display: none !important;
329
+ }
330
+ #content-area {
331
+ margin: 0 !important;
332
+ }
333
+ .no-print {
334
+ display: none !important;
335
+ }
336
+ .prose {
337
+ max-width: 100% !important;
338
+ }
339
+ }
340
+
341
+ /* Scrollbar thin */
342
+ ::-webkit-scrollbar {
343
+ width: 6px;
344
+ height: 6px;
345
+ }
346
+ ::-webkit-scrollbar-track {
347
+ background: transparent;
348
+ }
349
+ ::-webkit-scrollbar-thumb {
350
+ background: #d1d5db;
351
+ border-radius: 3px;
352
+ }
353
+ .dark ::-webkit-scrollbar-thumb {
354
+ background: #4b5563;
355
+ }
356
+
357
+ /* Search highlight */
358
+ mark {
359
+ background: #fef08a;
360
+ color: inherit;
361
+ border-radius: 2px;
362
+ padding: 0 2px;
363
+ }
364
+ mark.match-active {
365
+ background: #f97316;
366
+ color: #fff;
367
+ }
368
+ .dark mark {
369
+ background: #713f12;
370
+ color: #fef9c3;
371
+ }
372
+ .dark mark.match-active {
373
+ background: #ea580c;
374
+ color: #fff;
375
+ }
376
+ </style>
377
+ </head>
378
+
379
+ <body
380
+ class="h-full bg-gray-50 dark:bg-gray-950 text-gray-900 dark:text-gray-100 transition-colors duration-200"
381
+ >
382
+ <div class="h-full flex flex-col">
383
+ <!-- ── Header ── -->
384
+ <header
385
+ id="header"
386
+ class="no-print flex items-center justify-between px-4 h-14 border-b border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900 shrink-0 z-10 shadow-sm"
387
+ >
388
+ <div class="flex items-center gap-3">
389
+ <span
390
+ onclick="toggleSidebar()"
391
+ data-i18n-title="nav.toggle_sidebar"
392
+ title="Toggle sidebar"
393
+ class="text-blue-600 dark:text-blue-400 text-xl select-none cursor-pointer hover:opacity-70 transition-opacity"
394
+ >&#128218;</span
395
+ >
396
+ <span id="app-title" class="font-semibold text-base tracking-tight"
397
+ >Living Documentation</span
398
+ >
399
+ </div>
400
+
401
+ <div class="flex items-center gap-2">
402
+ <!-- Search (desktop — mirrors sidebar search) -->
403
+ <div class="relative hidden sm:block">
404
+ <input
405
+ id="header-search"
406
+ type="search"
407
+ data-i18n-placeholder="nav.search_placeholder"
408
+ placeholder="Search docs…"
409
+ class="w-56 pl-8 pr-3 py-1.5 text-sm rounded-lg border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800 text-gray-900 dark:text-gray-100 placeholder:text-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500"
410
+ />
411
+ <span
412
+ class="absolute left-2.5 top-2 text-gray-400 text-sm pointer-events-none"
413
+ >&#128269;</span
414
+ >
415
+ </div>
416
+
417
+ <!-- Dark mode toggle -->
418
+ <button
419
+ id="dark-toggle"
420
+ data-i18n-title="nav.toggle_dark"
421
+ title="Toggle dark mode"
422
+ class="p-2 rounded-lg text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors"
423
+ >
424
+ <span id="dark-icon" class="text-lg leading-none">&#9790;</span>
425
+ </button>
426
+
427
+ <!-- Word Cloud -->
428
+ <button
429
+ onclick="openWordCloud()"
430
+ data-i18n="nav.word_cloud"
431
+ class="text-sm px-3 py-1.5 rounded-lg text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors font-medium"
432
+ >
433
+ &#9729; Word Cloud
434
+ </button>
435
+
436
+ <!-- Diagram link -->
437
+ <a
438
+ href="/diagram"
439
+ data-i18n="nav.diagram"
440
+ class="text-sm px-3 py-1.5 rounded-lg text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors font-medium"
441
+ >
442
+ &#9671; Diagram
443
+ </a>
444
+
445
+ <!-- AI Context -->
446
+ <a
447
+ href="/context"
448
+ data-i18n="nav.ai_context"
449
+ class="text-sm px-3 py-1.5 rounded-lg text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors font-medium"
450
+ >
451
+ AI Context
452
+ </a>
453
+
454
+ <!-- Files -->
455
+ <button
456
+ onclick="openFilesModal()"
457
+ data-i18n="nav.files"
458
+ class="text-sm px-3 py-1.5 rounded-lg text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors font-medium"
459
+ >
460
+ &#128193; Files
461
+ </button>
462
+
463
+ <!-- Admin link -->
464
+ <a
465
+ href="/admin"
466
+ data-i18n="nav.admin"
467
+ class="text-sm px-3 py-1.5 rounded-lg text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors font-medium"
468
+ >
469
+ &#9881; Admin
470
+ </a>
471
+ </div>
472
+ </header>
473
+
474
+ <!-- ── Body ── -->
475
+ <div class="flex flex-1 overflow-hidden">
476
+ <!-- ── Sidebar ── -->
477
+ <aside
478
+ id="sidebar"
479
+ class="no-print w-72 shrink-0 flex flex-col border-r border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900 overflow-y-auto"
480
+ >
481
+ <!-- Mobile / sidebar search -->
482
+ <div class="sticky top-0 z-10 bg-white dark:bg-gray-900 p-3 border-b border-gray-100 dark:border-gray-800">
483
+ <div class="relative sm:hidden">
484
+ <input
485
+ id="sidebar-search"
486
+ type="search"
487
+ data-i18n-placeholder="nav.search_mobile_placeholder"
488
+ placeholder="Search…"
489
+ class="w-full pl-8 pr-3 py-1.5 text-sm rounded-lg border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800 text-gray-900 dark:text-gray-100 placeholder:text-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500"
490
+ />
491
+ <span
492
+ class="absolute left-2.5 top-2 text-gray-400 text-sm pointer-events-none"
493
+ >&#128269;</span
494
+ >
495
+ </div>
496
+
497
+ <!-- Stats -->
498
+ <div class="flex items-center justify-between mt-2 sm:mt-0">
499
+ <p
500
+ id="doc-count"
501
+ class="text-xs text-gray-400 dark:text-gray-500"
502
+ ></p>
503
+ <div class="flex items-center gap-2">
504
+ <button
505
+ id="toggle-attachments-btn"
506
+ onclick="toggleHideAttachments()"
507
+ data-i18n-title="nav.toggle_attachments"
508
+ title="Toggle attachments"
509
+ class="text-gray-400 hover:text-blue-500 dark:text-gray-500 dark:hover:text-blue-400 transition-colors leading-none"
510
+ >
511
+ <i class="fa-solid fa-paperclip"></i>
512
+ </button>
513
+ <button
514
+ id="toggle-categories-btn"
515
+ onclick="toggleHideCategories()"
516
+ data-i18n-title="nav.toggle_categories"
517
+ title="Toggle categories"
518
+ class="text-gray-400 hover:text-blue-500 dark:text-gray-500 dark:hover:text-blue-400 transition-colors leading-none"
519
+ >
520
+ <i class="fa-solid fa-tags"></i>
521
+ </button>
522
+ <button
523
+ onclick="openExportModal()"
524
+ data-i18n-title="nav.export"
525
+ title="Export"
526
+ class="text-gray-400 hover:text-blue-500 dark:text-gray-500 dark:hover:text-blue-400 transition-colors leading-none"
527
+ >
528
+ <i class="fa-solid fa-file-export"></i>
529
+ </button>
530
+ <button
531
+ onclick="openNewFolderModal()"
532
+ data-i18n-title="nav.new_folder"
533
+ title="New folder"
534
+ class="text-gray-400 hover:text-blue-500 dark:text-gray-500 dark:hover:text-blue-400 transition-colors leading-none"
535
+ >
536
+ <i class="fa-solid fa-folder-plus"></i>
537
+ </button>
538
+ <button
539
+ onclick="openNewDocModal()"
540
+ data-i18n-title="nav.new_document"
541
+ title="New document"
542
+ class="text-gray-400 hover:text-blue-500 dark:text-gray-500 dark:hover:text-blue-400 transition-colors leading-none"
543
+ >
544
+ <i class="fa-solid fa-file-circle-plus"></i>
545
+ </button>
546
+ </div>
547
+ </div>
548
+ </div>
549
+
550
+ <!-- Category tree -->
551
+ <nav id="category-tree" class="flex-1 py-2 space-y-0.5">
552
+ <p class="px-4 py-8 text-sm text-gray-400 text-center" data-i18n="common.loading">Loading…</p>
553
+ </nav>
554
+ </aside>
555
+
556
+ <!-- ── Main content ── -->
557
+ <main id="content-area" class="flex-1 overflow-y-auto">
558
+ <!-- Welcome / empty state -->
559
+ <div id="welcome" class="h-full flex items-center justify-center p-8">
560
+ <div class="max-w-md text-center">
561
+ <div class="text-5xl mb-4 select-none" aria-hidden="true">
562
+ &#128218;
563
+ </div>
564
+ <h2
565
+ data-i18n="welcome.title"
566
+ class="text-xl font-semibold text-gray-700 dark:text-gray-300 mb-2"
567
+ >
568
+ Select a document
569
+ </h2>
570
+ <p data-i18n="welcome.hint" class="text-sm text-gray-500 dark:text-gray-500">
571
+ Choose a document from the sidebar to start reading.
572
+ </p>
573
+ <p
574
+ id="welcome-pattern"
575
+ class="mt-4 text-xs text-gray-400 dark:text-gray-600 font-mono bg-gray-100 dark:bg-gray-800 rounded-lg px-3 py-2 inline-block"
576
+ >
577
+ YYYY_MM_DD_HH_mm_[Category]_title.md
578
+ </p>
579
+ <p data-i18n="welcome.pattern_hint" class="mt-2 text-xs text-gray-400">
580
+ Expected filename pattern
581
+ </p>
582
+ </div>
583
+ </div>
584
+
585
+ <!-- Document viewer -->
586
+ <article id="doc-view" class="hidden max-w-4xl mx-auto px-6 py-8">
587
+ <!-- Doc header -->
588
+ <header
589
+ class="sticky top-0 z-10 bg-gray-50 dark:bg-gray-950 -mx-6 px-6 pt-8 mb-8 pb-6 border-b border-gray-300 dark:border-gray-800"
590
+ >
591
+ <div class="flex items-start gap-4 flex-wrap">
592
+ <div class="shrink min-w-0">
593
+ <div class="flex items-center gap-2 mb-2 flex-wrap">
594
+ <span
595
+ id="doc-breadcrumbs"
596
+ class="flex items-center gap-2 flex-wrap"
597
+ ></span>
598
+ <span
599
+ id="doc-date"
600
+ class="text-xs text-gray-400 dark:text-gray-500"
601
+ ></span>
602
+ </div>
603
+ <div class="flex items-center gap-2">
604
+ <h1
605
+ id="doc-title"
606
+ class="min-w-0 text-2xl font-bold text-gray-900 dark:text-gray-50 leading-tight"
607
+ ></h1>
608
+ <button
609
+ type="button"
610
+ id="copy-doc-id-btn"
611
+ onclick="copyCurrentDocMcpId()"
612
+ data-i18n-title="doc.copy_mcp_id"
613
+ title="Copy MCP document id"
614
+ class="no-print inline-flex h-8 w-8 shrink-0 items-center justify-center rounded-md border border-gray-200 text-gray-500 transition-colors hover:bg-gray-50 hover:text-blue-600 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-blue-400"
615
+ >
616
+ <i class="fa-regular fa-copy" aria-hidden="true"></i>
617
+ </button>
618
+ </div>
619
+ </div>
620
+ <!-- Actions -->
621
+ <div class="flex items-center gap-2 flex-wrap ml-auto">
622
+ <!-- View mode actions -->
623
+ <div id="view-actions" class="flex items-center gap-2">
624
+ <button
625
+ onclick="validateCurrentDoc()"
626
+ id="validate-btn"
627
+ data-i18n-title="doc.validate_mode"
628
+ title="Validate document"
629
+ class="hidden no-print text-sm px-3 py-1.5 rounded-lg border border-green-700 bg-green-600 text-white font-semibold hover:bg-green-700 transition-colors"
630
+ >
631
+ <i class="fa-solid fa-check" aria-hidden="true" style="margin-right: 4px"></i>
632
+ <span data-i18n="doc.validate_btn">Validate</span>
633
+ </button>
634
+ <button
635
+ onclick="toggleMarker()"
636
+ id="stabilo-btn"
637
+ data-i18n-title="doc.marker_mode"
638
+ title="Mode Marker — surligner pour annoter"
639
+ class="no-print text-sm px-3 py-1.5 rounded-lg border border-gray-200 dark:border-gray-700 text-gray-600 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors"
640
+ >
641
+ <svg
642
+ id="stabilo-icon"
643
+ xmlns="http://www.w3.org/2000/svg"
644
+ width="18"
645
+ height="18"
646
+ viewBox="0 0 100 100"
647
+ fill="none"
648
+ style="
649
+ display: inline-block;
650
+ vertical-align: middle;
651
+ margin-right: 4px;
652
+ "
653
+ >
654
+ <!-- body -->
655
+ <rect
656
+ x="28"
657
+ y="10"
658
+ width="44"
659
+ height="52"
660
+ rx="6"
661
+ transform="rotate(40 50 50)"
662
+ fill="#bfdbfe"
663
+ stroke="currentColor"
664
+ stroke-width="6"
665
+ />
666
+ <!-- cap -->
667
+ <rect
668
+ x="52"
669
+ y="8"
670
+ width="22"
671
+ height="30"
672
+ rx="6"
673
+ transform="rotate(40 50 50)"
674
+ fill="#93c5fd"
675
+ stroke="currentColor"
676
+ stroke-width="6"
677
+ />
678
+ <!-- nib -->
679
+ <polygon
680
+ points="28,60 10,80 30,80 38,72"
681
+ fill="#93c5fd"
682
+ stroke="currentColor"
683
+ stroke-width="5"
684
+ stroke-linejoin="round"
685
+ />
686
+ <polygon
687
+ points="28,48 19,70 36,75 55,70"
688
+ fill="#93c5fd"
689
+ stroke="currentColor"
690
+ stroke-width="5"
691
+ stroke-linejoin="round"
692
+ />
693
+ <!-- underline stroke -->
694
+ <line
695
+ x1="10"
696
+ y1="90"
697
+ x2="72"
698
+ y2="90"
699
+ stroke="currentColor"
700
+ stroke-width="6"
701
+ stroke-linecap="round"
702
+ />
703
+ <!-- hidden cross (two diagonal bars) -->
704
+ <g id="stabilo-cross" style="display: none">
705
+ <line
706
+ x1="8"
707
+ y1="8"
708
+ x2="92"
709
+ y2="92"
710
+ stroke="currentColor"
711
+ stroke-width="8"
712
+ stroke-linecap="round"
713
+ />
714
+ <line
715
+ x1="92"
716
+ y1="8"
717
+ x2="8"
718
+ y2="92"
719
+ stroke="currentColor"
720
+ stroke-width="8"
721
+ stroke-linecap="round"
722
+ />
723
+ </g></svg
724
+ ><span data-i18n="doc.marker_btn">Marker</span>
725
+ </button>
726
+ <button
727
+ onclick="toggleFullWidth()"
728
+ id="full-width-btn"
729
+ data-i18n-title="doc.toggle_width"
730
+ title="Toggle full width"
731
+ class="no-print text-sm px-3 py-1.5 rounded-lg border border-gray-200 dark:border-gray-700 text-gray-600 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors"
732
+ >
733
+ <span data-i18n="doc.full_width_btn">&#8596; Full width</span>
734
+ </button>
735
+ <button
736
+ onclick="exportPDF()"
737
+ data-i18n-title="doc.export_pdf"
738
+ title="Export as PDF"
739
+ class="no-print text-sm px-3 py-1.5 rounded-lg border border-gray-200 dark:border-gray-700 text-gray-600 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors"
740
+ >
741
+ &#128196; <span data-i18n="doc.export_pdf_btn">Export PDF</span>
742
+ </button>
743
+ <button
744
+ onclick="copyLink()"
745
+ id="copy-link-btn"
746
+ data-i18n-title="doc.copy_link"
747
+ title="Copy link"
748
+ class="no-print text-sm px-3 py-1.5 rounded-lg border border-gray-200 dark:border-gray-700 text-gray-600 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors"
749
+ >
750
+ <i class="fa-solid fa-link"></i> <span data-i18n="doc.copy_link_btn">Copy link</span>
751
+ </button>
752
+ <button
753
+ onclick="openMetadataModal()"
754
+ id="metadata-btn"
755
+ data-i18n-title="metadata.button_title"
756
+ title="Manage source-file metadata"
757
+ class="no-print text-sm px-3 py-1.5 rounded-lg border border-gray-200 dark:border-gray-700 text-gray-600 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors"
758
+ >
759
+ <i class="fa-solid fa-code-compare"></i> <span data-i18n="metadata.button">Metadata</span>
760
+ </button>
761
+ <button
762
+ onclick="enterEditMode()"
763
+ data-i18n-title="doc.edit"
764
+ title="Edit document"
765
+ class="no-print text-sm px-3 py-1.5 rounded-lg border border-gray-200 dark:border-gray-700 text-gray-600 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors"
766
+ >
767
+ <i class="fa-solid fa-file-pen"></i> <span data-i18n="doc.edit_btn">Edit</span>
768
+ </button>
769
+ <button
770
+ onclick="askDeleteDocument()"
771
+ data-i18n-title="doc.delete"
772
+ title="Delete document"
773
+ class="no-print text-sm px-3 py-1.5 rounded-lg border border-red-200 dark:border-red-700 text-red-500 dark:text-red-400 hover:bg-red-50 dark:hover:bg-red-900/40 transition-colors"
774
+ >
775
+ <i class="fa-solid fa-trash"></i> <span data-i18n="doc.delete_btn">Delete</span>
776
+ </button>
777
+ </div>
778
+ <!-- Edit mode actions -->
779
+ <div id="edit-actions" class="hidden flex items-center gap-2">
780
+ <span id="edit-save-msg" class="text-xs"></span>
781
+ <button
782
+ onclick="exitEditMode()"
783
+ data-i18n-title="doc.cancel_edit"
784
+ title="Cancel editing"
785
+ class="text-sm px-3 py-1.5 rounded-lg border border-gray-200 dark:border-gray-700 text-gray-600 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors"
786
+ >
787
+ <span data-i18n="common.cancel">Cancel</span>
788
+ </button>
789
+ <button
790
+ onclick="openSnippetsModal()"
791
+ data-i18n-title="doc.snippets"
792
+ title="Insert a snippet"
793
+ class="text-sm px-3 py-1.5 rounded-lg border border-gray-200 dark:border-gray-700 text-gray-600 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors"
794
+ >
795
+ <span data-i18n="doc.snippets_btn">🧩 Snippets</span>
796
+ </button>
797
+ <button
798
+ onclick="saveDocument()"
799
+ data-i18n-title="doc.save"
800
+ title="Save document"
801
+ class="text-sm px-4 py-1.5 rounded-lg bg-blue-600 hover:bg-blue-700 text-white font-semibold transition-colors"
802
+ >
803
+ <span data-i18n="common.save">Save</span>
804
+ </button>
805
+ </div>
806
+ </div>
807
+ </div>
808
+ <!-- Accuracy gauge (shown when the current doc has metadata entries) -->
809
+ <div
810
+ id="accuracy-gauge"
811
+ onclick="openMetadataModal()"
812
+ class="hidden no-print mt-5 flex items-center gap-3 mr-auto w-full sm:w-80 cursor-pointer select-none"
813
+ >
814
+ <span
815
+ id="accuracy-gauge-label"
816
+ data-i18n="accuracy.label"
817
+ class="text-xs text-gray-500 dark:text-gray-400 shrink-0"
818
+ >Reliability</span>
819
+ <div
820
+ class="flex-1 h-4 rounded-full bg-gray-200 dark:bg-gray-700 overflow-hidden"
821
+ >
822
+ <div
823
+ id="accuracy-gauge-bar"
824
+ class="h-full w-full transition-all"
825
+ style="background: #dc2626;"
826
+ ></div>
827
+ </div>
828
+ <span
829
+ id="accuracy-gauge-value"
830
+ class="text-xs font-semibold shrink-0 tabular-nums"
831
+ style="color: #9ca3af"
832
+ >0%</span>
833
+ </div>
834
+
835
+ <!-- Navigation history back-links -->
836
+ <div
837
+ id="doc-back"
838
+ class="hidden mt-2 flex flex-wrap gap-1 text-xs"
839
+ ></div>
840
+ <!-- Search match notice (inside sticky header) -->
841
+ <div
842
+ id="search-notice"
843
+ class="hidden mt-4 rounded-lg bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800 text-sm text-yellow-800 dark:text-yellow-300 overflow-hidden"
844
+ >
845
+ <div
846
+ id="search-notice-title"
847
+ class="px-3 py-2 font-medium border-b border-yellow-200 dark:border-yellow-800"
848
+ ></div>
849
+ <ol
850
+ id="search-notice-list"
851
+ class="max-h-40 overflow-y-auto divide-y divide-yellow-100 dark:divide-yellow-900/40 list-none m-0 p-0"
852
+ ></ol>
853
+ </div>
854
+ <!-- Local search widget mount point (populated by local-search.js) -->
855
+ <div id="local-search-mount" class="hidden no-print"></div>
856
+ </header>
857
+
858
+ <!-- Rendered markdown -->
859
+ <div
860
+ id="doc-content"
861
+ class="prose prose-gray dark:prose-invert max-w-none prose-headings:scroll-mt-4 prose-a:text-blue-600 dark:prose-a:text-blue-400 prose-code:before:content-none prose-code:after:content-none prose-pre:bg-[#0d1117] prose-pre:border prose-pre:border-gray-700"
862
+ ></div>
863
+
864
+ <!-- Markdown editor -->
865
+ <textarea
866
+ id="doc-editor"
867
+ class="hidden w-full min-h-[70vh] px-4 py-3 text-sm font-mono leading-relaxed rounded-lg border border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-900 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500 resize-y"
868
+ spellcheck="false"
869
+ ></textarea>
870
+ </article>
871
+ </main>
872
+ </div>
873
+ <!-- end body row -->
874
+ </div>
875
+ <!-- end root -->
876
+
877
+ <!-- ── Metadata modal ── -->
878
+ <div
879
+ id="metadata-modal"
880
+ class="hidden fixed inset-0 z-50 flex items-center justify-center bg-black/50"
881
+ >
882
+ <div
883
+ class="bg-white dark:bg-gray-900 rounded-xl shadow-xl w-full max-w-2xl mx-4 p-6 space-y-4"
884
+ >
885
+ <div class="flex items-start justify-between gap-4">
886
+ <div>
887
+ <h3 class="text-base font-semibold text-gray-900 dark:text-gray-50">
888
+ <i class="fa-solid fa-code-compare mr-1"></i>
889
+ <span data-i18n="metadata.title">Source metadata</span>
890
+ </h3>
891
+ <p
892
+ data-i18n="metadata.subtitle"
893
+ class="text-xs text-gray-500 dark:text-gray-400 mt-1"
894
+ >
895
+ Attach source files to track whether this document is still accurate.
896
+ </p>
897
+ </div>
898
+ <button
899
+ onclick="closeMetadataModal()"
900
+ data-i18n-title="common.close"
901
+ title="Close"
902
+ class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-200 transition-colors"
903
+ >
904
+ <i class="fa-solid fa-xmark text-lg"></i>
905
+ </button>
906
+ </div>
907
+
908
+ <!-- Summary -->
909
+ <div
910
+ id="metadata-summary"
911
+ class="text-xs text-gray-600 dark:text-gray-300 bg-gray-50 dark:bg-gray-800 rounded-lg px-3 py-2"
912
+ ></div>
913
+
914
+ <!-- Read-only banner (SuperSeeded documents) -->
915
+ <div
916
+ id="metadata-readonly-banner"
917
+ data-i18n="metadata.readonly_banner"
918
+ class="hidden text-xs text-amber-800 dark:text-amber-200 bg-amber-50 dark:bg-amber-900/30 border-l-4 border-amber-500 rounded-r px-3 py-2"
919
+ >
920
+ Read-only — this document is SuperSeeded. Source metadata can still be viewed but not edited.
921
+ </div>
922
+
923
+ <!-- List -->
924
+ <div class="border border-gray-200 dark:border-gray-700 rounded-lg overflow-hidden">
925
+ <div
926
+ id="metadata-list"
927
+ class="max-h-64 overflow-y-auto"
928
+ ></div>
929
+ <div
930
+ id="metadata-empty"
931
+ data-i18n="metadata.empty"
932
+ class="hidden px-3 py-4 text-center text-xs text-gray-400"
933
+ >
934
+ No metadata yet — add a source file to start tracking accuracy.
935
+ </div>
936
+ </div>
937
+
938
+ <!-- Actions -->
939
+ <div class="flex items-center gap-2 flex-wrap">
940
+ <button
941
+ onclick="metadataToggleBrowser()"
942
+ id="metadata-add-btn"
943
+ class="text-sm px-3 py-1.5 rounded-lg border border-blue-200 dark:border-blue-700 text-blue-600 dark:text-blue-400 hover:bg-blue-50 dark:hover:bg-blue-900/30 transition-colors"
944
+ >
945
+ <i class="fa-solid fa-plus"></i> <span data-i18n="metadata.add">Add source file</span>
946
+ </button>
947
+ <button
948
+ onclick="metadataRefresh()"
949
+ id="metadata-refresh-btn"
950
+ class="text-sm px-3 py-1.5 rounded-lg border border-gray-200 dark:border-gray-700 text-gray-600 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors"
951
+ >
952
+ <i class="fa-solid fa-arrows-rotate"></i> <span data-i18n="metadata.refresh">Refresh hashes</span>
953
+ </button>
954
+ </div>
955
+
956
+ <!-- Source browser -->
957
+ <div
958
+ id="metadata-browser"
959
+ class="hidden border border-gray-200 dark:border-gray-700 rounded-lg overflow-hidden text-sm"
960
+ >
961
+ <div
962
+ class="flex items-center gap-2 px-3 py-2 bg-gray-50 dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700"
963
+ >
964
+ <button
965
+ id="metadata-browse-up"
966
+ onclick="metadataBrowseUp()"
967
+ data-i18n="common.up"
968
+ class="text-xs text-blue-600 dark:text-blue-400 hover:underline disabled:opacity-30 disabled:pointer-events-none shrink-0"
969
+ >
970
+ ↑ Up
971
+ </button>
972
+ <span
973
+ id="metadata-browse-path"
974
+ class="text-xs text-gray-500 dark:text-gray-400 font-mono truncate flex-1"
975
+ ></span>
976
+ </div>
977
+ <div
978
+ id="metadata-browse-list"
979
+ class="max-h-56 overflow-y-auto divide-y divide-gray-100 dark:divide-gray-800"
980
+ ></div>
981
+ </div>
982
+
983
+ <p id="metadata-error" class="hidden text-xs text-red-500"></p>
984
+
985
+ <div class="flex justify-end">
986
+ <button
987
+ onclick="closeMetadataModal()"
988
+ data-i18n="common.close"
989
+ class="text-sm px-4 py-2 rounded-lg border border-gray-200 dark:border-gray-700 text-gray-600 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors"
990
+ >
991
+ Close
992
+ </button>
993
+ </div>
994
+ </div>
995
+ </div>
996
+
997
+ <!-- ── New Folder modal ── -->
998
+ <div
999
+ id="new-folder-modal"
1000
+ class="hidden fixed inset-0 z-50 flex items-center justify-center bg-black/50"
1001
+ >
1002
+ <div
1003
+ class="bg-white dark:bg-gray-900 rounded-xl shadow-xl w-full max-w-md mx-4 p-6 space-y-4"
1004
+ >
1005
+ <h3 class="text-base font-semibold text-gray-900 dark:text-gray-50">
1006
+ <svg
1007
+ width="16"
1008
+ height="16"
1009
+ viewBox="0 0 20 20"
1010
+ fill="none"
1011
+ xmlns="http://www.w3.org/2000/svg"
1012
+ class="inline-block mr-1 align-text-bottom"
1013
+ >
1014
+ <path
1015
+ d="M2 5a2 2 0 012-2h3.586a1 1 0 01.707.293L9.707 4.707A1 1 0 0010.414 5H16a2 2 0 012 2v8a2 2 0 01-2 2H4a2 2 0 01-2-2V5z"
1016
+ stroke="currentColor"
1017
+ stroke-width="1.5"
1018
+ fill="none"
1019
+ />
1020
+ <line
1021
+ x1="10"
1022
+ y1="8"
1023
+ x2="10"
1024
+ y2="14"
1025
+ stroke="currentColor"
1026
+ stroke-width="1.5"
1027
+ stroke-linecap="round"
1028
+ />
1029
+ <line
1030
+ x1="7"
1031
+ y1="11"
1032
+ x2="13"
1033
+ y2="11"
1034
+ stroke="currentColor"
1035
+ stroke-width="1.5"
1036
+ stroke-linecap="round"
1037
+ />
1038
+ </svg>
1039
+ <span data-i18n="modal.new_folder.title">New Folder</span>
1040
+ </h3>
1041
+
1042
+ <!-- Folder name -->
1043
+ <div class="space-y-1.5">
1044
+ <label
1045
+ data-i18n="modal.new_folder.name_label"
1046
+ class="block text-xs font-medium text-gray-500 dark:text-gray-400"
1047
+ >Folder name</label
1048
+ >
1049
+ <input
1050
+ id="new-folder-name"
1051
+ type="text"
1052
+ data-i18n-placeholder="modal.new_folder.name_placeholder"
1053
+ placeholder="my-folder"
1054
+ oninput="newFolderUpdatePreview()"
1055
+ class="w-full px-3 py-2 text-sm rounded-lg border border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500"
1056
+ />
1057
+ </div>
1058
+
1059
+ <!-- Location -->
1060
+ <div class="space-y-1.5">
1061
+ <label
1062
+ data-i18n="modal.new_folder.location_label"
1063
+ class="block text-xs font-medium text-gray-500 dark:text-gray-400"
1064
+ >Location</label
1065
+ >
1066
+ <div class="flex items-center gap-2">
1067
+ <span
1068
+ id="new-folder-location-display"
1069
+ class="flex-1 text-sm text-gray-600 dark:text-gray-300 font-mono truncate"
1070
+ >/ (root)</span
1071
+ >
1072
+ <button
1073
+ onclick="newFolderToggleBrowser()"
1074
+ data-i18n="modal.new_folder.browse_btn"
1075
+ class="text-xs px-2 py-1.5 rounded-lg border border-gray-200 dark:border-gray-700 text-gray-500 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors shrink-0"
1076
+ >
1077
+ Browse…
1078
+ </button>
1079
+ </div>
1080
+ <!-- Inline browser -->
1081
+ <div
1082
+ id="new-folder-browser"
1083
+ class="hidden border border-gray-200 dark:border-gray-700 rounded-lg overflow-hidden text-sm"
1084
+ >
1085
+ <div
1086
+ class="flex items-center gap-2 px-3 py-2 bg-gray-50 dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700"
1087
+ >
1088
+ <button
1089
+ id="new-folder-browse-up"
1090
+ onclick="newFolderBrowseUp()"
1091
+ data-i18n="common.up"
1092
+ class="text-xs text-blue-600 dark:text-blue-400 hover:underline disabled:opacity-30 disabled:pointer-events-none shrink-0"
1093
+ >
1094
+ ↑ Up
1095
+ </button>
1096
+ <span
1097
+ id="new-folder-browse-path"
1098
+ class="text-xs text-gray-500 dark:text-gray-400 font-mono truncate flex-1"
1099
+ ></span>
1100
+ </div>
1101
+ <div
1102
+ id="new-folder-browse-list"
1103
+ class="max-h-40 overflow-y-auto divide-y divide-gray-100 dark:divide-gray-800"
1104
+ ></div>
1105
+ </div>
1106
+ </div>
1107
+
1108
+ <!-- Preview -->
1109
+ <div class="space-y-1">
1110
+ <label
1111
+ data-i18n="modal.new_folder.will_be_created"
1112
+ class="block text-xs font-medium text-gray-500 dark:text-gray-400"
1113
+ >Will be created at</label
1114
+ >
1115
+ <p
1116
+ id="new-folder-preview"
1117
+ data-i18n="modal.new_folder.enter_name"
1118
+ class="text-xs font-mono text-gray-500 dark:text-gray-400 bg-gray-100 dark:bg-gray-800 rounded-lg px-3 py-2 truncate"
1119
+ >
1120
+ (enter a name)
1121
+ </p>
1122
+ </div>
1123
+
1124
+ <p id="new-folder-error" class="hidden text-xs text-red-500"></p>
1125
+
1126
+ <div class="flex justify-end gap-3 pt-1">
1127
+ <button
1128
+ onclick="closeNewFolderModal()"
1129
+ data-i18n="common.cancel"
1130
+ class="text-sm px-4 py-2 rounded-lg border border-gray-200 dark:border-gray-700 text-gray-600 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors"
1131
+ >
1132
+ Cancel
1133
+ </button>
1134
+ <button
1135
+ onclick="createNewFolder()"
1136
+ id="new-folder-create-btn"
1137
+ data-i18n="modal.new_folder.create_btn"
1138
+ class="text-sm px-4 py-2 rounded-lg bg-blue-600 hover:bg-blue-700 text-white font-semibold transition-colors"
1139
+ >
1140
+ Create
1141
+ </button>
1142
+ </div>
1143
+ </div>
1144
+ </div>
1145
+
1146
+ <!-- ── New Document modal ── -->
1147
+ <div
1148
+ id="new-doc-modal"
1149
+ class="hidden fixed inset-0 z-50 flex items-center justify-center bg-black/50"
1150
+ >
1151
+ <div
1152
+ class="bg-white dark:bg-gray-900 rounded-xl shadow-xl w-full max-w-lg mx-4 p-6 space-y-4"
1153
+ >
1154
+ <h3 class="text-base font-semibold text-gray-900 dark:text-gray-50">
1155
+ &#10133; <span data-i18n="modal.new_doc.title">New Document</span>
1156
+ </h3>
1157
+
1158
+ <!-- Title -->
1159
+ <div class="space-y-1.5">
1160
+ <label
1161
+ data-i18n="modal.new_doc.title_label"
1162
+ class="block text-xs font-medium text-gray-500 dark:text-gray-400"
1163
+ >Title</label
1164
+ >
1165
+ <input
1166
+ id="new-doc-title"
1167
+ type="text"
1168
+ data-i18n-placeholder="modal.new_doc.title_placeholder"
1169
+ placeholder="My document"
1170
+ oninput="newDocUpdatePreview()"
1171
+ class="w-full px-3 py-2 text-sm rounded-lg border border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500"
1172
+ />
1173
+ </div>
1174
+
1175
+ <!-- Category -->
1176
+ <div class="space-y-1.5">
1177
+ <label
1178
+ data-i18n="modal.new_doc.category_label"
1179
+ class="block text-xs font-medium text-gray-500 dark:text-gray-400"
1180
+ >Category</label
1181
+ >
1182
+ <input
1183
+ id="new-doc-category"
1184
+ type="text"
1185
+ list="new-doc-category-options"
1186
+ autocomplete="off"
1187
+ placeholder="GENERAL"
1188
+ oninput="newDocSanitizeCategoryInput()"
1189
+ class="w-full px-3 py-2 text-sm rounded-lg border border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500"
1190
+ />
1191
+ <datalist id="new-doc-category-options"></datalist>
1192
+ </div>
1193
+
1194
+ <!-- Location -->
1195
+ <div class="space-y-1.5">
1196
+ <label
1197
+ data-i18n="modal.new_doc.location_label"
1198
+ class="block text-xs font-medium text-gray-500 dark:text-gray-400"
1199
+ >Location</label
1200
+ >
1201
+ <div class="flex items-center gap-2">
1202
+ <span
1203
+ id="new-doc-folder-display"
1204
+ class="flex-1 px-3 py-2 text-xs rounded-lg border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800 text-gray-500 dark:text-gray-400 font-mono truncate"
1205
+ >
1206
+ / (root)
1207
+ </span>
1208
+ <button
1209
+ onclick="newDocToggleBrowser()"
1210
+ class="text-sm px-3 py-1.5 rounded-lg border border-gray-200 dark:border-gray-700 text-gray-600 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors shrink-0"
1211
+ >
1212
+ &#128193; <span data-i18n="modal.new_doc.browse_btn">Browse</span>
1213
+ </button>
1214
+ </div>
1215
+ <!-- Inline folder browser -->
1216
+ <div
1217
+ id="new-doc-browser"
1218
+ class="hidden border border-gray-200 dark:border-gray-700 rounded-lg overflow-hidden"
1219
+ >
1220
+ <div
1221
+ class="flex items-center gap-2 px-3 py-2 bg-gray-50 dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700"
1222
+ >
1223
+ <button
1224
+ id="new-doc-browse-up"
1225
+ onclick="newDocBrowseUp()"
1226
+ data-i18n="common.up"
1227
+ class="text-xs text-blue-600 dark:text-blue-400 hover:underline disabled:opacity-30 disabled:pointer-events-none shrink-0"
1228
+ >
1229
+ &#8593; Up
1230
+ </button>
1231
+ <span
1232
+ id="new-doc-browse-path"
1233
+ class="font-mono text-xs text-gray-400 dark:text-gray-500 truncate flex-1 text-right"
1234
+ ></span>
1235
+ </div>
1236
+ <div
1237
+ id="new-doc-browse-list"
1238
+ class="divide-y divide-gray-100 dark:divide-gray-800 max-h-40 overflow-y-auto"
1239
+ ></div>
1240
+ <!-- Create new folder row -->
1241
+ <div
1242
+ class="border-t border-gray-200 dark:border-gray-700 flex items-center gap-2 px-3 py-2 bg-gray-50 dark:bg-gray-800/50"
1243
+ >
1244
+ <input
1245
+ id="new-doc-new-folder-name"
1246
+ type="text"
1247
+ data-i18n-placeholder="modal.new_doc.new_folder_placeholder"
1248
+ placeholder="New folder name…"
1249
+ class="flex-1 px-2 py-1 text-xs rounded border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-1 focus:ring-blue-400"
1250
+ />
1251
+ <button
1252
+ onclick="newDocCreateFolder()"
1253
+ data-i18n="modal.new_doc.create_folder_btn"
1254
+ class="text-xs px-2 py-1 rounded bg-gray-100 dark:bg-gray-800 text-gray-600 dark:text-gray-400 hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors shrink-0"
1255
+ >
1256
+ + Create
1257
+ </button>
1258
+ </div>
1259
+ </div>
1260
+ </div>
1261
+
1262
+ <!-- Filename preview -->
1263
+ <div class="space-y-1.5">
1264
+ <label
1265
+ data-i18n="modal.new_doc.filename_label"
1266
+ class="block text-xs font-medium text-gray-500 dark:text-gray-400"
1267
+ >Generated filename</label
1268
+ >
1269
+ <p
1270
+ id="new-doc-filename-preview"
1271
+ data-i18n="modal.new_doc.title_placeholder"
1272
+ class="text-xs bg-gray-100 dark:bg-gray-800 rounded-lg px-3 py-2 font-mono text-gray-600 dark:text-gray-300 break-all"
1273
+ >
1274
+ (enter a title)
1275
+ </p>
1276
+ </div>
1277
+
1278
+ <!-- Error -->
1279
+ <p id="new-doc-error" class="hidden text-xs text-red-500"></p>
1280
+
1281
+ <!-- Actions -->
1282
+ <div class="flex justify-end gap-3 pt-1">
1283
+ <button
1284
+ onclick="closeNewDocModal()"
1285
+ data-i18n="common.cancel"
1286
+ class="text-sm px-4 py-2 rounded-lg border border-gray-200 dark:border-gray-700 text-gray-600 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors"
1287
+ >
1288
+ Cancel
1289
+ </button>
1290
+ <button
1291
+ onclick="createNewDocument()"
1292
+ id="new-doc-create-btn"
1293
+ data-i18n="common.create"
1294
+ class="text-sm px-4 py-2 rounded-lg bg-blue-600 hover:bg-blue-700 text-white font-semibold transition-colors"
1295
+ >
1296
+ Create
1297
+ </button>
1298
+ </div>
1299
+ </div>
1300
+ </div>
1301
+
1302
+ <!-- ── Diagram link modal ── -->
1303
+ <div
1304
+ id="diag-link-modal"
1305
+ class="hidden fixed inset-0 z-50 flex items-center justify-center bg-black/50"
1306
+ >
1307
+ <div
1308
+ class="bg-white dark:bg-gray-900 rounded-xl shadow-xl w-full max-w-lg mx-4 p-6 space-y-5"
1309
+ >
1310
+ <h3 class="text-base font-semibold text-gray-900 dark:text-gray-50">
1311
+ <span data-i18n="modal.diag_link.title">&#9671; Link a diagram</span>
1312
+ </h3>
1313
+
1314
+ <!-- Mode toggle -->
1315
+ <div class="flex gap-4 text-sm">
1316
+ <label class="flex items-center gap-2 cursor-pointer">
1317
+ <input
1318
+ type="radio"
1319
+ name="diag-mode"
1320
+ id="diag-mode-existing"
1321
+ value="existing"
1322
+ checked
1323
+ onchange="diagModeChanged()"
1324
+ />
1325
+ <span data-i18n="modal.diag_link.existing_radio" class="text-gray-700 dark:text-gray-300"
1326
+ >Existing diagram</span
1327
+ >
1328
+ </label>
1329
+ <label class="flex items-center gap-2 cursor-pointer">
1330
+ <input
1331
+ type="radio"
1332
+ name="diag-mode"
1333
+ id="diag-mode-new"
1334
+ value="new"
1335
+ onchange="diagModeChanged()"
1336
+ />
1337
+ <span data-i18n="modal.diag_link.new_radio" class="text-gray-700 dark:text-gray-300">New diagram</span>
1338
+ </label>
1339
+ </div>
1340
+
1341
+ <!-- Existing diagram picker -->
1342
+ <div id="diag-existing-section" class="space-y-1.5">
1343
+ <label
1344
+ data-i18n="modal.diag_link.select_label"
1345
+ class="block text-xs font-medium text-gray-500 dark:text-gray-400"
1346
+ >Select diagram</label
1347
+ >
1348
+ <select
1349
+ id="diag-select"
1350
+ onchange="diagUpdatePreview()"
1351
+ class="w-full px-3 py-2 text-sm rounded-lg border border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500"
1352
+ ></select>
1353
+ </div>
1354
+
1355
+ <!-- New diagram name -->
1356
+ <div id="diag-new-section" class="hidden space-y-1.5">
1357
+ <label
1358
+ data-i18n="modal.diag_link.name_label"
1359
+ class="block text-xs font-medium text-gray-500 dark:text-gray-400"
1360
+ >Diagram name</label
1361
+ >
1362
+ <input
1363
+ id="diag-new-name"
1364
+ type="text"
1365
+ data-i18n-placeholder="modal.diag_link.name_placeholder"
1366
+ placeholder="My diagram"
1367
+ oninput="diagUpdatePreview()"
1368
+ class="w-full px-3 py-2 text-sm rounded-lg border border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500"
1369
+ />
1370
+ </div>
1371
+
1372
+ <!-- Image filename -->
1373
+ <div class="space-y-1.5">
1374
+ <label
1375
+ class="block text-xs font-medium text-gray-500 dark:text-gray-400"
1376
+ ><span data-i18n="modal.diag_link.img_label">Image filename</span>
1377
+ <span data-i18n="modal.diag_link.img_hint" class="font-normal text-gray-400"
1378
+ >(saved in ./images/)</span
1379
+ ></label
1380
+ >
1381
+ <input
1382
+ id="diag-img-name"
1383
+ type="text"
1384
+ placeholder="diagram.png"
1385
+ oninput="diagUpdatePreview()"
1386
+ class="w-full px-3 py-2 text-sm rounded-lg border border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 text-gray-900 dark:text-gray-100 font-mono focus:outline-none focus:ring-2 focus:ring-blue-500"
1387
+ />
1388
+ </div>
1389
+
1390
+ <!-- Markdown preview -->
1391
+ <div class="space-y-1.5">
1392
+ <label
1393
+ data-i18n="modal.diag_link.markdown_label"
1394
+ class="block text-xs font-medium text-gray-500 dark:text-gray-400"
1395
+ >Markdown that will be appended</label
1396
+ >
1397
+ <pre
1398
+ id="diag-preview"
1399
+ class="text-xs bg-gray-100 dark:bg-gray-800 rounded-lg px-3 py-2 overflow-x-auto text-gray-700 dark:text-gray-300 whitespace-pre-wrap"
1400
+ ></pre>
1401
+ </div>
1402
+
1403
+ <!-- Actions -->
1404
+ <div class="flex justify-end gap-3 pt-1">
1405
+ <button
1406
+ onclick="closeDiagramLinkModal()"
1407
+ data-i18n="common.cancel"
1408
+ class="text-sm px-4 py-2 rounded-lg border border-gray-200 dark:border-gray-700 text-gray-600 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors"
1409
+ >
1410
+ Cancel
1411
+ </button>
1412
+ <button
1413
+ onclick="insertDiagramLink()"
1414
+ id="diag-insert-btn"
1415
+ data-i18n="modal.diag_link.insert_btn"
1416
+ class="text-sm px-4 py-2 rounded-lg bg-blue-600 hover:bg-blue-700 text-white font-semibold transition-colors"
1417
+ >
1418
+ Insert &amp; Open Diagram
1419
+ </button>
1420
+ </div>
1421
+ </div>
1422
+ </div>
1423
+
1424
+ <!-- ── Snippets modal ── -->
1425
+ <div
1426
+ id="snippets-modal"
1427
+ class="hidden fixed inset-0 z-50 flex items-center justify-center bg-black/50"
1428
+ >
1429
+ <div
1430
+ class="bg-white dark:bg-gray-900 rounded-xl shadow-xl w-full max-w-lg mx-4 p-6 space-y-5 max-h-[90vh] overflow-y-auto"
1431
+ >
1432
+ <h3 class="text-base font-semibold text-gray-900 dark:text-gray-50">
1433
+ <span data-i18n="snippet.modal_title">🧩 Insert a snippet</span>
1434
+ </h3>
1435
+
1436
+ <div
1437
+ id="snippet-detect-msg"
1438
+ class="hidden rounded-lg px-3 py-2 text-xs"
1439
+ ></div>
1440
+
1441
+ <!-- Snippet type selector -->
1442
+ <div class="space-y-1.5">
1443
+ <label
1444
+ data-i18n="snippet.type_label"
1445
+ class="block text-xs font-medium text-gray-500 dark:text-gray-400"
1446
+ >Snippet type</label
1447
+ >
1448
+ <select
1449
+ id="snippet-type"
1450
+ onchange="snippetTypeChanged()"
1451
+ class="w-full px-3 py-2 text-sm rounded-lg border border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500"
1452
+ >
1453
+ <option data-i18n="snippet.diagram" value="diagram" selected>Diagram</option>
1454
+ <option data-i18n="snippet.collapsible" value="collapsible">Collapsible block (details)</option>
1455
+ <option data-i18n="snippet.link" value="link">Link</option>
1456
+ <option data-i18n="snippet.link_doc" value="doc-link">Link to document</option>
1457
+ <option data-i18n="snippet.link_anchor" value="anchor-link">Link to anchor</option>
1458
+ <option data-i18n="snippet.link_doc_anchor" value="anchor-doc-link">
1459
+ Link to document with anchor
1460
+ </option>
1461
+ <option data-i18n="snippet.numbered_list" value="ordered-list">Numbered list</option>
1462
+ <option data-i18n="snippet.bullet_list" value="unordered-list">Bullet list</option>
1463
+ <option data-i18n="snippet.code_block" value="code-block">Code block</option>
1464
+ <option data-i18n="snippet.blockquote" value="blockquote">Blockquote</option>
1465
+ <option data-i18n="snippet.separator" value="separator">Horizontal rule</option>
1466
+ <option data-i18n="snippet.image" value="image">Image</option>
1467
+ <option data-i18n="snippet.table" value="table">Table</option>
1468
+ <option data-i18n="snippet.tree" value="tree">Tree</option>
1469
+ <option data-i18n="snippet.colored_section" value="colored-section">Colored section</option>
1470
+ <option data-i18n="snippet.colored_text" value="colored-text">Colored text</option>
1471
+ <option data-i18n="snippet.emojis" value="emojis">😀 Emojis</option>
1472
+ <option data-i18n="snippet.attachment" value="attachment">📎 File attachment</option>
1473
+ <option data-i18n="snippet.local_search" value="local-search">🔎 Local search</option>
1474
+ </select>
1475
+ </div>
1476
+
1477
+ <!-- Panel: collapsible -->
1478
+ <div id="snip-panel-collapsible" class="space-y-3">
1479
+ <div class="space-y-1.5">
1480
+ <label
1481
+ data-i18n="snippet.collapsible_summary_label"
1482
+ class="block text-xs font-medium text-gray-500 dark:text-gray-400"
1483
+ >Summary title</label
1484
+ >
1485
+ <input
1486
+ id="snip-collapsible-summary"
1487
+ type="text"
1488
+ value="Details"
1489
+ oninput="snippetUpdatePreview()"
1490
+ class="w-full px-3 py-2 text-sm rounded-lg border border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500"
1491
+ />
1492
+ </div>
1493
+ </div>
1494
+
1495
+ <!-- Panel: link -->
1496
+ <div id="snip-panel-link" class="hidden space-y-3">
1497
+ <div class="space-y-1.5">
1498
+ <label
1499
+ data-i18n="snippet.link_text_label"
1500
+ class="block text-xs font-medium text-gray-500 dark:text-gray-400"
1501
+ >Link text</label
1502
+ >
1503
+ <input
1504
+ id="snip-link-text"
1505
+ type="text"
1506
+ data-i18n-placeholder="snippet.link_text_placeholder"
1507
+ placeholder="My link"
1508
+ oninput="snippetUpdatePreview()"
1509
+ class="w-full px-3 py-2 text-sm rounded-lg border border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500"
1510
+ />
1511
+ </div>
1512
+ <div class="space-y-1.5">
1513
+ <label
1514
+ data-i18n="snippet.link_url_label"
1515
+ class="block text-xs font-medium text-gray-500 dark:text-gray-400"
1516
+ >URL</label
1517
+ >
1518
+ <input
1519
+ id="snip-link-url"
1520
+ type="text"
1521
+ data-i18n-placeholder="snippet.link_url_placeholder"
1522
+ placeholder="https://..."
1523
+ oninput="snippetUpdatePreview()"
1524
+ class="w-full px-3 py-2 text-sm rounded-lg border border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500"
1525
+ />
1526
+ </div>
1527
+ </div>
1528
+
1529
+ <!-- Panel: doc-link -->
1530
+ <div id="snip-panel-doc-link" class="hidden space-y-3">
1531
+ <div class="space-y-1.5">
1532
+ <label
1533
+ data-i18n="snippet.link_doc_label"
1534
+ class="block text-xs font-medium text-gray-500 dark:text-gray-400"
1535
+ >Document</label
1536
+ >
1537
+ <select
1538
+ id="snip-doc-select"
1539
+ onchange="snippetUpdatePreview()"
1540
+ class="w-full px-3 py-2 text-sm rounded-lg border border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500"
1541
+ ></select>
1542
+ </div>
1543
+ <div class="space-y-1.5">
1544
+ <label
1545
+ class="block text-xs font-medium text-gray-500 dark:text-gray-400"
1546
+ ><span data-i18n="snippet.link_text_label">Link text</span>
1547
+ <span data-i18n="snippet.link_doc_text_hint" class="font-normal text-gray-400"
1548
+ >(optional, uses default title)</span
1549
+ ></label
1550
+ >
1551
+ <input
1552
+ id="snip-doc-link-text"
1553
+ type="text"
1554
+ placeholder=""
1555
+ oninput="snippetUpdatePreview()"
1556
+ class="w-full px-3 py-2 text-sm rounded-lg border border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500"
1557
+ />
1558
+ </div>
1559
+ </div>
1560
+
1561
+ <!-- Panel: anchor-link -->
1562
+ <div id="snip-panel-anchor-link" class="hidden space-y-3">
1563
+ <div class="space-y-1.5">
1564
+ <label
1565
+ data-i18n="snippet.link_text_label"
1566
+ class="block text-xs font-medium text-gray-500 dark:text-gray-400"
1567
+ >Link text</label
1568
+ >
1569
+ <input
1570
+ id="snip-anchor-text"
1571
+ type="text"
1572
+ data-i18n-placeholder="snippet.link_section_placeholder"
1573
+ placeholder="View section"
1574
+ oninput="snippetUpdatePreview()"
1575
+ class="w-full px-3 py-2 text-sm rounded-lg border border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500"
1576
+ />
1577
+ </div>
1578
+ <div class="space-y-1.5">
1579
+ <label
1580
+ class="block text-xs font-medium text-gray-500 dark:text-gray-400"
1581
+ ><span data-i18n="snippet.link_anchor_label">Anchor</span>
1582
+ <span data-i18n="snippet.link_anchor_select_hint" class="font-normal text-gray-400"
1583
+ >(pick a heading from the document)</span
1584
+ ></label
1585
+ >
1586
+ <select
1587
+ id="snip-anchor-id"
1588
+ onchange="snippetUpdatePreview()"
1589
+ class="w-full px-3 py-2 text-sm rounded-lg border border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500"
1590
+ ></select>
1591
+ </div>
1592
+ </div>
1593
+
1594
+ <!-- Panel: anchor-doc-link -->
1595
+ <div id="snip-panel-anchor-doc-link" class="hidden space-y-3">
1596
+ <div class="space-y-1.5">
1597
+ <label
1598
+ data-i18n="snippet.link_target_doc_label"
1599
+ class="block text-xs font-medium text-gray-500 dark:text-gray-400"
1600
+ >Target document</label
1601
+ >
1602
+ <select
1603
+ id="snip-anchor-doc-select"
1604
+ onchange="snippetAnchorDocChanged()"
1605
+ class="w-full px-3 py-2 text-sm rounded-lg border border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500"
1606
+ ></select>
1607
+ </div>
1608
+ <div class="space-y-1.5">
1609
+ <label
1610
+ data-i18n="snippet.link_text_label"
1611
+ class="block text-xs font-medium text-gray-500 dark:text-gray-400"
1612
+ >Link text</label
1613
+ >
1614
+ <input
1615
+ id="snip-anchor-doc-text"
1616
+ type="text"
1617
+ data-i18n-placeholder="snippet.link_section_placeholder"
1618
+ placeholder="View section"
1619
+ oninput="snippetUpdatePreview()"
1620
+ class="w-full px-3 py-2 text-sm rounded-lg border border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500"
1621
+ />
1622
+ </div>
1623
+ <div class="space-y-1.5">
1624
+ <label
1625
+ class="block text-xs font-medium text-gray-500 dark:text-gray-400"
1626
+ ><span data-i18n="snippet.link_anchor_label">Anchor</span>
1627
+ <span data-i18n="snippet.link_anchor_select_hint" class="font-normal text-gray-400"
1628
+ >(pick a heading from the document)</span
1629
+ ></label
1630
+ >
1631
+ <select
1632
+ id="snip-anchor-doc-id"
1633
+ onchange="snippetUpdatePreview()"
1634
+ class="w-full px-3 py-2 text-sm rounded-lg border border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500"
1635
+ ></select>
1636
+ </div>
1637
+ </div>
1638
+
1639
+ <!-- Panel: code-block -->
1640
+ <div id="snip-panel-code-block" class="hidden space-y-3">
1641
+ <div class="space-y-1.5">
1642
+ <label
1643
+ class="block text-xs font-medium text-gray-500 dark:text-gray-400"
1644
+ ><span data-i18n="snippet.code_lang_label">Language</span>
1645
+ <span data-i18n="snippet.code_lang_hint" class="font-normal text-gray-400"
1646
+ >(e.g. javascript, python, bash…)</span
1647
+ ></label
1648
+ >
1649
+ <input
1650
+ id="snip-code-lang"
1651
+ type="text"
1652
+ placeholder="javascript"
1653
+ oninput="snippetUpdatePreview()"
1654
+ class="w-full px-3 py-2 text-sm rounded-lg border border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500"
1655
+ />
1656
+ </div>
1657
+ </div>
1658
+
1659
+ <!-- Panel: image -->
1660
+ <div id="snip-panel-image" class="hidden space-y-3">
1661
+ <div class="space-y-1.5">
1662
+ <label
1663
+ data-i18n="snippet.image_alt_label"
1664
+ class="block text-xs font-medium text-gray-500 dark:text-gray-400"
1665
+ >Alt text</label
1666
+ >
1667
+ <input
1668
+ id="snip-image-alt"
1669
+ type="text"
1670
+ data-i18n-placeholder="snippet.image_alt_placeholder"
1671
+ placeholder="Image description"
1672
+ oninput="snippetUpdatePreview()"
1673
+ class="w-full px-3 py-2 text-sm rounded-lg border border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500"
1674
+ />
1675
+ </div>
1676
+ <div class="space-y-1.5">
1677
+ <label
1678
+ data-i18n="snippet.image_url_label"
1679
+ class="block text-xs font-medium text-gray-500 dark:text-gray-400"
1680
+ >Image URL</label
1681
+ >
1682
+ <input
1683
+ id="snip-image-url"
1684
+ type="text"
1685
+ data-i18n-placeholder="snippet.image_src_placeholder"
1686
+ placeholder="./images/my-image.png"
1687
+ oninput="snippetUpdatePreview()"
1688
+ class="w-full px-3 py-2 text-sm rounded-lg border border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500"
1689
+ />
1690
+ </div>
1691
+ </div>
1692
+
1693
+ <!-- Panel: table -->
1694
+ <div id="snip-panel-table" class="hidden space-y-3">
1695
+ <div class="flex items-center gap-5">
1696
+ <div class="flex items-center gap-2">
1697
+ <span data-i18n="snippet.table_rows_label" class="text-xs text-gray-500 dark:text-gray-400"
1698
+ >Rows</span
1699
+ >
1700
+ <button
1701
+ onclick="tableChangeRows(-1)"
1702
+ class="text-xs px-2 py-0.5 rounded border border-gray-200 dark:border-gray-700 text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors"
1703
+ >
1704
+
1705
+ </button>
1706
+ <span
1707
+ id="snip-table-rows"
1708
+ class="text-xs font-mono w-4 text-center"
1709
+ >3</span
1710
+ >
1711
+ <button
1712
+ onclick="tableChangeRows(1)"
1713
+ class="text-xs px-2 py-0.5 rounded border border-gray-200 dark:border-gray-700 text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors"
1714
+ >
1715
+ +
1716
+ </button>
1717
+ </div>
1718
+ <div class="flex items-center gap-2">
1719
+ <span data-i18n="snippet.table_cols_label" class="text-xs text-gray-500 dark:text-gray-400"
1720
+ >Columns</span
1721
+ >
1722
+ <button
1723
+ onclick="tableChangeCols(-1)"
1724
+ class="text-xs px-2 py-0.5 rounded border border-gray-200 dark:border-gray-700 text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors"
1725
+ >
1726
+
1727
+ </button>
1728
+ <span
1729
+ id="snip-table-cols"
1730
+ class="text-xs font-mono w-4 text-center"
1731
+ >3</span
1732
+ >
1733
+ <button
1734
+ onclick="tableChangeCols(1)"
1735
+ class="text-xs px-2 py-0.5 rounded border border-gray-200 dark:border-gray-700 text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors"
1736
+ >
1737
+ +
1738
+ </button>
1739
+ </div>
1740
+ </div>
1741
+ <div id="snip-table-grid" class="overflow-x-auto"></div>
1742
+ </div>
1743
+
1744
+ <!-- Panel: tree -->
1745
+ <div id="snip-panel-tree" class="hidden space-y-2">
1746
+ <p data-i18n="snippet.tree_hint" class="text-xs text-gray-400 dark:text-gray-500">
1747
+ ← → to indent · names with / will be folders
1748
+ </p>
1749
+ <div id="snip-tree-list" class="space-y-1"></div>
1750
+ <button
1751
+ onclick="treeAddItem()"
1752
+ data-i18n="snippet.tree_add_btn"
1753
+ class="text-xs px-3 py-1.5 rounded-lg border border-dashed border-gray-300 dark:border-gray-600 text-gray-500 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors w-full"
1754
+ >
1755
+ + Add item
1756
+ </button>
1757
+ </div>
1758
+
1759
+ <!-- Panel: diagram -->
1760
+ <div id="snip-panel-diagram" class="hidden space-y-3">
1761
+ <!-- Mode toggle -->
1762
+ <div class="flex gap-4 text-sm">
1763
+ <label class="flex items-center gap-2 cursor-pointer">
1764
+ <input
1765
+ type="radio"
1766
+ name="snip-diag-mode"
1767
+ id="snip-diag-mode-existing"
1768
+ value="existing"
1769
+ checked
1770
+ onchange="snippetDiagModeChanged()"
1771
+ />
1772
+ <span data-i18n="snippet.diagram_existing" class="text-gray-700 dark:text-gray-300"
1773
+ >Existing diagram</span
1774
+ >
1775
+ </label>
1776
+ <label class="flex items-center gap-2 cursor-pointer">
1777
+ <input
1778
+ type="radio"
1779
+ name="snip-diag-mode"
1780
+ id="snip-diag-mode-new"
1781
+ value="new"
1782
+ onchange="snippetDiagModeChanged()"
1783
+ />
1784
+ <span data-i18n="snippet.diagram_new" class="text-gray-700 dark:text-gray-300"
1785
+ >New diagram</span
1786
+ >
1787
+ </label>
1788
+ </div>
1789
+ <!-- Existing picker -->
1790
+ <div id="snip-diag-existing-section" class="space-y-1.5">
1791
+ <label
1792
+ data-i18n="snippet.diagram_select_label"
1793
+ class="block text-xs font-medium text-gray-500 dark:text-gray-400"
1794
+ >Select diagram</label
1795
+ >
1796
+ <select
1797
+ id="snip-diag-select"
1798
+ onchange="
1799
+ snippetDiagSyncImgName();
1800
+ snippetUpdatePreview();
1801
+ "
1802
+ class="w-full px-3 py-2 text-sm rounded-lg border border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500"
1803
+ ></select>
1804
+ </div>
1805
+ <!-- New diagram name -->
1806
+ <div id="snip-diag-new-section" class="hidden space-y-1.5">
1807
+ <label
1808
+ data-i18n="snippet.diagram_name_label"
1809
+ class="block text-xs font-medium text-gray-500 dark:text-gray-400"
1810
+ >Diagram name</label
1811
+ >
1812
+ <input
1813
+ id="snip-diag-new-name"
1814
+ type="text"
1815
+ data-i18n-placeholder="snippet.diagram_name_placeholder"
1816
+ placeholder="Diagram name…"
1817
+ oninput="
1818
+ snippetDiagSyncImgName();
1819
+ snippetUpdatePreview();
1820
+ "
1821
+ class="w-full px-3 py-2 text-sm rounded-lg border border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500"
1822
+ />
1823
+ </div>
1824
+ <!-- Image filename -->
1825
+ <div class="space-y-1.5">
1826
+ <label
1827
+ class="block text-xs font-medium text-gray-500 dark:text-gray-400"
1828
+ >
1829
+ <span data-i18n="snippet.diag_img_label">Image filename</span>
1830
+ <span data-i18n="snippet.diag_img_saved_hint" class="font-normal text-gray-400"
1831
+ >(saved in ./images/)</span
1832
+ >
1833
+ </label>
1834
+ <input
1835
+ id="snip-diag-img-name"
1836
+ type="text"
1837
+ placeholder="diagram.png"
1838
+ oninput="snippetUpdatePreview()"
1839
+ class="w-full px-3 py-2 text-sm rounded-lg border border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 text-gray-900 dark:text-gray-100 font-mono focus:outline-none focus:ring-2 focus:ring-blue-500"
1840
+ />
1841
+ </div>
1842
+ </div>
1843
+
1844
+ <!-- Panel: colored-section -->
1845
+ <div id="snip-panel-colored-section" class="hidden space-y-3">
1846
+ <div class="space-y-1.5">
1847
+ <label
1848
+ data-i18n="snippet.colored_section_color_label"
1849
+ class="block text-xs font-medium text-gray-500 dark:text-gray-400"
1850
+ >Color</label
1851
+ >
1852
+ <div class="flex flex-wrap gap-2">
1853
+ <button
1854
+ type="button"
1855
+ data-color-swatch="info"
1856
+ onclick="colorSectionPickSwatch(this)"
1857
+ data-i18n-title="snippet.colored_section_swatch_info"
1858
+ title="Info (blue)"
1859
+ class="w-7 h-7 rounded-full border-2 border-blue-400 bg-blue-100 ring-2 ring-transparent hover:ring-blue-400 transition-all color-swatch-btn selected-swatch"
1860
+ ></button>
1861
+ <button
1862
+ type="button"
1863
+ data-color-swatch="success"
1864
+ onclick="colorSectionPickSwatch(this)"
1865
+ data-i18n-title="snippet.colored_section_swatch_success"
1866
+ title="Success (green)"
1867
+ class="w-7 h-7 rounded-full border-2 border-green-400 bg-green-100 ring-2 ring-transparent hover:ring-green-400 transition-all color-swatch-btn"
1868
+ ></button>
1869
+ <button
1870
+ type="button"
1871
+ data-color-swatch="warning"
1872
+ onclick="colorSectionPickSwatch(this)"
1873
+ data-i18n-title="snippet.colored_section_swatch_warning"
1874
+ title="Warning (amber)"
1875
+ class="w-7 h-7 rounded-full border-2 border-amber-400 bg-amber-100 ring-2 ring-transparent hover:ring-amber-400 transition-all color-swatch-btn"
1876
+ ></button>
1877
+ <button
1878
+ type="button"
1879
+ data-color-swatch="danger"
1880
+ onclick="colorSectionPickSwatch(this)"
1881
+ data-i18n-title="snippet.colored_section_swatch_danger"
1882
+ title="Danger (red)"
1883
+ class="w-7 h-7 rounded-full border-2 border-red-400 bg-red-100 ring-2 ring-transparent hover:ring-red-400 transition-all color-swatch-btn"
1884
+ ></button>
1885
+ <button
1886
+ type="button"
1887
+ data-color-swatch="note"
1888
+ onclick="colorSectionPickSwatch(this)"
1889
+ data-i18n-title="snippet.colored_section_swatch_note"
1890
+ title="Note (purple)"
1891
+ class="w-7 h-7 rounded-full border-2 border-purple-400 bg-purple-100 ring-2 ring-transparent hover:ring-purple-400 transition-all color-swatch-btn"
1892
+ ></button>
1893
+ <button
1894
+ type="button"
1895
+ data-color-swatch="neutral"
1896
+ onclick="colorSectionPickSwatch(this)"
1897
+ data-i18n-title="snippet.colored_section_swatch_neutral"
1898
+ title="Neutral (gray)"
1899
+ class="w-7 h-7 rounded-full border-2 border-gray-400 bg-gray-100 ring-2 ring-transparent hover:ring-gray-400 transition-all color-swatch-btn"
1900
+ ></button>
1901
+ </div>
1902
+ </div>
1903
+ <div class="space-y-1.5">
1904
+ <label
1905
+ data-i18n="snippet.colored_section_content_label"
1906
+ class="block text-xs font-medium text-gray-500 dark:text-gray-400"
1907
+ >Content</label
1908
+ >
1909
+ <textarea
1910
+ id="snip-colored-content"
1911
+ rows="4"
1912
+ data-i18n-placeholder="snippet.colored_section_content_placeholder"
1913
+ placeholder="Your text here…"
1914
+ oninput="snippetUpdatePreview()"
1915
+ class="w-full px-3 py-2 text-sm rounded-lg border border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500 resize-y font-mono"
1916
+ ></textarea>
1917
+ </div>
1918
+ </div>
1919
+
1920
+ <!-- Panel: colored-text -->
1921
+ <div id="snip-panel-colored-text" class="hidden space-y-3">
1922
+ <div class="space-y-1.5">
1923
+ <label
1924
+ data-i18n="snippet.colored_text_color_label"
1925
+ class="block text-xs font-medium text-gray-500 dark:text-gray-400"
1926
+ >Color</label
1927
+ >
1928
+ <div class="flex flex-wrap gap-2">
1929
+ <button
1930
+ type="button"
1931
+ data-color-text-swatch="info"
1932
+ onclick="colorTextPickSwatch(this)"
1933
+ data-i18n-title="snippet.colored_section_swatch_info"
1934
+ title="Info (blue)"
1935
+ class="w-7 h-7 rounded-full border-2 border-blue-500 bg-blue-500 ring-2 ring-transparent hover:ring-blue-400 transition-all color-text-swatch-btn selected-text-swatch"
1936
+ ></button>
1937
+ <button
1938
+ type="button"
1939
+ data-color-text-swatch="success"
1940
+ onclick="colorTextPickSwatch(this)"
1941
+ data-i18n-title="snippet.colored_section_swatch_success"
1942
+ title="Success (green)"
1943
+ class="w-7 h-7 rounded-full border-2 border-green-500 bg-green-500 ring-2 ring-transparent hover:ring-green-400 transition-all color-text-swatch-btn"
1944
+ ></button>
1945
+ <button
1946
+ type="button"
1947
+ data-color-text-swatch="warning"
1948
+ onclick="colorTextPickSwatch(this)"
1949
+ data-i18n-title="snippet.colored_section_swatch_warning"
1950
+ title="Warning (amber)"
1951
+ class="w-7 h-7 rounded-full border-2 border-amber-500 bg-amber-500 ring-2 ring-transparent hover:ring-amber-400 transition-all color-text-swatch-btn"
1952
+ ></button>
1953
+ <button
1954
+ type="button"
1955
+ data-color-text-swatch="danger"
1956
+ onclick="colorTextPickSwatch(this)"
1957
+ data-i18n-title="snippet.colored_section_swatch_danger"
1958
+ title="Danger (red)"
1959
+ class="w-7 h-7 rounded-full border-2 border-red-500 bg-red-500 ring-2 ring-transparent hover:ring-red-400 transition-all color-text-swatch-btn"
1960
+ ></button>
1961
+ <button
1962
+ type="button"
1963
+ data-color-text-swatch="note"
1964
+ onclick="colorTextPickSwatch(this)"
1965
+ data-i18n-title="snippet.colored_section_swatch_note"
1966
+ title="Note (purple)"
1967
+ class="w-7 h-7 rounded-full border-2 border-purple-500 bg-purple-500 ring-2 ring-transparent hover:ring-purple-400 transition-all color-text-swatch-btn"
1968
+ ></button>
1969
+ <button
1970
+ type="button"
1971
+ data-color-text-swatch="neutral"
1972
+ onclick="colorTextPickSwatch(this)"
1973
+ data-i18n-title="snippet.colored_section_swatch_neutral"
1974
+ title="Neutral (gray)"
1975
+ class="w-7 h-7 rounded-full border-2 border-gray-500 bg-gray-500 ring-2 ring-transparent hover:ring-gray-400 transition-all color-text-swatch-btn"
1976
+ ></button>
1977
+ </div>
1978
+ </div>
1979
+ <div class="space-y-1.5">
1980
+ <label
1981
+ data-i18n="snippet.colored_text_content_label"
1982
+ class="block text-xs font-medium text-gray-500 dark:text-gray-400"
1983
+ >Text</label
1984
+ >
1985
+ <input
1986
+ id="snip-colored-text-content"
1987
+ type="text"
1988
+ data-i18n-placeholder="snippet.colored_text_content_placeholder"
1989
+ placeholder="Your text…"
1990
+ oninput="snippetUpdatePreview()"
1991
+ class="w-full px-3 py-2 text-sm rounded-lg border border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500"
1992
+ />
1993
+ </div>
1994
+ </div>
1995
+
1996
+ <!-- Panel: emojis -->
1997
+ <div id="snip-panel-emojis" class="hidden space-y-3">
1998
+ <div class="space-y-1.5">
1999
+ <label
2000
+ data-i18n="snippet.emoji_selected_label"
2001
+ class="block text-xs font-medium text-gray-500 dark:text-gray-400"
2002
+ >Selected emojis</label
2003
+ >
2004
+ <div class="flex gap-1">
2005
+ <input
2006
+ id="snip-emoji-string"
2007
+ type="text"
2008
+ oninput="snippetUpdatePreview()"
2009
+ class="flex-1 px-3 py-2 text-sm rounded-lg border border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500"
2010
+ />
2011
+ <button
2012
+ type="button"
2013
+ id="snip-emoji-clear"
2014
+ onclick="emojiClear()"
2015
+ data-i18n="snippet.emoji_clear_btn"
2016
+ class="px-3 py-2 text-xs rounded-lg border border-gray-300 dark:border-gray-600 hover:bg-gray-100 dark:hover:bg-gray-700"
2017
+ >Clear</button>
2018
+ </div>
2019
+ </div>
2020
+ <div class="space-y-1.5">
2021
+ <input
2022
+ id="snip-emoji-search"
2023
+ type="text"
2024
+ data-i18n-placeholder="snippet.emoji_search_placeholder"
2025
+ placeholder="Search emojis… (e.g. heart, star, rocket)"
2026
+ class="w-full px-3 py-2 text-sm rounded-lg border border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500"
2027
+ />
2028
+ </div>
2029
+ <div
2030
+ id="snip-emoji-grid"
2031
+ class="space-y-2 max-h-64 overflow-y-auto border border-gray-200 dark:border-gray-700 rounded-lg p-2 bg-white dark:bg-gray-900"
2032
+ ></div>
2033
+ </div>
2034
+
2035
+ <!-- Panel: file attachment -->
2036
+ <div id="snip-panel-attachment" class="hidden space-y-3">
2037
+ <div class="rounded-lg bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 px-3 py-3 text-sm text-blue-900 dark:text-blue-100 space-y-2">
2038
+ <p data-i18n-html="snippet.attachment_help">
2039
+ Click <strong>Insert</strong> to choose a file. It will be uploaded under the <code>files/</code> folder and inserted as a <i class="fa-solid fa-paperclip"></i> link in your document.
2040
+ </p>
2041
+ <p data-i18n-html="snippet.attachment_alt" class="text-xs text-blue-700 dark:text-blue-300">
2042
+ Tip: you can also drag &amp; drop a file onto the editor, or paste it from the clipboard.
2043
+ </p>
2044
+ </div>
2045
+ </div>
2046
+
2047
+ <!-- Markdown preview -->
2048
+ <div class="space-y-1.5">
2049
+ <label
2050
+ data-i18n="snippet.markdown_preview_label"
2051
+ class="block text-xs font-medium text-gray-500 dark:text-gray-400"
2052
+ >Markdown preview</label
2053
+ >
2054
+ <pre
2055
+ id="snippet-preview"
2056
+ class="text-xs bg-gray-100 dark:bg-gray-800 rounded-lg px-3 py-2 overflow-x-auto text-gray-700 dark:text-gray-300 whitespace-pre-wrap"
2057
+ ></pre>
2058
+ </div>
2059
+
2060
+ <!-- Actions -->
2061
+ <div class="flex justify-end gap-3 pt-1">
2062
+ <button
2063
+ onclick="closeSnippetsModal()"
2064
+ data-i18n="common.cancel"
2065
+ class="text-sm px-4 py-2 rounded-lg border border-gray-200 dark:border-gray-700 text-gray-600 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors"
2066
+ >
2067
+ Cancel
2068
+ </button>
2069
+ <button
2070
+ onclick="insertSnippet()"
2071
+ data-i18n="snippet.insert_btn"
2072
+ class="text-sm px-4 py-2 rounded-lg bg-blue-600 hover:bg-blue-700 text-white font-semibold transition-colors"
2073
+ >
2074
+ Insert
2075
+ </button>
2076
+ </div>
2077
+ </div>
2078
+ </div>
2079
+
2080
+ <!-- ── Image paste confirmation modal ── -->
2081
+ <div
2082
+ id="img-paste-modal"
2083
+ class="hidden fixed inset-0 z-50 flex items-center justify-center bg-black/50"
2084
+ >
2085
+ <div
2086
+ class="bg-white dark:bg-gray-900 rounded-xl shadow-xl w-full max-w-sm mx-4 p-6 space-y-5"
2087
+ >
2088
+ <h3 class="text-base font-semibold text-gray-900 dark:text-gray-50">
2089
+ 🖼️ <span data-i18n="modal.img_paste.title">Paste clipboard image?</span>
2090
+ </h3>
2091
+ <div class="space-y-1.5">
2092
+ <label
2093
+ class="block text-xs font-medium text-gray-500 dark:text-gray-400"
2094
+ >
2095
+ <span data-i18n="modal.img_paste.filename_label">Filename</span>
2096
+ <span data-i18n="modal.img_paste.saved_hint" class="font-normal text-gray-400"
2097
+ >(saved in images/)</span
2098
+ >
2099
+ </label>
2100
+ <div class="flex items-center gap-2">
2101
+ <input
2102
+ id="img-paste-name"
2103
+ type="text"
2104
+ class="flex-1 px-3 py-2 text-sm rounded-lg border border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 text-gray-900 dark:text-gray-100 font-mono focus:outline-none focus:ring-2 focus:ring-blue-500"
2105
+ />
2106
+ <span
2107
+ id="img-paste-ext"
2108
+ class="text-xs text-gray-400 shrink-0"
2109
+ ></span>
2110
+ </div>
2111
+ </div>
2112
+ <div class="flex justify-end gap-3 pt-1">
2113
+ <button
2114
+ onclick="imgPasteCancel()"
2115
+ data-i18n="common.cancel"
2116
+ class="text-sm px-4 py-2 rounded-lg border border-gray-200 dark:border-gray-700 text-gray-600 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors"
2117
+ >
2118
+ Cancel
2119
+ </button>
2120
+ <button
2121
+ onclick="imgPasteConfirm()"
2122
+ data-i18n="modal.img_paste.paste_btn"
2123
+ class="text-sm px-4 py-2 rounded-lg bg-blue-600 hover:bg-blue-700 text-white font-semibold transition-colors"
2124
+ >
2125
+ Paste
2126
+ </button>
2127
+ </div>
2128
+ </div>
2129
+ </div>
2130
+
2131
+ <!-- ── Marker: post-it creation popup ── -->
2132
+ <div
2133
+ id="stabilo-popup"
2134
+ class="hidden fixed z-50 w-80 bg-yellow-50 dark:bg-yellow-900/80 border-2 border-yellow-300 dark:border-yellow-600 rounded-xl shadow-2xl p-4 flex flex-col gap-3"
2135
+ style="top: 0; left: 0"
2136
+ >
2137
+ <div
2138
+ class="text-xs font-semibold text-yellow-700 dark:text-yellow-300 flex items-center gap-2"
2139
+ >
2140
+ <span>&#9999;</span> <span data-i18n="annotation.title">Annotation</span>
2141
+ </div>
2142
+ <div
2143
+ id="stabilo-selected-preview"
2144
+ class="text-xs text-yellow-800 dark:text-yellow-200 bg-yellow-100 dark:bg-yellow-800/50 rounded px-2 py-1 italic max-h-16 overflow-y-auto border border-yellow-200 dark:border-yellow-700"
2145
+ ></div>
2146
+ <textarea
2147
+ id="stabilo-note-input"
2148
+ rows="4"
2149
+ data-i18n-placeholder="annotation.placeholder"
2150
+ placeholder="Your comment or suggestion…"
2151
+ class="w-full text-sm rounded-lg border border-yellow-300 dark:border-yellow-600 bg-white dark:bg-yellow-950 text-gray-900 dark:text-gray-100 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-yellow-400 resize-none"
2152
+ >
2153
+ </textarea>
2154
+ <div class="flex justify-end gap-2">
2155
+ <button
2156
+ onclick="cancelMarkerPopup()"
2157
+ data-i18n="common.cancel"
2158
+ class="text-sm px-3 py-1.5 rounded-lg border border-yellow-300 dark:border-yellow-600 text-yellow-700 dark:text-yellow-300 hover:bg-yellow-100 dark:hover:bg-yellow-800 transition-colors"
2159
+ >
2160
+ Cancel
2161
+ </button>
2162
+ <button
2163
+ onclick="saveAnnotation()"
2164
+ data-i18n="annotation.save_btn"
2165
+ class="text-sm px-4 py-1.5 rounded-lg bg-yellow-400 hover:bg-yellow-500 dark:bg-yellow-600 dark:hover:bg-yellow-500 text-yellow-900 dark:text-white font-semibold transition-colors"
2166
+ >
2167
+ Save
2168
+ </button>
2169
+ </div>
2170
+ </div>
2171
+
2172
+ <!-- ── Marker: read-only post-it hover popup ── -->
2173
+ <div
2174
+ id="stabilo-read-popup"
2175
+ class="hidden fixed z-50 w-80 bg-yellow-50 dark:bg-yellow-900/80 border-2 border-yellow-300 dark:border-yellow-600 rounded-xl shadow-2xl p-4 flex flex-col gap-2 pointer-events-auto"
2176
+ style="top: 0; left: 0"
2177
+ >
2178
+ <div
2179
+ class="text-xs font-semibold text-yellow-700 dark:text-yellow-300 flex items-center gap-2"
2180
+ >
2181
+ <span>&#9999;</span>
2182
+ <span
2183
+ id="stabilo-read-date"
2184
+ class="font-normal text-yellow-600 dark:text-yellow-400"
2185
+ ></span>
2186
+ </div>
2187
+ <div
2188
+ id="stabilo-read-orphan"
2189
+ data-i18n="annotation.orphan"
2190
+ class="hidden text-xs font-semibold text-red-600 dark:text-red-400 bg-red-50 dark:bg-red-900/30 border border-red-200 dark:border-red-700 rounded px-2 py-1"
2191
+ ></div>
2192
+ <div
2193
+ id="stabilo-read-text"
2194
+ class="text-sm text-gray-800 dark:text-gray-100 whitespace-pre-wrap max-h-48 overflow-y-auto"
2195
+ ></div>
2196
+ <div
2197
+ class="flex justify-end pt-1 border-t border-yellow-200 dark:border-yellow-700"
2198
+ >
2199
+ <button
2200
+ id="stabilo-delete-btn"
2201
+ data-i18n="annotation.delete_btn"
2202
+ class="text-xs px-3 py-1.5 rounded-lg border border-red-200 dark:border-red-700 text-red-500 dark:text-red-400 hover:bg-red-50 dark:hover:bg-red-900/40 transition-colors flex items-center gap-1"
2203
+ >
2204
+ 🗑 Delete
2205
+ </button>
2206
+ </div>
2207
+ </div>
2208
+
2209
+ <!-- ── Marker: delete confirmation ── -->
2210
+ <div
2211
+ id="stabilo-confirm-delete"
2212
+ class="hidden fixed z-50 w-72 bg-white dark:bg-gray-900 border border-red-200 dark:border-red-700 rounded-xl shadow-2xl p-4 flex flex-col gap-3"
2213
+ style="top: 0; left: 0"
2214
+ >
2215
+ <p data-i18n="annotation.confirm_delete" class="text-sm text-gray-700 dark:text-gray-200">
2216
+ Delete this annotation?
2217
+ </p>
2218
+ <div class="flex justify-end gap-2">
2219
+ <button
2220
+ onclick="cancelDeleteAnnotation()"
2221
+ data-i18n="common.cancel"
2222
+ class="text-sm px-3 py-1.5 rounded-lg border border-gray-200 dark:border-gray-700 text-gray-600 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors"
2223
+ >
2224
+ Cancel
2225
+ </button>
2226
+ <button
2227
+ onclick="confirmDeleteAnnotation()"
2228
+ data-i18n="annotation.confirm_btn"
2229
+ class="text-sm px-4 py-1.5 rounded-lg bg-red-500 hover:bg-red-600 text-white font-semibold transition-colors"
2230
+ >
2231
+ Delete
2232
+ </button>
2233
+ </div>
2234
+ </div>
2235
+
2236
+ <!-- ── Document: delete confirmation ── -->
2237
+ <div
2238
+ id="doc-confirm-delete"
2239
+ class="hidden fixed inset-0 z-50 bg-black/50 flex items-center justify-center p-4"
2240
+ onclick="cancelDeleteDocument(event)"
2241
+ >
2242
+ <div
2243
+ class="w-full max-w-sm bg-white dark:bg-gray-900 border border-red-200 dark:border-red-700 rounded-xl shadow-2xl p-5 flex flex-col gap-3"
2244
+ onclick="event.stopPropagation()"
2245
+ >
2246
+ <p data-i18n="doc.confirm_delete" class="text-sm text-gray-700 dark:text-gray-200">
2247
+ Permanently delete this document?
2248
+ </p>
2249
+ <p id="doc-confirm-delete-title" class="text-xs text-gray-500 dark:text-gray-400 italic truncate"></p>
2250
+ <div class="flex justify-end gap-2 mt-2">
2251
+ <button
2252
+ onclick="cancelDeleteDocument()"
2253
+ data-i18n="common.cancel"
2254
+ class="text-sm px-3 py-1.5 rounded-lg border border-gray-200 dark:border-gray-700 text-gray-600 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors"
2255
+ >
2256
+ Cancel
2257
+ </button>
2258
+ <button
2259
+ onclick="confirmDeleteDocument()"
2260
+ data-i18n="doc.confirm_delete_btn"
2261
+ class="text-sm px-4 py-1.5 rounded-lg bg-red-500 hover:bg-red-600 text-white font-semibold transition-colors"
2262
+ >
2263
+ Delete
2264
+ </button>
2265
+ </div>
2266
+ </div>
2267
+ </div>
2268
+
2269
+ <!-- ── Marker: right elevator ── -->
2270
+ <div
2271
+ id="stabilo-elevator"
2272
+ class="hidden fixed top-14 right-0 w-10 flex flex-col items-center py-2 gap-2 z-40 overflow-y-auto"
2273
+ style="max-height: calc(100vh - 3.5rem)"
2274
+ ></div>
2275
+
2276
+ <!-- ── Image lightbox overlay ── -->
2277
+ <div
2278
+ id="img-lightbox"
2279
+ class="hidden fixed inset-0 z-50 bg-black/90 flex items-center justify-center cursor-pointer"
2280
+ onclick="closeLightbox()"
2281
+ >
2282
+ <img
2283
+ id="img-lightbox-img"
2284
+ src=""
2285
+ alt=""
2286
+ class="max-w-full max-h-full object-contain select-none"
2287
+ onclick="event.stopPropagation()"
2288
+ />
2289
+ <button
2290
+ onclick="closeLightbox()"
2291
+ class="absolute top-4 right-4 text-white/70 hover:text-white text-2xl leading-none"
2292
+ >
2293
+ &times;
2294
+ </button>
2295
+ </div>
2296
+
2297
+ <!-- ── Word Cloud overlay ── -->
2298
+ <div
2299
+ id="wc-overlay"
2300
+ class="hidden fixed inset-0 z-50 flex flex-col bg-white dark:bg-gray-950"
2301
+ >
2302
+ <div
2303
+ class="flex items-center justify-between px-6 h-14 border-b border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900 shrink-0"
2304
+ >
2305
+ <h2 data-i18n="wc.title" class="font-semibold text-base">&#9729; Word Cloud</h2>
2306
+ <button
2307
+ onclick="closeWordCloud()"
2308
+ data-i18n="wc.close_btn"
2309
+ class="text-sm px-3 py-1.5 rounded-lg text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors"
2310
+ >
2311
+ ✕ Close
2312
+ </button>
2313
+ </div>
2314
+ <!-- Search root toolbar -->
2315
+ <div
2316
+ class="border-b border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900 shrink-0"
2317
+ >
2318
+ <!-- Row 1: path + browse + launch -->
2319
+ <div class="flex items-center gap-3 px-6 py-3">
2320
+ <label
2321
+ data-i18n="wc.search_root_label"
2322
+ class="text-xs font-medium text-gray-500 dark:text-gray-400 shrink-0"
2323
+ >Search root</label
2324
+ >
2325
+ <input
2326
+ id="wc-root"
2327
+ type="text"
2328
+ readonly
2329
+ spellcheck="false"
2330
+ class="flex-1 px-3 py-1.5 text-sm rounded-lg border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800 text-gray-900 dark:text-gray-100 font-mono cursor-default focus:outline-none"
2331
+ />
2332
+ <button
2333
+ onclick="wcToggleBrowser()"
2334
+ class="text-sm px-3 py-1.5 rounded-lg border border-gray-200 dark:border-gray-700 text-gray-600 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors shrink-0"
2335
+ >
2336
+ &#128193; <span data-i18n="wc.browse_btn">Browse</span>
2337
+ </button>
2338
+ <button
2339
+ onclick="launchWordCloud()"
2340
+ data-i18n="wc.launch_btn"
2341
+ class="text-sm px-4 py-1.5 rounded-lg bg-blue-600 hover:bg-blue-700 text-white font-semibold transition-colors shrink-0"
2342
+ >
2343
+ &#9654; Launch
2344
+ </button>
2345
+ </div>
2346
+ <!-- Row 1b: exclude folders -->
2347
+ <div class="flex items-start gap-3 px-6 pb-2">
2348
+ <label
2349
+ data-i18n="wc.exclude_label"
2350
+ class="text-xs font-medium text-gray-500 dark:text-gray-400 shrink-0 pt-1.5"
2351
+ >Exclude</label
2352
+ >
2353
+ <textarea
2354
+ id="wc-exclude"
2355
+ rows="3"
2356
+ spellcheck="false"
2357
+ data-i18n-placeholder="wc.exclude_placeholder"
2358
+ placeholder="components/ui, package-lock.json (one per line or comma-separated)"
2359
+ oninput="wcOnExcludeChange()"
2360
+ class="flex-1 px-3 py-1.5 text-sm rounded-lg border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800 text-gray-900 dark:text-gray-100 font-mono focus:outline-none focus:ring-1 focus:ring-blue-400 resize-y"
2361
+ ></textarea>
2362
+ <button
2363
+ onclick="wcToggleExcludeBrowser()"
2364
+ class="text-sm px-3 py-1.5 rounded-lg border border-gray-200 dark:border-gray-700 text-gray-600 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors shrink-0"
2365
+ >
2366
+ &#128193; <span data-i18n="wc.browse_btn">Browse</span>
2367
+ </button>
2368
+ </div>
2369
+ <!-- Row 1c: exclude browser (collapsed by default) -->
2370
+ <div
2371
+ id="wc-exclude-browser"
2372
+ class="hidden border-t border-gray-200 dark:border-gray-700"
2373
+ >
2374
+ <div
2375
+ class="flex items-center gap-2 px-3 py-2 bg-gray-50 dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700"
2376
+ >
2377
+ <button
2378
+ id="wc-excl-browse-up"
2379
+ onclick="wcExclBrowseUp()"
2380
+ class="text-xs text-blue-600 dark:text-blue-400 hover:underline disabled:opacity-30 disabled:pointer-events-none shrink-0"
2381
+ >
2382
+ &#8593; Up
2383
+ </button>
2384
+ <span
2385
+ id="wc-excl-browse-path"
2386
+ class="font-mono text-xs text-gray-400 dark:text-gray-500 truncate flex-1 text-right"
2387
+ ></span>
2388
+ </div>
2389
+ <div
2390
+ id="wc-excl-browse-list"
2391
+ class="divide-y divide-gray-100 dark:divide-gray-800 max-h-52 overflow-y-auto"
2392
+ ></div>
2393
+ </div>
2394
+ <!-- Row 2: extension checkboxes -->
2395
+ <div class="flex items-center gap-x-4 gap-y-2 px-6 pb-3 flex-wrap">
2396
+ <span
2397
+ data-i18n="wc.extensions_label"
2398
+ class="text-xs font-medium text-gray-500 dark:text-gray-400 shrink-0"
2399
+ >Extensions</span
2400
+ >
2401
+ <button
2402
+ onclick="wcToggleAllExts()"
2403
+ id="wcToggleAllBtn"
2404
+ data-i18n="wc.all_btn"
2405
+ class="text-xs text-blue-600 dark:text-blue-400 hover:underline shrink-0"
2406
+ >
2407
+ All
2408
+ </button>
2409
+ <label
2410
+ class="flex items-center gap-1.5 text-xs text-gray-700 dark:text-gray-300 cursor-pointer"
2411
+ ><input type="checkbox" class="wc-ext" value="md" checked />
2412
+ .md</label
2413
+ >
2414
+ <label
2415
+ class="flex items-center gap-1.5 text-xs text-gray-700 dark:text-gray-300 cursor-pointer"
2416
+ ><input type="checkbox" class="wc-ext" value="mdx" /> .mdx</label
2417
+ >
2418
+ <label
2419
+ class="flex items-center gap-1.5 text-xs text-gray-700 dark:text-gray-300 cursor-pointer"
2420
+ ><input type="checkbox" class="wc-ext" value="txt" /> .txt</label
2421
+ >
2422
+ <span class="text-gray-200 dark:text-gray-700 text-xs">|</span>
2423
+ <label
2424
+ class="flex items-center gap-1.5 text-xs text-gray-700 dark:text-gray-300 cursor-pointer"
2425
+ ><input type="checkbox" class="wc-ext" value="ts" /> .ts</label
2426
+ >
2427
+ <label
2428
+ class="flex items-center gap-1.5 text-xs text-gray-700 dark:text-gray-300 cursor-pointer"
2429
+ ><input type="checkbox" class="wc-ext" value="tsx" /> .tsx</label
2430
+ >
2431
+ <label
2432
+ class="flex items-center gap-1.5 text-xs text-gray-700 dark:text-gray-300 cursor-pointer"
2433
+ ><input type="checkbox" class="wc-ext" value="js" /> .js</label
2434
+ >
2435
+ <label
2436
+ class="flex items-center gap-1.5 text-xs text-gray-700 dark:text-gray-300 cursor-pointer"
2437
+ ><input type="checkbox" class="wc-ext" value="jsx" /> .jsx</label
2438
+ >
2439
+ <label
2440
+ class="flex items-center gap-1.5 text-xs text-gray-700 dark:text-gray-300 cursor-pointer"
2441
+ ><input type="checkbox" class="wc-ext" value="mjs" /> .mjs</label
2442
+ >
2443
+ <span class="text-gray-200 dark:text-gray-700 text-xs">|</span>
2444
+ <label
2445
+ class="flex items-center gap-1.5 text-xs text-gray-700 dark:text-gray-300 cursor-pointer"
2446
+ ><input type="checkbox" class="wc-ext" value="java" /> .java</label
2447
+ >
2448
+ <label
2449
+ class="flex items-center gap-1.5 text-xs text-gray-700 dark:text-gray-300 cursor-pointer"
2450
+ ><input type="checkbox" class="wc-ext" value="kt" /> .kt</label
2451
+ >
2452
+ <label
2453
+ class="flex items-center gap-1.5 text-xs text-gray-700 dark:text-gray-300 cursor-pointer"
2454
+ ><input type="checkbox" class="wc-ext" value="py" /> .py</label
2455
+ >
2456
+ <label
2457
+ class="flex items-center gap-1.5 text-xs text-gray-700 dark:text-gray-300 cursor-pointer"
2458
+ ><input type="checkbox" class="wc-ext" value="go" /> .go</label
2459
+ >
2460
+ <label
2461
+ class="flex items-center gap-1.5 text-xs text-gray-700 dark:text-gray-300 cursor-pointer"
2462
+ ><input type="checkbox" class="wc-ext" value="rs" /> .rs</label
2463
+ >
2464
+ <label
2465
+ class="flex items-center gap-1.5 text-xs text-gray-700 dark:text-gray-300 cursor-pointer"
2466
+ ><input type="checkbox" class="wc-ext" value="cs" /> .cs</label
2467
+ >
2468
+ <label
2469
+ class="flex items-center gap-1.5 text-xs text-gray-700 dark:text-gray-300 cursor-pointer"
2470
+ ><input type="checkbox" class="wc-ext" value="swift" />
2471
+ .swift</label
2472
+ >
2473
+ <label
2474
+ class="flex items-center gap-1.5 text-xs text-gray-700 dark:text-gray-300 cursor-pointer"
2475
+ ><input type="checkbox" class="wc-ext" value="rb" /> .rb</label
2476
+ >
2477
+ <span class="text-gray-200 dark:text-gray-700 text-xs">|</span>
2478
+ <label
2479
+ class="flex items-center gap-1.5 text-xs text-gray-700 dark:text-gray-300 cursor-pointer"
2480
+ ><input type="checkbox" class="wc-ext" value="prisma" />
2481
+ .prisma</label
2482
+ >
2483
+ <label
2484
+ class="flex items-center gap-1.5 text-xs text-gray-700 dark:text-gray-300 cursor-pointer"
2485
+ ><input type="checkbox" class="wc-ext" value="graphql" />
2486
+ .graphql</label
2487
+ >
2488
+ <label
2489
+ class="flex items-center gap-1.5 text-xs text-gray-700 dark:text-gray-300 cursor-pointer"
2490
+ ><input type="checkbox" class="wc-ext" value="gql" /> .gql</label
2491
+ >
2492
+ <span class="text-gray-200 dark:text-gray-700 text-xs">|</span>
2493
+ <label
2494
+ class="flex items-center gap-1.5 text-xs text-gray-700 dark:text-gray-300 cursor-pointer"
2495
+ ><input type="checkbox" class="wc-ext" value="html" /> .html</label
2496
+ >
2497
+ <label
2498
+ class="flex items-center gap-1.5 text-xs text-gray-700 dark:text-gray-300 cursor-pointer"
2499
+ ><input type="checkbox" class="wc-ext" value="css" /> .css</label
2500
+ >
2501
+ <label
2502
+ class="flex items-center gap-1.5 text-xs text-gray-700 dark:text-gray-300 cursor-pointer"
2503
+ ><input type="checkbox" class="wc-ext" value="scss" /> .scss</label
2504
+ >
2505
+ <span class="text-gray-200 dark:text-gray-700 text-xs">|</span>
2506
+ <label
2507
+ class="flex items-center gap-1.5 text-xs text-gray-700 dark:text-gray-300 cursor-pointer"
2508
+ ><input type="checkbox" class="wc-ext" value="yml" /> .yml</label
2509
+ >
2510
+ <label
2511
+ class="flex items-center gap-1.5 text-xs text-gray-700 dark:text-gray-300 cursor-pointer"
2512
+ ><input type="checkbox" class="wc-ext" value="yaml" /> .yaml</label
2513
+ >
2514
+ <label
2515
+ class="flex items-center gap-1.5 text-xs text-gray-700 dark:text-gray-300 cursor-pointer"
2516
+ ><input type="checkbox" class="wc-ext" value="json" /> .json</label
2517
+ >
2518
+ <label
2519
+ class="flex items-center gap-1.5 text-xs text-gray-700 dark:text-gray-300 cursor-pointer"
2520
+ ><input type="checkbox" class="wc-ext" value="xml" /> .xml</label
2521
+ >
2522
+ <label
2523
+ class="flex items-center gap-1.5 text-xs text-gray-700 dark:text-gray-300 cursor-pointer"
2524
+ ><input type="checkbox" class="wc-ext" value="toml" /> .toml</label
2525
+ >
2526
+ </div>
2527
+ <!-- Row 3: inline folder browser (collapsed by default) -->
2528
+ <div
2529
+ id="wc-browser"
2530
+ class="hidden border-t border-gray-200 dark:border-gray-700"
2531
+ >
2532
+ <div
2533
+ class="flex items-center gap-2 px-3 py-2 bg-gray-50 dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700"
2534
+ >
2535
+ <button
2536
+ id="wc-browse-up"
2537
+ onclick="wcBrowseUp()"
2538
+ class="text-xs text-blue-600 dark:text-blue-400 hover:underline disabled:opacity-30 disabled:pointer-events-none shrink-0"
2539
+ >
2540
+ &#8593; Up
2541
+ </button>
2542
+ <span
2543
+ id="wc-browse-path"
2544
+ class="font-mono text-xs text-gray-400 dark:text-gray-500 truncate flex-1 text-right"
2545
+ ></span>
2546
+ </div>
2547
+ <div
2548
+ id="wc-browse-list"
2549
+ class="divide-y divide-gray-100 dark:divide-gray-800 max-h-52 overflow-y-auto"
2550
+ ></div>
2551
+ </div>
2552
+ </div>
2553
+ <div id="wc-body" class="flex-1 overflow-hidden flex">
2554
+ <!-- Left sidebar: stats + controls + top-50 words -->
2555
+ <div
2556
+ id="wc-sidebar"
2557
+ class="hidden w-56 shrink-0 border-r border-gray-200 dark:border-gray-800 flex flex-col bg-white dark:bg-gray-900 overflow-hidden"
2558
+ >
2559
+ <div
2560
+ id="wc-stats"
2561
+ class="px-3 py-2 text-xs text-gray-500 dark:text-gray-400 space-y-0.5 border-b border-gray-100 dark:border-gray-800"
2562
+ ></div>
2563
+ <div
2564
+ class="px-3 py-2 border-b border-gray-100 dark:border-gray-800 flex items-center gap-2"
2565
+ >
2566
+ <span data-i18n="wc.min_files_label" class="text-xs text-gray-500 dark:text-gray-400 shrink-0"
2567
+ >Min files</span
2568
+ >
2569
+ <input
2570
+ id="wc-min-files"
2571
+ type="number"
2572
+ min="1"
2573
+ max="20"
2574
+ value="1"
2575
+ class="w-12 px-1.5 py-0.5 text-xs rounded border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800 text-gray-800 dark:text-gray-200 focus:outline-none"
2576
+ />
2577
+ <button
2578
+ onclick="wcApplyFilter()"
2579
+ data-i18n="wc.apply_btn"
2580
+ class="text-xs text-blue-600 dark:text-blue-400 hover:underline"
2581
+ >
2582
+ Apply
2583
+ </button>
2584
+ </div>
2585
+ <p
2586
+ data-i18n="wc.top_words"
2587
+ class="px-3 pt-2 pb-1 text-xs font-semibold text-gray-400 dark:text-gray-600 uppercase tracking-wider"
2588
+ >
2589
+ Top words
2590
+ </p>
2591
+ <div id="wc-top-list" class="flex-1 overflow-y-auto"></div>
2592
+ </div>
2593
+ <!-- Canvas / status area -->
2594
+ <div id="wc-canvas-wrap" class="flex-1 relative overflow-hidden">
2595
+ <p
2596
+ id="wc-status"
2597
+ class="absolute inset-0 flex items-center justify-center text-gray-400 dark:text-gray-500 text-sm animate-pulse"
2598
+ ></p>
2599
+ <canvas
2600
+ id="wc-canvas"
2601
+ class="hidden"
2602
+ style="position: absolute; inset: 0"
2603
+ ></canvas>
2604
+ </div>
2605
+ <!-- Right detail panel: shown on word click -->
2606
+ <div
2607
+ id="wc-detail"
2608
+ class="hidden w-80 shrink-0 border-l border-gray-200 dark:border-gray-800 flex flex-col bg-white dark:bg-gray-900 overflow-hidden"
2609
+ >
2610
+ <div
2611
+ class="px-3 py-2 border-b border-gray-100 dark:border-gray-800 flex items-center justify-between"
2612
+ >
2613
+ <span
2614
+ id="wc-detail-word"
2615
+ class="font-bold text-sm text-gray-900 dark:text-gray-100"
2616
+ ></span>
2617
+ <button
2618
+ onclick="wcCloseDetail()"
2619
+ class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-200 text-xs leading-none"
2620
+ >
2621
+
2622
+ </button>
2623
+ </div>
2624
+ <p
2625
+ data-i18n="wc.found_in"
2626
+ class="px-3 pt-2 pb-1 text-xs font-semibold text-gray-400 dark:text-gray-600 uppercase tracking-wider"
2627
+ >
2628
+ Found in
2629
+ </p>
2630
+ <div
2631
+ id="wc-detail-files"
2632
+ class="flex-1 overflow-y-auto px-3 py-1 space-y-1 text-xs"
2633
+ ></div>
2634
+ </div>
2635
+ </div>
2636
+ </div>
2637
+ <!-- All inline logic extracted to modules under src/frontend/*.js -->
2638
+
2639
+ <!-- ── Export modal (Markdown / HTML / PDF) ────────────────────────────── -->
2640
+ <div
2641
+ id="export-modal"
2642
+ class="hidden fixed inset-0 z-50 flex items-center justify-center bg-black/50"
2643
+ >
2644
+ <div class="bg-white dark:bg-gray-900 rounded-xl shadow-xl w-full max-w-sm mx-4 p-6 space-y-4">
2645
+ <!-- Title -->
2646
+ <h3 class="text-base font-semibold text-gray-900 dark:text-gray-50">
2647
+ <i class="fa-solid fa-file-export mr-2 text-blue-500"></i>
2648
+ <span data-i18n="modal.export.title">Export</span>
2649
+ </h3>
2650
+
2651
+ <!-- Tabs -->
2652
+ <div class="flex gap-0 border-b border-gray-200 dark:border-gray-700">
2653
+ <button
2654
+ id="export-tab-markdown"
2655
+ onclick="switchExportTab('markdown')"
2656
+ data-i18n="modal.export.tab_markdown"
2657
+ class="px-4 py-2 text-sm border-b-2 border-blue-500 text-blue-600 dark:text-blue-400 font-semibold transition-colors"
2658
+ >Markdown</button>
2659
+ <button
2660
+ id="export-tab-html"
2661
+ onclick="switchExportTab('html')"
2662
+ data-i18n="modal.export.tab_html"
2663
+ class="px-4 py-2 text-sm text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300 transition-colors"
2664
+ >HTML</button>
2665
+ <button
2666
+ id="export-tab-pdf"
2667
+ onclick="switchExportTab('pdf')"
2668
+ data-i18n="modal.export.tab_pdf"
2669
+ class="px-4 py-2 text-sm text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300 transition-colors"
2670
+ >PDF</button>
2671
+ </div>
2672
+
2673
+ <!-- Folder list (html tab only) -->
2674
+ <div id="export-folder-section" class="hidden">
2675
+ <div
2676
+ id="export-folder-list"
2677
+ class="max-h-48 overflow-y-auto space-y-0.5 border border-gray-200 dark:border-gray-700 rounded-lg p-2"
2678
+ ></div>
2679
+ </div>
2680
+
2681
+ <!-- Markdown tab content -->
2682
+ <div id="export-content-markdown">
2683
+ <p class="text-xs text-gray-500 dark:text-gray-400" data-i18n="modal.export.markdown_hint">
2684
+ Export all documents and images as Markdown files, with internal links rewritten to relative paths.
2685
+ </p>
2686
+ <div class="flex justify-end gap-3 pt-2">
2687
+ <button
2688
+ onclick="closeExportModal()"
2689
+ data-i18n="common.cancel"
2690
+ class="text-sm px-4 py-2 rounded-lg border border-gray-200 dark:border-gray-700 text-gray-600 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors"
2691
+ >Cancel</button>
2692
+ <button
2693
+ id="export-md-btn"
2694
+ onclick="exportMarkdown()"
2695
+ data-i18n="modal.export.markdown_btn"
2696
+ class="text-sm px-4 py-2 rounded-lg bg-blue-600 hover:bg-blue-700 text-white font-semibold transition-colors"
2697
+ >Export Markdown</button>
2698
+ </div>
2699
+ </div>
2700
+
2701
+ <!-- HTML tab content -->
2702
+ <div id="export-content-html" class="hidden">
2703
+ <p class="text-xs text-gray-500 dark:text-gray-400" data-i18n="modal.export_html.hint">
2704
+ Select the folders to include in the ZIP archive.
2705
+ </p>
2706
+ <div class="flex justify-end gap-3 pt-2">
2707
+ <button
2708
+ onclick="closeExportModal()"
2709
+ data-i18n="common.cancel"
2710
+ class="text-sm px-4 py-2 rounded-lg border border-gray-200 dark:border-gray-700 text-gray-600 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors"
2711
+ >Cancel</button>
2712
+ <button
2713
+ id="export-notion-btn"
2714
+ onclick="exportHtml('notion')"
2715
+ data-i18n="modal.export_html.notion_btn"
2716
+ class="text-sm px-4 py-2 rounded-lg border border-blue-500 text-blue-600 dark:text-blue-400 hover:bg-blue-50 dark:hover:bg-blue-900/20 font-semibold transition-colors"
2717
+ >Export Notion</button>
2718
+ <button
2719
+ id="export-confluence-btn"
2720
+ onclick="exportHtml('confluence')"
2721
+ data-i18n="modal.export_html.confluence_btn"
2722
+ class="text-sm px-4 py-2 rounded-lg bg-blue-600 hover:bg-blue-700 text-white font-semibold transition-colors"
2723
+ >Export Confluence</button>
2724
+ </div>
2725
+ </div>
2726
+
2727
+ <!-- PDF tab content -->
2728
+ <div id="export-content-pdf" class="hidden">
2729
+ <p class="text-xs text-gray-500 dark:text-gray-400" data-i18n="modal.export.pdf_hint">
2730
+ Export all documents as a single PDF with table of contents.
2731
+ </p>
2732
+ <div class="flex justify-end gap-3 pt-2">
2733
+ <button
2734
+ onclick="closeExportModal()"
2735
+ data-i18n="common.cancel"
2736
+ class="text-sm px-4 py-2 rounded-lg border border-gray-200 dark:border-gray-700 text-gray-600 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors"
2737
+ >Cancel</button>
2738
+ <button
2739
+ id="export-pdf-btn"
2740
+ onclick="exportAllPdfFromModal()"
2741
+ data-i18n="modal.export.pdf_btn"
2742
+ class="text-sm px-4 py-2 rounded-lg bg-blue-600 hover:bg-blue-700 text-white font-semibold transition-colors"
2743
+ >Export all PDF</button>
2744
+ </div>
2745
+ </div>
2746
+ </div>
2747
+ </div>
2748
+
2749
+ <!-- ── Generic confirmation modal (used by showConfirm) ── -->
2750
+ <div
2751
+ id="confirm-modal"
2752
+ class="hidden fixed inset-0 z-[60] bg-black/50 flex items-center justify-center p-4"
2753
+ onclick="_confirmModalBackdrop(event)"
2754
+ >
2755
+ <div
2756
+ class="w-full max-w-md bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-700 rounded-xl shadow-2xl p-5 flex flex-col gap-3"
2757
+ onclick="event.stopPropagation()"
2758
+ >
2759
+ <h3 id="confirm-modal-title" class="font-semibold text-gray-900 dark:text-gray-100"></h3>
2760
+ <p id="confirm-modal-message" class="text-sm text-gray-700 dark:text-gray-200"></p>
2761
+ <p id="confirm-modal-detail" class="text-xs text-gray-500 dark:text-gray-400 italic break-all"></p>
2762
+ <div class="flex justify-end gap-2 mt-2">
2763
+ <button
2764
+ id="confirm-modal-cancel"
2765
+ type="button"
2766
+ class="text-sm px-3 py-1.5 rounded-lg border border-gray-200 dark:border-gray-700 text-gray-600 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors"
2767
+ ></button>
2768
+ <button
2769
+ id="confirm-modal-ok"
2770
+ type="button"
2771
+ class="text-sm px-4 py-1.5 rounded-lg text-white font-semibold transition-colors"
2772
+ ></button>
2773
+ </div>
2774
+ </div>
2775
+ </div>
2776
+
2777
+ <!-- ── Files modal ── -->
2778
+ <div
2779
+ id="files-modal"
2780
+ class="hidden fixed inset-0 z-50 flex items-center justify-center bg-black/40"
2781
+ onclick="if(event.target===this)closeFilesModal()"
2782
+ >
2783
+ <div
2784
+ class="bg-white dark:bg-gray-900 rounded-xl shadow-xl w-[min(720px,92vw)] max-h-[85vh] flex flex-col border border-gray-200 dark:border-gray-800"
2785
+ >
2786
+ <div
2787
+ class="flex items-center justify-between px-5 py-3 border-b border-gray-200 dark:border-gray-800 shrink-0"
2788
+ >
2789
+ <h2 data-i18n="files.title" class="font-semibold text-base text-gray-900 dark:text-gray-100">
2790
+ &#128193; Files
2791
+ </h2>
2792
+ <button
2793
+ onclick="closeFilesModal()"
2794
+ data-i18n-title="common.close"
2795
+ class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-200 text-xl leading-none px-2"
2796
+ >
2797
+ &times;
2798
+ </button>
2799
+ </div>
2800
+ <div id="files-modal-body" class="flex-1 overflow-y-auto p-5">
2801
+ <p class="text-sm text-gray-400" data-i18n="common.loading">Loading…</p>
2802
+ </div>
2803
+ </div>
2804
+ </div>
2805
+ </body>
2806
+ </html>