awacke1 commited on
Commit
8486ac9
·
verified ·
1 Parent(s): d45889f

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +488 -18
index.html CHANGED
@@ -1,19 +1,489 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  </html>
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Interactive Diagram Tool with AI</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <link rel="preconnect" href="https://fonts.googleapis.com">
9
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
10
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
11
+ <style>
12
+ body {
13
+ font-family: 'Inter', sans-serif;
14
+ overscroll-behavior: none;
15
+ }
16
+ .shape {
17
+ cursor: move;
18
+ user-select: none;
19
+ transition: transform 0.5s cubic-bezier(0.25, 1, 0.5, 1); /* Smooth animation for position changes */
20
+ }
21
+ .shape-text {
22
+ pointer-events: none;
23
+ user-select: none;
24
+ font-size: 14px;
25
+ font-weight: 500;
26
+ }
27
+ #canvas-svg {
28
+ touch-action: none;
29
+ }
30
+ .connector-line {
31
+ stroke-width: 2.5;
32
+ stroke-linecap: round;
33
+ }
34
+ .highlight-connector {
35
+ stroke: #fbbf24; /* amber-400 */
36
+ stroke-width: 4;
37
+ filter: drop-shadow(0 2px 4px rgba(0,0,0,0.2));
38
+ }
39
+ /* Modal styles */
40
+ #ai-modal {
41
+ transition: opacity 0.3s ease-in-out;
42
+ }
43
+ #ai-modal.hidden {
44
+ opacity: 0;
45
+ pointer-events: none;
46
+ }
47
+ .spinner {
48
+ border: 4px solid rgba(0, 0, 0, 0.1);
49
+ width: 36px;
50
+ height: 36px;
51
+ border-radius: 50%;
52
+ border-left-color: #4f46e5; /* indigo-600 */
53
+ animation: spin 1s ease-in-out infinite;
54
+ }
55
+ @keyframes spin {
56
+ 0% { transform: rotate(0deg); }
57
+ 100% { transform: rotate(360deg); }
58
+ }
59
+ </style>
60
+ </head>
61
+ <body class="bg-gray-100 flex flex-col h-screen antialiased">
62
+
63
+ <!-- Header -->
64
+ <header class="bg-white shadow-md p-4 z-20">
65
+ <div class="container mx-auto flex justify-between items-center">
66
+ <h1 class="text-xl font-bold text-gray-800">Diagram Tool with AI</h1>
67
+ </div>
68
+ </header>
69
+
70
+ <!-- Main Content -->
71
+ <div class="flex flex-1 overflow-hidden">
72
+ <!-- Toolbar -->
73
+ <aside class="w-64 bg-white p-4 space-y-4 shadow-lg overflow-y-auto z-10">
74
+ <h2 class="text-lg font-semibold text-gray-700 border-b pb-2">Tools</h2>
75
+ <div class="space-y-2">
76
+ <button id="add-rect" class="w-full bg-sky-500 hover:bg-sky-600 text-white font-bold py-2 px-4 rounded-lg transition-colors duration-200 flex items-center justify-center space-x-2">
77
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-square" viewBox="0 0 16 16"><path d="M14 1a1 1 0 0 1 1 1v12a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1h12zM2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2z"/></svg>
78
+ <span>Rectangle</span>
79
+ </button>
80
+ <button id="add-circle" class="w-full bg-emerald-500 hover:bg-emerald-600 text-white font-bold py-2 px-4 rounded-lg transition-colors duration-200 flex items-center justify-center space-x-2">
81
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-circle" viewBox="0 0 16 16"><path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z"/></svg>
82
+ <span>Circle</span>
83
+ </button>
84
+ <button id="add-diamond" class="w-full bg-indigo-500 hover:bg-indigo-600 text-white font-bold py-2 px-4 rounded-lg transition-colors duration-200 flex items-center justify-center space-x-2">
85
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-diamond-fill" viewBox="0 0 16 16"><path fill-rule="evenodd" d="M6.95.435c.58-.58 1.52-.58 2.1 0l6.515 6.516c.58.58.58 1.519 0 2.098L9.05 15.565c-.58.58-1.519.58-2.1 0L.435 9.05c-.58-.58-.58-1.519 0-2.098L6.95.435z"/></svg>
86
+ <span>Diamond</span>
87
+ </button>
88
+ </div>
89
+ <div class="border-t pt-4 space-y-2">
90
+ <button id="connect-btn" class="w-full bg-gray-600 hover:bg-gray-700 text-white font-bold py-2 px-4 rounded-lg transition-colors duration-200">Connect Shapes</button>
91
+ <button id="clear-btn" class="w-full bg-red-500 hover:bg-red-600 text-white font-bold py-2 px-4 rounded-lg transition-colors duration-200">Clear Canvas</button>
92
+ </div>
93
+ <div class="border-t pt-4 space-y-2">
94
+ <button id="ai-magic-btn" class="w-full bg-gradient-to-r from-purple-500 to-pink-500 hover:from-purple-600 hover:to-pink-600 text-white font-bold py-2 px-4 rounded-lg transition-all duration-200 flex items-center justify-center space-x-2">
95
+ <span>✨</span>
96
+ <span>Auto-Arrange & Explain</span>
97
+ </button>
98
+ </div>
99
+ <div id="status-box" class="mt-4 p-3 bg-gray-100 rounded-lg text-sm text-gray-700 text-center">
100
+ Add a shape to get started.
101
+ </div>
102
+ </aside>
103
+
104
+ <!-- Canvas Area -->
105
+ <main class="flex-1 bg-gray-200 p-4">
106
+ <div id="canvas-container" class="w-full h-full bg-white rounded-lg shadow-inner overflow-hidden relative">
107
+ <svg id="canvas-svg" class="w-full h-full"></svg>
108
+ </div>
109
+ </main>
110
+ </div>
111
+
112
+ <!-- AI Modal -->
113
+ <div id="ai-modal" class="fixed inset-0 bg-gray-900 bg-opacity-60 flex items-center justify-center p-4 z-50 hidden">
114
+ <div id="ai-modal-content" class="bg-white rounded-xl shadow-2xl p-6 w-full max-w-2xl transform transition-all">
115
+ <!-- Loading State -->
116
+ <div id="ai-loading" class="text-center">
117
+ <div class="spinner mx-auto mb-4"></div>
118
+ <h3 class="text-lg font-semibold text-gray-800">AI is thinking...</h3>
119
+ <p class="text-gray-600">Analyzing your diagram to find the best layout.</p>
120
+ </div>
121
+ <!-- Result State -->
122
+ <div id="ai-result" class="hidden">
123
+ <h3 class="text-2xl font-bold text-gray-800 mb-4">✨ Diagram Explanation</h3>
124
+ <div id="ai-explanation" class="prose prose-sm max-w-none text-gray-700 bg-gray-50 p-4 rounded-lg"></div>
125
+ <button id="close-modal-btn" class="mt-6 w-full bg-indigo-600 hover:bg-indigo-700 text-white font-bold py-2 px-4 rounded-lg transition-colors duration-200">Close</button>
126
+ </div>
127
+ </div>
128
+ </div>
129
+
130
+
131
+ <script>
132
+ // --- DOM Element References ---
133
+ const canvasContainer = document.getElementById('canvas-container');
134
+ const svg = document.getElementById('canvas-svg');
135
+ const addRectBtn = document.getElementById('add-rect');
136
+ const addCircleBtn = document.getElementById('add-circle');
137
+ const addDiamondBtn = document.getElementById('add-diamond');
138
+ const connectBtn = document.getElementById('connect-btn');
139
+ const clearBtn = document.getElementById('clear-btn');
140
+ const statusBox = document.getElementById('status-box');
141
+ const aiMagicBtn = document.getElementById('ai-magic-btn');
142
+ const aiModal = document.getElementById('ai-modal');
143
+ const aiModalContent = document.getElementById('ai-modal-content');
144
+ const aiLoading = document.getElementById('ai-loading');
145
+ const aiResult = document.getElementById('ai-result');
146
+ const aiExplanation = document.getElementById('ai-explanation');
147
+ const closeModalBtn = document.getElementById('close-modal-btn');
148
+
149
+ // --- State Management ---
150
+ let shapes = [];
151
+ let connectors = [];
152
+ let isDragging = false;
153
+ let isConnecting = false;
154
+ let draggedShape = null;
155
+ let connectionStartShape = null;
156
+ let offset = { x: 0, y: 0 };
157
+
158
+ // --- Core Functions ---
159
+
160
+ function render() {
161
+ svg.innerHTML = '';
162
+ const connectorsGroup = document.createElementNS('http://www.w3.org/2000/svg', 'g');
163
+ connectors.forEach(conn => {
164
+ const fromShape = shapes.find(s => s.id === conn.from);
165
+ const toShape = shapes.find(s => s.id === conn.to);
166
+ if (fromShape && toShape) {
167
+ const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
168
+ line.setAttribute('x1', fromShape.x);
169
+ line.setAttribute('y1', fromShape.y);
170
+ line.setAttribute('x2', toShape.x);
171
+ line.setAttribute('y2', toShape.y);
172
+ line.setAttribute('class', 'connector-line stroke-gray-500');
173
+ connectorsGroup.appendChild(line);
174
+ }
175
+ });
176
+ svg.appendChild(connectorsGroup);
177
+
178
+ const shapesGroup = document.createElementNS('http://www.w3.org/2000/svg', 'g');
179
+ shapes.forEach(shape => {
180
+ const group = document.createElementNS('http://www.w3.org/2000/svg', 'g');
181
+ group.setAttribute('class', 'shape');
182
+ group.setAttribute('data-id', shape.id);
183
+ group.setAttribute('transform', `translate(${shape.x}, ${shape.y})`);
184
+
185
+ let element;
186
+ let textElement = document.createElementNS('http://www.w3.org/2000/svg', 'text');
187
+ textElement.setAttribute('class', 'shape-text fill-white');
188
+ textElement.setAttribute('text-anchor', 'middle');
189
+ textElement.setAttribute('dominant-baseline', 'central');
190
+
191
+ switch (shape.type) {
192
+ case 'rect':
193
+ element = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
194
+ element.setAttribute('x', -shape.width / 2);
195
+ element.setAttribute('y', -shape.height / 2);
196
+ element.setAttribute('width', shape.width);
197
+ element.setAttribute('height', shape.height);
198
+ element.setAttribute('class', 'fill-sky-500');
199
+ element.setAttribute('rx', 8);
200
+ textElement.textContent = 'Process';
201
+ break;
202
+ case 'circle':
203
+ element = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
204
+ element.setAttribute('r', shape.radius);
205
+ element.setAttribute('class', 'fill-emerald-500');
206
+ textElement.textContent = 'Start/End';
207
+ break;
208
+ case 'diamond':
209
+ element = document.createElementNS('http://www.w3.org/2000/svg', 'polygon');
210
+ const points = `0,${-shape.height / 2} ${shape.width / 2},0 0,${shape.height / 2} ${-shape.width / 2},0`;
211
+ element.setAttribute('points', points);
212
+ element.setAttribute('class', 'fill-indigo-500');
213
+ textElement.textContent = 'Decision';
214
+ break;
215
+ }
216
+
217
+ if (connectionStartShape && connectionStartShape.id === shape.id) {
218
+ element.classList.add('highlight-connector');
219
+ }
220
+
221
+ group.appendChild(element);
222
+ group.appendChild(textElement);
223
+ shapesGroup.appendChild(group);
224
+ });
225
+ svg.appendChild(shapesGroup);
226
+ }
227
+
228
+ function addShape(type) {
229
+ const canvasRect = canvasContainer.getBoundingClientRect();
230
+ const newShape = {
231
+ id: `shape-${crypto.randomUUID().substring(0, 8)}`,
232
+ type: type,
233
+ x: canvasRect.width / 2 + (Math.random() - 0.5) * 100,
234
+ y: canvasRect.height / 2 + (Math.random() - 0.5) * 100,
235
+ width: 120,
236
+ height: 60,
237
+ radius: 40,
238
+ };
239
+ shapes.push(newShape);
240
+ updateStatus('Shape added. Drag to position.');
241
+ render();
242
+ }
243
+
244
+ function getMousePosition(evt) {
245
+ const CTM = svg.getScreenCTM();
246
+ const clientX = evt.touches ? evt.touches[0].clientX : evt.clientX;
247
+ const clientY = evt.touches ? evt.touches[0].clientY : evt.clientY;
248
+ return {
249
+ x: (clientX - CTM.e) / CTM.a,
250
+ y: (clientY - CTM.f) / CTM.d
251
+ };
252
+ }
253
+
254
+ function updateStatus(message) {
255
+ statusBox.textContent = message;
256
+ }
257
+
258
+ // --- Event Handlers ---
259
+
260
+ function handlePointerDown(evt) {
261
+ evt.preventDefault();
262
+ const target = evt.target.closest('.shape');
263
+ if (!target) return;
264
+
265
+ const shapeId = target.getAttribute('data-id');
266
+ const shape = shapes.find(s => s.id === shapeId);
267
+
268
+ if (isConnecting) {
269
+ if (!connectionStartShape) {
270
+ connectionStartShape = shape;
271
+ updateStatus('Selected start shape. Click another to connect.');
272
+ } else {
273
+ if (connectionStartShape.id !== shape.id) {
274
+ connectors.push({ from: connectionStartShape.id, to: shape.id });
275
+ updateStatus('Shapes connected!');
276
+ } else {
277
+ updateStatus('Cannot connect a shape to itself.');
278
+ }
279
+ connectionStartShape = null;
280
+ isConnecting = false;
281
+ connectBtn.classList.remove('bg-amber-500');
282
+ connectBtn.classList.add('bg-gray-600');
283
+ }
284
+ } else {
285
+ isDragging = true;
286
+ draggedShape = shape;
287
+ const mousePos = getMousePosition(evt);
288
+ offset.x = mousePos.x - draggedShape.x;
289
+ offset.y = mousePos.y - draggedShape.y;
290
+ updateStatus('Dragging shape...');
291
+ }
292
+ render();
293
+ }
294
+
295
+ function handlePointerMove(evt) {
296
+ if (!isDragging || !draggedShape) return;
297
+ evt.preventDefault();
298
+ const mousePos = getMousePosition(evt);
299
+ draggedShape.x = mousePos.x - offset.x;
300
+ draggedShape.y = mousePos.y - offset.y;
301
+ render();
302
+ }
303
+
304
+ function handlePointerUp(evt) {
305
+ if (isDragging) {
306
+ updateStatus('Drag shape or add another.');
307
+ }
308
+ isDragging = false;
309
+ draggedShape = null;
310
+ }
311
+
312
+ // --- Gemini API Integration ---
313
+ async function callGeminiForLayout() {
314
+ if (shapes.length < 2) {
315
+ updateStatus("Add at least two shapes to use the AI feature.");
316
+ return;
317
+ }
318
+
319
+ // Show loading modal
320
+ aiModal.classList.remove('hidden');
321
+ aiLoading.classList.remove('hidden');
322
+ aiResult.classList.add('hidden');
323
+
324
+ const canvasRect = canvasContainer.getBoundingClientRect();
325
+
326
+ // 1. Serialize the diagram data
327
+ const diagramData = {
328
+ nodes: shapes.map(s => ({ id: s.id, type: s.type, label: s.type === 'rect' ? 'Process' : (s.type === 'circle' ? 'Start/End' : 'Decision') })),
329
+ edges: connectors.map(c => ({ from: c.from, to: c.to })),
330
+ canvas: { width: canvasRect.width, height: canvasRect.height }
331
+ };
332
+
333
+ // 2. Create the prompt for the Gemini API
334
+ const prompt = `
335
+ You are an expert diagramming assistant. Your task is to analyze a given diagram structure and provide an optimal layout and a clear explanation.
336
+
337
+ The diagram is represented by a JSON object with nodes and edges.
338
+ - "nodes" contains a list of shapes with their ID and type.
339
+ - "edges" defines the connections between nodes using their IDs.
340
+ - "canvas" provides the dimensions of the drawing area.
341
+
342
+ Based on the following diagram data, please return a JSON object that contains:
343
+ 1. A "layout" object where keys are the node IDs and values are objects with optimal 'x' and 'y' coordinates for a clean, top-to-bottom flowchart layout. The coordinates should be within the canvas dimensions.
344
+ 2. A "explanation" string that describes the process flow of the diagram in a clear, step-by-step manner.
345
+
346
+ Diagram Data:
347
+ ${JSON.stringify(diagramData, null, 2)}
348
+ `;
349
+
350
+ // 3. Define the expected JSON schema for the response
351
+ const schema = {
352
+ type: "OBJECT",
353
+ properties: {
354
+ "layout": {
355
+ type: "OBJECT",
356
+ "description": "An object where keys are node IDs and values are objects with x and y coordinates.",
357
+ },
358
+ "explanation": {
359
+ type: "STRING",
360
+ "description": "A step-by-step explanation of the diagram's flow."
361
+ }
362
+ },
363
+ required: ["layout", "explanation"]
364
+ };
365
+
366
+ try {
367
+ // 4. Call the Gemini API
368
+ let chatHistory = [{ role: "user", parts: [{ text: prompt }] }];
369
+ const payload = {
370
+ contents: chatHistory,
371
+ generationConfig: {
372
+ responseMimeType: "application/json",
373
+ responseSchema: schema,
374
+ }
375
+ };
376
+ const apiKey = ""; // Leave empty, will be handled by the environment
377
+ const apiUrl = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=${apiKey}`;
378
+
379
+ const response = await fetch(apiUrl, {
380
+ method: 'POST',
381
+ headers: { 'Content-Type': 'application/json' },
382
+ body: JSON.stringify(payload)
383
+ });
384
+
385
+ if (!response.ok) {
386
+ throw new Error(`API call failed with status: ${response.status}`);
387
+ }
388
+
389
+ const result = await response.json();
390
+
391
+ if (result.candidates && result.candidates.length > 0 &&
392
+ result.candidates[0].content && result.candidates[0].content.parts &&
393
+ result.candidates[0].content.parts.length > 0) {
394
+
395
+ const jsonText = result.candidates[0].content.parts[0].text;
396
+ const parsedJson = JSON.parse(jsonText);
397
+
398
+ // 5. Apply the AI suggestions
399
+ applyAILayout(parsedJson.layout);
400
+ displayAIExplanation(parsedJson.explanation);
401
+
402
+ } else {
403
+ throw new Error("Invalid response structure from API.");
404
+ }
405
+
406
+ } catch (error) {
407
+ console.error("Error calling Gemini API:", error);
408
+ updateStatus("Error: Could not get AI suggestions.");
409
+ aiModal.classList.add('hidden'); // Hide modal on error
410
+ }
411
+ }
412
+
413
+ function applyAILayout(layout) {
414
+ shapes.forEach(shape => {
415
+ if (layout[shape.id]) {
416
+ shape.x = layout[shape.id].x;
417
+ shape.y = layout[shape.id].y;
418
+ }
419
+ });
420
+ render();
421
+ }
422
+
423
+ function displayAIExplanation(explanation) {
424
+ aiExplanation.innerHTML = explanation.replace(/\n/g, '<br>'); // Simple formatting
425
+ aiLoading.classList.add('hidden');
426
+ aiResult.classList.remove('hidden');
427
+ }
428
+
429
+
430
+ // --- Event Listeners ---
431
+ addRectBtn.addEventListener('click', () => addShape('rect'));
432
+ addCircleBtn.addEventListener('click', () => addShape('circle'));
433
+ addDiamondBtn.addEventListener('click', () => addShape('diamond'));
434
+
435
+ connectBtn.addEventListener('click', () => {
436
+ isConnecting = !isConnecting;
437
+ if (isConnecting) {
438
+ connectionStartShape = null;
439
+ connectBtn.classList.remove('bg-gray-600');
440
+ connectBtn.classList.add('bg-amber-500');
441
+ updateStatus('CONNECT MODE: Click a shape to start.');
442
+ } else {
443
+ connectionStartShape = null;
444
+ connectBtn.classList.remove('bg-amber-500');
445
+ connectBtn.classList.add('bg-gray-600');
446
+ updateStatus('Connect mode disabled.');
447
+ }
448
+ render();
449
+ });
450
+
451
+ clearBtn.addEventListener('click', () => {
452
+ shapes = [];
453
+ connectors = [];
454
+ isConnecting = false;
455
+ connectionStartShape = null;
456
+ connectBtn.classList.remove('bg-amber-500');
457
+ connectBtn.classList.add('bg-gray-600');
458
+ updateStatus('Canvas cleared. Add a new shape.');
459
+ render();
460
+ });
461
+
462
+ aiMagicBtn.addEventListener('click', callGeminiForLayout);
463
+
464
+ closeModalBtn.addEventListener('click', () => {
465
+ aiModal.classList.add('hidden');
466
+ });
467
+
468
+ // Close modal if clicking outside the content
469
+ aiModal.addEventListener('click', (e) => {
470
+ if (e.target === aiModal) {
471
+ aiModal.classList.add('hidden');
472
+ }
473
+ });
474
+
475
+ svg.addEventListener('mousedown', handlePointerDown);
476
+ svg.addEventListener('mousemove', handlePointerMove);
477
+ svg.addEventListener('mouseup', handlePointerUp);
478
+ svg.addEventListener('mouseleave', handlePointerUp);
479
+
480
+ svg.addEventListener('touchstart', handlePointerDown, { passive: false });
481
+ svg.addEventListener('touchmove', handlePointerMove, { passive: false });
482
+ svg.addEventListener('touchend', handlePointerUp);
483
+ svg.addEventListener('touchcancel', handlePointerUp);
484
+
485
+ // Initial render
486
+ render();
487
+ </script>
488
+ </body>
489
  </html>