2026-04-23 18:05:43 +08:00
|
|
|
"use client";
|
|
|
|
|
|
|
|
|
|
import { AnimatePresence, motion } from "framer-motion";
|
|
|
|
|
import { useCallback, useEffect, useState } from "react";
|
|
|
|
|
|
|
|
|
|
const phrases = [
|
2026-04-23 18:25:36 +08:00
|
|
|
"No signups. No accounts.",
|
2026-04-23 18:28:05 +08:00
|
|
|
"No uploads to the cloud. Ever.",
|
2026-04-23 18:05:43 +08:00
|
|
|
"100% local processing.",
|
2026-04-23 23:52:02 +08:00
|
|
|
"Free. Forever.",
|
2026-04-23 18:28:05 +08:00
|
|
|
"No limits. No hidden caps.",
|
2026-04-23 18:25:36 +08:00
|
|
|
"Works fully offline.",
|
2026-04-23 18:28:05 +08:00
|
|
|
"Unlimited batch processing.",
|
2026-05-10 22:18:11 +08:00
|
|
|
"51 image tools.",
|
2026-05-05 23:24:22 +08:00
|
|
|
"15 AI models. Your hardware.",
|
2026-04-23 18:28:05 +08:00
|
|
|
"Lightning fast. Built on Sharp.",
|
2026-04-23 18:05:43 +08:00
|
|
|
"Air-gapped ready.",
|
|
|
|
|
"One Docker container.",
|
|
|
|
|
"Open source. Always.",
|
2026-05-10 22:18:11 +08:00
|
|
|
"Every pixel stays on your hardware.",
|
|
|
|
|
"Your server. Your rules.",
|
|
|
|
|
"Deploys in a single docker pull.",
|
|
|
|
|
"The self-hosted alternative.",
|
|
|
|
|
"Resize, compress, convert. Locally.",
|
|
|
|
|
"Free image editor. No watermarks.",
|
|
|
|
|
"Bulk image processing. Unlimited.",
|
|
|
|
|
"Remove backgrounds offline.",
|
|
|
|
|
"Self-hosted Canva alternative.",
|
|
|
|
|
"No API keys needed.",
|
|
|
|
|
"GDPR compliant by design.",
|
|
|
|
|
"Works without internet.",
|
2026-04-23 18:05:43 +08:00
|
|
|
];
|
|
|
|
|
|
|
|
|
|
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 (
|
|
|
|
|
<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>
|
|
|
|
|
);
|
|
|
|
|
}
|