1/**
2 * Halcyon — boutique hotels on the Amalfi coast and in Kyoto.
3 *
4 * Demo images: /img/halcyon/{positano-sunset,pool-lemons,suite,ryokan,courtyard-pool,journals}.webp (each with
5 * -900 and -480 variants), included in the repo's public/img/halcyon — copy them next to your page or swap in your own.
6 *
7 * A warm, editorial luxury-hospitality homepage with real photography
8 * and no web fonts.
9 *
10 * Motion (all scroll effects are transform/opacity driven by one rAF loop
11 * that writes a `--p` progress variable onto `[data-hal-p]` elements):
12 * - Pinned hero: the Positano sunset photograph slowly pushes in and
13 * darkens to dusk as you scroll.
14 * - Booking bar: sticky at the bottom of the first screen, then docks
15 * under the condensed nav once you scroll past the hero.
16 * - Clip-path image reveals, a pinned horizontal "houses" gallery,
17 * hover-zoom room cards, an editorial rituals list with crossfading
18 * frame, an auto-advancing testimonial crossfade, map pins that drop
19 * in with routes drawing, and a newsletter form in the footer.
20 *
21 * Reduced motion: parallax, pinning and autoplay are removed; the gallery
22 * becomes a native swipe row; state changes stay instant.
23 * SSR-safe: no window/document access during render.
24 * Accessibility: real buttons/inputs/labels, visible focus rings,
25 * aria-live for the booking result and testimonial, AA contrast.
26 */
27import { memo, useEffect, useRef, useState, type CSSProperties, type ReactNode } from "react";
28
29/* ------------------------------------------------------------------ */
30/* Photography (pre-rendered, see /img/lux) */
31/* ------------------------------------------------------------------ */
32const lerp = (a: number, b: number, t: number) => a + (b - a) * t;
33type SceneKind = "positano" | "ravello" | "gion" | "arashiyama" | "roomSea" | "roomKyoto" | "pool" | "boat" | "tea" | "lantern";
34
35type Ph = { f: string; alt?: string; pos: string; zoom?: number };
36const PHOTO: Record<SceneKind, Ph> = {
37 positano: { f: "positano-sunset", alt: "pastel houses stacked up the hillside above the sea at sunset", pos: "50% 55%" },
38 ravello: { f: "pool-lemons", alt: "lemon pergola and infinity pool above the Amalfi coastline", pos: "50% 50%" },
39 gion: { f: "ryokan", alt: "tatami room with shoji screens opening onto a maple garden", pos: "40% 45%" },
40 arashiyama: { f: "ryokan", alt: "red maple and moss garden seen through an open shoji screen", pos: "78% 30%", zoom: 1.7 },
41 roomSea: { f: "suite", alt: "white bed and sea-view terrace beyond an open arched window", pos: "50% 55%" },
42 roomKyoto: { f: "ryokan", alt: "hinoki tub and tatami floor beside a garden doorway", pos: "40% 95%", zoom: 1.5 },
43 pool: { f: "courtyard-pool", alt: "plunge pool and sun loungers under a lemon tree against stone walls", pos: "50% 60%" },
44 boat: { f: "positano-sunset", alt: "calm Amalfi sea at sunset with a distant headland", pos: "8% 30%", zoom: 1.9 },
45 tea: { f: "ryokan", alt: "tatami room and wooden tub beside a moss garden", pos: "70% 55%", zoom: 1.25 },
46 lantern: { f: "ryokan", alt: "wooden shoji lattice windows of a Kyoto townhouse", pos: "8% 40%", zoom: 1.7 },
47};
48
49/** Responsive photograph from public/img/halcyon (480 / 900 / 1024 px, square). */
50function Photo({ f, alt, className, style, eager = false, sizes }: { f: string; alt: string; className?: string; style?: CSSProperties; eager?: boolean; sizes: string }) {
51 return (
52 <img
53 className={className}
54 src={`/img/halcyon/${f}-900.webp`}
55 srcSet={`/img/halcyon/${f}-480.webp 480w, /img/halcyon/${f}-900.webp 900w, /img/halcyon/${f}.webp 1024w`}
56 sizes={sizes}
57 width={1024}
58 height={1024}
59 alt={alt}
60 loading={eager ? undefined : "lazy"}
61 fetchPriority={eager ? "high" : undefined}
62 decoding="async"
63 style={style}
64 />
65 );
66}
67
68const Scene = memo(function Scene({ kind, label }: { kind: SceneKind; label: string }) {
69 const p = PHOTO[kind];
70 return <Photo f={p.f} alt={label.split(" ").length > 7 ? label : `${label}: ${p.alt}`} className="hal-scene" sizes="(max-width: 700px) 100vw, 40vw" style={{ objectPosition: p.pos, scale: p.zoom, transformOrigin: p.pos }} />;
71});
72
73/* Hero: the Positano photograph; a graded copy fades in as you scroll for dusk. */
74const HeroLandscape = memo(function HeroLandscape() {
75 return (
76 <div className="hal-hero-scene">
77 <Photo f="positano-sunset" alt="Positano terraces at golden hour, pastel houses stacked up the hillside above the Amalfi Coast sea" className="hal-hero-img" eager sizes="100vw" />
78 <Photo f="positano-sunset" alt="" className="hal-hero-img hal-dusk" sizes="100vw" />
79 </div>
80 );
81});
82
83/* ------------------------------------------------------------------ */
84/* Data */
85/* ------------------------------------------------------------------ */
86const HOUSES: { name: string; place: string; scene: SceneKind; rooms: number; since: string; from: string; note: string }[] = [
87 { name: "Casa Halcyon", place: "Positano, Amalfi Coast", scene: "positano", rooms: 24, since: "1962", from: "€690", note: "Fourteen terraces stacked above Spiaggia Grande; the lift opens into a lemon grove." },
88 { name: "Villa Limoni", place: "Ravello, Amalfi Coast", scene: "ravello", rooms: 17, since: "2009", from: "€820", note: "A 13th-century merchant's villa, 365 metres above the sea, with a pergola you can dine under." },
89 { name: "Halcyon Gion", place: "Higashiyama, Kyoto", scene: "gion", rooms: 21, since: "2018", from: "¥96,000", note: "Three restored machiya townhouses joined by a moss courtyard, a minute from Yasaka-jinja." },
90 { name: "Halcyon Arashiyama", place: "Ukyō-ku, Kyoto", scene: "arashiyama", rooms: 24, since: "2023", from: "¥118,000", note: "A riverside ryokan with hinoki baths and a private path into the bamboo before 7 a.m." },
91];
92
93const ROOMS: { name: string; house: string; scene: SceneKind; size: string; feat: string; price: string }[] = [
94 { name: "Sea-view Junior Suite", house: "Casa Halcyon · Positano", scene: "roomSea", size: "42 m²", feat: "Private terrace · rain shower", price: "€740" },
95 { name: "Garden Ryokan Room", house: "Halcyon Arashiyama · Kyoto", scene: "roomKyoto", size: "38 m²", feat: "Hinoki tub · futon or bed", price: "¥118,000" },
96 { name: "Lemon Terrace Suite", house: "Villa Limoni · Ravello", scene: "pool", size: "64 m²", feat: "Plunge pool · pergola", price: "€1,180" },
97];
98
99const RITUALS: { time: string; title: string; where: string; scene: SceneKind }[] = [
100 { time: "06:10", title: "The first boat to Li Galli", where: "Casa Halcyon · with Capitano Enzo", scene: "boat" },
101 { time: "11:00", title: "Lemon harvest on the upper terraces", where: "Villa Limoni · with Signora Aurelia", scene: "ravello" },
102 { time: "16:30", title: "Tea in the moss courtyard", where: "Halcyon Gion · usucha & wagashi", scene: "tea" },
103 { time: "21:00", title: "Lantern walk through Higashiyama", where: "Halcyon Gion · after the crowds", scene: "lantern" },
104];
105
106const QUOTES: { q: string; who: string; stay: string }[] = [
107 { q: "We came for three nights and stayed six. Somebody had pressed our linen with lemon leaves; I still don't know how they knew.", who: "Margot & Lise Verhoeven", stay: "Casa Halcyon · June 2026" },
108 { q: "The bath was filled at the exact moment the bamboo went quiet. Nothing was announced. Everything was anticipated.", who: "Daniel Okafor", stay: "Halcyon Arashiyama · April 2026" },
109 { q: "Dinner under the pergola, the coast turning violet below us — the best table I have sat at in twenty years of travel.", who: "Inés Calderón", stay: "Villa Limoni · September 2025" },
110 { q: "They walked us through Gion at nine, when the lanterns are lit and the streets finally belong to the people who live there.", who: "Henrik & Sofie Lund", stay: "Halcyon Gion · November 2025" },
111];
112
113const DATES = ["Thu 13 May", "Fri 14 May", "Sat 15 May", "Sun 16 May", "Mon 17 May", "Tue 18 May", "Wed 19 May", "Thu 20 May", "Fri 21 May", "Sat 22 May", "Sun 23 May", "Mon 24 May", "Tue 25 May", "Wed 26 May"];
114
115/* ------------------------------------------------------------------ */
116/* Page */
117/* ------------------------------------------------------------------ */
118export function Page() {
119 const rootRef = useRef<HTMLDivElement>(null);
120 const clockRef = useRef<HTMLSpanElement>(null);
121 const [navSmall, setNavSmall] = useState(false);
122 const [docked, setDocked] = useState(false);
123 const [dest, setDest] = useState<"amalfi" | "kyoto">("amalfi");
124 const [arrive, setArrive] = useState(1);
125 const [nights, setNights] = useState(4);
126 const [guests, setGuests] = useState(2);
127 const [datesOpen, setDatesOpen] = useState(false);
128 const [result, setResult] = useState("");
129 const [ritual, setRitual] = useState(0);
130 const [quote, setQuote] = useState(0);
131 const [quotePaused, setQuotePaused] = useState(false);
132 const [email, setEmail] = useState("");
133 const [subscribed, setSubscribed] = useState(false);
134 const [menuOpen, setMenuOpen] = useState(false);
135
136 /* Scroll engine: progress vars, nav + dock state, reveals, gallery sizing. */
137 useEffect(() => {
138 const root = rootRef.current;
139 if (!root) return;
140 const doc = root.ownerDocument;
141 const win = doc.defaultView;
142 if (!win) return;
143 const reduce = win.matchMedia("(prefers-reduced-motion: reduce)").matches;
144 root.classList.add("hal-js");
145
146 // Hosts that put overflow:hidden on BOTH <html> and <body> turn <body> into a
147 // non-scrolling scroll container, which silently breaks position:sticky.
148 // `clip` gives the same visual result without that side effect.
149 let restoreBody: (() => void) | null = null;
150 const fixRaf = win.requestAnimationFrame(() => {
151 const b = doc.body;
152 const bs = win.getComputedStyle(b);
153 const hs = win.getComputedStyle(doc.documentElement);
154 if (hs.overflowX !== "visible" && (bs.overflowX === "hidden" || bs.overflowY === "auto")) {
155 const prev = [b.style.overflowX, b.style.overflowY];
156 b.style.overflowX = "clip";
157 b.style.overflowY = "visible";
158 restoreBody = () => {
159 b.style.overflowX = prev[0];
160 b.style.overflowY = prev[1];
161 };
162 }
163 });
164
165 const progEls = Array.from(root.querySelectorAll<HTMLElement>("[data-hal-p]"));
166 const gallery = root.querySelector<HTMLElement>(".hal-houses");
167 const track = root.querySelector<HTMLElement>(".hal-houses-track");
168 const bar = root.querySelector<HTMLElement>(".hal-book");
169 const cur = root.querySelector<HTMLElement>(".hal-houses-cur");
170 const hero = root.querySelector<HTMLElement>(".hal-hero");
171 let galleryOn = false;
172
173 const measure = () => {
174 galleryOn = !reduce && win.innerWidth > 900;
175 if (gallery && track) {
176 const dist = galleryOn ? Math.max(0, track.scrollWidth - win.innerWidth) : 0;
177 gallery.style.setProperty("--dist", `${dist}px`);
178 gallery.dataset.dist = String(dist);
179 }
180 };
181
182 let raf = 0;
183 let lastSmall = false;
184 let lastDock = false;
185 const update = () => {
186 raf = 0;
187 const vh = win.innerHeight;
188 for (const el of progEls) {
189 const r = el.getBoundingClientRect();
190 const mode = el.dataset.halP;
191 let p = 0;
192 if (mode === "pin") p = (-r.top) / Math.max(1, r.height - vh);
193 else p = (vh - r.top) / (vh + r.height);
194 p = Math.min(1, Math.max(0, p));
195 el.style.setProperty("--p", p.toFixed(4));
196 if (el === gallery && track) {
197 const dist = Number(gallery.dataset.dist || 0);
198 track.style.transform = galleryOn ? `translate3d(${(-p * dist).toFixed(1)}px,0,0)` : "";
199 if (cur) cur.textContent = `0${Math.min(HOUSES.length, 1 + Math.floor(p * HOUSES.length * 0.999))}`;
200 }
201 if (el === hero && clockRef.current) {
202 const mins = Math.round(lerp(17 * 60 + 42, 20 * 60 + 16, Math.min(1, p * 1.25)));
203 clockRef.current.textContent = `${String(Math.floor(mins / 60)).padStart(2, "0")}:${String(mins % 60).padStart(2, "0")}`;
204 el.classList.toggle("is-dusk", p > 0.45);
205 }
206 }
207 const y = win.scrollY || doc.documentElement.scrollTop;
208 const small = y > 24;
209 if (small !== lastSmall) {
210 lastSmall = small;
211 setNavSmall(small);
212 }
213 if (bar) {
214 const top = bar.getBoundingClientRect().top;
215 const dock = top < 120 && y > vh * 0.5;
216 if (dock !== lastDock) {
217 lastDock = dock;
218 setDocked(dock);
219 }
220 }
221 };
222 const onScroll = () => {
223 if (!raf) raf = win.requestAnimationFrame(update);
224 };
225 const onResize = () => {
226 measure();
227 onScroll();
228 };
229 measure();
230 update();
231 win.addEventListener("scroll", onScroll, { passive: true });
232 win.addEventListener("resize", onResize);
233
234 // One-shot reveals.
235 const io = new win.IntersectionObserver(
236 (entries) => {
237 for (const e of entries) {
238 if (e.isIntersecting) {
239 e.target.classList.add("is-in");
240 io.unobserve(e.target);
241 }
242 }
243 },
244 { rootMargin: "0px 0px -12% 0px", threshold: 0.12 },
245 );
246 root.querySelectorAll("[data-hal-rv]").forEach((el) => io.observe(el));
247
248 return () => {
249 win.cancelAnimationFrame(fixRaf);
250 if (raf) win.cancelAnimationFrame(raf);
251 win.removeEventListener("scroll", onScroll);
252 win.removeEventListener("resize", onResize);
253 io.disconnect();
254 restoreBody?.();
255 root.classList.remove("hal-js");
256 };
257 }, []);
258
259 /* Testimonial autoplay (paused on hover/focus, off for reduced motion or hidden tab). */
260 useEffect(() => {
261 const root = rootRef.current;
262 const win = root?.ownerDocument.defaultView;
263 if (!win || quotePaused) return;
264 if (win.matchMedia("(prefers-reduced-motion: reduce)").matches) return;
265 const t = win.setInterval(() => {
266 if (!root!.ownerDocument.hidden) setQuote((q) => (q + 1) % QUOTES.length);
267 }, 6500);
268 return () => win.clearInterval(t);
269 }, [quotePaused]);
270
271 const depart = DATES[Math.min(DATES.length - 1, arrive + nights)] ?? DATES[DATES.length - 1];
272 const nightly = dest === "amalfi" ? 690 : 640;
273 const check = () => {
274 const suites = dest === "amalfi" ? 5 + (arrive % 3) : 3 + (arrive % 4);
275 setResult(
276 `${suites} rooms open ${DATES[arrive].replace(/^\w+ /, "")}–${depart.replace(/^\w+ /, "")} · from €${(nightly + guests * 35).toLocaleString("en-GB")} a night`,
277 );
278 };
279
280 const navLinks = ["Houses", "Rooms", "Rituals", "Journal", "Gift cards"];
281
282 return (
283 <div ref={rootRef} className="hal">
284 <style>{css}</style>
285 <a className="hal-skip" href="#hal-main">Skip to content</a>
286
287 {/* NAV */}
288 <header className={`hal-nav${navSmall ? " is-small" : ""}${menuOpen ? " is-open" : ""}`}>
289 <div className="hal-nav-bar">
290 <a className="hal-logo" href="#hal-top" aria-label="Halcyon — home">
291 <svg viewBox="0 0 28 28" aria-hidden="true" focusable="false"><circle cx="14" cy="14" r="13" fill="none" stroke="currentColor" strokeWidth="1.2" /><path d="M3 16.5 C 8 13.5 11 13.5 14 15 S 20 16.5 25 13.5" fill="none" stroke="currentColor" strokeWidth="1.2" /><circle cx="14" cy="10" r="3.2" fill="currentColor" /></svg>
292 <span>Halcyon</span>
293 </a>
294 <nav className="hal-nav-links" aria-label="Primary">
295 {navLinks.map((l) => (
296 <a key={l} href={`#hal-${l.toLowerCase().replace(/\s/g, "")}`}>{l}</a>
297 ))}
298 </nav>
299 <div className="hal-nav-end">
300 <a className="hal-nav-lang" href="#hal-top" aria-label="Language: English">EN</a>
301 <a className="hal-btn hal-btn-ink hal-btn-sm" href="#hal-book">Reserve</a>
302 <button className="hal-burger" type="button" aria-label={menuOpen ? "Close menu" : "Open menu"} aria-expanded={menuOpen} onClick={() => setMenuOpen((o) => !o)}>
303 <span /><span />
304 </button>
305 </div>
306 </div>
307 <div className="hal-sheet" hidden={!menuOpen}>
308 {navLinks.map((l) => (
309 <a key={l} href={`#hal-${l.toLowerCase().replace(/\s/g, "")}`} onClick={() => setMenuOpen(false)}>{l}</a>
310 ))}
311 </div>
312 </header>
313
314 <main id="hal-main">
315 {/* HERO — pinned, time-of-day */}
316 <section id="hal-top" className="hal-hero" data-hal-p="pin" aria-labelledby="hal-hero-title">
317 <div className="hal-hero-pin">
318 <HeroLandscape />
319 <div className="hal-hero-copy">
320 <p className="hal-eyebrow hal-hero-eyebrow">
321 <span>Positano</span>
322 <i aria-hidden="true" />
323 <span ref={clockRef} className="hal-clock">17:42</span>
324 <i aria-hidden="true" />
325 <span>24°C, sea calm</span>
326 </p>
327 <h1 id="hal-hero-title" className="hal-hero-title">
328 <span className="hal-line"><span>Where the light</span></span>
329 <span className="hal-line"><span><em>lingers</em> longer.</span></span>
330 </h1>
331 <p className="hal-hero-sub">Four small houses on the Amalfi coast and in Kyoto, kept by the same families since 1962. Eighty-six rooms, no lobbies, and a staff who will remember how you take your coffee.</p>
332 </div>
333 <p className="hal-hero-scroll" aria-hidden="true"><span />Scroll for dusk</p>
334 </div>
335 </section>
336
337 {/* BOOKING BAR — floats at the bottom of screen one, docks under the nav */}
338 <div id="hal-book" className={`hal-book${docked ? " is-docked" : ""}`}>
339 <form className="hal-book-card" onSubmit={(e) => { e.preventDefault(); check(); }} aria-label="Check availability">
340 <div className="hal-field hal-field-dest" role="group" aria-labelledby="hal-dest-l">
341 <span className="hal-label" id="hal-dest-l">Destination</span>
342 <div className="hal-seg" role="radiogroup" aria-label="Destination">
343 {(["amalfi", "kyoto"] as const).map((v) => (
344 <button key={v} type="button" role="radio" aria-checked={dest === v} className={dest === v ? "is-on" : ""} onClick={() => { setDest(v); setResult(""); }}>
345 {v === "amalfi" ? "Amalfi Coast" : "Kyoto"}
346 </button>
347 ))}
348 <span className="hal-seg-thumb" style={{ transform: `translateX(${dest === "amalfi" ? 0 : 100}%)` }} aria-hidden="true" />
349 </div>
350 </div>
351 <div className="hal-field hal-field-date">
352 <span className="hal-label" id="hal-arrive-l">Arrive</span>
353 <button type="button" className="hal-date-btn" aria-labelledby="hal-arrive-l" aria-expanded={datesOpen} onClick={() => setDatesOpen((o) => !o)}>
354 {DATES[arrive]} <small>→ {depart}</small>
355 </button>
356 {datesOpen && (
357 <div className="hal-pop" role="listbox" aria-label="Arrival date, May 2027">
358 <p className="hal-pop-title">May 2027</p>
359 <div className="hal-pop-grid">
360 {DATES.slice(0, 12).map((dd, i) => (
361 <button key={dd} type="button" role="option" aria-selected={arrive === i} className={arrive === i ? "is-on" : ""} onClick={() => { setArrive(i); setDatesOpen(false); setResult(""); }}>
362 <small>{dd.slice(0, 3)}</small>
363 {dd.split(" ")[1]}
364 </button>
365 ))}
366 </div>
367 </div>
368 )}
369 </div>
370 <div className="hal-field">
371 <span className="hal-label">Nights</span>
372 <div className="hal-step">
373 <button type="button" aria-label="Fewer nights" onClick={() => setNights((n) => Math.max(1, n - 1))}>−</button>
374 <output aria-live="polite">{nights}</output>
375 <button type="button" aria-label="More nights" onClick={() => setNights((n) => Math.min(10, n + 1))}>+</button>
376 </div>
377 </div>
378 <div className="hal-field">
379 <span className="hal-label">Guests</span>
380 <div className="hal-step">
381 <button type="button" aria-label="Fewer guests" onClick={() => setGuests((n) => Math.max(1, n - 1))}>−</button>
382 <output aria-live="polite">{guests}</output>
383 <button type="button" aria-label="More guests" onClick={() => setGuests((n) => Math.min(6, n + 1))}>+</button>
384 </div>
385 </div>
386 <button className="hal-btn hal-btn-terra hal-book-cta" type="submit">
387 <span className="hal-cta-full">Check availability</span>
388 <span className="hal-cta-short" aria-hidden="true">Search</span>
389 <svg viewBox="0 0 16 16" aria-hidden="true" focusable="false"><path d="M3 8h10M9 4l4 4-4 4" fill="none" stroke="currentColor" strokeWidth="1.5" /></svg>
390 </button>
391 <p className={`hal-book-result${result ? " is-on" : ""}`} aria-live="polite">{result}</p>
392 </form>
393 </div>
394
395 {/* INTRO */}
396 <section className="hal-intro" aria-labelledby="hal-intro-title">
397 <div className="hal-wrap hal-intro-grid">
398 <div className="hal-intro-text">
399 <p className="hal-eyebrow" data-hal-rv>Est. 1962 · Positano</p>
400 <h2 id="hal-intro-title" className="hal-h2" data-hal-rv>A house is not a hotel until somebody waits up for you.</h2>
401 <p className="hal-lede hal-drop" data-hal-rv>Nonna Teresa Aiello opened eight rooms above Spiaggia Grande with a borrowed espresso machine and a rule that every guest be met at the boat. Sixty-four years later her grandchildren keep four houses on two coasts — and the rule.</p>
402 <p className="hal-body" data-hal-rv>We have never built a lobby. You arrive into a kitchen, a garden, a tea room. Rooms are named, not numbered. In Kyoto we found a family who had been doing the same for nine generations, and asked if we might learn.</p>
403 <dl className="hal-figs" data-hal-rv>
404 <div><dt>Houses</dt><dd>4</dd></div>
405 <div><dt>Rooms & suites</dt><dd>86</dd></div>
406 <div><dt>Guests who return</dt><dd>61%</dd></div>
407 </dl>
408 </div>
409 <div className="hal-intro-media" data-hal-p="view">
410 <figure className="hal-clip hal-clip-a" data-hal-rv>
411 <div className="hal-clip-in"><Scene kind="positano" label="Casa Halcyon's terraces above Positano at golden hour" /></div>
412 <figcaption>Casa Halcyon, from the ferry at 18:20</figcaption>
413 </figure>
414 <figure className="hal-clip hal-clip-b" data-hal-rv>
415 <div className="hal-clip-in"><Scene kind="roomKyoto" label="A tatami room at Halcyon Arashiyama opening onto a maple garden" /></div>
416 <figcaption>Room Kaede, Arashiyama</figcaption>
417 </figure>
418 </div>
419 </div>
420 </section>
421
422 {/* HOUSES — pinned horizontal gallery */}
423 <section id="hal-houses" className="hal-houses" data-hal-p="pin" aria-labelledby="hal-houses-title">
424 <div className="hal-houses-pin">
425 <div className="hal-houses-head hal-wrap">
426 <div>
427 <p className="hal-eyebrow">The houses</p>
428 <h2 id="hal-houses-title" className="hal-h2">Four houses, two coasts.</h2>
429 </div>
430 <div className="hal-houses-meter" aria-hidden="true">
431 <span className="hal-houses-bar"><i /></span>
432 <span className="hal-houses-count"><b className="hal-houses-cur">01</b> — 04</span>
433 </div>
434 </div>
435 <ol className="hal-houses-track">
436 {HOUSES.map((h, i) => (
437 <li key={h.name} className="hal-house">
438 <a href="#hal-rooms" className="hal-house-link" aria-label={`${h.name}, ${h.place} — from ${h.from} a night`}>
439 <div className="hal-house-img"><div className="hal-house-par"><Scene kind={h.scene} label={`${h.name}, ${h.place}`} /></div></div>
440 <div className="hal-house-info">
441 <span className="hal-house-n">0{i + 1}</span>
442 <div>
443 <h3 className="hal-h3">{h.name}</h3>
444 <p className="hal-house-place">{h.place} · since {h.since}</p>
445 <p className="hal-house-note">{h.note}</p>
446 </div>
447 <p className="hal-house-meta"><span>{h.rooms} rooms</span><span>from {h.from}</span></p>
448 </div>
449 </a>
450 </li>
451 ))}
452 </ol>
453 </div>
454 </section>
455
456 {/* ROOMS */}
457 <section id="hal-rooms" className="hal-rooms" aria-labelledby="hal-rooms-title">
458 <div className="hal-wrap">
459 <div className="hal-sec-head">
460 <p className="hal-eyebrow" data-hal-rv>Rooms & suites</p>
461 <h2 id="hal-rooms-title" className="hal-h2" data-hal-rv>Rooms are named, never numbered.</h2>
462 <p className="hal-body hal-sec-sub" data-hal-rv>Every room has a window that frames something worth waking for. Rates include breakfast, the minibar, and the boat or car from wherever you land.</p>
463 </div>
464 <ul className="hal-room-grid">
465 {ROOMS.map((r, i) => (
466 <li key={r.name} className="hal-room" data-hal-rv style={{ "--d": `${i * 90}ms` } as CSSProperties}>
467 <a href="#hal-book" className="hal-room-link">
468 <div className="hal-room-img"><Scene kind={r.scene} label={`${r.name} at ${r.house}`} /></div>
469 <div className="hal-room-body">
470 <p className="hal-room-house">{r.house}</p>
471 <h3 className="hal-h3">{r.name}</h3>
472 <p className="hal-room-feat">{r.size} · {r.feat}</p>
473 <p className="hal-room-foot">
474 <span>from <strong>{r.price}</strong> / night</span>
475 <span className="hal-room-cta">View room <svg viewBox="0 0 16 16" aria-hidden="true" focusable="false"><path d="M3 8h10M9 4l4 4-4 4" fill="none" stroke="currentColor" strokeWidth="1.5" /></svg></span>
476 </p>
477 </div>
478 </a>
479 </li>
480 ))}
481 </ul>
482 </div>
483 </section>
484
485 {/* RITUALS */}
486 <section id="hal-rituals" className="hal-rituals" aria-labelledby="hal-rituals-title">
487 <div className="hal-wrap hal-rituals-grid">
488 <div className="hal-rituals-frame" aria-hidden="true">
489 {RITUALS.map((r, i) => (
490 <div key={r.title} className={`hal-rituals-img${ritual === i ? " is-on" : ""}`}><Scene kind={r.scene} label={r.title} /></div>
491 ))}
492 <span className="hal-rituals-time">{RITUALS[ritual].time}</span>
493 </div>
494 <div>
495 <p className="hal-eyebrow" data-hal-rv>A day, kept slowly</p>
496 <h2 id="hal-rituals-title" className="hal-h2" data-hal-rv>Small rituals, booked by nobody.</h2>
497 <ol className="hal-rituals-list">
498 {RITUALS.map((r, i) => (
499 <li key={r.title}>
500 <button type="button" className={`hal-ritual${ritual === i ? " is-on" : ""}`} aria-pressed={ritual === i} onMouseEnter={() => setRitual(i)} onFocus={() => setRitual(i)} onClick={() => setRitual(i)}>
501 <span className="hal-ritual-time">{r.time}</span>
502 <span className="hal-ritual-title">{r.title}</span>
503 <span className="hal-ritual-where">{r.where}</span>
504 </button>
505 </li>
506 ))}
507 </ol>
508 </div>
509 </div>
510 </section>
511
512 {/* TESTIMONIALS */}
513 <section className="hal-quotes" aria-labelledby="hal-quotes-title" onMouseEnter={() => setQuotePaused(true)} onMouseLeave={() => setQuotePaused(false)} onFocus={() => setQuotePaused(true)} onBlur={() => setQuotePaused(false)}>
514 <div className="hal-wrap hal-quotes-in">
515 <figure className="hal-quotes-fig" data-hal-p="view">
516 <Photo f="journals" alt="Hand-written family journals and faded photographs spread on terracotta" className="hal-quotes-img" sizes="(max-width: 900px) 90vw, 480px" />
517 </figure>
518 <div className="hal-quotes-body">
519 <h2 id="hal-quotes-title" className="hal-eyebrow">From the guest book</h2>
520 <div className="hal-quote-stack" aria-live="polite">
521 {QUOTES.map((q, i) => (
522 <figure key={q.who} className={`hal-quote${quote === i ? " is-on" : ""}`} aria-hidden={quote !== i}>
523 <blockquote><p>“{q.q}”</p></blockquote>
524 <figcaption><strong>{q.who}</strong><span>{q.stay}</span></figcaption>
525 </figure>
526 ))}
527 </div>
528 <div className="hal-quote-dots">
529 {QUOTES.map((q, i) => (
530 <button key={q.who} type="button" aria-label={`Show quote ${i + 1} of ${QUOTES.length}: ${q.who}`} aria-current={quote === i} className={quote === i ? "is-on" : ""} onClick={() => setQuote(i)}>
531 <i style={{ animationPlayState: quotePaused ? "paused" : "running" }} />
532 </button>
533 ))}
534 </div>
535 </div>
536 </div>
537 </section>
538
539 {/* MAP */}
540 <section id="hal-journal" className="hal-map" aria-labelledby="hal-map-title">
541 <div className="hal-wrap">
542 <div className="hal-sec-head">
543 <p className="hal-eyebrow" data-hal-rv>Getting here</p>
544 <h2 id="hal-map-title" className="hal-h2" data-hal-rv>Two coasts, 9,600 km apart, one way of keeping house.</h2>
545 </div>
546 <div className="hal-map-grid">
547 <figure className="hal-map-card" data-hal-rv>
548 <svg className="hal-map-svg" viewBox="0 0 600 420" role="img" aria-label="Map of the Amalfi Coast with Casa Halcyon in Positano and Villa Limoni in Ravello">
549 <rect width="600" height="420" fill="#E7EEEC" />
550 <path d="M0 120 C 60 130 110 170 150 200 C 190 230 230 226 270 240 C 320 256 350 250 390 238 C 430 226 470 236 520 210 C 550 196 580 170 600 160 L600 0 L0 0Z" fill="#EDE3D2" />
551 <path className="hal-draw" d="M0 120 C 60 130 110 170 150 200 C 190 230 230 226 270 240 C 320 256 350 250 390 238 C 430 226 470 236 520 210 C 550 196 580 170 600 160" fill="none" stroke="#A9502B" strokeWidth="1.5" />
552 <path className="hal-draw hal-draw-route" d="M180 220 C 240 300 380 300 420 240" fill="none" stroke="#2F5866" strokeWidth="1.5" strokeDasharray="4 6" />
553 <ellipse cx="80" cy="330" rx="40" ry="16" fill="#EDE3D2" />
554 <text x="80" y="370" className="hal-map-t hal-map-t-sm" textAnchor="middle">Capri</text>
555 <text x="300" y="380" className="hal-map-t hal-map-t-sea" textAnchor="middle">Golfo di Salerno</text>
556 <text x="300" y="90" className="hal-map-t hal-map-t-sm" textAnchor="middle">Monti Lattari</text>
557 <g className="hal-pin" style={{ "--d": "300ms" } as CSSProperties} transform="translate(180 214)">
558 <path d="M0 0 C -10 -14 -14 -20 -14 -28 A14 14 0 0 1 14 -28 C 14 -20 10 -14 0 0Z" fill="#A9502B" />
559 <circle cy="-28" r="5" fill="#FBF8F3" />
560 </g>
561 <text x="180" y="244" className="hal-map-t" textAnchor="middle">Positano</text>
562 <g className="hal-pin" style={{ "--d": "520ms" } as CSSProperties} transform="translate(420 232)">
563 <path d="M0 0 C -10 -14 -14 -20 -14 -28 A14 14 0 0 1 14 -28 C 14 -20 10 -14 0 0Z" fill="#A9502B" />
564 <circle cy="-28" r="5" fill="#FBF8F3" />
565 </g>
566 <text x="420" y="262" className="hal-map-t" textAnchor="middle">Ravello</text>
567 </svg>
568 <figcaption>
569 <strong>Amalfi Coast</strong>
570 <span>Naples airport → Positano 75 min by car, or 40 min by our boat from Salerno. Positano → Ravello 35 min along the SS163.</span>
571 </figcaption>
572 </figure>
573 <figure className="hal-map-card" data-hal-rv style={{ "--d": "120ms" } as CSSProperties}>
574 <svg className="hal-map-svg" viewBox="0 0 600 420" role="img" aria-label="Map of Kyoto with Halcyon Gion in Higashiyama and Halcyon Arashiyama in the west">
575 <rect width="600" height="420" fill="#EEEADF" />
576 {Array.from({ length: 9 }).map((_, i) => (
577 <line key={`v${i}`} x1={160 + i * 40} y1="40" x2={160 + i * 40} y2="400" stroke="#DCD3C1" strokeWidth="1" />
578 ))}
579 {Array.from({ length: 9 }).map((_, i) => (
580 <line key={`h${i}`} x1="160" y1={40 + i * 45} x2="500" y2={40 + i * 45} stroke="#DCD3C1" strokeWidth="1" />
581 ))}
582 <path d="M0 0 L120 0 C 100 80 90 160 110 240 C 120 300 90 360 60 420 L0 420Z" fill="#DCE2CC" />
583 <path className="hal-draw" d="M430 0 C 440 80 445 160 438 240 C 432 320 440 380 450 420" fill="none" stroke="#7FA3AC" strokeWidth="5" />
584 <path className="hal-draw" d="M70 60 C 90 140 70 220 100 300 C 120 350 140 390 150 420" fill="none" stroke="#7FA3AC" strokeWidth="4" />
585 <path className="hal-draw hal-draw-route" d="M110 200 C 200 250 360 250 470 190" fill="none" stroke="#2F5866" strokeWidth="1.5" strokeDasharray="4 6" />
586 <text x="448" y="400" className="hal-map-t hal-map-t-sea">Kamo-gawa</text>
587 <rect x="300" y="355" width="70" height="16" fill="#D6CDB9" />
588 <text x="335" y="390" className="hal-map-t hal-map-t-sm" textAnchor="middle">Kyoto Station</text>
589 <g className="hal-pin" style={{ "--d": "300ms" } as CSSProperties} transform="translate(110 196)">
590 <path d="M0 0 C -10 -14 -14 -20 -14 -28 A14 14 0 0 1 14 -28 C 14 -20 10 -14 0 0Z" fill="#A9502B" />
591 <circle cy="-28" r="5" fill="#FBF8F3" />
592 </g>
593 <text x="110" y="226" className="hal-map-t" textAnchor="middle">Arashiyama</text>
594 <g className="hal-pin" style={{ "--d": "520ms" } as CSSProperties} transform="translate(470 186)">
595 <path d="M0 0 C -10 -14 -14 -20 -14 -28 A14 14 0 0 1 14 -28 C 14 -20 10 -14 0 0Z" fill="#A9502B" />
596 <circle cy="-28" r="5" fill="#FBF8F3" />
597 </g>
598 <text x="470" y="216" className="hal-map-t" textAnchor="middle">Gion</text>
599 </svg>
600 <figcaption>
601 <strong>Kyoto</strong>
602 <span>Kyoto Station → Gion 18 min, → Arashiyama 25 min. Our car meets every Nozomi; a house-to-house transfer runs twice daily.</span>
603 </figcaption>
604 </figure>
605 </div>
606 </div>
607 </section>
608 </main>
609
610 {/* FOOTER + NEWSLETTER */}
611 <footer className="hal-foot">
612 <div className="hal-wrap">
613 <div className="hal-news">
614 <div>
615 <p className="hal-eyebrow hal-eyebrow-light">Letters from the coast</p>
616 <h2 className="hal-h2 hal-news-title">Four letters a year. Never more.</h2>
617 <p className="hal-news-sub">First word on the lemon harvest, autumn rooms in Kyoto, and the week each spring we open Casa Halcyon early.</p>
618 </div>
619 <form className="hal-news-form" onSubmit={(e) => { e.preventDefault(); if (/\S+@\S+\.\S+/.test(email)) setSubscribed(true); }}>
620 <label className="hal-news-label" htmlFor="hal-email">Email address</label>
621 <div className={`hal-news-row${subscribed ? " is-done" : ""}`}>
622 <input id="hal-email" type="email" required placeholder="you@example.com" autoComplete="email" value={email} onChange={(e) => setEmail(e.target.value)} disabled={subscribed} />
623 <button type="submit" className="hal-btn hal-btn-sand" disabled={subscribed}>{subscribed ? "Subscribed" : "Subscribe"}</button>
624 </div>
625 <p className="hal-news-note" aria-live="polite">{subscribed ? `Grazie — the next letter reaches ${email} in early spring.` : "We never share your address. Unsubscribe in one click."}</p>
626 </form>
627 </div>
628 <div className="hal-foot-cols">
629 <div>
630 <p className="hal-foot-h">Houses</p>
631 <ul>{HOUSES.map((h) => <li key={h.name}><a href="#hal-houses">{h.name}</a></li>)}</ul>
632 </div>
633 <div>
634 <p className="hal-foot-h">Stay</p>
635 <ul>{["Rooms & suites", "Seasonal offers", "Gift cards", "Weddings & buy-outs"].map((x) => <li key={x}><a href="#hal-rooms">{x}</a></li>)}</ul>
636 </div>
637 <div>
638 <p className="hal-foot-h">Halcyon</p>
639 <ul>{["Our story", "Careers", "Press room", "Sustainability report 2025"].map((x) => <li key={x}><a href="#hal-top">{x}</a></li>)}</ul>
640 </div>
641 <div>
642 <p className="hal-foot-h">Reservations</p>
643 <ul>
644 <li><a href="#hal-book">+39 089 875 0162</a></li>
645 <li><a href="#hal-book">stay@halcyon.example</a></li>
646 <li><span>Daily 08:00–22:00 CET</span></li>
647 </ul>
648 </div>
649 </div>
650 <p className="hal-foot-mark" aria-hidden="true">Halcyon</p>
651 <div className="hal-foot-legal">
652 <span>© 2026 Halcyon Case & Ryokan S.r.l. · Via Cristoforo Colombo 41, 84017 Positano (SA)</span>
653 <span><a href="#hal-top">Privacy</a><a href="#hal-top">Cookies</a><a href="#hal-top">Accessibility</a></span>
654 </div>
655 </div>
656 </footer>
657 </div>
658 );
659}
660
661export default Page;
662
663/* ------------------------------------------------------------------ */
664/* Styles */
665/* ------------------------------------------------------------------ */
666const css = String.raw`
667.hal {
668 --hal-sand: #F3ECE2;
669 --hal-paper: #FBF8F3;
670 --hal-ink: #1E1915;
671 --hal-ink-2: #5B5048;
672 --hal-terra: #A9502B;
673 --hal-terra-2: #C8693F;
674 --hal-sea: #2F5866;
675 --hal-night: #191513;
676 --hal-line: rgba(30, 25, 21, 0.14);
677 --hal-serif: ui-serif, "New York", "Iowan Old Style", "Palatino Linotype", Palatino, Georgia, serif;
678 --hal-sans: ui-sans-serif, -apple-system, BlinkMacSystemFont, "SF Pro Text", "Inter", "Segoe UI", Roboto, sans-serif;
679 --fd-ease-out: cubic-bezier(0.22, 1, 0.36, 1);
680 --fd-ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
681 --fd-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
682 --fd-dur-1: 120ms;
683 --fd-dur-2: 240ms;
684 --fd-dur-3: 420ms;
685 --fd-dur-4: 700ms;
686 --hal-nav-h: 84px;
687 --hal-nav-s: 60px;
688 position: relative;
689 display: block;
690 width: 100%;
691 margin: 0;
692 padding: 0;
693 overflow-x: clip;
694 background: var(--hal-sand);
695 color: var(--hal-ink);
696 font: 400 17px/1.6 var(--hal-sans);
697 letter-spacing: 0;
698 text-align: left;
699 -webkit-font-smoothing: antialiased;
700 -moz-osx-font-smoothing: grayscale;
701}
702.hal *, .hal *::before, .hal *::after {
703 box-sizing: border-box;
704 margin: 0;
705 padding: 0;
706 border: 0 solid transparent;
707 font: inherit;
708 color: inherit;
709 letter-spacing: inherit;
710 text-decoration: none;
711 list-style: none;
712 background: none;
713 -webkit-appearance: none;
714 appearance: none;
715 min-width: 0;
716}
717.hal :where(svg) { display: block; overflow: hidden; }
718.hal :where(button) { cursor: pointer; text-align: inherit; line-height: inherit; }
719.hal :where(button:disabled) { cursor: default; }
720.hal :focus-visible { outline: 2px solid rgba(169, 80, 43, 0.7); outline-offset: 2px; border-radius: 6px; }
721.hal [hidden] { display: none !important; }
722.hal-skip { position: absolute; left: 16px; top: -60px; z-index: 100; padding: 10px 16px; background: var(--hal-ink); color: var(--hal-paper); border-radius: 9999px; font: 500 13px/1 var(--hal-sans); }
723.hal-skip:focus { top: 12px; }
724
725.hal-wrap { width: 100%; max-width: 1280px; margin: 0 auto; padding: 0 40px; }
726.hal-eyebrow { display: flex; align-items: center; gap: 10px; font: 500 12px/1.4 var(--hal-sans); letter-spacing: 0.18em; text-transform: uppercase; color: var(--hal-terra); }
727.hal-h2 { font: 400 clamp(32px, 4.2vw, 64px)/1.06 var(--hal-serif); letter-spacing: -0.025em; color: var(--hal-ink); text-wrap: balance; }
728.hal-h3 { font: 400 28px/1.15 var(--hal-serif); letter-spacing: -0.015em; color: var(--hal-ink); }
729.hal-lede { font: 400 24px/1.5 var(--hal-serif); letter-spacing: -0.01em; color: var(--hal-ink); }
730.hal-body { font: 400 17px/1.7 var(--hal-sans); color: var(--hal-ink-2); }
731.hal-scene { display: block; width: 100%; height: 100%; max-width: none; object-fit: cover; }
732
733/* Buttons */
734.hal-btn { display: inline-flex; align-items: center; justify-content: center; gap: 10px; height: 52px; padding: 0 26px; border-radius: 9999px; font: 500 15px/1 var(--hal-sans); letter-spacing: 0.01em; white-space: nowrap; transition: background-color var(--fd-dur-2) var(--fd-ease-out), color var(--fd-dur-2) var(--fd-ease-out), transform var(--fd-dur-2) var(--fd-spring); }
735.hal-btn svg { width: 16px; height: 16px; transition: transform var(--fd-dur-2) var(--fd-ease-out); }
736.hal-btn:hover svg { transform: translateX(3px); }
737.hal-btn:active { transform: scale(0.96); transition-duration: var(--fd-dur-1); }
738.hal-btn-sm { height: 40px; padding: 0 18px; font-size: 13px; }
739.hal-btn-ink { background: var(--hal-ink); color: var(--hal-paper); }
740.hal-btn-ink:hover { background: #3A302A; }
741.hal-btn-terra { background: var(--hal-terra); color: #FFF8F1; }
742.hal-btn-terra:hover { background: #923F1E; }
743.hal-btn-sand { background: var(--hal-sand); color: var(--hal-ink); }
744.hal-btn-sand:hover { background: #FFFFFF; }
745
746/* NAV */
747.hal-nav { position: sticky; top: 0; z-index: 60; height: var(--hal-nav-h); margin-bottom: calc(var(--hal-nav-h) * -1); pointer-events: none; }
748.hal-nav-bar { pointer-events: auto; position: relative; display: flex; align-items: center; justify-content: space-between; gap: 24px; height: var(--hal-nav-h); padding: 0 40px; transition: height var(--fd-dur-3) var(--fd-ease-out), background-color var(--fd-dur-3) var(--fd-ease-out), box-shadow var(--fd-dur-3) var(--fd-ease-out); }
749.hal-nav.is-small .hal-nav-bar { height: var(--hal-nav-s); background: rgba(243, 236, 226, 0.86); -webkit-backdrop-filter: saturate(1.4) blur(18px); backdrop-filter: saturate(1.4) blur(18px); box-shadow: 0 1px 0 var(--hal-line); }
750.hal-logo { display: inline-flex; align-items: center; gap: 12px; color: var(--hal-ink); font: 400 22px/1 var(--hal-serif); letter-spacing: 0.22em; text-transform: uppercase; }
751.hal-logo svg { width: 28px; height: 28px; transition: transform var(--fd-dur-3) var(--fd-ease-out); }
752.hal-nav.is-small .hal-logo svg { transform: scale(0.86); }
753.hal-nav-links { display: flex; gap: 34px; }
754.hal-nav-links a { position: relative; font: 500 14px/1 var(--hal-sans); letter-spacing: 0.02em; color: var(--hal-ink); padding: 8px 0; }
755.hal-nav-links a::after { content: ""; position: absolute; left: 0; right: 0; bottom: 2px; height: 1px; background: currentColor; transform: scaleX(0); transform-origin: right; transition: transform var(--fd-dur-3) var(--fd-ease-out); }
756.hal-nav-links a:hover::after { transform: scaleX(1); transform-origin: left; }
757.hal-nav-end { display: flex; align-items: center; gap: 18px; }
758.hal-nav-lang { font: 500 13px/1 var(--hal-sans); letter-spacing: 0.08em; color: var(--hal-ink-2); }
759.hal-burger { display: none; width: 44px; height: 44px; border-radius: 9999px; position: relative; }
760.hal-burger span { position: absolute; left: 13px; right: 13px; height: 1.5px; background: var(--hal-ink); transition: transform var(--fd-dur-3) var(--fd-ease-out); }
761.hal-burger span:first-child { top: 18px; }
762.hal-burger span:last-child { top: 25px; }
763.hal-nav.is-open .hal-burger span:first-child { transform: translateY(3.5px) rotate(45deg); }
764.hal-nav.is-open .hal-burger span:last-child { transform: translateY(-3.5px) rotate(-45deg); }
765.hal-sheet { pointer-events: auto; display: flex; flex-direction: column; padding: 8px 24px 24px; background: rgba(243, 236, 226, 0.97); box-shadow: 0 1px 0 var(--hal-line), 0 30px 60px -30px rgba(30, 25, 21, 0.3); }
766.hal-sheet a { font: 400 28px/1.2 var(--hal-serif); padding: 12px 0; border-bottom: 1px solid var(--hal-line); color: var(--hal-ink); }
767
768/* HERO */
769.hal-hero { position: relative; height: 190vh; height: 190svh; background: #EAD3B4; --p: 0; }
770.hal-hero-pin { position: sticky; top: 0; height: 100vh; height: 100svh; min-height: 620px; overflow: hidden; }
771.hal-hero-scene { position: absolute; inset: 0; }
772.hal-hero-scene::after { content: ""; position: absolute; inset: 0; background: linear-gradient(90deg, rgba(246, 228, 204, 0.88) 0%, rgba(246, 228, 204, 0.62) 34%, rgba(246, 228, 204, 0) 68%); transition: background var(--fd-dur-4) var(--fd-ease-out); }
773.hal-hero.is-dusk .hal-hero-scene::after { background: linear-gradient(90deg, rgba(26, 16, 36, 0.72) 0%, rgba(26, 16, 36, 0.42) 34%, rgba(26, 16, 36, 0) 68%); }
774.hal-hero-img { position: absolute; left: 0; top: 0; display: block; width: 100%; height: 100%; max-width: none; object-fit: cover; object-position: 55% 40%; transform: scale(calc(1 + var(--p) * 0.08)); transform-origin: 60% 70%; will-change: transform; }
775.hal-dusk { opacity: var(--p); filter: brightness(0.55) saturate(0.85) hue-rotate(-14deg) contrast(1.1); }
776/* Camera cranes up as you scroll: near layers fall away faster than far ones, the sun sets behind the sea. */
777
778.hal-hero-copy { position: absolute; left: 0; right: 0; top: 0; bottom: 0; display: flex; flex-direction: column; justify-content: center; padding: var(--hal-nav-h) 40px 150px; max-width: 1280px; margin: 0 auto; transform: translate3d(0, calc(var(--p) * -60px), 0); opacity: calc(1.6 - var(--p) * 1.9); }
779.hal-hero-eyebrow { color: var(--hal-ink); transition: color var(--fd-dur-4) var(--fd-ease-out); margin-bottom: 28px; }
780.hal-hero-eyebrow i { width: 4px; height: 4px; border-radius: 50%; background: currentColor; opacity: 0.6; }
781.hal-clock { font-variant-numeric: tabular-nums; min-width: 3.2em; }
782.hal-hero-title { font: 400 clamp(56px, 7.4vw, 120px)/0.98 var(--hal-serif); letter-spacing: -0.04em; color: var(--hal-ink); max-width: 9.5em; transition: color var(--fd-dur-4) var(--fd-ease-out); }
783.hal-hero-title em { font-style: italic; color: var(--hal-terra); transition: color var(--fd-dur-4) var(--fd-ease-out); }
784.hal-line { display: block; overflow: hidden; padding-bottom: 0.08em; margin-bottom: -0.08em; }
785.hal-line > span { display: block; transform: translateY(105%); animation: hal-rise 1.2s var(--fd-ease-out) forwards; }
786.hal-line:nth-child(2) > span { animation-delay: 140ms; }
787@keyframes hal-rise { to { transform: translateY(0); } }
788.hal-hero-sub { max-width: 470px; margin-top: 30px; font: 400 17px/1.65 var(--hal-sans); color: var(--hal-ink-2); opacity: 0; animation: hal-fade 1s var(--fd-ease-out) 500ms forwards; transition: color var(--fd-dur-4) var(--fd-ease-out); }
789@keyframes hal-fade { from { opacity: 0; transform: translateY(14px); } to { opacity: 1; transform: none; } }
790.hal-hero.is-dusk .hal-hero-title, .hal-hero.is-dusk .hal-hero-eyebrow { color: #FBF3E8; }
791.hal-hero.is-dusk .hal-hero-title em { color: #F2B48C; }
792.hal-hero.is-dusk .hal-hero-sub { color: rgba(251, 243, 232, 0.86); }
793.hal-hero-scroll { position: absolute; right: 40px; bottom: 150px; display: flex; align-items: center; gap: 12px; font: 500 12px/1 var(--hal-sans); letter-spacing: 0.18em; text-transform: uppercase; color: var(--hal-ink); writing-mode: vertical-rl; opacity: calc(1 - var(--p) * 4); }
794.hal-hero-scroll span { display: block; width: 1px; height: 56px; background: currentColor; transform-origin: top; animation: hal-tick 2.4s var(--fd-ease-in-out) infinite; }
795@keyframes hal-tick { 0% { transform: scaleY(0); } 50% { transform: scaleY(1); transform-origin: top; } 51% { transform-origin: bottom; } 100% { transform: scaleY(0); transform-origin: bottom; } }
796.hal-l-sky, .hal-l-sun, .hal-l-far, .hal-l-sea, .hal-l-cliff, .hal-l-front { animation: hal-settle 1.6s var(--fd-ease-out) both; }
797@keyframes hal-settle { from { opacity: 0; translate: 0 3%; } to { opacity: 1; translate: 0 0; } }
798.hal-l-sky { animation-name: hal-fade-in; }
799@keyframes hal-fade-in { from { opacity: 0; } to { opacity: 1; } }
800
801/* BOOKING BAR */
802.hal-book { position: sticky; top: calc(var(--hal-nav-s) + 10px); bottom: 24px; z-index: 50; display: flex; justify-content: center; margin-top: -120px; padding: 0 40px; height: 96px; pointer-events: none; }
803.hal-book-card { pointer-events: auto; position: relative; display: grid; grid-template-columns: auto 1.3fr 0.8fr 0.8fr auto; align-items: center; gap: 0; width: 100%; max-width: 1120px; height: 96px; padding: 0 12px 0 24px; border-radius: 24px; background: rgba(251, 248, 243, 0.94); -webkit-backdrop-filter: saturate(1.3) blur(20px); backdrop-filter: saturate(1.3) blur(20px); box-shadow: 0 1px 0 rgba(255, 255, 255, 0.6) inset, 0 24px 60px -24px rgba(30, 25, 21, 0.35), 0 0 0 1px rgba(30, 25, 21, 0.06); transition: height var(--fd-dur-3) var(--fd-ease-out), border-radius var(--fd-dur-3) var(--fd-ease-out), box-shadow var(--fd-dur-3) var(--fd-ease-out), max-width var(--fd-dur-4) var(--fd-ease-out); }
804.hal-book.is-docked .hal-book-card { height: 68px; max-width: 1200px; border-radius: 9999px; box-shadow: 0 0 0 1px rgba(30, 25, 21, 0.08), 0 12px 30px -16px rgba(30, 25, 21, 0.3); }
805.hal-field { position: relative; display: flex; flex-direction: column; justify-content: center; gap: 6px; height: 100%; padding: 0 22px; border-left: 1px solid var(--hal-line); }
806.hal-field-dest { border-left: 0; padding-left: 0; }
807.hal-label { display: block; font: 500 11px/1 var(--hal-sans); letter-spacing: 0.16em; text-transform: uppercase; color: var(--hal-ink-2); transition: opacity var(--fd-dur-2) var(--fd-ease-out), height var(--fd-dur-3) var(--fd-ease-out); }
808.hal-book.is-docked .hal-label { opacity: 0; height: 0; margin-bottom: -6px; overflow: hidden; }
809.hal-seg { position: relative; display: grid; grid-template-columns: 1fr 1fr; padding: 4px; border-radius: 9999px; background: rgba(30, 25, 21, 0.06); }
810.hal-seg button { position: relative; z-index: 1; height: 36px; padding: 0 16px; border-radius: 9999px; font: 500 14px/1 var(--hal-sans); color: var(--hal-ink-2); text-align: center; white-space: nowrap; transition: color var(--fd-dur-2) var(--fd-ease-out); }
811.hal-seg button.is-on { color: var(--hal-ink); }
812.hal-seg-thumb { position: absolute; top: 4px; bottom: 4px; left: 4px; width: calc(50% - 4px); border-radius: 9999px; background: #FFFFFF; box-shadow: 0 1px 2px rgba(30, 25, 21, 0.12), 0 4px 12px -4px rgba(30, 25, 21, 0.18); transition: transform var(--fd-dur-3) var(--fd-spring); }
813.hal-date-btn { display: flex; align-items: baseline; gap: 8px; height: 32px; font: 400 20px/32px var(--hal-serif); letter-spacing: -0.01em; color: var(--hal-ink); white-space: nowrap; }
814.hal-date-btn small { font: 400 14px/1 var(--hal-sans); color: var(--hal-ink-2); }
815.hal-pop { position: absolute; left: 10px; bottom: calc(100% + 14px); width: 360px; padding: 18px; border-radius: 20px; background: var(--hal-paper); box-shadow: 0 0 0 1px var(--hal-line), 0 30px 60px -20px rgba(30, 25, 21, 0.4); animation: hal-pop var(--fd-dur-3) var(--fd-ease-out); }
816.hal-book.is-docked .hal-pop { bottom: auto; top: calc(100% + 14px); }
817@keyframes hal-pop { from { opacity: 0; transform: translateY(8px) scale(0.98); } to { opacity: 1; transform: none; } }
818.hal-pop-title { font: 400 18px/1.2 var(--hal-serif); margin-bottom: 12px; color: var(--hal-ink); }
819.hal-pop-grid { display: grid; grid-template-columns: repeat(6, 1fr); gap: 6px; }
820.hal-pop-grid button { display: flex; flex-direction: column; align-items: center; gap: 2px; height: 52px; justify-content: center; border-radius: 14px; font: 500 16px/1 var(--hal-sans); color: var(--hal-ink); transition: background-color var(--fd-dur-1) var(--fd-ease-out); }
821.hal-pop-grid button small { font: 500 10px/1 var(--hal-sans); letter-spacing: 0.08em; text-transform: uppercase; color: var(--hal-ink-2); }
822.hal-pop-grid button:hover { background: rgba(30, 25, 21, 0.06); }
823.hal-pop-grid button.is-on { background: var(--hal-ink); color: var(--hal-paper); }
824.hal-pop-grid button.is-on small { color: rgba(251, 248, 243, 0.7); }
825.hal-step { display: flex; align-items: center; gap: 10px; height: 32px; }
826.hal-step button { width: 32px; height: 32px; border-radius: 50%; border: 1px solid var(--hal-line); font: 400 18px/1 var(--hal-sans); color: var(--hal-ink); text-align: center; transition: background-color var(--fd-dur-1) var(--fd-ease-out), transform var(--fd-dur-2) var(--fd-spring); }
827.hal-step button:hover { background: rgba(30, 25, 21, 0.06); }
828.hal-step button:active { transform: scale(0.9); }
829.hal-step output { min-width: 18px; text-align: center; font: 400 20px/1 var(--hal-serif); color: var(--hal-ink); font-variant-numeric: tabular-nums; }
830.hal-cta-short { display: none; }
831.hal-book-cta { height: 64px; padding: 0 28px; transition: height var(--fd-dur-3) var(--fd-ease-out), background-color var(--fd-dur-2) var(--fd-ease-out), transform var(--fd-dur-2) var(--fd-spring); }
832.hal-book.is-docked .hal-book-cta { height: 48px; }
833.hal-book-result { position: absolute; left: 50%; top: calc(100% + 12px); transform: translate(-50%, -6px); padding: 10px 18px; border-radius: 9999px; background: var(--hal-ink); color: var(--hal-paper); font: 500 13px/1.3 var(--hal-sans); white-space: nowrap; opacity: 0; pointer-events: none; transition: opacity var(--fd-dur-3) var(--fd-ease-out), transform var(--fd-dur-3) var(--fd-ease-out); }
834.hal-book-result.is-on { opacity: 1; transform: translate(-50%, 0); }
835.hal-book:not(.is-docked) .hal-book-result { top: auto; bottom: calc(100% + 12px); }
836
837/* REVEALS */
838.hal-js [data-hal-rv] { opacity: 0; transform: translate3d(0, 28px, 0); transition: opacity 1s var(--fd-ease-out), transform 1.1s var(--fd-ease-out); transition-delay: var(--d, 0ms); }
839.hal-js [data-hal-rv].is-in { opacity: 1; transform: none; }
840
841/* INTRO */
842.hal-intro { padding: 180px 0 160px; }
843.hal-intro-grid { display: grid; grid-template-columns: 1fr 1.05fr; gap: 96px; align-items: start; }
844.hal-intro-text { position: relative; padding-top: 40px; }
845.hal-intro-text .hal-h2 { margin: 22px 0 36px; }
846.hal-drop::first-letter { float: left; font: 400 88px/0.82 var(--hal-serif); color: var(--hal-terra); margin: 8px 12px 0 0; }
847.hal-intro-text .hal-body { margin-top: 22px; max-width: 520px; }
848.hal-figs { display: grid; grid-template-columns: repeat(3, 1fr); gap: 24px; margin-top: 56px; padding-top: 28px; border-top: 1px solid var(--hal-line); }
849.hal-figs dt { font: 500 12px/1.3 var(--hal-sans); letter-spacing: 0.12em; text-transform: uppercase; color: var(--hal-ink-2); }
850.hal-figs dd { margin-top: 8px; font: 400 44px/1 var(--hal-serif); letter-spacing: -0.025em; color: var(--hal-ink); }
851.hal-intro-media { position: relative; height: 820px; --p: 0; }
852.hal-clip { position: absolute; margin: 0; }
853.hal-clip-a { right: 0; top: 0; width: 78%; height: 620px; transform: translate3d(0, calc(var(--p) * -40px), 0); }
854.hal-clip-b { left: 0; bottom: 0; width: 46%; height: 380px; z-index: 2; transform: translate3d(0, calc(var(--p) * -110px), 0); }
855.hal-clip-in { width: 100%; height: calc(100% - 34px); overflow: hidden; border-radius: 4px; background: #D9C4A6; }
856.hal-clip-b .hal-clip-in { box-shadow: 0 0 0 10px var(--hal-sand); }
857.hal-clip figcaption { margin-top: 12px; font: italic 400 15px/1.3 var(--hal-serif); color: var(--hal-ink-2); }
858.hal-js .hal-clip[data-hal-rv] { opacity: 1; transform: translate3d(0, calc(var(--p) * -40px), 0); }
859.hal-js .hal-clip-b[data-hal-rv] { transform: translate3d(0, calc(var(--p) * -110px), 0); }
860.hal-js .hal-clip .hal-clip-in { clip-path: inset(100% 0 0 0); transition: clip-path 1.4s var(--fd-ease-out); }
861.hal-js .hal-clip .hal-scene { transform: scale(1.18); transition: transform 2s var(--fd-ease-out); }
862.hal-js .hal-clip.is-in .hal-clip-in { clip-path: inset(0 0 0 0); }
863.hal-js .hal-clip.is-in .hal-scene { transform: scale(1); }
864.hal-js .hal-clip-b .hal-clip-in { transition-delay: 220ms; }
865.hal-js .hal-clip figcaption { opacity: 0; transition: opacity 1s var(--fd-ease-out) 700ms; }
866.hal-js .hal-clip.is-in figcaption { opacity: 1; }
867
868/* HOUSES — pinned horizontal */
869.hal-houses { position: relative; height: calc(100vh + var(--dist, 0px)); height: calc(100svh + var(--dist, 0px)); background: var(--hal-paper); --p: 0; }
870.hal-houses-pin { position: sticky; top: 0; height: 100vh; height: 100svh; min-height: 640px; display: flex; flex-direction: column; justify-content: center; gap: 44px; overflow: hidden; padding-top: 150px; }
871.hal-houses-head { display: flex; align-items: flex-end; justify-content: space-between; gap: 40px; }
872.hal-houses-head .hal-h2 { margin-top: 16px; }
873.hal-houses-meter { display: flex; align-items: center; gap: 18px; padding-bottom: 10px; }
874.hal-houses-bar { position: relative; display: block; width: 180px; height: 1px; background: var(--hal-line); }
875.hal-houses-bar i { position: absolute; inset: 0; background: var(--hal-ink); transform: scaleX(calc(0.25 + var(--p) * 0.75)); transform-origin: left; }
876.hal-houses-cur { font-weight: 600; color: var(--hal-ink); }
877.hal-houses-count { font: 500 13px/1 var(--hal-sans); letter-spacing: 0.12em; color: var(--hal-ink-2); font-variant-numeric: tabular-nums; }
878.hal-houses-track { display: flex; gap: 32px; padding: 0 40px; width: max-content; will-change: transform; }
879.hal-house { flex: 0 0 auto; width: min(58vw, 860px); }
880.hal-house-link { display: grid; grid-template-columns: 1.35fr 1fr; gap: 32px; height: min(58vh, 560px); color: var(--hal-ink); }
881.hal-house-img { position: relative; overflow: hidden; border-radius: 4px; background: #D9C4A6; }
882.hal-house-par { position: absolute; inset: 0 -12%; transform: translate3d(calc(var(--p) * 14% - 7%), 0, 0); }
883.hal-house-link:hover .hal-house-par .hal-scene { transform: scale(1.04); }
884.hal-house-par .hal-scene { transition: transform 1.2s var(--fd-ease-out); }
885.hal-house-info { display: flex; flex-direction: column; justify-content: space-between; padding: 8px 0 4px; }
886.hal-house-n { font: italic 400 20px/1 var(--hal-serif); color: var(--hal-terra); }
887.hal-house-info .hal-h3 { font-size: 40px; letter-spacing: -0.025em; }
888.hal-house-place { margin-top: 10px; font: 500 13px/1.4 var(--hal-sans); letter-spacing: 0.08em; text-transform: uppercase; color: var(--hal-ink-2); }
889.hal-house-note { margin-top: 22px; font: 400 17px/1.65 var(--hal-sans); color: var(--hal-ink-2); max-width: 34ch; }
890.hal-house-meta { display: flex; justify-content: space-between; padding-top: 18px; border-top: 1px solid var(--hal-line); font: 400 20px/1 var(--hal-serif); color: var(--hal-ink); }
891
892/* ROOMS */
893.hal-rooms { padding: 180px 0 140px; }
894.hal-sec-head { max-width: 760px; margin-bottom: 64px; }
895.hal-sec-head .hal-h2 { margin-top: 18px; }
896.hal-sec-sub { margin-top: 22px; max-width: 560px; }
897.hal-room-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 28px; }
898.hal-room-link { display: block; color: var(--hal-ink); }
899.hal-room-img { position: relative; aspect-ratio: 4 / 5; overflow: hidden; border-radius: 4px; background: #D9C4A6; }
900.hal-room-img .hal-scene { transition: transform 1.1s var(--fd-ease-out); }
901.hal-room-img::after { content: ""; position: absolute; inset: 0; background: linear-gradient(180deg, rgba(30, 25, 21, 0) 60%, rgba(30, 25, 21, 0.18)); opacity: 0; transition: opacity var(--fd-dur-4) var(--fd-ease-out); }
902.hal-room-link:hover .hal-room-img .hal-scene, .hal-room-link:focus-visible .hal-room-img .hal-scene { transform: scale(1.07); }
903.hal-room-link:hover .hal-room-img::after { opacity: 1; }
904.hal-room-body { padding-top: 22px; }
905.hal-room-house { font: 500 12px/1.4 var(--hal-sans); letter-spacing: 0.14em; text-transform: uppercase; color: var(--hal-terra); }
906.hal-room-body .hal-h3 { margin-top: 10px; }
907.hal-room-feat { margin-top: 8px; font: 400 15px/1.5 var(--hal-sans); color: var(--hal-ink-2); }
908.hal-room-foot { display: flex; align-items: center; justify-content: space-between; margin-top: 18px; padding-top: 16px; border-top: 1px solid var(--hal-line); font: 400 15px/1 var(--hal-sans); color: var(--hal-ink-2); }
909.hal-room-foot strong { font: 400 20px/1 var(--hal-serif); color: var(--hal-ink); }
910.hal-room-cta { display: inline-flex; align-items: center; gap: 8px; font: 500 14px/1 var(--hal-sans); color: var(--hal-ink); }
911.hal-room-cta svg { width: 16px; height: 16px; transition: transform var(--fd-dur-3) var(--fd-ease-out); }
912.hal-room-link:hover .hal-room-cta svg { transform: translateX(4px); }
913
914/* RITUALS */
915.hal-rituals { padding: 140px 0; background: var(--hal-night); color: #F3ECE2; }
916.hal-rituals .hal-h2 { color: #F7F0E6; margin: 18px 0 44px; }
917.hal-rituals .hal-eyebrow { color: #E6A47F; }
918.hal-rituals-grid { display: grid; grid-template-columns: 0.9fr 1.1fr; gap: 96px; align-items: center; }
919.hal-rituals-frame { position: relative; aspect-ratio: 4 / 5; overflow: hidden; border-radius: 4px; background: #2A221D; }
920.hal-rituals-img { position: absolute; inset: 0; opacity: 0; transform: scale(1.06); transition: opacity var(--fd-dur-4) var(--fd-ease-in-out), transform 1.4s var(--fd-ease-out); }
921.hal-rituals-img.is-on { opacity: 1; transform: scale(1); }
922.hal-rituals-time { position: absolute; left: 24px; bottom: 20px; font: 400 64px/1 var(--hal-serif); letter-spacing: -0.04em; color: #FFF8EE; text-shadow: 0 2px 30px rgba(0, 0, 0, 0.35); font-variant-numeric: tabular-nums; }
923.hal-rituals-list { border-top: 1px solid rgba(243, 236, 226, 0.16); }
924.hal-ritual { display: grid; grid-template-columns: 72px 1fr; grid-template-rows: auto auto; column-gap: 20px; row-gap: 6px; width: 100%; padding: 26px 0; border-bottom: 1px solid rgba(243, 236, 226, 0.16); color: rgba(243, 236, 226, 0.6); text-align: left; transition: color var(--fd-dur-3) var(--fd-ease-out), padding var(--fd-dur-3) var(--fd-ease-out); }
925.hal-ritual-time { grid-row: span 2; font: 500 14px/2 var(--hal-sans); letter-spacing: 0.1em; font-variant-numeric: tabular-nums; }
926.hal-ritual-title { font: 400 30px/1.15 var(--hal-serif); letter-spacing: -0.015em; }
927.hal-ritual-where { font: 400 15px/1.4 var(--hal-sans); color: rgba(243, 236, 226, 0.6); }
928.hal-ritual.is-on, .hal-ritual:hover { color: #FFF8EE; padding-left: 12px; }
929.hal-ritual.is-on .hal-ritual-time { color: #E6A47F; }
930.hal-ritual:focus-visible { outline-color: #E6A47F; }
931
932/* QUOTES */
933.hal-quotes { padding: 160px 0; background: var(--hal-sand); }
934.hal-quotes-in { display: grid; grid-template-columns: minmax(0, 5fr) minmax(0, 6fr); gap: clamp(40px, 7vw, 110px); align-items: center; text-align: left; }
935.hal-quotes-fig { margin: 0; aspect-ratio: 1; overflow: hidden; border-radius: 4px; box-shadow: 0 30px 60px -30px rgba(60, 30, 10, 0.4); }
936.hal-quotes-img { display: block; width: 100%; height: 100%; max-width: none; object-fit: cover; }
937.hal-quotes-body { display: flex; flex-direction: column; align-items: flex-start; min-width: 0; }
938.hal-quote-stack { display: grid; width: 100%; max-width: 980px; margin-top: 36px; }
939.hal-quote { grid-area: 1 / 1; opacity: 0; transform: translateY(10px); filter: blur(4px); transition: opacity 900ms var(--fd-ease-in-out), transform 900ms var(--fd-ease-out), filter 900ms var(--fd-ease-out); pointer-events: none; }
940.hal-quote.is-on { opacity: 1; transform: none; filter: none; pointer-events: auto; }
941.hal-quote blockquote p { font: italic 400 clamp(24px, 2.6vw, 38px)/1.3 var(--hal-serif); letter-spacing: -0.015em; color: var(--hal-ink); text-wrap: balance; }
942.hal-quote figcaption { display: flex; flex-direction: column; gap: 6px; margin-top: 32px; font: 400 15px/1.4 var(--hal-sans); color: var(--hal-ink-2); }
943.hal-quote figcaption strong { font: 500 15px/1.4 var(--hal-sans); color: var(--hal-ink); }
944.hal-quote-dots { display: flex; gap: 10px; margin-top: 44px; }
945.hal-quote-dots button { position: relative; width: 44px; height: 44px; }
946.hal-quote-dots button::before { content: ""; position: absolute; left: 4px; right: 4px; top: 21px; height: 2px; background: var(--hal-line); }
947.hal-quote-dots i { position: absolute; left: 4px; right: 4px; top: 21px; height: 2px; background: var(--hal-ink); transform: scaleX(0); transform-origin: left; }
948.hal-quote-dots button.is-on i { animation: hal-fill 6.5s linear forwards; }
949@keyframes hal-fill { to { transform: scaleX(1); } }
950
951/* MAP */
952.hal-map { padding: 160px 0; background: var(--hal-paper); }
953.hal-map-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 28px; }
954.hal-map-card { margin: 0; }
955.hal-map-svg { width: 100%; height: auto; border-radius: 4px; }
956.hal-map-card figcaption { display: grid; grid-template-columns: 160px 1fr; gap: 20px; margin-top: 22px; font: 400 15px/1.6 var(--hal-sans); color: var(--hal-ink-2); }
957.hal-map-card figcaption strong { font: 400 24px/1.2 var(--hal-serif); color: var(--hal-ink); }
958.hal-map-t { font: 500 13px/1 var(--hal-sans); fill: #3E342D; letter-spacing: 0.02em; }
959.hal-map-t-sm { font-size: 11px; letter-spacing: 0.14em; text-transform: uppercase; fill: #7A6D62; }
960.hal-map-t-sea { font: italic 400 15px/1 var(--hal-serif); fill: #50727C; }
961.hal-js .hal-draw { stroke-dasharray: 1200; stroke-dashoffset: 1200; transition: stroke-dashoffset 2.4s var(--fd-ease-out) 200ms; }
962.hal-js .hal-draw-route { stroke-dasharray: 4 6; stroke-dashoffset: 0; opacity: 0; transition: opacity 1s var(--fd-ease-out) 1.2s; }
963.hal-js .is-in .hal-draw { stroke-dashoffset: 0; }
964.hal-js .is-in .hal-draw-route { opacity: 1; animation: hal-march 1.2s linear infinite; }
965@keyframes hal-march { to { stroke-dashoffset: -20; } }
966.hal-pin { transform-box: fill-box; }
967.hal-js .hal-pin path, .hal-js .hal-pin circle { opacity: 0; transform: translateY(-36px); transition: opacity 400ms var(--fd-ease-out) var(--d), transform 700ms var(--fd-spring) var(--d); }
968.hal-js .is-in .hal-pin path, .hal-js .is-in .hal-pin circle { opacity: 1; transform: none; }
969
970/* FOOTER */
971.hal-foot { padding: 120px 0 40px; background: var(--hal-night); color: #E9E0D4; overflow: hidden; }
972.hal-news { display: grid; grid-template-columns: 1.1fr 1fr; gap: 80px; align-items: end; padding-bottom: 96px; border-bottom: 1px solid rgba(243, 236, 226, 0.14); }
973.hal-eyebrow-light { color: #E6A47F; }
974.hal-news-title { color: #F7F0E6; margin-top: 18px; }
975.hal-news-sub { margin-top: 18px; max-width: 470px; font: 400 17px/1.65 var(--hal-sans); color: rgba(243, 236, 226, 0.72); }
976.hal-news-label { display: block; margin-bottom: 12px; font: 500 12px/1 var(--hal-sans); letter-spacing: 0.14em; text-transform: uppercase; color: rgba(243, 236, 226, 0.72); }
977.hal-news-row { display: flex; gap: 8px; padding: 6px; border-radius: 9999px; background: rgba(243, 236, 226, 0.08); box-shadow: inset 0 0 0 1px rgba(243, 236, 226, 0.16); transition: box-shadow var(--fd-dur-2) var(--fd-ease-out); }
978.hal-news-row:focus-within { box-shadow: inset 0 0 0 1px rgba(230, 164, 127, 0.8); }
979.hal-news-row input { flex: 1; height: 52px; padding: 0 20px; border-radius: 9999px; background: transparent; font: 400 17px/1 var(--hal-sans); color: #FBF3E8; outline: none; }
980.hal-news-row input::placeholder { color: rgba(243, 236, 226, 0.5); }
981.hal-news-row.is-done { box-shadow: inset 0 0 0 1px rgba(230, 164, 127, 0.8); }
982.hal-news-row.is-done .hal-btn { background: #E6A47F; color: var(--hal-night); }
983.hal-news-note { margin-top: 14px; font: 400 14px/1.5 var(--hal-sans); color: rgba(243, 236, 226, 0.62); }
984.hal-foot-cols { display: grid; grid-template-columns: repeat(4, 1fr); gap: 40px; padding: 72px 0; }
985.hal-foot-h { font: 500 12px/1 var(--hal-sans); letter-spacing: 0.16em; text-transform: uppercase; color: rgba(243, 236, 226, 0.6); margin-bottom: 20px; }
986.hal-foot-cols li { margin-bottom: 12px; font: 400 15px/1.4 var(--hal-sans); color: #F3ECE2; }
987.hal-foot-cols a { color: #F3ECE2; transition: color var(--fd-dur-1) var(--fd-ease-out); }
988.hal-foot-cols a:hover { color: #E6A47F; }
989.hal-foot-mark { font: 400 clamp(88px, 21vw, 340px)/0.8 var(--hal-serif); letter-spacing: -0.05em; color: transparent; background: linear-gradient(180deg, rgba(243, 236, 226, 0.22), rgba(243, 236, 226, 0.02)); -webkit-background-clip: text; background-clip: text; text-align: center; white-space: nowrap; margin: 0 -40px; user-select: none; }
990.hal-foot-legal { display: flex; justify-content: space-between; gap: 24px; flex-wrap: wrap; padding-top: 28px; border-top: 1px solid rgba(243, 236, 226, 0.14); font: 400 13px/1.5 var(--hal-sans); color: rgba(243, 236, 226, 0.62); }
991.hal-foot-legal span:last-child { display: flex; gap: 22px; }
992.hal-foot-legal a { color: rgba(243, 236, 226, 0.78); }
993.hal-foot-legal a:hover { color: #FBF3E8; }
994
995/* RESPONSIVE */
996@media (max-width: 1080px) {
997 .hal-nav-links { gap: 22px; }
998 .hal-intro-grid { gap: 56px; }
999 .hal-book-card { grid-template-columns: auto 1.2fr 0.9fr 0.9fr auto; padding-left: 16px; }
1000 .hal-field { padding: 0 14px; }
1001 .hal-book-cta { padding: 0 20px; }
1002}
1003@media (max-width: 900px) {
1004 .hal-wrap { padding: 0 24px; }
1005 .hal-nav-bar { padding: 0 20px; }
1006 .hal-nav-links, .hal-nav-lang { display: none; }
1007 .hal-burger { display: block; }
1008 .hal-hero-copy { padding: var(--hal-nav-h) 24px 150px; }
1009 .hal-hero-scroll { display: none; }
1010 .hal-intro { padding: 120px 0 100px; }
1011 .hal-intro-grid, .hal-rituals-grid, .hal-news { grid-template-columns: 1fr; gap: 56px; }
1012 .hal-intro-media { height: 640px; }
1013 .hal-houses { height: auto; }
1014 .hal-houses-pin { position: relative; height: auto; min-height: 0; padding: 110px 0 90px; }
1015 .hal-houses-meter { display: none; }
1016 .hal-houses-track { width: auto; overflow-x: auto; scroll-snap-type: x mandatory; padding-bottom: 12px; scrollbar-width: none; transform: none !important; }
1017 .hal-houses-track::-webkit-scrollbar { display: none; }
1018 .hal-house { width: 82vw; scroll-snap-align: center; }
1019 .hal-house-link { grid-template-columns: 1fr; height: auto; gap: 20px; }
1020 .hal-house-img { aspect-ratio: 4 / 3; }
1021 .hal-house-par { inset: 0; transform: none; }
1022 .hal-house-note { margin-top: 14px; }
1023 .hal-house-meta { margin-top: 18px; }
1024 .hal-rooms { padding: 110px 0; }
1025 .hal-room-grid { grid-template-columns: 1fr; gap: 48px; }
1026 .hal-rituals { padding: 100px 0; }
1027 .hal-rituals-frame { aspect-ratio: 4 / 3; }
1028 .hal-quotes, .hal-map { padding: 110px 0; }
1029 .hal-quotes-in { grid-template-columns: 1fr; }
1030 .hal-quotes-fig { max-width: 420px; }
1031 .hal-map-grid { grid-template-columns: 1fr; }
1032 .hal-foot-cols { grid-template-columns: 1fr 1fr; }
1033}
1034@media (max-width: 760px) {
1035 .hal-book { padding: 0 12px; height: auto; margin-top: -196px; bottom: 12px; top: calc(var(--hal-nav-s) + 6px); }
1036 .hal-book-card { grid-template-columns: 1fr 1fr; grid-template-rows: auto auto auto; height: auto; padding: 14px; gap: 10px 0; border-radius: 22px; }
1037 .hal-field-dest { grid-column: 1 / -1; padding: 0; }
1038 .hal-field-dest .hal-label { display: none; }
1039 .hal-field-date { grid-column: 1 / -1; padding: 0 4px; border-left: 0; }
1040 .hal-field { padding: 0 4px; height: auto; gap: 4px; }
1041 .hal-field:nth-child(4) { padding-left: 16px; }
1042 .hal-field:nth-child(3) { border-left: 0; }
1043 .hal-book-cta { grid-column: 1 / -1; height: 52px; }
1044 .hal-label { font-size: 10px; }
1045 .hal-book.is-docked .hal-book-card { height: auto; grid-template-columns: 1fr auto; grid-template-rows: auto; gap: 8px; padding: 6px; border-radius: 9999px; }
1046 .hal-book.is-docked .hal-field:nth-child(3), .hal-book.is-docked .hal-field:nth-child(4), .hal-book.is-docked .hal-field-date { display: none; }
1047 .hal-book.is-docked .hal-field-dest { grid-column: 1; }
1048 .hal-book.is-docked .hal-book-cta { grid-column: 2; height: 44px; padding: 0 18px; }
1049 .hal-book.is-docked .hal-cta-full { display: none; }
1050 .hal-book.is-docked .hal-cta-short { display: inline; }
1051 .hal-pop { width: calc(100vw - 48px); left: -4px; }
1052 .hal-pop-grid { grid-template-columns: repeat(4, 1fr); }
1053 .hal-book-result { white-space: normal; width: max-content; max-width: calc(100vw - 48px); text-align: center; }
1054 .hal-hero-copy { padding: calc(var(--hal-nav-h) + 36px) 24px 290px; justify-content: flex-start; }
1055 /* Portrait crop: frame the sea + the cliff edge (viewBox x≈700–1125) instead of the centre. */
1056 .hal-hero-img { object-position: 60% 40%; }
1057 .hal-hero-scene::after { background: linear-gradient(180deg, rgba(246, 228, 204, 0.9) 0%, rgba(246, 228, 204, 0.7) 45%, rgba(246, 228, 204, 0) 75%); }
1058 .hal-hero.is-dusk .hal-hero-scene::after { background: linear-gradient(180deg, rgba(26, 16, 36, 0.75) 0%, rgba(26, 16, 36, 0.5) 45%, rgba(26, 16, 36, 0) 75%); }
1059 .hal-hero-title { font-size: clamp(48px, 14vw, 64px); }
1060 .hal-hero-sub { font-size: 15px; margin-top: 18px; }
1061 .hal-hero-eyebrow { margin-bottom: 18px; font-size: 11px; gap: 8px; }
1062}
1063@media (max-width: 560px) {
1064 .hal-h3 { font-size: 24px; }
1065 .hal-house-info .hal-h3 { font-size: 32px; }
1066 .hal-intro-media { height: 520px; }
1067 .hal-clip-a { width: 86%; height: 400px; }
1068 .hal-clip-b { width: 54%; height: 250px; }
1069 .hal-figs dd { font-size: 32px; }
1070 .hal-lede { font-size: 21px; }
1071 .hal-drop::first-letter { font-size: 64px; }
1072 .hal-ritual-title { font-size: 24px; }
1073 .hal-ritual { grid-template-columns: 56px 1fr; }
1074 .hal-rituals-time { font-size: 44px; }
1075 .hal-map-card figcaption { grid-template-columns: 1fr; gap: 6px; }
1076 .hal-foot-legal { flex-direction: column; }
1077 .hal-news-row { flex-direction: column; border-radius: 22px; }
1078 .hal-news-row .hal-btn { width: 100%; }
1079}
1080
1081@media (prefers-reduced-motion: reduce) {
1082 .hal *, .hal *::before, .hal *::after { animation-duration: 1ms !important; animation-iteration-count: 1 !important; transition-duration: 120ms !important; transition-delay: 0ms !important; }
1083 .hal-hero { height: 100vh; height: 100svh; }
1084 .hal-hero-img, .hal-hero-copy, .hal-house-par, .hal-clip-a, .hal-clip-b, .hal-js .hal-clip[data-hal-rv], .hal-js .hal-clip-b[data-hal-rv] { transform: none !important; }
1085 .hal-houses { height: auto; }
1086 .hal-houses-pin { position: relative; height: auto; min-height: 0; padding: 110px 0 90px; }
1087 .hal-houses-track { width: auto; overflow-x: auto; scroll-snap-type: x mandatory; transform: none !important; }
1088 .hal-house { scroll-snap-align: start; }
1089 .hal-houses-meter, .hal-hero-scroll { display: none; }
1090 .hal-js [data-hal-rv] { transform: none; }
1091 .hal-js .hal-clip .hal-clip-in { clip-path: none; }
1092 .hal-js .hal-clip .hal-scene { transform: none; }
1093}
1094@media (prefers-contrast: more) {
1095 .hal-book-card, .hal-nav.is-small .hal-nav-bar { background: var(--hal-paper); -webkit-backdrop-filter: none; backdrop-filter: none; box-shadow: 0 0 0 1px var(--hal-ink); }
1096}
1097@media (prefers-reduced-transparency: reduce) {
1098 .hal-book-card, .hal-nav.is-small .hal-nav-bar { background: var(--hal-paper); -webkit-backdrop-filter: none; backdrop-filter: none; }
1099}
1100`;