Pages Hard
Luxury Hotel — Grand Résidence
Forest green/champagne luxury hotel site using CSS scroll-snap-type:y mandatory for 5 full-height panels. IntersectionObserver fires per-panel entrance animations, nav adapts color on light panels, Playfair Display SC typography.
Open in Lab
MCP
gsap scroll-snap lenis intersection-observer playfair-display
Targets: JS HTML
Code
/* ── Demo 55: Luxury Hotel — Grand Résidence ── */
/* Palette: Deep Forest #0d1a0f / Champagne #e8d4a0 / Ivory #f8f4ec / Gold #c8a030 */
/* Fonts: Playfair Display SC + Lato */
:root {
--forest: #0d1a0f;
--forest-mid: #142018;
--forest-light: #1e3028;
--ivory: #f8f4ec;
--warm: #ede7d8;
--champagne: #e8d4a0;
--gold: #c8a030;
--gold-dim: rgba(200, 160, 48, 0.2);
--muted-gold: #8a7040;
--text-dark: #1a1208;
--text-light: rgba(248, 244, 236, 0.7);
}
*,
*::before,
*::after {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: "Lato", -apple-system, sans-serif;
background: var(--forest);
color: var(--ivory);
overflow: hidden;
-webkit-font-smoothing: antialiased;
}
/* ── Nav ── */
.nav {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 200;
display: flex;
align-items: center;
justify-content: space-between;
padding: 1.5rem 3rem;
background: transparent;
transition: background 0.5s;
}
.nav.on-light {
background: rgba(248, 244, 236, 0.95);
backdrop-filter: blur(10px);
}
.nav.on-light .nav-logo,
.nav.on-light .nav-links a {
color: var(--text-dark);
}
.nav-logo {
font-family: "Playfair Display SC", serif;
font-size: 0.95rem;
font-weight: 400;
letter-spacing: 0.12em;
color: var(--champagne);
text-decoration: none;
transition: color 0.5s;
}
.nav-links {
display: flex;
gap: 2.5rem;
list-style: none;
}
.nav-links a {
font-size: 0.78rem;
font-weight: 300;
letter-spacing: 0.1em;
color: var(--text-light);
text-decoration: none;
text-transform: uppercase;
transition: color 0.2s;
}
.nav-links a:hover {
color: var(--champagne);
}
.btn-gold {
display: inline-block;
padding: 0.7rem 1.5rem;
border: 1px solid var(--gold);
background: transparent;
color: var(--gold);
font-family: "Lato", sans-serif;
font-size: 0.72rem;
font-weight: 700;
letter-spacing: 0.12em;
text-transform: uppercase;
text-decoration: none;
transition: background 0.25s, color 0.25s;
cursor: pointer;
}
.btn-gold:hover,
.btn-submit {
background: var(--gold);
color: var(--forest);
}
.btn-gold--lg {
padding: 1rem 2.5rem;
font-size: 0.82rem;
}
.btn-submit {
width: 100%;
padding: 1rem;
font-size: 0.82rem;
border: none;
cursor: pointer;
font-family: "Lato", sans-serif;
letter-spacing: 0.12em;
text-transform: uppercase;
font-weight: 700;
}
/* ── Snap container ── */
.snap-container {
height: 100vh;
overflow-y: scroll;
scroll-snap-type: y mandatory;
scroll-behavior: smooth;
}
.snap-panel {
height: 100vh;
scroll-snap-align: start;
position: relative;
display: flex;
align-items: center;
}
/* ── Panel counter ── */
.panel-counter {
position: absolute;
bottom: 2rem;
right: 3rem;
font-size: 0.62rem;
letter-spacing: 0.15em;
color: rgba(232, 212, 160, 0.4);
font-family: "Lato", sans-serif;
}
.panel-eyebrow {
display: block;
font-size: 0.62rem;
font-weight: 700;
letter-spacing: 0.2em;
text-transform: uppercase;
color: var(--gold);
margin-bottom: 1rem;
}
/* ── Panel Hero ── */
.panel-hero {
background: var(--forest);
justify-content: flex-end;
}
.ph-bg {
position: absolute;
inset: 0;
background: radial-gradient(ellipse at 60% 40%, rgba(200, 160, 48, 0.06) 0%, transparent 60%),
linear-gradient(160deg, var(--forest-light) 0%, var(--forest) 60%, #080e08 100%);
}
.ph-content {
position: relative;
z-index: 1;
width: 100%;
display: grid;
grid-template-rows: auto 1fr auto;
height: 100vh;
padding: 5rem 3rem 3rem;
}
.ph-season {
font-family: "Lato", sans-serif;
font-size: 0.68rem;
font-weight: 300;
letter-spacing: 0.2em;
text-transform: uppercase;
color: var(--champagne);
}
.ph-center {
display: flex;
flex-direction: column;
justify-content: center;
}
.ph-center h1 {
font-family: "Playfair Display SC", serif;
font-size: clamp(3rem, 7vw, 6rem);
font-weight: 400;
line-height: 1.1;
letter-spacing: 0.02em;
margin-bottom: 1.5rem;
}
.ph-center h1 em {
font-style: italic;
color: var(--champagne);
}
.ph-center p {
font-size: 1rem;
color: var(--text-light);
line-height: 1.85;
max-width: 450px;
margin-bottom: 2.5rem;
font-weight: 300;
}
.ph-bottom {
display: flex;
align-items: center;
gap: 1.5rem;
}
.ph-scroll {
font-size: 0.65rem;
letter-spacing: 0.18em;
text-transform: uppercase;
color: var(--muted-gold);
white-space: nowrap;
}
.ph-scroll-line {
flex: 1;
max-width: 80px;
height: 1px;
background: var(--muted-gold);
position: relative;
overflow: hidden;
}
.ph-scroll-line::after {
content: "";
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 1px;
background: var(--champagne);
animation: line-move 2.5s ease-in-out infinite;
}
@keyframes line-move {
0% {
left: -100%;
}
100% {
left: 100%;
}
}
/* ── Panel Room ── */
.panel-room {
display: grid;
grid-template-columns: 1fr 1fr;
background: var(--ivory);
color: var(--text-dark);
}
.pr-image {
height: 100%;
}
.pr-image--1 {
background: linear-gradient(160deg, #2a2018 0%, #1a1810 50%, #281e10 100%);
}
.pr-content {
padding: 5rem 3rem;
display: flex;
flex-direction: column;
justify-content: center;
overflow: auto;
}
.pr-content h2 {
font-family: "Playfair Display SC", serif;
font-size: clamp(2.5rem, 5vw, 4rem);
font-weight: 400;
line-height: 1.15;
margin-bottom: 1.5rem;
color: var(--text-dark);
}
.pr-desc {
font-size: 0.95rem;
color: #5a5040;
line-height: 1.85;
margin-bottom: 2rem;
font-weight: 300;
}
.pr-details {
display: flex;
flex-direction: column;
gap: 0;
margin-bottom: 2.5rem;
border-top: 1px solid #d8d0c0;
}
.prd-item {
display: flex;
justify-content: space-between;
padding: 0.75rem 0;
border-bottom: 1px solid #d8d0c0;
}
.prd-item span:first-child {
font-size: 0.68rem;
font-weight: 700;
letter-spacing: 0.12em;
text-transform: uppercase;
color: #8a8070;
}
.prd-item span:last-child {
font-size: 0.88rem;
color: var(--text-dark);
}
.panel-room .panel-eyebrow {
color: var(--gold);
}
.panel-room .btn-gold {
color: var(--gold);
border-color: var(--gold);
}
.panel-room .btn-gold:hover {
background: var(--gold);
color: var(--text-dark);
}
/* ── Panel Amenities ── */
.panel-amenities {
background: var(--forest-mid);
justify-content: center;
}
.pa-content {
padding: 5rem 3rem;
max-width: 900px;
margin: 0 auto;
width: 100%;
}
.pa-content h2 {
font-family: "Playfair Display SC", serif;
font-size: clamp(2rem, 4vw, 3.5rem);
font-weight: 400;
line-height: 1.2;
margin-bottom: 3rem;
}
.pa-content h2 em {
font-style: italic;
color: var(--champagne);
}
.amen-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 2rem;
}
.amen-item {
padding: 1.5rem;
border: 1px solid rgba(200, 160, 48, 0.15);
transition: border-color 0.3s, background 0.3s;
}
.amen-item:hover {
border-color: rgba(200, 160, 48, 0.4);
background: rgba(200, 160, 48, 0.04);
}
.ai-icon {
font-size: 1.2rem;
color: var(--gold);
margin-bottom: 0.75rem;
}
.ai-name {
font-family: "Playfair Display SC", serif;
font-size: 0.9rem;
color: var(--champagne);
margin-bottom: 0.5rem;
letter-spacing: 0.05em;
}
.ai-desc {
font-size: 0.78rem;
color: var(--text-light);
line-height: 1.65;
font-weight: 300;
}
/* ── Panel Dining ── */
.panel-dining {
display: grid;
grid-template-columns: 1fr 1fr;
}
.pd-image {
height: 100%;
}
.pd-image--1 {
background: linear-gradient(145deg, #1a1208 0%, #100e06 50%, #1e1808 100%);
}
.pd-content {
padding: 5rem 3rem;
display: flex;
flex-direction: column;
justify-content: center;
overflow: auto;
}
.pd-content h2 {
font-family: "Playfair Display SC", serif;
font-size: clamp(2.5rem, 5vw, 4rem);
font-weight: 400;
margin-bottom: 0.5rem;
}
.pd-stars {
font-size: 1.5rem;
color: var(--gold);
margin-bottom: 1.5rem;
letter-spacing: 0.3em;
}
.pd-content p {
font-size: 0.95rem;
color: var(--text-light);
line-height: 1.85;
margin-bottom: 1.5rem;
font-weight: 300;
}
.pd-hours {
display: flex;
flex-direction: column;
gap: 0.3rem;
margin-bottom: 2rem;
}
.pd-hours span {
font-size: 0.78rem;
letter-spacing: 0.08em;
color: var(--muted-gold);
}
/* ── Panel Contact ── */
.panel-contact {
background: var(--forest-light);
justify-content: center;
}
.pc2-content {
padding: 5rem 3rem;
max-width: 700px;
margin: 0 auto;
width: 100%;
overflow: auto;
max-height: 100vh;
}
.pc2-content h2 {
font-family: "Playfair Display SC", serif;
font-size: clamp(2.5rem, 5vw, 4rem);
font-weight: 400;
line-height: 1.15;
margin-bottom: 2rem;
}
.pc2-content h2 em {
font-style: italic;
color: var(--champagne);
}
.reservation-form {
display: flex;
flex-direction: column;
gap: 1rem;
margin-bottom: 2.5rem;
}
.rf-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1rem;
}
.rf-field {
display: flex;
flex-direction: column;
gap: 0.4rem;
}
.rf-field label {
font-size: 0.62rem;
font-weight: 700;
letter-spacing: 0.15em;
text-transform: uppercase;
color: var(--muted-gold);
}
.rf-input-mock {
height: 44px;
background: rgba(248, 244, 236, 0.05);
border: 1px solid rgba(200, 160, 48, 0.2);
border-radius: 2px;
transition: border-color 0.2s;
}
.rf-input-mock:hover {
border-color: rgba(200, 160, 48, 0.5);
}
.rf-input-mock--tall {
height: 100px;
}
.pc2-contact {
display: flex;
flex-direction: column;
gap: 0.5rem;
padding-top: 2rem;
border-top: 1px solid rgba(200, 160, 48, 0.15);
}
.pc2-contact span {
font-size: 0.82rem;
color: var(--muted-gold);
font-weight: 300;
letter-spacing: 0.04em;
}
@media (max-width: 900px) {
.panel-room,
.panel-dining {
grid-template-columns: 1fr;
}
.pr-image,
.pd-image {
height: 40vh;
}
.amen-grid {
grid-template-columns: 1fr 1fr;
}
}
@media (max-width: 600px) {
.nav-links {
display: none;
}
.nav {
padding: 1rem 1.5rem;
}
.ph-content,
.pr-content,
.pa-content,
.pd-content,
.pc2-content {
padding: 5rem 1.5rem 3rem;
}
.amen-grid {
grid-template-columns: 1fr;
}
.rf-row {
grid-template-columns: 1fr;
}
}
html.reduced-motion .ph-scroll-line::after {
animation: none;
}
html.reduced-motion * {
transition-duration: 0.01ms !important;
}if (!window.MotionPreference) {
const __mql = window.matchMedia("(prefers-reduced-motion: reduce)");
const __listeners = new Set();
const MotionPreference = {
prefersReducedMotion() {
return __mql.matches;
},
setOverride(value) {
const reduced = Boolean(value);
document.documentElement.classList.toggle("reduced-motion", reduced);
window.dispatchEvent(new CustomEvent("motion-preference", { detail: { reduced } }));
for (const listener of __listeners) {
try {
listener({ reduced, override: reduced, systemReduced: __mql.matches });
} catch {}
}
},
onChange(listener) {
__listeners.add(listener);
try {
listener({
reduced: __mql.matches,
override: null,
systemReduced: __mql.matches,
});
} catch {}
return () => __listeners.delete(listener);
},
getState() {
return { reduced: __mql.matches, override: null, systemReduced: __mql.matches };
},
};
window.MotionPreference = MotionPreference;
}
function prefersReducedMotion() {
return window.MotionPreference.prefersReducedMotion();
}
function initDemoShell() {
// No-op shim in imported standalone snippets.
}
import gsap from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
gsap.registerPlugin(ScrollTrigger);
initDemoShell({
title: "Grand Résidence — Luxury Hotel",
category: "pages",
tech: ["gsap", "css-scroll-snap", "lenis", "playfair", "lato"],
});
const reduced = prefersReducedMotion();
if (reduced) document.documentElement.classList.add("reduced-motion");
// Nav color based on panel
const nav = document.getElementById("nav");
const snapContainer = document.getElementById("snap-container");
const panels = document.querySelectorAll(".snap-panel");
const lightPanels = new Set(["panel-room", "panel-dining"]);
function updateNav() {
const containerRect = snapContainer.getBoundingClientRect();
let visiblePanel = null;
panels.forEach((p) => {
const r = p.getBoundingClientRect();
const overlap = Math.min(r.bottom, containerRect.bottom) - Math.max(r.top, containerRect.top);
if (!visiblePanel || overlap > 0) visiblePanel = p;
});
if (visiblePanel) {
const isLight = lightPanels.has(
[...visiblePanel.classList].find((c) => c.startsWith("panel-"))
);
nav.classList.toggle("on-light", !!isLight);
}
}
snapContainer.addEventListener("scroll", updateNav);
updateNav();
// Hero entrance
if (!reduced) {
gsap.set([".ph-season", ".ph-center > *", ".ph-bottom"], { opacity: 0, y: 20 });
gsap
.timeline({ delay: 0.5, defaults: { ease: "expo.out" } })
.to(".ph-season", { opacity: 1, y: 0, duration: 0.8 })
.to(".ph-center > *", { opacity: 1, y: 0, duration: 1, stagger: 0.15 }, "-=0.4")
.to(".ph-bottom", { opacity: 1, y: 0, duration: 0.7 }, "-=0.3");
}
// IntersectionObserver for panel entrance animations
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (!entry.isIntersecting || reduced) return;
const panel = entry.target;
if (panel.classList.contains("panel-room")) {
gsap.set([".pr-content > *"], { opacity: 0, x: 30 });
gsap.to(".pr-content > *", {
opacity: 1,
x: 0,
duration: 0.9,
stagger: 0.1,
ease: "expo.out",
delay: 0.2,
});
gsap.set(".pr-image--1", { scale: 1.05 });
gsap.to(".pr-image--1", { scale: 1, duration: 1.2, ease: "power2.out" });
}
if (panel.classList.contains("panel-amenities")) {
gsap.set(".amen-item", { opacity: 0, y: 20 });
gsap.to(".amen-item", {
opacity: 1,
y: 0,
duration: 0.6,
stagger: 0.08,
ease: "expo.out",
delay: 0.3,
});
}
if (panel.classList.contains("panel-dining")) {
gsap.set([".pd-content > *"], { opacity: 0, x: 30 });
gsap.to(".pd-content > *", {
opacity: 1,
x: 0,
duration: 0.9,
stagger: 0.1,
ease: "expo.out",
delay: 0.2,
});
}
if (panel.classList.contains("panel-contact")) {
gsap.set([".pc2-content > *", ".rf-field", ".rf-row"], { opacity: 0, y: 18 });
gsap.to([".pc2-content > *", ".rf-row", ".rf-field", ".btn-submit"], {
opacity: 1,
y: 0,
duration: 0.7,
stagger: 0.08,
ease: "expo.out",
delay: 0.2,
});
}
observer.unobserve(panel);
});
},
{ root: snapContainer, threshold: 0.5 }
);
panels.forEach((p) => observer.observe(p));
// Amenity icon hover sparkle
if (!reduced) {
document.querySelectorAll(".amen-item").forEach((item) => {
item.addEventListener("mouseenter", () => {
gsap.to(item.querySelector(".ai-icon"), { rotation: 45, duration: 0.3, ease: "back.out(2)" });
});
item.addEventListener("mouseleave", () => {
gsap.to(item.querySelector(".ai-icon"), { rotation: 0, duration: 0.3, ease: "back.out(2)" });
});
});
}
// Hero scroll hint button click → scroll snap to room panel
document.getElementById("hero-btn")?.addEventListener("click", (e) => {
e.preventDefault();
document.getElementById("rooms").scrollIntoView({ behavior: "smooth" });
});
window.addEventListener("motion-preference", (e) => {
gsap.globalTimeline.paused(e.detail.reduced);
});<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Grand Résidence — Paris</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Playfair+Display+SC:ital,wght@0,400;0,700;1,400&family=Lato:wght@300;400;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="style.css">
<script type="importmap">{"imports":{"gsap":"https://esm.sh/gsap@3.13.0","gsap/ScrollTrigger":"https://esm.sh/gsap@3.13.0/ScrollTrigger","gsap/SplitText":"https://esm.sh/gsap@3.13.0/SplitText","gsap/Flip":"https://esm.sh/gsap@3.13.0/Flip","gsap/ScrambleTextPlugin":"https://esm.sh/gsap@3.13.0/ScrambleTextPlugin","gsap/TextPlugin":"https://esm.sh/gsap@3.13.0/TextPlugin","gsap/all":"https://esm.sh/gsap@3.13.0/all","gsap/":"https://esm.sh/gsap@3.13.0/","lenis":"https://esm.sh/lenis@1.1.13/dist/lenis.mjs","three":"https://esm.sh/three@0.171.0","three/addons/":"https://esm.sh/three@0.171.0/examples/jsm/"}}</script>
</head>
<body>
<nav class="nav" id="nav">
<a href="#" class="nav-logo">Grand Résidence</a>
<ul class="nav-links">
<li><a href="#rooms">Rooms</a></li>
<li><a href="#amenities">Amenities</a></li>
<li><a href="#dining">Dining</a></li>
<li><a href="#contact">Contact</a></li>
</ul>
<a href="#contact" class="btn-gold">Reserve a Room</a>
</nav>
<!-- Scroll-snap container -->
<div class="snap-container" id="snap-container">
<!-- Panel 1: Hero -->
<section class="snap-panel panel-hero" id="hero">
<div class="ph-bg" aria-hidden="true"></div>
<div class="ph-content">
<div class="ph-top">
<span class="ph-season">Est. 1887 · Paris, France</span>
</div>
<div class="ph-center">
<h1>Where Luxury<br><em>Becomes Home.</em></h1>
<p>A sanctuary in the heart of Paris. Forty-eight rooms and suites, each a world apart.</p>
<a href="#rooms" class="btn-gold btn-gold--lg" id="hero-btn">Explore the Hotel</a>
</div>
<div class="ph-bottom">
<div class="ph-scroll">Scroll to discover</div>
<div class="ph-scroll-line"></div>
</div>
</div>
<div class="panel-counter">01 / 05</div>
</section>
<!-- Panel 2: Room showcase -->
<section class="snap-panel panel-room" id="rooms">
<div class="pr-image pr-image--1" aria-hidden="true"></div>
<div class="pr-content">
<span class="panel-eyebrow">Accommodations</span>
<h2>The Prestige<br>Suite</h2>
<p class="pr-desc">85 square metres of refined French craftsmanship. Original Haussmann cornices, hand-selected antiques, and a private terrace overlooking the Seine.</p>
<div class="pr-details">
<div class="prd-item"><span>Floor</span><span>6th</span></div>
<div class="prd-item"><span>Size</span><span>85 m²</span></div>
<div class="prd-item"><span>View</span><span>Seine & Eiffel</span></div>
<div class="prd-item"><span>From</span><span>€1,200 / night</span></div>
</div>
<a href="#contact" class="btn-gold">Book this Suite</a>
</div>
<div class="panel-counter">02 / 05</div>
</section>
<!-- Panel 3: Amenities -->
<section class="snap-panel panel-amenities" id="amenities">
<div class="pa-content">
<span class="panel-eyebrow">Amenities</span>
<h2>Every Detail,<br><em>Considered.</em></h2>
<div class="amen-grid">
<div class="amen-item"><div class="ai-icon">⬙</div><div class="ai-name">Private Spa</div><div class="ai-desc">Thermal pools, hammam, and signature treatments</div></div>
<div class="amen-item"><div class="ai-icon">⬙</div><div class="ai-name">Rooftop Bar</div><div class="ai-desc">360° views from the 8th floor</div></div>
<div class="amen-item"><div class="ai-icon">⬙</div><div class="ai-name">Michelin Dining</div><div class="ai-desc">Two starred restaurants, one brasserie</div></div>
<div class="amen-item"><div class="ai-icon">⬙</div><div class="ai-name">Concierge</div><div class="ai-desc">24-hour personal service, every request</div></div>
<div class="amen-item"><div class="ai-icon">⬙</div><div class="ai-name">Private Cinema</div><div class="ai-desc">Curated screenings for hotel guests</div></div>
<div class="amen-item"><div class="ai-icon">⬙</div><div class="ai-name">Art Collection</div><div class="ai-desc">180 works, including three Picassos</div></div>
</div>
</div>
<div class="panel-counter">03 / 05</div>
</section>
<!-- Panel 4: Dining -->
<section class="snap-panel panel-dining" id="dining">
<div class="pd-image pd-image--1" aria-hidden="true"></div>
<div class="pd-content">
<span class="panel-eyebrow">Dining</span>
<h2>L'Atmosphère</h2>
<div class="pd-stars">★★</div>
<p>Chef Antoine Mercier's two-starred restaurant reimagines classical French cuisine through a contemporary lens. Seasonal, sustainable, sublime.</p>
<div class="pd-hours">
<span>Lunch: 12h–14h30</span>
<span>Dinner: 19h–22h30</span>
</div>
<a href="#contact" class="btn-gold">Reserve a Table</a>
</div>
<div class="panel-counter">04 / 05</div>
</section>
<!-- Panel 5: Contact / Reservation -->
<section class="snap-panel panel-contact" id="contact">
<div class="pc2-content">
<span class="panel-eyebrow">Reserve</span>
<h2>Begin Your<br><em>Stay.</em></h2>
<div class="reservation-form">
<div class="rf-row">
<div class="rf-field"><label>Arrival</label><div class="rf-input-mock"></div></div>
<div class="rf-field"><label>Departure</label><div class="rf-input-mock"></div></div>
</div>
<div class="rf-row">
<div class="rf-field"><label>Guests</label><div class="rf-input-mock"></div></div>
<div class="rf-field"><label>Room Type</label><div class="rf-input-mock"></div></div>
</div>
<div class="rf-field"><label>Special requests</label><div class="rf-input-mock rf-input-mock--tall"></div></div>
<button class="btn-gold btn-submit">Request Reservation</button>
</div>
<div class="pc2-contact">
<span>+33 1 42 00 00 00</span>
<span>reservations@grandresidence.fr</span>
<span>12 Rue de la Paix, Paris 75002</span>
</div>
</div>
<div class="panel-counter">05 / 05</div>
</section>
</div><!-- /snap-container -->
<script type="module" src="script.js"></script>
</body>
</html>Luxury Hotel — Grand Résidence
Forest green/champagne luxury hotel site using CSS scroll-snap-type:y mandatory for 5 full-height panels. IntersectionObserver fires per-panel entrance animations, nav adapts color on light panels, Playfair Display SC typography.
Source
- Repository:
libs-genclaude - Original demo id:
55-luxury-hotel
Notes
Forest green/champagne luxury hotel site using CSS scroll-snap-type:y mandatory for 5 full-height panels. IntersectionObserver fires per-panel entrance animations, nav adapts color on light panels, Playfair Display SC typography.