更新优化
This commit is contained in:
BIN
Logo_Black.png
Normal file
BIN
Logo_Black.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 32 KiB |
BIN
Logo_White.png
Normal file
BIN
Logo_White.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 34 KiB |
4483
package-lock.json
generated
4483
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -10,6 +10,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@heroui/react": "^2.8.8",
|
||||
"@next/third-parties": "^16.1.6",
|
||||
"framer-motion": "^12.30.0",
|
||||
"lucide-react": "^0.563.0",
|
||||
"next": "16.1.6",
|
||||
|
||||
BIN
public/logo-footer.png
Normal file
BIN
public/logo-footer.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 34 KiB |
BIN
public/logo-header.png
Normal file
BIN
public/logo-header.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 32 KiB |
BIN
public/logo.png
BIN
public/logo.png
Binary file not shown.
|
Before Width: | Height: | Size: 53 KiB |
BIN
public/wechat-qr.png
Normal file
BIN
public/wechat-qr.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 72 KiB |
@@ -2,6 +2,7 @@ import type { Metadata } from "next";
|
||||
import { NextIntlClientProvider } from "next-intl";
|
||||
import { getTranslations, setRequestLocale } from "next-intl/server";
|
||||
import { notFound } from "next/navigation";
|
||||
import { GoogleAnalytics } from "@next/third-parties/google";
|
||||
import { routing } from "@/i18n/routing";
|
||||
import { Locale } from "@/i18n/config";
|
||||
import { getBaseUrl, ogImage } from "@/lib/seo";
|
||||
@@ -78,6 +79,7 @@ export default async function LocaleLayout({ children, params }: Props) {
|
||||
</Providers>
|
||||
</NextIntlClientProvider>
|
||||
</body>
|
||||
<GoogleAnalytics gaId="G-ZC1KY3GRV0" />
|
||||
</html>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -15,13 +15,18 @@ export async function generateMetadata({
|
||||
const { locale } = await params;
|
||||
const t = await getTranslations({ locale, namespace: "metadata" });
|
||||
|
||||
return createPageMetadata({
|
||||
const meta = createPageMetadata({
|
||||
locale: locale as Locale,
|
||||
pathname: "/",
|
||||
title: t("homeTitle"),
|
||||
title: t("title"),
|
||||
description: t("homeDescription"),
|
||||
siteName: t("title"),
|
||||
});
|
||||
|
||||
return {
|
||||
...meta,
|
||||
title: { absolute: `${t("title")} — ${t("homeTitle")}` },
|
||||
};
|
||||
}
|
||||
|
||||
export default async function Home({
|
||||
|
||||
@@ -6,6 +6,7 @@ import TechHeroSection from "@/components/TechHeroSection";
|
||||
import ArchitectureSection from "@/components/ArchitectureSection";
|
||||
import AICapabilitiesSection from "@/components/AICapabilitiesSection";
|
||||
import Web3CapabilitiesSection from "@/components/Web3CapabilitiesSection";
|
||||
import BlockchainSection from "@/components/BlockchainSection";
|
||||
import TechCTASection from "@/components/TechCTASection";
|
||||
|
||||
export async function generateMetadata({
|
||||
@@ -39,6 +40,7 @@ export default async function TechPage({
|
||||
<ArchitectureSection />
|
||||
<AICapabilitiesSection />
|
||||
<Web3CapabilitiesSection />
|
||||
<BlockchainSection />
|
||||
<TechCTASection />
|
||||
</main>
|
||||
);
|
||||
|
||||
99
src/app/api/contact/route.ts
Normal file
99
src/app/api/contact/route.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
|
||||
const LARK_WEBHOOK_URL =
|
||||
"https://open.larksuite.com/open-apis/bot/v2/hook/6ac55b0c-e0d9-4932-9491-c09edb7acebb";
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const body = await request.json();
|
||||
const { name, company, email, phone, message } = body;
|
||||
|
||||
if (!name?.trim() || !email?.trim() || !message?.trim()) {
|
||||
return NextResponse.json(
|
||||
{ error: "Name, email and message are required" },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
const card = {
|
||||
msg_type: "interactive",
|
||||
card: {
|
||||
header: {
|
||||
title: {
|
||||
tag: "plain_text",
|
||||
content: "📩 新客户咨询",
|
||||
},
|
||||
template: "blue",
|
||||
},
|
||||
elements: [
|
||||
{
|
||||
tag: "div",
|
||||
fields: [
|
||||
{
|
||||
is_short: true,
|
||||
text: { tag: "lark_md", content: `**姓名**\n${name}` },
|
||||
},
|
||||
{
|
||||
is_short: true,
|
||||
text: {
|
||||
tag: "lark_md",
|
||||
content: `**公司**\n${company || "—"}`,
|
||||
},
|
||||
},
|
||||
{
|
||||
is_short: true,
|
||||
text: { tag: "lark_md", content: `**邮箱**\n${email}` },
|
||||
},
|
||||
{
|
||||
is_short: true,
|
||||
text: {
|
||||
tag: "lark_md",
|
||||
content: `**电话**\n${phone || "—"}`,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
tag: "div",
|
||||
text: {
|
||||
tag: "lark_md",
|
||||
content: `**需求描述**\n${message || "—"}`,
|
||||
},
|
||||
},
|
||||
{
|
||||
tag: "hr",
|
||||
},
|
||||
{
|
||||
tag: "note",
|
||||
elements: [
|
||||
{
|
||||
tag: "plain_text",
|
||||
content: `来源: 官网联系表单 | ${new Date().toLocaleString("zh-CN", { timeZone: "Asia/Hong_Kong" })}`,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const larkRes = await fetch(LARK_WEBHOOK_URL, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(card),
|
||||
});
|
||||
|
||||
if (!larkRes.ok) {
|
||||
return NextResponse.json(
|
||||
{ error: "Failed to send message" },
|
||||
{ status: 502 }
|
||||
);
|
||||
}
|
||||
|
||||
return NextResponse.json({ success: true });
|
||||
} catch {
|
||||
return NextResponse.json(
|
||||
{ error: "Internal server error" },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -364,6 +364,122 @@ body {
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
/* ===== Blockchain / Smart Chain — Triangle Layout ===== */
|
||||
.chain-layout {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 64px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.chain-triangle {
|
||||
position: relative;
|
||||
flex-shrink: 0;
|
||||
width: 340px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 28px 0;
|
||||
}
|
||||
|
||||
.chain-triangle-svg {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.chain-triangle-label {
|
||||
position: absolute;
|
||||
font-family: var(--f-sans);
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--c-text-primary);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.chain-triangle-label--top {
|
||||
top: 24px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.chain-triangle-label--bl {
|
||||
bottom: 24px;
|
||||
left: 5%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.chain-triangle-label--br {
|
||||
bottom: 24px;
|
||||
left: 95%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.chain-layers {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.chain-layer-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
padding: 24px 28px;
|
||||
border: 1px solid var(--c-border);
|
||||
}
|
||||
|
||||
.chain-layer-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.chain-layer-num {
|
||||
font-family: var(--f-mono);
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
color: var(--c-text-muted);
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.chain-layer-name {
|
||||
font-family: var(--f-serif);
|
||||
font-size: 22px;
|
||||
font-weight: 600;
|
||||
color: var(--c-text-primary);
|
||||
}
|
||||
|
||||
.chain-layer-rows {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.chain-layer-row {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.chain-layer-tag {
|
||||
font-family: var(--f-sans);
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 1.5px;
|
||||
color: var(--c-text-muted);
|
||||
background: #f2f0ec;
|
||||
padding: 3px 8px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.chain-layer-text {
|
||||
font-family: var(--f-sans);
|
||||
font-size: 14px;
|
||||
color: var(--c-text-secondary);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* ===== Footer ===== */
|
||||
.footer {
|
||||
display: flex;
|
||||
@@ -389,7 +505,6 @@ body {
|
||||
}
|
||||
|
||||
.footer-logo-img {
|
||||
filter: brightness(0) invert(1);
|
||||
}
|
||||
|
||||
.footer-tagline {
|
||||
@@ -417,6 +532,26 @@ body {
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.footer-social-wechat-wrap {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.footer-wechat-popup {
|
||||
position: fixed;
|
||||
z-index: 9999;
|
||||
background: #ffffff;
|
||||
padding: 8px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
|
||||
.footer-wechat-popup img {
|
||||
display: block;
|
||||
width: 160px;
|
||||
height: 160px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.footer-social-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -1688,6 +1823,193 @@ body {
|
||||
color: #cccccc;
|
||||
}
|
||||
|
||||
/* ─── Contact Modal ──────────────────────────── */
|
||||
|
||||
/* HeroUI Modal overrides — strip rounded corners & shadow */
|
||||
.cm-base {
|
||||
border-radius: 0 !important;
|
||||
background: var(--c-bg-light) !important;
|
||||
box-shadow: 0 8px 40px rgba(0, 0, 0, 0.12) !important;
|
||||
padding: 0 !important;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.cm-backdrop {
|
||||
background: rgba(26, 26, 26, 0.45) !important;
|
||||
backdrop-filter: blur(2px);
|
||||
}
|
||||
|
||||
/* Inner layout */
|
||||
.cm {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 32px;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.cm-header {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.cm-header-left {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.cm-tag {
|
||||
font-family: var(--f-mono);
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 3px;
|
||||
color: var(--c-text-muted);
|
||||
}
|
||||
|
||||
.cm-title {
|
||||
font-family: var(--f-serif);
|
||||
font-size: 28px;
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
letter-spacing: -1px;
|
||||
color: var(--c-text-primary);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.cm-close {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
background: none;
|
||||
border: 1px solid var(--c-border);
|
||||
color: var(--c-text-secondary);
|
||||
cursor: pointer;
|
||||
transition: color 0.2s;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.cm-close:hover {
|
||||
color: var(--c-text-primary);
|
||||
}
|
||||
|
||||
.cm-divider {
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
background: var(--c-border);
|
||||
margin: 24px 0;
|
||||
}
|
||||
|
||||
/* Form */
|
||||
.cm-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.cm-row {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.cm-field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.cm-label {
|
||||
font-family: var(--f-sans);
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.5px;
|
||||
color: var(--c-text-muted);
|
||||
}
|
||||
|
||||
.cm-required {
|
||||
color: var(--c-text-secondary);
|
||||
}
|
||||
|
||||
.cm-input,
|
||||
.cm-textarea {
|
||||
font-family: var(--f-sans);
|
||||
font-size: 14px;
|
||||
color: var(--c-text-primary);
|
||||
background: transparent;
|
||||
border: 1px solid var(--c-border);
|
||||
padding: 12px 14px;
|
||||
width: 100%;
|
||||
outline: none;
|
||||
transition: border-color 0.2s;
|
||||
}
|
||||
|
||||
.cm-input::placeholder,
|
||||
.cm-textarea::placeholder {
|
||||
color: var(--c-text-muted);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.cm-input:focus,
|
||||
.cm-textarea:focus {
|
||||
border-color: var(--c-text-secondary);
|
||||
}
|
||||
|
||||
.cm-textarea {
|
||||
resize: vertical;
|
||||
min-height: 100px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
.cm-footer {
|
||||
padding-top: 24px;
|
||||
}
|
||||
|
||||
.cm-btn {
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.cm-btn:disabled {
|
||||
opacity: 0.4;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.cm-btn.btn--outline-dark {
|
||||
border: 1px solid var(--c-border);
|
||||
}
|
||||
|
||||
/* Success state */
|
||||
.cm-success {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 24px;
|
||||
padding: 40px 0;
|
||||
}
|
||||
|
||||
.cm-success-text {
|
||||
font-family: var(--f-sans);
|
||||
font-size: 15px;
|
||||
line-height: 1.6;
|
||||
color: var(--c-text-primary);
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Error */
|
||||
.cm-error {
|
||||
font-family: var(--f-sans);
|
||||
font-size: 13px;
|
||||
color: #c62828;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* ─── Language Switcher ──────────────────────────── */
|
||||
.lang-switcher-trigger {
|
||||
font-size: 12px !important;
|
||||
@@ -1790,6 +2112,10 @@ body {
|
||||
.flow-arrow { transform: rotate(90deg); padding: 8px 0; }
|
||||
.flow-card { width: 100%; max-width: 360px; }
|
||||
|
||||
/* ── Chain layout ── */
|
||||
.chain-layout { gap: 40px; }
|
||||
.chain-triangle { width: 280px; }
|
||||
|
||||
/* ── Scenario ── */
|
||||
.scenario-content { flex-direction: column; gap: 32px; }
|
||||
|
||||
@@ -1964,4 +2290,20 @@ body {
|
||||
|
||||
/* ── Scenario metrics ── */
|
||||
.scenario-metrics { flex-direction: column; gap: 16px; }
|
||||
|
||||
/* ── Chain layout ── */
|
||||
.chain-layout {
|
||||
flex-direction: column;
|
||||
gap: 32px;
|
||||
align-items: center;
|
||||
}
|
||||
.chain-triangle { width: 220px; }
|
||||
.chain-layer-item { padding: 20px; }
|
||||
.chain-layer-name { font-size: 18px; }
|
||||
|
||||
/* ── Contact Modal ── */
|
||||
.cm { padding: 24px; }
|
||||
.cm-title { font-size: 24px; }
|
||||
.cm-row { grid-template-columns: 1fr; gap: 16px; }
|
||||
.cm-divider { margin: 20px 0; }
|
||||
}
|
||||
|
||||
@@ -18,7 +18,11 @@ export default async function AboutHeroSection() {
|
||||
return (
|
||||
<section className="about-hero-section">
|
||||
<div className="about-hero-content">
|
||||
<span className="about-hero-label">{t("label")}</span>
|
||||
<div className="decorated-tag">
|
||||
<span className="decorated-tag-line" />
|
||||
<span className="decorated-tag-text">{t("label")}</span>
|
||||
<span className="decorated-tag-line" />
|
||||
</div>
|
||||
<h1 className="about-hero-title">{t("title")}</h1>
|
||||
<p className="about-hero-subtitle">{t("subtitle")}</p>
|
||||
<div className="about-hero-vision">
|
||||
|
||||
83
src/components/BlockchainSection.tsx
Normal file
83
src/components/BlockchainSection.tsx
Normal file
@@ -0,0 +1,83 @@
|
||||
import { getTranslations } from "next-intl/server";
|
||||
|
||||
export default async function BlockchainSection() {
|
||||
const t = await getTranslations("blockchain");
|
||||
|
||||
const layers = [
|
||||
{
|
||||
name: t("layer1Name"),
|
||||
func: t("layer1Func"),
|
||||
value: t("layer1Value"),
|
||||
},
|
||||
{
|
||||
name: t("layer2Name"),
|
||||
func: t("layer2Func"),
|
||||
value: t("layer2Value"),
|
||||
},
|
||||
{
|
||||
name: t("layer3Name"),
|
||||
func: t("layer3Func"),
|
||||
value: t("layer3Value"),
|
||||
},
|
||||
];
|
||||
|
||||
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("layerCount")}</span>
|
||||
<span className="cap-metric-label">{t("layerLabel")}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="chain-layout">
|
||||
<div className="chain-triangle">
|
||||
<svg viewBox="0 0 200 180" className="chain-triangle-svg">
|
||||
<polygon
|
||||
points="100,10 10,170 190,170"
|
||||
fill="var(--c-bg-dark)"
|
||||
/>
|
||||
</svg>
|
||||
<span className="chain-triangle-label chain-triangle-label--top">
|
||||
{layers[0].name}
|
||||
</span>
|
||||
<span className="chain-triangle-label chain-triangle-label--bl">
|
||||
{layers[1].name}
|
||||
</span>
|
||||
<span className="chain-triangle-label chain-triangle-label--br">
|
||||
{layers[2].name}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="chain-layers">
|
||||
{layers.map((layer, i) => (
|
||||
<div key={layer.name} className="chain-layer-item">
|
||||
<div className="chain-layer-head">
|
||||
<span className="chain-layer-num">{`0${i + 1}`}</span>
|
||||
<span className="chain-layer-name">{layer.name}</span>
|
||||
</div>
|
||||
<div className="chain-layer-rows">
|
||||
<div className="chain-layer-row">
|
||||
<span className="chain-layer-tag">{t("colFunc")}</span>
|
||||
<span className="chain-layer-text">{layer.func}</span>
|
||||
</div>
|
||||
<div className="chain-layer-row">
|
||||
<span className="chain-layer-tag">{t("colValue")}</span>
|
||||
<span className="chain-layer-text">{layer.value}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { Link } from "@/i18n/navigation";
|
||||
import { ArrowRight } from "lucide-react";
|
||||
import ContactButton from "./ContactButton";
|
||||
|
||||
export default async function CTASection() {
|
||||
const t = await getTranslations("cta");
|
||||
@@ -13,9 +14,9 @@ export default async function CTASection() {
|
||||
<span>{t("services")}</span>
|
||||
<ArrowRight size={16} />
|
||||
</Link>
|
||||
<Link href="/contact" className="btn btn--outline-dark">
|
||||
<ContactButton className="btn btn--outline-dark">
|
||||
{t("contact")}
|
||||
</Link>
|
||||
</ContactButton>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
|
||||
27
src/components/ContactButton.tsx
Normal file
27
src/components/ContactButton.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
"use client";
|
||||
|
||||
import { useDisclosure } from "@heroui/react";
|
||||
import ContactModal from "./ContactModal";
|
||||
|
||||
interface ContactButtonProps {
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export default function ContactButton({
|
||||
className,
|
||||
style,
|
||||
children,
|
||||
}: ContactButtonProps) {
|
||||
const { isOpen, onOpen, onOpenChange } = useDisclosure();
|
||||
|
||||
return (
|
||||
<>
|
||||
<button className={className} style={style} onClick={onOpen}>
|
||||
{children}
|
||||
</button>
|
||||
<ContactModal isOpen={isOpen} onOpenChange={onOpenChange} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
181
src/components/ContactModal.tsx
Normal file
181
src/components/ContactModal.tsx
Normal file
@@ -0,0 +1,181 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { Modal, ModalContent } from "@heroui/react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { X } from "lucide-react";
|
||||
|
||||
interface ContactModalProps {
|
||||
isOpen: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
}
|
||||
|
||||
export default function ContactModal({
|
||||
isOpen,
|
||||
onOpenChange,
|
||||
}: ContactModalProps) {
|
||||
const t = useTranslations("contact");
|
||||
const [form, setForm] = useState({
|
||||
name: "",
|
||||
company: "",
|
||||
email: "",
|
||||
phone: "",
|
||||
message: "",
|
||||
});
|
||||
const [status, setStatus] = useState<
|
||||
"idle" | "loading" | "success" | "error"
|
||||
>("idle");
|
||||
|
||||
const handleChange = (
|
||||
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
|
||||
) => {
|
||||
setForm((prev) => ({ ...prev, [e.target.name]: e.target.value }));
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (!form.name.trim() || !form.email.trim() || !form.message.trim()) return;
|
||||
setStatus("loading");
|
||||
try {
|
||||
const res = await fetch("/api/contact", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(form),
|
||||
});
|
||||
if (!res.ok) throw new Error("Failed");
|
||||
setStatus("success");
|
||||
setForm({ name: "", company: "", email: "", phone: "", message: "" });
|
||||
} catch {
|
||||
setStatus("error");
|
||||
}
|
||||
};
|
||||
|
||||
const handleClose = (open: boolean) => {
|
||||
if (!open) setStatus("idle");
|
||||
onOpenChange(open);
|
||||
};
|
||||
|
||||
const canSubmit = form.name.trim() && form.email.trim() && form.message.trim();
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
onOpenChange={handleClose}
|
||||
size="lg"
|
||||
placement="center"
|
||||
hideCloseButton
|
||||
classNames={{
|
||||
backdrop: "cm-backdrop",
|
||||
wrapper: "cm-wrapper",
|
||||
base: "cm-base",
|
||||
}}
|
||||
>
|
||||
<ModalContent>
|
||||
{(onClose) => (
|
||||
<div className="cm">
|
||||
{/* Header */}
|
||||
<div className="cm-header">
|
||||
<div className="cm-header-left">
|
||||
<span className="cm-tag">CONTACT</span>
|
||||
<h2 className="cm-title">{t("title")}</h2>
|
||||
</div>
|
||||
<button className="cm-close" onClick={onClose} aria-label="Close">
|
||||
<X size={18} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="cm-divider" />
|
||||
|
||||
{/* Body */}
|
||||
{status === "success" ? (
|
||||
<div className="cm-success">
|
||||
<p className="cm-success-text">{t("success")}</p>
|
||||
<button className="btn btn--outline-dark cm-btn" onClick={onClose}>
|
||||
{t("close")}
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="cm-form">
|
||||
<div className="cm-row">
|
||||
<div className="cm-field">
|
||||
<label className="cm-label">
|
||||
{t("name")} <span className="cm-required">*</span>
|
||||
</label>
|
||||
<input
|
||||
className="cm-input"
|
||||
name="name"
|
||||
value={form.name}
|
||||
onChange={handleChange}
|
||||
placeholder={t("namePlaceholder")}
|
||||
/>
|
||||
</div>
|
||||
<div className="cm-field">
|
||||
<label className="cm-label">{t("company")}</label>
|
||||
<input
|
||||
className="cm-input"
|
||||
name="company"
|
||||
value={form.company}
|
||||
onChange={handleChange}
|
||||
placeholder={t("companyPlaceholder")}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="cm-row">
|
||||
<div className="cm-field">
|
||||
<label className="cm-label">
|
||||
{t("email")} <span className="cm-required">*</span>
|
||||
</label>
|
||||
<input
|
||||
className="cm-input"
|
||||
name="email"
|
||||
type="email"
|
||||
value={form.email}
|
||||
onChange={handleChange}
|
||||
placeholder={t("emailPlaceholder")}
|
||||
/>
|
||||
</div>
|
||||
<div className="cm-field">
|
||||
<label className="cm-label">{t("phone")}</label>
|
||||
<input
|
||||
className="cm-input"
|
||||
name="phone"
|
||||
value={form.phone}
|
||||
onChange={handleChange}
|
||||
placeholder={t("phonePlaceholder")}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="cm-field">
|
||||
<label className="cm-label">
|
||||
{t("message")} <span className="cm-required">*</span>
|
||||
</label>
|
||||
<textarea
|
||||
className="cm-textarea"
|
||||
name="message"
|
||||
value={form.message}
|
||||
onChange={handleChange}
|
||||
placeholder={t("messagePlaceholder")}
|
||||
rows={4}
|
||||
/>
|
||||
</div>
|
||||
{status === "error" && (
|
||||
<p className="cm-error">{t("error")}</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="cm-footer">
|
||||
<button
|
||||
className="btn btn--dark cm-btn"
|
||||
onClick={handleSubmit}
|
||||
disabled={!canSubmit || status === "loading"}
|
||||
>
|
||||
{status === "loading" ? t("submitting") : t("submit")}
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
@@ -27,7 +27,7 @@ export default async function FlywheelSection() {
|
||||
},
|
||||
];
|
||||
|
||||
const summarySteps = [t("summary1"), t("summary2"), t("summary3")];
|
||||
const summarySteps = [t("summary1"), t("summary2")];
|
||||
|
||||
return (
|
||||
<section className="section section--dark">
|
||||
|
||||
@@ -1,44 +1,6 @@
|
||||
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" },
|
||||
];
|
||||
import FooterSocial from "./FooterSocial";
|
||||
|
||||
export default async function Footer() {
|
||||
const t = await getTranslations("footer");
|
||||
@@ -63,7 +25,7 @@ export default async function Footer() {
|
||||
<div className="footer-brand">
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
<img
|
||||
src="/logo.png"
|
||||
src="/logo-footer.png"
|
||||
alt="DESUN SINGULARITY"
|
||||
width={160}
|
||||
height={46}
|
||||
@@ -72,18 +34,7 @@ export default async function Footer() {
|
||||
<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>
|
||||
<FooterSocial />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
112
src/components/FooterSocial.tsx
Normal file
112
src/components/FooterSocial.tsx
Normal file
@@ -0,0 +1,112 @@
|
||||
"use client";
|
||||
|
||||
import { useRef, useState } from "react";
|
||||
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
export default function FooterSocial() {
|
||||
const [showQr, setShowQr] = useState(false);
|
||||
const btnRef = useRef<HTMLButtonElement>(null);
|
||||
|
||||
const getPopupStyle = (): React.CSSProperties => {
|
||||
if (!btnRef.current) return {};
|
||||
const rect = btnRef.current.getBoundingClientRect();
|
||||
return {
|
||||
left: rect.left + rect.width / 2 - 88,
|
||||
top: rect.top - 176 - 12,
|
||||
};
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="footer-social">
|
||||
{/* WeChat — hover/click shows QR */}
|
||||
<div
|
||||
className="footer-social-wechat-wrap"
|
||||
onMouseEnter={() => setShowQr(true)}
|
||||
onMouseLeave={() => setShowQr(false)}
|
||||
>
|
||||
<button
|
||||
ref={btnRef}
|
||||
className="footer-social-icon"
|
||||
aria-label="WeChat"
|
||||
onClick={() => setShowQr((v) => !v)}
|
||||
>
|
||||
<WeChatIcon size={16} />
|
||||
</button>
|
||||
{showQr && (
|
||||
<div className="footer-wechat-popup" style={getPopupStyle()}>
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
<img
|
||||
src="/wechat-qr.png"
|
||||
alt="WeChat QR Code"
|
||||
width={160}
|
||||
height={160}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* LinkedIn */}
|
||||
<a
|
||||
href="https://www.linkedin.com/company/desunsingularity"
|
||||
className="footer-social-icon"
|
||||
aria-label="LinkedIn"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<LinkedInIcon size={16} />
|
||||
</a>
|
||||
|
||||
{/* X */}
|
||||
<a
|
||||
href="https://x.com/DesunHK"
|
||||
className="footer-social-icon"
|
||||
aria-label="X"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<XIcon size={16} />
|
||||
</a>
|
||||
|
||||
{/* Email */}
|
||||
<a
|
||||
href="mailto:contact@desunweb3.com"
|
||||
className="footer-social-icon"
|
||||
aria-label="Email"
|
||||
>
|
||||
<MailIcon size={16} />
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -4,13 +4,16 @@ import Image from "next/image";
|
||||
import { Link, usePathname } from "@/i18n/navigation";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useState } from "react";
|
||||
import { useDisclosure } from "@heroui/react";
|
||||
import { Menu, X } from "lucide-react";
|
||||
import LanguageSwitcher from "./LanguageSwitcher";
|
||||
import ContactModal from "./ContactModal";
|
||||
|
||||
export default function Header() {
|
||||
const pathname = usePathname();
|
||||
const t = useTranslations("header");
|
||||
const [mobileOpen, setMobileOpen] = useState(false);
|
||||
const { isOpen, onOpen, onOpenChange } = useDisclosure();
|
||||
|
||||
const navItems = [
|
||||
{ label: t("home"), href: "/" as const },
|
||||
@@ -23,7 +26,7 @@ export default function Header() {
|
||||
<header className="header">
|
||||
<Link href="/" className="logo">
|
||||
<Image
|
||||
src="/logo.png"
|
||||
src="/logo-header.png"
|
||||
alt="DESUN SINGULARITY"
|
||||
width={140}
|
||||
height={40}
|
||||
@@ -45,9 +48,9 @@ export default function Header() {
|
||||
|
||||
<div className="header-right">
|
||||
<LanguageSwitcher />
|
||||
<Link href="/contact" className="contact-btn">
|
||||
<button className="contact-btn" onClick={onOpen}>
|
||||
{t("contact")}
|
||||
</Link>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Mobile hamburger */}
|
||||
@@ -75,17 +78,20 @@ export default function Header() {
|
||||
))}
|
||||
<div className="mobile-nav-footer">
|
||||
<LanguageSwitcher />
|
||||
<Link
|
||||
href="/contact"
|
||||
<button
|
||||
className="contact-btn"
|
||||
onClick={() => setMobileOpen(false)}
|
||||
onClick={() => {
|
||||
setMobileOpen(false);
|
||||
onOpen();
|
||||
}}
|
||||
>
|
||||
{t("contact")}
|
||||
</Link>
|
||||
</button>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
)}
|
||||
<ContactModal isOpen={isOpen} onOpenChange={onOpenChange} />
|
||||
</header>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -13,7 +13,11 @@ export default async function HeroSection() {
|
||||
return (
|
||||
<section className="section section--light hero">
|
||||
<div className="hero-content">
|
||||
<span className="label-text">{t("label")}</span>
|
||||
<div className="decorated-tag">
|
||||
<span className="decorated-tag-line" />
|
||||
<span className="decorated-tag-text">{t("label")}</span>
|
||||
<span className="decorated-tag-line" />
|
||||
</div>
|
||||
<h1 className="heading-display">{t("title")}</h1>
|
||||
<p className="body-text body-text--lg">{t("subtitle")}</p>
|
||||
</div>
|
||||
|
||||
@@ -32,14 +32,6 @@ export default async function RecruitSection() {
|
||||
))}
|
||||
</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>
|
||||
);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { Link } from "@/i18n/navigation";
|
||||
import ContactButton from "./ContactButton";
|
||||
|
||||
export default async function SolutionsCTASection() {
|
||||
const t = await getTranslations("solCta");
|
||||
@@ -9,10 +9,10 @@ export default async function SolutionsCTASection() {
|
||||
<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 }}>
|
||||
<ContactButton className="btn btn--dark" style={{ gap: 8 }}>
|
||||
<span>{t("contact")}</span>
|
||||
<span>→</span>
|
||||
</Link>
|
||||
</ContactButton>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -30,7 +30,11 @@ export default async function SolutionsHeroSection() {
|
||||
return (
|
||||
<section className="section section--light">
|
||||
<div className="section-header" style={{ gap: 24 }}>
|
||||
<span className="label-text">{t("label")}</span>
|
||||
<div className="decorated-tag">
|
||||
<span className="decorated-tag-line" />
|
||||
<span className="decorated-tag-text">{t("label")}</span>
|
||||
<span className="decorated-tag-line" />
|
||||
</div>
|
||||
<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>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { Link } from "@/i18n/navigation";
|
||||
import ContactButton from "./ContactButton";
|
||||
|
||||
export default async function TechCTASection() {
|
||||
const t = await getTranslations("techCta");
|
||||
@@ -11,9 +12,9 @@ export default async function TechCTASection() {
|
||||
<Link href="/solutions" className="btn btn--dark">
|
||||
{t("solutions")}
|
||||
</Link>
|
||||
<Link href="/contact" className="btn btn--outline-dark">
|
||||
<ContactButton className="btn btn--outline-dark">
|
||||
{t("contact")}
|
||||
</Link>
|
||||
</ContactButton>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
|
||||
@@ -29,9 +29,9 @@
|
||||
"metric2Label": "Operating Area",
|
||||
"metric2Value": "14M+ m²",
|
||||
"metric2Desc": "Covering residential, commercial & industrial parks",
|
||||
"metric3Label": "Strategy APY",
|
||||
"metric3Value": "18%+",
|
||||
"metric3Desc": "Market-neutral strategy performance",
|
||||
"metric3Label": "Strategy Portfolio",
|
||||
"metric3Value": "10+",
|
||||
"metric3Desc": "Diversified quantitative strategy matrix",
|
||||
"metric4Label": "Licenses",
|
||||
"metric4Value": "Type 1/2/4/5/9",
|
||||
"metric4Desc": "Hong Kong SFC licenses"
|
||||
@@ -61,8 +61,7 @@
|
||||
"card3Feature2": "AI risk management",
|
||||
"card3Feature3": "Multi-market arbitrage",
|
||||
"summary1": "Create value in physical spaces",
|
||||
"summary2": "Discover quality assets and connect global capital",
|
||||
"summary3": "Amplify returns with quantitative strategies"
|
||||
"summary2": "Discover quality assets and connect global capital"
|
||||
},
|
||||
"market": {
|
||||
"label": "Market Opportunity",
|
||||
@@ -345,11 +344,48 @@
|
||||
"card4Desc": "Hong Kong SFC framework, full license coverage",
|
||||
"card4Value": "Type 1/2/4/5/9 Licenses"
|
||||
},
|
||||
"blockchain": {
|
||||
"label": "BLOCKCHAIN",
|
||||
"title": "DeSpace Smart Chain",
|
||||
"subtitle": "The digital operating system for urban spaces, the trust foundation for asset value",
|
||||
"layerCount": "3",
|
||||
"layerLabel": "Three-Layer Value Architecture",
|
||||
"colLayer": "LAYER",
|
||||
"colFunc": "FUNCTION",
|
||||
"colValue": "VALUE",
|
||||
"layer1Name": "Data Layer",
|
||||
"layer1Func": "Unified storage of urban space operational data",
|
||||
"layer1Value": "Authentic, complete, immutable",
|
||||
"layer2Name": "Rights Layer",
|
||||
"layer2Func": "On-chain asset rights confirmation and certification",
|
||||
"layer2Value": "Trusted data source for RWA issuance",
|
||||
"layer3Name": "Trust Layer",
|
||||
"layer3Func": "Transparent multi-party collaboration network",
|
||||
"layer3Value": "Eliminate trust friction, reduce transaction costs"
|
||||
},
|
||||
"techCta": {
|
||||
"title": "Explore How Our Technology Empowers Your Business",
|
||||
"solutions": "View Business Scenarios",
|
||||
"contact": "Contact Us"
|
||||
},
|
||||
"contact": {
|
||||
"title": "Contact Us",
|
||||
"name": "Name",
|
||||
"namePlaceholder": "Enter your name",
|
||||
"company": "Company",
|
||||
"companyPlaceholder": "Enter company name",
|
||||
"email": "Email",
|
||||
"emailPlaceholder": "Enter email address",
|
||||
"phone": "Phone",
|
||||
"phonePlaceholder": "Enter phone number",
|
||||
"message": "Message",
|
||||
"messagePlaceholder": "Briefly describe your needs",
|
||||
"submit": "Submit",
|
||||
"submitting": "Submitting...",
|
||||
"success": "Submitted successfully! We will contact you shortly.",
|
||||
"error": "Submission failed. Please try again later.",
|
||||
"close": "Close"
|
||||
},
|
||||
"footer": {
|
||||
"tagline": "AI + Web3 Powered Full-Chain Asset Technology Platform",
|
||||
"contactLabel": "Contact Us",
|
||||
|
||||
@@ -29,9 +29,9 @@
|
||||
"metric2Label": "運營面積",
|
||||
"metric2Value": "1400萬+ m²",
|
||||
"metric2Desc": "覆蓋住宅、商業、產業園",
|
||||
"metric3Label": "策略年化",
|
||||
"metric3Value": "18%+",
|
||||
"metric3Desc": "市場中性策略表現",
|
||||
"metric3Label": "策略組合",
|
||||
"metric3Value": "10+",
|
||||
"metric3Desc": "多元量化策略矩陣",
|
||||
"metric4Label": "合規牌照",
|
||||
"metric4Value": "1/2/4/5/9 類",
|
||||
"metric4Desc": "香港證監會牌照"
|
||||
@@ -61,8 +61,7 @@
|
||||
"card3Feature2": "AI 風控系統",
|
||||
"card3Feature3": "多市場套利",
|
||||
"summary1": "在實體空間創造價值",
|
||||
"summary2": "發現優質資產連接全球資本",
|
||||
"summary3": "以量化策略放大收益"
|
||||
"summary2": "發現優質資產連接全球資本"
|
||||
},
|
||||
"market": {
|
||||
"label": "市場機會",
|
||||
@@ -345,11 +344,48 @@
|
||||
"card4Desc": "香港 SFC 框架,全牌照覆蓋",
|
||||
"card4Value": "1/2/4/5/9 類牌照"
|
||||
},
|
||||
"blockchain": {
|
||||
"label": "BLOCKCHAIN",
|
||||
"title": "智能鏈 (DeSpace Smart Chain)",
|
||||
"subtitle": "城市空間的數字操作系統,資產價值的互信底座",
|
||||
"layerCount": "3",
|
||||
"layerLabel": "三層價值架構",
|
||||
"colLayer": "層級",
|
||||
"colFunc": "功能",
|
||||
"colValue": "價值",
|
||||
"layer1Name": "數據層",
|
||||
"layer1Func": "城市空間運營數據的統一存儲",
|
||||
"layer1Value": "真實、完整、不可篡改",
|
||||
"layer2Name": "確權層",
|
||||
"layer2Func": "資產權益的鏈上確權與存證",
|
||||
"layer2Value": "為 RWA 發行提供可信數據源",
|
||||
"layer3Name": "互信層",
|
||||
"layer3Func": "多方參與的透明協作網絡",
|
||||
"layer3Value": "消除信任摩擦,降低交易成本"
|
||||
},
|
||||
"techCta": {
|
||||
"title": "探索我們的技術如何賦能您的業務",
|
||||
"solutions": "查看業務場景",
|
||||
"contact": "聯繫我們"
|
||||
},
|
||||
"contact": {
|
||||
"title": "聯繫我們",
|
||||
"name": "姓名",
|
||||
"namePlaceholder": "請輸入您的姓名",
|
||||
"company": "公司",
|
||||
"companyPlaceholder": "請輸入公司名稱",
|
||||
"email": "郵箱",
|
||||
"emailPlaceholder": "請輸入郵箱地址",
|
||||
"phone": "電話",
|
||||
"phonePlaceholder": "請輸入聯繫電話",
|
||||
"message": "需求描述",
|
||||
"messagePlaceholder": "請簡要描述您的需求",
|
||||
"submit": "提交",
|
||||
"submitting": "提交中...",
|
||||
"success": "提交成功,我們會盡快與您聯繫!",
|
||||
"error": "提交失敗,請稍後重試",
|
||||
"close": "關閉"
|
||||
},
|
||||
"footer": {
|
||||
"tagline": "AI + Web3 驅動的全鏈路資產科技平台",
|
||||
"contactLabel": "聯繫我們",
|
||||
|
||||
@@ -29,9 +29,9 @@
|
||||
"metric2Label": "运营面积",
|
||||
"metric2Value": "1400万+ m²",
|
||||
"metric2Desc": "覆盖住宅、商业、产业园",
|
||||
"metric3Label": "策略年化",
|
||||
"metric3Value": "18%+",
|
||||
"metric3Desc": "市场中性策略表现",
|
||||
"metric3Label": "策略组合",
|
||||
"metric3Value": "10+",
|
||||
"metric3Desc": "多元量化策略矩阵",
|
||||
"metric4Label": "合规牌照",
|
||||
"metric4Value": "1/2/4/5/9 类",
|
||||
"metric4Desc": "香港证监会牌照"
|
||||
@@ -61,8 +61,7 @@
|
||||
"card3Feature2": "AI 风控系统",
|
||||
"card3Feature3": "多市场套利",
|
||||
"summary1": "在实体空间创造价值",
|
||||
"summary2": "发现优质资产连接全球资本",
|
||||
"summary3": "以量化策略放大收益"
|
||||
"summary2": "发现优质资产连接全球资本"
|
||||
},
|
||||
"market": {
|
||||
"label": "市场机会",
|
||||
@@ -345,11 +344,48 @@
|
||||
"card4Desc": "香港 SFC 框架,全牌照覆盖",
|
||||
"card4Value": "1/2/4/5/9 类牌照"
|
||||
},
|
||||
"blockchain": {
|
||||
"label": "BLOCKCHAIN",
|
||||
"title": "智能链 (DeSpace Smart Chain)",
|
||||
"subtitle": "城市空间的数字操作系统,资产价值的互信底座",
|
||||
"layerCount": "3",
|
||||
"layerLabel": "三层价值架构",
|
||||
"colLayer": "层级",
|
||||
"colFunc": "功能",
|
||||
"colValue": "价值",
|
||||
"layer1Name": "数据层",
|
||||
"layer1Func": "城市空间运营数据的统一存储",
|
||||
"layer1Value": "真实、完整、不可篡改",
|
||||
"layer2Name": "确权层",
|
||||
"layer2Func": "资产权益的链上确权与存证",
|
||||
"layer2Value": "为 RWA 发行提供可信数据源",
|
||||
"layer3Name": "互信层",
|
||||
"layer3Func": "多方参与的透明协作网络",
|
||||
"layer3Value": "消除信任摩擦,降低交易成本"
|
||||
},
|
||||
"techCta": {
|
||||
"title": "探索我们的技术如何赋能您的业务",
|
||||
"solutions": "查看业务场景",
|
||||
"contact": "联系我们"
|
||||
},
|
||||
"contact": {
|
||||
"title": "联系我们",
|
||||
"name": "姓名",
|
||||
"namePlaceholder": "请输入您的姓名",
|
||||
"company": "公司",
|
||||
"companyPlaceholder": "请输入公司名称",
|
||||
"email": "邮箱",
|
||||
"emailPlaceholder": "请输入邮箱地址",
|
||||
"phone": "电话",
|
||||
"phonePlaceholder": "请输入联系电话",
|
||||
"message": "需求描述",
|
||||
"messagePlaceholder": "请简要描述您的需求",
|
||||
"submit": "提交",
|
||||
"submitting": "提交中...",
|
||||
"success": "提交成功,我们会尽快与您联系!",
|
||||
"error": "提交失败,请稍后重试",
|
||||
"close": "关闭"
|
||||
},
|
||||
"footer": {
|
||||
"tagline": "AI + Web3 驱动的全链路资产科技平台",
|
||||
"contactLabel": "联系我们",
|
||||
|
||||
Reference in New Issue
Block a user