Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 37 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,13 @@ thiloho.github.io/
## Tech Stack Details

### Core Framework

- **Astro 5.14.6**: Static site generator with component islands architecture
- **TypeScript**: Strict mode enabled (`extends: "astro/tsconfigs/strict"`)
- **Node.js**: ES Modules (`"type": "module"`)

### Styling

- **Tailwind CSS 4.1.14**: Utility-first CSS framework
- **@tailwindcss/typography**: Typography plugin for prose content
- **Custom Fonts**:
Expand All @@ -86,23 +88,27 @@ thiloho.github.io/
- **Tiempos Headline**: Serif variant for headlines (weights: 300-900)

### Markdown Processing

- **markdown-it**: Markdown parser
- **rehype-slug**: Automatically add IDs to headings
- **rehype-autolink-headings**: Make headings linkable (wrap behavior)
- **Custom rehype plugin**: `rehypeWrapTables` wraps tables in scrollable divs
- **Syntax highlighting**: GitHub Dark theme via Shiki

### Content Management

- **Content Collections**: Defined in `src/content.config.ts`
- **blog**: Markdown files with frontmatter (title, description, pubDate, modDate)
- **tracks**: JSON file with music track data (id, title, youtubeLink, artist, album)

### Integrations

- **@astrojs/sitemap**: Automatic sitemap generation
- **@astrojs/rss**: RSS feed support
- **sanitize-html**: HTML sanitization

### Development Tools

- **Prettier**: Code formatting with plugins for Astro and Tailwind
- **Nix**: Development environment and server deployment

Expand All @@ -124,11 +130,13 @@ nix run .#deploy-server # Deploy to remote NixOS server
```

### Starting Development

1. Install dependencies: `npm install`
2. Start dev server: `npm run dev`
3. Open browser to `http://localhost:4321`

### Building for Production

1. Run build: `npm run build`
2. Output is in `dist/` directory
3. Preview with: `npm run preview`
Expand All @@ -140,12 +148,13 @@ nix run .#deploy-server # Deploy to remote NixOS server
Blog posts are stored in `src/content/blog/` with each post in its own directory containing a `index.md` file.

**Required frontmatter structure**:

```markdown
---
title: "Your Post Title"
description: "A brief description of the post"
pubDate: "2025-01-04"
modDate: "2025-04-27" # Optional
modDate: "2025-04-27" # Optional
---

## Your Content Here
Expand All @@ -154,6 +163,7 @@ Write your blog post content using Markdown...
```

**Blog post conventions**:

- Each post lives in its own directory: `src/content/blog/post-slug/index.md`
- Use kebab-case for directory names (e.g., `nixos-with-ext4-and-luks`)
- Include high-quality images in the same directory as the post
Expand All @@ -163,6 +173,7 @@ Write your blog post content using Markdown...
### Adding Music Tracks

Edit `src/content/tracks.json` following this schema:

```json
{
"id": 1,
Expand All @@ -176,30 +187,35 @@ Edit `src/content/tracks.json` following this schema:
## Code Conventions

### TypeScript

- Use strict TypeScript configuration
- Define types explicitly where needed
- Leverage Astro's built-in type system

### Astro Components

- Use `.astro` file extension
- Component names should be PascalCase (e.g., `TableOfContents.astro`)
- Place reusable components in `src/components/`
- Use layouts for page templates (`src/layouts/`)

### Styling

- Use Tailwind utility classes for styling
- Custom styles go in `src/styles/global.css`
- Follow the custom variant pattern: `@custom-variant dark (&:where(.dark, .dark *))`
- Apply responsive design with Tailwind's breakpoint prefixes

### Formatting

- Always run `npm run format` before committing
- Prettier automatically formats:
- Astro files (via `prettier-plugin-astro`)
- Tailwind classes (via `prettier-plugin-tailwindcss`)
- Configuration is in `.prettierrc`

### File Naming

- Components: PascalCase (e.g., `Header.astro`)
- Pages: kebab-case or descriptive names (e.g., `index.astro`, `legal-disclosure.astro`)
- Content directories: kebab-case (e.g., `nixos-with-ext4-and-luks`)
Expand All @@ -208,19 +224,22 @@ Edit `src/content/tracks.json` following this schema:
## Deployment

### GitHub Pages

- **Automatic deployment** on push to `main` branch
- Workflow file: `.github/workflows/deploy.yml`
- Uses official Astro GitHub Action (`withastro/action@v3`)
- Custom domain: thilohohlt.com (configured via `public/CNAME`)

### Deployment Process

1. Push to `main` branch
2. GitHub Actions builds the site
3. Astro generates static files
4. Deploy to GitHub Pages
5. Site available at https://thilohohlt.com

### Build Configuration

- Site URL: `https://thilohohlt.com` (defined in `astro.config.ts`)
- Prefetch: All links are prefetched (`prefetchAll: true`)
- Output: Static HTML/CSS/JS in `dist/`
Expand All @@ -230,12 +249,14 @@ Edit `src/content/tracks.json` following this schema:
This repository includes Nix configuration for self-hosted services listed at https://thilohohlt.com/services.

### Nix Files

- `flake.nix`: Main Nix flake configuration
- `flake.lock`: Dependency lock file
- `server/default.nix`: NixOS system configuration
- `server/hardware-configuration.nix`: Hardware-specific settings

### Nix Workflows

```bash
# Enter development shell (provides nixd, nixfmt)
nix develop
Expand All @@ -252,7 +273,9 @@ nix run .#deploy-server
```

### IDE Support for Nix

For NixOS option completions, use the `jnoortheen.nix-ide` VSCode extension with these settings:

```json
{
"nix.enableLanguageServer": true,
Expand All @@ -270,21 +293,25 @@ For NixOS option completions, use the `jnoortheen.nix-ide` VSCode extension with
## Key Features to Preserve

### Custom Rehype Plugins

The `rehypeWrapTables` plugin wraps tables in scrollable divs for responsive design:

```typescript
const rehypeWrapTables = () => {
// Wraps tables in div.overflow-x-auto.max-w-full
// Ensures tables are scrollable on mobile
}
};
```

### Markdown Configuration

- Syntax highlighting: GitHub Dark theme
- Auto-generated heading IDs (via rehype-slug)
- Clickable headings that wrap content (via rehype-autolink-headings)
- Tables wrapped for horizontal scrolling

### Font Loading

- All fonts use `font-display: swap` for performance
- Fonts are self-hosted in `/public/fonts/`
- Font families defined in `src/styles/global.css`
Expand All @@ -303,25 +330,29 @@ const rehypeWrapTables = () => {
### Common Tasks

**Adding a new blog post**:

1. Create directory in `src/content/blog/` with kebab-case name
2. Add `index.md` with proper frontmatter
3. Write content using Markdown
4. Test with `npm run dev`

**Modifying a component**:

1. Read the component file first
2. Understand its usage across the site
3. Make targeted changes
4. Preserve existing styling patterns
5. Format with Prettier

**Updating styles**:

1. Check `src/styles/global.css` for existing patterns
2. Use Tailwind utilities when possible
3. Add custom CSS only when necessary
4. Maintain dark mode support via custom variant

**Working with NixOS configuration**:

1. Changes to server config go in `server/` directory
2. Always validate with `nix run .#eval-server` before deploying
3. Be cautious with production server changes
Expand All @@ -340,22 +371,26 @@ const rehypeWrapTables = () => {
## Troubleshooting

### Build Fails

- Check `astro.config.ts` for syntax errors
- Verify all blog posts have required frontmatter
- Ensure TypeScript types are correct
- Run `npm install` to update dependencies

### Dev Server Issues

- Port 4321 might be in use (kill the process or use `--port` flag)
- Clear `.astro/` directory and rebuild: `rm -rf .astro && npm run dev`
- Check for TypeScript errors in components

### Styling Problems

- Run `npm run format` to fix Tailwind class ordering
- Check global.css for font loading issues
- Verify Tailwind plugin is loaded in Vite config

### Content Not Appearing

- Verify content collection schema in `src/content.config.ts`
- Check frontmatter matches schema requirements
- Ensure file paths are correct
Expand Down
8 changes: 1 addition & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src/components/Button.astro
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ interface Props {
const { href, variant = "text", title, id } = Astro.props;

const baseClasses =
"border-transparent border-b-2 p-2 cursor-pointer hover:border-neutral-300 hover:bg-neutral-200 hover:dark:border-neutral-600 hover:dark:bg-neutral-700 active:bg-neutral-200 active:dark:bg-neutral-700 active:border-neutral-300 active:dark:border-neutral-600";
"border-transparent border-b-2 p-2 cursor-pointer hover:border-[var(--color-accent-cyan)] hover:bg-neutral-200 hover:dark:border-[var(--color-accent-cyan-light)] hover:dark:bg-neutral-700 active:bg-neutral-200 active:dark:bg-neutral-700 active:border-[var(--color-accent-cyan)] active:dark:border-[var(--color-accent-cyan-light)] transition-colors duration-200";
const classes = `${baseClasses} ${variant === "icon" && href ? "inline-grid place-content-center" : "inline-block"}`;
---

Expand Down
35 changes: 25 additions & 10 deletions src/components/Nav.astro
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import Icon from "./Icon.astro";
import Button from "./Button.astro";

const routes = ["blog", "tracks", "services"];
const currentPath = Astro.url.pathname;
---

<nav class="sticky top-0 z-20 max-w-none bg-white dark:bg-neutral-800">
Expand All @@ -15,18 +16,32 @@ const routes = ["blog", "tracks", "services"];
</a>
<div class="flex overflow-x-auto">
{
routes.map((route) => (
<Button href={`/${route}`}>
{route
.split(" ")
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
.join(" ")}
</Button>
))
routes.map((route) => {
const isActive = currentPath.startsWith(`/${route}`);
return (
<span class="relative">
<Button href={`/${route}`}>
{route
.split(" ")
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
.join(" ")}
</Button>
{isActive && (
<span class="absolute right-0 bottom-0 left-0 h-0.5 bg-gradient-to-r from-[var(--color-accent-blue)] to-[var(--color-accent-cyan)] dark:from-[var(--color-accent-blue-light)] dark:to-[var(--color-accent-cyan-light)]" />
)}
</span>
);
})
}
<Button id="theme-toggle" variant="icon" title="Toggle dark mode">
<Icon name="moon" class="dark:hidden" />
<Icon name="sun" class="hidden dark:block" />
<Icon
name="moon"
class="transition-colors duration-200 hover:text-[var(--color-accent-cyan)] dark:hidden"
/>
<Icon
name="sun"
class="hidden transition-colors duration-200 hover:text-[var(--color-accent-cyan-light)] dark:block"
/>
</Button>
<Button variant="icon" href="/rss.xml" title="RSS feed">
<Icon name="rss" />
Expand Down
Loading