# 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 {post.data.title} ``` #### 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'; --- Description ``` - **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 Description ``` - **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 Hero ``` - **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 `