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>
This commit is contained in:
@@ -589,3 +589,21 @@ Araştırma ve kaynaklar: `docs/tasarim/2026-09-25-samimi-arayuz-arastirmasi.md`
|
|||||||
Seri (streak), kayıp korkusu, puan/XP, rozet koleksiyonu, liderlik tablosu, maskot, sahte
|
Seri (streak), kayıp korkusu, puan/XP, rozet koleksiyonu, liderlik tablosu, maskot, sahte
|
||||||
aciliyet/kıtlık, utandırarak onay. Tercih günlük alışkanlık değil, tek seferlik kaygılı bir
|
aciliyet/kıtlık, utandırarak onay. Tercih günlük alışkanlık değil, tek seferlik kaygılı bir
|
||||||
karar.
|
karar.
|
||||||
|
|
||||||
|
### 10.8 Funnel CTA yüzeyi ve 24'lük piksel liste
|
||||||
|
Bilal'in isteği (25 Eyl 2026): "CTA alanlarını arama içindeki alandan ilham, piksel detaylı yap;
|
||||||
|
pikseller bir kümede kümeleşmesin, interaktif kreatif olsun."
|
||||||
|
- **Yüzey — `PikselCtaYuzeyi`, `src/components/piksel-cta-yuzeyi.tsx`:** arama penceresindeki
|
||||||
|
kartın dili: `rounded-3xl border border-blue-400/20 bg-slate-950 text-white`, altta yazılıp
|
||||||
|
silinen satırlar (`FUNNEL_METINLERI`, `src/lib/funnel-metinleri.ts`), tek turuncu CTA.
|
||||||
|
Kullanıldığı yerler: ana sayfa kapanış CTA'sı, `/sonuc`'taki iki sihirbaz paneli. Eski
|
||||||
|
`bg-primary` + köşeye kümelenmiş `PixelField` bu yüzeylerde kullanılmaz.
|
||||||
|
- **Kreatif — `PikselListe`, `src/components/piksel-liste.tsx`:** 24 kare = 24 tercih, hero'nun
|
||||||
|
nokta ölçüsüyle (hücre 10, nokta 7.4, rx 2). Örnek modda liste kendini kurar (5 hayal · 13
|
||||||
|
dengeli · 6 güvenli, 110 ms arayla), yalnız ekrandayken. İmleç yakın kareleri büyütür; kart
|
||||||
|
içindeki düğmeye imleç/odak gelince 24 kare birden dolar; dokunmada basılan kare ve komşuları
|
||||||
|
nabız atar (`kt-piksel-nabiz`). Hareketi azalt: dolu, durağan. Renk her zaman
|
||||||
|
`PikselListeAciklama` metniyle.
|
||||||
|
- **Gerçek liste:** çekmecede ilerleme çubuğunun yerinde öğrencinin kendi listesi (`gercek`,
|
||||||
|
12×2): her kare o tercihin riskiyle, riski bilinmeyen beyaz; metin karşılığı yanındaki risk
|
||||||
|
dağılımında.
|
||||||
|
|||||||
@@ -303,3 +303,21 @@
|
|||||||
mask-image: none;
|
mask-image: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 24'lük piksel listede dokunma nabzı (bkz. components/piksel-liste.tsx):
|
||||||
|
basılan kare ve komşuları kısa bir büyüyüp söner. Tek seferlik, 420 ms. */
|
||||||
|
@keyframes kt-piksel-nabiz {
|
||||||
|
0% {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
35% {
|
||||||
|
transform: scale(1.3);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.kt-piksel-nabiz {
|
||||||
|
animation: kt-piksel-nabiz 0.42s cubic-bezier(0.23, 1, 0.32, 1);
|
||||||
|
}
|
||||||
|
|||||||
@@ -25,9 +25,9 @@ import { TurkeyPixelMap } from "@/features/pazarlama/components/turkey-pixel-map
|
|||||||
import {
|
import {
|
||||||
PixelCorner,
|
PixelCorner,
|
||||||
PixelDivider,
|
PixelDivider,
|
||||||
PixelField,
|
|
||||||
SectionEyebrow,
|
SectionEyebrow,
|
||||||
} from "@/components/pixel-decor";
|
} from "@/components/pixel-decor";
|
||||||
|
import { PikselCtaYuzeyi } from "@/components/piksel-cta-yuzeyi";
|
||||||
import { MAX_REVIZYON, URUNLER } from "@/lib/credits";
|
import { MAX_REVIZYON, URUNLER } from "@/lib/credits";
|
||||||
import { VERI_YIL_SAYISI } from "@/lib/veri-kapsami";
|
import { VERI_YIL_SAYISI } from "@/lib/veri-kapsami";
|
||||||
import {
|
import {
|
||||||
@@ -681,28 +681,14 @@ export default function Home() {
|
|||||||
<section className="px-4 pb-24 pt-6 sm:px-6">
|
<section className="px-4 pb-24 pt-6 sm:px-6">
|
||||||
<Parallax strength={12} className="mx-auto max-w-6xl">
|
<Parallax strength={12} className="mx-auto max-w-6xl">
|
||||||
<Reveal>
|
<Reveal>
|
||||||
<div className="relative isolate overflow-hidden rounded-3xl bg-primary px-4 py-20 text-white shadow-xl shadow-primary/20 sm:py-24">
|
{/* Funnel CTA yüzeyi (DESIGN.md §10.8): köşede kümelenen
|
||||||
{/* Hero'nun piksel dokusu koyu zeminde: ortası boş, kenarlara doğru yoğun */}
|
piksel zemini yerine 24'lük listenin interaktif kreatifi */}
|
||||||
{/* Piksel zemin panel içinde geride kalır; taşma payı için dikeyde büyütülür */}
|
<PikselCtaYuzeyi metinOfseti={7}>
|
||||||
<div
|
<h2 className="font-heading text-3xl font-bold text-balance sm:text-4xl">
|
||||||
aria-hidden
|
Tercih listen, pişmanlık listesi olmasın
|
||||||
className="pointer-events-none absolute inset-x-0 -inset-y-10 -z-10 text-white"
|
</h2>
|
||||||
>
|
<KapanisCta />
|
||||||
<Parallax strength={-22} className="h-full">
|
</PikselCtaYuzeyi>
|
||||||
<PixelField seed={8} />
|
|
||||||
</Parallax>
|
|
||||||
</div>
|
|
||||||
<div className="mx-auto flex max-w-3xl flex-col items-center text-center">
|
|
||||||
<MessageCircleQuestion
|
|
||||||
className="size-10 opacity-80"
|
|
||||||
aria-hidden
|
|
||||||
/>
|
|
||||||
<h2 className="mt-6 font-heading text-3xl font-bold sm:text-4xl">
|
|
||||||
Tercih listen, pişmanlık listesi olmasın
|
|
||||||
</h2>
|
|
||||||
<KapanisCta />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Reveal>
|
</Reveal>
|
||||||
</Parallax>
|
</Parallax>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
ListChecks,
|
ListChecks,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { FUNNEL_METINLERI } from "@/lib/funnel-metinleri";
|
||||||
import { TypingAnimation } from "@/components/ui/typing-animation";
|
import { TypingAnimation } from "@/components/ui/typing-animation";
|
||||||
import { SIHIRBAZ_AC_EVENT, sonucHref } from "@/features/sihirbaz/sihirbaz-sabitler";
|
import { SIHIRBAZ_AC_EVENT, sonucHref } from "@/features/sihirbaz/sihirbaz-sabitler";
|
||||||
import {
|
import {
|
||||||
@@ -16,39 +17,6 @@ import {
|
|||||||
useTercihProfili,
|
useTercihProfili,
|
||||||
} from "@/features/liste/hooks/use-tercih-profili";
|
} from "@/features/liste/hooks/use-tercih-profili";
|
||||||
|
|
||||||
const FUNNEL_METINLERI = [
|
|
||||||
"Sıralamana en uygun listeyi birlikte kuralım.",
|
|
||||||
"Geleceğin 24 satırlık bir plana sığar.",
|
|
||||||
"Gerçek YÖK Atlas verisiyle güvenle seç.",
|
|
||||||
"Her tercihinin bir nedeni olsun.",
|
|
||||||
"Riskleri biz hesaplayalım, kararı sen ver.",
|
|
||||||
"Sana uygun bölümler bir adım uzağında.",
|
|
||||||
"Dengeli bir liste, rahat bir tercih dönemi.",
|
|
||||||
"Tercihlerini şansa bırakma.",
|
|
||||||
"Doğru strateji, doğru yerleşme.",
|
|
||||||
"Hayalindeki bölüm için doğru plan.",
|
|
||||||
"Verilerle konuşan bir tercih listesi.",
|
|
||||||
"Üç adımda sana özel 24 tercih.",
|
|
||||||
"Tercih dönemini stressiz geçir.",
|
|
||||||
"Geçen yılın verisi, senin avantajın.",
|
|
||||||
"Güvenli ve iddialı tercihleri dengele.",
|
|
||||||
"Sıralaman ne söylüyor? Birlikte bakalım.",
|
|
||||||
"Her satırı senin için hesaplıyoruz.",
|
|
||||||
"Akıllı tercih, içi rahat bir bekleyiş.",
|
|
||||||
"Yerleşme riskini görerek seç.",
|
|
||||||
"Sana özel liste birkaç dakikada hazır.",
|
|
||||||
"İlgi alanların ve şehrin, tek listede.",
|
|
||||||
"Boş kalan tercih, kaçan fırsattır.",
|
|
||||||
"Uzman işi bir liste, senin onayınla.",
|
|
||||||
"Kararını veriye dayandır.",
|
|
||||||
"Doğru tercih, doğru bilgiyle başlar.",
|
|
||||||
"Listeni bugün kur, için rahat olsun.",
|
|
||||||
"Yerleşme riskin renklerle önünde.",
|
|
||||||
"En önemli 24 kararını birlikte verelim.",
|
|
||||||
"Senin sıralaman, sana özel plan.",
|
|
||||||
"İyi bir liste, iyi bir başlangıçtır.",
|
|
||||||
];
|
|
||||||
|
|
||||||
/** Fisher-Yates ile kopya üzerinde karıştırır; her açılışta farklı sıra. */
|
/** Fisher-Yates ile kopya üzerinde karıştırır; her açılışta farklı sıra. */
|
||||||
function karistir<T>(dizi: readonly T[]): T[] {
|
function karistir<T>(dizi: readonly T[]): T[] {
|
||||||
const kopya = [...dizi];
|
const kopya = [...dizi];
|
||||||
|
|||||||
77
src/components/piksel-cta-yuzeyi.tsx
Normal file
77
src/components/piksel-cta-yuzeyi.tsx
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useState, type ReactNode } from "react";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
import { TypingAnimation } from "@/components/ui/typing-animation";
|
||||||
|
import { FUNNEL_METINLERI } from "@/lib/funnel-metinleri";
|
||||||
|
import { PikselListe, PikselListeAciklama } from "@/components/piksel-liste";
|
||||||
|
|
||||||
|
/** Listeyi `ofset` kadar döndürür: aynı sayfadaki yüzeyler farklı satırla
|
||||||
|
* başlar. Rastgele değil, deterministik — sunucu ve istemci aynı çizer. */
|
||||||
|
function dondur<T>(dizi: readonly T[], ofset: number): T[] {
|
||||||
|
const n = ofset % dizi.length;
|
||||||
|
return [...dizi.slice(n), ...dizi.slice(0, n)];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Funnel CTA yüzeyi (DESIGN.md §10.8) — arama penceresindeki kartın dili:
|
||||||
|
* koyu slate-950 zemin, ince mavi kenar, yazılıp silinen kısa satırlar,
|
||||||
|
* turuncu CTA. Pikseller bir köşede kümelenmez; yüzeyin kreatifi 24'lük
|
||||||
|
* listenin kendisidir (PikselListe). Kartın içindeki bir düğmeye ya da
|
||||||
|
* linke imleç/odak gelince 24 kare birden dolar — "listen böyle hazır olur".
|
||||||
|
*/
|
||||||
|
export function PikselCtaYuzeyi({
|
||||||
|
children,
|
||||||
|
className,
|
||||||
|
metinOfseti = 0,
|
||||||
|
kayanMetin = true,
|
||||||
|
}: {
|
||||||
|
children: ReactNode;
|
||||||
|
className?: string;
|
||||||
|
/** Aynı sayfadaki iki yüzey aynı satırla başlamasın */
|
||||||
|
metinOfseti?: number;
|
||||||
|
kayanMetin?: boolean;
|
||||||
|
}) {
|
||||||
|
const [tamamla, setTamamla] = useState(false);
|
||||||
|
const [metinler] = useState(() => dondur(FUNNEL_METINLERI, metinOfseti));
|
||||||
|
|
||||||
|
const hedefMi = (t: EventTarget | null) =>
|
||||||
|
t instanceof Element && t.closest("a, button") != null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section
|
||||||
|
onPointerOver={(e) => setTamamla(hedefMi(e.target))}
|
||||||
|
onPointerLeave={() => setTamamla(false)}
|
||||||
|
onFocusCapture={(e) => setTamamla(hedefMi(e.target))}
|
||||||
|
onBlurCapture={() => setTamamla(false)}
|
||||||
|
className={cn(
|
||||||
|
"relative isolate overflow-hidden rounded-3xl border border-blue-400/20 bg-slate-950 text-white shadow-xl shadow-slate-900/10",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className="grid items-center gap-8 px-5 py-10 sm:px-10 sm:py-12 lg:grid-cols-[minmax(0,1fr)_16rem] lg:gap-12">
|
||||||
|
<div className="flex min-w-0 flex-col items-center text-center lg:items-start lg:text-left">
|
||||||
|
{children}
|
||||||
|
{kayanMetin ? (
|
||||||
|
<TypingAnimation
|
||||||
|
as="p"
|
||||||
|
words={metinler}
|
||||||
|
loop
|
||||||
|
typeSpeed={38}
|
||||||
|
deleteSpeed={18}
|
||||||
|
pauseDelay={2600}
|
||||||
|
className="mt-5 min-h-[2.75em] font-heading text-base font-bold leading-snug tracking-[-0.02em] text-slate-300 sm:min-h-[1.4em]"
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
<div className="order-first flex flex-col items-center lg:order-none">
|
||||||
|
<PikselListe
|
||||||
|
tamamla={tamamla}
|
||||||
|
className="w-full max-w-[9rem] sm:max-w-[11rem] lg:max-w-[14rem]"
|
||||||
|
/>
|
||||||
|
<PikselListeAciklama className="mt-4 justify-center" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
191
src/components/piksel-liste.tsx
Normal file
191
src/components/piksel-liste.tsx
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import {
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
type PointerEvent as ReactPointerEvent,
|
||||||
|
type ReactNode,
|
||||||
|
} from "react";
|
||||||
|
import { useReducedMotion } from "motion/react";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
import { DILIM_ETIKET } from "@/lib/risk";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 24'lük tercih listesinin piksel hâli — CTA yüzeylerinin interaktif
|
||||||
|
* kreatifi (Bilal'in isteği, 25 Eyl 2026: "pikseller bir köşede
|
||||||
|
* kümelenmesin, interaktif kreatif olsun"). Her kare bir tercih; hero'daki
|
||||||
|
* dot-matrix ölçüsüyle (hücre 10, nokta 7.4, rx 2).
|
||||||
|
*
|
||||||
|
* Davranış (DESIGN.md §10.8):
|
||||||
|
* - Örnek modda liste kendini kurar: kareler sırayla dolar (5 hayal ·
|
||||||
|
* 13 dengeli · 6 güvenli), bir süre durur, sakin söner, yeniden başlar.
|
||||||
|
* Yalnız ekrandayken çalışır.
|
||||||
|
* - İmleç ızgaranın üstündeyken yakındaki kareler hafifçe büyür.
|
||||||
|
* - `tamamla` true ise (ör. CTA düğmesine gelince) 24 kare birden dolar.
|
||||||
|
* - Dokunmada kareye basınca o kare ve komşuları kısa bir nabız atar.
|
||||||
|
* - Hareketi azalt: dolu ve durağan ızgara.
|
||||||
|
* - `gercek` verilirse döngü yok; kareler öğrencinin kendi listesidir
|
||||||
|
* (her kare o tercihin riskiyle).
|
||||||
|
* Renk tek başına anlam taşımaz: çağıran `PikselListeAciklama` ile metni
|
||||||
|
* yanına koyar.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type PikselKare = "hayal" | "dengeli" | "guvenli" | "belirsiz" | "bos";
|
||||||
|
|
||||||
|
const SUTUN = 6;
|
||||||
|
const SATIR = 4;
|
||||||
|
const TOPLAM = SUTUN * SATIR;
|
||||||
|
const CELL = 10;
|
||||||
|
const DOT = 7.4;
|
||||||
|
const INSET = (CELL - DOT) / 2;
|
||||||
|
|
||||||
|
const ORNEK: PikselKare[] = Array.from({ length: TOPLAM }, (_, i) =>
|
||||||
|
i < 5 ? "hayal" : i < 18 ? "dengeli" : "guvenli",
|
||||||
|
);
|
||||||
|
|
||||||
|
const RENK: Record<PikselKare, string> = {
|
||||||
|
hayal: "fill-red-500/85",
|
||||||
|
dengeli: "fill-amber-400/85",
|
||||||
|
guvenli: "fill-emerald-500/85",
|
||||||
|
belirsiz: "fill-white/55",
|
||||||
|
bos: "fill-white/15",
|
||||||
|
};
|
||||||
|
|
||||||
|
const ADIM_MS = 110;
|
||||||
|
const BEKLE_MS = 2600;
|
||||||
|
|
||||||
|
export function PikselListe({
|
||||||
|
tamamla = false,
|
||||||
|
gercek,
|
||||||
|
sutun = SUTUN,
|
||||||
|
className,
|
||||||
|
}: {
|
||||||
|
tamamla?: boolean;
|
||||||
|
/** Öğrencinin gerçek listesi (sırasıyla); verilirse örnek döngü kapanır */
|
||||||
|
gercek?: PikselKare[];
|
||||||
|
sutun?: number;
|
||||||
|
className?: string;
|
||||||
|
}) {
|
||||||
|
const hareketAzalt = useReducedMotion();
|
||||||
|
const satir = Math.ceil(TOPLAM / sutun);
|
||||||
|
const svgRef = useRef<SVGSVGElement>(null);
|
||||||
|
const [dolu, setDolu] = useState(hareketAzalt || gercek ? TOPLAM : 0);
|
||||||
|
const [gorunur, setGorunur] = useState(false);
|
||||||
|
const [imlec, setImlec] = useState<{ x: number; y: number } | null>(null);
|
||||||
|
const [nabiz, setNabiz] = useState<{ i: number; t: number } | null>(null);
|
||||||
|
|
||||||
|
// Yalnız ekrandayken çalış (IntersectionObserver, DESIGN.md §6)
|
||||||
|
useEffect(() => {
|
||||||
|
const el = svgRef.current;
|
||||||
|
if (!el) return;
|
||||||
|
const io = new IntersectionObserver(([e]) => setGorunur(e.isIntersecting), {
|
||||||
|
rootMargin: "0px 0px -10% 0px",
|
||||||
|
});
|
||||||
|
io.observe(el);
|
||||||
|
return () => io.disconnect();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Örnek döngü: 0 → 24 sırayla, bekle, sıfırla
|
||||||
|
useEffect(() => {
|
||||||
|
if (gercek || hareketAzalt || !gorunur || tamamla) return;
|
||||||
|
const id = window.setTimeout(
|
||||||
|
() => setDolu((d) => (d >= TOPLAM ? 0 : d + 1)),
|
||||||
|
dolu >= TOPLAM ? BEKLE_MS : dolu === 0 ? 500 : ADIM_MS,
|
||||||
|
);
|
||||||
|
return () => window.clearTimeout(id);
|
||||||
|
}, [dolu, gorunur, gercek, hareketAzalt, tamamla]);
|
||||||
|
|
||||||
|
const kareler: PikselKare[] = gercek
|
||||||
|
? Array.from({ length: TOPLAM }, (_, i) => gercek[i] ?? "bos")
|
||||||
|
: ORNEK.map((k, i) => (tamamla || i < dolu ? k : "bos"));
|
||||||
|
|
||||||
|
const imlecGuncelle = useCallback((e: ReactPointerEvent<SVGSVGElement>) => {
|
||||||
|
if (e.pointerType !== "mouse") return;
|
||||||
|
const svg = svgRef.current;
|
||||||
|
if (!svg) return;
|
||||||
|
const r = svg.getBoundingClientRect();
|
||||||
|
setImlec({
|
||||||
|
x: ((e.clientX - r.left) / r.width) * sutun * CELL,
|
||||||
|
y: ((e.clientY - r.top) / r.height) * satir * CELL,
|
||||||
|
});
|
||||||
|
}, [sutun, satir]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
ref={svgRef}
|
||||||
|
viewBox={`0 0 ${sutun * CELL} ${satir * CELL}`}
|
||||||
|
aria-hidden="true"
|
||||||
|
focusable="false"
|
||||||
|
onPointerMove={imlecGuncelle}
|
||||||
|
onPointerLeave={() => setImlec(null)}
|
||||||
|
className={cn("h-auto w-full touch-manipulation", className)}
|
||||||
|
>
|
||||||
|
{kareler.map((k, i) => {
|
||||||
|
const x = (i % sutun) * CELL;
|
||||||
|
const y = Math.floor(i / sutun) * CELL;
|
||||||
|
const cx = x + CELL / 2;
|
||||||
|
const cy = y + CELL / 2;
|
||||||
|
// İmlece yakın kare büyür (en çok %18), uzaklaştıkça etkisi söner
|
||||||
|
let olcek = 1;
|
||||||
|
if (imlec && !hareketAzalt) {
|
||||||
|
const d = Math.hypot(imlec.x - cx, imlec.y - cy);
|
||||||
|
olcek = 1 + Math.max(0, 1 - d / 16) * 0.18;
|
||||||
|
}
|
||||||
|
const nabizda =
|
||||||
|
nabiz &&
|
||||||
|
Math.abs((nabiz.i % sutun) - (i % sutun)) <= 1 &&
|
||||||
|
Math.abs(Math.floor(nabiz.i / sutun) - Math.floor(i / sutun)) <= 1;
|
||||||
|
return (
|
||||||
|
<rect
|
||||||
|
key={nabizda ? `${i}-${nabiz.t}` : i}
|
||||||
|
x={x + INSET}
|
||||||
|
y={y + INSET}
|
||||||
|
width={DOT}
|
||||||
|
height={DOT}
|
||||||
|
rx={2}
|
||||||
|
onPointerDown={(e) => {
|
||||||
|
if (e.pointerType === "mouse" || hareketAzalt) return;
|
||||||
|
setNabiz({ i, t: Date.now() });
|
||||||
|
}}
|
||||||
|
className={cn(
|
||||||
|
RENK[k],
|
||||||
|
"origin-center transition-[fill,transform] duration-300 ease-[cubic-bezier(0.23,1,0.32,1)] [transform-box:fill-box] motion-reduce:transition-none",
|
||||||
|
nabizda && "kt-piksel-nabiz",
|
||||||
|
)}
|
||||||
|
style={{ transform: olcek !== 1 ? `scale(${olcek})` : undefined }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Renklerin metin karşılığı (§2.2: renk tek başına anlam taşımaz). */
|
||||||
|
export function PikselListeAciklama({
|
||||||
|
className,
|
||||||
|
sayilar = { hayal: 5, dengeli: 13, guvenli: 6 },
|
||||||
|
}: {
|
||||||
|
className?: string;
|
||||||
|
sayilar?: { hayal: number; dengeli: number; guvenli: number };
|
||||||
|
}) {
|
||||||
|
const oge = (renk: string, sayi: number, etiket: string): ReactNode => (
|
||||||
|
<span className="inline-flex items-center gap-1.5">
|
||||||
|
<span className={cn("size-2 shrink-0 rounded-[2px]", renk)} aria-hidden />
|
||||||
|
{sayi} {etiket.toLocaleLowerCase("tr-TR")}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<p
|
||||||
|
className={cn(
|
||||||
|
"flex flex-wrap items-center gap-x-3 gap-y-1 text-sm font-medium text-slate-300",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{oge("bg-red-500", sayilar.hayal, DILIM_ETIKET.hayal)}
|
||||||
|
{oge("bg-amber-400", sayilar.dengeli, DILIM_ETIKET.dengeli)}
|
||||||
|
{oge("bg-emerald-500", sayilar.guvenli, DILIM_ETIKET.garanti)}
|
||||||
|
</p>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -15,7 +15,7 @@ import { useState } from "react";
|
|||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { PixelField } from "@/components/pixel-decor";
|
import { PikselListe } from "@/components/piksel-liste";
|
||||||
import { UniLogo } from "@/components/uni-logo";
|
import { UniLogo } from "@/components/uni-logo";
|
||||||
import { RiskEtiketi } from "@/components/risk-etiketi";
|
import { RiskEtiketi } from "@/components/risk-etiketi";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
@@ -122,7 +122,6 @@ export function ListeCekmecesi() {
|
|||||||
// drawer içinde erken ölçüm satırları kaydırıyordu
|
// drawer içinde erken ölçüm satırları kaydırıyordu
|
||||||
const [oturdu, setOturdu] = useState(false);
|
const [oturdu, setOturdu] = useState(false);
|
||||||
|
|
||||||
const doluluk = Math.round((liste.length / MANUEL_LISTE_MAX) * 100);
|
|
||||||
// Risk dağılımı: riskliden güvenliye, sıfır olanlar yazılmaz
|
// Risk dağılımı: riskliden güvenliye, sıfır olanlar yazılmaz
|
||||||
const riskSayilari = (["riskli", "az-riskli", "guvenli"] as const)
|
const riskSayilari = (["riskli", "az-riskli", "guvenli"] as const)
|
||||||
.map((r) => [r, liste.filter((t) => t.risk === r).length] as const)
|
.map((r) => [r, liste.filter((t) => t.risk === r).length] as const)
|
||||||
@@ -185,55 +184,66 @@ export function ListeCekmecesi() {
|
|||||||
<DrawerTitle className="px-10 text-center font-heading text-2xl font-bold text-slate-900">
|
<DrawerTitle className="px-10 text-center font-heading text-2xl font-bold text-slate-900">
|
||||||
Kendi Listem
|
Kendi Listem
|
||||||
</DrawerTitle>
|
</DrawerTitle>
|
||||||
{/* Durum ekran okuyucuya da duyurulur (WCAG 4.1.3) */}
|
{/* Durum satırı: solda listenin piksel hâli (ilerleme çubuğunun
|
||||||
<p role="status" className="mt-1 px-10 text-center text-sm text-slate-500">
|
yerine), sağda sayı ve risk dağılımı — dikine uzamasın diye
|
||||||
{liste.length === MANUEL_LISTE_MAX ? (
|
yan yana (DESIGN.md §5.3). Durum ekran okuyucuya duyurulur. */}
|
||||||
// Nadir, hak edilmiş an: küçük giriş (Emil: scale 0.95 + opaklık,
|
<div className="mt-3 flex items-center justify-center gap-3">
|
||||||
// güçlü ease-out, <300 ms). Hareketi azalt'ta yalnız opaklık.
|
<div
|
||||||
<span className="inline-flex items-center gap-1.5 font-semibold text-primary transition-[opacity,transform] duration-300 ease-[cubic-bezier(0.23,1,0.32,1)] starting:scale-95 starting:opacity-0 motion-reduce:starting:scale-100">
|
className="w-40 shrink-0 rounded-xl bg-slate-950 px-2.5 py-2"
|
||||||
<Check className="size-4" aria-hidden />
|
role="progressbar"
|
||||||
Listen tamam · {liste.length} / {MANUEL_LISTE_MAX}
|
aria-valuenow={liste.length}
|
||||||
</span>
|
aria-valuemin={0}
|
||||||
) : (
|
aria-valuemax={MANUEL_LISTE_MAX}
|
||||||
<>
|
aria-label="Liste doluluğu"
|
||||||
{liste.length} / {MANUEL_LISTE_MAX} tercih
|
>
|
||||||
</>
|
<PikselListe
|
||||||
)}
|
gercek={liste.map((t) =>
|
||||||
</p>
|
t.risk === "riskli"
|
||||||
<div
|
? "hayal"
|
||||||
className="mx-auto mt-3 h-2 w-full max-w-xs overflow-hidden rounded-full bg-slate-100"
|
: t.risk === "az-riskli"
|
||||||
role="progressbar"
|
? "dengeli"
|
||||||
aria-valuenow={liste.length}
|
: t.risk === "guvenli"
|
||||||
aria-valuemin={0}
|
? "guvenli"
|
||||||
aria-valuemax={MANUEL_LISTE_MAX}
|
: "belirsiz",
|
||||||
aria-label="Liste doluluğu"
|
)}
|
||||||
>
|
sutun={12}
|
||||||
<motion.div
|
/>
|
||||||
className="h-full rounded-full bg-primary"
|
</div>
|
||||||
animate={{ width: `${doluluk}%` }}
|
<div className="min-w-0 text-left">
|
||||||
transition={{ type: "spring", duration: 0.5, bounce: 0 }}
|
<p role="status" className="text-sm text-slate-500">
|
||||||
/>
|
{liste.length === MANUEL_LISTE_MAX ? (
|
||||||
</div>
|
// Nadir, hak edilmiş an: küçük giriş (Emil: scale 0.95 + opaklık,
|
||||||
{/* Listenin dengesi metinle (renk tek başına değil, §2.2) */}
|
// güçlü ease-out, <300 ms). Hareketi azalt'ta yalnız opaklık.
|
||||||
{riskSayilari.length > 0 ? (
|
<span className="inline-flex items-center gap-1.5 font-semibold text-primary transition-[opacity,transform] duration-300 ease-[cubic-bezier(0.23,1,0.32,1)] starting:scale-95 starting:opacity-0 motion-reduce:starting:scale-100">
|
||||||
<p className="mt-2.5 flex flex-wrap items-center justify-center gap-x-3 gap-y-1">
|
<Check className="size-4" aria-hidden />
|
||||||
{riskSayilari.map(([seviye, adet]) => (
|
Listen tamam · {liste.length} / {MANUEL_LISTE_MAX}
|
||||||
<span key={seviye} className="inline-flex items-center gap-1.5">
|
|
||||||
<RiskEtiketi risk={seviye} />
|
|
||||||
<span className="text-sm font-semibold text-slate-900">
|
|
||||||
{adet}
|
|
||||||
</span>
|
</span>
|
||||||
</span>
|
) : (
|
||||||
))}
|
<>
|
||||||
</p>
|
{liste.length} / {MANUEL_LISTE_MAX} tercih
|
||||||
) : null}
|
</>
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
{riskSayilari.length > 0 ? (
|
||||||
|
<p className="mt-1 flex flex-wrap items-center gap-x-3 gap-y-1">
|
||||||
|
{riskSayilari.map(([seviye, adet]) => (
|
||||||
|
<span key={seviye} className="inline-flex items-center gap-1.5">
|
||||||
|
<RiskEtiketi risk={seviye} />
|
||||||
|
<span className="text-sm font-semibold text-slate-900">
|
||||||
|
{adet}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</p>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{/* Denge ipucu: 24'lük iskeletin mantığı (alt sıralarda güvenli
|
{/* Denge ipucu: 24'lük iskeletin mantığı (alt sıralarda güvenli
|
||||||
programlar). Yalnız riskleri bilinen ve en az 6 tercihli
|
programlar). Yalnız riskleri bilinen ve en az 6 tercihli
|
||||||
listede; vaat değil, gerekçe. */}
|
listede; vaat değil, gerekçe. */}
|
||||||
{guvenliYok ? (
|
{guvenliYok ? (
|
||||||
<p className="mx-auto mt-2 max-w-sm text-center text-sm leading-relaxed text-slate-600">
|
<p className="mx-auto mt-2 max-w-sm text-center text-sm leading-relaxed text-slate-600">
|
||||||
Güvenli program yok; alta birkaç tane eklemek açıkta kalma riskini
|
Güvenli yok; alta birkaç tane eklemek riski azaltır.
|
||||||
azaltır.
|
|
||||||
</p>
|
</p>
|
||||||
) : null}
|
) : null}
|
||||||
</DrawerHeader>
|
</DrawerHeader>
|
||||||
@@ -325,18 +335,14 @@ export function ListeCekmecesi() {
|
|||||||
{/* Telefonda alt alta (başlık tek satır, buton tam genişlik):
|
{/* Telefonda alt alta (başlık tek satır, buton tam genişlik):
|
||||||
yan yana dizince başlık üç satıra kırılıp listenin yerini
|
yan yana dizince başlık üç satıra kırılıp listenin yerini
|
||||||
yiyordu (24 Eyl 2026, 375 px ölçümü). */}
|
yiyordu (24 Eyl 2026, 375 px ölçümü). */}
|
||||||
<div className="relative isolate flex flex-col gap-3 overflow-hidden rounded-2xl bg-primary px-5 py-4 text-left text-white sm:flex-row sm:gap-4 sm:py-5 sm:items-center sm:justify-between">
|
{/* Funnel yüzeyi (DESIGN.md §10.8): arama penceresindeki kartın
|
||||||
<div
|
koyu zemini; listenin piksel hâli başlıkta. */}
|
||||||
aria-hidden
|
<div className="flex flex-col gap-3 rounded-2xl border border-blue-400/20 bg-slate-950 px-5 py-4 text-left text-white sm:flex-row sm:items-center sm:justify-between sm:gap-4 sm:py-5">
|
||||||
className="pointer-events-none absolute inset-x-0 -inset-y-6 -z-10 text-white"
|
<div className="min-w-0 sm:order-1 sm:flex-1">
|
||||||
>
|
|
||||||
<PixelField seed={8} />
|
|
||||||
</div>
|
|
||||||
<div className="min-w-0">
|
|
||||||
<p className="font-heading text-xl font-bold sm:text-2xl">
|
<p className="font-heading text-xl font-bold sm:text-2xl">
|
||||||
{riskEksik ? "Risk analizi sıralamanı bekliyor" : "Risk analizini biz yaptık"}
|
{riskEksik ? "Risk analizi sıralamanı bekliyor" : "Risk analizini biz yaptık"}
|
||||||
</p>
|
</p>
|
||||||
<p className="mt-1 hidden truncate text-sm leading-relaxed text-white sm:block">
|
<p className="mt-1 hidden truncate text-sm leading-relaxed text-slate-300 sm:block">
|
||||||
{riskEksik
|
{riskEksik
|
||||||
? "Riskleri gösterelim; istersen 24'lük listeni de kur (3 kredi)."
|
? "Riskleri gösterelim; istersen 24'lük listeni de kur (3 kredi)."
|
||||||
: "Her seçimin yerleşme riski hazır — son kararı sen ver."}
|
: "Her seçimin yerleşme riski hazır — son kararı sen ver."}
|
||||||
@@ -345,7 +351,7 @@ export function ListeCekmecesi() {
|
|||||||
{riskEksik ? (
|
{riskEksik ? (
|
||||||
<Button
|
<Button
|
||||||
size="lg"
|
size="lg"
|
||||||
className="h-12 w-full shrink-0 cursor-pointer bg-orange-500 text-white transition-colors duration-200 hover:bg-orange-600 active:scale-[0.97] sm:w-auto"
|
className="h-12 w-full shrink-0 cursor-pointer bg-orange-500 text-white transition-colors duration-200 hover:bg-orange-600 active:scale-[0.97] sm:order-3 sm:w-auto"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
cekmeceKapat();
|
cekmeceKapat();
|
||||||
// Hedef "sihirbaz": kapı kapanınca akış /sonuc'ta devam
|
// Hedef "sihirbaz": kapı kapanınca akış /sonuc'ta devam
|
||||||
@@ -361,7 +367,7 @@ export function ListeCekmecesi() {
|
|||||||
<Button
|
<Button
|
||||||
asChild
|
asChild
|
||||||
size="lg"
|
size="lg"
|
||||||
className="h-12 w-full shrink-0 cursor-pointer bg-orange-500 text-white transition-colors duration-200 hover:bg-orange-600 active:scale-[0.97] sm:w-auto"
|
className="h-12 w-full shrink-0 cursor-pointer bg-orange-500 text-white transition-colors duration-200 hover:bg-orange-600 active:scale-[0.97] sm:order-3 sm:w-auto"
|
||||||
>
|
>
|
||||||
<Link href="/listem" onClick={cekmeceKapat}>
|
<Link href="/listem" onClick={cekmeceKapat}>
|
||||||
Listemde aç
|
Listemde aç
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { ArrowRight } from "lucide-react";
|
|||||||
import { animate } from "motion/mini";
|
import { animate } from "motion/mini";
|
||||||
import { useReducedMotion } from "motion/react";
|
import { useReducedMotion } from "motion/react";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { PixelField } from "@/components/pixel-decor";
|
import { PikselCtaYuzeyi } from "@/components/piksel-cta-yuzeyi";
|
||||||
import { useGorunumOlayi } from "@/components/use-gorunum-olayi";
|
import { useGorunumOlayi } from "@/components/use-gorunum-olayi";
|
||||||
import { olay, olayBirKez, siraKovasi } from "@/lib/analitik";
|
import { olay, olayBirKez, siraKovasi } from "@/lib/analitik";
|
||||||
import type { SihirbazFacetleri } from "@/types/yokatlas";
|
import type { SihirbazFacetleri } from "@/types/yokatlas";
|
||||||
@@ -279,70 +279,57 @@ export function SihirbazBolumu({
|
|||||||
</Button>
|
</Button>
|
||||||
</section>
|
</section>
|
||||||
) : girisBekliyor ? (
|
) : girisBekliyor ? (
|
||||||
<section
|
<div ref={hazirPanelRef} className="mt-16 min-w-0">
|
||||||
ref={hazirPanelRef}
|
<PikselCtaYuzeyi metinOfseti={3}>
|
||||||
className="relative isolate mt-16 min-w-0 overflow-hidden rounded-2xl bg-primary px-5 py-8 text-center text-white sm:p-12"
|
<h2 className="font-heading text-2xl font-bold text-balance sm:text-3xl">
|
||||||
>
|
Seçimlerin hazır — Yapay Zeka listen bir adım uzakta
|
||||||
<div
|
</h2>
|
||||||
aria-hidden
|
<p className="mt-3 max-w-xl text-base leading-relaxed text-slate-300">
|
||||||
className="pointer-events-none absolute inset-x-0 -inset-y-10 -z-10 text-white"
|
Yukarıdaki tablo her zaman ücretsiz. 5 deneme kredinle 24
|
||||||
>
|
tercihlik Yapay Zeka listeni de kurabilirsin: ücretsiz giriş yap,
|
||||||
<PixelField seed={13} />
|
seçimlerin kayıtlı — listen otomatik kurulsun (üretim 3 kredi,
|
||||||
</div>
|
kalan 2 kredinle danışmana soru sorarsın).
|
||||||
<h2 className="relative font-heading text-2xl font-bold sm:text-3xl">
|
</p>
|
||||||
Seçimlerin hazır — Yapay Zeka listen bir adım uzakta
|
<Button
|
||||||
</h2>
|
asChild
|
||||||
<p className="relative mx-auto mt-3 max-w-xl text-white/90">
|
size="lg"
|
||||||
Yukarıdaki tablo her zaman ücretsiz. 5 deneme kredinle 24
|
className="mt-6 h-auto min-h-12 w-full cursor-pointer whitespace-normal bg-orange-500 px-4 py-3 text-white transition-[background-color,transform] duration-200 hover:bg-orange-600 active:scale-[0.97] sm:w-auto sm:px-8"
|
||||||
tercihlik Yapay Zeka listeni de kurabilirsin: ücretsiz giriş yap,
|
|
||||||
seçimlerin kayıtlı — listen otomatik kurulsun (üretim 3
|
|
||||||
kredi, kalan 2 kredinle danışmana soru sorarsın).
|
|
||||||
</p>
|
|
||||||
<Button
|
|
||||||
asChild
|
|
||||||
size="lg"
|
|
||||||
className="relative mt-6 h-auto min-h-12 w-full cursor-pointer whitespace-normal bg-orange-500 px-4 py-3 text-white transition-colors duration-200 hover:bg-orange-600 sm:w-auto sm:px-8"
|
|
||||||
>
|
|
||||||
<Link
|
|
||||||
href={girisUrl}
|
|
||||||
onClick={() =>
|
|
||||||
olay("giris_cta_tiklandi", { kaynak: "hazir_panel" })
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
5 deneme kredinle Yapay Zeka listeni kur — giriş yap
|
<Link
|
||||||
<ArrowRight className="size-4" aria-hidden />
|
href={girisUrl}
|
||||||
</Link>
|
onClick={() =>
|
||||||
</Button>
|
olay("giris_cta_tiklandi", { kaynak: "hazir_panel" })
|
||||||
<p className="relative mt-3 text-xs text-white/70">
|
}
|
||||||
Kredi kartı gerekmez · Google ya da e-posta ile 30 saniyede
|
>
|
||||||
</p>
|
5 deneme kredinle Yapay Zeka listeni kur — giriş yap
|
||||||
</section>
|
<ArrowRight className="size-4" aria-hidden />
|
||||||
|
</Link>
|
||||||
|
</Button>
|
||||||
|
<p className="mt-3 text-sm text-slate-400">
|
||||||
|
Kredi kartı gerekmez · Google ya da e-posta ile 30 saniyede
|
||||||
|
</p>
|
||||||
|
</PikselCtaYuzeyi>
|
||||||
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<section className="relative isolate mt-16 min-w-0 overflow-hidden rounded-2xl bg-primary px-5 py-8 text-center text-white sm:p-12">
|
<PikselCtaYuzeyi className="mt-16 min-w-0" metinOfseti={11}>
|
||||||
<div
|
<h2 className="font-heading text-2xl font-bold text-balance sm:text-3xl">
|
||||||
aria-hidden
|
|
||||||
className="pointer-events-none absolute inset-x-0 -inset-y-10 -z-10 text-white"
|
|
||||||
>
|
|
||||||
<PixelField seed={19} />
|
|
||||||
</div>
|
|
||||||
<h2 className="relative font-heading text-2xl font-bold sm:text-3xl">
|
|
||||||
24 tercihi tek tek elemek yerine, yapay zekâ kursun
|
24 tercihi tek tek elemek yerine, yapay zekâ kursun
|
||||||
</h2>
|
</h2>
|
||||||
<p className="relative mx-auto mt-3 max-w-xl text-white/90">
|
<p className="mt-3 max-w-xl text-base leading-relaxed text-slate-300">
|
||||||
Tablodan elle eklemek her zaman serbest. Ama üç adımda ilgi
|
Tablodan elle eklemek her zaman serbest. Ama üç adımda ilgi
|
||||||
alanlarını ve şehirlerini söylersen, yapay zekâ hayal, dengeli
|
alanlarını ve şehirlerini söylersen, yapay zekâ hayal, dengeli ve
|
||||||
ve güvenli dilimlerine dağıtılmış 24 tercihi — her satırı
|
güvenli dilimlerine dağıtılmış 24 tercihi — her satırı yerleşme
|
||||||
yerleşme riski ve gerekçesiyle — senin yerine dizer.
|
riski ve gerekçesiyle — senin yerine dizer.
|
||||||
</p>
|
</p>
|
||||||
<Button
|
<Button
|
||||||
size="lg"
|
size="lg"
|
||||||
onClick={sihirbaziBaslat}
|
onClick={sihirbaziBaslat}
|
||||||
className="relative mt-6 h-auto min-h-12 w-full cursor-pointer whitespace-normal bg-orange-500 px-4 py-3 text-white transition-colors duration-200 hover:bg-orange-600 sm:w-auto sm:px-8"
|
className="mt-6 h-auto min-h-12 w-full cursor-pointer whitespace-normal bg-orange-500 px-4 py-3 text-white transition-[background-color,transform] duration-200 hover:bg-orange-600 active:scale-[0.97] sm:w-auto sm:px-8"
|
||||||
>
|
>
|
||||||
Yapay Zeka listemi kur
|
Yapay Zeka listemi kur
|
||||||
<ArrowRight className="size-4" aria-hidden />
|
<ArrowRight className="size-4" aria-hidden />
|
||||||
</Button>
|
</Button>
|
||||||
</section>
|
</PikselCtaYuzeyi>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{monteEdildi ? (
|
{monteEdildi ? (
|
||||||
|
|||||||
36
src/lib/funnel-metinleri.ts
Normal file
36
src/lib/funnel-metinleri.ts
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
/**
|
||||||
|
* CTA yüzeylerinde yazılıp silinen kısa satırlar (arama penceresindeki
|
||||||
|
* kart ve PikselCtaYuzeyi). "use client" yok: sunucu da okuyabilsin (§9.3).
|
||||||
|
*/
|
||||||
|
export const FUNNEL_METINLERI: readonly string[] = [
|
||||||
|
"Sıralamana en uygun listeyi birlikte kuralım.",
|
||||||
|
"Geleceğin 24 satırlık bir plana sığar.",
|
||||||
|
"Gerçek YÖK Atlas verisiyle güvenle seç.",
|
||||||
|
"Her tercihinin bir nedeni olsun.",
|
||||||
|
"Riskleri biz hesaplayalım, kararı sen ver.",
|
||||||
|
"Sana uygun bölümler bir adım uzağında.",
|
||||||
|
"Dengeli bir liste, rahat bir tercih dönemi.",
|
||||||
|
"Tercihlerini şansa bırakma.",
|
||||||
|
"Doğru strateji, doğru yerleşme.",
|
||||||
|
"Hayalindeki bölüm için doğru plan.",
|
||||||
|
"Verilerle konuşan bir tercih listesi.",
|
||||||
|
"Üç adımda sana özel 24 tercih.",
|
||||||
|
"Tercih dönemini stressiz geçir.",
|
||||||
|
"Geçen yılın verisi, senin avantajın.",
|
||||||
|
"Güvenli ve iddialı tercihleri dengele.",
|
||||||
|
"Sıralaman ne söylüyor? Birlikte bakalım.",
|
||||||
|
"Her satırı senin için hesaplıyoruz.",
|
||||||
|
"Akıllı tercih, içi rahat bir bekleyiş.",
|
||||||
|
"Yerleşme riskini görerek seç.",
|
||||||
|
"Sana özel liste birkaç dakikada hazır.",
|
||||||
|
"İlgi alanların ve şehrin, tek listede.",
|
||||||
|
"Boş kalan tercih, kaçan fırsattır.",
|
||||||
|
"Uzman işi bir liste, senin onayınla.",
|
||||||
|
"Kararını veriye dayandır.",
|
||||||
|
"Doğru tercih, doğru bilgiyle başlar.",
|
||||||
|
"Listeni bugün kur, için rahat olsun.",
|
||||||
|
"Yerleşme riskin renklerle önünde.",
|
||||||
|
"En önemli 24 kararını birlikte verelim.",
|
||||||
|
"Senin sıralaman, sana özel plan.",
|
||||||
|
"İyi bir liste, iyi bir başlangıçtır.",
|
||||||
|
];
|
||||||
Reference in New Issue
Block a user