feat(tasarim): liste çekmecesi "şişko ve sade" dile göre
- Başlık ortalı (§4.10), çarpı ve temizle düğmesi kanonik size-9 - Satırlar çerçevesiz liste satırı; ad sarar, üniversite adı ayrı satırda, risk etiketi metin + renkle adın altında, çıkar düğmesi 44 px - Alt bant telefonda alt alta: başlık üç satıra kırılıp listenin yerini yiyordu (liste 232 → 279 px, 375 px ölçümü) - 10-11 px yazılar ve mavi hover'lar kalktı; navbar sayaç rozeti 12 px Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -275,6 +275,11 @@ açılan modal değil. Çekmece kabı ve çarpı `cta-sira-form.tsx`'teki çekme
|
||||
kareler kısaltılır, not kısaltılır — içerik silinmez (§9.2 ölçüm).
|
||||
- Sıra: gösterge kareleri → tek cümlelik not (`text-sm text-slate-500`) → ikincil buton (§4.1)
|
||||
→ kaynaklar (logo solda, `text-xs` iki satır).
|
||||
- **Liste çekmecesi** (`liste-cekmecesi.tsx`, 24 Eyl 2026): başlık aynı düzende (temizle
|
||||
düğmesi çarpıyla aynı sınıf setinde solda). Satırlar çerçeveli kart değil, düz liste satırı
|
||||
(`border-b border-slate-100`); ad §4.15 gibi sarar, risk + taban sıra adın altında, çıkar
|
||||
düğmesi `size-11`. Listeler uzun olduğu için bu çekmece kaydırılır (tek ekran kuralının
|
||||
tek istisnası); alt bandı telefonda alt alta dizilir ki listeye en az ~280 px kalsın.
|
||||
- **Arama motoru uyarısı:** çekmece içeriği sayfa açılırken HTML'de yoktur (kapalıyken
|
||||
çizilmez). Google'ın görmesi gereken metin ve **zorunlu atıflar** (ör. URAP sırası + payda +
|
||||
dönem + aktif bağlantı) çekmeceye taşınmaz, sayfada da görünür kalır.
|
||||
|
||||
@@ -43,7 +43,8 @@ import {
|
||||
type PanInfo,
|
||||
} from "motion/react";
|
||||
import { RISK_ETIKET, type RiskSeviyesi } from "@/lib/risk";
|
||||
import { bolumBazAdi, turkishSlugify } from "@/lib/slug";
|
||||
import { bolumBazAdi, trBaslikDuzeni, turkishSlugify } from "@/lib/slug";
|
||||
import { SATIR_AD, SATIR_IKINCIL_LINK } from "@/components/liste-satiri";
|
||||
import { olay } from "@/lib/analitik";
|
||||
import { CihazCifti, IsletimSistemiLogosu } from "./cihaz-ikonlari";
|
||||
import {
|
||||
@@ -83,6 +84,12 @@ const RISK_NOKTA: Record<RiskSeviyesi, string> = {
|
||||
"az-riskli": "bg-amber-500",
|
||||
riskli: "bg-red-500",
|
||||
};
|
||||
// §4.5: nokta + renkli metin; renk tek başına anlam taşımaz
|
||||
const RISK_METIN: Record<RiskSeviyesi, string> = {
|
||||
guvenli: "text-emerald-600",
|
||||
"az-riskli": "text-amber-600",
|
||||
riskli: "text-red-600",
|
||||
};
|
||||
|
||||
export function ListeCekmecesi() {
|
||||
const router = useRouter();
|
||||
@@ -123,35 +130,33 @@ export function ListeCekmecesi() {
|
||||
<DrawerContent
|
||||
className="min-h-0 max-h-[92dvh] bg-white sm:mx-auto sm:max-w-2xl sm:border-x sm:border-slate-200 sm:shadow-2xl"
|
||||
>
|
||||
<DrawerHeader className="border-b border-slate-100 text-left md:text-left">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<DrawerTitle className="flex items-center gap-2 font-heading text-lg font-bold">
|
||||
<ListChecks className="size-5 text-primary" aria-hidden />
|
||||
Kendi Listem
|
||||
<span className="font-heading text-sm font-bold tabular-nums text-slate-400">
|
||||
{liste.length}/{MANUEL_LISTE_MAX}
|
||||
</span>
|
||||
</DrawerTitle>
|
||||
<div className="flex items-center gap-1">
|
||||
{/* Alt sayfa başlığı (DESIGN.md §4.10): ortalı başlık + bağlam;
|
||||
çarpı §4.3 kanonik sınıf setinde, temizle düğmesi aynı ölçüde
|
||||
solda. */}
|
||||
<DrawerHeader className="relative border-b border-slate-100 px-4 pt-5 pb-4 text-center md:text-center">
|
||||
{liste.length > 0 ? (
|
||||
<button
|
||||
type="button"
|
||||
onClick={temizle}
|
||||
title="Listeyi temizle"
|
||||
className="cursor-pointer rounded-full p-2 text-slate-400 transition-colors duration-200 hover:bg-slate-100 hover:text-red-500"
|
||||
className="absolute top-3 left-3 z-10 flex size-9 cursor-pointer items-center justify-center rounded-full border border-slate-200 bg-popover text-slate-500 transition-[background-color,transform] duration-150 hover:bg-slate-100 hover:text-slate-800 active:scale-[0.96]"
|
||||
>
|
||||
<Trash2 className="size-4" aria-hidden />
|
||||
<span className="sr-only">Listeyi temizle</span>
|
||||
</button>
|
||||
) : null}
|
||||
<DrawerClose className="cursor-pointer rounded-full p-2 text-slate-400 transition-colors duration-200 hover:bg-slate-100 hover:text-slate-600">
|
||||
<DrawerClose className="absolute top-3 right-3 z-10 flex size-9 cursor-pointer items-center justify-center rounded-full border border-slate-200 bg-popover text-slate-500 transition-[background-color,transform] duration-150 hover:bg-slate-100 hover:text-slate-800 active:scale-[0.96]">
|
||||
<X className="size-4" aria-hidden />
|
||||
<span className="sr-only">Kapat</span>
|
||||
</DrawerClose>
|
||||
</div>
|
||||
</div>
|
||||
<DrawerTitle className="px-10 text-center font-heading text-2xl font-bold text-slate-900">
|
||||
Kendi Listem
|
||||
</DrawerTitle>
|
||||
<p className="mt-1 px-10 text-center text-sm text-slate-500">
|
||||
{liste.length} / {MANUEL_LISTE_MAX} tercih
|
||||
</p>
|
||||
<div
|
||||
className="mt-3 h-1.5 overflow-hidden rounded-full bg-slate-100"
|
||||
className="mx-auto mt-3 h-2 w-full max-w-xs overflow-hidden rounded-full bg-slate-100"
|
||||
role="progressbar"
|
||||
aria-valuenow={liste.length}
|
||||
aria-valuemin={0}
|
||||
@@ -164,30 +169,10 @@ export function ListeCekmecesi() {
|
||||
transition={{ type: "spring", duration: 0.5, bounce: 0 }}
|
||||
/>
|
||||
</div>
|
||||
<ul
|
||||
className="mt-2.5 flex flex-wrap items-center gap-x-3 gap-y-1 text-[11px] text-slate-500"
|
||||
aria-label="Yerleşme riski renkleri"
|
||||
>
|
||||
{(
|
||||
[
|
||||
["guvenli", "yeşil güvenli"],
|
||||
["az-riskli", "turuncu az riskli"],
|
||||
["riskli", "kırmızı riskli"],
|
||||
] as const
|
||||
).map(([seviye, aciklama]) => (
|
||||
<li key={seviye} className="inline-flex items-center gap-1.5">
|
||||
<span
|
||||
className={`size-2 shrink-0 rounded-full ${RISK_NOKTA[seviye]}`}
|
||||
aria-hidden
|
||||
/>
|
||||
<span>{aciklama}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</DrawerHeader>
|
||||
|
||||
<div
|
||||
className="min-h-0 flex-1 overflow-x-hidden overflow-y-auto px-4 py-4"
|
||||
className="min-h-0 flex-1 overflow-x-hidden overflow-y-auto px-4 py-2"
|
||||
>
|
||||
{liste.length === 0 ? (
|
||||
<div className="relative flex flex-col items-center justify-center gap-2 overflow-hidden px-4 py-16 text-center">
|
||||
@@ -196,10 +181,10 @@ export function ListeCekmecesi() {
|
||||
strokeWidth={1.25}
|
||||
aria-hidden
|
||||
/>
|
||||
<p className="relative font-heading text-sm font-bold text-slate-700">
|
||||
<p className="relative font-heading text-lg font-bold text-slate-900">
|
||||
Henüz program seçmedin
|
||||
</p>
|
||||
<p className="relative text-xs leading-relaxed text-slate-500">
|
||||
<p className="relative text-sm leading-relaxed text-slate-500">
|
||||
Sonuç sayfasından program ekleyerek listeni doldurmaya başla.
|
||||
</p>
|
||||
</div>
|
||||
@@ -228,29 +213,32 @@ export function ListeCekmecesi() {
|
||||
<button
|
||||
type="button"
|
||||
onClick={programaEkleGit}
|
||||
className={`flex h-12 w-full cursor-pointer items-center justify-center rounded-full border border-dashed border-slate-300 text-slate-400 transition-colors duration-200 hover:border-primary/50 hover:bg-primary/5 hover:text-primary active:scale-[0.98] ${
|
||||
liste.length > 0 ? "mt-2" : "mt-1"
|
||||
className={`flex h-12 w-full cursor-pointer items-center justify-center gap-2 rounded-full border border-dashed border-slate-300 text-sm font-semibold text-slate-500 transition-[background-color,border-color,color,transform] duration-150 hover:border-orange-500 hover:bg-orange-50/60 hover:text-orange-500 active:scale-[0.97] ${
|
||||
liste.length > 0 ? "mt-3" : "mt-1"
|
||||
}`}
|
||||
>
|
||||
<Plus className="size-5" aria-hidden />
|
||||
<span className="sr-only">Sonuç sayfasından program ekle</span>
|
||||
Program ekle
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<DrawerFooter className="border-t border-slate-100 pt-4 pb-[max(1rem,env(safe-area-inset-bottom))]">
|
||||
<DrawerFooter className="border-t border-slate-100 pt-3 pb-[max(1rem,env(safe-area-inset-bottom))]">
|
||||
{/* C2 kalıcı satır: girişsiz kullanıcıya listenin uçucu olduğu
|
||||
hatırlatılır; giriş doğal "kaydet" gerekçesi olarak sunulur. */}
|
||||
{girisli === false ? (
|
||||
<div className="mb-3 flex items-center justify-between gap-3 rounded-xl border border-slate-200 bg-slate-50 px-4 py-3 text-left">
|
||||
<p className="min-w-0 text-xs leading-relaxed text-slate-600">
|
||||
<div className="mb-2 flex flex-wrap items-center justify-between gap-x-3 px-1 text-left">
|
||||
<p className="min-w-0 text-sm leading-relaxed text-slate-600">
|
||||
{/* O anki işletim sisteminin logosu — "bu tarayıcıda"yı somutlaştırır */}
|
||||
<IsletimSistemiLogosu className="mr-1.5 inline size-3.5 align-[-3px] text-slate-700" />
|
||||
<IsletimSistemiLogosu className="mr-1.5 inline size-4 align-[-3px] text-slate-700" />
|
||||
<span className="font-semibold text-slate-800">
|
||||
Listen yalnızca bu tarayıcıda.
|
||||
</span>{" "}
|
||||
</span>
|
||||
<span className="hidden sm:inline">
|
||||
{" "}
|
||||
Giriş yap; her cihazdan eriş{" "}
|
||||
<CihazCifti className="align-[-3px] text-slate-400" />
|
||||
</span>
|
||||
</p>
|
||||
<Link
|
||||
href={girisUrl}
|
||||
@@ -258,16 +246,19 @@ export function ListeCekmecesi() {
|
||||
cekmeceKapat();
|
||||
olay("giris_cta_tiklandi", { kaynak: "liste_cekmecesi" });
|
||||
}}
|
||||
className="inline-flex shrink-0 items-center gap-1 text-xs font-semibold text-primary transition-colors duration-200 hover:text-primary/80"
|
||||
className="inline-flex min-h-11 shrink-0 items-center gap-1 text-sm font-semibold text-primary transition-colors duration-150 hover:text-orange-500"
|
||||
>
|
||||
Giriş yap, kaydet
|
||||
<ArrowRight className="size-3.5" aria-hidden />
|
||||
<ArrowRight className="size-4" aria-hidden />
|
||||
</Link>
|
||||
</div>
|
||||
) : null}
|
||||
{/* Ana sayfadaki final CTA panelinin kompakt hâli: koyu primary
|
||||
zemin + kenarlara doğru yoğunlaşan piksel dokusu (PixelField) */}
|
||||
<div className="relative isolate flex items-center justify-between gap-4 overflow-hidden rounded-2xl bg-primary px-5 py-5 text-left text-white">
|
||||
{/* 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
|
||||
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">
|
||||
<div
|
||||
aria-hidden
|
||||
className="pointer-events-none absolute inset-x-0 -inset-y-6 -z-10 text-white"
|
||||
@@ -275,10 +266,10 @@ export function ListeCekmecesi() {
|
||||
<PixelField seed={8} />
|
||||
</div>
|
||||
<div className="min-w-0">
|
||||
<p className="font-heading text-2xl font-bold">
|
||||
<p className="font-heading text-xl font-bold sm:text-2xl">
|
||||
{riskEksik ? "Risk analizi sıralamanı bekliyor" : "Risk analizini biz yaptık"}
|
||||
</p>
|
||||
<p className="mt-1 truncate text-sm leading-relaxed text-white">
|
||||
<p className="mt-1 hidden truncate text-sm leading-relaxed text-white sm:block">
|
||||
{riskEksik
|
||||
? "Riskleri gösterelim; istersen 24'lük listeni de kur (3 kredi)."
|
||||
: "Her seçimin yerleşme riski hazır — son kararı sen ver."}
|
||||
@@ -287,7 +278,7 @@ export function ListeCekmecesi() {
|
||||
{riskEksik ? (
|
||||
<Button
|
||||
size="lg"
|
||||
className="shrink-0 cursor-pointer bg-orange-500 text-white transition-colors duration-200 hover:bg-orange-600"
|
||||
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"
|
||||
onClick={() => {
|
||||
cekmeceKapat();
|
||||
// Hedef "sihirbaz": kapı kapanınca akış /sonuc'ta devam
|
||||
@@ -303,7 +294,7 @@ export function ListeCekmecesi() {
|
||||
<Button
|
||||
asChild
|
||||
size="lg"
|
||||
className="shrink-0 cursor-pointer bg-orange-500 text-white transition-colors duration-200 hover:bg-orange-600"
|
||||
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"
|
||||
>
|
||||
<Link href="/listem" onClick={cekmeceKapat}>
|
||||
Listemde aç
|
||||
@@ -358,12 +349,12 @@ function CekmeceSatiri({
|
||||
exit={{ opacity: 0, x: 96, transition: { duration: 0.18 } }}
|
||||
transition={{ type: "spring", duration: 0.4, bounce: 0 }}
|
||||
whileDrag={{ scale: 1.03, zIndex: 10 }}
|
||||
className="relative mb-2 select-none last:mb-0"
|
||||
className="relative select-none border-b border-slate-100 last:border-b-0"
|
||||
>
|
||||
{/* Sil bandı: kart sağa kaydıkça altından görünür */}
|
||||
<motion.div
|
||||
style={{ opacity: silGorunur }}
|
||||
className="absolute inset-0 flex items-center rounded-xl bg-red-500 pl-4"
|
||||
className="absolute inset-0 flex items-center bg-red-500 pl-4"
|
||||
aria-hidden
|
||||
>
|
||||
<Trash2 className="size-4 text-white" />
|
||||
@@ -377,7 +368,7 @@ function CekmeceSatiri({
|
||||
dragElastic={{ left: 0, right: 0.7 }}
|
||||
dragSnapToOrigin
|
||||
onDragEnd={kaydirinca}
|
||||
className="relative flex items-center gap-2.5 rounded-xl border border-slate-200 bg-white px-3 py-2.5"
|
||||
className="relative flex items-center gap-3 bg-white py-3"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
@@ -385,58 +376,66 @@ function CekmeceSatiri({
|
||||
e.stopPropagation(); // yatay kaydırmayı tetiklemesin
|
||||
siraKontrol.start(e);
|
||||
}}
|
||||
className="-m-1 shrink-0 cursor-grab touch-none rounded p-1 text-slate-300 active:cursor-grabbing"
|
||||
className="-ml-1 flex h-11 w-7 shrink-0 cursor-grab touch-none items-center justify-center rounded-lg text-slate-300 transition-colors hover:text-slate-500 active:cursor-grabbing"
|
||||
>
|
||||
<GripVertical className="size-4" aria-hidden />
|
||||
<GripVertical className="size-5" aria-hidden />
|
||||
<span className="sr-only">{tercih.isim} sırasını değiştir</span>
|
||||
</button>
|
||||
<span className="flex size-6 shrink-0 items-center justify-center rounded-full bg-slate-100 font-heading text-[11px] font-bold tabular-nums">
|
||||
<span className="flex size-8 shrink-0 items-center justify-center rounded-full bg-slate-100 font-heading text-sm font-bold text-slate-900">
|
||||
{sira}
|
||||
</span>
|
||||
<UniLogo ad={tercih.universite} boy="xs" />
|
||||
<span className="min-w-0 flex-1">
|
||||
<Link
|
||||
href={bolumHref(tercih.isim)}
|
||||
onClick={cekmeceKapat}
|
||||
onPointerDown={(e) => e.stopPropagation()} // kaydırmayı tetiklemesin
|
||||
className="block truncate text-sm font-semibold hover:text-primary hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
||||
className={SATIR_AD}
|
||||
>
|
||||
{tercih.isim}
|
||||
</Link>
|
||||
<span className="block truncate text-xs text-slate-500">
|
||||
<span className="mt-1 flex items-center gap-2 text-sm leading-snug text-slate-600">
|
||||
<UniLogo ad={tercih.universite} boy="xs" />
|
||||
<Link
|
||||
href={uniHref(tercih.universite)}
|
||||
onClick={cekmeceKapat}
|
||||
onPointerDown={(e) => e.stopPropagation()} // kaydırmayı tetiklemesin
|
||||
title={`${tercih.universite} taban puanları sayfası`}
|
||||
className="hover:text-primary hover:underline"
|
||||
className={`min-w-0 ${SATIR_IKINCIL_LINK}`}
|
||||
>
|
||||
{tercih.universite}
|
||||
{trBaslikDuzeni(tercih.universite)}
|
||||
</Link>
|
||||
{tercih.il ? ` · ${tercih.il.toLocaleLowerCase("tr-TR")}` : ""}
|
||||
</span>
|
||||
</span>
|
||||
{tercih.efektifSira != null ? (
|
||||
<span className="shrink-0 text-xs tabular-nums text-slate-500">
|
||||
~{tercih.efektifSira.toLocaleString("tr-TR")}.
|
||||
</span>
|
||||
) : null}
|
||||
{tercih.risk || tercih.efektifSira != null ? (
|
||||
<span className="mt-1.5 flex flex-wrap items-center gap-x-3 gap-y-1 text-sm text-slate-500">
|
||||
{tercih.risk ? (
|
||||
<span className="inline-flex shrink-0 items-center gap-1.5 text-xs font-medium">
|
||||
<span
|
||||
className={`size-2.5 shrink-0 rounded-full ${RISK_NOKTA[tercih.risk]}`}
|
||||
className={`inline-flex items-center gap-1.5 text-xs font-semibold ${RISK_METIN[tercih.risk]}`}
|
||||
>
|
||||
<span
|
||||
className={`size-2 shrink-0 rounded-full ${RISK_NOKTA[tercih.risk]}`}
|
||||
aria-hidden
|
||||
/>
|
||||
{RISK_ETIKET[tercih.risk]}
|
||||
</span>
|
||||
) : null}
|
||||
{tercih.efektifSira != null ? (
|
||||
<span className="inline-flex items-center gap-1.5">
|
||||
Taban sıra
|
||||
<span className="font-semibold tabular-nums text-slate-900">
|
||||
~{tercih.efektifSira.toLocaleString("tr-TR")}.
|
||||
</span>
|
||||
</span>
|
||||
) : null}
|
||||
</span>
|
||||
) : null}
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => cikar(tercih.id)}
|
||||
onPointerDown={(e) => e.stopPropagation()} // kaydırmayı tetiklemesin
|
||||
className="shrink-0 cursor-pointer rounded-full p-1.5 text-slate-300 transition-colors duration-200 hover:bg-slate-100 hover:text-red-500"
|
||||
className="inline-flex size-11 shrink-0 cursor-pointer items-center justify-center rounded-full border border-slate-200 bg-white text-slate-500 transition-[background-color,border-color,color,transform] duration-150 hover:border-orange-500 hover:bg-orange-50/60 hover:text-orange-500 active:scale-[0.92]"
|
||||
>
|
||||
<X className="size-3.5" aria-hidden />
|
||||
<X className="size-4" aria-hidden />
|
||||
<span className="sr-only">{tercih.isim} listeden çıkar</span>
|
||||
</button>
|
||||
</motion.div>
|
||||
|
||||
@@ -46,7 +46,7 @@ export function ListemButonu() {
|
||||
initial={azMotion ? false : { scale: 0.6 }}
|
||||
animate={{ scale: 1 }}
|
||||
transition={{ type: "spring", duration: 0.4, bounce: 0.45 }}
|
||||
className="flex size-5 items-center justify-center rounded-full bg-orange-500 font-heading text-[11px] font-bold tabular-nums text-white"
|
||||
className="flex h-6 min-w-6 items-center justify-center rounded-full bg-orange-500 px-1.5 font-heading text-xs font-bold text-white"
|
||||
>
|
||||
{liste.length}
|
||||
</motion.span>
|
||||
|
||||
Reference in New Issue
Block a user