feat: initialize Next.js project with a comprehensive landing page structure, UI components, and assets.
41
.gitignore
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.*
|
||||
.yarn/*
|
||||
!.yarn/patches
|
||||
!.yarn/plugins
|
||||
!.yarn/releases
|
||||
!.yarn/versions
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# env files (can opt-in for committing if needed)
|
||||
.env*
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
8
LICENSE
Normal file
@@ -0,0 +1,8 @@
|
||||
COPYRIGHT (c) 2026 NihonBuzz (PT. Satu Meja Bertiga) - All Rights Reserved.
|
||||
|
||||
This source code is the proprietary property of NihonBuzz.
|
||||
Any unauthorized copying, distribution, reproduction, or modification of this file,
|
||||
via any medium, is strictly prohibited.
|
||||
|
||||
Proprietary and Confidential.
|
||||
Written by the NihonBuzz Tech Team.
|
||||
51
README.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# <img src="public/assets/logo-main.png" width="40" height="40" alt="NihonBuzz Logo" style="vertical-align: middle;"> NihonBuzz
|
||||
|
||||
**Connecting Indonesia & Japan through Education, Media, and Community.**
|
||||
|
||||
[](https://nihonbuzz.org)
|
||||
[](https://instagram.com/nihon_buzz)
|
||||
[](https://tiktok.com/@nihonbuzz)
|
||||
[](LICENSE)
|
||||
|
||||
---
|
||||
|
||||
## 🇯🇵 About Us
|
||||
**NihonBuzz** is an ed-tech and media ecosystem dedicated to bridging the gap between Indonesia and Japan. We empower the younger generation to explore Japanese language, culture, and career opportunities through a modern, industrial approach.
|
||||
|
||||
> *"Menjadi jembatan koneksi antara Indonesia dan Jepang melalui media, pendidikan, dan komunitas."*
|
||||
|
||||
## 🚀 Our Ecosystem
|
||||
|
||||
### 🎓 **NihonBuzz Academy**
|
||||
Professional non-formal education focusing on JLPT preparation (N5-N3) and career guidance.
|
||||
- **Curriculum**: Standardized for JLPT & NAT-TEST.
|
||||
- **Method**: Intensive Live Teaching + LMS Support.
|
||||
- **Target**: Zero Beginners to Job Seekers.
|
||||
|
||||
### 📰 **NihonBuzz Media**
|
||||
The leading platform for Japanese pop culture, lifestyle, and trends in Indonesia.
|
||||
- **Content**: Anime, Travel, Tech, and Cultural Insights.
|
||||
- **Reach**: Thousands of monthly readers and active community members.
|
||||
|
||||
### 🤝 **Community & Events**
|
||||
Organizing gatherings, study groups, and cultural festivals to foster a supportive environment for Japan enthusiasts.
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ Tech Stack (This Repository)
|
||||
This repository contains the source code for our **Company Profile & Landing Page**, built with a focus on performance and premium aesthetics.
|
||||
|
||||
| Core | Styling | UI Components | Animation |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
|  |  |  |  |
|
||||
|
||||
---
|
||||
|
||||
## 📍 Connect With Us
|
||||
- **Headquarters**: Jakarta Barat, Indonesia
|
||||
- **Email**: hello@nihonbuzz.org
|
||||
|
||||
---
|
||||
<p align="center">
|
||||
<sub>Built with ❤️ by the NihonBuzz Tech Team.</sub>
|
||||
</p>
|
||||
22
components.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"$schema": "https://ui.shadcn.com/schema.json",
|
||||
"style": "new-york",
|
||||
"rsc": true,
|
||||
"tsx": true,
|
||||
"tailwind": {
|
||||
"config": "",
|
||||
"css": "src/app/globals.css",
|
||||
"baseColor": "neutral",
|
||||
"cssVariables": true,
|
||||
"prefix": ""
|
||||
},
|
||||
"iconLibrary": "lucide",
|
||||
"aliases": {
|
||||
"components": "@/components",
|
||||
"utils": "@/lib/utils",
|
||||
"ui": "@/components/ui",
|
||||
"lib": "@/lib",
|
||||
"hooks": "@/hooks"
|
||||
},
|
||||
"registries": {}
|
||||
}
|
||||
18
eslint.config.mjs
Normal file
@@ -0,0 +1,18 @@
|
||||
import { defineConfig, globalIgnores } from "eslint/config";
|
||||
import nextVitals from "eslint-config-next/core-web-vitals";
|
||||
import nextTs from "eslint-config-next/typescript";
|
||||
|
||||
const eslintConfig = defineConfig([
|
||||
...nextVitals,
|
||||
...nextTs,
|
||||
// Override default ignores of eslint-config-next.
|
||||
globalIgnores([
|
||||
// Default ignores of eslint-config-next:
|
||||
".next/**",
|
||||
"out/**",
|
||||
"build/**",
|
||||
"next-env.d.ts",
|
||||
]),
|
||||
]);
|
||||
|
||||
export default eslintConfig;
|
||||
7
next.config.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import type { NextConfig } from "next";
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
/* config options here */
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
8381
package-lock.json
generated
Normal file
38
package.json
Normal file
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"name": "nihonbuzz",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "eslint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@radix-ui/react-accordion": "^1.2.12",
|
||||
"@radix-ui/react-avatar": "^1.1.11",
|
||||
"@radix-ui/react-dialog": "^1.1.15",
|
||||
"@radix-ui/react-slot": "^1.2.4",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"framer-motion": "^12.29.0",
|
||||
"lucide-react": "^0.562.0",
|
||||
"next": "16.1.4",
|
||||
"next-themes": "^0.4.6",
|
||||
"react": "19.2.3",
|
||||
"react-dom": "19.2.3",
|
||||
"tailwind-merge": "^3.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@cloudflare/next-on-pages": "^1.13.16",
|
||||
"@tailwindcss/postcss": "^4",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^19",
|
||||
"@types/react-dom": "^19",
|
||||
"eslint": "^9",
|
||||
"eslint-config-next": "16.1.4",
|
||||
"tailwindcss": "^4",
|
||||
"tw-animate-css": "^1.4.0",
|
||||
"typescript": "^5"
|
||||
}
|
||||
}
|
||||
7
postcss.config.mjs
Normal file
@@ -0,0 +1,7 @@
|
||||
const config = {
|
||||
plugins: {
|
||||
"@tailwindcss/postcss": {},
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
BIN
public/android-icon-144x144.png
Normal file
|
After Width: | Height: | Size: 7.5 KiB |
BIN
public/android-icon-192x192.png
Normal file
|
After Width: | Height: | Size: 8.0 KiB |
BIN
public/android-icon-36x36.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
public/android-icon-48x48.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
public/android-icon-72x72.png
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
public/android-icon-96x96.png
Normal file
|
After Width: | Height: | Size: 4.9 KiB |
BIN
public/apple-icon-114x114.png
Normal file
|
After Width: | Height: | Size: 5.9 KiB |
BIN
public/apple-icon-120x120.png
Normal file
|
After Width: | Height: | Size: 6.2 KiB |
BIN
public/apple-icon-144x144.png
Normal file
|
After Width: | Height: | Size: 7.5 KiB |
BIN
public/apple-icon-152x152.png
Normal file
|
After Width: | Height: | Size: 8.1 KiB |
BIN
public/apple-icon-180x180.png
Normal file
|
After Width: | Height: | Size: 9.9 KiB |
BIN
public/apple-icon-57x57.png
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
BIN
public/apple-icon-60x60.png
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
public/apple-icon-72x72.png
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
public/apple-icon-76x76.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
public/apple-icon-precomposed.png
Normal file
|
After Width: | Height: | Size: 8.6 KiB |
BIN
public/apple-icon.png
Normal file
|
After Width: | Height: | Size: 8.6 KiB |
BIN
public/assets/logo-icon.png
Normal file
|
After Width: | Height: | Size: 148 KiB |
BIN
public/assets/logo-main.png
Normal file
|
After Width: | Height: | Size: 97 KiB |
BIN
public/assets/logo-white.png
Normal file
|
After Width: | Height: | Size: 118 KiB |
BIN
public/assets/team/gilang.png
Normal file
|
After Width: | Height: | Size: 277 KiB |
BIN
public/assets/team/hamzah.png
Normal file
|
After Width: | Height: | Size: 493 KiB |
BIN
public/assets/team/herdy.png
Normal file
|
After Width: | Height: | Size: 539 KiB |
BIN
public/assets/testimonials/annisa.jpg
Normal file
|
After Width: | Height: | Size: 54 KiB |
BIN
public/assets/testimonials/dinda.png
Normal file
|
After Width: | Height: | Size: 7.2 KiB |
BIN
public/assets/testimonials/fajar.png
Normal file
|
After Width: | Height: | Size: 6.5 KiB |
BIN
public/assets/testimonials/reza.png
Normal file
|
After Width: | Height: | Size: 6.7 KiB |
BIN
public/assets/testimonials/tiara.png
Normal file
|
After Width: | Height: | Size: 8.3 KiB |
2
public/browserconfig.xml
Normal file
@@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<browserconfig><msapplication><tile><square70x70logo src="/ms-icon-70x70.png"/><square150x150logo src="/ms-icon-150x150.png"/><square310x310logo src="/ms-icon-310x310.png"/><TileColor>#ffffff</TileColor></tile></msapplication></browserconfig>
|
||||
BIN
public/favicon-16x16.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
public/favicon-32x32.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
public/favicon-96x96.png
Normal file
|
After Width: | Height: | Size: 4.9 KiB |
BIN
public/favicon.ico
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
1
public/file.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>
|
||||
|
After Width: | Height: | Size: 391 B |
1
public/globe.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
8
public/grid.svg
Normal file
@@ -0,0 +1,8 @@
|
||||
<svg width="100%" height="100%" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<pattern id="grid" width="40" height="40" patternUnits="userSpaceOnUse">
|
||||
<path d="M 40 0 L 0 0 0 40" fill="none" stroke="rgba(255,255,255,0.05)" stroke-width="1"/>
|
||||
</pattern>
|
||||
</defs>
|
||||
<rect width="100%" height="100%" fill="url(#grid)" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 339 B |
41
public/manifest.json
Normal file
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"name": "App",
|
||||
"icons": [
|
||||
{
|
||||
"src": "\/android-icon-36x36.png",
|
||||
"sizes": "36x36",
|
||||
"type": "image\/png",
|
||||
"density": "0.75"
|
||||
},
|
||||
{
|
||||
"src": "\/android-icon-48x48.png",
|
||||
"sizes": "48x48",
|
||||
"type": "image\/png",
|
||||
"density": "1.0"
|
||||
},
|
||||
{
|
||||
"src": "\/android-icon-72x72.png",
|
||||
"sizes": "72x72",
|
||||
"type": "image\/png",
|
||||
"density": "1.5"
|
||||
},
|
||||
{
|
||||
"src": "\/android-icon-96x96.png",
|
||||
"sizes": "96x96",
|
||||
"type": "image\/png",
|
||||
"density": "2.0"
|
||||
},
|
||||
{
|
||||
"src": "\/android-icon-144x144.png",
|
||||
"sizes": "144x144",
|
||||
"type": "image\/png",
|
||||
"density": "3.0"
|
||||
},
|
||||
{
|
||||
"src": "\/android-icon-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image\/png",
|
||||
"density": "4.0"
|
||||
}
|
||||
]
|
||||
}
|
||||
BIN
public/ms-icon-144x144.png
Normal file
|
After Width: | Height: | Size: 7.5 KiB |
BIN
public/ms-icon-150x150.png
Normal file
|
After Width: | Height: | Size: 8.0 KiB |
BIN
public/ms-icon-310x310.png
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
public/ms-icon-70x70.png
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
1
public/next.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
BIN
public/opengraph-image.jpg
Normal file
|
After Width: | Height: | Size: 118 KiB |
1
public/vercel.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1155 1000"><path d="m577.3 0 577.4 1000H0z" fill="#fff"/></svg>
|
||||
|
After Width: | Height: | Size: 128 B |
1
public/window.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" fill="#666"/></svg>
|
||||
|
After Width: | Height: | Size: 385 B |
BIN
src/app/apple-icon.png
Normal file
|
After Width: | Height: | Size: 8.6 KiB |
129
src/app/globals.css
Normal file
@@ -0,0 +1,129 @@
|
||||
@import "tailwindcss";
|
||||
@import "tw-animate-css";
|
||||
|
||||
@custom-variant dark (&:is(.dark *));
|
||||
|
||||
@theme inline {
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--font-sans: var(--font-jakarta);
|
||||
--font-mono: var(--font-geist-mono);
|
||||
--color-sidebar-ring: var(--sidebar-ring);
|
||||
--color-sidebar-border: var(--sidebar-border);
|
||||
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
||||
--color-sidebar-accent: var(--sidebar-accent);
|
||||
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
||||
--color-sidebar-primary: var(--sidebar-primary);
|
||||
--color-sidebar-foreground: var(--sidebar-foreground);
|
||||
--color-sidebar: var(--sidebar);
|
||||
--color-chart-5: var(--chart-5);
|
||||
--color-chart-4: var(--chart-4);
|
||||
--color-chart-3: var(--chart-3);
|
||||
--color-chart-2: var(--chart-2);
|
||||
--color-chart-1: var(--chart-1);
|
||||
--color-ring: var(--ring);
|
||||
--color-input: var(--input);
|
||||
--color-border: var(--border);
|
||||
--color-destructive: var(--destructive);
|
||||
--color-accent-foreground: var(--accent-foreground);
|
||||
--color-accent: var(--accent);
|
||||
--color-muted-foreground: var(--muted-foreground);
|
||||
--color-muted: var(--muted);
|
||||
--color-secondary-foreground: var(--secondary-foreground);
|
||||
--color-secondary: var(--secondary);
|
||||
--color-primary-foreground: var(--primary-foreground);
|
||||
--color-primary: var(--primary);
|
||||
--color-popover-foreground: var(--popover-foreground);
|
||||
--color-popover: var(--popover);
|
||||
--color-card-foreground: var(--card-foreground);
|
||||
--color-card: var(--card);
|
||||
--radius-sm: calc(var(--radius) - 4px);
|
||||
--radius-md: calc(var(--radius) - 2px);
|
||||
--radius-lg: var(--radius);
|
||||
--radius-xl: calc(var(--radius) + 4px);
|
||||
--radius-2xl: calc(var(--radius) + 8px);
|
||||
--radius-3xl: calc(var(--radius) + 12px);
|
||||
--radius-4xl: calc(var(--radius) + 16px);
|
||||
}
|
||||
|
||||
:root {
|
||||
--radius: 0.625rem;
|
||||
--background: #ffffff;
|
||||
--foreground: #0a0a0a;
|
||||
--card: #ffffff;
|
||||
--card-foreground: #0a0a0a;
|
||||
--popover: #ffffff;
|
||||
--popover-foreground: #0a0a0a;
|
||||
--primary: #FF4500;
|
||||
--primary-foreground: #ffffff;
|
||||
--secondary: #f5f5f5;
|
||||
--secondary-foreground: #171717;
|
||||
--muted: #f5f5f5;
|
||||
--muted-foreground: #737373;
|
||||
--accent: #f5f5f5;
|
||||
--accent-foreground: #171717;
|
||||
--destructive: #ef4444;
|
||||
--destructive-foreground: #ffffff;
|
||||
--border: #e5e5e5;
|
||||
--input: #e5e5e5;
|
||||
--ring: #FF4500;
|
||||
--chart-1: #e76e50;
|
||||
--chart-2: #2a9d8f;
|
||||
--chart-3: #264653;
|
||||
--chart-4: #f4a261;
|
||||
--chart-5: #e9c46a;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: #0a0a0b; /* Deep Black */
|
||||
--foreground: #ededed;
|
||||
|
||||
--card: #121212;
|
||||
--card-foreground: #ededed;
|
||||
|
||||
--popover: #121212;
|
||||
--popover-foreground: #ededed;
|
||||
|
||||
--primary: #FF4500; /* NIHONBUZZ RED */
|
||||
--primary-foreground: #ffffff;
|
||||
|
||||
--secondary: #262626;
|
||||
--secondary-foreground: #ededed;
|
||||
|
||||
--muted: #171717;
|
||||
--muted-foreground: #a3a3a3;
|
||||
|
||||
--accent: #171717; /* Dark accent for hovers */
|
||||
--accent-foreground: #ededed;
|
||||
|
||||
--destructive: #7f1d1d;
|
||||
--destructive-foreground: #ffffff;
|
||||
|
||||
--border: #262626;
|
||||
--input: #262626;
|
||||
--ring: #FF4500; /* Red Ring */
|
||||
|
||||
--chart-1: #FF4500; /* Red */
|
||||
--chart-2: #3b82f6; /* Blue */
|
||||
--chart-3: #f97316; /* Orange */
|
||||
--chart-4: #10b981; /* Emerald */
|
||||
--chart-5: #8b5cf6; /* Violet */
|
||||
|
||||
--sidebar: #0a0a0b;
|
||||
--sidebar-foreground: #ededed;
|
||||
--sidebar-primary: #FF4500;
|
||||
--sidebar-primary-foreground: #ffffff;
|
||||
--sidebar-accent: #262626;
|
||||
--sidebar-accent-foreground: #ededed;
|
||||
--sidebar-border: #262626;
|
||||
--sidebar-ring: #FF4500;
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border outline-ring/50;
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
}
|
||||
BIN
src/app/icon.png
Normal file
|
After Width: | Height: | Size: 4.9 KiB |
67
src/app/layout.tsx
Normal file
@@ -0,0 +1,67 @@
|
||||
import type { Metadata } from "next";
|
||||
import { Plus_Jakarta_Sans } from "next/font/google"; // Changed font
|
||||
import "./globals.css";
|
||||
|
||||
const jakarta = Plus_Jakarta_Sans({
|
||||
subsets: ["latin"],
|
||||
variable: "--font-jakarta",
|
||||
display: 'swap',
|
||||
});
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "NihonBuzz - Connecting You to Japan",
|
||||
description: "Platform edukasi dan media Jepang terdepan di Indonesia. Belajar bahasa Jepang (JLPT N5-N3), budaya, dan info karir.",
|
||||
icons: {
|
||||
icon: [
|
||||
{ url: '/favicon-32x32.png', sizes: '32x32', type: 'image/png' },
|
||||
{ url: '/favicon-16x16.png', sizes: '16x16', type: 'image/png' }
|
||||
],
|
||||
apple: [
|
||||
{ url: '/apple-icon-180x180.png', sizes: '180x180', type: 'image/png' },
|
||||
],
|
||||
other: [
|
||||
{ rel: 'android-chrome-192x192', url: '/android-icon-192x192.png' },
|
||||
{ rel: 'android-chrome-512x512', url: '/android-icon-512x512.png' }, // Assuming these exist or will be mapped
|
||||
],
|
||||
},
|
||||
openGraph: {
|
||||
title: "NihonBuzz - Connecting You to Japan",
|
||||
description: "Belajar Bahasa Jepang & Karir Profesional. Gabung NihonBuzz Academy sekarang.",
|
||||
url: 'https://nihonbuzz.org',
|
||||
siteName: 'NihonBuzz',
|
||||
images: [
|
||||
{
|
||||
url: '/opengraph-image.png',
|
||||
width: 1200,
|
||||
height: 630,
|
||||
alt: 'NihonBuzz Ecosystem',
|
||||
},
|
||||
],
|
||||
locale: 'id_ID',
|
||||
type: 'website',
|
||||
},
|
||||
twitter: {
|
||||
card: 'summary_large_image',
|
||||
title: "NihonBuzz - Connecting You to Japan",
|
||||
description: "Platform edukasi dan media Jepang terdepan di Indonesia.",
|
||||
images: ['/opengraph-image.png'],
|
||||
creator: '@nihonbuzz',
|
||||
},
|
||||
manifest: '/manifest.json',
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
return (
|
||||
<html lang="en" className="dark">
|
||||
<body
|
||||
className={`${jakarta.variable} font-sans antialiased`}
|
||||
>
|
||||
{children}
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
25
src/app/page.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import { Navbar } from "@/components/layout/navbar";
|
||||
import { Hero } from "@/components/sections/hero";
|
||||
import { About } from "@/components/sections/about";
|
||||
import { Programs } from "@/components/sections/programs";
|
||||
import { Services } from "@/components/sections/services";
|
||||
import { Team } from "@/components/sections/team";
|
||||
import { Testimonials } from "@/components/sections/testimonials";
|
||||
import { Faq } from "@/components/sections/faq";
|
||||
import { Footer } from "@/components/layout/footer";
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<main className="min-h-screen bg-background relative selection:bg-primary/30 selection:text-primary overflow-x-hidden">
|
||||
<Navbar />
|
||||
<Hero />
|
||||
<About />
|
||||
<Programs />
|
||||
<Services />
|
||||
<Team />
|
||||
<Testimonials />
|
||||
<Faq />
|
||||
<Footer />
|
||||
</main>
|
||||
);
|
||||
}
|
||||
62
src/components/layout/footer.tsx
Normal file
@@ -0,0 +1,62 @@
|
||||
import Link from "next/link";
|
||||
import { Facebook, Instagram, Mail, MapPin, Youtube } from "lucide-react";
|
||||
|
||||
export function Footer() {
|
||||
return (
|
||||
<footer className="border-t border-white/5 bg-black py-12 mt-20">
|
||||
<div className="container mx-auto px-4">
|
||||
<div className="grid md:grid-cols-4 gap-8 mb-12">
|
||||
<div className="col-span-1 md:col-span-2">
|
||||
<h4 className="text-xl font-bold mb-4 text-white">NihonBuzz</h4>
|
||||
<p className="text-muted-foreground mb-6 max-w-sm">
|
||||
Menjadi platform media dan edukasi budaya Jepang yang paling berdampak di Indonesia. #ConnectingIndonesiaJapan
|
||||
</p>
|
||||
<div className="flex gap-4">
|
||||
<Link href="https://facebook.com/nihonbuzz.id" className="text-muted-foreground hover:text-primary transition-colors">
|
||||
<Facebook className="w-5 h-5" />
|
||||
</Link>
|
||||
<Link href="https://instagram.com/nihon_buzz" className="text-muted-foreground hover:text-primary transition-colors">
|
||||
<Instagram className="w-5 h-5" />
|
||||
</Link>
|
||||
<Link href="https://tiktok.com/@nihonbuzz" className="text-muted-foreground hover:text-primary transition-colors">
|
||||
<svg role="img" viewBox="0 0 448 512" fill="currentColor" className="w-5 h-5" xmlns="http://www.w3.org/2000/svg"><path d="M448.5 209.9c-44 .1-87-13.6-122.8-39.2l0 178.7c0 33.1-10.1 65.4-29 92.6s-45.6 48-76.6 59.6-64.8 13.5-96.9 5.3-60.9-25.9-82.7-50.8-35.3-56-39-88.9 2.9-66.1 18.6-95.2 40-52.7 69.6-67.7 62.9-20.5 95.7-16l0 89.9c-15-4.7-31.1-4.6-46 .4s-27.9 14.6-37 27.3-14 28.1-13.9 43.9 5.2 31 14.5 43.7 22.4 22.1 37.4 26.9 31.1 4.8 46-.1 28-14.4 37.2-27.1 14.2-28.1 14.2-43.8l0-349.4 88 0c-.1 7.4 .6 14.9 1.9 22.2 3.1 16.3 9.4 31.9 18.7 45.7s21.3 25.6 35.2 34.6c19.9 13.1 43.2 20.1 67 20.1l0 87.4z"/></svg>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h5 className="font-bold text-white mb-4">Metode & Layanan</h5>
|
||||
<ul className="space-y-2 text-sm text-muted-foreground">
|
||||
<li><Link href="#" className="hover:text-primary">E-Course JLPT</Link></li>
|
||||
<li><Link href="#" className="hover:text-primary">Private Group</Link></li>
|
||||
<li><Link href="#" className="hover:text-primary">Study to Japan</Link></li>
|
||||
<li><Link href="#" className="hover:text-primary">Work to Japan</Link></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h5 className="font-bold text-white mb-4">Hubungi Kami</h5>
|
||||
<ul className="space-y-3 text-sm text-muted-foreground">
|
||||
<li className="flex items-start gap-3">
|
||||
<MapPin className="w-5 h-5 text-primary shrink-0" />
|
||||
<span>Jl. Palapa VII No.1, RT.12/RW.18, Kedoya Sel., Kec. Kb. Jeruk, Jakarta Barat 11520</span>
|
||||
</li>
|
||||
<li className="flex items-center gap-3">
|
||||
<Mail className="w-5 h-5 text-primary shrink-0" />
|
||||
<a href="mailto:hello@nihonbuzz.org" className="hover:text-primary">hello@nihonbuzz.org</a>
|
||||
</li>
|
||||
<li className="flex items-center gap-3">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="text-primary shrink-0"><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z"/></svg>
|
||||
<span>+62 851-2988-0919</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="border-t border-white/5 pt-8 text-center text-sm text-muted-foreground">
|
||||
<p>© {new Date().getFullYear()} NihonBuzz. All rights reserved.</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
)
|
||||
}
|
||||
133
src/components/layout/navbar.tsx
Normal file
@@ -0,0 +1,133 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import Image from "next/image";
|
||||
import { useState, useEffect } from "react";
|
||||
import { motion } from "framer-motion";
|
||||
import { Menu } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet";
|
||||
|
||||
const navLinks = [
|
||||
{ name: "Tentang Kami", href: "#about" },
|
||||
{ name: "Program Belajar", href: "#programs" },
|
||||
{ name: "Jadwal & Biaya", href: "#schedule" },
|
||||
{ name: "Artikel", href: "https://nihonbuzz.org/berita" }, // Link to legacy news/blog
|
||||
];
|
||||
|
||||
export function Navbar() {
|
||||
const [scrolled, setScrolled] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const handleScroll = () => {
|
||||
setScrolled(window.scrollY > 50);
|
||||
};
|
||||
window.addEventListener("scroll", handleScroll);
|
||||
return () => window.removeEventListener("scroll", handleScroll);
|
||||
}, []);
|
||||
|
||||
const handleScroll = (e: React.MouseEvent<HTMLAnchorElement, MouseEvent>, href: string) => {
|
||||
if (href.startsWith("#")) {
|
||||
e.preventDefault();
|
||||
const element = document.querySelector(href);
|
||||
if (element) {
|
||||
element.scrollIntoView({ behavior: "smooth" });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<motion.header
|
||||
initial={{ y: -100, opacity: 0 }}
|
||||
animate={{ y: 0, opacity: 1 }}
|
||||
transition={{ duration: 0.5 }}
|
||||
className="fixed top-0 left-0 right-0 z-50 flex justify-center pt-6 px-4"
|
||||
>
|
||||
<div
|
||||
className={`
|
||||
flex items-center justify-between px-6 py-3 rounded-full transition-all duration-300 ease-in-out
|
||||
${
|
||||
scrolled
|
||||
? "bg-background/80 backdrop-blur-xl border border-white/10 shadow-2xl w-full max-w-4xl"
|
||||
: "bg-transparent border-transparent w-full max-w-6xl"
|
||||
}
|
||||
`}
|
||||
>
|
||||
{/* Logo */}
|
||||
<Link href="/" className="relative flex items-center gap-2 group">
|
||||
<Image
|
||||
src="/assets/logo-white.png"
|
||||
alt="NihonBuzz"
|
||||
width={140}
|
||||
height={40}
|
||||
className="h-8 md:h-10 w-auto object-contain transition-transform duration-300 group-hover:scale-105"
|
||||
priority
|
||||
/>
|
||||
</Link>
|
||||
|
||||
{/* Desktop Navigation */}
|
||||
<nav className="hidden md:flex items-center gap-8">
|
||||
{navLinks.map((link) => (
|
||||
<Link
|
||||
key={link.name}
|
||||
href={link.href}
|
||||
onClick={(e) => handleScroll(e, link.href)}
|
||||
className="text-sm font-medium text-foreground/80 hover:text-primary transition-colors cursor-pointer"
|
||||
>
|
||||
{link.name}
|
||||
</Link>
|
||||
))}
|
||||
</nav>
|
||||
|
||||
{/* Action Button & Mobile Menu */}
|
||||
<div className="flex items-center gap-3">
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="hidden md:flex text-foreground/80 hover:text-primary hover:bg-white/5"
|
||||
size="sm"
|
||||
>
|
||||
Login Siswa
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
className="hidden md:flex rounded-full bg-primary hover:bg-primary/90 text-white shadow-[0_0_20px_-5px_rgba(255,69,0,0.5)]"
|
||||
size="sm"
|
||||
>
|
||||
Daftar Kelas
|
||||
</Button>
|
||||
|
||||
{/* Mobile Toggle */}
|
||||
<Sheet>
|
||||
<SheetTrigger asChild>
|
||||
<Button variant="ghost" size="icon" className="md:hidden text-foreground">
|
||||
<Menu className="w-5 h-5" />
|
||||
</Button>
|
||||
</SheetTrigger>
|
||||
<SheetContent side="top" className="w-full h-auto rounded-b-[2rem] border-white/10 bg-black/95 backdrop-blur-xl pt-20 pb-10">
|
||||
<nav className="flex flex-col items-center gap-6">
|
||||
{navLinks.map((link) => (
|
||||
<Link
|
||||
key={link.name}
|
||||
href={link.href}
|
||||
onClick={(e) => handleScroll(e, link.href)}
|
||||
className="text-lg font-medium text-foreground/80 hover:text-primary transition-colors cursor-pointer"
|
||||
>
|
||||
{link.name}
|
||||
</Link>
|
||||
))}
|
||||
<div className="flex flex-col gap-3 w-full max-w-xs mt-4">
|
||||
<Button variant="outline" className="w-full rounded-full border-white/10 text-foreground hover:bg-white/5">
|
||||
Login Siswa
|
||||
</Button>
|
||||
<Button className="w-full rounded-full bg-primary hover:bg-primary/90">
|
||||
Daftar Kelas
|
||||
</Button>
|
||||
</div>
|
||||
</nav>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
</div>
|
||||
</div>
|
||||
</motion.header>
|
||||
);
|
||||
}
|
||||
90
src/components/sections/about.tsx
Normal file
@@ -0,0 +1,90 @@
|
||||
"use client";
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
import { BookOpen, Users, Globe } from "lucide-react";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
|
||||
const features = [
|
||||
{
|
||||
icon: Globe,
|
||||
title: "Wawasan Mendalam",
|
||||
description: "Dari tradisi kuno hingga tren modern, kami memberikan wawasan mendalam tentang berbagai aspek kehidupan di Jepang yang inspiratif.",
|
||||
},
|
||||
{
|
||||
icon: Users,
|
||||
title: "Komunitas Pecinta Jepang",
|
||||
description: "Wadah untuk berdiskusi, bertukar ide, dan memperluas jaringan dengan sesama penggemar budaya Jepang dari berbagai latar belakang.",
|
||||
},
|
||||
{
|
||||
icon: BookOpen,
|
||||
title: "Edukasi & Karir",
|
||||
description: "Akses materi pembelajaran bahasa Jepang dan peluang karir profesional melalui Career Center kami (Premium).",
|
||||
},
|
||||
];
|
||||
|
||||
export function About() {
|
||||
return (
|
||||
<section id="about" className="py-24 bg-background relative overflow-hidden">
|
||||
<div className="container mx-auto px-4 relative z-10">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.6 }}
|
||||
className="text-center max-w-4xl mx-auto mb-16"
|
||||
>
|
||||
<h2 className="text-3xl md:text-5xl font-bold mb-6 text-foreground">
|
||||
Menggali Keindahan <span className="text-primary">Budaya Jepang</span>
|
||||
</h2>
|
||||
<p className="text-muted-foreground text-lg leading-relaxed mb-8">
|
||||
Nihonbuzz hadir untuk menjembatani ketertarikan generasi muda Indonesia terhadap Jepang
|
||||
dengan cara yang ringan, relevan, dan informatif.
|
||||
</p>
|
||||
|
||||
<div className="grid md:grid-cols-2 gap-8 text-left bg-card/30 p-8 rounded-3xl border border-white/5">
|
||||
<div>
|
||||
<h4 className="text-xl font-bold text-white mb-2">Visi Kami</h4>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Menjadi platform media dan edukasi budaya Jepang yang paling berdampak di Indonesia, menginspirasi generasi muda untuk menjelajah bahasa, budaya, dan karier di Jepang.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="text-xl font-bold text-white mb-2">Misi Utama</h4>
|
||||
<ul className="text-sm text-muted-foreground space-y-2 list-disc list-inside">
|
||||
<li>Membangun ekosistem belajar dan kerja yang inklusif.</li>
|
||||
<li>Menyediakan konten edukasi & hiburan berkualitas.</li>
|
||||
<li>Menjadi jembatan koneksi Indonesia - Jepang.</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
<div className="grid md:grid-cols-3 gap-8">
|
||||
{features.map((feature, index) => (
|
||||
<motion.div
|
||||
key={index}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.5, delay: index * 0.2 }}
|
||||
>
|
||||
<Card className="bg-card/50 backdrop-blur border-white/5 hover:border-primary/50 transition-colors duration-300 h-full group">
|
||||
<CardHeader>
|
||||
<div className="w-12 h-12 rounded-lg bg-primary/10 flex items-center justify-center mb-4 group-hover:bg-primary/20 transition-colors">
|
||||
<feature.icon className="w-6 h-6 text-primary" />
|
||||
</div>
|
||||
<CardTitle className="text-xl font-bold text-foreground">{feature.title}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-muted-foreground leading-relaxed">
|
||||
{feature.description}
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
75
src/components/sections/faq.tsx
Normal file
@@ -0,0 +1,75 @@
|
||||
"use client";
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
import {
|
||||
Accordion,
|
||||
AccordionContent,
|
||||
AccordionItem,
|
||||
AccordionTrigger,
|
||||
} from "@/components/ui/accordion";
|
||||
|
||||
const faqs = [
|
||||
{
|
||||
question: "Apa bedanya NihonBuzz Media dengan NihonBuzz Academy?",
|
||||
answer: "NihonBuzz Media adalah portal informasi budaya Jepang gratis. Sedangkan NihonBuzz Academy adalah lembaga pendidikan non-formal yang fokus mencetak talenta siap kerja/studi ke Jepang melalui bimbingan intensif dan kurikulum terstruktur."
|
||||
},
|
||||
{
|
||||
question: "Apakah kurikulumnya sesuai dengan standar JLPT?",
|
||||
answer: "Ya, 100%. Materi kami dirancang khusus untuk persiapan ujian JLPT (N5-N3) & NAT-TEST. Kami mencakup seluruh aspek: Kanji, Tata Bahasa (Bunpou), Kosa Kata (Kotoba), hingga Pendengaran (Chokai)."
|
||||
},
|
||||
{
|
||||
question: "Saya pemula (Zero Beginner), apakah bisa ikut?",
|
||||
answer: "Sangat bisa! Kami memiliki kelas 'Persiapan N5' yang dirancang khusus untuk pemula nol. Sensei akan membimbing dari pengenalan huruf (Hiragana/Katakana) hingga Anda lancar percakapan dasar."
|
||||
},
|
||||
{
|
||||
question: "Apakah tersedia rekaman jika saya berhalangan hadir?",
|
||||
answer: "Tentu. Setiap sesi Live Teaching direkam dan diunggah ke Learning Management System (LMS) kami. Anda bisa mengakses ulang materi dan rekaman kapan saja (Lifetime Access untuk materi tertentu)."
|
||||
},
|
||||
{
|
||||
question: "Bagaimana cara mendaftar kelas Private atau Group?",
|
||||
answer: "Silakan pilih program di bagian 'Program Belajar' di atas, atau klik tombol 'Daftar Kelas' di menu. Tim konsultan akademik kami akan menghubungi Anda untuk penempatan level (Placement Test) jika diperlukan."
|
||||
}
|
||||
];
|
||||
|
||||
export function Faq() {
|
||||
return (
|
||||
<section id="faq" className="py-24 bg-background relative z-10">
|
||||
<div className="container mx-auto px-4 max-w-3xl">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.6 }}
|
||||
className="text-center mb-12"
|
||||
>
|
||||
<h2 className="text-3xl md:text-5xl font-bold mb-6">
|
||||
Pertanyaan <span className="text-primary">Umum</span>
|
||||
</h2>
|
||||
<p className="text-muted-foreground text-lg">
|
||||
Hal-hal yang sering ditanyakan seputar NihonBuzz.
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.5, delay: 0.2 }}
|
||||
>
|
||||
<Accordion type="single" collapsible className="w-full">
|
||||
{faqs.map((faq, index) => (
|
||||
<AccordionItem key={index} value={`item-${index}`} className="border-white/10">
|
||||
<AccordionTrigger className="text-left text-lg hover:text-primary transition-colors">
|
||||
{faq.question}
|
||||
</AccordionTrigger>
|
||||
<AccordionContent className="text-muted-foreground leading-relaxed">
|
||||
{faq.answer}
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
))}
|
||||
</Accordion>
|
||||
</motion.div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
82
src/components/sections/hero.tsx
Normal file
@@ -0,0 +1,82 @@
|
||||
"use client";
|
||||
|
||||
import { motion, Variants } from "framer-motion";
|
||||
import { ArrowRight, Sparkles } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
const containerVariants: Variants = {
|
||||
hidden: { opacity: 0 },
|
||||
visible: {
|
||||
opacity: 1,
|
||||
transition: {
|
||||
staggerChildren: 0.2,
|
||||
delayChildren: 0.3,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const itemVariants: Variants = {
|
||||
hidden: { y: 20, opacity: 0 },
|
||||
visible: {
|
||||
y: 0,
|
||||
opacity: 1,
|
||||
transition: {
|
||||
duration: 0.8,
|
||||
ease: [0.215, 0.610, 0.355, 1.000],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export function Hero() {
|
||||
return (
|
||||
<section className="relative min-h-[110vh] flex flex-col items-center justify-center overflow-hidden w-full">
|
||||
{/* Background Gradients */}
|
||||
<div className="absolute top-0 left-1/2 -translate-x-1/2 w-[1000px] h-[600px] bg-primary/20 blur-[120px] rounded-full opacity-50 pointer-events-none" />
|
||||
<div className="absolute bottom-0 right-0 w-[800px] h-[800px] bg-blue-500/5 blur-[150px] rounded-full opacity-30 pointer-events-none" />
|
||||
|
||||
{/* Grid Pattern Overlay */}
|
||||
<div className="absolute inset-0 bg-[url('/grid.svg')] bg-center [mask-image:linear-gradient(180deg,white,rgba(255,255,255,0))]" />
|
||||
|
||||
<motion.div
|
||||
variants={containerVariants}
|
||||
initial="hidden"
|
||||
animate="visible"
|
||||
className="relative z-10 container px-4 mx-auto flex flex-col items-center text-center gap-8"
|
||||
>
|
||||
<motion.div variants={itemVariants}>
|
||||
<div className="inline-flex items-center gap-2 px-4 py-2 rounded-full border border-white/5 bg-white/5 backdrop-blur-sm shadow-inner text-sm text-foreground/80 mb-6">
|
||||
<Sparkles className="w-4 h-4 text-primary" />
|
||||
<span>Connecting Indonesia & Japan</span>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
<motion.h1
|
||||
variants={itemVariants}
|
||||
className="text-5xl md:text-7xl lg:text-8xl font-bold tracking-tight text-white leading-[1.1]"
|
||||
>
|
||||
Platform Edukasi <br />
|
||||
<span className="text-transparent bg-clip-text bg-gradient-to-r from-white via-white to-white/50">
|
||||
& Budaya Jepang
|
||||
</span>
|
||||
</motion.h1>
|
||||
|
||||
<motion.p
|
||||
variants={itemVariants}
|
||||
className="max-w-2xl text-lg md:text-xl text-muted-foreground leading-relaxed"
|
||||
>
|
||||
Nihonbuzz adalah media yang menghadirkan konten seputar bahasa, budaya, dan gaya hidup Jepang secara informatif dan menghibur untuk generasi muda Indonesia.
|
||||
</motion.p>
|
||||
|
||||
<motion.div variants={itemVariants} className="flex flex-col sm:flex-row gap-4 mt-4">
|
||||
<Button size="lg" className="rounded-full bg-primary hover:bg-primary/90 text-white min-w-[160px] h-12 shadow-[0_0_40px_-10px_rgba(255,69,0,0.5)]">
|
||||
Explore Services
|
||||
<ArrowRight className="w-4 h-4 ml-2" />
|
||||
</Button>
|
||||
<Button size="lg" variant="outline" className="rounded-full border-white/10 hover:bg-white/5 min-w-[160px] h-12">
|
||||
View Portfolio
|
||||
</Button>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
119
src/components/sections/programs.tsx
Normal file
@@ -0,0 +1,119 @@
|
||||
"use client";
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
import { CheckCircle2, Users, Zap, ArrowRight, Star } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
|
||||
const programs = [
|
||||
{
|
||||
title: "Bahasa Jepang Dasar",
|
||||
level: "Beginner",
|
||||
description: "Program fondasi bagi pemula nol. Pelajari Hiragana, Katakana, dan percakapan dasar sehari-hari.",
|
||||
features: ["Intensive Class", "Private Group", "Materi Digital", "Sertifikat"],
|
||||
popular: false,
|
||||
color: "from-blue-500/20 to-cyan-500/20",
|
||||
iconColor: "text-blue-400"
|
||||
},
|
||||
{
|
||||
title: "Persiapan JLPT N5",
|
||||
level: "Elementary",
|
||||
description: "Target lulus JLPT N5. Fokus pada tata bahasa, kanji dasar, dan latihan soal intensif.",
|
||||
features: ["Intensive Class", "Private Group", "Try Out JLPT", "Konsultasi Studi"],
|
||||
popular: true,
|
||||
color: "from-primary/20 to-orange-500/20",
|
||||
iconColor: "text-primary"
|
||||
},
|
||||
{
|
||||
title: "Persiapan JLPT N4",
|
||||
level: "Intermediate",
|
||||
description: "Lanjutan dari N5. Memperdalam pemahaman pola kalimat dan percakapan untuk studi/kerja.",
|
||||
features: ["Intensive Class", "Private Group", "Native Session", "Bimbingan Karir"],
|
||||
popular: false,
|
||||
color: "from-purple-500/20 to-pink-500/20",
|
||||
iconColor: "text-purple-400"
|
||||
}
|
||||
];
|
||||
|
||||
export function Programs() {
|
||||
return (
|
||||
<section id="programs" className="py-24 bg-background relative z-10">
|
||||
<div className="container mx-auto px-4">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.6 }}
|
||||
className="text-center mb-16"
|
||||
>
|
||||
<Badge variant="outline" className="mb-4 border-primary/20 text-primary bg-primary/5 px-4 py-1">
|
||||
Kurikulum Terstruktur
|
||||
</Badge>
|
||||
<h2 className="text-3xl md:text-5xl font-bold mb-6">
|
||||
Pilih Program <span className="text-primary">Sesuai Targetmu</span>
|
||||
</h2>
|
||||
<p className="text-muted-foreground text-lg max-w-2xl mx-auto">
|
||||
Kelas intensif dengan metode yang terbukti efektif, tersedia dalam format grup maupun privat eksklusif.
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
<div className="grid md:grid-cols-3 gap-8">
|
||||
{programs.map((program, index) => (
|
||||
<motion.div
|
||||
key={index}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.5, delay: index * 0.2 }}
|
||||
className="relative group"
|
||||
>
|
||||
<div
|
||||
className={`absolute inset-0 bg-gradient-to-b ${program.color} blur-3xl opacity-0 group-hover:opacity-100 transition-opacity duration-500 rounded-3xl`}
|
||||
/>
|
||||
|
||||
<div className="relative h-full bg-card/50 backdrop-blur-sm border border-white/5 rounded-2xl p-8 hover:border-primary/50 transition-colors duration-300 flex flex-col">
|
||||
{program.popular && (
|
||||
<div className="absolute -top-4 left-1/2 -translate-x-1/2 bg-primary text-white text-xs font-bold px-4 py-1 rounded-full shadow-lg flex items-center gap-1">
|
||||
<Star className="w-3 h-3 fill-current" /> MOST POPULAR
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mb-6">
|
||||
<h3 className="text-2xl font-bold mb-2">{program.title}</h3>
|
||||
<p className={`text-sm font-medium ${program.iconColor} mb-4`}>{program.level}</p>
|
||||
<p className="text-muted-foreground text-sm leading-relaxed min-h-[60px]">
|
||||
{program.description}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4 mb-8 flex-grow">
|
||||
<div className="flex flex-wrap gap-2 mb-4">
|
||||
<Badge variant="secondary" className="bg-white/5 hover:bg-white/10 text-xs font-normal">
|
||||
<Zap className="w-3 h-3 mr-1" /> Intensive
|
||||
</Badge>
|
||||
<Badge variant="secondary" className="bg-white/5 hover:bg-white/10 text-xs font-normal">
|
||||
<Users className="w-3 h-3 mr-1" /> Private Group
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
{program.features.slice(2).map((feat, i) => ( // Show distinct features
|
||||
<div key={i} className="flex items-center text-sm text-foreground/80">
|
||||
<CheckCircle2 className={`w-4 h-4 mr-2 ${program.iconColor}`} />
|
||||
{feat}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Button className="w-full rounded-full group-hover:bg-primary group-hover:text-white transition-all duration-300" variant={program.popular ? "default" : "outline"}>
|
||||
Daftar Sekarang
|
||||
</Button>
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
86
src/components/sections/services.tsx
Normal file
@@ -0,0 +1,86 @@
|
||||
"use client";
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
import Image from "next/image";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { ArrowLeft, ArrowRight, Book, Newspaper, Calendar } from "lucide-react";
|
||||
|
||||
const services = [
|
||||
{
|
||||
title: "Media & Berita",
|
||||
description: "Ikuti berita terkini seputar tren, gaya hidup, dan pop culture Jepang yang disajikan secara menarik.",
|
||||
action: "Baca Berita",
|
||||
icon: Newspaper,
|
||||
image: "/assets/service-media.jpg" // Placeholder path, using abstraction for now
|
||||
},
|
||||
{
|
||||
title: "E-Course Bahasa",
|
||||
description: "Belajar bahasa Jepang dari dasar hingga mahir dengan kurikulum yang mudah dipahami.",
|
||||
action: "Mulai Belajar",
|
||||
icon: Book,
|
||||
image: "/assets/service-course.jpg"
|
||||
},
|
||||
{
|
||||
title: "Event & Komunitas",
|
||||
description: "Dapatkan akses ke event eksklusif dan gathering komunitas pecinta Jepang di Indonesia.",
|
||||
action: "Gabung Event",
|
||||
icon: Calendar,
|
||||
image: "/assets/service-event.jpg"
|
||||
}
|
||||
];
|
||||
|
||||
export function Services() {
|
||||
return (
|
||||
<section id="schedule" className="py-24 bg-black/50 relative">
|
||||
{/* Background Elements */}
|
||||
<div className="absolute top-1/2 left-0 w-[500px] h-[500px] bg-primary/5 blur-[100px] rounded-full pointer-events-none -translate-y-1/2" />
|
||||
|
||||
<div className="container mx-auto px-4 relative z-10">
|
||||
<div className="flex flex-col md:flex-row justify-between items-end mb-16 gap-6">
|
||||
<div className="max-w-2xl">
|
||||
<h2 className="text-3xl md:text-5xl font-bold mb-6 text-foreground">
|
||||
Layanan <span className="text-primary">Eksklusif</span> Kami
|
||||
</h2>
|
||||
<p className="text-muted-foreground text-lg">
|
||||
Temukan berbagai cara untuk terhubung dengan dunia Jepang melalui platform kami.
|
||||
</p>
|
||||
</div>
|
||||
<Button variant="outline" className="rounded-full border-white/10 hover:bg-white/5">
|
||||
Lihat Semua Layanan <ArrowRight className="ml-2 w-4 h-4" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="grid md:grid-cols-3 gap-6">
|
||||
{services.map((service, index) => (
|
||||
<motion.div
|
||||
key={index}
|
||||
initial={{ opacity: 0, scale: 0.95 }}
|
||||
whileInView={{ opacity: 1, scale: 1 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.5, delay: index * 0.1 }}
|
||||
className="group relative overflow-hidden rounded-3xl border border-white/10 bg-gradient-to-b from-white/5 to-transparent p-1"
|
||||
>
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-primary/20 via-transparent to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-500" />
|
||||
|
||||
<div className="relative h-full bg-card/40 backdrop-blur-sm rounded-[22px] p-8 flex flex-col justify-between overflow-hidden">
|
||||
<div>
|
||||
<div className="w-12 h-12 rounded-full bg-white/5 flex items-center justify-center mb-6 border border-white/10 group-hover:scale-110 transition-transform duration-300">
|
||||
<service.icon className="w-5 h-5 text-white" />
|
||||
</div>
|
||||
<h3 className="text-2xl font-bold mb-3 text-white">{service.title}</h3>
|
||||
<p className="text-muted-foreground mb-8">
|
||||
{service.description}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center text-sm font-medium text-primary cursor-pointer group-hover:translate-x-2 transition-transform duration-300">
|
||||
{service.action} <ArrowRight className="ml-2 w-4 h-4" />
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
76
src/components/sections/team.tsx
Normal file
@@ -0,0 +1,76 @@
|
||||
"use client";
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
import Image from "next/image";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
|
||||
const team = [
|
||||
{
|
||||
name: "Gilang Romadan",
|
||||
role: "Chief Business Officer (CBO)",
|
||||
image: "/assets/team/gilang.png",
|
||||
},
|
||||
{
|
||||
name: "Muhammad Herdy Iskandar",
|
||||
role: "Chief Technology Officer (CTO)",
|
||||
image: "/assets/team/herdy.png",
|
||||
},
|
||||
{
|
||||
name: "Hamzah Hadits Sabil",
|
||||
role: "Chief Operating Officer (COO) & Sensei",
|
||||
image: "/assets/team/hamzah.png", // Start with placeholder, will try to download or use generic
|
||||
isSensei: true
|
||||
}
|
||||
];
|
||||
|
||||
export function Team() {
|
||||
return (
|
||||
<section id="team" className="py-24 bg-black/30 relative">
|
||||
<div className="container mx-auto px-4">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.6 }}
|
||||
className="text-center mb-16"
|
||||
>
|
||||
<h2 className="text-3xl md:text-5xl font-bold mb-6">
|
||||
Meet the <span className="text-primary">Team</span>
|
||||
</h2>
|
||||
<p className="text-muted-foreground text-lg max-w-2xl mx-auto">
|
||||
Dedikasi kami untuk membangun ekosistem edukasi dan teknologi Jepang terbaik.
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
<div className="grid md:grid-cols-3 gap-8 justify-center items-center">
|
||||
{team.map((member, index) => (
|
||||
<motion.div
|
||||
key={index}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.5, delay: index * 0.2 }}
|
||||
className="group flex flex-col items-center text-center"
|
||||
>
|
||||
<div className="relative w-48 h-48 mb-6 rounded-full overflow-hidden border-4 border-white/5 group-hover:border-primary/50 transition-colors duration-300">
|
||||
<Image
|
||||
src={member.image}
|
||||
alt={member.name}
|
||||
fill
|
||||
className="object-cover grayscale group-hover:grayscale-0 transition-all duration-500"
|
||||
/>
|
||||
</div>
|
||||
<h3 className="text-xl font-bold mb-1">{member.name}</h3>
|
||||
<p className="text-primary text-sm font-medium mb-3">{member.role}</p>
|
||||
{member.isSensei && (
|
||||
<Badge variant="outline" className="border-primary text-primary bg-primary/10">
|
||||
Head Sensei
|
||||
</Badge>
|
||||
)}
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
93
src/components/sections/testimonials.tsx
Normal file
@@ -0,0 +1,93 @@
|
||||
"use client";
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
import { Quote } from "lucide-react";
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||
import { Card, CardContent, CardHeader } from "@/components/ui/card";
|
||||
|
||||
const testimonials = [
|
||||
{
|
||||
name: "Annisa",
|
||||
role: "Mahasiswa Sastra Jepang",
|
||||
text: "Nihonbuzz nggak cuma ngajarin grammar dan kosakata, tapi juga kasih konteks real-life. Kayak gimana ngobrol di konbini, cara nyapa senior, sampai tips survive kalo kerja atau kuliah di Jepang. Semua dibahas!",
|
||||
image: "/assets/testimonials/annisa.jpg"
|
||||
},
|
||||
{
|
||||
name: "Dinda",
|
||||
role: "Mahasiswa",
|
||||
text: "Awalnya aku ragu karena belum pernah sama sekali belajar bahasa Jepang. Tapi Nihonbuzz punya modul yang gampang dicerna, dan sensei-nya sabar banget ngajarin. Sekarang aku udah bisa baca hiragana dan perkenalan diri!",
|
||||
image: "/assets/testimonials/dinda.png"
|
||||
},
|
||||
{
|
||||
name: "Fajar",
|
||||
role: "Job Seeker",
|
||||
text: "Setelah ikut kelas dan belajar bareng Nihonbuzz, aku jadi makin pede ngelamar program kerja di Jepang. Selain bahasanya, aku juga dapet insight soal budaya kerja dan proses pendaftarannya.",
|
||||
image: "/assets/testimonials/fajar.png"
|
||||
},
|
||||
{
|
||||
name: "Tiara",
|
||||
role: "Pelajar SMA",
|
||||
text: "Jujur aja, sempet mikir 'gratisan pasti seadanya', tapi ternyata modul Nihonbuzz super niat. Ada video, latihan, kuis, dan support dari timnya responsif banget. Ini baru kursus online yang ngerti kebutuhan pemula.",
|
||||
image: "/assets/testimonials/tiara.png"
|
||||
},
|
||||
{
|
||||
name: "Reza",
|
||||
role: "Freelance Designer",
|
||||
text: "Aku suka banget suasana kelasnya. Friendly tapi nggak ngasal. Ada struktur belajarnya dan materinya lengkap, plus dikasih latihan-latihan yang bisa langsung dipraktikkan.",
|
||||
image: "/assets/testimonials/reza.png"
|
||||
}
|
||||
];
|
||||
|
||||
export function Testimonials() {
|
||||
return (
|
||||
<section id="testimonials" className="py-24 bg-background relative overflow-hidden">
|
||||
<div className="container mx-auto px-4">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.6 }}
|
||||
className="text-center mb-16"
|
||||
>
|
||||
<h2 className="text-3xl md:text-5xl font-bold mb-6">
|
||||
Kata Mereka Tentang <span className="text-primary">NihonBuzz</span>
|
||||
</h2>
|
||||
<p className="text-muted-foreground text-lg max-w-2xl mx-auto">
|
||||
Cerita asli dari alumni dan siswa yang telah merasakan pengalaman belajar bersama kami.
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{testimonials.map((item, index) => (
|
||||
<motion.div
|
||||
key={index}
|
||||
initial={{ opacity: 0, scale: 0.95 }}
|
||||
whileInView={{ opacity: 1, scale: 1 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.5, delay: index * 0.1 }}
|
||||
>
|
||||
<Card className="h-full bg-card/50 border-white/5 hover:border-primary/20 transition-colors">
|
||||
<CardHeader className="flex flex-row items-center gap-4 pb-2">
|
||||
<Avatar className="h-12 w-12 border border-white/10">
|
||||
<AvatarImage src={item.image} alt={item.name} />
|
||||
<AvatarFallback>{item.name[0]}</AvatarFallback>
|
||||
</Avatar>
|
||||
<div>
|
||||
<h4 className="font-bold text-foreground">{item.name}</h4>
|
||||
<p className="text-xs text-primary">{item.role}</p>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-4">
|
||||
<Quote className="w-8 h-8 text-primary/20 mb-4" />
|
||||
<p className="text-muted-foreground text-sm leading-relaxed italic">
|
||||
"{item.text}"
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
66
src/components/ui/accordion.tsx
Normal file
@@ -0,0 +1,66 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as AccordionPrimitive from "@radix-ui/react-accordion"
|
||||
import { ChevronDownIcon } from "lucide-react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
function Accordion({
|
||||
...props
|
||||
}: React.ComponentProps<typeof AccordionPrimitive.Root>) {
|
||||
return <AccordionPrimitive.Root data-slot="accordion" {...props} />
|
||||
}
|
||||
|
||||
function AccordionItem({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof AccordionPrimitive.Item>) {
|
||||
return (
|
||||
<AccordionPrimitive.Item
|
||||
data-slot="accordion-item"
|
||||
className={cn("border-b last:border-b-0", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function AccordionTrigger({
|
||||
className,
|
||||
children,
|
||||
...props
|
||||
}: React.ComponentProps<typeof AccordionPrimitive.Trigger>) {
|
||||
return (
|
||||
<AccordionPrimitive.Header className="flex">
|
||||
<AccordionPrimitive.Trigger
|
||||
data-slot="accordion-trigger"
|
||||
className={cn(
|
||||
"focus-visible:border-ring focus-visible:ring-ring/50 flex flex-1 items-start justify-between gap-4 rounded-md py-4 text-left text-sm font-medium transition-all outline-none hover:underline focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 [&[data-state=open]>svg]:rotate-180",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<ChevronDownIcon className="text-muted-foreground pointer-events-none size-4 shrink-0 translate-y-0.5 transition-transform duration-200" />
|
||||
</AccordionPrimitive.Trigger>
|
||||
</AccordionPrimitive.Header>
|
||||
)
|
||||
}
|
||||
|
||||
function AccordionContent({
|
||||
className,
|
||||
children,
|
||||
...props
|
||||
}: React.ComponentProps<typeof AccordionPrimitive.Content>) {
|
||||
return (
|
||||
<AccordionPrimitive.Content
|
||||
data-slot="accordion-content"
|
||||
className="data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down overflow-hidden text-sm"
|
||||
{...props}
|
||||
>
|
||||
<div className={cn("pt-0 pb-4", className)}>{children}</div>
|
||||
</AccordionPrimitive.Content>
|
||||
)
|
||||
}
|
||||
|
||||
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
|
||||
53
src/components/ui/avatar.tsx
Normal file
@@ -0,0 +1,53 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as AvatarPrimitive from "@radix-ui/react-avatar"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
function Avatar({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof AvatarPrimitive.Root>) {
|
||||
return (
|
||||
<AvatarPrimitive.Root
|
||||
data-slot="avatar"
|
||||
className={cn(
|
||||
"relative flex size-8 shrink-0 overflow-hidden rounded-full",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function AvatarImage({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof AvatarPrimitive.Image>) {
|
||||
return (
|
||||
<AvatarPrimitive.Image
|
||||
data-slot="avatar-image"
|
||||
className={cn("aspect-square size-full", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function AvatarFallback({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof AvatarPrimitive.Fallback>) {
|
||||
return (
|
||||
<AvatarPrimitive.Fallback
|
||||
data-slot="avatar-fallback"
|
||||
className={cn(
|
||||
"bg-muted flex size-full items-center justify-center rounded-full",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export { Avatar, AvatarImage, AvatarFallback }
|
||||
46
src/components/ui/badge.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
import * as React from "react"
|
||||
import { Slot } from "@radix-ui/react-slot"
|
||||
import { cva, type VariantProps } from "class-variance-authority"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const badgeVariants = cva(
|
||||
"inline-flex items-center justify-center rounded-full border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default:
|
||||
"border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
|
||||
secondary:
|
||||
"border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
|
||||
destructive:
|
||||
"border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
|
||||
outline:
|
||||
"text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
function Badge({
|
||||
className,
|
||||
variant,
|
||||
asChild = false,
|
||||
...props
|
||||
}: React.ComponentProps<"span"> &
|
||||
VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
|
||||
const Comp = asChild ? Slot : "span"
|
||||
|
||||
return (
|
||||
<Comp
|
||||
data-slot="badge"
|
||||
className={cn(badgeVariants({ variant }), className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export { Badge, badgeVariants }
|
||||
62
src/components/ui/button.tsx
Normal file
@@ -0,0 +1,62 @@
|
||||
import * as React from "react"
|
||||
import { Slot } from "@radix-ui/react-slot"
|
||||
import { cva, type VariantProps } from "class-variance-authority"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const buttonVariants = cva(
|
||||
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
||||
destructive:
|
||||
"bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
|
||||
outline:
|
||||
"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
|
||||
secondary:
|
||||
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||
ghost:
|
||||
"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
|
||||
link: "text-primary underline-offset-4 hover:underline",
|
||||
},
|
||||
size: {
|
||||
default: "h-9 px-4 py-2 has-[>svg]:px-3",
|
||||
sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
|
||||
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
|
||||
icon: "size-9",
|
||||
"icon-sm": "size-8",
|
||||
"icon-lg": "size-10",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
size: "default",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
function Button({
|
||||
className,
|
||||
variant = "default",
|
||||
size = "default",
|
||||
asChild = false,
|
||||
...props
|
||||
}: React.ComponentProps<"button"> &
|
||||
VariantProps<typeof buttonVariants> & {
|
||||
asChild?: boolean
|
||||
}) {
|
||||
const Comp = asChild ? Slot : "button"
|
||||
|
||||
return (
|
||||
<Comp
|
||||
data-slot="button"
|
||||
data-variant={variant}
|
||||
data-size={size}
|
||||
className={cn(buttonVariants({ variant, size, className }))}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export { Button, buttonVariants }
|
||||
92
src/components/ui/card.tsx
Normal file
@@ -0,0 +1,92 @@
|
||||
import * as React from "react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
function Card({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
data-slot="card"
|
||||
className={cn(
|
||||
"bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
data-slot="card-header"
|
||||
className={cn(
|
||||
"@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-2 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
data-slot="card-title"
|
||||
className={cn("leading-none font-semibold", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
data-slot="card-description"
|
||||
className={cn("text-muted-foreground text-sm", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function CardAction({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
data-slot="card-action"
|
||||
className={cn(
|
||||
"col-start-2 row-span-2 row-start-1 self-start justify-self-end",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function CardContent({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
data-slot="card-content"
|
||||
className={cn("px-6", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
data-slot="card-footer"
|
||||
className={cn("flex items-center px-6 [.border-t]:pt-6", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export {
|
||||
Card,
|
||||
CardHeader,
|
||||
CardFooter,
|
||||
CardTitle,
|
||||
CardAction,
|
||||
CardDescription,
|
||||
CardContent,
|
||||
}
|
||||
139
src/components/ui/sheet.tsx
Normal file
@@ -0,0 +1,139 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as SheetPrimitive from "@radix-ui/react-dialog"
|
||||
import { XIcon } from "lucide-react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
function Sheet({ ...props }: React.ComponentProps<typeof SheetPrimitive.Root>) {
|
||||
return <SheetPrimitive.Root data-slot="sheet" {...props} />
|
||||
}
|
||||
|
||||
function SheetTrigger({
|
||||
...props
|
||||
}: React.ComponentProps<typeof SheetPrimitive.Trigger>) {
|
||||
return <SheetPrimitive.Trigger data-slot="sheet-trigger" {...props} />
|
||||
}
|
||||
|
||||
function SheetClose({
|
||||
...props
|
||||
}: React.ComponentProps<typeof SheetPrimitive.Close>) {
|
||||
return <SheetPrimitive.Close data-slot="sheet-close" {...props} />
|
||||
}
|
||||
|
||||
function SheetPortal({
|
||||
...props
|
||||
}: React.ComponentProps<typeof SheetPrimitive.Portal>) {
|
||||
return <SheetPrimitive.Portal data-slot="sheet-portal" {...props} />
|
||||
}
|
||||
|
||||
function SheetOverlay({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof SheetPrimitive.Overlay>) {
|
||||
return (
|
||||
<SheetPrimitive.Overlay
|
||||
data-slot="sheet-overlay"
|
||||
className={cn(
|
||||
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function SheetContent({
|
||||
className,
|
||||
children,
|
||||
side = "right",
|
||||
...props
|
||||
}: React.ComponentProps<typeof SheetPrimitive.Content> & {
|
||||
side?: "top" | "right" | "bottom" | "left"
|
||||
}) {
|
||||
return (
|
||||
<SheetPortal>
|
||||
<SheetOverlay />
|
||||
<SheetPrimitive.Content
|
||||
data-slot="sheet-content"
|
||||
className={cn(
|
||||
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out fixed z-50 flex flex-col gap-4 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500",
|
||||
side === "right" &&
|
||||
"data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right inset-y-0 right-0 h-full w-3/4 border-l sm:max-w-sm",
|
||||
side === "left" &&
|
||||
"data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left inset-y-0 left-0 h-full w-3/4 border-r sm:max-w-sm",
|
||||
side === "top" &&
|
||||
"data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top inset-x-0 top-0 h-auto border-b",
|
||||
side === "bottom" &&
|
||||
"data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom inset-x-0 bottom-0 h-auto border-t",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<SheetPrimitive.Close className="ring-offset-background focus:ring-ring data-[state=open]:bg-secondary absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none">
|
||||
<XIcon className="size-4" />
|
||||
<span className="sr-only">Close</span>
|
||||
</SheetPrimitive.Close>
|
||||
</SheetPrimitive.Content>
|
||||
</SheetPortal>
|
||||
)
|
||||
}
|
||||
|
||||
function SheetHeader({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
data-slot="sheet-header"
|
||||
className={cn("flex flex-col gap-1.5 p-4", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function SheetFooter({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
data-slot="sheet-footer"
|
||||
className={cn("mt-auto flex flex-col gap-2 p-4", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function SheetTitle({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof SheetPrimitive.Title>) {
|
||||
return (
|
||||
<SheetPrimitive.Title
|
||||
data-slot="sheet-title"
|
||||
className={cn("text-foreground font-semibold", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function SheetDescription({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof SheetPrimitive.Description>) {
|
||||
return (
|
||||
<SheetPrimitive.Description
|
||||
data-slot="sheet-description"
|
||||
className={cn("text-muted-foreground text-sm", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export {
|
||||
Sheet,
|
||||
SheetTrigger,
|
||||
SheetClose,
|
||||
SheetContent,
|
||||
SheetHeader,
|
||||
SheetFooter,
|
||||
SheetTitle,
|
||||
SheetDescription,
|
||||
}
|
||||
6
src/lib/utils.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { clsx, type ClassValue } from "clsx"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
}
|
||||
34
tsconfig.json
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2017",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "react-jsx",
|
||||
"incremental": true,
|
||||
"plugins": [
|
||||
{
|
||||
"name": "next"
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"next-env.d.ts",
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
".next/types/**/*.ts",
|
||||
".next/dev/types/**/*.ts",
|
||||
"**/*.mts"
|
||||
],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||