diff --git a/app/blog/[slug]/page.tsx b/app/blog/[slug]/page.tsx index d18a69d..d6a9dcf 100644 --- a/app/blog/[slug]/page.tsx +++ b/app/blog/[slug]/page.tsx @@ -9,6 +9,8 @@ import { PostToc } from '@/components/post-toc'; import { ScrollReveal } from '@/components/scroll-reveal'; import { PostCard } from '@/components/post-card'; import { PostStorylineNav } from '@/components/post-storyline-nav'; +import { SectionDivider } from '@/components/section-divider'; +import { FooterCue } from '@/components/footer-cue'; export function generateStaticParams() { return allPosts.map((post) => ({ @@ -48,77 +50,87 @@ export default function BlogPostPage({ params }: Props) {
- -
- {post.published_at && ( -

- {new Date(post.published_at).toLocaleDateString( - siteConfig.defaultLocale - )} -

- )} -

- {post.title} -

- {post.tags && ( -
- {post.tags.map((t) => ( - - #{t} - - ))} -
- )} -
-
+ + +
+ {post.published_at && ( +

+ {new Date(post.published_at).toLocaleDateString( + siteConfig.defaultLocale + )} +

+ )} +

+ {post.title} +

+ {post.tags && ( +
+ {post.tags.map((t) => ( + + #{t} + + ))} +
+ )} +
+
+
- -
- {post.feature_image && ( - // feature_image is stored as "../assets/xyz", serve from "/assets/xyz" - // eslint-disable-next-line @next/next/no-img-element - {post.title} - )} -
-
-
+ + +
+ {post.feature_image && ( + // feature_image is stored as "../assets/xyz", serve from "/assets/xyz" + // eslint-disable-next-line @next/next/no-img-element + {post.title} + )} +
+
+
+
- - - + + + + + + + {relatedPosts.length > 0 && ( - -
-
-

- 相關文章 -

-

- 為你挑選相似主題 -

-
-
- {relatedPosts.map((related) => ( - - ))} -
-
-
+ + +
+
+

+ 相關文章 +

+

+ 為你挑選相似主題 +

+
+
+ {relatedPosts.map((related) => ( + + ))} +
+
+
+
)}
diff --git a/app/blog/page.tsx b/app/blog/page.tsx index c30315a..3cd083d 100644 --- a/app/blog/page.tsx +++ b/app/blog/page.tsx @@ -11,10 +11,10 @@ export default function BlogIndexPage() { return (
-

+

所有文章

-

+

繼續往下滑,慢慢逛逛。

diff --git a/app/page.tsx b/app/page.tsx index 42de3b8..9e177f8 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -9,17 +9,17 @@ export default function HomePage() { return (
-

+

{siteConfig.name} 的最新動態

-

+

{siteConfig.tagline}

-

+

最新文章

-

+

標籤索引

-

+

目前共有 {tags.length} 個標籤。

@@ -35,7 +35,7 @@ export default function TagIndexPage() { {tag} ({count}) diff --git a/components/footer-cue.tsx b/components/footer-cue.tsx new file mode 100644 index 0000000..126beaf --- /dev/null +++ b/components/footer-cue.tsx @@ -0,0 +1,43 @@ +'use client'; + +import { useEffect, useRef, useState } from 'react'; + +export function FooterCue() { + const ref = useRef(null); + const [active, setActive] = useState(false); + + useEffect(() => { + const el = ref.current; + if (!el) return; + + if (!('IntersectionObserver' in window)) { + setActive(true); + return; + } + + const observer = new IntersectionObserver( + (entries) => { + entries.forEach((entry) => { + if (entry.isIntersecting) { + setActive(true); + } + }); + }, + { threshold: 0.2 } + ); + + observer.observe(el); + return () => observer.disconnect(); + }, []); + + return ( +
+ 即將展開 + +
+ ); +} diff --git a/components/hero.tsx b/components/hero.tsx index b313cf9..6421223 100644 --- a/components/hero.tsx +++ b/components/hero.tsx @@ -64,11 +64,12 @@ export function Hero() {
-
+
{initial}
-

+

+

diff --git a/components/right-sidebar.tsx b/components/right-sidebar.tsx index df967a6..688170c 100644 --- a/components/right-sidebar.tsx +++ b/components/right-sidebar.tsx @@ -103,7 +103,7 @@ export function RightSidebar() { {tag} diff --git a/components/section-divider.tsx b/components/section-divider.tsx new file mode 100644 index 0000000..9b44a57 --- /dev/null +++ b/components/section-divider.tsx @@ -0,0 +1,53 @@ +'use client'; + +import { useEffect, useRef, useState, ReactNode } from 'react'; +import clsx from 'clsx'; + +interface SectionDividerProps { + children: ReactNode; + className?: string; +} + +export function SectionDivider({ children, className }: SectionDividerProps) { + const ref = useRef(null); + const [visible, setVisible] = useState(false); + + useEffect(() => { + const el = ref.current; + if (!el) return; + + if (!('IntersectionObserver' in window)) { + setVisible(true); + return; + } + + const observer = new IntersectionObserver( + (entries) => { + entries.forEach((entry) => { + if (entry.isIntersecting) { + setVisible(true); + } + }); + }, + { threshold: 0.15, rootMargin: '0px 0px -20% 0px' } + ); + + observer.observe(el); + return () => observer.disconnect(); + }, []); + + return ( +
+ + {children} +
+ ); +} diff --git a/styles/globals.css b/styles/globals.css index e78774a..bdf1d5f 100644 --- a/styles/globals.css +++ b/styles/globals.css @@ -7,10 +7,22 @@ --motion-duration-medium: 260ms; --motion-ease-snappy: cubic-bezier(0.32, 0.72, 0, 1); --card-translate-y: -6px; + --line-height-body: clamp(1.5, 0.15vw + 1.45, 1.65); + + font-size: clamp(15px, 0.65vw + 11px, 19px); +} + +@media (min-width: 2560px) { + :root { + font-size: 20px; + } } body { - @apply bg-white text-gray-900 transition-colors duration-200 ease-snappy dark:bg-gray-950 dark:text-gray-100 text-[15px] sm:text-base; + @apply bg-white text-gray-900 transition-colors duration-200 ease-snappy dark:bg-gray-950 dark:text-gray-100; + font-size: 1rem; + line-height: var(--line-height-body); + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang TC', 'Noto Sans TC', 'Microsoft JhengHei', 'Noto Sans', sans-serif; } .toc-target-highlight { @@ -34,6 +46,37 @@ body { @apply -translate-y-0.5 shadow-md; } +.prose { + font-size: clamp(1rem, 0.2vw + 0.9rem, 1.2rem); + line-height: var(--line-height-body); +} + +.prose h1 { + font-size: clamp(2.2rem, 1.4rem + 2.2vw, 3.4rem); + line-height: 1.25; +} + +.prose h2 { + font-size: clamp(1.8rem, 1.1rem + 1.6vw, 2.8rem); + line-height: 1.3; +} + +.prose h3 { + font-size: clamp(1.4rem, 0.9rem + 1vw, 2rem); + line-height: 1.35; +} + +.prose p, +.prose li { + font-size: clamp(1rem, 0.2vw + 0.9rem, 1.15rem); + line-height: var(--line-height-body); +} + +.prose small, +.prose figcaption { + font-size: clamp(0.85rem, 0.2vw + 0.8rem, 0.95rem); +} + .prose h1 > a, .prose h2 > a, .prose h3 > a, @@ -103,6 +146,31 @@ body { } @layer components { + .type-display { + font-size: clamp(2.2rem, 1.6rem + 2.4vw, 3.5rem); + line-height: 1.2; + } + + .type-title { + font-size: clamp(1.6rem, 1.1rem + 1.4vw, 2.6rem); + line-height: 1.3; + } + + .type-subtitle { + font-size: clamp(1.25rem, 0.9rem + 1vw, 1.9rem); + line-height: 1.35; + } + + .type-body { + font-size: clamp(1rem, 0.2vw + 0.9rem, 1.15rem); + line-height: var(--line-height-body); + } + + .type-small { + font-size: clamp(0.85rem, 0.2vw + 0.8rem, 0.95rem); + line-height: 1.4; + } + .motion-card { transition: transform var(--motion-duration-medium) var(--motion-ease-snappy), box-shadow var(--motion-duration-medium) var(--motion-ease-snappy),