3 Commits

Author SHA1 Message Date
thiloho
bfad268f1f Fix height 2025-12-10 22:46:16 +01:00
thiloho
70a0434236 Update tracks list 2025-12-10 22:41:35 +01:00
thiloho
323ec46753 Add loading spinner for music collection 2025-12-10 22:10:03 +01:00
4 changed files with 1298 additions and 719 deletions

View File

@@ -71,11 +71,13 @@ thiloho.github.io/
## Tech Stack Details ## Tech Stack Details
### Core Framework ### Core Framework
- **Astro 5.14.6**: Static site generator with component islands architecture - **Astro 5.14.6**: Static site generator with component islands architecture
- **TypeScript**: Strict mode enabled (`extends: "astro/tsconfigs/strict"`) - **TypeScript**: Strict mode enabled (`extends: "astro/tsconfigs/strict"`)
- **Node.js**: ES Modules (`"type": "module"`) - **Node.js**: ES Modules (`"type": "module"`)
### Styling ### Styling
- **Tailwind CSS 4.1.14**: Utility-first CSS framework - **Tailwind CSS 4.1.14**: Utility-first CSS framework
- **@tailwindcss/typography**: Typography plugin for prose content - **@tailwindcss/typography**: Typography plugin for prose content
- **Custom Fonts**: - **Custom Fonts**:
@@ -86,6 +88,7 @@ thiloho.github.io/
- **Tiempos Headline**: Serif variant for headlines (weights: 300-900) - **Tiempos Headline**: Serif variant for headlines (weights: 300-900)
### Markdown Processing ### Markdown Processing
- **markdown-it**: Markdown parser - **markdown-it**: Markdown parser
- **rehype-slug**: Automatically add IDs to headings - **rehype-slug**: Automatically add IDs to headings
- **rehype-autolink-headings**: Make headings linkable (wrap behavior) - **rehype-autolink-headings**: Make headings linkable (wrap behavior)
@@ -93,16 +96,19 @@ thiloho.github.io/
- **Syntax highlighting**: GitHub Dark theme via Shiki - **Syntax highlighting**: GitHub Dark theme via Shiki
### Content Management ### Content Management
- **Content Collections**: Defined in `src/content.config.ts` - **Content Collections**: Defined in `src/content.config.ts`
- **blog**: Markdown files with frontmatter (title, description, pubDate, modDate) - **blog**: Markdown files with frontmatter (title, description, pubDate, modDate)
- **tracks**: JSON file with music track data (id, title, youtubeLink, artist, album) - **tracks**: JSON file with music track data (id, title, youtubeLink, artist, album)
### Integrations ### Integrations
- **@astrojs/sitemap**: Automatic sitemap generation - **@astrojs/sitemap**: Automatic sitemap generation
- **@astrojs/rss**: RSS feed support - **@astrojs/rss**: RSS feed support
- **sanitize-html**: HTML sanitization - **sanitize-html**: HTML sanitization
### Development Tools ### Development Tools
- **Prettier**: Code formatting with plugins for Astro and Tailwind - **Prettier**: Code formatting with plugins for Astro and Tailwind
- **Nix**: Development environment and server deployment - **Nix**: Development environment and server deployment
@@ -124,11 +130,13 @@ nix run .#deploy-server # Deploy to remote NixOS server
``` ```
### Starting Development ### Starting Development
1. Install dependencies: `npm install` 1. Install dependencies: `npm install`
2. Start dev server: `npm run dev` 2. Start dev server: `npm run dev`
3. Open browser to `http://localhost:4321` 3. Open browser to `http://localhost:4321`
### Building for Production ### Building for Production
1. Run build: `npm run build` 1. Run build: `npm run build`
2. Output is in `dist/` directory 2. Output is in `dist/` directory
3. Preview with: `npm run preview` 3. Preview with: `npm run preview`
@@ -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. Blog posts are stored in `src/content/blog/` with each post in its own directory containing a `index.md` file.
**Required frontmatter structure**: **Required frontmatter structure**:
```markdown ```markdown
--- ---
title: "Your Post Title" title: "Your Post Title"
description: "A brief description of the post" description: "A brief description of the post"
pubDate: "2025-01-04" pubDate: "2025-01-04"
modDate: "2025-04-27" # Optional modDate: "2025-04-27" # Optional
--- ---
## Your Content Here ## Your Content Here
@@ -154,6 +163,7 @@ Write your blog post content using Markdown...
``` ```
**Blog post conventions**: **Blog post conventions**:
- Each post lives in its own directory: `src/content/blog/post-slug/index.md` - 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`) - Use kebab-case for directory names (e.g., `nixos-with-ext4-and-luks`)
- Include high-quality images in the same directory as the post - Include high-quality images in the same directory as the post
@@ -163,6 +173,7 @@ Write your blog post content using Markdown...
### Adding Music Tracks ### Adding Music Tracks
Edit `src/content/tracks.json` following this schema: Edit `src/content/tracks.json` following this schema:
```json ```json
{ {
"id": 1, "id": 1,
@@ -176,23 +187,27 @@ Edit `src/content/tracks.json` following this schema:
## Code Conventions ## Code Conventions
### TypeScript ### TypeScript
- Use strict TypeScript configuration - Use strict TypeScript configuration
- Define types explicitly where needed - Define types explicitly where needed
- Leverage Astro's built-in type system - Leverage Astro's built-in type system
### Astro Components ### Astro Components
- Use `.astro` file extension - Use `.astro` file extension
- Component names should be PascalCase (e.g., `TableOfContents.astro`) - Component names should be PascalCase (e.g., `TableOfContents.astro`)
- Place reusable components in `src/components/` - Place reusable components in `src/components/`
- Use layouts for page templates (`src/layouts/`) - Use layouts for page templates (`src/layouts/`)
### Styling ### Styling
- Use Tailwind utility classes for styling - Use Tailwind utility classes for styling
- Custom styles go in `src/styles/global.css` - Custom styles go in `src/styles/global.css`
- Follow the custom variant pattern: `@custom-variant dark (&:where(.dark, .dark *))` - Follow the custom variant pattern: `@custom-variant dark (&:where(.dark, .dark *))`
- Apply responsive design with Tailwind's breakpoint prefixes - Apply responsive design with Tailwind's breakpoint prefixes
### Formatting ### Formatting
- Always run `npm run format` before committing - Always run `npm run format` before committing
- Prettier automatically formats: - Prettier automatically formats:
- Astro files (via `prettier-plugin-astro`) - Astro files (via `prettier-plugin-astro`)
@@ -200,6 +215,7 @@ Edit `src/content/tracks.json` following this schema:
- Configuration is in `.prettierrc` - Configuration is in `.prettierrc`
### File Naming ### File Naming
- Components: PascalCase (e.g., `Header.astro`) - Components: PascalCase (e.g., `Header.astro`)
- Pages: kebab-case or descriptive names (e.g., `index.astro`, `legal-disclosure.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`) - Content directories: kebab-case (e.g., `nixos-with-ext4-and-luks`)
@@ -208,12 +224,14 @@ Edit `src/content/tracks.json` following this schema:
## Deployment ## Deployment
### GitHub Pages ### GitHub Pages
- **Automatic deployment** on push to `main` branch - **Automatic deployment** on push to `main` branch
- Workflow file: `.github/workflows/deploy.yml` - Workflow file: `.github/workflows/deploy.yml`
- Uses official Astro GitHub Action (`withastro/action@v3`) - Uses official Astro GitHub Action (`withastro/action@v3`)
- Custom domain: thilohohlt.com (configured via `public/CNAME`) - Custom domain: thilohohlt.com (configured via `public/CNAME`)
### Deployment Process ### Deployment Process
1. Push to `main` branch 1. Push to `main` branch
2. GitHub Actions builds the site 2. GitHub Actions builds the site
3. Astro generates static files 3. Astro generates static files
@@ -221,6 +239,7 @@ Edit `src/content/tracks.json` following this schema:
5. Site available at https://thilohohlt.com 5. Site available at https://thilohohlt.com
### Build Configuration ### Build Configuration
- Site URL: `https://thilohohlt.com` (defined in `astro.config.ts`) - Site URL: `https://thilohohlt.com` (defined in `astro.config.ts`)
- Prefetch: All links are prefetched (`prefetchAll: true`) - Prefetch: All links are prefetched (`prefetchAll: true`)
- Output: Static HTML/CSS/JS in `dist/` - Output: Static HTML/CSS/JS in `dist/`
@@ -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. This repository includes Nix configuration for self-hosted services listed at https://thilohohlt.com/services.
### Nix Files ### Nix Files
- `flake.nix`: Main Nix flake configuration - `flake.nix`: Main Nix flake configuration
- `flake.lock`: Dependency lock file - `flake.lock`: Dependency lock file
- `server/default.nix`: NixOS system configuration - `server/default.nix`: NixOS system configuration
- `server/hardware-configuration.nix`: Hardware-specific settings - `server/hardware-configuration.nix`: Hardware-specific settings
### Nix Workflows ### Nix Workflows
```bash ```bash
# Enter development shell (provides nixd, nixfmt) # Enter development shell (provides nixd, nixfmt)
nix develop nix develop
@@ -252,7 +273,9 @@ nix run .#deploy-server
``` ```
### IDE Support for Nix ### IDE Support for Nix
For NixOS option completions, use the `jnoortheen.nix-ide` VSCode extension with these settings: For NixOS option completions, use the `jnoortheen.nix-ide` VSCode extension with these settings:
```json ```json
{ {
"nix.enableLanguageServer": true, "nix.enableLanguageServer": true,
@@ -270,21 +293,25 @@ For NixOS option completions, use the `jnoortheen.nix-ide` VSCode extension with
## Key Features to Preserve ## Key Features to Preserve
### Custom Rehype Plugins ### Custom Rehype Plugins
The `rehypeWrapTables` plugin wraps tables in scrollable divs for responsive design: The `rehypeWrapTables` plugin wraps tables in scrollable divs for responsive design:
```typescript ```typescript
const rehypeWrapTables = () => { const rehypeWrapTables = () => {
// Wraps tables in div.overflow-x-auto.max-w-full // Wraps tables in div.overflow-x-auto.max-w-full
// Ensures tables are scrollable on mobile // Ensures tables are scrollable on mobile
} };
``` ```
### Markdown Configuration ### Markdown Configuration
- Syntax highlighting: GitHub Dark theme - Syntax highlighting: GitHub Dark theme
- Auto-generated heading IDs (via rehype-slug) - Auto-generated heading IDs (via rehype-slug)
- Clickable headings that wrap content (via rehype-autolink-headings) - Clickable headings that wrap content (via rehype-autolink-headings)
- Tables wrapped for horizontal scrolling - Tables wrapped for horizontal scrolling
### Font Loading ### Font Loading
- All fonts use `font-display: swap` for performance - All fonts use `font-display: swap` for performance
- Fonts are self-hosted in `/public/fonts/` - Fonts are self-hosted in `/public/fonts/`
- Font families defined in `src/styles/global.css` - Font families defined in `src/styles/global.css`
@@ -303,12 +330,14 @@ const rehypeWrapTables = () => {
### Common Tasks ### Common Tasks
**Adding a new blog post**: **Adding a new blog post**:
1. Create directory in `src/content/blog/` with kebab-case name 1. Create directory in `src/content/blog/` with kebab-case name
2. Add `index.md` with proper frontmatter 2. Add `index.md` with proper frontmatter
3. Write content using Markdown 3. Write content using Markdown
4. Test with `npm run dev` 4. Test with `npm run dev`
**Modifying a component**: **Modifying a component**:
1. Read the component file first 1. Read the component file first
2. Understand its usage across the site 2. Understand its usage across the site
3. Make targeted changes 3. Make targeted changes
@@ -316,12 +345,14 @@ const rehypeWrapTables = () => {
5. Format with Prettier 5. Format with Prettier
**Updating styles**: **Updating styles**:
1. Check `src/styles/global.css` for existing patterns 1. Check `src/styles/global.css` for existing patterns
2. Use Tailwind utilities when possible 2. Use Tailwind utilities when possible
3. Add custom CSS only when necessary 3. Add custom CSS only when necessary
4. Maintain dark mode support via custom variant 4. Maintain dark mode support via custom variant
**Working with NixOS configuration**: **Working with NixOS configuration**:
1. Changes to server config go in `server/` directory 1. Changes to server config go in `server/` directory
2. Always validate with `nix run .#eval-server` before deploying 2. Always validate with `nix run .#eval-server` before deploying
3. Be cautious with production server changes 3. Be cautious with production server changes
@@ -340,22 +371,26 @@ const rehypeWrapTables = () => {
## Troubleshooting ## Troubleshooting
### Build Fails ### Build Fails
- Check `astro.config.ts` for syntax errors - Check `astro.config.ts` for syntax errors
- Verify all blog posts have required frontmatter - Verify all blog posts have required frontmatter
- Ensure TypeScript types are correct - Ensure TypeScript types are correct
- Run `npm install` to update dependencies - Run `npm install` to update dependencies
### Dev Server Issues ### Dev Server Issues
- Port 4321 might be in use (kill the process or use `--port` flag) - Port 4321 might be in use (kill the process or use `--port` flag)
- Clear `.astro/` directory and rebuild: `rm -rf .astro && npm run dev` - Clear `.astro/` directory and rebuild: `rm -rf .astro && npm run dev`
- Check for TypeScript errors in components - Check for TypeScript errors in components
### Styling Problems ### Styling Problems
- Run `npm run format` to fix Tailwind class ordering - Run `npm run format` to fix Tailwind class ordering
- Check global.css for font loading issues - Check global.css for font loading issues
- Verify Tailwind plugin is loaded in Vite config - Verify Tailwind plugin is loaded in Vite config
### Content Not Appearing ### Content Not Appearing
- Verify content collection schema in `src/content.config.ts` - Verify content collection schema in `src/content.config.ts`
- Check frontmatter matches schema requirements - Check frontmatter matches schema requirements
- Ensure file paths are correct - Ensure file paths are correct

View File

@@ -19,8 +19,8 @@ const thumbnail = `https://img.youtube.com/vi/${videoId}/maxresdefault.jpg`;
<a <a
href={youtubeLink} href={youtubeLink}
class="relative mt-4 block p-4 duration-300 after:absolute after:inset-0 after:z-0 after:bg-[rgba(255,255,255,0.75)] after:content-[''] first:mt-0 hover:scale-105 dark:after:bg-[rgba(38,38,38,0.75)]" class="relative mt-4 block bg-cover bg-center p-4 duration-300 after:absolute after:inset-0 after:z-0 after:bg-[rgba(255,255,255,0.75)] after:content-[''] first:mt-0 hover:scale-105 dark:after:bg-[rgba(38,38,38,0.75)]"
style={`word-break: break-word; background-image: url('${thumbnail}'); background-size: cover; background-position: center;`} style={`word-break: break-word; background-image: url('${thumbnail}')`}
> >
<div <div
class="relative z-10 flex flex-col gap-2 text-neutral-900 dark:text-white" class="relative z-10 flex flex-col gap-2 text-neutral-900 dark:text-white"

File diff suppressed because it is too large Load Diff

View File

@@ -7,10 +7,20 @@ const tracks = await getCollection("tracks");
--- ---
<PageLayout <PageLayout
title="Tracks" title={`Tracks (${tracks.length})`}
description="My entire music playlist. It contains all kinds of songs." description="My entire music playlist. It contains all kinds of songs."
> >
<div class="not-prose"> <div id="loading-indicator" class="flex flex-col items-center gap-2">
<div
class="h-8 w-8 animate-spin rounded-full border-4 border-neutral-300 border-t-neutral-900 dark:border-neutral-600 dark:border-t-white"
>
</div>
<span
id="loading-percentage"
class="text-sm text-neutral-900 dark:text-white">0%</span
>
</div>
<div id="tracks-container" class="not-prose hidden">
{ {
tracks.map(({ data: { title, artist, album, youtubeLink } }, index) => ( tracks.map(({ data: { title, artist, album, youtubeLink } }, index) => (
<Track {title} {artist} {album} {youtubeLink} index={++index} /> <Track {title} {artist} {album} {youtubeLink} index={++index} />
@@ -18,3 +28,40 @@ const tracks = await getCollection("tracks");
} }
</div> </div>
</PageLayout> </PageLayout>
<script is:inline define:vars={{ tracks }}>
const loadThumbnails = async () => {
const thumbnailUrls = tracks.map(({ data: { youtubeLink } }) => {
const videoId = youtubeLink.split("v=")[1];
return `https://img.youtube.com/vi/${videoId}/maxresdefault.jpg`;
});
let loadedCount = 0;
const total = thumbnailUrls.length;
const percentageEl = document.getElementById("loading-percentage");
const preloadImages = thumbnailUrls.map((url) => {
return new Promise((resolve) => {
const img = new Image();
const handleComplete = () => {
loadedCount++;
const percentage = Math.round((loadedCount / total) * 100);
percentageEl.textContent = `${percentage}%`;
resolve();
};
img.addEventListener("load", handleComplete, { once: true });
img.addEventListener("error", handleComplete, { once: true });
img.src = url;
});
});
await Promise.all(preloadImages);
document.getElementById("loading-indicator").classList.add("hidden");
document.getElementById("tracks-container").classList.remove("hidden");
};
loadThumbnails();
document.addEventListener("astro:after-swap", loadThumbnails);
</script>