chansung commited on
Commit
41f1cb9
·
verified ·
1 Parent(s): c128c05

Create script.js

Browse files
Files changed (1) hide show
  1. script.js +1657 -0
script.js ADDED
@@ -0,0 +1,1657 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ console.log("Script execution started.");
2
+ // --- Global Variables ---
3
+ let generatedCode = [];
4
+ let currentCleanedCode = [];
5
+
6
+ let evolutionTimeline = [];
7
+ let activeTimelineIndex = -1;
8
+ let selectedVariationGridIndex = -1;
9
+ let currentFullscreenHistoryIndex = -1;
10
+
11
+ let originalUserPromptForCurrentGeneration = '';
12
+ let lastPreviewUpdateTime = [];
13
+ let previewUpdateInterval = 500;
14
+ let numVariationsToGenerate = 4;
15
+ let activeApiControllers = [];
16
+
17
+ let lastGenerationConfig = {
18
+ prompt: '',
19
+ isRefinement: false,
20
+ numVariations: 4,
21
+ refinedTimelineIndex: -1,
22
+ thinkingBudget: 0
23
+ };
24
+
25
+
26
+ // --- DOM Element References ---
27
+ let apiKeyEl, codeOutputEl, errorMessageEl;
28
+ let modelSelEl;
29
+ let selectButtons = [];
30
+ let fullscreenButtons = [];
31
+ let previewItems = [];
32
+
33
+ let refinementLoadingIndicator;
34
+ let mainContentEl, configButtonEl;
35
+ let intervalSliderEl, intervalValueDisplayEl;
36
+
37
+ let fullscreenOverlayEl, fullscreenIframeEl, exitFullscreenBtnEl;
38
+ let perspectiveViewportEl, previewGridWrapperEl;
39
+
40
+ let historyPanelEl, historyPanelPlaceholderEl;
41
+ let selectedCodeTitleH3El;
42
+ let mainContentTitleH1El, mainContentSubtitleH2El;
43
+ let fullscreenHistoryNavEl, historyNavPrevBtnEl, historyNavNextBtnEl;
44
+ let promptModalOverlayEl, promptModalContentEl, modalUserPromptEl, modalGenerateBtnEl, modalCancelBtnEl, modalLoadingIndicatorEl;
45
+ let modalRefinementCheckboxEl, numVariationsSliderEl, numVariationsValueDisplayEl;
46
+ let configModalOverlayEl, configModalContentEl, configModalCloseBtnEl, copyCodeButtonEl;
47
+ let historyToggleButtonEl, historyArrowDownEl, historyArrowUpEl;
48
+ let exportCodeButtonEl;
49
+ let newButtonEl;
50
+ let confirmModalOverlayEl, confirmModalMessageEl, confirmModalConfirmBtnEl, confirmModalCancelBtnEl;
51
+ let currentConfirmCallback = null;
52
+ let historyNavLeftBtnEl, historyNavRightBtnEl;
53
+ let modalThinkingBudgetSliderEl, modalThinkingBudgetValueDisplayEl;
54
+ let promptDisplayModalOverlayEl, promptDisplayModalContentEl, fullPromptTextEl, promptDisplayModalCloseBtnEl;
55
+ let showPromptModalButtonEl; // Added for the new button
56
+ let variationNavLeftBtnEl, variationNavRightBtnEl; // Buttons for variation scrolling
57
+
58
+ // --- Elements for Initial Setup CTA ---
59
+ let initialSetupCtaEl;
60
+ let initialApiKeyInputEl;
61
+ let examplePromptsContainerEl;
62
+
63
+
64
+ // --- Constants ---
65
+ const API_BASE_URL = 'https://generativelanguage.googleapis.com/v1beta/models/';
66
+
67
+
68
+ // --- Helper Function to Process Stream for One Variation ---
69
+ async function processStreamForVariation(apiKey, prompt, variationIndex, modelName, signal) {
70
+ console.log(`[Variation ${variationIndex + 1}] Starting generation with model ${modelName}...`);
71
+ const previewFrame = document.getElementById(`preview-frame-${variationIndex + 1}`);
72
+ const loader = document.getElementById(`preview-loader-${variationIndex + 1}`);
73
+ const selectBtn = selectButtons[variationIndex];
74
+ const fullscreenBtn = fullscreenButtons[variationIndex];
75
+
76
+ const currentThinkingBudget = parseInt(lastGenerationConfig.thinkingBudget, 10);
77
+
78
+ if (!previewFrame) { console.error(`[Variation ${variationIndex + 1}] Preview frame missing.`); return false; }
79
+ if (!loader) { console.warn(`[Variation ${variationIndex + 1}] Loader missing.`); }
80
+
81
+
82
+ if (loader) loader.classList.remove('hidden');
83
+ if (selectBtn) selectBtn.disabled = true;
84
+ if (fullscreenBtn) fullscreenBtn.disabled = true;
85
+
86
+ if (previewFrame) updateLivePreviewInGrid(variationIndex, '<div class="flex items-center justify-center h-full"><p class="text-slate-500">Generating...</p></div>', false);
87
+ if(lastPreviewUpdateTime[variationIndex] !== undefined) lastPreviewUpdateTime[variationIndex] = 0;
88
+
89
+
90
+ const API_ENDPOINT = `${API_BASE_URL}${modelName}:streamGenerateContent?key=${apiKey}&alt=sse`;
91
+ let rawGeneratedCode = '';
92
+ let htmlBlockStarted = false;
93
+ let accumulatedStreamData = '';
94
+ let success = false;
95
+
96
+ try {
97
+ const requestBody = {
98
+ contents: [{ parts: [{ text: prompt }] }]
99
+ };
100
+
101
+ if (currentThinkingBudget > 0) {
102
+ requestBody.generationConfig = {
103
+ thinkingConfig: {
104
+ thinkingBudget: currentThinkingBudget
105
+ }
106
+ };
107
+ console.log(`[Variation ${variationIndex + 1}] Thinking Mode ENABLED. Budget: ${currentThinkingBudget}`);
108
+ }
109
+
110
+ const response = await fetch(API_ENDPOINT, {
111
+ method: 'POST',
112
+ headers: { 'Content-Type': 'application/json' },
113
+ body: JSON.stringify(requestBody),
114
+ signal: signal
115
+ });
116
+
117
+ if (!response.ok) {
118
+ let errorDetails = `HTTP Error: ${response.status} ${response.statusText}`;
119
+ try { const errorData = await response.json(); errorDetails += ` - ${errorData?.error?.message || 'No msg.'}`; } catch (e) { /* Ignore */ }
120
+ throw new Error(errorDetails);
121
+ }
122
+
123
+ const reader = response.body.getReader();
124
+ const decoder = new TextDecoder();
125
+ let sseBuffer = "";
126
+
127
+ while (true) {
128
+ const { done, value } = await reader.read();
129
+ if (done) break;
130
+ sseBuffer += decoder.decode(value, { stream: true });
131
+ let eolIndex;
132
+ while ((eolIndex = sseBuffer.indexOf('\n')) >= 0) {
133
+ const line = sseBuffer.substring(0, eolIndex).trim();
134
+ sseBuffer = sseBuffer.substring(eolIndex + 1);
135
+
136
+ if (line.startsWith('data: ')) {
137
+ try {
138
+ const jsonData = JSON.parse(line.substring(5).trim());
139
+ if (jsonData.candidates?.[0]?.content?.parts?.[0]?.text) {
140
+ const textPart = jsonData.candidates[0].content.parts[0].text;
141
+
142
+ if (!htmlBlockStarted) {
143
+ accumulatedStreamData += textPart;
144
+ const marker = "```html";
145
+ const markerIndex = accumulatedStreamData.indexOf(marker);
146
+ if (markerIndex !== -1) {
147
+ htmlBlockStarted = true;
148
+ let codeStartIndex = markerIndex + marker.length;
149
+ if (accumulatedStreamData[codeStartIndex] === '\n') {
150
+ codeStartIndex++;
151
+ }
152
+ rawGeneratedCode = accumulatedStreamData.substring(codeStartIndex);
153
+ }
154
+ } else {
155
+ rawGeneratedCode += textPart;
156
+ }
157
+
158
+ if (htmlBlockStarted) {
159
+ const now = Date.now();
160
+ if (lastPreviewUpdateTime[variationIndex] !== undefined && (now - lastPreviewUpdateTime[variationIndex] >= previewUpdateInterval)) {
161
+ updateLivePreviewInGrid(variationIndex, rawGeneratedCode, true);
162
+ lastPreviewUpdateTime[variationIndex] = now;
163
+ }
164
+ }
165
+ }
166
+ } catch (e) { console.warn(`[Var ${variationIndex + 1}] JSON parse error:`, e, "Problematic Line:", line); }
167
+ }
168
+ }
169
+ }
170
+
171
+ console.log(`[Variation ${variationIndex + 1}] Streaming finished. HTML Block Started: ${htmlBlockStarted}. Raw content after marker: ${(rawGeneratedCode || "").substring(0, 200)}`);
172
+ let finalExtractedHtml = null;
173
+ let processingErrorMessage = null;
174
+
175
+ if (htmlBlockStarted) {
176
+ let tempHtml = rawGeneratedCode.trim();
177
+ if (tempHtml.endsWith("```")) {
178
+ tempHtml = tempHtml.substring(0, tempHtml.length - 3).trim();
179
+ } else if (tempHtml.endsWith("```html")) {
180
+ tempHtml = tempHtml.substring(0, tempHtml.length - 7).trim();
181
+ }
182
+
183
+ if (tempHtml.length > 0) {
184
+ finalExtractedHtml = tempHtml;
185
+ const minLength = 20;
186
+ const hasStructuralTag = /<!DOCTYPE html|<html[^>]*>|<head[^>]*>|<body[^>]*>/i.test(finalExtractedHtml);
187
+ const hasAnyTag = /<[a-zA-Z][^>]*>/.test(finalExtractedHtml);
188
+
189
+ if (finalExtractedHtml.length >= minLength && (hasStructuralTag || hasAnyTag)) {
190
+ success = true;
191
+ console.log(`[Variation ${variationIndex + 1}] HTML validation SUCCEEDED.`);
192
+ } else {
193
+ success = false;
194
+ console.warn("[Variation " + (variationIndex + 1) + "] HTML validation FAILED (length: " + finalExtractedHtml.length + ", structural: " + hasStructuralTag + ", anyTag: " + hasAnyTag + "). Review content manually.");
195
+ }
196
+ } else {
197
+ processingErrorMessage = "// Error: No meaningful content found after '```html' marker.";
198
+ console.warn(`[Variation ${variationIndex + 1}] ${processingErrorMessage}`);
199
+ success = false;
200
+ }
201
+ } else {
202
+ processingErrorMessage = `// Error: '\`\`\`html' code block marker not found. Received:\n${(accumulatedStreamData || rawGeneratedCode || "").substring(0, 200)}`;
203
+ console.warn(`[Variation ${variationIndex + 1}] ${processingErrorMessage}`);
204
+ success = false;
205
+ }
206
+
207
+ if (success && finalExtractedHtml) {
208
+ let processedHtml = finalExtractedHtml;
209
+ const requiredHeadContent = `
210
+ <meta charset="UTF-8">
211
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
212
+ <script src="https://cdn.tailwindcss.com"><\\/script>
213
+ <link rel="preconnect" href="https://rsms.me/">
214
+ <link rel="stylesheet" href="https://rsms.me/inter/inter.css">
215
+ <style>
216
+ html { font-family: 'Inter', sans-serif; }
217
+ @supports (font-variation-settings: normal) {
218
+ html { font-family: 'Inter var', sans-serif; }
219
+ }
220
+ body {
221
+ background-color: #f8fafc; /* slate-50 */
222
+ color: #0f172a; /* slate-900 */
223
+ padding: 1rem;
224
+ }
225
+ </style>`;
226
+
227
+ if (!processedHtml.includes('<head>')) {
228
+ processedHtml = `<head>${requiredHeadContent}</head><body>${processedHtml}</body>`;
229
+ } else {
230
+ let tempRequired = "";
231
+ if (!processedHtml.includes('cdn.tailwindcss.com')) tempRequired += ` <script src="https://cdn.tailwindcss.com"><\\/script>\\n`;
232
+ if (!processedHtml.includes('inter.css')) tempRequired += ` <link rel="preconnect" href="https://rsms.me/">\\n <link rel="stylesheet" href="https://rsms.me/inter/inter.css">\\n`;
233
+ if (!processedHtml.includes("html { font-family: 'Inter', sans-serif; }")) {
234
+ tempRequired += ` <style>\\n html { font-family: 'Inter', sans-serif; }\\n @supports (font-variation-settings: normal) { html { font-family: 'Inter var', sans-serif; } }\\n body { background-color: #f8fafc; color: #0f172a; padding: 1rem; }\\n </style>\\n`;
235
+ }
236
+ if (tempRequired) {
237
+ processedHtml = processedHtml.replace(/<\/head>/i, `${tempRequired}</head>`);
238
+ }
239
+ }
240
+ if(currentCleanedCode[variationIndex] !== undefined) currentCleanedCode[variationIndex] = processedHtml;
241
+
242
+ const interactionScript = `<script>(function() { const VARIATION_INDEX = ${variationIndex}; })(); <\\/script>`;
243
+ const bodyEndIndex = processedHtml.lastIndexOf('</body>');
244
+ if(generatedCode[variationIndex] !== undefined) {
245
+ generatedCode[variationIndex] = (bodyEndIndex !== -1)
246
+ ? processedHtml.slice(0, bodyEndIndex) + interactionScript + processedHtml.slice(bodyEndIndex)
247
+ : processedHtml + interactionScript;
248
+ }
249
+ updateLivePreviewInGrid(variationIndex, null, true);
250
+ } else {
251
+ generatedCode[variationIndex] = '';
252
+ if (finalExtractedHtml) {
253
+ if(currentCleanedCode[variationIndex] !== undefined) currentCleanedCode[variationIndex] = finalExtractedHtml;
254
+ updateLivePreviewInGrid(variationIndex, finalExtractedHtml, false);
255
+ } else {
256
+ if(currentCleanedCode[variationIndex] !== undefined) currentCleanedCode[variationIndex] = processingErrorMessage;
257
+ updateLivePreviewInGrid(variationIndex, `<div class="p-4 text-red-400 font-medium">${processingErrorMessage.replace(/\\n/g, '<br>')}</div>`, false);
258
+ }
259
+ }
260
+
261
+ } catch (error) {
262
+ if (error.name === 'AbortError') {
263
+ console.log(`[Variation ${variationIndex + 1}] Fetch aborted.`);
264
+ updateLivePreviewInGrid(variationIndex, `<div class="p-4 text-yellow-400 font-medium">Generation Cancelled.</div>`, false);
265
+ } else {
266
+ console.error(`[Variation ${variationIndex + 1}] Generation error:`, error);
267
+ updateLivePreviewInGrid(variationIndex, `<div class="p-4 text-red-400 font-medium">Error: ${error.message}</div>`, false);
268
+ }
269
+ if(currentCleanedCode[variationIndex] !== undefined) currentCleanedCode[variationIndex] = `// Error: ${error.message}`;
270
+ if(generatedCode[variationIndex] !== undefined) generatedCode[variationIndex] = '';
271
+ } finally {
272
+ const finalLoader = document.getElementById(`preview-loader-${variationIndex + 1}`);
273
+ if (finalLoader) finalLoader.classList.add('hidden');
274
+ if (selectBtn) selectBtn.disabled = !success; // Disable if not successful
275
+ if (fullscreenBtn) fullscreenBtn.disabled = !success; // Disable if not successful
276
+ }
277
+ return success;
278
+ }
279
+
280
+ // --- Main Function to Generate Variations ---
281
+ async function generateVariations() {
282
+ console.log("generateVariations called.");
283
+ const userPrompt = modalUserPromptEl.value.trim();
284
+
285
+ if (!apiKeyEl || !modalUserPromptEl || !modelSelEl || !previewGridWrapperEl || !modalGenerateBtnEl || !modalLoadingIndicatorEl || !errorMessageEl || !codeOutputEl) {
286
+ console.error("Cannot generate variations: One or more critical elements are missing.");
287
+ if (errorMessageEl) errorMessageEl.textContent = "Initialization error. Cannot generate.";
288
+ return;
289
+ }
290
+
291
+ // Read API key: Prefer config modal, then initial input, then localStorage
292
+ let apiKey = apiKeyEl.value.trim();
293
+ if (!apiKey && initialApiKeyInputEl) {
294
+ apiKey = initialApiKeyInputEl.value.trim();
295
+ }
296
+ if (!apiKey) {
297
+ apiKey = localStorage.getItem('geminiApiKey') || '';
298
+ }
299
+
300
+ // Update input fields with the resolved API key
301
+ if (apiKeyEl && apiKeyEl.value !== apiKey) apiKeyEl.value = apiKey;
302
+ if (initialApiKeyInputEl && initialApiKeyInputEl.value !== apiKey) initialApiKeyInputEl.value = apiKey;
303
+
304
+
305
+ const selectedModel = modelSelEl.value;
306
+ const currentIsRefinementMode = modalRefinementCheckboxEl.checked;
307
+ const currentNumVariations = parseInt(numVariationsSliderEl.value, 10);
308
+ const currentThinkingBudget = parseInt(modalThinkingBudgetSliderEl.value, 10);
309
+
310
+ if (!apiKey || !userPrompt) {
311
+ errorMessageEl.textContent = 'Error: API Key and Prompt (via Alt+P) are required.';
312
+ if (!apiKey && initialSetupCtaEl && initialSetupCtaEl.classList.contains('flex')) {
313
+ if(initialApiKeyInputEl) initialApiKeyInputEl.focus();
314
+ } else if (!userPrompt && !promptModalOverlayEl.classList.contains('visible')) {
315
+ showPromptModal();
316
+ }
317
+ return;
318
+ }
319
+ if (!selectedModel) {
320
+ errorMessageEl.textContent = 'Error: Please select a model.';
321
+ return;
322
+ }
323
+
324
+ lastGenerationConfig = {
325
+ prompt: userPrompt,
326
+ isRefinement: currentIsRefinementMode,
327
+ numVariations: currentNumVariations,
328
+ refinedTimelineIndex: currentIsRefinementMode ? activeTimelineIndex : -1,
329
+ thinkingBudget: currentThinkingBudget
330
+ };
331
+
332
+
333
+ errorMessageEl.textContent = '';
334
+ console.log(`Mode: ${currentIsRefinementMode ? 'Refinement' : 'Initial'}, Model: ${selectedModel}, Variations: ${currentNumVariations}`);
335
+
336
+ let baseCodeForRefinement = null;
337
+ let contextPromptForRefinement = '';
338
+ originalUserPromptForCurrentGeneration = userPrompt;
339
+
340
+ if (currentIsRefinementMode && activeTimelineIndex !== -1 && evolutionTimeline[activeTimelineIndex]) {
341
+ baseCodeForRefinement = evolutionTimeline[activeTimelineIndex].code;
342
+ contextPromptForRefinement = evolutionTimeline[activeTimelineIndex].originalUserPrompt;
343
+ console.log(`Refining Evolution ${activeTimelineIndex + 1}. Original context: "${contextPromptForRefinement}"`);
344
+ } else if (currentIsRefinementMode) {
345
+ errorMessageEl.textContent = 'Error: No active evolution selected to refine. Uncheck "refine" or select an evolution from history.';
346
+ // Also hide the initial CTA if it's somehow visible
347
+ if (initialSetupCtaEl) initialSetupCtaEl.classList.add('hidden');
348
+ if (initialSetupCtaEl) initialSetupCtaEl.classList.remove('flex');
349
+ if (previewGridWrapperEl) previewGridWrapperEl.classList.remove('hidden');
350
+ return;
351
+ }
352
+
353
+ modalGenerateBtnEl.disabled = true;
354
+ modalLoadingIndicatorEl.classList.remove('hidden');
355
+ if (codeOutputEl && selectedCodeTitleH3El) {
356
+ codeOutputEl.innerHTML = '<code class="language-html">// Select a variation to view its code.</code>';
357
+ selectedCodeTitleH3El.textContent = "Selected Code:";
358
+ }
359
+ selectedVariationGridIndex = -1;
360
+
361
+ numVariationsToGenerate = currentNumVariations;
362
+ generatedCode = Array(numVariationsToGenerate).fill('');
363
+ currentCleanedCode = Array(numVariationsToGenerate).fill('');
364
+ lastPreviewUpdateTime = Array(numVariationsToGenerate).fill(0);
365
+ selectButtons = Array(numVariationsToGenerate).fill(null);
366
+ fullscreenButtons = Array(numVariationsToGenerate).fill(null);
367
+ previewItems = Array(numVariationsToGenerate).fill(null);
368
+
369
+ // Ensure correct layout state for generation
370
+ if (initialSetupCtaEl) {
371
+ initialSetupCtaEl.classList.remove('active-cta'); // Start hide animation
372
+ // Hide example prompt buttons immediately or animate them out too
373
+ if (examplePromptsContainerEl) {
374
+ examplePromptsContainerEl.querySelectorAll('.example-prompt-button').forEach(btn => btn.classList.remove('visible'));
375
+ }
376
+ setTimeout(() => { // Wait for animation to roughly finish
377
+ initialSetupCtaEl.style.display = 'none';
378
+ initialSetupCtaEl.classList.remove('flex', 'flex-grow');
379
+ }, 500); // Corresponds to CSS transition duration
380
+ }
381
+ if (mainContentEl) mainContentEl.classList.remove('justify-center-cta-active'); // Remove centering class from main-content
382
+ if (perspectiveViewportEl) perspectiveViewportEl.classList.remove('hidden');
383
+ // previewGridWrapperEl will be managed by showFourGridPreviewUI
384
+
385
+ showFourGridPreviewUI();
386
+
387
+ activeApiControllers = Array(numVariationsToGenerate).fill(null).map(() => new AbortController());
388
+
389
+ const prompts = Array(numVariationsToGenerate).fill(null).map((_, i) => {
390
+ if (currentIsRefinementMode && baseCodeForRefinement) {
391
+ return `
392
+ You are an expert web developer specializing in HTML, Tailwind CSS, and JavaScript, with a focus on MOBILE-FIRST refinement.
393
+ Original User Request (for context): "${contextPromptForRefinement}"
394
+ Base HTML Code to Refine (assume it's being targeted for mobile):
395
+ \`\`\`html
396
+ ${baseCodeForRefinement}
397
+ \`\`\`
398
+ User's Refinement Instructions for MOBILE VIEW: "${userPrompt}"
399
+ Instructions:
400
+ 1. Analyze the Base Code and the User's Refinement Instructions, ensuring the result is optimized for a typical mobile viewport (e.g., 375px width).
401
+ 2. Modify ONLY the necessary parts of the Base Code to implement the refinement.
402
+ 3. This is Variation ${i + 1} of ${numVariationsToGenerate} attempting this mobile refinement. Try a slightly different approach if possible.
403
+ 4. Ensure the refined code remains a single, complete, runnable HTML document suitable for mobile.
404
+ 5. ALWAYS include Tailwind CSS CDN (<script src="https://cdn.tailwindcss.com"><\\/script>) and Inter font (<link rel="stylesheet" href="https://rsms.me/inter/inter.css">) in the <head>.
405
+ 6. Ensure basic mobile-friendly body styling: <style>html { font-family: 'Inter', sans-serif; } body { background-color: #f8fafc; color: #0f172a; padding: 0.5rem; }</style>
406
+ 7. Output ONLY the raw, complete, refined HTML code. Do NOT include explanations or markdown. Start your response with \`\`\`html.
407
+ Refined Mobile-Optimized Code:`;
408
+ } else {
409
+ return `
410
+ You are an expert web developer specializing in clean, modern HTML, CSS (using Tailwind CSS classes), and JavaScript, specifically for MOBILE-FIRST designs.
411
+ Generate the complete, runnable HTML code for the following request, optimized for a typical mobile viewport (e.g., 375px width).
412
+ Ensure the code is self-contained and responsive.
413
+ 1. ALWAYS include Tailwind CSS CDN (<script src="https://cdn.tailwindcss.com"><\\/script>) in the <head>.
414
+ 2. ALWAYS include Inter font (<link rel="stylesheet" href="https://rsms.me/inter/inter.css">) and set html { font-family: 'Inter', sans-serif; } in a <style> tag in the <head>.
415
+ 3. Add basic body styling for readability: body { background-color: #f8fafc; color: #0f172a; padding: 0.5rem; /* Adjusted padding for mobile */ } in the same <style> tag.
416
+ 4. Add HTML comments to explain the code.
417
+ 5. This is Variation ${i + 1} of ${numVariationsToGenerate}. Try to make it distinct while adhering to mobile-first principles.
418
+ 6. Output ONLY the raw HTML code. Do NOT include explanations or markdown. Start your response with \`\`\`html.
419
+ User Request:
420
+ "${originalUserPromptForCurrentGeneration}"
421
+ Mobile-Optimized Code:`;
422
+ }
423
+ });
424
+
425
+ const generationPromises = prompts.map((prompt, index) =>
426
+ processStreamForVariation(apiKey, prompt, index, selectedModel, activeApiControllers[index].signal)
427
+ );
428
+
429
+ try {
430
+ const results = await Promise.allSettled(generationPromises);
431
+ console.log("Generation promises settled:", results);
432
+ const successfulGenerations = results.filter(r => r.status === 'fulfilled' && r.value === true).length;
433
+ if (successfulGenerations === 0 && !results.some(r => r.status === 'rejected' && r.reason.name === 'AbortError')) {
434
+ errorMessageElement.textContent = 'Error: All variations failed.';
435
+ }
436
+ else if (successfulGenerations < numVariationsToGenerate && !results.every(r => r.status === 'rejected' && r.reason.name === 'AbortError')) {
437
+ errorMessageElement.textContent = `Warning: ${numVariationsToGenerate - successfulGenerations} var(s) failed or were cancelled.`;
438
+ }
439
+
440
+ updateMainContentTitles("Select a Variation", "Click 'Select' on a preview below.");
441
+
442
+ } catch (error) {
443
+ console.error("Parallel generation error:", error);
444
+ errorMessageElement.textContent = `Unexpected Error: ${error.message}`;
445
+ showInitialPreviewStateUI();
446
+ } finally {
447
+ modalGenerateBtnEl.disabled = false;
448
+ modalLoadingIndicatorEl.classList.add('hidden');
449
+ updateVariationNavButtons(); // Re-evaluate arrows after generation attempt ends
450
+ console.log("Generation process finished.");
451
+ }
452
+ }
453
+
454
+ // --- UI Update Functions ---
455
+ function createPreviewItemDOM(index, isGridItem = true) {
456
+ const item = document.createElement('div');
457
+ item.id = `preview-item-${index + 1}`;
458
+ item.className = isGridItem ? 'preview-item-perspective' : 'single-preview-item';
459
+ if (isGridItem) item.dataset.variationGridIndex = index;
460
+
461
+ const header = document.createElement('div'); header.className = 'preview-header';
462
+ const title = document.createElement('span'); title.className = 'preview-header-title';
463
+ title.textContent = isGridItem ? `Variation ${index + 1}` : (evolutionTimeline[activeTimelineIndex]?.prompt.substring(0,30) + '...' || `Evolution ${activeTimelineIndex + 1}`);
464
+ header.appendChild(title);
465
+
466
+ const btns = document.createElement('div'); btns.className = 'preview-header-buttons';
467
+
468
+ // Only add the iframe-specific fullscreen button if it's NOT a grid item (i.e., it's the single large preview)
469
+ // or if we decide to keep it for individual grid items based on a different condition.
470
+ // For now, removing it from grid items as per user request.
471
+ if (!isGridItem) {
472
+ const fsBtn = document.createElement('button');
473
+ fsBtn.className = 'fullscreen-btn p-1 focus:outline-none focus:ring-1 focus:ring-cyan-500 rounded disabled:opacity-50';
474
+ // fsBtn.dataset.idx = index; // This index might be ambiguous if not a grid item
475
+ fsBtn.title = 'Full Screen';
476
+ fsBtn.disabled = !(evolutionTimeline[activeTimelineIndex]?.code); // Disable if no code for current history
477
+ fsBtn.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3"/></svg>`;
478
+ fsBtn.addEventListener('click', (e) => {
479
+ // When it's the single large preview, it's always from history (activeTimelineIndex)
480
+ if (activeTimelineIndex !== -1 && evolutionTimeline[activeTimelineIndex]?.code) {
481
+ enterFullscreen(activeTimelineIndex, true);
482
+ }
483
+ });
484
+ btns.appendChild(fsBtn);
485
+ }
486
+
487
+ if (isGridItem) {
488
+ const selBtn = document.createElement('button');
489
+ selBtn.className = 'select-variation-btn futuristic-button px-3 py-1 text-xs';
490
+ selBtn.dataset.variationGridIndex = index; selBtn.disabled = true; selBtn.textContent = 'Select';
491
+ selBtn.addEventListener('click', handleSelectVariationFromGrid);
492
+ selectButtons[index] = selBtn;
493
+ btns.appendChild(selBtn);
494
+ }
495
+
496
+ header.appendChild(btns);
497
+ item.appendChild(header);
498
+
499
+ const bodyEl = document.createElement('div'); bodyEl.className = 'preview-body';
500
+ const loaderDiv = document.createElement('div'); loaderDiv.id = `preview-loader-${index + 1}`;
501
+ loaderDiv.className = 'preview-loader ' + (isGridItem ? 'hidden' : '');
502
+ loaderDiv.innerHTML = '<div class="spinner"></div>';
503
+ bodyEl.appendChild(loaderDiv);
504
+
505
+ const iframeEl = document.createElement('iframe');
506
+ iframeEl.id = isGridItem ? `preview-frame-${index + 1}` : 'single-large-preview-frame';
507
+ iframeEl.title = `Preview ${index + 1}`; iframeEl.className = 'preview-frame';
508
+ iframeEl.srcdoc = '<div class="flex items-center justify-center h-full"><p class="text-slate-400">Preparing...</p></div>';
509
+ bodyEl.appendChild(iframeEl);
510
+ item.appendChild(bodyEl);
511
+
512
+ if (isGridItem) {
513
+ previewItems[index] = item;
514
+ // fullscreenButtons[index] = fsBtn; // fsBtn is no longer created for grid items here
515
+ } else {
516
+ // If it's the single large preview, we might want to store its fullscreen button if needed elsewhere
517
+ // For now, this is not strictly necessary as it's created and added directly.
518
+ }
519
+ return item;
520
+ }
521
+
522
+
523
+ function showFourGridPreviewUI() {
524
+ previewGridWrapperEl.innerHTML = '';
525
+ previewGridWrapperEl.className = 'mobile-preview-layout'; // Use this class for the flex row
526
+ if (numVariationsToGenerate === 1) {
527
+ previewGridWrapperEl.style.justifyContent = 'center';
528
+ } else {
529
+ previewGridWrapperEl.style.justifyContent = 'flex-start';
530
+ }
531
+ if(perspectiveViewportEl) perspectiveViewportEl.style.perspective = 'none'; // Remove perspective
532
+
533
+ for (let i = 0; i < numVariationsToGenerate; i++) {
534
+ // createPreviewItemDOM's second arg 'isGridItem = true' will assign 'preview-item-perspective'
535
+ // which will be styled as a mobile phone.
536
+ const item = createPreviewItemDOM(i, true);
537
+ previewGridWrapperEl.appendChild(item);
538
+ }
539
+ updateSelectedGridItemUI(); // This should still work to highlight.
540
+ updateVariationNavButtons(); // Explicitly update nav buttons after rendering previews
541
+ }
542
+
543
+ function showSingleLargePreviewUI(htmlContent, titleText, fullPromptText) {
544
+ previewGridWrapperEl.innerHTML = '';
545
+ previewGridWrapperEl.className = 'single-mode';
546
+ if(perspectiveViewportEl) perspectiveViewportEl.style.perspective = 'none';
547
+
548
+ // Ensure correct layout state for single preview
549
+ if (initialSetupCtaEl) {
550
+ initialSetupCtaEl.classList.remove('active-cta');
551
+ if (examplePromptsContainerEl) {
552
+ examplePromptsContainerEl.querySelectorAll('.example-prompt-button').forEach(btn => btn.classList.remove('visible'));
553
+ }
554
+ setTimeout(() => {
555
+ initialSetupCtaEl.style.display = 'none';
556
+ initialSetupCtaEl.classList.remove('flex', 'flex-grow');
557
+ }, 500);
558
+ }
559
+ if (mainContentEl) mainContentEl.classList.remove('justify-center-cta-active'); // Remove centering class from main-content
560
+ if (perspectiveViewportEl) perspectiveViewportEl.classList.remove('hidden');
561
+ if (previewGridWrapperEl) previewGridWrapperEl.classList.remove('hidden');
562
+
563
+ const item = document.createElement('div');
564
+ item.className = 'single-preview-item';
565
+
566
+ const bodyEl = document.createElement('div');
567
+ bodyEl.className = 'preview-body';
568
+
569
+ const iframeEl = document.createElement('iframe');
570
+ iframeEl.id = `single-large-preview-frame`;
571
+ iframeEl.title = titleText;
572
+ iframeEl.className = 'preview-frame';
573
+ iframeEl.srcdoc = htmlContent;
574
+
575
+ bodyEl.appendChild(iframeEl);
576
+ item.appendChild(bodyEl);
577
+ previewGridWrapperEl.appendChild(item);
578
+
579
+ // Handle subtitle truncation and clickability
580
+ const maxPromptLength = 50;
581
+ let displaySubtitle = fullPromptText;
582
+ mainContentSubtitleH2El.classList.remove('prompt-truncated');
583
+ delete mainContentSubtitleH2El.dataset.fullPrompt;
584
+
585
+ if (fullPromptText && fullPromptText.length > maxPromptLength) {
586
+ displaySubtitle = fullPromptText.substring(0, maxPromptLength) + "... (click to view full)";
587
+ mainContentSubtitleH2El.classList.add('prompt-truncated');
588
+ mainContentSubtitleH2El.dataset.fullPrompt = fullPromptText; // Store full prompt
589
+ }
590
+
591
+ updateMainContentTitles(titleText, displaySubtitle); // Use potentially truncated text
592
+ updateVariationNavButtons(); // Ensure nav buttons are updated for single view
593
+ }
594
+
595
+ function showInitialPreviewStateUI() {
596
+ // Hide the main preview grid and show the initial CTA
597
+ if (previewGridWrapperEl) previewGridWrapperEl.classList.add('hidden');
598
+ if (perspectiveViewportEl) perspectiveViewportEl.classList.add('hidden'); // Hide the whole viewport
599
+ if (mainContentEl) mainContentEl.classList.add('justify-center-cta-active'); // Add centering class to main-content
600
+
601
+ if (initialSetupCtaEl) {
602
+ initialSetupCtaEl.style.display = 'flex'; // Set display before animation
603
+ initialSetupCtaEl.classList.add('flex-grow');
604
+ // Use requestAnimationFrame to ensure display:flex is applied before adding class for transition
605
+ requestAnimationFrame(() => {
606
+ initialSetupCtaEl.classList.add('active-cta');
607
+ });
608
+ }
609
+
610
+ updateMainContentTitles("Welcome to Live Previews", "Set up your API Key or try an example below.");
611
+ if (codeOutputEl) codeOutputEl.innerHTML = '<code class="language-html">// Select a variation or history item to view its code.</code>';
612
+ if (selectedCodeTitleH3El) selectedCodeTitleH3El.textContent = "Selected Code:";
613
+ selectedVariationGridIndex = -1;
614
+ if (mainContentSubtitleH2El) {
615
+ mainContentSubtitleH2El.classList.remove('prompt-truncated');
616
+ delete mainContentSubtitleH2El.dataset.fullPrompt;
617
+ }
618
+
619
+ // Populate example prompts
620
+ if (examplePromptsContainerEl) {
621
+ examplePromptsContainerEl.innerHTML = ''; // Clear existing
622
+ const prompts = [
623
+ "A mobile app login screen with social media login options.",
624
+ "A product detail page for a mobile e-commerce app, showing image, price, and add to cart button.",
625
+ "A user profile screen for a mobile app with an avatar, name, and a list of user settings.",
626
+ "A chat interface for a mobile messaging app with message bubbles and an input field."
627
+ ];
628
+ prompts.forEach((promptText, index) => {
629
+ const button = document.createElement('button');
630
+ button.className = 'example-prompt-button';
631
+ button.textContent = promptText;
632
+ button.addEventListener('click', () => {
633
+ if (modalUserPromptEl) modalUserPromptEl.value = promptText;
634
+ showPromptModal();
635
+ });
636
+ examplePromptsContainerEl.appendChild(button);
637
+ // Staggered animation for buttons
638
+ setTimeout(() => {
639
+ button.classList.add('visible');
640
+ }, 300 + index * 120); // Delay after panel animation starts, then stagger
641
+ });
642
+ }
643
+ }
644
+
645
+ function updateMainContentTitles(title, subtitle) {
646
+ if (mainContentTitleH1El) mainContentTitleH1El.textContent = title;
647
+ if (mainContentSubtitleH2El) mainContentSubtitleH2El.textContent = subtitle;
648
+ }
649
+
650
+ function updateSelectedGridItemUI() {
651
+ previewItems.forEach((item, index) => {
652
+ if (!item) return;
653
+ item.classList.toggle('selected', index === selectedVariationGridIndex);
654
+ });
655
+ selectButtons.forEach((button, index) => {
656
+ if (!button) return;
657
+ // Button enabled/disabled state is handled by procStream's success.
658
+ // const hasCode = currentCleanedCode[index]?.length > 0 && !currentCleanedCode[index].startsWith("// Error");
659
+ // button.disabled = !hasCode;
660
+ // if (fullscreenButtons[index]) { fullscreenButtons[index].disabled = !hasCode; }
661
+
662
+ button.classList.remove('selected-state');
663
+ if (index === selectedVariationGridIndex) {
664
+ button.textContent = 'Selected';
665
+ button.classList.add('selected-state');
666
+ } else {
667
+ button.textContent = 'Select';
668
+ }
669
+ });
670
+ }
671
+
672
+ function updateLivePreviewInGrid(index, codeToRender = null, applyZoom = false) {
673
+ let baseHtml = codeToRender !== null ? codeToRender : generatedCode[index];
674
+ const frame = document.getElementById(`preview-frame-${index + 1}`);
675
+ if (!frame) { console.warn(`[updateLivePreviewInGrid][Var ${index + 1}] Frame not found.`); return; }
676
+
677
+ if (typeof baseHtml !== 'string') {
678
+ baseHtml = '<div class="p-4 text-orange-500">Invalid content received</div>';
679
+ }
680
+ let finalHtml = baseHtml;
681
+ try {
682
+ // REMOVED the injected scaling style for mobile previews
683
+ // The generated mobile code should be responsive within the iframe.
684
+ frame.srcdoc = finalHtml;
685
+ } catch (e) {
686
+ console.error(`[Var ${index + 1}] Error setting srcdoc for grid:`, e);
687
+ try {
688
+ frame.srcdoc = `<div class="p-4 text-red-500 font-semibold">Preview Render Error</div>`;
689
+ } catch (finalError) { console.error("Failed to display error in grid iframe:", finalError); }
690
+ }
691
+ }
692
+
693
+ // --- Event Handlers ---
694
+ function handleSelectVariationFromGrid(event) {
695
+ const idx = parseInt(event.target.dataset.variationGridIndex, 10);
696
+ // Check if code for this variation is valid (not an error message)
697
+ if (isNaN(idx) || !currentCleanedCode[idx] || currentCleanedCode[idx].startsWith("// Error")) {
698
+ console.warn(`Cannot select variation ${idx + 1}, code is invalid or generation failed.`);
699
+ return;
700
+ }
701
+
702
+ activeApiControllers.forEach((controller, controllerIndex) => {
703
+ if (controllerIndex !== idx && controller) {
704
+ console.log(`Aborting request for variation ${controllerIndex + 1}`);
705
+ controller.abort();
706
+ }
707
+ });
708
+ activeApiControllers = [];
709
+
710
+
711
+ selectedVariationGridIndex = idx;
712
+ const displayCode = generatedCode[idx];
713
+ const storeCode = currentCleanedCode[idx];
714
+
715
+ let originalPromptForThisEvolution;
716
+ let parentTimelineIdx = null;
717
+
718
+ const wasThisGenerationARefinement = lastGenerationConfig.isRefinement;
719
+ const refinedIndexForThisGen = lastGenerationConfig.refinedTimelineIndex;
720
+
721
+ if (wasThisGenerationARefinement && refinedIndexForThisGen !== -1 && evolutionTimeline[refinedIndexForThisGen]) {
722
+ originalPromptForThisEvolution = evolutionTimeline[refinedIndexForThisGen].originalUserPrompt;
723
+ parentTimelineIdx = refinedIndexForThisGen;
724
+ } else {
725
+ originalPromptForThisEvolution = originalUserPromptForCurrentGeneration;
726
+ }
727
+
728
+ const newHistoryEntry = {
729
+ id: 'evo-' + Date.now() + '-' + Math.random().toString(36).substr(2, 5),
730
+ prompt: originalUserPromptForCurrentGeneration,
731
+ originalUserPrompt: originalPromptForThisEvolution,
732
+ code: storeCode,
733
+ timestamp: new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit'}),
734
+ parentIndex: parentTimelineIdx
735
+ };
736
+ evolutionTimeline.push(newHistoryEntry);
737
+ activeTimelineIndex = evolutionTimeline.length - 1;
738
+
739
+ console.log(`Variation ${idx + 1} selected from grid. Added to timeline as Evolution ${activeTimelineIndex + 1}.`);
740
+
741
+ renderHistoryPanel();
742
+ showSingleLargePreviewUI(displayCode, `Evolution ${activeTimelineIndex + 1}: Active`, `Prompt: "${newHistoryEntry.prompt}"`);
743
+
744
+ if (codeOutputEl) {
745
+ codeOutputEl.textContent = storeCode;
746
+ if (selectedCodeTitleH3El) selectedCodeTitleH3El.textContent = `Code (Evolution ${activeTimelineIndex + 1}):`;
747
+ codeOutputEl.classList.remove('text-slate-200');
748
+ codeOutputEl.classList.add('text-slate-400');
749
+ if (codeOutputEl?.parentElement) { codeOutputEl.parentElement.scrollTop = 0; }
750
+ }
751
+
752
+
753
+ updateSelectedGridItemUI();
754
+ updateHistoryNavigationButtons(); // Update nav buttons
755
+
756
+ if(modalUserPromptEl) modalUserPromptEl.value = '';
757
+ if(modalUserPromptEl) modalUserPromptEl.placeholder = `Refine Evolution ${activeTimelineIndex + 1}...`;
758
+ if(modalRefinementCheckboxEl) modalRefinementCheckboxEl.checked = true;
759
+ }
760
+
761
+ function handleHistoryItemClick(timelineIdxToView) {
762
+ if (timelineIdxToView < 0 || timelineIdxToView >= evolutionTimeline.length) {
763
+ console.warn("Invalid history index clicked:", timelineIdxToView);
764
+ return;
765
+ }
766
+ activeTimelineIndex = timelineIdxToView;
767
+ const historyEntry = evolutionTimeline[activeTimelineIndex];
768
+
769
+ console.log(`History item ${activeTimelineIndex + 1} selected.`);
770
+
771
+ const displayCodeForHistory = historyEntry.code;
772
+
773
+ showSingleLargePreviewUI(displayCodeForHistory, `Evolution ${activeTimelineIndex + 1}: Active (Historical)`, `Prompt: "${historyEntry.prompt}"`);
774
+
775
+ if (codeOutputEl) {
776
+ codeOutputEl.textContent = historyEntry.code;
777
+ if (selectedCodeTitleH3El) selectedCodeTitleH3El.textContent = `Code (Evolution ${activeTimelineIndex + 1} - Historical):`;
778
+ codeOutputEl.classList.remove('text-slate-200');
779
+ codeOutputEl.classList.add('text-slate-400');
780
+ if (codeOutputEl?.parentElement) { codeOutputEl.parentElement.scrollTop = 0; }
781
+ }
782
+
783
+ renderHistoryPanel();
784
+ updateHistoryNavigationButtons(); // Update nav buttons
785
+
786
+ if(modalUserPromptEl) modalUserPromptEl.value = '';
787
+ if(modalUserPromptEl) modalUserPromptEl.placeholder = `Refine Evolution ${activeTimelineIndex + 1}...`;
788
+ if(modalRefinementCheckboxEl) modalRefinementCheckboxEl.checked = true;
789
+ selectedVariationGridIndex = -1;
790
+ updateSelectedGridItemUI();
791
+ }
792
+
793
+
794
+ // --- Render History Panel ---
795
+ function createHistoryThumbnailDOM(entry, index) {
796
+ const thumbItem = document.createElement('div');
797
+ thumbItem.className = 'history-thumbnail-item group';
798
+ thumbItem.dataset.timelineIndex = index;
799
+ thumbItem.setAttribute('aria-label', `Evolution Step ${index + 1}: ${entry.prompt.substring(0, 30)}...`);
800
+
801
+ // Note: Active class and dynamic transforms/z-index are applied in renderHistoryPanel
802
+
803
+ const previewContainer = document.createElement('div');
804
+ previewContainer.className = 'history-thumbnail-preview-container';
805
+ previewContainer.title = `Click to view Evolution ${index + 1}`;
806
+ previewContainer.addEventListener('click', () => handleHistoryItemClick(index));
807
+
808
+ const iframe = document.createElement('iframe');
809
+ iframe.className = 'history-thumbnail-preview';
810
+ iframe.title = `Preview of Evolution ${index + 1}`;
811
+ const scaledContent = `
812
+ <style>
813
+ html { transform: scale(0.25); transform-origin: 0 0; width: 400%; height: 400%; overflow: hidden !important; background-color: #fff; }
814
+ body { width: 100%; height: 100%; overflow: hidden !important; padding: 0 !important; margin: 0 !important; }
815
+ </style>
816
+ ${entry.code}
817
+ `;
818
+ iframe.srcdoc = scaledContent;
819
+ previewContainer.appendChild(iframe);
820
+
821
+ const titleEl = document.createElement('div');
822
+ titleEl.className = 'history-thumbnail-title';
823
+ titleEl.textContent = `Evo ${index + 1}: ${entry.prompt.substring(0, 20)}${entry.prompt.length > 20 ? '...' : ''}`;
824
+ titleEl.title = `Prompt: ${entry.prompt}\\nClick to view Evolution ${index + 1}`;
825
+ titleEl.addEventListener('click', () => handleHistoryItemClick(index));
826
+
827
+ const fsBtn = document.createElement('button');
828
+ fsBtn.className = 'history-thumbnail-fullscreen-btn';
829
+ fsBtn.title = 'View Fullscreen';
830
+ fsBtn.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3"/></svg>`;
831
+ fsBtn.addEventListener('click', (e) => {
832
+ e.stopPropagation();
833
+ enterFullscreen(index, true);
834
+ });
835
+
836
+ thumbItem.appendChild(previewContainer);
837
+ thumbItem.appendChild(titleEl);
838
+ thumbItem.appendChild(fsBtn);
839
+ return thumbItem;
840
+ }
841
+
842
+ function renderHistoryPanel() {
843
+ if (!historyPanelEl || !historyPanelPlaceholderEl) return;
844
+ historyPanelEl.innerHTML = ''; // Clear previous items
845
+
846
+ if (evolutionTimeline.length === 0) {
847
+ historyPanelEl.appendChild(historyPanelPlaceholderEl);
848
+ historyPanelPlaceholderEl.classList.remove('hidden');
849
+ return;
850
+ }
851
+
852
+ historyPanelPlaceholderEl.classList.add('hidden');
853
+
854
+ const totalItems = evolutionTimeline.length;
855
+ const middleIndex = Math.floor(totalItems / 2);
856
+ // const maxRotation = 15; // Max rotation in degrees for edge items (rotation is now 0)
857
+ const yOffsetFactor = 4; // How much non-active items lift towards the edges
858
+ const zOffsetFactor = -15; // How much non-active items move back towards the edges
859
+ const baseZIndex = 10; // Base z-index for non-active items
860
+
861
+ // Define specific offsets for the active item to make it stand out
862
+ const activeYOffset = -15; // Moves active item further up
863
+ const activeZOffset = 25; // Moves active item further forward
864
+
865
+ evolutionTimeline.forEach((entry, index) => {
866
+ const thumbItem = createHistoryThumbnailDOM(entry, index);
867
+ let finalTransform;
868
+ let finalZIndex;
869
+
870
+ if (index === activeTimelineIndex) {
871
+ thumbItem.classList.add('active-history-item');
872
+ // Transform for the active item: more prominent
873
+ finalTransform = `translateY(${activeYOffset}px) translateZ(${activeZOffset}px) rotate(0deg)`;
874
+ // The .active-history-item class in CSS sets z-index: 50.
875
+ // We can set it here too for consistency or rely on CSS.
876
+ finalZIndex = 50;
877
+ } else {
878
+ // Calculate transformations relative to the middle for non-active items
879
+ const deltaFromMiddle = index - middleIndex;
880
+ const rotation = 0; // Rotation is kept at 0
881
+ const translateY = Math.abs(deltaFromMiddle) * yOffsetFactor;
882
+ const translateZ = Math.abs(deltaFromMiddle) * zOffsetFactor;
883
+
884
+ finalTransform = `translateY(${translateY}px) translateZ(${translateZ}px) rotate(${rotation}deg)`;
885
+ finalZIndex = baseZIndex - Math.abs(deltaFromMiddle);
886
+ }
887
+
888
+ thumbItem.style.zIndex = `${finalZIndex}`;
889
+ thumbItem.style.transform = finalTransform;
890
+
891
+ historyPanelEl.appendChild(thumbItem);
892
+ });
893
+ const activeThumb = historyPanelEl.querySelector('.active-history-item');
894
+ if (activeThumb) {
895
+ // Keep scrollIntoView, maybe adjust behavior if needed
896
+ activeThumb.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'center' });
897
+ }
898
+ }
899
+
900
+ // --- Full Screen Logic ---
901
+ function enterFullscreen(index, isFromHistory = false) {
902
+ if (!fullscreenIframeEl || !fullscreenOverlayEl || !document.body || !fullscreenHistoryNavEl || !historyNavPrevBtnEl || !historyNavNextBtnEl) {
903
+ console.error("Cannot enter fullscreen: Overlay or nav elements missing.");
904
+ return;
905
+ }
906
+
907
+ let codeToDisplay;
908
+ currentFullscreenHistoryIndex = -1;
909
+ fullscreenHistoryNavEl.classList.remove('visible');
910
+
911
+ if (isFromHistory) {
912
+ if (index < 0 || index >= evolutionTimeline.length || !evolutionTimeline[index]) {
913
+ console.warn("Cannot enter fullscreen for history item", index); return;
914
+ }
915
+ codeToDisplay = evolutionTimeline[index].code;
916
+ currentFullscreenHistoryIndex = index;
917
+
918
+ fullscreenHistoryNavEl.classList.add('visible');
919
+ historyNavPrevBtnEl.disabled = (currentFullscreenHistoryIndex <= 0);
920
+ historyNavNextBtnEl.disabled = (currentFullscreenHistoryIndex >= evolutionTimeline.length - 1);
921
+
922
+ } else {
923
+ if (index < 0 || index >= numVariationsToGenerate || !generatedCode[index]) {
924
+ console.warn("Cannot enter fullscreen for variation grid item", index); return;
925
+ }
926
+ codeToDisplay = generatedCode[index];
927
+ }
928
+
929
+ fullscreenIframeEl.srcdoc = codeToDisplay;
930
+ fullscreenOverlayEl.classList.add('visible');
931
+ document.body.classList.add('fullscreen-active');
932
+ document.documentElement.style.overflow = 'hidden';
933
+ }
934
+ function exitFullscreen() {
935
+ if (!fullscreenOverlayEl || !document.body || !fullscreenIframeEl || !fullscreenHistoryNavEl) {
936
+ console.error("Cannot exit fullscreen: Overlay or nav elements missing.");
937
+ return;
938
+ }
939
+ fullscreenOverlayEl.classList.remove('visible');
940
+ document.body.classList.remove('fullscreen-active');
941
+ document.documentElement.style.overflow = '';
942
+ currentFullscreenHistoryIndex = -1;
943
+ fullscreenHistoryNavEl.classList.remove('visible');
944
+ setTimeout(() => { if (fullscreenIframeEl) fullscreenIframeEl.srcdoc = 'about:blank'; }, 300);
945
+ }
946
+
947
+ function showPreviousHistoryInFullscreen() {
948
+ if (currentFullscreenHistoryIndex > 0) {
949
+ currentFullscreenHistoryIndex--;
950
+ activeTimelineIndex = currentFullscreenHistoryIndex;
951
+ enterFullscreen(activeTimelineIndex, true);
952
+ renderHistoryPanel();
953
+ const historyEntry = evolutionTimeline[activeTimelineIndex];
954
+ if (historyEntry && codeOutputEl) {
955
+ codeOutputEl.textContent = historyEntry.code;
956
+ if(selectedCodeTitleH3El) selectedCodeTitleH3El.textContent = `Code (Evolution ${activeTimelineIndex + 1} - Historical):`;
957
+ }
958
+ }
959
+ }
960
+
961
+ function showNextHistoryInFullscreen() {
962
+ if (currentFullscreenHistoryIndex < evolutionTimeline.length - 1) {
963
+ currentFullscreenHistoryIndex++;
964
+ activeTimelineIndex = currentFullscreenHistoryIndex;
965
+ enterFullscreen(activeTimelineIndex, true);
966
+ renderHistoryPanel();
967
+ const historyEntry = evolutionTimeline[activeTimelineIndex];
968
+ if (historyEntry && codeOutputEl) {
969
+ codeOutputEl.textContent = historyEntry.code;
970
+ if(selectedCodeTitleH3El) selectedCodeTitleH3El.textContent = `Code (Evolution ${activeTimelineIndex + 1} - Historical):`;
971
+ }
972
+ }
973
+ }
974
+
975
+ // --- Prompt Modal Logic ---
976
+ function showPromptModal() {
977
+ if (!promptModalOverlayEl || !modalUserPromptEl || !modalRefinementCheckboxEl || !numVariationsSliderEl || !modalThinkingBudgetSliderEl || !modalThinkingBudgetValueDisplayEl) return;
978
+
979
+ modalRefinementCheckboxEl.checked = (activeTimelineIndex !== -1);
980
+ numVariationsSliderEl.value = lastGenerationConfig.numVariations;
981
+ if(numVariationsValueDisplayEl) numVariationsValueDisplayEl.textContent = numVariationsSliderEl.value;
982
+
983
+ modalThinkingBudgetSliderEl.value = lastGenerationConfig.thinkingBudget;
984
+ if(modalThinkingBudgetValueDisplayEl) modalThinkingBudgetValueDisplayEl.textContent = lastGenerationConfig.thinkingBudget;
985
+
986
+ // Animation handling
987
+ promptModalOverlayEl.classList.remove('modal-anim-fade-out');
988
+ promptModalOverlayEl.style.display = 'flex'; // Or 'block' if that was its original display type
989
+ promptModalOverlayEl.classList.add('modal-anim-fade-in');
990
+ // promptModalOverlayEl.classList.add('visible'); // Keep this if it controls other things besides opacity/visibility
991
+
992
+ modalUserPromptEl.focus();
993
+ }
994
+
995
+ function hidePromptModal() {
996
+ if (!promptModalOverlayEl) return;
997
+
998
+ promptModalOverlayEl.classList.remove('modal-anim-fade-in');
999
+ promptModalOverlayEl.classList.add('modal-anim-fade-out');
1000
+
1001
+ // Wait for animation to finish before hiding
1002
+ const handleAnimationEnd = () => {
1003
+ promptModalOverlayEl.style.display = 'none';
1004
+ // promptModalOverlayEl.classList.remove('visible'); // Keep this if it controls other things
1005
+ promptModalOverlayEl.removeEventListener('animationend', handleAnimationEnd);
1006
+ };
1007
+ promptModalOverlayEl.addEventListener('animationend', handleAnimationEnd);
1008
+ }
1009
+
1010
+ function handleModalGenerate() {
1011
+ if (!modalUserPromptEl || !modalGenerateBtnEl) return;
1012
+ const modalPrompt = modalUserPromptEl.value.trim();
1013
+ if (modalPrompt) {
1014
+ hidePromptModal();
1015
+ if (!modalGenerateBtnEl.disabled) {
1016
+ generateVariations();
1017
+ }
1018
+ } else {
1019
+ console.warn("Modal prompt is empty. Not generating.");
1020
+ }
1021
+ }
1022
+
1023
+ // --- Config Modal Logic ---
1024
+ function showConfigModal() {
1025
+ if (!configModalOverlayEl) return;
1026
+
1027
+ // Animation handling
1028
+ configModalOverlayEl.classList.remove('modal-anim-fade-out');
1029
+ configModalOverlayEl.style.display = 'flex'; // Or 'block'
1030
+ configModalOverlayEl.classList.add('modal-anim-fade-in');
1031
+ // configModalOverlayEl.classList.add('visible');
1032
+ }
1033
+ function hideConfigModal() {
1034
+ if (!configModalOverlayEl) return;
1035
+
1036
+ configModalOverlayEl.classList.remove('modal-anim-fade-in');
1037
+ configModalOverlayEl.classList.add('modal-anim-fade-out');
1038
+
1039
+ const handleAnimationEnd = () => {
1040
+ configModalOverlayEl.style.display = 'none';
1041
+ // configModalOverlayEl.classList.remove('visible');
1042
+ configModalOverlayEl.removeEventListener('animationend', handleAnimationEnd);
1043
+ };
1044
+ configModalOverlayEl.addEventListener('animationend', handleAnimationEnd);
1045
+ }
1046
+
1047
+ // --- Confirmation Modal Logic ---
1048
+ function showConfirmModal(message, onConfirmCallback) {
1049
+ if (!confirmModalOverlayEl || !confirmModalMessageEl || !confirmModalConfirmBtnEl || !confirmModalCancelBtnEl) {
1050
+ console.error("Confirmation modal elements not found!");
1051
+ // Fallback to default confirm if modal elements are missing
1052
+ if (confirm(message)) {
1053
+ onConfirmCallback();
1054
+ }
1055
+ return;
1056
+ }
1057
+
1058
+ confirmModalMessageEl.textContent = message;
1059
+ currentConfirmCallback = onConfirmCallback; // Store the callback
1060
+
1061
+ // Clear previous listeners (important!)
1062
+ confirmModalConfirmBtnEl.replaceWith(confirmModalConfirmBtnEl.cloneNode(true));
1063
+ confirmModalCancelBtnEl.replaceWith(confirmModalCancelBtnEl.cloneNode(true));
1064
+ // Re-find buttons after cloning
1065
+ confirmModalConfirmBtnEl = document.getElementById('confirm-modal-confirm-button');
1066
+ confirmModalCancelBtnEl = document.getElementById('confirm-modal-cancel-button');
1067
+
1068
+ // Add new listeners
1069
+ confirmModalConfirmBtnEl.addEventListener('click', handleConfirm);
1070
+ confirmModalCancelBtnEl.addEventListener('click', hideConfirmModal);
1071
+
1072
+ // Show modal with animation
1073
+ confirmModalOverlayEl.classList.remove('modal-anim-fade-out');
1074
+ confirmModalOverlayEl.style.display = 'flex';
1075
+ confirmModalOverlayEl.classList.add('modal-anim-fade-in');
1076
+ }
1077
+
1078
+ function hideConfirmModal() {
1079
+ if (!confirmModalOverlayEl) return;
1080
+
1081
+ confirmModalOverlayEl.classList.remove('modal-anim-fade-in');
1082
+ confirmModalOverlayEl.classList.add('modal-anim-fade-out');
1083
+
1084
+ const handleAnimationEnd = () => {
1085
+ confirmModalOverlayEl.style.display = 'none';
1086
+ currentConfirmCallback = null; // Clear callback when hidden
1087
+ confirmModalOverlayEl.removeEventListener('animationend', handleAnimationEnd);
1088
+ };
1089
+ confirmModalOverlayEl.addEventListener('animationend', handleAnimationEnd);
1090
+ }
1091
+
1092
+ function handleConfirm() {
1093
+ hideConfirmModal();
1094
+ if (typeof currentConfirmCallback === 'function') {
1095
+ currentConfirmCallback(); // Execute the stored callback
1096
+ }
1097
+ currentConfirmCallback = null; // Clear callback after execution
1098
+ }
1099
+
1100
+ // --- History Navigation Logic ---
1101
+ function navigateToPreviousHistory() {
1102
+ if (activeTimelineIndex > 0) {
1103
+ handleHistoryItemClick(activeTimelineIndex - 1);
1104
+ }
1105
+ }
1106
+
1107
+ function navigateToNextHistory() {
1108
+ if (activeTimelineIndex < evolutionTimeline.length - 1) {
1109
+ handleHistoryItemClick(activeTimelineIndex + 1);
1110
+ }
1111
+ }
1112
+
1113
+ function updateHistoryNavigationButtons() {
1114
+ if (!historyNavLeftBtnEl || !historyNavRightBtnEl) return;
1115
+ historyNavLeftBtnEl.disabled = activeTimelineIndex <= 0;
1116
+ historyNavRightBtnEl.disabled = activeTimelineIndex >= evolutionTimeline.length - 1;
1117
+ }
1118
+
1119
+ // --- Variation Scroll Navigation Logic ---
1120
+ function updateVariationNavButtons() {
1121
+ if (!perspectiveViewportEl || !variationNavLeftBtnEl || !variationNavRightBtnEl) return;
1122
+
1123
+ const كان_الزر_الأيسر_مخفيًا_سابقًا = variationNavLeftBtnEl.classList.contains('hidden');
1124
+ const canScroll = perspectiveViewportEl.scrollWidth > perspectiveViewportEl.clientWidth;
1125
+
1126
+ if (canScroll) {
1127
+ perspectiveViewportEl.style.justifyContent = 'flex-start'; // Align to start when scrollable
1128
+ variationNavLeftBtnEl.classList.remove('hidden');
1129
+ variationNavRightBtnEl.classList.remove('hidden');
1130
+
1131
+ // If the arrows just appeared (i.e., content became scrollable), reset scroll to the beginning.
1132
+ if (كان_الزر_الأيسر_مخفيًا_سابقًا) {
1133
+ perspectiveViewportEl.scrollLeft = 0;
1134
+ }
1135
+
1136
+ // Update disabled state based on current (potentially reset) scrollLeft
1137
+ variationNavLeftBtnEl.disabled = perspectiveViewportEl.scrollLeft <= 0;
1138
+ variationNavRightBtnEl.disabled = perspectiveViewportEl.scrollLeft >= (perspectiveViewportEl.scrollWidth - perspectiveViewportEl.clientWidth - 1); // -1 for potential subpixel issues
1139
+ } else {
1140
+ perspectiveViewportEl.style.justifyContent = 'center'; // Center when not scrollable
1141
+ variationNavLeftBtnEl.classList.add('hidden');
1142
+ variationNavRightBtnEl.classList.add('hidden');
1143
+ }
1144
+ }
1145
+
1146
+ function scrollVariations(direction) {
1147
+ if (!perspectiveViewportEl || !previewItems || previewItems.length === 0) return;
1148
+
1149
+ let scrollAmount;
1150
+ const firstPreviewItem = perspectiveViewportEl.querySelector('.preview-item-perspective');
1151
+
1152
+ if (firstPreviewItem) {
1153
+ const itemStyle = window.getComputedStyle(firstPreviewItem);
1154
+ const itemWidth = parseFloat(itemStyle.width) || 0;
1155
+ // Ensure previewGridWrapperEl is used for gap calculation if it exists, otherwise fallback.
1156
+ const previewGridWrapper = document.getElementById('preview-grid-wrapper');
1157
+ const gapStyle = previewGridWrapper ? window.getComputedStyle(previewGridWrapper).gap : '24px'; // Default gap if wrapper not found for some reason
1158
+ const gap = parseFloat(gapStyle) || 24;
1159
+
1160
+ if (itemWidth > 0) {
1161
+ scrollAmount = itemWidth + gap;
1162
+ } else {
1163
+ // If itemWidth is 0 (e.g., item not found or has no width), use a larger default scroll.
1164
+ scrollAmount = perspectiveViewportEl.clientWidth / 2 || 300; // Scroll by half viewport or 300px
1165
+ }
1166
+ } else {
1167
+ // Fallback if no preview items are found at all for calculation.
1168
+ scrollAmount = perspectiveViewportEl.clientWidth / 2 || 300;
1169
+ }
1170
+
1171
+ // Ensure scrollAmount is always positive and reasonable
1172
+ if (scrollAmount <= 24) { // If calculated amount is still too small (e.g. only gap)
1173
+ scrollAmount = Math.max(perspectiveViewportEl.clientWidth / 3, 200); // Min 200px or 1/3 of viewport
1174
+ }
1175
+
1176
+ const currentScrollLeft = perspectiveViewportEl.scrollLeft;
1177
+ const newScrollLeft = direction === 'left'
1178
+ ? currentScrollLeft - scrollAmount
1179
+ : currentScrollLeft + scrollAmount;
1180
+
1181
+ perspectiveViewportEl.scrollTo({
1182
+ left: newScrollLeft,
1183
+ behavior: 'smooth'
1184
+ });
1185
+ }
1186
+
1187
+ // --- Helper function to call Gemini for code splitting ---
1188
+ async function fetchCodeSplitFromGemini(apiKey, fullHtmlContent) {
1189
+ const exportModelName = "gemini-2.5-flash-preview-04-17"; // Hardcoded model for export
1190
+ const API_ENDPOINT = `${API_BASE_URL}${exportModelName}:generateContent?key=${apiKey}`;
1191
+ const prompt = `
1192
+ You are an expert web developer. You are given a single HTML document that might contain inline CSS within <style> tags and inline JavaScript within <script> tags. Your task is to separate this document into three distinct components: HTML structure, CSS styles, and JavaScript code.
1193
+
1194
+ Follow these instructions carefully:
1195
+ 1. **HTML Output**: This should be the main HTML structure.
1196
+ * If there were inline <style> tags, remove them. Add a <link rel="stylesheet" href="style.css"> in the <head> instead.
1197
+ * If there were inline <script> tags (especially those not setting up initial variables or configurations that need to be in the head), try to move their content to what will become an external script.js file. Add <script src="script.js" defer><\\/script> before the closing </body> tag. For simple, short scripts that are clearly for page setup and are in the head, they can sometimes remain, but prefer externalizing functional code.
1198
+ * Ensure the HTML output is clean and well-formed.
1199
+ 2. **CSS Output**: This should contain ALL CSS rules extracted from any <style>...</style> blocks in the original HTML. If no <style> blocks were present, this should be an empty string or a comment indicating no CSS.
1200
+ 3. **JavaScript Output**: This should contain ALL JavaScript code extracted from any <script>...</script> blocks (that are not JSON-LD or other non-executable script types). If no functional <script> blocks were present, this should be an empty string or a comment indicating no JavaScript.
1201
+
1202
+ Provide the output STRICTLY as a JSON object with the following keys: "html_code", "css_code", "js_code".
1203
+
1204
+ Example of desired JSON output format:
1205
+ {
1206
+ "html_code": "<!DOCTYPE html>...<link rel=\\\"stylesheet\\\" href=\\\"style.css\\\"><script src=\\\"script.js\\\" defer><\\/script></body></html>",
1207
+ "css_code": "body { font-family: sans-serif; } ...",
1208
+ "js_code": "console.log(\\\'Hello World!\\\'); ...\"
1209
+ }
1210
+
1211
+ Original HTML content:
1212
+ \`\`\`html
1213
+ ${fullHtmlContent}\n\`\`\`
1214
+
1215
+ Return ONLY the JSON object. Do not include any other explanatory text or markdown formatting outside the JSON structure itself.
1216
+ `;
1217
+
1218
+ try {
1219
+ const response = await fetch(API_ENDPOINT, {
1220
+ method: 'POST',
1221
+ headers: { 'Content-Type': 'application/json' },
1222
+ body: JSON.stringify({ contents: [{ parts: [{ text: prompt }] }] })
1223
+ });
1224
+
1225
+ if (!response.ok) {
1226
+ const errorData = await response.json().catch(() => null);
1227
+ const errorMsg = errorData?.error?.message || `HTTP Error: ${response.status}`;
1228
+ console.error('Gemini API Error:', errorMsg);
1229
+ throw new Error(`API Error: ${errorMsg}`);
1230
+ }
1231
+
1232
+ const responseData = await response.json();
1233
+ const candidate = responseData.candidates?.[0];
1234
+ if (candidate?.content?.parts?.[0]?.text) {
1235
+ let rawText = candidate.content.parts[0].text;
1236
+ console.log("Raw response from Gemini for splitting:", rawText); // Log the raw response
1237
+
1238
+ let jsonString = null;
1239
+
1240
+ // Attempt 1: Look for JSON within markdown code blocks
1241
+ const markdownJsonMatch = rawText.match(/```json\n(\{[\s\S]*\})\n```/s) || rawText.match(/```\n(\{[\s\S]*\})\n```/s);
1242
+ if (markdownJsonMatch && markdownJsonMatch[1]) {
1243
+ jsonString = markdownJsonMatch[1];
1244
+ } else {
1245
+ // Attempt 2: Fallback to existing regex if no markdown block found or it's malformed
1246
+ const directJsonMatch = rawText.match(/\{.*\}/s);
1247
+ if (directJsonMatch) {
1248
+ jsonString = directJsonMatch[0];
1249
+ }
1250
+ }
1251
+
1252
+ if (jsonString) {
1253
+ try {
1254
+ return JSON.parse(jsonString);
1255
+ } catch (parseError) {
1256
+ console.error("Failed to parse JSON string from model:", jsonString, parseError);
1257
+ throw new Error("JSON parsing failed after attempting to clean model response."); // Simplified error
1258
+ }
1259
+ }
1260
+ throw new Error("Clean JSON object not found in model's response after attempting to extract.");
1261
+ }
1262
+ throw new Error("No valid content found in model's response.");
1263
+
1264
+ } catch (error) {
1265
+ console.error('Error fetching or parsing split code:', error);
1266
+ throw error; // Re-throw to be caught by caller
1267
+ }
1268
+ }
1269
+
1270
+ // --- Full Prompt Display Modal Logic ---
1271
+ function showFullPromptModal(fullPrompt) {
1272
+ if (!promptDisplayModalOverlayEl || !fullPromptTextEl) return;
1273
+
1274
+ fullPromptTextEl.textContent = fullPrompt;
1275
+
1276
+ // Animation handling
1277
+ promptDisplayModalOverlayEl.classList.remove('modal-anim-fade-out');
1278
+ promptDisplayModalOverlayEl.style.display = 'flex';
1279
+ promptDisplayModalOverlayEl.classList.add('modal-anim-fade-in');
1280
+ }
1281
+
1282
+ function hideFullPromptModal() {
1283
+ if (!promptDisplayModalOverlayEl) return;
1284
+
1285
+ promptDisplayModalOverlayEl.classList.remove('modal-anim-fade-in');
1286
+ promptDisplayModalOverlayEl.classList.add('modal-anim-fade-out');
1287
+
1288
+ const handleAnimationEnd = () => {
1289
+ promptDisplayModalOverlayEl.style.display = 'none';
1290
+ promptDisplayModalOverlayEl.removeEventListener('animationend', handleAnimationEnd);
1291
+ };
1292
+ promptDisplayModalOverlayEl.addEventListener('animationend', handleAnimationEnd);
1293
+ }
1294
+
1295
+ // --- Initialization ---
1296
+ document.addEventListener('DOMContentLoaded', () => {
1297
+ console.log("DOMContentLoaded event fired.");
1298
+
1299
+ apiKeyEl = document.getElementById('api-key');
1300
+ modelSelEl = document.getElementById('model-select');
1301
+ const codeOutputPre = document.getElementById('code-output');
1302
+ if (codeOutputPre) { codeOutputEl = codeOutputPre.querySelector('code'); } else { console.error("Code output <pre> not found");}
1303
+ errorMessageEl = document.getElementById('error-message');
1304
+ refinementLoadingIndicator = document.getElementById('refinement-loading-indicator');
1305
+ mainContentEl = document.getElementById('main-content');
1306
+ configButtonEl = document.getElementById('config-button');
1307
+ intervalSliderEl = document.getElementById('preview-interval-slider');
1308
+ intervalValueDisplayEl = document.getElementById('interval-value');
1309
+ fullscreenOverlayEl = document.getElementById('fullscreen-overlay');
1310
+ fullscreenIframeEl = document.getElementById('fullscreen-iframe');
1311
+ exitFullscreenBtnEl = document.getElementById('exit-fullscreen-btn');
1312
+ perspectiveViewportEl = document.getElementById('perspective-viewport');
1313
+ previewGridWrapperEl = document.getElementById('preview-grid-wrapper');
1314
+ historyPanelEl = document.getElementById('history-panel');
1315
+ historyPanelPlaceholderEl = document.getElementById('history-panel-placeholder');
1316
+ selectedCodeTitleH3El = document.getElementById('selected-code-title');
1317
+ mainContentTitleH1El = document.getElementById('main-content-title');
1318
+ mainContentSubtitleH2El = document.getElementById('main-content-subtitle');
1319
+ fullscreenHistoryNavEl = document.getElementById('fullscreen-history-nav');
1320
+ historyNavPrevBtnEl = document.getElementById('history-nav-prev');
1321
+ historyNavNextBtnEl = document.getElementById('history-nav-next');
1322
+ promptModalOverlayEl = document.getElementById('prompt-modal-overlay');
1323
+ promptModalContentEl = document.getElementById('prompt-modal-content');
1324
+ modalUserPromptEl = document.getElementById('modal-user-prompt');
1325
+ modalGenerateBtnEl = document.getElementById('modal-generate-button');
1326
+ modalCancelBtnEl = document.getElementById('modal-cancel-button');
1327
+ modalLoadingIndicatorEl = document.getElementById('modal-loading-indicator');
1328
+ modalRefinementCheckboxEl = document.getElementById('modal-refinement-checkbox');
1329
+ numVariationsSliderEl = document.getElementById('num-variations-slider');
1330
+ numVariationsValueDisplayEl = document.getElementById('num-variations-value');
1331
+ configModalOverlayEl = document.getElementById('config-modal-overlay');
1332
+ configModalContentEl = document.getElementById('config-modal-content');
1333
+ configModalCloseBtnEl = document.getElementById('config-modal-close-button');
1334
+ copyCodeButtonEl = document.getElementById('copy-code-button');
1335
+ exportCodeButtonEl = document.getElementById('export-code-button');
1336
+ historyToggleButtonEl = document.getElementById('history-toggle-button');
1337
+ historyArrowDownEl = document.getElementById('history-arrow-down');
1338
+ historyArrowUpEl = document.getElementById('history-arrow-up');
1339
+ newButtonEl = document.getElementById('new-button');
1340
+ confirmModalOverlayEl = document.getElementById('confirm-modal-overlay');
1341
+ confirmModalMessageEl = document.getElementById('confirm-modal-message');
1342
+ confirmModalConfirmBtnEl = document.getElementById('confirm-modal-confirm-button');
1343
+ confirmModalCancelBtnEl = document.getElementById('confirm-modal-cancel-button');
1344
+ historyNavLeftBtnEl = document.getElementById('history-nav-left-button');
1345
+ historyNavRightBtnEl = document.getElementById('history-nav-right-button');
1346
+ modalThinkingBudgetSliderEl = document.getElementById('modal-thinking-budget-slider');
1347
+ modalThinkingBudgetValueDisplayEl = document.getElementById('modal-thinking-budget-value');
1348
+ promptDisplayModalOverlayEl = document.getElementById('prompt-display-modal-overlay');
1349
+ promptDisplayModalContentEl = document.getElementById('prompt-display-modal-content');
1350
+ fullPromptTextEl = document.getElementById('full-prompt-text');
1351
+ promptDisplayModalCloseBtnEl = document.getElementById('prompt-display-modal-close-button');
1352
+ showPromptModalButtonEl = document.getElementById('show-prompt-modal-button'); // Added
1353
+ variationNavLeftBtnEl = document.getElementById('variation-nav-left');
1354
+ variationNavRightBtnEl = document.getElementById('variation-nav-right');
1355
+
1356
+ // --- Elements for Initial Setup CTA ---
1357
+ initialSetupCtaEl = document.getElementById('initial-setup-cta');
1358
+ initialApiKeyInputEl = document.getElementById('initial-api-key-input');
1359
+ examplePromptsContainerEl = document.getElementById('example-prompts-container');
1360
+
1361
+
1362
+ // --- Check if all required elements exist ---
1363
+ let missingElements = [];
1364
+ const requiredElements = { apiKeyEl, modelSelEl, codeOutputEl, errorMessageEl, refinementLoadingIndicator, mainContentEl, configButtonEl, intervalSliderEl, intervalValueDisplayEl, fullscreenOverlayEl, fullscreenIframeEl, exitFullscreenBtnEl, perspectiveViewportEl, previewGridWrapperEl, historyPanelEl, historyPanelPlaceholderEl, selectedCodeTitleH3El, mainContentTitleH1El, mainContentSubtitleH2El, fullscreenHistoryNavEl, historyNavPrevBtnEl, historyNavNextBtnEl, promptModalOverlayEl, promptModalContentEl, modalUserPromptEl, modalGenerateBtnEl, modalCancelBtnEl, modalLoadingIndicatorEl, modalRefinementCheckboxEl, numVariationsSliderEl, numVariationsValueDisplayEl, configModalOverlayEl, configModalContentEl, configModalCloseBtnEl, copyCodeButtonEl, exportCodeButtonEl, historyToggleButtonEl, historyArrowDownEl, historyArrowUpEl, newButtonEl, confirmModalOverlayEl, confirmModalMessageEl, confirmModalConfirmBtnEl, confirmModalCancelBtnEl, historyNavLeftBtnEl, historyNavRightBtnEl, modalThinkingBudgetSliderEl, modalThinkingBudgetValueDisplayEl, promptDisplayModalOverlayEl, promptDisplayModalContentEl, fullPromptTextEl, promptDisplayModalCloseBtnEl, showPromptModalButtonEl, initialSetupCtaEl, initialApiKeyInputEl, examplePromptsContainerEl, variationNavLeftBtnEl, variationNavRightBtnEl }; // Added showPromptModalButtonEl
1365
+ for (const key in requiredElements) { if (!requiredElements[key]) { missingElements.push(key); } }
1366
+
1367
+ if (missingElements.length > 0) {
1368
+ console.error("Initialization Error: Critical elements missing!", missingElements);
1369
+ if (document.body) { document.body.innerHTML = `<div class="fixed inset-0 bg-red-900 text-red-200 p-8 flex flex-col items-center justify-center text-center"><h2 class="text-2xl font-bold mb-4">Application Initialization Error</h2><p class="mb-2">Could not find element(s): ${missingElements.join(', ')}.</p><p>Please ensure the HTML structure is correct.</p></div>`; }
1370
+ else { alert(`Initialization Error: Critical elements missing: ${missingElements.join(', ')}.`); }
1371
+ return;
1372
+ }
1373
+
1374
+ // --- Initial UI Setup ---
1375
+ showInitialPreviewStateUI();
1376
+ renderHistoryPanel();
1377
+ updateHistoryNavigationButtons(); // Initial button state
1378
+ updateVariationNavButtons(); // Initial state for variation nav buttons
1379
+
1380
+ // --- Event Listeners ---
1381
+ if (newButtonEl) {
1382
+ newButtonEl.addEventListener('click', () => {
1383
+ showConfirmModal(
1384
+ 'Start a new session? This will clear the current state.',
1385
+ () => { location.reload(); } // Pass the reload logic as the callback
1386
+ );
1387
+ });
1388
+ }
1389
+
1390
+ if (configButtonEl) configButtonEl.addEventListener('click', showConfigModal);
1391
+ if (showPromptModalButtonEl) showPromptModalButtonEl.addEventListener('click', showPromptModal); // Added listener for new button
1392
+ if (configModalCloseBtnEl) configModalCloseBtnEl.addEventListener('click', hideConfigModal);
1393
+ if (configModalOverlayEl) configModalOverlayEl.addEventListener('click', (e) => {
1394
+ if (e.target === configModalOverlayEl) { hideConfigModal(); }
1395
+ });
1396
+
1397
+ if (exitFullscreenBtnEl) exitFullscreenBtnEl.addEventListener('click', exitFullscreen);
1398
+ if (historyNavPrevBtnEl) historyNavPrevBtnEl.addEventListener('click', showPreviousHistoryInFullscreen);
1399
+ if (historyNavNextBtnEl) historyNavNextBtnEl.addEventListener('click', showNextHistoryInFullscreen);
1400
+
1401
+ if (modalGenerateBtnEl) modalGenerateBtnEl.addEventListener('click', handleModalGenerate);
1402
+ if (modalCancelBtnEl) modalCancelBtnEl.addEventListener('click', hidePromptModal);
1403
+ if (promptModalOverlayEl) promptModalOverlayEl.addEventListener('click', (e) => {
1404
+ if (e.target === promptModalOverlayEl) { hidePromptModal(); }
1405
+ });
1406
+ if (confirmModalOverlayEl) { // Add overlay click listener for confirm modal
1407
+ confirmModalOverlayEl.addEventListener('click', (e) => {
1408
+ if (e.target === confirmModalOverlayEl) { hideConfirmModal(); }
1409
+ });
1410
+ }
1411
+ if (promptDisplayModalOverlayEl) { // Listener for full prompt display modal overlay click
1412
+ promptDisplayModalOverlayEl.addEventListener('click', (e) => {
1413
+ if (e.target === promptDisplayModalOverlayEl) { hideFullPromptModal(); }
1414
+ });
1415
+ }
1416
+ if (promptDisplayModalCloseBtnEl) { // Listener for full prompt display modal close button
1417
+ promptDisplayModalCloseBtnEl.addEventListener('click', hideFullPromptModal);
1418
+ }
1419
+ if (mainContentSubtitleH2El) { // Listener for clicking the subtitle
1420
+ mainContentSubtitleH2El.addEventListener('click', (e) => {
1421
+ const fullPrompt = e.target.dataset.fullPrompt;
1422
+ if (fullPrompt) { // Check if the data attribute exists (meaning it was truncated)
1423
+ showFullPromptModal(fullPrompt);
1424
+ }
1425
+ });
1426
+ }
1427
+ if (modalUserPromptEl) modalUserPromptEl.addEventListener('keydown', (event) => {
1428
+ if (event.key === 'Enter' && (event.ctrlKey || event.metaKey)) {
1429
+ event.preventDefault(); handleModalGenerate();
1430
+ }
1431
+ });
1432
+ if (numVariationsSliderEl) {
1433
+ numVariationsSliderEl.addEventListener('input', (event) => {
1434
+ if(numVariationsValueDisplayEl) numVariationsValueDisplayEl.textContent = event.target.value;
1435
+ });
1436
+ if(numVariationsValueDisplayEl) numVariationsValueDisplayEl.textContent = numVariationsSliderEl.value;
1437
+ }
1438
+
1439
+ if (modalThinkingBudgetSliderEl) {
1440
+ modalThinkingBudgetSliderEl.addEventListener('input', (event) => {
1441
+ if(modalThinkingBudgetValueDisplayEl) modalThinkingBudgetValueDisplayEl.textContent = event.target.value;
1442
+ });
1443
+ if(modalThinkingBudgetValueDisplayEl) modalThinkingBudgetValueDisplayEl.textContent = modalThinkingBudgetSliderEl.value;
1444
+ }
1445
+
1446
+ if (intervalSliderEl) {
1447
+ intervalSliderEl.addEventListener('input', (event) => {
1448
+ previewUpdateInterval = parseInt(event.target.value, 10);
1449
+ if(intervalValueDisplayEl) intervalValueDisplayEl.textContent = previewUpdateInterval;
1450
+ });
1451
+ previewUpdateInterval = parseInt(intervalSliderEl.value, 10);
1452
+ if(intervalValueDisplayEl) intervalValueDisplayEl.textContent = previewUpdateInterval;
1453
+ }
1454
+
1455
+ if (copyCodeButtonEl) {
1456
+ copyCodeButtonEl.addEventListener('click', () => {
1457
+ if (codeOutputEl && codeOutputEl.textContent && codeOutputEl.textContent !== '// Select a variation or history item to view its code.') {
1458
+ const textToCopy = codeOutputEl.textContent;
1459
+ const textArea = document.createElement("textarea");
1460
+ textArea.value = textToCopy;
1461
+ textArea.style.position = "fixed"; // Prevent scrolling to bottom
1462
+ document.body.appendChild(textArea);
1463
+ textArea.focus();
1464
+ textArea.select();
1465
+ try {
1466
+ const successful = document.execCommand('copy');
1467
+ if (successful) {
1468
+ const originalText = copyCodeButtonEl.innerHTML;
1469
+ copyCodeButtonEl.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-check inline-block mr-1"><polyline points="20 6 9 17 4 12"></polyline></svg> Copied!`;
1470
+ copyCodeButtonEl.classList.add('copied');
1471
+ setTimeout(() => {
1472
+ copyCodeButtonEl.innerHTML = originalText;
1473
+ copyCodeButtonEl.classList.remove('copied');
1474
+ }, 2000);
1475
+ } else {
1476
+ console.error('Fallback: Failed to copy code using execCommand.');
1477
+ }
1478
+ } catch (err) {
1479
+ console.error('Fallback: Error copying code using execCommand: ', err);
1480
+ }
1481
+ document.body.removeChild(textArea);
1482
+ }
1483
+ });
1484
+ }
1485
+
1486
+ if (exportCodeButtonEl) {
1487
+ exportCodeButtonEl.addEventListener('click', async () => {
1488
+ const currentCode = codeOutputEl?.textContent;
1489
+ const apiKey = apiKeyEl?.value;
1490
+
1491
+ if (!currentCode || currentCode.startsWith('//') || currentCode.trim() === '') {
1492
+ alert('No code selected or available to export.');
1493
+ return;
1494
+ }
1495
+ if (!apiKey) {
1496
+ alert('API Key is missing. Please configure it in settings.');
1497
+ showConfigModal(); // Show config modal if API key is missing
1498
+ return;
1499
+ }
1500
+ if (typeof JSZip === 'undefined') {
1501
+ alert('JSZip library is not loaded. Export cannot proceed.');
1502
+ console.error("JSZip is not defined!");
1503
+ return;
1504
+ }
1505
+
1506
+ const originalButtonContent = exportCodeButtonEl.innerHTML;
1507
+ exportCodeButtonEl.disabled = true;
1508
+ exportCodeButtonEl.innerHTML = '<svg class="animate-spin h-4 w-4" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg> Exporting...';
1509
+
1510
+ try {
1511
+ const splitCode = await fetchCodeSplitFromGemini(apiKey, currentCode);
1512
+
1513
+ if (!splitCode || typeof splitCode.html_code === 'undefined' || typeof splitCode.css_code === 'undefined' || typeof splitCode.js_code === 'undefined') {
1514
+ throw new Error('Invalid response structure from code splitting model.');
1515
+ }
1516
+
1517
+ const zip = new JSZip();
1518
+ zip.file("index.html", splitCode.html_code);
1519
+ zip.file("style.css", splitCode.css_code);
1520
+ zip.file("script.js", splitCode.js_code);
1521
+
1522
+ const zipBlob = await zip.generateAsync({ type: "blob" });
1523
+
1524
+ const downloadLink = document.createElement('a');
1525
+ downloadLink.href = URL.createObjectURL(zipBlob);
1526
+ downloadLink.download = "exported_code.zip";
1527
+ document.body.appendChild(downloadLink);
1528
+ downloadLink.click();
1529
+ document.body.removeChild(downloadLink);
1530
+ URL.revokeObjectURL(downloadLink.href);
1531
+
1532
+ exportCodeButtonEl.innerHTML = originalButtonContent;
1533
+
1534
+ } catch (error) {
1535
+ console.error("Export failed:", error);
1536
+ alert(`Export failed: ${error.message}`);
1537
+ exportCodeButtonEl.innerHTML = originalButtonContent;
1538
+ } finally {
1539
+ exportCodeButtonEl.disabled = false;
1540
+ }
1541
+ });
1542
+ }
1543
+
1544
+ if (historyToggleButtonEl && historyPanelEl && historyArrowDownEl && historyArrowUpEl) {
1545
+ historyToggleButtonEl.addEventListener('click', () => {
1546
+ const isCollapsed = historyPanelEl.classList.toggle('history-collapsed');
1547
+ const rootStyles = getComputedStyle(document.documentElement);
1548
+ const expandedHeight = rootStyles.getPropertyValue('--history-panel-expanded-height').trim();
1549
+ const collapsedHeight = rootStyles.getPropertyValue('--history-panel-collapsed-height').trim();
1550
+
1551
+ if (isCollapsed) {
1552
+ document.documentElement.style.setProperty('--history-panel-current-height', collapsedHeight);
1553
+ historyArrowDownEl.classList.add('hidden');
1554
+ historyArrowUpEl.classList.remove('hidden');
1555
+ } else {
1556
+ document.documentElement.style.setProperty('--history-panel-current-height', expandedHeight);
1557
+ historyArrowDownEl.classList.remove('hidden');
1558
+ historyArrowUpEl.classList.add('hidden');
1559
+ // Ensure content is visible if re-expanding and it was set to display:none directly
1560
+ // This is now handled by the .history-collapsed CSS rule for children.
1561
+ renderHistoryPanel(); // Re-render to ensure items are correctly displayed if they were hidden
1562
+ }
1563
+ });
1564
+ }
1565
+
1566
+ if (historyNavLeftBtnEl) {
1567
+ historyNavLeftBtnEl.addEventListener('click', navigateToPreviousHistory);
1568
+ }
1569
+ if (historyNavRightBtnEl) {
1570
+ historyNavRightBtnEl.addEventListener('click', navigateToNextHistory);
1571
+ }
1572
+
1573
+ // Variation navigation listeners
1574
+ if (variationNavLeftBtnEl) {
1575
+ variationNavLeftBtnEl.addEventListener('click', () => scrollVariations('left'));
1576
+ }
1577
+ if (variationNavRightBtnEl) {
1578
+ variationNavRightBtnEl.addEventListener('click', () => scrollVariations('right'));
1579
+ }
1580
+ if (perspectiveViewportEl) {
1581
+ perspectiveViewportEl.addEventListener('scroll', updateVariationNavButtons);
1582
+ }
1583
+ window.addEventListener('resize', updateVariationNavButtons);
1584
+
1585
+ // Keyboard Shortcuts Listener
1586
+ document.addEventListener('keydown', (event) => {
1587
+ if (event.key === 'Escape') {
1588
+ // Check style.display instead of .visible class for animated modals
1589
+ if (configModalOverlayEl.style.display !== 'none' && !configModalOverlayEl.classList.contains('modal-anim-fade-out')) {
1590
+ hideConfigModal();
1591
+ } else if (promptModalOverlayEl.style.display !== 'none' && !promptModalOverlayEl.classList.contains('modal-anim-fade-out')) {
1592
+ hidePromptModal();
1593
+ } else if (confirmModalOverlayEl.style.display !== 'none' && !confirmModalOverlayEl.classList.contains('modal-anim-fade-out')) { // Add Escape handler for confirm modal
1594
+ hideConfirmModal();
1595
+ } else if (promptDisplayModalOverlayEl.style.display !== 'none' && !promptDisplayModalOverlayEl.classList.contains('modal-anim-fade-out')) { // Add Escape handler for prompt display modal
1596
+ hideFullPromptModal();
1597
+ } else if (document.body.classList.contains('fullscreen-active')) {
1598
+ exitFullscreen();
1599
+ }
1600
+ }
1601
+ const targetTagName = event.target ? event.target.tagName.toLowerCase() : null;
1602
+ const isTypingInInputOrTextarea = targetTagName === 'input' || targetTagName === 'textarea';
1603
+
1604
+ if (document.body.classList.contains('fullscreen-active') && currentFullscreenHistoryIndex !== -1 && !isTypingInInputOrTextarea) {
1605
+ if (event.key.toLowerCase() === 'w') {
1606
+ event.preventDefault();
1607
+ showPreviousHistoryInFullscreen();
1608
+ } else if (event.key.toLowerCase() === 'd') {
1609
+ event.preventDefault();
1610
+ showNextHistoryInFullscreen();
1611
+ }
1612
+ }
1613
+ if (event.altKey && !isTypingInInputOrTextarea) {
1614
+ if (event.key.toLowerCase() === 'p' || event.code === 'KeyP') {
1615
+ event.preventDefault();
1616
+ if (!promptModalOverlayEl.classList.contains('visible') && !configModalOverlayEl.classList.contains('visible')) {
1617
+ showPromptModal();
1618
+ }
1619
+ }
1620
+ else if (event.key.toLowerCase() === 'j' || event.code === 'KeyJ') {
1621
+ event.preventDefault();
1622
+ if (lastGenerationConfig.prompt) {
1623
+ console.log("Alt+R: Regenerating with last settings:", lastGenerationConfig);
1624
+ modalUserPromptEl.value = lastGenerationConfig.prompt;
1625
+ modalRefinementCheckboxEl.checked = lastGenerationConfig.isRefinement;
1626
+ numVariationsSliderEl.value = lastGenerationConfig.numVariations;
1627
+ if(numVariationsValueDisplayEl) numVariationsValueDisplayEl.textContent = numVariationsSliderEl.value;
1628
+ modalThinkingBudgetSliderEl.value = lastGenerationConfig.thinkingBudget;
1629
+ if(modalThinkingBudgetValueDisplayEl) modalThinkingBudgetValueDisplayEl.textContent = lastGenerationConfig.thinkingBudget;
1630
+
1631
+ if (lastGenerationConfig.isRefinement) {
1632
+ activeTimelineIndex = lastGenerationConfig.refinedTimelineIndex;
1633
+ }
1634
+ handleModalGenerate();
1635
+ } else {
1636
+ console.log("Alt+R: No last generation settings found. Opening prompt modal.");
1637
+ showPromptModal();
1638
+ }
1639
+ }
1640
+ else if (event.key.toLowerCase() === 'o' || event.code === 'KeyO') {
1641
+ event.preventDefault();
1642
+ if (!configModalOverlayEl.classList.contains('visible') && !promptModalOverlayEl.classList.contains('visible')) {
1643
+ showConfigModal();
1644
+ }
1645
+ }
1646
+ // History navigation shortcuts (non-fullscreen)
1647
+ if (event.key === 'PageUp') {
1648
+ event.preventDefault();
1649
+ navigateToPreviousHistory();
1650
+ } else if (event.key === 'PageDown') {
1651
+ event.preventDefault();
1652
+ navigateToNextHistory();
1653
+ }
1654
+ }
1655
+ });
1656
+ console.log("Initialization setup complete.");
1657
+ });