This commit is contained in:
toom1996
2026-10-22 10:36:49 +08:00
parent e7377a8771
commit 1adf71248f
6 changed files with 319 additions and 124 deletions

View File

@ -7,23 +7,27 @@ interface Props {
moduleZh?: string
note?: string
}
const { module = "This module", moduleZh = "", note = "" } = Astro.props
const { locale } = getI18n(Astro)
const { module = "", moduleZh = "", note = "" } = Astro.props
const { locale, t } = getI18n(Astro)
// 原实现整段是硬编码中文,英文站会直接显示中文。这里全部走词典,
// 模块名按语言取:中文优先 moduleZh,英文只取 module。
const moduleLabel = locale === 'cn' ? moduleZh || module : module || moduleZh
---
<section class="cs-wrap">
<div class="cs-kicker">
<span>Coming Soon</span>
<span>暂未上线</span>
<span>{t('Coming Soon')}</span>
<span>{t('Archive')}</span>
</div>
<h1 class="cs-title">暂未上线</h1>
<p class="cs-mod">{module}{moduleZh && ` · ${moduleZh}`}</p>
<h1 class="cs-title">{t('Coming Soon')}</h1>
<p class="cs-mod">{moduleLabel}</p>
<p class="cs-desc">
{note || "该模块正在筹备中,尚未对外开放。站点正按模块逐个完善,你可以先浏览已上线的走秀档案。"}
{note || t('This module is under construction and not open to the public yet. Browse the runway archive first.')}
</p>
<div class="cs-actions">
<a class="cs-btn" href={href(locale, ROUTES.runwayLooks)}>浏览走秀档案 →</a>
<a class="cs-btn-ghost" href={href(locale, ROUTES.home)}>返回首页</a>
<a class="cs-btn" href={href(locale, ROUTES.runwayLooks)}>{t('Browse runway archive →')}</a>
<a class="cs-btn-ghost" href={href(locale, ROUTES.home)}>{t('Back to home')}</a>
</div>
</section>

View File

@ -1,12 +1,17 @@
---
// 登录页的 en/cn 共享实现(语言由 Astro.currentLocale 运行时决定)。
// 对应页面:src/pages/{en,cn}/login.astro —— 仅做薄包装。
// 注意:本页**不走 Layout**,自带完整 <html> 文档(独立极简版式,故保留 <!doctype html>)。
//
// 设计方向:把登录页当成「一期杂志的封面页」——整幅满血封面 + 竖排刊头 + 套准标记 +
// 极端字距的巨标题。延续全站的黑白 / 直角 / 粗线语言,但把张力拉满,避免"白底表单"的平庸。
// 本页**不走 Layout**,自带完整 <html> 文档。
import '@/styles/global.css'
import { getI18n } from '@/lib/i18n'
import { href, ROUTES } from '@/lib/routes'
import { config } from '@/lib/config'
import { LOCALE_NAMES } from '@/lib/i18n'
import { getSsgIndexRunway, getSsgStreetSnapPopular } from '@/lib/ssg'
import { toAbs } from '@/lib/request'
import { i18n } from 'astro:config/client';
import { toCodes } from 'astro:i18n';
@ -24,7 +29,6 @@ const promptLabel = t('login prompt')
const accountLabel = t('username or email')
const passwordLabel = t('password')
const failLabel = t('login failed')
const membersLabel = t('members')
const backLabel = t('back to site')
const homeUrl = href(locale, ROUTES.runwayLooks)
const SITE_NAME = config.siteName ?? "Xisoa"
@ -35,7 +39,19 @@ const loginDesc = locale === 'cn'
const loginKeywords = locale === 'cn'
? `登录,账户,${SITE_NAME}`
: `login,account,${SITE_NAME}`
// 封面区:库里原图统一是 240×360(爬虫入库下的是缩略图),
// 放大成整屏必然糊,所以改用「接触印样」版式——多张图按原始尺寸铺成网格,
// 既不会拉伸,也符合时尚档案的视觉习惯。取不到图则降级为纯黑排版块。
const showCovers = (await getSsgIndexRunway()).hotBrand
.filter((b) => b.cover)
.map((b) => toAbs(b.cover))
const snapCovers = (await getSsgStreetSnapPopular(8))
.filter((s) => s.cover)
.map((s) => toAbs(s.cover))
const sheet = [...new Set([...showCovers, ...snapCovers])].slice(0, 6)
---
<!doctype html>
<html lang={locale === 'cn' ? 'zh-CN' : 'en'}>
<head>
@ -45,96 +61,187 @@ const loginKeywords = locale === 'cn'
<title>{pageTitle}</title>
<meta name="description" content={loginDesc} />
<meta name="keywords" content={loginKeywords} />
<style>
/* 印刷质感:极细扫描线,替代噪声图,零外部资源 */
.paper-grain {
background-image: repeating-linear-gradient(
0deg,
rgba(0, 0, 0, 0.035) 0 1px,
transparent 1px 3px
);
}
/* 逐行上浮:整页一次编排好的入场,而不是零散的微交互 */
@keyframes rise {
from { opacity: 0; transform: translateY(14px); }
to { opacity: 1; transform: none; }
}
.rise { animation: rise 0.62s cubic-bezier(0.22, 0.61, 0.36, 1) both; }
.rise-1 { animation-delay: 0.04s; }
.rise-2 { animation-delay: 0.12s; }
.rise-3 { animation-delay: 0.20s; }
.rise-4 { animation-delay: 0.28s; }
.rise-5 { animation-delay: 0.36s; }
@media (prefers-reduced-motion: reduce) {
.rise { animation: none; }
}
/* 竖排刊头(东方式版式,与"档案"气质一致) */
.vertical-label {
writing-mode: vertical-rl;
text-orientation: mixed;
}
/* 输入框:底线 2px,聚焦时整行压黑 */
.inkinput {
border: 0;
border-bottom: 2px solid rgba(0, 0, 0, 0.35);
background: transparent;
transition: border-color 0.18s ease;
}
.inkinput:focus { outline: none; border-bottom-color: #000; }
</style>
</head>
<!-- 全站一致:白底 + 粗黑大标题 + 宽字距小标 + 粗黑细线,直角无阴影 -->
<body class="min-h-screen bg-white text-black antialiased selection:bg-black selection:text-white">
<main class="mx-auto w-full max-w-[1400px] px-5 py-10 md:px-10 md:py-14 font-sans">
<div class="mx-auto flex min-h-screen w-full max-w-[1700px] flex-col">
<!-- 顶部工具行:返回档案 + 语言切换 -->
<header class="mb-12 flex items-center justify-between gap-6">
<a href={homeUrl} class="inline-flex items-center gap-2 text-[11px] uppercase tracking-[0.22em] text-black/50 transition-colors hover:text-black">
<!-- 顶栏:返回 / 期号 / 语言 -->
<header class="flex items-center justify-between gap-4 border-b border-black/15 px-5 py-4 md:px-10">
<a href={homeUrl} class="inline-flex items-center gap-2 text-[11px] uppercase tracking-[0.22em] text-black/55 transition-colors hover:text-black">
<span aria-hidden="true">←</span>{backLabel}
</a>
<div class="relative" x-data="{ open: false }" @keydown.escape.window="open = false" @click.outside="open = false">
<button type="button" @click="open = !open" class="inline-flex items-center gap-2 border border-black/15 px-3 py-2 text-[11px] uppercase tracking-[0.22em] text-black transition-colors hover:bg-black hover:text-white" aria-label={t('Language')} aria-haspopup="listbox" :aria-expanded="open">
<span>{LOCALE_NAMES[locale].nativeName}</span>
<svg viewBox="0 0 12 12" width="11" height="11" class="transition-transform duration-200" :class="open ? 'rotate-180' : ''" aria-hidden="true">
<path d="M2.5 4.5l3.5 3 3.5-3" stroke="currentColor" fill="none" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
<ul x-show="open" x-cloak class="absolute right-0 top-full z-20 mt-2 min-w-[170px] border border-black/15 bg-white py-1">
{languages.map((l) => (
<li>
<a href={getRelativeLocaleUrl(l.code, pathWithoutLocale)} @click="open = false" class:list={['block px-4 py-2 text-[11px] uppercase tracking-[0.2em] transition-colors hover:bg-black hover:text-white', l.code === locale ? 'font-bold text-black' : 'text-black/55']}>{l.label}</a>
</li>
))}
</ul>
<div class="flex items-center gap-5">
<span class="hidden text-[10px] uppercase tracking-[0.3em] text-black/35 sm:inline">N° 04 / 2026</span>
<div class="relative" x-data="{ open: false }" @keydown.escape.window="open = false" @click.outside="open = false">
<button type="button" @click="open = !open" class="inline-flex items-center gap-2 border border-black/20 px-3 py-2 text-[11px] uppercase tracking-[0.22em] text-black transition-colors hover:bg-black hover:text-white" aria-label={t('language')} aria-haspopup="listbox" :aria-expanded="open">
<span>{LOCALE_NAMES[locale].nativeName}</span>
<svg viewBox="0 0 12 12" width="11" height="11" class="transition-transform duration-200" :class="open ? 'rotate-180' : ''" aria-hidden="true">
<path d="M2.5 4.5l3.5 3 3.5-3" stroke="currentColor" fill="none" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
<ul x-show="open" x-cloak class="absolute right-0 top-full z-30 mt-2 min-w-[170px] border border-black/20 bg-white py-1">
{languages.map((l) => (
<li>
<a href={getRelativeLocaleUrl(l.code, pathWithoutLocale)} @click="open = false" class:list={['block px-4 py-2 text-[11px] uppercase tracking-[0.2em] transition-colors hover:bg-black hover:text-white', l.code === locale ? 'font-bold text-black' : 'text-black/55']}>{l.label}</a>
</li>
))}
</ul>
</div>
</div>
</header>
<!-- 报头:与账户页同一套 masthead(宽字距眉标 + 粗黑巨标题 + 档案标签 + 2px 粗黑线) -->
<section class="border-b-2 border-black pb-10">
<div class="flex flex-wrap items-end justify-between gap-x-16 gap-y-8">
<div>
<p class="mb-4 text-[11px] uppercase tracking-[0.3em] text-[var(--c-gray-600)]">{membersLabel}</p>
<h1 class="text-6xl font-black uppercase leading-[0.88] tracking-tight md:text-8xl">{loginLabel}</h1>
<!-- 主体:左封面 / 右表单 -->
<main class="grid flex-1 md:grid-cols-[1.08fr_1fr]">
<!-- 左:整幅封面 -->
<aside class="relative hidden overflow-hidden bg-black md:block">
{sheet.length > 0 ? (
<div class="grid h-full grid-cols-3 grid-rows-2 gap-px bg-black">
{sheet.map((src, i) => (
<div class="relative min-h-0 overflow-hidden">
<img
src={src}
alt=""
loading={i < 3 ? 'eager' : 'lazy'}
decoding="async"
class="absolute inset-0 h-full w-full object-cover"
/>
</div>
))}
</div>
) : (
<div class="h-full w-full bg-black"></div>
)}
<!-- 压暗 + 颗粒 -->
<div class="absolute inset-0 bg-gradient-to-t from-black via-black/45 to-black/10"></div>
<div class="paper-grain absolute inset-0 opacity-60 mix-blend-overlay"></div>
<!-- 左侧竖排刊头 -->
<div class="absolute left-5 top-8 flex flex-col items-center gap-4">
<span class="vertical-label text-[10px] uppercase tracking-[0.42em] text-white/55">{t('fashion archive')}</span>
<span class="h-16 w-px bg-white/30"></span>
</div>
<div class="pb-1 text-right text-[11px] uppercase leading-loose tracking-[0.22em] text-[var(--c-gray-600)]">
<p>{SITE_NAME}</p>
<p class="flex items-center justify-end gap-2 text-[var(--c-gray-700)]">Archive
<span class="bg-black px-1.5 py-0.5 text-[10px] font-bold tracking-widest text-white">2026</span>
</p>
<p class="tracking-[0.3em]">Paris · Milan · Tokyo</p>
<!-- 四角套准标记 -->
<span class="absolute left-5 top-5 h-3 w-3 border-l border-t border-white/40"></span>
<span class="absolute right-5 top-5 h-3 w-3 border-r border-t border-white/40"></span>
<span class="absolute bottom-5 left-5 h-3 w-3 border-b border-l border-white/40"></span>
<span class="absolute bottom-5 right-5 h-3 w-3 border-b border-r border-white/40"></span>
<!-- 底部刊名(压在图上) -->
<div class="absolute inset-x-0 bottom-0 p-8 md:p-12">
<p class="text-[10px] uppercase tracking-[0.4em] text-white/60">{t('archive')}</p>
<p class="mt-3 text-5xl font-black uppercase leading-[0.8] tracking-[-0.04em] text-white lg:text-7xl">{SITE_NAME}</p>
<p class="mt-5 text-[10px] uppercase tracking-[0.34em] text-white/50">{t('paris · milan · tokyo')}</p>
</div>
</div>
</section>
</aside>
<!-- 主体:左表单 / 右档案说明(mobile 单栏堆叠) -->
<div class="grid gap-12 pt-12 md:grid-cols-[minmax(0,1fr)_320px] md:gap-16 md:pt-16">
<!-- 右:表单 -->
<section class="relative flex flex-col justify-center px-5 py-10 md:px-14 md:py-14">
<span class="absolute right-5 top-5 hidden h-3 w-3 border-r border-t border-black/20 md:block"></span>
<div class="w-full max-w-md">
<p class="mb-10 text-[11px] uppercase tracking-[0.22em] text-[var(--c-gray-600)]">{promptLabel}</p>
<p class="rise rise-1 flex items-center gap-2 text-[10px] uppercase tracking-[0.36em] text-black/45">
<span class="inline-block h-[7px] w-[7px] bg-black"></span>{t('members only')}
</p>
<form x-data="loginForm" data-locale={locale} data-fail-label={failLabel} @submit.prevent="submit()" class="space-y-8" novalidate>
<div>
<label class="mb-2 block text-[11px] uppercase tracking-[0.22em] text-[var(--c-gray-700)]">{accountLabel}</label>
<input type="text" x-model="account" autocomplete="username" required autofocus
class="w-full border-0 border-b border-black/30 bg-transparent px-0 py-3 text-sm text-black outline-none transition-colors focus:border-black focus:ring-0" />
<h1 class="rise rise-2 mt-5 text-[clamp(52px,8vw,104px)] font-black uppercase leading-[0.82] tracking-[-0.045em]">
{loginLabel}
</h1>
<p class="rise rise-2 mt-6 max-w-sm text-sm leading-relaxed text-black/55">{promptLabel}</p>
<form x-data="loginForm" data-locale={locale} data-fail-label={failLabel} @submit.prevent="submit()" class="rise rise-3 mt-12 w-full max-w-md" novalidate>
<div class="flex items-baseline gap-4">
<span class="w-6 shrink-0 text-[10px] font-bold tracking-[0.2em] text-black/30">01</span>
<div class="flex-1">
<label for="lg-account" class="mb-1 block text-[10px] uppercase tracking-[0.28em] text-black/45">{accountLabel}</label>
<input id="lg-account" type="text" x-model="account" autocomplete="username" required autofocus
class="inkinput w-full py-3 text-base text-black md:text-lg" />
</div>
</div>
<div>
<label class="mb-2 block text-[11px] uppercase tracking-[0.22em] text-[var(--c-gray-700)]">{passwordLabel}</label>
<input type="password" x-model="password" autocomplete="current-password" required
class="w-full border-0 border-b border-black/30 bg-transparent px-0 py-3 text-sm text-black outline-none transition-colors focus:border-black focus:ring-0" />
<div class="mt-9 flex items-baseline gap-4">
<span class="w-6 shrink-0 text-[10px] font-bold tracking-[0.2em] text-black/30">02</span>
<div class="flex-1">
<label for="lg-password" class="mb-1 block text-[10px] uppercase tracking-[0.28em] text-black/45">{passwordLabel}</label>
<input id="lg-password" type="password" x-model="password" autocomplete="current-password" required
class="inkinput w-full py-3 text-base text-black md:text-lg" />
</div>
</div>
<p x-show="error" x-text="error" x-cloak class="text-[11px] uppercase tracking-[0.2em] text-red-600"></p>
<p x-show="error" x-text="error" x-cloak
class="mt-7 border-l-4 border-black bg-black px-4 py-3 text-[11px] uppercase tracking-[0.18em] text-white"></p>
<button type="submit" :disabled="loading"
class="w-full border border-black bg-black py-4 text-[12px] font-bold uppercase tracking-[0.2em] text-white transition-colors hover:bg-white hover:text-black disabled:cursor-not-allowed disabled:opacity-50">
<span x-show="!loading">{loginLabel}</span>
<span x-show="loading" x-cloak>…</span>
class="group mt-10 flex w-full items-center justify-between border border-black bg-black px-6 py-5 text-[12px] font-bold uppercase tracking-[0.24em] text-white transition-colors hover:bg-white hover:text-black disabled:cursor-not-allowed disabled:opacity-50">
<span x-show="!loading">{t('enter the archive →')}</span>
<span x-show="loading" x-cloak>{t('signing in…')}</span>
<span aria-hidden="true" class="text-base transition-transform duration-200 group-hover:translate-x-1">→</span>
</button>
</form>
<div class="mt-10 border-t border-black/15 pt-5">
<a href={homeUrl} class="inline-flex items-center gap-2 text-[11px] uppercase tracking-[0.2em] text-[var(--c-gray-600)] transition-colors hover:text-black">
<!-- 说明与补充入口 -->
<div class="rise rise-4 mt-12 max-w-md border-t border-black/10 pt-6">
<p class="text-[10px] uppercase tracking-[0.3em] text-black/40">{t('archive note')}</p>
<p class="mt-3 text-[13px] leading-relaxed text-black/55">
{t('minimalist editorial portfolio documenting haute couture shows, backstage fittings, and street culture. integrated with 2026 color forecast intelligence and ai palette generator.')}
</p>
</div>
<div class="rise rise-5 mt-8 flex max-w-md flex-wrap items-center justify-between gap-3 text-[10px] uppercase tracking-[0.24em] text-black/40">
<a href={homeUrl} class="inline-flex items-center gap-2 transition-colors hover:text-black">
<span aria-hidden="true">←</span>{backLabel}
</a>
<span class="tracking-[0.28em]">{t('runway · street · archive')}</span>
</div>
</div>
</section>
</main>
<aside class="border-t border-black/10 pt-8 md:border-l md:border-t-0 md:pl-8 md:pt-0">
<p class="mb-5 text-[11px] uppercase tracking-[0.3em] text-[var(--c-gray-600)]">Archive note</p>
<p class="text-lg leading-relaxed text-black/80">{t('minimalist editorial portfolio documenting haute couture shows, backstage fittings, and street culture. integrated with 2026 color forecast intelligence and ai palette generator.')}</p>
<div class="mt-8 border-t border-black/10 pt-5 text-[10px] uppercase tracking-[0.3em] text-[var(--c-gray-600)]">
<p class="mb-3">Runway · Street · Archive</p>
<p class="tracking-[0.3em]">Paris · Milan · Tokyo</p>
</div>
</aside>
</div>
</main>
<footer class="flex items-center justify-between border-t border-black/10 px-5 py-4 text-[10px] uppercase tracking-[0.28em] text-black/35 md:px-10">
<p>© 2026 {SITE_NAME}</p>
<p>Paris · Milan · Tokyo</p>
</footer>
</div>
<script>
import { getUser } from "@/lib/auth"

View File

@ -246,7 +246,7 @@ const headers = {
<slot />
</div>
<footer class="relative z-[1] bg-white mt-20 pt-10 border-t-2 border-black text-black w-full font-sans">
<footer class="relative z-[1] mt-20 w-full bg-black font-sans text-white">
<script>
import { getUser } from "@/lib/auth"
import type { AuthUser } from "@/lib/auth"
@ -343,58 +343,61 @@ const headers = {
}))
})
</script>
{/* Top Footer Grid */}
<div class="grid grid-cols-1 gap-10 md:grid-cols-12 md:gap-8 mb-10 pb-10 border-b border-black/10">
{/* Column 1: Brand & Manifesto */}
<div class="md:col-span-5 space-y-4">
<div class="flex items-center gap-2">
<span class="text-base font-black tracking-wider uppercase border-b-2 border-black pb-0.5">
{/* 页脚主体:黑底反白,作为整页收尾(沿用全站直角 / 粗线 / 宽字距语言) */}
<div class="mx-auto w-full max-w-[1400px] px-5 py-14 md:px-10 md:py-20">
<div class="grid grid-cols-1 gap-12 md:grid-cols-12 md:gap-8">
{/* 品牌 */}
<div class="md:col-span-6">
<p class="text-[10px] uppercase tracking-[0.34em] text-white/35">{t('Archive')}</p>
<p class="mt-5 text-5xl font-black uppercase leading-[0.85] tracking-tight md:text-7xl">
{SITE_NAME}
</span>
<span class="text-[10px] bg-black text-white px-2 py-0.5 font-bold uppercase tracking-widest rounded-full">
2026
</span>
</p>
<p class="mt-7 max-w-md text-sm leading-relaxed text-white/45">
{t('Minimalist editorial portfolio documenting haute couture shows, backstage fittings, and street culture. Integrated with 2026 color forecast intelligence and AI palette generator.')}
</p>
<div class="mt-8 flex flex-wrap items-center gap-x-3 gap-y-2 text-[10px] uppercase tracking-[0.3em] text-white/30">
<span>Paris</span><span>·</span><span>Milan</span><span>·</span><span>Tokyo</span><span>·</span><span>New York</span>
</div>
</div>
<p class="text-xs text-[var(--c-gray-800)] leading-relaxed max-w-md">
{t('Minimalist editorial portfolio documenting haute couture shows, backstage fittings, and street culture. Integrated with 2026 color forecast intelligence and AI palette generator.')}
</p>
{/* 导航 */}
<div class="md:col-span-3 md:col-start-8">
<h3 class="text-[10px] uppercase tracking-[0.3em] text-white/35">{t('Explore')}</h3>
<ul class="mt-5 space-y-3 text-[11px] uppercase tracking-[0.18em]">
<li><a href={href(locale, ROUTES.runwayLooks)} class="text-white/65 transition-colors hover:text-white">{t('all runway looks')}</a></li>
<li><a href={href(locale, ROUTES.streetSnaps)} class="text-white/65 transition-colors hover:text-white">{t('Street Style')}</a></li>
<li><a href={href(locale, ROUTES.account)} class="text-white/65 transition-colors hover:text-white">{t('my account')}</a></li>
</ul>
</div>
{/* Location Badge */}
<div class="flex flex-wrap gap-3 text-[11px] text-[var(--c-gray-700)] uppercase tracking-widest pt-1">
<span>Paris</span>
<span>•</span>
<span>Milan</span>
<span>•</span>
<span>Tokyo</span>
<span>•</span>
<span>New York</span>
{/* 站点操作(全部是真实可用的入口,不留死链) */}
<div class="md:col-span-3">
<h3 class="text-[10px] uppercase tracking-[0.3em] text-white/35">{t('Site')}</h3>
<ul class="mt-5 space-y-3 text-[11px] uppercase tracking-[0.18em]">
<li>
<a href={href(locale === 'cn' ? 'en' : 'cn', ROUTES.home)} class="text-white/65 transition-colors hover:text-white">
{locale === 'cn' ? 'English' : '中文'}
</a>
</li>
<li>
<button
type="button"
onclick="window.scrollTo({ top: 0, behavior: 'smooth' })"
class="cursor-pointer text-left uppercase tracking-[0.18em] text-white/65 transition-colors hover:text-white"
>
{t('Back to top ↑')}
</button>
</li>
</ul>
</div>
</div>
{/* Column 2: Explore */}
<div class="md:col-span-4 md:col-start-7 space-y-3">
<h3 class="text-[11px] tracking-[0.26em] uppercase font-bold text-black">Explore</h3>
<ul class="space-y-2 text-[11px] uppercase tracking-[0.18em] text-[var(--c-gray-800)]">
<li><a href={href(locale, ROUTES.runwayLooks)} class="hover:text-black transition-colors">{t('all runway looks')}</a></li>
<li><a href={href(locale, ROUTES.streetSnaps)} class="hover:text-black transition-colors">{t('Street Style')}</a></li>
<li><a href={href(locale, ROUTES.account)} class="hover:text-black transition-colors">{t('my account')}</a></li>
</ul>
{/* 底部条 */}
<div class="mt-14 flex flex-col gap-4 border-t border-white/10 pt-6 text-[10px] uppercase tracking-[0.26em] text-white/35 sm:flex-row sm:items-center sm:justify-between">
<p>© 2026 {SITE_NAME}. {t('all rights reserved.')}</p>
<p class="tracking-[0.3em]">{t('Runway · Street · Archive')}</p>
</div>
{/* Column 3: Connect */}
<div class="md:col-span-2 space-y-3">
<h3 class="text-[11px] tracking-[0.26em] uppercase font-bold text-black">Connect</h3>
<ul class="space-y-2 text-[11px] uppercase tracking-[0.18em] text-[var(--c-gray-800)]">
<li><a href="#" class="hover:text-black transition-colors">Instagram</a></li>
<li><a href="#" class="hover:text-black transition-colors">Newsletter</a></li>
<li><a href="#" class="hover:text-black transition-colors">Contact</a></li>
</ul>
</div>
</div>
<div class="mt-6 text-[11px] text-[var(--c-gray-600)] uppercase tracking-widest">
© 2026 {SITE_NAME}. {t('all rights reserved.')}
</div>
</footer>

View File

@ -21,6 +21,29 @@ export const dict: Record<string, TranslationEntry> = {
'loading': { cn: '加载中' },
// ── 通用文案 / 页脚 / 登录页 ──
// ⚠️ 必须全小写:translate() 用 dict[key.toLowerCase()] 查表,
// 带大写字母的 key 永远命中不了,会静默回退英文。
'page not found': { cn: '页面不存在' },
'coming soon': { cn: '即将上线' },
'archive': { cn: '档案' },
'archive note': { cn: '档案说明' },
'explore': { cn: '探索' },
'site': { cn: '站点' },
'back to top ↑': { cn: '返回顶部 ↑' },
'signing in…': { cn: '登录中…' },
'runway · street · archive': { cn: '秀场 · 街拍 · 档案' },
'paris · milan · tokyo': { cn: '巴黎 · 米兰 · 东京' },
'this module is under construction and not open to the public yet. browse the runway archive first.':
{ cn: '该模块正在筹备中,尚未对外开放。你可以先浏览已上线的走秀档案。' },
'browse runway archive →': { cn: '浏览走秀档案 →' },
'back to home': { cn: '返回首页' },
'enter the archive →': { cn: '进入档案 →' },
'members only': { cn: '仅限会员' },
'fashion archive': { cn: '时尚档案' },
'forgot password?': { cn: '忘记密码?' },
'ask the editor': { cn: '联系管理员' },
// ── 导航 / Navigation ──
'home': { cn: '首页' },
'login': { cn: '登录' },
@ -95,11 +118,11 @@ export const dict: Record<string, TranslationEntry> = {
'no street snap id specified': { cn: '未指定街拍 ID' },
'failed to load street snap. please refresh or try later.': { cn: '加载街拍失败,请刷新或稍后再试' },
'brands': { cn: '品牌' },
'Year': { cn: '年份' },
'Sort': { cn: '排序' },
'Most photos': { cn: '照片最多' },
'Newest year': { cn: '年份最新' },
'Oldest year': { cn: '年份最早' },
'year': { cn: '年份' },
'sort': { cn: '排序' },
'most photos': { cn: '照片最多' },
'newest year': { cn: '年份最新' },
'oldest year': { cn: '年份最早' },
// ── 地区 / Cities(街拍按城市分类,值对齐后端 street_snap.city 列;translate 会小写化 key)──
'city': { cn: '地区' },