init blog
This commit is contained in:
24
.gitignore
vendored
Normal file
24
.gitignore
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# build output
|
||||||
|
dist/
|
||||||
|
|
||||||
|
# generated types
|
||||||
|
.astro/
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
# logs
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
|
||||||
|
# environment variables
|
||||||
|
.env
|
||||||
|
.env.production
|
||||||
|
|
||||||
|
# macOS-specific files
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# jetbrains setting folder
|
||||||
|
.idea/
|
||||||
4
.vscode/extensions.json
vendored
Normal file
4
.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"recommendations": ["astro-build.astro-vscode"],
|
||||||
|
"unwantedRecommendations": []
|
||||||
|
}
|
||||||
11
.vscode/launch.json
vendored
Normal file
11
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"command": "./node_modules/.bin/astro dev",
|
||||||
|
"name": "Development server",
|
||||||
|
"request": "launch",
|
||||||
|
"type": "node-terminal"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
73
README.md
Normal file
73
README.md
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
# Technical Problem Solver's Blog
|
||||||
|
|
||||||
|
A clean, readable blog built with Astro and React. Focus on practical insights and learning notes.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **Clean Architecture**: Modular React components for article elements
|
||||||
|
- **TypeScript**: Full type safety throughout
|
||||||
|
- **MDX Support**: Write content with JSX components
|
||||||
|
- **Fast**: Static site generation with Astro
|
||||||
|
- **Readable**: Carefully designed typography and spacing
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
src/
|
||||||
|
├── components/ # Modular React components
|
||||||
|
│ ├── ArticleHeading.tsx
|
||||||
|
│ ├── ArticleParagraph.tsx
|
||||||
|
│ ├── ArticleList.tsx
|
||||||
|
│ ├── ArticleBlockquote.tsx
|
||||||
|
│ ├── BlogPostCard.tsx
|
||||||
|
│ └── Container.tsx
|
||||||
|
├── data/
|
||||||
|
│ └── blogPosts.ts # Blog post metadata
|
||||||
|
├── layouts/ # Astro layouts
|
||||||
|
│ ├── BaseLayout.astro
|
||||||
|
│ └── BlogLayout.astro
|
||||||
|
└── pages/ # Routes
|
||||||
|
├── index.astro # Home page
|
||||||
|
└── blog/ # Blog posts
|
||||||
|
├── first-note.astro
|
||||||
|
└── debugging-tips.astro
|
||||||
|
```
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install dependencies
|
||||||
|
npm install
|
||||||
|
|
||||||
|
# Start dev server
|
||||||
|
npm run dev
|
||||||
|
|
||||||
|
# Build for production
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
# Preview production build
|
||||||
|
npm run preview
|
||||||
|
```
|
||||||
|
|
||||||
|
## Writing Posts
|
||||||
|
|
||||||
|
1. Add post metadata to `src/data/blogPosts.ts`
|
||||||
|
2. Create a new `.astro` file in `src/pages/blog/`
|
||||||
|
3. Import and use the modular components
|
||||||
|
4. The post will automatically appear on the home page
|
||||||
|
|
||||||
|
## Components
|
||||||
|
|
||||||
|
The blog uses modular React components for article elements:
|
||||||
|
|
||||||
|
- `H1`, `H2`, `H3` - Headings with consistent styling
|
||||||
|
- `Paragraph`, `LeadParagraph` - Text with proper spacing
|
||||||
|
- `UL`, `OL`, `LI` - Lists with proper indentation
|
||||||
|
- `Blockquote`, `CodeBlock`, `InlineCode` - Code and quotes
|
||||||
|
|
||||||
|
## Design Philosophy
|
||||||
|
|
||||||
|
- **Readability First**: Clear typography, generous spacing
|
||||||
|
- **No Distractions**: Clean layout, focus on content
|
||||||
|
- **Practical**: Tools and techniques that work
|
||||||
|
- **Calm**: No hype, just facts
|
||||||
17
astro.config.mjs
Normal file
17
astro.config.mjs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
// @ts-check
|
||||||
|
import { defineConfig } from 'astro/config';
|
||||||
|
|
||||||
|
import react from '@astrojs/react';
|
||||||
|
import mdx from '@astrojs/mdx';
|
||||||
|
|
||||||
|
// https://astro.build/config
|
||||||
|
export default defineConfig({
|
||||||
|
site: 'https://mintel.me',
|
||||||
|
integrations: [react(), mdx()],
|
||||||
|
markdown: {
|
||||||
|
syntaxHighlight: 'shiki',
|
||||||
|
shikiConfig: {
|
||||||
|
theme: 'github-dark'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -42,6 +42,7 @@ This blog shows how I approach problems, not how pretty something looked last ye
|
|||||||
- Automation
|
- Automation
|
||||||
- Small scripts and systems
|
- Small scripts and systems
|
||||||
- Learning notes
|
- Learning notes
|
||||||
|
- FOSS
|
||||||
|
|
||||||
## Audience
|
## Audience
|
||||||
People who:
|
People who:
|
||||||
|
|||||||
6335
package-lock.json
generated
Normal file
6335
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
21
package.json
Normal file
21
package.json
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"name": "mintel-blog",
|
||||||
|
"type": "module",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"description": "Technical problem solver's blog - practical insights and learning notes",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "astro dev",
|
||||||
|
"build": "astro build",
|
||||||
|
"preview": "astro preview",
|
||||||
|
"astro": "astro"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@astrojs/mdx": "^4.3.13",
|
||||||
|
"@astrojs/react": "^4.4.2",
|
||||||
|
"@types/react": "^19.2.8",
|
||||||
|
"@types/react-dom": "^19.2.3",
|
||||||
|
"astro": "^5.16.8",
|
||||||
|
"react": "^19.2.3",
|
||||||
|
"react-dom": "^19.2.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
9
public/favicon.svg
Normal file
9
public/favicon.svg
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 128 128">
|
||||||
|
<path d="M50.4 78.5a75.1 75.1 0 0 0-28.5 6.9l24.2-65.7c.7-2 1.9-3.2 3.4-3.2h29c1.5 0 2.7 1.2 3.4 3.2l24.2 65.7s-11.6-7-28.5-7L67 45.5c-.4-1.7-1.6-2.8-2.9-2.8-1.3 0-2.5 1.1-2.9 2.7L50.4 78.5Zm-1.1 28.2Zm-4.2-20.2c-2 6.6-.6 15.8 4.2 20.2a17.5 17.5 0 0 1 .2-.7 5.5 5.5 0 0 1 5.7-4.5c2.8.1 4.3 1.5 4.7 4.7.2 1.1.2 2.3.2 3.5v.4c0 2.7.7 5.2 2.2 7.4a13 13 0 0 0 5.7 4.9v-.3l-.2-.3c-1.8-5.6-.5-9.5 4.4-12.8l1.5-1a73 73 0 0 0 3.2-2.2 16 16 0 0 0 6.8-11.4c.3-2 .1-4-.6-6l-.8.6-1.6 1a37 37 0 0 1-22.4 2.7c-5-.7-9.7-2-13.2-6.2Z" />
|
||||||
|
<style>
|
||||||
|
path { fill: #000; }
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
path { fill: #FFF; }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 749 B |
26
src/components/ArticleBlockquote.tsx
Normal file
26
src/components/ArticleBlockquote.tsx
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
interface BlockquoteProps {
|
||||||
|
children: React.ReactNode;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Blockquote: React.FC<BlockquoteProps> = ({ children, className = '' }) => (
|
||||||
|
<blockquote className={`border-l-4 border-slate-400 pl-4 italic text-slate-600 my-6 ${className}`}>
|
||||||
|
{children}
|
||||||
|
</blockquote>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const CodeBlock: React.FC<{ children: React.ReactNode; className?: string }> = ({ children, className = '' }) => (
|
||||||
|
<pre className={`bg-slate-900 text-slate-100 p-4 rounded-lg overflow-x-auto mb-4 ${className}`}>
|
||||||
|
<code className="font-mono text-sm">
|
||||||
|
{children}
|
||||||
|
</code>
|
||||||
|
</pre>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const InlineCode: React.FC<{ children: React.ReactNode; className?: string }> = ({ children, className = '' }) => (
|
||||||
|
<code className={`bg-slate-200 text-slate-900 px-1 py-0.5 rounded font-mono text-sm ${className}`}>
|
||||||
|
{children}
|
||||||
|
</code>
|
||||||
|
);
|
||||||
24
src/components/ArticleHeading.tsx
Normal file
24
src/components/ArticleHeading.tsx
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
interface HeadingProps {
|
||||||
|
children: React.ReactNode;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const H1: React.FC<HeadingProps> = ({ children, className = '' }) => (
|
||||||
|
<h1 className={`text-4xl font-bold text-slate-900 mb-4 mt-8 leading-tight ${className}`}>
|
||||||
|
{children}
|
||||||
|
</h1>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const H2: React.FC<HeadingProps> = ({ children, className = '' }) => (
|
||||||
|
<h2 className={`text-3xl font-bold text-slate-900 mb-4 mt-8 leading-snug ${className}`}>
|
||||||
|
{children}
|
||||||
|
</h2>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const H3: React.FC<HeadingProps> = ({ children, className = '' }) => (
|
||||||
|
<h3 className={`text-2xl font-bold text-slate-900 mb-4 mt-8 leading-relaxed ${className}`}>
|
||||||
|
{children}
|
||||||
|
</h3>
|
||||||
|
);
|
||||||
24
src/components/ArticleList.tsx
Normal file
24
src/components/ArticleList.tsx
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
interface ListProps {
|
||||||
|
children: React.ReactNode;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const UL: React.FC<ListProps> = ({ children, className = '' }) => (
|
||||||
|
<ul className={`list-disc list-inside text-slate-700 leading-relaxed mb-4 ml-4 ${className}`}>
|
||||||
|
{children}
|
||||||
|
</ul>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const OL: React.FC<ListProps> = ({ children, className = '' }) => (
|
||||||
|
<ol className={`list-decimal list-inside text-slate-700 leading-relaxed mb-4 ml-4 ${className}`}>
|
||||||
|
{children}
|
||||||
|
</ol>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const LI: React.FC<ListProps> = ({ children, className = '' }) => (
|
||||||
|
<li className={`mb-1 ${className}`}>
|
||||||
|
{children}
|
||||||
|
</li>
|
||||||
|
);
|
||||||
18
src/components/ArticleParagraph.tsx
Normal file
18
src/components/ArticleParagraph.tsx
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
interface ParagraphProps {
|
||||||
|
children: React.ReactNode;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Paragraph: React.FC<ParagraphProps> = ({ children, className = '' }) => (
|
||||||
|
<p className={`text-slate-700 leading-relaxed mb-4 ${className}`}>
|
||||||
|
{children}
|
||||||
|
</p>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const LeadParagraph: React.FC<ParagraphProps> = ({ children, className = '' }) => (
|
||||||
|
<p className={`text-xl text-slate-700 leading-relaxed mb-6 ${className}`}>
|
||||||
|
{children}
|
||||||
|
</p>
|
||||||
|
);
|
||||||
48
src/components/BlogPostCard.tsx
Normal file
48
src/components/BlogPostCard.tsx
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
interface BlogPostCardProps {
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
date: string;
|
||||||
|
slug: string;
|
||||||
|
tags?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const BlogPostCard: React.FC<BlogPostCardProps> = ({
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
date,
|
||||||
|
slug,
|
||||||
|
tags = []
|
||||||
|
}) => {
|
||||||
|
const formattedDate = new Date(date).toLocaleDateString('en-US', {
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'long',
|
||||||
|
day: 'numeric'
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<article className="border border-slate-200 rounded-lg p-6 hover:shadow-lg transition-shadow">
|
||||||
|
<a href={`/blog/${slug}`} className="block">
|
||||||
|
<h2 className="text-2xl font-bold text-slate-900 mb-2 hover:text-slate-700">
|
||||||
|
{title}
|
||||||
|
</h2>
|
||||||
|
<p className="text-slate-600 mb-3 leading-relaxed">
|
||||||
|
{description}
|
||||||
|
</p>
|
||||||
|
<div className="flex items-center justify-between text-sm text-slate-500">
|
||||||
|
<time dateTime={date}>{formattedDate}</time>
|
||||||
|
{tags.length > 0 && (
|
||||||
|
<div className="flex gap-2">
|
||||||
|
{tags.map(tag => (
|
||||||
|
<span key={tag} className="bg-slate-100 text-slate-700 px-2 py-1 rounded">
|
||||||
|
{tag}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</article>
|
||||||
|
);
|
||||||
|
};
|
||||||
23
src/components/Container.tsx
Normal file
23
src/components/Container.tsx
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
interface ContainerProps {
|
||||||
|
children: React.ReactNode;
|
||||||
|
className?: string;
|
||||||
|
maxWidth?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Container: React.FC<ContainerProps> = ({
|
||||||
|
children,
|
||||||
|
className = '',
|
||||||
|
maxWidth = 'max-w-4xl'
|
||||||
|
}) => (
|
||||||
|
<div className={`mx-auto px-4 ${maxWidth} ${className}`}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const ArticleContainer: React.FC<ContainerProps> = ({ children, className = '' }) => (
|
||||||
|
<Container maxWidth="max-w-3xl" className={`py-8 ${className}`}>
|
||||||
|
{children}
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
24
src/data/blogPosts.ts
Normal file
24
src/data/blogPosts.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
export interface BlogPost {
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
date: string;
|
||||||
|
slug: string;
|
||||||
|
tags: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const blogPosts: BlogPost[] = [
|
||||||
|
{
|
||||||
|
title: "Starting this blog",
|
||||||
|
description: "Why I'm writing things down in public",
|
||||||
|
date: "2024-01-15",
|
||||||
|
slug: "first-note",
|
||||||
|
tags: ["meta", "learning"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Debugging with print statements",
|
||||||
|
description: "When printf debugging is actually the right tool",
|
||||||
|
date: "2024-01-20",
|
||||||
|
slug: "debugging-tips",
|
||||||
|
tags: ["debugging", "tools"]
|
||||||
|
}
|
||||||
|
];
|
||||||
100
src/layouts/BaseLayout.astro
Normal file
100
src/layouts/BaseLayout.astro
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
---
|
||||||
|
interface Props {
|
||||||
|
title: string;
|
||||||
|
description?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { title, description = "Technical problem solver's blog - practical insights and learning notes" } = Astro.props;
|
||||||
|
|
||||||
|
// About info from context
|
||||||
|
const aboutInfo = {
|
||||||
|
name: "Marc Mintel",
|
||||||
|
role: "Technical problem solver",
|
||||||
|
email: "marc@mintel.me",
|
||||||
|
location: "Vulkaneifel, Germany"
|
||||||
|
};
|
||||||
|
---
|
||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>{title} | {aboutInfo.name}</title>
|
||||||
|
<meta name="description" content={description} />
|
||||||
|
<meta name="generator" content={Astro.generator} />
|
||||||
|
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||||
|
</head>
|
||||||
|
<body class="bg-white text-slate-900 font-sans antialiased">
|
||||||
|
<div class="min-h-screen flex flex-col">
|
||||||
|
<!-- Header with About Info -->
|
||||||
|
<header class="bg-white border-b border-slate-200 sticky top-0 z-10">
|
||||||
|
<div class="max-w-4xl mx-auto px-4 py-4">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<h1 class="text-xl font-bold text-slate-900">
|
||||||
|
<a href="/" class="hover:text-slate-700">{aboutInfo.name}</a>
|
||||||
|
</h1>
|
||||||
|
<p class="text-sm text-slate-600">{aboutInfo.role}</p>
|
||||||
|
</div>
|
||||||
|
<div class="text-right text-sm text-slate-600 hidden sm:block">
|
||||||
|
<p>{aboutInfo.email}</p>
|
||||||
|
<p>{aboutInfo.location}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<!-- Main Content -->
|
||||||
|
<main class="flex-grow">
|
||||||
|
<slot />
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<!-- Footer -->
|
||||||
|
<footer class="border-t border-slate-200 bg-slate-50">
|
||||||
|
<div class="max-w-4xl mx-auto px-4 py-6">
|
||||||
|
<p class="text-sm text-slate-600 text-center">
|
||||||
|
A public notebook of things I figured out, mistakes I made, and tools I tested.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style is:global>
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: #2563eb;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Smooth scrolling */
|
||||||
|
html {
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Focus styles for accessibility */
|
||||||
|
a:focus,
|
||||||
|
button:focus {
|
||||||
|
outline: 2px solid #2563eb;
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
133
src/layouts/BlogLayout.astro
Normal file
133
src/layouts/BlogLayout.astro
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
---
|
||||||
|
import type { CollectionEntry } from 'astro:content';
|
||||||
|
import BaseLayout from './BaseLayout.astro';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
post: CollectionEntry<'blog'>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { post } = Astro.props;
|
||||||
|
const { title, description, date, tags } = post.data;
|
||||||
|
const formattedDate = new Date(date).toLocaleDateString('en-US', {
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'long',
|
||||||
|
day: 'numeric'
|
||||||
|
});
|
||||||
|
---
|
||||||
|
|
||||||
|
<BaseLayout title={title} description={description}>
|
||||||
|
<article class="max-w-3xl mx-auto px-4 py-8">
|
||||||
|
<header class="mb-8">
|
||||||
|
<h1 class="text-4xl font-bold text-slate-900 mb-4 leading-tight">
|
||||||
|
{title}
|
||||||
|
</h1>
|
||||||
|
<div class="flex items-center justify-between text-sm text-slate-500 mb-4">
|
||||||
|
<time datetime={date}>{formattedDate}</time>
|
||||||
|
{tags && tags.length > 0 && (
|
||||||
|
<div class="flex gap-2">
|
||||||
|
{tags.map(tag => (
|
||||||
|
<span key={tag} class="bg-slate-100 text-slate-700 px-2 py-1 rounded text-xs">
|
||||||
|
{tag}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<p class="text-xl text-slate-600 leading-relaxed">
|
||||||
|
{description}
|
||||||
|
</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="prose prose-slate max-w-none">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</BaseLayout>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.prose {
|
||||||
|
line-height: 1.75;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prose h2 {
|
||||||
|
font-size: 1.875rem;
|
||||||
|
font-weight: 700;
|
||||||
|
margin-top: 2rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
color: #0f172a;
|
||||||
|
line-height: 1.3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prose h3 {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
color: #0f172a;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prose p {
|
||||||
|
margin-bottom: 1.25rem;
|
||||||
|
color: #334155;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prose ul, .prose ol {
|
||||||
|
margin-bottom: 1.25rem;
|
||||||
|
padding-left: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prose li {
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
color: #334155;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prose ul li {
|
||||||
|
list-style-type: disc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prose ol li {
|
||||||
|
list-style-type: decimal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prose blockquote {
|
||||||
|
border-left: 4px solid #94a3b8;
|
||||||
|
padding-left: 1rem;
|
||||||
|
font-style: italic;
|
||||||
|
color: #475569;
|
||||||
|
margin: 1.5rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prose code {
|
||||||
|
background-color: #f1f5f9;
|
||||||
|
color: #0f172a;
|
||||||
|
padding: 0.125rem 0.25rem;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
font-size: 0.875em;
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prose pre {
|
||||||
|
background-color: #0f172a;
|
||||||
|
color: #f1f5f9;
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
overflow-x: auto;
|
||||||
|
margin-bottom: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prose pre code {
|
||||||
|
background: none;
|
||||||
|
color: inherit;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prose a {
|
||||||
|
color: #2563eb;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prose a:hover {
|
||||||
|
color: #1d4ed8;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
79
src/pages/blog/debugging-tips.astro
Normal file
79
src/pages/blog/debugging-tips.astro
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
---
|
||||||
|
import BlogLayout from '../../layouts/BlogLayout.astro';
|
||||||
|
import { H2, H3 } from '../../components/ArticleHeading';
|
||||||
|
import { Paragraph, LeadParagraph } from '../../components/ArticleParagraph';
|
||||||
|
import { UL, LI } from '../../components/ArticleList';
|
||||||
|
import { CodeBlock, InlineCode } from '../../components/ArticleBlockquote';
|
||||||
|
|
||||||
|
const post = {
|
||||||
|
data: {
|
||||||
|
title: "Debugging with print statements",
|
||||||
|
description: "When printf debugging is actually the right tool",
|
||||||
|
date: new Date("2024-01-20"),
|
||||||
|
tags: ["debugging", "tools"]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
---
|
||||||
|
|
||||||
|
<BlogLayout post={post}>
|
||||||
|
<LeadParagraph>
|
||||||
|
Sometimes the simplest debugging tool is the best one. Print statements get a bad reputation, but they're often exactly what you need.
|
||||||
|
</LeadParagraph>
|
||||||
|
|
||||||
|
<H2>Why print statements work</H2>
|
||||||
|
|
||||||
|
<Paragraph>
|
||||||
|
Debuggers are powerful, but they change how your code runs. Print statements don't. They let you see what's actually happening in the real execution flow.
|
||||||
|
</Paragraph>
|
||||||
|
|
||||||
|
<H3>When to use them</H3>
|
||||||
|
|
||||||
|
<UL>
|
||||||
|
<LI>Quick investigation of unexpected behavior</LI>
|
||||||
|
<LI>Understanding data flow through multiple functions</LI>
|
||||||
|
<LI>Checking state at specific points in time</LI>
|
||||||
|
<LI>When setting up a debugger feels like overkill</LI>
|
||||||
|
</UL>
|
||||||
|
|
||||||
|
<H2>Make them useful</H2>
|
||||||
|
|
||||||
|
<Paragraph>
|
||||||
|
Bad print statements create noise. Good ones tell you exactly what you need to know.
|
||||||
|
</Paragraph>
|
||||||
|
|
||||||
|
<CodeBlock>
|
||||||
|
{`def process_data(data):
|
||||||
|
# Bad: What does this even mean?
|
||||||
|
print("debug 1")
|
||||||
|
|
||||||
|
# Good: Clear context and value
|
||||||
|
print(f"Processing {len(data)} items")
|
||||||
|
|
||||||
|
result = expensive_operation(data)
|
||||||
|
|
||||||
|
# Good: Show the important intermediate result
|
||||||
|
print(f"Operation result: {result}")
|
||||||
|
|
||||||
|
return result`}
|
||||||
|
</CodeBlock>
|
||||||
|
|
||||||
|
<H3>What to print</H3>
|
||||||
|
|
||||||
|
<UL>
|
||||||
|
<LI>Variable values at key points</LI>
|
||||||
|
<LI>Function entry/exit with parameters</LI>
|
||||||
|
<LI>Loop iterations with counters</LI>
|
||||||
|
<LI>Conditional branches taken</LI>
|
||||||
|
<LI>Timing information for performance</LI>
|
||||||
|
</UL>
|
||||||
|
|
||||||
|
<H2>Temporary by design</H2>
|
||||||
|
|
||||||
|
<Paragraph>
|
||||||
|
The beauty of print statements is that they're temporary. Add them, get your answer, remove them. No setup, no cleanup, no commitment.
|
||||||
|
</Paragraph>
|
||||||
|
|
||||||
|
<Paragraph>
|
||||||
|
Sometimes the most sophisticated tool is the one you can use in 5 seconds and throw away in 10.
|
||||||
|
</Paragraph>
|
||||||
|
</BlogLayout>
|
||||||
69
src/pages/blog/first-note.astro
Normal file
69
src/pages/blog/first-note.astro
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
---
|
||||||
|
import BlogLayout from '../../layouts/BlogLayout.astro';
|
||||||
|
import { H2, H3 } from '../../components/ArticleHeading';
|
||||||
|
import { Paragraph, LeadParagraph } from '../../components/ArticleParagraph';
|
||||||
|
import { UL, LI } from '../../components/ArticleList';
|
||||||
|
import { Blockquote, InlineCode } from '../../components/ArticleBlockquote';
|
||||||
|
|
||||||
|
const post = {
|
||||||
|
data: {
|
||||||
|
title: "Starting this blog",
|
||||||
|
description: "Why I'm writing things down in public",
|
||||||
|
date: new Date("2024-01-15"),
|
||||||
|
tags: ["meta", "learning"]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
---
|
||||||
|
|
||||||
|
<BlogLayout post={post}>
|
||||||
|
<LeadParagraph>
|
||||||
|
This blog is a public notebook. It's where I document things I learn, problems I solve, and tools I test.
|
||||||
|
</LeadParagraph>
|
||||||
|
|
||||||
|
<H2>Why write in public?</H2>
|
||||||
|
|
||||||
|
<Paragraph>
|
||||||
|
I forget things. Writing them down helps. Making them public helps me think more clearly and might help someone else.
|
||||||
|
</Paragraph>
|
||||||
|
|
||||||
|
<Paragraph>
|
||||||
|
The goal isn't to teach or impress. It's to document. If you find something useful here, great. If not, that's fine too.
|
||||||
|
</Paragraph>
|
||||||
|
|
||||||
|
<H2>What to expect</H2>
|
||||||
|
|
||||||
|
<UL>
|
||||||
|
<LI>Short entries, usually under 500 words</LI>
|
||||||
|
<LI>Practical solutions to specific problems</LI>
|
||||||
|
<LI>Notes on tools and workflows</LI>
|
||||||
|
<LI>Mistakes and what I learned</LI>
|
||||||
|
<LI>Occasional deep dives when needed</LI>
|
||||||
|
</UL>
|
||||||
|
|
||||||
|
<H2>How I work</H2>
|
||||||
|
|
||||||
|
<Paragraph>
|
||||||
|
My process is simple:
|
||||||
|
</Paragraph>
|
||||||
|
|
||||||
|
<UL>
|
||||||
|
<LI>I try things</LI>
|
||||||
|
<LI>I break things</LI>
|
||||||
|
<LI>I fix things</LI>
|
||||||
|
<LI>I write down what I learned</LI>
|
||||||
|
</UL>
|
||||||
|
|
||||||
|
<Blockquote>
|
||||||
|
Understanding doesn't expire. Finished projects do.
|
||||||
|
</Blockquote>
|
||||||
|
|
||||||
|
<H3>Tools I use</H3>
|
||||||
|
|
||||||
|
<Paragraph>
|
||||||
|
Mostly standard Unix tools, Python, shell scripts, and whatever gets the job done. I prefer <InlineCode>simple</InlineCode> over <InlineCode>clever</InlineCode>.
|
||||||
|
</Paragraph>
|
||||||
|
|
||||||
|
<Paragraph>
|
||||||
|
If you're building things and solving problems, maybe you'll find something useful here. If not, thanks for reading anyway.
|
||||||
|
</Paragraph>
|
||||||
|
</BlogLayout>
|
||||||
49
src/pages/index.astro
Normal file
49
src/pages/index.astro
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
---
|
||||||
|
import BaseLayout from '../layouts/BaseLayout.astro';
|
||||||
|
import { BlogPostCard } from '../components/BlogPostCard';
|
||||||
|
import { blogPosts } from '../data/blogPosts';
|
||||||
|
|
||||||
|
// Sort posts by date
|
||||||
|
const posts = [...blogPosts].sort((a, b) =>
|
||||||
|
new Date(b.date).getTime() - new Date(a.date).getTime()
|
||||||
|
);
|
||||||
|
---
|
||||||
|
|
||||||
|
<BaseLayout title="Blog" description="Technical problem solving blog - practical insights and learning notes">
|
||||||
|
<div class="max-w-4xl mx-auto px-4 py-8">
|
||||||
|
<!-- About Section -->
|
||||||
|
<section class="mb-12 pb-8 border-b border-slate-200">
|
||||||
|
<p class="text-lg text-slate-700 leading-relaxed">
|
||||||
|
I work on technical problems and build tools, scripts, and systems to solve them.
|
||||||
|
Sometimes that means code, sometimes automation, sometimes AI, sometimes something else.
|
||||||
|
The tool is secondary. The problem comes first.
|
||||||
|
</p>
|
||||||
|
<p class="text-sm text-slate-500 mt-4">
|
||||||
|
Topics: Vibe coding with AI • Debugging • Mac tools • Automation • Small scripts • Learning notes • FOSS
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Blog Posts -->
|
||||||
|
<section>
|
||||||
|
<h2 class="text-2xl font-bold text-slate-900 mb-6">Recent Notes</h2>
|
||||||
|
|
||||||
|
{posts.length === 0 ? (
|
||||||
|
<div class="text-center py-12">
|
||||||
|
<p class="text-slate-500">No posts yet. Check back soon!</p>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div class="space-y-4">
|
||||||
|
{posts.map(post => (
|
||||||
|
<BlogPostCard
|
||||||
|
title={post.title}
|
||||||
|
description={post.description}
|
||||||
|
date={post.date}
|
||||||
|
slug={post.slug}
|
||||||
|
tags={post.tags}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</BaseLayout>
|
||||||
14
tsconfig.json
Normal file
14
tsconfig.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"extends": "astro/tsconfigs/strict",
|
||||||
|
"include": [
|
||||||
|
".astro/types.d.ts",
|
||||||
|
"**/*"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"dist"
|
||||||
|
],
|
||||||
|
"compilerOptions": {
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"jsxImportSource": "react"
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user