import { Component, computed, signal, ChangeDetectionStrategy } from '@angular/core'; import { 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 { TranslatePipe } from '@ngx-translate/core'; import {UrlConstants} from '../../constants/UrlConstants'; import {AssetsConstants} from '../../constants/AssetsConstants'; import {RouterLink} from '@angular/router'; import {SharedFunctions} from '../../shared/SharedFunctions'; /** One role. Consecutive entries sharing a `company` render as a single employer block. */ interface ExperienceEntry { key: string; company: string; logo: string; highlightCount: number; } interface SkillGroup { titleKey: string; skillKeys: string[]; } @Component({ selector: 'app-about', imports: [ NgOptimizedImage, MatCardModule, MatChipsModule, MatIconModule, MatButtonModule, MatDividerModule, TranslatePipe, RouterLink ], templateUrl: './about.component.html', changeDetection: ChangeDetectionStrategy.Eager, styleUrl: './about.component.scss' }) export class AboutComponent { /** Roles older than the current decade are hidden until the reader asks for them. */ readonly showEarlierExperience = signal(false); readonly showEarlierEducation = signal(false); readonly recentExperience: ExperienceEntry[] = [ { key: 'ABOUT.XP.COMPANY9', company: 'colordigital', logo: AssetsConstants.COLORDIGITAL_LOGO, highlightCount: 3 }, { key: 'ABOUT.XP.COMPANY8', company: 'teraport', logo: AssetsConstants.TERAPORT_LOGO, highlightCount: 3 }, { key: 'ABOUT.XP.COMPANY7', company: 'colordigital-earlier', logo: AssetsConstants.COLORDIGITAL_LOGO, highlightCount: 3 }, { key: 'ABOUT.XP.COMPANY6', company: 'colordigital-earlier', logo: AssetsConstants.COLORDIGITAL_LOGO, highlightCount: 3 }, { key: 'ABOUT.XP.COMPANY5', company: 'assyst', logo: AssetsConstants.ASSYST_LOGO, highlightCount: 3 }, { key: 'ABOUT.XP.COMPANY4', company: 'assyst', logo: AssetsConstants.ASSYST_LOGO, highlightCount: 3 }, { key: 'ABOUT.XP.COMPANY3', company: 'assyst', logo: AssetsConstants.ASSYST_LOGO, highlightCount: 3 } ]; readonly earlierExperience: ExperienceEntry[] = [ { key: 'ABOUT.XP.COMPANY2', company: 'th-bingen', logo: AssetsConstants.TH_BINGEN_LOGO, highlightCount: 3 }, { key: 'ABOUT.XP.COMPANY1', company: 'th-bingen', logo: AssetsConstants.TH_BINGEN_LOGO, highlightCount: 3 }, { key: 'ABOUT.XP.COMPANY0', company: 'chamaeleon', logo: AssetsConstants.CHAMAELEON_LOGO, highlightCount: 3 } ]; readonly projectKeys = [ { key: 'ABOUT.PROJECT.TBP', externalLink: 'https://tbp.andreas-dahm.eu', internalLink: 'projects', identifier: 'to-be-played', }, { key: 'ABOUT.PROJECT.CLAUDE_BOX', externalLink: '', internalLink: 'projects', identifier: 'claude-in-a-box', }, { key: 'ABOUT.PROJECT.P2', externalLink: 'https://andreas-dahm.eu/', internalLink: 'projects', identifier: 'playground', }, { key: 'ABOUT.PROJECT.P1', externalLink: 'https://store.steampowered.com/app/1532640/El_Mucho/', internalLink: 'projects', identifier: 'elmucho', }, { key: 'ABOUT.PROJECT.P0', externalLink: 'https://itch.io/c/6628860/lobos-collection', internalLink: 'projects', identifier: 'gamejams', }, { key: 'ABOUT.PROJECT.DIPLOMA', externalLink: '', internalLink: 'projects', identifier: 'diploma', }, { key: 'ABOUT.PROJECT.TRIBBLE', externalLink: '', internalLink: 'projects', identifier: 'tribble-the-homeserver', } ]; readonly recentEducationKeys = [ 'ABOUT.EDUCATION.E6', 'ABOUT.EDUCATION.E5', 'ABOUT.EDUCATION.E4' ]; readonly earlierEducationKeys = [ 'ABOUT.EDUCATION.E3', 'ABOUT.EDUCATION.E2', 'ABOUT.EDUCATION.E1', 'ABOUT.EDUCATION.E0' ]; /** * Heading and chips travel together so the two can no longer drift apart — * they used to be declared in separate places and had ended up mismatched. */ readonly skillGroups: SkillGroup[] = [ { titleKey: 'ABOUT.SECTION.LANGUAGES', skillKeys: [ 'ABOUT.SKILLS.JAVA', 'ABOUT.SKILLS.SPRING', 'ABOUT.SKILLS.TYPESCRIPT', 'ABOUT.SKILLS.ANGULAR', 'ABOUT.SKILLS.CSHARP', 'ABOUT.SKILLS.PYTHON', 'ABOUT.SKILLS.VUE', 'ABOUT.SKILLS.REACT' ] }, { titleKey: 'ABOUT.SECTION.ARCH_CLOUD', skillKeys: [ 'ABOUT.SKILLS.ARCH_MICROSERVICES', 'ABOUT.SKILLS.ARCH_CLOUD', 'ABOUT.TOOLS.DOCKER', 'ABOUT.TOOLS.K8S', 'ABOUT.TOOLS.POSTGRES', 'ABOUT.TOOLS.MONGO', 'ABOUT.TOOLS.GITLAB', 'ABOUT.TOOLS.JENKINS' ] }, { titleKey: 'ABOUT.SECTION.SIM_ALGO', skillKeys: [ 'ABOUT.SKILLS.ENG_ALGO', 'ABOUT.SKILLS.ENG_SIM', 'ABOUT.SKILLS.ENG_GPU', 'ABOUT.SKILLS.ENG_PERF', 'ABOUT.SKILLS.UNITY' ] }, { titleKey: 'ABOUT.SECTION.AI_ENG', skillKeys: [ 'ABOUT.SKILLS.AI_AGENTS', 'ABOUT.SKILLS.AI_MCP', 'ABOUT.SKILLS.AI_CONTEXT', 'ABOUT.SKILLS.AI_HARNESS', 'ABOUT.SKILLS.AI_GATES' ] } ]; readonly visibleExperience = computed(() => { if (this.showEarlierExperience()) { return [...this.recentExperience, ...this.earlierExperience]; } return this.recentExperience; }); readonly visibleEducationKeys = computed(() => { if (this.showEarlierEducation()) { return [...this.recentEducationKeys, ...this.earlierEducationKeys]; } return this.recentEducationKeys; }); toggleEarlierExperience(): void { this.showEarlierExperience.update(shown => !shown); } toggleEarlierEducation(): void { this.showEarlierEducation.update(shown => !shown); } /** The employer header is drawn once per run of consecutive roles at the same company. */ isFirstRoleAtCompany(index: number): boolean { if (index === 0) { return true; } const entries = this.visibleExperience(); return entries[index - 1].company !== entries[index].company; } isLastRoleAtCompany(index: number): boolean { const entries = this.visibleExperience(); if (index === entries.length - 1) { return true; } return entries[index + 1].company !== entries[index].company; } highlightKeys(entry: ExperienceEntry): string[] { return Array.from({ length: entry.highlightCount }, (_, i) => `${entry.key}.HIGHLIGHTS.P${i + 1}`); } protected readonly UrlConstants = UrlConstants; protected readonly AssetsConstants = AssetsConstants; protected readonly SharedFunctions = SharedFunctions; }