Files
kolaytercih-post/src/eski/sahneler-urun.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

743 lines
33 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, useCurrentFrame } from "remotion";
import { YagmurHaritasi } from "./harita";
import { Ikon, type IkonAdi } from "./ikonlar";
import { LogoMark } from "./logo";
import { BaslikBlogu, Kutu, RiskEtiketi, Sahne, TuruncuCta, useDuzen } from "./ortak";
import { Eyebrow, PixelCorner, Wordmark } from "./piksel";
import { Kamera, MaskeSatir, OdakDisiPikseller, degerAl, type Anahtar } from "./sinema";
import { Telefon } from "./telefon";
import { FONT, R, ara, trSayi, yay, zipla } from "./tema";
import { VURUSLAR } from "./vurus";
const vurguStil = {
fontFamily: FONT.vurgu,
fontStyle: "italic" as const,
fontWeight: 800,
letterSpacing: "-0.04em",
color: R.primary,
};
/* ------------------------------------------------------------------ */
/* Adım 1 — 3B telefonda sihirbaz: sıralama, çipler, CTA */
/* ------------------------------------------------------------------ */
const CIPLER = ["Mühendislik", "Bilgisayar", "İstanbul", "Ankara", "İzmir", "Devlet"];
// Seçim kareleri imleç tıklarıyla aynı (zaman.json)
const SECIM: Record<number, number> = { 0: 58, 1: 66, 2: 74, 3: 82, 5: 92 };
function SihirbazEkrani() {
const frame = useCurrentFrame();
const metin = "45.320";
const yazilan = metin.slice(0, Math.max(0, Math.floor((frame - 22) / 4)));
const odak = frame >= 20;
const imlecGorunur = Math.floor(frame / 8) % 2 === 0 || yazilan.length < metin.length;
const cta = zipla(frame, 96);
const bas = ara(frame, [122, 125], [0, 1]) * (1 - ara(frame, [129, 134], [0, 1]));
const sayfa = yay(frame, 128, 22);
return (
<div style={{ padding: "0 20px", fontFamily: FONT.govde, color: R.slate900 }}>
{/* Site üst çubuğu */}
<div style={{ height: 56, display: "flex", alignItems: "center", justifyContent: "space-between" }}>
<span style={{ display: "inline-flex", alignItems: "center", gap: 4 }}>
<LogoMark boy={28} varyant="duruk" girisYok />
<Wordmark boy={21} />
</span>
<span style={{ width: 40, height: 40, borderRadius: 999, border: `1px solid ${R.slate200}`, display: "flex", alignItems: "center", justifyContent: "center" }}>
<Ikon ad="kullanici" boy={16} renk={R.slate600} />
</span>
</div>
<div style={{ marginTop: 18 }}>
<Eyebrow olcek={1}>Adım 1 / 3</Eyebrow>
<div style={{ marginTop: 14, fontFamily: FONT.baslik, fontWeight: 700, fontSize: 30, lineHeight: 1.1 }}>Sıralamanı gir</div>
<div style={{ marginTop: 8, fontSize: 16, lineHeight: 1.5, color: R.slate600 }}>Sana uyan programları bununla bulacağız.</div>
<div style={{ marginTop: 22, fontSize: 14, fontWeight: 600, color: R.slate700 }}>Başarı sıralaman</div>
<div
data-iz="a1-girdi"
style={{
marginTop: 8,
height: 48,
borderRadius: 999,
border: `1px solid ${odak ? R.primary : R.slate200}`,
boxShadow: odak ? "0 0 0 3px rgba(59,130,246,0.25)" : undefined,
display: "flex",
alignItems: "center",
gap: 12,
padding: "0 20px",
fontSize: 16,
}}
>
<Ikon ad="kupa" boy={16} renk={R.slate400} />
<span style={{ fontWeight: 600 }}>
{yazilan}
{odak && imlecGorunur ? <span style={{ color: R.primary, fontWeight: 400 }}>|</span> : null}
{!yazilan && !odak ? <span style={{ color: R.slate400, fontWeight: 400 }}>Örn. 45.320</span> : null}
</span>
</div>
<div style={{ marginTop: 20, fontSize: 14, fontWeight: 600, color: R.slate700 }}>Neyi seviyorsun, nerede okumak istersin?</div>
<div style={{ marginTop: 10, display: "flex", flexWrap: "wrap", gap: 8 }}>
{CIPLER.map((c, i) => {
const an = SECIM[i];
const secili = an != null && frame >= an;
const tik = an != null ? ara(frame, [an, an + 2], [0, 1]) * (1 - ara(frame, [an + 3, an + 7], [0, 1])) : 0;
return (
<span
key={c}
data-iz={`a1-cip-${i}`}
style={{
display: "inline-flex",
alignItems: "center",
gap: 6,
height: 40,
padding: "0 14px",
borderRadius: 999,
border: `1px solid ${secili ? R.primary : R.slate200}`,
background: secili ? R.primary : R.beyaz,
color: secili ? R.beyaz : R.slate700,
fontSize: 14,
fontWeight: 600,
transform: `scale(${1 - 0.04 * tik})`,
}}
>
{i >= 2 && i <= 4 ? <Ikon ad="konum" boy={14} /> : i === 5 ? <Ikon ad="devlet" boy={14} /> : null}
{c}
</span>
);
})}
</div>
<div style={{ marginTop: 26, opacity: Math.min(1, Math.max(0, cta)), transform: `scale(${0.85 + 0.15 * cta})` }}>
<span data-iz="a1-cta" style={{ display: "block" }}>
<TuruncuCta metin="Listemi oluştur" boy={16 / 40} basili={bas} style={{ width: "100%", height: 48 }} />
</span>
</div>
</div>
{/* CTA'dan sonra: liste hazırlanıyor paneli (3. adıma köprü) */}
<div
style={{
position: "absolute",
left: 12,
right: 12,
bottom: 16,
padding: 18,
borderRadius: 24,
background: R.beyaz,
border: `1px solid ${R.slate200}`,
boxShadow: "0 -10px 30px rgba(15,23,42,0.10)",
transform: `translateY(${(1 - sayfa) * 260}px)`,
}}
>
<div style={{ fontFamily: FONT.baslik, fontWeight: 700, fontSize: 20 }}>Listen hazırlanıyor</div>
<div style={{ marginTop: 4, fontSize: 14, color: R.slate500 }}>6 yılın verisi taranıyor…</div>
<div style={{ marginTop: 12, display: "grid", gridTemplateColumns: "repeat(12, 1fr)", gap: 5 }}>
{Array.from({ length: 24 }, (_, i) => {
const on = frame >= 134 + i * 1.2;
const renk = i < 5 ? R.riskli : i < 18 ? R.azRiskli : R.guvenli;
return <span key={i} style={{ aspectRatio: "1", borderRadius: 5, background: on ? renk : R.slate100 }} />;
})}
</div>
</div>
</div>
);
}
export function Adim1() {
const frame = useCurrentFrame();
const { dikey, w, h } = useDuzen();
const telGen = dikey ? 640 : 470;
const telDon = [
{ f: 0, s: -28, x: 12, r: -3 },
{ f: 34, s: -10, x: 5, r: -1 },
{ f: 70, s: 0, x: 0, r: 0 },
{ f: 185, s: 9, x: 4, r: 1 },
].map((a) => ({ f: a.f, s: a.s, x: a.x, r: a.r }));
const donY = degerAl(telDon as Anahtar[], frame, "s", 0);
const donX = degerAl(telDon as Anahtar[], frame, "x", 0);
const donZ = degerAl(telDon as Anahtar[], frame, "r", 0);
const kamera: Anahtar[] = dikey
? [
{ f: 0, s: 0.95, x: 0, y: 60 },
{ f: 24, s: 1.4, x: 0, y: -40 },
{ f: 50, s: 1.4, x: 0, y: -40 },
{ f: 64, s: 1.3, x: 0, y: 140 },
{ f: 100, s: 1.3, x: 0, y: 150 },
{ f: 118, s: 1.42, x: 0, y: 290 },
{ f: 140, s: 1.3, x: 0, y: 420 },
{ f: 185, s: 1, x: 0, y: 60 },
]
: [
{ f: 0, s: 1, x: 0, y: 0 },
{ f: 24, s: 1.6, x: 384, y: -140 },
{ f: 50, s: 1.6, x: 384, y: -140 },
{ f: 64, s: 1.55, x: 384, y: -10 },
{ f: 100, s: 1.55, x: 384, y: 0 },
{ f: 118, s: 1.7, x: 384, y: 90 },
{ f: 140, s: 1.5, x: 384, y: 250 },
{ f: 185, s: 1, x: 0, y: 0 },
];
return (
<Sahne>
<OdakDisiPikseller seed={11} adet={8} />
<Kamera anahtarlar={kamera} vuruslar={VURUSLAR.adim1}>
<AbsoluteFill>
<div
style={{
position: "absolute",
...(dikey
? { left: 70, right: 70, top: 130, textAlign: "center" as const, alignItems: "center" }
: { left: 140, width: 760, top: 0, bottom: 0, justifyContent: "center", alignItems: "flex-start" }),
display: "flex",
flexDirection: "column",
gap: 28,
// Kamera telefona dalınca kenarda yarım yazı kalmasın
opacity: dikey ? 1 : 1 - ara(frame, [14, 26], [0, 1]) + ara(frame, [150, 172], [0, 1]),
}}
>
{!dikey ? (
<MaskeSatir gecikme={4}>
<Eyebrow olcek={2.3}>Adım 1</Eyebrow>
</MaskeSatir>
) : null}
<div style={{ fontFamily: FONT.baslik, fontWeight: 700, fontSize: dikey ? 84 : 96, lineHeight: 1.05 }}>
<MaskeSatir gecikme={6}>Form doldurmaktan</MaskeSatir>
<MaskeSatir gecikme={12}>
<span style={vurguStil}>kısa</span> sürer.
</MaskeSatir>
</div>
{!dikey ? (
<MaskeSatir gecikme={20}>
<span style={{ display: "block", fontSize: 36, lineHeight: 1.45, color: R.slate600 }}>
Sıralamanı yaz, sevdiğin alanları ve şehirleri seç. Gerisini bir butona bırak.
</span>
</MaskeSatir>
) : null}
</div>
<div
style={{
position: "absolute",
left: (dikey ? w / 2 : w * 0.7) - telGen / 2,
top: (dikey ? h * 0.585 : h / 2) - (telGen * 2.05) / 2,
}}
>
<Telefon genislik={telGen} donX={donX} donY={donY} donZ={donZ}>
<SihirbazEkrani />
</Telefon>
</div>
</AbsoluteFill>
</Kamera>
</Sahne>
);
}
/* ------------------------------------------------------------------ */
/* Adım 2 — hero haritası dökülür, imleç ısı fırçasıyla tarar */
/* ------------------------------------------------------------------ */
const ISTATISTIK = [
{ deger: "21.000+", etiket: "Taranan program" },
{ deger: "6 yıl", etiket: "Taban sıralaması trendi" },
{ deger: "%100", etiket: "Resmî YÖK Atlas kaynağı" },
];
export function Adim2() {
const frame = useCurrentFrame();
const { dikey } = useDuzen();
const kilit = yay(frame, 150, 20);
const haritaY = dikey ? -70 : -40;
const kamera: Anahtar[] = [
{ f: 0, s: 1.42, x: 0, y: haritaY },
{ f: 58, s: 1.3, x: 0, y: haritaY },
{ f: 118, s: 1.22, x: 0, y: haritaY },
{ f: 138, s: 1, x: 0, y: 0 },
{ f: 200, s: 1.04, x: 0, y: 0 },
];
return (
<Sahne zemin={R.beyaz}>
<OdakDisiPikseller seed={21} adet={6} guc={0.7} />
<Kamera anahtarlar={kamera} vuruslar={VURUSLAR.adim2}>
<AbsoluteFill style={{ alignItems: "center", justifyContent: "center", gap: dikey ? 64 : 30 }}>
<BaslikBlogu
rozet="Adım 2"
baslik="6 yılın verisi, tek taramada"
aciklama={dikey ? "Taban sıralaması, kontenjan, doluluk. Hepsi resmî YÖK Atlas'tan." : undefined}
genis
/>
<YagmurHaritasi genislik={dikey ? 1000 : 860} baslangic={4} dokulme={42} fircaBas={56} fircaSure={64} iz="a2-firca" />
<div style={{ display: "flex", gap: dikey ? 18 : 28 }}>
{ISTATISTIK.map((s, i) => {
const g = yay(frame, 124 + i * 8, 20);
return (
<Kutu
key={s.etiket}
style={{ width: dikey ? 300 : 380, padding: dikey ? "30px 18px" : "22px 24px", textAlign: "center", opacity: g, transform: `translateY(${(1 - g) * 40}px)` }}
>
<div data-iz={`a2-stat-${i}`} style={{ fontFamily: FONT.baslik, fontWeight: 700, fontSize: dikey ? 74 : 64, lineHeight: 1, color: R.primary }}>
{s.deger}
</div>
<div style={{ marginTop: 14, fontSize: 26, fontWeight: 600, color: R.slate500, lineHeight: 1.3 }}>{s.etiket}</div>
</Kutu>
);
})}
</div>
<div style={{ display: "flex", alignItems: "center", gap: 22, opacity: kilit, transform: `translateY(${(1 - kilit) * 20}px)`, maxWidth: dikey ? 920 : 1400 }}>
<div style={{ width: 84, height: 84, borderRadius: 24, background: R.primary, display: "flex", alignItems: "center", justifyContent: "center", flexShrink: 0 }}>
<Ikon ad="kalkan" boy={44} renk={R.beyaz} />
</div>
<div style={{ fontSize: 36, fontWeight: 600, color: R.slate800, lineHeight: 1.3 }}>
Yapay zekâ bu havuzun dışına çıkamaz. <span style={{ color: R.primary }}>Uydurmak yok.</span>
</div>
</div>
</AbsoluteFill>
</Kamera>
</Sahne>
);
}
/* ------------------------------------------------------------------ */
/* Adım 3 — 24 kare dolar, imleç her yeni kareyi takip eder */
/* ------------------------------------------------------------------ */
const DILIMLER = [
{ ad: "Hayal", adet: 5, renk: R.riskli },
{ ad: "Dengeli", adet: 13, renk: R.azRiskli },
{ ad: "Güvenli", adet: 6, renk: R.guvenli },
] as const;
const KARELER = DILIMLER.flatMap((d) => Array.from({ length: d.adet }, () => d));
// Gerçek YÖK Atlas 2026 taban sıralamaları (data/yokatlas.db, SAYISAL);
// örnek aday 45.320. — risk riskHesapla() eşikleriyle birebir.
const SATIRLAR = [
{ sira: 3, ad: "Elektrik-Elektronik Mühendisliği", uni: "Dokuz Eylül Üniversitesi", il: "İzmir", taban: 38626, risk: "riskli" as const },
{ sira: 11, ad: "Bilgisayar Mühendisliği", uni: "Ankara Yıldırım Beyazıt Üniversitesi", il: "Ankara", taban: 55158, risk: "az-riskli" as const },
{ sira: 20, ad: "Bilgisayar Mühendisliği", uni: "Kocaeli Üniversitesi", il: "Kocaeli", taban: 70832, risk: "guvenli" as const },
];
export function Adim3() {
const frame = useCurrentFrame();
const { dikey } = useDuzen();
const kare = dikey ? 112 : 92;
const arali = dikey ? 16 : 12;
const sut = dikey ? 6 : 8;
const BAS = 24;
// İmlecin takip ettiği nokta: dolmakta olan kare
const t = Math.min(23, Math.max(0, (frame - BAS) / 2));
const kareMerkez = (i: number) => [(i % sut) * (kare + arali) + kare / 2, Math.floor(i / sut) * (kare + arali) + kare / 2];
const a = kareMerkez(Math.floor(t));
const b = kareMerkez(Math.min(23, Math.floor(t) + 1));
const f = t - Math.floor(t);
const takip = [a[0] + (b[0] - a[0]) * f, a[1] + (b[1] - a[1]) * f];
const kamera: Anahtar[] = dikey
? [
{ f: 0, s: 1.16, x: 0, y: -190 },
{ f: 80, s: 1.1, x: 0, y: -160 },
{ f: 112, s: 1.15, x: 0, y: 500 },
{ f: 150, s: 1.1, x: 0, y: 520 },
{ f: 185, s: 1, x: 0, y: 0 },
]
: [
{ f: 0, s: 1.45, x: -435, y: 90 },
{ f: 80, s: 1.3, x: -420, y: 110 },
{ f: 112, s: 1.45, x: 455, y: 120 },
{ f: 150, s: 1.35, x: 455, y: 130 },
{ f: 185, s: 1, x: 0, y: 0 },
];
const izgara = (
<div style={{ display: "flex", flexDirection: "column", alignItems: "center", gap: 36 }}>
<div style={{ position: "relative", display: "grid", gridTemplateColumns: `repeat(${sut}, ${kare}px)`, gap: arali }}>
{KARELER.map((d, i) => {
const s = zipla(frame, BAS + i * 2);
return (
<div
key={i}
style={{
width: kare,
height: kare,
borderRadius: kare * 0.26,
background: d.renk,
display: "flex",
alignItems: "center",
justifyContent: "center",
fontFamily: FONT.baslik,
fontWeight: 700,
fontSize: kare * 0.38,
color: R.beyaz,
transform: `scale(${Math.max(0, s)})`,
}}
>
{i + 1}
</div>
);
})}
<div data-iz="a3-takip" style={{ position: "absolute", left: takip[0], top: takip[1], width: 2, height: 2 }} />
</div>
<div style={{ display: "flex", gap: 22 }}>
{DILIMLER.map((d, i) => {
const g = yay(frame, 78 + i * 6, 16);
return (
<span
key={d.ad}
style={{
display: "inline-flex",
alignItems: "center",
gap: 14,
height: 76,
padding: "0 28px",
borderRadius: 999,
border: `3px solid ${R.slate200}`,
background: R.beyaz,
fontSize: 32,
fontWeight: 600,
color: R.slate800,
opacity: g,
transform: `translateY(${(1 - g) * 16}px)`,
}}
>
<span style={{ width: 20, height: 20, borderRadius: 6, background: d.renk }} />
{d.ad}
<span style={{ fontFamily: FONT.baslik, fontWeight: 700, color: R.slate900 }}>{d.adet}</span>
</span>
);
})}
</div>
</div>
);
const satirlar = (
<div style={{ display: "flex", flexDirection: "column", gap: 18, width: dikey ? 940 : 780 }}>
{SATIRLAR.map((s, i) => {
const g = yay(frame, 96 + i * 9, 20);
const hover = (i === 1 && frame >= 100 && frame < 132) || (i === 2 && frame >= 130 && frame < 158);
return (
<Kutu
key={s.uni}
style={{
display: "flex",
alignItems: "center",
gap: 26,
padding: "26px 30px",
opacity: g,
transform: `translateX(${(1 - g) * 60}px) scale(${hover ? 1.02 : 1})`,
borderColor: hover ? R.turuncu : R.slate200,
background: hover ? "#fff7ed" : R.beyaz,
}}
>
<div style={{ width: 70, fontFamily: FONT.baslik, fontWeight: 700, fontSize: 44, color: hover ? R.turuncu : R.slate400, textAlign: "center", flexShrink: 0 }}>
{s.sira}
</div>
<div data-iz={`a3-satir-${i}`} style={{ flex: 1, minWidth: 0 }}>
<div style={{ fontSize: 34, fontWeight: 600, lineHeight: 1.2, color: R.slate900 }}>{s.ad}</div>
<div style={{ marginTop: 6, fontSize: 26, color: R.slate500, lineHeight: 1.3 }}>
{s.uni} · {s.il}
</div>
<div style={{ marginTop: 12, display: "flex", alignItems: "center", gap: 20 }}>
<RiskEtiketi risk={s.risk} olcek={2.3} />
<span style={{ fontSize: 26, color: R.slate500 }}>
Taban <b style={{ color: R.slate900, fontWeight: 600 }}>{trSayi(s.taban)}</b>
</span>
</div>
</div>
</Kutu>
);
})}
<div style={{ fontSize: 22, color: R.slate400, textAlign: dikey ? "center" : "left", opacity: yay(frame, 128, 16) }}>
Örnek: sayısal, 45.320. sıra · YÖK Atlas 2026 taban sıralamaları
</div>
</div>
);
return (
<Sahne>
<OdakDisiPikseller seed={31} adet={6} guc={0.7} />
<Kamera anahtarlar={kamera} vuruslar={VURUSLAR.adim3}>
<AbsoluteFill style={{ alignItems: "center", justifyContent: "center", gap: dikey ? 60 : 56, padding: dikey ? "0 60px" : "0 100px" }}>
<BaslikBlogu rozet="Adım 3" baslik="Dengeli 24'lük listen hazır" genis />
<div style={{ display: "flex", flexDirection: dikey ? "column" : "row", alignItems: "center", gap: dikey ? 56 : 90 }}>
{izgara}
{satirlar}
</div>
</AbsoluteFill>
</Kamera>
</Sahne>
);
}
/* ------------------------------------------------------------------ */
/* Paket içinde — trend, PDF, revizyon (satır imleçle sürüklenir) */
/* ------------------------------------------------------------------ */
// Ankara Yıldırım Beyazıt Ü. Bilgisayar Müh. taban sırası, 2021–2026
const TREND = [37831, 27534, 21602, 30797, 42108, 55158];
const REVIZYON = [
{ ad: "Bilgisayar Müh. · Ege", renk: R.riskli },
{ ad: "Bilgisayar Müh. · Dokuz Eylül", renk: R.azRiskli },
{ ad: "Bilgisayar Müh. · Katip Çelebi", renk: R.guvenli },
];
function OzellikKarti({
ikon,
baslik,
aciklama,
gecikme,
children,
dikey,
}: {
ikon: IkonAdi;
baslik: string;
aciklama: string;
gecikme: number;
children: ReactNode;
dikey: boolean;
}) {
const frame = useCurrentFrame();
const g = yay(frame, gecikme, 22);
return (
<Kutu
style={{
position: "relative",
overflow: "visible",
width: dikey ? 940 : 520,
height: dikey ? 390 : 600,
padding: dikey ? "36px 40px" : "44px 40px",
display: "flex",
flexDirection: dikey ? "row" : "column",
gap: dikey ? 36 : 0,
opacity: g,
transform: `translateY(${(1 - g) * 80}px)`,
borderColor: "rgba(226,232,240,0.7)",
}}
>
<div style={{ position: "absolute", right: 30, top: 30 }}>
<PixelCorner boy={60} />
</div>
<div style={{ flex: dikey ? 1 : undefined }}>
<div style={{ width: 96, height: 96, borderRadius: 26, background: "rgba(59,130,246,0.1)", display: "flex", alignItems: "center", justifyContent: "center" }}>
<Ikon ad={ikon} boy={46} renk={R.primary} />
</div>
<div style={{ marginTop: 26, fontFamily: FONT.baslik, fontWeight: 700, fontSize: 48, lineHeight: 1.1 }}>{baslik}</div>
<div style={{ marginTop: 12, fontSize: 30, lineHeight: 1.4, color: R.slate600 }}>{aciklama}</div>
</div>
<div style={{ flex: 1, display: "flex", alignItems: dikey ? "center" : "flex-end", justifyContent: "center", marginTop: dikey ? 0 : 24 }}>
{children}
</div>
</Kutu>
);
}
export function Ozellikler() {
const frame = useCurrentFrame();
const { dikey } = useDuzen();
const gW = dikey ? 380 : 420;
const gH = dikey ? 200 : 170;
const min = 15000;
const max = 60000;
const noktalar = TREND.map((v, i) => [(i / (TREND.length - 1)) * gW, ((v - min) / (max - min)) * gH]);
const cizgi = ara(frame, [26, 58], [0, 1]);
const yol = noktalar.map((p, i) => `${i ? "L" : "M"}${p[0]},${p[1]}`).join(" ");
// Sürükle-bırak: imleç 58'de ilk satırı tutar, 92'de en alta bırakır
const tut = ara(frame, [58, 92], [0, 1], Easing.inOut(Easing.cubic));
const tutuldu = frame >= 58 && frame < 94;
const SATIR = 74;
return (
<Sahne>
<OdakDisiPikseller seed={41} adet={7} guc={0.8} />
<Kamera anahtarlar={[{ f: 0, s: 1.18, x: 0, y: 60 }, { f: 50, s: 1.04, x: 0, y: 0 }, { f: 140, s: 1.0, x: 0, y: 0 }]} vuruslar={VURUSLAR.ozellikler}>
<AbsoluteFill style={{ alignItems: "center", justifyContent: "center", gap: dikey ? 60 : 64 }}>
<div style={{ display: "flex", flexDirection: "column", alignItems: "center", gap: 36, textAlign: "center" }}>
<MaskeSatir gecikme={2}>
<Eyebrow olcek={dikey ? 2.6 : 2.3}>Paket içinde</Eyebrow>
</MaskeSatir>
<div style={{ fontFamily: FONT.baslik, fontWeight: 700, fontSize: dikey ? 88 : 88, lineHeight: 1.08, maxWidth: dikey ? 940 : 1600 }}>
<MaskeSatir gecikme={6}>
Her tercihin bir <span style={vurguStil}>“neden”i</span> var.
</MaskeSatir>
</div>
</div>
<div style={{ display: "flex", flexDirection: dikey ? "column" : "row", gap: dikey ? 28 : 36 }}>
<OzellikKarti dikey={dikey} ikon="trend" baslik="6 yıllık trend" aciklama="Her tercihin taban seyri tek bakışta." gecikme={8}>
<svg data-iz="oz-trend" width={gW + 20} height={gH + 20} viewBox={`-10 -10 ${gW + 20} ${gH + 20}`}>
<path d={yol} fill="none" stroke={R.primary} strokeWidth={6} strokeLinecap="round" strokeLinejoin="round" pathLength={1} strokeDasharray={1} strokeDashoffset={1 - cizgi} />
{noktalar.map((p, i) => (
<rect key={i} x={p[0] - 9} y={p[1] - 9} width={18} height={18} rx={5} fill={i === TREND.length - 1 ? R.turuncu : R.primary} opacity={cizgi >= i / (TREND.length - 1) ? 1 : 0} />
))}
</svg>
</OzellikKarti>
<OzellikKarti dikey={dikey} ikon="belge" baslik="PDF rapor" aciklama="Aile toplantısına hazır." gecikme={14}>
<div style={{ position: "relative", width: 230, height: 230 }}>
{[2, 1, 0].map((k) => {
const s = yay(frame, 34 + (2 - k) * 6, 18);
return (
<div
key={k}
style={{
position: "absolute",
left: 40 + k * 18,
top: 10 + k * 14,
width: 150,
height: 196,
borderRadius: 20,
background: R.beyaz,
border: `3px solid ${R.slate200}`,
boxShadow: "0 10px 24px rgba(15,23,42,0.06)",
transform: `rotate(${(k - 1) * 6 * s}deg)`,
padding: 20,
display: "flex",
flexDirection: "column",
gap: 12,
}}
>
{k === 0 ? (
<>
<div style={{ display: "flex", gap: 6 }}>
{[R.riskli, R.azRiskli, R.guvenli].map((c) => (
<span key={c} style={{ width: 18, height: 18, borderRadius: 5, background: c }} />
))}
</div>
{[1, 0.8, 0.9, 0.6, 0.75].map((wd, j) => (
<span key={j} style={{ height: 10, width: `${wd * 100}%`, borderRadius: 5, background: R.slate200 }} />
))}
</>
) : null}
</div>
);
})}
</div>
</OzellikKarti>
<OzellikKarti dikey={dikey} ikon="yenile" baslik="2 revizyon hakkı" aciklama="Fikrin değişir, listen de." gecikme={20}>
<div style={{ position: "relative", width: dikey ? 420 : 400, height: 3 * SATIR }}>
{REVIZYON.map((r, i) => {
// 0. satır sürüklenir; diğerleri yukarı kayarak yer açar
// Sürüklenen satır i'nin üstünden geçince o satır bir basamak yukarı kayar
const gecis = i === 1 ? (2 * tut - 0.5) / 0.8 : (2 * tut - 1.3) / 0.7;
const kay = Easing.inOut(Easing.cubic)(Math.min(1, Math.max(0, gecis)));
const y = i === 0 ? tut * 2 * SATIR : (i - kay) * SATIR;
const kalkis = i === 0 && tutuldu ? 1 : 0;
return (
<div
key={r.ad}
style={{
position: "absolute",
left: kalkis * 18,
top: y,
width: "100%",
height: 62,
borderRadius: 18,
border: `3px solid ${kalkis ? R.turuncu : R.slate200}`,
background: kalkis ? "#fff7ed" : R.beyaz,
display: "flex",
alignItems: "center",
gap: 16,
padding: "0 20px",
fontSize: 24,
fontWeight: 600,
color: R.slate800,
boxShadow: kalkis ? "0 18px 36px rgba(15,23,42,0.16)" : undefined,
transform: `scale(${1 + kalkis * 0.04}) rotate(${kalkis * -1.5}deg)`,
zIndex: i === 0 ? 2 : 1,
}}
>
<span style={{ width: 14, height: 14, borderRadius: 4, background: r.renk, flexShrink: 0 }} />
<span style={{ flex: 1, whiteSpace: "nowrap", overflow: "hidden" }}>{r.ad}</span>
{i === 0 ? (
<span data-iz="oz-rev-tut" style={{ display: "grid", gridTemplateColumns: "repeat(2, 6px)", gap: 4 }}>
{Array.from({ length: 6 }, (_, k) => (
<span key={k} style={{ width: 6, height: 6, borderRadius: 2, background: R.slate400 }} />
))}
</span>
) : null}
</div>
);
})}
</div>
</OzellikKarti>
</div>
</AbsoluteFill>
</Kamera>
</Sahne>
);
}
/* ------------------------------------------------------------------ */
/* Dürüstlük notu */
/* ------------------------------------------------------------------ */
export function Durustluk() {
const frame = useCurrentFrame();
const { dikey } = useDuzen();
const kalkan = zipla(frame, 0);
const alt = yay(frame, 64, 18);
return (
<Sahne zemin={R.beyaz}>
<OdakDisiPikseller seed={51} adet={6} guc={0.6} />
<Kamera anahtarlar={[{ f: 0, s: 1.02 }, { f: 140, s: 1.16 }]} vuruslar={VURUSLAR.durustluk}>
<AbsoluteFill style={{ alignItems: "center", justifyContent: "center", textAlign: "center", gap: 46, padding: "0 70px" }}>
<div style={{ width: 150, height: 150, borderRadius: 40, background: R.primary, display: "flex", alignItems: "center", justifyContent: "center", transform: `scale(${Math.max(0, kalkan)})` }}>
<Ikon ad="kalkan" boy={80} renk={R.beyaz} />
</div>
<div style={{ fontFamily: FONT.baslik, fontWeight: 700, fontSize: dikey ? 104 : 112, lineHeight: 1.06, maxWidth: dikey ? 920 : 1600 }}>
<MaskeSatir gecikme={8}>Yerleşme garantisi</MaskeSatir>
<MaskeSatir gecikme={16}>vermiyoruz.</MaskeSatir>
</div>
<div style={{ ...vurguStil, fontSize: dikey ? 92 : 96, lineHeight: 1.1 }}>
<MaskeSatir gecikme={36}>Çünkü kimse veremez.</MaskeSatir>
</div>
<div style={{ opacity: alt, transform: `translateY(${(1 - alt) * 24}px)`, display: "flex", flexDirection: "column", alignItems: "center", gap: 26, marginTop: 16 }}>
<div style={{ fontSize: dikey ? 40 : 40, color: R.slate600, fontWeight: 600, maxWidth: dikey ? 900 : 1400, 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} data-iz={`du-risk-${i}`} style={{ transform: `scale(${Math.max(0, zipla(frame, 76 + i * 5))})` }}>
<RiskEtiketi risk={r} olcek={3.2} />
</span>
))}
</div>
</div>
</AbsoluteFill>
</Kamera>
</Sahne>
);
}
/* ------------------------------------------------------------------ */
/* Kapanış — yalnız logo, tipografi, buton. Tıkla → her şey imlecin */
/* içine çöker → ilk kare: döngü */
/* ------------------------------------------------------------------ */
export function Kapanis() {
const frame = useCurrentFrame();
const { dikey } = useDuzen();
const buton = zipla(frame, 40);
const bas = ara(frame, [118, 121], [0, 1]) * (1 - ara(frame, [125, 130], [0, 1]));
// Çöküş: tıktan sonra içerik butonun olduğu noktaya büzülür
const cok = ara(frame, [124, 150], [0, 1], Easing.bezier(0.7, 0, 0.84, 0));
return (
<Sahne>
<Kamera anahtarlar={[{ f: 0, s: 1.12 }, { f: 100, s: 1.0 }]} vuruslar={VURUSLAR.kapanis}>
<AbsoluteFill
style={{
alignItems: "center",
justifyContent: "center",
textAlign: "center",
gap: dikey ? 70 : 56,
transformOrigin: "50% 66%",
transform: `scale(${1 - cok})`,
opacity: 1 - ara(frame, [140, 150], [0, 1]),
filter: `blur(${cok * 10}px)`,
}}
>
<div style={{ display: "flex", flexDirection: dikey ? "column" : "row", alignItems: "center", gap: dikey ? 18 : 28 }}>
<LogoMark boy={dikey ? 220 : 180} giris={4} rotaBas={22} />
<div style={{ opacity: yay(frame, 12, 16) }}>
<Wordmark boy={dikey ? 120 : 120} />
</div>
</div>
<div style={{ fontFamily: FONT.baslik, fontWeight: 700, fontSize: dikey ? 100 : 108, lineHeight: 1.06, maxWidth: dikey ? 920 : 1600 }}>
<MaskeSatir gecikme={20}>Listeni kur,</MaskeSatir>
<MaskeSatir gecikme={28}>
<span style={vurguStil}>panikle</span> değil.
</MaskeSatir>
</div>
<div data-iz="ka-buton" style={{ transform: `scale(${Math.max(0, buton)})` }}>
<TuruncuCta metin="kolaytercih.com" boy={dikey ? 1.2 : 1.1} basili={bas} />
</div>
</AbsoluteFill>
</Kamera>
</Sahne>
);
}