One Piece Hub'ı yazarken en çok vaktimi alan şey karakter sayfaları ya da arama değildi. Şu soruydu: siteyi 300. bölümde olan biri açtığında ne görmeli? Çünkü bir wiki, doğası gereği her şeyi bilir — ve okuyucunun bilmediği şeyi ona göstermek, o kişinin yıllarını mahvetmek demek.
Türkçe kaynak arayan biri genelde iki seçenekle karşılaşıyor: ya İngilizce ve devasa bir wiki, ya da dağınık forum başlıkları. İkisinde de aynı risk var. Bir karakterin sayfasını açıyorsun, sağ üstteki kutuda "Durum: Ölü" yazıyor.
Sitede Ne Var
Önce içerik tarafı, çünkü problemin büyüklüğü buradan anlaşılıyor:
One Piece Hub · içerik
- 67
- karakter
- 36
- ark, 10 saga altında
- 43
- şeytan meyvesi
- 46
- ödül kaydı
- 22
- dövüş
- 160
- quiz sorusu
Bunların üstünde ark ve bölüm takibi, tayfa sayfaları, güç sıralaması, haki rehberi ve başarımlar var. Yani sayfaların neredeyse tamamı, doğal hâliyle spoiler taşıyor.
Spoiler Nedir, Kod Açısından
Buna somut bir tanım vermeden hiçbir şey yazamazsın. Denediğim ilk tanım "önemli olay" idi ve işe yaramadı — neyin önemli olduğu bir yargı çağrısı ve her içerik parçasında yeniden vermen gerekiyor.
Sonra çok daha basit bir tanıma indim: One Piece doğrusal ilerliyor. Her içerik parçası bir arka ait ve arklar sıralı. Okuyucu da bir arkta. Gerisi karşılaştırma.
/**
* Bir içerik gizlenmeli mi?
* Kapı açıksa, kullanıcı nerede olduğunu söylemişse ve hedef ark
* kullanıcının bulunduğu arktan SONRA geliyorsa — evet.
*/
const isSpoiler = useCallback((arcSlug: string | undefined) => {
if (!mounted || !state.enabled || !arcSlug) return false
const targetIndex = ARCS.findIndex((a) => a.slug === arcSlug)
// Ark bilinmiyorsa gizleme. Yanlış pozitif, sitenin yarısını
// sebepsiz karartmak demek — belirsizlikte göstermek daha az zararlı.
if (targetIndex === -1 || currentArcIndex === -1) return false
return targetIndex > currentArcIndex
}, [mounted, state.enabled, currentArcIndex])O `targetIndex === -1` kontrolü kasıtlı ve önemli. Bir içeriğin arkı etiketlenmemişse gizlemiyorum. Alternatifi "bilmiyorsan sakla" olurdu ve o kural, etiketi eksik tek bir kayıt yüzünden sayfanın yarısını karartıyordu.
Bir de `mounted` kontrolü var. Tercih `localStorage`'da duruyor, yani sunucu bilmiyor. Bu kontrol olmadan sayfa önce her şeyi gösterip sonra karartıyordu — spoiler koruması için mükemmel bir başarısızlık biçimi.
Dikkat
Gizleme mantığını istemciye koyduğumun farkındayım: teknik olarak isteyen HTML kaynağından her şeyi okuyabilir. Bu kabul edilebilir, çünkü tehdit modeli bir saldırgan değil — kendi gözünü korumaya çalışan bir okuyucu. Kilit değil, perde.
Kullanıcıya "Neredesin" Diye Sormanın Yolu
Doğru sorunun ne olduğunu bulmak da ayrı bir işti. İlk denemem "kaçıncı bölümdesin?" diye bir sayı kutusuydu ve kendim bile dolduramadım. İnsanlar kaçıncı bölümde olduklarını hatırlamıyor — ama hangi olayda olduklarını gayet iyi biliyorlar.
Ark seçtirmeye geçince kendi kullandığım bir şeye dönüştü. Arkları saga başlıkları altında gruplamak da gerekliydi; 36 ark düz bir liste olarak korkutucu görünüyor, 10 saga altında toplanınca değil.
Ekranın köşesindeki düğme de durumu sürekli gösteriyor: kapı açıkken kaçıncı arkta olduğunu yazıyor. Görünmeyen bir koruma, güvenilmeyen bir koruma.
{enabled
? currentArc
? `Spoiler: ${currentArcIndex + 1}/${totalArcs}` // "Spoiler: 14/36"
: 'Spoiler Koru'
: 'Spoiler Ayarı'}Gizlenen Şey Nasıl Görünmeli
Burada iki seçenek vardı: içeriği tamamen kaldırmak ya da üstünü örtmek.
Kaldırmayı denedim ve tuhaf oldu — ark listesi ortadan kesiliyor, kullanıcı sitenin bozuk olduğunu sanıyor. Üstelik bir şeyin var olduğunu bilmek spoiler değil; ne olduğunu bilmek spoiler.
Şimdi kart yerinde duruyor, görseli bulanık, adı gizli ve üstünde "Spoiler" yazıyor. Tıklayınca tek o kart açılıyor — kapı kapanmıyor, sadece o kart için taviz veriliyor.
İyi bir spoiler koruması, korumayı kaldırma kararını da kullanıcıya bırakır. "Bunu görmek istiyor musun" sorusu, sorunun kendisi spoiler olmadığı sürece adil bir soru.
Erişilebilirlik tarafında da bir detay var: bulanıklaştırılmış kartın `aria-label`'ı içeriği ele vermiyor. Ekran okuyucu "Spoiler — gizli arc, göstermek için tıkla" diyor, ark adını okumuyor. Görselin `alt` metni de aynı şekilde değişiyor. Bulanıklık CSS'te, ama ekran okuyucu CSS görmez.
Karakter İlişkileri: Fizik Motoru Yazdım, Sonra Sildim
Sitenin ikinci uğraştırıcı parçası karakter ilişkileri ekranıydı ve orada da beklemediğim bir yere vardım.
Elimde 25 karakter ve aralarında elle yazılmış 29 bağ vardı — nakama, aile, rakiplik, düşmanlık, hoca-öğrenci, ittifak. Bunu force-directed bir grafla çizdim: düğümler birbirini iter, bağlı olanlar çeker, sistem kendi kendine dengeye oturur.
const REPULSION = 800, SPRING = 0.02, DAMPING = 0.9
function step(nodes, links, dt) {
// itme → çekme → sönümleme
// ...
for (const n of nodes) {
n.vx *= DAMPING; n.vy *= DAMPING
// Bu iki satırı ilk yazışta unutmuştum: hızlar hesaplanıyor ama
// konuma hiç uygulanmıyordu, yani hiçbir düğüm kıpırdamıyordu.
n.x += n.vx * dt; n.y += n.vy * dt
}
}Çalıştı ve güzel görünüyordu. Bir hafta sonra sildim, çünkü kendim bile kullanmıyordum.
- 1Her açılışta yerleşim farklı çıkıyordu. Luffy bir seferinde solda, bir seferinde sağda; ikinci ziyaretinde hiçbir şey tanıdık gelmiyor.
- 2Simülasyon otururken düğümler zıplıyordu. Bunu "yükleniyor" hissi sandım; kullanıcı "bozuk" olarak okudu.
- 3Mobilde 25 düğüm 375 piksele sığmıyor. Sürükle-yakınlaştır gerekiyordu ve o hareket sayfanın kendi kaydırmasıyla çakışıyordu.
- 4Bir karaktere tıklayınca ilişkilerini görmek istiyorsun; fizik yerleşiminde ilgili düğümler ekranın dört bir yanına dağılmış oluyor.
Yerine karakterleri bir çembere eşit aralıklarla dizdim. Sekiz satır, fizik yok, rastgelelik yok.
function getCircularLayout(count: number, cx: number, cy: number, r: number) {
return Array.from({ length: count }).map((_, i) => {
// -PI/2 kayması ilk karakteri tam tepeye alıyor; onsuz sağdan
// başlıyor ve kompozisyon eğri duruyor.
const angle = (2 * Math.PI * i) / count - Math.PI / 2
return { x: cx + r * Math.cos(angle), y: cy + r * Math.sin(angle) }
})
}Asıl kazanç şuydu: yerleşim sabit olduğu için ilişki türüne göre filtre anlamlı hâle geldi. "Sadece düşmanlıklar" dediğinde çember aynı kalıyor, sadece çizgiler değişiyor — karşılaştırma yapabiliyorsun. Force-directed'da her filtre yeni bir düzen demekti.
Force-directed layout bir keşif aracıdır: yapısını bilmediğin bir grafta küme aramak için iyidir. Ben zaten yapıyı biliyordum — 25 karakter, elle yazdığım 29 bağ.
Ne Öğrendim
İkisi de aynı derse çıkıyor aslında. Spoiler kapısında da, grafta da ilk çözümüm teknik olarak daha etkileyici olandı: "önemli olayı" tespit etmeye çalışmak, fiziği simüle etmek. İkisinde de işe yarayan çözüm daha aptal olanıydı — sıralı bir dizide indeks karşılaştırmak, düğümleri çembere dizmek.
İkinci ders spoiler tarafından: bir koruma özelliğinin en önemli parçası, koruduğunu göstermesi. Sessizce çalışan bir spoiler kapısına kimse güvenmez ve güvenilmeyen bir koruma kapatılır.
Sildiğim fizik kodunu hâlâ bir yerde saklıyorum. Karakter sayısı üç haneye çıkarsa geri getireceğim — o zaman gerçekten keşif aracına ihtiyacım olacak.