Floating options left
This commit is contained in:
160
app/page.tsx
160
app/page.tsx
@@ -1,7 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { Camera, History, VideoOff } from 'lucide-react';
|
||||
import { Camera, History, VideoOff, Settings, Video, Volume2, Palette, ChevronRight } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; // Assuming shadcn/ui has Select
|
||||
import { SHOE_DATABASE, type Shoe } from '@/lib/shoe-database';
|
||||
@@ -24,6 +24,7 @@ export default function HomePage() {
|
||||
const [isPopupOpen, setPopupOpen] = useState(false);
|
||||
const [isHistoryOpen, setHistoryOpen] = useState(false);
|
||||
const [history, setHistory] = useState<Shoe[]>([]);
|
||||
const [isSettingsPanelOpen, setSettingsPanelOpen] = useState(false);
|
||||
|
||||
// Effect to clean up the stream when component unmounts or stream changes
|
||||
useEffect(() => {
|
||||
@@ -185,17 +186,38 @@ export default function HomePage() {
|
||||
<div className="w-16 h-16 bg-white/20 rounded-full animate-pulse"></div>
|
||||
</div>
|
||||
|
||||
{/* Particle Effect */}
|
||||
{/* Particle Effect - Fixed positions to avoid hydration mismatch */}
|
||||
<div className="absolute inset-0 overflow-hidden pointer-events-none">
|
||||
{[...Array(20)].map((_, i) => (
|
||||
{[
|
||||
{ left: 10, top: 20, delay: 0, duration: 2.5 },
|
||||
{ left: 80, top: 15, delay: 0.3, duration: 3 },
|
||||
{ left: 25, top: 70, delay: 0.8, duration: 2.2 },
|
||||
{ left: 90, top: 40, delay: 1.2, duration: 2.8 },
|
||||
{ left: 5, top: 85, delay: 0.5, duration: 3.2 },
|
||||
{ left: 70, top: 25, delay: 1.5, duration: 2.4 },
|
||||
{ left: 40, top: 60, delay: 0.2, duration: 2.9 },
|
||||
{ left: 85, top: 75, delay: 1.8, duration: 2.6 },
|
||||
{ left: 15, top: 45, delay: 0.7, duration: 3.1 },
|
||||
{ left: 60, top: 10, delay: 1.1, duration: 2.3 },
|
||||
{ left: 30, top: 90, delay: 0.4, duration: 2.7 },
|
||||
{ left: 95, top: 55, delay: 1.6, duration: 2.1 },
|
||||
{ left: 50, top: 30, delay: 0.9, duration: 2.8 },
|
||||
{ left: 20, top: 65, delay: 0.1, duration: 3.3 },
|
||||
{ left: 75, top: 80, delay: 1.3, duration: 2.2 },
|
||||
{ left: 35, top: 5, delay: 0.6, duration: 2.9 },
|
||||
{ left: 65, top: 50, delay: 1.4, duration: 2.5 },
|
||||
{ left: 8, top: 35, delay: 1.7, duration: 3.1 },
|
||||
{ left: 88, top: 95, delay: 0.3, duration: 2.4 },
|
||||
{ left: 45, top: 22, delay: 1.9, duration: 2.7 }
|
||||
].map((particle, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="absolute w-1 h-1 bg-blue-500/30 rounded-full animate-ping"
|
||||
style={{
|
||||
left: `${Math.random() * 100}%`,
|
||||
top: `${Math.random() * 100}%`,
|
||||
animationDelay: `${Math.random() * 2}s`,
|
||||
animationDuration: `${2 + Math.random() * 2}s`,
|
||||
left: `${particle.left}%`,
|
||||
top: `${particle.top}%`,
|
||||
animationDelay: `${particle.delay}s`,
|
||||
animationDuration: `${particle.duration}s`,
|
||||
}}
|
||||
></div>
|
||||
))}
|
||||
@@ -207,22 +229,134 @@ export default function HomePage() {
|
||||
return (
|
||||
<>
|
||||
<video ref={videoRef} autoPlay playsInline muted onCanPlay={() => videoRef.current?.play()} className="h-full w-full object-cover" />
|
||||
<div className="absolute inset-0 flex flex-col items-center justify-between p-6">
|
||||
<div className="flex w-full items-start justify-between gap-2">
|
||||
<div className="w-64">
|
||||
|
||||
{/* Settings Panel Trigger - Bouncing Tab */}
|
||||
<div
|
||||
className={`absolute left-0 top-1/2 transform -translate-y-1/2 z-50 transition-all duration-500 ease-out ${
|
||||
isSettingsPanelOpen ? 'translate-x-80' : 'translate-x-0'
|
||||
}`}
|
||||
>
|
||||
<button
|
||||
onClick={() => setSettingsPanelOpen(!isSettingsPanelOpen)}
|
||||
className="bg-white/20 backdrop-blur-sm border border-white/30 rounded-r-2xl pl-4 pr-3 py-6 text-white shadow-xl hover:bg-white/30 transition-all duration-300 group animate-bounce"
|
||||
style={{
|
||||
animation: isSettingsPanelOpen ? 'none' : 'bounce 2s infinite'
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Settings
|
||||
size={20}
|
||||
className={`transition-transform duration-300 ${
|
||||
isSettingsPanelOpen ? 'rotate-90' : 'group-hover:rotate-12'
|
||||
}`}
|
||||
/>
|
||||
<ChevronRight
|
||||
size={16}
|
||||
className={`transition-transform duration-300 ${
|
||||
isSettingsPanelOpen ? 'rotate-180' : ''
|
||||
}`}
|
||||
/>
|
||||
</div>
|
||||
<div className="absolute -right-1 top-3 w-2 h-8 bg-gradient-to-r from-transparent to-white/10 rounded-r-full"></div>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Settings Panel */}
|
||||
<div className={`absolute left-0 top-0 bottom-0 w-80 bg-black/80 backdrop-blur-xl border-r border-white/20 transform transition-transform duration-500 ease-out z-40 ${
|
||||
isSettingsPanelOpen ? 'translate-x-0' : '-translate-x-full'
|
||||
}`}>
|
||||
<div className="p-6 h-full flex flex-col">
|
||||
{/* Panel Header */}
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<h2 className="text-xl font-bold text-white flex items-center gap-2">
|
||||
<Settings size={24} className="text-blue-400" />
|
||||
Configuración
|
||||
</h2>
|
||||
<button
|
||||
onClick={() => setSettingsPanelOpen(false)}
|
||||
className="text-white/60 hover:text-white transition-colors p-1"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Camera Selection */}
|
||||
<div className="mb-6">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<Video size={20} className="text-blue-400" />
|
||||
<label className="text-white font-medium">Cámara</label>
|
||||
</div>
|
||||
<Select value={selectedDeviceId} onValueChange={handleCameraChange}>
|
||||
<SelectTrigger className="w-full bg-black/50 text-white border-white/30">
|
||||
<SelectTrigger className="w-full bg-white/10 border-white/20 text-white hover:bg-white/20 transition-colors">
|
||||
<SelectValue placeholder="Seleccionar cámara..." />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectContent className="bg-black/90 backdrop-blur-xl border-white/20">
|
||||
{videoDevices.map((device) => (
|
||||
<SelectItem key={device.deviceId} value={device.deviceId}>
|
||||
<SelectItem key={device.deviceId} value={device.deviceId} className="text-white hover:bg-white/20">
|
||||
{device.label || `Cámara ${videoDevices.indexOf(device) + 1}`}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{/* Additional Options */}
|
||||
<div className="space-y-4 flex-1">
|
||||
{/* Quality Settings */}
|
||||
<div className="bg-white/5 rounded-lg p-4 border border-white/10">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<Palette size={20} className="text-green-400" />
|
||||
<span className="text-white font-medium">Calidad de Video</span>
|
||||
</div>
|
||||
<div className="grid grid-cols-3 gap-2">
|
||||
{['HD', 'FHD', '4K'].map((quality) => (
|
||||
<button
|
||||
key={quality}
|
||||
className="bg-white/10 hover:bg-blue-500/30 text-white text-sm py-2 px-3 rounded-md transition-colors border border-white/20"
|
||||
>
|
||||
{quality}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Audio Settings */}
|
||||
<div className="bg-white/5 rounded-lg p-4 border border-white/10">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<Volume2 size={20} className="text-purple-400" />
|
||||
<span className="text-white font-medium">Audio</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<button className="bg-white/10 hover:bg-purple-500/30 text-white text-sm py-2 px-4 rounded-md transition-colors border border-white/20">
|
||||
Silenciado
|
||||
</button>
|
||||
<span className="text-white/60 text-sm">Recomendado para mejor rendimiento</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* App Info */}
|
||||
<div className="bg-gradient-to-r from-blue-500/10 to-purple-500/10 rounded-lg p-4 border border-blue-500/20">
|
||||
<h3 className="text-white font-medium mb-2">Smart Store Assistant</h3>
|
||||
<p className="text-white/70 text-sm">
|
||||
Detector inteligente de Calzado con IA avanzada
|
||||
</p>
|
||||
<div className="mt-3 text-xs text-blue-300">
|
||||
v0.0.3 • Powered by Moonshot
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Footer */}
|
||||
<div className="pt-4 border-t border-white/10">
|
||||
<button className="w-full bg-gradient-to-r from-blue-500 to-purple-500 hover:from-blue-600 hover:to-purple-600 text-white font-medium py-2 px-4 rounded-lg transition-all duration-200 transform hover:scale-105">
|
||||
Guardar Configuración
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="absolute inset-0 flex flex-col items-center justify-between p-6">
|
||||
<div className="flex w-full items-start justify-end gap-2">
|
||||
<Button variant="ghost" size="icon" className="text-white hover:bg-white/20 hover:text-white" onClick={() => setHistoryOpen(true)}>
|
||||
<History size={24} />
|
||||
</Button>
|
||||
|
||||
Reference in New Issue
Block a user