feat(v2): 9–10 Ekim çalışma ağacı — deneyim dili 1. dalga, PWA manifest, revizyon niyet modeli, listem/tadımlık bileşen denetimi

9–10 Ekim oturumlarının commit'lenmemiş işi tek parça alındı
(raporlar: docs/ekip-raporlari/2026-10-09, 2026-10-10):
- Landing deneyim dili 1. dalga: sayfa geçişi (ViewTransition), piksel morf/şerit,
  sorun sahneleri, adım akışı, sayan sayı, mobil menü, adım göstergesi, logoya tepki;
  DESIGN.md §11 ve AGENTS.md kuralları, tasarım denetimine yeni kurallar
- Tema: tema ikonu, D kısayolu, tema geçişi
- PWA: manifest.ts ve ikonlar
- Danışman: revizyon niyet modeli (OPENROUTER_NIYET_MODEL), sohbet geçmişi, soru API'si
- /listem ve /sonuc tadımlığı bileşen denetimi düzeltmeleri
- eslint: .venv-tts/ yok sayılır

tsc, eslint ve tasarım denetimi sıfır hata.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
bilalgursen
2026-10-11 00:34:29 +03:00
parent da0e9dff7a
commit aec8a2ad76
80 changed files with 4134 additions and 654 deletions

View File

@@ -1,5 +1,6 @@
"use client";
import { logoTepkisi } from "@/lib/logo-tepkisi";
import { useId, useState } from "react";
import Link from "next/link";
import { useRouter } from "next/navigation";
@@ -211,6 +212,8 @@ export function CtaSiraForm({
if (sira == null) return;
const yeniProfil = { sira, tur, secimler };
tercihProfiliYaz(yeniProfil);
// Üst çubuktaki logo kürsüye döner (DESIGN.md §4.34).
logoTepkisi("sira");
setAcik(false);
router.push(sonucHref(girisli ? { sihirbaz: "1", kaynak: "cta" } : { hazir: "1" }));
}

View File

@@ -1,11 +1,13 @@
"use client";
import { logoTepkisi } from "@/lib/logo-tepkisi";
import { useState } from "react";
import Link from "next/link";
import { useRouter } from "next/navigation";
import { ArrowRight } from "lucide-react";
import { ArrowRight, Trophy } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { SayiRulo } from "@/components/ui/sayi-rulo";
import { SecimHaplari } from "@/components/ui/secim-haplari";
import {
Dialog,
@@ -22,7 +24,8 @@ import { olay, siraKovasi } from "@/lib/analitik";
import { onbellekliJsonGetir } from "@/lib/istek-cache";
import { useTercihProfili } from "@/features/liste/hooks/use-tercih-profili";
import type { SihirbazFacetleri } from "@/types/yokatlas";
import { sonucHref, type SihirbazSecimleri } from "@/features/sihirbaz/sihirbaz-sabitler";
import { PUAN_TURU_ETIKET, sonucHref, type SihirbazSecimleri } from "@/features/sihirbaz/sihirbaz-sabitler";
import { ListeDurumuSatiri } from "@/features/liste/components/liste-durumu-satiri";
import { tercihProfiliYaz } from "@/features/sihirbaz/sihirbaz-profil";
// lib/db sunucu tarafı; etiketler client bundle'a sızmasın diye burada.
@@ -99,6 +102,8 @@ export function HeroForm() {
if (!sira) return;
const profil = { sira, tur, secimler };
tercihProfiliYaz(profil);
// Üst çubuktaki logo kürsüye döner (DESIGN.md §4.34).
logoTepkisi("sira");
setModalAcik(false);
router.push(sonucHref(girisli ? { sihirbaz: "1", kaynak: "hero" } : { hazir: "1" }));
}
@@ -108,16 +113,27 @@ export function HeroForm() {
if (kayitliProfil) {
return (
<div className="flex w-full max-w-md flex-col items-center">
{/* Üst çubuktaki sıralama hapının iri hâli (DESIGN.md §1.1 "sayı
kahramandır", §11.2 "durumu yansıt"): site seni tanıyor. */}
<p className="mb-5 flex items-center gap-2.5 font-heading text-3xl leading-none font-bold tracking-tight text-slate-900 sm:text-4xl">
<Trophy className="size-6 shrink-0 text-orange-500 sm:size-7" aria-hidden />
<SayiRulo deger={kayitliProfil.sira} />
<span className="font-sans text-base font-semibold tracking-normal text-slate-500">
· {PUAN_TURU_ETIKET[kayitliProfil.tur] ?? kayitliProfil.tur}
</span>
</p>
<Button
asChild
size="lg"
className="w-full cursor-pointer bg-orange-500 text-white transition-colors duration-200 hover:bg-orange-600 sm:w-auto"
variant="cta"
className="w-full sm:w-auto"
>
<Link href={sonucHref()}>
Üniversitelere göz at
<ArrowRight className="size-4" aria-hidden />
</Link>
</Button>
<ListeDurumuSatiri varyant="hero" />
</div>
);
}
@@ -161,9 +177,9 @@ export function HeroForm() {
type="submit"
size="lg"
disabled={yukleniyor}
className="cursor-pointer bg-orange-500 text-white transition-colors duration-200 hover:bg-orange-600"
variant="cta"
>
Yapay Zeka listemi kur
{yukleniyor ? "Hazırlanıyor…" : "Yapay Zeka listemi kur"}
<ArrowRight className="size-4" aria-hidden />
</Button>
</form>

View File

@@ -5,6 +5,7 @@
// dynamic import: hem hero-form hem sonuc/sihirbaz-modal burayı kullanır.
import dynamic from "next/dynamic";
import { AdimGostergesiIskeleti } from "@/components/ui/adim-gostergesi";
import { Skeleton } from "@/components/ui/skeleton";
function SihirbazAdimlarSkeleton() {
@@ -14,11 +15,8 @@ function SihirbazAdimlarSkeleton() {
aria-live="polite"
className="flex min-h-64 flex-col gap-5"
>
<div className="flex items-center gap-1.5">
<Skeleton className="h-1.5 w-6 rounded-full" />
<Skeleton className="size-1.5 rounded-full" />
<Skeleton className="size-1.5 rounded-full" />
<Skeleton className="ml-2 h-3 w-14" />
<div className="pr-12">
<AdimGostergesiIskeleti adet={3} />
</div>
<div>
@@ -36,8 +34,8 @@ function SihirbazAdimlarSkeleton() {
</div>
<div className="flex items-center justify-between border-t border-slate-100 pt-4">
<Skeleton className="h-9 w-20 rounded-lg" />
<Skeleton className="h-9 w-24 rounded-lg" />
<Skeleton className="h-11 w-20 rounded-full" />
<Skeleton className="h-11 w-24 rounded-full" />
</div>
</div>
);

View File

@@ -15,6 +15,7 @@ import { toast } from "@/components/ui/bildirim";
import { cities } from "turkey-map-react/lib/data";
import { TransitionPanel } from "@/components/core/transition-panel";
import { adimGecisi, adimGecisiVaryantlari } from "@/lib/adim-gecisi";
import { AdimGostergesi } from "@/components/ui/adim-gostergesi";
import { Button } from "@/components/ui/button";
import { IlSecimHaritasi } from "./il-secim-haritasi";
import { normalizeIlAdi } from "@/lib/harita";
@@ -214,41 +215,16 @@ export function SihirbazAdimlar({
return (
<div className="flex flex-col gap-5">
{/* Adım göstergesi (DESIGN.md §10): üç parçalı çubuk + adların
kendisi; öğrenci nerede olduğunu ve ne kaldığını görür. Değişim
ekran okuyucuya duyurulur (WCAG 4.1.3). */}
<div>
<ol className="grid grid-cols-3 gap-2" aria-label="Sihirbaz adımları">
{ADIM_ADLARI.map((ad, i) => (
<li key={ad} className="flex flex-col gap-1.5">
{/* Dolum transform ile (yalnız transform/opaklık canlandırılır);
soldan dolar, güçlü ease-out 300 ms. */}
<span className="h-1.5 overflow-hidden rounded-full bg-slate-200" aria-hidden>
<span
className={`block h-full origin-left rounded-full bg-primary transition-transform duration-300 ease-[cubic-bezier(0.23,1,0.32,1)] motion-reduce:transition-none ${
i <= adim ? "scale-x-100" : "scale-x-0"
}`}
/>
</span>
<span
className={`text-sm ${
i === adim
? "font-semibold text-slate-900"
: i < adim
? "font-medium text-slate-600"
: "text-slate-400"
}`}
aria-current={i === adim ? "step" : undefined}
>
{ad}
</span>
</li>
))}
</ol>
<p role="status" className="sr-only">
Adım {adim + 1} / 3: {ADIM_ADLARI[adim]}
</p>
</div>
{/* Adım göstergesi (DESIGN.md §4.35, §10.2): numaralı, adlı; tamamlanan
adıma dokunarak geri dönülür. Değişimi ekran okuyucuya kendisi duyurur. */}
<AdimGostergesi
adimlar={ADIM_ADLARI}
aktif={adim}
onAdimSec={adimaGit}
ariaLabel="Sihirbaz adımları"
// Sağ üstteki kapatma çarpısına (size-9) yer bırakır
className="pr-12"
/>
{/* Geçiş sabitleri soru formuyla ortak (lib/adim-gecisi.ts) */}
<TransitionPanel
@@ -536,7 +512,8 @@ export function SihirbazAdimlar({
type="button"
onClick={() => adimaGit(adim + 1)}
disabled={!adimGecerli}
className="h-11 cursor-pointer bg-orange-500 px-5 text-white transition-[background-color,transform] duration-200 hover:bg-orange-600 active:scale-[0.97]"
variant="cta"
className="h-11 px-5"
>
Devam
<ArrowRight className="size-4" aria-hidden />
@@ -555,7 +532,8 @@ export function SihirbazAdimlar({
onTamamla({ kategoriler, iller, universiteTipi, oncelikler, ozelDurum });
}}
disabled={kategoriler.length === 0}
className="h-11 cursor-pointer bg-orange-500 px-5 text-white transition-[background-color,transform] duration-200 hover:bg-orange-600 active:scale-[0.97]"
variant="cta"
className="h-11 px-5"
>
{sonButonEtiketi}
<ArrowRight className="size-4" aria-hidden />

View File

@@ -1,5 +1,6 @@
"use client";
import { logoTepkisi } from "@/lib/logo-tepkisi";
import { useEffect, useRef, useState, useSyncExternalStore } from "react";
import Link from "next/link";
import { useRouter } from "next/navigation";
@@ -182,6 +183,8 @@ export function SihirbazBolumu({
*/
async function ilerle(secimler: SihirbazSecimleri) {
tercihProfiliYaz({ sira, tur, secimler });
// Üst çubuktaki logo kürsüye döner (DESIGN.md §4.34).
logoTepkisi("sira");
await modalCikisiniOynat();
setElleAcik(false);
if (!girisliMi) {