Build TercihAI MVP: landing page, YÖK Atlas data pipeline, rank-based results

- Add product vision/market research doc (VISION.md)
- Set up shadcn/ui (radix + nova preset) with blue/orange theme, Outfit + Work Sans fonts
- Landing page: hero with rank input, problem/steps sections, comparison table, FAQ
- Data pipeline: CSV archive ingest (2021-2024) + live YÖK Atlas API refresh (2025)
  into SQLite (npm run ingest / refresh); zeros normalized to NULL
- /sonuc page: hayal/dengeli/garanti buckets by COALESCE(sira2025, sira2024),
  score-type switcher, 2024→2025 trend indicators
- Add ui-ux-pro-max and shadcn agent skills, shadcn MCP config, launch.json

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
bilalgursen
2026-07-20 07:46:41 +03:00
parent 4c76bd9724
commit 87f265ec15
84 changed files with 16020 additions and 89 deletions

View File

@@ -1,26 +1,130 @@
@import "tailwindcss";
@import "tw-animate-css";
@import "shadcn/tailwind.css";
:root {
--background: #ffffff;
--foreground: #171717;
}
@custom-variant dark (&:is(.dark *));
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--font-sans: var(--font-geist-sans);
--font-sans: var(--font-work-sans);
--font-mono: var(--font-geist-mono);
--font-heading: var(--font-outfit);
--color-sidebar-ring: var(--sidebar-ring);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar: var(--sidebar);
--color-chart-5: var(--chart-5);
--color-chart-4: var(--chart-4);
--color-chart-3: var(--chart-3);
--color-chart-2: var(--chart-2);
--color-chart-1: var(--chart-1);
--color-ring: var(--ring);
--color-input: var(--input);
--color-border: var(--border);
--color-destructive: var(--destructive);
--color-accent-foreground: var(--accent-foreground);
--color-accent: var(--accent);
--color-muted-foreground: var(--muted-foreground);
--color-muted: var(--muted);
--color-secondary-foreground: var(--secondary-foreground);
--color-secondary: var(--secondary);
--color-primary-foreground: var(--primary-foreground);
--color-primary: var(--primary);
--color-popover-foreground: var(--popover-foreground);
--color-popover: var(--popover);
--color-card-foreground: var(--card-foreground);
--color-card: var(--card);
--radius-sm: calc(var(--radius) * 0.6);
--radius-md: calc(var(--radius) * 0.8);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) * 1.4);
--radius-2xl: calc(var(--radius) * 1.8);
--radius-3xl: calc(var(--radius) * 2.2);
--radius-4xl: calc(var(--radius) * 2.6);
}
@media (prefers-color-scheme: dark) {
:root {
--background: #0a0a0a;
--foreground: #ededed;
:root {
--background: oklch(1 0 0);
--foreground: oklch(0.145 0 0);
--card: oklch(1 0 0);
--card-foreground: oklch(0.145 0 0);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.145 0 0);
--primary: oklch(0.623 0.188 259.8);
--primary-foreground: oklch(0.985 0 0);
--secondary: oklch(0.97 0 0);
--secondary-foreground: oklch(0.205 0 0);
--muted: oklch(0.97 0 0);
--muted-foreground: oklch(0.556 0 0);
--accent: oklch(0.97 0 0);
--accent-foreground: oklch(0.205 0 0);
--destructive: oklch(0.577 0.245 27.325);
--border: oklch(0.922 0 0);
--input: oklch(0.922 0 0);
--ring: oklch(0.623 0.188 259.8 / 60%);
--chart-1: oklch(0.87 0 0);
--chart-2: oklch(0.556 0 0);
--chart-3: oklch(0.439 0 0);
--chart-4: oklch(0.371 0 0);
--chart-5: oklch(0.269 0 0);
--radius: 0.625rem;
--sidebar: oklch(0.985 0 0);
--sidebar-foreground: oklch(0.145 0 0);
--sidebar-primary: oklch(0.205 0 0);
--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);
}
.dark {
--background: oklch(0.145 0 0);
--foreground: oklch(0.985 0 0);
--card: oklch(0.205 0 0);
--card-foreground: oklch(0.985 0 0);
--popover: oklch(0.205 0 0);
--popover-foreground: oklch(0.985 0 0);
--primary: oklch(0.707 0.143 254.6);
--primary-foreground: oklch(0.145 0 0);
--secondary: oklch(0.269 0 0);
--secondary-foreground: oklch(0.985 0 0);
--muted: oklch(0.269 0 0);
--muted-foreground: oklch(0.708 0 0);
--accent: oklch(0.269 0 0);
--accent-foreground: oklch(0.985 0 0);
--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);
--chart-1: oklch(0.87 0 0);
--chart-2: oklch(0.556 0 0);
--chart-3: oklch(0.439 0 0);
--chart-4: oklch(0.371 0 0);
--chart-5: oklch(0.269 0 0);
--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-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);
}
@layer base {
* {
@apply border-border outline-ring/50;
}
}
body {
background: var(--background);
color: var(--foreground);
font-family: Arial, Helvetica, sans-serif;
}
body {
@apply bg-background text-foreground;
}
html {
@apply font-sans;
}
}

View File

@@ -1,10 +1,16 @@
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import { Outfit, Work_Sans, Geist_Mono } from "next/font/google";
import { Toaster } from "@/components/ui/sonner";
import "./globals.css";
const geistSans = Geist({
variable: "--font-geist-sans",
subsets: ["latin"],
const outfit = Outfit({
variable: "--font-outfit",
subsets: ["latin", "latin-ext"],
});
const workSans = Work_Sans({
variable: "--font-work-sans",
subsets: ["latin", "latin-ext"],
});
const geistMono = Geist_Mono({
@@ -13,8 +19,9 @@ const geistMono = Geist_Mono({
});
export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
title: "TercihAI — Cebindeki Tercih Danışmanı",
description:
"YKS sıralamana göre gerçek YÖK Atlas verisiyle dengeli 24 tercihlik liste. İnsan danışmanın onda bir fiyatına, yapay zekâ destekli tercih danışmanlığı.",
};
export default function RootLayout({
@@ -24,10 +31,13 @@ export default function RootLayout({
}>) {
return (
<html
lang="en"
className={`${geistSans.variable} ${geistMono.variable} h-full antialiased`}
lang="tr"
className={`${outfit.variable} ${workSans.variable} ${geistMono.variable} h-full antialiased`}
>
<body className="min-h-full flex flex-col">{children}</body>
<body className="min-h-full flex flex-col">
{children}
<Toaster position="top-center" />
</body>
</html>
);
}

View File

@@ -1,65 +1,377 @@
import Image from "next/image";
import {
BarChart3,
CheckCircle2,
Compass,
ListChecks,
MessageCircleQuestion,
ShieldCheck,
Sparkles,
TrendingUp,
Wallet,
X,
} from "lucide-react";
import { HeroForm } from "@/components/hero-form";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "@/components/ui/accordion";
import {
Card,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
const problems = [
{
icon: ListChecks,
title: "24 tercih, binlerce ihtimal",
description:
"23.000'den fazla lisans programı arasından sana uyan 24 tanesini doğru sırayla dizmek, tablolarla boğuşarak günler alıyor.",
},
{
icon: TrendingUp,
title: "Geçen yılın puanı yetmiyor",
description:
"Taban sıralamaları her yıl kayıyor. Tek yıla bakarak yapılan tercih, boşta kalmanın en yaygın sebebi.",
},
{
icon: Wallet,
title: "Danışman lüks haline geldi",
description:
"İyi bir tercih danışmanı 2.00010.000 TL istiyor. Ücretsiz robotlar ise sadece liste filtreliyor, akıl vermiyor.",
},
];
const steps = [
{
icon: Compass,
step: "1",
title: "Sıralamanı ve hedeflerini anlat",
description:
"YKS başarı sıralamanı gir; şehir, bölüm ve kariyer tercihlerini sohbet ederek netleştirelim.",
},
{
icon: BarChart3,
step: "2",
title: "Yapay zekâ veriyi tarasın",
description:
"Son 4 yılın YÖK Atlas verisi — taban sıralaması trendleri, kontenjan değişimleri, doluluk oranları — senin için analiz edilsin.",
},
{
icon: ShieldCheck,
step: "3",
title: "Dengeli 24'lük listeni al",
description:
"Hayal, dengeli ve garanti dilimlerine dağıtılmış, her satırı gerekçeli tercih listeni indir; aklına takılanı danışmanına sor.",
},
];
const faqs = [
{
question: "TercihAI'nin önerileri neye dayanıyor?",
answer:
"Tüm öneriler YÖK Atlas'ın resmî verisine dayanır: son 4 yılın taban başarı sıralamaları, kontenjanlar, yerleşme istatistikleri ve doluluk oranları. Yapay zekâ bu veriyi yorumlar; veri olmadan tahmin yürütmez.",
},
{
question: "Ücretsiz tercih robotlarından farkı ne?",
answer:
"Ücretsiz robotlar puan aralığına göre bölüm listeler ve gerisini sana bırakır. TercihAI ise listenin kendisini kurar: hangi tercihi kaçıncı sıraya, neden koyduğunu açıklar, riskini söyler ve sorularını cevaplar. Yani filtre değil, danışmandır.",
},
{
question: "Yerleşme garantisi veriyor musunuz?",
answer:
"Hayır — ve verdiğini iddia eden herkesten uzak dur. Taban sıralamaları her yıl adayların davranışına göre değişir; kimse garanti veremez. Biz riski şeffaf gösterir, listeyi hayal/dengeli/garanti dilimleriyle kurarak boşta kalma ihtimalini en aza indiririz. Tercih listenin son hali ve ÖSYM başvurusu her zaman senin sorumluluğundadır.",
},
{
question: "Fiyatlandırma nasıl çalışıyor?",
answer:
"Abonelik yok. Tercih dönemi boyunca geçerli tek seferlik paket alırsın: yapay zekâ danışman sohbeti, kişisel 24'lük liste, risk analizi ve liste revizyonları dahil. Temel program arama ise herkes için ücretsiz.",
},
];
const comparison = [
{
feature: "Sıralamana uygun program listesi",
robot: true,
danisman: true,
tercihai: true,
},
{
feature: "Son 4 yılın trend analizi",
robot: false,
danisman: true,
tercihai: true,
},
{
feature: "Gerekçeli, dengeli 24'lük liste",
robot: false,
danisman: true,
tercihai: true,
},
{
feature: "7/24 soru-cevap",
robot: false,
danisman: false,
tercihai: true,
},
{
feature: "Fiyat",
robot: "Ücretsiz",
danisman: "2.00010.000 TL",
tercihai: "İnsan danışmanın ~10'da 1'i",
},
];
function ComparisonCell({ value }: { value: boolean | string }) {
if (typeof value === "string") {
return <span className="text-sm font-medium">{value}</span>;
}
return value ? (
<CheckCircle2 className="mx-auto size-5 text-primary" aria-label="Var" />
) : (
<X className="mx-auto size-5 text-muted-foreground/50" aria-label="Yok" />
);
}
export default function Home() {
return (
<div className="flex flex-col flex-1 items-center justify-center bg-zinc-50 font-sans dark:bg-black">
<main className="flex flex-1 w-full max-w-3xl flex-col items-center justify-between py-32 px-16 bg-white dark:bg-black sm:items-start">
<Image
className="dark:invert"
src="/next.svg"
alt="Next.js logo"
width={100}
height={20}
priority
/>
<div className="flex flex-col items-center gap-6 text-center sm:items-start sm:text-left">
<h1 className="max-w-xs text-3xl font-semibold leading-10 tracking-tight text-black dark:text-zinc-50">
To get started, edit the page.tsx file.
<div className="flex min-h-screen flex-col bg-slate-50 text-slate-900">
{/* Navbar */}
<header className="sticky top-4 z-50 mx-4">
<div className="mx-auto flex h-14 max-w-6xl items-center justify-between rounded-2xl border border-slate-200 bg-white/80 px-5 shadow-sm backdrop-blur">
<a href="#" className="flex items-center gap-2 font-heading text-lg font-bold">
<Sparkles className="size-5 text-primary" aria-hidden />
TercihAI
</a>
<nav className="hidden items-center gap-6 text-sm font-medium text-slate-600 sm:flex">
<a href="#nasil-calisir" className="transition-colors duration-200 hover:text-slate-900">
Nasıl çalışır?
</a>
<a href="#karsilastirma" className="transition-colors duration-200 hover:text-slate-900">
Karşılaştır
</a>
<a href="#sss" className="transition-colors duration-200 hover:text-slate-900">
SSS
</a>
</nav>
<Button asChild size="sm" className="cursor-pointer">
<a href="#hero-form">Hemen başla</a>
</Button>
</div>
</header>
<main className="flex-1">
{/* Hero */}
<section className="mx-auto flex max-w-6xl flex-col items-center px-4 pb-20 pt-16 text-center sm:pt-24">
<Badge variant="secondary" className="mb-6 bg-primary/10 text-primary">
2026 YKS tercih dönemi ık
</Badge>
<h1 className="max-w-3xl font-heading text-4xl font-bold tracking-tight sm:text-6xl">
Dört yıllık geleceğini{" "}
<span className="text-primary">48 saatlik panikle</span> seçme
</h1>
<p className="max-w-md text-lg leading-8 text-zinc-600 dark:text-zinc-400">
Looking for a starting point or more instructions? Head over to{" "}
<a
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
className="font-medium text-zinc-950 dark:text-zinc-50"
<p className="mt-6 max-w-2xl text-lg leading-relaxed text-slate-600">
TercihAI, gerçek YÖK Atlas verisiyle çalışan yapay zekâ tercih
danışmanın. İnsan danışmanın binlerce lira aldığı işi dengeli
liste, risk analizi, soru-cevap onda bir fiyatına yapar.
</p>
<div id="hero-form" className="mt-10 flex w-full scroll-mt-28 justify-center">
<HeroForm />
</div>
<p className="mt-4 text-sm text-slate-500">
Temel arama her zaman ücretsiz · Kredi kartı gerekmez
</p>
{/* Sosyal kanıt: henüz kullanıcı yok — dürüstçe veri istatistikleri */}
<dl className="mt-16 grid w-full max-w-3xl grid-cols-1 gap-4 sm:grid-cols-3">
{[
{ value: "23.000+", label: "Taranan lisans programı" },
{ value: "4 yıl", label: "Taban sıralaması trend verisi" },
{ value: "%100", label: "Resmî YÖK Atlas kaynağı" },
].map((stat) => (
<div
key={stat.label}
className="rounded-2xl border border-slate-200 bg-white p-6"
>
<dt className="text-sm text-slate-500">{stat.label}</dt>
<dd className="mt-1 font-heading text-3xl font-bold text-primary">
{stat.value}
</dd>
</div>
))}
</dl>
</section>
{/* Problem */}
<section className="border-y border-slate-200 bg-white py-20">
<div className="mx-auto max-w-6xl px-4">
<h2 className="text-center font-heading text-3xl font-bold sm:text-4xl">
Tercih dönemi neden bu kadar stresli?
</h2>
<div className="mt-12 grid gap-6 md:grid-cols-3">
{problems.map((p) => (
<Card key={p.title} className="border-slate-200">
<CardHeader>
<div className="mb-2 flex size-11 items-center justify-center rounded-xl bg-primary/10">
<p.icon className="size-5 text-primary" aria-hidden />
</div>
<CardTitle className="font-heading text-xl">
{p.title}
</CardTitle>
<CardDescription className="text-base leading-relaxed">
{p.description}
</CardDescription>
</CardHeader>
</Card>
))}
</div>
</div>
</section>
{/* How it works */}
<section id="nasil-calisir" className="scroll-mt-24 py-20">
<div className="mx-auto max-w-6xl px-4">
<h2 className="text-center font-heading text-3xl font-bold sm:text-4xl">
Üç adımda listen hazır
</h2>
<div className="mt-12 grid gap-6 md:grid-cols-3">
{steps.map((s) => (
<div
key={s.step}
className="relative rounded-2xl border border-slate-200 bg-white p-8"
>
<span className="absolute -top-4 left-8 flex size-8 items-center justify-center rounded-full bg-orange-500 font-heading text-sm font-bold text-white">
{s.step}
</span>
<s.icon className="size-6 text-primary" aria-hidden />
<h3 className="mt-4 font-heading text-xl font-semibold">
{s.title}
</h3>
<p className="mt-2 leading-relaxed text-slate-600">
{s.description}
</p>
</div>
))}
</div>
</div>
</section>
{/* Comparison */}
<section
id="karsilastirma"
className="scroll-mt-24 border-y border-slate-200 bg-white py-20"
>
<div className="mx-auto max-w-4xl px-4">
<h2 className="text-center font-heading text-3xl font-bold sm:text-4xl">
Robot mu, danışman mı, TercihAI mı?
</h2>
<div className="mt-12 overflow-x-auto rounded-2xl border border-slate-200 bg-white">
<Table>
<TableHeader>
<TableRow className="bg-slate-50">
<TableHead className="w-[40%]">Özellik</TableHead>
<TableHead className="text-center">
Ücretsiz robotlar
</TableHead>
<TableHead className="text-center">
İnsan danışman
</TableHead>
<TableHead className="text-center font-heading font-bold text-primary">
TercihAI
</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{comparison.map((row) => (
<TableRow key={row.feature}>
<TableCell className="font-medium">
{row.feature}
</TableCell>
<TableCell className="text-center">
<ComparisonCell value={row.robot} />
</TableCell>
<TableCell className="text-center">
<ComparisonCell value={row.danisman} />
</TableCell>
<TableCell className="bg-primary/5 text-center">
<ComparisonCell value={row.tercihai} />
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
</div>
</section>
{/* FAQ */}
<section id="sss" className="scroll-mt-24 py-20">
<div className="mx-auto max-w-3xl px-4">
<h2 className="text-center font-heading text-3xl font-bold sm:text-4xl">
Sık sorulan sorular
</h2>
<Accordion type="single" collapsible className="mt-10">
{faqs.map((faq) => (
<AccordionItem key={faq.question} value={faq.question}>
<AccordionTrigger className="cursor-pointer text-left font-heading text-base font-semibold">
{faq.question}
</AccordionTrigger>
<AccordionContent className="text-base leading-relaxed text-slate-600">
{faq.answer}
</AccordionContent>
</AccordionItem>
))}
</Accordion>
</div>
</section>
{/* Final CTA */}
<section className="bg-primary py-20 text-white">
<div className="mx-auto flex max-w-3xl flex-col items-center px-4 text-center">
<MessageCircleQuestion className="size-10 opacity-80" aria-hidden />
<h2 className="mt-6 font-heading text-3xl font-bold sm:text-4xl">
Tercih listen, pişmanlık listesi olmasın
</h2>
<p className="mt-4 max-w-xl text-lg leading-relaxed text-white/90">
Sıralamanı gir, yapay zekâ danışmanın veriyle konuşsun. Tercih
dönemi bitmeden yerini al.
</p>
<Button
asChild
size="lg"
className="mt-8 h-12 cursor-pointer bg-orange-500 px-8 text-white transition-colors duration-200 hover:bg-orange-600"
>
Templates
</a>{" "}
or the{" "}
<a
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
className="font-medium text-zinc-950 dark:text-zinc-50"
>
Learning
</a>{" "}
center.
<a href="#hero-form">Ücretsiz dene</a>
</Button>
</div>
</section>
</main>
<footer className="border-t border-slate-200 bg-white py-10">
<div className="mx-auto max-w-6xl px-4 text-center text-sm leading-relaxed text-slate-500">
<p>
© 2026 TercihAI. Veriler resmî YÖK Atlas kaynağından derlenir;
TercihAI, ÖSYM veya YÖK ile bağlantılı değildir.
</p>
<p className="mt-2">
TercihAI bir karar destek aracıdır, yerleşme garantisi vermez.
Tercih listenizin son hali ve başvuru sorumluluğu size aittir.
</p>
</div>
<div className="flex flex-col gap-4 text-base font-medium sm:flex-row">
<a
className="flex h-12 w-full items-center justify-center gap-2 rounded-full bg-foreground px-5 text-background transition-colors hover:bg-[#383838] dark:hover:bg-[#ccc] md:w-[158px]"
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
className="dark:invert"
src="/vercel.svg"
alt="Vercel logomark"
width={16}
height={16}
/>
Deploy Now
</a>
<a
className="flex h-12 w-full items-center justify-center rounded-full border border-solid border-black/[.08] px-5 transition-colors hover:border-transparent hover:bg-black/[.04] dark:border-white/[.145] dark:hover:bg-[#1a1a1a] md:w-[158px]"
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
Documentation
</a>
</div>
</main>
</footer>
</div>
);
}

298
src/app/sonuc/page.tsx Normal file
View File

@@ -0,0 +1,298 @@
import Link from "next/link";
import type { Metadata } from "next";
import {
ArrowLeft,
Minus,
Rocket,
Scale,
ShieldCheck,
Sparkles,
TrendingDown,
TrendingUp,
} from "lucide-react";
import {
PUAN_TURLERI,
searchByRank,
type Program,
type PuanTuruKey,
} from "@/lib/db";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
export const metadata: Metadata = {
title: "Sıralamana Uygun Programlar — TercihAI",
};
const TUR_LABELS: Record<PuanTuruKey, string> = {
say: "Sayısal",
ea: "Eşit Ağırlık",
soz: "Sözel",
dil: "Dil",
tyt: "TYT (Önlisans)",
};
function TrendIcon({ p }: { p: Program }) {
if (p.sira2025 == null || p.sira2024 == null) {
return <Minus className="size-4 text-slate-400" aria-label="Veri yok" />;
}
const degisim = (p.sira2024 - p.sira2025) / p.sira2024;
// taban sıralaması küçülüyorsa bölüm zorlaşıyor demektir
if (degisim > 0.05) {
return (
<span className="inline-flex items-center gap-1 text-orange-600">
<TrendingUp className="size-4" aria-hidden />
<span className="text-xs">zorlaşıyor</span>
</span>
);
}
if (degisim < -0.05) {
return (
<span className="inline-flex items-center gap-1 text-emerald-600">
<TrendingDown className="size-4" aria-hidden />
<span className="text-xs">rahatlıyor</span>
</span>
);
}
return (
<span className="inline-flex items-center gap-1 text-slate-500">
<Minus className="size-4" aria-hidden />
<span className="text-xs">stabil</span>
</span>
);
}
function fmt(n: number | null): string {
return n == null ? "—" : n.toLocaleString("tr-TR");
}
function ProgramTable({ programs }: { programs: Program[] }) {
if (programs.length === 0) {
return (
<p className="rounded-xl border border-dashed border-slate-300 p-6 text-center text-sm text-slate-500">
Bu dilimde uygun program bulunamadı.
</p>
);
}
return (
<div className="overflow-x-auto rounded-2xl border border-slate-200 bg-white">
<Table>
<TableHeader>
<TableRow className="bg-slate-50">
<TableHead>Program</TableHead>
<TableHead>Üniversite</TableHead>
<TableHead className="text-right">2025 taban sırası</TableHead>
<TableHead className="text-right">2024</TableHead>
<TableHead>Trend</TableHead>
<TableHead className="text-right">Kontenjan</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{programs.map((p) => (
<TableRow key={p.id}>
<TableCell className="font-medium">
{p.isim}
{p.fakulte ? (
<span className="block text-xs text-slate-500">
{p.fakulte}
</span>
) : null}
</TableCell>
<TableCell>
{p.universite}
<span className="block text-xs text-slate-500">
{p.il}
{p.unitur ? ` · ${p.unitur.toLocaleLowerCase("tr-TR")}` : ""}
</span>
</TableCell>
<TableCell className="text-right font-medium tabular-nums">
{fmt(p.sira2025)}
</TableCell>
<TableCell className="text-right tabular-nums text-slate-500">
{fmt(p.sira2024)}
</TableCell>
<TableCell>
<TrendIcon p={p} />
</TableCell>
<TableCell className="text-right tabular-nums">
{fmt(p.kontenjan2025)}
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
);
}
export default async function SonucPage({
searchParams,
}: {
searchParams: Promise<{ sira?: string; tur?: string }>;
}) {
const params = await searchParams;
const sira = Number.parseInt(params.sira ?? "", 10);
const turKey: PuanTuruKey =
params.tur && params.tur in PUAN_TURLERI
? (params.tur as PuanTuruKey)
: "say";
if (!Number.isFinite(sira) || sira < 1 || sira > 4_000_000) {
return (
<main className="mx-auto flex max-w-2xl flex-col items-center px-4 py-24 text-center">
<h1 className="font-heading text-2xl font-bold">
Geçerli bir sıralama gerekli
</h1>
<p className="mt-3 text-slate-600">
Sonuçları görebilmek için ana sayfadan YKS başarı sıralamanı gir.
</p>
<Button asChild className="mt-6 cursor-pointer">
<Link href="/">
<ArrowLeft className="size-4" aria-hidden />
Ana sayfaya dön
</Link>
</Button>
</main>
);
}
const results = searchByRank(sira, turKey);
const sections = [
{
key: "hayal",
icon: Rocket,
title: "Hayal",
tone: "text-orange-600",
description:
"Geçen yıl tabanı senin sıralamanın üzerinde kapanan programlar. Şansın düşük ama listenin başında birkaç tane bulunmalı.",
programs: results.hayal,
},
{
key: "dengeli",
icon: Scale,
title: "Dengeli",
tone: "text-primary",
description:
"Tabanı sıralamanın hemen civarında. Listenin bel kemiği bu dilimden kurulur.",
programs: results.dengeli,
},
{
key: "garanti",
icon: ShieldCheck,
title: "Garanti",
tone: "text-emerald-600",
description:
"Tabanı sıralamanın belirgin altında. Boşta kalmamak için listenin sonunda mutlaka yer almalı.",
programs: results.garanti,
},
] as const;
return (
<div className="min-h-screen bg-slate-50 text-slate-900">
<header className="sticky top-4 z-50 mx-4">
<div className="mx-auto flex h-14 max-w-6xl items-center justify-between rounded-2xl border border-slate-200 bg-white/80 px-5 shadow-sm backdrop-blur">
<Link
href="/"
className="flex items-center gap-2 font-heading text-lg font-bold"
>
<Sparkles className="size-5 text-primary" aria-hidden />
TercihAI
</Link>
<Button asChild size="sm" variant="outline" className="cursor-pointer">
<Link href="/">
<ArrowLeft className="size-4" aria-hidden />
Yeni arama
</Link>
</Button>
</div>
</header>
<main className="mx-auto max-w-6xl px-4 py-12">
<Badge variant="secondary" className="bg-primary/10 text-primary">
Ücretsiz ön izleme
</Badge>
<h1 className="mt-4 font-heading text-3xl font-bold sm:text-4xl">
{sira.toLocaleString("tr-TR")}. sıradaki bir aday için görünüm
</h1>
<p className="mt-2 max-w-2xl text-slate-600">
2025 YÖK Atlas taban sıralamalarına göre üç dilime ayrılmış öneriler.
Taban sıralamaları her yıl değişir; bu liste garanti değil, başlangıç
noktasıdır.
</p>
{/* Puan türü seçimi */}
<nav className="mt-6 flex flex-wrap gap-2" aria-label="Puan türü">
{(Object.keys(PUAN_TURLERI) as PuanTuruKey[]).map((key) => (
<Button
key={key}
asChild
size="sm"
variant={key === turKey ? "default" : "outline"}
className="cursor-pointer"
>
<Link href={`/sonuc?sira=${sira}&tur=${key}`}>
{TUR_LABELS[key]}
</Link>
</Button>
))}
</nav>
<div className="mt-10 space-y-12">
{sections.map((s) => (
<section key={s.key} aria-labelledby={`baslik-${s.key}`}>
<div className="mb-4 flex items-start gap-3">
<s.icon className={`mt-1 size-6 ${s.tone}`} aria-hidden />
<div>
<h2
id={`baslik-${s.key}`}
className="font-heading text-2xl font-bold"
>
{s.title}
<span className="ml-2 text-base font-normal text-slate-500">
{s.programs.length} program
</span>
</h2>
<p className="text-sm text-slate-600">{s.description}</p>
</div>
</div>
<ProgramTable programs={s.programs} />
</section>
))}
</div>
{/* Ücretli katman CTA */}
<section className="mt-16 rounded-2xl bg-primary p-8 text-center text-white sm:p-12">
<h2 className="font-heading text-2xl font-bold sm:text-3xl">
Bu listeyi 24 tercihlik plana çevirelim mi?
</h2>
<p className="mx-auto mt-3 max-w-xl text-white/90">
Yapay zekâ danışman; şehir, bölüm ve kariyer hedeflerine göre bu
dilimlerden gerekçeli, dengeli bir tercih listesi kurar. Çok
yakında.
</p>
<Button
size="lg"
className="mt-6 h-12 cursor-pointer bg-orange-500 px-8 text-white transition-colors duration-200 hover:bg-orange-600"
disabled
>
AI danışman yakında
</Button>
</section>
<p className="mt-10 text-center text-xs leading-relaxed text-slate-500">
Veriler resmî YÖK Atlas kaynağından derlenmiştir. TercihAI bir karar
destek aracıdır; yerleşme garantisi vermez, tercih sorumluluğu adaya
aittir.
</p>
</main>
</div>
);
}