diff --git a/app/globals.css b/app/globals.css index f69486e..f1e6f11 100644 --- a/app/globals.css +++ b/app/globals.css @@ -48,6 +48,7 @@ /* Shadows */ --shadow-card: 0 1px 3px oklch(0 0 0 / 0.08); + --shadow-card-hover: 0 20px 45px -32px oklch(0 0 0 / 0.35), 0 1px 3px oklch(0 0 0 / 0.08); /* Animations */ --animate-fade-in: fade-in 0.3s ease-out; @@ -125,6 +126,48 @@ --color-code-copy-hover: oklch(0.85 0 0); --shadow-card: 0 1px 4px oklch(1 0 0 / 0.06); + --shadow-card-hover: 0 22px 50px -34px oklch(1 0 0 / 0.22), 0 1px 4px oklch(1 0 0 / 0.08); +} + +/* === Editorial list surfaces === */ +.editorial-hero { + position: relative; + overflow: hidden; + isolation: isolate; + background: + radial-gradient(circle at 20% 10%, color-mix(in oklch, var(--color-accent) 10%, transparent), transparent 34%), + linear-gradient(135deg, color-mix(in oklch, var(--color-surface) 88%, var(--color-canvas)), var(--color-canvas)); +} + +.editorial-hero::after { + content: ""; + position: absolute; + inset: 0; + pointer-events: none; + z-index: 0; + opacity: 0.35; + background-image: radial-gradient(color-mix(in oklch, var(--color-ink) 18%, transparent) 0.5px, transparent 0.5px); + background-size: 12px 12px; + mask-image: linear-gradient(135deg, black, transparent 78%); +} + +.dark .editorial-hero { + background: + radial-gradient(circle at 20% 10%, color-mix(in oklch, var(--color-accent) 14%, transparent), transparent 36%), + linear-gradient(135deg, color-mix(in oklch, var(--color-surface) 82%, var(--color-canvas)), var(--color-canvas)); +} + +.dark .editorial-hero::after { + opacity: 0.22; +} + +.empty-state { + display: grid; + gap: 0.75rem; + border: 1px dashed color-mix(in oklch, var(--color-border) 78%, var(--color-ink) 22%); + border-radius: 1.25rem; + background: color-mix(in oklch, var(--color-surface) 55%, transparent); + padding: 2rem; } /* === Code Blocks: Borderless, VS Code Style, Line Numbers === */ diff --git a/app/page.tsx b/app/page.tsx index bb20a78..56a0004 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -7,14 +7,45 @@ export default async function HomePage() { return ( diff --git a/app/posts/page.tsx b/app/posts/page.tsx index 7d04b71..23d5c2d 100644 --- a/app/posts/page.tsx +++ b/app/posts/page.tsx @@ -9,11 +9,28 @@ export default async function PostsPage() { return ( ) diff --git a/components/blog/PostCard.tsx b/components/blog/PostCard.tsx index 21eab35..1f158a8 100644 --- a/components/blog/PostCard.tsx +++ b/components/blog/PostCard.tsx @@ -1,7 +1,7 @@ 'use client'; import Link from 'next/link'; -import { m } from 'motion/react'; +import { m, useReducedMotion } from 'motion/react'; import type { PostMeta } from '@/lib/posts'; const tagToClientSlug = (tag: string): string => @@ -17,43 +17,46 @@ const tagToClientSlug = (tag: string): string => .replace(/^-|-$/g, ''); export function PostCard({ slug, title, date, excerpt, tags = [], author, readingTime, index = 0, coverImage }: PostMeta & { index?: number }) { + const shouldReduceMotion = useReducedMotion(); + return ( {coverImage && ( -
+
{`Cover
)} -
+
{author && ·} {author && {author}} {readingTime && ·} {readingTime && {readingTime} min read}
-

+

{title}

{excerpt && ( -

{excerpt}

+

{excerpt}

)} {tags && tags.length > 0 && ( -
+
{tags.slice(0, 3).map((tag) => ( - + {tag} ))} diff --git a/components/blog/PostList.tsx b/components/blog/PostList.tsx index a78e000..8dc4e67 100644 --- a/components/blog/PostList.tsx +++ b/components/blog/PostList.tsx @@ -1,6 +1,6 @@ "use client"; -import { m } from "motion/react"; +import { m, useReducedMotion } from "motion/react"; import { PostCard } from "./PostCard"; import type { PostMeta } from "@/lib/posts"; @@ -19,11 +19,18 @@ interface PostListProps { } export function PostList({ posts }: PostListProps) { + const shouldReduceMotion = useReducedMotion(); + return ( - - {posts.map((post) => ( + + {posts.map((post, index) => (
  • - +
  • ))}