5 Commits

Author SHA1 Message Date
Claude
3f427cf2b5 Add cyan accent color to enhance website aesthetics
This update introduces a vibrant cyan accent color alongside the existing
blue, creating a more visually engaging design while maintaining the clean,
minimalist aesthetic.

Changes:
- Define custom cyan and blue accent colors in global.css using @theme
- Add gradient effect (blue to cyan) on h1 and h2 headings
- Add cyan accent border on code blocks for visual distinction
- Update Button component with cyan border on hover/active states
- Add gradient indicator for active navigation items
- Add colorful hover states to theme toggle icons
- Include smooth color transitions for improved UX

Both light and dark modes are fully supported with appropriate color variants.
2025-12-07 16:57:16 +00:00
f270dba48f Merge pull request #1 from thiloho/claude/claude-md-mivy6h99sunra7ul-014B6aJhToNNfzgriAXxZ6RP
Add comprehensive CLAUDE.md documentation for AI assistants
2025-12-07 17:45:36 +01:00
Claude
0679f1b809 Add comprehensive CLAUDE.md documentation for AI assistants
This documentation provides:
- Complete project overview and tech stack details
- Directory structure explanation
- Development workflows and commands
- Content management guidelines for blog posts
- Code conventions and best practices
- Deployment process documentation
- NixOS server configuration guide
- Troubleshooting tips and common tasks
2025-12-07 16:41:52 +00:00
thiloho
822fc4d0ca Fix 2025-11-21 02:55:53 +01:00
thiloho
0e576a98d8 Fix 2025-11-21 02:32:40 +01:00
8 changed files with 548 additions and 22 deletions

411
CLAUDE.md Normal file
View File

@@ -0,0 +1,411 @@
# CLAUDE.md - AI Assistant Guide for thiloho.github.io
This document provides comprehensive guidance for AI assistants working on this codebase. It covers the project structure, development workflows, and key conventions to follow.
## Project Overview
This is the source code for **thilohohlt.com**, a personal website built with the Astro web framework. The repository also includes Nix configuration files for self-hosted services and server deployment.
**Live Site**: https://thilohohlt.com
**Tech Stack**: Astro 5.14.6, Tailwind CSS 4.1.14, TypeScript, Markdown
**Deployment**: GitHub Pages (automated via GitHub Actions)
## Directory Structure
```
thiloho.github.io/
├── .github/
│ └── workflows/
│ └── deploy.yml # GitHub Actions deployment workflow
├── public/ # Static assets (served as-is)
│ ├── fonts/ # Custom font files (Styrene, Tiempos)
│ ├── favicon.ico
│ ├── CNAME # Custom domain configuration
│ └── site.webmanifest
├── server/ # NixOS server configuration
│ ├── default.nix
│ └── hardware-configuration.nix
├── src/
│ ├── components/ # Reusable Astro components
│ │ ├── Button.astro
│ │ ├── Date.astro
│ │ ├── Footer.astro
│ │ ├── Head.astro
│ │ ├── Header.astro
│ │ ├── Icon.astro
│ │ ├── Nav.astro
│ │ ├── TableOfContents.astro
│ │ └── Track.astro
│ ├── content/ # Content collections
│ │ ├── blog/ # Blog post markdown files
│ │ │ ├── custom-email-domain/
│ │ │ ├── e2e-testing-spring-boot-applications/
│ │ │ ├── nixos-with-ext4-and-luks/
│ │ │ └── privacy-focused-operating-systems/
│ │ └── tracks.json # Music tracks data
│ ├── layouts/
│ │ └── PageLayout.astro # Main page layout
│ ├── pages/ # Routes (file-based routing)
│ │ ├── blog/
│ │ │ ├── [slug].astro # Dynamic blog post pages
│ │ │ └── index.astro # Blog listing page
│ │ ├── 404.astro
│ │ ├── index.astro # Homepage
│ │ ├── legal-disclosure.astro
│ │ ├── robots.txt.ts
│ │ ├── rss.xml.ts # RSS feed
│ │ ├── services.astro
│ │ └── tracks.astro
│ ├── styles/
│ │ └── global.css # Global styles and font definitions
│ └── content.config.ts # Content collections schema
├── astro.config.ts # Astro configuration
├── flake.nix # Nix flake configuration
├── flake.lock # Nix flake lock file
├── package.json # Node.js dependencies and scripts
├── package-lock.json
├── tsconfig.json # TypeScript configuration
└── .prettierrc # Prettier configuration
```
## 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**:
- **Styrene A**: Sans-serif font for headings (weights: 100-900)
- **Styrene B**: Monospace font for code (weights: 100-900)
- **Tiempos Text**: Serif font for body text (weights: 400-700)
- **Tiempos Fine**: Serif variant (weights: 300-900)
- **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
## Development Workflows
### Essential Commands
```bash
# Development
npm run dev # Start Astro dev server (http://localhost:4321)
npm run build # Build for production
npm run preview # Preview production build
npm run format # Format code with Prettier
# Nix (if using Nix)
nix develop # Enter dev shell (includes nixd, nixfmt)
nix run .#eval-server # Validate NixOS configuration
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`
## Content Management
### Creating Blog Posts
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
---
## Your Content Here
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
- Dates in `YYYY-MM-DD` format
- `modDate` is optional (use when updating existing posts)
### Adding Music Tracks
Edit `src/content/tracks.json` following this schema:
```json
{
"id": 1,
"title": "Track Title",
"youtubeLink": "https://youtube.com/...",
"artist": "Artist Name",
"album": "Album Name"
}
```
## 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`)
- Configuration files: standard names (e.g., `astro.config.ts`)
## 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/`
## NixOS Server Configuration
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
# Validate server configuration
nix run .#eval-server
# Deploy to remote server
nix run .#deploy-server
# This will:
# 1. Evaluate the configuration
# 2. Connect to the remote server (91.98.171.83)
# 3. Build and switch to new configuration
```
### IDE Support for Nix
For NixOS option completions, use the `jnoortheen.nix-ide` VSCode extension with these settings:
```json
{
"nix.enableLanguageServer": true,
"nix.serverPath": "nil",
"nix.serverSettings": {
"nixd": {
"formatting": {
"command": ["nixfmt"]
}
}
}
}
```
## 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`
## Important Notes for AI Assistants
### When Making Changes
1. **Always read files before editing**: Use the Read tool before making changes to understand existing code structure
2. **Preserve existing conventions**: Don't introduce new patterns unless necessary
3. **Test locally**: Encourage running `npm run dev` to test changes
4. **Format before committing**: Run `npm run format` to ensure consistent code style
5. **Respect content structure**: Blog posts must have proper frontmatter and directory structure
6. **Don't over-engineer**: Keep solutions simple and focused on the requested changes
### 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
4. Test locally when possible
### Things to Avoid
- ❌ Don't create documentation files unless explicitly requested
- ❌ Don't add unnecessary comments or docstrings
- ❌ Don't refactor code that isn't related to the task
- ❌ Don't add dependencies without good reason
- ❌ Don't modify font files or licenses in `/public/fonts/`
- ❌ Don't change the deployment workflow without understanding implications
- ❌ Don't edit generated files in `.astro/` or `dist/`
## 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
- Restart dev server after adding new content files
## Resources
- **Astro Docs**: https://docs.astro.build/
- **Tailwind CSS Docs**: https://tailwindcss.com/docs
- **Nix Manual**: https://nixos.org/manual/nix/stable/
- **Site Repository**: https://github.com/thiloho/thiloho.github.io (inferred)
## Version Info
Last updated: 2025-12-07
Astro version: 5.14.6
Tailwind version: 4.1.14
Node.js: ES Modules

View File

@@ -4,6 +4,54 @@ import sitemap from "@astrojs/sitemap";
import rehypeAutolinkHeadings from "rehype-autolink-headings";
import rehypeSlug from "rehype-slug";
type RehypeNode = {
type: string;
tagName?: string;
children?: RehypeNode[];
properties?: {
[key: string]: unknown;
className?: string[];
};
};
type RehypeParent = RehypeNode & {
children: RehypeNode[];
};
type RehypeRoot = RehypeParent;
const rehypeWrapTables = () => {
const visit = (node: RehypeNode, parent: RehypeParent | null) => {
if (node.type === "element" && node.tagName === "table" && parent) {
const wrapper: RehypeNode = {
type: "element",
tagName: "div",
properties: {
className: ["overflow-x-auto", "max-w-full"],
},
children: [node],
};
const index = parent.children.indexOf(node);
if (index !== -1) {
parent.children[index] = wrapper;
}
return;
}
if (Array.isArray(node.children)) {
for (const child of node.children) {
visit(child, node as RehypeParent);
}
}
};
return (tree: RehypeRoot) => {
visit(tree, null);
};
};
export default defineConfig({
site: "https://thilohohlt.com",
@@ -27,6 +75,7 @@ export default defineConfig({
behavior: "wrap",
},
],
rehypeWrapTables,
],
},

8
package-lock.json generated
View File

@@ -4582,7 +4582,6 @@
"integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"prettier": "bin/prettier.cjs"
},
@@ -4599,7 +4598,6 @@
"integrity": "sha512-RiBETaaP9veVstE4vUwSIcdATj6dKmXljouXc/DDNwBSPTp8FRkLGDSGFClKsAFeeg+13SB0Z1JZvbD76bigJw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@astrojs/compiler": "^2.9.1",
"prettier": "^3.0.0",
@@ -5030,7 +5028,6 @@
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.40.0.tgz",
"integrity": "sha512-Noe455xmA96nnqH5piFtLobsGbCij7Tu+tb3c1vYjNbTkfzGqXqQXG3wJaYXkRZuQ0vEYN4bhwg7QnIrqB5B+w==",
"license": "MIT",
"peer": true,
"dependencies": {
"@types/estree": "1.0.7"
},
@@ -5325,8 +5322,7 @@
"version": "4.1.14",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.14.tgz",
"integrity": "sha512-b7pCxjGO98LnxVkKjaZSDeNuljC4ueKUddjENJOADtubtdo8llTaJy7HwBMeLNSSo2N5QIAgklslK1+Ir8r6CA==",
"license": "MIT",
"peer": true
"license": "MIT"
},
"node_modules/tapable": {
"version": "2.3.0",
@@ -5810,7 +5806,6 @@
"resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz",
"integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==",
"license": "MIT",
"peer": true,
"dependencies": {
"esbuild": "^0.25.0",
"fdir": "^6.4.4",
@@ -6018,7 +6013,6 @@
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
"license": "MIT",
"peer": true,
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}

View File

@@ -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"}`;
---

View File

@@ -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">
@@ -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" />

View File

@@ -18,8 +18,11 @@ I am a software developer from Germany who is passionate about building high qua
## Software
- [Arch Linux](https://archlinux.org)
- [KDE Plasma](https://kde.org/plasma-desktop)
- [Visual Studio Code](https://code.visualstudio.com)
- [Firefox Developer Edition](https://www.mozilla.org/en-US/firefox/developer)
- [Tuta Mail](https://tuta.com)
- [IntelliJ](https://www.jetbrains.com/idea)
- [KDE Plasma](https://kde.org/plasma-desktop)
- [LocalSend](https://localsend.org)
- [Mullvad VPN](https://mullvad.net)
- [qBittorrent](https://www.qbittorrent.org)
- [Tuta Mail](https://tuta.com)
- [Visual Studio Code](https://code.visualstudio.com)

View File

@@ -22,7 +22,7 @@ const { title, description, pubDate, modDate, slug } = Astro.props;
<Header {title} {pubDate} {modDate} {slug} />
<main class="flex-1 bg-white dark:bg-neutral-800">
<div
class={`relative prose prose-neutral dark:prose-invert mx-auto px-4 ${pubDate ? "pt-0" : "pt-8"} pb-16 ${Astro.originPathname === "/" ? "prose-headings:scroll-mt-12" : "prose-headings:scroll-mt-24 lg:prose-headings:scroll-mt-16"} prose-h1:font-bold prose-pre:!bg-neutral-700 prose-a:font-normal prose-a:not-in-prose-headings:text-blue-800 prose-a:not-in-prose-headings:dark:text-blue-300 prose-a:hover:no-underline prose-a:active:bg-neutral-200 prose-a:active:dark:bg-neutral-700 prose-a:in-prose-headings:font-bold prose-a:in-prose-headings:decoration-2 prose-a:in-prose-headings:no-underline prose-a:in-prose-headings:hover:underline`}
class={`relative prose prose-neutral dark:prose-invert mx-auto px-4 ${pubDate ? "pt-0" : "pt-8"} pb-16 ${Astro.originPathname === "/" ? "prose-headings:scroll-mt-12" : "prose-headings:scroll-mt-24 lg:prose-headings:scroll-mt-16"} prose-table:text-base prose-h1:font-bold prose-pre:!bg-neutral-700 prose-a:font-normal prose-a:not-in-prose-headings:text-blue-800 prose-a:not-in-prose-headings:dark:text-blue-300 prose-a:hover:no-underline prose-a:active:bg-neutral-200 prose-a:active:dark:bg-neutral-700 prose-a:in-prose-headings:font-bold prose-a:in-prose-headings:decoration-2 prose-a:in-prose-headings:no-underline prose-a:in-prose-headings:hover:underline`}
>
<slot />
</div>

View File

@@ -2,6 +2,13 @@
@plugin "@tailwindcss/typography";
@custom-variant dark (&:where(.dark, .dark *));
@theme {
--color-accent-cyan: #0891b2;
--color-accent-cyan-light: #67e8f9;
--color-accent-blue: #1e40af;
--color-accent-blue-light: #93c5fd;
}
@layer base {
body {
font-family: "Tiempos Text", serif;
@@ -16,6 +23,32 @@
font-family: "Styrene A", sans-serif;
}
/* Gradient effect on h1 and h2 */
h1,
h2 {
background: linear-gradient(
135deg,
var(--color-accent-blue) 0%,
var(--color-accent-cyan) 100%
);
background-clip: text;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-size: 100%;
}
.dark h1,
.dark h2 {
background: linear-gradient(
135deg,
var(--color-accent-blue-light) 0%,
var(--color-accent-cyan-light) 100%
);
background-clip: text;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
code,
kbd,
samp,
@@ -24,9 +57,30 @@
font-family: "Styrene B", monospace;
}
/* Accent border on code blocks */
pre {
border-left: 4px solid var(--color-accent-cyan);
}
.dark pre {
border-left-color: var(--color-accent-cyan-light);
}
mark {
@apply bg-neutral-200 text-current dark:bg-neutral-600;
}
#software + ul {
@apply flex flex-wrap gap-4 !p-0;
}
#software + ul > li {
@apply !m-0 list-none !p-0;
}
#software + ul > li a {
@apply border border-neutral-300 px-2 py-1 dark:border-neutral-600;
}
}
/* Styrene A Font Family */