Compare commits

..

No commits in common. "1b64116fa307d9a03f1ccf292082e90868e557e3" and "b10690e123c917fe2097721a806951f207c21b6f" have entirely different histories.

4 changed files with 73 additions and 141 deletions

3
.gitignore vendored
View File

@ -33,6 +33,3 @@ src/utils/.env
.specstory/**
.specstory/
.cursorindexingignore
# AGENTS.md symlink
AGENTS.md

142
CLAUDE.md
View File

@ -1,113 +1,93 @@
```
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Development Commands
### Core Development
### Build the project
```bash
pnpm dev # Run development server
pnpm build # Build the project
pnpm preview # Build and preview with Wrangler
pnpm deploy # Build and deploy to Cloudflare Pages
npm run build
```
### Utilities
### Run development server
```bash
# Git commit message automation
pnpm commit # Interactive: review, accept/edit, optionally push
pnpm commit --accept # Auto-accept message, prompt for push
pnpm commit --accept --no-push # Auto-accept and commit without pushing
pnpm commit --accept --push # Fully automated: accept and push
npm run dev
```
pnpm notepad # Quick note-taking utility
### Lint the codebase
```bash
npm run lint
```
# Image conversion to AVIF format
pnpm run convert:avif:all # Convert all images
pnpm run convert:avif:jpeg # Convert JPEG only
pnpm run convert:avif:png # Convert PNG only
### Run tests
```bash
npm run test
```
### Build and preview a specific page
```bash
npm run build:page <page-name>
```
### Preview the blog section
```bash
npm run preview:blog
```
## High-Level Architecture
This is an Astro-based portfolio and blog site deployed on Cloudflare Pages. The architecture follows a content-driven approach with three distinct layers:
The website follows a clean separation of concerns with three distinct layers:
### 1. Content Layer (`src/content/**`)
Content is managed via Astro's Content Collections API with schema validation defined in `src/content.config.ts`:
1. **Content Layer** - Markdown/MDX files containing structured content located in `src/content/**`
2. **Component Layer** - Reusable UI components built with Astro, organized by purpose and functionality
3. **Layout & Structure Layer** - Page templates that orchestrate component composition across different sections
- **`blog/`** - Blog posts as MDX files
- Schema: title, description, pubDate, heroImage (optional), featured (boolean), category, tags
- Posts are sorted by pubDate (newest first)
### Content Structure
- All content is stored in Markdown/MDX format within the `src/content/**` directory
- Organized into logical groups:
- `sections/*` - About, Experience, Skills, Featured Project
- `pages/contact.mdx` - Contact form data
- `blog/*.mdx` - Blog posts with structured metadata and frontmatter
- **`sections/`** - Homepage section content (hero, experience, skills, featured-project)
- Each section has a custom schema for its specific data needs
- Experience entries include systemId, status, dates, company, role, achievements, links
- Skills entries include domain, tools, proficiency
### Component Structure
The component architecture follows a consistent pattern with different types of components:
- **`pages/`** - Page-specific content (contact form configuration)
- Includes form labels, social links, subject options
**Core Components**: Reusable elements like `BlogCard`, `FormattedDate`, and `Navigation`
### 2. Component Layer
Components are organized by purpose:
**Section Components**: Page-specific sections like `Experience`, `Skills`, and `FeaturedProject`
- **Core UI**: `BlogCard`, `FormattedDate`, `Navigation`, `Footer`, `GridOverlay`
- **Blog-specific**: `BlogFilters`, `ReadingProgress`, `TableOfContents`, `PostNavigation`, `RelatedPosts`
- **Section components**: `Hero`, `Experience`, `Skills`, `FeaturedProject`
**Layout Components**: Base templates that provide shared styling and structure (e.g., `BaseLayout`, `BlogPost`)
### 3. Page & Layout Layer
- **Layouts**: `BaseLayout` (shared structure), `BlogPost` (blog template)
- **Routes**: Static routes in `src/pages/` with dynamic blog routes via `[...slug].astro`
### Component Relationships
## Data Flow Patterns
**Blog Section Flow**: The blog page (`src/pages/blog/index.astro`) fetches all blog posts via `getCollection()` and organizes content into three distinct sections:
- Featured post (first with `featured: true`)
- Editor's picks (next 3 posts after featured)
- Latest posts (all posts for filtering)
### Blog Index (`src/pages/blog/index.astro`)
1. Fetches all posts via `getCollection('blog')`
2. Sorts by pubDate (newest first)
3. Identifies featured post (first with `featured: true` or fallback to latest)
4. Renders featured hero + filterable grid of all posts
5. Extracts unique categories for filter UI
**Content Rendering Pattern**: All components use a consistent data model where properties are passed through props. For example, `BlogCard` receives title, description, pubDate, and heroImage as parameters.
### Individual Blog Posts (`src/pages/blog/[...slug].astro`)
1. Uses `getStaticPaths()` to generate all blog post routes
2. For each post, calculates:
- Previous/next posts (by date)
- Related posts (matching category or shared tags, limited to 3)
- Reading time (based on word count, 200 wpm)
3. Passes everything to `BlogPost` layout which handles headings, navigation, related posts
### Content Collections
All content follows the schema validation pattern:
### Data Flow Architecture
```
MDX file → src/content.config.ts schema → getCollection() → Component props
Content Files → Astro Content API → Page Components → UI Components → Final Render
```
## Key Technical Patterns
- **Content Collection**: Configured in `src/content.config.ts` with schema validation for frontmatter
- **Data Fetching**: Uses Astro's content API to load and transform data from Markdown/MDX files
- **Component Composition**: Pages assemble components based on fetched data, creating dynamic and responsive layouts
### Image Handling
- Assets in `src/assets/` are processed by Astro (use relative paths in frontmatter)
- Static files in `public/media/` are served as-is (use absolute paths like `/media/file.mp4`)
- AVIF conversion utility available for optimization
### Design System Elements
- **Styling System**: Consistent use of classes like `.animate-on-scroll`, `.stagger-*`, and `.border-white/[0.1]`
- **Navigation**: Responsive mobile menu with smooth transitions
- **Accessibility**: Proper ARIA attributes, keyboard navigation support
- **Performance**: Optimized image loading and lazy rendering (using AVIF/WebP formats)
### Styling
- Tailwind CSS v4 via Vite plugin
- Custom animation classes: `.animate-on-scroll`, `.slide-up`, `.stagger-*`, `.fade-in`
- Monospace font used for technical labels and metadata
### Technical Features
- **AI Integration**: Blog post highlights AI/ML usage in technical workflow
- **Interactive Elements**: Form dropdowns, modal responses for contact form
- **Animation System**: Scroll-triggered animations with staggered effects
- **Responsive Design**: Mobile-first approach with viewport-specific classes and media queries
### Deployment
- Cloudflare Pages adapter configured in `astro.config.mjs`
- Image service set to "compile" mode
- Platform proxy enabled for development
## Blog Post Creation Workflow
1. Create `.mdx` file in `src/content/blog/` (filename becomes URL slug)
2. Add required frontmatter: title, description, pubDate
3. Optionally add: heroImage, featured, category, tags
4. Write content using Markdown/MDX with embedded JSX/HTML
5. Images can reference `src/assets/` (relative) or `public/media/` (absolute)
## Utility Scripts
- **`src/utils/convert-to-avif.js`** - Converts images to AVIF format with quality options
- **`src/utils/git-commit.js`** - Auto-generates commit messages from staged changes
- **`src/utils/notepad.js`** - Quick note-taking utility
The architecture is highly maintainable with clear separation of content from presentation. The use of Astro's data API and component system enables dynamic content generation while maintaining a consistent visual language throughout the site.
```

View File

@ -11,5 +11,3 @@ tags: ['opinions', 'satire']
**[2025-12-18 13:55:20]** This is my personal notepad. You will find nothing of interest here.
**[2025-12-18 14:32:02]** im trying to figure out what my favorite color is
**[2025-12-18 15:13:02]** ◈ INCIDENT REPORT #8291-Δ :: Personnel discovered singing in backwards Sumerian :: Object Class: K̵͉̈́E̴̹͝T̷͎̓E̸̳̿R̶̰̈́ :: The stars are RIGHT :: Recovered text fragment: 'ph'nglui mglw'nafh C̴t̷h̵u̷l̴h̵u̸ R'lyeh wgah'nagl fhtagn' :: Seventeen (17) observers report seeing ████ behind the moon :: Directive: OBSERVE. DO NOT ENGAGE. :: In his house at R'lyeh, dead ██████ waits dreaming ::

View File

@ -373,7 +373,7 @@ ${colors.bright}Usage:${colors.reset}
${colors.cyan}git add <files>${colors.reset}
2. Run this script:
${colors.cyan}pnpm commit [options]${colors.reset}
${colors.cyan}pnpm commit${colors.reset}
3. Review the AI-generated commit message
@ -388,23 +388,7 @@ ${colors.bright}Requirements:${colors.reset}
- Get your key from: ${colors.dim}https://openrouter.ai/keys${colors.reset}
${colors.bright}Options:${colors.reset}
--help, -h Show this help message
--accept, -a Auto-accept the generated commit message without prompting
--push, -p Automatically push to remote after committing
--no-push, -n Skip the push prompt (commit only, don't push)
${colors.bright}Examples:${colors.reset}
${colors.cyan}pnpm commit${colors.reset}
Interactive mode - review, accept/edit, optionally push
${colors.cyan}pnpm commit --accept${colors.reset}
Auto-accept commit message, still prompt for push
${colors.cyan}pnpm commit --accept --no-push${colors.reset}
Auto-accept and commit without pushing
${colors.cyan}pnpm commit --accept --push${colors.reset}
Fully automated - accept and push without any prompts
--help, -h Show this help message
`);
}
@ -419,11 +403,6 @@ async function main() {
process.exit(0);
}
// Check for flags
const autoAccept = args.includes('--accept') || args.includes('-a');
const autoPush = args.includes('--push') || args.includes('-p');
const noPush = args.includes('--no-push') || args.includes('-n');
// Load environment variables
loadEnv();
@ -449,49 +428,27 @@ async function main() {
// Generate commit message using OpenRouter
const generatedMessage = await generateCommitMessage(context);
let approved = autoAccept;
let message = generatedMessage;
// Get user approval
const rl = createReadlineInterface();
const { approved, message } = await getUserApproval(generatedMessage, rl);
// Get user approval if not auto-accepting
if (!autoAccept) {
const rl = createReadlineInterface();
const result = await getUserApproval(generatedMessage, rl);
approved = result.approved;
message = result.message;
if (!approved) {
console.log(`\n${colors.yellow}⏭️ Commit cancelled${colors.reset}`);
rl.close();
if (!approved) {
console.log(`\n${colors.yellow}⏭️ Commit cancelled${colors.reset}`);
process.exit(0);
}
} else {
console.log(`\n${colors.bright}${colors.green}📝 Generated commit message:${colors.reset}`);
console.log(`${colors.dim}${'─'.repeat(60)}${colors.reset}`);
console.log(message);
console.log(`${colors.dim}${'─'.repeat(60)}${colors.reset}\n`);
console.log(`${colors.cyan}Auto-accepting with --accept flag${colors.reset}`);
process.exit(0);
}
// Create the commit
const commitSuccess = createCommit(message);
if (!commitSuccess) {
rl.close();
process.exit(1);
}
// Handle push logic
let shouldPush = false;
if (noPush) {
console.log(`${colors.cyan}Skipping push with --no-push flag${colors.reset}`);
} else if (autoPush) {
console.log(`${colors.cyan}Auto-pushing with --push flag${colors.reset}`);
shouldPush = true;
} else {
const rl = createReadlineInterface();
shouldPush = await askToPush(rl);
rl.close();
}
// Ask to push
const shouldPush = await askToPush(rl);
rl.close();
if (shouldPush) {
pushToRemote();