- Added `SkipLink` component to `src/components/ui/SkipLink.tsx`
- Added translation keys to `src/i18n`
- Added `<SkipLink />` to `App.tsx`
- Added `id="main-content"` and `tabIndex={-1}` to page main containers
- Verified accessibility via Playwright test
Co-authored-by: ragusa-it <196988693+ragusa-it@users.noreply.github.com>
- Introduces a local tracking variable in `Navbar` scroll effect to prevent redundant `setState` calls during scroll.
- Ensures the state is correctly initialized on mount if the page is already scrolled.
- Uses `requestAnimationFrame` for efficient scroll handling.
This reduces the number of React reconciliation cycles during scrolling, improving main thread performance.
Co-authored-by: ragusa-it <196988693+ragusa-it@users.noreply.github.com>
- Added isMobile detection (<= 768px) in GradientBlinds.tsx
- Updated onPointerMove to calculate container-relative coordinates immediately on mobile
- Updated animation loop to skip scroll-based target updates on mobile
- Fixed regression where disabling mouse dampening on mobile would freeze the spotlight
- Prevents spotlight from drifting across the background during scroll inertia on mobile devices
Co-authored-by: ragusa-it <196988693+ragusa-it@users.noreply.github.com>
- Added isMobile detection (<= 768px) in GradientBlinds.tsx
- Updated onPointerMove to calculate container-relative coordinates immediately on mobile
- Updated animation loop to skip scroll-based target updates on mobile
- Prevents spotlight from drifting across the background during scroll inertia on mobile devices
Co-authored-by: ragusa-it <196988693+ragusa-it@users.noreply.github.com>
💡 What: Added ARIA roles to form status messages and hid decorative icons.
🎯 Why: Screen readers were missing dynamic success/error messages on form submission.
♿ Accessibility:
- Added `role="alert"` and `aria-live="polite"` to success, error, and rate-limit messages.
- Added `aria-hidden="true"` to decorative icons in the contact info section.
Co-authored-by: ragusa-it <196988693+ragusa-it@users.noreply.github.com>
- Move scroll position and bounding client rect calculations from `pointermove` handler to `requestAnimationFrame` loop.
- Use `pointerPosRef` to store raw event coordinates, reducing overhead in high-frequency event handlers.
- Ensure spotlight effect correctly accounts for scroll position updates during animation frames.
- Add regression test to verify `scrollX` is not accessed during pointer events.
Co-authored-by: ragusa-it <196988693+ragusa-it@users.noreply.github.com>
- Add visual asterisk for required inputs in Input.tsx
- Add .required style in Input.module.css
- Update Contact form to use required props and noValidate
- Add role="alert" to Contact form success/error messages
- Add tests for required field rendering
Co-authored-by: ragusa-it <196988693+ragusa-it@users.noreply.github.com>
Extracted the high-frequency typing animation logic into a new, memoized `TypedText` component.
This prevents the entire `Hero` component (including the heavy `GradientBlinds`) from re-rendering on every character update.
- Created `TypedText` component in `Hero.tsx`
- Wrapped `TypedText` in `React.memo`
- Moved `useTypingEffect` call into `TypedText`
- Updated `Hero` to use `TypedText`
Co-authored-by: ragusa-it <196988693+ragusa-it@users.noreply.github.com>
Added Strict-Transport-Security (HSTS) and Permissions-Policy headers to firebase.json to improve security posture.
- HSTS ensures browsers only connect via HTTPS for 1 year.
- Permissions-Policy restricts usage of sensitive features (camera, mic, geolocation).
Co-authored-by: ragusa-it <196988693+ragusa-it@users.noreply.github.com>
- Implemented `isValidEmail` utility with strict regex validation (rejects `<` and `>`) to prevent XSS vectors.
- Updated `Contact.tsx` to use `isValidEmail` instead of weak regex.
- Added comprehensive tests for `isValidEmail` in `src/utils/security.test.ts`.
- Fixed flaky test in `src/pages/__tests__/Contact.test.tsx` by clearing `localStorage` in `afterEach`.
- Added test case for invalid email submission.
- Documented findings in `.jules/sentinel.md`.
Co-authored-by: ragusa-it <196988693+ragusa-it@users.noreply.github.com>
- Add `aria-busy` attribute to Button when loading.
- Refactor Button rendering to keep children in DOM (visually hidden) instead of unmounting.
- Fix layout shift regression by replicating flex properties in content wrapper.
- Move inline styles to CSS modules.
- Add tests for loading state accessibility.
Co-authored-by: ragusa-it <196988693+ragusa-it@users.noreply.github.com>
- Added `useRateLimit` hook
- Integrated hook into `Contact.tsx`
- Added translations for rate limit error
- Added unit tests
- Fixed type error in `Button.tsx` to allow build to pass
- Splits About and Contact pages into separate chunks using React.lazy and Suspense.
- Keeps Home page eager loaded to prevent layout shifts.
- Adds PageLoader component as a fallback for Suspense.
- Reduces initial bundle size by loading secondary pages only when needed.
- Replaces the `cancelAnimationFrame` pattern with a boolean ticking flag to reduce function allocation and RAF overhead on high-frequency mousemove events.
- Uses closure variables for coordinates to ensure the latest position is used in the animation frame.
- Improves performance of the custom cursor on high-refresh-rate input devices.
Refactored the `Button` component to accept and spread standard HTML button attributes.
This allows developers to add `aria-label`, `aria-expanded`, and other accessibility properties,
which were previously ignored.
Added unit tests to verify that attributes are correctly passed to the DOM element.
Added strict security headers to `firebase.json` for Firebase Hosting.
Headers included:
- X-Content-Type-Options: nosniff
- X-Frame-Options: DENY
- Referrer-Policy: strict-origin-when-cross-origin
- Content-Security-Policy: Includes directives for 'self', Google Fonts, EmailJS, and disallows object/frame embedding.
Also initialized `.jules/sentinel.md` with the first security learning.