Skip to content

Commit c1ad8e1

Browse files
Merge pull request #9 from securitywithhem/scenario-rebuild
feat: complete resume system + certificate verification + performance fixes
2 parents 6fdbc34 + 82fc358 commit c1ad8e1

17 files changed

Lines changed: 113 additions & 10 deletions

components/approach/lattice-fallback.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,8 @@ export function LatticeFallback() {
6969
{pts.map((p, i) => (
7070
<circle
7171
key={i}
72-
cx={p.x}
73-
cy={p.y}
72+
cx={parseFloat(p.x.toFixed(1))}
73+
cy={parseFloat(p.y.toFixed(1))}
7474
r={1.6}
7575
fill="var(--paper)"
7676
fillOpacity={0.6}

components/resume/resume-modal.tsx

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
"use client";
2+
3+
import { useState, useEffect } from "react";
4+
import { Download, X } from "lucide-react";
5+
6+
/**
7+
* Resume selection modal. Triggered by Resume button in Hero section.
8+
* Allows user to choose between Software Developer or Cybersecurity/GRC resume.
9+
*/
10+
export function ResumeModal() {
11+
const [open, setOpen] = useState(false);
12+
13+
useEffect(() => {
14+
const handleEscape = (e: KeyboardEvent) => {
15+
if (e.key === "Escape") setOpen(false);
16+
};
17+
if (open) document.addEventListener("keydown", handleEscape);
18+
return () => document.removeEventListener("keydown", handleEscape);
19+
}, [open]);
20+
21+
return (
22+
<>
23+
{/* Resume Trigger Button */}
24+
<button
25+
onClick={() => setOpen(true)}
26+
className="link-underline text-[0.9rem] font-medium"
27+
>
28+
Resume
29+
</button>
30+
31+
{/* Modal Backdrop + Dialog */}
32+
{open && (
33+
<div
34+
className="fixed inset-0 z-50 flex items-center justify-center bg-black/40 backdrop-blur-sm"
35+
onClick={() => setOpen(false)}
36+
role="presentation"
37+
>
38+
<div
39+
className="relative w-full max-w-md rounded-lg bg-paper p-8 shadow-lg"
40+
onClick={(e) => e.stopPropagation()}
41+
role="dialog"
42+
aria-labelledby="resume-dialog-title"
43+
aria-modal="true"
44+
>
45+
{/* Close Button */}
46+
<button
47+
onClick={() => setOpen(false)}
48+
className="absolute top-4 right-4 rounded p-1 transition-colors hover:bg-ink-muted"
49+
aria-label="Close dialog"
50+
>
51+
<X size={20} className="text-ink" />
52+
</button>
53+
54+
{/* Title */}
55+
<h2
56+
id="resume-dialog-title"
57+
className="type-plate mb-6 pr-8 text-ink"
58+
>
59+
Download Resume
60+
</h2>
61+
62+
{/* Description */}
63+
<p className="type-body mb-8 text-ink-muted">
64+
Choose the resume that best fits the role you&apos;re applying
65+
for.
66+
</p>
67+
68+
{/* Download Options */}
69+
<div className="flex flex-col gap-3">
70+
<a
71+
href="/resumes/resume-software-developer.pdf"
72+
download="Hem-Gabhawala-Resume-Software-Developer.pdf"
73+
onClick={() => setOpen(false)}
74+
className="inline-flex items-center justify-center gap-2 rounded bg-ink px-6 py-3 text-[0.9rem] font-medium text-paper transition-colors hover:bg-accent motion-reduce:transition-none"
75+
>
76+
<Download size={16} />
77+
Software Developer
78+
</a>
79+
80+
<a
81+
href="/resumes/resume-cybersecurity-grc.pdf"
82+
download="Hem-Gabhawala-Resume-Cybersecurity-GRC.pdf"
83+
onClick={() => setOpen(false)}
84+
className="inline-flex items-center justify-center gap-2 rounded border border-ink bg-paper px-6 py-3 text-[0.9rem] font-medium text-ink transition-colors hover:bg-ink-muted motion-reduce:transition-none"
85+
>
86+
<Download size={16} />
87+
Cybersecurity / GRC
88+
</a>
89+
</div>
90+
91+
{/* Close hint (optional) */}
92+
<p className="type-data mt-6 text-center text-ink-dim">
93+
Press Esc or click outside to close
94+
</p>
95+
</div>
96+
</div>
97+
)}
98+
</>
99+
);
100+
}

components/sections/hero.tsx

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { profile } from "@/data/profile";
22
import { HeroReveal } from "./hero-reveal";
3+
import { ResumeModal } from "@/components/resume/resume-modal";
34

45
/**
56
* The four figures a recruiter should retain after three seconds. Rendered as
@@ -75,12 +76,7 @@ export function Hero() {
7576
>
7677
Email me
7778
</a>
78-
<a
79-
href={profile.resumeUrl}
80-
className="link-underline text-[0.9rem] font-medium"
81-
>
82-
Resume
83-
</a>
79+
<ResumeModal />
8480
</div>
8581
</div>
8682
</HeroReveal>

data/certificates.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,13 @@ export const certificates = [
2828
date: "2026-04-01",
2929
credentialUrl: null,
3030
},
31+
{
32+
id: "cert-isea",
33+
title: "Information Security Education and Awareness (ISEA)",
34+
issuer: "CDAC / MeitY",
35+
date: "2024-07-24",
36+
credentialUrl: null,
37+
},
3138
] satisfies Certificate[];
3239

3340
/** Newest first. */

data/tryhackme.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,14 @@ export const tryHackMe = [
1010
id: "thm-offensive-pentesting",
1111
title: "Offensive Pentesting",
1212
issuer: "TryHackMe",
13-
date: "2025-08-01",
13+
date: "2025-08-06",
1414
credentialUrl: null,
1515
},
1616
{
1717
id: "thm-web-app-pentesting",
1818
title: "Web Application Pentesting",
1919
issuer: "TryHackMe",
20-
date: "2025-09-01",
20+
date: "2025-09-20",
2121
credentialUrl: null,
2222
},
2323
{
953 KB
Binary file not shown.
953 KB
Binary file not shown.
346 KB
Binary file not shown.

public/certificates/cert-isea.pdf

216 KB
Binary file not shown.
253 KB
Loading

0 commit comments

Comments
 (0)