"use client"; // Program duvarı (DESIGN.md §4.29): "24 tercih, binlerce ihtimal" cümlesinin // resmi. Küçük bir pencerede piksel kareler satır satır akar — her kare bir // program; aralarında tam 24 turuncu kare yerinde durur: senin listen. // İmleç yaklaştığı satırları hızlandırır. Veri taşıyan pikseldir (§1.2); // anlamı altındaki cümle söyler, renk tek başına değil (§2.2). // // Esin: React Bits "Squares Terminal" (yanıp sönen kare ızgarası, imlece // tepki veren satırlar). Kod bize ait: koyu CRT ekranı, parlama ve tüp eğimi // alınmadı; açık zemin, hero'nun ızgara ölçüleri (hücre 10, nokta 7.4, köşe 2) // ve doku opaklıkları kullanılır. Tek , yalnız ekrandayken ~30 fps; // hareketi azalt'ta durağan tek kare. import { useEffect, useRef } from "react"; import { cn } from "@/lib/utils"; const HUCRE = 10; const NOKTA = 7.4; const BOSLUK = (HUCRE - NOKTA) / 2; // Hero ile aynı doku paleti const OPAKLIKLAR = [0.3, 0.16, 0.24, 0.12, 0.27, 0.19]; const TERCIH = 24; /** İmlecin satırları etkilediği dikey uzaklık (px) ve en çok ek hız (hücre/sn). */ const ETKI = 46; const EK_HIZ = 9; // Deterministik sözde-rastgele: sunucuda yok ama her ziyarette aynı desen const karma = (x: number, y: number) => { const n = Math.sin(x * 127.1 + y * 311.7) * 43758.5453; return n - Math.floor(n); }; export function ProgramDuvari({ etiket, className, }: { /** Pencere çubuğundaki kısa yazı, ör. "21.000+ program". */ etiket: string; className?: string; }) { const kapRef = useRef(null); const tuvalRef = useRef(null); useEffect(() => { const kap = kapRef.current; const tuval = tuvalRef.current; const ctx = tuval?.getContext("2d"); if (!kap || !tuval || !ctx) return; const azalt = window.matchMedia("(prefers-reduced-motion: reduce)").matches; let en = 0; let boy = 0; let sutun = 0; let satir = 0; let mavi = ""; let turuncu = ""; /** Satır başına kayma (hücre) ve imleç kaynaklı ek hız. */ let kayma: number[] = []; let ekHiz: number[] = []; let tercihler: { c: number; r: number }[] = []; let imlecY: number | null = null; let gorunur = false; let kare = 0; let onceki = 0; let basladi = 0; const renkleriOku = () => { // Tema değişince palet token'ları değişir (§2.3); renk CSS'ten okunur mavi = getComputedStyle(tuval).color; turuncu = getComputedStyle(document.documentElement).getPropertyValue("--color-orange-500").trim() || "#f97316"; }; const olc = () => { const kutu = kap.getBoundingClientRect(); const oran = Math.min(window.devicePixelRatio || 1, 2); en = Math.round(kutu.width); boy = Math.round(kutu.height); tuval.width = en * oran; tuval.height = boy * oran; ctx.setTransform(oran, 0, 0, oran, 0, 0); sutun = Math.ceil(en / HUCRE) + 1; satir = Math.floor(boy / HUCRE); kayma = Array.from({ length: satir }, (_, r) => kayma[r] ?? karma(r, 3) * 40); ekHiz = Array.from({ length: satir }, () => 0); // 24 tercih: kenarlardan bir hücre içeride, birbirine değmeyen sabit kareler tercihler = []; const dolu = new Set(); for (let i = 0; tercihler.length < TERCIH && i < 4000; i++) { const c = 1 + Math.floor(karma(i, 17) * Math.max(1, sutun - 3)); const r = 1 + Math.floor(karma(i, 29) * Math.max(1, satir - 2)); const yakin = [-1, 0, 1].some((dx) => [-1, 0, 1].some((dy) => dolu.has(`${c + dx}:${r + dy}`)), ); if (yakin) continue; dolu.add(`${c}:${r}`); tercihler.push({ c, r }); } }; const kareCiz = (x: number, y: number) => { ctx.beginPath(); if (ctx.roundRect) ctx.roundRect(x, y, NOKTA, NOKTA, 2); else ctx.rect(x, y, NOKTA, NOKTA); ctx.fill(); }; const ciz = (t: number) => { ctx.clearRect(0, 0, en, boy); const ustBosluk = (boy - satir * HUCRE) / 2; ctx.fillStyle = mavi; for (let r = 0; r < satir; r++) { const tam = Math.floor(kayma[r]); const kesir = kayma[r] - tam; // Tek satırlar sola, çift satırlar sağa akar const yon = r % 2 === 0 ? 1 : -1; for (let c = -1; c < sutun; c++) { const kimlik = c - yon * tam; const h = karma(kimlik, r); // Karelerin üçte biri boş: duvar değil doku if (h < 0.34) continue; const taban = OPAKLIKLAR[Math.abs(kimlik * 7 + r * 11) % OPAKLIKLAR.length]; // Yavaş nefes: her kare kendi fazında hafifçe parlar ve söner const nefes = 0.6 + 0.4 * Math.sin(t * 0.9 + h * 6.283); ctx.globalAlpha = taban * nefes; kareCiz((c + yon * kesir) * HUCRE + BOSLUK, ustBosluk + r * HUCRE + BOSLUK); } } // Tercihler: sırayla belirir (110 ms arayla), sonra yerinde kalır ctx.fillStyle = turuncu; const gecen = azalt ? Infinity : t - basladi; tercihler.forEach(({ c, r }, i) => { const yas = gecen - 0.4 - i * 0.11; if (yas <= 0) return; const giris = Math.min(1, yas / 0.3); const x = c * HUCRE + BOSLUK; const y = ustBosluk + r * HUCRE + BOSLUK; // Altındaki akan kareyi sil: turuncu kare tek başına okunur ctx.globalAlpha = 1; ctx.clearRect(c * HUCRE, ustBosluk + r * HUCRE, HUCRE, HUCRE); ctx.globalAlpha = 0.9 * giris; kareCiz(x, y); }); ctx.globalAlpha = 1; }; const adim = (simdi: number) => { kare = requestAnimationFrame(adim); // ~30 fps yeter: kareler yavaş akar if (simdi - onceki < 32) return; const dt = Math.min(0.1, (simdi - onceki) / 1000); onceki = simdi; const ustBosluk = (boy - satir * HUCRE) / 2; for (let r = 0; r < satir; r++) { const merkez = ustBosluk + r * HUCRE + HUCRE / 2; const yakinlik = imlecY == null ? 0 : Math.max(0, 1 - Math.abs(merkez - imlecY) / ETKI); // Ek hız hedefe yumuşak yaklaşır; imleç çekilince yavaşça söner ekHiz[r] += (yakinlik * yakinlik * EK_HIZ - ekHiz[r]) * Math.min(1, dt * 5); kayma[r] += (0.5 + karma(r, 7) * 1.3 + ekHiz[r]) * dt; } ciz(simdi / 1000); }; const baslat = () => { if (kare || azalt || !gorunur) return; onceki = performance.now(); if (!basladi) basladi = onceki / 1000; kare = requestAnimationFrame(adim); }; const durdur = () => { cancelAnimationFrame(kare); kare = 0; }; renkleriOku(); olc(); ciz(0); const boyut = new ResizeObserver(() => { olc(); ciz(performance.now() / 1000); }); boyut.observe(kap); const gorunum = new IntersectionObserver(([kayit]) => { gorunur = kayit.isIntersecting; if (gorunur) baslat(); else durdur(); }); gorunum.observe(kap); const tema = new MutationObserver(() => { renkleriOku(); ciz(performance.now() / 1000); }); tema.observe(document.documentElement, { attributes: true, attributeFilter: ["class"] }); const imlec = (e: PointerEvent) => { if (e.pointerType !== "mouse") return; const kutu = kap.getBoundingClientRect(); const icinde = e.clientX >= kutu.left && e.clientX <= kutu.right && e.clientY >= kutu.top - ETKI && e.clientY <= kutu.bottom + ETKI; imlecY = icinde ? e.clientY - kutu.top : null; }; window.addEventListener("pointermove", imlec, { passive: true }); return () => { durdur(); boyut.disconnect(); gorunum.disconnect(); tema.disconnect(); window.removeEventListener("pointermove", imlec); }; }, []); return (
{/* Mini pencere: aramadaki tarayıcı penceresiyle aynı dil (§4.20) */}
{etiket}
Her kare bir program; turuncular senin 24 tercihin.
); }