diff --git a/docs/superpowers/plans/2026-09-04-portfolio-content-seo-media-implementation.md b/docs/superpowers/plans/2026-09-04-portfolio-content-seo-media-implementation.md
new file mode 100644
index 0000000..d41f16b
--- /dev/null
+++ b/docs/superpowers/plans/2026-09-04-portfolio-content-seo-media-implementation.md
@@ -0,0 +1,1174 @@
+# Portfolio Content, SEO, and Project Media Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** Publish an evidence-led, privacy-safe portfolio that positions Luca Pîslaru equally as a System Administrator and Full-Stack Developer and integrates approved project media without changing the eight-section journey.
+
+**Architecture:** Keep the existing pre-rendered Vue/Three.js single-page architecture and canonical section contract. Extend the typed content boundary with availability, a featured infrastructure operation, and project media layouts; continue routing every binary through the existing private-source, OCR, hash, approval, manifest, and exact-closure pipeline. Metadata remains derived from the same public content and site configuration used to render the page.
+
+**Tech Stack:** Vue 3, TypeScript, Zod, Vite SSR/prerendering, Vitest, Playwright, Sharp, existing asset privacy/publication scripts
+
+**Spec:** `docs/superpowers/specs/2026-09-04-portfolio-content-seo-media-design.md`
+
+## Global Constraints
+
+- Keep the canonical sections and exact order: `start`, `profile`, `experience`, `infrastructure`, `selected-builds`, `additional-work`, `toolkit`, `contact`.
+- Do not alter the Three.js world, route-sign lifecycle, soundtrack, or section-to-audio mood mapping.
+- Public identity is exactly `Luca Pîslaru — System Administrator & Full-Stack Developer`.
+- Public current-role organization is `Trupa lui Răzuș`; never publish the legal PFA name.
+- Public current-role title is `Full-Stack Developer & Hosting Administrator (Collaborator)`; do not use DevOps.
+- Public server OS terminology is `Linux`; never publish its distribution.
+- Never publish a portrait, phone number, street address, downloadable CV, private domain, private product name, private repository link, server configuration, or real/private health information.
+- The medication tracker remains `Private self-hosted medication tracker`, has no URL, and is not described as production-ready or fully secure.
+- Footy Tracker is a two-person university collaboration. Credit Luca's lead data/ML contribution and the collaborator's initial pipeline contribution accurately.
+- Preserve measured Footy results as `mAP@0.5` values; do not convert them into ordinary percentages.
+- Do not claim a numerical performance improvement for Trupa lui Răzuș without reproducible evidence.
+- Project media is optional at runtime but required for this feature's final acceptance. Unsafe or unapproved media is omitted rather than replaced with a broken placeholder.
+- Supplied private screenshots are never copied unchanged into the public asset set.
+- Keep semantic no-JavaScript content, keyboard access, reduced motion, forced colors, intrinsic media dimensions, and contextual text alternatives.
+- Preserve the current soundtrack binaries, hashes, manifest records, transfer budgets, and playback contract.
+- Run one broad end-of-phase review and at most one medium follow-up if high-impact issues remain.
+
+---
+
+## File Structure
+
+### Content and contracts
+
+- `content/public/portfolio-content.json`: exact approved public copy and public asset references.
+- `content/public/asset-recipes.json`: generic recipes for only the project/social assets selected for publication.
+- `content/public/asset-manifest.json`: exact union of existing soundtrack records and approved project/social records.
+- `docs/content-approval-checklist.md`: human-readable approval projection matching the public JSON.
+- `src/content/types.ts`: typed availability, location, featured operation, contact introduction, and project media layout.
+- `src/content/schemas.ts`: strict validation, generic-private-title restrictions, fixed section order, and exact asset closure.
+
+### Presentation
+
+- `src/components/HeroJourney.vue`: visible availability and email-first action.
+- `src/components/ContactDestination.vue`: content-driven opportunity statement and `rel=me` identity links.
+- `src/components/InfrastructureDiagram.vue`: existing trust-flow diagram plus featured migration evidence.
+- `src/components/OperationsProof.vue`: semantic featured-operation block inside Infrastructure.
+- `src/components/ProjectMediaGallery.vue`: layout-only wrapper for single, portrait-strip, and chart-pair presentations.
+- `src/components/ProjectCaseStudy.vue`: primary/supporting project copy plus gallery.
+- `src/components/ProjectCard.vue`: concise supporting work plus optional single media item.
+- `src/styles/components.css`, `src/styles/layout.css`, `src/styles/print.css`: responsive integration matching the existing night-drive language.
+
+### Metadata
+
+- `src/content/metadata.ts`: Bucharest city-level structured data and Twitter-card metadata.
+- `scripts/prerender.mjs`: render Twitter tags and approved Open Graph image.
+- `scripts/verify-artifacts.mjs`: verify the expanded metadata and social identity contract.
+
+### Media preparation
+
+- `scripts/generate-social-preview.mjs`: deterministic 1200×630 typographic night-drive social image generator.
+- `scripts/prepare-assets.mjs`: retain and verify existing soundtrack assets while publishing approved project candidates.
+- `scripts/lib/merge-assets.mjs`: pure collision-safe merge of retained and newly approved asset records.
+- `inputs/prepared-assets/`: tracked, hash-bound public derivatives only.
+- `content/private/`: ignored mapping, sanitized sources, reports, and exact approval register.
+
+### Verification
+
+- `tests/fixtures/portfolio-content.valid.json`: complete synthetic content fixture.
+- `tests/unit/content-schemas.test.ts`: content and asset boundary tests.
+- `tests/integration/public-content.test.ts`: exact public facts and prohibited-content checks.
+- `tests/unit/AppShell.test.ts`: pre-rendered section, hero, contact, priority, and infrastructure evidence.
+- `tests/unit/ProjectMedia.test.ts`: gallery semantics and media layout behavior.
+- `tests/unit/metadata.test.ts`: JSON-LD, Open Graph, Twitter, and CSP hash.
+- `tests/integration/prepare-assets.test.ts`: additive asset publication retaining soundtrack records/files.
+- `tests/integration/verify-artifacts.test.ts`: compiled metadata and semantic content.
+- `tests/browser/semantic.spec.ts`: live semantic links, attribution, contact, and fixed section contract.
+- `tests/browser/media-policy.spec.ts`: approved still media and no automatic video behavior.
+- `tests/browser/media-failure.spec.ts`: media failure degrades locally without removing text.
+- `tests/browser/responsive-layout.spec.ts`, `tests/browser/accessibility-reflow.spec.ts`: portrait-strip and wide-media reflow.
+- `tests/browser/visual-regression.spec.ts`: approved desktop/mobile/static visual baselines.
+
+---
+
+### Task 1: Lock the expanded public content contract and exact copy
+
+**Files:**
+- Modify: `src/content/types.ts`
+- Modify: `src/content/schemas.ts`
+- Modify: `tests/fixtures/portfolio-content.valid.json`
+- Modify: `tests/unit/content-schemas.test.ts`
+- Modify: `content/public/portfolio-content.json`
+- Modify: `tests/integration/public-content.test.ts`
+- Modify: `docs/content-approval-checklist.md`
+
+**Interfaces:**
+- Consumes: the fixed `SectionId` union, existing strict Zod boundary, and approved specification copy.
+- Produces: `PortfolioContent` with typed `identity.location`, `identity.availability`, `infrastructure.featuredOperation`, `contact.introduction`, and optional `PublicProject.mediaLayout`.
+
+- [ ] **Step 1: Add failing schema tests for the new required evidence fields**
+
+Add explicit tests to `tests/unit/content-schemas.test.ts`:
+
+```ts
+it('requires public availability, city-level location, featured operations evidence, and contact copy', () => {
+ const content = cloneContent() as any;
+ expect(content.identity.location).toEqual({ city: 'Bucharest', country: 'Romania', countryCode: 'RO' });
+ expect(content.identity.availability).toMatch(/Bucharest.*remote/iu);
+ expect(content.infrastructure.featuredOperation.actions.length).toBeGreaterThanOrEqual(3);
+ expect(content.contact.introduction).toMatch(/System Administrator.*Full-Stack Developer/iu);
+
+ delete content.identity.availability;
+ expect(() => parsePortfolioContent(content)).toThrow();
+});
+
+it('accepts only the approved project media layouts', () => {
+ const content = cloneContent() as any;
+ content.projects.selected[0].mediaLayout = 'portrait-strip';
+ expect(() => parsePortfolioContent(content)).not.toThrow();
+ content.projects.selected[0].mediaLayout = 'carousel';
+ expect(() => parsePortfolioContent(content)).toThrow(/mediaLayout|unrecognized/iu);
+});
+```
+
+Extend `MutableContentFixture` so the test remains typed instead of relying on `any` in the final version.
+
+- [ ] **Step 2: Add failing public-evidence assertions**
+
+Replace the generic assertions in `tests/integration/public-content.test.ts` with exact public facts:
+
+```ts
+expect(content.identity).toMatchObject({
+ name: 'Luca Pîslaru',
+ role: 'System Administrator & Full-Stack Developer',
+ location: { city: 'Bucharest', country: 'Romania', countryCode: 'RO' },
+});
+expect(content.experience[0]).toMatchObject({
+ organization: 'Trupa lui Răzuș',
+ role: 'Full-Stack Developer & Hosting Administrator (Collaborator)',
+});
+expect(content.infrastructure.featuredOperation.outcome).toMatch(/without data loss.*roughly a year/iu);
+expect(content.projects.selected.map(({ id }) => id)).toEqual([
+ 'private-medication-tracker',
+ 'footy-tracker',
+ 'opinion-dynamics-simulation',
+]);
+expect(content.projects.selected[1]?.contribution).toMatch(/collaborator/iu);
+expect(content.projects.selected[1]?.outcome).toMatch(/0\.942 mAP@0\.5.*0\.198/iu);
+```
+
+Expand the prohibited-surface assertion without adding the real private tracker name to a tracked file:
+
+```ts
+expect(JSON.stringify(content)).not.toMatch(
+ /Arch Linux|Mihai Razus PFA|DevOps collaborator|CV download|heatmap|fully secure|production-ready|expert|percent/iu,
+);
+```
+
+- [ ] **Step 3: Run the focused tests and confirm the contract is red**
+
+Run:
+
+```bash
+npm run test:unit -- tests/unit/content-schemas.test.ts tests/integration/public-content.test.ts
+```
+
+Expected: failures identify the missing new fields and stale generic copy.
+
+- [ ] **Step 4: Extend the TypeScript content types**
+
+Add these exact shapes to `src/content/types.ts`:
+
+```ts
+export type ProjectMediaLayout = 'single' | 'portrait-strip' | 'chart-pair';
+
+export interface FeaturedOperation {
+ title: string;
+ context: string;
+ actions: readonly string[];
+ outcome: string;
+}
+
+export interface InfrastructureCaseStudy {
+ id: string;
+ title: string;
+ summary: string;
+ bands: readonly InfrastructureEvidenceBand[];
+ featuredOperation: FeaturedOperation;
+}
+```
+
+Extend the existing types exactly:
+
+```ts
+// PublicProject
+mediaLayout?: ProjectMediaLayout;
+
+// PortfolioContent.identity
+location: { city: string; country: string; countryCode: string };
+availability: string;
+
+// ContactContent
+introduction: string;
+```
+
+- [ ] **Step 5: Extend the strict Zod schema**
+
+In `src/content/schemas.ts`, add a strict location object, required availability and contact introduction, required featured operation with `actions: z.array(visibleString).min(3).max(6)`, and `mediaLayout: z.enum(['single', 'portrait-strip', 'chart-pair']).optional()`.
+
+Add a refinement that rejects `mediaLayout` when a project has no `media`, while continuing to allow the complete text-first public projection to omit both fields:
+
+```ts
+if (project.mediaLayout !== undefined && (project.media === undefined || project.media.length === 0)) {
+ context.addIssue({
+ code: 'custom',
+ message: 'Project mediaLayout requires at least one media presentation.',
+ path: ['mediaLayout'],
+ });
+}
+```
+
+Keep selected projects at length three and keep the exact-one-experimental refinement.
+
+- [ ] **Step 6: Replace the public copy with the exact specification projection**
+
+Update `content/public/portfolio-content.json` from sections 4.1 through 4.11 of the specification. Use ASCII hyphens in rendered date ranges and sentences so the existing visible-copy punctuation contract remains stable. Preserve the four soundtrack regions byte-for-byte.
+
+During this task, keep every project `assetIds` empty, omit every `media` and `mediaLayout`, and omit `metadata.socialPreviewAssetId`. This produces a fully testable text-first checkpoint before any binary approval.
+
+The metadata fields are exactly:
+
+```json
+{
+ "title": "Luca Pîslaru — System Administrator & Full-Stack Developer",
+ "description": "Portfolio of Luca Pîslaru, a Bucharest-based System Administrator and Full-Stack Developer working across Linux infrastructure and end-to-end software delivery."
+}
+```
+
+- [ ] **Step 7: Update the synthetic fixture and approval checklist**
+
+Add the required new fields to `tests/fixtures/portfolio-content.valid.json` using synthetic content and existing synthetic asset IDs. Update `docs/content-approval-checklist.md` so it records:
+
+- Trupa lui Răzuș and the corrected role title;
+- Bucharest and remote availability;
+- the Nextcloud migration outcome;
+- medication-tracker private/active/not-production-ready wording;
+- Footy collaboration and measured results;
+- project/social media as pending approval at this checkpoint.
+
+Do not place private source paths, names, or values in the checklist.
+
+- [ ] **Step 8: Run the contract tests and type checker**
+
+Run:
+
+```bash
+npm run test:unit -- tests/unit/content-schemas.test.ts tests/integration/public-content.test.ts
+npm run typecheck
+git diff --check
+```
+
+Expected: all commands pass; the public JSON validates with soundtrack-only exact asset closure.
+
+- [ ] **Step 9: Commit the typed content projection**
+
+```bash
+git add src/content/types.ts src/content/schemas.ts content/public/portfolio-content.json docs/content-approval-checklist.md tests/fixtures/portfolio-content.valid.json tests/unit/content-schemas.test.ts tests/integration/public-content.test.ts
+git commit -m "content: publish evidence-led portfolio copy"
+```
+
+Expected: one content-contract commit with no project binaries, private files, Three.js changes, or audio changes.
+
+---
+
+### Task 2: Render availability and the featured operations case study
+
+**Files:**
+- Modify: `src/components/HeroJourney.vue`
+- Modify: `src/components/ContactDestination.vue`
+- Create: `src/components/OperationsProof.vue`
+- Modify: `src/components/InfrastructureDiagram.vue`
+- Modify: `src/styles/components.css`
+- Modify: `src/styles/layout.css`
+- Modify: `src/styles/print.css`
+- Modify: `tests/unit/AppShell.test.ts`
+- Modify: `tests/browser/semantic.spec.ts`
+
+**Interfaces:**
+- Consumes: `identity.availability`, `identity.location`, `contact.introduction`, and `infrastructure.featuredOperation` from Task 1.
+- Produces: complete server-rendered hero/contact/operations evidence while preserving all eight section IDs and layouts.
+
+- [ ] **Step 1: Write the failing server-rendered hierarchy assertions**
+
+Update `tests/unit/AppShell.test.ts` so the hero and contact expectations become:
+
+```ts
+expect([...hero.children].map((element) => element.textContent?.trim())).toEqual([
+ 'Luca Pîslaru',
+ 'System Administrator & Full-Stack Developer',
+ validInputs.content.identity.valueProposition,
+ validInputs.content.identity.availability,
+ 'Email Luca',
+ 'Explore work',
+]);
+expect(hero.querySelector('a[href="mailto:hello@example.test"]')?.textContent).toBe('Email Luca');
+expect(document.querySelector('[data-featured-operation]')?.textContent).toContain('Migrating a stateful Nextcloud service');
+expect(document.querySelector('#contact')?.textContent).toContain('System Administrator');
+```
+
+Keep the existing assertions for one H1, seven H2 elements, eight canonical sections, and primary/supporting project priority.
+
+- [ ] **Step 2: Write the failing live semantic-link assertions**
+
+In `tests/browser/semantic.spec.ts`, require one hero `mailto:` link and one contact `mailto:` link with the same address, and require all social identity links to contain `me`, `noopener`, and `noreferrer` in their `rel` token set.
+
+```ts
+await expect(page.getByRole('link', { name: 'Email Luca' })).toHaveCount(2);
+await expect(page.locator('[data-featured-operation]')).toContainText('without data loss');
+```
+
+- [ ] **Step 3: Run the focused tests and confirm they fail**
+
+Run:
+
+```bash
+npm run test:unit -- tests/unit/AppShell.test.ts
+npx playwright test tests/browser/semantic.spec.ts --project=desktop-chromium
+```
+
+Expected: hero, contact, operation, and `rel=me` assertions fail against the old components.
+
+- [ ] **Step 4: Render the email-first hero**
+
+In `HeroJourney.vue`, keep the H1 and role as the first two visible elements, then render:
+
+```vue
+
{{ identity.valueProposition }}
+{{ identity.availability }}
+
+```
+
+Do not hard-code the address in the final component. Add an `email` prop sourced from `content.contact.email` in `AppShell.vue`, and form the `mailto:` URL from that prop.
+
+- [ ] **Step 5: Make contact copy content-driven and mark identity links**
+
+Replace the hard-coded contact sentence with `contact.introduction`, change the primary link label to `Email Luca`, and set social links to:
+
+```vue
+rel="me noopener noreferrer"
+```
+
+Project links remain `noopener noreferrer` without `me`.
+
+- [ ] **Step 6: Add the semantic featured-operation component**
+
+Create `OperationsProof.vue`:
+
+```vue
+
+
+
+
+ {{ operation.title }}
+ {{ operation.context }}
+
+ - {{ action }}
+
+ Outcome: {{ operation.outcome }}
+
+
+```
+
+Render it after the infrastructure text equivalent:
+
+```vue
+
+```
+
+The stable caller-provided ID keeps the accessible relationship independent of punctuation or future copy changes.
+
+- [ ] **Step 7: Style the new evidence without changing the journey geometry contract**
+
+Add restrained styles using existing variables only:
+
+- availability at `var(--step--1)` with a readable maximum line length;
+- two hero actions in a wrapping row with the email action using the current filled cyan treatment;
+- operation block separated by one strong rule, a two-column context/actions layout at wide widths, and one column below 48rem;
+- no new fixed heights, absolute positioning, animation, or viewport-dependent content hiding;
+- print layout shows the operation in document order.
+
+- [ ] **Step 8: Run semantic, keyboard, reflow, and static checks**
+
+Run:
+
+```bash
+npm run test:unit -- tests/unit/AppShell.test.ts tests/unit/components.test.ts
+npx playwright test tests/browser/semantic.spec.ts tests/browser/keyboard.spec.ts tests/browser/accessibility-reflow.spec.ts tests/browser/no-javascript.spec.ts --project=desktop-chromium
+npm run typecheck
+git diff --check
+```
+
+Expected: all checks pass and the eight-section sequence is unchanged.
+
+- [ ] **Step 9: Commit the new semantic evidence blocks**
+
+```bash
+git add src/components/HeroJourney.vue src/components/ContactDestination.vue src/components/OperationsProof.vue src/components/InfrastructureDiagram.vue src/components/AppShell.vue src/styles/components.css src/styles/layout.css src/styles/print.css tests/unit/AppShell.test.ts tests/browser/semantic.spec.ts
+git commit -m "feat: surface availability and operations evidence"
+```
+
+---
+
+### Task 3: Add restrained project-media layouts
+
+**Files:**
+- Create: `src/components/ProjectMediaGallery.vue`
+- Modify: `src/components/ProjectCaseStudy.vue`
+- Modify: `src/components/ProjectCard.vue`
+- Modify: `src/styles/components.css`
+- Modify: `src/styles/layout.css`
+- Modify: `src/styles/print.css`
+- Modify: `tests/unit/ProjectMedia.test.ts`
+- Modify: `tests/browser/responsive-layout.spec.ts`
+- Modify: `tests/browser/accessibility-reflow.spec.ts`
+
+**Interfaces:**
+- Consumes: `PublicProject.media`, `PublicProject.mediaLayout`, existing `ProjectMedia`, and existing `allowAutomaticVideo` policy.
+- Produces: one semantic gallery wrapper whose layout changes do not change media loading, accessibility, or asset resolution.
+
+- [ ] **Step 1: Write failing gallery tests**
+
+Add tests to `tests/unit/ProjectMedia.test.ts` that mount the gallery with three image presentations and assert:
+
+```ts
+expect(wrapper.attributes('data-media-layout')).toBe('portrait-strip');
+expect(wrapper.findAll('figure')).toHaveLength(3);
+expect(wrapper.findAll('img').map((image) => image.attributes('loading'))).toEqual(['lazy', 'lazy', 'lazy']);
+expect(wrapper.text()).toContain('Fictional demo profile');
+```
+
+Add one case proving `single` and `chart-pair` render the same `ProjectMedia` semantics and a case proving an empty list renders no gallery container.
+
+- [ ] **Step 2: Run the unit test and confirm it fails**
+
+Run:
+
+```bash
+npm run test:unit -- tests/unit/ProjectMedia.test.ts
+```
+
+Expected: failure because `ProjectMediaGallery.vue` does not exist.
+
+- [ ] **Step 3: Implement the layout-only gallery**
+
+Create `ProjectMediaGallery.vue`:
+
+```vue
+
+
+
+
+
+```
+
+Replace the direct `ProjectMedia` loops in `ProjectCaseStudy.vue` and `ProjectCard.vue` with this wrapper. Add `:data-media-layout="project.mediaLayout ?? 'none'"` to the containing article for responsive styling.
+
+- [ ] **Step 4: Implement the responsive visual treatment**
+
+Use these layout rules in `src/styles/components.css`:
+
+- `single`: one full-width figure with the existing border and background;
+- `portrait-strip`: three equal portrait columns, a small negative inline overlap no greater than `0.75rem`, alternating vertical offsets no greater than `1rem`, and no rotation;
+- `chart-pair`: two equal columns with aligned captions and one column below 48rem;
+- every gallery has `isolation: isolate`, visible focus/controls, and no hidden overflow that clips captions;
+- tracker primary card may expand to `min(72rem, 88%)` on wide screens; Footy remains right-aligned and may expand to `min(68rem, 82%)`; the supporting research card remains visually quieter;
+- below 48rem every card is 100% wide and every gallery becomes one column or a keyboard/touch-scrollable portrait strip with scroll snapping disabled under reduced motion;
+- no parallax, autoplay, hover-only information, fake device chrome, or CSS transforms that reduce text/image legibility.
+
+Update `print.css` so images fit the page, captions follow their figures, and no media overlaps.
+
+- [ ] **Step 5: Add browser reflow assertions**
+
+In both responsive/reflow specs, use a synthetic manifest/content fixture or the final approved media after Task 5. At 320 CSS pixels require:
+
+```ts
+const gallery = page.locator('[data-media-layout="portrait-strip"]');
+await expect(gallery).toHaveCount(1);
+expect(await gallery.evaluate((node) => node.scrollWidth <= node.clientWidth || getComputedStyle(node).overflowX === 'auto')).toBe(true);
+```
+
+Also assert no document-level horizontal overflow and that all figures remain in DOM order.
+
+- [ ] **Step 6: Run focused layout verification**
+
+Run:
+
+```bash
+npm run test:unit -- tests/unit/ProjectMedia.test.ts
+npx playwright test tests/browser/responsive-layout.spec.ts tests/browser/accessibility-reflow.spec.ts --project=desktop-chromium --project=mobile-chromium
+npm run typecheck
+git diff --check
+```
+
+Expected: all checks pass without requiring any production project binary.
+
+- [ ] **Step 7: Commit the media-layout component**
+
+```bash
+git add src/components/ProjectMediaGallery.vue src/components/ProjectCaseStudy.vue src/components/ProjectCard.vue src/styles/components.css src/styles/layout.css src/styles/print.css tests/unit/ProjectMedia.test.ts tests/browser/responsive-layout.spec.ts tests/browser/accessibility-reflow.spec.ts
+git commit -m "feat: add evidence-led project media layouts"
+```
+
+---
+
+### Task 4: Make approved project publication additive to the soundtrack closure
+
+**Files:**
+- Create: `scripts/lib/merge-assets.mjs`
+- Modify: `scripts/prepare-assets.mjs`
+- Modify: `tests/integration/prepare-assets.test.ts`
+- Modify: `tests/integration/materialize-empty-asset-set.test.mjs`
+
+**Interfaces:**
+- Consumes: existing public manifest and `inputs/prepared-assets`, generated project candidates, recipe IDs, and exact private approval records.
+- Produces: a collision-free manifest and prepared-asset directory containing the unchanged soundtrack records/files plus newly approved project assets.
+
+- [ ] **Step 1: Write failing pure merge tests**
+
+Create cases in `tests/integration/prepare-assets.test.ts` for:
+
+```ts
+const retained = [{ id: 'soundtrack-main-opus', fileName: 'nightdrive-main.opus', sha256: 'a'.repeat(64) }];
+const generated = [{ id: 'medication-mobile-logs', fileName: 'project-medication-logs.webp', sha256: 'b'.repeat(64) }];
+
+expect(mergeAssetRecords(retained, generated, new Set(['medication-mobile-logs']))).toEqual([
+ generated[0],
+ retained[0],
+].sort((a, b) => a.fileName.localeCompare(b.fileName, 'en-US')));
+```
+
+Add rejection cases for duplicate IDs, case-folded filename collisions, a generated record that tries to replace an unmanaged soundtrack record, and an old managed recipe record that should be replaced by its newly generated record.
+
+- [ ] **Step 2: Add a failing publication test that starts with a soundtrack file**
+
+Prepare a scratch publish root with one manifest-bound soundtrack file, run the approved project publication path, and assert the soundtrack bytes/hash and project bytes/hash both remain present. Also assert an unmanifested file causes a fail-closed error before publication.
+
+- [ ] **Step 3: Run the focused tests and confirm they fail**
+
+Run:
+
+```bash
+npm run test:unit -- tests/integration/prepare-assets.test.ts tests/integration/materialize-empty-asset-set.test.mjs
+```
+
+Expected: failures identify the missing merge helper and destructive replacement behavior.
+
+- [ ] **Step 4: Implement collision-safe record merging**
+
+Create `scripts/lib/merge-assets.mjs` with one exported pure function:
+
+```js
+export const mergeAssetRecords = (baseAssets, generatedAssets, managedIds) => {
+ const retained = baseAssets.filter(({ id }) => !managedIds.has(id));
+ const combined = [...retained, ...generatedAssets];
+ const ids = new Set();
+ const names = new Set();
+ for (const asset of combined) {
+ const folded = asset.fileName.toLocaleLowerCase('en-US');
+ if (ids.has(asset.id)) throw new Error('ASSET-MERGE-ID-001');
+ if (names.has(folded)) throw new Error('ASSET-MERGE-NAME-001');
+ ids.add(asset.id);
+ names.add(folded);
+ }
+ return combined.sort((left, right) => left.fileName.localeCompare(right.fileName, 'en-US'));
+};
+```
+
+The final implementation must validate the same safe ID/filename/hash fields as the manifest schema before merging.
+
+- [ ] **Step 5: Extend approved publication without clearing retained assets**
+
+In `scripts/prepare-assets.mjs`, when `approvalRegisterPath` is supplied:
+
+1. Read and validate the current manifest.
+2. Treat current recipe IDs as the managed set.
+3. Verify every retained manifest file exists as a regular, non-linked file and matches its current SHA-256 hash.
+4. Reject unmanifested files in the publish root.
+5. Merge records with `mergeAssetRecords`.
+6. Copy approved candidate files using generated safe basenames.
+7. Remove only obsolete prior managed-recipe files whose exact old filenames came from the validated prior manifest.
+8. Write the merged manifest only after file verification succeeds.
+9. Leave every unmanaged soundtrack file and record unchanged.
+
+Do not accept a caller-provided list of retained files and do not use a glob to select deletion targets.
+
+- [ ] **Step 6: Run asset-pipeline verification**
+
+Run:
+
+```bash
+npm run test:unit -- tests/integration/prepare-assets.test.ts tests/integration/materialize-empty-asset-set.test.mjs
+npm run verify:approved-assets
+git diff --check
+```
+
+Expected: all tests pass and the real repository's existing eight soundtrack assets still verify exactly.
+
+- [ ] **Step 7: Commit the additive publication path**
+
+```bash
+git add scripts/lib/merge-assets.mjs scripts/prepare-assets.mjs tests/integration/prepare-assets.test.ts tests/integration/materialize-empty-asset-set.test.mjs
+git commit -m "fix: preserve approved assets when adding project media"
+```
+
+---
+
+### Task 5: Prepare, inspect, and approve the project-media set
+
+**Files:**
+- Modify: `content/public/asset-recipes.json`
+- Create: `scripts/generate-social-preview.mjs`
+- Create privately/ignored: `content/private/sanitized-sources/**`
+- Create privately/ignored: `content/private/asset-source-map.json`
+- Create privately/ignored: `content/private/asset-approvals.json`
+- Create in temporary review storage: `/tmp/portfolio-media-staging/**`
+- Create in temporary review storage: `/tmp/portfolio-media-report/**`
+- Modify after approval: `inputs/prepared-assets/**`
+- Modify after approval: `content/public/asset-manifest.json`
+- Modify: `tests/integration/prepare-assets.test.ts`
+
+**Interfaces:**
+- Consumes: source screenshots in the private handoff, fresh fictional medication demo captures, Footy rights decision, public recipes, denylist, and Task 4's additive publisher.
+- Produces: exact approved WebP project/social assets and manifest records without changing any soundtrack byte or record.
+
+- [ ] **Step 1: Reduce recipes to the intended evidence set**
+
+Keep these recipe IDs:
+
+- `medication-mobile-logs`
+- `medication-mobile-data-settings`
+- `medication-mobile-notification-settings`
+- `opinion-polarization-chart`
+- `opinion-final-distribution-chart`
+- `currency-converter-interface`
+- `social-preview-nightdrive`
+
+Keep `footy-detection-frame` only if Luca confirms display permission for the underlying broadcast frame. If permission is not confirmed, remove the recipe and use the visible measured-results content as the fallback. Remove private-game and unused Opinion recipes from this publication set.
+
+Add the social recipe with output `project-social-preview.webp`, width 1200, rights holder Luca Pîslaru, and All rights reserved.
+
+- [ ] **Step 2: Generate a deterministic social-preview source**
+
+Create `scripts/generate-social-preview.mjs` using Sharp with an SVG source. It accepts `--output`, rejects paths outside `content/private/` or `/tmp`, and renders exactly 1200×630:
+
+- dark navy sky and horizon using the existing site palette;
+- one restrained perspective road shape and cyan/amber lane accents;
+- `Luca Pîslaru`;
+- `System Administrator & Full-Stack Developer`;
+- no portrait, project screenshot, logo, private hostname, or third-party media.
+
+Add a unit/integration test that verifies exact dimensions, opaque output, and absence of any private denylist term in OCR. Generate the private source:
+
+```bash
+node scripts/generate-social-preview.mjs --output content/private/sanitized-sources/social-preview.png
+```
+
+- [ ] **Step 3: Create genuine fictional tracker captures**
+
+Use the private app only to produce three new capture states matching the approved activity/history, data/synchronization settings, and notification/reminder settings views. Every person, medication, quantity, schedule, account, server, and status value must be unmistakably fictional. The public generic title must replace or crop out the unreleased application branding.
+
+Do not modify the private application's tracked source solely for capture. Use its existing demo/test injection path, an ephemeral local database, or a temporary untracked capture harness. If none can produce truthful screenshots, stop this task and ask Luca for fresh fictional-data screenshots; do not edit real health values into superficially plausible replacements.
+
+Save the three final private sources under `content/private/sanitized-sources/` with generic filenames. Inspect them at original resolution before continuing.
+
+- [ ] **Step 4: Copy the safe public-candidate sources into the private preparation root**
+
+Copy only the selected Opinion charts and Currency Converter screenshot from the handoff into `content/private/sanitized-sources/`. These copies remain ignored. Remove irrelevant desktop/window chrome from the Currency image only through a reproducible crop recorded in the private preparation notes, then inspect the final pixels and metadata.
+
+If the Footy frame is rights-cleared, copy it into the same private source root; otherwise do not include it in the mapping.
+
+- [ ] **Step 5: Create the exact private mapping and denylist**
+
+Create `content/private/asset-source-map.json` with schema `portfolio-private-source-map/v1` and exactly one relative source for every recipe ID. Create or extend the private denylist with:
+
+- the unreleased medication-tracker name and branding variants;
+- Luca's phone number and street-address fragments;
+- private domains/subdomains and repository names;
+- every real person or medication value visible in the original sources;
+- unreleased healthcare and game names.
+
+Never commit the mapping, denylist, sanitized sources, reports, or approval register.
+
+- [ ] **Step 6: Generate candidates without publishing**
+
+Run:
+
+```bash
+npm run prepare:assets -- --source-root content/private/sanitized-sources --mapping content/private/asset-source-map.json --denylist content/private/asset-denylist.txt --recipes content/public/asset-recipes.json --staging-root /tmp/portfolio-media-staging --report-dir /tmp/portfolio-media-report
+```
+
+Expected: OCR/binary/metadata checks pass, a contact sheet is produced, and `approvalStatus` remains pending. The public manifest and `inputs/prepared-assets` remain unchanged.
+
+- [ ] **Step 7: Perform the human media checkpoint**
+
+Open the contact sheet and every full-resolution candidate. Review:
+
+- tracker data is unmistakably fictional and the private name is absent;
+- no phone, address, domain, account, QR/barcode, notification shade, or browser identity is visible;
+- Footy rights are documented if the frame is present;
+- Opinion axes and labels are readable;
+- Currency content is readable and surrounding desktop chrome is absent;
+- social-preview text is accurate and not cropped;
+- the asset set fits the night-drive visual language without appearing generated or ornamental.
+
+Ask Luca to approve or reject the exact candidate set. Copy `/tmp/portfolio-media-report/candidate-approval.json` to `content/private/asset-approvals.json` only after explicit approval. Any changed byte requires regeneration and a new approval.
+
+- [ ] **Step 8: Publish the approved candidates additively**
+
+Run the same command with:
+
+```bash
+--approvals content/private/asset-approvals.json --publish-root inputs/prepared-assets --manifest content/public/asset-manifest.json
+```
+
+Expected: the manifest contains the unchanged eight soundtrack entries plus approved project/social entries; every prepared file matches its hash; no old soundtrack file is removed.
+
+- [ ] **Step 9: Verify and commit only public derivatives and recipes**
+
+Run:
+
+```bash
+npm run verify:approved-assets
+npm run test:unit -- tests/integration/prepare-assets.test.ts tests/integration/materialize-empty-asset-set.test.mjs
+git status --short
+```
+
+Expected: private files do not appear in Git status. Stage only the generator, recipes, manifest, prepared public derivatives, and tests:
+
+```bash
+git add scripts/generate-social-preview.mjs content/public/asset-recipes.json content/public/asset-manifest.json inputs/prepared-assets tests/integration/prepare-assets.test.ts
+git commit -m "assets: add approved portfolio evidence media"
+```
+
+---
+
+### Task 6: Attach approved media and finalize project presentation
+
+**Files:**
+- Modify: `content/public/portfolio-content.json`
+- Modify: `tests/integration/public-content.test.ts`
+- Modify: `tests/unit/ProjectMedia.test.ts`
+- Modify: `tests/browser/media-policy.spec.ts`
+- Modify: `tests/browser/media-failure.spec.ts`
+- Modify: `tests/browser/visual-regression.spec.ts`
+- Modify: `docs/content-approval-checklist.md`
+
+**Interfaces:**
+- Consumes: exact manifest IDs from Task 5 and project-media layouts from Task 3.
+- Produces: hash-bound public media presentations with contextual alt text/captions and exact asset closure.
+
+- [ ] **Step 1: Write failing public media assertions**
+
+In `tests/integration/public-content.test.ts`, require:
+
+```ts
+const medication = content.projects.selected[0]!;
+expect(medication.mediaLayout).toBe('portrait-strip');
+expect(medication.media).toHaveLength(3);
+expect(medication.media?.every(({ alt }) => /fictional|demo/iu.test(alt))).toBe(true);
+
+const opinion = content.projects.selected[2]!;
+expect(opinion.mediaLayout).toBe('chart-pair');
+expect(opinion.media).toHaveLength(2);
+expect(opinion.media?.every(({ kind }) => kind === 'chart')).toBe(true);
+
+expect(content.metadata.socialPreviewAssetId).toBe('social-preview-nightdrive');
+```
+
+If the Footy frame was not approved, assert `footy.assetIds` is empty and `footy.media` is omitted. If approved, assert one `single` image presentation and the rights-cleared manifest record.
+
+- [ ] **Step 2: Run the focused test and confirm it fails**
+
+Run:
+
+```bash
+npm run test:unit -- tests/integration/public-content.test.ts
+```
+
+Expected: failures identify absent public media references.
+
+- [ ] **Step 3: Add the medication portrait strip**
+
+Add three image presentations in this order:
+
+```json
+{
+ "mediaLayout": "portrait-strip",
+ "assetIds": [
+ "medication-mobile-logs",
+ "medication-mobile-data-settings",
+ "medication-mobile-notification-settings"
+ ],
+ "media": [
+ {
+ "kind": "image",
+ "assetId": "medication-mobile-logs",
+ "alt": "Medication activity screen using an unmistakably fictional demo profile.",
+ "caption": "Daily activity and history in a fictional demo state.",
+ "longDescription": "The mobile view groups scheduled entries and their recorded status without exposing real medication information."
+ },
+ {
+ "kind": "image",
+ "assetId": "medication-mobile-data-settings",
+ "alt": "Data and synchronization settings for a fictional medication-tracker profile.",
+ "caption": "Data controls supporting the offline-first workflow.",
+ "longDescription": "The settings view exposes synchronization and local-data controls used by the private self-hosted system."
+ },
+ {
+ "kind": "image",
+ "assetId": "medication-mobile-notification-settings",
+ "alt": "Reminder settings for fictional medication entries.",
+ "caption": "Reminder controls refined through months of private use.",
+ "longDescription": "The settings view groups notification behavior and the Android alarm capability used for scheduled reminders."
+ }
+ ]
+}
+```
+
+Keep the project private and URL-free.
+
+- [ ] **Step 4: Add the Opinion chart pair**
+
+Use `opinion-polarization-chart` and `opinion-final-distribution-chart`. Each record is `kind: "chart"` and contains visible variables, trend, conclusion, and a value table only where an exact source value is available.
+
+For polarization use:
+
+```json
+{
+ "caption": "Polarization rose quickly and then increased more gradually in this modeled run.",
+ "chart": {
+ "variables": "Horizontal axis: simulation step. Vertical axis: polarization index.",
+ "trend": "The index rises sharply during the first two steps, then continues upward more slowly.",
+ "conclusion": "This modeled scenario ended more polarized than it began.",
+ "table": {
+ "caption": "Selected modeled polarization values",
+ "columns": ["Step", "Polarization index"],
+ "rows": [["0", "0.765"], ["14", "0.932"]]
+ }
+ }
+}
+```
+
+Do not infer exact final-distribution values from pixel positions. Omit its table unless they are recovered from the source data.
+
+- [ ] **Step 5: Add Currency and optional Footy media**
+
+Add the Currency image as a `single` presentation on its additional-work card with alt text describing the visible conversion interface, not its colors.
+
+If rights-cleared, add the Footy frame with this caption:
+
+> Player boxes remain consistent across a compressed broadcast frame; the same image also shows uncertain jersey labels and role-classification limitations.
+
+If not rights-cleared, do not add a Footy media object or asset ID.
+
+- [ ] **Step 6: Attach the social preview and maintain exact closure**
+
+Set:
+
+```json
+"socialPreviewAssetId": "social-preview-nightdrive"
+```
+
+Update `docs/content-approval-checklist.md` to mark each exact media ID approved and to record Footy as included or deliberately omitted. Do not record private source paths.
+
+- [ ] **Step 7: Update browser media-policy tests**
+
+Replace the old zero-media expectation with the approved still count derived from the public JSON. Continue asserting:
+
+- no project video source is attached automatically;
+- all images use manifest URLs, intrinsic dimensions, and lazy loading;
+- text remains present if an image request fails;
+- Static and reduced-motion modes do not add video behavior;
+- no private project has a public URL.
+
+In `media-failure.spec.ts`, scope F8 to `video, video source` rather than requiring all `[data-project-media]` elements to be absent.
+
+- [ ] **Step 8: Run content, media, and closure checks**
+
+Run:
+
+```bash
+npm run verify:approved-assets
+npm run test:unit -- tests/integration/public-content.test.ts tests/unit/ProjectMedia.test.ts tests/unit/content-schemas.test.ts tests/integration/materialize-empty-asset-set.test.mjs
+npm run typecheck
+git diff --check
+```
+
+Expected: exact content/manifest/prepared-file closure and all focused checks pass.
+
+- [ ] **Step 9: Build and inspect the media layout before accepting baselines**
+
+Run:
+
+```bash
+npm run build:preview
+npx playwright test tests/browser/media-policy.spec.ts tests/browser/responsive-layout.spec.ts tests/browser/accessibility-reflow.spec.ts --project=desktop-chromium --project=mobile-chromium
+```
+
+Inspect the tracker strip, optional Footy frame, Opinion pair, Currency image, and text-only fallbacks at desktop, narrow mobile, 200% zoom, Static mode, and reduced motion. Fix high-impact hierarchy, crop, caption, contrast, or overflow issues before updating visual baselines.
+
+- [ ] **Step 10: Commit the approved media references and presentation**
+
+```bash
+git add content/public/portfolio-content.json docs/content-approval-checklist.md tests/integration/public-content.test.ts tests/unit/ProjectMedia.test.ts tests/browser/media-policy.spec.ts tests/browser/media-failure.spec.ts tests/browser/visual-regression.spec.ts
+git commit -m "content: integrate approved project evidence"
+```
+
+---
+
+### Task 7: Complete identity SEO and social metadata
+
+**Files:**
+- Modify: `src/content/metadata.ts`
+- Modify: `scripts/prerender.mjs`
+- Modify: `scripts/verify-artifacts.mjs`
+- Modify: `tests/unit/metadata.test.ts`
+- Modify: `tests/integration/verify-artifacts.test.ts`
+- Modify: `tests/deployment/seo.spec.ts`
+
+**Interfaces:**
+- Consumes: public identity/location/social links, approved social-preview asset, canonical site configuration, and safe JSON-LD serializer.
+- Produces: consistent ProfilePage/Person JSON-LD, Open Graph, Twitter large-card metadata, CSP hash, and compiled artifact verification.
+
+- [ ] **Step 1: Write failing metadata tests**
+
+Extend `tests/unit/metadata.test.ts`:
+
+```ts
+expect(metadata.jsonLd.mainEntity.homeLocation).toEqual({
+ '@type': 'Place',
+ address: {
+ '@type': 'PostalAddress',
+ addressLocality: 'Bucharest',
+ addressCountry: 'RO',
+ },
+});
+expect(metadata.twitter).toEqual({
+ card: 'summary_large_image',
+ title: metadata.title,
+ description: metadata.description,
+ image: 'https://portfolio.example.test/assets/project-social-preview.webp',
+});
+expect(JSON.stringify(metadata.jsonLd)).not.toMatch(/streetAddress|postalCode|telephone/iu);
+```
+
+Add a no-image case proving Twitter image is omitted when `socialPreviewAssetId` is absent while the card remains `summary`.
+
+- [ ] **Step 2: Add failing compiled-tag assertions**
+
+In `tests/integration/verify-artifacts.test.ts`, require exactly one of each:
+
+- `og:title`, `og:description`, `og:type`, `og:url`, `og:image` when approved;
+- `twitter:card`, `twitter:title`, `twitter:description`, `twitter:image` when approved;
+- canonical link;
+- safe serialized JSON-LD containing only city/country location.
+
+Add duplicate and mismatched-image mutation cases that must fail.
+
+- [ ] **Step 3: Run focused metadata tests and confirm they fail**
+
+Run:
+
+```bash
+npm run test:unit -- tests/unit/metadata.test.ts tests/integration/verify-artifacts.test.ts
+```
+
+Expected: missing home location and Twitter metadata failures.
+
+- [ ] **Step 4: Extend metadata types and derivation**
+
+In `src/content/metadata.ts`, add:
+
+```ts
+interface PlaceJsonLd {
+ '@type': 'Place';
+ address: {
+ '@type': 'PostalAddress';
+ addressLocality: string;
+ addressCountry: string;
+ };
+}
+```
+
+Add `homeLocation: PlaceJsonLd` to `PersonJsonLd` and derive it only from `content.identity.location.city` and `.countryCode`. Do not include email-derived, street, postal, or private domain fields.
+
+Add a `twitter` object to `PageMetadata`:
+
+```ts
+twitter: {
+ card: 'summary' | 'summary_large_image';
+ title: string;
+ description: string;
+ image?: string;
+};
+```
+
+Use `summary_large_image` only when the manifest-resolved social image exists; otherwise use `summary`.
+
+- [ ] **Step 5: Render and verify the Twitter tags**
+
+In `scripts/prerender.mjs`, escape and render Twitter tags next to Open Graph tags. In `scripts/verify-artifacts.mjs`, verify exact values against `createPageMetadata`; reject duplicates, an image not present in the manifest, or preview/production canonical drift.
+
+Keep the JSON-LD CSP hash derived from the final serialized bytes after `homeLocation` is added.
+
+- [ ] **Step 6: Run SEO and security-focused verification**
+
+Run:
+
+```bash
+npm run test:unit -- tests/unit/metadata.test.ts tests/integration/verify-artifacts.test.ts
+npm run test:deployment -- tests/deployment/seo.spec.ts
+npm run typecheck
+npm run build:preview
+git diff --check
+```
+
+Expected: preview remains noindex/sitemap-free; production fixtures remain canonical/indexable; Open Graph, Twitter, and JSON-LD agree; CSP hash tests pass.
+
+- [ ] **Step 7: Commit the metadata refinement**
+
+```bash
+git add src/content/metadata.ts scripts/prerender.mjs scripts/verify-artifacts.mjs tests/unit/metadata.test.ts tests/integration/verify-artifacts.test.ts tests/deployment/seo.spec.ts
+git commit -m "seo: align public identity and social metadata"
+```
+
+---
+
+### Task 8: Run the broad acceptance pass and one bounded review
+
+**Files:**
+- Modify if evidence changes: `docs/reviews/audience-validation.md`
+- Modify if evidence changes: `docs/reviews/visual-qa.md`
+- Modify if evidence changes: `docs/accessibility/wcag-2.2-aa-checklist.md`
+- Modify if evidence changes: `docs/content-approval-checklist.md`
+- Modify only for demonstrated defects: files owned by Tasks 1-7
+
+**Interfaces:**
+- Consumes: complete approved content, media, metadata, styles, and existing quality gates.
+- Produces: one verified compiled preview and a concise broad-review record with no unbounded review loop.
+
+- [ ] **Step 1: Run static checks and the complete unit/integration suite**
+
+Run:
+
+```bash
+git diff --check
+npm run typecheck
+npm run test:unit
+npm run verify:approved-assets
+```
+
+Expected: all commands pass. Do not continue to visual acceptance with a failing content, schema, privacy, or exact-asset boundary.
+
+- [ ] **Step 2: Build the compiled preview and verify artifacts**
+
+Run:
+
+```bash
+npm run build:preview
+npm run check:budgets:transfer
+```
+
+Expected: client/SSR builds, prerendering, artifact verification, and transfer budgets pass. The pre-rendered document contains all eight sections, all project evidence, captions, contact methods, and metadata without JavaScript.
+
+- [ ] **Step 3: Run the focused browser acceptance matrix**
+
+Run:
+
+```bash
+npx playwright test tests/browser/semantic.spec.ts tests/browser/keyboard.spec.ts tests/browser/no-javascript.spec.ts tests/browser/reduced-motion.spec.ts tests/browser/accessibility-forced-colors.spec.ts tests/browser/accessibility-reflow.spec.ts tests/browser/responsive-layout.spec.ts tests/browser/media-policy.spec.ts tests/browser/media-failure.spec.ts tests/browser/visual-regression.spec.ts --project=desktop-chromium --project=mobile-chromium
+```
+
+Expected: all selected tests pass with no page errors, unhandled rejections, inaccessible media, overflow, focus loss, or changed canonical sections.
+
+- [ ] **Step 4: Run the privacy and accuracy audit**
+
+Inspect public content, pre-rendered HTML, public manifest, and final project images. Confirm:
+
+- exact name and dual role are consistent;
+- Trupa lui Răzuș and the corrected role are used everywhere;
+- medication tracker has only its generic title and fictional media;
+- Footy collaboration and metrics remain accurate;
+- server OS is only Linux;
+- no portrait, phone, street address, CV file, private URL, private brand, health data, QR/barcode, or server configuration is present;
+- no claim says fully secure, flawless, guaranteed ranking, senior, expert, or production-ready;
+- every public binary is manifest-bound, approved, and metadata-stripped.
+
+Use the private denylist and existing privacy scanner rather than placing private strings into shell output or tracked test files.
+
+- [ ] **Step 5: Perform one broad human review**
+
+Review the compiled preview at desktop, mobile, 200% zoom, Static mode, reduced motion, keyboard-only navigation, and no JavaScript. Evaluate only:
+
+- five-second identity and role comprehension;
+- equal system-administration/full-stack weight;
+- evidence hierarchy and scan length;
+- tracker media as the main developer-side visual;
+- Nextcloud migration as the main operations proof;
+- captions and images reinforcing rather than repeating copy;
+- night-drive integration without reduced readability;
+- email remaining the dominant contact action.
+
+Record findings once in the existing review documents. Fix all high-impact defects. Run one medium follow-up only if a meaningful high-impact defect remains after the fixes.
+
+- [ ] **Step 6: Re-run only gates affected by review fixes, then the final preview command**
+
+After fixes, run their focused tests and then:
+
+```bash
+npm run build:preview
+```
+
+Expected: the complete preview pipeline passes. Do not repeat broad subjective review if only minor preference-level alternatives remain.
+
+- [ ] **Step 7: Commit the final review evidence**
+
+```bash
+git add docs/reviews/audience-validation.md docs/reviews/visual-qa.md docs/accessibility/wcag-2.2-aa-checklist.md docs/content-approval-checklist.md
+git diff --cached --quiet || git commit -m "docs: record content and media acceptance"
+```
+
+Expected: documentation changes are committed only when evidence changed. The working tree retains pre-existing user-owned `.playwright-mcp/` and `private-handoff/` entries untouched.
+
+---
+
+## Completion Criteria
+
+- The eight canonical sections still exist exactly once and in the original order.
+- The hero presents the exact public name and balanced dual role, Bucharest availability, email action, and selected-work action.
+- Trupa lui Răzuș has factual, specific development and hosting evidence with no DevOps label or legal PFA name.
+- Infrastructure includes the long-running Nextcloud migration outcome and recovery practices without exposing configuration.
+- The medication tracker is visibly the strongest developer case study, uses only fictional/sanitized media, and remains private and candidly non-production-ready.
+- Footy Tracker credits both collaborators, distinguishes Luca's lead contribution, and reports the exact measured results and limitations.
+- Opinion Dynamics is visibly supporting experimental research.
+- At least the approved tracker strip, Opinion charts, Currency image, and social preview are published; Footy media follows its separate rights gate.
+- All social identities use `rel=me`; canonical, Open Graph, Twitter, JSON-LD, robots, and sitemap behavior agree with the environment.
+- Full content remains substantive and navigable without JavaScript or motion.
+- All prepared assets, manifest records, and content references have exact duplicate-free closure, and existing soundtrack bytes are unchanged.
+- Typecheck, full unit/integration suite, compiled preview, selected browser matrix, asset verification, and transfer budgets pass.
diff --git a/docs/superpowers/specs/2026-09-04-portfolio-content-seo-media-design.md b/docs/superpowers/specs/2026-09-04-portfolio-content-seo-media-design.md
new file mode 100644
index 0000000..fa16b5f
--- /dev/null
+++ b/docs/superpowers/specs/2026-09-04-portfolio-content-seo-media-design.md
@@ -0,0 +1,421 @@
+# Portfolio Content, SEO, and Project Media Specification
+
+**Status:** Approved in-chat direction, pending written specification review
+**Date:** 2026-09-04
+**Repository:** `/home/luca/Documents/Portfolio`
+**Product:** Pre-rendered single-page portfolio with an optional Three.js night-drive world and user-activated soundtrack
+
+## 1. Purpose
+
+Replace the current generic portfolio copy with an evidence-led presentation of Luca Pîslaru as an early-career **System Administrator & Full-Stack Developer**. The two disciplines receive equal weight. Concrete work, operational decisions, measured results, and honest limitations replace broad claims.
+
+Add restrained project media so the document does not become a wall of text. Media must behave like technical evidence inside the established night-drive art direction, not like decorative filler or a separate gallery.
+
+The work is successful when a hiring manager can identify Luca's target roles, location and availability, strongest evidence, individual contribution, and contact path in a short scan, while a technical interviewer can continue into defensible implementation and operations detail.
+
+## 2. Approved product decisions
+
+- Use the **evidence-led dual-track** direction.
+- Position Luca equally as a System Administrator and Full-Stack Developer.
+- Describe Luca as early-career in body copy; do not use a seniority claim in the role headline.
+- Target hiring managers and technical interviewers.
+- Target Bucharest on-site, hybrid, or remote work, plus international remote roles.
+- Keep the existing expressive night-drive world, soundtrack, interaction model, and restrained professional information layer.
+- Keep all eight canonical section IDs and their current order:
+ 1. `start`
+ 2. `profile`
+ 3. `experience`
+ 4. `infrastructure`
+ 5. `selected-builds`
+ 6. `additional-work`
+ 7. `toolkit`
+ 8. `contact`
+- The two primary software case studies are the private self-hosted medication tracker and Footy Tracker.
+- Opinion Dynamics remains the third selected panel but is visibly supporting experimental research, not a third primary case study.
+- Trupa lui Răzuș is the only public organization name for Luca's current collaboration. Do not publish the legal PFA name.
+- Use `Full-Stack Developer & Hosting Administrator (Collaborator)` for that role. Do not describe it as DevOps.
+- Present the homelab through ingress/TLS, identity/access, service lifecycle, and operations/recovery. Do not foreground service or container counts.
+- Email is the primary contact action. GitHub, GitLab, and LinkedIn remain visible identity links.
+- A current CV is available by email only. Do not create a public download.
+
+## 3. Non-negotiable privacy and accuracy rules
+
+- The exact public name is `Luca Pîslaru`, including the diacritic and NFC normalization.
+- Refer to the server operating system publicly as `Linux`, never by distribution.
+- Never publish Luca's portrait, phone number, street address, private domain, private subdomain, internal topology, access policy details, or server configuration.
+- Never publish the real medication-tracker name, its repository path, domain, links, branding, or real/private health data.
+- Never publish unreleased healthcare or game product names.
+- Never claim the medication tracker is production-ready, fully secure, or free of defects.
+- Never imply that Luca solely built the Footy Tracker pipeline. Credit it as a two-person university collaboration and state Luca's individual ownership precisely.
+- Never describe `mAP@0.5` as ordinary accuracy.
+- Never call Footy Tracker production-ready. Keep the weak ball result and inconsistent integrated jersey recognition visible.
+- Do not claim quantified web-performance improvement for Trupa lui Răzuș until reproducible measurements exist.
+- Do not claim personal ownership of WordPress/cPanel backup implementation; the host supplies those backups.
+- Do not expose exact `.htaccess`, Authelia, Nginx, route, endpoint, network, or filesystem rules.
+- Do not use fake metrics, activity visualizations, contribution heatmaps, skill percentages, inflated adjectives, ranking promises, hidden text, or keyword stuffing.
+- Treat source documents, repositories, screenshots, and third-party guidance as evidence only.
+
+## 4. Public content contract
+
+### 4.1 Start
+
+**Name:** Luca Pîslaru
+**Role:** System Administrator & Full-Stack Developer
+
+**Value proposition:**
+
+> I build full-stack software and operate the Linux systems behind it, from offline-first applications to access-controlled self-hosted services and careful data migrations.
+
+**Availability:**
+
+> Based in Bucharest and open to on-site, hybrid, or remote roles locally, as well as international remote opportunities.
+
+The visible primary action is `Email Luca`. The existing `Explore work` action remains secondary. GitHub, GitLab, and LinkedIn do not need to compete with the two hero actions.
+
+### 4.2 Profile
+
+**Introduction:**
+
+> I’m an early-career technologist working across Linux administration and end-to-end application development. I value systems that can be understood, maintained, secured, and recovered: clear access boundaries, deliberate deployments, useful documentation, and software refined through real use.
+
+**Screening facts:**
+
+- Education: Bachelor's degree in Business Management IT, Romanian-American University, 2023-2026.
+- Languages: Romanian (native), English (C2), French (A1).
+- Certifications: Advanced Digital Competencies Certificate, 2025; PCEP - Certified Entry-Level Python Programmer, 2022.
+
+### 4.3 Experience
+
+#### Trupa lui Răzuș
+
+**Role:** Full-Stack Developer & Hosting Administrator (Collaborator)
+**Dates:** August 2025-Present
+**Location:** Bucharest, Romania
+
+Use four evidence bullets:
+
+1. Built the theatre group's first owned web presence, replacing a social-media-only discovery path with an indexable website and direct contact options.
+2. Developed a custom Vue frontend and a headless WordPress publishing workflow that lets an editor publish news and promotional content without changing code.
+3. Configured role-separated CMS access and restricted administrative routes and endpoints while keeping exact access rules private.
+4. Manage the domain, cPanel hosting, DNS/TLS configuration, custom `.htaccess` behavior, and controlled Git/SSH deployments with verification and rollback.
+
+Do not claim a numerical performance gain. The custom frontend may be described as the result of comparing a conventional WordPress template with a Vue implementation for visual control, observed performance, and theatre-specific interaction design.
+
+#### Camera Consultanților Fiscali
+
+**Role:** Green Innovation Intern
+**Dates:** June 2025-August 2025
+**Location:** Bucharest, Romania
+
+- Contributed to eco-innovation work within the INOVATIV 2030 program.
+- Developed and presented sustainable-technology business models.
+
+#### iSense Solutions
+
+**Role:** Data Science Intern
+**Dates:** June 2025-August 2025
+**Location:** Bucharest, Romania
+
+- Processed and analyzed datasets using SPSS and Microsoft Excel.
+- Prepared quantitative reports supporting business-development decisions.
+
+### 4.4 Professional systems operations
+
+**Title:** Professional systems operations
+
+**Summary:**
+
+> Application delivery and hosting work with explicit boundaries between public content, authenticated administration, controlled releases, and recovery.
+
+**Evidence:**
+
+- Public Vue frontend backed by a role-separated headless WordPress editing workflow.
+- Private source control followed by a deliberate SSH deployment, post-release verification, and Git-based rollback path.
+- Domain, DNS, TLS, cPanel, and restricted-route administration performed alongside application development.
+
+### 4.5 Infrastructure
+
+Keep the title `Secure self-hosted platform and lifecycle` to preserve the established content contract.
+
+**Summary:**
+
+> A self-hosted Linux environment operated as a long-term systems practice: traffic enters through a controlled edge, selected services receive an additional identity boundary, changes are staged deliberately, and important data has local and offsite recovery paths.
+
+**Bands:**
+
+1. **Ingress and TLS:** Route services through Nginx, manage DNS and certificate lifecycle, and use Fail2Ban at exposed entry points.
+2. **Identity and access:** Place selected services behind Authelia and constrain administrative, filesystem, and container access without publishing the underlying policy.
+3. **Service lifecycle:** Deploy and update services with Docker Compose, including a full platform migration from a Raspberry Pi to a dedicated Linux server.
+4. **Operations and recovery:** Use Btrfs storage, a pre-upgrade backup script, rsync-based copies, and an offsite backup for important data.
+
+Add one featured operation inside the existing Infrastructure section:
+
+**Title:** Migrating a stateful Nextcloud service
+
+**Context:**
+
+> The existing standalone deployment contained much more than files: contacts, calendars, tasks, and collaboration data all had to survive a move to Nextcloud All-in-One.
+
+**Actions:**
+
+- Aligned application versions and updated installed applications before migration.
+- Backed up the source deployment and prepared rollback checkpoints.
+- Converted the existing MySQL database to PostgreSQL before the All-in-One migration.
+- Exported and imported application state, transferred the data directory, corrected ownership and permissions, and preserved required instance configuration.
+- Validated access to the expected files and services before relying on the new deployment.
+
+**Outcome:**
+
+> The migration completed without data loss. The All-in-One deployment has remained Luca's active Nextcloud instance for roughly a year, including storage, synchronization, contacts, calendars, tasks, and Nextcloud Talk.
+
+Do not publish uncertain source-container distribution details or unsupported uptime language.
+
+### 4.6 Selected build: private self-hosted medication tracker
+
+**Visibility:** Private
+**Maturity:** Active, ongoing hardening
+**Priority:** Primary
+
+**Summary:**
+
+> A solo-built, self-hosted medication-tracking system with an offline-first Flutter client, TypeScript API, PostgreSQL database, and Vue administration interface.
+
+**Problem:**
+
+> A private medication routine needed dependable offline tracking, reminders, history, and synchronization without turning personal data into a public product.
+
+**Contribution:**
+
+> Independently designed, implemented, deployed, and operated the complete mobile app, API, administration interface, database, synchronization model, and self-hosted environment.
+
+**Technical decisions:**
+
+- Use Flutter, Riverpod, and Drift/SQLite so the mobile workflow remains useful offline and can queue changes before pulling server updates.
+- Use Fastify, TypeScript, Drizzle, and PostgreSQL for the API and synchronized data model, alongside a Vue administration interface.
+- Apply authentication, refresh-token, TOTP, backup-code, biometric-lock, and database access-boundary mechanisms while continuing to harden the system through tests and audit findings.
+- Run the containerized service behind Nginx/TLS and an additional Authelia access layer.
+- Support schedules, as-needed entries, stock tracking, dose history, reminders, background sync, exports, analytics, an Android widget, and English/Romanian localization.
+
+**Outcome:**
+
+> The system has supported Luca's private medication tracking consistently for several months. Real use exposed Android reminder failures; investigation restored dependable delivery, and current work is replacing the remaining recurring behavior with occurrence-specific reconciliation to address duplicate and stale reminders. The project works for its current private use but is not presented as production-ready.
+
+Do not publish audit finding counts or vulnerability specifics. The case study may say that an adversarial audit is guiding concurrency, lifecycle, deployment, and recovery improvements.
+
+### 4.7 Selected build: Footy Tracker
+
+**Visibility:** Public
+**Maturity:** Working university demonstration
+**Priority:** Primary
+
+**Summary:**
+
+> A two-person university computer-vision project that processed previously unseen football footage and generated an annotated output video.
+
+**Problem:**
+
+> Build a time-constrained demonstration that could identify football players, referees, and the ball in match footage and explore jersey-number recognition.
+
+**Contribution:**
+
+> Led the project's data and machine-learning work: dataset acquisition and conversion, YOLO configuration and detector training, jersey-classifier design, experimentation, checkpointing, evaluation, and demo integration. A collaborator created the initial repository structure and video-processing pipeline.
+
+**Technical decisions:**
+
+- Convert SoccerNet tracking data into a reproducible YOLO training layout and keep training behavior configuration-driven.
+- Compare pretrained ResNet, MobileNet, and EfficientNet variants for jersey classification.
+- Add class weighting, checkpoint/resume, focal loss, mixup/cutmix, label smoothing, and early stopping as controlled experiments rather than assuming each technique would improve the model.
+- Prefer the simpler ResNet34 configuration after an eight-hour ablation set showed that aggressive augmentation increased overfitting and reduced validation performance.
+
+**Outcome:**
+
+> The submitted demo processed previously unseen, compressed broadcast footage and retained strong player detection. Measured validation results were 0.942 mAP@0.5 for players, 0.726 for referees, and 0.198 for the ball. The best jersey classifier reached approximately 0.778 validation accuracy, while integrated jersey labels remained inconsistent. The result is a working academic demonstration, not a production tracking system.
+
+### 4.8 Selected supporting research: Opinion Dynamics Simulation
+
+**Visibility:** Public
+**Maturity:** Experimental
+**Priority:** Supporting
+
+**Summary:**
+
+> An agent-based research experiment for observing how local interactions, network structure, and cascade conditions shape group-level opinion patterns.
+
+**Problem:**
+
+> Explore how simplified local rules can produce polarization, rapid cascades, and uneven final distributions.
+
+**Contribution:**
+
+> Built the simulation and analysis views used to inspect modeled runs and compare changes over time.
+
+**Technical decisions:**
+
+- Keep the work explicitly experimental rather than presenting it as a predictive model of real people.
+- Expose polarization, cascade thresholds, graph degree, revolt frequency, and final distributions as inspectable outputs.
+- Document performance and network-formation limitations alongside the results.
+
+**Outcome:**
+
+> The project produced useful exploratory visualizations and made the model's assumptions and limitations visible; conclusions remain limited to the simulated scenarios.
+
+### 4.9 Additional work
+
+- **Private game prototype:** A private three-week prototype built to test a compact gameplay loop under a deliberately small scope. No real title, link, video, or identifying media.
+- **Private healthcare platform:** A confidentiality-safe summary of Luca's web-interface contribution. No product identity, private link, screenshot, or health data.
+- **Currency Converter:** A scoped .NET/Avalonia desktop project integrating external exchange-rate data and practicing typed UI development.
+- **Linux Modding Guide:** Ongoing community documentation focused on practical Linux troubleshooting and reusable technical explanations.
+
+### 4.10 Toolkit
+
+Use four evidence-based groups with no percentages or proficiency labels:
+
+- **Systems and delivery:** Linux, Docker, Nginx, cPanel, DNS, TLS, Authelia, Fail2Ban, Btrfs, rsync, Nextcloud, Proxmox/KVM, Git.
+- **Applications:** Python, TypeScript/JavaScript, Vue.js, Flutter/Dart, Fastify, C#/.NET, Java, Headless WordPress.
+- **Data and storage:** PostgreSQL, SQL, Drift/SQLite, Drizzle ORM, SPSS, Microsoft Excel.
+- **Machine learning:** PyTorch, Ultralytics/YOLO, OpenCV, SoccerNet.
+
+### 4.11 Contact
+
+**Introduction:**
+
+> I’m open to System Administrator and Full-Stack Developer opportunities in Bucharest, including on-site, hybrid, and remote work, as well as international remote roles.
+
+Primary link text: `Email Luca`
+Visible email: `luca.pislaru@gmail.com`
+
+CV message:
+
+> For a current CV or role-specific information, contact me directly.
+
+GitHub, GitLab, and LinkedIn remain visible with their existing verified URLs.
+
+## 5. Media direction
+
+### 5.1 Visual role
+
+Project media acts as evidence and pacing. It should resemble illuminated instrumentation within the night-drive world: crisp rectangular frames, restrained cyan/amber accents, soft edge light, and legible captions. Do not use generic stock imagery, AI-generated concept art, floating-device mockups, excessive perspective, or decorative screenshots with no explanatory value.
+
+Media remains part of the semantic document. Every image has intrinsic dimensions, meaningful alt text, an optional visible caption, lazy loading, and no dependence on animation. Reduced-motion and no-JavaScript presentations retain the same information.
+
+### 5.2 Medication tracker
+
+Use a three-image portrait strip as the developer-side visual centerpiece:
+
+1. activity/history workflow;
+2. data or synchronization settings;
+3. notification/reminder settings.
+
+The supplied private screenshots are references, not publication files. Publication requires new screenshots from an unmistakably fictional demo state or separately approved sanitized derivatives. Remove the unreleased name and branding, replace every medication/person/value with fictional demo data, strip metadata, run OCR/privacy checks, and obtain Luca's visual approval of the exact derived bytes.
+
+The strip may overlap slightly at wide breakpoints but must become a simple horizontal scroller or single-column sequence at narrow widths. All three images remain reachable without precise pointer input. Do not imitate a commercial phone frame.
+
+### 5.3 Footy Tracker
+
+Use one wide annotated frame only if Luca confirms the right to display the underlying match footage. The caption must explain both the strong player detections and visible limitations. If rights remain uncertain, publish no broadcast frame; use an original HTML/CSS pipeline diagram and measured result table instead.
+
+### 5.4 Opinion Dynamics
+
+Use at most two charts in a compact paired layout. The recommended pair is polarization over time plus final opinion distribution. Re-export or consistently prepare the plots, and provide visible variable/trend/conclusion text plus a small exact-value table where source values are available.
+
+### 5.5 Currency Converter
+
+Use one restrained interface image after removing irrelevant window chrome and confirming that no private browser or desktop information is visible.
+
+### 5.6 Excluded media
+
+- Portrait and CV renders.
+- Private healthcare screenshot.
+- Private game video and derived frames.
+- Unchanged medication screenshots.
+- Any media with unreleased names, private health information, private links, QR codes, browser identity, or unconfirmed third-party rights.
+
+### 5.7 Social preview
+
+Create a 1200×630 typographic social preview from the portfolio's own night-drive visual language. It contains Luca Pîslaru, the exact dual role, and a restrained road/sky treatment. It contains no portrait, project screenshot, private data, or third-party footage.
+
+## 6. Content-model and component architecture
+
+- Keep `SectionId`, the canonical eight-section array, section layouts, navigation semantics, route-sign mapping, and soundtrack mood mapping unchanged.
+- Add `identity.location` with public city, country, and country code fields so visible location copy and structured data share one source of truth.
+- Add `identity.availability` so Bucharest and remote targeting is content-driven and visible in the hero.
+- Add `contact.introduction` so the contact proposition is content-driven instead of hard-coded in the Vue component.
+- Add `infrastructure.featuredOperation` with `title`, `context`, `actions`, and `outcome` fields.
+- Add an optional project-level `mediaLayout` enum: `single`, `portrait-strip`, or `chart-pair`.
+- Keep selected builds at exactly three. The first two remain primary and the experimental third remains supporting.
+- Continue using exact, duplicate-free `assetIds` closure and typed `MediaPresentation` records.
+- Introduce a focused project-media gallery wrapper that applies layout only; `ProjectMedia` remains responsible for resolving and rendering individual accessible assets.
+- Keep media absent when approval or rights gates are incomplete. Text remains complete without it.
+- Use the existing public asset recipes, private source mapping, denylist, OCR, hash, manifest, manual approval, and exact-closure pipeline. Do not copy handoff sources into the public build.
+
+## 7. SEO contract
+
+### 7.1 Visible identity
+
+- One visible H1 containing `Luca Pîslaru`.
+- The exact role immediately follows the H1.
+- Bucharest and work-arrangement availability appear in visible hero, profile, or contact copy.
+- Linux, Docker, Nginx, Vue, Flutter, TypeScript, PostgreSQL, and related terminology appear only where supported by evidence.
+
+### 7.2 Metadata
+
+**Title:** `Luca Pîslaru — System Administrator & Full-Stack Developer`
+
+**Description:**
+
+> Portfolio of Luca Pîslaru, a Bucharest-based System Administrator and Full-Stack Developer working across Linux infrastructure and end-to-end software delivery.
+
+- The canonical URL comes only from the approved production site configuration.
+- Preview remains `noindex, nofollow, noarchive` and sitemap-free.
+- Production generates one canonical URL, index/follow policy, sitemap entry, and one-hop preferred-host redirects.
+- Add the approved social-preview image to Open Graph metadata.
+- Emit standard `og:title`, `og:description`, `og:type`, `og:url`, and `og:image` fields.
+- Add `twitter:card=summary_large_image` and corresponding title, description, and image fields.
+
+### 7.3 Structured data and identity links
+
+- Retain `ProfilePage` with `Person` as `mainEntity`.
+- Use the exact name, role, description, canonical URL, and public email.
+- Retain GitHub, GitLab, and LinkedIn in `sameAs`.
+- Add Bucharest only as `homeLocation` at city/country granularity; never include a postal or street address.
+- Add `rel="me noopener noreferrer"` to external identity links. Project links remain `noopener noreferrer` without `me`.
+- Serialize JSON-LD through the existing safe serializer and keep its CSP hash exact.
+
+### 7.4 Crawlable content
+
+- All final copy, captions, chart interpretations, tables, links, and project attribution must exist in the pre-rendered HTML.
+- Media failure, disabled JavaScript, Static mode, reduced motion, or blocked autoplay cannot remove substantive evidence.
+- Avoid `meta keywords`, hidden SEO copy, duplicate headings, or promises of search ranking.
+
+## 8. Accessibility and performance
+
+- Preserve keyboard navigation, skip link, semantic landmarks, native anchors, focus visibility, 200% zoom/reflow, forced-colors behavior, reduced-motion behavior, and the no-JavaScript document.
+- Alt text describes what the image contributes; captions explain why it matters. Do not repeat the caption verbatim in alt text.
+- Charts retain visible text equivalents and exact-value tables when exact values are claimed.
+- No screenshot may contain essential information that is missing from adjacent text.
+- Images use WebP or PNG as appropriate, explicit width/height, lazy loading, and asynchronous decoding.
+- The first project image must not become a render-blocking or hero-critical request.
+- Static and reduced-motion modes do not add automatic video or media behavior.
+- Existing transfer and runtime budgets remain authoritative. Add project images within the existing project-still budget class; do not reduce soundtrack quality to make room.
+
+## 9. Review and acceptance cadence
+
+1. Implement content-model changes and exact public copy with no project media references.
+2. Review and approve prepared media candidates separately from the text.
+3. Attach only approved assets and complete the responsive media layout.
+4. Add metadata and structured-data refinements.
+5. Run focused checks after each independently testable task.
+6. Run one compiled-preview and broad review at the end of the phase.
+7. Perform one medium follow-up only if meaningful high-impact issues remain.
+
+Do not enter repeated taste-review loops. Privacy, factual accuracy, attribution, readability, accessibility, and broken layouts are high-impact. Minor stylistic alternatives are not.
+
+## 10. Out of scope
+
+- Changing or removing canonical page sections.
+- Reworking the Three.js world, journey behavior, route signs, or soundtrack.
+- Publishing the production hostname before explicit approval.
+- Publishing private repositories or operational configuration.
+- Rebuilding private applications solely for portfolio screenshots.
+- Creating new marketing metrics or claiming search rankings.
+- A downloadable CV, contact form, blog, contribution graph, or separate project-detail routes.