83 lines
3.9 KiB
Vue
83 lines
3.9 KiB
Vue
<script setup lang="ts">
|
|
import { computed } from 'vue';
|
|
import ContactDestination from './ContactDestination.vue';
|
|
import ExperienceTimeline from './ExperienceTimeline.vue';
|
|
import InfrastructureDiagram from './InfrastructureDiagram.vue';
|
|
import PortfolioSection from './PortfolioSection.vue';
|
|
import ProjectCard from './ProjectCard.vue';
|
|
import ProjectCaseStudy from './ProjectCaseStudy.vue';
|
|
import SiteNavigation from './SiteNavigation.vue';
|
|
import ToolkitGrid from './ToolkitGrid.vue';
|
|
import type { PortfolioContent, SectionId } from '@/content/types';
|
|
|
|
const props = defineProps<{ content: Readonly<PortfolioContent> }>();
|
|
|
|
const canonicalSectionIds = [
|
|
'start', 'profile', 'experience', 'infrastructure', 'selected-builds', 'additional-work', 'toolkit', 'contact',
|
|
] as const satisfies readonly SectionId[];
|
|
|
|
const navigationById = computed(() => new Map(props.content.navigation.map((item) => [item.id, item])));
|
|
|
|
const sectionLabel = (id: SectionId): string => {
|
|
const item = navigationById.value.get(id);
|
|
if (item === undefined) {
|
|
throw new Error(`Missing canonical navigation label: ${id}`);
|
|
}
|
|
return item.label;
|
|
};
|
|
|
|
const canonicalNavigation = computed(() => canonicalSectionIds.map((id) => ({ id, label: sectionLabel(id) })));
|
|
</script>
|
|
|
|
<template>
|
|
<a class="skip-link" href="#portfolio-content">Skip to portfolio content</a>
|
|
<header class="site-header">
|
|
<h1>{{ content.identity.name }}</h1>
|
|
<p>{{ content.identity.role }}</p>
|
|
<p class="print-portfolio-label" aria-hidden="true">Portfolio</p>
|
|
<SiteNavigation :items="canonicalNavigation" />
|
|
</header>
|
|
|
|
<main id="portfolio-content" class="portfolio-main">
|
|
<PortfolioSection id="start" :index="1" :eyebrow="sectionLabel('start').toUpperCase()" :heading="sectionLabel('start').toUpperCase()">
|
|
<p>{{ content.identity.valueProposition }}</p>
|
|
</PortfolioSection>
|
|
|
|
<PortfolioSection id="profile" :index="2" :eyebrow="sectionLabel('profile').toUpperCase()" :heading="sectionLabel('profile').toUpperCase()">
|
|
<p>{{ content.profile.introduction }}</p>
|
|
<dl>
|
|
<template v-for="fact in content.profile.screeningFacts" :key="fact.label">
|
|
<dt>{{ fact.label }}</dt>
|
|
<dd>{{ fact.value }}</dd>
|
|
</template>
|
|
</dl>
|
|
</PortfolioSection>
|
|
|
|
<PortfolioSection id="experience" :index="3" :eyebrow="sectionLabel('experience').toUpperCase()" :heading="sectionLabel('experience').toUpperCase()">
|
|
<ExperienceTimeline :experience="content.experience" :systems-operations="content.systemsOperations" />
|
|
</PortfolioSection>
|
|
|
|
<PortfolioSection id="infrastructure" :index="4" :eyebrow="sectionLabel('infrastructure').toUpperCase()" :heading="sectionLabel('infrastructure').toUpperCase()">
|
|
<InfrastructureDiagram :infrastructure="content.infrastructure" />
|
|
</PortfolioSection>
|
|
|
|
<PortfolioSection id="selected-builds" :index="5" :eyebrow="sectionLabel('selected-builds').toUpperCase()" :heading="sectionLabel('selected-builds').toUpperCase()">
|
|
<ProjectCaseStudy v-for="project in content.projects.selected" :key="project.id" :project="project" />
|
|
</PortfolioSection>
|
|
|
|
<PortfolioSection id="additional-work" :index="6" :eyebrow="sectionLabel('additional-work').toUpperCase()" :heading="sectionLabel('additional-work').toUpperCase()">
|
|
<ProjectCard v-for="project in content.projects.additional" :key="project.id" :project="project" />
|
|
</PortfolioSection>
|
|
|
|
<PortfolioSection id="toolkit" :index="7" :eyebrow="sectionLabel('toolkit').toUpperCase()" :heading="sectionLabel('toolkit').toUpperCase()">
|
|
<ToolkitGrid :groups="content.toolkit" />
|
|
</PortfolioSection>
|
|
|
|
<PortfolioSection id="contact" :index="8" :eyebrow="sectionLabel('contact').toUpperCase()" :heading="sectionLabel('contact').toUpperCase()">
|
|
<footer>
|
|
<ContactDestination :contact="content.contact" :identity="content.identity" />
|
|
</footer>
|
|
</PortfolioSection>
|
|
</main>
|
|
</template>
|