From 2c95a86af8eb773df641766450811200cf984275 Mon Sep 17 00:00:00 2001 From: Developer Date: Wed, 25 Mar 2026 12:52:01 +0000 Subject: [PATCH] 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