content: replace scaffolding with bilingual pages and case studies

Ship the final German and English copy, compose every route from shared page primitives, and cover claims, parity and contact briefing in tests.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-25 17:34:04 +02:00
parent 9ea2885c7b
commit d954361724
95 changed files with 4161 additions and 419 deletions

View File

@@ -1,5 +1,5 @@
<div class="grid">
@for (cat of categories; track cat.category) {
@for (cat of categories(); track cat.category) {
<app-skill-card
[title]="cat.title"
[category]="cat.category"

View File

@@ -1,114 +1,125 @@
import { Component } from '@angular/core';
import { ChangeDetectionStrategy, Component, computed, input } from '@angular/core';
import { SkillCategory } from '../../../../models/skill-category';
import { SkillCard } from '../skill-card/skill-card';
const BASE_CATEGORIES: readonly SkillCategory[] = [
{
title: 'Programming',
category: 'programming',
gridArea: 'prog',
skills: [
{ name: 'TypeScript', icon: 'typescript', url: 'https://www.typescriptlang.org/' },
{
name: 'JavaScript',
icon: 'javascript',
url: 'https://developer.mozilla.org/en-US/docs/Web/JavaScript',
},
{ name: 'Angular', icon: 'angular', url: 'https://angular.dev/' },
{ name: 'Java', icon: 'java', url: 'https://www.java.com/' },
{ name: 'Spring', icon: 'java-spring', url: 'https://spring.io/' },
{ name: 'C#', icon: 'c-sharp', url: 'https://learn.microsoft.com/en-us/dotnet/csharp/' },
{ name: '.NET', icon: 'c-sharp-net', url: 'https://dotnet.microsoft.com/' },
{ name: 'Kafka', icon: 'kafka', url: 'https://kafka.apache.org/' },
{ name: 'Elasticsearch', icon: 'elasticseach', url: 'https://www.elastic.co/' },
],
},
{
title: 'Databases',
category: 'db',
gridArea: 'db',
skills: [
{
name: 'SQL Server',
icon: 'microsoftsqlserver',
url: 'https://www.microsoft.com/en-us/sql-server',
},
{ name: 'PostgreSQL', icon: 'postgresql', url: 'https://www.postgresql.org/' },
{ name: 'MySQL', icon: 'mysql', url: 'https://www.mysql.com/' },
{ name: 'MariaDB', icon: 'mariadb', url: 'https://mariadb.org/' },
],
},
{
title: 'DevOps',
category: 'devops',
gridArea: 'devops',
skills: [
{ name: 'Kubernetes', icon: 'kubernetes', url: 'https://kubernetes.io/' },
{ name: 'Docker', icon: 'docker', url: 'https://www.docker.com/' },
{ name: 'GitLab', icon: 'gitlab', url: 'https://gitlab.com/' },
{
name: 'Azure DevOps',
icon: 'devops',
url: 'https://azure.microsoft.com/en-us/products/devops',
},
{ name: 'Azure', icon: 'azure', url: 'https://azure.microsoft.com/' },
{ name: 'Hetzner', icon: 'hetzner', url: 'https://www.hetzner.com/' },
{ name: 'Netcup', icon: 'netcup', url: 'https://www.netcup.eu/' },
],
},
{
title: 'Operating Systems',
category: 'os',
gridArea: 'os',
skills: [
{ name: 'Arch Linux', icon: 'arch', url: 'https://archlinux.org/' },
{ name: 'Ubuntu', icon: 'ubuntu', url: 'https://ubuntu.com/' },
{ name: 'macOS', icon: 'macos', url: 'https://www.apple.com/macos/' },
{ name: 'Windows', icon: 'windows', url: 'https://www.microsoft.com/windows' },
],
},
{
title: 'Infrastructure as Code',
category: 'iac',
gridArea: 'iac',
skills: [
{ name: 'Terraform', icon: 'terraform', url: 'https://www.terraform.io/' },
{ name: 'Ansible', icon: 'ansible', url: 'https://www.ansible.com/' },
],
},
{
title: 'Hypervisors',
category: 'hyperviser',
gridArea: 'hyper',
skills: [
{ name: 'Proxmox', icon: 'proxmox', url: 'https://www.proxmox.com/' },
{
name: 'Hyper-V',
icon: 'hyperv',
url: 'https://learn.microsoft.com/en-us/virtualization/hyper-v-on-windows/',
},
],
},
{
title: 'Tools',
category: 'tools',
gridArea: 'tools',
skills: [
{ name: 'JetBrains', icon: 'jetbrains', url: 'https://www.jetbrains.com/' },
{ name: 'Visual Studio', icon: 'vs', url: 'https://visualstudio.microsoft.com/' },
{
name: 'Microsoft Office',
icon: 'office',
url: 'https://www.microsoft.com/microsoft-365',
},
],
},
];
@Component({
selector: 'app-skills-grid',
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [SkillCard],
templateUrl: './skills-grid.html',
styleUrl: './skills-grid.scss',
})
export class SkillsGrid {
categories: SkillCategory[] = [
{
title: 'Programming',
category: 'programming',
gridArea: 'prog',
skills: [
{ name: 'TypeScript', icon: 'typescript', url: 'https://www.typescriptlang.org/' },
{
name: 'JavaScript',
icon: 'javascript',
url: 'https://developer.mozilla.org/en-US/docs/Web/JavaScript',
},
{ name: 'Angular', icon: 'angular', url: 'https://angular.dev/' },
{ name: 'Java', icon: 'java', url: 'https://www.java.com/' },
{ name: 'Spring', icon: 'java-spring', url: 'https://spring.io/' },
{ name: 'C#', icon: 'c-sharp', url: 'https://learn.microsoft.com/en-us/dotnet/csharp/' },
{ name: '.NET', icon: 'c-sharp-net', url: 'https://dotnet.microsoft.com/' },
{ name: 'Kafka', icon: 'kafka', url: 'https://kafka.apache.org/' },
{ name: 'Elasticsearch', icon: 'elasticseach', url: 'https://www.elastic.co/' },
],
},
{
title: 'Databases',
category: 'db',
gridArea: 'db',
skills: [
{
name: 'SQL Server',
icon: 'microsoftsqlserver',
url: 'https://www.microsoft.com/en-us/sql-server',
},
{ name: 'PostgreSQL', icon: 'postgresql', url: 'https://www.postgresql.org/' },
{ name: 'MySQL', icon: 'mysql', url: 'https://www.mysql.com/' },
{ name: 'MariaDB', icon: 'mariadb', url: 'https://mariadb.org/' },
],
},
{
title: 'DevOps',
category: 'devops',
gridArea: 'devops',
skills: [
{ name: 'Kubernetes', icon: 'kubernetes', url: 'https://kubernetes.io/' },
{ name: 'Docker', icon: 'docker', url: 'https://www.docker.com/' },
{ name: 'GitLab', icon: 'gitlab', url: 'https://gitlab.com/' },
{
name: 'Azure DevOps',
icon: 'devops',
url: 'https://azure.microsoft.com/en-us/products/devops',
},
{ name: 'Azure', icon: 'azure', url: 'https://azure.microsoft.com/' },
{ name: 'Hetzner', icon: 'hetzner', url: 'https://www.hetzner.com/' },
{ name: 'Netcup', icon: 'netcup', url: 'https://www.netcup.eu/' },
],
},
{
title: 'Operating Systems',
category: 'os',
gridArea: 'os',
skills: [
{ name: 'Arch Linux', icon: 'arch', url: 'https://archlinux.org/' },
{ name: 'Ubuntu', icon: 'ubuntu', url: 'https://ubuntu.com/' },
{ name: 'macOS', icon: 'macos', url: 'https://www.apple.com/macos/' },
{ name: 'Windows', icon: 'windows', url: 'https://www.microsoft.com/windows' },
],
},
{
title: 'Infrastructure as Code',
category: 'iac',
gridArea: 'iac',
skills: [
{ name: 'Terraform', icon: 'terraform', url: 'https://www.terraform.io/' },
{ name: 'Ansible', icon: 'ansible', url: 'https://www.ansible.com/' },
],
},
{
title: 'Hypervisors',
category: 'hyperviser',
gridArea: 'hyper',
skills: [
{ name: 'Proxmox', icon: 'proxmox', url: 'https://www.proxmox.com/' },
{
name: 'Hyper-V',
icon: 'hyperv',
url: 'https://learn.microsoft.com/en-us/virtualization/hyper-v-on-windows/',
},
],
},
{
title: 'Tools',
category: 'tools',
gridArea: 'tools',
skills: [
{ name: 'JetBrains', icon: 'jetbrains', url: 'https://www.jetbrains.com/' },
{ name: 'Visual Studio', icon: 'vs', url: 'https://visualstudio.microsoft.com/' },
{
name: 'Microsoft Office',
icon: 'office',
url: 'https://www.microsoft.com/microsoft-365',
},
],
},
];
readonly categoryTitles = input<Readonly<Record<string, string>>>({});
protected readonly categories = computed(() => {
const titles = this.categoryTitles();
return BASE_CATEGORIES.map((category) => ({
...category,
title: titles[category.category] ?? category.title,
}));
});
}

View File

@@ -1,7 +0,0 @@
<div class="main">
@if (page(); as copy) {
<h1>{{ copy.title }}</h1>
<p>{{ copy.description }}</p>
}
<app-skills-grid></app-skills-grid>
</div>

View File

@@ -1,22 +0,0 @@
.main {
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: var(--color-text);
font-family: var(--font-sans);
padding: var(--space-8) var(--content-gutter);
}
h1 {
font-size: var(--text-3xl);
font-weight: 600;
line-height: var(--leading-tight);
margin: 0;
}
p {
color: var(--color-text-muted);
margin: var(--space-2) 0 var(--space-9);
}

View File

@@ -1,31 +0,0 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { PLACEHOLDER_CONTENT } from '../../../core/content/placeholder-content';
import { SITE_CONTENT } from '../../../core/content/content.token';
import { SITE_CONFIG } from '../../../core/content/site-config';
import { Skills } from './skills';
describe('Skills', () => {
let component: Skills;
let fixture: ComponentFixture<Skills>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [Skills],
providers: [{ provide: SITE_CONTENT, useValue: PLACEHOLDER_CONTENT }],
}).compileComponents();
fixture = TestBed.createComponent(Skills);
component = fixture.componentInstance;
await fixture.whenStable();
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('renders the localized stack title instead of the person name', () => {
const heading = (fixture.nativeElement as HTMLElement).querySelector('h1');
expect(heading?.textContent?.trim()).toBe('Stack');
expect(heading?.textContent).not.toContain(SITE_CONFIG.personName);
});
});

View File

@@ -1,14 +0,0 @@
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
import { ContentService } from '../../../core/content/content.service';
import { SkillsGrid } from './skills-grid/skills-grid';
@Component({
selector: 'app-skills',
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [SkillsGrid],
templateUrl: './skills.html',
styleUrl: './skills.scss',
})
export class Skills {
protected readonly page = inject(ContentService).page('stack');
}