diff --git a/angular.json b/angular.json
index 599894c..792344a 100644
--- a/angular.json
+++ b/angular.json
@@ -20,7 +20,7 @@
"outputPath": "dist",
"index": "src/index.html",
"browser": "src/main.ts",
- "polyfills": ["zone.js"],
+ "polyfills": [],
"tsConfig": "tsconfig.app.json",
"inlineStyleLanguage": "scss",
"assets": [
diff --git a/package-lock.json b/package-lock.json
index 6dd31aa..bac5275 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -20,10 +20,6 @@
"@angular/platform-server": "^21.2.13",
"@angular/router": "^21.2.13",
"@angular/ssr": "^21.2.11",
- "@fortawesome/angular-fontawesome": "^4.0.0",
- "@fortawesome/fontawesome-svg-core": "^7.2.0",
- "@fortawesome/free-brands-svg-icons": "^7.2.0",
- "@fortawesome/free-solid-svg-icons": "^7.2.0",
"@types/markdown-it": "^14.1.2",
"express": "^5.2.1",
"markdown-it": "^14.1.1",
@@ -4333,64 +4329,6 @@
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
- "node_modules/@fortawesome/angular-fontawesome": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/@fortawesome/angular-fontawesome/-/angular-fontawesome-4.0.0.tgz",
- "integrity": "sha512-TCqHqT5ovFY1A4RgMpoBUgS+RX3OVs39+CzHFgzDhbCPAopOa26J748TZJcuZwJAvGAk9tbWeVEmWuLByINAeg==",
- "license": "MIT",
- "dependencies": {
- "@fortawesome/fontawesome-svg-core": "^7.1.0",
- "tslib": "^2.8.1"
- },
- "peerDependencies": {
- "@angular/core": "^21.0.0"
- }
- },
- "node_modules/@fortawesome/fontawesome-common-types": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-7.2.0.tgz",
- "integrity": "sha512-IpR0bER9FY25p+e7BmFH25MZKEwFHTfRAfhOyJubgiDnoJNsSvJ7nigLraHtp4VOG/cy8D7uiV0dLkHOne5Fhw==",
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/@fortawesome/fontawesome-svg-core": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-7.2.0.tgz",
- "integrity": "sha512-6639htZMjEkwskf3J+e6/iar+4cTNM9qhoWuRfj9F3eJD6r7iCzV1SWnQr2Mdv0QT0suuqU8BoJCZUyCtP9R4Q==",
- "license": "MIT",
- "dependencies": {
- "@fortawesome/fontawesome-common-types": "7.2.0"
- },
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/@fortawesome/free-brands-svg-icons": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-7.2.0.tgz",
- "integrity": "sha512-VNG8xqOip1JuJcC3zsVsKRQ60oXG9+oYNDCosjoU/H9pgYmLTEwWw8pE0jhPz/JWdHeUuK6+NQ3qsM4gIbdbYQ==",
- "license": "(CC-BY-4.0 AND MIT)",
- "dependencies": {
- "@fortawesome/fontawesome-common-types": "7.2.0"
- },
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/@fortawesome/free-solid-svg-icons": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-7.2.0.tgz",
- "integrity": "sha512-YTVITFGN0/24PxzXrwqCgnyd7njDuzp5ZvaCx5nq/jg55kUYd94Nj8UTchBdBofi/L0nwRfjGOg0E41d2u9T1w==",
- "license": "(CC-BY-4.0 AND MIT)",
- "dependencies": {
- "@fortawesome/fontawesome-common-types": "7.2.0"
- },
- "engines": {
- "node": ">=6"
- }
- },
"node_modules/@gar/promise-retry": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@gar/promise-retry/-/promise-retry-1.0.3.tgz",
diff --git a/package.json b/package.json
index 938f15f..232a80b 100644
--- a/package.json
+++ b/package.json
@@ -66,10 +66,6 @@
"@angular/platform-server": "^21.2.13",
"@angular/router": "^21.2.13",
"@angular/ssr": "^21.2.11",
- "@fortawesome/angular-fontawesome": "^4.0.0",
- "@fortawesome/fontawesome-svg-core": "^7.2.0",
- "@fortawesome/free-brands-svg-icons": "^7.2.0",
- "@fortawesome/free-solid-svg-icons": "^7.2.0",
"@types/markdown-it": "^14.1.2",
"express": "^5.2.1",
"markdown-it": "^14.1.1",
diff --git a/src/app/app.config.ts b/src/app/app.config.ts
index 049bceb..c019ad9 100644
--- a/src/app/app.config.ts
+++ b/src/app/app.config.ts
@@ -1,12 +1,12 @@
import type { ApplicationConfig } from '@angular/core';
-import { provideZoneChangeDetection } from '@angular/core';
+import { provideZonelessChangeDetection } from '@angular/core';
import { provideRouter, withInMemoryScrolling } from '@angular/router';
import { routes } from './app.routes';
export const appConfig: ApplicationConfig = {
providers: [
- provideZoneChangeDetection({ eventCoalescing: true }),
+ provideZonelessChangeDetection(),
provideRouter(
routes,
withInMemoryScrolling({
diff --git a/src/app/home/home.component.html b/src/app/home/home.component.html
index 55f146b..cd32c88 100644
--- a/src/app/home/home.component.html
+++ b/src/app/home/home.component.html
@@ -14,18 +14,16 @@
@@ -90,7 +88,7 @@
@if (isLongBioMounted) {
diff --git a/src/app/home/home.component.ts b/src/app/home/home.component.ts
index 66215b3..f345f35 100644
--- a/src/app/home/home.component.ts
+++ b/src/app/home/home.component.ts
@@ -8,10 +8,6 @@ import {
signal,
} from '@angular/core';
import { NgOptimizedImage } from '@angular/common';
-import { faArrowRight, faArrowDown, faEnvelope, faPhone } from '@fortawesome/free-solid-svg-icons';
-import { faDiscord, faGithub, faLinkedin } from '@fortawesome/free-brands-svg-icons';
-import type { IconDefinition } from '@fortawesome/angular-fontawesome';
-import { FaIconComponent } from '@fortawesome/angular-fontawesome';
import { interval } from 'rxjs';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
@@ -23,11 +19,21 @@ import type {
} from '../../languages/language.types';
import { FooterComponent } from '../footer/footer.component';
import { LanguageService } from '../services/language.service';
+import { IconComponent } from '../shared/icon/icon.component';
+import {
+ arrowDownIcon,
+ arrowRightIcon,
+ discordIcon,
+ envelopeIcon,
+ githubIcon,
+ linkedinIcon,
+ phoneIcon,
+} from '../shared/icon/icons';
@Component({
selector: 'app-home',
standalone: true,
- imports: [FooterComponent, FaIconComponent, NgOptimizedImage],
+ imports: [FooterComponent, IconComponent, NgOptimizedImage],
templateUrl: './home.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
@@ -65,13 +71,13 @@ export class HomeComponent implements OnInit, AfterViewInit, OnDestroy {
protected readonly global = global;
protected readonly age = this.calculateAge(global.birthdate);
protected readonly contactMailHref = `mailto:${global.contactMail}`;
- protected readonly faArrowRight: IconDefinition = faArrowRight;
- protected readonly faArrowDown: IconDefinition = faArrowDown;
- protected readonly faEnvelope: IconDefinition = faEnvelope;
- protected readonly faPhone: IconDefinition = faPhone;
- protected readonly faDiscord: IconDefinition = faDiscord;
- protected readonly faGithub: IconDefinition = faGithub;
- protected readonly faLinkedin: IconDefinition = faLinkedin;
+ protected readonly arrowRightIcon = arrowRightIcon;
+ protected readonly arrowDownIcon = arrowDownIcon;
+ protected readonly envelopeIcon = envelopeIcon;
+ protected readonly phoneIcon = phoneIcon;
+ protected readonly discordIcon = discordIcon;
+ protected readonly githubIcon = githubIcon;
+ protected readonly linkedinIcon = linkedinIcon;
private readonly isLongBioShownState = signal(false);
private readonly isLongBioMountedState = signal(false);
diff --git a/src/app/imprint/imprint.component.html b/src/app/imprint/imprint.component.html
index ef7b6ab..1ebc11d 100644
--- a/src/app/imprint/imprint.component.html
+++ b/src/app/imprint/imprint.component.html
@@ -8,7 +8,7 @@
diff --git a/src/app/imprint/imprint.component.ts b/src/app/imprint/imprint.component.ts
index 70b6f8b..7469549 100644
--- a/src/app/imprint/imprint.component.ts
+++ b/src/app/imprint/imprint.component.ts
@@ -1,17 +1,16 @@
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
import { RouterLink } from '@angular/router';
-import type { IconDefinition } from '@fortawesome/angular-fontawesome';
-import { FaIconComponent } from '@fortawesome/angular-fontawesome';
-import { faArrowLeft } from '@fortawesome/free-solid-svg-icons';
import { global } from '../../global';
import { FooterComponent } from '../footer/footer.component';
import { LanguageService } from '../services/language.service';
+import { IconComponent } from '../shared/icon/icon.component';
+import { arrowLeftIcon } from '../shared/icon/icons';
@Component({
selector: 'app-imprint',
standalone: true,
- imports: [FooterComponent, FaIconComponent, RouterLink],
+ imports: [FooterComponent, IconComponent, RouterLink],
templateUrl: './imprint.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
@@ -20,7 +19,7 @@ export class ImprintComponent {
protected readonly content = this.languageService.content;
protected readonly global = global;
- protected readonly faArrowLeft: IconDefinition = faArrowLeft;
+ protected readonly arrowLeftIcon = arrowLeftIcon;
protected readonly contactMailHref = `mailto:${global.contactMail}`;
protected readonly abuseMailHref = `mailto:${global.abuseMail}`;
diff --git a/src/app/shared/icon/icon.component.ts b/src/app/shared/icon/icon.component.ts
new file mode 100644
index 0000000..ba86ac1
--- /dev/null
+++ b/src/app/shared/icon/icon.component.ts
@@ -0,0 +1,47 @@
+import { ChangeDetectionStrategy, Component, input } from '@angular/core';
+
+import type { SvgIcon, SvgIconSize } from './icon.types';
+
+@Component({
+ selector: 'app-icon',
+ standalone: true,
+ template: `
+
+ `,
+ styles: [
+ `
+ :host {
+ display: inline-flex;
+ line-height: 1;
+ }
+
+ .app-icon {
+ box-sizing: content-box;
+ display: inline-block;
+ width: 1.25em;
+ height: 1em;
+ overflow: visible;
+ vertical-align: -0.125em;
+ }
+
+ .app-icon-lg {
+ font-size: 1.25em;
+ vertical-align: -0.2em;
+ }
+ `,
+ ],
+ changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class IconComponent {
+ readonly icon = input.required();
+ readonly size = input('base');
+}
diff --git a/src/app/shared/icon/icon.types.ts b/src/app/shared/icon/icon.types.ts
new file mode 100644
index 0000000..c7b7dcf
--- /dev/null
+++ b/src/app/shared/icon/icon.types.ts
@@ -0,0 +1,6 @@
+export interface SvgIcon {
+ readonly viewBox: string;
+ readonly path: string;
+}
+
+export type SvgIconSize = 'base' | 'lg';
diff --git a/src/app/shared/icon/icons.ts b/src/app/shared/icon/icons.ts
new file mode 100644
index 0000000..02400cb
--- /dev/null
+++ b/src/app/shared/icon/icons.ts
@@ -0,0 +1,41 @@
+import type { SvgIcon } from './icon.types';
+
+export const arrowDownIcon: SvgIcon = {
+ viewBox: '0 0 384 512',
+ path: 'M169.4 502.6c12.5 12.5 32.8 12.5 45.3 0l160-160c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L224 402.7 224 32c0-17.7-14.3-32-32-32s-32 14.3-32 32l0 370.7-105.4-105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3l160 160z',
+};
+
+export const arrowLeftIcon: SvgIcon = {
+ viewBox: '0 0 512 512',
+ path: 'M9.4 233.4c-12.5 12.5-12.5 32.8 0 45.3l160 160c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L109.3 288 480 288c17.7 0 32-14.3 32-32s-14.3-32-32-32l-370.7 0 105.4-105.4c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0l-160 160z',
+};
+
+export const arrowRightIcon: SvgIcon = {
+ viewBox: '0 0 512 512',
+ path: 'M502.6 278.6c12.5-12.5 12.5-32.8 0-45.3l-160-160c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L402.7 224 32 224c-17.7 0-32 14.3-32 32s14.3 32 32 32l370.7 0-105.4 105.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0l160-160z',
+};
+
+export const discordIcon: SvgIcon = {
+ viewBox: '0 0 576 512',
+ path: 'M492.5 69.8c-.2-.3-.4-.6-.8-.7-38.1-17.5-78.4-30-119.7-37.1-.4-.1-.8 0-1.1 .1s-.6 .4-.8 .8c-5.5 9.9-10.5 20.2-14.9 30.6-44.6-6.8-89.9-6.8-134.4 0-4.5-10.5-9.5-20.7-15.1-30.6-.2-.3-.5-.6-.8-.8s-.7-.2-1.1-.2c-41.3 7.1-81.6 19.6-119.7 37.1-.3 .1-.6 .4-.8 .7-76.2 113.8-97.1 224.9-86.9 334.5 0 .3 .1 .5 .2 .8s.3 .4 .5 .6c44.4 32.9 94 58 146.8 74.2 .4 .1 .8 .1 1.1 0s.7-.4 .9-.7c11.3-15.4 21.4-31.8 30-48.8 .1-.2 .2-.5 .2-.8s0-.5-.1-.8-.2-.5-.4-.6-.4-.3-.7-.4c-15.8-6.1-31.2-13.4-45.9-21.9-.3-.2-.5-.4-.7-.6s-.3-.6-.3-.9 0-.6 .2-.9 .3-.5 .6-.7c3.1-2.3 6.2-4.7 9.1-7.1 .3-.2 .6-.4 .9-.4s.7 0 1 .1c96.2 43.9 200.4 43.9 295.5 0 .3-.1 .7-.2 1-.2s.7 .2 .9 .4c2.9 2.4 6 4.9 9.1 7.2 .2 .2 .4 .4 .6 .7s.2 .6 .2 .9-.1 .6-.3 .9-.4 .5-.6 .6c-14.7 8.6-30 15.9-45.9 21.8-.2 .1-.5 .2-.7 .4s-.3 .4-.4 .7-.1 .5-.1 .8 .1 .5 .2 .8c8.8 17 18.8 33.3 30 48.8 .2 .3 .6 .6 .9 .7s.8 .1 1.1 0c52.9-16.2 102.6-41.3 147.1-74.2 .2-.2 .4-.4 .5-.6s.2-.5 .2-.8c12.3-126.8-20.5-236.9-86.9-334.5zm-302 267.7c-29 0-52.8-26.6-52.8-59.2s23.4-59.2 52.8-59.2c29.7 0 53.3 26.8 52.8 59.2 0 32.7-23.4 59.2-52.8 59.2zm195.4 0c-29 0-52.8-26.6-52.8-59.2s23.4-59.2 52.8-59.2c29.7 0 53.3 26.8 52.8 59.2 0 32.7-23.2 59.2-52.8 59.2z',
+};
+
+export const envelopeIcon: SvgIcon = {
+ viewBox: '0 0 512 512',
+ path: 'M48 64c-26.5 0-48 21.5-48 48 0 15.1 7.1 29.3 19.2 38.4l208 156c17.1 12.8 40.5 12.8 57.6 0l208-156c12.1-9.1 19.2-23.3 19.2-38.4 0-26.5-21.5-48-48-48L48 64zM0 196L0 384c0 35.3 28.7 64 64 64l384 0c35.3 0 64-28.7 64-64l0-188-198.4 148.8c-34.1 25.6-81.1 25.6-115.2 0L0 196z',
+};
+
+export const githubIcon: SvgIcon = {
+ viewBox: '0 0 512 512',
+ path: 'M173.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3 .3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5 .3-6.2 2.3zm44.2-1.7c-2.9 .7-4.9 2.6-4.6 4.9 .3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM252.8 8c-138.7 0-244.8 105.3-244.8 244 0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1 100-33.2 167.8-128.1 167.8-239 0-138.7-112.5-244-251.2-244zM105.2 352.9c-1.3 1-1 3.3 .7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3 .3 2.9 2.3 3.9 1.6 1 3.6 .7 4.3-.7 .7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3 .7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3 .7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9s4.3 3.3 5.6 2.3c1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z',
+};
+
+export const linkedinIcon: SvgIcon = {
+ viewBox: '0 0 448 512',
+ path: 'M416 32L31.9 32C14.3 32 0 46.5 0 64.3L0 447.7C0 465.5 14.3 480 31.9 480L416 480c17.6 0 32-14.5 32-32.3l0-383.4C448 46.5 433.6 32 416 32zM135.4 416l-66.4 0 0-213.8 66.5 0 0 213.8-.1 0zM102.2 96a38.5 38.5 0 1 1 0 77 38.5 38.5 0 1 1 0-77zM384.3 416l-66.4 0 0-104c0-24.8-.5-56.7-34.5-56.7-34.6 0-39.9 27-39.9 54.9l0 105.8-66.4 0 0-213.8 63.7 0 0 29.2 .9 0c8.9-16.8 30.6-34.5 62.9-34.5 67.2 0 79.7 44.3 79.7 101.9l0 117.2z',
+};
+
+export const phoneIcon: SvgIcon = {
+ viewBox: '0 0 512 512',
+ path: 'M160.2 25C152.3 6.1 131.7-3.9 112.1 1.4l-5.5 1.5c-64.6 17.6-119.8 80.2-103.7 156.4 37.1 175 174.8 312.7 349.8 349.8 76.3 16.2 138.8-39.1 156.4-103.7l1.5-5.5c5.4-19.7-4.7-40.3-23.5-48.1l-97.3-40.5c-16.5-6.9-35.6-2.1-47 11.8l-38.6 47.2C233.9 335.4 177.3 277 144.8 205.3L189 169.3c13.9-11.3 18.6-30.4 11.8-47L160.2 25z',
+};
diff --git a/src/main.server.ts b/src/main.server.ts
index b328231..b7c62b3 100644
--- a/src/main.server.ts
+++ b/src/main.server.ts
@@ -1,18 +1,12 @@
import type { BootstrapContext } from '@angular/platform-browser';
import { bootstrapApplication } from '@angular/platform-browser';
import type { ApplicationRef } from '@angular/core';
-import { provideZoneChangeDetection } from '@angular/core';
import { AppComponent } from './app/app.component';
import { config } from './app/app.config.server';
const bootstrap: (context: BootstrapContext) => Promise = (
context: BootstrapContext,
-): Promise =>
- bootstrapApplication(
- AppComponent,
- { ...config, providers: [provideZoneChangeDetection(), ...config.providers] },
- context,
- );
+): Promise => bootstrapApplication(AppComponent, config, context);
export default bootstrap;