refactor: specimen-sheet portfolio rebuild — authentic, verified, minimal - #8
Merged
Conversation
…l telemetry) True from-scratch rebuild toward the immersive scenario edition. - Quarantine legacy rendered/logic layer into legacy/ (excluded from tsconfig + ESLint); content preserved for per-phase porting. - Design System v3 (dark industrial telemetry): CRT substrate, white-phosphor fg, four per-scenario accent tokens (red-amber/blue/violet/teal), 2px radius ceiling, 1px hairline system, precision-grid + mono-label utilities. - Fonts Geist -> Inter + JetBrains Mono (doc-mandated two-typeface system). - Add PRODUCT.md + DESIGN.md (impeccable-init equivalent) locking audience, brand lane, palette, typefaces, motion law. - lint-staged: add --no-warn-ignored so staged legacy/ files don't fail the hook. - Minimal server-rendered Phase 1 skeleton page; build passes (static prerender, 113 kB First Load JS), typecheck + lint clean. Direction set with taste-skill brutalist (Tactical Telemetry) lens; three deliberate overrides where 03_UI_UX.md wins (4 accents, 2px radius, restraint). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Sticky navbar with IntersectionObserver scroll-spy and per-scenario active highlight (App Flow: nav reflects which of the 4 scenarios is in view, not a lump "Scenarios"); condensed desktop nav + full mobile panel; resume download. - Hero: fully server-rendered/crawlable copy with a reduced-motion-aware entrance (Reveal honors prefers-reduced-motion at the call site); primary CTA targets the first scenario anchor; résumé download; focus-area telemetry row. - Footer: contact affordances, social links, back-to-top, telemetry framing. - Skip-to-content link; global scroll-padding-top for the sticky-nav offset. - Data/types layer: Profile + ScenarioMeta (nav meta only), navigation contract (navLeaves single source of anchor ids), accent scope helpers, data accessors. - Industrial Button primitive (buttonVariants, no Slot dep). - A11y: full palette + all 4 accents WCAG-AA verified via culori; routed real small text to fg-muted, nudged fg-dim #5A5A60 -> #71717A (4.09:1) for decorative separators. Verified: typecheck + lint clean; build passes (static, 167 kB First Load); SSR DOM contains hero copy, all 13 anchors, nav, skip link, résumé — no hydration warnings. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… (both tiers) The de-risking gate. Builds SC-01 end-to-end across both render tiers from one content object, and proves the two-tier model holds. Capability + data: - lib/capability: WebGL2 / deviceMemory-hardwareConcurrency / reduced-motion / <768px detection, decided once and session-cached; SSR-safe fallback default (no hydration mismatch). prefers-reduced-motion and mobile always force fallback. - Full Scenario type (Backend Schema); Offensive Security scenario data (recon -> exploit -> report beats, "trace" interactive moment, network-topology environment, non-null fallback with 1:1 animationBeats); projects + skills. - Build-time integrity gate (lib/scenarios): throws on parity/reference violations at import -> fails `next build`. Rendering (components/scenarios): - Fallback tier (design-first): 2D SVG network topology, Framer Motion, reacts to active beat + trace, reduced-motion-aware at the call site. - Full tier: R3F procedural topology, GSAP ScrollTrigger scroll-scrubbed camera, subtle bloom; dynamically imported (ssr:false) so the ~1.2 MB three.js chunk is code-split out of the initial load. - Stage orchestrator: tier selection, IntersectionObserver beat tracking, keyboard-accessible interactive trace; Suspense falls back to the 2D visual while the 3D chunk loads (no blank state). Both tiers consume identical data. - Server section wrapper renders unit/title/summary in SSR DOM always. Gate evidence: typecheck + lint clean; build static-prerenders; SSR DOM contains all 3 beats + summary + payoff + interactive trigger (content parity); three.js absent from initial HTML and its 1.2 MB chunk is lazy (fallback tier stays 172 kB First Load). Automated Lighthouse deferred to Phase 6 (no CLI in env). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The hero <h1> is the LCP element; animating it from opacity:0 kept it invisible until hydration, delaying Largest Contentful Paint. Add a transform-only "slide" mode to Reveal (opacity stays 1) and use it for all above-the-fold hero content. Prod Lighthouse (mobile): LCP 7.1s -> 2.8s, FCP 0.9s, Speed Index 0.9s. SSR HTML now emits zero opacity:0 inline styles. Transforms don't affect layout, so no CLS. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- About: server-rendered bio, pull quote, focus areas. - Journey: vertical timeline of the VAPT -> GRC & AI Security arc (sorted ascending), per-item reveal, semantic <time>. - Skills: narrative-grouped catalogue (grouping is the signal, no fabricated scores). Expanded flat Skill[] across all categories — offensive ids stay intact for the SC-01 integrity gate and Phase 4 scenarios reference the rest. - Data: TimelineEvent + SkillGroup types; data/timeline.ts; skillGroups view model. - Page now renders About/Journey/Skills in App Flow order; scenarios 2–4 and certifications/tryhackme/github/blog/contact remain wired placeholders. Verified: typecheck + lint clean; build static; SSR DOM contains all three sections' content. A11y: routed the journey category label off fg-dim (4.09:1) to fg-muted (5.77:1) for AA on small text. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Scenario-visual registry: stage now resolves Fallback + code-split Scene by scenario id (decoupled from the offensive scenario). - SC-02 data: audit-ledger environment, blue accent, capture/chain/verify beats, "trace the hash chain" interactive moment, Dharma payoff, 1:1 fallback beats (integrity gate passes). - Fallback tier: 2D hash-chained ledger (SVG), reduced-motion-aware verify sweep. - Full tier: descending 3D ledger blocks, GSAP scroll-scrubbed camera pan, verification pulse on trace; code-split (three.js absent from initial HTML). Verified: typecheck + lint clean; build static; SSR DOM has all 3 beats + summary + payoff + trigger; GRC scene chunk is separate. Blue accent AA-verified (7.09:1). Awaiting 3D spot-check before SC-03. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- GRC camera: pulled back to z~8.6 with a 4th keyframe so the full chain frames without clipping, gentle S-curve descent. - Both scenes: GSAP scrub true -> 0.8 (inertial catch-up) and eased look-at target (lerp), so orientation no longer snaps. Noticeably smoother scrubbing. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- SC-03 data: encrypted-vault environment, violet accent, encrypt/classify/ zero-knowledge beats, "reveal an encrypted field" interactive moment (type reveal), VaultIQ payoff, 1:1 fallback beats (integrity gate passes). - Fallback tier: 2D grid of encrypted panels (SVG) — ciphertext -> plaintext on reveal, lock indicator, reduced-motion-aware crossfade. - Full tier: 3D vault grid, panels light per beat + lock bar dims on reveal, GSAP scrub 0.8 + eased look-at; code-split (three.js absent from initial HTML). Verified: typecheck + lint clean; build static; SSR DOM has all 3 beats + summary + payoff + trigger; 3 scenario anchors. Violet accent AA (6.29:1). Awaiting 3D spot-check before SC-04. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Scenario schema: payoffProjectId nullable + optional payoffSummary, so a cert/capability-backed scenario (App Flow: SC-04 payoff is AWS-cert-backed skills, not a project) is first-class. Integrity gate updated accordingly. - SC-04 data: cloud-architecture environment, teal accent, containerize/deploy/ secure beats, "inspect the security layer" moment (type inspect), capability payoff, 1:1 fallback beats. - Fallback tier: 2D architecture diagram (SVG) with inspect callout on the IAM node; reduced-motion safe. - Full tier: 3D service graph, nodes/edges light per beat, inspect focuses IAM (scale + rotating ring), GSAP scrub 0.8 + eased look-at; code-split. - Stage renders a capability payoff (summary + beat skills) when no project. - A11y: techStack chips fg-dim -> fg-muted (AA on small text) across payoffs. Verified: typecheck + lint clean; build static; SSR has all beats + capability payoff; 4 scenario anchors; three.js lazy. Teal AA (10.63:1). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Certificate type; data/certificates.ts + data/tryhackme.ts (ported), sorted newest-first; shared lib/format.ts date helper. - Certifications: grid of credential cards (issuer/title/date, verify link when present). - TryHackMe: headline stats (Top 2% · 175+ labs · paths) + completed learning paths + profile link. Completes the scenario spine + credentials. Remaining placeholders: GitHub, Blog, Contact (Phase 5). Verified: typecheck + lint clean; build static; both sections in SSR DOM. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Contact: hardened API route (server-side Zod validation, HTML/control-char sanitization, escapeHtml email body, honeypot + timing trap, IP rate-limit 3/hr, Resend delivery). Fixed legacy bug: sends to owner email, not the site hostname. 503 when RESEND_API_KEY absent (no silent pretend-success). Client form uses native validation + server error display — no zod/RHF in the client bundle (First Load 255kB -> 177kB). - GitHub: server component fetches project repos (ISR 1h, GITHUB_TOKEN optional), graceful fallback to static data when API/repo unavailable. - SEO: full metadata (title/desc/canonical/OG/Twitter), JSON-LD Person + WebSite (scenarios as parts), dynamic OG image (next/og), sitemap, robots — all in the server-rendered DOM. Dropped Blog (unscoped by the plan) from nav. - CSP: enforcing in production, unsafe-eval dropped (dev-only for HMR), narrowly scoped; unsafe-inline retained for Next's static hydration scripts (documented tradeoff vs nonce-based dynamic rendering). env module + rate-limit + github client re-derived and re-reviewed. Verified: typecheck + lint clean; build static (home 177kB, ISR 1h); SSR has contact + github + JSON-LD; robots/sitemap/og-image 200; contact API validation + honeypot + rate-limit exercised. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The 3D scenes rendered geometry with no text, so full-tier viewers couldn't tell what each node/block/panel/service was — only the 2D fallback showed labels. - Add drei <Html> labels to all four 3D scenes (SceneLabel), using the site mono font (no font asset, CSP-safe), colored by active/accent state. SC-03 labels even reflect the ciphertext->plaintext reveal. - Fix SC-04 2D fallback: reposition data/iam nodes to fit the viewport without overlap, shrink node label font so "ECS · Docker" etc. no longer clip. Content parity intact (labels already in SSR DOM via fallback + beats). Verified: typecheck + lint clean; build static. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… overlap - SC-02 ledger: 3D tag now uses a concise, meaningful `short` label (e.g. "Access policy", "Audit verified") instead of "#a1f3 policy.access.updated". 2D fallback keeps the log-style dotted names. - SC-03 vault: 3D tag shows the field name (doc.title -> Q3 Financials on reveal) instead of "doc.title · 8a3f9c2e" — much shorter. - SceneLabel: smaller (10px), with a translucent background chip + border so crowded labels stay individually readable. Verified: typecheck + lint clean; build static. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ccents) - profile.name -> "Hem Nikesh Gabhawala" (propagates to hero, footer, metadata, title, JSON-LD Person, OG alt). - "Résumé"/"résumé" -> "Resume"/"Download resume" in hero + navbar (desktop + mobile). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replaced the four-paragraph "most people who say..." bio with tighter,
punchier copy: shorter sentences, a standalone one-line transition ("That
offensive mindset is what I now apply to defensive security."), and a closing
paragraph that states the thesis directly instead of hedging ("not pivoting
away" -> "not moving away... I'm building on it").
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… nodes The Journey section read as a plain static list. Upgraded using ui-ux-pro-max (scroll-reveal/stagger guidance) and the established taste-skill direction: - JourneyRail (client wrapper): accent progress line drawn over the static rail via useScroll+useSpring, filling as the visitor reads through; wraps the server-rendered <ol> as children so SSR content is untouched. - JourneyAmbientScene: capability-gated (full tier only) R3F canvas behind the list — a soft glowing chain with a light pulse traveling top-to-bottom (chronological progress), low bloom, low opacity, autoplay-only (no GSAP scrub — Journey isn't a Scenario, so it doesn't need the full two-tier apparatus). Dynamically imported, code-split like the four scenario scenes. - Reveal: new `inView` mode (whileInView, viewport once) so per-item entrances trigger on scroll instead of a fixed mount delay, matching gsap scroll-reveal guidance (small y-offset, no re-trigger on scroll direction change). - Node markers: pure-CSS pulsing ring (.node-pulse), stripped entirely under reduced-motion rather than just paused. Verified: typecheck + lint clean; build static; SSR DOM content unchanged (all timeline text present); three.js still absent from initial HTML; home First Load JS 177kB -> 182kB (+5kB, the ambient scene chunk stays lazy). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…nt language Design review: 3D + bloom is exclusively the four Scenarios' device per the brief's two-tier immersive layer — it's what makes SC-01-04 feel like the payoff. Grafting a glowing WebGL canvas + a novel pulse-ring motif onto Journey (a plain section before the scenarios) borrowed that vocabulary out of context, which is why it read as forced and disconnected from the rest of the site. Removed entirely: journey-ambient-scene.tsx (R3F canvas + bloom), the mix-blend-screen mount, capability-tier gating for it, and the invented .node-pulse CSS ring. Replaced with the language already used everywhere else on the site (nav dot, scenario STEP labels): accent color = active/reached, nothing else. - JourneyRail: kept only the scroll-linked accent fill on the existing rail line (a color transition on an element already there, not a new visual). - JourneyNode: node marker goes from hollow/muted to solid accent fill once scrolled into view — same "reached" treatment scenario nodes use. Verified: typecheck + lint clean; build static; Journey SSR content unchanged; three.js still absent from initial HTML. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… caption labels SC-02/03/04 panels were filling large surfaces with full-saturation accent color + high emissive + bloom stacked together -> neon pastel slabs instead of technical panels. The AI vault's separate "lock bar" mesh was a harsh floating glow blob. Labels were boxed chips centered ON the shape face, reading as a redaction bar rather than an annotation. - All three scenes: panel/node surface stays dark (#111114) always; state now reads through a thin drei <Edges> outline (accent when reached, brighter/ thicker when verified/revealed/focused) plus a LOW emissive tint (0.08-0.24, down from 0.9-2.4) — signal, not fill. - AI vault: removed the separate glowing lock-bar mesh entirely; unlock state is carried by the edge outline plus the label flipping cipher->plaintext. - Cloud inspect ring: emissiveIntensity 2.4 -> 0.8 (reads as a focus ring, not a flare). - SceneLabel: dropped the background chip/border; plain mono caption with a text-shadow, repositioned BELOW each shape (negative offsetY) instead of centered on its face. - Bloom tuned consistently across all four scenes: intensity 0.55->0.3, luminanceThreshold 0.25->0.5, so only genuinely bright accents (pulse/reveal markers) bloom, not the whole surface. Verified: typecheck + lint clean; build static. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Contact: "Looking for..." → "Open to security roles." Confident availability, specifics in subline - Hero: Drop leading zeros on stats (03/01 → 3/1) Reads cleaner alongside "Top 2%" and "175+" - Spelling: Résumé → Resume (no accents) Consistency across both hero and contact sections Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Add verified credential links with zero dead links: - lib/certificate-files.ts: fs-based verification helper Reads public/certificates/ at build time; links appear only when files exist - components/sections/credentials.tsx: new Credentials plate Shows "View ↗" affordance next to each entry with a verification target (issuer URL or local file; plain text if neither) - public/certificates/README.md: naming convention guide Names like cert-google-cybersecurity.pdf trigger "View" link automatically - components/contact/copy-email.tsx: new client island Clipboard copy for email address (recruiters copy far more than mailto) No dead links ever; the feature is self-documenting. Drop a file, get a link. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Remove unused contact form and its entire backend chain: - Delete: app/api/contact/route.ts, lib/validations/contact.ts, lib/rate-limit.ts - Remove: RESEND_API_KEY, GITHUB_TOKEN from lib/env.ts (orphaned) - Remove: contactFormSchema, ContactFormInput from lib/types.ts - Remove: resend npm dependency - Prune: .env.local.example documentation Rationale: recruiters email or copy address far more than forms. Email is simpler, clearer, and requires no backend. Removing form shaves 69 kB from First Load JS (237 kB → 168 kB). Contact plate now leads with email as sole primary action, with Copy button + links as the full contact surface. No functionality lost. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Rewrite all project descriptions grounded in real GitHub repos: - Dharma (GRC): Local AI, tamper-evident ledger, private repo Highlight: "Runs on local AI — sensitive data never leaves the org" - VaultIQ (AI Security): 98.3% AI detection, client-side encryption Highlight: "98.3% AI-content detection · ROC-AUC 0.997" (verified metric) - Network Reconnaissance Tool (Offensive): Nmap automation, real public repo Highlight: "Offensive tooling — the practice behind the defensive work" Fix overclaims sitewide: - Profile bio: "zero-knowledge" → "client-side encryption + measured AI detection" - Profile aboutBio: Same correction + accurate Dharma/VaultIQ descriptions - Approach Build beat: Remove "even platform can't read it" → add 98.3% accuracy - Timeline: Update milestone date (2026-01 → 2026-06), accurate description - Skills: Rename "Zero-Knowledge Architecture" → "Client-Side Encryption (AES-256)" Result: every claim is now verifiable, all metrics are real, projects are live. Recruiters can check source code; no vague marketing language remains. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Update root layout.tsx: - Swap Instrument Serif + Inter → single Archivo variable font - Add root className: archivo.variable (variable font registration) - Mount SiteHeader + SiteFooter chrome Update app/opengraph-image.tsx: - Reframe for specimen aesthetic - Use accent-on-ink colors for text Update app/page.tsx: - Wire all new section components (Hero, Approach, Work, Capabilities, Credentials, Contact) - Remove old sections (scenarios, about, timeline, projects, certifications) components/layout/site-header.tsx: - Sticky, transparent over hero, gains wash on scroll - Text inverts on dark plates (approach/contact) - Smooth transition between light/dark text color components/layout/footer.tsx: - Brief attribution, anchored to close plate - Spec-compliant, no design clutter Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
styles/globals.css (complete rewrite): - OKLCH token definitions: --paper, --ink, --close, --accent - Accent variants: --accent-on-dark for contrast on ink - Muted text variants: --ink-dim, --on-close-muted (WCAG AA verified) - Type scale: .type-statement, .type-plate, .type-body, .type-data, .type-figure - Keyframes: @Keyframes transit-fall (animation band motion) - All contrast ratios documented in comments (15 pairs, all ≥4.5:1) lib/types.ts (extend Project interface): - Add highlight?: string (one outcome a recruiter retains) - Add sourceNote?: string (shown for private repos instead of dead link) - Remove ContactFormInput (form is gone) lib/seo/jsonld.ts + config.ts: - Update for current profile + project structure - Canonical site URL handling data/navigation.ts: - Navigation structure for scroll-spy and header links Result: consistent type hierarchy across all plates, contrast verified at build, no orphaned styles or unused tokens. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
PRODUCT.md (complete rewrite): - Register: brand (portfolio/presence, not app) - Audience: recruiters, startup founders, technical decision-makers - Core principle: authentic specimen sheet, not AI-generated site - Visual language: Archivo variable font, OKLCH tokens, specimen typography - Content strategy: grounded claims, verifiable projects, no marketing fluff DESIGN.md (complete rewrite): - Token definitions and contrast gates - Six-plate structure: paper (hero) → ink (approach) → paper (work) → paper-2 (capabilities) → paper-2 (credentials) → close (contact) - Type scale and hierarchy rules - Motion principles: easeOut, reduced-motion, reveal pattern - Hard rules: no viewBox/preserveAspectRatio in contact band (learned from guilloche fail) - Accessibility: WCAG AA, 4.5:1 body text, measured via culori Docs/: - Update 01_PRD.md: specimen sheet positioning - Update 03_UI_UX.md: plate structure, asymmetric layout - Update 06_App_Flow.md: section-by-section narrative flow Result: documentation is current, architectural decisions are recorded, future changes have a foundation to build on. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Clean up from design rebuild:
- Delete old sections: about, certifications, github, journey-timeline, skills, tryhackme
- Delete scenario system: all 4 scenario types + registry (replaced by simple project list)
- Delete old UI components: button, journey-rail
- Delete scenario data files: scenario-{ai,cloud,grc,offensive}.ts, scenarios.ts
- Delete legacy folder: entire old codebase snapshot for reference
The new specimen-sheet design renders six clean plates (hero, approach, work,
capabilities, credentials, contact) with no scenario system. Old code kept in git
history; can be recovered if needed, but won't ship.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Complete portfolio redesign from scenario-driven to specimen-sheet model.
Focus: authentic presence (no AI artifacts), verifiable content (all projects real, all metrics proven),
and recruiter-direct flow (email-first contact, no forms).
Changes
Content
Design
Technical
public/certificates/→ "View ↗" links light up automaticallyTest Plan
npm run type-check)npm run lint)npm run build)Review Notes
🚀 Ready to merge to
Hemand deploy