diff --git a/app/components/theme-debug.tsx b/app/components/theme-debug.tsx new file mode 100644 index 0000000..139597f --- /dev/null +++ b/app/components/theme-debug.tsx @@ -0,0 +1,2 @@ + + diff --git a/app/components/theme-logo.tsx b/app/components/theme-logo.tsx new file mode 100644 index 0000000..6c5898d --- /dev/null +++ b/app/components/theme-logo.tsx @@ -0,0 +1,36 @@ +"use client" + +import { useTheme } from "next-themes" +import { useEffect, useState } from "react" + +export function ThemeLogo() { + const { theme } = useTheme() + const [mounted, setMounted] = useState(false) + + useEffect(() => { + setMounted(true) + }, []) + + if (!mounted) { + // Return light theme logo by default to avoid hydration mismatch + return ( + Firecrawl + ) + } + + const logoSrc = theme === "dark" + ? "/firecrawl-logo-with-fire-dark.webp" + : "/firecrawl-logo-with-fire.webp" + + return ( + Firecrawl + ) +} diff --git a/app/components/theme-provider.tsx b/app/components/theme-provider.tsx new file mode 100644 index 0000000..8c90fbc --- /dev/null +++ b/app/components/theme-provider.tsx @@ -0,0 +1,9 @@ +"use client" + +import * as React from "react" +import { ThemeProvider as NextThemesProvider } from "next-themes" +import { type ThemeProviderProps } from "next-themes/dist/types" + +export function ThemeProvider({ children, ...props }: ThemeProviderProps) { + return {children} +} diff --git a/app/components/theme-toggle.tsx b/app/components/theme-toggle.tsx new file mode 100644 index 0000000..e688dd7 --- /dev/null +++ b/app/components/theme-toggle.tsx @@ -0,0 +1,84 @@ +"use client" + +import * as React from "react" +import { useTheme } from "next-themes" +import { Button } from "@/components/ui/button" + +export function ThemeToggle() { + const { setTheme, theme, resolvedTheme } = useTheme() + const [mounted, setMounted] = React.useState(false) + + React.useEffect(() => { + setMounted(true) + }, []) + + if (!mounted) { + return null + } + + const toggleTheme = () => { + setTheme(resolvedTheme === "light" ? "dark" : "light") + } + + return ( + + ) +} + +function SunIcon(props: React.SVGProps) { + return ( + + + + + + + + + + + + ) +} + +function MoonIcon(props: React.SVGProps) { + return ( + + + + ) +} diff --git a/app/globals.css b/app/globals.css index fe8b620..e12cb7d 100644 --- a/app/globals.css +++ b/app/globals.css @@ -138,6 +138,352 @@ --radius: 0.5rem; } +/* Dark theme overrides */ +.dark { + --color-background: hsl(240 10% 3.9%); + --color-foreground: hsl(0 0% 98%); + --color-card: hsl(240 10% 3.9%); + --color-card-foreground: hsl(0 0% 98%); + --color-popover: hsl(240 10% 3.9%); + --color-popover-foreground: hsl(0 0% 98%); + --color-primary: hsl(0 0% 98%); + --color-primary-foreground: hsl(240 5.9% 10%); + --color-secondary: hsl(240 3.7% 15.9%); + --color-secondary-foreground: hsl(0 0% 98%); + --color-muted: hsl(240 3.7% 15.9%); + --color-muted-foreground: hsl(240 5% 64.9%); + --color-accent: hsl(240 3.7% 15.9%); + --color-accent-foreground: hsl(0 0% 98%); + --color-destructive: hsl(0 62.8% 30.6%); + --color-destructive-foreground: hsl(0 0% 98%); + --color-border: hsl(240 3.7% 15.9%); + --color-input: hsl(240 3.7% 15.9%); + --color-ring: hsl(240 4.9% 83.9%); +} + +/* Dark mode styles for common elements */ +.dark .bg-white { + background-color: hsl(240 10% 3.9%); +} + +/* !important */ +.dark .text-gray-900, +.dark .text-zinc-900 { + color: hsl(0 0% 98%); +} + +/* Dark mode for specific color values */ +.dark [class*="text-[#36322F]"] { + color: hsl(0 0% 98%); +} + +.dark .text-gray-600, +.dark .text-zinc-500, +.dark .text-zinc-600 { + color: hsl(240 5% 64.9%); +} + +.dark .bg-gray-50, +.dark .bg-gray-100 { + background-color: hsl(240 3.7% 15.9%); +} + +.dark .border-gray-200, +.dark .border-zinc-300 { + border-color: hsl(240 3.7% 15.9%); +} + +.dark .bg-gray-900 { + background-color: hsl(240 10% 3.9%); +} + +/* Dark mode for specific Open Lovable elements */ +.dark .bg-orange-400\/50, +.dark .bg-orange-300\/30, +.dark .bg-orange-200\/20 { + background-color: hsl(240 10% 3.9%); +} + +.dark .bg-yellow-300\/40 { + background-color: hsl(240 10% 3.9%); +} + +/* Dark mode for design style buttons */ +.dark .p-3.rounded-lg.border.border-gray-200.bg-white { + background-color: hsl(240 3.7% 15.9%); + border-color: hsl(240 3.7% 15.9%); + color: hsl(0 0% 98%); +} + +.dark .p-3.rounded-lg.border.border-gray-200.bg-white:hover { + background-color: hsl(240 3.7% 20%); + border-color: hsl(25 95% 53%); +} + +.dark .p-3.rounded-lg.border.border-gray-200.bg-white .text-sm.font-medium { + color: hsl(0 0% 98%); +} + +.dark .p-3.rounded-lg.border.border-gray-200.bg-white .text-xs.text-gray-500 { + color: hsl(240 5% 64.9%); +} + +/* Dark mode for selected/active design style buttons */ +.dark .p-3.rounded-lg.border.border-orange-200.bg-orange-50\/50 { + background-color: hsl(25 95% 53%); + border-color: hsl(25 95% 53%); + color: hsl(240 10% 3.9%); +} + +.dark .p-3.rounded-lg.border.border-orange-200.bg-orange-50\/50 .text-sm.font-medium { + color: hsl(240 10% 3.9%); +} + +.dark .p-3.rounded-lg.border.border-orange-200.bg-orange-50\/50 .text-xs.text-gray-500 { + color: hsl(240 10% 3.9%); + opacity: 0.8; +} + +/* Dark mode for selected buttons with orange-400 border and orange-50 background */ +.dark .p-3.rounded-lg.border.border-orange-400.bg-orange-50 { + background-color: hsl(240 3.7% 15.9%); + border-color: hsl(25 95% 53%); + border-width: 2px; + color: hsl(0 0% 98%); + box-shadow: 0 0 0 1px hsl(25 95% 53%), 0 4px 12px -1px rgba(0, 0, 0, 0.15); +} + +.dark .p-3.rounded-lg.border.border-orange-400.bg-orange-50 .text-sm.font-medium { + color: hsl(0 0% 98%); + font-weight: 600; +} + +.dark .p-3.rounded-lg.border.border-orange-400.bg-orange-50 .text-xs.text-gray-500 { + color: hsl(240 5% 64.9%); + font-weight: 500; +} + +/* Light mode improvements for selected buttons */ +.p-3.rounded-lg.border.border-orange-400.bg-orange-50 { + background-color: hsl(0 0% 98%); + border-color: hsl(25 95% 53%); + border-width: 2px; + color: hsl(240 10% 3.9%); + box-shadow: 0 0 0 1px hsl(25 95% 53%), 0 4px 12px -1px rgba(0, 0, 0, 0.1); +} + +.p-3.rounded-lg.border.border-orange-400.bg-orange-50 .text-sm.font-medium { + color: hsl(240 10% 3.9%); + font-weight: 600; +} + +.p-3.rounded-lg.border.border-orange-400.bg-orange-50 .text-xs.text-gray-500 { + color: hsl(240 5% 40%); + font-weight: 500; +} + +/* Dark mode for hover states on design style buttons */ +.dark .p-3.rounded-lg.border.border-gray-200.bg-white:hover { + background-color: hsl(25 95% 53%); + border-color: hsl(25 95% 53%); + color: hsl(240 10% 3.9%); +} + +.dark .p-3.rounded-lg.border.border-gray-200.bg-white:hover .text-sm.font-medium { + color: hsl(240 10% 3.9%); +} + +.dark .p-3.rounded-lg.border.border-gray-200.bg-white:hover .text-xs.text-gray-500 { + color: hsl(240 10% 3.9%); + opacity: 0.8; +} + +/* Dark mode for dropdowns and selectors */ +.dark .bg-white { + background-color: hsl(240 10% 3.9%); +} + +/* Dark mode for design container */ +.dark .bg-white\/80.backdrop-blur-sm.border.border-gray-200.rounded-xl.p-4.shadow-sm { + background-color: hsl(240 10% 3.9% / 0.9); + border-color: hsl(240 3.7% 15.9%); + backdrop-filter: blur(12px); + box-shadow: 0 8px 32px -4px rgba(0, 0, 0, 0.3), 0 2px 8px -2px rgba(0, 0, 0, 0.1); +} + +/* Light mode improvements for design container */ +.bg-white\/80.backdrop-blur-sm.border.border-gray-200.rounded-xl.p-4.shadow-sm { + background-color: hsl(0 0% 100% / 0.9); + border-color: hsl(240 5.9% 90%); + backdrop-filter: blur(12px); + box-shadow: 0 8px 32px -4px rgba(0, 0, 0, 0.1), 0 2px 8px -2px rgba(0, 0, 0, 0.05); +} + +/* Dark mode for loading spinner and text */ +.dark .w-16.h-16.border-4.border-orange-200.border-t-orange-500.rounded-full.animate-spin.mx-auto { + border-color: hsl(25 95% 53%); + border-top-color: hsl(25 95% 70%); +} + +.dark .text-xl.font-semibold.text-gray-800 { + color: hsl(0 0% 98%); +} + +.dark .text-gray-600.text-sm { + color: hsl(240 5% 64.9%); +} + +/* Dark mode for file explorer */ +.dark .flex-1.overflow-y-auto.p-2.scrollbar-hide { + background-color: hsl(240 10% 3.9%); +} + +.dark .text-sm { + color: hsl(0 0% 98%); +} + +.dark .flex.items-center.gap-1.py-1.px-2.hover\:bg-gray-100.rounded.cursor-pointer.text-gray-700 { + color: hsl(0 0% 98%); +} + +.dark .flex.items-center.gap-1.py-1.px-2.hover\:bg-gray-100.rounded.cursor-pointer.text-gray-700:hover { + background-color: hsl(240 3.7% 15.9%); +} + +/* Dark mode for file items */ +.dark .text-xs.flex.items-center.gap-1 { + color: hsl(0 0% 98%); +} + +.dark .text-xs.flex.items-center.gap-1:hover { + color: hsl(25 95% 70%); + background-color: hsl(240 3.7% 15.9%); + border-radius: 0.375rem; + padding: 0.25rem 0.5rem; + transition: all 0.2s ease; +} + +/* Light mode improvements for file items */ +.text-xs.flex.items-center.gap-1 { + color: hsl(240 10% 3.9%); + transition: all 0.2s ease; +} + +/* Theme toggle button improvements */ +.dark .rounded-full.fixed.bottom-4.right-4.z-\[9999\].bg-white.dark\:bg-gray-800.border-gray-300.dark\:border-gray-600.text-gray-700.dark\:text-gray-200.hover\:bg-gray-50.dark\:hover\:bg-gray-700.shadow-lg.transition-all.duration-200.hover\:scale-110 { + background-color: hsl(240 10% 3.9%); + border-color: hsl(240 3.7% 15.9%); + color: hsl(0 0% 98%); + box-shadow: 0 10px 25px -3px rgba(0, 0, 0, 0.3), 0 4px 6px -2px rgba(0, 0, 0, 0.1); +} + +.dark .rounded-full.fixed.bottom-4.right-4.z-\[9999\].bg-white.dark\:bg-gray-800.border-gray-300.dark\:border-gray-600.text-gray-700.dark\:text-gray-200.hover\:bg-gray-50.dark\:hover\:bg-gray-700.shadow-lg.transition-all.duration-200.hover\:scale-110:hover { + background-color: hsl(240 3.7% 15.9%); + border-color: hsl(25 95% 53%); + transform: scale(1.1); +} + +/* Light mode theme toggle improvements */ +.rounded-full.fixed.bottom-4.right-4.z-\[9999\].bg-white.dark\:bg-gray-800.border-gray-300.dark\:border-gray-600.text-gray-700.dark\:text-gray-200.hover\:bg-gray-50.dark\:hover\:bg-gray-700.shadow-lg.transition-all.duration-200.hover\:scale-110 { + box-shadow: 0 10px 25px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); +} + +.rounded-full.fixed.bottom-4.right-4.z-\[9999\].bg-white.dark\:bg-gray-800.border-gray-300.dark\:border-gray-600.text-gray-700.dark\:text-gray-200.hover\:bg-gray-50.dark\:hover\:bg-gray-700.shadow-lg.transition-all.duration-200.hover\:scale-110:hover { + border-color: hsl(25 95% 53%); + transform: scale(1.1); +} + +.text-xs.flex.items-center.gap-1:hover { + color: hsl(25 95% 53%); + background-color: hsl(25 95% 95%); + border-radius: 0.375rem; + padding: 0.25rem 0.5rem; +} + +/* Light mode improvements for loading elements */ +.w-16.h-16.border-4.border-orange-200.border-t-orange-500.rounded-full.animate-spin.mx-auto { + border-color: hsl(25 95% 53%); + border-top-color: hsl(25 95% 70%); + box-shadow: 0 0 20px rgba(251, 146, 60, 0.3); +} + +.text-xl.font-semibold.text-gray-800 { + color: hsl(240 10% 3.9%); +} + +.text-gray-600.text-sm { + color: hsl(240 5% 40%); +} + +.dark .text-black { + color: hsl(0 0% 98%); +} + +.dark .hover\:bg-gray-50:hover { + background-color: hsl(240 3.7% 15.9%); +} + +.dark .hover\:bg-gray-100:hover { + background-color: hsl(240 3.7% 15.9%); +} + +/* Dark mode for select elements */ +.dark select { + background-color: hsl(240 10% 3.9%); + color: hsl(0 0% 98%); + border-color: hsl(240 3.7% 15.9%); +} + +.dark select option { + background-color: hsl(240 10% 3.9%); + color: hsl(0 0% 98%); +} + +.dark select option:hover { + background-color: hsl(240 3.7% 15.9%); +} + +/* Dark mode for specific Open Lovable elements */ +.dark .bg-card { + background-color: hsl(240 10% 3.9%); +} + +.dark .border-border { + border-color: hsl(240 3.7% 15.9%); +} + +.dark .text-black { + color: hsl(0 0% 98%); +} + +.dark .hover\:text-gray-700:hover { + color: hsl(0 0% 98%); +} + +/* Dark mode for page background */ +.dark .bg-background { + background-color: hsl(240 10% 3.9%); +} + +.dark .text-foreground { + color: hsl(0 0% 98%); +} + +/* Dark mode for specific backgrounds */ +.dark .bg-gray-50 { + background-color: hsl(240 10% 3.9%); +} + +.dark .bg-gray-100 { + background-color: hsl(240 10% 3.9%); +} + +.dark .bg-gray-900 { + background-color: hsl(240 10% 3.9%); +} + + + @layer utilities { /* Hide scrollbar for Chrome, Safari and Opera */ .scrollbar-hide::-webkit-scrollbar { @@ -169,6 +515,17 @@ background-color: theme('colors.background'); color: theme('colors.foreground'); } + + /* Dark mode overrides for common elements */ + .dark { + background-color: theme('colors.background'); + color: theme('colors.foreground'); + } + + /* Ensure dark mode applies to all elements */ + .dark * { + border-color: theme('colors.border'); + } } @layer utilities { diff --git a/app/layout.tsx b/app/layout.tsx index 8c11a46..7cd0cd3 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,6 +1,7 @@ import type { Metadata } from "next"; import { Inter } from "next/font/google"; import "./globals.css"; +import { ThemeProvider } from "@/app/components/theme-provider"; const inter = Inter({ subsets: ["latin"] }); @@ -15,9 +16,16 @@ export default function RootLayout({ children: React.ReactNode; }>) { return ( - + - {children} + + {children} + ); diff --git a/app/page.tsx b/app/page.tsx index dfe0d89..2eedb37 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -22,6 +22,8 @@ import { } from '@/lib/icons'; import { motion, AnimatePresence } from 'framer-motion'; import CodeApplicationProgress, { type CodeApplicationState } from '@/components/CodeApplicationProgress'; +import { ThemeToggle } from '@/app/components/theme-toggle'; +import { ThemeLogo } from '@/app/components/theme-logo'; interface SandboxData { sandboxId: string; @@ -2739,6 +2741,9 @@ Focus on the key sections and content, making it clean and modern.`; return (
+ {/* Theme Toggle */} + + {/* Home Screen Overlay */} {showHomeScreen && (
@@ -2786,11 +2791,7 @@ Focus on the key sections and content, making it clean and modern.`; {/* Header */}
- Firecrawl +
- Firecrawl +
{/* Model Selector - Left side */} diff --git a/components/ui/button.tsx b/components/ui/button.tsx index 56dba58..967fdf9 100644 --- a/components/ui/button.tsx +++ b/components/ui/button.tsx @@ -19,6 +19,7 @@ const buttonVariants = cva( size: { default: "h-10 px-4 py-2", sm: "h-8 px-3 py-1 text-sm", + icon: "h-8 w-8 p-0", lg: "h-12 px-6 py-3", }, }, diff --git a/package.json b/package.json index 818cf29..e22e280 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "groq-sdk": "^0.29.0", "lucide-react": "^0.532.0", "next": "15.4.3", + "next-themes": "^0.4.6", "react": "19.1.0", "react-dom": "19.1.0", "react-icons": "^5.5.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fe23553..598531e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -71,6 +71,9 @@ importers: next: specifier: 15.4.3 version: 15.4.3(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + next-themes: + specifier: ^0.4.6 + version: 0.4.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0) react: specifier: 19.1.0 version: 19.1.0 @@ -1941,6 +1944,12 @@ packages: resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} engines: {node: '>= 0.6'} + next-themes@0.4.6: + resolution: {integrity: sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==} + peerDependencies: + react: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc + react-dom: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc + next@15.4.3: resolution: {integrity: sha512-uW7Qe6poVasNIE1X382nI29oxSdFJzjQzTgJFLD43MxyPfGKKxCMySllhBpvqr48f58Om+tLMivzRwBpXEytvA==} engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0} @@ -4421,6 +4430,11 @@ snapshots: negotiator@1.0.0: {} + next-themes@0.4.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + next@15.4.3(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: '@next/env': 15.4.3 diff --git a/public/firecrawl-logo-with-fire-dark.webp b/public/firecrawl-logo-with-fire-dark.webp new file mode 100644 index 0000000..8111c79 Binary files /dev/null and b/public/firecrawl-logo-with-fire-dark.webp differ