gitmaps 1.1.0 → 1.1.2
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.
- package/README.md +267 -118
- package/app/[...slug]/page.client.tsx +1 -0
- package/app/[...slug]/page.tsx +6 -0
- package/app/analytics.db +0 -0
- package/app/api/analytics/route.ts +64 -0
- package/app/api/auth/positions/route.ts +95 -33
- package/app/api/build-info/route.ts +19 -0
- package/app/api/chat/route.ts +13 -2
- package/app/api/og-image/route.ts +14 -0
- package/app/api/repo/file-content/route.ts +73 -20
- package/app/api/repo/load/route.test.ts +62 -0
- package/app/api/repo/load/route.ts +41 -1
- package/app/api/repo/pdf-thumb/route.ts +127 -0
- package/app/api/repo/resolve-slug/route.ts +51 -0
- package/app/api/repo/tree/route.ts +188 -104
- package/app/api/version/route.ts +26 -0
- package/app/globals.css +5706 -4938
- package/app/layout.tsx +1279 -490
- package/app/lib/auto-arrange.test.ts +158 -0
- package/app/lib/auto-arrange.ts +147 -0
- package/app/lib/canvas-export.ts +358 -358
- package/app/lib/canvas.ts +625 -564
- package/app/lib/cards.tsx +1361 -916
- package/app/lib/chat.tsx +65 -9
- package/app/lib/code-editor.ts +86 -2
- package/app/lib/context.test.ts +32 -0
- package/app/lib/context.ts +19 -3
- package/app/lib/cursor-sharing.ts +34 -0
- package/app/lib/events.tsx +71 -93
- package/app/lib/export-canvas.ts +287 -0
- package/app/lib/file-card-plugin.ts +148 -148
- package/app/lib/file-modal.tsx +49 -0
- package/app/lib/file-preview.ts +486 -427
- package/app/lib/github-import.test.ts +424 -0
- package/app/lib/initial-route-hydration.test.ts +283 -0
- package/app/lib/initial-route-hydration.ts +202 -0
- package/app/lib/landing-reset.test.ts +99 -0
- package/app/lib/landing-reset.ts +106 -0
- package/app/lib/landing-shell.test.ts +75 -0
- package/app/lib/large-repo-optimization.ts +37 -0
- package/app/lib/layout-snapshots.ts +320 -0
- package/app/lib/loading.test.ts +69 -0
- package/app/lib/loading.tsx +160 -45
- package/app/lib/mount-cleanup.test.ts +52 -0
- package/app/lib/mount-cleanup.ts +34 -0
- package/app/lib/mount-init.test.ts +123 -0
- package/app/lib/mount-init.ts +107 -0
- package/app/lib/mount-lifecycle.test.ts +39 -0
- package/app/lib/mount-lifecycle.ts +12 -0
- package/app/lib/mount-route-wiring.test.ts +87 -0
- package/app/lib/mount-route-wiring.ts +84 -0
- package/app/lib/multi-repo.ts +14 -0
- package/app/lib/onboarding-tutorial.ts +278 -0
- package/app/lib/positions.ts +190 -121
- package/app/lib/recent-commits.test.ts +947 -0
- package/app/lib/recent-commits.ts +227 -0
- package/app/lib/repo-handoff.test.ts +23 -0
- package/app/lib/repo-handoff.ts +16 -0
- package/app/lib/repo-progressive.ts +119 -0
- package/app/lib/repo-select.test.ts +61 -0
- package/app/lib/repo-select.ts +74 -0
- package/app/lib/repo.tsx +1383 -987
- package/app/lib/role.ts +228 -0
- package/app/lib/route-catchall.test.ts +27 -0
- package/app/lib/route-repo-entry.test.ts +95 -0
- package/app/lib/route-repo-entry.ts +36 -0
- package/app/lib/router-contract.test.ts +22 -0
- package/app/lib/router-contract.ts +19 -0
- package/app/lib/shared-layout.test.ts +86 -0
- package/app/lib/shared-layout.ts +82 -0
- package/app/lib/status-bar.test.ts +118 -0
- package/app/lib/status-bar.ts +365 -128
- package/app/lib/sync-controls.test.ts +43 -0
- package/app/lib/sync-controls.tsx +303 -0
- package/app/lib/test-dom.ts +145 -0
- package/app/lib/test-fixtures/router-contract/[...slug]/page.tsx +3 -0
- package/app/lib/test-fixtures/router-contract/api/health/route.ts +3 -0
- package/app/lib/test-fixtures/router-contract/api/version/route.ts +3 -0
- package/app/lib/test-fixtures/router-contract/galaxy-canvas/page.tsx +3 -0
- package/app/lib/test-fixtures/router-contract/page.tsx +3 -0
- package/app/lib/transclusion-smoke.test.ts +163 -0
- package/app/lib/tutorial.ts +301 -0
- package/app/lib/version.ts +93 -0
- package/app/lib/viewport-culling.ts +740 -735
- package/app/lib/virtual-files.ts +456 -0
- package/app/lib/webgl-text.ts +189 -0
- package/app/lib/{galaxydraw-bridge.ts → xydraw-bridge.ts} +485 -482
- package/app/lib/{galaxydraw.test.ts → xydraw.test.ts} +228 -229
- package/app/og-image.png +0 -0
- package/app/page.client.tsx +70 -269
- package/app/page.tsx +15 -16
- package/app/state/machine.js +13 -0
- package/package.json +84 -75
- package/server.ts +10 -0
- package/app/[owner]/[repo]/page.tsx +0 -6
- package/app/[slug]/page.tsx +0 -6
- package/packages/galaxydraw/README.md +0 -296
- package/packages/galaxydraw/banner.png +0 -0
- package/packages/galaxydraw/demo/build-static.ts +0 -100
- package/packages/galaxydraw/demo/client.ts +0 -154
- package/packages/galaxydraw/demo/dist/client.js +0 -8
- package/packages/galaxydraw/demo/index.html +0 -256
- package/packages/galaxydraw/demo/server.ts +0 -96
- package/packages/galaxydraw/dist/index.js +0 -984
- package/packages/galaxydraw/dist/index.js.map +0 -16
- package/packages/galaxydraw/node_modules/.bin/tsc.bunx +0 -0
- package/packages/galaxydraw/node_modules/.bin/tsc.exe +0 -0
- package/packages/galaxydraw/node_modules/.bin/tsserver.bunx +0 -0
- package/packages/galaxydraw/node_modules/.bin/tsserver.exe +0 -0
- package/packages/galaxydraw/package.json +0 -49
- package/packages/galaxydraw/perf.test.ts +0 -284
- package/packages/galaxydraw/src/core/cards.ts +0 -435
- package/packages/galaxydraw/src/core/engine.ts +0 -339
- package/packages/galaxydraw/src/core/events.ts +0 -81
- package/packages/galaxydraw/src/core/layout.ts +0 -136
- package/packages/galaxydraw/src/core/minimap.ts +0 -216
- package/packages/galaxydraw/src/core/state.ts +0 -177
- package/packages/galaxydraw/src/core/viewport.ts +0 -106
- package/packages/galaxydraw/src/galaxydraw.css +0 -166
- package/packages/galaxydraw/src/index.ts +0 -40
- package/packages/galaxydraw/tsconfig.json +0 -30
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { CanvasContext } from './context';
|
|
2
|
+
import {
|
|
3
|
+
bootstrapInitialRouteUi,
|
|
4
|
+
handleInitialRouteError,
|
|
5
|
+
hideInitialRouteLanding,
|
|
6
|
+
hydrateInitialRouteRepo,
|
|
7
|
+
migrateLegacyHashRoute,
|
|
8
|
+
resolveInitialRepoPath,
|
|
9
|
+
showInitialRouteCloneStart,
|
|
10
|
+
} from './initial-route-hydration';
|
|
11
|
+
import { handlePopstateRepoEntry } from './route-repo-entry';
|
|
12
|
+
|
|
13
|
+
export async function wireMountRoutes(
|
|
14
|
+
ctx: CanvasContext,
|
|
15
|
+
options: {
|
|
16
|
+
isDisposed: () => boolean;
|
|
17
|
+
showLandingPlaceholder: () => void;
|
|
18
|
+
updateFavoriteStar: (path: string) => void;
|
|
19
|
+
applySharedLayout: () => Promise<void>;
|
|
20
|
+
hydrateRoutes?: typeof hydrateInitialRouteRepo;
|
|
21
|
+
resolveRepoPath?: typeof resolveInitialRepoPath;
|
|
22
|
+
handleRouteError?: typeof handleInitialRouteError;
|
|
23
|
+
bootstrapRepoUi?: typeof bootstrapInitialRouteUi;
|
|
24
|
+
bindPopstate?: typeof bindMountPopstate;
|
|
25
|
+
},
|
|
26
|
+
) {
|
|
27
|
+
const hydrateRoutes = options.hydrateRoutes || hydrateInitialRouteRepo;
|
|
28
|
+
const resolveRepoPath = options.resolveRepoPath || resolveInitialRepoPath;
|
|
29
|
+
const handleRouteError = options.handleRouteError || handleInitialRouteError;
|
|
30
|
+
const bootstrapRepoUi = options.bootstrapRepoUi || bootstrapInitialRouteUi;
|
|
31
|
+
const bindPopstate = options.bindPopstate || bindMountPopstate;
|
|
32
|
+
|
|
33
|
+
await hydrateRoutes(ctx, {
|
|
34
|
+
disposed: options.isDisposed(),
|
|
35
|
+
showLandingPlaceholder: options.showLandingPlaceholder,
|
|
36
|
+
hideLanding: hideInitialRouteLanding,
|
|
37
|
+
migrateLegacyHashRoute,
|
|
38
|
+
resolveRepoPath: async (slug) => {
|
|
39
|
+
try {
|
|
40
|
+
return await resolveRepoPath(slug, {
|
|
41
|
+
onCloneStart: showInitialRouteCloneStart,
|
|
42
|
+
} as any);
|
|
43
|
+
} catch (err: any) {
|
|
44
|
+
return await handleRouteError(err);
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
bootstrapRepoUi: async (resolvedPath) => {
|
|
48
|
+
await bootstrapRepoUi(ctx, resolvedPath, {
|
|
49
|
+
disposed: options.isDisposed(),
|
|
50
|
+
applySharedLayout: options.applySharedLayout,
|
|
51
|
+
} as any);
|
|
52
|
+
},
|
|
53
|
+
updateFavoriteStar: options.updateFavoriteStar,
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
bindPopstate(ctx, {
|
|
57
|
+
isDisposed: options.isDisposed,
|
|
58
|
+
showLandingPlaceholder: options.showLandingPlaceholder,
|
|
59
|
+
updateFavoriteStar: options.updateFavoriteStar,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function bindMountPopstate(
|
|
64
|
+
ctx: CanvasContext,
|
|
65
|
+
options: {
|
|
66
|
+
isDisposed: () => boolean;
|
|
67
|
+
showLandingPlaceholder: () => void;
|
|
68
|
+
updateFavoriteStar: (path: string) => void;
|
|
69
|
+
addListener?: (type: string, handler: () => void) => void;
|
|
70
|
+
},
|
|
71
|
+
) {
|
|
72
|
+
const addListener = options.addListener || ((type: string, handler: () => void) => {
|
|
73
|
+
window.addEventListener(type, handler);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
addListener('popstate', () => {
|
|
77
|
+
handlePopstateRepoEntry(ctx, {
|
|
78
|
+
disposed: options.isDisposed(),
|
|
79
|
+
currentRepoPath: ctx.snap().context.repoPath,
|
|
80
|
+
showLandingPlaceholder: options.showLandingPlaceholder,
|
|
81
|
+
updateFavoriteStar: options.updateFavoriteStar,
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
}
|
package/app/lib/multi-repo.ts
CHANGED
|
@@ -165,6 +165,20 @@ export function unloadRepo(ctx: CanvasContext, repoPath: string) {
|
|
|
165
165
|
/**
|
|
166
166
|
* Create repo zone tabs in the sidebar for switching between repos.
|
|
167
167
|
*/
|
|
168
|
+
export function clearMultiRepoWorkspace(ctx?: CanvasContext) {
|
|
169
|
+
for (const [, repo] of loadedRepos) {
|
|
170
|
+
if (repo.zoneLabel) repo.zoneLabel.remove();
|
|
171
|
+
}
|
|
172
|
+
loadedRepos.clear();
|
|
173
|
+
_activeRepoPath = null;
|
|
174
|
+
|
|
175
|
+
const container = document.getElementById('repoTabs');
|
|
176
|
+
if (container) {
|
|
177
|
+
container.innerHTML = '';
|
|
178
|
+
container.style.display = 'none';
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
168
182
|
export function renderRepoTabs(ctx: CanvasContext) {
|
|
169
183
|
const container = document.getElementById('repoTabs');
|
|
170
184
|
if (!container) return;
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interactive Onboarding Tutorial — Guide new users through GitMaps
|
|
3
|
+
*
|
|
4
|
+
* Features:
|
|
5
|
+
* - Step-by-step interactive tour
|
|
6
|
+
* - Highlights UI elements as it explains them
|
|
7
|
+
* - Keyboard shortcuts cheat sheet
|
|
8
|
+
* - Skip/resume anytime
|
|
9
|
+
* - Persists completion state
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { CanvasContext } from './context';
|
|
13
|
+
|
|
14
|
+
export interface TutorialStep {
|
|
15
|
+
id: string;
|
|
16
|
+
title: string;
|
|
17
|
+
description: string;
|
|
18
|
+
highlightSelector: string;
|
|
19
|
+
position: 'top' | 'bottom' | 'left' | 'right';
|
|
20
|
+
action?: () => void;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const TUTORIAL_STEPS: TutorialStep[] = [
|
|
24
|
+
{
|
|
25
|
+
id: 'welcome',
|
|
26
|
+
title: 'Welcome to GitMaps! 🎉',
|
|
27
|
+
description: 'Explore codebases on an infinite canvas. Let\'s take a quick tour of the features.',
|
|
28
|
+
highlightSelector: '#app',
|
|
29
|
+
position: 'bottom',
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
id: 'repo-selector',
|
|
33
|
+
title: 'Repository Selector',
|
|
34
|
+
description: 'Select any loaded repository from the dropdown. Import new repos from GitHub with the button.',
|
|
35
|
+
highlightSelector: '#repoSelect',
|
|
36
|
+
position: 'bottom',
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
id: 'commit-timeline',
|
|
40
|
+
title: 'Commit Timeline',
|
|
41
|
+
description: 'Browse through commit history. Click any commit to see what changed. Use ← → arrow keys to navigate.',
|
|
42
|
+
highlightSelector: '#commitTimeline',
|
|
43
|
+
position: 'right',
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
id: 'canvas-area',
|
|
47
|
+
title: 'Infinite Canvas',
|
|
48
|
+
description: 'Your code lives here! Each file is a card. Pan with Space+Drag or middle-click. Scroll to zoom.',
|
|
49
|
+
highlightSelector: '#canvasViewport',
|
|
50
|
+
position: 'top',
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
id: 'file-cards',
|
|
54
|
+
title: 'File Cards',
|
|
55
|
+
description: 'Each card shows a file with code preview. Green/red markers show additions/deletions. Hover to see full preview.',
|
|
56
|
+
highlightSelector: '.file-card',
|
|
57
|
+
position: 'bottom',
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
id: 'minimap',
|
|
61
|
+
title: 'Minimap',
|
|
62
|
+
description: 'Never get lost! The minimap shows your entire canvas. Click to jump to any area.',
|
|
63
|
+
highlightSelector: '#minimap',
|
|
64
|
+
position: 'top',
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
id: 'arrange-toolbar',
|
|
68
|
+
title: 'Arrange Tools',
|
|
69
|
+
description: 'Organize cards with H (row), V (column), or G (grid). W fits all cards on screen.',
|
|
70
|
+
highlightSelector: '#arrangeToolbar',
|
|
71
|
+
position: 'bottom',
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
id: 'zoom-controls',
|
|
75
|
+
title: 'Zoom Controls',
|
|
76
|
+
description: 'Fine-tune zoom with the slider or +/- keys. Current zoom level shown in percentage.',
|
|
77
|
+
highlightSelector: '#zoomSlider',
|
|
78
|
+
position: 'top',
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
id: 'shortcuts',
|
|
82
|
+
title: 'Keyboard Shortcuts',
|
|
83
|
+
description: 'Press ? anytime to see all shortcuts. Power users love Ctrl+F (search), Ctrl+G (dependency graph), and Ctrl+O (find file).',
|
|
84
|
+
highlightSelector: '#hotkeyToggle',
|
|
85
|
+
position: 'bottom',
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
id: 'done',
|
|
89
|
+
title: 'You\'re Ready! 🚀',
|
|
90
|
+
description: 'Start exploring! Import a repo, arrange cards your way, and enjoy spatial code exploration.',
|
|
91
|
+
highlightSelector: '#app',
|
|
92
|
+
position: 'bottom',
|
|
93
|
+
action: () => {
|
|
94
|
+
localStorage.setItem('gitcanvas:onboardingComplete', 'true');
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
];
|
|
98
|
+
|
|
99
|
+
let currentStep = 0;
|
|
100
|
+
let tutorialOverlay: HTMLElement | null = null;
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Check if user has completed onboarding
|
|
104
|
+
*/
|
|
105
|
+
export function hasCompletedOnboarding(): boolean {
|
|
106
|
+
return localStorage.getItem('gitcanvas:onboardingComplete') === 'true';
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Reset onboarding progress
|
|
111
|
+
*/
|
|
112
|
+
export function resetOnboarding(): void {
|
|
113
|
+
localStorage.removeItem('gitcanvas:onboardingComplete');
|
|
114
|
+
currentStep = 0;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Start the onboarding tutorial
|
|
119
|
+
*/
|
|
120
|
+
export function startOnboarding(ctx: CanvasContext): void {
|
|
121
|
+
if (tutorialOverlay) return;
|
|
122
|
+
|
|
123
|
+
currentStep = 0;
|
|
124
|
+
showTutorialStep(ctx, currentStep);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Show a specific tutorial step
|
|
129
|
+
*/
|
|
130
|
+
function showTutorialStep(ctx: CanvasContext, stepIndex: number): void {
|
|
131
|
+
if (stepIndex < 0 || stepIndex >= TUTORIAL_STEPS.length) {
|
|
132
|
+
hideTutorial();
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const step = TUTORIAL_STEPS[stepIndex];
|
|
137
|
+
|
|
138
|
+
// Create overlay if needed
|
|
139
|
+
if (!tutorialOverlay) {
|
|
140
|
+
tutorialOverlay = document.createElement('div');
|
|
141
|
+
tutorialOverlay.className = 'tutorial-overlay';
|
|
142
|
+
tutorialOverlay.innerHTML = `
|
|
143
|
+
<div class="tutorial-backdrop"></div>
|
|
144
|
+
<div class="tutorial-content">
|
|
145
|
+
<div class="tutorial-header">
|
|
146
|
+
<h3 class="tutorial-title"></h3>
|
|
147
|
+
<button class="tutorial-close" id="tutorialClose">×</button>
|
|
148
|
+
</div>
|
|
149
|
+
<div class="tutorial-description"></div>
|
|
150
|
+
<div class="tutorial-progress">
|
|
151
|
+
<span class="tutorial-step-count"></span>
|
|
152
|
+
</div>
|
|
153
|
+
<div class="tutorial-actions">
|
|
154
|
+
<button class="btn-ghost" id="tutorialSkip">Skip Tour</button>
|
|
155
|
+
<button class="btn-primary" id="tutorialNext">Next →</button>
|
|
156
|
+
</div>
|
|
157
|
+
</div>
|
|
158
|
+
`;
|
|
159
|
+
|
|
160
|
+
document.body.appendChild(tutorialOverlay);
|
|
161
|
+
|
|
162
|
+
// Add styles
|
|
163
|
+
const style = document.createElement('style');
|
|
164
|
+
style.textContent = `
|
|
165
|
+
.tutorial-overlay {
|
|
166
|
+
position: fixed;
|
|
167
|
+
inset: 0;
|
|
168
|
+
z-index: 99999;
|
|
169
|
+
display: flex;
|
|
170
|
+
align-items: center;
|
|
171
|
+
justify-content: center;
|
|
172
|
+
animation: fadeIn 0.2s ease;
|
|
173
|
+
}
|
|
174
|
+
.tutorial-backdrop {
|
|
175
|
+
position: absolute;
|
|
176
|
+
inset: 0;
|
|
177
|
+
background: rgba(10, 10, 15, 0.85);
|
|
178
|
+
backdrop-filter: blur(4px);
|
|
179
|
+
}
|
|
180
|
+
.tutorial-content {
|
|
181
|
+
position: relative;
|
|
182
|
+
background: var(--bg-secondary);
|
|
183
|
+
border: 1px solid var(--border-primary);
|
|
184
|
+
border-radius: 12px;
|
|
185
|
+
padding: 24px;
|
|
186
|
+
max-width: 450px;
|
|
187
|
+
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
|
|
188
|
+
animation: slideUp 0.3s ease;
|
|
189
|
+
}
|
|
190
|
+
.tutorial-header {
|
|
191
|
+
display: flex;
|
|
192
|
+
align-items: center;
|
|
193
|
+
justify-content: space-between;
|
|
194
|
+
margin-bottom: 12px;
|
|
195
|
+
}
|
|
196
|
+
.tutorial-title {
|
|
197
|
+
margin: 0;
|
|
198
|
+
font-size: 18px;
|
|
199
|
+
font-weight: 600;
|
|
200
|
+
background: linear-gradient(135deg, #a78bfa, #60a5fa);
|
|
201
|
+
-webkit-background-clip: text;
|
|
202
|
+
-webkit-text-fill-color: transparent;
|
|
203
|
+
}
|
|
204
|
+
.tutorial-close {
|
|
205
|
+
background: none;
|
|
206
|
+
border: none;
|
|
207
|
+
color: var(--text-muted);
|
|
208
|
+
font-size: 24px;
|
|
209
|
+
cursor: pointer;
|
|
210
|
+
padding: 4px 8px;
|
|
211
|
+
border-radius: 4px;
|
|
212
|
+
transition: all 0.2s;
|
|
213
|
+
}
|
|
214
|
+
.tutorial-close:hover {
|
|
215
|
+
background: var(--bg-tertiary);
|
|
216
|
+
color: var(--text-primary);
|
|
217
|
+
}
|
|
218
|
+
.tutorial-description {
|
|
219
|
+
color: var(--text-primary);
|
|
220
|
+
font-size: 14px;
|
|
221
|
+
line-height: 1.6;
|
|
222
|
+
margin-bottom: 20px;
|
|
223
|
+
}
|
|
224
|
+
.tutorial-progress {
|
|
225
|
+
margin-bottom: 20px;
|
|
226
|
+
}
|
|
227
|
+
.tutorial-step-count {
|
|
228
|
+
font-size: 12px;
|
|
229
|
+
color: var(--text-muted);
|
|
230
|
+
}
|
|
231
|
+
.tutorial-actions {
|
|
232
|
+
display: flex;
|
|
233
|
+
gap: 8px;
|
|
234
|
+
justify-content: space-between;
|
|
235
|
+
}
|
|
236
|
+
.tutorial-actions button {
|
|
237
|
+
padding: 10px 20px;
|
|
238
|
+
border-radius: 8px;
|
|
239
|
+
border: none;
|
|
240
|
+
font-size: 13px;
|
|
241
|
+
font-weight: 600;
|
|
242
|
+
cursor: pointer;
|
|
243
|
+
transition: all 0.2s;
|
|
244
|
+
}
|
|
245
|
+
.btn-primary {
|
|
246
|
+
background: linear-gradient(135deg, #7c3aed, #3b82f6);
|
|
247
|
+
color: white;
|
|
248
|
+
}
|
|
249
|
+
.btn-primary:hover {
|
|
250
|
+
transform: translateY(-1px);
|
|
251
|
+
box-shadow: 0 4px 12px rgba(124, 58, 237, 0.4);
|
|
252
|
+
}
|
|
253
|
+
@keyframes fadeIn {
|
|
254
|
+
from { opacity: 0; }
|
|
255
|
+
to { opacity: 1; }
|
|
256
|
+
}
|
|
257
|
+
@keyframes slideUp {
|
|
258
|
+
from { transform: translateY(20px); opacity: 0; }
|
|
259
|
+
to { transform: translateY(0); opacity: 1; }
|
|
260
|
+
}
|
|
261
|
+
`;
|
|
262
|
+
document.head.appendChild(style);
|
|
263
|
+
|
|
264
|
+
// Wire up buttons
|
|
265
|
+
tutorialOverlay.querySelector('#tutorialClose')?.addEventListener('click', hideTutorial);
|
|
266
|
+
tutorialOverlay.querySelector('#tutorialSkip')?.addEventListener('click', () => {
|
|
267
|
+
hideTutorial();
|
|
268
|
+
localStorage.setItem('gitcanvas:onboardingComplete', 'true');
|
|
269
|
+
});
|
|
270
|
+
tutorialOverlay.querySelector('#tutorialNext')?.addEventListener('click', () => {
|
|
271
|
+
const step = TUTORIAL_STEPS[currentStep];
|
|
272
|
+
if (step.action) step.action();
|
|
273
|
+
currentStep++;
|
|
274
|
+
showTutorialStep(ctx, currentStep);
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
// Keyboard navigation
|
|
278
|
+
document.addEventListener('keydown', ha
|