163 lines
5.8 KiB
Markdown
163 lines
5.8 KiB
Markdown
|
|
# Parallax Pinned Chapters + Snap Carousel Design
|
||
|
|
|
||
|
|
**Date:** 2026-03-05
|
||
|
|
**Status:** Approved
|
||
|
|
|
||
|
|
## Overview
|
||
|
|
|
||
|
|
Redesign the landing page scroll experience using a "chapter" model. Sections are grouped into 4 chapters that pin (sticky) in place as the user scrolls. Each subsequent chapter slides up and covers the previous one, creating a layered card-stack effect. The services carousel is rebuilt with CSS scroll-snap for native horizontal snapping.
|
||
|
|
|
||
|
|
## Chapter Structure
|
||
|
|
|
||
|
|
| Chapter | Sections | Purpose |
|
||
|
|
| ------- | -------------------------- | --------------- |
|
||
|
|
| 1 | HeroSection + TrustStrip | "Who we are" |
|
||
|
|
| 2 | ServicesCarousel | "What we offer" |
|
||
|
|
| 3 | WhyUsSection + CTABanner | "Why choose us" |
|
||
|
|
| 4 | SupportDownloads + Contact | "Get in touch" |
|
||
|
|
|
||
|
|
## Scroll Behavior
|
||
|
|
|
||
|
|
### Pinning Mechanism
|
||
|
|
|
||
|
|
- Each chapter wrapper uses `position: sticky; top: 0`
|
||
|
|
- Chapters stack with increasing `z-index` (1, 2, 3, 4)
|
||
|
|
- Each chapter has a solid background so it fully covers the chapter behind it
|
||
|
|
- A subtle `box-shadow` on the top edge of each chapter creates the "sliding over" depth illusion
|
||
|
|
- The outer container uses `scroll-snap-type: y proximity` for soft vertical snap (helps chapters land cleanly but does not fight free scrolling)
|
||
|
|
|
||
|
|
### Chapter Details
|
||
|
|
|
||
|
|
**Chapter 1: Hero + TrustStrip**
|
||
|
|
|
||
|
|
- `min-height: 100dvh` to fill the viewport
|
||
|
|
- Hero fills most of the space, TrustStrip anchored at the bottom
|
||
|
|
- Pins in place while Chapter 2 slides up over it
|
||
|
|
- Existing gradient background + dot pattern preserved
|
||
|
|
|
||
|
|
**Chapter 2: ServicesCarousel**
|
||
|
|
|
||
|
|
- Natural content height (not forced to viewport height)
|
||
|
|
- Pins in place while Chapter 3 slides up
|
||
|
|
- Carousel rebuilt with CSS scroll-snap (see Carousel section below)
|
||
|
|
|
||
|
|
**Chapter 3: WhyUs + CTABanner**
|
||
|
|
|
||
|
|
- Natural content height
|
||
|
|
- Pins in place while Chapter 4 slides up
|
||
|
|
- WhyUs image gets a subtle parallax speed difference (scrolls slightly slower than text) for added depth
|
||
|
|
|
||
|
|
**Chapter 4: SupportDownloads + Contact**
|
||
|
|
|
||
|
|
- Normal scroll, no pinning (last chapter, nothing covers it)
|
||
|
|
- Existing fade-in animations preserved
|
||
|
|
|
||
|
|
## Carousel Rebuild (CSS Scroll-Snap)
|
||
|
|
|
||
|
|
The current carousel uses absolute positioning with JS-driven transforms and offset calculations. This will be replaced with a native CSS scroll-snap approach.
|
||
|
|
|
||
|
|
### New approach
|
||
|
|
|
||
|
|
- Horizontal scroll container with `scroll-snap-type: x mandatory`
|
||
|
|
- Each card is a snap point with `scroll-snap-align: center`
|
||
|
|
- Cards are laid out in a flex row, each taking full width of the visible area
|
||
|
|
- Native touch/swipe works out of the box
|
||
|
|
- Dot indicators sync with scroll position via `IntersectionObserver` or `scrollLeft` calculation
|
||
|
|
- Arrow buttons use `scrollBy()` with `behavior: 'smooth'`
|
||
|
|
- Auto-play uses `scrollBy()` and pauses on user interaction (touch, hover, focus)
|
||
|
|
- Personal/Business tab toggle resets scroll position to 0
|
||
|
|
|
||
|
|
### Benefits over current approach
|
||
|
|
|
||
|
|
- No absolute positioning or transform math
|
||
|
|
- Touch/swipe is native and performant
|
||
|
|
- Reduced JS complexity
|
||
|
|
- Better accessibility (native scroll semantics)
|
||
|
|
- Respects `prefers-reduced-motion` automatically
|
||
|
|
|
||
|
|
## Technical Implementation
|
||
|
|
|
||
|
|
### New component: ChapterWrapper
|
||
|
|
|
||
|
|
A reusable wrapper that applies sticky positioning:
|
||
|
|
|
||
|
|
```tsx
|
||
|
|
interface ChapterProps {
|
||
|
|
children: React.ReactNode;
|
||
|
|
zIndex: number;
|
||
|
|
className?: string;
|
||
|
|
}
|
||
|
|
|
||
|
|
function Chapter({ children, zIndex, className }: ChapterProps) {
|
||
|
|
return (
|
||
|
|
<section className={cn("sticky top-0", className)} style={{ zIndex }}>
|
||
|
|
{children}
|
||
|
|
</section>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Updated PublicLandingView structure
|
||
|
|
|
||
|
|
```tsx
|
||
|
|
<main>
|
||
|
|
<Chapter zIndex={1} className="min-h-dvh">
|
||
|
|
<HeroSection />
|
||
|
|
<TrustStrip />
|
||
|
|
</Chapter>
|
||
|
|
|
||
|
|
<Chapter zIndex={2}>
|
||
|
|
<ServicesCarousel />
|
||
|
|
</Chapter>
|
||
|
|
|
||
|
|
<Chapter zIndex={3}>
|
||
|
|
<WhyUsSection />
|
||
|
|
<CTABanner />
|
||
|
|
</Chapter>
|
||
|
|
|
||
|
|
<div>
|
||
|
|
{" "}
|
||
|
|
{/* No sticky - last chapter */}
|
||
|
|
<SupportDownloadsSection />
|
||
|
|
<ContactSection />
|
||
|
|
</div>
|
||
|
|
</main>
|
||
|
|
```
|
||
|
|
|
||
|
|
### Shadow/depth effect
|
||
|
|
|
||
|
|
Each Chapter (except Chapter 1) gets a top shadow to enhance the "sliding over" illusion:
|
||
|
|
|
||
|
|
```css
|
||
|
|
.chapter-overlay {
|
||
|
|
box-shadow: 0 -8px 30px -10px rgba(0, 0, 0, 0.1);
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
## Mobile Considerations
|
||
|
|
|
||
|
|
- Use `100dvh` (dynamic viewport height) instead of `100vh` to avoid iOS address bar issues
|
||
|
|
- Carousel CSS scroll-snap is touch-native and works well on mobile
|
||
|
|
- Sticky positioning works on mobile browsers (iOS Safari, Chrome Android)
|
||
|
|
- If performance is poor on low-end devices, sticky can be disabled via a CSS class
|
||
|
|
|
||
|
|
## Accessibility
|
||
|
|
|
||
|
|
- `prefers-reduced-motion: reduce` disables sticky pinning behavior (falls back to normal scroll)
|
||
|
|
- Carousel maintains keyboard navigation (arrow keys, tab through cards)
|
||
|
|
- ARIA attributes preserved: `role="region"`, `aria-roledescription="carousel"`, slide labels
|
||
|
|
- Scroll-snap does not interfere with screen readers
|
||
|
|
|
||
|
|
## Files to Modify
|
||
|
|
|
||
|
|
| File | Change |
|
||
|
|
| ------------------------------------------------------- | --------------------------------------- |
|
||
|
|
| `features/landing-page/views/PublicLandingView.tsx` | Wrap sections in Chapter components |
|
||
|
|
| `features/landing-page/components/ServicesCarousel.tsx` | Rebuild with CSS scroll-snap |
|
||
|
|
| `features/landing-page/components/HeroSection.tsx` | Adjust to fill chapter space (flex-1) |
|
||
|
|
| `features/landing-page/components/TrustStrip.tsx` | Adjust to anchor at bottom of Chapter 1 |
|
||
|
|
| `features/landing-page/hooks/useInfiniteCarousel.ts` | Replace with scroll-snap hook or remove |
|
||
|
|
| `features/landing-page/components/index.ts` | Export new Chapter component |
|
||
|
|
| New: `features/landing-page/components/Chapter.tsx` | Chapter wrapper component |
|
||
|
|
| New: `features/landing-page/hooks/useSnapCarousel.ts` | Hook for scroll-snap carousel state |
|