"Phosphor" nasıl yapıldı?
Sayfanın kahraman bölümü gerçek bir CRT monitör gibi davranıyor: cam bombesi, tarama çizgileri, fosfor parlaması ve içinde karakter karakter yazılan canlı bir Findie agent oturumu. Bu rehber iki motoru anlatır: 2D terminal kaynağı ve üstündeki WebGL CRT shader'ı.
Konsept
Findie'nin terminali bir süs değil: ayrık (detached) bir PTY daemon gerçek shell oturumlarını pencereden bağımsız yaşatır; uygulamayı kapatsan da oturum ölmez. "Asla kapanmayan terminal" vaadini anlatmanın en dürüst yolu, sayfanın kendisini bir terminale çevirmekti. Fosfor rengi de markadan geliyor: mavi fosforlu CRT'ler (PC monitörleri) + Findie mavisi (#037BFC).
Katman 1: 2D terminal kaynağı
Görünmez bir canvas'a klasik bir terminal çizilir; WebGL bu canvas'ı doku (texture) olarak alır:
- Satır listesi
{text, color, big}nesneleri; her karede yalnızca görünür satırlar çizilir. - Daktilo kuyruğu: senaryo
[metin, renk, cps]üçlüleri; her karedecps × dtkadar karakter eklenir. Komut satırlarından sonra 500ms nefes, banner'dan sonra 700ms. - Senaryo gerçek Findie akışı:
findie run "..."→ kart BACKLOG'dan alınır → izole worktree açılır → agent düzenler → testler → merge kuyruğu →findie/trunk fast-forwarded. Üç görev dönüşümlü oynar; bütçe satırı amber, onaylar mavi. - Parlama ucuz yoldan: metin
shadowBlurile çizilir (fosfor halesi), piksel fontu VT323. - Kaynak canvas ekranın 0.72× çözünürlüğünde: CRT zaten yumuşak; küçük doku = her karede daha ucuz
texImage2D.
Katman 2: WebGL CRT shader'ı
vec2 c = v - .5; float r2 = dot(c, c);
vec2 uv = v + c * (r2 * .2 + r2*r2 * .14); // cam bombesi (barrel)
col.r = tex(uv + vec2(.0014, 0)).r; // kromatik sapma
col.b = tex(uv - vec2(.0014, 0)).b;
col += 4tapBlur * .16; // ucuz bloom
col *= .9 + .1 * sin(uv.y * H * 2.4); // tarama çizgileri
col *= .955 + .045 * sin(uv.x * W * 3.14); // aralık maskesi (grille)
col *= vignette; col *= 1. + .02 * sin(T*93.); // köşe kararması + titreme
Hepsi tek fragment shader'da, tek üçgenle (fullscreen triangle) çiziliyor. WebGL yoksa kaynak canvas görünür yapılır: distorsiyonsuz ama yine canlı bir terminal (fail-soft).
Ürün diliyle DOM bölümleri
Kahraman sahne "dünya", gerisi ürünün kendi arayüz dili (kullanıcı geri bildirimiyle netleşen kural):
- Geist (ürünün fontu, node_modules'tan woff2 vendorlandı) + Geist Mono etiketler.
- Koyu nötr yüzeyler (
#10141d), 14px yuvarlak köşeli kartlar, incergbaborder'lar; hover'da hafif yükselme. - Findie mavisi primary buton (10px radius, iç highlight, mavi glow gölge) ve pill "çip" komponentleri: üründeki model rozetlerinin akrabası.
- Gerçek ekran görüntüsü ürün-stili çerçevede: üst bilgi barı + ince border + üzerine %50 opak tarama çizgisi overlay'i (dünya sürekliliği).
Performans ve erişilebilirlik
- Feed yalnızca üst banda çizilir (masaüstü %56, mobil %30): DOM başlığıyla asla çakışmaz. Bu sınır 2. turda mobil ekran görüntüsünün yakaladığı çakışmadan doğdu.
- Sekme görünür değilken döngü durur (
IntersectionObserver). prefers-reduced-motion/?static=1: senaryo anında tamamlanır, tek distorsiyonlu kare çizilir, imleç yanıp sönmez.- Anlatının tamamı DOM'da; canvas'lar
aria-hidden.
Üç turlu eleştiri döngüsü
- Tur 1: feed başlığın üzerine akıyordu; üst-bant sınırı eklendi. Barrel neredeyse görünmüyordu; katsayı artırıldı. Çift
h1temizlendi. - Tur 2: mobilde bant hâlâ genişti; 844px yükseklikte %30'a indirildi. Font ölçüsü
SW/58'e büyütüldü. - Tur 3: masaüstü + mobil + statik tam sayfa doğrulaması, konsol taraması, canlı deploy.
Sen de kur
# 1. İçeriği offscreen 2D canvas'a çiz (daktilo kuyruğu + shadowBlur fosfor)
# 2. Fullscreen-triangle WebGL: barrel + chroma + scanline + grille + vignette
# 3. Kaynağı 0.7x çözünürlükte tut; her frame texImage2D ile yükle
# 4. Feed'i üst banda kilitle; DOM başlığıyla çakışmasın
# 5. reduced-motion: senaryoyu anında bitir, tek kare çiz
npx wrangler pages deploy ./site --project-name=my-crt