Started with about page and my cv
This commit is contained in:
9
src/app/layout/app/app.component.html
Normal file
9
src/app/layout/app/app.component.html
Normal 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>
|
||||
10
src/app/layout/app/app.component.scss
Normal file
10
src/app/layout/app/app.component.scss
Normal 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;
|
||||
}
|
||||
16
src/app/layout/app/app.component.ts
Normal file
16
src/app/layout/app/app.component.ts
Normal 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();
|
||||
}
|
||||
58
src/app/layout/topbar/topbar.component.html
Normal file
58
src/app/layout/topbar/topbar.component.html
Normal 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>
|
||||
73
src/app/layout/topbar/topbar.component.scss
Normal file
73
src/app/layout/topbar/topbar.component.scss
Normal 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; }
|
||||
}
|
||||
44
src/app/layout/topbar/topbar.component.ts
Normal file
44
src/app/layout/topbar/topbar.component.ts
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user