update
This commit is contained in:
@ -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>
|
||||
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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>
|
||||
|
||||
|
||||
@ -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: '地区' },
|
||||
|
||||
Reference in New Issue
Block a user