diff --git a/angular.json b/angular.json index 666b77a..c0d44b9 100644 --- a/angular.json +++ b/angular.json @@ -25,7 +25,7 @@ "tsConfig": "tsconfig.app.json", "inlineStyleLanguage": "scss", "assets": [ - "src/favicon.ico", + "src/assets/favicon.ico", { "glob": "**/*", "input": "public" diff --git a/src/app/app.config.ts b/src/app/app.config.ts index 8fe8c53..03c2f48 100644 --- a/src/app/app.config.ts +++ b/src/app/app.config.ts @@ -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'; diff --git a/src/app/app.html b/src/app/app.html deleted file mode 100644 index e69f15c..0000000 --- a/src/app/app.html +++ /dev/null @@ -1,36 +0,0 @@ - - {{ 'APP.TITLE' | translate }} - - - - - - - - - {{ lang.lang() === 'de' ? ('LANG.DE' | translate) : ('LANG.EN' | translate) }} - - - - - - {{ 'LANG.DE' | translate }} - - - - - {{ 'LANG.EN' | translate }} - - - - - - - - -
- -
diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts index da126ee..2c0a7ba 100644 --- a/src/app/app.routes.ts +++ b/src/app/app.routes.ts @@ -1,6 +1,6 @@ import { Routes } from '@angular/router'; -import {WelcomeComponent} from './features/welcome/welcome'; +import {AboutComponent} from './pages/about/about.component'; export const routes: Routes = [ - { path: '', component: WelcomeComponent }, + { path: '', component: AboutComponent }, ]; diff --git a/src/app/app.scss b/src/app/app.scss deleted file mode 100644 index 4120908..0000000 --- a/src/app/app.scss +++ /dev/null @@ -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; } diff --git a/src/app/app.ts b/src/app/app.ts deleted file mode 100644 index 7ed0683..0000000 --- a/src/app/app.ts +++ /dev/null @@ -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'); -} diff --git a/src/app/constants/AssetsConstants.ts b/src/app/constants/AssetsConstants.ts new file mode 100644 index 0000000..d11ad51 --- /dev/null +++ b/src/app/constants/AssetsConstants.ts @@ -0,0 +1,7 @@ +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'; +} diff --git a/src/app/constants/Constants.ts b/src/app/constants/LocalStoreConstants.ts similarity index 80% rename from src/app/constants/Constants.ts rename to src/app/constants/LocalStoreConstants.ts index e1b8d9c..71d3b66 100644 --- a/src/app/constants/Constants.ts +++ b/src/app/constants/LocalStoreConstants.ts @@ -1,4 +1,4 @@ -export class Constants{ +export class LocalStoreConstants { static readonly THEME_KEY = 'theme'; static readonly LANGUAGE_KEY = 'lang'; diff --git a/src/app/constants/UrlConstants.ts b/src/app/constants/UrlConstants.ts new file mode 100644 index 0000000..ec5fe29 --- /dev/null +++ b/src/app/constants/UrlConstants.ts @@ -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'; +} diff --git a/src/app/features/welcome/welcome.html b/src/app/features/welcome/welcome.html deleted file mode 100644 index e20ab2d..0000000 --- a/src/app/features/welcome/welcome.html +++ /dev/null @@ -1,28 +0,0 @@ - - - {{ `WELCOME.TITLE` | translate }} - {{ `WELCOME.SUB` | translate }} - - - -

{{ `WELCOME.TEXT` | translate }}

-

- {{ `WELCOME.COUNTER` | translate }}: {{ count() }} -

-
- - - - - -
diff --git a/src/app/features/welcome/welcome.scss b/src/app/features/welcome/welcome.scss deleted file mode 100644 index c51b898..0000000 --- a/src/app/features/welcome/welcome.scss +++ /dev/null @@ -1,5 +0,0 @@ -mat-card { - margin-top: 2rem; - display: block; - background-color: var(--app-card-background); -} diff --git a/src/app/features/welcome/welcome.ts b/src/app/features/welcome/welcome.ts deleted file mode 100644 index f41e8a1..0000000 --- a/src/app/features/welcome/welcome.ts +++ /dev/null @@ -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); } -} diff --git a/src/app/layout/app/app.component.html b/src/app/layout/app/app.component.html new file mode 100644 index 0000000..f236b2d --- /dev/null +++ b/src/app/layout/app/app.component.html @@ -0,0 +1,9 @@ + + +
+ +
+ + diff --git a/src/app/layout/app/app.component.scss b/src/app/layout/app/app.component.scss new file mode 100644 index 0000000..f3ee3d5 --- /dev/null +++ b/src/app/layout/app/app.component.scss @@ -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; +} diff --git a/src/app/layout/app/app.component.ts b/src/app/layout/app/app.component.ts new file mode 100644 index 0000000..5a5aef1 --- /dev/null +++ b/src/app/layout/app/app.component.ts @@ -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(); +} diff --git a/src/app/layout/topbar/topbar.component.html b/src/app/layout/topbar/topbar.component.html new file mode 100644 index 0000000..b93633d --- /dev/null +++ b/src/app/layout/topbar/topbar.component.html @@ -0,0 +1,58 @@ + + + + {{ 'APP.TITLE' | translate }} + + + + + + + + + + + + + + + diff --git a/src/app/layout/topbar/topbar.component.scss b/src/app/layout/topbar/topbar.component.scss new file mode 100644 index 0000000..a148769 --- /dev/null +++ b/src/app/layout/topbar/topbar.component.scss @@ -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; } +} diff --git a/src/app/layout/topbar/topbar.component.ts b/src/app/layout/topbar/topbar.component.ts new file mode 100644 index 0000000..9dad819 --- /dev/null +++ b/src/app/layout/topbar/topbar.component.ts @@ -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; +} diff --git a/src/app/pages/about/about.component.html b/src/app/pages/about/about.component.html new file mode 100644 index 0000000..b8c830b --- /dev/null +++ b/src/app/pages/about/about.component.html @@ -0,0 +1,110 @@ +
+ +
+ {{ 'ABOUT.ALT.PROFILE' | translate }} +
+ +
+

{{ 'ABOUT.HELLO' | translate }}

+

+ {{ 'ABOUT.LEAD' | translate }} +

+ +
+
+ + {{ 'ABOUT.ROLE' | translate }} +
+
+ + {{ 'ABOUT.LOCATION' | translate }} +
+ +
+ + GitHub + · + LinkedIn +
+
+ + +
+
+ + +

{{ 'ABOUT.SECTION.SKILLS' | translate }}

+
+
+

{{ 'ABOUT.SECTION.PRIMARY' | translate }}

+ + @for (s of primarySkills; track primarySkills) { + {{ s | translate }} + } + +
+ +
+

{{ 'ABOUT.SECTION.TOOLSET' | translate }}

+ + @for (t of toolset; track toolset) { + {{ t | translate }} + } + +
+
+
+ + +

{{ 'ABOUT.SECTION.EXPERIENCE' | translate }}

+ +
+
+
+ {{ 'ABOUT.XP.T1.ROLE' | translate }} + {{ 'ABOUT.XP.T1.TIME' | translate }} +
+
{{ 'ABOUT.XP.T1.COMPANY' | translate }}
+
    +
  • {{ 'ABOUT.XP.T1.P1' | translate }}
  • +
  • {{ 'ABOUT.XP.T1.P2' | translate }}
  • +
  • {{ 'ABOUT.XP.T1.P3' | translate }}
  • +
+
+ + + +
+
+ {{ 'ABOUT.XP.T2.ROLE' | translate }} + {{ 'ABOUT.XP.T2.TIME' | translate }} +
+
{{ 'ABOUT.XP.T2.COMPANY' | translate }}
+
    +
  • {{ 'ABOUT.XP.T2.P1' | translate }}
  • +
  • {{ 'ABOUT.XP.T2.P2' | translate }}
  • +
+
+
+
+
diff --git a/src/app/pages/about/about.component.scss b/src/app/pages/about/about.component.scss new file mode 100644 index 0000000..fb5e36d --- /dev/null +++ b/src/app/pages/about/about.component.scss @@ -0,0 +1,86 @@ +.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; + .row { + display:flex; align-items:center; gap:.4rem; + a { color: inherit; } + } + } + + .actions { + display:flex; gap:.5rem; flex-wrap:wrap; margin-top:.5rem; margin-bottom: .25rem; + .mat-icon { margin-right:.25rem; } + } + } +} + +/* Skills block */ +.skills { + border-radius: 16px; + 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; + 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; + 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; } +} diff --git a/src/app/pages/about/about.component.ts b/src/app/pages/about/about.component.ts new file mode 100644 index 0000000..be63a71 --- /dev/null +++ b/src/app/pages/about/about.component.ts @@ -0,0 +1,60 @@ +import { Component, inject } 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 {RouterLink} from '@angular/router'; +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, RouterLink + ], + templateUrl: './about.component.html', + styleUrl: './about.component.scss' +}) +export class AboutComponent { + cvHref = 'assets/cv/andreas-dahm-cv.pdf'; + + 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.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; +} diff --git a/src/app/pages/project-details/project-details.component.html b/src/app/pages/project-details/project-details.component.html new file mode 100644 index 0000000..cc13d82 --- /dev/null +++ b/src/app/pages/project-details/project-details.component.html @@ -0,0 +1 @@ +

project-details works!

diff --git a/src/app/pages/project-details/project-details.component.scss b/src/app/pages/project-details/project-details.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/app/pages/project-details/project-details.component.ts b/src/app/pages/project-details/project-details.component.ts new file mode 100644 index 0000000..47934ab --- /dev/null +++ b/src/app/pages/project-details/project-details.component.ts @@ -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 { + +} diff --git a/src/app/pages/projects/projects.component.html b/src/app/pages/projects/projects.component.html new file mode 100644 index 0000000..885859a --- /dev/null +++ b/src/app/pages/projects/projects.component.html @@ -0,0 +1 @@ +

projects works!

diff --git a/src/app/pages/projects/projects.component.scss b/src/app/pages/projects/projects.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/app/pages/projects/projects.component.ts b/src/app/pages/projects/projects.component.ts new file mode 100644 index 0000000..2b83d2e --- /dev/null +++ b/src/app/pages/projects/projects.component.ts @@ -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 { + +} diff --git a/src/app/service/language.service.ts b/src/app/service/language.service.ts index 4556eb1..908b62d 100644 --- a/src/app/service/language.service.ts +++ b/src/app/service/language.service.ts @@ -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(); diff --git a/src/app/service/reload.service.ts b/src/app/service/reload.service.ts index e05c006..f6d0ce1 100644 --- a/src/app/service/reload.service.ts +++ b/src/app/service/reload.service.ts @@ -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())); } } diff --git a/src/app/service/theme.service.ts b/src/app/service/theme.service.ts index 902bf78..4959abb 100644 --- a/src/app/service/theme.service.ts +++ b/src/app/service/theme.service.ts @@ -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 { diff --git a/src/favicon.ico b/src/assets/favicon.ico similarity index 100% rename from src/favicon.ico rename to src/assets/favicon.ico diff --git a/src/assets/i18n/de.json b/src/assets/i18n/de.json index b2bee79..c9edb2e 100644 --- a/src/assets/i18n/de.json +++ b/src/assets/i18n/de.json @@ -1,13 +1,63 @@ { - "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" + }, + "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", + "JENKINS": "Jenkins", + "K8S": "Kubernetes / k3d", + "POSTGRES": "PostgreSQL", + "MONGO": "MongoDB", + "GRAFANA": "Grafana/Prometheus" + }, + "XP": { + "T1": { + "COMPANY": "Teraport GmbH", + "ROLE": "Senior Software Developer / Architect", + "TIME": "Feb. 2024 – heute", + "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)." + } + } + } + } } diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json index 3db395b..82b38b4 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -1,13 +1,62 @@ { - "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" + }, + "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", + "K8S": "Kubernetes / k3d", + "POSTGRES": "PostgreSQL", + "MONGO": "MongoDB", + "GRAFANA": "Grafana/Prometheus" + }, + "XP": { + "T1": { + "COMPANY": "Teraport GmbH", + "ROLE": "Senior Software Developer / Architect", + "TIME": "Feb. 2024 – heute", + "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)." + } + } + } + } } diff --git a/src/assets/me.webp b/src/assets/me.webp new file mode 100644 index 0000000..87d37ae Binary files /dev/null and b/src/assets/me.webp differ diff --git a/src/index.html b/src/index.html index deaa5e4..d5ad8af 100644 --- a/src/index.html +++ b/src/index.html @@ -5,7 +5,7 @@ PlaygroundFrontend - + diff --git a/src/main.ts b/src/main.ts index de803b6..b0ef0a7 100644 --- a/src/main.ts +++ b/src/main.ts @@ -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));