mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
feat: replace typing cursor with static subtitle, add gradient mesh hero background
This commit is contained in:
@@ -1,10 +1,18 @@
|
||||
import { Github } from "lucide-react";
|
||||
import { FadeIn } from "./fade-in";
|
||||
import { TypingCursor } from "./typing-cursor";
|
||||
|
||||
export function Hero() {
|
||||
return (
|
||||
<section className="px-6 pt-32 pb-20 md:pt-44 md:pb-32">
|
||||
<section className="relative overflow-hidden px-6 pt-32 pb-24 md:pt-44 md:pb-36">
|
||||
{/* Stripe-style gradient mesh background */}
|
||||
<div className="pointer-events-none absolute inset-0 -z-10">
|
||||
<div className="absolute -top-[40%] -left-[20%] h-[80%] w-[60%] rounded-full bg-amber-400/20 blur-[120px]" />
|
||||
<div className="absolute -top-[20%] -right-[10%] h-[70%] w-[50%] rounded-full bg-orange-300/15 blur-[100px]" />
|
||||
<div className="absolute top-[20%] left-[30%] h-[60%] w-[40%] rounded-full bg-yellow-200/10 blur-[140px]" />
|
||||
<div className="absolute -bottom-[30%] -right-[20%] h-[60%] w-[50%] rounded-full bg-orange-500/10 blur-[120px]" />
|
||||
<div className="absolute bottom-0 left-0 h-px w-full bg-gradient-to-r from-transparent via-border to-transparent" />
|
||||
</div>
|
||||
|
||||
<div className="mx-auto max-w-4xl text-center">
|
||||
<FadeIn>
|
||||
<h1 className="text-4xl font-extrabold tracking-tight sm:text-5xl md:text-6xl lg:text-7xl">
|
||||
@@ -13,8 +21,9 @@ export function Hero() {
|
||||
</FadeIn>
|
||||
|
||||
<FadeIn delay={0.1}>
|
||||
<p className="mt-6 text-xl md:text-2xl">
|
||||
<TypingCursor />
|
||||
<p className="mx-auto mt-6 max-w-2xl text-lg text-muted md:text-xl">
|
||||
50+ image processing tools with local AI. Self-hosted, air-gapped, and fully offline. No
|
||||
data ever leaves your network.
|
||||
</p>
|
||||
</FadeIn>
|
||||
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
|
||||
const phrases = [
|
||||
"100% local processing.",
|
||||
"Zero data leaves your network.",
|
||||
"50+ image tools.",
|
||||
"14 AI models. Your hardware.",
|
||||
"Air-gapped ready.",
|
||||
"Enterprise-grade. Free forever.",
|
||||
"One Docker container.",
|
||||
"Open source. Always.",
|
||||
];
|
||||
|
||||
export function TypingCursor() {
|
||||
const [index, setIndex] = useState(0);
|
||||
|
||||
const advance = useCallback(() => {
|
||||
setIndex((i) => (i + 1) % phrases.length);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setInterval(advance, 3000);
|
||||
return () => clearInterval(timer);
|
||||
}, [advance]);
|
||||
|
||||
return (
|
||||
<span className="inline-flex items-center">
|
||||
<AnimatePresence mode="wait">
|
||||
<motion.span
|
||||
key={phrases[index]}
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: -10 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
className="text-accent"
|
||||
>
|
||||
{phrases[index]}
|
||||
</motion.span>
|
||||
</AnimatePresence>
|
||||
<motion.span
|
||||
animate={{ opacity: [1, 0] }}
|
||||
transition={{ duration: 0.8, repeat: Infinity, repeatType: "reverse" }}
|
||||
className="ml-0.5 inline-block w-[3px] h-[1em] bg-accent align-middle"
|
||||
/>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user