forked from UKSOURCE/ipv6
feat: add partners carousel, update sponsor section, add Nasalization font
This commit is contained in:
@@ -5,6 +5,7 @@ import HeroSection from "@/app/components/home/HeroSection";
|
||||
import VisionSection from "@/app/components/home/VisionSection";
|
||||
import StatsSection from "@/app/components/home/StatsSection";
|
||||
import AgendaSection from "@/app/components/home/AgendaSection";
|
||||
import PartnersCarousel from "@/app/components/home/PartnersCarousel";
|
||||
|
||||
export default function HomePageClient() {
|
||||
const { home } = useTranslation();
|
||||
@@ -15,6 +16,7 @@ export default function HomePageClient() {
|
||||
<VisionSection data={home.vision} />
|
||||
<StatsSection data={home.stats} />
|
||||
<AgendaSection data={home.agenda} />
|
||||
<PartnersCarousel />
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
// Tên file chính xác trong public/assets/img/carousel/
|
||||
const ROW_1 = [
|
||||
{ name: "NVIDIA", file: "Nvidia_logo.svg.png" },
|
||||
{ name: "Maxis", file: "Maxis-logo.png" },
|
||||
{ name: "Singtel", file: "Singtel_logo.svg.png" },
|
||||
{ name: "NIDA", file: "nida.png" },
|
||||
{ name: "VNNIC", file: "vnnic.png" },
|
||||
{ name: "Netflix", file: "Logonetflix.png" },
|
||||
{ name: "Microsoft",file: "Microsoft-Logo.png" },
|
||||
{ name: "Alibaba", file: "Alibaba-Logo.png" },
|
||||
];
|
||||
|
||||
const ROW_2 = [
|
||||
{ name: "Tencent", file: "tencent.png" },
|
||||
{ name: "AirTrunk", file: "airtrunk.png" },
|
||||
{ name: "Celcomdigi",file: "celcomdigi.png" },
|
||||
{ name: "Grab", file: "grab.png" },
|
||||
{ name: "Logo 1", file: "logo_transparent.png" },
|
||||
{ name: "Logo 2", file: "Equinix_logo.svg.png" },
|
||||
{ name: "Logo 3", file: "XL_Axiata-Logo.wine.png" },
|
||||
{ name: "Logo 4", file: "Huawei_Standard_logo.svg.png" },
|
||||
];
|
||||
|
||||
function CarouselRow({
|
||||
items,
|
||||
base,
|
||||
reverse = false,
|
||||
}: {
|
||||
items: { name: string; file: string }[];
|
||||
base: string;
|
||||
reverse?: boolean;
|
||||
}) {
|
||||
// Lặp 3 lần để đảm bảo không bao giờ thấy khoảng trống
|
||||
const tripled = [...items, ...items, ...items];
|
||||
// Mỗi item: w=150px, gap=20px → 1 set = items.length * 170 - 20
|
||||
const setWidth = items.length * 150 + (items.length - 1) * 20;
|
||||
|
||||
return (
|
||||
<div className="overflow-hidden w-full">
|
||||
<div
|
||||
className="flex gap-5 w-max"
|
||||
style={{
|
||||
animation: `${reverse ? "scroll-reverse" : "scroll-fwd"} ${items.length * 3}s linear infinite`,
|
||||
["--set-width" as string]: `${setWidth}px`,
|
||||
}}
|
||||
>
|
||||
{tripled.map((item, i) => (
|
||||
<div
|
||||
key={`${item.file}-${i}`}
|
||||
className="flex items-center justify-center shrink-0 w-[150px] h-[76px] rounded-xl border border-white/10 hover:border-primary/40 transition-colors duration-300 overflow-hidden"
|
||||
style={{ background: "rgba(255,255,255,0.82)" }}
|
||||
>
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
<img
|
||||
src={`${base}/assets/img/carousel/${item.file}`}
|
||||
alt={item.name}
|
||||
style={{
|
||||
maxHeight: 48,
|
||||
maxWidth: 118,
|
||||
width: "auto",
|
||||
height: "auto",
|
||||
objectFit: "contain",
|
||||
mixBlendMode: "multiply",
|
||||
filter: "contrast(1.05)",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function PartnersCarousel() {
|
||||
const [base, setBase] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
const match = window.location.pathname.match(/^(\/ipv6)/);
|
||||
setBase(match ? match[1] : "");
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<section className="w-full py-16 lg:py-24 overflow-hidden border-t border-white/5">
|
||||
<div className="mb-10 px-[var(--spacing-gutter)] max-w-hd mx-auto">
|
||||
<p className="text-primary font-[var(--font-label-caps)] text-xs tracking-[0.3em] uppercase mb-3">
|
||||
Partners & Sponsors
|
||||
</p>
|
||||
<h2 className="text-on-surface font-[var(--font-display-lg)] text-2xl lg:text-4xl font-bold">
|
||||
Trusted by Industry Leaders
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-5">
|
||||
<CarouselRow items={ROW_1} base={base} reverse={false} />
|
||||
<CarouselRow items={ROW_2} base={base} reverse={true} />
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -19,28 +19,28 @@ export default function SponsorContact({ data }: { data: SponsorContactData }) {
|
||||
|
||||
<div className="relative z-10 grid grid-cols-1 lg:grid-cols-2 gap-12 lg:gap-24 items-center">
|
||||
{/* Left Content */}
|
||||
<div className="space-y-12">
|
||||
<div className="space-y-8">
|
||||
<div>
|
||||
<h2 className="font-[var(--font-display-lg)] text-[clamp(2rem,4vw,4rem)] text-on-surface mb-6 lg:mb-8 leading-[1.1]">
|
||||
<h2 className="font-[var(--font-display-lg)] text-[clamp(1.75rem,3.5vw,3rem)] text-on-surface mb-4 leading-[1.1]">
|
||||
{data.title}
|
||||
</h2>
|
||||
<p className="font-body-base text-base lg:text-[20px] text-on-surface-variant max-w-xl leading-relaxed opacity-80">
|
||||
<p className="font-body-base text-sm lg:text-[17px] text-on-surface-variant max-w-xl leading-relaxed opacity-80">
|
||||
{data.description}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-8 lg:gap-12">
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-5">
|
||||
{/* Phone */}
|
||||
<div className="group">
|
||||
<div className="flex items-center gap-6">
|
||||
<div className="w-16 h-16 border border-primary/20 bg-primary/5 rounded-2xl flex items-center justify-center group-hover:border-primary/50 group-hover:bg-primary/10 transition-all duration-300">
|
||||
<span className="material-symbols-outlined text-primary text-3xl">call</span>
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="w-13 h-13 shrink-0 border border-primary/20 bg-primary/5 rounded-xl flex items-center justify-center group-hover:border-primary/50 group-hover:bg-primary/10 transition-all duration-300">
|
||||
<span className="material-symbols-outlined text-primary text-2xl">call</span>
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-[var(--font-label-caps)] text-[10px] lg:text-xs font-bold tracking-[0.25em] uppercase text-primary mb-2 opacity-60">
|
||||
<div className="min-w-0">
|
||||
<p className="font-[var(--font-label-caps)] text-[10px] font-bold tracking-[0.2em] uppercase text-primary mb-1 opacity-60">
|
||||
{data.phone.label}
|
||||
</p>
|
||||
<p className="font-[var(--font-display-lg)] text-lg lg:text-2xl text-on-surface">
|
||||
<p className="font-[var(--font-display-lg)] text-base text-on-surface whitespace-nowrap">
|
||||
{data.phone.value}
|
||||
</p>
|
||||
</div>
|
||||
@@ -49,15 +49,15 @@ export default function SponsorContact({ data }: { data: SponsorContactData }) {
|
||||
|
||||
{/* Email */}
|
||||
<div className="group">
|
||||
<div className="flex items-center gap-6">
|
||||
<div className="w-16 h-16 border border-primary/20 bg-primary/5 rounded-2xl flex items-center justify-center group-hover:border-primary/50 group-hover:bg-primary/10 transition-all duration-300">
|
||||
<span className="material-symbols-outlined text-primary text-3xl">mail</span>
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="w-13 h-13 shrink-0 border border-primary/20 bg-primary/5 rounded-xl flex items-center justify-center group-hover:border-primary/50 group-hover:bg-primary/10 transition-all duration-300">
|
||||
<span className="material-symbols-outlined text-primary text-2xl">mail</span>
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-[var(--font-label-caps)] text-[10px] lg:text-xs font-bold tracking-[0.25em] uppercase text-primary mb-2 opacity-60">
|
||||
<div className="min-w-0">
|
||||
<p className="font-[var(--font-label-caps)] text-[10px] font-bold tracking-[0.2em] uppercase text-primary mb-1 opacity-60">
|
||||
{data.email.label}
|
||||
</p>
|
||||
<p className="font-[var(--font-display-lg)] text-lg lg:text-2xl text-on-surface break-all">
|
||||
<p className="font-[var(--font-display-lg)] text-base text-on-surface truncate">
|
||||
{data.email.value}
|
||||
</p>
|
||||
</div>
|
||||
@@ -67,7 +67,7 @@ export default function SponsorContact({ data }: { data: SponsorContactData }) {
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className="gold-gradient-bg text-on-primary w-full sm:w-auto px-12 lg:px-16 py-5 lg:py-6 rounded-2xl font-bold uppercase tracking-widest text-sm lg:text-base shadow-2xl shadow-primary/20 hover:shadow-primary/40 transition-all duration-500 active:scale-95 flex items-center justify-center gap-4 group"
|
||||
className="gold-gradient-bg text-on-primary w-full sm:w-auto px-8 lg:px-10 py-3.5 rounded-xl font-bold uppercase tracking-widest text-sm shadow-2xl shadow-primary/20 hover:shadow-primary/40 transition-all duration-500 active:scale-95 flex items-center justify-center gap-3 group"
|
||||
>
|
||||
{data.cta}
|
||||
<span className="material-symbols-outlined group-hover:translate-x-2 transition-transform">
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { useLanguage } from "@/app/context/LanguageContext";
|
||||
|
||||
type SponsorHeroData = {
|
||||
eyebrow: string;
|
||||
@@ -9,7 +12,15 @@ type SponsorHeroData = {
|
||||
secondaryCta: { label: string; href: string };
|
||||
};
|
||||
|
||||
const PROSPECTUS_LINKS: Record<string, string> = {
|
||||
en: "https://drive.google.com/file/d/12uNVGFQ5ixuL2UwJpb6d-x-iuYjAt7Pc/view",
|
||||
vi: "https://drive.google.com/file/d/1YDpYwdWJHbxKa66u0Iq5PQ4aEaWgvkfI/view",
|
||||
};
|
||||
|
||||
export default function SponsorHero({ data }: { data: SponsorHeroData }) {
|
||||
const { lang } = useLanguage();
|
||||
const prospectusHref = PROSPECTUS_LINKS[lang] ?? PROSPECTUS_LINKS.en;
|
||||
|
||||
return (
|
||||
<section className="relative py-16 md:py-32 overflow-hidden text-center px-[var(--spacing-gutter)]">
|
||||
<div className="relative z-10 max-w-4xl mx-auto w-full">
|
||||
@@ -36,7 +47,9 @@ export default function SponsorHero({ data }: { data: SponsorHeroData }) {
|
||||
{/* CTAs */}
|
||||
<div className="flex flex-col sm:flex-row items-center justify-center gap-4 md:gap-6">
|
||||
<Link
|
||||
href={data.primaryCta.href}
|
||||
href={prospectusHref}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="gold-gradient-bg text-on-primary px-10 py-4 rounded-xl font-bold uppercase tracking-widest text-xs md:text-sm shadow-xl shadow-primary/20 hover:shadow-primary/30 transition-all duration-300 active:scale-95 w-full sm:w-auto"
|
||||
>
|
||||
{data.primaryCta.label}
|
||||
|
||||
@@ -2,6 +2,7 @@ type ItemCard = {
|
||||
icon: string;
|
||||
title: string;
|
||||
status: string;
|
||||
benefits?: string[];
|
||||
};
|
||||
|
||||
type Highlight = {
|
||||
@@ -24,6 +25,11 @@ export default function SponsorItemBased({ data }: { data: SponsorItemBasedData
|
||||
<div className="max-w-hd mx-auto">
|
||||
{/* Header */}
|
||||
<div className="text-center mb-16 lg:mb-28">
|
||||
{data.eyebrow && (
|
||||
<p className="text-primary font-[var(--font-label-caps)] text-xs tracking-[0.3em] uppercase mb-4 opacity-80">
|
||||
{data.eyebrow}
|
||||
</p>
|
||||
)}
|
||||
<h2 className="font-[var(--font-display-lg)] text-[clamp(1.75rem,3.5vw,3.5rem)] mb-4 md:mb-6 text-on-surface leading-tight">
|
||||
{data.title}
|
||||
</h2>
|
||||
@@ -33,21 +39,30 @@ export default function SponsorItemBased({ data }: { data: SponsorItemBasedData
|
||||
</div>
|
||||
|
||||
{/* 4-col cards */}
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6 lg:gap-10 mb-16 lg:mb-24">
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6 lg:gap-8 mb-16 lg:mb-24">
|
||||
{data.items.map((item) => (
|
||||
<div
|
||||
key={item.title}
|
||||
className="glass-panel p-8 lg:p-12 rounded-3xl text-center group hover:bg-surface-container-high transition-all duration-500 transform hover:-translate-y-2 border-primary/10"
|
||||
className="glass-panel p-8 rounded-3xl group hover:bg-surface-container-high transition-all duration-500 transform hover:-translate-y-2 border-primary/10 flex flex-col items-center text-center"
|
||||
>
|
||||
<div className="w-16 h-16 gold-gradient-bg rounded-2xl flex items-center justify-center mx-auto mb-8 rotate-3 group-hover:rotate-0 transition-transform duration-500 shadow-xl shadow-primary/20">
|
||||
<span className="material-symbols-outlined text-on-primary text-3xl lg:text-4xl">
|
||||
<div className="w-14 h-14 gold-gradient-bg rounded-2xl flex items-center justify-center mb-6 rotate-3 group-hover:rotate-0 transition-transform duration-500 shadow-xl shadow-primary/20">
|
||||
<span className="material-symbols-outlined text-on-primary text-2xl">
|
||||
{item.icon}
|
||||
</span>
|
||||
</div>
|
||||
<h4 className="font-[var(--font-display-lg)] text-xl lg:text-2xl mb-3 text-on-surface">
|
||||
<h4 className="font-[var(--font-display-lg)] text-lg lg:text-xl mb-2 text-on-surface leading-tight">
|
||||
{item.title}
|
||||
</h4>
|
||||
<p className="text-[11px] font-bold tracking-[0.3em] uppercase text-primary">
|
||||
{item.benefits && item.benefits.length > 0 && (
|
||||
<ul className="mt-3 mb-4 space-y-1.5 flex-1">
|
||||
{item.benefits.map((b) => (
|
||||
<li key={b} className="text-sm lg:text-base text-on-surface-variant/80">
|
||||
{b}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
<p className="text-xs font-bold tracking-[0.3em] uppercase text-primary mt-auto">
|
||||
{item.status}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user