feat: initialize Next.js project with a comprehensive landing page structure, UI components, and assets.

This commit is contained in:
nihonbuzz
2026-01-23 03:23:33 +07:00
commit c1e5db5bce
76 changed files with 10164 additions and 0 deletions

41
.gitignore vendored Normal file
View 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
View 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
View 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.**
[![Website](https://img.shields.io/badge/Website-nihonbuzz.org-red?style=flat-square&logo=google-chrome)](https://nihonbuzz.org)
[![Instagram](https://img.shields.io/badge/Instagram-%40nihon_buzz-E4405F?style=flat-square&logo=instagram)](https://instagram.com/nihon_buzz)
[![TikTok](https://img.shields.io/badge/TikTok-%40nihonbuzz-000000?style=flat-square&logo=tiktok)](https://tiktok.com/@nihonbuzz)
[![License](https://img.shields.io/badge/License-Proprietary-blue?style=flat-square)](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 |
| :--- | :--- | :--- | :--- |
| ![Next.js](https://img.shields.io/badge/Next.js_15-black?logo=next.js) | ![Tailwind CSS](https://img.shields.io/badge/Tailwind-38B2AC?logo=tailwind-css) | ![Shadcn UI](https://img.shields.io/badge/Shadcn_UI-000000?logo=shadcnui) | ![Framer Motion](https://img.shields.io/badge/Framer_Motion-0055FF?logo=framer) |
---
## 📍 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
View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

38
package.json Normal file
View 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
View File

@@ -0,0 +1,7 @@
const config = {
plugins: {
"@tailwindcss/postcss": {},
},
};
export default config;

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

BIN
public/apple-icon-57x57.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

BIN
public/apple-icon-60x60.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

BIN
public/apple-icon-72x72.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

BIN
public/apple-icon-76x76.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

BIN
public/apple-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

BIN
public/assets/logo-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 KiB

BIN
public/assets/logo-main.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 277 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 493 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 539 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

2
public/browserconfig.xml Normal file
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
public/favicon-32x32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
public/favicon-96x96.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

1
public/file.svg Normal file
View 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
View 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
View 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
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

BIN
public/ms-icon-150x150.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

BIN
public/ms-icon-310x310.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

BIN
public/ms-icon-70x70.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

1
public/next.svg Normal file
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

1
public/vercel.svg Normal file
View 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
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

129
src/app/globals.css Normal file
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

67
src/app/layout.tsx Normal file
View 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
View 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>
);
}

View 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>&copy; {new Date().getFullYear()} NihonBuzz. All rights reserved.</p>
</div>
</div>
</footer>
)
}

View 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>
);
}

View 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>
);
}

View 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>
);
}

View 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>
);
}

View 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>
);
}

View 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>
);
}

View 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>
);
}

View 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>
);
}

View 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 }

View 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 }

View 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 }

View 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 }

View 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
View 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
View 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
View 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"]
}