# Portfolio Phase 3: Media, Accessibility, Resilience, and Performance 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:** Replace text-first media slots with cleared authentic project assets and prove that the complete experience meets its accessibility, resilience, responsive, and performance contracts. **Architecture:** A deterministic asset-preparation script re-encodes approved inputs into safe generated filenames and a hash-bound manifest; original files and the private clearance register remain outside version control. Media components load only through the manifest and obey motion/data policies. Browser instrumentation and fault hooks are compiled into test builds only. **Tech Stack:** Node.js, Sharp, ffmpeg, Tesseract.js, ZXing, Vue 3, Vitest, Playwright, axe-core, Lighthouse CLI **Spec:** `docs/superpowers/specs/2026-09-01-portfolio-journey-design.md` ## Global Constraints - Only authentic user-supplied project media and code-generated diagrams/world artwork may ship. - Original filenames, private names, source paths, metadata, browser chrome, account data, QR/barcodes, and title screens never enter distributable output. - Health-related assets use visibly fictional demo values only and require Luca’s manual approval after final re-encoding. - Video uses `preload="none"`, no native inaccessible controls, no autoplay in reduced/static/data-saver modes, and an author-owned play/pause control. - Every chart has a visible caption and adjacent structured description; exact data tables appear when a claim depends on exact values. - Automated accessibility supports but never replaces the recorded WCAG 2.2 A/AA manual checklist. - Release budgets and fault/browser matrix are hard gates; ordinary device memory-pressure observations remain advisory. --- ### Task 1: Build deterministic asset preparation and clearance checks **Files:** - Create: `scripts/prepare-assets.mjs` - Create: `scripts/lib/asset-recipes.mjs` - Create: `scripts/lib/hash-file.mjs` - Create: `scripts/lib/privacy-scan.mjs` - Create: `scripts/lib/video-frames.mjs` - Create: `content/public/asset-recipes.json` - Create: `docs/asset-clearance-template.md` - Create: `tests/fixtures/assets/` - Test: `tests/integration/prepare-assets.test.ts` - Modify: `package.json` **Interfaces:** - Consumes: `--source-root `, approved recipe JSON, and a denylist file descriptor/path supplied only to the scanner process. - Produces: `inputs/prepared-assets/`, updated `content/public/asset-manifest.json`, and a private scan report outside tracked paths. - [ ] **Step 1: Install asset tooling into the exact lockfile** Run: ```bash npm install --save-dev sharp tesseract.js @zxing/library file-type lighthouse ``` Add `prepare:assets` and `verify:assets` scripts that pass explicit paths rather than discover user directories. - [ ] **Step 2: Write failing deterministic-preparation tests** Fixtures cover EXIF removal, generated safe filenames, byte-identical output across two runs, unexpected/extra output rejection, OCR denylist match, QR/barcode rejection, video poster/frame sampling, title-frame exclusion, malformed media, and command failure without leaking matched text. ```ts expect(result.manifest.assets[0].filename).toMatch(/^project-[a-z0-9-]+\.[a-z0-9]+$/); expect(result.reportText).not.toContain(secretCanary); expect(firstOutputHash).toBe(secondOutputHash); ``` - [ ] **Step 3: Define exact public recipes** The recipe file uses only generic asset IDs and generated outputs: ```json { "schema": "portfolio-asset-recipes/v1", "assets": [ { "id": "medication-mobile-overview", "kind": "image", "output": "project-medication-overview.webp", "maxWidth": 720 }, { "id": "medication-mobile-settings", "kind": "image", "output": "project-medication-settings.webp", "maxWidth": 720 }, { "id": "footy-detection-frame", "kind": "image", "output": "project-footy-detection.webp", "maxWidth": 1600 }, { "id": "opinion-polarization-chart", "kind": "image", "output": "project-opinion-polarization.webp", "maxWidth": 1000 }, { "id": "private-game-loop", "kind": "video", "output": "project-private-game-loop.webm", "startSeconds": 3, "durationSeconds": 6 }, { "id": "healthcare-interface", "kind": "image", "output": "project-healthcare-interface.webp", "maxWidth": 1440 }, { "id": "currency-converter-interface", "kind": "image", "output": "project-currency-converter.webp", "maxWidth": 1440 } ] } ``` Additional opinion charts follow the same `opinion--chart` naming. Source filenames/paths are supplied through an untracked private mapping and never copied into the recipe or manifest. - [ ] **Step 4: Implement safe image/video preparation** Images are decoded, resized without enlargement, flattened where needed, and re-encoded as WebP/PNG with metadata omitted. Video is decoded by ffmpeg in a resource-limited subprocess, cropped to approved gameplay time, stripped of audio/unexpected tracks unless separately cleared, scaled to the approved cap, encoded as VP9 WebM plus H.264 MP4 fallback, and given a cleared poster. Subprocess arguments are arrays, never shell-concatenated strings. - [ ] **Step 5: Implement final-output scanning** Run OCR and QR/barcode detection against every final image/poster and sampled video frames; run byte/text/metadata checks against every output and decompressed test container. Failure output contains only opaque rule ID and coordinate/frame. Unsupported/encrypted/malformed input fails closed. - [ ] **Step 6: Require manual final review** Generate a contact sheet for Luca showing every final derivative at full readable scale plus representative/all-changing video frames. Record approval in an untracked private clearance register; only approved hashes may be copied to `inputs/prepared-assets` and the public manifest. - [ ] **Step 7: Run and commit only safe outputs/code** ```bash npm run test:unit -- tests/integration/prepare-assets.test.ts npm run prepare:assets -- --source-root /absolute/private/source --mapping /absolute/private/mapping.json --denylist /absolute/private/denylist.txt git add scripts content/public/asset-recipes.json content/public/asset-manifest.json inputs/prepared-assets docs/asset-clearance-template.md tests package.json package-lock.json git commit -m "feat: add deterministic cleared project media" ``` Before committing, `git diff --cached --name-only` must contain no original source name or private path. --- ### Task 2: Integrate project media, chart descriptions, and controlled video **Files:** - Create: `src/components/ProjectMedia.vue` - Create: `src/components/AccessibleVideo.vue` - Create: `src/components/ChartFigure.vue` - Create: `src/composables/useLazyMedia.ts` - Modify: `src/components/ProjectCaseStudy.vue` - Modify: `src/components/ProjectCard.vue` - Modify: `content/public/portfolio-content.json` - Test: `tests/unit/ProjectMedia.test.ts` - Test: `tests/browser/media-policy.spec.ts` **Interfaces:** - Consumes: approved asset IDs/copy, public manifest, motion policy. - Produces: intrinsic-size figures, author-controlled video, lazy/detached media lifecycle, accessible fallbacks. - [ ] **Step 1: Write failing component policy tests** Assert manifest-only URLs, contextual alt from content, figure captions, chart descriptions, no native video controls, `preload="none"`, detached sources outside the threshold, and explicit Play behavior in reduced/static/data-saver modes. - [ ] **Step 2: Implement manifest-bound media resolution** ```ts export interface MediaPresentation { assetId: string; alt: string; caption?: string; longDescription?: string; } ``` `ProjectMedia` rejects unknown IDs and never reads contextual wording from the manifest. - [ ] **Step 3: Implement author-owned video controls** `AccessibleVideo` uses a real Play/Pause button, visible state, poster, caption/description, time-preserving detach on offscreen pause, and failure text. It attaches sources within 300 CSS pixels only when automatic media policy permits; otherwise explicit Play attaches them. - [ ] **Step 4: Implement chart figures** Each chart renders `
`, visible `
`, structured variables/trend/conclusion text, and a table only when exact values support the claim. Color never carries the conclusion alone. - [ ] **Step 5: Verify and commit** ```bash npm run test:unit -- tests/unit/ProjectMedia.test.ts npm run test:browser -- tests/browser/media-policy.spec.ts git add src/components src/composables/useLazyMedia.ts content/public/portfolio-content.json tests git commit -m "feat: present cleared accessible project media" ``` --- ### Task 3: Enforce WCAG 2.2 A/AA and screen-reader workflows **Files:** - Create: `tests/browser/accessibility-axe.spec.ts` - Create: `tests/browser/accessibility-keyboard.spec.ts` - Create: `tests/browser/accessibility-reflow.spec.ts` - Create: `tests/browser/accessibility-forced-colors.spec.ts` - Create: `docs/accessibility/wcag-2.2-aa-checklist.md` - Create: `docs/accessibility/screen-reader-scripts.md` - Modify: `src/styles/base.css` - Modify: `src/styles/components.css` - Modify: `src/styles/world.css` **Interfaces:** - Consumes: all default/audio/static/reduced/media-failure/missing-social states. - Produces: automated reports plus manual NVDA/Firefox and VoiceOver/Safari evidence. - [ ] **Step 1: Write automated axe/state tests** ```ts for (const state of ['default', 'audio-running', 'static', 'reduced-motion', 'media-failure', 'missing-social']) { test(`${state} has no axe violations`, async ({ page }) => { await enterState(page, state); expect((await new AxeBuilder({ page }).analyze()).violations).toEqual([]); }); } ``` - [ ] **Step 2: Test keyboard, focus, and target geometry** Enumerate every enabled author-owned pointer target and require 44×44 CSS pixels except marked inline sentence links. Verify focus is visible, inside safe bounds, and not overlapped by fixed HUD/controls. Keyboard traversal reaches Contact in logical order. - [ ] **Step 3: Test reflow and user styles** Run at 320 CSS pixels and desktop 400% zoom/effective 320 width. Apply WCAG text-spacing overrides and forced colors; require no lost/clipped content, control, focus ring, or Contact action. - [ ] **Step 4: Execute named screen-reader scripts** Record versions/results for NVDA+Firefox on Windows and VoiceOver+Safari on macOS/iOS. Follow the seven literal tasks in the spec, including scene description, navigation focus, audio controls, Toolkit labels, chart descriptions, video controls, and contact links. - [ ] **Step 5: Resolve every applicable criterion and commit evidence** ```bash npm run test:browser -- tests/browser/accessibility-axe.spec.ts tests/browser/accessibility-keyboard.spec.ts tests/browser/accessibility-reflow.spec.ts tests/browser/accessibility-forced-colors.spec.ts git add tests/browser/accessibility-* docs/accessibility src/styles git commit -m "test: enforce accessible portfolio interaction" ``` No automated violation remains open; false positives require evidence in the checklist. --- ### Task 4: Implement deterministic fault injection and browser coverage **Files:** - Create: `src/testing/fault-hooks.ts` - Create: `tests/browser/fault-matrix.spec.ts` - Create: `tests/browser/context-loss.spec.ts` - Create: `tests/browser/audio-interruption.spec.ts` - Create: `tests/browser/media-failure.spec.ts` - Create: `tests/helpers/test-build-auth.ts` - Modify: `vite.config.ts` - Modify: `playwright.config.ts` **Interfaces:** - Consumes: authenticated test-build secret generated per run. - Produces: F1–F13 fault hooks only in test builds and cross-engine failure evidence. - [ ] **Step 1: Write failing absence/authentication tests** Production bundles must not contain `__PORTFOLIO_TEST_HOOKS__`. A test build rejects missing/wrong ephemeral token. Correct token exposes only named deterministic failures and state snapshots. - [ ] **Step 2: Implement compile-time-eliminated hooks** Hooks cover scene chunk, shader, model, texture, poster, video, hydration, pre/post-first-frame exception, context loss/restore, audio start rejection, and audio interruption. They cannot mutate content, URLs, approval data, or production configuration. - [ ] **Step 3: Implement exact failure oracles** Every case asserts H1, sections, anchors, contact, private-link omissions, focus behavior, one static fallback, no duplicate resources, and no unhandled exception/rejection. - [ ] **Step 4: Expand the executable browser matrix** Configure current Chromium/Firefox/WebKit/Edge equivalents and the previous stable majors available in CI containers. Record physical iOS Safari and Pixel Chromium separately; do not silently replace them with emulation. - [ ] **Step 5: Verify and commit** ```bash npm run build:preview npm run test:browser -- tests/browser/fault-matrix.spec.ts tests/browser/context-loss.spec.ts tests/browser/audio-interruption.spec.ts tests/browser/media-failure.spec.ts git add src/testing tests playwright.config.ts vite.config.ts git commit -m "test: add deterministic resilience matrix" ``` --- ### Task 5: Add performance instrumentation and enforce budgets **Files:** - Create: `src/testing/performance-instrumentation.ts` - Create: `tests/performance/drive-trace.spec.ts` - Create: `tests/performance/physical-drive.protocol.ts` - Create: `tests/performance/budgets.test.ts` - Create: `scripts/check-bundle-budgets.mjs` - Create: `docs/performance/lab-runbook.md` - Modify: `package.json` - Modify: `vite.config.ts` **Interfaces:** - Consumes: instrumented test build, physical reference devices, pinned network profiles. - Produces: five-run lab JSON, bundle report, physical protocol log, and pass/fail summary. - [ ] **Step 1: Write unit tests for metric calculations** Cover nearest-rank p95, LCP cutoff, CLS windows, pre-running TBT, post-running long tasks, event-to-next-paint eight-action oracle, scene continuity, lost-presentation share, and zero unexpected late shifts. - [ ] **Step 2: Implement test-only instrumentation** Use PerformanceObserver and marks around complete scene update+render. Strip instrumentation from production with a compile-time constant. Never collect GPU/audio identity or export telemetry. - [ ] **Step 3: Implement `drive-trace.v1` exactly** Use the offsets/actions in the spec, trusted CDP input, pinned High/Low tiers, five fresh profiles per reference class, precise timeouts, and the deterministic navigation-settlement barrier. Write one result JSON per run plus aggregation. - [ ] **Step 4: Implement `physical-drive.v1` as a protocol module** Generate the exact W3C `driveTouch`/`driveKeys` actions, safe-viewport coordinates, cycle direction, excluded 389/390-second cycles, terminal-Static branches, rotations, backgrounding, interruption, context loss, and Contact finish. The module outputs actions and assertions; platform runners consume it without changing timing. - [ ] **Step 5: Enforce transfer/runtime budgets** Read the exact byte/frame/bundle thresholds from §14 into named constants and fail the build report on any violation. Audit dynamic chunk separation so reduced motion downloads no Three.js/live-scene asset. - [ ] **Step 6: Run lab gates and commit tooling/results** ```bash npm run build:preview node scripts/check-bundle-budgets.mjs dist npx playwright test tests/performance/drive-trace.spec.ts git add src/testing tests/performance scripts/check-bundle-budgets.mjs docs/performance package.json vite.config.ts git commit -m "perf: enforce journey budgets and reference traces" ``` Physical-device result files are committed only after device metadata is reviewed for privacy. --- ### Task 6: Complete visual QA, flash analysis, and audience validation **Files:** - Create: `tests/browser/visual-regression.spec.ts` - Create: `docs/accessibility/flash-analysis.md` - Create: `docs/reviews/audience-validation.md` - Create: `docs/reviews/visual-qa.md` - Modify: scene/style/component files identified by the captured diffs **Interfaces:** - Consumes: final Phase 3 captures across viewports/states. - Produces: approved visual baseline, flash evidence, and three-person audience results. - [ ] **Step 1: Capture every required visual state** Capture desktop widths 1280/1440/1920/2560, tablet 768, mobile portrait/landscape, reduced motion, Static, audio-running, media failure, missing social, and Contact arrival. Use deterministic journey/test state but production rendering code. - [ ] **Step 2: Review composition against the product direction** Check readable near-black panels, restrained cyan/amber, no content scanlines/glow, original car silhouette, stable chase composition, no AI/generated project art, balanced systems/software weight, and restrained mobile world density. - [ ] **Step 3: Run flash analysis** Analyze final scene/video captures in every current engine. Record that no content exceeds the three-flashes threshold and resolve any failure by reducing/removing the effect. - [ ] **Step 4: Run the evidence-first audience check** Three independent hiring/technical reviewers use Static or reduced motion. Record 30-second identity/role/contact and 60-second systems/software proof results plus professionalism/evidence-balance ratings. Revise content hierarchy if thresholds fail. - [ ] **Step 5: Run the complete Phase 3 gate and commit** ```bash npm run typecheck npm run test:unit npm run build:preview npm run test:browser node scripts/check-bundle-budgets.mjs dist git add tests/browser/visual-regression.spec.ts docs/accessibility docs/reviews src git commit -m "test: approve portfolio quality and audience evidence" ``` ## Phase 3 completion checkpoint The build is a launch candidate only after cleared final assets, WCAG records, cross-engine fault results, performance budgets, physical-device evidence, flash analysis, and audience thresholds all pass without exceptions hidden in CI logs.