1090 lines
35 KiB
Markdown
1090 lines
35 KiB
Markdown
# Koko Blog - TODO List
|
|
|
|
Detailed task breakdown with sub-steps for systematic implementation.
|
|
|
|
---
|
|
|
|
## Phase 0: Project Initialization (Critical)
|
|
|
|
### 0. Initialize Astro Project
|
|
**Priority:** Critical | **Status:** Pending
|
|
|
|
- [ ] Astro 5.x scaffold with `output: 'static'` and Dracula/Shiki
|
|
- [ ] TypeScript strict mode via `astro/tsconfigs/strict`
|
|
- [ ] Path aliases (`@/*`, `@components/*`, `@layouts/*`, `@styles/*`, `@utils/*`)
|
|
- [ ] `src/content/config.ts` — Zod schema for blog posts (title, slug, excerpt, publishedAt, category, tags, image, draft)
|
|
- [ ] `src/env.d.ts` with Astro client types
|
|
- [ ] `pnpm build` passes cleanly
|
|
|
|
|
|
### Phase 1: Complete Synthwave Theme Implementation (`src/styles/`)
|
|
**Priority:** Critical | **Status:** Pending
|
|
|
|
**STACK ENFORCEMENT (NON-NEGOTIABLE):**
|
|
- Framework: Pure CSS in `.css` files
|
|
- Forbidden: CSS-in-JS, styled-components, Tailwind classes in TS
|
|
- Required patterns: CSS custom properties, Astro-native styling
|
|
- Verification: Check no `import styles from...` patterns
|
|
|
|
**NON-FUNCTIONAL REQUIREMENTS:**
|
|
- Bundle size: CSS must be < 20KB total
|
|
- Performance: No render-blocking CSS (use media queries)
|
|
- Security: No user input in CSS (static only)
|
|
|
|
**Implementation Checklist:**
|
|
- [ ] Define complete color palette
|
|
- [ ] Primary purple (#6a1b9a) with all variations (main, dark, light)
|
|
- [ ] Secondary blue (#000080) with all variations
|
|
- [ ] Background colors (default black #000, paper #121212)
|
|
- [ ] Accent colors for highlights (magenta variations)
|
|
- [ ] Opacity variations for dark mode (0%, 5%, 10%, 20% opacity layers)
|
|
- **Verify:** All hex codes match PLAN.md exactly
|
|
|
|
- [ ] Implement gradient system
|
|
- [ ] Default gradient: 135deg from black through purple to black
|
|
- [ ] Gradient 1: 135deg with full opacity at 100%
|
|
- [ ] Gradient 2: Repeating gradient for shimmer effect
|
|
- [ ] Gradient transitions with CSS custom properties
|
|
- **Verify:** Gradients render correctly in dev and build
|
|
|
|
- [ ] Build shimmer effects
|
|
- [ ] Background shimmer overlay (90deg, transparent through purple to transparent)
|
|
- [ ] Floating glass effect with 45deg overlay
|
|
- [ ] Low opacity layers for depth perception
|
|
- [ ] CSS animation for subtle movement (optional)
|
|
- **Verify:** Animations respect `prefers-reduced-motion`
|
|
|
|
- [ ] Create typography scale
|
|
- [ ] Headline: 2.5rem (mobile) to 3rem (desktop), weight 800
|
|
- [ ] HeadlineMedium: 2rem to 2.5rem, weight 600
|
|
- [ ] Title: 1.8rem, weight 700
|
|
- [ ] Subtitle: 1.2rem, color #aaa
|
|
- [ ] Body: 1rem, color #eee
|
|
- [ ] Meta text: 0.9rem, color #888
|
|
|
|
- [ ] Define transition timings
|
|
- [ ] Shimmer-in easing: cubic-bezier(0.4, 0, 0.6, 1)
|
|
- [ ] Gradient-in easing: cubic-bezier(0.53, 0.55, 0.55, 1)
|
|
- [ ] Duration variants: short (0.3s), medium (0.5s), long (0.8s)
|
|
|
|
**Review Checklist for Loki:**
|
|
- [ ] Build passes: `pnpm build` exits 0
|
|
- [ ] No React imports in any file
|
|
- [ ] CSS file size < 20KB
|
|
- [ ] All colors match hex codes in PLAN.md
|
|
- [ ] Typography scale implemented correctly
|
|
|
|
---
|
|
|
|
### 2. Build Responsive Layout with Collapsible Sidebar (`Layout.astro`, `Header.astro`, `Sidebar.astro`, `Footer.astro`)
|
|
**Priority:** Critical | **Status:** Pending
|
|
|
|
**STACK ENFORCEMENT (NON-NEGOTIABLE):**
|
|
- Framework: Astro `.astro` components ONLY
|
|
- Forbidden: React, JSX, React hooks (useState, useEffect, etc.), react-router-dom
|
|
- Forbidden: Vue, Svelte, or any other framework
|
|
- Required patterns: Astro frontmatter, `<script>` tags for vanilla JS, CSS `<style>` blocks
|
|
- Verification: `grep -r "from 'react'" src/components/` must return NOTHING
|
|
|
|
**NON-FUNCTIONAL REQUIREMENTS:**
|
|
- Bundle size: Each component < 15KB, total layout < 50KB
|
|
- Performance: No render-blocking JS, minimal client-side JS
|
|
- Accessibility: ARIA labels, keyboard navigation, focus management
|
|
|
|
**Pre-Implementation (Thor must do before dispatching Odin):**
|
|
- [ ] Read existing `src/content/config.ts` to understand data types
|
|
- [ ] Read `src/styles/synthwave.css` to understand theme integration
|
|
- [ ] Verify Astro version and capabilities
|
|
|
|
**Implementation Checklist:**
|
|
|
|
#### Layout.astro (Base Structure)
|
|
- [ ] Create base `Layout.astro` structure
|
|
- [ ] Main container with full viewport height
|
|
- [ ] Flexbox column layout
|
|
- [ ] Theme gradient background integration (import from synthwave.css)
|
|
- [ ] Transition effects for all interactive elements
|
|
- [ ] Props via `const { title } = Astro.props` in frontmatter
|
|
- [ ] Import and use Header, Sidebar, Footer components
|
|
- [ ] `<slot />` for main content
|
|
- **Code pattern to follow:**
|
|
```astro
|
|
---
|
|
const { title } = Astro.props;
|
|
import Header from './Header.astro';
|
|
import Sidebar from './Sidebar.astro';
|
|
import Footer from './Footer.astro';
|
|
---
|
|
```
|
|
- **Verify:** No React imports, no JSX, no `export default`
|
|
|
|
#### Header.astro
|
|
- [ ] Implement `Header.astro` component
|
|
- [ ] Fixed position at top (z-index: 100)
|
|
- [ ] Logo/branding area (left side)
|
|
- [ ] Mobile menu toggle button (right side, visible only on mobile via CSS)
|
|
- [ ] Desktop navigation links (optional)
|
|
- [ ] Z-index layering for overlay elements
|
|
- **Verify:** Uses CSS for responsive visibility, not JS
|
|
|
|
#### Sidebar.astro
|
|
- [ ] Build `Sidebar.astro` component
|
|
- [ ] Fixed position, left side
|
|
- [ ] 300px width
|
|
- [ ] Synthwave gradient background
|
|
- [ ] Shadow: 3px 0 20px rgba(106, 27, 154, 0.3)
|
|
- [ ] Navigation list with hover effects
|
|
- [ ] Social media links section
|
|
- **Verify:** Uses CSS for positioning, not JS
|
|
|
|
#### Mobile Menu Logic
|
|
- [ ] Implement mobile menu behavior in `Layout.astro`
|
|
- [ ] Add `<script>` tag at bottom of Layout.astro for vanilla JS
|
|
- [ ] Hamburger icon rotation animation (180deg) via CSS transitions
|
|
- [ ] Sidebar slide-in from left (translateX animation via CSS)
|
|
- [ ] Animation duration: 0.5s exactly with cubic-bezier(0.4, 0, 0.6, 1)
|
|
- [ ] Click-outside detection to close on mobile (JS event listener)
|
|
- [ ] Pointer-events management when closed (CSS)
|
|
- **Code pattern:**
|
|
```html
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
const menuBtn = document.querySelector('.menu-toggle');
|
|
const sidebar = document.querySelector('.sidebar');
|
|
// Toggle classes for CSS animations
|
|
});
|
|
</script>
|
|
```
|
|
- **Verify:** No `useState`, no React hooks, vanilla JS only
|
|
|
|
#### Main Content Area
|
|
- [ ] Create Main Content area in Layout.astro
|
|
- [ ] Dynamic padding-left based on sidebar state (CSS class toggle)
|
|
- [ ] Responsive max-width (1400px)
|
|
- [ ] Padding for all viewports (2rem desktop, 1rem mobile)
|
|
- [ ] Transition on sidebar state change (CSS transition property)
|
|
|
|
#### Footer.astro
|
|
- [ ] Implement `Footer.astro` component
|
|
- [ ] Paper background color (#121212)
|
|
- [ ] Copyright text (centered)
|
|
- [ ] Footer link group (centered)
|
|
- [ ] Top border (1px solid #333)
|
|
- [ ] Hover effects on links (CSS :hover)
|
|
- **Verify:** No interactive JS needed, pure CSS
|
|
|
|
#### Responsive Behavior
|
|
- [ ] Screen size detection
|
|
- [ ] Window resize event listener in `<script>` tag
|
|
- [ ] Breakpoint detection (768px mobile threshold)
|
|
- [ ] CSS class toggling for layout state
|
|
- **Verify:** CSS media queries for layout changes, JS only for menu toggle
|
|
|
|
**Review Checklist for Loki:**
|
|
- [ ] Build passes: `pnpm build` exits 0
|
|
- [ ] Check for forbidden imports: `grep -r "from 'react'" src/components/` returns nothing
|
|
- [ ] Check for JSX: No `.tsx` files in components directory
|
|
- [ ] Verify vanilla JS: All `<script>` tags use standard DOM API, no React
|
|
- [ ] Bundle check: `du -sh dist/assets/*` shows minimal JS
|
|
- [ ] Responsive test: Resize browser, layout adapts correctly
|
|
- [ ] Mobile menu: Opens/closes smoothly in 0.5s
|
|
|
|
---
|
|
|
|
### 3. Implement Homepage Hero with Carousel (`Hero.astro`)
|
|
**Priority:** High | **Status:** Pending
|
|
|
|
**STACK ENFORCEMENT (NON-NEGOTIABLE):**
|
|
- Framework: Astro `.astro` component
|
|
- Forbidden: React hooks, Vue, Svelte, jQuery plugins
|
|
- Forbidden: External carousel libraries (Swiper, Slick, etc.)
|
|
- Required patterns: Vanilla JS in `<script>` tag, CSS animations
|
|
- Verification: Check no external carousel dependencies
|
|
|
|
**NON-FUNCTIONAL REQUIREMENTS:**
|
|
- Performance: Carousel must use CSS transforms, not layout-triggering properties
|
|
- Accessibility: Pause button for auto-rotation, keyboard navigation
|
|
- Bundle size: Hero component must be <20KB including images
|
|
|
|
**Pre-Implementation (Thor must verify):**
|
|
- [ ] Read `src/styles/synthwave.css` to extract color variables
|
|
- [ ] Confirm `public/images/sliders/` directory exists with hero images
|
|
- [ ] Check existing Image component usage in other .astro files
|
|
|
|
**Implementation Checklist:**
|
|
|
|
#### Hero.astro Structure
|
|
- [ ] Create `Hero.astro` section container
|
|
- [ ] Full viewport height (100vh desktop, 80vh mobile)
|
|
- [ ] Center content vertically and horizontally with flexbox
|
|
- [ ] Gradient background integration (use CSS variables from synthwave.css)
|
|
- [ ] Dark overlay for text contrast (rgba(0,0,0,0.5))
|
|
- **Code pattern:**
|
|
```astro
|
|
---
|
|
interface Props {
|
|
slides: Array<{
|
|
image: string;
|
|
title: string;
|
|
subtitle: string;
|
|
}>;
|
|
}
|
|
const { slides } = Astro.props;
|
|
---
|
|
```
|
|
|
|
#### Carousel Structure
|
|
- [ ] Build carousel structure in Astro template (static HTML)
|
|
- [ ] Array of slider items defined in frontmatter or passed as props
|
|
- [ ] Create 3-5 slides with sample data
|
|
- [ ] Each slide contains: image, title, subtitle, button label
|
|
- [ ] Use `<Image />` from `astro:assets` for slide images
|
|
- [ ] Absolute positioning for slides with CSS
|
|
- **Verify:** No dynamic imports, all images resolved at build time
|
|
|
|
#### Auto-Rotation Logic
|
|
- [ ] Implement auto-rotation via `<script>` tag
|
|
- [ ] 5 second interval between slides (setInterval)
|
|
- [ ] DOM-based state using data attributes (e.g., `data-slide="0"`)
|
|
- [ ] Pause on hover (mouseenter/mouseleave events)
|
|
- [ ] Reset rotation timer on manual change
|
|
- **Code pattern:**
|
|
```html
|
|
<script>
|
|
let currentSlide = 0;
|
|
const slides = document.querySelectorAll('.hero-slide');
|
|
let intervalId;
|
|
|
|
function rotateSlide() {
|
|
currentSlide = (currentSlide + 1) % slides.length;
|
|
updateSlideVisibility();
|
|
}
|
|
|
|
intervalId = setInterval(rotateSlide, 5000);
|
|
</script>
|
|
```
|
|
- **Verify:** Uses vanilla JS only, no React hooks
|
|
|
|
#### Slide Transitions
|
|
- [ ] Create slide transitions with CSS
|
|
- [ ] Opacity fade (0.8s duration)
|
|
- [ ] Transform translateX for directional movement (optional)
|
|
- [ ] Active slide: opacity 1, z-index 2, display: flex
|
|
- [ ] Inactive slides: opacity 0, z-index 1, display: none
|
|
- **Performance:** Use `transform` and `opacity` only (GPU accelerated)
|
|
|
|
#### Visual Effects
|
|
- [ ] Build title with synthwave effects
|
|
- [ ] Gradient text using primary purple (#6a1b9a)
|
|
- [ ] Webkit background clip for gradient text effect
|
|
- [ ] Responsive font sizing (mobile-first)
|
|
- [ ] Text shadow for glow effect (optional)
|
|
|
|
- [ ] Implement action buttons
|
|
- [ ] Primary button: filled purple (#6a1b9a), white text
|
|
- [ ] Secondary button: transparent, purple outline
|
|
- [ ] Hover effects: transform scale(1.02), box-shadow
|
|
- [ ] Active state: darker purple background
|
|
|
|
#### Carousel Controls
|
|
- [ ] Create carousel controls via vanilla JS
|
|
- [ ] Navigation dots at bottom-right (8px circles)
|
|
- [ ] Active dot: purple (#6a1b9a), scale(1.2)
|
|
- [ ] Inactive dots: gray (#333), transparent
|
|
- [ ] Previous/Next arrow buttons (bottom-center, 40px)
|
|
- [ ] Click handlers update current slide index
|
|
- **Verify:** All interactions work without page reload
|
|
|
|
**Review Checklist for Loki:**
|
|
- [ ] Build passes: `pnpm build` exits 0
|
|
- [ ] No external carousel libraries in package.json
|
|
- [ ] Check bundle size: `du -sh dist/assets/*` - Hero JS < 5KB
|
|
- [ ] Carousel auto-rotates every 5 seconds
|
|
- [ ] Manual controls work (dots and arrows)
|
|
- [ ] Hover pauses rotation
|
|
- [ ] All hex codes match PLAN.md (#6a1b9a for purple)
|
|
- [ ] Responsive: Looks good on mobile (<768px) and desktop (>1024px)
|
|
- [ ] Accessibility: Can navigate with keyboard (Tab, Enter, Arrow keys)
|
|
|
|
---
|
|
|
|
### 4. Create Post Card Component (`PostCard.astro`)
|
|
**Priority:** High | **Status:** Pending
|
|
|
|
**STACK ENFORCEMENT (NON-NEGOTIABLE):**
|
|
- Framework: Astro `.astro` component
|
|
- Forbidden: React hooks, state management, event handlers in JSX
|
|
- Required patterns: Astro props interface, CSS :hover effects
|
|
- Data source: Content Collections via `getCollection('blog')`
|
|
- Verification: Component receives data via props, not API calls
|
|
|
|
**NON-FUNCTIONAL REQUIREMENTS:**
|
|
- Performance: CSS-only hover effects (no JS for interactions)
|
|
- Accessibility: Proper heading hierarchy, alt text for images
|
|
- Bundle size: Component < 10KB
|
|
|
|
**Pre-Implementation (Thor must verify):**
|
|
- [ ] Read `src/content/config.ts` to understand BlogPost type
|
|
- [ ] Read `src/styles/synthwave.css` for color/variable reference
|
|
- [ ] Check sample post data exists in `src/content/blog/`
|
|
|
|
**Implementation Checklist:**
|
|
|
|
#### Component Structure
|
|
- [ ] Design `PostCard.astro` with proper typing
|
|
- [ ] Create Props interface extending Content Collection entry
|
|
- [ ] Props via `const { post } = Astro.props` with TypeScript type
|
|
- [ ] Container styling from synthwave.css
|
|
- **Code pattern:**
|
|
```astro
|
|
---
|
|
import type { CollectionEntry } from 'astro:content';
|
|
interface Props {
|
|
post: CollectionEntry<'blog'>;
|
|
}
|
|
const { post } = Astro.props;
|
|
const { title, excerpt, publishedAt, category, image } = post.data;
|
|
---
|
|
```
|
|
|
|
#### Styling
|
|
- [ ] Container styling
|
|
- [ ] Semi-transparent background (rgba(255,255,255,0.05))
|
|
- [ ] 20px border-radius
|
|
- [ ] 2rem padding (1.5rem mobile via media query)
|
|
- [ ] Position relative for overlays
|
|
- **Verify:** Uses CSS variables from synthwave.css
|
|
|
|
- [ ] Implement hover effects (CSS-only)
|
|
- [ ] Transform: translateY(-5px) on hover
|
|
- [ ] Box-shadow: 0 15px 40px rgba(106, 27, 154, 0.3) on hover
|
|
- [ ] Background lightening (increase opacity by 0.05)
|
|
- [ ] Transition: all 0.3s cubic-bezier(0.4, 0, 0.6, 1)
|
|
- **Important:** CSS `:hover` only, no JavaScript
|
|
- **Code pattern:**
|
|
```css
|
|
.post-card {
|
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.6, 1);
|
|
}
|
|
.post-card:hover {
|
|
transform: translateY(-5px);
|
|
box-shadow: 0 15px 40px rgba(106, 27, 154, 0.3);
|
|
}
|
|
```
|
|
|
|
#### Card Header
|
|
- [ ] Build card header section
|
|
- [ ] Category tag (pill-shaped, purple background #6a1b9a)
|
|
- [ ] Publication date formatted (e.g., "Mar 1, 2026")
|
|
- [ ] Tags displayed as comma-separated list
|
|
- [ ] Flex layout with justify-between
|
|
- **Verify:** Date formatting happens at build time, not client-side
|
|
|
|
#### Title Element
|
|
- [ ] Create title with styling
|
|
- [ ] Hover color change (#fff to #eee) via CSS
|
|
- [ ] Left border accent (purple #6a1b9a, 4px wide)
|
|
- [ ] Standard anchor link: `<a href={`/posts/${post.slug}`}>`
|
|
- [ ] Heading element (h2 or h3 based on context)
|
|
- **Accessibility:** Semantic heading, not just styled div
|
|
|
|
#### Content Area
|
|
- [ ] Implement excerpt display
|
|
- [ ] Truncated text (150-200 characters)
|
|
- [ ] Ellipsis for overflow (`text-overflow: ellipsis`)
|
|
- [ ] Line-height: 1.8 for readability
|
|
- [ ] Color: #eee (from synthwave.css variables)
|
|
- **Note:** Truncation at build time, not via CSS
|
|
|
|
- [ ] Add image preview
|
|
- [ ] Use `<Image />` from `astro:assets`
|
|
- [ ] Import: `import { Image } from 'astro:assets'`
|
|
- [ ] Full-width responsive image with defined width/height
|
|
- [ ] Border-radius: 12px
|
|
- [ ] Aspect ratio preservation via props
|
|
- [ ] `loading="lazy"` for performance
|
|
- **Code pattern:**
|
|
```astro
|
|
<Image
|
|
src={post.data.image}
|
|
alt={post.data.title}
|
|
width={800}
|
|
height={450}
|
|
loading="lazy"
|
|
/>
|
|
```
|
|
|
|
#### Call to Action
|
|
- [ ] Create "Read More" link
|
|
- [ ] Standard anchor tag: `<a href={`/posts/${post.slug}`}>`
|
|
- [ ] Purple background button styling (#6a1b9a)
|
|
- [ ] Hover: darker purple (#510ca9) with lift effect
|
|
- [ ] Text: "Read More →" or similar
|
|
- **Verify:** No JavaScript event handlers, standard link behavior
|
|
|
|
**Review Checklist for Loki:**
|
|
- [ ] Build passes: `pnpm build` exits 0
|
|
- [ ] Component file ends with `.astro`, not `.tsx`
|
|
- [ ] No React imports or hooks
|
|
- [ ] Uses `astro:assets` Image component
|
|
- [ ] Props properly typed with CollectionEntry
|
|
- [ ] All hex codes match (#6a1b9a for purple)
|
|
- [ ] Hover effects work with CSS only (check in browser)
|
|
- [ ] Responsive: Card stacks properly on mobile
|
|
- [ ] Accessibility: Images have alt text, semantic headings
|
|
|
|
---
|
|
|
|
### 5. Set Up Astro Content Collections (`src/content/`)
|
|
**Priority:** Critical | **Status:** Pending
|
|
|
|
**STACK ENFORCEMENT (NON-NEGOTIABLE):**
|
|
- Framework: Astro Content Collections API
|
|
- Forbidden: marked.js, highlight.js, manual Markdown parsing
|
|
- Forbidden: Runtime Markdown rendering (everything at build time)
|
|
- Required patterns: Zod validation, `getCollection()`, `getStaticPaths()`
|
|
- Verification: Check no manual markdown libraries in package.json
|
|
|
|
**NON-FUNCTIONAL REQUIREMENTS:**
|
|
- Performance: All Markdown rendered at build time (zero runtime cost)
|
|
- Type Safety: Full TypeScript inference from Zod schema
|
|
- Bundle size: Content collections add ~0KB to client bundle (server-side only)
|
|
|
|
**Pre-Implementation (Thor must verify):**
|
|
- [ ] Confirm `src/content/config.ts` already exists from Phase 0
|
|
- [ ] Verify Zod schema matches PLAN.md requirements
|
|
- [ ] Check sample posts exist in `src/content/blog/`
|
|
|
|
**Implementation Checklist:**
|
|
|
|
#### Schema Verification
|
|
- [ ] Verify `src/content/config.ts` exists with correct Zod schema:
|
|
```ts
|
|
import { defineCollection, z } from 'astro:content';
|
|
|
|
const blog = defineCollection({
|
|
type: 'content',
|
|
schema: z.object({
|
|
title: z.string(),
|
|
slug: z.string(),
|
|
excerpt: z.string(),
|
|
publishedAt: z.string(),
|
|
category: z.string(),
|
|
tags: z.array(z.string()).default([]),
|
|
image: z.string().optional(),
|
|
draft: z.boolean().default(false),
|
|
}),
|
|
});
|
|
|
|
export const collections = { blog };
|
|
```
|
|
- **Verify:** Schema matches PLAN.md exactly
|
|
- **Verify:** TypeScript types are generated (check `.astro/types.d.ts`)
|
|
|
|
#### Post Files
|
|
- [ ] Organize post files correctly
|
|
- [ ] Location: `src/content/blog/*.md` (one file per post)
|
|
- [ ] Filename format: `[slug].md` (e.g., `hello-world.md`)
|
|
- [ ] Frontmatter structure matches schema
|
|
- **Sample post structure:**
|
|
```markdown
|
|
---
|
|
title: "Hello World"
|
|
slug: "hello-world"
|
|
excerpt: "First post on the blog"
|
|
publishedAt: "2026-03-01"
|
|
category: "General"
|
|
tags: ["intro", "welcome"]
|
|
image: "/images/posts/hello.jpg"
|
|
draft: false
|
|
---
|
|
|
|
# Hello World
|
|
|
|
This is the content...
|
|
```
|
|
|
|
#### Post Page Implementation (`[slug].astro`)
|
|
- [ ] Create `src/pages/posts/[slug].astro` with static path generation:
|
|
```astro
|
|
---
|
|
import { getCollection } from 'astro:content';
|
|
import Layout from '../../components/Layout.astro';
|
|
|
|
export async function getStaticPaths() {
|
|
const posts = await getCollection('blog');
|
|
return posts.map(entry => ({
|
|
params: { slug: entry.slug },
|
|
props: { entry },
|
|
}));
|
|
}
|
|
|
|
const { entry } = Astro.props;
|
|
const { Content, headings } = await entry.render();
|
|
---
|
|
```
|
|
- **Critical:** Must use `getStaticPaths()` for static generation
|
|
- **Verify:** `getCollection('blog')` returns typed entries
|
|
- **Verify:** `entry.slug` matches filename
|
|
|
|
- [ ] Render post content:
|
|
- [ ] Use `<Content />` component for body
|
|
- [ ] No HTML sanitization needed (Astro handles this)
|
|
- [ ] Access frontmatter via `entry.data`
|
|
- **Code pattern:**
|
|
```astro
|
|
<Layout title={entry.data.title}>
|
|
<article>
|
|
<h1>{entry.data.title}</h1>
|
|
<time>{entry.data.publishedAt}</time>
|
|
<Content />
|
|
</article>
|
|
</Layout>
|
|
```
|
|
|
|
#### Syntax Highlighting
|
|
- [ ] Verify Shiki configuration in `astro.config.mjs`:
|
|
```js
|
|
markdown: {
|
|
shikiConfig: {
|
|
theme: 'dracula',
|
|
},
|
|
}
|
|
```
|
|
- **Note:** Shiki runs at build time, no client JS needed
|
|
- **Theme:** Dracula (already configured in Phase 0)
|
|
|
|
- [ ] Style code blocks via CSS:
|
|
- [ ] Target Shiki output classes (`.shiki`, `.line`, etc.)
|
|
- [ ] Dark background: #1a1a2e
|
|
- [ ] Light text: #f8f8f2
|
|
- [ ] Padding: 1.5rem
|
|
- [ ] Border-radius: 10px
|
|
- [ ] Overflow-x: auto (horizontal scroll for long lines)
|
|
- **Verify:** Check built HTML has syntax highlighting
|
|
|
|
- [ ] Style inline code:
|
|
- [ ] Background: #2d313e
|
|
- [ ] Monospace font family
|
|
- [ ] Padding: 0.2em 0.4em
|
|
- [ ] Border-radius: 4px
|
|
|
|
- [ ] Remove forbidden libraries:
|
|
- [ ] Check package.json for `marked`, `highlight.js`
|
|
- [ ] If present, run: `pnpm remove marked highlight.js`
|
|
- **Verify:** `grep -r "marked\|highlight.js" src/` returns nothing
|
|
|
|
**Review Checklist for Loki:**
|
|
- [ ] Build passes: `pnpm build` exits 0
|
|
- [ ] TypeScript check passes: `pnpm astro check` exits 0
|
|
- [ ] No marked.js or highlight.js in dependencies
|
|
- [ ] Create test post, verify it renders at `/posts/[slug]`
|
|
- [ ] Code blocks have syntax highlighting (check built HTML)
|
|
- [ ] All post frontmatter is accessible
|
|
- [ ] Draft posts are excluded from collection (check Zod schema)
|
|
|
|
---
|
|
|
|
### 6. Image Handling with Astro + Sharp
|
|
**Priority:** High | **Status:** Pending
|
|
|
|
**STACK ENFORCEMENT (NON-NEGOTIABLE):**
|
|
- Framework: Astro Assets (`astro:assets`) with Sharp
|
|
- Forbidden: JSDOM runtime image processing, manual image manipulation
|
|
- Forbidden: External image CDNs (Cloudinary, Imgix, etc.)
|
|
- Required patterns: `<Image />` component, build-time optimization
|
|
- Verification: All images processed at build time
|
|
|
|
**NON-FUNCTIONAL REQUIREMENTS:**
|
|
- Performance: Images converted to WebP at build time
|
|
- Bundle size: Zero runtime image processing code
|
|
- Accessibility: All images have alt text
|
|
- Format: WebP with JPEG/PNG fallback for older browsers
|
|
|
|
**Pre-Implementation (Thor must verify):**
|
|
- [ ] Check `astro.config.mjs` has Sharp service configured
|
|
- [ ] Verify `sharp` is in dependencies (not devDependencies)
|
|
- [ ] Confirm sample images exist in `public/images/posts/`
|
|
|
|
**Implementation Checklist:**
|
|
|
|
#### Astro Configuration
|
|
- [ ] Verify `astro.config.mjs` has image service:
|
|
```js
|
|
import { defineConfig } from 'astro/config';
|
|
|
|
export default defineConfig({
|
|
output: 'static',
|
|
image: {
|
|
service: {
|
|
entrypoint: 'astro/assets/services/sharp',
|
|
},
|
|
},
|
|
});
|
|
```
|
|
- **Verify:** Sharp service is configured
|
|
- **Verify:** No external image services
|
|
|
|
#### Pre-Build Optimization (Optional)
|
|
- [ ] Create `scripts/optimize-images.js` for pre-processing (if needed):
|
|
- [ ] Read originals from `public/images/posts/`
|
|
- [ ] Resize to max 800px wide, maintain aspect ratio
|
|
- [ ] Output WebP to `public/images/previews/`
|
|
- [ ] Add to `package.json` scripts: `"prebuild": "node scripts/optimize-images.js"`
|
|
- **Note:** Astro's `<Image />` component handles most optimization automatically
|
|
- **Only add if:** You need specific custom optimizations
|
|
|
|
#### Image Component Usage
|
|
- [ ] In `.astro` components, use `<Image />` from `astro:assets`:
|
|
```astro
|
|
---
|
|
import { Image } from 'astro:assets';
|
|
import myImage from '../assets/my-image.jpg';
|
|
---
|
|
|
|
<Image
|
|
src={myImage}
|
|
alt="Description"
|
|
width={800}
|
|
height={450}
|
|
loading="lazy"
|
|
/>
|
|
```
|
|
- **Critical:** Must import image to get processed
|
|
- **Critical:** Must specify width and height (prevents CLS)
|
|
- **Verify:** Image component auto-generates WebP
|
|
- **Verify:** `loading="lazy"` handled automatically
|
|
|
|
- [ ] For public directory images:
|
|
```astro
|
|
<Image
|
|
src="/images/posts/my-image.jpg"
|
|
alt="Description"
|
|
width={800}
|
|
height={450}
|
|
inferSize
|
|
/>
|
|
```
|
|
- **Note:** Use `inferSize` for public images to auto-detect dimensions
|
|
|
|
#### Critical Images (Hero, Above Fold)
|
|
- [ ] Use `loading="eager"` for hero images (above the fold):
|
|
```astro
|
|
<Image
|
|
src={heroImage}
|
|
alt="Hero"
|
|
width={1920}
|
|
height={1080}
|
|
loading="eager"
|
|
priority
|
|
/>
|
|
```
|
|
- **Verify:** These images preload, not lazy loaded
|
|
- **Performance:** Improves LCP (Largest Contentful Paint)
|
|
|
|
#### Remove Forbidden Patterns
|
|
- [ ] Delete any JSDOM runtime image processing
|
|
- [ ] Remove manual image src updates in `<script>` tags
|
|
- [ ] No `new Image()` JavaScript objects for layout
|
|
- **Verify:** All image handling is declarative in `.astro` templates
|
|
|
|
**Review Checklist for Loki:**
|
|
- [ ] Build passes: `pnpm build` exits 0
|
|
- [ ] Check `dist/` directory: Images are WebP format
|
|
- [ ] Check HTML: Images have width/height attributes (no CLS)
|
|
- [ ] All images have alt text (accessibility)
|
|
- [ ] Hero images use `loading="eager"`
|
|
- [ ] No JSDOM or runtime image processing code
|
|
- [ ] Bundle size check: Image optimization adds minimal JS
|
|
- [ ] Output WebP to `public/images/previews/`
|
|
- [ ] Run before build: add to `prebuild` script in `package.json`
|
|
|
|
- [ ] In `.astro` components: use `<Image />` from `astro:assets`
|
|
- [ ] `<Image src={...} alt={...} width={800} height={450} />`
|
|
- [ ] `<Image />` auto-generates WebP, adds width/height (prevents CLS)
|
|
- [ ] `loading="lazy"` is handled automatically by `<Image />`
|
|
- [ ] No JSDOM needed — image src is updated at build time, not runtime
|
|
|
|
- [ ] Critical images (hero slides): use `loading="eager"` to preload
|
|
|
|
- [ ] Remove: JSDOM runtime image processing (replaced by Astro's build-time handling)
|
|
|
|
---
|
|
|
|
## Phase 2: Content & Navigation (Critical)
|
|
|
|
### 7. File-Based Routing (`src/pages/`)
|
|
**Priority:** Critical | **Status:** Pending
|
|
|
|
- [ ] Create `src/pages/index.astro` → routes to `/`
|
|
- [ ] Import and use `Layout.astro`, `Hero.astro`, `PostGrid.astro`
|
|
- [ ] Fetch posts at build time: `const posts = await getCollection('blog')`
|
|
|
|
- [ ] Create `src/pages/posts/[slug].astro` → routes to `/posts/[slug]`
|
|
- [ ] Export `getStaticPaths()` to generate all post pages at build time
|
|
- [ ] `Astro.params.slug` for post lookup
|
|
|
|
- [ ] Create `src/pages/404.astro` → routes to `/404`
|
|
- [ ] Synthwave-styled error page
|
|
- [ ] "Return to Homepage" link (`<a href="/">`)
|
|
|
|
- [ ] Navigation between pages: standard `<a href="/posts/my-post">` links
|
|
- [ ] No `useNavigate`, no `BrowserRouter`, no route library
|
|
|
|
- [ ] Remove: react-router-dom from dependencies entirely
|
|
|
|
---
|
|
|
|
### 8. Create Full Post Page (`src/pages/posts/[slug].astro`)
|
|
**Priority:** High | **Status:** Pending
|
|
|
|
- [ ] Export `getStaticPaths()`:
|
|
```ts
|
|
export async function getStaticPaths() {
|
|
const posts = await getCollection('blog');
|
|
return posts.map(entry => ({ params: { slug: entry.slug }, props: { entry } }));
|
|
}
|
|
```
|
|
- [ ] Destructure `const { entry } = Astro.props`
|
|
- [ ] Call `const { Content, headings } = await entry.render()`
|
|
|
|
- [ ] Build post page layout
|
|
- [ ] Reuse `<Layout>` component
|
|
- [ ] Full-width content area, max-width 800px for readability
|
|
|
|
- [ ] Implement post header
|
|
- [ ] Title (larger font, centered)
|
|
- [ ] Metadata bar (date, category, tags)
|
|
- [ ] Featured image via `<Image />`
|
|
|
|
- [ ] Render post body
|
|
- [ ] Use `<Content />` component — Markdown is pre-rendered, no manual HTML sanitization
|
|
- [ ] Syntax highlighting automatic via Shiki
|
|
- [ ] Style headings, tables, blockquotes via CSS in `<style>` block
|
|
|
|
- [ ] Build navigation footer
|
|
- [ ] Previous/Next post links (standard `<a>` tags)
|
|
- [ ] Back to home `<a href="/">`
|
|
|
|
- [ ] Optional: build TOC from `headings` array returned by `entry.render()`
|
|
|
|
---
|
|
|
|
### 9. Build 404 Error Page
|
|
**Priority:** Medium | **Status:** Pending
|
|
|
|
- [ ] Create error page layout
|
|
- [ ] Full viewport height
|
|
- [ ] Centered content
|
|
- [ ] Synthwave gradient background
|
|
|
|
- [ ] Design error display
|
|
- [ ] Large "404" text (8rem desktop, 4rem mobile)
|
|
- [ ] Gradient text effect
|
|
- [ ] Rotation animation (10deg, subtle pulse)
|
|
- [ ] Error message text
|
|
- [ ] Path display (if available)
|
|
|
|
- [ ] Implement action buttons
|
|
- [ ] "Return to Homepage" primary button
|
|
- [ ] "Contact Us" secondary button (outline)
|
|
- [ ] Navigation handlers
|
|
|
|
- [ ] Add decorative elements
|
|
- [ ] Synthwave card styling
|
|
- [ ] Hover effects
|
|
- [ ] Subtle animations
|
|
|
|
---
|
|
|
|
## Phase 3: Content Management (Medium)
|
|
|
|
### 10. Build Post Grid with Category Filtering
|
|
**Priority:** Medium | **Status:** Pending
|
|
|
|
- [ ] Create grid layout system
|
|
- [ ] CSS Grid or Flexbox
|
|
- [ ] Responsive columns (1 mobile, 2 tablet, 3 desktop)
|
|
- [ ] Gap spacing (1.5rem)
|
|
- [ ] Auto-rows for variable height cards
|
|
|
|
- [ ] Implement category filtering
|
|
- [ ] Filter state management
|
|
- [ ] Category buttons/tags
|
|
- [ ] Active filter highlighting
|
|
- [ ] Clear filter option
|
|
|
|
- [ ] Add sorting options
|
|
- [ ] Date (newest first, default)
|
|
- [ ] Title (alphabetical)
|
|
- [ ] Category
|
|
|
|
- [ ] Build pagination (optional)
|
|
- [ ] Page size (9-12 posts)
|
|
- [ ] Page navigation
|
|
- [ ] "Load more" button alternative
|
|
|
|
---
|
|
|
|
### 11. Implement Category Carousel
|
|
**Priority:** Medium | **Status:** Pending
|
|
|
|
- [ ] Create horizontal scroll container
|
|
- [ ] Overflow-x: auto
|
|
- [ ] Hide scrollbar (custom styling)
|
|
- [ ] Snap scrolling (optional)
|
|
|
|
- [ ] Design category cards
|
|
- [ ] Icon or image for each category
|
|
- [ ] Category name
|
|
- [ ] Post count badge
|
|
- [ ] Hover lift effect
|
|
|
|
- [ ] Add navigation arrows
|
|
- [ ] Left/right scroll buttons
|
|
- [ ] Show/hide based on scroll position
|
|
- [ ] Smooth scroll behavior
|
|
|
|
---
|
|
|
|
### 12. Integrate Subscription Form
|
|
**Priority:** Low | **Status:** Pending
|
|
|
|
- [ ] Create form component
|
|
- [ ] Email input field
|
|
- [ ] Submit button
|
|
- [ ] Validation
|
|
|
|
- [ ] Style with synthwave theme
|
|
- [ ] Purple accent on focus
|
|
- [ ] Rounded inputs
|
|
- [ ] Hover effects
|
|
|
|
- [ ] Handle form submission
|
|
- [ ] Client-side validation
|
|
- [ ] Success message
|
|
- [ ] Error handling
|
|
- [ ] Integration with email service (optional)
|
|
|
|
---
|
|
|
|
## Phase 4: Build & Deployment (Critical)
|
|
|
|
### 13. Configure Astro Build (`astro.config.mjs`)
|
|
**Priority:** Critical | **Status:** Pending
|
|
|
|
- [ ] Set `output: 'static'` for pure static HTML output
|
|
- [ ] Set `compressHTML: true` for HTML minification
|
|
- [ ] Configure Shiki theme:
|
|
```js
|
|
markdown: { shikiConfig: { theme: 'dracula' } }
|
|
```
|
|
- [ ] Set image service to sharp:
|
|
```js
|
|
image: { service: { entrypoint: 'astro/assets/services/sharp' } }
|
|
```
|
|
- [ ] Set `site:` URL for canonical link generation
|
|
- [ ] Verify build: `pnpm build` → `dist/` contains static HTML files
|
|
- [ ] Check `dist/` sizes: `du -sh dist/assets/*`
|
|
- [ ] Total JS should be < 50KB (most pages: 0KB if no `client:*` directives used)
|
|
- [ ] Remove: separate `vite.config.ts` (not needed — Astro wraps Vite internally)
|
|
|
|
---
|
|
|
|
### 14. Configure TypeScript Strict Mode
|
|
**Priority:** High | **Status:** Pending
|
|
|
|
- [ ] Update `tsconfig.json` with strict settings:
|
|
- [ ] `strict: true`
|
|
- [ ] `noImplicitAny: true`
|
|
- [ ] `strictNullChecks: true`
|
|
- [ ] `noUnusedLocals: true`
|
|
- [ ] `noUnusedParameters: true`
|
|
- [ ] `noImplicitReturns: true`
|
|
- [ ] `noFallthroughCasesInSwitch: true`
|
|
|
|
- [ ] Set up path aliases in `tsconfig.json` (Astro reads this natively):
|
|
- [ ] `@/*` → `./src/*`
|
|
- [ ] `@components/*` → `./src/components/*`
|
|
- [ ] `@utils/*` → `./src/utils/*`
|
|
- [ ] `@styles/*` → `./src/styles/*`
|
|
- [ ] `@pages/*` → `./src/pages/*`
|
|
|
|
- [ ] Create `src/env.d.ts`:
|
|
- [ ] `/// <reference types="astro/client" />`
|
|
|
|
- [ ] Remove: `vite-env.d.ts` (replaced by `env.d.ts`)
|
|
- [ ] Remove: separate Vite resolve aliases (tsconfig paths work natively in Astro)
|
|
- [ ] Run type checking: `pnpm astro check` instead of `tsc --noEmit`
|
|
|
|
---
|
|
|
|
### 15. Write Documentation
|
|
**Priority:** Medium | **Status:** Pending
|
|
|
|
- [ ] Create README.md
|
|
- [ ] Project description
|
|
- [ ] Installation instructions
|
|
- [ ] Development workflow
|
|
- [ ] Build commands
|
|
- [ ] Deployment guide
|
|
|
|
- [ ] Write CHANGELOG.md
|
|
- [ ] Version numbering (SemVer)
|
|
- [ ] Feature list per version
|
|
- [ ] Breaking changes documentation
|
|
|
|
- [ ] Create CONTRIBUTING.md (optional)
|
|
- [ ] Code style guidelines
|
|
- [ ] PR process
|
|
- [ ] Issue reporting
|
|
|
|
---
|
|
|
|
## Phase 5: Testing & Polish (High)
|
|
|
|
### 16. Performance Testing and Optimization
|
|
**Priority:** High | **Status:** Pending
|
|
|
|
- [ ] Build and preview: `pnpm build && pnpm preview`
|
|
- [ ] Inspect `dist/`: confirm pre-rendered HTML files (not a JS bundle)
|
|
- [ ] Check bundle size: `du -sh dist/assets/*`
|
|
- [ ] Total JS < 50KB (pages with no `client:*` directives: 0KB JS)
|
|
- [ ] Add a test post `src/content/blog/test.md`, confirm it appears at `/posts/test`
|
|
- [ ] Syntax highlighting test: add a fenced code block, confirm colors render in build
|
|
|
|
- [ ] Run Lighthouse audit against `pnpm preview` server
|
|
- [ ] Performance score >90
|
|
- [ ] Accessibility score >90
|
|
- [ ] Best Practices >90
|
|
- [ ] SEO score >90
|
|
|
|
- [ ] Measure load times
|
|
- [ ] First Contentful Paint < 50ms
|
|
- [ ] Largest Contentful Paint < 1s
|
|
- [ ] Cumulative Layout Shift < 0.1
|
|
|
|
- [ ] Optimize critical resources
|
|
- [ ] Inline critical CSS
|
|
- [ ] Use `loading="eager"` on hero images (already preloaded)
|
|
- [ ] Font loading optimization
|
|
- [ ] Verify no `client:*` directives on non-interactive components
|
|
|
|
---
|
|
|
|
### 17. Cross-Browser Testing
|
|
**Priority:** Medium | **Status:** Pending
|
|
|
|
- [ ] Test on Chrome
|
|
- [ ] Latest version
|
|
- [ ] Performance metrics
|
|
- [ ] Feature parity
|
|
|
|
- [ ] Test on Firefox
|
|
- [ ] Latest version
|
|
- [ ] CSS compatibility
|
|
- [ ] Animation performance
|
|
|
|
- [ ] Test on Safari
|
|
- [ ] macOS version
|
|
- [ ] iOS version (mobile)
|
|
- [ ] Webkit-specific issues
|
|
|
|
- [ ] Test on Edge
|
|
- [ ] Chromium version
|
|
- [ ] Windows compatibility
|
|
|
|
---
|
|
|
|
### 18. Accessibility Audit (WCAG 2.1 AA)
|
|
**Priority:** Medium | **Status:** Pending
|
|
|
|
- [ ] Keyboard navigation
|
|
- [ ] Tab order logical
|
|
- [ ] Focus indicators visible
|
|
- [ ] Skip links provided
|
|
|
|
- [ ] Screen reader testing
|
|
- [ ] Alt text for images
|
|
- [ ] ARIA labels where needed
|
|
- [ ] Heading structure (h1-h6)
|
|
- [ ] Landmark regions
|
|
|
|
- [ ] Color contrast
|
|
- [ ] Text contrast ratio ≥4.5:1
|
|
- [ ] Large text contrast ≥3:1
|
|
- [ ] UI component contrast
|
|
|
|
- [ ] Motion and animation
|
|
- [ ] Respect prefers-reduced-motion
|
|
- [ ] No auto-playing content without pause
|
|
- [ ] Animation duration reasonable
|
|
|
|
---
|
|
|
|
## Phase 6: Future Enhancements (Low Priority)
|
|
|
|
### 19. Optional: Docker Setup
|
|
**Priority:** Low | **Status:** Pending
|
|
|
|
- [ ] Create Dockerfile
|
|
- [ ] Multi-stage build
|
|
- [ ] Node.js base image
|
|
- [ ] pnpm installation
|
|
- [ ] Build stage
|
|
- [ ] Nginx production stage
|
|
|
|
- [ ] Write docker-compose.yml
|
|
- [ ] Volume mounting
|
|
- [ ] Port mapping (80:80)
|
|
- [ ] Environment variables
|
|
|
|
- [ ] Create deployment scripts
|
|
- [ ] Build script
|
|
- [ ] Deploy script
|
|
- [ ] Environment setup
|
|
|
|
---
|
|
|
|
## Quick Reference
|
|
|
|
### Color Palette
|
|
|
|
| Use Case | Hex Code | Notes |
|
|
|----------|----------|-------|
|
|
| Primary | #6a1b9a | Main purple |
|
|
| Primary Dark | #510ca9 | Hover states |
|
|
| Primary Light | #935edb | Highlights |
|
|
| Secondary | #000080 | Indie blue |
|
|
| Background | #000 | True black |
|
|
| Paper | #121212 | Slightly lighter |
|
|
| Text Primary | #fff | Headings |
|
|
| Text Secondary | #eee | Body text |
|
|
| Text Muted | #888 | Meta text |
|
|
| Text Tertiary | #aaa | Subtitles |
|
|
|
|
### Breakpoints
|
|
|
|
| Name | Width | Target |
|
|
|------|-------|--------|
|
|
| Mobile | < 768px | Phones |
|
|
| Tablet | 768px - 1024px | Tablets |
|
|
| Desktop | > 1024px | Laptops/Desktops |
|
|
|
|
### Performance Budget
|
|
|
|
| Metric | Target |
|
|
|--------|--------|
|
|
| Load Time | < 100ms |
|
|
| Bundle Size | < 200KB gzipped |
|
|
| FCP | < 50ms |
|
|
| LCP | < 1s |
|
|
| TTI | < 2s |
|
|
| CLS | < 0.1 |
|
|
|
|
---
|
|
|
|
**Last Updated:** 2026-02-28
|
|
|
|
**Next Review:** After Phase 1 completion
|