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 (
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 (
);
}
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 (
{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 (
);
}
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.
);
}