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

@@ -74,6 +74,11 @@
--chart-4: oklch(0.371 0 0);
--chart-5: oklch(0.269 0 0);
--radius: 0.625rem;
/* Hareket eğrileri (DESIGN.md §6); JS karşılığı src/lib/hareket.ts EGRI */
--egri-giris: cubic-bezier(0.16, 1, 0.3, 1);
--egri-standart: cubic-bezier(0.22, 1, 0.36, 1);
--egri-guclu-cikis: cubic-bezier(0.23, 1, 0.32, 1);
--egri-yerlesme: cubic-bezier(0.32, 0.72, 0, 1);
--sidebar: oklch(0.985 0 0);
--sidebar-foreground: oklch(0.145 0 0);
--sidebar-primary: oklch(0.205 0 0);
@@ -484,3 +489,297 @@
animation: none;
}
}
/* Tema geçişi (DESIGN.md §2.3, lib/tema-gecisi.ts): yeni tema header'daki
logodan yumuşak bir halka gibi açılır. Eski görüntü yerinde durur; yeni
görüntü radyal degrade maskesiyle üstünde belirir. Maskenin kenarı keskin
değil: yarıçapın %55'ine kadar tam opak, oradan kenara doğru tüy gibi
söner (--kt-tema-cekirdek; JS'teki CEKIRDEK ile aynı olmalı) — ışık
yayılır gibi, sert çizgi yok. Yarıçap (--kt-tema-r) 0 → --kt-tema-son
yumuşak kalkıp yumuşak durur; kısa ve hafif (600 ms) — asıl gösteri,
geçiş bitince oynayan logo dönüşümüdür. Merkez ve son yarıçap JS'ten gelir.
Hareket azaltılmışsa JS geçişi hiç başlatmaz, tema düz değişir. */
@property --kt-tema-r {
syntax: "<length>";
inherits: true;
initial-value: 0px;
}
.kt-tema-gecis {
view-transition-name: root;
--kt-tema-cekirdek: 0.55;
}
.kt-tema-gecis::view-transition-old(root),
.kt-tema-gecis::view-transition-new(root) {
animation: none;
mix-blend-mode: normal;
}
.kt-tema-gecis::view-transition-old(root) {
z-index: 1;
}
.kt-tema-gecis::view-transition-new(root) {
z-index: 2;
mask-image: radial-gradient(
circle at var(--kt-tema-x) var(--kt-tema-y),
#000 calc(var(--kt-tema-r) * var(--kt-tema-cekirdek)),
transparent var(--kt-tema-r)
);
animation: kt-tema-halka 600ms cubic-bezier(0.4, 0, 0.2, 1) both;
}
@keyframes kt-tema-halka {
from {
--kt-tema-r: 0px;
}
to {
--kt-tema-r: var(--kt-tema-son);
}
}
/* Sayfa geçişi (DESIGN.md §6, components/sayfa-gecisi.tsx): rota değişince
eski içerik hızla söner, yenisi hafifçe yükselerek belirir — çıkış girişten
hızlı. Üst çubuk kendi grubunda ve animasyonsuz: içerik değişir, çubuk
yerinde kalır (uygulama hissi, §11.4). Tema geçişinde (`.kt-tema-gecis`)
çubuk adını bırakır ki halka onu da kaplasın. */
.kt-ust-cubuk {
view-transition-name: kt-ust-cubuk;
}
.kt-tema-gecis .kt-ust-cubuk {
view-transition-name: none;
}
::view-transition-group(kt-ust-cubuk) {
animation: none;
z-index: 100;
}
::view-transition-old(kt-ust-cubuk) {
display: none;
}
::view-transition-new(kt-ust-cubuk) {
animation: none;
}
::view-transition-old(.kt-sayfa-cik) {
animation: 120ms var(--egri-standart) both kt-sayfa-sol reverse;
}
::view-transition-new(.kt-sayfa-gir) {
animation:
200ms var(--egri-standart) 80ms both kt-sayfa-sol,
280ms var(--egri-giris) both kt-sayfa-yuksel;
}
@keyframes kt-sayfa-sol {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes kt-sayfa-yuksel {
from {
translate: 0 8px;
}
to {
translate: 0 0;
}
}
@media (prefers-reduced-motion: reduce) {
::view-transition-old(.kt-sayfa-cik),
::view-transition-new(.kt-sayfa-gir) {
animation: none;
}
}
/* Ana sayfa ilk ekranının girişi (DESIGN.md §6 "sayfa başına tek orkestre
giriş", §11.2): önce harita belirir, sonra başlık, sonra form netleşerek
gelir — sırayla, üst üste binmeden. `--sira` ile kademelenir. Başlık LCP
öğesi olduğundan hiçbir kare tam saydam başlamaz (opaklık 0,35'ten). */
.kt-hero-gir {
animation: kt-hero-gir 0.7s var(--egri-giris) both;
animation-delay: calc(var(--sira, 0) * 110ms + 80ms);
}
.kt-hero-harita {
animation: kt-hero-harita 1.1s var(--egri-standart) both;
}
@keyframes kt-hero-gir {
from {
opacity: 0.35;
filter: blur(10px);
translate: 0 12px;
}
to {
opacity: 1;
filter: blur(0);
translate: 0 0;
}
}
@keyframes kt-hero-harita {
from {
opacity: 0;
scale: 0.985;
}
to {
opacity: 1;
scale: 1;
}
}
@media (prefers-reduced-motion: reduce) {
.kt-hero-gir,
.kt-hero-harita {
animation: kt-unblur-sade 0.25s ease-out both;
}
}
/* "Nasıl çalışır" adımları arasındaki çizgi (DESIGN.md §4.32): adımın rakamı
biçimine dönüşüp geri toplanınca (adim-akisi.tsx `data-dolu` yazar) soldan
maviyle dolar. JS'siz istemcide dolu durur. */
@media (scripting: enabled) {
.kt-cizgi-dol {
transform: scaleX(0);
transform-origin: left;
transition: transform 0.6s var(--egri-guclu-cikis);
}
.kt-cizgi-dol[data-dolu] {
transform: scaleX(1);
}
@media (prefers-reduced-motion: reduce) {
.kt-cizgi-dol {
transform: none;
transition: none;
}
}
}
/* Piksel morf (components/piksel-morf.tsx, DESIGN.md §4.38): her piksel bir
<g>; yeri `translate` ile verilir ve kare değişince yeni hücresine süzülür.
`gizli` küçülüp söner. Turuncu vurgu, doku karesinin üstündeki ikinci
karedir: çabuk ısınır, yavaş soğur (ısı fırçasıyla aynı asimetri).
Sekme yok (§6); kayma süresi sahneye göre `--morf-sure` ile ayarlanır. */
.kt-morf-px {
transform-box: fill-box;
transform-origin: center;
transition:
translate var(--morf-sure, 0.55s) var(--egri-standart) var(--g, 0ms),
opacity 0.3s var(--egri-standart) var(--g, 0ms),
scale 0.3s var(--egri-standart) var(--g, 0ms);
}
.kt-morf-px[data-ton="gizli"] {
opacity: 0;
scale: 0.6;
}
.kt-morf-vurgu {
opacity: 0;
fill-opacity: 0.7;
transition: opacity 1.2s var(--egri-standart);
}
.kt-morf-taban {
transition: opacity 0.6s var(--egri-standart);
}
.kt-morf-px[data-ton="vurgu"] > .kt-morf-taban {
opacity: 0;
transition: opacity 0.25s var(--egri-standart) var(--g, 0ms);
}
.kt-morf-px[data-ton="vurgu"] > .kt-morf-vurgu {
opacity: 1;
animation:
kt-morf-isin 0.25s var(--egri-standart) var(--g, 0ms) backwards,
kt-morf-nabiz 3.6s ease-in-out var(--n, 0s) infinite;
}
@keyframes kt-morf-isin {
from {
opacity: 0;
}
}
@keyframes kt-morf-nabiz {
50% {
fill-opacity: 0.35;
}
}
@media (prefers-reduced-motion: reduce) {
.kt-morf-px,
.kt-morf-taban,
.kt-morf-vurgu {
transition: none;
}
.kt-morf-px[data-ton="vurgu"] > .kt-morf-vurgu {
animation: none;
}
}
/* Kendini çizen tik (landing-parcalari.tsx, DESIGN.md §5.2.3): karşılaştırma
tablosu ekrana girince (Reveal → .kt-reveal-in) önce halka, sonra işaret
çizilir; satır gecikmesi `--tik-g` ile gelir. `pathLength="1"` bekler. */
@media (scripting: enabled) and (prefers-reduced-motion: no-preference) {
.kt-reveal .kt-tik-ciz {
stroke-dasharray: 1;
stroke-dashoffset: 1;
opacity: 0;
transition:
stroke-dashoffset 0.5s var(--egri-standart) calc(var(--tik-g, 0ms) + 250ms),
opacity 0.01s linear calc(var(--tik-g, 0ms) + 250ms);
}
.kt-reveal .kt-tik-isaret {
transition-duration: 0.3s, 0.01s;
transition-delay:
calc(var(--tik-g, 0ms) + 600ms),
calc(var(--tik-g, 0ms) + 600ms);
}
.kt-reveal.kt-reveal-in .kt-tik-ciz {
stroke-dashoffset: 0;
opacity: 1;
}
}
/* Hero haritasının serpintisi (turkey-pixel-map.tsx, DESIGN.md §11.1 V2):
piksel bir süre yerinde durur, kopup `--d` birim (10 = bir hücre) düşerken
söner, sonra yerinde yeniden belirir. Süre ve faz satır içi. */
@keyframes kt-serpinti {
0%,
42% {
translate: 0 0;
opacity: 1;
animation-timing-function: ease-in;
}
76% {
translate: 0 calc(var(--d, 20) * 1px);
opacity: 0;
animation-timing-function: step-end;
}
80% {
translate: 0 0;
opacity: 0;
animation-timing-function: ease-out;
}
100% {
translate: 0 0;
opacity: 1;
}
}
.kt-serpinti {
animation: kt-serpinti 6s infinite;
}
@media (prefers-reduced-motion: reduce) {
.kt-serpinti {
animation: none;
}
}
/* Piksel şeridi (components/piksel-serit.tsx, DESIGN.md §4.38): kaydırmaya
bağlı sütun. Okuma çizgisini geçen piksel `data-yandi` alır ve turuncuya
ısınır; geri kaydırınca yavaş soğur. Şeridin ucu (`data-uc`) tam turuncu. */
.kt-serit-px {
fill: currentColor;
fill-opacity: var(--op, 0.2);
transition:
fill 0.9s var(--egri-standart),
fill-opacity 0.9s var(--egri-standart);
}
.kt-serit-px[data-yandi] {
fill: var(--color-orange-500);
fill-opacity: 0.62;
transition-duration: 0.16s;
}
.kt-serit-px[data-uc] {
fill-opacity: 1;
}
@media (prefers-reduced-motion: reduce) {
.kt-serit-px {
transition: none;
}
}