From 42435e97608f56bca1447cc9b1d4160a3acdb99f Mon Sep 17 00:00:00 2001 From: Nima <74723240+nimkimi@users.noreply.github.com> Date: Thu, 20 Aug 2026 11:52:12 +0200 Subject: [PATCH 01/11] Remove the Playground section The live component demos read as filler next to the case studies. Drops the homepage section, the nav item, the three demo components, and their tests; the arrival e2e now uses the contact heading as its below-fold probe. --- e2e/arrival-motion.spec.ts | 9 +- src/app/page.tsx | 6 - src/components/layout/SiteNav.tsx | 1 - src/components/motion/SmoothScroll.tsx | 2 +- src/components/playground/CopyChip.tsx | 93 -------- .../playground/PlaygroundSection.tsx | 50 ----- .../playground/SegmentedControl.tsx | 88 -------- src/components/playground/SpringSlider.tsx | 128 ----------- tests/components/PlaygroundSection.test.tsx | 49 ----- tests/components/SiteNav.test.tsx | 6 +- tests/components/playground.test.tsx | 207 ------------------ tests/integration/pages.test.tsx | 4 +- 12 files changed, 10 insertions(+), 633 deletions(-) delete mode 100644 src/components/playground/CopyChip.tsx delete mode 100644 src/components/playground/PlaygroundSection.tsx delete mode 100644 src/components/playground/SegmentedControl.tsx delete mode 100644 src/components/playground/SpringSlider.tsx delete mode 100644 tests/components/PlaygroundSection.test.tsx delete mode 100644 tests/components/playground.test.tsx diff --git a/e2e/arrival-motion.spec.ts b/e2e/arrival-motion.spec.ts index 965704d..bdd97b9 100644 --- a/e2e/arrival-motion.spec.ts +++ b/e2e/arrival-motion.spec.ts @@ -10,7 +10,7 @@ import { test, expect } from '@playwright/test'; * reduced motion the stylesheet hides it before first paint and the unmount * follows on a microtask. * - Hero

renders "Nima Hakimi" (the period rides on the same line). - * - "Selected Work" / "Playground" / "Contact" headings live further down, + * - "Selected Work" / "Contact" headings live further down, * each gated by a masked scroll Reveal (inline transform: translateY(...)). */ @@ -86,12 +86,7 @@ test.describe('scroll reveal', () => { // Let the arrival settle and the hero appear. await expect(page.getByRole('heading', { name: 'Nima Hakimi' })).toBeVisible({ timeout: 3000 }); - // Playground heading lives well below the fold behind a Reveal. - const playground = page.getByRole('heading', { name: /playground/i }); - await playground.scrollIntoViewIfNeeded(); - await expect(playground).toBeVisible({ timeout: 3000 }); - - // Contact closing moment, even further down. + // Contact closing moment, well below the fold behind a Reveal. const contact = page.getByRole('heading', { name: /open to frontend roles/i, }); diff --git a/src/app/page.tsx b/src/app/page.tsx index 428b832..98a4ffe 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -3,7 +3,6 @@ import Reveal from '@/components/motion/Reveal'; import MagneticButton from '@/components/motion/MagneticButton'; import UnderlineLink from '@/components/motion/UnderlineLink'; import WorkRow from '@/components/work/WorkRow'; -import PlaygroundSection from '@/components/playground/PlaygroundSection'; import work from '@/data/work'; import { SITE_EMAIL, SITE_SOCIAL_LINKS } from '@/lib/site'; @@ -85,11 +84,6 @@ export default function Home() { - {/* Playground — live interactive component demos */} -
- -
- {/* Contact — big-type closing moment */}
diff --git a/src/components/layout/SiteNav.tsx b/src/components/layout/SiteNav.tsx index f0c0516..dc79ae7 100644 --- a/src/components/layout/SiteNav.tsx +++ b/src/components/layout/SiteNav.tsx @@ -12,7 +12,6 @@ type NavItem = { label: string; href: string }; const NAV_ITEMS: NavItem[] = [ { label: 'Work', href: '/work' }, { label: 'About', href: '/about' }, - { label: 'Playground', href: '/#playground' }, { label: 'Contact', href: '/contact' }, ]; diff --git a/src/components/motion/SmoothScroll.tsx b/src/components/motion/SmoothScroll.tsx index c3a8cc7..f800428 100644 --- a/src/components/motion/SmoothScroll.tsx +++ b/src/components/motion/SmoothScroll.tsx @@ -24,7 +24,7 @@ export default function SmoothScroll({ children }: { children: React.ReactNode } id = requestAnimationFrame(raf); }); - // Smooth in-page anchor navigation (`/#work`, `#playground`, `#contact`). + // Smooth in-page anchor navigation (`/#work`, `#contact`). // We intercept clicks on links whose target is a same-page hash and hand // the scroll to Lenis. Anything that isn't a resolvable in-page target is // left to the browser (native jump still works, the CSS import above fixes diff --git a/src/components/playground/CopyChip.tsx b/src/components/playground/CopyChip.tsx deleted file mode 100644 index 8954863..0000000 --- a/src/components/playground/CopyChip.tsx +++ /dev/null @@ -1,93 +0,0 @@ -'use client'; - -import { useEffect, useRef, useState } from 'react'; -import { AnimatePresence, motion, useReducedMotion } from 'motion/react'; - -const VALUE = 'hakimi.nima1@gmail.com'; - -/** - * A copy-to-clipboard chip. Clicking copies the value and swaps the label to - * "COPIED" with a tick for 1.5s, then reverts. The swap is a cross-fade that - * collapses to an instant cut under reduced motion. `aria-live` announces the - * state change to screen readers; the button stays a single focusable target. - */ -export default function CopyChip() { - const reduce = useReducedMotion(); - const [copied, setCopied] = useState(false); - const timer = useRef | null>(null); - - useEffect( - () => () => { - if (timer.current) clearTimeout(timer.current); - }, - [] - ); - - const copy = async () => { - try { - await navigator.clipboard?.writeText(VALUE); - } catch { - // Clipboard may be blocked (e.g. insecure context); still flash feedback. - } - setCopied(true); - if (timer.current) clearTimeout(timer.current); - timer.current = setTimeout(() => setCopied(false), 1500); - }; - - const swap = reduce - ? { initial: false, animate: {}, exit: {} } - : { - initial: { y: 8, opacity: 0 }, - animate: { y: 0, opacity: 1 }, - exit: { y: -8, opacity: 0 }, - }; - - return ( - - ); -} diff --git a/src/components/playground/PlaygroundSection.tsx b/src/components/playground/PlaygroundSection.tsx deleted file mode 100644 index 89f345f..0000000 --- a/src/components/playground/PlaygroundSection.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import Reveal from '@/components/motion/Reveal'; -import SegmentedControl from './SegmentedControl'; -import SpringSlider from './SpringSlider'; -import CopyChip from './CopyChip'; - -type Demo = { - caption: string; - node: React.ReactNode; -}; - -const demos: Demo[] = [ - { caption: 'Segmented control', node: }, - { caption: 'Spring slider', node: }, - { caption: 'Copy chip', node: }, -]; - -/** - * Frames a few genuinely interactive component demos — the "I sweat the - * details" proof. Hairline separation, no heavy cards. Each demo is a - * self-contained client island wrapped in a staggered Reveal. - */ -export default function PlaygroundSection() { - return ( - <> - -

Playground

-
- -

- Small interactions, built to feel right. -

-
- -
- {demos.map((demo, i) => ( - -
-
{demo.node}
-

{demo.caption}

-
-
- ))} -
- - ); -} diff --git a/src/components/playground/SegmentedControl.tsx b/src/components/playground/SegmentedControl.tsx deleted file mode 100644 index a5ee593..0000000 --- a/src/components/playground/SegmentedControl.tsx +++ /dev/null @@ -1,88 +0,0 @@ -'use client'; - -import { useId, useRef, useState } from 'react'; -import { motion, useReducedMotion } from 'motion/react'; - -const OPTIONS = ['Design', 'Build', 'Ship'] as const; -type Option = (typeof OPTIONS)[number]; - -/** - * A segmented control with a single acid-lime indicator that slides between - * options. The indicator uses a shared `layoutId` so it physically travels - * (springs) from one segment to the next; under reduced motion it jumps. - * - * Keyboard: roving tabindex — Left/Right (and Up/Down) move the selection, - * Home/End jump to the ends. Implemented as an ARIA radiogroup. - */ -export default function SegmentedControl() { - const reduce = useReducedMotion(); - const [active, setActive] = useState
@@ -451,7 +438,6 @@

Programmatisk

Prose er forslag. Kode er garantier.
- @@ -481,7 +467,6 @@

AGENTS.md: alltid i kontekst

Leses automatisk hver session, fra git root og cwd · path-spesifikke regler: .github/instructions/*.instructions.md med applyTo-glob · leser også CLAUDE.md

- @@ -505,7 +490,6 @@

Skills: kunnskap on demand

  • Samme gate: custom agents (.agent.md, subagent med eget context window og begrensede tools)
  • - @@ -549,7 +533,6 @@

    Hvorfor hooks, og ikke bare prose?

    permissionRequest
    - @@ -597,7 +580,6 @@

    Sikkerhet

    debug: /env viser lastede hooks · stdout → ~/.copilot/logs/ · lastes ved session-start · Windows: egne bash- og powershell-nøkler

    - @@ -635,7 +617,6 @@

    Fra min egen verktøykasse

    - @@ -660,7 +641,6 @@

    Sensors fanger opp etter
    Dårlig output er ofte en manglende sensor.
    - @@ -676,7 +656,6 @@

    Oppsummert

    Åpen standard, aug 2026: Agent Plugins 1.0 · AWS · Anysphere · Microsoft · OpenAI · Vercel

    agent = modell + harness
    Spørsmål?
    - @@ -684,7 +663,6 @@

    Oppsummert

    -