import type { ReactNode } from "react"; import { AbsoluteFill, Easing, Sequence, interpolate, useCurrentFrame } from "remotion"; import { Ikon, type IkonAdi } from "./ikonlar"; import { SahneImleci, TakipKamera, imlecKonumu, type Durak } from "./imlec"; import { PikselKonturDolgu, PikselSheen } from "./kinetik"; import { LogoMark } from "./logo"; import { RiskEtiketi, Sahne, TuruncuCta, useDuzen } from "./ortak"; import { Eyebrow, Wordmark } from "./piksel"; import { vurguStil } from "./sahneler-1"; import { Kamera, MaskeSatir, OdakDisiPikseller } from "./sinema"; import { FONT, R, ara, hash, trSayi, yay, zipla } from "./tema"; import { VURUSLAR } from "./vurus"; import ZAMAN from "./zaman.json"; /* ------------------------------------------------------------------ */ /* Off — sessizlik: "Off. Kafan mı karıştı?" imleç kararsız dolanır */ /* ------------------------------------------------------------------ */ export function Off() { const { dikey, w, h } = useDuzen(); const cx = w / 2 + (dikey ? 170 : 330); const cy = h / 2 + (dikey ? 160 : 90); // Kararsız imleç: küçük kıvrımlar, sonra kadrajın ortasına (lens buradan patlar) const duraklar: Durak[] = [ { f: 14, x: cx, y: cy }, { f: 22, x: cx - 40, y: cy + 20, g: 8 }, { f: 30, x: cx + 26, y: cy - 24, g: 8 }, { f: 38, x: cx - 18, y: cy + 30, g: 8 }, { f: 58, x: w / 2, y: h / 2, g: 14 }, ]; return (
Off.
Kafan mı karıştı?
); } /* ------------------------------------------------------------------ */ /* Danışman — BAM: DanışmanlaKonuş */ /* ------------------------------------------------------------------ */ function DanismanlaKonus({ boy }: { boy: number }) { return ( Danışmanla Konuş ); } export function Danisman() { const frame = useCurrentFrame(); const { dikey, w, h } = useDuzen(); // BAM (15. kare, lens kesmesi) anında büyükten oturur const otur = interpolate(frame, [15, 26], [1.3, 1], { extrapolateLeft: "clamp", extrapolateRight: "clamp", easing: Easing.out(Easing.cubic) }); // Patlayan pikseller const kareler: ReactNode[] = []; const d = frame - 15; if (d >= 0 && d < 40) { for (let n = 0; n < 28; n++) { const aci = (n / 28) * Math.PI * 2 + hash(n, 2, 7); const uz = (dikey ? 260 : 300) + Easing.out(Easing.cubic)(Math.min(1, d / 30)) * (dikey ? 420 : 620) * (0.5 + hash(n, 3, 1) * 0.5); const G = 24; const x = Math.round((w / 2 + Math.cos(aci) * uz) / G) * G; const y = Math.round((h / 2 - 40 + Math.sin(aci) * uz * 0.7) / G) * G; const b = 30 * (1 - d / 42); kareler.push(); } } return ( {kareler}
Aklına takılanı sor.
Gece 3'te bile. Sıkılmaz, yargılamaz.
); } /* ------------------------------------------------------------------ */ /* Sohbet — tam ekran danışman; imleç yalnız yazarken/gönderirken */ /* ------------------------------------------------------------------ */ // Gerçek YÖK Atlas 2026 verisi: İzmir, İngilizce Bilgisayar Mühendisliği, devlet const SONUCLAR = [ { uni: "Ege Üniversitesi", taban: 44589, risk: "riskli" as const }, { uni: "Dokuz Eylül Üniversitesi", taban: 52071, risk: "az-riskli" as const }, { uni: "İzmir Katip Çelebi Üniversitesi", taban: 71833, risk: "guvenli" as const }, ]; const TS = ZAMAN.tiklar.sohbet; // [girdi1, gönder1, girdi2, gönder2] const YS = ZAMAN.yazma.sohbet; type Mesaj = | { tur: "kullanici"; metin: string; yaz: number[]; gonder: number } | { tur: "arac"; ad: string; detay: string; bas: number; bitis: number } | { tur: "cevap"; bas: number; tahmin: number; Icerik: () => ReactNode }; function SonucListesi({ olcek }: { olcek: number }) { const frame = useCurrentFrame(); return (
Sıralamana göre İzmir'de üç devlet seçeneği var:
{SONUCLAR.map((s, i) => { const g = yay(frame, 100 + i * 7, 16); return (
{s.uni}
Bilgisayar Mühendisliği · Devlet
Taban {trSayi(s.taban)}
); })}
); } export function Sohbet() { const frame = useCurrentFrame(); const { dikey, w, h } = useDuzen(); const o = dikey ? 1 : 0.86; // ölçek const L = dikey ? { x: 50, w: 980, ustY: 80, ustH: 130, altY: 1630, altH: 170 } : { x: 260, w: 1400, ustY: 30, ustH: 110, altY: 900, altH: 130 }; const gonderX = L.x + L.w - L.altH / 2 - 10; const altMerkez = L.altY + L.altH / 2; const SENARYO: Mesaj[] = [ { tur: "kullanici", metin: "İzmir'de İngilizce bilgisayar mühendisliği var mı?", yaz: YS[0], gonder: TS[1] }, { tur: "arac", ad: "program_ara", detay: "İzmir · İngilizce", bas: TS[1] + 6, bitis: TS[1] + 32 }, { tur: "cevap", bas: 90, tahmin: 620 * o, Icerik: () => }, { tur: "kullanici", metin: "Aslında İzmir dışına çıkmak istemiyorum.", yaz: YS[1], gonder: TS[3] }, { tur: "arac", ad: "profil_guncelle", detay: "şehir: İzmir", bas: TS[3] + 6, bitis: TS[3] + 22 }, { tur: "cevap", bas: 224, tahmin: 260 * o, Icerik: () => (
Tamam, profilini güncelledim. Yeni listende yalnız İzmir'deki programlar olacak.
Şehir: İzmir
), }, ]; const yazilan = (() => { for (const m of SENARYO) { if (m.tur !== "kullanici") continue; if (frame >= m.yaz[0] && frame < m.gonder) { const n = Math.floor(ara(frame, [m.yaz[0], m.yaz[1]], [0, m.metin.length], (t) => t)); return m.metin.slice(0, n); } } return ""; })(); const basis = TS.some((t, i) => i % 2 === 1 && frame >= t - 2 && frame < t + 3); const girdiX = L.x + 260; const imlec1: Durak[] = [ { f: 8, x: w * 0.72, y: altMerkez - 260 }, { f: TS[0], x: girdiX, y: altMerkez + 10, g: 12 }, { f: TS[1], x: gonderX, y: altMerkez, g: 12 }, { f: 72, x: gonderX - 60, y: altMerkez - 220, g: 12 }, ]; const imlec2: Durak[] = [ { f: 156, x: w * 0.7, y: altMerkez - 300 }, { f: TS[2], x: girdiX, y: altMerkez + 10, g: 10 }, { f: TS[3], x: gonderX, y: altMerkez, g: 12 }, { f: 216, x: gonderX - 80, y: altMerkez - 240, g: 12 }, ]; const nokta = (f: number) => (f < 120 ? imlecKonumu(imlec1, f) : imlecKonumu(imlec2, f)); return ( {/* Üst şerit */}
{frame >= TS[3] ? 58 : frame >= TS[1] ? 59 : 60} soru hakkı
{/* Mesajlar */}
{SENARYO.map((m, i) => { const bas = m.tur === "kullanici" ? m.gonder : m.bas; if (frame < bas) return null; const g = yay(frame, bas, 16); const tahmin = m.tur === "kullanici" ? 200 * o : m.tur === "arac" ? 90 * o : m.tahmin; const kap = { maxHeight: g >= 0.999 ? undefined : g * tahmin, overflow: "hidden" as const, flexShrink: 0 }; if (m.tur === "kullanici") { return (
{m.metin}
); } if (m.tur === "arac") { const bitti = frame >= m.bitis; return (
{bitti ? : } {m.ad} · {m.detay} {!bitti ? · YÖK Atlas'ta arıyor{".".repeat(1 + (Math.floor(frame / 6) % 3))} : null}
); } return (
); })}
{/* Yazma kutusu */}
{yazilan || "Listen hakkında bir şey sor…"} {yazilan && Math.floor(frame / 8) % 2 === 0 ? | : null}
Havuz dışına çıkamaz, uydurma bölüm önermez
); } /* ------------------------------------------------------------------ */ /* Paket — Apple hızlı kesmeleri: trend, PDF, revizyon */ /* ------------------------------------------------------------------ */ const TREND = [37831, 27534, 21602, 30797, 42108, 55158]; // AYBÜ Bilgisayar, 2021–2026 function PaketKarti({ ikon, baslik, alt, children }: { ikon: IkonAdi; baslik: string; alt: string; children: ReactNode }) { const frame = useCurrentFrame(); const { dikey } = useDuzen(); return (
{baslik}
{alt}
{children}
); } function TrendGorseli() { const frame = useCurrentFrame(); const { dikey } = useDuzen(); const gW = dikey ? 860 : 760; const gH = dikey ? 420 : 440; const noktalar = TREND.map((v, i) => [(i / (TREND.length - 1)) * gW, ((v - 15000) / 45000) * gH]); const c = ara(frame, [6, 34], [0, 1]); const yol = noktalar.map((p, i) => `${i ? "L" : "M"}${p[0]},${p[1]}`).join(" "); return ( {noktalar.map((p, i) => ( = i / (TREND.length - 1) ? 1 : 0}> {2021 + i} ))} ); } function PdfGorseli() { const frame = useCurrentFrame(); const { dikey } = useDuzen(); const k = dikey ? 1.6 : 1.5; return (
{[2, 1, 0].map((i) => { const s = yay(frame, 4 + (2 - i) * 5, 18); return (
{i === 0 ? ( <>
{[R.riskli, R.azRiskli, R.guvenli].map((c) => ( ))}
{[1, 0.8, 0.9, 0.6, 0.75, 0.5].map((wd, j) => ( ))} ) : null}
); })}
); } const REVIZYON = [ { ad: "Ege · Bilgisayar Müh.", renk: R.riskli }, { ad: "Dokuz Eylül · Bilgisayar Müh.", renk: R.azRiskli }, { ad: "Katip Çelebi · Bilgisayar Müh.", renk: R.guvenli }, ]; function RevizyonGorseli() { const frame = useCurrentFrame(); // alt dizi içi kare (0–45) const { dikey } = useDuzen(); const [tut, birak] = ZAMAN.tiklar.paket.map((t) => t - 90); const SAT = dikey ? 130 : 120; const g = dikey ? 880 : 760; const ilerle = ara(frame, [tut, birak], [0, 1], Easing.inOut(Easing.cubic)); const tutuldu = frame >= tut && frame < birak + 2; return (
{REVIZYON.map((r, i) => { const gecis = i === 1 ? (2 * ilerle - 0.5) / 0.8 : (2 * ilerle - 1.3) / 0.7; const kay = Easing.inOut(Easing.cubic)(Math.min(1, Math.max(0, gecis))); const y = i === 0 ? ilerle * 2 * SAT : (i - kay) * SAT; const kalk = i === 0 && tutuldu ? 1 : 0; return (
{r.ad} {Array.from({ length: 6 }, (_, k) => ( ))}
); })}
); } export function Paket() { const { dikey, w, h } = useDuzen(); const [tut, birak] = ZAMAN.tiklar.paket; // Revizyon kartında imleç ilk satırı tutup en alta sürükler (kamera takip eder) const SAT = dikey ? 130 : 120; const gW = dikey ? 880 : 760; const kartX = dikey ? (w - gW) / 2 : w * 0.5 + 60 + (w * 0.5 - 220 - gW) / 2; const kartY = dikey ? h / 2 + 230 : h / 2 - (3 * SAT) / 2; const tutX = kartX + gW - 50; const duraklar: Durak[] = [ { f: 92, x: tutX + 120, y: kartY + 360 }, { f: tut, x: tutX, y: kartY + (SAT - 20) / 2, g: 8 }, { f: birak, x: tutX + 24, y: kartY + 2 * SAT + (SAT - 20) / 2, g: birak - tut }, { f: 134, x: tutX + 90, y: kartY + 2 * SAT + 120, g: 10 }, ]; const nokta = (f: number) => imlecKonumu(duraklar, f); return ( ); } /* ------------------------------------------------------------------ */ /* Dürüstlük — Outline Fill Track: "Çünkü kimse veremez." */ /* ------------------------------------------------------------------ */ export function Durust() { const frame = useCurrentFrame(); const { dikey, w } = useDuzen(); const alt = yay(frame, 86, 18); return (
Yerleşme garantisi vermiyoruz.
Veren olursa, uzak dur. Biz riski satır satır gösteririz.
{(["guvenli", "az-riskli", "riskli"] as const).map((r, i) => ( ))}
); } /* ------------------------------------------------------------------ */ /* Hazır — sessizlik */ /* ------------------------------------------------------------------ */ export function Hazir() { const frame = useCurrentFrame(); const { dikey } = useDuzen(); const kare = dikey ? 64 : 56; return (
{[R.logoMavi, R.logoSeftali, R.logoTuruncu].map((c, i) => (
))}
Hazır mısın?
); } /* ------------------------------------------------------------------ */ /* Final — logo + tipografi + buton; tık → çöküş → boş ekran (döngü) */ /* ------------------------------------------------------------------ */ export function Final() { const frame = useCurrentFrame(); const { dikey, w, h } = useDuzen(); const [tik] = ZAMAN.tiklar.final; const otur = interpolate(frame, [15, 26], [1.2, 1], { extrapolateLeft: "clamp", extrapolateRight: "clamp", easing: Easing.out(Easing.cubic) }); const bas = ara(frame, [tik - 2, tik + 1], [0, 1]) * (1 - ara(frame, [tik + 5, tik + 10], [0, 1])); const cok = ara(frame, [tik + 4, tik + 30], [0, 1], Easing.bezier(0.7, 0, 0.84, 0)); // Buton kadrajda sabit bir noktada (imleç ve çöküş kökeni) const butonY = dikey ? h / 2 + 350 : h / 2 + 260; const duraklar: Durak[] = [ { f: 84, x: w / 2 + (dikey ? 300 : 520), y: butonY + (dikey ? 380 : 200) }, { f: tik - 4, x: w / 2 + 60, y: butonY + 8, g: 26 }, { f: tik, x: w / 2 + 60, y: butonY + 8 }, { f: 170, x: w / 2, y: h / 2, g: 30 }, ]; const nokta = (f: number) => imlecKonumu(duraklar, f); return (
Listeni kur, panikle değil.
); }