Skip to content
muhammad1502Public

About

Personal CV / portfolio — Muhammad Abdullah, Cybersecurity Analyst

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Muhammad Abdullah: portfolio

Personal portfolio for Muhammad Abdullah, Security Operations Analyst. It's a single-page, static site designed in the style of apple.com, with light/dark themes and subtle scroll animations. Live at https://mabddullah.vercel.app.


Stack

Layer Choice
Build Vite 8
UI React 19 + TypeScript
Styling One plain stylesheet, src/styles/site.css (CSS custom properties)
Motion CSS transitions + IntersectionObserver (no animation library)
Icons lucide-react (GitHub and LinkedIn marks in brand-icons.ts)
Font SF Pro via the system stack on Apple devices; self-hosted Inter elsewhere (@fontsource-variable/inter)
Hosting Vercel (Git-connected, auto-deploy on push to main)

There is no router and no backend: it's a static single page, pre-rendered to HTML at build time and hydrated in the browser. All content is data-driven from a single TypeScript file.


Project layout

.
├── index.html                # Head: meta, OG/Twitter, JSON-LD, favicon, FOUC script
├── public/                   # Served at site root, copied verbatim into dist/
│   ├── favicon.png
│   ├── apple-touch-icon.png
│   ├── og.png                # 1200×630 social share card
│   ├── robots.txt            # Allows search + AI crawlers; points to sitemap
│   ├── sitemap.xml
│   └── llms.txt              # Plain-text profile for LLM crawlers (AI readability)
├── src/
│   ├── main.tsx              # Entry; hydrates the pre-rendered <App> (or renders /?print)
│   ├── entry-server.tsx      # Build-time render of <App> to HTML
│   ├── app/
│   │   ├── App.tsx           # Page composition: hero, about, experience, projects, skills, certs, contact
│   │   ├── lib/
│   │   │   ├── sections.ts       # In-page nav items + resume PDF path (nav + footer)
│   │   │   ├── metrics.tsx       # **metric** emphasis renderer, skills list splitter
│   │   │   ├── useThemeMode.ts   # Light/dark: follows OS until toggled, then stored
│   │   │   └── motion.ts         # prefers-reduced-motion helper
│   │   └── components/
│   │       ├── resume-data.ts      # ⭐ ALL content + types (edit here)
│   │       ├── GlobalNav.tsx       # Sticky translucent nav; full-screen menu ≤833px
│   │       ├── Hero.tsx            # Photo, name, title, Download CV / Contact
│   │       ├── ExperienceTile.tsx  # One card per role, with stats and buttons
│   │       ├── CountUp.tsx         # Animated stat numbers
│   │       ├── DetailsModal.tsx    # "Learn more" overlay (native <dialog>)
│   │       ├── Projects.tsx        # Project cards (grid; swipe carousel on phones)
│   │       ├── BackToTop.tsx       # Back-to-top button with reading-progress ring
│   │       ├── ThemeIcon.tsx       # Sun/moon morphing icon
│   │       ├── SectionChips.tsx    # Phone sticky section chips
│   │       ├── CommandPalette.tsx  # ⌘K / Ctrl+K quick actions
│   │       ├── Toast.tsx           # Confirmation toasts (aria-live)
│   │       ├── Footer.tsx          # Directory columns + legal line
│   │       └── PrintResume.tsx     # Optional print layout at /?print (data-driven)
│   ├── styles/
│   │   ├── site.css                # All site styles + light/dark tokens
│   └── imports/
│       └── muhammad-abdullah.jpg   # Profile photo (EXIF-stripped, 384px)
├── vercel.json               # Framework, build, cache + security headers
├── vite.config.ts            # Plugins + manual vendor chunk splitting
└── tsconfig.json

Editing content

All resume content lives in src/app/components/resume-data.ts: profile, contacts, experience, projects, skills and certifications. The React app imports and renders it; the components are generic and never hardcode copy.

Edit resume-data.ts directly. The types (ResumeEntry, Role, SkillGroup, Certification, etc.) are defined at the top of the file and enforce the structure of each entry: TypeScript will flag a malformed entry at build time (npm run build).

Each experience entry supports optional bullets (string list), sections (labeled paragraphs), roles (sub-positions), href (adds a "View on GitHub" / "Visit website" button) and stats (big animated numbers on the tile). Entries with sections, bullets or roles get a Learn more button that opens the full detail in a modal. Text wrapped in **double asterisks** in any body string (including profile.about) renders emphasized.

A project can also carry an optional writeup (a ResumeEntry with sections). Its card then gets a Read write-up button that opens it in the same modal.

stats should only restate figures that already appear in that entry's copy.


Develop

npm install
npm run dev        # http://localhost:5173

Build

npm run build      # type-check, client build, server build, then scripts/prerender.mjs
                   # bakes the page HTML (and an Inter preload) into dist/index.html
npm run preview    # serve the production build locally

Requires Node 20.19+ or 22.12+ (what Vite 8 needs; pinned via engines in package.json).


Design & theming

  • Modelled on apple.com: values (type scale, colours, 44px blurred nav, pill buttons, breakpoints 734 / 833 / 1068px) are taken from apple.com's own stylesheets and documented at the top of site.css.
  • Every CTA uses one button size (44px tall); buttons in a pair are equal width.
  • Light/dark tokens live on :root / :root[data-theme='dark'] in site.css. The choice is stored in localStorage (theme-mode) and otherwise follows the OS prefers-color-scheme.
  • An inline script in index.html sets data-theme and the background before React mounts, so dark-mode users never see a light flash (FOUC).
  • All content renders immediately (no fade-in or scroll-reveal). Motion only decorates what is already on screen. Desktop: a ⌘K / Ctrl+K quick-actions palette (jump to a section, download the CV, copy email with a toast, open LinkedIn/GitHub, switch theme), a slight cursor tilt on cards and hover to replay a stat's count-up. Everywhere: scroll-linked effects (cards and headlines settle into place, the hero recedes, About text lights up word by word as you read), count-up stats, a back-to-top button with a reading-progress ring, a current-section marker (sliding underline in the nav, a dot in the phone menu, sticky section chips on phones), a terminal-style whoami line in About, a cursor spotlight and lift on cards, a tap ripple and press-in on touch screens, a phone bottom-sheet for details (swipe down to close), a swipeable Projects carousel with page dots on phones, light haptic ticks on Android, a crossfade between themes (View Transitions, with a colour-fade fallback) with a sun-to-moon icon morph, and small button hover glows. Under prefers-reduced-motion all movement is off; the theme crossfade stays because a fade has no motion.

Assets & images

  • The profile photo (src/imports/muhammad-abdullah.jpg) is EXIF-stripped (GPS/device metadata removed) and resized to 384px: it displays at 144px in a circular avatar. To swap it, replace that file (keep it small; Vite hashes and emits it into dist/assets/). If the image is ever missing, the avatar falls back to the MA initials monogram (see Hero.tsx).
  • Favicon, apple-touch-icon, and the OG image live in public/ (NOT dist/, dist/ is wiped and rebuilt on every Vercel deploy).
  • public/resume.pdf is the downloadable CV. It is generated from the /?print layout (PrintResume.tsx), which reads the same resume-data.ts as the site, so the two never disagree. The layout is a single column with standard headings so applicant tracking systems parse it in order. To regenerate after editing content: npm run build && npm run preview, open http://localhost:4173/?print in Chrome, Print, Save as PDF (paper size Letter, margins Default, headers and footers off), and replace public/resume.pdf.

Deploy (Vercel)

Connected to this GitHub repo. Every push to main triggers a production build. All config is in vercel.json:

  • Framework vite, build npm run build, output dist
  • No rewrites: the site is a single page, so unknown paths return a real 404 (public/404.html)
  • Cache-Control: immutable (1 year) on hashed /assets/*
  • Security headers: a strict CSP (script-src 'self', no inline scripts; the pre-paint theme script lives in public/theme-init.js), X-Frame-Options: DENY, X-Content-Type-Options, Referrer-Policy, Permissions-Policy and Cross-Origin-Opener-Policy. Vercel adds HSTS and the HTTP to HTTPS redirect.
  • public/.well-known/security.txt (RFC 9116) lists the security contact. Update its Expires date before 2027-09-28. SECURITY.md points GitHub's Security tab to the same contact, and .github/dependabot.yml opens weekly grouped dependency-update PRs.

Domain note: absolute URLs (OG image, canonical, JSON-LD, sitemap, robots, llms.txt, security.txt, the CV) point to the live domain https://mabddullah.vercel.app. The original muhammad-abdullah-resume.vercel.app permanently redirects (308) there. If a custom domain is added later, update those references across index.html and public/ (sitemap.xml, robots.txt, llms.txt).


SEO & AI readability

  • Meta: title, description, Open Graph + Twitter cards, canonical, theme-color.
  • JSON-LD Person schema in index.html: gives search engines and AI crawlers structured facts (name, role, employer, skills, socials).
  • robots.txt explicitly allows search and AI crawlers (GPTBot, ClaudeBot, PerplexityBot, Google-Extended) and points to the sitemap.
  • llms.txt: a plain-text profile summary for LLM crawlers, so AI answers about "Muhammad Abdullah" stay accurate.

To preview share cards: opengraph.xyz or metatags.io. Platforms cache OG data hard: use their debuggers (LinkedIn Post Inspector, Facebook Sharing Debugger) to force a re-scrape.

About

Personal CV / portfolio — Muhammad Abdullah, Cybersecurity Analyst

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages