- PikselCtaYuzeyi: koyu slate-950 yüzey, yazılıp silinen satırlar, tek turuncu CTA (arama penceresindeki kartın dili); ana sayfa kapanışı ve /sonuc'taki iki panelde köşeye kümelenen PixelField'in yerine - PikselListe: 24 kare = 24 tercih; kendini kurar (5 hayal · 13 dengeli · 6 güvenli), imlece tepki verir, CTA'ya gelince tamamı dolar, dokunmada nabız; hareketi azalt'ta durağan; renk hep metinle - Liste çekmecesi: ilerleme çubuğunun yerinde öğrencinin kendi listesi pikselde (her kare o tercihin riski); başlık yan yana dizildi, liste alanı 150 → 234 px (375 px ölçümü) - Funnel metinleri src/lib/funnel-metinleri.ts'e taşındı Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
112 lines
4.0 KiB
TypeScript
112 lines
4.0 KiB
TypeScript
"use client";
|
||
|
||
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="mt-5 overflow-hidden rounded-2xl border border-blue-400/20 bg-slate-950 px-4 py-5 text-white sm:px-5"
|
||
>
|
||
<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>
|
||
);
|
||
}
|