diff --git a/src/components/PostList.astro b/src/components/PostList.astro index 63431ff..70d82a3 100644 --- a/src/components/PostList.astro +++ b/src/components/PostList.astro @@ -1,23 +1,93 @@ --- const { posts, tag } = Astro.props; ---- -{tag &&

Tagged: {tag}

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

- {post.title} -

-
- {new Date(post.date).toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' })} - {post.tags.map((t) => ( - {t} - ))} -
-

{post.rawExcerpt}

+// Extract first cover image directly from the raw markdown body. +// Cheap regex — avoids rendering the full HTML for each card. +function firstImage(body) { + const m = body && body.match(/!\[[^\]]*\]\((\/images\/[^)]+)\)/); + return m ? m[1] : null; +} + +const enriched = posts.map((p) => ({ + ...p, + cover: firstImage(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 && hero && ( +
+ + {hero.cover && ( +
+ +
+ )} +
+
Top story
+

{hero.title}

+

{hero.rawExcerpt}

+
+ {fmt(hero.date)} + {hero.tags.slice(0, 4).map((t) => ( + {t} + ))} +
+
+ +
+)} + +{(tag ? enriched : rest).map((post, i) => ( +
+ + {post.cover && ( +
+ +
+ )} +
+

{post.title}

+

{post.rawExcerpt}

+
+ + {post.tags.slice(0, 3).map((t) => ( + {t} + ))} +
+
+
))} -{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 6d982d6..17e69dd 100644 --- a/src/layouts/Layout.astro +++ b/src/layouts/Layout.astro @@ -449,6 +449,207 @@ const year = new Date().getFullYear(); .post-content > p:first-of-type::first-letter { font-size: 52px; } .related-posts-grid { grid-template-columns: 1fr; } } + + /* ========== Reveal animation for cards ========== */ + [data-reveal] { + opacity: 0; + transform: translateY(12px); + transition: opacity 0.6s ease, transform 0.6s ease; + } + [data-reveal].is-visible { + opacity: 1; + transform: translateY(0); + } + + /* ========== Tag banner ========== */ + .tag-banner { + font-family: 'Fraunces', 'Noto Serif SC', serif; + font-weight: 600; + font-size: 24px; + color: var(--ink); + margin-bottom: 28px; + padding-bottom: 16px; + border-bottom: 1px solid var(--line); + font-variation-settings: 'opsz' 36; + } + .tag-banner em { font-style: italic; color: var(--accent); } + + /* ========== Hero card ========== */ + .post-hero { + margin-bottom: 44px; + border-radius: 14px; + overflow: hidden; + background: var(--bg-card); + border: 1px solid var(--line); + transition: transform 0.18s ease, border-color 0.18s ease, box-shadow 0.18s ease; + } + .post-hero:hover { + transform: translateY(-2px); + box-shadow: 0 18px 48px -22px rgba(40, 30, 50, 0.22); + } + .post-hero-link { + display: grid; + grid-template-columns: 1.1fr 1fr; + text-decoration: none; + color: inherit; + } + .post-hero-img { + overflow: hidden; + background: var(--bg-soft); + min-height: 280px; + } + .post-hero-img img { + width: 100%; height: 100%; + object-fit: cover; display: block; + transition: transform 0.6s ease; + } + .post-hero:hover .post-hero-img img { transform: scale(1.04); } + .post-hero-body { + padding: 36px 36px; + display: flex; + flex-direction: column; + justify-content: center; + } + .post-hero-flag { + color: var(--accent); + font-size: 11px; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.16em; + margin-bottom: 12px; + } + .post-hero-title { + font-family: 'Fraunces', 'Noto Serif SC', serif; + font-weight: 700; + font-size: clamp(26px, 3.5vw, 36px); + line-height: 1.12; + letter-spacing: -0.02em; + color: var(--ink); + margin-bottom: 14px; + font-variation-settings: 'opsz' 144; + transition: color 0.15s; + } + .post-hero:hover .post-hero-title { color: var(--accent); } + .post-hero-excerpt { + color: var(--ink-soft); + font-size: 16px; + line-height: 1.6; + margin-bottom: 18px; + } + .post-hero-meta { + display: flex; flex-wrap: wrap; gap: 10px; align-items: center; + color: var(--ink-muted); + font-size: 13px; + letter-spacing: 0.02em; + } + .post-hero-tag { + display: inline-block; + padding: 2px 10px; + border: 1px solid var(--line); + border-radius: 999px; + color: var(--ink-soft); + font-size: 12px; + } + .post-hero-tag a { color: inherit; text-decoration: none; } + + /* ========== Row cards ========== */ + .post-row { + background: var(--bg-card); + border: 1px solid var(--line); + border-radius: 12px; + margin-bottom: 16px; + overflow: hidden; + transition: transform 0.18s ease, border-color 0.18s ease, box-shadow 0.18s ease; + } + .post-row:hover { + transform: translateY(-2px); + box-shadow: 0 14px 36px -18px rgba(40, 30, 50, 0.18); + } + .post-row-link { + display: grid; + grid-template-columns: 220px 1fr; + text-decoration: none; + color: inherit; + } + .post-row-img { + overflow: hidden; + background: var(--bg-soft); + } + .post-row-img img { + width: 100%; height: 100%; + min-height: 160px; + object-fit: cover; display: block; + transition: transform 0.5s ease; + } + .post-row:hover .post-row-img img { transform: scale(1.04); } + .post-row-body { + padding: 22px 26px; + display: flex; flex-direction: column; + justify-content: center; + } + .post-row-link:has(.post-row-body:only-child) { + grid-template-columns: 1fr; + } + .post-row-title { + font-family: 'Fraunces', 'Noto Serif SC', serif; + font-weight: 600; + font-size: 22px; + line-height: 1.22; + letter-spacing: -0.015em; + color: var(--ink); + margin-bottom: 8px; + font-variation-settings: 'opsz' 80; + transition: color 0.15s; + } + .post-row:hover .post-row-title { color: var(--accent); } + .post-row-excerpt { + color: var(--ink-soft); + font-size: 15px; + line-height: 1.55; + margin-bottom: 12px; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; + } + .post-row-meta { + display: flex; flex-wrap: wrap; gap: 8px; align-items: center; + color: var(--ink-muted); + font-size: 12px; + letter-spacing: 0.02em; + } + .post-row-tag { + display: inline-block; + padding: 1px 9px; + background: var(--bg-soft); + border: 1px solid var(--line); + border-radius: 999px; + color: var(--ink-soft); + font-size: 11px; + font-weight: 500; + } + .post-row-tag a { color: inherit; text-decoration: none; } + + .post-empty { + color: var(--ink-muted); + text-align: center; + padding: 80px 0; + font-size: 15px; + } + + @media (max-width: 820px) { + .post-hero-link { grid-template-columns: 1fr; } + .post-hero-img { min-height: 220px; max-height: 260px; } + .post-hero-body { padding: 24px 22px; } + } + @media (max-width: 700px) { + .post-row-link { grid-template-columns: 110px 1fr; } + .post-row-img img { min-height: 110px; } + .post-row-body { padding: 16px 18px; } + .post-row-title { font-size: 17px; } + .post-row-excerpt { font-size: 13px; } + } +