Compare commits

7 Commits

Author SHA1 Message Date
a88e284c1e Remove artifacts and set language of markdown sites
All checks were successful
Deploy Stable Website / Build and Deploy (push) Successful in 46s
Deploy Dev Website / Build and Deploy (push) Successful in 42s
2026-03-22 22:19:42 +01:00
32a150c0ce Add workflow for stable
All checks were successful
Deploy Dev Website / Build and Deploy (push) Successful in 43s
Deploy Stable Website / Build and Deploy (push) Successful in 41s
2026-03-22 22:02:22 +01:00
5258627fa9 Update readme to mention astro 2026-03-22 22:00:32 +01:00
ac9ab52d63 Use repository link for projects
All checks were successful
Deploy Astro / Build and Deploy (push) Successful in 49s
2026-03-22 21:54:49 +01:00
4f16fce5fe Fix noref tag 2026-03-22 21:54:34 +01:00
d5fb608091 Use better page transistions 2026-03-22 21:54:24 +01:00
e6e17d880e Astro Projektbericht 2026-03-22 20:47:16 +01:00
16 changed files with 217 additions and 101 deletions

2
.gitattributes vendored Normal file
View File

@@ -0,0 +1,2 @@
*.md linguist-language=Astro
*.mdx linguist-language=Astro

View File

@@ -1,4 +1,4 @@
name: Deploy Astro
name: Deploy Dev Website
on:
push:
branches:
@@ -9,13 +9,13 @@ jobs:
name: Build and Deploy
runs-on: ubuntu-latest
services:
mermaid:
image: yuzutech/kroki-mermaid
kroki:
image: yuzutech/kroki
env:
KROKI_MERMAID_HOST: mermaid
# services:
# mermaid:
# image: yuzutech/kroki-mermaid
# kroki:
# image: yuzutech/kroki
# env:
# KROKI_MERMAID_HOST: mermaid
steps:
- uses: actions/checkout@v4
@@ -41,6 +41,6 @@ jobs:
username: ${{ secrets.SSH_USERNAME }}
key: ${{ secrets.SSH_KEY }}
source: "dist/*"
target: /srv/website/dev
target: /srv/website/stable
overwrite: true
strip_components: 1

View File

@@ -0,0 +1,46 @@
name: Deploy Stable Website
on:
push:
branches:
- senpai
jobs:
build-and-deploy:
name: Build and Deploy
runs-on: ubuntu-latest
# services:
# mermaid:
# image: yuzutech/kroki-mermaid
# kroki:
# image: yuzutech/kroki
# env:
# KROKI_MERMAID_HOST: mermaid
steps:
- uses: actions/checkout@v4
name: Checkout code
- name: Setup node and npm
uses: actions/setup-node@v3
with:
node-version: lts/*
- name: Install dependencies
run: npm install
- name: Check and build
run: npm run build
# env:
# KROKI_SERVER: "http://kroki:8000"
- name: Copy files via ssh
uses: appleboy/scp-action@v1
with:
host: c0ntroller.de
username: ${{ secrets.SSH_USERNAME }}
key: ${{ secrets.SSH_KEY }}
source: "dist/*"
target: /srv/website/dev
overwrite: true
strip_components: 1

View File

@@ -1,43 +1,13 @@
# Astro Starter Kit: Minimal
# Frontpage
```sh
npm create astro@latest -- --template minimal
```
[![Read the blog entry at c0ntroller.de](https://c0ntroller.de/img/read-blog.svg)](https://c0ntroller.de/portfolio/astro-rewrite/)
> 🧑‍🚀 **Seasoned astronaut?** Delete this file. Have fun!
| Stable | Dev
| ------ | ---
| [![Build Status Stable](https://git.c0ntroller.de/c0ntroller/frontpage/actions/workflows/stable.yml/badge.svg?branch=senpai)](https://c0ntroller.de/) | [![Build Status Dev](https://git.c0ntroller.de/c0ntroller/frontpage/actions/workflows/dev.yml/badge.svg?branch=dev)](https://dev.c0ntroller.de/)
## 🚀 Project Structure
This repository contains my Astro-based website.
Inside of your Astro project, you'll see the following folders and files:
When changes are made to `senpai` it will be deploy on https://c0ntroller.de
```text
/
├── public/
├── src/
│ └── pages/
│ └── index.astro
└── package.json
```
Astro looks for `.astro` or `.md` files in the `src/pages/` directory. Each page is exposed as a route based on its file name.
There's nothing special about `src/components/`, but that's where we like to put any Astro/React/Vue/Svelte/Preact components.
Any static assets, like images, can be placed in the `public/` directory.
## 🧞 Commands
All commands are run from the root of the project, from a terminal:
| Command | Action |
| :------------------------ | :----------------------------------------------- |
| `npm install` | Installs dependencies |
| `npm run dev` | Starts local dev server at `localhost:4321` |
| `npm run build` | Build your production site to `./dist/` |
| `npm run preview` | Preview your build locally, before deploying |
| `npm run astro ...` | Run CLI commands like `astro add`, `astro check` |
| `npm run astro -- --help` | Get help using the Astro CLI |
## 👀 Want to learn more?
Feel free to check [our documentation](https://docs.astro.build) or jump into our [Discord server](https://astro.build/chat).
When changes are made in the `dev` branch it will be deployed on https://dev.c0ntroller.de

View File

@@ -3,7 +3,7 @@ const currentPath = Astro.url.pathname;
import Logo from "../assets/logo.svg";
---
<nav class="main-nav glass-container">
<nav class="main-nav glass-container" style="view-transition-name:navigation;">
<div class="nav-content">
<a href="/" class="logo">
<Logo class="logo-img" />

View File

@@ -17,6 +17,7 @@ const portfolioCollection = defineCollection({
summary: z.string(),
tags: z.array(z.string()).optional(),
pubDate: z.date(),
repository: z.url().regex(/git/).optional(),
}),
});

View File

@@ -2,10 +2,65 @@
title: Astro Rewrite
summary: "Next.js entwickelt sich rasend - zu schnell für mich, um Features und Sicherheitsupdates immer zu verfolgen. Und React für einen Blog? Unnötig. Eine einfache statische Lösung aber mit Komponenten? Astro ist genau das. Also: Willkommen zum neuen Anstrich in Astro!"
repository: "https://git.c0ntroller.de/c0ntroller/frontpage"
pubDate: 2023-04-03
pubDate: 2025-03-22
tags:
- Astro
- SSR
- Webentwicklung
---
Next.js war einfach Magie.
Als ich React lernte und noch überlegte, wie ich am besten ein Backend dazu stelle, fragete ein Freund, warum ich nicht einfach Next.js nehme.
Und das war magisch.
Denn ich konnte mein Gelerntes nutzen und hatte trotzdem direkt ein Backend mit dabei.
Ich hab mir darin nicht nur ein kleines Projekt gebaut, sondern später auch meine erste Website.
Die Struktur war einfach: Eine Datei in `app` anlegen und... Moment.
Stimmt gar nicht mehr.
Also: Eine Datei in `pages` anlegen. Oder doch in `app`?
Beides Ok.
Achso, außer dass `app` dann deprecated wurde.
Next.js war für mich die erste Berührung mit dem JavaScript Ökosystem und plötzlich verstand ich die ganzen Memes.
Dann kam auch plötzlich die Nacht vom dependebot und ich hatte plötzlich 20 PRs, die alle Next.js Updates waren.
Nur, dass ab irgendeinem Punkt man plötzlich sich mit komplizierteren Updates rumschlagen musste und nicht einfach nur `npm audit fix` machen konnte.
Also brauchte es eine Alternative.
## Sinneswandel
React ist cool.
Ziemlich performant für das, was es macht, sehr flexibel und einmal gelernt kann man auch andere Frameworks leicht lernen.
Aber wofür braucht eine Homepage eine komplexe reaktive Komponente?
Naja, eigentlich braucht sie das nicht.
Aber es war an dem Punkt für mich die einzige Möglichkeit absiets von Vanilla HTML5, CSS und JS zu arbeiten.
Gleichzeitig merkte ich bei immer mehr Seiten, dass hier Unmengen an Rechenleistung in JavaScript nur für hübsche Effekte drauf ging.
Ich bin sehr für schöne Effekte, aber immer mehr fand ich, dass man als **guter** Web Entwickler eigentlich so wenig JavaScript wie möglich einsetzen sollte.
JavaScript Frameworks sind im Normalfall aber das genaue Gegenteil.
Ich wollte meine Seite also neu machen und hatte eien Vision, die nicht ganz erreichbar erschien.
Und dann fand ich ein Framework, dass eigentlich genau das war, was ich suchte: Astro.
## Astro
Astro zeichnet sich dadurch aus, dass es komplett statische Seiten generiert.
Falls man das braucht, kann man aber auch Server Side Rendering nutzen, oder sogar beides gleichzeitig.
Islands gibt es sogar für die verschiedensten Frameworks.
Aber all das brauchte ich gar nicht.
Eine cleane modulare Webseite aufbauen, mit verschachtelten Komponenten, scoped CSS und ohne JavaScript, dass man nicht selbst schreibt.
Das war genau das, was ich suchte.
Und so habe ich mich an die Arbeit gemacht, meine Seite neu zu bauen.
Und gebaut.
Und gebaut.
Und gebaut...
Tja ich fing an zu arbeiten und plötzlich war da gar nicht mehr so viel Zeit.
Aber nach wirklich langer Zeit und viel Arbeit ist es jetzt endlich soweit - die neue Seite ist hübsch, sehr schnell, hat vollen Score bei Lighthouse und ist komplett in Astro gebaut.
Ich bin sehr zufrieden damit.
Vielleicht kommt irgendwann ja nochmal wieder ein Terminal dazu, dass ich früher als Webseite hatte.
Aber vorerst genieße ich es erstmal wieder eine eine hübsche und schnelle Seite zu haben.

View File

@@ -15,10 +15,10 @@ const { entry } = Astro.props;
const { Content } = await render(entry);
---
<BaseLayout title={entry.data.title} theme="blog">
<div class="glass-container header">
<h1>{entry.data.title}</h1>
<div class="glass-container header" style={`view-transition-name: blog-header-${entry.id};`}>
<h1 style={`view-transition-name: blog-headline-${entry.id};`}>{entry.data.title}</h1>
<div class="meta">
<time>{entry.data.pubDate.toLocaleDateString()}</time>
<time style={`view-transition-name: blog-pubdate-${entry.id};`}>{entry.data.pubDate.toLocaleDateString()}</time>
</div>
</div>

View File

@@ -6,16 +6,16 @@ const posts = await getCollection('blog');
posts.sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf());
---
<BaseLayout title="Blog" theme="blog" description="Gedanken über Technologie, Design und das Leben von c0ntroller.de.">
<div class="glass-container header">
<div class="glass-container header" style="view-transition-name:main-glass;">
<h1>Artikel & Gedanken</h1>
<p>Gedanken über Technologie, Design und das Leben.</p>
</div>
<div class="list">
{posts.map(post => (
<a href={`/blog/${post.id}`} class="glass-container link-card">
<h2>{post.data.title}</h2>
<p class="date">{post.data.pubDate.toLocaleDateString()}</p>
<a href={`/blog/${post.id}`} class="glass-container link-card" style={`view-transition-name: blog-header-${post.id};`}>
<h2 style={`view-transition-name: blog-headline-${post.id};`}>{post.data.title}</h2>
<time class="date" style={`view-transition-name: blog-pubdate-${post.id};`}>{post.data.pubDate.toLocaleDateString()}</time>
<p>{post.data.summary}</p>
</a>
))}

View File

@@ -34,8 +34,8 @@ const chapters = allPages.reduce((acc, p) => {
}, {} as Record<string, typeof allPages>);
---
<BaseLayout title={page.data.title} theme="book">
<div class="glass-container content prose">
<h1>{page.data.title}</h1>
<div class="glass-container content prose" style="view-transition-name:main-glass;">
<h1 style={`view-transition-name: book-headline-${page.id.replaceAll("/", "-")};`}>{page.data.title}</h1>
<Content />
</div>

View File

@@ -13,7 +13,7 @@ const chapters = pages.reduce((acc, page) => {
}, {} as Record<string, typeof pages>);
---
<BaseLayout title="Das Buch" theme="book" description="Ein fortlaufendes Werk über Software, Design und Architektur von c0ntroller.de.">
<div class="glass-container header">
<div class="glass-container header" style="view-transition-name:main-glass;">
<h1>Das Buch</h1>
<p>Ein fortlaufendes Werk über Software, Design und Architektur.</p>
</div>
@@ -25,7 +25,7 @@ const chapters = pages.reduce((acc, page) => {
<h3>{chapter.replace(/-/g, ' ').toUpperCase()}</h3>
<ul class="page-list">
{items.map(item => (
<li><a href={`/book/${item.id}`}>{item.data.title}</a></li>
<li><a href={`/book/${item.id}`} style={`view-transition-name: book-headline-${item.id.replaceAll("/", "-")};`}>{item.data.title}</a></li>
))}
</ul>
</div>

View File

@@ -11,7 +11,7 @@ import socials from "../data/socials.json";
<div class="socials fade-in-up shadow-glow glass-bg glass-container">
{
socials.map(({ name, url, icon }) => (
<a href={url} target="_blank" rel="noreferrer" class="social-link">
<a href={url} target="_blank" referrerpolicy="no-referrer" class="social-link">
<Icon name={icon} desc={name} />
</a>
))

View File

@@ -1,5 +1,6 @@
---
import { getCollection, render } from 'astro:content';
import { Icon } from "astro-icon/components";
import BaseLayout from '../../layouts/BaseLayout.astro';
import "katex/dist/katex.min.css";
import "rehype-callouts/theme/obsidian"
@@ -15,14 +16,26 @@ const { entry } = Astro.props;
const { Content } = await render(entry);
---
<BaseLayout title={entry.data.title} theme="portfolio">
<div class="glass-container header">
<h1>{entry.data.title}</h1>
<div class="glass-container header" style={`view-transition-name: project-header-${entry.id};`}>
<h1 style={`view-transition-name: project-headline-${entry.id};`}>{entry.data.title}</h1>
<p>{entry.data.summary}</p>
{entry.data.tags && (
<div class="tags">
{entry.data.tags.map(tech => <span class="tag">{tech}</span>)}
</div>
)}
{(entry.data.tags || entry.data.repository) &&
(<div class="additional-meta">
<div class="meta-tags">
{entry.data.tags && (
<div class="tags">
{entry.data.tags?.map(tech => <span class="tag">{tech}</span>)}
</div>
)}
</div>
<div class="meta-repository">
{ entry.data.repository && (
<a href={entry.data.repository} referrerpolicy="no-referrer">
<Icon name="simple-icons:git" style={`view-transition-name: project-git-${entry.id};`} />
</a>
)}
</div>
</div>)}
</div>
<div class="glass-container content prose">
@@ -39,6 +52,7 @@ const { Content } = await render(entry);
gap: 0.5rem;
margin-top: 1rem;
flex-wrap: wrap;
align-items: center;
}
.tag {
font-size: 0.8rem;
@@ -49,4 +63,23 @@ const { Content } = await render(entry);
.content {
line-height: 1.8;
}
.additional-meta {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
gap: 0.5rem;
width: 100%;
justify-content: space-between;
margin-top: 1rem;
align-items: center;
}
.meta-repository {
flex-shrink: 1;
font-size: 1.2rem;
color: rgb(var(--accent-base));
}
.meta-repository a {
display: flex;
align-items: center;
}
</style>

View File

@@ -1,26 +1,37 @@
---
import { getCollection } from 'astro:content';
import { Icon } from "astro-icon/components";
import BaseLayout from '../../layouts/BaseLayout.astro';
const projects = await getCollection('portfolio');
projects.sort((a, b) => b.data.pubDate.getTime() - a.data.pubDate.getTime());
---
<BaseLayout title="Portfolio" theme="portfolio" description="Eine Auswahl aktueller Projekte und Experimente von c0ntroller.de.">
<div class="glass-container">
<div class="glass-container" style="view-transition-name:main-glass;">
<h1>Meine Arbeiten</h1>
<p>Eine Auswahl aktueller Projekte und Experimente.</p>
</div>
<div class="grid">
{projects.map(project => (
<a href={`/portfolio/${project.id}`} class="glass-container link-card">
<h2>{project.data.title}</h2>
<a href={`/portfolio/${project.id}`} class="glass-container link-card" style={`view-transition-name: project-header-${project.id};`}>
<h2 style={`view-transition-name: project-headline-${project.id};`}>{project.data.title}</h2>
<p>{project.data.summary}</p>
{project.data.tags && (
<div class="tags">
{project.data.tags?.map(tech => <span class="tag">{tech}</span>)}
{(project.data.tags || project.data.repository) &&
(<div class="additional-meta">
<div class="meta-tags">
{project.data.tags && (
<div class="tags">
{project.data.tags?.map(tech => <span class="tag">{tech}</span>)}
</div>
)}
</div>
)}
<div class="meta-repository">
{ project.data.repository && (
<Icon name="simple-icons:git" style={`view-transition-name: project-git-${project.id};`} />
)}
</div>
</div>)}
</a>
))}
</div>
@@ -40,11 +51,22 @@ projects.sort((a, b) => b.data.pubDate.getTime() - a.data.pubDate.getTime());
.link-card h2 {
margin-top: 0;
}
.additional-meta {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
gap: 0.5rem;
width: 100%;
justify-content: space-between;
margin-top: 1rem;
align-items: center;
}
.tags {
display: flex;
gap: 0.5rem;
margin-top: 1rem;
flex-wrap: wrap;
flex-grow: 1;
align-items: center;
}
.tag {
font-size: 0.8rem;
@@ -52,4 +74,11 @@ projects.sort((a, b) => b.data.pubDate.getTime() - a.data.pubDate.getTime());
padding: 0.25rem 0.5rem;
border-radius: 0.25rem;
}
.meta-repository {
flex-shrink: 1;
font-size: 1.2rem;
display: flex;
align-items: center;
color: rgb(var(--accent-base));
}
</style>

View File

@@ -1,22 +0,0 @@
import { execSync } from "child_process";
import { statSync } from "fs";
export function remarkModifiedTime() {
return function (tree, file) {
const filepath = file.history[0];
try {
const result = execSync(`git log -1 --pretty="format:%cI" "${filepath}"`);
// If result is empty or undefined, fallback to fs stat
if (!result || !result.toString().trim()) {
throw new Error("No git history");
}
file.data.astro.frontmatter.lastModified = result.toString();
return;
} catch (e) {
// Ignore, fallback to fs stat
const result = statSync(filepath);
file.data.astro.frontmatter.lastModified = result.mtime.toISOString();
return;
}
};
}

View File

@@ -5,8 +5,10 @@
transition: color .4s ease, background-color .4s ease, border-color .4s ease, box-shadow .4s ease;
}
@view-transition {
navigation: auto;
@media (prefers-reduced-motion: no-preference) {
@view-transition {
navigation: auto;
}
}
::view-transition-group(root) {