# Layered Mountain Landscape Hero With Depth Parallax

> Free parallax scrolling hero with drawn mountain ridges that slide at different depths behind the headline. React and plain HTML, CSS and JS versions.

A shadcn/ui block from the [@weinc](https://we.inc/components?utm_source=shadcn.exchange) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/weinc/layered-mountain-landscape-hero-with-depth-parallax).

- Page, with a live preview: https://shadcn.exchange/r/weinc/layered-mountain-landscape-hero-with-depth-parallax
- Screenshot: https://shadcn.exchange/shot/weinc/layered-mountain-landscape-hero-with-depth-parallax/light.png
- Registry item: https://we.inc/r/layered-mountain-landscape-hero-with-depth-parallax.json
- Listed: 2026-10-28, source last changed 2026-10-28

## Install

```bash
npx shadcn@latest add https://shadcn.exchange/r/weinc/layered-mountain-landscape-hero-with-depth-parallax.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Files

### `components/we-inc/layered-mountain-landscape-hero-with-depth-parallax.tsx`

```tsx
import { useEffect, useRef } from "react";

// Each layer moves down by scroll * depth, so a higher depth reads as further away.
const LAYERS = [
  { depth: 0.42, fill: "#e8c39c", d: "M0 250 L110 205 L230 228 L370 150 L500 212 L640 168 L780 118 L930 190 L1060 146 L1210 214 L1330 172 L1440 198 V400 H0Z" },
  { depth: 0.3, fill: "#c98f6e", d: "M0 292 L150 238 L300 276 L450 206 L620 268 L760 226 L910 286 L1080 216 L1250 270 L1440 236 V400 H0Z" },
  { depth: 0.18, fill: "#8d5a49", d: "M0 330 C180 286 330 270 520 312 S850 356 1040 304 S1310 270 1440 314 V400 H0Z" },
];
const FRONT = "M0 250 C220 196 470 222 720 240 S1180 204 1440 232 V400 H0Z";
const NOTES = [
  { title: "Twelve rooms", text: "Every room faces the eastern ridge, so first light reaches the bed before the alarm does." },
  { title: "Marked trails", text: "Three loops leave from the porch, from a gentle valley walk to a full day on the high saddle." },
  { title: "A slow kitchen", text: "Breakfast runs until eleven and dinner is one long table, cooked from what the valley grows." },
];

export default function LayeredMountainLandscapeHeroWithDepthParallax() {
  const root = useRef<HTMLDivElement>(null);
  const layers = useRef<(HTMLDivElement | null)[]>([]);

  useEffect(() => {
    const box = root.current;
    if (!box) return;
    const reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
    let frame = 0;
    let on = true;
    const draw = () => {
      frame = 0;
      const s = box.scrollTop; // on page scroll use: Math.max(0, -hero.getBoundingClientRect().top)
      box.classList.toggle("w6lnd-moved", s > 8);
      if (reduce) return;
      layers.current.forEach((l) => {
        if (l) l.style.transform = `translate3d(0,${(s * Number(l.dataset.depth)).toFixed(1)}px,0)`;
      });
    };
    const req = () => {
      if (on && !frame) frame = requestAnimationFrame(draw);
    };
    const io = new IntersectionObserver(([e]) => {
      on = e.isIntersecting;
      box.toggleAttribute("data-paused", !on);
      if (on) req();
    });
    io.observe(box);
    box.addEventListener("scroll", req, { passive: true });
    return () => {
      cancelAnimationFrame(frame);
      io.disconnect();
      box.removeEventListener("scroll", req);
    };
  }, []);

  let i = 0;
  const layerRef = (n: HTMLDivElement | null) => {
    if (n) layers.current[Number(n.dataset.i)] = n;
  };

  return (
    <div
      ref={root}
      tabIndex={0}
      role="region"
      aria-label="Haldor Ridge Lodge, scrollable preview"
      className="w6lnd relative h-[640px] overflow-y-auto overscroll-contain bg-[#21181a] text-[#2a1b19] outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-[#8d5a49]"
    >
      <style>{`
        .w6lnd-layer { will-change: transform; }
        .w6lnd-mist { animation: w6lnd-drift 26s ease-in-out infinite alternate; }
        .w6lnd[data-paused] .w6lnd-mist { animation-play-state: paused; }
        @keyframes w6lnd-drift { from { transform: translate3d(-4%,0,0); } to { transform: translate3d(4%,0,0); } }
        .w6lnd-hint { transition: opacity .5s ease; }
        .w6lnd-moved .w6lnd-hint { opacity: 0; }
        @media (prefers-reduced-motion: reduce) { .w6lnd-mist { animation: none; } .w6lnd-layer { will-change: auto; } }
      `}</style>
      <header className="relative h-[640px] overflow-hidden bg-gradient-to-b from-[#f6e3c8] via-[#f2d3ae] to-[#eab98f]">
        <div ref={layerRef} data-i={i++} data-depth="0.55" className="w6lnd-layer absolute left-[64%] top-[44%] -ml-16 h-32 w-32 rounded-full bg-[#fbeedb] shadow-[0_0_80px_30px_rgba(251,238,219,0.55)]" aria-hidden="true" />
        <div className="w6lnd-hint pointer-events-none absolute right-4 top-4 z-30 rounded-full bg-[#2a1b19]/80 px-3 py-1.5 text-xs font-medium text-[#f6e3c8]">Scroll inside</div>
        <div ref={layerRef} data-i={i++} data-depth="0.35" className="w6lnd-layer absolute inset-x-0 top-16 z-10 px-6 text-center">
          <p className="text-xs font-semibold uppercase tracking-[0.3em] text-[#6e3f30]">Haldor Ridge Lodge</p>
          <h1 className="mx-auto mt-4 max-w-3xl text-[clamp(36px,6.6vw,78px)] font-semibold leading-[1.02] tracking-[-0.02em]">Wake up above the cloud line</h1>
          <p className="mx-auto mt-4 max-w-md text-base leading-relaxed text-[#4a2c24]">A small lodge at the head of the valley, with the ridge outside every window.</p>
        </div>
        {LAYERS.map((l) => (
          <div key={l.depth} ref={layerRef} data-i={i++} data-depth={l.depth} aria-hidden="true" className="w6lnd-layer absolute inset-x-0 bottom-0 z-20 h-[62%]">
            <svg viewBox="0 0 1440 400" preserveAspectRatio="none" className="h-full w-full"><path d={l.d} fill={l.fill} /></svg>
          </div>
        ))}
        <div aria-hidden="true" className="w6lnd-mist absolute inset-x-[-10%] bottom-[16%] z-20 h-24 bg-gradient-to-b from-transparent via-[#f6e3c8]/40 to-transparent" />
        <div aria-hidden="true" className="absolute inset-x-0 bottom-0 z-20 h-[30%]">
          <svg viewBox="0 0 1440 400" preserveAspectRatio="none" className="h-full w-full"><path d={FRONT} fill="#21181a" /></svg>
        </div>
        <div className="absolute inset-x-0 bottom-8 z-30 flex flex-wrap justify-center gap-3 px-6">
          <a href="#" className="inline-flex h-12 items-center rounded-full bg-[#f6e3c8] px-6 text-sm font-semibold text-[#21181a] transition hover:bg-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#f6e3c8] focus-visible:ring-offset-2 focus-visible:ring-offset-[#21181a]">Check winter dates</a>
          <a href="#" className="inline-flex h-12 items-center rounded-full border border-[#f6e3c8]/50 px-6 text-sm font-semibold text-[#f6e3c8] transition hover:border-[#f6e3c8] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#f6e3c8]">See the trails</a>
        </div>
      </header>
      <section className="px-6 pb-20 pt-10 text-[#f1dcc4] sm:px-10">
        <div className="mx-auto max-w-5xl">
          <h2 className="max-w-xl text-2xl font-semibold leading-snug sm:text-3xl">Quiet rooms, early light and a porch that opens onto three trails.</h2>
          <ul className="mt-10 grid gap-8 sm:grid-cols-3">
            {NOTES.map((n) => (
              <li key={n.title} className="border-t border-[#f1dcc4]/25 pt-5">
                <h3 className="text-base font-semibold">{n.title}</h3>
                <p className="mt-2 text-sm leading-relaxed text-[#d9bfa6]">{n.text}</p>
              </li>
            ))}
          </ul>
        </div>
      </section>
    </div>
  );
}
```
