@@ -25,7 +25,7 @@
|
||||
"tsConfig": "tsconfig.app.json",
|
||||
"inlineStyleLanguage": "scss",
|
||||
"assets": [
|
||||
"src/favicon.ico",
|
||||
"src/assets/favicon.ico",
|
||||
{
|
||||
"glob": "**/*",
|
||||
"input": "public"
|
||||
|
||||
626
package-lock.json
generated
24
package.json
@@ -10,26 +10,26 @@
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular-devkit/build-angular": "~20.3.9",
|
||||
"@angular/animations": "20.3.10",
|
||||
"@angular-devkit/build-angular": "~20.3.12",
|
||||
"@angular/animations": "20.3.14",
|
||||
"@angular/cdk": "20.2.11",
|
||||
"@angular/common": "20.3.10",
|
||||
"@angular/compiler": "20.3.10",
|
||||
"@angular/core": "20.3.10",
|
||||
"@angular/forms": "20.3.10",
|
||||
"@angular/common": "~20.3.14",
|
||||
"@angular/compiler": "20.3.14",
|
||||
"@angular/core": "20.3.14",
|
||||
"@angular/forms": "~20.3.14",
|
||||
"@angular/material": "20.2.11",
|
||||
"@angular/platform-browser": "20.3.10",
|
||||
"@angular/router": "20.3.10",
|
||||
"@angular/platform-browser": "~20.3.14",
|
||||
"@angular/router": "~20.3.14",
|
||||
"@ngx-translate/core": "~17.0.0",
|
||||
"@ngx-translate/http-loader": "~17.0.0",
|
||||
"rxjs": "~7.8.0",
|
||||
"tslib": "^2.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular/build": "20.3.9",
|
||||
"@angular/cli": "20.3.9",
|
||||
"@angular/compiler-cli": "20.3.10",
|
||||
"@angular/platform-browser-dynamic": "20.3.10",
|
||||
"@angular/build": "20.3.12",
|
||||
"@angular/cli": "20.3.12",
|
||||
"@angular/compiler-cli": "20.3.14",
|
||||
"@angular/platform-browser-dynamic": "~20.3.14",
|
||||
"@types/jasmine": "~5.1.0",
|
||||
"jasmine-core": "~5.9.0",
|
||||
"karma": "~6.4.0",
|
||||
|
||||
@@ -6,7 +6,7 @@ import {provideAnimations} from '@angular/platform-browser/animations';
|
||||
import {provideHttpClient} from '@angular/common/http';
|
||||
import {provideTranslateService} from '@ngx-translate/core';
|
||||
import {provideTranslateHttpLoader} from '@ngx-translate/http-loader';
|
||||
import {Constants} from './constants/Constants';
|
||||
import {LocalStoreConstants} from './constants/LocalStoreConstants';
|
||||
|
||||
|
||||
const INITIAL_LANG = getInitialLang();
|
||||
@@ -30,7 +30,7 @@ export const appConfig: ApplicationConfig = {
|
||||
};
|
||||
|
||||
function getInitialLang(): string {
|
||||
const saved = localStorage.getItem(Constants.LANGUAGE_KEY);
|
||||
const saved = localStorage.getItem(LocalStoreConstants.LANGUAGE_KEY);
|
||||
if (saved) return saved;
|
||||
const nav = typeof navigator !== 'undefined' ? navigator.language?.toLowerCase() : 'en';
|
||||
return nav?.startsWith('de') ? 'de' : 'en';
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
<mat-toolbar color="primary">
|
||||
<span>{{ 'APP.TITLE' | translate }}</span>
|
||||
<span class="spacer"></span>
|
||||
|
||||
<!-- Language -->
|
||||
<mat-form-field appearance="outline" style="width: 170px; margin-right: 8px;">
|
||||
<mat-select [value]="lang.lang()" (selectionChange)="lang.use($event.value)">
|
||||
<mat-select-trigger>
|
||||
<img class="flag-icon" [src]="lang.lang() === 'de' ? '/assets/flags/de.svg' : '/assets/flags/gb.svg'"
|
||||
alt="" aria-hidden="true">
|
||||
<span style="margin-left: 8px;">
|
||||
{{ lang.lang() === 'de' ? ('LANG.DE' | translate) : ('LANG.EN' | translate) }}
|
||||
</span>
|
||||
</mat-select-trigger>
|
||||
|
||||
<mat-option value="de">
|
||||
<img class="flag-icon" src="/assets/flags/de.svg" alt="" aria-hidden="true">
|
||||
<span> {{ 'LANG.DE' | translate }} </span>
|
||||
</mat-option>
|
||||
|
||||
<mat-option value="en">
|
||||
<img class="flag-icon" src="/assets/flags/gb.svg" alt="" aria-hidden="true">
|
||||
<span> {{ 'LANG.EN' | translate }} </span>
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<!-- Theme -->
|
||||
<button mat-icon-button (click)="theme.toggle()">
|
||||
<mat-icon>{{ themeIcon() }}</mat-icon>
|
||||
</button>
|
||||
</mat-toolbar>
|
||||
|
||||
<main class="container app-surface">
|
||||
<router-outlet />
|
||||
</main>
|
||||
@@ -1,6 +1,10 @@
|
||||
import { Routes } from '@angular/router';
|
||||
import {WelcomeComponent} from './features/welcome/welcome';
|
||||
import {AboutComponent} from './pages/about/about.component';
|
||||
import {ProjectsComponent} from './pages/projects/projects.component';
|
||||
|
||||
export const routes: Routes = [
|
||||
{ path: '', component: WelcomeComponent },
|
||||
{ path: '', component: AboutComponent },
|
||||
{ path: 'about', component: AboutComponent},
|
||||
{ path: 'projects', component: ProjectsComponent},
|
||||
];
|
||||
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
.spacer { flex: 1 1 auto; }
|
||||
.container { max-width: 960px; margin: 24px auto; padding: 0 16px; }
|
||||
mat-form-field { --mdc-outlined-text-field-container-shape: 20px; }
|
||||
|
||||
.flag-icon {
|
||||
width: 20px;
|
||||
height: 14px;
|
||||
object-fit: cover;
|
||||
border-radius: 2px;
|
||||
box-shadow: 0 0 0 1px rgba(0,0,0,.08) inset;
|
||||
vertical-align: -2px;
|
||||
}
|
||||
mat-option .flag-icon { margin-right: 8px; }
|
||||
@@ -1,32 +0,0 @@
|
||||
import {Component, computed, effect, inject} from '@angular/core';
|
||||
import { RouterOutlet } from '@angular/router';
|
||||
import { MatToolbarModule } from '@angular/material/toolbar';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { ThemeService } from './service/theme.service';
|
||||
|
||||
import {LanguageService} from './service/language.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
standalone: true,
|
||||
imports: [
|
||||
RouterOutlet,
|
||||
MatToolbarModule, MatIconModule, MatButtonModule,
|
||||
MatFormFieldModule, MatSelectModule,
|
||||
FormsModule,
|
||||
TranslateModule
|
||||
],
|
||||
templateUrl: './app.html',
|
||||
styleUrl: './app.scss',
|
||||
})
|
||||
export class App {
|
||||
readonly theme = inject(ThemeService);
|
||||
readonly lang = inject(LanguageService);
|
||||
readonly themeIcon = computed(() => this.theme.theme() === 'dark' ? 'light_mode' : 'dark_mode');
|
||||
}
|
||||
16
src/app/constants/AssetsConstants.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
export class AssetsConstants {
|
||||
|
||||
static readonly ME = '/assets/me.webp';
|
||||
static readonly LOGO = '/assets/favicon.ico';
|
||||
static readonly FLAG_DE = '/assets/flags/de.svg';
|
||||
static readonly FLAG_EN = '/assets/flags/gb.svg';
|
||||
|
||||
//logos
|
||||
static readonly CHAMAELEON_LOGO = '/assets/logos/logo_chamaeleon.svg';
|
||||
static readonly TH_BINGEN_LOGO = '/assets/logos/bingen-logo-white.svg';
|
||||
static readonly ASSYST_LOG = '/assets/logos/assyst_gmbh_logo.jpg';
|
||||
static readonly COLORDIGITAL_LOGO = '/assets/logos/dmixcloud_logo.jpg';
|
||||
static readonly TERAPORT_LOGO = '/assets/logos/teraport_gmbh_logo.jpg';
|
||||
|
||||
static readonly CV: 'assets/cv/andreas-dahm-cv.pdf';
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
export class Constants{
|
||||
export class LocalStoreConstants {
|
||||
|
||||
static readonly THEME_KEY = 'theme';
|
||||
static readonly LANGUAGE_KEY = 'lang';
|
||||
4
src/app/constants/UrlConstants.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export class UrlConstants {
|
||||
static readonly LINKED_IN = 'https://www.linkedin.com/in/andreas-dahm-2395991ba';
|
||||
static readonly GIT_HUB = 'https://github.com/LoboTheDark';
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
<mat-card>
|
||||
<mat-card-header>
|
||||
<mat-card-title>{{ `WELCOME.TITLE` | translate }} </mat-card-title>
|
||||
<mat-card-subtitle>{{ `WELCOME.SUB` | translate }}</mat-card-subtitle>
|
||||
</mat-card-header>
|
||||
|
||||
<mat-card-content>
|
||||
<p>{{ `WELCOME.TEXT` | translate }}</p>
|
||||
<p style="margin-top: 8px;">
|
||||
{{ `WELCOME.COUNTER` | translate }}: <strong data-testid="counter">{{ count() }}</strong>
|
||||
</p>
|
||||
</mat-card-content>
|
||||
|
||||
<mat-card-actions>
|
||||
<button mat-raised-button color="primary"
|
||||
(click)="inc()"
|
||||
data-testid="inc"
|
||||
>
|
||||
<mat-icon>add</mat-icon> {{ `WELCOME.INC` | translate }}
|
||||
</button>
|
||||
<button mat-stroked-button
|
||||
(click)="reset()"
|
||||
data-testid="reset"
|
||||
>
|
||||
<mat-icon>restart_alt</mat-icon> {{ `WELCOME.RESET` | translate }}
|
||||
</button>
|
||||
</mat-card-actions>
|
||||
</mat-card>
|
||||
@@ -1,5 +0,0 @@
|
||||
mat-card {
|
||||
margin-top: 2rem;
|
||||
display: block;
|
||||
background-color: var(--app-card-background);
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
import { Component, signal } from '@angular/core';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import {TranslatePipe} from '@ngx-translate/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-welcome',
|
||||
standalone: true,
|
||||
imports: [MatCardModule, MatButtonModule, MatIconModule, TranslatePipe],
|
||||
templateUrl: './welcome.html',
|
||||
styleUrl: './welcome.scss',
|
||||
})
|
||||
export class WelcomeComponent {
|
||||
readonly count = signal(0);
|
||||
inc() { this.count.update(v => v + 1); }
|
||||
reset() { this.count.set(0); }
|
||||
}
|
||||
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
@@ -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
@@ -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
@@ -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="/projects" mat-button>{{ 'TOPBAR.HOBBY' | translate }}</a>
|
||||
<a routerLink="/projects" 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
@@ -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
@@ -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;
|
||||
}
|
||||
175
src/app/pages/about/about.component.html
Normal file
@@ -0,0 +1,175 @@
|
||||
<section class="about">
|
||||
<mat-card class="hero">
|
||||
<div class="photo">
|
||||
<img
|
||||
[ngSrc]="AssetsConstants.ME"
|
||||
width="320" height="400"
|
||||
alt="{{ 'ABOUT.ALT.PROFILE' | translate }}"
|
||||
draggable="false"
|
||||
oncontextmenu="return false;"
|
||||
priority />
|
||||
</div>
|
||||
|
||||
<div class="intro">
|
||||
<h1>{{ 'ABOUT.HELLO' | translate }}</h1>
|
||||
<p class="lead">
|
||||
{{ 'ABOUT.LEAD' | translate }}
|
||||
</p>
|
||||
|
||||
<div class="meta">
|
||||
<div class="row">
|
||||
<mat-icon aria-hidden="true">work</mat-icon>
|
||||
<span>{{ 'ABOUT.ROLE' | translate }}</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<mat-icon aria-hidden="true">location_on</mat-icon>
|
||||
<span>{{ 'ABOUT.LOCATION' | translate }}</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<mat-icon aria-hidden="true">email</mat-icon>
|
||||
<a href="" (click)="openMail($event)">
|
||||
{{ 'ABOUT.CONTACT_ME' | translate }}
|
||||
</a>
|
||||
</div>
|
||||
<div class="row">
|
||||
<mat-icon aria-hidden="true">link</mat-icon>
|
||||
<a href="{{UrlConstants.GIT_HUB}}" target="_blank" rel="noopener">GitHub</a>
|
||||
<span>·</span>
|
||||
<a href="{{UrlConstants.LINKED_IN}}" target="_blank" rel="noopener">LinkedIn</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</mat-card>
|
||||
|
||||
<mat-card class="skills">
|
||||
<h2>{{ 'ABOUT.SECTION.SKILLS' | translate }}</h2>
|
||||
<div class="chip-groups">
|
||||
<div>
|
||||
<h3>{{ 'ABOUT.SECTION.PRIMARY' | translate }}</h3>
|
||||
<mat-chip-set aria-label="Primary skills">
|
||||
@for (s of primarySkills; track primarySkills) {
|
||||
<mat-chip >{{ s | translate }}</mat-chip>
|
||||
}
|
||||
</mat-chip-set>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3>{{ 'ABOUT.SECTION.TOOLSET' | translate }}</h3>
|
||||
<mat-chip-set aria-label="Toolset">
|
||||
@for (t of toolset; track toolset) {
|
||||
<mat-chip>{{ t | translate }}</mat-chip>
|
||||
}
|
||||
</mat-chip-set>
|
||||
</div>
|
||||
</div>
|
||||
</mat-card>
|
||||
|
||||
<mat-card class="experience">
|
||||
<h2>{{ 'ABOUT.SECTION.EXPERIENCE' | translate }}</h2>
|
||||
<div class="xp-list">
|
||||
@for (entry of xpKeys; track entry) {
|
||||
<div class="xp-item">
|
||||
<div class="xp-head-grid">
|
||||
<div class="logo-wrap">
|
||||
<img
|
||||
src="{{entry.logo}}"
|
||||
alt=""
|
||||
class="company-logo"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</div>
|
||||
<div class="head-row">
|
||||
<strong>{{ (entry.key + '.ROLE') | translate }}</strong>
|
||||
<span class="time">{{ (entry.key + '.TIME') | translate }}</span>
|
||||
</div>
|
||||
|
||||
<div class="company-row">
|
||||
{{ (entry.key + '.COMPANY') | translate }}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="highlights">
|
||||
<ul>
|
||||
<li>{{ entry.key + '.HIGHLIGHTS.P1' | translate }}</li>
|
||||
<li>{{ entry.key + '.HIGHLIGHTS.P2' | translate }}</li>
|
||||
<li>{{ entry.key + '.HIGHLIGHTS.P3' | translate }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if(entry.key !== xpKeys.at(xpKeys.length-1)?.key)
|
||||
{
|
||||
<mat-divider></mat-divider>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</mat-card>
|
||||
<mat-card class="projects">
|
||||
<h2>{{ 'ABOUT.SECTION.PROJECTS' | translate }}</h2>
|
||||
|
||||
<div class="xp-list">
|
||||
@for (entry of projectKeys; track entry) {
|
||||
<div class="xp-item">
|
||||
<div class="head-row">
|
||||
<strong>{{ (entry.key + '.TITLE') | translate }}</strong>
|
||||
</div>
|
||||
<div class="company-row">
|
||||
{{ (entry.key + '.DESCRIPTION') | translate }}
|
||||
</div>
|
||||
<div class="link-row">
|
||||
<a class="link-with-icon"
|
||||
href="{{entry.externalLink}}"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer">
|
||||
<mat-icon>open_in_new</mat-icon>
|
||||
{{ (entry.key + '.LINK_EXTERNAL') | translate }}
|
||||
</a>
|
||||
</div>
|
||||
<div class="link-row">
|
||||
<a class="link-with-icon"
|
||||
href="{{entry.internalLink}}"
|
||||
rel="noopener noreferrer">
|
||||
<mat-icon>link</mat-icon>
|
||||
{{ (entry.key + '.LINK_INTERNAL') | translate }}
|
||||
</a>
|
||||
</div>
|
||||
<div class="highlights-noMargin">
|
||||
<ul>
|
||||
<li>{{ entry.key + '.HIGHLIGHTS.P1' | translate }}</li>
|
||||
<li>{{ entry.key + '.HIGHLIGHTS.P2' | translate }}</li>
|
||||
<li>{{ entry.key + '.HIGHLIGHTS.P3' | translate }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if(entry.key !== projectKeys.at(projectKeys.length-1)?.key)
|
||||
{
|
||||
<mat-divider></mat-divider>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</mat-card>
|
||||
|
||||
<mat-card class="education">
|
||||
<h2>{{ 'ABOUT.SECTION.EDUCATION' | translate }}</h2>
|
||||
|
||||
<div class="xp-list">
|
||||
<div class="xp-item">
|
||||
@for (entry of educationKeys; track entry) {
|
||||
<div class="head-row">
|
||||
<strong>{{ (entry.key + '.WHERE') | translate }}</strong>
|
||||
<span class="time">{{ (entry.key + '.WHEN') | translate }}</span>
|
||||
</div>
|
||||
<div class="company-row">
|
||||
{{ (entry.key + '.WHAT') | translate }}
|
||||
</div>
|
||||
|
||||
@if(entry.key !== educationKeys.at(educationKeys.length-1)?.key)
|
||||
{
|
||||
<mat-divider style="margin-top: .5rem; margin-bottom: .5rem"></mat-divider>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</mat-card>
|
||||
</section>
|
||||
217
src/app/pages/about/about.component.scss
Normal file
@@ -0,0 +1,217 @@
|
||||
.about {
|
||||
display: grid;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
/* Hero block: Photo + Intro */
|
||||
.hero {
|
||||
display: grid;
|
||||
grid-template-columns: 240px 1fr;
|
||||
gap: 1.25rem;
|
||||
border-radius: 16px;
|
||||
background: var(--app-card-background);
|
||||
|
||||
.photo {
|
||||
align-items:flex-start; justify-content:center;
|
||||
img {
|
||||
display:block;
|
||||
width: 100%; height: auto;
|
||||
max-width: 220px;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 6px 24px rgba(0,0,0,.25);
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
|
||||
.intro {
|
||||
display:flex; flex-direction:column; gap:.5rem;
|
||||
h1 { margin-top: .25rem }
|
||||
.lead { opacity:.9; margin: .25rem 0 0.5rem; }
|
||||
|
||||
.meta {
|
||||
display:flex; flex-direction:column; gap:.25rem; margin-bottom: 0.5rem;
|
||||
.row {
|
||||
display:flex; align-items:center; gap:.4rem;
|
||||
a { color: inherit; }
|
||||
}
|
||||
}
|
||||
|
||||
.actions {
|
||||
display:flex; gap:.5rem; flex-wrap:wrap; margin-top:.5rem;
|
||||
.mat-icon { margin-right:.25rem; }
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Skills block */
|
||||
.skills {
|
||||
border-radius: 16px;
|
||||
padding: 5px;
|
||||
background: var(--app-card-background);
|
||||
|
||||
h2 { margin-top: .25rem; margin-left: .25rem; }
|
||||
.chip-groups {
|
||||
margin-left: .25rem;
|
||||
display:grid; gap:1rem;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
margin-bottom: .5rem;
|
||||
h3 { margin: .2rem 0 .4rem; font-size: .95rem; opacity:.85; }
|
||||
mat-chip-set {
|
||||
display:flex; flex-wrap:wrap; gap:.4rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Experience block */
|
||||
.experience {
|
||||
border-radius: 16px;
|
||||
padding: 5px;
|
||||
background: var(--app-card-background);
|
||||
h2 { margin-top: .25rem;margin-left: .25rem; }
|
||||
.xp-list {
|
||||
margin-left: .25rem;
|
||||
display: grid; gap: .75rem;
|
||||
}
|
||||
.xp-item {
|
||||
.xp-head {
|
||||
display:flex; align-items:baseline; gap:.5rem;
|
||||
.time { opacity:.75; font-size:.9rem; }
|
||||
}
|
||||
.xp-sub { opacity:.9; margin-bottom:.25rem; }
|
||||
ul { margin: .25rem 0 .5rem 1.15rem; }
|
||||
}
|
||||
}
|
||||
|
||||
/* Experience block */
|
||||
.projects {
|
||||
border-radius: 16px;
|
||||
padding: 5px;
|
||||
background: var(--app-card-background);
|
||||
h2 { margin-top: .25rem;margin-left: .25rem; }
|
||||
.xp-list {
|
||||
margin-left: .25rem;
|
||||
display: grid; gap: .75rem;
|
||||
}
|
||||
.xp-item {
|
||||
.xp-head {
|
||||
display:flex; align-items:baseline; gap:.5rem;
|
||||
.time { opacity:.75; font-size:.9rem; }
|
||||
}
|
||||
.xp-sub { opacity:.9; margin-bottom:.25rem; }
|
||||
ul { margin: .25rem 0 .5rem 1.15rem; }
|
||||
}
|
||||
}
|
||||
|
||||
/* Experience block */
|
||||
.education {
|
||||
border-radius: 16px;
|
||||
padding: 5px;
|
||||
background: var(--app-card-background);
|
||||
h2 { margin-top: .25rem;margin-left: .25rem; }
|
||||
.xp-list {
|
||||
margin-left: .25rem;
|
||||
display: grid; gap: .75rem;
|
||||
}
|
||||
.xp-item {
|
||||
.xp-head {
|
||||
display:flex; align-items:baseline; gap:.5rem;
|
||||
.time { opacity:.75; font-size:.9rem; }
|
||||
}
|
||||
.xp-sub { opacity:.9; margin-bottom:.25rem; }
|
||||
ul { margin: .25rem 0 .5rem 1.15rem; }
|
||||
}
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 900px) {
|
||||
.hero { grid-template-columns: 1fr; }
|
||||
.hero .photo { justify-content: flex-start; }
|
||||
.skills .chip-groups { grid-template-columns: 1fr; }
|
||||
}
|
||||
|
||||
.xp-head-grid {
|
||||
display: grid;
|
||||
grid-template-columns: calc(48px + .75rem) 1fr; /* 1: Logo, 2: Text */
|
||||
grid-template-rows: auto auto; /* 1: Role/Time, 2: Company */
|
||||
column-gap: .75rem;
|
||||
}
|
||||
|
||||
.logo-wrap {
|
||||
grid-row: 1 / span 2;
|
||||
grid-column: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.company-logo {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
object-fit: contain;
|
||||
opacity: .9;
|
||||
border-radius: 10%;
|
||||
background-color: var(--app-logo-bg);
|
||||
}
|
||||
|
||||
|
||||
.head-row {
|
||||
grid-row: 1;
|
||||
grid-column: 2;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: baseline;
|
||||
gap: .5rem 1rem;
|
||||
|
||||
strong {
|
||||
font-size: 1rem;
|
||||
}
|
||||
.time {
|
||||
opacity: .75; font-size: .9rem;
|
||||
}
|
||||
}
|
||||
|
||||
.company-row {
|
||||
grid-row: 2;
|
||||
grid-column: 2;
|
||||
margin-top: .1rem;
|
||||
opacity: .85;
|
||||
}
|
||||
|
||||
.link-row {
|
||||
grid-row: 2;
|
||||
grid-column: 2;
|
||||
margin-top: .1rem;
|
||||
opacity: .85;
|
||||
vertical-align: center;
|
||||
}
|
||||
|
||||
.link-with-icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: .35rem;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.link-with-icon mat-icon {
|
||||
font-size: 18px;
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
}
|
||||
|
||||
.highlights {
|
||||
margin-top: .4rem;
|
||||
margin-left: .75rem;
|
||||
padding-left: 1.2rem;
|
||||
|
||||
li {
|
||||
margin: .2rem 0;
|
||||
}
|
||||
}
|
||||
|
||||
.highlights-noMargin {
|
||||
margin-top: .4rem;
|
||||
|
||||
li {
|
||||
margin: .2rem 0;
|
||||
}
|
||||
}
|
||||
139
src/app/pages/about/about.component.ts
Normal file
@@ -0,0 +1,139 @@
|
||||
import { Component} from '@angular/core';
|
||||
import { CommonModule, NgOptimizedImage } from '@angular/common';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { MatChipsModule } from '@angular/material/chips';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatDividerModule } from '@angular/material/divider';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import {UrlConstants} from '../../constants/UrlConstants';
|
||||
import {AssetsConstants} from '../../constants/AssetsConstants';
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'app-about',
|
||||
standalone: true,
|
||||
imports: [
|
||||
CommonModule, NgOptimizedImage,
|
||||
MatCardModule, MatChipsModule, MatIconModule, MatButtonModule, MatDividerModule,
|
||||
TranslateModule
|
||||
],
|
||||
templateUrl: './about.component.html',
|
||||
styleUrl: './about.component.scss'
|
||||
})
|
||||
export class AboutComponent {
|
||||
cvHref = AssetsConstants.CV;
|
||||
|
||||
xpKeys = [
|
||||
{
|
||||
key: 'ABOUT.XP.COMPANY8',
|
||||
logo: AssetsConstants.TERAPORT_LOGO
|
||||
},
|
||||
{
|
||||
key: 'ABOUT.XP.COMPANY7',
|
||||
logo: AssetsConstants.COLORDIGITAL_LOGO
|
||||
},
|
||||
{
|
||||
key: 'ABOUT.XP.COMPANY6',
|
||||
logo: AssetsConstants.COLORDIGITAL_LOGO
|
||||
},
|
||||
{
|
||||
key: 'ABOUT.XP.COMPANY5',
|
||||
logo: AssetsConstants.ASSYST_LOG
|
||||
},
|
||||
{
|
||||
key: 'ABOUT.XP.COMPANY4',
|
||||
logo: AssetsConstants.ASSYST_LOG
|
||||
},
|
||||
{
|
||||
key: 'ABOUT.XP.COMPANY3',
|
||||
logo: AssetsConstants.ASSYST_LOG
|
||||
},
|
||||
{
|
||||
key: 'ABOUT.XP.COMPANY2',
|
||||
logo: AssetsConstants.TH_BINGEN_LOGO
|
||||
},
|
||||
{
|
||||
key: 'ABOUT.XP.COMPANY1',
|
||||
logo: AssetsConstants.TH_BINGEN_LOGO
|
||||
},
|
||||
{
|
||||
key: 'ABOUT.XP.COMPANY0',
|
||||
logo: AssetsConstants.CHAMAELEON_LOGO
|
||||
}
|
||||
];
|
||||
|
||||
projectKeys = [
|
||||
{
|
||||
key: 'ABOUT.PROJECT.P2',
|
||||
externalLink: 'https://andreas-dahm.eu/',
|
||||
internalLink: 'projects'
|
||||
},
|
||||
{
|
||||
key: 'ABOUT.PROJECT.P1',
|
||||
externalLink: 'https://store.steampowered.com/app/1532640/El_Mucho/',
|
||||
internalLink: 'projects'
|
||||
},
|
||||
{
|
||||
key: 'ABOUT.PROJECT.P0',
|
||||
externalLink: 'https://itch.io/c/6628860/lobos-collection',
|
||||
internalLink: 'projects'
|
||||
}
|
||||
];
|
||||
|
||||
educationKeys = [
|
||||
{
|
||||
key: 'ABOUT.EDUCATION.E6'
|
||||
},
|
||||
{
|
||||
key: 'ABOUT.EDUCATION.E5'
|
||||
},
|
||||
{
|
||||
key: 'ABOUT.EDUCATION.E4'
|
||||
},
|
||||
{
|
||||
key: 'ABOUT.EDUCATION.E3'
|
||||
},
|
||||
{
|
||||
key: 'ABOUT.EDUCATION.E2'
|
||||
},
|
||||
{
|
||||
key: 'ABOUT.EDUCATION.E1'
|
||||
},
|
||||
{
|
||||
key: 'ABOUT.EDUCATION.E0'
|
||||
}
|
||||
]
|
||||
|
||||
primarySkills = [
|
||||
'ABOUT.SKILLS.JAVA',
|
||||
'ABOUT.SKILLS.SPRING',
|
||||
'ABOUT.SKILLS.ANGULAR',
|
||||
'ABOUT.SKILLS.DOCKER',
|
||||
'ABOUT.SKILLS.UNITY',
|
||||
'ABOUT.SKILLS.PYTHON',
|
||||
'ABOUT.SKILLS.CSHARP',
|
||||
'ABOUT.SKILLS.TYPESCRIPT'
|
||||
];
|
||||
|
||||
toolset = [
|
||||
'ABOUT.TOOLS.GIT',
|
||||
'ABOUT.TOOLS.GITHUB',
|
||||
'ABOUT.TOOLS.GITLAB',
|
||||
'ABOUT.TOOLS.JENKINS',
|
||||
'ABOUT.TOOLS.K8S',
|
||||
'ABOUT.TOOLS.POSTGRES',
|
||||
'ABOUT.TOOLS.MONGO',
|
||||
'ABOUT.TOOLS.GRAFANA',
|
||||
];
|
||||
|
||||
openMail(event: Event) {
|
||||
event.preventDefault();
|
||||
const user = 'andreas.dahm';
|
||||
const domain = 'gmail.com';
|
||||
globalThis.location.href = `mailto:${user}@${domain}`;
|
||||
}
|
||||
|
||||
protected readonly UrlConstants = UrlConstants;
|
||||
protected readonly AssetsConstants = AssetsConstants;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<p>project-details works!</p>
|
||||
11
src/app/pages/project-details/project-details.component.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-project-details',
|
||||
imports: [],
|
||||
templateUrl: './project-details.component.html',
|
||||
styleUrl: './project-details.component.scss',
|
||||
})
|
||||
export class ProjectDetailsComponent {
|
||||
|
||||
}
|
||||
1
src/app/pages/projects/projects.component.html
Normal file
@@ -0,0 +1 @@
|
||||
<p>Working in progress!</p>
|
||||
0
src/app/pages/projects/projects.component.scss
Normal file
11
src/app/pages/projects/projects.component.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-projects',
|
||||
imports: [],
|
||||
templateUrl: './projects.component.html',
|
||||
styleUrl: './projects.component.scss',
|
||||
})
|
||||
export class ProjectsComponent {
|
||||
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Injectable, inject, signal } from '@angular/core';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import {Constants} from '../constants/Constants';
|
||||
import {LocalStoreConstants} from '../constants/LocalStoreConstants';
|
||||
|
||||
type Lang = 'de' | 'en';
|
||||
|
||||
@@ -19,12 +19,12 @@ export class LanguageService {
|
||||
use(l: Lang) {
|
||||
this.lang.set(l);
|
||||
this.translate.use(l);
|
||||
try { localStorage.setItem(Constants.LANGUAGE_KEY, l); } catch {}
|
||||
try { localStorage.setItem(LocalStoreConstants.LANGUAGE_KEY, l); } catch {}
|
||||
}
|
||||
|
||||
private getInitial(): Lang {
|
||||
try {
|
||||
const stored = localStorage.getItem(Constants.LANGUAGE_KEY) as Lang | null;
|
||||
const stored = localStorage.getItem(LocalStoreConstants.LANGUAGE_KEY) as Lang | null;
|
||||
if (stored === 'de' || stored === 'en') return stored;
|
||||
} catch {}
|
||||
const browser = (navigator.language || 'en').toLowerCase();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Injectable, NgZone, signal } from '@angular/core';
|
||||
import {Constants} from '../constants/Constants';
|
||||
import {LocalStoreConstants} from '../constants/LocalStoreConstants';
|
||||
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
@@ -20,13 +20,13 @@ export class ReloadService {
|
||||
|
||||
|
||||
private informListeners(e: StorageEvent, zone: NgZone) {
|
||||
if (e.key === Constants.LANGUAGE_KEY) {
|
||||
if (e.key === LocalStoreConstants.LANGUAGE_KEY) {
|
||||
zone.run(() => this._languageChangedTick.update(v => v + 1));
|
||||
}
|
||||
}
|
||||
|
||||
bumpLanguageChanged(): void {
|
||||
this._reloadTick.update(v => v + 1);
|
||||
localStorage.setItem(Constants.RELOAD_ALL_LANG_LISTENER_KEY, String(Date.now()));
|
||||
localStorage.setItem(LocalStoreConstants.RELOAD_ALL_LANG_LISTENER_KEY, String(Date.now()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Injectable, effect, inject, signal } from '@angular/core';
|
||||
import { DOCUMENT } from '@angular/common';
|
||||
import { OverlayContainer } from '@angular/cdk/overlay';
|
||||
import {Constants} from '../constants/Constants';
|
||||
import {LocalStoreConstants} from '../constants/LocalStoreConstants';
|
||||
|
||||
type Theme = 'light' | 'dark';
|
||||
|
||||
@@ -20,13 +20,13 @@ export class ThemeService {
|
||||
body.classList.toggle('dark', isDark);
|
||||
overlayEl.classList.toggle('dark', isDark);
|
||||
|
||||
try { localStorage.setItem(Constants.THEME_KEY, this.theme()); } catch {}
|
||||
try { localStorage.setItem(LocalStoreConstants.THEME_KEY, this.theme()); } catch {}
|
||||
});
|
||||
|
||||
try {
|
||||
const mm = globalThis.matchMedia('(prefers-color-scheme: dark)');
|
||||
mm.addEventListener('change', e => {
|
||||
const stored = localStorage.getItem(Constants.THEME_KEY) as Theme | null;
|
||||
const stored = localStorage.getItem(LocalStoreConstants.THEME_KEY) as Theme | null;
|
||||
if (!stored) this.setTheme(e.matches ? 'dark' : 'light');
|
||||
});
|
||||
} catch {}
|
||||
@@ -37,7 +37,7 @@ export class ThemeService {
|
||||
|
||||
private getInitialTheme(): Theme {
|
||||
try {
|
||||
const stored = localStorage.getItem(Constants.THEME_KEY) as Theme | null;
|
||||
const stored = localStorage.getItem(LocalStoreConstants.THEME_KEY) as Theme | null;
|
||||
if (stored === 'dark' || stored === 'light') return stored;
|
||||
} catch {}
|
||||
try {
|
||||
|
||||
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 33 KiB |
@@ -1,13 +1,219 @@
|
||||
{
|
||||
"APP": { "TITLE": "Playground" },
|
||||
"WELCOME": {
|
||||
"TITLE": "Willkommen 👋",
|
||||
"SUB": "Angular 20 + Material",
|
||||
"TEXT": "Das ist eine einfache Start-Komponente als Basis.",
|
||||
"COUNTER": "Zähler",
|
||||
"INC": "Inkrementieren",
|
||||
"RESET": "Zurücksetzen"
|
||||
"APP": {
|
||||
"TITLE": "Playground",
|
||||
"COPYRIGHT": "Bilder urheberrechtlich geschützt, keine Nutzung ohne Zustimmung!"
|
||||
},
|
||||
"TOPBAR": {
|
||||
"ABOUT": "Über mich",
|
||||
"CONTACT": "Kontakt",
|
||||
"PROJECTS": "Projekte",
|
||||
"HOBBY": "Hobbies",
|
||||
"SETTINGS": "Einstellungen",
|
||||
"LANGUAGE": "Sprache",
|
||||
"APPEARANCE": "Darstellung"
|
||||
},
|
||||
"THEME": { "LIGHT": "Hell", "DARK": "Dunkel" },
|
||||
"LANG": { "LABEL": "Sprache", "EN": "Englisch", "DE": "Deutsch" }
|
||||
"LANG": { "LABEL": "Sprache", "EN": "Englisch", "DE": "Deutsch" },
|
||||
"ABOUT": {
|
||||
"ALT": { "PROFILE": "Profilfoto von Andreas Dahm" },
|
||||
"HELLO": "Hallo, ich bin Andreas Dahm.",
|
||||
"LEAD": "Bereits in meiner Ausbildung zum Fachinformatiker hat mich fasziniert, wie man durch Code komplexe Probleme elegant lösen kann. Nach meinem Studium der Angewandten Informatik an der FH Bingen konnte ich diese Begeisterung in verschiedensten Projekten vertiefen – von 3D-Simulationen bis zu modernen Web-Applikationen. Heute arbeite ich als Senior Software Developer und Architekt mit Fokus auf Java, Angular und DevOps. Danke, dass du vorbeischaust!",
|
||||
"ROLE": "Senior Software Entwickler / Full-Stack Entwickler / Softwarearchitekt",
|
||||
"LOCATION": "München · Remote",
|
||||
"DOWNLOAD_CV": "Lebenslauf herunterladen",
|
||||
"VIEW_PROJECTS": "Projekte ansehen",
|
||||
"CONTACT_ME": "Kontaktiere mich",
|
||||
"SECTION": {
|
||||
"SKILLS": "Fähigkeiten & Stack",
|
||||
"PRIMARY": "Schwerpunkte",
|
||||
"TOOLSET": "Toolset",
|
||||
"EXPERIENCE": "Erfahrung",
|
||||
"PROJECTS": "Projekte",
|
||||
"EDUCATION": "Ausbildung"
|
||||
},
|
||||
"SKILLS": {
|
||||
"JAVA": "Java 8/Java 21+",
|
||||
"SPRING": "Spring Boot 2/3",
|
||||
"ANGULAR": "Angular 20+",
|
||||
"DOCKER": "Docker",
|
||||
"UNITY": "Unity",
|
||||
"PYTHON": "Python",
|
||||
"CSHARP": "C#",
|
||||
"TYPESCRIPT": "TypeScript"
|
||||
},
|
||||
"TOOLS": {
|
||||
"GIT": "Git",
|
||||
"GITHUB": "Github",
|
||||
"GITLAB": "Gitlab",
|
||||
"JENKINS": "Jenkins",
|
||||
"K8S": "Kubernetes / k3d",
|
||||
"POSTGRES": "PostgreSQL",
|
||||
"MONGO": "MongoDB",
|
||||
"GRAFANA": "Grafana/Prometheus"
|
||||
},
|
||||
"XP": {
|
||||
"COMPANY8": {
|
||||
"COMPANY": "Teraport GmbH",
|
||||
"ROLE": "Senior Software Developer / Architect",
|
||||
"TIME": "Feb. 2024 – heute",
|
||||
"HIGHLIGHTS": {
|
||||
"P1": "Architektur und Implementierung der Datenbankanbindung mit Hibernate 6.x.",
|
||||
"P2": "Konzeption und Entwicklung einer Full-Stack-Webanwendung für Kollisionsanalysen (Angular + Spring Boot + Docker).",
|
||||
"P3": "Entwicklung eines Kostenanalysetools mit dem Namen MIDO."
|
||||
}
|
||||
},
|
||||
"COMPANY7": {
|
||||
"COMPANY": "ColorDigital GmbH",
|
||||
"ROLE": "Lead Software Developer",
|
||||
"TIME": "März 2023 – Dez. 2023",
|
||||
"HIGHLIGHTS": {
|
||||
"P1": "Planung einer neuen Cloud-Architektur zur Migration eines umfangreichen Legacy-Monolithen in eine moderne Microservice-Plattform.",
|
||||
"P2": "Leitung eines Teams von drei bis vier Backend-Entwickler:innen.",
|
||||
"P3": "Zusammenarbeit mit Partnern und Kund:innen bei der Integration ihrer Systeme in die DMIx-Cloud."
|
||||
}
|
||||
},
|
||||
"COMPANY6": {
|
||||
"COMPANY": "ColorDigital GmbH",
|
||||
"ROLE": "Senior Software Developer",
|
||||
"TIME": "März 2021 – März 2023",
|
||||
"HIGHLIGHTS": {
|
||||
"P1": "Arbeit an der DMIx-Core-Cloud, darunter die Implementierung einer Elasticsearch-basierten Echtzeit-Suchfunktion.",
|
||||
"P2": "Implementierung des neuen Browser-Tools „Moodboard“ (Node.js + Vue.js).",
|
||||
"P3": "Entwicklung von „PAX“, einem Tool für den Datenaustausch zwischen der DMIx-Cloud und PLM/ERP-Systemen, inkl. CI/CD-Integration."
|
||||
}
|
||||
},
|
||||
"COMPANY5": {
|
||||
"COMPANY": "Assyst GmbH",
|
||||
"ROLE": "Teamleitung",
|
||||
"TIME": "Sep. 2015 – Feb. 2021",
|
||||
"HIGHLIGHTS": {
|
||||
"P1": "Teamleitung der Vidya-Softwareabteilung, inkl. Personalverantwortung und Bewerbungsprozesse.",
|
||||
"P2": "Leitung mehrerer Projekte mit externen Partnern und Freelancer:innen.",
|
||||
"P3": "Steuerung strategischer Initiativen im Bereich 3D-Softwareentwicklung."
|
||||
}
|
||||
},
|
||||
"COMPANY4": {
|
||||
"COMPANY": "Assyst GmbH",
|
||||
"ROLE": "Software Engineer",
|
||||
"TIME": "Apr. 2010 – Feb. 2021",
|
||||
"HIGHLIGHTS": {
|
||||
"P1": "Arbeit an Kerntechnologien wie Algorithmen, Simulation, Rendering und Kollisionserkennung.",
|
||||
"P2": "Einsatz verschiedener Technologien, u. a. Java, OpenCL, OpenGL, GLSL-Shader, C++ und REST-Schnittstellen.",
|
||||
"P3": "Mitarbeit an nationalen und internationalen Forschungsprojekten (EU7 und ZIM)."
|
||||
}
|
||||
},
|
||||
"COMPANY3": {
|
||||
"COMPANY": "Assyst GmbH",
|
||||
"ROLE": "Praktikum und Diplomarbeit",
|
||||
"TIME": "Apr. 2009 – Apr. 2010",
|
||||
"HIGHLIGHTS": {
|
||||
"P1": "Implementierung produktiver Funktionen in der Echtzeit-Kleidersimulationssoftware Vidya.",
|
||||
"P2": "Durchführung von Recherchen und Literaturstudien für die Diplomarbeit.",
|
||||
"P3": "Erfolgreiche Umsetzung des Diplomthemas in Vidya."
|
||||
}
|
||||
},
|
||||
"COMPANY2": {
|
||||
"COMPANY": "TH Bingen (University of Applied Sciences)",
|
||||
"ROLE": "Tutor für Grundlagen der Java-Programmierung",
|
||||
"TIME": "Apr. 2008 – Aug. 2008",
|
||||
"HIGHLIGHTS": {
|
||||
"P1": "Erstellung praktischer und theoretischer Lehrmaterialien.",
|
||||
"P2": "Durchführung einer 20-stündigen Vorlesungsreihe für den Bioinformatik-Studiengang.",
|
||||
"P3": "Unterstützung bei praktischen Programmierübungen."
|
||||
}
|
||||
},
|
||||
"COMPANY1": {
|
||||
"COMPANY": "TH Bingen (University of Applied Sciences)",
|
||||
"ROLE": "Wissenschaftliche Hilfskraft",
|
||||
"TIME": "Okt. 2007 – Apr. 2008",
|
||||
"HIGHLIGHTS": {
|
||||
"P1": "Evaluation verschiedener Web-2.0-Technologien.",
|
||||
"P2": "Implementierung einer Java-basierten Schnittstelle zu Microsoft Excel.",
|
||||
"P3": "Integration des MIT-Exhibit-Frameworks und Vorbereitung für die CeBIT 2008."
|
||||
}
|
||||
},
|
||||
"COMPANY0": {
|
||||
"COMPANY": "Chamaeleon AG",
|
||||
"ROLE": "Ausbildung Fachinformatiker Anwendungsentwicklung",
|
||||
"TIME": "Jul. 2002 – Jun. 2005",
|
||||
"HIGHLIGHTS": {
|
||||
"P1": "Entwicklung in PERL, PHP und ASP.",
|
||||
"P2": "Portierung, Wartung und Reengineering von bestender Software.",
|
||||
"P3": "Regelmäßige Durchführung von Systemtests und Qualitätskontrollen, sowie deren Dokumentation."
|
||||
}
|
||||
}
|
||||
},
|
||||
"PROJECT": {
|
||||
"P2": {
|
||||
"TITLE": "Playground",
|
||||
"DESCRIPTION": "Dieses Projekt ist, wie der Name schon sagt, ein persönlicher Playground, der im Laufe der Zeit wachsen soll. Es präsentiert verschiedene private Projekte, an denen ich arbeite, und dokumentiert deren Fortschritt, zentrale Ideen und wichtige Meilensteine.",
|
||||
"LINK_EXTERNAL": "andreas-dahm.eu",
|
||||
"LINK_INTERNAL": "Projektdetails",
|
||||
"HIGHLIGHTS": {
|
||||
"P1": "Einsatz moderner Technologien und CI/CD-Pipelines (Angular 20+, Spring Boot 4, GitHub).",
|
||||
"P2": "Präsentation persönlicher Projekte und kontinuierliche Verbesserung algorithmischer Fähigkeiten.",
|
||||
"P3": "Vertiefung von JavaScript/TypeScript-, Angular- und Spring-Boot-Kenntnissen durch praktisches Arbeiten."
|
||||
}
|
||||
},
|
||||
"P1": {
|
||||
"TITLE": "El Mucho",
|
||||
"DESCRIPTION": "Dies ist mein erstes veröffentlichtes Spiel auf Steam. Ich habe es gemeinsam mit meinem Bruder entwickelt und damit eines meiner persönlichen Ziele erreicht: ein eigenes Videospiel zu erstellen und zu veröffentlichen.",
|
||||
"LINK_EXTERNAL": "Steam Store",
|
||||
"LINK_INTERNAL": "Projektdetails",
|
||||
"HIGHLIGHTS": {
|
||||
"P1": "Veröffentlichung eines Spiels auf Steam und Integration der Steam-API.",
|
||||
"P2": "Konzeption, Planung und vollständige Entwicklung eines eigenen Spiels.",
|
||||
"P3": "Implementierung komplexer Algorithmen wie einer eigenen A*-Pfadfindungslogik und Spiel-KI."
|
||||
}
|
||||
},
|
||||
"P0": {
|
||||
"TITLE": "Game Jams",
|
||||
"DESCRIPTION": "Dieser Bereich zeigt einige meiner bisherigen Game-Jam-Beiträge. Game Jams sind eine großartige Möglichkeit, neue Spielideen auszuprobieren und schnell zu überprüfen, ob ein Game-Loop funktioniert. Die engen Zeitvorgaben fördern den Fokus, es ist faszinierend, was man in so kurzer Zeit alles schaffen kann.",
|
||||
"LINK_EXTERNAL": "Itch.io Sammlung",
|
||||
"LINK_INTERNAL": "Projektdetails",
|
||||
"HIGHLIGHTS": {
|
||||
"P1": "Planung eines realistischen Projektumfangs mit einem Team, der innerhalb von 48 Stunden umsetzbar ist.",
|
||||
"P2": "Lernen, fokussiert und effizient unter strengen Zeitvorgaben zu arbeiten.",
|
||||
"P3": "Die Freude zu erleben, in kurzer Zeit ein spielbares Projekt zu erstellen und andere damit spielen zu sehen."
|
||||
}
|
||||
}
|
||||
},
|
||||
"EDUCATION": {
|
||||
"E6": {
|
||||
"WHERE": "FH Bingen (University of Applied Sciences)",
|
||||
"WHEN": "2006 - 2010",
|
||||
"WHAT": "Diplom (FH), Angewandte Informatik (Schnitt 1.4)"
|
||||
},
|
||||
"E5": {
|
||||
"WHERE": "FH Koblenz (University of Applied Sciences)",
|
||||
"WHEN": "2005 - 2006",
|
||||
"WHAT": "1 Semester Ingenieurinformatik"
|
||||
},
|
||||
"E4": {
|
||||
"WHERE": "BBS Montabaur",
|
||||
"WHEN": "2002 - 2005",
|
||||
"WHAT": "Ausbildung zum Fachinformatiker Anwendungsentwicklung"
|
||||
},
|
||||
"E3": {
|
||||
"WHERE": "BBS Andernach - Höhere Berufsbachschule (Informatik)",
|
||||
"WHEN": "2000 - 2002",
|
||||
"WHAT": "Fachhochschulreife"
|
||||
},
|
||||
"E2": {
|
||||
"WHERE": "BBS Andernach - Berufsfachschule Technik (E-Technik)",
|
||||
"WHEN": "1998 - 2000",
|
||||
"WHAT": "Realschulabschluss"
|
||||
},
|
||||
"E1": {
|
||||
"WHERE": "Hauptschule Hinter Burg Mayen (Albert Schweitzer Realschule plus)",
|
||||
"WHEN": "1995 - 1998 ",
|
||||
"WHAT": "Hauptschulabschluss"
|
||||
},
|
||||
"E0": {
|
||||
"WHERE": "Albert Schweitzer Realschule Mayen",
|
||||
"WHEN": "1993 - 1995",
|
||||
"WHAT": "Kein Abschluss. Wechsel zur Hauptschule"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,209 @@
|
||||
{
|
||||
"APP": { "TITLE": "Playground" },
|
||||
"WELCOME": {
|
||||
"TITLE": "Welcome 👋",
|
||||
"SUB": "Angular 20 + Material",
|
||||
"TEXT": "This is a simple start component.",
|
||||
"COUNTER": "Counter",
|
||||
"INC": "Increment",
|
||||
"RESET": "Reset"
|
||||
"APP": {
|
||||
"TITLE": "Playground",
|
||||
"COPYRIGHT": "Images protected by copyright, no use without permission!"
|
||||
},
|
||||
"TOPBAR": {
|
||||
"ABOUT": "About me",
|
||||
"CONTACT": "Contact",
|
||||
"PROJECTS": "Projects",
|
||||
"HOBBY": "Hobby's",
|
||||
"SETTINGS": "Settings",
|
||||
"LANGUAGE": "Language",
|
||||
"APPEARANCE": "Appearance"
|
||||
},
|
||||
"THEME": { "LIGHT": "Light", "DARK": "Dark" },
|
||||
"LANG": { "LABEL": "Language", "EN": "English", "DE": "Deutsch" }
|
||||
"LANG": { "LABEL": "Language", "EN": "English", "DE": "Deutsch" },
|
||||
"ABOUT": {
|
||||
"ALT": { "PROFILE": "Profile photo of Andreas Dahm" },
|
||||
"HELLO": "Hello, I’m Andreas Dahm.",
|
||||
"LEAD": "During my training as an application developer, I became fascinated by how complex problems can be solved elegantly through code. After studying Applied Computer Science at FH Bingen, I was able to deepen this passion in a wide variety of projects – from 3D simulations to modern web applications. Today, I work as a Senior Software Developer and Architect with a focus on Java, Angular, and DevOps. Thanks for stopping by!",
|
||||
"ROLE": "Senior Software Developer / Full-Stack Developer / Software Architect",
|
||||
"LOCATION": "Munich · Remote",
|
||||
"DOWNLOAD_CV": "Download CV",
|
||||
"VIEW_PROJECTS": "View projects",
|
||||
"CONTACT_ME": "Contact me",
|
||||
"SECTION": {
|
||||
"SKILLS": "Skills & Stack",
|
||||
"PRIMARY": "Core",
|
||||
"TOOLSET": "Toolset",
|
||||
"EXPERIENCE": "Experience",
|
||||
"PROJECTS": "Projects",
|
||||
"EDUCATION": "Education"
|
||||
},
|
||||
"SKILLS": {
|
||||
"JAVA": "Java 8/Java 21+",
|
||||
"SPRING": "Spring Boot 2/3",
|
||||
"ANGULAR": "Angular 20+",
|
||||
"DOCKER": "Docker",
|
||||
"UNITY": "Unity",
|
||||
"PYTHON": "Python",
|
||||
"CSHARP": "C#",
|
||||
"TYPESCRIPT": "TypeScript"
|
||||
},
|
||||
"TOOLS": {
|
||||
"GIT": "Git",
|
||||
"GITHUB": "Github",
|
||||
"GITLAB": "Gitlab",
|
||||
"JENKINS": "Jenkins",
|
||||
"K8S": "Kubernetes / k3d",
|
||||
"POSTGRES": "PostgreSQL",
|
||||
"MONGO": "MongoDB",
|
||||
"GRAFANA": "Grafana/Prometheus"
|
||||
},
|
||||
"XP": {
|
||||
"COMPANY8": {
|
||||
"COMPANY": "Teraport GmbH",
|
||||
"ROLE": "Senior Software Developer / Architect",
|
||||
"TIME": "Feb. 2024 – now",
|
||||
"HIGHLIGHTS": {
|
||||
"P1": "Architecture and implementation of database connectivity using Hibernate 6.x.",
|
||||
"P2": "Design and development of a full-stack web application for collision analysis (Angular + Spring Boot + Docker).",
|
||||
"P3": "Development of a cost analysis tool called MIDO."
|
||||
}
|
||||
},
|
||||
"COMPANY7": {
|
||||
"COMPANY": "ColorDigital GmbH",
|
||||
"ROLE": "Lead Software Developer",
|
||||
"TIME": "Mar. 2023 – Dec. 2023",
|
||||
"HIGHLIGHTS": {
|
||||
"P1": "Planning a new cloud architecture to migrate a large legacy monolith to a modern microservice-based platform.",
|
||||
"P2": "Led the development team of three to four backend engineers.",
|
||||
"P3": "Collaborated with partners and customers on integrating their systems with the DMIx."
|
||||
}
|
||||
},
|
||||
"COMPANY6": {
|
||||
"COMPANY": "ColorDigital GmbH",
|
||||
"ROLE": "Senior Software Developer",
|
||||
"TIME": "Mar. 2021 – Mar. 2023",
|
||||
"HIGHLIGHTS": {
|
||||
"P1": "Worked on the DMIx core cloud, including the implementation of an Elasticsearch-based real-time search module.",
|
||||
"P2": "Implementation of a new browser tool called 'Moodboard' (Node.js + Vue.js).",
|
||||
"P3": "Development of 'PAX', a tool for data exchange between the DMIx cloud and customer PLM/ERP systems, including CI/CD integration."
|
||||
}
|
||||
},
|
||||
"COMPANY5": {
|
||||
"COMPANY": "Assyst GmbH",
|
||||
"ROLE": "Team leadership",
|
||||
"TIME": "Sep. 2015 – Feb. 2021",
|
||||
"HIGHLIGHTS": {
|
||||
"P1": "Team lead of the Vidya software department, including disciplinary responsibility and interviews.",
|
||||
"P2": "Led several projects with external partners and freelancers.",
|
||||
"P3": "Managed strategic initiatives in 3D software development."
|
||||
}
|
||||
},
|
||||
"COMPANY4": {
|
||||
"COMPANY": "Assyst GmbH",
|
||||
"ROLE": "Software engineer",
|
||||
"TIME": "Apr. 2010 – Feb. 2021",
|
||||
"HIGHLIGHTS": {
|
||||
"P1": "Worked on core technologies such as algorithms, simulation, rendering, and collision detection.",
|
||||
"P2": "Used technologies including Java, OpenCL, OpenGL, GLSL shaders, C++, and REST interfaces.",
|
||||
"P3": "Participated in national and international research projects (EU7 and ZIM)."
|
||||
}
|
||||
},
|
||||
"COMPANY3": {
|
||||
"COMPANY": "Assyst GmbH",
|
||||
"ROLE": "Internship and Diploma",
|
||||
"TIME": "Apr. 2009 – Apr. 2010",
|
||||
"HIGHLIGHTS": {
|
||||
"P1": "Implemented production features in the real-time cloth simulation software Vidya.",
|
||||
"P2": "Conducted research and literature review for the diploma thesis.",
|
||||
"P3": "Successfully implemented the diploma topic in Vidya."
|
||||
}
|
||||
},
|
||||
"COMPANY2": {
|
||||
"COMPANY": "TH Bingen (University of Applied Sciences)",
|
||||
"ROLE": "Tutorial for fundamentals of Java programming",
|
||||
"TIME": "Apr. 2008 – Aug. 2008",
|
||||
"HIGHLIGHTS": {
|
||||
"P1": "Preparation of practical and theoretical course material.",
|
||||
"P2": "Held a 20-hour lecture series for the bioinformatics programme.",
|
||||
"P3": "Provided assistance during practical programming exercises."
|
||||
}
|
||||
},
|
||||
"COMPANY1": {
|
||||
"COMPANY": "TH Bingen (University of Applied Sciences)",
|
||||
"ROLE": "Research Asssitent",
|
||||
"TIME": "Oct. 2007 – Apr. 2008",
|
||||
"HIGHLIGHTS": {
|
||||
"P1": "Evaluation of various Web 2.0 technologies.",
|
||||
"P2": "Implementation of a Java-based interface for Microsoft Excel.",
|
||||
"P3": "Integration of the MIT Exhibit framework and preparation for CeBIT 2008."
|
||||
}
|
||||
}
|
||||
},
|
||||
"PROJECT": {
|
||||
"P2": {
|
||||
"TITLE": "Playground",
|
||||
"DESCRIPTION": "This project is, as the name suggests, a personal playground that will grow over time. It showcases various private projects I’m working on and documents their progress, key ideas, and milestones.",
|
||||
"LINK_EXTERNAL": "andreas-dahm.eu",
|
||||
"LINK_INTERNAL": "Project details",
|
||||
"HIGHLIGHTS": {
|
||||
"P1": "Using modern technologies and CI/CD pipelines (Angular 20+, Spring Boot 4, GitHub).",
|
||||
"P2": "Showcasing personal projects and improving algorithmic skills over time.",
|
||||
"P3": "Deepening knowledge in JavaScript/TypeScript, Angular, Spring Boot and related technologies through hands-on practice."
|
||||
}
|
||||
},
|
||||
"P1": {
|
||||
"TITLE": "El Mucho",
|
||||
"DESCRIPTION": "This is my first published game on Steam. I developed it together with my brother, fulfilling one of my personal goals: creating and releasing my own video game.",
|
||||
"LINK_EXTERNAL": "Steam Store",
|
||||
"LINK_INTERNAL": "Project details",
|
||||
"HIGHLIGHTS": {
|
||||
"P1": "Publishing a game on Steam and integrating the Steam API.",
|
||||
"P2": "Designing, planning and developing a complete game from scratch.",
|
||||
"P3": "Implementing complex algorithms, including a custom A* pathfinding system and game AI logic."
|
||||
}
|
||||
},
|
||||
"P0": {
|
||||
"TITLE": "Game Jams",
|
||||
"DESCRIPTION": "This section showcases several of my past game jam creations. Game jams are a great way to explore new game ideas and quickly validate whether a game loop works. The tight time constraints force you to focus, and it’s always exciting to see what can be achieved within such a short timeframe.",
|
||||
"LINK_EXTERNAL": "Itch.io Collection",
|
||||
"LINK_INTERNAL": "Project details",
|
||||
"HIGHLIGHTS": {
|
||||
"P1": "Planning a realistic project scope with a team that can be built within 48 hours.",
|
||||
"P2": "Learning to stay focused and work effectively under strict time constraints.",
|
||||
"P3": "Experiencing the joy of creating a playable game in a short timeframe and seeing others enjoy it."
|
||||
}
|
||||
}
|
||||
},
|
||||
"EDUCATION": {
|
||||
"E6": {
|
||||
"WHERE": "FH Bingen (University of Applied Sciences)",
|
||||
"WHEN": "2006 – 2010",
|
||||
"WHAT": "Diploma (FH), Applied Computer Science (final grade 1.4)"
|
||||
},
|
||||
"E5": {
|
||||
"WHERE": "FH Koblenz (University of Applied Sciences)",
|
||||
"WHEN": "2005 – 2006",
|
||||
"WHAT": "1 semester of Engineering Informatics"
|
||||
},
|
||||
"E4": {
|
||||
"WHERE": "BBS Montabaur",
|
||||
"WHEN": "2002 – 2005",
|
||||
"WHAT": "Apprenticeship as IT Specialist for Application Development"
|
||||
},
|
||||
"E3": {
|
||||
"WHERE": "BBS Andernach – Higher Vocational School (Computer Science)",
|
||||
"WHEN": "2000 – 2002",
|
||||
"WHAT": "University entrance qualification (Fachhochschulreife)"
|
||||
},
|
||||
"E2": {
|
||||
"WHERE": "BBS Andernach – Vocational School for Technology (Electrical Engineering)",
|
||||
"WHEN": "1998 – 2000",
|
||||
"WHAT": "Secondary school certificate (Realschulabschluss)"
|
||||
},
|
||||
"E1": {
|
||||
"WHERE": "Hauptschule Hinter Burg Mayen (Albert Schweitzer Realschule Plus)",
|
||||
"WHEN": "1995 – 1998",
|
||||
"WHAT": "Lower secondary school certificate (Hauptschulabschluss)"
|
||||
},
|
||||
"E0": {
|
||||
"WHERE": "Albert Schweitzer Realschule Mayen",
|
||||
"WHEN": "1993 – 1995",
|
||||
"WHAT": "No degree (transferred to lower secondary school)"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BIN
src/assets/logos/assyst_gmbh_logo.jpg
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
19
src/assets/logos/bingen-logo-white.svg
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
src/assets/logos/dmixcloud_logo.jpg
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
66
src/assets/logos/logo_chamaeleon.svg
Normal file
@@ -0,0 +1,66 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 19.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Konstruktion_Kopie" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px"
|
||||
y="0px" viewBox="0 0 216 120" style="enable-background:new 0 0 216 120;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#58585A;}
|
||||
.st1{fill:#007CBF;}
|
||||
</style>
|
||||
<g>
|
||||
<g>
|
||||
<path class="st0" d="M0.8,108.8V108c0-6.7,3.5-10.6,8.1-10.6c2,0,3.5,0.7,5,2l-2,3.4c-0.7-0.7-1.6-1.5-2.9-1.5c-2.3,0-4,2.6-4,7
|
||||
v0.3c0,4.5,1.6,7,4.1,6.9c1.3,0,2.1-0.7,2.9-1.6l2,3.1c-1.2,1.4-3,2.3-5.3,2.3C4,119.3,0.8,115.5,0.8,108.8z"/>
|
||||
<path class="st0" d="M16.7,90.5h4.1v10.2c1-1.8,2.4-3.3,4.9-3.3c3.3,0,5.3,2.5,5.3,6.6v15h-4.1v-13.9c0-2.6-1.1-4-2.9-4
|
||||
s-3.1,1.5-3.1,4v13.8h-4.1V90.5z"/>
|
||||
<path class="st0" d="M33.9,112.9v-0.4c0-4.5,2.5-6.8,6.2-6.8c1.5,0,2.7,0.4,3.9,0.8v-1.3c0-2.7-1.5-4-3.8-4c-1.7,0-3.2,0.8-4,1.3
|
||||
l-1.3-3.4c1.7-0.9,3.6-1.6,5.9-1.6c2.3,0,4.1,0.7,5.3,1.9c1.3,1.3,2,3.2,2,5.7v13.8H44v-2.5c-1,1.8-2.6,2.9-4.7,2.9
|
||||
C36.2,119.3,33.9,116.9,33.9,112.9z M44,112.4v-2.9c-0.8-0.4-1.9-0.6-2.9-0.6c-2,0-3.1,1.4-3.1,3.6v0.2c0,2.2,1.1,3.4,2.7,3.5
|
||||
C42.5,116.1,44,114.4,44,112.4z"/>
|
||||
<path class="st0" d="M51.9,97.7h4.1v2.9c1-1.9,2.5-3.3,4.8-3.3c2.1,0,3.7,1.1,4.6,3c1.1-1.6,2.6-3,5.1-3c3.1,0,5.4,2.2,5.4,6.6v15
|
||||
h-4.1v-13.8c0-2.7-1-4-2.8-4c-1.8,0-3,1.5-3,4.1v13.8h-4.2v-13.8c0-2.7-1-4-2.7-4c-1.8,0-3,1.6-3,4.1v13.8h-4.1V97.7z"/>
|
||||
<path class="st0" d="M78.9,112.9v-0.4c0-4.5,2.5-6.8,6.2-6.8c1.5,0,2.7,0.4,3.9,0.8v-1.3c0-2.7-1.5-4-3.8-4c-1.7,0-3.2,0.8-4,1.3
|
||||
l-1.3-3.4c1.7-0.9,3.6-1.6,5.9-1.6c2.3,0,4.1,0.7,5.3,1.9c1.3,1.3,2,3.2,2,5.7v13.8H89v-2.5c-1,1.8-2.6,2.9-4.7,2.9
|
||||
C81.3,119.3,78.9,116.9,78.9,112.9z M89,112.4v-2.9c-0.8-0.4-1.9-0.6-2.9-0.6c-2,0-3.1,1.4-3.1,3.6v0.2c0,2.2,1.1,3.4,2.7,3.5
|
||||
C87.5,116.1,89,114.4,89,112.4z"/>
|
||||
<path class="st0" d="M96.2,108.7v-0.6c0-6.7,3.3-10.8,7.6-10.8c4.7,0,7.3,4.2,7.3,10.8c0,0.2,0,1,0,1.9h-10.7
|
||||
c0.3,3.7,2.2,5.6,4.6,5.6c1.6,0,2.8-0.9,3.9-2l1.9,3.1c-1.8,1.6-3.7,2.5-6.1,2.5C99.9,119.3,96.2,115.5,96.2,108.7z M107.1,107
|
||||
c-0.1-3.6-1.3-6.1-3.3-6.1c-1.9,0-3.3,2.2-3.5,6.1H107.1z"/>
|
||||
<path class="st0" d="M114.4,90.5h4.1v28.4h-4.1V90.5z"/>
|
||||
<path class="st0" d="M122,108.7v-0.6c0-6.7,3.3-10.8,7.6-10.8c4.7,0,7.3,4.2,7.3,10.8c0,0.2,0,1,0,1.9h-10.7
|
||||
c0.3,3.7,2.2,5.6,4.6,5.6c1.6,0,2.8-0.9,3.9-2l1.9,3.1c-1.8,1.6-3.7,2.5-6.1,2.5C125.8,119.3,122,115.5,122,108.7z M132.9,107
|
||||
c-0.1-3.6-1.3-6.1-3.3-6.1c-1.9,0-3.3,2.2-3.5,6.1H132.9z"/>
|
||||
<path class="st0" d="M139.4,108.7v-0.7c0-6.5,3.4-10.7,8-10.7c4.6,0,8,4.1,8,10.6v0.7c0,6.5-3.4,10.6-8,10.6
|
||||
C142.7,119.3,139.4,115.2,139.4,108.7z M151.2,108.6v-0.4c0-4.3-1.6-7.1-3.9-7.1c-2.3,0-3.9,2.8-3.9,7.1v0.4
|
||||
c0,4.3,1.6,7.1,3.9,7.1C149.7,115.7,151.2,112.8,151.2,108.6z"/>
|
||||
<path class="st0" d="M158.7,97.7h4.1v2.9c1-1.8,2.4-3.3,4.9-3.3c3.3,0,5.3,2.5,5.3,6.6v15h-4.1v-13.9c0-2.6-1.1-4-2.9-4
|
||||
s-3.1,1.5-3.1,4v13.8h-4.1V97.7z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st0" d="M189.3,96l-6.5,22.9h3.9l1.4-5.1h6.1l1.4,5.1h4.1L193.1,96H189.3z M193.2,110.2H189l2.1-7.7L193.2,110.2z"/>
|
||||
<path class="st0" d="M208.3,106.1v3.5h3.5v5.4c-0.5,0.3-1.3,0.5-2,0.5c-3.1,0-5.1-3-5.1-7.8v-0.4c0-4.6,2-7.7,5-7.7
|
||||
c1.3,0,2.4,0.5,3.3,1.5l0.2,0.2l2.2-3.3l-0.2-0.1c-1.6-1.4-3.4-2-5.6-2c-5.4,0-9,4.6-9,11.4v0.4c0,7,3.4,11.4,8.9,11.4
|
||||
c2,0,3.9-0.6,5.9-1.9l0.1-0.1v-11.1H208.3z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st0" d="M92.2,50.7C93.8,33,107.1,18,125.4,14.9c21.7-3.6,42.3,11.1,45.9,32.8c0.2,1.3,0.4,2.6,0.5,4
|
||||
c0.4-5.3-0.4-9.6-0.4-9.6c-4.8-27-30.5-45.1-57.6-40.6C101.4,3.6,90.9,10,83.4,18.9c0.6-4.6,0.4-10.2-1.9-7.8
|
||||
c-3.2,3.3-13.2,6.4-16.5,7.1c0,0,0,0,0,0c-8.5,1.9-15.8,9-18.9,21.8c-3,12.1-14.5,20.2-14.6,20.3l0,0c7,8,26.7-3.2,26.9-3.3
|
||||
l0.9,1.8c-0.2,0.1-8.5,4.8-18.4,5.9c0,0,10.7,5.2,32-3.5c0,0,0,0,0,0c0,0,0.4-0.2,1.1-0.5c0.2-0.1,0.4-0.2,0.7-0.3c0,0,0,0,0,0
|
||||
C78.2,58.8,86.4,54.6,92.2,50.7z M64,40.9c-4.7,1.3-9.7-1.5-11-6.2c-1.3-4.7,1.5-9.7,6.2-11c4.7-1.3,9.7,1.5,11,6.2
|
||||
C71.5,34.6,68.7,39.6,64,40.9z"/>
|
||||
</g>
|
||||
<path class="st1" d="M89.3,21.2c-0.4-2.3-1-4.5-1.7-6.7c-1.5,1.4-2.9,2.9-4.2,4.4c0.6-4.6,0.4-10.2-1.9-7.8
|
||||
c-3.2,3.3-13.2,6.4-16.5,7.1c0,0,0,0,0,0c-8.5,1.9-15.8,9-18.9,21.8c-3,12.1-14.5,20.2-14.6,20.3l0,0c7,8,26.7-3.2,26.9-3.3l0.9,1.8
|
||||
c-0.2,0.1-8.5,4.8-18.4,5.9c0,0,0.7,0.3,1.9,0.7c2.7,0.6,5.4,0.9,8.2,0.9c2.5,0,5.5-0.3,8.9-1C79.7,60.7,92.7,41.5,89.3,21.2z
|
||||
M53,34.6c-1.3-4.7,1.5-9.7,6.2-11c4.7-1.3,9.7,1.5,11,6.2c1.3,4.7-1.5,9.7-6.2,11C59.2,42.2,54.3,39.4,53,34.6z"/>
|
||||
<path class="st1" d="M65.7,35.7c1.5-0.4,2.3-1.6,2-3.5c-0.1-1-1.1-2.4-3.3-2.1c-2.2,0.4-2.1,2.2-1.8,3.5
|
||||
C62.9,34.8,64.2,36.1,65.7,35.7z"/>
|
||||
<path class="st1" d="M171.3,47.8c-3.6-21.7-24.2-36.4-45.9-32.8c-18.3,3-31.6,18.1-33.2,35.8c0.2-0.2,0.5-0.3,0.7-0.5
|
||||
c0.8,0.3,2.6,0.5,5,0.6c-0.1,1.5-0.8,4.2-4.1,6.4C92,58.5,91,59.4,89,59.9c-3.3,0.8-6.8,2.9-7.3,3.7c-0.5,0.8-0.4,1-0.1,1.5
|
||||
c0.5,0.6,1.3,1,5.6,0.6c2.9-0.2,4.8-0.6,5.4-0.6c0.6-0.1,0.7-0.7,0.9-1.4c0.9-3.4,15.2-6.8,16.2-10.8c0.2-0.7,0.2-1.5,0.2-2.2
|
||||
c4.4-0.2,9.2-0.7,13.9-1.2c-0.4,0.7-0.7,1.4-0.9,2.1c-0.9,3.1-0.1,6.7,0.7,9.1c-4.1,1.1-6.4,2.8-6.4,3.8c0,1,3.8,1.5,6.9,1.4
|
||||
c2.3-0.1,5.1-0.1,5.6-0.7c0.4-0.4,0.6-0.7,0.1-1.9c-0.3-0.6-1.7-4.1,0.5-8.1c1.7-3,2.8-3.8,9.6-7.1c2.4-1.2,4.5-2.1,6.4-2.9
|
||||
c3.1-0.5,6.5-0.6,9.2,0.4c3.6,1.3,5.5,3.8,6.2,7.9c0.8,4.3-2.6,9.3-6.6,9.9l-1-1.9c3.5-0.6,5.8-3.9,5.3-7.4c-0.6-3.9-3-6.9-8.8-6.6
|
||||
c-2.8,0.2-4.2,1-6.4,2.6c-10.7,9.1,0.6,24.8,12.4,21.9c11.5-2.9,14.6-12.7,15.1-20.2C171.6,50.4,171.5,49.1,171.3,47.8z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.4 KiB |
BIN
src/assets/logos/teraport_gmbh_logo.jpg
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
src/assets/me.webp
Normal file
|
After Width: | Height: | Size: 105 KiB |
@@ -2,10 +2,10 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>PlaygroundFrontend</title>
|
||||
<title>Andreas Dahm - Playground</title>
|
||||
<base href="/">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||
<link rel="icon" type="image/x-icon" href="assets/favicon.ico">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined&display=swap" rel="stylesheet">
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { bootstrapApplication } from '@angular/platform-browser';
|
||||
import { appConfig } from './app/app.config';
|
||||
import { App } from './app/app';
|
||||
import packageJson from '../package.json';
|
||||
import {AppComponent} from './app/layout/app/app.component';
|
||||
|
||||
if (packageJson.version) {
|
||||
console.log(`🌟 Frontend version: ${packageJson.version}`);
|
||||
}
|
||||
|
||||
bootstrapApplication(App, appConfig)
|
||||
bootstrapApplication(AppComponent, appConfig)
|
||||
.catch((err) => console.error(err));
|
||||
|
||||
@@ -26,11 +26,17 @@ $dark-theme: mat.define-theme((
|
||||
--app-bg: #{mat.get-theme-color($light-theme, surface-container-low)};
|
||||
--app-fg: #{mat.get-theme-color($light-theme, on-surface)};
|
||||
--app-card-background: #fafafa;
|
||||
--app-logo-bg: #313131;
|
||||
--link-color: #38a7ff;
|
||||
--link-color-hover: #66bfff;
|
||||
}
|
||||
.dark {
|
||||
--app-bg: #{mat.get-theme-color($dark-theme, surface-container-low)};
|
||||
--app-fg: #{mat.get-theme-color($dark-theme, on-surface)};
|
||||
--app-card-background: #313131;
|
||||
--app-logo-bg: #313131;
|
||||
--link-color: #6dbcff;
|
||||
--link-color-hover: #9ad2ff;
|
||||
}
|
||||
|
||||
/* ---- global background and tests ---- */
|
||||
@@ -67,3 +73,15 @@ body {
|
||||
box-shadow 220ms ease,
|
||||
fill 220ms ease;
|
||||
}
|
||||
|
||||
/* links */
|
||||
a {
|
||||
color: var(--link-color);
|
||||
text-decoration: none;
|
||||
font-weight: 500;
|
||||
|
||||
&:hover {
|
||||
color: var(--link-color-hover);
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||