// pages/index.jsx import React, { useState, useEffect, useMemo, useCallback } from "react"; import Head from "next/head"; import { useRouter } from "next/router"; import { motion, AnimatePresence } from "framer-motion"; import posthog from "posthog-js"; 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"; /* -------------------- PostHog: Client Init + Pageviews -------------------- */ // Fallback-Init (falls du instrumentation-client.ts noch nicht nutzt) if (typeof window !== "undefined" && !(posthog).__initialized) { const key = process.env.NEXT_PUBLIC_POSTHOG_KEY || ""; const host = process.env.NEXT_PUBLIC_POSTHOG_HOST || "https://us.i.posthog.com"; if (key) { posthog.init(key, { api_host: host, capture_pageview: false, // Pageviews steuern wir selbst unten loaded: () => { if (process.env.NODE_ENV === "development") posthog.debug(); }, }); // kleiner Guard gegen Doppel-Init bei Fast Refresh (posthog).__initialized = true; } else { console.warn("β οΈ NEXT_PUBLIC_POSTHOG_KEY ist nicht gesetzt"); } } /* ------------------------------------------------------------------------- */ if (typeof window !== "undefined") { sessionStorage.removeItem("fancytext_recent_fonts"); } export default function HomePage() { const router = useRouter(); // --- Helper: Server-Tracking via /api/track --- const serverTrack = useCallback(async (payload) => { try { await fetch("/api/track", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload), }); } catch { // optional: console.warn("Server track failed", e); } }, []); // Pageview-Tracking (pages router) useEffect(() => { const send = (url) => { const href = typeof window !== "undefined" && window.location ? window.location.href : url; // Client-Pageview posthog.capture("$pageview", { $current_url: href, path: url, }); // Server-Pageview (optional, fΓΌr serverseitige Analysen) serverTrack({ distinctId: "anon_client", event: "$pageview", properties: { path: url, href }, }); }; // initial if (router?.asPath) send(router.asPath); const handleRouteChange = (url) => send(url); router.events.on("routeChangeComplete", handleRouteChange); return () => router.events.off("routeChangeComplete", handleRouteChange); }, [router, serverTrack]); 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) => { // Client-Event posthog.capture("font_copied", { font_name: fontName, text_length: text.length, category: fontTransforms[fontName]?.category, }); // Server-Event serverTrack({ distinctId: "anon_client", event: "font_copied", properties: { 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; }); }, [serverTrack] ); const trackFontLike = useCallback( (fontName, liked) => { posthog.capture("font_liked", { font_name: fontName, action: liked ? "like" : "unlike", }); serverTrack({ distinctId: "anon_client", event: "font_liked", properties: { font_name: fontName, action: liked ? "like" : "unlike" }, }); }, [serverTrack] ); const handleQuickShare = useCallback(async () => { const shareData = { title: "FancyText - Cool Fonts! π₯", text: "Check out this app for cool Instagram & TikTok fonts! 30+ fonts free β¨", url: typeof window !== "undefined" ? window.location.href : "https://fancytext.app", }; if (navigator.share) { try { await navigator.share(shareData); posthog.capture("app_shared", { method: "web_share_api" }); serverTrack({ distinctId: "anon_client", event: "app_shared", properties: { method: "web_share_api" }, }); } catch { posthog.capture("app_share_failed"); serverTrack({ distinctId: "anon_client", event: "app_share_failed", properties: {}, }); } } else { await navigator.clipboard.writeText(`${shareData.text}\n${shareData.url}`); posthog.capture("app_shared", { method: "clipboard_copy" }); serverTrack({ distinctId: "anon_client", event: "app_shared", properties: { method: "clipboard_copy" }, }); alert("Link copied to clipboard! π"); } }, [serverTrack]); const handleTextChange = useCallback( (text) => { setInputText(text); setPreviewFont(null); posthog.capture("text_changed", { length: text?.length ?? 0 }); serverTrack({ distinctId: "anon_client", event: "text_changed", properties: { length: text?.length ?? 0 }, }); }, [serverTrack] ); const handleCategoryChange = useCallback( (cat) => { setSelectedCategory(cat); posthog.capture("category_changed", { category: cat }); serverTrack({ distinctId: "anon_client", event: "category_changed", properties: { category: cat }, }); }, [serverTrack] ); const handleSearch = useCallback( (q) => { setSearchQuery(q); posthog.capture("font_search", { query_length: q?.length ?? 0 }); serverTrack({ distinctId: "anon_client", event: "font_search", properties: { query_length: q?.length ?? 0 }, }); }, [serverTrack] ); 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); posthog.capture("random_font_selected", { font: newFont }); serverTrack({ distinctId: "anon_client", event: "random_font_selected", properties: { font: newFont }, }); }, [previewFont, serverTrack]); const displayText = previewFont ? transformText(inputText || "Try me!", previewFont) : inputText; return ( <>