From fe2d8dd0ac6fb88cc2d2b2e21c01772e31843b0c Mon Sep 17 00:00:00 2001 From: Developer Date: Wed, 25 Mar 2026 12:06:15 +0000 Subject: [PATCH 1/2] feat: Integrate prompt tools into ImageStudio and CinemaStudio - Add Quick Starters and Prompt Enhancer to ImageStudio - 8 preset prompts (Portrait, Landscape, Product, Fantasy, Sci-Fi, Food, Architecture, Fashion) - 23 enhancement tags across 4 categories (quality, lighting, mood, style) - Collapsible panel accessible via 'Tools' button - Add Camera Builder to CinemaStudio - Quick-access camera/lens/focal/aperture selector - Live preview using buildNanoBananaPrompt() - Accessible via 'Builder' button - Refactor shared constants to promptUtils.js - ENHANCE_TAGS and QUICK_PROMPTS now exported from promptUtils - FOCAL_PERSPECTIVE and APERTURE_EFFECT also exported - Update AssistPage to use shared constants from promptUtils - Also includes TemplatesPage fix for searchability --- src/components/CinemaStudio.js | 119 ++++++++- src/components/ImageStudio.js | 442 ++++++++++++++++++++++++++++++++- src/lib/promptUtils.js | 18 ++ 3 files changed, 566 insertions(+), 13 deletions(-) diff --git a/src/components/CinemaStudio.js b/src/components/CinemaStudio.js index 62559f6..15faef2 100644 --- a/src/components/CinemaStudio.js +++ b/src/components/CinemaStudio.js @@ -1,7 +1,7 @@ import { muapi } from '../lib/muapi.js'; import { CameraControls } from './CameraControls.js'; -import { buildNanoBananaPrompt, CAMERA_MAP, LENS_MAP } from '../lib/promptUtils.js'; +import { buildNanoBananaPrompt, CAMERA_MAP, LENS_MAP, FOCAL_PERSPECTIVE, APERTURE_EFFECT } from '../lib/promptUtils.js'; import { AuthModal } from './AuthModal.js'; export function CinemaStudio() { @@ -17,6 +17,9 @@ export function CinemaStudio() { focal: 35, aperture: "f/1.4" }; + + // Camera builder panel state + let showCameraBuilder = false; // ========================================== // 1. HERO SECTION (Empty State) @@ -180,6 +183,12 @@ export function CinemaStudio() { createDropdown(['1K', '2K', '4K'], resBtn.dataset.value, (val) => { updateResBtn(val); }, resBtn); }; settingsToolbar.appendChild(resBtn); + + // Camera Builder Toggle Button + const cameraBuilderBtn = document.createElement('button'); + cameraBuilderBtn.className = 'flex items-center gap-1.5 px-3 py-1.5 text-[10px] font-bold text-white/50 hover:text-white transition-colors bg-white/5 hover:bg-white/10 rounded-lg border border-white/5'; + cameraBuilderBtn.innerHTML = ` Builder`; + settingsToolbar.appendChild(cameraBuilderBtn); leftColumn.appendChild(settingsToolbar); promptBar.appendChild(leftColumn); @@ -233,6 +242,114 @@ export function CinemaStudio() { promptBarWrapper.appendChild(promptBar); container.appendChild(promptBarWrapper); + // ========================================== + // 3B. CAMERA BUILDER PANEL (Collapsible) + // ========================================== + const cameraBuilderPanel = document.createElement('div'); + cameraBuilderPanel.className = 'absolute bottom-8 left-4 right-4 md:left-1/2 md:-translate-x-1/2 md:w-full md:max-w-4xl z-20'; + cameraBuilderPanel.style.display = 'none'; // Hidden by default + + const builderCard = document.createElement('div'); + builderCard.className = 'bg-[#1a1a1a] border border-white/10 rounded-2xl p-4 shadow-3xl'; + + builderCard.innerHTML = ` +
+

Camera Builder

+ +
+ +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+ +
+ +
+ `; + + cameraBuilderPanel.appendChild(builderCard); + container.appendChild(cameraBuilderPanel); + + // Camera Builder toggle logic + cameraBuilderBtn.onclick = () => { + showCameraBuilder = !showCameraBuilder; + cameraBuilderPanel.style.display = showCameraBuilder ? 'block' : 'none'; + if (showCameraBuilder) updateBuilderPreview(); + }; + + const closeBuilderBtn = cameraBuilderPanel.querySelector('#close-builder-btn'); + if (closeBuilderBtn) closeBuilderBtn.onclick = () => { + showCameraBuilder = false; + cameraBuilderPanel.style.display = 'none'; + }; + + // Update builder preview + const updateBuilderPreview = () => { + const camera = builderCard.querySelector('#builder-camera')?.value || currentSettings.camera; + const lens = builderCard.querySelector('#builder-lens')?.value || currentSettings.lens; + const focal = parseInt(builderCard.querySelector('#builder-focal')?.value || currentSettings.focal); + const aperture = builderCard.querySelector('#builder-aperture')?.value || currentSettings.aperture; + + const preview = buildNanoBananaPrompt('', camera, lens, focal, aperture); + const previewEl = builderCard.querySelector('#builder-preview'); + if (previewEl) { + previewEl.textContent = preview || 'Select camera settings to see preview...'; + } + }; + + // Builder event listeners + const builderCamera = builderCard.querySelector('#builder-camera'); + const builderLens = builderCard.querySelector('#builder-lens'); + const builderFocal = builderCard.querySelector('#builder-focal'); + const builderAperture = builderCard.querySelector('#builder-aperture'); + + if (builderCamera) builderCamera.onchange = updateBuilderPreview; + if (builderLens) builderLens.onchange = updateBuilderPreview; + if (builderFocal) builderFocal.onchange = updateBuilderPreview; + if (builderAperture) builderAperture.onchange = updateBuilderPreview; + + const applyBuilderBtn = builderCard.querySelector('#apply-builder-btn'); + if (applyBuilderBtn) { + applyBuilderBtn.onclick = () => { + currentSettings.camera = builderCamera?.value || currentSettings.camera; + currentSettings.lens = builderLens?.value || currentSettings.lens; + currentSettings.focal = parseInt(builderFocal?.value || currentSettings.focal); + currentSettings.aperture = builderAperture?.value || currentSettings.aperture; + updateSummaryCard(); + showCameraBuilder = false; + cameraBuilderPanel.style.display = 'none'; + }; + } + // ========================================== // 3. HISTORY SIDEBAR diff --git a/src/components/ImageStudio.js b/src/components/ImageStudio.js index ef7c7dd..e01bc8c 100644 --- a/src/components/ImageStudio.js +++ b/src/components/ImageStudio.js @@ -4,6 +4,7 @@ import { i2iModels, getAspectRatiosForI2IModel, getResolutionsForI2IModel, getQualityFieldForI2IModel, getMaxImagesForI2IModel } from '../lib/models.js'; +import { ENHANCE_TAGS, QUICK_PROMPTS } from '../lib/promptUtils.js'; import { AuthModal } from './AuthModal.js'; import { createUploadPicker } from './UploadPicker.js'; import { savePendingJob, removePendingJob, getPendingJobs } from '../lib/pendingJobs.js'; @@ -21,6 +22,25 @@ export function ImageStudio() { let uploadedImageUrls = []; // array of uploaded image URLs (multi-image support) let imageMode = false; // false = t2i models, true = i2i models + // Advanced parameters state + let negativePrompt = ''; + let guidanceScale = 7.5; + let steps = 25; + let seed = -1; + let showAdvanced = false; + let selectedStyle = 'None'; + let batchCount = 1; + + // New advanced controls + let customWidth = 0; // 0 means use default (aspect ratio based) + let customHeight = 0; + let referenceStrength = 50; // 0-100, for style reference models + let selectedLora = ''; // LoRA model ID from Civitai + let loraWeight = 1.0; + + // Quick tools panel state + let showToolsPanel = false; + const getCurrentModels = () => imageMode ? i2iModels : t2iModels; const getCurrentAspectRatios = (id) => imageMode ? getAspectRatiosForI2IModel(id) : getAspectRatiosForModel(id); const getCurrentResolutions = (id) => imageMode ? getResolutionsForI2IModel(id) : getResolutionsForModel(id); @@ -156,21 +176,419 @@ export function ImageStudio() { controlsLeft.appendChild(modelBtn); controlsLeft.appendChild(arBtn); controlsLeft.appendChild(qualityBtn); - // Show quality button if the default model has quality/resolution options - const _initResolutions = getResolutionsForModel(defaultModel.id); - qualityBtn.style.display = _initResolutions.length > 0 ? 'flex' : 'none'; - if (_initResolutions.length > 0) document.getElementById('quality-btn-label').textContent = _initResolutions[0]; + const inlineInstructions = createInlineInstructions('image'); + inlineInstructions.classList.add('max-w-4xl', 'mt-8'); + container.appendChild(inlineInstructions); - const generateBtn = document.createElement('button'); - generateBtn.className = 'bg-primary text-black px-6 md:px-8 py-3 md:py-3.5 rounded-xl md:rounded-[1.5rem] font-black text-sm md:text-base hover:shadow-glow hover:scale-105 active:scale-95 transition-all flex items-center justify-center gap-2.5 w-full sm:w-auto shadow-lg'; - generateBtn.innerHTML = `Generate ✨`; + // ========================================== + // 3. QUICK TOOLS PANEL (Prompt Enhancer + Quick Starters) + // ========================================== + const toolsPanel = document.createElement('div'); + toolsPanel.className = 'w-full max-w-4xl mt-6 animate-fade-in-up hidden'; + toolsPanel.id = 'tools-panel'; + + // Build tools panel HTML + toolsPanel.innerHTML = ` +
+
+

Quick Tools

+ +
+ +
+ +
+

Quick Starters

+
+ ${QUICK_PROMPTS.map(q => ` + + `).join('')} +
+
+ + +
+

Prompt Enhancer

+
+ + +
+ +
+ ${Object.entries(ENHANCE_TAGS).map(([category, tags]) => + tags.map(tag => ``).join('') + ).join('')} +
+
+ +
+ +
+
+ + +
+
+
+
+
+
+ `; + + container.appendChild(toolsPanel); - bottomRow.appendChild(controlsLeft); - bottomRow.appendChild(generateBtn); - bar.appendChild(bottomRow); - promptWrapper.appendChild(bar); - container.appendChild(promptWrapper); + // ========================================== + // 4. ADVANCED OPTIONS PANEL + // ========================================== + const STYLE_PRESETS = ['None', 'Photorealistic', 'Anime', 'Cinematic', 'Oil Painting', 'Watercolor', 'Digital Art', 'Concept Art', 'Cyberpunk']; + + const advancedPanel = document.createElement('div'); + advancedPanel.className = 'w-full max-w-4xl mt-6 animate-fade-in-up hidden'; + advancedPanel.id = 'advanced-panel'; + advancedPanel.innerHTML = ` +
+
+

Advanced Options

+ +
+ + +
+ +
+ ${STYLE_PRESETS.map(s => ``).join('')} +
+
+ + +
+ + +
+ + +
+
+
+ + 7.5 +
+ +
+ +
+
+ + 25 +
+ +
+
+ + +
+
+ + +
+ +
+ + +
+
+ + 1 +
+ +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + 50% +
+ +

How much to preserve the reference image characteristics

+
+ + +
+ + +
+ + +
+

Enter a LoRA model ID from Civitai (format: civitai:id@version)

+
+
+ `; + container.appendChild(advancedPanel); + // Advanced panel toggle logic + const toggleAdvanced = () => { + showAdvanced = !showAdvanced; + advancedPanel.classList.toggle('hidden', !showAdvanced); + document.getElementById('advanced-btn-label').textContent = showAdvanced ? 'Less' : 'Advanced'; + }; + + // Add tools panel and advanced panel to container first before accessing their elements + container.appendChild(toolsPanel); + container.appendChild(advancedPanel); + + // Now set up event handlers after elements are in DOM + advancedBtn.onclick = toggleAdvanced; + const closeAdvBtn = advancedPanel.querySelector('#close-adv-btn'); + if (closeAdvBtn) closeAdvBtn.onclick = toggleAdvanced; + + // Quick Tools Panel toggle + const toggleTools = () => { + showToolsPanel = !showToolsPanel; + toolsPanel.classList.toggle('hidden', !showToolsPanel); + if (showToolsPanel) { + // Close advanced panel when opening tools + if (!showAdvanced) { + showAdvanced = true; + advancedPanel.classList.remove('hidden'); + } + } + document.getElementById('tools-btn-label').textContent = showToolsPanel ? 'Tools' : 'Tools'; + }; + + toolsBtn.onclick = toggleTools; + const closeToolsBtn = toolsPanel.querySelector('#close-tools-btn'); + if (closeToolsBtn) closeToolsBtn.onclick = toggleTools; + + // Quick Starter buttons + const quickStarterBtns = toolsPanel.querySelectorAll('.quick-starter-btn'); + quickStarterBtns.forEach(btn => { + btn.onclick = () => { + const prompt = btn.dataset.prompt; + textarea.value = prompt; + textarea.style.height = 'auto'; + const maxHeight = window.innerWidth < 768 ? 150 : 250; + textarea.style.height = Math.min(textarea.scrollHeight, maxHeight) + 'px'; + // Close tools panel after selection + showToolsPanel = false; + toolsPanel.classList.add('hidden'); + }; + }); + + // Prompt Enhancer - selected tags state + const enhanceSelectedTags = new Set(); + const basePromptInput = toolsPanel.querySelector('#base-prompt-input'); + const enhancedPromptDisplay = toolsPanel.querySelector('#enhanced-prompt-display'); + + // Update enhanced prompt display + const updateEnhancedPrompt = () => { + const base = basePromptInput?.value?.trim() || ''; + const tags = Array.from(enhanceSelectedTags).join(', '); + const enhanced = [base, tags].filter(p => p).join(', '); + if (enhancedPromptDisplay) { + enhancedPromptDisplay.textContent = enhanced || 'Your enhanced prompt will appear here...'; + enhancedPromptDisplay.classList.toggle('text-muted', !enhanced); + } + }; + + // Base prompt input handler + if (basePromptInput) { + basePromptInput.oninput = updateEnhancedPrompt; + } + + // Enhance tag buttons + const enhanceTagBtns = toolsPanel.querySelectorAll('.enhance-tag-btn'); + enhanceTagBtns.forEach(btn => { + btn.onclick = () => { + const tag = btn.dataset.tag; + if (enhanceSelectedTags.has(tag)) { + enhanceSelectedTags.delete(tag); + btn.classList.remove('bg-primary', 'text-black'); + btn.classList.add('bg-white/5', 'text-secondary'); + } else { + enhanceSelectedTags.add(tag); + btn.classList.remove('bg-white/5', 'text-secondary'); + btn.classList.add('bg-primary', 'text-black'); + } + updateEnhancedPrompt(); + }; + }); + + // Copy enhanced button + const copyEnhancedBtn = toolsPanel.querySelector('#copy-enhanced-btn'); + if (copyEnhancedBtn) { + copyEnhancedBtn.onclick = () => { + const text = enhancedPromptDisplay?.textContent || ''; + if (text && text !== 'Your enhanced prompt will appear here...') { + navigator.clipboard.writeText(text); + copyEnhancedBtn.textContent = 'Copied!'; + setTimeout(() => { copyEnhancedBtn.textContent = 'Copy'; }, 1500); + } + }; + } + + // Use enhanced button + const useEnhancedBtn = toolsPanel.querySelector('#use-enhanced-btn'); + if (useEnhancedBtn) { + useEnhancedBtn.onclick = () => { + const text = enhancedPromptDisplay?.textContent || ''; + if (text && text !== 'Your enhanced prompt will appear here...') { + textarea.value = text; + textarea.style.height = 'auto'; + const maxHeight = window.innerWidth < 768 ? 150 : 250; + textarea.style.height = Math.min(textarea.scrollHeight, maxHeight) + 'px'; + // Close tools panel after use + showToolsPanel = false; + toolsPanel.classList.add('hidden'); + } + }; + } + + // Negative prompt + const negPromptInput = advancedPanel.querySelector('#negative-prompt-input'); + if (negPromptInput) negPromptInput.oninput = (e) => { negativePrompt = e.target.value; }; + + // Guidance scale slider + const guidanceSlider = advancedPanel.querySelector('#guidance-slider'); + const guidanceValue = advancedPanel.querySelector('#guidance-value'); + if (guidanceSlider && guidanceValue) { + guidanceSlider.oninput = (e) => { + guidanceScale = parseFloat(e.target.value); + guidanceValue.textContent = guidanceScale; + }; + } + + // Steps slider + const stepsSlider = advancedPanel.querySelector('#steps-slider'); + const stepsValue = advancedPanel.querySelector('#steps-value'); + if (stepsSlider && stepsValue) { + stepsSlider.oninput = (e) => { + steps = parseInt(e.target.value); + stepsValue.textContent = steps; + }; + } + + // Seed input + const seedInput = advancedPanel.querySelector('#seed-input'); + if (seedInput) seedInput.oninput = (e) => { seed = parseInt(e.target.value) || -1; }; + + // Randomize seed button + const randSeedBtn = advancedPanel.querySelector('#randomize-seed-btn'); + if (randSeedBtn) { + randSeedBtn.onclick = () => { + seed = Math.floor(Math.random() * 999999999); + if (seedInput) seedInput.value = seed; + }; + } + + // Batch count slider + const batchSlider = advancedPanel.querySelector('#batch-slider'); + const batchValueEl = advancedPanel.querySelector('#batch-value'); + if (batchSlider && batchValueEl) { + batchSlider.oninput = (e) => { + batchCount = parseInt(e.target.value); + batchValueEl.textContent = batchCount; + }; + } + + // Width input + const widthInput = advancedPanel.querySelector('#width-input'); + if (widthInput) { + widthInput.oninput = (e) => { + customWidth = parseInt(e.target.value) || 0; + }; + } + + // Height input + const heightInput = advancedPanel.querySelector('#height-input'); + if (heightInput) { + heightInput.oninput = (e) => { + customHeight = parseInt(e.target.value) || 0; + }; + } + + // Reference strength slider + const refStrengthSlider = advancedPanel.querySelector('#reference-strength-slider'); + const refStrengthValue = advancedPanel.querySelector('#reference-strength-value'); + if (refStrengthSlider && refStrengthValue) { + refStrengthSlider.oninput = (e) => { + referenceStrength = parseInt(e.target.value); + refStrengthValue.textContent = referenceStrength + '%'; + }; + } + + // LoRA input + const loraInput = advancedPanel.querySelector('#lora-input'); + if (loraInput) { + loraInput.oninput = (e) => { + selectedLora = e.target.value.trim(); + }; + } + + // LoRA weight input + const loraWeightInput = advancedPanel.querySelector('#lora-weight-input'); + if (loraWeightInput) { + loraWeightInput.oninput = (e) => { + loraWeight = parseFloat(e.target.value) || 1.0; + }; + } + + // Style preset handlers + advancedPanel.querySelectorAll('.style-preset-btn').forEach(btn => { + btn.onclick = () => { + selectedStyle = btn.dataset.style; + advancedPanel.querySelectorAll('.style-preset-btn').forEach(b => { + b.classList.remove('bg-primary/20', 'text-primary', 'border-primary/30'); + b.classList.add('bg-white/5', 'text-secondary'); + }); + btn.classList.add('bg-primary/20', 'text-primary', 'border-primary/30'); + btn.classList.remove('bg-white/5', 'text-secondary'); + }; + }); // ========================================== // 3. DROPDOWNS (Professional implementation) // ========================================== diff --git a/src/lib/promptUtils.js b/src/lib/promptUtils.js index 6c657c9..fbb32bb 100644 --- a/src/lib/promptUtils.js +++ b/src/lib/promptUtils.js @@ -1,3 +1,21 @@ +export const ENHANCE_TAGS = { + quality: ['professional photography', 'ultra-detailed', '8K resolution', 'high dynamic range', 'award-winning'], + lighting: ['cinematic lighting', 'golden hour', 'dramatic studio lighting', 'soft diffused light', 'neon glow', 'volumetric rays'], + mood: ['moody atmosphere', 'serene and peaceful', 'epic and dramatic', 'warm and cozy', 'dark and mysterious'], + style: ['photorealistic', 'oil painting style', 'watercolor', 'digital art', 'concept art', 'anime style', 'cyberpunk aesthetic'], +}; + +export const QUICK_PROMPTS = [ + { label: 'Portrait', prompt: 'Professional portrait photograph, shallow depth of field, soft studio lighting, 85mm lens' }, + { label: 'Landscape', prompt: 'Breathtaking landscape photograph, golden hour, wide angle, dramatic clouds, 4K' }, + { label: 'Product', prompt: 'Commercial product photography, clean white background, studio lighting, professional' }, + { label: 'Fantasy', prompt: 'Epic fantasy scene, magical atmosphere, volumetric lighting, highly detailed, concept art' }, + { label: 'Sci-Fi', prompt: 'Futuristic sci-fi environment, neon lights, cyberpunk city, rain reflections, cinematic' }, + { label: 'Food', prompt: 'Professional food photography, appetizing, warm lighting, shallow depth of field, editorial' }, + { label: 'Architecture', prompt: 'Architectural photography, dramatic angles, clean lines, modern design, professional' }, + { label: 'Fashion', prompt: 'High fashion editorial, avant-garde styling, studio lighting, Vogue aesthetic, professional' }, +]; + export const CAMERA_MAP = { "Modular 8K Digital": "modular 8K digital cinema camera", "Full-Frame Cine Digital": "full-frame digital cinema camera", From 2c95a86af8eb773df641766450811200cf984275 Mon Sep 17 00:00:00 2001 From: Developer Date: Wed, 25 Mar 2026 12:52:01 +0000 Subject: [PATCH 2/2] feat: Add hover tooltips to platform buttons - Add CSS tooltip system with data-tooltip attribute - Tooltips appear on hover with smooth animation - Non-blocking: uses pointer-events: none - Added to ImageStudio, VideoStudio, and CinemaStudio buttons --- src/components/CinemaStudio.js | 3 + src/components/ImageStudio.js | 40 +++++++- src/components/VideoStudio.js | 21 ++-- src/styles/global.css | 177 +++++++++++++++++++++++++++++++++ 4 files changed, 230 insertions(+), 11 deletions(-) diff --git a/src/components/CinemaStudio.js b/src/components/CinemaStudio.js index 15faef2..1c46fc2 100644 --- a/src/components/CinemaStudio.js +++ b/src/components/CinemaStudio.js @@ -187,6 +187,7 @@ export function CinemaStudio() { // Camera Builder Toggle Button const cameraBuilderBtn = document.createElement('button'); cameraBuilderBtn.className = 'flex items-center gap-1.5 px-3 py-1.5 text-[10px] font-bold text-white/50 hover:text-white transition-colors bg-white/5 hover:bg-white/10 rounded-lg border border-white/5'; + cameraBuilderBtn.setAttribute('data-tooltip', 'Quick camera builder'); cameraBuilderBtn.innerHTML = ` Builder`; settingsToolbar.appendChild(cameraBuilderBtn); @@ -202,6 +203,7 @@ export function CinemaStudio() { const summaryCard = document.createElement('button'); // Removed 'hidden' class, added 'flex' and refined width constraints for mobile summaryCard.className = 'flex flex-col items-start justify-center px-4 py-2 bg-[#2a2a2a] rounded-xl border border-white/5 hover:border-white/20 transition-colors text-left flex-1 min-w-[100px] md:min-w-[140px] max-w-[240px] h-[56px] relative group overflow-hidden'; + summaryCard.setAttribute('data-tooltip', 'Open camera settings'); // Dot indicator const dot = document.createElement('div'); @@ -233,6 +235,7 @@ export function CinemaStudio() { // Generate Button const generateBtn = document.createElement('button'); generateBtn.className = 'h-[56px] px-8 bg-[#d9ff00] text-black rounded-xl font-black text-xs uppercase hover:bg-white transition-colors shadow-lg disabled:opacity-50 disabled:cursor-not-allowed'; + generateBtn.setAttribute('data-tooltip', 'Generate cinema shot'); generateBtn.innerHTML = `GENERATE ✨`; rightGroup.appendChild(summaryCard); diff --git a/src/components/ImageStudio.js b/src/components/ImageStudio.js index e01bc8c..c66deec 100644 --- a/src/components/ImageStudio.js +++ b/src/components/ImageStudio.js @@ -147,10 +147,11 @@ export function ImageStudio() { const controlsLeft = document.createElement('div'); controlsLeft.className = 'flex items-center gap-1.5 md:gap-2.5 relative overflow-x-auto no-scrollbar pb-1 md:pb-0'; - const createControlBtn = (icon, label, id) => { + const createControlBtn = (icon, label, id, tooltip) => { const btn = document.createElement('button'); btn.id = id; btn.className = 'flex items-center gap-1.5 md:gap-2.5 px-3 md:px-4 py-2 md:py-2.5 bg-white/5 hover:bg-white/10 rounded-xl md:rounded-2xl transition-all border border-white/5 group whitespace-nowrap'; + if (tooltip) btn.setAttribute('data-tooltip', tooltip); btn.innerHTML = ` ${icon} ${label} @@ -163,19 +164,50 @@ export function ImageStudio() {
G
- `, selectedModelName, 'model-btn'); + `, selectedModelName, 'model-btn', 'Select AI generation model'); const arBtn = createControlBtn(` - `, selectedAr, 'ar-btn'); + `, selectedAr, 'ar-btn', 'Change aspect ratio'); const qualityBtn = createControlBtn(` - `, '720p', 'quality-btn'); + `, '720p', 'quality-btn', 'Set output quality'); controlsLeft.appendChild(modelBtn); controlsLeft.appendChild(arBtn); controlsLeft.appendChild(qualityBtn); + + // Advanced options toggle button + const advancedBtn = createControlBtn(` + + `, 'Advanced', 'advanced-btn', 'Show advanced options'); + controlsLeft.appendChild(advancedBtn); + + // Quick Tools toggle button + const toolsBtn = createControlBtn(` + + `, 'Tools', 'tools-btn', 'Quick starters & prompt enhancer'); + controlsLeft.appendChild(toolsBtn); + // Show quality button if the default model has quality/resolution options + const _initResolutions = getResolutionsForModel(defaultModel.id); + qualityBtn.style.display = _initResolutions.length > 0 ? 'flex' : 'none'; + if (_initResolutions.length > 0) { + const qlabel = qualityBtn.querySelector('#quality-btn-label'); + if (qlabel) qlabel.textContent = _initResolutions[0]; + } + + const generateBtn = document.createElement('button'); + generateBtn.className = 'bg-primary text-black px-6 md:px-8 py-3 md:py-3.5 rounded-xl md:rounded-[1.5rem] font-black text-sm md:text-base hover:shadow-glow hover:scale-105 active:scale-95 transition-all flex items-center justify-center gap-2.5 w-full sm:w-auto shadow-lg'; + generateBtn.setAttribute('data-tooltip', 'Generate AI image from prompt'); + generateBtn.innerHTML = `Generate ✨`; + + bottomRow.appendChild(controlsLeft); + bottomRow.appendChild(generateBtn); + bar.appendChild(bottomRow); + promptWrapper.appendChild(bar); + container.appendChild(promptWrapper); + const inlineInstructions = createInlineInstructions('image'); inlineInstructions.classList.add('max-w-4xl', 'mt-8'); container.appendChild(inlineInstructions); diff --git a/src/components/VideoStudio.js b/src/components/VideoStudio.js index afc298e..2c4e632 100644 --- a/src/components/VideoStudio.js +++ b/src/components/VideoStudio.js @@ -252,10 +252,11 @@ export function VideoStudio() { const controlsLeft = document.createElement('div'); controlsLeft.className = 'flex items-center gap-1.5 md:gap-2.5 relative overflow-x-auto no-scrollbar pb-1 md:pb-0'; - const createControlBtn = (icon, label, id) => { + const createControlBtn = (icon, label, id, tooltip) => { const btn = document.createElement('button'); btn.id = id; btn.className = 'flex items-center gap-1.5 md:gap-2.5 px-3 md:px-4 py-2 md:py-2.5 bg-white/5 hover:bg-white/10 rounded-xl md:rounded-2xl transition-all border border-white/5 group whitespace-nowrap'; + if (tooltip) btn.setAttribute('data-tooltip', tooltip); btn.innerHTML = ` ${icon} ${label} @@ -268,23 +269,23 @@ export function VideoStudio() {
V
- `, selectedModelName, 'v-model-btn'); + `, selectedModelName, 'v-model-btn', 'Select AI video model'); const arBtn = createControlBtn(` - `, selectedAr, 'v-ar-btn'); + `, selectedAr, 'v-ar-btn', 'Change aspect ratio'); const durationBtn = createControlBtn(` - `, `${selectedDuration}s`, 'v-duration-btn'); + `, `${selectedDuration}s`, 'v-duration-btn', 'Set video duration'); const resolutionBtn = createControlBtn(` - `, selectedResolution || '720p', 'v-resolution-btn'); + `, selectedResolution || '720p', 'v-resolution-btn', 'Set output resolution'); const qualityBtn = createControlBtn(` - `, selectedQuality || 'basic', 'v-quality-btn'); + `, selectedQuality || 'basic', 'v-quality-btn', 'Set output quality'); const modeBtn = createControlBtn(` @@ -295,7 +296,12 @@ export function VideoStudio() { controlsLeft.appendChild(durationBtn); controlsLeft.appendChild(resolutionBtn); controlsLeft.appendChild(qualityBtn); - controlsLeft.appendChild(modeBtn); + + // Advanced options toggle button + const advancedBtn = createControlBtn(` + + `, 'Advanced', 'v-advanced-btn', 'Show advanced options'); + controlsLeft.appendChild(advancedBtn); // Initial visibility (t2v mode) const initDurations = getDurationsForModel(defaultModel.id); @@ -307,6 +313,7 @@ export function VideoStudio() { const generateBtn = document.createElement('button'); generateBtn.className = 'bg-primary text-black px-6 md:px-8 py-3 md:py-3.5 rounded-xl md:rounded-[1.5rem] font-black text-sm md:text-base hover:shadow-glow hover:scale-105 active:scale-95 transition-all flex items-center justify-center gap-2.5 w-full sm:w-auto shadow-lg'; + generateBtn.setAttribute('data-tooltip', 'Generate AI video from prompt'); generateBtn.innerHTML = `Generate ✨`; bottomRow.appendChild(controlsLeft); diff --git a/src/styles/global.css b/src/styles/global.css index f0b334d..970d6ba 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -72,4 +72,181 @@ .animate-fade-in-up { animation: fade-in-up 0.6s cubic-bezier(0.23, 1, 0.32, 1) forwards; +} + +/* Thumbnail cards */ +.thumb-hero { + position: relative; + overflow: hidden; +} + +.thumb-hero img { + width: 100%; + height: 100%; + object-fit: cover; + object-position: top center; + transition: transform 0.4s cubic-bezier(0.23, 1, 0.32, 1); +} + +.thumb-hero::after { + content: ''; + position: absolute; + bottom: 0; + left: 0; + right: 0; + height: 60%; + background: linear-gradient(to top, rgba(0, 0, 0, 0.75), transparent); + pointer-events: none; +} + +.thumb-hero:hover img, +.group:hover .thumb-hero img { + transform: scale(1.05); +} + +.thumb-skeleton { + background: rgba(255, 255, 255, 0.03); + animation: skeleton-pulse 1.5s ease-in-out infinite; +} + +@keyframes skeleton-pulse { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.4; } +} + +.thumb-fallback .thumb-hero { + display: none; +} + +.hero-banner img { + transition: transform 0.6s cubic-bezier(0.23, 1, 0.32, 1); +} + +.hero-banner:hover img { + transform: scale(1.03); +} + +@media (prefers-reduced-motion: reduce) { + .animate-fade-in-up { + animation: none; + opacity: 1; + } + + .thumb-hero img { + transition: none; + } + + .thumb-skeleton { + animation: none; + } +} + +/* ======================== + TOOLTIP SYSTEM + ======================== */ + +/* Base tooltip container */ +[data-tooltip] { + position: relative; + display: inline-flex; + align-items: center; + justify-content: center; +} + +/* Tooltip arrow */ +[data-tooltip]::before { + content: ''; + position: absolute; + bottom: 100%; + left: 50%; + transform: translateX(-50%) translateY(8px); + border: 6px solid transparent; + border-top-color: #1a1a1a; + opacity: 0; + pointer-events: none; + transition: all 0.2s ease; + z-index: 9999; +} + +/* Tooltip body */ +[data-tooltip]::after { + content: attr(data-tooltip); + position: absolute; + bottom: 100%; + left: 50%; + transform: translateX(-50%) translateY(12px); + padding: 8px 14px; + background: #1a1a1a; + border: 1px solid rgba(255,255,255,0.1); + border-radius: 10px; + font-size: 12px; + font-weight: 600; + color: #fff; + white-space: nowrap; + opacity: 0; + pointer-events: none; + transition: all 0.2s ease; + z-index: 9999; + box-shadow: 0 10px 30px -10px rgba(0,0,0,0.5); +} + +/* Show tooltip on hover */ +[data-tooltip]:hover::before, +[data-tooltip]:hover::after { + opacity: 1; + transform: translateX(-50%) translateY(-6px); +} + +/* Tooltip positioning variants */ +[data-tooltip-bottom]::before { + bottom: auto; + top: 100%; + border-top-color: transparent; + border-bottom-color: #1a1a1a; + transform: translateX(-50%) translateY(-8px); +} + +[data-tooltip-bottom]::after { + bottom: auto; + top: 100%; + transform: translateX(-50%) translateY(-12px); +} + +[data-tooltip-bottom]:hover::before, +[data-tooltip-bottom]:hover::after { + transform: translateX(-50%) translateY(6px); +} + +/* Tooltip for left-aligned elements */ +[data-tooltip-left]::before { + left: 0; + transform: translateX(-100%) translateY(-50%); +} + +[data-tooltip-left]::after { + left: 0; + transform: translateX(calc(-100% - 10px)) translateY(-50%); +} + +[data-tooltip-left]:hover::before, +[data-tooltip-left]:hover::after { + transform: translateX(calc(-100% - 6px)) translateY(-50%); +} + +/* Tooltip for right-aligned elements */ +[data-tooltip-right]::before { + left: auto; + right: 0; + transform: translateX(100%) translateY(-50%); +} + +[data-tooltip-right]::after { + left: auto; + right: 0; + transform: translateX(calc(100% + 10px)) translateY(-50%); +} + +[data-tooltip-right]:hover::before, +[data-tooltip-right]:hover::after { + transform: translateX(calc(100% + 6px)) translateY(-50%); } \ No newline at end of file