update
This commit is contained in:
@ -36,16 +36,16 @@ const fullLoginLabel = t('go to full login')
|
||||
<button type="button" @click="close()" class="-mt-1 text-[11px] uppercase tracking-widest opacity-60 transition-opacity hover:opacity-100">{closeLabel}</button>
|
||||
</div>
|
||||
|
||||
<p class="mb-7 text-[11px] uppercase tracking-widest text-zinc-400">{prompt}</p>
|
||||
<p class="mb-7 text-[11px] uppercase tracking-widest text-[var(--c-gray-600)]">{prompt}</p>
|
||||
|
||||
<form @submit.prevent="submit()" class="space-y-6" novalidate>
|
||||
<div>
|
||||
<label class="mb-2 block text-[11px] uppercase tracking-widest text-zinc-500">{accountLabel}</label>
|
||||
<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
|
||||
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-zinc-500">{passwordLabel}</label>
|
||||
<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
|
||||
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>
|
||||
@ -60,7 +60,7 @@ const fullLoginLabel = t('go to full login')
|
||||
</form>
|
||||
|
||||
<div class="mt-6 border-t border-black/10 pt-4 text-center">
|
||||
<button type="button" @click="goFullLogin()" class="text-[10px] uppercase tracking-[0.2em] text-zinc-400 transition-colors hover:text-black">{fullLoginLabel}</button>
|
||||
<button type="button" @click="goFullLogin()" class="text-[10px] uppercase tracking-[0.2em] text-[var(--c-gray-600)] transition-colors hover:text-black">{fullLoginLabel}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -93,13 +93,13 @@
|
||||
@apply m-0 text-[12px] tracking-[0.22em] uppercase font-bold;
|
||||
}
|
||||
.showsmodal-x {
|
||||
@apply appearance-none bg-transparent border-0 w-6 h-6 inline-flex items-center justify-center cursor-pointer text-[14px] leading-none text-[#999] transition-colors hover:text-black;
|
||||
@apply appearance-none bg-transparent border-0 w-6 h-6 inline-flex items-center justify-center cursor-pointer text-[14px] leading-none text-[var(--c-gray-600)] transition-colors hover:text-black;
|
||||
}
|
||||
.showsmodal-search {
|
||||
@apply px-4 pt-1 pb-3 shrink-0;
|
||||
}
|
||||
.showsmodal-input {
|
||||
@apply w-full border border-black/15 px-3 py-2 text-[13px] outline-none bg-[#fafafa] focus:bg-white focus:border-black;
|
||||
@apply w-full border border-black/15 px-3 py-2 text-[13px] outline-none bg-[var(--c-gray-50)] focus:bg-white focus:border-black;
|
||||
}
|
||||
.showsmodal-tip {
|
||||
@apply mt-2 text-[11px] leading-snug text-black/45;
|
||||
@ -120,10 +120,10 @@
|
||||
@apply flex-1 min-h-0 overflow-y-auto px-2 py-1 overscroll-contain;
|
||||
}
|
||||
.showsmodal-hint {
|
||||
@apply text-[10px] tracking-[0.22em] uppercase text-[#999] px-3 py-2;
|
||||
@apply text-[10px] tracking-[0.22em] uppercase text-[var(--c-gray-600)] px-3 py-2;
|
||||
}
|
||||
.showsmodal-loading {
|
||||
@apply flex justify-center py-6 text-[#bdbdbd];
|
||||
@apply flex justify-center py-6 text-[var(--c-gray-500)];
|
||||
}
|
||||
.showsmodal-list {
|
||||
@apply list-none m-0 p-0;
|
||||
@ -142,10 +142,10 @@
|
||||
@apply flex-1 truncate;
|
||||
}
|
||||
.showsmodal-empty {
|
||||
@apply py-6 text-center text-[#999] text-[13px];
|
||||
@apply py-6 text-center text-[var(--c-gray-600)] text-[13px];
|
||||
}
|
||||
.showsmodal-foot {
|
||||
@apply flex items-center justify-between px-4 py-3 border-t border-black/5 shrink-0 text-[11px] text-[#888];
|
||||
@apply flex items-center justify-between px-4 py-3 border-t border-black/5 shrink-0 text-[11px] text-[var(--c-gray-700)];
|
||||
}
|
||||
.showsmodal-ok {
|
||||
@apply appearance-none bg-black text-white border border-black px-4 py-1.5 cursor-pointer text-[12px] tracking-[0.1em] transition-colors hover:bg-black/85;
|
||||
|
||||
@ -41,7 +41,7 @@ const { locale } = getI18n(Astro)
|
||||
justify-content: space-between;
|
||||
border-top: 1px solid #000;
|
||||
padding-top: 18px;
|
||||
@apply text-[11px] tracking-[0.25em] uppercase text-[#666];
|
||||
@apply text-[11px] tracking-[0.25em] uppercase text-[var(--c-gray-800)];
|
||||
}
|
||||
.cs-title {
|
||||
@apply font-black tracking-[-0.02em];
|
||||
@ -54,7 +54,7 @@ const { locale } = getI18n(Astro)
|
||||
margin: 0 0 18px;
|
||||
}
|
||||
.cs-desc {
|
||||
@apply text-[13px] text-[#666];
|
||||
@apply text-[13px] text-[var(--c-gray-800)];
|
||||
line-height: 1.7;
|
||||
max-width: 52ch;
|
||||
margin: 0 0 28px;
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
---
|
||||
// 走秀 / 街拍详情页共享的全屏大图灯箱:左栏缩略图条 + 大图 + 上/下·左/右翻页 + 单图收藏。
|
||||
//
|
||||
// 视觉遵循全站「黑白编辑风」:直角细框、mono 小字 + 宽字距、零填充计数、hover 反色(bg-black + 白字)。
|
||||
// 视觉遵循全站「黑白编辑风」:直角细框、小字 + 宽字距、零填充计数、hover 反色(bg-black + 白字)。
|
||||
// 交互逻辑全部在 src/lib/gallery.ts(createGalleryView);本组件只负责标记。
|
||||
//
|
||||
// 各页独有的附加层通过具名插槽 details 注入(走秀页塞「当前 look 的细节图簇」,街拍页不传)。
|
||||
@ -29,7 +29,7 @@ const thumbAspect = aspect === "3/4" ? "aspect-[3/4]" : "aspect-[2/3]"
|
||||
class="fixed inset-0 z-[100] bg-white flex flex-col select-none"
|
||||
aria-hidden="true"
|
||||
>
|
||||
{/* 计数:mono + 0.22em 字距(全站「小标与计数」档),直角细框,零填充 */}
|
||||
{/* 计数:小字 + 0.22em 字距(全站「小标与计数」档),直角细框,零填充 */}
|
||||
<div
|
||||
class="absolute top-5 left-1/2 -translate-x-1/2 z-30 border border-black/15 bg-white/90 px-3 py-1 text-[11px] tracking-[0.22em] text-[#111] backdrop-blur-md"
|
||||
x-text="`${String(lbIndex + 1).padStart(2, '0')} / ${String(imageCount).padStart(2, '0')}`"
|
||||
@ -71,7 +71,7 @@ const thumbAspect = aspect === "3/4" ? "aspect-[3/4]" : "aspect-[2/3]"
|
||||
data-lb-index={i}
|
||||
data-thumb-url={im.thumb}
|
||||
@click={`showLb(${i})`}
|
||||
class={`lb-thumb relative block w-full ${thumbAspect} mb-2 overflow-hidden cursor-pointer transition-opacity duration-200 opacity-40 hover:opacity-90 bg-[#eee] bg-cover bg-center`}
|
||||
class={`lb-thumb relative block w-full ${thumbAspect} mb-2 overflow-hidden cursor-pointer transition-opacity duration-200 opacity-40 hover:opacity-90 bg-[var(--c-gray-200)] bg-cover bg-center`}
|
||||
x-bind:class="lbIndex === Number($el.dataset.lbIndex) ? 'opacity-100 ring-1 ring-black' : ''"
|
||||
aria-label={`${i + 1}`}
|
||||
>
|
||||
@ -87,7 +87,7 @@ const thumbAspect = aspect === "3/4" ? "aspect-[3/4]" : "aspect-[2/3]"
|
||||
:data-lb-index="5 + i"
|
||||
:data-thumb-url="im.thumb"
|
||||
@click="showLb(5 + i)"
|
||||
class={`lb-thumb relative block w-full ${thumbAspect} mb-2 overflow-hidden cursor-pointer transition-opacity duration-200 opacity-40 hover:opacity-90 bg-[#eee] bg-cover bg-center`}
|
||||
class={`lb-thumb relative block w-full ${thumbAspect} mb-2 overflow-hidden cursor-pointer transition-opacity duration-200 opacity-40 hover:opacity-90 bg-[var(--c-gray-200)] bg-cover bg-center`}
|
||||
x-bind:class="lbIndex === Number($el.dataset.lbIndex) ? 'opacity-100 ring-1 ring-black' : ''"
|
||||
:aria-label="String(5 + i + 1)"
|
||||
>
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
---
|
||||
// 加载屏:首屏/整页切换时遮盖未渲染内容,避免 FOUC。
|
||||
// 轻量原则(用户要求"朴实流畅"):
|
||||
// 轻量原则(追求朴实流畅):
|
||||
// - 只用静态大字 + 小标 + 一条 2px 细线进度(transform/opacity 动画,最便宜);
|
||||
// - 去掉昂贵的多层 text-shadow 位移动画与外部字体;最短展示时间压缩到 ~350ms,跟手不拖沓。
|
||||
|
||||
@ -48,14 +48,14 @@ const { t } = getI18n(Astro)
|
||||
font-size: 0.72rem;
|
||||
letter-spacing: 0.34em;
|
||||
text-transform: uppercase;
|
||||
color: #888;
|
||||
color: var(--c-gray-700);
|
||||
animation: ls-pulse 1.4s ease-in-out infinite;
|
||||
}
|
||||
#loading-screen .ls-bar {
|
||||
margin-top: 2em;
|
||||
width: min(180px, 60vw);
|
||||
height: 2px;
|
||||
background: #e6e6e6;
|
||||
background: var(--c-gray-300);
|
||||
overflow: hidden;
|
||||
}
|
||||
#loading-screen .ls-bar::before {
|
||||
|
||||
@ -89,11 +89,11 @@ const homeHref = href(locale, ROUTES.home)
|
||||
<section class="mt-16">
|
||||
{/* 每行张数切换(应对图集图片很多的情况):2/3/4/5 列,移动端固定 2 列 */}
|
||||
<div class="flex items-center justify-between mb-5 border-b border-black/10 pb-3">
|
||||
<span class="text-[11px] uppercase tracking-[0.22em] text-[#999]">
|
||||
<span class="text-[11px] uppercase tracking-[0.22em] text-[var(--c-gray-600)]">
|
||||
{(imageTotal || images.length)} LOOKS
|
||||
</span>
|
||||
<div class="flex items-center gap-1" role="group" aria-label="Images per row">
|
||||
<span class="text-[11px] uppercase tracking-[0.18em] text-[#999] mr-2 hidden sm:inline">PER ROW</span>
|
||||
<span class="text-[11px] uppercase tracking-[0.18em] text-[var(--c-gray-600)] mr-2 hidden sm:inline">PER ROW</span>
|
||||
{/* 每行张数切换:只有「3 张 / 5 张」两档,用竖条图标直观表达列数(避免文字数字按钮)。
|
||||
density 只影响 md+;移动端固定 2 列(5 列在手机上不可用)。 */}
|
||||
<button
|
||||
@ -145,7 +145,7 @@ const homeHref = href(locale, ROUTES.home)
|
||||
data-fav-id={im.id}
|
||||
data-fav-url={im.raw}
|
||||
@click={`openLb(${i})`}
|
||||
class="relative bg-[#f2f2f2] cursor-zoom-in group"
|
||||
class="relative bg-[var(--c-gray-100)] cursor-zoom-in group"
|
||||
>
|
||||
<img
|
||||
src={im.url}
|
||||
@ -202,10 +202,10 @@ const homeHref = href(locale, ROUTES.home)
|
||||
x-show="!isAuthed && preview"
|
||||
x-cloak
|
||||
login-redirect
|
||||
class="relative bg-[#f2f2f2] cursor-pointer group overflow-hidden"
|
||||
class="relative bg-[var(--c-gray-100)] cursor-pointer group overflow-hidden"
|
||||
>
|
||||
{/* 与真实图片等高的 3:4 图位:锁图标 + 提示,点击跳登录 */}
|
||||
<div class="w-full aspect-[2/3] flex items-center justify-center text-black group-hover:text-[#9a9a9a] transition-colors pointer-events-none">
|
||||
<div class="w-full aspect-[2/3] flex items-center justify-center text-black group-hover:text-[var(--c-gray-700)] transition-colors pointer-events-none">
|
||||
<div class="flex flex-col items-center gap-2">
|
||||
<svg class="w-7 h-7" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round">
|
||||
<rect x="4.5" y="10.5" width="15" height="10" rx="2" />
|
||||
@ -231,7 +231,7 @@ const homeHref = href(locale, ROUTES.home)
|
||||
:data-fav-id="im.id"
|
||||
:data-fav-url="im.raw"
|
||||
@click="openLb(5 + i)"
|
||||
class="relative bg-[#f2f2f2] cursor-zoom-in group"
|
||||
class="relative bg-[var(--c-gray-100)] cursor-zoom-in group"
|
||||
>
|
||||
<img
|
||||
:src="im.url"
|
||||
@ -285,7 +285,7 @@ const homeHref = href(locale, ROUTES.home)
|
||||
</article>
|
||||
) : (
|
||||
<div class="flex flex-col items-center justify-center min-h-[60vh] px-5 text-center">
|
||||
<p class="text-sm text-[#888] uppercase tracking-widest mb-4">
|
||||
<p class="text-sm text-[var(--c-gray-700)] uppercase tracking-widest mb-4">
|
||||
{error === "missing" ? t('No article ID specified') : t('Failed to load article. Please refresh or try later.')}
|
||||
</p>
|
||||
|
||||
@ -312,13 +312,13 @@ const homeHref = href(locale, ROUTES.home)
|
||||
x-cloak
|
||||
class="absolute right-4 sm:right-6 bottom-[72px] z-30 flex flex-col items-end gap-2"
|
||||
>
|
||||
<span class="text-[10px] uppercase tracking-[0.22em] text-[#999]">Details</span>
|
||||
<span class="text-[10px] uppercase tracking-[0.22em] text-[var(--c-gray-600)]">Details</span>
|
||||
<div class="flex max-w-[min(70vw,440px)] gap-2 overflow-x-auto no-scrollbar">
|
||||
<template x-for="(d, i) in currentDetailList" :key="d.url">
|
||||
<button
|
||||
type="button"
|
||||
@click.stop="openDetailLbAt(i)"
|
||||
class="relative flex-none w-[52px] sm:w-[68px] aspect-[2/3] overflow-hidden border border-black/15 bg-[#eee] cursor-zoom-in transition-colors hover:border-black"
|
||||
class="relative flex-none w-[52px] sm:w-[68px] aspect-[2/3] overflow-hidden border border-black/15 bg-[var(--c-gray-200)] cursor-zoom-in transition-colors hover:border-black"
|
||||
:aria-label="`Detail ${i + 1}`"
|
||||
>
|
||||
<img :src="d.thumb" :alt="d.name || ''" loading="lazy" decoding="async" class="w-full h-full object-cover" />
|
||||
@ -459,7 +459,7 @@ const homeHref = href(locale, ROUTES.home)
|
||||
// 为什么要把这个类型写出来:`Alpine.data` 的泛型 T 靠**对象字面量**推断,而这里用了对象展开
|
||||
// ({...createGalleryView()}),推断会退化成 {},于是方法里的 this 只剩 Alpine 魔法属性,
|
||||
// 所有 this.xxx 都报「属性不存在」。给回调标注返回类型,T 才有确定值。
|
||||
// (@types/alpinejs 那个自引用泛型约束的老毛病,详见 MEMORY.md。)
|
||||
// (@types/alpinejs 那个自引用泛型约束的老毛病。)
|
||||
type ArticleExtras = {
|
||||
density: number
|
||||
currentDetailName: string
|
||||
|
||||
@ -72,7 +72,7 @@ const displayTitle = (snap?.title || "").replace(/\t/g, " · ")
|
||||
data-fav-id={im.id}
|
||||
data-fav-url={im.raw}
|
||||
@click={`openLb(${i})`}
|
||||
class="relative bg-[#f2f2f2] cursor-zoom-in group"
|
||||
class="relative bg-[var(--c-gray-100)] cursor-zoom-in group"
|
||||
>
|
||||
<img
|
||||
src={im.url}
|
||||
@ -99,7 +99,7 @@ const displayTitle = (snap?.title || "").replace(/\t/g, " · ")
|
||||
</button>
|
||||
|
||||
{im.name && (
|
||||
<figcaption class="text-[11px] uppercase tracking-widest text-[#999] mt-2">
|
||||
<figcaption class="text-[11px] uppercase tracking-widest text-[var(--c-gray-600)] mt-2">
|
||||
{im.name}
|
||||
</figcaption>
|
||||
)}
|
||||
@ -114,10 +114,10 @@ const displayTitle = (snap?.title || "").replace(/\t/g, " · ")
|
||||
x-show="!isAuthed && preview"
|
||||
x-cloak
|
||||
login-redirect
|
||||
class="relative bg-[#f2f2f2] cursor-pointer group overflow-hidden"
|
||||
class="relative bg-[var(--c-gray-100)] cursor-pointer group overflow-hidden"
|
||||
>
|
||||
{/* 与真实图片等高的 3:4 图位:锁图标 + 提示,点击跳登录 */}
|
||||
<div class="w-full aspect-[3/4] flex items-center justify-center text-[#c4c4c4] group-hover:text-[#9a9a9a] transition-colors pointer-events-none">
|
||||
<div class="w-full aspect-[3/4] flex items-center justify-center text-[var(--c-gray-600)] group-hover:text-[var(--c-gray-700)] transition-colors pointer-events-none">
|
||||
<div class="flex flex-col items-center gap-2">
|
||||
<svg class="w-7 h-7" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round">
|
||||
<rect x="4.5" y="10.5" width="15" height="10" rx="2" />
|
||||
@ -128,7 +128,7 @@ const displayTitle = (snap?.title || "").replace(/\t/g, " · ")
|
||||
</div>
|
||||
{/* 与真实图 figcaption 同高的 caption 行(正常图显示 img01 等小字):
|
||||
占位显示 +N photos,保证遮罩格子与真实图片格子总高一致 */}
|
||||
<figcaption class="text-[11px] uppercase tracking-widest text-[#cfcfcf] mt-2">+{lockedCount} {t('photos')}</figcaption>
|
||||
<figcaption class="text-[11px] uppercase tracking-widest text-[var(--c-gray-600)] mt-2">+{lockedCount} {t('photos')}</figcaption>
|
||||
</figure>
|
||||
))
|
||||
}
|
||||
@ -142,7 +142,7 @@ const displayTitle = (snap?.title || "").replace(/\t/g, " · ")
|
||||
:data-fav-id="im.id"
|
||||
:data-fav-url="im.raw"
|
||||
@click="openLb(5 + i)"
|
||||
class="relative bg-[#f2f2f2] cursor-zoom-in group"
|
||||
class="relative bg-[var(--c-gray-100)] cursor-zoom-in group"
|
||||
>
|
||||
<img
|
||||
:src="im.url"
|
||||
@ -166,7 +166,7 @@ const displayTitle = (snap?.title || "").replace(/\t/g, " · ")
|
||||
</svg>
|
||||
</button>
|
||||
<template x-if="im.name">
|
||||
<figcaption class="text-[11px] uppercase tracking-widest text-[#999] mt-2" x-text="im.name"></figcaption>
|
||||
<figcaption class="text-[11px] uppercase tracking-widest text-[var(--c-gray-600)] mt-2" x-text="im.name"></figcaption>
|
||||
</template>
|
||||
</figure>
|
||||
</template>
|
||||
@ -177,7 +177,7 @@ const displayTitle = (snap?.title || "").replace(/\t/g, " · ")
|
||||
</article>
|
||||
) : (
|
||||
<div class="flex flex-col items-center justify-center min-h-[60vh] px-5 text-center">
|
||||
<p class="text-sm text-[#888] uppercase tracking-widest mb-4">
|
||||
<p class="text-sm text-[var(--c-gray-700)] uppercase tracking-widest mb-4">
|
||||
{error === "missing" ? t('No street snap ID specified') : t('Failed to load street snap. Please refresh or try later.')}
|
||||
</p>
|
||||
|
||||
@ -204,7 +204,7 @@ const displayTitle = (snap?.title || "").replace(/\t/g, " · ")
|
||||
import { createGalleryView } from "@/lib/gallery"
|
||||
|
||||
// 图集 + 大图灯箱的共享逻辑全在 lib/gallery.ts;街拍详情没有额外扩展,只补一个 init 调共享初始化。
|
||||
// 回调显式标注返回类型:对象展开会让 Alpine.data 的泛型 T 退化成 {},this.xxx 全部报错(见 Item.astro 说明)。
|
||||
// 回调显式标注返回类型:对象展开会让 Alpine.data 的泛型 T 退化成 {},this.xxx 全部报错(见 RunwayLookCard.astro 说明)。
|
||||
document.addEventListener("alpine:init", () => {
|
||||
Alpine.data(
|
||||
"streetSnapView",
|
||||
|
||||
@ -32,12 +32,12 @@ const noHistoryLabel = t("no history yet")
|
||||
const browseLabel = t("browse the archive")
|
||||
---
|
||||
<Layout title={titleLabel} description={accountDesc} keywords={accountKeywords}>
|
||||
<!-- 页面版心与 Layout 一致:黑白编辑风,衬线大标题 + 等宽小标 + 粗黑细线 -->
|
||||
<!-- 页面版心与 Layout 一致:黑白编辑风,粗黑大标题 + 宽字距小标 + 粗黑细线 -->
|
||||
<main class="account-root mx-auto w-full max-w-[1400px] px-5 py-14 md:px-10 md:py-20 font-sans" x-data="accountPage" data-locale={locale}>
|
||||
|
||||
<!-- ===== 未登录:整版引导 ===== -->
|
||||
<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-zinc-400">{pleaseLabel}</p>
|
||||
<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>
|
||||
<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>
|
||||
@ -45,19 +45,19 @@ const browseLabel = t("browse the archive")
|
||||
<!-- ===== 已登录 ===== -->
|
||||
<div x-show="user" x-cloak>
|
||||
|
||||
<!-- 报头:衬线大标题会员名 + 档案标签 -->
|
||||
<!-- 报头:粗黑大标题会员名 + 档案标签 -->
|
||||
<header class="flex flex-wrap items-end justify-between gap-x-16 gap-y-8 border-b-2 border-black pb-10 pt-2">
|
||||
<div class="min-w-0">
|
||||
<p class="mb-4 text-[11px] uppercase tracking-[0.3em] text-zinc-400">{t("member profile")}</p>
|
||||
<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>
|
||||
<!-- 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>
|
||||
</div>
|
||||
<div class="shrink-0 pb-1 text-right text-[11px] uppercase leading-loose tracking-[0.22em] text-zinc-400">
|
||||
<p>Portfolio Studio</p>
|
||||
<p class="flex items-center justify-end gap-2 text-zinc-500">Archive
|
||||
<div class="shrink-0 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>
|
||||
@ -69,7 +69,7 @@ const browseLabel = t("browse the archive")
|
||||
|
||||
<!-- 左:编号目录式垂直导航(移动端折成横向条) -->
|
||||
<nav class="lg:sticky lg:top-12 lg:self-start" aria-label="Account sections">
|
||||
<p class="mb-3 hidden text-[10px] uppercase tracking-[0.3em] text-zinc-400 lg:block">Index</p>
|
||||
<p class="mb-3 hidden text-[10px] uppercase tracking-[0.3em] text-[var(--c-gray-600)] lg:block">Index</p>
|
||||
<div class="flex gap-10 overflow-x-auto border-b border-black/15 text-[12px] uppercase tracking-[0.2em] lg:flex-col lg:gap-0 lg:overflow-visible lg:border-b-0 lg:border-t lg:border-black/10">
|
||||
<button type="button" @click="tab='saved'" :class="tab==='saved' ? 'border-black text-black' : 'border-transparent text-black/40 hover:text-black'" class="-mb-px whitespace-nowrap border-b-2 px-0.5 pb-4 pt-1 text-left transition-colors lg:mb-0 lg:w-full lg:border-b-0 lg:border-l-2 lg:px-5 lg:py-4">
|
||||
<span class="mr-3 text-[10px] opacity-60">01</span>{savedLooksLabel}
|
||||
@ -86,7 +86,7 @@ const browseLabel = t("browse the archive")
|
||||
|
||||
<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>
|
||||
<p class="pb-1 text-[11px] uppercase tracking-[0.22em] text-zinc-400"><span class="font-bold text-black" x-text="favoritesCount"></span> {itemsLabel}</p>
|
||||
<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>
|
||||
|
||||
<!-- 粒度切换:图集 / 单图(服务端 kind 过滤分页) -->
|
||||
@ -105,7 +105,7 @@ const browseLabel = t("browse the archive")
|
||||
<div x-show="favorites.length > 0" class="grid grid-cols-2 gap-x-4 gap-y-10 sm:grid-cols-3 lg:grid-cols-3 xl:grid-cols-4 lg:gap-x-5">
|
||||
<template x-for="f in favorites" :key="f.id">
|
||||
<div class="group">
|
||||
<a :href="itemHref(f)" target="_blank" rel="noopener" class="relative block aspect-[3/4] overflow-hidden border border-black/10 bg-[#f1f1f1] transition-colors group-hover:border-black">
|
||||
<a :href="itemHref(f)" target="_blank" rel="noopener" class="relative block aspect-[3/4] overflow-hidden border border-black/10 bg-[var(--c-gray-100)] transition-colors group-hover:border-black">
|
||||
<img :src="coverUrl(f)" :alt="f.title" loading="lazy" class="h-full w-full object-cover transition-transform duration-700 group-hover:scale-[1.04]" />
|
||||
</a>
|
||||
<button @click="removeFav(f.id)" class="card-act absolute right-2 top-2 border border-black/15 bg-white/90 px-2.5 py-1 text-[9px] uppercase tracking-[0.16em] text-black/70 opacity-0 backdrop-blur transition-opacity hover:bg-black hover:text-white group-hover:opacity-100">{removeLabel}</button>
|
||||
@ -113,7 +113,7 @@ const browseLabel = t("browse the archive")
|
||||
<a :href="itemHref(f)" target="_blank" rel="noopener" class="block">
|
||||
<p class="truncate text-[13px] font-medium leading-snug" x-text="f.title"></p>
|
||||
</a>
|
||||
<p class="mt-1.5 truncate text-[10px] uppercase tracking-[0.16em] text-zinc-400" x-text="f.brand || '—'"></p>
|
||||
<p class="mt-1.5 truncate text-[10px] uppercase tracking-[0.16em] text-[var(--c-gray-600)]" x-text="f.brand || '—'"></p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -138,8 +138,8 @@ const browseLabel = t("browse the archive")
|
||||
<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>
|
||||
<div class="flex items-baseline gap-8 pb-1">
|
||||
<p class="text-[11px] uppercase tracking-[0.22em] text-zinc-400"><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-zinc-400 underline-offset-4 transition-colors hover:text-black hover:underline">{clearHistoryLabel}</button>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -153,7 +153,7 @@ const browseLabel = t("browse the archive")
|
||||
<div x-show="histTotal > 0" class="grid grid-cols-2 gap-x-4 gap-y-10 sm:grid-cols-3 lg:grid-cols-3 xl:grid-cols-4 lg:gap-x-5">
|
||||
<template x-for="h in history" :key="h.id">
|
||||
<div class="group">
|
||||
<a :href="itemHref(h)" target="_blank" rel="noopener" class="relative block aspect-[3/4] overflow-hidden border border-black/10 bg-[#f1f1f1] transition-colors group-hover:border-black">
|
||||
<a :href="itemHref(h)" target="_blank" rel="noopener" class="relative block aspect-[3/4] overflow-hidden border border-black/10 bg-[var(--c-gray-100)] transition-colors group-hover:border-black">
|
||||
<img :src="coverUrl(h)" :alt="h.data?.title || ''" loading="lazy" class="h-full w-full object-cover transition-transform duration-700 group-hover:scale-[1.04]" />
|
||||
</a>
|
||||
<button @click="removeHist(h.id)" class="card-act absolute right-2 top-2 border border-black/15 bg-white/90 px-2.5 py-1 text-[9px] uppercase tracking-[0.16em] text-black/70 opacity-0 backdrop-blur transition-opacity hover:bg-black hover:text-white group-hover:opacity-100">{removeLabel}</button>
|
||||
@ -161,7 +161,7 @@ const browseLabel = t("browse the archive")
|
||||
<a :href="itemHref(h)" target="_blank" rel="noopener" class="block">
|
||||
<p class="truncate text-[13px] font-medium leading-snug" x-text="h.data?.title || '…'"></p>
|
||||
</a>
|
||||
<p class="mt-1.5 truncate text-[10px] uppercase tracking-[0.16em] text-zinc-400" x-text="h.data?.brand || '—'"></p>
|
||||
<p class="mt-1.5 truncate text-[10px] uppercase tracking-[0.16em] text-[var(--c-gray-600)]" x-text="h.data?.brand || '—'"></p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -202,7 +202,7 @@ const browseLabel = t("browse the archive")
|
||||
|
||||
const PLACEHOLDER = "data:image/svg+xml;utf8," + encodeURIComponent(
|
||||
'<svg xmlns="http://www.w3.org/2000/svg" width="300" height="400">' +
|
||||
'<rect width="300" height="400" fill="#f1f1f1"/>' +
|
||||
'<rect width="300" height="400" fill="var(--c-gray-100)"/>' +
|
||||
'<text x="150" y="200" font-family="sans-serif" font-size="16" letter-spacing="3" fill="#9ca3af" text-anchor="middle">LOOK</text>' +
|
||||
'</svg>'
|
||||
)
|
||||
|
||||
@ -79,7 +79,7 @@ const streetItems: {
|
||||
|
||||
<!-- Runway -->
|
||||
<div class="panel" x-show="activeTab === 'backstage'" x-transition.opacity.duration.300ms>
|
||||
<div class="container px-md md:px-xl text-2xl md:text-2xl-1 font-medium mb-4">
|
||||
<div class="text-2xl font-medium mb-4">
|
||||
Hot Brand
|
||||
</div>
|
||||
|
||||
@ -207,20 +207,16 @@ const streetItems: {
|
||||
<style>
|
||||
@reference "tailwindcss";
|
||||
|
||||
:root {
|
||||
--brand: #cf5a44;
|
||||
}
|
||||
|
||||
/* 标签 */
|
||||
.tab {
|
||||
@apply relative pb-[6px] text-[#bbb] transition-colors duration-200 no-underline;
|
||||
@apply relative pb-[6px] text-[var(--c-gray-600)] transition-colors duration-200 no-underline;
|
||||
}
|
||||
.tab:hover {
|
||||
@apply text-[#666];
|
||||
@apply text-[var(--c-gray-800)];
|
||||
}
|
||||
.tab::after {
|
||||
content: "";
|
||||
@apply absolute left-0 right-0 bottom-0 h-[2px] bg-[var(--brand)] scale-x-0 origin-left transition-transform duration-[250ms] ease-in-out;
|
||||
@apply absolute left-0 right-0 bottom-0 h-[2px] bg-black scale-x-0 origin-left transition-transform duration-[250ms] ease-in-out;
|
||||
}
|
||||
.tab.is-active {
|
||||
@apply text-black;
|
||||
@ -283,7 +279,7 @@ const streetItems: {
|
||||
position: relative;
|
||||
display: block;
|
||||
height: clamp(500px, 80vh, 900px);
|
||||
background: #f0f0f0;
|
||||
background: var(--c-gray-100);
|
||||
overflow: hidden;
|
||||
}
|
||||
.hs-img-box > img {
|
||||
@ -307,7 +303,7 @@ const streetItems: {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: 0;
|
||||
background-image: linear-gradient(100deg, #f0f0f0 30%, #e6e6e6 50%, #f0f0f0 70%);
|
||||
background-image: linear-gradient(100deg, var(--c-gray-100) 30%, var(--c-gray-300) 50%, var(--c-gray-100) 70%);
|
||||
background-size: 200% 100%;
|
||||
animation: hs-shimmer 1.3s ease-in-out infinite;
|
||||
transition: opacity 0.35s ease;
|
||||
@ -321,7 +317,7 @@ const streetItems: {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #bdbdbd;
|
||||
color: var(--c-gray-500);
|
||||
pointer-events: none;
|
||||
z-index: 2;
|
||||
transition: opacity 0.3s ease;
|
||||
@ -390,22 +386,18 @@ const streetItems: {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.hs-caption {
|
||||
padding: 60px 28px 26px;
|
||||
}
|
||||
.hs-cap-brand {
|
||||
font-size: 26px;
|
||||
}
|
||||
.hs-cap-meta {
|
||||
font-size: 15px;
|
||||
}
|
||||
.hs-caption {
|
||||
@apply md:px-7 md:pb-7 md:pt-14;
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
.hs-swiper,
|
||||
.hs-img-box {
|
||||
height: clamp(340px, 56vh, 540px);
|
||||
}
|
||||
.hs-cap-brand {
|
||||
@apply md:text-[26px];
|
||||
}
|
||||
.hs-cap-meta {
|
||||
@apply md:text-[15px];
|
||||
}
|
||||
.hs-swiper,
|
||||
.hs-img-box {
|
||||
@apply max-md:h-[clamp(340px,56vh,540px)];
|
||||
}
|
||||
|
||||
/* ========================= Street Style ========================= */
|
||||
@ -416,17 +408,17 @@ const streetItems: {
|
||||
@apply grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 gap-[14px] md:gap-[18px];
|
||||
}
|
||||
.ss-card {
|
||||
@apply relative block w-full overflow-hidden bg-[#f0f0f0] no-underline text-inherit;
|
||||
@apply relative block w-full overflow-hidden bg-[var(--c-gray-100)] no-underline text-inherit;
|
||||
aspect-ratio: 3 / 4;
|
||||
}
|
||||
.ss-img-box {
|
||||
@apply absolute inset-0 w-full h-full overflow-hidden;
|
||||
background: #f0f0f0;
|
||||
background: var(--c-gray-100);
|
||||
}
|
||||
.ss-img-box > img {
|
||||
@apply absolute inset-0 w-full h-full object-cover;
|
||||
opacity: 0;
|
||||
transition: opacity 0.45s ease, transform 0.7s ease;
|
||||
transition: opacity 0.45s ease, transform 0.4s ease;
|
||||
}
|
||||
.ss-img-box.is-loaded > img {
|
||||
opacity: 1;
|
||||
@ -443,7 +435,7 @@ const streetItems: {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: 0;
|
||||
background-image: linear-gradient(100deg, #f0f0f0 30%, #e6e6e6 50%, #f0f0f0 70%);
|
||||
background-image: linear-gradient(100deg, var(--c-gray-100) 30%, var(--c-gray-300) 50%, var(--c-gray-100) 70%);
|
||||
background-size: 200% 100%;
|
||||
animation: hs-shimmer 1.3s ease-in-out infinite;
|
||||
transition: opacity 0.35s ease;
|
||||
@ -458,7 +450,7 @@ const streetItems: {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #bdbdbd;
|
||||
color: var(--c-gray-500);
|
||||
pointer-events: none;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
@ -590,7 +582,12 @@ const streetItems: {
|
||||
|
||||
// 图片加载完成(每张图的 Alpine 组件 dispatch "img-ready")后更新 Swiper
|
||||
window.addEventListener("img-ready", scheduleSwiperUpdate)
|
||||
</script>
|
||||
|
||||
{/* 图片加载状态组件独立成块,刻意不依赖上面的 swiper 模块:
|
||||
避免 swiper 依赖预构建异常(如 Vite 504 Outdated Optimize Dep)时,整段脚本不执行、
|
||||
连 Alpine 的 imgLoader 注册一起失效,导致 x-data="imgLoader()" 报未定义。 */}
|
||||
<script>
|
||||
/* ---------------- 共享图片加载状态组件 ---------------- */
|
||||
// Hot Brand 与 Street Style 复用同一份逻辑:
|
||||
// 1) init() 检查 complete + naturalWidth,缓存已完成的图片立即置位,不一直显示骨架;
|
||||
|
||||
@ -46,7 +46,7 @@ const loginKeywords = locale === 'cn'
|
||||
<meta name="description" content={loginDesc} />
|
||||
<meta name="keywords" content={loginKeywords} />
|
||||
</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">
|
||||
|
||||
@ -73,16 +73,16 @@ const loginKeywords = locale === 'cn'
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- 报头:与账户页同一套 masthead(mono 眉标 + 衬线巨标题 + 档案标签 + 2px 粗黑线) -->
|
||||
<!-- 报头:与账户页同一套 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-zinc-400">{membersLabel}</p>
|
||||
<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>
|
||||
</div>
|
||||
<div class="pb-1 text-right text-[11px] uppercase leading-loose tracking-[0.22em] text-zinc-400">
|
||||
<p>Portfolio Studio</p>
|
||||
<p class="flex items-center justify-end gap-2 text-zinc-500">Archive
|
||||
<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>
|
||||
@ -94,17 +94,17 @@ const loginKeywords = locale === 'cn'
|
||||
<div class="grid gap-12 pt-12 md:grid-cols-[minmax(0,1fr)_320px] md:gap-16 md:pt-16">
|
||||
|
||||
<div class="w-full max-w-md">
|
||||
<p class="mb-10 text-[11px] uppercase tracking-[0.22em] text-zinc-400">{promptLabel}</p>
|
||||
<p class="mb-10 text-[11px] uppercase tracking-[0.22em] text-[var(--c-gray-600)]">{promptLabel}</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-zinc-500">{accountLabel}</label>
|
||||
<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" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="mb-2 block text-[11px] uppercase tracking-[0.22em] text-zinc-500">{passwordLabel}</label>
|
||||
<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>
|
||||
@ -119,16 +119,16 @@ const loginKeywords = locale === 'cn'
|
||||
</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-zinc-400 transition-colors hover:text-black">
|
||||
<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">
|
||||
<span aria-hidden="true">←</span>{backLabel}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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-zinc-400">Archive note</p>
|
||||
<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-zinc-400">
|
||||
<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>
|
||||
|
||||
@ -162,7 +162,7 @@ const runwayGroups = [
|
||||
|
||||
<!-- Card List -->
|
||||
<template x-for="it in items" :key="it.id">
|
||||
<CollectionCard clien />
|
||||
<CollectionCard />
|
||||
</template>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user