fix(sonuc,liste): tadımlık listesi olanda tablonun altına iner, çekmecede risk etiketi metinle

- /sonuc: kayıtlı listesi olan ziyaretçide harita ile ekleme tablosu arasına
  tadımlık CTA'sı girmez, tablonun altına iner (karar sunucudaki liste sayısı
  çerezinden, hidrasyonda zıplamaz)
- Liste çekmecesi: risk noktası tek başına değil RISK_ETIKET metniyle
- Sıralama formu: kompakt başlık düzeni
This commit is contained in:
bilalgursen
2026-09-24 00:01:40 +03:00
parent ec002d7cfc
commit 22accb1241
4 changed files with 104 additions and 52 deletions

View File

@@ -106,14 +106,19 @@ export default function SonucPage({
);
}
const tadimlik = <TadimlikBolumu sira={sira} tur={turKey} />;
return (
<div className="min-h-screen bg-slate-50 text-slate-900">
<main className="mx-auto max-w-4xl px-4 py-12">
{/* Sayfa hiyerarşisi: manuel listenin haritası → tadımlık → ham
tablo. İki farklı ziyaretçi, iki farklı ilk ekran:
{/* Sayfa hiyerarşisi: manuel listenin haritası → ham tablo →
tadımlık (listesi yoksa tadımlık → tablo). İki farklı
ziyaretçi, iki farklı ilk ekran:
• Listesi OLAN ziyaretçi ekranda önce kendi kurduğu listeyi
görür — o kişi "bu ürün ne?" aşamasını geçmiştir.
görür — o kişi "bu ürün ne?" aşamasını geçmiştir. Haritanın
hemen altında ekleme alanı (tablo) durur; tadımlık CTA'sı
ikisinin arasına girmez, tablonun altına iner.
• Listesi OLMAYAN ziyaretçide harita DOM'a hiç girmez
(manuel-harita.tsx), sıra bugünküyle birebir aynı kalır:
tadımlık → tablo. Tadımlık, ödeme öncesi gösterebildiğimiz
@@ -131,8 +136,12 @@ export default function SonucPage({
<ManuelHarita yerAyir={haritayaYerAyir} />
{/* Girişsiz tadımlık: gerçek rapor satırı formatında tek örnek.
Havuzda içerik yoksa bileşen hiç render edilmez. */}
<TadimlikBolumu sira={sira} tur={turKey} />
Havuzda içerik yoksa bileşen hiç render edilmez. Listesi
OLAN ziyaretçide harita ile tablo (+ ile ekleme alanı)
arasına CTA girmesin diye tadımlık tablonun altına iner;
karar sunucudaki liste sayısı çerezinden verilir, böylece
hidrasyonda yer değiştirip zıplamaz. */}
{haritayaYerAyir ? null : tadimlik}
{/* Yapay Zeka'dan bağımsız ham tablo — sayfanın ana gövdesi;
puan türü navbar'daki sıralama rozetinden değiştirilir.
@@ -145,6 +154,8 @@ export default function SonucPage({
uniturGrubu={uniturGrubu}
/>
{haritayaYerAyir ? tadimlik : null}
{/* Sihirbaz akışı (CTA + modal) — üretim ve liste /listem'de
yaşar, burada yalnızca funnel kartları render edilir */}
<SonucSihirbazi

View File

@@ -422,10 +422,13 @@ function CekmeceSatiri({
</span>
) : null}
{tercih.risk ? (
<span
className={`size-2.5 shrink-0 rounded-full ${RISK_NOKTA[tercih.risk]}`}
aria-label={RISK_ETIKET[tercih.risk]}
/>
<span className="inline-flex shrink-0 items-center gap-1.5 text-xs font-medium">
<span
className={`size-2.5 shrink-0 rounded-full ${RISK_NOKTA[tercih.risk]}`}
aria-hidden
/>
{RISK_ETIKET[tercih.risk]}
</span>
) : null}
<button
type="button"

View File

@@ -120,11 +120,11 @@ export function TadimlikSatiri({
const girisUrl = `/giris?callback=${encodeURIComponent(listemUretHref("tadimlik"))}`;
return (
// first:mt-0 — /sonuc'ta harita bu <section>'ın ÜSTÜNDE durur ama yalnızca
// kayıtlı listesi olan ziyaretçide DOM'a girer (manuel-harita.tsx). Liste
// yoksa tadımlık main'in ilk DOM çocuğudur (JSX yorumları düğüm üretmez) ve
// main'in py-12'si tek başına yeterli üst boşluğu verir; harita varsa
// tadımlık ikinci çocuk olur ve bölüm ritmini veren mt-16'yı alır.
// first:mt-0 — kayıtlı listesi olan ziyaretçide /sonuc tadımlığı haritanın
// ve tablonun ALTINA koyar (sonuc/page.tsx), orada bölüm ritmini veren
// mt-16'yı alır. Liste yoksa tadımlık main'in ilk DOM çocuğudur (JSX
// yorumları düğüm üretmez) ve main'in py-12'si tek başına yeterli üst
// boşluğu verir.
// Tadımlığın render edilmediği durumlarda (girişli kullanıcı, boş havuz)
// bu bileşen hiç dönmediği için kural ilk gerçek bölüme geçer.
<section ref={gorunumRef} className="mt-16 min-w-0 first:mt-0">

View File

@@ -20,7 +20,7 @@ import {
DrawerDescription,
DrawerTitle,
} from "@/components/ui/drawer";
import { SectionEyebrow } from "@/components/pixel-decor";
import { PagePixelDivider, SectionEyebrow } from "@/components/pixel-decor";
import { useGorunumOlayi } from "@/components/use-gorunum-olayi";
import {
SihirbazAdimlarLazy,
@@ -51,6 +51,31 @@ const KANITLAR = [
{ ikon: Database, etiket: "YÖK Atlas verisi" },
] as const;
// Kayıtlı profilli CTA'lar /listem boş durumunun dilini taşır (bkz.
// listem-bos-cta.tsx): büyük turuncu buton, piksel ayraç. Sınıf seti tek yerde.
const PROFILLI_CTA_SINIF =
"h-12 cursor-pointer bg-orange-500 px-6 text-white transition-colors duration-200 hover:bg-orange-600";
function turEtiketi(tur: string) {
return PUAN_TURLERI_SECENEK.find((t) => t.deger === tur)?.etiket ?? tur.toUpperCase();
}
/** "Sayısal · 100.000. sıra" — kayıtlı profilin kısa özeti. */
function KayitliSira({ sira, tur }: { sira: number; tur: string }) {
return (
<span className="inline-flex items-center gap-1.5 text-sm text-slate-600">
<Trophy className="size-4 shrink-0 text-primary" aria-hidden />
<span>
Sıralaman kayıtlı:{" "}
<strong className="font-semibold tabular-nums text-slate-900">
{sira.toLocaleString("tr-TR")}.
</strong>{" "}
sıra · {turEtiketi(tur)}
</span>
</span>
);
}
/**
* Programatik SEO sayfalarındaki funnel girişi. Statik/SSR çıktı her zaman
* profilsiz hâldir (sayfalar prerender edilmeye devam eder); hydration
@@ -344,42 +369,53 @@ export function CtaSiraForm({
if (kompakt) {
// CLS: kayıtlı profil hydration SONRASI bulunur. Satır tablonun üstünde
// olduğundan yüksekliği o anda değişmemeli — form yerinde (görünmez ve
// inert) kalır, profilli kısayol aynı ızgara hücresine bindirilir; hücrenin
// yüksekliğini her iki durumda da form belirler.
// inert) kalır, profilli kısayol (başlığıyla birlikte) aynı ızgara hücresine
// bindirilir; hücrenin yüksekliğini her iki durumda da başlık + form belirler.
// Profilli içerik bu yüksekliği aşmayacak kadar kısa tutulmalı.
const kompaktBaslik = baslik ?? "Sıralaman bu tabanların neresinde?";
return (
<section
aria-labelledby={baslikId}
className="rounded-2xl border border-slate-200 bg-white p-4 sm:p-5"
className={cn(
"rounded-2xl border p-4 transition-colors duration-200 sm:p-5",
profil ? "border-primary/20 bg-primary/5" : "border-slate-200 bg-white",
)}
>
<p className="text-sm leading-6 text-slate-600">
<strong
id={baslikId}
className="font-heading text-base font-bold text-slate-900"
>
{baslik ?? "Sıralaman bu tabanların neresinde?"}
</strong>{" "}
Ücretsiz, üyelik gerekmez.
</p>
<div className="mt-3 grid">
<div className="grid">
<div
ref={profil ? undefined : formRef}
inert={profil ? true : undefined}
className={cn("col-start-1 row-start-1", profil && "invisible")}
>
{siraFormu}
<p className="text-sm leading-6 text-slate-600">
<strong
id={profil ? undefined : baslikId}
className="font-heading text-base font-bold text-slate-900"
>
{kompaktBaslik}
</strong>{" "}
Ücretsiz, üyelik gerekmez.
</p>
<div className="mt-3">{siraFormu}</div>
</div>
{profil ? (
<div className="col-start-1 row-start-1 flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
<p className="text-sm leading-6 text-slate-600">
Sıralaman kayıtlı ({profil.sira.toLocaleString("tr-TR")}. sıra).
</p>
<Link
href={sonucHref()}
className="inline-flex h-11 items-center justify-center gap-2 rounded-full bg-orange-500 px-6 text-sm font-semibold text-white transition-colors duration-200 hover:bg-orange-600"
>
Sıralamana uygun programları gör
<ArrowRight className="size-4" aria-hidden />
</Link>
<div className="col-start-1 row-start-1 flex flex-col items-center justify-center gap-3 text-center sm:flex-row sm:justify-between sm:gap-6 sm:text-left">
<div className="flex min-w-0 flex-col items-center gap-1 sm:items-start">
<p
id={baslikId}
className="font-heading text-base font-bold leading-6 text-slate-900"
>
{kompaktBaslik}
</p>
<KayitliSira sira={profil.sira} tur={profil.tur} />
</div>
<PagePixelDivider seed={67} className="w-24 sm:hidden" />
<Button asChild size="lg" className={cn(PROFILLI_CTA_SINIF, "w-full sm:w-auto")}>
<Link href={sonucHref()}>
Sıralamana uygun programları gör
<ArrowRight className="size-4" aria-hidden />
</Link>
</Button>
</div>
) : null}
</div>
@@ -390,22 +426,24 @@ export function CtaSiraForm({
if (profil) {
return (
<section className="rounded-2xl border border-primary/20 bg-primary/5 p-6 sm:p-8">
<h2 className="font-heading text-xl font-bold text-slate-900">
<section className="flex flex-col items-center rounded-2xl border border-primary/20 bg-primary/5 px-6 py-8 text-center sm:px-10 sm:py-10">
<h2 className="font-heading text-xl font-bold text-balance text-slate-900 sm:text-2xl">
{baslik ?? "Sıralamana yakın programlar hangileri? Saniyede gör."}
</h2>
<p className="mt-2 text-sm leading-6 text-slate-600">
Sıralaman kayıtlı ({profil.sira.toLocaleString("tr-TR")}. sıra) —
gerçek YÖK Atlas verisiyle hayal, dengeli ve güvenli dilimlerindeki
<div className="mt-3">
<KayitliSira sira={profil.sira} tur={profil.tur} />
</div>
<p className="mt-2 max-w-xl text-sm leading-6 text-slate-600">
Gerçek YÖK Atlas verisiyle hayal, dengeli ve güvenli dilimlerindeki
programların hazır.
</p>
<Link
href={sonucHref()}
className="mt-4 inline-flex h-12 items-center justify-center gap-2 rounded-full bg-orange-500 px-7 text-sm font-semibold text-white transition-colors duration-200 hover:bg-orange-600"
>
Sıralamana uygun programları gör
<ArrowRight className="size-4" aria-hidden />
</Link>
<PagePixelDivider seed={67} className="mt-6" />
<Button asChild size="lg" className={cn(PROFILLI_CTA_SINIF, "mt-6")}>
<Link href={sonucHref()}>
Sıralamana uygun programları gör
<ArrowRight className="size-4" aria-hidden />
</Link>
</Button>
</section>
);
}