From 7bf6af98d157516804c8b0b39cef04be5059274a Mon Sep 17 00:00:00 2001 From: claude-code Date: Wed, 29 Apr 2026 18:16:36 +0800 Subject: [PATCH] =?UTF-8?q?design(qipaobuzz):=20premium=20broadsheet=20lis?= =?UTF-8?q?ting=20=E2=80=94=20hero=20+=20image-row,=20hairline=20dividers,?= =?UTF-8?q?=20scroll-reveal=20animations?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace bordered .post cards with a flowing newspaper-style listing. - First post becomes a hero (16:7 cover + overlay-style headline). - Subsequent posts are image-left rows with ink-stroke vertical accent. - Hover: title underline-fill + image scale + saturate. - IntersectionObserver scroll-reveal, no library deps. - Mobile: 110px thumb, 3-line excerpt clamp. --- src/components/PostList.astro | 102 ++++++++++++--- src/layouts/Layout.astro | 237 +++++++++++++++++++++++++++++++--- 2 files changed, 305 insertions(+), 34 deletions(-) diff --git a/src/components/PostList.astro b/src/components/PostList.astro index 047e577..e31c84e 100644 --- a/src/components/PostList.astro +++ b/src/components/PostList.astro @@ -1,25 +1,93 @@ --- -import { getVisibleTags } from '../lib/markdown.js'; +import { getVisibleTags, renderMarkdown, extractFirstImage } from '../lib/markdown.js'; const { posts, tag } = Astro.props; const visible = getVisibleTags(); ---- -{tag &&

Tagged: {tag}

} -{posts.map((post) => ( -
-

- {post.title} -

- -

{post.rawExcerpt}

+// Pre-compute first cover image per post (from rendered body) so the listing +// can show a real photo on the left without re-parsing inside the template. +const enriched = posts.map((p) => ({ + ...p, + cover: extractFirstImage(renderMarkdown(p.body)), +})); + +const hero = enriched[0]; +const rest = enriched.slice(1); + +const fmt = (d) => new Date(d).toLocaleDateString('en-US', + { year: 'numeric', month: 'long', day: 'numeric' }); +--- +{tag && ( +

+ 栏目 + Tagged: {tag} +

+)} + +{enriched.length > 0 && !tag && ( + +)} + +{(tag ? enriched : rest).map((post, i) => ( + ))} -{posts.length === 0 && ( -

No posts yet.

+{enriched.length === 0 && ( +

尚无文章 · No posts yet.

)} + + diff --git a/src/layouts/Layout.astro b/src/layouts/Layout.astro index 977ae08..3a9e36c 100644 --- a/src/layouts/Layout.astro +++ b/src/layouts/Layout.astro @@ -235,27 +235,219 @@ const ads = site.ads; margin: 0 8px; } - .post { - background: var(--paper); - border: 1px solid var(--border); - border-left: 4px solid var(--red); - padding: 22px 26px; - margin-bottom: 18px; + /* ========== LISTING — premium Chinese broadsheet style ========== + No card boxes; hairline dividers, hover ink-fade, subtle reveal. */ + + .tag-banner { + font-family: var(--display); + font-size: 1.6rem; + color: var(--red); + letter-spacing: 4px; + border-bottom: 2px double var(--red); + padding-bottom: 8px; + margin-bottom: 22px; + } + .tag-banner em { font-style: italic; color: var(--ink); margin-left: 4px; } + .tag-banner-mark { + display: inline-block; + padding: 0 10px; + margin-right: 10px; + background: var(--red); + color: var(--paper); + font-size: 0.85rem; + letter-spacing: 4px; + vertical-align: middle; + border-radius: 1px; + } + + /* HERO post (top story) — wide image with text below in newspaper style */ + .post-hero { + margin: 6px 0 30px; + opacity: 0; + transform: translateY(8px); + transition: opacity 0.6s ease-out, transform 0.6s ease-out; + } + .post-hero.is-visible { opacity: 1; transform: translateY(0); } + .post-hero-link { + display: block; + text-decoration: none; + color: inherit; + } + .post-hero-img { position: relative; - transition: box-shadow 0.2s, transform 0.2s; + overflow: hidden; + aspect-ratio: 16 / 7; + border: 1px solid var(--gold); + box-shadow: 0 4px 18px rgba(31,20,16,0.10); + margin-bottom: 14px; + background: var(--rice-2); } - .post:hover { - box-shadow: 0 6px 20px rgba(139, 13, 34, 0.12); - transform: translateY(-1px); + .post-hero-img img { + width: 100%; height: 100%; + object-fit: cover; + transition: transform 0.6s ease, filter 0.6s ease; + display: block; } - .post::before { - content: "闻"; - position: absolute; - top: 8px; right: 12px; - color: var(--gold); + .post-hero-link:hover .post-hero-img img { + transform: scale(1.025); + filter: saturate(1.05); + } + .post-hero-flag { + display: inline-block; + font-family: var(--brand-cn); + font-size: 0.95rem; + color: var(--paper); + background: var(--red); + padding: 2px 12px; + letter-spacing: 4px; + margin-bottom: 8px; + } + .post-hero-title { + font-family: var(--display); + font-size: 2.1rem; + line-height: 1.25; + margin: 4px 0 8px; + color: var(--ink); + transition: color 0.25s ease; + } + .post-hero-link:hover .post-hero-title { color: var(--red); } + .post-hero-excerpt { + color: var(--ink-soft); + font-size: 1.02rem; + line-height: 1.7; + margin-bottom: 8px; + max-width: 68ch; + } + .post-hero-meta { + font-size: 0.85rem; + color: var(--muted); + letter-spacing: 1px; + display: flex; flex-wrap: wrap; gap: 6px 14px; align-items: center; + } + .post-hero-tag { + font-size: 0.72rem; + color: var(--red); + border: 1px solid var(--red); + padding: 0 8px; + letter-spacing: 1.5px; + text-transform: uppercase; + } + + /* Decorative section divider before the post list */ + .post-row + .post-row::before, + .post-hero + .post-row::before { + content: ""; + display: block; + height: 1px; + background: linear-gradient(90deg, transparent, var(--gold), transparent); + margin: 0 auto 22px; + max-width: 70%; + } + + /* List rows — image left + flowing text right, no card box */ + .post-row { + margin-bottom: 22px; + opacity: 0; + transform: translateY(8px); + transition: opacity 0.55s ease-out, transform 0.55s ease-out; + } + .post-row.is-visible { opacity: 1; transform: translateY(0); } + .post-row-link { + display: grid; + grid-template-columns: 180px 1fr; + gap: 22px; + text-decoration: none; + color: inherit; + align-items: start; + } + .post-row-img { + width: 180px; + aspect-ratio: 4 / 3; + overflow: hidden; + border: 1px solid var(--border); + box-shadow: 0 1px 6px rgba(31,20,16,0.06); + background: var(--rice-2); + position: relative; + } + .post-row-img::after { + /* faint vertical ink-stroke — Chinese print feel */ + content: ""; + position: absolute; right: -2px; top: 8%; bottom: 8%; + width: 1px; + background: linear-gradient(180deg, transparent, var(--red), transparent); opacity: 0.35; - font-size: 1.1rem; } + .post-row-img img { + width: 100%; height: 100%; + object-fit: cover; + display: block; + transition: transform 0.5s ease, filter 0.5s ease; + } + .post-row-link:hover .post-row-img img { + transform: scale(1.04); + filter: saturate(1.05); + } + .post-row-body { min-width: 0; } + .post-row-title { + font-family: var(--display); + font-size: 1.35rem; + line-height: 1.35; + color: var(--ink); + margin-bottom: 6px; + letter-spacing: 0.5px; + transition: color 0.25s ease; + position: relative; + padding-bottom: 2px; + background-image: linear-gradient(var(--red), var(--red)); + background-size: 0% 1px; + background-repeat: no-repeat; + background-position: 0 100%; + transition: background-size 0.4s ease, color 0.25s ease; + } + .post-row-link:hover .post-row-title { + color: var(--red); + background-size: 100% 1px; + } + .post-row-excerpt { + color: var(--ink-soft); + font-size: 0.96rem; + line-height: 1.65; + margin-bottom: 8px; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; + } + .post-row-meta { + font-size: 0.78rem; + color: var(--muted); + letter-spacing: 1px; + display: flex; flex-wrap: wrap; gap: 4px 12px; align-items: center; + } + .post-row-meta time { font-style: italic; } + .post-row-tag { + font-size: 0.7rem; + color: var(--red); + letter-spacing: 1.5px; + padding: 0 6px; + border-left: 2px solid var(--red); + text-transform: uppercase; + } + /* Rows without an image — keep flowing text indent for rhythm */ + .post-row-link:not(:has(.post-row-img)) { + grid-template-columns: 1fr; + } + + .post-empty { + text-align: center; + color: var(--muted); + padding: 60px 0; + font-style: italic; + letter-spacing: 2px; + } + + /* ========== SINGLE-POST PAGE (legacy .post / .post-meta still used) ========== + Kept for the article page (slug.astro) — minimal wrappers. */ .post-title { font-family: var(--display); font-size: 1.6rem; @@ -461,11 +653,22 @@ const ads = site.ads; @media (max-width: 600px) { body { padding: 12px 8px 30px; } main { padding: 16px 14px; } - .post { padding: 16px 18px; } .post-content { padding: 20px 18px; } .brand-name { font-size: 2.1rem; } .related-posts-grid { grid-template-columns: 1fr; } nav.qp-nav a { margin: 2px 4px; padding: 3px 10px; font-size: 0.85rem; } + .post-hero-title { font-size: 1.55rem; } + .post-hero-excerpt { font-size: 0.96rem; } + .post-row-link { + grid-template-columns: 110px 1fr; + gap: 14px; + } + .post-row-img { width: 110px; } + .post-row-title { font-size: 1.1rem; } + .post-row-excerpt { + font-size: 0.88rem; + -webkit-line-clamp: 3; + } }