ALIEZ 1 bulan lalu
induk
melakukan
26100014df
8 mengubah file dengan 154 tambahan dan 39 penghapusan
  1. TEMPAT SAMPAH
      public/banner1.png
  2. TEMPAT SAMPAH
      public/banner2.png
  3. TEMPAT SAMPAH
      public/banner3.png
  4. TEMPAT SAMPAH
      public/banner4.png
  5. TEMPAT SAMPAH
      public/banner5.png
  6. 19 39
      src/app/[locale]/page.tsx
  7. 46 0
      src/components/home-hero-background-carousel.tsx
  8. 89 0
      src/components/home-hero-carousel.tsx

TEMPAT SAMPAH
public/banner1.png


TEMPAT SAMPAH
public/banner2.png


TEMPAT SAMPAH
public/banner3.png


TEMPAT SAMPAH
public/banner4.png


TEMPAT SAMPAH
public/banner5.png


+ 19 - 39
src/app/[locale]/page.tsx

@@ -3,6 +3,7 @@ import { Link } from "@/i18n/navigation";
 import { courses, type CourseCategory } from "@/data/courses";
 import { SectionHeading } from "@/components/section-heading";
 import { IconBook, IconChart, IconShield } from "@/components/icons";
+import { HomeHeroCarousel } from "@/components/home-hero-carousel";
 
 type Props = { params: Promise<{ locale: string }> };
 
@@ -48,73 +49,52 @@ export default async function HomePage({ params }: Props) {
     },
   ];
 
+  const heroBanners = ["/banner1.png", "/banner2.png", "/banner3.png", "/banner4.png", "/banner5.png"];
+
   return (
     <div>
-      {/* Hero — Edulan 式:分栏 + 装饰卡片 */}
-      <section className="ui-enter relative overflow-hidden border-b border-slate-200/80 bg-gradient-to-br from-slate-900 via-[#0c1929] to-slate-900 text-white">
-        <div className="hero-grid pointer-events-none absolute inset-0 opacity-60" />
-        <div className="pointer-events-none absolute -right-20 top-1/2 h-[420px] w-[420px] -translate-y-1/2 rounded-full bg-blue-500/20 blur-3xl" />
-        <div className="pointer-events-none absolute -left-32 bottom-0 h-72 w-72 rounded-full bg-amber-500/15 blur-3xl" />
+      {/* Hero:左文案 + 右轮播 + 统一背景 */}
+      <section className="ui-enter relative overflow-hidden border-b border-slate-200/80 bg-gradient-to-br from-slate-900 via-[#0f1b2d] to-slate-900 text-white">
+        <div className="hero-grid pointer-events-none absolute inset-0 z-0 opacity-40" />
+        <div className="pointer-events-none absolute -right-16 top-16 z-0 h-72 w-72 rounded-full bg-blue-500/18 blur-3xl" />
+        <div className="pointer-events-none absolute -left-20 bottom-8 z-0 h-64 w-64 rounded-full bg-amber-500/12 blur-3xl" />
 
-        <div className="site-container relative grid gap-10 py-14 md:grid-cols-2 md:items-center md:py-24 lg:gap-16">
-          <div>
+        <div className="site-container relative z-10 grid gap-10 py-16 md:grid-cols-2 md:items-center md:py-20 lg:gap-14">
+          <div className="max-w-xl md:pr-4">
             <p className="text-sm font-semibold tracking-wide text-amber-300/95">
               {t("heroEyebrow")}
             </p>
-            <h1 className="font-serif mt-5 text-4xl font-bold leading-[1.15] tracking-tight md:text-5xl lg:text-[3.25rem]">
+            <h1 className="font-serif mt-4 text-4xl font-bold leading-[1.12] tracking-tight text-white md:text-5xl lg:text-[3.15rem]">
               {t("heroTitle")}
             </h1>
-            <p className="mt-6 max-w-lg text-lg leading-relaxed text-slate-300">
+            <p className="mt-5 max-w-lg text-base leading-relaxed text-slate-200 md:text-lg">
               {t("heroSubtitle")}
             </p>
-            <div className="mt-10 flex flex-wrap gap-4">
+            <div className="mt-8 flex flex-wrap items-center gap-4">
               <Link
                 href="/courses"
-                className="ui-interactive-btn inline-flex items-center justify-center rounded-full bg-gradient-to-r from-amber-400 to-amber-500 px-8 py-3.5 text-sm font-bold text-[var(--navy)] shadow-xl shadow-amber-500/20 transition hover:from-amber-300 hover:to-amber-400"
+                className="ui-interactive-btn inline-flex min-w-[130px] items-center justify-center rounded-full bg-gradient-to-r from-amber-400 to-amber-500 px-7 py-3 text-sm font-bold text-[var(--navy)] shadow-xl shadow-amber-500/20 transition hover:from-amber-300 hover:to-amber-400"
               >
                 {t("ctaCourses")}
               </Link>
               <Link
                 href="/about"
-                className="ui-interactive-btn inline-flex items-center justify-center rounded-full border-2 border-white/25 bg-white/5 px-8 py-3.5 text-sm font-semibold text-white backdrop-blur-sm transition hover:border-white/40 hover:bg-white/10"
+                className="ui-interactive-btn inline-flex min-w-[130px] items-center justify-center rounded-full border border-white/35 bg-white/8 px-7 py-3 text-sm font-semibold text-white transition hover:border-white/55 hover:bg-white/12"
               >
                 {t("ctaAbout")}
               </Link>
             </div>
           </div>
 
-          <div className="relative hidden md:block">
-            <div className="relative z-10 rounded-3xl border border-white/10 bg-white/5 p-6 shadow-2xl shadow-black/30 backdrop-blur-md">
-              <div className="flex items-center gap-3 border-b border-white/10 pb-4">
-                <div className="h-3 w-3 rounded-full bg-red-400/90" />
-                <div className="h-3 w-3 rounded-full bg-amber-400/90" />
-                <div className="h-3 w-3 rounded-full bg-emerald-400/90" />
-                <span className="ml-2 text-xs text-slate-400">Learning Hub</span>
-              </div>
-              <div className="mt-5 space-y-3">
-                {[1, 2, 3].map((i) => (
-                  <div
-                    key={i}
-                    className="flex items-center gap-3 rounded-xl bg-white/5 p-3 ring-1 ring-white/10"
-                  >
-                    <div className="h-10 w-10 shrink-0 rounded-lg bg-gradient-to-br from-blue-500 to-indigo-600 opacity-90" />
-                    <div className="min-w-0 flex-1">
-                      <div className="h-2.5 w-3/4 max-w-[180px] rounded bg-white/20" />
-                      <div className="mt-2 h-2 w-1/2 max-w-[100px] rounded bg-white/10" />
-                    </div>
-                  </div>
-                ))}
-              </div>
-              <p className="mt-5 text-center text-xs text-slate-500">
-                课程进度 · 会员权益 · 问答解锁(示意)
-              </p>
+          <div className="relative w-full md:justify-self-end md:pl-4">
+            <div className="w-full md:max-w-[720px]">
+            <HomeHeroCarousel images={heroBanners} />
             </div>
-            <div className="absolute -bottom-4 -right-4 -z-0 h-40 w-40 rounded-3xl bg-gradient-to-br from-blue-600/40 to-indigo-800/40 blur-2xl" />
           </div>
         </div>
 
         {/* 数据条 */}
-        <div className="border-t border-white/10 bg-black/20">
+        <div className="relative z-10 border-t border-white/10 bg-black/20">
           <div className="site-container grid grid-cols-2 gap-6 py-8 md:grid-cols-4">
             {stats.map((s) => (
               <div key={s.label} className="text-center md:text-left">

+ 46 - 0
src/components/home-hero-background-carousel.tsx

@@ -0,0 +1,46 @@
+"use client";
+
+import { useEffect, useState } from "react";
+
+type HomeHeroBackgroundCarouselProps = {
+  images: string[];
+  intervalMs?: number;
+};
+
+export function HomeHeroBackgroundCarousel({
+  images,
+  intervalMs = 5000,
+}: HomeHeroBackgroundCarouselProps) {
+  const safeImages = images.filter(Boolean);
+  const [activeIndex, setActiveIndex] = useState(0);
+
+  useEffect(() => {
+    if (safeImages.length <= 1) return;
+    const timer = window.setInterval(() => {
+      setActiveIndex((prev) => (prev + 1) % safeImages.length);
+    }, intervalMs);
+    return () => window.clearInterval(timer);
+  }, [intervalMs, safeImages.length]);
+
+  if (safeImages.length === 0) return null;
+
+  return (
+    <div className="absolute inset-0 -z-20">
+      {safeImages.map((src, idx) => (
+        // eslint-disable-next-line @next/next/no-img-element
+        <img
+          key={src}
+          src={src}
+          alt=""
+          aria-hidden="true"
+          className={`absolute inset-0 h-full w-full object-cover transition-opacity duration-700 ${
+            idx === activeIndex ? "opacity-100" : "opacity-0"
+          }`}
+        />
+      ))}
+      {/* 只在左侧做可读性增强,不整屏压暗 Banner */}
+      <div className="absolute inset-y-0 left-0 w-[48%] bg-gradient-to-r from-slate-950/48 via-slate-950/18 to-transparent" />
+      <div className="absolute inset-x-0 bottom-0 h-24 bg-gradient-to-t from-slate-950/30 to-transparent" />
+    </div>
+  );
+}

+ 89 - 0
src/components/home-hero-carousel.tsx

@@ -0,0 +1,89 @@
+"use client";
+
+import { useEffect, useState } from "react";
+
+type HomeHeroCarouselProps = {
+  images: string[];
+  intervalMs?: number;
+};
+
+export function HomeHeroCarousel({ images, intervalMs = 4500 }: HomeHeroCarouselProps) {
+  const safeImages = images.filter(Boolean);
+  const [activeIndex, setActiveIndex] = useState(0);
+
+  useEffect(() => {
+    if (safeImages.length <= 1) return;
+    const timer = window.setInterval(() => {
+      setActiveIndex((prev) => (prev + 1) % safeImages.length);
+    }, intervalMs);
+    return () => window.clearInterval(timer);
+  }, [intervalMs, safeImages.length]);
+
+  useEffect(() => {
+    if (safeImages.length === 0) return;
+    if (activeIndex > safeImages.length - 1) setActiveIndex(0);
+  }, [activeIndex, safeImages.length]);
+
+  if (safeImages.length === 0) return null;
+
+  const goPrev = () => {
+    setActiveIndex((prev) => (prev - 1 + safeImages.length) % safeImages.length);
+  };
+
+  const goNext = () => {
+    setActiveIndex((prev) => (prev + 1) % safeImages.length);
+  };
+
+  return (
+    <div className="relative z-10 overflow-hidden rounded-3xl border border-white/10 bg-white/5 shadow-2xl shadow-black/30 backdrop-blur-md">
+      <div className="relative aspect-[16/9] w-full">
+        {safeImages.map((src, idx) => (
+          // eslint-disable-next-line @next/next/no-img-element
+          <img
+            key={src}
+            src={src}
+            alt={`Banner ${idx + 1}`}
+            className={`absolute inset-0 h-full w-full object-cover transition-opacity duration-500 ${
+              idx === activeIndex ? "opacity-100" : "opacity-0"
+            }`}
+          />
+        ))}
+      </div>
+
+      {safeImages.length > 1 ? (
+        <>
+          <button
+            type="button"
+            onClick={goPrev}
+            aria-label="Previous banner"
+            className="absolute left-3 top-1/2 -translate-y-1/2 rounded-full border border-white/20 bg-black/35 px-2.5 py-1 text-sm text-white transition hover:bg-black/55"
+          >
+            ‹
+          </button>
+          <button
+            type="button"
+            onClick={goNext}
+            aria-label="Next banner"
+            className="absolute right-3 top-1/2 -translate-y-1/2 rounded-full border border-white/20 bg-black/35 px-2.5 py-1 text-sm text-white transition hover:bg-black/55"
+          >
+            ›
+          </button>
+
+          <div className="absolute bottom-3 left-1/2 flex -translate-x-1/2 items-center gap-2">
+            {safeImages.map((src, idx) => (
+              <button
+                key={`${src}-dot`}
+                type="button"
+                aria-label={`Go to banner ${idx + 1}`}
+                onClick={() => setActiveIndex(idx)}
+                className={`h-2.5 w-2.5 rounded-full transition ${
+                  idx === activeIndex ? "bg-white" : "bg-white/40 hover:bg-white/60"
+                }`}
+              />
+            ))}
+          </div>
+        </>
+      ) : null}
+    </div>
+  );
+}