Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,643 changes: 300 additions & 1,343 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"private": true,
"packageManager": "npm@10.9.3",
"dependencies": {
"@angular/cdk": "^22.1.0",
"@angular/common": "^22.0.8",
"@angular/compiler": "^22.0.8",
"@angular/core": "^22.0.8",
Expand Down
Binary file added public/assets/hero/hero-background.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/assets/hero/portrait-default.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/assets/hero/portrait-hover.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions public/assets/hero/waving-hand.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
226 changes: 115 additions & 111 deletions src/app/layout/header/header.html
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@
@for (item of navigationItems; track item.fragment) {
<li>
<a
class="group relative inline-flex items-center justify-center"
class="group relative inline-flex items-center justify-center text-foreground"
routerLink="/"
[fragment]="item.fragment"
>
Expand Down Expand Up @@ -133,124 +133,128 @@
</span>
</button>
</div>
</header>

<!-- Mobile navigation menu -->
<div
id="mobile-navigation"
class="fixed inset-x-0 top-0 z-[60] h-[656px] max-h-[100dvh] w-full overflow-y-auto overscroll-contain bg-[url('/assets/backgrounds/mobile-menu-background.webp')] bg-no-repeat [background-size:100%_100%] transition-transform duration-500 ease-[cubic-bezier(0.22,1,0.36,1)] motion-reduce:duration-0 lg:hidden"
[class.translate-y-0]="isMobileMenuOpen()"
[class.-translate-y-full]="!isMobileMenuOpen()"
[class.pointer-events-none]="!isMobileMenuOpen()"
[attr.aria-hidden]="!isMobileMenuOpen()"
[attr.inert]="isMobileMenuOpen() ? null : ''"
>
<div class="flex h-16 items-center justify-between px-4">
<a
class="inline-flex focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-primary"
routerLink="/"
aria-label="Go to homepage"
(click)="closeMobileMenu(false)"
>
<img class="size-8" src="/logo.svg" alt="" draggable="false" />
</a>
<!-- Mobile navigation menu -->
<div
id="mobile-navigation"
role="dialog"
aria-modal="true"
aria-label="Navigation menu"
class="fixed inset-x-0 top-0 z-60 h-164 max-h-[100dvh] w-full overflow-y-auto overscroll-contain bg-[url('/assets/backgrounds/mobile-menu-background.webp')] bg-no-repeat [background-size:100%_100%] transition-transform duration-500 ease-[cubic-bezier(0.22,1,0.36,1)] motion-reduce:duration-0 lg:hidden"
[cdkTrapFocus]="isMobileMenuOpen()"
[class.translate-y-0]="isMobileMenuOpen()"
[class.-translate-y-full]="!isMobileMenuOpen()"
[class.pointer-events-none]="!isMobileMenuOpen()"
[attr.aria-hidden]="!isMobileMenuOpen()"
[attr.inert]="isMobileMenuOpen() ? null : ''"
>
<div class="flex h-16 items-center justify-between px-4">
<a
class="inline-flex focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-primary"
routerLink="/"
aria-label="Go to homepage"
(click)="closeMobileMenu()"
>
<img class="size-8" src="/logo.svg" alt="" draggable="false" />
</a>

<button
#closeMenuButton
class="group inline-flex size-11 items-center justify-center focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary"
type="button"
aria-label="Close navigation menu"
aria-controls="mobile-navigation"
(click)="closeMobileMenu()"
>
<img
class="size-8 group-active:hidden"
src="/assets/icons/navigation/menu-close.svg"
alt=""
aria-hidden="true"
draggable="false"
/>
<button
#closeMenuButton
class="group inline-flex size-11 items-center justify-center focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary"
type="button"
aria-label="Close navigation menu"
aria-controls="mobile-navigation"
(click)="closeMobileMenu()"
>
<img
class="size-8 group-active:hidden"
src="/assets/icons/navigation/menu-close.svg"
alt=""
aria-hidden="true"
draggable="false"
/>

<img
class="hidden size-8 group-active:block"
src="/assets/icons/navigation/close-active.svg"
alt=""
aria-hidden="true"
draggable="false"
/>
</button>
</div>
<img
class="hidden size-8 group-active:block"
src="/assets/icons/navigation/close-active.svg"
alt=""
aria-hidden="true"
draggable="false"
/>
</button>
</div>

<div class="mt-16 flex flex-col items-center gap-8">
<!-- Mobile navigation -->
<nav aria-label="Mobile navigation">
<ul class="flex flex-col items-center gap-8">
@for (item of navigationItems; track item.fragment) {
<li>
<a
class="inline-flex items-center justify-center p-2.5 font-sans text-lg font-bold leading-[1.2] text-foreground focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-primary"
routerLink="/"
[fragment]="item.fragment"
(click)="closeMobileMenu(false)"
>
{{ item.label }}
</a>
</li>
}
</ul>
</nav>
<div class="mt-16 flex flex-col items-center gap-8">
<!-- Mobile navigation -->
<nav aria-label="Mobile navigation">
<ul class="flex flex-col items-center gap-8">
@for (item of navigationItems; track item.fragment) {
<li>
<a
class="inline-flex items-center justify-center p-2.5 font-sans text-lg font-bold leading-[1.2] text-foreground focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-primary"
routerLink="/"
[fragment]="item.fragment"
(click)="closeMobileMenu()"
>
{{ item.label }}
</a>
</li>
}
</ul>
</nav>

<!-- Language switch -->
<button
class="inline-flex h-8 items-center gap-1 p-0 font-sans text-base font-semibold leading-[1.2] focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-primary"
type="button"
[attr.aria-label]="
activeLanguage() === 'en' ? 'Switch language to German' : 'Switch language to English'
"
(click)="toggleLanguage()"
>
<span
class="inline-flex items-center justify-center"
[class.text-tertiary]="activeLanguage() === 'en'"
[class.text-foreground]="activeLanguage() === 'de'"
<!-- Language switch -->
<button
class="inline-flex h-8 items-center gap-1 p-0 font-sans text-base font-semibold leading-[1.2] focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-primary"
type="button"
[attr.aria-label]="
activeLanguage() === 'en' ? 'Switch language to German' : 'Switch language to English'
"
(click)="toggleLanguage()"
>
EN
</span>

<span class="relative h-4 w-10 shrink-0" aria-hidden="true">
<span
class="absolute top-1/2 left-2 h-2 w-8 -translate-y-1/2 rounded-full border border-foreground bg-tertiary"
></span>
class="inline-flex items-center justify-center"
[class.text-tertiary]="activeLanguage() === 'en'"
[class.text-foreground]="activeLanguage() === 'de'"
>
EN
</span>

<span
class="absolute top-0 left-0 size-4 rounded-full border border-foreground bg-primary transition-transform duration-200 ease-out motion-reduce:transition-none"
[class.translate-x-6]="activeLanguage() === 'de'"
></span>
</span>
<span class="relative h-4 w-10 shrink-0" aria-hidden="true">
<span
class="absolute top-1/2 left-2 h-2 w-8 -translate-y-1/2 rounded-full border border-foreground bg-tertiary"
></span>

<span
class="inline-flex items-center justify-center"
[class.text-tertiary]="activeLanguage() === 'de'"
[class.text-foreground]="activeLanguage() === 'en'"
>
DE
</span>
</button>
</div>
<span
class="absolute top-0 left-0 size-4 rounded-full border border-foreground bg-primary transition-transform duration-200 ease-out motion-reduce:transition-none"
[class.translate-x-6]="activeLanguage() === 'de'"
></span>
</span>

<!-- Social links -->
<div class="mt-16 flex items-center justify-center gap-6">
@for (item of socialLinks; track item.id) {
<a
class="inline-flex size-10 items-center justify-center rounded-full focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary"
[href]="item.href"
[attr.aria-label]="item.accessibleLabel"
[attr.target]="item.opensInNewTab ? '_blank' : null"
[attr.rel]="item.opensInNewTab ? 'noopener noreferrer' : null"
(click)="closeMobileMenu(false)"
>
<img class="size-10" [src]="item.iconSrc" alt="" draggable="false" />
</a>
}
<span
class="inline-flex items-center justify-center"
[class.text-tertiary]="activeLanguage() === 'de'"
[class.text-foreground]="activeLanguage() === 'en'"
>
DE
</span>
</button>
</div>

<!-- Social links -->
<div class="mt-16 flex items-center justify-center gap-6">
@for (item of socialLinks; track item.id) {
<a
class="inline-flex size-10 items-center justify-center rounded-full focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary"
[href]="item.href"
[attr.aria-label]="item.accessibleLabel"
[attr.target]="item.opensInNewTab ? '_blank' : null"
[attr.rel]="item.opensInNewTab ? 'noopener noreferrer' : null"
(click)="closeMobileMenu()"
>
<img class="size-10" [src]="item.iconSrc" alt="" draggable="false" />
</a>
}
</div>
</div>
</div>
</header>
22 changes: 15 additions & 7 deletions src/app/layout/header/header.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { DOCUMENT } from '@angular/common';
import { CdkTrapFocus } from '@angular/cdk/a11y';
import {
afterNextRender,
Component,
Expand All @@ -17,7 +18,7 @@ const DESKTOP_MEDIA_QUERY = '(min-width: 64rem)';

@Component({
selector: 'app-header',
imports: [RouterLink],
imports: [RouterLink, CdkTrapFocus],
templateUrl: './header.html',
})
export class Header implements OnDestroy {
Expand Down Expand Up @@ -114,14 +115,21 @@ export class Header implements OnDestroy {
return;
}

if (restoreFocus) {
this.openMenuButton().nativeElement.focus({
preventScroll: true,
});
}

this.isMobileMenuOpen.set(false);
this.restoreBodyScroll();

if (restoreFocus) {
afterNextRender(
() => {
this.openMenuButton().nativeElement.focus({
preventScroll: true,
});
},
{
injector: this.injector,
},
);
}
}

@HostListener('document:keydown.escape')
Expand Down
Loading