commit dc5c87b6aa8ed65ebe092995e29dd7a6e68ca7c4 Author: bilalgursen Date: Wed Sep 30 20:22:38 2026 +0300 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 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fe2f123 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +node_modules +out +.DS_Store +*.log diff --git a/README.md b/README.md new file mode 100644 index 0000000..0086cb1 --- /dev/null +++ b/README.md @@ -0,0 +1,58 @@ +# KolayTercih lansman videosu (v3) + +Remotion ile kodla üretilen 64 saniyelik, **kesintisiz döngü** lansman filmi. Müzik ve efektler +kodla sentezlendi. Bileşen ve geçişlerin bir kısmı [remocn.dev](https://remocn.dev) kayıt +defterinden alınıp (`src/remocn/`) KolayTercih'in piksel diline uyarlandı (`src/kinetik.tsx`). + +## Kurgu + +Her sahne tek bir fikir anlatır. Üç yerde müzik birden kesilir, ekran sessizleşir, sonra lens +patlamasıyla (remocn `lensZoom`) BAM gelir: + +| Sn | Sahne | Metin | +|---|---|---| +| 0–3.5 | Açılış | "45.320." (Track Text) · "Sıralaman geldi. Tebrikler! Sırada küçük bir bulmaca var." | +| 3.5–7 | Seçenekler | Perspective Marquee'de akan bölümler · "21.000+ program var. Senin 24 hakkın var." (Sheen) | +| 7–10.5 | Kaos | "Kafanda yüz sekme açık, değil mi?" | +| 10.5–13 | **Sessizlik** | "Derin bir nefes al." | +| 13–16.5 | **BAM → Logo** | Dev piksel "KolayTercih" (Gradient Scale Cut) → logo · "Tercih listeni gerçek veriyle kur, panikle değil." | +| 16.5–22 | Adım 1 | "Sıralamanı yaz." Tam ekran sihirbaz; kamera imlece kilitlenir | +| 22–28 | Adım 2 | "Tabloları biz okuruz." Harita yukarıdan dökülür; imleç ısı fırçası, kamera takip eder · "Taranan: 21.000+ program" (Outline Fill Track) | +| 28–33.5 | Adım 3 | "Listen hazır." · "Hayal de var, akıl da." (Path Ride) · 24 kare, imleç her kareyi takip eder | +| 33.5–36 | **Sessizlik** | "Off. Kafan mı karıştı?" (imleç kararsız dolanır) | +| 36–38 | **BAM → Danışman** | DanışmanlaKonuş · "Aklına takılanı sor. Gece 3'te bile." | +| 38–48 | Sohbet | Tam ekran danışman: `program_ara`, `profil_guncelle` (gerçek YÖK Atlas verisi) | +| 48–52.5 | Paket | Apple hızlı kesmeleri: "6 yıllık trend." · "PDF rapor. Aile toplantısına hazır." · "2 revizyon. Fikrin değişir, listen de." | +| 52.5–56.5 | Dürüstlük | "Yerleşme garantisi vermiyoruz. Çünkü kimse veremez." (Outline Fill) | +| 56.5–58 | **Sessizlik** | "Hazır mısın?" | +| 58–64 | **BAM → Final** | Logo + "Listeni kur, panikle değil." + kolaytercih.com butonu → tık → çöküş → boş ekran (döngü) | + +- İmleç yalnız bir şey yapılırken görünür; göründüğü sahnelerde kamera ona yaklaşıp takip eder + (`TakipKamera`, `src/imlec.tsx`). Geçtiği yerde piksel iz, tıkladığı yerde piksel patlaması. +- Telefon maketi yok; arayüz tüm kadrajı kullanır. +- Gren v2'nin üçte biri. Görseller `` ile yüklendiği için titreme yok. + +## remocn bileşenleri + +`sheen-slide-in`, `tracking-in`, `gradient-scale-cut-text`, `outline-fill-track-text`, +`perspective-marquee`, `path-ride` (yardımcı fonksiyonları), `zoom-blur`, `lens-zoom`. +İndirme: `https://remocn.dev/r/.json` (shadcn kayıt biçimi). Uyarlamada yumuşak degradeler +logonun üç rengiyle (mavi, şeftali, turuncu) basamaklı piksel bantlara, açılma kenarları kare +kare ilerleyen maskelere çevrildi; yazı tipleri Cal Sans / Bricolage. + +## Değiştirmek + +- Süreler, geçişler, imleç tıkları ve yazma aralıkları: `src/zaman.json` → sonra + `python3 ses/uret.py`. +- Punch zoomlar ve hareket bulanıklığı pencereleri: `src/vurus.ts`. +- Metinler: `src/sahneler-1.tsx`, `src/sahneler-2.tsx`, `src/sahneler-3.tsx`. + +```bash +pnpm install +pnpm studio +python3 ses/uret.py +node scripts/kareler.mjs Dikey 0.3 400 540 740 +pnpm render:dikey && pnpm render:yatay +``` + +v2 sahneleri `src/eski/` altında duruyor (derlemeye girmez). diff --git a/package.json b/package.json new file mode 100644 index 0000000..7e43e28 --- /dev/null +++ b/package.json @@ -0,0 +1,27 @@ +{ + "name": "kolaytercih-lansman", + "private": true, + "scripts": { + "studio": "remotion studio", + "render:dikey": "remotion render Lansman-Dikey out/kolaytercih-lansman-9x16.mp4", + "render:yatay": "remotion render Lansman-Yatay out/kolaytercih-lansman-16x9.mp4" + }, + "dependencies": { + "@remotion/cli": "^4.0.530", + "@remotion/fonts": "^4.0.530", + "@remotion/google-fonts": "4.0.530", + "@remotion/layout-utils": "4.0.530", + "@remotion/motion-blur": "4.0.530", + "@remotion/paths": "4.0.530", + "@remotion/transitions": "4.0.530", + "react": "19.2.4", + "react-dom": "19.2.4", + "remotion": "^4.0.530" + }, + "devDependencies": { + "@remotion/bundler": "4.0.530", + "@remotion/renderer": "4.0.530", + "@types/react": "^19.3.0", + "typescript": "^7.0.2" + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..6dda53f --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,2991 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@remotion/cli': + specifier: ^4.0.530 + version: 4.0.530(postcss@8.5.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@7.0.2) + '@remotion/fonts': + specifier: ^4.0.530 + version: 4.0.530(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@remotion/google-fonts': + specifier: 4.0.530 + version: 4.0.530(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@remotion/layout-utils': + specifier: 4.0.530 + version: 4.0.530 + '@remotion/motion-blur': + specifier: 4.0.530 + version: 4.0.530(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@remotion/paths': + specifier: 4.0.530 + version: 4.0.530(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@remotion/transitions': + specifier: 4.0.530 + version: 4.0.530(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + react: + specifier: 19.2.4 + version: 19.2.4 + react-dom: + specifier: 19.2.4 + version: 19.2.4(react@19.2.4) + remotion: + specifier: ^4.0.530 + version: 4.0.530(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + devDependencies: + '@remotion/bundler': + specifier: 4.0.530 + version: 4.0.530(postcss@8.5.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@remotion/renderer': + specifier: 4.0.530 + version: 4.0.530(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@types/react': + specifier: ^19.3.0 + version: 19.3.0 + typescript: + specifier: ^7.0.2 + version: 7.0.2 + +packages: + + '@babel/code-frame@7.29.7': + resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.29.7': + resolution: {integrity: sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.29.7': + resolution: {integrity: sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.29.8': + resolution: {integrity: sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.29.7': + resolution: {integrity: sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==} + engines: {node: '>=6.9.0'} + + '@babel/helper-globals@7.29.7': + resolution: {integrity: sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.29.7': + resolution: {integrity: sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.29.7': + resolution: {integrity: sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-string-parser@7.29.7': + resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.29.7': + resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.29.7': + resolution: {integrity: sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.29.7': + resolution: {integrity: sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.24.1': + resolution: {integrity: sha512-Zo9c7N3xdOIQrNip7Lc9wvRPzlRtovHVE4lkz8WEDr7uYh/GMQhSiIgFxGIArRHYdJE5kxtZjAf8rT0xhdLCzg==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/parser@7.29.9': + resolution: {integrity: sha512-CjXrNHTnvqBVqHgdBysY3vk2T8tpJHb5/RMeHJBTyVa9xgugCB0CJTx/3oO8RV2QRQP391RWpB7D6hLjm8V9uA==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/template@7.29.7': + resolution: {integrity: sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.29.8': + resolution: {integrity: sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.24.0': + resolution: {integrity: sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.29.8': + resolution: {integrity: sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==} + engines: {node: '>=6.9.0'} + + '@emnapi/core@1.11.3': + resolution: {integrity: sha512-zLpS5asjEb7lq8jYLq37N6XKaE41DIexlY1rF/z4/tIl3wo13Sqm28fRyfIsKZD+NZ8mM5RoKkpW/rBcuoSZSg==} + + '@emnapi/runtime@1.11.3': + resolution: {integrity: sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==} + + '@emnapi/wasi-threads@1.2.3': + resolution: {integrity: sha512-ELEBe8PsLvvJ6QMr0zLt8ffvOHW/dc1m3CEzNMg7aJUv3bMaoDtw2TXyDAwkYBuroxxuHEwhRTLJSe5sya547g==} + + '@esbuild/aix-ppc64@0.28.1': + resolution: {integrity: sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.28.1': + resolution: {integrity: sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.28.1': + resolution: {integrity: sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.28.1': + resolution: {integrity: sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.28.1': + resolution: {integrity: sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.28.1': + resolution: {integrity: sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.28.1': + resolution: {integrity: sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.28.1': + resolution: {integrity: sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.28.1': + resolution: {integrity: sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.28.1': + resolution: {integrity: sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.28.1': + resolution: {integrity: sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.28.1': + resolution: {integrity: sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.28.1': + resolution: {integrity: sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.28.1': + resolution: {integrity: sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.28.1': + resolution: {integrity: sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.28.1': + resolution: {integrity: sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.28.1': + resolution: {integrity: sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.28.1': + resolution: {integrity: sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.28.1': + resolution: {integrity: sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.28.1': + resolution: {integrity: sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.28.1': + resolution: {integrity: sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.28.1': + resolution: {integrity: sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.28.1': + resolution: {integrity: sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.28.1': + resolution: {integrity: sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.28.1': + resolution: {integrity: sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.28.1': + resolution: {integrity: sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/source-map@0.3.11': + resolution: {integrity: sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==} + + '@jridgewell/sourcemap-codec@1.6.0': + resolution: {integrity: sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@mediabunny/aac-encoder@1.56.1': + resolution: {integrity: sha512-a+OY/BctP7zC4G/prHikooOVaoWXMD4+bUyEwHg2dtQiQdIwC6TDJdVYJu5dfrdSNOiAFkv1HKcYEeROdwMezg==} + peerDependencies: + mediabunny: ^1.0.0 + + '@mediabunny/flac-encoder@1.56.1': + resolution: {integrity: sha512-zbmorMVlF1izwtlz5mWnu7A1rJLM+V26I0pEFr9Td1EDTwE5uAGRSW+/UZlnjwj1t00Woe3Zonfh9kb7lRRBqg==} + peerDependencies: + mediabunny: ^1.0.0 + + '@mediabunny/mp3-encoder@1.56.1': + resolution: {integrity: sha512-p66ovEq6mZt3ejje45NOju84hIJI4v8rHfsmc1D2J34ziV0USE8AdJBzmiwqxl6nZkIAQLNB19HzbVRcQfc51g==} + peerDependencies: + mediabunny: ^1.0.0 + + '@module-federation/error-codes@0.22.0': + resolution: {integrity: sha512-xF9SjnEy7vTdx+xekjPCV5cIHOGCkdn3pIxo9vU7gEZMIw0SvAEdsy6Uh17xaCpm8V0FWvR0SZoK9Ik6jGOaug==} + + '@module-federation/runtime-core@0.22.0': + resolution: {integrity: sha512-GR1TcD6/s7zqItfhC87zAp30PqzvceoeDGYTgF3Vx2TXvsfDrhP6Qw9T4vudDQL3uJRne6t7CzdT29YyVxlgIA==} + + '@module-federation/runtime-tools@0.22.0': + resolution: {integrity: sha512-4ScUJ/aUfEernb+4PbLdhM/c60VHl698Gn1gY21m9vyC1Ucn69fPCA1y2EwcCB7IItseRMoNhdcWQnzt/OPCNA==} + + '@module-federation/runtime@0.22.0': + resolution: {integrity: sha512-38g5iPju2tPC3KHMPxRKmy4k4onNp6ypFPS1eKGsNLUkXgHsPMBFqAjDw96iEcjri91BrahG4XcdyKi97xZzlA==} + + '@module-federation/sdk@0.22.0': + resolution: {integrity: sha512-x4aFNBKn2KVQRuNVC5A7SnrSCSqyfIWmm1DvubjbO9iKFe7ith5niw8dqSFBekYBg2Fwy+eMg4sEFNVvCAdo6g==} + + '@module-federation/webpack-bundler-runtime@0.22.0': + resolution: {integrity: sha512-aM8gCqXu+/4wBmJtVeMeeMN5guw3chf+2i6HajKtQv7SJfxV/f4IyNQJUeUQu9HfiAZHjqtMV5Lvq/Lvh8LdyA==} + + '@napi-rs/wasm-runtime@1.0.7': + resolution: {integrity: sha512-SeDnOO0Tk7Okiq6DbXmmBODgOAb9dp9gjlphokTUxmt8U3liIP1ZsozBahH69j/RJv+Rfs6IwUKHTgQYJ/HBAw==} + + '@remotion/bundler@4.0.530': + resolution: {integrity: sha512-kIKGcfV53nSi0KdrVwFmuNtsUEKShzR1EQ0UH/hVUxIh4z3tmxa7nMC7XX1rj0fJ4id0UT2wCqas6kD5DvpDlg==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + + '@remotion/canvas@4.0.530': + resolution: {integrity: sha512-kKvrVKFqzvK4Mq5EYfRu+yZsBkor8I41crPJu9wavMCtkAFf5XIZZNJTZ13CI/Iq8NKASANqBoRFUM91N4Undg==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + + '@remotion/captions@4.0.530': + resolution: {integrity: sha512-KpIG/jA5fJ/hTh2185MJ+yaodSNeHgQiMYwkVSOiaIJp82WJfzpqypGrU2PSYC4ty/K6ov0pj0lJQAcmoVRZeg==} + + '@remotion/cli@4.0.530': + resolution: {integrity: sha512-Ur3Lq1EqbEE3/ecYusb+vPPdWUAOWKLbMW3PiDNxa1aAOMuleQuoZzmRENUmr4qKguDRyV5HZNf1HTfs0c+ePQ==} + hasBin: true + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + + '@remotion/codemods@4.0.530': + resolution: {integrity: sha512-mvJQ6MLgr9brWKTlKbrr7w9NPl0Hpsbm7r+HEk1DF/gLtKP7bgVt7H/ztOApDG3T8UrcCC1o2nH4tiqAWwY+CA==} + + '@remotion/compositor-darwin-arm64@4.0.530': + resolution: {integrity: sha512-Dh4mm8Buw3Dm0/gfHTqc6ZM/+2BBupoWtodTGbDpRNB9lXrawKFNnfNzXk/pqXrb2rUd1uDgAiFUmpt10Xa3zQ==} + cpu: [arm64] + os: [darwin] + + '@remotion/compositor-darwin-x64@4.0.530': + resolution: {integrity: sha512-pEYto9+JfMAuzqjFTDNbzwEBtLOg4vanO/68kFYEebnP/Y4qBSLAOZihudPVQdoxqlUBK+OrvauaZceOR1JTEg==} + cpu: [x64] + os: [darwin] + + '@remotion/compositor-linux-arm64-gnu@4.0.530': + resolution: {integrity: sha512-2sHLT4e0BP9vSFWPhylX4tWHL3VC3eAl90SdcgN6ZA3dU/XcMDYwW+ceYhdeM7Ryh5dOXkqhLLN/HoyJrfwegA==} + cpu: [arm64] + os: [linux] + + '@remotion/compositor-linux-arm64-musl@4.0.530': + resolution: {integrity: sha512-eqYokUW0SyqK6YnjJivh6v6JFqpvgCnqniVZULM8o7f/wBB8sxDOtNuk8Jy0VISPgBFtVVKDdt9smFTNbeO3dQ==} + cpu: [arm64] + os: [linux] + + '@remotion/compositor-linux-x64-gnu@4.0.530': + resolution: {integrity: sha512-Gn6d7zenyWrkZumMLBwUWg73wVcRe554EYEFdz/SvpglqCgLlOSpA/lBrCp9hU05QXCPyOpMzWXo6BCrLx7wqg==} + cpu: [x64] + os: [linux] + + '@remotion/compositor-linux-x64-musl@4.0.530': + resolution: {integrity: sha512-8/l0t9P0zX2rd8Ootp7iUJy0u08Ce5KexG8YJI7VRYseqcKkuZnvOFts4SKKC+nGKEf9CeWYVQjsN7dj/rXnJA==} + cpu: [x64] + os: [linux] + + '@remotion/compositor-win32-x64-msvc@4.0.530': + resolution: {integrity: sha512-XP/jYCNa/4ITubAUdM2NVzXL+pCyjpglR6Hdm850n0LgophKcMRjyA4CUC0kHqKV5wCtLL9aX3ZD5KuvPHnkcw==} + cpu: [x64] + os: [win32] + + '@remotion/elevenlabs@4.0.530': + resolution: {integrity: sha512-NbYbB9El/FfCaXRK0WueeLep/mYGL+rw+26JyW5oVyRBFt/M5nxOpIUL/kz+0oCUyKiFMBoRA4skBpvgiE2HWw==} + + '@remotion/fonts@4.0.530': + resolution: {integrity: sha512-0McTzWCAM8rbspWdRGOdy8vijOcWU98JntYfjuFkgYKY0iT6rInTop69GYLhwy4kXXQX/3f4Ky/D6rMgQTRcUg==} + + '@remotion/google-fonts@4.0.530': + resolution: {integrity: sha512-y5BZZP6fXf32lq2QH+6gORjMtxTB/WXE9Z8bQjzEecbQwFp+iTxl6TLA6wCWffG68kI29Dk1FA4gcH0GnhS8GQ==} + + '@remotion/layout-utils@4.0.530': + resolution: {integrity: sha512-8fl2EQlz0uR+V/kRwZBIRSydr3ES+qS0dVRMr5tL8dLx3gDYYO4Ju+m7/ejX0edCazzhGipb9JUZnXWoku4Oaw==} + + '@remotion/licensing@4.0.530': + resolution: {integrity: sha512-xMnT4tT/cEcjF7CdcOP3V1pE4uMp7y6T2+vv74DQnFE0XcyU8QlwwjD/28uubprk/rLEw8F5cUHwzWDYuec90g==} + + '@remotion/media-parser@4.0.530': + resolution: {integrity: sha512-0kjn93BmeANiTQ7KNP8f06zU74pyrodCRUg3o21mabfQnIZ6SwFYdYyCl7drWIz9G6PDjEGvsQXAgdwa1VwvtA==} + + '@remotion/media-utils@4.0.530': + resolution: {integrity: sha512-ZP+wK2sdumVW3vkjeGzrVGVBIdRllqKgCmDPbqF2dP+xkJP8yzIfnhVbzn6f50wSDo2Z0hXJg2De0CAx+zreBA==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + + '@remotion/media@4.0.530': + resolution: {integrity: sha512-oI8/4WivgRT1ywnPalpihq3wD95yYF2ZBXJ1A7cnPRDiyuynYzeHe4f87OWDLYsQbsvp7m5LdlaTMetZc2VMUg==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + + '@remotion/motion-blur@4.0.530': + resolution: {integrity: sha512-7FeoqUgYj+Ep18PDpBtPxhrBcf+JDGyAnUNXYfe0B66J3WGRsaWPGXVRcsFHqwMSngTf+GbMsQ4qx+yQFUY2Gg==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + + '@remotion/openai-whisper@4.0.530': + resolution: {integrity: sha512-POCg/a3yxfvYK6SoQXy0h1Xv3BFtH0yQYE8j7rrcnOKK+IH4YxQ1Gbuqs076wRx/enLHuZQKWp5b2npa5exNJg==} + + '@remotion/paths@4.0.530': + resolution: {integrity: sha512-8p5PIb6xje/JwU4g5d18HG01GFlAWFAqS0NwDiz6yE+d46uTUPGmVlaeTjYfwaATYIod+SdicnaU4Oh6Tlhy9g==} + + '@remotion/player@4.0.530': + resolution: {integrity: sha512-eqKGEOB29wOZp8E8L8zIRTPmfYH4BmQwKIyMYRt3NK0vC1uShGDm9c2gTV/aiO2098GYfdP/BtRxWMtcazym8A==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + + '@remotion/renderer@4.0.530': + resolution: {integrity: sha512-B7kjM+SpIdPE7WrJviEHjSRJFfCg3zIJg/Gm9uHNtfhDw2d6QKyuJxhmef2vjVm9mPK54nkvV6sOq/cjmL8G/g==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + + '@remotion/shapes@4.0.530': + resolution: {integrity: sha512-K9+GONb7QMlGSz9gZS5mHzoCsNQvgCPCpqz0CLuy43kSV3g15BeK5pq5/eSKD1/PvDj5jCVTT7wZOnhP/rylXQ==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + + '@remotion/streaming@4.0.530': + resolution: {integrity: sha512-V6GfSZvHXkoOsgDHsZXr/WeVXR/IQ8n7EUnZCk5ApZu4u0pzwPM/gfWWrH+SDcAKe4MLYHX2cTWhOMP/HFbzFg==} + + '@remotion/studio-protocol@4.0.530': + resolution: {integrity: sha512-vHhvU0aKVFrQa7SWBFTEd9m2kiyZtgsf8dxzkO7h5JhzCyJBN1SGmxukjptt/ok5BFYWyixNBAnTyic12T3oww==} + + '@remotion/studio-server@4.0.530': + resolution: {integrity: sha512-VCa1lh7/qyeW5UtXhU6g65RpCbjBYG0tDF4aY6zxCNjCLmQUUEHiDST/pGhnm1nCAT/bQW3SUynitrw5yn/Afg==} + + '@remotion/studio-shared@4.0.530': + resolution: {integrity: sha512-erBu4UhV+lYWf0BX+ZlHbiG5Wjn9fqyIqkMhypOyQHPVuQGw3I5sKw2dTOb8tuM9VejHFzcAtFvqkQmLRm3lNQ==} + + '@remotion/studio@4.0.530': + resolution: {integrity: sha512-Yki1b9nT3uWhVhzjASVH3AaWGEE4rqIRAoAHo67FCOgXSROpwWMNOc7AECPTsdVF8xyaI113hgYsACwsRv1ziQ==} + peerDependencies: + '@remotion/video-matting': 4.0.530 + '@remotion/whisper-webgpu': 4.0.530 + react: '>=16.8.0' + react-dom: '>=16.8.0' + peerDependenciesMeta: + '@remotion/video-matting': + optional: true + '@remotion/whisper-webgpu': + optional: true + + '@remotion/timeline-utils@4.0.530': + resolution: {integrity: sha512-Ec81QAOY2fjm+td/jankYpjShU3zjfbsLybw2U4RGLEe4dpRXIz1KKvqsv7ExjG7MTxdiEzRXmJi7Ia+/b/nyQ==} + + '@remotion/transitions@4.0.530': + resolution: {integrity: sha512-GnQpW70xGBBlAUdVXzBaxlm/hagM0+c/W2OZVz7LuaNhRMcitixZZZm0XAkCr+pNOKj0Ky576DakqCgwkYtS3A==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + + '@remotion/web-renderer@4.0.530': + resolution: {integrity: sha512-10mD2X8+Bl2D08pRgN2ecJS1ENx8yCA7/lmjP9kJavuGa/e7QlbA76SD62/byTilzokSdx3VmSQrQjrRBExMng==} + peerDependencies: + react: '>=18.0.0' + react-dom: '>=18.0.0' + + '@remotion/zod-types@4.0.530': + resolution: {integrity: sha512-DfrQ7hMXAbDTR8xmKDHjbhdWo5z7aOTH8pGg/pmVY7wp5jXXOLyNHw3QeED14l7squ57tjR7z2gn0iDrsh3ptw==} + + '@rspack/binding-darwin-arm64@1.7.11': + resolution: {integrity: sha512-oduECiZVqbO5zlVw+q7Vy65sJFth99fWPTyucwvLJJtJkPL5n17Uiql2cYP6Ijn0pkqtf1SXgK8WjiKLG5bIig==} + cpu: [arm64] + os: [darwin] + + '@rspack/binding-darwin-x64@1.7.11': + resolution: {integrity: sha512-a1+TtTE9ap6RalgFi7FGIgkJP6O4Vy6ctv+9WGJy53E4kuqHR0RygzaiVxCI/GMc/vBT9vY23hyrpWb3d1vtXA==} + cpu: [x64] + os: [darwin] + + '@rspack/binding-linux-arm64-gnu@1.7.11': + resolution: {integrity: sha512-P0QrGRPbTWu6RKWfN0bDtbnEps3rXH0MWIMreZABoUrVmNQKtXR6e73J3ub6a+di5s2+K0M2LJ9Bh2/H4UsDUA==} + cpu: [arm64] + os: [linux] + + '@rspack/binding-linux-arm64-musl@1.7.11': + resolution: {integrity: sha512-6ky7R43VMjWwmx3Yx7Jl7faLBBMAgMDt+/bN35RgwjiPgsIByz65EwytUVuW9rikB43BGHvA/eqlnjLrUzNBqw==} + cpu: [arm64] + os: [linux] + + '@rspack/binding-linux-x64-gnu@1.7.11': + resolution: {integrity: sha512-cuOJMfCOvb2Wgsry5enXJ3iT1FGUjdPqtGUBVupQlEG4ntSYsQ2PtF4wIDVasR3wdxC5nQbipOrDiN/u6fYsdQ==} + cpu: [x64] + os: [linux] + + '@rspack/binding-linux-x64-musl@1.7.11': + resolution: {integrity: sha512-CoK37hva4AmHGh3VCsQXmGr40L36m1/AdnN5LEjUX6kx5rEH7/1nEBN6Ii72pejqDVvk9anEROmPDiPw10tpFg==} + cpu: [x64] + os: [linux] + + '@rspack/binding-wasm32-wasi@1.7.11': + resolution: {integrity: sha512-OtrmnPUVJMxjNa3eDMfHyPdtlLRmmp/aIm0fQHlAOATbZvlGm12q7rhPW5BXTu1yh+1rQ1/uqvz+SzKEZXuJaQ==} + cpu: [wasm32] + + '@rspack/binding-win32-arm64-msvc@1.7.11': + resolution: {integrity: sha512-lObFW6e5lCWNgTBNwT//yiEDbsxm9QG4BYUojqeXxothuzJ/L6ibXz6+gLMvbOvLGV3nKgkXmx8GvT9WDKR0mA==} + cpu: [arm64] + os: [win32] + + '@rspack/binding-win32-ia32-msvc@1.7.11': + resolution: {integrity: sha512-0pYGnZd8PPqNR68zQ8skamqNAXEA1sUfXuAdYcknIIRq2wsbiwFzIc0Pov1cIfHYab37G7sSIPBiOUdOWF5Ivw==} + cpu: [ia32] + os: [win32] + + '@rspack/binding-win32-x64-msvc@1.7.11': + resolution: {integrity: sha512-EeQXayoQk/uBkI3pdoXfQBXNIUrADq56L3s/DFyM2pJeUDrWmhfIw2UFIGkYPTMSCo8F2JcdcGM32FGJrSnU0Q==} + cpu: [x64] + os: [win32] + + '@rspack/binding@1.7.11': + resolution: {integrity: sha512-2MGdy2s2HimsDT444Bp5XnALzNRxuBNc7y0JzyuqKbHBywd4x2NeXyhWXXoxufaCFu5PBc9Qq9jyfjW2Aeh06Q==} + + '@rspack/core@1.7.11': + resolution: {integrity: sha512-rsD9b+Khmot5DwCMiB3cqTQo53ioPG3M/A7BySu8+0+RS7GCxKm+Z+mtsjtG/vsu4Tn2tcqCdZtA3pgLoJB+ew==} + engines: {node: '>=18.12.0'} + peerDependencies: + '@swc/helpers': '>=0.5.1' + peerDependenciesMeta: + '@swc/helpers': + optional: true + + '@rspack/lite-tapable@1.1.0': + resolution: {integrity: sha512-E2B0JhYFmVAwdDiG14+DW0Di4Ze4Jg10Pc4/lILUrd5DRCaklduz2OvJ5HYQ6G+hd+WTzqQb3QnDNfK4yvAFYw==} + + '@rspack/plugin-react-refresh@1.6.1': + resolution: {integrity: sha512-eqqW5645VG3CzGzFgNg5HqNdHVXY+567PGjtDhhrM8t67caxmsSzRmT5qfoEIfBcGgFkH9vEg7kzXwmCYQdQDw==} + peerDependencies: + react-refresh: '>=0.10.0 <1.0.0' + webpack-hot-middleware: 2.x + peerDependenciesMeta: + webpack-hot-middleware: + optional: true + + '@svgr/babel-plugin-add-jsx-attribute@8.0.0': + resolution: {integrity: sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/babel-plugin-remove-jsx-attribute@8.0.0': + resolution: {integrity: sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0': + resolution: {integrity: sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0': + resolution: {integrity: sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/babel-plugin-svg-dynamic-title@8.0.0': + resolution: {integrity: sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/babel-plugin-svg-em-dimensions@8.0.0': + resolution: {integrity: sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/babel-plugin-transform-react-native-svg@8.1.0': + resolution: {integrity: sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/babel-plugin-transform-svg-component@8.0.0': + resolution: {integrity: sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw==} + engines: {node: '>=12'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/babel-preset@8.1.0': + resolution: {integrity: sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/core@8.1.0': + resolution: {integrity: sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==} + engines: {node: '>=14'} + + '@svgr/hast-util-to-babel-ast@8.0.0': + resolution: {integrity: sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q==} + engines: {node: '>=14'} + + '@svgr/plugin-jsx@8.1.0': + resolution: {integrity: sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA==} + engines: {node: '>=14'} + peerDependencies: + '@svgr/core': '*' + + '@tanstack/react-virtual@3.14.9': + resolution: {integrity: sha512-qZyr0FZDP8rDC4WBhsryIZmAd9bveJvFGUJJtskWaew6/0dTRS6wZxnR6VQ5bY2KwL3LjerrHqQLk3a0GKcPXQ==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + '@tanstack/virtual-core@3.17.7': + resolution: {integrity: sha512-bp+v10y65sp2H7WpWfIMyxTNfl8ZVfxFTLRjPIFRryi6FV/J33z4IS53WO4pTk36KlvJ4iLiQz+oaydDC1xbcA==} + + '@tybys/wasm-util@0.10.4': + resolution: {integrity: sha512-W3c4gRigFS0T/Ma4qIYF3GDAc5AQdHb1yL5znJT1Zv1YaD9Kitx656wBjvr19qbiosmZT8lWDM5BEMynUqX65A==} + + '@types/dom-mediacapture-transform@0.1.12': + resolution: {integrity: sha512-d7/QsLRwF864A5mgIM/YrfiglHoYn7zgCcAoJgW404r+2DwnNr7EBbLnCWpmOMgH8y0te73L1AV6H1bmauaWFw==} + + '@types/dom-webcodecs@0.1.13': + resolution: {integrity: sha512-O5hkiFIcjjszPIYyUSyvScyvrBoV3NOEEZx/pMlsu44TKzWNkLVBBxnxJz42in5n3QIolYOcBYFCPZZ0h8SkwQ==} + + '@types/eslint-scope@3.7.7': + resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==} + + '@types/eslint@9.6.1': + resolution: {integrity: sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==} + + '@types/estree@1.0.9': + resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/node@26.6.3': + resolution: {integrity: sha512-dsqMQQoeTLqu9wynDD00q573mNzso3IdQOAfHRJqLCcmCFPoGo9A1bDpUcv/9tnKpErQWv9uKeGfl37EIS02Yg==} + + '@types/react@19.3.0': + resolution: {integrity: sha512-N0rFCuH9YoxG9/m61l9MfpJKfmLOVU0em7ipIz6TRgSSkvReLB9vL85GB+yr8Bs5leqpvg96JSwF4ZS1s4viQg==} + + '@typescript/typescript-aix-ppc64@7.0.2': + resolution: {integrity: sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==} + engines: {node: '>=16.20.0'} + cpu: [ppc64] + os: [aix] + + '@typescript/typescript-darwin-arm64@7.0.2': + resolution: {integrity: sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [darwin] + + '@typescript/typescript-darwin-x64@7.0.2': + resolution: {integrity: sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [darwin] + + '@typescript/typescript-freebsd-arm64@7.0.2': + resolution: {integrity: sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [freebsd] + + '@typescript/typescript-freebsd-x64@7.0.2': + resolution: {integrity: sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [freebsd] + + '@typescript/typescript-linux-arm64@7.0.2': + resolution: {integrity: sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [linux] + + '@typescript/typescript-linux-arm@7.0.2': + resolution: {integrity: sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ==} + engines: {node: '>=16.20.0'} + cpu: [arm] + os: [linux] + + '@typescript/typescript-linux-loong64@7.0.2': + resolution: {integrity: sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ==} + engines: {node: '>=16.20.0'} + cpu: [loong64] + os: [linux] + + '@typescript/typescript-linux-mips64el@7.0.2': + resolution: {integrity: sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA==} + engines: {node: '>=16.20.0'} + cpu: [mips64el] + os: [linux] + + '@typescript/typescript-linux-ppc64@7.0.2': + resolution: {integrity: sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA==} + engines: {node: '>=16.20.0'} + cpu: [ppc64] + os: [linux] + + '@typescript/typescript-linux-riscv64@7.0.2': + resolution: {integrity: sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ==} + engines: {node: '>=16.20.0'} + cpu: [riscv64] + os: [linux] + + '@typescript/typescript-linux-s390x@7.0.2': + resolution: {integrity: sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw==} + engines: {node: '>=16.20.0'} + cpu: [s390x] + os: [linux] + + '@typescript/typescript-linux-x64@7.0.2': + resolution: {integrity: sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [linux] + + '@typescript/typescript-netbsd-arm64@7.0.2': + resolution: {integrity: sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [netbsd] + + '@typescript/typescript-netbsd-x64@7.0.2': + resolution: {integrity: sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [netbsd] + + '@typescript/typescript-openbsd-arm64@7.0.2': + resolution: {integrity: sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [openbsd] + + '@typescript/typescript-openbsd-x64@7.0.2': + resolution: {integrity: sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [openbsd] + + '@typescript/typescript-sunos-x64@7.0.2': + resolution: {integrity: sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [sunos] + + '@typescript/typescript-win32-arm64@7.0.2': + resolution: {integrity: sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [win32] + + '@typescript/typescript-win32-x64@7.0.2': + resolution: {integrity: sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [win32] + + '@webassemblyjs/ast@1.14.1': + resolution: {integrity: sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==} + + '@webassemblyjs/floating-point-hex-parser@1.13.2': + resolution: {integrity: sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==} + + '@webassemblyjs/helper-api-error@1.13.2': + resolution: {integrity: sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==} + + '@webassemblyjs/helper-buffer@1.14.1': + resolution: {integrity: sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==} + + '@webassemblyjs/helper-numbers@1.13.2': + resolution: {integrity: sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==} + + '@webassemblyjs/helper-wasm-bytecode@1.13.2': + resolution: {integrity: sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==} + + '@webassemblyjs/helper-wasm-section@1.14.1': + resolution: {integrity: sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==} + + '@webassemblyjs/ieee754@1.13.2': + resolution: {integrity: sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==} + + '@webassemblyjs/leb128@1.13.2': + resolution: {integrity: sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==} + + '@webassemblyjs/utf8@1.13.2': + resolution: {integrity: sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==} + + '@webassemblyjs/wasm-edit@1.14.1': + resolution: {integrity: sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==} + + '@webassemblyjs/wasm-gen@1.14.1': + resolution: {integrity: sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==} + + '@webassemblyjs/wasm-opt@1.14.1': + resolution: {integrity: sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==} + + '@webassemblyjs/wasm-parser@1.14.1': + resolution: {integrity: sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==} + + '@webassemblyjs/wast-printer@1.14.1': + resolution: {integrity: sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==} + + '@xtuc/ieee754@1.2.0': + resolution: {integrity: sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==} + + '@xtuc/long@4.2.2': + resolution: {integrity: sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==} + + acorn-import-phases@1.0.4: + resolution: {integrity: sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==} + engines: {node: '>=10.13.0'} + peerDependencies: + acorn: ^8.14.0 + + acorn@8.18.0: + resolution: {integrity: sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==} + engines: {node: '>=0.4.0'} + hasBin: true + + ajv-formats@3.0.1: + resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + + ajv-keywords@5.1.0: + resolution: {integrity: sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==} + peerDependencies: + ajv: ^8.8.2 + + ajv@8.20.0: + resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + ast-types@0.16.1: + resolution: {integrity: sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==} + engines: {node: '>=4'} + + ast-types@0.16.3: + resolution: {integrity: sha512-FvWoWYfSCM6kRxCSH+MGLHIKKGRL6A6AW7Zek2O32REPQRdg131428uRTKMBYAeRd3XXAaHDS60Wpri7CdKDrA==} + engines: {node: '>=4'} + + baseline-browser-mapping@2.11.26: + resolution: {integrity: sha512-GLQdD3y6UF8iVuMJl5fHgE4jdn/ua7n+toKfLgNlg3BqQtOZjpy68T8Tup8/wGWZCDlm7KMg7tPb4MPn7oN0TQ==} + engines: {node: '>=6.0.0'} + hasBin: true + + browserslist@4.29.3: + resolution: {integrity: sha512-1R4kiYKXGViqEN0CnoDrXc1StD9niAwu+j2dukWzrD4bJgsD4lDmEp0CRbc6E/vYJIfTHwPmwyaKtVSudICdPA==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + camelcase@6.3.0: + resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} + engines: {node: '>=10'} + + caniuse-lite@1.0.30001814: + resolution: {integrity: sha512-/Uaf1lAzr59XcMpW0o96WoEfr+VXK2OX4U9AgFoiSHsVJ4HppnIFUjtYzsyDH2+tgANaQb2/oxYGwCPapN1FpA==} + + chrome-trace-event@1.0.4: + resolution: {integrity: sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==} + engines: {node: '>=6.0'} + + commander@2.20.3: + resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + cosmiconfig@8.3.6: + resolution: {integrity: sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==} + engines: {node: '>=14'} + peerDependencies: + typescript: '>=4.9.5' + peerDependenciesMeta: + typescript: + optional: true + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + css-loader@7.1.4: + resolution: {integrity: sha512-vv3J9tlOl04WjiMvHQI/9tmIrCxVrj6PFbHemBB1iihpeRbi/I4h033eoFIhwxBBqLhI0KYFS7yvynBFhIZfTw==} + engines: {node: '>= 18.12.0'} + peerDependencies: + '@rspack/core': 0.x || ^1.0.0 || ^2.0.0-0 + webpack: ^5.27.0 + peerDependenciesMeta: + '@rspack/core': + optional: true + webpack: + optional: true + + cssesc@3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + define-lazy-prop@2.0.0: + resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==} + engines: {node: '>=8'} + + dot-case@3.0.4: + resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==} + + dotenv@17.3.1: + resolution: {integrity: sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==} + engines: {node: '>=12'} + + electron-to-chromium@1.5.443: + resolution: {integrity: sha512-TDJG36L9A3CWWwZ97HKaE+Iz1sW80pNp6sAU0owzCIV3Zc9eVyRqZK8Vz6NKuMIU8WjP0pIkoqCa0VRf/yFD0Q==} + + enhanced-resolve@5.26.0: + resolution: {integrity: sha512-9vhedylFonb2YGogzUKX6+Ja72gOJbN1QHAqdrvqLwhdl/QWbKopzoUC9EbQNVsAns/bx/4uyqalrQAoy1IByw==} + engines: {node: '>=10.13.0'} + + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + + error-ex@1.3.4: + resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} + + error-stack-parser@2.1.4: + resolution: {integrity: sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==} + + es-module-lexer@2.3.2: + resolution: {integrity: sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==} + + esbuild@0.28.1: + resolution: {integrity: sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==} + engines: {node: '>=18'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + eslint-scope@5.1.1: + resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} + engines: {node: '>=8.0.0'} + + esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@4.3.0: + resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + events@3.3.0: + resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} + engines: {node: '>=0.8.x'} + + execa@5.1.1: + resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} + engines: {node: '>=10'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-uri@3.1.8: + resolution: {integrity: sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg==} + + fs-monkey@1.0.3: + resolution: {integrity: sha512-cybjIfiiE+pTWicSCLFHSrXZ6EilF30oh91FDP9S2B051prEa7QWfrVTQm10/dDpswBDXZugPa1Ogu8Yh+HV0Q==} + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + get-stream@6.0.1: + resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} + engines: {node: '>=10'} + + glob-to-regexp@0.4.1: + resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + html-entities@2.6.0: + resolution: {integrity: sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ==} + + human-signals@2.1.0: + resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} + engines: {node: '>=10.17.0'} + + icss-utils@5.1.0: + resolution: {integrity: sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==} + engines: {node: ^10 || ^12 || >= 14} + peerDependencies: + postcss: ^8.1.0 + + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + + is-docker@2.2.1: + resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} + engines: {node: '>=8'} + hasBin: true + + is-stream@2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + + is-wsl@2.2.0: + resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} + engines: {node: '>=8'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + jest-worker@27.5.1: + resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==} + engines: {node: '>= 10.13.0'} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@4.3.2: + resolution: {integrity: sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==} + hasBin: true + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json-parse-even-better-errors@2.3.1: + resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + kiwi-schema@0.5.0: + resolution: {integrity: sha512-X+FpfU0yTEtc6aTHS7VwbOpvQwRt70+pXXWRI5fd6CvWhe7pSVC854TVo4Zo0x5/wwcWj+/9KUlXpdcP0dY9AA==} + hasBin: true + + kleur@3.0.3: + resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} + engines: {node: '>=6'} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + loader-runner@4.3.2: + resolution: {integrity: sha512-DFEqQ3ihfS9blba08cLfYf1NRAIEm+dDjic073DRDc3/JspI/8wYmtDsHwd3+4hwvdxSK7PGaElfTmm0awWJ4w==} + engines: {node: '>=6.11.5'} + + lower-case@2.0.2: + resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + lru-cache@6.0.0: + resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} + engines: {node: '>=10'} + + mediabunny@1.56.1: + resolution: {integrity: sha512-DNOm0bRBmeOxJgCK/g3LxNWbxY5morTauNdlychujGmJ9L37ezVezkNoI1WtO2Xdb4WlWWJlYfIKnOEzd5phRw==} + + memfs@3.4.3: + resolution: {integrity: sha512-eivjfi7Ahr6eQTn44nvTnR60e4a1Fs1Via2kCR5lHo/kyNoiMWaXCNJ/GpSd0ilXas2JSOl9B5FTIhflXu0hlg==} + engines: {node: '>= 4.0.0'} + + merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + mimic-fn@2.1.0: + resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} + engines: {node: '>=6'} + + minimist@1.2.6: + resolution: {integrity: sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + nanoid@3.3.19: + resolution: {integrity: sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + neo-async@2.6.2: + resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} + + no-case@3.0.4: + resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} + + node-releases@2.0.57: + resolution: {integrity: sha512-kQK9LGGFiHtrWiNhZtA7Qbw17AQz+dmsEKODRIVTXA9+e5MS/2gZEBhYJt13GrAz5/IOZKddH/0Z3TP/Zgo+yw==} + engines: {node: '>=18'} + + npm-run-path@4.0.1: + resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} + engines: {node: '>=8'} + + onetime@5.1.2: + resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} + engines: {node: '>=6'} + + open@8.4.2: + resolution: {integrity: sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==} + engines: {node: '>=12'} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + parse-json@5.2.0: + resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} + engines: {node: '>=8'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + postcss-modules-extract-imports@3.1.0: + resolution: {integrity: sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==} + engines: {node: ^10 || ^12 || >= 14} + peerDependencies: + postcss: ^8.1.0 + + postcss-modules-local-by-default@4.2.0: + resolution: {integrity: sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw==} + engines: {node: ^10 || ^12 || >= 14} + peerDependencies: + postcss: ^8.1.0 + + postcss-modules-scope@3.2.1: + resolution: {integrity: sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA==} + engines: {node: ^10 || ^12 || >= 14} + peerDependencies: + postcss: ^8.1.0 + + postcss-modules-values@4.0.0: + resolution: {integrity: sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==} + engines: {node: ^10 || ^12 || >= 14} + peerDependencies: + postcss: ^8.1.0 + + postcss-selector-parser@7.1.6: + resolution: {integrity: sha512-7qASPzhKF2l2KLboRZux8CCTRMdGiV08vWmyKzPz22qZ7ZjQBOeY7rNzNoCLSUiftJ7HUq0GERHmxw/t0dCdMw==} + engines: {node: '>=4'} + + postcss-value-parser@4.2.0: + resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} + + postcss@8.5.28: + resolution: {integrity: sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==} + engines: {node: ^10 || ^12 || >=14} + + prismjs@1.30.0: + resolution: {integrity: sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==} + engines: {node: '>=6'} + + prompts@2.4.2: + resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} + engines: {node: '>= 6'} + + react-dom@19.2.4: + resolution: {integrity: sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==} + peerDependencies: + react: ^19.2.4 + + react-refresh@0.18.0: + resolution: {integrity: sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==} + engines: {node: '>=0.10.0'} + + react@19.2.4: + resolution: {integrity: sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==} + engines: {node: '>=0.10.0'} + + recast@0.23.21: + resolution: {integrity: sha512-mFAyJq9vUbSTARLZUvAEf1z3YxlvAwswbmxMx2mPA/MSm4KmpwvwvhsH/NIrZhyOuwD60Lzyw2qh83uCbgTPYw==} + engines: {node: '>= 4'} + + remotion@4.0.530: + resolution: {integrity: sha512-7pp7F6P1Jdh/xncBiUGzNt9Vt4AMiw8M4KDtpM5J5KMN8JftlVuC/aGyUISjbUcWxf37x+YMDiPprWLecwIqkw==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + scheduler@0.27.0: + resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + + schema-utils@4.5.0: + resolution: {integrity: sha512-zJlMCZ0cAR5p/Y4oVpRoqioDMJcGxaXRrQ/4rP4WyR84vc5z/DolXdbvXeDpTwbtocDFr2rhPqHPErDCUtz2kA==} + engines: {node: '>= 10.13.0'} + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + semver@7.5.3: + resolution: {integrity: sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==} + engines: {node: '>=10'} + hasBin: true + + semver@7.8.5: + resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==} + engines: {node: '>=10'} + hasBin: true + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + signal-exit@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + + sisteransi@1.0.5: + resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} + + snake-case@3.0.4: + resolution: {integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==} + + source-map-js@1.2.2: + resolution: {integrity: sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==} + engines: {node: '>=0.10.0'} + + source-map-support@0.5.21: + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + source-map@0.8.0: + resolution: {integrity: sha512-d8EqvL+k/SOXCreS/SUzg2ciyHqBBLcN/yuRjFsbvVhHTE2pgei7oAhmPM7kWFbkX6OSMQfUq4KbkF3au9lhYQ==} + engines: {node: '>= 12'} + + stackframe@1.3.4: + resolution: {integrity: sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==} + + strip-final-newline@2.0.0: + resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} + engines: {node: '>=6'} + + style-loader@4.0.0: + resolution: {integrity: sha512-1V4WqhhZZgjVAVJyt7TdDPZoPBPNHbekX4fWnCJL1yQukhCeZhJySUL+gL9y6sNdN95uEOS83Y55SqHcP7MzLA==} + engines: {node: '>= 18.12.0'} + peerDependencies: + webpack: ^5.27.0 + + supports-color@8.1.1: + resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} + engines: {node: '>=10'} + + svg-parser@2.1.0: + resolution: {integrity: sha512-bwLf38YmY+TDYHJw1Ex0Co8c4yeXuJAo8YnXGZrscxrvYoVVIvLeniEkV1Ks/54VteMnL4FtyN1+P+TZJdUPmQ==} + engines: {node: '>=8'} + + tapable@2.3.3: + resolution: {integrity: sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==} + engines: {node: '>=6'} + + terser-webpack-plugin@5.6.1: + resolution: {integrity: sha512-201R5j+sJpK8nFWwKVyNfZot8FaJbLZDq5evriVzbV1wDtSXDjRUDRfJzHpAaxFDMEhsZL1QkeqM61wgsS3KaQ==} + engines: {node: '>= 10.13.0'} + peerDependencies: + '@minify-html/node': '*' + '@swc/core': '*' + '@swc/css': '*' + '@swc/html': '*' + clean-css: '*' + cssnano: '*' + csso: '*' + esbuild: '*' + html-minifier-terser: '*' + lightningcss: '*' + postcss: '*' + uglify-js: '*' + webpack: ^5.1.0 + peerDependenciesMeta: + '@minify-html/node': + optional: true + '@swc/core': + optional: true + '@swc/css': + optional: true + '@swc/html': + optional: true + clean-css: + optional: true + cssnano: + optional: true + csso: + optional: true + esbuild: + optional: true + html-minifier-terser: + optional: true + lightningcss: + optional: true + postcss: + optional: true + uglify-js: + optional: true + + terser@5.51.2: + resolution: {integrity: sha512-bWnjSNscmuI+GJze6ZupnHP8G/cTcsJF+bXCeQknk2SHQsgbNJnLrqiH9jZ2W4STPVXH2mDKKRX3iwPhc9Cn/Q==} + engines: {node: '>=10'} + hasBin: true + + tiny-invariant@1.3.3: + resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} + + to-fast-properties@2.0.0: + resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} + engines: {node: '>=4'} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + typescript@7.0.2: + resolution: {integrity: sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==} + engines: {node: '>=16.20.0'} + hasBin: true + + undici-types@8.9.0: + resolution: {integrity: sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==} + + update-browserslist-db@1.3.3: + resolution: {integrity: sha512-pJ2sYawQS0R/WI928Gj5GlPhTGzbMelq0+4INtSYNDV9ErKJcX6xjGWkoG/VnB3dpUm00zALaqkrUD77pO5TDQ==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + watchpack@2.5.2: + resolution: {integrity: sha512-6i/00NBjP4yGPs+caKSyRfpTF/8Torsu0MOW3mMzIbhgISFder8i7xbqgHlLMwJrdiN8ndBV3UA1/AfzPSr+jg==} + engines: {node: '>=10.13.0'} + + webpack-sources@3.6.0: + resolution: {integrity: sha512-EIMmPVvNI0CYXyRPp34F2Qk7W37/BZVZIofS6LAUnrNjCVahS+hO4mxwFJDjQjODTGZDtVzLYoN2+1dYkhk9qA==} + engines: {node: '>=10.13.0'} + + webpack@5.105.0: + resolution: {integrity: sha512-gX/dMkRQc7QOMzgTe6KsYFM7DxeIONQSui1s0n/0xht36HvrgbxtM1xBlgx596NbpHuQU8P7QpKwrZYwUX48nw==} + engines: {node: '>=10.13.0'} + hasBin: true + peerDependencies: + webpack-cli: '*' + peerDependenciesMeta: + webpack-cli: + optional: true + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + ws@8.21.0: + resolution: {integrity: sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + yallist@4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + + zod@4.5.4: + resolution: {integrity: sha512-sC95tT5iHHH9gtpj6A81kh+NEaRAUFN+qlUPDUbRfOMvNf5QCBqsb3WgvnpVtK5Y+4UfA6KqufotuTvMGiTlsA==} + +snapshots: + + '@babel/code-frame@7.29.7': + dependencies: + '@babel/helper-validator-identifier': 7.29.7 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.29.7': {} + + '@babel/core@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.8 + '@babel/helper-compilation-targets': 7.29.7 + '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) + '@babel/helpers': 7.29.7 + '@babel/parser': 7.29.9 + '@babel/template': 7.29.7 + '@babel/traverse': 7.29.8 + '@babel/types': 7.29.8 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.29.8': + dependencies: + '@babel/parser': 7.29.9 + '@babel/types': 7.29.8 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/helper-compilation-targets@7.29.7': + dependencies: + '@babel/compat-data': 7.29.7 + '@babel/helper-validator-option': 7.29.7 + browserslist: 4.29.3 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-globals@7.29.7': {} + + '@babel/helper-module-imports@7.29.7': + dependencies: + '@babel/traverse': 7.29.8 + '@babel/types': 7.29.8 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-module-imports': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + '@babel/traverse': 7.29.8 + transitivePeerDependencies: + - supports-color + + '@babel/helper-string-parser@7.29.7': {} + + '@babel/helper-validator-identifier@7.29.7': {} + + '@babel/helper-validator-option@7.29.7': {} + + '@babel/helpers@7.29.7': + dependencies: + '@babel/template': 7.29.7 + '@babel/types': 7.29.8 + + '@babel/parser@7.24.1': + dependencies: + '@babel/types': 7.29.8 + + '@babel/parser@7.29.9': + dependencies: + '@babel/types': 7.29.8 + + '@babel/template@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/parser': 7.29.9 + '@babel/types': 7.29.8 + + '@babel/traverse@7.29.8': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.8 + '@babel/helper-globals': 7.29.7 + '@babel/parser': 7.29.9 + '@babel/template': 7.29.7 + '@babel/types': 7.29.8 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.24.0': + dependencies: + '@babel/helper-string-parser': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + to-fast-properties: 2.0.0 + + '@babel/types@7.29.8': + dependencies: + '@babel/helper-string-parser': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + + '@emnapi/core@1.11.3': + dependencies: + '@emnapi/wasi-threads': 1.2.3 + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@1.11.3': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/wasi-threads@1.2.3': + dependencies: + tslib: 2.8.1 + optional: true + + '@esbuild/aix-ppc64@0.28.1': + optional: true + + '@esbuild/android-arm64@0.28.1': + optional: true + + '@esbuild/android-arm@0.28.1': + optional: true + + '@esbuild/android-x64@0.28.1': + optional: true + + '@esbuild/darwin-arm64@0.28.1': + optional: true + + '@esbuild/darwin-x64@0.28.1': + optional: true + + '@esbuild/freebsd-arm64@0.28.1': + optional: true + + '@esbuild/freebsd-x64@0.28.1': + optional: true + + '@esbuild/linux-arm64@0.28.1': + optional: true + + '@esbuild/linux-arm@0.28.1': + optional: true + + '@esbuild/linux-ia32@0.28.1': + optional: true + + '@esbuild/linux-loong64@0.28.1': + optional: true + + '@esbuild/linux-mips64el@0.28.1': + optional: true + + '@esbuild/linux-ppc64@0.28.1': + optional: true + + '@esbuild/linux-riscv64@0.28.1': + optional: true + + '@esbuild/linux-s390x@0.28.1': + optional: true + + '@esbuild/linux-x64@0.28.1': + optional: true + + '@esbuild/netbsd-arm64@0.28.1': + optional: true + + '@esbuild/netbsd-x64@0.28.1': + optional: true + + '@esbuild/openbsd-arm64@0.28.1': + optional: true + + '@esbuild/openbsd-x64@0.28.1': + optional: true + + '@esbuild/openharmony-arm64@0.28.1': + optional: true + + '@esbuild/sunos-x64@0.28.1': + optional: true + + '@esbuild/win32-arm64@0.28.1': + optional: true + + '@esbuild/win32-ia32@0.28.1': + optional: true + + '@esbuild/win32-x64@0.28.1': + optional: true + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.6.0 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/source-map@0.3.11': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/sourcemap-codec@1.6.0': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.6.0 + + '@mediabunny/aac-encoder@1.56.1(mediabunny@1.56.1)': + dependencies: + mediabunny: 1.56.1 + + '@mediabunny/flac-encoder@1.56.1(mediabunny@1.56.1)': + dependencies: + mediabunny: 1.56.1 + + '@mediabunny/mp3-encoder@1.56.1(mediabunny@1.56.1)': + dependencies: + mediabunny: 1.56.1 + + '@module-federation/error-codes@0.22.0': {} + + '@module-federation/runtime-core@0.22.0': + dependencies: + '@module-federation/error-codes': 0.22.0 + '@module-federation/sdk': 0.22.0 + + '@module-federation/runtime-tools@0.22.0': + dependencies: + '@module-federation/runtime': 0.22.0 + '@module-federation/webpack-bundler-runtime': 0.22.0 + + '@module-federation/runtime@0.22.0': + dependencies: + '@module-federation/error-codes': 0.22.0 + '@module-federation/runtime-core': 0.22.0 + '@module-federation/sdk': 0.22.0 + + '@module-federation/sdk@0.22.0': {} + + '@module-federation/webpack-bundler-runtime@0.22.0': + dependencies: + '@module-federation/runtime': 0.22.0 + '@module-federation/sdk': 0.22.0 + + '@napi-rs/wasm-runtime@1.0.7': + dependencies: + '@emnapi/core': 1.11.3 + '@emnapi/runtime': 1.11.3 + '@tybys/wasm-util': 0.10.4 + optional: true + + '@remotion/bundler@4.0.530(postcss@8.5.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@remotion/media-parser': 4.0.530 + '@remotion/studio': 4.0.530(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@remotion/studio-shared': 4.0.530(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@remotion/timeline-utils': 4.0.530(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@rspack/core': 1.7.11 + '@rspack/plugin-react-refresh': 1.6.1(react-refresh@0.18.0) + css-loader: 7.1.4(@rspack/core@1.7.11)(webpack@5.105.0(postcss@8.5.28)) + esbuild: 0.28.1 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + react-refresh: 0.18.0 + remotion: 4.0.530(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + style-loader: 4.0.0(webpack@5.105.0(postcss@8.5.28)) + webpack: 5.105.0(esbuild@0.28.1)(postcss@8.5.28) + transitivePeerDependencies: + - '@minify-html/node' + - '@remotion/video-matting' + - '@remotion/whisper-webgpu' + - '@swc/core' + - '@swc/css' + - '@swc/helpers' + - '@swc/html' + - bufferutil + - clean-css + - cssnano + - csso + - html-minifier-terser + - lightningcss + - postcss + - uglify-js + - utf-8-validate + - webpack-cli + - webpack-hot-middleware + + '@remotion/canvas@4.0.530(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@remotion/player': 4.0.530(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@remotion/studio-shared': 4.0.530(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + remotion: 4.0.530(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + + '@remotion/captions@4.0.530': {} + + '@remotion/cli@4.0.530(postcss@8.5.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@7.0.2)': + dependencies: + '@babel/parser': 7.24.1 + '@remotion/bundler': 4.0.530(postcss@8.5.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@remotion/media-utils': 4.0.530(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@remotion/player': 4.0.530(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@remotion/renderer': 4.0.530(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@remotion/studio': 4.0.530(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@remotion/studio-server': 4.0.530(postcss@8.5.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@7.0.2) + '@remotion/studio-shared': 4.0.530(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + dotenv: 17.3.1 + minimist: 1.2.6 + prompts: 2.4.2 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + remotion: 4.0.530(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + semver: 7.5.3 + transitivePeerDependencies: + - '@minify-html/node' + - '@remotion/video-matting' + - '@remotion/whisper-webgpu' + - '@swc/core' + - '@swc/css' + - '@swc/helpers' + - '@swc/html' + - bufferutil + - clean-css + - cssnano + - csso + - html-minifier-terser + - lightningcss + - postcss + - supports-color + - typescript + - uglify-js + - utf-8-validate + - webpack-cli + - webpack-hot-middleware + + '@remotion/codemods@4.0.530(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@babel/parser': 7.24.1 + '@babel/types': 7.24.0 + '@remotion/studio-protocol': 4.0.530 + '@remotion/studio-shared': 4.0.530(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + ast-types: 0.16.1 + recast: 0.23.21 + remotion: 4.0.530(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + transitivePeerDependencies: + - react + - react-dom + + '@remotion/compositor-darwin-arm64@4.0.530': + optional: true + + '@remotion/compositor-darwin-x64@4.0.530': + optional: true + + '@remotion/compositor-linux-arm64-gnu@4.0.530': + optional: true + + '@remotion/compositor-linux-arm64-musl@4.0.530': + optional: true + + '@remotion/compositor-linux-x64-gnu@4.0.530': + optional: true + + '@remotion/compositor-linux-x64-musl@4.0.530': + optional: true + + '@remotion/compositor-win32-x64-msvc@4.0.530': + optional: true + + '@remotion/elevenlabs@4.0.530': + dependencies: + '@remotion/captions': 4.0.530 + zod: 4.5.4 + + '@remotion/fonts@4.0.530(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + remotion: 4.0.530(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + transitivePeerDependencies: + - react + - react-dom + + '@remotion/google-fonts@4.0.530(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + remotion: 4.0.530(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + transitivePeerDependencies: + - react + - react-dom + + '@remotion/layout-utils@4.0.530': {} + + '@remotion/licensing@4.0.530': {} + + '@remotion/media-parser@4.0.530': {} + + '@remotion/media-utils@4.0.530(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + mediabunny: 1.56.1 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + remotion: 4.0.530(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + + '@remotion/media@4.0.530(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + mediabunny: 1.56.1 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + remotion: 4.0.530(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + zod: 4.5.4 + + '@remotion/motion-blur@4.0.530(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + remotion: 4.0.530(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + + '@remotion/openai-whisper@4.0.530': + dependencies: + '@remotion/captions': 4.0.530 + zod: 4.5.4 + + '@remotion/paths@4.0.530(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + remotion: 4.0.530(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + transitivePeerDependencies: + - react + - react-dom + + '@remotion/player@4.0.530(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + remotion: 4.0.530(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + + '@remotion/renderer@4.0.530(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@remotion/licensing': 4.0.530 + '@remotion/streaming': 4.0.530 + execa: 5.1.1 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + remotion: 4.0.530(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + source-map: 0.8.0 + ws: 8.21.0 + optionalDependencies: + '@remotion/compositor-darwin-arm64': 4.0.530 + '@remotion/compositor-darwin-x64': 4.0.530 + '@remotion/compositor-linux-arm64-gnu': 4.0.530 + '@remotion/compositor-linux-arm64-musl': 4.0.530 + '@remotion/compositor-linux-x64-gnu': 4.0.530 + '@remotion/compositor-linux-x64-musl': 4.0.530 + '@remotion/compositor-win32-x64-msvc': 4.0.530 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + '@remotion/shapes@4.0.530(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@remotion/paths': 4.0.530(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + remotion: 4.0.530(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + + '@remotion/streaming@4.0.530': {} + + '@remotion/studio-protocol@4.0.530': + dependencies: + zod: 4.5.4 + + '@remotion/studio-server@4.0.530(postcss@8.5.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@7.0.2)': + dependencies: + '@babel/parser': 7.24.1 + '@babel/types': 7.24.0 + '@remotion/bundler': 4.0.530(postcss@8.5.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@remotion/codemods': 4.0.530(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@remotion/renderer': 4.0.530(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@remotion/studio-protocol': 4.0.530 + '@remotion/studio-shared': 4.0.530(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@svgr/core': 8.1.0(typescript@7.0.2) + '@svgr/plugin-jsx': 8.1.0(@svgr/core@8.1.0(typescript@7.0.2)) + kiwi-schema: 0.5.0 + memfs: 3.4.3 + open: 8.4.2 + recast: 0.23.21 + remotion: 4.0.530(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + semver: 7.5.3 + zod: 4.5.4 + transitivePeerDependencies: + - '@minify-html/node' + - '@remotion/video-matting' + - '@remotion/whisper-webgpu' + - '@swc/core' + - '@swc/css' + - '@swc/helpers' + - '@swc/html' + - bufferutil + - clean-css + - cssnano + - csso + - html-minifier-terser + - lightningcss + - postcss + - react + - react-dom + - supports-color + - typescript + - uglify-js + - utf-8-validate + - webpack-cli + - webpack-hot-middleware + + '@remotion/studio-shared@4.0.530(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + remotion: 4.0.530(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + transitivePeerDependencies: + - react + - react-dom + + '@remotion/studio@4.0.530(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + '@remotion/canvas': 4.0.530(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@remotion/captions': 4.0.530 + '@remotion/codemods': 4.0.530(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@remotion/elevenlabs': 4.0.530 + '@remotion/media': 4.0.530(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@remotion/media-utils': 4.0.530(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@remotion/openai-whisper': 4.0.530 + '@remotion/paths': 4.0.530(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@remotion/player': 4.0.530(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@remotion/renderer': 4.0.530(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@remotion/studio-protocol': 4.0.530 + '@remotion/studio-shared': 4.0.530(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@remotion/timeline-utils': 4.0.530(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@remotion/web-renderer': 4.0.530(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@remotion/zod-types': 4.0.530(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@tanstack/react-virtual': 3.14.9(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + mediabunny: 1.56.1 + memfs: 3.4.3 + open: 8.4.2 + prismjs: 1.30.0 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + remotion: 4.0.530(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + semver: 7.5.3 + zod: 4.5.4 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + '@remotion/timeline-utils@4.0.530(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + mediabunny: 1.56.1 + remotion: 4.0.530(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + transitivePeerDependencies: + - react + - react-dom + + '@remotion/transitions@4.0.530(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@remotion/paths': 4.0.530(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@remotion/shapes': 4.0.530(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + remotion: 4.0.530(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + + '@remotion/web-renderer@4.0.530(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@mediabunny/aac-encoder': 1.56.1(mediabunny@1.56.1) + '@mediabunny/flac-encoder': 1.56.1(mediabunny@1.56.1) + '@mediabunny/mp3-encoder': 1.56.1(mediabunny@1.56.1) + '@remotion/licensing': 4.0.530 + mediabunny: 1.56.1 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + remotion: 4.0.530(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + + '@remotion/zod-types@4.0.530(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + remotion: 4.0.530(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + transitivePeerDependencies: + - react + - react-dom + + '@rspack/binding-darwin-arm64@1.7.11': + optional: true + + '@rspack/binding-darwin-x64@1.7.11': + optional: true + + '@rspack/binding-linux-arm64-gnu@1.7.11': + optional: true + + '@rspack/binding-linux-arm64-musl@1.7.11': + optional: true + + '@rspack/binding-linux-x64-gnu@1.7.11': + optional: true + + '@rspack/binding-linux-x64-musl@1.7.11': + optional: true + + '@rspack/binding-wasm32-wasi@1.7.11': + dependencies: + '@napi-rs/wasm-runtime': 1.0.7 + optional: true + + '@rspack/binding-win32-arm64-msvc@1.7.11': + optional: true + + '@rspack/binding-win32-ia32-msvc@1.7.11': + optional: true + + '@rspack/binding-win32-x64-msvc@1.7.11': + optional: true + + '@rspack/binding@1.7.11': + optionalDependencies: + '@rspack/binding-darwin-arm64': 1.7.11 + '@rspack/binding-darwin-x64': 1.7.11 + '@rspack/binding-linux-arm64-gnu': 1.7.11 + '@rspack/binding-linux-arm64-musl': 1.7.11 + '@rspack/binding-linux-x64-gnu': 1.7.11 + '@rspack/binding-linux-x64-musl': 1.7.11 + '@rspack/binding-wasm32-wasi': 1.7.11 + '@rspack/binding-win32-arm64-msvc': 1.7.11 + '@rspack/binding-win32-ia32-msvc': 1.7.11 + '@rspack/binding-win32-x64-msvc': 1.7.11 + + '@rspack/core@1.7.11': + dependencies: + '@module-federation/runtime-tools': 0.22.0 + '@rspack/binding': 1.7.11 + '@rspack/lite-tapable': 1.1.0 + + '@rspack/lite-tapable@1.1.0': {} + + '@rspack/plugin-react-refresh@1.6.1(react-refresh@0.18.0)': + dependencies: + error-stack-parser: 2.1.4 + html-entities: 2.6.0 + react-refresh: 0.18.0 + + '@svgr/babel-plugin-add-jsx-attribute@8.0.0(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + + '@svgr/babel-plugin-remove-jsx-attribute@8.0.0(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + + '@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + + '@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + + '@svgr/babel-plugin-svg-dynamic-title@8.0.0(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + + '@svgr/babel-plugin-svg-em-dimensions@8.0.0(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + + '@svgr/babel-plugin-transform-react-native-svg@8.1.0(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + + '@svgr/babel-plugin-transform-svg-component@8.0.0(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + + '@svgr/babel-preset@8.1.0(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@svgr/babel-plugin-add-jsx-attribute': 8.0.0(@babel/core@7.29.7) + '@svgr/babel-plugin-remove-jsx-attribute': 8.0.0(@babel/core@7.29.7) + '@svgr/babel-plugin-remove-jsx-empty-expression': 8.0.0(@babel/core@7.29.7) + '@svgr/babel-plugin-replace-jsx-attribute-value': 8.0.0(@babel/core@7.29.7) + '@svgr/babel-plugin-svg-dynamic-title': 8.0.0(@babel/core@7.29.7) + '@svgr/babel-plugin-svg-em-dimensions': 8.0.0(@babel/core@7.29.7) + '@svgr/babel-plugin-transform-react-native-svg': 8.1.0(@babel/core@7.29.7) + '@svgr/babel-plugin-transform-svg-component': 8.0.0(@babel/core@7.29.7) + + '@svgr/core@8.1.0(typescript@7.0.2)': + dependencies: + '@babel/core': 7.29.7 + '@svgr/babel-preset': 8.1.0(@babel/core@7.29.7) + camelcase: 6.3.0 + cosmiconfig: 8.3.6(typescript@7.0.2) + snake-case: 3.0.4 + transitivePeerDependencies: + - supports-color + - typescript + + '@svgr/hast-util-to-babel-ast@8.0.0': + dependencies: + '@babel/types': 7.24.0 + entities: 4.5.0 + + '@svgr/plugin-jsx@8.1.0(@svgr/core@8.1.0(typescript@7.0.2))': + dependencies: + '@babel/core': 7.29.7 + '@svgr/babel-preset': 8.1.0(@babel/core@7.29.7) + '@svgr/core': 8.1.0(typescript@7.0.2) + '@svgr/hast-util-to-babel-ast': 8.0.0 + svg-parser: 2.1.0 + transitivePeerDependencies: + - supports-color + + '@tanstack/react-virtual@3.14.9(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@tanstack/virtual-core': 3.17.7 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + + '@tanstack/virtual-core@3.17.7': {} + + '@tybys/wasm-util@0.10.4': + dependencies: + tslib: 2.8.1 + optional: true + + '@types/dom-mediacapture-transform@0.1.12': + dependencies: + '@types/dom-webcodecs': 0.1.13 + + '@types/dom-webcodecs@0.1.13': {} + + '@types/eslint-scope@3.7.7': + dependencies: + '@types/eslint': 9.6.1 + '@types/estree': 1.0.9 + + '@types/eslint@9.6.1': + dependencies: + '@types/estree': 1.0.9 + '@types/json-schema': 7.0.15 + + '@types/estree@1.0.9': {} + + '@types/json-schema@7.0.15': {} + + '@types/node@26.6.3': + dependencies: + undici-types: 8.9.0 + + '@types/react@19.3.0': + dependencies: + csstype: 3.2.3 + + '@typescript/typescript-aix-ppc64@7.0.2': + optional: true + + '@typescript/typescript-darwin-arm64@7.0.2': + optional: true + + '@typescript/typescript-darwin-x64@7.0.2': + optional: true + + '@typescript/typescript-freebsd-arm64@7.0.2': + optional: true + + '@typescript/typescript-freebsd-x64@7.0.2': + optional: true + + '@typescript/typescript-linux-arm64@7.0.2': + optional: true + + '@typescript/typescript-linux-arm@7.0.2': + optional: true + + '@typescript/typescript-linux-loong64@7.0.2': + optional: true + + '@typescript/typescript-linux-mips64el@7.0.2': + optional: true + + '@typescript/typescript-linux-ppc64@7.0.2': + optional: true + + '@typescript/typescript-linux-riscv64@7.0.2': + optional: true + + '@typescript/typescript-linux-s390x@7.0.2': + optional: true + + '@typescript/typescript-linux-x64@7.0.2': + optional: true + + '@typescript/typescript-netbsd-arm64@7.0.2': + optional: true + + '@typescript/typescript-netbsd-x64@7.0.2': + optional: true + + '@typescript/typescript-openbsd-arm64@7.0.2': + optional: true + + '@typescript/typescript-openbsd-x64@7.0.2': + optional: true + + '@typescript/typescript-sunos-x64@7.0.2': + optional: true + + '@typescript/typescript-win32-arm64@7.0.2': + optional: true + + '@typescript/typescript-win32-x64@7.0.2': + optional: true + + '@webassemblyjs/ast@1.14.1': + dependencies: + '@webassemblyjs/helper-numbers': 1.13.2 + '@webassemblyjs/helper-wasm-bytecode': 1.13.2 + + '@webassemblyjs/floating-point-hex-parser@1.13.2': {} + + '@webassemblyjs/helper-api-error@1.13.2': {} + + '@webassemblyjs/helper-buffer@1.14.1': {} + + '@webassemblyjs/helper-numbers@1.13.2': + dependencies: + '@webassemblyjs/floating-point-hex-parser': 1.13.2 + '@webassemblyjs/helper-api-error': 1.13.2 + '@xtuc/long': 4.2.2 + + '@webassemblyjs/helper-wasm-bytecode@1.13.2': {} + + '@webassemblyjs/helper-wasm-section@1.14.1': + dependencies: + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/helper-buffer': 1.14.1 + '@webassemblyjs/helper-wasm-bytecode': 1.13.2 + '@webassemblyjs/wasm-gen': 1.14.1 + + '@webassemblyjs/ieee754@1.13.2': + dependencies: + '@xtuc/ieee754': 1.2.0 + + '@webassemblyjs/leb128@1.13.2': + dependencies: + '@xtuc/long': 4.2.2 + + '@webassemblyjs/utf8@1.13.2': {} + + '@webassemblyjs/wasm-edit@1.14.1': + dependencies: + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/helper-buffer': 1.14.1 + '@webassemblyjs/helper-wasm-bytecode': 1.13.2 + '@webassemblyjs/helper-wasm-section': 1.14.1 + '@webassemblyjs/wasm-gen': 1.14.1 + '@webassemblyjs/wasm-opt': 1.14.1 + '@webassemblyjs/wasm-parser': 1.14.1 + '@webassemblyjs/wast-printer': 1.14.1 + + '@webassemblyjs/wasm-gen@1.14.1': + dependencies: + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/helper-wasm-bytecode': 1.13.2 + '@webassemblyjs/ieee754': 1.13.2 + '@webassemblyjs/leb128': 1.13.2 + '@webassemblyjs/utf8': 1.13.2 + + '@webassemblyjs/wasm-opt@1.14.1': + dependencies: + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/helper-buffer': 1.14.1 + '@webassemblyjs/wasm-gen': 1.14.1 + '@webassemblyjs/wasm-parser': 1.14.1 + + '@webassemblyjs/wasm-parser@1.14.1': + dependencies: + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/helper-api-error': 1.13.2 + '@webassemblyjs/helper-wasm-bytecode': 1.13.2 + '@webassemblyjs/ieee754': 1.13.2 + '@webassemblyjs/leb128': 1.13.2 + '@webassemblyjs/utf8': 1.13.2 + + '@webassemblyjs/wast-printer@1.14.1': + dependencies: + '@webassemblyjs/ast': 1.14.1 + '@xtuc/long': 4.2.2 + + '@xtuc/ieee754@1.2.0': {} + + '@xtuc/long@4.2.2': {} + + acorn-import-phases@1.0.4(acorn@8.18.0): + dependencies: + acorn: 8.18.0 + + acorn@8.18.0: {} + + ajv-formats@3.0.1(ajv@8.20.0): + optionalDependencies: + ajv: 8.20.0 + + ajv-keywords@5.1.0(ajv@8.20.0): + dependencies: + ajv: 8.20.0 + fast-deep-equal: 3.1.3 + + ajv@8.20.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.8 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + + argparse@2.0.1: {} + + ast-types@0.16.1: + dependencies: + tslib: 2.8.1 + + ast-types@0.16.3: + dependencies: + tslib: 2.8.1 + + baseline-browser-mapping@2.11.26: {} + + browserslist@4.29.3: + dependencies: + baseline-browser-mapping: 2.11.26 + caniuse-lite: 1.0.30001814 + electron-to-chromium: 1.5.443 + node-releases: 2.0.57 + update-browserslist-db: 1.3.3(browserslist@4.29.3) + + buffer-from@1.1.2: {} + + callsites@3.1.0: {} + + camelcase@6.3.0: {} + + caniuse-lite@1.0.30001814: {} + + chrome-trace-event@1.0.4: {} + + commander@2.20.3: {} + + convert-source-map@2.0.0: {} + + cosmiconfig@8.3.6(typescript@7.0.2): + dependencies: + import-fresh: 3.3.1 + js-yaml: 4.3.2 + parse-json: 5.2.0 + path-type: 4.0.0 + optionalDependencies: + typescript: 7.0.2 + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + css-loader@7.1.4(@rspack/core@1.7.11)(webpack@5.105.0(postcss@8.5.28)): + dependencies: + icss-utils: 5.1.0(postcss@8.5.28) + postcss: 8.5.28 + postcss-modules-extract-imports: 3.1.0(postcss@8.5.28) + postcss-modules-local-by-default: 4.2.0(postcss@8.5.28) + postcss-modules-scope: 3.2.1(postcss@8.5.28) + postcss-modules-values: 4.0.0(postcss@8.5.28) + postcss-value-parser: 4.2.0 + semver: 7.8.5 + optionalDependencies: + '@rspack/core': 1.7.11 + webpack: 5.105.0(esbuild@0.28.1)(postcss@8.5.28) + + cssesc@3.0.0: {} + + csstype@3.2.3: {} + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + define-lazy-prop@2.0.0: {} + + dot-case@3.0.4: + dependencies: + no-case: 3.0.4 + tslib: 2.8.1 + + dotenv@17.3.1: {} + + electron-to-chromium@1.5.443: {} + + enhanced-resolve@5.26.0: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.3.3 + + entities@4.5.0: {} + + error-ex@1.3.4: + dependencies: + is-arrayish: 0.2.1 + + error-stack-parser@2.1.4: + dependencies: + stackframe: 1.3.4 + + es-module-lexer@2.3.2: {} + + esbuild@0.28.1: + optionalDependencies: + '@esbuild/aix-ppc64': 0.28.1 + '@esbuild/android-arm': 0.28.1 + '@esbuild/android-arm64': 0.28.1 + '@esbuild/android-x64': 0.28.1 + '@esbuild/darwin-arm64': 0.28.1 + '@esbuild/darwin-x64': 0.28.1 + '@esbuild/freebsd-arm64': 0.28.1 + '@esbuild/freebsd-x64': 0.28.1 + '@esbuild/linux-arm': 0.28.1 + '@esbuild/linux-arm64': 0.28.1 + '@esbuild/linux-ia32': 0.28.1 + '@esbuild/linux-loong64': 0.28.1 + '@esbuild/linux-mips64el': 0.28.1 + '@esbuild/linux-ppc64': 0.28.1 + '@esbuild/linux-riscv64': 0.28.1 + '@esbuild/linux-s390x': 0.28.1 + '@esbuild/linux-x64': 0.28.1 + '@esbuild/netbsd-arm64': 0.28.1 + '@esbuild/netbsd-x64': 0.28.1 + '@esbuild/openbsd-arm64': 0.28.1 + '@esbuild/openbsd-x64': 0.28.1 + '@esbuild/openharmony-arm64': 0.28.1 + '@esbuild/sunos-x64': 0.28.1 + '@esbuild/win32-arm64': 0.28.1 + '@esbuild/win32-ia32': 0.28.1 + '@esbuild/win32-x64': 0.28.1 + + escalade@3.2.0: {} + + eslint-scope@5.1.1: + dependencies: + esrecurse: 4.3.0 + estraverse: 4.3.0 + + esprima@4.0.1: {} + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@4.3.0: {} + + estraverse@5.3.0: {} + + events@3.3.0: {} + + execa@5.1.1: + dependencies: + cross-spawn: 7.0.6 + get-stream: 6.0.1 + human-signals: 2.1.0 + is-stream: 2.0.1 + merge-stream: 2.0.0 + npm-run-path: 4.0.1 + onetime: 5.1.2 + signal-exit: 3.0.7 + strip-final-newline: 2.0.0 + + fast-deep-equal@3.1.3: {} + + fast-uri@3.1.8: {} + + fs-monkey@1.0.3: {} + + gensync@1.0.0-beta.2: {} + + get-stream@6.0.1: {} + + glob-to-regexp@0.4.1: {} + + graceful-fs@4.2.11: {} + + has-flag@4.0.0: {} + + html-entities@2.6.0: {} + + human-signals@2.1.0: {} + + icss-utils@5.1.0(postcss@8.5.28): + dependencies: + postcss: 8.5.28 + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + is-arrayish@0.2.1: {} + + is-docker@2.2.1: {} + + is-stream@2.0.1: {} + + is-wsl@2.2.0: + dependencies: + is-docker: 2.2.1 + + isexe@2.0.0: {} + + jest-worker@27.5.1: + dependencies: + '@types/node': 26.6.3 + merge-stream: 2.0.0 + supports-color: 8.1.1 + + js-tokens@4.0.0: {} + + js-yaml@4.3.2: + dependencies: + argparse: 2.0.1 + + jsesc@3.1.0: {} + + json-parse-even-better-errors@2.3.1: {} + + json-schema-traverse@1.0.0: {} + + json5@2.2.3: {} + + kiwi-schema@0.5.0: {} + + kleur@3.0.3: {} + + lines-and-columns@1.2.4: {} + + loader-runner@4.3.2: {} + + lower-case@2.0.2: + dependencies: + tslib: 2.8.1 + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + lru-cache@6.0.0: + dependencies: + yallist: 4.0.0 + + mediabunny@1.56.1: + dependencies: + '@types/dom-mediacapture-transform': 0.1.12 + '@types/dom-webcodecs': 0.1.13 + + memfs@3.4.3: + dependencies: + fs-monkey: 1.0.3 + + merge-stream@2.0.0: {} + + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + mimic-fn@2.1.0: {} + + minimist@1.2.6: {} + + ms@2.1.3: {} + + nanoid@3.3.19: {} + + neo-async@2.6.2: {} + + no-case@3.0.4: + dependencies: + lower-case: 2.0.2 + tslib: 2.8.1 + + node-releases@2.0.57: {} + + npm-run-path@4.0.1: + dependencies: + path-key: 3.1.1 + + onetime@5.1.2: + dependencies: + mimic-fn: 2.1.0 + + open@8.4.2: + dependencies: + define-lazy-prop: 2.0.0 + is-docker: 2.2.1 + is-wsl: 2.2.0 + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + parse-json@5.2.0: + dependencies: + '@babel/code-frame': 7.29.7 + error-ex: 1.3.4 + json-parse-even-better-errors: 2.3.1 + lines-and-columns: 1.2.4 + + path-key@3.1.1: {} + + path-type@4.0.0: {} + + picocolors@1.1.1: {} + + postcss-modules-extract-imports@3.1.0(postcss@8.5.28): + dependencies: + postcss: 8.5.28 + + postcss-modules-local-by-default@4.2.0(postcss@8.5.28): + dependencies: + icss-utils: 5.1.0(postcss@8.5.28) + postcss: 8.5.28 + postcss-selector-parser: 7.1.6 + postcss-value-parser: 4.2.0 + + postcss-modules-scope@3.2.1(postcss@8.5.28): + dependencies: + postcss: 8.5.28 + postcss-selector-parser: 7.1.6 + + postcss-modules-values@4.0.0(postcss@8.5.28): + dependencies: + icss-utils: 5.1.0(postcss@8.5.28) + postcss: 8.5.28 + + postcss-selector-parser@7.1.6: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + + postcss-value-parser@4.2.0: {} + + postcss@8.5.28: + dependencies: + nanoid: 3.3.19 + picocolors: 1.1.1 + source-map-js: 1.2.2 + + prismjs@1.30.0: {} + + prompts@2.4.2: + dependencies: + kleur: 3.0.3 + sisteransi: 1.0.5 + + react-dom@19.2.4(react@19.2.4): + dependencies: + react: 19.2.4 + scheduler: 0.27.0 + + react-refresh@0.18.0: {} + + react@19.2.4: {} + + recast@0.23.21: + dependencies: + ast-types: 0.16.3 + esprima: 4.0.1 + source-map: 0.6.1 + tiny-invariant: 1.3.3 + tslib: 2.8.1 + + remotion@4.0.530(react-dom@19.2.4(react@19.2.4))(react@19.2.4): + dependencies: + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + + require-from-string@2.0.2: {} + + resolve-from@4.0.0: {} + + scheduler@0.27.0: {} + + schema-utils@4.5.0: + dependencies: + '@types/json-schema': 7.0.15 + ajv: 8.20.0 + ajv-formats: 3.0.1(ajv@8.20.0) + ajv-keywords: 5.1.0(ajv@8.20.0) + + semver@6.3.1: {} + + semver@7.5.3: + dependencies: + lru-cache: 6.0.0 + + semver@7.8.5: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + signal-exit@3.0.7: {} + + sisteransi@1.0.5: {} + + snake-case@3.0.4: + dependencies: + dot-case: 3.0.4 + tslib: 2.8.1 + + source-map-js@1.2.2: {} + + source-map-support@0.5.21: + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + + source-map@0.6.1: {} + + source-map@0.8.0: {} + + stackframe@1.3.4: {} + + strip-final-newline@2.0.0: {} + + style-loader@4.0.0(webpack@5.105.0(postcss@8.5.28)): + dependencies: + webpack: 5.105.0(esbuild@0.28.1)(postcss@8.5.28) + + supports-color@8.1.1: + dependencies: + has-flag: 4.0.0 + + svg-parser@2.1.0: {} + + tapable@2.3.3: {} + + terser-webpack-plugin@5.6.1(esbuild@0.28.1)(postcss@8.5.28)(webpack@5.105.0(postcss@8.5.28)): + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + jest-worker: 27.5.1 + schema-utils: 4.5.0 + terser: 5.51.2 + webpack: 5.105.0(esbuild@0.28.1)(postcss@8.5.28) + optionalDependencies: + esbuild: 0.28.1 + postcss: 8.5.28 + + terser@5.51.2: + dependencies: + '@jridgewell/source-map': 0.3.11 + acorn: 8.18.0 + commander: 2.20.3 + source-map-support: 0.5.21 + + tiny-invariant@1.3.3: {} + + to-fast-properties@2.0.0: {} + + tslib@2.8.1: {} + + typescript@7.0.2: + optionalDependencies: + '@typescript/typescript-aix-ppc64': 7.0.2 + '@typescript/typescript-darwin-arm64': 7.0.2 + '@typescript/typescript-darwin-x64': 7.0.2 + '@typescript/typescript-freebsd-arm64': 7.0.2 + '@typescript/typescript-freebsd-x64': 7.0.2 + '@typescript/typescript-linux-arm': 7.0.2 + '@typescript/typescript-linux-arm64': 7.0.2 + '@typescript/typescript-linux-loong64': 7.0.2 + '@typescript/typescript-linux-mips64el': 7.0.2 + '@typescript/typescript-linux-ppc64': 7.0.2 + '@typescript/typescript-linux-riscv64': 7.0.2 + '@typescript/typescript-linux-s390x': 7.0.2 + '@typescript/typescript-linux-x64': 7.0.2 + '@typescript/typescript-netbsd-arm64': 7.0.2 + '@typescript/typescript-netbsd-x64': 7.0.2 + '@typescript/typescript-openbsd-arm64': 7.0.2 + '@typescript/typescript-openbsd-x64': 7.0.2 + '@typescript/typescript-sunos-x64': 7.0.2 + '@typescript/typescript-win32-arm64': 7.0.2 + '@typescript/typescript-win32-x64': 7.0.2 + + undici-types@8.9.0: {} + + update-browserslist-db@1.3.3(browserslist@4.29.3): + dependencies: + browserslist: 4.29.3 + escalade: 3.2.0 + picocolors: 1.1.1 + + util-deprecate@1.0.2: {} + + watchpack@2.5.2: + dependencies: + graceful-fs: 4.2.11 + + webpack-sources@3.6.0: {} + + webpack@5.105.0(esbuild@0.28.1)(postcss@8.5.28): + dependencies: + '@types/eslint-scope': 3.7.7 + '@types/estree': 1.0.9 + '@types/json-schema': 7.0.15 + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/wasm-edit': 1.14.1 + '@webassemblyjs/wasm-parser': 1.14.1 + acorn: 8.18.0 + acorn-import-phases: 1.0.4(acorn@8.18.0) + browserslist: 4.29.3 + chrome-trace-event: 1.0.4 + enhanced-resolve: 5.26.0 + es-module-lexer: 2.3.2 + eslint-scope: 5.1.1 + events: 3.3.0 + glob-to-regexp: 0.4.1 + graceful-fs: 4.2.11 + json-parse-even-better-errors: 2.3.1 + loader-runner: 4.3.2 + mime-types: 2.1.35 + neo-async: 2.6.2 + schema-utils: 4.5.0 + tapable: 2.3.3 + terser-webpack-plugin: 5.6.1(esbuild@0.28.1)(postcss@8.5.28)(webpack@5.105.0(postcss@8.5.28)) + watchpack: 2.5.2 + webpack-sources: 3.6.0 + transitivePeerDependencies: + - '@minify-html/node' + - '@swc/core' + - '@swc/css' + - '@swc/html' + - clean-css + - cssnano + - csso + - esbuild + - html-minifier-terser + - lightningcss + - postcss + - uglify-js + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + ws@8.21.0: {} + + yallist@3.1.1: {} + + yallist@4.0.0: {} + + zod@4.5.4: {} diff --git a/public/fonts/ClarityCity-Bold.woff2 b/public/fonts/ClarityCity-Bold.woff2 new file mode 100644 index 0000000..da8c2fa Binary files /dev/null and b/public/fonts/ClarityCity-Bold.woff2 differ diff --git a/public/fonts/ClarityCity-Regular.woff2 b/public/fonts/ClarityCity-Regular.woff2 new file mode 100644 index 0000000..feedf7f Binary files /dev/null and b/public/fonts/ClarityCity-Regular.woff2 differ diff --git a/public/fonts/ClarityCity-SemiBold.woff2 b/public/fonts/ClarityCity-SemiBold.woff2 new file mode 100644 index 0000000..a7dbc8c Binary files /dev/null and b/public/fonts/ClarityCity-SemiBold.woff2 differ diff --git a/public/fonts/bricolage-grotesque-latin-300-normal.woff b/public/fonts/bricolage-grotesque-latin-300-normal.woff new file mode 100644 index 0000000..35200bf Binary files /dev/null and b/public/fonts/bricolage-grotesque-latin-300-normal.woff differ diff --git a/public/fonts/bricolage-grotesque-latin-600-normal.woff b/public/fonts/bricolage-grotesque-latin-600-normal.woff new file mode 100644 index 0000000..a0b1b60 Binary files /dev/null and b/public/fonts/bricolage-grotesque-latin-600-normal.woff differ diff --git a/public/fonts/bricolage-grotesque-latin-800-normal.woff b/public/fonts/bricolage-grotesque-latin-800-normal.woff new file mode 100644 index 0000000..5ffe9d7 Binary files /dev/null and b/public/fonts/bricolage-grotesque-latin-800-normal.woff differ diff --git a/public/fonts/bricolage-grotesque-latin-ext-300-normal.woff b/public/fonts/bricolage-grotesque-latin-ext-300-normal.woff new file mode 100644 index 0000000..aa4be3a Binary files /dev/null and b/public/fonts/bricolage-grotesque-latin-ext-300-normal.woff differ diff --git a/public/fonts/bricolage-grotesque-latin-ext-600-normal.woff b/public/fonts/bricolage-grotesque-latin-ext-600-normal.woff new file mode 100644 index 0000000..84334f4 Binary files /dev/null and b/public/fonts/bricolage-grotesque-latin-ext-600-normal.woff differ diff --git a/public/fonts/bricolage-grotesque-latin-ext-800-normal.woff b/public/fonts/bricolage-grotesque-latin-ext-800-normal.woff new file mode 100644 index 0000000..988e558 Binary files /dev/null and b/public/fonts/bricolage-grotesque-latin-ext-800-normal.woff differ diff --git a/public/fonts/cal-sans-latin-400-normal.woff b/public/fonts/cal-sans-latin-400-normal.woff new file mode 100644 index 0000000..687442d Binary files /dev/null and b/public/fonts/cal-sans-latin-400-normal.woff differ diff --git a/public/fonts/cal-sans-latin-ext-400-normal.woff b/public/fonts/cal-sans-latin-ext-400-normal.woff new file mode 100644 index 0000000..b3ca3b1 Binary files /dev/null and b/public/fonts/cal-sans-latin-ext-400-normal.woff differ diff --git a/public/gren/g1.png b/public/gren/g1.png new file mode 100644 index 0000000..d5138bf Binary files /dev/null and b/public/gren/g1.png differ diff --git a/public/gren/g2.png b/public/gren/g2.png new file mode 100644 index 0000000..0fe7e09 Binary files /dev/null and b/public/gren/g2.png differ diff --git a/public/gren/g3.png b/public/gren/g3.png new file mode 100644 index 0000000..d2f13ec Binary files /dev/null and b/public/gren/g3.png differ diff --git a/public/gren/g4.png b/public/gren/g4.png new file mode 100644 index 0000000..eeb5663 Binary files /dev/null and b/public/gren/g4.png differ diff --git a/public/gren/g5.png b/public/gren/g5.png new file mode 100644 index 0000000..04bcac7 Binary files /dev/null and b/public/gren/g5.png differ diff --git a/public/gren/g6.png b/public/gren/g6.png new file mode 100644 index 0000000..0086aa5 Binary files /dev/null and b/public/gren/g6.png differ diff --git a/public/kolay-tercih-logo.svg b/public/kolay-tercih-logo.svg new file mode 100644 index 0000000..d4444e2 --- /dev/null +++ b/public/kolay-tercih-logo.svg @@ -0,0 +1,14 @@ + + KolayTercih + + + + + + + + + + + + diff --git a/public/ses/efekt.wav b/public/ses/efekt.wav new file mode 100644 index 0000000..3235468 Binary files /dev/null and b/public/ses/efekt.wav differ diff --git a/public/ses/muzik.wav b/public/ses/muzik.wav new file mode 100644 index 0000000..256a983 Binary files /dev/null and b/public/ses/muzik.wav differ diff --git a/remotion.config.ts b/remotion.config.ts new file mode 100644 index 0000000..4d7270f --- /dev/null +++ b/remotion.config.ts @@ -0,0 +1,9 @@ +import { Config } from "@remotion/cli/config"; + +Config.setVideoImageFormat("jpeg"); +Config.setJpegQuality(95); +Config.setCodec("h264"); +Config.setCrf(21); +Config.setX264Preset("slow"); +Config.setDelayRenderTimeoutInMilliseconds(180000); +Config.setConcurrency(4); diff --git a/scripts/kareler.mjs b/scripts/kareler.mjs new file mode 100644 index 0000000..c1379a8 --- /dev/null +++ b/scripts/kareler.mjs @@ -0,0 +1,13 @@ +// Kullanım: node scripts/kareler.mjs <ölçek> kare1 kare2 ... +import { bundle } from "@remotion/bundler"; +import { renderStill, selectComposition } from "@remotion/renderer"; +import path from "node:path"; + +const [bicim, olcek, ...kareler] = process.argv.slice(2); +const serveUrl = await bundle({ entryPoint: path.resolve("src/index.ts") }); +const composition = await selectComposition({ serveUrl, id: `Lansman-${bicim}` }); +for (const f of kareler) { + await renderStill({ composition, serveUrl, frame: Number(f), output: `out/kare/${bicim[0].toLowerCase()}-${f}.png`, scale: Number(olcek) }); + process.stdout.write(`${f} `); +} +console.log("tamam"); diff --git a/ses/uret.py b/ses/uret.py new file mode 100644 index 0000000..21226de --- /dev/null +++ b/ses/uret.py @@ -0,0 +1,678 @@ +""" +KolayTercih lansman videosunun müziği ve ses efektleri — tamamen sentez +(numpy), dışarıdan örnek/sample yok, lisans derdi yok. + +Zaman çizelgesi src/zaman.json'dan okunur; video ile aynı kaynak. +120 BPM (1 vuruş = 0.5 sn = 15 kare), Do majör. Video döngüye girer: son kare ilk kareyle aynıdır, ses de öyle — SON'dan +taşan kuyruk dosyanın başına eklenir. Çıktılar: + public/ses/muzik.wav — müzik (stereo, 44.1 kHz, 16 bit) + public/ses/efekt.wav — ses efektleri +Çalıştırma: python3 ses/uret.py +""" + +import json +import wave +from pathlib import Path + +import numpy as np + +KOK = Path(__file__).resolve().parent.parent +SR = 44100 +Z = json.loads((KOK / "src/zaman.json").read_text()) +FPS = Z["fps"] +S = {s["id"]: s["bas"] / FPS for s in Z["sahneler"]} +SON = Z["son"] / FPS +# Döngü: SON'dan sonra taşan kuyruk (reverb, son akor) başa sarılır +KUYRUK = 3.5 +N = int((SON + KUYRUK) * SR) +rng = np.random.default_rng(11) +B = 0.5 # vuruş (sn) +ZAMAN_TIK = Z["tiklar"] + + +def lf(sahne, kare): + """Sahne içi kare → mutlak saniye.""" + return S[sahne] + kare / FPS + + +# ------------------------------------------------------------------ # +# Temel araçlar # +# ------------------------------------------------------------------ # + + +def zaman(d): + return np.arange(int(d * SR)) / SR + + +def mf(m): + return 440.0 * 2 ** ((m - 69) / 12) + + +def fftconv(x, h): + n = len(x) + len(h) - 1 + boy = 1 << (n - 1).bit_length() + y = np.fft.irfft(np.fft.rfft(x, boy) * np.fft.rfft(h, boy), boy)[:n] + return y + + +def sinc_lp(fc, taps=255): + n = np.arange(taps) - (taps - 1) / 2 + h = np.sinc(2 * fc / SR * n) * np.blackman(taps) + return h / h.sum() + + +def lp(x, fc, taps=255): + return fftconv(x, sinc_lp(fc, taps))[(taps - 1) // 2 : (taps - 1) // 2 + len(x)] + + +def hp(x, fc, taps=255): + return x - lp(x, fc, taps) + + +def bp(x, f1, f2, taps=255): + return lp(hp(x, f1, taps), f2, taps) + + +def gurultu(d): + return rng.standard_normal(int(d * SR)) + + +def zarf(d, a=0.005, r=0.05): + n = int(d * SR) + e = np.ones(n) + na = max(1, min(n, int(a * SR))) + e[:na] = np.linspace(0, 1, na) + nr = max(1, min(n, int(r * SR))) + e[n - nr :] *= np.linspace(1, 0, nr) + return e + + +def bos(): + return np.zeros((N, 2)) + + +def ekle(tampon, sinyal, t0, kazanc=1.0, pan=0.0): + i = int(round(t0 * SR)) + if i >= N or i + len(sinyal) <= 0: + return + if sinyal.ndim == 1: + a = (pan + 1) * np.pi / 4 + sinyal = np.stack([sinyal * np.cos(a), sinyal * np.sin(a)], 1) * np.sqrt(2) + if i < 0: + sinyal = sinyal[-i:] + i = 0 + n = min(len(sinyal), N - i) + tampon[i : i + n] += sinyal[:n] * kazanc + + +def reverb(tampon, sure=2.4, tau=0.55, karisim=1.0): + t = zaman(sure) + cikis = np.zeros_like(tampon) + for k in range(2): + ir = rng.standard_normal(len(t)) * np.exp(-t / tau) + ir[: int(0.012 * SR)] = 0 + ir = lp(ir, 5500) + ir /= np.sqrt((ir**2).sum()) + cikis[:, k] = fftconv(tampon[:, k], ir)[: len(tampon)] + return cikis * karisim + + +# ------------------------------------------------------------------ # +# Enstrümanlar # +# ------------------------------------------------------------------ # + + +def kick(guc=1.0): + t = zaman(0.55) + f = 44 + 120 * np.exp(-t / 0.032) + faz = 2 * np.pi * np.cumsum(f) / SR + govde = np.sin(faz) * np.exp(-t / 0.26) + tik = hp(gurultu(0.55), 2500) * np.exp(-t / 0.003) * 0.35 + return np.tanh((govde + tik) * 1.6 * guc) * 0.9 + + +def clap(): + d = 0.35 + t = zaman(d) + g = bp(gurultu(d), 900, 6000) + e = np.zeros_like(t) + for o in (0.0, 0.011, 0.022): + e += np.where(t >= o, np.exp(-(t - o) / (0.006 if o < 0.02 else 0.13)), 0) + return g * e * 0.55 + + +def hat(acik=False): + d = 0.35 if acik else 0.08 + t = zaman(d) + return hp(gurultu(d), 7500) * np.exp(-t / (0.11 if acik else 0.022)) * 0.28 + + +def bas(m, d): + t = zaman(d) + f = mf(m) + s = sum(np.sin(2 * np.pi * f * k * t + k) / k ** 1.4 for k in range(1, 7)) + return s * zarf(d, 0.004, 0.06) * np.exp(-t / 0.9) * 0.55 + + +def ped(notalar, d, parlak=1.0): + t = zaman(d) + sol = np.zeros(len(t)) + sag = np.zeros(len(t)) + for m in notalar: + for cent, pan in ((-8, -0.7), (0, 0.0), (8, 0.7)): + f = mf(m) * 2 ** (cent / 1200) + v = np.zeros(len(t)) + for k in range(1, 11): + if f * k > 9000: + break + v += np.sin(2 * np.pi * f * k * t + rng.uniform(0, 6.28)) / k ** (1.9 - 0.4 * parlak) + a = (pan + 1) * np.pi / 4 + sol += v * np.cos(a) + sag += v * np.sin(a) + e = zarf(d, 0.5, 0.9) + # yavaş nefes: pedin içinde hafif hareket + e *= 0.85 + 0.15 * np.sin(2 * np.pi * 0.25 * t) + return np.stack([sol * e, sag * e], 1) * (0.06 / max(1, len(notalar) / 3)) + + +def pluck(m, d=0.5, parlak=1.0): + t = zaman(d) + f = mf(m) + s = ( + np.sin(2 * np.pi * f * t) * np.exp(-t / 0.32) + + 0.45 * parlak * np.sin(2 * np.pi * 2 * f * t) * np.exp(-t / 0.14) + + 0.18 * parlak * np.sin(2 * np.pi * 3 * f * t) * np.exp(-t / 0.06) + ) + return s * zarf(d, 0.002, 0.03) * 0.3 + + +def piyano(m, d=1.8): + t = zaman(d) + f = mf(m) + s = ( + np.sin(2 * np.pi * f * t) * np.exp(-t / 0.9) + + 0.5 * np.sin(2 * np.pi * 2.001 * f * t) * np.exp(-t / 0.5) + + 0.22 * np.sin(2 * np.pi * 3.004 * f * t) * np.exp(-t / 0.25) + + 0.1 * np.sin(2 * np.pi * 4.01 * f * t) * np.exp(-t / 0.12) + ) + return s * zarf(d, 0.003, 0.2) * 0.32 + + +def zil(m, d=1.4): + t = zaman(d) + f = mf(m) + s = np.sin(2 * np.pi * f * t) * np.exp(-t / 0.5) + 0.35 * np.sin(2 * np.pi * 2.76 * f * t) * np.exp(-t / 0.25) + return s * zarf(d, 0.002, 0.2) * 0.25 + + +def yukselen(d, f0=180, f1=1500): + t = zaman(d) + p = t / d + f = f0 * (f1 / f0) ** p + faz = 2 * np.pi * np.cumsum(f) / SR + ton = (np.sin(faz) + 0.5 * np.sin(faz * 1.005 + 1)) * 0.25 + g = gurultu(d) + parlak = hp(g, 3000) + koyu = lp(g, 900) + gur = koyu * (1 - p) + parlak * p + return (ton + gur * 0.35) * p ** 2.2 * 0.6 + + +def darbe(): + """Logo / final vuruşu: alt bas düşüşü + gövde + parlak patlama.""" + d = 2.6 + t = zaman(d) + f = 30 + 45 * np.exp(-t / 0.25) + faz = 2 * np.pi * np.cumsum(f) / SR + alt = np.sin(faz) * np.exp(-t / 1.1) + govde = lp(gurultu(d), 1400) * np.exp(-t / 0.18) * 0.8 + parlak = hp(gurultu(d), 4000) * np.exp(-t / 0.35) * 0.25 + return np.tanh((alt * 1.2 + govde + parlak) * 1.4) * 0.95 + + +def ters_sisme(notalar, d=1.2): + """Ters çevrilmiş ped + reverb: vuruştan önce çekilen nefes.""" + p = ped(notalar, d, parlak=1.4) + p = p + reverb(p, 1.2, 0.35, 0.8) + t = zaman(d)[:, None] + return (p * (t / d) ** 2)[::-1] * 3.2 + + +# ------------------------------------------------------------------ # +# Efektler # +# ------------------------------------------------------------------ # + + +def whoosh(d=0.7, tur="zoom"): + t = zaman(d) + g = gurultu(d) + koyu = lp(g, 500 if tur == "zoom" else 900) + acik = lp(g, 7000) + p = t / d + tepe = 0.62 if tur != "yuksel" else 0.5 + iz = np.exp(-((p - tepe) / 0.23) ** 2) + karisim = koyu * (1 - iz) + acik * iz + e = np.where(p < tepe, (p / tepe) ** 2.2, np.exp(-(p - tepe) / 0.12)) + s = karisim * e * 0.4 + if tur == "kaydir": + pan = np.linspace(-0.8, 0.8, len(t)) + else: + pan = np.zeros(len(t)) + a = (pan + 1) * np.pi / 4 + return np.stack([s * np.cos(a), s * np.sin(a)], 1) * np.sqrt(2) + + +def pop(f=700, guc=1.0): + t = zaman(0.12) + fr = f * (1 + 0.9 * np.exp(-t / 0.012)) + faz = 2 * np.pi * np.cumsum(fr) / SR + return np.sin(faz) * np.exp(-t / 0.045) * zarf(0.12, 0.001, 0.02) * 0.4 * guc + + +def tik(f=2600, guc=1.0): + t = zaman(0.03) + s = hp(gurultu(0.03), 3000) * np.exp(-t / 0.0025) * 0.5 + np.sin(2 * np.pi * f * t) * np.exp(-t / 0.008) * 0.3 + return s * guc + + +def tus(): + """Klavye tuşu: kısa, gövdeli tık.""" + t = zaman(0.05) + g = bp(gurultu(0.05), 1200, 6000) * np.exp(-t / 0.006) + return (g * 0.5 + np.sin(2 * np.pi * rng.uniform(180, 260) * t) * np.exp(-t / 0.01) * 0.3) * rng.uniform(0.6, 1.0) + + +def tiklama(): + """Buton basışı.""" + t = zaman(0.08) + return (bp(gurultu(0.08), 800, 5000) * np.exp(-t / 0.008) * 0.6 + np.sin(2 * np.pi * 160 * t) * np.exp(-t / 0.02) * 0.5) + + +def pirilti(sure, yogun=40, f0=2200, f1=5200, guc=0.12): + """Pikseller dökülürken minik ışıltılar.""" + s = np.zeros((int((sure + 0.2) * SR), 2)) + for _ in range(int(yogun * sure)): + t0 = rng.uniform(0, sure) + b = pop(rng.uniform(f0, f1), guc=rng.uniform(0.3, 1.0)) + i = int(t0 * SR) + a = (rng.uniform(-0.9, 0.9) + 1) * np.pi / 4 + s[i : i + len(b), 0] += b * np.cos(a) * guc * 1.4 + s[i : i + len(b), 1] += b * np.sin(a) * guc * 1.4 + return s + + +def gumbur(d=1.0): + t = zaman(d) + return lp(gurultu(d), 180) * np.sin(np.pi * t / d) ** 2 * 1.2 + + + +def tok_whoosh(d=0.9, tur="zoom"): + """Geçiş sesi: tok, gövdeli. Alçak "vuuş" + varışta alt bas darbesi.""" + t = zaman(d) + p = t / d + g = gurultu(d) + koyu = lp(g, 260) + orta = lp(g, 1300) + tepe = 0.68 + iz = np.clip((p / tepe), 0, 1) ** 1.5 + karisim = koyu * (1 - iz) + orta * iz + e = np.where(p < tepe, (p / tepe) ** 2.4, np.exp(-(p - tepe) / 0.07)) + vuus = karisim * e * 0.9 + # Varış darbesi: 95 → 42 Hz inen sinüs + d0 = int(tepe * d * SR) + tt = zaman(d)[: len(t) - d0] + f = 42 + 55 * np.exp(-tt / 0.06) + faz = 2 * np.pi * np.cumsum(f) / SR + darbe_ = np.zeros(len(t)) + darbe_[d0:] = np.sin(faz) * np.exp(-tt / 0.22) * 0.9 + govde = np.zeros(len(t)) + govde[d0:] = lp(gurultu(len(tt) / SR), 400)[: len(tt)] * np.exp(-tt / 0.05) * 0.5 + s = np.tanh((vuus + darbe_ + govde) * 1.3) + pan = np.linspace(-0.6, 0.6, len(t)) if tur == "kaydir" else np.zeros(len(t)) + a = (pan + 1) * np.pi / 4 + return np.stack([s * np.cos(a), s * np.sin(a)], 1) * np.sqrt(2) + + +def tok_vurus(): + """Sert kesmelerin (bölüm kartları) vuruşu: tok davul + alt bas.""" + d = 0.9 + t = zaman(d) + f = 40 + 90 * np.exp(-t / 0.045) + faz = 2 * np.pi * np.cumsum(f) / SR + alt = np.sin(faz) * np.exp(-t / 0.35) + tom = np.sin(2 * np.pi * 92 * t) * np.exp(-t / 0.16) * 0.5 + tok = lp(gurultu(d), 700) * np.exp(-t / 0.035) * 0.8 + return np.tanh((alt + tom + tok) * 1.5) * 0.95 + + +# ------------------------------------------------------------------ # +# Müzik # +# ------------------------------------------------------------------ # + +C9 = ([60, 67, 74, 76], 36) +G6 = ([59, 67, 71, 74], 43) +AM7 = ([57, 64, 67, 72], 45) +FM9 = ([53, 60, 64, 67, 69], 41) +ILERLEYIS = [C9, G6, AM7, FM9] +ILERLEYIS_YZ = [AM7, FM9, C9, G6] + + +def bam_ani(sahne): + """lensZoom geçişi 30 kare; kesme yarısında (15. kare) olur: BAM anı.""" + return S[sahne] + 15 / FPS + + +def bwomp(): + """'Off.' için esprili inen iki nota (tuba gibi yumuşak).""" + d = 0.7 + t = zaman(d) + f = np.where(t < 0.28, mf(52), mf(47)) * (1 - 0.04 * t) + faz = 2 * np.pi * np.cumsum(f) / SR + s = (np.sin(faz) + 0.3 * np.sin(2 * faz) + 0.1 * np.sin(3 * faz)) * zarf(d, 0.02, 0.2) + s *= np.where(t < 0.28, 1, 0.9) * np.exp(-np.maximum(0, t - 0.28) / 0.35) + return lp(s, 1800) * 0.5 + + +def soru_plink(): + """'Kafan mı karıştı?' için yükselen soru tınısı.""" + a = zil(79, 0.5) * 0.8 + b = zil(84, 0.9) + out = np.zeros(len(a) + int(0.16 * SR) + len(b)) + out[: len(a)] += a + out[int(0.16 * SR) : int(0.16 * SR) + len(b)] += b + return out + + +def muzik(): + davul = bos() + ahenk = bos() + parlak = bos() + kickler = [] + + def groove(bas_t, bitis_t, ilerleyis, arp=True, arp_parlak=1.0, davul_var=True, hafif=False): + bar = 0 + t = bas_t + while t < bitis_t - 0.01: + notalar, kok = ilerleyis[bar % 4] + d = min(2.0, bitis_t - t) + ekle(ahenk, ped(notalar, d + 0.05) * zarf(d + 0.05, 0.4, 0.05)[:, None], t, 1.0) + for k in range(8): + tt = t + k * B / 2 + if tt >= bitis_t - 0.02: + break + ekle(ahenk, bas(kok + (12 if k % 4 == 3 else 0), B / 2 * 0.9), tt, 0.85) + if arp: + ton = sorted(set(notalar + [n + 12 for n in notalar])) + desen = [0, 2, 1, 3, 2, 4, 3, 5, 4, 3, 2, 4, 1, 3, 2, 5] + for k in range(16): + tt = t + k * B / 4 + if tt >= bitis_t - 0.02: + break + ekle(ahenk, pluck(ton[desen[k] % len(ton)] + 12, 0.35, arp_parlak), tt, 0.5 if k % 4 else 0.7, pan=np.sin(k * 0.8) * 0.5) + if davul_var: + for k in range(4): + tt = t + k * B + if tt >= bitis_t - 0.02: + break + ekle(davul, kick(), tt, 0.8 if hafif else 0.95) + kickler.append(tt) + if k % 2 == 1 and not hafif: + ekle(davul, clap(), tt, 0.6) + ekle(davul, hat(), tt + B / 2, 0.5, pan=0.25) + ekle(davul, hat(), tt + B / 4, 0.18, pan=-0.2) + ekle(davul, hat(), tt + 3 * B / 4, 0.18, pan=-0.2) + t += 2.0 + bar += 1 + + def saat(bas_t, bitis_t, guc=0.6): + t = bas_t + while t < bitis_t - 0.01: + ekle(davul, tik(3400, 0.25), t, guc, pan=0.4) + t += B / 2 + + def bam(t0, akor=C9): + ekle(davul, darbe(), t0, 1.0) + ekle(parlak, zil(84, 2.2), t0, 0.6) + ekle(parlak, zil(79, 2.2), t0, 0.5) + for j, m in enumerate(akor[0]): + ekle(parlak, piyano(m + 12, 1.8), t0 + j * 0.02, 0.4) + + def sessizlik_sonu(t_bam): + """Sessizliğin sonunda BAM'e çekilen ters nefes.""" + ekle(parlak, ters_sisme(C9[0], 0.7), t_bam - 0.7, 0.9) + + b_logo, b_yz, b_final = bam_ani("logo"), bam_ani("danisman"), bam_ani("final") + tik_final = S["final"] + ZAMAN_TIK["final"][0] / FPS + + # --- Giriş: saat + ped + piyano; seçeneklerde kalp atışı + saat(0, S["kaos"]) + ekle(ahenk, ped(AM7[0], S["secenek"] + 0.6), 0, 0.8) + ekle(ahenk, ped(FM9[0], S["kaos"] - S["secenek"] + 0.3), S["secenek"], 0.9) + for i, m in enumerate([76, 72, 69, 72, 76, 79, 76, 72, 69]): + ekle(parlak, piyano(m), i * 0.75, 0.5, pan=0.3 if i % 2 else -0.3) + t = S["secenek"] + while t < S["kaos"]: + ekle(davul, kick(0.5), t, 0.45) + ekle(davul, kick(0.35), t + 0.18, 0.25) + t += 1.0 + + # --- Kaos: gerilim → SERT DURUŞ (nefes sahnesi sessiz) + dur1 = S["nefes"] + ekle(ahenk, ped(FM9[0], 2.0) , S["kaos"], 1.0) + ekle(ahenk, ped([55, 62, 67, 71, 74], dur1 - S["kaos"] - 2.0), S["kaos"] + 2.0, 1.1) + ekle(parlak, yukselen(dur1 - S["kaos"] - 0.4), S["kaos"] + 0.4, 0.9) + t = S["kaos"] + 1.0 + while t < dur1 - 1.0: + ekle(davul, kick(0.8), t, 0.7) + kickler.append(t) + t += B + adim = B / 2 + t = dur1 - 1.0 + while t < dur1 - 0.05: + ekle(davul, clap(), t, 0.35 + 0.45 * (t - (dur1 - 1.0))) + t += adim + adim = max(B / 8, adim * 0.8) + # Sessizlik: tek piyano notası, sonra ters nefes → BAM + ekle(parlak, piyano(76, 2.4), dur1 + 0.45, 0.35) + sessizlik_sonu(b_logo) + bam(b_logo) + + # --- Groove: logo (arp'sız) → üç adım (arp'lı), Off'ta SERT DURUŞ + dur2 = S["off"] + groove(b_logo, S["adim1"], ILERLEYIS, arp=False) + groove(S["adim1"], dur2, ILERLEYIS, arp_parlak=1.2) + # Off: esprili bwomp + soru tınısı; ters nefes → BAM (DanışmanlaKonuş) + sessizlik_sonu(b_yz) + bam(b_yz, AM7) + + # --- Yapay zekâ: parlak groove; paket kartlarında vurgular + groove(b_yz, S["paket"], ILERLEYIS_YZ, arp_parlak=1.6, hafif=True) + groove(S["paket"], S["durust"], ILERLEYIS, arp_parlak=1.4) + for k in range(3): + ekle(davul, tok_vurus() * 0.6, S["paket"] + k * 1.5, 0.8) + + # --- Dürüstlük: kırılma (piyano + ped) → Hazır'da SERT DURUŞ + dur3 = S["hazir"] + for i, (notalar, kok) in enumerate([AM7, FM9]): + tt = S["durust"] + i * 2.0 + ekle(ahenk, ped(notalar, 2.0) * zarf(2.0, 0.3, 0.1)[:, None], tt, 1.1) + for j, m in enumerate(notalar[1:]): + ekle(parlak, piyano(m + 12, 1.8), tt + j * 0.18, 0.5) + ekle(ahenk, bas(kok, 1.8), tt, 0.6) + # Hazır: iki yumuşak tık, ters nefes → BAM (final) + ekle(davul, tik(2400, 0.5), dur3 + 0.5, 0.6) + ekle(davul, tik(2600, 0.5), dur3 + 1.0, 0.6) + sessizlik_sonu(b_final) + bam(b_final) + + # --- Final: groove → butona tık = son akor → saat tıkırtısı döngüye + groove(b_final, tik_final, ILERLEYIS, arp_parlak=1.3) + ekle(davul, darbe(), tik_final, 0.7) + ekle(ahenk, ped(C9[0] + [48], 2.8), tik_final, 1.2) + for j, m in enumerate([72, 76, 79, 84, 88]): + ekle(parlak, piyano(m, 2.6), tik_final + j * 0.06, 0.45) + ekle(parlak, zil(96, 2.5), tik_final, 0.3) + saat(SON - 1.0, SON + 0.01, 0.45) + + zarf_sc = np.ones(N) + tt = np.arange(N) / SR + for k in kickler: + i = int(k * SR) + n = min(int(0.35 * SR), N - i) + if n > 0: + zarf_sc[i : i + n] = np.minimum(zarf_sc[i : i + n], 1 - 0.45 * np.exp(-tt[:n] / 0.11)) + ahenk *= zarf_sc[:, None] + + karisim = davul * 0.9 + ahenk + parlak + yanki = reverb(ahenk * 0.35 + parlak * 0.6 + davul * 0.08, 2.6, 0.6) + # Sert duruşlar gerçekten sessiz olsun: yankı kuyruğu sessizlik başında hızla kısılır + for d in (S["nefes"], S["off"], S["hazir"]): + i0 = int(d * SR) + n = int(0.35 * SR) + z = np.ones(N) + z[i0 : i0 + n] = np.linspace(1, 0.08, n) + z[i0 + n : int((d + 1.7) * SR)] = 0.08 + yanki *= z[:, None] + return karisim + yanki + + +# ------------------------------------------------------------------ # +# Efekt izi # +# ------------------------------------------------------------------ # + +ZAMAN_TIK = Z["tiklar"] +ZAMAN_YAZ = Z["yazma"] + + +def efektler(): + e = bos() + + # Geçişler: zoomBlur → tok vuuş; lensZoom → emiş + alt bas düşüşü (BAM müzikte) + for s in Z["sahneler"][1:]: + g = s.get("gecis", {}) + tur = g.get("tur") + if tur == "zoomBlur": + ekle(e, tok_whoosh(0.9), S[s["id"]] + 0.3 - 0.9 * 0.68, 0.9) + elif tur == "lensZoom": + t_bam = bam_ani(s["id"]) + ekle(e, whoosh(0.5, "zoom")[::-1] * 0.8, t_bam - 0.5, 0.9) + ekle(e, tok_vurus(), t_bam, 0.9) + + # İmleç tıkları ve yazma (tek kaynak: zaman.json) + for sahne, kareler in ZAMAN_TIK.items(): + for f in kareler: + ekle(e, tiklama(), lf(sahne, f), 0.75) + ekle(e, pop(1300, 0.35), lf(sahne, f) + 0.01) + for sahne, araliklar in ZAMAN_YAZ.items(): + for a, b in araliklar: + f = a + while f < b: + ekle(e, tus(), lf(sahne, f), 0.7) + f += 4 if sahne == "adim1" else rng.choice([2, 3]) + + # Açılış: rozet, iz açılışı + ekle(e, pop(900, 0.5), lf("acilis", 8)) + ekle(e, whoosh(0.6, "yuksel") * 0.4, lf("acilis", 14), 0.6) + # Seçenekler: sayaç, sheen + for k in range(16): + ekle(e, tik(2000 + k * 70, 0.4), lf("secenek", 4 + 36 * (k / 15) ** 1.9)) + ekle(e, pop(1100, 0.8), lf("secenek", 38)) + ekle(e, pirilti(0.5, 60, 3000, 6000), lf("secenek", 50), 0.7) + # Kaos: kartlar + for i in range(8): + ekle(e, pop(rng.uniform(420, 880), 0.7), lf("kaos", 6 + i * 5), pan=rng.uniform(-0.7, 0.7)) + # Off: esprili ses + ekle(e, bwomp(), lf("off", 3), 0.9) + ekle(e, soru_plink() * 0.8, lf("off", 20), 0.8) + # Logo: dev kelime → kesme, rota, sheen + ekle(e, whoosh(0.5, "kaydir") * 0.6, lf("logo", 18), 0.8) + ekle(e, pop(700, 0.9), lf("logo", 26)) + ekle(e, pirilti(0.5, 60, 3000, 6000), lf("logo", 46), 0.7) + # Adımlar: başlık sheen + for a in ("adim1", "adim2", "adim3"): + ekle(e, pop(620, 0.8), lf(a, 2)) + ekle(e, pirilti(0.45, 50, 3000, 6000), lf(a, 4), 0.6) + ekle(e, pop(600, 0.8), lf("adim1", 104)) + for i in range(24): + ekle(e, pop(1800 + i * 40, 0.25), lf("adim1", ZAMAN_TIK["adim1"][-1] + 10 + i * 1.1)) + # Adım 2: harita yağmuru, ısı fırçası, kontur dolgu + ekle(e, pirilti(1.4, 90), lf("adim2", 4), 0.9) + ekle(e, pirilti(2.3, 32, 1500, 3000, 0.08), lf("adim2", 50), 0.7) + ekle(e, whoosh(1.2, "yuksel") * 0.35, lf("adim2", 146), 0.7) + # Adım 3: yol çizimi, 24 kare pentatonik + ekle(e, pirilti(1.3, 40, 2400, 4800, 0.1), lf("adim3", 6), 0.7) + penta = [60, 62, 64, 67, 69] + for i in range(24): + m = penta[i % 5] + 12 * (i // 5) + ekle(e, pop(min(mf(m + 12), 5000), 0.55), lf("adim3", 62 + i * 2), pan=((i % 8) / 7 - 0.5) * 0.8) + for f in (112, 117, 122): + ekle(e, pop(800, 0.5), lf("adim3", f)) + # Danışman: piksel patlaması, sheen + ekle(e, pirilti(0.8, 90, 1800, 5200, 0.14), lf("danisman", 15), 0.9) + ekle(e, pirilti(0.5, 60, 3000, 6000), lf("danisman", 28), 0.7) + # Sohbet: araç, cevaplar + ts = ZAMAN_TIK["sohbet"] + for bas_f, bit_f, cev_f in [(ts[1] + 6, ts[1] + 32, 90), (ts[3] + 6, ts[3] + 22, 224)]: + ekle(e, pop(1200, 0.4), lf("sohbet", bas_f)) + f = bas_f + 6 + while f < bit_f: + ekle(e, tik(4200, 0.15), lf("sohbet", f), 0.6) + f += 6 + ekle(e, zil(88, 0.9), lf("sohbet", cev_f), 0.3) + ekle(e, zil(95, 0.9), lf("sohbet", cev_f + 3), 0.22) + for f in (100, 107, 114): + ekle(e, pop(900, 0.5), lf("sohbet", f)) + ekle(e, pop(1000, 0.8), lf("sohbet", 242)) + # Paket: kart sheen'leri, sürükle-bırak + for f in (2, 47, 92): + ekle(e, pirilti(0.45, 50, 3000, 6000), lf("paket", f), 0.6) + tp = ZAMAN_TIK["paket"] + ekle(e, whoosh(0.8, "kaydir") * 0.4, lf("paket", tp[0]), 0.6) + ekle(e, tok_vurus() * 0.3, lf("paket", tp[1]), 0.8) + # Dürüstlük + ekle(e, pop(330, 1.0), lf("durust", 0)) + ekle(e, pirilti(1.2, 36, 1600, 3200, 0.09), lf("durust", 56), 0.7) + for k, f in enumerate([94, 99, 104]): + ekle(e, pop([784, 659, 523][k], 0.7), lf("durust", f)) + # Hazır: üç kare + for i in range(3): + ekle(e, pop([660, 880, 990][i], 0.6), lf("hazir", 4 + i * 6)) + # Final: buton, çöküş + ekle(e, pop(700, 0.9), lf("final", 38)) + tf = ZAMAN_TIK["final"][0] + ekle(e, tok_whoosh(0.9)[::-1] * 0.8, lf("final", tf + 30) - 0.9, 0.8) + + e += reverb(e, 1.4, 0.3, 0.25) + return e + + +# ------------------------------------------------------------------ # +# Yazım # +# ------------------------------------------------------------------ # + + +def yaz(yol, x, hedef_tepe=0.89, hedef_rms=None): + n = int(SON * SR) + # Döngü: SON'dan taşan kuyruğu başa ekle ki son → ilk kare geçişi kesintisiz olsun + kuyruk = x[n:] + x = x[:n].copy() + x[: len(kuyruk)] += kuyruk + if hedef_rms: + rms = np.sqrt((x**2).mean()) + x = x * (hedef_rms / rms) + x = np.tanh(x * 1.1) / np.tanh(1.1) # yumuşak limitleyici + x = x / np.abs(x).max() * hedef_tepe + veri = (x * 32767).astype(" = { + acilis: Acilis, + secenek: Secenek, + kaos: Kaos, + nefes: Nefes, + logo: LogoSahnesi, + adim1: Adim1, + adim2: Adim2, + adim3: Adim3, + off: Off, + danisman: Danisman, + sohbet: Sohbet, + paket: Paket, + durust: Durust, + hazir: Hazir, + final: Final, +}; + +export const SURE = ZAMAN.son; + +/** Punch zoomlarda ve hızlı kamera hareketlerinde alt-kare hareket bulanıklığı. */ +function Bulaniklik({ children, pencereler }: { children: ReactNode; pencereler: [number, number][] }) { + const frame = useCurrentFrame(); + if (!pencereler.some(([a, b]) => frame >= a && frame <= b)) return <>{children}; + return ( + + {children} + + ); +} + +const gecisSuresi = (g?: Gecis) => (g && g.tur !== "kesme" ? (g.sure ?? 18) : 0); + +export function Lansman() { + const sahneler = ZAMAN.sahneler as SahneTanim[]; + return ( + + + {sahneler.map((s, i) => { + const sonraki = sahneler[i + 1]; + const uzunluk = (sonraki ? sonraki.bas : ZAMAN.son) - s.bas + gecisSuresi(sonraki?.gecis); + const Bilesen = BILESENLER[s.id]; + // Lens geçişi zaten kendi zoom blur'unu taşır ve sahneyi çok kez çizer; + // üstüne alt-kare bulanıklığı binerse kare başına maliyet katlanır. + const lensGiris = s.gecis?.tur === "lensZoom" ? gecisSuresi(s.gecis) + 4 : -1; + const lensCikis = sonraki?.gecis?.tur === "lensZoom" ? uzunluk - gecisSuresi(sonraki.gecis) - 4 : Infinity; + const pencereler: [number, number][] = [ + ...(VURUSLAR[s.id] ?? []).map((v): [number, number] => [v.f - 1, v.f + 7]), + ...(BULANIK[s.id] ?? []), + ].filter(([a, b]) => a > lensGiris && b < lensCikis); + const g = i > 0 ? s.gecis : undefined; + return ( + + {g && g.tur === "lensZoom" ? ( + + ) : null} + {g && g.tur === "zoomBlur" ? ( + + ) : null} + + + + + + + ); + })} + + + + + ); +} diff --git a/src/Root.tsx b/src/Root.tsx new file mode 100644 index 0000000..689f9f1 --- /dev/null +++ b/src/Root.tsx @@ -0,0 +1,15 @@ +import { Composition } from "remotion"; +import "./fontlar"; +import { Lansman, SURE } from "./Lansman"; +import { FPS } from "./tema"; + +export function Root() { + return ( + <> + {/* Reels / TikTok / Shorts — öğrenci kitlesinin ana kanalı */} + + {/* YouTube, site, sunum */} + + + ); +} diff --git a/src/eski/ara.tsx b/src/eski/ara.tsx new file mode 100644 index 0000000..0bf875e --- /dev/null +++ b/src/eski/ara.tsx @@ -0,0 +1,162 @@ +import type { ReactNode } from "react"; +import { AbsoluteFill, Easing, interpolate, interpolateColors, useCurrentFrame } from "remotion"; +import { Sahne, useDuzen } from "./ortak"; +import { Eyebrow } from "./piksel"; +import { Kamera, MaskeSatir, OdakDisiPikseller } from "./sinema"; +import { FONT, R, hash, opaklik } from "./tema"; +import { VURUSLAR } from "./vurus"; + +/** + * Bölüm arası (Apple'ın "chapter" kartı): hero dokusundan yapılmış iri + * piksel rakam yukarıdan dökülür, imleç üstünden geçip pikselleri ısıtır, + * rakama tıklar ve kamera o noktadan bir sonraki sahneye dalar (geçiş + * kökeni zaman.json'daki `koken`, rakamın merkezi). + */ + +const RAKAMLAR: Record = { + 1: ["..#..", ".##..", "#.#..", "..#..", "..#..", "..#..", "#####"], + 2: [".###.", "#...#", "....#", "...#.", "..#..", ".#...", "#####"], + 3: ["####.", "....#", "....#", ".###.", "....#", "....#", "####."], +}; + +/** Rakamın ekran konumu — zaman.json `koken` ile aynı olmalı. */ +export const RAKAM_MERKEZ = { dikey: [0.5, 0.36], yatay: [0.3, 0.5] } as const; + +export function AraSahne({ + id, + no, + rozet, + baslik, + alt, +}: { + id: string; + no: number; + rozet: string; + baslik: ReactNode; + alt: string; +}) { + const frame = useCurrentFrame(); + const { dikey, w, h } = useDuzen(); + const hucre = dikey ? 66 : 72; + const desen = RAKAMLAR[no]; + const [mx, my] = dikey ? RAKAM_MERKEZ.dikey : RAKAM_MERKEZ.yatay; + const cx = mx * w; + const cy = my * h; + // Izgara: rakamın çevresine 2 hücre serpinti payı + const SUT = 9; + const SAT = 11; + const ox = cx - (SUT * hucre) / 2; + const oy = cy - (SAT * hucre) / 2; + + // Isı fırçası: sol üstten sağ alta, rakamın üstünden + const FB = 14; + const FS = 22; + const yol = (p: number): [number, number] => [ox + hucre * (0.8 + p * 7.4), oy + hucre * (1.2 + p * 8.4) + Math.sin(p * Math.PI) * -hucre * 1.4]; + const fircaNokta = (f: number) => { + const p = (f - FB) / FS; + if (p < 0 || p > 1) return null; + return yol(Easing.inOut(Easing.sin)(p)); + }; + const gecmis = Array.from({ length: 40 }, (_, k) => fircaNokta(frame - k)); + const isaret = yol(Easing.inOut(Easing.sin)(Math.min(1, Math.max(0, (frame - FB) / FS)))); + // Tık anında rakam nabız atar + const tik = frame - 40; + const nabiz = tik >= 0 && tik < 12 ? 1 + 0.12 * Math.sin((tik / 12) * Math.PI) : 1; + + const kareler: ReactNode[] = []; + for (let y = 0; y < SAT; y++) { + for (let x = 0; x < SUT; x++) { + const rx = x - 2; + const ry = y - 2; + const dolu = ry >= 0 && ry < 7 && rx >= 0 && rx < 5 && desen[ry][rx] === "#"; + // Serpinti: rakamın etrafında seyrek, silik pikseller (hero dokusu) + if (!dolu && hash(x, y, no * 7) > 0.22) continue; + const t0 = (y / SAT) * 14 + hash(x, y, no) * 6; + const ilerle = interpolate(frame, [t0, t0 + 14], [0, 1], { + extrapolateLeft: "clamp", + extrapolateRight: "clamp", + easing: Easing.bezier(0.23, 1, 0.32, 1), + }); + if (ilerle <= 0) continue; + const px = ox + x * hucre + hucre / 2; + const py = oy + y * hucre + hucre / 2; + let isi = 0; + for (let k = 0; k < gecmis.length; k++) { + const b = gecmis[k]; + if (!b) continue; + if ((b[0] - px) ** 2 + (b[1] - py) ** 2 > (hucre * 1.5) ** 2) continue; + isi = k < 4 ? 1 : Math.pow(1 - Math.min(1, (k - 4) / 34), 2); + break; + } + if (tik >= 0 && dolu) isi = Math.max(isi, Math.max(0, 1 - tik / 16)); + const tabanOp = dolu ? 0.55 + opaklik(x, y) * 1.3 : opaklik(x, y) * 0.8; + const op = (tabanOp + (0.95 - tabanOp) * isi) * Math.min(1, ilerle * 2); + const dot = hucre * 0.8; + const hh = dot * (1 + (1 - ilerle) * 1.1); + kareler.push( + 0 ? interpolateColors(isi, [0, 1], [R.primary, R.turuncu]) : R.primary} + fillOpacity={op} + />, + ); + } + } + + const metin = ( +
+ + {rozet} + +
+ + {baslik} + +
+ + {alt} + +
+ ); + + return ( + + + + + + {kareler} + +
+
+ {metin} + + + + ); +} + +export const Ara1 = () => ( + +); +export const Ara2 = () => ( + +); +export const Ara3 = () => ( + +); diff --git a/src/eski/imlec.tsx b/src/eski/imlec.tsx new file mode 100644 index 0000000..02fe338 --- /dev/null +++ b/src/eski/imlec.tsx @@ -0,0 +1,236 @@ +import { useLayoutEffect, useRef } from "react"; +import { Easing, continueRender, delayRender, interpolateColors, useCurrentFrame, useVideoConfig } from "remotion"; +import { R, hash } from "./tema"; +import ZAMAN from "./zaman.json"; + +/** + * Filmin baş karakteri: tek bir imleç. Sahneler arasında kesintisiz dolaşır + * ve hedeflerine **motion tracking** ile kilitlenir: hedef, sahnedeki bir + * öğenin `data-iz` adıdır ve konumu her karede DOM'dan ölçülür + * (getBoundingClientRect). Böylece kamera yaklaşsa, telefon 3B dönse, kart + * uçuşsa da imleç öğenin üstünde kalır. + * + * Geçtiği yerde sitedeki ısı fırçası gibi turuncu pikseller bırakır (hızla + * orantılı, 0.7 sn'de soğur); tıkladığı yerde halka + piksel patlaması. + * Konum hesabı ölçüme bağlı olduğu için çizim, React render'ı yerine + * useLayoutEffect içinde doğrudan DOM'a yazılır (ekran görüntüsünden önce). + */ + +type Hedef = string | [number, number] | { dikey: [number, number]; yatay: [number, number] }; +type Nokta = { s: string; f: number; h: Hedef; dx?: number; dy?: number; g?: number; tik?: boolean }; +type Durak = { F: number; h: Hedef; dx: number; dy: number; g: number; tik: boolean }; + +const BAS: Record = Object.fromEntries(ZAMAN.sahneler.map((s) => [s.id, s.bas])); +export const DURAKLAR: Durak[] = (ZAMAN.imlec as Nokta[]) + .map((n) => ({ F: BAS[n.s] + n.f, h: n.h, dx: n.dx ?? 0, dy: n.dy ?? 0, g: n.g ?? 14, tik: !!n.tik })) + .sort((a, b) => a.F - b.F); +const TIKLAR = DURAKLAR.filter((d) => d.tik); + +const yolEgri = Easing.bezier(0.33, 0, 0.12, 1); // insan eli: hızlı kalkış, yumuşak varış + +export function IzleyenImlec() { + const frame = useCurrentFrame(); + const { width: W, height: H } = useVideoConfig(); + const dikey = H > W; + const kokRef = useRef(null); + const svgRef = useRef(null); + const imlecRefs = useRef<(HTMLDivElement | null)[]>([]); + const BOY = dikey ? 72 : 60; + + useLayoutEffect(() => { + // Remotion kompozisyonu önce ekran dışındaki sıfır genişlikli bir kapta + // çizer, sonra görünür tuvale taşır. Ölçüm ancak taşındıktan sonra + // doğrudur; o ana kadar kare tutulur (delayRender). + if (ciz()) return; + const tutamac = delayRender("İmleç: yerleşim bekleniyor"); + let iptal = false; + const dene = () => { + if (iptal) return; + if (ciz()) continueRender(tutamac); + else requestAnimationFrame(dene); + }; + requestAnimationFrame(dene); + return () => { + iptal = true; + continueRender(tutamac); + }; + + function ciz(): boolean { + const kok = kokRef.current?.parentElement; + if (!kok) return false; + const kr = kok.getBoundingClientRect(); + if (kr.width < 1) return false; + const olcek = kr.width / W; + const onbellek = new Map(); + + const olc = (h: Hedef): [number, number] | null => { + if (Array.isArray(h)) return [h[0] * W, h[1] * H]; + if (typeof h === "object") { + const p = dikey ? h.dikey : h.yatay; + return [p[0] * W, p[1] * H]; + } + if (onbellek.has(h)) return onbellek.get(h)!; + const el = kok.querySelector(`[data-iz="${h}"]`); + let sonuc: [number, number] | null = null; + if (el) { + const r = el.getBoundingClientRect(); + if (r.width > 0 || r.height > 0) { + sonuc = [(r.left + r.width / 2 - kr.left) / olcek, (r.top + r.height / 2 - kr.top) / olcek]; + } + } + onbellek.set(h, sonuc); + return sonuc; + }; + const durakNoktasi = (i: number): [number, number] | null => { + const d = DURAKLAR[i]; + const p = olc(d.h); + return p ? [p[0] + d.dx, p[1] + d.dy] : null; + }; + // Hedef ölçülemezse (öğe henüz sahnede yoksa) bir önceki durağa düş + const guvenli = (i: number): [number, number] => { + for (let k = i; k >= 0; k--) { + const p = durakNoktasi(k); + if (p) return p; + } + return [W / 2, H / 2]; + }; + + const konum = (t: number): [number, number] => { + let j = DURAKLAR.findIndex((d) => d.F > t); + if (j === -1) return guvenli(DURAKLAR.length - 1); + if (j === 0) return guvenli(0); + const i = j - 1; + const a = guvenli(i); + const d = DURAKLAR[j]; + const kalkis = Math.max(DURAKLAR[i].F, d.F - d.g); + if (t <= kalkis) return a; + const b = durakNoktasi(j) ?? a; + const p = yolEgri(Math.min(1, (t - kalkis) / (d.F - kalkis))); + // Hafif yay: düz çizgi değil, el hareketi gibi kavis + const mx = b[0] - a[0]; + const my = b[1] - a[1]; + const uz = Math.hypot(mx, my); + const kavis = Math.sin(Math.PI * p) * Math.min(90, uz * 0.12); + const nx = uz ? -my / uz : 0; + const ny = uz ? mx / uz : 0; + return [a[0] + mx * p + nx * kavis, a[1] + my * p + ny * kavis]; + }; + + const [x, y] = konum(frame); + // Durağan anlarda nefes gibi minik salınım + const sx = Math.sin(frame / 13) * 1.6; + const sy = Math.cos(frame / 17) * 1.4; + + // Basış + let bas = 1; + for (const t of TIKLAR) { + const d = frame - t.F; + if (d >= -2 && d <= 2) bas = Math.min(bas, 1 - 0.16 * ((d + 2) / 4)); + else if (d > 2 && d <= 9) bas = Math.min(bas, 0.84 + 0.16 * ((d - 2) / 7)); + } + + // İmleç + hareket bulanıklığı hayaletleri + const [px, py] = konum(frame - 1); + const hiz = Math.hypot(x - px, y - py); + const tipX = (4.04 / 24) * BOY; + const tipY = (4.04 / 24) * BOY; + imlecRefs.current.forEach((el, k) => { + if (!el) return; + if (k === 0) { + el.style.transform = `translate(${x + sx - tipX}px, ${y + sy - tipY}px) scale(${bas})`; + el.style.opacity = "1"; + return; + } + const [gx, gy] = konum(frame - k * 0.33); + const op = hiz > 22 ? [0, 0.28, 0.16, 0.08][k] * Math.min(1, (hiz - 22) / 30) : 0; + el.style.transform = `translate(${gx + sx - tipX}px, ${gy + sy - tipY}px)`; + el.style.opacity = String(op); + }); + + // Piksel izi (ısı fırçası): hızla orantılı, ızgaraya oturan kareler + const G = dikey ? 26 : 24; + const hucreler = new Map(); + const IZ = 21; + for (let k = 1; k <= IZ; k++) { + const [ax, ay] = konum(frame - k); + const [bx, by] = konum(frame - k - 1); + const v = Math.hypot(ax - bx, ay - by); + if (v < 5) continue; + const isi = (1 - k / IZ) * Math.min(1, v / 24); + // Hızlı harekette ara noktaları da doldur ki iz kopuk olmasın + const adim = Math.max(1, Math.ceil(v / G)); + for (let s = 0; s < adim; s++) { + const qx = ax + ((bx - ax) * s) / adim; + const qy = ay + ((by - ay) * s) / adim; + const anahtar = `${Math.floor(qx / G)},${Math.floor(qy / G)}`; + hucreler.set(anahtar, Math.max(hucreler.get(anahtar) ?? 0, isi)); + } + } + const dot = G * 0.74; + let svg = ""; + hucreler.forEach((isi, anahtar) => { + const [cx, cy] = anahtar.split(",").map(Number); + const renk = interpolateColors(isi, [0, 1], [R.primary, R.turuncu]); + svg += ``; + }); + + // Tık: halka + piksel patlaması + for (const t of TIKLAR) { + const d = frame - t.F; + if (d < 0 || d > 24) continue; + const [cx, cy] = konum(t.F); + const p = Easing.out(Easing.cubic)(Math.min(1, d / 18)); + const r = 12 + (dikey ? 78 : 64) * p; + svg += ``; + const KARE = 12; + const Gp = dikey ? 14 : 12; + for (let n = 0; n < 10; n++) { + const aci = (n / 10) * Math.PI * 2 + hash(t.F, n, 3) * 0.6; + const uz = (18 + (dikey ? 96 : 80) * (0.6 + 0.4 * hash(t.F, n, 5))) * p; + // Konum ızgaraya oturur: piksel adımlarıyla dağılan kareler + const qx = Math.round((cx + Math.cos(aci) * uz) / Gp) * Gp; + const qy = Math.round((cy + Math.sin(aci) * uz) / Gp) * Gp; + const boy = KARE * (1 - d / 26); + const renk = [R.turuncu, R.primary, R.logoSeftali][n % 3]; + svg += ``; + } + } + if (svgRef.current) svgRef.current.innerHTML = svg; + return true; + } + }, [frame, W, H, dikey, BOY]); + + return ( +
+ + {[0, 1, 2, 3].map((k) => ( +
{ + imlecRefs.current[k] = el; + }} + style={{ + position: "absolute", + left: 0, + top: 0, + width: BOY, + height: BOY, + transformOrigin: `${(4.04 / 24) * 100}% ${(4.04 / 24) * 100}%`, + filter: k === 0 ? "drop-shadow(0 8px 14px rgba(15,23,42,0.28))" : undefined, + opacity: 0, + }} + > + + + +
+ ))} +
+ ); +} diff --git a/src/eski/sahneler-giris.tsx b/src/eski/sahneler-giris.tsx new file mode 100644 index 0000000..d71fd19 --- /dev/null +++ b/src/eski/sahneler-giris.tsx @@ -0,0 +1,277 @@ +import { measureText } from "@remotion/layout-utils"; +import { AbsoluteFill, Easing, interpolate, useCurrentFrame } from "remotion"; +import { YagmurAlani } from "./harita"; +import { LogoMark } from "./logo"; +import { Sahne, useDuzen } from "./ortak"; +import { Eyebrow, KelimeKelime, Wordmark } from "./piksel"; +import { Kamera, MaskeSatir, OdakDisiPikseller, type Anahtar } from "./sinema"; +import { FONT, R, ara, hash, trSayi, zipla } from "./tema"; +import { VURUSLAR } from "./vurus"; + +const vurguStil = { + fontFamily: FONT.vurgu, + fontStyle: "italic" as const, + fontWeight: 800, + letterSpacing: "-0.04em", + color: R.primary, +}; + +/* ------------------------------------------------------------------ */ +/* Açılış — boş ekran, imleç tıklar, sıralama belirir */ +/* ------------------------------------------------------------------ */ +export function Acilis() { + const frame = useCurrentFrame(); + const { dikey } = useDuzen(); + // Tık (10. kare) noktasından büyüyerek belirir: kt-unblur + const ac = interpolate(frame, [10, 30], [0, 1], { extrapolateLeft: "clamp", extrapolateRight: "clamp", easing: Easing.bezier(0.16, 1, 0.3, 1) }); + const sayi = ara(frame, [12, 46], [0, 45320], Easing.bezier(0.16, 1, 0.3, 1)); + return ( + + + + YKS sonuçları açıklandı +
+ {trSayi(sayi)} + . +
+
+ Sıralaman geldi. + + Asıl bulmaca şimdi başlıyor. + +
+
+
+
+ ); +} + +/* ------------------------------------------------------------------ */ +/* İhtimal — 21.000+ program, 24 hak; imleç pikselleri ısıtarak geçer */ +/* ------------------------------------------------------------------ */ +export function Ihtimal() { + const frame = useCurrentFrame(); + const { dikey, w, h } = useDuzen(); + const sayi = ara(frame, [0, 40], [0, 21000], Easing.bezier(0.16, 1, 0.3, 1)); + const arti = zipla(frame, 38); + const firca = { + bas: 8, + sure: 66, + yol: (p: number): [number, number] => + dikey + ? [w * (0.08 + p * 0.84), h * (0.1 + p * 0.8) + Math.sin(p * Math.PI * 2) * h * 0.08] + : [w * (0.03 + p * 0.94), h * (0.16 + Math.sin(p * Math.PI) * 0.7)], + }; + return ( + + + + +
+ {trSayi(sayi)} + + +
+
lisans ve önlisans programı
+
+ + Senin sadece 24 hakkın var. + +
+
+ Matematik biraz karışık. +
+
+
+
+ ); +} + +/* ------------------------------------------------------------------ */ +/* Kaos — soru kartları uçuşur; imleç kartları tek tek takip eder, */ +/* sonra hepsi imlecin tıkladığı noktaya emilir */ +/* ------------------------------------------------------------------ */ +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], +]; +// İmlecin üzerinde durduğu kart ve kareler (zaman.json imleç senaryosuyla aynı) +const HOVER: Record = { 1: [12, 30], 3: [30, 48], 5: [48, 66], 6: [66, 82] }; +const KAOS_KAMERA: Anahtar[] = [ + { f: 0, s: 0.96, r: -1.2 }, + { f: 86, s: 1.1, r: 0.8 }, + { f: 112, s: 1.3, r: 0 }, +]; + +export function Kaos() { + const frame = useCurrentFrame(); + const { dikey, w, h } = useDuzen(); + const yerler = dikey ? YER_DIKEY : YER_YATAY; + const cek = ara(frame, [89, 112], [0, 1], Easing.in(Easing.cubic)); + return ( + + + + {Array.from({ length: 16 }, (_, i) => { + const y = ((i * 150 - frame * (6 + (i % 3) * 3)) % (h + 300) + h + 300) % (h + 300) - 150; + return ( +
+ {[0.5, 0.2, 0.15, 0.15].map((oran, j) => ( +
+ ))} +
+ ); + })} + + + {SORULAR.map((s, i) => { + const [nx, ny] = yerler[i]; + const g = zipla(frame, 6 + i * 5); + const yuzX = Math.sin((frame + i * 20) / 18) * 16; + const yuzY = Math.cos((frame + i * 13) / 15) * 14; + const x = nx * (w / 2) * (1 - cek) + yuzX * (1 - cek); + const y = ny * (h / 2) * (1 - cek) + yuzY * (1 - cek); + const donus = (hash(i, 1, 2) - 0.5) * 10 + cek * 90 * (i % 2 ? 1 : -1); + const hv = HOVER[i]; + const ust = hv + ? interpolate(frame, [hv[0], hv[0] + 4, hv[1] - 2, hv[1] + 2], [0, 1, 1, 0], { extrapolateLeft: "clamp", extrapolateRight: "clamp" }) + : 0; + return ( +
0.5 ? "#fff7ed" : R.beyaz, + border: `3px solid ${ust > 0.5 ? R.turuncu : R.slate200}`, + borderRadius: 30, + padding: dikey ? "24px 32px" : "22px 30px", + fontSize: dikey ? 38 : 34, + fontWeight: 600, + color: ust > 0.5 ? R.turuncu : R.slate700, + whiteSpace: "nowrap", + boxShadow: `0 ${12 + ust * 16}px ${30 + ust * 20}px rgba(15,23,42,${0.08 + ust * 0.06})`, + }} + > + {s} +
+ ); + })} + + +

+ +

+
+ + + ); +} + +/* ------------------------------------------------------------------ */ +/* Logo — imlecin tıkladığı noktadan kareler iner, rota, slogan */ +/* ------------------------------------------------------------------ */ +export function LogoSahnesi() { + const frame = useCurrentFrame(); + const { dikey } = useDuzen(); + const markBoy = dikey ? 300 : 280; + const yaziBoy = dikey ? 150 : 170; + const olc = (metin: string, agirlik: string) => + measureText({ text: metin, fontFamily: "Bricolage", fontSize: yaziBoy, fontWeight: agirlik, letterSpacing: `${-0.05 * yaziBoy}px` }).width; + const yaziGen = olc("Kolay", "300") + olc("Tercih", "600"); + const ac = ara(frame, [26, 50], [0, 1]); + const bosluk = 30; + const kaydirX = dikey ? 0 : interpolate(ac, [0, 1], [0, -(yaziGen + bosluk) / 2]); + const kaydirY = dikey ? interpolate(ac, [0, 1], [0, -110]) : 0; + return ( + + + + +
+ +
+ +
+
+
+ +
+ +
+
+
+
+ ); +} diff --git a/src/eski/sahneler-urun.tsx b/src/eski/sahneler-urun.tsx new file mode 100644 index 0000000..ea8089d --- /dev/null +++ b/src/eski/sahneler-urun.tsx @@ -0,0 +1,742 @@ +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 = { 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 ( +
+ {/* Site üst çubuğu */} +
+ + + + + + + +
+
+ Adım 1 / 3 +
Sıralamanı gir
+
Sana uyan programları bununla bulacağız.
+
Başarı sıralaman
+
+ + + {yazilan} + {odak && imlecGorunur ? | : null} + {!yazilan && !odak ? Örn. 45.320 : null} + +
+
Neyi seviyorsun, nerede okumak istersin?
+
+ {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 ( + + {i >= 2 && i <= 4 ? : i === 5 ? : null} + {c} + + ); + })} +
+
+ + + +
+
+ {/* CTA'dan sonra: liste hazırlanıyor paneli (3. adıma köprü) */} +
+
Listen hazırlanıyor
+
6 yılın verisi taranıyor…
+
+ {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 ; + })} +
+
+
+ ); +} + +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 ( + + + + +
+ {!dikey ? ( + + Adım 1 + + ) : null} +
+ Form doldurmaktan + + kısa sürer. + +
+ {!dikey ? ( + + + Sıralamanı yaz, sevdiğin alanları ve şehirleri seç. Gerisini bir butona bırak. + + + ) : null} +
+
+ + + +
+
+
+
+ ); +} + +/* ------------------------------------------------------------------ */ +/* 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 ( + + + + + + +
+ {ISTATISTIK.map((s, i) => { + const g = yay(frame, 124 + i * 8, 20); + return ( + +
+ {s.deger} +
+
{s.etiket}
+
+ ); + })} +
+
+
+ +
+
+ Yapay zekâ bu havuzun dışına çıkamaz. Uydurmak yok. +
+
+
+
+
+ ); +} + +/* ------------------------------------------------------------------ */ +/* 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 = ( +
+
+ {KARELER.map((d, i) => { + const s = zipla(frame, BAS + i * 2); + return ( +
+ {i + 1} +
+ ); + })} +
+
+
+ {DILIMLER.map((d, i) => { + const g = yay(frame, 78 + i * 6, 16); + return ( + + + {d.ad} + {d.adet} + + ); + })} +
+
+ ); + + const satirlar = ( +
+ {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 ( + +
+ {s.sira} +
+
+
{s.ad}
+
+ {s.uni} · {s.il} +
+
+ + + Taban {trSayi(s.taban)} + +
+
+
+ ); + })} +
+ Örnek: sayısal, 45.320. sıra · YÖK Atlas 2026 taban sıralamaları +
+
+ ); + + return ( + + + + + +
+ {izgara} + {satirlar} +
+
+
+
+ ); +} + +/* ------------------------------------------------------------------ */ +/* 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 ( + +
+ +
+
+
+ +
+
{baslik}
+
{aciklama}
+
+
+ {children} +
+
+ ); +} + +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 ( + + + + +
+ + Paket içinde + +
+ + Her tercihin bir “neden”i var. + +
+
+
+ + + + {noktalar.map((p, i) => ( + = i / (TREND.length - 1) ? 1 : 0} /> + ))} + + + +
+ {[2, 1, 0].map((k) => { + const s = yay(frame, 34 + (2 - k) * 6, 18); + return ( +
+ {k === 0 ? ( + <> +
+ {[R.riskli, R.azRiskli, R.guvenli].map((c) => ( + + ))} +
+ {[1, 0.8, 0.9, 0.6, 0.75].map((wd, j) => ( + + ))} + + ) : null} +
+ ); + })} +
+
+ +
+ {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 ( +
+ + {r.ad} + {i === 0 ? ( + + {Array.from({ length: 6 }, (_, k) => ( + + ))} + + ) : null} +
+ ); + })} +
+
+
+
+
+
+ ); +} + +/* ------------------------------------------------------------------ */ +/* 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 ( + + + + +
+ +
+
+ Yerleşme garantisi + vermiyoruz. +
+
+ Çünkü kimse veremez. +
+
+
+ Veren olursa, uzak dur. Biz riski satır satır gösteririz. +
+
+ {(["guvenli", "az-riskli", "riskli"] as const).map((r, i) => ( + + + + ))} +
+
+
+
+
+ ); +} + +/* ------------------------------------------------------------------ */ +/* 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 ( + + + +
+ +
+ +
+
+
+ Listeni kur, + + panikle değil. + +
+
+ +
+
+
+
+ ); +} diff --git a/src/eski/sahneler-yz.tsx b/src/eski/sahneler-yz.tsx new file mode 100644 index 0000000..650ce18 --- /dev/null +++ b/src/eski/sahneler-yz.tsx @@ -0,0 +1,441 @@ +import type { ReactNode } from "react"; +import { AbsoluteFill, useCurrentFrame } from "remotion"; +import { Ikon } from "./ikonlar"; +import { LogoMark } from "./logo"; +import { RiskEtiketi, Sahne, useDuzen } from "./ortak"; +import { Eyebrow } 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"; + +/* ------------------------------------------------------------------ */ +/* Danışman başlığı — sitedeki DanismanBaslik (sohbet-client.tsx) */ +/* ------------------------------------------------------------------ */ +function DanismanlaKonus({ boy }: { boy: number }) { + return ( + + + + Danışmanla + Konuş + + + ); +} + +// Sitedeki ORNEK_SORULAR (sohbet-client.tsx) + danışman araçlarının gerçek kullanımları +const ORNEK_SORULAR = [ + "Listemdeki riskli tercihler ne kadar riskli?", + "İlk 5 tercihimin taban trendi nasıl?", + "Güvenli dilimden bir tercihi üste alsam mantıklı mı?", + "X bölümü ile Y arasında kalırsam neye bakmalıyım?", + "İstanbul'da %50 burslu vakıf var mı?", + "Bu üniversite nasıl bir yer?", +]; + +/* ------------------------------------------------------------------ */ +/* Yapay zekâ bölüm açılışı */ +/* ------------------------------------------------------------------ */ +export function YzBaslik() { + const frame = useCurrentFrame(); + const { dikey, w, h } = useDuzen(); + const mark = zipla(frame, 2); + return ( + + + {ORNEK_SORULAR.map((q, i) => { + const x = (dikey ? [0.08, 0.52, 0.18, 0.6, 0.05, 0.45] : [0.05, 0.62, 0.2, 0.7, 0.1, 0.55])[i] * w; + const hiz = 3.2 + (i % 3) * 0.9; + const y = h * (0.95 - ((i * 0.19 + (frame * hiz) / h) % 1.15)); + const derin = i % 2 === 0; + return ( +
+ {q} +
+ ); + })} +
+ + +
+
+ +
+
+ + Aklına takılanı{" "} + + sor. + + +
+ + + Gece 3'te bile cevap verir. Sıkılmaz, yargılamaz, “bu da soru mu” demez. + + +
+
+
+
+ ); +} + +/* ------------------------------------------------------------------ */ +/* Sohbet — 3B telefonda üç gerçek kullanım senaryosu */ +/* ------------------------------------------------------------------ */ + +// Gerçek YÖK Atlas 2026 verisi (data/yokatlas.db): İzmir'de İngilizce +// Bilgisayar Mühendisliği, devlet; örnek aday 45.320. (riskHesapla eşikleri) +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 }, +]; + +type Mesaj = + | { tur: "kullanici"; metin: string; yaz: [number, number]; gonder: number } + | { tur: "arac"; ad: string; detay: string; bas: number; bitis: number } + | { tur: "cevap"; bas: number; Icerik: () => ReactNode; tahmin: number; iz?: string }; + +function SonucListesi() { + const frame = useCurrentFrame(); + return ( +
+
Sıralamana göre İzmir'de üç devlet seçeneği öne çıkıyor:
+ {SONUCLAR.map((s, i) => { + const g = yay(frame, 102 + i * 7, 16); + return ( +
+
Bilgisayar Mühendisliği
+
{s.uni} · Devlet
+
+ + + Taban {trSayi(s.taban)} + +
+
+ ); + })} +
+ ); +} + +function ProfilCevabi() { + const frame = useCurrentFrame(); + const cip = zipla(frame, 292); + return ( +
+
Profilini güncelledim. Yeni listende havuz yalnız İzmir'deki programlardan süzülecek.
+ + + Şehir: İzmir + + +
+ ); +} + +const SENARYO: Mesaj[] = [ + { tur: "kullanici", metin: "İzmir'de İngilizce bilgisayar mühendisliği var mı?", yaz: [22, 52], gonder: 58 }, + { tur: "arac", ad: "program_ara", detay: "İzmir · İngilizce", bas: 64, bitis: 90 }, + { tur: "cevap", bas: 92, tahmin: 330, Icerik: SonucListesi }, + { tur: "kullanici", metin: "Dokuz Eylül'deki akreditasyonlu mu?", yaz: [142, 158], gonder: 164 }, + { tur: "arac", ad: "program_detay", detay: "Dokuz Eylül", bas: 170, bitis: 188 }, + { + tur: "cevap", + bas: 190, + tahmin: 120, + iz: "yz-cevap-2", + Icerik: () => ( + <> + Evet, MÜDEK akreditasyonlu ve eğitim dili İngilizce. 2026'da son yerleşen 52.071. oldu; sen 45.320.'sin. + + ), + }, + { tur: "kullanici", metin: "Aslında İzmir dışına çıkmak istemiyorum.", yaz: [226, 246], gonder: 250 }, + { tur: "arac", ad: "profil_guncelle", detay: "şehir: İzmir", bas: 256, bitis: 272 }, + { tur: "cevap", bas: 274, tahmin: 140, Icerik: ProfilCevabi }, +]; + +function Avatar({ canli }: { canli: boolean }) { + return ( + + + + ); +} + +/** Telefonun içindeki sohbet ekranı (site px, 390 genişlik). */ +function SohbetEkrani() { + const frame = useCurrentFrame(); + 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.metin.length], (t) => t)); + return m.metin.slice(0, n); + } + } + return ""; + })(); + const basis = SENARYO.some((m) => m.tur === "kullanici" && frame >= m.gonder - 2 && frame < m.gonder + 3); + const onerilerGit = ara(frame, [52, 60], [1, 0]); + const kredi = frame >= 250 ? 57 : frame >= 164 ? 58 : frame >= 58 ? 59 : 60; + return ( +
+
+ + + {kredi} soru hakkı + +
+
+ {onerilerGit > 0 ? ( +
+
Örnek sorular
+ {ORNEK_SORULAR.slice(0, 3).map((q, i) => { + const g = yay(frame, 6 + i * 4, 14); + return ( + + {q} + + ); + })} +
+ ) : null} + {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" ? 80 : m.tur === "arac" ? 40 : m.tahmin; + const kap = { maxHeight: g >= 0.999 ? undefined : g * tahmin, overflow: "hidden" as const, flexShrink: 0 }; + if (m.tur === "kullanici") { + return ( +
+
+ {m.metin} +
+
+ ); + } + if (m.tur === "arac") { + const bitti = frame >= m.bitis; + return ( +
+ + + {bitti ? : } + {m.ad} + · {m.detay} + {!bitti ? · arıyor{".".repeat(1 + (Math.floor(frame / 6) % 3))} : null} + +
+ ); + } + return ( +
+
+ +
+
+ ); + })} +
+
+
+ + {yazilan || "Listen hakkında bir şey sor…"} + {yazilan && Math.floor(frame / 8) % 2 === 0 ? | : null} + + + + +
+
+
+ ); +} + +// Olan biteni anlatan altyazılar (Apple tarzı), kameradan bağımsız +const ALTYAZI: { bas: number; son: number; metin: ReactNode }[] = [ + { bas: 4, son: 60, metin: "Sen soruyorsun." }, + { bas: 64, son: 94, metin: "O, YÖK Atlas'ta arıyor." }, + { bas: 98, son: 158, metin: "Gerçek kayıtla cevaplıyor." }, + { bas: 164, son: 244, metin: "Ayrıntıya da iniyor." }, + { bas: 252, son: 345, metin: "Fikrin değişirse, profilini güncelliyor." }, +]; + +export function YzSohbet() { + const frame = useCurrentFrame(); + const { dikey, w, h } = useDuzen(); + const telGen = dikey ? 700 : 480; + const don = [ + { f: 0, s: -22, x: 8 }, + { f: 40, s: -5, x: 2 }, + { f: 300, s: 3, x: 1 }, + { f: 350, s: 10, x: 4 }, + ] as Anahtar[]; + const kamera: Anahtar[] = dikey + ? [ + { f: 0, s: 1, y: 60 }, + { f: 18, s: 1.3, y: 650 }, + { f: 52, s: 1.3, y: 650 }, + { f: 70, s: 1.2, y: 420 }, + { f: 112, s: 1.14, y: 330 }, + { f: 134, s: 1.3, y: 650 }, + { f: 166, s: 1.3, y: 620 }, + { f: 196, s: 1.22, y: 440 }, + { f: 218, s: 1.3, y: 650 }, + { f: 252, s: 1.3, y: 620 }, + { f: 280, s: 1.22, y: 440 }, + { f: 305, s: 1.2, y: 420 }, + { f: 334, s: 1, y: 60 }, + ] + : [ + { f: 0, s: 1, x: 0, y: 0 }, + { f: 18, s: 1.35, x: 150, y: 330 }, + { f: 52, s: 1.35, x: 150, y: 330 }, + { f: 70, s: 1.25, x: 150, y: 180 }, + { f: 112, s: 1.2, x: 150, y: 120 }, + { f: 134, s: 1.35, x: 150, y: 330 }, + { f: 166, s: 1.35, x: 150, y: 310 }, + { f: 196, s: 1.28, x: 150, y: 200 }, + { f: 218, s: 1.35, x: 150, y: 330 }, + { f: 252, s: 1.35, x: 150, y: 310 }, + { f: 280, s: 1.28, x: 150, y: 200 }, + { f: 305, s: 1.25, x: 150, y: 180 }, + { f: 334, s: 1, x: 0, y: 0 }, + ]; + const dipnot = yay(frame, 312, 20); + + return ( + + + +
+ + + +
+
+ {/* Altyazılar: kameradan bağımsız, hep okunur */} + +
+ {!dikey ? ( + + Yapay zekâ danışman + + ) : null} +
+ {ALTYAZI.map((a) => + frame >= a.bas - 1 && frame < a.son + 16 ? ( +
+ + {a.metin} + +
+ ) : null, + )} +
+
+
+ Havuz dışına çıkamaz, uydurma bölüm önermez +
+
+
+ ); +} diff --git a/src/eski/telefon.tsx b/src/eski/telefon.tsx new file mode 100644 index 0000000..2dd5619 --- /dev/null +++ b/src/eski/telefon.tsx @@ -0,0 +1,129 @@ +import type { ReactNode } from "react"; +import { R } from "./tema"; + +/** + * 3B telefon maketi. Ekranın içi **sitenin kendi px ölçüsüyle** (390 px + * genişlik) yazılır ve ekrana ölçeklenir; böylece h-12, text-base gibi + * değerler DESIGN.md'deki gibi birebir kullanılır. Marka/logo taşımayan + * genel bir cihaz çerçevesi. + */ +export function Telefon({ + genislik, + donX = 0, + donY = 0, + donZ = 0, + children, +}: { + genislik: number; + donX?: number; + donY?: number; + donZ?: number; + children: ReactNode; +}) { + const yukseklik = genislik * 2.05; + const cerceve = genislik * 0.034; + const yaricap = genislik * 0.16; + const ekranW = genislik - 2 * cerceve; + const ekranH = yukseklik - 2 * cerceve; + const k = ekranW / 390; + // Işık açıyla kayar: telefon döndükçe camdan geçen parlama + const isikKonum = 50 + donY * 2.2 - donX * 1.2; + return ( +
+
+ {/* Gövde */} +
+ {/* Ekran */} +
+
+ + {children} +
+
+ {/* Kamera adası */} +
+ {/* Cam parlaması */} +
+
+
+ ); +} + +/** iOS benzeri durum çubuğu (site px). */ +function DurumCubugu() { + return ( +
+ 09:41 + + {[5, 7, 9, 11].map((h, i) => ( + + ))} + + + + +
+ ); +} diff --git a/src/fontlar.ts b/src/fontlar.ts new file mode 100644 index 0000000..d48bf2a --- /dev/null +++ b/src/fontlar.ts @@ -0,0 +1,32 @@ +import { loadFont } from "@remotion/fonts"; +import { staticFile } from "remotion"; + +// fontsource'un latin / latin-ext ayrımı: Türkçe harfler (ğ, ş, İ) latin-ext'te +const LATIN = + "U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD"; +const LATIN_EXT = + "U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF"; + +const f = (dosya: string) => staticFile(`fonts/${dosya}`); + +export const fontlarHazir = Promise.all([ + loadFont({ family: "Cal Sans", url: f("cal-sans-latin-400-normal.woff"), unicodeRange: LATIN }), + loadFont({ family: "Cal Sans", url: f("cal-sans-latin-ext-400-normal.woff"), unicodeRange: LATIN_EXT }), + ...(["300", "600", "800"] as const).flatMap((w) => [ + loadFont({ + family: "Bricolage", + weight: w, + url: f(`bricolage-grotesque-latin-${w}-normal.woff`), + unicodeRange: LATIN, + }), + loadFont({ + family: "Bricolage", + weight: w, + url: f(`bricolage-grotesque-latin-ext-${w}-normal.woff`), + unicodeRange: LATIN_EXT, + }), + ]), + loadFont({ family: "Clarity City", weight: "400", url: f("ClarityCity-Regular.woff2") }), + loadFont({ family: "Clarity City", weight: "600", url: f("ClarityCity-SemiBold.woff2") }), + loadFont({ family: "Clarity City", weight: "700", url: f("ClarityCity-Bold.woff2") }), +]); diff --git a/src/harita.tsx b/src/harita.tsx new file mode 100644 index 0000000..fd94e86 --- /dev/null +++ b/src/harita.tsx @@ -0,0 +1,302 @@ +import type { ReactNode } from "react"; +import { + AbsoluteFill, + Easing, + interpolate, + interpolateColors, + useCurrentFrame, + useVideoConfig, +} from "remotion"; +import { R, hash, opaklik } from "./tema"; + +/** + * Hero'daki dot-matrix Türkiye (turkey-pixel-map.tsx): aynı 64×26 ızgara, + * aynı şehirler, kıyının altına doğru seyrelen serpinti ve imleç ısı + * fırçası (use-pixel-heat.ts: ısınma 0.15 sn, soğuma 1.4 sn). Videoda + * pikseller yukarıdan dökülerek yerine oturur; ardından bir imleç haritanın + * üzerinden geçip pikselleri turuncuya ısıtır. + */ + +const GRID = [ + "..######..............##########................................", + "..#######...........##############..............................", + "..#######.........#################................#####........", + ".########...###########################...........########......", + ".######.....###############################################.....", + ".###........###############################################.....", + ".##.......##################################################....", + ".###########################################################....", + ".#############################################################..", + "..#############################################################.", + "...############################################################.", + "...##########################################################...", + "...##########################################################...", + "...###########################################################..", + "..############################################################..", + "..############################################################..", + "...###########################################################..", + "....##########################################################..", + "....##########################################################..", + ".....#########################################################..", + ".....###################################################..##....", + ".....############################################...............", + "......##########..######################...###..................", + "...........#####....#########....#####..........................", + "............###.......#####......####...........................", + ".................................###............................", +]; +const SUTUN = GRID[0].length; +const SATIR = GRID.length; +const SERPINTI = 9; + +const SEHIRLER = [ + { x: 8, y: 4 }, + { x: 23, y: 9 }, + { x: 4, y: 16 }, + { x: 16, y: 22 }, + { x: 32, y: 14 }, + { x: 46, y: 5 }, + { x: 47, y: 18 }, + { x: 58, y: 15 }, +]; +const sehirIndeks = new Map(SEHIRLER.map((c, i) => [`${c.x},${c.y}`, i])); + +type Piksel = { x: number; y: number; op: number; tur: "kara" | "sehir" | "serpinti" | "vurgu"; sira: number }; + +// Modül yüklenirken tek sefer: hero'daki MAP_MARKUP ile aynı üretim +const PIKSELLER: Piksel[] = (() => { + const p: Piksel[] = []; + GRID.forEach((satir, y) => { + for (let x = 0; x < satir.length; x++) { + if (satir[x] !== "#") continue; + const si = sehirIndeks.get(`${x},${y}`); + p.push({ x, y, op: si == null ? opaklik(x, y) : 0.7, tur: si == null ? "kara" : "sehir", sira: si ?? 0 }); + } + }); + const sonKara: number[] = Array(SUTUN).fill(-1); + GRID.forEach((satir, y) => { + for (let x = 0; x < satir.length; x++) if (satir[x] === "#") sonKara[x] = y; + }); + let vurgu = 0; + for (let x = 0; x < SUTUN; x++) { + if (sonKara[x] < 0) continue; + for (let d = 1; d <= SERPINTI; d++) { + const y = sonKara[x] + d; + if (y >= SATIR + SERPINTI) break; + const yogun = 0.05 + 0.4 * (1 - (d - 1) / SERPINTI) ** 2; + if (hash(x, y) > yogun) continue; + if (hash(x, y, 99) > 0.96) { + p.push({ x, y, op: 0.7, tur: "vurgu", sira: vurgu++ % 8 }); + continue; + } + const op = Math.round(opaklik(x, y) * (1 - d / (SERPINTI + 1)) * 100) / 100; + p.push({ x, y, op, tur: "serpinti", sira: 0 }); + } + } + return p; +})(); + +const belirEgri = Easing.bezier(0.23, 1, 0.32, 1); // kt-piksel-belir + +export const HARITA_SUTUN = SUTUN; +export const HARITA_SATIR = SATIR + SERPINTI; +const varsayilanYol = (p: number) => { + const e = Easing.inOut(Easing.sin)(p); + return { x: -4 + e * (SUTUN + 8), y: 11 + Math.sin(p * Math.PI * 2.2) * 6 }; +}; + +/** Tailwind animate-pulse: opaklık 1 → 0.5 → 1, burada 3.6 sn (hero şehirleri). */ +const nabiz = (frame: number, gecikmeSn: number) => { + const t = ((frame / 30 - gecikmeSn) % 3.6 + 3.6) % 3.6 / 3.6; + return 0.75 + 0.25 * Math.cos(t * Math.PI * 2); +}; + +export function YagmurHaritasi({ + genislik, + baslangic = 0, + dokulme = 40, + fircaBas, + fircaSure = 70, + iz, + fircaYol, +}: { + genislik: number; + baslangic?: number; + /** Üst satırdan alt satıra dökülme süresi (kare) */ + dokulme?: number; + /** Isı fırçasının (imleç) haritaya girdiği kare */ + fircaBas?: number; + fircaSure?: number; + /** Fırçanın ucuna konan görünmez işaretin adı */ + iz?: string; + /** Fırça yolu, ızgara biriminde (p: 0..1, sin eğrili) */ + fircaYol?: (p: number) => { x: number; y: number }; +}) { + const frame = useCurrentFrame(); + const hucre = genislik / SUTUN; + const dot = hucre * 0.74; + const ic = (hucre - dot) / 2; + const yukseklik = (SATIR + SERPINTI) * hucre; + + // Fırça yolu (ızgara biriminde): soldan sağa hafif dalgalı bir geçiş + const firca = (f: number) => { + if (fircaBas == null) return null; + const p = (f - fircaBas) / fircaSure; + if (p < 0 || p > 1) return null; + return (fircaYol ?? varsayilanYol)(p); + }; + const YARICAP = 3.4; // 34 viewBox birimi / 10'luk hücre + const isiHesapla = (x: number, y: number) => { + if (fircaBas == null || frame < fircaBas) return 0; + let isi = 0; + for (let k = 0; k < 48; k++) { + const b = firca(frame - k); + if (!b) continue; + if ((b.x - x - 0.5) ** 2 + (b.y - y - 0.5) ** 2 > YARICAP * YARICAP) continue; + // 0.15 sn'de ısın, 1.4 sn'de soğu (easeOut) + const h = k < 4.5 ? 1 : Math.pow(1 - Math.min(1, (k - 4.5) / 42), 2.2); + if (h > isi) isi = h; + break; // en yakın geçmiş temas en sıcak olandır + } + return isi; + }; + + const rectler: ReactNode[] = []; + for (const p of PIKSELLER) { + // Dökülme: üst satır önce; serpinti en son, kıyıdan aşağı damlar gibi + const t0 = baslangic + (p.y / (SATIR + SERPINTI)) * dokulme + hash(p.x, p.y, 3) * 10; + const ilerle = interpolate(frame, [t0, t0 + 16], [0, 1], { + extrapolateLeft: "clamp", + extrapolateRight: "clamp", + easing: belirEgri, + }); + if (ilerle <= 0) continue; + const dusme = (1 - ilerle) * (hucre * (7 + hash(p.x, p.y, 8) * 9)); + const uzama = 1 + (1 - ilerle) * 0.9; // düşerken dikine uzar + const gorunur = Math.min(1, ilerle * 2.2); + const w = dot; + const h = dot * uzama; + const x = p.x * hucre + ic; + const y = p.y * hucre + ic - dusme - (h - dot) / 2; + + let fill: string = R.primary; + let op = p.op; + if (p.tur === "sehir" || p.tur === "vurgu") { + fill = R.turuncu; + op = 0.7 * nabiz(frame, p.sira * 0.45); + } else { + const isi = isiHesapla(p.x, p.y); + if (isi > 0) { + fill = interpolateColors(isi, [0, 1], [R.primary, R.turuncu]); + op = p.op + (0.85 - p.op) * isi; + } + } + rectler.push( + , + ); + } + + // İşaret, fırça yolunun o anki noktasında durur (öncesinde başta, sonrasında sonda) + const isaret = (() => { + if (fircaBas == null) return null; + const p = Math.min(1, Math.max(0, (frame - fircaBas) / fircaSure)); + return (fircaYol ?? varsayilanYol)(p); + })(); + return ( +
+ + {rectler} + + {iz && isaret ? ( +
+ ) : null} +
+ ); +} + +/** Ekranı dolduran piksel alanı — hero dokusu gibi yukarıdan aşağı dökülür. */ +export function YagmurAlani({ + baslangic, + dokulme, + hucre = 40, + bosluk, + firca, + iz, +}: { + baslangic: number; + dokulme: number; + hucre?: number; + bosluk?: { rx: number; ry: number }; + /** İmleç yolu: kare → ekran noktası (px). Geçtiği pikseller ısınır. */ + firca?: { bas: number; sure: number; yol: (p: number) => [number, number] }; + iz?: string; +}) { + const frame = useCurrentFrame(); + const { width, height } = useVideoConfig(); + const sut = Math.ceil(width / hucre); + const sat = Math.ceil(height / hucre); + const dot = hucre * 0.74; + const kareler: ReactNode[] = []; + const fircaNokta = (f: number): [number, number] | null => { + if (!firca) return null; + const p = (f - firca.bas) / firca.sure; + if (p < 0 || p > 1) return null; + return firca.yol(Easing.inOut(Easing.sin)(p)); + }; + const gecmis: ([number, number] | null)[] = Array.from({ length: 48 }, (_, k) => fircaNokta(frame - k)); + const YARICAP = hucre * 3.2; + for (let y = 0; y < sat; y++) { + for (let x = 0; x < sut; x++) { + const cx = x * hucre + hucre / 2 - width / 2; + const cy = y * hucre + hucre / 2 - height / 2; + if (bosluk && (cx / bosluk.rx) ** 2 + (cy / bosluk.ry) ** 2 < 1) continue; + // Yoğunluk kenarlara doğru artar (hero serpintisi gibi seyrek başlar) + if (hash(x, y, 21) > 0.82) continue; + const t0 = baslangic + (y / sat) * dokulme + hash(x, y, 5) * 12; + const ilerle = interpolate(frame, [t0, t0 + 14], [0, 1], { + extrapolateLeft: "clamp", + extrapolateRight: "clamp", + easing: belirEgri, + }); + if (ilerle <= 0) continue; + const vurgu = hash(x, y, 77) > 0.975; + let op = (vurgu ? 0.8 : opaklik(x, y) + 0.04) * Math.min(1, ilerle * 2); + const h = dot * (1 + (1 - ilerle) * 1.2); + // Isı fırçası: imlecin son 1.6 sn'de değdiği pikseller turuncuya ısınır + let isi = 0; + if (!vurgu && firca) { + const px = x * hucre + hucre / 2; + const py = y * hucre + hucre / 2; + for (let k = 0; k < gecmis.length; k++) { + const b = gecmis[k]; + if (!b) continue; + if ((b[0] - px) ** 2 + (b[1] - py) ** 2 > YARICAP * YARICAP) continue; + isi = k < 4.5 ? 1 : Math.pow(1 - Math.min(1, (k - 4.5) / 42), 2.2); + break; + } + if (isi > 0) op = op + (0.85 - op) * isi; + } + kareler.push( + 0 ? interpolateColors(isi, [0, 1], [R.primary, R.turuncu]) : R.primary} + fillOpacity={op} + />, + ); + } + } + const isaret = firca ? firca.yol(Easing.inOut(Easing.sin)(Math.min(1, Math.max(0, (frame - firca.bas) / firca.sure)))) : null; + return ( + + + {kareler} + + {iz && isaret ?
: null} + + ); +} diff --git a/src/ikonlar.tsx b/src/ikonlar.tsx new file mode 100644 index 0000000..dd2ef84 --- /dev/null +++ b/src/ikonlar.tsx @@ -0,0 +1,102 @@ +import type { CSSProperties } from "react"; + +// lucide-react yollarının kopyası (sitedeki ikon seti; DESIGN.md §4.7) +const YOLLAR: Record = { + ok: ["M5 12h14", "m12 5 7 7-7 7"], + kupa: [ + "M10 14.66v1.626a2 2 0 0 1-.976 1.696A5 5 0 0 0 7 21.978", + "M14 14.66v1.626a2 2 0 0 0 .976 1.696A5 5 0 0 1 17 21.978", + "M18 9h1.5a1 1 0 0 0 0-5H18", + "M4 22h16", + "M6 9a6 6 0 0 0 12 0V3a1 1 0 0 0-1-1H7a1 1 0 0 0-1 1z", + "M6 9H4.5a1 1 0 0 1 0-5H6", + ], + kalkan: [ + "M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z", + "m9 12 2 2 4-4", + ], + belge: [ + "M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z", + "M14 2v5a1 1 0 0 0 1 1h5", + "M10 9H8", + "M16 13H8", + "M16 17H8", + ], + soru: [ + "M2.992 16.342a2 2 0 0 1 .094 1.167l-1.065 3.29a1 1 0 0 0 1.236 1.168l3.413-.998a2 2 0 0 1 1.099.092 10 10 0 1 0-4.777-4.719", + "M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3", + "M12 17h.01", + ], + trend: ["M16 7h6v6", "m22 7-8.5 8.5-5-5L2 17"], + konum: [ + "M20 10c0 4.993-5.539 10.193-7.399 11.799a1 1 0 0 1-1.202 0C9.539 20.193 4 14.993 4 10a8 8 0 0 1 16 0", + "M15 10a3 3 0 1 1-6 0 3 3 0 0 1 6 0", + ], + devlet: [ + "M10 18v-7", + "M11.119 2.205a2 2 0 0 1 1.762 0l7.84 3.846A.5.5 0 0 1 20.5 7h-17a.5.5 0 0 1-.22-.949z", + "M14 18v-7", + "M18 18v-7", + "M3 22h18", + "M6 18v-7", + ], + tik: ["M20 6 9 17l-5-5"], + yenile: [ + "M21 12a9 9 0 0 0-9-9 9.75 9.75 0 0 0-6.74 2.74L3 8", + "M3 3v5h5", + "M3 12a9 9 0 0 0 9 9 9.75 9.75 0 0 0 6.74-2.74L21 16", + "M16 16h5v5", + ], + gonder: ["M5 12h14", "m12 5 7 7-7 7"], + arama: ["m21 21-4.34-4.34", "M19 11a8 8 0 1 1-16 0 8 8 0 0 1 16 0"], + kullanici: ["M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2", "M16 7a4 4 0 1 1-8 0 4 4 0 0 1 8 0"], +}; + +export type IkonAdi = keyof typeof YOLLAR; + +export function Ikon({ + ad, + boy, + renk = "currentColor", + kalinlik = 2, + style, +}: { + ad: IkonAdi; + boy: number; + renk?: string; + kalinlik?: number; + style?: CSSProperties; +}) { + return ( + + {YOLLAR[ad].map((d) => ( + + ))} + + ); +} + +/** İmleç (mouse-pointer-2) — dolu, beyaz kenarlı. */ +export function Imlec({ boy, style }: { boy: number; style?: CSSProperties }) { + return ( + + + + ); +} diff --git a/src/imlec.tsx b/src/imlec.tsx new file mode 100644 index 0000000..637d5c6 --- /dev/null +++ b/src/imlec.tsx @@ -0,0 +1,216 @@ +import type { ReactNode } from "react"; +import { AbsoluteFill, Easing, interpolate, interpolateColors, useCurrentFrame, useVideoConfig } from "remotion"; +import { R, hash } from "./tema"; +import { vurusCarpani, type Vurus } from "./vurus"; + +/** + * Sahne içi imleç. v3'te imleç sürekli ekranda değil: yalnız bir şey + * yapılırken (yazı kutusuna tıklama, çip seçme, harita tarama, gönderme) + * belirir. Konumları sahnenin kendi koordinatlarında verilir; aynı yol + * fonksiyonunu kamera da okur ve imleci takip eder (cursor tracking). + * + * Geçtiği yerde sitenin ısı fırçası gibi piksel iz bırakır, tıkladığı yerde + * halka + piksel patlaması. + */ + +export type Durak = { f: number; x: number; y: number; g?: number; /** Bir önceki duraktan düz ve sabit hızla (takip edilen yol örnekleri için) */ dogrusal?: boolean }; + +const yolEgri = Easing.bezier(0.33, 0, 0.12, 1); + +/** Duraklar arasında el hareketi gibi kavisli yol; `f` kare. */ +export function imlecKonumu(duraklar: Durak[], f: number): [number, number] { + if (duraklar.length === 0) return [0, 0]; + const j = duraklar.findIndex((d) => d.f > f); + if (j === -1) { + const s = duraklar[duraklar.length - 1]; + return [s.x, s.y]; + } + if (j === 0) return [duraklar[0].x, duraklar[0].y]; + const a = duraklar[j - 1]; + const b = duraklar[j]; + if (b.dogrusal) { + const p = (f - a.f) / (b.f - a.f); + return [a.x + (b.x - a.x) * p, a.y + (b.y - a.y) * p]; + } + const g = b.g ?? 14; + const kalkis = Math.max(a.f, b.f - g); + if (f <= kalkis) return [a.x, a.y]; + const p = yolEgri(Math.min(1, (f - kalkis) / (b.f - kalkis))); + const mx = b.x - a.x; + const my = b.y - a.y; + const uz = Math.hypot(mx, my); + const kavis = Math.sin(Math.PI * p) * Math.min(90, uz * 0.12); + const nx = uz ? -my / uz : 0; + const ny = uz ? mx / uz : 0; + return [a.x + mx * p + nx * kavis, a.y + my * p + ny * kavis]; +} + +export function SahneImleci({ + duraklar, + tiklar = [], + gorunur, + boy = 72, +}: { + duraklar: Durak[]; + tiklar?: number[]; + /** [belirme, kaybolma] kareleri */ + gorunur: [number, number]; + boy?: number; +}) { + const frame = useCurrentFrame(); + const { width: W, height: H } = useVideoConfig(); + const op = interpolate(frame, [gorunur[0], gorunur[0] + 8, gorunur[1] - 8, gorunur[1]], [0, 1, 1, 0], { + extrapolateLeft: "clamp", + extrapolateRight: "clamp", + }); + const belir = interpolate(frame, [gorunur[0], gorunur[0] + 10], [0.6, 1], { extrapolateLeft: "clamp", extrapolateRight: "clamp", easing: Easing.out(Easing.back(2)) }); + const [x, y] = imlecKonumu(duraklar, frame); + const sx = Math.sin(frame / 13) * 1.6; + const sy = Math.cos(frame / 17) * 1.4; + + let bas = 1; + for (const t of tiklar) { + const d = frame - t; + if (d >= -2 && d <= 2) bas = Math.min(bas, 1 - 0.16 * ((d + 2) / 4)); + else if (d > 2 && d <= 9) bas = Math.min(bas, 0.84 + 0.16 * ((d - 2) / 7)); + } + + // Piksel iz: hızla orantılı, ızgaraya oturan kareler (0.7 sn'de soğur) + const G = boy * 0.36; + const hucreler = new Map(); + if (op > 0) { + for (let k = 1; k <= 21; k++) { + if (frame - k < gorunur[0]) break; + const [ax, ay] = imlecKonumu(duraklar, frame - k); + const [bx, by] = imlecKonumu(duraklar, frame - k - 1); + const v = Math.hypot(ax - bx, ay - by); + if (v < 5) continue; + const isi = (1 - k / 21) * Math.min(1, v / 24); + const adim = Math.max(1, Math.ceil(v / G)); + for (let s = 0; s < adim; s++) { + const qx = ax + ((bx - ax) * s) / adim; + const qy = ay + ((by - ay) * s) / adim; + const an = `${Math.floor(qx / G)},${Math.floor(qy / G)}`; + hucreler.set(an, Math.max(hucreler.get(an) ?? 0, isi)); + } + } + } + const dot = G * 0.74; + const iz: ReactNode[] = []; + hucreler.forEach((isi, an) => { + const [cx, cy] = an.split(",").map(Number); + iz.push( + , + ); + }); + + const patlama: ReactNode[] = []; + for (const t of tiklar) { + const d = frame - t; + if (d < 0 || d > 24) continue; + const [cx, cy] = imlecKonumu(duraklar, t); + const p = Easing.out(Easing.cubic)(Math.min(1, d / 18)); + patlama.push(); + const Gp = boy * 0.2; + for (let n = 0; n < 10; n++) { + const aci = (n / 10) * Math.PI * 2 + hash(t, n, 3) * 0.6; + const uz = (18 + boy * 1.3 * (0.6 + 0.4 * hash(t, n, 5))) * p; + const qx = Math.round((cx + Math.cos(aci) * uz) / Gp) * Gp; + const qy = Math.round((cy + Math.sin(aci) * uz) / Gp) * Gp; + const b = boy * 0.17 * (1 - d / 26); + patlama.push(); + } + } + + const tip = (4.04 / 24) * boy; + return ( + + + {iz} + {patlama} + + {op > 0 ? ( +
+ + + +
+ ) : null} +
+ ); +} + +/* ------------------------------------------------------------------ */ +/* İmleci takip eden kamera */ +/* ------------------------------------------------------------------ */ + +export type TakipAnahtar = { f: number; s: number; t: number }; + +function ara(list: TakipAnahtar[], f: number, k: "s" | "t") { + if (f <= list[0].f) return list[0][k]; + for (let i = 0; i < list.length - 1; i++) { + const a = list[i]; + const b = list[i + 1]; + if (f <= b.f) return interpolate(f, [a.f, b.f], [a[k], b[k]], { easing: Easing.bezier(0.65, 0, 0.35, 1) }); + } + return list[list.length - 1][k]; +} + +/** + * Kamera `t` oranında imlece kilitlenir (0 = kadraj ortası, 1 = imleç tam + * ortada) ve `s` kadar yaklaşır. İmleç konumu birkaç karelik gecikmeyle + * yumuşatılır: kamera operatörü eli gibi hafif geriden gelir. + */ +export function TakipKamera({ + nokta, + anahtarlar, + vuruslar, + children, +}: { + nokta: (f: number) => [number, number]; + anahtarlar: TakipAnahtar[]; + vuruslar?: Vurus[]; + children: ReactNode; +}) { + const frame = useCurrentFrame(); + const { width: W, height: H } = useVideoConfig(); + const s = ara(anahtarlar, frame, "s") * vurusCarpani(frame, vuruslar); + const t = ara(anahtarlar, frame, "t"); + let px = 0; + let py = 0; + let ag = 0; + for (let k = 0; k < 8; k++) { + const w = 1 - k / 8; + const [qx, qy] = nokta(frame - k); + px += qx * w; + py += qy * w; + ag += w; + } + px /= ag; + py /= ag; + const x = (px - W / 2) * t; + const y = (py - H / 2) * t; + const sx = Math.sin(frame / 47) * 4 + Math.sin(frame / 19) * 1.2; + const sy = Math.cos(frame / 53) * 3; + return ( + {children} + ); +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..e78af8b --- /dev/null +++ b/src/index.ts @@ -0,0 +1,4 @@ +import { registerRoot } from "remotion"; +import { Root } from "./Root"; + +registerRoot(Root); diff --git a/src/kinetik.tsx b/src/kinetik.tsx new file mode 100644 index 0000000..65d09dc --- /dev/null +++ b/src/kinetik.tsx @@ -0,0 +1,397 @@ +import { measureText } from "@remotion/layout-utils"; +import type { CSSProperties, ReactNode } from "react"; +import { useMemo } from "react"; +import { Easing, interpolate, spring, useCurrentFrame, useVideoConfig } from "remotion"; +import { getPathRideFade, getPathRidePoint, samplePath } from "./remocn/path-ride"; +import { FONT, R } from "./tema"; + +/** + * remocn.dev kinetik yazı efektlerinin KolayTercih uyarlamaları. Hareket + * matematiği remocn bileşenlerinden (src/remocn/*) alındı; görünüm sitenin + * diline çevrildi: + * - yumuşak degrade yerine logodaki üç karenin renkleriyle **basamaklı + * (piksel) bantlar** — mavi #3b82f6, şeftali #fdc7a7, turuncu #ff5a00; + * - açılma kenarları kare kare ilerler (kuantize), düzgün kaymaz; + * - yazı tipleri Cal Sans / Bricolage, zemin slate-50. + */ + +const PIKSEL_BANT = (aci = 100, bas = 38) => + `linear-gradient(${aci}deg, transparent ${bas}%, ${R.logoSeftali} ${bas}% ${bas + 6}%, ${R.logoTuruncu} ${bas + 6}% ${bas + 12}%, ${R.logoMavi} ${bas + 12}% ${bas + 18}%, transparent ${bas + 18}%)`; + +/** 0..1 ilerlemeyi n basamağa böler: kenar pürüzsüz değil, piksel piksel ilerler. */ +const basamak = (p: number, n: number) => Math.floor(p * n) / n; + +const clamp = { extrapolateLeft: "clamp" as const, extrapolateRight: "clamp" as const }; + +/* ------------------------------------------------------------------ */ +/* Sheen Slide In — remocn/sheen-slide-in */ +/* ------------------------------------------------------------------ */ +/** + * Başlık sağdan üstel bir oturuşla kayar (remocn'un travelEm eğrisi), üstünden + * logodaki üç renkten oluşan piksel bir parlama bandı geçer. + */ +export function PikselSheen({ + children, + gecikme = 0, + fontSize = 96, + renk = R.slate900, + style, +}: { + children: ReactNode; + gecikme?: number; + fontSize?: number; + renk?: string; + style?: CSSProperties; +}) { + const frame = Math.max(0, useCurrentFrame() - gecikme); + const oturus = 1.8 * Math.exp(-frame / 2.5) + 0.28 * Math.exp(-frame / 12); // remocn SETTLE/CREEP + const opak = interpolate(frame, [0, 4], [0, 1], clamp); + const bantOpak = interpolate(frame, [0, 4], [0, 1], clamp) * interpolate(frame, [44, 54], [1, 0], clamp); + const bantKonum = basamak(interpolate(frame, [0, 44], [0, 1], { ...clamp, easing: Easing.bezier(0.42, 0, 0.58, 1) }), 14); + const yazi: CSSProperties = { fontFamily: FONT.baslik, fontWeight: 700, fontSize, lineHeight: 1.08, letterSpacing: "0.005em", whiteSpace: "nowrap" }; + return ( + + {children} + + {children} + + + ); +} + +/* ------------------------------------------------------------------ */ +/* Tracking In — remocn/tracking-in */ +/* ------------------------------------------------------------------ */ +/** Harf aralığı açık başlar, yaylanarak kapanır; bulanıklık çözülür. */ +export function IzAc({ + children, + gecikme = 0, + fontSize = 200, + renk = R.slate900, + baslangicAralik = 0.5, + style, +}: { + children: ReactNode; + gecikme?: number; + fontSize?: number; + renk?: string; + baslangicAralik?: number; + style?: CSSProperties; +}) { + const frame = useCurrentFrame() - gecikme; + const { fps } = useVideoConfig(); + const t = spring({ frame, fps, config: { damping: 18, stiffness: 90 } }); + return ( + + {children} + + ); +} + +/* ------------------------------------------------------------------ */ +/* Gradient Scale Cut Text — remocn/gradient-scale-cut-text */ +/* ------------------------------------------------------------------ */ +/** + * Dev bir kelime, piksel basamaklarla açılan bir maskeyle ve logo renklerinde + * sert geçişli bir bantla ekranı süpürür; `kesme` karesinde sert bir kesmeyle + * kompakt içeriğe (ör. logo) döner, kompakt olan bulanıklıktan oturur. + */ +export function PikselDevKesme({ + metin, + devBoy, + kesme = 14, + gecikme = 0, + children, +}: { + metin: string; + devBoy: number; + kesme?: number; + gecikme?: number; + children: ReactNode; +}) { + const frame = useCurrentFrame() - gecikme; + const { width } = useVideoConfig(); + const acilma = basamak(interpolate(frame, [0, 30], [0.02, 1], { ...clamp, easing: Easing.bezier(0.22, 0.74, 0.24, 1) }), 16); + const kenar = acilma * 104; + const maske = `linear-gradient(90deg, #000 0%, #000 ${kenar - 2}%, transparent ${kenar}%)`; + const dev = interpolate(frame, [0, kesme - 1], [0, 1], { ...clamp, easing: Easing.out(Easing.cubic) }); + const otur = interpolate(frame, [kesme, kesme + 8], [0, 1], { ...clamp, easing: Easing.out(Easing.cubic) }); + const katman: CSSProperties = { fontFamily: FONT.vurgu, fontWeight: 600, letterSpacing: "-0.05em", fontSize: devBoy, lineHeight: 1, whiteSpace: "nowrap" }; + if (frame < kesme) { + return ( +
+
+ {metin} + + {metin} + +
+
+ ); + } + return ( +
+ {children} +
+ ); +} + +/* ------------------------------------------------------------------ */ +/* Outline Fill Track Text — remocn/outline-fill-track-text */ +/* ------------------------------------------------------------------ */ +/** + * Öndeki kelime maskeden yükselir; ikisi birlikte tek bir kaydırma çizgisinde + * ilerlerken değer önce kontur olarak gelir, sonra soldan sağa **piksel + * basamaklarla** dolar. + */ +export function PikselKonturDolgu({ + on, + deger, + fontSize, + gecikme = 0, + genislik, + dolguRenk = R.primary, +}: { + on: string; + deger: string; + fontSize: number; + gecikme?: number; + genislik: number; + dolguRenk?: string; +}) { + const frame = useCurrentFrame() - gecikme; + const olc = (m: string) => measureText({ text: m, fontFamily: "Cal Sans", fontSize, fontWeight: "700" }).width; + const onW = useMemo(() => olc(on), [on, fontSize]); // eslint-disable-line react-hooks/exhaustive-deps + const degerW = useMemo(() => olc(deger), [deger, fontSize]); // eslint-disable-line react-hooks/exhaustive-deps + const bosluk = fontSize * 0.35; + const giris = interpolate(frame, [0, 16], [0, 1], { ...clamp, easing: Easing.bezier(0.16, 0.84, 0.24, 1) }); + const iz = interpolate(frame, [16, 60], [0, 1], { ...clamp, easing: Easing.bezier(0.42, 0, 0.58, 1) }); + const dolgu = basamak(interpolate(frame, [26, 62], [0, 1], clamp), 12); + const toplam = onW + bosluk + degerW; + // Başta öndeki kelime ortada; sonda ikisi birlikte ortalanır + const basX = genislik / 2 - onW / 2; + const sonX = genislik / 2 - toplam / 2; + const x = basX + (sonX - basX) * iz; + const kenar = giris * 112 - 8; + const maske = `linear-gradient(180deg, #000 0%, #000 ${Math.max(0, kenar - 10)}%, transparent ${Math.min(100, kenar + 10)}%)`; + const yazi: CSSProperties = { fontFamily: FONT.baslik, fontWeight: 700, fontSize, lineHeight: 1, whiteSpace: "nowrap", position: "absolute", top: 0 }; + return ( +
+ {on} + + {deger} + {deger} + +
+ ); +} + +/* ------------------------------------------------------------------ */ +/* Perspective Marquee — remocn/perspective-marquee */ +/* ------------------------------------------------------------------ */ +/** + * 3B eğik, sonsuz akan şerit; ufka doğru kayan öğeler alan derinliğiyle + * bulanıklaşır. Öğelerin arasında logodaki gibi üç kare durur. + */ +export function PikselMarquee({ + ogeler, + fontSize = 84, + hiz = 3, + donY = -26, + donX = 8, + renk = R.slate900, + soluk = R.slate50, + ters = false, + kaydir = 0, +}: { + ogeler: string[]; + fontSize?: number; + hiz?: number; + donY?: number; + donX?: number; + renk?: string; + soluk?: string; + ters?: boolean; + kaydir?: number; +}) { + const frame = useCurrentFrame(); + const { width } = useVideoConfig(); + const ara = fontSize * 1.4; + const genislikler = useMemo( + () => ogeler.map((o) => measureText({ text: o, fontFamily: "Cal Sans", fontSize, fontWeight: "700" }).width + ara), + [ogeler, fontSize, ara], + ); + const set = genislikler.reduce((a, b) => a + b, 0); + const kayma = (((frame * hiz + kaydir) % set) + set) % set; + const offset = ters ? -set + kayma : -kayma; + const dizi = [...ogeler, ...ogeler, ...ogeler]; + let x = offset; + return ( +
+
+
+ {dizi.map((o, i) => { + const w = genislikler[i % ogeler.length]; + const merkez = x + w / 2; + x += w; + const uzak = Math.min(1, Math.abs((merkez - width / 2) / (width / 2))); + return ( + + {o} + + {[R.logoMavi, R.logoSeftali, R.logoTuruncu].map((c) => ( + + ))} + + + ); + })} +
+
+
+
+ ); +} + +/* ------------------------------------------------------------------ */ +/* Path Ride — remocn/path-ride */ +/* ------------------------------------------------------------------ */ +/** + * Bir eğri kendini piksel noktalarla çizer, yazı başının arkasından eğriye + * binerek gelir; sonra eğri gerilip düz bir taban çizgisine döner. Noktaların + * rengi yol boyunca risk diline göre değişir (hayal → dengeli → güvenli); + * uçlarda metin etiketi durur, renk tek başına anlam taşımaz. + */ +export function PikselYolSurus({ + metin, + yol, + fontSize, + genislik, + yukseklik, + gecikme = 0, + taban, +}: { + metin: string; + /** SVG yol verisi, kabın px koordinatlarında */ + yol: string; + fontSize: number; + genislik: number; + yukseklik: number; + gecikme?: number; + /** Düzleşince yazının oturacağı taban çizgisi (y, px) */ + taban: number; +}) { + const frame = useCurrentFrame() - gecikme; + const ornek = useMemo(() => samplePath(yol, 400), [yol]); + const harfler = Array.from(metin); + const ilerleme = harfler.map((c) => measureText({ text: c, fontFamily: "Cal Sans", fontSize, fontWeight: "700" }).width); + // remocn zamanlaması: çiz 0–40, düzleş 50–68, sil 52–70 + const ease = Easing.inOut(Easing.cubic); + const L = ornek.length; + const bas = interpolate(frame, [0, 40], [0, L], { ...clamp, easing: ease }); + const kuyruk = interpolate(frame, [52, 70], [0, L], { ...clamp, easing: ease }); + const k = interpolate(frame, [50, 68], [0, 1], { ...clamp, easing: ease }); + let acc = 0; + const merkezler = ilerleme.map((a) => { + const c = acc + a / 2; + acc += a; + return c; + }); + const yaziW = acc; + const ONDE = 0.3 * fontSize; + const duz = { start: L - ONDE - yaziW, left: genislik / 2 - yaziW / 2, baseline: taban }; + const RENKLER = [R.riskli, R.azRiskli, R.guvenli]; + const noktalar: ReactNode[] = []; + const ADIM = fontSize * 0.28; + for (let s = kuyruk; s <= bas; s += ADIM) { + const p = getPathRidePoint(ornek, s, k, duz); + const u = s / L; + const renk = RENKLER[Math.min(2, Math.floor(u * 3))]; + const boy = fontSize * 0.16; + noktalar.push(); + } + return ( +
+ + {noktalar} + + {harfler.map((c, j) => { + const arc = bas - ONDE - (yaziW - merkezler[j]); + const fade = getPathRideFade(arc, fontSize); + if (fade <= 0) return null; + const p = getPathRidePoint(ornek, arc, k, duz, fontSize * 0.12); + return ( + + {c} + + ); + })} +
+ ); +} diff --git a/src/logo.tsx b/src/logo.tsx new file mode 100644 index 0000000..813f8e1 --- /dev/null +++ b/src/logo.tsx @@ -0,0 +1,125 @@ +import { Easing, useCurrentFrame } from "remotion"; +import { FPS, R, zipla } from "./tema"; + +/** + * Sitedeki AnimasyonluMark'ın (src/components/animasyonlu-mark.tsx) kare + * kare karşılığı. Motion'ın keyframe dizileri burada zaman → değer + * fonksiyonuna çevrildi; süreler, duraklar ve eğriler birebir aynı. + * + * - "rota": her kare sıradakinin yerine süzülür (mavi → şeftalinin yeri, + * şeftali → turuncunun, turuncu → mavinin), yolun ortasında 0.84'e küçülüp + * 0.6'ya solar; kalkışlar 0.2 sn kademeli, tam tur 3.6 sn. + * - "nabiz": kareler yerinde, 120° faz farkıyla 0.82'ye küçülüp büyür (1.8 sn). + */ + +const POZISYONLAR = [ + [27, 56], + [91, 29], + [91, 95], +] as const; +const KARELER = [ + { boyut: 57, renk: R.logoMavi, baslangic: 0 }, + { boyut: 56, renk: R.logoSeftali, baslangic: 1 }, + { boyut: 56, renk: R.logoTuruncu, baslangic: 2 }, +] as const; + +const ROTA_SURE = 3.6; +const ROTA_GECIKME_ADIMI = 0.2; +const KONUM_ZAMANLARI = [0, 0.25, 1 / 3, 0.583, 2 / 3, 0.917, 1]; +const NABIZ_ZAMANLARI = [0, 0.125, 0.25, 1 / 3, 0.458, 0.583, 2 / 3, 0.792, 0.917, 1]; +const NABIZ_OLCEK = [1, 0.84, 1, 1, 0.84, 1, 1, 0.84, 1, 1]; +const NABIZ_OPAKLIK = [1, 0.6, 1, 1, 0.6, 1, 1, 0.6, 1, 1]; + +const easeInOut = Easing.bezier(0.42, 0, 0.58, 1); // Motion "easeInOut" +const sine = Easing.bezier(0.37, 0, 0.63, 1); + +/** Motion keyframe dizisi: segment başına easing ile ara değer. */ +function kf(p: number, zamanlar: number[], degerler: number[], ease = easeInOut) { + if (p <= 0) return degerler[0]; + if (p >= 1) return degerler[degerler.length - 1]; + let i = 0; + while (i < zamanlar.length - 2 && p > zamanlar[i + 1]) i++; + const t = (p - zamanlar[i]) / (zamanlar[i + 1] - zamanlar[i] || 1); + return degerler[i] + (degerler[i + 1] - degerler[i]) * ease(t); +} + +function rotaDegerleri(baslangic: number, eksen: 0 | 1) { + const [p0, p1, p2] = [0, 1, 2].map((adim) => POZISYONLAR[(baslangic + adim) % 3][eksen]); + return [p0, p1, p1, p2, p2, p0, p0]; +} + +export function LogoMark({ + boy, + giris = 0, + beyaz = false, + varyant = "rota", + rotaBas, + rotaTur = 1, + turuncu = false, + girisYok = false, +}: { + boy: number; + /** Karelerin sahneye iniş karesi */ + giris?: number; + /** Mavi zeminde mavi kare beyaza döner */ + beyaz?: boolean; + varyant?: "rota" | "nabiz" | "duruk"; + /** Rota koreografisinin başladığı kare; verilmezse iniş bitince başlar */ + rotaBas?: number; + /** Kaç tam tur dönsün; sonra kareler yerinde durur */ + rotaTur?: number; + /** Danışman başlığındaki gibi tüm kareler turuncu */ + turuncu?: boolean; + girisYok?: boolean; +}) { + const frame = useCurrentFrame(); + const bas = rotaBas ?? giris + 22; + return ( + + {KARELER.map((k, i) => { + // İniş: sitede yok; sahneye giriş için kare kendi yerinde yaylanarak belirir + const inis = girisYok ? 1 : zipla(frame, giris + i * 5); + let [x, y] = POZISYONLAR[k.baslangic] as unknown as [number, number]; + let olcek = 1; + let opak = 1; + const t = (frame - bas) / FPS; + if (varyant === "rota") { + const gecikme = i * ROTA_GECIKME_ADIMI; + const yerel = t - gecikme; + if (yerel > 0 && yerel < ROTA_SURE * rotaTur) { + const p = (yerel % ROTA_SURE) / ROTA_SURE; + x = kf(p, KONUM_ZAMANLARI, rotaDegerleri(k.baslangic, 0)); + y = kf(p, KONUM_ZAMANLARI, rotaDegerleri(k.baslangic, 1)); + olcek = kf(p, NABIZ_ZAMANLARI, NABIZ_OLCEK); + opak = kf(p, NABIZ_ZAMANLARI, NABIZ_OPAKLIK); + } + } else if (varyant === "nabiz") { + const yerel = frame / FPS - [0, 0.6, 1.2][i]; + if (yerel > 0) { + const p = (yerel % 1.8) / 1.8; + olcek = kf(p, [0, 0.5, 1], [1, 0.82, 1], sine); + opak = kf(p, [0, 0.5, 1], [1, 0.55, 1], sine); + } + } + const renk = turuncu ? R.turuncu : beyaz && i === 0 ? R.beyaz : k.renk; + return ( + + ); + })} + + ); +} diff --git a/src/ortak.tsx b/src/ortak.tsx new file mode 100644 index 0000000..9c1b7a8 --- /dev/null +++ b/src/ortak.tsx @@ -0,0 +1,190 @@ +import type { CSSProperties, ReactNode } from "react"; +import { AbsoluteFill, useCurrentFrame, useVideoConfig } from "remotion"; +import { Ikon } from "./ikonlar"; +import { Eyebrow, KelimeKelime } from "./piksel"; +import { FONT, R, yay } from "./tema"; + +export const useDuzen = () => { + const { width, height } = useVideoConfig(); + return { dikey: height > width, w: width, h: height }; +}; + +/** Sahne zemini: ana sayfanın slate-50'si. */ +export function Sahne({ + children, + zemin = R.slate50, + style, +}: { + children: ReactNode; + zemin?: string; + style?: CSSProperties; +}) { + return ( + + {children} + + ); +} + +/** Bölüm başlık bloğu (DESIGN.md §5.1): rozet → h2 → giriş paragrafı. */ +export function BaslikBlogu({ + rozet, + baslik, + aciklama, + vurgu, + hiza = "center", + gecikme = 0, + genis = false, +}: { + rozet: string; + baslik: string; + aciklama?: string; + vurgu?: string[]; + hiza?: "center" | "left"; + gecikme?: number; + /** Başlık tek satıra sığsın diye geniş sütun (yatayda dar sahneler için) */ + genis?: boolean; +}) { + const frame = useCurrentFrame(); + const { dikey } = useDuzen(); + const r = yay(frame, gecikme, 16); + const p = yay(frame, gecikme + 16, 20); + return ( +
+
+ {rozet} +
+

+ +

+ {aciklama ? ( +

+ {aciklama} +

+ ) : null} +
+ ); +} + +/** Sade kutu: rounded-2xl border border-slate-200 bg-white (×2.2). */ +export function Kutu({ children, style }: { children: ReactNode; style?: CSSProperties }) { + return ( +
+ {children} +
+ ); +} + +/** Turuncu CTA — metin önce, ArrowRight sonda; Sparkles yok (AGENTS.md). */ +export function TuruncuCta({ + metin, + boy = 1, + basili = 0, + style, +}: { + metin: string; + boy?: number; + /** 0→1: basılma anı (active:scale-[0.97] + hover:bg-orange-600) */ + basili?: number; + style?: CSSProperties; +}) { + return ( +
0.5 ? R.turuncuKoyu : R.turuncu, + color: R.beyaz, + fontFamily: FONT.govde, + fontWeight: 600, + fontSize: 40 * boy, + transform: `scale(${1 - 0.03 * basili})`, + whiteSpace: "nowrap", + ...style, + }} + > + {metin} + +
+ ); +} + +/** Risk etiketi (RiskEtiketi): nokta + metin, renk asla tek başına. */ +export function RiskEtiketi({ + risk, + olcek = 2.4, +}: { + risk: "guvenli" | "az-riskli" | "riskli"; + olcek?: number; +}) { + const t = { + guvenli: { nokta: R.guvenli, yazi: R.guvenliYazi, etiket: "Güvenli" }, + "az-riskli": { nokta: R.azRiskli, yazi: R.azRiskliYazi, etiket: "Az riskli" }, + riskli: { nokta: R.riskli, yazi: R.riskliYazi, etiket: "Riskli" }, + }[risk]; + return ( + + + {t.etiket} + + ); +} diff --git a/src/piksel.tsx b/src/piksel.tsx new file mode 100644 index 0000000..b768dc8 --- /dev/null +++ b/src/piksel.tsx @@ -0,0 +1,218 @@ +import type { CSSProperties, ReactNode } from "react"; +import { + AbsoluteFill, + interpolateColors, + useCurrentFrame, + useVideoConfig, +} from "remotion"; +import { FONT, R, ara, hash, opaklik, yay } from "./tema"; + +/* ------------------------------------------------------------------ */ +/* PixelMark + SectionEyebrow — sitedeki rozetin video karşılığı */ +/* ------------------------------------------------------------------ */ + +/** 2×2 piksel imzası; pikseller saat yönünde sırayla ısınıp söner (animate-pixel-isi). */ +export function PixelMark({ boy }: { boy: number }) { + const frame = useCurrentFrame(); + const isi = (gecikme: number) => { + const t = ((frame / 30 - gecikme) % 2.4 + 2.4) % 2.4; + return t < 0.6 ? Math.sin((t / 0.6) * Math.PI) : 0; + }; + const px = (x: number, y: number, op: number, gecikme: number) => { + const h = isi(gecikme); + return ( + + ); + }; + return ( + + {px(0, 0, 0.35, 0)} + + {px(10, 10, 0.18, 1.2)} + {px(0, 10, 0.6, 1.8)} + + ); +} + +/** Rozet: rounded-full border-slate-200 bg-white px-3.5 py-1.5 text-xs font-semibold text-primary (×ölçek). */ +export function Eyebrow({ children, olcek = 2.4 }: { children: ReactNode; olcek?: number }) { + return ( + + + {children} + + ); +} + +/** "Kolay" Bricolage 300 + "Tercih" 600, tracking -0.05em (logo yazısı). */ +export function Wordmark({ boy, renk = R.murekkep }: { boy: number; renk?: string }) { + return ( + + Kolay + Tercih + + ); +} + +/** + * CTA bandının zemini (PixelMasonry): büyüklü küçüklü, makro hücre içinde + * kaymış kareler, yalnız kenar çerçevesinde; orta boş. + */ +export function PikselMasonry({ + seed = 8, + renk = R.beyaz, + giris = 0, + kenar = 0.3, + style, +}: { + seed?: number; + renk?: string; + giris?: number; + kenar?: number; + style?: CSSProperties; +}) { + const frame = useCurrentFrame(); + const { width: w, height: h } = useVideoConfig(); + const CELL = 22; + const MAKRO = 4; + const kisa = Math.min(w, h); + const kareler: ReactNode[] = []; + for (let my = 0; my < h / CELL / MAKRO; my++) { + for (let mx = 0; mx < w / CELL / MAKRO; mx++) { + const ox = mx * MAKRO; + const oy = my * MAKRO; + const cx = (ox + MAKRO / 2) * CELL; + const cy = (oy + MAKRO / 2) * CELL; + const k = Math.min(cx, w - cx, cy, h - cy) / kisa; + if (k >= kenar) continue; + const yogun = 0.06 + 0.5 * (1 - k / kenar) ** 1.6; + if (hash(mx, my, seed) > yogun) continue; + const r = hash(mx, my, seed + 7); + const n = r < 0.5 ? 2 : r < 0.85 ? 3 : 4; + const serbest = MAKRO - n; + const kx = ox + Math.floor(hash(mx, my, seed + 11) * (serbest + 1)); + const ky = oy + Math.floor(hash(mx, my, seed + 13) * (serbest + 1)); + const vurgu = n === 2 && hash(mx, my, seed + 99) > 0.9; + const ins = CELL * 0.13; + const t0 = giris + k * 40 + hash(mx, my, seed + 3) * 10; + const s = ara(frame, [t0, t0 + 12], [0, 1]); + const boy = (n * CELL - 2 * ins) * s; + const nabiz = vurgu ? 0.55 + 0.3 * Math.sin(frame / 9 + mx) : 1; + kareler.push( + , + ); + } + } + return ( + + + {kareler} + + + ); +} + +/** Kart köşesindeki üç piksel (PixelCorner). */ +export function PixelCorner({ boy }: { boy: number }) { + const p = (x: number, y: number, op: number) => ( + + ); + return ( + + {p(20, 0, 0.5)} + {p(10, 0, 0.14)} + {p(20, 10, 0.26)} + + ); +} + +/** Kelime kelime beliren başlık — her kelime kendi yayıyla yukarı oturur. */ +export function KelimeKelime({ + metin, + gecikme = 0, + aralik = 3, + style, + vurgu, +}: { + metin: string; + gecikme?: number; + aralik?: number; + style?: CSSProperties; + /** Vurgulanacak kelimeler → italik mavi Bricolage (hero'daki span) */ + vurgu?: string[]; +}) { + const frame = useCurrentFrame(); + const kelimeler = metin.split(" "); + return ( + + {kelimeler.map((k, i) => { + const s = yay(frame, gecikme + i * aralik, 18); + const vurgulu = vurgu?.includes(k.replace(/[.,!?]/g, "")); + return ( + + {k} + + ); + })} + + ); +} diff --git a/src/remocn/gradient-scale-cut-text.tsx b/src/remocn/gradient-scale-cut-text.tsx new file mode 100644 index 0000000..95c4db2 --- /dev/null +++ b/src/remocn/gradient-scale-cut-text.tsx @@ -0,0 +1,215 @@ +"use client"; + +import { Easing, interpolate, useCurrentFrame } from "remotion"; + +export interface GradientScaleCutTextProps { + text?: string; + giantFontSize?: number; + compactFontSize?: number; + fontWeight?: number; + gradientStart?: string; + gradientEnd?: string; + ghostColor?: string; + backgroundColor?: string; + anchorOffsetX?: number; + giantTravel?: number; + settleTravel?: number; + cutFrame?: number; + revealSoftness?: number; + compactBlur?: number; + speed?: number; + className?: string; +} + +export const gradientScaleCutTextLength = 36; + +const REVEAL_END = 33; +const SETTLE_FRAMES = 8; +const GIANT_Y = -28; +const GIANT_X_CORRECTION = -48; +const COMPACT_Y = 5; +const COMPACT_X_SCALE = 0.87; +const COMPACT_START_X_CORRECTION = 28; +const FONT_FAMILY = "Arial, Helvetica, sans-serif"; +const clamp = { + extrapolateLeft: "clamp" as const, + extrapolateRight: "clamp" as const, +}; + +interface TextLayerProps { + text: string; + fontSize: number; + fontWeight: number; + gradientStart: string; + gradientEnd: string; + ghostColor: string; + maskImage: string; +} + +function TextLayer({ + text, + fontSize, + fontWeight, + gradientStart, + gradientEnd, + ghostColor, + maskImage, +}: TextLayerProps) { + const textStyle = { + display: "block", + fontFamily: FONT_FAMILY, + fontSize, + fontWeight, + letterSpacing: 0, + lineHeight: 1.12, + whiteSpace: "nowrap" as const, + }; + + return ( +
+ {text} + + {text} + +
+ ); +} + +export function GradientScaleCutText({ + text = "Introducing", + giantFontSize = 520, + compactFontSize = 132, + fontWeight = 700, + gradientStart = "#f04a14", + gradientEnd = "#f3f1f1", + ghostColor = "#170b0a", + backgroundColor = "#000000", + anchorOffsetX = -16, + giantTravel = 400, + settleTravel = 160, + cutFrame = 13, + revealSoftness = 14, + compactBlur = 12, + speed = 1, + className, +}: GradientScaleCutTextProps) { + const frame = useCurrentFrame() * Math.max(speed, 0); + const safeCutFrame = Math.max(cutFrame, 1); + const safeGiantFontSize = Math.max(giantFontSize, 1); + const safeCompactFontSize = Math.max(compactFontSize, 1); + const safeSoftness = Math.max(revealSoftness, 0); + const safeBlur = Math.max(compactBlur, 0); + const revealProgress = interpolate(frame, [0, REVEAL_END], [0.02, 1], { + ...clamp, + easing: Easing.bezier(0.22, 0.74, 0.24, 1), + }); + const revealEdge = revealProgress * (100 + safeSoftness / 2); + const revealSolid = Math.max(0, revealEdge - safeSoftness / 2); + const revealFade = revealEdge + safeSoftness / 2; + const maskImage = `linear-gradient(90deg, #000 0%, #000 ${revealSolid}%, transparent ${revealFade}%, transparent ${revealFade}%)`; + const giantProgress = interpolate( + frame, + [0, Math.max(safeCutFrame - 1, 1)], + [0, 1], + { + ...clamp, + easing: Easing.out(Easing.cubic), + }, + ); + const settleProgress = interpolate( + frame, + [safeCutFrame, safeCutFrame + SETTLE_FRAMES], + [0, 1], + { + ...clamp, + easing: Easing.out(Easing.cubic), + }, + ); + const moveProgress = interpolate( + frame, + [ + safeCutFrame + 1, + safeCutFrame + 2, + safeCutFrame + 4, + safeCutFrame + SETTLE_FRAMES, + ], + [0, 0.58, 0.875, 1], + clamp, + ); + const showGiant = frame < safeCutFrame; + const compactX = + interpolate(moveProgress, [0, 1], [settleTravel, 0], clamp) + + COMPACT_START_X_CORRECTION * (1 - moveProgress); + const compactBlurValue = safeBlur * (1 - settleProgress); + + return ( +
+ {showGiant ? ( +
+ +
+ ) : ( +
+ +
+ )} +
+ ); +} diff --git a/src/remocn/lens-zoom.tsx b/src/remocn/lens-zoom.tsx new file mode 100644 index 0000000..4636202 --- /dev/null +++ b/src/remocn/lens-zoom.tsx @@ -0,0 +1,535 @@ +"use client"; + +import type { + TransitionPresentation, + TransitionPresentationComponentProps, +} from "@remotion/transitions"; +import type { ReactNode } from "react"; +import { useId } from "react"; +import { Easing, interpolate, useCurrentFrame, useVideoConfig } from "remotion"; + +export const LENS_ZOOM_DURATION_IN_FRAMES = 28; + +export const SHAKE_STOPS = [-9 / 11, -3 / 11, 4 / 11, 8 / 11] as const; + +export const LENS_ZOOM_EASE_IN = Easing.bezier(0.54, 0, 0.82, 0.8); +export const LENS_ZOOM_EASE_OUT = Easing.bezier(0.18, 0.2, 0.46, 1); + +export type LensZoomProps = { + inScaleFrom?: number; + inScaleTo?: number; + inFovFrom?: number; + inFovTo?: number; + inBlurFrom?: number; + inBlurTo?: number; + inRotationFrom?: number; + inRotationTo?: number; + outScaleFrom?: number; + outScaleTo?: number; + outFovFrom?: number; + outFovTo?: number; + outBlurFrom?: number; + outBlurTo?: number; + outRotationFrom?: number; + outRotationTo?: number; + anticipateScale?: number; + anticipatePortion?: number; + lensSteps?: number; + blurSamples?: number; + blurScaleGain?: number; + blurFade?: number; + shakeAmount?: number; + shakeFrequency?: number; + shakeCyclesPerUnit?: number; + shakeTranslatePx?: number; + shakeRotateDeg?: number; + redScale?: number; + greenScale?: number; + blueScale?: number; + letterboxAspect?: number; + width?: number; + height?: number; + distortionCenterX?: number; + distortionCenterY?: number; +}; + +function hashed(seed: number, i: number): number { + let t = Math.imul(i ^ seed, 0x27d4eb2d); + t ^= t >>> 15; + t = Math.imul(t, 0x85ebca6b); + t ^= t >>> 13; + return ((t >>> 0) / 4294967296) * 2 - 1; +} + +function valueNoise(seed: number, t: number): number { + const i = Math.floor(t); + const f = t - i; + const u = f * f * (3 - 2 * f); + return hashed(seed, i) * (1 - u) + hashed(seed, i + 1) * u; +} + +function fovHalf(fov: number): number { + return (Math.min(fov, 178) / 2) * (Math.PI / 180); +} + +function lensMagnification(r: number, fov: number): number { + if (fov < 0.01) return 1; + const theta = fovHalf(fov); + const peak = Math.tan(theta) / theta; + if (r <= 0) return peak; + const a = r * theta; + if (a >= Math.PI / 2 - 1e-3) return 1; + const rSource = Math.tan(a) / Math.tan(theta); + if (rSource <= 1e-6) return peak; + return Math.max(1, r / rSource); +} + +function radiusForMagnification(target: number, fov: number): number { + let lo = 0; + let hi = 1; + for (let i = 0; i < 24; i++) { + const mid = (lo + hi) / 2; + if (lensMagnification(mid, fov) > target) lo = mid; + else hi = mid; + } + return (lo + hi) / 2; +} + +function ringClip( + rInner: number, + rOuter: number | null, + w: number, + h: number, + cx: number, + cy: number, +): string { + const circle = (r: number, clockwise: boolean) => { + const s = clockwise ? 1 : 0; + return `M ${cx + r} ${cy} A ${r} ${r} 0 1 ${s} ${cx - r} ${cy} A ${r} ${r} 0 1 ${s} ${cx + r} ${cy} Z`; + }; + const outer = + rOuter === null + ? `M 0 0 L ${w} 0 L ${w} ${h} L 0 ${h} Z` + : circle(rOuter, true); + return rInner > 0 ? `${outer} ${circle(rInner, false)}` : outer; +} + +type HalfProps = { + scene: ReactNode; + scale: number; + fov: number; + blur: number; + rotation: number; + centerX: number; + centerY: number; + width: number; + height: number; + lensSteps: number; + blurSamples: number; + blurScaleGain: number; + blurFade: number; +}; + +function TransitionHalf({ + scene, + scale, + fov, + blur, + rotation, + centerX, + centerY, + width, + height, + lensSteps, + blurSamples, + blurScaleGain, + blurFade, +}: HalfProps) { + const origin = `${centerX}px ${centerY}px`; + const base = scale / 100; + + const peak = lensMagnification(0, fov); + const steps = peak <= 1.001 ? 0 : lensSteps; + const rings = Array.from({ length: steps }, (_, i) => { + const s = 1 + ((peak - 1) * (i + 0.5)) / steps; + return { s, r: radiusForMagnification(s, fov) }; + }); + + const samples = + blur <= 0.05 ? 1 : Math.max(2, Math.round((blurSamples * blur) / 60)); + const weights = Array.from( + { length: samples }, + (_, j) => (1 - j / samples) ** blurFade, + ); + + const groups: { + key: number; + alpha: number; + copies: { key: string; clip: string; k: number }[]; + }[] = []; + let running = 0; + for (let j = samples - 1; j >= 0; j--) { + running += weights[j]; + const g = + samples === 1 + ? 1 + : 1 + (blur / 100) * blurScaleGain * (j / (samples - 1)); + const halfWidth = (width / 2) * g; + const copies: { key: string; clip: string; k: number }[] = []; + + copies.push({ + key: "base", + clip: ringClip( + rings.length ? rings[0].r * halfWidth : 0, + null, + width, + height, + centerX, + centerY, + ), + k: g * base, + }); + + for (let i = 0; i < rings.length; i++) { + const inner = i + 1 < rings.length ? rings[i + 1].r * halfWidth : 0; + copies.push({ + key: `${i}`, + clip: ringClip( + inner, + rings[i].r * halfWidth, + width, + height, + centerX, + centerY, + ), + k: g * rings[i].s * base, + }); + } + + groups.push({ key: j, alpha: weights[j] / running, copies }); + } + + return ( +
+ {groups.map(({ key, alpha, copies }) => ( +
+ {copies.map(({ key: ck, clip, k }) => ( +
+ {scene} +
+ ))} +
+ ))} +
+ ); +} + +interface LensZoomStageProps extends HalfProps { + shakeEnvelope: number; + shakeFrequency: number; + shakeCyclesPerUnit: number; + shakeTranslatePx: number; + shakeRotateDeg: number; + redScale: number; + greenScale: number; + blueScale: number; + letterboxAspect: number; +} + +function LensZoomStage({ + shakeEnvelope, + shakeFrequency, + shakeCyclesPerUnit, + shakeTranslatePx, + shakeRotateDeg, + redScale, + greenScale, + blueScale, + letterboxAspect, + ...half +}: LensZoomStageProps) { + const frame = useCurrentFrame(); + const { fps } = useVideoConfig(); + const filterId = useId(); + const { width, height, centerX, centerY } = half; + + const core = ; + + const split = redScale !== greenScale || blueScale !== greenScale; + const channels: { id: string; scale: number; matrix: string }[] = [ + { + id: "r", + scale: redScale, + matrix: "1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0", + }, + { + id: "g", + scale: greenScale, + matrix: "0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 1 0", + }, + { + id: "b", + scale: blueScale, + matrix: "0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 1 0", + }, + ]; + + const composited = split ? ( +
+ {channels.map((c) => ( +
+ {core} +
+ ))} +
+ ) : ( + core + ); + + const t = (frame / fps) * shakeFrequency * shakeCyclesPerUnit; + const octave = (seed: number) => + valueNoise(seed, t) + 0.45 * valueNoise(seed + 977, t * 2.6); + const shakeX = octave(11) * shakeTranslatePx * shakeEnvelope; + const shakeY = octave(23) * shakeTranslatePx * shakeEnvelope; + const shakeR = octave(41) * shakeRotateDeg * shakeEnvelope; + + const barHeight = + letterboxAspect > 0 + ? Math.max(0, (height - width / letterboxAspect) / 2) + : 0; + + return ( +
+ {split ? ( + + + {channels.map((c) => ( + + + + ))} + + + ) : null} + +
+ {composited} +
+ + {barHeight > 0 ? ( + <> +
+
+ + ) : null} +
+ ); +} + +const LensZoomPresentation: React.FC< + TransitionPresentationComponentProps +> = ({ + children, + presentationDirection, + presentationProgress, + passedProps, +}) => { + const config = useVideoConfig(); + const { + inScaleFrom = 100, + inScaleTo = 150, + inFovFrom = 0, + inFovTo = 160, + inBlurFrom = 0, + inBlurTo = 60, + inRotationFrom = 0, + inRotationTo = -15, + + outScaleFrom = 135, + outScaleTo = 100, + outFovFrom = 135, + outFovTo = 0, + outBlurFrom = 45, + outBlurTo = 0, + outRotationFrom = 345, + outRotationTo = 360, + + anticipateScale = 100, + anticipatePortion = 0.35, + + lensSteps = 9, + blurSamples = 7, + blurScaleGain = 1, + blurFade = 1.5, + + shakeAmount = 50, + shakeFrequency = 1.4, + shakeCyclesPerUnit = 8, + shakeTranslatePx = 73, + shakeRotateDeg = 1.1, + + redScale = 1.01, + greenScale = 1.0, + blueScale = 0.99, + + letterboxAspect = 0, + + width = config.width, + height = config.height, + distortionCenterX = width / 2, + distortionCenterY = height / 2, + } = passedProps; + + const exiting = presentationDirection === "exiting"; + + if (exiting ? presentationProgress >= 0.5 : presentationProgress < 0.5) + return null; + + const p = exiting + ? interpolate(presentationProgress, [0, 0.5], [0, 1], { + easing: LENS_ZOOM_EASE_IN, + extrapolateLeft: "clamp", + extrapolateRight: "clamp", + }) + : interpolate(presentationProgress, [0.5, 1], [0, 1], { + easing: LENS_ZOOM_EASE_OUT, + extrapolateLeft: "clamp", + extrapolateRight: "clamp", + }); + + const anticipating = + exiting && anticipatePortion > 0 && anticipateScale < inScaleFrom; + + const pFx = + anticipating && exiting + ? interpolate(p, [anticipatePortion, 1], [0, 1], { + extrapolateLeft: "clamp", + extrapolateRight: "clamp", + }) + : p; + + const lerp = (from: number, to: number) => from + (to - from) * pFx; + + const exitScale = anticipating + ? interpolate( + p, + [0, anticipatePortion, 1], + [inScaleFrom, anticipateScale, inScaleTo], + { + easing: Easing.inOut(Easing.ease), + extrapolateLeft: "clamp", + extrapolateRight: "clamp", + }, + ) + : lerp(inScaleFrom, inScaleTo); + + const shakeEnvelope = + interpolate( + presentationProgress, + SHAKE_STOPS.map((s) => 0.5 + s / 2), + [0, shakeAmount, shakeAmount, 0], + { extrapolateLeft: "clamp", extrapolateRight: "clamp" }, + ) / 100; + + return ( + + ); +}; + +export function lensZoom( + props: LensZoomProps = {}, +): TransitionPresentation { + return { + component: LensZoomPresentation, + props, + }; +} diff --git a/src/remocn/outline-fill-track-text.tsx b/src/remocn/outline-fill-track-text.tsx new file mode 100644 index 0000000..b94d1d2 --- /dev/null +++ b/src/remocn/outline-fill-track-text.tsx @@ -0,0 +1,236 @@ +"use client"; + +import { useMemo } from "react"; +import { Easing, interpolate, useCurrentFrame, useVideoConfig } from "remotion"; + +export interface OutlineFillTrackTextProps { + leadText?: string; + valueText?: string; + fontSize?: number; + fontWeight?: number; + color?: string; + outlineColor?: string; + outlineWidth?: number; + backgroundColor?: string; + glowColor?: string; + enterOffset?: number; + anchorOffsetX?: number; + trackDistance?: number; + wordGap?: number; + endPadding?: number; + fillDuration?: number; + speed?: number; + className?: string; +} + +export const outlineFillTrackTextLength = 80; + +const ENTER_END = 16; +const TRACK_START = 16; +const TRACK_END = 72; +const FILL_START = 34; +const FILL_PREFILL = 10; +const FONT_FAMILY = "Arial, Helvetica, sans-serif"; +const clamp = { + extrapolateLeft: "clamp" as const, + extrapolateRight: "clamp" as const, +}; + +function estimateTextWidth( + text: string, + fontSize: number, + fontWeight: number, +): number { + if (typeof document === "undefined") { + return text.length * fontSize * 0.56; + } + + const context = document.createElement("canvas").getContext("2d"); + if (!context) return text.length * fontSize * 0.56; + context.font = `${fontWeight} ${fontSize}px Arial`; + return context.measureText(text).width; +} + +export function OutlineFillTrackText({ + leadText = "Keep", + valueText = "1000000000%", + fontSize = 368, + fontWeight = 700, + color = "#f4f3f6", + outlineColor = "#e6e2ed", + outlineWidth = 3, + backgroundColor = "#030012", + glowColor = "#64109a", + enterOffset = 240, + anchorOffsetX = -84, + trackDistance = 0, + wordGap = 220, + endPadding = 96, + fillDuration = 38, + speed = 1, + className, +}: OutlineFillTrackTextProps) { + const frame = useCurrentFrame() * Math.max(speed, 0); + const { width: compositionWidth } = useVideoConfig(); + const safeFontSize = Math.max(fontSize, 1); + const safeFontWeight = Math.max(fontWeight, 1); + const safeOutlineWidth = Math.max(outlineWidth, 0); + const safeGap = Math.max(wordGap, 0); + const safeEndPadding = Math.max(endPadding, 0); + const safeFillDuration = Math.max(fillDuration, 1); + + const measurements = useMemo(() => { + const leadWidth = estimateTextWidth(leadText, safeFontSize, safeFontWeight); + const valueWidth = estimateTextWidth( + valueText, + safeFontSize, + safeFontWeight, + ); + return { + valueWidth, + distance: leadWidth / 2 + safeGap + valueWidth / 2, + }; + }, [leadText, safeFontSize, safeFontWeight, safeGap, valueText]); + + const valueOffset = trackDistance > 0 ? trackDistance : measurements.distance; + const anchorX = compositionWidth / 2 + anchorOffsetX; + const trailingOverflow = Math.max( + 0, + anchorX + measurements.valueWidth / 2 - (compositionWidth - safeEndPadding), + ); + const travelDistance = valueOffset + trailingOverflow; + const enterProgress = interpolate(frame, [0, ENTER_END], [0, 1], { + ...clamp, + easing: Easing.bezier(0.16, 0.84, 0.24, 1), + }); + const trackProgress = interpolate(frame, [TRACK_START, TRACK_END], [0, 1], { + ...clamp, + easing: Easing.bezier(0.42, 0, 0.58, 1), + }); + const fillProgress = interpolate( + frame, + [FILL_START - FILL_PREFILL, FILL_START + safeFillDuration], + [0, 1], + clamp, + ); + const leadY = enterOffset * (1 - enterProgress); + const revealEdge = enterProgress * 112 - 8; + const revealSolid = Math.max(0, Math.min(100, revealEdge - 10)); + const revealFade = Math.max(0, Math.min(100, revealEdge + 10)); + const leadMask = `linear-gradient(180deg, #000 0%, #000 ${revealSolid}%, transparent ${revealFade}%, transparent 100%)`; + const glowOpacity = interpolate( + enterProgress, + [0, 0.18, 0.72, 1], + [0.92, 1, 0.52, 0], + clamp, + ); + const fieldY = interpolate(enterProgress, [0, 1], [-42, -210], clamp); + const textStyle = { + display: "block", + fontFamily: FONT_FAMILY, + fontSize: safeFontSize, + fontWeight: safeFontWeight, + letterSpacing: 0, + lineHeight: 0.86, + whiteSpace: "nowrap" as const, + }; + + return ( +
+
+ +
+
+ {leadText} +
+ +
+ + {valueText} + + + {valueText} + +
+ +
+
+
+ ); +} diff --git a/src/remocn/path-ride.tsx b/src/remocn/path-ride.tsx new file mode 100644 index 0000000..f4321c6 --- /dev/null +++ b/src/remocn/path-ride.tsx @@ -0,0 +1,430 @@ +"use client"; + +import { + getLength, + getPointAtLength, + getTangentAtLength, +} from "@remotion/paths"; +import { useEffect, useMemo, useState } from "react"; +import { + continueRender, + delayRender, + Easing, + interpolate, + useCurrentFrame, + useVideoConfig, +} from "remotion"; + +// KolayTercih: Google Fonts yerine projenin kendi Cal Sans'ı (fontlar.ts) +const INTER = "'Cal Sans', sans-serif"; + +export interface PathRideProps { + text?: string; + path?: string; + fontSize?: number; + fontWeight?: number; + fontFamily?: string; + color?: string; + lineColor?: string; + lineWidth?: number; + speed?: number; + className?: string; +} + +/** An S-curve across a 1280×720 frame. */ +export const pathRideDefaultPath = + "M 120 560 C 360 120 560 120 640 360 S 920 600 1160 160"; + +export const pathRideDefaults = { + text: "Follow the line", + path: pathRideDefaultPath, + fontSize: 44, + fontWeight: 700, + fontFamily: INTER, + color: "#fafafa", + lineColor: "#D97757", + lineWidth: 3, + speed: 1, +} satisfies Required>; + +/** Beats on the 30 fps clock. */ +export const pathRideTiming = { + draw: [0, 40], + straighten: [50, 68], + erase: [52, 70], +} as const; + +/** Gap between the head and the last letter, in em. */ +const LEAD = 0.3; +/** Letters fade in over this much arc length, in em. */ +const FADE_IN = 0.5; +/** Extra lift of the baseline above the line, in em. */ +const LIFT = 0.08; +/** Samples along the path; the line and the letters read positions from them. */ +const SAMPLES = 400; +const MEASURE_SIZE = 100; +/** Inter's baseline and cap height in a `line-height: 1` box, until measured. */ +const INTER_METRICS = { baseline: 0.8637, capHeight: 0.7275 }; + +const CLAMP = { + extrapolateLeft: "clamp", + extrapolateRight: "clamp", +} as const; + +const easeInOutCubic = Easing.inOut(Easing.cubic); + +function finite(value: number | undefined, fallback: number) { + return typeof value === "number" && Number.isFinite(value) ? value : fallback; +} + +/** Maps a composition frame onto the 30 fps timeline, scaled by `speed`. */ +export function getPathRideTime( + frame: number, + options: { fps?: number; speed?: number } = {}, +) { + const fps = Math.max(1, finite(options.fps, 30)); + const speed = Math.max(0, finite(options.speed, 1)); + return (Math.max(0, finite(frame, 0)) * 30 * speed) / fps; +} + +/** Arc length drawn so far. */ +export function getPathRideHead(t: number, length: number) { + return interpolate(t, [...pathRideTiming.draw], [0, length], { + ...CLAMP, + easing: easeInOutCubic, + }); +} + +/** Arc length erased from the start. */ +export function getPathRideTail(t: number, length: number) { + return interpolate(t, [...pathRideTiming.erase], [0, length], { + ...CLAMP, + easing: easeInOutCubic, + }); +} + +/** How far the curve has pulled straight, 0..1. */ +export function getPathRideStraighten(t: number) { + return interpolate(t, [...pathRideTiming.straighten], [0, 1], { + ...CLAMP, + easing: easeInOutCubic, + }); +} + +/** Frame where the line has erased and the text rests on its baseline. */ +export const pathRideLength = Math.max( + pathRideTiming.straighten[1], + pathRideTiming.erase[1], +); + +/** Letter centers from the text start, and the total width. */ +export function getPathRideCenters(advances: readonly number[]) { + let x = 0; + const centers = advances.map((a) => { + const c = x + a / 2; + x += a; + return c; + }); + return { centers, width: x }; +} + +/** + * Arc length of a letter's center: it trails the head by the lead plus the + * text after it, so the last letter follows the head most closely. + */ +export function getPathRideArc( + head: number, + center: number, + width: number, + fontSize: number, +) { + return head - LEAD * fontSize - (width - center); +} + +/** Opacity of a letter from how far onto the curve it has come. */ +export function getPathRideFade(arc: number, fontSize: number) { + return Math.min(1, Math.max(0, arc / (FADE_IN * fontSize))); +} + +export interface Point { + x: number; + y: number; +} + +export interface PathSamples { + length: number; + points: Point[]; + tangents: Point[]; +} + +/** Points and unit tangents at evenly spaced arc lengths along `path`. */ +export function samplePath(path: string, count = SAMPLES): PathSamples { + const length = getLength(path); + const n = Math.max(2, Math.round(count)); + const points: Point[] = []; + const tangents: Point[] = []; + for (let i = 0; i < n; i++) { + const at = (length * i) / (n - 1); + // Both can come back null on a degenerate path; reuse the last sample. + const p = getPointAtLength(path, at) ?? points[i - 1] ?? { x: 0, y: 0 }; + const tangent = getTangentAtLength(path, at) ?? + tangents[i - 1] ?? { x: 1, y: 0 }; + points.push({ x: p.x, y: p.y }); + tangents.push({ x: tangent.x, y: tangent.y }); + } + return { length, points, tangents }; +} + +/** Position and unit tangent at arc length `s`, clamped to the path. */ +export function getPathSample(samples: PathSamples, s: number) { + const n = samples.points.length; + const u = samples.length > 0 ? (s / samples.length) * (n - 1) : 0; + const i = Math.min(n - 2, Math.max(0, Math.floor(u))); + const f = Math.min(1, Math.max(0, u - i)); + const a = samples.points[i]; + const b = samples.points[i + 1]; + const ta = samples.tangents[i]; + const tb = samples.tangents[i + 1]; + const tx = ta.x + (tb.x - ta.x) * f; + const ty = ta.y + (tb.y - ta.y) * f; + const norm = Math.hypot(tx, ty) || 1; + return { + x: a.x + (b.x - a.x) * f, + y: a.y + (b.y - a.y) * f, + tx: tx / norm, + ty: ty / norm, + }; +} + +export interface PathRideStraight { + /** Arc length that lands on the text's left edge. */ + start: number; + /** Reference x of the text's left edge on the straight baseline. */ + left: number; + /** Reference y of the straight baseline. */ + baseline: number; +} + +export interface PathRidePose { + x: number; + y: number; + angle: number; +} + +/** + * A point of the path pulled `k` of the way from the curve to the straight + * baseline, lifted `lift` along its normal. Arc length maps to x one to one, + * so the text keeps its spacing while the curve pulls taut. + */ +export function getPathRidePoint( + samples: PathSamples, + s: number, + k: number, + straight: PathRideStraight, + lift = 0, +): PathRidePose { + const p = getPathSample(samples, s); + const x = p.x + (straight.left + (s - straight.start) - p.x) * k; + const y = p.y + (straight.baseline - p.y) * k; + const tx = p.tx + (1 - p.tx) * k; + const ty = p.ty * (1 - k); + const norm = Math.hypot(tx, ty) || 1; + return { + x: x + (ty / norm) * lift, + y: y - (tx / norm) * lift, + angle: Math.atan2(ty, tx), + }; +} + +/** Polyline of the pulled path between two arc lengths. */ +export function getPathRideLine( + samples: PathSamples, + from: number, + to: number, + k: number, + straight: PathRideStraight, +) { + if (to <= from) return ""; + const step = samples.length / (samples.points.length - 1); + const stops = [from]; + for (let s = Math.ceil(from / step) * step; s < to; s += step) { + if (s > from) stops.push(s); + } + stops.push(to); + return stops + .map((s, i) => { + const p = getPathRidePoint(samples, s, k, straight); + return `${i === 0 ? "M" : "L"}${p.x.toFixed(2)} ${p.y.toFixed(2)}`; + }) + .join(""); +} + +function useMetrics(text: string, font: string, fallback: number) { + const chars = Array.from(text); + const key = chars.join(""); + const [metrics, setMetrics] = useState<{ + advances: number[]; + baseline: number; + capHeight: number; + } | null>(null); + + useEffect(() => { + const handle = delayRender("path-ride: measuring the text"); + let released = false; + const release = () => { + if (released) return; + released = true; + continueRender(handle); + }; + const measure = () => { + if (released) return; + const ctx = document.createElement("canvas").getContext("2d"); + if (ctx) { + ctx.font = font; + const m = ctx.measureText("H"); + const ascent = m.fontBoundingBoxAscent / MEASURE_SIZE; + const descent = m.fontBoundingBoxDescent / MEASURE_SIZE; + setMetrics({ + advances: Array.from(key).map( + (c) => ctx.measureText(c).width / MEASURE_SIZE, + ), + baseline: + ascent > 0 + ? (1 - (ascent + descent)) / 2 + ascent + : INTER_METRICS.baseline, + capHeight: + m.actualBoundingBoxAscent > 0 + ? m.actualBoundingBoxAscent / MEASURE_SIZE + : INTER_METRICS.capHeight, + }); + } + release(); + }; + document.fonts.load(font, key).then(measure, measure); + return release; + }, [key, font]); + + return ( + metrics ?? { + advances: chars.map(() => fallback), + baseline: INTER_METRICS.baseline, + capHeight: INTER_METRICS.capHeight, + } + ); +} + +export function PathRide({ + text = pathRideDefaults.text, + path = pathRideDefaults.path, + fontSize = pathRideDefaults.fontSize, + fontWeight = pathRideDefaults.fontWeight, + fontFamily = pathRideDefaults.fontFamily, + color = pathRideDefaults.color, + lineColor = pathRideDefaults.lineColor, + lineWidth = pathRideDefaults.lineWidth, + speed = pathRideDefaults.speed, + className, +}: PathRideProps) { + const frame = useCurrentFrame(); + const { fps, width, height } = useVideoConfig(); + const unit = height / 720; + const t = getPathRideTime(frame, { fps, speed }); + const metrics = useMetrics( + text, + `${fontWeight} ${MEASURE_SIZE}px ${fontFamily}`, + 0.55, + ); + const samples = useMemo(() => samplePath(path), [path]); + const chars = Array.from(text); + const advances = metrics.advances.map((a) => a * fontSize); + const { centers, width: textWidth } = getPathRideCenters(advances); + const length = samples.length; + const head = getPathRideHead(t, length); + const tail = getPathRideTail(t, length); + const k = getPathRideStraighten(t); + const lift = lineWidth / 2 + LIFT * fontSize; + // The line settles `lift` below the text so the capitals center on the frame. + const straight: PathRideStraight = { + start: getPathRideArc(length, 0, textWidth, fontSize), + left: 640 - textWidth / 2, + baseline: 360 + (metrics.capHeight * fontSize) / 2 + lift, + }; + const tip = getPathRidePoint(samples, head, k, straight); + const tipOpacity = interpolate( + head, + [0, length * 0.02, length * 0.98, length], + [0, 1, 1, 0], + CLAMP, + ); + // Reference space (1280×720, y down) mapped onto the composition. + const toX = (x: number) => width / 2 + (x - 640) * unit; + const toY = (y: number) => height / 2 + (y - 360) * unit; + const size = fontSize * unit; + const line = getPathRideLine(samples, tail, head, k, straight); + + return ( +
+ + + {line ? ( + + ) : null} + {tipOpacity > 0 ? ( + + ) : null} + + + {chars.map((char, j) => { + const arc = getPathRideArc(head, centers[j], textWidth, fontSize); + const fade = getPathRideFade(arc, fontSize); + if (fade <= 0) return null; + const pose = getPathRidePoint(samples, arc, k, straight, lift); + return ( + + {char} + + ); + })} +
+ ); +} diff --git a/src/remocn/perspective-marquee.tsx b/src/remocn/perspective-marquee.tsx new file mode 100644 index 0000000..e87325d --- /dev/null +++ b/src/remocn/perspective-marquee.tsx @@ -0,0 +1,145 @@ +"use client"; + +import { useCurrentFrame } from "remotion"; + +export interface PerspectiveMarqueeProps { + items?: string[]; + fontSize?: number; + color?: string; + fontWeight?: number; + pixelsPerFrame?: number; + rotateY?: number; + rotateX?: number; + perspective?: number; + fadeColor?: string; + speed?: number; + className?: string; +} + +const FONT_FAMILY = + "var(--font-geist-sans), -apple-system, BlinkMacSystemFont, sans-serif"; + +const DEFAULT_ITEMS = [ + "Vercel", + "Linear", + "Stripe", + "Figma", + "Notion", + "Raycast", + "Arc", + "Cursor", +]; + +export function PerspectiveMarquee({ + items = DEFAULT_ITEMS, + fontSize = 84, + color = "#fafafa", + fontWeight = 700, + pixelsPerFrame = 2, + rotateY = -28, + rotateX = 8, + perspective = 1200, + fadeColor = "#050505", + speed = 1, + className, +}: PerspectiveMarqueeProps) { + const frame = useCurrentFrame() * speed; + + // Approximate width of one full item set so the modulo wraps cleanly. + const itemPadding = fontSize * 0.9; + const approxItemWidth = items.reduce( + (acc, item) => acc + item.length * fontSize * 0.6 + itemPadding, + 0, + ); + + const offset = -((frame * pixelsPerFrame) % approxItemWidth); + + // Render the row 3x so the loop is seamless even when rotated in 3D. + const rendered = [...items, ...items, ...items]; + + return ( +
+
+
+ {rendered.map((item, i) => { + // Approximate this item's center X (relative to the marquee row, + // including the rolling offset). Used to dial in depth-of-field blur + // and opacity by horizontal position on screen. + const itemCenter = + i * (approxItemWidth / items.length) + + approxItemWidth / items.length / 2 + + offset; + // Normalize roughly to [-1, 1] across a 1280px viewport. + const norm = (itemCenter - 640) / 640; + const distance = Math.min(1, Math.abs(norm)); + const blurPx = distance * 6; + const opacity = 1 - distance * 0.4; + + return ( + + {item} + + ); + })} +
+
+ + {/* Vignette to fade marquee edges into background */} +
+
+
+ ); +} diff --git a/src/remocn/sheen-slide-in.tsx b/src/remocn/sheen-slide-in.tsx new file mode 100644 index 0000000..1d2f7ba --- /dev/null +++ b/src/remocn/sheen-slide-in.tsx @@ -0,0 +1,145 @@ +"use client"; + +import { Easing, interpolate, useCurrentFrame } from "remotion"; + +export interface SheenSlideInProps { + text: string; + exitAt?: number; + fontSize?: number; + baseColor?: string; + sheenColor?: string; + fontWeight?: number; + speed?: number; + className?: string; +} + +const SETTLE_AMP_EM = 1.8; +const SETTLE_TAU = 2.5; +const CREEP_AMP_EM = 0.28; +const CREEP_TAU = 12; +const FADE_FRAMES = 4; +const SHEEN_IN_START = 0; +const SHEEN_IN_END = 4; +const SHEEN_SWEEP_END = 48; +const SHEEN_OUT_START = 48; +const SHEEN_OUT_END = 58; +const EXIT_FRAMES = 4; +const EXIT_SCALE = 0.9; + +export function sheenSlideInLength(exitAt = 60): number { + return exitAt + EXIT_FRAMES; +} + +function travelEm(frame: number): number { + return ( + SETTLE_AMP_EM * Math.exp(-frame / SETTLE_TAU) + + CREEP_AMP_EM * Math.exp(-frame / CREEP_TAU) + ); +} + +const exitEasing = Easing.out(Easing.quad); + +export function SheenSlideIn({ + text, + exitAt = 60, + fontSize = 72, + baseColor = "#18181b", + sheenColor = "#4f8ef7", + fontWeight = 400, + speed = 1, + className, +}: SheenSlideInProps) { + const frame = useCurrentFrame() * speed; + + const x = fontSize * travelEm(frame); + + const exit = interpolate(frame, [exitAt, exitAt + EXIT_FRAMES], [0, 1], { + extrapolateLeft: "clamp", + extrapolateRight: "clamp", + easing: exitEasing, + }); + + const opacity = interpolate(frame, [0, FADE_FRAMES], [0, 1], { + extrapolateLeft: "clamp", + extrapolateRight: "clamp", + }); + + const sheenOpacity = + interpolate(frame, [SHEEN_IN_START, SHEEN_IN_END], [0, 1], { + extrapolateLeft: "clamp", + extrapolateRight: "clamp", + }) * + interpolate(frame, [SHEEN_OUT_START, SHEEN_OUT_END], [1, 0], { + extrapolateLeft: "clamp", + extrapolateRight: "clamp", + }); + + const sheenPosition = interpolate(frame, [0, SHEEN_SWEEP_END], [160, -60], { + extrapolateLeft: "clamp", + extrapolateRight: "clamp", + easing: Easing.bezier(0.42, 0, 0.58, 1), + }); + + const textStyle: React.CSSProperties = { + fontSize, + fontWeight, + letterSpacing: "-0.02em", + lineHeight: 1.1, + margin: 0, + fontFamily: + "var(--font-geist-sans, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif)", + }; + + if (exit >= 1) { + return ( +
+ ); + } + + return ( +
+
+ + {text} + + + {text} + +
+
+ ); +} diff --git a/src/remocn/soft-blur-in.tsx b/src/remocn/soft-blur-in.tsx new file mode 100644 index 0000000..6bd443c --- /dev/null +++ b/src/remocn/soft-blur-in.tsx @@ -0,0 +1,92 @@ +"use client"; + +import { Easing, interpolate, useCurrentFrame } from "remotion"; + +export interface SoftBlurInProps { + text: string; + blur?: number; + fontSize?: number; + color?: string; + fontWeight?: number; + speed?: number; + className?: string; +} + +export function SoftBlurIn({ + text, + blur = 12, + fontSize = 72, + color = "#171717", + fontWeight = 600, + speed = 1, + className, +}: SoftBlurInProps) { + const frame = useCurrentFrame() * speed; + + const chars = Array.from(text); + const charDurationFrames = 27; + const charTravelFrames = 9; + const staggerFrames = 1; + + return ( +
+ + {chars.map((char, i) => { + const local = frame - i * staggerFrames; + const easing = Easing.bezier(0.22, 1, 0.36, 1); + const opacity = interpolate(local, [0, charDurationFrames], [0, 1], { + extrapolateLeft: "clamp", + extrapolateRight: "clamp", + easing, + }); + const y = interpolate(local, [0, charTravelFrames], [16, 0], { + extrapolateLeft: "clamp", + extrapolateRight: "clamp", + easing, + }); + const blurAmount = interpolate( + local, + [0, charDurationFrames], + [blur, 0], + { extrapolateLeft: "clamp", extrapolateRight: "clamp", easing }, + ); + return ( + + {char} + + ); + })} + +
+ ); +} diff --git a/src/remocn/tracking-in.tsx b/src/remocn/tracking-in.tsx new file mode 100644 index 0000000..4185099 --- /dev/null +++ b/src/remocn/tracking-in.tsx @@ -0,0 +1,71 @@ +"use client"; + +import { interpolate, spring, useCurrentFrame, useVideoConfig } from "remotion"; + +export interface TrackingInProps { + text: string; + startTracking?: number; + startBlur?: number; + fontSize?: number; + color?: string; + fontWeight?: number; + speed?: number; + className?: string; +} + +export function TrackingIn({ + text, + startTracking = 0.5, + startBlur = 12, + fontSize = 96, + color = "#171717", + fontWeight = 700, + speed = 1, + className, +}: TrackingInProps) { + const frame = useCurrentFrame() * speed; + const { fps } = useVideoConfig(); + + const t = spring({ + frame, + fps, + config: { damping: 18, stiffness: 90 }, + }); + + const letterSpacing = `${interpolate(t, [0, 1], [startTracking, -0.03])}em`; + const blurAmount = interpolate(t, [0, 1], [startBlur, 0]); + const opacity = interpolate(frame, [0, 15], [0, 1], { + extrapolateLeft: "clamp", + extrapolateRight: "clamp", + }); + + return ( +
+ + {text} + +
+ ); +} diff --git a/src/remocn/zoom-blur.tsx b/src/remocn/zoom-blur.tsx new file mode 100644 index 0000000..7592e85 --- /dev/null +++ b/src/remocn/zoom-blur.tsx @@ -0,0 +1,53 @@ +"use client"; + +import type { + TransitionPresentation, + TransitionPresentationComponentProps, +} from "@remotion/transitions"; +import type React from "react"; +import { AbsoluteFill } from "remotion"; + +export type ZoomBlurProps = { + blur?: number; + rise?: number; +}; + +const ZoomBlurPresentation: React.FC< + TransitionPresentationComponentProps +> = ({ + children, + presentationProgress, + presentationDirection, + passedProps, +}) => { + const { blur = 16, rise = 0 } = passedProps; + const entering = presentationDirection === "entering"; + const p = presentationProgress; + + const style: React.CSSProperties = entering + ? { + opacity: p, + transform: `scale(${0.9 + p * 0.1})${ + rise > 0 ? ` translateY(${(1 - p) * rise}px)` : "" + }`, + filter: p < 1 ? `blur(${(1 - p) * blur}px)` : undefined, + } + : { + opacity: 1 - p, + transform: `scale(${1 + p * 0.12})${ + rise > 0 ? ` translateY(${-p * rise}px)` : "" + }`, + filter: p > 0 ? `blur(${p * blur}px)` : undefined, + }; + + return {children}; +}; + +export function zoomBlur( + props: ZoomBlurProps = {}, +): TransitionPresentation { + return { + component: ZoomBlurPresentation, + props, + }; +} diff --git a/src/sahneler-1.tsx b/src/sahneler-1.tsx new file mode 100644 index 0000000..e4ded36 --- /dev/null +++ b/src/sahneler-1.tsx @@ -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 ( + + + + + YKS sonuçları açıklandı + + + 45.320. + +
+ Sıralaman geldi. Tebrikler! + + Sırada küçük bir bulmaca var. + +
+
+
+
+ ); +} + +/* ------------------------------------------------------------------ */ +/* 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 ( + + +
+ +
+
+ +
+ +
+ {trSayi(sayi)} + + +
+
program var.
+
+ + Senin 24 hakkın var. + +
+
+
+
+ ); +} + +/* ------------------------------------------------------------------ */ +/* 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 ( + + + {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 ( +
+ {s} +
+ ); + })} + +

+ +

+
+
+
+ ); +} + +/* ------------------------------------------------------------------ */ +/* 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 ( + + +
+
+ + Derin bir nefes al. + +
+ + + ); +} + +/* ------------------------------------------------------------------ */ +/* 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 = ( +
+ +
+ +
+
+ ); + return ( + + + + + {kilit} + + +
+ {dikey ? ( + <> + + Tercih listeni + + + gerçek veriyle kur, + + + ) : ( + + Tercih listeni gerçek veriyle kur, + + )} +
+ + panikle + değil. + +
+
+
+
+
+ ); +} diff --git a/src/sahneler-2.tsx b/src/sahneler-2.tsx new file mode 100644 index 0000000..3b78ab2 --- /dev/null +++ b/src/sahneler-2.tsx @@ -0,0 +1,418 @@ +import { Easing, useCurrentFrame } from "remotion"; +import { HARITA_SATIR, HARITA_SUTUN, YagmurHaritasi } from "./harita"; +import { Ikon } from "./ikonlar"; +import { SahneImleci, TakipKamera, imlecKonumu, type Durak } from "./imlec"; +import { PikselKonturDolgu, PikselSheen, PikselYolSurus } from "./kinetik"; +import { RiskEtiketi, Sahne, TuruncuCta, useDuzen } from "./ortak"; +import { OdakDisiPikseller } from "./sinema"; +import { FONT, R, ara, trSayi, yay, zipla } from "./tema"; +import { VURUSLAR } from "./vurus"; +import ZAMAN from "./zaman.json"; + +/** Adım başlığı: turuncu piksel rozetinde numara + Sheen Slide In başlık. */ +function AdimBasligi({ no, metin, alt, dikey }: { no: number; metin: string; alt?: string; dikey: boolean }) { + const frame = useCurrentFrame(); + const r = zipla(frame, 2); + const kare = dikey ? 96 : 88; + return ( +
+
+
+ {no} +
+ + {metin} + +
+ {alt ? ( +
{alt}
+ ) : null} +
+ ); +} + +/* ------------------------------------------------------------------ */ +/* Adım 1 — tam ekran sihirbaz; kamera imlece kilitlenir */ +/* ------------------------------------------------------------------ */ +const CIPLER = ["Mühendislik", "Bilgisayar", "İstanbul", "Ankara", "İzmir", "Devlet"]; +const T1 = ZAMAN.tiklar.adim1; // [girdi, çip0, çip1, çip2, çip5, CTA] +const SECIM: Record = { 0: T1[1], 1: T1[2], 2: T1[3], 5: T1[4] }; + +export function Adim1() { + const frame = useCurrentFrame(); + const { dikey } = useDuzen(); + // Yerleşim (px): dikey 1080×1920, yatay 1920×1080 + const L = dikey + ? { x: 70, w: 940, etiket1: 380, girdi: [440, 160], etiket2: 700, cip: { y: 770, h: 110, w: 455, ara: 30, sut: 2 }, cta: [1240, 140], kare: [1480, 62] } + : { x: 140, w: 1640, etiket1: 250, girdi: [300, 140], etiket2: 500, cip: { y: 560, h: 100, w: 255, ara: 22, sut: 6 }, cta: [730, 130], kare: [920, 52] }; + const cipMerkez = (i: number): [number, number] => { + const c = i % L.cip.sut; + const r = Math.floor(i / L.cip.sut); + return [L.x + c * (L.cip.w + L.cip.ara) + L.cip.w / 2, L.cip.y + r * (L.cip.h + L.cip.ara) + L.cip.h / 2]; + }; + const girdiY = L.girdi[0] + L.girdi[1] / 2; + const ctaY = L.cta[0] + L.cta[1] / 2; + const duraklar: Durak[] = [ + { f: 16, x: dikey ? 820 : 1500, y: dikey ? 1520 : 900 }, + { f: T1[0], x: L.x + 230, y: girdiY + 20, g: 14 }, + { f: T1[1], x: cipMerkez(0)[0], y: cipMerkez(0)[1], g: 12 }, + { f: T1[2], x: cipMerkez(1)[0], y: cipMerkez(1)[1], g: 8 }, + { f: T1[3], x: cipMerkez(2)[0], y: cipMerkez(2)[1], g: 8 }, + { f: T1[4], x: cipMerkez(5)[0], y: cipMerkez(5)[1], g: 8 }, + { f: T1[5], x: L.x + L.w * 0.6, y: ctaY, g: 16 }, + { f: 152, x: dikey ? 780 : 1420, y: dikey ? 1600 : 980, g: 20 }, + ]; + const nokta = (f: number) => imlecKonumu(duraklar, f); + + const metin = "45.320"; + const [yb, ys] = ZAMAN.yazma.adim1[0]; + const yazilan = metin.slice(0, Math.max(0, Math.floor(((frame - yb) / (ys - yb)) * metin.length + 0.5))); + const odak = frame >= T1[0]; + const imlecGor = Math.floor(frame / 8) % 2 === 0 || (frame >= yb && frame <= ys); + const cta = zipla(frame, 104); + const bas = ara(frame, [T1[5] - 2, T1[5] + 1], [0, 1]) * (1 - ara(frame, [T1[5] + 5, T1[5] + 10], [0, 1])); + const u = dikey ? 1 : 0.92; // yazı ölçeği + + return ( + + + + +
Başarı sıralaman
+
+ + + {yazilan} + {odak && imlecGor ? | : null} + {!odak ? Örn. 45.320 : null} + +
+
+ Neyi seviyorsun, nerede okumak istersin? +
+ {CIPLER.map((c, i) => { + const [cx, cy] = cipMerkez(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 + 8], [0, 1])) : 0; + const gir = yay(frame, 8 + i * 2, 14); + return ( +
+ {i >= 2 && i <= 4 ? : i === 5 ? : null} + {c} +
+ ); + })} +
+ +
+ {/* CTA'dan sonra: 24 piksel dolar (liste hazırlanıyor) */} +
+
Listen hazırlanıyor…
+
+ {Array.from({ length: 24 }, (_, i) => { + const on = frame >= T1[5] + 10 + i * 1.1; + const renk = i < 5 ? R.riskli : i < 18 ? R.azRiskli : R.guvenli; + return ; + })} +
+
+ +
+
+ ); +} + +/* ------------------------------------------------------------------ */ +/* Adım 2 — harita dökülür; imleç ısı fırçası, kamera ona kilitlenir */ +/* ------------------------------------------------------------------ */ +const FB = 50; // fırça başlangıcı +const FS = 72; // fırça süresi +const fircaYol = (p: number) => { + const e = Easing.inOut(Easing.sin)(p); + return { x: 3 + e * (HARITA_SUTUN - 7), y: 12 + Math.sin(p * Math.PI * 2) * 6 }; +}; + +export function Adim2() { + const frame = useCurrentFrame(); + const { dikey, w } = useDuzen(); + const haritaW = dikey ? 1040 : 1100; + const hucre = haritaW / HARITA_SUTUN; + const hx = (w - haritaW) / 2; + const hy = dikey ? 470 : 150; + const ekran = (p: number): [number, number] => { + const g = fircaYol(Math.min(1, Math.max(0, p))); + return [hx + g.x * hucre, hy + g.y * hucre]; + }; + // İmleç fırçanın ucunda: yolun örnekleri (2 karede bir) doğrusal duraklar + const duraklar: Durak[] = [{ f: 38, x: ekran(0)[0] - 80, y: ekran(0)[1] + 120 }, { f: FB, x: ekran(0)[0], y: ekran(0)[1], g: 12 }]; + for (let f = FB + 2; f <= FB + FS; f += 2) { + const [x, y] = ekran((f - FB) / FS); + duraklar.push({ f, x, y, dogrusal: true }); + } + duraklar.push({ f: FB + FS + 16, x: ekran(1)[0] + 60, y: ekran(1)[1] + 140, g: 14 }); + const nokta = (f: number) => imlecKonumu(duraklar, f); + const kilit = yay(frame, 150, 18); + + return ( + + + +
+ +
+
+ +
+
+
+ +
+
+ Yapay zekâ bu havuzun dışına çıkamaz. Uydurmak yok. +
+
+ +
+
+ ); +} + +/* ------------------------------------------------------------------ */ +/* Adım 3 — Path Ride + 24 kare; 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)); +const DOLUM = 62; // ilk karenin karesi + +export function Adim3() { + const frame = useCurrentFrame(); + const { dikey, w } = useDuzen(); + const G = dikey ? { sut: 6, kare: 130, ara: 18, y: 820 } : { sut: 12, kare: 108, ara: 16, y: 560 }; + const izW = G.sut * G.kare + (G.sut - 1) * G.ara; + const gx = (w - izW) / 2; + const kareMerkez = (i: number): [number, number] => [ + gx + (i % G.sut) * (G.kare + G.ara) + G.kare / 2, + G.y + Math.floor(i / G.sut) * (G.kare + G.ara) + G.kare / 2, + ]; + const satirSayisi = Math.ceil(24 / G.sut); + const lejantY = G.y + satirSayisi * (G.kare + G.ara) + 20; + const ornekY = lejantY + (dikey ? 130 : 110); + const duraklar: Durak[] = [{ f: DOLUM - 8, x: kareMerkez(0)[0] - 60, y: kareMerkez(0)[1] + 150 }]; + for (let i = 0; i < 24; i++) { + const [x, y] = kareMerkez(i); + duraklar.push({ f: DOLUM + i * 2, x: x + 10, y: y + 20, ...(i === 0 ? { g: 8 } : { dogrusal: true }) }); + } + duraklar.push({ f: 124, x: w / 2 + 40, y: lejantY + 50, g: 14 }); + duraklar.push({ f: 146, x: w / 2 + 140, y: ornekY + 60, g: 14 }); + const nokta = (f: number) => imlecKonumu(duraklar, f); + const yolKap = dikey + ? { y: 250, w: 1080, h: 520, yol: "M 80 420 C 300 60 520 60 560 260 S 820 470 1000 120", taban: 320, font: 84 } + : { y: 150, w: 1920, h: 360, yol: "M 260 300 C 560 40 860 40 960 180 S 1360 330 1660 60", taban: 230, font: 96 }; + + return ( + + + + +
+ +
+ {KARELER.map((d, i) => { + const [cx, cy] = kareMerkez(i); + const s = zipla(frame, DOLUM + i * 2); + return ( +
+ {i + 1} +
+ ); + })} +
+ {DILIMLER.map((d, i) => { + const g = yay(frame, 112 + i * 5, 14); + return ( + + + {d.ad} + {d.adet} + + ); + })} +
+ {/* Gerçek örnek satır: YÖK Atlas 2026, sayısal, 45.320. sıra */} +
= 144 ? R.turuncu : R.slate200}`, + opacity: yay(frame, 128, 16), + transform: `translateY(${(1 - yay(frame, 128, 16)) * 30}px)`, + }} + > +
11
+
+
Bilgisayar Mühendisliği
+
Ankara Yıldırım Beyazıt Üniversitesi · Ankara
+
+
+ + + Taban {trSayi(55158)} + +
+
+ +
+
+ ); +} + diff --git a/src/sahneler-3.tsx b/src/sahneler-3.tsx new file mode 100644 index 0000000..056daed --- /dev/null +++ b/src/sahneler-3.tsx @@ -0,0 +1,656 @@ +import type { ReactNode } from "react"; +import { AbsoluteFill, Easing, Sequence, interpolate, useCurrentFrame } from "remotion"; +import { Ikon, type IkonAdi } from "./ikonlar"; +import { SahneImleci, TakipKamera, imlecKonumu, type Durak } from "./imlec"; +import { PikselKonturDolgu, PikselSheen } from "./kinetik"; +import { LogoMark } from "./logo"; +import { RiskEtiketi, Sahne, TuruncuCta, useDuzen } from "./ortak"; +import { Eyebrow, Wordmark } from "./piksel"; +import { vurguStil } from "./sahneler-1"; +import { Kamera, MaskeSatir, OdakDisiPikseller } from "./sinema"; +import { FONT, R, ara, hash, trSayi, yay, zipla } from "./tema"; +import { VURUSLAR } from "./vurus"; +import ZAMAN from "./zaman.json"; + +/* ------------------------------------------------------------------ */ +/* Off — sessizlik: "Off. Kafan mı karıştı?" imleç kararsız dolanır */ +/* ------------------------------------------------------------------ */ +export function Off() { + const { dikey, w, h } = useDuzen(); + const cx = w / 2 + (dikey ? 170 : 330); + const cy = h / 2 + (dikey ? 160 : 90); + // Kararsız imleç: küçük kıvrımlar, sonra kadrajın ortasına (lens buradan patlar) + const duraklar: Durak[] = [ + { f: 14, x: cx, y: cy }, + { f: 22, x: cx - 40, y: cy + 20, g: 8 }, + { f: 30, x: cx + 26, y: cy - 24, g: 8 }, + { f: 38, x: cx - 18, y: cy + 30, g: 8 }, + { f: 58, x: w / 2, y: h / 2, g: 14 }, + ]; + return ( + + +
+ + Off. + +
+
+ + Kafan mı karıştı? + +
+
+ +
+ ); +} + +/* ------------------------------------------------------------------ */ +/* Danışman — BAM: DanışmanlaKonuş */ +/* ------------------------------------------------------------------ */ +function DanismanlaKonus({ boy }: { boy: number }) { + return ( + + + + Danışmanla + Konuş + + + ); +} + +export function Danisman() { + const frame = useCurrentFrame(); + const { dikey, w, h } = useDuzen(); + // BAM (15. kare, lens kesmesi) anında büyükten oturur + const otur = interpolate(frame, [15, 26], [1.3, 1], { extrapolateLeft: "clamp", extrapolateRight: "clamp", easing: Easing.out(Easing.cubic) }); + // Patlayan pikseller + const kareler: ReactNode[] = []; + const d = frame - 15; + if (d >= 0 && d < 40) { + for (let n = 0; n < 28; n++) { + const aci = (n / 28) * Math.PI * 2 + hash(n, 2, 7); + const uz = (dikey ? 260 : 300) + Easing.out(Easing.cubic)(Math.min(1, d / 30)) * (dikey ? 420 : 620) * (0.5 + hash(n, 3, 1) * 0.5); + const G = 24; + const x = Math.round((w / 2 + Math.cos(aci) * uz) / G) * G; + const y = Math.round((h / 2 - 40 + Math.sin(aci) * uz * 0.7) / G) * G; + const b = 30 * (1 - d / 42); + kareler.push(); + } + } + return ( + + + + + {kareler} + + +
+ +
+ + Aklına takılanı sor. + +
+ Gece 3'te bile. Sıkılmaz, yargılamaz. +
+
+
+
+ ); +} + +/* ------------------------------------------------------------------ */ +/* Sohbet — tam ekran danışman; imleç yalnız yazarken/gönderirken */ +/* ------------------------------------------------------------------ */ +// Gerçek YÖK Atlas 2026 verisi: İzmir, İngilizce Bilgisayar Mühendisliği, devlet +const SONUCLAR = [ + { uni: "Ege Üniversitesi", taban: 44589, risk: "riskli" as const }, + { uni: "Dokuz Eylül Üniversitesi", taban: 52071, risk: "az-riskli" as const }, + { uni: "İzmir Katip Çelebi Üniversitesi", taban: 71833, risk: "guvenli" as const }, +]; +const TS = ZAMAN.tiklar.sohbet; // [girdi1, gönder1, girdi2, gönder2] +const YS = ZAMAN.yazma.sohbet; + +type Mesaj = + | { tur: "kullanici"; metin: string; yaz: number[]; gonder: number } + | { tur: "arac"; ad: string; detay: string; bas: number; bitis: number } + | { tur: "cevap"; bas: number; tahmin: number; Icerik: () => ReactNode }; + +function SonucListesi({ olcek }: { olcek: number }) { + const frame = useCurrentFrame(); + return ( +
+
Sıralamana göre İzmir'de üç devlet seçeneği var:
+ {SONUCLAR.map((s, i) => { + const g = yay(frame, 100 + i * 7, 16); + return ( +
+
+
{s.uni}
+
Bilgisayar Mühendisliği · Devlet
+
+
+ + + Taban {trSayi(s.taban)} + +
+
+ ); + })} +
+ ); +} + +export function Sohbet() { + const frame = useCurrentFrame(); + const { dikey, w, h } = useDuzen(); + const o = dikey ? 1 : 0.86; // ölçek + const L = dikey + ? { x: 50, w: 980, ustY: 80, ustH: 130, altY: 1630, altH: 170 } + : { x: 260, w: 1400, ustY: 30, ustH: 110, altY: 900, altH: 130 }; + const gonderX = L.x + L.w - L.altH / 2 - 10; + const altMerkez = L.altY + L.altH / 2; + + const SENARYO: Mesaj[] = [ + { tur: "kullanici", metin: "İzmir'de İngilizce bilgisayar mühendisliği var mı?", yaz: YS[0], gonder: TS[1] }, + { tur: "arac", ad: "program_ara", detay: "İzmir · İngilizce", bas: TS[1] + 6, bitis: TS[1] + 32 }, + { tur: "cevap", bas: 90, tahmin: 620 * o, Icerik: () => }, + { tur: "kullanici", metin: "Aslında İzmir dışına çıkmak istemiyorum.", yaz: YS[1], gonder: TS[3] }, + { tur: "arac", ad: "profil_guncelle", detay: "şehir: İzmir", bas: TS[3] + 6, bitis: TS[3] + 22 }, + { + tur: "cevap", + bas: 224, + tahmin: 260 * o, + Icerik: () => ( +
+
Tamam, profilini güncelledim. Yeni listende yalnız İzmir'deki programlar olacak.
+ + + Şehir: İzmir + + +
+ ), + }, + ]; + + const yazilan = (() => { + for (const m of SENARYO) { + if (m.tur !== "kullanici") continue; + if (frame >= m.yaz[0] && frame < m.gonder) { + const n = Math.floor(ara(frame, [m.yaz[0], m.yaz[1]], [0, m.metin.length], (t) => t)); + return m.metin.slice(0, n); + } + } + return ""; + })(); + const basis = TS.some((t, i) => i % 2 === 1 && frame >= t - 2 && frame < t + 3); + + const girdiX = L.x + 260; + const imlec1: Durak[] = [ + { f: 8, x: w * 0.72, y: altMerkez - 260 }, + { f: TS[0], x: girdiX, y: altMerkez + 10, g: 12 }, + { f: TS[1], x: gonderX, y: altMerkez, g: 12 }, + { f: 72, x: gonderX - 60, y: altMerkez - 220, g: 12 }, + ]; + const imlec2: Durak[] = [ + { f: 156, x: w * 0.7, y: altMerkez - 300 }, + { f: TS[2], x: girdiX, y: altMerkez + 10, g: 10 }, + { f: TS[3], x: gonderX, y: altMerkez, g: 12 }, + { f: 216, x: gonderX - 80, y: altMerkez - 240, g: 12 }, + ]; + const nokta = (f: number) => (f < 120 ? imlecKonumu(imlec1, f) : imlecKonumu(imlec2, f)); + + return ( + + + {/* Üst şerit */} +
+ + + {frame >= TS[3] ? 58 : frame >= TS[1] ? 59 : 60} soru hakkı + +
+ {/* Mesajlar */} +
+ {SENARYO.map((m, i) => { + const bas = m.tur === "kullanici" ? m.gonder : m.bas; + if (frame < bas) return null; + const g = yay(frame, bas, 16); + const tahmin = m.tur === "kullanici" ? 200 * o : m.tur === "arac" ? 90 * o : m.tahmin; + const kap = { maxHeight: g >= 0.999 ? undefined : g * tahmin, overflow: "hidden" as const, flexShrink: 0 }; + if (m.tur === "kullanici") { + return ( +
+
{m.metin}
+
+ ); + } + if (m.tur === "arac") { + const bitti = frame >= m.bitis; + return ( +
+ + + + + {bitti ? : } + {m.ad} + · {m.detay} + {!bitti ? · YÖK Atlas'ta arıyor{".".repeat(1 + (Math.floor(frame / 6) % 3))} : null} + +
+ ); + } + return ( +
+
+ +
+
+ ); + })} +
+ {/* Yazma kutusu */} +
+ + {yazilan || "Listen hakkında bir şey sor…"} + {yazilan && Math.floor(frame / 8) % 2 === 0 ? | : null} + + + + +
+
+ Havuz dışına çıkamaz, uydurma bölüm önermez +
+ + +
+
+ ); +} + +/* ------------------------------------------------------------------ */ +/* Paket — Apple hızlı kesmeleri: trend, PDF, revizyon */ +/* ------------------------------------------------------------------ */ +const TREND = [37831, 27534, 21602, 30797, 42108, 55158]; // AYBÜ Bilgisayar, 2021–2026 + +function PaketKarti({ ikon, baslik, alt, children }: { ikon: IkonAdi; baslik: string; alt: string; children: ReactNode }) { + const frame = useCurrentFrame(); + const { dikey } = useDuzen(); + return ( + +
+
+ +
+ + {baslik} + +
+ {alt} +
+
+
{children}
+
+ ); +} + +function TrendGorseli() { + const frame = useCurrentFrame(); + const { dikey } = useDuzen(); + const gW = dikey ? 860 : 760; + const gH = dikey ? 420 : 440; + const noktalar = TREND.map((v, i) => [(i / (TREND.length - 1)) * gW, ((v - 15000) / 45000) * gH]); + const c = ara(frame, [6, 34], [0, 1]); + const yol = noktalar.map((p, i) => `${i ? "L" : "M"}${p[0]},${p[1]}`).join(" "); + return ( + + + {noktalar.map((p, i) => ( + = i / (TREND.length - 1) ? 1 : 0}> + + + {2021 + i} + + + ))} + + ); +} + +function PdfGorseli() { + const frame = useCurrentFrame(); + const { dikey } = useDuzen(); + const k = dikey ? 1.6 : 1.5; + return ( +
+ {[2, 1, 0].map((i) => { + const s = yay(frame, 4 + (2 - i) * 5, 18); + return ( +
+ {i === 0 ? ( + <> +
+ {[R.riskli, R.azRiskli, R.guvenli].map((c) => ( + + ))} +
+ {[1, 0.8, 0.9, 0.6, 0.75, 0.5].map((wd, j) => ( + + ))} + + ) : null} +
+ ); + })} +
+ ); +} + +const REVIZYON = [ + { ad: "Ege · Bilgisayar Müh.", renk: R.riskli }, + { ad: "Dokuz Eylül · Bilgisayar Müh.", renk: R.azRiskli }, + { ad: "Katip Çelebi · Bilgisayar Müh.", renk: R.guvenli }, +]; + +function RevizyonGorseli() { + const frame = useCurrentFrame(); // alt dizi içi kare (0–45) + const { dikey } = useDuzen(); + const [tut, birak] = ZAMAN.tiklar.paket.map((t) => t - 90); + const SAT = dikey ? 130 : 120; + const g = dikey ? 880 : 760; + const ilerle = ara(frame, [tut, birak], [0, 1], Easing.inOut(Easing.cubic)); + const tutuldu = frame >= tut && frame < birak + 2; + return ( +
+ {REVIZYON.map((r, i) => { + const gecis = i === 1 ? (2 * ilerle - 0.5) / 0.8 : (2 * ilerle - 1.3) / 0.7; + const kay = Easing.inOut(Easing.cubic)(Math.min(1, Math.max(0, gecis))); + const y = i === 0 ? ilerle * 2 * SAT : (i - kay) * SAT; + const kalk = i === 0 && tutuldu ? 1 : 0; + return ( +
+ + {r.ad} + + {Array.from({ length: 6 }, (_, k) => ( + + ))} + +
+ ); + })} +
+ ); +} + +export function Paket() { + const { dikey, w, h } = useDuzen(); + const [tut, birak] = ZAMAN.tiklar.paket; + // Revizyon kartında imleç ilk satırı tutup en alta sürükler (kamera takip eder) + const SAT = dikey ? 130 : 120; + const gW = dikey ? 880 : 760; + const kartX = dikey ? (w - gW) / 2 : w * 0.5 + 60 + (w * 0.5 - 220 - gW) / 2; + const kartY = dikey ? h / 2 + 230 : h / 2 - (3 * SAT) / 2; + const tutX = kartX + gW - 50; + const duraklar: Durak[] = [ + { f: 92, x: tutX + 120, y: kartY + 360 }, + { f: tut, x: tutX, y: kartY + (SAT - 20) / 2, g: 8 }, + { f: birak, x: tutX + 24, y: kartY + 2 * SAT + (SAT - 20) / 2, g: birak - tut }, + { f: 134, x: tutX + 90, y: kartY + 2 * SAT + 120, g: 10 }, + ]; + const nokta = (f: number) => imlecKonumu(duraklar, f); + return ( + + + + + + + + + + + + + + + + + + + + + + ); +} + +/* ------------------------------------------------------------------ */ +/* Dürüstlük — Outline Fill Track: "Çünkü kimse veremez." */ +/* ------------------------------------------------------------------ */ +export function Durust() { + const frame = useCurrentFrame(); + const { dikey, w } = useDuzen(); + const alt = yay(frame, 86, 18); + return ( + + + +
+ +
+
+ Yerleşme garantisi + vermiyoruz. +
+ +
+
+ Veren olursa, uzak dur. Biz riski satır satır gösteririz. +
+
+ {(["guvenli", "az-riskli", "riskli"] as const).map((r, i) => ( + + + + ))} +
+
+
+
+
+ ); +} + +/* ------------------------------------------------------------------ */ +/* Hazır — sessizlik */ +/* ------------------------------------------------------------------ */ +export function Hazir() { + const frame = useCurrentFrame(); + const { dikey } = useDuzen(); + const kare = dikey ? 64 : 56; + return ( + + +
+ {[R.logoMavi, R.logoSeftali, R.logoTuruncu].map((c, i) => ( +
+ ))} +
+
+ Hazır mısın? +
+ + + ); +} + +/* ------------------------------------------------------------------ */ +/* Final — logo + tipografi + buton; tık → çöküş → boş ekran (döngü) */ +/* ------------------------------------------------------------------ */ +export function Final() { + const frame = useCurrentFrame(); + const { dikey, w, h } = useDuzen(); + const [tik] = ZAMAN.tiklar.final; + const otur = interpolate(frame, [15, 26], [1.2, 1], { extrapolateLeft: "clamp", extrapolateRight: "clamp", easing: Easing.out(Easing.cubic) }); + const bas = ara(frame, [tik - 2, tik + 1], [0, 1]) * (1 - ara(frame, [tik + 5, tik + 10], [0, 1])); + const cok = ara(frame, [tik + 4, tik + 30], [0, 1], Easing.bezier(0.7, 0, 0.84, 0)); + // Buton kadrajda sabit bir noktada (imleç ve çöküş kökeni) + const butonY = dikey ? h / 2 + 350 : h / 2 + 260; + const duraklar: Durak[] = [ + { f: 84, x: w / 2 + (dikey ? 300 : 520), y: butonY + (dikey ? 380 : 200) }, + { f: tik - 4, x: w / 2 + 60, y: butonY + 8, g: 26 }, + { f: tik, x: w / 2 + 60, y: butonY + 8 }, + { f: 170, x: w / 2, y: h / 2, g: 30 }, + ]; + const nokta = (f: number) => imlecKonumu(duraklar, f); + return ( + + + +
+ + +
+
+ Listeni kur, + + panikle değil. + +
+
+ +
+
+ +
+
+ ); +} diff --git a/src/sinema.tsx b/src/sinema.tsx new file mode 100644 index 0000000..1545678 --- /dev/null +++ b/src/sinema.tsx @@ -0,0 +1,201 @@ +import type { CSSProperties, ReactNode } from "react"; +import { AbsoluteFill, Easing, Img, interpolate, staticFile, useCurrentFrame, useVideoConfig } from "remotion"; +import { R, hash } from "./tema"; +import { vurusCarpani, type Vurus } from "./vurus"; + +/* ------------------------------------------------------------------ */ +/* Kamera — sahneyi takip eden sanal kamera */ +/* ------------------------------------------------------------------ */ + +export type Anahtar = { + /** Sahne içi kare */ + f: number; + /** Yakınlık (1 = tam kadraj) */ + s?: number; + /** Kadrajın merkezine getirilecek noktanın merkeze uzaklığı (px, ölçeksiz uzayda) */ + x?: number; + y?: number; + /** Hafif yatış (derece) */ + r?: number; +}; + +const kameraEgri = Easing.bezier(0.65, 0, 0.35, 1); + +export function degerAl(anahtarlar: Anahtar[], frame: number, alan: "s" | "x" | "y" | "r", varsayilan: number): number { + const noktalar = anahtarlar.filter((a) => a[alan] != null).map((a) => ({ f: a.f, v: a[alan] as number })); + if (noktalar.length === 0) return varsayilan; + if (frame <= noktalar[0].f) return noktalar[0].v; + for (let i = 0; i < noktalar.length - 1; i++) { + const a = noktalar[i]; + const b = noktalar[i + 1]; + if (frame <= b.f) return interpolate(frame, [a.f, b.f], [a.v, b.v], { easing: kameraEgri }); + } + return noktalar[noktalar.length - 1].v; +} + +/** + * Anahtar kareler arasında yumuşak (ease-in-out) kamera: yakınlaşır, + * uzaklaşır, kaydırır. Üstüne çok hafif bir el kamerası salınımı biner. + */ +export function Kamera({ + anahtarlar, + children, + salinim = 1, + vuruslar, +}: { + anahtarlar: Anahtar[]; + children: ReactNode; + salinim?: number; + /** Müziğe oturan ani zoom in / zoom out'lar (vurus.ts) */ + vuruslar?: Vurus[]; +}) { + const frame = useCurrentFrame(); + const s = degerAl(anahtarlar, frame, "s", 1) * vurusCarpani(frame, vuruslar); + const x = degerAl(anahtarlar, frame, "x", 0); + const y = degerAl(anahtarlar, frame, "y", 0); + const r = degerAl(anahtarlar, frame, "r", 0); + const sx = Math.sin(frame / 47) * 5 * salinim + Math.sin(frame / 19) * 1.5 * salinim; + const sy = Math.cos(frame / 53) * 4 * salinim; + const sr = Math.sin(frame / 71) * 0.15 * salinim; + return ( + + {children} + + ); +} + +/* ------------------------------------------------------------------ */ +/* Film dokusu: gren + vinyet */ +/* ------------------------------------------------------------------ */ + +const KARO = 768; + +/** + * Film greni. Karolar Remotion'ın 'i ile çizilir: her kare görselin + * yüklenmesini bekler. (CSS background-image beklenmiyordu; bazı karelerde + * gren hiç çizilmeyince ekran bir anlığına açılıp "siyah-beyaz" titriyordu.) + */ +export function Gren({ guc = 1 }: { guc?: number }) { + const frame = useCurrentFrame(); + const { width, height } = useVideoConfig(); + const n = (frame % 6) + 1; + const ox = -Math.floor(hash(frame, 1, 4) * KARO); + const oy = -Math.floor(hash(frame, 2, 9) * KARO); + const sut = Math.ceil((width - ox) / KARO); + const sat = Math.ceil((height - oy) / KARO); + const karolar: ReactNode[] = []; + for (let j = 0; j < sat; j++) { + for (let i = 0; i < sut; i++) { + karolar.push( + , + ); + } + } + return ( + + {karolar} + + ); +} + +export function Vinyet() { + return ( + + ); +} + +/* ------------------------------------------------------------------ */ +/* Odak dışı pikseller — alan derinliği */ +/* ------------------------------------------------------------------ */ + +/** + * Kameranın odağı dışında kalan iri, bulanık piksel kareleri: sahneye + * derinlik verir, markanın kare dilini "bokeh" olarak taşır. Yavaşça süzülür. + */ +export function OdakDisiPikseller({ seed = 1, adet = 9, guc = 1 }: { seed?: number; adet?: number; guc?: number }) { + const frame = useCurrentFrame(); + const { width, height } = useVideoConfig(); + const renkler = [R.primary, R.turuncu, R.logoSeftali, R.primary]; + return ( + + {Array.from({ length: adet }, (_, i) => { + const boy = 90 + hash(i, seed, 1) * 220; + const yakin = hash(i, seed, 2) > 0.5; + const x = hash(i, seed, 3) * width - boy / 2 + Math.sin((frame + i * 40) / (90 + i * 7)) * 40; + const y = hash(i, seed, 4) * height - boy / 2 - frame * (0.4 + hash(i, seed, 5) * 0.8) * (yakin ? 1.4 : 0.7); + return ( +
+ ); + })} + + ); +} + +/* ------------------------------------------------------------------ */ +/* Maskeli satır — Apple'ın metin açılışı */ +/* ------------------------------------------------------------------ */ + +/** Satır, görünmez bir çizginin arkasından yükselir (overflow maskesi). */ +export function MaskeSatir({ + children, + gecikme = 0, + sure = 22, + cikis, + style, +}: { + children: ReactNode; + gecikme?: number; + sure?: number; + /** Bu karede satır yukarı kayarak maskenin arkasına çekilir */ + cikis?: number; + style?: CSSProperties; +}) { + const frame = useCurrentFrame(); + const g = interpolate(frame, [gecikme, gecikme + sure], [0, 1], { + extrapolateLeft: "clamp", + extrapolateRight: "clamp", + easing: Easing.bezier(0.16, 1, 0.3, 1), + }); + const c = + cikis == null + ? 0 + : interpolate(frame, [cikis, cikis + 14], [0, 1], { + extrapolateLeft: "clamp", + extrapolateRight: "clamp", + easing: Easing.bezier(0.7, 0, 0.84, 0), + }); + return ( + + {children} + + ); +} diff --git a/src/tema.ts b/src/tema.ts new file mode 100644 index 0000000..7c4fe69 --- /dev/null +++ b/src/tema.ts @@ -0,0 +1,87 @@ +import { Easing, interpolate, spring } from "remotion"; + +/** + * KolayTercih marka değerleri — kaynak: kolaytercih/docs/tasarim/DESIGN.md + * ve src/app/globals.css. Video tarayıcı dışı bir ortam olduğu için Tailwind + * sınıflarının hex karşılıkları burada tek yerde durur. + */ +export const R = { + primary: "#3b82f6", // --primary oklch(0.623 0.188 259.8) + turuncu: "#ff6900", // orange-500 (v4) + turuncuKoyu: "#f54900", // orange-600 — yalnız CTA zemin hover'ı + logoTuruncu: "#ff5a00", // logodaki turuncu kare + logoSeftali: "#fdc7a7", // logodaki şeftali kare + logoMavi: "#3b82f6", + slate900: "#0f172a", + slate800: "#1e293b", + slate700: "#334155", + slate600: "#475569", + slate500: "#64748b", + slate400: "#94a3b8", + slate300: "#cbd5e1", + slate200: "#e2e8f0", + slate100: "#f1f5f9", + slate50: "#f8fafc", + beyaz: "#ffffff", + murekkep: "#0a0a0a", // logo yazısı + // Risk dili — her zaman metin etiketiyle (DESIGN.md §2.2) + guvenli: "#00bc7d", // emerald-500 + guvenliYazi: "#009966", // emerald-600 + azRiskli: "#fe9a00", // amber-500 + azRiskliYazi: "#e17100", // amber-600 + riskli: "#fb2c36", // red-500 + riskliYazi: "#e7000b", // red-600 +}; + +export const FONT = { + baslik: "'Cal Sans', 'Clarity City', sans-serif", + govde: "'Clarity City', sans-serif", + vurgu: "'Bricolage', 'Clarity City', sans-serif", +}; + +export const FPS = 30; +/** 120 BPM ızgara: bir vuruş 15 kare, bir ölçü 60 kare. Kesmeler vuruşa oturur. */ +export const VURUS = 15; + +/** Sıçramasız yay — Apple'ın damping 1'i (DESIGN.md §4.17 yakınlaşma). */ +export const yay = (frame: number, gecikme = 0, sure = 24) => + spring({ + frame: frame - gecikme, + fps: FPS, + config: { damping: 200 }, + durationInFrames: sure, + }); + +/** Logo kareleri ve CTA gibi "dokunulabilir" şeyler için hafif tepkili yay. */ +export const zipla = (frame: number, gecikme = 0) => + spring({ + frame: frame - gecikme, + fps: FPS, + config: { damping: 13, stiffness: 170, mass: 0.7 }, + }); + +export const ara = ( + frame: number, + giris: [number, number], + cikis: [number, number], + ease: (t: number) => number = Easing.bezier(0.22, 1, 0.36, 1), +) => + interpolate(frame, giris, cikis, { + extrapolateLeft: "clamp", + extrapolateRight: "clamp", + easing: ease, + }); + +/** Deterministik sözde-rastgele — sitedeki pixel-decor ile aynı hash. */ +export const hash = (x: number, y: number, seed = 0) => { + const n = Math.sin(x * 127.1 + y * 311.7 + seed * 74.7) * 43758.5453; + return n - Math.floor(n); +}; + +/** Hero dokusunun opaklık paleti (pixel-decor OPACITIES). */ +export const OPAKLIKLAR = [0.3, 0.16, 0.24, 0.12, 0.27, 0.19]; +export const opaklik = (x: number, y: number) => + OPAKLIKLAR[(x * 7 + y * 11) % OPAKLIKLAR.length]; + +export const trSayi = (n: number) => + Math.round(n).toLocaleString("tr-TR", { maximumFractionDigits: 0 }); diff --git a/src/vurus.ts b/src/vurus.ts new file mode 100644 index 0000000..5a3f9dd --- /dev/null +++ b/src/vurus.ts @@ -0,0 +1,45 @@ +/** + * Sahne içi "punch" zoomları: müziğin vurgularına oturan ani yakınlaşma + * (g > 0) ya da geri tepme (g < 0). Kamera bu listeyi okur; Lansman da aynı + * kareleri hareket bulanıklığı (motion blur) pencereleri olarak kullanır. + * Kareler sahne içidir. + */ +export type Vurus = { f: number; g: number }; + +export const VURUSLAR: Record = { + acilis: [{ f: 16, g: 0.06 }], + secenek: [{ f: 52, g: 0.06 }], + kaos: [{ f: 45, g: -0.05 }, { f: 75, g: 0.05 }], + logo: [{ f: 15, g: 0.1 }, { f: 52, g: 0.04 }], + adim1: [{ f: 124, g: 0.07 }], + adim2: [{ f: 140, g: 0.06 }], + adim3: [{ f: 90, g: 0.07 }], + danisman: [{ f: 15, g: 0.12 }], + sohbet: [{ f: 90, g: 0.04 }, { f: 224, g: 0.04 }], + paket: [{ f: 0, g: 0.05 }, { f: 45, g: 0.05 }, { f: 90, g: 0.05 }], + durust: [{ f: 36, g: 0.08 }], + final: [{ f: 15, g: 0.1 }, { f: 120, g: 0.08 }], +}; + +/** Punch eğrisi: 5 karede sert yaklaş, ~10 karede yumuşakça geri gel. */ +export function vurusCarpani(frame: number, vuruslar: Vurus[] | undefined) { + if (!vuruslar) return 1; + let c = 1; + for (const v of vuruslar) { + const d = frame - v.f; + if (d < 0 || d > 30) continue; + const e = d < 5 ? 1 - (1 - d / 5) ** 3 : Math.exp(-(d - 5) / 8); + c *= 1 + v.g * e; + } + return c; +} + +/** Kameranın imlece hızla kilitlendiği / bıraktığı anlar: hareket bulanıklığı pencereleri. */ +export const BULANIK: Record = { + adim1: [[18, 32], [128, 142]], + adim2: [[46, 58], [118, 140]], + adim3: [[54, 68], [104, 128]], + sohbet: [[8, 20], [60, 80], [156, 168], [204, 214]], + paket: [[92, 102]], + final: [[108, 116], [124, 156]], +}; diff --git a/src/zaman.json b/src/zaman.json new file mode 100644 index 0000000..93576c2 --- /dev/null +++ b/src/zaman.json @@ -0,0 +1,31 @@ +{ + "fps": 30, + "sahneler": [ + { "id": "acilis", "bas": 0 }, + { "id": "secenek", "bas": 105, "gecis": { "tur": "zoomBlur", "sure": 18 } }, + { "id": "kaos", "bas": 210, "gecis": { "tur": "zoomBlur", "sure": 18 } }, + { "id": "nefes", "bas": 315, "gecis": { "tur": "kesme" }, "sessiz": true }, + { "id": "logo", "bas": 375, "gecis": { "tur": "lensZoom", "sure": 30 }, "bam": true }, + { "id": "adim1", "bas": 495, "gecis": { "tur": "zoomBlur", "sure": 18 } }, + { "id": "adim2", "bas": 660, "gecis": { "tur": "zoomBlur", "sure": 18 } }, + { "id": "adim3", "bas": 840, "gecis": { "tur": "zoomBlur", "sure": 18 } }, + { "id": "off", "bas": 1005, "gecis": { "tur": "kesme" }, "sessiz": true }, + { "id": "danisman", "bas": 1065, "gecis": { "tur": "lensZoom", "sure": 30 }, "bam": true }, + { "id": "sohbet", "bas": 1140, "gecis": { "tur": "zoomBlur", "sure": 18 } }, + { "id": "paket", "bas": 1440, "gecis": { "tur": "zoomBlur", "sure": 18 } }, + { "id": "durust", "bas": 1575, "gecis": { "tur": "zoomBlur", "sure": 18 } }, + { "id": "hazir", "bas": 1695, "gecis": { "tur": "kesme" }, "sessiz": true }, + { "id": "final", "bas": 1740, "gecis": { "tur": "lensZoom", "sure": 30 }, "bam": true } + ], + "son": 1920, + "tiklar": { + "adim1": [30, 70, 80, 90, 100, 124], + "sohbet": [18, 56, 166, 200], + "paket": [100, 122], + "final": [120] + }, + "yazma": { + "adim1": [[34, 58]], + "sohbet": [[22, 50], [170, 194]] + } +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..c8eac75 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "bundler", + "jsx": "react-jsx", + "strict": true, + "noEmit": true, + "skipLibCheck": true, + "noUnusedLocals": true, + "lib": ["DOM", "ES2022"], + "resolveJsonModule": true + }, + "include": ["src"], + "exclude": ["src/eski"] +}