Started with about page and my cv

This commit is contained in:
2025-11-09 12:21:34 +01:00
parent b80d70d4df
commit 5969e66872
36 changed files with 637 additions and 169 deletions

View File

@@ -0,0 +1,9 @@
<app-topbar />
<main class="container app-surface">
<router-outlet />
</main>
<footer class="foot">
<small>© {{ currentYear }} Andreas Dahm - {{ `APP.COPYRIGHT` | translate }}</small>
</footer>

View File

@@ -0,0 +1,10 @@
.container { max-width: 1100px; margin: 0 auto; padding: 1rem; }
.app-surface {
background: var(--app-bg);
color: var(--app-fg);
transition: background-color 220ms ease, color 220ms ease;
}
.foot {
border-top: 1px solid rgba(0,0,0,.08);
padding: 1rem; text-align: center; opacity: .8;
}

View File

@@ -0,0 +1,16 @@
import { Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';
import {TopbarComponent} from '../topbar/topbar.component';
import {TranslatePipe} from '@ngx-translate/core';
@Component({
selector: 'app-root',
standalone: true,
imports: [RouterOutlet, TopbarComponent, TranslatePipe],
templateUrl: './app.component.html',
styleUrl: './app.component.scss'
})
export class AppComponent {
currentYear = new Date().getFullYear();
}

View File

@@ -0,0 +1,58 @@
<mat-toolbar class="topbar" color="primary" (keydown)="onKeydown($event)">
<a class="brand" routerLink="/">
<img class="logo-dot"
src="{{AssetsConstants.LOGO}}"
alt="" aria-hidden="true"
draggable="false"
oncontextmenu="return false;"
>
<span class="brand-text">{{ 'APP.TITLE' | translate }}</span>
</a>
<nav class="nav">
<a routerLink="/about" mat-button>{{ 'TOPBAR.ABOUT' | translate }}</a>
<a routerLink="/projects" mat-button>{{ 'TOPBAR.PROJECTS' | translate }}</a>
<a routerLink="/hobbys" mat-button>{{ 'TOPBAR.HOBBY' | translate }}</a>
<a routerLink="/contact" mat-button>{{ 'TOPBAR.CONTACT' | translate }}</a>
</nav>
<span class="spacer"></span>
<!-- Settings: Sprache + Theme -->
<button mat-icon-button [matMenuTriggerFor]="settingsMenu" aria-label="Open settings" matTooltip="{{ 'TOPBAR.SETTINGS' | translate }}">
<mat-icon>tune</mat-icon>
</button>
<mat-menu #settingsMenu="matMenu" xPosition="before">
<div class="menu-section">
<div class="menu-title">{{ 'TOPBAR.LANGUAGE' | translate }}</div>
<button mat-menu-item (click)="setLang('de')">
<img class="flag-icon" src="{{AssetsConstants.FLAG_DE}}" alt="" aria-hidden="true">
<span>{{ 'LANG.DE' | translate }}</span>
@if (lang.lang() === 'de')
{
<mat-icon >check</mat-icon>
}
</button>
<button mat-menu-item (click)="setLang('en')">
<img class="flag-icon" src="{{AssetsConstants.FLAG_EN}}" alt="" aria-hidden="true">
<span>{{ 'LANG.EN' | translate }}</span>
@if (lang.lang() === 'en')
{
<mat-icon>check</mat-icon>
}
</button>
</div>
<mat-divider></mat-divider>
<div class="menu-section">
<div class="menu-title">{{ 'TOPBAR.APPEARANCE' | translate }}</div>
<button mat-menu-item (click)="theme.toggle()">
<mat-icon>{{ themeIcon() }}</mat-icon>
<span>
{{ theme.theme() === 'dark' ? ('THEME.LIGHT' | translate) : ('THEME.DARK' | translate) }}
</span>
<span class="kbd">Ctrl/⌘ + J</span>
</button>
</div>
</mat-menu>
</mat-toolbar>

View File

@@ -0,0 +1,73 @@
.topbar {
position: sticky; top: 0; z-index: 100;
backdrop-filter: saturate(1.1) blur(8px);
background:
color-mix(in oklab, var(--app-bg) 80%, transparent);
border-bottom: 1px solid rgba(0,0,0,.08);
.brand {
display:flex; align-items:center; gap:.6rem;
color: inherit; text-decoration: none;
.logo-dot {
width: 48px; height: 48px; border-radius: 50%;
}
.brand-text { font-weight: 600; letter-spacing:.2px; }
}
.nav { display:flex; gap:.25rem; margin-left:.5rem; }
.spacer { flex: 1; }
.flag-icon { width: 18px; height: 18px; border-radius: 2px; margin-right:.5rem; }
.menu-section { padding:.25rem .5rem .5rem; }
.menu-title { font-size:.75rem; opacity:.75; padding:.25rem .75rem .5rem; }
.kbd {
margin-left:auto; font-size:.7rem; opacity:.65; border:1px solid currentColor;
border-radius:4px; padding:0 .35rem;
}
}
::ng-deep .mat-mdc-menu-item .mdc-list-item__primary-text {
display: flex;
align-items: center;
gap: .5rem;
}
::ng-deep .mat-mdc-menu-item .kbd {
margin-left: auto;
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
font-size: 11px;
line-height: 1.6;
padding: 0 .35rem;
border: 0px solid currentColor;
border-radius: 4px;
opacity: .65;
}
::ng-deep .mat-mdc-menu-item .mat-icon {
width: 20px; height: 20px; font-size: 20px;
}
::ng-deep .mat-mdc-menu-item .flag-icon {
width: 20px !important;
height: 14px !important;
object-fit: cover;
border-radius: 2px;
margin-right: .5rem;
vertical-align: middle;
}
::ng-deep .mat-mdc-menu-panel {
border-radius: 10px !important;
border: 1px solid rgba(0,0,0,.14);
}
.dark ::ng-deep .mat-mdc-menu-panel {
border-color: rgba(255,255,255,.06);
}
/* Responsive: Collapse navigation to icon if width is smaller than 760px */
@media (max-width: 760px) {
.topbar .nav { display:none; }
}

View File

@@ -0,0 +1,44 @@
import { Component, computed, inject } from '@angular/core';
import { RouterLink } from '@angular/router';
import { MatToolbarModule } from '@angular/material/toolbar';
import { MatIconModule } from '@angular/material/icon';
import { MatButtonModule } from '@angular/material/button';
import { MatMenuModule } from '@angular/material/menu';
import { MatTooltipModule } from '@angular/material/tooltip';
import { TranslateModule } from '@ngx-translate/core';
import { ThemeService } from '../../service/theme.service';
import { LanguageService } from '../../service/language.service';
import { MatDivider } from '@angular/material/divider';
import {AssetsConstants} from '../../constants/AssetsConstants';
@Component({
selector: 'app-topbar',
standalone: true,
imports: [
RouterLink,
MatToolbarModule, MatIconModule, MatButtonModule, MatMenuModule, MatTooltipModule,
TranslateModule, MatDivider
],
templateUrl: './topbar.component.html',
styleUrl: './topbar.component.scss'
})
export class TopbarComponent {
readonly theme = inject(ThemeService);
readonly lang = inject(LanguageService);
readonly themeIcon = computed(() =>
this.theme.theme() === 'dark' ? 'light_mode' : 'dark_mode'
);
onKeydown(e: KeyboardEvent) {
const metaOrCtrl = e.metaKey || e.ctrlKey;
if (metaOrCtrl && (e.key.toLowerCase() === 'j')) {
e.preventDefault();
this.theme.toggle();
}
}
setLang(code: 'de' | 'en') { this.lang.use(code); }
protected readonly AssetsConstants = AssetsConstants;
}