living-documentation 7.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 (173) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +329 -0
  3. package/dist/bin/cli.d.ts +3 -0
  4. package/dist/bin/cli.d.ts.map +1 -0
  5. package/dist/bin/cli.js +62 -0
  6. package/dist/bin/cli.js.map +1 -0
  7. package/dist/src/frontend/admin.html +1073 -0
  8. package/dist/src/frontend/annotations.js +546 -0
  9. package/dist/src/frontend/boot.js +90 -0
  10. package/dist/src/frontend/config.js +19 -0
  11. package/dist/src/frontend/dark-mode.js +20 -0
  12. package/dist/src/frontend/diagram/alignment.js +161 -0
  13. package/dist/src/frontend/diagram/clipboard.js +172 -0
  14. package/dist/src/frontend/diagram/constants.js +109 -0
  15. package/dist/src/frontend/diagram/debug.js +43 -0
  16. package/dist/src/frontend/diagram/edge-panel.js +260 -0
  17. package/dist/src/frontend/diagram/edge-rendering.js +12 -0
  18. package/dist/src/frontend/diagram/grid.js +78 -0
  19. package/dist/src/frontend/diagram/groups.js +102 -0
  20. package/dist/src/frontend/diagram/history.js +153 -0
  21. package/dist/src/frontend/diagram/image-name-modal.js +48 -0
  22. package/dist/src/frontend/diagram/image-upload.js +36 -0
  23. package/dist/src/frontend/diagram/label-editor.js +115 -0
  24. package/dist/src/frontend/diagram/link-panel.js +144 -0
  25. package/dist/src/frontend/diagram/main.js +299 -0
  26. package/dist/src/frontend/diagram/network.js +1473 -0
  27. package/dist/src/frontend/diagram/node-panel.js +267 -0
  28. package/dist/src/frontend/diagram/node-rendering.js +773 -0
  29. package/dist/src/frontend/diagram/persistence.js +161 -0
  30. package/dist/src/frontend/diagram/ports.js +386 -0
  31. package/dist/src/frontend/diagram/selection-overlay.js +336 -0
  32. package/dist/src/frontend/diagram/state.js +39 -0
  33. package/dist/src/frontend/diagram/t.js +3 -0
  34. package/dist/src/frontend/diagram/toast.js +21 -0
  35. package/dist/src/frontend/diagram/unlock-hold.js +182 -0
  36. package/dist/src/frontend/diagram/zoom.js +20 -0
  37. package/dist/src/frontend/diagram-link-modal.js +137 -0
  38. package/dist/src/frontend/diagram.html +1279 -0
  39. package/dist/src/frontend/documents.js +373 -0
  40. package/dist/src/frontend/export.js +338 -0
  41. package/dist/src/frontend/i18n/en.json +406 -0
  42. package/dist/src/frontend/i18n/fr.json +406 -0
  43. package/dist/src/frontend/i18n.js +32 -0
  44. package/dist/src/frontend/image-paste.js +101 -0
  45. package/dist/src/frontend/index.html +2314 -0
  46. package/dist/src/frontend/misc.js +25 -0
  47. package/dist/src/frontend/new-doc-modal.js +260 -0
  48. package/dist/src/frontend/new-folder-modal.js +174 -0
  49. package/dist/src/frontend/search.js +157 -0
  50. package/dist/src/frontend/sidebar-helpers.js +58 -0
  51. package/dist/src/frontend/sidebar.js +182 -0
  52. package/dist/src/frontend/snippet-detect.js +25 -0
  53. package/dist/src/frontend/snippet-table.js +85 -0
  54. package/dist/src/frontend/snippet-tree.js +94 -0
  55. package/dist/src/frontend/snippets.js +534 -0
  56. package/dist/src/frontend/state.js +28 -0
  57. package/dist/src/frontend/utils.js +21 -0
  58. package/dist/src/frontend/vendor/wordcloud2.js +1187 -0
  59. package/dist/src/frontend/wordcloud.js +693 -0
  60. package/dist/src/lib/config.d.ts +17 -0
  61. package/dist/src/lib/config.d.ts.map +1 -0
  62. package/dist/src/lib/config.js +79 -0
  63. package/dist/src/lib/config.js.map +1 -0
  64. package/dist/src/lib/parser.d.ts +11 -0
  65. package/dist/src/lib/parser.d.ts.map +1 -0
  66. package/dist/src/lib/parser.js +111 -0
  67. package/dist/src/lib/parser.js.map +1 -0
  68. package/dist/src/mcp/server.d.ts +3 -0
  69. package/dist/src/mcp/server.d.ts.map +1 -0
  70. package/dist/src/mcp/server.js +986 -0
  71. package/dist/src/mcp/server.js.map +1 -0
  72. package/dist/src/mcp/tools/diagrams.d.ts +44 -0
  73. package/dist/src/mcp/tools/diagrams.d.ts.map +1 -0
  74. package/dist/src/mcp/tools/diagrams.js +245 -0
  75. package/dist/src/mcp/tools/diagrams.js.map +1 -0
  76. package/dist/src/mcp/tools/documents.d.ts +26 -0
  77. package/dist/src/mcp/tools/documents.d.ts.map +1 -0
  78. package/dist/src/mcp/tools/documents.js +127 -0
  79. package/dist/src/mcp/tools/documents.js.map +1 -0
  80. package/dist/src/mcp/tools/source.d.ts +29 -0
  81. package/dist/src/mcp/tools/source.d.ts.map +1 -0
  82. package/dist/src/mcp/tools/source.js +200 -0
  83. package/dist/src/mcp/tools/source.js.map +1 -0
  84. package/dist/src/routes/annotations.d.ts +3 -0
  85. package/dist/src/routes/annotations.d.ts.map +1 -0
  86. package/dist/src/routes/annotations.js +83 -0
  87. package/dist/src/routes/annotations.js.map +1 -0
  88. package/dist/src/routes/browse.d.ts +3 -0
  89. package/dist/src/routes/browse.d.ts.map +1 -0
  90. package/dist/src/routes/browse.js +75 -0
  91. package/dist/src/routes/browse.js.map +1 -0
  92. package/dist/src/routes/config.d.ts +3 -0
  93. package/dist/src/routes/config.d.ts.map +1 -0
  94. package/dist/src/routes/config.js +97 -0
  95. package/dist/src/routes/config.js.map +1 -0
  96. package/dist/src/routes/diagrams.d.ts +3 -0
  97. package/dist/src/routes/diagrams.d.ts.map +1 -0
  98. package/dist/src/routes/diagrams.js +69 -0
  99. package/dist/src/routes/diagrams.js.map +1 -0
  100. package/dist/src/routes/documents.d.ts +8 -0
  101. package/dist/src/routes/documents.d.ts.map +1 -0
  102. package/dist/src/routes/documents.js +332 -0
  103. package/dist/src/routes/documents.js.map +1 -0
  104. package/dist/src/routes/export.d.ts +3 -0
  105. package/dist/src/routes/export.d.ts.map +1 -0
  106. package/dist/src/routes/export.js +277 -0
  107. package/dist/src/routes/export.js.map +1 -0
  108. package/dist/src/routes/images.d.ts +3 -0
  109. package/dist/src/routes/images.d.ts.map +1 -0
  110. package/dist/src/routes/images.js +49 -0
  111. package/dist/src/routes/images.js.map +1 -0
  112. package/dist/src/routes/wordcloud.d.ts +3 -0
  113. package/dist/src/routes/wordcloud.d.ts.map +1 -0
  114. package/dist/src/routes/wordcloud.js +95 -0
  115. package/dist/src/routes/wordcloud.js.map +1 -0
  116. package/dist/src/server.d.ts +7 -0
  117. package/dist/src/server.d.ts.map +1 -0
  118. package/dist/src/server.js +76 -0
  119. package/dist/src/server.js.map +1 -0
  120. package/dist/starting-doc/.annotations.json +3 -0
  121. package/dist/starting-doc/.diagrams.json +1884 -0
  122. package/dist/starting-doc/.living-doc.json +39 -0
  123. package/dist/starting-doc/1_tutorial/2026_04_11_13_25_[General]_crer_vos_dossiers.md +16 -0
  124. package/dist/starting-doc/1_tutorial/2026_04_11_18_58_[General]_creer_un_document_dans_un_dossier.md +9 -0
  125. package/dist/starting-doc/1_tutorial/2026_04_12_09_00_[General]_editer_et_sauvegarder.md +39 -0
  126. package/dist/starting-doc/1_tutorial/2026_04_12_10_00_[General]_utiliser_les_snippets.md +71 -0
  127. package/dist/starting-doc/2026_04_08_20_52_[General]_welcome.md +17 -0
  128. package/dist/starting-doc/2026_04_11_12_55_[General]_premiers_pas.md +271 -0
  129. package/dist/starting-doc/2_guide/2026_04_08_00_04_[DOCUMENT]_utilisation_des_images_plein_ecran_lien_clickable.md +40 -0
  130. package/dist/starting-doc/2_guide/2026_04_08_23_38_[Configuration]_demarrage_de_living_documentation.md +32 -0
  131. package/dist/starting-doc/2_guide/2026_04_09_09_00_[NAVIGATION]_recherche_plein_texte.md +65 -0
  132. package/dist/starting-doc/2_guide/2026_04_09_10_00_[EXPORT]_exporter_en_pdf.md +43 -0
  133. package/dist/starting-doc/2_guide/2026_04_09_11_00_[Configuration]_configurer_le_panneau_admin.md +55 -0
  134. package/dist/starting-doc/2_guide/2026_04_09_12_00_[Configuration]_extra_files.md +68 -0
  135. package/dist/starting-doc/2_guide/2026_04_09_13_00_[WORDCLOUD]_word_cloud.md +54 -0
  136. package/dist/starting-doc/2_guide/2026_04_09_14_00_[DIAGRAM]_creer_et_lier_un_diagramme.md +77 -0
  137. package/dist/starting-doc/3_concept/2026_04_08_20_58_[DOCUMENTING]_ADRS.md +20 -0
  138. package/dist/starting-doc/3_concept/2026_04_08_22_15_[DOCUMENTING]_living_documentation.md +17 -0
  139. package/dist/starting-doc/3_concept/2026_04_08_22_46_[METHODOLOGY]_diataxis_architecture_du_contenu.md +16 -0
  140. package/dist/starting-doc/4_reference/2026_04_08_23_14_[FUNDAMENTALS]_the_living_documentation_tool.md +41 -0
  141. package/dist/starting-doc/4_reference/2026_04_09_01_00_[REFERENCE]_raccourcis_clavier.md +61 -0
  142. package/dist/starting-doc/4_reference/2026_04_09_02_00_[REFERENCE]_tokens_pattern_nommage.md +75 -0
  143. package/dist/starting-doc/4_reference/2026_04_09_03_00_[REFERENCE]_types_de_snippets.md +68 -0
  144. package/dist/starting-doc/4_reference/2026_04_11_17_31_[FUNDAMENTALS]_architecturer_une_documentation.md +12 -0
  145. package/dist/starting-doc/4_reference/2026_04_12_14_07_[FUNDAMENTALS]_dossiers_et_catgories.md +89 -0
  146. package/dist/starting-doc/images/admin_screenshot.png +0 -0
  147. package/dist/starting-doc/images/ajout-document.png +0 -0
  148. package/dist/starting-doc/images/ajouter-document-categorie.png +0 -0
  149. package/dist/starting-doc/images/ajouter_un_document_dans_un_dossier.png +0 -0
  150. package/dist/starting-doc/images/architecturer_une_documentation_reference.png +0 -0
  151. package/dist/starting-doc/images/cr_er_un_document.png +0 -0
  152. package/dist/starting-doc/images/creation-nouveau-dossier.png +0 -0
  153. package/dist/starting-doc/images/creer-document-context-engineering.png +0 -0
  154. package/dist/starting-doc/images/creer-dossier-only-tutoriel.png +0 -0
  155. package/dist/starting-doc/images/creer-dossier-tutoriel.png +0 -0
  156. package/dist/starting-doc/images/creer-dossiers-done.png +0 -0
  157. package/dist/starting-doc/images/creer-un-document.png +0 -0
  158. package/dist/starting-doc/images/creer-vos-dossiers-tutoriel.png +0 -0
  159. package/dist/starting-doc/images/creer-vos-dossiers.png +0 -0
  160. package/dist/starting-doc/images/decouverte_adrs.png +0 -0
  161. package/dist/starting-doc/images/diataxis.png +0 -0
  162. package/dist/starting-doc/images/diataxis_callout.png +0 -0
  163. package/dist/starting-doc/images/document-cree.png +0 -0
  164. package/dist/starting-doc/images/liens_snippets.png +0 -0
  165. package/dist/starting-doc/images/living_documentation.png +0 -0
  166. package/dist/starting-doc/images/npm_logo.png +0 -0
  167. package/dist/starting-doc/images/popup-creer-document.png +0 -0
  168. package/dist/starting-doc/images/popup-creer-dossier.png +0 -0
  169. package/dist/starting-doc/images/popup-dossier-cree.png +0 -0
  170. package/dist/starting-doc/images/quatre-dossiers-crees.png +0 -0
  171. package/dist/starting-doc/images/screenshot-living-doc.png +0 -0
  172. package/dist/starting-doc/images/the_living_documentation_tool.png +0 -0
  173. package/package.json +49 -0
@@ -0,0 +1,1279 @@
1
+ <!doctype html>
2
+ <html lang="fr">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Diagram — Living Documentation</title>
7
+ <script src="/i18n.js"></script>
8
+ <script src="https://cdn.tailwindcss.com?plugins=typography"></script>
9
+ <script>
10
+ tailwind.config = { darkMode: "class", theme: { extend: {} } };
11
+ </script>
12
+ <script src="https://unpkg.com/vis-network@9.1.9/standalone/umd/vis-network.min.js"></script>
13
+ <style>
14
+ #vis-canvas > div {
15
+ border: none !important;
16
+ }
17
+ .tool-btn {
18
+ display: flex;
19
+ align-items: center;
20
+ justify-content: center;
21
+ width: 2rem;
22
+ height: 2rem;
23
+ border-radius: 0.5rem;
24
+ font-size: 0.875rem;
25
+ cursor: pointer;
26
+ flex-shrink: 0;
27
+ transition:
28
+ background-color 0.15s,
29
+ color 0.15s;
30
+ color: #4b5563;
31
+ background: none;
32
+ border: none;
33
+ }
34
+ .dark .tool-btn {
35
+ color: #9ca3af;
36
+ }
37
+ .tool-btn:hover {
38
+ background: #f3f4f6;
39
+ }
40
+ .dark .tool-btn:hover {
41
+ background: #1f2937;
42
+ }
43
+ .tool-active {
44
+ background: #fff7ed !important;
45
+ color: #ea580c !important;
46
+ }
47
+ .dark .tool-active {
48
+ background: rgba(124, 45, 18, 0.3) !important;
49
+ color: #fb923c !important;
50
+ }
51
+ #vis-canvas.cursor-crosshair canvas {
52
+ cursor: crosshair !important;
53
+ }
54
+
55
+ /* Floating panels */
56
+ .float-panel {
57
+ position: absolute;
58
+ top: 0.75rem;
59
+ left: 50%;
60
+ transform: translateX(-50%);
61
+ display: flex;
62
+ align-items: center;
63
+ gap: 0.25rem;
64
+ padding: 0.375rem 0.5rem;
65
+ background: white;
66
+ border: 1px solid #e5e7eb;
67
+ border-radius: 0.5rem;
68
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
69
+ z-index: 10;
70
+ }
71
+ .dark .float-panel {
72
+ background: #1f2937;
73
+ border-color: #374151;
74
+ }
75
+ .float-panel.hidden {
76
+ display: none !important;
77
+ }
78
+ .panel-sep {
79
+ width: 1px;
80
+ height: 1rem;
81
+ background: #e5e7eb;
82
+ margin: 0 0.125rem;
83
+ flex-shrink: 0;
84
+ }
85
+ .dark .panel-sep {
86
+ background: #374151;
87
+ }
88
+ .edge-btn-active {
89
+ background: #fff7ed !important;
90
+ color: #ea580c !important;
91
+ }
92
+ .dark .edge-btn-active {
93
+ background: rgba(124, 45, 18, 0.3) !important;
94
+ color: #fb923c !important;
95
+ }
96
+
97
+ /* Floating label textarea */
98
+ #labelInput {
99
+ position: absolute;
100
+ z-index: 20;
101
+ padding: 4px 8px;
102
+ font-size: 13px;
103
+ font-family:
104
+ system-ui,
105
+ -apple-system,
106
+ sans-serif;
107
+ font-weight: 500;
108
+ text-align: center;
109
+ background: rgba(255, 255, 255, 0.95);
110
+ color: #1f2937;
111
+ border: 2px solid #3b82f6;
112
+ border-radius: 0.5rem;
113
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
114
+ outline: none;
115
+ resize: none;
116
+ overflow: hidden;
117
+ line-height: 1.4;
118
+ min-width: 80px;
119
+ min-height: 28px;
120
+ }
121
+ .dark #labelInput {
122
+ background: rgba(17, 24, 39, 0.95);
123
+ color: #f3f4f6;
124
+ }
125
+ #labelInput.hidden {
126
+ display: none;
127
+ }
128
+
129
+ /* Debug overlay layer */
130
+ #debugLayer {
131
+ position: absolute;
132
+ inset: 0;
133
+ pointer-events: none;
134
+ z-index: 12;
135
+ overflow: hidden;
136
+ }
137
+ .debug-box {
138
+ position: absolute;
139
+ pointer-events: auto;
140
+ font: 10px/1.4 monospace;
141
+ white-space: pre;
142
+ user-select: text;
143
+ cursor: text;
144
+ padding: 3px 6px;
145
+ border: 1px solid #f97316;
146
+ border-radius: 3px;
147
+ color: #c2410c;
148
+ background: rgba(255, 255, 255, 0.9);
149
+ }
150
+ .dark .debug-box {
151
+ color: #fbbf24;
152
+ background: rgba(0, 0, 0, 0.78);
153
+ }
154
+
155
+ /* Resize / rotation / selection overlay */
156
+ #selectionOverlay {
157
+ position: absolute;
158
+ display: none;
159
+ border: 2px dashed #f97316;
160
+ border-radius: 3px;
161
+ pointer-events: none;
162
+ z-index: 15;
163
+ box-sizing: border-box;
164
+ }
165
+ .resize-handle {
166
+ position: absolute;
167
+ width: 10px;
168
+ height: 10px;
169
+ background: white;
170
+ border: 2px solid #f97316;
171
+ border-radius: 2px;
172
+ pointer-events: all;
173
+ z-index: 1;
174
+ }
175
+ .dark .resize-handle {
176
+ background: #374151;
177
+ }
178
+ #rh-rotate {
179
+ position: absolute;
180
+ width: 16px;
181
+ height: 16px;
182
+ background: white;
183
+ border: 2px solid #f97316;
184
+ border-radius: 50%;
185
+ pointer-events: all;
186
+ z-index: 1;
187
+ cursor: grab;
188
+ display: flex;
189
+ align-items: center;
190
+ justify-content: center;
191
+ font-size: 10px;
192
+ color: #f97316;
193
+ }
194
+ #rh-rotate:active { cursor: grabbing; }
195
+ .dark #rh-rotate { background: #374151; }
196
+ #rh-label-rotate {
197
+ position: absolute;
198
+ width: 16px;
199
+ height: 16px;
200
+ background: white;
201
+ border: 2px solid #6366f1;
202
+ border-radius: 50%;
203
+ pointer-events: all;
204
+ z-index: 1;
205
+ cursor: grab;
206
+ display: flex;
207
+ align-items: center;
208
+ justify-content: center;
209
+ font-size: 10px;
210
+ color: #6366f1;
211
+ }
212
+ #rh-label-rotate:active { cursor: grabbing; }
213
+ .dark #rh-label-rotate { background: #374151; }
214
+
215
+ /* Toast notifications */
216
+ #toastContainer {
217
+ position: fixed;
218
+ bottom: 1.5rem;
219
+ right: 1.5rem;
220
+ display: flex;
221
+ flex-direction: column-reverse;
222
+ gap: 0.5rem;
223
+ z-index: 1000;
224
+ pointer-events: none;
225
+ }
226
+ .ld-toast {
227
+ pointer-events: all;
228
+ padding: 0.6rem 1rem;
229
+ border-radius: 0.5rem;
230
+ font-size: 0.8rem;
231
+ font-weight: 500;
232
+ box-shadow: 0 4px 16px rgba(0,0,0,0.18);
233
+ cursor: pointer;
234
+ opacity: 0;
235
+ transform: translateY(0.5rem);
236
+ transition: opacity 0.2s ease, transform 0.2s ease;
237
+ background: #1c1917;
238
+ color: #fafaf9;
239
+ border: 1px solid #292524;
240
+ }
241
+ .dark .ld-toast {
242
+ background: #fafaf9;
243
+ color: #1c1917;
244
+ border-color: #e7e5e4;
245
+ }
246
+ .ld-toast--error {
247
+ background: #7f1d1d;
248
+ color: #fef2f2;
249
+ border-color: #991b1b;
250
+ }
251
+ .ld-toast--visible {
252
+ opacity: 1;
253
+ transform: translateY(0);
254
+ }
255
+ </style>
256
+ </head>
257
+ <body
258
+ class="bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100 flex flex-col h-screen overflow-hidden"
259
+ >
260
+ <script>
261
+ const _d = localStorage.getItem("ld-dark") === "true";
262
+ document.documentElement.classList.toggle("dark", _d);
263
+ </script>
264
+
265
+ <!-- ── Top bar ── -->
266
+ <header
267
+ class="flex items-center gap-1 px-2 h-12 shrink-0 border-b border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-900 shadow-sm z-10"
268
+ >
269
+ <button
270
+ onclick="history.back()"
271
+ class="tool-btn !w-auto px-2 text-xs font-medium text-gray-500 dark:text-gray-400"
272
+ data-i18n="diagram.back_btn">← Back</button
273
+ >
274
+ <div class="w-px h-6 bg-gray-200 dark:bg-gray-700 mx-0.5"></div>
275
+ <button id="btnSidebar" class="tool-btn" data-i18n-title="diagram.diagrams_list_title">
276
+
277
+ </button>
278
+ <div class="w-px h-6 bg-gray-200 dark:bg-gray-700 mx-0.5"></div>
279
+
280
+ <button
281
+ id="toolSelect"
282
+ class="tool-btn tool-active"
283
+ data-i18n-title="diagram.toolbar.select"
284
+ >
285
+ <svg
286
+ width="11"
287
+ height="13"
288
+ viewBox="0 0 11 13"
289
+ fill="currentColor"
290
+ stroke="none"
291
+ >
292
+ <path d="M1 1 L1 12 L4 9 L6.5 13 L8 12.2 L5.5 8.2 L10 8.2 Z" />
293
+ </svg>
294
+ </button>
295
+ <div class="w-px h-6 bg-gray-200 dark:bg-gray-700 mx-0.5"></div>
296
+
297
+ <button
298
+ id="toolBox"
299
+ class="tool-btn"
300
+ data-i18n-title="diagram.toolbar.box"
301
+ >
302
+ <svg
303
+ width="15"
304
+ height="10"
305
+ viewBox="0 0 15 10"
306
+ fill="none"
307
+ stroke="currentColor"
308
+ stroke-width="1.5"
309
+ >
310
+ <rect x="1" y="1" width="13" height="8" rx="1" />
311
+ </svg>
312
+ </button>
313
+ <button
314
+ id="toolEllipse"
315
+ class="tool-btn"
316
+ data-i18n-title="diagram.toolbar.ellipse"
317
+ >
318
+ <svg
319
+ width="16"
320
+ height="10"
321
+ viewBox="0 0 16 10"
322
+ fill="none"
323
+ stroke="currentColor"
324
+ stroke-width="1.5"
325
+ >
326
+ <ellipse cx="8" cy="5" rx="7" ry="4" />
327
+ </svg>
328
+ </button>
329
+ <button
330
+ id="toolDatabase"
331
+ class="tool-btn"
332
+ data-i18n-title="diagram.toolbar.database"
333
+ >
334
+ <svg
335
+ width="12"
336
+ height="16"
337
+ viewBox="0 0 12 16"
338
+ fill="none"
339
+ stroke="currentColor"
340
+ stroke-width="1.5"
341
+ stroke-linecap="round"
342
+ >
343
+ <ellipse cx="6" cy="3" rx="5" ry="2" />
344
+ <path d="M1 3v10c0 1.1 2.2 2 5 2s5-.9 5-2V3" />
345
+ <path d="M11 7.5c0 1.1-2.2 2-5 2s-5-.9-5-2" />
346
+ </svg>
347
+ </button>
348
+ <button
349
+ id="toolCircle"
350
+ class="tool-btn"
351
+ data-i18n-title="diagram.toolbar.circle"
352
+ >
353
+ <svg
354
+ width="13"
355
+ height="13"
356
+ viewBox="0 0 13 13"
357
+ fill="none"
358
+ stroke="currentColor"
359
+ stroke-width="1.5"
360
+ >
361
+ <circle cx="6.5" cy="6.5" r="5.5" />
362
+ </svg>
363
+ </button>
364
+ <button
365
+ id="toolActor"
366
+ class="tool-btn"
367
+ data-i18n-title="diagram.toolbar.actor"
368
+ >
369
+ <svg
370
+ width="12"
371
+ height="17"
372
+ viewBox="0 0 12 17"
373
+ fill="none"
374
+ stroke="currentColor"
375
+ stroke-width="1.5"
376
+ stroke-linecap="round"
377
+ >
378
+ <circle cx="6" cy="3" r="2.2" />
379
+ <line x1="6" y1="5.2" x2="6" y2="10" />
380
+ <line x1="1.5" y1="7.5" x2="10.5" y2="7.5" />
381
+ <line x1="6" y1="10" x2="2.5" y2="15" />
382
+ <line x1="6" y1="10" x2="9.5" y2="15" />
383
+ </svg>
384
+ </button>
385
+ <button
386
+ id="toolPostIt"
387
+ class="tool-btn"
388
+ data-i18n-title="diagram.toolbar.postit"
389
+ >
390
+ <svg width="13" height="13" viewBox="0 0 13 13" fill="none" stroke="currentColor" stroke-width="1.4">
391
+ <path d="M1 1 H9 L12 4 V12 H1 Z" />
392
+ <path d="M9 1 V4 H12" stroke-opacity="0.5"/>
393
+ </svg>
394
+ </button>
395
+ <button
396
+ id="toolTextFree"
397
+ class="tool-btn"
398
+ data-i18n-title="diagram.toolbar.text_free"
399
+ style="font-size:11px; font-weight:600;"
400
+ >
401
+ T
402
+ </button>
403
+ <button
404
+ id="toolImage"
405
+ class="tool-btn"
406
+ data-i18n-title="diagram.toolbar.image"
407
+ >
408
+ <svg width="14" height="14" viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round">
409
+ <rect x="1" y="1" width="12" height="12" rx="1.5"/>
410
+ <circle cx="4.5" cy="4.5" r="1.2"/>
411
+ <path d="M1 9.5 L4 6.5 L6.5 9 L9 7 L13 10.5"/>
412
+ </svg>
413
+ </button>
414
+ <div class="w-px h-6 bg-gray-200 dark:bg-gray-700 mx-0.5"></div>
415
+
416
+ <button
417
+ id="toolArrow"
418
+ class="tool-btn"
419
+ data-i18n-title="diagram.toolbar.arrow"
420
+ >
421
+
422
+ </button>
423
+ <div class="w-px h-6 bg-gray-200 dark:bg-gray-700 mx-0.5"></div>
424
+
425
+ <button
426
+ id="btnDelete"
427
+ class="tool-btn"
428
+ data-i18n-title="diagram.toolbar.delete"
429
+ >
430
+ <svg
431
+ width="11"
432
+ height="11"
433
+ viewBox="0 0 11 11"
434
+ fill="none"
435
+ stroke="currentColor"
436
+ stroke-width="1.8"
437
+ stroke-linecap="round"
438
+ >
439
+ <line x1="1" y1="1" x2="10" y2="10" />
440
+ <line x1="10" y1="1" x2="1" y2="10" />
441
+ </svg>
442
+ </button>
443
+ <div class="w-px h-6 bg-gray-200 dark:bg-gray-700 mx-0.5"></div>
444
+
445
+ <button
446
+ id="btnAlign"
447
+ class="tool-btn"
448
+ data-i18n-title="diagram.toolbar.align_guides"
449
+ >
450
+ <svg
451
+ width="14"
452
+ height="14"
453
+ viewBox="0 0 14 14"
454
+ fill="none"
455
+ stroke="currentColor"
456
+ stroke-width="1.4"
457
+ >
458
+ <line x1="0.5" y1="7.5" x2="13.5" y2="7.5" />
459
+ <line x1="3.5" y1="0.5" x2="3.5" y2="13.5" stroke-dasharray="1.5 1.5" />
460
+ <line x1="10.5" y1="0.5" x2="10.5" y2="13.5" stroke-dasharray="1.5 1.5" />
461
+ </svg>
462
+ </button>
463
+ <button
464
+ id="btnGrid"
465
+ class="tool-btn"
466
+ data-i18n-title="diagram.toolbar.grid"
467
+ >
468
+ <svg
469
+ width="14"
470
+ height="14"
471
+ viewBox="0 0 14 14"
472
+ fill="none"
473
+ stroke="currentColor"
474
+ stroke-width="1.2"
475
+ >
476
+ <line x1="5" y1="1" x2="5" y2="13" />
477
+ <line x1="9" y1="1" x2="9" y2="13" />
478
+ <line x1="1" y1="5" x2="13" y2="5" />
479
+ <line x1="1" y1="9" x2="13" y2="9" />
480
+ <rect x="1" y="1" width="12" height="12" rx="1" />
481
+ </svg>
482
+ </button>
483
+ <button
484
+ id="btnEdgeStraight"
485
+ class="tool-btn"
486
+ data-i18n-title="diagram.toolbar.edge_style"
487
+ >
488
+ <svg width="14" height="14" viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round">
489
+ <line x1="2" y1="12" x2="12" y2="2" />
490
+ <polyline points="8,2 12,2 12,6" />
491
+ </svg>
492
+ </button>
493
+ <button
494
+ id="btnResizeMode"
495
+ class="tool-btn active-tool"
496
+ data-i18n-title="diagram.toolbar.resize_corner"
497
+ >
498
+ <!-- icon-resize-corner: dashed square + single arrow top-right → bottom-left -->
499
+ <svg id="icon-resize-corner" width="14" height="14" viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
500
+ <rect x="1" y="1" width="12" height="12" stroke-dasharray="2.5 2" />
501
+ <line x1="4" y1="4" x2="10" y2="10" />
502
+ <polyline points="7,10 10,10 10,7" />
503
+ </svg>
504
+ <!-- icon-resize-center: dashed square + two double-headed diagonal arrows -->
505
+ <svg id="icon-resize-center" width="14" height="14" viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="hidden">
506
+ <rect x="1" y="1" width="12" height="12" stroke-dasharray="2.5 2" />
507
+ <line x1="4" y1="4" x2="10" y2="10" />
508
+ <polyline points="4,6.5 4,4 6.5,4" />
509
+ <polyline points="10,7.5 10,10 7.5,10" />
510
+ <line x1="10" y1="4" x2="4" y2="10" />
511
+ <polyline points="7.5,4 10,4 10,6.5" />
512
+ <polyline points="4,7.5 4,10 6.5,10" />
513
+ </svg>
514
+ </button>
515
+ <div class="w-px h-6 bg-gray-200 dark:bg-gray-700 mx-0.5"></div>
516
+
517
+ <input
518
+ id="diagramTitle"
519
+ type="text"
520
+ data-i18n-placeholder="diagram.toolbar.title_placeholder" placeholder="Diagram title"
521
+ class="flex-1 min-w-0 px-2 py-1 text-sm bg-transparent border-0 focus:outline-none focus:ring-1 focus:ring-blue-500 rounded text-gray-700 dark:text-gray-300 placeholder:text-gray-400"
522
+ />
523
+ <div class="w-px h-6 bg-gray-200 dark:bg-gray-700 mx-0.5"></div>
524
+
525
+ <button id="btnZoomOut" class="tool-btn" data-i18n-title="diagram.toolbar.zoom_out">
526
+
527
+ </button>
528
+ <span
529
+ id="zoomLevel"
530
+ class="text-xs text-gray-500 dark:text-gray-400 w-10 text-center tabular-nums select-none"
531
+ >100%</span
532
+ >
533
+ <button id="btnZoomIn" class="tool-btn" data-i18n-title="diagram.toolbar.zoom_in">
534
+ +
535
+ </button>
536
+ <button id="btnZoomReset" class="tool-btn" data-i18n-title="diagram.toolbar.fit">
537
+
538
+ </button>
539
+ <div class="w-px h-6 bg-gray-200 dark:bg-gray-700 mx-0.5"></div>
540
+
541
+ <button id="btnDark" class="tool-btn">
542
+ <span id="darkIcon">☽</span>
543
+ </button>
544
+ <div class="w-px h-6 bg-gray-200 dark:bg-gray-700 mx-0.5"></div>
545
+
546
+ <button
547
+ id="btnDebug"
548
+ class="hidden tool-btn text-xs font-mono"
549
+ data-i18n-title="diagram.toolbar.debug"
550
+ >
551
+ dbg
552
+ </button>
553
+ <div
554
+ id="sepDebug"
555
+ class="hidden w-px h-6 bg-gray-200 dark:bg-gray-700 mx-0.5"
556
+ ></div>
557
+
558
+ <button
559
+ id="btnSave"
560
+ disabled
561
+ class="px-3 py-1.5 text-xs font-semibold rounded-lg bg-blue-600 hover:bg-blue-700 text-white disabled:opacity-40 disabled:cursor-not-allowed shrink-0 transition-colors"
562
+ >
563
+ <span data-i18n="diagram.toolbar.save">Save</span>
564
+ </button>
565
+ </header>
566
+
567
+ <!-- ── Body ── -->
568
+ <div class="flex flex-1 overflow-hidden">
569
+ <!-- Sidebar -->
570
+ <div
571
+ id="sidebar"
572
+ class="w-56 shrink-0 border-r border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800 flex flex-col overflow-hidden"
573
+ style="transition: width 0.2s ease"
574
+ >
575
+ <div
576
+ class="flex items-center justify-between px-3 py-2 border-b border-gray-200 dark:border-gray-700 shrink-0"
577
+ >
578
+ <span
579
+ class="text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider"
580
+ data-i18n="diagram.sidebar.title">Diagrams</span
581
+ >
582
+ <button
583
+ id="btnNewDiagram"
584
+ class="text-xs font-medium text-blue-600 dark:text-blue-400 hover:text-blue-700 dark:hover:text-blue-300 transition-colors"
585
+ data-i18n="diagram.sidebar.new_btn"
586
+ >
587
+ + New
588
+ </button>
589
+ </div>
590
+ <div id="diagramList" class="flex-1 overflow-y-auto py-1"></div>
591
+ </div>
592
+
593
+ <!-- Canvas area -->
594
+ <div class="relative flex-1 overflow-hidden bg-gray-50 dark:bg-gray-950">
595
+ <div id="vis-canvas" class="w-full h-full"></div>
596
+
597
+ <!-- Debug overlay layer -->
598
+ <div id="debugLayer"></div>
599
+
600
+ <!-- Stamp overlay: intercepts canvas clicks during stamp mode -->
601
+ <div id="stampOverlay" style="position:absolute;inset:0;display:none;z-index:11;cursor:crosshair;"></div>
602
+
603
+ <!-- Selection / resize overlay -->
604
+ <div id="selectionOverlay">
605
+ <div
606
+ id="rh-tl"
607
+ class="resize-handle"
608
+ style="top: -5px; left: -5px; cursor: nw-resize"
609
+ ></div>
610
+ <div
611
+ id="rh-tr"
612
+ class="resize-handle"
613
+ style="top: -5px; right: -5px; cursor: ne-resize"
614
+ ></div>
615
+ <div
616
+ id="rh-bl"
617
+ class="resize-handle"
618
+ style="bottom: -5px; left: -5px; cursor: sw-resize"
619
+ ></div>
620
+ <div
621
+ id="rh-br"
622
+ class="resize-handle"
623
+ style="bottom: -5px; right: -5px; cursor: se-resize"
624
+ ></div>
625
+ <div id="rh-rotate" data-i18n-title="diagram.selection.rotate_shape">↻</div>
626
+ <div id="rh-label-rotate" data-i18n-title="diagram.selection.rotate_text" style="left: 0; top: -28px;">
627
+ <svg width="10" height="10" viewBox="0 0 10 10" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round">
628
+ <g transform="rotate(25,5,5)">
629
+ <line x1="2.5" y1="3" x2="7.5" y2="3"/>
630
+ <line x1="5" y1="3" x2="5" y2="8"/>
631
+ </g>
632
+ </svg>
633
+ </div>
634
+ </div>
635
+
636
+ <!-- Node panel -->
637
+ <div id="nodePanel" class="float-panel hidden">
638
+ <button
639
+ id="btnNodeLock"
640
+ class="tool-btn !w-6 !h-6 text-sm"
641
+ data-i18n-title="diagram.node_panel.lock"
642
+ >🔒</button>
643
+ <div class="panel-sep"></div>
644
+ <div id="nodePanelControls" class="contents">
645
+ <div id="nodePaletteContainer" class="contents"></div>
646
+ <div class="panel-sep"></div>
647
+ <input
648
+ id="nodeBgOpacity"
649
+ type="range"
650
+ min="0"
651
+ max="100"
652
+ step="5"
653
+ value="100"
654
+ class="w-16 h-1 accent-orange-500 cursor-pointer"
655
+ data-i18n-title="diagram.node_panel.bg_opacity"
656
+ />
657
+ <div class="panel-sep"></div>
658
+ <button
659
+ id="btnNodeLabelEdit"
660
+ class="tool-btn !w-6 !h-6"
661
+ data-i18n-title="diagram.node_panel.edit_label"
662
+ >
663
+
664
+ </button>
665
+ <button
666
+ id="btnNodeLink"
667
+ class="tool-btn !w-6 !h-6"
668
+ data-i18n-title="diagram.node_panel.edit_link"
669
+ >
670
+ <svg width="13" height="13" viewBox="0 0 13 13" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round">
671
+ <path d="M5 7.5a3.5 3.5 0 0 0 5 0l1.5-1.5a3.5 3.5 0 0 0-5-5L5 2.5"/>
672
+ <path d="M8 5.5a3.5 3.5 0 0 0-5 0L1.5 7a3.5 3.5 0 0 0 5 5L8 10.5"/>
673
+ </svg>
674
+ </button>
675
+ <div class="panel-sep"></div>
676
+ <button
677
+ id="btnNodeFontDecrease"
678
+ class="tool-btn !w-8 !h-6"
679
+ style="font-size: 10px"
680
+ data-i18n-title="diagram.node_panel.font_decrease"
681
+ >
682
+ Aa−
683
+ </button>
684
+ <button
685
+ id="btnNodeFontIncrease"
686
+ class="tool-btn !w-8 !h-6"
687
+ style="font-size: 10px"
688
+ data-i18n-title="diagram.node_panel.font_increase"
689
+ >
690
+ Aa+
691
+ </button>
692
+ <div class="panel-sep"></div>
693
+ <!-- Horizontal align -->
694
+ <button
695
+ id="btnAlignLeft"
696
+ class="tool-btn !w-6 !h-6"
697
+ data-i18n-title="diagram.node_panel.align_left"
698
+ >
699
+ <svg
700
+ width="12"
701
+ height="10"
702
+ viewBox="0 0 12 10"
703
+ fill="none"
704
+ stroke="currentColor"
705
+ stroke-width="1.4"
706
+ stroke-linecap="round"
707
+ >
708
+ <line x1="1" y1="2" x2="11" y2="2" />
709
+ <line x1="1" y1="5" x2="7" y2="5" />
710
+ <line x1="1" y1="8" x2="9" y2="8" />
711
+ </svg>
712
+ </button>
713
+ <button
714
+ id="btnAlignCenter"
715
+ class="tool-btn !w-6 !h-6"
716
+ data-i18n-title="diagram.node_panel.align_center"
717
+ >
718
+ <svg
719
+ width="12"
720
+ height="10"
721
+ viewBox="0 0 12 10"
722
+ fill="none"
723
+ stroke="currentColor"
724
+ stroke-width="1.4"
725
+ stroke-linecap="round"
726
+ >
727
+ <line x1="1" y1="2" x2="11" y2="2" />
728
+ <line x1="2.5" y1="5" x2="9.5" y2="5" />
729
+ <line x1="1.5" y1="8" x2="10.5" y2="8" />
730
+ </svg>
731
+ </button>
732
+ <button
733
+ id="btnAlignRight"
734
+ class="tool-btn !w-6 !h-6"
735
+ data-i18n-title="diagram.node_panel.align_right"
736
+ >
737
+ <svg
738
+ width="12"
739
+ height="10"
740
+ viewBox="0 0 12 10"
741
+ fill="none"
742
+ stroke="currentColor"
743
+ stroke-width="1.4"
744
+ stroke-linecap="round"
745
+ >
746
+ <line x1="1" y1="2" x2="11" y2="2" />
747
+ <line x1="5" y1="5" x2="11" y2="5" />
748
+ <line x1="3" y1="8" x2="11" y2="8" />
749
+ </svg>
750
+ </button>
751
+ <div class="panel-sep"></div>
752
+ <!-- Vertical align -->
753
+ <button
754
+ id="btnValignTop"
755
+ class="tool-btn !w-6 !h-6"
756
+ data-i18n-title="diagram.node_panel.align_top"
757
+ >
758
+ <svg
759
+ width="10"
760
+ height="12"
761
+ viewBox="0 0 10 12"
762
+ fill="none"
763
+ stroke="currentColor"
764
+ stroke-linecap="round"
765
+ >
766
+ <line x1="1" y1="1.5" x2="9" y2="1.5" stroke-width="2" />
767
+ <line x1="1" y1="5" x2="8" y2="5" stroke-width="1.3" />
768
+ <line x1="1" y1="8.5" x2="6" y2="8.5" stroke-width="1.3" />
769
+ </svg>
770
+ </button>
771
+ <button
772
+ id="btnValignMiddle"
773
+ class="tool-btn !w-6 !h-6"
774
+ data-i18n-title="diagram.node_panel.align_middle"
775
+ >
776
+ <svg
777
+ width="10"
778
+ height="12"
779
+ viewBox="0 0 10 12"
780
+ fill="none"
781
+ stroke="currentColor"
782
+ stroke-linecap="round"
783
+ >
784
+ <line x1="1" y1="2.5" x2="8" y2="2.5" stroke-width="1.3" />
785
+ <line x1="1" y1="6" x2="9" y2="6" stroke-width="2" />
786
+ <line x1="1" y1="9.5" x2="6" y2="9.5" stroke-width="1.3" />
787
+ </svg>
788
+ </button>
789
+ <button
790
+ id="btnValignBottom"
791
+ class="tool-btn !w-6 !h-6"
792
+ data-i18n-title="diagram.node_panel.align_bottom"
793
+ >
794
+ <svg
795
+ width="10"
796
+ height="12"
797
+ viewBox="0 0 10 12"
798
+ fill="none"
799
+ stroke="currentColor"
800
+ stroke-linecap="round"
801
+ >
802
+ <line x1="1" y1="3.5" x2="8" y2="3.5" stroke-width="1.3" />
803
+ <line x1="1" y1="7" x2="6" y2="7" stroke-width="1.3" />
804
+ <line x1="1" y1="10.5" x2="9" y2="10.5" stroke-width="2" />
805
+ </svg>
806
+ </button>
807
+ <div class="panel-sep"></div>
808
+ <button
809
+ id="btnZOrderBack"
810
+ class="tool-btn !w-7 !h-6"
811
+ data-i18n-title="diagram.node_panel.send_back"
812
+ >
813
+ <svg
814
+ width="14"
815
+ height="14"
816
+ viewBox="0 0 14 14"
817
+ fill="none"
818
+ stroke="currentColor"
819
+ stroke-width="1.4"
820
+ stroke-linecap="round"
821
+ stroke-linejoin="round"
822
+ >
823
+ <rect x="1" y="5" width="8" height="8" rx="1" />
824
+ <rect
825
+ x="5"
826
+ y="1"
827
+ width="8"
828
+ height="8"
829
+ rx="1"
830
+ stroke-opacity="0.35"
831
+ />
832
+ </svg>
833
+ </button>
834
+ <button
835
+ id="btnZOrderFront"
836
+ class="tool-btn !w-7 !h-6"
837
+ data-i18n-title="diagram.node_panel.bring_front"
838
+ >
839
+ <svg
840
+ width="14"
841
+ height="14"
842
+ viewBox="0 0 14 14"
843
+ fill="none"
844
+ stroke="currentColor"
845
+ stroke-width="1.4"
846
+ stroke-linecap="round"
847
+ stroke-linejoin="round"
848
+ >
849
+ <rect
850
+ x="1"
851
+ y="5"
852
+ width="8"
853
+ height="8"
854
+ rx="1"
855
+ stroke-opacity="0.35"
856
+ />
857
+ <rect x="5" y="1" width="8" height="8" rx="1" />
858
+ </svg>
859
+ </button>
860
+
861
+ <div class="panel-sep"></div>
862
+
863
+ <!-- Stamp: copy color (goutte) -->
864
+ <button
865
+ id="btnStampColor"
866
+ class="tool-btn !w-7 !h-6"
867
+ data-i18n-title="diagram.node_panel.stamp_color"
868
+ >
869
+ <svg width="14" height="14" viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round">
870
+ <path d="M7 2 C7 2 3 6.5 3 9 a4 4 0 0 0 8 0 C11 6.5 7 2 7 2 Z" fill="currentColor" fill-opacity="0.25"/>
871
+ </svg>
872
+ </button>
873
+
874
+ <!-- Stamp: copy font size -->
875
+ <button
876
+ id="btnStampFontSize"
877
+ class="tool-btn !w-7 !h-6 font-mono text-xs font-bold"
878
+ data-i18n-title="diagram.node_panel.stamp_font"
879
+ >Aa</button>
880
+
881
+ <!-- Stamp: copy size -->
882
+ <button
883
+ id="btnStampSize"
884
+ class="tool-btn !w-7 !h-6"
885
+ data-i18n-title="diagram.node_panel.stamp_size"
886
+ >
887
+ <svg width="14" height="14" viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round">
888
+ <rect x="2" y="4" width="6" height="6" rx="0.8" fill="currentColor" fill-opacity="0.25"/>
889
+ <path d="M10 2 L12 2 L12 4"/>
890
+ <path d="M12 2 L9 5"/>
891
+ <path d="M10 12 L12 12 L12 10"/>
892
+ <path d="M12 12 L9 9"/>
893
+ </svg>
894
+ </button>
895
+
896
+ <div class="panel-sep"></div>
897
+
898
+ <!-- Rotation anti-horaire 10° -->
899
+ <button
900
+ id="btnRotateCCW"
901
+ class="tool-btn !w-7 !h-6"
902
+ data-i18n-title="diagram.node_panel.rotate_ccw"
903
+ style="font-size:15px; line-height:1;"
904
+ >↺</button>
905
+
906
+ <!-- Rotation horaire 10° -->
907
+ <button
908
+ id="btnRotateCW"
909
+ class="tool-btn !w-7 !h-6"
910
+ data-i18n-title="diagram.node_panel.rotate_cw"
911
+ style="font-size:15px; line-height:1;"
912
+ >↻</button>
913
+
914
+ <div class="panel-sep"></div>
915
+
916
+ <!-- Copy as PNG -->
917
+ <button
918
+ id="btnCopyPng"
919
+ class="tool-btn !h-6 px-1.5 font-mono text-xs font-semibold flex items-center gap-0.5"
920
+ data-i18n-title="diagram.node_panel.copy_png"
921
+ >
922
+ <svg width="10" height="10" viewBox="0 0 10 10" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round">
923
+ <line x1="5" y1="7" x2="5" y2="1"/>
924
+ <polyline points="2,4 5,1 8,4"/>
925
+ <line x1="1" y1="9" x2="9" y2="9"/>
926
+ </svg>
927
+ PNG
928
+ </button>
929
+
930
+ <div class="panel-sep"></div>
931
+
932
+ <!-- Group -->
933
+ <button
934
+ id="btnGroup"
935
+ class="tool-btn !w-8 !h-7"
936
+ data-i18n-title="diagram.node_panel.group"
937
+ >
938
+ <svg width="22" height="14" viewBox="0 0 22 14" fill="none" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round">
939
+ <rect x="0.7" y="1" width="20.6" height="12" rx="2" stroke-dasharray="2.5,1.5"/>
940
+ <rect x="4" y="4.5" width="3" height="5" rx="1" fill="currentColor"/>
941
+ <rect x="15" y="4.5" width="3" height="5" rx="1" fill="currentColor"/>
942
+ </svg>
943
+ </button>
944
+
945
+ <!-- Ungroup -->
946
+ <button
947
+ id="btnUngroup"
948
+ class="tool-btn !w-8 !h-7"
949
+ data-i18n-title="diagram.node_panel.ungroup"
950
+ >
951
+ <svg width="26" height="14" viewBox="0 0 26 14" fill="none" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round">
952
+ <rect x="0.7" y="1" width="9" height="12" rx="2" stroke-dasharray="2.5,1.5"/>
953
+ <rect x="3" y="4.5" width="3" height="5" rx="1" fill="currentColor"/>
954
+ <rect x="16.3" y="1" width="9" height="12" rx="2" stroke-dasharray="2.5,1.5"/>
955
+ <rect x="19" y="4.5" width="3" height="5" rx="1" fill="currentColor"/>
956
+ </svg>
957
+ </button>
958
+ </div><!-- /nodePanelControls -->
959
+ </div>
960
+
961
+ <!-- Link panel -->
962
+ <div id="linkPanel" class="float-panel hidden" style="min-width:260px; flex-direction:column; align-items:stretch; gap:0.5rem; padding:0.75rem;">
963
+ <div class="text-xs font-semibold text-gray-500 dark:text-gray-400 mb-1" data-i18n="diagram.link_panel.title">Link on this shape</div>
964
+
965
+ <label class="flex items-center gap-2 text-xs cursor-pointer">
966
+ <input type="radio" name="linkType" id="linkTypeUrl" value="url" class="accent-orange-500"/>
967
+ <span data-i18n="diagram.link_panel.external">External URL</span>
968
+ </label>
969
+ <div id="linkUrlRow">
970
+ <input id="linkUrlInput" type="url" data-i18n-placeholder="diagram.link_panel.url_placeholder" placeholder="https://…"
971
+ class="w-full text-xs border border-gray-300 dark:border-gray-600 rounded px-2 py-1 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-1 focus:ring-orange-400"/>
972
+ </div>
973
+
974
+ <label class="flex items-center gap-2 text-xs cursor-pointer">
975
+ <input type="radio" name="linkType" id="linkTypeDiagram" value="diagram" class="accent-orange-500"/>
976
+ <span data-i18n="diagram.link_panel.existing_diagram">Existing diagram</span>
977
+ </label>
978
+ <div id="linkDiagramRow" class="hidden">
979
+ <select id="linkDiagramSelect"
980
+ class="w-full text-xs border border-gray-300 dark:border-gray-600 rounded px-2 py-1 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-1 focus:ring-orange-400">
981
+ </select>
982
+ </div>
983
+
984
+ <label class="flex items-center gap-2 text-xs cursor-pointer">
985
+ <input type="radio" name="linkType" id="linkTypeNew" value="new" class="accent-orange-500"/>
986
+ <span data-i18n="diagram.link_panel.new_diagram">New diagram</span>
987
+ </label>
988
+ <div id="linkNewRow" class="hidden">
989
+ <input id="linkNewName" type="text" data-i18n-placeholder="diagram.link_panel.diagram_name_placeholder" placeholder="Diagram name…"
990
+ class="w-full text-xs border border-gray-300 dark:border-gray-600 rounded px-2 py-1 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-1 focus:ring-orange-400"/>
991
+ </div>
992
+
993
+ <div class="flex gap-1 mt-1">
994
+ <button id="btnLinkSave" class="flex-1 text-xs bg-orange-500 hover:bg-orange-600 text-white rounded px-2 py-1" data-i18n="diagram.link_panel.save_btn">Save</button>
995
+ <button id="btnLinkRemove" class="text-xs border border-gray-300 dark:border-gray-600 rounded px-2 py-1 hover:bg-red-50 dark:hover:bg-red-900/20 hover:text-red-600" data-i18n="diagram.link_panel.remove_btn">Remove</button>
996
+ <button id="btnLinkCancel" class="text-xs border border-gray-300 dark:border-gray-600 rounded px-2 py-1 hover:bg-gray-100 dark:hover:bg-gray-700">✕</button>
997
+ </div>
998
+ </div>
999
+
1000
+ <!-- Edge panel -->
1001
+ <div id="edgePanel" class="float-panel hidden">
1002
+ <button
1003
+ id="btnEdgeLock"
1004
+ class="tool-btn !w-6 !h-6 text-sm"
1005
+ data-i18n-title="diagram.edge_panel.lock"
1006
+ >🔒</button>
1007
+ <div class="panel-sep"></div>
1008
+ <div id="edgePanelControls" class="contents">
1009
+ <button
1010
+ id="edgeBtnNone"
1011
+ class="tool-btn !w-7 !h-6 font-mono text-xs"
1012
+ data-i18n-title="diagram.edge_panel.no_arrow"
1013
+ >
1014
+
1015
+ </button>
1016
+ <button
1017
+ id="edgeBtnTo"
1018
+ class="tool-btn !w-7 !h-6 text-xs"
1019
+ data-i18n-title="diagram.edge_panel.arrow_to"
1020
+ >
1021
+
1022
+ </button>
1023
+ <button
1024
+ id="edgeBtnBoth"
1025
+ class="tool-btn !w-8 !h-6 text-xs"
1026
+ data-i18n-title="diagram.edge_panel.arrow_both"
1027
+ >
1028
+ ←→
1029
+ </button>
1030
+ <div class="panel-sep"></div>
1031
+ <button
1032
+ id="edgeBtnSolid"
1033
+ class="tool-btn !w-8 !h-6"
1034
+ data-i18n-title="diagram.edge_panel.solid"
1035
+ >
1036
+ <svg width="22" height="4" viewBox="0 0 22 4">
1037
+ <line
1038
+ x1="1"
1039
+ y1="2"
1040
+ x2="21"
1041
+ y2="2"
1042
+ stroke="currentColor"
1043
+ stroke-width="2"
1044
+ stroke-linecap="round"
1045
+ />
1046
+ </svg>
1047
+ </button>
1048
+ <button
1049
+ id="edgeBtnDashed"
1050
+ class="tool-btn !w-8 !h-6"
1051
+ data-i18n-title="diagram.edge_panel.dashed"
1052
+ >
1053
+ <svg width="22" height="4" viewBox="0 0 22 4">
1054
+ <line
1055
+ x1="1"
1056
+ y1="2"
1057
+ x2="21"
1058
+ y2="2"
1059
+ stroke="currentColor"
1060
+ stroke-width="2"
1061
+ stroke-linecap="round"
1062
+ stroke-dasharray="4,3"
1063
+ />
1064
+ </svg>
1065
+ </button>
1066
+ <div class="panel-sep"></div>
1067
+ <button
1068
+ id="btnEdgeFontDecrease"
1069
+ class="tool-btn !w-8 !h-6"
1070
+ style="font-size: 10px"
1071
+ data-i18n-title="diagram.edge_panel.font_decrease"
1072
+ >
1073
+ Aa−
1074
+ </button>
1075
+ <button
1076
+ id="btnEdgeFontIncrease"
1077
+ class="tool-btn !w-8 !h-6"
1078
+ style="font-size: 10px"
1079
+ data-i18n-title="diagram.edge_panel.font_increase"
1080
+ >
1081
+ Aa+
1082
+ </button>
1083
+ <div class="panel-sep"></div>
1084
+ <button
1085
+ id="btnEdgeLabelEdit"
1086
+ class="tool-btn !w-6 !h-6"
1087
+ data-i18n-title="diagram.edge_panel.edit_label"
1088
+ >
1089
+
1090
+ </button>
1091
+ <button
1092
+ id="btnEdgeLabelWidthReset"
1093
+ class="tool-btn !w-6 !h-6 text-xs"
1094
+ data-i18n-title="diagram.edge_panel.reset_label_width"
1095
+ >
1096
+
1097
+ </button>
1098
+ <div class="panel-sep"></div>
1099
+ <button
1100
+ id="btnEdgeLabelRotateCCW"
1101
+ class="tool-btn !w-7 !h-6 text-sm"
1102
+ data-i18n-title="diagram.edge_panel.rotate_label_ccw"
1103
+ >
1104
+
1105
+ </button>
1106
+ <button
1107
+ id="btnEdgeLabelRotateCW"
1108
+ class="tool-btn !w-7 !h-6 text-sm"
1109
+ data-i18n-title="diagram.edge_panel.rotate_label_cw"
1110
+ >
1111
+
1112
+ </button>
1113
+ <div class="panel-sep"></div>
1114
+ <button
1115
+ id="btnEdgeLabelOffsetLeft"
1116
+ class="tool-btn !w-6 !h-6 text-sm"
1117
+ data-i18n-title="diagram.edge_panel.offset_label_left"
1118
+ >←</button>
1119
+ <button
1120
+ id="btnEdgeLabelOffsetRight"
1121
+ class="tool-btn !w-6 !h-6 text-sm"
1122
+ data-i18n-title="diagram.edge_panel.offset_label_right"
1123
+ >→</button>
1124
+ <button
1125
+ id="btnEdgeLabelOffsetUp"
1126
+ class="tool-btn !w-6 !h-6 text-sm"
1127
+ data-i18n-title="diagram.edge_panel.offset_label_up"
1128
+ >↑</button>
1129
+ <button
1130
+ id="btnEdgeLabelOffsetDown"
1131
+ class="tool-btn !w-6 !h-6 text-sm"
1132
+ data-i18n-title="diagram.edge_panel.offset_label_down"
1133
+ >↓</button>
1134
+ <button
1135
+ id="btnEdgeLabelOffsetReset"
1136
+ class="tool-btn !w-6 !h-6 text-xs"
1137
+ data-i18n-title="diagram.edge_panel.offset_label_reset"
1138
+ >⊙</button>
1139
+ <div class="panel-sep"></div>
1140
+ <button id="btnEdgeWidthDecrease" class="tool-btn !w-7 !h-6" style="font-size:10px" data-i18n-title="diagram.edge_panel.width_decrease">W−</button>
1141
+ <button id="btnEdgeWidthIncrease" class="tool-btn !w-7 !h-6" style="font-size:10px" data-i18n-title="diagram.edge_panel.width_increase">W+</button>
1142
+ <div class="panel-sep"></div>
1143
+ <div id="edgePaletteContainer" class="contents"></div>
1144
+ <div class="panel-sep"></div>
1145
+ <button
1146
+ id="btnEdgeClearPorts"
1147
+ class="tool-btn !w-8 !h-6 text-xs"
1148
+ data-i18n-title="diagram.edge_panel.clear_ports"
1149
+ >
1150
+
1151
+ </button>
1152
+ </div><!-- /edgePanelControls -->
1153
+ </div>
1154
+
1155
+ <!-- Floating label textarea -->
1156
+ <textarea
1157
+ id="labelInput"
1158
+ class="hidden"
1159
+ rows="1"
1160
+ data-i18n-placeholder="diagram.label_input.placeholder" placeholder="Label…"
1161
+ ></textarea>
1162
+
1163
+ <!-- Empty state -->
1164
+ <div
1165
+ id="emptyState"
1166
+ class="absolute inset-0 flex flex-col items-center justify-center text-gray-400 dark:text-gray-600 pointer-events-none select-none"
1167
+ >
1168
+ <svg
1169
+ width="52"
1170
+ height="44"
1171
+ viewBox="0 0 52 44"
1172
+ fill="none"
1173
+ stroke="currentColor"
1174
+ stroke-width="1.5"
1175
+ class="mb-3 opacity-50"
1176
+ >
1177
+ <rect x="2" y="4" width="18" height="12" rx="2" />
1178
+ <rect x="32" y="28" width="18" height="12" rx="2" />
1179
+ <line x1="20" y1="10" x2="32" y2="34" stroke-dasharray="4,3" />
1180
+ <rect x="17" y="16" width="18" height="12" rx="2" />
1181
+ <line x1="26" y1="16" x2="26" y2="10" stroke-dasharray="4,3" />
1182
+ </svg>
1183
+ <p class="text-sm" data-i18n="diagram.empty_state">Select or create a diagram</p>
1184
+ </div>
1185
+ </div>
1186
+ </div>
1187
+
1188
+ <div id="toastContainer"></div>
1189
+
1190
+ <!-- Image name modal -->
1191
+ <div id="imageNameModal" style="display:none;position:fixed;inset:0;z-index:1000;background:rgba(0,0,0,0.5);"
1192
+ class="flex items-center justify-center">
1193
+ <div class="bg-white dark:bg-gray-800 rounded-xl shadow-2xl p-6 w-80 flex flex-col gap-3">
1194
+ <p class="text-sm font-semibold text-gray-700 dark:text-gray-200" data-i18n="diagram.image_modal.title">Image filename</p>
1195
+ <p class="text-xs text-gray-400 dark:text-gray-500">
1196
+ Lettres, chiffres, <code class="bg-gray-100 dark:bg-gray-700 px-0.5 rounded">_</code> et
1197
+ <code class="bg-gray-100 dark:bg-gray-700 px-0.5 rounded">-</code> uniquement.
1198
+ Laisser vide pour un nom automatique.
1199
+ </p>
1200
+ <div class="flex items-center gap-1">
1201
+ <input id="imageNameInput" type="text" autocomplete="off" spellcheck="false"
1202
+ class="flex-1 rounded border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700
1203
+ text-gray-900 dark:text-gray-100 text-sm px-2 py-1 outline-none focus:ring-2 focus:ring-blue-400"
1204
+ data-i18n-placeholder="diagram.image_modal.placeholder" placeholder="image_name" />
1205
+ <span class="text-xs text-gray-400 dark:text-gray-500 whitespace-nowrap">.png</span>
1206
+ </div>
1207
+ <p id="imageNameError" class="text-xs text-red-500 hidden" data-i18n="diagram.image_modal.error_chars">Allowed characters: letters, numbers, _ and -</p>
1208
+ <div class="flex gap-2 justify-end">
1209
+ <button id="imageNameCancel"
1210
+ class="px-3 py-1 text-xs rounded border border-gray-300 dark:border-gray-600
1211
+ text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700"
1212
+ data-i18n="diagram.image_modal.cancel_btn">
1213
+ Cancel
1214
+ </button>
1215
+ <button id="imageNameConfirm"
1216
+ class="px-3 py-1 text-xs rounded bg-blue-500 text-white hover:bg-blue-600"
1217
+ data-i18n="diagram.image_modal.paste_btn">
1218
+ Paste
1219
+ </button>
1220
+ </div>
1221
+ </div>
1222
+ </div>
1223
+
1224
+ <script type="module" src="/diagram/main.js"></script>
1225
+ <script type="module">
1226
+ import { NODE_COLORS, NODE_L_RATIOS, DEFAULT_NODE_PALETTE, DEFAULT_EDGE_PALETTE, deriveNodeColors } from '/diagram/constants.js';
1227
+ import { st } from '/diagram/state.js';
1228
+ (async () => {
1229
+ // diagramNodePalette: array of 15 bg hex strings (positional, matching DEFAULT_NODE_PALETTE)
1230
+ // diagramEdgePalette: array of hex strings
1231
+ let nodeBgOverrides = null; // array of 15 hex | null
1232
+ let edgePalette = DEFAULT_EDGE_PALETTE;
1233
+ try {
1234
+ const cfg = await fetch('/api/config').then((r) => r.json());
1235
+ await window.initI18n(cfg.language);
1236
+ if (Array.isArray(cfg.diagramNodePalette) && cfg.diagramNodePalette.length) nodeBgOverrides = cfg.diagramNodePalette;
1237
+ if (Array.isArray(cfg.diagramEdgePalette) && cfg.diagramEdgePalette.length) edgePalette = cfg.diagramEdgePalette;
1238
+ } catch { await window.initI18n('en'); /* config unavailable — use defaults */ }
1239
+
1240
+ // Apply node color overrides to shared state so renderers pick them up.
1241
+ DEFAULT_NODE_PALETTE.forEach((key, i) => {
1242
+ const customBg = nodeBgOverrides && nodeBgOverrides[i];
1243
+ if (customBg && customBg !== NODE_COLORS[key]?.bg) {
1244
+ st.nodeColorOverrides[key] = deriveNodeColors(customBg, NODE_L_RATIOS[key] || 0.60);
1245
+ }
1246
+ });
1247
+
1248
+ // Build node palette buttons.
1249
+ const nc = document.getElementById('nodePaletteContainer');
1250
+ DEFAULT_NODE_PALETTE.forEach((key, i) => {
1251
+ if (!nc) return;
1252
+ const customBg = nodeBgOverrides && nodeBgOverrides[i];
1253
+ const c = (customBg ? deriveNodeColors(customBg, NODE_L_RATIOS[key] || 0.60) : null) || NODE_COLORS[key];
1254
+ const btn = document.createElement('button');
1255
+ btn.dataset.color = key;
1256
+ btn.className = 'w-5 h-5 rounded-full border-2 hover:scale-125 transition-transform';
1257
+ btn.style.background = c.bg;
1258
+ btn.style.borderColor = c.border;
1259
+ btn.title = key.replace('c-', '');
1260
+ nc.appendChild(btn);
1261
+ });
1262
+
1263
+ // Build edge palette buttons.
1264
+ const ec = document.getElementById('edgePaletteContainer');
1265
+ edgePalette.forEach((hex) => {
1266
+ if (!ec) return;
1267
+ const btn = document.createElement('button');
1268
+ btn.dataset.edgeColor = hex;
1269
+ btn.className = 'edge-color-btn w-4 h-4 rounded-full hover:scale-125 transition-transform';
1270
+ btn.style.background = hex;
1271
+ btn.style.border = '2px solid rgba(0,0,0,0.2)';
1272
+ btn.title = hex;
1273
+ ec.appendChild(btn);
1274
+ });
1275
+ window.applyI18n();
1276
+ })();
1277
+ </script>
1278
+ </body>
1279
+ </html>