feat: KolayTercih lansman videosu (Remotion, v3)

64 saniyelik, kesintisiz döngü lansman filmi; 9:16 ve 16:9.
- remocn.dev bileşenleri (sheen-slide-in, tracking-in, gradient-scale-cut-text,
  outline-fill-track-text, perspective-marquee, path-ride, zoom-blur, lens-zoom)
  KolayTercih piksel diline uyarlandı (src/kinetik.tsx)
- Sahne içi imleç + imleci takip eden kamera, piksel iz ve tık patlaması
- Sessizlik → BAM kurgusu (lensZoom), punch zoom ve hareket bulanıklığı
- Müzik ve efektler numpy ile sentezlendi (ses/uret.py), zaman.json tek kaynak
- Örnek taban sıralamaları gerçek YÖK Atlas 2026 kayıtları

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
bilalgursen
2026-09-30 20:22:38 +03:00
commit dc5c87b6aa
61 changed files with 11111 additions and 0 deletions

252
src/sahneler-1.tsx Normal file
View File

@@ -0,0 +1,252 @@
import { measureText } from "@remotion/layout-utils";
import { AbsoluteFill, Easing, interpolate, useCurrentFrame } from "remotion";
import { IzAc, PikselDevKesme, PikselMarquee, PikselSheen } from "./kinetik";
import { LogoMark } from "./logo";
import { Sahne, useDuzen } from "./ortak";
import { Eyebrow, KelimeKelime, Wordmark } from "./piksel";
import { Kamera, MaskeSatir, OdakDisiPikseller } from "./sinema";
import { FONT, R, ara, hash, trSayi, zipla } from "./tema";
import { VURUSLAR } from "./vurus";
export const vurguStil = {
fontFamily: FONT.vurgu,
fontStyle: "italic" as const,
fontWeight: 800,
letterSpacing: "-0.04em",
color: R.primary,
};
/* ------------------------------------------------------------------ */
/* Açılış — boş ekran → "45.320." (Track Text) → tebrik */
/* ------------------------------------------------------------------ */
export function Acilis() {
const { dikey } = useDuzen();
return (
<Sahne>
<Kamera anahtarlar={[{ f: 0, s: 1.08 }, { f: 110, s: 1 }]} vuruslar={VURUSLAR.acilis}>
<AbsoluteFill style={{ alignItems: "center", justifyContent: "center", gap: 44, textAlign: "center" }}>
<MaskeSatir gecikme={8} sure={18}>
<Eyebrow olcek={dikey ? 2.8 : 2.4}>YKS sonuçları açıklandı</Eyebrow>
</MaskeSatir>
<IzAc gecikme={14} fontSize={dikey ? 250 : 240}>
45.320<span style={{ color: R.primary }}>.</span>
</IzAc>
<div style={{ fontFamily: FONT.baslik, fontWeight: 700, fontSize: dikey ? 88 : 84, lineHeight: 1.1, maxWidth: dikey ? 960 : 1600 }}>
<MaskeSatir gecikme={42}>Sıralaman geldi. Tebrikler!</MaskeSatir>
<MaskeSatir gecikme={56} style={{ color: R.slate500 }}>
Sırada küçük bir <span style={vurguStil}>bulmaca</span> var.
</MaskeSatir>
</div>
</AbsoluteFill>
</Kamera>
</Sahne>
);
}
/* ------------------------------------------------------------------ */
/* Seçenekler — Perspective Marquee: akıp giden bölümler, 21.000+ / 24 */
/* ------------------------------------------------------------------ */
const UST_SIRA = ["Bilgisayar Mühendisliği", "Hukuk", "Tıp", "Psikoloji", "Mimarlık", "İşletme", "Hemşirelik"];
const ALT_SIRA = ["Diş Hekimliği", "Endüstri Mühendisliği", "Öğretmenlik", "Uluslararası İlişkiler", "Yazılım Mühendisliği", "Eczacılık"];
export function Secenek() {
const frame = useCurrentFrame();
const { dikey, h } = useDuzen();
const sayi = ara(frame, [4, 40], [0, 21000], Easing.bezier(0.16, 1, 0.3, 1));
const arti = zipla(frame, 38);
return (
<Sahne>
<Kamera anahtarlar={[{ f: 0, s: 1.02 }, { f: 125, s: 1.12 }]} vuruslar={VURUSLAR.secenek}>
<div style={{ position: "absolute", left: 0, right: 0, top: h * (dikey ? 0.2 : 0.14) - 60 }}>
<PikselMarquee ogeler={UST_SIRA} fontSize={dikey ? 100 : 96} hiz={6} renk={R.slate400} donY={-22} donX={10} />
</div>
<div style={{ position: "absolute", left: 0, right: 0, top: h * (dikey ? 0.8 : 0.86) - 60 }}>
<PikselMarquee ogeler={ALT_SIRA} fontSize={dikey ? 100 : 96} hiz={5} renk={R.slate400} donY={22} donX={-10} ters />
</div>
<AbsoluteFill style={{ alignItems: "center", justifyContent: "center", textAlign: "center" }}>
<div style={{ fontFamily: FONT.baslik, fontWeight: 700, fontSize: dikey ? 230 : 240, lineHeight: 1, color: R.slate900, letterSpacing: "-0.01em" }}>
{trSayi(sayi)}
<span style={{ display: "inline-block", color: R.primary, transform: `scale(${Math.max(0, arti)})` }}>+</span>
</div>
<div style={{ marginTop: 16, fontSize: dikey ? 46 : 42, fontWeight: 600, color: R.slate500 }}>program var.</div>
<div style={{ marginTop: dikey ? 70 : 50 }}>
<PikselSheen gecikme={50} fontSize={dikey ? 86 : 88}>
Senin <span style={{ color: R.primary }}>24</span> hakkın var.
</PikselSheen>
</div>
</AbsoluteFill>
</Kamera>
</Sahne>
);
}
/* ------------------------------------------------------------------ */
/* Kaos — sekmeler gibi açılan sorular, sonra ani sessizlik */
/* ------------------------------------------------------------------ */
const SORULAR = [
"Geçen yılın tabanı yeter mi?",
"Kontenjan düştü mü?",
"Hangi şehir?",
"Devlet mi, vakıf mı?",
"Danışman 3.000 TL mi?!",
"Teyzem “hukuk yaz” dedi",
"Ya boşta kalırsam?",
"Hangisi kaçıncı sıraya?",
];
const YER_YATAY = [
[-0.62, -0.62], [0.55, -0.7], [-0.78, -0.08], [0.74, -0.18],
[-0.6, 0.55], [0.58, 0.52], [-0.12, 0.8], [0.1, -0.86],
];
const YER_DIKEY = [
[-0.38, -0.74], [0.36, -0.6], [-0.42, -0.42], [0.4, -0.3],
[-0.36, 0.34], [0.38, 0.48], [-0.3, 0.66], [0.32, 0.8],
];
export function Kaos() {
const frame = useCurrentFrame();
const { dikey, w, h } = useDuzen();
const yerler = dikey ? YER_DIKEY : YER_YATAY;
// Sonda kartlar hızlanır, ekran doluyor hissi; sonra sessizliğe sert kesme
const telas = ara(frame, [60, 104], [0, 1], Easing.in(Easing.quad));
return (
<Sahne>
<Kamera anahtarlar={[{ f: 0, s: 0.96, r: -1.2 }, { f: 104, s: 1.14, r: 0.8 }]} vuruslar={VURUSLAR.kaos}>
{SORULAR.map((s, i) => {
const [nx, ny] = yerler[i];
const g = zipla(frame, 6 + i * 5);
const hizli = 1 + telas * 3;
const yuzX = Math.sin(((frame + i * 20) / 18) * hizli) * (16 + telas * 20);
const yuzY = Math.cos(((frame + i * 13) / 15) * hizli) * (14 + telas * 18);
// Sırayla "sekme değiştirme" vurgusu
const aktif = Math.floor(frame / 9) % SORULAR.length === i && frame > 20;
return (
<div
key={s}
style={{
position: "absolute",
left: w / 2 + nx * (w / 2) + yuzX,
top: h / 2 + ny * (h / 2) + yuzY,
transform: `translate(-50%, -50%) scale(${Math.max(0, g) * (aktif ? 1.07 : 1)}) rotate(${(hash(i, 1, 2) - 0.5) * 10}deg)`,
background: aktif ? "#fff7ed" : R.beyaz,
border: `3px solid ${aktif ? R.turuncu : R.slate200}`,
borderRadius: 30,
padding: dikey ? "24px 32px" : "22px 30px",
fontSize: dikey ? 38 : 34,
fontWeight: 600,
color: aktif ? R.turuncu : R.slate700,
whiteSpace: "nowrap",
boxShadow: "0 12px 30px rgba(15,23,42,0.08)",
}}
>
{s}
</div>
);
})}
<AbsoluteFill style={{ alignItems: "center", justifyContent: "center" }}>
<h2
style={{
margin: 0,
textAlign: "center",
fontFamily: FONT.baslik,
fontWeight: 700,
fontSize: 104,
lineHeight: 1.08,
maxWidth: dikey ? 800 : 1200,
background: `radial-gradient(closest-side, ${R.slate50} 60%, rgba(248,250,252,0))`,
padding: "40px 60px",
}}
>
<KelimeKelime metin="Kafanda yüz sekme açık, değil mi?" gecikme={4} aralik={3} vurgu={["yüz", "sekme"]} />
</h2>
</AbsoluteFill>
</Kamera>
</Sahne>
);
}
/* ------------------------------------------------------------------ */
/* Nefes — sessizlik. Tek bir turuncu piksel nefes alır. */
/* ------------------------------------------------------------------ */
export function Nefes() {
const frame = useCurrentFrame();
const { dikey } = useDuzen();
const nefes = 1 + 0.12 * Math.sin((frame / 30) * Math.PI * 1.1);
const kare = dikey ? 64 : 56;
return (
<Sahne zemin={R.beyaz}>
<AbsoluteFill style={{ alignItems: "center", justifyContent: "center", gap: 56 }}>
<div
style={{
width: kare,
height: kare,
borderRadius: kare * 0.23,
background: R.logoTuruncu,
opacity: interpolate(frame, [2, 12], [0, 1], { extrapolateLeft: "clamp", extrapolateRight: "clamp" }),
transform: `scale(${nefes})`,
}}
/>
<div style={{ fontFamily: FONT.baslik, fontWeight: 700, fontSize: dikey ? 64 : 60, color: R.slate400 }}>
<MaskeSatir gecikme={10} sure={24}>
Derin bir nefes al.
</MaskeSatir>
</div>
</AbsoluteFill>
</Sahne>
);
}
/* ------------------------------------------------------------------ */
/* Logo — BAM: dev piksel kelime süpürür, logoya kesilir, slogan sheen */
/* ------------------------------------------------------------------ */
export function LogoSahnesi() {
const frame = useCurrentFrame();
const { dikey } = useDuzen();
const markBoy = dikey ? 250 : 230;
const yaziBoy = dikey ? 130 : 150;
// Kilit: işaret + yazı (yatayda yan yana, dikeyde alt alta)
const yaziGen =
measureText({ text: "Kolay", fontFamily: "Bricolage", fontSize: yaziBoy, fontWeight: "300", letterSpacing: `${-0.05 * yaziBoy}px` }).width +
measureText({ text: "Tercih", fontFamily: "Bricolage", fontSize: yaziBoy, fontWeight: "600", letterSpacing: `${-0.05 * yaziBoy}px` }).width;
const kilit = (
<div style={{ display: "flex", flexDirection: dikey ? "column" : "row", alignItems: "center", gap: dikey ? 10 : 24, transform: `translateY(${dikey ? -170 : -120}px)` }}>
<LogoMark boy={markBoy} girisYok rotaBas={32} />
<div style={{ width: dikey ? undefined : yaziGen }}>
<Wordmark boy={yaziBoy} />
</div>
</div>
);
return (
<Sahne zemin={R.beyaz}>
<OdakDisiPikseller seed={3} adet={8} guc={ara(frame, [30, 60], [0, 1])} />
<Kamera anahtarlar={[{ f: 0, s: 1.1 }, { f: 130, s: 1 }]} vuruslar={VURUSLAR.logo}>
<PikselDevKesme metin="KolayTercih" devBoy={dikey ? 420 : 520} gecikme={8} kesme={18}>
{kilit}
</PikselDevKesme>
<AbsoluteFill style={{ alignItems: "center", justifyContent: "center", textAlign: "center" }}>
<div style={{ marginTop: dikey ? 560 : 400, display: "flex", flexDirection: "column", alignItems: "center", gap: 6 }}>
{dikey ? (
<>
<PikselSheen gecikme={46} fontSize={76}>
Tercih listeni
</PikselSheen>
<PikselSheen gecikme={52} fontSize={76}>
gerçek veriyle kur,
</PikselSheen>
</>
) : (
<PikselSheen gecikme={46} fontSize={80}>
Tercih listeni gerçek veriyle kur,
</PikselSheen>
)}
<div style={{ fontSize: dikey ? 76 : 80, lineHeight: 1.1 }}>
<MaskeSatir gecikme={62}>
<span style={vurguStil}>panikle</span>
<span style={{ fontFamily: FONT.baslik, fontWeight: 700, color: R.slate900 }}> değil.</span>
</MaskeSatir>
</div>
</div>
</AbsoluteFill>
</Kamera>
</Sahne>
);
}