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>
743 lines
33 KiB
TypeScript
743 lines
33 KiB
TypeScript
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>
|
||
);
|
||
}
|