diff --git a/public/city/city-hero.png b/public/city/city-hero.png new file mode 100644 index 0000000..3d61151 Binary files /dev/null and b/public/city/city-hero.png differ diff --git a/public/pages/about-hero.png b/public/pages/about-hero.png new file mode 100644 index 0000000..1e950a9 Binary files /dev/null and b/public/pages/about-hero.png differ diff --git a/public/pages/about-intro-overlay.png b/public/pages/about-intro-overlay.png new file mode 100644 index 0000000..8279e90 Binary files /dev/null and b/public/pages/about-intro-overlay.png differ diff --git a/public/pages/join-hero.png b/public/pages/join-hero.png new file mode 100644 index 0000000..5f7277a Binary files /dev/null and b/public/pages/join-hero.png differ diff --git a/public/pages/railway-hero.png b/public/pages/railway-hero.png new file mode 100644 index 0000000..f8949e5 Binary files /dev/null and b/public/pages/railway-hero.png differ diff --git a/src/app/[locale]/about/page.tsx b/src/app/[locale]/about/page.tsx new file mode 100644 index 0000000..083544e --- /dev/null +++ b/src/app/[locale]/about/page.tsx @@ -0,0 +1,15 @@ +import { notFound } from "next/navigation"; +import { AboutPage } from "@/components/about/about-page"; +import { getLocaleMessages, isLocale } from "@/lib/i18n"; + +export async function generateMetadata({ params }: { params: Promise<{ locale: string }> }) { + const { locale } = await params; + if (!isLocale(locale)) notFound(); + return { title: getLocaleMessages(locale).footer.columns[1].links[0] }; +} + +export default async function AboutRoute({ params }: { params: Promise<{ locale: string }> }) { + const { locale } = await params; + if (!isLocale(locale)) notFound(); + return ; +} diff --git a/src/app/[locale]/city/page.tsx b/src/app/[locale]/city/page.tsx new file mode 100644 index 0000000..2527831 --- /dev/null +++ b/src/app/[locale]/city/page.tsx @@ -0,0 +1,15 @@ +import { notFound } from "next/navigation"; +import { SimpleTitlePage } from "@/components/pages/simple-title-page"; +import { getLocaleMessages, isLocale } from "@/lib/i18n"; + +export async function generateMetadata({ params }: { params: Promise<{ locale: string }> }) { + const { locale } = await params; + if (!isLocale(locale)) notFound(); + return { title: getLocaleMessages(locale).header.nav[3] }; +} + +export default async function CityPage({ params }: { params: Promise<{ locale: string }> }) { + const { locale } = await params; + if (!isLocale(locale)) notFound(); + return ; +} diff --git a/src/app/[locale]/join-us/page.tsx b/src/app/[locale]/join-us/page.tsx new file mode 100644 index 0000000..afb08e2 --- /dev/null +++ b/src/app/[locale]/join-us/page.tsx @@ -0,0 +1,16 @@ +import { notFound } from "next/navigation"; +import { SimpleTitlePage } from "@/components/pages/simple-title-page"; +import { getLocaleMessages, isLocale } from "@/lib/i18n"; + +export async function generateMetadata({ params }: { params: Promise<{ locale: string }> }) { + const { locale } = await params; + if (!isLocale(locale)) notFound(); + return { title: getLocaleMessages(locale).footer.columns[1].links[2] }; +} + +export default async function JoinUsPage({ params }: { params: Promise<{ locale: string }> }) { + const { locale } = await params; + if (!isLocale(locale)) notFound(); + const messages = getLocaleMessages(locale); + return ; +} diff --git a/src/app/[locale]/policy/[slug]/page.tsx b/src/app/[locale]/policy/[slug]/page.tsx index 3f1cc3f..1da78dd 100644 --- a/src/app/[locale]/policy/[slug]/page.tsx +++ b/src/app/[locale]/policy/[slug]/page.tsx @@ -22,5 +22,5 @@ export default async function PolicyPage({ params }: { params: Promise<{ slug: s if (!isLocale(locale)) notFound(); const catalog = await getPolicyCatalog(slug); if (!catalog["zh-Hans"]) notFound(); - return
; + return ; } diff --git a/src/app/[locale]/railway/page.tsx b/src/app/[locale]/railway/page.tsx new file mode 100644 index 0000000..e9cce7e --- /dev/null +++ b/src/app/[locale]/railway/page.tsx @@ -0,0 +1,15 @@ +import { notFound } from "next/navigation"; +import { SimpleTitlePage } from "@/components/pages/simple-title-page"; +import { getLocaleMessages, isLocale } from "@/lib/i18n"; + +export async function generateMetadata({ params }: { params: Promise<{ locale: string }> }) { + const { locale } = await params; + if (!isLocale(locale)) notFound(); + return { title: getLocaleMessages(locale).header.nav[2] }; +} + +export default async function RailwayPage({ params }: { params: Promise<{ locale: string }> }) { + const { locale } = await params; + if (!isLocale(locale)) notFound(); + return ; +} diff --git a/src/app/globals.css b/src/app/globals.css index 23ed97e..ea238b4 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -74,19 +74,29 @@ a { color: inherit; text-decoration: none; } .home-collage-small { top: 376px; left: 27.28%; width: 44.3%; height: 272px; background-image: url("/home/collage-3.png"); } .home-collage-bottom { top: 376px; left: 0; width: 25.83%; height: 272px; background-image: url("/home/collage-4.png"); } .home-collage-tall { top: 0; right: 0; width: 27.1%; height: 648px; background-image: url("/home/collage-5.png"); } -.home-actions { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 50px; margin-top: 100px; padding-bottom: 121px; } -.home-action-card { display: block; min-width: 0; color: var(--ink); } -.home-action-image { height: 323px; border-radius: 13px; background: center / cover no-repeat; transition: transform .2s ease, box-shadow .2s ease; } -.home-action-image--about { background-image: url("/home/about-card.png"); } -.home-action-image--join { background-image: url("/home/join-card.png"); } -.home-action-card > span { display: flex; align-items: center; gap: 20px; margin-top: 42px; font-size: 42px; font-weight: 700; line-height: 1.2; letter-spacing: -.04em; } -.home-action-card b { color: var(--blue); font-family: Arial, sans-serif; font-size: 36px; font-weight: 400; line-height: 1; } -.home-action-card:hover .home-action-image, .home-action-card:focus-visible .home-action-image { box-shadow: 0 10px 25px rgba(15, 23, 42, .16); transform: translateY(-3px); } +.page-action-cards { display: grid; width: min(1200px, calc(100% - 32px)); grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 50px; margin-right: auto; margin-left: auto; padding-bottom: 121px; } +.home-actions { margin-top: 100px; } +.page-action-card { display: block; min-width: 0; color: var(--ink); } +.page-action-image { height: 323px; border-radius: 13px; background-position: center; background-size: cover; background-repeat: no-repeat; transition: transform .2s ease, box-shadow .2s ease; } +.page-action-card > span { display: flex; align-items: center; gap: 20px; margin-top: 42px; font-size: 42px; font-weight: 700; line-height: 1.2; letter-spacing: -.04em; } +.page-action-card b { color: var(--blue); font-family: Arial, sans-serif; font-size: 36px; font-weight: 400; line-height: 1; } +.page-action-card:hover .page-action-image, .page-action-card:focus-visible .page-action-image { box-shadow: 0 10px 25px rgba(15, 23, 42, .16); transform: translateY(-3px); } + +.title-page { padding-bottom: 48px; } +.page-title-hero { position: relative; display: flex; width: min(1200px, calc(100% - 32px)); height: 414px; align-items: flex-start; margin: 0 auto; padding-top: 156px; } +.page-title-hero-media { position: absolute; inset: 0; overflow: hidden; border-radius: 10px; background-image: var(--page-title-hero-image); background-position: var(--page-title-hero-position); background-size: cover; background-repeat: no-repeat; } +.page-title-hero-label { position: relative; z-index: 1; margin-left: -44px; padding: 10px 46px; background: #8f56a2; color: #fff; } +.page-title-hero-label h1 { margin: 0; font-size: 62px; font-weight: 700; line-height: 80.4px; letter-spacing: .25px; } +.page-location { display: flex; width: min(1200px, calc(100% - 40px)); height: 88.8px; align-items: center; margin: 0 auto; border-top: .8px solid #d9d9d9; } +.about-introduction { display: grid; width: min(1200px, calc(100% - 32px)); min-height: 512px; grid-template-columns: repeat(2, minmax(0, 1fr)); margin: 50px auto 0; overflow: hidden; border-radius: 10px; } +.about-introduction-copy { display: flex; flex-direction: column; justify-content: center; padding: 64px 80px; background: #535360; color: #fff; } +.about-introduction-copy p { align-self: flex-start; margin: 0 0 39px; padding: 4px 10px; background: #1267d8; color: #fff; font-size: 18px; font-weight: 700; line-height: 28px; } +.about-introduction-copy h2 { margin: 0; font-size: 40px; font-weight: 700; line-height: 1.28; letter-spacing: -.04em; } +.about-introduction-image { min-height: 512px; background: url("/pages/about-intro-overlay.png") center / cover no-repeat; } +.about-action-cards { margin-top: 50px; } .news-page { padding: 30px 0 60px; } -.friendly-links-hero { background: var(--policy-hero-image) center 28.2% / 100% auto no-repeat; } -.friendly-links-page-inner { gap: 50px; } -.friendly-links-content { width: min(1214px, 100%); margin: 0 auto; } +.friendly-links-content { width: min(1200px, calc(100% - 32px)); margin: 50px auto 0; } .friendly-links-section + .friendly-links-section { margin-top: 50px; } .friendly-links-section h2 { margin: 0 0 20px; color: #181c20; font-size: 40px; font-weight: 700; line-height: 48px; } .friendly-links-section h2::after { display: block; width: 28.875px; height: 5.25px; margin-top: 17.5px; background: #8f56a2; content: ""; } @@ -99,10 +109,7 @@ a.friendly-link-card:hover, a.friendly-link-card:focus-visible { border-color: # .friendly-link-card-logo img { display: block; width: 65.67px; height: 65.67px; object-fit: cover; } .friendly-link-card-description { color: #8d949a; font-size: 16.42px; line-height: 21.89px; text-align: center; } .friendly-link-card-archive { position: absolute; top: 13px; right: 5px; padding: 4px 6px; border-radius: 4px; background: #e72329; color: #fff; font-size: 10px; line-height: 12px; } -.friendly-links-page .page-bottom-nav { margin-top: 176px; } -.members-page-inner { gap: 50px; } -.members-hero { background: var(--policy-hero-image) center 47% / 100% auto no-repeat; } -.members-sections { width: min(1200px, 100%); } +.members-sections { width: min(1200px, calc(100% - 32px)); margin: 50px auto 0; } .member-section + .member-section { margin-top: 46px; } .member-section-heading { margin: 0 0 20px; } .member-section-heading h2 { margin: 0; color: #202326; font-size: 30px; font-weight: 700; line-height: 1.35; letter-spacing: -.03em; } @@ -117,7 +124,6 @@ a.member-card:hover, a.member-card:focus-visible { opacity: .82; transform: tran .member-card-copy h3 { margin: 0; font-size: 20px; font-weight: 700; line-height: 1.4; letter-spacing: -.03em; } .member-card-copy p { margin: 4px 0 0; color: #5f6464; font-size: 14px; line-height: 1.45; } .member-empty { margin: 0; padding: 40px 0; color: #697684; font-size: 16px; } -.members-page .page-bottom-nav { margin-top: 80px; } .news-page-inner { max-width: 1200px; margin: 0 auto; padding: 0 24px; } .news-page-header { display: flex; justify-content: center; align-items: center; margin: 0 0 30px; } .news-page-header h1 { margin: 0; font-size: clamp(52px, 5vw, 88px); letter-spacing: -0.07em; line-height: 1; font-weight: 800; } @@ -165,12 +171,8 @@ a.member-card:hover, a.member-card:focus-visible { opacity: .82; transform: tran .news-detail-body li { margin: 8px 0; } .news-detail-body a, .policy-body a { color: #1267d8; text-decoration: underline; text-underline-offset: 3px; } .news-detail-body a:hover, .policy-body a:hover { color: #8f56a2; } -.policy-page { padding: 0 0 48px; background: #fff; } -.policy-page-inner { display: flex; flex-direction: column; gap: 50px; align-items: center; padding: 0 40px 48px; } -.policy-hero { width: min(1200px, 100%); min-height: 322px; display: flex; align-items: flex-start; padding: 156px 48px 0; border-radius: 10px; background: linear-gradient(110deg, rgba(143, 86, 162, 0.82), rgba(143, 86, 162, 0.18)), var(--policy-hero-image, url("/policy-hero.png")) center / cover; overflow: hidden; } -.policy-hero-inner { margin-left: -48px; padding: 10px 46px; background: #8f56a2; } -.policy-hero h1 { margin: 0; color: #fff; font-size: clamp(38px, 4vw, 62px); line-height: 1.3; letter-spacing: 0.25px; } -.policy-card { width: min(1200px, 100%); padding: 28px; border-radius: 26px; background: #fefdfa; box-shadow: 0 10px 15px rgba(15, 23, 42, 0.04); } +.policy-page { background: var(--paper); } +.policy-card { width: min(1200px, calc(100% - 32px)); margin: 50px auto 0; padding: 28px; border-radius: 26px; background: #fefdfa; box-shadow: 0 10px 15px rgba(15, 23, 42, 0.04); } .policy-ai-notice { display: inline-flex; margin: 0 0 26px; padding: 6px 12px; border-radius: 999px; background: rgba(255, 179, 15, 0.1); color: #ff8f0f; font-size: 13px; line-height: 1.5; } .policy-body { color: #1d2732; font-size: 17px; line-height: 1.9; } .policy-body h1, .policy-body h2, .policy-body h3 { color: #8f56a2; font-weight: 400; } @@ -263,6 +265,8 @@ html[data-theme="dark"] .news-detail-body, html[data-theme="dark"] .policy-body html[data-theme="dark"] .policy-page { background: var(--paper); } html[data-theme="dark"] .policy-card { background: #19222d; box-shadow: 0 10px 15px rgba(0, 0, 0, 0.2); } html[data-theme="dark"] .page-bottom-nav { border-color: #394553; } +html[data-theme="dark"] .page-location { border-color: #394553; } +html[data-theme="dark"] .about-introduction-copy { background: #1a222d; } html[data-theme="dark"] .page-breadcrumb, html[data-theme="dark"] .page-breadcrumb-separator, html[data-theme="dark"] .page-breadcrumb a { color: #cbd5df; } html[data-theme="dark"] .news-share { border-color: rgba(255, 255, 255, 0.14); } html[data-theme="dark"] .status-code, html[data-theme="dark"] .status-content h1 { color: var(--ink); } @@ -307,6 +311,8 @@ html[data-theme="dark"] .member-card-copy p, html[data-theme="dark"] .member-emp html:not([data-theme]) .policy-page { background: var(--paper); } html:not([data-theme]) .policy-card { background: #19222d; box-shadow: 0 10px 15px rgba(0, 0, 0, 0.2); } html:not([data-theme]) .page-bottom-nav { border-color: #394553; } + html:not([data-theme]) .page-location { border-color: #394553; } + html:not([data-theme]) .about-introduction-copy { background: #1a222d; } html:not([data-theme]) .page-breadcrumb, html:not([data-theme]) .page-breadcrumb-separator, html:not([data-theme]) .page-breadcrumb a { color: #cbd5df; } html:not([data-theme]) .news-share { border-color: rgba(255, 255, 255, 0.14); } html:not([data-theme]) .status-code, html:not([data-theme]) .status-content h1 { color: var(--ink); } @@ -364,18 +370,25 @@ html[data-theme="dark"] .member-card-copy p, html[data-theme="dark"] .member-emp .home-collage-small { top: 202px; left: 39%; width: 61%; height: 158px; } .home-collage-tall { display: none; } .home-news { margin-top: 48px; } - .home-actions { width: calc(100% - 32px); grid-template-columns: 1fr; gap: 42px; margin-top: 48px; padding-bottom: 48px; } - .home-action-image { height: auto; aspect-ratio: 575 / 323; } - .home-action-card > span { margin-top: 24px; font-size: clamp(32px, 7vw, 42px); } + .page-action-cards { width: calc(100% - 32px); grid-template-columns: 1fr; gap: 42px; padding-bottom: 48px; } + .home-actions, .about-action-cards { margin-top: 48px; } + .page-action-image { height: auto; aspect-ratio: 575 / 323; } + .page-action-card > span { margin-top: 24px; font-size: clamp(32px, 7vw, 42px); } + .title-page { padding-bottom: 32px; } + .page-title-hero { width: calc(100% - 32px); height: clamp(300px, 46vw, 414px); padding-top: clamp(104px, 17.3vw, 156px); } + .page-title-hero-label { margin-left: -20px; padding: 8px 32px; } + .page-title-hero-label h1 { font-size: clamp(40px, 6.88vw, 62px); line-height: 1.3; } + .page-location { width: calc(100% - 32px); min-height: 80px; height: auto; padding: 24px 0; } + .about-introduction { width: calc(100% - 32px); min-height: 420px; margin-top: 48px; } + .about-introduction-copy { padding: 48px; } + .about-introduction-copy h2 { font-size: clamp(32px, 4.45vw, 40px); } + .about-introduction-image { min-height: 420px; } .news-page-inner { padding: 0 16px; } .friendly-links-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } .news-page-header h1 { font-size: clamp(46px, 12vw, 72px); } .news-toolbar { align-items: flex-start; flex-wrap: wrap; gap: 16px; } .news-toolbar-right { margin-left: auto; } - .policy-page-inner { gap: 28px; padding: 0 16px 32px; } - .policy-hero { min-height: 220px; padding: 90px 28px 0; } - .policy-hero-inner { margin-left: -28px; padding: 8px 28px; } - .policy-card { padding: 20px; border-radius: 20px; } + .policy-card { width: calc(100% - 32px); margin-top: 48px; padding: 20px; border-radius: 20px; } .policy-body { font-size: 16px; } .policy-body h1 { font-size: 38px; } .policy-body h2 { margin-top: 32px; font-size: 36px; } @@ -433,12 +446,23 @@ html[data-theme="dark"] .member-card-copy p, html[data-theme="dark"] .member-emp .home-collage { width: calc(100% - 24px); height: 280px; margin-top: 36px; } .home-collage-wide, .home-collage-medium { height: 148px; } .home-collage-bottom, .home-collage-small { top: 160px; height: 120px; } - .home-actions { width: calc(100% - 24px); gap: 32px; margin-top: 36px; padding-bottom: 24px; } - .home-action-card > span { gap: 14px; margin-top: 18px; font-size: 30px; } - .home-action-card b { font-size: 30px; } + .page-action-cards { width: calc(100% - 24px); gap: 32px; padding-bottom: 24px; } + .home-actions, .about-action-cards { margin-top: 36px; } + .page-action-card > span { gap: 14px; margin-top: 18px; font-size: 30px; } + .page-action-card b { font-size: 30px; } + .title-page { padding-bottom: 24px; } + .page-title-hero { width: calc(100% - 24px); height: 290px; padding-top: 96px; } + .page-title-hero-label { margin-left: -12px; padding: 8px 26px; } + .page-title-hero-label h1 { font-size: clamp(34px, 10vw, 42px); line-height: 1.3; } + .page-location { width: calc(100% - 24px); min-height: 72px; padding: 20px 0; } + .about-introduction { width: calc(100% - 24px); grid-template-columns: 1fr; min-height: 0; margin-top: 36px; } + .about-introduction-copy { min-height: 280px; padding: 42px 28px; } + .about-introduction-copy p { margin-bottom: 24px; } + .about-introduction-copy h2 { font-size: clamp(30px, 8.5vw, 36px); } + .about-introduction-image { min-height: 280px; } .news-page { padding-top: 20px; } - .friendly-links-content { width: 100%; } + .friendly-links-content, .members-sections { width: calc(100% - 24px); margin-top: 36px; } .friendly-links-section + .friendly-links-section { margin-top: 36px; } .friendly-links-section h2 { margin-bottom: 16px; font-size: 30px; line-height: 1.35; } .friendly-links-grid { grid-template-columns: 1fr; gap: 12px; } @@ -474,11 +498,7 @@ html[data-theme="dark"] .member-card-copy p, html[data-theme="dark"] .member-emp .status-content h1 { overflow-wrap: anywhere; } .status-back-button { font-size: 24px; } - .policy-page-inner { gap: 20px; padding: 0 12px 24px; } - .policy-hero { min-height: 170px; padding: 64px 20px 0; border-radius: 8px; } - .policy-hero-inner { margin-left: -20px; padding: 7px 20px; max-width: 100%; } - .policy-hero h1 { font-size: 34px; overflow-wrap: anywhere; } - .policy-card { padding: 16px; border-radius: 18px; } + .policy-card { width: calc(100% - 24px); margin-top: 36px; padding: 16px; border-radius: 18px; } .policy-body { font-size: 16px; line-height: 1.8; overflow-wrap: anywhere; } .policy-body h1 { font-size: 34px; } .policy-body h2 { margin: 30px 0 18px; font-size: 34px; } diff --git a/src/components/about/about-page.tsx b/src/components/about/about-page.tsx new file mode 100644 index 0000000..0510312 --- /dev/null +++ b/src/components/about/about-page.tsx @@ -0,0 +1,47 @@ +import { PageActionCards } from "@/components/layout/page-action-cards"; +import { PageLocation } from "@/components/layout/page-location"; +import { PageTitleHero } from "@/components/layout/page-title-hero"; +import { getLocaleMessages, type Locale } from "@/lib/i18n"; + +const introduction: Record = { + "zh-Hans": { label: "小简介", title: ["铁道+建筑+生存", "一个以高兴就好的", "小服务器"] }, + "zh-Hant": { label: "小簡介", title: ["鐵道+建築+生存", "一個以開心為主的", "小伺服器"] }, + en: { label: "A short introduction", title: ["Railways, building", "and survival — a small", "server built for joy."] }, + jp: { label: "紹介", title: ["鉄道・建築・サバイバル", "楽しさを大切にする", "小さなサーバーです"] }, +}; + +export function AboutPage({ locale }: { locale: Locale }) { + const messages = getLocaleMessages(locale); + const title = messages.footer.columns[1].links[0]; + const copy = introduction[locale]; + + return ( + <> +
+ + +
+
+

{copy.label}

+

{copy.title[0]}
{copy.title[1]}
{copy.title[2]}

+
+
+ + +
+ + + ); +} diff --git a/src/components/home/home-page-content.tsx b/src/components/home/home-page-content.tsx index 888eeb6..86c3fcc 100644 --- a/src/components/home/home-page-content.tsx +++ b/src/components/home/home-page-content.tsx @@ -1,6 +1,7 @@ "use client"; import { useRouteLocale } from "@/components/layout/locale-context"; +import { PageActionCards } from "@/components/layout/page-action-cards"; import Link from "next/link"; import { useEffect, useState } from "react"; @@ -10,32 +11,22 @@ import { applyLocale, getInitialLocale, withLocale, getLocaleMessages, type Loca const homeFeatureCopy: Record = { "zh-Hans": { railway: { title: ["从点", "到网"], description: ["从一个车站扩展到线网", "铁路承载了根基"] }, - city: { title: ["从地面", "到天空"], description: ["城建蓬勃发展", "自由盛放"] }, - about: "关于我们", - join: "加入我们", + city: { title: ["从地面", "到天空"], description: ["城市蓬勃发展", "自由盛放"] }, }, "zh-Hant": { railway: { title: ["從點", "到網"], description: ["從一個車站擴展至路網", "鐵路承載了根基"] }, - city: { title: ["從地面", "到天空"], description: ["城市建設蓬勃發展", "自由綻放"] }, - about: "關於我們", - join: "加入我們", + city: { title: ["從地面", "到天空"], description: ["城市蓬勃發展", "自由綻放"] }, }, en: { railway: { title: ["From a stop", "to a network"], description: ["From one station to a network.", "Railways carry the foundation."] }, city: { title: ["From ground", "to sky"], description: ["Cities thrive and grow.", "Ideas bloom freely."] }, - about: "About us", - join: "Join us", }, jp: { railway: { title: ["一点から", "ネットワークへ"], description: ["ひとつの駅から路線網へ。", "鉄道がその基盤を支えます。"] }, city: { title: ["地上から", "空へ"], description: ["都市は大きく発展し、", "自由に花開きます。"] }, - about: "私たちについて", - join: "参加する", }, }; @@ -137,16 +128,15 @@ export function HomePageContent({ newsByLocale }: { newsByLocale: Record -
- -
+ ); } diff --git a/src/components/layout/page-action-cards.tsx b/src/components/layout/page-action-cards.tsx new file mode 100644 index 0000000..62098d2 --- /dev/null +++ b/src/components/layout/page-action-cards.tsx @@ -0,0 +1,31 @@ +import Link from "next/link"; +import { withLocale, type Locale } from "@/lib/i18n"; + +export type PageActionCard = { + href: string; + image: string; + label: string; +}; + +export function PageActionCards({ + locale, + label, + items, + className = "", +}: { + locale: Locale; + label: string; + items: PageActionCard[]; + className?: string; +}) { + return ( +
+ {items.map((item) => ( + +
+ ); +} diff --git a/src/components/layout/page-location.tsx b/src/components/layout/page-location.tsx new file mode 100644 index 0000000..f0df425 --- /dev/null +++ b/src/components/layout/page-location.tsx @@ -0,0 +1,11 @@ +import { PageBreadcrumb } from "@/components/layout/page-breadcrumb"; + +export function PageLocation({ + items, + ariaLabel, +}: { + items: Array<{ label: string; href?: string }>; + ariaLabel: string; +}) { + return
; +} diff --git a/src/components/layout/page-title-hero.tsx b/src/components/layout/page-title-hero.tsx new file mode 100644 index 0000000..8577e2a --- /dev/null +++ b/src/components/layout/page-title-hero.tsx @@ -0,0 +1,23 @@ +import type { CSSProperties } from "react"; + +export function PageTitleHero({ + title, + image, + imagePosition = "center", +}: { + title: string; + image: string; + imagePosition?: string; +}) { + const mediaStyle = { + "--page-title-hero-image": `url("${image}")`, + "--page-title-hero-position": imagePosition, + } as CSSProperties; + + return ( +
+
+ ); +} diff --git a/src/components/links/friendly-links-page.tsx b/src/components/links/friendly-links-page.tsx index 84a00a0..c22cc61 100644 --- a/src/components/links/friendly-links-page.tsx +++ b/src/components/links/friendly-links-page.tsx @@ -1,6 +1,6 @@ import links from "@/data/links.json"; -import type { CSSProperties } from "react"; -import { PageBreadcrumb } from "@/components/layout/page-breadcrumb"; +import { PageLocation } from "@/components/layout/page-location"; +import { PageTitleHero } from "@/components/layout/page-title-hero"; import { getLocaleMessages, type Locale } from "@/lib/i18n"; type LocalizedText = Partial>; @@ -48,15 +48,12 @@ function FriendlyLinkCard({ link, locale, visitLabel }: { link: LinkEntry; local export function FriendlyLinksPage({ locale }: { locale: Locale }) { const messages = getLocaleMessages(locale); - const heroStyle = { "--policy-hero-image": 'url("/links/friendly-links-hero.png")' } as CSSProperties; const visitLabels: Record = { "zh-Hans": "访问", "zh-Hant": "造訪", en: "Visit", jp: "訪問" }; return ( -
-
-
-

{messages.footer.legal[2]}

-
+ <> +
+
{linkSections.map((section) => (
@@ -67,8 +64,8 @@ export function FriendlyLinksPage({ locale }: { locale: Locale }) {
))}
-
-
-
+ + + ); } diff --git a/src/components/members/members-page.tsx b/src/components/members/members-page.tsx index 69e0402..a078d99 100644 --- a/src/components/members/members-page.tsx +++ b/src/components/members/members-page.tsx @@ -1,6 +1,6 @@ import members from "@/data/members.json"; -import type { CSSProperties } from "react"; -import { PageBreadcrumb } from "@/components/layout/page-breadcrumb"; +import { PageLocation } from "@/components/layout/page-location"; +import { PageTitleHero } from "@/components/layout/page-title-hero"; import { getLocaleMessages, type Locale } from "@/lib/i18n"; type LocalizedText = Record; @@ -43,14 +43,11 @@ function MemberCard({ member, locale }: { member: MemberEntry; locale: Locale }) export function MembersPage({ locale }: { locale: Locale }) { const messages = getLocaleMessages(locale); const pageTitle = memberData.title[locale]; - const heroStyle = { "--policy-hero-image": 'url("/members/members-hero.png")' } as CSSProperties; return ( -
-
-
-

{pageTitle}

-
+ <> +
+
{memberData.sections.map((section) => (
@@ -63,8 +60,11 @@ export function MembersPage({ locale }: { locale: Locale }) {
))}
-
-
-
+ + + ); } diff --git a/src/components/pages/simple-title-page.tsx b/src/components/pages/simple-title-page.tsx new file mode 100644 index 0000000..2d144f9 --- /dev/null +++ b/src/components/pages/simple-title-page.tsx @@ -0,0 +1,24 @@ +import { PageLocation } from "@/components/layout/page-location"; +import { PageTitleHero } from "@/components/layout/page-title-hero"; +import { getLocaleMessages, type Locale } from "@/lib/i18n"; + +export function SimpleTitlePage({ + locale, + title, + image, + imagePosition, +}: { + locale: Locale; + title: string; + image: string; + imagePosition?: string; +}) { + const messages = getLocaleMessages(locale); + + return ( + <> +
+ + + ); +} diff --git a/src/components/policy/policy-page-content.tsx b/src/components/policy/policy-page-content.tsx index 9afe7d4..b667191 100644 --- a/src/components/policy/policy-page-content.tsx +++ b/src/components/policy/policy-page-content.tsx @@ -5,7 +5,8 @@ import { useRouteLocale } from "@/components/layout/locale-context"; import { useEffect, useState } from "react"; import { getAiTranslationNotice, getInitialLocale, getLocaleMessages, type Locale } from "@/lib/i18n"; import type { PolicyDocument } from "@/data/policy"; -import { PageBreadcrumb } from "@/components/layout/page-breadcrumb"; +import { PageLocation } from "@/components/layout/page-location"; +import { PageTitleHero } from "@/components/layout/page-title-hero"; type PolicyCatalog = Partial>; @@ -30,31 +31,25 @@ export function PolicyPageContent({ catalog }: { catalog: PolicyCatalog }) { return ( <> -
-
-

{document.title}

-
-
-
- {isTranslated && ( -
- {getAiTranslationNotice(locale)} -
- )} -
-
-
-
- -
-
+
+ +
+ {isTranslated && ( +
+ {getAiTranslationNotice(locale)} +
+ )} +
+
+
+ ); -} \ No newline at end of file +}