feat: add custom cursor, navbar responsiveness, and active section indicator

This commit is contained in:
2026-04-12 21:04:30 +02:00
parent dfcba4965f
commit da0b03893f
23 changed files with 3565 additions and 354 deletions
+47
View File
@@ -41,6 +41,53 @@ Custom utility class `text-huge` uses `clamp(2rem, 8vw, 8rem)` for responsive he
Scroll snap: `.snap-container` (y proximity) + `.snap-section` (align start) on homepage sections.
## Navbar
**Responsive breakpoints**: The navbar uses `lg` (1024px) as the mobile/desktop toggle. Between `lg` and `xl` (1280px), text sizes, gaps, and padding scale down to prevent overflow. Mobile (<1024px) has compact padding and logo.
**Active section indicator**: Scroll-based detection (not IntersectionObserver — sections are dynamically imported with `ssr: false` so they don't exist at mount time). A sliding `h-1 bg-primary` bar under the desktop nav links animates between sections using CSS transitions. Only visible on homepage (`pathname === "/"`). Recalculates position on scroll state change and window resize.
## Custom Cursor (`CustomCursor.tsx`)
A GSAP-powered custom cursor rendered in `layout.tsx` (direct import, not `next/dynamic``ssr: false` is not allowed in Server Component layouts in Next.js 16). Desktop-only (`pointer: fine`), disabled for `prefers-reduced-motion`.
**Default shape**: Terminal-style block (22×22px, 5px radius), white fill + 2px outline ring with 1px gap. Uses `mix-blend-mode: difference` for automatic color inversion against any background.
**Mouse tracking**: Uses `gsap.to` with `overwrite: "auto"` for all positioning. Does NOT use `gsap.quickTo` — it conflicts with `gsap.to` on the same x/y properties during morph transitions (causes "not eligible for reset" errors).
**Three interaction modes** (opt-in via `data-cursor` attribute):
### `data-cursor="hug"` — Hug mode
- Cursor morphs into a transparent outline frame around the element with 6px padding
- Element scales to 1.05 with glow (`box-shadow` via `.cursor-hugged` class in `globals.css`)
- Click bounce: mousedown → scale(0.95), mouseup → scale(1.08) → scale(1.05) spring
- Border-radius resolved from element, then first child (for wrapper divs like the About photo), fallback to pill (`100px`)
- Used on: hero CTAs, navbar desktop links + Contattaci button, CtaSection button, contatti page phone/email links, footer social icons, About section photo
### `data-cursor="underline"` — Underline mode
- Cursor morphs into a 3px-tall bar under the `<h3>` question text
- Used on: FAQ section question items
### FAQ split/merge (underline + block cursor)
- When a FAQ accordion opens (`data-faq-open` attribute detected via MutationObserver):
1. A phantom div takes over the underline position (stays as visual anchor)
2. Main cursor pulses (thickens to 6px), then peels off as a block cursor
3. Block cursor is free to follow the mouse through the answer text
- When the FAQ closes: block cursor flies back up to the underline, phantom fades out, cursor becomes the underline again
- Switching FAQs: block cursor flies directly to the new question's underline
### Shared behaviors
- **Rubber-band pull**: While morphed (hug or underline), cursor applies 15% of mouse-to-element-center distance as pull offset. Gives an elastic "trying to follow" feel.
- **Scroll tracking**: `scroll` event recalculates morph position from `getBoundingClientRect()`. Both the main cursor and phantom underline update on scroll.
- **Z-index**: Walks the full ancestor chain and uses the highest explicit z-index + 1. Navbar elements get z-51 (above backdrop blur). Body content gets z-1 (below navbar). Default circle mode stays at z-9999.
- **Edge cases**: Cursor hidden until first mousemove. Fades out when mouse leaves window. Event delegation via `closest()` — no cached DOM refs (SPA-safe).
### Files involved
- `src/components/CustomCursor.tsx` — All cursor logic (self-contained client component)
- `src/app/globals.css``cursor: none` media query, `.cursor-hugged` glow class
- `src/app/layout.tsx` — Renders `<CustomCursor />` as last child in `<body>`
- `src/components/sections/FaqSection.tsx``data-cursor="underline"`, `data-faq-open` attribute
## Key Conventions
- Path alias: `@/*` maps to `./src/*`