276 lines
11 KiB
Plaintext
276 lines
11 KiB
Plaintext
---
|
|
import '../styles/global.css'
|
|
import { getLocaleFromUrl, useTranslations, useTranslatedPath } from '../i18n/utils'
|
|
import { LOCALES, LOCALE_NAMES, isLocaleSegment } from '../i18n/config'
|
|
|
|
const {
|
|
title = 'Portfolio Studio',
|
|
activeCategory = 'Shows',
|
|
activeNavTab,
|
|
activeThemePalette = null,
|
|
} = Astro.props;
|
|
|
|
const locale = getLocaleFromUrl(Astro.url)
|
|
const { t } = useTranslations(locale)
|
|
const translatePath = useTranslatedPath(locale)
|
|
|
|
// 当前路径去掉语言前缀,用于语言切换器拼出「同页面、另一语言」的链接
|
|
const _segs = Astro.url.pathname.split('/').filter(Boolean);
|
|
const _stripped = _segs.length > 0 && isLocaleSegment(_segs[0])
|
|
? '/' + _segs.slice(1).join('/')
|
|
: Astro.url.pathname;
|
|
const currentPath = _stripped === '' ? '/' : _stripped;
|
|
|
|
const categories: { label: string; value: PortfolioCategory | 'Trending Colors' }[] = [
|
|
{ label: 'x', value: 'x' },
|
|
{ label: 'Backstage', value: 'Backstage' },
|
|
{ label: 'Events', value: 'Events' },
|
|
{ label: 'Street', value: 'Street' },
|
|
{ label: 'Trending Colors', value: 'Trending Colors' },
|
|
];
|
|
---
|
|
<!doctype html>
|
|
<html lang={locale === 'cn' ? 'zh-CN' : 'en'}>
|
|
<head>
|
|
<meta charset="utf-8" />
|
|
<meta name="viewport" content="width=device-width" />
|
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
<link rel="icon" href="/favicon.ico" />
|
|
<meta name="generator" content={Astro.generator} />
|
|
<title>{title}</title>
|
|
<!-- 语言自动检测:无前缀且浏览器为中文环境时,跳转到 /cn 前缀 -->
|
|
<script is:inline>
|
|
(function () {
|
|
try {
|
|
var SUPPORTED = ['en', 'cn'];
|
|
var path = location.pathname;
|
|
var seg = path.split('/').filter(Boolean)[0];
|
|
if (SUPPORTED.indexOf(seg) !== -1) return; // 已有语言前缀
|
|
var lang = (navigator.language || 'en').toLowerCase();
|
|
var target = lang.indexOf('zh') === 0 ? 'cn' : 'en';
|
|
if (target === 'en') return; // 默认英文无需跳转
|
|
if (sessionStorage.getItem('lang-redirect')) return;
|
|
sessionStorage.setItem('lang-redirect', '1');
|
|
location.pathname = '/' + target + path;
|
|
} catch (e) {}
|
|
})();
|
|
</script>
|
|
</head>
|
|
<body class="bg-white text-black min-h-screen flex flex-col p-[30px_50px] font-sans">
|
|
|
|
<!-- 顶部主导航栏 -->
|
|
<nav class="flex justify-between items-center text-[13px] font-bold tracking-[0.5px] uppercase">
|
|
<div class="flex flex-wrap items-center gap-6 md:gap-8">
|
|
<a href={translatePath('/portfolio')} class="relative pb-[2px] cursor-pointer transition-opacity duration-200 flex items-center gap-1.5 hover:opacity-60 opacity-80 border-b-2 border-transparent">{t('Portfolio')}<span class="text-[10px] bg-black text-white px-1.5 py-0.5 rounded-full font-mono tracking-tight leading-none">
|
|
2026
|
|
</span></a>
|
|
<a href={translatePath('/shows')} class="relative pb-[2px] cursor-pointer hover:opacity-60 transition-opacity duration-200">{t('All Shows')}</a>
|
|
<a href={translatePath('/latest-projects')} class="relative pb-[2px] cursor-pointer hover:opacity-60 transition-opacity duration-200">{t('Latest Projects')}</a>
|
|
<a href={translatePath('/about')} class="relative pb-[2px] cursor-pointer hover:opacity-60 transition-opacity duration-200">{t('About')}</a>
|
|
<a href={translatePath('/contact')} class="relative pb-[2px] cursor-pointer hover:opacity-60 transition-opacity duration-200">{t('Contact Us')}</a>
|
|
</div>
|
|
|
|
<div class="flex items-center gap-3" id="auth-slot">
|
|
<div class="flex items-center gap-2 normal-case" aria-label="Language">
|
|
{LOCALES.map((l) => (
|
|
<a
|
|
href={translatePath(currentPath, l)}
|
|
aria-current={l === locale ? 'true' : undefined}
|
|
class:list={[
|
|
'cursor-pointer hover:opacity-60 transition-opacity duration-200',
|
|
l === locale ? 'opacity-100 underline underline-offset-4' : 'opacity-40',
|
|
]}
|
|
>{LOCALE_NAMES[l]}</a>
|
|
))}
|
|
</div>
|
|
<span class="opacity-25">/</span>
|
|
<a href={translatePath('/login')} class="hover:opacity-60 transition-opacity duration-200">{t('Login')}</a>
|
|
<a href={translatePath('/register')} class="hover:opacity-60 transition-opacity duration-200">{t('Signup')}</a>
|
|
</div>
|
|
</nav>
|
|
|
|
<div class="flex-1 flex flex-col">
|
|
<slot />
|
|
</div>
|
|
|
|
<footer class="relative z-[1] bg-white mt-20 pt-10 border-t-2 border-black text-black w-full font-sans">
|
|
<script>
|
|
import { getUser, fetchMe, clearSession } from "../lib/auth.ts"
|
|
const slot = document.getElementById("auth-slot")
|
|
function render(user) {
|
|
if (user) {
|
|
slot.innerHTML = `<span class="opacity-70">Hi, ${user.username}</span><button id="logout-btn" class="hover:opacity-60 transition-opacity cursor-pointer bg-transparent border-0 p-0 font-bold text-[13px] uppercase tracking-[0.5px]">Logout</button>`
|
|
const btn = slot.querySelector("#logout-btn")
|
|
if (btn) btn.addEventListener("click", () => { clearSession(); location.reload() })
|
|
} else {
|
|
slot.innerHTML = `<a href="/login" class="hover:opacity-60 transition-opacity duration-200">Login</a><a href="/register" class="hover:opacity-60 transition-opacity duration-200">Signup</a>`
|
|
}
|
|
}
|
|
const cached = getUser()
|
|
if (cached) render(cached)
|
|
fetchMe().then((user) => render(user)).catch(() => {})
|
|
</script>
|
|
{/* Top Footer Grid */}
|
|
<div class="grid grid-cols-1 md:grid-cols-12 gap-8 mb-10 pb-10 border-b border-zinc-200">
|
|
|
|
{/* 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">
|
|
Portfolio Studio
|
|
</span>
|
|
<span class="text-[10px] font-mono bg-black text-white px-2 py-0.5 font-bold uppercase tracking-widest">
|
|
2026 Edition
|
|
</span>
|
|
</div>
|
|
|
|
<p class="text-xs text-zinc-600 leading-relaxed max-w-md">
|
|
Minimalist editorial portfolio documenting haute couture shows, backstage fittings, and street culture. Integrated with 2026 color forecast intelligence and AI palette generator.
|
|
</p>
|
|
|
|
{/* Location Badge */}
|
|
<div class="flex flex-wrap gap-3 text-[11px] font-mono text-zinc-500 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>
|
|
</div>
|
|
|
|
{/* Column 2: Quick Navigation */}
|
|
<div class="md:col-span-3 space-y-3">
|
|
<span class="text-[11px] font-mono uppercase tracking-widest text-zinc-400 font-bold block mb-2">
|
|
Categories
|
|
</span>
|
|
<ul class="space-y-2 list-none p-0 m-0 text-xs font-mono font-bold uppercase tracking-wider">
|
|
{categories.map((cat) => (
|
|
<li>
|
|
<button
|
|
data-category={cat.value}
|
|
class={`category-btn hover:opacity-60 transition-opacity cursor-pointer flex items-center gap-2 ${
|
|
activeCategory === cat.value ? 'underline font-black text-black' : 'text-zinc-600'
|
|
}`}
|
|
>
|
|
<span>{cat.label}</span>
|
|
{cat.value === 'Trending Colors' && (
|
|
<span class="w-1.5 h-1.5 rounded-full bg-amber-400"></span>
|
|
)}
|
|
</button>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</div>
|
|
|
|
{/* Column 3: Color Studio & Actions */}
|
|
<div class="md:col-span-4 space-y-3">
|
|
<span class="text-[11px] font-mono uppercase tracking-widest text-zinc-400 font-bold block mb-2">
|
|
Color Studio Tools
|
|
</span>
|
|
|
|
<div class="flex flex-col gap-2">
|
|
<button
|
|
id="btn-ai-generator"
|
|
class="w-full bg-black text-white hover:bg-zinc-800 text-xs font-mono font-bold uppercase tracking-wider py-2.5 px-4 flex items-center justify-between transition-colors cursor-pointer"
|
|
>
|
|
<div class="flex items-center gap-2">
|
|
<!-- <Sparkles class="w-3.5 h-3.5 text-amber-300" /> -->
|
|
<span>AI Color Trend Generator</span>
|
|
</div>
|
|
<span class="text-[10px] text-zinc-400 font-normal">Gemini 2026</span>
|
|
</button>
|
|
|
|
<button
|
|
id="btn-extractor"
|
|
class="w-full bg-zinc-100 hover:bg-zinc-200 border border-zinc-300 text-black text-xs font-mono font-bold uppercase tracking-wider py-2.5 px-4 flex items-center justify-between transition-colors cursor-pointer"
|
|
>
|
|
<div class="flex items-center gap-2">
|
|
<!-- <Layers class="w-3.5 h-3.5 text-zinc-600" /> -->
|
|
<span>Photo Swatch Extractor</span>
|
|
</div>
|
|
<span class="text-[10px] text-zinc-500 font-normal">Upload</span>
|
|
</button>
|
|
|
|
{activeThemePalette && (
|
|
<button
|
|
id="btn-reset-theme"
|
|
class="w-full bg-red-50 hover:bg-red-100 border border-red-200 text-red-700 text-xs font-mono font-bold uppercase tracking-wider py-2 px-4 flex items-center justify-between transition-colors cursor-pointer"
|
|
>
|
|
<div class="flex items-center gap-2">
|
|
{/* <RefreshCw class="w-3.5 h-3.5" /> */}
|
|
<span>Reset B&W Theme</span>
|
|
</div>
|
|
<span class="text-[10px] opacity-80">Default</span>
|
|
</button>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
{/* Active Theme Strip */}
|
|
{activeThemePalette && (
|
|
<div class="mb-6 p-4 bg-zinc-50 border border-zinc-200 flex flex-col sm:flex-row justify-between items-start sm:items-center gap-3">
|
|
<div class="flex items-center gap-2 text-xs font-mono">
|
|
<span class="w-2.5 h-2.5 rounded-full bg-emerald-500 animate-pulse"></span>
|
|
<span class="font-bold uppercase">Active Site Theme:</span>
|
|
<span class="text-zinc-600">{activeThemePalette.name}</span>
|
|
</div>
|
|
|
|
<div class="flex gap-1 h-6 w-48 border border-zinc-300 overflow-hidden">
|
|
{activeThemePalette.colors.map((c) => (
|
|
<div
|
|
style={`background-color: ${c.hex}`}
|
|
class="h-full flex-1"
|
|
title={`${c.name} (${c.hex})`}
|
|
></div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Bottom Legal & Back To Top */}
|
|
<div class="flex flex-col sm:flex-row justify-between items-center text-[12px] font-mono text-zinc-500 gap-4">
|
|
<div>
|
|
<span>© 2026 PORTFOLIO STUDIO. ALL RIGHTS RESERVED.</span>
|
|
</div>
|
|
|
|
<div class="flex items-center gap-6">
|
|
<a
|
|
href={translatePath('/about')}
|
|
class="nav-btn hover:text-black cursor-pointer uppercase font-bold"
|
|
>
|
|
{t('About')}
|
|
</a>
|
|
<a
|
|
href={translatePath('/contact')}
|
|
class="nav-btn hover:text-black cursor-pointer uppercase font-bold"
|
|
>
|
|
{t('Contact')}
|
|
</a>
|
|
<button
|
|
id="back-to-top"
|
|
class="flex items-center gap-1 font-bold text-black hover:opacity-60 transition-opacity cursor-pointer uppercase"
|
|
>
|
|
<span>{t('Back to top')}</span>
|
|
<!-- <ArrowUp class="w-3.5 h-3.5" /> -->
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mt-6 text-[11px] font-mono text-zinc-400 uppercase tracking-widest">
|
|
{t('© 2026 PORTFOLIO STUDIO. ALL RIGHTS RESERVED.')}
|
|
</div>
|
|
</footer>
|
|
</body>
|
|
</html>
|
|
|
|
<style>
|
|
html,
|
|
body {
|
|
margin: 0;
|
|
}
|
|
</style>
|