mirror of
https://github.com/KivotosRailway/website.git
synced 2026-11-03 11:06:32 +09:00
增加友情链接页面
This commit is contained in:
@@ -0,0 +1,32 @@
|
|||||||
|
(() => {
|
||||||
|
const canonicalLocale = (value) => {
|
||||||
|
const language = value?.toLowerCase().replaceAll("_", "-");
|
||||||
|
if (!language) return null;
|
||||||
|
if (/^zh(?:-|$)/.test(language)) return /^zh-(tw|hk|mo)(-|$)|-hant(?:-|$)/.test(language) ? "zh-Hant" : "zh-Hans";
|
||||||
|
if (/^en(?:-|$)/.test(language)) return "en";
|
||||||
|
if (/^(ja|jp)(?:-|$)/.test(language)) return "jp";
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
const parts = location.pathname.split("/");
|
||||||
|
const firstSegment = parts[1];
|
||||||
|
const pathLocale = canonicalLocale(firstSegment);
|
||||||
|
const isUnknownLanguagePath = !pathLocale && /^[a-z]{2,3}(?:-[a-z0-9]+)*$/i.test(firstSegment ?? "");
|
||||||
|
let locale = pathLocale;
|
||||||
|
if (!locale) {
|
||||||
|
try { locale = canonicalLocale(localStorage.getItem("kr-locale")); } catch {}
|
||||||
|
}
|
||||||
|
if (!locale) {
|
||||||
|
for (const language of navigator.languages?.length ? navigator.languages : [navigator.language]) {
|
||||||
|
locale = canonicalLocale(language);
|
||||||
|
if (locale) break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
locale ??= "en";
|
||||||
|
const path = pathLocale || isUnknownLanguagePath ? `/${parts.slice(2).join("/")}` : location.pathname;
|
||||||
|
const normalizedPath = path.replace(/\/$/, "");
|
||||||
|
const destination = `/${locale}${normalizedPath}/`;
|
||||||
|
const search = new URLSearchParams(location.search);
|
||||||
|
search.delete("lang");
|
||||||
|
const canonicalUrl = destination + (search.size ? `?${search}` : "") + location.hash;
|
||||||
|
if (location.pathname + location.search + location.hash !== canonicalUrl) location.replace(canonicalUrl);
|
||||||
|
})();
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
(() => {
|
||||||
|
const isTheme = (value) => value === "light" || value === "system" || value === "dark";
|
||||||
|
let preference = "system";
|
||||||
|
try {
|
||||||
|
const cookieTheme = document.cookie.split(";").map((entry) => entry.trim()).find((entry) => entry.startsWith("kr-theme="))?.slice("kr-theme=".length);
|
||||||
|
const savedTheme = isTheme(cookieTheme) ? cookieTheme : localStorage.getItem("kr-theme");
|
||||||
|
if (isTheme(savedTheme)) preference = savedTheme;
|
||||||
|
} catch {}
|
||||||
|
const theme = preference === "system" ? (matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light") : preference;
|
||||||
|
document.documentElement.dataset.theme = theme;
|
||||||
|
document.documentElement.style.colorScheme = theme;
|
||||||
|
})();
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
<svg width="430" height="430" viewBox="0 0 430 430" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M111.798 108.829C114.867 107 118.76 109.212 118.76 112.785V268.853C118.76 270.507 119.647 272.035 121.085 272.854L214.023 325.806C215.464 326.627 217.235 326.609 218.66 325.761L308.993 271.944C310.387 271.114 311.241 269.611 311.241 267.988V161.141C311.241 159.49 310.357 157.965 308.924 157.145L219.537 105.974C218.136 105.171 216.417 105.162 215.007 105.948L176.667 127.329C173.598 129.04 169.82 126.821 169.82 123.307V80.0176C169.82 78.3714 170.698 76.8504 172.125 76.0283L214.96 51.3372C216.394 50.5105 218.161 50.5173 219.589 51.3549L356.426 131.628C357.835 132.454 358.701 133.966 358.701 135.599V295.301C358.701 296.947 357.822 298.469 356.396 299.291L218.632 378.687C217.221 379.501 215.484 379.508 214.066 378.706L73.6378 299.284C72.1932 298.467 71.3 296.936 71.3 295.276V135.578C71.3 133.956 72.1538 132.453 73.5475 131.623L111.798 108.829Z" fill="#333333"/>
|
||||||
|
<path d="M261.536 241.829V188.171L215.001 161.309L168.466 188.171V241.829L215.001 268.691L261.536 241.829Z" fill="#FD6727"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.1 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 49 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 7.5 MiB |
@@ -0,0 +1,13 @@
|
|||||||
|
import type { Metadata } from "next";
|
||||||
|
import { FriendlyLinksPage } from "@/components/links/friendly-links-page";
|
||||||
|
import { getLocaleMessages, isLocale } from "@/lib/i18n";
|
||||||
|
|
||||||
|
export async function generateMetadata({ params }: { params: Promise<{ locale?: string }> }): Promise<Metadata> {
|
||||||
|
const { locale } = await params;
|
||||||
|
return { title: getLocaleMessages(isLocale(locale) ? locale : "zh-Hans").footer.legal[2] };
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function LinksPage({ params }: { params: Promise<{ locale?: string }> }) {
|
||||||
|
const { locale } = await params;
|
||||||
|
return <FriendlyLinksPage locale={isLocale(locale) ? locale : "zh-Hans"} />;
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { default, generateMetadata } from "@/app/(legacy)/links/page";
|
||||||
+26
-1
@@ -55,6 +55,20 @@ a { color: inherit; text-decoration: none; }
|
|||||||
.home-collage { position: relative; height: 648px; margin-top: 50px; }.home-collage-tile { position: absolute; border-radius: 13px; background: #d9d9d9; }.home-collage-wide { top: 0; left: 0; width: 41.4%; height: 359px; }.home-collage-medium { top: 0; left: 42.57%; width: 28.87%; height: 359px; }.home-collage-small { top: 376px; left: 27.28%; width: 44.3%; height: 272px; }.home-collage-bottom { top: 376px; left: 0; width: 25.83%; height: 272px; }.home-collage-tall { top: 0; right: 0; width: 27.1%; height: 648px; }
|
.home-collage { position: relative; height: 648px; margin-top: 50px; }.home-collage-tile { position: absolute; border-radius: 13px; background: #d9d9d9; }.home-collage-wide { top: 0; left: 0; width: 41.4%; height: 359px; }.home-collage-medium { top: 0; left: 42.57%; width: 28.87%; height: 359px; }.home-collage-small { top: 376px; left: 27.28%; width: 44.3%; height: 272px; }.home-collage-bottom { top: 376px; left: 0; width: 25.83%; height: 272px; }.home-collage-tall { top: 0; right: 0; width: 27.1%; height: 648px; }
|
||||||
|
|
||||||
.news-page { padding: 30px 0 60px; }
|
.news-page { padding: 30px 0 60px; }
|
||||||
|
.friendly-links-hero { background: var(--policy-hero-image) center 26.71% / 100% auto no-repeat; }
|
||||||
|
.friendly-links-page-inner { gap: 50px; }
|
||||||
|
.friendly-links-content { width: min(1214px, 100%); margin: 0 auto; }
|
||||||
|
.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-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 16px; }
|
||||||
|
.friendly-link-card { position: relative; display: flex; align-items: stretch; min-width: 0; min-height: 186px; overflow: hidden; border: 1.1px solid #e3e6e8; border-radius: 16.42px; background: #fff; color: #181c20; transition: border-color .2s ease, box-shadow .2s ease, transform .2s ease; }
|
||||||
|
a.friendly-link-card:hover, a.friendly-link-card:focus-visible { border-color: #8f56a2; box-shadow: 0 8px 20px rgba(40, 29, 47, .1); transform: translateY(-2px); }
|
||||||
|
.friendly-link-card-content { display: flex; flex: 1; flex-direction: column; align-items: center; justify-content: center; gap: 16.42px; padding: 32.83px 10.94px; }
|
||||||
|
.friendly-link-card-heading { display: flex; align-items: center; justify-content: center; gap: 16px; min-width: 0; font-size: 27.36px; font-weight: 400; line-height: normal; white-space: nowrap; }
|
||||||
|
.friendly-link-card-heading img { width: 65.67px; height: 65.67px; overflow: hidden; border-radius: 8px; 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; }
|
||||||
.news-page-inner { max-width: 1200px; margin: 0 auto; padding: 0 24px; }
|
.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 { 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; }
|
.news-page-header h1 { margin: 0; font-size: clamp(52px, 5vw, 88px); letter-spacing: -0.07em; line-height: 1; font-weight: 800; }
|
||||||
@@ -104,7 +118,7 @@ a { color: inherit; text-decoration: none; }
|
|||||||
.news-detail-body a:hover, .policy-body a:hover { color: #8f56a2; }
|
.news-detail-body a:hover, .policy-body a:hover { color: #8f56a2; }
|
||||||
.policy-page { padding: 0 0 48px; background: #fff; }
|
.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-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)), url("/policy-hero.png") center / cover; overflow: hidden; }
|
.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-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-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-card { width: min(1200px, 100%); padding: 28px; border-radius: 26px; background: #fefdfa; box-shadow: 0 10px 15px rgba(15, 23, 42, 0.04); }
|
||||||
@@ -188,6 +202,9 @@ html[data-theme="dark"] .theme-toggle button:hover, html[data-theme="dark"] .the
|
|||||||
html[data-theme="dark"] .theme-toggle button.active:hover, html[data-theme="dark"] .theme-toggle button.active:focus-visible { background: #2b3746; }
|
html[data-theme="dark"] .theme-toggle button.active:hover, html[data-theme="dark"] .theme-toggle button.active:focus-visible { background: #2b3746; }
|
||||||
html[data-theme="dark"] .theme-toggle img { filter: brightness(0) invert(1); }
|
html[data-theme="dark"] .theme-toggle img { filter: brightness(0) invert(1); }
|
||||||
html[data-theme="dark"] .news-toolbar { border-color: rgba(255, 255, 255, 0.16); }
|
html[data-theme="dark"] .news-toolbar { border-color: rgba(255, 255, 255, 0.16); }
|
||||||
|
html[data-theme="dark"] .friendly-links-section h2 { color: var(--ink); }
|
||||||
|
html[data-theme="dark"] .friendly-link-card { border-color: #394553; background: #19222d; color: var(--ink); }
|
||||||
|
html[data-theme="dark"] .friendly-link-card-description { color: #a9b5c1; }
|
||||||
html[data-theme="dark"] .news-filter-button, html[data-theme="dark"] .news-view-toggle { color: var(--ink); border-color: rgba(255, 255, 255, 0.2); background: rgba(255, 255, 255, 0.04); }
|
html[data-theme="dark"] .news-filter-button, html[data-theme="dark"] .news-view-toggle { color: var(--ink); border-color: rgba(255, 255, 255, 0.2); background: rgba(255, 255, 255, 0.04); }
|
||||||
html[data-theme="dark"] .news-pagination-button, html[data-theme="dark"] .news-share-button { color: var(--ink); border-color: rgba(255, 255, 255, 0.2); background: #1b2531; }
|
html[data-theme="dark"] .news-pagination-button, html[data-theme="dark"] .news-share-button { color: var(--ink); border-color: rgba(255, 255, 255, 0.2); background: #1b2531; }
|
||||||
html[data-theme="dark"] .news-detail-card { background: #19222d; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2); }
|
html[data-theme="dark"] .news-detail-card { background: #19222d; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2); }
|
||||||
@@ -224,6 +241,9 @@ html[data-theme="dark"] .site-footer-title, html[data-theme="dark"] .site-footer
|
|||||||
html:not([data-theme]) .theme-toggle button.active:hover, html:not([data-theme]) .theme-toggle button.active:focus-visible { background: #2b3746; }
|
html:not([data-theme]) .theme-toggle button.active:hover, html:not([data-theme]) .theme-toggle button.active:focus-visible { background: #2b3746; }
|
||||||
html:not([data-theme]) .theme-toggle img { filter: brightness(0) invert(1); }
|
html:not([data-theme]) .theme-toggle img { filter: brightness(0) invert(1); }
|
||||||
html:not([data-theme]) .news-toolbar { border-color: rgba(255, 255, 255, 0.16); }
|
html:not([data-theme]) .news-toolbar { border-color: rgba(255, 255, 255, 0.16); }
|
||||||
|
html:not([data-theme]) .friendly-links-section h2 { color: var(--ink); }
|
||||||
|
html:not([data-theme]) .friendly-link-card { border-color: #394553; background: #19222d; color: var(--ink); }
|
||||||
|
html:not([data-theme]) .friendly-link-card-description { color: #a9b5c1; }
|
||||||
html:not([data-theme]) .news-filter-button, html:not([data-theme]) .news-view-toggle { color: var(--ink); border-color: rgba(255, 255, 255, 0.2); background: rgba(255, 255, 255, 0.04); }
|
html:not([data-theme]) .news-filter-button, html:not([data-theme]) .news-view-toggle { color: var(--ink); border-color: rgba(255, 255, 255, 0.2); background: rgba(255, 255, 255, 0.04); }
|
||||||
html:not([data-theme]) .news-pagination-button, html:not([data-theme]) .news-share-button { color: var(--ink); border-color: rgba(255, 255, 255, 0.2); background: #1b2531; }
|
html:not([data-theme]) .news-pagination-button, html:not([data-theme]) .news-share-button { color: var(--ink); border-color: rgba(255, 255, 255, 0.2); background: #1b2531; }
|
||||||
html:not([data-theme]) .news-detail-card { background: #19222d; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2); }
|
html:not([data-theme]) .news-detail-card { background: #19222d; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2); }
|
||||||
@@ -277,6 +297,7 @@ html[data-theme="dark"] .site-footer-title, html[data-theme="dark"] .site-footer
|
|||||||
.home-collage-small { top: 202px; left: 39%; width: 61%; height: 158px; }
|
.home-collage-small { top: 202px; left: 39%; width: 61%; height: 158px; }
|
||||||
.home-collage-tall { display: none; }
|
.home-collage-tall { display: none; }
|
||||||
.news-page-inner { padding: 0 16px; }
|
.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-page-header h1 { font-size: clamp(46px, 12vw, 72px); }
|
||||||
.news-toolbar { align-items: flex-start; flex-wrap: wrap; gap: 16px; }
|
.news-toolbar { align-items: flex-start; flex-wrap: wrap; gap: 16px; }
|
||||||
.news-toolbar-right { margin-left: auto; }
|
.news-toolbar-right { margin-left: auto; }
|
||||||
@@ -337,6 +358,10 @@ html[data-theme="dark"] .site-footer-title, html[data-theme="dark"] .site-footer
|
|||||||
.home-collage-bottom, .home-collage-small { top: 160px; height: 120px; }
|
.home-collage-bottom, .home-collage-small { top: 160px; height: 120px; }
|
||||||
|
|
||||||
.news-page { padding-top: 20px; }
|
.news-page { padding-top: 20px; }
|
||||||
|
.friendly-links-content { width: 100%; }
|
||||||
|
.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; }
|
||||||
.news-page-inner { padding: 0 12px; }
|
.news-page-inner { padding: 0 12px; }
|
||||||
.news-page-header { margin-bottom: 20px; }
|
.news-page-header { margin-bottom: 20px; }
|
||||||
.news-page-header h1 { font-size: 52px; }
|
.news-page-header h1 { font-size: 52px; }
|
||||||
|
|||||||
+11
-2
@@ -1,7 +1,16 @@
|
|||||||
import SiteDocument from "@/components/layout/site-document";
|
import Script from "next/script";
|
||||||
|
import "@/app/globals.css";
|
||||||
|
|
||||||
export { metadata, viewport } from "@/components/layout/site-document";
|
export { metadata, viewport } from "@/components/layout/site-document";
|
||||||
|
|
||||||
export default function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) {
|
export default function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) {
|
||||||
return <SiteDocument>{children}</SiteDocument>;
|
return (
|
||||||
|
<html lang="zh-Hans" suppressHydrationWarning>
|
||||||
|
<body>
|
||||||
|
{children}
|
||||||
|
<Script id="theme-preference" src="/init-theme.js" strategy="beforeInteractive" />
|
||||||
|
<Script id="locale-canonicalization" src="/init-locale.js" strategy="beforeInteractive" />
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import type { Metadata, Viewport } from "next";
|
import type { Metadata, Viewport } from "next";
|
||||||
import "@/app/globals.css";
|
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: {
|
title: {
|
||||||
@@ -7,75 +6,10 @@ export const metadata: Metadata = {
|
|||||||
template: "%s | 基沃托斯铁道公团 KivotosRailway",
|
template: "%s | 基沃托斯铁道公团 KivotosRailway",
|
||||||
},
|
},
|
||||||
description: "Kivotos Railway website",
|
description: "Kivotos Railway website",
|
||||||
icons: {
|
icons: { icon: "/icon/icon.svg" },
|
||||||
icon: "/icon/icon.svg",
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const viewport: Viewport = {
|
export const viewport: Viewport = {
|
||||||
width: "device-width",
|
width: "device-width",
|
||||||
initialScale: 1,
|
initialScale: 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function RootLayout({
|
|
||||||
children,
|
|
||||||
}: Readonly<{ children: React.ReactNode }>) {
|
|
||||||
return (
|
|
||||||
<html lang="zh-Hans" suppressHydrationWarning>
|
|
||||||
<head>
|
|
||||||
{/* Run during HTML parsing, before the body can paint. */}
|
|
||||||
<script
|
|
||||||
id="theme-preference"
|
|
||||||
dangerouslySetInnerHTML={{ __html: `(() => {
|
|
||||||
const isTheme = (value) => value === 'light' || value === 'system' || value === 'dark';
|
|
||||||
let preference = 'system';
|
|
||||||
try {
|
|
||||||
const cookieTheme = document.cookie.split(';').map((entry) => entry.trim()).find((entry) => entry.startsWith('kr-theme='))?.slice('kr-theme='.length);
|
|
||||||
const savedTheme = isTheme(cookieTheme) ? cookieTheme : localStorage.getItem('kr-theme');
|
|
||||||
if (isTheme(savedTheme)) preference = savedTheme;
|
|
||||||
} catch {}
|
|
||||||
const theme = preference === 'system' ? (matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') : preference;
|
|
||||||
document.documentElement.dataset.theme = theme;
|
|
||||||
document.documentElement.style.colorScheme = theme;
|
|
||||||
})();` }}
|
|
||||||
/>
|
|
||||||
<script
|
|
||||||
id="locale-canonicalization"
|
|
||||||
dangerouslySetInnerHTML={{ __html: `(() => {
|
|
||||||
const canonicalLocale = (value) => {
|
|
||||||
const language = value?.toLowerCase().replaceAll('_', '-');
|
|
||||||
if (!language) return null;
|
|
||||||
if (/^zh(?:-|$)/.test(language)) return /^zh-(tw|hk|mo)(-|$)|-hant(?:-|$)/.test(language) ? 'zh-Hant' : 'zh-Hans';
|
|
||||||
if (/^en(?:-|$)/.test(language)) return 'en';
|
|
||||||
if (/^(ja|jp)(?:-|$)/.test(language)) return 'jp';
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
const parts = location.pathname.split('/');
|
|
||||||
const firstSegment = parts[1];
|
|
||||||
const pathLocale = canonicalLocale(firstSegment);
|
|
||||||
const isUnknownLanguagePath = !pathLocale && /^[a-z]{2,3}(?:-[a-z0-9]+)*$/i.test(firstSegment ?? '');
|
|
||||||
let locale = pathLocale;
|
|
||||||
if (!locale) {
|
|
||||||
try { locale = canonicalLocale(localStorage.getItem('kr-locale')); } catch {}
|
|
||||||
}
|
|
||||||
if (!locale) {
|
|
||||||
for (const language of navigator.languages?.length ? navigator.languages : [navigator.language]) {
|
|
||||||
locale = canonicalLocale(language);
|
|
||||||
if (locale) break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
locale ??= 'en';
|
|
||||||
const path = pathLocale || isUnknownLanguagePath ? '/' + parts.slice(2).join('/') : location.pathname;
|
|
||||||
const normalizedPath = path.replace(/\\/$/, '');
|
|
||||||
const destination = '/' + locale + normalizedPath + '/';
|
|
||||||
const search = new URLSearchParams(location.search);
|
|
||||||
search.delete('lang');
|
|
||||||
const canonicalUrl = destination + (search.size ? '?' + search : '') + location.hash;
|
|
||||||
if (location.pathname + location.search + location.hash !== canonicalUrl) location.replace(canonicalUrl);
|
|
||||||
})();` }}
|
|
||||||
/>
|
|
||||||
</head>
|
|
||||||
<body>{children}</body>
|
|
||||||
</html>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import links from "@/data/links.json";
|
||||||
|
import type { CSSProperties } from "react";
|
||||||
|
import { PageBreadcrumb } from "@/components/layout/page-breadcrumb";
|
||||||
|
import { getLocaleMessages, type Locale } from "@/lib/i18n";
|
||||||
|
|
||||||
|
type LinkEntry = { name: string; description: string; logo: string; url?: string; archived?: boolean };
|
||||||
|
type LocalizedText = Record<Locale, string>;
|
||||||
|
type LinkSection = { id: string; title: LocalizedText; links: LinkEntry[] };
|
||||||
|
const linkSections = links.sections as LinkSection[];
|
||||||
|
|
||||||
|
function FriendlyLinkCard({ link }: { link: LinkEntry }) {
|
||||||
|
const content = <><span className="friendly-link-card-content"><span className="friendly-link-card-heading"><img src={link.logo} alt="" width={48} height={48} /><span>{link.name}</span></span><span className="friendly-link-card-description">{link.description}</span></span>{link.archived && <span className="friendly-link-card-archive">已归档</span>}</>;
|
||||||
|
return link.url ? <a className="friendly-link-card" href={link.url} target="_blank" rel="noreferrer" aria-label={`访问 ${link.name}`}>{content}</a> : <article className="friendly-link-card">{content}</article>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function FriendlyLinksPage({ locale }: { locale: Locale }) {
|
||||||
|
const messages = getLocaleMessages(locale);
|
||||||
|
const heroStyle = { "--policy-hero-image": 'url("/links/friendly-links-hero.png")' } as CSSProperties;
|
||||||
|
return <main className="policy-page friendly-links-page"><div className="policy-page-inner friendly-links-page-inner"><section className="policy-hero friendly-links-hero" aria-labelledby="friendly-links-title" style={heroStyle}><div className="policy-hero-inner"><h1 id="friendly-links-title">{messages.footer.legal[2]}</h1></div></section><div className="friendly-links-content">{linkSections.map((section) => <section className="friendly-links-section" key={section.id} aria-labelledby={`friendly-links-${section.id}`}><h2 id={`friendly-links-${section.id}`}>{section.title[locale]}</h2><div className="friendly-links-grid">{section.links.map((link, index) => <FriendlyLinkCard key={`${section.id}-${link.name}-${index}`} link={link} />)}</div></section>)}</div></div><div className="page-bottom-nav"><div className="page-bottom-nav-inner"><PageBreadcrumb items={[{ label: messages.common.home, href: "/" }, { label: messages.footer.legal[2] }]} /></div></div></main>;
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"sections": [
|
||||||
|
{
|
||||||
|
"id": "services",
|
||||||
|
"title": { "zh-Hans": "服务提供", "zh-Hant": "服務提供", "en": "Service providers", "jp": "サービス提供" },
|
||||||
|
"links": [
|
||||||
|
{ "name": "德威云", "description": "Minecraft 服务器提供方", "logo": "/links/fetarute.png", "url": "https://www.deviy.cn/" },
|
||||||
|
{ "name": "腾讯云", "description": "CDN、对象存储 提供方", "logo": "/links/fetarute.png", "url": "https://cloud.tencent.com/" },
|
||||||
|
{ "name": "蓝色秋生", "description": "网页服务提供方,对象存储、CDN 维护方", "logo": "/links/blueakio.svg", "url": "https://www.blueakio.com/" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "friends",
|
||||||
|
"title": { "zh-Hans": "朋友们", "zh-Hant": "朋友們", "en": "Friends", "jp": "友だち" },
|
||||||
|
"links": [
|
||||||
|
{ "name": "德威云", "description": "Minecraft 服务器提供方", "logo": "/links/fetarute.png", "url": "https://deviy.cn/" },
|
||||||
|
{ "name": "腾讯云", "description": "CDN、对象存储 提供方", "logo": "/links/fetarute.png", "url": "https://cloud.tencent.com/" },
|
||||||
|
{ "name": "腾讯云", "description": "CDN、对象存储 提供方", "logo": "/links/fetarute.png", "url": "https://cloud.tencent.com/" },
|
||||||
|
{ "name": "蓝色秋生", "description": "网页服务提供方,对象存储、CDN 维护方", "logo": "/links/fetarute.png", "url": "https://liqiusheng.cn/" },
|
||||||
|
{ "name": "腾讯云", "description": "CDN、对象存储 提供方", "logo": "/links/fetarute.png", "url": "https://cloud.tencent.com/" },
|
||||||
|
{ "name": "腾讯云", "description": "CDN、对象存储 提供方", "logo": "/links/fetarute.png", "url": "https://cloud.tencent.com/" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user