diff --git a/.agents/memory/architecture.md b/.agents/memory/architecture.md index 5f3f548..4394f2f 100644 --- a/.agents/memory/architecture.md +++ b/.agents/memory/architecture.md @@ -44,7 +44,7 @@ Maintained alongside the [architecture guide](../../wiki/architecture.md) and [d `src/lib/metro.ts` layers the map on top: `LINES` 1–8 (1 Career, 2 .NET, 3 Azure, 4 Architecture, 5 Side Projects; 6 Languages, 7 Data, 8 Frontend in the Line Guide only), `MAP_LINES`, `GUIDE_LINES`, and `STATIONS` with hand-placed coordinates. `role(company, periodStart)` looks roles up in `EXPERIENCES` and throws at build time when one is missing; the two 2018–2021 T-Systems roles share a station. `src/lib/metro-geometry.ts` holds the build-time path math. -The same data feeds the homepage sections, resume route, terminal command table, metadata descriptions, and `JsonLd.astro` (employer from `CURRENT_ROLE`, address from `PROFILE.address`). The SiteSign subtitle, map lede, station captions, advisory lines, Customer Information copy, and some terminal strings are presentation literals and need review when facts change. The independent public CV PDF has unresolved career differences; the owner deferred reconciliation. +The same data feeds the homepage sections, resume route, terminal command table, metadata descriptions, and `JsonLd.astro` (employer from `CURRENT_ROLE`, address from `PROFILE.address`). The SiteSign subtitle, map lede, station captions, advisory lines, Customer Information copy, and some terminal strings are presentation literals and need review when facts change. The public CV PDF is a checked-in print of `/resume/`; regenerate it when resume data or layout changes. ## UI Features diff --git a/AGENTS.md b/AGENTS.md index 5c564f0..e72c90c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -92,7 +92,7 @@ Data layer - **Pinned + ledger model** — `src/lib/github.ts` takes the pinned repos (Connections departures board and the map's Side Projects line) directly from the profile `pinnedItems` (pin order, owned non-forks, metadata repos excluded) and builds the "Later departures" ledger from the first 100 recently updated public non-fork repos minus pins; there is no pagination. Requests time out after 10 s and Pages lookups run six at a time. - **Pages URL enrichment** — REST `GET /repos/jonathanperis/{repo}/pages` provides `pagesUrl`; standard `https://jonathanperis.github.io//` homepage URLs are fallback Pages links. - **Fallback data** — `FALLBACK` covers absent tokens and failed/unusable GraphQL fetches. Individual Pages lookup failures preserve fetched repo data and standard Pages homepage fallbacks. -- **Shared profile data** — `src/lib/data.ts` powers the portfolio, resume, terminal command table, and JSON-LD (`YEARS_OF_EXPERIENCE`, `PROFILE.address`, and `CURRENT_ROLE` are single sources). Check remaining presentation copy in `src/components/` (sign, status strip, sections) and the station captions in `src/lib/metro.ts` when updating profile facts. The separate public PDF has unresolved differences; career reconciliation is deferred by the owner. +- **Shared profile data** — `src/lib/data.ts` powers the portfolio, resume, terminal command table, and JSON-LD (`YEARS_OF_EXPERIENCE`, `PROFILE.address`, and `CURRENT_ROLE` are single sources). Check remaining presentation copy in `src/components/` (sign, status strip, sections) and the station captions in `src/lib/metro.ts` when updating profile facts. `public/cv_jonathan_peris.pdf` is a checked-in print of `/resume/` (headless Chrome, A4); regenerate it after resume or `data.ts` changes (see [Resume Page](wiki/resume_page.md#downloadable-pdf)). - **Terminal easter egg** — Konami code opens a native `` terminal. Static output is built at build time by `buildCommandTable()` (`terminal-commands.ts`); `runCommand()` (`terminal.ts`) resolves own-key commands only and handles `help`, `about`, `stack`, `contact`, `neofetch`, `git log`, `ls`, `cat availability.txt`, `whoami`, `pwd`, `date`, `sudo hire me`, `echo`, `clear`, `exit`, and `quit`. - **SEO** — `RootLayout.astro` derives canonical, Open Graph URL, and English alternate URL from the page canonical path and configured site. Astro generates `sitemap-index.xml` and `sitemap-0.xml`; robots advertises the generated index. `public/sitemap.xml` preserves the old entry point as a compatibility index, without handwritten route entries or timestamps. - **Documentation status** — `wiki/` is repository Markdown, not a deployed documentation route; GitHub Wiki is disabled. PRODUCT/DESIGN distinguish implemented UI from proposals. Update related docs using the maintenance map above. @@ -125,7 +125,7 @@ jonathanperis.github.io/ │ └── styles/ │ └── globals.css ├── public/ -│ ├── cv_jonathan_peris.pdf +│ ├── cv_jonathan_peris.pdf # Checked-in print of /resume/ │ ├── manifest.json │ ├── robots.txt / sitemap.xml │ └── favicon.svg / apple-touch-icon.png / icon-192.png / icon-512.png diff --git a/DESIGN.md b/DESIGN.md index e3dec21..d97d451 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -93,7 +93,7 @@ Light line colors (4, 6, 7, 8) carry ink numerals for contrast. Line definitions | `CustomerInfo` | Bordered two-pane block: "Now boarding" availability board with Based / Works / Timetable (Resume · PDF), and contact rows (label, mono handle, arrow) that invert to black on hover/focus | | `SiteFooter` | Mono row: diagram note, "Hidden service" Konami hint, copyright | | `Terminal` | "Control room · jonathan.sh" dialog: sign background, ink border, red offset shadow, yellow prompt | -| `/resume/` | Same tokens on paper: black print bar, red section headings and print button, hairline tags; print rules switch to plain black on white | +| `/resume/` | White A4 sheet on paper under a black toolbar; red title line, accent bar, small red caps section heads with hairline rules, skills label/value table, "Title · Company" entries with red company, mono dates/locations, red-dot bullets, and mono "Stack:" lines; prints in color on A4 | ## Interaction rules diff --git a/PRODUCT.md b/PRODUCT.md index 4ebbd15..0a114e2 100644 --- a/PRODUCT.md +++ b/PRODUCT.md @@ -12,7 +12,7 @@ This document combines the current product baseline with the proposals that stil |---|---|---| | Header and status | Station-sign header (name, title, subtitle, line roundels 1–5 that act as map-filter labels, nav: Stations, Connections, Lines, Information, Resume); "Good service" status strip with availability, location, and live Itanhaém clock | Contact entry in the header nav (Information is reachable; no direct contact CTA) | | Career map | Build-time SVG map: Career line through nine stations (ten roles), .NET, Azure, and Architecture lines, Side Projects line of pinned repos; CSS-only line filter; "You are here" terminus | Browser validation of legibility and hit targets at small widths | -| Experience | Station Index: roles newest first with periods, locations, descriptions, served lines, and tags; education as "Depot" | Owner-approved impact statements; PDF reconciliation is deferred | +| Experience | Station Index: roles newest first with periods, locations, descriptions, served lines, and tags; education as "Depot" | Owner-approved impact statements | | Projects | Connections departures board of GitHub profile pins (up to six, pin order) with Source/Live site/Homepage links and stars; "Later departures" `
` ledger | Authored proof per project (`signal`/`proof`); contextual accessible names for repeated links | | Skills | Line Guide: six skill lines with computed interchanges | Problem-oriented capability framing | | Principles | Service Notes: three engineering principles as advisories | Outcome-specific proof | diff --git a/public/cv_jonathan_peris.pdf b/public/cv_jonathan_peris.pdf index d8d75d1..8a60d93 100644 Binary files a/public/cv_jonathan_peris.pdf and b/public/cv_jonathan_peris.pdf differ diff --git a/src/lib/github.ts b/src/lib/github.ts index be1a3dd..cd5d27e 100644 --- a/src/lib/github.ts +++ b/src/lib/github.ts @@ -20,10 +20,11 @@ const PAGES_LOOKUP_CONCURRENCY = 6; const FALLBACK: GitHubRepo[] = [ { title: "cpnucleo", description: "Hands-on .NET 10 project-management learning lab: compare REST/gRPC and EF Core/Dapper with Astro, PostgreSQL, OpenTelemetry, and executable contract tests.", url: "https://github.com/jonathanperis/cpnucleo", lang: "C#", langColor: "#7355dd", stars: 9, homepageUrl: "https://jonathanperis.github.io/cpnucleo/", pagesUrl: "https://jonathanperis.github.io/cpnucleo/", pinned: true }, { title: "super-mango-editor", description: "C11/raylib platformer and visual level editor. Play in the browser via WebAssembly, build TOML worlds, and learn game development with Sandbox School.", url: "https://github.com/jonathanperis/super-mango-editor", lang: "C", langColor: "#555555", stars: 1, homepageUrl: "https://jonathanperis.github.io/super-mango-editor/", pagesUrl: "https://jonathanperis.github.io/super-mango-editor/", pinned: true }, - { title: "rinha2-back-end-dotnet", description: "High-performance backend for the Rinha de Backend challenge — built with ASP.NET 9, PostgreSQL, Nginx, Native AOT, and Docker", url: "https://github.com/jonathanperis/rinha2-back-end-dotnet", lang: "HTML", langColor: "#e34c26", stars: 3, homepageUrl: "https://jonathanperis.github.io/rinha2-back-end-dotnet/", pagesUrl: "https://jonathanperis.github.io/rinha2-back-end-dotnet/", pinned: true }, + { title: "solar-system-simulator", description: "Physics-first orbital mechanics lab in C11 and raylib: native and WebAssembly simulations, reproducible A/B experiments, and a source-backed small-body atlas.", url: "https://github.com/jonathanperis/solar-system-simulator", lang: "C", langColor: "#555555", stars: 0, homepageUrl: "https://jonathanperis.github.io/solar-system-simulator/", pagesUrl: "https://jonathanperis.github.io/solar-system-simulator/", pinned: true }, + { title: "jonlib", description: "Jonlib: graphics and math libraries written in Bend 2, targeting raylib 6.0 parity.", url: "https://github.com/jonathanperis/jonlib", lang: "Python", langColor: "#3572A5", stars: 0, pinned: true }, { title: "blazor-mudblazor-starter", description: "Local-first .NET 10 learning sandbox for Blazor Server and MudBlazor, with hands-on labs for state, forms, APIs, SQLite, authentication, localization, CSV, and Docker.", url: "https://github.com/jonathanperis/blazor-mudblazor-starter", lang: "C#", langColor: "#7355dd", stars: 2, homepageUrl: "https://jonathanperis.github.io/blazor-mudblazor-starter/docs/", pagesUrl: "https://jonathanperis.github.io/blazor-mudblazor-starter/", pinned: true }, { title: "speedy-bird-lynx", description: "Flappy Bird-inspired arcade game built with ReactLynx and TypeScript. Android host, Lynx web preview, playable Canvas demo, and Astro docs; iOS source scaffold included. Every pipe makes it faster.", url: "https://github.com/jonathanperis/speedy-bird-lynx", lang: "TypeScript", langColor: "#3178c6", stars: 1, homepageUrl: "https://jonathanperis.github.io/speedy-bird-lynx/", pagesUrl: "https://jonathanperis.github.io/speedy-bird-lynx/", pinned: true }, - { title: "solar-system-simulator", description: "Physics-first orbital mechanics lab in C11 and raylib: native and WebAssembly simulations, reproducible A/B experiments, and a source-backed small-body atlas.", url: "https://github.com/jonathanperis/solar-system-simulator", lang: "C", langColor: "#555555", stars: 0, homepageUrl: "https://jonathanperis.github.io/solar-system-simulator/", pagesUrl: "https://jonathanperis.github.io/solar-system-simulator/", pinned: true }, + { title: "rinha2-back-end-dotnet", description: "High-performance backend for the Rinha de Backend challenge — built with ASP.NET 9, PostgreSQL, Nginx, Native AOT, and Docker", url: "https://github.com/jonathanperis/rinha2-back-end-dotnet", lang: "HTML", langColor: "#e34c26", stars: 3, homepageUrl: "https://jonathanperis.github.io/rinha2-back-end-dotnet/", pagesUrl: "https://jonathanperis.github.io/rinha2-back-end-dotnet/" }, { title: "rinha4-back-end-c", description: "Rinha de Backend 2026 C implementation with raw HTTP/1, Unix sockets, SCM_RIGHTS load balancing, and exact IVF fraud scoring", url: "https://github.com/jonathanperis/rinha4-back-end-c", lang: "C", langColor: "#555555", stars: 0, homepageUrl: "https://jonathanperis.github.io/rinha4-back-end-c/", pagesUrl: "https://jonathanperis.github.io/rinha4-back-end-c/" }, { title: "rinha2-back-end-k6", description: "Grafana k6 stress tests for the Rinha de Backend challenge — Dockerized with InfluxDB metrics export", url: "https://github.com/jonathanperis/rinha2-back-end-k6", lang: "JavaScript", langColor: "#f1e05a", stars: 0, homepageUrl: "https://jonathanperis.github.io/rinha2-back-end-k6/", pagesUrl: "https://jonathanperis.github.io/rinha2-back-end-k6/" }, ]; diff --git a/src/pages/resume.astro b/src/pages/resume.astro index 51bde92..78575b5 100644 --- a/src/pages/resume.astro +++ b/src/pages/resume.astro @@ -1,14 +1,36 @@ --- import RootLayout from '../layouts/RootLayout.astro'; -import { PROFILE, SKILLS, EDUCATION, EXPERIENCES, YEARS_OF_EXPERIENCE } from '../lib/data'; +import { PROFILE, SKILLS, SKILL_GROUPS, EDUCATION, EXPERIENCES, YEARS_OF_EXPERIENCE } from '../lib/data'; const contactLinks = [ { label: PROFILE.email, href: `mailto:${PROFILE.email}` }, { label: PROFILE.location }, - { label: PROFILE.linkedin, href: `https://www.${PROFILE.linkedin}/` }, { label: PROFILE.github, href: `https://${PROFILE.github}` }, - { label: PROFILE.website, href: `https://${PROFILE.website}/` }, + { label: PROFILE.linkedin, href: `https://www.${PROFILE.linkedin}/` }, ]; + +/** Resume labels for each skill group, in reading order. */ +const SKILL_LABELS: Record<(typeof SKILL_GROUPS)[number]['key'], string> = { + languages: 'Programming', + backend: 'Backend', + architecture: 'Architecture', + cloud: 'Cloud & DevOps', + databases: 'Databases', + frontend: 'Frontend', +}; +const skillRows = (Object.keys(SKILL_LABELS) as Array).map((key) => ({ + label: SKILL_LABELS[key], + values: SKILLS[key].join(', '), +})); + +// The years-of-experience phrase is emphasized in the summary, wherever data.ts puts it. +const yearsPhrase = `${YEARS_OF_EXPERIENCE} years of experience`; +const [summaryBefore, summaryAfter] = PROFILE.summary.includes(yearsPhrase) + ? PROFILE.summary.split(yearsPhrase) + : [PROFILE.summary, undefined]; + +/** One bullet per sentence of the role description. ".NET" never splits: a break needs ". " before a capital. */ +const bullets = (text: string) => text.split(/(?<=\.)\s+(?=[A-Z])/); --- - -
-
- - ← Back to portfolio - - + +
+
+ ← Back to portfolio +
- -
- - -
-

- {PROFILE.name} -

-

- {PROFILE.title} +

+
+

{PROFILE.name}

+

{PROFILE.title}·.NET & Azure

+

+ {contactLinks.map(({ label, href }) => (href ? {label} : {label}))}

-
- {contactLinks.map(({ label, href }) => href - ? {label} - : {label})} -
+
-
- - -
-

- Summary -

-

- {PROFILE.summary} +

+

Summary

+

+ {summaryBefore}{summaryAfter !== undefined && <>{yearsPhrase}{summaryAfter}}

- -
-

- Technical Skills -

-
-
- Languages: - {SKILLS.languages.join(', ')} -
-
- Backend: - {SKILLS.backend.join(', ')} -
-
- Architecture: - {SKILLS.architecture.join(', ')} -
-
- Cloud & DevOps: - {SKILLS.cloud.join(', ')} -
-
- Databases: - {SKILLS.databases.join(', ')} -
-
- Frontend: - {SKILLS.frontend.join(', ')} -
-
+
+

Technical Skills

+
+ {skillRows.map((row) => <>
{row.label}
{row.values}
)} +
- -
-

- Experience -

-
- {EXPERIENCES.map((exp) => ( -
-
-

- {exp.title} ·{exp.company} -

- - {exp.period} - -
-

- {exp.location} -

-

- {exp.description} -

-
- {exp.tags.map((t) => ( - - {t} - - ))} -
+
+

Experience

+ {EXPERIENCES.map((exp) => ( +
+
+

{exp.title}·{exp.company}

+ {exp.period}
- ))} -
+

{exp.location}

+
    + {bullets(exp.description).map((sentence) =>
  • {sentence}
  • )} +
+

Stack: {exp.tags.join(', ')}

+ + ))}
- -
-

- Education -

-
-

- {EDUCATION.degree} — {EDUCATION.field} -

- - {EDUCATION.period} - -
-

- {EDUCATION.institution} -

-

- {EDUCATION.description} -

+
+

Education

+
+
+

{EDUCATION.degree} — {EDUCATION.field}

+ {EDUCATION.period} +
+

{EDUCATION.institution}

+

{EDUCATION.description}

+
@@ -157,23 +104,249 @@ const contactLinks = [ document.querySelector("[data-print]")?.addEventListener("click", () => window.print()); - diff --git a/wiki/architecture.md b/wiki/architecture.md index 06e27e2..4a3ee37 100644 --- a/wiki/architecture.md +++ b/wiki/architecture.md @@ -92,7 +92,7 @@ Runtime (static HTML + CSS + small inline scripts): - **Progressive enhancement** — All content is in the HTML; nothing is hidden until a script runs. The status clock is server-rendered as `UTC−3` and upgraded to live local time by script. The train is shown only under `@supports (offset-path: …)`. - **Build-time failure for map drift** — `role()` in `metro.ts` fails the build if a station references a role that is missing from `data.ts`, instead of drawing a ghost station. - **Self-hosted fonts** — The Astro Fonts API (`fonts` in `astro.config.ts`, `fontProviders.google()`) downloads Overpass and Overpass Mono at build time; `` tags in the layout preload the latin subset. There is no runtime request to Google Fonts. -- **Shared content with explicit exceptions** — [`src/lib/data.ts`](../src/lib/data.ts) supplies profile, address, years of experience, availability, engineering principles, skills and skill groups, education, experience, socials (with display handles), and `CURRENT_ROLE`. Terminal command output and JSON-LD are derived from it. The SiteSign subtitle, map lede, station captions in `metro.ts`, Service Notes advisory lines, Customer Information copy, and some terminal strings remain presentation literals; review them when changing facts. The separate CV PDF is not generated from this data. +- **Shared content with explicit exceptions** — [`src/lib/data.ts`](../src/lib/data.ts) supplies profile, address, years of experience, availability, engineering principles, skills and skill groups, education, experience, socials (with display handles), and `CURRENT_ROLE`. Terminal command output and JSON-LD are derived from it. The SiteSign subtitle, map lede, station captions in `metro.ts`, Service Notes advisory lines, Customer Information copy, and some terminal strings remain presentation literals; review them when changing facts. The checked-in CV PDF is a print of `/resume/`, so it reflects this data only as of its last regeneration. - **Dynamic-but-build-time projects** — `src/lib/github.ts` fetches GitHub data during `bun run build`; no GitHub API calls happen from the deployed browser page. - **Layered fallback** — Missing tokens and failed/unusable GraphQL fetches use `FALLBACK`. Failed Pages enrichment preserves the fetched repository and its standard Pages homepage fallback. Authenticated builds depend on live GitHub data; see [Dynamic Projects](dynamic_projects.md) for limits and ordering. - **Analytics opt-in by environment** — GA4 scripts are emitted only when `PUBLIC_GA_ID` is present; tracked elements declare `data-track-event`/`data-track-label` attributes. diff --git a/wiki/index.md b/wiki/index.md index eb1c5e9..f3082a9 100644 --- a/wiki/index.md +++ b/wiki/index.md @@ -51,7 +51,7 @@ Update the relevant guide in the same change as its source. Link to configuratio | CI, release, dependency updates | [Workflows](../.github/workflows/), [`renovate.json`](../renovate.json), shared Renovate preset | README, Deployment, AGENTS | | Rendering or source-tree changes | [`astro.config.ts`](../astro.config.ts), [`src/`](../src/) | Architecture, Project Structure, [agent memory](../.agents/memory/architecture.md) | | Project discovery | [`src/lib/github.ts`](../src/lib/github.ts), [`Connections.astro`](../src/components/sections/Connections.astro), [`MetroMap.astro`](../src/components/sections/MetroMap.astro) (Side Projects line) | Dynamic Projects, Architecture, agent memory | -| Career/profile content | [`src/lib/data.ts`](../src/lib/data.ts), [`src/lib/metro.ts`](../src/lib/metro.ts) stations and captions, presentation literals, independent PDF | Resume Page, Easter Egg; inspect section components, terminal commands, metadata, JSON-LD, and PDF for related changes | +| Career/profile content | [`src/lib/data.ts`](../src/lib/data.ts), [`src/lib/metro.ts`](../src/lib/metro.ts) stations and captions, presentation literals, checked-in resume PDF | Resume Page, Easter Egg; inspect section components, terminal commands, metadata, JSON-LD; regenerate the PDF from `/resume/` | | Map lines, stations, or geometry | [`src/lib/metro.ts`](../src/lib/metro.ts), [`src/lib/metro-geometry.ts`](../src/lib/metro-geometry.ts), [`MetroMap.astro`](../src/components/sections/MetroMap.astro), line tokens in [`globals.css`](../src/styles/globals.css) | Architecture, DESIGN, agent memory | | Metadata, sitemap, analytics | Layout, Astro components, sitemap integration, [`public/`](../public/) | SEO & Analytics | | UI or experiment status | [`src/components/`](../src/components/), [`src/lib/terminal-commands.ts`](../src/lib/terminal-commands.ts), [`src/lib/terminal.ts`](../src/lib/terminal.ts), and global CSS | PRODUCT, DESIGN, Easter Egg | diff --git a/wiki/project_structure.md b/wiki/project_structure.md index 0d854a6..d5f4389 100644 --- a/wiki/project_structure.md +++ b/wiki/project_structure.md @@ -37,7 +37,7 @@ jonathanperis.github.io/ │ └── styles/ │ └── globals.css # Tailwind import, design tokens, route bullets, terminal, line filter ├── public/ -│ ├── cv_jonathan_peris.pdf # Independent CV asset; reconciliation deferred +│ ├── cv_jonathan_peris.pdf # Print of /resume/ (regenerated manually) │ ├── manifest.json # Web app metadata and icons │ ├── robots.txt # Advertises generated sitemap-index.xml │ ├── sitemap.xml # Compatibility index pointing to generated sitemap-0.xml diff --git a/wiki/resume_page.md b/wiki/resume_page.md index 0ba69ec..2962593 100644 --- a/wiki/resume_page.md +++ b/wiki/resume_page.md @@ -4,49 +4,50 @@ ## Overview -The `/resume/` route renders a **print-optimized resume** from shared portfolio data. The page can be printed or saved to PDF from the browser; that flow does not read or regenerate the separate checked-in PDF asset. +The `/resume/` route renders a **print-optimized resume** from shared portfolio data. The page can be printed or saved to PDF from the browser, and the downloadable `/cv_jonathan_peris.pdf` is a checked-in print of this route. ## How It Works -- `src/pages/resume.astro` imports `PROFILE`, `SKILLS`, `EDUCATION`, `EXPERIENCES`, and `YEARS_OF_EXPERIENCE` from `src/lib/data.ts`. +- `src/pages/resume.astro` imports `PROFILE`, `SKILLS`, `SKILL_GROUPS` (for its key type), `EDUCATION`, `EXPERIENCES`, and `YEARS_OF_EXPERIENCE` from `src/lib/data.ts`. - The page is wrapped in `RootLayout` with resume-specific title, description, and canonical path (`/resume/`). Canonical, Open Graph, and English alternate URLs all identify this route. -- It renders header, summary, technical skills, all experience entries, and education inside a `
` landmark. Email, LinkedIn, GitHub, and website in the header are real links (`mailto:`/`https://`); location is plain text. +- It renders header, summary, technical skills, all experience entries, and education inside a `
` sheet. Email, GitHub, and LinkedIn in the header contact row are real links (`mailto:`/`https://`); location is plain text. - The "Print / Save as PDF" button (`data-print`) is wired by a small Astro-processed page script, inlined in the HTML (no inline `onclick`), that calls `window.print()`, so the browser print dialog can save as PDF. It also carries `data-track-event="cta_click"` / `data-track-label="resume_print"` for GA4. ## Sections | Section | Data Source | |---|---| -| Header | `PROFILE.name`, `PROFILE.email`, `PROFILE.location`, `PROFILE.linkedin`, `PROFILE.github`, `PROFILE.website` | -| Summary | `PROFILE.summary` | -| Technical Skills | `SKILLS` (languages, backend, architecture, cloud, databases, frontend) | -| Experience | `EXPERIENCES[]` from `src/lib/data.ts` | -| Education | `EDUCATION` | +| Header | `PROFILE.name`; red title line `PROFILE.title` + " · .NET & Azure" (presentation copy); mono contact row with `PROFILE.email`, `PROFILE.location`, `PROFILE.github`, `PROFILE.linkedin`; short red accent bar | +| Summary | `PROFILE.summary`, with the `${YEARS_OF_EXPERIENCE} years of experience` phrase set in bold when present | +| Technical Skills | Label/value table from `SKILLS`; labels come from `SKILL_LABELS` in the page (Programming, Backend, Architecture, Cloud & DevOps, Databases, Frontend) | +| Experience | `EXPERIENCES[]`: "Title · Company" (company in red), mono period on the right, mono location, one red-dot bullet per sentence of `description` (split on ". " before a capital, so ".NET" never splits), and a mono "Stack:" line from `tags` | +| Education | `EDUCATION`: "Degree — Field", mono period, mono institution, description | ## Print Styling Print rules live in [`src/pages/resume.astro`](../src/pages/resume.astro), alongside the resume markup. The page has dual styles: -- **Screen:** Light paper/ink theme matching the portfolio — black (`sign`) print bar, red (line 1) section headings and print button, ink text, hairline-bordered tags. -- **Print:** White background, black text, compact A4 margins, and `print:break-inside-avoid` on experience entries. - -```css -@media print { - body { background: white !important; color: black !important; } - .resume-page { padding: 0.4in 0.5in !important; } - @page { size: A4; margin: 0; } -} -``` +- **Screen:** an A4-width white sheet on the site's paper background, under a black (`sign`) toolbar with "Back to portfolio" and a red "Print / Save as PDF" button. Section heads are small red caps followed by a hairline rule. The mobile layout (`@media screen and (max-width: 760px)`) stacks the skills table and moves dates under titles; it is screen-only because an A4 print area is also narrower than 760px. +- **Print:** the sheet becomes the A4 page (`@page { size: A4; margin: 14mm 17mm }`), in full color (`print-color-adjust: exact`) with the red (line 1) accents kept, at 9.6pt so the current data fits on two pages. Entry headers, locations, and section heads avoid page breaks after them; bullets avoid breaking inside. ## Navigation - The SiteSign navigation (`Resume`) and the Customer Information "Timetable" row (`Resume`) link to `/resume/`, the HTML resume. - The resume page has a "Back to portfolio" link to `/`. -## Independent PDF Asset +## Downloadable PDF + +[`public/cv_jonathan_peris.pdf`](../public/cv_jonathan_peris.pdf), served at `/cv_jonathan_peris.pdf`, is a checked-in A4 print of `/resume/`. Since 2026-10-07 it replaces the older, separately written Google Docs CV (still in git history). The Customer Information "Timetable" row links to it as `PDF` (`cta_click` / `info_pdf`). -[`public/cv_jonathan_peris.pdf`](../public/cv_jonathan_peris.pdf) remains a separately maintained public asset at `/cv_jonathan_peris.pdf`. It is not generated from `data.ts`. The Customer Information "Timetable" row links to it as `PDF` (`cta_click` / `info_pdf`); the `/resume/` route and its print button do not use it. +The build does not regenerate it. After changing `data.ts` resume content or `resume.astro`, rebuild, serve `out/`, and print the route with headless Chrome (real Node first on `PATH`, as for any build): + +```sh +bun run build +(cd out && python3 -m http.server 4599) & +"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" --headless=new --no-pdf-header-footer \ + --virtual-time-budget=6000 --print-to-pdf=public/cv_jonathan_peris.pdf http://localhost:4599/resume/ +``` -The PDF and shared data disagree on the 2023 T-Systems project/client and the XP role description. The owner deferred career reconciliation on 2026-09-17. Confirm authoritative facts before changing either representation; do not treat the PDF and web resume as synchronized. +Run these from the repository root (adjust the Chrome path on other systems) and stop the server afterwards. Check that the PDF still fits on two pages. When updating approved profile facts, also inspect presentation literals in `src/components/` (notably `SiteSign.astro`, the `MetroMap.astro` lede, and `CustomerInfo.astro`), station captions in `src/lib/metro.ts`, `src/lib/terminal-commands.ts`, and `resume.astro`. Metadata descriptions and JSON-LD derive years, employer, and address from `data.ts`, but shared data does not cover every sign, caption, or terminal string. Adding, removing, or renaming a role in `EXPERIENCES` requires a matching `STATIONS` update in `metro.ts`: a new role appears on the map and in the Station Index only once a station references it, and a station whose role is missing fails the build.