fancytextstuff/pages/index.jsx

266 lines
9.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// pages/index.jsx
import React, { useState, useEffect, useMemo, useCallback } from "react";
import Head from "next/head";
import { motion, AnimatePresence } from "framer-motion";
import {
fontTransforms,
getFontsByCategory,
getPopularFonts,
transformText,
} from "@/components/fontTransforms";
import MobileOptimizedHeader from "@/components/MobileOptimizedHeader";
import EnhancedTextInput from "@/components/EnhancedTextInput";
import ImprovedCategoryFilter from "@/components/ImprovedCategoryFilter";
import PerformanceOptimizedFontCard from "@/components/PerformanceOptimizedFontCard";
import InfoSection from "@/components/InfoSection";
import SocialButtons from "@/components/SocialButtons";
import FancyTextPreview from "@/components/FancyTextPreview";
import SEOHead from "@/components/SEOHead";
if (typeof window !== "undefined") {
sessionStorage.removeItem("fancytext_recent_fonts");
}
export default function HomePage() {
const [inputText, setInputText] = useState("Hello Instagram!");
const [previewFont, setPreviewFont] = useState(null);
const [selectedCategory, setSelectedCategory] = useState("all");
const [searchQuery, setSearchQuery] = useState("");
const [recentFonts, setRecentFonts] = useState([]);
const [isMobile, setIsMobile] = useState(false);
const [animationsEnabled, setAnimationsEnabled] = useState(() => {
if (typeof window !== "undefined") {
const hasReducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
const isLowEndDevice = (navigator.hardwareConcurrency ?? 8) < 4;
return !hasReducedMotion && !isLowEndDevice;
}
return true;
});
useEffect(() => {
const checkMobile = () => {
if (typeof window !== "undefined") {
setIsMobile(window.innerWidth < 768 || /iPhone|iPad|iPod|Android/i.test(navigator.userAgent));
}
};
checkMobile();
window.addEventListener("resize", checkMobile);
return () => window.removeEventListener("resize", checkMobile);
}, []);
useEffect(() => {
setRecentFonts([]);
}, []);
const [debouncedText, setDebouncedText] = useState(inputText);
useEffect(() => {
const delay = isMobile ? 200 : 100;
const handler = setTimeout(() => setDebouncedText(inputText), delay);
return () => clearTimeout(handler);
}, [inputText, isMobile]);
const filteredFonts = useMemo(() => {
const list = getFontsByCategory(selectedCategory);
if (!searchQuery) return list;
const q = searchQuery.toLowerCase();
return list.filter((font) => {
const desc = fontTransforms[font]?.description?.toLowerCase() ?? "";
return font.toLowerCase().includes(q) || desc.includes(q);
});
}, [selectedCategory, searchQuery]);
const popularFonts = useMemo(() => getPopularFonts(), []);
const fontCounts = useMemo(() => {
const total = Object.keys(fontTransforms).length;
const counts = { all: total };
Object.values(fontTransforms).forEach(({ category }) => {
counts[category] = (counts[category] || 0) + 1;
});
return counts;
}, []);
const trackFontCopy = useCallback((fontName, text) => {
window.gtag?.("event", "font_copied", {
font_name: fontName,
text_length: text.length,
category: fontTransforms[fontName]?.category,
});
setRecentFonts((prev) => {
const updated = [fontName, ...prev.filter((f) => f !== fontName)].slice(0, 5);
return updated;
});
}, []);
const trackFontLike = useCallback((fontName, liked) => {
window.gtag?.("event", "font_liked", {
font_name: fontName,
action: liked ? "like" : "unlike",
});
}, []);
const handleQuickShare = useCallback(async () => {
const shareData = {
title: "FancyText - Cool Fonts! 🔥",
text: "Check out this app for cool Instagram & TikTok fonts! 30+ fonts free ✨",
url: window.location.href,
};
if (navigator.share) {
try {
await navigator.share(shareData);
} catch {}
} else {
await navigator.clipboard.writeText(`${shareData.text}\n${shareData.url}`);
alert("Link copied to clipboard! 🗌");
}
window.gtag?.("event", "app_shared", { method: "button_click" });
}, []);
const handleTextChange = useCallback((text) => {
setInputText(text);
setPreviewFont(null);
}, []);
const handleCategoryChange = useCallback((cat) => setSelectedCategory(cat), []);
const handleSearch = useCallback((q) => setSearchQuery(q), []);
const handleRandomFont = useCallback(() => {
const fontList = Object.keys(fontTransforms);
let tries = 0;
let newFont;
do {
newFont = fontList[Math.floor(Math.random() * fontList.length)];
tries++;
} while (newFont === previewFont && tries < 50);
setPreviewFont(newFont);
}, [previewFont]);
const displayText = previewFont
? transformText(inputText || "Try me!", previewFont)
: inputText;
return (
<>
<Head>
<title>FancyText | Viral Fonts</title>
<meta name="description" content="Make your posts pop with 30+ copy-paste fonts. Free, no login, mobile-ready. Works on IG, TikTok, Threads & more." />
<link rel="canonical" href="https://fancytext.app" />
<meta property="og:title" content="30+ Fancy Fonts for TikTok & Instagram 🔥" />
<meta property="og:description" content="Create viral bios, comments & posts in seconds no login, always free." />
<meta property="og:image" content="https://fancytext.app/social-preview.png" />
<meta property="og:url" content="https://fancytext.app" />
<meta property="og:type" content="website" />
<link rel="icon" href="/images/favicon.ico" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="mobile-web-app-capable" content="yes" />
<script type="application/ld+json" dangerouslySetInnerHTML={{
__html: JSON.stringify({
"@context": "https://schema.org",
"@type": "WebApplication",
name: "FancyText",
url: "https://fancytext.app",
applicationCategory: "WebApp",
operatingSystem: "All",
offers: {
"@type": "Offer",
price: "0.00",
priceCurrency: "USD",
},
})
}} />
</Head>
<div className="fixed top-4 right-4 z-[100] flex gap-4 text-sm text-black bg-white/90 px-3 py-1 rounded-lg shadow-lg backdrop-blur-sm">
<a href="#about" className="hover:underline">About</a>
<a href="#" onClick={(e) => {
e.preventDefault();
document.getElementById("privacy")?.scrollIntoView({ behavior: "smooth" });
}} className="hover:underline">Privacy</a>
</div>
<div className="min-h-screen bg-gradient-to-br from-indigo-900 via-purple-900 to-pink-800 relative overflow-hidden">
<SEOHead currentText={inputText} />
<div className="relative z-10 container mx-auto px-4 py-8 max-w-6xl">
<MobileOptimizedHeader
animationsEnabled={animationsEnabled}
onToggleAnimations={setAnimationsEnabled}
totalFonts={Object.keys(fontTransforms).length}
onQuickShare={handleQuickShare}
/>
<EnhancedTextInput
inputText={displayText}
onTextChange={handleTextChange}
onSearch={handleSearch}
searchQuery={searchQuery}
placeholder="✍️ Start typing ..."
onRandomFont={handleRandomFont}
/>
<ImprovedCategoryFilter
selectedCategory={selectedCategory}
onCategoryChange={handleCategoryChange}
fontCounts={fontCounts}
isMobile={isMobile}
/>
{recentFonts.length > 0 && (
<div className="mb-16">
<h2 className="text-white text-lg font-semibold mb-4">🕘 Recently Used Fonts</h2>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 mb-10">
{recentFonts.map((name) => (
<PerformanceOptimizedFontCard
key={`recent_${name}`}
fontName={name}
transformedText={transformText(debouncedText, name)}
category={fontTransforms[name]?.category}
isPopular={popularFonts.includes(name)}
animationsEnabled={animationsEnabled}
index={-1}
onCopy={trackFontCopy}
onLike={trackFontLike}
onShare={handleQuickShare}
/>
))}
</div>
</div>
)}
<motion.div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 mt-10 mb-8" layout>
<AnimatePresence mode="popLayout">
{filteredFonts.map((name, i) => (
<PerformanceOptimizedFontCard
key={name}
fontName={name}
transformedText={transformText(debouncedText, name)}
category={fontTransforms[name]?.category}
isPopular={popularFonts.includes(name)}
animationsEnabled={animationsEnabled}
index={i}
onCopy={trackFontCopy}
onLike={trackFontLike}
onShare={handleQuickShare}
/>
))}
</AnimatePresence>
</motion.div>
<SocialButtons onShare={handleQuickShare} />
<InfoSection currentText={debouncedText} />
</div>
</div>
</>
);
}
export async function getServerSideProps() {
return { props: {} };
}