feat(v2): 9–10 Ekim çalışma ağacı — deneyim dili 1. dalga, PWA manifest, revizyon niyet modeli, listem/tadımlık bileşen denetimi

9–10 Ekim oturumlarının commit'lenmemiş işi tek parça alındı
(raporlar: docs/ekip-raporlari/2026-10-09, 2026-10-10):
- Landing deneyim dili 1. dalga: sayfa geçişi (ViewTransition), piksel morf/şerit,
  sorun sahneleri, adım akışı, sayan sayı, mobil menü, adım göstergesi, logoya tepki;
  DESIGN.md §11 ve AGENTS.md kuralları, tasarım denetimine yeni kurallar
- Tema: tema ikonu, D kısayolu, tema geçişi
- PWA: manifest.ts ve ikonlar
- Danışman: revizyon niyet modeli (OPENROUTER_NIYET_MODEL), sohbet geçmişi, soru API'si
- /listem ve /sonuc tadımlığı bileşen denetimi düzeltmeleri
- eslint: .venv-tts/ yok sayılır

tsc, eslint ve tasarım denetimi sıfır hata.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
bilalgursen
2026-10-11 00:34:29 +03:00
parent da0e9dff7a
commit aec8a2ad76
80 changed files with 4134 additions and 654 deletions

View File

@@ -10,23 +10,22 @@ import {
} from "lucide-react";
import Link from "next/link";
import { Suspense } from "react";
import { AdimAkisi } from "@/features/pazarlama/components/adim-akisi";
import { BolumGecisi } from "@/features/pazarlama/components/bolum-gecisi";
import { ComparisonCell } from "@/features/pazarlama/components/landing-parcalari";
import { HeroBaslik } from "@/features/pazarlama/components/hero-baslik";
import { KapanisCta } from "@/features/pazarlama/components/hero-focus-button";
import { HeroForm } from "@/features/sihirbaz/components/hero-form";
import { Parallax } from "@/components/parallax";
import { Reveal } from "@/features/pazarlama/components/reveal";
import { SayanSayi } from "@/features/pazarlama/components/sayan-sayi";
import { SorunSahnesi } from "@/features/pazarlama/components/sorun-sahneleri";
import { SosyalKanitBandi } from "@/features/pazarlama/components/sosyal-kanit";
import { TurkeyPixelMap } from "@/features/pazarlama/components/turkey-pixel-map";
import { SuzulenHaplar } from "@/features/pazarlama/components/suzulen-haplar";
import { VeriAkisi } from "@/features/pazarlama/components/veri-akisi";
import { katalogHaplari } from "@/features/pazarlama/landing-verisi";
import {
PixelDivider,
PixelRakam,
PixelSahne,
SectionEyebrow,
} from "@/components/pixel-decor";
import { SectionEyebrow } from "@/components/pixel-decor";
import { PikselCtaYuzeyi } from "@/components/piksel-cta-yuzeyi";
import { URUNLER } from "@/lib/credits";
import {
@@ -68,78 +67,57 @@ const TOPUP_FIYATI = (URUNLER.topup.amountKurus / 100).toLocaleString("tr-TR", {
maximumFractionDigits: 0,
});
// Sorun kartlarının piksel sahneleri (DESIGN.md §4.31): hero haritasıyla aynı
// ızgara dili — `#` doku, `-` silik, `o` turuncu vurgu. Her biri kartın
// söylediğini çizer: binlerce programın içinde tam 24 seçim; her yıl başka
// boyda çubuklar ve yalnız sonuncusu işaretli; basamak basamak tırmanan fiyat.
const SAHNE_IHTIMAL = [
"o#o#######o##o##o#######",
"####o###o#########o###o#",
"######o####o###o########",
"#o##o###################",
"######o##o####o####o##o#",
"#o##########o####o######",
".###..o#......####.#..o.",
"..#...###...##......#...",
];
const SAHNE_YILLAR = [
"........................",
".................##.....",
".........##......##.....",
".##......##......##.....",
".##......##..##..##..oo.",
".##..##..##..##..##..##.",
".##..##..##..##..##..##.",
".##..##..##..##..##..##.",
];
const SAHNE_FIYAT = [
".....................ooo",
"..................###---",
"...............###------",
"............###---------",
".........###------------",
"......###---------------",
"...###------------------",
"###---------------------",
];
// Bölüm ritmi (DESIGN.md §5.1; Bilal, 11 Eki 2026: "section'lar arası es yok,
// her şey çok sıkışık"). Bölümler arasında piksel şeridi durur (§4.38) ve
// çevresinde boşluk ister: şerit üst bölümün içeriğinden uzak, alt bölümün
// rozetine yakındır — bir bölümü bitirip sıradakine iner. Alt boşluk bu yüzden
// üsttekinden bir kademe büyük. Şerit geçiş çizgisinin iki yanına telefonda
// 56, sm'den itibaren 75 px taşar; kalan nefes telefonda 56/40, sm'de 85/53,
// lg'de 101/69 px (üst bölüme/alt bölüme).
const BOLUM_BOSLUGU = "pt-24 pb-28 sm:pt-32 sm:pb-40 lg:pt-36 lg:pb-44";
// Sorun kartlarının piksel sahneleri (DESIGN.md §4.31, §4.38): hero haritasıyla
// aynı ızgara dili. Her biri kartın söylediğini hareketle çizer: binlerce
// programın içinde ısınıp sönen tam 24 seçim; her yıl bir sola kayan, sağdan
// başka boyda gelen çubuklar; basamak basamak tırmanan fiyat. Izgaralar
// `sorun-sahneleri.tsx`te.
const problems = [
{
sahne: SAHNE_IHTIMAL,
sahne: "ihtimal",
title: "24 tercih, binlerce ihtimal",
description: `${PROGRAM_SAYISI_CUMLE} lisans ve önlisans programı arasından sana uyan 24 tanesini doğru sırayla dizmek, tablolarla boğuşarak günler alıyor.`,
},
{
sahne: SAHNE_YILLAR,
sahne: "yillar",
title: "Geçen yılın puanı yetmiyor",
description:
"Taban sıralamaları her yıl kayıyor; tek yıla bakmak riski gizler.",
},
{
sahne: SAHNE_FIYAT,
sahne: "fiyat",
title: "Danışman lüks haline geldi",
description:
"Armut'ta tercih danışmanlığı ilanları ortalama 1.000–3.000 TL (Eylül 2026). Ücretsiz robotların çoğu ise yalnızca liste filtreliyor; listeyi kurup gerekçesini yazmıyor.",
},
];
] as const;
const steps = [
{
step: 1,
title: "Sıralamanı ve hedeflerini anlat",
description:
no: 1,
baslik: "Sıralamanı ve hedeflerini anlat",
aciklama:
"YKS başarı sıralamanı gir; sıralamana uygun ilgi alanı, şehir ve üniversite tercihlerini birkaç adımda işaretle.",
},
{
step: 2,
title: `Son ${VERI_YIL_SAYISI} yılın resmî verisi taransın`,
description:
no: 2,
baslik: `Son ${VERI_YIL_SAYISI} yılın resmî verisi taransın`,
aciklama:
"YÖK Atlas'ın taban sıralaması trendleri, kontenjan değişimleri ve doluluk oranları sıralamana göre elensin; yapay zekâ yalnızca bu havuzdan seçer, dışına çıkamaz.",
},
{
step: 3,
title: "Dengeli 24'lük listeni al",
description:
no: 3,
baslik: "Dengeli 24'lük listeni al",
aciklama:
"Hayal, dengeli ve güvenli dilimlerine dağıtılmış, her satırı gerekçeli tercih listeni indir; aklına takılanı listen üzerinden yapay zekâya sor.",
},
] as const;
@@ -270,19 +248,23 @@ export default function Home() {
aria-hidden
// SVG altta 9 hücrelik serpinti payı taşıdığı için silüet merkezi
// yüksekliğin %37,1'inde; -1/2 yerine bu oranla hizalanır
className="pointer-events-none absolute left-1/2 top-[43%] -z-10 hidden -translate-x-1/2 -translate-y-[37.14%] sm:block"
// Telefonda harita gizlenmez (DESIGN.md §11.2-9): ekrandan taşacak
// kadar iri durur, başlığın üstündeki boşluğu doldurur ve serpinti
// başlığa doğru dökülür.
className="kt-hero-harita pointer-events-none absolute left-1/2 top-[27%] -z-10 -translate-x-1/2 -translate-y-[37.14%] sm:top-[43%]"
>
{/* Arka katman: harita scroll'un gerisinde kalır, derinlik verir */}
<Parallax fromStart strength={-52}>
{/* Harita orta ekranlarda nefes payı bırakır, ekranla birlikte
ölçeklenir ve çok geniş ekranda 1665px tavanına oturur;
mobilde tümüyle gizli. */}
<TurkeyPixelMap className="w-[min(1665px,82vw)] max-w-none text-primary" />
telefonda ekranın 1,7 katı (kenarlardan taşar, kırpılır). */}
<TurkeyPixelMap className="w-[170vw] max-w-none text-primary sm:w-[min(1665px,82vw)]" />
</Parallax>
</div>
<div
aria-hidden
className="pointer-events-none absolute inset-0 -z-10 bg-[radial-gradient(ellipse_58%_52%_at_50%_42%,color-mix(in_oklab,var(--color-slate-50)_90%,transparent)_25%,transparent_75%)]"
// Okunurluk perdesi: telefonda başlık daha aşağıda ve dar, perde ona göre
className="pointer-events-none absolute inset-0 -z-10 bg-[radial-gradient(ellipse_95%_38%_at_50%_62%,color-mix(in_oklab,var(--color-slate-50)_92%,transparent)_35%,transparent_80%)] sm:bg-[radial-gradient(ellipse_58%_52%_at_50%_42%,color-mix(in_oklab,var(--color-slate-50)_90%,transparent)_25%,transparent_75%)]"
/>
<Parallax
fromStart
@@ -292,22 +274,21 @@ export default function Home() {
<HeroBaslik />
<div
id="hero-form"
className="mt-6 flex w-full scroll-mt-28 justify-center"
className="kt-hero-gir mt-6 flex w-full scroll-mt-28 justify-center"
style={{ "--sira": 2 } as React.CSSProperties}
>
<HeroForm />
</div>
</Parallax>
</section>
{/* Hero → Problem geçişi: border yerine piksel ayraç */}
<div aria-hidden className="mx-auto w-56">
<Parallax strength={26}>
<PixelDivider seed={11} />
</Parallax>
</div>
{/* Hero → Problem geçişi: border ya da ayraç yerine piksel şeridi.
Haritanın serpintisinden başlar, kaydırdıkça turuncuya ısınarak
bölümden bölüme iner (DESIGN.md §4.38). */}
<BolumGecisi tohum={11} ust={6} ustTelefon={6} ustKisaTelefon={3} />
{/* Problem */}
<section className="py-20 sm:py-24">
<section className={BOLUM_BOSLUGU}>
<div className="mx-auto max-w-6xl px-4">
{/* Başlık gövdeden hafif hızlı akar — katman derinliği */}
<Parallax strength={16}>
@@ -322,13 +303,18 @@ export default function Home() {
</Reveal>
</Parallax>
{/* Sahne kartı (DESIGN.md §4.31): üstte hero'nun piksel diliyle
kartın söylediğini çizen sahne, altında başlık ve metin. */}
<Parallax strength={7} className="mt-12 grid gap-6 md:grid-cols-3">
kartın söylediğini çizen sahne, altında başlık ve metin.
Sahneler sırayla oynar; karta gelince o sahne hemen oynar
(`data-sahne-karti`, sorun-sahneleri.tsx). */}
<Parallax strength={7} className="mt-12 grid gap-6 sm:mt-16 md:grid-cols-3">
{problems.map((p, i) => (
<Reveal key={p.title} delay={i * 0.08} className="h-full">
<div className="flex h-full flex-col overflow-hidden rounded-2xl border border-slate-200/70 bg-card shadow-sm transition-[translate,border-color,box-shadow] duration-300 hover:-translate-y-1 hover:border-orange-500 hover:shadow-lg hover:shadow-orange-500/5">
<div
data-sahne-karti=""
className="flex h-full flex-col overflow-hidden rounded-2xl border border-slate-200/70 bg-card shadow-sm transition-[translate,border-color,box-shadow] duration-300 hover:-translate-y-1 motion-reduce:hover:translate-y-0 hover:border-orange-500 hover:shadow-lg hover:shadow-orange-500/5"
>
<div className="border-b border-slate-100 bg-slate-50 px-6 py-8">
<PixelSahne izgara={p.sahne} />
<SorunSahnesi tur={p.sahne} sira={i} />
</div>
<div className="p-6">
<h3 className="font-heading text-xl font-bold text-balance">
@@ -346,14 +332,10 @@ export default function Home() {
</section>
{/* Problem → Nasıl çalışır geçişi */}
<div aria-hidden className="mx-auto w-56">
<Parallax strength={26}>
<PixelDivider seed={17} />
</Parallax>
</div>
<BolumGecisi tohum={17} />
{/* How it works */}
<section id="nasil-calisir" className="scroll-mt-24 py-20 sm:py-24">
<section id="nasil-calisir" className={`scroll-mt-24 ${BOLUM_BOSLUGU}`}>
<div className="mx-auto max-w-6xl px-4">
<Parallax strength={16}>
<Reveal className="flex flex-col items-center gap-4 text-center">
@@ -362,7 +344,7 @@ export default function Home() {
{/* Kuralların şeffaf anlatımına köprü: /meraklisina */}
<Link
href="/meraklisina"
className="group inline-flex items-center gap-1.5 rounded-full bg-orange-500 px-3.5 py-1.5 font-bricolage text-xs font-semibold text-white transition-colors duration-200 hover:bg-orange-600"
className="group inline-flex min-h-8 items-center gap-1 rounded-full px-2 text-sm font-semibold text-slate-600 transition-colors duration-200 hover:text-orange-500"
>
Meraklısına
<ArrowUpRight
@@ -381,34 +363,10 @@ export default function Home() {
</Reveal>
</Parallax>
{/* Adımlar kartsız (DESIGN.md §4.32): numara hero'nun piksel
diliyle iri rakam, md'den itibaren rakamlar düz çizgiyle
birbirine bağlanır. */}
<Parallax strength={7} className="mt-12">
<ol className="grid gap-12 md:grid-cols-3 md:gap-8">
{steps.map((s, i) => (
<li key={s.step}>
<Reveal
delay={i * 0.08}
className="relative flex flex-col items-center text-center"
>
<PixelRakam rakam={s.step} className="h-24 w-auto" />
{i < steps.length - 1 ? (
<span
aria-hidden
className="absolute top-12 left-[calc(50%+4.5rem)] hidden h-0.5 w-[calc(100%-7rem)] -translate-y-1/2 rounded-full bg-slate-200 md:block"
/>
) : null}
<h3 className="mt-6 font-heading text-xl font-bold text-balance">
<span className="sr-only">{s.step}. adım: </span>
{s.title}
</h3>
<p className="mt-2 max-w-sm text-base leading-relaxed text-slate-600">
{s.description}
</p>
</Reveal>
</li>
))}
</ol>
diliyle iri rakam; her rakam sırayla adımının biçimine dönüşüp
geri toplanır, md'den itibaren aradaki düz çizgi dolar. */}
<Parallax strength={7} className="mt-12 sm:mt-16">
<AdimAkisi adimlar={steps} yilSayisi={VERI_YIL_SAYISI} />
</Parallax>
{/* Veri akışı + veriye dayalı güven + dürüstlük notu (DESIGN.md
@@ -416,10 +374,10 @@ export default function Home() {
ekranda solda dürüstlük notu (sola yaslı), ortada animasyon,
sağda (YÖK Atlas girdilerinin yanında) üç sayı (sağa yaslı).
lg altında hepsi ortalı; sıra: animasyon → sayılar → not. */}
<Parallax strength={10} className="mt-20">
<Parallax strength={10} className="mt-24 sm:mt-32">
<Reveal
delay={0.1}
className="grid items-center gap-12 lg:grid-cols-[minmax(0,1fr)_22rem_minmax(0,1fr)] lg:gap-8 xl:grid-cols-[minmax(0,1fr)_24rem_minmax(0,1fr)] xl:gap-12"
className="grid items-center gap-16 lg:grid-cols-[minmax(0,1fr)_22rem_minmax(0,1fr)] lg:gap-8 xl:grid-cols-[minmax(0,1fr)_24rem_minmax(0,1fr)] xl:gap-12"
>
<VeriAkisi className="lg:order-2" />
@@ -432,7 +390,7 @@ export default function Home() {
{stat.label}
</dt>
<dd className="order-1 font-heading text-4xl font-bold leading-none tracking-tight text-primary sm:text-5xl">
{stat.value}
<SayanSayi deger={stat.value} />
{stat.birim ? (
<span className="ml-1.5 font-sans text-base font-semibold tracking-normal text-slate-400">
{stat.birim}
@@ -463,14 +421,10 @@ export default function Home() {
</section>
{/* Nasıl çalışır → Karşılaştırma geçişi */}
<div aria-hidden className="mx-auto w-56">
<Parallax strength={26}>
<PixelDivider seed={23} />
</Parallax>
</div>
<BolumGecisi tohum={23} />
{/* Comparison */}
<section id="karsilastirma" className="scroll-mt-24 py-20 sm:py-24">
<section id="karsilastirma" className={`scroll-mt-24 ${BOLUM_BOSLUGU}`}>
<div className="mx-auto max-w-4xl px-4">
<Parallax strength={16}>
<Reveal className="flex flex-col items-center gap-4 text-center">
@@ -488,7 +442,7 @@ export default function Home() {
üstte yapışık durur (site başlığının hemen altında: telefonda 96 px,
sm'den itibaren 128 px — ölçüldü).
lg'de klasik dört sütunlu tablo. */}
<Parallax strength={7} className="mt-12">
<Parallax strength={7} className="mt-12 sm:mt-16">
<Reveal delay={0.08}>
<div className="rounded-2xl border border-slate-200/70 bg-card shadow-sm lg:hidden">
<div
@@ -502,8 +456,12 @@ export default function Home() {
</span>
</div>
<ul className="divide-y divide-slate-100">
{comparison.map((row) => (
<li key={row.feature} className="px-3 py-5">
{comparison.map((row, i) => (
<li
key={row.feature}
className="px-3 py-5"
style={{ "--tik-g": `${i * 70}ms` } as React.CSSProperties}
>
<p className="px-1 text-center text-base font-semibold leading-snug text-slate-900">
{row.feature}
</p>
@@ -551,10 +509,11 @@ export default function Home() {
</TableRow>
</TableHeader>
<TableBody>
{comparison.map((row) => (
{comparison.map((row, i) => (
<TableRow
key={row.feature}
className="hover:bg-transparent"
style={{ "--tik-g": `${i * 70}ms` } as React.CSSProperties}
>
<TableCell className="whitespace-normal py-4 pl-5 text-base font-semibold text-slate-900">
{row.feature}
@@ -582,14 +541,10 @@ export default function Home() {
</section>
{/* Karşılaştırma → Fiyat geçişi */}
<div aria-hidden className="mx-auto w-56">
<Parallax strength={26}>
<PixelDivider seed={29} />
</Parallax>
</div>
<BolumGecisi tohum={29} />
{/* Fiyat */}
<section id="fiyat" className="scroll-mt-24 py-20 sm:py-24">
<section id="fiyat" className={`scroll-mt-24 ${BOLUM_BOSLUGU}`}>
<div className="mx-auto max-w-4xl px-4">
<Parallax strength={16}>
<Reveal className="flex flex-col items-center gap-4 text-center">
@@ -610,7 +565,7 @@ export default function Home() {
<Parallax
strength={7}
className="mt-12 grid gap-6 sm:grid-cols-[1fr_auto]"
className="mt-12 grid gap-6 sm:mt-16 sm:grid-cols-[1fr_auto]"
>
<Reveal
delay={0.08}
@@ -648,10 +603,7 @@ export default function Home() {
<SosyalKanitBandi />
</Suspense>
<Button
asChild
className="h-12 w-full cursor-pointer bg-orange-500 px-8 text-white transition-colors duration-200 hover:bg-orange-600"
>
<Button asChild size="lg" variant="cta" className="w-full">
<Link href="/paket?kaynak=fiyat_paket">
{PAKET_FIYATI} TL — Paketi incele
<ArrowRight className="size-4" aria-hidden />
@@ -677,11 +629,7 @@ export default function Home() {
+{URUNLER.topup.credits} soru hakkı, {TOPUP_FIYATI} TL. Pakete
ek olarak, istediğin zaman.
</p>
<Button
asChild
variant="outline"
className="mt-4 h-11 w-full cursor-pointer"
>
<Button asChild variant="ikincil" className="mt-4 h-11 w-full">
<Link href="/paket?kaynak=fiyat_kredi">
+{URUNLER.topup.credits} kredi — {TOPUP_FIYATI} TL
<ArrowRight className="size-4" aria-hidden />
@@ -695,12 +643,8 @@ export default function Home() {
{/* Katalog: süzülen haplar (DESIGN.md §4.30) */}
{haplar.length >= 6 ? (
<>
<div aria-hidden className="mx-auto w-56">
<Parallax strength={26}>
<PixelDivider seed={41} />
</Parallax>
</div>
<section id="katalog" className="scroll-mt-24 py-20 sm:py-24">
<BolumGecisi tohum={41} />
<section id="katalog" className={`scroll-mt-24 ${BOLUM_BOSLUGU}`}>
<div className="mx-auto max-w-6xl px-4">
<Parallax strength={16}>
<Reveal className="flex flex-col items-center gap-4 text-center">
@@ -719,8 +663,7 @@ export default function Home() {
<Button
asChild
size="lg"
variant="outline"
className="cursor-pointer px-6 transition-[background-color,border-color,color,transform] duration-150 hover:border-orange-500 hover:bg-orange-50/60 hover:text-orange-500 active:scale-[0.97]"
variant="ikincil"
>
<Link href="/bolumler">
Tüm bölümleri gör
@@ -734,14 +677,10 @@ export default function Home() {
) : null}
{/* Fiyat → SSS geçişi */}
<div aria-hidden className="mx-auto w-56">
<Parallax strength={26}>
<PixelDivider seed={31} />
</Parallax>
</div>
<BolumGecisi tohum={31} />
{/* FAQ */}
<section id="sss" className="scroll-mt-24 py-20 sm:py-24">
<section id="sss" className={`scroll-mt-24 ${BOLUM_BOSLUGU}`}>
<div className="mx-auto max-w-3xl px-4">
<Parallax strength={16}>
<Reveal className="flex flex-col items-center gap-4 text-center">
@@ -756,7 +695,7 @@ export default function Home() {
<Accordion
type="single"
collapsible
className="mt-10 rounded-2xl border border-slate-200/70 bg-card px-6 shadow-sm"
className="mt-12 rounded-2xl border border-slate-200/70 bg-card px-6 shadow-sm sm:mt-16"
>
{faqs.map((faq) => (
<AccordionItem
@@ -779,7 +718,7 @@ export default function Home() {
</section>
{/* Final CTA — tam genişlik bant yerine yüzen, köşeleri yumuşak panel */}
<section className="px-4 pb-24 pt-6 sm:px-6">
<section className="px-4 pb-24 pt-6 sm:px-6 sm:pb-32">
<Parallax strength={12} className="mx-auto max-w-6xl">
<Reveal>
{/* Kapanış yüzeyi (DESIGN.md §10.8): koyu kart + 24'lük