Files
kolaytercih/src/components/arama-funnel-karti.tsx
bilalgursen 71e6389131 feat(tasarim): funnel CTA'ları arama kartı dilinde, 24'lük piksel liste kreatifi
- 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>
2026-09-24 02:12:45 +03:00

112 lines
4.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"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>
);
}