Restore home lifestyle/voices with i18n and polish about/static CTAs.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
ab9d8f6670
commit
863643aa33
@ -1,62 +1,11 @@
|
|||||||
import Link from "next/link";
|
|
||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
import SiteShell from "@/components/SiteShell";
|
import AboutClient from "@/components/AboutClient";
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "关于 nomadro",
|
title: "关于 nomadro · About",
|
||||||
description: "nomadro —— 用一行代码环游世界,数字游民旅居指南",
|
description: "nomadro —— 用一行代码环游世界,数字游民旅居指南",
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function AboutPage() {
|
export default function AboutPage() {
|
||||||
return (
|
return <AboutClient />;
|
||||||
<SiteShell>
|
|
||||||
<div className="legal-page about-page">
|
|
||||||
<nav className="detail-nav">
|
|
||||||
<Link href="/">← 返回首页</Link>
|
|
||||||
</nav>
|
|
||||||
<article className="legal-content">
|
|
||||||
<span className="section-tag">🌏 ABOUT</span>
|
|
||||||
<h1>关于 nomadro</h1>
|
|
||||||
<p className="legal-updated">用一行代码环游世界</p>
|
|
||||||
|
|
||||||
<section>
|
|
||||||
<h2>我们是谁</h2>
|
|
||||||
<p>
|
|
||||||
nomadro 是一个面向数字游民的旅居决策平台。我们把目的地数据、签证信息、费用估算、时区协作与社区经验集中在一起,帮你更快做出「下一站去哪」的决定。
|
|
||||||
</p>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section>
|
|
||||||
<h2>能做什么</h2>
|
|
||||||
<ul className="about-list">
|
|
||||||
<li>🗺️ 探索全球热门游民城市与对比工具</li>
|
|
||||||
<li>🧮 计算生活成本、机票与启动金目标</li>
|
|
||||||
<li>📋 签证匹配、保险与上网方案参考</li>
|
|
||||||
<li>🗓️ 规划多城行程并分享给伙伴</li>
|
|
||||||
<li>🗣️ 落地短语、行李清单与活动日历</li>
|
|
||||||
</ul>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section>
|
|
||||||
<h2>联系我们</h2>
|
|
||||||
<p>
|
|
||||||
合作与反馈:
|
|
||||||
<a href="mailto:hello@nomadro.com"> hello@nomadro.com</a>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
开源仓库:
|
|
||||||
<a href="https://gitea.dsx2020.com/eric/nomadweb" target="_blank" rel="noopener noreferrer">
|
|
||||||
gitea.dsx2020.com/eric/nomadweb
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<div className="about-actions">
|
|
||||||
<Link href="/#destinations" className="btn btn-primary">开始探索 🌍</Link>
|
|
||||||
<Link href="/login" className="btn btn-ghost">加入社区</Link>
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
</div>
|
|
||||||
</SiteShell>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,6 +8,14 @@ export const metadata: Metadata = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const LOGS = [
|
const LOGS = [
|
||||||
|
{
|
||||||
|
date: "2026-09-04",
|
||||||
|
tag: "首页/关于页",
|
||||||
|
items: [
|
||||||
|
"首页恢复「游民一天」与「游民心声」区块,中英文化并直达七天指南/活动/社区",
|
||||||
|
"关于页改为可切换语言,CTA 指向下一站、社区与旅居计划;静态帮助页主按钮高亮",
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
date: "2026-09-04",
|
date: "2026-09-04",
|
||||||
tag: "学院/博客/社区/电子书",
|
tag: "学院/博客/社区/电子书",
|
||||||
|
|||||||
68
frontend/src/components/AboutClient.tsx
Normal file
68
frontend/src/components/AboutClient.tsx
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import Link from "next/link";
|
||||||
|
import SiteShell from "@/components/SiteShell";
|
||||||
|
import { useI18n } from "@/lib/i18n";
|
||||||
|
|
||||||
|
export default function AboutClient() {
|
||||||
|
const { t } = useI18n();
|
||||||
|
const A = t.aboutPage;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SiteShell>
|
||||||
|
<div className="legal-page about-page">
|
||||||
|
<nav className="detail-nav">
|
||||||
|
<Link href="/">← {t.common.backHome}</Link>
|
||||||
|
</nav>
|
||||||
|
<article className="legal-content">
|
||||||
|
<span className="section-tag">{A.tag}</span>
|
||||||
|
<h1>{A.title}</h1>
|
||||||
|
<p className="legal-updated">{A.subtitle}</p>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>{A.whoTitle}</h2>
|
||||||
|
<p>{A.whoBody}</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>{A.whatTitle}</h2>
|
||||||
|
<ul className="about-list">
|
||||||
|
<li>{A.what1}</li>
|
||||||
|
<li>{A.what2}</li>
|
||||||
|
<li>{A.what3}</li>
|
||||||
|
<li>{A.what4}</li>
|
||||||
|
<li>{A.what5}</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>{A.contactTitle}</h2>
|
||||||
|
<p>
|
||||||
|
{A.contactBody}
|
||||||
|
<a href="mailto:hello@nomadro.com"> hello@nomadro.com</a>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
{A.repoBody}
|
||||||
|
<a href="https://gitea.dsx2020.com/eric/nomadweb" target="_blank" rel="noopener noreferrer">
|
||||||
|
{" "}
|
||||||
|
gitea.dsx2020.com/eric/nomadweb
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div className="about-actions">
|
||||||
|
<Link href="/next-stop" className="btn btn-primary">
|
||||||
|
{A.ctaExplore}
|
||||||
|
</Link>
|
||||||
|
<Link href="/community" className="btn btn-ghost">
|
||||||
|
{A.ctaCommunity}
|
||||||
|
</Link>
|
||||||
|
<Link href="/plan" className="btn btn-ghost">
|
||||||
|
{A.ctaPlan}
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
</SiteShell>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1,58 +1,48 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useToast } from "@/lib/toast";
|
import Link from "next/link";
|
||||||
import { api } from "@/lib/api";
|
import { useI18n } from "@/lib/i18n";
|
||||||
|
import NewsletterSubscribe from "@/components/NewsletterSubscribe";
|
||||||
import type { Testimonial } from "@/lib/types";
|
import type { Testimonial } from "@/lib/types";
|
||||||
|
|
||||||
export default function Community({ testimonials }: { testimonials: Testimonial[] }) {
|
export default function Community({ testimonials }: { testimonials: Testimonial[] }) {
|
||||||
const { toast } = useToast();
|
const { t } = useI18n();
|
||||||
|
const C = t.homeCommunity;
|
||||||
const [active, setActive] = useState(0);
|
const [active, setActive] = useState(0);
|
||||||
const [paused, setPaused] = useState(false);
|
const [paused, setPaused] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (testimonials.length <= 1 || paused) return;
|
if (testimonials.length <= 1 || paused) return;
|
||||||
const t = setInterval(() => setActive((i) => (i + 1) % testimonials.length), 4500);
|
const id = setInterval(() => setActive((i) => (i + 1) % testimonials.length), 4500);
|
||||||
return () => clearInterval(t);
|
return () => clearInterval(id);
|
||||||
}, [testimonials.length, paused]);
|
}, [testimonials.length, paused]);
|
||||||
|
|
||||||
const handleSubscribe = async (e: React.FormEvent<HTMLFormElement>) => {
|
const cur = testimonials[active];
|
||||||
e.preventDefault();
|
|
||||||
const form = e.currentTarget;
|
|
||||||
const email = (form.elements.namedItem("email") as HTMLInputElement).value;
|
|
||||||
try {
|
|
||||||
await api.subscribe(email);
|
|
||||||
toast("订阅成功!欢迎加入 nomadro 社区 🎉");
|
|
||||||
form.reset();
|
|
||||||
} catch { /* ignore */ }
|
|
||||||
};
|
|
||||||
|
|
||||||
const t = testimonials[active];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<section className="section community" id="voices">
|
||||||
<section className="section community" id="community">
|
|
||||||
<div className="container">
|
<div className="container">
|
||||||
<div className="section-header reveal">
|
<div className="section-header reveal">
|
||||||
<span className="section-tag">🤝 COMMUNITY</span>
|
<span className="section-tag">{C.tag}</span>
|
||||||
<h2>游民心声</h2>
|
<h2>{C.title}</h2>
|
||||||
<p>来自全球数字游民的真实故事</p>
|
<p>{C.subtitle}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{t && (
|
{cur && (
|
||||||
<div
|
<div
|
||||||
className="testimonial-carousel reveal"
|
className="testimonial-carousel reveal"
|
||||||
onMouseEnter={() => setPaused(true)}
|
onMouseEnter={() => setPaused(true)}
|
||||||
onMouseLeave={() => setPaused(false)}
|
onMouseLeave={() => setPaused(false)}
|
||||||
>
|
>
|
||||||
<div className="testimonial-featured" key={t.id}>
|
<div className="testimonial-featured" key={cur.id}>
|
||||||
<div className="testimonial-avatar">{t.avatar}</div>
|
<div className="testimonial-avatar">{cur.avatar}</div>
|
||||||
<div className="testimonial-content">
|
<div className="testimonial-content">
|
||||||
<div className="testimonial-stars">{"⭐".repeat(t.rating)}</div>
|
<div className="testimonial-stars">{"⭐".repeat(cur.rating)}</div>
|
||||||
<p>“{t.content}”</p>
|
<p>“{cur.content}”</p>
|
||||||
<div className="testimonial-author">
|
<div className="testimonial-author">
|
||||||
<strong>{t.author}</strong>
|
<strong>{cur.author}</strong>
|
||||||
<span>{t.role}</span>
|
<span>{cur.role}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -60,9 +50,10 @@ export default function Community({ testimonials }: { testimonials: Testimonial[
|
|||||||
{testimonials.map((item, i) => (
|
{testimonials.map((item, i) => (
|
||||||
<button
|
<button
|
||||||
key={item.id}
|
key={item.id}
|
||||||
|
type="button"
|
||||||
className={`testimonial-dot${i === active ? " active" : ""}`}
|
className={`testimonial-dot${i === active ? " active" : ""}`}
|
||||||
onClick={() => setActive(i)}
|
onClick={() => setActive(i)}
|
||||||
aria-label={`评价 ${i + 1}`}
|
aria-label={`${i + 1}`}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@ -87,16 +78,20 @@ export default function Community({ testimonials }: { testimonials: Testimonial[
|
|||||||
|
|
||||||
<div className="cta-banner reveal">
|
<div className="cta-banner reveal">
|
||||||
<div className="cta-content">
|
<div className="cta-content">
|
||||||
<h2>准备好开始你的旅居之旅了吗? 🌍</h2>
|
<h2>{C.ctaTitle}</h2>
|
||||||
<p>加入 50,000+ 数字游民社区,获取目的地指南、签证攻略与独家优惠</p>
|
<p>{C.ctaDesc}</p>
|
||||||
|
<div className="dating-empty-actions" style={{ marginTop: 12, justifyContent: "flex-start" }}>
|
||||||
|
<Link href="/community" className="btn btn-primary btn-sm">
|
||||||
|
{t.nav.community}
|
||||||
|
</Link>
|
||||||
|
<Link href="/meetups" className="btn btn-sm">
|
||||||
|
{t.nav.meetups}
|
||||||
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<form className="cta-form" onSubmit={handleSubscribe}>
|
</div>
|
||||||
<input type="email" name="email" placeholder="输入你的邮箱 📧" required />
|
<NewsletterSubscribe source="home-voices" />
|
||||||
<button type="submit" className="btn btn-primary"><span>免费订阅</span><span>🚀</span></button>
|
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -46,6 +46,8 @@ const NomadTips = lazy(() => import("./NomadTips"));
|
|||||||
const FeedbackWidget = lazy(() => import("./FeedbackWidget"));
|
const FeedbackWidget = lazy(() => import("./FeedbackWidget"));
|
||||||
const OnboardingTour = lazy(() => import("./OnboardingTour"));
|
const OnboardingTour = lazy(() => import("./OnboardingTour"));
|
||||||
const ToolsStrip = lazy(() => import("./ToolsStrip"));
|
const ToolsStrip = lazy(() => import("./ToolsStrip"));
|
||||||
|
const Lifestyle = lazy(() => import("./Lifestyle"));
|
||||||
|
const Community = lazy(() => import("./Community"));
|
||||||
const KeyboardShortcuts = lazy(() => import("./KeyboardShortcuts"));
|
const KeyboardShortcuts = lazy(() => import("./KeyboardShortcuts"));
|
||||||
const PwaInstallHint = lazy(() => import("./PwaInstallHint"));
|
const PwaInstallHint = lazy(() => import("./PwaInstallHint"));
|
||||||
const RecentlyViewed = lazy(() => import("./RecentlyViewed"));
|
const RecentlyViewed = lazy(() => import("./RecentlyViewed"));
|
||||||
@ -166,6 +168,10 @@ export default function HomeClient(props: Props) {
|
|||||||
<ToolsStrip />
|
<ToolsStrip />
|
||||||
</WhenVisible>
|
</WhenVisible>
|
||||||
|
|
||||||
|
<WhenVisible minHeight={420}>
|
||||||
|
<Lifestyle />
|
||||||
|
</WhenVisible>
|
||||||
|
|
||||||
<div className="container home-ring-wrap">
|
<div className="container home-ring-wrap">
|
||||||
<RingNext title={t.ring.continue} steps={rings.afterDiscover} />
|
<RingNext title={t.ring.continue} steps={rings.afterDiscover} />
|
||||||
</div>
|
</div>
|
||||||
@ -176,6 +182,11 @@ export default function HomeClient(props: Props) {
|
|||||||
<WhenVisible minHeight={220}>
|
<WhenVisible minHeight={220}>
|
||||||
<BlogSection posts={props.blog} limit={3} />
|
<BlogSection posts={props.blog} limit={3} />
|
||||||
</WhenVisible>
|
</WhenVisible>
|
||||||
|
{props.testimonials.length > 0 && (
|
||||||
|
<WhenVisible minHeight={320}>
|
||||||
|
<Community testimonials={props.testimonials} />
|
||||||
|
</WhenVisible>
|
||||||
|
)}
|
||||||
<WhenVisible minHeight={200}>
|
<WhenVisible minHeight={200}>
|
||||||
<FAQSection faqs={props.faqs} limit={5} />
|
<FAQSection faqs={props.faqs} limit={5} />
|
||||||
</WhenVisible>
|
</WhenVisible>
|
||||||
|
|||||||
@ -1,41 +1,59 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { useI18n } from "@/lib/i18n";
|
||||||
|
|
||||||
const ITEMS = [
|
const ITEM_KEYS = ["morning", "work", "explore", "collab", "social"] as const;
|
||||||
{ time: "🌅 07:00", icon: "🧘", title: "晨间仪式", desc: "瑜伽、冥想或海边跑步,用身心唤醒开启新的一天", tip: "推荐:清迈河边晨跑、巴厘岛海滩瑜伽" },
|
|
||||||
{ time: "☕ 09:00", icon: "💻", title: "深度工作", desc: "在 Co-working Space 或咖啡馆专注 4 小时,完成核心任务", tip: "推荐:Punspace 清迈、Outsite 里斯本" },
|
|
||||||
{ time: "🍜 13:00", icon: "🍽️", title: "本地探索", desc: "品尝地道美食,与当地人交流,感受文化碰撞", tip: "推荐:清迈夜市、巴塞罗那 Tapas 之旅" },
|
|
||||||
{ time: "🌇 17:00", icon: "📞", title: "跨时区协作", desc: "与全球团队视频会议,灵活安排跨时区沟通", tip: "工具:Calendly 排时区、World Time Buddy" },
|
|
||||||
{ time: "🌙 20:00", icon: "🎉", title: "社群社交", desc: "参加 Nomad Meetup,结识来自世界各地的同行者", tip: "平台:Nomad List Meetups、Facebook 群组" },
|
|
||||||
];
|
|
||||||
|
|
||||||
export default function Lifestyle() {
|
export default function Lifestyle() {
|
||||||
|
const { t } = useI18n();
|
||||||
|
const L = t.lifestyle;
|
||||||
const [active, setActive] = useState(0);
|
const [active, setActive] = useState(0);
|
||||||
|
const items = ITEM_KEYS.map((key) => ({
|
||||||
|
key,
|
||||||
|
time: L[`time_${key}` as keyof typeof L] as string,
|
||||||
|
icon: L[`icon_${key}` as keyof typeof L] as string,
|
||||||
|
title: L[`title_${key}` as keyof typeof L] as string,
|
||||||
|
desc: L[`desc_${key}` as keyof typeof L] as string,
|
||||||
|
tip: L[`tip_${key}` as keyof typeof L] as string,
|
||||||
|
}));
|
||||||
|
const cur = items[active];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="section lifestyle" id="lifestyle">
|
<section className="section lifestyle" id="lifestyle">
|
||||||
<div className="container">
|
<div className="container">
|
||||||
<div className="section-header reveal">
|
<div className="section-header reveal">
|
||||||
<span className="section-tag">💻 NOMAD LIFE</span>
|
<span className="section-tag">{L.tag}</span>
|
||||||
<h2>数字游民的一天</h2>
|
<h2>{L.title}</h2>
|
||||||
<p>自由不等于散漫,高效工作才能尽情探索 — 点击卡片查看详情</p>
|
<p>{L.subtitle}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="lifestyle-layout reveal">
|
<div className="lifestyle-layout reveal">
|
||||||
<div className="timeline">
|
<div className="timeline">
|
||||||
<div className="timeline-line">
|
<div className="timeline-line">
|
||||||
<svg viewBox="0 0 4 600" preserveAspectRatio="none">
|
<svg viewBox="0 0 4 600" preserveAspectRatio="none">
|
||||||
<line x1="2" y1="0" x2="2" y2="600" stroke="url(#timelineGrad)" strokeWidth="3" strokeDasharray="8 6" className="timeline-svg-line" />
|
<line
|
||||||
|
x1="2"
|
||||||
|
y1="0"
|
||||||
|
x2="2"
|
||||||
|
y2="600"
|
||||||
|
stroke="url(#timelineGrad)"
|
||||||
|
strokeWidth="3"
|
||||||
|
strokeDasharray="8 6"
|
||||||
|
className="timeline-svg-line"
|
||||||
|
/>
|
||||||
<defs>
|
<defs>
|
||||||
<linearGradient id="timelineGrad" x1="0" y1="0" x2="0" y2="1">
|
<linearGradient id="timelineGrad" x1="0" y1="0" x2="0" y2="1">
|
||||||
<stop offset="0%" stopColor="#FF6B6B" /><stop offset="50%" stopColor="#FFE66D" /><stop offset="100%" stopColor="#4ECDC4" />
|
<stop offset="0%" stopColor="#FF6B6B" />
|
||||||
|
<stop offset="50%" stopColor="#FFE66D" />
|
||||||
|
<stop offset="100%" stopColor="#4ECDC4" />
|
||||||
</linearGradient>
|
</linearGradient>
|
||||||
</defs>
|
</defs>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
{ITEMS.map((item, i) => (
|
{items.map((item, i) => (
|
||||||
<div
|
<div
|
||||||
key={item.time}
|
key={item.key}
|
||||||
className={`timeline-item${active === i ? " active" : ""}`}
|
className={`timeline-item${active === i ? " active" : ""}`}
|
||||||
onClick={() => setActive(i)}
|
onClick={() => setActive(i)}
|
||||||
role="button"
|
role="button"
|
||||||
@ -54,21 +72,30 @@ export default function Lifestyle() {
|
|||||||
|
|
||||||
<div className="lifestyle-detail">
|
<div className="lifestyle-detail">
|
||||||
<div className="lifestyle-detail-card">
|
<div className="lifestyle-detail-card">
|
||||||
<span className="lifestyle-detail-icon">{ITEMS[active].icon}</span>
|
<span className="lifestyle-detail-icon">{cur.icon}</span>
|
||||||
<span className="lifestyle-detail-time">{ITEMS[active].time}</span>
|
<span className="lifestyle-detail-time">{cur.time}</span>
|
||||||
<h3>{ITEMS[active].title}</h3>
|
<h3>{cur.title}</h3>
|
||||||
<p>{ITEMS[active].desc}</p>
|
<p>{cur.desc}</p>
|
||||||
<div className="lifestyle-tip">
|
<div className="lifestyle-tip">
|
||||||
<span>💡</span>
|
<span>💡</span>
|
||||||
<p>{ITEMS[active].tip}</p>
|
<p>{cur.tip}</p>
|
||||||
|
</div>
|
||||||
|
<div className="dating-empty-actions" style={{ marginTop: 16 }}>
|
||||||
|
<Link href="/digital/day/1" className="btn btn-primary btn-sm">
|
||||||
|
{L.ctaDay}
|
||||||
|
</Link>
|
||||||
|
<Link href="/meetups" className="btn btn-sm">
|
||||||
|
{L.ctaMeetups}
|
||||||
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<div className="lifestyle-dots">
|
<div className="lifestyle-dots">
|
||||||
{ITEMS.map((_, i) => (
|
{items.map((item, i) => (
|
||||||
<button
|
<button
|
||||||
key={i}
|
key={item.key}
|
||||||
|
type="button"
|
||||||
className={`lifestyle-dot${active === i ? " active" : ""}`}
|
className={`lifestyle-dot${active === i ? " active" : ""}`}
|
||||||
onClick={() => setActive(i)}
|
onClick={() => setActive(i)}
|
||||||
aria-label={`第 ${i + 1} 项`}
|
aria-label={`${i + 1}`}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,13 +1,15 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import { useI18n } from "@/lib/i18n";
|
||||||
import type { StaticPageDef } from "@/lib/staticPages";
|
import type { StaticPageDef } from "@/lib/staticPages";
|
||||||
|
|
||||||
export default function StaticPage({ page }: { page: StaticPageDef }) {
|
export default function StaticPage({ page }: { page: StaticPageDef }) {
|
||||||
|
const { t } = useI18n();
|
||||||
return (
|
return (
|
||||||
<div className="legal-page">
|
<div className="legal-page">
|
||||||
<nav className="detail-nav">
|
<nav className="detail-nav">
|
||||||
<Link href="/">← 返回首页</Link>
|
<Link href="/">← {t.common.backHome}</Link>
|
||||||
</nav>
|
</nav>
|
||||||
<article className="legal-content reveal">
|
<article className="legal-content reveal">
|
||||||
<span className="section-tag">{page.tag}</span>
|
<span className="section-tag">{page.tag}</span>
|
||||||
@ -19,15 +21,19 @@ export default function StaticPage({ page }: { page: StaticPageDef }) {
|
|||||||
{s.body && <p>{s.body}</p>}
|
{s.body && <p>{s.body}</p>}
|
||||||
{s.list && (
|
{s.list && (
|
||||||
<ul className="about-list">
|
<ul className="about-list">
|
||||||
{s.list.map((item) => <li key={item}>{item}</li>)}
|
{s.list.map((item) => (
|
||||||
|
<li key={item}>{item}</li>
|
||||||
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
)}
|
)}
|
||||||
</section>
|
</section>
|
||||||
))}
|
))}
|
||||||
{page.cta && page.cta.length > 0 && (
|
{page.cta && page.cta.length > 0 && (
|
||||||
<div className="about-actions">
|
<div className="about-actions">
|
||||||
{page.cta.map((c) => (
|
{page.cta.map((c, i) => (
|
||||||
<Link key={c.href} href={c.href} className="btn btn-primary">{c.label}</Link>
|
<Link key={c.href} href={c.href} className={`btn${i === 0 ? " btn-primary" : " btn-ghost"}`}>
|
||||||
|
{c.label}
|
||||||
|
</Link>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -147,6 +147,65 @@ export const zh = {
|
|||||||
sortCostDesc: "💰 价格从高到低",
|
sortCostDesc: "💰 价格从高到低",
|
||||||
sortSpeed: "📶 网速排序",
|
sortSpeed: "📶 网速排序",
|
||||||
},
|
},
|
||||||
|
lifestyle: {
|
||||||
|
tag: "💻 NOMAD LIFE",
|
||||||
|
title: "数字游民的一天",
|
||||||
|
subtitle: "自由不等于散漫,高效工作才能尽情探索 — 点击卡片查看详情",
|
||||||
|
ctaDay: "七天落地指南",
|
||||||
|
ctaMeetups: "去参加活动",
|
||||||
|
time_morning: "🌅 07:00",
|
||||||
|
icon_morning: "🧘",
|
||||||
|
title_morning: "晨间仪式",
|
||||||
|
desc_morning: "瑜伽、冥想或海边跑步,用身心唤醒开启新的一天",
|
||||||
|
tip_morning: "推荐:清迈河边晨跑、巴厘岛海滩瑜伽",
|
||||||
|
time_work: "☕ 09:00",
|
||||||
|
icon_work: "💻",
|
||||||
|
title_work: "深度工作",
|
||||||
|
desc_work: "在 Co-working Space 或咖啡馆专注 4 小时,完成核心任务",
|
||||||
|
tip_work: "推荐:Punspace 清迈、Outsite 里斯本",
|
||||||
|
time_explore: "🍜 13:00",
|
||||||
|
icon_explore: "🍽️",
|
||||||
|
title_explore: "本地探索",
|
||||||
|
desc_explore: "品尝地道美食,与当地人交流,感受文化碰撞",
|
||||||
|
tip_explore: "推荐:清迈夜市、巴塞罗那 Tapas 之旅",
|
||||||
|
time_collab: "🌇 17:00",
|
||||||
|
icon_collab: "📞",
|
||||||
|
title_collab: "跨时区协作",
|
||||||
|
desc_collab: "与全球团队视频会议,灵活安排跨时区沟通",
|
||||||
|
tip_collab: "工具:Calendly 排时区、World Time Buddy",
|
||||||
|
time_social: "🌙 20:00",
|
||||||
|
icon_social: "🎉",
|
||||||
|
title_social: "社群社交",
|
||||||
|
desc_social: "参加 Nomad Meetup,结识来自世界各地的同行者",
|
||||||
|
tip_social: "平台:nomadro 活动、同城社群",
|
||||||
|
},
|
||||||
|
homeCommunity: {
|
||||||
|
tag: "🤝 COMMUNITY",
|
||||||
|
title: "游民心声",
|
||||||
|
subtitle: "来自全球数字游民的真实故事",
|
||||||
|
ctaTitle: "准备好开始你的旅居之旅了吗?",
|
||||||
|
ctaDesc: "订阅周报,或直接走进社区与活动",
|
||||||
|
},
|
||||||
|
aboutPage: {
|
||||||
|
tag: "🌏 ABOUT",
|
||||||
|
title: "关于 nomadro",
|
||||||
|
subtitle: "用一行代码环游世界",
|
||||||
|
whoTitle: "我们是谁",
|
||||||
|
whoBody:
|
||||||
|
"nomadro 是一个面向数字游民的旅居决策平台。我们把目的地数据、签证信息、费用估算、时区协作与社区经验集中在一起,帮你更快做出「下一站去哪」的决定。",
|
||||||
|
whatTitle: "能做什么",
|
||||||
|
what1: "🗺️ 探索全球热门游民城市与对比工具",
|
||||||
|
what2: "🧮 计算生活成本、机票与启动金目标",
|
||||||
|
what3: "📋 签证匹配、保险与上网方案参考",
|
||||||
|
what4: "🗓️ 规划多城行程并分享给伙伴",
|
||||||
|
what5: "🗣️ 落地短语、行李清单与活动日历",
|
||||||
|
contactTitle: "联系我们",
|
||||||
|
contactBody: "合作与反馈:",
|
||||||
|
repoBody: "开源仓库:",
|
||||||
|
ctaExplore: "智能下一站",
|
||||||
|
ctaCommunity: "加入社区",
|
||||||
|
ctaPlan: "打开计划",
|
||||||
|
},
|
||||||
common: {
|
common: {
|
||||||
backHome: "← 返回首页",
|
backHome: "← 返回首页",
|
||||||
browseDest: "浏览目的地",
|
browseDest: "浏览目的地",
|
||||||
@ -1483,6 +1542,65 @@ export const en: { [K in keyof typeof zh]: typeof zh[K] extends string ? string
|
|||||||
sortCostDesc: "💰 Cost high → low",
|
sortCostDesc: "💰 Cost high → low",
|
||||||
sortSpeed: "📶 Wi‑Fi speed",
|
sortSpeed: "📶 Wi‑Fi speed",
|
||||||
},
|
},
|
||||||
|
lifestyle: {
|
||||||
|
tag: "💻 NOMAD LIFE",
|
||||||
|
title: "A day in nomad life",
|
||||||
|
subtitle: "Freedom still needs rhythm — tap a card for detail",
|
||||||
|
ctaDay: "7-day landing guide",
|
||||||
|
ctaMeetups: "Find meetups",
|
||||||
|
time_morning: "🌅 07:00",
|
||||||
|
icon_morning: "🧘",
|
||||||
|
title_morning: "Morning ritual",
|
||||||
|
desc_morning: "Yoga, meditation, or a run by the water to wake body and mind",
|
||||||
|
tip_morning: "Try: riverside run in Chiang Mai, beach yoga in Bali",
|
||||||
|
time_work: "☕ 09:00",
|
||||||
|
icon_work: "💻",
|
||||||
|
title_work: "Deep work",
|
||||||
|
desc_work: "Four focused hours in a coworking space or café for core tasks",
|
||||||
|
tip_work: "Try: Punspace Chiang Mai, Outsite Lisbon",
|
||||||
|
time_explore: "🍜 13:00",
|
||||||
|
icon_explore: "🍽️",
|
||||||
|
title_explore: "Local explore",
|
||||||
|
desc_explore: "Eat local food, talk to people, feel the culture shift",
|
||||||
|
tip_explore: "Try: Chiang Mai night market, Barcelona tapas walk",
|
||||||
|
time_collab: "🌇 17:00",
|
||||||
|
icon_collab: "📞",
|
||||||
|
title_collab: "Cross-timezone sync",
|
||||||
|
desc_collab: "Video with a global team — schedule around the clock",
|
||||||
|
tip_collab: "Tools: Calendly time zones, World Time Buddy",
|
||||||
|
time_social: "🌙 20:00",
|
||||||
|
icon_social: "🎉",
|
||||||
|
title_social: "Community night",
|
||||||
|
desc_social: "Join a nomad meetup and meet people from everywhere",
|
||||||
|
tip_social: "Platforms: nomadro events, local groups",
|
||||||
|
},
|
||||||
|
homeCommunity: {
|
||||||
|
tag: "🤝 COMMUNITY",
|
||||||
|
title: "Nomad voices",
|
||||||
|
subtitle: "Real stories from digital nomads worldwide",
|
||||||
|
ctaTitle: "Ready for your next move?",
|
||||||
|
ctaDesc: "Subscribe to the weekly brief, or jump into community and events",
|
||||||
|
},
|
||||||
|
aboutPage: {
|
||||||
|
tag: "🌏 ABOUT",
|
||||||
|
title: "About nomadro",
|
||||||
|
subtitle: "Travel the world with one line of code",
|
||||||
|
whoTitle: "Who we are",
|
||||||
|
whoBody:
|
||||||
|
"nomadro is a move-decision platform for digital nomads. We bring destination data, visas, cost estimates, timezone collaboration, and community experience into one place so you can decide where to go next faster.",
|
||||||
|
whatTitle: "What you can do",
|
||||||
|
what1: "🗺️ Explore top nomad cities and comparison tools",
|
||||||
|
what2: "🧮 Estimate living costs, flights, and startup runway",
|
||||||
|
what3: "📋 Visa matching, insurance, and connectivity references",
|
||||||
|
what4: "🗓️ Plan multi-city trips and share with partners",
|
||||||
|
what5: "🗣️ Landing phrases, packing lists, and event calendars",
|
||||||
|
contactTitle: "Contact",
|
||||||
|
contactBody: "Partnerships & feedback:",
|
||||||
|
repoBody: "Open source:",
|
||||||
|
ctaExplore: "Smart next stop",
|
||||||
|
ctaCommunity: "Join community",
|
||||||
|
ctaPlan: "Open plan",
|
||||||
|
},
|
||||||
common: {
|
common: {
|
||||||
backHome: "← Back home",
|
backHome: "← Back home",
|
||||||
browseDest: "Destinations",
|
browseDest: "Destinations",
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user