Assist_Design/docs/plans/2026-03-05-parallax-pinned-chapters-design.md
barsa 57f2c543d1 style: update typography and layout across components
- Replaced font references in globals.css to use DM Sans and JetBrains Mono for improved typography consistency.
- Adjusted various components to utilize the new font styles, enhancing visual hierarchy and readability.
- Updated layout properties in AppShell and Sidebar for better alignment and spacing.
- Enhanced button styles to include a new subtle variant for improved UI flexibility.
- Refactored SearchFilterBar to support active filter display, improving user interaction experience.
- Made minor adjustments to the DashboardView and landing page components for better visual consistency.
2026-03-06 10:45:51 +09:00

5.8 KiB

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

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:

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

<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:

.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