Refactor topbar HTML, projects TS and styles

Minor markup and formatting cleanup plus layout adjustments.

- topbar.component.html: removed mat-toolbar color attribute, compacted/normalized element attributes and spacing, fixed small markup spacing issues.
- projects.component.ts: code formatting and whitespace normalization (imports, property spacing, object literals, small function signature/timeout formatting); no logic changes.
- styles.scss: reorganized topbar rules (added app-topbar wrapper, display and color), switched .hero from grid to flex with responsive flex-basis, adjusted photo and intro flex behavior, increased project grid column min width and centered grid with max-width, removed featured card full-width grid span.

These changes improve consistency, readability and adjust layout/responsiveness of the topbar/hero/project grid.
This commit is contained in:
2026-02-22 12:27:20 +01:00
parent 0e78e6b471
commit c6edc922fe
3 changed files with 147 additions and 148 deletions

View File

@@ -1,11 +1,7 @@
<mat-toolbar class="topbar" color="primary" (keydown)="onKeydown($event)"> <mat-toolbar class="topbar" (keydown)="onKeydown($event)">
<a class="brand" routerLink="/"> <a class="brand" routerLink="/">
<img class="logo-dot" <img class="logo-dot" src="{{AssetsConstants.LOGO}}" alt="" aria-hidden="true" draggable="false"
src="{{AssetsConstants.LOGO}}" oncontextmenu="return false;">
alt="" aria-hidden="true"
draggable="false"
oncontextmenu="return false;"
>
<span class="brand-text">{{ 'APP.TITLE' | translate }}</span> <span class="brand-text">{{ 'APP.TITLE' | translate }}</span>
</a> </a>
@@ -17,11 +13,7 @@
</nav> </nav>
<!-- Mobile nav menu button --> <!-- Mobile nav menu button -->
<button <button mat-icon-button class="nav-menu-btn" [matMenuTriggerFor]="navMenu" aria-label="Open navigation">
mat-icon-button
class="nav-menu-btn"
[matMenuTriggerFor]="navMenu"
aria-label="Open navigation">
<mat-icon>menu</mat-icon> <mat-icon>menu</mat-icon>
</button> </button>
@@ -46,7 +38,8 @@
<span class="spacer"></span> <span class="spacer"></span>
<!-- Settings: Sprache + Theme --> <!-- Settings: Sprache + Theme -->
<button mat-icon-button [matMenuTriggerFor]="settingsMenu" aria-label="Open settings" matTooltip="{{ 'TOPBAR.SETTINGS' | translate }}"> <button mat-icon-button [matMenuTriggerFor]="settingsMenu" aria-label="Open settings"
matTooltip="{{ 'TOPBAR.SETTINGS' | translate }}">
<mat-icon>tune</mat-icon> <mat-icon>tune</mat-icon>
</button> </button>
@@ -58,7 +51,7 @@
<span>{{ 'LANG.DE' | translate }}</span> <span>{{ 'LANG.DE' | translate }}</span>
@if (lang.lang() === 'de') @if (lang.lang() === 'de')
{ {
<mat-icon >check</mat-icon> <mat-icon>check</mat-icon>
} }
</button> </button>
<button mat-menu-item (click)="setLang('en')"> <button mat-menu-item (click)="setLang('en')">
@@ -66,7 +59,7 @@
<span>{{ 'LANG.EN' | translate }}</span> <span>{{ 'LANG.EN' | translate }}</span>
@if (lang.lang() === 'en') @if (lang.lang() === 'en')
{ {
<mat-icon>check</mat-icon> <mat-icon>check</mat-icon>
} }
</button> </button>
</div> </div>
@@ -82,4 +75,4 @@
</button> </button>
</div> </div>
</mat-menu> </mat-menu>
</mat-toolbar> </mat-toolbar>

View File

@@ -1,14 +1,14 @@
import {Component, computed, inject, CUSTOM_ELEMENTS_SCHEMA, OnDestroy, OnInit} from '@angular/core'; import { Component, computed, inject, CUSTOM_ELEMENTS_SCHEMA, OnDestroy, OnInit } from '@angular/core';
import {ActivatedRoute, Router} from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
import {Subscription} from "rxjs"; import { Subscription } from "rxjs";
import {MatCardModule} from "@angular/material/card"; import { MatCardModule } from "@angular/material/card";
import {MatChipsModule} from "@angular/material/chips"; import { MatChipsModule } from "@angular/material/chips";
import {MatIcon} from "@angular/material/icon"; import { MatIcon } from "@angular/material/icon";
import {TranslatePipe} from "@ngx-translate/core"; import { TranslatePipe } from "@ngx-translate/core";
import {MatButtonModule} from "@angular/material/button"; import { MatButtonModule } from "@angular/material/button";
import {MatDialog} from "@angular/material/dialog"; import { MatDialog } from "@angular/material/dialog";
import {ProjectDialogComponent} from "./dialog/project-dialog.component"; import { ProjectDialogComponent } from "./dialog/project-dialog.component";
import {AssetsConstants} from "../../constants/AssetsConstants"; import { AssetsConstants } from "../../constants/AssetsConstants";
export interface Projects { export interface Projects {
identifier: string; identifier: string;
@@ -46,124 +46,124 @@ export interface Projects {
}) })
export class ProjectsComponent implements OnInit, OnDestroy { export class ProjectsComponent implements OnInit, OnDestroy {
private readonly route = inject(ActivatedRoute); private readonly route = inject(ActivatedRoute);
private readonly dialog = inject(MatDialog); private readonly dialog = inject(MatDialog);
private readonly router = inject(Router); private readonly router = inject(Router);
private queryParamSub: Subscription | undefined; private queryParamSub: Subscription | undefined;
allProjects: Projects[] = [ allProjects: Projects[] = [
{ {
identifier: "playground", identifier: "playground",
title: 'PROJECTS.PLAYGROUND.TITLE', title: 'PROJECTS.PLAYGROUND.TITLE',
shortDescription: 'PROJECTS.PLAYGROUND.SHORT_DESCRIPTION', shortDescription: 'PROJECTS.PLAYGROUND.SHORT_DESCRIPTION',
introduction: 'PROJECTS.PLAYGROUND.INTRODUCTION', introduction: 'PROJECTS.PLAYGROUND.INTRODUCTION',
images: [], images: [],
icon: 'web', icon: 'web',
assets: '', assets: '',
links: [{name: 'PROJECTS.LINK_TO_PROJECT', url: 'https://andreas-dahm.eu'}], links: [{ name: 'PROJECTS.LINK_TO_PROJECT', url: 'https://andreas-dahm.eu' }],
bulletPoints: [ bulletPoints: [
'PROJECTS.PLAYGROUND.BULLET_1', 'PROJECTS.PLAYGROUND.BULLET_1',
'PROJECTS.PLAYGROUND.BULLET_2', 'PROJECTS.PLAYGROUND.BULLET_2',
'PROJECTS.PLAYGROUND.BULLET_3', 'PROJECTS.PLAYGROUND.BULLET_3',
'PROJECTS.PLAYGROUND.BULLET_4', 'PROJECTS.PLAYGROUND.BULLET_4',
], ],
isFeatured: false, isFeatured: false,
technologies: ['Angular', 'TypeScript', 'SCSS', 'HTML', 'GitHub Actions', 'Docker'] technologies: ['Angular', 'TypeScript', 'SCSS', 'HTML', 'GitHub Actions', 'Docker']
}, },
{ {
identifier: "elmucho", identifier: "elmucho",
title: 'PROJECTS.EL_MUCHO.TITLE', title: 'PROJECTS.EL_MUCHO.TITLE',
shortDescription: 'PROJECTS.EL_MUCHO.SHORT_DESCRIPTION', shortDescription: 'PROJECTS.EL_MUCHO.SHORT_DESCRIPTION',
introduction: 'PROJECTS.EL_MUCHO.INTRODUCTION', introduction: 'PROJECTS.EL_MUCHO.INTRODUCTION',
images: AssetsConstants.EL_MUCHO_IMAGES.map(url => ({ url, source: '' })), images: AssetsConstants.EL_MUCHO_IMAGES.map(url => ({ url, source: '' })),
icon: 'sports_esports', icon: 'sports_esports',
assets: '', assets: '',
links: [{name: 'PROJECTS.LINK_TO_PROJECT', url: 'https://store.steampowered.com/app/1532640/El_Mucho/'}], links: [{ name: 'PROJECTS.LINK_TO_PROJECT', url: 'https://store.steampowered.com/app/1532640/El_Mucho/' }],
bulletPoints: [ bulletPoints: [
'PROJECTS.EL_MUCHO.BULLET_1', 'PROJECTS.EL_MUCHO.BULLET_1',
'PROJECTS.EL_MUCHO.BULLET_2', 'PROJECTS.EL_MUCHO.BULLET_2',
'PROJECTS.EL_MUCHO.BULLET_3', 'PROJECTS.EL_MUCHO.BULLET_3',
'PROJECTS.EL_MUCHO.BULLET_4', 'PROJECTS.EL_MUCHO.BULLET_4',
], ],
isFeatured: true, isFeatured: true,
technologies: ['Unity', 'C#', 'Steamworks', 'Git'] technologies: ['Unity', 'C#', 'Steamworks', 'Git']
}, },
{ {
identifier: "gamejams", identifier: "gamejams",
title: 'PROJECTS.GAME_JAMS.TITLE', title: 'PROJECTS.GAME_JAMS.TITLE',
shortDescription: 'PROJECTS.GAME_JAMS.SHORT_DESCRIPTION', shortDescription: 'PROJECTS.GAME_JAMS.SHORT_DESCRIPTION',
introduction: 'PROJECTS.GAME_JAMS.INTRODUCTION', introduction: 'PROJECTS.GAME_JAMS.INTRODUCTION',
images: AssetsConstants.GAME_JAMS_IMAGES.map(url => ({ url, source: '' })), images: AssetsConstants.GAME_JAMS_IMAGES.map(url => ({ url, source: '' })),
icon: 'videogame_asset', icon: 'videogame_asset',
assets: '', assets: '',
links: [{name: 'PROJECTS.LINK_TO_PROJECT', url: 'https://itch.io/c/6628860/lobos-collection'}], links: [{ name: 'PROJECTS.LINK_TO_PROJECT', url: 'https://itch.io/c/6628860/lobos-collection' }],
bulletPoints: [ bulletPoints: [
'PROJECTS.GAME_JAMS.BULLET_1', 'PROJECTS.GAME_JAMS.BULLET_1',
'PROJECTS.GAME_JAMS.BULLET_2', 'PROJECTS.GAME_JAMS.BULLET_2',
'PROJECTS.GAME_JAMS.BULLET_3', 'PROJECTS.GAME_JAMS.BULLET_3',
'PROJECTS.GAME_JAMS.BULLET_4', 'PROJECTS.GAME_JAMS.BULLET_4',
], ],
isFeatured: false, isFeatured: false,
technologies: ['Unity', 'C#', 'Git'] technologies: ['Unity', 'C#', 'Git']
}, },
{ {
identifier: "diploma", identifier: "diploma",
title: 'PROJECTS.DIPLOMA.TITLE', title: 'PROJECTS.DIPLOMA.TITLE',
shortDescription: 'PROJECTS.DIPLOMA.SHORT_DESCRIPTION', shortDescription: 'PROJECTS.DIPLOMA.SHORT_DESCRIPTION',
introduction: 'PROJECTS.DIPLOMA.INTRODUCTION', introduction: 'PROJECTS.DIPLOMA.INTRODUCTION',
images: AssetsConstants.DIPLOMA_IMAGES.map(url => ({ url, source: '' })), images: AssetsConstants.DIPLOMA_IMAGES.map(url => ({ url, source: '' })),
icon: 'history_edu', icon: 'history_edu',
assets: AssetsConstants.DIPLOMA, assets: AssetsConstants.DIPLOMA,
links: [{name: 'PROJECTS.LINK_TO_PROJECT', url: 'https://www.th-bingen.de'}], links: [{ name: 'PROJECTS.LINK_TO_PROJECT', url: 'https://www.th-bingen.de' }],
bulletPoints: [ bulletPoints: [
'PROJECTS.DIPLOMA.BULLET_1', 'PROJECTS.DIPLOMA.BULLET_1',
'PROJECTS.DIPLOMA.BULLET_2', 'PROJECTS.DIPLOMA.BULLET_2',
'PROJECTS.DIPLOMA.BULLET_3', 'PROJECTS.DIPLOMA.BULLET_3',
'PROJECTS.DIPLOMA.BULLET_4', 'PROJECTS.DIPLOMA.BULLET_4',
], ],
isFeatured: false, isFeatured: false,
technologies: ['C++', 'OpenGL', 'Qt', '3D-Scanner'] technologies: ['C++', 'OpenGL', 'Qt', '3D-Scanner']
}, },
{ {
identifier: "tribble-the-homeserver", identifier: "tribble-the-homeserver",
title: 'PROJECTS.TRIBBLE.TITLE', title: 'PROJECTS.TRIBBLE.TITLE',
shortDescription: 'PROJECTS.TRIBBLE.SHORT_DESCRIPTION', shortDescription: 'PROJECTS.TRIBBLE.SHORT_DESCRIPTION',
introduction: 'PROJECTS.TRIBBLE.INTRODUCTION', introduction: 'PROJECTS.TRIBBLE.INTRODUCTION',
images: [ images: [
{ url: AssetsConstants.TRIBBLE_IMAGES[0], source: 'https://upload.wikimedia.org/wikipedia/commons/0/03/Hostinger_Logo.png'}, { url: AssetsConstants.TRIBBLE_IMAGES[0], source: 'https://upload.wikimedia.org/wikipedia/commons/0/03/Hostinger_Logo.png' },
{ url: AssetsConstants.TRIBBLE_IMAGES[1], source: 'https://dashboardicons.com/icons/docker-engine'}, { url: AssetsConstants.TRIBBLE_IMAGES[1], source: 'https://dashboardicons.com/icons/docker-engine' },
{ url: AssetsConstants.TRIBBLE_IMAGES[2], source: 'https://dashboardicons.com/icons/gitea'}, { url: AssetsConstants.TRIBBLE_IMAGES[2], source: 'https://dashboardicons.com/icons/gitea' },
{ url: AssetsConstants.TRIBBLE_IMAGES[3], source: 'https://commons.wikimedia.org/wiki/File:Traefik.logo.png'} { url: AssetsConstants.TRIBBLE_IMAGES[3], source: 'https://commons.wikimedia.org/wiki/File:Traefik.logo.png' }
], ],
icon: 'dns', icon: 'dns',
assets: '', assets: '',
links: [ links: [
{name: 'Ubuntu Server', url: 'https://ubuntu.com/server'}, { name: 'Ubuntu Server', url: 'https://ubuntu.com/server' },
{name: 'Docker', url: 'https://www.docker.com/'}, { name: 'Docker', url: 'https://www.docker.com/' },
{name: 'Traefik', url: 'https://traefik.io/'}, { name: 'Traefik', url: 'https://traefik.io/' },
{name: 'Gitea', url: 'https://gitea.io/'}, { name: 'Gitea', url: 'https://gitea.io/' },
{name: 'Jellyfin', url: 'https://jellyfin.org/'}, { name: 'Jellyfin', url: 'https://jellyfin.org/' },
{name: 'AdGuard Home', url: 'https://adguard.com/en/adguard-home/overview.html'}, { name: 'AdGuard Home', url: 'https://adguard.com/en/adguard-home/overview.html' },
{name: 'Paperless-ngx', url: 'https://paperless-ngx.com/'}, { name: 'Paperless-ngx', url: 'https://paperless-ngx.com/' },
{name: 'Tailscale', url: 'https://tailscale.com/'} { name: 'Tailscale', url: 'https://tailscale.com/' }
], ],
bulletPoints: [ bulletPoints: [
'PROJECTS.TRIBBLE.BULLET_1', 'PROJECTS.TRIBBLE.BULLET_1',
'PROJECTS.TRIBBLE.BULLET_2', 'PROJECTS.TRIBBLE.BULLET_2',
'PROJECTS.TRIBBLE.BULLET_3', 'PROJECTS.TRIBBLE.BULLET_3',
'PROJECTS.TRIBBLE.BULLET_4', 'PROJECTS.TRIBBLE.BULLET_4',
], ],
isFeatured: false, isFeatured: false,
technologies: ['Ubuntu Server', 'Docker', 'Traefik', 'Gitea', 'Jellyfin', 'AdGuard Home', 'Paperless-ngx', 'Tailscale'] technologies: ['Ubuntu Server', 'Docker', 'Traefik', 'Gitea', 'Jellyfin', 'AdGuard Home', 'Paperless-ngx', 'Tailscale']
} }
] ]
featuredProject = computed(() => this.allProjects.find(p => p.isFeatured)); featuredProject = computed(() => this.allProjects.find(p => p.isFeatured));
otherProjects = computed(() => this.allProjects.filter(p => !p.isFeatured)); otherProjects = computed(() => this.allProjects.filter(p => !p.isFeatured));
ngOnInit(): void { ngOnInit(): void {
window.scrollTo({ top: 0, behavior: 'smooth' }); window.scrollTo({ top: 0, behavior: 'smooth' });
setTimeout(() =>{ this.dialogOpenFunction(); },10); setTimeout(() => { this.dialogOpenFunction(); }, 10);
} }
ngOnDestroy(): void { ngOnDestroy(): void {
@@ -172,8 +172,7 @@ export class ProjectsComponent implements OnInit, OnDestroy {
} }
} }
private dialogOpenFunction() : void private dialogOpenFunction(): void {
{
this.queryParamSub = this.route.queryParamMap.subscribe(params => { this.queryParamSub = this.route.queryParamMap.subscribe(params => {
const projectIdentifier = params.get('project'); const projectIdentifier = params.get('project');
if (projectIdentifier) { if (projectIdentifier) {

View File

@@ -425,16 +425,21 @@ app-root {
} }
/* ---- Topbar ---- */ /* ---- Topbar ---- */
.topbar { app-topbar {
position: sticky; position: sticky;
top: 0; top: 0;
z-index: 100; z-index: 100;
display: block;
}
.topbar {
backdrop-filter: saturate(1.1) blur(8px); backdrop-filter: saturate(1.1) blur(8px);
background: color-mix(in srgb, var(--card-bg) 80%, transparent); background: color-mix(in srgb, var(--card-bg) 80%, transparent);
border-bottom: 1px solid rgba(0, 0, 0, .08); border-bottom: 1px solid rgba(0, 0, 0, .08);
display: flex; display: flex;
align-items: center; align-items: center;
padding: clamp(0.5rem, 1vw, 1rem); padding: clamp(0.5rem, 1vw, 1rem);
color: var(--app-fg);
} }
.topbar .brand { .topbar .brand {
@@ -539,8 +544,8 @@ app-root {
} }
.hero { .hero {
display: grid; display: flex;
grid-template-columns: repeat(auto-fit, minmax(min(100%, 425px), 1fr)); flex-wrap: wrap;
gap: clamp(1rem, 4vw, 1.5rem); gap: clamp(1rem, 4vw, 1.5rem);
border-radius: var(--card-radius); border-radius: var(--card-radius);
background: var(--card-bg); background: var(--card-bg);
@@ -550,7 +555,8 @@ app-root {
.hero .photo { .hero .photo {
display: flex; display: flex;
align-items: flex-start; align-items: flex-start;
justify-content: center; justify-content: flex-start;
flex: 0 0 clamp(300px, 100%, 425px);
} }
.hero .photo img { .hero .photo img {
@@ -567,6 +573,7 @@ app-root {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: clamp(0.5rem, 2vw, 1rem); gap: clamp(0.5rem, 2vw, 1rem);
flex: 1 1 300px;
} }
.hero .intro h1 { .hero .intro h1 {
@@ -748,7 +755,9 @@ app-root {
.project-grid { .project-grid {
display: grid; display: grid;
gap: clamp(1rem, 3vw, 1.5rem); gap: clamp(1rem, 3vw, 1.5rem);
grid-template-columns: repeat(auto-fill, minmax(min(100%, 350px), 1fr)); grid-template-columns: repeat(auto-fill, minmax(min(100%, 450px), 1fr));
max-width: 1200px;
margin: 0 auto;
} }
.project-card { .project-card {
@@ -763,9 +772,7 @@ app-root {
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15); box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
} }
.project-card.featured {
grid-column: 1 / -1;
}
.project-card mat-card-header { .project-card mat-card-header {
padding-bottom: 1rem; padding-bottom: 1rem;