diff --git a/README.md b/README.md
index d6b2032..e8c0b1d 100644
--- a/README.md
+++ b/README.md
@@ -2,17 +2,21 @@
# YMB Site
-YMB 公开站点响应式首页原型,参考 YouTube 的内容发现信息架构,支持桌面端与移动端。
+YMB 公开站点 React + Vite 项目,参考 YouTube 的内容发现信息架构,支持桌面端与移动端。
## 运行
-直接用浏览器打开 `index.html`,或在当前目录启动任意静态文件服务器。
+```bash
+pnpm install
+pnpm dev
+pnpm build
+```
## 包含
- 桌面端:顶部搜索、侧边导航、推荐网格、Shorts 横滑区、频道订阅。
- 移动端:顶部紧凑栏、双列内容、横滑 Shorts、底部导航。
-- 原生 JS:搜索、题材筛选、播放提示、关注切换。
+- React:搜索、题材筛选、播放提示、关注切换、移动端菜单。
- 多语言:印尼语 `?lang=id`、English `?lang=en`、简体中文 `?lang=zh`。
- SEO:动态 `lang/title/description`、canonical、hreflang、Open Graph、Twitter Card、JSON-LD、robots.txt、sitemap.xml。
diff --git a/index.html b/index.html
index 4bdb678..c04af66 100644
--- a/index.html
+++ b/index.html
@@ -3,46 +3,23 @@
-
+
-
-
+
-
+
-
-
-
- YMB · Cerita pendek, emosi besar
-
-
+
+
+
+
+
+
+
+ YMB · Short stories, big emotions
-
-
-
-
- ▶ymb
-
-
-
-
-
- YMB ORIGINALCerita pendek,
emosi besar.
Temukan drama yang bikin kamu terus menonton. Episode baru setiap hari.
-
-
- CHANNEL UNTUKMU
Ikuti cerita favoritmu
Kelola →
-
-
-
-
-
-
+
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..7cddcb0
--- /dev/null
+++ b/package.json
@@ -0,0 +1,9 @@
+{
+ "name": "ymb-site",
+ "private": true,
+ "version": "1.0.0",
+ "type": "module",
+ "scripts": {"dev":"vite","build":"vite build","preview":"vite preview"},
+ "dependencies": {"@vitejs/plugin-react":"latest","vite":"latest","react":"latest","react-dom":"latest","lucide-react":"latest"},
+ "devDependencies": {}
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
new file mode 100644
index 0000000..dc8e313
--- /dev/null
+++ b/pnpm-lock.yaml
@@ -0,0 +1,497 @@
+lockfileVersion: '9.0'
+
+settings:
+ autoInstallPeers: true
+ excludeLinksFromLockfile: false
+
+importers:
+
+ .:
+ dependencies:
+ '@vitejs/plugin-react':
+ specifier: latest
+ version: 6.3.0(vite@8.3.7)
+ lucide-react:
+ specifier: latest
+ version: 1.71.0(react@19.3.1)
+ react:
+ specifier: latest
+ version: 19.3.1
+ react-dom:
+ specifier: latest
+ version: 19.3.1(react@19.3.1)
+ vite:
+ specifier: latest
+ version: 8.3.7
+
+packages:
+
+ '@oxc-project/types@0.156.0':
+ resolution: {integrity: sha512-1Duyg48TdJNdyhEB8Xn1L36bT9Pv6GUtlKjHxOys8xkaF/rQq3XJE+bAurSK/AgIz5mGFsb77cn9Pb20+WCScw==}
+
+ '@rolldown/binding-android-arm-eabi@1.2.18':
+ resolution: {integrity: sha512-ubTG+4KlidvioxjOFdsdEa2F6JVGZb8m2ByJUqQ7fYzUeT1cEn194POcsQ10PKH4p//q0YQn4SB3YpSuzcA1Yg==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm]
+ os: [android]
+
+ '@rolldown/binding-android-arm64@1.2.18':
+ resolution: {integrity: sha512-2Xa1t/JeFGTZn5TbzgPppcCftF8sCVnTNvrIvoSdcZhoFzAuVy1L3xxQUQ4sfWDtwVzg1u6Yi1R3SlJugRNV2Q==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm64]
+ os: [android]
+
+ '@rolldown/binding-darwin-arm64@1.2.18':
+ resolution: {integrity: sha512-iUzw9HshVvrWci9gVXjxWS7/M/IcLvNo67vu2c/w9zKBz4hIvPRixdUpIW/iCfhldLaq9ER2hecsI9UY5z0/yA==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@rolldown/binding-darwin-x64@1.2.18':
+ resolution: {integrity: sha512-XuDHFzbFEtZl0QGJi92+fmG67tfnp1iZ9VOeDkwasGyTq2VG3/5PnJnvAr+MTzFh/rNScJ3VhN+XpZyyGsN7Jw==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [x64]
+ os: [darwin]
+
+ '@rolldown/binding-freebsd-x64@1.2.18':
+ resolution: {integrity: sha512-MM8opLfsLK15oEXDz6iBc0t3ahu+ew9jsAmha+ovZ33hSwYgqo7YmpRITqFTfW7xA71BLEQ3jJdTsaCmhB1c9w==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@rolldown/binding-linux-arm-gnueabihf@1.2.18':
+ resolution: {integrity: sha512-J6EoCWiy5RnFQzHrKyHXXevZ9lukQfGpnchX/zG/k/WC2nc9O9XyS1JgA8kI9VxMFggMbCPjpv7LcikriXfehw==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm]
+ os: [linux]
+
+ '@rolldown/binding-linux-arm64-gnu@1.2.18':
+ resolution: {integrity: sha512-HIkFuLF7CdDeki+1aCZLLbLq0hk/PHntb+z4/YCTo47JbksTD6jJEELIz92tYROmDmvMrNqUMqBi71BkJLDR+A==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm64]
+ os: [linux]
+
+ '@rolldown/binding-linux-arm64-musl@1.2.18':
+ resolution: {integrity: sha512-4oqWItLLYt7IceOnElCct8T2ek5p7PmD7LN04TRJRXGo5f3HtKTgGH6PhYRFK4C2ICkq/LMUU0BVPvvlAKhm8g==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm64]
+ os: [linux]
+
+ '@rolldown/binding-linux-ppc64-gnu@1.2.18':
+ resolution: {integrity: sha512-+AkRly5SrCEgnPhGRfub7pucASYEC2fTsxZg7VCcnozpA2+Wn/gCYy1t7LuJS67/xyJu/7noYBJncDUkbwgw2A==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@rolldown/binding-linux-s390x-gnu@1.2.18':
+ resolution: {integrity: sha512-Ttg1r9vVykMnQrE/GwYO9icKtXiuiMfRe/w74xJkeoVuvhRG+WtT6LEMOf58Yl63yd77lhHzO8Y55R8CC3XALA==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [s390x]
+ os: [linux]
+
+ '@rolldown/binding-linux-x64-gnu@1.2.18':
+ resolution: {integrity: sha512-xX1Tt4o5jUMma0vJsXp5eRZA9nOuyigb2dKJMMzZRmzMxLYGE9PaKKSqHPzPvSQgM8ZOEacSTQwDQcwWfh0xmQ==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [x64]
+ os: [linux]
+
+ '@rolldown/binding-linux-x64-musl@1.2.18':
+ resolution: {integrity: sha512-4VVlGI5K2kociZlW3lSN8VjG3TW5yQIlAcfCdBXMgGjJxZll15Fa4ocRHXHvIVVnwlibCw/NQajJrNDiqqpiiw==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [x64]
+ os: [linux]
+
+ '@rolldown/binding-openharmony-arm64@1.2.18':
+ resolution: {integrity: sha512-PApTsgH1jLYuyy3tSCz9De82Gq5qXwi7vImVZhEzTgX1C4150Cp5M2ZfL3pLvbCvbEHL0oJeC6pd6podtv8Y5w==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm64]
+ os: [openharmony]
+
+ '@rolldown/binding-win32-arm64-msvc@1.2.18':
+ resolution: {integrity: sha512-J2XjWjx/SP+4x0H4r6dz3Vzb5WVkg7re40Do9K7HJ3sK2/xcmZrWwakXlSwaMvOkvruLJCD4CL/3wqsIR/thgg==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm64]
+ os: [win32]
+
+ '@rolldown/binding-win32-x64-msvc@1.2.18':
+ resolution: {integrity: sha512-XwRJxRQvkuEtS0hIIt29QrqPJ+JzMJSNYHJw4ae/mc6uEarfRG1ceyg4HmmWiBdL/YCb6g7N5BLbSvmRJRYZHw==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [x64]
+ os: [win32]
+
+ '@rolldown/pluginutils@1.1.0':
+ resolution: {integrity: sha512-K+NU2HDCEem4sClgHH4ZZXwdJeNFkJo+hRk479DIwdIviAPN2hFJH1KHHwg8CxHQXvi0M5XY4deTXINfIiSG6w==}
+
+ '@vitejs/plugin-react@6.3.0':
+ resolution: {integrity: sha512-H9E955aVvD2F4zNHCw4eIZbIfF6xKldlh7MNu+hSU7xwQzh65lI/N+CJWqmfhi0PEjDXIt/H7T7J/4wU63KJiw==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ peerDependencies:
+ '@rolldown/plugin-babel': ^0.1.7 || ^0.2.0 || ^0.3.0
+ babel-plugin-react-compiler: ^1.0.0
+ oxc-transform-react: '>=0.152.0 <0.157.0'
+ vite: ^8.0.0
+ peerDependenciesMeta:
+ '@rolldown/plugin-babel':
+ optional: true
+ babel-plugin-react-compiler:
+ optional: true
+ oxc-transform-react:
+ optional: true
+
+ detect-libc@2.1.2:
+ resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
+ engines: {node: '>=8'}
+
+ fdir@6.5.0:
+ resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
+ engines: {node: '>=12.0.0'}
+ peerDependencies:
+ picomatch: ^3 || ^4
+ peerDependenciesMeta:
+ picomatch:
+ optional: true
+
+ fsevents@2.3.3:
+ resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
+ engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
+ os: [darwin]
+
+ lightningcss-android-arm64@1.33.0:
+ resolution: {integrity: sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [android]
+
+ lightningcss-darwin-arm64@1.33.0:
+ resolution: {integrity: sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [darwin]
+
+ lightningcss-darwin-x64@1.33.0:
+ resolution: {integrity: sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [darwin]
+
+ lightningcss-freebsd-x64@1.33.0:
+ resolution: {integrity: sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [freebsd]
+
+ lightningcss-linux-arm-gnueabihf@1.33.0:
+ resolution: {integrity: sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm]
+ os: [linux]
+
+ lightningcss-linux-arm64-gnu@1.33.0:
+ resolution: {integrity: sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [linux]
+
+ lightningcss-linux-arm64-musl@1.33.0:
+ resolution: {integrity: sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [linux]
+
+ lightningcss-linux-x64-gnu@1.33.0:
+ resolution: {integrity: sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [linux]
+
+ lightningcss-linux-x64-musl@1.33.0:
+ resolution: {integrity: sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [linux]
+
+ lightningcss-win32-arm64-msvc@1.33.0:
+ resolution: {integrity: sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [win32]
+
+ lightningcss-win32-x64-msvc@1.33.0:
+ resolution: {integrity: sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [win32]
+
+ lightningcss@1.33.0:
+ resolution: {integrity: sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==}
+ engines: {node: '>= 12.0.0'}
+
+ lucide-react@1.71.0:
+ resolution: {integrity: sha512-8YtvF+bqT/EMqAXU7kWvYdRu+TV9j4kS1eIS7IbN1j1Z1DgupbrTL0nDIygdqgBDTD7AJPP607VdVgzDTar4zA==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ nanoid@3.3.20:
+ resolution: {integrity: sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==}
+ engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
+ hasBin: true
+
+ picocolors@1.1.1:
+ resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
+
+ picomatch@4.0.7:
+ resolution: {integrity: sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==}
+ engines: {node: '>=12'}
+
+ postcss@8.5.32:
+ resolution: {integrity: sha512-LJlKCCAUFKOuZxipUdYw1NzIITEsPcgyOG9LkvRJvuKj/bRzbuRY0ARXvL0pWGpsZvn8jku+LMep2eU0seHp8g==}
+ engines: {node: ^10 || ^12 || >=14}
+
+ react-dom@19.3.1:
+ resolution: {integrity: sha512-Y0bARz53lhEC1NMCASs0/j+faB/zqt3R8hpvg1e8+61ZVJ4UL0UdC4cBbXfZMcIuhrfgeILTLe+ZeBCavQPtKQ==}
+ peerDependencies:
+ react: ^19.3.1
+
+ react@19.3.1:
+ resolution: {integrity: sha512-QNhWdt7nrgVWWQG58QuGsAR3jv3Hbe4mb4vz7w2csnHavciZJgda1CPNaldF6eVxv90ud3jUXpKlHL69gO38yw==}
+ engines: {node: '>=0.10.0'}
+
+ rolldown@1.2.18:
+ resolution: {integrity: sha512-C1uuT3UR60SShuCaPQFAAZyefJCWZlAKB02BV3BuPE2Rs4fevRQltEKoFO2tV5W9/eJhU5RieV6vJj3euulfNQ==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ hasBin: true
+
+ scheduler@0.28.0:
+ resolution: {integrity: sha512-juorfCmIkIw8tT+p5BXSm6PJjQF/ycEYmKyzURCIt/RaZIhL+PulbQ9Yu2z1HdOJDdqDTlxA1+xKBmHXJsczAw==}
+
+ source-map-js@1.2.2:
+ resolution: {integrity: sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==}
+ engines: {node: '>=0.10.0'}
+
+ tinyglobby@0.2.17:
+ resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==}
+ engines: {node: '>=12.0.0'}
+
+ vite@8.3.7:
+ resolution: {integrity: sha512-oxh0dMxrayBr33ScJvvBJ2HBQ2jc1TBrXKR2NyL/MJ9WPuX19mN2DXWUmywKdyw5RFVQBo8hmtb2FMC0LHrnng==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ hasBin: true
+ peerDependencies:
+ '@types/node': ^20.19.0 || >=22.12.0
+ '@vitejs/devtools': ^0.7.1 || ^0.8.0
+ esbuild: ^0.27.0 || ^0.28.0
+ jiti: '>=1.21.0'
+ less: ^4.0.0
+ sass: ^1.70.0
+ sass-embedded: ^1.70.0
+ stylus: '>=0.54.8'
+ sugarss: ^5.0.0
+ terser: ^5.16.0
+ tsx: ^4.8.1
+ yaml: ^2.4.2
+ peerDependenciesMeta:
+ '@types/node':
+ optional: true
+ '@vitejs/devtools':
+ optional: true
+ esbuild:
+ optional: true
+ jiti:
+ optional: true
+ less:
+ optional: true
+ sass:
+ optional: true
+ sass-embedded:
+ optional: true
+ stylus:
+ optional: true
+ sugarss:
+ optional: true
+ terser:
+ optional: true
+ tsx:
+ optional: true
+ yaml:
+ optional: true
+
+snapshots:
+
+ '@oxc-project/types@0.156.0': {}
+
+ '@rolldown/binding-android-arm-eabi@1.2.18':
+ optional: true
+
+ '@rolldown/binding-android-arm64@1.2.18':
+ optional: true
+
+ '@rolldown/binding-darwin-arm64@1.2.18':
+ optional: true
+
+ '@rolldown/binding-darwin-x64@1.2.18':
+ optional: true
+
+ '@rolldown/binding-freebsd-x64@1.2.18':
+ optional: true
+
+ '@rolldown/binding-linux-arm-gnueabihf@1.2.18':
+ optional: true
+
+ '@rolldown/binding-linux-arm64-gnu@1.2.18':
+ optional: true
+
+ '@rolldown/binding-linux-arm64-musl@1.2.18':
+ optional: true
+
+ '@rolldown/binding-linux-ppc64-gnu@1.2.18':
+ optional: true
+
+ '@rolldown/binding-linux-s390x-gnu@1.2.18':
+ optional: true
+
+ '@rolldown/binding-linux-x64-gnu@1.2.18':
+ optional: true
+
+ '@rolldown/binding-linux-x64-musl@1.2.18':
+ optional: true
+
+ '@rolldown/binding-openharmony-arm64@1.2.18':
+ optional: true
+
+ '@rolldown/binding-win32-arm64-msvc@1.2.18':
+ optional: true
+
+ '@rolldown/binding-win32-x64-msvc@1.2.18':
+ optional: true
+
+ '@rolldown/pluginutils@1.1.0': {}
+
+ '@vitejs/plugin-react@6.3.0(vite@8.3.7)':
+ dependencies:
+ '@rolldown/pluginutils': 1.1.0
+ vite: 8.3.7
+
+ detect-libc@2.1.2: {}
+
+ fdir@6.5.0(picomatch@4.0.7):
+ optionalDependencies:
+ picomatch: 4.0.7
+
+ fsevents@2.3.3:
+ optional: true
+
+ lightningcss-android-arm64@1.33.0:
+ optional: true
+
+ lightningcss-darwin-arm64@1.33.0:
+ optional: true
+
+ lightningcss-darwin-x64@1.33.0:
+ optional: true
+
+ lightningcss-freebsd-x64@1.33.0:
+ optional: true
+
+ lightningcss-linux-arm-gnueabihf@1.33.0:
+ optional: true
+
+ lightningcss-linux-arm64-gnu@1.33.0:
+ optional: true
+
+ lightningcss-linux-arm64-musl@1.33.0:
+ optional: true
+
+ lightningcss-linux-x64-gnu@1.33.0:
+ optional: true
+
+ lightningcss-linux-x64-musl@1.33.0:
+ optional: true
+
+ lightningcss-win32-arm64-msvc@1.33.0:
+ optional: true
+
+ lightningcss-win32-x64-msvc@1.33.0:
+ optional: true
+
+ lightningcss@1.33.0:
+ dependencies:
+ detect-libc: 2.1.2
+ optionalDependencies:
+ lightningcss-android-arm64: 1.33.0
+ lightningcss-darwin-arm64: 1.33.0
+ lightningcss-darwin-x64: 1.33.0
+ lightningcss-freebsd-x64: 1.33.0
+ lightningcss-linux-arm-gnueabihf: 1.33.0
+ lightningcss-linux-arm64-gnu: 1.33.0
+ lightningcss-linux-arm64-musl: 1.33.0
+ lightningcss-linux-x64-gnu: 1.33.0
+ lightningcss-linux-x64-musl: 1.33.0
+ lightningcss-win32-arm64-msvc: 1.33.0
+ lightningcss-win32-x64-msvc: 1.33.0
+
+ lucide-react@1.71.0(react@19.3.1):
+ dependencies:
+ react: 19.3.1
+
+ nanoid@3.3.20: {}
+
+ picocolors@1.1.1: {}
+
+ picomatch@4.0.7: {}
+
+ postcss@8.5.32:
+ dependencies:
+ nanoid: 3.3.20
+ picocolors: 1.1.1
+ source-map-js: 1.2.2
+
+ react-dom@19.3.1(react@19.3.1):
+ dependencies:
+ react: 19.3.1
+ scheduler: 0.28.0
+
+ react@19.3.1: {}
+
+ rolldown@1.2.18:
+ dependencies:
+ '@oxc-project/types': 0.156.0
+ '@rolldown/pluginutils': 1.1.0
+ optionalDependencies:
+ '@rolldown/binding-android-arm-eabi': 1.2.18
+ '@rolldown/binding-android-arm64': 1.2.18
+ '@rolldown/binding-darwin-arm64': 1.2.18
+ '@rolldown/binding-darwin-x64': 1.2.18
+ '@rolldown/binding-freebsd-x64': 1.2.18
+ '@rolldown/binding-linux-arm-gnueabihf': 1.2.18
+ '@rolldown/binding-linux-arm64-gnu': 1.2.18
+ '@rolldown/binding-linux-arm64-musl': 1.2.18
+ '@rolldown/binding-linux-ppc64-gnu': 1.2.18
+ '@rolldown/binding-linux-s390x-gnu': 1.2.18
+ '@rolldown/binding-linux-x64-gnu': 1.2.18
+ '@rolldown/binding-linux-x64-musl': 1.2.18
+ '@rolldown/binding-openharmony-arm64': 1.2.18
+ '@rolldown/binding-win32-arm64-msvc': 1.2.18
+ '@rolldown/binding-win32-x64-msvc': 1.2.18
+
+ scheduler@0.28.0: {}
+
+ source-map-js@1.2.2: {}
+
+ tinyglobby@0.2.17:
+ dependencies:
+ fdir: 6.5.0(picomatch@4.0.7)
+ picomatch: 4.0.7
+
+ vite@8.3.7:
+ dependencies:
+ lightningcss: 1.33.0
+ picomatch: 4.0.7
+ postcss: 8.5.32
+ rolldown: 1.2.18
+ tinyglobby: 0.2.17
+ optionalDependencies:
+ fsevents: 2.3.3
diff --git a/public/assets/Outfit-Bold.ttf b/public/assets/Outfit-Bold.ttf
new file mode 100644
index 0000000..0389ff2
Binary files /dev/null and b/public/assets/Outfit-Bold.ttf differ
diff --git a/public/assets/PlusJakartaSans-Regular.ttf b/public/assets/PlusJakartaSans-Regular.ttf
new file mode 100644
index 0000000..b655eb4
Binary files /dev/null and b/public/assets/PlusJakartaSans-Regular.ttf differ
diff --git a/public/assets/PlusJakartaSans-SemiBold.ttf b/public/assets/PlusJakartaSans-SemiBold.ttf
new file mode 100644
index 0000000..609def9
Binary files /dev/null and b/public/assets/PlusJakartaSans-SemiBold.ttf differ
diff --git a/public/assets/friends.jpg b/public/assets/friends.jpg
new file mode 100644
index 0000000..c8a06d1
Binary files /dev/null and b/public/assets/friends.jpg differ
diff --git a/public/assets/heir.jpg b/public/assets/heir.jpg
new file mode 100644
index 0000000..eb9208c
Binary files /dev/null and b/public/assets/heir.jpg differ
diff --git a/public/assets/hero.jpg b/public/assets/hero.jpg
new file mode 100644
index 0000000..d397b1f
Binary files /dev/null and b/public/assets/hero.jpg differ
diff --git a/public/assets/revenge.jpg b/public/assets/revenge.jpg
new file mode 100644
index 0000000..e82cb29
Binary files /dev/null and b/public/assets/revenge.jpg differ
diff --git a/public/assets/romance.jpg b/public/assets/romance.jpg
new file mode 100644
index 0000000..cc39851
Binary files /dev/null and b/public/assets/romance.jpg differ
diff --git a/public/assets/second-chance.jpg b/public/assets/second-chance.jpg
new file mode 100644
index 0000000..55683a8
Binary files /dev/null and b/public/assets/second-chance.jpg differ
diff --git a/public/assets/secret.jpg b/public/assets/secret.jpg
new file mode 100644
index 0000000..ce53b5a
Binary files /dev/null and b/public/assets/secret.jpg differ
diff --git a/public/assets/wedding.jpg b/public/assets/wedding.jpg
new file mode 100644
index 0000000..67da900
Binary files /dev/null and b/public/assets/wedding.jpg differ
diff --git a/src/main.jsx b/src/main.jsx
new file mode 100644
index 0000000..52c5599
--- /dev/null
+++ b/src/main.jsx
@@ -0,0 +1,24 @@
+// Author: fruit.com.cn
+import React, { useMemo, useState } from 'react';
+import { createRoot } from 'react-dom/client';
+import { Bell, Bookmark, Compass, History, Home, Menu, Play, Search, UserRound, X } from 'lucide-react';
+import './styles.css';
+
+const ASSET = '/assets/';
+const copy = {
+ id: { title: 'YMB · Cerita pendek, emosi besar', description: 'Temukan drama pendek terbaik dan episode baru setiap hari.', search: 'Cari drama, aktor, atau genre', hero: 'Cerita pendek,', accent: 'emosi besar.', watch: 'Mulai menonton', picks: 'Drama pilihan minggu ini', shorts: 'Cerita dalam satu tarikan napas', channels: 'Ikuti cerita favoritmu', follow: 'Ikuti', following: 'Mengikuti', genres: ['Semua', 'Romansa', 'Balas dendam', 'Keluarga', 'Rahasia'] },
+ en: { title: 'YMB · Short stories, big emotions', description: 'Discover the best short dramas and new episodes every day.', search: 'Search dramas, actors, or genres', hero: 'Short stories,', accent: 'big emotions.', watch: 'Watch now', picks: 'Drama picks this week', shorts: 'Stories in one breath', channels: 'Follow your favorite stories', follow: 'Follow', following: 'Following', genres: ['All', 'Romance', 'Revenge', 'Family', 'Secrets'] },
+ zh: { title: 'YMB · 短故事,大情绪', description: '发现精彩短剧,每天更新新剧集。', search: '搜索短剧、演员或题材', hero: '短故事,', accent: '大情绪。', watch: '立即观看', picks: '本周精选短剧', shorts: '一口气看完的故事', channels: '关注你喜欢的故事', follow: '关注', following: '已关注', genres: ['全部', '爱情', '复仇', '家庭', '秘密'] }
+};
+const videos = [['Pernikahan yang Tak Terduga','YMB Originals','Romansa','wedding.jpg','12:48'],['Rahasia di Balik Senyuman','Cerita Senja','Rahasia','secret.jpg','09:32'],['Pewaris yang Kembali','Drama Pilihan','Keluarga','heir.jpg','15:20'],['Kesempatan Kedua untuk Kita','YMB Originals','Kisah kedua','second-chance.jpg','11:04'],['Cinta Setelah Balas Dendam','Cerita Senja','Balas dendam','revenge.jpg','13:40'],['Sahabat yang Selalu Ada','Drama Pilihan','Keluarga','friends.jpg','08:16'],['Kota Kecil, Rahasia Besar','YMB Originals','Rahasia','hero.jpg','10:12'],['Satu Janji Terakhir','Drama Pilihan','Romansa','romance.jpg','07:55']];
+const shorts = [['Dia kembali setelah 5 tahun','second-chance.jpg'],['Ketika rahasia terbongkar','secret.jpg'],['Satu pesan yang mengubah semuanya','romance.jpg'],['Jangan percaya senyumnya','revenge.jpg'],['Pewaris tanpa nama','heir.jpg'],['Hari pernikahan itu','wedding.jpg']];
+const channels = [['YMB Originals','2,4 jt pengikut','Y','#ca4d43'],['Cerita Senja','986 rb pengikut','C','#5c4aa1'],['Drama Pilihan','742 rb pengikut','D','#2e7d71'],['Kisah Hari Ini','428 rb pengikut','K','#a06a39']];
+function useLanguage() { const initial = new URLSearchParams(location.search).get('lang'); return useState(copy[initial] ? initial : 'id'); }
+function App() {
+ const [lang, setLang] = useLanguage(); const text = copy[lang]; const [genre, setGenre] = useState(text.genres[0]); const [query, setQuery] = useState(''); const [menu, setMenu] = useState(false); const [toast, setToast] = useState('');
+ const filtered = useMemo(() => videos.filter((item) => (!query || item.join(' ').toLowerCase().includes(query.toLowerCase())) && (genre === text.genres[0] || item[2] === genre)), [genre, query, text]);
+ const notify = (message) => { setToast(message); window.setTimeout(() => setToast(''), 1600); };
+ const changeLanguage = (value) => { setLang(value); setGenre(copy[value].genres[0]); const url = new URL(window.location.href); url.searchParams.set('lang', value); window.history.replaceState(null, '', url); document.title = copy[value].title; document.documentElement.lang = value === 'zh' ? 'zh-CN' : value; };
+ return <>YMB ORIGINAL{text.hero}
{text.accent}
{text.description}
{text.picks}
Lihat semua →{text.genres.map((item) => )}
{filtered.map((item) =>
notify(`▶ ${item[0]}`)}>
{item[4]}{item[1][0]}{item[0]}{item[1]}
{(Math.random() * 900 + 100).toFixed(0)} rb tayangan · kemarin
)}
{text.shorts}
Explore →{shorts.map((item) =>
notify(`▶ ${item[0]}`)}>
{item[0]})}
{text.channels}
{channels.map((item) =>
{item[2]}{item[0]}{item[1]}
)}
{toast && {toast}
}{menu && }>;
+}
+createRoot(document.getElementById('root')).render();
diff --git a/src/styles.css b/src/styles.css
new file mode 100644
index 0000000..8c71331
--- /dev/null
+++ b/src/styles.css
@@ -0,0 +1,2 @@
+/* Author: fruit.com.cn */
+@font-face{font-family:Jakarta;src:url('/assets/PlusJakartaSans-Regular.ttf')}@font-face{font-family:Jakarta;src:url('/assets/PlusJakartaSans-SemiBold.ttf');font-weight:600}@font-face{font-family:Outfit;src:url('/assets/Outfit-Bold.ttf');font-weight:700}:root{--bg:#0f0f0f;--surface:#181818;--line:#303030;--muted:#aaa;--accent:#ff4b3e;--sidebar:230px}*{box-sizing:border-box}body{margin:0;background:var(--bg);color:#f1f1f1;font:14px Jakarta,Arial,sans-serif}a{color:inherit;text-decoration:none}button,input,select{font:inherit}.topbar{height:64px;position:sticky;top:0;z-index:5;display:flex;align-items:center;gap:24px;padding:0 28px;background:#0f0f0ff2;border-bottom:1px solid #222;backdrop-filter:blur(15px)}.brand{display:flex;align-items:center;gap:8px;font:700 26px Outfit;min-width:150px}.brand-symbol{display:grid;place-items:center;width:28px;height:28px;border-radius:8px;background:var(--accent)}.search-form{display:flex;max-width:620px;flex:1;margin:auto}.search-form input{width:100%;height:40px;padding:0 16px;border:1px solid #3b3b3b;border-radius:22px 0 0 22px;background:#121212;color:#fff}.search-form button{width:54px;border:1px solid #3b3b3b;border-left:0;border-radius:0 22px 22px 0;background:#282828;color:#fff;display:grid;place-items:center}.top-actions{display:flex;align-items:center;gap:14px}.top-actions select{border:1px solid #363636;border-radius:16px;background:#1d1d1d;color:#ddd;padding:6px 9px;font-size:11px}.profile-chip{display:grid;place-items:center;width:32px;height:32px;border-radius:50%;background:#d45a68}.icon-button{border:0;background:transparent;color:#fff;display:grid;place-items:center}.sidebar{position:fixed;top:64px;bottom:0;width:var(--sidebar);padding:20px 12px;background:var(--bg);border-right:1px solid #222;z-index:4}.sidebar nav{display:grid;gap:4px}.sidebar nav a,.genre-link{display:flex;align-items:center;gap:16px;width:100%;padding:11px 14px;border:0;border-radius:10px;background:transparent;color:#ccc;text-align:left;cursor:pointer}.sidebar nav a:hover,.genre-link:hover{background:#272727;color:#fff}.sidebar small{display:block;padding:14px;color:#888;font-size:10px;letter-spacing:.12em}.nav-rule{height:1px;background:var(--line);margin:18px 10px}.content{max-width:1500px;padding:28px 34px 80px calc(var(--sidebar) + 34px);margin:auto}.hero-card{position:relative;min-height:350px;overflow:hidden;border-radius:18px;background:#27191a;margin-bottom:44px}.hero-copy{position:relative;z-index:1;width:52%;padding:54px}.hero-copy small,.section-heading span{color:#ff786f;font-size:11px;letter-spacing:.12em;font-weight:600}.hero-copy h1{margin:12px 0;font:700 56px/1 Outfit;letter-spacing:-.04em}.hero-copy h1 em{color:#ff786f;font-style:normal}.hero-copy p{max-width:390px;color:#ccc;line-height:1.7}.primary-button,.chip,.channel-card button{border:0;border-radius:22px;padding:11px 17px;cursor:pointer;font-weight:600}.primary-button{background:#fff;color:#111}.hero-art{position:absolute;inset:0 0 0 40%}.hero-art img{width:100%;height:100%;object-fit:cover}.hero-art:after{content:'';position:absolute;inset:0;background:linear-gradient(90deg,#27191a,rgba(39,25,26,.72) 28%,transparent 80%)}section{margin-bottom:46px}.section-heading{display:flex;justify-content:space-between;align-items:end;margin-bottom:17px}.section-heading h2{margin:0;font:700 24px Outfit}.chip-row{display:flex;gap:8px;overflow:auto;margin-bottom:18px}.chip{background:#2b2b2b;color:#fff;white-space:nowrap}.chip.active,.chip:hover{background:#fff;color:#111}.video-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:24px 16px}.video-card{cursor:pointer;min-width:0}.thumb{position:relative;aspect-ratio:16/9;overflow:hidden;border-radius:10px;background:#222}.thumb img{width:100%;height:100%;object-fit:cover}.thumb b{position:absolute;right:7px;bottom:7px;padding:3px 6px;background:#000c;border-radius:4px;font-size:11px}.video-meta{display:flex;gap:9px;padding-top:9px}.channel-avatar{display:grid;place-items:center;flex:none;width:32px;height:32px;border-radius:50%;font-weight:700}.video-meta strong,.channel-card strong{display:block;line-height:1.4}.video-meta small,.channel-card small{display:block;color:#999;font-size:11px;line-height:1.45;margin-top:4px}.shorts-row{display:grid;grid-template-columns:repeat(6,1fr);gap:14px}.short-card{position:relative;aspect-ratio:9/14;overflow:hidden;border-radius:11px;cursor:pointer}.short-card img{width:100%;height:100%;object-fit:cover}.short-card:after{content:'';position:absolute;inset:45% 0 0;background:linear-gradient(transparent,#000c)}.short-card strong{position:absolute;z-index:1;bottom:10px;left:9px;right:9px;font-size:12px}.channel-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:12px}.channel-card{display:flex;align-items:center;gap:10px;padding:14px;border:1px solid var(--line);border-radius:11px}.channel-card button{margin-left:auto;padding:8px 11px;background:#fff;color:#111}.bottom-nav,.mobile-only{display:none}.toast{position:fixed;left:50%;bottom:24px;z-index:8;transform:translateX(-50%);background:#fff;color:#111;padding:11px 16px;border-radius:7px}.scrim{position:fixed;inset:0;z-index:3;background:#0008;border:0}@media(max-width:1100px){.video-grid{grid-template-columns:repeat(3,1fr)}.shorts-row{grid-template-columns:repeat(5,1fr)}.channel-grid{grid-template-columns:repeat(2,1fr)}}@media(max-width:760px){.topbar{height:58px;padding:0 14px;gap:12px}.mobile-only{display:grid}.sidebar{display:none;top:58px;width:245px}.sidebar.open{display:block}.desktop-only{display:none}.brand{min-width:auto;font-size:23px}.content{padding:20px 14px 90px}.hero-card{min-height:420px;margin:0 -14px 36px;border-radius:0;display:flex;align-items:flex-end}.hero-copy{width:100%;padding:24px}.hero-copy h1{font-size:42px}.hero-art{inset:0}.hero-art:after{background:linear-gradient(0deg,#27191a,rgba(39,25,26,.35) 55%,transparent)}.video-grid{grid-template-columns:repeat(2,1fr);gap:22px 12px}.shorts-row{display:flex;overflow:auto;margin-right:-14px}.short-card{flex:0 0 130px}.channel-grid{grid-template-columns:1fr}.bottom-nav{position:fixed;display:flex;justify-content:space-around;align-items:center;left:0;right:0;bottom:0;height:66px;z-index:5;background:#151515;border-top:1px solid #292929}.bottom-nav a{display:grid;place-items:center;gap:4px;color:#aaa;font-size:10px}.bottom-nav a:hover{color:#fff}}
diff --git a/vite.config.js b/vite.config.js
new file mode 100644
index 0000000..144d4e2
--- /dev/null
+++ b/vite.config.js
@@ -0,0 +1,5 @@
+// Author: fruit.com.cn
+import { defineConfig } from 'vite';
+import react from '@vitejs/plugin-react';
+
+export default defineConfig({ plugins: [react()] });