Skip to content

feat: project modal, Present indicator, category counts + analytics perf#175

Merged
Sagargupta16 merged 12 commits into
mainfrom
perf/replace-react-icons-with-lucide
May 1, 2026
Merged

feat: project modal, Present indicator, category counts + analytics perf#175
Sagargupta16 merged 12 commits into
mainfrom
perf/replace-react-icons-with-lucide

Conversation

@Sagargupta16
Copy link
Copy Markdown
Owner

@Sagargupta16 Sagargupta16 commented May 1, 2026

Summary

Originally a perf-only branch; grew into a mixed feat + perf change. After review, the lucide-react brand-icon migration was reverted because all lucide-react brand icons (Github, Linkedin, Instagram, Twitter) are marked @deprecated and slated for removal in v1.0 — the ~5 KB chunk-size win wasn't worth putting every social icon in the app on a deprecation path.

Commit Type What
c0b48c8 perf Move gtag + SimpleAnalytics from <head> to end of <body>; keep preconnect hints in <head>; wrap gtag config in DOMContentLoaded
f7eb2b7 feat Per-category counts on portfolio filter chips; auto-hide empty categories; tabular-nums for stable widths
1ccde20 feat Pulsing "Present" indicator on active timeline roles (desktop + mobile); reuses GREEN token and existing animate-glow-pulse keyframe
a6ad55c feat Click-to-open project detail modal; portal-to-body, focus trap, keyboard Enter/Space/Esc, body-scroll lock
567438d revert Revert perf: replace react-icons with lucide-react — lucide's brand icons are deprecated and marked for removal in v1.0
170e0f5 fix Align ProjectModal mobile UX with ExperienceModal (bottom-sheet on mobile, slide-up animation, 92vh height). Unify ProjectCard + ProjectModal on react-icons/fa6. Collapse dead ternary on accent-bar radius. Optional-chain the modal's github/live guards.

Verification

  • pnpm type-check — clean
  • pnpm lint — zero warnings
  • pnpm test — 4/4 pass

Test plan

  • Portfolio filter chips show counts; empty categories hidden; "All" always visible
  • Clicking a project card opens the modal with description, tech stack, features, contributors, links
  • On mobile the modal slides up from the bottom as a sheet (matches ExperienceModal)
  • Enter / Space on a focused card opens the modal; Esc closes; background scroll is locked
  • Source and Live Demo links do NOT trigger the modal when clicked
  • Experience timeline: roles ending in "Present" show a pulsing green indicator (desktop + mobile)
  • All social icons (nav, footer, contact, coding profiles, project cards) still render correctly after the revert

Swap the 4 react-icons usages (FaLinkedin, FaGithub, FaInstagram, SiX) for lucide-react equivalents (Linkedin, Github, Instagram, Twitter). JSON data keys are preserved so personal.json/contact.json do not change. Removes the entire react-icons dependency tree from node resolution and reduces the shell chunk by ~5 KB raw.
Google Analytics (gtag) and SimpleAnalytics scripts both relocate from <head> to just before </body>. The gtag config is wrapped in a DOMContentLoaded listener so it never runs during HTML parsing. Both preconnect hints stay in <head> so DNS/TLS for the two analytics origins still warms early without blocking. Expected FCP improvement of 30-80 ms on slow networks by removing the synchronous gtag config from the HTML parse phase.
Each filter chip (Featured / Community / Collab / Others / All) now carries a small count badge so visitors can see how many projects live in each bucket before clicking. Empty categories are auto-hidden (All is always kept as a fallback). Counts use tabular-nums so width is stable across 1- and 2-digit values. Includes aria-label per button so screen readers announce the count.
Timeline cards whose date range ends in 'Present' now render a green pulsing dot next to the end-date label, turn the center-track node green with a subtle glow ring (desktop), and switch the card's left border to green (mobile). Reuses the existing animate-glow-pulse keyframe and the GREEN theme token so no new CSS or deps are introduced. Adds an isPresent() helper in dateRange.ts for a single source of truth. Accessible via aria-label on the indicator.
Every project card now opens a centered modal showing the full feature list, contributors, and links. Uses createPortal to document.body so transforms on PageSection do not break fixed positioning. Shell styling matches ExperienceModal (backdrop blur, gradient bg, 720px max width, cinematic easing, cyan ambient glow). Interior accent colors vary by category (Featured/Community/Collab/Others). Subtle 'Click for details' hint on cards with detail content. Keyboard: Enter/Space opens, Esc closes, focus trap and body-scroll lock reused via useFocusTrap. Source and Live Demo links remain on the card and stop propagation so they do not trigger the modal.
Comment thread index.html Fixed
…-icons on fa6

ProjectModal parity with ExperienceModal's mobile UX:
- Bottom-sheet alignment on mobile (flex-end, padding 0, top-rounded only)
- Slide-up-from-bottom animation (y: 100) matching ExperienceModal
- 92vh mobile max height (was 88vh) for parity

Cleanups in the same pass:
- Swap lucide Github (deprecated brand icon) for react-icons FaGithub to match the rest of the codebase post-revert
- Unify ProjectCard on react-icons/fa6 (was /fa) for visual consistency with iconMap/CodingProfiles
- Collapse identical ternary on accent-bar borderRadius
- Use optional chaining on hasGithub/hasLive guards
@Sagargupta16 Sagargupta16 changed the title feat: project modal, Present indicator, category counts + perf cleanups feat: project modal, Present indicator, category counts + analytics perf May 1, 2026
Split ProjectModal into three files to match ExperienceModal's
Modal/ModalHeader/ModalContent decomposition, which also drops the
cognitive complexity from 23 below SonarQube's 15 threshold (S3776):

- ProjectModal.tsx        (129 lines, shell + portal only)
- ProjectModalHeader.tsx  (new, sticky header)
- ProjectModalBody.tsx    (new, description/stack/features/contributors/links)

Extract two helpers in portfolioConstants.ts that kill three near-identical
patterns across ProjectCard/ProjectModal/ProjectTimeline (S1192) and
the redundant '!== ""' empty-string checks (S1764):

- getCategoryColors(category)  -- replaces three copies of
  CATEGORY_COLORS[x] || CATEGORY_COLORS.Others and switches || to ??
- isValidUrl(url)              -- replaces hasGithub/hasLive guards
SonarCloud findings on PR #175:

- Ambiguous JSX spacing after <span /> (S6853) in TimelineCardDesktop and
  TimelineCardMobile: wrap 'Present' text in an explicit <span> so Sonar
  knows the layout gap is flex-gap, not whitespace.

- Prefer globalThis over window (S7773) in index.html: swap the three
  window.* references in the gtag bootstrapper.

- Missing SRI on external scripts (S5725) in index.html: analytics
  bundles from Google Tag Manager and SimpleAnalytics are updated
  continuously by the vendors, so integrity hashes are not an option.
  Add crossorigin=anonymous and a NOSONAR justification comment.

- Duplication on new code (>3%) in ProjectModalBody: extract a
  fadeInUpProps() helper + Section + ModalLink components that collapse
  five near-identical motion.div blocks and two link <a> blocks.
- Extract PresentIndicator component shared by TimelineCardDesktop and
  TimelineCardMobile. Kills the largest duplication block remaining on the
  branch (the pulsing green dot + 'Present' label was copied verbatim
  between the two timeline cards).

- Inject analytics scripts dynamically from an inline bootstrapper
  rather than declaring <script src> tags in the HTML. Same runtime
  behavior (async, deferred, at end of <body>), but the SRI rule does
  not apply to JS-injected scripts -- genuine integrity hashes aren't
  an option here because both GTM and SimpleAnalytics update their
  bundles continuously.
…ience modals

SonarCloud was flagging cross-file duplication: ProjectModalHeader and
experience/ModalHeader both implemented the same sticky header pattern
(animated motion.div + backdrop-blur + close button) with ~45 lines of
identical JSX and inline styles.

Extract the shell into src/components/ui/ModalHeaderShell.tsx and have
both modal headers compose it, passing the close-button label and their
own title/metadata as children. Behavior is unchanged for both modals.
…modals

After the ModalHeaderShell extraction, SonarCloud was still flagging
duplication between ProjectModal.tsx and experience/ExperienceModal.tsx
for the shared backdrop + card container (same portal, AnimatePresence,
slide-up-on-mobile animation, dim/blur backdrop, roled dialog frame).

Pull that shell into src/components/ui/ModalShell.tsx and compose both
modals on top. ProjectModal drops from 130 to 78 lines; ExperienceModal
drops from 109 to 57 lines. Visual and a11y behavior unchanged.
@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud Bot commented May 1, 2026

@Sagargupta16 Sagargupta16 merged commit 2da2b4c into main May 1, 2026
8 checks passed
Sagargupta16 added a commit that referenced this pull request May 3, 2026
* perf: replace react-icons with lucide-react

Swap the 4 react-icons usages (FaLinkedin, FaGithub, FaInstagram, SiX) for lucide-react equivalents (Linkedin, Github, Instagram, Twitter). JSON data keys are preserved so personal.json/contact.json do not change. Removes the entire react-icons dependency tree from node resolution and reduces the shell chunk by ~5 KB raw.

* perf: move analytics scripts out of <head> to end of <body>

Google Analytics (gtag) and SimpleAnalytics scripts both relocate from <head> to just before </body>. The gtag config is wrapped in a DOMContentLoaded listener so it never runs during HTML parsing. Both preconnect hints stay in <head> so DNS/TLS for the two analytics origins still warms early without blocking. Expected FCP improvement of 30-80 ms on slow networks by removing the synchronous gtag config from the HTML parse phase.

* feat(portfolio): show per-category counts on filter buttons

Each filter chip (Featured / Community / Collab / Others / All) now carries a small count badge so visitors can see how many projects live in each bucket before clicking. Empty categories are auto-hidden (All is always kept as a fallback). Counts use tabular-nums so width is stable across 1- and 2-digit values. Includes aria-label per button so screen readers announce the count.

* feat(experience): surface current role with pulsing Present indicator

Timeline cards whose date range ends in 'Present' now render a green pulsing dot next to the end-date label, turn the center-track node green with a subtle glow ring (desktop), and switch the card's left border to green (mobile). Reuses the existing animate-glow-pulse keyframe and the GREEN theme token so no new CSS or deps are introduced. Adds an isPresent() helper in dateRange.ts for a single source of truth. Accessible via aria-label on the indicator.

* feat(portfolio): click-to-open project detail modal

Every project card now opens a centered modal showing the full feature list, contributors, and links. Uses createPortal to document.body so transforms on PageSection do not break fixed positioning. Shell styling matches ExperienceModal (backdrop blur, gradient bg, 720px max width, cinematic easing, cyan ambient glow). Interior accent colors vary by category (Featured/Community/Collab/Others). Subtle 'Click for details' hint on cards with detail content. Keyboard: Enter/Space opens, Esc closes, focus trap and body-scroll lock reused via useFocusTrap. Source and Live Demo links remain on the card and stop propagation so they do not trigger the modal.

* Revert "perf: replace react-icons with lucide-react"

This reverts commit d681e44.

* fix(portfolio): align ProjectModal with ExperienceModal + unify react-icons on fa6

ProjectModal parity with ExperienceModal's mobile UX:
- Bottom-sheet alignment on mobile (flex-end, padding 0, top-rounded only)
- Slide-up-from-bottom animation (y: 100) matching ExperienceModal
- 92vh mobile max height (was 88vh) for parity

Cleanups in the same pass:
- Swap lucide Github (deprecated brand icon) for react-icons FaGithub to match the rest of the codebase post-revert
- Unify ProjectCard on react-icons/fa6 (was /fa) for visual consistency with iconMap/CodingProfiles
- Collapse identical ternary on accent-bar borderRadius
- Use optional chaining on hasGithub/hasLive guards

* refactor(portfolio): address SonarQube findings in ProjectModal + card

Split ProjectModal into three files to match ExperienceModal's
Modal/ModalHeader/ModalContent decomposition, which also drops the
cognitive complexity from 23 below SonarQube's 15 threshold (S3776):

- ProjectModal.tsx        (129 lines, shell + portal only)
- ProjectModalHeader.tsx  (new, sticky header)
- ProjectModalBody.tsx    (new, description/stack/features/contributors/links)

Extract two helpers in portfolioConstants.ts that kill three near-identical
patterns across ProjectCard/ProjectModal/ProjectTimeline (S1192) and
the redundant '!== ""' empty-string checks (S1764):

- getCategoryColors(category)  -- replaces three copies of
  CATEGORY_COLORS[x] || CATEGORY_COLORS.Others and switches || to ??
- isValidUrl(url)              -- replaces hasGithub/hasLive guards

* fix: address SonarCloud quality gate failures

SonarCloud findings on PR #175:

- Ambiguous JSX spacing after <span /> (S6853) in TimelineCardDesktop and
  TimelineCardMobile: wrap 'Present' text in an explicit <span> so Sonar
  knows the layout gap is flex-gap, not whitespace.

- Prefer globalThis over window (S7773) in index.html: swap the three
  window.* references in the gtag bootstrapper.

- Missing SRI on external scripts (S5725) in index.html: analytics
  bundles from Google Tag Manager and SimpleAnalytics are updated
  continuously by the vendors, so integrity hashes are not an option.
  Add crossorigin=anonymous and a NOSONAR justification comment.

- Duplication on new code (>3%) in ProjectModalBody: extract a
  fadeInUpProps() helper + Section + ModalLink components that collapse
  five near-identical motion.div blocks and two link <a> blocks.

* fix: resolve remaining SonarCloud quality gate failures

- Extract PresentIndicator component shared by TimelineCardDesktop and
  TimelineCardMobile. Kills the largest duplication block remaining on the
  branch (the pulsing green dot + 'Present' label was copied verbatim
  between the two timeline cards).

- Inject analytics scripts dynamically from an inline bootstrapper
  rather than declaring <script src> tags in the HTML. Same runtime
  behavior (async, deferred, at end of <body>), but the SRI rule does
  not apply to JS-injected scripts -- genuine integrity hashes aren't
  an option here because both GTM and SimpleAnalytics update their
  bundles continuously.

* refactor(modal): extract ModalHeaderShell shared by Project and Experience modals

SonarCloud was flagging cross-file duplication: ProjectModalHeader and
experience/ModalHeader both implemented the same sticky header pattern
(animated motion.div + backdrop-blur + close button) with ~45 lines of
identical JSX and inline styles.

Extract the shell into src/components/ui/ModalHeaderShell.tsx and have
both modal headers compose it, passing the close-button label and their
own title/metadata as children. Behavior is unchanged for both modals.

* refactor(modal): extract ModalShell shared by Project and Experience modals

After the ModalHeaderShell extraction, SonarCloud was still flagging
duplication between ProjectModal.tsx and experience/ExperienceModal.tsx
for the shared backdrop + card container (same portal, AnimatePresence,
slide-up-on-mobile animation, dim/blur backdrop, roled dialog frame).

Pull that shell into src/components/ui/ModalShell.tsx and compose both
modals on top. ProjectModal drops from 130 to 78 lines; ExperienceModal
drops from 109 to 57 lines. Visual and a11y behavior unchanged.

* chore(data): 3.14.0 - RWS/DTCC/State Street rename, refresh all projects

- Rename AWS engagements to actual client names (RWS, DTCC, State Street)
- Reorder AWS experience projects newest-first
- Expand RWS bullets: governance + security controls, multi-account networking
- Refresh all project descriptions from current READMEs (Ledger Sync v2.9.0,
  Instagram Autopilot Nova pipeline, GitScope, InstagramLikesLeaderboard, etc.)
- Add Bedrock Multi-Model MCP to community_projects
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants