gitmaps 1.0.0 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. package/README.md +265 -122
  2. package/app/[...slug]/page.client.tsx +1 -0
  3. package/app/[...slug]/page.tsx +6 -0
  4. package/app/[owner]/[repo]/page.client.tsx +5 -0
  5. package/app/[slug]/page.client.tsx +5 -0
  6. package/app/analytics.db +0 -0
  7. package/app/api/analytics/route.ts +64 -0
  8. package/app/api/auth/positions/route.ts +95 -33
  9. package/app/api/build-info/route.ts +19 -0
  10. package/app/api/chat/route.ts +13 -2
  11. package/app/api/manifest.json/route.ts +20 -0
  12. package/app/api/og-image/route.ts +14 -0
  13. package/app/api/pwa-icon/route.ts +14 -0
  14. package/app/api/repo/clone-stream/route.ts +20 -12
  15. package/app/api/repo/file-content/route.ts +73 -20
  16. package/app/api/repo/imports/route.ts +21 -3
  17. package/app/api/repo/list/route.ts +30 -0
  18. package/app/api/repo/load/route.test.ts +62 -0
  19. package/app/api/repo/load/route.ts +41 -1
  20. package/app/api/repo/pdf-thumb/route.ts +127 -0
  21. package/app/api/repo/resolve-slug/route.ts +51 -0
  22. package/app/api/repo/tree/route.ts +188 -104
  23. package/app/api/repo/upload/route.ts +6 -9
  24. package/app/api/sw.js/route.ts +70 -0
  25. package/app/api/version/route.ts +26 -0
  26. package/app/galaxy-canvas/page.client.tsx +2 -0
  27. package/app/galaxy-canvas/page.tsx +5 -0
  28. package/app/globals.css +5844 -4694
  29. package/app/icon.png +0 -0
  30. package/app/layout.tsx +1284 -467
  31. package/app/lib/auto-arrange.test.ts +158 -0
  32. package/app/lib/auto-arrange.ts +147 -0
  33. package/app/lib/canvas-export.ts +358 -358
  34. package/app/lib/canvas-text.ts +4 -72
  35. package/app/lib/canvas.ts +625 -564
  36. package/app/lib/card-arrangement.ts +21 -7
  37. package/app/lib/card-context-menu.tsx +2 -2
  38. package/app/lib/card-groups.ts +9 -2
  39. package/app/lib/cards.tsx +1361 -914
  40. package/app/lib/chat.tsx +65 -9
  41. package/app/lib/code-editor.ts +86 -2
  42. package/app/lib/connections.tsx +34 -43
  43. package/app/lib/context.test.ts +32 -0
  44. package/app/lib/context.ts +19 -3
  45. package/app/lib/cursor-sharing.ts +34 -0
  46. package/app/lib/events.tsx +76 -73
  47. package/app/lib/export-canvas.ts +287 -0
  48. package/app/lib/file-card-plugin.ts +148 -134
  49. package/app/lib/file-modal.tsx +49 -0
  50. package/app/lib/file-preview.ts +486 -400
  51. package/app/lib/github-import.test.ts +424 -0
  52. package/app/lib/global-search.ts +48 -27
  53. package/app/lib/initial-route-hydration.test.ts +283 -0
  54. package/app/lib/initial-route-hydration.ts +202 -0
  55. package/app/lib/landing-reset.test.ts +99 -0
  56. package/app/lib/landing-reset.ts +106 -0
  57. package/app/lib/landing-shell.test.ts +75 -0
  58. package/app/lib/large-repo-optimization.ts +37 -0
  59. package/app/lib/layers.tsx +17 -18
  60. package/app/lib/layout-snapshots.ts +320 -0
  61. package/app/lib/loading.test.ts +69 -0
  62. package/app/lib/loading.tsx +160 -45
  63. package/app/lib/mount-cleanup.test.ts +52 -0
  64. package/app/lib/mount-cleanup.ts +34 -0
  65. package/app/lib/mount-init.test.ts +123 -0
  66. package/app/lib/mount-init.ts +107 -0
  67. package/app/lib/mount-lifecycle.test.ts +39 -0
  68. package/app/lib/mount-lifecycle.ts +12 -0
  69. package/app/lib/mount-route-wiring.test.ts +87 -0
  70. package/app/lib/mount-route-wiring.ts +84 -0
  71. package/app/lib/multi-repo.ts +14 -0
  72. package/app/lib/onboarding-tutorial.ts +278 -0
  73. package/app/lib/perf-overlay.ts +78 -0
  74. package/app/lib/positions.ts +191 -122
  75. package/app/lib/recent-commits.test.ts +869 -0
  76. package/app/lib/recent-commits.ts +227 -0
  77. package/app/lib/repo-handoff.test.ts +23 -0
  78. package/app/lib/repo-handoff.ts +16 -0
  79. package/app/lib/repo-progressive.ts +119 -0
  80. package/app/lib/repo-select.test.ts +61 -0
  81. package/app/lib/repo-select.ts +74 -0
  82. package/app/lib/repo.tsx +1383 -977
  83. package/app/lib/role.ts +228 -0
  84. package/app/lib/route-catchall.test.ts +27 -0
  85. package/app/lib/route-repo-entry.test.ts +95 -0
  86. package/app/lib/route-repo-entry.ts +36 -0
  87. package/app/lib/router-contract.test.ts +22 -0
  88. package/app/lib/router-contract.ts +19 -0
  89. package/app/lib/shared-layout.test.ts +86 -0
  90. package/app/lib/shared-layout.ts +82 -0
  91. package/app/lib/shortcuts-panel.ts +2 -0
  92. package/app/lib/status-bar.test.ts +118 -0
  93. package/app/lib/status-bar.ts +365 -128
  94. package/app/lib/sync-controls.test.ts +43 -0
  95. package/app/lib/sync-controls.tsx +303 -0
  96. package/app/lib/test-dom.ts +145 -0
  97. package/app/lib/test-fixtures/router-contract/[...slug]/page.tsx +3 -0
  98. package/app/lib/test-fixtures/router-contract/api/health/route.ts +3 -0
  99. package/app/lib/test-fixtures/router-contract/api/version/route.ts +3 -0
  100. package/app/lib/test-fixtures/router-contract/galaxy-canvas/page.tsx +3 -0
  101. package/app/lib/test-fixtures/router-contract/page.tsx +3 -0
  102. package/app/lib/transclusion-smoke.test.ts +163 -0
  103. package/app/lib/tutorial.ts +301 -0
  104. package/app/lib/version.ts +93 -0
  105. package/app/lib/viewport-culling.ts +740 -728
  106. package/app/lib/virtual-files.ts +456 -0
  107. package/app/lib/webgl-text.ts +189 -0
  108. package/app/lib/{galaxydraw-bridge.ts → xydraw-bridge.ts} +485 -477
  109. package/app/lib/{galaxydraw.test.ts → xydraw.test.ts} +228 -229
  110. package/app/og-image.png +0 -0
  111. package/app/page.client.tsx +70 -215
  112. package/app/page.tsx +27 -92
  113. package/app/state/machine.js +13 -0
  114. package/banner.png +0 -0
  115. package/package.json +17 -8
  116. package/server.ts +11 -1
  117. package/app/api/connections/route.ts +0 -72
  118. package/app/api/positions/route.ts +0 -80
  119. package/app/api/repo/browse/route.ts +0 -55
  120. package/app/lib/pr-review.ts +0 -374
  121. package/packages/galaxydraw/README.md +0 -296
  122. package/packages/galaxydraw/banner.png +0 -0
  123. package/packages/galaxydraw/demo/build-static.ts +0 -100
  124. package/packages/galaxydraw/demo/client.ts +0 -154
  125. package/packages/galaxydraw/demo/dist/client.js +0 -8
  126. package/packages/galaxydraw/demo/index.html +0 -256
  127. package/packages/galaxydraw/demo/server.ts +0 -96
  128. package/packages/galaxydraw/dist/index.js +0 -984
  129. package/packages/galaxydraw/dist/index.js.map +0 -16
  130. package/packages/galaxydraw/node_modules/.bin/tsc.bunx +0 -0
  131. package/packages/galaxydraw/node_modules/.bin/tsc.exe +0 -0
  132. package/packages/galaxydraw/node_modules/.bin/tsserver.bunx +0 -0
  133. package/packages/galaxydraw/node_modules/.bin/tsserver.exe +0 -0
  134. package/packages/galaxydraw/package.json +0 -49
  135. package/packages/galaxydraw/perf.test.ts +0 -284
  136. package/packages/galaxydraw/src/core/cards.ts +0 -435
  137. package/packages/galaxydraw/src/core/engine.ts +0 -339
  138. package/packages/galaxydraw/src/core/events.ts +0 -81
  139. package/packages/galaxydraw/src/core/layout.ts +0 -136
  140. package/packages/galaxydraw/src/core/minimap.ts +0 -216
  141. package/packages/galaxydraw/src/core/state.ts +0 -177
  142. package/packages/galaxydraw/src/core/viewport.ts +0 -106
  143. package/packages/galaxydraw/src/galaxydraw.css +0 -166
  144. package/packages/galaxydraw/src/index.ts +0 -40
  145. package/packages/galaxydraw/tsconfig.json +0 -30
@@ -0,0 +1,301 @@
1
+ import { CanvasContext } from './context';
2
+
3
+ export function initTutorial(ctx: CanvasContext) {
4
+ if (typeof window === 'undefined') return;
5
+
6
+ // Check if tutorial was already completed
7
+ if (localStorage.getItem('gitcanvas:tutorial_completed') === 'true') {
8
+ return;
9
+ }
10
+
11
+ // Only start tutorial if we actually have a repo loaded (hiding landing overlay)
12
+ const landing = document.getElementById('landingOverlay');
13
+ if (landing && landing.style.display !== 'none') {
14
+ // We'll wait until a repo is loaded. We can do this safely by polling or listening.
15
+ // For simplicity, we just poll until the landing overlay is hidden.
16
+ const interval = setInterval(() => {
17
+ if (landing.style.display === 'none') {
18
+ clearInterval(interval);
19
+ startTutorialSequence(ctx);
20
+ }
21
+ }, 500);
22
+ return;
23
+ }
24
+
25
+ startTutorialSequence(ctx);
26
+ }
27
+
28
+ function startTutorialSequence(ctx: CanvasContext) {
29
+ const steps = [
30
+ {
31
+ title: "Welcome to GitMaps 🌌",
32
+ text: "You are now viewing your codebase as a 5-dimensional spatial canvas. Let's learn how to navigate it.",
33
+ target: null,
34
+ position: "center",
35
+ },
36
+ {
37
+ title: "Exploring the Canvas ✋",
38
+ text: "Click and drag anywhere on the empty background to <b>pan</b> around the map.",
39
+ target: "#canvasViewport",
40
+ position: "center",
41
+ },
42
+ {
43
+ title: "Deep Dive 🔍",
44
+ text: "Use your <b>scroll wheel</b> or trackpad to zoom in and out. The canvas will automatically reveal more details as you get closer.",
45
+ target: null,
46
+ position: "center",
47
+ },
48
+ {
49
+ title: "Semantic Layers 🥞",
50
+ text: "Use the layer selector at the bottom to instantly switch your perspective. See the codebase by Files, Functions, or Tokens.",
51
+ target: "#layersBarContainer",
52
+ position: "above",
53
+ },
54
+ {
55
+ title: "Load Another Repo 📂",
56
+ text: "You can map any public GitHub repository instantly. Just use the search bar or import button here.",
57
+ target: ".repo-selector",
58
+ position: "right-of",
59
+ },
60
+ {
61
+ title: "Select & Inspect 🎯",
62
+ text: "Click any file or function block to select it. Right click to access powerful tools.",
63
+ target: null,
64
+ position: "center",
65
+ }
66
+ ];
67
+
68
+ let currentStep = 0;
69
+
70
+ const overlay = document.createElement('div');
71
+ overlay.id = 'tutorialOverlay';
72
+ overlay.className = 'tutorial-overlay';
73
+
74
+ // Inject styles explicitly so it's guaranteed to match the premium theme
75
+ const style = document.createElement('style');
76
+ style.innerHTML = `
77
+ .tutorial-overlay {
78
+ position: fixed;
79
+ top: 0; left: 0; right: 0; bottom: 0;
80
+ z-index: 100000;
81
+ pointer-events: none;
82
+ display: flex;
83
+ align-items: center;
84
+ justify-content: center;
85
+ transition: opacity 0.3s ease;
86
+ }
87
+ .tutorial-dialog {
88
+ pointer-events: auto;
89
+ background: rgba(15, 23, 42, 0.85);
90
+ backdrop-filter: blur(16px);
91
+ border: 1px solid rgba(255, 255, 255, 0.1);
92
+ border-radius: 16px;
93
+ padding: 24px;
94
+ width: 320px;
95
+ box-shadow: 0 20px 40px rgba(0, 0, 0, 0.5), inset 0 1px 0 rgba(255, 255, 255, 0.1);
96
+ color: #fff;
97
+ position: absolute;
98
+ transition: all 0.4s cubic-bezier(0.16, 1, 0.3, 1);
99
+ transform: scale(0.95) translateY(10px);
100
+ opacity: 0;
101
+ animation: tutorialSlide 0.4s cubic-bezier(0.16, 1, 0.3, 1) forwards;
102
+ }
103
+ @keyframes tutorialSlide {
104
+ to { transform: scale(1) translateY(0); opacity: 1; }
105
+ }
106
+ .tutorial-title {
107
+ font-size: 18px;
108
+ font-weight: 600;
109
+ margin: 0 0 12px 0;
110
+ background: linear-gradient(135deg, #a78bfa, #60a5fa);
111
+ -webkit-background-clip: text;
112
+ -webkit-text-fill-color: transparent;
113
+ }
114
+ .tutorial-text {
115
+ font-size: 14px;
116
+ color: #cbd5e1;
117
+ line-height: 1.5;
118
+ margin-bottom: 24px;
119
+ }
120
+ .tutorial-footer {
121
+ display: flex;
122
+ justify-content: space-between;
123
+ align-items: center;
124
+ border-top: 1px solid rgba(255, 255, 255, 0.05);
125
+ padding-top: 16px;
126
+ }
127
+ .tutorial-dots {
128
+ display: flex;
129
+ gap: 6px;
130
+ }
131
+ .tutorial-dot {
132
+ width: 6px; height: 6px;
133
+ border-radius: 50%;
134
+ background: rgba(255, 255, 255, 0.2);
135
+ transition: all 0.3s;
136
+ }
137
+ .tutorial-dot.active {
138
+ background: #a78bfa;
139
+ width: 16px;
140
+ border-radius: 4px;
141
+ }
142
+ .tutorial-btn {
143
+ background: linear-gradient(135deg, #a78bfa, #60a5fa);
144
+ border: none;
145
+ border-radius: 8px;
146
+ color: #fff;
147
+ font-weight: 600;
148
+ padding: 8px 16px;
149
+ cursor: pointer;
150
+ transition: all 0.2s;
151
+ box-shadow: 0 4px 12px rgba(96, 165, 250, 0.3);
152
+ }
153
+ .tutorial-btn:hover {
154
+ transform: translateY(-1px);
155
+ box-shadow: 0 6px 16px rgba(96, 165, 250, 0.4);
156
+ }
157
+ .tutorial-skip {
158
+ position: absolute;
159
+ top: 24px; right: 24px;
160
+ color: rgba(255, 255, 255, 0.4);
161
+ background: none; border: none;
162
+ font-size: 12px; cursor: pointer;
163
+ transition: color 0.2s;
164
+ pointer-events: auto;
165
+ }
166
+ .tutorial-skip:hover {
167
+ color: #fff;
168
+ }
169
+ .tutorial-highlight {
170
+ position: fixed;
171
+ box-shadow: 0 0 0 9999px rgba(0,0,0,0.7), 0 0 20px 5px rgba(167, 139, 250, 0.4);
172
+ border-radius: 12px;
173
+ pointer-events: none;
174
+ z-index: -1;
175
+ transition: all 0.4s cubic-bezier(0.16, 1, 0.3, 1);
176
+ border: 2px solid #a78bfa;
177
+ }
178
+ .tutorial-bg {
179
+ position: fixed;
180
+ top: 0; left: 0; right: 0; bottom: 0;
181
+ background: rgba(0, 0, 0, 0.7);
182
+ z-index: -2;
183
+ pointer-events: none;
184
+ transition: opacity 0.4s;
185
+ }
186
+ `;
187
+ document.head.appendChild(style);
188
+
189
+ const dialog = document.createElement('div');
190
+ dialog.className = 'tutorial-dialog';
191
+
192
+ const skipBtn = document.createElement('button');
193
+ skipBtn.className = 'tutorial-skip';
194
+ skipBtn.innerText = 'Skip';
195
+ skipBtn.onclick = finishTutorial;
196
+
197
+ const highlightBox = document.createElement('div');
198
+ highlightBox.className = 'tutorial-highlight';
199
+ highlightBox.style.display = 'none';
200
+
201
+ const bgShadow = document.createElement('div');
202
+ bgShadow.className = 'tutorial-bg';
203
+ bgShadow.style.display = 'block';
204
+
205
+ overlay.appendChild(bgShadow);
206
+ overlay.appendChild(highlightBox);
207
+ overlay.appendChild(skipBtn);
208
+ overlay.appendChild(dialog);
209
+ document.body.appendChild(overlay);
210
+
211
+ function renderStep() {
212
+ const step = steps[currentStep];
213
+
214
+ const dotsHtml = steps.map((_, i) =>
215
+ `<div class="tutorial-dot ${i === currentStep ? 'active' : ''}"></div>`
216
+ ).join('');
217
+
218
+ dialog.innerHTML = `
219
+ <h3 class="tutorial-title">${step.title}</h3>
220
+ <div class="tutorial-text">${step.text}</div>
221
+ <div class="tutorial-footer">
222
+ <div class="tutorial-dots">${dotsHtml}</div>
223
+ <button class="tutorial-btn" id="tutorialNextBtn">${currentStep === steps.length - 1 ? 'Start Mapping 🚀' : 'Next'}</button>
224
+ </div>
225
+ `;
226
+
227
+ document.getElementById('tutorialNextBtn')!.onclick = nextStep;
228
+
229
+ // Handle target highlighting
230
+ if (step.target) {
231
+ const el = document.querySelector(step.target);
232
+ if (el) {
233
+ const rect = el.getBoundingClientRect();
234
+ highlightBox.style.display = 'block';
235
+ bgShadow.style.display = 'none';
236
+ // Add some padding to highlight
237
+ const p = 8;
238
+ highlightBox.style.top = `${rect.top - p}px`;
239
+ highlightBox.style.left = `${rect.left - p}px`;
240
+ highlightBox.style.width = `${rect.width + p * 2}px`;
241
+ highlightBox.style.height = `${rect.height + p * 2}px`;
242
+
243
+ if (step.position === 'left-of') {
244
+ // Position dialog to the left of the highlight
245
+ dialog.style.top = `${rect.top}px`;
246
+ dialog.style.left = `${rect.left - 340}px`;
247
+ dialog.style.bottom = 'auto';
248
+ dialog.style.right = 'auto';
249
+ dialog.style.transform = 'translateY(0)';
250
+ } else if (step.position === 'right-of') {
251
+ // Position dialog to the right of the highlight
252
+ dialog.style.top = `${rect.top}px`;
253
+ dialog.style.left = `${rect.right + 20}px`;
254
+ dialog.style.bottom = 'auto';
255
+ dialog.style.right = 'auto';
256
+ dialog.style.transform = 'translateY(0)';
257
+ } else if (step.position === 'above') {
258
+ dialog.style.top = `${Math.max(20, rect.top - 200)}px`;
259
+ dialog.style.left = `${rect.left + (rect.width / 2) - 160}px`;
260
+ dialog.style.bottom = 'auto';
261
+ dialog.style.right = 'auto';
262
+ dialog.style.transform = 'translateY(0)';
263
+ }
264
+ } else {
265
+ highlightBox.style.display = 'none';
266
+ bgShadow.style.display = 'block';
267
+ centerDialog();
268
+ }
269
+ } else {
270
+ highlightBox.style.display = 'none';
271
+ bgShadow.style.display = 'block';
272
+ centerDialog();
273
+ }
274
+ }
275
+
276
+ function centerDialog() {
277
+ dialog.style.top = '50%';
278
+ dialog.style.left = '50%';
279
+ dialog.style.transform = 'translate(-50%, -50%)';
280
+ }
281
+
282
+ function nextStep() {
283
+ if (currentStep < steps.length - 1) {
284
+ currentStep++;
285
+ renderStep();
286
+ } else {
287
+ finishTutorial();
288
+ }
289
+ }
290
+
291
+ function finishTutorial() {
292
+ overlay.style.opacity = '0';
293
+ setTimeout(() => {
294
+ overlay.remove();
295
+ style.remove();
296
+ }, 300);
297
+ localStorage.setItem('gitcanvas:tutorial_completed', 'true');
298
+ }
299
+
300
+ renderStep();
301
+ }
@@ -0,0 +1,93 @@
1
+ import { showToast } from './utils';
2
+
3
+ let cachedCommit = '220aa78';
4
+ let cachedCommitDate = '2026-03-17';
5
+
6
+ export function getVersion(): string {
7
+ return cachedCommit;
8
+ }
9
+
10
+ export function getVersionDate(): string {
11
+ return cachedCommitDate;
12
+ }
13
+
14
+ async function fetchVersion(): Promise<{ commit: string; commitDate: string }> {
15
+ const bootstrapCommit = (window as any).__GITMAPS_BUILD_COMMIT__ || '';
16
+ const bootstrapDate = (window as any).__GITMAPS_BUILD_DATE__ || '';
17
+
18
+ if (bootstrapCommit) {
19
+ cachedCommit = bootstrapCommit;
20
+ cachedCommitDate = bootstrapDate;
21
+ return { commit: cachedCommit, commitDate: cachedCommitDate };
22
+ }
23
+
24
+ try {
25
+ const response = await fetch('/api/build-info', { cache: 'no-store' });
26
+ if (!response.ok) throw new Error(await response.text());
27
+ const data = await response.json();
28
+ cachedCommit = data.commit || 'unknown';
29
+ cachedCommitDate = data.commitDate || '';
30
+ return { commit: cachedCommit, commitDate: cachedCommitDate };
31
+ } catch {
32
+ return { commit: cachedCommit, commitDate: cachedCommitDate };
33
+ }
34
+ }
35
+
36
+ export async function renderVersionBadge(): Promise<void> {
37
+ const existing = document.getElementById('versionBadge');
38
+ if (existing) existing.remove();
39
+
40
+ const badge = document.createElement('button');
41
+ badge.id = 'versionBadge';
42
+ badge.type = 'button';
43
+ badge.style.cssText = `
44
+ position: fixed;
45
+ top: 12px;
46
+ right: 12px;
47
+ padding: 6px 12px;
48
+ background: rgba(15, 23, 42, 0.88);
49
+ border: 1px solid rgba(124, 58, 237, 0.32);
50
+ border-radius: 8px;
51
+ font-size: 10px;
52
+ color: rgba(167, 139, 250, 0.9);
53
+ font-family: 'JetBrains Mono', monospace;
54
+ z-index: 10002;
55
+ backdrop-filter: blur(8px);
56
+ cursor: pointer;
57
+ transition: all 0.2s;
58
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.22);
59
+ `;
60
+ badge.innerHTML = `
61
+ <span style="opacity:0.64">GitMaps</span>
62
+ <span style="margin:0 6px">·</span>
63
+ <span id="versionBadgeCommit" style="color:#c4b5fd">loading...</span>
64
+ `;
65
+ badge.title = 'Loading build version...';
66
+
67
+ badge.addEventListener('click', async () => {
68
+ if (!cachedCommit || cachedCommit === 'unknown') {
69
+ showToast('Build commit not available yet', 'error');
70
+ return;
71
+ }
72
+ await navigator.clipboard.writeText(cachedCommit);
73
+ showToast(`Copied commit ${cachedCommit}`, 'success');
74
+ });
75
+
76
+ badge.addEventListener('mouseenter', () => {
77
+ badge.style.background = 'rgba(15, 23, 42, 0.96)';
78
+ badge.style.borderColor = 'rgba(124, 58, 237, 0.62)';
79
+ });
80
+ badge.addEventListener('mouseleave', () => {
81
+ badge.style.background = 'rgba(15, 23, 42, 0.88)';
82
+ badge.style.borderColor = 'rgba(124, 58, 237, 0.32)';
83
+ });
84
+
85
+ document.body.appendChild(badge);
86
+
87
+ const { commit, commitDate } = await fetchVersion();
88
+ const commitEl = document.getElementById('versionBadgeCommit');
89
+ if (commitEl) commitEl.textContent = commit;
90
+ badge.title = commitDate
91
+ ? `GitMaps ${commit}\nBuilt from commit on ${commitDate}\nClick to copy commit hash`
92
+ : `GitMaps ${commit}\nClick to copy commit hash`;
93
+ }