Skip to content
Open
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
104 changes: 93 additions & 11 deletions app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
--card-foreground: oklch(0.145 0 0);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.145 0 0);
--primary: oklch(0.205 0 0);
--primary: oklch(0.52 0.24 25);
--primary-foreground: oklch(0.985 0 0);
--secondary: oklch(0.97 0 0);
--secondary-foreground: oklch(0.205 0 0);
Expand All @@ -66,20 +66,20 @@
--destructive: oklch(0.577 0.245 27.325);
--border: oklch(0.922 0 0);
--input: oklch(0.922 0 0);
--ring: oklch(0.708 0 0);
--ring: oklch(0.52 0.24 25);
--chart-1: oklch(0.646 0.222 41.116);
--chart-2: oklch(0.6 0.118 184.704);
--chart-3: oklch(0.398 0.07 227.392);
--chart-4: oklch(0.828 0.189 84.429);
--chart-5: oklch(0.769 0.188 70.08);
--sidebar: oklch(0.985 0 0);
--sidebar-foreground: oklch(0.145 0 0);
--sidebar-primary: oklch(0.205 0 0);
--sidebar-primary: oklch(0.52 0.24 25);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.97 0 0);
--sidebar-accent-foreground: oklch(0.205 0 0);
--sidebar-border: oklch(0.922 0 0);
--sidebar-ring: oklch(0.708 0 0);
--sidebar-ring: oklch(0.52 0.24 25);
}

.dark {
Expand All @@ -89,8 +89,8 @@
--card-foreground: oklch(0.985 0 0);
--popover: oklch(0.205 0 0);
--popover-foreground: oklch(0.985 0 0);
--primary: oklch(0.922 0 0);
--primary-foreground: oklch(0.205 0 0);
--primary: oklch(0.65 0.22 25);
--primary-foreground: oklch(0.12 0 0);
--secondary: oklch(0.269 0 0);
--secondary-foreground: oklch(0.985 0 0);
--muted: oklch(0.269 0 0);
Expand All @@ -100,20 +100,20 @@
--destructive: oklch(0.704 0.191 22.216);
--border: oklch(1 0 0 / 10%);
--input: oklch(1 0 0 / 15%);
--ring: oklch(0.556 0 0);
--ring: oklch(0.65 0.22 25);
--chart-1: oklch(0.488 0.243 264.376);
--chart-2: oklch(0.696 0.17 162.48);
--chart-3: oklch(0.769 0.188 70.08);
--chart-4: oklch(0.627 0.265 303.9);
--chart-5: oklch(0.645 0.246 16.439);
--sidebar: oklch(0.205 0 0);
--sidebar-foreground: oklch(0.985 0 0);
--sidebar-primary: oklch(0.488 0.243 264.376);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-primary: oklch(0.65 0.22 25);
--sidebar-primary-foreground: oklch(0.12 0 0);
--sidebar-accent: oklch(0.269 0 0);
--sidebar-accent-foreground: oklch(0.985 0 0);
--sidebar-border: oklch(1 0 0 / 10%);
--sidebar-ring: oklch(0.556 0 0);
--sidebar-ring: oklch(0.65 0.22 25);
}

@layer base {
Expand All @@ -123,4 +123,86 @@
body {
@apply bg-background text-foreground;
}
}
}

@keyframes fade-in-up {
from {
opacity: 0;
transform: translateY(24px);
}
to {
opacity: 1;
transform: translateY(0);
}
}

@keyframes float {
0%, 100% { transform: translateY(0px); }
50% { transform: translateY(-8px); }
}

@keyframes scale-in {
from {
opacity: 0;
transform: scale(0.8);
}
to {
opacity: 1;
transform: scale(1);
}
}

@keyframes glow-pulse {
0%, 100% {
box-shadow: 0 0 0 0 oklch(0.65 0.22 25 / 0%);
}
50% {
box-shadow: 0 0 24px 6px oklch(0.65 0.22 25 / 35%);
}
}

@keyframes slide-in-right {
from {
opacity: 0;
transform: translateX(-16px);
}
to {
opacity: 1;
transform: translateX(0);
}
}

@keyframes shimmer {
0% { background-position: -200% center; }
100% { background-position: 200% center; }
}

.animate-fade-in-up {
animation: fade-in-up 0.6s cubic-bezier(0.16, 1, 0.3, 1) forwards;
opacity: 0;
}

.animate-float {
animation: float 4s ease-in-out infinite;
}

.animate-scale-in {
animation: scale-in 0.5s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
opacity: 0;
}

.animate-glow-pulse {
animation: glow-pulse 2.5s ease-in-out infinite;
}

.animate-slide-in-right {
animation: slide-in-right 0.5s cubic-bezier(0.16, 1, 0.3, 1) forwards;
opacity: 0;
}

.animation-delay-100 { animation-delay: 100ms; }
.animation-delay-200 { animation-delay: 200ms; }
.animation-delay-300 { animation-delay: 300ms; }
.animation-delay-400 { animation-delay: 400ms; }
.animation-delay-500 { animation-delay: 500ms; }
.animation-delay-600 { animation-delay: 600ms; }
23 changes: 15 additions & 8 deletions app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,20 @@ export default function Home() {
<>
{/* Hero */}
<section className="relative flex flex-1 flex-col items-center justify-center px-6 py-20">
{/* Subtle gradient background */}
<div className="pointer-events-none absolute inset-0 bg-[radial-gradient(ellipse_at_center,_rgba(99,102,241,0.08)_0%,_transparent_70%)]" />
{/* Subtle red gradient background */}
<div className="pointer-events-none absolute inset-0 bg-[radial-gradient(ellipse_at_center,_rgba(225,29,72,0.1)_0%,_transparent_70%)]" />

<div className="relative flex max-w-2xl flex-col items-center gap-6 text-center">
<h1 className="text-4xl font-bold tracking-tight sm:text-5xl">
<h1 className="animate-fade-in-up text-4xl font-bold tracking-tight sm:text-5xl">
Is your site AI-search ready?
</h1>
<p className="max-w-lg text-lg text-foreground">
<p className="animate-fade-in-up animation-delay-100 max-w-lg text-lg text-foreground">
Citable analyzes how well your website ranks in AI-generated
answers from ChatGPT, Perplexity, and Google AI Overviews.
</p>
<UrlForm />
<div className="animate-fade-in-up animation-delay-200 w-full flex justify-center">
<UrlForm />
</div>
</div>
</section>

Expand All @@ -27,7 +29,7 @@ export default function Home() {
className="border-t border-border/40 bg-muted/30 px-6 py-20"
>
<div className="mx-auto max-w-4xl">
<h2 className="mb-12 text-center text-2xl font-bold tracking-tight">
<h2 className="mb-12 text-center text-2xl font-bold tracking-tight animate-fade-in-up">
How it works
</h2>
<div className="grid gap-8 sm:grid-cols-3">
Expand All @@ -36,18 +38,21 @@ export default function Home() {
step={1}
title="Enter your URL"
description="We'll scan up to 10 pages on your site"
delay="animation-delay-100"
/>
<Step
icon={<Search className="size-6" />}
step={2}
title="Pick your scraper"
description="Choose a free built-in browser or your own Firecrawl key"
delay="animation-delay-200"
/>
<Step
icon={<BarChart3 className="size-6" />}
step={3}
title="Get your score"
description="With a detailed breakdown"
delay="animation-delay-300"
/>
</div>
</div>
Expand All @@ -61,15 +66,17 @@ function Step({
step,
title,
description,
delay = "",
}: {
icon: React.ReactNode
step: number
title: string
description: string
delay?: string
}) {
return (
<div className="flex flex-col items-center gap-3 text-center">
<div className="flex size-12 items-center justify-center rounded-full bg-primary/10 text-primary">
<div className={`flex flex-col items-center gap-3 text-center animate-fade-in-up ${delay}`}>
<div className="flex size-12 items-center justify-center rounded-full bg-primary/10 text-primary transition-all duration-300 hover:bg-primary/20 hover:scale-110 animate-float">
{icon}
</div>
<span className="text-xs font-medium text-muted-foreground">
Expand Down
2 changes: 1 addition & 1 deletion components/AppBackground.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export function AppBackground() {
>
<PixelBlast
variant="circle"
color="#6366f1"
color="#e11d48"
pixelSize={5}
patternScale={2.5}
patternDensity={1}
Expand Down
5 changes: 3 additions & 2 deletions components/FactorBreakdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,17 @@ export const FactorBreakdown = memo(function FactorBreakdown({

return (
<div className="divide-y divide-border/40">
{entries.map(([key, factor]) => {
{entries.map(([key, factor], index) => {
const pct = Math.round((factor.score / factor.maxScore) * 100)
const colors = getColor(pct)
const rubric = SCORING_RUBRIC.find((r) => r.key === key)
const isOpen = expanded === key
const achieved = rubric ? getAchievedTier(rubric, factor.score) : null
const next = rubric ? getNextTier(rubric, factor.score) : null
const delayClass = `animation-delay-${Math.min(index * 100, 600)}`

return (
<div key={key} className="py-4 first:pt-0 last:pb-0">
<div key={key} className={`py-4 first:pt-0 last:pb-0 animate-fade-in-up ${delayClass}`}>
{/* Header row — always visible */}
<button
type="button"
Expand Down
10 changes: 5 additions & 5 deletions components/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,23 +26,23 @@ export function Navbar() {
<nav className="flex items-center justify-between sticky top-0 z-50 border-b border-border/40 bg-background/60 px-6 py-4 backdrop-blur-md">
{/* Logo */}
{isHome && (
<span className="text-lg font-bold tracking-tight">Citable</span>
<span className="text-lg font-bold tracking-tight text-primary animate-slide-in-right">Citable</span>
)}
{(isScoring || isRankings) && (
<Link
href="/"
className="text-lg font-bold tracking-tight hover:opacity-80 transition-opacity"
className="text-lg font-bold tracking-tight text-primary hover:opacity-80 transition-all duration-200 hover:scale-105"
>
Citable
</Link>
)}
{isResults && (
<Link
href="/"
className="flex items-center gap-2 text-sm text-muted-foreground hover:text-foreground transition-colors"
className="flex items-center gap-2 text-sm text-muted-foreground hover:text-foreground transition-all duration-200 group"
>
<ArrowLeft className="size-4" />
<span className="text-lg font-bold tracking-tight text-foreground">Citable</span>
<ArrowLeft className="size-4 transition-transform duration-200 group-hover:-translate-x-0.5" />
<span className="text-lg font-bold tracking-tight text-primary">Citable</span>
</Link>
)}

Expand Down
2 changes: 1 addition & 1 deletion components/ScoreGauge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export function ScoreGauge({ score, size = "md" }: ScoreGaugeProps) {
const label = getScoreLabel(score)

return (
<div className="flex flex-col items-center gap-1">
<div className="flex flex-col items-center gap-1 animate-scale-in">
<svg
width={config.width}
height={config.width}
Expand Down
9 changes: 7 additions & 2 deletions components/UrlForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export function UrlForm() {
}

return (
<Card className="w-full max-w-xl border-border/50 bg-card/50 backdrop-blur-sm">
<Card className="w-full max-w-xl border-border/50 bg-card/50 backdrop-blur-sm transition-shadow duration-300 hover:shadow-[0_0_32px_0px_oklch(0.65_0.22_25_/_20%)]">
<CardHeader>
<CardTitle className="text-lg">Analyze your website</CardTitle>
</CardHeader>
Expand Down Expand Up @@ -145,7 +145,12 @@ export function UrlForm() {
</div>

{/* Submit */}
<Button type="submit" size="lg" disabled={loading} className="w-full">
<Button
type="submit"
size="lg"
disabled={loading}
className="w-full transition-all duration-300 hover:shadow-[0_0_20px_2px_oklch(0.65_0.22_25_/_40%)] hover:scale-[1.02] active:scale-[0.98]"
>
{loading ? (
<>
<Loader2 className="size-4 animate-spin" />
Expand Down