Files
koko_blog/TODO.md
T
2026-04-30 19:44:13 +02:00

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