# 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, `
```
- **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 `
```
- **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: ``
- [ ] 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 `` 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
```
#### Call to Action
- [ ] Create "Read More" link
- [ ] Standard anchor tag: ``
- [ ] 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 `` component for body
- [ ] No HTML sanitization needed (Astro handles this)
- [ ] Access frontmatter via `entry.data`
- **Code pattern:**
```astro
{entry.data.title}
```
#### 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: `` 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 `` component handles most optimization automatically
- **Only add if:** You need specific custom optimizations
#### Image Component Usage
- [ ] In `.astro` components, use `` from `astro:assets`:
```astro
---
import { Image } from 'astro:assets';
import myImage from '../assets/my-image.jpg';
---
```
- **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
```
- **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
```
- **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 `