diff --git a/apps/sale/app/(guest)/page.tsx b/apps/sale/app/(guest)/page.tsx
index 8f1e25c..0c2e82e 100644
--- a/apps/sale/app/(guest)/page.tsx
+++ b/apps/sale/app/(guest)/page.tsx
@@ -1,16 +1,17 @@
import type { Metadata } from "next";
-import { Header } from '@workspace/ui/components/layout/Header';
-import { Footer } from '@workspace/ui/components/layout/Footer';
-import { HeroSection } from '@/features/landing/components/HeroSection';
-import { PainPointSection } from '@/features/landing/components/PainPointSection';
-import { PosterSection } from '@/features/landing/components/PosterSection';
-import { SolutionSection } from '@/features/landing/components/SolutionSection';
-import { HowItWorksSection } from '@/features/landing/components/HowItWorksSection';
-import { TutorSection } from '@/features/landing/components/TutorSection';
-import { TutorCarouselSection } from '@/features/landing/components/TutorCarouselSection';
-import { FaqSection } from '@/features/landing/components/FaqSection';
-import { TutorPainpointSection } from '@/features/landing/components/TutorPainpointSection';
-import { TutorGuideTrust } from '@/features/tutor-guide/components/TutorGuideTrust';
+import { Header } from "@workspace/ui/components/layout/Header";
+import { Footer } from "@workspace/ui/components/layout/Footer";
+import { HeroSection } from "@/features/landing/components/HeroSection";
+import { AIFeaturesSection } from "@/features/landing/components/AIFeaturesSection";
+import { VideoIntroSection } from "@/features/landing/components/VideoIntroSection";
+import { FeatureShowcaseSection } from "@/features/landing/components/FeatureShowcaseSection";
+import { HowItWorksSection } from "@/features/landing/components/HowItWorksSection";
+import { TutorSection } from "@/features/landing/components/TutorSection";
+import { TutorCarouselSection } from "@/features/landing/components/TutorCarouselSection";
+import { FeedbackSection } from "@/features/landing/components/FeedbackSection";
+import { FaqSection } from "@/features/landing/components/FaqSection";
+
+import { TutorGuideTrust } from "@/features/tutor-guide/components/TutorGuideTrust";
export const metadata: Metadata = {
title: "Cộng Đồng Gia Sư - Tìm Gia Sư Phù Hợp Nhanh Chóng Với AI",
@@ -49,19 +50,17 @@ export default function LandingPage() {
-
-
-
-
+
+
+
+
>
);
}
-
-
diff --git a/apps/sale/features/landing/components/AIFeaturesSection.tsx b/apps/sale/features/landing/components/AIFeaturesSection.tsx
new file mode 100644
index 0000000..699b387
--- /dev/null
+++ b/apps/sale/features/landing/components/AIFeaturesSection.tsx
@@ -0,0 +1,109 @@
+"use client";
+
+import Image from "next/image";
+import Link from "next/link";
+import {
+ Sparkle,
+ MicrophoneStage,
+ BookOpen,
+ Brain,
+ ArrowRight,
+} from "@phosphor-icons/react";
+
+const AI_POINTS = [
+ {
+ icon: Sparkle,
+ label: "Tìm kiếm gia sư bằng AI",
+ bgIcon: "bg-purple-500/20 text-purple-300",
+ },
+ {
+ icon: Brain,
+ label: "Gợi ý gia sư phù hợp với bạn",
+ bgIcon: "bg-cyan-500/20 text-cyan-300",
+ },
+];
+
+export function AIFeaturesSection() {
+ return (
+
+
+ {/* Dark Container Box (Udemy / Modern AI Card Style) */}
+
+ {/* Subtle Ambient Light Gradients */}
+
+
+
+
+ {/* Left Column: Headline, Description, 2x2 Feature Bullets & CTA */}
+
+
+
+ Nâng cao chất lượng học tập cùng{" "}
+ BeeWise AI
+
+
+
+ BeeWise tối ưu hóa việc học tập qua nhiều công cụ AI, giúp bạn
+ kết nối đúng người, chuẩn bị kỹ càng và học hiệu quả hơn.
+
+
+
+ {/* 2x2 Features Bullets */}
+
+ {AI_POINTS.map((item, idx) => {
+ const Icon = item.icon;
+ return (
+
+
+
+
+
+ {item.label}
+
+
+ );
+ })}
+
+
+ {/* CTA Button */}
+
+
+
Trải nghiệm ngay
+
+
+
+
+
+ {/* Right Column: AI Visual Artwork / Collage Image (Larger, No Border, Crisp Quality) */}
+
+
+
+
+
+ );
+}
diff --git a/apps/sale/features/landing/components/FeatureShowcaseSection.tsx b/apps/sale/features/landing/components/FeatureShowcaseSection.tsx
new file mode 100644
index 0000000..886a951
--- /dev/null
+++ b/apps/sale/features/landing/components/FeatureShowcaseSection.tsx
@@ -0,0 +1,411 @@
+"use client";
+
+import Image from "next/image";
+import {
+ Sparkle,
+ ShieldCheck,
+ ChatCircleDots,
+ CalendarCheck,
+ CheckCircle,
+ MagnifyingGlass,
+ PaperPlaneTilt,
+ Clock,
+ Star,
+ Check,
+ FileText,
+ VideoCamera,
+} from "@phosphor-icons/react";
+
+interface ShowcaseItem {
+ id: string;
+ tag: string;
+ tagColor: string;
+ title: string;
+ subtitle: string;
+ description: string;
+ benefits: string[];
+ icon: React.ElementType;
+ iconColor: string;
+ accentBg: string;
+ mockupTitle: string;
+ renderMockup: () => React.ReactNode;
+}
+
+const SHOWCASE_ITEMS: ShowcaseItem[] = [
+ {
+ id: "ai-search",
+ tag: "Beewise AI",
+ tagColor: "bg-purple-500/10 text-purple-600 border-purple-200",
+ title: "AI Gợi Ý Gia Sư Phù Hợp",
+ subtitle: "Tìm đúng gia sư cho từng mục tiêu học tập",
+ description:
+ "Thay vì phải đăng bài trên các trang mạng xã hội, hệ thống AI của BeeWise phân tích sâu nhu cầu học tập, trình độ hiện tại, khu vực và ngân sách để đề xuất những gia sư tối ưu nhất.",
+ benefits: [
+ "Kết nối nhanh chóng chỉ sau 30 giây",
+ "Độ tương thích mục tiêu lên đến 98%",
+ "Gợi ý dựa trên đánh giá thực tế của học viên",
+ ],
+ icon: Sparkle,
+ iconColor: "text-purple-600",
+ accentBg: "from-purple-500/10 via-indigo-500/5 to-transparent",
+ mockupTitle: "Tìm kiếm bằng AI",
+ renderMockup: () => (
+
+
+
+ ),
+ },
+ {
+ id: "verified-tutor",
+ tag: "Đã kiểm duyệt",
+ tagColor: "bg-emerald-500/10 text-emerald-600 border-emerald-200",
+ title: "Gia Sư Được Xác Thực",
+ subtitle: "Hồ sơ minh bạch, uy tín tuyệt đối",
+ description:
+ "Mọi gia sư trên BeeWise đều phải hoàn thành quy trình xác thực đa tầng gồm kiểm tra thông tin, bằng cấp chuyên môn, chứng chỉ quốc tế và phỏng vấn trực tiếp trước khi nhận lớp.",
+ benefits: [
+ "100% hồ sơ được kiểm duyệt thực tế",
+ "Bằng cấp, chứng chỉ rõ ràng, công khai",
+ "Đánh giá chân thực từ học viên đã học",
+ ],
+ icon: ShieldCheck,
+ iconColor: "text-emerald-600",
+ accentBg: "from-emerald-500/10 via-teal-500/5 to-transparent",
+ mockupTitle: "Hồ sơ kiểm duyệt • Mã GS: BW-8892",
+ renderMockup: () => (
+
+ {/* Tutor Profile Header Card */}
+
+
+
+
+
+ Lê Quỳnh Anh
+
+
+ VERIFIED
+
+
+
+ Thủ khoa ĐH Ngoại Thương • IELTS 8.0
+
+
+ ✓ Đã qua phỏng vấn chuyên môn bởi BeeWise
+
+
+
+
+ {/* Verification Badges Grid */}
+
+ {[
+ {
+ label: "Căn cước công dân",
+ status: "Đã xác thực CCCD",
+ icon: CheckCircle,
+ },
+ {
+ label: "Bằng Cử nhân Xuất sắc",
+ status: "Đại học Ngoại Thương",
+ icon: FileText,
+ },
+ {
+ label: "Chứng chỉ Quốc tế",
+ status: "IELTS 8.0 (2024)",
+ icon: Star,
+ },
+ {
+ label: "Phỏng vấn giảng dạy",
+ status: "Đạt chuẩn sư phạm",
+ icon: VideoCamera,
+ },
+ ].map((item, i) => {
+ const ItemIcon = item.icon;
+ return (
+
+
+
+
+
+
+ {item.label}
+
+
+ {item.status}
+
+
+
+ );
+ })}
+
+
+ ),
+ },
+ {
+ id: "live-chat",
+ tag: "Tương tác trực tiếp",
+ tagColor: "bg-amber-500/10 text-amber-600 border-amber-200",
+ title: "Phòng Chat Hỗ Trợ",
+ subtitle: "Trao đổi bảo mật",
+ description:
+ "Hệ thống phòng chat trực tiếp giúp phụ huynh, học viên và gia sư dễ dàng trao đổi mục tiêu học tập, gửi bài tập và tài liệu học tập ngay trên nền tảng mà không lo rò rỉ thông tin cá nhân.",
+ benefits: [
+ "Trao đổi trực tiếp, phản hồi nhanh chóng",
+ "Gửi tài liệu, đề thi và hình ảnh bài tập tiện lợi",
+ "Bảo mật thông tin cá nhân tối đa",
+ ],
+ icon: ChatCircleDots,
+ iconColor: "text-amber-600",
+ accentBg: "from-amber-500/10 via-orange-500/5 to-transparent",
+ mockupTitle: "Phòng chat • Gia sư & Học viên",
+ renderMockup: () => (
+
+ {/* Chat Message 1 (Learner) */}
+
+
+ Em chào cô Mai Linh ạ! Em muốn tìm gia sư kèm môn Toán 12 vào tối
+ Thứ 3 và Thứ 6.
+
+
+
+ {/* Chat Message 2 (Tutor) */}
+
+
+ ML
+
+
+ Chào em nhé! Lịch tối T3 và T6 cô đang trống. Em muốn tập trung vào
+ phần Hàm số hay Hình học trước để cô chuẩn bị giáo án? 📚
+
+
+
+ {/* Chat Input Bar */}
+
+
+ Nhập tin nhắn trao đổi...
+
+
+
+
+
+
+ ),
+ },
+ {
+ id: "schedule-management",
+ tag: "Linh hoạt",
+ tagColor: "bg-blue-500/10 text-blue-600 border-blue-200",
+ title: "Lịch Học & Nhắc Nhở Tự Động",
+ subtitle: "Chủ động thời gian, không bỏ lỡ buổi học",
+ description:
+ "Dễ dàng xem trước lịch rảnh của gia sư trong tuần, đăng ký buổi học thử và nhận thông báo nhắc nhở tự động trước mỗi ca dạy để việc học luôn đúng lộ trình.",
+ benefits: [
+ "Xem lịch trống theo thời gian thực",
+ "Thông báo nhắc nhở trước mỗi buổi học",
+ "Linh hoạt điều chỉnh lịch học khi cần",
+ ],
+ icon: CalendarCheck,
+ iconColor: "text-blue-600",
+ accentBg: "from-blue-500/10 via-indigo-500/5 to-transparent",
+ mockupTitle: "Thời khóa biểu • Lịch học tuần này",
+ renderMockup: () => (
+
+ {/* Upcoming class card */}
+
+
+
+
+ Buổi học sắp diễn ra
+
+
+ Tối nay
+
+
+
+
+
+
+ Toán 12 • Luyện Đề Số 4
+
+
+
+ 19:30 - 21:00 • Với Thầy Hoàng Nam
+
+
+
+ Vào lớp
+
+
+
+
+ {/* Weekly Slot Grid */}
+
+ {[
+ { day: "Thứ 2", time: "19:00", active: false, label: "Trống" },
+ { day: "Thứ 4", time: "19:30", active: true, label: "Đã đặt" },
+ { day: "Thứ 6", time: "19:30", active: true, label: "Đã đặt" },
+ { day: "Chủ nhật", time: "09:00", active: false, label: "Trống" },
+ ].map((slot, i) => (
+
+
{slot.day}
+
{slot.time}
+
+ {slot.label}
+
+
+ ))}
+
+
+ ),
+ },
+];
+
+export function FeatureShowcaseSection() {
+ return (
+
+ {/* Background ambient glow */}
+
+
+
+
+ {/* Section Header */}
+
+
+ Trải nghiệm nền tảng
+
+
+
+ BeeWise cung cấp các tính năng giúp bạn
+ kết nối & học tập
+
+
+
+ Thiết kế trực quan, dễ thao tác và đồng bộ - giúp phụ huynh, học
+ viên và gia sư kết nối an toàn, minh bạch và hiệu quả nhất.
+
+
+
+ {/* Zigzag Showcase Rows */}
+
+ {SHOWCASE_ITEMS.map((item, index) => {
+ const Icon = item.icon;
+ const isEven = index % 2 === 1; // Reverse order for zigzag
+
+ return (
+
+ {/* Content Side (5 cols) */}
+
+
+
+ {item.tag}
+
+
+
+
+ {item.title}
+
+
+ {item.subtitle}
+
+
+
+
+ {item.description}
+
+
+ {/* Bullet Benefits */}
+
+ {item.benefits.map((benefit, i) => (
+
+ ))}
+
+
+
+ {/* Mockup Image / Visual Window Side (7 cols - Focus on Image) */}
+
+
+
+ {/* Window Controls Header */}
+
+
+
+
+
+
+
+ {item.mockupTitle}
+
+
+
+ {/* Mockup Body Content */}
+
+ {item.renderMockup()}
+
+
+
+
+
+ );
+ })}
+
+
+
+ );
+}
diff --git a/apps/sale/features/landing/components/FeedbackSection.tsx b/apps/sale/features/landing/components/FeedbackSection.tsx
new file mode 100644
index 0000000..27b12c2
--- /dev/null
+++ b/apps/sale/features/landing/components/FeedbackSection.tsx
@@ -0,0 +1,149 @@
+"use client";
+
+import Image from "next/image";
+import { Star, ChatCircle } from "@phosphor-icons/react";
+
+interface FeedbackItem {
+ id: string;
+ name: string;
+ role: string;
+ subject: string;
+ avatar: string;
+ rating: number;
+ comment: string;
+}
+
+const FEEDBACK_LIST: FeedbackItem[] = [
+ {
+ id: "1",
+ name: "Chị Thu Hà",
+ role: "Phụ huynh",
+ subject: "Toán lớp 9 ôn thi vào 10",
+ avatar:
+ "https://images.unsplash.com/photo-1544005313-94ddf0286df2?w=150&auto=format&fit=crop&q=80",
+ rating: 5,
+ comment:
+ "Nhờ tìm gia sư qua AI mà bé nhà mình tìm được cô giáo rất hợp tính. Sau 2 tháng điểm kiểm tra môn Toán tăng từ 6.5 lên 8.5!",
+ },
+ {
+ id: "2",
+ name: "Minh Quân",
+ role: "Học viên",
+ subject: "Luyện thi IELTS 7.0",
+ avatar:
+ "https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?w=150&auto=format&fit=crop&q=80",
+ rating: 5,
+ comment:
+ "Tính năng AI tóm tắt bài học cực kỳ tiện, học xong có ngay tài liệu ôn lại. Thầy giáo nhiệt tình và hướng dẫn phát âm rất chuẩn.",
+ },
+ {
+ id: "3",
+ name: "Anh Hoàng Nam",
+ role: "Phụ huynh",
+ subject: "Tiếng Anh lớp 6",
+ avatar:
+ "https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=150&auto=format&fit=crop&q=80",
+ rating: 5,
+ comment:
+ "Giao diện dễ dùng, chỉ mất chưa đầy 1 phút là hệ thống gợi ý đúng gia sư gần nhà. Hồ sơ gia sư minh bạch nên gia đình rất yên tâm.",
+ },
+ {
+ id: "4",
+ name: "Thảo Vy",
+ role: "Học viên",
+ subject: "Hóa học lớp 11",
+ avatar:
+ "https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=150&auto=format&fit=crop&q=80",
+ rating: 5,
+ comment:
+ "Trước đây mình sợ môn Hóa lắm, nhờ chị gia sư kiên nhẫn giảng lại từ gốc mà giờ mình tự tin làm hết các bài tập nâng cao trên lớp.",
+ },
+];
+
+export function FeedbackSection() {
+ return (
+
+ {/* Decorative background glows */}
+
+
+
+
+ {/* Header */}
+
+
+
+ Đánh giá từ người dùng
+
+
+
+ Học viên & Phụ huynh{" "}
+ nói gì về BeeWise?
+
+
+
+ Những trải nghiệm thực tế từ cộng đồng học viên và phụ huynh đã tin
+ tưởng đồng hành cùng BeeWise.
+
+
+
+ {/* 4 Cards in 1 Row on Desktop */}
+
+ {FEEDBACK_LIST.map((item) => (
+
+ {/* Rating stars */}
+
+
+
+ {Array.from({ length: item.rating }).map((_, i) => (
+
+ ))}
+
+
+ {item.role}
+
+
+
+ {/* Feedback content in quotes */}
+
+ “{item.comment}”
+
+
+
+ {/* User info */}
+
+
+
+
+
+
+ {item.name}
+
+
+ {item.subject}
+
+
+
+
+ ))}
+
+
+
+ );
+}
diff --git a/apps/sale/features/landing/components/HeroVisual.tsx b/apps/sale/features/landing/components/HeroVisual.tsx
deleted file mode 100644
index 85cfd89..0000000
--- a/apps/sale/features/landing/components/HeroVisual.tsx
+++ /dev/null
@@ -1,339 +0,0 @@
-"use client";
-
-import Image from "next/image";
-import { motion } from "motion/react";
-import {
- BookOpenIcon,
- PencilSimpleIcon,
- MathOperationsIcon,
- BrainIcon,
- StarIcon,
- GraduationCapIcon,
- ChartLineUpIcon,
- LightbulbIcon,
- FlaskIcon,
- MusicNotesIcon,
-} from "@phosphor-icons/react";
-
-const FLOATING_ICONS = [
- {
- Icon: BookOpenIcon,
- top: "20%",
- left: "15%",
- size: 28,
- delay: 0,
- duration: 5.2,
- amplitude: 10,
- color: "#FFC500",
- bg: "rgba(255,197,0,0.15)",
- rotate: -12,
- },
- {
- Icon: PencilSimpleIcon,
- top: "18%",
- left: "82%",
- size: 22,
- delay: 0.8,
- duration: 4.8,
- amplitude: 8,
- color: "#FADC76",
- bg: "rgba(250,220,118,0.15)",
- rotate: 18,
- },
- {
- Icon: StarIcon,
- top: "10%",
- left: "40%",
- size: 18,
- delay: 1.4,
- duration: 6.1,
- amplitude: 12,
- color: "#FFC500",
- bg: "rgba(255,197,0,0.12)",
- rotate: 0,
- weight: "fill" as const,
- },
- {
- Icon: MathOperationsIcon,
- top: "38%",
- left: "4%",
- size: 24,
- delay: 0.5,
- duration: 5.5,
- amplitude: 9,
- color: "#CFE1FA",
- bg: "rgba(207,225,250,0.12)",
- rotate: 6,
- },
- {
- Icon: FlaskIcon,
- top: "45%",
- left: "87%",
- size: 22,
- delay: 1.1,
- duration: 4.6,
- amplitude: 11,
- color: "#FADC76",
- bg: "rgba(250,220,118,0.12)",
- rotate: -8,
- },
- {
- Icon: BrainIcon,
- top: "32%",
- left: "78%",
- size: 26,
- delay: 2.0,
- duration: 5.9,
- amplitude: 10,
- color: "#CFE1FA",
- bg: "rgba(207,225,250,0.15)",
- rotate: 10,
- },
- {
- Icon: GraduationCapIcon,
- top: "72%",
- left: "8%",
- size: 30,
- delay: 0.3,
- duration: 5.0,
- amplitude: 8,
- color: "#FFC500",
- bg: "rgba(255,197,0,0.15)",
- rotate: -6,
- weight: "fill" as const,
- },
- {
- Icon: ChartLineUpIcon,
- top: "78%",
- left: "82%",
- size: 22,
- delay: 1.7,
- duration: 6.3,
- amplitude: 13,
- color: "#FADC76",
- bg: "rgba(250,220,118,0.12)",
- rotate: 14,
- },
- {
- Icon: LightbulbIcon,
- top: "60%",
- left: "3%",
- size: 20,
- delay: 2.5,
- duration: 4.9,
- amplitude: 7,
- color: "#CFE1FA",
- bg: "rgba(207,225,250,0.10)",
- rotate: -4,
- },
- {
- Icon: MusicNotesIcon,
- top: "65%",
- left: "88%",
- size: 20,
- delay: 1.9,
- duration: 5.7,
- amplitude: 9,
- color: "#FFC500",
- bg: "rgba(255,197,0,0.10)",
- rotate: 20,
- },
-];
-
-function HexGrid() {
- return (
-
-
-
-
-
-
-
-
-
- );
-}
-
-function StatBadge({
- value,
- label,
- delay,
- className,
-}: {
- value: string;
- label: string;
- delay: number;
- className?: string;
-}) {
- return (
-
-
- {value}
-
-
- {label}
-
-
- );
-}
-
-export function HeroVisual() {
- return (
-
-
-
-
-
-
- {[340, 240, 140].map((d, i) => (
-
- ))}
-
-
- {FLOATING_ICONS.map(
- (
- {
- Icon,
- top,
- left,
- size,
- delay,
- duration,
- amplitude,
- color,
- bg,
- rotate,
- weight,
- },
- i,
- ) => (
-
-
-
- ),
- )}
-
-
-
-
-
-
-
-
- );
-}
diff --git a/apps/sale/features/landing/components/HowItWorksSection.tsx b/apps/sale/features/landing/components/HowItWorksSection.tsx
index b15bccf..6b5fe59 100644
--- a/apps/sale/features/landing/components/HowItWorksSection.tsx
+++ b/apps/sale/features/landing/components/HowItWorksSection.tsx
@@ -111,7 +111,7 @@ export function HowItWorksSection() {
diff --git a/apps/sale/features/landing/components/PainPointSection.tsx b/apps/sale/features/landing/components/PainPointSection.tsx
deleted file mode 100644
index 0a32512..0000000
--- a/apps/sale/features/landing/components/PainPointSection.tsx
+++ /dev/null
@@ -1,118 +0,0 @@
-"use client";
-
-import Image from "next/image";
-import { useRef } from "react";
-import { gsap } from "gsap";
-import { useGSAP } from "@gsap/react";
-import { ScrollTrigger } from "gsap/ScrollTrigger";
-
-import {
- Clock,
- ShieldWarning,
- ChatCircleDots,
- MagnifyingGlass,
-} from "@phosphor-icons/react";
-import { PAIN_POINTS } from "../data/landing.data";
-
-if (typeof window !== "undefined") {
- gsap.registerPlugin(ScrollTrigger, useGSAP);
-}
-
-const ICON_MAP = {
- Clock,
- ShieldWarning,
- ChatCircleDots,
- MagnifyingGlass,
-} as const;
-
-type IconKey = keyof typeof ICON_MAP;
-
-export function PainPointSection() {
- const containerRef = useRef(null);
-
- useGSAP(
- () => {
- const bubbles = gsap.utils.toArray(".painpoint-bubble");
-
- gsap.set(bubbles, { scale: 0, autoAlpha: 0, transformOrigin: "50% 50%" });
-
- ScrollTrigger.create({
- trigger: containerRef.current,
- start: "top 75%",
- onEnter: () => {
- bubbles.forEach((bubble, i) => {
- const delay = i * 0.15 + gsap.utils.random(-0.05, 0.05);
-
- gsap.to(bubble as Element, {
- scale: 1,
- autoAlpha: 1,
- duration: 0.6,
- ease: "back.out(1.5)",
- delay: delay,
- });
- });
- },
- once: true,
- });
- },
- { scope: containerRef },
- );
-
- return (
-
-
-
-
-
-
-
-
-
- {/* Cột phải (Bubbles) - Dàn 2 cột 2 dòng căn đều */}
-
- {PAIN_POINTS.map((point) => {
- const Icon = ICON_MAP[point.icon as IconKey];
- return (
-
-
-
-
-
- {point.text}
-
-
- );
- })}
-
-
- {/* Mobile version - Only Image */}
-
-
-
-
-
-
- );
-}
diff --git a/apps/sale/features/landing/components/PosterSection.tsx b/apps/sale/features/landing/components/PosterSection.tsx
deleted file mode 100644
index 68d4c55..0000000
--- a/apps/sale/features/landing/components/PosterSection.tsx
+++ /dev/null
@@ -1,36 +0,0 @@
-"use client";
-import Image from "next/image";
-import { useRouter } from "next/navigation";
-
-export function PosterSection() {
- const router = useRouter();
- return (
- router.push("/login")}
- >
- {/* Desktop version */}
-
-
-
- {/* Mobile version */}
-
-
-
-
- );
-}
diff --git a/apps/sale/features/landing/components/SolutionMotion.tsx b/apps/sale/features/landing/components/SolutionMotion.tsx
deleted file mode 100644
index 073746b..0000000
--- a/apps/sale/features/landing/components/SolutionMotion.tsx
+++ /dev/null
@@ -1,53 +0,0 @@
-"use client";
-
-import { useRef, useEffect, useState } from "react";
-import { useReducedMotion } from "motion/react";
-import { motion } from "motion/react";
-
-type SolutionMotionProps = {
- children: React.ReactNode;
- index: number;
-};
-
-export function SolutionMotion({ children, index }: SolutionMotionProps) {
- const ref = useRef(null);
- const [visible, setVisible] = useState(false);
- const shouldReduceMotion = useReducedMotion();
-
- useEffect(() => {
- const el = ref.current;
- if (!el || shouldReduceMotion) {
- setVisible(true);
- return;
- }
-
- const observer = new IntersectionObserver(
- ([entry]) => {
- if (entry.isIntersecting) {
- setVisible(true);
- observer.disconnect();
- }
- },
- { threshold: 0.15 },
- );
-
- observer.observe(el);
- return () => observer.disconnect();
- }, [shouldReduceMotion]);
-
- return (
-
- {children}
-
- );
-}
diff --git a/apps/sale/features/landing/components/SolutionSection.tsx b/apps/sale/features/landing/components/SolutionSection.tsx
deleted file mode 100644
index 81d9478..0000000
--- a/apps/sale/features/landing/components/SolutionSection.tsx
+++ /dev/null
@@ -1,68 +0,0 @@
-import { SOLUTION_FEATURES } from "../data/landing.data";
-import { SolutionMotion } from "./SolutionMotion";
-import { HeroVisual } from "./HeroVisual";
-
-const CELL_STYLES: Record = {
- primary: "bg-primary text-primary-foreground col-span-1 sm:col-span-2",
- accent: "bg-accent text-primary",
-};
-
-export function SolutionSection() {
- return (
-
-
-
- {/* Cột trái (Sticky) - Chỉ hiển thị trên Desktop */}
-
-
- {/* Cột phải (Danh sách tính năng) */}
-
- {SOLUTION_FEATURES.map((feature, index) => (
-
-
-
-
- {feature.title}
-
-
- {feature.description}
-
-
-
-
- ))}
-
- {/* HeroVisual hiển thị trên Mobile/Tablet ở cuối danh sách tính năng */}
-
-
-
-
-
- );
-}
diff --git a/apps/sale/features/landing/components/TutorPainpointSection.tsx b/apps/sale/features/landing/components/TutorPainpointSection.tsx
deleted file mode 100644
index 301d5eb..0000000
--- a/apps/sale/features/landing/components/TutorPainpointSection.tsx
+++ /dev/null
@@ -1,115 +0,0 @@
-"use client";
-
-import Image from "next/image";
-import { useRef } from "react";
-import { gsap } from "gsap";
-import { useGSAP } from "@gsap/react";
-import { ScrollTrigger } from "gsap/ScrollTrigger";
-
-import {
- MagnifyingGlass,
- ShieldWarning,
- Star,
- Certificate,
-} from "@phosphor-icons/react";
-import { TUTOR_PAIN_POINTS } from "../data/landing.data";
-
-if (typeof window !== "undefined") {
- gsap.registerPlugin(ScrollTrigger, useGSAP);
-}
-
-const ICON_MAP = {
- MagnifyingGlass,
- ShieldWarning,
- Star,
- Certificate,
-} as const;
-
-type IconKey = keyof typeof ICON_MAP;
-
-export function TutorPainpointSection() {
- const containerRef = useRef(null);
-
- useGSAP(
- () => {
- const bubbles = gsap.utils.toArray(".tutor-painpoint-bubble");
-
- gsap.set(bubbles, { autoAlpha: 0, y: 30 });
-
- ScrollTrigger.create({
- trigger: containerRef.current,
- start: "top 75%",
- onEnter: () => {
- gsap.to(bubbles, {
- y: 0,
- autoAlpha: 1,
- duration: 0.8,
- ease: "power2.out",
- stagger: 0.15,
- });
- },
- once: true,
- });
- },
- { scope: containerRef },
- );
-
- return (
-
- {/* Desktop Background */}
-
-
-
-
-
-
- {/* Cột trái (Bubbles) - Dàn 2 cột 2 dòng căn đều */}
-
- {TUTOR_PAIN_POINTS.map((point) => {
- const Icon = ICON_MAP[point.icon as IconKey];
- return (
-
-
-
-
-
- {point.text}
-
-
- );
- })}
-
-
-
-
- {/* Mobile version - Only Image */}
-
-
-
-
-
-
- );
-}
diff --git a/apps/sale/features/landing/components/VideoIntroSection.tsx b/apps/sale/features/landing/components/VideoIntroSection.tsx
new file mode 100644
index 0000000..c8473d7
--- /dev/null
+++ b/apps/sale/features/landing/components/VideoIntroSection.tsx
@@ -0,0 +1,119 @@
+"use client";
+
+import {
+ PlayCircle,
+ CheckCircle,
+ Student,
+ ChalkboardTeacher,
+ Handshake,
+} from "@phosphor-icons/react";
+import Image from "next/image";
+
+export function VideoIntroSection() {
+ return (
+
+
+
+
+
+
+ {/* Text Content */}
+
+
+ Khám phá BeeWise
+
+
+
+ Nền tảng 2 trong 1
+
+
+
+ BeeWise là nền tảng kết nối trực tiếp học viên với những gia sư
+ chất lượng, giúp quá trình học tập và giảng dạy dễ dàng hơn bao
+ giờ hết.
+
+
+
+ {[
+ {
+ icon: Student,
+ title: "Học viên hài lòng",
+ desc: "Nhiều đánh giá tốt",
+ },
+ {
+ icon: ChalkboardTeacher,
+ title: "Gia sư chuyên nghiệp",
+ desc: "Đã qua kiểm duyệt",
+ },
+ {
+ icon: Handshake,
+ title: "Kết nối nhanh chóng",
+ desc: "Beewise AI",
+ },
+ {
+ icon: CheckCircle,
+ title: "An toàn & Minh bạch",
+ desc: "Đánh giá từ học viên",
+ },
+ ].map((item, idx) => (
+
+
+
+
+
+
+ {item.title}
+
+
+ {item.desc}
+
+
+
+ ))}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* Cute floating badge */}
+
+
+
+
+
+ Xem giới thiệu nền tảng
+
+
+
+
+
+
+ );
+}
diff --git a/apps/sale/features/tutor-guide/components/TutorGuideTrust.tsx b/apps/sale/features/tutor-guide/components/TutorGuideTrust.tsx
index b4dd500..512c50f 100644
--- a/apps/sale/features/tutor-guide/components/TutorGuideTrust.tsx
+++ b/apps/sale/features/tutor-guide/components/TutorGuideTrust.tsx
@@ -1,100 +1,142 @@
+"use client";
+
import Image from "next/image";
import {
- CalendarCheckIcon,
- ChatCircleTextIcon,
- ArrowsCounterClockwiseIcon,
- BellIcon,
- LockIcon,
-} from "@phosphor-icons/react/dist/ssr";
+ CalendarCheck,
+ ChatCircleText,
+ ArrowsCounterClockwise,
+ Bell,
+ Lock,
+ Headset,
+ ShieldCheck,
+} from "@phosphor-icons/react";
-const TRUST_ITEMS = [
+const TRUST_FEATURES = [
{
id: "schedule",
- icon: CalendarCheckIcon,
- label: "Theo dõi lịch học rõ ràng",
+ icon: CalendarCheck,
+ title: "Theo dõi lịch học rõ ràng",
+ desc: "Cập nhật thời khóa biểu theo thời gian thực, dễ dàng quản lý ca học và buổi dạy trong tuần.",
+ color: "text-blue-600 bg-blue-500/10 border-blue-200",
},
{
id: "history",
- icon: ChatCircleTextIcon,
- label: "Quản lý lịch sử trao đổi",
+ icon: ChatCircleText,
+ title: "Quản lý lịch sử trao đổi",
+ desc: "Toàn bộ thông tin bài học, tài liệu và tiến độ đều được lưu trữ tập trung, tiện tra cứu.",
+ color: "text-purple-600 bg-purple-500/10 border-purple-200",
},
{
id: "reconcile",
- icon: ArrowsCounterClockwiseIcon,
- label: "Đối soát thông tin minh bạch",
+ icon: ArrowsCounterClockwise,
+ title: "Đối soát thông tin minh bạch",
+ desc: "Hệ thống ghi nhận số buổi dạy chuẩn xác, đảm bảo quyền lợi tài chính công bằng cho cả hai bên.",
+ color: "text-emerald-600 bg-emerald-500/10 border-emerald-200",
+ },
+ {
+ id: "notify",
+ icon: Bell,
+ title: "Nhắc lịch & Thông báo tự động",
+ desc: "Gửi thông báo nhắc nhở trước giờ học qua hệ thống giúp bạn không bao giờ bỏ lỡ buổi học.",
+ color: "text-amber-600 bg-amber-500/10 border-amber-200",
+ },
+ {
+ id: "data",
+ icon: Lock,
+ title: "Lưu trữ dữ liệu an toàn",
+ desc: "Mọi thông tin cá nhân và liên lạc đều được mã hóa bảo mật, chống rò rỉ dữ liệu.",
+ color: "text-teal-600 bg-teal-500/10 border-teal-200",
+ },
+ {
+ id: "support",
+ icon: Headset,
+ title: "Cố vấn hỗ trợ xuyên suốt",
+ desc: "Đội ngũ BeeWise luôn sẵn sàng đồng hành, giải đáp thắc mắc và hỗ trợ trong mọi tình huống.",
+ color: "text-rose-600 bg-rose-500/10 border-rose-200",
},
- { id: "notify", icon: BellIcon, label: "Nhắc lịch và thông báo tự động" },
- { id: "data", icon: LockIcon, label: "Lưu trữ dữ liệu an toàn" },
];
export function TutorGuideTrust() {
return (
-
-
- {/* Content left */}
-
-
-
- An tâm khi đồng hành{" "}
- cùng BeeWise
-
-
- BeeWise xây dựng quy trình quản lý minh bạch để bảo vệ quyền lợi
- của cả gia sư và học viên.
-
-
+ {/* Ambient background glows */}
+
+
- {/* 2-col tag grid — not a bullet list */}
-
- {TRUST_ITEMS.map((item) => {
- const Icon = item.icon;
- return (
+
+ {/* Section Header */}
+
+
+ Minh bạch & An tâm
+
+
+
+ An tâm khi đồng hành{" "}
+ cùng BeeWise
+
+
+
+ BeeWise xây dựng quy trình quản lý minh bạch để bảo vệ tối đa quyền
+ lợi và tạo dựng niềm tin vững chắc cho cả gia sư và học viên.
+
+
+
+ {/* 6 Cards Grid (3 cols on desktop, 2 cols on tablet, 1 col on mobile) */}
+
+ {TRUST_FEATURES.map((item) => {
+ const Icon = item.icon;
+ return (
+
+
-
-
-
-
- {item.label}
-
+
+
+
+
+ {item.title}
+
+
+ {item.desc}
+
- );
- })}
-
+
+
+ );
+ })}
+
+
+ {/* Bottom cute banner with sticker illustration */}
+
+
+
+ Bạn cần hỗ trợ thêm thông tin hoặc tư vấn lớp học?
+
+
+ Đội ngũ cố vấn BeeWise luôn sẵn sàng giải đáp 24/7 mọi thắc mắc
+ của bạn.
+
- {/* Right: Honey potion sticker */}
-
diff --git a/apps/sale/public/images/Banner/AIBanner.png b/apps/sale/public/images/Banner/AIBanner.png
new file mode 100644
index 0000000..5232cbd
Binary files /dev/null and b/apps/sale/public/images/Banner/AIBanner.png differ
diff --git a/packages/core/configs/fonts.ts b/packages/core/configs/fonts.ts
index d55a91d..bf72571 100644
--- a/packages/core/configs/fonts.ts
+++ b/packages/core/configs/fonts.ts
@@ -13,6 +13,8 @@ export const googleSans = localFont({
style: "italic",
},
],
+ display: "swap",
+ preload: false,
variable: "--font-google-sans",
});
@@ -29,5 +31,7 @@ export const nunito = localFont({
style: "italic",
},
],
+ display: "swap",
+ preload: false,
variable: "--font-nunito-family",
});
diff --git a/packages/core/configs/navbar.ts b/packages/core/configs/navbar.ts
index 76985d6..90e9dfd 100644
--- a/packages/core/configs/navbar.ts
+++ b/packages/core/configs/navbar.ts
@@ -103,7 +103,6 @@ export function resolveNavbarState({
const guestMenu: NavbarItem[] = [
{ label: "Gia sư", href: "/tutors" },
- { label: "Cách hoạt động", href: "/#how-it-works" },
{ label: "Trở thành gia sư", href: "/tutor-guide" },
{ label: "Về chúng tôi", href: "/about-us" },
];
diff --git a/packages/ui/components/StrokeText.tsx b/packages/ui/components/StrokeText.tsx
new file mode 100644
index 0000000..a3d497a
--- /dev/null
+++ b/packages/ui/components/StrokeText.tsx
@@ -0,0 +1,339 @@
+"use client";
+
+import {
+ CSSProperties,
+ useEffect,
+ useId,
+ useLayoutEffect,
+ useMemo,
+ useRef,
+ useState,
+} from "react";
+import { gsap } from "gsap";
+import { ScrollTrigger } from "gsap/ScrollTrigger";
+
+if (typeof window !== "undefined") {
+ gsap.registerPlugin(ScrollTrigger);
+}
+
+export type StrokeTextTrigger = "mount" | "hover" | "scroll" | "loop";
+export type StrokeTextFillMode = "wipe" | "fade" | "none";
+
+export interface StrokeTextProps {
+ text?: string;
+ strokeColor?: string;
+ fillColor?: string;
+ strokeWidth?: number;
+ drawDuration?: number;
+ fillDelay?: number;
+ stagger?: number;
+ ease?: string;
+ trigger?: StrokeTextTrigger;
+ fillMode?: StrokeTextFillMode;
+ fontSize?: number;
+ fontWeight?: number | string;
+ letterSpacing?: number;
+ reverse?: boolean;
+ className?: string;
+ style?: CSSProperties;
+}
+
+interface StrokeTextBox {
+ x: number;
+ y: number;
+ width: number;
+ height: number;
+}
+
+const DEFAULT_TEXT = "Beewise 2026";
+
+const StrokeText = ({
+ text = DEFAULT_TEXT,
+ strokeColor = "#A78BFA",
+ fillColor = "#F8FAFC",
+ strokeWidth = 1.4,
+ drawDuration = 1.6,
+ fillDelay = 0.2,
+ stagger = 0.05,
+ ease = "power2.out",
+ trigger = "mount",
+ fillMode = "wipe",
+ fontSize = 128,
+ fontWeight = 800,
+ letterSpacing = -4,
+ reverse = false,
+ className = "",
+ style = {},
+}: StrokeTextProps) => {
+ const rootRef = useRef
(null);
+ const strokeTextRef = useRef(null);
+ const wipeRectRef = useRef(null);
+
+ const [box, setBox] = useState(null);
+
+ const rawId = useId();
+ const wipeId = `stroke-text-wipe-${rawId.replace(/[^a-zA-Z0-9_-]/g, "")}`;
+
+ const characters = useMemo(() => Array.from(String(text ?? "")), [text]);
+
+ const dash = Math.max(fontSize * 7, 200);
+
+ const fontStyle = useMemo(
+ () => ({
+ fontSize: `${fontSize}px`,
+ fontWeight,
+ letterSpacing: `${letterSpacing}px`,
+ }),
+ [fontSize, fontWeight, letterSpacing],
+ );
+
+ useLayoutEffect(() => {
+ const node = strokeTextRef.current;
+ if (!node) return undefined;
+
+ let cancelled = false;
+
+ const measure = () => {
+ if (cancelled || !strokeTextRef.current) return;
+ let bbox: DOMRect | undefined;
+ try {
+ bbox = strokeTextRef.current.getBBox();
+ } catch {
+ return;
+ }
+ if (!bbox || !bbox.width) return;
+
+ const pad = Math.max(Number(strokeWidth) || 1, fontSize * 0.1);
+ const next = {
+ x: bbox.x - pad,
+ y: bbox.y - pad,
+ width: bbox.width + pad * 2,
+ height: bbox.height + pad * 2,
+ };
+
+ setBox((prev) =>
+ prev &&
+ Math.abs(prev.x - next.x) < 0.5 &&
+ Math.abs(prev.width - next.width) < 0.5 &&
+ Math.abs(prev.y - next.y) < 0.5
+ ? prev
+ : next,
+ );
+ };
+
+ measure();
+ if (typeof document !== "undefined" && document.fonts?.ready) {
+ document.fonts.ready.then(measure).catch(() => {});
+ }
+
+ return () => {
+ cancelled = true;
+ };
+ }, [characters, fontSize, fontWeight, letterSpacing, strokeWidth]);
+
+ useEffect(() => {
+ const root = rootRef.current;
+ if (typeof window === "undefined" || !root || !box) return undefined;
+
+ const strokes = gsap.utils.toArray(
+ root.querySelectorAll("[data-stroke-char]"),
+ );
+ const fills = gsap.utils.toArray(root.querySelectorAll("[data-fill-char]"));
+ const wipe = wipeRectRef.current;
+ if (!strokes.length) return undefined;
+
+ const fillEnabled = fillMode !== "none";
+ const useWipe = fillEnabled && fillMode === "wipe";
+ const fillDuration = Math.max(0.4, drawDuration * 0.5);
+ const staggerConfig: number | gsap.StaggerVars = reverse
+ ? { each: stagger, from: "end" as const }
+ : stagger;
+ const targets = [...strokes, ...fills, wipe].filter(Boolean);
+
+ const setStart = () => {
+ gsap.killTweensOf(targets);
+ gsap.set(strokes, { strokeDasharray: dash, strokeDashoffset: dash });
+ gsap.set(fills, { opacity: useWipe ? 1 : 0 });
+ if (wipe) gsap.set(wipe, { attr: { width: 0 } });
+ };
+
+ const setEnd = () => {
+ gsap.killTweensOf(targets);
+ gsap.set(strokes, { strokeDasharray: dash, strokeDashoffset: 0 });
+ gsap.set(fills, { opacity: fillEnabled ? 1 : 0 });
+ if (wipe)
+ gsap.set(wipe, { attr: { width: fillEnabled ? box.width : 0 } });
+ };
+
+ const prefersReducedMotion = window.matchMedia?.(
+ "(prefers-reduced-motion: reduce)",
+ ).matches;
+ if (prefersReducedMotion) {
+ setEnd();
+ return () => gsap.killTweensOf(targets);
+ }
+
+ const build = () => {
+ setStart();
+ const tl = gsap.timeline({
+ paused: true,
+ repeat: trigger === "loop" ? -1 : 0,
+ repeatDelay: trigger === "loop" ? 0.9 : 0,
+ defaults: { overwrite: "auto" },
+ });
+
+ tl.to(
+ strokes,
+ {
+ strokeDashoffset: 0,
+ duration: drawDuration,
+ ease,
+ stagger: staggerConfig,
+ },
+ 0,
+ );
+
+ if (useWipe && wipe) {
+ tl.to(
+ wipe,
+ {
+ attr: { width: box.width },
+ duration: fillDuration,
+ ease: "power2.inOut",
+ },
+ drawDuration + fillDelay,
+ );
+ } else if (fillEnabled) {
+ tl.to(
+ fills,
+ {
+ opacity: 1,
+ duration: fillDuration,
+ ease: "power2.out",
+ stagger: staggerConfig,
+ },
+ drawDuration + fillDelay,
+ );
+ }
+
+ return tl;
+ };
+
+ let timeline: gsap.core.Timeline | null = null;
+ let scrollTrigger: ReturnType | null = null;
+ let removeHover: (() => void) | null = null;
+
+ if (trigger === "hover") {
+ setEnd();
+ const play = () => {
+ timeline?.kill();
+ timeline = build();
+ timeline.play(0);
+ };
+ root.addEventListener("pointerenter", play);
+ removeHover = () => root.removeEventListener("pointerenter", play);
+ } else {
+ timeline = build();
+ if (trigger === "scroll") {
+ scrollTrigger = ScrollTrigger.create({
+ trigger: root,
+ start: "top 82%",
+ once: true,
+ onEnter: () => timeline?.play(0),
+ });
+ } else {
+ timeline.play(0);
+ }
+ }
+
+ return () => {
+ removeHover?.();
+ scrollTrigger?.kill();
+ timeline?.kill();
+ gsap.killTweensOf(targets);
+ };
+ }, [
+ box,
+ dash,
+ drawDuration,
+ fillDelay,
+ stagger,
+ ease,
+ trigger,
+ fillMode,
+ reverse,
+ ]);
+
+ const viewBox = box
+ ? `${box.x} ${box.y} ${box.width} ${box.height}`
+ : `0 ${-fontSize} 600 ${fontSize * 1.3}`;
+
+ return (
+
+
+ {fillMode === "wipe" && box && (
+
+
+
+
+
+ )}
+
+
+ {characters.map((char, index) => (
+
+ {char}
+
+ ))}
+
+
+
+ {characters.map((char, index) => (
+
+ {char}
+
+ ))}
+
+
+
+ );
+};
+
+export default StrokeText;