first commit
This commit is contained in:
83
src/components/AICapabilitiesSection.tsx
Normal file
83
src/components/AICapabilitiesSection.tsx
Normal file
@@ -0,0 +1,83 @@
|
||||
import { getTranslations } from "next-intl/server";
|
||||
|
||||
export default async function AICapabilitiesSection() {
|
||||
const t = await getTranslations("aiCap");
|
||||
|
||||
const aiCards = [
|
||||
{
|
||||
num: t("card1Num"),
|
||||
title: t("card1Title"),
|
||||
desc: t("card1Desc"),
|
||||
metrics: [
|
||||
{ label: t("card1Metric1Label"), value: t("card1Metric1Value") },
|
||||
{ label: t("card1Metric2Label"), value: t("card1Metric2Value") },
|
||||
],
|
||||
},
|
||||
{
|
||||
num: t("card2Num"),
|
||||
title: t("card2Title"),
|
||||
desc: t("card2Desc"),
|
||||
metrics: [
|
||||
{ label: t("card2Metric1Label"), value: t("card2Metric1Value") },
|
||||
{ label: t("card2Metric2Label"), value: t("card2Metric2Value") },
|
||||
],
|
||||
},
|
||||
{
|
||||
num: t("card3Num"),
|
||||
title: t("card3Title"),
|
||||
desc: t("card3Desc"),
|
||||
metrics: [
|
||||
{ label: t("card3Metric1Label"), value: t("card3Metric1Value") },
|
||||
{ label: t("card3Metric2Label"), value: t("card3Metric2Value") },
|
||||
],
|
||||
},
|
||||
{
|
||||
num: t("card4Num"),
|
||||
title: t("card4Title"),
|
||||
desc: t("card4Desc"),
|
||||
metrics: [
|
||||
{ label: t("card4Metric1Label"), value: t("card4Metric1Value") },
|
||||
{ label: t("card4Metric2Label"), value: t("card4Metric2Value") },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<section className="section section--light">
|
||||
<div className="cap-header">
|
||||
<div className="cap-header-left">
|
||||
<div className="cap-divider">
|
||||
<span className="cap-divider-line" />
|
||||
<span className="cap-divider-label">{t("label")}</span>
|
||||
</div>
|
||||
<h2 className="cap-title">{t("title")}</h2>
|
||||
<p className="cap-subtitle">{t("subtitle")}</p>
|
||||
</div>
|
||||
<div className="cap-header-right">
|
||||
<span className="cap-big-number">{t("moduleCount")}</span>
|
||||
<span className="cap-metric-label">{t("moduleLabel")}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="cap-grid">
|
||||
{aiCards.map((card) => (
|
||||
<div key={card.num} className="cap-card cap-card--light">
|
||||
<div className="cap-card-header">
|
||||
<span className="cap-card-num">{card.num}</span>
|
||||
<h3 className="cap-card-title">{card.title}</h3>
|
||||
</div>
|
||||
<p className="cap-card-desc">{card.desc}</p>
|
||||
<div className="cap-card-metrics">
|
||||
{card.metrics.map((m) => (
|
||||
<div key={m.label} className="cap-card-metric">
|
||||
<span className="cap-card-metric-label">{m.label}</span>
|
||||
<span className="cap-card-metric-value">{m.value}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
60
src/components/AboutHeroSection.tsx
Normal file
60
src/components/AboutHeroSection.tsx
Normal file
@@ -0,0 +1,60 @@
|
||||
import { getTranslations } from "next-intl/server";
|
||||
|
||||
export default async function AboutHeroSection() {
|
||||
const t = await getTranslations("aboutHero");
|
||||
|
||||
const capabilities = [
|
||||
{ icon: t("cap1Icon"), label: t("cap1Label") },
|
||||
{ icon: t("cap2Icon"), label: t("cap2Label") },
|
||||
{ icon: t("cap3Icon"), label: t("cap3Label") },
|
||||
{ icon: t("cap4Icon"), label: t("cap4Label") },
|
||||
];
|
||||
|
||||
const strategies = [
|
||||
{ tag: t("strat1Tag"), title: t("strat1Title"), desc: t("strat1Desc") },
|
||||
{ tag: t("strat2Tag"), title: t("strat2Title"), desc: t("strat2Desc") },
|
||||
];
|
||||
|
||||
return (
|
||||
<section className="about-hero-section">
|
||||
<div className="about-hero-content">
|
||||
<span className="about-hero-label">{t("label")}</span>
|
||||
<h1 className="about-hero-title">{t("title")}</h1>
|
||||
<p className="about-hero-subtitle">{t("subtitle")}</p>
|
||||
<div className="about-hero-vision">
|
||||
<span className="about-hero-vision-label">{t("visionLabel")}</span>
|
||||
<span className="about-hero-vision-text">{t("visionText")}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="about-sub-block">
|
||||
<div className="about-divider-label">
|
||||
<span className="about-divider-label-text">{t("capLabel")}</span>
|
||||
</div>
|
||||
<div className="about-cap-grid">
|
||||
{capabilities.map((cap) => (
|
||||
<div key={cap.icon} className="about-cap-card">
|
||||
<span className="about-cap-icon">{cap.icon}</span>
|
||||
<span className="about-cap-text">{cap.label}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="about-sub-block">
|
||||
<div className="about-divider-label">
|
||||
<span className="about-divider-label-text">{t("stratLabel")}</span>
|
||||
</div>
|
||||
<div className="about-strat-grid">
|
||||
{strategies.map((s) => (
|
||||
<div key={s.tag} className="about-strat-card">
|
||||
<span className="about-strat-tag">{s.tag}</span>
|
||||
<h3 className="about-strat-title">{s.title}</h3>
|
||||
<span className="about-strat-desc">{s.desc}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
50
src/components/ArchitectureSection.tsx
Normal file
50
src/components/ArchitectureSection.tsx
Normal file
@@ -0,0 +1,50 @@
|
||||
import { getTranslations } from "next-intl/server";
|
||||
|
||||
export default async function ArchitectureSection() {
|
||||
const t = await getTranslations("architecture");
|
||||
|
||||
return (
|
||||
<section className="section section--dark">
|
||||
<div className="section-header">
|
||||
<div className="decorated-tag">
|
||||
<span className="decorated-tag-line decorated-tag-line--dark" />
|
||||
<span className="decorated-tag-text">{t("tag")}</span>
|
||||
<span className="decorated-tag-line decorated-tag-line--dark" />
|
||||
</div>
|
||||
<h2 className="heading-section">{t("title")}</h2>
|
||||
</div>
|
||||
|
||||
<div className="arch-diagram">
|
||||
<div className="arch-top">
|
||||
<span className="arch-top-label">{t("topLabel")}</span>
|
||||
<span className="arch-top-desc">{t("topDesc")}</span>
|
||||
</div>
|
||||
|
||||
<div className="arch-connector">
|
||||
<div className="arch-vert-line" />
|
||||
<span className="arch-connector-icon">▼</span>
|
||||
</div>
|
||||
|
||||
<div className="arch-engines">
|
||||
<div className="arch-engine arch-engine--light">
|
||||
<span className="arch-engine-label">{t("engine1Label")}</span>
|
||||
<h3 className="arch-engine-title arch-engine-title--dark">{t("engine1Title")}</h3>
|
||||
<span className="arch-engine-desc">{t("engine1Desc")}</span>
|
||||
</div>
|
||||
|
||||
<div className="arch-middle">
|
||||
<span className="arch-arrow">◄</span>
|
||||
<span className="arch-middle-label">{t("middleLabel")}</span>
|
||||
<span className="arch-arrow">►</span>
|
||||
</div>
|
||||
|
||||
<div className="arch-engine arch-engine--dark">
|
||||
<span className="arch-engine-label arch-engine-label--dim">{t("engine2Label")}</span>
|
||||
<h3 className="arch-engine-title">{t("engine2Title")}</h3>
|
||||
<span className="arch-engine-desc arch-engine-desc--muted">{t("engine2Desc")}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
22
src/components/CTASection.tsx
Normal file
22
src/components/CTASection.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { Link } from "@/i18n/navigation";
|
||||
import { ArrowRight } from "lucide-react";
|
||||
|
||||
export default async function CTASection() {
|
||||
const t = await getTranslations("cta");
|
||||
|
||||
return (
|
||||
<section className="section section--light cta">
|
||||
<h2 className="heading-cta heading-cta--light">{t("title")}</h2>
|
||||
<div className="cta-buttons">
|
||||
<Link href="/solutions" className="btn btn--dark">
|
||||
<span>{t("services")}</span>
|
||||
<ArrowRight size={16} />
|
||||
</Link>
|
||||
<Link href="/contact" className="btn btn--outline-dark">
|
||||
{t("contact")}
|
||||
</Link>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
68
src/components/FlywheelSection.tsx
Normal file
68
src/components/FlywheelSection.tsx
Normal file
@@ -0,0 +1,68 @@
|
||||
import { getTranslations } from "next-intl/server";
|
||||
|
||||
export default async function FlywheelSection() {
|
||||
const t = await getTranslations("flywheel");
|
||||
|
||||
const cards = [
|
||||
{
|
||||
tag: t("card1Tag"),
|
||||
title: t("card1Title"),
|
||||
subtitle: t("card1Subtitle"),
|
||||
desc: t("card1Desc"),
|
||||
features: [t("card1Feature1"), t("card1Feature2")],
|
||||
},
|
||||
{
|
||||
tag: t("card2Tag"),
|
||||
title: t("card2Title"),
|
||||
subtitle: t("card2Subtitle"),
|
||||
desc: t("card2Desc"),
|
||||
features: [t("card2Feature1"), t("card2Feature2"), t("card2Feature3")],
|
||||
},
|
||||
{
|
||||
tag: t("card3Tag"),
|
||||
title: t("card3Title"),
|
||||
subtitle: t("card3Subtitle"),
|
||||
desc: t("card3Desc"),
|
||||
features: [t("card3Feature1"), t("card3Feature2"), t("card3Feature3")],
|
||||
},
|
||||
];
|
||||
|
||||
const summarySteps = [t("summary1"), t("summary2"), t("summary3")];
|
||||
|
||||
return (
|
||||
<section className="section section--dark">
|
||||
<div className="section-header">
|
||||
<span className="label-text">{t("label")}</span>
|
||||
<h2 className="heading-section">{t("title")}</h2>
|
||||
<p className="body-text">{t("subtitle")}</p>
|
||||
</div>
|
||||
|
||||
<div className="flywheel-cards">
|
||||
{cards.map((card) => (
|
||||
<div key={card.title} className="flywheel-card">
|
||||
<span className="label-text">{card.tag}</span>
|
||||
<h3 className="flywheel-card-title">{card.title}</h3>
|
||||
<span className="body-text">{card.subtitle}</span>
|
||||
<p className="body-text body-text--muted">{card.desc}</p>
|
||||
<ul className="flywheel-features">
|
||||
{card.features.map((f) => (
|
||||
<li key={f} className="body-text body-text--sm body-text--muted">
|
||||
• {f}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="flywheel-summary">
|
||||
{summarySteps.map((step, i) => (
|
||||
<span key={step} className="flywheel-summary-item">
|
||||
{i > 0 && <span className="flywheel-arrow">→</span>}
|
||||
<span className="body-text body-text--muted">{step}</span>
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
117
src/components/Footer.tsx
Normal file
117
src/components/Footer.tsx
Normal file
@@ -0,0 +1,117 @@
|
||||
import { Link } from "@/i18n/navigation";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
function WeChatIcon({ size = 16 }: { size?: number }) {
|
||||
return (
|
||||
<svg width={size} height={size} viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M8.691 2.188C3.891 2.188 0 5.476 0 9.53c0 2.212 1.17 4.203 3.002 5.55a.59.59 0 0 1 .213.665l-.39 1.48c-.019.07-.048.141-.048.213 0 .163.13.295.29.295a.326.326 0 0 0 .167-.054l1.903-1.114a.864.864 0 0 1 .717-.098 10.16 10.16 0 0 0 2.837.403c.276 0 .543-.027.811-.05a6.329 6.329 0 0 1-.235-1.69c0-3.542 3.276-6.426 7.315-6.426.348 0 .688.029 1.023.074C16.088 4.68 12.727 2.189 8.691 2.189zM5.785 5.991a1.09 1.09 0 0 1 1.083 1.09 1.09 1.09 0 0 1-1.083 1.09A1.09 1.09 0 0 1 4.7 7.08a1.09 1.09 0 0 1 1.085-1.09zm5.88 0a1.09 1.09 0 0 1 1.083 1.09 1.09 1.09 0 0 1-1.083 1.09 1.09 1.09 0 0 1-1.085-1.09 1.09 1.09 0 0 1 1.085-1.09zm2.927 3.525c-3.508 0-6.36 2.51-6.36 5.596 0 3.088 2.852 5.596 6.36 5.596a7.5 7.5 0 0 0 2.36-.382.636.636 0 0 1 .527.074l1.402.822a.244.244 0 0 0 .122.04.214.214 0 0 0 .213-.217c0-.053-.02-.105-.035-.156l-.286-1.09a.432.432 0 0 1 .156-.488c1.352-.998 2.22-2.465 2.22-4.1 0-3.087-2.853-5.596-6.36-5.596h-.319zm-1.834 2.89a.905.905 0 0 1 .9.907.905.905 0 0 1-.9.906.905.905 0 0 1-.902-.906.905.905 0 0 1 .902-.906zm3.99 0a.905.905 0 0 1 .9.907.905.905 0 0 1-.9.906.905.905 0 0 1-.902-.906.905.905 0 0 1 .901-.906z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
function LinkedInIcon({ size = 16 }: { size?: number }) {
|
||||
return (
|
||||
<svg width={size} height={size} viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433a2.062 2.062 0 0 1-2.063-2.065 2.064 2.064 0 1 1 2.063 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
function XIcon({ size = 16 }: { size?: number }) {
|
||||
return (
|
||||
<svg width={size} height={size} viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M18.901 1.153h3.68l-8.04 9.19L24 22.846h-7.406l-5.8-7.584-6.638 7.584H.474l8.6-9.83L0 1.154h7.594l5.243 6.932ZM17.61 20.644h2.039L6.486 3.24H4.298Z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
function MailIcon({ size = 16 }: { size?: number }) {
|
||||
return (
|
||||
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round">
|
||||
<rect x="2" y="4" width="20" height="16" rx="2" />
|
||||
<path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
const socialIcons = [
|
||||
{ icon: WeChatIcon, label: "WeChat" },
|
||||
{ icon: LinkedInIcon, label: "LinkedIn" },
|
||||
{ icon: XIcon, label: "X" },
|
||||
{ icon: MailIcon, label: "Email" },
|
||||
];
|
||||
|
||||
export default async function Footer() {
|
||||
const t = await getTranslations("footer");
|
||||
const th = await getTranslations("header");
|
||||
|
||||
const pageLinks = [
|
||||
{ label: th("home"), href: "/" as const },
|
||||
{ label: th("tech"), href: "/tech" as const },
|
||||
{ label: th("solutions"), href: "/solutions" as const },
|
||||
{ label: th("about"), href: "/about" as const },
|
||||
];
|
||||
|
||||
const businessLinks = [
|
||||
{ label: t("bizDeSpace"), href: "#" as const },
|
||||
{ label: t("bizRWA"), href: "#" as const },
|
||||
{ label: t("bizQuant"), href: "#" as const },
|
||||
];
|
||||
|
||||
return (
|
||||
<footer className="footer">
|
||||
<div className="footer-main">
|
||||
<div className="footer-brand">
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
<img
|
||||
src="/logo.png"
|
||||
alt="DESUN SINGULARITY"
|
||||
width={160}
|
||||
height={46}
|
||||
className="footer-logo-img"
|
||||
/>
|
||||
<p className="footer-tagline">{t("tagline")}</p>
|
||||
<div className="footer-contact">
|
||||
<span className="footer-contact-label">{t("contactLabel")}</span>
|
||||
<div className="footer-social">
|
||||
{socialIcons.map((item) => (
|
||||
<a
|
||||
key={item.label}
|
||||
href="#"
|
||||
className="footer-social-icon"
|
||||
aria-label={item.label}
|
||||
>
|
||||
<item.icon size={16} />
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="footer-links">
|
||||
<div className="footer-col">
|
||||
<span className="footer-col-title">{t("pagesTitle")}</span>
|
||||
{pageLinks.map((link) => (
|
||||
<Link key={link.href} href={link.href} className="footer-link">
|
||||
{link.label}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
<div className="footer-col">
|
||||
<span className="footer-col-title">{t("businessTitle")}</span>
|
||||
{businessLinks.map((link) => (
|
||||
<a key={link.label} href={link.href} className="footer-link">
|
||||
{link.label}
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="footer-divider" />
|
||||
|
||||
<div className="footer-bottom">
|
||||
<span className="footer-copyright">{t("copyright")}</span>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
91
src/components/Header.tsx
Normal file
91
src/components/Header.tsx
Normal file
@@ -0,0 +1,91 @@
|
||||
"use client";
|
||||
|
||||
import Image from "next/image";
|
||||
import { Link, usePathname } from "@/i18n/navigation";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useState } from "react";
|
||||
import { Menu, X } from "lucide-react";
|
||||
import LanguageSwitcher from "./LanguageSwitcher";
|
||||
|
||||
export default function Header() {
|
||||
const pathname = usePathname();
|
||||
const t = useTranslations("header");
|
||||
const [mobileOpen, setMobileOpen] = useState(false);
|
||||
|
||||
const navItems = [
|
||||
{ label: t("home"), href: "/" as const },
|
||||
{ label: t("tech"), href: "/tech" as const },
|
||||
{ label: t("solutions"), href: "/solutions" as const },
|
||||
{ label: t("about"), href: "/about" as const },
|
||||
];
|
||||
|
||||
return (
|
||||
<header className="header">
|
||||
<Link href="/" className="logo">
|
||||
<Image
|
||||
src="/logo.png"
|
||||
alt="DESUN SINGULARITY"
|
||||
width={140}
|
||||
height={40}
|
||||
priority
|
||||
/>
|
||||
</Link>
|
||||
|
||||
<nav className="nav">
|
||||
{navItems.map((item) => (
|
||||
<Link
|
||||
key={item.href}
|
||||
href={item.href}
|
||||
className={`nav-item ${pathname === item.href ? "nav-item-active" : ""}`}
|
||||
>
|
||||
{item.label}
|
||||
</Link>
|
||||
))}
|
||||
</nav>
|
||||
|
||||
<div className="header-right">
|
||||
<LanguageSwitcher />
|
||||
<Link href="/contact" className="contact-btn">
|
||||
{t("contact")}
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{/* Mobile hamburger */}
|
||||
<button
|
||||
className="mobile-menu-btn"
|
||||
onClick={() => setMobileOpen(!mobileOpen)}
|
||||
aria-label="Toggle menu"
|
||||
>
|
||||
{mobileOpen ? <X size={22} /> : <Menu size={22} />}
|
||||
</button>
|
||||
|
||||
{/* Mobile overlay */}
|
||||
{mobileOpen && (
|
||||
<div className="mobile-overlay" onClick={() => setMobileOpen(false)}>
|
||||
<nav className="mobile-nav" onClick={(e) => e.stopPropagation()}>
|
||||
{navItems.map((item) => (
|
||||
<Link
|
||||
key={item.href}
|
||||
href={item.href}
|
||||
className={`mobile-nav-item ${pathname === item.href ? "mobile-nav-item--active" : ""}`}
|
||||
onClick={() => setMobileOpen(false)}
|
||||
>
|
||||
{item.label}
|
||||
</Link>
|
||||
))}
|
||||
<div className="mobile-nav-footer">
|
||||
<LanguageSwitcher />
|
||||
<Link
|
||||
href="/contact"
|
||||
className="contact-btn"
|
||||
onClick={() => setMobileOpen(false)}
|
||||
>
|
||||
{t("contact")}
|
||||
</Link>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
)}
|
||||
</header>
|
||||
);
|
||||
}
|
||||
31
src/components/HeroSection.tsx
Normal file
31
src/components/HeroSection.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import { getTranslations } from "next-intl/server";
|
||||
|
||||
export default async function HeroSection() {
|
||||
const t = await getTranslations("hero");
|
||||
|
||||
const metrics = [
|
||||
{ label: t("metric1Label"), value: t("metric1Value"), desc: t("metric1Desc") },
|
||||
{ label: t("metric2Label"), value: t("metric2Value"), desc: t("metric2Desc") },
|
||||
{ label: t("metric3Label"), value: t("metric3Value"), desc: t("metric3Desc") },
|
||||
{ label: t("metric4Label"), value: t("metric4Value"), desc: t("metric4Desc") },
|
||||
];
|
||||
|
||||
return (
|
||||
<section className="section section--light hero">
|
||||
<div className="hero-content">
|
||||
<span className="label-text">{t("label")}</span>
|
||||
<h1 className="heading-display">{t("title")}</h1>
|
||||
<p className="body-text body-text--lg">{t("subtitle")}</p>
|
||||
</div>
|
||||
<div className="metric-grid">
|
||||
{metrics.map((m) => (
|
||||
<div key={m.label} className="metric-card">
|
||||
<span className="label-text">{m.label}</span>
|
||||
<span className="metric-value">{m.value}</span>
|
||||
<span className="body-text body-text--sm">{m.desc}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
57
src/components/LanguageSwitcher.tsx
Normal file
57
src/components/LanguageSwitcher.tsx
Normal file
@@ -0,0 +1,57 @@
|
||||
"use client";
|
||||
|
||||
import { usePathname, useRouter } from "@/i18n/navigation";
|
||||
import { useLocale } from "next-intl";
|
||||
import { locales, localeNames, type Locale } from "@/i18n/config";
|
||||
import {
|
||||
Dropdown,
|
||||
DropdownTrigger,
|
||||
DropdownMenu,
|
||||
DropdownItem,
|
||||
Button,
|
||||
} from "@heroui/react";
|
||||
import type { Selection } from "@heroui/react";
|
||||
import { ChevronDown } from "lucide-react";
|
||||
|
||||
export default function LanguageSwitcher() {
|
||||
const locale = useLocale() as Locale;
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
|
||||
function handleSelectionChange(keys: Selection) {
|
||||
const next = Array.from(keys)[0] as Locale;
|
||||
if (next && next !== locale) {
|
||||
document.cookie = `NEXT_LOCALE=${next};path=/;max-age=31536000`;
|
||||
router.replace(pathname, { locale: next });
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Dropdown>
|
||||
<DropdownTrigger>
|
||||
<Button
|
||||
variant="bordered"
|
||||
size="sm"
|
||||
className="lang-switcher-trigger"
|
||||
endContent={<ChevronDown size={12} />}
|
||||
>
|
||||
{localeNames[locale]}
|
||||
</Button>
|
||||
</DropdownTrigger>
|
||||
<DropdownMenu
|
||||
aria-label="Language"
|
||||
selectionMode="single"
|
||||
selectedKeys={new Set([locale])}
|
||||
disallowEmptySelection
|
||||
onSelectionChange={handleSelectionChange}
|
||||
className="lang-switcher-menu"
|
||||
>
|
||||
{locales.map((l) => (
|
||||
<DropdownItem key={l} className="lang-switcher-item">
|
||||
{localeNames[l]}
|
||||
</DropdownItem>
|
||||
))}
|
||||
</DropdownMenu>
|
||||
</Dropdown>
|
||||
);
|
||||
}
|
||||
30
src/components/MarketSection.tsx
Normal file
30
src/components/MarketSection.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import { getTranslations } from "next-intl/server";
|
||||
|
||||
export default async function MarketSection() {
|
||||
const t = await getTranslations("market");
|
||||
|
||||
const marketData = [
|
||||
{ label: t("metric1Label"), value: t("metric1Value"), desc: t("metric1Desc") },
|
||||
{ label: t("metric2Label"), value: t("metric2Value"), desc: t("metric2Desc") },
|
||||
{ label: t("metric3Label"), value: t("metric3Value"), desc: t("metric3Desc") },
|
||||
];
|
||||
|
||||
return (
|
||||
<section className="section section--light">
|
||||
<div className="section-header">
|
||||
<span className="label-text">{t("label")}</span>
|
||||
<h2 className="heading-section heading-section--dark">{t("title")}</h2>
|
||||
<p className="body-text">{t("subtitle")}</p>
|
||||
</div>
|
||||
<div className="metric-grid metric-grid--3">
|
||||
{marketData.map((m) => (
|
||||
<div key={m.label} className="metric-card metric-card--tall">
|
||||
<span className="label-text">{m.label}</span>
|
||||
<span className="metric-value">{m.value}</span>
|
||||
<span className="body-text body-text--sm">{m.desc}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
49
src/components/MilestonesSection.tsx
Normal file
49
src/components/MilestonesSection.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
import { getTranslations } from "next-intl/server";
|
||||
|
||||
export default async function MilestonesSection() {
|
||||
const t = await getTranslations("milestones");
|
||||
|
||||
const milestones = [
|
||||
{ date: t("ms1Date"), title: t("ms1Title"), desc: t("ms1Desc") },
|
||||
{ date: t("ms2Date"), title: t("ms2Title"), desc: t("ms2Desc") },
|
||||
{ date: t("ms3Date"), title: t("ms3Title"), desc: t("ms3Desc") },
|
||||
{ date: t("ms4Date"), title: t("ms4Title"), desc: t("ms4Desc") },
|
||||
];
|
||||
|
||||
return (
|
||||
<section className="about-section about-section--dark">
|
||||
<div className="about-sub-block">
|
||||
<div className="about-divider-label about-divider-label--dark">
|
||||
<div className="about-divider-label-row">
|
||||
<span className="about-divider-label-text about-divider-label-text--muted">
|
||||
{t("label")}
|
||||
</span>
|
||||
<span className="about-mono-tag">{t("tag")}</span>
|
||||
</div>
|
||||
</div>
|
||||
<h2 className="about-section-title about-section-title--light">
|
||||
{t("title")}
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div className="timeline">
|
||||
{milestones.map((ms) => (
|
||||
<div key={ms.title} className="timeline-item">
|
||||
<span className="timeline-date">{ms.date}</span>
|
||||
<div className="timeline-content">
|
||||
<span className="timeline-title">{ms.title}</span>
|
||||
<span className="timeline-desc">{ms.desc}</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
<div className="timeline-item timeline-item--last">
|
||||
<span className="timeline-date timeline-date--dim">{t("continueDate")}</span>
|
||||
<div className="timeline-content">
|
||||
<span className="timeline-title timeline-title--dim">{t("continueTitle")}</span>
|
||||
<span className="timeline-desc timeline-desc--dim">{t("continueDesc")}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
64
src/components/ParentCompanySection.tsx
Normal file
64
src/components/ParentCompanySection.tsx
Normal file
@@ -0,0 +1,64 @@
|
||||
import { getTranslations } from "next-intl/server";
|
||||
|
||||
export default async function ParentCompanySection() {
|
||||
const t = await getTranslations("parentCompany");
|
||||
|
||||
const metrics = [
|
||||
{ value: t("metric1Value"), label: t("metric1Label") },
|
||||
{ value: t("metric2Value"), label: t("metric2Label") },
|
||||
{ value: t("metric3Value"), label: t("metric3Label") },
|
||||
{ value: t("metric4Value"), label: t("metric4Label") },
|
||||
{ value: t("metric5Value"), label: t("metric5Label") },
|
||||
];
|
||||
|
||||
const bizTypes = [
|
||||
{ title: t("biz1Title"), stats: t("biz1Stats") },
|
||||
{ title: t("biz2Title"), stats: t("biz2Stats") },
|
||||
{ title: t("biz3Title"), stats: t("biz3Stats") },
|
||||
{ title: t("biz4Title"), stats: t("biz4Stats") },
|
||||
];
|
||||
|
||||
return (
|
||||
<section className="about-section about-section--dark">
|
||||
<div className="about-sub-block">
|
||||
<div className="about-divider-label about-divider-label--dark">
|
||||
<div className="about-divider-label-row">
|
||||
<span className="about-divider-label-text about-divider-label-text--muted">
|
||||
{t("bgLabel")}
|
||||
</span>
|
||||
<span className="about-mono-tag">{t("stockCode")}</span>
|
||||
</div>
|
||||
</div>
|
||||
<h2 className="about-section-title about-section-title--light">
|
||||
{t("title")}
|
||||
</h2>
|
||||
<p className="about-body-lg">{t("subtitle")}</p>
|
||||
</div>
|
||||
|
||||
<div className="parent-metric-grid">
|
||||
{metrics.map((m) => (
|
||||
<div key={m.label} className="parent-metric-card">
|
||||
<span className="parent-metric-value">{m.value}</span>
|
||||
<span className="parent-metric-label">{m.label}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="about-sub-block">
|
||||
<div className="about-divider-label about-divider-label--dark">
|
||||
<span className="about-divider-label-text about-divider-label-text--dim">
|
||||
{t("bizLabel")}
|
||||
</span>
|
||||
</div>
|
||||
<div className="parent-biz-grid">
|
||||
{bizTypes.map((b) => (
|
||||
<div key={b.title} className="parent-biz-card">
|
||||
<span className="parent-biz-title">{b.title}</span>
|
||||
<span className="parent-biz-stats">{b.stats}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
7
src/components/Providers.tsx
Normal file
7
src/components/Providers.tsx
Normal file
@@ -0,0 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { HeroUIProvider } from "@heroui/react";
|
||||
|
||||
export default function Providers({ children }: { children: React.ReactNode }) {
|
||||
return <HeroUIProvider>{children}</HeroUIProvider>;
|
||||
}
|
||||
46
src/components/RecruitSection.tsx
Normal file
46
src/components/RecruitSection.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
import { getTranslations } from "next-intl/server";
|
||||
|
||||
export default async function RecruitSection() {
|
||||
const t = await getTranslations("recruit");
|
||||
|
||||
const positions = [t("pos1"), t("pos2"), t("pos3"), t("pos4")];
|
||||
|
||||
return (
|
||||
<section className="about-section about-section--light">
|
||||
<div className="about-sub-block">
|
||||
<div className="about-divider-label">
|
||||
<div className="about-divider-label-row">
|
||||
<span className="about-divider-label-text">{t("label")}</span>
|
||||
<span className="about-mono-tag about-mono-tag--muted">{t("tag")}</span>
|
||||
</div>
|
||||
</div>
|
||||
<h2 className="recruit-title">{t("title")}</h2>
|
||||
</div>
|
||||
|
||||
<div className="recruit-content">
|
||||
<div className="recruit-left">
|
||||
<span className="recruit-col-label">{t("positionsLabel")}</span>
|
||||
<div className="recruit-positions">
|
||||
{positions.map((pos, i) => (
|
||||
<div
|
||||
key={pos}
|
||||
className={`recruit-pos${i < positions.length - 1 ? " recruit-pos--border" : ""}`}
|
||||
>
|
||||
<span className="recruit-pos-title">{pos}</span>
|
||||
<span className="recruit-pos-arrow">→</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="recruit-right">
|
||||
<span className="recruit-col-label">{t("newsLabel")}</span>
|
||||
<div className="recruit-news-placeholder">
|
||||
<span style={{ fontSize: 32 }}>📰</span>
|
||||
<span className="recruit-news-text">{t("newsPlaceholder")}</span>
|
||||
<span className="recruit-news-desc">{t("newsDesc")}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
121
src/components/ScenarioSection.tsx
Normal file
121
src/components/ScenarioSection.tsx
Normal file
@@ -0,0 +1,121 @@
|
||||
import { getTranslations } from "next-intl/server";
|
||||
|
||||
interface ScenarioData {
|
||||
variant: "dark" | "light";
|
||||
label: string;
|
||||
tag: string;
|
||||
titles: [string, string];
|
||||
ai: {
|
||||
desc: string;
|
||||
benefit: string;
|
||||
metrics?: { label: string; value: string }[];
|
||||
};
|
||||
web3: {
|
||||
desc: string;
|
||||
benefit: string;
|
||||
};
|
||||
steps: string[];
|
||||
}
|
||||
|
||||
export default async function ScenarioSection({ data }: { data: ScenarioData }) {
|
||||
const t = await getTranslations("scenario");
|
||||
const isDark = data.variant === "dark";
|
||||
|
||||
return (
|
||||
<section className={`section ${isDark ? "section--dark" : "section--light"}`}>
|
||||
{/* Header */}
|
||||
<div className="scenario-header">
|
||||
<div className={`scenario-divider-row ${isDark ? "scenario-divider-row--dark" : ""}`}>
|
||||
<span className={`scenario-label ${isDark ? "scenario-label--muted" : ""}`}>
|
||||
{data.label}
|
||||
</span>
|
||||
<span className="scenario-tag">{data.tag}</span>
|
||||
</div>
|
||||
<h2 className={`scenario-title ${isDark ? "scenario-title--light" : ""}`}>
|
||||
{data.titles[0]}
|
||||
</h2>
|
||||
<h2 className={`scenario-title ${isDark ? "scenario-title--light" : ""}`}>
|
||||
{data.titles[1]}
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="scenario-content">
|
||||
{/* Left: AI + Web3 blocks */}
|
||||
<div className="scenario-left">
|
||||
{/* AI Block */}
|
||||
<div className={`scenario-block ${isDark ? "scenario-block--dark" : ""}`}>
|
||||
<div className="scenario-block-header">
|
||||
<div className={`scenario-icon ${isDark ? "scenario-icon--light" : "scenario-icon--dark"}`}>
|
||||
<span className={`scenario-icon-text ${isDark ? "scenario-icon-text--dark" : "scenario-icon-text--light"}`}>
|
||||
AI
|
||||
</span>
|
||||
</div>
|
||||
<span className={`scenario-block-label ${isDark ? "scenario-block-label--light" : ""}`}>
|
||||
{t("aiLabel")}
|
||||
</span>
|
||||
</div>
|
||||
<p className="scenario-block-desc">{data.ai.desc}</p>
|
||||
{data.ai.metrics && (
|
||||
<div className="scenario-metrics">
|
||||
{data.ai.metrics.map((m) => (
|
||||
<div key={m.label} className="scenario-metric">
|
||||
<span className={`scenario-metric-value ${isDark ? "scenario-metric-value--light" : ""}`}>
|
||||
{m.value}
|
||||
</span>
|
||||
<span className="scenario-metric-label">{m.label}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{!data.ai.metrics && (
|
||||
<span className={`scenario-benefit ${isDark ? "scenario-benefit--light" : ""}`}>
|
||||
→ {data.ai.benefit}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Web3 Block */}
|
||||
<div className={`scenario-block ${isDark ? "scenario-block--dark" : ""}`}>
|
||||
<div className="scenario-block-header">
|
||||
<div className={`scenario-icon scenario-icon--outline ${isDark ? "scenario-icon--outline-light" : "scenario-icon--outline-dark"}`}>
|
||||
<span className={`scenario-icon-text ${isDark ? "scenario-icon-text--light" : ""}`}>
|
||||
W3
|
||||
</span>
|
||||
</div>
|
||||
<span className={`scenario-block-label ${isDark ? "scenario-block-label--light" : ""}`}>
|
||||
{t("web3Label")}
|
||||
</span>
|
||||
</div>
|
||||
<p className="scenario-block-desc">{data.web3.desc}</p>
|
||||
<span className={`scenario-benefit ${isDark ? "scenario-benefit--light" : ""}`}>
|
||||
→ {data.web3.benefit}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right: Process steps */}
|
||||
<div className="scenario-right">
|
||||
<span className={`scenario-process-label ${isDark ? "scenario-process-label--dim" : ""}`}>
|
||||
{t("processLabel")}
|
||||
</span>
|
||||
<div className="scenario-steps">
|
||||
{data.steps.map((step, i) => (
|
||||
<div
|
||||
key={step}
|
||||
className={`scenario-step ${i < data.steps.length - 1 ? (isDark ? "scenario-step--border-dark" : "scenario-step--border") : ""}`}
|
||||
>
|
||||
<span className={`scenario-step-num ${isDark ? "scenario-step-num--dim" : ""}`}>
|
||||
{String(i + 1).padStart(2, "0")}
|
||||
</span>
|
||||
<span className={`scenario-step-text ${isDark ? "scenario-step-text--light" : ""}`}>
|
||||
{step}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
18
src/components/SolutionsCTASection.tsx
Normal file
18
src/components/SolutionsCTASection.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { Link } from "@/i18n/navigation";
|
||||
|
||||
export default async function SolutionsCTASection() {
|
||||
const t = await getTranslations("solCta");
|
||||
|
||||
return (
|
||||
<section className="section section--light" style={{ gap: 32 }}>
|
||||
<div className="sol-cta-divider" />
|
||||
<h2 className="heading-section heading-section--dark">{t("title")}</h2>
|
||||
<p className="body-text body-text--lg sol-cta-desc">{t("desc")}</p>
|
||||
<Link href="/contact" className="btn btn--dark" style={{ gap: 8 }}>
|
||||
<span>{t("contact")}</span>
|
||||
<span>→</span>
|
||||
</Link>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
56
src/components/SolutionsHeroSection.tsx
Normal file
56
src/components/SolutionsHeroSection.tsx
Normal file
@@ -0,0 +1,56 @@
|
||||
import { getTranslations } from "next-intl/server";
|
||||
|
||||
export default async function SolutionsHeroSection() {
|
||||
const t = await getTranslations("solHero");
|
||||
|
||||
const flowCards = [
|
||||
{
|
||||
num: t("flow1Num"),
|
||||
title: t("flow1Title"),
|
||||
sub: t("flow1Sub"),
|
||||
ai: t("flow1AI"),
|
||||
web3: t("flow1Web3"),
|
||||
},
|
||||
{
|
||||
num: t("flow2Num"),
|
||||
title: t("flow2Title"),
|
||||
sub: t("flow2Sub"),
|
||||
ai: t("flow2AI"),
|
||||
web3: t("flow2Web3"),
|
||||
},
|
||||
{
|
||||
num: t("flow3Num"),
|
||||
title: t("flow3Title"),
|
||||
sub: t("flow3Sub"),
|
||||
ai: t("flow3AI"),
|
||||
web3: t("flow3Web3"),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<section className="section section--light">
|
||||
<div className="section-header" style={{ gap: 24 }}>
|
||||
<span className="label-text">{t("label")}</span>
|
||||
<h1 className="sol-hero-title">{t("title")}</h1>
|
||||
<h2 className="sol-hero-subtitle">{t("subtitle")}</h2>
|
||||
<p className="body-text body-text--lg sol-hero-desc">{t("desc")}</p>
|
||||
</div>
|
||||
|
||||
<div className="flow-diagram">
|
||||
{flowCards.map((card, i) => (
|
||||
<div key={card.num} className="flow-item">
|
||||
{i > 0 && <span className="flow-arrow">→</span>}
|
||||
<div className="flow-card">
|
||||
<span className="flow-card-num">{card.num}</span>
|
||||
<h3 className="flow-card-title">{card.title}</h3>
|
||||
<span className="flow-card-sub">{card.sub}</span>
|
||||
<div className="flow-card-divider" />
|
||||
<span className="flow-card-tag">{card.ai}</span>
|
||||
<span className="flow-card-tag">{card.web3}</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
55
src/components/TeamSection.tsx
Normal file
55
src/components/TeamSection.tsx
Normal file
@@ -0,0 +1,55 @@
|
||||
import { getTranslations } from "next-intl/server";
|
||||
|
||||
function MemberCard({ role, name, bio }: { role: string; name: string; bio: string }) {
|
||||
return (
|
||||
<div className="team-card">
|
||||
<span className="team-card-role">{role}</span>
|
||||
<h3 className="team-card-name">{name}</h3>
|
||||
<p className="team-card-bio">{bio}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default async function TeamSection() {
|
||||
const t = await getTranslations("team");
|
||||
|
||||
const teamRow1 = [
|
||||
{ role: t("ceo"), name: t("ceoName"), bio: t("ceoBio") },
|
||||
{ role: t("coo"), name: t("cooName"), bio: t("cooBio") },
|
||||
{ role: t("cmo"), name: t("cmoName"), bio: t("cmoBio") },
|
||||
{ role: t("cto"), name: t("ctoName"), bio: t("ctoBio") },
|
||||
];
|
||||
|
||||
const teamRow2 = [
|
||||
{ role: t("cio"), name: t("cioName"), bio: t("cioBio") },
|
||||
{ role: t("strategist"), name: t("strategistName"), bio: t("strategistBio") },
|
||||
{ role: t("analyst"), name: t("analystName"), bio: t("analystBio") },
|
||||
];
|
||||
|
||||
return (
|
||||
<section className="about-section about-section--light">
|
||||
<div className="about-sub-block">
|
||||
<div className="about-divider-label">
|
||||
<div className="about-divider-label-row">
|
||||
<span className="about-divider-label-text">{t("label")}</span>
|
||||
<span className="about-count-tag">{t("count")}</span>
|
||||
</div>
|
||||
</div>
|
||||
<h2 className="team-title">{t("title")}</h2>
|
||||
</div>
|
||||
|
||||
<div className="team-grid">
|
||||
<div className="team-row team-row--4">
|
||||
{teamRow1.map((m) => (
|
||||
<MemberCard key={m.name} {...m} />
|
||||
))}
|
||||
</div>
|
||||
<div className="team-row team-row--3">
|
||||
{teamRow2.map((m) => (
|
||||
<MemberCard key={m.name} {...m} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
20
src/components/TechCTASection.tsx
Normal file
20
src/components/TechCTASection.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { Link } from "@/i18n/navigation";
|
||||
|
||||
export default async function TechCTASection() {
|
||||
const t = await getTranslations("techCta");
|
||||
|
||||
return (
|
||||
<section className="section section--light cta">
|
||||
<h2 className="heading-section heading-section--dark">{t("title")}</h2>
|
||||
<div className="cta-buttons">
|
||||
<Link href="/solutions" className="btn btn--dark">
|
||||
{t("solutions")}
|
||||
</Link>
|
||||
<Link href="/contact" className="btn btn--outline-dark">
|
||||
{t("contact")}
|
||||
</Link>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
18
src/components/TechHeroSection.tsx
Normal file
18
src/components/TechHeroSection.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import { getTranslations } from "next-intl/server";
|
||||
|
||||
export default async function TechHeroSection() {
|
||||
const t = await getTranslations("techHero");
|
||||
|
||||
return (
|
||||
<section className="section section--light tech-hero">
|
||||
<div className="decorated-tag">
|
||||
<span className="decorated-tag-line" />
|
||||
<span className="decorated-tag-text">{t("tag")}</span>
|
||||
<span className="decorated-tag-line" />
|
||||
</div>
|
||||
<h1 className="heading-display">{t("title1")}</h1>
|
||||
<h1 className="heading-display">{t("title2")}</h1>
|
||||
<p className="body-text body-text--lg tech-hero-subtitle">{t("subtitle")}</p>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
44
src/components/Web3CapabilitiesSection.tsx
Normal file
44
src/components/Web3CapabilitiesSection.tsx
Normal file
@@ -0,0 +1,44 @@
|
||||
import { getTranslations } from "next-intl/server";
|
||||
|
||||
export default async function Web3CapabilitiesSection() {
|
||||
const t = await getTranslations("web3Cap");
|
||||
|
||||
const web3Cards = [
|
||||
{ num: t("card1Num"), title: t("card1Title"), desc: t("card1Desc"), value: t("card1Value") },
|
||||
{ num: t("card2Num"), title: t("card2Title"), desc: t("card2Desc"), value: t("card2Value") },
|
||||
{ num: t("card3Num"), title: t("card3Title"), desc: t("card3Desc"), value: t("card3Value") },
|
||||
{ num: t("card4Num"), title: t("card4Title"), desc: t("card4Desc"), value: t("card4Value") },
|
||||
];
|
||||
|
||||
return (
|
||||
<section className="section section--dark">
|
||||
<div className="cap-header">
|
||||
<div className="cap-header-left">
|
||||
<div className="cap-divider cap-divider--dark">
|
||||
<span className="cap-divider-line cap-divider-line--dark" />
|
||||
<span className="cap-divider-label">{t("label")}</span>
|
||||
</div>
|
||||
<h2 className="cap-title cap-title--light">{t("title")}</h2>
|
||||
<p className="cap-subtitle cap-subtitle--muted">{t("subtitle")}</p>
|
||||
</div>
|
||||
<div className="cap-header-right">
|
||||
<span className="cap-big-number cap-big-number--light">{t("moduleCount")}</span>
|
||||
<span className="cap-metric-label">{t("moduleLabel")}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="cap-grid cap-grid--dark">
|
||||
{web3Cards.map((card) => (
|
||||
<div key={card.num} className="cap-card cap-card--dark">
|
||||
<div className="cap-card-header">
|
||||
<span className="cap-card-num cap-card-num--dim">{card.num}</span>
|
||||
<h3 className="cap-card-title cap-card-title--light">{card.title}</h3>
|
||||
</div>
|
||||
<p className="cap-card-desc cap-card-desc--muted">{card.desc}</p>
|
||||
<span className="cap-card-value">{card.value}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user