diff --git a/.workbuddy/memory/2026-09-13.md b/.workbuddy/memory/2026-09-13.md
new file mode 100644
index 0000000..cee9855
--- /dev/null
+++ b/.workbuddy/memory/2026-09-13.md
@@ -0,0 +1,57 @@
+# 2026-09-13 工作日志
+
+## 首页图片加载状态 + Swiper 初始化优化(14:5x)✅
+用户建议对首页 Index.astro 的 Hot Brand / Street Style 图片加载与 Swiper 初始化做一层优化(7 点)。改动:
+- **统一图片状态初始化逻辑**:Hot Brand `hs-img-box` 与 Street Style `ss-img-box` 两份完全重复的内联 `x-data="{loaded,error,init()检查complete+naturalWidth,handleLoad,handleError}"` 抽成共享 Alpine 组件 `imgLoader`,在 `
\ No newline at end of file
+ // 销毁已有实例:防止重复初始化 / View Transitions 重新进入时旧实例泄漏
+ function destroySwipers() {
+ for (const swiper of swipers) {
+ if (!swiper.destroyed) swiper.destroy(true, true)
+ }
+ swipers = []
+ }
+
+ function initSwipers() {
+ destroySwipers()
+ document.querySelectorAll(".hs-swiper").forEach((el) => {
+ const prevBtn = el.parentElement?.querySelector(".hs-prev")
+ const nextBtn = el.parentElement?.querySelector(".hs-next")
+ const swiper = new Swiper(el, {
+ modules: [Navigation, FreeMode, Keyboard],
+ slidesPerView: "auto",
+ spaceBetween: 4,
+ grabCursor: true,
+ speed: 220,
+ freeMode: { enabled: true, sticky: true, momentumBounce: false, momentumRatio: 0.5 },
+ keyboard: { enabled: true },
+ navigation: { prevEl: prevBtn, nextEl: nextBtn, disabledClass: "swiper-button-disabled" },
+ observer: true,
+ observeParents: true,
+ observeSlideChildren: true,
+ resizeObserver: true,
+ })
+ swipers.push(swiper)
+ })
+ scheduleSwiperUpdate()
+ }
+
+ // 统一入口:延后一帧,避免首次进入时 DOM / Alpine / Swiper 初始化时序互相抢
+ function setupSwipers() {
+ requestAnimationFrame(() => initSwipers())
+ }
+
+ // 首次普通加载:等 DOMContentLoaded
+ if (document.readyState === "loading") {
+ document.addEventListener("DOMContentLoaded", setupSwipers, { once: true })
+ } else {
+ setupSwipers()
+ }
+
+ // Astro View Transitions 客户端导航进入:重新初始化
+ document.addEventListener("astro:page-load", setupSwipers)
+
+ // Tab 切换后重算尺寸(事件委托,兼容 View Transitions 后的新 DOM)
+ document.addEventListener("click", (e) => {
+ if (e.target.closest(".tab")) {
+ setTimeout(scheduleSwiperUpdate, 60)
+ }
+ })
+
+ // 浏览器尺寸变化
+ window.addEventListener("resize", scheduleSwiperUpdate)
+
+ // 图片加载完成(每张图的 Alpine 组件 dispatch "img-ready")后更新 Swiper
+ window.addEventListener("img-ready", scheduleSwiperUpdate)
+
+ /* ---------------- 共享图片加载状态组件 ---------------- */
+ // Hot Brand 与 Street Style 复用同一份逻辑:
+ // 1) init() 检查 complete + naturalWidth,缓存已完成的图片立即置位,不一直显示骨架;
+ // 2) 保留 @load / @error,处理 Alpine 初始化之后才完成的图片。
+ document.addEventListener("alpine:init", () => {
+ Alpine.data("imgLoader", () => ({
+ loaded: false,
+ error: false,
+ init() {
+ const img = this.$el.querySelector("img")
+ if (!img) return
+ if (img.complete) {
+ this.loaded = img.naturalWidth > 0
+ this.error = img.naturalWidth === 0
+ if (this.loaded || this.error) this.$dispatch("img-ready")
+ }
+ },
+ handleLoad() {
+ this.error = false
+ this.loaded = true
+ this.$dispatch("img-ready")
+ },
+ handleError() {
+ this.loaded = false
+ this.error = true
+ this.$dispatch("img-ready")
+ },
+ }))
+ })
+
diff --git a/src/components/Item.astro b/src/components/Item.astro
index d531f5c..0b3a700 100644
--- a/src/components/Item.astro
+++ b/src/components/Item.astro
@@ -45,7 +45,7 @@ for (const d of (article?.details ?? []) as { look_index?: number; images?: unkn
}
// 未登录预览上限:详情页(图集)未登录最多展示前 PREVIEW_LIMIT 张图片;其余由登录门禁提示,
-// 已登录(客户端 loadFull 拉全量)或图片本就不超上限时全部展示。前 PREVIEW_LIMIT 张服务端已渲染(SEO/无 JS 友好)。
+// 已登录(客户端 loadAuthedState 拉全量)或图片本就不超上限时全部展示。前 PREVIEW_LIMIT 张服务端已渲染(SEO/无 JS 友好)。
const PREVIEW_LIMIT = 5
const previewImages = images.slice(0, PREVIEW_LIMIT)
@@ -214,7 +214,7 @@ const homeHref = href(locale, ROUTES.home)
))
}
- {/* 登录后追加的额外图片:extraImages 由客户端 loadFull() 在已登录时填充,未登录为空不渲染 */}
+ {/* 登录后追加的额外图片:extraImages 由客户端 loadAuthedState() 在已登录时填充,未登录为空不渲染 */}