From c25d2664b8021c138ae41deecc80c83522f18928 Mon Sep 17 00:00:00 2001 From: "Khoa.vo" Date: Wed, 7 Jan 2026 23:05:28 +0700 Subject: [PATCH] UI Polish: Refined Lightbox controls, added Cookie Expired popup, and improved mobile filters --- app/globals.css | 141 +++++-- app/layout.tsx | 11 +- app/page.tsx | 28 +- components/BottomNav.tsx | 100 +++++ components/CookieExpiredDialog.tsx | 38 +- components/Gallery.tsx | 530 +++++++++++++----------- components/MobileCookieInstructions.tsx | 99 +++-- components/Navbar.tsx | 86 ++-- components/PromptHero.tsx | 391 ++++++----------- components/PromptLibrary.tsx | 157 ++++--- components/Settings.tsx | 403 ++++++++++++------ components/UploadHistory.tsx | 86 ++-- components/theme-provider.tsx | 74 ++++ data/prompts.json | 7 +- eslint.config.mjs | 28 +- 15 files changed, 1261 insertions(+), 918 deletions(-) create mode 100644 components/BottomNav.tsx create mode 100644 components/theme-provider.tsx diff --git a/app/globals.css b/app/globals.css index 7554243..9bf3c91 100644 --- a/app/globals.css +++ b/app/globals.css @@ -25,50 +25,78 @@ @layer base { :root { - /* Light Mode (from Reference) */ - --background: #F3F4F6; - --foreground: #111827; + /* Light Mode - Slate Refresh */ + --background: #F8FAFC; + /* Slate-50 */ + --foreground: #0F172A; + /* Slate-900 */ --card: #FFFFFF; - --card-foreground: #111827; - --popover: #FFFFFF; - --popover-foreground: #111827; - --primary: #FFD700; - --primary-foreground: #111827; - --secondary: #E5E7EB; - --secondary-foreground: #111827; - --muted: #E5E7EB; - --muted-foreground: #6B7280; - --accent: #FFD700; - --accent-foreground: #111827; + --card-foreground: #1E293B; + /* Slate-800 */ + --popover: rgba(255, 255, 255, 0.8); + --popover-foreground: #0F172A; + --primary: #7C3AED; + /* Violet-600 */ + --primary-foreground: #FFFFFF; + --secondary: #E2E8F0; + /* Slate-200 */ + --secondary-foreground: #0F172A; + --muted: #F1F5F9; + /* Slate-100 */ + --muted-foreground: #64748B; + /* Slate-500 */ + --accent: #E2E8F0; + --accent-foreground: #0F172A; --destructive: #EF4444; - --destructive-foreground: #FEF2F2; - --border: #E5E7EB; - --input: #E5E7EB; - --ring: #FFD700; - --radius: 0.5rem; + --destructive-foreground: #FFFFFF; + --border: #E2E8F0; + /* Slate-200 */ + --input: #F1F5F9; + --ring: rgba(124, 58, 237, 0.4); + --radius: 1.25rem; + /* Modern rounded corners (20px) */ + + /* Spacing & Effects */ + --header-height: 4rem; + --nav-height: 5rem; + --shadow-soft: 0 4px 6px -1px rgb(0 0 0 / 0.05), 0 2px 4px -2px rgb(0 0 0 / 0.05); + --shadow-md: 0 10px 15px -3px rgb(0 0 0 / 0.07), 0 4px 6px -4px rgb(0 0 0 / 0.07); + --shadow-lg: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1); } .dark { - /* Dark Mode (from Reference) */ - --background: #1F2937; - --foreground: #F9FAFB; - --card: #374151; - --card-foreground: #F9FAFB; - --popover: #374151; - --popover-foreground: #F9FAFB; - --primary: #FFD700; - --primary-foreground: #111827; - --secondary: #4B5563; - --secondary-foreground: #F9FAFB; - --muted: #4B5563; - --muted-foreground: #9CA3AF; - --accent: #FFD700; - --accent-foreground: #111827; - --destructive: #EF4444; - --destructive-foreground: #FEF2F2; - --border: #4B5563; - --input: #4B5563; - --ring: #FFD700; + /* Dark Mode - Deep Navy Slate */ + --background: #0F172A; + /* Slate-900 */ + --foreground: #F8FAFC; + /* Slate-50 */ + --card: #1E293B; + /* Slate-800 */ + --card-foreground: #F1F5F9; + --popover: rgba(30, 41, 59, 0.8); + --popover-foreground: #F8FAFC; + --primary: #8B5CF6; + /* Violet-500 (brighter for dark) */ + --primary-foreground: #FFFFFF; + --secondary: #334155; + /* Slate-700 */ + --secondary-foreground: #F8FAFC; + --muted: #334155; + /* Slate-700 */ + --muted-foreground: #94A3B8; + /* Slate-400 */ + --accent: #334155; + --accent-foreground: #F8FAFC; + --destructive: #F87171; + --destructive-foreground: #FFFFFF; + --border: #334155; + /* Slate-700 */ + --input: #0F172A; + --ring: rgba(139, 92, 246, 0.5); + + --shadow-soft: 0 4px 6px -1px rgb(0 0 0 / 0.3), 0 2px 4px -2px rgb(0 0 0 / 0.3); + --shadow-md: 0 10px 15px -3px rgb(0 0 0 / 0.4), 0 4px 6px -4px rgb(0 0 0 / 0.4); + --shadow-lg: 0 20px 25px -5px rgb(0 0 0 / 0.5), 0 8px 10px -6px rgb(0 0 0 / 0.5); } * { @@ -76,13 +104,14 @@ } body { - @apply bg-background text-foreground; + @apply bg-background text-foreground transition-colors duration-300; } } -/* Custom Scrollbar */ +/* Custom Scrollbar - Minimal & Modern */ ::-webkit-scrollbar { - width: 8px; + width: 6px; + height: 6px; } ::-webkit-scrollbar-track { @@ -90,10 +119,32 @@ } ::-webkit-scrollbar-thumb { - background: #374151; - border-radius: 4px; + background: var(--muted); + border-radius: 10px; } ::-webkit-scrollbar-thumb:hover { - background: #4B5563; + background: var(--muted-foreground); +} + +/* Utility Classes for Modern Look */ +.glass-panel { + @apply bg-popover backdrop-blur-xl border border-white/10 dark:border-white/5; +} + +.shadow-premium { + box-shadow: var(--shadow-md); +} + +.shadow-premium-lg { + box-shadow: var(--shadow-lg); +} + +.no-scrollbar::-webkit-scrollbar { + display: none; +} + +.no-scrollbar { + -ms-overflow-style: none; + scrollbar-width: none; } \ No newline at end of file diff --git a/app/layout.tsx b/app/layout.tsx index 4a4421d..135f51f 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -2,6 +2,7 @@ import type { Metadata } from "next"; import { Inter } from "next/font/google"; import "./globals.css"; import ErrorBoundary from "@/components/ErrorBoundary"; +import { ThemeProvider } from "@/components/theme-provider"; const inter = Inter({ subsets: ["latin"] }); @@ -16,11 +17,13 @@ export default function RootLayout({ children: React.ReactNode; }>) { return ( - + - - {children} - + + + {children} + + ); diff --git a/app/page.tsx b/app/page.tsx index 55327a5..4a8248a 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,7 +1,6 @@ "use client"; import { useEffect } from 'react'; - import { useStore } from '@/lib/store'; import { Navbar } from "@/components/Navbar"; import { Gallery } from "@/components/Gallery"; @@ -9,9 +8,8 @@ import { PromptHero } from "@/components/PromptHero"; import { Settings } from "@/components/Settings"; import { PromptLibrary } from "@/components/PromptLibrary"; import { UploadHistory } from "@/components/UploadHistory"; - import { CookieExpiredDialog } from "@/components/CookieExpiredDialog"; - +import { BottomNav } from "@/components/BottomNav"; export default function Home() { const { currentView, setCurrentView, loadGallery } = useStore(); @@ -20,17 +18,29 @@ export default function Home() { loadGallery(); }, [loadGallery]); + const handleTabChange = (tab: 'create' | 'library' | 'uploads' | 'settings') => { + if (tab === 'create') setCurrentView('gallery'); + else if (tab === 'uploads') setCurrentView('history'); + else setCurrentView(tab); + }; + + const getActiveTab = () => { + if (currentView === 'gallery') return 'create'; + if (currentView === 'history') return 'uploads'; + return currentView as 'create' | 'library' | 'uploads' | 'settings'; + }; + return ( -
- {/* Top Navbar */} +
+ {/* Top Navbar - Mobile Header */} {/* Main Content Area */} -
+
{/* Scrollable Container */} -
-
+
+
{/* Always show Hero on Create View */} {currentView === 'gallery' && ( @@ -52,6 +62,8 @@ export default function Home() {
+ {/* Bottom Navigation (Mobile & Desktop App-like) */} +
diff --git a/components/BottomNav.tsx b/components/BottomNav.tsx new file mode 100644 index 0000000..d3e6a5c --- /dev/null +++ b/components/BottomNav.tsx @@ -0,0 +1,100 @@ +import React from 'react'; +import { Sparkles, LayoutGrid, Clock, Settings, Zap } from 'lucide-react'; +import { cn } from '@/lib/utils'; +import { motion } from 'framer-motion'; + +interface BottomNavProps { + currentTab?: 'create' | 'library' | 'uploads' | 'settings'; + onTabChange?: (tab: 'create' | 'library' | 'uploads' | 'settings') => void; +} + +export function BottomNav({ currentTab = 'create', onTabChange }: BottomNavProps) { + return ( + + ); +} diff --git a/components/CookieExpiredDialog.tsx b/components/CookieExpiredDialog.tsx index 2cb661d..c09595a 100644 --- a/components/CookieExpiredDialog.tsx +++ b/components/CookieExpiredDialog.tsx @@ -26,37 +26,43 @@ export function CookieExpiredDialog() { }; return ( -
-
+
+
- {/* Decorative header background */} -
+ {/* Top Glow/Gradient */} +
-
+
+ {/* Close Button */} -
- + {/* Cookie Icon Container */} +
+ {/* Glow effect */} +
+
+ +
-

Cookies Expired

+

Cookies Expired

-

- Your {providerName} session has timed out. +

+ Your {providerName} session has timed out. To continue generating images, please refresh your cookies.

@@ -64,9 +70,9 @@ export function CookieExpiredDialog() { href={providerUrl} target="_blank" rel="noopener noreferrer" - className="w-full py-3 px-4 bg-white/5 hover:bg-white/10 text-white font-medium rounded-xl transition-all flex items-center justify-center gap-2 border border-white/5" + className="w-full py-4 px-6 bg-[#27272A] hover:bg-[#3F3F46] text-white font-bold rounded-2xl transition-all flex items-center justify-center gap-3 border border-white/5 active:scale-[0.98]" > - + Open {providerName}
diff --git a/components/Gallery.tsx b/components/Gallery.tsx index d5098ae..fd96850 100644 --- a/components/Gallery.tsx +++ b/components/Gallery.tsx @@ -37,6 +37,15 @@ export function Gallery() { const [videoPromptValue, setVideoPromptValue] = React.useState(''); const [useSourceImage, setUseSourceImage] = React.useState(true); const [selectedIndex, setSelectedIndex] = React.useState(null); + const [showControls, setShowControls] = React.useState(true); + const [isMobile, setIsMobile] = React.useState(false); + + React.useEffect(() => { + const checkMobile = () => setIsMobile(window.innerWidth < 768); + checkMobile(); + window.addEventListener('resize', checkMobile); + return () => window.removeEventListener('resize', checkMobile); + }, []); React.useEffect(() => { if (selectedIndex !== null && gallery[selectedIndex]) { @@ -389,25 +398,28 @@ export function Gallery() { return (
{/* Header with Clear All */} -
+
-

{gallery.length} Generated Images

+

{gallery.length} Creations

+

Your library of generated images

- + {gallery.length > 0 && ( + + )}
{/* Videos Section - Show generated videos */} {videos.length > 0 && (
-
+
-

{videos.length} Generated Video{videos.length > 1 ? 's' : ''}

+

{videos.length} Generated Video{videos.length > 1 ? 's' : ''}

{videos.map((vid) => ( @@ -416,7 +428,7 @@ export function Gallery() { layout initial={{ opacity: 0, scale: 0.9 }} animate={{ opacity: 1, scale: 1 }} - className="group relative aspect-video rounded-xl overflow-hidden bg-black border border-white/10 shadow-lg" + className="group relative aspect-video rounded-2xl overflow-hidden bg-black border border-gray-200 dark:border-gray-800 shadow-sm" >