6b509fe052
- Add og:title, og:description, og:image, og:url, og:type, og:site_name to BaseLayout - Add og:locale (en_GB) + og:locale:alternate (fr_FR, nb_NO) multilingual signals - Add article:published_time, article:section, article:author on all 5 article pages - Add Twitter/X summary_large_image card and canonical link on every page - Generate 13 Jazz Noir branded 1200x630 PNG OG images (satori + resvg-js) - Add scripts/generate-og-images.mjs + Special Elite font for future regeneration - Add public/images/articles/ and public/assets/ which were previously untracked Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
180 lines
6.1 KiB
Plaintext
180 lines
6.1 KiB
Plaintext
---
|
|
import { schoolDossiers } from "../data/site";
|
|
import {
|
|
cvHero,
|
|
cvHighlights,
|
|
cvMandates,
|
|
cvSkillTracks,
|
|
cvSourceNotes,
|
|
cvTimeline,
|
|
} from "../data/cv";
|
|
import BaseLayout from "../layouts/BaseLayout.astro";
|
|
import LocaleCopy from "../components/LocaleCopy.astro";
|
|
|
|
const currentMandates = cvMandates.slice(0, 3);
|
|
const educationSlice = schoolDossiers.slice(0, 5);
|
|
---
|
|
|
|
<BaseLayout
|
|
title="CV | Dave Gilligan"
|
|
description="A custom CV desk covering Dave Gilligan's current ventures, earlier finance and reinsurance work, education route, and the operating themes tying them together."
|
|
ogImage="/images/og/cv.png"
|
|
>
|
|
<main class="cv-page">
|
|
<section class="container cv-hero">
|
|
<div class="cv-hero__copy">
|
|
<span class="eyebrow"><LocaleCopy copy={cvHero.eyebrow} /></span>
|
|
<h1><LocaleCopy copy={cvHero.title} /></h1>
|
|
<p class="cv-hero__lede"><LocaleCopy copy={cvHero.lede} /></p>
|
|
|
|
<div class="cv-hero__actions">
|
|
<a class="button button--dark" href="#career-record">Read the chronology</a>
|
|
<a class="button button--soft" href="/education">Open the education issue</a>
|
|
</div>
|
|
</div>
|
|
|
|
<aside class="panel cv-poster">
|
|
<div class="capsule__kicker">
|
|
<span>Editorial note</span>
|
|
<span>Public record</span>
|
|
</div>
|
|
<p class="cv-poster__eyebrow">Bridgework</p>
|
|
<h2>Finance, systems, language, and advocacy on one line.</h2>
|
|
<p><LocaleCopy copy={cvHero.note} /></p>
|
|
|
|
<div class="cv-poster__grid">
|
|
{cvHighlights.en.map((_, i) => (
|
|
<article>
|
|
<span>Signal</span>
|
|
<p><LocaleCopy copy={{ en: cvHighlights.en[i], fr: cvHighlights.fr[i], nb: cvHighlights.nb[i] }} /></p>
|
|
</article>
|
|
))}
|
|
</div>
|
|
</aside>
|
|
</section>
|
|
|
|
<section class="container cv-current">
|
|
<div class="section-header">
|
|
<div class="section-header__title">Current Mandates</div>
|
|
<div class="section-header__meta">
|
|
The live front of the work: one civic desk, one Norwegian ENK, one AI infrastructure house.
|
|
</div>
|
|
</div>
|
|
|
|
<div class="cv-current__grid">
|
|
{currentMandates.map((mandate) => (
|
|
<article class="panel cv-mandate">
|
|
<div class="cv-mandate__meta">
|
|
<span>{mandate.years}</span>
|
|
<span>{mandate.role}</span>
|
|
<span>{mandate.location}</span>
|
|
</div>
|
|
<h2>{mandate.org}</h2>
|
|
<p class="cv-mandate__summary"><LocaleCopy copy={mandate.summary} /></p>
|
|
<p><LocaleCopy copy={mandate.detail} /></p>
|
|
<a href={mandate.sourceUrl} target="_blank" rel="noreferrer">
|
|
Source: {mandate.sourceLabel}
|
|
</a>
|
|
</article>
|
|
))}
|
|
</div>
|
|
</section>
|
|
|
|
<section id="career-record" class="container cv-record">
|
|
<div class="section-header">
|
|
<div class="section-header__title">Career Record</div>
|
|
<div class="section-header__meta">
|
|
Earlier chapters are kept compact here so the timeline stays readable without losing the hard details.
|
|
</div>
|
|
</div>
|
|
|
|
<div class="cv-record__layout">
|
|
<div class="cv-record__timeline">
|
|
{cvTimeline.map((role) => (
|
|
<article class="cv-role">
|
|
<div class="cv-role__year">{role.years}</div>
|
|
<div class="panel cv-role__body">
|
|
<div class="cv-role__heading">
|
|
<div>
|
|
<p>{role.location}</p>
|
|
<h3>{role.org}</h3>
|
|
</div>
|
|
<strong>{role.role}</strong>
|
|
</div>
|
|
|
|
<p class="cv-role__summary"><LocaleCopy copy={role.summary} /></p>
|
|
|
|
<ul>
|
|
{role.bullets.en.map((_, i) => (
|
|
<li><LocaleCopy copy={{ en: role.bullets.en[i], fr: role.bullets.fr[i], nb: role.bullets.nb[i] }} /></li>
|
|
))}
|
|
</ul>
|
|
</div>
|
|
</article>
|
|
))}
|
|
</div>
|
|
|
|
<aside class="cv-side">
|
|
<article class="panel cv-tracklist">
|
|
<div class="capsule__kicker">
|
|
<span>Operating tracks</span>
|
|
<span>Portable assets</span>
|
|
</div>
|
|
{cvSkillTracks.map((track) => (
|
|
<section>
|
|
<h3><LocaleCopy copy={track.label} /></h3>
|
|
<ul>
|
|
{track.items.map((item) => (
|
|
<li>{item}</li>
|
|
))}
|
|
</ul>
|
|
</section>
|
|
))}
|
|
</article>
|
|
|
|
<article class="panel cv-education-note">
|
|
<div class="capsule__kicker">
|
|
<span>Study route</span>
|
|
<span>Five schools</span>
|
|
</div>
|
|
<p>
|
|
The education record has its own issue because it reads more like a migration map than
|
|
a list of institutions.
|
|
</p>
|
|
|
|
<div class="cv-education-note__list">
|
|
{educationSlice.map((school) => (
|
|
<article>
|
|
<strong>{school.institution}</strong>
|
|
<span>{school.years}</span>
|
|
<p>{school.program}</p>
|
|
</article>
|
|
))}
|
|
</div>
|
|
|
|
<a class="button button--dark" href="/education">Read the school dossier</a>
|
|
</article>
|
|
</aside>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="container source-block">
|
|
<div class="section-header">
|
|
<div class="section-header__title">Source Notes</div>
|
|
<div class="section-header__meta">
|
|
Current roles are paraphrased from official sites. Earlier employment history is grounded in the standing public profile and your supplied LinkedIn text.
|
|
</div>
|
|
</div>
|
|
|
|
<div class="source-list">
|
|
{cvSourceNotes.map((source) => (
|
|
<article>
|
|
<a href={source.url} target="_blank" rel="noreferrer">{source.label}</a>
|
|
<p>{source.note}</p>
|
|
</article>
|
|
))}
|
|
</div>
|
|
</section>
|
|
</main>
|
|
</BaseLayout>
|