update
This commit is contained in:
@ -23,16 +23,18 @@ const fullLoginLabel = t('go to full login')
|
||||
x-show="isOpen"
|
||||
x-cloak
|
||||
data-auth-fail={failLabel}
|
||||
data-auth-modal
|
||||
class="fixed inset-0 z-[200] flex items-center justify-center bg-black/40 px-5"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-label={title}
|
||||
@click.self="close()"
|
||||
@keydown.escape.window="close()"
|
||||
@keydown.tab="trapFocus($event)"
|
||||
>
|
||||
<div class="w-full max-w-sm border-2 border-black bg-white p-8">
|
||||
<div class="mb-6 flex items-start justify-between border-b-2 border-black pb-4">
|
||||
<h3 class="text-2xl font-black uppercase leading-none tracking-tight">{title}</h3>
|
||||
<h3 class="text-2xl font-semibold uppercase leading-none tracking-display">{title}</h3>
|
||||
<button type="button" @click="close()" class="-mt-1 text-[11px] uppercase tracking-widest opacity-60 transition-opacity hover:opacity-100">{closeLabel}</button>
|
||||
</div>
|
||||
|
||||
@ -40,19 +42,19 @@ const fullLoginLabel = t('go to full login')
|
||||
|
||||
<form @submit.prevent="submit()" class="space-y-6" novalidate>
|
||||
<div>
|
||||
<label class="mb-2 block text-[11px] uppercase tracking-widest text-[var(--c-gray-700)]">{accountLabel}</label>
|
||||
<input type="text" x-model="account" autocomplete="username" required
|
||||
<label for="auth-account" class="mb-2 block text-[11px] uppercase tracking-widest text-[var(--c-gray-700)]">{accountLabel}</label>
|
||||
<input id="auth-account" type="text" x-model="account" autocomplete="username" required
|
||||
class="w-full border-0 border-b border-black/30 bg-transparent px-0 py-2 text-sm outline-none transition-colors focus:border-black focus:ring-0" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-2 block text-[11px] uppercase tracking-widest text-[var(--c-gray-700)]">{passwordLabel}</label>
|
||||
<input type="password" x-model="password" autocomplete="current-password" required
|
||||
<label for="auth-password" class="mb-2 block text-[11px] uppercase tracking-widest text-[var(--c-gray-700)]">{passwordLabel}</label>
|
||||
<input id="auth-password" type="password" x-model="password" autocomplete="current-password" required
|
||||
class="w-full border-0 border-b border-black/30 bg-transparent px-0 py-2 text-sm outline-none transition-colors focus:border-black focus:ring-0" />
|
||||
</div>
|
||||
|
||||
<p x-show="error" x-text="error" x-cloak class="text-xs uppercase tracking-wide text-red-600"></p>
|
||||
|
||||
<button type="submit" :disabled="loading"
|
||||
<button type="submit" :disabled="loading" :aria-busy="loading"
|
||||
class="w-full border border-black bg-black py-3.5 text-[12px] font-bold uppercase tracking-[0.2em] text-white transition-colors hover:bg-white hover:text-black disabled:opacity-40">
|
||||
<span x-show="!loading">{title}</span>
|
||||
<span x-show="loading" x-cloak>…</span>
|
||||
@ -75,6 +77,7 @@ const fullLoginLabel = t('go to full login')
|
||||
interface AuthStore {
|
||||
isOpen: boolean
|
||||
pending: HTMLElement | null
|
||||
lastFocused: HTMLElement | null
|
||||
account: string
|
||||
password: string
|
||||
error: string
|
||||
@ -83,6 +86,7 @@ const fullLoginLabel = t('go to full login')
|
||||
close(): void
|
||||
submit(): Promise<void>
|
||||
goFullLogin(): void
|
||||
trapFocus(e: KeyboardEvent): void
|
||||
}
|
||||
|
||||
document.addEventListener("alpine:init", () => {
|
||||
@ -90,6 +94,7 @@ const fullLoginLabel = t('go to full login')
|
||||
const authStore: AuthStore = {
|
||||
isOpen: false,
|
||||
pending: null as HTMLElement | null,
|
||||
lastFocused: null as HTMLElement | null,
|
||||
account: "",
|
||||
password: "",
|
||||
error: "",
|
||||
@ -98,9 +103,15 @@ const fullLoginLabel = t('go to full login')
|
||||
request(el: HTMLElement): boolean {
|
||||
if (getUser()) return true // 已登录:放行,交还原生点击
|
||||
this.pending = el
|
||||
this.lastFocused = el
|
||||
this.error = ""
|
||||
this.isOpen = true
|
||||
document.body.style.overflow = "hidden" // 锁背景滚动
|
||||
// 焦点移入弹窗:屏幕阅读器立即播报 aria-label,键盘用户不必从页面顶端重新 Tab
|
||||
requestAnimationFrame(() => {
|
||||
const first = document.querySelector<HTMLElement>("[data-auth-modal] input")
|
||||
first?.focus()
|
||||
})
|
||||
return false
|
||||
},
|
||||
|
||||
@ -112,6 +123,11 @@ const fullLoginLabel = t('go to full login')
|
||||
this.pending = null
|
||||
this.error = ""
|
||||
document.body.style.overflow = ""
|
||||
// 关闭后把焦点还给触发它的元素。否则焦点会掉回 <body>,
|
||||
// 键盘用户等于被「传送」回页面顶部,需要重新 Tab 十几下。
|
||||
const back = this.lastFocused
|
||||
this.lastFocused = null
|
||||
if (back && document.contains(back)) back.focus?.()
|
||||
},
|
||||
|
||||
async submit() {
|
||||
@ -142,6 +158,29 @@ const fullLoginLabel = t('go to full login')
|
||||
this.close()
|
||||
window.location.assign(`/${loc}/login?redirect=${encodeURIComponent(p)}`)
|
||||
},
|
||||
|
||||
// 焦点陷阱:aria-modal="true" 只是「声明」,不会自动把 Tab 关在弹窗里。
|
||||
// 不实现这个,用户 Tab 几下就会跑到背后被遮住的页面元素上。
|
||||
trapFocus(e: KeyboardEvent) {
|
||||
const root = document.querySelector<HTMLElement>("[data-auth-modal]")
|
||||
if (!root) return
|
||||
const items = Array.from(
|
||||
root.querySelectorAll<HTMLElement>(
|
||||
'a[href], button:not([disabled]), input:not([disabled]), select, textarea, [tabindex]:not([tabindex="-1"])'
|
||||
)
|
||||
).filter((el) => el.offsetParent !== null)
|
||||
if (items.length === 0) return
|
||||
const first = items[0]
|
||||
const last = items[items.length - 1]
|
||||
if (!first || !last) return
|
||||
if (e.shiftKey && document.activeElement === first) {
|
||||
e.preventDefault()
|
||||
last.focus()
|
||||
} else if (!e.shiftKey && document.activeElement === last) {
|
||||
e.preventDefault()
|
||||
first.focus()
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
Alpine.store("auth", authStore)
|
||||
|
||||
@ -48,7 +48,7 @@ const moduleLabel = locale === 'cn' ? moduleZh || module : module || moduleZh
|
||||
@apply text-[11px] tracking-[0.25em] uppercase text-[var(--c-gray-800)];
|
||||
}
|
||||
.cs-title {
|
||||
@apply font-black tracking-[-0.02em];
|
||||
@apply font-bold tracking-[-0.02em];
|
||||
line-height: 0.9;
|
||||
font-size: clamp(56px, 13vw, 128px);
|
||||
margin: 0.16em 0 0.3em;
|
||||
|
||||
@ -132,7 +132,7 @@ const hasImages = previewImages.length > 0
|
||||
{
|
||||
hasItem ? (
|
||||
<article class="mx-auto max-w-[1600px] px-5 md:px-10 py-12 md:py-16">
|
||||
<h1 class="font-black leading-[0.95] tracking-tight text-[10vw] md:text-[4.5rem] mt-8">
|
||||
<h1 class="font-bold leading-[0.95] tracking-tight text-[10vw] md:text-[4.5rem] mt-8">
|
||||
{title}
|
||||
</h1>
|
||||
|
||||
|
||||
@ -38,7 +38,7 @@ const browseLabel = t("browse the archive")
|
||||
<!-- ===== 未登录:整版引导 ===== -->
|
||||
<div x-show="!user" x-cloak class="flex flex-col items-center justify-center border border-black/15 px-6 py-32 text-center">
|
||||
<p class="mb-5 text-[11px] uppercase tracking-[0.3em] text-[var(--c-gray-600)]">{pleaseLabel}</p>
|
||||
<p class="mb-12 text-5xl font-black uppercase leading-none tracking-tight md:text-7xl">{titleLabel}</p>
|
||||
<p class="mb-12 text-5xl font-bold uppercase leading-none tracking-tight md:text-7xl">{titleLabel}</p>
|
||||
<a href={loginUrl} class="inline-flex items-center justify-center border border-black bg-black px-10 py-4 text-[12px] font-bold uppercase tracking-[0.22em] text-white transition-colors hover:bg-white hover:text-black">{loginLabel}</a>
|
||||
</div>
|
||||
|
||||
@ -50,7 +50,7 @@ const browseLabel = t("browse the archive")
|
||||
<div class="min-w-0">
|
||||
<p class="mb-4 text-[11px] uppercase tracking-[0.3em] text-[var(--c-gray-600)]">{t("member profile")}</p>
|
||||
<div class="flex flex-wrap items-end gap-x-4">
|
||||
<h1 class="text-6xl font-black uppercase leading-[0.88] tracking-tight md:text-8xl" x-text="(user?.username || '')"></h1>
|
||||
<h1 class="text-6xl font-bold uppercase leading-[0.88] tracking-tight md:text-8xl" x-text="(user?.username || '')"></h1>
|
||||
<!-- VIP 徽标:仅高级用户(图集详情页可见高清原图)显示 -->
|
||||
<span x-show="user?.tier === 'vip'" x-cloak class="mb-2 inline-block border border-black bg-black px-2.5 py-1 text-[10px] font-bold uppercase tracking-[0.3em] text-white">{t("vip")}</span>
|
||||
</div>
|
||||
@ -85,7 +85,7 @@ const browseLabel = t("browse the archive")
|
||||
<div x-show="tab==='saved'" x-cloak>
|
||||
|
||||
<div class="mb-10 flex flex-wrap items-end justify-between gap-x-8 gap-y-4">
|
||||
<h2 class="text-4xl font-black uppercase leading-none tracking-tight md:text-6xl">{savedLooksLabel}</h2>
|
||||
<h2 class="text-4xl font-bold uppercase leading-none tracking-tight md:text-6xl">{savedLooksLabel}</h2>
|
||||
<p class="pb-1 text-[11px] uppercase tracking-[0.22em] text-[var(--c-gray-600)]"><span class="font-bold text-black" x-text="favoritesCount"></span> {itemsLabel}</p>
|
||||
</div>
|
||||
|
||||
@ -97,7 +97,7 @@ const browseLabel = t("browse the archive")
|
||||
|
||||
<!-- 空态 -->
|
||||
<div x-show="favorites.length === 0" class="flex flex-col items-center justify-center border-y border-black/10 py-28 text-center">
|
||||
<p class="mb-10 text-3xl font-black uppercase tracking-tight text-black/80 md:text-4xl">{noSavedLabel}</p>
|
||||
<p class="mb-10 text-3xl font-bold uppercase tracking-tight text-black/80 md:text-4xl">{noSavedLabel}</p>
|
||||
<a href={archiveUrl} class="inline-flex items-center justify-center border border-black bg-white px-8 py-3.5 text-[11px] font-bold uppercase tracking-[0.2em] text-black transition-colors hover:bg-black hover:text-white">{browseLabel}</a>
|
||||
</div>
|
||||
|
||||
@ -136,7 +136,7 @@ const browseLabel = t("browse the archive")
|
||||
<div x-show="tab==='history'" x-cloak>
|
||||
|
||||
<div class="mb-10 flex flex-wrap items-end justify-between gap-x-8 gap-y-4">
|
||||
<h2 class="text-4xl font-black uppercase leading-none tracking-tight md:text-6xl">{historyLabel}</h2>
|
||||
<h2 class="text-4xl font-bold uppercase leading-none tracking-tight md:text-6xl">{historyLabel}</h2>
|
||||
<div class="flex items-baseline gap-8 pb-1">
|
||||
<p class="text-[11px] uppercase tracking-[0.22em] text-[var(--c-gray-600)]"><span class="font-bold text-black" x-text="histTotal"></span> {itemsLabel}</p>
|
||||
<button x-show="histTotal > 0" @click="clearAllHistory()" class="text-[11px] uppercase tracking-[0.2em] text-[var(--c-gray-600)] underline-offset-4 transition-colors hover:text-black hover:underline">{clearHistoryLabel}</button>
|
||||
@ -145,7 +145,7 @@ const browseLabel = t("browse the archive")
|
||||
|
||||
<!-- 空态 -->
|
||||
<div x-show="histTotal === 0" class="flex flex-col items-center justify-center border-y border-black/10 py-28 text-center">
|
||||
<p class="mb-10 text-3xl font-black uppercase tracking-tight text-black/80 md:text-4xl">{noHistoryLabel}</p>
|
||||
<p class="mb-10 text-3xl font-bold uppercase tracking-tight text-black/80 md:text-4xl">{noHistoryLabel}</p>
|
||||
<a href={archiveUrl} class="inline-flex items-center justify-center border border-black bg-white px-8 py-3.5 text-[11px] font-bold uppercase tracking-[0.2em] text-black transition-colors hover:bg-black hover:text-white">{browseLabel}</a>
|
||||
</div>
|
||||
|
||||
|
||||
@ -46,10 +46,10 @@ const streetItems: {
|
||||
<div x-data="{ activeTab: 'backstage' }" class="flex flex-col min-h-screen">
|
||||
|
||||
<!-- 二级分类标签页 -->
|
||||
<div id="tabbar" class="mt-16 md:mt-24">
|
||||
<ul class="flex items-center gap-6 list-none p-0 m-0 overflow-x-auto">
|
||||
<div id="tabbar" class="mt-20 md:mt-28">
|
||||
<ul class="flex items-center gap-7 md:gap-9 list-none p-0 m-0 overflow-x-auto no-scrollbar">
|
||||
<li
|
||||
class="tab text-[42px] font-extrabold cursor-pointer whitespace-nowrap"
|
||||
class="tab text-[clamp(1.875rem,4.6vw,3.25rem)] font-bold tracking-display leading-[0.95] cursor-pointer whitespace-nowrap"
|
||||
:class="{ 'is-active': activeTab === 'backstage' }"
|
||||
@click="activeTab = 'backstage'"
|
||||
>
|
||||
@ -57,7 +57,7 @@ const streetItems: {
|
||||
</li>
|
||||
|
||||
<li
|
||||
class="tab text-[42px] font-extrabold cursor-pointer whitespace-nowrap"
|
||||
class="tab text-[clamp(1.875rem,4.6vw,3.25rem)] font-bold tracking-display leading-[0.95] cursor-pointer whitespace-nowrap"
|
||||
:class="{ 'is-active': activeTab === 'street' }"
|
||||
@click="activeTab = 'street'"
|
||||
>
|
||||
@ -65,7 +65,7 @@ const streetItems: {
|
||||
</li>
|
||||
|
||||
<li
|
||||
class="tab text-[42px] font-extrabold cursor-pointer whitespace-nowrap"
|
||||
class="tab text-[clamp(1.875rem,4.6vw,3.25rem)] font-bold tracking-display leading-[0.95] cursor-pointer whitespace-nowrap"
|
||||
:class="{ 'is-active': activeTab === 'events' }"
|
||||
@click="activeTab = 'events'"
|
||||
>
|
||||
@ -75,11 +75,11 @@ const streetItems: {
|
||||
</div>
|
||||
|
||||
<!-- 中间内容展示区 -->
|
||||
<main class="grow min-h-[400px] mt-10">
|
||||
<main class="grow min-h-[400px] mt-14">
|
||||
|
||||
<!-- Runway -->
|
||||
<div class="panel" x-show="activeTab === 'backstage'" x-transition.opacity.duration.300ms>
|
||||
<div class="text-2xl font-medium mb-4">
|
||||
<div class="text-[18px] uppercase tracking-label font-semibold mb-6 text-[var(--c-gray-800)]">
|
||||
Hot Brand
|
||||
</div>
|
||||
|
||||
@ -122,7 +122,7 @@ const streetItems: {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="hs-controls flex justify-between items-center text-[13px] font-bold">
|
||||
<div class="hs-controls flex justify-between items-center text-[13px] font-medium">
|
||||
<div
|
||||
class="text-[16px] hs-arrow hs-prev cursor-pointer hover:opacity-60 transition-opacity duration-200"
|
||||
role="button"
|
||||
@ -209,14 +209,15 @@ const streetItems: {
|
||||
|
||||
/* 标签 */
|
||||
.tab {
|
||||
@apply relative pb-[6px] text-[var(--c-gray-600)] transition-colors duration-200 no-underline;
|
||||
@apply relative pb-[6px] text-[var(--c-gray-500)] transition-colors duration-[240ms] no-underline;
|
||||
}
|
||||
.tab:hover {
|
||||
@apply text-[var(--c-gray-800)];
|
||||
}
|
||||
.tab::after {
|
||||
content: "";
|
||||
@apply absolute left-0 right-0 bottom-0 h-[2px] bg-black scale-x-0 origin-left transition-transform duration-[250ms] ease-in-out;
|
||||
@apply absolute left-0 right-0 bottom-0 h-[1px] bg-black scale-x-0 origin-left transition-transform duration-[240ms];
|
||||
transition-timing-function: var(--ease-editorial, ease);
|
||||
}
|
||||
.tab.is-active {
|
||||
@apply text-black;
|
||||
@ -367,18 +368,18 @@ const streetItems: {
|
||||
.hs-cap-brand {
|
||||
@apply font-sans;
|
||||
font-size: 21px;
|
||||
font-weight: 700;
|
||||
font-weight: 500;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.02em;
|
||||
letter-spacing: 0.06em;
|
||||
line-height: 1.1;
|
||||
text-shadow: 0 1px 4px rgba(0, 0, 0, 0.55);
|
||||
}
|
||||
.hs-cap-meta {
|
||||
@apply font-sans;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
opacity: 0.9;
|
||||
letter-spacing: 0.04em;
|
||||
font-weight: 400;
|
||||
opacity: 0.85;
|
||||
letter-spacing: 0.02em;
|
||||
line-height: 1.3;
|
||||
text-shadow: 0 1px 3px rgba(0, 0, 0, 0.5);
|
||||
max-width: 90%;
|
||||
@ -405,8 +406,12 @@ const streetItems: {
|
||||
@apply mb-9;
|
||||
}
|
||||
.ss-grid {
|
||||
@apply grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 gap-[14px] md:gap-[18px];
|
||||
@apply grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 gap-4 md:gap-5;
|
||||
}
|
||||
/* 刻意不做逐项入场动画。
|
||||
frontend-design skill 把它列为「AI 生成的识别特征」:
|
||||
每个 section 淡入上滑、每张卡片都错落出现,是通用水印,不承载信息。
|
||||
这里只保留图片加载的 blur/opacity 淡入(那是真实的状态变化)。 */
|
||||
.ss-card {
|
||||
@apply relative block w-full overflow-hidden bg-[var(--c-gray-100)] no-underline text-inherit;
|
||||
aspect-ratio: 3 / 4;
|
||||
@ -483,17 +488,17 @@ const streetItems: {
|
||||
pointer-events: none;
|
||||
}
|
||||
.ss-cap-loc {
|
||||
font-size: clamp(18px, 2vw, 26px);
|
||||
font-weight: 700;
|
||||
font-size: clamp(1.125rem, 2vw, 1.625rem);
|
||||
font-weight: 500;
|
||||
line-height: 1.1;
|
||||
letter-spacing: -0.01em;
|
||||
text-shadow: 0 1px 4px rgba(0, 0, 0, 0.55);
|
||||
}
|
||||
.ss-cap-event {
|
||||
font-size: clamp(10px, 0.8vw, 12px);
|
||||
font-weight: 500;
|
||||
font-size: clamp(0.625rem, 0.8vw, 0.75rem);
|
||||
font-weight: 400;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.16em;
|
||||
letter-spacing: 0.18em;
|
||||
line-height: 1.4;
|
||||
opacity: 0.85;
|
||||
text-shadow: 0 1px 3px rgba(0, 0, 0, 0.5);
|
||||
|
||||
@ -171,7 +171,7 @@ const sheet = [...new Set([...showCovers, ...snapCovers])].slice(0, 6)
|
||||
<!-- 底部刊名(压在图上) -->
|
||||
<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-3 text-5xl font-bold 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>
|
||||
</aside>
|
||||
@ -184,7 +184,7 @@ const sheet = [...new Set([...showCovers, ...snapCovers])].slice(0, 6)
|
||||
<span class="inline-block h-[7px] w-[7px] bg-black"></span>{t('members only')}
|
||||
</p>
|
||||
|
||||
<h1 class="rise rise-2 mt-5 text-[clamp(52px,8vw,104px)] font-black uppercase leading-[0.82] tracking-[-0.045em]">
|
||||
<h1 class="rise rise-2 mt-5 text-[clamp(52px,8vw,104px)] font-bold uppercase leading-[0.82] tracking-[-0.045em]">
|
||||
{loginLabel}
|
||||
</h1>
|
||||
|
||||
|
||||
@ -62,6 +62,11 @@ const pathWithoutLocale = locales.includes(pathName.split('/')[1])
|
||||
? pathName.replace(`/${locale}`, '')
|
||||
: pathName;
|
||||
|
||||
// 导航当前项:给对应链接加 aria-current="page",屏幕阅读器据此播报「当前页面」。
|
||||
// 只比较去尾斜杠后的路径,避免 /runway-looks 与 /runway-looks/ 被判成两项。
|
||||
const normPath = (p: string) => p.replace(/\/+$/, '') || '/';
|
||||
const isCurrent = (target: string) => normPath(pathWithoutLocale) === normPath(target);
|
||||
|
||||
// 顶部导航数据模型(替代原 Alpine x-data 的 menu/state)。
|
||||
// 下拉菜单与语言列表均由此驱动渲染,逻辑用 Astro 原生 script 控制开合。
|
||||
const headers = {
|
||||
@ -136,11 +141,18 @@ const headers = {
|
||||
<!-- 加载动画屏:独立组件,标记/样式/隐藏逻辑封装在 LoadingOverlay.astro -->
|
||||
<LoadingOverlay />
|
||||
|
||||
<!-- 键盘用户跳过导航:平时视觉隐藏,Tab 聚焦时出现在左上角。
|
||||
没有它,键盘用户每进一页都要按十几次 Tab 才能摸到正文。 -->
|
||||
<a
|
||||
href="#main"
|
||||
class="sr-only focus:not-sr-only focus:fixed focus:left-4 focus:top-4 focus:z-[100] focus:bg-black focus:px-4 focus:py-2 focus:text-[12px] focus:uppercase focus:tracking-label focus:text-white"
|
||||
>跳到主内容</a>
|
||||
|
||||
<!-- 顶部主导航栏 -->
|
||||
<nav class="flex justify-between items-center text-[13px] font-bold tracking-[0.5px] uppercase">
|
||||
<nav class="flex justify-between items-center text-[13px] font-semibold tracking-label uppercase">
|
||||
<div class="flex flex-wrap items-center gap-6 md:gap-8">
|
||||
<a href={href(locale, ROUTES.home)} class="relative pb-[2px] cursor-pointer hover:opacity-60 transition-opacity duration-200">{t('Home')}</a>
|
||||
<a href={href(locale, ROUTES.home)} 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">
|
||||
<a href={href(locale, ROUTES.home)} class="nav-link cursor-pointer" aria-current={isCurrent(ROUTES.home) ? 'page' : undefined}>{t('Home')}</a>
|
||||
<a href={href(locale, ROUTES.home)} class="nav-link cursor-pointer flex items-center gap-1.5 opacity-70">
|
||||
{t('Portfolio')}
|
||||
<span class="text-[10px] bg-black text-white px-1.5 py-0.5 rounded-full tracking-tight leading-none">
|
||||
2026
|
||||
@ -149,7 +161,7 @@ const headers = {
|
||||
<!-- 全部的 runway:菜单项用 Astro 渲染(headers.runways.map),开合逻辑用 Alpine.js -->
|
||||
<div class="relative" x-data="{ open: false }" @mouseenter="open = true" @mouseleave="open = false">
|
||||
<div class="flex items-center gap-1">
|
||||
<a href={href(locale, ROUTES.runwayLooks)} class="relative pb-[2px] cursor-pointer hover:opacity-60 transition-opacity duration-200">{t('all runway looks')}</a>
|
||||
<a href={href(locale, ROUTES.runwayLooks)} class="nav-link cursor-pointer" aria-current={isCurrent(ROUTES.runwayLooks) ? 'page' : undefined}>{t('all runway looks')}</a>
|
||||
<button type="button" @click="open = !open" @click.outside="open = false" @keydown.escape.window="open = false" class="pb-[2px] hover:opacity-60 transition-opacity duration-200 flex items-center" aria-label="展开 runway 列表" aria-haspopup="true" :aria-expanded="open">
|
||||
<svg class="w-2.5 h-2.5 transition-transform duration-200" :class="open ? 'rotate-180' : ''" viewBox="0 0 10 6" fill="none"><path d="M1 1l4 4 4-4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>
|
||||
</button>
|
||||
@ -167,7 +179,7 @@ const headers = {
|
||||
<!-- 街拍:菜单项用 Astro 渲染(headers.streets.map),开合逻辑用 Alpine.js -->
|
||||
<div class="relative" x-data="{ open: false }" @mouseenter="open = true" @mouseleave="open = false">
|
||||
<div class="flex items-center gap-1">
|
||||
<a href={href(locale, ROUTES.streetSnaps)} class="relative pb-[2px] cursor-pointer hover:opacity-60 transition-opacity duration-200">{t('Street Style')}</a>
|
||||
<a href={href(locale, ROUTES.streetSnaps)} class="nav-link cursor-pointer" aria-current={isCurrent(ROUTES.streetSnaps) ? 'page' : undefined}>{t('Street Style')}</a>
|
||||
<button type="button" @click="open = !open" @click.outside="open = false" @keydown.escape.window="open = false" class="pb-[2px] hover:opacity-60 transition-opacity duration-200 flex items-center" aria-label="展开 street style 列表" aria-haspopup="true" :aria-expanded="open">
|
||||
<svg viewBox="0 0 12 12" width="10" height="10"><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>
|
||||
@ -207,7 +219,7 @@ const headers = {
|
||||
@click={`open = false; pick('${l.code}')`}
|
||||
class:list={[
|
||||
'block px-4 py-1.5 text-[13px] uppercase tracking-[0.5px] hover:bg-black/5 transition-colors',
|
||||
l.code === locale ? 'font-bold' : 'opacity-60',
|
||||
l.code === locale ? 'font-medium' : 'opacity-60',
|
||||
]}
|
||||
>{l.label}</a>
|
||||
</li>
|
||||
@ -218,7 +230,7 @@ const headers = {
|
||||
<!-- 未登录:Login 链接(Alpine 登录后置为隐藏)。
|
||||
login-redirect:交给 AuthModal 的全局门禁捕获,带上「当前页」跳登录页,
|
||||
登录成功后回到原页,而不是一律落到个人中心(无 JS 时回落 href 到 /login)。 -->
|
||||
<a x-show="!user" x-cloak login-redirect href={href(locale, ROUTES.login)} class="hover:opacity-60 transition-opacity duration-200 uppercase tracking-[0.5px] text-[13px]">{t('Login')}</a>
|
||||
<a x-show="!user" x-cloak login-redirect href={href(locale, ROUTES.login)} class="nav-link uppercase tracking-label text-[13px]">{t('Login')}</a>
|
||||
|
||||
<!-- 已登录:账号下拉(hover 展开,与语言切换器同款交互) -->
|
||||
<div x-show="user" x-cloak class="relative" @mouseenter="open = true" @mouseleave="open = false">
|
||||
@ -242,7 +254,22 @@ const headers = {
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="flex-1 flex flex-col">
|
||||
<!-- 公告条:放在导航之下,作为头部的收边。
|
||||
纯 CSS 循环(内容重复两遍,轨道左移 50% 即为一个周期),零 JS。 -->
|
||||
<div class="marquee -mx-5 md:-mx-[50px] mt-6 border-b border-black/10 py-2 text-[11px] uppercase tracking-wide text-[var(--c-gray-800)]">
|
||||
<div class="marquee__track">
|
||||
{Array.from({ length: 2 }).map(() => (
|
||||
<Fragment>
|
||||
<span>✦ 2026 秋冬时装周档案</span>
|
||||
<span>✦ Runway · Street · Archive</span>
|
||||
<span>✦ 全球品牌持续收录</span>
|
||||
<span>✦ New Season</span>
|
||||
</Fragment>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="main" tabindex="-1" class="flex-1 flex flex-col">
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
@ -349,21 +376,21 @@ const headers = {
|
||||
|
||||
{/* 品牌 */}
|
||||
<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">
|
||||
<p class="text-[10px] uppercase tracking-wide text-white/35">{t('Archive')}</p>
|
||||
<p class="mt-5 text-5xl font-bold uppercase leading-[0.85] tracking-display md:text-7xl">
|
||||
{SITE_NAME}
|
||||
</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">
|
||||
<div class="mt-8 flex flex-wrap items-center gap-x-3 gap-y-2 text-[10px] uppercase tracking-wide text-white/30">
|
||||
<span>Paris</span><span>·</span><span>Milan</span><span>·</span><span>Tokyo</span><span>·</span><span>New York</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 导航 */}
|
||||
<div class="md:col-span-3 md:col-start-8">
|
||||
<h3 class="text-[10px] uppercase tracking-[0.3em] text-white/35">{t('Explore')}</h3>
|
||||
<h3 class="text-[10px] uppercase tracking-wide 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>
|
||||
@ -373,7 +400,7 @@ const headers = {
|
||||
|
||||
{/* 站点操作(全部是真实可用的入口,不留死链) */}
|
||||
<div class="md:col-span-3">
|
||||
<h3 class="text-[10px] uppercase tracking-[0.3em] text-white/35">{t('Site')}</h3>
|
||||
<h3 class="text-[10px] uppercase tracking-wide 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">
|
||||
@ -394,9 +421,9 @@ const headers = {
|
||||
</div>
|
||||
|
||||
{/* 底部条 */}
|
||||
<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">
|
||||
<div class="mt-14 flex flex-col gap-4 border-t border-white/10 pt-6 text-[10px] uppercase tracking-label 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>
|
||||
<p class="tracking-wide">{t('Runway · Street · Archive')}</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
@ -10,7 +10,7 @@ Astro.response.status = 404
|
||||
|
||||
<Layout title={t('Page not found')}>
|
||||
<div class="flex flex-1 flex-col items-center justify-center min-h-[50vh] text-center gap-6">
|
||||
<h1 class="text-6xl font-black tracking-tight">404</h1>
|
||||
<h1 class="text-6xl font-medium tracking-tight">404</h1>
|
||||
<p class="text-sm text-[var(--c-gray-700)] uppercase tracking-[0.2em]">{t('Page not found')}</p>
|
||||
<a
|
||||
href={href(locale, ROUTES.home)}
|
||||
|
||||
@ -1,6 +1,57 @@
|
||||
@import "tailwindcss";
|
||||
|
||||
/* 中性灰阶令牌:统一全站散落的 #f1f1f1/#efefef/#e6e6e6/#ccc/#bdbdbd/#999/#888/#666 等 */
|
||||
/* ============================================================
|
||||
设计令牌(Tailwind v4 @theme)
|
||||
|
||||
参考规律(NABOUN 实站抓取 + ui-ux-pro-max 方法论):
|
||||
1. 字重只用 400 / 500,高级感来自字距与留白,不来自粗体
|
||||
2. 全大写 + 0.12em 字距 + 11~13px 是标签的统一签名
|
||||
3. 1px 发丝线分块,不用色块;全站只保留黑白灰三层明度
|
||||
4. 图片统一竖版,卡片无圆角无遮罩
|
||||
|
||||
约束:不加载任何 webfont(体积与首屏优先),层级靠字重 + 字距 + 尺度建立。
|
||||
============================================================ */
|
||||
@theme {
|
||||
--font-sans: ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI",
|
||||
"PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Noto Sans SC",
|
||||
"Helvetica Neue", Arial, "Apple Color Emoji", "Segoe UI Emoji", sans-serif;
|
||||
|
||||
/* 字距:全站只允许这四档。
|
||||
历史上散落的 0.5px / 0.04em / 0.14em / 0.22em / 0.26em 全部收敛到这里。 */
|
||||
--tracking-display: -0.03em;
|
||||
--tracking-label: 0.12em;
|
||||
--tracking-wide: 0.18em;
|
||||
--tracking-meta: 0.02em;
|
||||
|
||||
/* 动效:统一缓动 + 三档时长,替代 150/200/250/300/400/450ms 混用。
|
||||
0.22,1,0.36,1 是「快出慢停」曲线,进入视口时更稳,不弹。 */
|
||||
--ease-editorial: cubic-bezier(0.22, 1, 0.36, 1);
|
||||
|
||||
/* 字号阶梯:全部用 rem,而不是 px。
|
||||
默认根字号 16px 下数值与原来的 px 完全等价,视觉零变化;
|
||||
但用户把浏览器「字体大小」调大时,rem 会跟着放大,px 不会(Vercel #1.3)。
|
||||
括号里是 16px 基准下的等价像素。 */
|
||||
--text-micro: 0.625rem; /* 10px */
|
||||
--text-micro--line-height: 1.4;
|
||||
--text-label: 0.6875rem; /* 11px */
|
||||
--text-label--line-height: 1.2;
|
||||
--text-meta: 0.8125rem; /* 13px */
|
||||
--text-meta--line-height: 1.55;
|
||||
--text-body: 0.9375rem; /* 15px */
|
||||
--text-body--line-height: 1.75;
|
||||
--text-lead: 1.0625rem; /* 17px */
|
||||
--text-lead--line-height: 1.55;
|
||||
--text-h3: 1.375rem; /* 22px */
|
||||
--text-h3--line-height: 1.25;
|
||||
--text-h2: clamp(1.625rem, 3vw, 2.125rem); /* 26 → 34px */
|
||||
--text-h2--line-height: 1.15;
|
||||
--text-h1: clamp(2.125rem, 5.5vw, 4rem); /* 34 → 64px */
|
||||
--text-h1--line-height: 0.95;
|
||||
--text-display: clamp(2.625rem, 7vw, 5.25rem); /* 42 → 84px */
|
||||
--text-display--line-height: 0.92;
|
||||
}
|
||||
|
||||
/* 中性灰阶 + 发丝线令牌:统一全站散落的 #f1f1f1/#efefef/#e6e6e6/#ccc/#bdbdbd/#999/#888/#666 等 */
|
||||
:root {
|
||||
--c-gray-50: #f6f6f6;
|
||||
--c-gray-100: #f1f1f1;
|
||||
@ -11,6 +62,20 @@
|
||||
--c-gray-600: #999;
|
||||
--c-gray-700: #888;
|
||||
--c-gray-800: #666;
|
||||
|
||||
/* 发丝线:现代极简电商的主要分隔手段。1px + 12% 黑,比纯黑细框更透气。 */
|
||||
--line: rgba(0, 0, 0, 0.12);
|
||||
--line-strong: rgba(0, 0, 0, 0.24);
|
||||
}
|
||||
|
||||
/* 英文站字距单独收紧一档。
|
||||
原因:英文标签是全大写 + 宽字距,大写字母本身等高、无升降部,
|
||||
加上 0.12em 字间空隙,整行密度明显低于中文,视觉上会「变轻」。
|
||||
收窄到 0.09em 让单词重新聚拢,重量感就回来了。
|
||||
中文站保持原值(方块字之间需要更多呼吸,收太紧会糊)。 */
|
||||
html[lang="en"] {
|
||||
--tracking-label: 0.09em;
|
||||
--tracking-wide: 0.14em;
|
||||
}
|
||||
|
||||
[x-cloak] { display: none !important; }
|
||||
@ -49,4 +114,109 @@ select:focus-visible,
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
全局排版细节
|
||||
============================================================ */
|
||||
|
||||
/* 数字等宽:页码 / 年份 / 计数在内容切换时不会左右跳动 */
|
||||
body {
|
||||
font-variant-numeric: tabular-nums;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
|
||||
/* 翻页 / 筛选导致内容高度变化时,避免滚动条出现消失引起的整页横向抖动。
|
||||
来自 Vercel web-design-guidelines #3.6 */
|
||||
scrollbar-gutter: stable;
|
||||
}
|
||||
|
||||
/* 长标题折行更均衡,避免最后一行只剩一个词(#1.5)。
|
||||
目前只作用于大标题,正文不设,避免影响折行节奏。 */
|
||||
h1,
|
||||
.display-title {
|
||||
text-wrap: balance;
|
||||
}
|
||||
|
||||
::selection {
|
||||
background: #111;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* 导航链接:hover 时 1px 黑线从左滑入。
|
||||
比原来的整体 opacity 变化更克制,也更像编辑类网站的处理。 */
|
||||
.nav-link {
|
||||
position: relative;
|
||||
}
|
||||
.nav-link::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: -3px;
|
||||
height: 1px;
|
||||
background: currentColor;
|
||||
transform: scaleX(0);
|
||||
transform-origin: left center;
|
||||
transition: transform 240ms var(--ease-editorial, ease);
|
||||
}
|
||||
.nav-link:hover::after {
|
||||
transform: scaleX(1);
|
||||
}
|
||||
|
||||
/* 触屏(粗指针)上把点击热区撑到 ≥44px 高,但不动布局。
|
||||
13px 的导航文字在手机上可点区域只有 ~16px 高,拇指很容易点空(Vercel #3.3)。
|
||||
用伪元素扩展命中区,而不是加 padding —— 加 padding 会把下划线一起推下去。 */
|
||||
@media (pointer: coarse) {
|
||||
.nav-link::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: -0.75rem -0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
顶部公告条(marquee)
|
||||
NABOUN 式的循环滚动条:零 JS、纯 CSS、无缝衔接。
|
||||
用法:内容在容器里重复两遍,轨道整体左移 50% 即为一个周期。
|
||||
============================================================ */
|
||||
.marquee {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.marquee__track {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 2.5rem;
|
||||
padding-left: 2.5rem;
|
||||
animation: marquee-scroll 32s linear infinite;
|
||||
will-change: transform;
|
||||
}
|
||||
.marquee:hover .marquee__track {
|
||||
animation-play-state: paused;
|
||||
}
|
||||
@keyframes marquee-scroll {
|
||||
from { transform: translateX(0); }
|
||||
to { transform: translateX(-50%); }
|
||||
}
|
||||
|
||||
/* 列表入场:逐项错落 40ms,避免整屏同时出现造成的「跳一下」 */
|
||||
@keyframes rise-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(8px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
animation-duration: 0.01ms !important;
|
||||
animation-iteration-count: 1 !important;
|
||||
transition-duration: 0.01ms !important;
|
||||
scroll-behavior: auto !important;
|
||||
}
|
||||
}
|
||||
|
||||
@ -23,10 +23,12 @@
|
||||
@apply flex justify-between items-end gap-6 py-5 pb-[22px];
|
||||
}
|
||||
.showsh1 {
|
||||
@apply text-[clamp(40px,6vw,72px)] leading-[0.9] font-black tracking-[-0.02em] my-0 mb-[10px];
|
||||
@apply text-[clamp(2.5rem,6vw,4.5rem)] leading-[0.9] font-bold my-0 mb-[10px];
|
||||
letter-spacing: var(--tracking-display, -0.03em);
|
||||
}
|
||||
.snapsh1 {
|
||||
@apply text-[clamp(34px,5.5vw,64px)] leading-[0.95] font-black tracking-[-0.02em] my-0 mb-[8px];
|
||||
@apply text-[clamp(2.125rem,5.5vw,4rem)] leading-[0.95] font-bold my-0 mb-[8px];
|
||||
letter-spacing: var(--tracking-display, -0.03em);
|
||||
}
|
||||
|
||||
/* 已激活筛选 pills */
|
||||
@ -34,7 +36,8 @@
|
||||
.snapspills { @apply flex flex-wrap gap-2 mt-4; }
|
||||
.showspill,
|
||||
.snapspill {
|
||||
@apply inline-flex items-center gap-2 border-[1.5px] border-black p-[6px_10px] text-[10.5px] uppercase tracking-[0.14em] text-black bg-white;
|
||||
@apply inline-flex items-center gap-2 border border-black p-[6px_10px] text-[10px] uppercase text-black bg-white;
|
||||
letter-spacing: var(--tracking-label, 0.12em);
|
||||
}
|
||||
.showspill .x,
|
||||
.snapspill .x {
|
||||
@ -56,11 +59,14 @@
|
||||
}
|
||||
.showsaside-head,
|
||||
.snapsaside-head {
|
||||
@apply flex items-center justify-between p-[14px_14px_12px] border-b border-black shrink-0;
|
||||
@apply flex items-center justify-between p-[14px_14px_12px] border-b border-black/15 shrink-0;
|
||||
}
|
||||
.showsaside-h,
|
||||
.snapsaside-h {
|
||||
@apply text-[11px] tracking-[0.26em] uppercase font-bold m-0 leading-none transition-opacity duration-200;
|
||||
@apply text-[11px] uppercase font-medium m-0 leading-none transition-opacity duration-[240ms];
|
||||
/* 不用 @apply tracking-*:本文件 @reference 的是 tailwind 默认主题,
|
||||
取不到 global.css 里 @theme 定义的值,只能走运行时 CSS 变量。 */
|
||||
letter-spacing: var(--tracking-wide, 0.18em);
|
||||
}
|
||||
.showsaside-body,
|
||||
.snapsaside-body {
|
||||
@ -74,7 +80,8 @@
|
||||
}
|
||||
.showsg-h,
|
||||
.snapsg-h {
|
||||
@apply flex justify-between items-center text-[11px] tracking-[0.22em] uppercase font-bold text-black mb-2 cursor-pointer select-none;
|
||||
@apply flex justify-between items-center text-[11px] uppercase font-medium text-black mb-2 cursor-pointer select-none;
|
||||
letter-spacing: var(--tracking-wide, 0.18em);
|
||||
}
|
||||
.showsg-right {
|
||||
@apply inline-flex items-center gap-2.5;
|
||||
@ -113,7 +120,8 @@
|
||||
.snapschecks--flat li label { @apply inline-flex items-center gap-2 cursor-pointer w-full; }
|
||||
|
||||
.showsmore {
|
||||
@apply block w-full mt-[12px] text-center text-[11px] uppercase tracking-[0.22em] text-black font-bold cursor-pointer;
|
||||
@apply block w-full mt-[12px] text-center text-[11px] uppercase text-black font-medium cursor-pointer;
|
||||
letter-spacing: var(--tracking-wide, 0.18em);
|
||||
}
|
||||
.showsclear,
|
||||
.snapsclear {
|
||||
@ -126,7 +134,7 @@
|
||||
|
||||
.showsgrid,
|
||||
.snapsgrid {
|
||||
@apply grid grid-cols-2 gap-[22px] relative max-sm:grid-cols-1;
|
||||
@apply grid grid-cols-2 gap-6 relative max-sm:grid-cols-1;
|
||||
}
|
||||
.showsgrid.is-loading::before,
|
||||
.snapsgrid.is-loading::before {
|
||||
@ -137,7 +145,17 @@
|
||||
|
||||
.showscard,
|
||||
.snapscard {
|
||||
@apply relative flex gap-[14px] no-underline text-inherit bg-white p-3 transition-colors duration-200 hover:border-black max-md:flex-col max-md:gap-[10px];
|
||||
@apply relative flex gap-[14px] no-underline text-inherit bg-white p-3 border border-transparent transition-[color,background-color,border-color,transform] duration-[240ms] hover:border-black/20 max-md:flex-col max-md:gap-[10px];
|
||||
transition-timing-function: var(--ease-editorial, ease);
|
||||
}
|
||||
/* hover 时整体上浮 2px:比单纯变色更能表达「可点」,且不触发重排。
|
||||
必须包在 (hover: hover) 里:触屏浏览器会「粘滞 hover」,
|
||||
手指离开后卡片停在上浮状态,看起来像坏了。 */
|
||||
@media (hover: hover) {
|
||||
.showscard:hover,
|
||||
.snapscard:hover {
|
||||
@apply -translate-y-[2px];
|
||||
}
|
||||
}
|
||||
.showscard:hover .showscard-img img,
|
||||
.snapscard:hover .snapscard-img img { @apply scale-[1.04]; }
|
||||
@ -176,11 +194,13 @@
|
||||
}
|
||||
.showscard-title,
|
||||
.snapscard-title {
|
||||
@apply text-[15.5px] font-bold leading-[1.32] my-0 mb-[10px] text-[#111] tracking-[-0.01em] line-clamp-2 h-[41px] overflow-hidden;
|
||||
@apply text-[16px] font-bold leading-[1.32] my-0 mb-[10px] text-[#111] line-clamp-2 h-[42px] overflow-hidden;
|
||||
letter-spacing: var(--tracking-display, -0.03em);
|
||||
}
|
||||
.showscard-meta,
|
||||
.snapscard-meta {
|
||||
@apply text-[9.5px] text-[var(--c-gray-700)] my-0 mb-[14px] leading-[1.55] tracking-[0.04em] uppercase line-clamp-2 h-[29px] overflow-hidden;
|
||||
@apply text-[10px] text-[var(--c-gray-700)] my-0 mb-[14px] leading-[1.55] uppercase line-clamp-2 h-[31px] overflow-hidden;
|
||||
letter-spacing: var(--tracking-meta, 0.02em);
|
||||
}
|
||||
|
||||
.showscard-strip,
|
||||
@ -200,7 +220,8 @@
|
||||
.showscard-thumb.placeholder,
|
||||
.snapscard-thumb.placeholder { @apply bg-[var(--c-gray-50)]; }
|
||||
.showscard-thumb-more {
|
||||
@apply absolute inset-0 bg-black/55 text-white text-[11px] font-bold tracking-[0.04em] flex items-center justify-center;
|
||||
@apply absolute inset-0 bg-black/55 text-white text-[11px] font-medium flex items-center justify-center;
|
||||
letter-spacing: var(--tracking-meta, 0.02em);
|
||||
}
|
||||
|
||||
/* 骨架屏 */
|
||||
@ -218,7 +239,10 @@
|
||||
.showspager,
|
||||
.snapspager { @apply flex flex-wrap items-center justify-center gap-2 mt-[38px]; }
|
||||
.pg {
|
||||
@apply min-w-[40px] h-[40px] px-3 border-[1.5px] border-black bg-white text-black text-[11px] uppercase tracking-[0.12em] font-bold cursor-pointer rounded-none transition-colors duration-200;
|
||||
/* 边框不透明度提到 50%:UI 组件的边界对比度需 ≥3:1(WCAG 2.2 / Vercel #2.3),
|
||||
black/25 只有约 1.8:1,弱视用户几乎看不见分页框。 */
|
||||
@apply min-w-[40px] h-[40px] px-3 border border-black/50 bg-white text-black text-[11px] uppercase font-medium cursor-pointer rounded-none transition-colors duration-[240ms];
|
||||
letter-spacing: var(--tracking-label, 0.12em);
|
||||
}
|
||||
.pg.is-current { @apply bg-black text-white cursor-default; }
|
||||
.pg:disabled { @apply opacity-30 cursor-default; }
|
||||
|
||||
Reference in New Issue
Block a user