Bilal (24 Eyl 2026): ana sayfa CTA'sında doku hiç görünmüyordu, bazı kartlarda çok azdı; sonra "çok fazla piksel, küçük olanlar hiç olmasın" ve "/sonuc'taki kartın arkasına da piksel koymuşsun; pikseller CTA'larda olacak sadece". - Kök neden: PixelMasonry sabit 420×120 çizimi kaplayacak şekilde ölçekleniyordu; yüksek kartlarda görünen kısım boş banda düşüyordu. Artık kabın gerçek ölçüsüne göre üretiliyor (ResizeObserver), yalnız kenar çerçevesinde, orta boş; `haric` verilen öğenin (24'lük piksel tablosu) çevresi her genişlikte boş. Sunucuda boş, ölçülünce belirir. - Küçük (tek hücre) kare yok: 4×4 makro hücrede 2–4 hücrelik kareler, iri olan seyrek ve silik. Ölçüm: kapanış kartı 1280 px'te 58 kare (tabloya binen 0), 375 px'te 27 kare. - Beyaz içerik kartlarından zemin geri alındı (tadımlık, /listem üretim ve kilit kartları, boş durum, sıralama-yok, meraklısına, paket kartları, kaydet bandı, katalog üstü kompakt satır, /sonuc "listen hazır"). Kalanlar: CtaBandi, PikselCtaYuzeyi, arama penceresi kartı. PikselZemin'in açık tonu kaldırıldı. DESIGN.md §10.8 güncellendi. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
114 lines
4.1 KiB
TypeScript
114 lines
4.1 KiB
TypeScript
"use client";
|
||
|
||
import { PikselZemin } from "@/components/piksel-zemin";
|
||
import { useState } from "react";
|
||
import { usePathname, useRouter, useSearchParams } from "next/navigation";
|
||
import {
|
||
ArrowRight,
|
||
ChartNoAxesCombined,
|
||
Database,
|
||
ListChecks,
|
||
} from "lucide-react";
|
||
import { Button } from "@/components/ui/button";
|
||
import { FUNNEL_METINLERI } from "@/lib/funnel-metinleri";
|
||
import { TypingAnimation } from "@/components/ui/typing-animation";
|
||
import { SIHIRBAZ_AC_EVENT, sonucHref } from "@/features/sihirbaz/sihirbaz-sabitler";
|
||
import {
|
||
profilToplamaAc,
|
||
useTercihProfili,
|
||
} from "@/features/liste/hooks/use-tercih-profili";
|
||
|
||
/** Fisher-Yates ile kopya üzerinde karıştırır; her açılışta farklı sıra. */
|
||
function karistir<T>(dizi: readonly T[]): T[] {
|
||
const kopya = [...dizi];
|
||
for (let i = kopya.length - 1; i > 0; i--) {
|
||
const j = Math.floor(Math.random() * (i + 1));
|
||
[kopya[i], kopya[j]] = [kopya[j], kopya[i]];
|
||
}
|
||
return kopya;
|
||
}
|
||
|
||
const KANITLAR = [
|
||
{ ikon: ListChecks, etiket: "24 tercih" },
|
||
{ ikon: ChartNoAxesCombined, etiket: "Risk analizi" },
|
||
{ ikon: Database, etiket: "YÖK Atlas verisi" },
|
||
];
|
||
|
||
/**
|
||
* Arama modalının açılış ekranında yaşayan satın alım funnel kartı.
|
||
* Rastgele sırayla yazılıp silinen funnel metinleriyle adayı sihirbaza
|
||
* iter: /sonuc üzerindeyse oradaki orkestratörü olayla açar, başka
|
||
* sayfadaysa profile göre /sonuc'a (sihirbaz açık) yönlendirir ya da
|
||
* sıralama kapısını açar. Modal yalnızca açıkken mount olduğundan
|
||
* karıştırma her açılışta yenilenir ve hydration sorunu yaratmaz.
|
||
*/
|
||
export function AramaFunnelKarti({ onSec }: { onSec: () => void }) {
|
||
const router = useRouter();
|
||
const pathname = usePathname();
|
||
const searchParams = useSearchParams();
|
||
const profil = useTercihProfili();
|
||
const [metinler] = useState(() => karistir(FUNNEL_METINLERI));
|
||
|
||
function sihirbazaGit() {
|
||
onSec();
|
||
// Orkestratör (SihirbazBolumu) yalnızca /sonuc tabloyu render ederken
|
||
// monte: profil çerezi (client'ta aynası localStorage profili) ya da eski
|
||
// linklerdeki ?sira= geçerliyken. Profilsiz /sonuc'ta (SiraGerekli
|
||
// görünümü) olay boşa gider — kapıya düş.
|
||
if (pathname === "/sonuc" && (profil || searchParams.get("sira"))) {
|
||
window.dispatchEvent(new Event(SIHIRBAZ_AC_EVENT));
|
||
return;
|
||
}
|
||
if (profil) {
|
||
router.push(sonucHref({ sihirbaz: "1", kaynak: "arama" }));
|
||
return;
|
||
}
|
||
// Yapay Zeka niyetli CTA: kapı tamamlanınca /sonuc'ta üretim akışı devam
|
||
// etmeli ("sonuc" hedefi ücretsiz tabloda bırakıyor, liste hiç kurulmuyordu).
|
||
profilToplamaAc("sihirbaz");
|
||
}
|
||
|
||
return (
|
||
<section
|
||
aria-label="Tercih listeni kur"
|
||
className="relative isolate mt-5 overflow-hidden rounded-2xl border border-blue-400/20 bg-slate-950 px-4 py-5 text-white sm:px-5"
|
||
>
|
||
<PikselZemin seed={61} />
|
||
<div className="grid items-center gap-4 sm:grid-cols-[minmax(0,1fr)_auto] sm:gap-5">
|
||
<div className="min-w-0">
|
||
<TypingAnimation
|
||
as="h3"
|
||
words={metinler}
|
||
loop
|
||
typeSpeed={38}
|
||
deleteSpeed={18}
|
||
pauseDelay={2600}
|
||
className="min-h-[2.75em] font-heading text-base font-bold leading-snug tracking-[-0.02em] sm:min-h-[1.4em] sm:text-lg"
|
||
/>
|
||
|
||
<div className="mt-3 flex flex-wrap gap-x-4 gap-y-2">
|
||
{KANITLAR.map(({ ikon: Ikon, etiket }) => (
|
||
<span
|
||
key={etiket}
|
||
className="inline-flex items-center gap-1.5 text-xs font-medium text-slate-200"
|
||
>
|
||
<Ikon className="size-3.5 text-orange-500" aria-hidden />
|
||
{etiket}
|
||
</span>
|
||
))}
|
||
</div>
|
||
</div>
|
||
|
||
<Button
|
||
size="lg"
|
||
onClick={sihirbazaGit}
|
||
className="w-full shrink-0 cursor-pointer bg-orange-500 text-white transition-colors duration-200 hover:bg-orange-600 focus-visible:ring-white/70 sm:w-auto"
|
||
>
|
||
Yapay Zeka listemi kur
|
||
<ArrowRight className="size-4" aria-hidden />
|
||
</Button>
|
||
</div>
|
||
</section>
|
||
);
|
||
}
|