Redesign portfolio around systems case studies

This commit is contained in:
v7
2026-09-06 19:39:23 +02:00
parent bd3f260700
commit 384c8563de
29 changed files with 1198 additions and 841 deletions

107
src/app/features/portfolio/portfolio-page.component.ts Executable file → Normal file
View File

@@ -1,19 +1,116 @@
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
import { ActivatedRoute, RouterLink } from '@angular/router';
import { PageShellComponent } from '../../core/layout/page-shell.component';
import { PortfolioSectionComponent } from './portfolio-section.component';
import { SeoService } from '../../core/services/seo.service';
import { MAIN_NAVIGATION } from '../../shared/config/navigation.config';
import { PortfolioProjectId } from '../work/portfolio-project.model';
import { getPortfolioProject, PORTFOLIO_PROJECTS } from '../work/portfolio-projects.data';
import { SystemDiagramComponent } from '../work/system-diagram.component';
@Component({
selector: 'app-portfolio-page',
standalone: true,
imports: [PageShellComponent, PortfolioSectionComponent],
imports: [PageShellComponent, RouterLink, SystemDiagramComponent],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<app-page-shell [sections]="sections">
<app-portfolio-section />
<article>
<header class="case-hero shell">
<nav class="breadcrumb mono" aria-label="Breadcrumb"><a routerLink="/">Work</a><span>/</span><span>{{ project.index }}</span></nav>
<div class="case-heading">
<div>
<p class="eyebrow mono">{{ project.category }} · {{ project.period }}</p>
<h1>{{ project.title }}</h1>
</div>
<div class="case-intro">
<p>{{ project.summary }}</p>
<dl><div><dt>Role</dt><dd>{{ project.role }}</dd></div><div><dt>Period</dt><dd>{{ project.period }}</dd></div></dl>
</div>
</div>
</header>
<section class="case-section shell narrative" aria-labelledby="challenge-title">
<p class="case-index mono">01 / Context</p>
<div><h2 id="challenge-title">The challenge</h2><p class="large-copy">{{ project.challenge }}</p></div>
</section>
<section class="case-section shell" aria-labelledby="contributions-title">
<p class="case-index mono">02 / Delivery</p>
<div>
<h2 id="contributions-title">What I contributed</h2>
<ol class="contribution-list">
@for (contribution of project.contributions; track contribution; let index = $index) {
<li><span class="mono">{{ contributionNumber(index) }}</span><p>{{ contribution }}</p></li>
}
</ol>
</div>
</section>
<section class="diagram-wrap">
<div class="shell"><app-system-diagram [kind]="project.diagram" [title]="project.title" /></div>
</section>
<section class="case-section shell" aria-labelledby="decisions-title">
<p class="case-index mono">03 / Architecture</p>
<div>
<h2 id="decisions-title">Key decisions</h2>
<div class="decision-grid">
@for (decision of project.decisions; track decision.title; let index = $index) {
<article><span class="mono">{{ contributionNumber(index) }}</span><h3>{{ decision.title }}</h3><p>{{ decision.detail }}</p></article>
}
</div>
</div>
</section>
<section class="case-section shell narrative outcome" aria-labelledby="outcome-title">
<p class="case-index mono">04 / Result</p>
<div><h2 id="outcome-title">The outcome</h2><p class="large-copy">{{ project.outcome }}</p></div>
</section>
<section class="case-stack shell" aria-label="Technology stack">
<p class="case-index mono">Technology</p>
<ul>@for (technology of project.stack; track technology) { <li>{{ technology }}</li> }</ul>
</section>
<nav class="case-pagination shell" aria-label="Case studies">
<a [routerLink]="['/work', previous.slug]"><span class="mono">← Previous</span><strong>{{ previous.title }}</strong></a>
<a [routerLink]="['/work', next.slug]"><span class="mono">Next →</span><strong>{{ next.title }}</strong></a>
</nav>
<section class="contact-block shell case-contact" aria-labelledby="case-contact-title">
<p class="eyebrow mono">Have a complex system?</p>
<h2 id="case-contact-title">Let’s make the next change safer and more useful.</h2>
<div class="contact-actions"><a class="button button-light" href="mailto:amar@dzanan.net">Start a conversation <span aria-hidden="true">↗</span></a><a class="text-link light" routerLink="/cv">View full CV →</a></div>
</section>
</article>
</app-page-shell>
`
})
export class PortfolioPageComponent {
readonly sections = MAIN_NAVIGATION;
}
readonly project = getPortfolioProject(inject(ActivatedRoute).snapshot.data['projectId'] as PortfolioProjectId);
readonly projectIndex = PORTFOLIO_PROJECTS.findIndex(item => item.id === this.project.id);
readonly previous = PORTFOLIO_PROJECTS[(this.projectIndex - 1 + PORTFOLIO_PROJECTS.length) % PORTFOLIO_PROJECTS.length];
readonly next = PORTFOLIO_PROJECTS[(this.projectIndex + 1) % PORTFOLIO_PROJECTS.length];
constructor() {
inject(SeoService).update({
title: `${this.project.title} — Amar Džanan`,
description: this.project.seoDescription,
path: `/work/${this.project.slug}`,
type: 'article',
structuredData: {
'@context': 'https://schema.org',
'@type': 'Article',
headline: this.project.title,
description: this.project.seoDescription,
author: { '@type': 'Person', name: 'Amar Džanan', url: 'https://dzanan.net/' },
url: `https://dzanan.net/work/${this.project.slug}`
}
});
}
contributionNumber(index: number): string {
return String(index + 1).padStart(2, '0');
}
}