From c1f676c7334075d9d1285249db1deabb0315eb3a Mon Sep 17 00:00:00 2001 From: Nicholai Date: Sat, 27 Dec 2025 04:53:44 -0700 Subject: [PATCH] refactor: clean up template structure and update documentation - Delete legacy component files (BlogFilters, PostNavigation, ReadingProgress, TableOfContents, RelatedPosts, Experience, FeaturedProject, Hero, Skills) - Remove obsolete init-template.js and template.config.json - Update CLAUDE.md and README.md with new command list - Delete SETUP.md and unused content files - Refactor content config and blog index logic - Adjust layout and page files for new structure --- CLAUDE.md | 155 ++-- README.md | 301 ++----- SETUP.md | 503 ----------- dev/continuity.md | 77 +- init-template.js | 260 ------ src/components/BlogFilters.astro | 179 ---- src/components/PostNavigation.astro | 106 --- src/components/ReadingProgress.astro | 71 -- src/components/RelatedPosts.astro | 47 -- src/components/TableOfContents.astro | 121 --- src/components/sections/Experience.astro | 150 ---- src/components/sections/FeaturedProject.astro | 185 ---- src/components/sections/Hero.astro | 301 ------- src/components/sections/Skills.astro | 106 --- src/content.config.ts | 92 +- src/content/blog/sample-case-study.mdx | 104 --- .../blog/welcome-to-your-portfolio.mdx | 90 +- src/content/blog/writing-your-first-post.mdx | 195 ----- src/content/pages/contact.mdx | 32 - src/content/sections/experience.mdx | 35 - src/content/sections/featured-project.mdx | 20 - src/content/sections/hero.mdx | 8 - src/content/sections/skills.mdx | 22 - src/layouts/BlogPost.astro | 300 ++----- src/pages/blog/index.astro | 227 +---- src/pages/contact.astro | 799 ++---------------- src/pages/index.astro | 86 +- template.config.json | 65 -- 28 files changed, 340 insertions(+), 4297 deletions(-) delete mode 100644 SETUP.md delete mode 100644 init-template.js delete mode 100644 src/components/BlogFilters.astro delete mode 100644 src/components/PostNavigation.astro delete mode 100644 src/components/ReadingProgress.astro delete mode 100644 src/components/RelatedPosts.astro delete mode 100644 src/components/TableOfContents.astro delete mode 100644 src/components/sections/Experience.astro delete mode 100644 src/components/sections/FeaturedProject.astro delete mode 100644 src/components/sections/Hero.astro delete mode 100644 src/components/sections/Skills.astro delete mode 100644 src/content/blog/sample-case-study.mdx delete mode 100644 src/content/blog/writing-your-first-post.mdx delete mode 100644 src/content/pages/contact.mdx delete mode 100644 src/content/sections/experience.mdx delete mode 100644 src/content/sections/featured-project.mdx delete mode 100644 src/content/sections/hero.mdx delete mode 100644 src/content/sections/skills.mdx delete mode 100644 template.config.json diff --git a/CLAUDE.md b/CLAUDE.md index 2ad47c2..9529244 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,130 +1,81 @@ # CLAUDE.md -This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. +Development guidance for this Astro template repository. -## Development Commands +## Commands ### Core Development ```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 +pnpm dev # Development server +pnpm build # Build for production +pnpm preview # Preview build with Wrangler +pnpm deploy # Deploy to Cloudflare Pages ``` ### Utilities ```bash -# Git commit message automation -pnpm commit # Interactive: review, accept/edit, optionally push - -# 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 - -# Cloudflare types generation -pnpm cf-typegen # Generate Cloudflare types for TypeScript - -# Template initialization -node init-template.js # Interactive setup wizard -node init-template.js --config # Use template.config.json +pnpm commit # AI-powered commit messages +pnpm convert:avif:all # Convert all images to AVIF +pnpm convert:avif:jpeg +pnpm convert:avif:png +pnpm cf-typegen # Generate Cloudflare types ``` -## Repository Change Documentation +## Change Documentation -**MANDATORY**: Whenever making any change to the repository (implementations, bug fixes, refactoring, etc.), you MUST update `dev/continuity.md` with: -- Details of the changes made -- Next steps or follow-up actions required -- Any context or decisions that should be preserved for future work +**IMPORTANT**: Update `dev/continuity.md` when making changes to document: +- What changed and why +- Decisions made +- Next steps -This ensures continuity of work and helps maintain context across development sessions. +## Architecture -## High-Level Architecture +Minimal Astro template with content-driven architecture: -This is an Astro-based template built for portfolio and blog sites deployed on Cloudflare Pages. The architecture follows a content-driven approach with three distinct layers: +### Content Layer (`src/content/`) +- **blog/** - MDX blog posts with schema validation +- **sections/** - Homepage sections (hero, experience, skills, featured-project) +- **pages/** - Page-specific content (contact) -### 1. Content Layer (`src/content/**`) -Content is managed via Astro's Content Collections API with schema validation defined in `src/content.config.ts`: +Schema defined in `src/content.config.ts` -- **`blog/`** - Blog posts as MDX files - - Schema: title, description, pubDate, updatedDate (optional), heroImage (optional), featured (boolean), category, tags - - Posts are sorted by pubDate (newest first) +### Component Layer +- **Core UI**: BlogCard, FormattedDate, Navigation, Footer, GridOverlay +- **Blog**: BlogFilters, ReadingProgress, TableOfContents, PostNavigation, RelatedPosts +- **Sections**: Hero, Experience, Skills, FeaturedProject -- **`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, tags, description, achievements, link - - Skills entries include id, domain, tools, proficiency +### Routes +- Static routes in `src/pages/` +- Dynamic blog routes via `[...slug].astro` +- Layouts in `src/layouts/` -- **`pages/`** - Page-specific content (contact form configuration) - - Includes form labels, social links, subject options +## Data Flow -### 2. Component Layer -Components are organized by purpose: +**Blog Index** (`src/pages/blog/index.astro`): +1. Fetch all posts via `getCollection('blog')` +2. Sort by pubDate (newest first) +3. Identify featured post (first with `featured: true`) +4. Render featured hero + filterable grid +5. Extract categories for filter UI -- **Core UI**: `BlogCard`, `FormattedDate`, `Navigation`, `Footer`, `GridOverlay` -- **Blog-specific**: `BlogFilters`, `ReadingProgress`, `TableOfContents`, `PostNavigation`, `RelatedPosts` -- **Section components**: `Hero`, `Experience`, `Skills`, `FeaturedProject` +**Individual Posts** (`src/pages/blog/[...slug].astro`): +1. `getStaticPaths()` generates routes +2. Calculate previous/next posts (by date) +3. Find related posts (matching category/tags, limit 3) +4. Calculate reading time (200 wpm) +5. Pass to `BlogPost` layout -### 3. Page & Layout Layer -- **Layouts**: `BaseLayout` (shared structure), `BlogPost` (blog template) -- **Routes**: Static routes in `src/pages/` with dynamic blog routes via `[...slug].astro` +## Image Handling +- `src/assets/` - Processed by Astro (relative paths) +- `public/media/` - Served as-is (absolute paths like `/media/file.mp4`) +- AVIF conversion utility available -## Data Flow Patterns - -### 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 - -### 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: -``` -MDX file → src/content.config.ts schema → getCollection() → Component props -``` - -## Key Technical Patterns - -### 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 - -### Deployment -- Cloudflare Pages adapter configured in `astro.config.mjs` -- Image service set to "compile" mode +## Deployment +- Cloudflare Pages adapter configured +- Image service: "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 -- **`init-template.js`** - Template initialization wizard (interactive and config modes) -- **`src/utils/convert-to-avif.js`** - Converts images to AVIF format with quality options -- **`src/utils/git-commit.js`** - AI-powered commit message generation using OpenRouter API - -## Template Features - -This template is designed to be easily customizable and includes: - -- **Content Collections** - Type-safe MDX content with schema validation -- **Design System** - Comprehensive design documentation in `dev/design.json` -- **SEO Optimization** - Structured data, meta tags, Open Graph support -- **Image Optimization** - AVIF conversion utility for modern image formats -- **AI Tooling** - Optional AI-powered git commit messages -- **Deployment Ready** - Pre-configured for Cloudflare Pages +- **`src/utils/convert-to-avif.js`** - Image optimization +- **`src/utils/git-commit.js`** - AI commit message generation (requires OpenRouter API key in `src/utils/.env`) diff --git a/README.md b/README.md index 6ff774d..01ff35c 100644 --- a/README.md +++ b/README.md @@ -1,276 +1,109 @@ -# Astro Portfolio & Blog Template +# Astro Template -A modern, high-performance portfolio and blog template built with **Astro 5**, **React 19**, and **Tailwind CSS 4**. Features an industrial dark design system, MDX blog support, and deployment-ready for Cloudflare Pages. +Minimal Astro development environment with React, Tailwind CSS, and Cloudflare Pages deployment. -## Features +## Stack -### Content Management -- **Type-Safe Content Collections** - Schema validation with Astro's Content Collections API -- **MDX Blog Support** - Write posts in Markdown with embedded React components -- **Flexible Taxonomies** - Organize content with categories and tags -- **Featured Posts** - Highlight your best work -- **Related Posts** - Automatic suggestions based on content similarity -- **Reading Time Calculation** - Automatic based on word count - -### Design & UX -- **Industrial Dark Design System** - Professional, modern aesthetic -- **Fully Responsive** - Mobile-first design approach -- **Custom Components** - Blog cards, filters, navigation, table of contents -- **Reading Progress Indicator** - Visual progress bar for long-form content -- **Theme Toggle** - Dark/light mode support -- **Grid Overlay** - Optional design grid for development - -### Performance & SEO -- **Lightning Fast** - Built with Astro for optimal performance -- **SEO Optimized** - Structured data (JSON-LD), meta tags, Open Graph -- **AVIF Image Support** - Modern image format with conversion utility -- **RSS Feed** - Automatic feed generation -- **Sitemap** - Auto-generated for search engines -- **Cloudflare Pages Ready** - Optimized for edge deployment - -### Developer Experience -- **TypeScript** - Full type safety -- **pnpm** - Fast, disk-efficient package manager -- **AI-Powered Git Commits** - Automatic commit message generation -- **AVIF Converter** - Built-in image optimization utility -- **Template Initialization** - Interactive setup script +- **Astro 5** - Static site framework +- **React 19** - Interactive components +- **Tailwind CSS 4** - Styling +- **MDX** - Markdown with JSX +- **TypeScript** - Type safety +- **Cloudflare Pages** - Deployment +- **pnpm** - Package manager ## Quick Start -### Prerequisites +```bash +# Install dependencies +pnpm install -- **Node.js** 18+ -- **pnpm** (recommended) - `npm install -g pnpm` +# Start dev server +pnpm dev -### Installation +# Build +pnpm build -1. **Clone or download this template** - ```bash - git clone - cd astro-template - ``` - -2. **Install dependencies** - ```bash - pnpm install - ``` - -3. **Initialize your template** - ```bash - node init-template.js - ``` - Follow the prompts to personalize with your information. - -4. **Start development server** - ```bash - pnpm dev - ``` - Open [http://localhost:4321](http://localhost:4321) - -5. **Replace placeholder content** - - Update images in `src/assets/` and `public/media/` - - Customize sections in `src/content/sections/` - - Write your first blog post in `src/content/blog/` - -See [SETUP.md](./SETUP.md) for detailed setup instructions. +# Deploy +pnpm deploy +``` ## Project Structure ``` -/ -├── public/ # Static assets (served as-is) -│ ├── media/ # Videos, large images -│ └── fonts/ # Web fonts -├── src/ -│ ├── assets/ # Optimized images (processed by Astro) -│ ├── components/ # Reusable UI components -│ │ ├── sections/ # Homepage section components -│ │ └── ... -│ ├── content/ # Content collections -│ │ ├── blog/ # Blog posts (MDX) -│ │ ├── sections/ # Homepage sections (MDX) -│ │ └── pages/ # Page-specific content -│ ├── layouts/ # Page layouts -│ ├── pages/ # File-based routing -│ ├── styles/ # Global styles -│ ├── utils/ # Utility scripts -│ └── consts.ts # Site-wide constants -├── dev/ # Development resources -│ ├── design.json # Design system documentation -│ └── continuity.md # Development log -├── template.config.json # Template configuration -└── init-template.js # Setup script +src/ +├── assets/ # Images (processed by Astro) +├── components/ # React/Astro components +├── content/ # MDX content collections +│ ├── blog/ # Blog posts +│ ├── sections/ # Homepage sections +│ └── pages/ # Page content +├── layouts/ # Page layouts +├── pages/ # Routes +├── styles/ # Global CSS +└── utils/ # Utility scripts + +public/ +└── media/ # Static assets + +dev/ +├── design.json # Design system docs +└── continuity.md # Development log ``` ## Content Collections -### Blog Posts - -Create MDX files in `src/content/blog/`: +### Blog Posts (`src/content/blog/`) ```yaml --- -title: 'Your Post Title' -description: 'SEO description' +title: 'Post Title' +description: 'Description' pubDate: 'Dec 27 2024' -heroImage: '../../assets/your-image.avif' +heroImage: '../../assets/image.avif' featured: true -category: 'Tutorial' -tags: ['Tag1', 'Tag2'] +category: 'Category' +tags: ['tag1', 'tag2'] --- -Your content here... +Content here... ``` -### Sections +### Sections (`src/content/sections/`) -Customize homepage sections in `src/content/sections/`: -- `hero.mdx` - Hero section with headline and bio -- `experience.mdx` - Work history and achievements -- `skills.mdx` - Technical skills and proficiencies -- `featured-project.mdx` - Showcase your best work +- `hero.mdx` - Hero section +- `experience.mdx` - Work history +- `skills.mdx` - Skills +- `featured-project.mdx` - Featured work -## Available Commands +See files for schema examples. -### Development -```bash -pnpm dev # Start dev server (localhost:4321) -pnpm build # Build for production -pnpm preview # Build and preview with Wrangler -``` - -### Deployment -```bash -pnpm deploy # Build and deploy to Cloudflare Pages -``` - -### Utilities -```bash -pnpm commit # AI-powered commit messages -pnpm convert:avif:all # Convert all images to AVIF -pnpm convert:avif:jpeg # Convert JPEG only -pnpm convert:avif:png # Convert PNG only -pnpm cf-typegen # Generate Cloudflare types -``` - -### Template Setup -```bash -node init-template.js # Interactive setup -node init-template.js --config # Use template.config.json -``` - -## Customization - -### Design System - -The design system is documented in `dev/design.json`. Key areas: - -- **Colors**: Modify CSS custom properties in `src/styles/global.css` -- **Typography**: Adjust font scales and families -- **Spacing**: Grid system and spacing tokens -- **Components**: Design patterns and component specs - -### Configuration Files - -- **template.config.json** - Site-wide settings (name, URLs, social links) -- **astro.config.mjs** - Astro configuration -- **wrangler.jsonc** - Cloudflare Pages settings -- **src/consts.ts** - Global constants - -### Adding Content - -1. **New Blog Post**: Create `.mdx` file in `src/content/blog/` -2. **Update Sections**: Edit files in `src/content/sections/` -3. **Add Images**: Place in `src/assets/` (processed) or `public/media/` (static) -4. **Customize Components**: Modify files in `src/components/` - -## Deployment - -### Cloudflare Pages (Default) - -1. **Build your site** - ```bash - pnpm build - ``` - -2. **Deploy** - ```bash - pnpm deploy - ``` - Or connect your Git repository to Cloudflare Pages for automatic deployments. - -### Other Platforms - -This template can be adapted for other platforms: -- **Vercel**: Change adapter to `@astrojs/vercel` -- **Netlify**: Change adapter to `@astrojs/netlify` -- **Static**: Remove adapter for static site generation - -## AI-Powered Commit Messages - -This template includes an AI commit message generator: - -1. **Setup**: Create `src/utils/.env` with your OpenRouter API key - ``` - OPENROUTER_API_KEY=your_key_here - ``` - -2. **Use**: Stage changes and run - ```bash - pnpm commit - ``` - -See `src/utils/README.md` for details. - -## Image Optimization - -Convert images to AVIF format for optimal performance: +## Utilities ```bash -# Convert all images +# AI-powered commit messages +pnpm commit + +# Convert images to AVIF pnpm convert:avif:all - -# Convert specific formats pnpm convert:avif:jpeg pnpm convert:avif:png + +# Generate Cloudflare types +pnpm cf-typegen ``` -The utility processes files in `src/assets/` and `public/media/`. +## Configuration -## Tech Stack +- `src/consts.ts` - Site constants +- `astro.config.mjs` - Astro config +- `wrangler.jsonc` - Cloudflare config +- `dev/design.json` - Design system -- **Framework**: [Astro 5](https://astro.build) -- **UI Library**: [React 19](https://react.dev) -- **Styling**: [Tailwind CSS 4](https://tailwindcss.com) -- **Deployment**: [Cloudflare Pages](https://pages.cloudflare.com) -- **Content**: MDX with Content Collections -- **Package Manager**: pnpm +## Development -## Browser Support - -- Chrome/Edge 90+ -- Firefox 88+ -- Safari 14+ -- Modern mobile browsers - -## Contributing - -This is a template repository. Feel free to fork and customize for your needs. +See `CLAUDE.md` for detailed architecture and development patterns. ## License -MIT License - see [LICENSE](./LICENSE) for details. - -## Support - -- [Documentation](./SETUP.md) -- [Astro Docs](https://docs.astro.build) -- [Cloudflare Pages Docs](https://developers.cloudflare.com/pages) - -## Credits - -Built with modern web technologies and best practices for performance, SEO, and developer experience. - ---- - -**Ready to make it yours?** Run `node init-template.js` to get started! +MIT diff --git a/SETUP.md b/SETUP.md deleted file mode 100644 index c0ddd66..0000000 --- a/SETUP.md +++ /dev/null @@ -1,503 +0,0 @@ -# Setup Guide - -Complete step-by-step guide to setting up and customizing your Astro portfolio template. - -## Table of Contents - -1. [Initial Setup](#initial-setup) -2. [Template Personalization](#template-personalization) -3. [Content Customization](#content-customization) -4. [Media Assets](#media-assets) -5. [Design Customization](#design-customization) -6. [Deployment](#deployment) -7. [Optional Features](#optional-features) -8. [Troubleshooting](#troubleshooting) - -## Initial Setup - -### Prerequisites - -Before you begin, make sure you have: - -- **Node.js 18 or later** - [Download here](https://nodejs.org/) -- **pnpm** (recommended) - Install with `npm install -g pnpm` -- **Git** - [Download here](https://git-scm.com/) -- A **code editor** (VS Code recommended) - -### Installation - -1. **Clone or download the template** - ```bash - git clone - cd astro-template - ``` - -2. **Install dependencies** - ```bash - pnpm install - ``` - -3. **Verify installation** - ```bash - pnpm dev - ``` - Open [http://localhost:4321](http://localhost:4321) - you should see the template with placeholder content. - -## Template Personalization - -### Option 1: Interactive Setup (Recommended) - -Run the setup wizard to personalize all template files: - -```bash -node init-template.js -``` - -The wizard will ask for: -- Your name and profession -- Site URL and description -- Contact information (email, location) -- Social media links -- Company/branding details -- Cloudflare project name - -Review the summary and confirm to apply changes. - -### Option 2: Manual Configuration - -Edit `template.config.json` with your information, then run: - -```bash -node init-template.js --config -``` - -### What Gets Updated - -The setup script updates: -- `src/consts.ts` - Site-wide constants -- `src/components/BaseHead.astro` - SEO and structured data -- `src/components/Navigation.astro` - Branding -- `src/components/Footer.astro` - Contact and social links -- `src/content/sections/hero.mdx` - Hero section -- `src/content/sections/experience.mdx` - Work history -- `src/content/pages/contact.mdx` - Contact page -- `astro.config.mjs` - Site URL -- `wrangler.jsonc` - Cloudflare project name -- `package.json` - Package name - -## Content Customization - -### Homepage Sections - -Edit files in `src/content/sections/`: - -#### Hero Section (`hero.mdx`) - -```yaml ---- -headlineLine1: "YOUR NAME" -headlineLine2: "HERE" -portfolioYear: "Portfolio 2025" -location: "Your City, State" -locationLabel: "Location" -bio: "Your professional bio..." ---- -``` - -#### Experience (`experience.mdx`) - -```yaml ---- -sectionTitle: "Experience" -sectionSubtitle: "History" -sectionLabel: "/// Your professional journey." -entries: - - systemId: "SYS.01" - status: "ACTIVE" - dates: "2020 — PRESENT" - company: "Company Name" - role: "Your Role" - tags: ["Skill 1", "Skill 2"] - description: "What you do..." - achievements: - - label: "Projects" - text: "Notable projects..." - link: - url: "https://company.com" - text: "Visit Website" ---- -``` - -#### Skills (`skills.mdx`) - -```yaml ---- -sectionTitle: "Technical" -sectionSubtitle: "Arsenal" -description: "Your skills and specialties" -skills: - - id: "01" - domain: "Primary Skill" - tools: "Tool 1 • Tool 2 • Tool 3" - proficiency: "Expert" ---- -``` - -#### Featured Project (`featured-project.mdx`) - -```yaml ---- -role: "Your Role" -client: "Client Name" -year: "2024" -region: "Global" -projectTitle: "Project" -projectSubtitle: "Name" -projectDescription: "Brief description..." -stats: - - label: "Category" - value: "Value" -videoUrl: "/media/your-video.mp4" -linkUrl: "/blog/your-case-study/" ---- -``` - -### Blog Posts - -#### Creating a New Post - -1. Create a new MDX file in `src/content/blog/`: - ```bash - touch src/content/blog/my-new-post.mdx - ``` - -2. Add frontmatter and content: - ```mdx - --- - title: 'Your Post Title' - description: 'Brief description for SEO' - pubDate: 'Dec 27 2024' - heroImage: '../../assets/hero-image.avif' - featured: false - category: 'Tutorial' - tags: ['Web Dev', 'Astro'] - --- - - ## Introduction - - Your content here... - ``` - -3. The file name becomes the URL slug: - - `my-new-post.mdx` → `/blog/my-new-post/` - -#### Blog Frontmatter Fields - -**Required:** -- `title` - Post title -- `description` - SEO description -- `pubDate` - Publication date - -**Optional:** -- `heroImage` - Header image path -- `featured` - Set to `true` to feature on blog index -- `category` - For filtering -- `tags` - Array of tags -- `updatedDate` - Last update date - -#### Example Blog Post Template - -See `dev/blog_template.mdx` for a complete example. - -### Contact Page - -Edit `src/content/pages/contact.mdx`: - -```yaml ---- -pageTitleLine1: "Get In" -pageTitleLine2: "Touch" -availabilityText: "Available for new projects" -email: "your@email.com" -location: "Your City" -coordinates: "XX.XXXX° N, XX.XXXX° W" -socialLinks: - - name: "LinkedIn" - url: "https://linkedin.com/in/yourprofile" -formLabels: - name: "/// Your Name" - email: "/// Your Email" - subject: "/// Subject" - message: "/// Message" - submit: "Send Message" -subjectOptions: - - value: "project" - label: "Project Inquiry" ---- -``` - -## Media Assets - -### Images - -#### Placeholder Images to Replace - -See `public/media/PLACEHOLDER_ASSETS.md` for a complete list. - -**Priority replacements:** -1. Default OG image: `src/assets/nicholai-medium-portrait.avif` -2. Blog post hero images -3. Featured project video -4. Favicons - -#### Adding Your Images - -1. **For blog/content images** (optimized by Astro): - - Place in `src/assets/` - - Reference: `../../assets/image-name.jpg` - -2. **For videos/large files** (static): - - Place in `public/media/` - - Reference: `/media/filename.mp4` - -#### Converting to AVIF - -AVIF provides superior compression. Convert your images: - -```bash -# Convert all images -pnpm convert:avif:all - -# Convert specific formats -pnpm convert:avif:jpeg -pnpm convert:avif:png - -# Custom quality (0-100) -node src/utils/convert-to-avif.js --jpeg --quality 80 -``` - -### Favicons - -Generate a complete favicon set: - -1. Use [favicon.io](https://favicon.io) or [RealFaviconGenerator](https://realfavicongenerator.net) -2. Replace files in `public/`: - - `favicon.ico` - - `favicon-32.png` - - `favicon-192.png` - - `apple-touch-icon.png` - - `favicon.svg` - -## Design Customization - -### Color Scheme - -Edit CSS custom properties in `src/styles/global.css`: - -```css -:root { - /* Background colors */ - --theme-bg-primary: #0B0D11; - --theme-bg-secondary: #12141A; - - /* Text colors */ - --theme-text-primary: #E8E9ED; - --theme-text-muted: #9CA3B3; - - /* Accent colors */ - --brand-accent: #3D8374; -} -``` - -### Typography - -Fonts are loaded in `src/components/BaseHead.astro`. To change: - -1. Update Google Fonts link (line 124-137) -2. Update font families in `global.css` - -### Design System - -Full design documentation in `dev/design.json`: -- Color palettes -- Typography scales -- Spacing system -- Grid system -- Component patterns - -## Deployment - -### Cloudflare Pages (Default) - -#### Option 1: CLI Deployment - -```bash -pnpm deploy -``` - -#### Option 2: Git Integration - -1. Push your code to GitHub/GitLab -2. Go to [Cloudflare Pages Dashboard](https://dash.cloudflare.com/pages) -3. Connect your repository -4. Configure build settings: - - **Build command**: `pnpm build` - - **Build output directory**: `dist` - - **Root directory**: `/` - - **Node version**: 18 or later -5. Deploy! - -### Environment Variables - -For Cloudflare Pages deployment: - -1. Go to your project settings → Environment Variables -2. Add any required variables (e.g., API keys for forms) - -### Custom Domain - -1. In Cloudflare Pages, go to Custom Domains -2. Add your domain -3. Follow DNS configuration instructions - -### Other Platforms - -**Vercel:** -```bash -# Change adapter -pnpm remove @astrojs/cloudflare -pnpm add @astrojs/vercel -``` - -Update `astro.config.mjs`: -```js -import vercel from '@astrojs/vercel/serverless'; - -export default defineConfig({ - adapter: vercel(), - // ... -}); -``` - -**Netlify:** -```bash -pnpm remove @astrojs/cloudflare -pnpm add @astrojs/netlify -``` - -**Static (no adapter):** -Remove the `adapter` from `astro.config.mjs` for pure static site generation. - -## Optional Features - -### AI-Powered Git Commits - -1. Get an API key from [OpenRouter.ai](https://openrouter.ai) - -2. Create `src/utils/.env`: - ``` - OPENROUTER_API_KEY=your_key_here - ``` - -3. Stage changes and run: - ```bash - pnpm commit - ``` - -The script generates a commit message, lets you edit it, and optionally pushes. - -### Analytics - -Add analytics to `src/components/BaseHead.astro`: - -```astro - - -``` - -### Contact Form Integration - -The template includes a contact form. To make it functional: - -**Option 1: Cloudflare Forms** (Recommended) -- Forms are built-in with Cloudflare Pages -- View submissions in your Cloudflare Dashboard - -**Option 2: External Service** -- [Formspree](https://formspree.io) -- [Web3Forms](https://web3forms.com) -- [Netlify Forms](https://www.netlify.com/products/forms/) - -Update the form action in `src/pages/contact.astro`. - -## Troubleshooting - -### Build Errors - -**"Cannot find module..."** -```bash -# Reinstall dependencies -rm -rf node_modules pnpm-lock.yaml -pnpm install -``` - -**TypeScript errors** -```bash -# Generate types -pnpm cf-typegen -``` - -### Dev Server Issues - -**Port already in use** -```bash -# Use a different port -pnpm dev -- --port 3000 -``` - -**Changes not reflecting** -- Hard reload: Ctrl+Shift+R (Windows) / Cmd+Shift+R (Mac) -- Clear `.astro` cache and restart - -### Image Issues - -**Images not loading** -- Check file paths (relative vs absolute) -- Ensure images are in correct directory -- Restart dev server - -**AVIF conversion fails** -```bash -# Install Sharp dependencies -pnpm install sharp -``` - -### Deployment Issues - -**Build fails on Cloudflare** -- Check Node version (should be 18+) -- Verify `pnpm build` works locally -- Check build logs for specific errors - -**404 on deployed site** -- Verify build output directory is `dist` -- Check routing/links are correct -- Clear Cloudflare cache - -## Next Steps - -1. **Test your site locally**: `pnpm dev` -2. **Build for production**: `pnpm build` -3. **Preview build**: `pnpm preview` -4. **Deploy**: `pnpm deploy` -5. **Update `dev/continuity.md`** with your changes - -## Support - -- **Astro Documentation**: [docs.astro.build](https://docs.astro.build) -- **Cloudflare Pages**: [developers.cloudflare.com/pages](https://developers.cloudflare.com/pages) -- **Tailwind CSS**: [tailwindcss.com/docs](https://tailwindcss.com/docs) - ---- - -**Need help?** Check the existing blog posts for examples of how to use various features. diff --git a/dev/continuity.md b/dev/continuity.md index 51b2ad7..4d9f9bc 100644 --- a/dev/continuity.md +++ b/dev/continuity.md @@ -1,69 +1,54 @@ # Continuity Log -This file tracks development changes, decisions, and next steps for maintaining project context across sessions. - -## Instructions - -When making changes to this template: -1. Add a new entry with today's date -2. Document what changed and why -3. Note any decisions made -4. List next steps or follow-up items +Development log for tracking changes, decisions, and next steps. ## Entry Template ```markdown ## YYYY-MM-DD - Brief Description -### Changes Made -- List what was changed -- Be specific about files and features +### Changes +- What changed +- Why it changed -### Decisions & Rationale -- Document why certain approaches were chosen -- Note trade-offs or alternatives considered - -### Testing Steps -1. How to verify the changes work -2. What to check +### Decisions +- Key decisions made +- Trade-offs considered ### Next Steps -- [ ] Follow-up task 1 -- [ ] Follow-up task 2 +- [ ] Follow-up items ``` --- -## 2024-12-27 - Template Initialization +## 2024-12-27 - Minimal Template Setup -### Changes Made -- Created as a reusable Astro portfolio/blog template -- Replaced all personal content with placeholders -- Added template initialization script (`init-template.js`) -- Created comprehensive documentation (README.md, SETUP.md) -- Included utility scripts for AVIF conversion and AI commit messages +### Changes +- Created minimal Astro development template +- Single example blog post showing MDX structure +- Minimal section examples (hero, experience, skills, featured-project) +- Simple contact page example +- Utility scripts: AVIF conversion, AI commit messages +- Design system documentation in `dev/design.json` -### Template Structure -- **Content**: MDX-based content collections (blog, sections, pages) -- **Design**: Industrial dark design system with full customization -- **Deployment**: Pre-configured for Cloudflare Pages -- **Tools**: pnpm, TypeScript, Tailwind CSS 4, React 19 +### Stack +- Astro 5 + React 19 + Tailwind CSS 4 +- TypeScript +- MDX content collections +- Cloudflare Pages deployment +- pnpm package manager -### Initial Setup Steps -1. Run `node init-template.js` to personalize -2. Replace placeholder images in `src/assets/` and `public/media/` -3. Update sections in `src/content/sections/` -4. Write first blog post in `src/content/blog/` -5. Deploy with `pnpm deploy` +### Structure +- Content-driven architecture with type-safe schemas +- Example content showing data structures +- Utility scripts for common tasks +- Clean development environment ### Next Steps -- [ ] Customize design system (colors, fonts) if needed -- [ ] Add your own content and media assets -- [ ] Configure analytics and contact form -- [ ] Connect domain and deploy +- [ ] Replace example content with your own +- [ ] Customize design system as needed +- [ ] Configure deployment --- -## Future Entries - -Add your development log entries below... +Add new entries below... diff --git a/init-template.js b/init-template.js deleted file mode 100644 index b60c8dc..0000000 --- a/init-template.js +++ /dev/null @@ -1,260 +0,0 @@ -#!/usr/bin/env node - -/** - * Template Initialization Script - * - * This script personalizes the Astro portfolio template with your information. - * - * Usage: - * node init-template.js # Interactive mode (prompts for info) - * node init-template.js --config # Config mode (reads template.config.json) - * node init-template.js --help # Show help - */ - -import fs from 'fs/promises'; -import path from 'path'; -import { fileURLToPath } from 'url'; -import readline from 'readline'; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); - -// ANSI color codes for better terminal output -const colors = { - reset: '\x1b[0m', - bright: '\x1b[1m', - dim: '\x1b[2m', - green: '\x1b[32m', - yellow: '\x1b[33m', - blue: '\x1b[34m', - cyan: '\x1b[36m', - red: '\x1b[31m' -}; - -function log(message, color = 'reset') { - console.log(`${colors[color]}${message}${colors.reset}`); -} - -function prompt(question) { - const rl = readline.createInterface({ - input: process.stdin, - output: process.stdout - }); - - return new Promise((resolve) => { - rl.question(`${colors.cyan}${question}${colors.reset} `, (answer) => { - rl.close(); - resolve(answer.trim()); - }); - }); -} - -async function loadConfig() { - try { - const configPath = path.join(__dirname, 'template.config.json'); - const configData = await fs.readFile(configPath, 'utf-8'); - return JSON.parse(configData); - } catch (error) { - log('Warning: Could not load template.config.json', 'yellow'); - return null; - } -} - -async function interactiveMode() { - log('\n=== Astro Portfolio Template Setup ===\n', 'bright'); - log('This wizard will help you personalize your template.\n', 'dim'); - - const config = { - site: {}, - branding: {}, - personal: {}, - social: {}, - seo: {}, - cloudflare: {} - }; - - // Site information - log('\n--- Site Information ---\n', 'blue'); - config.personal.fullName = await prompt('Your full name:') || 'Your Name'; - config.personal.firstName = config.personal.fullName.split(' ')[0]; - config.personal.lastName = config.personal.fullName.split(' ').slice(1).join(' ') || 'Name'; - config.personal.jobTitle = await prompt('Your profession/job title:') || 'Your Profession'; - config.site.title = `${config.personal.fullName} — ${config.personal.jobTitle}`; - config.site.description = await prompt('Brief description of what you do:') || 'Professional portfolio and blog'; - config.site.url = await prompt('Your site URL (e.g., https://example.com):') || 'https://yoursite.com'; - config.site.author = config.personal.fullName; - - // Branding - log('\n--- Branding ---\n', 'blue'); - const defaultInitials = config.personal.firstName[0] + (config.personal.lastName[0] || 'N'); - config.branding.initials = await prompt(`Your initials (default: ${defaultInitials}):`) || defaultInitials; - config.branding.year = await prompt('Portfolio year (default: 2025):') || '2025'; - config.branding.backgroundText = await prompt(`Large background text (default: ${config.personal.lastName.toUpperCase()}):`) || config.personal.lastName.toUpperCase(); - config.branding.companyName = await prompt('Company name (optional):') || 'Your Company'; - - // Contact information - log('\n--- Contact Information ---\n', 'blue'); - config.personal.email = await prompt('Email address:') || 'your@email.com'; - config.personal.location = await prompt('Location (e.g., San Francisco, CA):') || 'Your City, State'; - config.personal.locationCountry = await prompt('Country:') || 'Your Country'; - config.personal.bio = await prompt('Professional bio (1-2 sentences):') || 'Professional bio here.'; - - // Social links - log('\n--- Social Links ---\n', 'blue'); - config.social.linkedin = await prompt('LinkedIn URL (full URL):') || 'https://linkedin.com/in/yourprofile'; - config.social.github = await prompt('GitHub URL:') || 'https://github.com/yourusername'; - config.social.twitter = await prompt('Twitter URL:') || 'https://twitter.com/yourhandle'; - const twitterHandle = config.social.twitter.split('/').pop(); - config.social.twitterHandle = twitterHandle.startsWith('@') ? twitterHandle : `@${twitterHandle}`; - config.social.website = config.site.url; - - // Cloudflare - log('\n--- Deployment ---\n', 'blue'); - const defaultProjectName = config.site.url.replace(/https?:\/\//, '').replace(/[^a-z0-9-]/gi, '-').toLowerCase(); - config.cloudflare.projectName = await prompt(`Cloudflare project name (default: ${defaultProjectName}):`) || defaultProjectName; - - // SEO - config.seo.keywords = ['Portfolio', 'Blog', config.personal.jobTitle]; - config.seo.serviceTypes = ['Service 1', 'Service 2', 'Service 3']; - config.seo.companyUrl = config.branding.companyName !== 'Your Company' - ? await prompt('Company website URL (optional):') - : 'https://example.com'; - - return config; -} - -async function applyConfig(config) { - log('\n🔧 Applying configuration...\n', 'bright'); - - const replacements = { - // Site-wide - 'Your Name — Your Profession': config.site.title, - 'Your Name': config.personal.fullName, - 'Your Profession': config.personal.jobTitle, - 'Your professional description here. Describe what you do, who you work with, and what makes you unique.': config.site.description, - 'your professional description': config.site.description, - 'https://yoursite.com': config.site.url, - 'your@email.com': config.personal.email, - - // Branding - 'YN / 2025': `${config.branding.initials} / ${config.branding.year}`, - 'NAME': config.branding.backgroundText, - - // Personal - 'Your': config.personal.firstName, - 'Your City, State': config.personal.location, - 'Your Country': config.personal.locationCountry, - 'Your Company': config.branding.companyName, - - // Social - 'https://linkedin.com/in/yourprofile': config.social.linkedin, - 'https://github.com/yourusername': config.social.github, - 'https://twitter.com/yourhandle': config.social.twitter, - '@yourhandle': config.social.twitterHandle, - - // Cloudflare - 'astro-portfolio-template': config.cloudflare.projectName - }; - - const filesToUpdate = [ - 'src/consts.ts', - 'src/components/BaseHead.astro', - 'src/components/Navigation.astro', - 'src/components/Footer.astro', - 'src/content/sections/hero.mdx', - 'src/content/sections/experience.mdx', - 'src/content/pages/contact.mdx', - 'astro.config.mjs', - 'wrangler.jsonc', - 'package.json' - ]; - - for (const file of filesToUpdate) { - try { - const filePath = path.join(__dirname, file); - let content = await fs.readFile(filePath, 'utf-8'); - - for (const [search, replace] of Object.entries(replacements)) { - content = content.replaceAll(search, replace); - } - - await fs.writeFile(filePath, content, 'utf-8'); - log(`✓ Updated ${file}`, 'green'); - } catch (error) { - log(`✗ Failed to update ${file}: ${error.message}`, 'red'); - } - } - - // Save config for reference - try { - const configPath = path.join(__dirname, 'template.config.json'); - await fs.writeFile( - configPath, - JSON.stringify(config, null, 2), - 'utf-8' - ); - log('✓ Saved configuration to template.config.json', 'green'); - } catch (error) { - log(`✗ Failed to save config: ${error.message}`, 'red'); - } - - log('\n✨ Template personalization complete!\n', 'bright'); - log('Next steps:', 'cyan'); - log(' 1. Review the changes made to your files', 'dim'); - log(' 2. Replace placeholder images in src/assets/ and public/media/', 'dim'); - log(' 3. Update content in src/content/sections/ with your info', 'dim'); - log(' 4. Run `pnpm dev` to preview your site', 'dim'); - log(' 5. Run `pnpm deploy` when ready to publish\n', 'dim'); -} - -async function showHelp() { - log('\n=== Astro Portfolio Template Setup ===\n', 'bright'); - log('Usage:', 'cyan'); - log(' node init-template.js Interactive mode (prompts for info)', 'dim'); - log(' node init-template.js --config Config mode (reads template.config.json)', 'dim'); - log(' node init-template.js --help Show this help message\n', 'dim'); - log('Interactive mode will ask you questions and personalize the template.', 'dim'); - log('Config mode reads from template.config.json file.\n', 'dim'); -} - -async function main() { - const args = process.argv.slice(2); - - if (args.includes('--help') || args.includes('-h')) { - await showHelp(); - return; - } - - if (args.includes('--config')) { - log('\n📄 Running in config mode...\n', 'bright'); - const config = await loadConfig(); - - if (!config) { - log('Error: template.config.json not found or invalid.', 'red'); - log('Create one or run without --config flag for interactive mode.\n', 'yellow'); - process.exit(1); - } - - await applyConfig(config); - } else { - // Interactive mode - const config = await interactiveMode(); - - log('\n📋 Configuration summary:', 'yellow'); - console.log(JSON.stringify(config, null, 2)); - - const confirm = await prompt('\nApply this configuration? (yes/no):'); - - if (confirm.toLowerCase() === 'yes' || confirm.toLowerCase() === 'y') { - await applyConfig(config); - } else { - log('\nSetup cancelled. No changes were made.\n', 'yellow'); - } - } -} - -main().catch((error) => { - log(`\n❌ Error: ${error.message}\n`, 'red'); - console.error(error); - process.exit(1); -}); diff --git a/src/components/BlogFilters.astro b/src/components/BlogFilters.astro deleted file mode 100644 index 2622b2c..0000000 --- a/src/components/BlogFilters.astro +++ /dev/null @@ -1,179 +0,0 @@ ---- -interface Props { - categories: string[]; - class?: string; -} - -const { categories, class: className = '' } = Astro.props; ---- - -
- -
- -
- - /// SECTOR SELECT - - - {categories.map((category) => ( - - ))} -
- - -
-
- > -
- - -
-
- - -
- - 0 ARTICLES - - -
-
- - - - diff --git a/src/components/PostNavigation.astro b/src/components/PostNavigation.astro deleted file mode 100644 index 79d21e5..0000000 --- a/src/components/PostNavigation.astro +++ /dev/null @@ -1,106 +0,0 @@ ---- -import { Image } from 'astro:assets'; -import type { ImageMetadata } from 'astro'; - -interface NavPost { - title: string; - href: string; - heroImage?: ImageMetadata; -} - -interface Props { - prevPost?: NavPost; - nextPost?: NavPost; -} - -const { prevPost, nextPost } = Astro.props; ---- - -{(prevPost || nextPost) && ( - -)} diff --git a/src/components/ReadingProgress.astro b/src/components/ReadingProgress.astro deleted file mode 100644 index b31f83a..0000000 --- a/src/components/ReadingProgress.astro +++ /dev/null @@ -1,71 +0,0 @@ ---- -// Reading progress bar that tracks scroll position ---- - -
-
-
- - - - diff --git a/src/components/RelatedPosts.astro b/src/components/RelatedPosts.astro deleted file mode 100644 index b49cc44..0000000 --- a/src/components/RelatedPosts.astro +++ /dev/null @@ -1,47 +0,0 @@ ---- -import BlogCard from './BlogCard.astro'; -import type { ImageMetadata } from 'astro'; - -interface RelatedPost { - title: string; - description: string; - pubDate: Date; - heroImage?: ImageMetadata; - category?: string; - tags?: string[]; - href: string; -} - -interface Props { - posts: RelatedPost[]; - class?: string; -} - -const { posts, class: className = '' } = Astro.props; ---- - -{posts.length > 0 && ( -
-
- - /// RELATED_ARCHIVES - - -
- -
- {posts.slice(0, 3).map((post) => ( - - ))} -
-
-)} diff --git a/src/components/TableOfContents.astro b/src/components/TableOfContents.astro deleted file mode 100644 index 133a0e8..0000000 --- a/src/components/TableOfContents.astro +++ /dev/null @@ -1,121 +0,0 @@ ---- -interface Props { - headings: Array<{ - depth: number; - slug: string; - text: string; - }>; - class?: string; -} - -const { headings, class: className = '' } = Astro.props; - -// Filter to only H2 and H3 headings -const tocHeadings = headings.filter((h) => h.depth === 2 || h.depth === 3); ---- - -{tocHeadings.length > 0 && ( - -)} - - - - diff --git a/src/components/sections/Experience.astro b/src/components/sections/Experience.astro deleted file mode 100644 index 83a5835..0000000 --- a/src/components/sections/Experience.astro +++ /dev/null @@ -1,150 +0,0 @@ ---- -interface Props { - sectionTitle: string; - sectionSubtitle: string; - sectionLabel: string; - description: string; - entries: Array<{ - systemId: string; - status: string; - dates: string; - company: string; - role: string; - tags?: string[]; - description: string; - achievements?: Array<{ - label: string; - text: string; - }>; - link?: { - url: string; - text: string; - }; - }>; -} - -const { sectionTitle, sectionSubtitle, sectionLabel, description, entries } = Astro.props; ---- -
- -
diff --git a/src/components/sections/FeaturedProject.astro b/src/components/sections/FeaturedProject.astro deleted file mode 100644 index 9a0d73d..0000000 --- a/src/components/sections/FeaturedProject.astro +++ /dev/null @@ -1,185 +0,0 @@ ---- -interface Props { - role: string; - client: string; - year: string; - region: string; - projectTitle: string; - projectSubtitle: string; - projectDescription: string; - stats: Array<{ - label: string; - value: string; - }>; - videoUrl: string; - linkUrl: string; -} - -const { role, client, year, region, projectTitle, projectSubtitle, projectDescription, stats, videoUrl, linkUrl } = Astro.props; ---- -
- - - - -
- -
- - - -
-
- - -
- - - -
- - -
- - -
-
-
-
- SYS.ROLE -
- {role} -
-
- SYS.CLIENT - {client} -
-
- SYS.YEAR - {year} -
-
- SYS.REGION - {region} -
-
- - -
- -
- - -
-
- - -
-

- {projectTitle} {projectSubtitle} -

-

- {projectDescription} -

-
- - -
-
- {stats.map((stat, idx) => ( -
-
- 0{idx + 1}. {stat.label} - {stat.value} -
-
-
-
-
- ))} -
- -
-
- UPLINK.PROJECT_DETAIL -
- - - -
-
-
-
- -
-
- -
- -
- - diff --git a/src/components/sections/Hero.astro b/src/components/sections/Hero.astro deleted file mode 100644 index 3563d7a..0000000 --- a/src/components/sections/Hero.astro +++ /dev/null @@ -1,301 +0,0 @@ ---- -import { Picture } from 'astro:assets'; -import heroPortrait from '../../assets/nicholai-closeup-portrait.avif'; - -interface Props { - headlineLine1: string; - headlineLine2: string; - portfolioYear: string; - location: string; - locationLabel: string; - bio: string; -} - -const { headlineLine1, headlineLine2, portfolioYear, location, locationLabel, bio } = Astro.props; ---- -
- -
- - -
-
- -
-
- - - -
-
- - -
- {Array.from({ length: 100 }).map((_, i) => ( -
- ))} -
- - - -
- - -
-
-
-
- SYS.PRTF / {portfolioYear} -
-
- -
-
- {locationLabel} - /// -
-
{location}
-
00:00:00 MST
-
-
- - -
-

- {headlineLine1} - {headlineLine2} -

- -

- {bio} -

-
- - -
- -
- - - - -
-
-
-
- Scroll - To Explore -
-
- - -
-
-
- - - - diff --git a/src/components/sections/Skills.astro b/src/components/sections/Skills.astro deleted file mode 100644 index 3dede34..0000000 --- a/src/components/sections/Skills.astro +++ /dev/null @@ -1,106 +0,0 @@ ---- -import { Image } from 'astro:assets'; - -interface Props { - sectionTitle: string; - sectionSubtitle: string; - description: string; - skills: Array<{ - id: string; - domain: string; - tools: string; - proficiency: string; - }>; -} - -const { sectionTitle, sectionSubtitle, description, skills } = Astro.props; - -// Image map for skill data attributes -const imageMap: Record = { - "01": "compositing", - "02": "3d", - "03": "ai", - "04": "dev" -}; ---- - -
-
- - -
-
-
-
- SYS.TOOLSET /// PIPELINE_CAPABILITIES -
-

- {sectionTitle} - {sectionSubtitle} -

-
-
-
- - TECH_STACK_MANIFEST -
-

- {description} -

-
-
- - -
- - -
-
/// ID.TAG
-
DOMAIN.SPECIALIZATION
- - -
- - {skills.map((skill, index) => { - const proficiencyClass = skill.proficiency === "Expert" || skill.proficiency === "Specialist" - ? "border-brand-accent/50 text-brand-accent bg-brand-accent/5" - : "border-[var(--theme-border-strong)] text-[var(--theme-text-secondary)]"; - - return ( -
- -
- - -
- {skill.id} - {skill.id} -
- - -
-

{skill.domain}

- -
-
- - -
- {skill.tools} -
- - - - - -
-
- ); - })} - -
-
- -
diff --git a/src/content.config.ts b/src/content.config.ts index d7bafff..5429276 100644 --- a/src/content.config.ts +++ b/src/content.config.ts @@ -2,108 +2,18 @@ import { defineCollection, z } from 'astro:content'; import { glob } from 'astro/loaders'; const blog = defineCollection({ - // Load Markdown and MDX files in the `src/content/blog/` directory. loader: glob({ base: './src/content/blog', pattern: '**/*.{md,mdx}' }), - // Type-check frontmatter using a schema schema: ({ image }) => z.object({ title: z.string(), description: z.string(), - // Transform string to Date object pubDate: z.coerce.date(), updatedDate: z.coerce.date().optional(), heroImage: image().optional(), - // Blog hub fields featured: z.boolean().optional().default(false), category: z.string().optional(), tags: z.array(z.string()).optional(), }), }); -const sections = defineCollection({ - loader: glob({ base: './src/content/sections', pattern: '**/*.{md,mdx}' }), - schema: z.object({ - // Hero section - headlineLine1: z.string().optional(), - headlineLine2: z.string().optional(), - portfolioYear: z.string().optional(), - location: z.string().optional(), - locationLabel: z.string().optional(), - bio: z.string().optional(), - // Experience section - sectionTitle: z.string().optional(), - sectionSubtitle: z.string().optional(), - sectionLabel: z.string().optional(), - description: z.string().optional(), - // Experience entries - entries: z.array(z.object({ - systemId: z.string(), - status: z.string(), - dates: z.string(), - company: z.string(), - role: z.string(), - tags: z.array(z.string()).optional(), - description: z.string(), - achievements: z.array(z.object({ - label: z.string(), - text: z.string(), - })).optional(), - link: z.object({ - url: z.string(), - text: z.string(), - }).optional(), - })).optional(), - // Skills entries - skills: z.array(z.object({ - id: z.string(), - domain: z.string(), - tools: z.string(), - proficiency: z.string(), - })).optional(), - // Featured project - role: z.string().optional(), - client: z.string().optional(), - year: z.string().optional(), - region: z.string().optional(), - projectTitle: z.string().optional(), - projectSubtitle: z.string().optional(), - projectDescription: z.string().optional(), - stats: z.array(z.object({ - label: z.string(), - value: z.string(), - })).optional(), - videoUrl: z.string().optional(), - linkUrl: z.string().optional(), - }), -}); - -const pages = defineCollection({ - loader: glob({ base: './src/content/pages', pattern: '**/*.{md,mdx}' }), - schema: z.object({ - pageTitleLine1: z.string().optional(), - pageTitleLine2: z.string().optional(), - availabilityText: z.string().optional(), - email: z.string().optional(), - location: z.string().optional(), - locationCountry: z.string().optional(), - coordinates: z.string().optional(), - socialLinks: z.array(z.object({ - name: z.string(), - url: z.string(), - })).optional(), - formLabels: z.object({ - name: z.string().optional(), - email: z.string().optional(), - subject: z.string().optional(), - message: z.string().optional(), - submit: z.string().optional(), - transmissionUplink: z.string().optional(), - }).optional(), - subjectOptions: z.array(z.object({ - value: z.string(), - label: z.string(), - })).optional(), - }), -}); - -export const collections = { blog, sections, pages }; +export const collections = { blog }; diff --git a/src/content/blog/sample-case-study.mdx b/src/content/blog/sample-case-study.mdx deleted file mode 100644 index 6cde4ec..0000000 --- a/src/content/blog/sample-case-study.mdx +++ /dev/null @@ -1,104 +0,0 @@ ---- -title: 'Sample Project Case Study' -description: 'A template for documenting your projects with technical details, creative process, and results. Use this structure for your own case studies.' -pubDate: 'Dec 20 2024' -heroImage: '../../assets/g-star-image.avif' -featured: false -category: 'Case Study' -tags: ['Project', 'Portfolio', 'Template'] ---- - -## Project Overview - -This is a template for creating detailed case studies of your work. Case studies are an excellent way to showcase not just the final result, but your process, problem-solving approach, and the value you delivered. - -### Quick Facts - -- **Client**: Client Name -- **Role**: Your Role -- **Timeline**: Project Duration -- **Technologies**: List of tools/technologies used -- **Team Size**: Number of collaborators -- **Deliverables**: What was produced - -## The Challenge - -Start by describing the problem or opportunity. What was the client trying to achieve? What constraints or requirements existed? - -> "This section should clearly articulate the why behind the project. Context is crucial for demonstrating your understanding of business and creative objectives." - -### Key Requirements -- Requirement or constraint 1 -- Requirement or constraint 2 -- Requirement or constraint 3 - -## The Approach - -Explain your methodology and creative/technical process. How did you tackle the challenge? - -### Research & Planning -Describe any research, competitive analysis, or planning phase. What insights informed your approach? - -### Design & Development -Walk through your design decisions and technical implementation. Include: -- Design explorations -- Technical architecture -- Tools and workflows -- Iterations and refinements - -### Collaboration -If this was a team effort, describe how you collaborated and what your specific contributions were. - -## Technical Implementation - -```javascript -// Include relevant code snippets to demonstrate technical expertise -function exampleFunction() { - // This shows both your coding ability and willingness to share knowledge - return "Real code examples add credibility to case studies"; -} -``` - -For technical projects, this section can include: -- Architecture diagrams -- Performance optimizations -- Interesting technical challenges solved -- Tools or frameworks utilized - -## Results & Impact - -Quantify the results whenever possible: -- **Metric 1**: Specific improvement or outcome -- **Metric 2**: Measurable result -- **Metric 3**: Business or creative impact - -### Client Feedback - -> "Include testimonials or direct quotes from clients/stakeholders if available. Social proof is powerful." - -## Visual Documentation - - - -*Caption: Describe what this video demonstrates* - -## Lessons Learned - -Reflect on what you learned from this project: -- Technical skills developed -- Process improvements discovered -- Challenges overcome -- What you'd do differently next time - -This shows growth mindset and continuous improvement. - -## Conclusion - -Summarize the project outcome and its significance. What made this project successful? What are you most proud of? - ---- - -**Want to work together on something like this?** [Get in touch](/contact) to discuss your next project. diff --git a/src/content/blog/welcome-to-your-portfolio.mdx b/src/content/blog/welcome-to-your-portfolio.mdx index bc3da06..e637135 100644 --- a/src/content/blog/welcome-to-your-portfolio.mdx +++ b/src/content/blog/welcome-to-your-portfolio.mdx @@ -1,76 +1,54 @@ --- -title: 'Welcome to Your New Portfolio' -description: 'This is a starter template for your Astro-based portfolio and blog. Learn about the features and capabilities of this template.' +title: 'Example Blog Post' +description: 'This is an example blog post showing the content schema and MDX capabilities.' pubDate: 'Dec 27 2024' heroImage: '../../assets/nicholai-medium-portrait.avif' featured: true -category: 'Getting Started' -tags: ['Template', 'Introduction', 'Guide'] +category: 'Example' +tags: ['Template', 'Example'] --- -## Welcome! +## Example Post -This is your new portfolio and blog, built with **Astro**, **React**, and **Tailwind CSS**. This template provides everything you need to showcase your work, share your thoughts, and connect with your audience. +This is an example blog post demonstrating the MDX content structure. -## Key Features +### Frontmatter Schema -### Content Management -- **MDX Support**: Write blog posts using Markdown with embedded React components -- **Type-Safe Content Collections**: Schema validation ensures your content is always properly structured -- **Flexible Taxonomies**: Organize posts with categories and tags +Required fields: +- `title` - Post title +- `description` - Brief description +- `pubDate` - Publication date -### Blog Functionality -- **Featured Posts**: Highlight your best work on the blog index -- **Related Posts**: Automatic suggestions based on categories and tags -- **Reading Time**: Calculated automatically from word count -- **Table of Contents**: Auto-generated navigation for long-form content -- **Post Navigation**: Easy browsing between previous and next posts +Optional fields: +- `heroImage` - Header image +- `featured` - Boolean for featured posts +- `category` - Category for filtering +- `tags` - Array of tags +- `updatedDate` - Last update date -### Design System -This template includes a comprehensive industrial dark design system with: -- Carefully crafted color palettes and typography scales -- Consistent spacing and grid systems -- Professional component patterns -- Smooth animations and transitions +### Content Features -All design tokens are documented in `dev/design.json` for easy customization. +You can use standard Markdown: -### Performance & SEO -- **Cloudflare Pages Deployment**: Global CDN for lightning-fast load times -- **Optimized Images**: AVIF format support with conversion utilities -- **Structured Data**: SEO-optimized with JSON-LD schemas -- **RSS Feed**: Keep your audience updated automatically -- **Reading Progress**: Visual indicator for long-form content +- Lists +- **Bold text** +- *Italic text* +- `Code snippets` -## Customization - -This template is designed to be easily customized: - -1. **Content**: Update the files in `src/content/` to add your own bio, experience, skills, and projects -2. **Design**: Modify `dev/design.json` to adjust colors, typography, and spacing -3. **Configuration**: Edit `template.config.json` or use the setup script to update site-wide settings -4. **Components**: All UI components are in `src/components/` for easy modification - -## Utilities Included - -This template comes with helpful utility scripts: - -```bash -pnpm convert:avif:all # Convert images to AVIF format -pnpm commit # AI-powered git commit messages -pnpm cf-typegen # Generate Cloudflare types +```javascript +// Code blocks with syntax highlighting +const example = "Hello World"; ``` -## Getting Started +And embed HTML/JSX: -Check out the other template posts to learn more: -- **Sample Case Study**: See how to structure project documentation -- **Writing Your First Post**: Learn about MDX features and content authoring +
+ Custom styled content +
-Ready to make this portfolio your own? Start by customizing the content in `src/content/sections/` to tell your story. +### Images and Media -## What's Next? +Images from `src/assets/` are processed by Astro. +Static files from `public/media/` are served as-is. -Explore the codebase, make it your own, and start creating. The architecture is designed to be intuitive, with clear separation between content, components, and configuration. - -Happy building! +Replace this example post with your own content. diff --git a/src/content/blog/writing-your-first-post.mdx b/src/content/blog/writing-your-first-post.mdx deleted file mode 100644 index 8f05278..0000000 --- a/src/content/blog/writing-your-first-post.mdx +++ /dev/null @@ -1,195 +0,0 @@ ---- -title: 'Writing Your First Blog Post' -description: 'Learn how to create and publish blog posts using MDX, including examples of all the content types you can include: images, videos, code blocks, and more.' -pubDate: 'Dec 15 2024' -heroImage: '../../assets/PENCIL_1.3.1_wipe.avif' -featured: false -category: 'Tutorial' -tags: ['Guide', 'MDX', 'Writing', 'Tutorial'] ---- - -## Getting Started with MDX - -MDX combines the simplicity of Markdown with the power of React components. This means you can write naturally in Markdown while embedding interactive elements when needed. - -## Frontmatter - -Every blog post starts with frontmatter - the YAML metadata at the top of the file: - -```yaml ---- -title: 'Your Post Title' -description: 'A brief description for SEO and previews' -pubDate: 'Dec 27 2024' -heroImage: '../../assets/your-image.avif' -featured: false -category: 'Tutorial' -tags: ['Tag1', 'Tag2', 'Tag3'] ---- -``` - -### Required Fields -- `title`: The post title (shown in browser tab, social shares, etc.) -- `description`: SEO description and preview text -- `pubDate`: Publication date (used for sorting) - -### Optional Fields -- `heroImage`: Header image (relative path from `src/assets/` or absolute from `public/`) -- `featured`: Set to `true` to feature this post on the blog index -- `category`: Primary category for organization -- `tags`: Array of tags for discovery and related posts -- `updatedDate`: If you revise the post significantly - -## Text Formatting - -Standard Markdown formatting works as expected: - -**Bold text** using `**bold**` -*Italic text* using `*italic*` -~~Strikethrough~~ using `~~strikethrough~~` - -### Headings - -Use `##` for main sections (h2), `###` for subsections (h3), and so on. The table of contents is automatically generated from your headings. - -## Links and References - -Create links using `[link text](URL)` syntax: -- [Internal page links](/contact) -- [External links](https://astro.build) -- [Link to another post](/blog/sample-case-study) - -## Lists - -### Unordered Lists -- First item -- Second item - - Nested item - - Another nested item -- Third item - -### Ordered Lists -1. Step one -2. Step two -3. Step three - -### Checklists -- [x] Completed task -- [ ] Pending task -- [ ] Another pending task - -## Code Blocks - -Inline code uses single backticks: `const example = true` - -Multi-line code blocks use triple backticks with language specification: - -```javascript -// JavaScript example -function greet(name) { - console.log(`Hello, ${name}!`); -} - -greet("World"); -``` - -```css -/* CSS example */ -.example { - color: var(--color-primary); - font-size: 1.125rem; - transition: all 0.3s ease; -} -``` - -```bash -# Shell commands -pnpm dev -pnpm build -pnpm deploy -``` - -## Blockquotes - -Use `>` for blockquotes: - -> "This is a blockquote. Great for highlighting important points, testimonials, or notable quotes." - -> Multi-line blockquotes work too. -> Just prefix each line with `>`. - -## Images - -Reference images from `src/assets/` (processed by Astro) or `public/` (served as-is): - -```markdown -![Alt text](../../assets/your-image.avif) -![Alt text](/media/your-image.jpg) -``` - -Images in `src/assets/` get automatically optimized. Use the AVIF conversion utility for best results: - -```bash -pnpm convert:avif:all -``` - -## Videos - -Embed videos using HTML video tags: - -```html - -``` - - - -## Tables - -Create tables using pipe syntax: - -| Feature | Supported | Notes | -|---------|-----------|-------| -| MDX | ✓ | Full support | -| Images | ✓ | AVIF recommended | -| Videos | ✓ | MP4/WebM | -| Components | ✓ | React components | - -## Horizontal Rules - -Create section dividers with three dashes: - ---- - -Like that! - -## Tips for Great Posts - -1. **Use descriptive headings** - They create the table of contents -2. **Add hero images** - Visual appeal matters -3. **Tag appropriately** - Helps with discovery and related posts -4. **Write good descriptions** - They're used for SEO and previews -5. **Use code examples** - Share knowledge and demonstrate expertise -6. **Break up long sections** - Images, quotes, and code blocks add visual variety -7. **Proofread** - Typos undermine credibility - -## Publishing Your Post - -1. Create your `.mdx` file in `src/content/blog/` -2. Write your content with proper frontmatter -3. Add any images to `src/assets/` or `public/media/` -4. Preview with `pnpm dev` -5. Build and deploy with `pnpm deploy` - -The blog index automatically updates, related posts are generated, and your RSS feed includes the new content. - -## What's Next? - -Now that you know the basics, start writing! Share your expertise, document your projects, or write tutorials to help others. Your unique perspective is valuable. - -Happy writing! diff --git a/src/content/pages/contact.mdx b/src/content/pages/contact.mdx deleted file mode 100644 index a44bba8..0000000 --- a/src/content/pages/contact.mdx +++ /dev/null @@ -1,32 +0,0 @@ ---- -pageTitleLine1: "Get In" -pageTitleLine2: "Touch" -availabilityText: "Available for new projects and collaborations. Let's work together." -email: "your@email.com" -location: "Your City, State" -locationCountry: "Your Country" -coordinates: "00.0000° N, 00.0000° W" -socialLinks: - - name: "LinkedIn" - url: "https://linkedin.com/in/yourprofile" - - name: "GitHub" - url: "https://github.com/yourusername" - - name: "Twitter" - url: "https://twitter.com/yourhandle" -formLabels: - transmissionUplink: "Contact Form" - name: "/// Your Name" - email: "/// Your Email" - subject: "/// Subject" - message: "/// Message" - submit: "Send Message" -subjectOptions: - - value: "project" - label: "Project Inquiry" - - value: "collab" - label: "Collaboration" - - value: "job" - label: "Job Opportunity" - - value: "other" - label: "Other" ---- diff --git a/src/content/sections/experience.mdx b/src/content/sections/experience.mdx deleted file mode 100644 index 31108a0..0000000 --- a/src/content/sections/experience.mdx +++ /dev/null @@ -1,35 +0,0 @@ ---- -sectionTitle: "Experience" -sectionSubtitle: "History" -sectionLabel: "/// Your professional journey." -description: "" -entries: - - systemId: "SYS.01" - status: "ACTIVE" - dates: "2020 — PRESENT" - company: "Your Current Company" - role: "Your Role" - tags: - - "Tag 1" - - "Tag 2" - - "Tag 3" - description: "Brief description of your role and responsibilities at this position. What do you do day-to-day? What are you known for?" - achievements: - - label: "Projects" - text: "List notable projects, clients, or achievements here. This can be a comma-separated list." - - label: "Technologies" - text: "Key technologies or tools you use in this role." - link: - url: "https://example.com" - text: "Visit Company Website" - - systemId: "SYS.02" - status: "DAEMON" - dates: "2015 — 2020" - company: "Previous Company" - role: "Previous Role" - description: "Description of your previous role. You can add as many experience entries as you need. The 'status' field can be 'ACTIVE', 'DAEMON', or other custom values." - tags: - - "Skill A" - - "Skill B" - - "Skill C" ---- diff --git a/src/content/sections/featured-project.mdx b/src/content/sections/featured-project.mdx deleted file mode 100644 index b6d6aa2..0000000 --- a/src/content/sections/featured-project.mdx +++ /dev/null @@ -1,20 +0,0 @@ ---- -role: "Your Role" -client: "Client Name" -year: "2024" -region: "Global" -projectTitle: "Project" -projectSubtitle: "Name" -projectDescription: "Brief description of your featured project. What made it special? What was your contribution? This appears on your homepage to showcase your best work." -stats: - - label: "Stat Label 1" - value: "Stat Value 1" - - label: "Stat Label 2" - value: "Stat Value 2" - - label: "Technologies" - value: "Tool / Stack / Framework" - - label: "Stat Label 3" - value: "Stat Value 3" -videoUrl: "/media/placeholder-video.mp4" -linkUrl: "/blog/sample-case-study/" ---- diff --git a/src/content/sections/hero.mdx b/src/content/sections/hero.mdx deleted file mode 100644 index 1a99ae5..0000000 --- a/src/content/sections/hero.mdx +++ /dev/null @@ -1,8 +0,0 @@ ---- -headlineLine1: "YOUR NAME" -headlineLine2: "HERE" -portfolioYear: "Portfolio 2025" -location: "Your City, State" -locationLabel: "Location" -bio: "Your professional bio goes here. Describe your expertise, what you do, and what makes you unique. This appears on your homepage hero section." ---- diff --git a/src/content/sections/skills.mdx b/src/content/sections/skills.mdx deleted file mode 100644 index b9eb37b..0000000 --- a/src/content/sections/skills.mdx +++ /dev/null @@ -1,22 +0,0 @@ ---- -sectionTitle: "Technical" -sectionSubtitle: "Arsenal" -description: "A look at your skills and specialties." -skills: - - id: "01" - domain: "Your Primary Skill" - tools: "Tool 1 • Tool 2 • Tool 3 • Tool 4" - proficiency: "Expert" - - id: "02" - domain: "Your Secondary Skill" - tools: "Tool A • Tool B • Tool C • Tool D" - proficiency: "Advanced" - - id: "03" - domain: "Another Specialty" - tools: "Software X • Software Y • Software Z" - proficiency: "Specialist" - - id: "04" - domain: "Additional Skill" - tools: "Framework 1 • Framework 2 • Platform 3" - proficiency: "Proficient" ---- diff --git a/src/layouts/BlogPost.astro b/src/layouts/BlogPost.astro index f841954..71ecc5e 100644 --- a/src/layouts/BlogPost.astro +++ b/src/layouts/BlogPost.astro @@ -1,30 +1,8 @@ --- -import type { CollectionEntry } from 'astro:content'; import type { ImageMetadata } from 'astro'; import BaseLayout from './BaseLayout.astro'; import FormattedDate from '../components/FormattedDate.astro'; -import ReadingProgress from '../components/ReadingProgress.astro'; -import TableOfContents from '../components/TableOfContents.astro'; -import PostNavigation from '../components/PostNavigation.astro'; -import RelatedPosts from '../components/RelatedPosts.astro'; import { Image } from 'astro:assets'; -import { SOCIAL_LINKS } from '../consts'; - -interface NavPost { - title: string; - href: string; - heroImage?: ImageMetadata; -} - -interface RelatedPost { - title: string; - description: string; - pubDate: Date; - heroImage?: ImageMetadata; - category?: string; - tags?: string[]; - href: string; -} interface Props { title: string; @@ -34,10 +12,6 @@ interface Props { heroImage?: ImageMetadata; category?: string; tags?: string[]; - headings?: Array<{ depth: number; slug: string; text: string }>; - prevPost?: NavPost; - nextPost?: NavPost; - relatedPosts?: RelatedPost[]; readTime?: string; } @@ -49,224 +23,78 @@ const { heroImage, category, tags, - headings = [], - prevPost, - nextPost, - relatedPosts = [], - readTime = '5 min read', + readTime, } = Astro.props; - -// Article structured data (JSON-LD) -// References the canonical Person @id from BaseLayout for knowledge graph linking -const articleSchema = { - "@context": "https://schema.org", - "@type": "Article", - "headline": title, - "description": description, - "datePublished": pubDate.toISOString(), - "dateModified": (updatedDate || pubDate).toISOString(), - "author": { - "@id": `${SOCIAL_LINKS.website}/#person` - }, - "publisher": { - "@id": `${SOCIAL_LINKS.website}/#person` - }, - "mainEntityOfPage": { - "@type": "WebPage", - "@id": Astro.url.href - }, - ...(heroImage && { "image": new URL(heroImage.src, Astro.url).toString() }), - ...(category && { "articleSection": category }), - ...(tags && tags.length > 0 && { "keywords": tags.join(", ") }) -}; --- - - - diff --git a/src/pages/index.astro b/src/pages/index.astro index bdc3377..b33f3bd 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -1,71 +1,29 @@ --- import BaseLayout from '../layouts/BaseLayout.astro'; -import Hero from '../components/sections/Hero.astro'; -import Experience from '../components/sections/Experience.astro'; -import FeaturedProject from '../components/sections/FeaturedProject.astro'; -import Skills from '../components/sections/Skills.astro'; -import { getEntry } from 'astro:content'; - -// Fetch all section content -const heroEntry = await getEntry('sections', 'hero'); -const experienceEntry = await getEntry('sections', 'experience'); -const skillsEntry = await getEntry('sections', 'skills'); -const featuredProjectEntry = await getEntry('sections', 'featured-project'); - -// Extract content from entries -const heroContent = { - headlineLine1: heroEntry.data.headlineLine1 || '', - headlineLine2: heroEntry.data.headlineLine2 || '', - portfolioYear: heroEntry.data.portfolioYear || '', - location: heroEntry.data.location || '', - locationLabel: heroEntry.data.locationLabel || '', - bio: heroEntry.data.bio || '', -}; - -const experienceContent = { - sectionTitle: experienceEntry.data.sectionTitle || '', - sectionSubtitle: experienceEntry.data.sectionSubtitle || '', - sectionLabel: experienceEntry.data.sectionLabel || '', - description: experienceEntry.data.description || '', - entries: experienceEntry.data.entries || [], -}; - -const skillsContent = { - sectionTitle: skillsEntry.data.sectionTitle || '', - sectionSubtitle: skillsEntry.data.sectionSubtitle || '', - description: skillsEntry.data.description || '', - skills: skillsEntry.data.skills || [], -}; - -const featuredProjectContent = { - role: featuredProjectEntry.data.role || '', - client: featuredProjectEntry.data.client || '', - year: featuredProjectEntry.data.year || '', - region: featuredProjectEntry.data.region || '', - projectTitle: featuredProjectEntry.data.projectTitle || '', - projectSubtitle: featuredProjectEntry.data.projectSubtitle || '', - projectDescription: featuredProjectEntry.data.projectDescription || '', - stats: featuredProjectEntry.data.stats || [], - videoUrl: featuredProjectEntry.data.videoUrl || '', - linkUrl: featuredProjectEntry.data.linkUrl || '', -}; +import { SITE_TITLE, SITE_DESCRIPTION } from '../consts'; --- - - + +
+

{SITE_TITLE}

+

{SITE_DESCRIPTION}

- - - - - - -
-
-
- - - diff --git a/template.config.json b/template.config.json deleted file mode 100644 index db5ab8f..0000000 --- a/template.config.json +++ /dev/null @@ -1,65 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft-07/schema#", - "description": "Template configuration for personalizing your Astro portfolio/blog", - - "site": { - "title": "Your Name — Your Profession", - "description": "Your professional description here. Describe what you do, who you work with, and what makes you unique.", - "url": "https://yoursite.com", - "author": "Your Name" - }, - - "branding": { - "initials": "YN", - "year": "2025", - "backgroundText": "NAME", - "companyName": "Your Company" - }, - - "personal": { - "firstName": "Your", - "lastName": "Name", - "fullName": "Your Name", - "jobTitle": "Your Profession", - "bio": "Your professional bio goes here. Describe your expertise, what you do, and what makes you unique.", - "email": "your@email.com", - "location": "Your City, State", - "locationCountry": "Your Country", - "coordinates": "00.0000° N, 00.0000° W" - }, - - "social": { - "linkedin": "https://linkedin.com/in/yourprofile", - "github": "https://github.com/yourusername", - "twitter": "https://twitter.com/yourhandle", - "twitterHandle": "@yourhandle", - "website": "https://yoursite.com" - }, - - "seo": { - "keywords": ["Your Skill 1", "Your Skill 2", "Your Skill 3"], - "serviceTypes": ["Service 1", "Service 2", "Service 3"], - "companyUrl": "https://example.com" - }, - - "cloudflare": { - "projectName": "astro-portfolio-template" - }, - - "deployment": { - "platform": "cloudflare", - "notes": "This template is configured for Cloudflare Pages by default" - }, - - "_instructions": { - "usage": "Edit this file with your information, then run 'node init-template.js --config' to apply changes", - "fields": { - "site.url": "Your production URL (used for sitemaps, RSS, canonical URLs)", - "branding.initials": "2-3 character initials for navigation branding (e.g., 'JD' for John Doe)", - "branding.backgroundText": "Large decorative text in footer (usually surname or brand name)", - "personal.coordinates": "Optional. GPS coordinates for location (format: 'XX.XXXX° N, XX.XXXX° W')", - "social": "Add or remove social platforms as needed", - "cloudflare.projectName": "Cloudflare Pages project name (lowercase, hyphens allowed)" - } - } -}