100 lines
4.1 KiB
TypeScript
100 lines
4.1 KiB
TypeScript
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
|
|
import { CommonModule, NgOptimizedImage } from '@angular/common';
|
|
import { RouterLink } from '@angular/router';
|
|
import { SectionNavItem } from '../../shared/models/section-nav.model';
|
|
import { ThemeToggleComponent } from '../../shared/ui/theme-toggle/theme-toggle.component';
|
|
|
|
@Component({
|
|
selector: 'app-page-shell',
|
|
standalone: true,
|
|
imports: [CommonModule, RouterLink, ThemeToggleComponent, NgOptimizedImage],
|
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
template: `
|
|
<div
|
|
class="min-h-screen bg-[#f4f7f5] text-zinc-800 font-saira transition-colors duration-300 dark:bg-slate-950 dark:text-zinc-100"
|
|
>
|
|
<header
|
|
class="sticky top-0 z-30 border-b border-zinc-200/70 bg-[#f8faf7]/85 backdrop-blur transition-colors duration-300 dark:border-slate-900/60 dark:bg-slate-950/70"
|
|
>
|
|
<div
|
|
class="mx-auto flex max-w-6xl items-center justify-between px-4 py-4 md:px-8"
|
|
>
|
|
<div class="flex items-center gap-3">
|
|
<img
|
|
ngSrc="/images/profile.jpg"
|
|
width="40"
|
|
height="40"
|
|
priority
|
|
alt="Dzanan"
|
|
class="h-10 w-10 rounded-full object-cover ring-1 ring-emerald-600/20 transition-colors dark:ring-emerald-500/30"
|
|
/>
|
|
<div class="flex flex-col leading-tight">
|
|
<span class="text-sm text-zinc-500 dark:text-zinc-400"
|
|
>Senior Software Engineer</span
|
|
>
|
|
<span class="text-base font-semibold">Amar Džanan</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Desktop Nav & Toggle -->
|
|
<div class="hidden items-center gap-4 md:flex">
|
|
<nav class="flex items-center gap-2">
|
|
@for (section of sections; track section.label) {
|
|
<a
|
|
[routerLink]="section.path || []"
|
|
[fragment]="section.fragment"
|
|
class="rounded-full px-3 py-2 text-sm font-medium text-zinc-600 transition hover:bg-emerald-50/80 hover:text-zinc-900 dark:text-zinc-200 dark:hover:bg-slate-900 dark:hover:text-white"
|
|
>
|
|
{{ section.label }}
|
|
</a>
|
|
}
|
|
</nav>
|
|
<div
|
|
class="h-6 w-px bg-zinc-200 dark:bg-slate-800"
|
|
aria-hidden="true"
|
|
></div>
|
|
<app-theme-toggle />
|
|
</div>
|
|
|
|
<!-- Mobile Toggle & Menu Placeholder -->
|
|
<div class="flex items-center gap-4 md:hidden">
|
|
<app-theme-toggle />
|
|
<span class="text-sm text-zinc-500 dark:text-zinc-400"
|
|
>Navigate</span
|
|
>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
<main class="relative overflow-hidden opacity-0 animate-[fade-in-up_1s_ease-out_0.3s_forwards]">
|
|
<div
|
|
class="pointer-events-none absolute inset-0 opacity-60 transition-opacity duration-500 dark:opacity-60"
|
|
aria-hidden="true"
|
|
>
|
|
<div
|
|
class="absolute inset-x-0 top-0 h-96 bg-[radial-gradient(circle_at_50%_20%,rgba(16,185,129,0.1),transparent_42%)] dark:bg-[radial-gradient(circle_at_50%_20%,rgba(16,185,129,0.2),transparent_40%)]"
|
|
></div>
|
|
<div
|
|
class="absolute inset-y-0 right-0 w-1/2 bg-[radial-gradient(circle_at_80%_50%,rgba(14,165,233,0.07),transparent_38%)] dark:bg-[radial-gradient(circle_at_80%_50%,rgba(59,130,246,0.12),transparent_35%)]"
|
|
></div>
|
|
</div>
|
|
<div class="relative">
|
|
<ng-content />
|
|
</div>
|
|
</main>
|
|
<footer
|
|
class="border-t border-zinc-200/70 bg-[#f8faf7]/85 transition-colors duration-300 dark:border-slate-900/60 dark:bg-slate-950/80"
|
|
>
|
|
<div
|
|
class="mx-auto flex max-w-6xl flex-wrap items-center justify-between gap-4 px-4 py-6 text-sm text-zinc-500 dark:text-zinc-400 md:px-8"
|
|
>
|
|
<span>Built for clarity and craft.</span>
|
|
<span class="text-zinc-600 dark:text-zinc-500">dzanan.net</span>
|
|
</div>
|
|
</footer>
|
|
</div>
|
|
`
|
|
})
|
|
export class PageShellComponent {
|
|
@Input({ required: true }) sections: SectionNavItem[] = [];
|
|
}
|