2025-06-16 16:07:02 +03:00
class EmbedDesigner {
constructor ( ) {
this . params = this . parseURLParams ( ) ;
this . config = this . getInitialConfig ( ) ;
this . initDesignerMode ( ) ;
}
parseURLParams ( ) {
const urlParams = new URLSearchParams ( window . location . search ) ;
const params = { } ;
for ( const [ key , value ] of urlParams . entries ( ) ) {
params [ key ] = decodeURIComponent ( value ) ;
}
return params ;
}
getInitialConfig ( ) {
2025-06-16 23:33:48 +03:00
// Check if we have URL parameters
const hasUrlParams = Object . keys ( this . params ) . length > 0 ;
// If URL params exist, prioritize them over saved config
if ( hasUrlParams ) {
// Start with defaults, then apply saved config, then override with URL params
const savedConfig = this . loadFromLocalStorage ( ) || { } ;
return {
prompt : this . params . prompt || savedConfig . prompt || '' ,
context : this . params . context ? this . params . context . split ( ',' ) . map ( c => c . trim ( ) ) : ( savedConfig . context || [ ] ) ,
model : this . params . model || savedConfig . model || 'gpt-4o' ,
mode : this . params . agentMode || savedConfig . mode || 'chat' ,
thinking : this . params . thinking === 'true' ? true : ( this . params . thinking === 'false' ? false : ( savedConfig . thinking || false ) ) ,
max : this . params . max === 'true' ? true : ( this . params . max === 'false' ? false : ( savedConfig . max || false ) ) ,
lightColor : this . params . lightColor || savedConfig . lightColor || '#3b82f6' ,
darkColor : this . params . darkColor || savedConfig . darkColor || '#60a5fa' ,
height : this . params . height || savedConfig . height || '400' ,
2025-06-17 19:41:56 +03:00
themeMode : this . params . themeMode || savedConfig . themeMode || 'auto' ,
2025-06-18 16:21:53 +03:00
filetree : this . params . filetree ? decodeURIComponent ( this . params . filetree ) . split ( '\n' ) . filter ( f => f . trim ( ) ) : ( savedConfig . filetree || [ ] ) ,
2025-06-18 17:28:14 +03:00
showFiletree : savedConfig . showFiletree !== undefined ? savedConfig . showFiletree : true ,
showDiff : this . params . showDiff === 'true' || savedConfig . showDiff || false ,
diffFilename : this . params . diffFilename || savedConfig . diffFilename || '' ,
diffOldText : this . params . diffOldText ? decodeURIComponent ( this . params . diffOldText ) : ( savedConfig . diffOldText || '' ) ,
2025-06-18 20:26:44 +03:00
diffNewText : this . params . diffNewText ? decodeURIComponent ( this . params . diffNewText ) : ( savedConfig . diffNewText || '' ) ,
flashButton : this . params . flashButton || savedConfig . flashButton || 'none'
2025-06-16 23:33:48 +03:00
} ;
}
// Otherwise, try to load from localStorage
2025-06-16 16:07:02 +03:00
const savedConfig = this . loadFromLocalStorage ( ) ;
if ( savedConfig ) {
return savedConfig ;
}
2025-06-16 23:33:48 +03:00
// Fall back to defaults
2025-06-16 16:07:02 +03:00
return {
2025-06-16 23:33:48 +03:00
prompt : '' ,
context : [ ] ,
model : 'gpt-4o' ,
mode : 'chat' ,
thinking : false ,
max : false ,
lightColor : '#3b82f6' ,
darkColor : '#60a5fa' ,
height : '400' ,
2025-06-17 19:41:56 +03:00
themeMode : 'auto' ,
2025-06-18 16:21:53 +03:00
filetree : [ ] ,
2025-06-18 17:28:14 +03:00
showFiletree : true ,
showDiff : false ,
diffFilename : '' ,
diffOldText : '' ,
2025-06-18 20:26:44 +03:00
diffNewText : '' ,
flashButton : 'none'
2025-06-16 16:07:02 +03:00
} ;
}
loadFromLocalStorage ( ) {
try {
const saved = localStorage . getItem ( 'embedDesignerConfig' ) ;
if ( saved ) {
const config = JSON . parse ( saved ) ;
// Validate the loaded config has all required fields
if ( config && typeof config === 'object' ) {
2025-06-17 19:41:56 +03:00
// Ensure all properties have defaults
return {
prompt : config . prompt || '' ,
context : config . context || [ ] ,
model : config . model || 'gpt-4o' ,
mode : config . mode || 'chat' ,
thinking : config . thinking || false ,
max : config . max || false ,
lightColor : config . lightColor || '#3b82f6' ,
darkColor : config . darkColor || '#60a5fa' ,
height : config . height || '400' ,
themeMode : config . themeMode || 'auto' ,
2025-06-18 16:21:53 +03:00
filetree : config . filetree || [ ] ,
2025-06-18 17:28:14 +03:00
showFiletree : config . showFiletree !== undefined ? config . showFiletree : true ,
showDiff : config . showDiff || false ,
diffFilename : config . diffFilename || '' ,
diffOldText : config . diffOldText || '' ,
2025-06-18 20:26:44 +03:00
diffNewText : config . diffNewText || '' ,
flashButton : config . flashButton || 'none'
2025-06-17 19:41:56 +03:00
} ;
2025-06-16 16:07:02 +03:00
}
}
} catch ( e ) {
console . error ( 'Error loading from localStorage:' , e ) ;
}
return null ;
}
saveToLocalStorage ( ) {
try {
localStorage . setItem ( 'embedDesignerConfig' , JSON . stringify ( this . config ) ) ;
// Auto-save is silent - no notification
} catch ( e ) {
console . error ( 'Error saving to localStorage:' , e ) ;
}
}
clearLocalStorage ( ) {
try {
localStorage . removeItem ( 'embedDesignerConfig' ) ;
this . showNotification ( 'Settings cleared!' ) ;
} catch ( e ) {
console . error ( 'Error clearing localStorage:' , e ) ;
}
}
initDesignerMode ( ) {
this . setupDesignerElements ( ) ;
this . setupDesignerEvents ( ) ;
this . setupDesignerColors ( ) ;
this . updatePreview ( ) ;
2025-06-16 16:24:18 +03:00
this . updateIframeSnippet ( ) ;
2025-06-16 16:07:02 +03:00
}
setupDesignerElements ( ) {
// Populate form with current config
document . getElementById ( 'designer-prompt' ) . value = this . config . prompt ;
document . getElementById ( 'designer-context' ) . value = this . config . context . join ( ', ' ) ;
2025-06-17 19:41:56 +03:00
document . getElementById ( 'designer-filetree' ) . value = this . config . filetree . join ( '\n' ) ;
2025-06-16 16:07:02 +03:00
// Handle model selection
const modelSelect = document . getElementById ( 'designer-model' ) ;
const customModelInput = document . getElementById ( 'designer-custom-model' ) ;
// Check if model is one of the predefined options
const isPredefinedModel = Array . from ( modelSelect . options ) . some ( opt => opt . value === this . config . model ) ;
if ( isPredefinedModel ) {
modelSelect . value = this . config . model ;
customModelInput . classList . add ( 'hidden' ) ;
} else {
// It's a custom model
modelSelect . value = 'custom' ;
customModelInput . value = this . config . model ;
customModelInput . classList . remove ( 'hidden' ) ;
}
document . getElementById ( 'designer-mode-select' ) . value = this . config . mode ;
document . getElementById ( 'designer-thinking' ) . checked = this . config . thinking ;
document . getElementById ( 'designer-max' ) . checked = this . config . max ;
2025-06-18 16:21:53 +03:00
// Set show filetree checkbox (default to true if not set)
const showFiletreeCheckbox = document . getElementById ( 'designer-show-filetree' ) ;
if ( showFiletreeCheckbox ) {
showFiletreeCheckbox . checked = this . config . showFiletree !== false ;
}
2025-06-16 16:07:02 +03:00
// Set height slider
const heightSlider = document . getElementById ( 'designer-height' ) ;
const heightValue = document . getElementById ( 'height-value' ) ;
if ( heightSlider ) {
heightSlider . value = this . config . height ;
if ( heightValue ) {
heightValue . textContent = this . config . height ;
}
}
// Set theme mode buttons
this . updateThemeModeButtons ( ) ;
// Set color values
const lightColorPicker = document . getElementById ( 'designer-light-color' ) ;
const lightColorText = document . getElementById ( 'designer-light-color-text' ) ;
const darkColorPicker = document . getElementById ( 'designer-dark-color' ) ;
const darkColorText = document . getElementById ( 'designer-dark-color-text' ) ;
if ( lightColorPicker ) {
lightColorPicker . value = this . config . lightColor ;
lightColorText . value = this . config . lightColor ;
}
if ( darkColorPicker ) {
darkColorPicker . value = this . config . darkColor ;
darkColorText . value = this . config . darkColor ;
}
2025-06-18 17:28:14 +03:00
// Set up diff view
const showDiffCheckbox = document . getElementById ( 'designer-show-diff' ) ;
const diffFields = document . getElementById ( 'diff-fields' ) ;
if ( showDiffCheckbox ) {
showDiffCheckbox . checked = this . config . showDiff || false ;
if ( diffFields ) {
diffFields . classList . toggle ( 'hidden' , ! this . config . showDiff ) ;
}
}
// Set diff field values
const diffFilename = document . getElementById ( 'designer-diff-filename' ) ;
const diffOldText = document . getElementById ( 'designer-diff-old' ) ;
const diffNewText = document . getElementById ( 'designer-diff-new' ) ;
2025-06-18 20:26:44 +03:00
const flashButton = document . getElementById ( 'designer-flash-button' ) ;
2025-06-18 17:28:14 +03:00
if ( diffFilename ) diffFilename . value = this . config . diffFilename || '' ;
if ( diffOldText ) diffOldText . value = this . config . diffOldText || '' ;
if ( diffNewText ) diffNewText . value = this . config . diffNewText || '' ;
2025-06-18 20:26:44 +03:00
if ( flashButton ) flashButton . value = this . config . flashButton || 'none' ;
2025-06-16 16:07:02 +03:00
}
updateThemeModeButtons ( ) {
// Update theme mode button states
document . querySelectorAll ( '.theme-mode-btn' ) . forEach ( btn => {
btn . classList . remove ( 'bg-dynamic-primary' , 'text-white' ) ;
btn . classList . add ( 'bg-dynamic-background' , 'text-dynamic-foreground' ) ;
} ) ;
const activeButton = document . getElementById ( ` theme- ${ this . config . themeMode } ` ) ;
if ( activeButton ) {
activeButton . classList . remove ( 'bg-dynamic-background' , 'text-dynamic-foreground' ) ;
activeButton . classList . add ( 'bg-dynamic-primary' , 'text-white' ) ;
}
}
setupDesignerEvents ( ) {
// Form changes update preview
2025-06-18 20:26:44 +03:00
[ 'designer-prompt' , 'designer-context' , 'designer-mode-select' , 'designer-thinking' , 'designer-max' , 'designer-filetree' , 'designer-show-filetree' , 'designer-diff-filename' , 'designer-diff-old' , 'designer-diff-new' , 'designer-flash-button' ] . forEach ( id => {
2025-06-16 16:07:02 +03:00
const element = document . getElementById ( id ) ;
2025-06-18 16:21:53 +03:00
if ( element ) {
element . addEventListener ( 'input' , ( ) => this . updateConfigFromForm ( ) ) ;
element . addEventListener ( 'change' , ( ) => this . updateConfigFromForm ( ) ) ;
}
2025-06-16 16:07:02 +03:00
} ) ;
2025-06-18 17:28:14 +03:00
// Diff view toggle
const showDiffCheckbox = document . getElementById ( 'designer-show-diff' ) ;
const diffFields = document . getElementById ( 'diff-fields' ) ;
if ( showDiffCheckbox ) {
showDiffCheckbox . addEventListener ( 'change' , ( e ) => {
if ( diffFields ) {
diffFields . classList . toggle ( 'hidden' , ! e . target . checked ) ;
}
this . updateConfigFromForm ( ) ;
} ) ;
}
2025-06-16 16:07:02 +03:00
// Theme mode buttons
document . querySelectorAll ( '.theme-mode-btn' ) . forEach ( btn => {
btn . addEventListener ( 'click' , ( e ) => {
const mode = e . target . id . replace ( 'theme-' , '' ) ;
this . config . themeMode = mode ;
this . updateThemeModeButtons ( ) ;
this . updatePreview ( ) ;
this . saveToLocalStorage ( ) ;
} ) ;
} ) ;
// Color preset buttons
document . querySelectorAll ( '.color-preset' ) . forEach ( btn => {
btn . addEventListener ( 'click' , ( e ) => {
const lightColor = e . currentTarget . getAttribute ( 'data-light' ) ;
const darkColor = e . currentTarget . getAttribute ( 'data-dark' ) ;
// Update color pickers and text inputs
const lightColorPicker = document . getElementById ( 'designer-light-color' ) ;
const lightColorText = document . getElementById ( 'designer-light-color-text' ) ;
const darkColorPicker = document . getElementById ( 'designer-dark-color' ) ;
const darkColorText = document . getElementById ( 'designer-dark-color-text' ) ;
if ( lightColorPicker && lightColor ) {
lightColorPicker . value = lightColor ;
lightColorText . value = lightColor ;
}
if ( darkColorPicker && darkColor ) {
darkColorPicker . value = darkColor ;
darkColorText . value = darkColor ;
}
this . updateConfigFromForm ( ) ;
} ) ;
} ) ;
// Model dropdown special handling
const modelSelect = document . getElementById ( 'designer-model' ) ;
const customModelInput = document . getElementById ( 'designer-custom-model' ) ;
modelSelect . addEventListener ( 'change' , ( ) => {
if ( modelSelect . value === 'custom' ) {
customModelInput . classList . remove ( 'hidden' ) ;
customModelInput . focus ( ) ;
} else {
customModelInput . classList . add ( 'hidden' ) ;
}
this . updateConfigFromForm ( ) ;
} ) ;
// Custom model input
customModelInput . addEventListener ( 'input' , ( ) => this . updateConfigFromForm ( ) ) ;
// Height slider
const heightSlider = document . getElementById ( 'designer-height' ) ;
const heightValue = document . getElementById ( 'height-value' ) ;
if ( heightSlider ) {
heightSlider . addEventListener ( 'input' , ( e ) => {
if ( heightValue ) {
heightValue . textContent = e . target . value ;
}
this . updateConfigFromForm ( ) ;
} ) ;
}
// Color pickers
const lightColorPicker = document . getElementById ( 'designer-light-color' ) ;
const lightColorText = document . getElementById ( 'designer-light-color-text' ) ;
const darkColorPicker = document . getElementById ( 'designer-dark-color' ) ;
const darkColorText = document . getElementById ( 'designer-dark-color-text' ) ;
if ( lightColorPicker ) {
lightColorPicker . addEventListener ( 'input' , ( e ) => {
lightColorText . value = e . target . value ;
this . updateConfigFromForm ( ) ;
} ) ;
}
if ( lightColorText ) {
lightColorText . addEventListener ( 'input' , ( e ) => {
if ( /^#[0-9A-Fa-f]{6}$/ . test ( e . target . value ) ) {
lightColorPicker . value = e . target . value ;
this . updateConfigFromForm ( ) ;
}
} ) ;
}
if ( darkColorPicker ) {
darkColorPicker . addEventListener ( 'input' , ( e ) => {
darkColorText . value = e . target . value ;
this . updateConfigFromForm ( ) ;
} ) ;
}
if ( darkColorText ) {
darkColorText . addEventListener ( 'input' , ( e ) => {
if ( /^#[0-9A-Fa-f]{6}$/ . test ( e . target . value ) ) {
darkColorPicker . value = e . target . value ;
this . updateConfigFromForm ( ) ;
}
} ) ;
}
// Generate embed button
document . getElementById ( 'generate-embed' ) . addEventListener ( 'click' , ( ) => this . showEmbedModal ( ) ) ;
2025-06-16 16:24:18 +03:00
// Iframe snippet click to copy
document . getElementById ( 'iframe-snippet' ) . addEventListener ( 'click' , ( ) => this . copyIframeCode ( ) ) ;
2025-06-16 16:07:02 +03:00
// Reset settings button
document . getElementById ( 'reset-settings' ) . addEventListener ( 'click' , ( ) => {
if ( confirm ( 'Are you sure you want to reset all settings to defaults?' ) ) {
this . clearLocalStorage ( ) ;
// Reset to default config
this . config = {
prompt : '' ,
context : [ ] ,
model : 'gpt-4o' ,
mode : 'chat' ,
thinking : false ,
max : false ,
lightColor : '#3b82f6' ,
darkColor : '#60a5fa' ,
height : '400' ,
2025-06-17 19:41:56 +03:00
themeMode : 'auto' ,
2025-06-18 16:21:53 +03:00
filetree : [ ] ,
2025-06-18 17:28:14 +03:00
showFiletree : true ,
showDiff : false ,
diffFilename : '' ,
diffOldText : '' ,
diffNewText : ''
2025-06-16 16:07:02 +03:00
} ;
// Update UI to reflect defaults
this . setupDesignerElements ( ) ;
this . updatePreview ( ) ;
2025-06-16 16:24:18 +03:00
this . updateIframeSnippet ( ) ;
2025-06-16 16:07:02 +03:00
}
} ) ;
2025-06-18 17:28:14 +03:00
// Example select
const exampleSelect = document . getElementById ( 'example-select' ) ;
if ( exampleSelect ) {
exampleSelect . addEventListener ( 'change' , ( e ) => {
const example = e . target . value ;
if ( example ) {
switch ( example ) {
case 'vibe-coding' :
this . loadVibeCodingExample ( ) ;
break ;
case 'vibe-coding-diff' :
this . loadVibeCodingDiffExample ( ) ;
break ;
case 'chatgpt' :
this . loadChatGPTExample ( ) ;
break ;
case 'claude' :
this . loadClaudeExample ( ) ;
break ;
case 'image-analysis' :
this . loadImageAnalysisExample ( ) ;
break ;
case 'api-design' :
this . loadAPIDesignExample ( ) ;
break ;
}
// Reset select to placeholder after loading
exampleSelect . value = '' ;
}
} ) ;
}
2025-06-16 16:24:18 +03:00
2025-06-16 16:07:02 +03:00
// Modal events
document . getElementById ( 'close-modal' ) . addEventListener ( 'click' , ( ) => this . hideEmbedModal ( ) ) ;
document . getElementById ( 'copy-embed-code' ) . addEventListener ( 'click' , ( ) => this . copyEmbedCode ( ) ) ;
document . getElementById ( 'copy-share-url' ) . addEventListener ( 'click' , ( ) => this . copyShareURL ( ) ) ;
// Close modal on backdrop click
document . getElementById ( 'embed-modal' ) . addEventListener ( 'click' , ( e ) => {
if ( e . target . id === 'embed-modal' ) this . hideEmbedModal ( ) ;
} ) ;
}
updateConfigFromForm ( ) {
const heightSlider = document . getElementById ( 'designer-height' ) ;
const modelSelect = document . getElementById ( 'designer-model' ) ;
const customModelInput = document . getElementById ( 'designer-custom-model' ) ;
const lightColorText = document . getElementById ( 'designer-light-color-text' ) ;
const darkColorText = document . getElementById ( 'designer-dark-color-text' ) ;
// Get model value
let modelValue = modelSelect . value ;
if ( modelValue === 'custom' ) {
modelValue = customModelInput . value || 'Custom Model' ;
}
this . config = {
prompt : document . getElementById ( 'designer-prompt' ) . value ,
context : document . getElementById ( 'designer-context' ) . value . split ( ',' ) . map ( c => c . trim ( ) ) . filter ( c => c ) ,
model : modelValue ,
mode : document . getElementById ( 'designer-mode-select' ) . value ,
thinking : document . getElementById ( 'designer-thinking' ) . checked ,
max : document . getElementById ( 'designer-max' ) . checked ,
lightColor : lightColorText ? lightColorText . value : '#3b82f6' ,
darkColor : darkColorText ? darkColorText . value : '#60a5fa' ,
height : heightSlider ? heightSlider . value : '400' ,
2025-06-17 19:41:56 +03:00
themeMode : this . config . themeMode || 'auto' ,
2025-06-18 16:21:53 +03:00
filetree : document . getElementById ( 'designer-filetree' ) . value . split ( '\n' ) . map ( f => f . trim ( ) ) . filter ( f => f ) ,
2025-06-18 17:28:14 +03:00
showFiletree : document . getElementById ( 'designer-show-filetree' ) . checked ,
showDiff : document . getElementById ( 'designer-show-diff' ) . checked ,
diffFilename : document . getElementById ( 'designer-diff-filename' ) . value ,
diffOldText : document . getElementById ( 'designer-diff-old' ) . value ,
2025-06-18 20:26:44 +03:00
diffNewText : document . getElementById ( 'designer-diff-new' ) . value ,
flashButton : document . getElementById ( 'designer-flash-button' ) . value
2025-06-16 16:07:02 +03:00
} ;
this . updatePreview ( ) ;
this . saveToLocalStorage ( ) ;
2025-06-16 16:24:18 +03:00
this . updateIframeSnippet ( ) ;
}
updateIframeSnippet ( ) {
const snippet = document . getElementById ( 'iframe-snippet' ) ;
if ( ! snippet ) return ;
const code = this . generateEmbedCode ( ) ;
// Show a shortened version in the snippet
const shortCode = code . replace ( /\n/g , ' ' ) . replace ( /\s+/g , ' ' ) ;
snippet . textContent = shortCode ;
2025-06-16 16:07:02 +03:00
}
setupDesignerColors ( ) {
// Set up fixed designer colors - always use default blue theme in light mode
const root = document . documentElement ;
// Always use default blue colors for designer
root . style . setProperty ( '--primary' , '59 130 246' ) ; // Blue-500
root . style . setProperty ( '--background' , '255 255 255' ) ;
root . style . setProperty ( '--foreground' , '15 23 42' ) ;
root . style . setProperty ( '--muted' , '248 250 252' ) ;
root . style . setProperty ( '--muted-foreground' , '100 116 139' ) ;
root . style . setProperty ( '--border' , '226 232 240' ) ;
root . style . setProperty ( '--accent' , '16 185 129' ) ;
}
updateDesignerColors ( ) {
// Deprecated - no longer update designer colors
// Designer maintains fixed color scheme
}
updatePreview ( ) {
const previewContainer = document . getElementById ( 'preview-container' ) ;
const previewWrapper = document . getElementById ( 'preview-wrapper' ) ;
if ( ! previewContainer ) return ;
// Update preview wrapper height
if ( previewWrapper ) {
previewWrapper . style . height = ` ${ this . config . height } px ` ;
}
// Generate preview URL with parameters
const previewUrl = this . generatePreviewURL ( ) ;
// Update iframe src
let iframe = previewContainer . querySelector ( 'iframe' ) ;
if ( ! iframe ) {
iframe = document . createElement ( 'iframe' ) ;
iframe . className = 'w-full h-full border-0 rounded-lg' ;
previewContainer . innerHTML = '' ;
previewContainer . appendChild ( iframe ) ;
}
iframe . src = previewUrl ;
}
generatePreviewURL ( ) {
const params = new URLSearchParams ( ) ;
if ( this . config . prompt ) params . set ( 'prompt' , this . config . prompt ) ;
if ( this . config . context . length > 0 ) params . set ( 'context' , this . config . context . join ( ',' ) ) ;
if ( this . config . model !== 'gpt-4o' ) params . set ( 'model' , this . config . model ) ;
if ( this . config . mode !== 'chat' ) params . set ( 'agentMode' , this . config . mode ) ;
if ( this . config . thinking ) params . set ( 'thinking' , 'true' ) ;
if ( this . config . max ) params . set ( 'max' , 'true' ) ;
if ( this . config . lightColor !== '#3b82f6' ) params . set ( 'lightColor' , this . config . lightColor ) ;
if ( this . config . darkColor !== '#60a5fa' ) params . set ( 'darkColor' , this . config . darkColor ) ;
if ( this . config . themeMode !== 'auto' ) params . set ( 'themeMode' , this . config . themeMode ) ;
2025-06-18 16:21:53 +03:00
if ( this . config . showFiletree && this . config . filetree && this . config . filetree . length > 0 ) params . set ( 'filetree' , encodeURIComponent ( this . config . filetree . join ( '\n' ) ) ) ;
2025-06-18 17:28:14 +03:00
if ( this . config . showDiff ) {
params . set ( 'showDiff' , 'true' ) ;
if ( this . config . diffFilename ) params . set ( 'diffFilename' , this . config . diffFilename ) ;
2025-06-18 20:26:44 +03:00
if ( this . config . flashButton && this . config . flashButton !== 'none' ) params . set ( 'flashButton' , this . config . flashButton ) ;
2025-06-18 17:28:14 +03:00
// Truncate diff text if too long to prevent URL length issues
if ( this . config . diffOldText ) {
const truncated = this . config . diffOldText . substring ( 0 , 100 ) ;
params . set ( 'diffOldText' , encodeURIComponent ( truncated ) + '...' ) ;
}
if ( this . config . diffNewText ) {
const truncated = this . config . diffNewText . substring ( 0 , 100 ) ;
params . set ( 'diffNewText' , encodeURIComponent ( truncated ) + '...' ) ;
}
}
2025-06-16 16:07:02 +03:00
params . set ( 'preview' , 'true' ) ;
return ` /embed-preview/? ${ params . toString ( ) } ` ;
}
generateShareURL ( ) {
const params = new URLSearchParams ( ) ;
if ( this . config . prompt ) params . set ( 'prompt' , this . config . prompt ) ;
if ( this . config . context . length > 0 ) params . set ( 'context' , this . config . context . join ( ',' ) ) ;
if ( this . config . model !== 'gpt-4o' ) params . set ( 'model' , this . config . model ) ;
if ( this . config . mode !== 'chat' ) params . set ( 'agentMode' , this . config . mode ) ;
if ( this . config . thinking ) params . set ( 'thinking' , 'true' ) ;
if ( this . config . max ) params . set ( 'max' , 'true' ) ;
if ( this . config . lightColor !== '#3b82f6' ) params . set ( 'lightColor' , this . config . lightColor ) ;
if ( this . config . darkColor !== '#60a5fa' ) params . set ( 'darkColor' , this . config . darkColor ) ;
if ( this . config . themeMode !== 'auto' ) params . set ( 'themeMode' , this . config . themeMode ) ;
2025-06-18 16:21:53 +03:00
if ( this . config . showFiletree && this . config . filetree && this . config . filetree . length > 0 ) params . set ( 'filetree' , encodeURIComponent ( this . config . filetree . join ( '\n' ) ) ) ;
2025-06-18 17:28:14 +03:00
if ( this . config . showDiff ) {
params . set ( 'showDiff' , 'true' ) ;
if ( this . config . diffFilename ) params . set ( 'diffFilename' , this . config . diffFilename ) ;
2025-06-18 20:26:44 +03:00
if ( this . config . flashButton && this . config . flashButton !== 'none' ) params . set ( 'flashButton' , this . config . flashButton ) ;
2025-06-18 17:28:14 +03:00
// Truncate diff text if too long to prevent URL length issues
if ( this . config . diffOldText ) {
const truncated = this . config . diffOldText . substring ( 0 , 150 ) ;
params . set ( 'diffOldText' , encodeURIComponent ( truncated ) ) ;
}
if ( this . config . diffNewText ) {
const truncated = this . config . diffNewText . substring ( 0 , 150 ) ;
params . set ( 'diffNewText' , encodeURIComponent ( truncated ) ) ;
}
}
2025-06-16 16:07:02 +03:00
return ` ${ window . location . origin } /embed-preview/? ${ params . toString ( ) } ` ;
}
generateEmbedCode ( ) {
const url = this . generateShareURL ( ) ;
return ` <iframe
src = "${url}"
width = "100%"
height = "${this.config.height}"
frameborder = "0"
style = "border-radius: 12px; border: 1px solid #e5e7eb;" >
< / i f r a m e > ` ;
}
showEmbedModal ( ) {
const modal = document . getElementById ( 'embed-modal' ) ;
const embedCode = document . getElementById ( 'embed-code' ) ;
const shareUrl = document . getElementById ( 'share-url' ) ;
embedCode . value = this . generateEmbedCode ( ) ;
shareUrl . value = this . generateShareURL ( ) ;
modal . classList . remove ( 'hidden' ) ;
}
hideEmbedModal ( ) {
document . getElementById ( 'embed-modal' ) . classList . add ( 'hidden' ) ;
}
async copyEmbedCode ( ) {
const embedCode = document . getElementById ( 'embed-code' ) . value ;
await this . copyToClipboard ( embedCode ) ;
this . showNotification ( 'Embed code copied to clipboard!' ) ;
}
async copyShareURL ( ) {
const shareUrl = this . generateShareURL ( ) ;
await this . copyToClipboard ( shareUrl ) ;
this . showNotification ( 'Share URL copied to clipboard!' ) ;
}
2025-06-16 16:24:18 +03:00
async copyIframeCode ( ) {
const embedCode = this . generateEmbedCode ( ) ;
await this . copyToClipboard ( embedCode ) ;
this . showNotification ( 'Iframe code copied to clipboard!' ) ;
}
2025-06-16 16:07:02 +03:00
async copyToClipboard ( text ) {
if ( navigator . clipboard && window . isSecureContext ) {
await navigator . clipboard . writeText ( text ) ;
} else {
// Fallback
const textArea = document . createElement ( 'textarea' ) ;
textArea . value = text ;
textArea . style . position = 'fixed' ;
textArea . style . left = '-999999px' ;
textArea . style . top = '-999999px' ;
document . body . appendChild ( textArea ) ;
textArea . focus ( ) ;
textArea . select ( ) ;
document . execCommand ( 'copy' ) ;
textArea . remove ( ) ;
}
}
showNotification ( message , duration = 2000 ) {
const notification = document . getElementById ( 'notification' ) ;
if ( ! notification ) return ;
notification . textContent = message ;
notification . classList . remove ( 'opacity-0' ) ;
notification . classList . add ( 'opacity-100' ) ;
setTimeout ( ( ) => {
notification . classList . remove ( 'opacity-100' ) ;
notification . classList . add ( 'opacity-0' ) ;
} , duration ) ;
}
2025-06-16 16:24:18 +03:00
2025-06-18 17:28:14 +03:00
loadVibeCodingExample ( ) {
// Vibe coding example WITHOUT diff
2025-06-18 16:21:53 +03:00
// Set vibe coding example values
2025-06-17 19:41:56 +03:00
document . getElementById ( 'designer-prompt' ) . value =
2025-06-18 17:28:14 +03:00
` Refactor my React ProductList component:
- Extract filtering logic to custom hook
- Split into smaller components
- Add TypeScript types
- Implement virtualization
2025-06-17 19:41:56 +03:00
2025-06-18 17:28:14 +03:00
Currently handles display , filtering , sorting , and pagination in one file .
2025-06-18 16:38:40 +03:00
2025-06-18 17:28:14 +03:00
@ web Check React Query docs for data fetching patterns . ` ;
2025-06-17 19:41:56 +03:00
2025-06-18 17:28:14 +03:00
document . getElementById ( 'designer-context' ) . value = '@codebase, ProductList.tsx' ;
2025-06-17 19:41:56 +03:00
document . getElementById ( 'designer-filetree' ) . value =
2025-06-18 17:28:14 +03:00
` src/components/ProductList.tsx*
2025-06-18 16:21:53 +03:00
src / components / ProductCard . tsx
src / hooks / useProducts . ts
2025-06-18 17:28:14 +03:00
src / types / product . ts ` ;
2025-06-17 19:41:56 +03:00
2025-06-18 16:21:53 +03:00
// Set vibe coding settings
document . getElementById ( 'designer-model' ) . value = 'Claude 4 Opus' ;
2025-06-17 19:41:56 +03:00
document . getElementById ( 'designer-mode-select' ) . value = 'agent' ;
document . getElementById ( 'designer-thinking' ) . checked = true ;
2025-06-18 16:21:53 +03:00
document . getElementById ( 'designer-max' ) . checked = true ;
document . getElementById ( 'designer-show-filetree' ) . checked = true ;
2025-06-18 16:30:39 +03:00
// Set height and colors for coding example
const heightSlider = document . getElementById ( 'designer-height' ) ;
const heightValue = document . getElementById ( 'height-value' ) ;
if ( heightSlider ) {
heightSlider . value = '500' ;
if ( heightValue ) {
heightValue . textContent = '500' ;
}
}
// Set developer-friendly color scheme (purple/violet)
document . getElementById ( 'designer-light-color' ) . value = '#8b5cf6' ;
document . getElementById ( 'designer-light-color-text' ) . value = '#8b5cf6' ;
document . getElementById ( 'designer-dark-color' ) . value = '#a78bfa' ;
document . getElementById ( 'designer-dark-color-text' ) . value = '#a78bfa' ;
// Set dark theme mode for coding
this . config . themeMode = 'dark' ;
this . updateThemeModeButtons ( ) ;
2025-06-18 17:28:14 +03:00
// No diff for this example
document . getElementById ( 'designer-show-diff' ) . checked = false ;
document . getElementById ( 'diff-fields' ) . classList . add ( 'hidden' ) ;
2025-06-18 20:26:44 +03:00
document . getElementById ( 'designer-flash-button' ) . value = 'none' ;
2025-06-18 17:28:14 +03:00
2025-06-18 16:21:53 +03:00
// Update config from form
this . updateConfigFromForm ( ) ;
this . showNotification ( 'Vibe coding example loaded!' ) ;
}
2025-06-18 17:28:14 +03:00
loadVibeCodingDiffExample ( ) {
// Vibe coding WITH diff - user giving feedback on suggested changes
document . getElementById ( 'designer-prompt' ) . value =
` Actually, don't use Error | null for the error state. Instead:
- Create a custom ApiError type with code , message , and retry ( )
- Add an AbortController for request cancellation
- Include error boundary integration
- Show me how to handle network vs API errors differently
Also add a refetch function that the UI can call . ` ;
document . getElementById ( 'designer-context' ) . value = '@codebase, useProducts.ts' ;
document . getElementById ( 'designer-filetree' ) . value =
` src/hooks/useProducts.ts*
src / types / errors . ts
src / utils / api . ts ` ;
// Set same settings as regular vibe coding
document . getElementById ( 'designer-model' ) . value = 'Claude 4 Opus' ;
document . getElementById ( 'designer-mode-select' ) . value = 'agent' ;
document . getElementById ( 'designer-thinking' ) . checked = true ;
document . getElementById ( 'designer-max' ) . checked = true ;
document . getElementById ( 'designer-show-filetree' ) . checked = true ;
// Set height and colors
const heightSlider = document . getElementById ( 'designer-height' ) ;
const heightValue = document . getElementById ( 'height-value' ) ;
if ( heightSlider ) {
heightSlider . value = '500' ;
if ( heightValue ) {
heightValue . textContent = '500' ;
}
}
document . getElementById ( 'designer-light-color' ) . value = '#8b5cf6' ;
document . getElementById ( 'designer-light-color-text' ) . value = '#8b5cf6' ;
document . getElementById ( 'designer-dark-color' ) . value = '#a78bfa' ;
document . getElementById ( 'designer-dark-color-text' ) . value = '#a78bfa' ;
this . config . themeMode = 'dark' ;
this . updateThemeModeButtons ( ) ;
// Add diff view showing the previous suggestion
document . getElementById ( 'designer-show-diff' ) . checked = true ;
document . getElementById ( 'diff-fields' ) . classList . remove ( 'hidden' ) ;
document . getElementById ( 'designer-diff-filename' ) . value = 'useProducts.ts' ;
document . getElementById ( 'designer-diff-old' ) . value =
` const [products, setProducts] = useState([]);
const [ loading , setLoading ] = useState ( true ) ; ` ;
document . getElementById ( 'designer-diff-new' ) . value =
` const [products, setProducts] = useState<Product[]>([]);
const [ loading , setLoading ] = useState ( true ) ;
const [ error , setError ] = useState < Error | null > ( null ) ; ` ;
2025-06-18 20:26:44 +03:00
// Flash accept button for this example with diff
document . getElementById ( 'designer-flash-button' ) . value = 'accept' ;
2025-06-18 17:28:14 +03:00
// Update config from form
this . updateConfigFromForm ( ) ;
this . showNotification ( 'Vibe coding with diff loaded!' ) ;
}
loadChatGPTExample ( ) {
// ChatGPT example with green colors
2025-06-18 16:21:53 +03:00
document . getElementById ( 'designer-prompt' ) . value =
` I'm planning a dinner party for 8 people this weekend. One person is vegetarian, another is gluten-free, and I want to make something impressive but not too complicated.
Can you suggest a menu with appetizers , main course , and dessert that would work for everyone ? I have about 4 hours to prepare everything and a moderate cooking skill level . ` ;
2025-06-18 17:28:14 +03:00
document . getElementById ( 'designer-context' ) . value = '#Kitchen Layout.png, #Pantry Inventory.jpg' ;
2025-06-18 16:21:53 +03:00
document . getElementById ( 'designer-filetree' ) . value = '' ;
2025-06-18 17:28:14 +03:00
// ChatGPT settings
2025-06-18 16:21:53 +03:00
document . getElementById ( 'designer-model' ) . value = 'GPT 4o' ;
document . getElementById ( 'designer-mode-select' ) . value = 'chat' ;
document . getElementById ( 'designer-thinking' ) . checked = false ;
document . getElementById ( 'designer-max' ) . checked = false ;
document . getElementById ( 'designer-show-filetree' ) . checked = false ;
2025-06-17 19:41:56 +03:00
2025-06-18 17:28:14 +03:00
// Set height and colors for ChatGPT
2025-06-18 16:30:39 +03:00
const heightSlider = document . getElementById ( 'designer-height' ) ;
const heightValue = document . getElementById ( 'height-value' ) ;
if ( heightSlider ) {
heightSlider . value = '350' ;
if ( heightValue ) {
heightValue . textContent = '350' ;
}
}
2025-06-18 17:28:14 +03:00
// ChatGPT green color scheme
2025-06-18 16:30:39 +03:00
document . getElementById ( 'designer-light-color' ) . value = '#10b981' ;
document . getElementById ( 'designer-light-color-text' ) . value = '#10b981' ;
document . getElementById ( 'designer-dark-color' ) . value = '#34d399' ;
document . getElementById ( 'designer-dark-color-text' ) . value = '#34d399' ;
2025-06-18 17:28:14 +03:00
// Light theme for ChatGPT
2025-06-18 16:30:39 +03:00
this . config . themeMode = 'light' ;
this . updateThemeModeButtons ( ) ;
2025-06-18 17:28:14 +03:00
// Clear diff view
document . getElementById ( 'designer-show-diff' ) . checked = false ;
document . getElementById ( 'diff-fields' ) . classList . add ( 'hidden' ) ;
2025-06-18 20:26:44 +03:00
document . getElementById ( 'designer-flash-button' ) . value = 'none' ;
2025-06-18 17:28:14 +03:00
// Update config from form
this . updateConfigFromForm ( ) ;
this . showNotification ( 'ChatGPT example loaded!' ) ;
}
loadClaudeExample ( ) {
// Claude example with orange colors - daily usage
document . getElementById ( 'designer-prompt' ) . value =
` Help me write a professional email to decline a job offer while keeping the door open for future opportunities.
Context :
- Received offer from TechCorp as Senior Engineer
- Great team and compensation , but role doesn ' t align with my career goals
- Want to maintain good relationship with the hiring manager Sarah
- Interested in their upcoming ML team expansion next year
Keep it warm but professional , around 150 - 200 words . ` ;
document . getElementById ( 'designer-context' ) . value = '' ;
document . getElementById ( 'designer-filetree' ) . value = '' ;
// Claude settings
document . getElementById ( 'designer-model' ) . value = 'Claude 3.7 Sonnet' ;
document . getElementById ( 'designer-mode-select' ) . value = 'chat' ;
document . getElementById ( 'designer-thinking' ) . checked = false ;
document . getElementById ( 'designer-max' ) . checked = false ;
document . getElementById ( 'designer-show-filetree' ) . checked = false ;
// Set height and colors for Claude
const heightSlider = document . getElementById ( 'designer-height' ) ;
const heightValue = document . getElementById ( 'height-value' ) ;
if ( heightSlider ) {
heightSlider . value = '350' ;
if ( heightValue ) {
heightValue . textContent = '350' ;
}
}
// Claude orange color scheme
document . getElementById ( 'designer-light-color' ) . value = '#f97316' ;
document . getElementById ( 'designer-light-color-text' ) . value = '#f97316' ;
document . getElementById ( 'designer-dark-color' ) . value = '#fb923c' ;
document . getElementById ( 'designer-dark-color-text' ) . value = '#fb923c' ;
// Auto theme for Claude
this . config . themeMode = 'auto' ;
this . updateThemeModeButtons ( ) ;
// Clear diff view
document . getElementById ( 'designer-show-diff' ) . checked = false ;
document . getElementById ( 'diff-fields' ) . classList . add ( 'hidden' ) ;
2025-06-18 20:26:44 +03:00
document . getElementById ( 'designer-flash-button' ) . value = 'none' ;
2025-06-18 17:28:14 +03:00
// Update config from form
this . updateConfigFromForm ( ) ;
this . showNotification ( 'Claude example loaded!' ) ;
}
loadImageAnalysisExample ( ) {
// Image analysis example
document . getElementById ( 'designer-prompt' ) . value =
` Analyze these UI screenshots and provide detailed feedback on:
- Visual hierarchy and layout
- Color scheme and contrast
- Typography choices
- Accessibility concerns
- Mobile responsiveness issues ` ;
document . getElementById ( 'designer-context' ) . value = '#Homepage Desktop.png, #Homepage Mobile.png, #Dashboard View.jpg' ;
document . getElementById ( 'designer-filetree' ) . value = '' ;
// Image analysis settings
document . getElementById ( 'designer-model' ) . value = 'GPT 4.1' ;
document . getElementById ( 'designer-mode-select' ) . value = 'chat' ;
document . getElementById ( 'designer-thinking' ) . checked = false ;
document . getElementById ( 'designer-max' ) . checked = false ;
document . getElementById ( 'designer-show-filetree' ) . checked = false ;
// Set height and colors
const heightSlider = document . getElementById ( 'designer-height' ) ;
const heightValue = document . getElementById ( 'height-value' ) ;
if ( heightSlider ) {
heightSlider . value = '400' ;
if ( heightValue ) {
heightValue . textContent = '400' ;
}
}
// Pink/purple color scheme for design
document . getElementById ( 'designer-light-color' ) . value = '#ec4899' ;
document . getElementById ( 'designer-light-color-text' ) . value = '#ec4899' ;
document . getElementById ( 'designer-dark-color' ) . value = '#f472b6' ;
document . getElementById ( 'designer-dark-color-text' ) . value = '#f472b6' ;
// Light theme
this . config . themeMode = 'light' ;
this . updateThemeModeButtons ( ) ;
// Clear diff view
document . getElementById ( 'designer-show-diff' ) . checked = false ;
document . getElementById ( 'diff-fields' ) . classList . add ( 'hidden' ) ;
2025-06-18 20:26:44 +03:00
document . getElementById ( 'designer-flash-button' ) . value = 'none' ;
2025-06-18 17:28:14 +03:00
// Update config from form
this . updateConfigFromForm ( ) ;
this . showNotification ( 'Image analysis example loaded!' ) ;
}
loadAPIDesignExample ( ) {
// API design example
document . getElementById ( 'designer-prompt' ) . value =
` Design a RESTful API for a task management system with:
- User authentication
- Project and task CRUD operations
- Team collaboration features
- Real - time notifications
Include endpoint definitions , request / response schemas , and error handling patterns . ` ;
2025-06-18 20:41:57 +03:00
document . getElementById ( 'designer-context' ) . value = '@Web, openapi.yaml' ;
2025-06-18 17:28:14 +03:00
document . getElementById ( 'designer-filetree' ) . value =
` api/
api / v1 /
api / v1 / users /
api / v1 / projects /
api / v1 / tasks /
docs / openapi . yaml * ` ;
// API design settings
document . getElementById ( 'designer-model' ) . value = 'Claude 4 Opus' ;
document . getElementById ( 'designer-mode-select' ) . value = 'manual' ;
document . getElementById ( 'designer-thinking' ) . checked = true ;
document . getElementById ( 'designer-max' ) . checked = false ;
document . getElementById ( 'designer-show-filetree' ) . checked = true ;
// Set height and colors
const heightSlider = document . getElementById ( 'designer-height' ) ;
const heightValue = document . getElementById ( 'height-value' ) ;
if ( heightSlider ) {
heightSlider . value = '500' ;
if ( heightValue ) {
heightValue . textContent = '500' ;
}
}
// Blue color scheme for API
document . getElementById ( 'designer-light-color' ) . value = '#3b82f6' ;
document . getElementById ( 'designer-light-color-text' ) . value = '#3b82f6' ;
document . getElementById ( 'designer-dark-color' ) . value = '#60a5fa' ;
document . getElementById ( 'designer-dark-color-text' ) . value = '#60a5fa' ;
// Dark theme for technical
this . config . themeMode = 'dark' ;
this . updateThemeModeButtons ( ) ;
// Clear diff view
document . getElementById ( 'designer-show-diff' ) . checked = false ;
document . getElementById ( 'diff-fields' ) . classList . add ( 'hidden' ) ;
2025-06-18 20:26:44 +03:00
document . getElementById ( 'designer-flash-button' ) . value = 'none' ;
2025-06-18 17:28:14 +03:00
2025-06-17 19:41:56 +03:00
// Update config from form
this . updateConfigFromForm ( ) ;
2025-06-16 16:24:18 +03:00
2025-06-18 17:28:14 +03:00
this . showNotification ( 'API design example loaded!' ) ;
2025-06-16 16:24:18 +03:00
}
2025-06-16 16:07:02 +03:00
}
// Dark mode toggle function
function toggleDarkMode ( ) {
const body = document . body ;
const isDark = body . classList . contains ( 'dark-mode' ) ;
if ( isDark ) {
body . classList . remove ( 'dark-mode' ) ;
localStorage . setItem ( 'darkMode' , 'false' ) ;
document . querySelector ( '.dark-mode-toggle .sun-icon' ) . style . display = 'block' ;
document . querySelector ( '.dark-mode-toggle .moon-icon' ) . style . display = 'none' ;
} else {
body . classList . add ( 'dark-mode' ) ;
localStorage . setItem ( 'darkMode' , 'true' ) ;
document . querySelector ( '.dark-mode-toggle .sun-icon' ) . style . display = 'none' ;
document . querySelector ( '.dark-mode-toggle .moon-icon' ) . style . display = 'block' ;
}
}
// Initialize when DOM is ready
document . addEventListener ( 'DOMContentLoaded' , ( ) => {
window . embedDesigner = new EmbedDesigner ( ) ;
// Initialize dark mode from localStorage
const darkMode = localStorage . getItem ( 'darkMode' ) ;
if ( darkMode === 'true' ) {
document . body . classList . add ( 'dark-mode' ) ;
document . querySelector ( '.dark-mode-toggle .sun-icon' ) . style . display = 'none' ;
document . querySelector ( '.dark-mode-toggle .moon-icon' ) . style . display = 'block' ;
}
} ) ;