@innovastudio/contentbox 1.6.175 → 1.6.177

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -115892,6 +115892,10 @@ class CodeChat$1 {
115892
115892
  this.builder = builder;
115893
115893
  const builderStuff = this.builder.builderStuff;
115894
115894
  this.builderStuff = builderStuff;
115895
+
115896
+ // Check if demo mode is enabled
115897
+ this.isDemoMode = this.builder.demoMode || false;
115898
+ this.demoConversations = this.builder.demoConversations || [];
115895
115899
  const out = s => this.out(s);
115896
115900
 
115897
115901
  // Load saved settings or use defaults
@@ -115901,6 +115905,9 @@ class CodeChat$1 {
115901
115905
  this.systemModel = this.builder.systemModel;
115902
115906
  }
115903
115907
  this.codeModels = [{
115908
+ id: 'anthropic/claude-opus-4.5',
115909
+ label: 'Claude Opus 4.5'
115910
+ }, {
115904
115911
  id: 'google/gemini-3-pro-preview',
115905
115912
  label: 'Google Gemini 3 Pro Preview'
115906
115913
  }, {
@@ -116228,6 +116235,12 @@ class CodeChat$1 {
116228
116235
  // backward
116229
116236
  this.imageModels = this.builder.imageGenerationModels;
116230
116237
  }
116238
+ let inputPlaceholderText;
116239
+ if (this.builder.editor) {
116240
+ inputPlaceholderText = out('e.g., Create a landing page for a creative studio');
116241
+ } else {
116242
+ inputPlaceholderText = out('e.g., Create an article about a productive home workspace');
116243
+ }
116231
116244
  let html = `
116232
116245
  <style>
116233
116246
  #chatPanel {
@@ -116559,10 +116572,12 @@ class CodeChat$1 {
116559
116572
  box-shadow: 6px 14px 20px 0px rgba(95, 95, 95, 0.11);
116560
116573
  z-index: 10005;
116561
116574
  display: none;
116575
+ pointer-events: auto; /* Reset cursor to default to prevent it from getting stuck */
116562
116576
  }
116563
116577
 
116564
116578
  #settingsDialog.open {
116565
116579
  display: block;
116580
+ pointer-events: auto; /* Reset cursor to default to prevent it from getting stuck */
116566
116581
  }
116567
116582
 
116568
116583
  #settingsDialog .settings-header {
@@ -116823,9 +116838,118 @@ class CodeChat$1 {
116823
116838
  body.dark #chatPanel .copy-button:hover {
116824
116839
  background: rgba(255, 255, 255, 0.2);
116825
116840
  }
116841
+
116842
+
116843
+ /* Quick Image Size Button */
116844
+ #chatPanel .quick-image-size-button {
116845
+ width: 48px;
116846
+ height: 48px;
116847
+ border: none;
116848
+ border-radius: 8px;
116849
+ font-size: 13px;
116850
+ font-weight: 500;
116851
+ cursor: pointer;
116852
+ transition: background 0.2s;
116853
+ display: flex;
116854
+ align-items: center;
116855
+ justify-content: center;
116856
+ }
116857
+
116858
+ #chatPanel .quick-image-size-button:hover:not(:disabled) {
116859
+ background: rgba(0, 0, 0, 0.05);
116860
+ }
116861
+
116862
+ #chatPanel .quick-image-size-button:disabled {
116863
+ opacity: 0.6;
116864
+ cursor: not-allowed;
116865
+ }
116866
+
116867
+ body.dark #chatPanel .quick-image-size-button {
116868
+ background: #484848;
116869
+ }
116870
+
116871
+ body.dark #chatPanel .quick-image-size-button:hover:not(:disabled) {
116872
+ background: #4c4c4c;
116873
+ }
116874
+
116875
+ /* Image Size Popover */
116876
+ #chatPanel .image-size-popover {
116877
+ position: absolute;
116878
+ bottom: 72px;
116879
+ right: 68px;
116880
+ background: white;
116881
+ border: 1px solid #e5e5e5;
116882
+ border-radius: 8px;
116883
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
116884
+ z-index: 10006;
116885
+ min-width: 160px;
116886
+ max-height: 300px;
116887
+ overflow-y: auto;
116888
+ }
116889
+
116890
+ body.dark #chatPanel .image-size-popover {
116891
+ background: #3a3a3a;
116892
+ border-color: #555;
116893
+ }
116894
+
116895
+ #chatPanel .size-options {
116896
+ padding: 8px;
116897
+ }
116898
+
116899
+ #chatPanel .size-option {
116900
+ display: flex;
116901
+ align-items: center;
116902
+ padding: 10px 12px;
116903
+ cursor: pointer;
116904
+ border-radius: 6px;
116905
+ transition: background 0.2s;
116906
+ font-size: 14px;
116907
+ }
116908
+ #chatPanel .size-option label,
116909
+ #chatPanel .size-option input {
116910
+ cursor: pointer;
116911
+ }
116912
+
116913
+ #chatPanel .size-option:hover {
116914
+ background: rgba(0, 0, 0, 0.05);
116915
+ }
116916
+
116917
+ #chatPanel .size-option.selected {
116918
+ background: rgba(62, 147, 247, 0.1);
116919
+ font-weight: 500;
116920
+ }
116921
+
116922
+ #chatPanel .size-option input[type="radio"] {
116923
+ margin-right: 8px;
116924
+ }
116925
+
116926
+ body.dark #chatPanel .size-option:hover {
116927
+ background: rgba(255, 255, 255, 0.1);
116928
+ }
116929
+
116930
+ body.dark #chatPanel .size-option.selected {
116931
+ background: rgba(62, 147, 247, 0.2);
116932
+ }
116933
+
116934
+ #chatPanel .image-size-popover::-webkit-scrollbar {
116935
+ width: 6px;
116936
+ }
116937
+
116938
+ #chatPanel .image-size-popover::-webkit-scrollbar-track {
116939
+ background: transparent;
116940
+ }
116941
+
116942
+ #chatPanel .image-size-popover::-webkit-scrollbar-thumb {
116943
+ background: #e5e5e5;
116944
+ border-radius: 3px;
116945
+ }
116946
+
116947
+ body.dark #chatPanel .image-size-popover::-webkit-scrollbar-thumb {
116948
+ background: #555;
116949
+ }
116826
116950
  </style>
116827
116951
  <div
116828
- class="hidden"
116952
+ class="hidden keep-selection"
116829
116953
  id="chatPanel"
116830
116954
  role="dialog"
116831
116955
  aria-labelledby="chatPanelTitle"
@@ -116875,10 +116999,24 @@ class CodeChat$1 {
116875
116999
  <label for="promptInput" class="sr-only">${out('Message input')}</label>
116876
117000
  <textarea
116877
117001
  id="promptInput"
116878
- placeholder="${out('e.g., Create a landing page and explain best practices for hero sections')}"
117002
+ placeholder="${inputPlaceholderText}"
116879
117003
  rows="1"
116880
117004
  aria-label="${out('Type your message')}"
116881
117005
  ></textarea>
117006
+
117007
+ <!-- NEW: Quick image size button -->
117008
+ <button
117009
+ id="quickImageSizeButton"
117010
+ type="button"
117011
+ class="quick-image-size-button"
117012
+ aria-label="${out('Select image size')}"
117013
+ aria-haspopup="true"
117014
+ aria-expanded="false"
117015
+ style="display: none;"
117016
+ >
117017
+
117018
+ </button>
117019
+
116882
117020
  <button
116883
117021
  id="sendButton"
116884
117022
  type="button"
@@ -116892,15 +117030,30 @@ class CodeChat$1 {
116892
117030
  </svg>
116893
117031
  </button>
116894
117032
  </div>
117033
+
117034
+ <!-- NEW: Size selection popover -->
117035
+ <div
117036
+ id="imageSizePopover"
117037
+ class="image-size-popover"
117038
+ role="menu"
117039
+ aria-hidden="true"
117040
+ style="display: none;"
117041
+ >
117042
+ <div class="size-options" id="sizeOptionsContainer">
117043
+ <!-- Options populated dynamically -->
117044
+ </div>
117045
+ </div>
117046
+
116895
117047
  </div>
116896
117048
  </div>
116897
117049
 
116898
117050
  <!-- Settings Dialog Overlay -->
116899
- <div id="settingsOverlay" aria-hidden="true"></div>
117051
+ <div id="settingsOverlay" class="keep-selection" aria-hidden="true"></div>
116900
117052
 
116901
117053
  <!-- Settings Dialog -->
116902
117054
  <div
116903
117055
  id="settingsDialog"
117056
+ class="keep-selection"
116904
117057
  role="dialog"
116905
117058
  aria-labelledby="settingsTitle"
116906
117059
  aria-modal="true"
@@ -117034,9 +117187,16 @@ class CodeChat$1 {
117034
117187
  this.chatModelSelect.value = this.settings.chatModel;
117035
117188
  this.imageModelSelect.value = this.settings.imageModel;
117036
117189
  this.imageSizeSelect.value = this.settings.imageSize;
117037
- let isChatVisible = localStorage.getItem('chatPanelVisible') !== 'false';
117190
+
117191
+ // Check saved state - default to closed if never set
117192
+ const savedState = localStorage.getItem('chatPanelVisible');
117193
+ let isChatVisible = savedState === 'true'; // Only open if explicitly set to 'true'
117038
117194
  if (!isChatVisible) {
117039
117195
  modal.classList.add('hidden');
117196
+ modal.setAttribute('aria-hidden', 'true');
117197
+ } else {
117198
+ modal.classList.remove('hidden');
117199
+ modal.removeAttribute('aria-hidden');
117040
117200
  }
117041
117201
  const btnClose = modal.querySelector('.close-button');
117042
117202
  btnClose.addEventListener('click', () => {
@@ -117119,7 +117279,45 @@ class CodeChat$1 {
117119
117279
  this.closeChatButton = closeChatButton;
117120
117280
  this.sendButton = sendButton;
117121
117281
  this.messagesContainer = messagesContainer;
117282
+
117283
+ // NEW: Quick image size button elements
117284
+ this.quickImageSizeButton = builderStuff.querySelector('#quickImageSizeButton');
117285
+ this.imageSizePopover = builderStuff.querySelector('#imageSizePopover');
117286
+ this.sizeOptionsContainer = builderStuff.querySelector('#sizeOptionsContainer');
117287
+
117288
+ // Quick Image Size Button Handlers
117289
+ this.quickImageSizeButton.addEventListener('click', e => {
117290
+ e.stopPropagation();
117291
+ this.toggleImageSizePopover();
117292
+ });
117293
+
117294
+ // Close popover when clicking outside
117295
+ document.addEventListener('click', e => {
117296
+ if (!this.imageSizePopover.contains(e.target) && !this.quickImageSizeButton.contains(e.target)) {
117297
+ this.closeImageSizePopover();
117298
+ }
117299
+ });
117300
+
117301
+ // Close popover on Escape
117302
+ this.imageSizePopover.addEventListener('keydown', e => {
117303
+ if (e.key === 'Escape') {
117304
+ this.closeImageSizePopover();
117305
+ this.quickImageSizeButton.focus();
117306
+ }
117307
+ });
117122
117308
  this.renderImageOptions();
117309
+ if (this.demoConversations) {
117310
+ this.loadConversations();
117311
+ }
117312
+ if (this.isDemoMode) {
117313
+ this.addDemoBanner();
117314
+
117315
+ // Disable input in demo mode
117316
+ this.promptInput.disabled = true;
117317
+ // this.promptInput.placeholder = out('Demo mode - Chat is read-only');
117318
+ this.sendButton.disabled = true;
117319
+ this.messagesContainer.scrollTop = this.messagesContainer.scrollHeight;
117320
+ }
117123
117321
  }
117124
117322
  renderImageOptions() {
117125
117323
  const out = s => this.out(s);
@@ -117226,6 +117424,9 @@ class CodeChat$1 {
117226
117424
  // Trigger initial render with defaults
117227
117425
  modelSelect.value = defaultModelId;
117228
117426
  renderSizes(defaultModelId, false);
117427
+
117428
+ // NEW: Initialize quick size button
117429
+ this.updateQuickImageSizeButton();
117229
117430
  }
117230
117431
 
117231
117432
  /**
@@ -117291,6 +117492,9 @@ class CodeChat$1 {
117291
117492
  this.settingsOverlay.setAttribute('aria-hidden', 'true');
117292
117493
  this.settingsDialog.classList.remove('open');
117293
117494
  this.settingsDialog.setAttribute('aria-hidden', 'true');
117495
+
117496
+ // Reset cursor to default to prevent it from getting stuck
117497
+ document.body.style.cursor = '';
117294
117498
  if (this.settingsLastFocusedElement) {
117295
117499
  this.settingsLastFocusedElement.focus();
117296
117500
  }
@@ -117302,6 +117506,9 @@ class CodeChat$1 {
117302
117506
  this.settings.imageModel = this.imageModelSelect.value;
117303
117507
  this.settings.imageSize = this.imageSizeSelect.value;
117304
117508
  this.saveSettingsToStorage();
117509
+
117510
+ // NEW: Update quick button when settings change
117511
+ this.updateQuickImageSizeButton();
117305
117512
  this.closeSettings();
117306
117513
  }
117307
117514
 
@@ -117335,6 +117542,106 @@ Your job:
117335
117542
  3. Determine what each image should depict based on surrounding content and context
117336
117543
  4. Create detailed image generation prompts for each image
117337
117544
 
117545
+ 📸 EDITORIAL STYLE GUIDE - Apply to all image prompts:
117546
+
117547
+ - Default aesthetic: Minimalist magazine-style (Kinfolk, Cereal, Vogue Living)
117548
+ - Composition: Clean, lots of negative space, well-balanced
117549
+
117550
+
117551
+ CREATIVE PROMPT ENHANCEMENT:
117552
+ When constructing image prompts, intelligently enrich basic descriptions using these proven cinematic stylers:
117553
+
117554
+ 1. **Golden Hour Cinematic**
117555
+ "Warm sunlight, golden-hour lighting. Low contrast with lifted shadows, ethereal glow throughout. Overall vibe: relaxed, coastal, sun-kissed, carefree, West Coast lifestyle, evoking warmth and endless summer."
117556
+ → Best for: Landscapes, architecture, outdoor scenes, lifestyle content
117557
+
117558
+ 2. **Morning Halo Effect**
117559
+ "The lighting is soft, morning sunlight coming, creating a 'halo' effect. Vibrant cinematic shot"
117560
+ → Best for: Portraits, people in action, outdoor activities
117561
+
117562
+ 3. **Natural Forest Serenity**
117563
+ "in a lush, green forest. The sunlight should be filtering through the trees, creating a serene and natural atmosphere"
117564
+ → Best for: Nature scenes, outdoor activities, wellness content
117565
+
117566
+ 4. **Coastal Summer Drama**
117567
+ "on a dramatic windswept coastline. The overall atmosphere is cheerful, breezy, and full of summer warmth"
117568
+ → Best for: Beach/coastal scenes, adventure, travel content
117569
+
117570
+ 5. **Modern Interior Elegance**
117571
+ "illuminated by warm, natural lighting. The background features a softly blurred modern interior with subtle lights and cool tones, adding depth without distraction"
117572
+ → Best for: Indoor portraits, product shots, professional/business settings
117573
+
117574
+ ENHANCEMENT STRATEGY:
117575
+ - Analyze the subject matter and context from the HTML/request
117576
+ - Select the most appropriate styler that matches the scene type
117577
+ - Check if user has specified lighting/time → DO NOT apply contradicting styler elements
117578
+ * Example: User says "daylight" → DO NOT add "golden-hour" or "sunset"
117579
+ - Aim for creative elevation while maintaining the core subject integrity
117580
+ - Blend the styler naturally into the prompt (adapt grammar, pronouns, context)
117581
+ - IF user is vague (no specific lighting/mood) → Apply full styler enhancement
117582
+ - User's explicit details ALWAYS take precedence over styler recommendations
117583
+ ` +
117584
+ /*
117585
+ ENHANCEMENT STRATEGY:
117586
+ - Analyze the subject matter and context from the HTML/request
117587
+ - Select the most appropriate styler that matches the scene type
117588
+ - Blend the styler naturally into the prompt (adapt grammar, pronouns, context)
117589
+ - If the subject already has specific lighting/atmosphere details, complement rather than override
117590
+ - Aim for creative elevation while maintaining the core subject integrity
117591
+ */
117592
+ `
117593
+ Examples of enriched prompts:
117594
+ - Basic: "a house with mountain view"
117595
+ → Enhanced: "a house with beautiful mountain view. Warm sunlight, golden-hour lighting. Low contrast with lifted shadows, ethereal glow throughout. Overall vibe: relaxed, coastal, sun-kissed, carefree, West Coast lifestyle, evoking warmth and endless summer."
117596
+
117597
+ - Basic: "person hiking"
117598
+ → Enhanced: "a person hiking in nature. The lighting is soft, morning sunlight coming, creating a 'halo' effect. Vibrant cinematic shot"
117599
+
117600
+ - Basic: "woman reading"
117601
+ → Enhanced: "a woman sitting comfortably and reading. Her face is illuminated by warm, natural lighting. The background features a softly blurred modern interior with subtle lights and cool tones, adding depth without distraction"
117602
+
117603
+ - Already detailed: "dramatic headshot with bokeh and rim lighting"
117604
+ → No enhancement needed (preserve user's vision)
117605
+
117606
+
117607
+ CONTEXT-SPECIFIC GUIDELINES:
117608
+
117609
+ Interior/Indoor scenes:
117610
+ - Lighting: Soft natural window light
117611
+ - Backdrop: white walls or neutral tones
117612
+ - Mood: Calm, serene, inviting, uncluttered
117613
+ - Reference: "Kinfolk interior photography"
117614
+
117615
+ People/Portraits/Activities:
117616
+ - Lighting: Natural diffused light, flattering and soft
117617
+ - Styling: Effortlessly elegant, contemporary casual
117618
+ - Pose: Candid, authentic moments, relaxed
117619
+ - Reference: "modern lifestyle editorial photography"
117620
+
117621
+ Products/Objects:
117622
+ - Lighting: Clean studio or soft natural light
117623
+ - Backdrop: Minimalist neutral, lots of breathing room
117624
+ - Mood: Refined simplicity, premium feel
117625
+ - Reference: "high-end editorial product photography"
117626
+
117627
+ Food/Culinary:
117628
+ - Lighting: Soft daylight, gentle overhead or 45° angle
117629
+ - Styling: Minimal props, artisan ceramics, linen
117630
+ - Mood: Fresh, appetizing, rustic-modern
117631
+ - Reference: "Bon Appétit editorial food styling"
117632
+
117633
+ Outdoor/Landscape:
117634
+ - Composition: Serene, contemplative, minimal
117635
+ - Mood: Calm atmosphere, breathing space, lifted brightness
117636
+ - Colors: Vibrant cinematic shot.
117637
+ - Reference: "editorial travel photography"
117638
+
117639
+ CRITICAL RULES:
117640
+ 1. If user specifies a style (cinematic, vintage, vibrant, etc.) → RESPECT IT, only add quality markers
117641
+ 2. If user is vague → APPLY FULL EDITORIAL TREATMENT based on subject category
117642
+ 3. Always maintain cohesive aesthetic across all images
117643
+ 4. Every prompt should feel like it belongs in a high-end lifestyle magazine
117644
+
117338
117645
  Respond with a JSON array with one entry per image to generate:
117339
117646
  [
117340
117647
  {
@@ -117445,6 +117752,11 @@ Response: [
117445
117752
  */
117446
117753
  async sendMessage() {
117447
117754
  const out = s => this.out(s);
117755
+
117756
+ // Prevent sending messages in demo mode
117757
+ if (this.isDemoMode) {
117758
+ return;
117759
+ }
117448
117760
  const prompt = this.promptInput.value.trim();
117449
117761
  if (!prompt) return;
117450
117762
  this.addMessage('user', prompt);
@@ -117512,14 +117824,42 @@ Response: [
117512
117824
  } else if (result.type === 'image') {
117513
117825
  if (result.imageDetails && result.imageDetails.length > 1) {
117514
117826
  this.addMessage('assistant', `✓ Generated ${result.imageDetails.length} images successfully`);
117827
+ // this.addMessage('assistant', `✓ ${out('Generated {count} images successfully').replace('{count}', result.imageDetails.length)}`);
117515
117828
  this.addImagePreview(result.imageDetails);
117516
117829
  } else {
117517
- this.addMessage('assistant', '✓ Generated image successfully');
117830
+ this.addMessage('assistant', '✓ Image generated successfully');
117831
+ // this.addMessage('assistant', `✓ ${out('Image generated successfully')}`);
117518
117832
  if (result.generatedUrls && result.generatedUrls[0]) {
117519
117833
  this.addImagePreview([{
117520
117834
  url: result.generatedUrls[0],
117521
117835
  context: result.description
117522
117836
  }]);
117837
+
117838
+ // If there is a selected image, replace with the new image
117839
+ const url = result.generatedUrls[0];
117840
+ if (this.builder.editor) {
117841
+ // ContentBox
117842
+ const img = this.builder.editor.activeImage;
117843
+ if (img) {
117844
+ this.builder.editor.saveForUndo();
117845
+ img.addEventListener('load', () => {
117846
+ this.builder.editor.element.image.repositionImageTool();
117847
+ this.builder.editor.elmTool.repositionElementTool();
117848
+ });
117849
+ this.builder.editor.activeImage.setAttribute('src', url);
117850
+ }
117851
+ } else {
117852
+ // ContentBuilder
117853
+ const img = this.builder.activeImage;
117854
+ if (img) {
117855
+ this.builder.uo.saveForUndo();
117856
+ img.addEventListener('load', () => {
117857
+ this.builder.element.image.repositionImageTool();
117858
+ this.builder.elmTool.repositionElementTool();
117859
+ });
117860
+ this.builder.activeImage.setAttribute('src', url);
117861
+ }
117862
+ }
117523
117863
  }
117524
117864
  }
117525
117865
  }
@@ -117573,15 +117913,16 @@ Response: [
117573
117913
  `;
117574
117914
  images.forEach(img => {
117575
117915
  previewHTML += `
117576
- <div style="border-radius: 8px; overflow: hidden; background: rgba(255,255,255,0.05);">
117577
- <img src="${img.url}" alt="${img.context || 'Generated image'}" style="width: 100%; height: 150px; object-fit: cover;" />
117916
+ <div style="border-radius: 6px; overflow: hidden; background: rgba(255,255,255,0.05);">
117917
+ <img src="${img.url}" alt="${img.context || 'Generated image'}" style="width: 100%;" />
117578
117918
  <div style="padding: 8px; font-size: 11px; opacity: 0.8;">
117579
117919
  ${img.context || ''}
117580
117920
  <a href="${img.url}" target="_blank" rel="noopener noreferrer" style="display: block; margin-top: 4px;">View</a>
117581
117921
  </div>
117582
117922
  </div>
117583
- `;
117923
+ `; // height: 150px; object-fit: cover;
117584
117924
  });
117925
+
117585
117926
  previewHTML += '</div>';
117586
117927
  previewDiv.innerHTML = previewHTML;
117587
117928
  this.messagesContainer.appendChild(previewDiv);
@@ -117602,6 +117943,32 @@ Response: [
117602
117943
 
117603
117944
  // Check if image generation is enabled
117604
117945
  const imageGenEnabled = this.builder.generateMediaUrl_Fal ? true : false;
117946
+
117947
+ // Build context about previously generated images
117948
+ let imageHistoryContext = '';
117949
+ const previousImageResults = this.conversationResults.filter(r => r.type === 'image' && r.generatedUrls && r.generatedUrls.length > 0);
117950
+ if (previousImageResults.length > 0) {
117951
+ imageHistoryContext = '\n\n=== PREVIOUSLY GENERATED IMAGES IN THIS CONVERSATION ===\n';
117952
+ imageHistoryContext += 'The following images were generated earlier in this conversation:\n\n';
117953
+ previousImageResults.forEach((result, idx) => {
117954
+ if (result.imageDetails && result.imageDetails.length > 0) {
117955
+ result.imageDetails.forEach((img, imgIdx) => {
117956
+ imageHistoryContext += `Generated Image ${idx + 1}.${imgIdx + 1}:\n`;
117957
+ imageHistoryContext += ` URL: ${img.url}\n`;
117958
+ imageHistoryContext += ` Context: ${img.context}\n`;
117959
+ imageHistoryContext += ` Description: ${result.description}\n\n`;
117960
+ });
117961
+ } else {
117962
+ result.generatedUrls.forEach((url, urlIdx) => {
117963
+ imageHistoryContext += `Generated Image ${idx + 1}.${urlIdx + 1}:\n`;
117964
+ imageHistoryContext += ` URL: ${url}\n`;
117965
+ imageHistoryContext += ` Description: ${result.description}\n\n`;
117966
+ });
117967
+ }
117968
+ });
117969
+ imageHistoryContext += '⚠️ IMPORTANT: If the user refers to "the generated image", "this image", "the image", or "previous image", they are referring to these images above.\n';
117970
+ imageHistoryContext += '⚠️ DO NOT create a new image task. Instead, create a CODE task that uses these existing URLs.\n\n';
117971
+ }
117605
117972
  const classificationPrompt = `Analyze this user message and break it down into tasks.
117606
117973
 
117607
117974
  CURRENT HTML CONTEXT (use this to understand the page structure):
@@ -117609,11 +117976,19 @@ CURRENT HTML CONTEXT (use this to understand the page structure):
117609
117976
  ${this.builder.html()}
117610
117977
  \`\`\`
117611
117978
 
117979
+ ${imageHistoryContext}
117980
+
117612
117981
  IMPORTANT: This is a WEB BUILDER tool with chat capabilities. Valid requests are:
117613
117982
  - Creating/editing/removing HTML content (code tasks)
117614
117983
  ${imageGenEnabled ? '- Generating images for the webpage (image tasks)' : ''}
117615
117984
  - Asking questions or having conversations (chat tasks) - THIS CAN BE ABOUT ANYTHING
117616
117985
 
117986
+ CRITICAL CONTEXT RULE:
117987
+ - In a web builder, "create an article/blog/story/content" means CREATE A WEBPAGE (CODE task)
117988
+ - Only use CHAT task for questions, explanations, or requests for advice
117989
+ - If user says "create", "write", "make", "build" followed by content → CODE task
117990
+ - If user says "explain", "how do I", "what is", "tell me about" → CHAT task
117991
+
117617
117992
  ${imageGenEnabled ? '' : `
117618
117993
  ⚠️ AI IMAGE GENERATION IS CURRENTLY DISABLED
117619
117994
  - If user requests AI image generation/creation, explain that it's disabled
@@ -117683,6 +118058,18 @@ IMAGE TASK RULES:
117683
118058
  - IMPORTANT: Look at the HTML context to understand if multiple images are involved
117684
118059
  - If the target section has multiple images, indicate this in targetElement (e.g., "all 3 images in Culinary Delights section")
117685
118060
  - Image tasks should come BEFORE code tasks that depend on them
118061
+
118062
+ - CRITICAL: DO NOT create image task for these phrases (they mean REUSE existing):
118063
+ * "use the generated image" / "use the image"
118064
+ * "use this image" / "use existing image"
118065
+ * "with the generated image" / "with this image"
118066
+ * "with the previous image" / "with the last image"
118067
+ * "using the image I just created/generated"
118068
+ * Any reference to a PREVIOUS image from conversation history
118069
+ - Decision logic:
118070
+ * "Generate a new image of X" → CREATE image task (new generation)
118071
+ * "Create article using the generated image" → NO image task (reuse existing)
118072
+
117686
118073
  ` : ''}
117687
118074
 
117688
118075
  Examples:
@@ -117720,6 +118107,17 @@ Input: "Create a landing page about wood furniture workshop"
117720
118107
  Output: {"is_valid": true, "tasks": [{"type": "code", "description": "Create a landing page for a wood furniture workshop", "order": 1}], "is_mixed": false}
117721
118108
  Note: No image task created because user did not explicitly request AI image generation
117722
118109
 
118110
+ Input: "Create an inspiring article and use the generated image"
118111
+ Output: {"is_valid": true, "tasks": [{"type": "code", "description": "Create article using previously generated image from conversation history", "order": 1}], "is_mixed": false}
118112
+ Note: No image task because user wants to REUSE existing image
118113
+
118114
+ Input: "Write a blog post about productivity and use this image"
118115
+ Output: {"is_valid": true, "tasks": [{"type": "code", "description": "Create blog post using previously generated image", "order": 1}], "is_mixed": false}
118116
+
118117
+ Input: "Generate another mountain image and create a landing page"
118118
+ Output: {"tasks": [{"type": "image", "description": "Generate new mountain image", "imagePrompt": "...", "order": 1}, {"type": "code", "description": "Create landing page with new image", "order": 2}], "is_mixed": true}
118119
+ Note: "another" and "generate" indicate NEW image generation
118120
+
117723
118121
  ` : `
117724
118122
  Input: "Generate an AI image of a mountain"
117725
118123
  Output: {"is_valid": false, "reason": "AI image generation is currently disabled.", "tasks": [], "is_mixed": false}
@@ -117803,7 +118201,9 @@ Output: {"is_valid": false, "reason": "AI image generation is currently disabled
117803
118201
  if (imageTasksFromThisRequest.length > 0) {
117804
118202
  hasGeneratedImages = true;
117805
118203
  imageContext = '\n\n=== GENERATED IMAGE URLS (USE THESE FOR YOUR TASK) ===\n';
117806
- imageTasksFromThisRequest.forEach(imageTask => {
118204
+
118205
+ // ⭐ REVERSE to show most recent first
118206
+ imageTasksFromThisRequest.reverse().forEach(imageTask => {
117807
118207
  if (imageTask.imageDetails && imageTask.imageDetails.length > 0) {
117808
118208
  // Multiple images with context
117809
118209
  imageTask.imageDetails.forEach((img, idx) => {
@@ -117818,7 +118218,8 @@ Output: {"is_valid": false, "reason": "AI image generation is currently disabled
117818
118218
  });
117819
118219
  }
117820
118220
  });
117821
- imageContext += '\n⚠️ IMPORTANT: Use these generated image URLs (not placehold.co or other sources) for the specific images mentioned in your task.\n';
118221
+ imageContext += '\n⚠️ IMPORTANT: Use the FIRST image URL listed above (most recent) unless the task specifically asks for a different image.\n';
118222
+ imageContext += '⚠️ The first image is the MOST RECENTLY GENERATED and should be used by default.\n';
117822
118223
  imageContext += '⚠️ Your task description specifies WHICH images to update - follow it precisely.\n';
117823
118224
  }
117824
118225
 
@@ -117915,6 +118316,10 @@ ${this.builder.html()}
117915
118316
  TASK: ${task.description}
117916
118317
 
117917
118318
  IMPORTANT: Follow the Best Practices in Content framework.
118319
+ ${this.builder.editor ? `CRITICAL: This project uses the Content.css and Box framework, NOT Tailwind. ONLY use classes explicitly documented in this framework guide.
118320
+ DO NOT use Tailwind classes like gap-4, w-1/2, top-4, hover:scale-120, etc.` : `CRITICAL: This project uses the Content.css framework, NOT Tailwind. ONLY use classes explicitly documented in this framework guide.
118321
+ DO NOT use Tailwind classes like gap-4, w-1/2, top-4, hover:scale-120, etc.`}
118322
+ For flexibility, you can use inline styles or embedded <style> at the end of the generated HTML.
117918
118323
 
117919
118324
  ${imageContext}
117920
118325
  ${chatContext}
@@ -118367,6 +118772,319 @@ ${this.builder.html()}
118367
118772
  document.body.removeChild(announcement);
118368
118773
  }, 1000);
118369
118774
  }
118775
+
118776
+ /**
118777
+ * ============================================================================
118778
+ * DEMO MODE
118779
+ * ============================================================================
118780
+ */
118781
+ loadConversations() {
118782
+ // Load demo conversations
118783
+ if (this.demoConversations && this.demoConversations.length > 0) {
118784
+ this.demoConversations.forEach(msg => {
118785
+ if (msg.role === 'user') {
118786
+ this.addMessage('user', msg.content);
118787
+ } else if (msg.role === 'assistant') {
118788
+ this.addMessage('assistant', msg.content, true);
118789
+
118790
+ // Add image preview if available
118791
+ if (msg.imagePreview && msg.imagePreview.length > 0) {
118792
+ this.addImagePreview(msg.imagePreview);
118793
+
118794
+ // Include in conversationResults with correct structure
118795
+ this.conversationResults.push({
118796
+ type: 'image',
118797
+ description: 'Previously generated image from demo',
118798
+ content: `Generated ${msg.imagePreview.length} images`,
118799
+ generatedUrls: msg.imagePreview.map(img => img.url),
118800
+ // Array of URLs
118801
+ imageDetails: msg.imagePreview.map(img => ({
118802
+ // Array of objects
118803
+ url: img.url,
118804
+ context: img.context,
118805
+ prompt: img.context || '' // Use context as prompt if no prompt available
118806
+ })),
118807
+
118808
+ targetElement: 'demo images'
118809
+ });
118810
+ }
118811
+ }
118812
+ });
118813
+ }
118814
+ }
118815
+ addDemoBanner() {
118816
+ const out = s => this.out(s);
118817
+ const banner = document.createElement('div');
118818
+ banner.className = 'demo-banner';
118819
+ banner.innerHTML = `
118820
+ <div style="
118821
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
118822
+ color: white;
118823
+ padding: 12px 16px;
118824
+ border-radius: 8px;
118825
+ font-size: 13px;
118826
+ display: flex;
118827
+ align-items: center;
118828
+ gap: 8px;
118829
+ box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
118830
+ ">
118831
+ <span style="font-size: 18px;">📖</span>
118832
+ <div>
118833
+ <strong>${out('Read-Only Demo')}</strong>
118834
+ <div style="font-size: 11px; opacity: 0.9; margin-top: 2px;">
118835
+ ${out('Full AI chat available in the complete version.')}
118836
+ </div>
118837
+ </div>
118838
+ </div>
118839
+ `;
118840
+
118841
+ // Insert banner at the top of messages container
118842
+ // this.messagesContainer.insertBefore(banner, this.messagesContainer.firstChild);
118843
+ this.messagesContainer.appendChild(banner);
118844
+ }
118845
+
118846
+ /**
118847
+ * Update the quick image size button based on current model and settings
118848
+ */
118849
+ updateQuickImageSizeButton() {
118850
+ const imageGenEnabled = this.builder.generateMediaUrl_Fal ? true : false;
118851
+ if (!imageGenEnabled) {
118852
+ this.quickImageSizeButton.style.display = 'none';
118853
+ return;
118854
+ }
118855
+ const currentModelId = this.settings.imageModel;
118856
+ const availableSizes = this.getSizesForModel(currentModelId);
118857
+
118858
+ // Hide button if model has no size options
118859
+ if (!availableSizes || availableSizes.length === 0) {
118860
+ this.quickImageSizeButton.style.display = 'none';
118861
+ return;
118862
+ }
118863
+
118864
+ // Show button
118865
+ this.quickImageSizeButton.style.display = 'flex';
118866
+
118867
+ // Smart selection: check if current size is available
118868
+ let selectedSize = this.settings.imageSize;
118869
+ if (!availableSizes.includes(selectedSize)) {
118870
+ // Current size not available, find best fallback
118871
+ selectedSize = this.findBestFallback(selectedSize, availableSizes);
118872
+ this.settings.imageSize = selectedSize;
118873
+
118874
+ // Sync with settings dialog
118875
+ this.imageSizeSelect.value = selectedSize;
118876
+
118877
+ // Save to storage
118878
+ this.saveSettingsToStorage();
118879
+ }
118880
+
118881
+ // Update button label
118882
+ this.updateButtonLabel(selectedSize);
118883
+
118884
+ // Populate popover options
118885
+ this.populateSizePopover(availableSizes, selectedSize);
118886
+ }
118887
+
118888
+ /**
118889
+ * Get available sizes for a model (reuse existing logic)
118890
+ */
118891
+ getSizesForModel(modelId) {
118892
+ const model = this.imageModels.find(m => m.id === modelId);
118893
+ if (!model) return null;
118894
+
118895
+ // if sizes explicitly empty array → means no size options
118896
+ if (Array.isArray(model.sizes) && model.sizes.length === 0) {
118897
+ return null;
118898
+ }
118899
+ const defaultSizes = ['square', 'square_hd', 'landscape_4_3', 'landscape_16_9', 'portrait_4_3', 'portrait_16_9'];
118900
+ return model.sizes || defaultSizes;
118901
+ }
118902
+
118903
+ /**
118904
+ * Find best fallback size when current selection is not available
118905
+ */
118906
+ findBestFallback(preferredSize, availableSizes) {
118907
+ // Strategy 1: Try to match orientation
118908
+ const orientation = this.getOrientation(preferredSize);
118909
+ const matchingOrientation = availableSizes.find(size => this.getOrientation(size) === orientation);
118910
+ if (matchingOrientation) return matchingOrientation;
118911
+
118912
+ // Strategy 2: Common defaults
118913
+ if (availableSizes.includes('16:9')) return '16:9';
118914
+ if (availableSizes.includes('landscape_16_9')) return 'landscape_16_9';
118915
+ if (availableSizes.includes('1:1')) return '1:1';
118916
+ if (availableSizes.includes('square')) return 'square';
118917
+
118918
+ // Strategy 3: First available
118919
+ return availableSizes[0];
118920
+ }
118921
+
118922
+ /**
118923
+ * Determine orientation from size value
118924
+ */
118925
+ getOrientation(size) {
118926
+ const landscape = ['16:9', '21:9', '4:3', '3:2', '5:4', 'landscape_16_9', 'landscape_4_3'];
118927
+ const portrait = ['9:16', '9:21', '3:4', '2:3', '4:5', 'portrait_16_9', 'portrait_4_3'];
118928
+ if (landscape.includes(size)) return 'landscape';
118929
+ if (portrait.includes(size)) return 'portrait';
118930
+ return 'square';
118931
+ }
118932
+
118933
+ /**
118934
+ * Update button label to show current size
118935
+ */
118936
+ updateButtonLabel(sizeValue) {
118937
+ if (!sizeValue) {
118938
+ this.quickImageSizeButton.textContent = '□';
118939
+ this.quickImageSizeButton.setAttribute('aria-label', this.out('Select image size'));
118940
+ return;
118941
+ }
118942
+
118943
+ // Convert verbose labels to short form
118944
+ const labelMap = {
118945
+ 'landscape_16_9': '16:9',
118946
+ 'landscape_4_3': '4:3',
118947
+ 'portrait_16_9': '9:16',
118948
+ 'portrait_4_3': '3:4',
118949
+ 'square': '1:1',
118950
+ 'square_hd': '1:1 HD'
118951
+ };
118952
+ const displayLabel = labelMap[sizeValue] || sizeValue;
118953
+ this.quickImageSizeButton.textContent = displayLabel;
118954
+ this.quickImageSizeButton.setAttribute('aria-label', `${this.out('Image size')}: ${displayLabel}`);
118955
+ }
118956
+
118957
+ /**
118958
+ * Populate the size popover with available options
118959
+ */
118960
+ populateSizePopover(sizes, selectedSize) {
118961
+ const out = s => this.out(s);
118962
+ const sizeDefs = {
118963
+ 'square_hd': out('Square HD'),
118964
+ 'square': out('Square'),
118965
+ 'landscape_4_3': out('Landscape 4x3'),
118966
+ 'landscape_16_9': out('Landscape 16x9'),
118967
+ 'portrait_4_3': out('Portrait 3x4'),
118968
+ 'portrait_16_9': out('Portrait 9x16'),
118969
+ '1:1': out('Square'),
118970
+ '3:2': out('Landscape 3x2'),
118971
+ '4:3': out('Landscape 4x3'),
118972
+ '5:4': out('Landscape 5x4'),
118973
+ '16:9': out('Landscape 16x9'),
118974
+ '21:9': out('Landscape 21:9'),
118975
+ '2:3': out('Portrait 2x3'),
118976
+ '3:4': out('Portrait 3x4'),
118977
+ '4:5': out('Portrait 4x5'),
118978
+ '9:16': out('Portrait 9x16'),
118979
+ '9:21': out('Portrait 9:21')
118980
+ };
118981
+ this.sizeOptionsContainer.innerHTML = '';
118982
+ sizes.forEach(size => {
118983
+ const option = document.createElement('div');
118984
+ option.className = 'size-option' + (size === selectedSize ? ' selected' : '');
118985
+ option.setAttribute('role', 'menuitem');
118986
+ option.setAttribute('tabindex', '0');
118987
+ const radio = document.createElement('input');
118988
+ radio.type = 'radio';
118989
+ radio.name = 'quickImageSize';
118990
+ radio.value = size;
118991
+ radio.checked = size === selectedSize;
118992
+ radio.id = `quick-size-${size}`;
118993
+ const label = document.createElement('label');
118994
+ label.htmlFor = `quick-size-${size}`;
118995
+ label.textContent = sizeDefs[size] || size;
118996
+ // label.style.cursor = 'pointer';
118997
+ label.style.flex = '1';
118998
+ option.appendChild(radio);
118999
+ option.appendChild(label);
119000
+
119001
+ // Click handler
119002
+ option.addEventListener('click', () => {
119003
+ this.selectImageSize(size);
119004
+ });
119005
+
119006
+ // Keyboard handler
119007
+ option.addEventListener('keydown', e => {
119008
+ if (e.key === 'Enter' || e.key === ' ') {
119009
+ e.preventDefault();
119010
+ this.selectImageSize(size);
119011
+ }
119012
+ });
119013
+ this.sizeOptionsContainer.appendChild(option);
119014
+ });
119015
+ }
119016
+
119017
+ /**
119018
+ * Handle size selection
119019
+ */
119020
+ selectImageSize(size) {
119021
+ // Update settings
119022
+ this.settings.imageSize = size;
119023
+
119024
+ // Sync with settings dialog
119025
+ this.imageSizeSelect.value = size;
119026
+
119027
+ // Update button label
119028
+ this.updateButtonLabel(size);
119029
+
119030
+ // Update popover selection
119031
+ this.sizeOptionsContainer.querySelectorAll('.size-option').forEach(opt => {
119032
+ opt.classList.remove('selected');
119033
+ opt.querySelector('input[type="radio"]').checked = false;
119034
+ });
119035
+
119036
+ // const selectedOption = this.sizeOptionsContainer.querySelector(`#quick-size-${size}`);
119037
+ const selectedOption = Array.from(this.sizeOptionsContainer.querySelectorAll('input[type="radio"]')).find(radio => radio.value === size);
119038
+ if (selectedOption) {
119039
+ selectedOption.closest('.size-option').classList.add('selected');
119040
+ selectedOption.checked = true;
119041
+ }
119042
+
119043
+ // Save to storage
119044
+ this.saveSettingsToStorage();
119045
+
119046
+ // Close popover
119047
+ this.closeImageSizePopover();
119048
+
119049
+ // Focus back to input
119050
+ this.promptInput.focus();
119051
+ }
119052
+
119053
+ /**
119054
+ * Toggle popover visibility
119055
+ */
119056
+ toggleImageSizePopover() {
119057
+ const isVisible = this.imageSizePopover.style.display !== 'none';
119058
+ if (isVisible) {
119059
+ this.closeImageSizePopover();
119060
+ } else {
119061
+ this.openImageSizePopover();
119062
+ }
119063
+ }
119064
+
119065
+ /**
119066
+ * Open the size popover
119067
+ */
119068
+ openImageSizePopover() {
119069
+ this.imageSizePopover.style.display = 'block';
119070
+ this.imageSizePopover.setAttribute('aria-hidden', 'false');
119071
+ this.quickImageSizeButton.setAttribute('aria-expanded', 'true');
119072
+
119073
+ // Focus first option
119074
+ const firstOption = this.sizeOptionsContainer.querySelector('.size-option');
119075
+ if (firstOption) {
119076
+ firstOption.focus();
119077
+ }
119078
+ }
119079
+
119080
+ /**
119081
+ * Close the size popover
119082
+ */
119083
+ closeImageSizePopover() {
119084
+ this.imageSizePopover.style.display = 'none';
119085
+ this.imageSizePopover.setAttribute('aria-hidden', 'true');
119086
+ this.quickImageSizeButton.setAttribute('aria-expanded', 'false');
119087
+ }
118370
119088
  out(s) {
118371
119089
  let result = this.builder.lang[s];
118372
119090
  if (result) return result;else return s;
@@ -118782,6 +119500,91 @@ Use grayscale for minimalist design.
118782
119500
 
118783
119501
  > **Editorial Style:** Stick to black, white, and grays for a clean, minimalist aesthetic. Use color sparingly for accents.
118784
119502
 
119503
+ ` + `
119504
+
119505
+ ### Animation
119506
+
119507
+ **Transition**
119508
+
119509
+ Classes: transition-none, transition, transition-colors, transition-opacity, transition-shadow, transition-transform, transition-all
119510
+
119511
+ **Duration**
119512
+
119513
+ Classes: duration-75, duration-100, duration-150, duration-200, duration-300, duration-500, duration-700, duration-1000, duration-1500
119514
+
119515
+ **Timing**
119516
+
119517
+ Classes: ease-linear, ease-in, ease-out, ease-in-out
119518
+
119519
+ **Delay**
119520
+
119521
+ Classes: delay-75, delay-100, delay-150, delay-200, delay-300, delay-500
119522
+
119523
+ **Scale**
119524
+
119525
+ Classes: scale-0, scale-50, scale-75, scale-90, scale-95, scale-100, scale-105, scale-110, scale-125, scale-150
119526
+
119527
+ **Hover Effect**
119528
+
119529
+ Classes: hover:scale-105
119530
+
119531
+ **Rotate**
119532
+
119533
+ Classes: rotate-0, rotate-45, rotate-90, rotate-180
119534
+
119535
+ **Translate X**
119536
+
119537
+ Classes: translate-x-0, translate-x-1, translate-x-2, translate-x-4, translate-x-8
119538
+
119539
+ **Translate Y**
119540
+
119541
+ Classes: translate-y-0, translate-y-1, translate-y-2, translate-y-4, translate-y-8
119542
+
119543
+ **Skew**
119544
+
119545
+ Classes: skew-x-0, skew-x-3, skew-x-6, skew-y-0, skew-y-3, skew-y-6
119546
+
119547
+ **Overflow**
119548
+
119549
+ Classes: overflow-hidden, overflow-visible, overflow-scroll, overflow-auto
119550
+
119551
+ **Opacity**
119552
+
119553
+ Classes:
119554
+
119555
+ | Class | Color |
119556
+ | ------------ | ------------- |
119557
+ | '.opacity-0' | opacity: 0 |
119558
+ | '.opacity-2' | opacity: 0.02 |
119559
+ | '.opacity-4' | opacity: 0.04 |
119560
+ | '.opacity-5' | opacity: 0.05 |
119561
+ | '.opacity-6' | opacity: 0.06 |
119562
+ | '.opacity-8' | opacity: 0.07 |
119563
+ | '.opacity-10' | opacity: 0.1 |
119564
+ | '.opacity-12' | opacity: 0.12 |
119565
+ | '.opacity-15' | opacity: 0.15 |
119566
+ | '.opacity-20' | opacity: 0.2 |
119567
+ | '.opacity-25' | opacity: 0.25 |
119568
+ | '.opacity-30' | opacity: 0.3 |
119569
+ | '.opacity-35' | opacity: 0.35 |
119570
+ | '.opacity-40' | opacity: 0.4 |
119571
+ | '.opacity-45' | opacity: 0.45 |
119572
+ | '.opacity-50' | opacity: 0.5 |
119573
+ | '.opacity-55' | opacity: 0.55 |
119574
+ | '.opacity-60' | opacity: 0.6 |
119575
+ | '.opacity-65' | opacity: 0.65 |
119576
+ | '.opacity-70' | opacity: 0.7 |
119577
+ | '.opacity-75' | opacity: 0.75 |
119578
+ | '.opacity-80' | opacity: 0.8 |
119579
+ | '.opacity-85' | opacity: 0.85 |
119580
+ | '.opacity-90' | opacity: 0.9 |
119581
+ | '.opacity-95' | opacity: 0.95 |
119582
+ | '.opacity-100' | opacity: 1 |
119583
+
119584
+ **Animation keyframes**
119585
+
119586
+ Classes: spin, ping, pulse, bounce
119587
+ ` + `
118785
119588
  ---
118786
119589
  ` + `
118787
119590
  ## Common Patterns
@@ -119058,6 +119861,31 @@ When creating icon-based features, benefits, or service sections: use Bootstrap
119058
119861
  <h4 class="size-18 font-medium text-center pb-3">Lightning Fast</h4>
119059
119862
  <p class="size-14 leading-16 text-gray-600 text-center">Description...</p>
119060
119863
 
119864
+ ### 9. Image Layout with Hover Effect
119865
+
119866
+ Use this pattern for all images (galleries, featured images, portfolio items):
119867
+
119868
+ <div class="w-full overflow-hidden relative bg-gray-50" style="aspect-ratio:16/9">
119869
+ <img src="..." alt="..." class="w-full h-full object-cover transition-transform duration-1500 hover:scale-105">
119870
+ </div>
119871
+
119872
+ **Key elements:**
119873
+ - 'aspect-ratio:16/9' - maintains proportions (adjust as needed: 1/1, 4/3, 3/2, 16/9, etc.)
119874
+ - 'overflow-hidden' - keeps scaled image contained
119875
+ - 'bg-gray-50' - placeholder while image loads
119876
+ - 'transition-transform duration-1000' - smooth 1-second animation
119877
+ - 'hover:scale-105' - subtle 5% scale on hover
119878
+ - 'object-cover' - ensures image fills container
119879
+
119880
+ **Common aspect ratios:**
119881
+ - Square: 'aspect-ratio:1/1'
119882
+ - Portrait: 'aspect-ratio:3/4' or 'aspect-ratio:2/3'
119883
+ - Landscape: 'aspect-ratio:4/3' or 'aspect-ratio:3/2'
119884
+ - Wide: 'aspect-ratio:16/9' or 'aspect-ratio:21/9'
119885
+ - Tall portrait: 'aspect-ratio:9/16'
119886
+
119887
+ Always include this hover effect for a polished, interactive feel.
119888
+
119061
119889
  `;
119062
119890
 
119063
119891
  const contextCodeBlock$1 = `
@@ -119240,6 +120068,191 @@ Remember: The goal is to create code blocks that are **self-contained**, **confl
119240
120068
  </div>
119241
120069
  `;
119242
120070
 
120071
+ const contextDesignGuide$1 = `
120072
+ # Design Directive:
120073
+
120074
+ ## Purpose
120075
+
120076
+ This guide complements the Content Framework documentation. It explains how to create **diverse, creative designs** that go beyond default aesthetics while strictly adhering to framework utility classes.
120077
+
120078
+ ## Core Philosophy: Design Freedom Within Constraints
120079
+
120080
+ The frameworks provide utility classes for structure and typography. To create rich, varied designs:
120081
+
120082
+ 1. **Use framework classes** for all structural elements
120083
+ 2. **Add inline styles** for colors, backgrounds, gradients, shadows, transforms, and animations
120084
+ 3. **Create embedded styles** in '<style>...</style>' blocks at the end when needed for animations, transitions, or reusable patterns
120085
+
120086
+ **Example <style>: When needed for animations, transitions, or reusable patterns**
120087
+
120088
+ <div class="row">
120089
+ <div class="column">
120090
+
120091
+ <!-- GUIDANCE: Use this layout to show images at any aspect ratio with a hover scale effect -->
120092
+ <div class="w-full overflow-hidden relative bg-gray-50" style="aspect-ratio:16/9">
120093
+ <img src="https://placehold.co/1200x1200/f5f5f5/999?text=Featured" alt="Featured Project" class="w-full h-full object-cover transition-transform duration-1500 hover:scale-105">
120094
+ </div>
120095
+
120096
+ </div>
120097
+ </div>
120098
+
120099
+ <div class="row">
120100
+ <div class="column">
120101
+
120102
+ <div class="featured-image">
120103
+ <img src="https://placehold.co/1600x700" alt="Featured Project">
120104
+ </div>
120105
+
120106
+ </div>
120107
+ </div>
120108
+
120109
+ <div class="row portfolio-row">
120110
+ <div class="column">
120111
+
120112
+ <div class="row-image">
120113
+ <img src="https://placehold.co/1200x900" alt="Digital Banking">
120114
+ </div>
120115
+
120116
+ </div>
120117
+ <div class="column">
120118
+
120119
+ <!-- Content -->
120120
+
120121
+ </div>
120122
+ </div>
120123
+
120124
+ <div class="row">
120125
+
120126
+ <div class="column">
120127
+
120128
+ <div class="p-12 bg-gray-50 hover-lift">
120129
+ <p class="size-18 leading-17 text-black pb-8">The level of professionalism and expertise demonstrated throughout the project was outstanding. Highly recommend to anyone looking for quality work.</p>
120130
+ <p class="size-14 font-semibold text-black pb-1">Michael Chen</p>
120131
+ <p class="size-13 text-gray-600 tracking-wide">Founder, Innovation Labs</p>
120132
+ </div>
120133
+
120134
+ </div>
120135
+ <div class="column">
120136
+
120137
+ <!-- Content -->
120138
+
120139
+ </div>
120140
+ </div>
120141
+ <style>
120142
+ /* Featured image */
120143
+
120144
+ .featured-image {
120145
+ width: 100%;
120146
+ aspect-ratio: 21/9;
120147
+ overflow: hidden;
120148
+ position: relative;
120149
+ background: #fafafa;
120150
+ }
120151
+
120152
+ .featured-image img {
120153
+ width: 100%;
120154
+ height: 100%;
120155
+ object-fit: cover;
120156
+ transition: transform 2s cubic-bezier(0.4, 0, 0.2, 1);
120157
+ }
120158
+
120159
+ .featured-image:hover img {
120160
+ transform: scale(1.03);
120161
+ }
120162
+
120163
+ /* Row image */
120164
+
120165
+ .row-image {
120166
+ width: 100%;
120167
+ aspect-ratio: 4/3;
120168
+ overflow: hidden;
120169
+ position: relative;
120170
+ background: #fafafa;
120171
+ }
120172
+
120173
+ .row-image img {
120174
+ width: 100%;
120175
+ height: 100%;
120176
+ object-fit: cover;
120177
+ transition: transform 1.4s cubic-bezier(0.4, 0, 0.2, 1);
120178
+ }
120179
+
120180
+ .portfolio-row:hover .row-image img {
120181
+ transform: scale(1.05);
120182
+ }
120183
+
120184
+ /* Smooth hover animations */
120185
+ .hover-lift {
120186
+ transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
120187
+ }
120188
+
120189
+ .hover-lift:hover {
120190
+ transform: translateY(-4px);
120191
+ }
120192
+ </style>
120193
+
120194
+
120195
+ ## Key Aesthetic Philosophy:
120196
+ **Swiss/Minimalist meets Portfolio Presentation** - Clean, grid-based, information-focused design where content hierarchy is created through scale, weight, and spacing rather than color or decoration. Every element serves a functional purpose. Photography is professional and high-quality. Typography creates drama through size contrast. The overall feel is sophisticated, professional, and timeless.
120197
+
120198
+ ## Design Characteristics:
120199
+
120200
+ ### Typography Treatment
120201
+ - **Extreme scale contrast**: Massive headlines (80px-140px) paired with tiny metadata text
120202
+ - **Version/number typography**: Large numbers (like "1.1", "2.4", "2027") used as design elements
120203
+ - **Mix of weights**: Bold project names with light secondary information
120204
+
120205
+ ### Layout Patterns
120206
+ - **Horizontal information bars**: Thin lines (1px-2px) separating sections or containing metadata
120207
+ - **Asymmetric content placement**: Image on left (40-45%), text/info on right (55-60%)
120208
+ - **Card-based presentation**: Content contained in white cards floating on gray backgrounds
120209
+ - **Top metadata strips**: Small text in corners (studio name, project type, year)
120210
+ - **Multi-column text blocks**: Body copy split into 2-3 narrow columns for readability
120211
+
120212
+ ### Image Treatment
120213
+ - **Photography as hero**: Large, high-quality images dominating the layout (not decorative)
120214
+ - **Clean crops**: Images in rectangular containers, no fancy shapes
120215
+ - **Generous padding**: White space around images, never cramping them
120216
+ - **Grid systems for galleries**: Even-sized thumbnails in rows (4x2, 4x4 grids)
120217
+ - **Product-on-white aesthetic**: Clean product photography on pure white backgrounds
120218
+
120219
+ ### Color & Background
120220
+ - **Pure white dominant**: Clean #ffffff backgrounds, not cream or off-white
120221
+ - **Minimal color usage**: Mostly black text on white, with occasional single accent color
120222
+ - **Light gray backgrounds**: #f5f5f5 or #e5e5e5 for page backgrounds behind white cards
120223
+ - **Strategic black sections**: Full black backgrounds for contrast moments
120224
+ - **Accent color blocks**: Small pops of color (red, orange) used sparingly as highlights
120225
+
120226
+ ### Information Architecture
120227
+ - **Numbered lists**: "1. OVERVIEW", "2. SERVICES", etc. as section headers
120228
+ - **Metadata in lines**: "Client Name | Project Type | Year" separated by pipes or lines
120229
+ - **Small label categories**: Uppercase 11px labels like "SPECIALISED IN", "CONTACT", "ABOUT"
120230
+ - **Horizontal dividing lines**: Thin rules (1px) creating visual sections
120231
+ - **List-style information**: Stacked items with consistent spacing and alignment
120232
+
120233
+ ### Spatial Organization
120234
+ - **Air and breathing room**: Generous margins and padding throughout
120235
+ - **Contained sections**: Content lives in defined rectangular areas
120236
+ - **Aligned grids**: Everything aligns to invisible grid lines
120237
+ - **Consistent gaps**: Equal spacing between elements (20px, 30px, 40px)
120238
+ - **Edge-to-edge in black sections**: Dark sections can be full-bleed
120239
+
120240
+ ### Professional Polish
120241
+ - **Precise alignment**: Everything lines up perfectly (left edges, baselines, top edges)
120242
+ - **Consistent typography system**: Limited font sizes used repeatedly (12px, 14px, 16px, 32px, 80px, 120px)
120243
+ - **No decorative elements**: Everything serves a purpose, nothing ornamental
120244
+ - **Clean hover states**: Understated interactions, not flashy
120245
+
120246
+ ### What to AVOID:
120247
+ - ❌ Gradients (use solid colors only)
120248
+ - ❌ Rounded corners on everything (keep rectangular, maybe subtle 4-8px radius on cards)
120249
+ - ❌ Drop shadows (use very subtle if any, like 0 2px 8px rgba(0,0,0,0.04))
120250
+ - ❌ Overly decorative fonts (stick to clean system-ui)
120251
+ - ❌ Busy patterns or textures
120252
+ - ❌ Too many accent colors (pick one, use sparingly)
120253
+ - ❌ Cramped spacing (always err on the side of more whitespace)
120254
+ `;
120255
+
119243
120256
  class ContentBuilder {
119244
120257
  constructor(opts = {}) {
119245
120258
  let defaults = {
@@ -119257,6 +120270,33 @@ class ContentBuilder {
119257
120270
  'snippets': []
119258
120271
  },
119259
120272
  screenMode: 'desktop',
120273
+ demoMode: false,
120274
+ /*
120275
+ demoConversations: [
120276
+ {
120277
+ role: 'user',
120278
+ content: 'Generate an image: "a house with beautiful mountain view. warm sunlight, golden-hour lighting, filmic teal-orange color grade".',
120279
+ // timestamp: Date.now() - 180000 // 3 minutes ago
120280
+ },
120281
+ {
120282
+ role: 'assistant',
120283
+ content: '✓ Image generated successfully',
120284
+ imagePreview: [
120285
+ { url: 'uploads/ai-8vnqv.png', context: 'Generate image of a house with a beautiful mountain view' },
120286
+ ]
120287
+ },
120288
+ {
120289
+ role: 'user',
120290
+ content: `Create a thoughtful article about finding home in extraordinary landscapes. Write vivid prose about mountain dwellings and how our environments shape the way we live.
120291
+ Use the generated image for this article, and present it as a premium design magazine feature.`,
120292
+ },
120293
+ {
120294
+ role: 'assistant',
120295
+ content: '✓ Create a thoughtful article about finding home in extraordinary landscapes with vivid prose about mountain dwellings and their impact on our lives, formatted as a premium design magazine feature.',
120296
+ }
120297
+ ],
120298
+ */
120299
+
119260
120300
  // Live Preview
119261
120301
  // previewURL: 'preview.html',
119262
120302
  onPreviewOpen: () => {
@@ -120526,7 +121566,7 @@ class ContentBuilder {
120526
121566
  this.ShortcutInfo = new ShortcutInfo(this);
120527
121567
  if (!this.opts.isContentBox) {
120528
121568
  this.codechat = new CodeChat$1({
120529
- context: contextContentFramework$1 + contextCodeBlock$1
121569
+ context: contextContentFramework$1 + contextCodeBlock$1 + contextDesignGuide$1
120530
121570
  }, this);
120531
121571
  if (this.startAIAssistant) {
120532
121572
  this.openAIAssistant();
@@ -143374,7 +144414,10 @@ class CodeChat {
143374
144414
  };
143375
144415
  this.builder = builder;
143376
144416
  const builderStuff = this.builder.builderStuff;
143377
- this.builderStuff = builderStuff;
144417
+ this.builderStuff = builderStuff; // Check if demo mode is enabled
144418
+
144419
+ this.isDemoMode = this.builder.demoMode || false;
144420
+ this.demoConversations = this.builder.demoConversations || [];
143378
144421
 
143379
144422
  const out = s => this.out(s); // Load saved settings or use defaults
143380
144423
 
@@ -143387,6 +144430,9 @@ class CodeChat {
143387
144430
  }
143388
144431
 
143389
144432
  this.codeModels = [{
144433
+ id: 'anthropic/claude-opus-4.5',
144434
+ label: 'Claude Opus 4.5'
144435
+ }, {
143390
144436
  id: 'google/gemini-3-pro-preview',
143391
144437
  label: 'Google Gemini 3 Pro Preview'
143392
144438
  }, {
@@ -143718,6 +144764,14 @@ class CodeChat {
143718
144764
  this.imageModels = this.builder.imageGenerationModels;
143719
144765
  }
143720
144766
 
144767
+ let inputPlaceholderText;
144768
+
144769
+ if (this.builder.editor) {
144770
+ inputPlaceholderText = out('e.g., Create a landing page for a creative studio');
144771
+ } else {
144772
+ inputPlaceholderText = out('e.g., Create an article about a productive home workspace');
144773
+ }
144774
+
143721
144775
  let html = `
143722
144776
  <style>
143723
144777
  #chatPanel {
@@ -144049,10 +145103,12 @@ class CodeChat {
144049
145103
  box-shadow: 6px 14px 20px 0px rgba(95, 95, 95, 0.11);
144050
145104
  z-index: 10005;
144051
145105
  display: none;
145106
+ pointer-events: auto; /* Reset cursor to default to prevent it from getting stuck */
144052
145107
  }
144053
145108
 
144054
145109
  #settingsDialog.open {
144055
145110
  display: block;
145111
+ pointer-events: auto; /* Reset cursor to default to prevent it from getting stuck */
144056
145112
  }
144057
145113
 
144058
145114
  #settingsDialog .settings-header {
@@ -144313,9 +145369,118 @@ class CodeChat {
144313
145369
  body.dark #chatPanel .copy-button:hover {
144314
145370
  background: rgba(255, 255, 255, 0.2);
144315
145371
  }
145372
+
145373
+
145374
+ /* Quick Image Size Button */
145375
+ #chatPanel .quick-image-size-button {
145376
+ width: 48px;
145377
+ height: 48px;
145378
+ border: none;
145379
+ border-radius: 8px;
145380
+ font-size: 13px;
145381
+ font-weight: 500;
145382
+ cursor: pointer;
145383
+ transition: background 0.2s;
145384
+ display: flex;
145385
+ align-items: center;
145386
+ justify-content: center;
145387
+ }
145388
+
145389
+ #chatPanel .quick-image-size-button:hover:not(:disabled) {
145390
+ background: rgba(0, 0, 0, 0.05);
145391
+ }
145392
+
145393
+ #chatPanel .quick-image-size-button:disabled {
145394
+ opacity: 0.6;
145395
+ cursor: not-allowed;
145396
+ }
145397
+
145398
+ body.dark #chatPanel .quick-image-size-button {
145399
+ background: #484848;
145400
+ }
145401
+
145402
+ body.dark #chatPanel .quick-image-size-button:hover:not(:disabled) {
145403
+ background: #4c4c4c;
145404
+ }
145405
+
145406
+ /* Image Size Popover */
145407
+ #chatPanel .image-size-popover {
145408
+ position: absolute;
145409
+ bottom: 72px;
145410
+ right: 68px;
145411
+ background: white;
145412
+ border: 1px solid #e5e5e5;
145413
+ border-radius: 8px;
145414
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
145415
+ z-index: 10006;
145416
+ min-width: 160px;
145417
+ max-height: 300px;
145418
+ overflow-y: auto;
145419
+ }
145420
+
145421
+ body.dark #chatPanel .image-size-popover {
145422
+ background: #3a3a3a;
145423
+ border-color: #555;
145424
+ }
145425
+
145426
+ #chatPanel .size-options {
145427
+ padding: 8px;
145428
+ }
145429
+
145430
+ #chatPanel .size-option {
145431
+ display: flex;
145432
+ align-items: center;
145433
+ padding: 10px 12px;
145434
+ cursor: pointer;
145435
+ border-radius: 6px;
145436
+ transition: background 0.2s;
145437
+ font-size: 14px;
145438
+ }
145439
+ #chatPanel .size-option label,
145440
+ #chatPanel .size-option input {
145441
+ cursor: pointer;
145442
+ }
145443
+
145444
+ #chatPanel .size-option:hover {
145445
+ background: rgba(0, 0, 0, 0.05);
145446
+ }
145447
+
145448
+ #chatPanel .size-option.selected {
145449
+ background: rgba(62, 147, 247, 0.1);
145450
+ font-weight: 500;
145451
+ }
145452
+
145453
+ #chatPanel .size-option input[type="radio"] {
145454
+ margin-right: 8px;
145455
+ }
145456
+
145457
+ body.dark #chatPanel .size-option:hover {
145458
+ background: rgba(255, 255, 255, 0.1);
145459
+ }
145460
+
145461
+ body.dark #chatPanel .size-option.selected {
145462
+ background: rgba(62, 147, 247, 0.2);
145463
+ }
145464
+
145465
+ #chatPanel .image-size-popover::-webkit-scrollbar {
145466
+ width: 6px;
145467
+ }
145468
+
145469
+ #chatPanel .image-size-popover::-webkit-scrollbar-track {
145470
+ background: transparent;
145471
+ }
145472
+
145473
+ #chatPanel .image-size-popover::-webkit-scrollbar-thumb {
145474
+ background: #e5e5e5;
145475
+ border-radius: 3px;
145476
+ }
145477
+
145478
+ body.dark #chatPanel .image-size-popover::-webkit-scrollbar-thumb {
145479
+ background: #555;
145480
+ }
144316
145481
  </style>
144317
145482
  <div
144318
- class="hidden"
145483
+ class="hidden keep-selection"
144319
145484
  id="chatPanel"
144320
145485
  role="dialog"
144321
145486
  aria-labelledby="chatPanelTitle"
@@ -144365,10 +145530,24 @@ class CodeChat {
144365
145530
  <label for="promptInput" class="sr-only">${out('Message input')}</label>
144366
145531
  <textarea
144367
145532
  id="promptInput"
144368
- placeholder="${out('e.g., Create a landing page and explain best practices for hero sections')}"
145533
+ placeholder="${inputPlaceholderText}"
144369
145534
  rows="1"
144370
145535
  aria-label="${out('Type your message')}"
144371
145536
  ></textarea>
145537
+
145538
+ <!-- NEW: Quick image size button -->
145539
+ <button
145540
+ id="quickImageSizeButton"
145541
+ type="button"
145542
+ class="quick-image-size-button"
145543
+ aria-label="${out('Select image size')}"
145544
+ aria-haspopup="true"
145545
+ aria-expanded="false"
145546
+ style="display: none;"
145547
+ >
145548
+
145549
+ </button>
145550
+
144372
145551
  <button
144373
145552
  id="sendButton"
144374
145553
  type="button"
@@ -144382,15 +145561,30 @@ class CodeChat {
144382
145561
  </svg>
144383
145562
  </button>
144384
145563
  </div>
145564
+
145565
+ <!-- NEW: Size selection popover -->
145566
+ <div
145567
+ id="imageSizePopover"
145568
+ class="image-size-popover"
145569
+ role="menu"
145570
+ aria-hidden="true"
145571
+ style="display: none;"
145572
+ >
145573
+ <div class="size-options" id="sizeOptionsContainer">
145574
+ <!-- Options populated dynamically -->
145575
+ </div>
145576
+ </div>
145577
+
144385
145578
  </div>
144386
145579
  </div>
144387
145580
 
144388
145581
  <!-- Settings Dialog Overlay -->
144389
- <div id="settingsOverlay" aria-hidden="true"></div>
145582
+ <div id="settingsOverlay" class="keep-selection" aria-hidden="true"></div>
144390
145583
 
144391
145584
  <!-- Settings Dialog -->
144392
145585
  <div
144393
145586
  id="settingsDialog"
145587
+ class="keep-selection"
144394
145588
  role="dialog"
144395
145589
  aria-labelledby="settingsTitle"
144396
145590
  aria-modal="true"
@@ -144520,11 +145714,17 @@ class CodeChat {
144520
145714
  this.codeModelSelect.value = this.settings.codeModel;
144521
145715
  this.chatModelSelect.value = this.settings.chatModel;
144522
145716
  this.imageModelSelect.value = this.settings.imageModel;
144523
- this.imageSizeSelect.value = this.settings.imageSize;
144524
- let isChatVisible = localStorage.getItem('chatPanelVisible') !== 'false';
145717
+ this.imageSizeSelect.value = this.settings.imageSize; // Check saved state - default to closed if never set
145718
+
145719
+ const savedState = localStorage.getItem('chatPanelVisible');
145720
+ let isChatVisible = savedState === 'true'; // Only open if explicitly set to 'true'
144525
145721
 
144526
145722
  if (!isChatVisible) {
144527
145723
  modal.classList.add('hidden');
145724
+ modal.setAttribute('aria-hidden', 'true');
145725
+ } else {
145726
+ modal.classList.remove('hidden');
145727
+ modal.removeAttribute('aria-hidden');
144528
145728
  }
144529
145729
 
144530
145730
  const btnClose = modal.querySelector('.close-button');
@@ -144608,8 +145808,43 @@ class CodeChat {
144608
145808
  this.promptInput = promptInput;
144609
145809
  this.closeChatButton = closeChatButton;
144610
145810
  this.sendButton = sendButton;
144611
- this.messagesContainer = messagesContainer;
145811
+ this.messagesContainer = messagesContainer; // NEW: Quick image size button elements
145812
+
145813
+ this.quickImageSizeButton = builderStuff.querySelector('#quickImageSizeButton');
145814
+ this.imageSizePopover = builderStuff.querySelector('#imageSizePopover');
145815
+ this.sizeOptionsContainer = builderStuff.querySelector('#sizeOptionsContainer'); // Quick Image Size Button Handlers
145816
+
145817
+ this.quickImageSizeButton.addEventListener('click', e => {
145818
+ e.stopPropagation();
145819
+ this.toggleImageSizePopover();
145820
+ }); // Close popover when clicking outside
145821
+
145822
+ document.addEventListener('click', e => {
145823
+ if (!this.imageSizePopover.contains(e.target) && !this.quickImageSizeButton.contains(e.target)) {
145824
+ this.closeImageSizePopover();
145825
+ }
145826
+ }); // Close popover on Escape
145827
+
145828
+ this.imageSizePopover.addEventListener('keydown', e => {
145829
+ if (e.key === 'Escape') {
145830
+ this.closeImageSizePopover();
145831
+ this.quickImageSizeButton.focus();
145832
+ }
145833
+ });
144612
145834
  this.renderImageOptions();
145835
+
145836
+ if (this.demoConversations) {
145837
+ this.loadConversations();
145838
+ }
145839
+
145840
+ if (this.isDemoMode) {
145841
+ this.addDemoBanner(); // Disable input in demo mode
145842
+
145843
+ this.promptInput.disabled = true; // this.promptInput.placeholder = out('Demo mode - Chat is read-only');
145844
+
145845
+ this.sendButton.disabled = true;
145846
+ this.messagesContainer.scrollTop = this.messagesContainer.scrollHeight;
145847
+ }
144613
145848
  }
144614
145849
 
144615
145850
  renderImageOptions() {
@@ -144712,7 +145947,9 @@ class CodeChat {
144712
145947
  }); // Trigger initial render with defaults
144713
145948
 
144714
145949
  modelSelect.value = defaultModelId;
144715
- renderSizes(defaultModelId, false);
145950
+ renderSizes(defaultModelId, false); // NEW: Initialize quick size button
145951
+
145952
+ this.updateQuickImageSizeButton();
144716
145953
  }
144717
145954
  /**
144718
145955
  * ============================================================================
@@ -144784,7 +146021,9 @@ class CodeChat {
144784
146021
  this.settingsOverlay.classList.remove('open');
144785
146022
  this.settingsOverlay.setAttribute('aria-hidden', 'true');
144786
146023
  this.settingsDialog.classList.remove('open');
144787
- this.settingsDialog.setAttribute('aria-hidden', 'true');
146024
+ this.settingsDialog.setAttribute('aria-hidden', 'true'); // Reset cursor to default to prevent it from getting stuck
146025
+
146026
+ document.body.style.cursor = '';
144788
146027
 
144789
146028
  if (this.settingsLastFocusedElement) {
144790
146029
  this.settingsLastFocusedElement.focus();
@@ -144798,7 +146037,9 @@ class CodeChat {
144798
146037
  this.settings.chatModel = this.chatModelSelect.value;
144799
146038
  this.settings.imageModel = this.imageModelSelect.value;
144800
146039
  this.settings.imageSize = this.imageSizeSelect.value;
144801
- this.saveSettingsToStorage();
146040
+ this.saveSettingsToStorage(); // NEW: Update quick button when settings change
146041
+
146042
+ this.updateQuickImageSizeButton();
144802
146043
  this.closeSettings();
144803
146044
  }
144804
146045
  /**
@@ -144833,6 +146074,106 @@ Your job:
144833
146074
  3. Determine what each image should depict based on surrounding content and context
144834
146075
  4. Create detailed image generation prompts for each image
144835
146076
 
146077
+ 📸 EDITORIAL STYLE GUIDE - Apply to all image prompts:
146078
+
146079
+ - Default aesthetic: Minimalist magazine-style (Kinfolk, Cereal, Vogue Living)
146080
+ - Composition: Clean, lots of negative space, well-balanced
146081
+
146082
+
146083
+ CREATIVE PROMPT ENHANCEMENT:
146084
+ When constructing image prompts, intelligently enrich basic descriptions using these proven cinematic stylers:
146085
+
146086
+ 1. **Golden Hour Cinematic**
146087
+ "Warm sunlight, golden-hour lighting. Low contrast with lifted shadows, ethereal glow throughout. Overall vibe: relaxed, coastal, sun-kissed, carefree, West Coast lifestyle, evoking warmth and endless summer."
146088
+ → Best for: Landscapes, architecture, outdoor scenes, lifestyle content
146089
+
146090
+ 2. **Morning Halo Effect**
146091
+ "The lighting is soft, morning sunlight coming, creating a 'halo' effect. Vibrant cinematic shot"
146092
+ → Best for: Portraits, people in action, outdoor activities
146093
+
146094
+ 3. **Natural Forest Serenity**
146095
+ "in a lush, green forest. The sunlight should be filtering through the trees, creating a serene and natural atmosphere"
146096
+ → Best for: Nature scenes, outdoor activities, wellness content
146097
+
146098
+ 4. **Coastal Summer Drama**
146099
+ "on a dramatic windswept coastline. The overall atmosphere is cheerful, breezy, and full of summer warmth"
146100
+ → Best for: Beach/coastal scenes, adventure, travel content
146101
+
146102
+ 5. **Modern Interior Elegance**
146103
+ "illuminated by warm, natural lighting. The background features a softly blurred modern interior with subtle lights and cool tones, adding depth without distraction"
146104
+ → Best for: Indoor portraits, product shots, professional/business settings
146105
+
146106
+ ENHANCEMENT STRATEGY:
146107
+ - Analyze the subject matter and context from the HTML/request
146108
+ - Select the most appropriate styler that matches the scene type
146109
+ - Check if user has specified lighting/time → DO NOT apply contradicting styler elements
146110
+ * Example: User says "daylight" → DO NOT add "golden-hour" or "sunset"
146111
+ - Aim for creative elevation while maintaining the core subject integrity
146112
+ - Blend the styler naturally into the prompt (adapt grammar, pronouns, context)
146113
+ - IF user is vague (no specific lighting/mood) → Apply full styler enhancement
146114
+ - User's explicit details ALWAYS take precedence over styler recommendations
146115
+ ` +
146116
+ /*
146117
+ ENHANCEMENT STRATEGY:
146118
+ - Analyze the subject matter and context from the HTML/request
146119
+ - Select the most appropriate styler that matches the scene type
146120
+ - Blend the styler naturally into the prompt (adapt grammar, pronouns, context)
146121
+ - If the subject already has specific lighting/atmosphere details, complement rather than override
146122
+ - Aim for creative elevation while maintaining the core subject integrity
146123
+ */
146124
+ `
146125
+ Examples of enriched prompts:
146126
+ - Basic: "a house with mountain view"
146127
+ → Enhanced: "a house with beautiful mountain view. Warm sunlight, golden-hour lighting. Low contrast with lifted shadows, ethereal glow throughout. Overall vibe: relaxed, coastal, sun-kissed, carefree, West Coast lifestyle, evoking warmth and endless summer."
146128
+
146129
+ - Basic: "person hiking"
146130
+ → Enhanced: "a person hiking in nature. The lighting is soft, morning sunlight coming, creating a 'halo' effect. Vibrant cinematic shot"
146131
+
146132
+ - Basic: "woman reading"
146133
+ → Enhanced: "a woman sitting comfortably and reading. Her face is illuminated by warm, natural lighting. The background features a softly blurred modern interior with subtle lights and cool tones, adding depth without distraction"
146134
+
146135
+ - Already detailed: "dramatic headshot with bokeh and rim lighting"
146136
+ → No enhancement needed (preserve user's vision)
146137
+
146138
+
146139
+ CONTEXT-SPECIFIC GUIDELINES:
146140
+
146141
+ Interior/Indoor scenes:
146142
+ - Lighting: Soft natural window light
146143
+ - Backdrop: white walls or neutral tones
146144
+ - Mood: Calm, serene, inviting, uncluttered
146145
+ - Reference: "Kinfolk interior photography"
146146
+
146147
+ People/Portraits/Activities:
146148
+ - Lighting: Natural diffused light, flattering and soft
146149
+ - Styling: Effortlessly elegant, contemporary casual
146150
+ - Pose: Candid, authentic moments, relaxed
146151
+ - Reference: "modern lifestyle editorial photography"
146152
+
146153
+ Products/Objects:
146154
+ - Lighting: Clean studio or soft natural light
146155
+ - Backdrop: Minimalist neutral, lots of breathing room
146156
+ - Mood: Refined simplicity, premium feel
146157
+ - Reference: "high-end editorial product photography"
146158
+
146159
+ Food/Culinary:
146160
+ - Lighting: Soft daylight, gentle overhead or 45° angle
146161
+ - Styling: Minimal props, artisan ceramics, linen
146162
+ - Mood: Fresh, appetizing, rustic-modern
146163
+ - Reference: "Bon Appétit editorial food styling"
146164
+
146165
+ Outdoor/Landscape:
146166
+ - Composition: Serene, contemplative, minimal
146167
+ - Mood: Calm atmosphere, breathing space, lifted brightness
146168
+ - Colors: Vibrant cinematic shot.
146169
+ - Reference: "editorial travel photography"
146170
+
146171
+ CRITICAL RULES:
146172
+ 1. If user specifies a style (cinematic, vintage, vibrant, etc.) → RESPECT IT, only add quality markers
146173
+ 2. If user is vague → APPLY FULL EDITORIAL TREATMENT based on subject category
146174
+ 3. Always maintain cohesive aesthetic across all images
146175
+ 4. Every prompt should feel like it belongs in a high-end lifestyle magazine
146176
+
144836
146177
  Respond with a JSON array with one entry per image to generate:
144837
146178
  [
144838
146179
  {
@@ -144937,7 +146278,12 @@ Response: [
144937
146278
 
144938
146279
 
144939
146280
  async sendMessage() {
144940
- const out = s => this.out(s);
146281
+ const out = s => this.out(s); // Prevent sending messages in demo mode
146282
+
146283
+
146284
+ if (this.isDemoMode) {
146285
+ return;
146286
+ }
144941
146287
 
144942
146288
  const prompt = this.promptInput.value.trim();
144943
146289
  if (!prompt) return;
@@ -145004,16 +146350,45 @@ Response: [
145004
146350
  this.addMessage('assistant', `✓ ${result.description}`);
145005
146351
  } else if (result.type === 'image') {
145006
146352
  if (result.imageDetails && result.imageDetails.length > 1) {
145007
- this.addMessage('assistant', `✓ Generated ${result.imageDetails.length} images successfully`);
146353
+ this.addMessage('assistant', `✓ Generated ${result.imageDetails.length} images successfully`); // this.addMessage('assistant', `✓ ${out('Generated {count} images successfully').replace('{count}', result.imageDetails.length)}`);
146354
+
145008
146355
  this.addImagePreview(result.imageDetails);
145009
146356
  } else {
145010
- this.addMessage('assistant', '✓ Generated image successfully');
146357
+ this.addMessage('assistant', '✓ Image generated successfully'); // this.addMessage('assistant', `✓ ${out('Image generated successfully')}`);
145011
146358
 
145012
146359
  if (result.generatedUrls && result.generatedUrls[0]) {
145013
146360
  this.addImagePreview([{
145014
146361
  url: result.generatedUrls[0],
145015
146362
  context: result.description
145016
- }]);
146363
+ }]); // If there is a selected image, replace with the new image
146364
+
146365
+ const url = result.generatedUrls[0];
146366
+
146367
+ if (this.builder.editor) {
146368
+ // ContentBox
146369
+ const img = this.builder.editor.activeImage;
146370
+
146371
+ if (img) {
146372
+ this.builder.editor.saveForUndo();
146373
+ img.addEventListener('load', () => {
146374
+ this.builder.editor.element.image.repositionImageTool();
146375
+ this.builder.editor.elmTool.repositionElementTool();
146376
+ });
146377
+ this.builder.editor.activeImage.setAttribute('src', url);
146378
+ }
146379
+ } else {
146380
+ // ContentBuilder
146381
+ const img = this.builder.activeImage;
146382
+
146383
+ if (img) {
146384
+ this.builder.uo.saveForUndo();
146385
+ img.addEventListener('load', () => {
146386
+ this.builder.element.image.repositionImageTool();
146387
+ this.builder.elmTool.repositionElementTool();
146388
+ });
146389
+ this.builder.activeImage.setAttribute('src', url);
146390
+ }
146391
+ }
145017
146392
  }
145018
146393
  }
145019
146394
  }
@@ -145071,14 +146446,14 @@ Response: [
145071
146446
  `;
145072
146447
  images.forEach(img => {
145073
146448
  previewHTML += `
145074
- <div style="border-radius: 8px; overflow: hidden; background: rgba(255,255,255,0.05);">
145075
- <img src="${img.url}" alt="${img.context || 'Generated image'}" style="width: 100%; height: 150px; object-fit: cover;" />
146449
+ <div style="border-radius: 6px; overflow: hidden; background: rgba(255,255,255,0.05);">
146450
+ <img src="${img.url}" alt="${img.context || 'Generated image'}" style="width: 100%;" />
145076
146451
  <div style="padding: 8px; font-size: 11px; opacity: 0.8;">
145077
146452
  ${img.context || ''}
145078
146453
  <a href="${img.url}" target="_blank" rel="noopener noreferrer" style="display: block; margin-top: 4px;">View</a>
145079
146454
  </div>
145080
146455
  </div>
145081
- `;
146456
+ `; // height: 150px; object-fit: cover;
145082
146457
  });
145083
146458
  previewHTML += '</div>';
145084
146459
  previewDiv.innerHTML = previewHTML;
@@ -145098,7 +146473,34 @@ Response: [
145098
146473
  ...this.builder.defaultHeaders
145099
146474
  }; // Check if image generation is enabled
145100
146475
 
145101
- const imageGenEnabled = this.builder.generateMediaUrl_Fal ? true : false;
146476
+ const imageGenEnabled = this.builder.generateMediaUrl_Fal ? true : false; // Build context about previously generated images
146477
+
146478
+ let imageHistoryContext = '';
146479
+ const previousImageResults = this.conversationResults.filter(r => r.type === 'image' && r.generatedUrls && r.generatedUrls.length > 0);
146480
+
146481
+ if (previousImageResults.length > 0) {
146482
+ imageHistoryContext = '\n\n=== PREVIOUSLY GENERATED IMAGES IN THIS CONVERSATION ===\n';
146483
+ imageHistoryContext += 'The following images were generated earlier in this conversation:\n\n';
146484
+ previousImageResults.forEach((result, idx) => {
146485
+ if (result.imageDetails && result.imageDetails.length > 0) {
146486
+ result.imageDetails.forEach((img, imgIdx) => {
146487
+ imageHistoryContext += `Generated Image ${idx + 1}.${imgIdx + 1}:\n`;
146488
+ imageHistoryContext += ` URL: ${img.url}\n`;
146489
+ imageHistoryContext += ` Context: ${img.context}\n`;
146490
+ imageHistoryContext += ` Description: ${result.description}\n\n`;
146491
+ });
146492
+ } else {
146493
+ result.generatedUrls.forEach((url, urlIdx) => {
146494
+ imageHistoryContext += `Generated Image ${idx + 1}.${urlIdx + 1}:\n`;
146495
+ imageHistoryContext += ` URL: ${url}\n`;
146496
+ imageHistoryContext += ` Description: ${result.description}\n\n`;
146497
+ });
146498
+ }
146499
+ });
146500
+ imageHistoryContext += '⚠️ IMPORTANT: If the user refers to "the generated image", "this image", "the image", or "previous image", they are referring to these images above.\n';
146501
+ imageHistoryContext += '⚠️ DO NOT create a new image task. Instead, create a CODE task that uses these existing URLs.\n\n';
146502
+ }
146503
+
145102
146504
  const classificationPrompt = `Analyze this user message and break it down into tasks.
145103
146505
 
145104
146506
  CURRENT HTML CONTEXT (use this to understand the page structure):
@@ -145106,11 +146508,19 @@ CURRENT HTML CONTEXT (use this to understand the page structure):
145106
146508
  ${this.builder.html()}
145107
146509
  \`\`\`
145108
146510
 
146511
+ ${imageHistoryContext}
146512
+
145109
146513
  IMPORTANT: This is a WEB BUILDER tool with chat capabilities. Valid requests are:
145110
146514
  - Creating/editing/removing HTML content (code tasks)
145111
146515
  ${imageGenEnabled ? '- Generating images for the webpage (image tasks)' : ''}
145112
146516
  - Asking questions or having conversations (chat tasks) - THIS CAN BE ABOUT ANYTHING
145113
146517
 
146518
+ CRITICAL CONTEXT RULE:
146519
+ - In a web builder, "create an article/blog/story/content" means CREATE A WEBPAGE (CODE task)
146520
+ - Only use CHAT task for questions, explanations, or requests for advice
146521
+ - If user says "create", "write", "make", "build" followed by content → CODE task
146522
+ - If user says "explain", "how do I", "what is", "tell me about" → CHAT task
146523
+
145114
146524
  ${imageGenEnabled ? '' : `
145115
146525
  ⚠️ AI IMAGE GENERATION IS CURRENTLY DISABLED
145116
146526
  - If user requests AI image generation/creation, explain that it's disabled
@@ -145180,6 +146590,18 @@ IMAGE TASK RULES:
145180
146590
  - IMPORTANT: Look at the HTML context to understand if multiple images are involved
145181
146591
  - If the target section has multiple images, indicate this in targetElement (e.g., "all 3 images in Culinary Delights section")
145182
146592
  - Image tasks should come BEFORE code tasks that depend on them
146593
+
146594
+ - CRITICAL: DO NOT create image task for these phrases (they mean REUSE existing):
146595
+ * "use the generated image" / "use the image"
146596
+ * "use this image" / "use existing image"
146597
+ * "with the generated image" / "with this image"
146598
+ * "with the previous image" / "with the last image"
146599
+ * "using the image I just created/generated"
146600
+ * Any reference to a PREVIOUS image from conversation history
146601
+ - Decision logic:
146602
+ * "Generate a new image of X" → CREATE image task (new generation)
146603
+ * "Create article using the generated image" → NO image task (reuse existing)
146604
+
145183
146605
  ` : ''}
145184
146606
 
145185
146607
  Examples:
@@ -145217,6 +146639,17 @@ Input: "Create a landing page about wood furniture workshop"
145217
146639
  Output: {"is_valid": true, "tasks": [{"type": "code", "description": "Create a landing page for a wood furniture workshop", "order": 1}], "is_mixed": false}
145218
146640
  Note: No image task created because user did not explicitly request AI image generation
145219
146641
 
146642
+ Input: "Create an inspiring article and use the generated image"
146643
+ Output: {"is_valid": true, "tasks": [{"type": "code", "description": "Create article using previously generated image from conversation history", "order": 1}], "is_mixed": false}
146644
+ Note: No image task because user wants to REUSE existing image
146645
+
146646
+ Input: "Write a blog post about productivity and use this image"
146647
+ Output: {"is_valid": true, "tasks": [{"type": "code", "description": "Create blog post using previously generated image", "order": 1}], "is_mixed": false}
146648
+
146649
+ Input: "Generate another mountain image and create a landing page"
146650
+ Output: {"tasks": [{"type": "image", "description": "Generate new mountain image", "imagePrompt": "...", "order": 1}, {"type": "code", "description": "Create landing page with new image", "order": 2}], "is_mixed": true}
146651
+ Note: "another" and "generate" indicate NEW image generation
146652
+
145220
146653
  ` : `
145221
146654
  Input: "Generate an AI image of a mountain"
145222
146655
  Output: {"is_valid": false, "reason": "AI image generation is currently disabled.", "tasks": [], "is_mixed": false}
@@ -145299,8 +146732,9 @@ Output: {"is_valid": false, "reason": "AI image generation is currently disabled
145299
146732
 
145300
146733
  if (imageTasksFromThisRequest.length > 0) {
145301
146734
  hasGeneratedImages = true;
145302
- imageContext = '\n\n=== GENERATED IMAGE URLS (USE THESE FOR YOUR TASK) ===\n';
145303
- imageTasksFromThisRequest.forEach(imageTask => {
146735
+ imageContext = '\n\n=== GENERATED IMAGE URLS (USE THESE FOR YOUR TASK) ===\n'; // ⭐ REVERSE to show most recent first
146736
+
146737
+ imageTasksFromThisRequest.reverse().forEach(imageTask => {
145304
146738
  if (imageTask.imageDetails && imageTask.imageDetails.length > 0) {
145305
146739
  // Multiple images with context
145306
146740
  imageTask.imageDetails.forEach((img, idx) => {
@@ -145315,7 +146749,8 @@ Output: {"is_valid": false, "reason": "AI image generation is currently disabled
145315
146749
  });
145316
146750
  }
145317
146751
  });
145318
- imageContext += '\n⚠️ IMPORTANT: Use these generated image URLs (not placehold.co or other sources) for the specific images mentioned in your task.\n';
146752
+ imageContext += '\n⚠️ IMPORTANT: Use the FIRST image URL listed above (most recent) unless the task specifically asks for a different image.\n';
146753
+ imageContext += '⚠️ The first image is the MOST RECENTLY GENERATED and should be used by default.\n';
145319
146754
  imageContext += '⚠️ Your task description specifies WHICH images to update - follow it precisely.\n';
145320
146755
  } // ⭐ ALSO CHECK FOR CHAT RESULTS FROM PREVIOUS TASKS
145321
146756
 
@@ -145414,6 +146849,10 @@ ${this.builder.html()}
145414
146849
  TASK: ${task.description}
145415
146850
 
145416
146851
  IMPORTANT: Follow the Best Practices in Content framework.
146852
+ ${this.builder.editor ? `CRITICAL: This project uses the Content.css and Box framework, NOT Tailwind. ONLY use classes explicitly documented in this framework guide.
146853
+ DO NOT use Tailwind classes like gap-4, w-1/2, top-4, hover:scale-120, etc.` : `CRITICAL: This project uses the Content.css framework, NOT Tailwind. ONLY use classes explicitly documented in this framework guide.
146854
+ DO NOT use Tailwind classes like gap-4, w-1/2, top-4, hover:scale-120, etc.`}
146855
+ For flexibility, you can use inline styles or embedded <style> at the end of the generated HTML.
145417
146856
 
145418
146857
  ${imageContext}
145419
146858
  ${chatContext}
@@ -145894,6 +147333,320 @@ ${this.builder.html()}
145894
147333
  document.body.removeChild(announcement);
145895
147334
  }, 1000);
145896
147335
  }
147336
+ /**
147337
+ * ============================================================================
147338
+ * DEMO MODE
147339
+ * ============================================================================
147340
+ */
147341
+
147342
+
147343
+ loadConversations() {
147344
+ // Load demo conversations
147345
+ if (this.demoConversations && this.demoConversations.length > 0) {
147346
+ this.demoConversations.forEach(msg => {
147347
+ if (msg.role === 'user') {
147348
+ this.addMessage('user', msg.content);
147349
+ } else if (msg.role === 'assistant') {
147350
+ this.addMessage('assistant', msg.content, true); // Add image preview if available
147351
+
147352
+ if (msg.imagePreview && msg.imagePreview.length > 0) {
147353
+ this.addImagePreview(msg.imagePreview); // Include in conversationResults with correct structure
147354
+
147355
+ this.conversationResults.push({
147356
+ type: 'image',
147357
+ description: 'Previously generated image from demo',
147358
+ content: `Generated ${msg.imagePreview.length} images`,
147359
+ generatedUrls: msg.imagePreview.map(img => img.url),
147360
+ // Array of URLs
147361
+ imageDetails: msg.imagePreview.map(img => ({
147362
+ // Array of objects
147363
+ url: img.url,
147364
+ context: img.context,
147365
+ prompt: img.context || '' // Use context as prompt if no prompt available
147366
+
147367
+ })),
147368
+ targetElement: 'demo images'
147369
+ });
147370
+ }
147371
+ }
147372
+ });
147373
+ }
147374
+ }
147375
+
147376
+ addDemoBanner() {
147377
+ const out = s => this.out(s);
147378
+
147379
+ const banner = document.createElement('div');
147380
+ banner.className = 'demo-banner';
147381
+ banner.innerHTML = `
147382
+ <div style="
147383
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
147384
+ color: white;
147385
+ padding: 12px 16px;
147386
+ border-radius: 8px;
147387
+ font-size: 13px;
147388
+ display: flex;
147389
+ align-items: center;
147390
+ gap: 8px;
147391
+ box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
147392
+ ">
147393
+ <span style="font-size: 18px;">📖</span>
147394
+ <div>
147395
+ <strong>${out('Read-Only Demo')}</strong>
147396
+ <div style="font-size: 11px; opacity: 0.9; margin-top: 2px;">
147397
+ ${out('Full AI chat available in the complete version.')}
147398
+ </div>
147399
+ </div>
147400
+ </div>
147401
+ `; // Insert banner at the top of messages container
147402
+ // this.messagesContainer.insertBefore(banner, this.messagesContainer.firstChild);
147403
+
147404
+ this.messagesContainer.appendChild(banner);
147405
+ }
147406
+ /**
147407
+ * Update the quick image size button based on current model and settings
147408
+ */
147409
+
147410
+
147411
+ updateQuickImageSizeButton() {
147412
+ const imageGenEnabled = this.builder.generateMediaUrl_Fal ? true : false;
147413
+
147414
+ if (!imageGenEnabled) {
147415
+ this.quickImageSizeButton.style.display = 'none';
147416
+ return;
147417
+ }
147418
+
147419
+ const currentModelId = this.settings.imageModel;
147420
+ const availableSizes = this.getSizesForModel(currentModelId); // Hide button if model has no size options
147421
+
147422
+ if (!availableSizes || availableSizes.length === 0) {
147423
+ this.quickImageSizeButton.style.display = 'none';
147424
+ return;
147425
+ } // Show button
147426
+
147427
+
147428
+ this.quickImageSizeButton.style.display = 'flex'; // Smart selection: check if current size is available
147429
+
147430
+ let selectedSize = this.settings.imageSize;
147431
+
147432
+ if (!availableSizes.includes(selectedSize)) {
147433
+ // Current size not available, find best fallback
147434
+ selectedSize = this.findBestFallback(selectedSize, availableSizes);
147435
+ this.settings.imageSize = selectedSize; // Sync with settings dialog
147436
+
147437
+ this.imageSizeSelect.value = selectedSize; // Save to storage
147438
+
147439
+ this.saveSettingsToStorage();
147440
+ } // Update button label
147441
+
147442
+
147443
+ this.updateButtonLabel(selectedSize); // Populate popover options
147444
+
147445
+ this.populateSizePopover(availableSizes, selectedSize);
147446
+ }
147447
+ /**
147448
+ * Get available sizes for a model (reuse existing logic)
147449
+ */
147450
+
147451
+
147452
+ getSizesForModel(modelId) {
147453
+ const model = this.imageModels.find(m => m.id === modelId);
147454
+ if (!model) return null; // if sizes explicitly empty array → means no size options
147455
+
147456
+ if (Array.isArray(model.sizes) && model.sizes.length === 0) {
147457
+ return null;
147458
+ }
147459
+
147460
+ const defaultSizes = ['square', 'square_hd', 'landscape_4_3', 'landscape_16_9', 'portrait_4_3', 'portrait_16_9'];
147461
+ return model.sizes || defaultSizes;
147462
+ }
147463
+ /**
147464
+ * Find best fallback size when current selection is not available
147465
+ */
147466
+
147467
+
147468
+ findBestFallback(preferredSize, availableSizes) {
147469
+ // Strategy 1: Try to match orientation
147470
+ const orientation = this.getOrientation(preferredSize);
147471
+ const matchingOrientation = availableSizes.find(size => this.getOrientation(size) === orientation);
147472
+ if (matchingOrientation) return matchingOrientation; // Strategy 2: Common defaults
147473
+
147474
+ if (availableSizes.includes('16:9')) return '16:9';
147475
+ if (availableSizes.includes('landscape_16_9')) return 'landscape_16_9';
147476
+ if (availableSizes.includes('1:1')) return '1:1';
147477
+ if (availableSizes.includes('square')) return 'square'; // Strategy 3: First available
147478
+
147479
+ return availableSizes[0];
147480
+ }
147481
+ /**
147482
+ * Determine orientation from size value
147483
+ */
147484
+
147485
+
147486
+ getOrientation(size) {
147487
+ const landscape = ['16:9', '21:9', '4:3', '3:2', '5:4', 'landscape_16_9', 'landscape_4_3'];
147488
+ const portrait = ['9:16', '9:21', '3:4', '2:3', '4:5', 'portrait_16_9', 'portrait_4_3'];
147489
+ if (landscape.includes(size)) return 'landscape';
147490
+ if (portrait.includes(size)) return 'portrait';
147491
+ return 'square';
147492
+ }
147493
+ /**
147494
+ * Update button label to show current size
147495
+ */
147496
+
147497
+
147498
+ updateButtonLabel(sizeValue) {
147499
+ if (!sizeValue) {
147500
+ this.quickImageSizeButton.textContent = '□';
147501
+ this.quickImageSizeButton.setAttribute('aria-label', this.out('Select image size'));
147502
+ return;
147503
+ } // Convert verbose labels to short form
147504
+
147505
+
147506
+ const labelMap = {
147507
+ 'landscape_16_9': '16:9',
147508
+ 'landscape_4_3': '4:3',
147509
+ 'portrait_16_9': '9:16',
147510
+ 'portrait_4_3': '3:4',
147511
+ 'square': '1:1',
147512
+ 'square_hd': '1:1 HD'
147513
+ };
147514
+ const displayLabel = labelMap[sizeValue] || sizeValue;
147515
+ this.quickImageSizeButton.textContent = displayLabel;
147516
+ this.quickImageSizeButton.setAttribute('aria-label', `${this.out('Image size')}: ${displayLabel}`);
147517
+ }
147518
+ /**
147519
+ * Populate the size popover with available options
147520
+ */
147521
+
147522
+
147523
+ populateSizePopover(sizes, selectedSize) {
147524
+ const out = s => this.out(s);
147525
+
147526
+ const sizeDefs = {
147527
+ 'square_hd': out('Square HD'),
147528
+ 'square': out('Square'),
147529
+ 'landscape_4_3': out('Landscape 4x3'),
147530
+ 'landscape_16_9': out('Landscape 16x9'),
147531
+ 'portrait_4_3': out('Portrait 3x4'),
147532
+ 'portrait_16_9': out('Portrait 9x16'),
147533
+ '1:1': out('Square'),
147534
+ '3:2': out('Landscape 3x2'),
147535
+ '4:3': out('Landscape 4x3'),
147536
+ '5:4': out('Landscape 5x4'),
147537
+ '16:9': out('Landscape 16x9'),
147538
+ '21:9': out('Landscape 21:9'),
147539
+ '2:3': out('Portrait 2x3'),
147540
+ '3:4': out('Portrait 3x4'),
147541
+ '4:5': out('Portrait 4x5'),
147542
+ '9:16': out('Portrait 9x16'),
147543
+ '9:21': out('Portrait 9:21')
147544
+ };
147545
+ this.sizeOptionsContainer.innerHTML = '';
147546
+ sizes.forEach(size => {
147547
+ const option = document.createElement('div');
147548
+ option.className = 'size-option' + (size === selectedSize ? ' selected' : '');
147549
+ option.setAttribute('role', 'menuitem');
147550
+ option.setAttribute('tabindex', '0');
147551
+ const radio = document.createElement('input');
147552
+ radio.type = 'radio';
147553
+ radio.name = 'quickImageSize';
147554
+ radio.value = size;
147555
+ radio.checked = size === selectedSize;
147556
+ radio.id = `quick-size-${size}`;
147557
+ const label = document.createElement('label');
147558
+ label.htmlFor = `quick-size-${size}`;
147559
+ label.textContent = sizeDefs[size] || size; // label.style.cursor = 'pointer';
147560
+
147561
+ label.style.flex = '1';
147562
+ option.appendChild(radio);
147563
+ option.appendChild(label); // Click handler
147564
+
147565
+ option.addEventListener('click', () => {
147566
+ this.selectImageSize(size);
147567
+ }); // Keyboard handler
147568
+
147569
+ option.addEventListener('keydown', e => {
147570
+ if (e.key === 'Enter' || e.key === ' ') {
147571
+ e.preventDefault();
147572
+ this.selectImageSize(size);
147573
+ }
147574
+ });
147575
+ this.sizeOptionsContainer.appendChild(option);
147576
+ });
147577
+ }
147578
+ /**
147579
+ * Handle size selection
147580
+ */
147581
+
147582
+
147583
+ selectImageSize(size) {
147584
+ // Update settings
147585
+ this.settings.imageSize = size; // Sync with settings dialog
147586
+
147587
+ this.imageSizeSelect.value = size; // Update button label
147588
+
147589
+ this.updateButtonLabel(size); // Update popover selection
147590
+
147591
+ this.sizeOptionsContainer.querySelectorAll('.size-option').forEach(opt => {
147592
+ opt.classList.remove('selected');
147593
+ opt.querySelector('input[type="radio"]').checked = false;
147594
+ }); // const selectedOption = this.sizeOptionsContainer.querySelector(`#quick-size-${size}`);
147595
+
147596
+ const selectedOption = Array.from(this.sizeOptionsContainer.querySelectorAll('input[type="radio"]')).find(radio => radio.value === size);
147597
+
147598
+ if (selectedOption) {
147599
+ selectedOption.closest('.size-option').classList.add('selected');
147600
+ selectedOption.checked = true;
147601
+ } // Save to storage
147602
+
147603
+
147604
+ this.saveSettingsToStorage(); // Close popover
147605
+
147606
+ this.closeImageSizePopover(); // Focus back to input
147607
+
147608
+ this.promptInput.focus();
147609
+ }
147610
+ /**
147611
+ * Toggle popover visibility
147612
+ */
147613
+
147614
+
147615
+ toggleImageSizePopover() {
147616
+ const isVisible = this.imageSizePopover.style.display !== 'none';
147617
+
147618
+ if (isVisible) {
147619
+ this.closeImageSizePopover();
147620
+ } else {
147621
+ this.openImageSizePopover();
147622
+ }
147623
+ }
147624
+ /**
147625
+ * Open the size popover
147626
+ */
147627
+
147628
+
147629
+ openImageSizePopover() {
147630
+ this.imageSizePopover.style.display = 'block';
147631
+ this.imageSizePopover.setAttribute('aria-hidden', 'false');
147632
+ this.quickImageSizeButton.setAttribute('aria-expanded', 'true'); // Focus first option
147633
+
147634
+ const firstOption = this.sizeOptionsContainer.querySelector('.size-option');
147635
+
147636
+ if (firstOption) {
147637
+ firstOption.focus();
147638
+ }
147639
+ }
147640
+ /**
147641
+ * Close the size popover
147642
+ */
147643
+
147644
+
147645
+ closeImageSizePopover() {
147646
+ this.imageSizePopover.style.display = 'none';
147647
+ this.imageSizePopover.setAttribute('aria-hidden', 'true');
147648
+ this.quickImageSizeButton.setAttribute('aria-expanded', 'false');
147649
+ }
145897
147650
 
145898
147651
  out(s) {
145899
147652
  let result = this.builder.lang[s];
@@ -146311,6 +148064,91 @@ Use grayscale for minimalist design.
146311
148064
 
146312
148065
  > **Editorial Style:** Stick to black, white, and grays for a clean, minimalist aesthetic. Use color sparingly for accents.
146313
148066
 
148067
+ ` + `
148068
+
148069
+ ### Animation
148070
+
148071
+ **Transition**
148072
+
148073
+ Classes: transition-none, transition, transition-colors, transition-opacity, transition-shadow, transition-transform, transition-all
148074
+
148075
+ **Duration**
148076
+
148077
+ Classes: duration-75, duration-100, duration-150, duration-200, duration-300, duration-500, duration-700, duration-1000, duration-1500
148078
+
148079
+ **Timing**
148080
+
148081
+ Classes: ease-linear, ease-in, ease-out, ease-in-out
148082
+
148083
+ **Delay**
148084
+
148085
+ Classes: delay-75, delay-100, delay-150, delay-200, delay-300, delay-500
148086
+
148087
+ **Scale**
148088
+
148089
+ Classes: scale-0, scale-50, scale-75, scale-90, scale-95, scale-100, scale-105, scale-110, scale-125, scale-150
148090
+
148091
+ **Hover Effect**
148092
+
148093
+ Classes: hover:scale-105
148094
+
148095
+ **Rotate**
148096
+
148097
+ Classes: rotate-0, rotate-45, rotate-90, rotate-180
148098
+
148099
+ **Translate X**
148100
+
148101
+ Classes: translate-x-0, translate-x-1, translate-x-2, translate-x-4, translate-x-8
148102
+
148103
+ **Translate Y**
148104
+
148105
+ Classes: translate-y-0, translate-y-1, translate-y-2, translate-y-4, translate-y-8
148106
+
148107
+ **Skew**
148108
+
148109
+ Classes: skew-x-0, skew-x-3, skew-x-6, skew-y-0, skew-y-3, skew-y-6
148110
+
148111
+ **Overflow**
148112
+
148113
+ Classes: overflow-hidden, overflow-visible, overflow-scroll, overflow-auto
148114
+
148115
+ **Opacity**
148116
+
148117
+ Classes:
148118
+
148119
+ | Class | Color |
148120
+ | ------------ | ------------- |
148121
+ | '.opacity-0' | opacity: 0 |
148122
+ | '.opacity-2' | opacity: 0.02 |
148123
+ | '.opacity-4' | opacity: 0.04 |
148124
+ | '.opacity-5' | opacity: 0.05 |
148125
+ | '.opacity-6' | opacity: 0.06 |
148126
+ | '.opacity-8' | opacity: 0.07 |
148127
+ | '.opacity-10' | opacity: 0.1 |
148128
+ | '.opacity-12' | opacity: 0.12 |
148129
+ | '.opacity-15' | opacity: 0.15 |
148130
+ | '.opacity-20' | opacity: 0.2 |
148131
+ | '.opacity-25' | opacity: 0.25 |
148132
+ | '.opacity-30' | opacity: 0.3 |
148133
+ | '.opacity-35' | opacity: 0.35 |
148134
+ | '.opacity-40' | opacity: 0.4 |
148135
+ | '.opacity-45' | opacity: 0.45 |
148136
+ | '.opacity-50' | opacity: 0.5 |
148137
+ | '.opacity-55' | opacity: 0.55 |
148138
+ | '.opacity-60' | opacity: 0.6 |
148139
+ | '.opacity-65' | opacity: 0.65 |
148140
+ | '.opacity-70' | opacity: 0.7 |
148141
+ | '.opacity-75' | opacity: 0.75 |
148142
+ | '.opacity-80' | opacity: 0.8 |
148143
+ | '.opacity-85' | opacity: 0.85 |
148144
+ | '.opacity-90' | opacity: 0.9 |
148145
+ | '.opacity-95' | opacity: 0.95 |
148146
+ | '.opacity-100' | opacity: 1 |
148147
+
148148
+ **Animation keyframes**
148149
+
148150
+ Classes: spin, ping, pulse, bounce
148151
+ ` + `
146314
148152
  ---
146315
148153
  ` + `
146316
148154
  ## Common Patterns
@@ -146587,6 +148425,31 @@ When creating icon-based features, benefits, or service sections: use Bootstrap
146587
148425
  <h4 class="size-18 font-medium text-center pb-3">Lightning Fast</h4>
146588
148426
  <p class="size-14 leading-16 text-gray-600 text-center">Description...</p>
146589
148427
 
148428
+ ### 9. Image Layout with Hover Effect
148429
+
148430
+ Use this pattern for all images (galleries, featured images, portfolio items):
148431
+
148432
+ <div class="w-full overflow-hidden relative bg-gray-50" style="aspect-ratio:16/9">
148433
+ <img src="..." alt="..." class="w-full h-full object-cover transition-transform duration-1500 hover:scale-105">
148434
+ </div>
148435
+
148436
+ **Key elements:**
148437
+ - 'aspect-ratio:16/9' - maintains proportions (adjust as needed: 1/1, 4/3, 3/2, 16/9, etc.)
148438
+ - 'overflow-hidden' - keeps scaled image contained
148439
+ - 'bg-gray-50' - placeholder while image loads
148440
+ - 'transition-transform duration-1000' - smooth 1-second animation
148441
+ - 'hover:scale-105' - subtle 5% scale on hover
148442
+ - 'object-cover' - ensures image fills container
148443
+
148444
+ **Common aspect ratios:**
148445
+ - Square: 'aspect-ratio:1/1'
148446
+ - Portrait: 'aspect-ratio:3/4' or 'aspect-ratio:2/3'
148447
+ - Landscape: 'aspect-ratio:4/3' or 'aspect-ratio:3/2'
148448
+ - Wide: 'aspect-ratio:16/9' or 'aspect-ratio:21/9'
148449
+ - Tall portrait: 'aspect-ratio:9/16'
148450
+
148451
+ Always include this hover effect for a polished, interactive feel.
148452
+
146590
148453
  `;
146591
148454
 
146592
148455
  const contextBoxFramework = `
@@ -147355,45 +149218,120 @@ The frameworks provide utility classes for structure and typography. To create r
147355
149218
  <!-- Content -->
147356
149219
  </div>
147357
149220
 
147358
- <!-- More sections... -->
149221
+ <!-- Section 3 -->
149222
+ <div class="is-section is-box is-section-100 type-system-ui">
149223
+
149224
+ <div class="is-container leading-14 size-18 is-content-1100">
149225
+
149226
+ <div class="row">
149227
+ <div class="column">
149228
+
149229
+ <!-- GUIDANCE: Use this layout to show images at any aspect ratio with a hover scale effect -->
149230
+ <div class="w-full overflow-hidden relative bg-gray-50" style="aspect-ratio:16/9">
149231
+ <img src="https://placehold.co/1200x1200/f5f5f5/999?text=Featured" alt="Featured Project" class="w-full h-full object-cover transition-transform duration-1500 hover:scale-105">
149232
+ </div>
149233
+
149234
+ </div>
149235
+ </div>
149236
+
149237
+ <div class="row">
149238
+ <div class="column">
149239
+
149240
+ <div class="featured-image">
149241
+ <img src="https://placehold.co/1600x700" alt="Featured Project">
149242
+ </div>
149243
+
149244
+ </div>
149245
+ </div>
149246
+
149247
+ <div class="row portfolio-row">
149248
+ <div class="column">
149249
+
149250
+ <div class="row-image">
149251
+ <img src="https://placehold.co/1200x900" alt="Digital Banking">
149252
+ </div>
149253
+
149254
+ </div>
149255
+ <div class="column">
149256
+
149257
+ <!-- Content -->
149258
+
149259
+ </div>
149260
+ </div>
149261
+
149262
+ <div class="row">
149263
+
149264
+ <div class="column">
149265
+
149266
+ <div class="p-12 bg-gray-50 hover-lift">
149267
+ <p class="size-18 leading-17 text-black pb-8">The level of professionalism and expertise demonstrated throughout the project was outstanding. Highly recommend to anyone looking for quality work.</p>
149268
+ <p class="size-14 font-semibold text-black pb-1">Michael Chen</p>
149269
+ <p class="size-13 text-gray-600 tracking-wide">Founder, Innovation Labs</p>
149270
+ </div>
149271
+
149272
+ </div>
149273
+ <div class="column">
149274
+
149275
+ <!-- Content -->
147359
149276
 
147360
- <!-- Embedded styles if needed -->
149277
+ </div>
149278
+ </div>
149279
+ </div>
149280
+ </div>
149281
+
149282
+ <!-- Embedded Styke -->
147361
149283
  <style>
147362
- @keyframes pulse {
147363
- 0%,
147364
- 100% {
147365
- opacity: 1;
147366
- transform: scale(1);
147367
- }
147368
- 50% {
147369
- opacity: 0.8;
149284
+ /* Subtle hover zoom for background images */
149285
+ .is-overlay-bg {
149286
+ transition: transform 1s ease;
149287
+ }
149288
+ .is-box:hover .is-overlay-bg {
147370
149289
  transform: scale(1.05);
147371
149290
  }
147372
- }
147373
149291
 
147374
- @keyframes scroll {
147375
- 0%,
147376
- 100% {
147377
- transform: translateY(0);
147378
- opacity: 0;
149292
+ /* Featured image */
149293
+ .featured-image {
149294
+ width: 100%;
149295
+ aspect-ratio: 21/9;
149296
+ overflow: hidden;
149297
+ position: relative;
149298
+ background: #fafafa;
147379
149299
  }
147380
- 50% {
147381
- opacity: 1;
149300
+ .featured-image img {
149301
+ width: 100%;
149302
+ height: 100%;
149303
+ object-fit: cover;
149304
+ transition: transform 2s cubic-bezier(0.4, 0, 0.2, 1);
149305
+ }
149306
+ .featured-image:hover img {
149307
+ transform: scale(1.03);
147382
149308
  }
147383
- }
147384
149309
 
147385
- @keyframes rotate {
147386
- from {
147387
- transform: rotate(0deg);
149310
+ /* Row image */
149311
+ .row-image {
149312
+ width: 100%;
149313
+ aspect-ratio: 4/3;
149314
+ overflow: hidden;
149315
+ position: relative;
149316
+ background: #fafafa;
147388
149317
  }
147389
- to {
147390
- transform: rotate(360deg);
149318
+ .row-image img {
149319
+ width: 100%;
149320
+ height: 100%;
149321
+ object-fit: cover;
149322
+ transition: transform 1.4s cubic-bezier(0.4, 0, 0.2, 1);
149323
+ }
149324
+ .portfolio-row:hover .row-image img {
149325
+ transform: scale(1.05);
147391
149326
  }
147392
- }
147393
149327
 
147394
- .custom-hover-effect {
147395
- transition: all 0.3s ease;
147396
- }
149328
+ /* Smooth hover animations */
149329
+ .hover-lift {
149330
+ transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
149331
+ }
149332
+ .hover-lift:hover {
149333
+ transform: translateY(-4px);
149334
+ }
147397
149335
  </style>
147398
149336
 
147399
149337
 
@@ -147425,9 +149363,9 @@ The frameworks provide utility classes for structure and typography. To create r
147425
149363
  - **Pure white dominant**: Clean #ffffff backgrounds, not cream or off-white
147426
149364
  - **Minimal color usage**: Mostly black text on white, with occasional single accent color
147427
149365
  - **Light gray backgrounds**: #f5f5f5 or #e5e5e5 for page backgrounds behind white cards
147428
- - **Strategic black sections**: Full black backgrounds for contrast moments
147429
- - **Accent color blocks**: Small pops of color (red, orange) used sparingly as highlights
147430
-
149366
+ ` + // - **Strategic black sections**: Full black backgrounds for contrast moments
149367
+ // - **Accent color blocks**: Small pops of color (red, orange) used sparingly as highlights
149368
+ `
147431
149369
  ### Information Architecture
147432
149370
  - **Numbered lists**: "1. OVERVIEW", "2. SERVICES", etc. as section headers
147433
149371
  - **Metadata in lines**: "Client Name | Project Type | Year" separated by pipes or lines
@@ -154066,6 +156004,33 @@ Add an image for each feature.`, 'Create a new block showcasing a photo gallery
154066
156004
  */
154067
156005
 
154068
156006
  },
156007
+ demoMode: false,
156008
+
156009
+ /*
156010
+ demoConversations: [
156011
+ {
156012
+ role: 'user',
156013
+ content: 'Generate an image: "a house with beautiful mountain view. warm sunlight, golden-hour lighting, filmic teal-orange color grade".',
156014
+ // timestamp: Date.now() - 180000 // 3 minutes ago
156015
+ },
156016
+ {
156017
+ role: 'assistant',
156018
+ content: '✓ Image generated successfully',
156019
+ imagePreview: [
156020
+ { url: 'uploads/ai-8vnqv.png', context: 'Generate image of a house with a beautiful mountain view' },
156021
+ ]
156022
+ },
156023
+ {
156024
+ role: 'user',
156025
+ content: `Create a thoughtful article about finding home in extraordinary landscapes. Write vivid prose about mountain dwellings and how our environments shape the way we live.
156026
+ Use the generated image for this article, and present it as a premium design magazine feature.`,
156027
+ },
156028
+ {
156029
+ role: 'assistant',
156030
+ content: '✓ Create a thoughtful article about finding home in extraordinary landscapes with vivid prose about mountain dwellings and their impact on our lives, formatted as a premium design magazine feature.',
156031
+ }
156032
+ ],
156033
+ */
154069
156034
 
154070
156035
  /* Old Version (backward compatible) */
154071
156036
  onAddSectionOpen: function () {},
@@ -157480,7 +159445,8 @@ Add an image for each feature.`, 'Create a new block showcasing a photo gallery
157480
159445
  }
157481
159446
  });
157482
159447
  arrSections.forEach(section => {
157483
- // Code Blocks Handling
159448
+ if (!section.classList.contains('is-section')) return; // Code Blocks Handling
159449
+
157484
159450
  let codeBlocks = section.querySelectorAll('[data-html]');
157485
159451
  codeBlocks.forEach(element => {
157486
159452
  let html = decodeURIComponent(element.getAttribute('data-html')); // Original code is stored in data-html attribute
@@ -158317,6 +160283,8 @@ Add an image for each feature.`, 'Create a new block showcasing a photo gallery
158317
160283
  // Add missing is-box class for full width (non splitted) section
158318
160284
  if (!section.querySelector('.is-box')) {
158319
160285
  section.classList.add('is-box');
160286
+ } else {
160287
+ section.classList.remove('is-box');
158320
160288
  }
158321
160289
 
158322
160290
  const containers = section.querySelectorAll('.is-container');