Spaces:
Running
Running
Update index.html
Browse files- index.html +189 -1477
index.html
CHANGED
@@ -1,1479 +1,191 @@
|
|
1 |
-
|
2 |
-
|
3 |
-
|
4 |
-
|
5 |
-
|
6 |
-
|
7 |
-
|
8 |
-
|
9 |
-
|
10 |
-
|
11 |
-
|
12 |
-
|
13 |
-
|
14 |
-
|
15 |
-
|
16 |
-
|
17 |
-
|
18 |
-
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
-
|
23 |
-
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
|
56 |
-
|
57 |
-
|
58 |
-
|
59 |
-
|
60 |
-
|
61 |
-
|
62 |
-
|
63 |
-
|
64 |
-
|
65 |
-
|
66 |
-
|
67 |
-
|
68 |
-
|
69 |
-
|
70 |
-
|
71 |
-
|
72 |
-
|
73 |
-
|
74 |
-
|
75 |
-
|
76 |
-
|
77 |
-
|
78 |
-
|
79 |
-
|
80 |
-
|
81 |
-
|
82 |
-
|
83 |
-
|
84 |
-
|
85 |
-
|
86 |
-
|
87 |
-
|
88 |
-
|
89 |
-
|
90 |
-
|
91 |
-
|
92 |
-
|
93 |
-
|
94 |
-
|
95 |
-
|
96 |
-
|
97 |
-
|
98 |
-
|
99 |
-
|
100 |
-
|
101 |
-
|
102 |
-
|
103 |
-
|
104 |
-
|
105 |
-
|
106 |
-
|
107 |
-
|
108 |
-
|
109 |
-
|
110 |
-
|
111 |
-
|
112 |
-
|
113 |
-
|
114 |
-
|
115 |
-
|
116 |
-
|
117 |
-
|
118 |
-
|
119 |
-
|
120 |
-
|
121 |
-
|
122 |
-
|
123 |
-
|
124 |
-
|
125 |
-
|
126 |
-
|
127 |
-
|
128 |
-
|
129 |
-
|
130 |
-
|
131 |
-
|
132 |
-
|
133 |
-
|
134 |
-
|
135 |
-
|
136 |
-
|
137 |
-
const marker = "```html";
|
138 |
-
const markerIndex = accumulatedStreamData.indexOf(marker);
|
139 |
-
if (markerIndex !== -1) {
|
140 |
-
htmlBlockStarted = true;
|
141 |
-
let codeStartIndex = markerIndex + marker.length;
|
142 |
-
if (accumulatedStreamData[codeStartIndex] === '\n') {
|
143 |
-
codeStartIndex++;
|
144 |
-
}
|
145 |
-
rawGeneratedCode = accumulatedStreamData.substring(codeStartIndex);
|
146 |
-
}
|
147 |
-
} else {
|
148 |
-
rawGeneratedCode += textPart;
|
149 |
-
}
|
150 |
-
|
151 |
-
if (htmlBlockStarted) {
|
152 |
-
const now = Date.now();
|
153 |
-
if (lastPreviewUpdateTime[variationIndex] !== undefined && (now - lastPreviewUpdateTime[variationIndex] >= previewUpdateInterval)) {
|
154 |
-
updateLivePreviewInGrid(variationIndex, rawGeneratedCode, true);
|
155 |
-
lastPreviewUpdateTime[variationIndex] = now;
|
156 |
-
}
|
157 |
-
}
|
158 |
-
}
|
159 |
-
} catch (e) { console.warn(`[Var ${variationIndex + 1}] JSON parse error:`, e, "Problematic Line:", line); }
|
160 |
-
}
|
161 |
-
}
|
162 |
-
}
|
163 |
-
|
164 |
-
console.log(`[Variation ${variationIndex + 1}] Streaming finished. HTML Block Started: ${htmlBlockStarted}. Raw content after marker: ${(rawGeneratedCode || "").substring(0, 200)}`);
|
165 |
-
let finalExtractedHtml = null;
|
166 |
-
let processingErrorMessage = null;
|
167 |
-
|
168 |
-
if (htmlBlockStarted) {
|
169 |
-
let tempHtml = rawGeneratedCode.trim();
|
170 |
-
if (tempHtml.endsWith("```")) {
|
171 |
-
tempHtml = tempHtml.substring(0, tempHtml.length - 3).trim();
|
172 |
-
} else if (tempHtml.endsWith("```html")) {
|
173 |
-
tempHtml = tempHtml.substring(0, tempHtml.length - 7).trim();
|
174 |
-
}
|
175 |
-
|
176 |
-
if (tempHtml.length > 0) {
|
177 |
-
finalExtractedHtml = tempHtml;
|
178 |
-
const minLength = 20;
|
179 |
-
const hasStructuralTag = /<!DOCTYPE html|<html[^>]*>|<head[^>]*>|<body[^>]*>/i.test(finalExtractedHtml);
|
180 |
-
const hasAnyTag = /<[a-zA-Z][^>]*>/.test(finalExtractedHtml);
|
181 |
-
|
182 |
-
if (finalExtractedHtml.length >= minLength && (hasStructuralTag || hasAnyTag)) {
|
183 |
-
success = true;
|
184 |
-
console.log(`[Variation ${variationIndex + 1}] HTML validation SUCCEEDED.`);
|
185 |
-
} else {
|
186 |
-
success = false;
|
187 |
-
console.warn("[Variation " + (variationIndex + 1) + "] HTML validation FAILED (length: " + finalExtractedHtml.length + ", structural: " + hasStructuralTag + ", anyTag: " + hasAnyTag + "). Review content manually.");
|
188 |
-
}
|
189 |
-
} else {
|
190 |
-
processingErrorMessage = "// Error: No meaningful content found after '```html' marker.";
|
191 |
-
console.warn(`[Variation ${variationIndex + 1}] ${processingErrorMessage}`);
|
192 |
-
success = false;
|
193 |
-
}
|
194 |
-
} else {
|
195 |
-
processingErrorMessage = `// Error: '\`\`\`html' code block marker not found. Received:\n${(accumulatedStreamData || rawGeneratedCode || "").substring(0, 200)}`;
|
196 |
-
console.warn(`[Variation ${variationIndex + 1}] ${processingErrorMessage}`);
|
197 |
-
success = false;
|
198 |
-
}
|
199 |
-
|
200 |
-
if (success && finalExtractedHtml) {
|
201 |
-
let processedHtml = finalExtractedHtml;
|
202 |
-
const requiredHeadContent = `
|
203 |
-
<meta charset="UTF-8">
|
204 |
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
205 |
-
<script src="https://cdn.tailwindcss.com"><\\/script>
|
206 |
-
<link rel="preconnect" href="https://rsms.me/">
|
207 |
-
<link rel="stylesheet" href="https://rsms.me/inter/inter.css">
|
208 |
-
<style>
|
209 |
-
html { font-family: 'Inter', sans-serif; }
|
210 |
-
@supports (font-variation-settings: normal) {
|
211 |
-
html { font-family: 'Inter var', sans-serif; }
|
212 |
-
}
|
213 |
-
body {
|
214 |
-
background-color: #f8fafc; /* slate-50 */
|
215 |
-
color: #0f172a; /* slate-900 */
|
216 |
-
padding: 1rem;
|
217 |
-
}
|
218 |
-
</style>`;
|
219 |
-
|
220 |
-
if (!processedHtml.includes('<head>')) {
|
221 |
-
processedHtml = `<head>${requiredHeadContent}</head><body>${processedHtml}</body>`;
|
222 |
-
} else {
|
223 |
-
let tempRequired = "";
|
224 |
-
if (!processedHtml.includes('cdn.tailwindcss.com')) tempRequired += ` <script src="https://cdn.tailwindcss.com"><\\/script>\\n`;
|
225 |
-
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`;
|
226 |
-
if (!processedHtml.includes("html { font-family: 'Inter', sans-serif; }")) {
|
227 |
-
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`;
|
228 |
-
}
|
229 |
-
if (tempRequired) {
|
230 |
-
processedHtml = processedHtml.replace(/<\/head>/i, `${tempRequired}</head>`);
|
231 |
-
}
|
232 |
-
}
|
233 |
-
if(currentCleanedCode[variationIndex] !== undefined) currentCleanedCode[variationIndex] = processedHtml;
|
234 |
-
|
235 |
-
const interactionScript = `<script>(function() { const VARIATION_INDEX = ${variationIndex}; })(); <\\/script>`;
|
236 |
-
const bodyEndIndex = processedHtml.lastIndexOf('</body>');
|
237 |
-
if(generatedCode[variationIndex] !== undefined) {
|
238 |
-
generatedCode[variationIndex] = (bodyEndIndex !== -1)
|
239 |
-
? processedHtml.slice(0, bodyEndIndex) + interactionScript + processedHtml.slice(bodyEndIndex)
|
240 |
-
: processedHtml + interactionScript;
|
241 |
-
}
|
242 |
-
updateLivePreviewInGrid(variationIndex, null, true);
|
243 |
-
} else {
|
244 |
-
generatedCode[variationIndex] = '';
|
245 |
-
if (finalExtractedHtml) {
|
246 |
-
if(currentCleanedCode[variationIndex] !== undefined) currentCleanedCode[variationIndex] = finalExtractedHtml;
|
247 |
-
updateLivePreviewInGrid(variationIndex, finalExtractedHtml, false);
|
248 |
-
} else {
|
249 |
-
if(currentCleanedCode[variationIndex] !== undefined) currentCleanedCode[variationIndex] = processingErrorMessage;
|
250 |
-
updateLivePreviewInGrid(variationIndex, `<div class="p-4 text-red-400 font-medium">${processingErrorMessage.replace(/\\n/g, '<br>')}</div>`, false);
|
251 |
-
}
|
252 |
-
}
|
253 |
-
|
254 |
-
} catch (error) {
|
255 |
-
if (error.name === 'AbortError') {
|
256 |
-
console.log(`[Variation ${variationIndex + 1}] Fetch aborted.`);
|
257 |
-
updateLivePreviewInGrid(variationIndex, `<div class="p-4 text-yellow-400 font-medium">Generation Cancelled.</div>`, false);
|
258 |
-
} else {
|
259 |
-
console.error(`[Variation ${variationIndex + 1}] Generation error:`, error);
|
260 |
-
updateLivePreviewInGrid(variationIndex, `<div class="p-4 text-red-400 font-medium">Error: ${error.message}</div>`, false);
|
261 |
-
}
|
262 |
-
if(currentCleanedCode[variationIndex] !== undefined) currentCleanedCode[variationIndex] = `// Error: ${error.message}`;
|
263 |
-
if(generatedCode[variationIndex] !== undefined) generatedCode[variationIndex] = '';
|
264 |
-
} finally {
|
265 |
-
const finalLoader = document.getElementById(`preview-loader-${variationIndex + 1}`);
|
266 |
-
if (finalLoader) finalLoader.classList.add('hidden');
|
267 |
-
if (selectBtn) selectBtn.disabled = !success; // Disable if not successful
|
268 |
-
if (fullscreenBtn) fullscreenBtn.disabled = !success; // Disable if not successful
|
269 |
-
}
|
270 |
-
return success;
|
271 |
-
}
|
272 |
-
|
273 |
-
// --- Main Function to Generate Variations ---
|
274 |
-
async function generateVariations() {
|
275 |
-
console.log("generateVariations called.");
|
276 |
-
const userPrompt = modalUserPromptEl.value.trim();
|
277 |
-
|
278 |
-
if (!apiKeyEl || !modalUserPromptEl || !modelSelEl || !previewGridWrapperEl || !modalGenerateBtnEl || !modalLoadingIndicatorEl || !errorMessageEl || !codeOutputEl) {
|
279 |
-
console.error("Cannot generate variations: One or more critical elements are missing.");
|
280 |
-
if (errorMessageEl) errorMessageEl.textContent = "Initialization error. Cannot generate.";
|
281 |
-
return;
|
282 |
-
}
|
283 |
-
|
284 |
-
const apiKey = apiKeyEl.value.trim();
|
285 |
-
const selectedModel = modelSelEl.value;
|
286 |
-
const currentIsRefinementMode = modalRefinementCheckboxEl.checked;
|
287 |
-
const currentNumVariations = parseInt(numVariationsSliderEl.value, 10);
|
288 |
-
const currentThinkingBudget = parseInt(modalThinkingBudgetSliderEl.value, 10);
|
289 |
-
|
290 |
-
if (!apiKey || !userPrompt) {
|
291 |
-
errorMessageEl.textContent = 'Error: API Key and Prompt (via Alt+P) are required.';
|
292 |
-
if (!userPrompt && !promptModalOverlayEl.classList.contains('visible')) showPromptModal();
|
293 |
-
return;
|
294 |
-
}
|
295 |
-
if (!selectedModel) {
|
296 |
-
errorMessageEl.textContent = 'Error: Please select a model.';
|
297 |
-
return;
|
298 |
-
}
|
299 |
-
|
300 |
-
lastGenerationConfig = {
|
301 |
-
prompt: userPrompt,
|
302 |
-
isRefinement: currentIsRefinementMode,
|
303 |
-
numVariations: currentNumVariations,
|
304 |
-
refinedTimelineIndex: currentIsRefinementMode ? activeTimelineIndex : -1,
|
305 |
-
thinkingBudget: currentThinkingBudget
|
306 |
-
};
|
307 |
-
|
308 |
-
|
309 |
-
errorMessageEl.textContent = '';
|
310 |
-
console.log(`Mode: ${currentIsRefinementMode ? 'Refinement' : 'Initial'}, Model: ${selectedModel}, Variations: ${currentNumVariations}`);
|
311 |
-
|
312 |
-
let baseCodeForRefinement = null;
|
313 |
-
let contextPromptForRefinement = '';
|
314 |
-
originalUserPromptForCurrentGeneration = userPrompt;
|
315 |
-
|
316 |
-
if (currentIsRefinementMode && activeTimelineIndex !== -1 && evolutionTimeline[activeTimelineIndex]) {
|
317 |
-
baseCodeForRefinement = evolutionTimeline[activeTimelineIndex].code;
|
318 |
-
contextPromptForRefinement = evolutionTimeline[activeTimelineIndex].originalUserPrompt;
|
319 |
-
console.log(`Refining Evolution ${activeTimelineIndex + 1}. Original context: "${contextPromptForRefinement}"`);
|
320 |
-
} else if (currentIsRefinementMode) {
|
321 |
-
errorMessageEl.textContent = 'Error: No active evolution selected to refine. Uncheck "refine" or select an evolution from history.';
|
322 |
-
return;
|
323 |
-
}
|
324 |
-
|
325 |
-
modalGenerateBtnEl.disabled = true;
|
326 |
-
modalLoadingIndicatorEl.classList.remove('hidden');
|
327 |
-
if (codeOutputEl && selectedCodeTitleH3El) {
|
328 |
-
codeOutputEl.innerHTML = '<code class="language-html">// Select a variation to view its code.</code>';
|
329 |
-
selectedCodeTitleH3El.textContent = "Selected Code:";
|
330 |
-
}
|
331 |
-
selectedVariationGridIndex = -1;
|
332 |
-
|
333 |
-
numVariationsToGenerate = currentNumVariations;
|
334 |
-
generatedCode = Array(numVariationsToGenerate).fill('');
|
335 |
-
currentCleanedCode = Array(numVariationsToGenerate).fill('');
|
336 |
-
lastPreviewUpdateTime = Array(numVariationsToGenerate).fill(0);
|
337 |
-
selectButtons = Array(numVariationsToGenerate).fill(null);
|
338 |
-
fullscreenButtons = Array(numVariationsToGenerate).fill(null);
|
339 |
-
previewItems = Array(numVariationsToGenerate).fill(null);
|
340 |
-
|
341 |
-
|
342 |
-
showFourGridPreviewUI();
|
343 |
-
|
344 |
-
activeApiControllers = Array(numVariationsToGenerate).fill(null).map(() => new AbortController());
|
345 |
-
|
346 |
-
const prompts = Array(numVariationsToGenerate).fill(null).map((_, i) => {
|
347 |
-
if (currentIsRefinementMode && baseCodeForRefinement) {
|
348 |
-
return `
|
349 |
-
You are an expert web developer specializing in HTML, Tailwind CSS, and JavaScript.
|
350 |
-
Original User Request (for context): "${contextPromptForRefinement}"
|
351 |
-
Base HTML Code to Refine:
|
352 |
-
\`\`\`html
|
353 |
-
${baseCodeForRefinement}
|
354 |
-
\`\`\`
|
355 |
-
User's Refinement Instructions: "${userPrompt}"
|
356 |
-
Instructions:
|
357 |
-
1. Analyze the Base Code and the User's Refinement Instructions.
|
358 |
-
2. Modify ONLY the necessary parts of the Base Code to implement the refinement.
|
359 |
-
3. This is Variation ${i + 1} of ${numVariationsToGenerate} attempting this refinement. Try a slightly different approach if possible.
|
360 |
-
4. Ensure the refined code remains a single, complete, runnable HTML document.
|
361 |
-
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>.
|
362 |
-
6. Add basic body styling for readability: <style>html { font-family: 'Inter', sans-serif; } body { background-color: #f8fafc; color: #0f172a; padding: 1rem; }</style>
|
363 |
-
7. Output ONLY the raw, complete, refined HTML code. Do NOT include explanations or markdown. Start your response with \`\`\`html.
|
364 |
-
Refined Code:`;
|
365 |
-
} else {
|
366 |
-
return `
|
367 |
-
You are an expert web developer specializing in clean, modern HTML, CSS (using Tailwind CSS classes), and JavaScript.
|
368 |
-
Generate the complete, runnable HTML code for the following request.
|
369 |
-
Ensure the code is self-contained.
|
370 |
-
1. ALWAYS include Tailwind CSS CDN (<script src="https://cdn.tailwindcss.com"><\\/script>) in the <head>.
|
371 |
-
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>.
|
372 |
-
3. Add basic body styling for readability: body { background-color: #f8fafc; color: #0f172a; padding: 1rem; } in the same <style> tag.
|
373 |
-
4. Add HTML comments to explain the code.
|
374 |
-
5. This is Variation ${i + 1} of ${numVariationsToGenerate}. Try to make it distinct.
|
375 |
-
6. Output ONLY the raw HTML code. Do NOT include explanations or markdown. Start your response with \`\`\`html.
|
376 |
-
User Request:
|
377 |
-
"${originalUserPromptForCurrentGeneration}"
|
378 |
-
Code:`;
|
379 |
-
}
|
380 |
-
});
|
381 |
-
|
382 |
-
const generationPromises = prompts.map((prompt, index) =>
|
383 |
-
processStreamForVariation(apiKey, prompt, index, selectedModel, activeApiControllers[index].signal)
|
384 |
-
);
|
385 |
-
|
386 |
-
try {
|
387 |
-
const results = await Promise.allSettled(generationPromises);
|
388 |
-
console.log("Generation promises settled:", results);
|
389 |
-
const successfulGenerations = results.filter(r => r.status === 'fulfilled' && r.value === true).length;
|
390 |
-
if (successfulGenerations === 0 && !results.some(r => r.status === 'rejected' && r.reason.name === 'AbortError')) {
|
391 |
-
errorMessageElement.textContent = 'Error: All variations failed.';
|
392 |
-
}
|
393 |
-
else if (successfulGenerations < numVariationsToGenerate && !results.every(r => r.status === 'rejected' && r.reason.name === 'AbortError')) {
|
394 |
-
errorMessageElement.textContent = `Warning: ${numVariationsToGenerate - successfulGenerations} var(s) failed or were cancelled.`;
|
395 |
-
}
|
396 |
-
|
397 |
-
updateMainContentTitles("Select a Variation", "Click 'Select' on a preview below.");
|
398 |
-
|
399 |
-
} catch (error) {
|
400 |
-
console.error("Parallel generation error:", error);
|
401 |
-
errorMessageElement.textContent = `Unexpected Error: ${error.message}`;
|
402 |
-
showInitialPreviewStateUI();
|
403 |
-
} finally {
|
404 |
-
modalGenerateBtnEl.disabled = false;
|
405 |
-
modalLoadingIndicatorEl.classList.add('hidden');
|
406 |
-
console.log("Generation process finished.");
|
407 |
-
}
|
408 |
-
}
|
409 |
-
|
410 |
-
// --- UI Update Functions ---
|
411 |
-
function createPreviewItemDOM(index, isGridItem = true) {
|
412 |
-
const item = document.createElement('div');
|
413 |
-
item.id = `preview-item-${index + 1}`;
|
414 |
-
item.className = isGridItem ? 'preview-item-perspective' : 'single-preview-item';
|
415 |
-
if (isGridItem) item.dataset.variationGridIndex = index;
|
416 |
-
|
417 |
-
const header = document.createElement('div'); header.className = 'preview-header';
|
418 |
-
const title = document.createElement('span'); title.className = 'preview-header-title';
|
419 |
-
title.textContent = isGridItem ? `Variation ${index + 1}` : (evolutionTimeline[activeTimelineIndex]?.prompt.substring(0,30) + '...' || `Evolution ${activeTimelineIndex + 1}`);
|
420 |
-
header.appendChild(title);
|
421 |
-
|
422 |
-
const btns = document.createElement('div'); btns.className = 'preview-header-buttons';
|
423 |
-
|
424 |
-
const fsBtn = document.createElement('button');
|
425 |
-
fsBtn.className = 'fullscreen-btn p-1 focus:outline-none focus:ring-1 focus:ring-cyan-500 rounded disabled:opacity-50';
|
426 |
-
fsBtn.dataset.idx = index;
|
427 |
-
fsBtn.title = 'Full Screen'; fsBtn.disabled = true;
|
428 |
-
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>`;
|
429 |
-
fsBtn.addEventListener('click', (e) => {
|
430 |
-
const idxToFullscreen = isGridItem ? parseInt(e.currentTarget.dataset.idx) : activeTimelineIndex;
|
431 |
-
const fromHistory = !isGridItem;
|
432 |
-
if (!isNaN(idxToFullscreen) && (isGridItem ? generatedCode[idxToFullscreen] : evolutionTimeline[idxToFullscreen]?.code)) {
|
433 |
-
enterFullscreen(idxToFullscreen, fromHistory);
|
434 |
-
}
|
435 |
-
});
|
436 |
-
|
437 |
-
btns.appendChild(fsBtn);
|
438 |
-
|
439 |
-
if (isGridItem) {
|
440 |
-
const selBtn = document.createElement('button');
|
441 |
-
selBtn.className = 'select-variation-btn futuristic-button px-3 py-1 text-xs';
|
442 |
-
selBtn.dataset.variationGridIndex = index; selBtn.disabled = true; selBtn.textContent = 'Select';
|
443 |
-
selBtn.addEventListener('click', handleSelectVariationFromGrid);
|
444 |
-
selectButtons[index] = selBtn;
|
445 |
-
btns.appendChild(selBtn);
|
446 |
-
}
|
447 |
-
|
448 |
-
header.appendChild(btns);
|
449 |
-
item.appendChild(header);
|
450 |
-
|
451 |
-
const bodyEl = document.createElement('div'); bodyEl.className = 'preview-body';
|
452 |
-
const loaderDiv = document.createElement('div'); loaderDiv.id = `preview-loader-${index + 1}`;
|
453 |
-
loaderDiv.className = 'preview-loader ' + (isGridItem ? 'hidden' : '');
|
454 |
-
loaderDiv.innerHTML = '<div class="spinner"></div>';
|
455 |
-
bodyEl.appendChild(loaderDiv);
|
456 |
-
|
457 |
-
const iframeEl = document.createElement('iframe');
|
458 |
-
iframeEl.id = isGridItem ? `preview-frame-${index + 1}` : 'single-large-preview-frame';
|
459 |
-
iframeEl.title = `Preview ${index + 1}`; iframeEl.className = 'preview-frame';
|
460 |
-
iframeEl.srcdoc = '<div class="flex items-center justify-center h-full"><p class="text-slate-400">Preparing...</p></div>';
|
461 |
-
bodyEl.appendChild(iframeEl);
|
462 |
-
item.appendChild(bodyEl);
|
463 |
-
|
464 |
-
if (isGridItem) {
|
465 |
-
previewItems[index] = item;
|
466 |
-
fullscreenButtons[index] = fsBtn;
|
467 |
-
}
|
468 |
-
return item;
|
469 |
-
}
|
470 |
-
|
471 |
-
|
472 |
-
function showFourGridPreviewUI() {
|
473 |
-
previewGridWrapperEl.innerHTML = '';
|
474 |
-
if (numVariationsToGenerate === 1) {
|
475 |
-
previewGridWrapperEl.className = 'single-mode';
|
476 |
-
if(perspectiveViewportEl) perspectiveViewportEl.style.perspective = 'none';
|
477 |
-
const item = createPreviewItemDOM(0, true);
|
478 |
-
previewGridWrapperEl.appendChild(item);
|
479 |
-
|
480 |
-
} else if (numVariationsToGenerate === 2) {
|
481 |
-
previewGridWrapperEl.className = 'grid grid-cols-2 grid-rows-1 gap-6';
|
482 |
-
if(perspectiveViewportEl) perspectiveViewportEl.style.perspective = '1500px';
|
483 |
-
for (let i = 0; i < numVariationsToGenerate; i++) {
|
484 |
-
const item = createPreviewItemDOM(i, true);
|
485 |
-
previewGridWrapperEl.appendChild(item);
|
486 |
-
}
|
487 |
-
} else {
|
488 |
-
previewGridWrapperEl.className = 'grid-mode';
|
489 |
-
if(perspectiveViewportEl) perspectiveViewportEl.style.perspective = '1500px';
|
490 |
-
for (let i = 0; i < numVariationsToGenerate; i++) {
|
491 |
-
const item = createPreviewItemDOM(i, true);
|
492 |
-
previewGridWrapperEl.appendChild(item);
|
493 |
-
}
|
494 |
-
}
|
495 |
-
updateSelectedGridItemUI();
|
496 |
-
}
|
497 |
-
|
498 |
-
function showSingleLargePreviewUI(htmlContent, titleText, fullPromptText) {
|
499 |
-
previewGridWrapperEl.innerHTML = '';
|
500 |
-
previewGridWrapperEl.className = 'single-mode';
|
501 |
-
if(perspectiveViewportEl) perspectiveViewportEl.style.perspective = 'none';
|
502 |
-
|
503 |
-
const item = document.createElement('div');
|
504 |
-
item.className = 'single-preview-item';
|
505 |
-
|
506 |
-
const bodyEl = document.createElement('div');
|
507 |
-
bodyEl.className = 'preview-body';
|
508 |
-
|
509 |
-
const iframeEl = document.createElement('iframe');
|
510 |
-
iframeEl.id = `single-large-preview-frame`;
|
511 |
-
iframeEl.title = titleText;
|
512 |
-
iframeEl.className = 'preview-frame';
|
513 |
-
iframeEl.srcdoc = htmlContent;
|
514 |
-
|
515 |
-
bodyEl.appendChild(iframeEl);
|
516 |
-
item.appendChild(bodyEl);
|
517 |
-
previewGridWrapperEl.appendChild(item);
|
518 |
-
|
519 |
-
// Handle subtitle truncation and clickability
|
520 |
-
const maxPromptLength = 50;
|
521 |
-
let displaySubtitle = fullPromptText;
|
522 |
-
mainContentSubtitleH2El.classList.remove('prompt-truncated');
|
523 |
-
delete mainContentSubtitleH2El.dataset.fullPrompt;
|
524 |
-
|
525 |
-
if (fullPromptText && fullPromptText.length > maxPromptLength) {
|
526 |
-
displaySubtitle = fullPromptText.substring(0, maxPromptLength) + "... (click to view full)";
|
527 |
-
mainContentSubtitleH2El.classList.add('prompt-truncated');
|
528 |
-
mainContentSubtitleH2El.dataset.fullPrompt = fullPromptText; // Store full prompt
|
529 |
-
}
|
530 |
-
|
531 |
-
updateMainContentTitles(titleText, displaySubtitle); // Use potentially truncated text
|
532 |
-
}
|
533 |
-
|
534 |
-
function showInitialPreviewStateUI() {
|
535 |
-
previewGridWrapperEl.innerHTML = '<div class="col-span-2 row-span-2 flex items-center justify-center text-slate-500 text-lg"><p>Press Alt+O, then Alt+P to begin.</p></div>';
|
536 |
-
previewGridWrapperEl.className = 'grid-mode';
|
537 |
-
if(perspectiveViewportEl) perspectiveViewportEl.style.perspective = '1500px';
|
538 |
-
updateMainContentTitles("Live Previews", "Powered by Gemini Models");
|
539 |
-
if (codeOutputEl) codeOutputEl.innerHTML = '<code class="language-html">// Select a variation or history item to view its code.</code>';
|
540 |
-
if (selectedCodeTitleH3El) selectedCodeTitleH3El.textContent = "Selected Code:";
|
541 |
-
selectedVariationGridIndex = -1;
|
542 |
-
// Reset subtitle state when going back to initial view
|
543 |
-
if (mainContentSubtitleH2El) {
|
544 |
-
mainContentSubtitleH2El.classList.remove('prompt-truncated');
|
545 |
-
delete mainContentSubtitleH2El.dataset.fullPrompt;
|
546 |
-
}
|
547 |
-
}
|
548 |
-
|
549 |
-
function updateMainContentTitles(title, subtitle) {
|
550 |
-
if (mainContentTitleH1El) mainContentTitleH1El.textContent = title;
|
551 |
-
if (mainContentSubtitleH2El) mainContentSubtitleH2El.textContent = subtitle;
|
552 |
-
}
|
553 |
-
|
554 |
-
function updateSelectedGridItemUI() {
|
555 |
-
previewItems.forEach((item, index) => {
|
556 |
-
if (!item) return;
|
557 |
-
item.classList.toggle('selected', index === selectedVariationGridIndex);
|
558 |
-
});
|
559 |
-
selectButtons.forEach((button, index) => {
|
560 |
-
if (!button) return;
|
561 |
-
// Button enabled/disabled state is handled by procStream's success.
|
562 |
-
// const hasCode = currentCleanedCode[index]?.length > 0 && !currentCleanedCode[index].startsWith("// Error");
|
563 |
-
// button.disabled = !hasCode;
|
564 |
-
// if (fullscreenButtons[index]) { fullscreenButtons[index].disabled = !hasCode; }
|
565 |
-
|
566 |
-
button.classList.remove('selected-state');
|
567 |
-
if (index === selectedVariationGridIndex) {
|
568 |
-
button.textContent = 'Selected';
|
569 |
-
button.classList.add('selected-state');
|
570 |
-
} else {
|
571 |
-
button.textContent = 'Select';
|
572 |
-
}
|
573 |
-
});
|
574 |
-
}
|
575 |
-
|
576 |
-
function updateLivePreviewInGrid(index, codeToRender = null, applyZoom = false) {
|
577 |
-
let baseHtml = codeToRender !== null ? codeToRender : generatedCode[index];
|
578 |
-
const frame = document.getElementById(`preview-frame-${index + 1}`);
|
579 |
-
if (!frame) { console.warn(`[updateLivePreviewInGrid][Var ${index + 1}] Frame not found.`); return; }
|
580 |
-
|
581 |
-
if (typeof baseHtml !== 'string') {
|
582 |
-
baseHtml = '<div class="p-4 text-orange-500">Invalid content received</div>';
|
583 |
-
applyZoom = false;
|
584 |
-
}
|
585 |
-
let finalHtml = baseHtml;
|
586 |
-
try {
|
587 |
-
if (applyZoom && numVariationsToGenerate > 1) {
|
588 |
-
const scaleStyle = `<style>html { transform: scale(0.5); transform-origin: 0 0; width: 200%; height: 200%; overflow: auto !important; } body { overflow: visible !important; min-height: 100% !important; height: auto !important; width: auto !important; }</style>`;
|
589 |
-
const headEndIndex = finalHtml.toLowerCase().lastIndexOf('</head>');
|
590 |
-
if (headEndIndex !== -1) {
|
591 |
-
finalHtml = finalHtml.slice(0, headEndIndex) + scaleStyle + finalHtml.slice(headEndIndex);
|
592 |
-
} else {
|
593 |
-
finalHtml = scaleStyle + finalHtml;
|
594 |
-
}
|
595 |
-
}
|
596 |
-
frame.srcdoc = finalHtml;
|
597 |
-
} catch (e) {
|
598 |
-
console.error(`[Var ${index + 1}] Error setting srcdoc for grid:`, e);
|
599 |
-
try {
|
600 |
-
frame.srcdoc = `<div class="p-4 text-red-500 font-semibold">Preview Render Error</div>`;
|
601 |
-
} catch (finalError) { console.error("Failed to display error in grid iframe:", finalError); }
|
602 |
-
}
|
603 |
-
}
|
604 |
-
|
605 |
-
// --- Event Handlers ---
|
606 |
-
function handleSelectVariationFromGrid(event) {
|
607 |
-
const idx = parseInt(event.target.dataset.variationGridIndex, 10);
|
608 |
-
// Check if code for this variation is valid (not an error message)
|
609 |
-
if (isNaN(idx) || !currentCleanedCode[idx] || currentCleanedCode[idx].startsWith("// Error")) {
|
610 |
-
console.warn(`Cannot select variation ${idx + 1}, code is invalid or generation failed.`);
|
611 |
-
return;
|
612 |
-
}
|
613 |
-
|
614 |
-
activeApiControllers.forEach((controller, controllerIndex) => {
|
615 |
-
if (controllerIndex !== idx && controller) {
|
616 |
-
console.log(`Aborting request for variation ${controllerIndex + 1}`);
|
617 |
-
controller.abort();
|
618 |
-
}
|
619 |
-
});
|
620 |
-
activeApiControllers = [];
|
621 |
-
|
622 |
-
|
623 |
-
selectedVariationGridIndex = idx;
|
624 |
-
const displayCode = generatedCode[idx];
|
625 |
-
const storeCode = currentCleanedCode[idx];
|
626 |
-
|
627 |
-
let originalPromptForThisEvolution;
|
628 |
-
let parentTimelineIdx = null;
|
629 |
-
|
630 |
-
const wasThisGenerationARefinement = lastGenerationConfig.isRefinement;
|
631 |
-
const refinedIndexForThisGen = lastGenerationConfig.refinedTimelineIndex;
|
632 |
-
|
633 |
-
if (wasThisGenerationARefinement && refinedIndexForThisGen !== -1 && evolutionTimeline[refinedIndexForThisGen]) {
|
634 |
-
originalPromptForThisEvolution = evolutionTimeline[refinedIndexForThisGen].originalUserPrompt;
|
635 |
-
parentTimelineIdx = refinedIndexForThisGen;
|
636 |
-
} else {
|
637 |
-
originalPromptForThisEvolution = originalUserPromptForCurrentGeneration;
|
638 |
-
}
|
639 |
-
|
640 |
-
const newHistoryEntry = {
|
641 |
-
id: 'evo-' + Date.now() + '-' + Math.random().toString(36).substr(2, 5),
|
642 |
-
prompt: originalUserPromptForCurrentGeneration,
|
643 |
-
originalUserPrompt: originalPromptForThisEvolution,
|
644 |
-
code: storeCode,
|
645 |
-
timestamp: new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit'}),
|
646 |
-
parentIndex: parentTimelineIdx
|
647 |
-
};
|
648 |
-
evolutionTimeline.push(newHistoryEntry);
|
649 |
-
activeTimelineIndex = evolutionTimeline.length - 1;
|
650 |
-
|
651 |
-
console.log(`Variation ${idx + 1} selected from grid. Added to timeline as Evolution ${activeTimelineIndex + 1}.`);
|
652 |
-
|
653 |
-
renderHistoryPanel();
|
654 |
-
showSingleLargePreviewUI(displayCode, `Evolution ${activeTimelineIndex + 1}: Active`, `Prompt: "${newHistoryEntry.prompt}"`);
|
655 |
-
|
656 |
-
if (codeOutputEl) {
|
657 |
-
codeOutputEl.textContent = storeCode;
|
658 |
-
if (selectedCodeTitleH3El) selectedCodeTitleH3El.textContent = `Code (Evolution ${activeTimelineIndex + 1}):`;
|
659 |
-
codeOutputEl.classList.remove('text-slate-200');
|
660 |
-
codeOutputEl.classList.add('text-slate-400');
|
661 |
-
if (codeOutputEl?.parentElement) { codeOutputEl.parentElement.scrollTop = 0; }
|
662 |
-
}
|
663 |
-
|
664 |
-
|
665 |
-
updateSelectedGridItemUI();
|
666 |
-
updateHistoryNavigationButtons(); // Update nav buttons
|
667 |
-
|
668 |
-
if(modalUserPromptEl) modalUserPromptEl.value = '';
|
669 |
-
if(modalUserPromptEl) modalUserPromptEl.placeholder = `Refine Evolution ${activeTimelineIndex + 1}...`;
|
670 |
-
if(modalRefinementCheckboxEl) modalRefinementCheckboxEl.checked = true;
|
671 |
-
}
|
672 |
-
|
673 |
-
function handleHistoryItemClick(timelineIdxToView) {
|
674 |
-
if (timelineIdxToView < 0 || timelineIdxToView >= evolutionTimeline.length) {
|
675 |
-
console.warn("Invalid history index clicked:", timelineIdxToView);
|
676 |
-
return;
|
677 |
-
}
|
678 |
-
activeTimelineIndex = timelineIdxToView;
|
679 |
-
const historyEntry = evolutionTimeline[activeTimelineIndex];
|
680 |
-
|
681 |
-
console.log(`History item ${activeTimelineIndex + 1} selected.`);
|
682 |
-
|
683 |
-
const displayCodeForHistory = historyEntry.code;
|
684 |
-
|
685 |
-
showSingleLargePreviewUI(displayCodeForHistory, `Evolution ${activeTimelineIndex + 1}: Active (Historical)`, `Prompt: "${historyEntry.prompt}"`);
|
686 |
-
|
687 |
-
if (codeOutputEl) {
|
688 |
-
codeOutputEl.textContent = historyEntry.code;
|
689 |
-
if (selectedCodeTitleH3El) selectedCodeTitleH3El.textContent = `Code (Evolution ${activeTimelineIndex + 1} - Historical):`;
|
690 |
-
codeOutputEl.classList.remove('text-slate-200');
|
691 |
-
codeOutputEl.classList.add('text-slate-400');
|
692 |
-
if (codeOutputEl?.parentElement) { codeOutputEl.parentElement.scrollTop = 0; }
|
693 |
-
}
|
694 |
-
|
695 |
-
renderHistoryPanel();
|
696 |
-
updateHistoryNavigationButtons(); // Update nav buttons
|
697 |
-
|
698 |
-
if(modalUserPromptEl) modalUserPromptEl.value = '';
|
699 |
-
if(modalUserPromptEl) modalUserPromptEl.placeholder = `Refine Evolution ${activeTimelineIndex + 1}...`;
|
700 |
-
if(modalRefinementCheckboxEl) modalRefinementCheckboxEl.checked = true;
|
701 |
-
selectedVariationGridIndex = -1;
|
702 |
-
updateSelectedGridItemUI();
|
703 |
-
}
|
704 |
-
|
705 |
-
|
706 |
-
// --- Render History Panel ---
|
707 |
-
function createHistoryThumbnailDOM(entry, index) {
|
708 |
-
const thumbItem = document.createElement('div');
|
709 |
-
thumbItem.className = 'history-thumbnail-item group';
|
710 |
-
thumbItem.dataset.timelineIndex = index;
|
711 |
-
thumbItem.setAttribute('aria-label', `Evolution Step ${index + 1}: ${entry.prompt.substring(0, 30)}...`);
|
712 |
-
|
713 |
-
// Note: Active class and dynamic transforms/z-index are applied in renderHistoryPanel
|
714 |
-
|
715 |
-
const previewContainer = document.createElement('div');
|
716 |
-
previewContainer.className = 'history-thumbnail-preview-container';
|
717 |
-
previewContainer.title = `Click to view Evolution ${index + 1}`;
|
718 |
-
previewContainer.addEventListener('click', () => handleHistoryItemClick(index));
|
719 |
-
|
720 |
-
const iframe = document.createElement('iframe');
|
721 |
-
iframe.className = 'history-thumbnail-preview';
|
722 |
-
iframe.title = `Preview of Evolution ${index + 1}`;
|
723 |
-
const scaledContent = `
|
724 |
-
<style>
|
725 |
-
html { transform: scale(0.25); transform-origin: 0 0; width: 400%; height: 400%; overflow: hidden !important; background-color: #fff; }
|
726 |
-
body { width: 100%; height: 100%; overflow: hidden !important; padding: 0 !important; margin: 0 !important; }
|
727 |
-
</style>
|
728 |
-
${entry.code}
|
729 |
-
`;
|
730 |
-
iframe.srcdoc = scaledContent;
|
731 |
-
previewContainer.appendChild(iframe);
|
732 |
-
|
733 |
-
const titleEl = document.createElement('div');
|
734 |
-
titleEl.className = 'history-thumbnail-title';
|
735 |
-
titleEl.textContent = `Evo ${index + 1}: ${entry.prompt.substring(0, 20)}${entry.prompt.length > 20 ? '...' : ''}`;
|
736 |
-
titleEl.title = `Prompt: ${entry.prompt}\\nClick to view Evolution ${index + 1}`;
|
737 |
-
titleEl.addEventListener('click', () => handleHistoryItemClick(index));
|
738 |
-
|
739 |
-
const fsBtn = document.createElement('button');
|
740 |
-
fsBtn.className = 'history-thumbnail-fullscreen-btn';
|
741 |
-
fsBtn.title = 'View Fullscreen';
|
742 |
-
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>`;
|
743 |
-
fsBtn.addEventListener('click', (e) => {
|
744 |
-
e.stopPropagation();
|
745 |
-
enterFullscreen(index, true);
|
746 |
-
});
|
747 |
-
|
748 |
-
thumbItem.appendChild(previewContainer);
|
749 |
-
thumbItem.appendChild(titleEl);
|
750 |
-
thumbItem.appendChild(fsBtn);
|
751 |
-
return thumbItem;
|
752 |
-
}
|
753 |
-
|
754 |
-
function renderHistoryPanel() {
|
755 |
-
if (!historyPanelEl || !historyPanelPlaceholderEl) return;
|
756 |
-
historyPanelEl.innerHTML = ''; // Clear previous items
|
757 |
-
|
758 |
-
if (evolutionTimeline.length === 0) {
|
759 |
-
historyPanelEl.appendChild(historyPanelPlaceholderEl);
|
760 |
-
historyPanelPlaceholderEl.classList.remove('hidden');
|
761 |
-
return;
|
762 |
-
}
|
763 |
-
|
764 |
-
historyPanelPlaceholderEl.classList.add('hidden');
|
765 |
-
|
766 |
-
const totalItems = evolutionTimeline.length;
|
767 |
-
const middleIndex = Math.floor(totalItems / 2);
|
768 |
-
// const maxRotation = 15; // Max rotation in degrees for edge items (rotation is now 0)
|
769 |
-
const yOffsetFactor = 4; // How much non-active items lift towards the edges
|
770 |
-
const zOffsetFactor = -15; // How much non-active items move back towards the edges
|
771 |
-
const baseZIndex = 10; // Base z-index for non-active items
|
772 |
-
|
773 |
-
// Define specific offsets for the active item to make it stand out
|
774 |
-
const activeYOffset = -15; // Moves active item further up
|
775 |
-
const activeZOffset = 25; // Moves active item further forward
|
776 |
-
|
777 |
-
evolutionTimeline.forEach((entry, index) => {
|
778 |
-
const thumbItem = createHistoryThumbnailDOM(entry, index);
|
779 |
-
let finalTransform;
|
780 |
-
let finalZIndex;
|
781 |
-
|
782 |
-
if (index === activeTimelineIndex) {
|
783 |
-
thumbItem.classList.add('active-history-item');
|
784 |
-
// Transform for the active item: more prominent
|
785 |
-
finalTransform = `translateY(${activeYOffset}px) translateZ(${activeZOffset}px) rotate(0deg)`;
|
786 |
-
// The .active-history-item class in CSS sets z-index: 50.
|
787 |
-
// We can set it here too for consistency or rely on CSS.
|
788 |
-
finalZIndex = 50;
|
789 |
-
} else {
|
790 |
-
// Calculate transformations relative to the middle for non-active items
|
791 |
-
const deltaFromMiddle = index - middleIndex;
|
792 |
-
const rotation = 0; // Rotation is kept at 0
|
793 |
-
const translateY = Math.abs(deltaFromMiddle) * yOffsetFactor;
|
794 |
-
const translateZ = Math.abs(deltaFromMiddle) * zOffsetFactor;
|
795 |
-
|
796 |
-
finalTransform = `translateY(${translateY}px) translateZ(${translateZ}px) rotate(${rotation}deg)`;
|
797 |
-
finalZIndex = baseZIndex - Math.abs(deltaFromMiddle);
|
798 |
-
}
|
799 |
-
|
800 |
-
thumbItem.style.zIndex = `${finalZIndex}`;
|
801 |
-
thumbItem.style.transform = finalTransform;
|
802 |
-
|
803 |
-
historyPanelEl.appendChild(thumbItem);
|
804 |
-
});
|
805 |
-
const activeThumb = historyPanelEl.querySelector('.active-history-item');
|
806 |
-
if (activeThumb) {
|
807 |
-
// Keep scrollIntoView, maybe adjust behavior if needed
|
808 |
-
activeThumb.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'center' });
|
809 |
-
}
|
810 |
-
}
|
811 |
-
|
812 |
-
// --- Full Screen Logic ---
|
813 |
-
function enterFullscreen(index, isFromHistory = false) {
|
814 |
-
if (!fullscreenIframeEl || !fullscreenOverlayEl || !document.body || !fullscreenHistoryNavEl || !historyNavPrevBtnEl || !historyNavNextBtnEl) {
|
815 |
-
console.error("Cannot enter fullscreen: Overlay or nav elements missing.");
|
816 |
-
return;
|
817 |
-
}
|
818 |
-
|
819 |
-
let codeToDisplay;
|
820 |
-
currentFullscreenHistoryIndex = -1;
|
821 |
-
fullscreenHistoryNavEl.classList.remove('visible');
|
822 |
-
|
823 |
-
if (isFromHistory) {
|
824 |
-
if (index < 0 || index >= evolutionTimeline.length || !evolutionTimeline[index]) {
|
825 |
-
console.warn("Cannot enter fullscreen for history item", index); return;
|
826 |
-
}
|
827 |
-
codeToDisplay = evolutionTimeline[index].code;
|
828 |
-
currentFullscreenHistoryIndex = index;
|
829 |
-
|
830 |
-
fullscreenHistoryNavEl.classList.add('visible');
|
831 |
-
historyNavPrevBtnEl.disabled = (currentFullscreenHistoryIndex <= 0);
|
832 |
-
historyNavNextBtnEl.disabled = (currentFullscreenHistoryIndex >= evolutionTimeline.length - 1);
|
833 |
-
|
834 |
-
} else {
|
835 |
-
if (index < 0 || index >= numVariationsToGenerate || !generatedCode[index]) {
|
836 |
-
console.warn("Cannot enter fullscreen for variation grid item", index); return;
|
837 |
-
}
|
838 |
-
codeToDisplay = generatedCode[index];
|
839 |
-
}
|
840 |
-
|
841 |
-
fullscreenIframeEl.srcdoc = codeToDisplay;
|
842 |
-
fullscreenOverlayEl.classList.add('visible');
|
843 |
-
document.body.classList.add('fullscreen-active');
|
844 |
-
document.documentElement.style.overflow = 'hidden';
|
845 |
-
}
|
846 |
-
function exitFullscreen() {
|
847 |
-
if (!fullscreenOverlayEl || !document.body || !fullscreenIframeEl || !fullscreenHistoryNavEl) {
|
848 |
-
console.error("Cannot exit fullscreen: Overlay or nav elements missing.");
|
849 |
-
return;
|
850 |
-
}
|
851 |
-
fullscreenOverlayEl.classList.remove('visible');
|
852 |
-
document.body.classList.remove('fullscreen-active');
|
853 |
-
document.documentElement.style.overflow = '';
|
854 |
-
currentFullscreenHistoryIndex = -1;
|
855 |
-
fullscreenHistoryNavEl.classList.remove('visible');
|
856 |
-
setTimeout(() => { if (fullscreenIframeEl) fullscreenIframeEl.srcdoc = 'about:blank'; }, 300);
|
857 |
-
}
|
858 |
-
|
859 |
-
function showPreviousHistoryInFullscreen() {
|
860 |
-
if (currentFullscreenHistoryIndex > 0) {
|
861 |
-
currentFullscreenHistoryIndex--;
|
862 |
-
activeTimelineIndex = currentFullscreenHistoryIndex;
|
863 |
-
enterFullscreen(activeTimelineIndex, true);
|
864 |
-
renderHistoryPanel();
|
865 |
-
const historyEntry = evolutionTimeline[activeTimelineIndex];
|
866 |
-
if (historyEntry && codeOutputEl) {
|
867 |
-
codeOutputEl.textContent = historyEntry.code;
|
868 |
-
if(selectedCodeTitleH3El) selectedCodeTitleH3El.textContent = `Code (Evolution ${activeTimelineIndex + 1} - Historical):`;
|
869 |
-
}
|
870 |
-
}
|
871 |
-
}
|
872 |
-
|
873 |
-
function showNextHistoryInFullscreen() {
|
874 |
-
if (currentFullscreenHistoryIndex < evolutionTimeline.length - 1) {
|
875 |
-
currentFullscreenHistoryIndex++;
|
876 |
-
activeTimelineIndex = currentFullscreenHistoryIndex;
|
877 |
-
enterFullscreen(activeTimelineIndex, true);
|
878 |
-
renderHistoryPanel();
|
879 |
-
const historyEntry = evolutionTimeline[activeTimelineIndex];
|
880 |
-
if (historyEntry && codeOutputEl) {
|
881 |
-
codeOutputEl.textContent = historyEntry.code;
|
882 |
-
if(selectedCodeTitleH3El) selectedCodeTitleH3El.textContent = `Code (Evolution ${activeTimelineIndex + 1} - Historical):`;
|
883 |
-
}
|
884 |
-
}
|
885 |
-
}
|
886 |
-
|
887 |
-
// --- Prompt Modal Logic ---
|
888 |
-
function showPromptModal() {
|
889 |
-
if (!promptModalOverlayEl || !modalUserPromptEl || !modalRefinementCheckboxEl || !numVariationsSliderEl || !modalThinkingBudgetSliderEl || !modalThinkingBudgetValueDisplayEl) return;
|
890 |
-
|
891 |
-
modalRefinementCheckboxEl.checked = (activeTimelineIndex !== -1);
|
892 |
-
numVariationsSliderEl.value = lastGenerationConfig.numVariations;
|
893 |
-
if(numVariationsValueDisplayEl) numVariationsValueDisplayEl.textContent = numVariationsSliderEl.value;
|
894 |
-
|
895 |
-
modalThinkingBudgetSliderEl.value = lastGenerationConfig.thinkingBudget;
|
896 |
-
if(modalThinkingBudgetValueDisplayEl) modalThinkingBudgetValueDisplayEl.textContent = lastGenerationConfig.thinkingBudget;
|
897 |
-
|
898 |
-
// Animation handling
|
899 |
-
promptModalOverlayEl.classList.remove('modal-anim-fade-out');
|
900 |
-
promptModalOverlayEl.style.display = 'flex'; // Or 'block' if that was its original display type
|
901 |
-
promptModalOverlayEl.classList.add('modal-anim-fade-in');
|
902 |
-
// promptModalOverlayEl.classList.add('visible'); // Keep this if it controls other things besides opacity/visibility
|
903 |
-
|
904 |
-
modalUserPromptEl.focus();
|
905 |
-
}
|
906 |
-
|
907 |
-
function hidePromptModal() {
|
908 |
-
if (!promptModalOverlayEl) return;
|
909 |
-
|
910 |
-
promptModalOverlayEl.classList.remove('modal-anim-fade-in');
|
911 |
-
promptModalOverlayEl.classList.add('modal-anim-fade-out');
|
912 |
-
|
913 |
-
// Wait for animation to finish before hiding
|
914 |
-
const handleAnimationEnd = () => {
|
915 |
-
promptModalOverlayEl.style.display = 'none';
|
916 |
-
// promptModalOverlayEl.classList.remove('visible'); // Keep this if it controls other things
|
917 |
-
promptModalOverlayEl.removeEventListener('animationend', handleAnimationEnd);
|
918 |
-
};
|
919 |
-
promptModalOverlayEl.addEventListener('animationend', handleAnimationEnd);
|
920 |
-
}
|
921 |
-
|
922 |
-
function handleModalGenerate() {
|
923 |
-
if (!modalUserPromptEl || !modalGenerateBtnEl) return;
|
924 |
-
const modalPrompt = modalUserPromptEl.value.trim();
|
925 |
-
if (modalPrompt) {
|
926 |
-
hidePromptModal();
|
927 |
-
if (!modalGenerateBtnEl.disabled) {
|
928 |
-
generateVariations();
|
929 |
-
}
|
930 |
-
} else {
|
931 |
-
console.warn("Modal prompt is empty. Not generating.");
|
932 |
-
}
|
933 |
-
}
|
934 |
-
|
935 |
-
// --- Config Modal Logic ---
|
936 |
-
function showConfigModal() {
|
937 |
-
if (!configModalOverlayEl) return;
|
938 |
-
|
939 |
-
// Animation handling
|
940 |
-
configModalOverlayEl.classList.remove('modal-anim-fade-out');
|
941 |
-
configModalOverlayEl.style.display = 'flex'; // Or 'block'
|
942 |
-
configModalOverlayEl.classList.add('modal-anim-fade-in');
|
943 |
-
// configModalOverlayEl.classList.add('visible');
|
944 |
-
}
|
945 |
-
function hideConfigModal() {
|
946 |
-
if (!configModalOverlayEl) return;
|
947 |
-
|
948 |
-
configModalOverlayEl.classList.remove('modal-anim-fade-in');
|
949 |
-
configModalOverlayEl.classList.add('modal-anim-fade-out');
|
950 |
-
|
951 |
-
const handleAnimationEnd = () => {
|
952 |
-
configModalOverlayEl.style.display = 'none';
|
953 |
-
// configModalOverlayEl.classList.remove('visible');
|
954 |
-
configModalOverlayEl.removeEventListener('animationend', handleAnimationEnd);
|
955 |
-
};
|
956 |
-
configModalOverlayEl.addEventListener('animationend', handleAnimationEnd);
|
957 |
-
}
|
958 |
-
|
959 |
-
// --- Confirmation Modal Logic ---
|
960 |
-
function showConfirmModal(message, onConfirmCallback) {
|
961 |
-
if (!confirmModalOverlayEl || !confirmModalMessageEl || !confirmModalConfirmBtnEl || !confirmModalCancelBtnEl) {
|
962 |
-
console.error("Confirmation modal elements not found!");
|
963 |
-
// Fallback to default confirm if modal elements are missing
|
964 |
-
if (confirm(message)) {
|
965 |
-
onConfirmCallback();
|
966 |
-
}
|
967 |
-
return;
|
968 |
-
}
|
969 |
-
|
970 |
-
confirmModalMessageEl.textContent = message;
|
971 |
-
currentConfirmCallback = onConfirmCallback; // Store the callback
|
972 |
-
|
973 |
-
// Clear previous listeners (important!)
|
974 |
-
confirmModalConfirmBtnEl.replaceWith(confirmModalConfirmBtnEl.cloneNode(true));
|
975 |
-
confirmModalCancelBtnEl.replaceWith(confirmModalCancelBtnEl.cloneNode(true));
|
976 |
-
// Re-find buttons after cloning
|
977 |
-
confirmModalConfirmBtnEl = document.getElementById('confirm-modal-confirm-button');
|
978 |
-
confirmModalCancelBtnEl = document.getElementById('confirm-modal-cancel-button');
|
979 |
-
|
980 |
-
// Add new listeners
|
981 |
-
confirmModalConfirmBtnEl.addEventListener('click', handleConfirm);
|
982 |
-
confirmModalCancelBtnEl.addEventListener('click', hideConfirmModal);
|
983 |
-
|
984 |
-
// Show modal with animation
|
985 |
-
confirmModalOverlayEl.classList.remove('modal-anim-fade-out');
|
986 |
-
confirmModalOverlayEl.style.display = 'flex';
|
987 |
-
confirmModalOverlayEl.classList.add('modal-anim-fade-in');
|
988 |
-
}
|
989 |
-
|
990 |
-
function hideConfirmModal() {
|
991 |
-
if (!confirmModalOverlayEl) return;
|
992 |
-
|
993 |
-
confirmModalOverlayEl.classList.remove('modal-anim-fade-in');
|
994 |
-
confirmModalOverlayEl.classList.add('modal-anim-fade-out');
|
995 |
-
|
996 |
-
const handleAnimationEnd = () => {
|
997 |
-
confirmModalOverlayEl.style.display = 'none';
|
998 |
-
currentConfirmCallback = null; // Clear callback when hidden
|
999 |
-
confirmModalOverlayEl.removeEventListener('animationend', handleAnimationEnd);
|
1000 |
-
};
|
1001 |
-
confirmModalOverlayEl.addEventListener('animationend', handleAnimationEnd);
|
1002 |
-
}
|
1003 |
-
|
1004 |
-
function handleConfirm() {
|
1005 |
-
hideConfirmModal();
|
1006 |
-
if (typeof currentConfirmCallback === 'function') {
|
1007 |
-
currentConfirmCallback(); // Execute the stored callback
|
1008 |
-
}
|
1009 |
-
currentConfirmCallback = null; // Clear callback after execution
|
1010 |
-
}
|
1011 |
-
|
1012 |
-
// --- History Navigation Logic ---
|
1013 |
-
function navigateToPreviousHistory() {
|
1014 |
-
if (activeTimelineIndex > 0) {
|
1015 |
-
handleHistoryItemClick(activeTimelineIndex - 1);
|
1016 |
-
}
|
1017 |
-
}
|
1018 |
-
|
1019 |
-
function navigateToNextHistory() {
|
1020 |
-
if (activeTimelineIndex < evolutionTimeline.length - 1) {
|
1021 |
-
handleHistoryItemClick(activeTimelineIndex + 1);
|
1022 |
-
}
|
1023 |
-
}
|
1024 |
-
|
1025 |
-
function updateHistoryNavigationButtons() {
|
1026 |
-
if (!historyNavLeftBtnEl || !historyNavRightBtnEl) return;
|
1027 |
-
historyNavLeftBtnEl.disabled = activeTimelineIndex <= 0;
|
1028 |
-
historyNavRightBtnEl.disabled = activeTimelineIndex >= evolutionTimeline.length - 1;
|
1029 |
-
}
|
1030 |
-
|
1031 |
-
// --- Helper function to call Gemini for code splitting ---
|
1032 |
-
async function fetchCodeSplitFromGemini(apiKey, fullHtmlContent) {
|
1033 |
-
const exportModelName = "gemini-2.5-flash-preview-04-17"; // Hardcoded model for export
|
1034 |
-
const API_ENDPOINT = `${API_BASE_URL}${exportModelName}:generateContent?key=${apiKey}`;
|
1035 |
-
const prompt = `
|
1036 |
-
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.
|
1037 |
-
|
1038 |
-
Follow these instructions carefully:
|
1039 |
-
1. **HTML Output**: This should be the main HTML structure.
|
1040 |
-
* If there were inline <style> tags, remove them. Add a <link rel="stylesheet" href="style.css"> in the <head> instead.
|
1041 |
-
* 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.
|
1042 |
-
* Ensure the HTML output is clean and well-formed.
|
1043 |
-
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.
|
1044 |
-
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.
|
1045 |
-
|
1046 |
-
Provide the output STRICTLY as a JSON object with the following keys: "html_code", "css_code", "js_code".
|
1047 |
-
|
1048 |
-
Example of desired JSON output format:
|
1049 |
-
{
|
1050 |
-
"html_code": "<!DOCTYPE html>...<link rel=\\\"stylesheet\\\" href=\\\"style.css\\\"><script src=\\\"script.js\\\" defer><\\/script></body></html>",
|
1051 |
-
"css_code": "body { font-family: sans-serif; } ...",
|
1052 |
-
"js_code": "console.log(\\\'Hello World!\\\'); ...\"
|
1053 |
-
}
|
1054 |
-
|
1055 |
-
Original HTML content:
|
1056 |
-
\`\`\`html
|
1057 |
-
${fullHtmlContent}\n\`\`\`
|
1058 |
-
|
1059 |
-
Return ONLY the JSON object. Do not include any other explanatory text or markdown formatting outside the JSON structure itself.
|
1060 |
-
`;
|
1061 |
-
|
1062 |
-
try {
|
1063 |
-
const response = await fetch(API_ENDPOINT, {
|
1064 |
-
method: 'POST',
|
1065 |
-
headers: { 'Content-Type': 'application/json' },
|
1066 |
-
body: JSON.stringify({ contents: [{ parts: [{ text: prompt }] }] })
|
1067 |
-
});
|
1068 |
-
|
1069 |
-
if (!response.ok) {
|
1070 |
-
const errorData = await response.json().catch(() => null);
|
1071 |
-
const errorMsg = errorData?.error?.message || `HTTP Error: ${response.status}`;
|
1072 |
-
console.error('Gemini API Error:', errorMsg);
|
1073 |
-
throw new Error(`API Error: ${errorMsg}`);
|
1074 |
-
}
|
1075 |
-
|
1076 |
-
const responseData = await response.json();
|
1077 |
-
const candidate = responseData.candidates?.[0];
|
1078 |
-
if (candidate?.content?.parts?.[0]?.text) {
|
1079 |
-
let rawText = candidate.content.parts[0].text;
|
1080 |
-
console.log("Raw response from Gemini for splitting:", rawText); // Log the raw response
|
1081 |
-
|
1082 |
-
let jsonString = null;
|
1083 |
-
|
1084 |
-
// Attempt 1: Look for JSON within markdown code blocks
|
1085 |
-
const markdownJsonMatch = rawText.match(/```json\n(\{[\s\S]*\})\n```/s) || rawText.match(/```\n(\{[\s\S]*\})\n```/s);
|
1086 |
-
if (markdownJsonMatch && markdownJsonMatch[1]) {
|
1087 |
-
jsonString = markdownJsonMatch[1];
|
1088 |
-
} else {
|
1089 |
-
// Attempt 2: Fallback to existing regex if no markdown block found or it's malformed
|
1090 |
-
const directJsonMatch = rawText.match(/\{.*\}/s);
|
1091 |
-
if (directJsonMatch) {
|
1092 |
-
jsonString = directJsonMatch[0];
|
1093 |
-
}
|
1094 |
-
}
|
1095 |
-
|
1096 |
-
if (jsonString) {
|
1097 |
-
try {
|
1098 |
-
return JSON.parse(jsonString);
|
1099 |
-
} catch (parseError) {
|
1100 |
-
console.error("Failed to parse JSON string from model:", jsonString, parseError);
|
1101 |
-
throw new Error("JSON parsing failed after attempting to clean model response."); // Simplified error
|
1102 |
-
}
|
1103 |
-
}
|
1104 |
-
throw new Error("Clean JSON object not found in model's response after attempting to extract.");
|
1105 |
-
}
|
1106 |
-
throw new Error("No valid content found in model's response.");
|
1107 |
-
|
1108 |
-
} catch (error) {
|
1109 |
-
console.error('Error fetching or parsing split code:', error);
|
1110 |
-
throw error; // Re-throw to be caught by caller
|
1111 |
-
}
|
1112 |
-
}
|
1113 |
-
|
1114 |
-
// --- Full Prompt Display Modal Logic ---
|
1115 |
-
function showFullPromptModal(fullPrompt) {
|
1116 |
-
if (!promptDisplayModalOverlayEl || !fullPromptTextEl) return;
|
1117 |
-
|
1118 |
-
fullPromptTextEl.textContent = fullPrompt;
|
1119 |
-
|
1120 |
-
// Animation handling
|
1121 |
-
promptDisplayModalOverlayEl.classList.remove('modal-anim-fade-out');
|
1122 |
-
promptDisplayModalOverlayEl.style.display = 'flex';
|
1123 |
-
promptDisplayModalOverlayEl.classList.add('modal-anim-fade-in');
|
1124 |
-
}
|
1125 |
-
|
1126 |
-
function hideFullPromptModal() {
|
1127 |
-
if (!promptDisplayModalOverlayEl) return;
|
1128 |
-
|
1129 |
-
promptDisplayModalOverlayEl.classList.remove('modal-anim-fade-in');
|
1130 |
-
promptDisplayModalOverlayEl.classList.add('modal-anim-fade-out');
|
1131 |
-
|
1132 |
-
const handleAnimationEnd = () => {
|
1133 |
-
promptDisplayModalOverlayEl.style.display = 'none';
|
1134 |
-
promptDisplayModalOverlayEl.removeEventListener('animationend', handleAnimationEnd);
|
1135 |
-
};
|
1136 |
-
promptDisplayModalOverlayEl.addEventListener('animationend', handleAnimationEnd);
|
1137 |
-
}
|
1138 |
-
|
1139 |
-
// --- Initialization ---
|
1140 |
-
document.addEventListener('DOMContentLoaded', () => {
|
1141 |
-
console.log("DOMContentLoaded event fired.");
|
1142 |
-
|
1143 |
-
apiKeyEl = document.getElementById('api-key');
|
1144 |
-
modelSelEl = document.getElementById('model-select');
|
1145 |
-
const codeOutputPre = document.getElementById('code-output');
|
1146 |
-
if (codeOutputPre) { codeOutputEl = codeOutputPre.querySelector('code'); } else { console.error("Code output <pre> not found");}
|
1147 |
-
errorMessageEl = document.getElementById('error-message');
|
1148 |
-
refinementLoadingIndicator = document.getElementById('refinement-loading-indicator');
|
1149 |
-
mainContentEl = document.getElementById('main-content');
|
1150 |
-
configButtonEl = document.getElementById('config-button');
|
1151 |
-
intervalSliderEl = document.getElementById('preview-interval-slider');
|
1152 |
-
intervalValueDisplayEl = document.getElementById('interval-value');
|
1153 |
-
fullscreenOverlayEl = document.getElementById('fullscreen-overlay');
|
1154 |
-
fullscreenIframeEl = document.getElementById('fullscreen-iframe');
|
1155 |
-
exitFullscreenBtnEl = document.getElementById('exit-fullscreen-btn');
|
1156 |
-
perspectiveViewportEl = document.getElementById('perspective-viewport');
|
1157 |
-
previewGridWrapperEl = document.getElementById('preview-grid-wrapper');
|
1158 |
-
historyPanelEl = document.getElementById('history-panel');
|
1159 |
-
historyPanelPlaceholderEl = document.getElementById('history-panel-placeholder');
|
1160 |
-
selectedCodeTitleH3El = document.getElementById('selected-code-title');
|
1161 |
-
mainContentTitleH1El = document.getElementById('main-content-title');
|
1162 |
-
mainContentSubtitleH2El = document.getElementById('main-content-subtitle');
|
1163 |
-
fullscreenHistoryNavEl = document.getElementById('fullscreen-history-nav');
|
1164 |
-
historyNavPrevBtnEl = document.getElementById('history-nav-prev');
|
1165 |
-
historyNavNextBtnEl = document.getElementById('history-nav-next');
|
1166 |
-
promptModalOverlayEl = document.getElementById('prompt-modal-overlay');
|
1167 |
-
promptModalContentEl = document.getElementById('prompt-modal-content');
|
1168 |
-
modalUserPromptEl = document.getElementById('modal-user-prompt');
|
1169 |
-
modalGenerateBtnEl = document.getElementById('modal-generate-button');
|
1170 |
-
modalCancelBtnEl = document.getElementById('modal-cancel-button');
|
1171 |
-
modalLoadingIndicatorEl = document.getElementById('modal-loading-indicator');
|
1172 |
-
modalRefinementCheckboxEl = document.getElementById('modal-refinement-checkbox');
|
1173 |
-
numVariationsSliderEl = document.getElementById('num-variations-slider');
|
1174 |
-
numVariationsValueDisplayEl = document.getElementById('num-variations-value');
|
1175 |
-
configModalOverlayEl = document.getElementById('config-modal-overlay');
|
1176 |
-
configModalContentEl = document.getElementById('config-modal-content');
|
1177 |
-
configModalCloseBtnEl = document.getElementById('config-modal-close-button');
|
1178 |
-
copyCodeButtonEl = document.getElementById('copy-code-button');
|
1179 |
-
exportCodeButtonEl = document.getElementById('export-code-button');
|
1180 |
-
historyToggleButtonEl = document.getElementById('history-toggle-button');
|
1181 |
-
historyArrowDownEl = document.getElementById('history-arrow-down');
|
1182 |
-
historyArrowUpEl = document.getElementById('history-arrow-up');
|
1183 |
-
newButtonEl = document.getElementById('new-button');
|
1184 |
-
confirmModalOverlayEl = document.getElementById('confirm-modal-overlay');
|
1185 |
-
confirmModalMessageEl = document.getElementById('confirm-modal-message');
|
1186 |
-
confirmModalConfirmBtnEl = document.getElementById('confirm-modal-confirm-button');
|
1187 |
-
confirmModalCancelBtnEl = document.getElementById('confirm-modal-cancel-button');
|
1188 |
-
historyNavLeftBtnEl = document.getElementById('history-nav-left-button');
|
1189 |
-
historyNavRightBtnEl = document.getElementById('history-nav-right-button');
|
1190 |
-
modalThinkingBudgetSliderEl = document.getElementById('modal-thinking-budget-slider');
|
1191 |
-
modalThinkingBudgetValueDisplayEl = document.getElementById('modal-thinking-budget-value');
|
1192 |
-
promptDisplayModalOverlayEl = document.getElementById('prompt-display-modal-overlay');
|
1193 |
-
promptDisplayModalContentEl = document.getElementById('prompt-display-modal-content');
|
1194 |
-
fullPromptTextEl = document.getElementById('full-prompt-text');
|
1195 |
-
promptDisplayModalCloseBtnEl = document.getElementById('prompt-display-modal-close-button');
|
1196 |
-
|
1197 |
-
|
1198 |
-
// --- Check if all required elements exist ---
|
1199 |
-
let missingElements = [];
|
1200 |
-
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 };
|
1201 |
-
for (const key in requiredElements) { if (!requiredElements[key]) { missingElements.push(key); } }
|
1202 |
-
|
1203 |
-
if (missingElements.length > 0) {
|
1204 |
-
console.error("Initialization Error: Critical elements missing!", missingElements);
|
1205 |
-
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>`; }
|
1206 |
-
else { alert(`Initialization Error: Critical elements missing: ${missingElements.join(', ')}.`); }
|
1207 |
-
return;
|
1208 |
-
}
|
1209 |
-
|
1210 |
-
// --- Initial UI Setup ---
|
1211 |
-
showInitialPreviewStateUI();
|
1212 |
-
renderHistoryPanel();
|
1213 |
-
updateHistoryNavigationButtons(); // Initial button state
|
1214 |
-
|
1215 |
-
// --- Event Listeners ---
|
1216 |
-
if (newButtonEl) {
|
1217 |
-
newButtonEl.addEventListener('click', () => {
|
1218 |
-
showConfirmModal(
|
1219 |
-
'Start a new session? This will clear the current state.',
|
1220 |
-
() => { location.reload(); } // Pass the reload logic as the callback
|
1221 |
-
);
|
1222 |
-
});
|
1223 |
-
}
|
1224 |
-
|
1225 |
-
if (configButtonEl) configButtonEl.addEventListener('click', showConfigModal);
|
1226 |
-
if (configModalCloseBtnEl) configModalCloseBtnEl.addEventListener('click', hideConfigModal);
|
1227 |
-
if (configModalOverlayEl) configModalOverlayEl.addEventListener('click', (e) => {
|
1228 |
-
if (e.target === configModalOverlayEl) { hideConfigModal(); }
|
1229 |
-
});
|
1230 |
-
|
1231 |
-
if (exitFullscreenBtnEl) exitFullscreenBtnEl.addEventListener('click', exitFullscreen);
|
1232 |
-
if (historyNavPrevBtnEl) historyNavPrevBtnEl.addEventListener('click', showPreviousHistoryInFullscreen);
|
1233 |
-
if (historyNavNextBtnEl) historyNavNextBtnEl.addEventListener('click', showNextHistoryInFullscreen);
|
1234 |
-
|
1235 |
-
if (modalGenerateBtnEl) modalGenerateBtnEl.addEventListener('click', handleModalGenerate);
|
1236 |
-
if (modalCancelBtnEl) modalCancelBtnEl.addEventListener('click', hidePromptModal);
|
1237 |
-
if (promptModalOverlayEl) promptModalOverlayEl.addEventListener('click', (e) => {
|
1238 |
-
if (e.target === promptModalOverlayEl) { hidePromptModal(); }
|
1239 |
-
});
|
1240 |
-
if (confirmModalOverlayEl) { // Add overlay click listener for confirm modal
|
1241 |
-
confirmModalOverlayEl.addEventListener('click', (e) => {
|
1242 |
-
if (e.target === confirmModalOverlayEl) { hideConfirmModal(); }
|
1243 |
-
});
|
1244 |
-
}
|
1245 |
-
if (promptDisplayModalOverlayEl) { // Listener for full prompt display modal overlay click
|
1246 |
-
promptDisplayModalOverlayEl.addEventListener('click', (e) => {
|
1247 |
-
if (e.target === promptDisplayModalOverlayEl) { hideFullPromptModal(); }
|
1248 |
-
});
|
1249 |
-
}
|
1250 |
-
if (promptDisplayModalCloseBtnEl) { // Listener for full prompt display modal close button
|
1251 |
-
promptDisplayModalCloseBtnEl.addEventListener('click', hideFullPromptModal);
|
1252 |
-
}
|
1253 |
-
if (mainContentSubtitleH2El) { // Listener for clicking the subtitle
|
1254 |
-
mainContentSubtitleH2El.addEventListener('click', (e) => {
|
1255 |
-
const fullPrompt = e.target.dataset.fullPrompt;
|
1256 |
-
if (fullPrompt) { // Check if the data attribute exists (meaning it was truncated)
|
1257 |
-
showFullPromptModal(fullPrompt);
|
1258 |
-
}
|
1259 |
-
});
|
1260 |
-
}
|
1261 |
-
if (modalUserPromptEl) modalUserPromptEl.addEventListener('keydown', (event) => {
|
1262 |
-
if (event.key === 'Enter' && (event.ctrlKey || event.metaKey)) {
|
1263 |
-
event.preventDefault(); handleModalGenerate();
|
1264 |
-
}
|
1265 |
-
});
|
1266 |
-
if (numVariationsSliderEl) {
|
1267 |
-
numVariationsSliderEl.addEventListener('input', (event) => {
|
1268 |
-
if(numVariationsValueDisplayEl) numVariationsValueDisplayEl.textContent = event.target.value;
|
1269 |
-
});
|
1270 |
-
if(numVariationsValueDisplayEl) numVariationsValueDisplayEl.textContent = numVariationsSliderEl.value;
|
1271 |
-
}
|
1272 |
-
|
1273 |
-
if (modalThinkingBudgetSliderEl) {
|
1274 |
-
modalThinkingBudgetSliderEl.addEventListener('input', (event) => {
|
1275 |
-
if(modalThinkingBudgetValueDisplayEl) modalThinkingBudgetValueDisplayEl.textContent = event.target.value;
|
1276 |
-
});
|
1277 |
-
if(modalThinkingBudgetValueDisplayEl) modalThinkingBudgetValueDisplayEl.textContent = modalThinkingBudgetSliderEl.value;
|
1278 |
-
}
|
1279 |
-
|
1280 |
-
if (intervalSliderEl) {
|
1281 |
-
intervalSliderEl.addEventListener('input', (event) => {
|
1282 |
-
previewUpdateInterval = parseInt(event.target.value, 10);
|
1283 |
-
if(intervalValueDisplayEl) intervalValueDisplayEl.textContent = previewUpdateInterval;
|
1284 |
-
});
|
1285 |
-
previewUpdateInterval = parseInt(intervalSliderEl.value, 10);
|
1286 |
-
if(intervalValueDisplayEl) intervalValueDisplayEl.textContent = previewUpdateInterval;
|
1287 |
-
}
|
1288 |
-
|
1289 |
-
if (copyCodeButtonEl) {
|
1290 |
-
copyCodeButtonEl.addEventListener('click', () => {
|
1291 |
-
if (codeOutputEl && codeOutputEl.textContent && codeOutputEl.textContent !== '// Select a variation or history item to view its code.') {
|
1292 |
-
const textToCopy = codeOutputEl.textContent;
|
1293 |
-
const textArea = document.createElement("textarea");
|
1294 |
-
textArea.value = textToCopy;
|
1295 |
-
textArea.style.position = "fixed"; // Prevent scrolling to bottom
|
1296 |
-
document.body.appendChild(textArea);
|
1297 |
-
textArea.focus();
|
1298 |
-
textArea.select();
|
1299 |
-
try {
|
1300 |
-
const successful = document.execCommand('copy');
|
1301 |
-
if (successful) {
|
1302 |
-
const originalText = copyCodeButtonEl.innerHTML;
|
1303 |
-
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!`;
|
1304 |
-
copyCodeButtonEl.classList.add('copied');
|
1305 |
-
setTimeout(() => {
|
1306 |
-
copyCodeButtonEl.innerHTML = originalText;
|
1307 |
-
copyCodeButtonEl.classList.remove('copied');
|
1308 |
-
}, 2000);
|
1309 |
-
} else {
|
1310 |
-
console.error('Fallback: Failed to copy code using execCommand.');
|
1311 |
-
}
|
1312 |
-
} catch (err) {
|
1313 |
-
console.error('Fallback: Error copying code using execCommand: ', err);
|
1314 |
-
}
|
1315 |
-
document.body.removeChild(textArea);
|
1316 |
-
}
|
1317 |
-
});
|
1318 |
-
}
|
1319 |
-
|
1320 |
-
if (exportCodeButtonEl) {
|
1321 |
-
exportCodeButtonEl.addEventListener('click', async () => {
|
1322 |
-
const currentCode = codeOutputEl?.textContent;
|
1323 |
-
const apiKey = apiKeyEl?.value;
|
1324 |
-
|
1325 |
-
if (!currentCode || currentCode.startsWith('//') || currentCode.trim() === '') {
|
1326 |
-
alert('No code selected or available to export.');
|
1327 |
-
return;
|
1328 |
-
}
|
1329 |
-
if (!apiKey) {
|
1330 |
-
alert('API Key is missing. Please configure it in settings.');
|
1331 |
-
showConfigModal(); // Show config modal if API key is missing
|
1332 |
-
return;
|
1333 |
-
}
|
1334 |
-
if (typeof JSZip === 'undefined') {
|
1335 |
-
alert('JSZip library is not loaded. Export cannot proceed.');
|
1336 |
-
console.error("JSZip is not defined!");
|
1337 |
-
return;
|
1338 |
-
}
|
1339 |
-
|
1340 |
-
const originalButtonContent = exportCodeButtonEl.innerHTML;
|
1341 |
-
exportCodeButtonEl.disabled = true;
|
1342 |
-
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...';
|
1343 |
-
|
1344 |
-
try {
|
1345 |
-
const splitCode = await fetchCodeSplitFromGemini(apiKey, currentCode);
|
1346 |
|
1347 |
-
|
1348 |
-
|
1349 |
-
|
1350 |
-
|
1351 |
-
|
1352 |
-
zip.file("index.html", splitCode.html_code);
|
1353 |
-
zip.file("style.css", splitCode.css_code);
|
1354 |
-
zip.file("script.js", splitCode.js_code);
|
1355 |
-
|
1356 |
-
const zipBlob = await zip.generateAsync({ type: "blob" });
|
1357 |
|
1358 |
-
|
1359 |
-
|
1360 |
-
|
1361 |
-
|
1362 |
-
|
1363 |
-
|
1364 |
-
|
1365 |
-
|
1366 |
-
|
1367 |
-
|
1368 |
-
|
1369 |
-
|
1370 |
-
|
1371 |
-
|
1372 |
-
|
1373 |
-
|
1374 |
-
|
1375 |
-
|
1376 |
-
|
1377 |
-
|
1378 |
-
|
1379 |
-
|
1380 |
-
|
1381 |
-
|
1382 |
-
|
1383 |
-
|
1384 |
-
|
1385 |
-
|
1386 |
-
|
1387 |
-
|
1388 |
-
|
1389 |
-
|
1390 |
-
|
1391 |
-
|
1392 |
-
|
1393 |
-
|
1394 |
-
|
1395 |
-
|
1396 |
-
|
1397 |
-
|
1398 |
-
|
1399 |
-
|
1400 |
-
|
1401 |
-
|
1402 |
-
|
1403 |
-
|
1404 |
-
|
1405 |
-
|
1406 |
-
|
1407 |
-
// Keyboard Shortcuts Listener
|
1408 |
-
document.addEventListener('keydown', (event) => {
|
1409 |
-
if (event.key === 'Escape') {
|
1410 |
-
// Check style.display instead of .visible class for animated modals
|
1411 |
-
if (configModalOverlayEl.style.display !== 'none' && !configModalOverlayEl.classList.contains('modal-anim-fade-out')) {
|
1412 |
-
hideConfigModal();
|
1413 |
-
} else if (promptModalOverlayEl.style.display !== 'none' && !promptModalOverlayEl.classList.contains('modal-anim-fade-out')) {
|
1414 |
-
hidePromptModal();
|
1415 |
-
} else if (confirmModalOverlayEl.style.display !== 'none' && !confirmModalOverlayEl.classList.contains('modal-anim-fade-out')) { // Add Escape handler for confirm modal
|
1416 |
-
hideConfirmModal();
|
1417 |
-
} else if (promptDisplayModalOverlayEl.style.display !== 'none' && !promptDisplayModalOverlayEl.classList.contains('modal-anim-fade-out')) { // Add Escape handler for prompt display modal
|
1418 |
-
hideFullPromptModal();
|
1419 |
-
} else if (document.body.classList.contains('fullscreen-active')) {
|
1420 |
-
exitFullscreen();
|
1421 |
-
}
|
1422 |
-
}
|
1423 |
-
const targetTagName = event.target ? event.target.tagName.toLowerCase() : null;
|
1424 |
-
const isTypingInInputOrTextarea = targetTagName === 'input' || targetTagName === 'textarea';
|
1425 |
-
|
1426 |
-
if (document.body.classList.contains('fullscreen-active') && currentFullscreenHistoryIndex !== -1 && !isTypingInInputOrTextarea) {
|
1427 |
-
if (event.key.toLowerCase() === 'w') {
|
1428 |
-
event.preventDefault();
|
1429 |
-
showPreviousHistoryInFullscreen();
|
1430 |
-
} else if (event.key.toLowerCase() === 'd') {
|
1431 |
-
event.preventDefault();
|
1432 |
-
showNextHistoryInFullscreen();
|
1433 |
-
}
|
1434 |
-
}
|
1435 |
-
if (event.altKey && !isTypingInInputOrTextarea) {
|
1436 |
-
if (event.key.toLowerCase() === 'p' || event.code === 'KeyP') {
|
1437 |
-
event.preventDefault();
|
1438 |
-
if (!promptModalOverlayEl.classList.contains('visible') && !configModalOverlayEl.classList.contains('visible')) {
|
1439 |
-
showPromptModal();
|
1440 |
-
}
|
1441 |
-
}
|
1442 |
-
else if (event.key.toLowerCase() === 'r' || event.code === 'KeyR') {
|
1443 |
-
event.preventDefault();
|
1444 |
-
if (lastGenerationConfig.prompt) {
|
1445 |
-
console.log("Alt+R: Regenerating with last settings:", lastGenerationConfig);
|
1446 |
-
modalUserPromptEl.value = lastGenerationConfig.prompt;
|
1447 |
-
modalRefinementCheckboxEl.checked = lastGenerationConfig.isRefinement;
|
1448 |
-
numVariationsSliderEl.value = lastGenerationConfig.numVariations;
|
1449 |
-
if(numVariationsValueDisplayEl) numVariationsValueDisplayEl.textContent = numVariationsSliderEl.value;
|
1450 |
-
modalThinkingBudgetSliderEl.value = lastGenerationConfig.thinkingBudget;
|
1451 |
-
if(modalThinkingBudgetValueDisplayEl) modalThinkingBudgetValueDisplayEl.textContent = lastGenerationConfig.thinkingBudget;
|
1452 |
-
|
1453 |
-
if (lastGenerationConfig.isRefinement) {
|
1454 |
-
activeTimelineIndex = lastGenerationConfig.refinedTimelineIndex;
|
1455 |
-
}
|
1456 |
-
handleModalGenerate();
|
1457 |
-
} else {
|
1458 |
-
console.log("Alt+R: No last generation settings found. Opening prompt modal.");
|
1459 |
-
showPromptModal();
|
1460 |
-
}
|
1461 |
-
}
|
1462 |
-
else if (event.key.toLowerCase() === 'o' || event.code === 'KeyO') {
|
1463 |
-
event.preventDefault();
|
1464 |
-
if (!configModalOverlayEl.classList.contains('visible') && !promptModalOverlayEl.classList.contains('visible')) {
|
1465 |
-
showConfigModal();
|
1466 |
-
}
|
1467 |
-
}
|
1468 |
-
// History navigation shortcuts (non-fullscreen)
|
1469 |
-
if (event.key === 'PageUp') {
|
1470 |
-
event.preventDefault();
|
1471 |
-
navigateToPreviousHistory();
|
1472 |
-
} else if (event.key === 'PageDown') {
|
1473 |
-
event.preventDefault();
|
1474 |
-
navigateToNextHistory();
|
1475 |
-
}
|
1476 |
-
}
|
1477 |
-
});
|
1478 |
-
console.log("Initialization setup complete.");
|
1479 |
-
});
|
|
|
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>Gemini Code Generator & Preview (Config Modal)</title>
|
7 |
+
<script src="https://cdn.tailwindcss.com"></script>
|
8 |
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
|
9 |
+
<link rel="preconnect" href="https://rsms.me/">
|
10 |
+
<link rel="stylesheet" href="https://rsms.me/inter/inter.css">
|
11 |
+
<link rel="stylesheet" href="style.css">
|
12 |
+
</head>
|
13 |
+
<body class="font-sans">
|
14 |
+
<div id="top-left-controls">
|
15 |
+
<button id="new-button" aria-label="New Session" title="Start New Session (Refresh)">
|
16 |
+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-plus"><path d="M5 12h14"/><path d="M12 5v14"/></svg>
|
17 |
+
</button>
|
18 |
+
<button id="config-button" aria-label="Open Configuration" title="Open Configuration (Alt / Option + O)">
|
19 |
+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-settings-2"><path d="M20 7h-9"/><path d="M14 17H5"/><circle cx="17" cy="17" r="3"/><circle cx="7" cy="7" r="3"/></svg>
|
20 |
+
</button>
|
21 |
+
</div>
|
22 |
+
|
23 |
+
<div id="main-content" class="p-6 flex flex-col space-y-4">
|
24 |
+
<div class="text-center mt-8">
|
25 |
+
<h1 id="main-content-title" class="text-2xl font-bold text-cyan-400">Live Previews</h1>
|
26 |
+
<h2 id="main-content-subtitle" class="text-base font-semibold text-slate-300 mt-2">Powered by Gemini Models</h2>
|
27 |
+
<p class="mt-3 text-xs text-slate-500">
|
28 |
+
<kbd class="px-2 py-1.5 text-xs font-semibold text-gray-800 bg-gray-100 border border-gray-200 rounded-lg dark:bg-gray-600 dark:text-gray-100 dark:border-gray-500">Alt / Option</kbd> + <kbd class="px-2 py-1.5 text-xs font-semibold text-gray-800 bg-gray-100 border border-gray-200 rounded-lg dark:bg-gray-600 dark:text-gray-100 dark:border-gray-500">P</kbd> to open prompt.
|
29 |
+
<kbd class="px-2 py-1.5 text-xs font-semibold text-gray-800 bg-gray-100 border border-gray-200 rounded-lg dark:bg-gray-600 dark:text-gray-100 dark:border-gray-500">Alt / Option</kbd> + <kbd class="px-2 py-1.5 text-xs font-semibold text-gray-800 bg-gray-100 border border-gray-200 rounded-lg dark:bg-gray-600 dark:text-gray-100 dark:border-gray-500">R</kbd> to regenerate.
|
30 |
+
<kbd class="px-2 py-1.5 text-xs font-semibold text-gray-800 bg-gray-100 border border-gray-200 rounded-lg dark:bg-gray-600 dark:text-gray-100 dark:border-gray-500">Alt / Option</kbd> + <kbd class="px-2 py-1.5 text-xs font-semibold text-gray-800 bg-gray-100 border border-gray-200 rounded-lg dark:bg-gray-600 dark:text-gray-100 dark:border-gray-500">O</kbd> for settings.
|
31 |
+
</p>
|
32 |
+
<div id="error-message" class="mt-2 text-red-400 text-sm font-medium"></div>
|
33 |
+
</div>
|
34 |
+
|
35 |
+
<div id="perspective-viewport">
|
36 |
+
<div id="preview-grid-wrapper" class="grid-mode">
|
37 |
+
</div>
|
38 |
+
</div>
|
39 |
+
|
40 |
+
<p class="mt-2 text-xs text-slate-500 flex-shrink-0 text-center">Note: Some JS/external resources might be restricted by the sandbox.</p>
|
41 |
+
</div>
|
42 |
+
|
43 |
+
<div id="history-panel-controls">
|
44 |
+
<button id="history-nav-left-button" class="history-nav-button" aria-label="Previous History Step" title="Previous History (Alt + Page Up)" disabled>
|
45 |
+
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-left"><path d="m15 18-6-6 6-6"/></svg>
|
46 |
+
</button>
|
47 |
+
<button id="history-toggle-button" aria-label="Toggle History Panel">
|
48 |
+
<svg id="history-arrow-down" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-down hidden"><path d="m6 9 6 6 6-6"/></svg>
|
49 |
+
<svg id="history-arrow-up" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-up"><path d="m18 15-6-6-6 6"/></svg>
|
50 |
+
</button>
|
51 |
+
<button id="history-nav-right-button" class="history-nav-button" aria-label="Next History Step" title="Next History (Alt + Page Down)" disabled>
|
52 |
+
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-right"><path d="m9 18 6-6-6-6"/></svg>
|
53 |
+
</button>
|
54 |
+
</div>
|
55 |
+
|
56 |
+
<div id="history-panel" class="history-collapsed">
|
57 |
+
<div id="history-panel-placeholder">Evolution history will appear here.</div>
|
58 |
+
</div>
|
59 |
+
|
60 |
+
<div id="refinement-loading-indicator">
|
61 |
+
<span>Refining code...</span>
|
62 |
+
<svg class="animate-spin h-5 w-5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
63 |
+
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
64 |
+
<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>
|
65 |
+
</svg>
|
66 |
+
</div>
|
67 |
+
|
68 |
+
<div id="fullscreen-overlay">
|
69 |
+
<div id="fullscreen-history-nav">
|
70 |
+
<button id="history-nav-prev" title="Previous History (W)">
|
71 |
+
<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="inline-block mr-1"><polyline points="15 18 9 12 15 6"></polyline></svg>
|
72 |
+
Prev (W)
|
73 |
+
</button>
|
74 |
+
<button id="history-nav-next" title="Next History (D)">
|
75 |
+
Next (D)
|
76 |
+
<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="inline-block ml-1"><polyline points="9 18 15 12 9 6"></polyline></svg>
|
77 |
+
</button>
|
78 |
+
</div>
|
79 |
+
<button id="exit-fullscreen-btn" class="px-3 py-1 text-sm font-medium rounded hover:bg-red-600 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-1 focus:ring-offset-slate-900" title="Exit Full Screen">
|
80 |
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" class="inline-block mr-1 -mt-px"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>
|
81 |
+
Exit
|
82 |
+
</button>
|
83 |
+
<iframe id="fullscreen-iframe" title="Full Screen Preview"></iframe>
|
84 |
+
</div>
|
85 |
+
|
86 |
+
<div id="prompt-modal-overlay" class="modal-overlay">
|
87 |
+
<div id="prompt-modal-content" class="modal-content">
|
88 |
+
<h3 class="text-xl font-semibold text-cyan-300">Enter Prompt (Alt+P)</h3>
|
89 |
+
<textarea id="modal-user-prompt" rows="6" class="futuristic-input block w-full px-3 py-2 sm:text-sm" placeholder="Describe the web page/app to build or refine..."></textarea>
|
90 |
+
|
91 |
+
<div class="flex items-center mt-2">
|
92 |
+
<input id="modal-refinement-checkbox" name="modal-refinement-checkbox" type="checkbox" class="h-4 w-4 text-cyan-500 focus:ring-cyan-400 border-slate-600 rounded bg-slate-700 focus:ring-offset-slate-800">
|
93 |
+
<label for="modal-refinement-checkbox" class="ml-2 block text-sm text-slate-200">Use prompt to refine active evolution</label>
|
94 |
+
</div>
|
95 |
+
|
96 |
+
<div class="mt-2">
|
97 |
+
<label for="num-variations-slider" class="block text-sm font-medium text-slate-300 mb-1">Number of Variations: <span id="num-variations-value">4</span></label>
|
98 |
+
<input type="range" id="num-variations-slider" name="num-variations-slider" min="1" max="4" step="1" value="4" class="futuristic-slider w-full h-2 rounded-lg appearance-none cursor-pointer">
|
99 |
+
</div>
|
100 |
+
|
101 |
+
<div class="mt-2">
|
102 |
+
<label for="model-select" class="block text-sm font-medium text-slate-300 mb-1">Select Model:</label>
|
103 |
+
<select id="model-select" name="model-select" class="futuristic-select block w-full sm:text-sm">
|
104 |
+
<option value="gemini-2.5-pro-preview-05-06">gemini-2.5-pro-preview-05-06</option>
|
105 |
+
<option value="gemini-2.5-flash-preview-04-17">gemini-2.5-flash-preview-04-17</option>
|
106 |
+
<option value="gemini-2.0-flash">gemini-2.0-flash</option>
|
107 |
+
<option value="gemini-2.0-flash-lite">gemini-2.0-flash-lite</option>
|
108 |
+
</select>
|
109 |
+
</div>
|
110 |
+
|
111 |
+
<div class="mt-3">
|
112 |
+
<label for="modal-thinking-budget-slider" class="block text-sm font-medium text-slate-300 mb-1">Thinking Budget: <span id="modal-thinking-budget-value">0</span></label>
|
113 |
+
<input type="range" id="modal-thinking-budget-slider" name="modal-thinking-budget-slider" min="0" max="24576" step="1024" value="0" class="futuristic-slider w-full h-2 rounded-lg appearance-none cursor-pointer">
|
114 |
+
<p class="mt-1 text-xs text-slate-400">Set to 0 to disable. Higher values may improve quality for complex prompts but increase latency.</p>
|
115 |
+
</div>
|
116 |
+
|
117 |
+
<p class="text-xs text-slate-400 mt-1">Use Ctrl/Cmd+Enter to generate, Esc to close.</p>
|
118 |
+
<div class="flex justify-end gap-3 mt-2">
|
119 |
+
<button id="modal-cancel-button" class="futuristic-button modal-button-secondary px-4 py-2 text-sm">Cancel</button>
|
120 |
+
<button id="modal-generate-button" class="futuristic-button px-4 py-2 text-sm">
|
121 |
+
Generate
|
122 |
+
<svg id="modal-loading-indicator" class="animate-spin -mr-1 ml-3 h-5 w-5 text-white hidden" 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>
|
123 |
+
</button>
|
124 |
+
</div>
|
125 |
+
</div>
|
126 |
+
</div>
|
127 |
+
|
128 |
+
<div id="config-modal-overlay" class="modal-overlay">
|
129 |
+
<div id="config-modal-content" class="modal-content">
|
130 |
+
<h3 class="text-xl font-semibold text-cyan-300">Configuration (Alt+O)</h3>
|
131 |
+
|
132 |
+
<div class="space-y-4">
|
133 |
+
<div>
|
134 |
+
<label for="api-key" class="block text-sm font-medium text-slate-300 mb-1">Gemini API Key (AI Studio):</label>
|
135 |
+
<input type="password" id="api-key" name="api-key" class="futuristic-input block w-full sm:text-sm" placeholder="Enter your API Key">
|
136 |
+
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
137 |
|
138 |
+
<div class="pt-1">
|
139 |
+
<label for="preview-interval-slider" class="block text-sm font-medium text-slate-300 mb-1">Live Preview Update Interval: <span id="interval-value">500</span>ms</label>
|
140 |
+
<input type="range" id="preview-interval-slider" name="preview-interval-slider" min="100" max="2000" step="100" value="500" class="futuristic-slider w-full h-2 rounded-lg appearance-none cursor-pointer">
|
141 |
+
<p class="mt-1 text-xs text-slate-400">Min time between preview updates (higher = less frequent).</p>
|
142 |
+
</div>
|
|
|
|
|
|
|
|
|
|
|
143 |
|
144 |
+
<div id="code-output-container">
|
145 |
+
<div id="code-output-header">
|
146 |
+
<h3 id="selected-code-title" class="text-lg font-medium text-cyan-300 flex-shrink-0">Selected Code:</h3>
|
147 |
+
<div class="flex items-center gap-2">
|
148 |
+
<button id="copy-code-button" title="Copy Code">
|
149 |
+
<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-copy"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"/><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/></svg>
|
150 |
+
</button>
|
151 |
+
<button id="export-code-button" title="Export Code as ZIP">
|
152 |
+
<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-download"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" x2="12" y1="15" y2="3"/></svg>
|
153 |
+
</button>
|
154 |
+
</div>
|
155 |
+
</div>
|
156 |
+
<div class="flex-grow overflow-hidden min-h-0 flex">
|
157 |
+
<pre id="code-output" class="h-full"><code class="language-html">// Select a variation or history item to view its code.</code></pre>
|
158 |
+
</div>
|
159 |
+
</div>
|
160 |
+
</div>
|
161 |
+
|
162 |
+
<div class="flex justify-end gap-3 mt-4">
|
163 |
+
<button id="config-modal-close-button" class="futuristic-button modal-button-secondary px-4 py-2 text-sm">Close</button>
|
164 |
+
</div>
|
165 |
+
</div>
|
166 |
+
</div>
|
167 |
+
|
168 |
+
<div id="confirm-modal-overlay" class="modal-overlay">
|
169 |
+
<div id="confirm-modal-content" class="modal-content max-w-md">
|
170 |
+
<h3 class="text-xl font-semibold text-cyan-300">Confirm Action</h3>
|
171 |
+
<p id="confirm-modal-message" class="text-sm text-slate-300">Are you sure?</p>
|
172 |
+
<div class="flex justify-end gap-3 mt-4">
|
173 |
+
<button id="confirm-modal-cancel-button" class="futuristic-button modal-button-secondary px-4 py-2 text-sm">Cancel</button>
|
174 |
+
<button id="confirm-modal-confirm-button" class="futuristic-button px-4 py-2 text-sm bg-red-600 hover:bg-red-700">Confirm</button>
|
175 |
+
</div>
|
176 |
+
</div>
|
177 |
+
</div>
|
178 |
+
|
179 |
+
<div id="prompt-display-modal-overlay" class="modal-overlay">
|
180 |
+
<div id="prompt-display-modal-content" class="modal-content max-w-2xl">
|
181 |
+
<h3 class="text-xl font-semibold text-cyan-300 mb-2">Full Prompt</h3>
|
182 |
+
<pre id="full-prompt-text" class="text-sm text-slate-300 whitespace-pre-wrap break-words bg-slate-800 p-3 rounded border border-slate-600 max-h-96 overflow-y-auto"></pre>
|
183 |
+
<div class="flex justify-end gap-3 mt-4">
|
184 |
+
<button id="prompt-display-modal-close-button" class="futuristic-button modal-button-secondary px-4 py-2 text-sm">Close</button>
|
185 |
+
</div>
|
186 |
+
</div>
|
187 |
+
</div>
|
188 |
+
|
189 |
+
<script src="script.js"></script>
|
190 |
+
</body>
|
191 |
+
</html>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|