Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 0 additions & 22 deletions src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,6 @@ const VIBE_LABELS = [
"LGTM Speedrunner",
];

const STATS_PREVIEW = [
{ label: "Sessions Analyzed", value: 2847, suffix: "+" },
{ label: "Prompts Decoded", value: 18420, suffix: "+" },
{ label: "Vibe Scores Given", value: 847, suffix: "+" },
];

const HOW_IT_WORKS = [
{
step: "01",
Expand Down Expand Up @@ -488,22 +482,6 @@ export default function Home() {
</BlurFade>
</section>

{/* ─── COUNTER STRIP ─── */}
<section className="relative z-10 border-y border-white/[0.04] py-16">
<div className="mx-auto flex max-w-4xl flex-wrap items-center justify-center gap-x-16 gap-y-8 px-6">
{STATS_PREVIEW.map((stat, i) => (
<BlurFade key={stat.label} delay={0.1 + i * 0.1} inView>
<div className="text-center">
<p className="text-4xl font-bold tabular-nums text-white md:text-5xl">
<NumberTicker value={stat.value} delay={0.2} />
{stat.suffix}
</p>
<p className="mt-1 text-sm text-white/25">{stat.label}</p>
</div>
</BlurFade>
))}
</div>
</section>

{/* ─── FINAL CTA ─── */}
<section className="relative z-10 flex flex-col items-center px-6 py-32">
Expand Down
89 changes: 89 additions & 0 deletions src/components/cookie-consent-banner.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
"use client"

import { useState, useEffect } from "react"
import { motion, AnimatePresence } from "motion/react"

const CONSENT_KEY = "cookie-consent"

export type ConsentStatus = "accepted" | "declined" | null

export function getConsentStatus(): ConsentStatus {
if (typeof window === "undefined") return null
const value = localStorage.getItem(CONSENT_KEY)
if (value === "accepted" || value === "declined") return value
return null
}

function setConsentStatus(status: "accepted" | "declined") {
localStorage.setItem(CONSENT_KEY, status)
}

export function CookieConsentBanner({
onConsent,
}: {
onConsent: (accepted: boolean) => void
}) {
const [visible, setVisible] = useState(false)

useEffect(() => {
const status = getConsentStatus()
if (status === null) {
setVisible(true)
} else {
onConsent(status === "accepted")
}
}, [onConsent])

function handleAccept() {
setConsentStatus("accepted")
setVisible(false)
onConsent(true)
}

function handleDecline() {
setConsentStatus("declined")
setVisible(false)
onConsent(false)
}

return (
<AnimatePresence>
{visible && (
<motion.div
initial={{ y: 100, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
exit={{ y: 100, opacity: 0 }}
transition={{ type: "spring", damping: 25, stiffness: 200 }}
className="fixed bottom-0 left-0 right-0 z-[100] p-4 sm:p-6"
>
<div className="mx-auto flex max-w-2xl flex-col gap-4 rounded-2xl border border-white/[0.08] bg-[#111111]/95 p-5 shadow-2xl backdrop-blur-xl sm:flex-row sm:items-center sm:gap-6 sm:p-6">
<div className="flex-1">
<p className="text-sm font-medium text-white/80">
We use cookies for anonymous analytics
</p>
<p className="mt-1 text-xs leading-relaxed text-white/35">
We use PostHog to track anonymous usage events (page views,
button clicks) to improve the experience. No trace data is ever
collected.
</p>
</div>
<div className="flex shrink-0 gap-3">
<button
onClick={handleDecline}
className="rounded-lg border border-white/[0.08] bg-white/[0.03] px-4 py-2 text-sm font-medium text-white/50 transition-colors hover:border-white/[0.12] hover:text-white/70"
>
Decline
</button>
<button
onClick={handleAccept}
className="rounded-lg bg-purple-600 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-purple-500"
>
Accept
</button>
</div>
</div>
</motion.div>
)}
</AnimatePresence>
)
}
42 changes: 33 additions & 9 deletions src/components/providers/posthog-provider.tsx
Original file line number Diff line number Diff line change
@@ -1,37 +1,61 @@
"use client"

import { useEffect, useRef } from "react"
import { useEffect, useRef, useCallback, useState } from "react"
import { usePathname, useSearchParams } from "next/navigation"
import posthog from "posthog-js"
import {
initPostHog,
installGlobalErrorHandlers,
isPostHogEnabled,
} from "@/lib/analytics/posthog"
import {
CookieConsentBanner,
getConsentStatus,
} from "@/components/cookie-consent-banner"

/** Tracks Next.js route changes as PostHog pageviews. */
function usePageviewTracking() {
function usePageviewTracking(enabled: boolean) {
const pathname = usePathname()
const searchParams = useSearchParams()

useEffect(() => {
if (!isPostHogEnabled) return
if (!enabled || !isPostHogEnabled) return
const url = pathname + (searchParams.toString() ? `?${searchParams.toString()}` : "")
posthog.capture("$pageview", { $current_url: url })
}, [pathname, searchParams])
}, [pathname, searchParams, enabled])
}

export function PostHogProvider({ children }: { children: React.ReactNode }) {
const initialized = useRef(false)
const [consentGiven, setConsentGiven] = useState(false)

// Initialize PostHog only if consent was already given (from storage)
useEffect(() => {
if (initialized.current) return
initialized.current = true
initPostHog()
installGlobalErrorHandlers()
const status = getConsentStatus()
if (status === "accepted") {
initialized.current = true
setConsentGiven(true)
initPostHog()
installGlobalErrorHandlers()
}
}, [])

const handleConsent = useCallback((accepted: boolean) => {
if (accepted && !initialized.current) {
initialized.current = true
setConsentGiven(true)
initPostHog()
installGlobalErrorHandlers()
}
}, [])

usePageviewTracking()
usePageviewTracking(consentGiven)

return <>{children}</>
return (
<>
{children}
{isPostHogEnabled && <CookieConsentBanner onConsent={handleConsent} />}
</>
)
}
Loading