Files
kolaytercih-post/src/sahneler-3.tsx
bilalgursen dc5c87b6aa 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>
2026-09-30 20:22:38 +03:00

657 lines
30 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 (
<Sahne zemin={R.beyaz}>
<AbsoluteFill style={{ alignItems: "center", justifyContent: "center", textAlign: "center", gap: 20 }}>
<div style={{ fontFamily: FONT.baslik, fontWeight: 700, fontSize: dikey ? 170 : 180, color: R.slate300, lineHeight: 1 }}>
<MaskeSatir gecikme={2} sure={16}>
Off.
</MaskeSatir>
</div>
<div style={{ fontFamily: FONT.baslik, fontWeight: 700, fontSize: dikey ? 76 : 80, color: R.slate500 }}>
<MaskeSatir gecikme={18} sure={18}>
Kafan mı karıştı?
</MaskeSatir>
</div>
</AbsoluteFill>
<SahneImleci duraklar={duraklar} gorunur={[14, 64]} boy={dikey ? 76 : 64} />
</Sahne>
);
}
/* ------------------------------------------------------------------ */
/* Danışman — BAM: DanışmanlaKonuş */
/* ------------------------------------------------------------------ */
function DanismanlaKonus({ boy }: { boy: number }) {
return (
<span style={{ display: "inline-flex", alignItems: "center", gap: boy * 0.08, fontFamily: FONT.vurgu, fontSize: boy, letterSpacing: "-0.05em", color: R.slate900, whiteSpace: "nowrap" }}>
<LogoMark boy={boy * 1.25} varyant="nabiz" turuncu girisYok />
<span>
<span style={{ fontWeight: 300 }}>Danışmanla</span>
<span style={{ fontWeight: 600 }}>Konuş</span>
</span>
</span>
);
}
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(<rect key={n} x={x - b / 2} y={y - b / 2} width={b} height={b} rx={b * 0.27} fill={[R.turuncu, R.logoSeftali, R.primary][n % 3]} fillOpacity={1 - d / 40} />);
}
}
return (
<Sahne zemin={R.beyaz}>
<OdakDisiPikseller seed={61} adet={8} />
<Kamera anahtarlar={[{ f: 0, s: 1.05 }, { f: 80, s: 1.12 }]} vuruslar={VURUSLAR.danisman}>
<svg width={w} height={h} style={{ position: "absolute", inset: 0 }}>
{kareler}
</svg>
<AbsoluteFill style={{ alignItems: "center", justifyContent: "center", textAlign: "center", gap: 46 }}>
<div style={{ transform: `scale(${otur})` }}>
<DanismanlaKonus boy={dikey ? 110 : 150} />
</div>
<PikselSheen gecikme={28} fontSize={dikey ? 90 : 100}>
Aklına takılanı sor.
</PikselSheen>
<div style={{ fontSize: dikey ? 40 : 40, color: R.slate500 }}>
<MaskeSatir gecikme={40}>Gece 3'te bile. Sıkılmaz, yargılamaz.</MaskeSatir>
</div>
</AbsoluteFill>
</Kamera>
</Sahne>
);
}
/* ------------------------------------------------------------------ */
/* 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 (
<div style={{ display: "flex", flexDirection: "column", gap: 14 * olcek }}>
<div>Sıralamana göre İzmir'de üç devlet seçeneği var:</div>
{SONUCLAR.map((s, i) => {
const g = yay(frame, 100 + i * 7, 16);
return (
<div key={s.uni} style={{ display: "flex", alignItems: "center", gap: 20 * olcek, background: R.beyaz, border: `3px solid ${R.slate200}`, borderRadius: 24 * olcek, padding: `${16 * olcek}px ${22 * olcek}px`, opacity: g, transform: `translateY(${(1 - g) * 14}px)` }}>
<div style={{ flex: 1 }}>
<div style={{ fontWeight: 600, color: R.slate900 }}>{s.uni}</div>
<div style={{ fontSize: "0.72em", color: R.slate500 }}>Bilgisayar Mühendisliği · Devlet</div>
</div>
<div style={{ display: "flex", flexDirection: "column", alignItems: "flex-end", gap: 4 }}>
<RiskEtiketi risk={s.risk} olcek={2 * olcek} />
<span style={{ fontSize: "0.72em", color: R.slate500 }}>
Taban <b style={{ color: R.slate900 }}>{trSayi(s.taban)}</b>
</span>
</div>
</div>
);
})}
</div>
);
}
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: () => <SonucListesi olcek={o} /> },
{ 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: () => (
<div style={{ display: "flex", flexDirection: "column", gap: 16, alignItems: "flex-start" }}>
<div>Tamam, profilini güncelledim. Yeni listende yalnız İzmir'deki programlar olacak.</div>
<span style={{ display: "inline-flex", alignItems: "center", gap: 10, height: 64 * o, padding: `0 ${24 * o}px`, borderRadius: 999, background: R.primary, color: R.beyaz, fontWeight: 600, fontSize: "0.85em", transform: `scale(${Math.max(0, zipla(frame, 242))})`, transformOrigin: "left center" }}>
<Ikon ad="konum" boy={28 * o} />
Şehir: İzmir
<Ikon ad="tik" boy={28 * o} kalinlik={3} />
</span>
</div>
),
},
];
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 (
<Sahne>
<TakipKamera
nokta={nokta}
vuruslar={VURUSLAR.sohbet}
anahtarlar={[
{ f: 0, s: 1, t: 0 },
{ f: TS[0], s: 1.3, t: 0.8 },
{ f: TS[1] + 4, s: 1.3, t: 0.8 },
{ f: 80, s: 1.02, t: 0 },
{ f: 150, s: 1.04, t: 0 },
{ f: TS[2], s: 1.3, t: 0.8 },
{ f: TS[3] + 4, s: 1.3, t: 0.8 },
{ f: 214, s: 1.02, t: 0 },
{ f: 300, s: 1.06, t: 0 },
]}
>
{/* Üst şerit */}
<div style={{ position: "absolute", left: L.x, top: L.ustY, width: L.w, height: L.ustH, display: "flex", alignItems: "center", justifyContent: "space-between", borderBottom: `3px solid ${R.slate200}` }}>
<DanismanlaKonus boy={dikey ? 52 : 50} />
<span style={{ display: "inline-flex", alignItems: "center", height: 64 * o, padding: `0 ${26 * o}px`, borderRadius: 999, border: `3px solid ${R.slate200}`, fontSize: 28 * o, fontWeight: 600, color: R.slate700 }}>
{frame >= TS[3] ? 58 : frame >= TS[1] ? 59 : 60} soru hakkı
</span>
</div>
{/* Mesajlar */}
<div
style={{
position: "absolute",
left: L.x,
width: L.w,
top: L.ustY + L.ustH + 20,
height: L.altY - (L.ustY + L.ustH + 20) - 20,
display: "flex",
flexDirection: "column",
justifyContent: "flex-end",
gap: 26 * o,
overflow: "hidden",
fontSize: 40 * o,
lineHeight: 1.4,
}}
>
{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 (
<div key={i} style={{ ...kap, display: "flex", justifyContent: "flex-end" }}>
<div style={{ maxWidth: "82%", background: R.primary, color: R.beyaz, padding: `${22 * o}px ${30 * o}px`, borderRadius: 34 * o, fontWeight: 600, opacity: g, transform: `translateY(${(1 - g) * 20}px)` }}>{m.metin}</div>
</div>
);
}
if (m.tur === "arac") {
const bitti = frame >= m.bitis;
return (
<div key={i} style={{ ...kap, display: "flex", alignItems: "center", gap: 16 }}>
<span style={{ width: 64 * o, height: 64 * o, borderRadius: 999, border: `3px solid ${R.slate200}`, background: R.beyaz, display: "flex", alignItems: "center", justifyContent: "center" }}>
<LogoMark boy={40 * o} varyant={bitti ? "duruk" : "nabiz"} girisYok />
</span>
<span style={{ display: "inline-flex", alignItems: "center", gap: 12, height: 60 * o, padding: `0 ${24 * o}px`, borderRadius: 999, background: R.slate50, border: `3px solid ${R.slate200}`, fontSize: 26 * o, fontWeight: 600, color: R.slate600, opacity: g, whiteSpace: "nowrap" }}>
{bitti ? <Ikon ad="tik" boy={26 * o} renk={R.guvenliYazi} kalinlik={3} /> : <Ikon ad="arama" boy={26 * o} renk={R.primary} />}
<span style={{ fontFamily: FONT.vurgu, color: R.primary }}>{m.ad}</span>
<span>· {m.detay}</span>
{!bitti ? <span style={{ color: R.slate400 }}>· YÖK Atlas'ta arıyor{".".repeat(1 + (Math.floor(frame / 6) % 3))}</span> : null}
</span>
</div>
);
}
return (
<div key={i} style={{ ...kap, paddingLeft: 80 * o }}>
<div style={{ maxWidth: "94%", background: R.slate100, color: R.slate800, padding: `${24 * o}px ${30 * o}px`, borderRadius: 34 * o, opacity: g, transform: `translateY(${(1 - g) * 20}px)` }}>
<m.Icerik />
</div>
</div>
);
})}
</div>
{/* Yazma kutusu */}
<div
style={{
position: "absolute",
left: L.x,
top: L.altY,
width: L.w,
height: L.altH,
display: "flex",
alignItems: "center",
gap: 20,
borderRadius: 48 * o,
border: `4px solid ${yazilan ? R.primary : R.slate200}`,
boxShadow: yazilan ? "0 0 0 10px rgba(59,130,246,0.16)" : undefined,
background: R.beyaz,
padding: `0 16px 0 ${44 * o}px`,
fontSize: 42 * o,
}}
>
<span style={{ flex: 1, color: yazilan ? R.slate900 : R.slate400, whiteSpace: "nowrap", overflow: "hidden" }}>
{yazilan || "Listen hakkında bir şey sor…"}
{yazilan && Math.floor(frame / 8) % 2 === 0 ? <span style={{ color: R.primary }}>|</span> : null}
</span>
<span style={{ width: L.altH - 36, height: L.altH - 36, borderRadius: 999, background: basis ? R.turuncuKoyu : R.turuncu, display: "flex", alignItems: "center", justifyContent: "center", transform: `scale(${basis ? 0.93 : 1})`, flexShrink: 0 }}>
<Ikon ad="ok" boy={56 * o} renk={R.beyaz} kalinlik={2.6} style={{ transform: "rotate(-90deg)" }} />
</span>
</div>
<div style={{ position: "absolute", left: 0, width: w, top: dikey ? h - 100 : h - 50, display: "flex", justifyContent: "center", opacity: yay(frame, 262, 16) }}>
<Eyebrow olcek={dikey ? 2.3 : 2}>Havuz dışına çıkamaz, uydurma bölüm önermez</Eyebrow>
</div>
<SahneImleci duraklar={imlec1} tiklar={[TS[0], TS[1]]} gorunur={[8, 74]} boy={dikey ? 76 : 64} />
<SahneImleci duraklar={imlec2} tiklar={[TS[2], TS[3]]} gorunur={[156, 218]} boy={dikey ? 76 : 64} />
</TakipKamera>
</Sahne>
);
}
/* ------------------------------------------------------------------ */
/* 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 (
<AbsoluteFill style={{ alignItems: "center", justifyContent: "center", gap: dikey ? 60 : 40, flexDirection: dikey ? "column" : "row", padding: dikey ? "0 70px" : "0 160px" }}>
<div style={{ display: "flex", flexDirection: "column", alignItems: dikey ? "center" : "flex-start", textAlign: dikey ? "center" : "left", gap: 24, flex: dikey ? undefined : 1 }}>
<div style={{ width: 120, height: 120, borderRadius: 32, background: "rgba(59,130,246,0.1)", display: "flex", alignItems: "center", justifyContent: "center", transform: `scale(${Math.max(0, zipla(frame, 0))})` }}>
<Ikon ad={ikon} boy={58} renk={R.primary} />
</div>
<PikselSheen gecikme={2} fontSize={dikey ? 110 : 120}>
{baslik}
</PikselSheen>
<div style={{ fontSize: dikey ? 46 : 46, color: R.slate500, lineHeight: 1.3 }}>
<MaskeSatir gecikme={10}>{alt}</MaskeSatir>
</div>
</div>
<div style={{ flex: dikey ? undefined : 1, display: "flex", justifyContent: "center" }}>{children}</div>
</AbsoluteFill>
);
}
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 (
<svg width={gW + 40} height={gH + 90} viewBox={`-20 -20 ${gW + 40} ${gH + 90}`}>
<path d={yol} fill="none" stroke={R.primary} strokeWidth={10} strokeLinecap="round" strokeLinejoin="round" pathLength={1} strokeDasharray={1} strokeDashoffset={1 - c} />
{noktalar.map((p, i) => (
<g key={i} opacity={c >= i / (TREND.length - 1) ? 1 : 0}>
<rect x={p[0] - 16} y={p[1] - 16} width={32} height={32} rx={9} fill={i === TREND.length - 1 ? R.turuncu : R.primary} />
<text x={p[0]} y={gH + 50} textAnchor="middle" fontSize={28} fontFamily="Clarity City" fontWeight={600} fill={R.slate400}>
{2021 + i}
</text>
</g>
))}
</svg>
);
}
function PdfGorseli() {
const frame = useCurrentFrame();
const { dikey } = useDuzen();
const k = dikey ? 1.6 : 1.5;
return (
<div style={{ position: "relative", width: 330 * k, height: 380 * k }}>
{[2, 1, 0].map((i) => {
const s = yay(frame, 4 + (2 - i) * 5, 18);
return (
<div
key={i}
style={{
position: "absolute",
left: (60 + i * 30) * k,
top: (20 + i * 22) * k,
width: 220 * k,
height: 290 * k,
borderRadius: 26 * k,
background: R.beyaz,
border: `3px solid ${R.slate200}`,
boxShadow: "0 18px 40px rgba(15,23,42,0.08)",
transform: `rotate(${(i - 1) * 7 * s}deg)`,
padding: 26 * k,
display: "flex",
flexDirection: "column",
gap: 16 * k,
}}
>
{i === 0 ? (
<>
<div style={{ display: "flex", gap: 8 * k }}>
{[R.riskli, R.azRiskli, R.guvenli].map((c) => (
<span key={c} style={{ width: 24 * k, height: 24 * k, borderRadius: 7 * k, background: c }} />
))}
</div>
{[1, 0.8, 0.9, 0.6, 0.75, 0.5].map((wd, j) => (
<span key={j} style={{ height: 14 * k, width: `${wd * 100}%`, borderRadius: 7 * k, background: R.slate200 }} />
))}
</>
) : null}
</div>
);
})}
</div>
);
}
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 (
<div style={{ position: "relative", width: g, height: 3 * SAT }}>
{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 (
<div
key={r.ad}
style={{
position: "absolute",
left: kalk * 24,
top: y,
width: "100%",
height: SAT - 20,
borderRadius: 30,
border: `4px solid ${kalk ? R.turuncu : R.slate200}`,
background: kalk ? "#fff7ed" : R.beyaz,
display: "flex",
alignItems: "center",
gap: 24,
padding: "0 32px",
fontSize: dikey ? 40 : 38,
fontWeight: 600,
color: R.slate800,
boxShadow: kalk ? "0 24px 48px rgba(15,23,42,0.16)" : undefined,
transform: `scale(${1 + kalk * 0.04}) rotate(${kalk * -1.5}deg)`,
zIndex: i === 0 ? 2 : 1,
}}
>
<span style={{ width: 24, height: 24, borderRadius: 7, background: r.renk }} />
<span style={{ flex: 1 }}>{r.ad}</span>
<span style={{ display: "grid", gridTemplateColumns: "repeat(2, 9px)", gap: 6 }}>
{Array.from({ length: 6 }, (_, k) => (
<span key={k} style={{ width: 9, height: 9, borderRadius: 3, background: R.slate400 }} />
))}
</span>
</div>
);
})}
</div>
);
}
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 (
<Sahne>
<OdakDisiPikseller seed={71} adet={6} guc={0.6} />
<TakipKamera
nokta={nokta}
vuruslar={VURUSLAR.paket}
anahtarlar={[
{ f: 0, s: 1.06, t: 0 },
{ f: 44, s: 1.0, t: 0 },
{ f: 45, s: 1.06, t: 0 },
{ f: 89, s: 1.0, t: 0 },
{ f: 90, s: 1.04, t: 0 },
{ f: tut, s: dikey ? 1.15 : 1.08, t: dikey ? 0.35 : 0.12 },
{ f: birak, s: dikey ? 1.15 : 1.08, t: dikey ? 0.35 : 0.12 },
{ f: 135, s: 1.05, t: 0.1 },
]}
>
<Sequence durationInFrames={45}>
<PaketKarti ikon="trend" baslik="6 yıllık trend." alt="Her tercihin geçmişi, tek bakışta.">
<TrendGorseli />
</PaketKarti>
</Sequence>
<Sequence from={45} durationInFrames={45}>
<PaketKarti ikon="belge" baslik="PDF rapor." alt="Aile toplantısına hazır.">
<PdfGorseli />
</PaketKarti>
</Sequence>
<Sequence from={90}>
<PaketKarti ikon="yenile" baslik="2 revizyon." alt="Fikrin değişir, listen de.">
<RevizyonGorseli />
</PaketKarti>
</Sequence>
<SahneImleci duraklar={duraklar} tiklar={[tut, birak]} gorunur={[92, 136]} boy={dikey ? 76 : 64} />
</TakipKamera>
</Sahne>
);
}
/* ------------------------------------------------------------------ */
/* 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 (
<Sahne zemin={R.beyaz}>
<Kamera anahtarlar={[{ f: 0, s: 1.02 }, { f: 140, s: 1.12 }]} vuruslar={VURUSLAR.durust}>
<AbsoluteFill style={{ alignItems: "center", justifyContent: "center", textAlign: "center", gap: 40 }}>
<div style={{ width: 140, height: 140, borderRadius: 38, background: R.primary, display: "flex", alignItems: "center", justifyContent: "center", transform: `scale(${Math.max(0, zipla(frame, 0))})` }}>
<Ikon ad="kalkan" boy={76} renk={R.beyaz} />
</div>
<div style={{ fontFamily: FONT.baslik, fontWeight: 700, fontSize: dikey ? 104 : 116, lineHeight: 1.06 }}>
<MaskeSatir gecikme={6}>Yerleşme garantisi</MaskeSatir>
<MaskeSatir gecikme={14}>vermiyoruz.</MaskeSatir>
</div>
<PikselKonturDolgu on="Çünkü" deger="kimse veremez." fontSize={dikey ? 84 : 100} gecikme={30} genislik={w} />
<div style={{ opacity: alt, transform: `translateY(${(1 - alt) * 20}px)`, display: "flex", flexDirection: "column", alignItems: "center", gap: 24, marginTop: 10 }}>
<div style={{ fontSize: 40, color: R.slate600, fontWeight: 600, maxWidth: dikey ? 900 : 1500, lineHeight: 1.4 }}>
Veren olursa, uzak dur. Biz riski satır satır gösteririz.
</div>
<div style={{ display: "flex", gap: 44 }}>
{(["guvenli", "az-riskli", "riskli"] as const).map((r, i) => (
<span key={r} style={{ transform: `scale(${Math.max(0, zipla(frame, 94 + i * 5))})` }}>
<RiskEtiketi risk={r} olcek={3.2} />
</span>
))}
</div>
</div>
</AbsoluteFill>
</Kamera>
</Sahne>
);
}
/* ------------------------------------------------------------------ */
/* Hazır — sessizlik */
/* ------------------------------------------------------------------ */
export function Hazir() {
const frame = useCurrentFrame();
const { dikey } = useDuzen();
const kare = dikey ? 64 : 56;
return (
<Sahne zemin={R.beyaz}>
<AbsoluteFill style={{ alignItems: "center", justifyContent: "center", gap: 50 }}>
<div style={{ display: "flex", gap: kare * 0.22 }}>
{[R.logoMavi, R.logoSeftali, R.logoTuruncu].map((c, i) => (
<div key={c} style={{ width: kare, height: kare, borderRadius: kare * 0.23, background: c, transform: `scale(${Math.max(0, zipla(frame, 4 + i * 6))})` }} />
))}
</div>
<div style={{ fontFamily: FONT.baslik, fontWeight: 700, fontSize: dikey ? 70 : 66, color: R.slate500 }}>
<MaskeSatir gecikme={8}>Hazır mısın?</MaskeSatir>
</div>
</AbsoluteFill>
</Sahne>
);
}
/* ------------------------------------------------------------------ */
/* 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 (
<Sahne>
<TakipKamera
nokta={nokta}
vuruslar={VURUSLAR.final}
anahtarlar={[
{ f: 0, s: 1.06, t: 0 },
{ f: 90, s: 1, t: 0 },
{ f: tik - 6, s: 1.4, t: 1 },
{ f: tik + 10, s: 1.4, t: 1 },
{ f: tik + 36, s: 1, t: 0 },
]}
>
<AbsoluteFill
style={{
alignItems: "center",
justifyContent: "center",
textAlign: "center",
gap: dikey ? 70 : 54,
transformOrigin: `50% ${(butonY / h) * 100}%`,
transform: `scale(${otur * (1 - cok)})`,
opacity: 1 - ara(frame, [tik + 18, tik + 30], [0, 1]),
filter: `blur(${cok * 10}px)`,
}}
>
<div style={{ display: "flex", flexDirection: dikey ? "column" : "row", alignItems: "center", gap: dikey ? 14 : 26 }}>
<LogoMark boy={dikey ? 220 : 180} girisYok rotaBas={24} />
<Wordmark boy={dikey ? 116 : 116} />
</div>
<div style={{ fontFamily: FONT.baslik, fontWeight: 700, fontSize: dikey ? 100 : 108, lineHeight: 1.06 }}>
<MaskeSatir gecikme={20}>Listeni kur,</MaskeSatir>
<MaskeSatir gecikme={28}>
<span style={vurguStil}>panikle</span> değil.
</MaskeSatir>
</div>
<div style={{ transform: `scale(${Math.max(0, zipla(frame, 38))})` }}>
<TuruncuCta metin="kolaytercih.com" boy={dikey ? 1.25 : 1.15} basili={bas} />
</div>
</AbsoluteFill>
<SahneImleci duraklar={duraklar} tiklar={[tik]} gorunur={[84, 176]} boy={dikey ? 80 : 68} />
</TakipKamera>
</Sahne>
);
}