Integrated Pagefind for static site search with built-in Chinese word segmentation support. Changes: 1. **Installed Pagefind** (v1.4.0) as dev dependency 2. **Updated build script** to run Pagefind indexing after Next.js build - Indexes all 69 pages with 5,711 words - Automatic Chinese (zh-tw) language detection 3. **Created search modal component** (components/search-modal.tsx) - Dynamic Pagefind UI loading (lazy-loaded on demand) - Keyboard shortcuts (Cmd+K / Ctrl+K) - Chinese translations for UI elements - Dark mode compatible styling 4. **Added search button to header** (components/site-header.tsx) - Integrated SearchButton with keyboard shortcut display - Modal state management 5. **Custom Pagefind styles** (styles/globals.css) - Tailwind-based styling to match site design - Dark mode support - Highlight styling for search results Features: - ✅ Full-text search across all blog posts and pages - ✅ Built-in Chinese word segmentation (Unicode-based) - ✅ Mixed Chinese/English query support - ✅ Zero bundle impact (20KB lazy-loaded on search activation) - ✅ Keyboard shortcuts (⌘K / Ctrl+K) - ✅ Search result highlighting with excerpts - ✅ Dark mode compatible Technical Details: - Pagefind runs post-build to index .next directory - Search index stored in .next/pagefind/ - Chinese segmentation works automatically via Unicode boundaries - No third-party services or API keys required 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
313 lines
7.9 KiB
CSS
313 lines
7.9 KiB
CSS
@tailwind base;
|
|
@tailwind components;
|
|
@tailwind utilities;
|
|
|
|
:root {
|
|
--motion-duration-short: 180ms;
|
|
--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-weight-regular: 400;
|
|
--font-weight-medium: 500;
|
|
--font-weight-semibold: 600;
|
|
--font-system-sans: -apple-system, BlinkMacSystemFont, 'SF Pro Text', 'SF Pro Display', 'PingFang TC', 'PingFang SC', 'Hiragino Sans', 'Hiragino Kaku Gothic ProN', 'Segoe UI Variable Text', 'Segoe UI', 'Microsoft JhengHei', 'Microsoft YaHei', 'Noto Sans TC', 'Noto Sans SC', 'Noto Sans CJK TC', 'Noto Sans CJK SC', 'Source Han Sans TC', 'Source Han Sans SC', 'Roboto', 'Ubuntu', 'Cantarell', 'Inter', 'Helvetica Neue', Arial, sans-serif;
|
|
|
|
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;
|
|
font-size: 1rem;
|
|
line-height: var(--line-height-body);
|
|
font-family: var(--font-system-sans);
|
|
}
|
|
|
|
@keyframes timeline-scroll {
|
|
0% {
|
|
transform: translate(-50%, -10%);
|
|
opacity: 0;
|
|
}
|
|
|
|
15% {
|
|
opacity: 1;
|
|
}
|
|
|
|
85% {
|
|
opacity: 1;
|
|
}
|
|
|
|
100% {
|
|
transform: translate(-50%, 110%);
|
|
opacity: 0;
|
|
}
|
|
}
|
|
|
|
|
|
.toc-target-highlight {
|
|
@apply bg-yellow-50/60 dark:bg-yellow-900/40 transition-colors duration-500;
|
|
}
|
|
|
|
/* Subtle hover for article elements */
|
|
.prose blockquote {
|
|
@apply transition-transform transition-shadow duration-180 ease-snappy;
|
|
border-left: 4px solid var(--color-accent, #2563eb);
|
|
background: linear-gradient(135deg, rgba(37, 99, 235, 0.04), rgba(37, 99, 235, 0.08));
|
|
padding: 1.2rem 1.5rem;
|
|
font-style: italic;
|
|
color: rgba(15, 23, 42, 0.75);
|
|
position: relative;
|
|
}
|
|
|
|
.dark .prose blockquote {
|
|
background: linear-gradient(135deg, rgba(96, 165, 250, 0.12), rgba(96, 165, 250, 0.06));
|
|
color: rgba(226, 232, 240, 0.8);
|
|
border-left-color: rgba(96, 165, 250, 0.9);
|
|
}
|
|
|
|
.prose blockquote:hover {
|
|
@apply -translate-y-0.5 shadow-sm;
|
|
}
|
|
|
|
.prose blockquote::before {
|
|
content: '“';
|
|
position: absolute;
|
|
top: 0.5rem;
|
|
left: 0.8rem;
|
|
font-size: 3rem;
|
|
font-family: 'Times New Roman', 'Noto Serif TC', serif;
|
|
color: rgba(37, 99, 235, 0.25);
|
|
pointer-events: none;
|
|
}
|
|
|
|
.prose pre {
|
|
@apply transition-transform transition-shadow duration-180 ease-snappy;
|
|
}
|
|
|
|
.prose pre:hover {
|
|
@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,
|
|
.prose h4>a,
|
|
.prose h5>a,
|
|
.prose h6>a {
|
|
text-decoration: none !important;
|
|
color: inherit !important;
|
|
}
|
|
|
|
.hero-title {
|
|
position: relative;
|
|
display: inline-flex;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.hero-title__sweep {
|
|
position: absolute;
|
|
inset: 0;
|
|
background: linear-gradient(120deg, transparent 10%, rgba(59, 130, 246, 0.35) 45%, transparent 90%);
|
|
transform: translateX(-120%);
|
|
animation: hero-sweep 4s var(--motion-ease-snappy) infinite;
|
|
pointer-events: none;
|
|
}
|
|
|
|
|
|
@keyframes hero-sweep {
|
|
0% {
|
|
transform: translateX(-120%);
|
|
opacity: 0;
|
|
}
|
|
|
|
30% {
|
|
opacity: 0.4;
|
|
}
|
|
|
|
60% {
|
|
transform: translateX(120%);
|
|
opacity: 0;
|
|
}
|
|
|
|
100% {
|
|
transform: translateX(120%);
|
|
opacity: 0;
|
|
}
|
|
}
|
|
|
|
.tag-chip {
|
|
position: relative;
|
|
overflow: hidden;
|
|
transition: color var(--motion-duration-short) var(--motion-ease-snappy), background-color var(--motion-duration-short) var(--motion-ease-snappy);
|
|
}
|
|
|
|
.tag-chip::after {
|
|
content: '';
|
|
position: absolute;
|
|
left: 50%;
|
|
bottom: 4px;
|
|
width: 0;
|
|
height: 2px;
|
|
background: currentColor;
|
|
opacity: 0.5;
|
|
transition: width var(--motion-duration-short) var(--motion-ease-snappy), left var(--motion-duration-short) var(--motion-ease-snappy);
|
|
}
|
|
|
|
.tag-chip:hover::after,
|
|
.tag-chip:focus-visible::after {
|
|
width: 100%;
|
|
left: 0;
|
|
}
|
|
|
|
@layer components {
|
|
.type-display {
|
|
font-size: clamp(2.2rem, 1.6rem + 2.4vw, 3.5rem);
|
|
line-height: 1.2;
|
|
font-weight: var(--font-weight-semibold);
|
|
font-family: var(--font-serif-eng), "Songti SC", serif;
|
|
}
|
|
|
|
.type-title {
|
|
font-size: clamp(1.6rem, 1.1rem + 1.4vw, 2.6rem);
|
|
line-height: 1.3;
|
|
font-weight: var(--font-weight-semibold);
|
|
font-family: var(--font-serif-eng), "Songti SC", serif;
|
|
letter-spacing: 0.02em;
|
|
}
|
|
|
|
.type-subtitle {
|
|
font-size: clamp(1.25rem, 0.9rem + 1vw, 1.9rem);
|
|
line-height: 1.35;
|
|
font-weight: var(--font-weight-medium);
|
|
font-family: var(--font-serif-eng), "Songti SC", serif;
|
|
letter-spacing: 0.02em;
|
|
}
|
|
|
|
.type-body {
|
|
font-size: clamp(1rem, 0.2vw + 0.9rem, 1.15rem);
|
|
line-height: var(--line-height-body);
|
|
font-weight: var(--font-weight-regular);
|
|
}
|
|
|
|
.type-small {
|
|
font-size: clamp(0.85rem, 0.2vw + 0.8rem, 0.95rem);
|
|
line-height: 1.4;
|
|
font-weight: var(--font-weight-regular);
|
|
}
|
|
|
|
h1 {
|
|
font-weight: 700;
|
|
letter-spacing: -0.03em;
|
|
font-family: var(--font-serif-eng), "Songti SC", serif;
|
|
}
|
|
|
|
h2 {
|
|
font-weight: 600;
|
|
letter-spacing: -0.02em;
|
|
font-family: var(--font-serif-eng), "Songti SC", serif;
|
|
}
|
|
|
|
.type-nav {
|
|
font-size: clamp(0.95rem, 0.2vw + 0.85rem, 1.05rem);
|
|
font-weight: var(--font-weight-medium);
|
|
letter-spacing: 0.04em;
|
|
}
|
|
|
|
.motion-card {
|
|
transition: transform var(--motion-duration-medium) var(--motion-ease-snappy),
|
|
box-shadow var(--motion-duration-medium) var(--motion-ease-snappy),
|
|
background-color var(--motion-duration-medium) var(--motion-ease-snappy),
|
|
border-color var(--motion-duration-medium) var(--motion-ease-snappy);
|
|
}
|
|
|
|
.motion-card:hover {
|
|
transform: translateY(var(--card-translate-y));
|
|
}
|
|
|
|
.motion-link {
|
|
transition: color var(--motion-duration-short) var(--motion-ease-snappy),
|
|
transform var(--motion-duration-short) var(--motion-ease-snappy);
|
|
}
|
|
}
|
|
|
|
/* Pagefind Search Styles */
|
|
.pagefind-ui__search-input {
|
|
@apply w-full rounded-lg border border-slate-200 bg-white px-4 py-3 text-slate-900 placeholder:text-slate-400 focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-200 dark:border-slate-700 dark:bg-slate-800 dark:text-slate-100 dark:placeholder:text-slate-500 dark:focus:ring-blue-500;
|
|
}
|
|
|
|
.pagefind-ui__search-clear {
|
|
@apply rounded-md px-2 py-1 text-sm text-slate-500 hover:bg-slate-100 hover:text-slate-700 dark:text-slate-400 dark:hover:bg-slate-700 dark:hover:text-slate-200;
|
|
}
|
|
|
|
.pagefind-ui__results {
|
|
@apply space-y-4;
|
|
}
|
|
|
|
.pagefind-ui__result {
|
|
@apply rounded-lg border border-slate-200 bg-white p-4 transition-all hover:border-blue-300 hover:shadow-md dark:border-slate-700 dark:bg-slate-800 dark:hover:border-blue-600;
|
|
}
|
|
|
|
.pagefind-ui__result-link {
|
|
@apply text-blue-600 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300;
|
|
text-decoration: none;
|
|
}
|
|
|
|
.pagefind-ui__result-title {
|
|
@apply mb-2 text-lg font-semibold text-slate-900 dark:text-slate-100;
|
|
}
|
|
|
|
.pagefind-ui__result-excerpt {
|
|
@apply text-sm text-slate-600 dark:text-slate-300;
|
|
}
|
|
|
|
.pagefind-ui__result-excerpt mark {
|
|
@apply bg-yellow-200 font-semibold text-slate-900 dark:bg-yellow-600 dark:text-slate-100;
|
|
padding: 0.125rem 0.25rem;
|
|
border-radius: 0.25rem;
|
|
}
|
|
|
|
.pagefind-ui__message {
|
|
@apply text-center text-sm text-slate-500 dark:text-slate-400;
|
|
padding: 2rem 0;
|
|
}
|
|
|
|
.pagefind-ui__button {
|
|
@apply rounded-lg bg-blue-600 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-blue-500 dark:hover:bg-blue-600;
|
|
} |