Compare commits

..

8 Commits

Author SHA1 Message Date
bilalgursen
e3519a50cc [Bilal] content(tadimlik): paragraf maskenin gerçeğini söyler
Buton "24 satırın tamamı" iddiasından arındırılmıştı (2373a9a) ama iki
eleman yukarıdaki paragraf aynı vaadi sürdürüyordu: "24 tercihlik
listenin her satırı bu formatta kurulur". Maske girişsiz/paketsiz
kullanıcıya 3 satır açıyor (rapor-maske.ts:12 ACIK_SATIR), kalan 21'in
gerekçe/risk/trend metni maskeli geliyor.

Cümleye "ilk üçü hemen açılır" eklendi — sayı yazıldı, bulanıklaştırma
ve garanti dili yok. "3" elle yazıldığı için (ACIK_SATIR `server-only`,
bu dosya "use client") tek kaynak bağı koda yorum olarak iliştirildi.

Bulgu: 36-tasarimci-g1-ve-navbar.md B2. Metin yönü Bilal'in.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 23:21:31 +03:00
bilalgursen
e9126650c3 fix(sonuc): harita tablonun altına alınır — hidrasyon kayması kapanır
Liste store'unun sunucu snapshot'ı her zaman boş liste döner
(liste-store.ts:86), kayıtlı manuel liste ancak hidrasyondan sonra
okunur. Harita sayfanın tepesindeyken kayıtlı listesi olan dönüş
ziyaretçisinde ~368 px yüksekliğinde sonradan beliriyor ve tadımlık ile
tabloyu aşağı itiyordu; main'in ilk çocuğu olduğu için impact fraction
~1, CLS eşiğinin çok üstü. Silinen placeholder bunu yeri sunucuda
rezerve ederek engelliyordu.

Harita tablonun altına alındı: sonradan belirdiğinde yalnızca sihirbaz
ile dipnotu iter, ikisi de katlanın altında. Bilgi mimarisi olarak da
doğru sıra — harita, tablodan kurulan listenin görselleştirmesi.

Yeni sıra: tadımlık → tablo → harita → sihirbaz → dipnot.
Sıraya bağlı stiller yeni sıraya göre doğrulandı: tadımlığın
first:mt-0'ı artık her durumda geçerli (üstünde kardeş kalmadı), harita
kendi mt-16'sını alıyor (tablo section'ı ve sihirbaz kartlarıyla aynı
ritim — eskiden üst boşluğu main'in py-12'sinden geliyordu).

Bulgu: 36-tasarimci-g1-ve-navbar.md B1.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 23:20:34 +03:00
bilalgursen
d2e11908ba [Bilal] content(tadimlik): CTA'nın yeni vaadi
ÖNERİ — tek başına düşürülebilir (git reset --hard HEAD~1). Bir önceki
commit yalnız yanlış iddiayı kaldırdı ve kendi başına tutarlı; bu commit
CTA'ya yeni bir cümle öneriyor, karar Bilal'in.

Buton: "Giriş yap ve listeni oluştur" -> "Kendi listemi oluştur"
Alt satır: "· Giriş sonrası liste hemen üretilir" eklenir.

Sayı YOK ve bilerek: ACIK_SATIR (rapor-maske.ts, server-only) ve
DENEME_KREDISI (credits.ts, appdb import ediyor) bu "use client" dosyaya
import edilemez; elle "3"/"5" yazmak tek kaynak kuralını bozar.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 02:21:15 +03:00
bilalgursen
2373a9a526 content(tadimlik): teslim edilmeyen "24 satırın tamamı" iddiası kalkar
Maske girişsiz kullanıcıya 24 satırın tamamını değil yalnız ilk birkaçının
analizini açıyor (rapor-maske.ts); "5 deneme kredin hazır" da butonun
götürdüğü yerin sözü değil. Buton metni yeni vaat eklemeden yanlış olanı
bırakıyor: "Giriş yap ve listeni oluştur". Kanonik sağ ok yerinde duruyor.

Üstteki "Bu, 24 satırlık Yapay Zeka listenden yalnızca bir örnek." cümlesi
doğru olduğu için değişmedi — üretilen liste gerçekten 24 satır.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 02:20:57 +03:00
bilalgursen
72ef3b8669 fix(tadimlik): CTA girişten dönüşte üretimi başlatır
Tadımlık CTA'sının giriş callback'i sonucHref() yerine
listemUretHref("tadimlik") — kullanıcı /sonuc'a geri bırakılmak yerine
üretimin koştuğu /listem'e iner, liste ek tık olmadan başlar.
"tadimlik" IZINLI_KAYNAKLAR'a eklendi, yoksa ölçüm "bilinmiyor"a düşerdi.

Ayrıca tadimlik_goruntulendi'nin üstüne seri kırılması notu: olay bugüne
kadar sayfanın ~2.200 px altında yaşıyordu, bundan sonra ilk ekranda —
iki dönem karşılaştırılamaz.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 02:18:45 +03:00
bilalgursen
5311459073 refactor(sihirbaz): listemUretHref tek kaynağa taşınır
Yerel fonksiyon sihirbaz-sabitler.ts'e (sonucHref'in yanına) taşındı ve
export edildi; `uret=1` parametre sırası sözleşmesinin yorumu da onunla
gitti. Davranış birebir aynı — tadımlık CTA'sı da aynı hedefi kullanacağı
için literal kopyanın dördüncü kez çoğalması engellendi.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 02:18:22 +03:00
bilalgursen
552d5ce5f1 fix(sonuc): tadımlık tablonun üstüne alınır
Yeni sıra: ManuelHarita -> TadimlikBolumu -> SonucProgramTablosu. Tadımlık
ödeme öncesi gösterebildiğimiz tek gerçek önizleme; tablonun ~1.500 px
altında değil ilk ekranda durmalı. Props ve bileşen içleri değişmedi.

Tadımlık <section>'ına first:mt-0: harita boş durumda null döndüğü için bu
bölüm main'in ilk DOM çocuğu olabiliyor, main'in py-12'si yeterli.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 02:17:57 +03:00
bilalgursen
daa232c3c1 fix(sonuc): boş harita ilk ekranı işgal etmez
Manuel liste boşken ManuelHarita placeholder yerine null döner. Boş gri
alan /sonuc'un ilk ekranını kaplıyordu; ödeme öncesi ilk ekranda durması
gereken şey tadımlık. İlk "+" ile liste dolunca harita kendiliğinden
belirir, il geometrisi yine ancak o anda iner.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 02:17:29 +03:00
6 changed files with 84 additions and 41 deletions

View File

@@ -97,15 +97,30 @@ export default function SonucPage({
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: önce Türkiye haritası, sonra liste. Manuel
liste store'una abone olduğundan tablodan "+" ile eklenenler
buraya canlı yansır. */}
<ManuelHarita />
{/* Sayfa hiyerarşisi: tadımlık → ham tablo → manuel listenin
haritası. Tadımlık, ödeme öncesi gösterebildiğimiz TEK gerçek
önizleme olduğu için ilk ekranda durur.
{/* Yapay Zeka'dan bağımsız ham tablo — haritanın hemen altında,
sayfanın ana gövdesi; puan türü navbar'daki sıralama
rozetinden değiştirilir. Sihirbaz seçimleri (il, devlet/
vakıf) profil çerezinden tabloya uygulanır. */}
Harita neden TABLONUN ALTINDA: liste store'unun sunucu
snapshot'ı her zaman boş liste döner (liste-store.ts), kayıtlı
liste ancak hidrasyondan sonra okunur. Harita yukarıda olsaydı
dönüş ziyaretçisinde sayfa açıldıktan saniyeler sonra ~368 px
yüksekliğinde belirip tadımlığı ve tabloyu aşağı itecekti
(impact fraction ~1, CLS eşiğinin çok üstü). Aşağıda belirince
yalnızca sihirbaz ile dipnotu iter, ikisi de katlanın altında.
Bilgi mimarisi olarak da doğru sıra: harita, tablodan kurulan
listenin görselleştirmesidir. Liste boşken hiç render edilmez
(bkz. manuel-harita.tsx); store'a abone olduğu için tablodan
"+" ile eklenenler buraya canlı yansır. */}
{/* 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} />
{/* Yapay Zeka'dan bağımsız ham tablo — sayfanın ana gövdesi;
puan türü navbar'daki sıralama rozetinden değiştirilir.
Sihirbaz seçimleri (il, devlet/vakıf) profil çerezinden
tabloya uygulanır. */}
<SonucProgramTablosu
sira={sira}
tur={turKey}
@@ -113,9 +128,7 @@ export default function SonucPage({
uniturGrubu={uniturGrubu}
/>
{/* 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} />
<ManuelHarita />
{/* Sihirbaz akışı (CTA + modal) — üretim ve liste /listem'de
yaşar, burada yalnızca funnel kartları render edilir */}

View File

@@ -64,23 +64,18 @@ export function ManuelHarita() {
};
}, [liste]);
// Liste boşken 197 KB'lık il geometrisi hiç inmesin: aynı oranlı
// placeholder alanı rezerve eder, ilk eklemede lazy harita yerine oturur.
if (liste.length === 0) {
return (
<div>
<div className="flex aspect-[1050/447] w-full items-center justify-center rounded-xl bg-slate-100/60">
<p className="max-w-xs px-4 text-center text-xs text-slate-500">
Tablodan <span aria-hidden>+</span> ile ekledikçe üniversiteler
haritada belirir.
</p>
</div>
</div>
);
}
// Liste boşken hiç yer tutulmaz: boş harita /sonuc'un ilk ekranını işgal
// ediyordu, oysa ödeme öncesi ilk ekranda durması gereken şey tadımlık.
// İlk "+" ile liste dolunca harita kendiliğinden belirir; 197 KB'lık il
// geometrisi yine ancak o anda iner (TercihHaritasiLazy zaten `dynamic`).
if (liste.length === 0) return null;
// mt-16: /sonuc'ta harita artık tablonun ALTINDA duruyor (bkz. sonuc/page.tsx)
// ve sayfanın bölüm ritmini paylaşıyor — tablo <section>'ı ve sihirbaz
// kartları da mt-16 kullanır. Eskiden main'in ilk çocuğu olduğu için üst
// boşluğu main'in py-12'sinden alıyordu, kendi margin'i yoktu.
return (
<div>
<div className="mt-16">
<TercihHaritasiLazy
pinler={pinler}
seciliIl={seciliIl}

View File

@@ -25,7 +25,7 @@ import { useGorunumOlayi } from "@/components/use-gorunum-olayi";
import { olay, siraKovasi } from "@/lib/analitik";
import { dilimdenRisk, riskHesapla } from "@/lib/risk";
import { turkishSlugify } from "@/lib/slug";
import { PROFIL_DEGISTI_EVENT, sonucHref } from "@/features/sihirbaz/sihirbaz-sabitler";
import { listemUretHref, PROFIL_DEGISTI_EVENT } from "@/features/sihirbaz/sihirbaz-sabitler";
import { tercihProfiliOku } from "@/features/sihirbaz/sihirbaz-profil";
import type { TadimlikSatir, TadimlikTip } from "@/lib/tadimlik-havuzu";
@@ -97,6 +97,13 @@ export function TadimlikSatiri({
return () => window.removeEventListener(PROFIL_DEGISTI_EVENT, guncelle);
}, [sira, tur, ilk]);
// SERİ KIRILMASI (22 Eyl 2026): bu olay bu değişiklikten önce /sonuc'un
// ~2.200 px altında, tablonun arkasında yaşıyordu; artık tadımlık ilk
// ekranda. Adet bir gecede 0'dan yukarı fırlayacak — kırık ölçüm değil,
// kazanılan payda. Öncesi ve sonrası KARŞILAŞTIRILAMAZ; anlamlı ölçüt
// bundan sonra "tadımlık CTA tıklaması / tadımlık görüntülenmesi" oranıdır.
// (Not analitik.ts'e değil buraya yazıldı: o dosya aynı gece başka bir
// dalın altında — CTO şartnamesi, çakışma kararı #1.)
const gorunumRef = useGorunumOlayi<HTMLElement>("tadimlik_goruntulendi", {
kova: siraKovasi(sira),
});
@@ -108,18 +115,32 @@ export function TadimlikSatiri({
const seri = raporSiraSerisi(p.siraGecmisi, p.ilkYil);
const taban = p.efektifSira ?? programEtkinSira(seri);
const devlet = p.unitur === "DEVLET";
const girisUrl = `/giris?callback=${encodeURIComponent(sonucHref())}`;
// Giriş dönüşü kullanıcıyı /sonuc'a geri bırakmaz: callback doğrudan
// üretimin koştuğu /listem'e iner, liste ek tık olmadan orada başlar.
const girisUrl = `/giris?callback=${encodeURIComponent(listemUretHref("tadimlik"))}`;
return (
<section ref={gorunumRef} className="mt-16 min-w-0">
// first:mt-0 — /sonuc'ta bu <section> main'in ilk DOM çocuğudur (harita
// artık tablonun altında, üstte hiçbir kardeş yok; JSX yorumları düğüm
// üretmez): 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">
<div className="flex flex-col items-center text-center">
<SectionEyebrow>Yapay zekâdan örnek satır</SectionEyebrow>
<h2 className="mt-4 font-heading text-2xl font-bold sm:text-3xl">
Yapay zekâ listendeki her satırı böyle anlatır
</h2>
{/* "ilk üçü" = rapor-maske.ts içindeki ACIK_SATIR (3). O dosya
`server-only`, bu dosya "use client" — sabit buraya import
edilemiyor, sayı elle yazıldı. ACIK_SATIR değişirse bu cümle de
aynı commit'te değişmeli. Cümle kasten "her satır bu formatta
kurulur"u sınırlıyor: maske girişsiz/paketsiz kullanıcıya 3 satır
açıyor, kalan 21'in gerekçe/risk/trend metni maskeli geliyor. */}
<p className="mx-auto mt-3 max-w-xl text-slate-600">
Sıralamana uygun bir programın gerekçesi, yerleşme riski ve taban
trendi — 24 tercihlik listenin her satırı bu formatta kurulur.
trendi — 24 tercihlik listenin her satırı bu formatta kurulur, ilk
üçü hemen açılır.
</p>
</div>
@@ -172,7 +193,8 @@ export function TadimlikSatiri({
<p className="text-sm text-slate-600">
Bu, 24 satırlık Yapay Zeka listenden yalnızca bir örnek.
<span className="block text-xs text-slate-500">
Kredi kartı gerekmez · Google ya da e-posta ile 30 saniyede
Kredi kartı gerekmez · Google ya da e-posta ile 30 saniyede ·
Giriş sonrası liste hemen üretilir
</span>
</p>
<Button
@@ -183,7 +205,10 @@ export function TadimlikSatiri({
href={girisUrl}
onClick={() => olay("giris_cta_tiklandi", { kaynak: "tadimlik" })}
>
24 satırın tamamı için giriş yap — 5 deneme kredin hazır
{/* Buton metni listenin tamamını vaat etmez: maske girişsiz
kullanıcıya 24 satırdan yalnız ilk birkaçının analizini
açıyor (rapor-maske.ts), gerisi teslim edilmiyor. */}
Kendi listemi oluştur
<ArrowRight className="size-4" aria-hidden />
</Link>
</Button>

View File

@@ -29,6 +29,8 @@ const IZINLI_KAYNAKLAR = new Set([
"giris_donusu",
"bos_durum",
"odeme_sonrasi",
// /sonuc'taki girişsiz tadımlık CTA'sı (tadimlik-satiri.tsx).
"tadimlik",
// /sonuc'ta ?sihirbaz=1 ile otomatik başlayan üretimin gerçek yüzeyleri.
// Eskiden hepsi "giris_donusu" sayılıyordu (güvenlik denetimi G3).
"hero",

View File

@@ -11,20 +11,14 @@ import { PixelField } from "@/components/pixel-decor";
import { useGorunumOlayi } from "@/components/use-gorunum-olayi";
import { olay, olayBirKez, siraKovasi } from "@/lib/analitik";
import type { SihirbazFacetleri } from "@/types/yokatlas";
import { SIHIRBAZ_AC_EVENT, SIHIRBAZ_STORAGE_KEY, sihirbazDogrula, type SihirbazSecimleri } from "@/features/sihirbaz/sihirbaz-sabitler";
import { listemUretHref, SIHIRBAZ_AC_EVENT, SIHIRBAZ_STORAGE_KEY, sihirbazDogrula, type SihirbazSecimleri } from "@/features/sihirbaz/sihirbaz-sabitler";
import { tercihProfiliYaz } from "@/features/sihirbaz/sihirbaz-profil";
import { SihirbazModal } from "./sihirbaz-modal";
// Üretim /listem'de koşar (bkz. listem/liste-uretici.tsx); sihirbaz
// tamamlanınca seçimler storage'a yazılıp kullanıcı buraya yönlendirilir.
// `kaynak` denemenin hangi yüzeyden başladığını ölçer (bkz.
// rapor/components/uretim-basladi-olayi.tsx — izinli değerler orada).
// `uret=1` HER ZAMAN ilk parametre kalmalı: giris-karti.tsx:31 funnel
// metnini `callbackURL.startsWith("/listem?uret=1")` ile seçiyor, sıra
// bozulursa girişteki "Seçimlerin hazır" metni kaybolur.
function listemUretHref(kaynak: string) {
return `/listem?uret=1&kaynak=${kaynak}`;
}
// Hedef URL'i `listemUretHref` kurar (sihirbaz-sabitler.ts) — `uret=1`
// parametre sırası sözleşmesi ve izinli `kaynak` değerleri orada yazılı.
// SSR'da false, client'ta true — storage'a ancak mount sonrası dokunuruz
const bosAbone = () => () => {};

View File

@@ -168,6 +168,20 @@ export function sonucHref(ekstra?: Record<string, string>): string {
return `/sonuc?${new URLSearchParams(girdiler)}`;
}
/**
* Üretimin koştuğu /listem URL'i. `kaynak` denemenin hangi yüzeyden
* başladığını ölçer (izinli değerler:
* rapor/components/uretim-basladi-olayi.tsx — yeni yüzeyi önce oraya ekle).
*
* `uret=1` HER ZAMAN ilk parametre kalmalı: giris-karti.tsx funnel metnini
* `callbackURL.startsWith("/listem?uret=1")` ile seçiyor, sıra bozulursa
* girişteki "Seçimlerin hazır" metni sessizce kaybolur. Bu sözleşmenin
* literal kopyası çoğalmasın diye tek kaynak burası.
*/
export function listemUretHref(kaynak: string): string {
return `/listem?uret=1&kaynak=${kaynak}`;
}
/** Prompt'a enjekte edilecek Türkçe özet. */
export function secimOzeti(secimler: SihirbazSecimleri): string {
const satirlar = [