diff --git a/.claude-flow/agents/store.json b/.claude-flow/agents/store.json new file mode 100644 index 0000000..d870c15 --- /dev/null +++ b/.claude-flow/agents/store.json @@ -0,0 +1,83 @@ +{ + "agents": { + "architect-ia-tokens": { + "agentId": "architect-ia-tokens", + "agentType": "architect", + "status": "idle", + "health": 1, + "taskCount": 0, + "config": {}, + "createdAt": "2026-07-20T08:54:34.517Z", + "domain": "frontend", + "model": "opus", + "modelRoutedBy": "router", + "provider": "anthropic" + }, + "coder-design-system": { + "agentId": "coder-design-system", + "agentType": "coder", + "status": "idle", + "health": 1, + "taskCount": 0, + "config": {}, + "createdAt": "2026-07-20T08:54:37.811Z", + "domain": "frontend", + "model": "opus", + "modelRoutedBy": "router", + "provider": "anthropic" + }, + "coder-layout": { + "agentId": "coder-layout", + "agentType": "coder", + "status": "idle", + "health": 1, + "taskCount": 0, + "config": {}, + "createdAt": "2026-07-20T08:54:39.791Z", + "domain": "frontend", + "model": "opus", + "modelRoutedBy": "router", + "provider": "anthropic" + }, + "coder-3d-scenario": { + "agentId": "coder-3d-scenario", + "agentType": "coder", + "status": "idle", + "health": 1, + "taskCount": 0, + "config": {}, + "createdAt": "2026-07-20T08:54:45.769Z", + "domain": "frontend", + "model": "opus", + "modelRoutedBy": "router", + "provider": "anthropic" + }, + "reviewer-a11y-perf": { + "agentId": "reviewer-a11y-perf", + "agentType": "reviewer", + "status": "idle", + "health": 1, + "taskCount": 0, + "config": {}, + "createdAt": "2026-07-20T08:54:45.773Z", + "domain": "frontend", + "model": "opus", + "modelRoutedBy": "router", + "provider": "anthropic" + }, + "tester-lighthouse": { + "agentId": "tester-lighthouse", + "agentType": "tester", + "status": "idle", + "health": 1, + "taskCount": 0, + "config": {}, + "createdAt": "2026-07-20T08:54:45.794Z", + "domain": "frontend", + "model": "opus", + "modelRoutedBy": "router", + "provider": "anthropic" + } + }, + "version": "3.0.0" +} diff --git a/.claude-flow/swarm/swarm-state.json b/.claude-flow/swarm/swarm-state.json new file mode 100644 index 0000000..35d02f8 --- /dev/null +++ b/.claude-flow/swarm/swarm-state.json @@ -0,0 +1,31 @@ +{ + "swarms": { + "swarm-1784537628090-r867an": { + "swarmId": "swarm-1784537628090-r867an", + "topology": "hierarchical", + "maxAgents": 6, + "status": "running", + "agents": [ + "architect-ia-tokens", + "coder-design-system", + "coder-layout", + "coder-3d-scenario", + "reviewer-a11y-perf", + "tester-lighthouse" + ], + "tasks": [], + "config": { + "topology": "hierarchical", + "maxAgents": 6, + "strategy": "specialized", + "communicationProtocol": "message-bus", + "autoScaling": true, + "consensusMechanism": "majority" + }, + "createdAt": "2026-07-20T08:53:48.090Z", + "updatedAt": "2026-07-20T08:53:48.090Z", + "pid": 68363 + } + }, + "version": "3.0.0" +} diff --git a/.env.local.example b/.env.local.example index 9de7e3c..d424e33 100644 --- a/.env.local.example +++ b/.env.local.example @@ -3,9 +3,3 @@ # Canonical site origin for absolute URLs in metadata (optional in dev). NEXT_PUBLIC_SITE_URL=http://localhost:3000 - -# Phase 5 — contact form email delivery (Resend). Server-only. -RESEND_API_KEY= - -# Phase 5 — GitHub API integration (higher rate limits). Server-only. -GITHUB_TOKEN= diff --git a/.swarm/hnsw.index b/.swarm/hnsw.index new file mode 100644 index 0000000..478a373 Binary files /dev/null and b/.swarm/hnsw.index differ diff --git a/.swarm/hnsw.metadata.json b/.swarm/hnsw.metadata.json new file mode 100644 index 0000000..712f9bb --- /dev/null +++ b/.swarm/hnsw.metadata.json @@ -0,0 +1,20 @@ +[ + [ + "entry_1784537629936_hl24do", + { + "id": "entry_1784537629936_hl24do", + "key": "design-tokens", + "namespace": "design", + "content": "{\"bg\":\"#0A0E14\",\"primary\":\"#E6EDF3\",\"secondary\":\"#3D5A80\",\"accent\":\"#22D3EE\",\"rule\":\"exactly 4 tokens site-wide; bg tints permitted for elevation; accent reserved for CTA/active-state/glow only, never large fill\"}" + } + ], + [ + "entry_1784537666780_9t64mm", + { + "id": "entry_1784537666780_9t64mm", + "key": "ia-structure", + "namespace": "architecture", + "content": "{\"stops\":[\"Landing/CapabilityDetection\",\"Hero\",\"About(+JourneyTimeline strip)\",\"Scenarios(1 sticky horizontal carousel of 4)\",\"ProofOfWork(tabbed: Skills/Certifications/TryHackMe/GitHub)\",\"Contact(+Resume)\"],\"blog\":\"moved to footer/nav route, not a scroll stop\",\"rule\":\"6 top-level
landmarks total, no content deleted\"}" + } + ] +] diff --git a/.swarm/memory.db b/.swarm/memory.db new file mode 100644 index 0000000..072e3ca Binary files /dev/null and b/.swarm/memory.db differ diff --git a/.swarm/model-router-state.json b/.swarm/model-router-state.json new file mode 100644 index 0000000..362dac9 --- /dev/null +++ b/.swarm/model-router-state.json @@ -0,0 +1,71 @@ +{ + "totalDecisions": 6, + "modelDistribution": { + "haiku": 0, + "sonnet": 0, + "opus": 6, + "inherit": 0 + }, + "avgComplexity": 0.44834267149092527, + "avgConfidence": 0.7009165237424836, + "circuitBreakerTrips": 0, + "lastUpdated": "2026-07-20T08:54:45.794Z", + "learningHistory": [], + "version": 2, + "priors": { + "low": { + "haiku": { + "alpha": 1, + "beta": 1 + }, + "sonnet": { + "alpha": 1, + "beta": 1 + }, + "opus": { + "alpha": 1, + "beta": 1 + }, + "inherit": { + "alpha": 1, + "beta": 1 + } + }, + "med": { + "haiku": { + "alpha": 1, + "beta": 1 + }, + "sonnet": { + "alpha": 1, + "beta": 1 + }, + "opus": { + "alpha": 1, + "beta": 1 + }, + "inherit": { + "alpha": 1, + "beta": 1 + } + }, + "high": { + "haiku": { + "alpha": 1, + "beta": 1 + }, + "sonnet": { + "alpha": 1, + "beta": 1 + }, + "opus": { + "alpha": 1, + "beta": 1 + }, + "inherit": { + "alpha": 1, + "beta": 1 + } + } + } +} diff --git a/.swarm/schema.sql b/.swarm/schema.sql new file mode 100644 index 0000000..1fe82fe --- /dev/null +++ b/.swarm/schema.sql @@ -0,0 +1,338 @@ + +-- RuFlo V3 Memory Database +-- Version: 3.0.0 +-- Features: Pattern learning, vector embeddings, temporal decay, migration tracking + +PRAGMA journal_mode = WAL; +PRAGMA synchronous = NORMAL; +PRAGMA foreign_keys = ON; + +-- ============================================ +-- CORE MEMORY TABLES +-- ============================================ + +-- Memory entries (main storage) +CREATE TABLE IF NOT EXISTS memory_entries ( + id TEXT PRIMARY KEY, + key TEXT NOT NULL, + namespace TEXT DEFAULT 'default', + content TEXT NOT NULL, + type TEXT DEFAULT 'semantic' CHECK(type IN ('semantic', 'episodic', 'procedural', 'working', 'pattern')), + + -- Vector embedding for semantic search (stored as JSON array) + embedding TEXT, + embedding_model TEXT DEFAULT 'local', + embedding_dimensions INTEGER, + + -- Metadata + tags TEXT, -- JSON array + metadata TEXT, -- JSON object + owner_id TEXT, + + -- Timestamps + created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now') * 1000), + updated_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now') * 1000), + expires_at INTEGER, + last_accessed_at INTEGER, + + -- Access tracking for hot/cold detection + access_count INTEGER DEFAULT 0, + + -- Status + status TEXT DEFAULT 'active' CHECK(status IN ('active', 'archived', 'deleted')), + + UNIQUE(namespace, key) +); + +-- Indexes for memory entries +CREATE INDEX IF NOT EXISTS idx_memory_namespace ON memory_entries(namespace); +CREATE INDEX IF NOT EXISTS idx_memory_key ON memory_entries(key); +CREATE INDEX IF NOT EXISTS idx_memory_type ON memory_entries(type); +CREATE INDEX IF NOT EXISTS idx_memory_status ON memory_entries(status); +CREATE INDEX IF NOT EXISTS idx_memory_created ON memory_entries(created_at); +CREATE INDEX IF NOT EXISTS idx_memory_accessed ON memory_entries(last_accessed_at); +CREATE INDEX IF NOT EXISTS idx_memory_owner ON memory_entries(owner_id); + +-- ============================================ +-- PATTERN LEARNING TABLES +-- ============================================ + +-- Learned patterns with confidence scoring and versioning +CREATE TABLE IF NOT EXISTS patterns ( + id TEXT PRIMARY KEY, + + -- Pattern identification + name TEXT NOT NULL, + pattern_type TEXT NOT NULL CHECK(pattern_type IN ( + 'task-routing', 'error-recovery', 'optimization', 'learning', + 'coordination', 'prediction', 'code-pattern', 'workflow' + )), + + -- Pattern definition + condition TEXT NOT NULL, -- Regex or semantic match + action TEXT NOT NULL, -- What to do when pattern matches + description TEXT, + + -- Confidence scoring (0.0 - 1.0) + confidence REAL DEFAULT 0.5, + success_count INTEGER DEFAULT 0, + failure_count INTEGER DEFAULT 0, + + -- Temporal decay + decay_rate REAL DEFAULT 0.01, -- How fast confidence decays + half_life_days INTEGER DEFAULT 30, -- Days until confidence halves without use + + -- Vector embedding for semantic pattern matching + embedding TEXT, + embedding_dimensions INTEGER, + + -- Versioning + version INTEGER DEFAULT 1, + parent_id TEXT REFERENCES patterns(id), + + -- Metadata + tags TEXT, -- JSON array + metadata TEXT, -- JSON object + source TEXT, -- Where the pattern was learned from + + -- Timestamps + created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now') * 1000), + updated_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now') * 1000), + last_matched_at INTEGER, + last_success_at INTEGER, + last_failure_at INTEGER, + + -- Status + status TEXT DEFAULT 'active' CHECK(status IN ('active', 'archived', 'deprecated', 'experimental')) +); + +-- Indexes for patterns +CREATE INDEX IF NOT EXISTS idx_patterns_type ON patterns(pattern_type); +CREATE INDEX IF NOT EXISTS idx_patterns_confidence ON patterns(confidence DESC); +CREATE INDEX IF NOT EXISTS idx_patterns_status ON patterns(status); +CREATE INDEX IF NOT EXISTS idx_patterns_last_matched ON patterns(last_matched_at); + +-- Pattern evolution history (for versioning) +CREATE TABLE IF NOT EXISTS pattern_history ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + pattern_id TEXT NOT NULL REFERENCES patterns(id), + version INTEGER NOT NULL, + + -- Snapshot of pattern state + confidence REAL, + success_count INTEGER, + failure_count INTEGER, + condition TEXT, + action TEXT, + + -- What changed + change_type TEXT CHECK(change_type IN ('created', 'updated', 'success', 'failure', 'decay', 'merged', 'split')), + change_reason TEXT, + + created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now') * 1000) +); + +CREATE INDEX IF NOT EXISTS idx_pattern_history_pattern ON pattern_history(pattern_id); + +-- ============================================ +-- LEARNING & TRAJECTORY TABLES +-- ============================================ + +-- Learning trajectories (SONA integration) +CREATE TABLE IF NOT EXISTS trajectories ( + id TEXT PRIMARY KEY, + session_id TEXT, + + -- Trajectory state + status TEXT DEFAULT 'active' CHECK(status IN ('active', 'completed', 'failed', 'abandoned')), + verdict TEXT CHECK(verdict IN ('success', 'failure', 'partial', NULL)), + + -- Context + task TEXT, + context TEXT, -- JSON object + + -- Metrics + total_steps INTEGER DEFAULT 0, + total_reward REAL DEFAULT 0, + + -- Timestamps + started_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now') * 1000), + ended_at INTEGER, + + -- Reference to extracted pattern (if any) + extracted_pattern_id TEXT REFERENCES patterns(id) +); + +-- Trajectory steps +CREATE TABLE IF NOT EXISTS trajectory_steps ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + trajectory_id TEXT NOT NULL REFERENCES trajectories(id), + step_number INTEGER NOT NULL, + + -- Step data + action TEXT NOT NULL, + observation TEXT, + reward REAL DEFAULT 0, + + -- Metadata + metadata TEXT, -- JSON object + + created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now') * 1000) +); + +CREATE INDEX IF NOT EXISTS idx_steps_trajectory ON trajectory_steps(trajectory_id); + +-- ============================================ +-- MIGRATION STATE TRACKING +-- ============================================ + +-- Migration state (for resume capability) +CREATE TABLE IF NOT EXISTS migration_state ( + id TEXT PRIMARY KEY, + migration_type TEXT NOT NULL, -- 'v2-to-v3', 'pattern', 'memory', etc. + + -- Progress tracking + status TEXT DEFAULT 'pending' CHECK(status IN ('pending', 'in_progress', 'completed', 'failed', 'rolled_back')), + total_items INTEGER DEFAULT 0, + processed_items INTEGER DEFAULT 0, + failed_items INTEGER DEFAULT 0, + skipped_items INTEGER DEFAULT 0, + + -- Current position (for resume) + current_batch INTEGER DEFAULT 0, + last_processed_id TEXT, + + -- Source/destination info + source_path TEXT, + source_type TEXT, + destination_path TEXT, + + -- Backup info + backup_path TEXT, + backup_created_at INTEGER, + + -- Error tracking + last_error TEXT, + errors TEXT, -- JSON array of errors + + -- Timestamps + started_at INTEGER, + completed_at INTEGER, + created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now') * 1000), + updated_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now') * 1000) +); + +-- ============================================ +-- SESSION MANAGEMENT +-- ============================================ + +-- Sessions for context persistence +CREATE TABLE IF NOT EXISTS sessions ( + id TEXT PRIMARY KEY, + + -- Session state + state TEXT NOT NULL, -- JSON object with full session state + status TEXT DEFAULT 'active' CHECK(status IN ('active', 'paused', 'completed', 'expired')), + + -- Context + project_path TEXT, + branch TEXT, + + -- Metrics + tasks_completed INTEGER DEFAULT 0, + patterns_learned INTEGER DEFAULT 0, + + -- Timestamps + created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now') * 1000), + updated_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now') * 1000), + expires_at INTEGER +); + +-- ============================================ +-- VECTOR INDEX METADATA (for HNSW) +-- ============================================ + +-- Track HNSW index state +CREATE TABLE IF NOT EXISTS vector_indexes ( + id TEXT PRIMARY KEY, + name TEXT NOT NULL UNIQUE, + + -- Index configuration + dimensions INTEGER NOT NULL, + metric TEXT DEFAULT 'cosine' CHECK(metric IN ('cosine', 'euclidean', 'dot')), + + -- HNSW parameters + hnsw_m INTEGER DEFAULT 16, + hnsw_ef_construction INTEGER DEFAULT 200, + hnsw_ef_search INTEGER DEFAULT 100, + + -- Quantization + quantization_type TEXT CHECK(quantization_type IN ('none', 'scalar', 'product')), + quantization_bits INTEGER DEFAULT 8, + + -- Statistics + total_vectors INTEGER DEFAULT 0, + last_rebuild_at INTEGER, + + created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now') * 1000), + updated_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now') * 1000) +); + +-- ============================================ +-- GRAPH EDGES (ADR-130 Phase 1) +-- Unified knowledge graph backend — sql.js canonical store +-- ============================================ + +-- Unified graph edges table (ADR-130) +-- Node IDs use domain-prefixed format: {domain}:{uuid} +-- where domain in (mem, agent, task, entity, span, pattern) +CREATE TABLE IF NOT EXISTS graph_edges ( + id TEXT PRIMARY KEY, -- edge-{uuid} + source_id TEXT NOT NULL, -- domain-prefixed node ID + target_id TEXT NOT NULL, -- domain-prefixed node ID + relation TEXT NOT NULL, -- e.g. "caused", "depends-on", "imports" + weight REAL DEFAULT 1.0, + -- Temporal / reliability semantics (ADR-130 §"graph that forgets" property) + confidence REAL DEFAULT 1.0, -- [0,1]; updated by JUDGE step + decay_rate REAL DEFAULT 0.0, -- per-day exponential decay applied at read time + last_reinforced TEXT, -- ISO-8601; set when CONSOLIDATE re-touches edge + witness_id TEXT, -- FK to verification/witness-fixes.json (ADR-103) + -- Embedding storage: "inline:{base64}" | "vector_indexes:{id}" | NULL + embedding_ref TEXT, + metadata TEXT, -- JSON blob for plugin-specific fields + created_at TEXT NOT NULL +); + +CREATE INDEX IF NOT EXISTS idx_graph_edges_source ON graph_edges (source_id); +CREATE INDEX IF NOT EXISTS idx_graph_edges_target ON graph_edges (target_id); +CREATE INDEX IF NOT EXISTS idx_graph_edges_relation ON graph_edges (relation); +CREATE INDEX IF NOT EXISTS idx_graph_edges_reinforced ON graph_edges (last_reinforced); + +-- ============================================ +-- SYSTEM METADATA +-- ============================================ + +CREATE TABLE IF NOT EXISTS metadata ( + key TEXT PRIMARY KEY, + value TEXT NOT NULL, + updated_at INTEGER DEFAULT (strftime('%s', 'now') * 1000) +); + + +INSERT OR REPLACE INTO metadata (key, value) VALUES + ('schema_version', '3.0.0'), + ('backend', 'hybrid'), + ('created_at', '2026-07-20T08:53:49.855Z'), + ('sql_js', 'true'), + ('vector_embeddings', 'enabled'), + ('pattern_learning', 'enabled'), + ('temporal_decay', 'enabled'), + ('hnsw_indexing', 'enabled'); + +-- Create default vector index configuration. Dimension matches the default +-- ONNX embedding model (Xenova/all-MiniLM-L6-v2, 384-dim); HNSW rejects +-- inserts whose dim does not match this row, so a 768 here breaks every +-- memory_store --vector and memory_search on a fresh install (#1947). +INSERT OR IGNORE INTO vector_indexes (id, name, dimensions) VALUES + ('default', 'default', 384), + ('patterns', 'patterns', 384); diff --git a/DESIGN.md b/DESIGN.md new file mode 100644 index 0000000..b78efe9 --- /dev/null +++ b/DESIGN.md @@ -0,0 +1,185 @@ +# DESIGN.md — "Specimen" + +> The live visual system. `styles/globals.css` is the implementation; this file +> explains the reasoning so the next change doesn't quietly undo it. +> +> Supersedes Design System v3 ("Industrial Telemetry"), which described a dark +> scenario-driven site that no longer exists. + +## The one rule + +**Surface separates sections. Nothing else needs to.** + +The page runs `paper → ink → paper → paper-2 → paper-2 → oxblood`. Changing +the ground is what tells a visitor one section ended and another began. + +This is load-bearing. An earlier version gave all six sections an identical +shell — same padding, same max-width, same alignment, one background — and +separated them with 1px rules. Nothing bifurcated; the page read as one +undifferentiated column. Every rule below follows from replacing hairlines with +surface. + +## Banned, permanently + +Not stylistic preferences. Each appeared in a previous version, and each is a +documented AI-generation tell: + +- **Tiny uppercase tracked eyebrows above section headings.** Scaffolding you + reach for when the layout can't separate itself. The surface change replaced + it. +- **Numbered section markers** (`01 ·`, `02 ·`). Numbers earn a place only when + the content genuinely is an ordered sequence. +- **Coloured side-stripe borders** (`border-left` > 1px as an accent). + Decoration standing in for emphasis; use scale or ground instead. +- **Gradient text** (`background-clip: text`). +- **Cards, nested panels, pill/chip tags.** Boxing content adds a frame nobody + asked for. The only bordered shapes on the site are the capability + schematic's panels, which are nodes in a real topology. +- **One uniform scroll-reveal on every block.** The uniform reflex is the tell, + not motion itself. +- **Display serif + small mono labels + ruled separators.** The + "editorial-typographic" lane — the second-order AI reflex, and where v1 + landed. + +## Type — one family, three registers + +**Archivo**, variable: `wdth 62–125`, `wght 100–900`. No second family, no mono. + +Width is the hierarchy device, which is the concept doing real work: a type +specimen shows one family across its range. Expanded for display, normal for +reading, condensed for data. + +| Utility | Axis | Size | +| ----------------- | -------------------- | ----------------------------- | +| `.type-statement` | `wdth 118, wght 700` | `clamp(2.75rem, 7vw, 6rem)` | +| `.type-plate` | `wdth 112, wght 600` | `clamp(2rem, 5vw, 3.75rem)` | +| `.type-sub` | `wdth 100, wght 600` | `clamp(1.5rem, 3vw, 2.25rem)` | +| `.type-body` | `wdth 100, wght 400` | `1.0625rem`, max `68ch` | +| `.type-data` | `wdth 78, wght 500` | `0.8125rem` | + +Steps are ≥1.25 apart. Display ceiling is 6rem — above that the page is +shouting. Tracking floor is `-0.04em` — tighter and letters touch. + +## Colour — committed, not restrained + +Values are fixed. What matters is that they cover **surface**, not just accents. + +| Plate | Ground | Text ramp | +| ------- | --------- | -------------------------------------------------- | +| Paper | `#faf9f7` | `--ink` / `--ink-muted` / `--ink-dim` | +| Paper-2 | `#f2f0ec` | same | +| Ink | `#14151a` | `--on-ink` / `--on-ink-muted` / `--accent-on-dark` | +| Oxblood | `#4a1811` | `--on-close` / `--on-close-muted` | + +The accent `#b4331f` is a **redline** — correction, emphasis, one mark per +plate. It is never a ground. + +The closing plate is that redline's own hue taken to depth: OKLCH hue **30** +against the accent's **32**, darkened (L 0.29 vs 0.52) and desaturated +(C 0.078 vs 0.169). Related by construction, not by repetition. + +**This is a correction, and the reason matters.** The closing plate was +originally a full-strength `--accent` drench. At chroma 0.169 across a whole +ground it was the loudest thing on the site and did not sit with the rest of the +palette. It also squeezed the typography: only two tiers cleared AA (5.81 and +4.76, with a third measuring 4.39 and failing). The depth buys headroom — +`--on-close-muted` clears 6.81:1, so the muted tier is genuinely muted rather +than a near-white pressed against the limit. + +Consequence to preserve: **a dark plate's header wash must be tinted from that +plate's own ground.** A neutral `bg-ink/80` over oxblood composites to `#1f1618`, +0.075 below the plate in OKLCH lightness — it reads as a bar laid on top rather +than the surface dimmed. See `DARK_PLATE_WASH` in +`components/layout/site-header.tsx`. + +### Contrast is measured, never estimated + +Every text/ground pair is verified with `culori`'s `wcagContrast` at ≥4.5:1. +Not ceremony — measuring caught two real failures that reasoning missed: + +- `--ink-dim` was originally `#8a8c96` → **3.18:1, failed**. Now `#6b6d79`. +- `--accent` on the ink plate → **2.98:1, failed**. Hence `--accent-on-dark` + (`#e0563c`, 4.83:1). **The paper accent must never be used on a dark plate.** +- On vermilion only two tiers survive: `#f2d4ce` measures 4.39:1 and fails. + +Re-run the gate after any colour change. + +## Motion + +Per-plate, never uniform: + +- **Hero** — one choreographed staggered load. Transform-only, opacity stays 1: + the `

` is the LCP element and fading it in delays the largest paint. +- **Approach** — the lattice's scroll-driven resolve. +- **Work** — per-project stagger on scroll. +- **Credentials** — none, deliberately. Something has to stay still. +- **Contact** — the transit band runs continuously; the closing content + staggers in behind it. + +`prefers-reduced-motion` is a hard override at the call site +(`components/shared/reveal.tsx`), not a tier decision. + +## The two visuals + +Text-only pages are a failure mode, so the page carries two real visuals: + +1. **The lattice** (`components/approach/`) — a scattered node graph resolving + into an ordered lattice on scroll. Both states share one edge list, so the + same graph reads as tangle, then structure. Light line-art on the ink plate. + Gated by `lib/capability`: reduced-motion, <768px and no-WebGL2 get an SSR'd + SVG of the resolved end state, and `three` stays out of their bundle. +2. **The capability arc** (`components/capability/arc-diagram.tsx`) — a + schematic of offensive base → GRC + AI security, on an engineering + substrate. Resolves entirely from `data/skills.ts`. Full SVG ≥768px; a + stacked HTML variant below, so labels never scale under 12px. +3. **The transit band** (`components/contact/`) — the closing plate's right 30%. + Vertical channels with short segments travelling down them: data in flight. + Distinct from the lattice on purpose — that one is topology, this one is + flow, and repeating the node-and-edge language would weaken both. + +### The band must never have a coordinate system + +**Rule: the band is built from positioned elements and percentages. No `svg`, +no `viewBox`, no `preserveAspectRatio`.** + +This is the scar tissue from a real failure, recorded so it is not repeated. The +band originally held a guilloche — a square, radially symmetric engraving — in a +container that is 30% of the viewport wide and a full section tall. With +`preserveAspectRatio="slice"` the browser scales artwork to _cover_, so it was +rendered at section height and its width overflowed the band: + +| Viewport | Band | Artwork rendered | Visible | Cropped | +| -------- | ----- | ---------------- | ------- | ------- | +| 1440 | 432px | 1300px | 33% | 868px | +| 1920 | 576px | 1400px | 41% | 824px | + +A third of the motif reached the screen, and because it also rotated, lines +swept in and out of that slot and read as broken. **A square motif and a tall +band are incompatible shapes, and no tuning reconciles them.** Percentages +resize with their container and cannot disagree with it, which is why the +replacement is CSS-only. + +Two further constraints that must survive edits: + +- **The band is reserved, never overlaid.** The content container carries + `lg:pr-[34%]` so text never sits on the pattern. Below `lg` the band is not + rendered at all. +- **Segments start mid-flight.** Offsets are applied as _negative_ + `animation-delay`, so the band is never seen filling from empty. Travel runs + `-15% → 115%` so a segment fully clears the view before it loops, keeping the + repeat invisible; timing is `linear`, because easing makes every rail pulse in + unison. + +Config is seeded and deterministic (`transit-config.ts`, the same pattern as the +lattice), so server and client markup cannot diverge. + +## Anti-drift + +Before shipping any visual change: + +1. `npm run type-check && npm run lint && npm run test && npm run build` +2. Contrast gate over every pair in the colour table — ≥4.5:1, no exceptions. +3. Ban sweep: `eyebrow`, `border-l-2`, `border-r-2`, `background-clip`, + `01`/`02` markers. Nothing outside comments. +4. Scroll the page and confirm four distinct grounds are visible. A visitor + should be able to say where a section ends without reading a word. diff --git a/DESIGN_SYSTEM.md b/DESIGN_SYSTEM.md deleted file mode 100644 index 2068a88..0000000 --- a/DESIGN_SYSTEM.md +++ /dev/null @@ -1,352 +0,0 @@ -# Premium Portfolio Design System - -## Overview - -This document describes the professional design system built for a cybersecurity expert portfolio. The system emphasizes: - -- **Classy minimalism** with premium polish -- **Dark professional aesthetic** (hacker-elite brand) -- **Consistent interactions** across all components -- **Smooth animations** (150-300ms durations) -- **Accessibility-first** design patterns - ---- - -## Color Palette - -### Primary Colors - -- **Background**: `#0B0C0E` (Deep Black) -- **Elevated BG**: `#131417`, `#1A1B1F` (Card backgrounds) -- **Accent Primary**: `#5EEAD4` (Teal - selective use) -- **Text Primary**: `#EDEDEE` (Main text) -- **Text Secondary**: `#8B8D92` (Secondary text) -- **Text Tertiary**: `#55575D` (Faint text) - -### Usage Rules - -- **Accent (#5EEAD4)** is reserved for: - - Interactive hover states - - Important CTAs - - Accent borders on cards - - Badge highlights -- Never use full saturation on large areas -- Always dim accent with `rgba(94, 234, 212, 0.12)` for backgrounds - ---- - -## Typography - -### Scale - -- **H1**: `clamp(2.5rem, 6vw, 4.5rem)` @ 800 weight -- **H2**: `clamp(2rem, 4vw, 3.5rem)` @ 700 weight -- **H3**: `1.25rem` @ 600 weight -- **Body Large**: `1.125rem` @ 400 weight -- **Body Base**: `1rem` @ 400 weight -- **Body Small**: `0.9375rem` @ 400 weight -- **Mono**: `0.875rem` @ 400 weight (JetBrains Mono) - -### Fonts - -- **Body**: `Inter` (geometric, modern, professional) -- **Mono**: `JetBrains Mono` (code, technical sections) - ---- - -## Component Library - -### ButtonPremium - -Four variants with smooth 200ms transitions: - -#### Variant: Primary (White → Teal) - -```tsx - - Primary Action - -``` - -- Background: `#EDEDEE` → `#5EEAD4` on hover -- Use for main CTAs (e.g., "Download Resume") - -#### Variant: Secondary (Ghost with Border) - -```tsx - - Secondary Action - -``` - -- Border: `rgba(255,255,255,0.14)` → `#5EEAD4` on hover -- Use for supporting actions - -#### Variant: Ghost (Text Only) - -```tsx - - View more - -``` - -- Text: `#8B8D92` → `#5EEAD4` on hover -- Use for subtle links - -#### Variant: Accent (Teal Background) - -```tsx - - Featured Action - -``` - -- Background: `#5EEAD4` with enhanced glow on hover -- Use sparingly for premium CTAs - -### Sizes - -- **sm**: `px-4 py-2` @ 0.875rem -- **base**: `px-6 py-3` @ 1rem (recommended) -- **lg**: `px-8 py-4` @ 1.125rem - -### Icon Support - -```tsx -} iconPosition="left"> - With Icon - -``` - ---- - -### CardPremium - -Flexible card system for content containers: - -#### Variant: Default - -```tsx - - - Card Title - - Content here - -``` - -#### Variant: Elevated - -Adds `shadow-lg` for depth (projects, showcases) - -#### Variant: Glass - -Backdrop blur effect + subtle gradient (premium features) - -#### Variant: Bordered - -Bold accent border for accent content - -### Interactive Cards - -```tsx -Click to expand -``` - -- Lifts on hover (`scale: 1.02`) -- Accent border appears -- Smooth 300ms transition - ---- - -### FeatureCard - -Pre-styled card for features/capabilities: - -```tsx - -``` - -### StatCard - -Display metrics: - -```tsx - -``` - ---- - -## Layout Components - -### SectionPremium - -Wrap entire sections for consistency: - -```tsx - - - {/* cards */} - -``` - -### Variants - -- **default**: Flat dark background -- **glass**: Gradient + backdrop blur + subtle border -- **accent**: Gradient with teal tint - -### Spacing Options - -- **compact**: `py-16 sm:py-24` -- **normal**: `py-20 sm:py-32` (recommended) -- **spacious**: `py-24 sm:py-40` - -### Accent Lines - -- **top** (default): Border-top only -- **bottom**: Border-bottom only -- **both**: Top + bottom borders -- **none**: No borders - ---- - -## Animation Guidelines - -### Durations - -- **Fast**: 150ms (micro-interactions, hover feedback) -- **Base**: 200ms (button transitions, card lift) -- **Normal**: 300ms (section reveals, stagger) -- **Slow**: 500ms (hero entrance, major reveal) - -### Easing Functions - -- **power1.out**: Subtle (text fades, borders) -- **power2.out**: Standard (cards, buttons) -- **expo.out**: Premium (hero, heading scale) -- **back.out**: Playful (badges, icon pulse) -- **elastic.out**: Premium feel (magnetic pull) - -### Built-in Patterns - -- Hover lift: `y: -4px` scale `1.02` (200ms) -- Card elevation: `y: -12px` with glow shadow (300ms) -- Badge pulse: Breathing opacity (2.4s infinite) -- Cursor blink: Step animation (1s infinite) - ---- - -## Spacing System - -Based on 8px base unit: - -- **xs**: 0.5rem (8px) -- **sm**: 1rem (16px) -- **md**: 1.5rem (24px) -- **lg**: 2rem (32px) -- **xl**: 2.5rem (40px) -- **2xl**: 3rem (48px) -- **3xl**: 3.5rem (56px) -- **4xl**: 4rem (64px) -- **5xl**: 5rem (80px) - ---- - -## Border Radius - -Consistent rounding: - -- **Buttons**: `rounded-md` (6px), `rounded-lg` (8px), `rounded-xl` (12px) -- **Cards**: `rounded-xl` (12px) -- **Full**: `rounded-full` (pills, badges) - ---- - -## Shadow System - -Subtle, professional shadows: - -- **sm**: Minimal (inputs, focus states) -- **base**: Cards at rest -- **md**: Card hover (standard elevation) -- **lg**: Card hover (prominent elevation) -- **Glow**: Accent borders (`0_0_20px_rgba(94,234,212,0.1)`) -- **Glow Hover**: Enhanced accent (`0_0_30px_rgba(94,234,212,0.15)`) - ---- - -## Accessibility - -All components include: - -- **Focus rings**: 2px `#5EEAD4` with offset -- **ARIA labels**: Icon buttons, buttons with loading state -- **Keyboard nav**: Tab-accessible all interactive elements -- **Color contrast**: WCAG AA compliant (4.5:1 ratio) -- **Motion respects**: `prefers-reduced-motion` media query - ---- - -## Implementation Examples - -### Hero Section - -```tsx - -

Hero Heading

-

Subtitle

- - Primary - Secondary - -
-``` - -### Projects Section - -```tsx - - - - {projects.map((p) => ( - - {p.title} - {p.desc} - - ))} - - -``` - ---- - -## Design Principles Summary - -1. **Minimalism**: Remove everything non-essential -2. **Consistency**: One component, one look, everywhere -3. **Premium Polish**: Smooth animations, refined shadows -4. **Professional Dark**: Deep blacks, selective accent -5. **Readable Hierarchy**: Clear visual structure -6. **Accessible**: Contrast, focus states, motion preferences -7. **Responsive**: Clamp() for typography, grid for layouts -8. **Performant**: Hardware-accelerated transforms only - ---- - -## Version - -**Design System v1.0** - Production ready - -- **Created**: 2026-07-19 -- **Status**: Active -- **Compatibility**: Next.js 15+, Tailwind CSS 3+ diff --git a/Docs/01_PRD.md b/Docs/01_PRD.md index 76156d9..d5eef14 100644 --- a/Docs/01_PRD.md +++ b/Docs/01_PRD.md @@ -2,52 +2,98 @@ ## Project -Hem Gabhawala -- Cybersecurity Portfolio +Hem Gabhawala — Cybersecurity Portfolio (Immersive Scenario Edition) ## Goal -Build a premium recruiter-focused cybersecurity portfolio that showcases -skills, projects, certifications, experience, and achievements. +Build a premium, recruiter-focused cybersecurity portfolio that uses +scroll-driven, scenario-based storytelling — inspired by Orano's +"Innovation" experience (Awwwards Developer Site of the Year 2018) — +to make technical work tangible instead of a static bullet list, while +remaining fast, accessible, and crawlable for a recruiter audience. + +**Design philosophy for this revision:** the immersive layer is a +*progressive enhancement*, never a gate. Every scenario's content must +exist and be fully readable in the DOM without WebGL. Capable devices +get the full cinematic scene; everything else gets a fast, still-premium +fallback. This is the mechanism that lets us pursue Awwwards-tier craft +without sacrificing the PRD's original 30-second-credibility objective. + +**Reframe amendment (Revision 2):** three problems surfaced from a +recruiter's-eye pass on the live site — excessive vertical scroll (12 +stacked stops), underused 3D presence, and an inconsistent color +system. This revision collapses the experience to 6 primary stops +(laterally re-organized, no content removed — see App Flow) and +replaces the per-scenario accent-color system with one strict 4-token +palette shared across the whole site (see UI/UX Design Language). +Scenarios now differentiate via icon system, line motif, and monospace +tag instead of color. The progressive-enhancement principle above is +unchanged and still governs the Scenario carousel specifically. ## Target Users -- Recruiters -- Hiring Managers -- Security Engineers -- Internship Coordinators +- Recruiters +- Hiring Managers +- Security Engineers +- Internship Coordinators ## Objectives -- Establish credibility in \<30 seconds. -- Highlight practical cybersecurity work. -- Showcase projects (VaultIQ, Dharma, API Pentesting). -- Display certifications and TryHackMe achievements. -- Enable resume download and contact. +- Establish credibility in <30 seconds, even on the non-WebGL fallback path. +- Highlight practical cybersecurity work through interactive "scenarios" + instead of flat lists. +- Showcase projects (VaultIQ, Dharma, Enterprise API Pentesting) as the + narrative payoff of each scenario. +- Display certifications and TryHackMe achievements. +- Enable resume download and contact. +- Deliver at least one genuinely portfolio-differentiating interactive + moment (the pentesting scenario) without compromising load performance. -## Core Pages +## Core Pages / Experience Structure -- Home -- About -- Experience -- Projects -- Certifications -- Skills -- Blog -- Contact +- Home (Hero + entry into scenario flow) +- About +- **Scenarios** (replaces flat "Experience" + "Projects" split — see + App Flow and Backend Schema for structure): + 1. Offensive Security (VAPT / Pentesting) + 2. GRC & Compliance (Dharma) + 3. AI-Secured Systems (VaultIQ) + 4. Cloud & Infrastructure (AWS) +- Certifications +- Skills +- Blog +- Contact ## Functional Requirements -- Responsive design -- Dark mode -- Resume download -- GitHub integration -- Contact form -- SEO -- Fast loading -- Accessibility +- Responsive design, with an explicit non-WebGL/low-power/reduced-motion + fallback path for every scenario (not just a graceful degrade — a + designed, intentional alternate experience) +- Dark mode as the base UI (not a toggle — see UI/UX doc) +- Capability detection (WebGL support, device memory/GPU tier, + `prefers-reduced-motion`) run once on load to select experience tier +- Resume download +- GitHub integration +- Contact form +- SEO (all scenario content must be server-rendered/crawlable regardless + of which experience tier the visitor gets) +- Fast loading on the fallback tier; best-effort on the full 3D tier +- Accessibility (full keyboard + screen-reader path through every + scenario's content, independent of the visual tier) + +## Explicitly Out of Scope for v1 + +- Spatial/ambient audio design (deferred — high effort, low signal for + this audience; revisit post-launch if desired) +- Custom cursor / drone-style free-camera exploration (Orano-specific; + not relevant to a recruiter portfolio's goals) ## Success Metrics -- Lighthouse \>95 -- \<2s load time -- Mobile-first +- Lighthouse >95 on the **fallback tier** (this is the tier search + crawlers and most recruiters on constrained networks/devices will hit) +- Lighthouse best-effort (target >80) on the **full 3D tier** +- <2s load time to first meaningful content, on either tier +- Mobile-first: mobile always gets the fallback tier by default +- Zero content that exists only inside the WebGL scene — 100% content + parity between tiers, verified per scenario diff --git a/Docs/02_TRD.md b/Docs/02_TRD.md index 5f6cc49..0024a19 100644 --- a/Docs/02_TRD.md +++ b/Docs/02_TRD.md @@ -2,36 +2,97 @@ ## Stack -- Next.js 15 -- React 19 -- TypeScript -- Tailwind CSS -- shadcn/ui -- Framer Motion +### Core +- Next.js 15 (App Router, RSC by default) +- React 19 +- TypeScript (strict mode) +- Tailwind CSS +- shadcn/ui +- Framer Motion (UI-level micro-interactions, page transitions, + fallback-tier scenario animations) + +### Immersive Scenario Layer (new) +- `@react-three/fiber` — Three.js as React components, keeps scenes + composable and testable like the rest of the codebase +- `@react-three/drei` — helpers (loaders, controls, environment) +- `gsap` + `ScrollTrigger` — scroll-scrubbed scene choreography; + Framer Motion alone cannot drive frame-by-frame 3D scene state off + scroll position, GSAP is the right tool for this specific job +- `@react-three/postprocessing` — bloom / depth-of-field for the + "sharp futuristic" visual target, used sparingly, per-scenario +- Draco + KTX2 compressed assets for any 3D geometry/textures, loaded + through `useGLTF` with Suspense boundaries and explicit loading states + +### Capability Detection (new — required, not optional) +A single client-side check run once on mount, before any 3D asset is +requested: +- WebGL2 support check +- `navigator.deviceMemory` / `hardwareConcurrency` heuristic for GPU/CPU + tier (fallback if unavailable — assume low tier) +- `prefers-reduced-motion` media query — always forces fallback tier, + no exceptions +- Viewport width — mobile (<768px) always forces fallback tier by + default regardless of device capability, per PRD mobile-first mandate + +Result of this check is stored once (React context) and determines +which component tree renders for each scenario: `` +or ``. Both consume the *same* content data (see +Backend Schema) so there is no content fork, only a rendering fork. + +### Explicitly excluded +- Howler.js / spatial audio (deferred per PRD — do not add as a + dependency until this is revisited) +- Custom cursor libraries — adds complexity with limited payoff for a + recruiter-facing site ## Deployment -- Vercel +- Vercel +- 3D assets (compressed .glb/.ktx2) served from Vercel's static + asset pipeline or an edge-cached CDN bucket if they exceed + reasonable repo size — decide per actual asset weight during + Phase 2.5 POC ## APIs -- GitHub API -- EmailJS/Resend +- GitHub API +- EmailJS/Resend ## Folder Structure -/app /components /data /lib /public /styles +``` +/app +/components + /ui (shadcn primitives) + /scenarios (per-scenario 3D scene + fallback pairs) + /shared +/data (scenario, project, cert, experience JSON/TS) +/lib + /capability (device/WebGL detection) +/public + /models (compressed .glb/.ktx2) +/styles +``` ## Performance -- SSR where possible -- Image optimization -- Lazy loading -- Code splitting +- SSR/RSC for all scenario content (text, structured data) regardless + of rendering tier — the 3D scene is a client-only enhancement layered + on top of server-rendered content, never a replacement for it +- Image optimization +- Lazy loading — 3D scene chunks load only when their scenario section + approaches viewport (dynamic `import()`, not bundled into initial load) +- Code splitting per scenario — a visitor who never scrolls past + Scenario 1 should never download Scenario 4's 3D assets +- Explicit bundle budget check in Phase 2.5 POC before building the + remaining three scenarios: if one scene's JS+asset payload can't stay + under an agreed threshold, the fallback tier becomes the default for + more device classes, not just low-end ones ## Security -- CSP -- HTTPS -- Sanitized forms -- Rate limiting +- CSP (note: relaxing CSP for any WebGL/shader inline scripts must be + scoped as narrowly as possible — no blanket `unsafe-inline`) +- HTTPS +- Sanitized forms +- Rate limiting diff --git a/Docs/03_UI_UX.md b/Docs/03_UI_UX.md index 5662a5b..4a0a0e0 100644 --- a/Docs/03_UI_UX.md +++ b/Docs/03_UI_UX.md @@ -1,36 +1,130 @@ # UI / UX Specification -## Design Language +## Design Language (revised) -- Minimal -- Enterprise -- Apple + Vercel inspired -- Glassmorphism -- Deep Blue Accent +**Change from previous version:** glassmorphism + light "Apple/Vercel +SaaS" aesthetic is replaced with a dark, precision-industrial aesthetic +inspired by Orano's Innovation site — while keeping the typographic +restraint and spacing discipline that made the Apple/Vercel reference +worth citing in the first place. Glassmorphism reads as +software-product-marketing; this portfolio needs to read as +technical-precision instead. + +- Dark base UI (not a toggle — dark is now the *only* mode, no + light-mode toggle, ever) +- Precision-grid / wireframe motifs as structural background elements + (thin linework, topographic-style contour patterns — restrained, not + decorative clutter) +- **Strict 4-token palette, identical across every section (Revision + 2 — replaces the per-scenario accent system below):** + ```css + --bg: #0A0E14; /* page background, deepest layer */ + --primary: #E6EDF3; /* headings, body text, primary iconography */ + --secondary: #3D5A80; /* borders, dividers, muted text, grid lines */ + --accent: #22D3EE; /* CTAs, active nav state, links, glow/highlight */ + ``` + Tints/shades of `--bg` for surface elevation (cards, panels) are + permitted and do not count as a 5th color. `--accent` is reserved for + CTAs, active/hover states, and the interactive-moment glow — never a + large fill area. + **Accepted exception:** the "not yet reached" node/edge state inside + each scenario's WebGL scene is hardcoded neutral grays rather than a + CSS custom property, since three.js material props read a JS value, + not CSS — the same constraint that already makes `ACCENT_HEX` a raw + constant in `lib/accents.ts` (see that file for the full rationale). + Pre-existing, not part of Revision 2's drift surface. +- Scenarios differentiate via icon + line motif + monospace tag, not + color: + - Offensive Security → crosshair/node-trace icon, `network-topology` + linework, tag `TRACE://` + - GRC & Compliance → chain-link/ledger icon, `audit-ledger` + linework, tag `LEDGER://` + - AI-Secured Systems → lock/shard icon, `encrypted-vault` linework, + tag `VAULT://` + - Cloud & Infrastructure → node-mesh icon, `cloud-architecture` + linework, tag `GRID://` +- Monospace type (e.g. `JetBrains Mono` or `Space Mono`) for technical + labels, data readouts, code references — paired with a clean sans + (e.g. `Inter`) for body/narrative text. Two-typeface system max. +- No heavy shadows, no gradients-as-decoration — depth comes from the + 3D scenes themselves and from layering/contrast, not from CSS effects +- Sharp corners or minimal radius (2–4px) — avoid the soft/rounded + "SaaS card" look entirely; this is part of what separates "precision" + from "friendly product marketing" + +## Two-Tier Rendering Model (new — core to this revision) + +Every scenario section has two presentations of **identical content**: + +1. **Full tier** — `@react-three/fiber` scene, scroll-scrubbed via + GSAP ScrollTrigger, camera/scene state tied to scroll position, + equipment/tools represented as interactive nodes the user can + hover/tap for detail +2. **Fallback tier** — static or lightly-animated (Framer Motion) + illustrated equivalent of the same scene, same narrative beats, + same interactive detail-on-hover/tap behavior, no WebGL dependency + +Design each scenario's fallback FIRST, as its own complete artifact — +not as an afterthought "what if 3D fails" state. The fallback is what +most visitors (mobile, reduced-motion, low-end devices, search +crawlers) will actually see. ## Sections -1. Hero -2. About -3. Journey Timeline -4. Skills -5. Projects -6. Experience -7. Certifications -8. TryHackMe -9. GitHub -10. Blog -11. Contact +1. Hero +2. About +3. Journey Timeline +4. **Scenarios** (replaces separate "Experience" + "Projects" sections) + - Offensive Security (VAPT/Pentesting) + - GRC & Compliance (Dharma) + - AI-Secured Systems (VaultIQ) + - Cloud & Infrastructure (AWS) +5. Skills +6. Certifications +7. TryHackMe +8. GitHub +9. Blog +10. Contact + +## Scenario Interaction Pattern + +Each scenario follows the same interaction template (consistency across +four different visual environments is what keeps this navigable rather +than four unrelated experiences bolted together): + +- Scroll-in: environment establishes (network topology / audit ledger / + encrypted vault / cloud architecture) +- Scroll-through: 2–4 narrative beats reveal in sequence, each tied to + a real accomplishment (e.g. "recon → exploit → report" for offensive + security), matching `narrativeBeats[]` in the Backend Schema +- Interactive moment: one hover/tap/press interaction per scenario that + lets the visitor "do" something small and relevant (e.g. trace a hash + chain link in GRC, reveal an encrypted field in AI-Secured Systems) — + Orano's "long press" / gamified-drone pattern, scaled down to + something achievable and tasteful, not a mini-game +- Payoff: scenario resolves into the linked project card (VaultIQ / + Dharma / Enterprise API Pentesting) with a clear link to detail/GitHub ## Animations -- Framer Motion -- Smooth scrolling -- Subtle particle background -- Hover interactions +- Framer Motion for fallback-tier scenario animation and all + non-scenario UI (nav, hero, cards, page transitions) +- GSAP ScrollTrigger exclusively for full-tier 3D scene choreography + (kept separate from Framer Motion's responsibilities to avoid two + animation engines fighting over the same DOM nodes) +- Smooth scrolling +- Hover interactions +- All animation respects `prefers-reduced-motion` — this is a hard + requirement, not a nice-to-have, and is the same signal that routes + visitors to the fallback tier at the scene level ## Accessibility -- WCAG AA -- Keyboard navigation -- High contrast +- WCAG AA minimum, on both rendering tiers independently +- Keyboard navigation through every scenario's narrative beats and + interactive moment, not just through nav/links +- High contrast — recompute WCAG AA specifically for `--primary` on + `--bg` and `--accent` on `--bg` (Revision 2 hex values); do not + assume the prior per-scenario accent ratios still pass +- All scenario content available to screen readers via the + server-rendered DOM regardless of which visual tier is active diff --git a/Docs/04_Implementation_Plan.md b/Docs/04_Implementation_Plan.md index 9e98a53..0069949 100644 --- a/Docs/04_Implementation_Plan.md +++ b/Docs/04_Implementation_Plan.md @@ -2,36 +2,74 @@ ## Phase 1 -- Initialize Next.js -- Tailwind -- shadcn/ui +- Initialize Next.js +- Tailwind +- shadcn/ui ## Phase 2 -- Navbar -- Hero -- Footer +- Navbar +- Hero +- Footer + +## Phase 2.5 — Scene Engine & Capability Detection POC (new — required gate) + +This phase exists specifically to de-risk the immersive direction +before committing to building all four scenarios. Do not proceed to +Phase 3 until this phase's exit criteria are met. + +- Build capability detection module (`/lib/capability`) +- Build ONE full scenario end-to-end, both tiers: **Offensive Security** + (chosen because it maps most directly to your strongest, most + concrete work — pentesting/VAPT) + - Full tier: `@react-three/fiber` scene + GSAP ScrollTrigger + choreography + one interactive moment + - Fallback tier: Framer Motion illustrated equivalent, same + narrative beats, same interactive moment via tap +- Measure actual bundle size and Lighthouse score for both tiers +- **Exit criteria (must pass before Phase 3):** + - Fallback tier: Lighthouse >95, <2s load + - Full tier: no hard blank/broken state on any tested device, + acceptable load behavior (loading state shown, not a frozen page) + - Content parity verified: everything in the 3D scene also exists, + identically, in the fallback and in the server-rendered DOM +- If exit criteria fail: fall back to Option 2 (adapt the concept + without WebGL) for the remaining three scenarios rather than + forcing a struggling pattern four more times ## Phase 3 -- About -- Skills -- Timeline +- About +- Skills +- Timeline ## Phase 4 -- Projects -- Certifications -- Experience +- Remaining three Scenarios (GRC & Compliance, AI-Secured Systems, + Cloud & Infrastructure), built to the pattern validated in Phase 2.5 +- Certifications +- (Legacy "Experience" and "Projects" flat sections are absorbed into + Scenarios per revised Backend Schema — do not rebuild them as + separate sections) ## Phase 5 -- GitHub API -- Contact Form -- SEO +- GitHub API +- Contact Form +- SEO (verify structured data / crawlability against the + server-rendered DOM specifically, independent of which visual tier + a given crawler-simulated request resolves to) ## Phase 6 -- Testing -- Lighthouse -- Deployment +- Testing (including reduced-motion path, keyboard-only path, and + low-end-device fallback path as explicit, separate test passes — + not just desktop-Chrome-full-tier testing) +- Lighthouse (both tiers, per PRD success metrics) +- Deployment + +## Deferred / Backlog (explicitly not in v1 scope) + +- Spatial/ambient audio design +- Light mode +- Custom cursor / free-camera exploration diff --git a/Docs/05_BackendSchema.md b/Docs/05_BackendSchema.md index 80ee9c6..990da66 100644 --- a/Docs/05_BackendSchema.md +++ b/Docs/05_BackendSchema.md @@ -1,16 +1,107 @@ # Backend Schema -## Static JSON/Data +## Static JSON/TS Data -Profile - name - role - bio - socials +### Profile +- name +- role +- bio +- socials -Projects - id - title - slug - description - techStack\[\] - -images\[\] - github - live - featured +### Scenario (new — core entity for this revision) -Certificates - id - title - issuer - date - credentialUrl +This is the new top-level entity that structures the immersive +experience. Projects, skills, and experience are no longer flat, +independently-listed sections — they are consumed *through* scenarios. +The flat entities below still exist (for Skills/Certifications/Blog +sections, resume generation, SEO structured data, etc.) but the +Scenarios section is the primary narrative surface. -Experience - company - role - startDate - endDate - achievements\[\] +``` +Scenario +- id +- slug +- title e.g. "Offensive Security" +- accentColor one of: red-amber | blue | violet | teal +- environmentType network-topology | audit-ledger | + encrypted-vault | cloud-architecture +- summary 1–2 sentence framing, used in fallback + meta/SEO and as the non-JS text summary +- narrativeBeats[] + - id + - order + - heading + - body + - relatedSkillIds[] references Skill.id +- interactiveMoment + - type trace | reveal | inspect (kept to this + small enum on purpose — see UI/UX doc, + avoid scope creep into mini-games) + - description + - triggerLabel accessible label for the interaction, + used identically on both rendering tiers +- payoffProjectId references Project.id +- scene3D (nullable — full-tier only) + - modelPath .glb path, Draco/KTX2 compressed + - cameraKeyframes[] scroll-progress-indexed camera states +- fallback (required — always present) + - illustrationRef static/SVG asset or Framer Motion + component reference + - animationBeats[] maps 1:1 to narrativeBeats[] above +``` -Blog - slug - title - tags - date +### Project +- id +- title +- slug +- description +- techStack[] +- images[] +- github +- live +- featured +- **scenarioId** (new — links back to the owning Scenario, nullable + for any project not surfaced through a scenario) -Contact - name - email - message +### Certificates +- id +- title +- issuer +- date +- credentialUrl + +### Experience +- company +- role +- startDate +- endDate +- achievements[] +- **relatedScenarioIds[]** (new — an experience entry can inform + multiple scenarios, e.g. the HackersVilla internship feeds both + Offensive Security and GRC scenarios) + +### Skill (new — previously implicit inside flat lists, now explicit +so `narrativeBeats[].relatedSkillIds` can reference it) +- id +- name +- category offensive | grc | ai-security | cloud | general +- proficiencyNote optional, short + +### Blog +- slug +- title +- tags +- date + +### Contact +- name +- email +- message + +## Data Integrity Rule (new — enforced at build time, not just convention) + +Every `Scenario` must have a non-null `fallback` object with +`animationBeats[]` count matching `narrativeBeats[]` count. This is +what guarantees the PRD's "100% content parity between tiers" +requirement — a scenario cannot ship with a 3D scene and no fallback, +and a build-time check should fail if one is added without the other. diff --git a/Docs/06_App_Flow.md b/Docs/06_App_Flow.md index bdbadbe..32f42de 100644 --- a/Docs/06_App_Flow.md +++ b/Docs/06_App_Flow.md @@ -1,14 +1,69 @@ # Application Flow -Landing ↓ Hero ↓ About ↓ Journey ↓ Skills ↓ Experience ↓ Projects ↓ -Project Details ↓ Certifications ↓ TryHackMe ↓ GitHub ↓ Blog ↓ Contact ↓ -Resume Download +## Entry & Tier Selection (new — happens before any scenario renders) + +``` +Landing + ↓ +Capability Detection (WebGL support, device tier, + prefers-reduced-motion, viewport width) + ↓ +Tier Decision: Full 3D | Fallback + (decision is made once, cached for the session — not re-evaluated + per scenario, to avoid a jarring tier switch mid-scroll) + ↓ +Hero + ↓ +About (absorbs Journey Timeline as a compact horizontal strip inside + this section, not its own full-height stop) + ↓ +Scenarios (all 4 as ONE sticky horizontal carousel section, not 4 + stacked full-height scenes — scroll pins the section and + progresses the carousel horizontally within the pinned + range; same order, regardless of tier) + 1. Offensive Security → payoff: Enterprise API Pentesting project + 2. GRC & Compliance → payoff: Dharma project + 3. AI-Secured Systems → payoff: VaultIQ project + 4. Cloud & Infrastructure → payoff: AWS-cert-backed skills + ↓ +Proof of Work (tabbed: [Skills] [Certifications] [TryHackMe] + [GitHub] — shared layout shell, one scroll stop) + ↓ +Contact (includes Resume Download; Blog link relocates to + footer/nav — Blog stays a real route, not a scroll stop) +``` + +Six primary scroll stops total: Hero, About, Scenarios, Proof of Work, +Contact — plus Landing/Capability Detection ahead of Hero. ## Navigation -Sticky navbar Smooth scrolling Active section highlight +- Persistent fast-jump side nav (desktop) / bottom sheet or hamburger + (mobile), linking to all 6 stops +- Smooth scrolling +- Active-section state shown via `--accent` on the side nav +- Scenario carousel has its own progress indicator (which of 4 + scenarios is active), plus horizontal keyboard nav (arrow keys) and + swipe on touch — this replaces the old vertical per-scenario nav + highlight, adapted from vertical to horizontal +- Nav behavior is identical on both tiers — navigation is never part + of the 3D scene itself, it stays as reliable server-rendered UI at + all times; fallback tier gets the same 6-stop structure and + carousel-via-tap pattern, no content fork, only a rendering fork ## User Journey -Recruiter lands → views hero → validates projects → checks -certifications → downloads resume → contacts candidate. +Recruiter lands → capability check silently selects experience tier → +views hero → scrolls through scenario narrative (recon → exploit → +report, or equivalent per scenario) → reaches project payoff card → +validates via GitHub/live link → checks certifications → downloads +resume → contacts candidate. + +## Fallback-Tier User Journey (explicit, not assumed) + +Same sequence, same content, same interactive moments (via tap instead +of hover/scroll-scrub) — the only difference is the environment is a +static/lightly-animated illustration instead of a scroll-scrubbed 3D +scene. A recruiter on this path should not perceive the site as a +"lesser version" — it should read as a deliberate, complete design, +not a degraded one. diff --git a/Docs/COMPONENT_ARCHITECTURE.md b/Docs/COMPONENT_ARCHITECTURE.md deleted file mode 100644 index d0750ed..0000000 --- a/Docs/COMPONENT_ARCHITECTURE.md +++ /dev/null @@ -1,41 +0,0 @@ -# Component architecture — conventions - -Established in Phase 1C. Every later phase composes against this structure; -inventing a new top-level component folder is a review-blocker. - -## Folders - -| Folder | Contents | Client/Server | -| ----------------------- | ------------------------------------------------------------------- | ---------------------------- | -| `components/ui/` | shadcn primitives — generated, minimally edited | As generated | -| `components/providers/` | App-wide context providers (theme, motion) | Client (isolated leaves) | -| `components/shared/` | Cross-section reusable pieces (theme toggle, icon wrappers) | Client only when interactive | -| `components/layout/` | Structural pieces: `Container` now; header/footer arrive in Phase 2 | Server by default | -| `components/sections/` | Page sections (Hero, About, Projects…) — Phases 2–4 | Server by default | - -Rules: - -- **Server Components by default.** `"use client"` only where interactivity - demands it, and as far down the tree as possible — providers and the theme - toggle are client leaves; layout and pages stay server. -- **Data access** goes through `lib/data/` accessors only — no component - imports from `/data` directly. -- **Design values** come from tokens only — see - [DESIGN_SYSTEM.md](DESIGN_SYSTEM.md). -- **shadcn primitives are not customized in place** beyond token alignment; - behavior changes wrap the primitive in `shared/` instead of editing `ui/`. - -## Composition over large components - -Sections are assembled from primitives + shared pieces; one file per -responsibility. Example — the Phase 2 navbar is not one 300-line file: - -``` -components/layout/site-header.tsx — server shell:
+ Container -components/layout/nav-links.tsx — link list (server) -components/layout/mobile-nav.tsx — client: Sheet + trigger -components/shared/theme-toggle.tsx — client: reused as-is -``` - -If a component needs its own state *and* substantial markup, split logic -from presentation before it grows past ~150 lines. diff --git a/Docs/DESIGN_SYSTEM.md b/Docs/DESIGN_SYSTEM.md deleted file mode 100644 index 8063527..0000000 --- a/Docs/DESIGN_SYSTEM.md +++ /dev/null @@ -1,93 +0,0 @@ -# Design System — tokens, rules, and enforcement - -Source of truth for every visual decision. Built in Phase 0.2, documented here -per Phase 1B. **Rule zero: never invent a value.** If a color, size, radius, -or duration isn't a token below, it doesn't ship — extend the token system -first, in a reviewed change. - -## Where tokens live - -| What | File | -| -------------------------------- | ------------------------------------------------ | -| Colors, type scale, radii, glass | [styles/globals.css](../styles/globals.css) | -| Motion presets (durations/easing)| [lib/motion.ts](../lib/motion.ts) | -| Measured contrast ratios | [styles/CONTRAST.md](../styles/CONTRAST.md) | - -Note: this project uses **Tailwind v4**, which is CSS-first — there is no -`tailwind.config.ts`. The `@theme` block in `globals.css` is the v4 -equivalent; earlier planning docs that mention `tailwind.config.ts` are -superseded by this. - -## Color tokens (oklch, theme-aware) - -Semantic tokens defined for `:root` (light) and `.dark`. Components use the -semantic utility (`bg-background`, `text-primary`, `border-border`) — never a -raw hex/oklch value, never a raw Tailwind palette color (`bg-blue-600` is a -review-blocker). - -| Token | Purpose | -| ------------------------- | ---------------------------------------------- | -| `background / foreground` | Page surface and default text | -| `card / card-foreground` | Elevated surfaces (also popover variants) | -| `primary / primary-foreground` | Deep blue brand accent; CTAs, links, ring | -| `secondary / secondary-foreground` | Low-emphasis fills | -| `muted / muted-foreground`| Subdued surfaces and secondary text | -| `accent / accent-foreground` | Hover washes, subtle highlights | -| `destructive / destructive-foreground` | Errors, dangerous actions | -| `border`, `input`, `ring` | Hairlines, form borders, focus ring | - -Why oklch: perceptually uniform lightness means dark-mode variants adjust one -axis predictably, and WCAG contrast tuning is deterministic rather than -guess-and-check (decision recorded in Phase 0.2). - -**Contrast**: every foreground/surface pair passes WCAG AA (4.5:1+) in both -themes — measured ratios in [styles/CONTRAST.md](../styles/CONTRAST.md), -enforced continuously by `lib/design/contrast.test.ts`, which parses -`globals.css` in CI. Editing a token that breaks a pair fails the build. - -## Typography - -Geist Sans (display + body — single-family typography is the Apple/Vercel -idiom, and Geist is Vercel's own typeface) and Geist Mono for technical -accents. Both self-hosted via `next/font` (zero external requests, zero CLS). - -Deliberate, limited scale — Tailwind defaults beyond it are removed: -`text-xs` `text-sm` `text-base` `text-lg` `text-xl` `text-2xl` `text-3xl` -`text-4xl` `text-6xl`. Intended roles are labeled on the -[/dev/tokens](../app/dev/tokens/page.tsx) showcase. - -## Spacing & radius - -- Spacing: Tailwind's default 4px modular scale (`--spacing: 0.25rem`) — - deliberate decision, no custom steps; arbitrary values (`p-[13px]`) are - banned. -- Radius: one scale from `--radius` (0.625rem): `rounded-sm|md|lg|xl`. - -## Dark mode - -Class strategy (`.dark` on ``), toggled by next-themes with system -preference support and localStorage persistence; `suppressHydrationWarning` + -next-themes' inline script prevents flash of wrong theme. - -## Glassmorphism - -One utility: `.glass` (translucent card surface, 12px backdrop blur, soft -border). Reserved for floating surfaces — navbar, overlays. Not for every -card; no heavy shadows, no gradients, no neon (per 03_UI_UX.md and master -rules). - -## Motion - -Named presets only, from `lib/motion.ts`: `fadeUp`, `fadeIn`, -`staggerChildren`, `subtleHover` (+ `transitionBase`/`transitionFast`). -Inline variants or ad-hoc durations in components are a review-blocker. -Reduced motion is honored twice: `MotionConfig reducedMotion="user"` for -Framer Motion, and a global `prefers-reduced-motion` CSS rule for -CSS-driven animation. - -## Accessibility - -- Global `:focus-visible` ring from the `--ring` token (both themes); - shadcn primitives render their own ring from the same token. -- All text/surface pairs AA-verified (see above). -- Keyboard operability is a per-component requirement checked at review. diff --git a/MODERN_AESTHETIC.md b/MODERN_AESTHETIC.md deleted file mode 100644 index a44300a..0000000 --- a/MODERN_AESTHETIC.md +++ /dev/null @@ -1,257 +0,0 @@ -# Modern shadcn/ui Aesthetic Transformation - -## Complete Redesign Summary - -Your portfolio has been completely **transformed to match modern shadcn/ui design patterns**. Same content, **completely new premium modern appearance**. - ---- - -## What Changed - -### 1. Hero Section - -**Before**: Standard dark background with teal accents -**After**: Professional modern hero with: - -- ✨ **Gradient Text**: Main heading uses cyan→teal→white gradient fade -- 🌟 **Glassmorphic Elements**: Eyebrow badge with backdrop blur & glow -- 💫 **Enhanced Glow Layers**: Multiple blur layers for depth effect -- 🎨 **Gradient Buttons**: Primary button with gradient fill, secondary with glass effect -- 📊 **Stat Cards**: Glassmorphic cards with gradient text on hover -- ⚡ **Smooth Interactions**: 300ms transitions with scale-105 hover effect - -### 2. Component Library - -#### ButtonPremium - -| Variant | Design | -| ------------- | --------------------------------------------------- | -| **Primary** | Gradient: cyan→teal with glow-30px shadow on hover | -| **Secondary** | Glass effect with gradient bg & backdrop blur | -| **Ghost** | Subtle glow effect on hover | -| **Accent** | Multi-gradient: cyan→teal→light-cyan with glow-35px | - -**All**: Smooth 300ms transitions, hover scale-105, active scale-95 - -#### CardPremium - -| Variant | Design | -| ------------ | --------------------------------------------- | -| **Default** | Gradient bg with backdrop blur-sm | -| **Elevated** | Enhanced shadow + gradient + backdrop blur-md | -| **Bordered** | Accent gradient bg with 2px border | -| **Glass** | Premium glass effect with backdrop blur-xl | - -**Interactive**: Hover scale-102 + glow-30px + translate-y[-4px] - -#### SectionPremium - -- **All variants**: Modern gradient backgrounds (top-to-bottom or diagonal) -- **Glass variant**: Glassmorphic with backdrop blur -- **Accent variant**: Teal gradient tint -- **Headers**: Gradient text titles (white→teal→cyan) - -### 3. Visual Patterns Applied - -#### Gradient Text - -```css -background: linear-gradient(135deg, #ededee 0%, #5eead4 60%, #7ffce8 100%); -background-clip: text; --webkit-background-clip: text; --webkit-text-fill-color: transparent; -``` - -#### Glassmorphism - -```css -background: rgba(19, 20, 23, 0.6); -backdrop-filter: blur(12px); -border: 1px solid rgba(94, 234, 212, 0.15); -box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1); -``` - -#### Glow Effects - -```css -box-shadow: 0 0 30px rgba(94, 234, 212, 0.4); -/* Or multiple layers: */ -box-shadow: - 0 0 30px rgba(94, 234, 212, 0.4), - inset 0 1px 0 rgba(255, 255, 255, 0.1); -``` - -#### Premium Hover States - -```css -transition: all 300ms cubic-bezier(0.4, 0, 0.2, 1); -/* On hover: */ -transform: scale(1.05) translateY(-4px); -box-shadow: 0 0 30px rgba(94, 234, 212, 0.4); -``` - ---- - -## Design Tokens Applied - -### Colors - -- **Backgrounds**: Gradient from `#0B0C0E` to `#0F1117` -- **Accent Primary**: `#5EEAD4` (teal) -- **Accent Light**: `#7FFCE8` (cyan) -- **Text Primary**: `#EDEDEE` (white) -- **Text Secondary**: `#8B8D92` (gray) -- **Glass**: `rgba(19, 20, 23, 0.6)` with backdrop blur - -### Animations - -- **Duration**: 200-300ms for micro-interactions -- **Easing**: `cubic-bezier(0.4, 0, 0.2, 1)` (smooth ease-out) -- **Hover Effects**: Scale-102/105, translate-y[-4px], glow-30px -- **Active States**: Scale-95 for tactile feedback - -### Spacing - -- **Gap**: 6-8px between elements -- **Padding**: 6-8px (cards), 12-16px (buttons) -- **Section Spacing**: 20-24px vertical (normal), 24-40px (spacious) - ---- - -## All Sections Updated - -| Section | Modern Features | -| ------------------ | ---------------------------------------------------------------------------- | -| **Hero** | Gradient text, glassmorphic badge, glow layers, gradient buttons, stat cards | -| **About** | Gradient section header, modern badges with glow | -| **Skills** | Gradient title, responsive grid, modern cards | -| **Projects** | Premium card interactions, hover glow effects, gradient headers | -| **Experience** | Modern section wrapper, gradient eyebrow, sleek styling | -| **Certifications** | Glassmorphic cards, accent highlighting | -| **Journey** | Premium timeline, gradient section header | -| **GitHub** | Modern card grid, hover elevation effects | -| **Contact** | Spacious modern section, enhanced messaging | - ---- - -## Modern Features Across Portfolio - -✨ **Gradient Text Effects** - -- Applied to: Hero heading, section titles, eyebrows -- Effect: White → Teal → Cyan smooth fade - -🌟 **Glassmorphism** - -- Applied to: Buttons, cards, section backgrounds -- Effect: Backdrop blur + semi-transparent + subtle gradient - -💫 **Glow/Neon Effects** - -- Applied to: Buttons (hover), cards (interactive), badges, stat cards -- Effect: Soft 0_0_30px glow with teal accent - -🎯 **Premium Hover States** - -- Scale-102/105 with smooth ease -- Translate-y[-4px] for elevation illusion -- Enhanced glow shadows on hover -- 300ms smooth transitions - -🔮 **Layered Backgrounds** - -- Multiple gradient layers for depth -- Subtle grid patterns faintly visible -- Gradient overlays from darker to slightly lighter -- All with backdrop blur for premium feel - ---- - -## Code Efficiency - -✅ **No Token Waste** - -- Used existing design system tokens efficiently -- Minimal code duplication -- Reusable gradient patterns via Tailwind utilities -- Shared animation durations across components - -✅ **Production Ready** - -- All animations GPU-accelerated -- No janky transitions -- Smooth 300ms easing throughout -- Responsive across all breakpoints - -✅ **Consistent Aesthetic** - -- Same color palette everywhere (#5EEAD4, #7FFCE8) -- Same animation patterns throughout -- Same spacing rhythm -- Same typography hierarchy - ---- - -## Build Verification - -``` -✓ Compiled successfully in 2.2s -✓ TypeScript strict mode pass -✓ ESLint pass with no warnings -✓ Prettier formatting applied -✓ Page size: 95.4 kB (route) + 329 kB (First Load JS) -``` - ---- - -## How to View - -Start your dev server: - -```bash -npm run dev -``` - -Visit: `http://localhost:3001` - -**Scroll through and experience:** - -- ✨ Hero gradient text entrance animation -- 💫 Glow effects on all interactive elements -- 🎨 Modern gradient cards with hover effects -- 🌟 Smooth section transitions with glassmorphic layers -- ⚡ Premium micro-interactions everywhere - ---- - -## Summary - -Your cybersecurity portfolio now features a **modern, professional, premium aesthetic** that matches contemporary shadcn/ui design patterns. The transformation includes: - -- **Gradient text effects** for modern visual hierarchy -- **Glassmorphic elements** for sophisticated depth -- **Glow/neon accents** for premium feel -- **Smooth 300ms animations** throughout -- **Scale-based hover effects** for tactile feedback -- **Professional color palette** (teal + cyan accents) - -**Everything matches the shadcn/ui reference you provided**, while keeping all your content intact. The design is production-ready, fully responsive, and optimized for performance. - ---- - -## Next Steps - -1. **Explore the site** at `localhost:3001` -2. **Scroll through sections** to see modern animations -3. **Hover over buttons** to see glow effects -4. **Interact with cards** to see elevation effects -5. **Check responsive behavior** on mobile - -If you'd like any adjustments to colors, animation speeds, or specific effects, just let me know and I can fine-tune them immediately! - ---- - -**Status**: ✅ Complete and Production Ready -**Aesthetic**: Modern shadcn/ui design -**Content**: All your cybersecurity expertise preserved -**Performance**: Optimized and fast diff --git a/PRODUCT.md b/PRODUCT.md new file mode 100644 index 0000000..f8bc876 --- /dev/null +++ b/PRODUCT.md @@ -0,0 +1,69 @@ +# PRODUCT.md — Hem Gabhawala Portfolio + +> Locked product context. `DESIGN.md` covers the visual system. +> Supersedes the "Immersive Scenario Edition" brief, which described a dark +> scenario-driven site that no longer exists. + +## Register + +**Brand** — design IS the product. This is a portfolio; a visitor's impression +is the thing being made. Not app UI, not a dashboard. + +## Audience (in priority order) + +1. **Recruiters / internship coordinators** — skim in under 40 seconds, often + on a phone. They hit the fallback visual tier. Credibility must land without + WebGL and without reading a paragraph. +2. **Hiring managers** — want concrete proof: real engagements, real systems, + source where it exists. +3. **Security engineers** — judge technical taste. The measured contrast, the + honest capability schematic and the absence of theatrics are for them. + +## The one job + +Get a recruiter from cold load to composing an email. Everything on the page is +either evidence for that or it is cut. + +## Positioning + +Offensive security (VAPT) practitioner moving into GRC and AI security. The +argument is the arc, not the inventory: an attacker's understanding of how +systems fail, applied to designing systems that don't. Three built platforms +are the proof it isn't theoretical. + +## Brand lane + +**Technical specimen.** Measured, precise, committed. One typeface shown across +its range; four surfaces; one accent used as a redline until it takes the whole +final plate. + +Explicitly not: the dark-neon cyber portfolio (first-order cliché for this +field), and not the light editorial serif page (second-order cliché — display +serif + mono labels + hairline rules, which is where a previous version landed +and why it read as AI-made). + +## Non-negotiables + +- **WCAG AA on every plate**, contrast measured with `culori`, never estimated. + Two real failures were caught this way; see DESIGN.md. +- **3D is progressive enhancement, never a gate.** Full content parity across + the WebGL tier, the fallback tier and server-rendered DOM. +- `prefers-reduced-motion` is a hard override at the animation call site. +- Full keyboard path; the capability schematic carries a text description. +- Fast: `three` never enters the bundle for visitors who cannot use it. +- **No external resource requests.** Nothing hotlinked — an earlier version + pulled its hero portrait from a template site, which CSP blocked in + production anyway. + +## Content rules + +- `data/` is the single source of truth. Components import it directly; no + component-local copies of skills, projects or credentials. +- Nothing on the page that isn't in `data/` or the résumé PDF. +- Two facts per project — what it is, how it's built. The "why it matters" + argument runs once, on the Approach plate. + +## Out of scope + +Light/dark toggle · custom cursor · audio · blog · project detail pages · +mini-game-scale interaction. diff --git a/PROJECT_SUMMARY.md b/PROJECT_SUMMARY.md deleted file mode 100644 index 1d1be29..0000000 --- a/PROJECT_SUMMARY.md +++ /dev/null @@ -1,318 +0,0 @@ -# Premium Portfolio Design System — Project Summary - -## Overview - -You now have a **production-ready, classy, professional design system** integrated across your entire cybersecurity portfolio. The transformation focuses on **minimalism, premium polish, and consistent professional aesthetics** across all sections. - -**Build Status**: ✅ Compiled successfully -**Dev Server**: Running on `localhost:3001` -**Page Size**: 95.1 kB (route) + 328 kB (First Load JS) - ---- - -## What Was Built - -### 1. Master Design System (`/lib/design/design-system.ts`) - -A centralized token definition covering: - -- **Color Palette**: Deep black backgrounds (#0B0C0E), teal accent (#5EEAD4), professional grays -- **Typography**: Clamp-based responsive scales, Inter + JetBrains Mono -- **Spacing**: 8px base unit system (xs–5xl tokens) -- **Animations**: 150–500ms durations with premium easing (expo, back, elastic) -- **Shadows**: Subtle elevation + accent glow effects -- **Component Tokens**: Button, card, input, badge styling definitions -- **Z-Index Stack**: Organized layer management -- **CSS Variables Export**: Global consumption via `getCSSVariables()` - -### 2. Component Library - -#### ButtonPremium (`/components/ui/button-premium.tsx`) - -- **4 Variants**: primary (white→teal), secondary (border), ghost (text), accent (teal bg) -- **3 Sizes**: sm (8px), base (12px), lg (14px) -- **States**: Hover, active (scale-95), disabled (opacity-50) -- **Features**: Loading spinner, icon placement, focus rings, smooth 200ms transitions -- **ButtonGroup**: Organize related actions with spacing options -- **IconButton**: Compact icon-only variant with aria-label - -#### CardPremium (`/components/ui/card-premium.tsx`) - -- **4 Variants**: default, elevated (shadow), bordered, glass (backdrop blur) -- **Interactive Mode**: Hover scale-102 + accent border + glow shadow -- **Subcomponents**: CardHeader, CardTitle, CardContent, CardFooter -- **StatCard**: Display metrics with trend indicators -- **FeatureCard**: Feature showcase with icon, title, description -- **All**: Smooth 200–300ms transitions, professional borders - -#### SectionPremium (`/components/layout/section-premium.tsx`) - -- **Purpose**: Consistent section wrapper for all portfolio sections -- **Spacing Options**: compact, normal, spacious (responsive py values) -- **Variants**: default, dark, glass (gradient + blur), accent -- **Accent Lines**: top, bottom, both, none for subtle visual separation -- **Built-in**: Subtle grid background overlay, max-width container, responsive padding -- **SectionHeader**: Eyebrow badge + title + subtitle pattern (centered, max-width options) -- **SectionGrid**: Responsive grid (1–4 columns) with gap control (compact, normal, loose) - -### 3. Section Refactoring - -All 9 portfolio sections upgraded to use premium components: - -| Section | Before | After | -| ------------------ | ----------------------------- | ------------------------------------------------ | -| **About** | Generic styling, plain badges | SectionPremium + premium teal badges | -| **Skills** | Basic grid | SectionPremium + SectionHeader + responsive grid | -| **Projects** | Standard spacing | SectionPremium + SectionGrid layout | -| **Experience** | Manual styling | Premium section wrapper + consistent headers | -| **Certifications** | Generic cards | SectionPremium with professional spacing | -| **TryHackMe** | Basic layout | Premium section with accent border | -| **Journey** | Standard timeline | SectionPremium with subtle accent lines | -| **GitHub** | Manual grid | SectionGrid with responsive columns | -| **Contact** | Simple form | Spacious SectionPremium with enhanced messaging | - -### 4. Visual Reference Guides - -- **DESIGN_SYSTEM.md**: 350+ lines of implementation documentation -- **Interactive Reference Artifact**: Visual guide with color swatches, button demos, typography samples - ---- - -## Design Principles Implemented - -### Minimalism - -- Remove non-essential elements -- Consistent grid-based spacing -- Purposeful use of color (accent only where it matters) - -### Premium Polish - -- Smooth 200–300ms transitions on all interactions -- Subtle glow shadows on accent elements -- Refined typography hierarchy (clamp() for responsive scaling) -- Professional micro-interactions (hover lift, scale-on-active) - -### Professional Dark Aesthetic - -- Deep black backgrounds (#0B0C0E) for elite feel -- Selective teal accent (#5EEAD4) for CTAs and highlights -- High contrast text (#EDEDEE primary, #8B8D92 secondary) -- Subtle borders (rgba(255,255,255,0.08–0.14)) - -### Consistency - -- One component, one look, everywhere -- Unified section spacing via SectionPremium -- Standardized heading pattern (eyebrow + title + subtitle) -- Responsive grid layouts for all content areas - -### Accessibility - -- WCAG AA compliant (4.5:1 contrast ratio) -- 2px focus rings in teal (#5EEAD4) -- ARIA labels on all icons -- `prefers-reduced-motion` support for animations -- Keyboard-navigable all interactive elements - ---- - -## Files Changed/Created - -### New Components - -``` -/components/ui/button-premium.tsx (167 lines) -/components/ui/card-premium.tsx (233 lines) -/components/layout/section-premium.tsx (174 lines) -/lib/design/design-system.ts (258 lines) -``` - -### Updated Sections (9 files) - -``` -/components/sections/about.tsx • SectionPremium integration -/components/sections/about-content.tsx • Premium badge styling -/components/sections/projects.tsx • SectionHeader + SectionGrid -/components/sections/skills.tsx • Premium section wrapper -/components/sections/journey-timeline.tsx • SectionPremium layout -/components/experience/experience-timeline.tsx • Premium spacing + headers -/components/certifications/certifications.tsx • Standardized styling -/components/certifications/tryhackme-section.tsx • Premium section layout -/components/github/github-section.tsx • SectionGrid + responsive layout -/components/contact/contact-section.tsx • Spacious section + messaging -``` - -### Documentation - -``` -/DESIGN_SYSTEM.md (352 lines — complete guide) -/PROJECT_SUMMARY.md (this file) -/design-system-reference.html (Interactive visual guide) -``` - ---- - -## What This Means for Your Site - -### Visual Impact - -✅ **Classy, minimalist aesthetic** across all sections -✅ **Consistent 12px gap system** for professional rhythm -✅ **Teal accent highlights** on hover states and CTAs -✅ **Smooth micro-interactions** (buttons lift, cards glow) -✅ **Professional typography** with responsive scaling - -### User Experience - -✅ **Clear visual hierarchy** (headings→subtext→body) -✅ **Professional feel** that doesn't look AI-generated -✅ **Fast interactions** (hardware-accelerated transforms) -✅ **Accessible to all users** (keyboard, screen readers, reduced motion) - -### Developer Experience - -✅ **Single source of truth** (design-system.ts) -✅ **Reusable components** (ButtonPremium, CardPremium, SectionPremium) -✅ **Easy to extend** (new sections just wrap in SectionPremium) -✅ **TypeScript-safe** (strict mode, proper types) - ---- - -## How to Use Going Forward - -### Adding a New Section - -```tsx -import { - SectionPremium, - SectionHeader, - SectionGrid, -} from "@/components/layout/section-premium"; - -export function NewSection() { - return ( - - - {/* Your content here */} - - ); -} -``` - -### Using Premium Buttons - -```tsx -import { ButtonPremium, ButtonGroup } from "@/components/ui/button-premium"; - - - - Download Resume - - - View Projects - -; -``` - -### Using Premium Cards - -```tsx -import { - CardPremium, - CardHeader, - CardTitle, - CardContent, -} from "@/components/ui/card-premium"; - - - - Card Title - - Your content here -; -``` - -### Accessing Design Tokens - -```tsx -import { designSystem } from "@/lib/design/design-system"; - -// Colors -const accentColor = designSystem.colors.accent.primary; // #5EEAD4 -const bgPrimary = designSystem.colors.bg.primary; // #0B0C0E - -// Spacing -const spacing = designSystem.spacing.md; // 1.5rem - -// Animations -const duration = designSystem.animations.durations.base; // 200ms -``` - ---- - -## Next Steps (Optional) - -### Performance Optimization - -- [ ] Run Lighthouse audit (target: SEO 100, Performance 90+) -- [ ] Test Core Web Vitals (LCP, FID, CLS) -- [ ] Verify responsive behavior (375px, 768px, 1024px, 1280px) - -### Browser Testing - -- [ ] Test on Chrome, Firefox, Safari, Edge -- [ ] Verify focus states and keyboard navigation -- [ ] Test color contrast with accessibility checker - -### Content Enhancements - -- [ ] Update section subtitles with more compelling copy -- [ ] Add case studies or project depth -- [ ] Enhance About section with personal narrative - -### Animation Fine-tuning - -- [ ] Adjust GSAP animations if needed -- [ ] Test animations on older devices -- [ ] Verify prefers-reduced-motion behavior - ---- - -## Technical Stack - -- **Framework**: Next.js 15 (App Router, Server Components) -- **Styling**: Tailwind CSS 3 + inline design system colors -- **Animations**: GSAP + ScrollTrigger (hero/projects sections) -- **Typography**: Inter (body) + JetBrains Mono (code) -- **Build**: Turbopack (fast builds), TypeScript strict mode -- **Accessibility**: WCAG AA compliant, focus states, ARIA labels - ---- - -## Version - -**Design System v1.0 — Production Ready** - -- Created: 2026-07-19 -- Status: All sections refactored and integrated -- Compatibility: Next.js 15+, Tailwind CSS 3+, React 18+ - ---- - -## Summary - -You've transformed your portfolio into a **premium, professional experience** that looks: - -- ✨ **Classy** — refined without being overly ornate -- 🎯 **Simple** — minimalist, focused, easy to navigate -- 💎 **Attractive** — polished, smooth, professional -- 🏆 **Premium** — consistent, premium feel throughout - -The design system is now the **single source of truth** for your entire site. All future sections can be built using the same components and tokens, ensuring consistency and reducing code duplication. - -**Next**: Start the dev server with `npm run dev` and explore how the new design system feels across all sections! diff --git a/app/api/contact/route.ts b/app/api/contact/route.ts deleted file mode 100644 index a8e28ab..0000000 --- a/app/api/contact/route.ts +++ /dev/null @@ -1,170 +0,0 @@ -import { NextRequest, NextResponse } from "next/server"; - -import { env } from "@/lib/env"; -import { contactFormSchema } from "@/lib/validations/contact"; -import { checkRateLimit, formatTimeUntilReset } from "@/lib/rate-limit"; - -/** - * POST /api/contact - * - * Contact form submission endpoint. Server-side re-validates input, - * sanitizes, checks rate limits, and sends via Resend. - * - * Returns JSON with explicit status: "success" | "error" | "validation_error" | "rate_limited" - * so the client can branch on the outcome and show appropriate messaging. - */ - -/** - * Expected client payload. Note: honeypot and submittedAt are sent by the - * client but NOT included in the validated contactFormSchema — they're - * checked separately before validation. - */ -interface ContactRequest { - name: string; - email: string; - message: string; - honeypot?: string; // Should be empty; filled by bots - submittedAt?: number; // Timestamp when form was submitted (client-side) -} - -export async function POST(request: NextRequest) { - // Extract client IP for rate limiting. Vercel provides x-forwarded-for. - const ip = - request.headers.get("x-forwarded-for")?.split(",")[0]?.trim() || - request.headers.get("x-real-ip") || - "unknown"; - - // 1. Check rate limit early (cheap operation, fail fast). - const rateLimit = checkRateLimit(ip); - if (!rateLimit.allowed) { - return NextResponse.json( - { - status: "rate_limited", - error: "Too many submissions. Please try again later.", - retryAfter: formatTimeUntilReset(rateLimit.resetAt), - }, - { status: 429 }, - ); - } - - // 2. Parse request body. - let body: unknown; - try { - body = await request.json(); - } catch { - return NextResponse.json( - { status: "error", error: "Invalid request body" }, - { status: 400 }, - ); - } - - const payload = body as ContactRequest; - - // 3. Check honeypot (should be empty). - if (payload.honeypot && payload.honeypot.trim().length > 0) { - // Silently reject; don't leak that we detected a bot. - return NextResponse.json( - { status: "success", message: "Thank you! We'll be in touch soon." }, - { status: 200 }, - ); - } - - // 4. Check minimum time to submit (should take at least 1 second to fill the form). - if (payload.submittedAt) { - const submitTimeMs = Date.now() - payload.submittedAt; - if (submitTimeMs < 1000) { - // Likely a bot; silently reject. - return NextResponse.json( - { status: "success", message: "Thank you! We'll be in touch soon." }, - { status: 200 }, - ); - } - } - - // 5. Validate input against the shared schema. - const validationResult = contactFormSchema.safeParse({ - name: payload.name, - email: payload.email, - message: payload.message, - }); - - if (!validationResult.success) { - return NextResponse.json( - { - status: "validation_error", - errors: validationResult.error.flatten().fieldErrors, - }, - { status: 400 }, - ); - } - - const { name, email, message } = validationResult.data; - - // 6. Send via Resend. - try { - // Dynamic import to avoid loading Resend at module initialization time - // (which fails during static generation if RESEND_API_KEY is not set). - const { Resend: ResendClient } = await import("resend"); - const resend = new ResendClient(env.RESEND_API_KEY); - const result = await resend.emails.send({ - from: "Contact Form ", // Use Resend's default sender; configure your domain for production. - to: env.NEXT_PUBLIC_SITE_URL - ? new URL("/", env.NEXT_PUBLIC_SITE_URL).hostname // Extract domain from site URL - : "contact@example.com", // Fallback for development - replyTo: email, - subject: `New contact form submission from ${name}`, - html: ` -

New Contact Form Submission

-

Name: ${escapeHtml(name)}

-

Email: ${escapeHtml(email)}

-

Message:

-
${escapeHtml(message)}
- `, - }); - - if (result.error) { - console.error("Resend API error:", result.error); - return NextResponse.json( - { - status: "error", - error: "Failed to send email. Please try again later.", - }, - { status: 500 }, - ); - } - - return NextResponse.json( - { - status: "success", - message: - "Thank you! I've received your message and will get back to you soon.", - }, - { status: 200 }, - ); - } catch (error) { - console.error("Contact form error:", error); - return NextResponse.json( - { - status: "error", - error: "Something went wrong. Please try again later.", - }, - { status: 500 }, - ); - } -} - -/** - * Escape HTML entities to prevent injection into the email body. - * The schema validates against HTML tags client- and server-side, but - * this adds a defense-in-depth layer for email safety. - */ -function escapeHtml(text: string): string { - const map: Record = { - "&": "&", - "<": "<", - ">": ">", - '"': """, - "'": "'", - }; - return text.replace(/[&<>"']/g, (char) => map[char] || char); -} diff --git a/app/api/github/route.ts b/app/api/github/route.ts deleted file mode 100644 index 1a755d8..0000000 --- a/app/api/github/route.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { NextRequest, NextResponse } from "next/server"; - -import { fetchGitHubRepo, repoToMetadata } from "@/lib/github"; - -/** - * GET /api/github?owner=...&repo=... - * - * Server-side proxy to GitHub API. Accepts owner/repo query params and - * returns sanitized repo metadata (stars, forks, language, updated, description). - * - * - Authentication (GITHUB_TOKEN) happens server-side only, never exposed to client. - * - Caching is handled by fetchGitHubRepo via next: { revalidate: 3600 }. - * - Error responses don't leak internal details — client falls back to static data. - */ -export async function GET(request: NextRequest) { - const { searchParams } = new URL(request.url); - const owner = searchParams.get("owner"); - const repo = searchParams.get("repo"); - - if (!owner || !repo) { - return NextResponse.json( - { error: "Missing owner or repo query parameter" }, - { status: 400 }, - ); - } - - // Validate inputs to prevent injection or abuse — allow only alphanumeric + hyphens. - if (!/^[a-zA-Z0-9_-]+$/.test(owner) || !/^[a-zA-Z0-9_.-]+$/.test(repo)) { - return NextResponse.json( - { error: "Invalid owner or repo format" }, - { status: 400 }, - ); - } - - const repoData = await fetchGitHubRepo(owner, repo); - - if (!repoData) { - // Return 404 so the client knows to use fallback data, not retry. - return NextResponse.json( - { error: "Repository not found or API unreachable" }, - { status: 404 }, - ); - } - - const metadata = repoToMetadata(repoData); - - // Cache successful responses for 1 hour (same as the internal fetch). - // CDN caches via Cache-Control header; Vercel edge cache respects this. - return NextResponse.json(metadata, { - headers: { - "Cache-Control": "public, s-maxage=3600, stale-while-revalidate=86400", - }, - }); -} diff --git a/app/dev/tokens/page.tsx b/app/dev/tokens/page.tsx deleted file mode 100644 index c03dd61..0000000 --- a/app/dev/tokens/page.tsx +++ /dev/null @@ -1,308 +0,0 @@ -"use client"; - -import { motion } from "motion/react"; -import { Button } from "@/components/ui/button"; -import { - Card, - CardContent, - CardDescription, - CardHeader, - CardTitle, -} from "@/components/ui/card"; -import { - Dialog, - DialogContent, - DialogDescription, - DialogHeader, - DialogTitle, - DialogTrigger, -} from "@/components/ui/dialog"; -import { Input } from "@/components/ui/input"; -import { - Sheet, - SheetContent, - SheetDescription, - SheetHeader, - SheetTitle, - SheetTrigger, -} from "@/components/ui/sheet"; -import { ThemeToggle } from "@/components/shared/theme-toggle"; -import { fadeIn, fadeUp, staggerChildren, subtleHover } from "@/lib/motion"; - -/** - * Design-tokens showcase (Phase 0.2 review artifact). - * Dev-only reference page — never linked from production navigation. - * Every color, type size, spacing step, radius, motion preset, and the - * glass utility is rendered here for review against Docs/03_UI_UX.md. - */ - -const colorPairs = [ - { - name: "background / foreground", - bg: "bg-background", - fg: "text-foreground", - border: true, - }, - { - name: "card / card-foreground", - bg: "bg-card", - fg: "text-card-foreground", - border: true, - }, - { - name: "popover / popover-foreground", - bg: "bg-popover", - fg: "text-popover-foreground", - border: true, - }, - { - name: "primary / primary-foreground", - bg: "bg-primary", - fg: "text-primary-foreground", - }, - { - name: "secondary / secondary-foreground", - bg: "bg-secondary", - fg: "text-secondary-foreground", - }, - { - name: "muted / muted-foreground", - bg: "bg-muted", - fg: "text-muted-foreground", - }, - { - name: "accent / accent-foreground", - bg: "bg-accent", - fg: "text-accent-foreground", - }, - { - name: "destructive / destructive-foreground", - bg: "bg-destructive", - fg: "text-destructive-foreground", - }, -] as const; - -const typeScale = [ - { cls: "text-xs", label: "text-xs — captions, badges" }, - { cls: "text-sm", label: "text-sm — secondary UI text" }, - { cls: "text-base", label: "text-base — body copy" }, - { cls: "text-lg", label: "text-lg — lead paragraph" }, - { cls: "text-xl", label: "text-xl — card titles" }, - { cls: "text-2xl", label: "text-2xl — section subheading" }, - { cls: "text-3xl", label: "text-3xl — section heading" }, - { cls: "text-4xl", label: "text-4xl — page heading" }, - { cls: "text-6xl", label: "text-6xl — hero display" }, -] as const; - -const spacingSteps = [ - "w-1", - "w-2", - "w-4", - "w-6", - "w-8", - "w-12", - "w-16", - "w-24", -] as const; - -const radiusSteps = [ - { cls: "rounded-sm", label: "sm" }, - { cls: "rounded-md", label: "md" }, - { cls: "rounded-lg", label: "lg" }, - { cls: "rounded-xl", label: "xl" }, -] as const; - -function Section({ - title, - children, -}: { - title: string; - children: React.ReactNode; -}) { - return ( -
-

{title}

- {children} -
- ); -} - -export default function TokensPage() { - return ( -
-
-
-

Phase 0.2 — dev only

-

- Design tokens -

-

- Reference sheet for every token. Ratios documented in - styles/CONTRAST.md; enforced by lib/design/contrast.test.ts. -

-
- -
- -
-
- {colorPairs.map((pair) => ( -
-

Aa

-

{pair.name}

-
- ))} -
-
- -
-
- {typeScale.map((t) => ( -

- {t.label} -

- ))} -

- font-mono — Geist Mono for technical accents -

-
-
- -
-
- {spacingSteps.map((w) => ( -
-
- - {w} - -
- ))} -
-
- -
-
- {radiusSteps.map((r) => ( -
- - {r.label} - -
- ))} -
-
- -
-
- - - - - - - - - - - - Themed dialog - - Surface, border, and focus ring all come from tokens. - - - - - - - - - - - Themed sheet - - Used for mobile navigation in Phase 2. - - - - -
-
- -
-
- -
- - - - - fadeUp - Entrance for sections - - - - - - - fadeIn - Opacity-only entrance - - - - - - - subtleHover - Hover me — 1.02 scale - - - - -
- -
-
-
- {colorPairs.map((pair) => ( -
- ))} -
-
-
-
-
-
-

.glass

-

- Translucent card surface + backdrop blur + soft border. Reserved - for floating surfaces (navbar, overlays) — not every card. -

-
-
-
- - - -

- Rules: no hardcoded colors, sizes, or durations outside - styles/globals.css and lib/motion.ts. Later phases extend this - system — they never reinvent it. -

-
-
-
- ); -} diff --git a/app/error.tsx b/app/error.tsx deleted file mode 100644 index b3ba671..0000000 --- a/app/error.tsx +++ /dev/null @@ -1,25 +0,0 @@ -"use client"; - -import { Button } from "@/components/ui/button"; - -/** Root error boundary — App Router requires this to be a Client Component. */ -export default function Error({ - error, - reset, -}: { - error: Error & { digest?: string }; - reset: () => void; -}) { - return ( -
-

Something went wrong

-

- An unexpected error occurred - {error.digest ? ` (ref: ${error.digest})` : ""}. -

- -
- ); -} diff --git a/app/layout.tsx b/app/layout.tsx index 5d6e0db..ac33fca 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,96 +1,103 @@ import type { Metadata } from "next"; -import { Geist, Geist_Mono } from "next/font/google"; -import { Footer } from "@/components/layout/footer"; +import { Archivo } from "next/font/google"; +import { CapabilityProvider } from "@/lib/capability"; import { SiteHeader } from "@/components/layout/site-header"; -import { PageTransition } from "@/components/shared/page-transition"; -import { ScrollProgress } from "@/components/layout/scroll-progress"; -import { MotionProvider } from "@/components/providers/motion-provider"; -import { ThemeProvider } from "@/components/providers/theme-provider"; -import { LayoutClient } from "@/components/layout/layout-client"; -import { getProfile } from "@/lib/data"; -import { env } from "@/lib/env"; -import { generatePersonSchema } from "@/lib/seo/jsonld"; -import { DEFAULT_OG_IMAGE } from "@/lib/seo/config"; +import { SiteFooter } from "@/components/layout/footer"; +import { + SITE_NAME, + SITE_TITLE, + SITE_URL, + SITE_DESCRIPTION, +} from "@/lib/seo/config"; +import { generatePersonSchema, generateWebsiteSchema } from "@/lib/seo/jsonld"; import "@/styles/globals.css"; -const geistSans = Geist({ - variable: "--font-geist-sans", +/** + * One typeface, shown across its range — see styles/globals.css. + * + * Archivo is variable on both width (62–125) and weight (100–900), and the + * width axis is what carries hierarchy here: expanded for display, normal for + * reading, condensed for dense data. A single family exercised properly beats + * a display+body pair that hedges, and it means there is no second font to + * load at all. + * + * "optional" because the hero

is the LCP element and a fallback-font swap + * there was the largest layout-shift contributor; the browser skips the swap + * once the metric-matched fallback has painted, trading a rare cold-cache + * mismatch for zero CLS. + */ +const archivo = Archivo({ + variable: "--font-archivo", subsets: ["latin"], + axes: ["wdth"], + display: "optional", }); -const geistMono = Geist_Mono({ - variable: "--font-geist-mono", - subsets: ["latin"], -}); - -const { name, role } = getProfile(); -const siteName = `${name} — Cybersecurity Portfolio`; -const description = `${name}, ${role}. Hands-on offensive security: penetration testing, security tooling, certifications, and TryHackMe achievements.`; - +// OpenGraph/Twitter images are auto-populated by Next from app/opengraph-image.tsx. export const metadata: Metadata = { - metadataBase: new URL(env.NEXT_PUBLIC_SITE_URL ?? "http://localhost:3000"), - title: { - default: siteName, - template: "%s — Hem Gabhawala", - }, - description, + metadataBase: new URL(SITE_URL), + title: { default: SITE_TITLE, template: `%s — ${SITE_NAME}` }, + description: SITE_DESCRIPTION, + applicationName: SITE_NAME, + authors: [{ name: SITE_NAME }], + creator: SITE_NAME, + keywords: [ + "cybersecurity", + "penetration testing", + "VAPT", + "GRC", + "AI security", + "cloud security", + "portfolio", + SITE_NAME, + ], + alternates: { canonical: "/" }, openGraph: { type: "website", url: "/", - siteName, - title: siteName, - description, - images: [DEFAULT_OG_IMAGE], + siteName: SITE_NAME, + title: SITE_TITLE, + description: SITE_DESCRIPTION, }, twitter: { card: "summary_large_image", - title: siteName, - description, - images: [DEFAULT_OG_IMAGE.url], - }, - icons: { - icon: "/favicon.ico", + title: SITE_TITLE, + description: SITE_DESCRIPTION, }, robots: { index: true, follow: true, - nocache: false, - googleBot: { - index: true, - follow: true, - }, + googleBot: { index: true, follow: true }, }, + icons: { icon: "/favicon.ico" }, }; export default function RootLayout({ children, -}: Readonly<{ - children: React.ReactNode; -}>) { - const personSchema = generatePersonSchema(); - +}: Readonly<{ children: React.ReactNode }>) { return ( - - + + ", - "Hello there, nice site you have", - "Interested in your work click thanks", - " plus some padding text to pass min length", - ])("rejects HTML/script injection in message: %j", (message) => { - expect(contactFormSchema.safeParse({ ...valid, message }).success).toBe( - false, - ); - }); - - it("rejects HTML injection in name", () => { - expect( - contactFormSchema.safeParse({ ...valid, name: "Jane" }).success, - ).toBe(false); - }); - - it("rejects control characters (header-injection style)", () => { - expect( - contactFormSchema.safeParse({ - ...valid, - message: "legit looking message\u0000with a null byte inside", - }).success, - ).toBe(false); - }); - - it("allows plain multi-line messages", () => { - expect( - contactFormSchema.safeParse({ - ...valid, - message: "Line one of the message.\nLine two, still plain text.", - }).success, - ).toBe(true); - }); -}); diff --git a/lib/validations/contact.ts b/lib/validations/contact.ts deleted file mode 100644 index 61c0c1c..0000000 --- a/lib/validations/contact.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { z } from "zod"; -import type { ContactFormInput } from "@/lib/types"; - -/** - * Contact form validation — the exact schema Phase 5's form and rate-limited - * API route will consume. Deliberately strict: this is a security boundary - * for user-supplied input that ends up in an outbound email. - */ - -/** Any HTML tag-like sequence: blocks `