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>
|
||||
|
||||
|
||||
@ -40,7 +40,7 @@ const metaKeywords = keywords ?? defaultKeywords
|
||||
// 后端据此选 *_en / *_cn 列。客户端 fetch 则自行按 URL path 推断,无需此处。
|
||||
setApiLocale(locale)
|
||||
|
||||
// 经 data-* 属性把已翻译字符串传入打包型 <script>;define:vars 会让脚本变内联 classic,导致 import 报错,故改用 data 属性
|
||||
// 经 data-* 属性把已翻译字符串传入打包型 script 标签;define:vars 会让脚本变内联 classic,导致 import 报错,故改用 data 属性
|
||||
// (账号区现改为 Alpine 组件,译文直接在模板内用 t() 输出,无需 data 属性中转)
|
||||
|
||||
const pathName = Astro.url.pathname;
|
||||
@ -51,7 +51,7 @@ const pathWithoutLocale = locales.includes(pathName.split('/')[1])
|
||||
: pathName;
|
||||
|
||||
// 顶部导航数据模型(替代原 Alpine x-data 的 menu/state)。
|
||||
// 下拉菜单与语言列表均由此驱动渲染,逻辑用 Astro 原生 <script> 控制开合。
|
||||
// 下拉菜单与语言列表均由此驱动渲染,逻辑用 Astro 原生 script 控制开合。
|
||||
const headers = {
|
||||
// 走秀下拉菜单项:方案 A —— 全部 + 4 个真实 collection_type 分类(与列表页 COLLECTION 筛选一致),
|
||||
// 深链带 ?collection= 参数,由 RunwayLooks 的 init() 读取并自动选中筛选。
|
||||
@ -100,9 +100,9 @@ const headers = {
|
||||
<meta name="description" content={metaDescription} />
|
||||
<meta name="keywords" content={metaKeywords} />
|
||||
</head>
|
||||
<body class="bg-white text-black min-h-screen flex flex-col p-[30px_50px] font-sans">
|
||||
<body class="bg-white text-black min-h-screen flex flex-col px-5 py-5 md:px-[50px] md:py-[30px] font-sans">
|
||||
|
||||
<!-- 加载动画屏:独立组件,标记/样式/隐藏逻辑封装在 LoadingScreen.astro -->
|
||||
<!-- 加载动画屏:独立组件,标记/样式/隐藏逻辑封装在 LoadingOverlay.astro -->
|
||||
<LoadingOverlay />
|
||||
|
||||
<!-- 顶部主导航栏 -->
|
||||
@ -313,25 +313,24 @@ const headers = {
|
||||
})
|
||||
</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">
|
||||
|
||||
<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">
|
||||
Portfolio Studio
|
||||
{SITE_NAME}
|
||||
</span>
|
||||
<span class="text-[10px] bg-black text-white px-2 py-0.5 font-bold uppercase tracking-widest">
|
||||
2026 Edition
|
||||
<span class="text-[10px] bg-black text-white px-2 py-0.5 font-bold uppercase tracking-widest rounded-full">
|
||||
2026
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<p class="text-xs text-zinc-600 leading-relaxed max-w-md">
|
||||
<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>
|
||||
|
||||
{/* Location Badge */}
|
||||
<div class="flex flex-wrap gap-3 text-[11px] text-zinc-500 uppercase tracking-widest pt-1">
|
||||
<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>
|
||||
@ -341,10 +340,30 @@ const headers = {
|
||||
<span>New York</span>
|
||||
</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>
|
||||
|
||||
{/* 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-zinc-400 uppercase tracking-widest">
|
||||
{t('© 2026 PORTFOLIO STUDIO. ALL RIGHTS RESERVED.')}
|
||||
<div class="mt-6 text-[11px] text-[var(--c-gray-600)] uppercase tracking-widest">
|
||||
© 2026 {SITE_NAME}. {t('all rights reserved.')}
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
// src/lib/crypto.ts — 前端 JS 请求签名(反爬一层,非加密)
|
||||
//
|
||||
// 仅做 HMAC-SHA256 计算,由 api.ts 的公开请求原语附带 X-Sign / X-Sign-Ts / X-Sign-Nonce 头,
|
||||
// 仅做 HMAC-SHA256 计算,由 request.ts 的唯一请求入口 request() 附带 X-Sign / X-Sign-Ts / X-Sign-Nonce 头,
|
||||
// 供后端 middleware.ClientSign 校验「请求大概率来自真前端」。
|
||||
//
|
||||
// 安全说明:secret 必须出现在前端 bundle 才能签名(对浏览器可见),故只是「提高成本」,
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
// src/lib/gallery.ts —— 走秀 / 街拍详情页共享的「图集 + 大图灯箱」Alpine 视图工厂。
|
||||
//
|
||||
// Item.astro(走秀)与 StreetSnap.astro(街拍)的灯箱交互逐行同构,差异只有三处:
|
||||
// RunwayLookCard.astro(走秀)与 StreetSnapCard.astro(街拍)的灯箱交互逐行同构,差异只有三处:
|
||||
// ① 根 / 画廊选择器 ② 单图收藏类型 ③ 已登录全量取数函数
|
||||
// 故抽成工厂;各自独有的部分(每行张数、细节图灯箱)由组件在返回值上自行叠加。
|
||||
//
|
||||
@ -79,6 +79,8 @@ export function createGalleryView(cfg: GalleryConfig) {
|
||||
// 登录态快照:用响应式属性暴露给 x-show 表达式(Alpine 表达式作用域访问不到模块 import 的
|
||||
// getUser,故在此初始化,避免 x-show="!getUser()..." 求值报错导致遮罩卡在 x-cloak 隐藏)。
|
||||
isAuthed: false,
|
||||
// 登录事件处理器引用:组件销毁(destroy)时据此移除全局监听,避免跨页面 / 视图切换累积监听器泄漏。
|
||||
onAuthLogin: null as (() => void) | null,
|
||||
// 未登录门禁:后端截断图片集时为 true(详情页最多看 PREVIEW_LIMIT 张)
|
||||
preview: false,
|
||||
// 图集级收藏态:由 loadAuthedState 取回,广播给页面右下角 fav-fab
|
||||
@ -107,11 +109,22 @@ export function createGalleryView(cfg: GalleryConfig) {
|
||||
|
||||
// 已登录:用一次详情请求拿回完整图片集 + 收藏态(合并原三处 /me/favorites/checks 调用)
|
||||
if (getUser()) this.loadAuthedState()
|
||||
// 登录后(如从门禁跳登录回跳)自动补全全量 + 收藏态
|
||||
window.addEventListener("auth:login", () => {
|
||||
// 登录后(如从门禁跳登录回跳)自动补全全量 + 收藏态。
|
||||
// 用命名引用保存处理器,组件销毁(destroy)时据此移除,避免跨页面 / 视图切换累积监听器泄漏。
|
||||
this.onAuthLogin = () => {
|
||||
this.isAuthed = true
|
||||
if (getUser()) this.loadAuthedState()
|
||||
})
|
||||
}
|
||||
window.addEventListener("auth:login", this.onAuthLogin)
|
||||
},
|
||||
|
||||
// 组件销毁时移除全局登录监听(Alpine 在 x-data 根节点移除时自动调用 destroy),
|
||||
// 防止内存 / 监听器随路由切换(尤其是启用 View Transitions 时)不断累积。
|
||||
destroy() {
|
||||
if (this.onAuthLogin) {
|
||||
window.removeEventListener("auth:login", this.onAuthLogin)
|
||||
this.onAuthLogin = null
|
||||
}
|
||||
},
|
||||
|
||||
countImages(): number {
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
// 真正的 HTTP 端点(/me/history*)与单篇回查(getHistoryMeta)在 api.ts。
|
||||
//
|
||||
// 语义(2026-09-02 用户拍板):
|
||||
// - 浏览历史 = 「点开过哪篇文章」,触发时机在 item 详情页(打开某篇走秀/街拍即记一条)。
|
||||
// - 浏览历史 = 「点开过哪篇文章」,触发时机在走秀/街拍详情页(打开某篇即记一条)。
|
||||
// 列表页进入不记。
|
||||
// - 服务端只存 (user_id, target_uid, viewed_at):target_uid 为文章对外编码串
|
||||
// (首字符区分类型:s 开头=街拍 / r 开头=走秀)。
|
||||
@ -77,7 +77,7 @@ function serverMode(): boolean {
|
||||
// ---------- 对外 API ----------
|
||||
|
||||
/**
|
||||
* 记录一次浏览:打开某篇文章详情时调用(见 item 详情页)。
|
||||
* 记录一次浏览:打开某篇文章详情时调用(走秀/街拍详情页)。
|
||||
* 登录态:写入本地缓存 + 后台 POST 服务端(upsert 刷新时间,失败不回滚本地)。
|
||||
* 未登录:仅写入本地缓存。
|
||||
*/
|
||||
|
||||
@ -3,7 +3,7 @@ import { DEFAULT_LOCALE, type Locale } from './index';
|
||||
/**
|
||||
* 单文件翻译字典。
|
||||
*
|
||||
* 设计(按你的要求):
|
||||
* 设计:
|
||||
* - key 就是「英文原文」,直接当文案用,无需额外维护英文副本;
|
||||
* - value 只存「非默认语言」的翻译(本项目默认语言是 en,所以只存 cn 等);
|
||||
* - 英文环境调用 t(key) 直接返回 key 本身;
|
||||
@ -80,6 +80,7 @@ export const dict: Record<string, TranslationEntry> = {
|
||||
|
||||
// ── 页脚 / Footer ──
|
||||
'© 2026 portfolio studio. all rights reserved.': { cn: '© 2026 PORTFOLIO STUDIO. 保留所有权利。' },
|
||||
'all rights reserved.': { cn: '保留所有权利。' },
|
||||
|
||||
// ── 页脚简介 / Manifesto ──
|
||||
'minimalist editorial portfolio documenting haute couture shows, backstage fittings, and street culture. integrated with 2026 color forecast intelligence and ai palette generator.':
|
||||
|
||||
@ -9,7 +9,7 @@ import { getLocaleByPath, getRelativeLocaleUrl } from 'astro:i18n';
|
||||
import { translate } from './dictionary';
|
||||
|
||||
// ── 配置 / Config ──
|
||||
/** 默认语言(无前缀时使用的语言,如 /shows)。 */
|
||||
/** 默认语言(配置为 prefixDefaultLocale: true,故也带前缀,如 /en/shows)。 */
|
||||
export const DEFAULT_LOCALE = 'en' as const;
|
||||
|
||||
/** 支持的语言列表。URL 前缀用的就是这些值,例如 /cn/shows 的 `cn`。 */
|
||||
@ -31,7 +31,7 @@ export const LOCALE_NAMES: Record<string, { name: string; nativeName: string }>
|
||||
// ── 工具 / Utils ──
|
||||
/**
|
||||
* 从 URL 解析语言,委托 Astro 内置的 astro:i18n.getLocaleByPath。
|
||||
* 无前缀(默认语言)路径会解析回 DEFAULT_LOCALE。
|
||||
* 解析失败或非受支持语言时回退 DEFAULT_LOCALE。
|
||||
*/
|
||||
export function getLocaleFromUrl(url: URL | string): Locale {
|
||||
const pathname = typeof url === 'string' ? url : url.pathname;
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
// 全站 URL 路由集中管理
|
||||
// - ROUTES:逻辑路由(不带 locale 前缀)。静态用字符串,动态用工厂函数。
|
||||
// - href:服务端(.astro frontmatter)使用,跟随 astro.config 的 prefixDefaultLocale 规则拼前缀。
|
||||
// - clientHref:客户端脚本(Alpine/Vue)使用,astro:i18n 在浏览器不可用,故自行拼接。
|
||||
// - clientHref:客户端脚本(Alpine)使用,astro:i18n 在浏览器不可用,故自行拼接。
|
||||
|
||||
import { getRelativeLocaleUrl } from 'astro:i18n';
|
||||
|
||||
@ -16,7 +16,6 @@ export const ROUTES = {
|
||||
// 动态路由(工厂函数)。
|
||||
// 走秀 / 街拍详情按内容类型拆分到独立路由,与列表页 slug 保持一致;
|
||||
// 前端按类型化路由分流到对应后端强类型接口(/runway-looks/:id、/street-snaps/:id)。
|
||||
// 旧 /item/[id] 仅保留为 301 重定向(见 src/pages/{locale}/item/[id].astro),按 id 前缀回退。
|
||||
runwayLook: (id: string | number) => `/runway-looks/${id}`,
|
||||
streetSnap: (id: string | number) => `/street-snaps/${id}`,
|
||||
} as const;
|
||||
|
||||
@ -1,5 +1,18 @@
|
||||
@import "tailwindcss";
|
||||
|
||||
/* 中性灰阶令牌:统一全站散落的 #f1f1f1/#efefef/#e6e6e6/#ccc/#bdbdbd/#999/#888/#666 等 */
|
||||
:root {
|
||||
--c-gray-50: #f6f6f6;
|
||||
--c-gray-100: #f1f1f1;
|
||||
--c-gray-200: #efefef;
|
||||
--c-gray-300: #e6e6e6;
|
||||
--c-gray-400: #ccc;
|
||||
--c-gray-500: #bdbdbd;
|
||||
--c-gray-600: #999;
|
||||
--c-gray-700: #888;
|
||||
--c-gray-800: #666;
|
||||
}
|
||||
|
||||
[x-cloak] { display: none !important; }
|
||||
|
||||
/* ===== 共享原子:被多个组件复用的极小样式片段 =====
|
||||
@ -25,4 +38,15 @@
|
||||
.no-scrollbar::-webkit-scrollbar { display: none; }
|
||||
.no-scrollbar { -ms-overflow-style: none; scrollbar-width: none; }
|
||||
|
||||
/* 键盘聚焦可见性:所有可交互元素加黑色描边环,提升无障碍与精致度
|
||||
(hover:opacity 只覆盖鼠标态,键盘 Tab 用户需要明确的焦点指示) */
|
||||
a:focus-visible,
|
||||
button:focus-visible,
|
||||
input:focus-visible,
|
||||
select:focus-visible,
|
||||
[role="button"]:focus-visible {
|
||||
outline: 2px solid #111;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -5,8 +5,6 @@
|
||||
|
||||
@reference "tailwindcss";
|
||||
|
||||
[x-cloak] { display: none !important; }
|
||||
|
||||
/* ====================== 顶部 / 主体布局 ====================== */
|
||||
.showsroot,
|
||||
.snapsroot {
|
||||
@ -27,8 +25,8 @@
|
||||
}
|
||||
|
||||
/* 已激活筛选 pills */
|
||||
.showspills { @apply flex flex-wrap gap-2 pt-3.5; }
|
||||
.snapspills { @apply flex flex-wrap gap-2 mb-4; }
|
||||
.showspills { @apply flex flex-wrap gap-2 mt-4; }
|
||||
.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;
|
||||
@ -49,7 +47,7 @@
|
||||
@apply flex-none w-[240px] bg-white sticky top-5 max-h-[calc(100vh-40px)] flex flex-col max-md:static max-md:max-h-none max-md:w-full;
|
||||
}
|
||||
.showsaside {
|
||||
@apply rounded-none transition-[flex-basis] duration-250 ease-in-out;
|
||||
@apply rounded-none transition-[flex-basis] duration-[250ms] ease-in-out;
|
||||
}
|
||||
.showsaside-head,
|
||||
.snapsaside-head {
|
||||
@ -61,13 +59,13 @@
|
||||
}
|
||||
.showsaside-body,
|
||||
.snapsaside-body {
|
||||
@apply p-[14px_14px_18px] overflow-y-auto flex-1 min-h-0 [scrollbar-width:thin] [scrollbar-color:#ccc_#fff];
|
||||
@apply p-[14px_14px_18px] overflow-y-auto flex-1 min-h-0 [scrollbar-width:thin] [scrollbar-color:var(--c-gray-400)_#fff];
|
||||
}
|
||||
|
||||
/* 筛选分组 */
|
||||
.showsg,
|
||||
.snapsg {
|
||||
@apply border-t border-[#e6e6e6] py-[12px] pb-[6px] first-of-type:border-t-0 first-of-type:pt-1;
|
||||
@apply border-t border-[var(--c-gray-300)] py-[12px] pb-[6px] first-of-type:border-t-0 first-of-type:pt-1;
|
||||
}
|
||||
.showsg-h,
|
||||
.snapsg-h {
|
||||
@ -78,7 +76,7 @@
|
||||
}
|
||||
.showsg-chev,
|
||||
.snapsg-chev {
|
||||
@apply inline-block text-[9px] leading-none text-[#999] transition-transform duration-200 ml-1;
|
||||
@apply inline-block text-[9px] leading-none text-[var(--c-gray-600)] transition-transform duration-200 ml-1;
|
||||
}
|
||||
.showsg-chev.is-collapsed,
|
||||
.snapsg-chev.is-collapsed { @apply -rotate-90; }
|
||||
@ -90,7 +88,7 @@
|
||||
}
|
||||
.showschecks li,
|
||||
.snapschecks li {
|
||||
@apply flex items-center gap-2 py-[5px] text-[11.5px] uppercase tracking-[0.04em] text-black cursor-pointer select-none hover:bg-[#f6f6f6];
|
||||
@apply flex items-center gap-2 py-[5px] text-[11.5px] uppercase tracking-[0.04em] text-black cursor-pointer select-none hover:bg-[var(--c-gray-50)];
|
||||
}
|
||||
.showschecks input[type="radio"],
|
||||
.snapschecks input[type="radio"] {
|
||||
@ -114,7 +112,7 @@
|
||||
}
|
||||
.showsclear,
|
||||
.snapsclear {
|
||||
@apply mt-[22px] block w-full text-left pt-[16px] border-t border-black/10 text-[11px] uppercase tracking-[0.18em] text-[#666] cursor-pointer hover:text-black transition-colors duration-150;
|
||||
@apply mt-[22px] block w-full text-left pt-[16px] border-t border-black/10 text-[11px] uppercase tracking-[0.18em] text-[var(--c-gray-800)] cursor-pointer hover:text-black transition-colors duration-150;
|
||||
}
|
||||
|
||||
/* ====================== 右 网格 / 卡片 ====================== */
|
||||
@ -124,7 +122,7 @@
|
||||
|
||||
.showsgrid,
|
||||
.snapsgrid {
|
||||
@apply grid grid-cols-2 gap-[22px] relative max-[560px]:grid-cols-1;
|
||||
@apply grid grid-cols-2 gap-[22px] relative max-sm:grid-cols-1;
|
||||
}
|
||||
.showsgrid.is-loading::before,
|
||||
.snapscontent.is-loading::before {
|
||||
@ -135,29 +133,29 @@
|
||||
|
||||
.showscard,
|
||||
.snapscard {
|
||||
@apply relative flex gap-[14px] no-underline text-inherit bg-white p-3 transition-colors duration-200 hover:border-black max-[700px]:flex-col max-[700px]:gap-[10px];
|
||||
@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];
|
||||
}
|
||||
.showscard:hover .showscard-img img,
|
||||
.snapscard:hover .snapscard-img img { @apply scale-104; }
|
||||
.snapscard:hover .snapscard-img img { @apply scale-[1.04]; }
|
||||
|
||||
.showscard-left,
|
||||
.snapscard-left {
|
||||
@apply flex-none w-[30%] min-w-[110px] max-[700px]:w-full max-[700px]:flex-none;
|
||||
@apply flex-none w-[30%] min-w-[110px] max-md:w-full max-md:flex-none;
|
||||
}
|
||||
.showscard-img,
|
||||
.snapscard-img {
|
||||
@apply relative w-full aspect-[3/4] bg-[#f1f1f1] overflow-hidden rounded-none;
|
||||
@apply relative w-full aspect-[3/4] bg-[var(--c-gray-100)] overflow-hidden rounded-none;
|
||||
}
|
||||
.showscard-img img,
|
||||
.snapscard-img img {
|
||||
@apply w-full h-full object-cover transition-transform duration-600 opacity-0;
|
||||
@apply w-full h-full object-cover transition-transform duration-[400ms] opacity-0;
|
||||
}
|
||||
.showscard-img img.is-loaded,
|
||||
.snapscard-img img.is-loaded { @apply opacity-100; }
|
||||
|
||||
.showsspin,
|
||||
.snapsspin {
|
||||
@apply absolute inset-0 flex items-center justify-center text-[#bdbdbd] opacity-100 transition-opacity duration-300 pointer-events-none z-10;
|
||||
@apply absolute inset-0 flex items-center justify-center text-[var(--c-gray-500)] opacity-100 transition-opacity duration-300 pointer-events-none z-10;
|
||||
}
|
||||
.showscard-img.is-loaded .showsspin,
|
||||
.snapscard-img.is-loaded .snapsspin,
|
||||
@ -166,7 +164,7 @@
|
||||
|
||||
.showscard-right,
|
||||
.snapscard-right {
|
||||
@apply flex-1 min-w-0 flex flex-col justify-between max-[700px]:w-full;
|
||||
@apply flex-1 min-w-0 flex flex-col justify-between max-md:w-full;
|
||||
}
|
||||
.showscard-info,
|
||||
.snapscard-info {
|
||||
@ -178,7 +176,7 @@
|
||||
}
|
||||
.showscard-meta,
|
||||
.snapscard-meta {
|
||||
@apply text-[9.5px] text-[#888] my-0 mb-[14px] leading-[1.55] tracking-[0.04em] uppercase line-clamp-2 h-[29px] overflow-hidden;
|
||||
@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;
|
||||
}
|
||||
|
||||
.showscard-strip,
|
||||
@ -187,16 +185,16 @@
|
||||
}
|
||||
.showscard-thumb,
|
||||
.snapscard-thumb {
|
||||
@apply relative aspect-[3/4] bg-[#f1f1f1] overflow-hidden;
|
||||
@apply relative aspect-[3/4] bg-[var(--c-gray-100)] overflow-hidden;
|
||||
}
|
||||
.showscard-thumb img,
|
||||
.snapscard-thumb img {
|
||||
@apply w-full h-full object-cover opacity-0 transition-opacity duration-450 ease-in-out;
|
||||
@apply w-full h-full object-cover opacity-0 transition-opacity duration-[450ms] ease-in-out;
|
||||
}
|
||||
.showscard-thumb img.is-loaded,
|
||||
.snapscard-thumb img.is-loaded { @apply opacity-100; }
|
||||
.showscard-thumb.placeholder,
|
||||
.snapscard-thumb.placeholder { @apply bg-[#f6f6f6]; }
|
||||
.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;
|
||||
}
|
||||
@ -208,7 +206,7 @@
|
||||
.snapscard--skel .snapscard-img,
|
||||
.snapscard--skel .snapscard-thumb,
|
||||
.snapscard--skel .snapsline {
|
||||
@apply bg-[#efefef];
|
||||
@apply bg-[var(--c-gray-200)];
|
||||
animation: looksShim 1.6s ease infinite;
|
||||
}
|
||||
|
||||
@ -220,12 +218,12 @@
|
||||
}
|
||||
.pg.is-current { @apply bg-black text-white cursor-default; }
|
||||
.pg:disabled { @apply opacity-30 cursor-default; }
|
||||
.pg-ellipsis { @apply px-1 text-[#999] select-none text-[11px]; }
|
||||
.pg-ellipsis { @apply px-1 text-[var(--c-gray-600)] select-none text-[11px]; }
|
||||
.pg-wrap { @apply inline-flex items-center; }
|
||||
|
||||
.showsempty,
|
||||
.snapsempty {
|
||||
@apply col-span-full p-[80px_20px] text-center text-xs tracking-[0.22em] uppercase text-[#999] border border-dashed border-[#ccc];
|
||||
@apply col-span-full p-[80px_20px] text-center text-xs tracking-[0.22em] uppercase text-[var(--c-gray-600)] border border-dashed border-[var(--c-gray-400)];
|
||||
}
|
||||
|
||||
@keyframes looksProg { 0% { left: -38%; } 100% { left: 100%; } }
|
||||
|
||||
Reference in New Issue
Block a user