first commit
All checks were successful
Deploy dzanan.net / deploy (push) Successful in 1m2s

This commit is contained in:
v7
2026-05-14 20:58:16 +02:00
commit 1599615ec7
48 changed files with 13000 additions and 0 deletions

View File

@@ -0,0 +1,85 @@
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { SectionComponent } from '../../shared/ui/section/section.component';
@Component({
selector: 'app-about-it-section',
standalone: true,
imports: [CommonModule, SectionComponent],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<app-section
[id]="sectionId"
eyebrow="About IT"
title="Thoughtful engineering over novelty"
lead="I gravitate toward choices that keep the codebase predictable, observable, and easy to evolve."
>
<div class="grid gap-6 lg:grid-cols-3">
<div
class="rounded-xl border border-zinc-200 bg-white/60 p-4 dark:border-slate-800/60 dark:bg-slate-900/40"
>
<p class="text-sm font-semibold text-emerald-600 dark:text-emerald-300">
Architecture
</p>
<p class="mt-2 text-sm text-zinc-600 dark:text-zinc-300">
Lean compositions, clear data boundaries, and contracts that are
easy to test. Small, composable modules beat monolith components.
</p>
</div>
<div
class="rounded-xl border border-zinc-200 bg-white/60 p-4 dark:border-slate-800/60 dark:bg-slate-900/40"
>
<p class="text-sm font-semibold text-emerald-600 dark:text-emerald-300">Front end</p>
<p class="mt-2 text-sm text-zinc-600 dark:text-zinc-300">
Signals for straightforward state, typed APIs, accessibility baked
into the UI, and styling that stays aligned with design systems.
</p>
</div>
<div
class="rounded-xl border border-zinc-200 bg-white/60 p-4 dark:border-slate-800/60 dark:bg-slate-900/40"
>
<p class="text-sm font-semibold text-emerald-600 dark:text-emerald-300">
Delivery
</p>
<p class="mt-2 text-sm text-zinc-600 dark:text-zinc-300">
Automated checks, preview builds, and incremental releases over
big-bang launches. Measure impact; iterate with feedback.
</p>
</div>
<div
class="rounded-xl border border-zinc-200 bg-white/60 p-4 dark:border-slate-800/60 dark:bg-slate-900/40"
>
<p class="text-sm font-semibold text-emerald-600 dark:text-emerald-300">
Backend Integration
</p>
<p class="mt-2 text-sm text-zinc-600 dark:text-zinc-300">
Seamless integration with backend services, optimizing data flow, and ensuring efficient full-stack performance.
</p>
</div>
<div
class="rounded-xl border border-zinc-200 bg-white/60 p-4 dark:border-slate-800/60 dark:bg-slate-900/40"
>
<p class="text-sm font-semibold text-emerald-600 dark:text-emerald-300">
API Contracts
</p>
<p class="mt-2 text-sm text-zinc-600 dark:text-zinc-300">
Defining clear, strongly-typed API contracts to ensure reliability, improve developer experience, and reduce integration friction.
</p>
</div>
<div
class="rounded-xl border border-zinc-200 bg-white/60 p-4 dark:border-slate-800/60 dark:bg-slate-900/40"
>
<p class="text-sm font-semibold text-emerald-600 dark:text-emerald-300">
Client Handling
</p>
<p class="mt-2 text-sm text-zinc-600 dark:text-zinc-300">
Proactive communication, understanding client needs, translating requirements into technical specifications, and ensuring timely delivery with clear expectations.
</p>
</div>
</div>
</app-section>
`
})
export class AboutItSectionComponent {
readonly sectionId = 'about-it';
}

View File

@@ -0,0 +1,60 @@
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { SectionComponent } from '../../shared/ui/section/section.component';
@Component({
selector: 'app-about-me-section',
standalone: true,
imports: [CommonModule, SectionComponent],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<app-section
[id]="sectionId"
eyebrow="About me"
title="Engineer, collaborator, perpetual learner"
lead="I like to keep teams moving: establish a clear plan, align stakeholders, and turn good ideas into shipped, measurable outcomes."
>
<div class="grid gap-8 lg:grid-cols-2">
<div class="space-y-4 text-zinc-600 dark:text-zinc-200">
<p>
I care about pairing good taste with solid engineering practices.
Whether building a new product or refining an existing one, I work
to keep the codebase tidy, the UI cohesive, and the delivery
predictable.
</p>
<p>
I thrive in cross-functional teams, translating between design,
product, and engineering to keep efforts aligned and transparent.
</p>
</div>
<div class="grid gap-3">
<div
class="rounded-xl border border-zinc-200 bg-white/60 p-4 dark:border-slate-800/60 dark:bg-slate-900/40"
>
<p class="text-sm font-semibold text-emerald-600 dark:text-emerald-300">
Working principles
</p>
<ul class="mt-2 space-y-2 text-sm text-zinc-600 dark:text-zinc-300">
<li>• Design for clarity, then refine for speed.</li>
<li>• Prefer simple interfaces over clever abstractions.</li>
<li>• Optimize for maintainability and team velocity.</li>
</ul>
</div>
<div
class="rounded-xl border border-zinc-200 bg-white/60 p-4 dark:border-slate-800/60 dark:bg-slate-900/40"
>
<p class="text-sm font-semibold text-emerald-600 dark:text-emerald-300">Ways I help</p>
<ul class="mt-2 space-y-2 text-sm text-zinc-600 dark:text-zinc-300">
<li>• Structuring design systems and component libraries.</li>
<li>• Improving developer experience and tooling.</li>
<li>• Coaching teams on pragmatic testing and reviews.</li>
</ul>
</div>
</div>
</div>
</app-section>
`
})
export class AboutMeSectionComponent {
readonly sectionId = 'about-me';
}

View File

@@ -0,0 +1,19 @@
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { PageShellComponent } from '../../core/layout/page-shell.component';
import { CvSectionComponent } from './cv-section.component';
import { MAIN_NAVIGATION } from '../../shared/config/navigation.config';
@Component({
selector: 'app-cv-page',
standalone: true,
imports: [PageShellComponent, CvSectionComponent],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<app-page-shell [sections]="sections">
<app-cv-section />
</app-page-shell>
`
})
export class CvPageComponent {
readonly sections = MAIN_NAVIGATION;
}

View File

@@ -0,0 +1,80 @@
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { SectionComponent } from '../../shared/ui/section/section.component';
@Component({
selector: 'app-cv-section',
standalone: true,
imports: [CommonModule, SectionComponent],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<app-section
[id]="sectionId"
eyebrow="CV"
title="Snapshot of my experience"
lead="Highlights of how I work and where I add value. Full CV is ready to share on request."
>
<div class="grid gap-6 lg:grid-cols-3">
<div
class="rounded-xl border border-zinc-200 bg-white/60 p-4 dark:border-slate-800/60 dark:bg-slate-900/40"
>
<p class="text-sm font-semibold text-emerald-600 dark:text-emerald-300">
Experience focus
</p>
<ul class="mt-2 space-y-2 text-sm text-zinc-600 dark:text-zinc-300">
<li>• Leading front-end builds for product teams.</li>
<li>• Partnering with design on component systems.</li>
<li>• Modernizing legacy code into maintainable stacks.</li>
<li>• Coaching teams on delivery practices and reviews.</li>
</ul>
</div>
<div
class="rounded-xl border border-zinc-200 bg-white/60 p-4 dark:border-slate-800/60 dark:bg-slate-900/40"
>
<p class="text-sm font-semibold text-emerald-600 dark:text-emerald-300">Skills & tools</p>
<div class="mt-3 flex flex-wrap gap-2 text-xs">
<span class="rounded-full bg-zinc-100 px-3 py-1 text-zinc-700 dark:bg-slate-800/80 dark:text-zinc-200">
Angular
</span>
<span class="rounded-full bg-zinc-100 px-3 py-1 text-zinc-700 dark:bg-slate-800/80 dark:text-zinc-200">
TypeScript
</span>
<span class="rounded-full bg-zinc-100 px-3 py-1 text-zinc-700 dark:bg-slate-800/80 dark:text-zinc-200">
Node.js
</span>
<span class="rounded-full bg-zinc-100 px-3 py-1 text-zinc-700 dark:bg-slate-800/80 dark:text-zinc-200">
Tailwind
</span>
<span class="rounded-full bg-zinc-100 px-3 py-1 text-zinc-700 dark:bg-slate-800/80 dark:text-zinc-200">
Design systems
</span>
<span class="rounded-full bg-zinc-100 px-3 py-1 text-zinc-700 dark:bg-slate-800/80 dark:text-zinc-200">
Testing
</span>
<span class="rounded-full bg-zinc-100 px-3 py-1 text-zinc-700 dark:bg-slate-800/80 dark:text-zinc-200">
CI/CD
</span>
</div>
</div>
<div
class="rounded-xl border border-zinc-200 bg-white/60 p-4 dark:border-slate-800/60 dark:bg-slate-900/40"
>
<p class="text-sm font-semibold text-emerald-600 dark:text-emerald-300">
How I structure work
</p>
<ul class="mt-2 space-y-2 text-sm text-zinc-600 dark:text-zinc-300">
<li>• Clarify requirements with fast prototypes.</li>
<li>• Ship in small increments with measurable outcomes.</li>
<li>• Automate tests and checks to keep releases calm.</li>
<li>
• Document interfaces and patterns for future teammates.
</li>
</ul>
</div>
</div>
</app-section>
`
})
export class CvSectionComponent {
readonly sectionId = 'cv';
}

View File

@@ -0,0 +1,146 @@
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { SectionComponent } from '../../shared/ui/section/section.component';
type TimelineSide = 'left' | 'right';
type TimelineEntry = {
step: string;
phase: string;
title: string;
subtitle: string;
side: TimelineSide;
pulsating?: boolean; // Make pulsating an optional boolean property
};
@Component({
selector: 'app-home-section',
standalone: true,
imports: [CommonModule, SectionComponent],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<app-section
[id]="sectionId"
eyebrow="Career Journey"
title="Building dependable software that feels effortless"
lead="Here’s a timeline of my professional growth and key milestones, highlighting significant roles and contributions."
>
<div
class="overflow-hidden rounded-3xl border border-zinc-200 bg-white/60 shadow-xl shadow-emerald-500/5 backdrop-blur-sm transition-colors duration-300 dark:border-slate-800/60 dark:bg-slate-950/40 dark:shadow-emerald-900/20"
>
<div class="relative px-4 py-16 md:px-12">
<!-- Central Line -->
<div
class="absolute left-8 top-0 h-full w-px -translate-x-1/2 bg-gradient-to-b from-transparent via-zinc-300 to-transparent transition-colors duration-300 dark:via-slate-700 md:left-1/2"
aria-hidden="true"
></div>
<div class="flex flex-col gap-12 md:gap-20">
@for (entry of timeline; track entry.title) {
<div
class="group relative grid grid-cols-[2rem_1fr] gap-8 md:grid-cols-[1fr_auto_1fr] md:items-center md:gap-0"
>
<!-- Left Side Content (Desktop) -->
<div class="hidden md:block md:col-start-1 md:pr-12 md:text-right">
@if (entry.side === 'left') {
<div class="relative">
<span class="mb-2 inline-block text-xs font-bold uppercase tracking-widest text-emerald-600/90 dark:text-emerald-500/80">
{{ entry.step }} &bull; {{ entry.phase }}
</span>
<h3 class="mb-2 text-xl font-semibold text-zinc-900 transition-colors group-hover:text-emerald-600 dark:text-slate-100 dark:group-hover:text-emerald-400">
{{ entry.title }}
</h3>
<p class="text-base text-zinc-600 leading-relaxed dark:text-slate-400">
{{ entry.subtitle }}
</p>
</div>
}
</div>
<!-- Center Marker -->
<div class="relative col-start-1 flex items-center justify-center md:col-start-2">
<div class="relative flex h-3 w-3 items-center justify-center">
@if (entry.pulsating) {
<span
class="absolute inline-flex h-full w-full animate-ping rounded-full bg-emerald-400 opacity-75"
></span>
}
<div
class="relative h-3 w-3 rounded-full bg-emerald-500 ring-4 ring-white shadow-[0_0_12px_rgba(16,185,129,0.6)] transition-all duration-300 group-hover:scale-125 dark:ring-slate-950"
></div>
</div>
</div>
<!-- Right Side Content (Desktop) & Mobile Content (All) -->
<div class="col-start-2 md:col-start-3 md:pl-12">
@if (entry.side === 'right') {
<div class="relative">
<span class="mb-2 inline-block text-xs font-bold uppercase tracking-widest text-emerald-600/90 dark:text-emerald-500/80">
{{ entry.step }} &bull; {{ entry.phase }}
</span>
<h3 class="mb-2 text-xl font-semibold text-zinc-900 transition-colors group-hover:text-emerald-600 dark:text-slate-100 dark:group-hover:text-emerald-400">
{{ entry.title }}
</h3>
<p class="text-base text-zinc-600 leading-relaxed dark:text-slate-400">
{{ entry.subtitle }}
</p>
</div>
}
@else {
<!-- Mobile only view for 'left' entries -->
<div class="block md:hidden">
<span class="mb-2 inline-block text-xs font-bold uppercase tracking-widest text-emerald-600/90 dark:text-emerald-500/80">
{{ entry.step }} &bull; {{ entry.phase }}
</span>
<h3 class="mb-2 text-xl font-semibold text-zinc-900 transition-colors group-hover:text-emerald-600 dark:text-slate-100 dark:group-hover:text-emerald-400">
{{ entry.title }}
</h3>
<p class="text-base text-zinc-600 leading-relaxed dark:text-slate-400">
{{ entry.subtitle }}
</p>
</div>
}
</div>
</div>
}
</div>
</div>
</div>
</app-section>
`
})
export class HomeSectionComponent {
readonly sectionId = 'home';
readonly timeline: TimelineEntry[] = [
{
step: '2000 - 2010',
phase: 'COMP-2000 Ltd',
title: 'Junior Developer',
subtitle: 'Started my career focusing on foundational programming skills and contributing to small-scale projects.',
side: 'left'
},
{
step: '2010 - 2020',
phase: 'COMP-2000 Ltd',
title: 'Senior Developer',
subtitle: 'Led development teams, implemented complex features, and mentored junior staff, specializing in backend systems.',
side: 'right'
},
{
step: '2020 - 2024',
phase: 'COMP-2000 Ltd',
title: 'Technical Lead',
subtitle: 'Architected scalable solutions and managed the technical roadmap for critical product lines.',
side: 'left'
},
{
step: '2024 - Present',
phase: 'Unija ETL Group',
title: 'Senior Software Engineer',
subtitle: 'Currently focused on developing robust ETL processes and data integration solutions within a dynamic financial environment.',
side: 'right',
pulsating: true
}
];
}

View File

@@ -0,0 +1,28 @@
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { PageShellComponent } from '../../core/layout/page-shell.component';
import { HomeSectionComponent } from '../home/home-section.component';
import { AboutMeSectionComponent } from '../about/about-me-section.component';
import { AboutItSectionComponent } from '../about/about-it-section.component';
import { MAIN_NAVIGATION } from '../../shared/config/navigation.config';
@Component({
selector: 'app-landing-page',
standalone: true,
imports: [
PageShellComponent,
HomeSectionComponent,
AboutMeSectionComponent,
AboutItSectionComponent
],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<app-page-shell [sections]="sections">
<app-home-section />
<app-about-me-section />
<app-about-it-section />
</app-page-shell>
`
})
export class LandingPageComponent {
readonly sections = MAIN_NAVIGATION;
}

View File

@@ -0,0 +1,19 @@
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { PageShellComponent } from '../../core/layout/page-shell.component';
import { PortfolioSectionComponent } from './portfolio-section.component';
import { MAIN_NAVIGATION } from '../../shared/config/navigation.config';
@Component({
selector: 'app-portfolio-page',
standalone: true,
imports: [PageShellComponent, PortfolioSectionComponent],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<app-page-shell [sections]="sections">
<app-portfolio-section />
</app-page-shell>
`
})
export class PortfolioPageComponent {
readonly sections = MAIN_NAVIGATION;
}

View File

@@ -0,0 +1,86 @@
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { SectionComponent } from '../../shared/ui/section/section.component';
type PortfolioItem = {
title: string;
summary: string;
period: string;
tags: string[];
};
@Component({
selector: 'app-portfolio-section',
standalone: true,
imports: [CommonModule, SectionComponent],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<app-section
[id]="sectionId"
eyebrow="Portfolio"
title="Representative work"
lead="A few examples of the kind of outcomes I like to deliver. Swap in your own case studies here."
>
<div class="grid gap-6 md:grid-cols-2">
@for (item of items; track item.title) {
<article
class="flex h-full flex-col gap-3 rounded-xl border border-zinc-200 bg-white/60 p-5 dark:border-slate-800/60 dark:bg-slate-900/40"
>
<div class="flex items-center justify-between text-sm text-zinc-500 dark:text-zinc-400">
<span>{{ item.period }}</span>
<div class="flex flex-wrap gap-1">
@for (tag of item.tags; track tag) {
<span
class="rounded-full bg-zinc-100 px-2 py-1 text-[11px] text-zinc-700 dark:bg-slate-800/80 dark:text-zinc-200"
>
{{ tag }}
</span>
}
</div>
</div>
<h3 class="text-lg font-semibold text-zinc-900 dark:text-zinc-50">
{{ item.title }}
</h3>
<p class="text-sm text-zinc-600 dark:text-zinc-300">
{{ item.summary }}
</p>
</article>
}
</div>
</app-section>
`
})
export class PortfolioSectionComponent {
readonly sectionId = 'portfolio';
readonly items: PortfolioItem[] = [
{
title: 'Design system foundation',
period: '2024',
summary:
'Built a lightweight component system with clear tokens and documentation, reducing UI delivery time while keeping accessibility first.',
tags: ['Angular', 'Tailwind', 'Design systems']
},
{
title: 'Modernize a legacy SPA',
period: '2023',
summary:
'Incrementally refactored a legacy front end into modular features with typed APIs, improving stability and developer velocity.',
tags: ['Refactoring', 'TypeScript', 'DX']
},
{
title: 'API-first product slice',
period: '2022',
summary:
'Shipped a new product slice with clear contracts between front end and services, enabling parallel delivery across teams.',
tags: ['Architecture', 'APIs', 'Collaboration']
},
{
title: 'Performance and observability pass',
period: '2021',
summary:
'Improved performance budgets, added monitoring, and set up alerting to catch regressions before they reached users.',
tags: ['Performance', 'Monitoring', 'Quality']
}
];
}