diff --git a/src/app/(dashboard)/tools/audio-compress/page.tsx b/src/app/(dashboard)/tools/audio-compress/page.tsx index 1f0d5c4..3b104af 100644 --- a/src/app/(dashboard)/tools/audio-compress/page.tsx +++ b/src/app/(dashboard)/tools/audio-compress/page.tsx @@ -83,9 +83,9 @@ export default function AudioCompressPage() { useEffect(() => setMounted(true), []); const { t } = useTranslation(); - const getT = (key: string) => { - if (!mounted) return getServerTranslations("en").t(key); - return t(key); + const getT = (key: string, params?: Record) => { + if (!mounted) return getServerTranslations("en").t(key, params); + return t(key, params); }; const { files, addFile, removeFile, clearFiles, processingStatus, setProcessingStatus } = diff --git a/src/app/(dashboard)/tools/image-compress/page.tsx b/src/app/(dashboard)/tools/image-compress/page.tsx index 206c6a7..ed1ac71 100644 --- a/src/app/(dashboard)/tools/image-compress/page.tsx +++ b/src/app/(dashboard)/tools/image-compress/page.tsx @@ -57,9 +57,9 @@ export default function ImageCompressPage() { useEffect(() => setMounted(true), []); const { t } = useTranslation(); - const getT = (key: string) => { - if (!mounted) return getServerTranslations("en").t(key); - return t(key); + const getT = (key: string, params?: Record) => { + if (!mounted) return getServerTranslations("en").t(key, params); + return t(key, params); }; const { files, addFile, removeFile, clearFiles, processingStatus, setProcessingStatus } = diff --git a/src/app/(dashboard)/tools/video-frames/page.tsx b/src/app/(dashboard)/tools/video-frames/page.tsx index cc58728..d03559a 100644 --- a/src/app/(dashboard)/tools/video-frames/page.tsx +++ b/src/app/(dashboard)/tools/video-frames/page.tsx @@ -70,9 +70,9 @@ export default function VideoFramesPage() { useEffect(() => setMounted(true), []); const { t } = useTranslation(); - const getT = (key: string) => { - if (!mounted) return getServerTranslations("en").t(key); - return t(key); + const getT = (key: string, params?: Record) => { + if (!mounted) return getServerTranslations("en").t(key, params); + return t(key, params); }; const { files, addFile, removeFile, clearFiles, processingStatus, setProcessingStatus } = diff --git a/src/app/globals.css b/src/app/globals.css index 5fd7904..3fd22d9 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -106,8 +106,21 @@ ); background-size: 1000px 100%; } + + /* Subtle film grain (Apple-ish texture) */ + .noise-overlay { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23n)' opacity='.35'/%3E%3C/svg%3E"); + background-size: 180px 180px; + } + + /* Gentle top fade used by hero backgrounds */ + .mask-fade-y { + -webkit-mask-image: linear-gradient(to bottom, transparent 0%, black 18%, black 82%, transparent 100%); + mask-image: linear-gradient(to bottom, transparent 0%, black 18%, black 82%, transparent 100%); + } } + @layer utilities { .text-balance { text-wrap: balance; diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 776eaec..b9ef42d 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,15 +1,13 @@ import type { Metadata } from "next"; -import { Inter } from "next/font/google"; import "./globals.css"; import { Header } from "@/components/layout/Header"; import { Footer } from "@/components/layout/Footer"; import { cn } from "@/lib/utils"; -const inter = Inter({ subsets: ["latin"], variable: "--font-inter" }); - export const metadata: Metadata = { title: "Mini Game AI - AI-Powered Tools for Game Developers", - description: "Transform your game development workflow with AI-powered tools. Video to frames, image compression, audio processing, and more.", + description: + "Transform your game development workflow with AI-powered tools. Video to frames, image compression, audio processing, and more.", keywords: ["game development", "AI tools", "video processing", "image compression", "audio processing"], }; @@ -20,7 +18,7 @@ export default function RootLayout({ }>) { return ( - +
{children}
diff --git a/src/app/page.tsx b/src/app/page.tsx index 227686e..a31aa7f 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,255 +1,289 @@ "use client"; import Link from "next/link"; -import { motion } from "framer-motion"; +import { motion, useReducedMotion } from "framer-motion"; import { ArrowRight, - Video, - Image, + ChevronDown, + Image as ImageIcon, Music, + ShieldCheck, Sparkles, + Video, Zap, - Shield, - Users, - Check, } from "lucide-react"; +import { useEffect, useMemo, useState } from "react"; import { Button } from "@/components/ui/button"; -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; -import { Badge } from "@/components/ui/badge"; +import { Card } from "@/components/ui/card"; +import { cn } from "@/lib/utils"; import { useTranslation, getServerTranslations } from "@/lib/i18n"; -import { useState, useEffect } from "react"; -function useFeatures() { +type TFn = (key: string, params?: Record) => string; + +function useStableT(): TFn { const [mounted, setMounted] = useState(false); - useEffect(() => setMounted(true), []); const { t } = useTranslation(); - - const getT = (key: string) => { - if (!mounted) return getServerTranslations("en").t(key); - return t(key); - }; - return [ - { - icon: Video, - title: getT("home.tools.videoToFrames.title"), - description: getT("home.tools.videoToFrames.description"), - href: "/tools/video-frames", - }, - { - icon: Image, - title: getT("home.tools.imageCompression.title"), - description: getT("home.tools.imageCompression.description"), - href: "/tools/image-compress", - }, - { - icon: Music, - title: getT("home.tools.audioCompression.title"), - description: getT("home.tools.audioCompression.description"), - href: "/tools/audio-compress", - }, - { - icon: Sparkles, - title: getT("home.tools.aiTools.title"), - description: getT("home.tools.aiTools.description"), - href: "/tools/ai-tools", - }, - ]; + useEffect(() => setMounted(true), []); + + return useMemo(() => { + const serverT = getServerTranslations("en").t; + return (key: string, params?: Record) => { + if (!mounted) return serverT(key, params); + return t(key, params); + }; + }, [mounted, t]); } -function useBenefits() { - const [mounted, setMounted] = useState(false); - useEffect(() => setMounted(true), []); - const { t } = useTranslation(); - - const getT = (key: string) => { - if (!mounted) return getServerTranslations("en").t(key); - return t(key); - }; - - return [ - { - icon: Zap, - title: getT("home.benefits.lightningFast.title"), - description: getT("home.benefits.lightningFast.description"), - }, - { - icon: Shield, - title: getT("home.benefits.secure.title"), - description: getT("home.benefits.secure.description"), - }, - { - icon: Users, - title: getT("home.benefits.forDevelopers.title"), - description: getT("home.benefits.forDevelopers.description"), - }, - ]; +function SectionHeader({ kicker, title, description }: { kicker?: string; title: string; description?: string }) { + return ( +
+ {kicker ? ( +
+ + {kicker} +
+ ) : null} +

+ {title} +

+ {description ? ( +

+ {description} +

+ ) : null} +
+ ); } -function usePricingPlans() { - const [mounted, setMounted] = useState(false); - useEffect(() => setMounted(true), []); - const { t } = useTranslation(); +function BackgroundAuras({ reduceMotion }: { reduceMotion: boolean }) { + return ( +
+
- const getT = (key: string) => { - if (!mounted) return getServerTranslations("en").t(key); - return t(key); - }; +