diff --git a/docs/superpowers/plans/2026-04-24-experience-timeline.md b/docs/superpowers/plans/2026-04-24-experience-timeline.md deleted file mode 100644 index a56f57e..0000000 --- a/docs/superpowers/plans/2026-04-24-experience-timeline.md +++ /dev/null @@ -1,378 +0,0 @@ -# Experience Timeline 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 the horizontal experience card carousel with a full-width vertical timeline that surfaces job descriptions and sub-phase breakdowns from static data. - -**Architecture:** Extend the `Job` type with optional `summary` and `sub_phases` fields, update `data.ts` with CV content, then replace the card carousel with a new `ExperienceTimeline` server component. No client-side JS — all content is statically rendered. - -**Tech Stack:** Next.js (App Router), TypeScript, TailwindCSS - ---- - -## File Map - -| Action | File | Responsibility | -|--------|------|---------------| -| Modify | `lib/types.ts` | Add `JobSubPhase`, extend `Job` | -| Modify | `lib/data.ts` | Add `summary` + `sub_phases` to WPP and GIAP entries | -| Create | `components/experience-timeline.tsx` | Renders the full vertical timeline (server component) | -| Modify | `components/experience.tsx` | Swap card loop for `` | -| Delete | `components/experience-card.tsx` | Replaced by timeline component | - ---- - -### Task 1: Extend types - -**Files:** -- Modify: `lib/types.ts` - -- [ ] **Step 1: Add `JobSubPhase` and extend `Job`** - -Replace the entire contents of `lib/types.ts` with: - -```typescript -export interface Skill { - name: string; -} - -export interface JobSubPhase { - label: string; - start_date: string; - end_date: string | null; - bullets: string[]; -} - -export interface Job { - id: number; - position: string; - company: string; - still_working: boolean; - start_date: string; - end_date: string | null; - technologies: string[]; - summary?: string; - sub_phases?: JobSubPhase[]; -} - -export interface Project { - id: number; - name: string; - short_description: string; - description: string; - technologies: string[]; - thumbnails: string[]; - category: 'Web' | 'Mobile' | 'Desktop' | 'Api' | 'Game'; - github_url?: string | null; - visit_url?: string | null; - download_url?: string | null; - commercial?: boolean; -} -``` - -- [ ] **Step 2: Verify TypeScript is happy** - -```bash -npx tsc --noEmit -``` - -Expected: no errors (existing job entries have no `sub_phases` yet, so optional fields are fine). - -- [ ] **Step 3: Commit** - -```bash -git add lib/types.ts -git commit -m "feat: add JobSubPhase type and extend Job with summary and sub_phases" -``` - ---- - -### Task 2: Update job data - -**Files:** -- Modify: `lib/data.ts` - -- [ ] **Step 1: Update the WPP entry (id: 8)** - -Replace the WPP job object in the `jobs` array: - -```typescript -{ - id: 8, - position: "Software Engineer", - company: "WPP Media | Choreograph | Wavemaker", - still_working: true, - start_date: "2023-09-13", - end_date: null, - summary: - "Advanced from frontend UI development to backend systems engineering, leading infrastructure-agnostic design initiatives.", - sub_phases: [ - { - label: "Backend & Infrastructure", - start_date: "2025-03-01", - end_date: null, - bullets: [ - "Engineered and optimized backend applications and internal tools using Python, FastAPI, and Django.", - "Streamlined CI/CD workflows and containerized applications with GitLab Pipelines, Docker, and Kubernetes across GCP and Azure environments.", - ], - }, - { - label: "Frontend Architecture", - start_date: "2023-09-13", - end_date: "2025-03-01", - bullets: [ - "Architected scalable microfrontends utilizing Angular and Module Federation, seamlessly integrating standalone internal tools into a unified enterprise shell application.", - "Ensured seamless integration of UI components with Kubernetes-based deployments and Azure Pipelines.", - ], - }, - ], - technologies: [ - "Angular", - "Azure", - "Azure Pipelines", - "Django", - "Docker", - "FastAPI", - "GCP", - "Gitlab CI", - "Gitlab Pipelines", - "Kubernetes", - "PostgreSQL", - "Python", - "React", - "SCSS", - "TailwindCSS", - "Typescript", - ], -}, -``` - -- [ ] **Step 2: Update the GIAP entry (id: 2)** - -Replace the GIAP job object: - -```typescript -{ - id: 2, - position: "Full Stack Developer", - company: "GIAP", - still_working: false, - start_date: "2021-05-19", - end_date: "2023-02-03", - sub_phases: [ - { - label: "Desktop / Backend", - start_date: "2021-05-19", - end_date: "2022-02-01", - bullets: [ - "Architected and optimized complex PostGIS/PostgreSQL cross-database comparison queries utilizing Common Table Expressions (CTEs), drastically reducing execution time from over 5 minutes to under 15 seconds.", - "Developed a robust GIS data assertion module using Python and Qt to automatically validate spatial data against strict compliance standards.", - ], - }, - { - label: "Frontend", - start_date: "2022-02-01", - end_date: "2023-02-03", - bullets: [ - "Engineered a comprehensive, public-facing web application for the City of Gdańsk (geogdansk.pl) leveraging React, TypeScript, Redux, and the ArcGIS JS API.", - ], - }, - ], - technologies: [ - "Python", - "React", - "Typescript", - "PostgreSQL", - "PostGIS", - "ArcGIS JS API", - "Redux", - "Qt", - "QGIS", - "Git", - ], -}, -``` - -- [ ] **Step 3: Verify TypeScript is happy** - -```bash -npx tsc --noEmit -``` - -Expected: no errors. - -- [ ] **Step 4: Commit** - -```bash -git add lib/data.ts -git commit -m "feat: add sub_phases and summary to WPP and GIAP job entries" -``` - ---- - -### Task 3: Create ExperienceTimeline component - -**Files:** -- Create: `components/experience-timeline.tsx` - -- [ ] **Step 1: Create the component** - -```tsx -import { Job } from "@/lib/types"; -import Chip from "./chip"; -import formatDate from "@/utils/format-date"; - -const ExperienceTimeline = ({ jobs }: { jobs: Job[] }) => ( -
-
- {jobs.map((job) => ( -
-
-
-
-
-
-

{job.position}

- - {formatDate(job.start_date)} –{" "} - {job.still_working ? "Present" : formatDate(job.end_date!)} - -
-

{job.company}

- - {job.summary && ( -

{job.summary}

- )} - - {job.sub_phases && job.sub_phases.length > 0 && ( -
- {job.sub_phases.map((phase) => ( -
-
- - {phase.label} - - - {formatDate(phase.start_date)} –{" "} - {phase.end_date ? formatDate(phase.end_date) : "Present"} - -
-
    - {phase.bullets.map((b) => ( -
  • - {b} -
  • - ))} -
-
- ))} -
- )} - -
-

- Technologies -

-
- {job.technologies.map((tech) => ( - - ))} -
-
-
-
- ))} -
-); - -export default ExperienceTimeline; -``` - -- [ ] **Step 2: Verify TypeScript is happy** - -```bash -npx tsc --noEmit -``` - -Expected: no errors. - -- [ ] **Step 3: Commit** - -```bash -git add components/experience-timeline.tsx -git commit -m "feat: add ExperienceTimeline server component" -``` - ---- - -### Task 4: Wire up timeline in Experience section and remove old card - -**Files:** -- Modify: `components/experience.tsx` -- Delete: `components/experience-card.tsx` - -- [ ] **Step 1: Replace `experience.tsx`** - -```tsx -import { Job } from "@/lib/types"; -import ExperienceTimeline from "@/components/experience-timeline"; - -const Experience = ({ jobs }: { jobs: Job[] }) => ( -
-

- Experience -

- -
-); - -export default Experience; -``` - -- [ ] **Step 2: Delete the old card component** - -```bash -rm components/experience-card.tsx -``` - -- [ ] **Step 3: Verify no remaining imports of ExperienceCard** - -```bash -grep -r "experience-card\|ExperienceCard" /mnt/drive/dev/gabrielkaszewski-next --include="*.tsx" --include="*.ts" | grep -v node_modules -``` - -Expected: no output. - -- [ ] **Step 4: Verify TypeScript is happy** - -```bash -npx tsc --noEmit -``` - -Expected: no errors. - -- [ ] **Step 5: Start dev server and visually verify the timeline renders correctly** - -```bash -npm run dev -``` - -Open `http://localhost:3000` and scroll to the Experience section. Verify: -- Vertical line and dots are visible -- WPP shows summary + two sub-phase blocks with bullets -- digimonkeys.com shows only dates + chips (no sub-phases) -- GIAP shows two sub-phase blocks with bullets -- Tech chips render under each entry - -- [ ] **Step 6: Commit** - -```bash -git add components/experience.tsx -git commit -m "feat: replace experience card carousel with vertical timeline" -``` diff --git a/docs/superpowers/plans/2026-04-24-gravity-bug-fixes.md b/docs/superpowers/plans/2026-04-24-gravity-bug-fixes.md deleted file mode 100644 index 2d5305f..0000000 --- a/docs/superpowers/plans/2026-04-24-gravity-bug-fixes.md +++ /dev/null @@ -1,247 +0,0 @@ -# Gravity Engine Bug Fixes 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:** Fix three root-cause bugs in `GravityEngine`: too many physics bodies (lag), Phase 1 layout thrashing (startup freeze), and inline style destruction on restore (image breakage). - -**Architecture:** All three fixes are in `lib/gravity-engine.ts`. The `dirtyElements` field changes from `HTMLElement[]` to `Array<{el, originalCssText}>` so `stop()` restores exact pre-gravity inline styles (fixing image breakage). `start()` is rewritten with a clean read-then-write two-pass approach and a `MAX_DEPTH = 3` depth cap so sections/cards become physics units instead of hundreds of atomic text nodes (fixing lag and startup freeze). No other files change. - -**Tech Stack:** TypeScript, browser DOM APIs - ---- - -## Files - -- **Modify:** `lib/gravity-engine.ts` — only file changed - ---- - -### Task 1: Rewrite `start()` and `stop()` with all three fixes - -**Files:** -- Modify: `lib/gravity-engine.ts` - -- [ ] **Step 1: Read the current file** - -```bash -cat /mnt/drive/dev/gabrielkaszewski-next/lib/gravity-engine.ts -``` - -Confirm it starts with the `PhysicsBody` interface and `GravityEngine` class with `bodies`, `dirtyElements`, `animationFrameId`, `isRunning`, and `INITIAL_FORCE` fields. - -- [ ] **Step 2: Replace the `dirtyElements` field** - -Find this line in `GravityEngine`: -```typescript - private dirtyElements: HTMLElement[] = []; -``` - -Replace with: -```typescript - private dirtyElements: Array<{ el: HTMLElement; originalCssText: string }> = []; -``` - -- [ ] **Step 3: Replace the entire `start()` method** - -Find and replace the full `start()` method with: - -```typescript - start() { - if (this.isRunning) return; - this.isRunning = true; - this.dirtyElements = []; - - const MAX_DEPTH = 3; - const containers = document.querySelectorAll(".gravity-body"); - const leafElements: HTMLElement[] = []; - const intermediates: { el: HTMLElement; w: number; h: number }[] = []; - - // READ-ONLY traversal: classify all elements and snapshot sizes. - // No DOM writes here — one layout flush for the entire traversal. - const collectElements = (el: HTMLElement, depth: number) => { - // Skip purely decorative elements (e.g. background images). - if (el.classList.contains("pointer-events-none")) return; - - const isSolid = ["SVG", "IMG", "BUTTON", "IFRAME", "A"].includes( - el.tagName.toUpperCase(), - ); - - // Treat as a leaf if solid, childless, or at the depth cap. - if (isSolid || el.children.length === 0 || depth >= MAX_DEPTH) { - if (el.offsetWidth > 0 && el.offsetHeight > 0) { - leafElements.push(el); - } - return; - } - - intermediates.push({ el, w: el.offsetWidth, h: el.offsetHeight }); - Array.from(el.children).forEach((child) => - collectElements(child as HTMLElement, depth + 1), - ); - }; - - const rootSizes: { el: HTMLElement; w: number; h: number }[] = []; - containers.forEach((container) => { - const htmlContainer = container as HTMLElement; - rootSizes.push({ - el: htmlContainer, - w: htmlContainer.offsetWidth, - h: htmlContainer.offsetHeight, - }); - Array.from(htmlContainer.children).forEach((child) => - collectElements(child as HTMLElement, 0), - ); - }); - - // WRITE PASS 1: size-lock roots and intermediates so they don't collapse - // when their children become position:fixed. - [...rootSizes, ...intermediates].forEach(({ el, w, h }) => { - this.dirtyElements.push({ el, originalCssText: el.style.cssText }); - el.style.width = `${w}px`; - el.style.height = `${h}px`; - }); - - // READ PASS 2: batch all getBoundingClientRect calls before any leaf writes. - const snapshots = leafElements.map((el) => ({ - el, - rect: el.getBoundingClientRect(), - })); - - // WRITE PASS 2: apply fixed positioning to leaves using snapshotted rects. - this.bodies = snapshots.map(({ el, rect }) => { - this.dirtyElements.push({ el, originalCssText: el.style.cssText }); - el.style.width = `${rect.width}px`; - el.style.height = `${rect.height}px`; - el.style.margin = "0px"; - el.style.position = "fixed"; - el.style.left = "0px"; - el.style.top = "0px"; - el.style.transform = `translate(${rect.left}px, ${rect.top}px)`; - - const body: PhysicsBody = { - el, - x: rect.left, - y: rect.top, - vx: (Math.random() - 0.5) * 8, - vy: (Math.random() - 0.5) * 5, - width: rect.width, - height: rect.height, - originX: rect.left, - originY: rect.top, - isDragging: false, - startX: 0, - startY: 0, - }; - - this.attachMouseEvents(body); - return body; - }); - - this.tick(); - } -``` - -- [ ] **Step 4: Replace the entire `stop()` method** - -Find and replace the full `stop()` method with: - -```typescript - stop() { - this.isRunning = false; - if (this.animationFrameId) { - cancelAnimationFrame(this.animationFrameId); - this.animationFrameId = null; - } - - const DURATION = 600; - - // Glide each body back to its origin position. - this.bodies.forEach((body) => { - body.el.style.transition = `transform ${DURATION}ms cubic-bezier(0.22, 1, 0.36, 1)`; - body.el.style.transform = `translate(${body.originX}px, ${body.originY}px)`; - }); - - // After the transition completes, restore every element's original inline - // styles so document flow is fully recovered — including elements that had - // pre-existing inline styles we must not erase (e.g. Next.js fill images). - const elementsToClear = [...this.dirtyElements]; - this.bodies = []; - this.dirtyElements = []; - - setTimeout(() => { - elementsToClear.forEach(({ el, originalCssText }) => { - el.style.cssText = originalCssText; - }); - }, DURATION); - } -``` - -- [ ] **Step 5: Verify TypeScript compiles with no errors** - -```bash -cd /mnt/drive/dev/gabrielkaszewski-next && npx tsc --noEmit -``` - -Expected: no errors. - -- [ ] **Step 6: Commit** - -```bash -git -C /mnt/drive/dev/gabrielkaszewski-next add lib/gravity-engine.ts -git -C /mnt/drive/dev/gabrielkaszewski-next commit -m "fix: two-pass start, depth cap, cssText restore on stop" -``` - ---- - -### Task 2: Visual verification - -**Files:** none — browser testing only - -- [ ] **Step 1: Start the dev server** - -```bash -cd /mnt/drive/dev/gabrielkaszewski-next && npm run dev -``` - -Open `http://localhost:3000`. - -- [ ] **Step 2: Test home page — activation** - -Click the gravity toggle (bottom-right, yellow). Verify: -- No freeze or stutter at the moment of activation -- Elements fall and bounce without visible lag -- Hero section text/links fall; background image does NOT move (excluded via `pointer-events-none` class) - -- [ ] **Step 3: Test home page — deactivation** - -Click toggle again. Verify: -- Elements glide back with spring curve (~600ms) -- Page returns to its original layout — no broken images, no collapsed sections, no leftover inline styles -- Hero background image is still correctly filling the section - -- [ ] **Step 4: Test projects page** - -Navigate to `http://localhost:3000/projects`. Toggle gravity on/off. Verify: -- Activation is instant (no freeze) -- Animation runs smoothly — project entries fall as chunked units (left panel + image panel), not as hundreds of individual text nodes -- Deactivation restores layout cleanly - -- [ ] **Step 5: Test about page** - -Navigate to `http://localhost:3000/about`. Toggle gravity on/off. Verify: -- Profile photo glides back and renders correctly after restore -- All sections (hobbies, toolkit, FAQ) restore to original layout - -- [ ] **Step 6: Toggle multiple times** - -On each page, toggle gravity on/off 3 times in succession. Verify no accumulated style corruption across cycles. - -- [ ] **Step 7: Commit any fixes found, or mark done** - -If visual issues were found and fixed during testing: -```bash -git -C /mnt/drive/dev/gabrielkaszewski-next add lib/gravity-engine.ts -git -C /mnt/drive/dev/gabrielkaszewski-next commit -m "fix: address visual issues found during gravity verification" -``` - -If no issues found: skip this step. diff --git a/docs/superpowers/plans/2026-04-24-gravity-fix.md b/docs/superpowers/plans/2026-04-24-gravity-fix.md deleted file mode 100644 index a209f94..0000000 --- a/docs/superpowers/plans/2026-04-24-gravity-fix.md +++ /dev/null @@ -1,339 +0,0 @@ -# Gravity Engine Fix 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:** Fix lag and container-blowout bugs in `GravityEngine`, and add a spring-curve glide-back animation when gravity is toggled off. - -**Architecture:** Cache element dimensions at `start()` to eliminate per-frame layout reflow. Track every element whose inline styles are modified in a `dirtyElements` array so `stop()` can fully restore the DOM. On deactivation, animate leaves back to their origin positions via a CSS transition before clearing all styles. - -**Tech Stack:** TypeScript, browser DOM APIs, CSS transitions (`cubic-bezier`) - ---- - -## Files - -- **Modify:** `lib/gravity-engine.ts` — only file changed - ---- - -### Task 1: Extend `PhysicsBody` and add `dirtyElements` to `GravityEngine` - -**Files:** -- Modify: `lib/gravity-engine.ts:1-17` - -- [ ] **Step 1: Replace the `PhysicsBody` interface and class fields** - -Open `lib/gravity-engine.ts`. Replace lines 1–17 with: - -```typescript -interface PhysicsBody { - el: HTMLElement; - x: number; - y: number; - vx: number; - vy: number; - width: number; - height: number; - originX: number; - originY: number; - isDragging: boolean; - startX: number; - startY: number; -} - -export class GravityEngine { - private bodies: PhysicsBody[] = []; - private dirtyElements: HTMLElement[] = []; - private animationFrameId: number | null = null; - private isRunning = false; - - INITIAL_FORCE = 10; -``` - -- [ ] **Step 2: Verify TypeScript compiles** - -```bash -npx tsc --noEmit -``` - -Expected: no errors. - -- [ ] **Step 3: Commit** - -```bash -git add lib/gravity-engine.ts -git commit -m "refactor: extend PhysicsBody with cached dims and add dirtyElements" -``` - ---- - -### Task 2: Refactor `start()` into three ordered phases - -**Files:** -- Modify: `lib/gravity-engine.ts` — the `start()` method - -- [ ] **Step 1: Replace the entire `start()` method** - -Find and replace the `start()` method (currently lines 19–96) with: - -```typescript - start() { - if (this.isRunning) return; - this.isRunning = true; - this.dirtyElements = []; - - const containers = document.querySelectorAll(".gravity-body"); - const leafElements: HTMLElement[] = []; - - // Phase 1 — Size-lock: freeze intermediate container dimensions - // so they don't collapse when children become position:fixed. - const extractLeaves = (el: HTMLElement) => { - const isSolid = ["SVG", "IMG", "BUTTON", "IFRAME", "A"].includes( - el.tagName.toUpperCase(), - ); - - if (isSolid || el.children.length === 0) { - if (el.offsetWidth > 0 && el.offsetHeight > 0) { - leafElements.push(el); - } - return; - } - - el.style.width = `${el.offsetWidth}px`; - el.style.height = `${el.offsetHeight}px`; - this.dirtyElements.push(el); - - Array.from(el.children).forEach((child) => - extractLeaves(child as HTMLElement), - ); - }; - - containers.forEach((container) => { - const htmlContainer = container as HTMLElement; - htmlContainer.style.width = `${htmlContainer.offsetWidth}px`; - htmlContainer.style.height = `${htmlContainer.offsetHeight}px`; - this.dirtyElements.push(htmlContainer); - - Array.from(htmlContainer.children).forEach((child) => - extractLeaves(child as HTMLElement), - ); - }); - - // Phase 2 — Read: batch all getBoundingClientRect() before any writes. - const snapshots = leafElements.map((el) => ({ - el, - rect: el.getBoundingClientRect(), - })); - - // Phase 3 — Write: apply fixed positioning using snapshotted rects. - this.bodies = snapshots.map(({ el, rect }) => { - el.style.width = `${rect.width}px`; - el.style.height = `${rect.height}px`; - el.style.margin = "0px"; - el.style.position = "fixed"; - el.style.left = "0px"; - el.style.top = "0px"; - el.style.transform = `translate(${rect.left}px, ${rect.top}px)`; - this.dirtyElements.push(el); - - const body: PhysicsBody = { - el, - x: rect.left, - y: rect.top, - vx: (Math.random() - 0.5) * 8, - vy: (Math.random() - 0.5) * 5, - width: rect.width, - height: rect.height, - originX: rect.left, - originY: rect.top, - isDragging: false, - startX: 0, - startY: 0, - }; - - this.attachMouseEvents(body); - return body; - }); - - this.tick(); - } -``` - -- [ ] **Step 2: Verify TypeScript compiles** - -```bash -npx tsc --noEmit -``` - -Expected: no errors. - -- [ ] **Step 3: Commit** - -```bash -git add lib/gravity-engine.ts -git commit -m "refactor: split start() into size-lock, read, write phases" -``` - ---- - -### Task 3: Fix `tick()` — use cached dimensions, remove redundant style sets - -**Files:** -- Modify: `lib/gravity-engine.ts` — the `tick` arrow function - -- [ ] **Step 1: Replace the `tick` method** - -Find and replace the `private tick = () => { ... };` block with: - -```typescript - private tick = () => { - if (!this.isRunning) return; - - const gravity = 0.5; - const bounce = -0.7; - const floorY = window.innerHeight; - - this.bodies.forEach((body) => { - if (body.isDragging) { - body.el.style.transform = `translate(${body.x}px, ${body.y}px)`; - return; - } - - body.vy += gravity; - body.x += body.vx; - body.y += body.vy; - - body.vx *= 0.99; - body.vy *= 0.99; - - if (body.y + body.height > floorY) { - body.y = floorY - body.height; - body.vy *= bounce; - body.vx *= 0.9; - } - - body.el.style.transform = `translate(${body.x}px, ${body.y}px)`; - }); - - this.animationFrameId = requestAnimationFrame(this.tick); - }; -``` - -- [ ] **Step 2: Verify TypeScript compiles** - -```bash -npx tsc --noEmit -``` - -Expected: no errors. - -- [ ] **Step 3: Commit** - -```bash -git add lib/gravity-engine.ts -git commit -m "perf: use cached body dimensions in tick, remove redundant style sets" -``` - ---- - -### Task 4: Implement `stop()` with glide-back animation and dirty-set cleanup - -**Files:** -- Modify: `lib/gravity-engine.ts` — the `stop()` method - -- [ ] **Step 1: Replace the `stop()` method** - -Find and replace the entire `stop()` method with: - -```typescript - stop() { - this.isRunning = false; - if (this.animationFrameId) { - cancelAnimationFrame(this.animationFrameId); - this.animationFrameId = null; - } - - const DURATION = 600; - - // Glide each body back to its origin position. - this.bodies.forEach((body) => { - body.el.style.transition = `transform ${DURATION}ms cubic-bezier(0.22, 1, 0.36, 1)`; - body.el.style.transform = `translate(${body.originX}px, ${body.originY}px)`; - }); - - // After the transition completes, clear all inline styles from every - // element we touched so the document flow is fully restored. - const elementsToClear = [...this.dirtyElements]; - this.bodies = []; - this.dirtyElements = []; - - setTimeout(() => { - elementsToClear.forEach((el) => { - el.style.transform = ""; - el.style.transition = ""; - el.style.position = ""; - el.style.left = ""; - el.style.top = ""; - el.style.width = ""; - el.style.height = ""; - el.style.margin = ""; - }); - }, DURATION); - } -``` - -- [ ] **Step 2: Verify TypeScript compiles** - -```bash -npx tsc --noEmit -``` - -Expected: no errors. - -- [ ] **Step 3: Commit** - -```bash -git add lib/gravity-engine.ts -git commit -m "feat: glide-back animation on stop, full dirty-set cleanup" -``` - ---- - -### Task 5: Visual verification - -**Files:** none — browser testing only - -- [ ] **Step 1: Start the dev server** - -```bash -npm run dev -``` - -Open `http://localhost:3000` in a browser. - -- [ ] **Step 2: Test activation** - -Click the gravity toggle button (bottom-right, yellow). Verify: -- Elements fall and bounce with no visible lag -- Page containers do not collapse or change size while gravity is active -- Elements can be dragged and thrown - -- [ ] **Step 3: Test deactivation** - -Click the toggle again. Verify: -- All elements glide back to their original positions with a spring curve (~600ms) -- After the animation, the page looks exactly like it did before gravity was activated (no leftover inline styles, no layout shift) -- Toggling on/off multiple times works correctly each time - -- [ ] **Step 4: Test the about page** - -Navigate to `http://localhost:3000/about`, repeat the activation/deactivation test. The about page also has a `.gravity-body` container with more complex nested layout (flex, prose, grid sections) — verify it also restores cleanly. - -- [ ] **Step 5: Commit if any fixes were needed, otherwise done** - -```bash -git add lib/gravity-engine.ts -git commit -m "fix: address visual issues found during gravity testing" -``` - -(Skip this step if no issues were found.) diff --git a/docs/superpowers/specs/2026-04-24-experience-timeline-design.md b/docs/superpowers/specs/2026-04-24-experience-timeline-design.md deleted file mode 100644 index d0df0be..0000000 --- a/docs/superpowers/specs/2026-04-24-experience-timeline-design.md +++ /dev/null @@ -1,87 +0,0 @@ -# Experience Section — Vertical Timeline Redesign - -## Overview - -Replace the horizontal card carousel in the Experience section with a full-width vertical timeline. The new design surfaces job descriptions and sub-phase breakdowns directly from static data — no JavaScript required. - -## Constraints - -- No client-side JavaScript. All content is statically rendered. -- Data lives in `lib/data.ts`; the `Job` type in `lib/types.ts` is extended to support the new fields. - -## Data Model Changes - -### `lib/types.ts` - -Add `JobSubPhase` and extend `Job`: - -```typescript -export interface JobSubPhase { - label: string; - start_date: string; - end_date: string | null; // null = present - bullets: string[]; -} - -export interface Job { - id: number; - position: string; - company: string; - still_working: boolean; - start_date: string; - end_date: string | null; - technologies: string[]; - summary?: string; // optional one-liner shown below company name - sub_phases?: JobSubPhase[]; // optional; if absent, no bullets shown -} -``` - -### `lib/data.ts` - -Update all three job entries: - -**WPP** — add `summary` + two sub-phases: -- Backend & Infrastructure (Mar 2025 – Present) -- Frontend Architecture (Sep 2023 – Mar 2025) - -**digimonkeys.com** — no changes to content (no summary, no sub_phases). - -**GIAP** — add two sub-phases: -- Desktop / Backend (May 2021 – Feb 2022) -- Frontend (Feb 2022 – Feb 2023) - -Bullet text sourced from CV verbatim (slightly trimmed for display). - -## Component Changes - -### Remove - -- `components/experience-card.tsx` — replaced entirely. - -### Add - -- `components/experience-timeline.tsx` — renders the full timeline list as a server component. Accepts `jobs: Job[]`. Renders each entry as a timeline card with dot, vertical line, header, optional sub-phases, and tech chips. Pure JSX, no `useState`/`useEffect`. - -### Update - -- Wherever the Experience section renders `ExperienceCard` in a scroll container — replace with ``. Remove the horizontal scroll wrapper. - -## Visual Design - -- Vertical connecting line: left-aligned, gradient from accent color to transparent. -- Each entry: glassmorphism card (`bg-white/5`, `border-white/10`, `rounded-2xl`), matching the existing site style. -- Sub-phases rendered as nested cards inside the entry card (`bg-white/[0.03]`, `border-white/[0.07]`, `rounded-xl`). -- Sub-phase header: label in small uppercase accent color + date right-aligned. -- Bullets: `
    ` with `text-sm text-white/60`. -- Tech chips: existing `` component reused. -- Divider line between summary/phases and chips: `border-t border-white/10`. - -## Entries (final) - -| Job | Sub-phases | Bullets | -|-----|-----------|---------| -| WPP (Sep 2023–Present) | Backend & Infra, Frontend Arch | Yes | -| digimonkeys.com (May 2021–Present) | None | No | -| GIAP (May 2021–Feb 2023) | Desktop/Backend, Frontend | Yes | - -Freelance period (Feb 2023–Sep 2023) is intentionally excluded. diff --git a/docs/superpowers/specs/2026-04-24-gravity-fix-design.md b/docs/superpowers/specs/2026-04-24-gravity-fix-design.md deleted file mode 100644 index b802dc9..0000000 --- a/docs/superpowers/specs/2026-04-24-gravity-fix-design.md +++ /dev/null @@ -1,46 +0,0 @@ -# Gravity Engine Fix — Design Spec - -**Date:** 2026-04-24 -**Branch:** gravity -**Files in scope:** `lib/gravity-engine.ts` - -## Problem - -Two bugs in the current `GravityEngine`: - -1. **Lag** — `tick()` calls `el.getBoundingClientRect()` on every body every frame, forcing a layout reflow at 60fps. -2. **Container blowout** — `extractLeaves()` sets `width`/`height` on intermediate nodes, but `stop()` only clears styles on leaf bodies and `.gravity-body` roots. Intermediate nodes keep their locked dimensions, breaking layout on deactivation. - -## Approach — Option A: Cache + dirty-set + CSS transition glide-back - -### Data structures - -`PhysicsBody` gains four new fields: -- `width`, `height` — snapshotted from `getBoundingClientRect()` at `start()`, used in `tick()` for floor collision instead of live DOM queries -- `originX`, `originY` — `rect.left`/`rect.top` at activation time, used as the glide-back transform target - -`GravityEngine` gains: -- `dirtyElements: HTMLElement[]` — every element whose inline styles are touched (leaves, intermediate containers, `.gravity-body` roots). `stop()` iterates this single list to fully wipe styles. - -### `start()` — three strictly ordered phases - -1. **Size-lock phase** — walk `.gravity-body` containers and all descendants via `extractLeaves`. For each intermediate node (non-solid, has children), write `width`/`height` from `offsetWidth`/`offsetHeight` and push to `dirtyElements`. Push `.gravity-body` roots too. -2. **Read phase** — batch all `getBoundingClientRect()` across collected leaf elements before touching any positions. One layout flush, no interleaving. -3. **Write phase** — using snapshotted rects, set each leaf to `position: fixed`, zero `left`/`top`, apply `translate(rect.left, rect.top)`. Cache `width`/`height`/`originX`/`originY`. Push leaf to `dirtyElements`. Attach pointer events. - -### `tick()` — one change - -Replace `el.getBoundingClientRect().height` with `body.height`. Remove redundant `position`/`left`/`top` re-sets inside the dragging branch (set once at start, never change). - -### `stop()` — glide-back then full cleanup - -1. Cancel animation frame, set `isRunning = false`. -2. For each body: apply `transition: transform 0.6s cubic-bezier(0.22, 1, 0.36, 1)` and set `transform: translate(originX, originY)`. -3. After `600ms` (`setTimeout`): iterate `dirtyElements`, clear all inline styles (`transform`, `position`, `left`, `top`, `width`, `height`, `margin`, `transition`). Document flow fully restored. -4. Clear `bodies[]` and `dirtyElements[]`. - -## Success criteria - -- No layout reflow during animation loop -- Toggling gravity off glides all elements back to their original positions with a spring curve -- After glide completes, all intermediate container styles are cleared and layout is indistinguishable from the initial page load