);
}
diff --git a/src/features/liste/components/yurtdisi-haritalar-lazy.tsx b/src/features/liste/components/yurtdisi-haritalar-lazy.tsx
index fc69096..bf13c92 100644
--- a/src/features/liste/components/yurtdisi-haritalar-lazy.tsx
+++ b/src/features/liste/components/yurtdisi-haritalar-lazy.tsx
@@ -5,7 +5,14 @@
import dynamic from "next/dynamic";
+// loading: chunk inene kadar şeridin yüksekliğini rezerve eder. Türkiye
+// haritası /sonuc'un en üstüne çıktığı için bu şerit de katlamanın üstünde;
+// yer tutucusuz hâlinde yoktan belirip altındaki her şeyi iterdi.
+// h-26 (104 px) tek satırlık gerçek yüksekliktir: figür w-52 (208 px),
+// svg oranı 1050×447 → 88,6 px, figcaption text-xs → 16 px. Kap mt-3'ü
+// bileşenin kendi kökündekiyle aynı (yurtdisi-haritalar.tsx). Birden çok
+// ülke alt satıra kayarsa fark kalır — sunucu ülke sayısını bilemez.
export const YurtdisiHaritalarLazy = dynamic(
() => import("./yurtdisi-haritalar").then((m) => m.YurtdisiHaritalar),
- { ssr: false },
+ { ssr: false, loading: () =>
},
);
diff --git a/src/features/liste/liste-dogrula.ts b/src/features/liste/liste-dogrula.ts
index 65674ce..2396a66 100644
--- a/src/features/liste/liste-dogrula.ts
+++ b/src/features/liste/liste-dogrula.ts
@@ -6,6 +6,16 @@ import type { RiskSeviyesi } from "@/lib/risk";
export const MANUEL_LISTE_MAX = 24;
+/**
+ * Listenin sunucu aynası — yalnızca ELEMAN SAYISI taşır, hiçbir kişisel veri
+ * ya da program kimliği taşımaz. Liste localStorage'ta yaşadığı için sunucu
+ * snapshot'ı her zaman boştur; /sonuc bu çerezi okuyup haritaya hidrasyondan
+ * önce tam ölçülü yer ayırabilsin diye var (bkz. sonuc/page.tsx).
+ * Sabit burada durur çünkü bu dosya client store ile sunucu tarafının ortak
+ * zemini; store'un kendisi "use client".
+ */
+export const LISTE_SAYI_CEREZ_ADI = "kolaytercih.liste-sayi";
+
export type ManuelKaynak = "sonuc" | "bolum" | "universite";
export type ManuelTercih = {
diff --git a/src/features/liste/liste-store.ts b/src/features/liste/liste-store.ts
index 26167dc..5464c92 100644
--- a/src/features/liste/liste-store.ts
+++ b/src/features/liste/liste-store.ts
@@ -14,6 +14,7 @@ import {
} from "@/features/sihirbaz/sihirbaz-sabitler";
import { tercihProfiliOku } from "@/features/sihirbaz/sihirbaz-profil";
import {
+ LISTE_SAYI_CEREZ_ADI,
MANUEL_LISTE_MAX,
dogrula,
type ManuelKaynak,
@@ -39,10 +40,23 @@ function yayinla() {
for (const cb of dinleyiciler) cb();
}
+/**
+ * Sunucu aynası: listenin yalnızca UZUNLUĞU çereze yazılır (kişisel veri yok).
+ * /sonuc bunu okuyup haritaya hidrasyondan önce tam ölçülü yer ayırır; yoksa
+ * kayıtlı listesi olan ziyaretçide harita sonradan belirip sayfayı iterdi.
+ * Öznitelikler profilCerezineYaz ile birebir aynı (sihirbaz-profil.ts).
+ */
+function sayiCereziniYaz(n: number) {
+ if (typeof document === "undefined") return;
+ const guvenli = window.location.protocol === "https:" ? "; secure" : "";
+ document.cookie = `${LISTE_SAYI_CEREZ_ADI}=${n}; path=/; max-age=31536000; samesite=lax${guvenli}`;
+}
+
function kaydet() {
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify(liste));
} catch {}
+ sayiCereziniYaz(liste.length);
}
function yukle() {
@@ -52,6 +66,10 @@ function yukle() {
const ham = localStorage.getItem(STORAGE_KEY);
if (ham) liste = dogrula(JSON.parse(ham));
} catch {}
+ // Çerezi her açılışta gerçek uzunlukla tazele: yeni dağıtımdan sonra ilk
+ // ziyarette çerezi doğurur, localStorage silinmiş ama çerez kalmış durumda
+ // da 0 yazıp /sonuc'un boşuna yer ayırmasını engeller.
+ sayiCereziniYaz(liste.length);
// Profilsiz + ile eklenenlerin riski null'dır. Profil sonradan hangi yoldan
// yazılırsa yazılsın (kapı, hero/CTA formu, sihirbaz, giriş senkronu) riskler
// dolsun: açılışta bir kez, sonra her profil değişiminde.
@@ -62,6 +80,7 @@ function yukle() {
if (e.key !== STORAGE_KEY) return;
try {
liste = e.newValue ? dogrula(JSON.parse(e.newValue)) : [];
+ sayiCereziniYaz(liste.length);
yayinla();
} catch {}
});
@@ -87,6 +106,20 @@ export function useManuelListe(): ManuelTercih[] {
);
}
+/**
+ * Store'un client'ta hidre olup olmadığı: sunucu snapshot'ı false, hidrasyondan
+ * sonra true. `liste` ile aynı anda flip eder (ikisi de abone içindeki
+ * yukle()'den doğar), bu yüzden "boş liste" ile "henüz okunmadı" ayrımı
+ * gereken yerlerde tek render'da tutarlıdır — bkz. ManuelHarita.
+ */
+export function useListeHidreMi(): boolean {
+ return useSyncExternalStore(
+ abone,
+ () => yuklendi,
+ () => false,
+ );
+}
+
export function useCekmeceAcik(): boolean {
return useSyncExternalStore(
abone,
diff --git a/src/features/rapor/components/tadimlik-satiri.tsx b/src/features/rapor/components/tadimlik-satiri.tsx
index 7caa8ec..276b5b1 100644
--- a/src/features/rapor/components/tadimlik-satiri.tsx
+++ b/src/features/rapor/components/tadimlik-satiri.tsx
@@ -120,9 +120,11 @@ export function TadimlikSatiri({
const girisUrl = `/giris?callback=${encodeURIComponent(listemUretHref("tadimlik"))}`;
return (
- // first:mt-0 — /sonuc'ta bu
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.
+ // first:mt-0 — /sonuc'ta harita bu 'ı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.
// 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.