Otherside Travel Journal & Destination Guides AI website prompt
An immersive travel journal featuring handpicked destinations, inspiring field stories, memorable moments, and a personalized trip planner for discovering your next adventure.
Now put it in WordPress
You have a site. Now make it yours.
An AI build hands you a front end and stops there. No CMS, no client login, no real ownership: you cannot hand it over, and you cannot change it without going back and re-prompting. UiChemy turns that build into native, editable WordPress you own, in the builder you already use.
Bolt to WordPressClaude to WordPressCodex to WordPressCursor to WordPressLovable to WordPressv0 to WordPressAI to WordPress
Get Started
More prompts
Showing 6 prompts
Frequently asked questions
What does the Otherside Travel Journal & Destination Guides prompt build?
An immersive travel journal featuring handpicked destinations, inspiring field stories, memorable moments, and a personalized trip planner for discovering your next adventure.
Which AI tools does the Otherside Travel Journal & Destination Guides prompt work with?
It's written for Claude. Paste the whole prompt into Claude and it builds the complete site for you, then bring the result into WordPress with UiChemy. This prompt also has a WordPress version: with the UiChemy MCP connected, Claude builds the page straight into WordPress.
How do I turn the result into a WordPress site?
After the AI builds your site, UiChemy Composer rebuilds it as native, editable WordPress in Elementor or Gutenberg (Bricks coming soon) — you own it and edit it in your builder. See AI to WordPress.
Is the Otherside Travel Journal & Destination Guides prompt free?
Yes — it's free to copy, no account needed.
Can I edit the website after it's built?
Yes. Once it's in WordPress with UiChemy, every section is native and editable — no re-prompting, no lock-in.
Copy prompt
Copy code
Coming soon
Copy SKILL.md
Coming soon
Otherside Travel Journal & Destination Guides · Section prompt
# MASTER PROMPT — Otherside — Travel Journal & Destination Guides
### Plain HTML, CSS and JavaScript. No WordPress, no page builder, no framework, no build step.
## ▶ HOW TO USE THIS PROMPT
**Paste this whole document into Claude. Nothing to attach.** It carries the complete source of the site plus the address of every image, so Claude can assemble a folder that owns its own assets and hand it back as a zip.
**INSTRUCTION TO CLAUDE — BUILD IT, DO NOT ASK.** Every decision is already made in this document. Do not stop to ask the user questions, do not offer options, and do not ask for confirmation before starting. Write the files, zip them, hand over the archive, then report what you built.
1. Create a folder named `otherside` and write out every file in the appendix below **exactly as written** — do not reformat, rename, shorten, reorder or "improve" anything.
2. **Download the 30 images into `assets/`.** The code already points at `assets/…` — nothing in it references a remote host. Run this from inside the folder; it reads the ASSET MANIFEST at the end of this document:
```bash
mkdir -p assets
curl -fsSL 'https://demo.uichemy.com/wp-content/uploads/2026/09/a1.jpg' -o 'assets/a1.jpg'
curl -fsSL 'https://demo.uichemy.com/wp-content/uploads/2026/09/a2.jpg' -o 'assets/a2.jpg'
curl -fsSL 'https://demo.uichemy.com/wp-content/uploads/2026/09/a3.jpg' -o 'assets/a3.jpg'
curl -fsSL 'https://demo.uichemy.com/wp-content/uploads/2026/09/d-amalfi.jpg' -o 'assets/d-amalfi.jpg'
curl -fsSL 'https://demo.uichemy.com/wp-content/uploads/2026/09/d-bali.jpg' -o 'assets/d-bali.jpg'
curl -fsSL 'https://demo.uichemy.com/wp-content/uploads/2026/09/d-dolomites.jpg' -o 'assets/d-dolomites.jpg'
curl -fsSL 'https://demo.uichemy.com/wp-content/uploads/2026/09/d-iceland.jpg' -o 'assets/d-iceland.jpg'
curl -fsSL 'https://demo.uichemy.com/wp-content/uploads/2026/09/d-kyoto.jpg' -o 'assets/d-kyoto.jpg'
curl -fsSL 'https://demo.uichemy.com/wp-content/uploads/2026/09/d-morocco.jpg' -o 'assets/d-morocco.jpg'
curl -fsSL 'https://demo.uichemy.com/wp-content/uploads/2026/09/d-norway.jpg' -o 'assets/d-norway.jpg'
curl -fsSL 'https://demo.uichemy.com/wp-content/uploads/2026/09/d-patagonia.jpg' -o 'assets/d-patagonia.jpg'
curl -fsSL 'https://demo.uichemy.com/wp-content/uploads/2026/09/m1.jpg' -o 'assets/m1.jpg'
curl -fsSL 'https://demo.uichemy.com/wp-content/uploads/2026/09/m2.jpg' -o 'assets/m2.jpg'
curl -fsSL 'https://demo.uichemy.com/wp-content/uploads/2026/09/m3.jpg' -o 'assets/m3.jpg'
curl -fsSL 'https://demo.uichemy.com/wp-content/uploads/2026/09/m4.jpg' -o 'assets/m4.jpg'
curl -fsSL 'https://demo.uichemy.com/wp-content/uploads/2026/09/m5.jpg' -o 'assets/m5.jpg'
curl -fsSL 'https://demo.uichemy.com/wp-content/uploads/2026/09/m6.jpg' -o 'assets/m6.jpg'
curl -fsSL 'https://demo.uichemy.com/wp-content/uploads/2026/09/plane.jpg' -o 'assets/plane.jpg'
curl -fsSL 'https://demo.uichemy.com/wp-content/uploads/2026/09/sea.jpg' -o 'assets/sea.jpg'
curl -fsSL 'https://demo.uichemy.com/wp-content/uploads/2026/09/story.jpg' -o 'assets/story.jpg'
curl -fsSL 'https://demo.uichemy.com/wp-content/uploads/2026/09/through1.jpg' -o 'assets/through1.jpg'
curl -fsSL 'https://demo.uichemy.com/wp-content/uploads/2026/09/through2.jpg' -o 'assets/through2.jpg'
curl -fsSL 'https://demo.uichemy.com/wp-content/uploads/2026/09/w-aurora.jpg' -o 'assets/w-aurora.jpg'
curl -fsSL 'https://demo.uichemy.com/wp-content/uploads/2026/09/w-glacier.jpg' -o 'assets/w-glacier.jpg'
curl -fsSL 'https://demo.uichemy.com/wp-content/uploads/2026/09/w-machu.jpg' -o 'assets/w-machu.jpg'
curl -fsSL 'https://demo.uichemy.com/wp-content/uploads/2026/09/w-maldives.jpg' -o 'assets/w-maldives.jpg'
curl -fsSL 'https://demo.uichemy.com/wp-content/uploads/2026/09/w-namibia.jpg' -o 'assets/w-namibia.jpg'
curl -fsSL 'https://demo.uichemy.com/wp-content/uploads/2026/09/w-santorini.jpg' -o 'assets/w-santorini.jpg'
curl -fsSL 'https://demo.uichemy.com/wp-content/uploads/2026/09/w-swiss.jpg' -o 'assets/w-swiss.jpg'
curl -fsSL 'https://demo.uichemy.com/wp-content/uploads/2026/09/w-zhangjiajie.jpg' -o 'assets/w-zhangjiajie.jpg'
```
Then check every file arrived and none is a zero-byte or HTML error page:
```bash
ls assets | wc -l # expect 30 files
find assets -size -1k # expect no output
```
If any download fails, retry it — hosts occasionally drop a request. Do **not** fall back to hotlinking the remote URL: the whole point is a folder that works offline.
3. **Zip that folder and give the user the archive.** Its contents:
```
otherside/
index.html
sections/01-hero.html
sections/02-destinations.html
sections/03-planner.html
sections/04-story.html
sections/05-moments.html
sections/06-footer.html
template.html
assets/
a1.jpg
a2.jpg
a3.jpg
d-amalfi.jpg
… 26 more
```
```bash
zip -r otherside.zip otherside -x '*.DS_Store'
```
Then hand over `otherside.zip` as a file the user can download, not as text in the chat.
4. Before handing it over, serve the folder and confirm it runs:
```bash
cd otherside && python3 -m http.server 8000 # then open http://localhost:8000
```
Check the page renders, the images load from `assets/` with the network throttled or offline, and scrolling drives the animations.
The finished folder needs no network for its images — they are in `assets/`; only the motion libraries load from a CDN.
Do not rebuild the design from the description below. The description is there so a person can understand and edit the code. The appendix is the deliverable.
---
## ▶ WHAT YOU GET
Otherside is an independent travel journal. Every door leads somewhere: handpicked destinations, slow routes and field notes from the far side of the map.
**8 code files plus an `assets/` folder holding 30 images, delivered as `otherside.zip`.**
| File | What it is |
|---|---|
| `index.html` | a page, 108 KB |
| `sections/01-hero.html` | a page, 28 KB |
| `sections/02-destinations.html` | a page, 17 KB |
| `sections/03-planner.html` | a page, 20 KB |
| `sections/04-story.html` | a page, 13 KB |
| `sections/05-moments.html` | a page, 12 KB |
| `sections/06-footer.html` | a page, 10 KB |
| `template.html` | a page, 5 KB |
| `assets/` | 30 images, downloaded at build time |
**Libraries, from a CDN, already referenced in the HTML:** `gsap.min.js`, `ScrollTrigger.min.js`, `lenis.min.js`. No npm install, no bundler, no framework.
**Sections (5):** `#top` · `#destinations` · `#planner` · `#journal` · `#moments`.
---
## ▶ WHERE THE MEDIA COMES FROM
All 30 files (30 images) live in `assets/` inside the folder, and every reference in the markup, the CSS and the script is a relative `assets/…` path. Once the folder is assembled it owns its own media: no CDN, no media library, no external image host. Open it on a laptop with the wifi off and it still renders.
They are fetched **once, at build time**, from the WordPress media library on `demo.uichemy.com` — the same `otherside__` files the WordPress build of this page uses. That is only how they travel into the folder; nothing in the delivered code points back at that host. Replacing an image means dropping a new file into `assets/` under the same name.
The manifest at the end of this document lists every source URL against the filename it is saved as, so the download step is reproducible and checkable.
---
## ▶ WHAT THIS PROMPT DELIBERATELY DOES NOT CONTAIN
- No WordPress, no page builder, and none of the wrapper classes or compatibility CSS a builder needs. The WordPress build is a separate prompt.
- No framework, no TypeScript, no bundler, no `package.json`.
- No backend. Any form here validates and replies in place; it posts nowhere.
- No CMS. The content lives in the files.
- No external image host. The images ship inside the zip; nothing in the code calls out to a media library at runtime.
---
## ASSET MANIFEST — download these into `assets/`
Source is the media library on `demo.uichemy.com`. Save each one under the name in the second column; the code already refers to it by that name.
| Download from | Save as |
|---|---|
| `https://demo.uichemy.com/wp-content/uploads/2026/09/a1.jpg` | `assets/a1.jpg` |
| `https://demo.uichemy.com/wp-content/uploads/2026/09/a2.jpg` | `assets/a2.jpg` |
| `https://demo.uichemy.com/wp-content/uploads/2026/09/a3.jpg` | `assets/a3.jpg` |
| `https://demo.uichemy.com/wp-content/uploads/2026/09/d-amalfi.jpg` | `assets/d-amalfi.jpg` |
| `https://demo.uichemy.com/wp-content/uploads/2026/09/d-bali.jpg` | `assets/d-bali.jpg` |
| `https://demo.uichemy.com/wp-content/uploads/2026/09/d-dolomites.jpg` | `assets/d-dolomites.jpg` |
| `https://demo.uichemy.com/wp-content/uploads/2026/09/d-iceland.jpg` | `assets/d-iceland.jpg` |
| `https://demo.uichemy.com/wp-content/uploads/2026/09/d-kyoto.jpg` | `assets/d-kyoto.jpg` |
| `https://demo.uichemy.com/wp-content/uploads/2026/09/d-morocco.jpg` | `assets/d-morocco.jpg` |
| `https://demo.uichemy.com/wp-content/uploads/2026/09/d-norway.jpg` | `assets/d-norway.jpg` |
| `https://demo.uichemy.com/wp-content/uploads/2026/09/d-patagonia.jpg` | `assets/d-patagonia.jpg` |
| `https://demo.uichemy.com/wp-content/uploads/2026/09/m1.jpg` | `assets/m1.jpg` |
| `https://demo.uichemy.com/wp-content/uploads/2026/09/m2.jpg` | `assets/m2.jpg` |
| `https://demo.uichemy.com/wp-content/uploads/2026/09/m3.jpg` | `assets/m3.jpg` |
| `https://demo.uichemy.com/wp-content/uploads/2026/09/m4.jpg` | `assets/m4.jpg` |
| `https://demo.uichemy.com/wp-content/uploads/2026/09/m5.jpg` | `assets/m5.jpg` |
| `https://demo.uichemy.com/wp-content/uploads/2026/09/m6.jpg` | `assets/m6.jpg` |
| `https://demo.uichemy.com/wp-content/uploads/2026/09/plane.jpg` | `assets/plane.jpg` |
| `https://demo.uichemy.com/wp-content/uploads/2026/09/sea.jpg` | `assets/sea.jpg` |
| `https://demo.uichemy.com/wp-content/uploads/2026/09/story.jpg` | `assets/story.jpg` |
| `https://demo.uichemy.com/wp-content/uploads/2026/09/through1.jpg` | `assets/through1.jpg` |
| `https://demo.uichemy.com/wp-content/uploads/2026/09/through2.jpg` | `assets/through2.jpg` |
| `https://demo.uichemy.com/wp-content/uploads/2026/09/w-aurora.jpg` | `assets/w-aurora.jpg` |
| `https://demo.uichemy.com/wp-content/uploads/2026/09/w-glacier.jpg` | `assets/w-glacier.jpg` |
| `https://demo.uichemy.com/wp-content/uploads/2026/09/w-machu.jpg` | `assets/w-machu.jpg` |
| `https://demo.uichemy.com/wp-content/uploads/2026/09/w-maldives.jpg` | `assets/w-maldives.jpg` |
| `https://demo.uichemy.com/wp-content/uploads/2026/09/w-namibia.jpg` | `assets/w-namibia.jpg` |
| `https://demo.uichemy.com/wp-content/uploads/2026/09/w-santorini.jpg` | `assets/w-santorini.jpg` |
| `https://demo.uichemy.com/wp-content/uploads/2026/09/w-swiss.jpg` | `assets/w-swiss.jpg` |
| `https://demo.uichemy.com/wp-content/uploads/2026/09/w-zhangjiajie.jpg` | `assets/w-zhangjiajie.jpg` |
---
# APPENDIX · VERBATIM SOURCE
The complete source of every file. **Write them out exactly** — do not change a value, a comment, a class name, or the order of a declaration. Recreate this file layout:
```
index.html
sections/01-hero.html
sections/02-destinations.html
sections/03-planner.html
sections/04-story.html
sections/05-moments.html
sections/06-footer.html
template.html
```
## `index.html`
```html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="data:,">
<title>Otherside — Travel Journal & Destination Guides</title>
<meta name="description" content="Otherside is an independent travel journal. Every door leads somewhere: handpicked destinations, slow routes and field notes from the far side of the map.">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Bricolage+Grotesque:opsz,[email protected],300;12..96,400;12..96,500;12..96,700&family=Manrope:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.5/gsap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.5/ScrollTrigger.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/lenis.min.js"></script>
<style>
/* ---------- OTHERSIDE locked palette ---------- */
:root{
--bg:#F3F2F6; /* page: cool mist (never cream) */
--surface:#FFFFFF; /* cards, panels */
--ink:#1E1A24; /* text + hairlines only — never a background */
--mute:#6A6474; /* secondary text */
--line:#D9D6DE; /* hairlines */
--plum:#6B2E5F; /* primary: buttons, heading accents (white text ok) */
--plum-deep:#55224B; /* primary hover */
--apricot:#FF9F43; /* accent: tags, dots, highlights (ink text only) */
--apricot-deep:#F08A24;/* accent hover */
--lilac:#E7DEEE; /* soft primary tint: panels, footer */
--display:'Bricolage Grotesque', system-ui, sans-serif;
--body:'Manrope', system-ui, sans-serif;
--mono:'JetBrains Mono', ui-monospace, monospace;
--ease:cubic-bezier(.22,.8,.16,1);
--gut:clamp(16px,3vw,40px);
--radius:18px;
}
*,*::before,*::after{box-sizing:border-box}
html.lenis,html.lenis body{height:auto}
.lenis.lenis-smooth{scroll-behavior:auto !important}
.lenis.lenis-stopped{overflow:hidden}
body{margin:0;background:var(--bg);color:var(--ink);font-family:var(--body);font-size:16px;line-height:1.55;overflow-x:hidden;-webkit-font-smoothing:antialiased}
img{display:block;max-width:100%}
a{color:inherit}
button,input,select{font:inherit;color:inherit}
button{background:none;border:0;cursor:pointer}
:focus-visible{outline:2px solid var(--apricot);outline-offset:3px}
h1,h2,h3{font-family:var(--display);font-weight:400;letter-spacing:-.03em;line-height:.95;margin:0}
em.it{font-style:normal;color:var(--plum)}
/* ---------- shared atoms ---------- */
.wrap{max-width:1440px;margin-inline:auto;padding-inline:var(--gut)}
.eyebrow{font-family:var(--mono);font-size:12px;letter-spacing:.08em;text-transform:uppercase;color:var(--mute)}
.dot{display:inline-block;width:8px;height:8px;border-radius:50%;background:var(--apricot);vertical-align:middle;margin-right:.5em}
.btn{display:inline-flex;align-items:center;gap:.6em;padding:14px 22px;border-radius:999px;background:var(--plum);color:#fff;
font-weight:600;font-size:14px;text-decoration:none;transition:background .25s var(--ease),transform .25s var(--ease)}
.btn:hover{background:var(--plum-deep);transform:translateY(-2px)}
.btn--light{background:var(--surface);color:var(--ink)}
.btn--light:hover{background:var(--apricot);color:var(--ink)}
.btn--line{background:transparent;color:var(--ink);box-shadow:inset 0 0 0 1px var(--ink)}
.btn--line:hover{background:var(--plum);color:#fff;box-shadow:none}
.btn .arr{transition:transform .25s var(--ease)}
.btn:hover .arr{transform:translateX(3px)}
.tag{display:inline-block;padding:4px 10px;border-radius:999px;background:var(--apricot);color:var(--ink);font-family:var(--mono);font-size:11px;text-transform:uppercase;letter-spacing:.06em}
/* split-text words: JS wraps words in .w > span */
.w{display:inline-block;overflow:hidden;vertical-align:top;padding-bottom:.08em;margin-bottom:-.08em}
.w>span{display:inline-block;will-change:transform}
</style>
</head>
<body>
<script>
/* Shared runtime: window.OS
OS.lenis smooth scroll instance (null under reduced motion), synced to ScrollTrigger
OS.split(el) wraps each word of el in <span class="w"><span>word</span></span>, returns the inner spans
OS.reveal(el) split + words rise in when el enters the viewport (once)
OS.reduced true when reduced motion is requested */
window.OS=(function(){
gsap.registerPlugin(ScrollTrigger);
const reduced=matchMedia('(prefers-reduced-motion: reduce) and (update: none)').matches;
let lenis=null;
if(!reduced){
lenis=new Lenis({duration:1.15,easing:t=>Math.min(1,1.001-Math.pow(2,-10*t))});
lenis.on('scroll',ScrollTrigger.update);
gsap.ticker.add(t=>lenis.raf(t*1000));
gsap.ticker.lagSmoothing(0);
}
function split(el){
const walk=n=>{[...n.childNodes].forEach(c=>{
if(c.nodeType===3){const f=document.createDocumentFragment();
c.textContent.split(/(\s+)/).forEach(t=>{if(!t)return;if(/^\s+$/.test(t)){f.appendChild(document.createTextNode(t));return}
const o=document.createElement('span');o.className='w';const i=document.createElement('span');i.textContent=t;o.appendChild(i);f.appendChild(o)});
c.replaceWith(f)}
else if(c.nodeType===1&&c.tagName!=='BR'&&!c.classList.contains('w'))walk(c)})};
walk(el);return [...el.querySelectorAll('.w>span')];
}
function reveal(el,opts={}){
const s=split(el);if(reduced)return s;
gsap.from(s,{yPercent:110,duration:1,ease:'power4.out',stagger:.04,...opts,scrollTrigger:{trigger:el,start:'top 85%',once:true}});
return s;
}
return {lenis,split,reveal,reduced};
})();
</script>
<header class="oh-nav" id="oh-nav">
<div class="oh-nav__bar">
<a class="oh-logo" href="#top" aria-label="Otherside, back to top">
<svg class="oh-logo__mark" viewBox="0 0 18 26" aria-hidden="true">
<rect x="1.5" y="1.5" width="15" height="23" rx="7.5" fill="none" stroke="currentColor" stroke-width="1.8"/>
<rect x="5" y="5.5" width="8" height="15" rx="4" class="oh-logo__glass"/>
</svg>
<span>Otherside</span>
</a>
<nav class="oh-links" aria-label="Primary">
<a href="#destinations">Destinations</a>
<a href="#planner">Planner</a>
<a href="#journal">Journal</a>
<a href="#moments">Moments</a>
</nav>
<a class="oh-cta" href="#planner">Plan a trip</a>
<button class="oh-burger" type="button" aria-expanded="false" aria-controls="oh-panel">Menu</button>
</div>
<div class="oh-panel" id="oh-panel" role="dialog" aria-modal="true" aria-label="Menu">
<div class="oh-panel__top">
<span class="oh-panel__brand">Otherside</span>
<button class="oh-panel__close" type="button">Close <span aria-hidden="true">×</span></button>
</div>
<nav class="oh-panel__links" aria-label="Menu links">
<a href="#destinations"><span class="oh-panel__n">01</span>Destinations</a>
<a href="#planner"><span class="oh-panel__n">02</span>Planner</a>
<a href="#journal"><span class="oh-panel__n">03</span>Journal</a>
<a href="#moments"><span class="oh-panel__n">04</span>Moments</a>
</nav>
<a class="btn oh-panel__cta" href="#planner">Plan a trip <span class="arr">→</span></a>
</div>
</header>
<section class="oh" id="top" aria-label="Otherside: the world is just outside the window">
<div class="oh-stage">
<!-- drifting colour haze behind everything -->
<div class="oh-haze" aria-hidden="true"><span class="oh-orb oh-orb--a"></span><span class="oh-orb oh-orb--b"></span><span class="oh-orb oh-orb--c"></span></div>
<!-- the view: full-screen layer clipped to the window shape, opens up on scroll -->
<div class="oh-win" aria-hidden="true">
<div class="oh-imgs">
<img src="assets/w-maldives.jpg" alt="" decoding="async" fetchpriority="high">
<img src="assets/w-santorini.jpg" alt="" decoding="async">
<img src="assets/w-swiss.jpg" alt="" decoding="async">
<img src="assets/w-aurora.jpg" alt="" decoding="async">
<img src="assets/w-machu.jpg" alt="" decoding="async">
<img src="assets/w-namibia.jpg" alt="" decoding="async">
<img src="assets/w-zhangjiajie.jpg" alt="" decoding="async">
<img src="assets/w-glacier.jpg" alt="" decoding="async">
</div>
<div class="oh-clouds"><i></i><i></i><i></i><i></i><i></i><i></i><i></i></div>
<div class="oh-winbox"><div class="oh-blind"><span class="oh-blind__pull"></span></div><span class="oh-glare"></span></div>
<div class="oh-vscrim"></div>
</div>
<!-- the cabin window bezel (a ring drawn with spread shadows) -->
<div class="oh-frame" aria-hidden="true"><span class="oh-frame__rivets"></span></div>
<h1 class="oh-h">
<span class="oh-h__l">The world is</span>
<span class="oh-h__r">just <em class="oh-hl">outside</em></span>
</h1>
<div class="oh-board" aria-live="polite">
<span class="oh-board__lbl"><span class="oh-board__dot"></span>Now boarding</span>
<span class="oh-flaps" aria-hidden="true"></span>
<span class="oh-sr">Baa Atoll, Maldives</span>
</div>
<p class="oh-intro">An independent travel journal for people who’d rather arrive slowly. Handpicked places, honest routes, window seat always.</p>
<div class="oh-pass">
<div class="oh-pass__leg">
<span class="oh-pass__k">From</span>
<span class="oh-pass__code">HME</span>
<span class="oh-pass__v">Your sofa</span>
</div>
<div class="oh-pass__route" aria-hidden="true">
<span class="oh-pass__line"></span>
<svg class="oh-pass__plane" viewBox="0 0 24 24"><path d="M21 15.5v-2l-8-5V3.2a1.5 1.5 0 0 0-3 0v5.3l-8 5v2l8-2.5v5.2L7.5 20v1.5l4-1 4 1V20L13 18.2V13z"/></svg>
</div>
<div class="oh-pass__leg oh-pass__leg--to">
<span class="oh-pass__k">To</span>
<span class="oh-pass__code oh-pass__to">MLE</span>
<span class="oh-pass__v oh-pass__place">Baa Atoll, Maldives</span>
</div>
<div class="oh-pass__stub">
<span class="oh-pass__k">Seat</span><span class="oh-pass__seat">12A</span>
</div>
<a class="btn oh-go" href="#destinations">Start exploring <span class="arr">→</span></a>
</div>
<div class="oh-s2">
<p class="oh-s2__k"><span class="dot"></span><span class="oh-s2__where">Baa Atoll, Maldives</span></p>
<h2 class="oh-s2__h">Wake up somewhere you can’t pronounce yet</h2>
<p class="oh-s2__sub">Scroll on. We’ve walked every place on the next page before writing a word about it.</p>
</div>
<div class="oh-hint" aria-hidden="true"><span>Scroll to fly</span><span class="oh-hint__line"></span></div>
</div>
</section>
<style>
/* ================= 01 HERO: nav + airplane-window portal ================= */
/* ---------- nav ---------- */
.oh-nav{position:fixed;inset:0 0 auto 0;z-index:60;color:var(--ink)}
.oh-nav__bar{display:grid;grid-template-columns:1fr auto 1fr;align-items:center;gap:24px;height:72px;padding-inline:var(--gut);
transition:background .35s var(--ease),box-shadow .35s var(--ease),backdrop-filter .35s var(--ease)}
.oh-nav--solid .oh-nav__bar{background:color-mix(in srgb,var(--bg) 72%,transparent);-webkit-backdrop-filter:blur(16px) saturate(1.3);backdrop-filter:blur(16px) saturate(1.3);
box-shadow:0 1px 0 color-mix(in srgb,var(--line) 80%,transparent)}
.oh-nav--over{color:#fff}
.oh-nav--solid.oh-nav--over{color:var(--ink)}
.oh-logo{display:inline-flex;align-items:center;gap:10px;text-decoration:none;font-family:var(--display);font-weight:500;font-size:22px;letter-spacing:-.03em;justify-self:start}
.oh-logo__mark{width:17px;height:25px;overflow:visible}
.oh-logo__glass{fill:var(--apricot)}
.oh-links{display:flex;gap:30px}
.oh-links a{position:relative;font-size:14px;font-weight:500;text-decoration:none;padding:6px 0}
.oh-links a::after{content:"";position:absolute;left:0;right:0;bottom:0;height:1px;background:currentColor;transform:scaleX(0);transform-origin:right;transition:transform .35s var(--ease)}
.oh-links a:hover::after{transform:scaleX(1);transform-origin:left}
.oh-cta{justify-self:end;display:inline-flex;align-items:center;padding:11px 20px;border-radius:999px;background:var(--plum);color:#fff;
font-size:14px;font-weight:600;text-decoration:none;transition:background .3s var(--ease),transform .3s var(--ease)}
.oh-cta:hover{background:var(--plum-deep);transform:translateY(-2px)}
.oh-burger{display:none;justify-self:end;padding:9px 18px;border-radius:999px;box-shadow:inset 0 0 0 1px currentColor;color:inherit;font-size:14px;font-weight:600}
.oh-panel{position:fixed;inset:0;z-index:70;background:var(--lilac);color:var(--ink);display:flex;flex-direction:column;padding:0 var(--gut) 32px;
visibility:hidden;opacity:0;clip-path:inset(0 0 100% 0);transition:clip-path .6s var(--ease),opacity .3s,visibility 0s .6s;overflow-y:auto}
.oh-panel.is-open{visibility:visible;opacity:1;clip-path:inset(0 0 0 0);transition:clip-path .6s var(--ease),opacity .2s,visibility 0s}
.oh-panel__top{display:flex;justify-content:space-between;align-items:center;height:72px;flex:none}
.oh-panel__brand{font-family:var(--display);font-size:22px;font-weight:500;letter-spacing:-.03em}
.oh-panel__close{padding:9px 18px;border-radius:999px;box-shadow:inset 0 0 0 1px var(--ink);font-size:14px;font-weight:600}
.oh-panel__links{display:flex;flex-direction:column;margin-top:auto;border-top:1px solid color-mix(in srgb,var(--plum) 25%,transparent)}
.oh-panel__links a{display:flex;align-items:baseline;gap:14px;padding:14px 0;font-family:var(--display);font-size:clamp(40px,11vw,64px);font-weight:400;letter-spacing:-.04em;line-height:1;
text-decoration:none;border-bottom:1px solid color-mix(in srgb,var(--plum) 25%,transparent)}
.oh-panel__links a:hover{color:var(--plum)}
.oh-panel__n{font-family:var(--mono);font-size:12px;letter-spacing:.06em;color:var(--plum)}
.oh-panel__cta{align-self:flex-start;margin-top:28px}
/* ---------- section + sticky stage ---------- */
.oh{position:relative;height:320vh;background:var(--bg)}
.oh-stage{position:sticky;top:0;height:100vh;height:100svh;overflow:hidden;background:var(--bg);
--ww:min(24vw,34svh);--wh:calc(var(--ww) * 1.42);--wr:calc(var(--ww) * .46);--fw:calc(var(--ww) * .12);--cy:50%}
/* haze orbs */
.oh-haze{position:absolute;inset:0;z-index:0;pointer-events:none}
.oh-orb{position:absolute;border-radius:50%;filter:blur(70px);opacity:.75}
.oh-orb--a{width:46vw;height:46vw;left:-12vw;top:-18vw;background:radial-gradient(closest-side,var(--lilac),transparent)}
.oh-orb--b{width:38vw;height:38vw;right:-10vw;bottom:-16vw;background:radial-gradient(closest-side,color-mix(in srgb,var(--apricot) 45%,transparent),transparent)}
.oh-orb--c{width:30vw;height:30vw;right:18vw;top:-10vw;background:radial-gradient(closest-side,color-mix(in srgb,var(--plum) 22%,transparent),transparent)}
/* the view */
.oh-win{position:absolute;inset:0;z-index:2;overflow:hidden;will-change:clip-path;
clip-path:inset(calc(var(--cy) - var(--wh) / 2) calc(50% - var(--ww) / 2) calc(100% - var(--cy) - var(--wh) / 2) calc(50% - var(--ww) / 2) round var(--wr))}
.oh-imgs{position:absolute;inset:-4%;will-change:transform}
.oh-imgs img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;opacity:0}
.oh-imgs img:first-child{opacity:1}
.oh-clouds{position:absolute;inset:0;pointer-events:none;will-change:transform,opacity}
.oh-clouds i{position:absolute;left:50%;top:var(--cy);width:calc(var(--ww) * .9);height:calc(var(--ww) * .42);margin:calc(var(--ww) * -.21) 0 0 calc(var(--ww) * -.45);
border-radius:50%;background:radial-gradient(closest-side,color-mix(in srgb,#fff 92%,transparent),color-mix(in srgb,#fff 40%,transparent) 55%,transparent);filter:blur(6px);opacity:.9}
.oh-winbox{position:absolute;left:50%;top:var(--cy);width:var(--ww);height:var(--wh);transform:translate(-50%,-50%);border-radius:var(--wr);overflow:hidden;pointer-events:none}
.oh-blind{position:absolute;inset:0;will-change:transform;
background:repeating-linear-gradient(180deg,var(--surface) 0 calc(var(--wh) / 14 - 1px),var(--line) calc(var(--wh) / 14 - 1px) calc(var(--wh) / 14));
box-shadow:inset 0 -10px 18px -10px color-mix(in srgb,var(--ink) 25%,transparent)}
.oh-blind__pull{position:absolute;left:50%;bottom:10px;width:34%;height:8px;margin-left:-17%;border-radius:8px;background:var(--line);box-shadow:inset 0 1px 0 color-mix(in srgb,var(--ink) 12%,transparent)}
.oh-glare{position:absolute;inset:0;background:linear-gradient(115deg,color-mix(in srgb,#fff 38%,transparent) 0 18%,transparent 34%,transparent 62%,color-mix(in srgb,#fff 14%,transparent) 70%,transparent 80%);mix-blend-mode:screen}
.oh-vscrim{position:absolute;inset:0;opacity:0;pointer-events:none;
background:linear-gradient(180deg,color-mix(in srgb,var(--ink) 25%,transparent),transparent 30%,transparent 45%,color-mix(in srgb,var(--ink) 55%,transparent))}
/* bezel: transparent box, ring from spread shadows (they follow border-radius) */
.oh-frame{position:absolute;z-index:3;left:50%;top:var(--cy);width:var(--ww);height:var(--wh);margin:calc(var(--wh) / -2) 0 0 calc(var(--ww) / -2);
border-radius:var(--wr);pointer-events:none;will-change:transform,opacity;
box-shadow:
inset 0 0 0 2px color-mix(in srgb,var(--ink) 10%,transparent),
inset 0 10px 22px -8px color-mix(in srgb,var(--ink) 35%,transparent),
0 0 0 calc(var(--fw) * .35) color-mix(in srgb,var(--line) 70%,var(--surface)),
0 0 0 calc(var(--fw) * .38) color-mix(in srgb,var(--ink) 8%,transparent),
0 0 0 var(--fw) var(--surface),
0 0 0 calc(var(--fw) + 1px) color-mix(in srgb,var(--ink) 8%,transparent),
0 calc(var(--fw) * 1.6) calc(var(--fw) * 3) calc(var(--fw) * -.6) color-mix(in srgb,var(--plum) 30%,transparent)}
.oh-frame__rivets{position:absolute;inset:calc(var(--fw) * -.7);border-radius:calc(var(--wr) + var(--fw) * .7);
background:radial-gradient(circle,var(--line) 0 2px,transparent 2.5px) 50% 0/100% 100% no-repeat,radial-gradient(circle,var(--line) 0 2px,transparent 2.5px) 50% 100%/100% 100% no-repeat}
/* headline flanks the window */
.oh-h{position:absolute;z-index:4;left:var(--gut);right:var(--gut);top:var(--cy);transform:translateY(-50%);display:grid;
grid-template-columns:1fr calc(var(--ww) + var(--fw) * 2 + clamp(40px,5vw,90px)) 1fr;align-items:center;pointer-events:none;
font-family:var(--display);font-weight:300;font-size:clamp(40px,min(5.4vw,10svh),104px);line-height:.95;letter-spacing:-.045em;color:var(--ink)}
.oh-h__l{grid-column:1;text-align:right;display:block}
.oh-h__r{grid-column:3;text-align:left;display:block}
.oh-hl{font-style:normal;color:var(--plum)}
.oh-h .w>span{will-change:transform}
/* split-flap board */
.oh-board{position:absolute;z-index:4;left:var(--gut);top:clamp(96px,15svh,150px);display:flex;flex-direction:column;gap:10px}
.oh-board__lbl{font-family:var(--mono);font-size:11px;letter-spacing:.1em;text-transform:uppercase;color:var(--mute);display:flex;align-items:center;gap:8px}
.oh-board__dot{width:8px;height:8px;border-radius:50%;background:var(--apricot);animation:oh-blink 1.2s steps(2,end) infinite}
@keyframes oh-blink{50%{opacity:.2}}
.oh-flaps{display:flex;gap:3px;perspective:400px}
.oh-flap{position:relative;display:grid;place-items:center;width:clamp(22px,1.9vw,30px);height:clamp(32px,2.7vw,42px);border-radius:5px;
background:var(--surface);color:var(--plum);font-family:var(--mono);font-weight:500;font-size:clamp(15px,1.3vw,21px);
box-shadow:0 1px 0 var(--line),0 6px 14px -8px color-mix(in srgb,var(--plum) 40%,transparent);transform-origin:50% 50%}
.oh-flap::after{content:"";position:absolute;left:0;right:0;top:50%;height:1px;background:color-mix(in srgb,var(--line) 90%,transparent)}
.oh-sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
.oh-intro{position:absolute;z-index:4;right:var(--gut);top:clamp(96px,15svh,150px);max-width:30ch;margin:0;font-size:15px;line-height:1.55;color:var(--mute);text-align:right}
/* boarding pass */
.oh-pass{position:absolute;z-index:5;left:50%;bottom:clamp(18px,4svh,40px);transform:translateX(-50%);width:min(980px,calc(100% - var(--gut) * 2));
display:grid;grid-template-columns:auto 1fr auto auto auto;align-items:center;gap:clamp(14px,2vw,28px);padding:14px 14px 14px 26px;border-radius:22px;
background:var(--surface);box-shadow:0 24px 50px -28px color-mix(in srgb,var(--plum) 45%,transparent),0 0 0 1px color-mix(in srgb,var(--line) 70%,transparent);
-webkit-mask:radial-gradient(circle 10px at calc(100% - var(--stub,190px)) 0,transparent 98%,#fff) top/100% 51% no-repeat,radial-gradient(circle 10px at calc(100% - var(--stub,190px)) 100%,transparent 98%,#fff) bottom/100% 51% no-repeat;
mask:radial-gradient(circle 10px at calc(100% - var(--stub,190px)) 0,transparent 98%,#fff) top/100% 51% no-repeat,radial-gradient(circle 10px at calc(100% - var(--stub,190px)) 100%,transparent 98%,#fff) bottom/100% 51% no-repeat}
.oh-pass__leg{display:flex;flex-direction:column;gap:2px;min-width:0}
.oh-pass__leg--to{min-width:13ch}
.oh-pass__k{font-family:var(--mono);font-size:10px;letter-spacing:.1em;text-transform:uppercase;color:var(--mute)}
.oh-pass__code{font-family:var(--display);font-size:clamp(26px,2.4vw,38px);font-weight:500;letter-spacing:-.02em;line-height:1;color:var(--ink);display:inline-block}
.oh-pass__v{font-size:12.5px;color:var(--mute);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.oh-pass__route{position:relative;height:24px;min-width:80px}
.oh-pass__line{position:absolute;left:0;right:0;top:50%;border-top:1.5px dashed color-mix(in srgb,var(--plum) 40%,transparent)}
.oh-pass__line::after{content:"";position:absolute;left:0;top:-1.5px;height:1.5px;width:var(--p,0%);background:var(--plum)}
.oh-pass__plane{position:absolute;top:50%;left:var(--p,0%);width:22px;height:22px;margin:-11px 0 0 -11px;fill:var(--plum);transform:rotate(90deg)}
.oh-pass__stub{display:flex;flex-direction:column;gap:2px;padding-left:clamp(14px,2vw,26px);border-left:1.5px dashed var(--line)}
.oh-pass__seat{font-family:var(--display);font-size:clamp(20px,1.8vw,28px);font-weight:500;line-height:1;color:var(--apricot-deep)}
.oh-go{white-space:nowrap}
/* stage 2 — landed */
.oh-s2{position:absolute;z-index:6;left:var(--gut);right:var(--gut);bottom:clamp(40px,10svh,110px);color:#fff;pointer-events:none;
text-shadow:0 2px 30px color-mix(in srgb,var(--ink) 35%,transparent)}
.oh-s2__k{margin:0 0 18px;font-family:var(--mono);font-size:12px;letter-spacing:.1em;text-transform:uppercase;opacity:0}
.oh-s2__h{font-family:var(--display);font-weight:300;font-size:clamp(44px,min(7vw,12svh),132px);line-height:.93;letter-spacing:-.045em;max-width:15ch}
.oh-s2__sub{margin:22px 0 0;max-width:40ch;font-size:17px;font-weight:500;opacity:0}
/* scroll hint */
.oh-hint{position:absolute;z-index:4;right:var(--gut);top:var(--cy);transform:translateY(calc(var(--wh) / 2 - 40px));display:flex;flex-direction:column;align-items:center;gap:10px;
color:var(--mute);font-family:var(--mono);font-size:11px;letter-spacing:.1em;text-transform:uppercase;writing-mode:vertical-rl}
.oh-hint__line{position:relative;width:1px;height:48px;overflow:hidden;background:var(--line)}
.oh-hint__line::after{content:"";position:absolute;left:0;top:0;width:1px;height:40%;background:var(--plum);animation:oh-drip 1.8s var(--ease) infinite}
@keyframes oh-drip{from{transform:translateY(-100%)}to{transform:translateY(260%)}}
/* reduced motion */
.oh--static{height:100vh;height:100svh}
.oh--static .oh-blind,.oh--static .oh-s2,.oh--static .oh-hint{display:none}
/* ---------- responsive ---------- */
@media (max-width:1100px){
.oh-links{gap:20px}
.oh-pass{grid-template-columns:auto 1fr auto auto;--stub:0px;-webkit-mask:none;mask:none}
.oh-go{display:none}
}
@media (max-width:900px){
.oh-intro{display:none}
}
@media (max-width:759px){
.oh-nav__bar{grid-template-columns:1fr auto;height:64px}
.oh-links,.oh-cta{display:none}
.oh-burger{display:inline-flex}
.oh-panel__top{height:64px}
.oh{height:260vh}
.oh-stage{--ww:min(52vw,26svh);--cy:54%}
.oh-h{top:calc(var(--cy) - var(--wh) / 2 - var(--fw) - 22px);transform:translateY(-100%);display:block;text-align:center;font-size:clamp(36px,10.5vw,48px)}
.oh-h__l,.oh-h__r{text-align:center}
.oh-board{top:76px;left:0;right:0;align-items:center;display:none}
.oh-hint{display:none}
.oh-pass{grid-template-columns:auto 1fr auto;padding:12px 16px;gap:12px;border-radius:18px}
.oh-pass__stub{display:none}
.oh-pass__leg--to{min-width:0;text-align:right;align-items:flex-end}
.oh-pass__v{font-size:11.5px;max-width:28vw}
.oh-s2__sub{font-size:15px}
}
@media (max-width:759px) and (min-height:700px){
.oh-board{display:flex}
.oh-stage{--cy:58%}
.oh-h{font-size:clamp(34px,9.6vw,44px)}
}
</style>
<script>
(function(){
const OS=window.OS, sec=document.querySelector('.oh'); if(!sec||!OS) return;
const $=s=>sec.querySelector(s), $$=s=>[...sec.querySelectorAll(s)];
const nav=document.getElementById('oh-nav'), bar=nav.querySelector('.oh-nav__bar');
const panel=document.getElementById('oh-panel'), burger=nav.querySelector('.oh-burger'), closeBtn=nav.querySelector('.oh-panel__close');
/* ---------- anchor scrolling (Lenis-aware) ---------- */
function go(e){
const id=e.currentTarget.getAttribute('href'); const t=id&&id.length>1&&document.querySelector(id);
if(!t||!OS.lenis) return;
e.preventDefault(); OS.lenis.start(); OS.lenis.scrollTo(t,{offset:id==='#top'?0:-64});
}
/* ---------- mobile menu ---------- */
function openMenu(){
panel.classList.add('is-open'); burger.setAttribute('aria-expanded','true');
OS.lenis?.stop(); document.documentElement.style.overflow='hidden';
setTimeout(()=>closeBtn.focus(),50);
}
function closeMenu(focusBack){
if(!panel.classList.contains('is-open')) return;
panel.classList.remove('is-open'); burger.setAttribute('aria-expanded','false');
document.documentElement.style.overflow=''; OS.lenis?.start();
if(focusBack) burger.focus();
}
burger.addEventListener('click',openMenu);
closeBtn.addEventListener('click',()=>closeMenu(true));
document.addEventListener('keydown',e=>{if(e.key==='Escape')closeMenu(true)});
addEventListener('resize',()=>{if(innerWidth>=760)closeMenu(false)});
panel.querySelectorAll('a').forEach(a=>a.addEventListener('click',e=>{closeMenu(false);go(e)}));
nav.querySelectorAll('.oh-nav__bar a').forEach(a=>a.addEventListener('click',go));
sec.querySelectorAll('a[href^="#"]').forEach(a=>a.addEventListener('click',go));
/* ---------- destinations shown through the window ---------- */
const D=[
{n:'MALDIVES', c:'MLE',p:'Baa Atoll, Maldives'},
{n:'SANTORINI',c:'JTR',p:'Oia, Santorini'},
{n:'ALPS', c:'ZRH',p:'Bernese Oberland, Switzerland'},
{n:'TROMSO', c:'TOS',p:'Aurora season, Norway'},
{n:'PERU', c:'CUZ',p:'Machu Picchu, Peru'},
{n:'NAMIBIA', c:'WDH',p:'Deadvlei, Namibia'},
{n:'HUNAN', c:'DYG',p:'Wulingyuan, China'},
{n:'ICELAND', c:'KEF',p:'Jökulsárlón, Iceland'}
];
const CELLS=9, CYCLE=3.4, GLYPHS='ABCDEFGHIJKLMNOPRSTUVWXYZ';
const imgs=$$('.oh-imgs img'), flapsEl=$('.oh-flaps'), sr=$('.oh-sr');
const toCode=$('.oh-pass__to'), place=$('.oh-pass__place'), where=$('.oh-s2__where'), pass=$('.oh-pass');
const flaps=Array.from({length:CELLS},()=>{const s=document.createElement('span');s.className='oh-flap';s.textContent=' ';flapsEl.appendChild(s);return s});
const pad=w=>w.padEnd(CELLS,' ');
pad(D[0].n).split('').forEach((ch,i)=>flaps[i].textContent=ch);
function flipTo(el,target,delay,spins){
if(OS.reduced){el.textContent=target;return}
let k=0;
const tick=()=>{
const last=k>=spins; el.textContent=last?target:GLYPHS[Math.random()*GLYPHS.length|0];
gsap.fromTo(el,{rotateX:-80},{rotateX:0,duration:.07,ease:'power1.out'});
if(!last){k++;gsap.delayedCall(.055,tick)}
};
gsap.delayedCall(delay,tick);
}
function flipWord(word){pad(word).split('').forEach((ch,i)=>{if(flaps[i].textContent!==ch||ch!==' ')flipTo(flaps[i],ch,i*.045,4+(Math.random()*4|0))})}
function flipCode(code){
gsap.timeline().to(toCode,{rotateX:90,duration:.16,ease:'power2.in'}).add(()=>{toCode.textContent=code}).to(toCode,{rotateX:0,duration:.3,ease:'back.out(2)'});
}
let cur=0, cycling=true, planeTw=null;
function flyPlane(){
planeTw&&planeTw.kill();
if(OS.reduced) return pass.style.setProperty('--p','100%');
planeTw=gsap.fromTo(pass,{'--p':'0%'},{'--p':'100%',duration:CYCLE,ease:'power1.inOut'});
}
function show(k){
const prev=imgs[cur], next=imgs[k]; cur=k;
gsap.set(next,{zIndex:2}); gsap.set(prev,{zIndex:1});
gsap.fromTo(next,{opacity:0,scale:1.14},{opacity:1,scale:1,duration:1.2,ease:'power2.out',onComplete:()=>gsap.set(prev,{opacity:0})});
flipWord(D[k].n); flipCode(D[k].c);
place.textContent=D[k].p; where.textContent=D[k].p; sr.textContent=D[k].p;
flyPlane();
}
let loop=null;
function schedule(){loop&&loop.kill();loop=gsap.delayedCall(CYCLE,()=>{if(cycling)show((cur+1)%D.length);schedule()})}
/* ---------- nav: solid bar after the hero ---------- */
ScrollTrigger.create({trigger:sec,start:'bottom 72px',
onEnter:()=>nav.classList.add('oh-nav--solid'),onLeaveBack:()=>nav.classList.remove('oh-nav--solid')});
/* ---------- reduced motion ---------- */
if(OS.reduced){sec.classList.add('oh--static');schedule();return}
const stage=$('.oh-stage'), win=$('.oh-win'), frame=$('.oh-frame'), wimgs=$('.oh-imgs'), clouds=$('.oh-clouds'), cl=$$('.oh-clouds i');
const blind=$('.oh-blind'), glare=$('.oh-glare'), vscrim=$('.oh-vscrim'), board=$('.oh-board'), intro=$('.oh-intro'), hint=$('.oh-hint');
const hl=OS.split($('.oh-h__l')), hr=OS.split($('.oh-h__r'));
const s2h=OS.split($('.oh-s2__h')), s2k=$('.oh-s2__k'), s2s=$('.oh-s2__sub');
gsap.set(s2h,{yPercent:110});
gsap.set(pass,{xPercent:-50,x:0}); /* own the centring so later y tweens keep it */
/* clouds drifting past the window, forever */
const g={W:0,H:0,ww:0,wh:0,cy:0,r:0};
function measure(){
const cs=getComputedStyle(frame);
g.W=stage.clientWidth; g.H=stage.clientHeight; g.ww=frame.offsetWidth; g.wh=frame.offsetHeight;
g.cy=frame.offsetTop+g.wh/2; g.r=parseFloat(cs.borderTopLeftRadius)||g.ww*.46;
}
measure();
const clipAt=t=>{ /* t: 0 = window, 1 = full screen */
const T=(g.cy-g.wh/2)*(1-t), B=(g.H-g.cy-g.wh/2)*(1-t), L=((g.W-g.ww)/2)*(1-t), r=g.r*(1-t);
return `inset(${T}px ${L}px ${B}px ${L}px round ${r}px)`;
};
cl.forEach((c,i)=>{
const dur=gsap.utils.random(4.5,8.5), y=gsap.utils.random(-.42,.42), s=gsap.utils.random(.6,1.5);
gsap.set(c,{y:()=>y*g.wh,scale:s,opacity:gsap.utils.random(.55,.95)});
gsap.fromTo(c,{x:()=>g.ww*1.2},{x:()=>-g.ww*1.2,duration:dur,ease:'none',repeat:-1,delay:-i*dur/cl.length});
});
/* orbs breathe */
$$('.oh-orb').forEach((o,i)=>gsap.to(o,{x:gsap.utils.random(-80,80),y:gsap.utils.random(-60,60),scale:gsap.utils.random(.85,1.2),duration:gsap.utils.random(6,10),ease:'sine.inOut',yoyo:true,repeat:-1,delay:i}));
/* pointer: bezel tilts, view shifts the other way */
const fine=matchMedia('(pointer:fine)').matches;
const rig={fx:gsap.quickTo(frame,'x',{duration:.8,ease:'power3'}),fy:gsap.quickTo(frame,'y',{duration:.8,ease:'power3'}),
rx:gsap.quickTo(frame,'rotationY',{duration:.8,ease:'power3'}),ry:gsap.quickTo(frame,'rotationX',{duration:.8,ease:'power3'}),
ix:gsap.quickTo(wimgs,'x',{duration:1.1,ease:'power3'}),iy:gsap.quickTo(wimgs,'y',{duration:1.1,ease:'power3'}),
cx:gsap.quickTo(clouds,'x',{duration:.9,ease:'power3'})};
gsap.set(frame,{transformPerspective:900});
let live=true;
if(fine) stage.addEventListener('pointermove',e=>{
if(!live) return;
const nx=e.clientX/g.W-.5, ny=e.clientY/g.H-.5;
rig.fx(nx*14);rig.fy(ny*10);rig.rx(nx*10);rig.ry(-ny*8);rig.ix(-nx*38);rig.iy(-ny*26);rig.cx(-nx*60);
});
/* scroll: the window opens into the whole screen (proxy 0→1, clip rebuilt from live geometry) */
const open={t:0}; const applyClip=()=>{win.style.clipPath=clipAt(open.t)};
ScrollTrigger.addEventListener('refresh',()=>{measure();applyClip()});
const tl=gsap.timeline({defaults:{ease:'none'},
scrollTrigger:{trigger:sec,start:'top top',end:'bottom bottom',scrub:1,invalidateOnRefresh:true,
onRefreshInit:measure,
onUpdate(self){
const p=self.progress, stop=p>.03;
if(stop===cycling){cycling=!stop}
live=p<.08;
nav.classList.toggle('oh-nav--over',p>.42&&p<.995);
if(!live){gsap.to([frame,wimgs,clouds],{x:0,y:0,rotationX:0,rotationY:0,duration:.6,overwrite:'auto'})}
}}});
tl.to(hint,{autoAlpha:0,duration:.04},0)
.to(hl,{xPercent:-60,autoAlpha:0,duration:.14,stagger:.01,ease:'power2.in'},.02)
.to(hr,{xPercent:60,autoAlpha:0,duration:.14,stagger:-.01,ease:'power2.in'},.02)
.to([board,intro],{autoAlpha:0,y:-24,duration:.1},.03)
.to(pass,{autoAlpha:0,y:40,duration:.1},.04)
.to(open,{t:1,ease:'power2.inOut',duration:.5,onUpdate:applyClip},.08)
.to(frame,{scale:()=>Math.max(g.W/g.ww,g.H/g.wh)*1.35,opacity:0,ease:'power2.in',duration:.42},.08)
.to(glare,{opacity:0,duration:.2},.1)
.fromTo(wimgs,{scale:1.18},{scale:1,ease:'power1.out',duration:.6,immediateRender:false},.08)
.to(clouds,{scale:4,opacity:0,ease:'power2.in',duration:.4},.12)
.to(vscrim,{opacity:1,duration:.25},.4)
.to(s2k,{opacity:1,duration:.06},.58)
.to(s2h,{yPercent:0,ease:'power3.out',duration:.1,stagger:.012},.6)
.to(s2s,{opacity:1,duration:.08},.72)
.to({},{duration:.2},.8);
/* ---------- intro on load ---------- */
applyClip(); schedule(); flyPlane();
if(scrollY<10){
const o={ease:'power3.out'};
gsap.from(bar,{...o,yPercent:-100,autoAlpha:0,duration:1,delay:.1,clearProps:'transform,opacity,visibility'});
gsap.from(frame,{...o,scale:.8,autoAlpha:0,duration:1.4,delay:.1});
gsap.to(blind,{yPercent:-102,duration:1.5,ease:'power3.inOut',delay:.9});
gsap.from(hl,{xPercent:40,autoAlpha:0,duration:1.2,ease:'power4.out',stagger:.07,delay:.5});
gsap.from(hr,{xPercent:-40,autoAlpha:0,duration:1.2,ease:'power4.out',stagger:.07,delay:.6});
gsap.from([board,intro],{...o,autoAlpha:0,y:20,duration:.9,delay:1.1,stagger:.1});
gsap.from(pass,{...o,autoAlpha:0,yPercent:60,duration:1,delay:1.3});
gsap.from(hint,{autoAlpha:0,duration:.9,delay:1.6});
} else gsap.set(blind,{yPercent:-102});
})();
</script>
<main>
<section class="od" id="destinations" aria-labelledby="od-title">
<style>
.od{background:var(--bg);padding-block:clamp(80px,11vw,160px);overflow:hidden}
.od-head{display:grid;grid-template-columns:minmax(0,1.6fr) minmax(0,1fr);gap:clamp(28px,5vw,80px);align-items:end}
.od-eyebrow{margin:0 0 22px}
.od-title{font-size:clamp(40px,6.6vw,96px);max-width:14ch}
.od-side{display:flex;flex-direction:column;align-items:flex-start;gap:22px;padding-bottom:6px}
.od-side p{margin:0;color:var(--mute);font-size:clamp(15px,1.2vw,17px);max-width:38ch}
/* filters */
.od-bar{display:flex;align-items:center;justify-content:space-between;gap:16px 24px;flex-wrap:wrap;
margin-top:clamp(40px,6vw,72px);padding-top:22px;border-top:1px solid var(--line)}
.od-chips{display:flex;flex-wrap:wrap;gap:8px}
.od-chip{padding:9px 16px;border-radius:999px;font-size:14px;font-weight:600;color:var(--ink);
box-shadow:inset 0 0 0 1px var(--line);background:var(--surface);
transition:background .25s var(--ease),color .25s var(--ease),box-shadow .25s var(--ease)}
.od-chip:hover{box-shadow:inset 0 0 0 1px var(--plum);color:var(--plum)}
.od-chip[aria-pressed="true"]{background:var(--plum);color:#fff;box-shadow:none}
.od-count{margin:0;display:flex;align-items:baseline;gap:10px;font-family:var(--mono);font-size:12px;letter-spacing:.08em;text-transform:uppercase;color:var(--mute)}
.od-count__n{display:inline-block;overflow:hidden;height:1em;line-height:1;font-family:var(--display);font-size:clamp(28px,3vw,40px);
letter-spacing:-.03em;color:var(--plum);text-transform:none;min-width:1.2em}
.od-count__n span{display:inline-block}
/* grid */
.od-grid{list-style:none;margin:28px 0 0;padding:0;display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:clamp(14px,1.6vw,24px)}
.od-item{min-width:0}
.od-item[hidden]{display:none}
.od-card{position:relative;display:block;border-radius:var(--radius);overflow:hidden;text-decoration:none;color:#fff;
aspect-ratio:3/4;background:var(--lilac);isolation:isolate;
transition:transform .5s var(--ease),box-shadow .5s var(--ease)}
.od-media{position:absolute;inset:0;overflow:hidden}
.od-media img{width:100%;height:100%;object-fit:cover;transition:transform .9s ease}
.od-card::after{content:"";position:absolute;inset:0;z-index:1;pointer-events:none;
background:linear-gradient(to top,
color-mix(in srgb,var(--ink) 72%,transparent) 0%,
color-mix(in srgb,var(--ink) 30%,transparent) 38%,
color-mix(in srgb,var(--ink) 0%,transparent) 62%),
linear-gradient(to bottom,color-mix(in srgb,var(--ink) 28%,transparent) 0%,color-mix(in srgb,var(--ink) 0%,transparent) 24%)}
.od-top{position:absolute;z-index:2;inset:16px 16px auto 16px;display:flex;justify-content:space-between;align-items:flex-start;gap:8px}
.od-idx{font-family:var(--mono);font-size:12px;letter-spacing:.08em;color:#fff}
.od-body{position:absolute;z-index:2;left:18px;right:18px;bottom:18px}
.od-region{display:block;font-family:var(--mono);font-size:11px;letter-spacing:.1em;text-transform:uppercase;color:#fff;opacity:.85;margin-bottom:8px}
.od-name{font-size:clamp(22px,1.9vw,28px);line-height:1.02;color:#fff;padding-right:52px}
.od-go{position:absolute;z-index:2;right:16px;bottom:16px;width:44px;height:44px;border-radius:50%;display:grid;place-items:center;
background:var(--plum);color:#fff;font-size:18px;opacity:0;transform:scale(.6) rotate(-45deg);
transition:opacity .4s var(--ease),transform .5s var(--ease),background .25s var(--ease)}
.od-card:focus-visible .od-go{opacity:1;transform:none}
@media (hover:hover){
.od-card:hover{transform:translateY(-8px);box-shadow:0 24px 48px -24px color-mix(in srgb,var(--plum) 55%,transparent)}
.od-card:hover .od-media img{transform:scale(1.08)}
.od-card:hover .od-go{opacity:1;transform:none}
.od-go:hover{background:var(--plum-deep)}
}
@media (hover:none){.od-go{opacity:1;transform:none;width:38px;height:38px;font-size:16px}}
.od-empty{margin:24px 0 0;color:var(--mute)}
/* marquee */
.od-marquee{margin-top:clamp(56px,8vw,112px);overflow:hidden;border-block:1px solid var(--line);padding-block:clamp(16px,2vw,28px);max-width:100%}
.od-track{display:flex;width:max-content;will-change:transform}
.od-run{display:flex;align-items:center;flex-shrink:0}
.od-word{font-family:var(--display);font-weight:500;font-size:clamp(48px,8vw,128px);line-height:1;letter-spacing:-.03em;white-space:nowrap;
color:transparent;-webkit-text-stroke:1.5px var(--ink);padding-inline:clamp(18px,2.4vw,40px);transition:color .35s var(--ease),-webkit-text-stroke-color .35s var(--ease)}
.od-word:nth-child(4n+1){color:var(--plum);-webkit-text-stroke-color:var(--plum)}
.od-marquee:hover .od-word{-webkit-text-stroke-color:var(--plum)}
.od-sep{flex-shrink:0;width:clamp(12px,1.2vw,18px);height:clamp(12px,1.2vw,18px);border-radius:50%;background:var(--apricot)}
@media (max-width:1024px){
.od-grid{grid-template-columns:repeat(2,minmax(0,1fr))}
.od-head{grid-template-columns:1fr;align-items:start}
}
@media (max-width:599px){
.od-bar{align-items:flex-start}
.od-grid{display:flex;overflow-x:auto;scroll-snap-type:x mandatory;overscroll-behavior-x:contain;
margin-inline:calc(var(--gut)*-1);padding:6px var(--gut) 14px;scroll-padding-inline:var(--gut);scrollbar-width:none;gap:12px}
.od-grid::-webkit-scrollbar{display:none}
.od-item{flex:0 0 calc((100% - 12px) / 1.2);scroll-snap-align:start}
.od-word{-webkit-text-stroke-width:1px}
}
</style>
<div class="wrap">
<div class="od-head">
<div>
<p class="eyebrow od-eyebrow"><span class="dot"></span>Destinations — 08 places</p>
<h2 class="od-title" id="od-title">Handpicked places, <em class="it">walked</em> before we wrote about them</h2>
</div>
<div class="od-side">
<p>No listicles, no borrowed photos. Every guide here started with our own boots on the trail, a notebook in the pocket and a few wrong turns worth keeping.</p>
<a class="btn btn--line" href="#destinations">All destinations <span class="arr" aria-hidden="true">→</span></a>
</div>
</div>
<div class="od-bar">
<div class="od-chips" role="group" aria-label="Filter destinations by landscape">
<button class="od-chip" type="button" data-filter="all" aria-pressed="true">All</button>
<button class="od-chip" type="button" data-filter="mountains" aria-pressed="false">Mountains</button>
<button class="od-chip" type="button" data-filter="coast" aria-pressed="false">Coast</button>
<button class="od-chip" type="button" data-filter="desert" aria-pressed="false">Desert</button>
<button class="od-chip" type="button" data-filter="cities" aria-pressed="false">Cities</button>
</div>
<p class="od-count" aria-live="polite"><span class="od-count__n"><span>08</span></span><span class="od-count__l">places shown</span></p>
</div>
<ul class="od-grid">
<li class="od-item" data-cat="coast mountains">
<a class="od-card" href="#destinations" aria-label="Skógafoss and the South Coast, Iceland">
<div class="od-media"><img src="assets/d-iceland.jpg" alt="Skógafoss waterfall pouring over green cliffs on Iceland's south coast" loading="lazy" width="600" height="800"></div>
<div class="od-top"><span class="od-idx">01</span><span class="tag">Best: Jun–Aug</span></div>
<div class="od-body"><span class="od-region">Iceland</span><h3 class="od-name">Skógafoss & the South Coast</h3></div>
<span class="od-go" aria-hidden="true">→</span>
</a>
</li>
<li class="od-item" data-cat="cities">
<a class="od-card" href="#destinations" aria-label="Old-town temples, Japan">
<div class="od-media"><img src="assets/d-kyoto.jpg" alt="Wooden temple roofs rising above a quiet old-town lane in Kyoto" loading="lazy" width="600" height="800"></div>
<div class="od-top"><span class="od-idx">02</span><span class="tag">Best: Mar–Apr</span></div>
<div class="od-body"><span class="od-region">Japan</span><h3 class="od-name">Old-town temples</h3></div>
<span class="od-go" aria-hidden="true">→</span>
</a>
</li>
<li class="od-item" data-cat="mountains">
<a class="od-card" href="#destinations" aria-label="Torres del Paine, Chile">
<div class="od-media"><img src="assets/d-patagonia.jpg" alt="Granite towers of Torres del Paine above a glacial lake" loading="lazy" width="600" height="800"></div>
<div class="od-top"><span class="od-idx">03</span><span class="tag">Best: Nov–Mar</span></div>
<div class="od-body"><span class="od-region">Chile</span><h3 class="od-name">Torres del Paine</h3></div>
<span class="od-go" aria-hidden="true">→</span>
</a>
</li>
<li class="od-item" data-cat="desert">
<a class="od-card" href="#destinations" aria-label="Erg Chebbi dunes, Morocco">
<div class="od-media"><img src="assets/d-morocco.jpg" alt="Wind-shaped orange dunes of Erg Chebbi in the Moroccan Sahara" loading="lazy" width="600" height="800"></div>
<div class="od-top"><span class="od-idx">04</span><span class="tag">Best: Oct–Apr</span></div>
<div class="od-body"><span class="od-region">Morocco</span><h3 class="od-name">Erg Chebbi dunes</h3></div>
<span class="od-go" aria-hidden="true">→</span>
</a>
</li>
<li class="od-item" data-cat="coast">
<a class="od-card" href="#destinations" aria-label="Lofoten fishing villages, Norway">
<div class="od-media"><img src="assets/d-norway.jpg" alt="Red fishing cabins on stilts beneath steep Lofoten peaks" loading="lazy" width="600" height="800"></div>
<div class="od-top"><span class="od-idx">05</span><span class="tag">Best: Feb–Mar</span></div>
<div class="od-body"><span class="od-region">Norway</span><h3 class="od-name">Lofoten fishing villages</h3></div>
<span class="od-go" aria-hidden="true">→</span>
</a>
</li>
<li class="od-item" data-cat="mountains">
<a class="od-card" href="#destinations" aria-label="Rice terraces of Sidemen, Indonesia">
<div class="od-media"><img src="assets/d-bali.jpg" alt="Stepped green rice terraces in the Sidemen valley, Bali" loading="lazy" width="600" height="800"></div>
<div class="od-top"><span class="od-idx">06</span><span class="tag">Best: May–Sep</span></div>
<div class="od-body"><span class="od-region">Indonesia</span><h3 class="od-name">Rice terraces of Sidemen</h3></div>
<span class="od-go" aria-hidden="true">→</span>
</a>
</li>
<li class="od-item" data-cat="mountains">
<a class="od-card" href="#destinations" aria-label="Alpine lakes, Italy">
<div class="od-media"><img src="assets/d-dolomites.jpg" alt="Still alpine lake reflecting the jagged Dolomite peaks" loading="lazy" width="600" height="800"></div>
<div class="od-top"><span class="od-idx">07</span><span class="tag">Best: Jun–Sep</span></div>
<div class="od-body"><span class="od-region">Italy</span><h3 class="od-name">Alpine lakes</h3></div>
<span class="od-go" aria-hidden="true">→</span>
</a>
</li>
<li class="od-item" data-cat="coast cities">
<a class="od-card" href="#destinations" aria-label="Positano and the Amalfi Coast, Italy">
<div class="od-media"><img src="assets/d-amalfi.jpg" alt="Pastel houses of Positano stacked down the cliffs to the sea" loading="lazy" width="600" height="800"></div>
<div class="od-top"><span class="od-idx">08</span><span class="tag">Best: May–Jun</span></div>
<div class="od-body"><span class="od-region">Italy</span><h3 class="od-name">Positano & the Amalfi Coast</h3></div>
<span class="od-go" aria-hidden="true">→</span>
</a>
</li>
</ul>
</div>
<div class="od-marquee" aria-hidden="true">
<div class="od-track">
<div class="od-run">
<span class="od-word">Iceland</span><span class="od-sep"></span>
<span class="od-word">Kyoto</span><span class="od-sep"></span>
<span class="od-word">Patagonia</span><span class="od-sep"></span>
<span class="od-word">Erg Chebbi</span><span class="od-sep"></span>
<span class="od-word">Lofoten</span><span class="od-sep"></span>
<span class="od-word">Sidemen</span><span class="od-sep"></span>
<span class="od-word">Dolomites</span><span class="od-sep"></span>
<span class="od-word">Amalfi</span><span class="od-sep"></span>
</div>
</div>
</div>
<script>
(function(){
const root=document.getElementById('destinations');
if(!root)return;
const R=window.OS&&OS.reduced;
const hasG=typeof gsap!=='undefined';
const items=[...root.querySelectorAll('.od-item')];
const chips=[...root.querySelectorAll('.od-chip')];
const numWrap=root.querySelector('.od-count__n');
const numEl=numWrap.querySelector('span');
const pad=n=>String(n).padStart(2,'0');
/* heading */
if(window.OS)OS.reveal(root.querySelector('.od-title'));
/* ---------- scroll entrance ---------- */
const CLIP_OFF='inset(100% 0% 0% 0% round 18px)', CLIP_ON='inset(0% 0% 0% 0% round 18px)';
const pending=new Set();
if(hasG&&!R){
items.forEach(it=>{
const card=it.querySelector('.od-card');
gsap.set(card,{clipPath:CLIP_OFF});
gsap.set(it.querySelector('.od-media'),{scale:1.22});
pending.add(it);
});
ScrollTrigger.batch(items,{
start:'top 90%',once:true,
onEnter:batch=>{
const live=batch.filter(b=>pending.has(b));
live.forEach(b=>pending.delete(b));
if(!live.length)return;
gsap.to(live.map(b=>b.querySelector('.od-card')),{clipPath:CLIP_ON,duration:1.2,ease:'power4.inOut',stagger:.1,
onComplete(){gsap.set(this.targets(),{clearProps:'clipPath'})}});
gsap.to(live.map(b=>b.querySelector('.od-media')),{scale:1,duration:1.6,ease:'power3.out',stagger:.1,clearProps:'transform'});
}
});
}
/* cards that never scrolled in (e.g. filtered while hidden) are shown instantly */
function flushPending(){
pending.forEach(it=>{
gsap.set(it.querySelector('.od-card'),{clearProps:'clipPath'});
gsap.set(it.querySelector('.od-media'),{clearProps:'transform'});
});
pending.clear();
}
/* ---------- count roll ---------- */
let shown=items.length;
function setCount(n){
if(!hasG||R){numEl.textContent=pad(n);shown=n;return}
const from=shown;shown=n;
const o={v:from};
gsap.killTweensOf(numEl);
gsap.fromTo(numEl,{yPercent:n>from?100:-100},{yPercent:0,duration:.5,ease:'power3.out'});
gsap.to(o,{v:n,duration:.5,ease:'power2.out',onUpdate:()=>{numEl.textContent=pad(Math.round(o.v))}});
}
/* ---------- filter with hand-written FLIP ---------- */
let busy=null;
const match=(it,f)=>f==='all'||it.dataset.cat.split(' ').includes(f);
function apply(f){
chips.forEach(c=>c.setAttribute('aria-pressed',String(c.dataset.filter===f)));
const next=items.filter(it=>match(it,f));
const leaving=items.filter(it=>!it.hidden&&!next.includes(it));
const entering=next.filter(it=>it.hidden);
const staying=next.filter(it=>!it.hidden);
setCount(next.length);
if(!hasG||R){
items.forEach(it=>{it.hidden=!next.includes(it)});
return;
}
flushPending();
if(busy){busy.progress(1).kill();busy=null}
const out=gsap.timeline();
busy=out;
if(leaving.length){
out.to(leaving,{opacity:0,scale:.9,duration:.28,ease:'power2.in',stagger:.03});
}
out.add(()=>{
/* First: record rects of the cards that stay */
const first=new Map(staying.map(it=>[it,it.getBoundingClientRect()]));
/* Change layout */
leaving.forEach(it=>{it.hidden=true;gsap.set(it,{clearProps:'opacity,transform'})});
entering.forEach(it=>{it.hidden=false});
/* Last + Invert + Play */
const tl=gsap.timeline({onComplete:()=>{busy=null;ScrollTrigger.refresh()}});
busy=tl;
staying.forEach(it=>{
const a=first.get(it),b=it.getBoundingClientRect();
const dx=a.left-b.left,dy=a.top-b.top;
if(Math.abs(dx)<1&&Math.abs(dy)<1)return;
tl.fromTo(it,{x:dx,y:dy},{x:0,y:0,duration:.7,ease:'power3.inOut',clearProps:'transform'},0);
});
if(entering.length){
tl.fromTo(entering,{opacity:0,scale:.9,y:24},{opacity:1,scale:1,y:0,duration:.6,ease:'power3.out',stagger:.06,clearProps:'opacity,transform'},.15);
}
/* reset horizontal scroller on mobile */
const g=root.querySelector('.od-grid');
if(g.scrollLeft)g.scrollTo({left:0,behavior:'smooth'});
});
}
chips.forEach(c=>c.addEventListener('click',()=>{
if(c.getAttribute('aria-pressed')==='true')return;
apply(c.dataset.filter);
}));
/* ---------- marquee ---------- */
const mq=root.querySelector('.od-marquee');
const track=root.querySelector('.od-track');
const run=track.querySelector('.od-run');
/* clone enough copies to cover wide screens seamlessly */
const clone=run.cloneNode(true);track.appendChild(clone);
if(!hasG||R)return;
const loop=gsap.to(track,{xPercent:-50,duration:42,ease:'none',repeat:-1});
const skewTo=gsap.quickTo(track,'skewX',{duration:.5,ease:'power3.out'});
let hover=1,hoverT=1,vel=0;
mq.addEventListener('mouseenter',()=>{hoverT=0});
mq.addEventListener('mouseleave',()=>{hoverT=1});
ScrollTrigger.create({trigger:mq,start:'top bottom',end:'bottom top',
onUpdate:self=>{vel=self.getVelocity()},
onToggle:self=>{self.isActive?loop.play():loop.pause()}});
gsap.ticker.add(()=>{
hover+=(hoverT-hover)*.08;
vel*=.92;
const boost=Math.min(Math.abs(vel)/400,5);
loop.timeScale(hover*(1+boost));
skewTo(hover*gsap.utils.clamp(-8,8,-vel/300));
});
})();
</script>
</section>
<section class="op" id="planner" aria-labelledby="op-title">
<div class="wrap">
<div class="op-panel">
<div class="op-media" aria-hidden="true">
<img class="op-img" src="assets/plane.jpg" alt="" loading="lazy" decoding="async">
</div>
<div class="op-shade" aria-hidden="true"></div>
<div class="op-grid">
<div class="op-copy">
<p class="eyebrow op-eyebrow"><span class="dot"></span>Trip planner</p>
<h2 class="op-title" id="op-title">Find your next door</h2>
<p class="op-lede">Tell us how you like to travel. We'll pull three routes from our field notes — no bookings, no pop-ups, just ideas.</p>
</div>
<div class="op-card">
<form class="op-form" data-atom-form novalidate>
<div class="op-row op-row--two">
<label class="op-field">
<span class="op-label">From</span>
<input class="op-input" type="text" name="from" placeholder="Berlin" autocomplete="address-level2" maxlength="40">
</label>
<label class="op-field">
<span class="op-label">Where to</span>
<span class="op-select">
<select class="op-input" name="dest">
<option value="surprise">Anywhere surprising</option>
<option value="iceland">Iceland</option>
<option value="japan">Japan</option>
<option value="chile">Chile</option>
<option value="morocco">Morocco</option>
<option value="norway">Norway</option>
<option value="indonesia">Indonesia</option>
<option value="italy">Italy</option>
</select>
</span>
</label>
</div>
<fieldset class="op-field op-set">
<legend class="op-label">When</legend>
<div class="op-months">
<label class="op-chip"><input type="radio" name="month" value="0"><span>Jan</span></label>
<label class="op-chip"><input type="radio" name="month" value="1"><span>Feb</span></label>
<label class="op-chip"><input type="radio" name="month" value="2"><span>Mar</span></label>
<label class="op-chip"><input type="radio" name="month" value="3"><span>Apr</span></label>
<label class="op-chip"><input type="radio" name="month" value="4"><span>May</span></label>
<label class="op-chip"><input type="radio" name="month" value="5" checked><span>Jun</span></label>
<label class="op-chip"><input type="radio" name="month" value="6"><span>Jul</span></label>
<label class="op-chip"><input type="radio" name="month" value="7"><span>Aug</span></label>
<label class="op-chip"><input type="radio" name="month" value="8"><span>Sep</span></label>
<label class="op-chip"><input type="radio" name="month" value="9"><span>Oct</span></label>
<label class="op-chip"><input type="radio" name="month" value="10"><span>Nov</span></label>
<label class="op-chip"><input type="radio" name="month" value="11"><span>Dec</span></label>
</div>
</fieldset>
<div class="op-row op-row--two">
<div class="op-field">
<span class="op-label" id="op-trav-label">Travellers</span>
<div class="op-stepper" role="group" aria-labelledby="op-trav-label">
<button type="button" class="op-step" data-step="-1" aria-label="Fewer travellers">−</button>
<output class="op-count" name="travellers" aria-live="polite">2</output>
<button type="button" class="op-step" data-step="1" aria-label="More travellers">+</button>
</div>
</div>
<fieldset class="op-field op-set">
<legend class="op-label">Style</legend>
<div class="op-styles">
<label class="op-chip op-chip--wide"><input type="radio" name="style" value="Slow" checked><span>Slow</span></label>
<label class="op-chip op-chip--wide"><input type="radio" name="style" value="Wild"><span>Wild</span></label>
<label class="op-chip op-chip--wide"><input type="radio" name="style" value="Coastal"><span>Coastal</span></label>
<label class="op-chip op-chip--wide"><input type="radio" name="style" value="City"><span>City</span></label>
</div>
</fieldset>
</div>
<button type="submit" class="btn op-submit"><span class="op-submit-txt">Show my routes <span class="arr">→</span></span><span class="op-loading" aria-hidden="true">Pulling routes<i>.</i><i>.</i><i>.</i></span></button>
</form>
<div class="op-results" aria-live="polite" hidden>
<div class="op-results-in">
<div class="op-results-head">
<p class="op-results-title"></p>
<button type="button" class="op-reset">Start over</button>
</div>
<ol class="op-list"></ol>
</div>
</div>
</div>
</div>
</div>
<ul class="op-stats">
<li class="op-stat"><span class="op-num" data-to="42">42</span><span class="op-cap">countries logged</span></li>
<li class="op-stat"><span class="op-num" data-to="318">318</span><span class="op-cap">field notes</span></li>
<li class="op-stat"><span class="op-num" data-to="11">11</span><span class="op-cap">years on the road</span></li>
</ul>
</div>
<style>
.op{background:var(--bg);padding-block:clamp(64px,9vw,128px);overflow:hidden}
.op-panel{position:relative;border-radius:28px;overflow:hidden;background:var(--lilac);transform-origin:50% 0;isolation:isolate}
.op-media{position:absolute;inset:0;overflow:hidden;z-index:-2}
.op-img{position:absolute;left:0;top:-12%;width:100%;height:124%;max-width:none;object-fit:cover;will-change:transform}
.op-shade{position:absolute;inset:0;z-index:-1;pointer-events:none;
background:linear-gradient(100deg,color-mix(in srgb,var(--plum-deep) 62%,transparent) 0%,color-mix(in srgb,var(--plum) 28%,transparent) 48%,transparent 75%)}
.op-grid{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,500px);gap:clamp(28px,4vw,64px);align-items:start;
padding:clamp(28px,5vw,72px);min-height:720px}
.op-copy{color:#fff;max-width:560px;padding-top:clamp(4px,2vw,24px)}
.op-eyebrow{color:#fff;margin:0 0 20px}
.op-title{color:#fff;font-size:clamp(48px,7.4vw,112px);font-weight:500}
.op-lede{margin:24px 0 0;font-size:clamp(16px,1.3vw,18px);line-height:1.6;color:#fff;max-width:440px;
text-shadow:0 1px 12px color-mix(in srgb,var(--plum-deep) 45%,transparent)}
/* card + form */
.op-card{background:var(--surface);border-radius:22px;padding:clamp(20px,2.4vw,32px);
box-shadow:0 30px 60px -30px color-mix(in srgb,var(--plum-deep) 45%,transparent)}
.op-form{display:grid;gap:20px}
.op-row--two{display:grid;grid-template-columns:1fr 1fr;gap:16px}
.op-field{display:grid;gap:8px;min-width:0}
.op-set{border:0;margin:0;padding:0}
.op-label{font-family:var(--mono);font-size:11px;letter-spacing:.08em;text-transform:uppercase;color:var(--mute);padding:0;margin-bottom:8px}
label.op-field .op-label,div.op-field .op-label{margin-bottom:0}
.op-input{width:100%;min-height:48px;padding:12px 14px;border:1px solid var(--line);border-radius:12px;background:var(--surface);
font-size:15px;color:var(--ink);transition:border-color .2s var(--ease),box-shadow .2s var(--ease)}
.op-input::placeholder{color:color-mix(in srgb,var(--mute) 70%,transparent)}
.op-input:focus{outline:none;border-color:var(--apricot);box-shadow:0 0 0 3px color-mix(in srgb,var(--apricot) 35%,transparent)}
.op-select{position:relative;display:block}
.op-select select{appearance:none;-webkit-appearance:none;padding-right:38px;cursor:pointer}
.op-select::after{content:"";position:absolute;right:16px;top:50%;width:8px;height:8px;border-right:1.5px solid var(--ink);border-bottom:1.5px solid var(--ink);
transform:translateY(-70%) rotate(45deg);pointer-events:none}
.op-months{display:grid;grid-template-columns:repeat(6,minmax(0,1fr));gap:6px}
.op-styles{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:6px}
.op-chip{position:relative;display:block;cursor:pointer}
.op-chip input{position:absolute;inset:0;opacity:0;margin:0;cursor:pointer}
.op-chip span{display:flex;align-items:center;justify-content:center;min-height:44px;padding:0 6px;border:1px solid var(--line);border-radius:999px;
font-size:13px;font-weight:600;color:var(--ink);background:var(--surface);transition:background .2s var(--ease),border-color .2s var(--ease),color .2s var(--ease)}
.op-chip:hover span{border-color:var(--plum)}
.op-chip input:checked+span{background:var(--plum);border-color:var(--plum);color:#fff}
.op-chip input:focus-visible+span{box-shadow:0 0 0 3px color-mix(in srgb,var(--apricot) 55%,transparent);border-color:var(--apricot)}
.op-stepper{display:flex;align-items:center;justify-content:space-between;min-height:48px;border:1px solid var(--line);border-radius:12px;padding:2px}
.op-step{width:44px;height:44px;border-radius:10px;font-size:20px;line-height:1;color:var(--plum);transition:background .2s var(--ease)}
.op-step:hover:not(:disabled){background:var(--lilac)}
.op-step:disabled{color:var(--line);cursor:not-allowed}
.op-count{font-family:var(--display);font-size:22px;min-width:2ch;text-align:center}
.op-submit{width:100%;justify-content:center;min-height:52px;font-size:15px;margin-top:4px;position:relative}
.op-loading{display:none}
.op-submit.is-loading .op-submit-txt{display:none}
.op-submit.is-loading .op-loading{display:inline}
.op-submit.is-loading{pointer-events:none}
.op-loading i{font-style:normal;animation:op-dot 1s infinite}
.op-loading i:nth-child(2){animation-delay:.15s}
.op-loading i:nth-child(3){animation-delay:.3s}
@keyframes op-dot{0%,100%{opacity:.2}50%{opacity:1}}
/* results */
.op-results{overflow:hidden}
.op-results-in{padding-top:24px;margin-top:24px;border-top:1px solid var(--line)}
.op-results-head{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:14px}
.op-results-title{margin:0;font-family:var(--mono);font-size:11px;letter-spacing:.08em;text-transform:uppercase;color:var(--mute)}
.op-reset{min-height:44px;padding:0 4px;font-size:14px;font-weight:600;color:var(--plum);text-decoration:underline;text-underline-offset:4px;
text-decoration-color:color-mix(in srgb,var(--plum) 35%,transparent);flex-shrink:0}
.op-reset:hover{text-decoration-color:var(--apricot)}
.op-list{list-style:none;margin:0;padding:0;display:grid;gap:10px}
.op-route{padding:16px 18px;border-radius:16px;background:color-mix(in srgb,var(--lilac) 60%,#fff)}
.op-route-meta{display:flex;flex-wrap:wrap;gap:6px 10px;align-items:center;font-family:var(--mono);font-size:11px;letter-spacing:.06em;text-transform:uppercase;color:var(--plum)}
.op-route-path{margin:8px 0 4px;font-family:var(--display);font-size:20px;line-height:1.15;letter-spacing:-.02em;color:var(--ink)}
.op-route-note{margin:0;font-size:14px;line-height:1.5;color:var(--mute)}
/* stats */
.op-stats{list-style:none;margin:clamp(40px,5vw,72px) 0 0;padding:0;display:grid;grid-template-columns:repeat(3,1fr)}
.op-stat{padding:8px clamp(16px,3vw,40px);border-left:1px solid var(--line);display:grid;gap:6px}
.op-stat:first-child{border-left:0;padding-left:0}
.op-num{font-family:var(--display);font-size:clamp(56px,7vw,104px);line-height:.9;letter-spacing:-.04em;color:var(--plum);font-variant-numeric:tabular-nums}
.op-cap{font-size:15px;color:var(--mute)}
@media (max-width:1024px){
.op-grid{grid-template-columns:minmax(0,1fr) minmax(0,440px)}
}
@media (max-width:860px){
.op-grid{grid-template-columns:1fr;min-height:0;padding:28px 20px 20px;gap:36px}
.op-media{bottom:auto;height:520px}
.op-media::after{content:"";position:absolute;inset:auto 0 0;height:45%;background:linear-gradient(to bottom,transparent,var(--lilac))}
.op-shade{bottom:auto;height:520px;background:linear-gradient(180deg,color-mix(in srgb,var(--plum-deep) 58%,transparent) 0%,color-mix(in srgb,var(--plum) 22%,transparent) 55%,transparent 90%)}
.op-copy{padding-top:8px}
}
@media (max-width:560px){
.op-panel{border-radius:22px}
.op-grid{padding:24px 14px 14px}
.op-row--two{grid-template-columns:1fr}
.op-card{padding:18px 16px}
.op-months{gap:5px}
.op-chip span{font-size:12px;padding:0 2px}
.op-styles{grid-template-columns:repeat(4,minmax(0,1fr))}
.op-stats{grid-template-columns:1fr}
.op-stat{border-left:0;border-top:1px solid var(--line);padding:18px 0}
.op-stat:first-child{border-top:0}
.op-results-head{flex-wrap:wrap}
}
@media (prefers-reduced-motion: reduce) and (update: none){
.op-loading i{animation:none;opacity:1}
.op-img{top:0;height:100%}
}
</style>
<script>
(function(){
const root=document.getElementById('planner');if(!root)return;
const R=window.OS&&OS.reduced;
const panel=root.querySelector('.op-panel'),img=root.querySelector('.op-img');
const form=root.querySelector('.op-form'),res=root.querySelector('.op-results');
const list=root.querySelector('.op-list'),rTitle=root.querySelector('.op-results-title');
const submit=root.querySelector('.op-submit'),count=root.querySelector('.op-count');
const steps=root.querySelectorAll('.op-step');
/* default month = the current month */
const cur=form.querySelector('input[name="month"][value="'+new Date().getMonth()+'"]');if(cur)cur.checked=true;
/* travellers stepper */
let trav=2;
function setTrav(n){trav=Math.max(1,Math.min(8,n));count.textContent=trav;count.value=trav;
steps[0].disabled=trav<=1;steps[1].disabled=trav>=8}
steps.forEach(b=>b.addEventListener('click',()=>setTrav(trav+ +b.dataset.step)));
setTrav(2);
/* route data: 3 ideas per destination */
const MONTHS=['January','February','March','April','May','June','July','August','September','October','November','December'];
const D={
iceland:{name:'Iceland',routes:[
{days:9,path:'Reykjavík → Vík → Höfn',how:'by rental car',note:'two nights on the black sand coast'},
{days:7,path:'Akureyri → Mývatn → Húsavík',how:'on the northern ring',note:'hot pools and a whale-watching morning'},
{days:6,path:'Stykkishólmur → Snæfellsjökull → Arnarstapi',how:'along the peninsula',note:'cliff walks under the glacier'}]},
japan:{name:'Japan',routes:[
{days:10,path:'Kyoto → Nara → Koyasan',how:'by local train',note:'a night in a temple lodging on the mountain'},
{days:8,path:'Kanazawa → Shirakawa-go → Takayama',how:'by bus through the Alps',note:'thatched farmhouses and morning markets'},
{days:9,path:'Onomichi → Shimanami Kaido → Matsuyama',how:'by bicycle over the islands',note:'six bridges and a hot spring finish'}]},
chile:{name:'Chile',routes:[
{days:12,path:'Puerto Natales → Torres del Paine → El Calafate',how:'by bus and on foot',note:'the W trek with refugio nights'},
{days:9,path:'San Pedro de Atacama → Salar → Valle de la Luna',how:'by 4×4',note:'salt flats by day, clear skies by night'},
{days:8,path:'Valparaíso → Pichilemu → Colchagua',how:'down the coast road',note:'painted hills, surf towns and small vineyards'}]},
morocco:{name:'Morocco',routes:[
{days:8,path:'Marrakech → Aït Benhaddou → Merzouga',how:'over the High Atlas',note:'one night in a desert camp'},
{days:7,path:'Fes → Chefchaouen → Tangier',how:'by train and shared taxi',note:'the blue town in the Rif, slow mornings'},
{days:6,path:'Essaouira → Sidi Kaouki → Taghazout',how:'along the Atlantic',note:'fish grills and windy beach walks'}]},
norway:{name:'Norway',routes:[
{days:10,path:'Bergen → Flåm → Geiranger',how:'by train, ferry and bus',note:'a fjord night with the window open'},
{days:9,path:'Svolvær → Reine → Å',how:'through the Lofoten islands',note:'red cabins on stilts and midnight light'},
{days:7,path:'Oslo → Rjukan → Stavanger',how:'across the Telemark',note:'a morning on Preikestolen before the crowds'}]},
indonesia:{name:'Indonesia',routes:[
{days:11,path:'Ubud → Sidemen → Amed',how:'by scooter and driver',note:'rice terraces, then reef snorkelling'},
{days:9,path:'Labuan Bajo → Komodo → Padar',how:'on a liveaboard boat',note:'three nights on deck under the stars'},
{days:10,path:'Yogyakarta → Borobudur → Bromo',how:'by overnight train',note:'a sunrise over the volcano rim'}]},
italy:{name:'Italy',routes:[
{days:8,path:'Bolzano → Val di Funes → Cortina',how:'through the Dolomites',note:'hut-to-hut days with long lunches'},
{days:7,path:'Naples → Procida → Amalfi',how:'by ferry and bus',note:'a quiet island night before the coast'},
{days:9,path:'Bologna → Modena → Parma',how:'by regional train',note:'market mornings and one very long dinner'}]}
};
const SURPRISE=['chile','chile','japan','japan','italy','norway','iceland','iceland','italy','morocco','morocco','indonesia'];
const STYLE={
Slow:{d:3,line:'Fewer stops, longer stays — we added three days to linger.'},
Wild:{d:1,line:'Add a trail day off the map and a night somewhere with no signal.'},
Coastal:{d:0,line:'Keep the water on one side: pick rooms that face the sea.'},
City:{d:-2,line:'Base yourself in town and do the rest as day trips.'}
};
function el(tag,cls,txt){const e=document.createElement(tag);if(cls)e.className=cls;if(txt!=null)e.textContent=txt;return e}
function build(){
const fd=new FormData(form);
const from=(fd.get('from')||'').toString().trim()||'Berlin';
let key=fd.get('dest')||'surprise';
const m=+(fd.get('month')||0),style=fd.get('style')||'Slow';
const surprised=key==='surprise';if(surprised)key=SURPRISE[m];
const d=D[key],s=STYLE[style];
const who=trav===1?'solo':trav+' travellers';
rTitle.textContent=(surprised?'Surprise: ':'')+d.name+' · '+MONTHS[m]+' · from '+from+' · '+who;
list.textContent='';
d.routes.forEach((r,i)=>{
const li=el('li','op-route');
const meta=el('div','op-route-meta');
meta.appendChild(el('span',null,'Route '+(i+1)));
meta.appendChild(el('span',null,'· '+Math.max(4,r.days+s.d)+' days'));
meta.appendChild(el('span',null,'· '+style));
li.appendChild(meta);
li.appendChild(el('p','op-route-path',r.path));
li.appendChild(el('p','op-route-note',cap(r.how)+', '+r.note+'. '+s.line));
list.appendChild(li);
});
}
function cap(t){return t.charAt(0).toUpperCase()+t.slice(1)}
function refresh(){if(window.ScrollTrigger)ScrollTrigger.refresh()}
function open(){
const wasOpen=!res.hidden;res.hidden=false;
if(R){refresh();return}
const cards=list.children;
if(!wasOpen){
gsap.fromTo(res,{height:0},{height:'auto',duration:.7,ease:'power3.inOut',onComplete:()=>{res.style.height='';refresh()}});
}
gsap.fromTo(cards,{y:18,opacity:0},{y:0,opacity:1,duration:.55,ease:'power3.out',stagger:.08,delay:wasOpen?0:.25,clearProps:'transform,opacity'});
}
function close(){
const done=()=>{res.hidden=true;res.style.height='';list.textContent='';refresh()};
form.reset();setTrav(2);if(cur)cur.checked=true;
form.querySelector('.op-input').focus({preventScroll:true});
if(R){done();return}
gsap.to(res,{height:0,duration:.5,ease:'power3.inOut',onComplete:done});
}
form.addEventListener('submit',e=>{
e.preventDefault();
if(submit.classList.contains('is-loading'))return;
submit.classList.add('is-loading');submit.setAttribute('aria-busy','true');
setTimeout(()=>{
build();submit.classList.remove('is-loading');submit.removeAttribute('aria-busy');open();
},R?0:600);
});
root.querySelector('.op-reset').addEventListener('click',close);
/* headline reveal */
const title=root.querySelector('.op-title');
if(window.OS)OS.reveal(title);
if(R||!window.gsap)return;
/* panel grows from ~88% to full wrap width */
const mm=gsap.matchMedia();
mm.add({desk:'(min-width:861px)',mob:'(max-width:860px)'},ctx=>{
gsap.fromTo(panel,{scale:ctx.conditions.desk?.88:.94},{scale:1,ease:'none',
scrollTrigger:{trigger:root,start:'top bottom',end:'top 15%',scrub:true}});
});
/* photo parallax inside the panel */
gsap.fromTo(img,{yPercent:-8},{yPercent:8,ease:'none',
scrollTrigger:{trigger:panel,start:'top bottom',end:'bottom top',scrub:true}});
/* stats count up */
root.querySelectorAll('.op-num').forEach(n=>{
const to=+n.dataset.to,o={v:0};n.textContent='0';
gsap.to(o,{v:to,duration:1.8,ease:'power2.out',
onUpdate:()=>{n.textContent=Math.round(o.v)},
scrollTrigger:{trigger:n,start:'top 88%',once:true}});
});
})();
</script>
</section>
<section class="os os-story" id="journal" aria-labelledby="os-story-title">
<style>
.os-story{background:var(--surface);padding-block:clamp(72px,11vw,160px);position:relative}
.os-story__feat{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:clamp(32px,6vw,110px);align-items:start}
/* --- sticky media --- */
.os-story__media{position:sticky;top:12vh;height:76vh;min-height:420px;border-radius:calc(var(--radius) + 10px);overflow:hidden;background:var(--lilac);isolation:isolate}
.os-story__media img{width:100%;height:100%;object-fit:cover;transform-origin:50% 50%;will-change:transform}
.os-story__chip{position:absolute;left:18px;bottom:18px;z-index:2;display:inline-flex;align-items:center;gap:.5em;padding:9px 14px;border-radius:999px;background:#fff;color:var(--ink);
font-family:var(--mono);font-size:11px;letter-spacing:.06em;text-transform:uppercase;box-shadow:0 8px 24px color-mix(in srgb,var(--plum) 18%,transparent)}
.os-story__chip .dot{margin-right:0}
/* --- text column --- */
.os-story__text{padding-top:clamp(0px,6vh,80px);max-width:640px}
.os-story__text .eyebrow{display:block;margin-bottom:22px}
.os-story__h2{font-size:clamp(36px,5.6vw,84px);color:var(--ink)}
.os-story__h2 em{font-style:normal;color:var(--plum)}
.os-story__by{display:flex;align-items:center;flex-wrap:wrap;gap:10px 16px;margin:34px 0 40px;padding-bottom:26px;border-bottom:1px solid var(--line);font-size:14px;color:var(--mute)}
.os-story__av{width:44px;height:44px;border-radius:50%;background:var(--lilac);color:var(--plum);display:grid;place-items:center;font-family:var(--display);font-weight:700;font-size:15px;letter-spacing:0;flex:none}
.os-story__name{color:var(--ink);font-weight:600}
.os-story__sep{width:4px;height:4px;border-radius:50%;background:var(--line)}
.os-story__p{font-size:clamp(16px,1.25vw,18px);line-height:1.75;color:color-mix(in srgb,var(--ink) 88%,var(--mute));margin:0 0 1.35em}
.os-story__p:first-of-type::first-letter{font-family:var(--display);float:left;font-size:4.1em;line-height:.82;padding:.06em .1em 0 0;color:var(--plum)}
.os-story__quote{position:relative;margin:clamp(40px,6vh,72px) 0;padding:0 0 0 clamp(8px,1vw,14px)}
.os-story__quote-mark{display:block;font-family:var(--display);font-size:clamp(90px,9vw,140px);line-height:.6;height:.42em;color:var(--apricot);font-weight:700}
.os-story__quote p{margin:0;font-family:var(--display);font-size:clamp(28px,3.2vw,46px);line-height:1.08;letter-spacing:-.025em;color:var(--plum)}
.os-story__quote cite{display:block;margin-top:18px;font-style:normal;font-family:var(--mono);font-size:12px;letter-spacing:.08em;text-transform:uppercase;color:var(--mute)}
.os-story__facts{margin:0 0 40px;border-top:1px solid var(--line)}
.os-story__fact{display:grid;grid-template-columns:minmax(120px,38%) 1fr;gap:16px;padding:16px 0;border-bottom:1px solid var(--line)}
.os-story__fact dt{font-family:var(--mono);font-size:12px;letter-spacing:.08em;text-transform:uppercase;color:var(--mute);padding-top:3px}
.os-story__fact dd{margin:0;font-weight:600;color:var(--ink)}
/* --- latest notes --- */
.os-story__latest{margin-top:clamp(96px,13vw,190px);padding-top:clamp(40px,5vw,64px);border-top:1px solid var(--line)}
.os-story__head{display:flex;align-items:flex-end;justify-content:space-between;gap:20px 32px;flex-wrap:wrap;margin-bottom:clamp(32px,4vw,56px)}
.os-story__head .eyebrow{display:block;margin-bottom:14px}
.os-story__h3{font-size:clamp(32px,4vw,56px)}
.os-story__grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:clamp(20px,2.4vw,36px);list-style:none;margin:0;padding:0}
.os-story__card{perspective:900px}
.os-story__link{display:block;text-decoration:none;color:inherit;transform-style:preserve-3d;will-change:transform}
.os-story__img{position:relative;aspect-ratio:4/3;border-radius:var(--radius);overflow:hidden;background:var(--lilac);
box-shadow:0 1px 0 color-mix(in srgb,var(--ink) 4%,transparent);transition:box-shadow .5s var(--ease)}
.os-story__img img{width:100%;height:100%;object-fit:cover;transition:transform .9s var(--ease)}
.os-story__img .tag{position:absolute;top:14px;left:14px;z-index:1}
.os-story__card-body{padding:20px 4px 0}
.os-story__card-title{font-family:var(--display);font-size:clamp(21px,1.7vw,26px);line-height:1.12;letter-spacing:-.02em;margin:0 0 12px;font-weight:500}
.os-story__card-title span{background:linear-gradient(var(--plum),var(--plum)) 0 100%/0 2px no-repeat;padding-bottom:2px;transition:background-size .55s var(--ease)}
.os-story__meta{font-family:var(--mono);font-size:12px;letter-spacing:.06em;text-transform:uppercase;color:var(--mute)}
.os-story__link:hover .os-story__img img,.os-story__link:focus-visible .os-story__img img{transform:scale(1.07)}
.os-story__link:hover .os-story__img,.os-story__link:focus-visible .os-story__img{box-shadow:0 26px 48px -22px color-mix(in srgb,var(--plum) 45%,transparent)}
.os-story__link:hover .os-story__card-title span,.os-story__link:focus-visible .os-story__card-title span{background-size:100% 2px}
@media (max-width:900px){
.os-story__feat{grid-template-columns:minmax(0,1fr);gap:36px}
.os-story__media{position:relative;top:auto;height:auto;min-height:0;aspect-ratio:4/5}
.os-story__text{padding-top:0;max-width:none}
.os-story__grid{grid-template-columns:minmax(0,1fr);gap:40px}
}
@media (max-width:480px){
.os-story .wrap{padding-inline:16px}
.os-story__fact{grid-template-columns:1fr;gap:4px}
.os-story__head .btn{width:100%;justify-content:center}
}
@media (prefers-reduced-motion: reduce) and (update: none){
.os-story__img img,.os-story__card-title span,.os-story__img{transition:none}
.os-story__link:hover .os-story__img img{transform:none}
}
</style>
<div class="wrap">
<article class="os-story__feat">
<figure class="os-story__media" style="margin:0">
<img src="assets/story.jpg" alt="Green aurora ribbons over a snowy valley at night" loading="lazy" width="1200" height="1500">
<figcaption class="os-story__chip"><span class="dot" aria-hidden="true"></span>Abisko, Swedish Lapland · 68°N</figcaption>
</figure>
<div class="os-story__text">
<span class="eyebrow"><span class="dot" aria-hidden="true"></span>Field note 118 · Featured</span>
<h2 class="os-story__h2" id="os-story-title">Nine nights under the aurora, and the one that <em>stayed quiet</em></h2>
<div class="os-story__by os-story__rise">
<span class="os-story__av" aria-hidden="true">MK</span>
<span class="os-story__name">Mara Kovač</span>
<span class="os-story__sep" aria-hidden="true"></span>
<span>8 min read</span>
<span class="os-story__sep" aria-hidden="true"></span>
<time datetime="2026-02">Feb 2026</time>
</div>
<p class="os-story__p os-story__rise">The cabin was a ten-minute walk from the station, which at minus twenty-six is long enough to stop feeling your eyelashes. I had booked nine nights because every forecast I read said the odds get better the longer you stay. What nobody tells you is how much of the waiting happens indoors, next to a kettle that never quite stops ticking.</p>
<p class="os-story__p os-story__rise">Most evenings went the same way. Wool socks, a thermos of tea, the torch switched to red, a slow walk down to the frozen lake. Some nights the sky gave everything: green curtains folding over each other, pale pink at the hems, so bright the snow threw shadows. Some nights it gave a faint grey smudge that I only believed in once the camera confirmed it.</p>
<p class="os-story__p os-story__rise">Night six was the clearest of the trip. No cloud, no moon, stars down to the horizon. I stood on the ice for three hours and nothing happened. Not a flicker. I walked back feeling oddly cheated, as if the weather had broken a promise it never made.</p>
<blockquote class="os-story__quote os-story__rise">
<span class="os-story__quote-mark" aria-hidden="true">“</span>
<p>The sky owes you nothing. That's exactly why it's worth waiting for.</p>
<cite>From the notebook, night six</cite>
</blockquote>
<p class="os-story__p os-story__rise">It took me until the train home to understand that the quiet night was the best one. It was the night I stopped chasing and simply watched: the cold, the silence, a village of a few hundred people asleep under a sky the size of a continent. Slow travel is not a style. It is agreeing to be somewhere long enough that the place can surprise you, including by doing nothing at all.</p>
<dl class="os-story__facts os-story__rise">
<div class="os-story__fact"><dt>Getting there</dt><dd>Night train from Stockholm, 17 h</dd></div>
<div class="os-story__fact"><dt>Stay</dt><dd>3 nights minimum</dd></div>
<div class="os-story__fact"><dt>Best window</dt><dd>Late Jan – mid Mar</dd></div>
</dl>
<a class="btn os-story__rise" href="#journal">Read the full note <span class="arr" aria-hidden="true">→</span></a>
</div>
</article>
<div class="os-story__latest">
<div class="os-story__head">
<div>
<span class="eyebrow"><span class="dot" aria-hidden="true"></span>From the journal</span>
<h2 class="os-story__h3">Latest notes</h2>
</div>
<a class="btn btn--line" href="#journal">The whole journal <span class="arr" aria-hidden="true">→</span></a>
</div>
<ul class="os-story__grid">
<li class="os-story__card">
<a class="os-story__link" href="#journal">
<div class="os-story__img"><span class="tag">Hiking</span><img src="assets/a1.jpg" alt="A narrow mountain ridge trail under low cloud" loading="lazy" width="800" height="600"></div>
<div class="os-story__card-body">
<h3 class="os-story__card-title"><span>The ridge walk nobody puts on a postcard</span></h3>
<p class="os-story__meta" style="margin:0">Jan 2026 · 6 min</p>
</div>
</a>
</li>
<li class="os-story__card">
<a class="os-story__link" href="#journal">
<div class="os-story__img"><span class="tag">Rail</span><img src="assets/a2.jpg" alt="A sleeper train carriage window at dusk" loading="lazy" width="800" height="600"></div>
<div class="os-story__card-body">
<h3 class="os-story__card-title"><span>Sleeper trains are back, and they're slow on purpose</span></h3>
<p class="os-story__meta" style="margin:0">Dec 2025 · 5 min</p>
</div>
</a>
</li>
<li class="os-story__card">
<a class="os-story__link" href="#journal">
<div class="os-story__img"><span class="tag">Food</span><img src="assets/a3.jpg" alt="Lantern-lit food stalls at a busy night market" loading="lazy" width="800" height="600"></div>
<div class="os-story__card-body">
<h3 class="os-story__card-title"><span>Eating our way through one night market, stall by stall</span></h3>
<p class="os-story__meta" style="margin:0">Nov 2025 · 7 min</p>
</div>
</a>
</li>
</ul>
</div>
</div>
<script>
(function(){
var root=document.getElementById('journal');
if(!root||!window.OS)return;
var q=function(s){return root.querySelectorAll(s)};
OS.reveal(root.querySelector('.os-story__h2'));
OS.reveal(root.querySelector('.os-story__h3'));
if(OS.reduced)return;
var mm=gsap.matchMedia();
var media=root.querySelector('.os-story__media');
var img=media.querySelector('img');
var chip=media.querySelector('.os-story__chip');
/* clip-path wipe on enter */
gsap.set(media,{clipPath:'inset(100% 0% 0% 0% round 28px)'});
gsap.set(chip,{autoAlpha:0,y:12});
gsap.timeline({scrollTrigger:{trigger:media,start:'top 82%',once:true}})
.to(media,{clipPath:'inset(0% 0% 0% 0% round 28px)',duration:1.4,ease:'power4.inOut'})
.to(chip,{autoAlpha:1,y:0,duration:.6,ease:'power3.out'},'-=.35');
/* scrubbed scale / parallax */
mm.add('(min-width:901px)',function(){
gsap.fromTo(img,{scale:1.15,yPercent:-3},{scale:1,yPercent:3,ease:'none',
scrollTrigger:{trigger:root.querySelector('.os-story__feat'),start:'top 80%',end:'bottom 20%',scrub:true}});
});
mm.add('(max-width:900px)',function(){
gsap.fromTo(img,{scale:1.15},{scale:1,ease:'none',
scrollTrigger:{trigger:media,start:'top bottom',end:'bottom top',scrub:true}});
});
/* paragraphs, quote, facts rise in */
q('.os-story__rise').forEach(function(el){
gsap.from(el,{autoAlpha:0,y:36,duration:1,ease:'power3.out',
scrollTrigger:{trigger:el,start:'top 88%',once:true}});
});
/* cards stagger in */
gsap.from(q('.os-story__card'),{autoAlpha:0,y:60,duration:1.1,ease:'power3.out',stagger:.12,
scrollTrigger:{trigger:root.querySelector('.os-story__grid'),start:'top 85%',once:true}});
/* pointer tilt (fine pointers only) */
mm.add('(hover:hover) and (pointer:fine)',function(){
var off=[];
q('.os-story__link').forEach(function(card){
var rx=gsap.quickTo(card,'rotationX',{duration:.6,ease:'power3.out'});
var ry=gsap.quickTo(card,'rotationY',{duration:.6,ease:'power3.out'});
function move(e){
var r=card.getBoundingClientRect();
var px=(e.clientX-r.left)/r.width-.5, py=(e.clientY-r.top)/r.height-.5;
ry(px*12); rx(-py*12);
}
function leave(){rx(0);ry(0)}
card.addEventListener('pointermove',move);
card.addEventListener('pointerleave',leave);
off.push(function(){card.removeEventListener('pointermove',move);card.removeEventListener('pointerleave',leave);gsap.set(card,{rotationX:0,rotationY:0})});
});
return function(){off.forEach(function(f){f()})};
});
})();
</script>
</section>
<section class="om" id="moments" aria-labelledby="om-title">
<div class="om-word" aria-hidden="true">moments</div>
<div class="om-track">
<div class="om-intro">
<p class="eyebrow"><span class="dot"></span>Moments</p>
<h2 class="om-title" id="om-title">Moments worth passing on</h2>
<p class="om-copy">Small things from the road that stuck with us. Send one to someone who needs a door opened.</p>
<p class="om-count" aria-live="polite"><span class="om-count-now">01</span> <span class="om-count-sep">/</span> 06</p>
<p class="om-hint" aria-hidden="true">Keep scrolling <span class="om-hint-arr">→</span></p>
</div>
<div class="om-rail" role="list">
<article class="om-card" role="listitem">
<div class="om-media"><img src="assets/m1.jpg" alt="A swimmer floating in clear turquoise water in the early morning" loading="lazy" decoding="async"></div>
<p class="om-cap">Floating off Lampedusa, 7 am</p>
<button class="om-share" type="button" aria-label="Share this moment"><svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="18" cy="5" r="2.6"/><circle cx="6" cy="12" r="2.6"/><circle cx="18" cy="19" r="2.6"/><path d="M8.3 10.8l7.4-4.4M8.3 13.2l7.4 4.4"/></svg></button>
</article>
<article class="om-card" role="listitem">
<div class="om-media"><img src="assets/m2.jpg" alt="Hot air balloons drifting over a valley at sunrise" loading="lazy" decoding="async"></div>
<p class="om-cap">Balloons over the valley, first light</p>
<button class="om-share" type="button" aria-label="Share this moment"><svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="18" cy="5" r="2.6"/><circle cx="6" cy="12" r="2.6"/><circle cx="18" cy="19" r="2.6"/><path d="M8.3 10.8l7.4-4.4M8.3 13.2l7.4 4.4"/></svg></button>
</article>
<article class="om-card" role="listitem">
<div class="om-media"><img src="assets/m3.jpg" alt="A camper van parked on an empty desert highway" loading="lazy" decoding="async"></div>
<p class="om-cap">Route 62, van, no signal</p>
<button class="om-share" type="button" aria-label="Share this moment"><svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="18" cy="5" r="2.6"/><circle cx="6" cy="12" r="2.6"/><circle cx="18" cy="19" r="2.6"/><path d="M8.3 10.8l7.4-4.4M8.3 13.2l7.4 4.4"/></svg></button>
</article>
<article class="om-card" role="listitem">
<div class="om-media"><img src="assets/m4.jpg" alt="Two lit tents under a sky full of stars" loading="lazy" decoding="async"></div>
<p class="om-cap">Two tents, a million stars</p>
<button class="om-share" type="button" aria-label="Share this moment"><svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="18" cy="5" r="2.6"/><circle cx="6" cy="12" r="2.6"/><circle cx="18" cy="19" r="2.6"/><path d="M8.3 10.8l7.4-4.4M8.3 13.2l7.4 4.4"/></svg></button>
</article>
<article class="om-card" role="listitem">
<div class="om-media"><img src="assets/m5.jpg" alt="A kayaker paddling beneath a towering glacier wall" loading="lazy" decoding="async"></div>
<p class="om-cap">Paddling under the glacier face</p>
<button class="om-share" type="button" aria-label="Share this moment"><svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="18" cy="5" r="2.6"/><circle cx="6" cy="12" r="2.6"/><circle cx="18" cy="19" r="2.6"/><path d="M8.3 10.8l7.4-4.4M8.3 13.2l7.4 4.4"/></svg></button>
</article>
<article class="om-card" role="listitem">
<div class="om-media"><img src="assets/m6.jpg" alt="Friends on a rooftop looking over city lights at dusk" loading="lazy" decoding="async"></div>
<p class="om-cap">Rooftop, last night in the city</p>
<button class="om-share" type="button" aria-label="Share this moment"><svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="18" cy="5" r="2.6"/><circle cx="6" cy="12" r="2.6"/><circle cx="18" cy="19" r="2.6"/><path d="M8.3 10.8l7.4-4.4M8.3 13.2l7.4 4.4"/></svg></button>
</article>
</div>
</div>
<div class="om-bar" aria-hidden="true"><span class="om-bar-fill"></span></div>
<div class="om-toast" role="status" aria-live="polite"></div>
</section>
<style>
/* ---------- 05 Moments: pinned horizontal strip ---------- */
.om{position:relative;background:var(--bg);overflow:hidden;padding:clamp(64px,10vw,120px) 0 clamp(56px,8vw,96px);isolation:isolate}
.om-word{position:absolute;left:var(--gut);top:clamp(40px,8vw,90px);z-index:-1;pointer-events:none;user-select:none;
font-family:var(--display);font-weight:700;letter-spacing:-.05em;line-height:.8;white-space:nowrap;
font-size:34vw;color:transparent;-webkit-text-stroke:1px var(--plum);opacity:.35;will-change:transform}
/* base = mobile / reduced motion: intro stacked, native scroll-snap rail */
.om-track{position:relative}
.om-intro{padding-inline:var(--gut);max-width:560px}
.om-title{font-size:clamp(40px,9vw,64px);margin:14px 0 16px}
.om-copy{color:var(--mute);margin:0 0 20px;max-width:34ch}
.om-count{font-family:var(--mono);font-size:13px;letter-spacing:.06em;color:var(--ink);margin:0}
.om-count-now{display:inline-block;min-width:2ch;color:var(--plum);font-weight:500}
.om-count-sep{color:var(--mute)}
.om-hint{display:none}
.om-rail{display:flex;gap:14px;margin-top:32px;padding:4px var(--gut) 12px;overflow-x:auto;overscroll-behavior-x:contain;
scroll-snap-type:x mandatory;scroll-padding-inline:var(--gut);scrollbar-width:none;-webkit-overflow-scrolling:touch}
.om-rail::-webkit-scrollbar{display:none}
.om-card{position:relative;flex:0 0 72vw;max-width:340px;aspect-ratio:3/4.3;border-radius:22px;overflow:hidden;
background:var(--lilac);scroll-snap-align:start;box-shadow:0 18px 40px -26px color-mix(in srgb,var(--plum) 55%,transparent)}
.om-media{position:absolute;inset:0;overflow:hidden}
.om-media img{width:100%;height:100%;object-fit:cover;transform-origin:50% 50%;will-change:transform}
.om-cap{position:absolute;left:12px;bottom:12px;right:64px;margin:0;width:max-content;max-width:calc(100% - 24px);
padding:7px 11px;border-radius:999px;background:#fff;color:var(--ink);
font-family:var(--mono);font-size:11px;line-height:1.3;letter-spacing:.02em}
.om-share{position:absolute;top:12px;right:12px;width:42px;height:42px;border-radius:50%;display:grid;place-items:center;
background:var(--plum);color:#fff;transition:background .25s var(--ease),transform .25s var(--ease)}
.om-share svg{width:18px;height:18px;fill:none;stroke:currentColor;stroke-width:1.8;stroke-linecap:round}
.om-share:hover{background:var(--plum-deep);transform:scale(1.08)}
.om-share.is-done{background:var(--apricot);color:var(--ink)}
.om-bar{position:relative;height:3px;margin:22px var(--gut) 0;border-radius:3px;overflow:hidden;
background:color-mix(in srgb,var(--plum) 14%,transparent)}
.om-bar-fill{position:absolute;inset:0;background:var(--apricot);transform:scaleX(0);transform-origin:0 50%}
.om-toast{position:fixed;left:50%;bottom:28px;z-index:60;translate:-50% 0;pointer-events:none;
padding:11px 18px;border-radius:999px;background:var(--surface);color:var(--ink);
font-family:var(--mono);font-size:12px;letter-spacing:.03em;white-space:nowrap;
box-shadow:0 0 0 1px var(--line),0 14px 30px -14px color-mix(in srgb,var(--plum) 45%,transparent);
opacity:0;transform:translateY(16px);transition:opacity .35s var(--ease),transform .35s var(--ease)}
.om-toast.is-on{opacity:1;transform:translateY(0)}
.om-toast::before{content:"";display:inline-block;width:7px;height:7px;border-radius:50%;background:var(--apricot);margin-right:.6em;vertical-align:middle}
/* desktop pinned strip — same condition as the JS matchMedia */
@media (min-width:900px) and (min-width: 0px){
.om{height:100vh;height:100svh;padding:0;display:flex;flex-direction:column;justify-content:center}
.om-word{top:50%;left:6vw;font-size:28vw;translate:0 -54%}
.om-track{display:flex;align-items:center;width:max-content;padding-left:var(--gut);padding-right:8vw;will-change:transform}
.om-intro{flex:0 0 auto;width:clamp(320px,30vw,460px);padding:0;margin-right:5vw;max-width:none}
.om-title{font-size:clamp(48px,5.4vw,88px)}
.om-count{font-size:14px;margin-top:8px}
.om-hint{display:block;margin:28px 0 0;font-family:var(--mono);font-size:11px;letter-spacing:.08em;text-transform:uppercase;color:var(--mute)}
.om-hint-arr{display:inline-block;animation:om-nudge 1.6s var(--ease) infinite}
.om-rail{display:flex;align-items:flex-start;gap:2.4vw;margin:0;padding:0;overflow:visible;scroll-snap-type:none}
.om-card{flex:0 0 auto;width:min(26vw,calc((100svh - 190px) * 3 / 4.3));max-width:none}
.om-card:nth-of-type(even){margin-top:6vh}
.om-cap{left:16px;bottom:16px;font-size:12px}
.om-share{top:16px;right:16px;width:46px;height:46px}
.om-bar{position:absolute;left:var(--gut);right:var(--gut);bottom:clamp(28px,5vh,52px);margin:0}
}
@keyframes om-nudge{0%,100%{transform:translateX(0)}50%{transform:translateX(5px)}}
@media (prefers-reduced-motion: reduce) and (update: none){
.om-toast{transition:opacity .2s linear;transform:none}
}
</style>
<script>
(function(){
const sec=document.getElementById('moments');
if(!sec)return;
const OSx=window.OS||{};
const track=sec.querySelector('.om-track');
const rail=sec.querySelector('.om-rail');
const cards=[...sec.querySelectorAll('.om-card')];
const word=sec.querySelector('.om-word');
const fill=sec.querySelector('.om-bar-fill');
const now=sec.querySelector('.om-count-now');
const toast=sec.querySelector('.om-toast');
const title=sec.querySelector('.om-title');
const pad=n=>String(n).padStart(2,'0');
if(OSx.reveal&&title)OSx.reveal(title);
/* which card is nearest the viewport centre */
let last=-1;
function syncCount(){
const mid=innerWidth/2;let best=0,bd=Infinity;
cards.forEach((c,i)=>{const r=c.getBoundingClientRect();const d=Math.abs(r.left+r.width/2-mid);if(d<bd){bd=d;best=i}});
if(best!==last){last=best;now.textContent=pad(best+1)}
}
/* ---------- share: copy to clipboard + toast ---------- */
let tt;
function say(msg){
toast.textContent=msg;toast.classList.add('is-on');
clearTimeout(tt);tt=setTimeout(()=>toast.classList.remove('is-on'),2200);
}
function legacyCopy(text){
try{const ta=document.createElement('textarea');ta.value=text;ta.setAttribute('readonly','');
ta.style.cssText='position:fixed;top:0;left:0;opacity:0;pointer-events:none';
document.body.appendChild(ta);ta.select();const ok=document.execCommand&&document.execCommand('copy');ta.remove();return !!ok}
catch(e){return false}
}
sec.addEventListener('click',e=>{
const btn=e.target.closest('.om-share');if(!btn)return;
const cap=btn.closest('.om-card').querySelector('.om-cap').textContent.trim();
const text='Otherside — '+cap;
const done=ok=>{
if(ok){say('Copied — pass it on');btn.classList.add('is-done');setTimeout(()=>btn.classList.remove('is-done'),1400)}
else say('Copy isn’t available here');
};
if(navigator.clipboard&&window.isSecureContext&&navigator.clipboard.writeText){
navigator.clipboard.writeText(text).then(()=>done(true),()=>done(legacyCopy(text)));
}else done(legacyCopy(text));
});
/* ---------- mobile / reduced: native rail drives count + bar ---------- */
function railSync(){
const max=rail.scrollWidth-rail.clientWidth;
fill.style.transform='scaleX('+(max>0?rail.scrollLeft/max:0)+')';
syncCount();
}
let raf=0;
function onRail(){if(raf)return;raf=requestAnimationFrame(()=>{raf=0;railSync()})}
if(!window.gsap||!window.ScrollTrigger){rail.addEventListener('scroll',onRail,{passive:true});railSync();return}
const mm=gsap.matchMedia();
mm.add('(min-width:900px) and (min-width: 0px)',()=>{
const dist=()=>Math.max(0,track.scrollWidth-innerWidth);
const tl=gsap.timeline({
defaults:{ease:'none'},
scrollTrigger:{
trigger:sec,start:'top top',end:()=>'+='+dist(),
pin:true,scrub:1,invalidateOnRefresh:true,anticipatePin:1,
onUpdate:syncCount,onRefresh:syncCount
}
});
tl.fromTo(track,{x:0},{x:()=>-dist(),duration:1},0)
.fromTo(word,{x:0},{x:()=>-dist()*0.32,duration:1},0)
.fromTo(fill,{scaleX:0},{scaleX:1,duration:1},0);
cards.forEach(card=>{
gsap.fromTo(card.querySelector('img'),{scale:1.12},{scale:1,ease:'none',
scrollTrigger:{trigger:card,containerAnimation:tl,start:'left right',end:'right left',scrub:true}});
});
return ()=>{last=-1;now.textContent='01'};
});
mm.add('(max-width:899.98px), (prefers-reduced-motion: reduce) and (update: none)',()=>{
rail.addEventListener('scroll',onRail,{passive:true});
railSync();
return ()=>{rail.removeEventListener('scroll',onRail);fill.style.transform=''};
});
})();
</script>
</main>
<footer class="of" id="contact">
<div class="wrap of-inner">
<!-- newsletter -->
<div class="of-news">
<div class="of-news-head">
<p class="eyebrow of-eyebrow"><span class="dot"></span>Postcards</p>
<h2 class="of-title">Postcards, not spam</h2>
</div>
<div class="of-news-body">
<p class="of-copy">One letter a month: a new place, a slow route, and one thing we got wrong. Unsubscribe with one click.</p>
<form class="of-form" data-atom-form novalidate>
<div class="of-field">
<label class="of-sr" for="of-email">Email address</label>
<input class="of-input" id="of-email" name="email" type="email" required autocomplete="email" placeholder="[email protected]" aria-describedby="of-status">
<button class="btn of-submit" type="submit">Subscribe <span class="arr">→</span></button>
</div>
<p class="of-status" id="of-status" role="status" aria-live="polite"></p>
<div class="of-done" hidden>
<svg class="of-check" viewBox="0 0 40 40" aria-hidden="true">
<circle class="of-check-ring" cx="20" cy="20" r="17"/>
<path class="of-check-tick" d="M12.5 20.5l5 5 10-11"/>
</svg>
<p class="of-done-msg">You're on the list. First postcard lands next month.</p>
</div>
</form>
</div>
</div>
<!-- link columns -->
<nav class="of-cols" aria-label="Footer">
<div class="of-col">
<p class="eyebrow of-colhead">Destinations</p>
<ul class="of-list">
<li><a class="of-link" href="#destinations">Iceland</a></li>
<li><a class="of-link" href="#destinations">Japan</a></li>
<li><a class="of-link" href="#destinations">Chile</a></li>
<li><a class="of-link" href="#destinations">Morocco</a></li>
<li><a class="of-link" href="#destinations">Norway</a></li>
<li><a class="of-link" href="#destinations">Italy</a></li>
</ul>
</div>
<div class="of-col">
<p class="eyebrow of-colhead">Journal</p>
<ul class="of-list">
<li><a class="of-link" href="#journal">Field notes</a></li>
<li><a class="of-link" href="#journal">Rail</a></li>
<li><a class="of-link" href="#journal">Food</a></li>
<li><a class="of-link" href="#journal">Hiking</a></li>
</ul>
</div>
<div class="of-col">
<p class="eyebrow of-colhead">Otherside</p>
<ul class="of-list">
<li><a class="of-link" href="#about">About</a></li>
<li><a class="of-link" href="#about">Our rules for writing</a></li>
<li><a class="of-link" href="#contact">Work with us</a></li>
<li><a class="of-link" href="#contact">Press</a></li>
</ul>
</div>
<div class="of-col">
<p class="eyebrow of-colhead">Say hello</p>
<ul class="of-list">
<li><a class="of-link" href="mailto:[email protected]">[email protected]</a></li>
<li><a class="of-link" href="#contact">Instagram</a></li>
</ul>
<p class="of-note">Replies within two days, from a human.</p>
</div>
</nav>
</div>
<!-- giant wordmark -->
<div class="of-mark" aria-label="Otherside">
<div class="of-mark-line" aria-hidden="true">Otherside</div>
</div>
<!-- bottom bar -->
<div class="wrap">
<div class="of-bar">
<span>© 2026 Otherside Journal</span>
<span>Photos: Pexels contributors</span>
<span>Made for slow travellers</span>
<button class="of-top" type="button" aria-label="Back to top">↑</button>
</div>
</div>
</footer>
<style>
.of{position:relative;margin-top:0;background:var(--lilac);color:var(--ink);border-radius:36px 36px 0 0;overflow:hidden;padding-top:clamp(56px,8vw,112px)}
.of-inner{display:grid;gap:clamp(56px,7vw,96px)}
/* newsletter */
.of-news{display:grid;grid-template-columns:1.1fr 1fr;gap:clamp(28px,5vw,80px);align-items:end}
.of-eyebrow{margin:0 0 18px;color:var(--plum)}
.of-title{font-size:clamp(44px,7.4vw,118px);font-weight:300;color:var(--ink)}
.of-copy{margin:0 0 22px;max-width:44ch;color:var(--mute);font-size:17px}
.of-sr{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}
.of-field{display:flex;gap:8px;padding:6px;border-radius:999px;background:var(--surface);
box-shadow:0 0 0 1px color-mix(in srgb,var(--plum) 14%,transparent);transition:box-shadow .25s var(--ease)}
.of-field:focus-within{box-shadow:0 0 0 2px var(--plum)}
.of-form.is-error .of-field{box-shadow:0 0 0 2px var(--plum)}
.of-input{flex:1;min-width:0;border:0;background:transparent;padding:10px 16px;font-size:16px;color:var(--ink);outline:none}
.of-input::placeholder{color:var(--mute)}
.of-submit{flex:none}
.of-status{min-height:1.5em;margin:10px 0 0 18px;font-size:14px;font-weight:600;color:var(--plum)}
.of-status:empty{visibility:hidden}
.of-done{display:flex;align-items:center;gap:14px;padding:14px 20px;border-radius:999px;background:var(--surface);
box-shadow:0 0 0 1px color-mix(in srgb,var(--plum) 14%,transparent)}
.of-done[hidden],.of-field[hidden],.of-status[hidden]{display:none}
.of-done-msg{margin:0;font-weight:600;color:var(--ink)}
.of-check{width:34px;height:34px;flex:none}
.of-check-ring{fill:color-mix(in srgb,var(--apricot) 28%,transparent);stroke:var(--apricot-deep);stroke-width:2;
stroke-dasharray:107;stroke-dashoffset:107;transition:stroke-dashoffset .7s var(--ease)}
.of-check-tick{fill:none;stroke:var(--plum);stroke-width:3;stroke-linecap:round;stroke-linejoin:round;
stroke-dasharray:26;stroke-dashoffset:26;transition:stroke-dashoffset .45s var(--ease) .5s}
.of-done.is-on .of-check-ring,.of-done.is-on .of-check-tick{stroke-dashoffset:0}
.of-done.is-on{animation:of-pop .5s var(--ease)}
@keyframes of-pop{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}
/* columns */
.of-cols{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:32px clamp(20px,3vw,48px);
padding-top:clamp(28px,4vw,48px);border-top:1px solid color-mix(in srgb,var(--plum) 20%,transparent)}
.of-colhead{margin:0 0 16px;color:var(--plum)}
.of-list{list-style:none;margin:0;padding:0;display:grid;gap:8px}
.of-link{position:relative;display:inline-block;text-decoration:none;color:var(--ink);font-weight:500;overflow-wrap:anywhere;transition:color .25s var(--ease)}
.of-link::after{content:"";position:absolute;left:0;right:0;bottom:-2px;height:1px;background:currentColor;
transform:scaleX(0);transform-origin:right;transition:transform .35s var(--ease)}
.of-link:hover,.of-link:focus-visible{color:var(--plum)}
.of-link:hover::after,.of-link:focus-visible::after{transform:scaleX(1);transform-origin:left}
.of-note{margin:14px 0 0;font-size:14px;color:var(--mute);max-width:24ch}
/* wordmark */
.of-mark{overflow:hidden;margin-top:clamp(56px,8vw,112px);padding-inline:var(--gut)}
.of-mark-line{font-family:var(--display);font-weight:300;font-size:19vw;line-height:.8;letter-spacing:-.05em;color:var(--plum);
white-space:nowrap;text-align:center;padding-bottom:.06em}
.of-l{display:inline-block;will-change:transform}
/* bottom bar */
.of-bar{display:flex;flex-wrap:wrap;align-items:center;gap:10px 28px;padding:20px 0 24px;
border-top:1px solid color-mix(in srgb,var(--plum) 24%,transparent);font-family:var(--mono);font-size:12px;color:var(--mute)}
.of-top{margin-left:auto;width:44px;height:44px;border-radius:50%;background:var(--plum);color:#fff;font-size:18px;
display:grid;place-items:center;transition:background .25s var(--ease),transform .25s var(--ease)}
.of-top:hover{background:var(--plum-deep);transform:translateY(-3px)}
@media (max-width:900px){
.of-news{grid-template-columns:1fr;align-items:start}
.of-cols{grid-template-columns:repeat(2,minmax(0,1fr))}
}
@media (max-width:560px){
.of{border-radius:28px 28px 0 0}
.of-field{flex-direction:column;border-radius:22px;padding:8px}
.of-input{padding:12px 12px;width:100%}
.of-submit{width:100%;justify-content:center}
.of-status{margin-left:6px}
.of-done{border-radius:22px}
.of-bar{gap:8px 18px}
}
@media (max-width:340px){
.of-cols{grid-template-columns:1fr}
}
@media (prefers-reduced-motion: reduce) and (update: none){
.of-check-ring,.of-check-tick{transition:none}
.of-done.is-on{animation:none}
.of-link::after,.of-top,.of-field{transition:none}
}
</style>
<script>
(function(){
const root=document.querySelector('.of');if(!root)return;
const OS=window.OS||{};
/* heading reveal */
const title=root.querySelector('.of-title');
if(title&&OS.reveal)OS.reveal(title);
/* newsletter form (no network) */
const form=root.querySelector('.of-form');
const input=form.querySelector('.of-input');
const status=form.querySelector('.of-status');
const field=form.querySelector('.of-field');
const done=form.querySelector('.of-done');
const ok=v=>/^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/.test(v);
form.addEventListener('submit',e=>{
e.preventDefault();
const v=input.value.trim();
if(!v||!ok(v)){
form.classList.add('is-error');input.setAttribute('aria-invalid','true');
status.textContent=v?'That address looks off. Try [email protected].':'Add your email so we know where to send it.';
input.focus();return;
}
form.classList.remove('is-error');input.removeAttribute('aria-invalid');status.textContent='';
field.hidden=true;status.hidden=true;done.hidden=false;
requestAnimationFrame(()=>requestAnimationFrame(()=>done.classList.add('is-on')));
});
input.addEventListener('input',()=>{
if(form.classList.contains('is-error')&&ok(input.value.trim())){
form.classList.remove('is-error');input.removeAttribute('aria-invalid');status.textContent='';
}
});
/* wordmark: split letters + fit to width */
const line=root.querySelector('.of-mark-line');
const word=line.textContent.trim();line.textContent='';
const letters=[...word].map(ch=>{const s=document.createElement('span');s.className='of-l';s.textContent=ch;line.appendChild(s);return s});
const fit=()=>{
line.style.fontSize='19vw';
const avail=line.parentElement.clientWidth-parseFloat(getComputedStyle(line.parentElement).paddingLeft)*2;
const w=line.scrollWidth;
if(w>avail&&w>0)line.style.fontSize=(parseFloat(getComputedStyle(line).fontSize)*avail/w)+'px';
};
fit();
if(document.fonts&&document.fonts.ready)document.fonts.ready.then(()=>{fit();if(window.ScrollTrigger)ScrollTrigger.refresh()});
let rt;addEventListener('resize',()=>{clearTimeout(rt);rt=setTimeout(fit,120)});
if(!OS.reduced&&window.gsap&&window.ScrollTrigger){
gsap.fromTo(letters,{yPercent:100},{yPercent:0,ease:'none',stagger:.08,
scrollTrigger:{trigger:root.querySelector('.of-mark'),start:'top bottom',end:'bottom bottom',scrub:.6}});
}
/* back to top */
root.querySelector('.of-top').addEventListener('click',()=>{
OS.lenis?OS.lenis.scrollTo(0):window.scrollTo({top:0,behavior:OS.reduced?'auto':'smooth'});
});
})();
</script>
<script>window.addEventListener('load',()=>ScrollTrigger.refresh())</script>
</body>
</html>
```
## `sections/01-hero.html`
```html
<header class="oh-nav" id="oh-nav">
<div class="oh-nav__bar">
<a class="oh-logo" href="#top" aria-label="Otherside, back to top">
<svg class="oh-logo__mark" viewBox="0 0 18 26" aria-hidden="true">
<rect x="1.5" y="1.5" width="15" height="23" rx="7.5" fill="none" stroke="currentColor" stroke-width="1.8"/>
<rect x="5" y="5.5" width="8" height="15" rx="4" class="oh-logo__glass"/>
</svg>
<span>Otherside</span>
</a>
<nav class="oh-links" aria-label="Primary">
<a href="#destinations">Destinations</a>
<a href="#planner">Planner</a>
<a href="#journal">Journal</a>
<a href="#moments">Moments</a>
</nav>
<a class="oh-cta" href="#planner">Plan a trip</a>
<button class="oh-burger" type="button" aria-expanded="false" aria-controls="oh-panel">Menu</button>
</div>
<div class="oh-panel" id="oh-panel" role="dialog" aria-modal="true" aria-label="Menu">
<div class="oh-panel__top">
<span class="oh-panel__brand">Otherside</span>
<button class="oh-panel__close" type="button">Close <span aria-hidden="true">×</span></button>
</div>
<nav class="oh-panel__links" aria-label="Menu links">
<a href="#destinations"><span class="oh-panel__n">01</span>Destinations</a>
<a href="#planner"><span class="oh-panel__n">02</span>Planner</a>
<a href="#journal"><span class="oh-panel__n">03</span>Journal</a>
<a href="#moments"><span class="oh-panel__n">04</span>Moments</a>
</nav>
<a class="btn oh-panel__cta" href="#planner">Plan a trip <span class="arr">→</span></a>
</div>
</header>
<section class="oh" id="top" aria-label="Otherside: the world is just outside the window">
<div class="oh-stage">
<!-- drifting colour haze behind everything -->
<div class="oh-haze" aria-hidden="true"><span class="oh-orb oh-orb--a"></span><span class="oh-orb oh-orb--b"></span><span class="oh-orb oh-orb--c"></span></div>
<!-- the view: full-screen layer clipped to the window shape, opens up on scroll -->
<div class="oh-win" aria-hidden="true">
<div class="oh-imgs">
<img src="assets/w-maldives.jpg" alt="" decoding="async" fetchpriority="high">
<img src="assets/w-santorini.jpg" alt="" decoding="async">
<img src="assets/w-swiss.jpg" alt="" decoding="async">
<img src="assets/w-aurora.jpg" alt="" decoding="async">
<img src="assets/w-machu.jpg" alt="" decoding="async">
<img src="assets/w-namibia.jpg" alt="" decoding="async">
<img src="assets/w-zhangjiajie.jpg" alt="" decoding="async">
<img src="assets/w-glacier.jpg" alt="" decoding="async">
</div>
<div class="oh-clouds"><i></i><i></i><i></i><i></i><i></i><i></i><i></i></div>
<div class="oh-winbox"><div class="oh-blind"><span class="oh-blind__pull"></span></div><span class="oh-glare"></span></div>
<div class="oh-vscrim"></div>
</div>
<!-- the cabin window bezel (a ring drawn with spread shadows) -->
<div class="oh-frame" aria-hidden="true"><span class="oh-frame__rivets"></span></div>
<h1 class="oh-h">
<span class="oh-h__l">The world is</span>
<span class="oh-h__r">just <em class="oh-hl">outside</em></span>
</h1>
<div class="oh-board" aria-live="polite">
<span class="oh-board__lbl"><span class="oh-board__dot"></span>Now boarding</span>
<span class="oh-flaps" aria-hidden="true"></span>
<span class="oh-sr">Baa Atoll, Maldives</span>
</div>
<p class="oh-intro">An independent travel journal for people who’d rather arrive slowly. Handpicked places, honest routes, window seat always.</p>
<div class="oh-pass">
<div class="oh-pass__leg">
<span class="oh-pass__k">From</span>
<span class="oh-pass__code">HME</span>
<span class="oh-pass__v">Your sofa</span>
</div>
<div class="oh-pass__route" aria-hidden="true">
<span class="oh-pass__line"></span>
<svg class="oh-pass__plane" viewBox="0 0 24 24"><path d="M21 15.5v-2l-8-5V3.2a1.5 1.5 0 0 0-3 0v5.3l-8 5v2l8-2.5v5.2L7.5 20v1.5l4-1 4 1V20L13 18.2V13z"/></svg>
</div>
<div class="oh-pass__leg oh-pass__leg--to">
<span class="oh-pass__k">To</span>
<span class="oh-pass__code oh-pass__to">MLE</span>
<span class="oh-pass__v oh-pass__place">Baa Atoll, Maldives</span>
</div>
<div class="oh-pass__stub">
<span class="oh-pass__k">Seat</span><span class="oh-pass__seat">12A</span>
</div>
<a class="btn oh-go" href="#destinations">Start exploring <span class="arr">→</span></a>
</div>
<div class="oh-s2">
<p class="oh-s2__k"><span class="dot"></span><span class="oh-s2__where">Baa Atoll, Maldives</span></p>
<h2 class="oh-s2__h">Wake up somewhere you can’t pronounce yet</h2>
<p class="oh-s2__sub">Scroll on. We’ve walked every place on the next page before writing a word about it.</p>
</div>
<div class="oh-hint" aria-hidden="true"><span>Scroll to fly</span><span class="oh-hint__line"></span></div>
</div>
</section>
<style>
/* ================= 01 HERO: nav + airplane-window portal ================= */
/* ---------- nav ---------- */
.oh-nav{position:fixed;inset:0 0 auto 0;z-index:60;color:var(--ink)}
.oh-nav__bar{display:grid;grid-template-columns:1fr auto 1fr;align-items:center;gap:24px;height:72px;padding-inline:var(--gut);
transition:background .35s var(--ease),box-shadow .35s var(--ease),backdrop-filter .35s var(--ease)}
.oh-nav--solid .oh-nav__bar{background:color-mix(in srgb,var(--bg) 72%,transparent);-webkit-backdrop-filter:blur(16px) saturate(1.3);backdrop-filter:blur(16px) saturate(1.3);
box-shadow:0 1px 0 color-mix(in srgb,var(--line) 80%,transparent)}
.oh-nav--over{color:#fff}
.oh-nav--solid.oh-nav--over{color:var(--ink)}
.oh-logo{display:inline-flex;align-items:center;gap:10px;text-decoration:none;font-family:var(--display);font-weight:500;font-size:22px;letter-spacing:-.03em;justify-self:start}
.oh-logo__mark{width:17px;height:25px;overflow:visible}
.oh-logo__glass{fill:var(--apricot)}
.oh-links{display:flex;gap:30px}
.oh-links a{position:relative;font-size:14px;font-weight:500;text-decoration:none;padding:6px 0}
.oh-links a::after{content:"";position:absolute;left:0;right:0;bottom:0;height:1px;background:currentColor;transform:scaleX(0);transform-origin:right;transition:transform .35s var(--ease)}
.oh-links a:hover::after{transform:scaleX(1);transform-origin:left}
.oh-cta{justify-self:end;display:inline-flex;align-items:center;padding:11px 20px;border-radius:999px;background:var(--plum);color:#fff;
font-size:14px;font-weight:600;text-decoration:none;transition:background .3s var(--ease),transform .3s var(--ease)}
.oh-cta:hover{background:var(--plum-deep);transform:translateY(-2px)}
.oh-burger{display:none;justify-self:end;padding:9px 18px;border-radius:999px;box-shadow:inset 0 0 0 1px currentColor;color:inherit;font-size:14px;font-weight:600}
.oh-panel{position:fixed;inset:0;z-index:70;background:var(--lilac);color:var(--ink);display:flex;flex-direction:column;padding:0 var(--gut) 32px;
visibility:hidden;opacity:0;clip-path:inset(0 0 100% 0);transition:clip-path .6s var(--ease),opacity .3s,visibility 0s .6s;overflow-y:auto}
.oh-panel.is-open{visibility:visible;opacity:1;clip-path:inset(0 0 0 0);transition:clip-path .6s var(--ease),opacity .2s,visibility 0s}
.oh-panel__top{display:flex;justify-content:space-between;align-items:center;height:72px;flex:none}
.oh-panel__brand{font-family:var(--display);font-size:22px;font-weight:500;letter-spacing:-.03em}
.oh-panel__close{padding:9px 18px;border-radius:999px;box-shadow:inset 0 0 0 1px var(--ink);font-size:14px;font-weight:600}
.oh-panel__links{display:flex;flex-direction:column;margin-top:auto;border-top:1px solid color-mix(in srgb,var(--plum) 25%,transparent)}
.oh-panel__links a{display:flex;align-items:baseline;gap:14px;padding:14px 0;font-family:var(--display);font-size:clamp(40px,11vw,64px);font-weight:400;letter-spacing:-.04em;line-height:1;
text-decoration:none;border-bottom:1px solid color-mix(in srgb,var(--plum) 25%,transparent)}
.oh-panel__links a:hover{color:var(--plum)}
.oh-panel__n{font-family:var(--mono);font-size:12px;letter-spacing:.06em;color:var(--plum)}
.oh-panel__cta{align-self:flex-start;margin-top:28px}
/* ---------- section + sticky stage ---------- */
.oh{position:relative;height:320vh;background:var(--bg)}
.oh-stage{position:sticky;top:0;height:100vh;height:100svh;overflow:hidden;background:var(--bg);
--ww:min(24vw,34svh);--wh:calc(var(--ww) * 1.42);--wr:calc(var(--ww) * .46);--fw:calc(var(--ww) * .12);--cy:50%}
/* haze orbs */
.oh-haze{position:absolute;inset:0;z-index:0;pointer-events:none}
.oh-orb{position:absolute;border-radius:50%;filter:blur(70px);opacity:.75}
.oh-orb--a{width:46vw;height:46vw;left:-12vw;top:-18vw;background:radial-gradient(closest-side,var(--lilac),transparent)}
.oh-orb--b{width:38vw;height:38vw;right:-10vw;bottom:-16vw;background:radial-gradient(closest-side,color-mix(in srgb,var(--apricot) 45%,transparent),transparent)}
.oh-orb--c{width:30vw;height:30vw;right:18vw;top:-10vw;background:radial-gradient(closest-side,color-mix(in srgb,var(--plum) 22%,transparent),transparent)}
/* the view */
.oh-win{position:absolute;inset:0;z-index:2;overflow:hidden;will-change:clip-path;
clip-path:inset(calc(var(--cy) - var(--wh) / 2) calc(50% - var(--ww) / 2) calc(100% - var(--cy) - var(--wh) / 2) calc(50% - var(--ww) / 2) round var(--wr))}
.oh-imgs{position:absolute;inset:-4%;will-change:transform}
.oh-imgs img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;opacity:0}
.oh-imgs img:first-child{opacity:1}
.oh-clouds{position:absolute;inset:0;pointer-events:none;will-change:transform,opacity}
.oh-clouds i{position:absolute;left:50%;top:var(--cy);width:calc(var(--ww) * .9);height:calc(var(--ww) * .42);margin:calc(var(--ww) * -.21) 0 0 calc(var(--ww) * -.45);
border-radius:50%;background:radial-gradient(closest-side,color-mix(in srgb,#fff 92%,transparent),color-mix(in srgb,#fff 40%,transparent) 55%,transparent);filter:blur(6px);opacity:.9}
.oh-winbox{position:absolute;left:50%;top:var(--cy);width:var(--ww);height:var(--wh);transform:translate(-50%,-50%);border-radius:var(--wr);overflow:hidden;pointer-events:none}
.oh-blind{position:absolute;inset:0;will-change:transform;
background:repeating-linear-gradient(180deg,var(--surface) 0 calc(var(--wh) / 14 - 1px),var(--line) calc(var(--wh) / 14 - 1px) calc(var(--wh) / 14));
box-shadow:inset 0 -10px 18px -10px color-mix(in srgb,var(--ink) 25%,transparent)}
.oh-blind__pull{position:absolute;left:50%;bottom:10px;width:34%;height:8px;margin-left:-17%;border-radius:8px;background:var(--line);box-shadow:inset 0 1px 0 color-mix(in srgb,var(--ink) 12%,transparent)}
.oh-glare{position:absolute;inset:0;background:linear-gradient(115deg,color-mix(in srgb,#fff 38%,transparent) 0 18%,transparent 34%,transparent 62%,color-mix(in srgb,#fff 14%,transparent) 70%,transparent 80%);mix-blend-mode:screen}
.oh-vscrim{position:absolute;inset:0;opacity:0;pointer-events:none;
background:linear-gradient(180deg,color-mix(in srgb,var(--ink) 25%,transparent),transparent 30%,transparent 45%,color-mix(in srgb,var(--ink) 55%,transparent))}
/* bezel: transparent box, ring from spread shadows (they follow border-radius) */
.oh-frame{position:absolute;z-index:3;left:50%;top:var(--cy);width:var(--ww);height:var(--wh);margin:calc(var(--wh) / -2) 0 0 calc(var(--ww) / -2);
border-radius:var(--wr);pointer-events:none;will-change:transform,opacity;
box-shadow:
inset 0 0 0 2px color-mix(in srgb,var(--ink) 10%,transparent),
inset 0 10px 22px -8px color-mix(in srgb,var(--ink) 35%,transparent),
0 0 0 calc(var(--fw) * .35) color-mix(in srgb,var(--line) 70%,var(--surface)),
0 0 0 calc(var(--fw) * .38) color-mix(in srgb,var(--ink) 8%,transparent),
0 0 0 var(--fw) var(--surface),
0 0 0 calc(var(--fw) + 1px) color-mix(in srgb,var(--ink) 8%,transparent),
0 calc(var(--fw) * 1.6) calc(var(--fw) * 3) calc(var(--fw) * -.6) color-mix(in srgb,var(--plum) 30%,transparent)}
.oh-frame__rivets{position:absolute;inset:calc(var(--fw) * -.7);border-radius:calc(var(--wr) + var(--fw) * .7);
background:radial-gradient(circle,var(--line) 0 2px,transparent 2.5px) 50% 0/100% 100% no-repeat,radial-gradient(circle,var(--line) 0 2px,transparent 2.5px) 50% 100%/100% 100% no-repeat}
/* headline flanks the window */
.oh-h{position:absolute;z-index:4;left:var(--gut);right:var(--gut);top:var(--cy);transform:translateY(-50%);display:grid;
grid-template-columns:1fr calc(var(--ww) + var(--fw) * 2 + clamp(40px,5vw,90px)) 1fr;align-items:center;pointer-events:none;
font-family:var(--display);font-weight:300;font-size:clamp(40px,min(5.4vw,10svh),104px);line-height:.95;letter-spacing:-.045em;color:var(--ink)}
.oh-h__l{grid-column:1;text-align:right;display:block}
.oh-h__r{grid-column:3;text-align:left;display:block}
.oh-hl{font-style:normal;color:var(--plum)}
.oh-h .w>span{will-change:transform}
/* split-flap board */
.oh-board{position:absolute;z-index:4;left:var(--gut);top:clamp(96px,15svh,150px);display:flex;flex-direction:column;gap:10px}
.oh-board__lbl{font-family:var(--mono);font-size:11px;letter-spacing:.1em;text-transform:uppercase;color:var(--mute);display:flex;align-items:center;gap:8px}
.oh-board__dot{width:8px;height:8px;border-radius:50%;background:var(--apricot);animation:oh-blink 1.2s steps(2,end) infinite}
@keyframes oh-blink{50%{opacity:.2}}
.oh-flaps{display:flex;gap:3px;perspective:400px}
.oh-flap{position:relative;display:grid;place-items:center;width:clamp(22px,1.9vw,30px);height:clamp(32px,2.7vw,42px);border-radius:5px;
background:var(--surface);color:var(--plum);font-family:var(--mono);font-weight:500;font-size:clamp(15px,1.3vw,21px);
box-shadow:0 1px 0 var(--line),0 6px 14px -8px color-mix(in srgb,var(--plum) 40%,transparent);transform-origin:50% 50%}
.oh-flap::after{content:"";position:absolute;left:0;right:0;top:50%;height:1px;background:color-mix(in srgb,var(--line) 90%,transparent)}
.oh-sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
.oh-intro{position:absolute;z-index:4;right:var(--gut);top:clamp(96px,15svh,150px);max-width:30ch;margin:0;font-size:15px;line-height:1.55;color:var(--mute);text-align:right}
/* boarding pass */
.oh-pass{position:absolute;z-index:5;left:50%;bottom:clamp(18px,4svh,40px);transform:translateX(-50%);width:min(980px,calc(100% - var(--gut) * 2));
display:grid;grid-template-columns:auto 1fr auto auto auto;align-items:center;gap:clamp(14px,2vw,28px);padding:14px 14px 14px 26px;border-radius:22px;
background:var(--surface);box-shadow:0 24px 50px -28px color-mix(in srgb,var(--plum) 45%,transparent),0 0 0 1px color-mix(in srgb,var(--line) 70%,transparent);
-webkit-mask:radial-gradient(circle 10px at calc(100% - var(--stub,190px)) 0,transparent 98%,#fff) top/100% 51% no-repeat,radial-gradient(circle 10px at calc(100% - var(--stub,190px)) 100%,transparent 98%,#fff) bottom/100% 51% no-repeat;
mask:radial-gradient(circle 10px at calc(100% - var(--stub,190px)) 0,transparent 98%,#fff) top/100% 51% no-repeat,radial-gradient(circle 10px at calc(100% - var(--stub,190px)) 100%,transparent 98%,#fff) bottom/100% 51% no-repeat}
.oh-pass__leg{display:flex;flex-direction:column;gap:2px;min-width:0}
.oh-pass__leg--to{min-width:13ch}
.oh-pass__k{font-family:var(--mono);font-size:10px;letter-spacing:.1em;text-transform:uppercase;color:var(--mute)}
.oh-pass__code{font-family:var(--display);font-size:clamp(26px,2.4vw,38px);font-weight:500;letter-spacing:-.02em;line-height:1;color:var(--ink);display:inline-block}
.oh-pass__v{font-size:12.5px;color:var(--mute);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.oh-pass__route{position:relative;height:24px;min-width:80px}
.oh-pass__line{position:absolute;left:0;right:0;top:50%;border-top:1.5px dashed color-mix(in srgb,var(--plum) 40%,transparent)}
.oh-pass__line::after{content:"";position:absolute;left:0;top:-1.5px;height:1.5px;width:var(--p,0%);background:var(--plum)}
.oh-pass__plane{position:absolute;top:50%;left:var(--p,0%);width:22px;height:22px;margin:-11px 0 0 -11px;fill:var(--plum);transform:rotate(90deg)}
.oh-pass__stub{display:flex;flex-direction:column;gap:2px;padding-left:clamp(14px,2vw,26px);border-left:1.5px dashed var(--line)}
.oh-pass__seat{font-family:var(--display);font-size:clamp(20px,1.8vw,28px);font-weight:500;line-height:1;color:var(--apricot-deep)}
.oh-go{white-space:nowrap}
/* stage 2 — landed */
.oh-s2{position:absolute;z-index:6;left:var(--gut);right:var(--gut);bottom:clamp(40px,10svh,110px);color:#fff;pointer-events:none;
text-shadow:0 2px 30px color-mix(in srgb,var(--ink) 35%,transparent)}
.oh-s2__k{margin:0 0 18px;font-family:var(--mono);font-size:12px;letter-spacing:.1em;text-transform:uppercase;opacity:0}
.oh-s2__h{font-family:var(--display);font-weight:300;font-size:clamp(44px,min(7vw,12svh),132px);line-height:.93;letter-spacing:-.045em;max-width:15ch}
.oh-s2__sub{margin:22px 0 0;max-width:40ch;font-size:17px;font-weight:500;opacity:0}
/* scroll hint */
.oh-hint{position:absolute;z-index:4;right:var(--gut);top:var(--cy);transform:translateY(calc(var(--wh) / 2 - 40px));display:flex;flex-direction:column;align-items:center;gap:10px;
color:var(--mute);font-family:var(--mono);font-size:11px;letter-spacing:.1em;text-transform:uppercase;writing-mode:vertical-rl}
.oh-hint__line{position:relative;width:1px;height:48px;overflow:hidden;background:var(--line)}
.oh-hint__line::after{content:"";position:absolute;left:0;top:0;width:1px;height:40%;background:var(--plum);animation:oh-drip 1.8s var(--ease) infinite}
@keyframes oh-drip{from{transform:translateY(-100%)}to{transform:translateY(260%)}}
/* reduced motion */
.oh--static{height:100vh;height:100svh}
.oh--static .oh-blind,.oh--static .oh-s2,.oh--static .oh-hint{display:none}
/* ---------- responsive ---------- */
@media (max-width:1100px){
.oh-links{gap:20px}
.oh-pass{grid-template-columns:auto 1fr auto auto;--stub:0px;-webkit-mask:none;mask:none}
.oh-go{display:none}
}
@media (max-width:900px){
.oh-intro{display:none}
}
@media (max-width:759px){
.oh-nav__bar{grid-template-columns:1fr auto;height:64px}
.oh-links,.oh-cta{display:none}
.oh-burger{display:inline-flex}
.oh-panel__top{height:64px}
.oh{height:260vh}
.oh-stage{--ww:min(52vw,26svh);--cy:54%}
.oh-h{top:calc(var(--cy) - var(--wh) / 2 - var(--fw) - 22px);transform:translateY(-100%);display:block;text-align:center;font-size:clamp(36px,10.5vw,48px)}
.oh-h__l,.oh-h__r{text-align:center}
.oh-board{top:76px;left:0;right:0;align-items:center;display:none}
.oh-hint{display:none}
.oh-pass{grid-template-columns:auto 1fr auto;padding:12px 16px;gap:12px;border-radius:18px}
.oh-pass__stub{display:none}
.oh-pass__leg--to{min-width:0;text-align:right;align-items:flex-end}
.oh-pass__v{font-size:11.5px;max-width:28vw}
.oh-s2__sub{font-size:15px}
}
@media (max-width:759px) and (min-height:700px){
.oh-board{display:flex}
.oh-stage{--cy:58%}
.oh-h{font-size:clamp(34px,9.6vw,44px)}
}
</style>
<script>
(function(){
const OS=window.OS, sec=document.querySelector('.oh'); if(!sec||!OS) return;
const $=s=>sec.querySelector(s), $$=s=>[...sec.querySelectorAll(s)];
const nav=document.getElementById('oh-nav'), bar=nav.querySelector('.oh-nav__bar');
const panel=document.getElementById('oh-panel'), burger=nav.querySelector('.oh-burger'), closeBtn=nav.querySelector('.oh-panel__close');
/* ---------- anchor scrolling (Lenis-aware) ---------- */
function go(e){
const id=e.currentTarget.getAttribute('href'); const t=id&&id.length>1&&document.querySelector(id);
if(!t||!OS.lenis) return;
e.preventDefault(); OS.lenis.start(); OS.lenis.scrollTo(t,{offset:id==='#top'?0:-64});
}
/* ---------- mobile menu ---------- */
function openMenu(){
panel.classList.add('is-open'); burger.setAttribute('aria-expanded','true');
OS.lenis?.stop(); document.documentElement.style.overflow='hidden';
setTimeout(()=>closeBtn.focus(),50);
}
function closeMenu(focusBack){
if(!panel.classList.contains('is-open')) return;
panel.classList.remove('is-open'); burger.setAttribute('aria-expanded','false');
document.documentElement.style.overflow=''; OS.lenis?.start();
if(focusBack) burger.focus();
}
burger.addEventListener('click',openMenu);
closeBtn.addEventListener('click',()=>closeMenu(true));
document.addEventListener('keydown',e=>{if(e.key==='Escape')closeMenu(true)});
addEventListener('resize',()=>{if(innerWidth>=760)closeMenu(false)});
panel.querySelectorAll('a').forEach(a=>a.addEventListener('click',e=>{closeMenu(false);go(e)}));
nav.querySelectorAll('.oh-nav__bar a').forEach(a=>a.addEventListener('click',go));
sec.querySelectorAll('a[href^="#"]').forEach(a=>a.addEventListener('click',go));
/* ---------- destinations shown through the window ---------- */
const D=[
{n:'MALDIVES', c:'MLE',p:'Baa Atoll, Maldives'},
{n:'SANTORINI',c:'JTR',p:'Oia, Santorini'},
{n:'ALPS', c:'ZRH',p:'Bernese Oberland, Switzerland'},
{n:'TROMSO', c:'TOS',p:'Aurora season, Norway'},
{n:'PERU', c:'CUZ',p:'Machu Picchu, Peru'},
{n:'NAMIBIA', c:'WDH',p:'Deadvlei, Namibia'},
{n:'HUNAN', c:'DYG',p:'Wulingyuan, China'},
{n:'ICELAND', c:'KEF',p:'Jökulsárlón, Iceland'}
];
const CELLS=9, CYCLE=3.4, GLYPHS='ABCDEFGHIJKLMNOPRSTUVWXYZ';
const imgs=$$('.oh-imgs img'), flapsEl=$('.oh-flaps'), sr=$('.oh-sr');
const toCode=$('.oh-pass__to'), place=$('.oh-pass__place'), where=$('.oh-s2__where'), pass=$('.oh-pass');
const flaps=Array.from({length:CELLS},()=>{const s=document.createElement('span');s.className='oh-flap';s.textContent=' ';flapsEl.appendChild(s);return s});
const pad=w=>w.padEnd(CELLS,' ');
pad(D[0].n).split('').forEach((ch,i)=>flaps[i].textContent=ch);
function flipTo(el,target,delay,spins){
if(OS.reduced){el.textContent=target;return}
let k=0;
const tick=()=>{
const last=k>=spins; el.textContent=last?target:GLYPHS[Math.random()*GLYPHS.length|0];
gsap.fromTo(el,{rotateX:-80},{rotateX:0,duration:.07,ease:'power1.out'});
if(!last){k++;gsap.delayedCall(.055,tick)}
};
gsap.delayedCall(delay,tick);
}
function flipWord(word){pad(word).split('').forEach((ch,i)=>{if(flaps[i].textContent!==ch||ch!==' ')flipTo(flaps[i],ch,i*.045,4+(Math.random()*4|0))})}
function flipCode(code){
gsap.timeline().to(toCode,{rotateX:90,duration:.16,ease:'power2.in'}).add(()=>{toCode.textContent=code}).to(toCode,{rotateX:0,duration:.3,ease:'back.out(2)'});
}
let cur=0, cycling=true, planeTw=null;
function flyPlane(){
planeTw&&planeTw.kill();
if(OS.reduced) return pass.style.setProperty('--p','100%');
planeTw=gsap.fromTo(pass,{'--p':'0%'},{'--p':'100%',duration:CYCLE,ease:'power1.inOut'});
}
function show(k){
const prev=imgs[cur], next=imgs[k]; cur=k;
gsap.set(next,{zIndex:2}); gsap.set(prev,{zIndex:1});
gsap.fromTo(next,{opacity:0,scale:1.14},{opacity:1,scale:1,duration:1.2,ease:'power2.out',onComplete:()=>gsap.set(prev,{opacity:0})});
flipWord(D[k].n); flipCode(D[k].c);
place.textContent=D[k].p; where.textContent=D[k].p; sr.textContent=D[k].p;
flyPlane();
}
let loop=null;
function schedule(){loop&&loop.kill();loop=gsap.delayedCall(CYCLE,()=>{if(cycling)show((cur+1)%D.length);schedule()})}
/* ---------- nav: solid bar after the hero ---------- */
ScrollTrigger.create({trigger:sec,start:'bottom 72px',
onEnter:()=>nav.classList.add('oh-nav--solid'),onLeaveBack:()=>nav.classList.remove('oh-nav--solid')});
/* ---------- reduced motion ---------- */
if(OS.reduced){sec.classList.add('oh--static');schedule();return}
const stage=$('.oh-stage'), win=$('.oh-win'), frame=$('.oh-frame'), wimgs=$('.oh-imgs'), clouds=$('.oh-clouds'), cl=$$('.oh-clouds i');
const blind=$('.oh-blind'), glare=$('.oh-glare'), vscrim=$('.oh-vscrim'), board=$('.oh-board'), intro=$('.oh-intro'), hint=$('.oh-hint');
const hl=OS.split($('.oh-h__l')), hr=OS.split($('.oh-h__r'));
const s2h=OS.split($('.oh-s2__h')), s2k=$('.oh-s2__k'), s2s=$('.oh-s2__sub');
gsap.set(s2h,{yPercent:110});
gsap.set(pass,{xPercent:-50,x:0}); /* own the centring so later y tweens keep it */
/* clouds drifting past the window, forever */
const g={W:0,H:0,ww:0,wh:0,cy:0,r:0};
function measure(){
const cs=getComputedStyle(frame);
g.W=stage.clientWidth; g.H=stage.clientHeight; g.ww=frame.offsetWidth; g.wh=frame.offsetHeight;
g.cy=frame.offsetTop+g.wh/2; g.r=parseFloat(cs.borderTopLeftRadius)||g.ww*.46;
}
measure();
const clipAt=t=>{ /* t: 0 = window, 1 = full screen */
const T=(g.cy-g.wh/2)*(1-t), B=(g.H-g.cy-g.wh/2)*(1-t), L=((g.W-g.ww)/2)*(1-t), r=g.r*(1-t);
return `inset(${T}px ${L}px ${B}px ${L}px round ${r}px)`;
};
cl.forEach((c,i)=>{
const dur=gsap.utils.random(4.5,8.5), y=gsap.utils.random(-.42,.42), s=gsap.utils.random(.6,1.5);
gsap.set(c,{y:()=>y*g.wh,scale:s,opacity:gsap.utils.random(.55,.95)});
gsap.fromTo(c,{x:()=>g.ww*1.2},{x:()=>-g.ww*1.2,duration:dur,ease:'none',repeat:-1,delay:-i*dur/cl.length});
});
/* orbs breathe */
$$('.oh-orb').forEach((o,i)=>gsap.to(o,{x:gsap.utils.random(-80,80),y:gsap.utils.random(-60,60),scale:gsap.utils.random(.85,1.2),duration:gsap.utils.random(6,10),ease:'sine.inOut',yoyo:true,repeat:-1,delay:i}));
/* pointer: bezel tilts, view shifts the other way */
const fine=matchMedia('(pointer:fine)').matches;
const rig={fx:gsap.quickTo(frame,'x',{duration:.8,ease:'power3'}),fy:gsap.quickTo(frame,'y',{duration:.8,ease:'power3'}),
rx:gsap.quickTo(frame,'rotationY',{duration:.8,ease:'power3'}),ry:gsap.quickTo(frame,'rotationX',{duration:.8,ease:'power3'}),
ix:gsap.quickTo(wimgs,'x',{duration:1.1,ease:'power3'}),iy:gsap.quickTo(wimgs,'y',{duration:1.1,ease:'power3'}),
cx:gsap.quickTo(clouds,'x',{duration:.9,ease:'power3'})};
gsap.set(frame,{transformPerspective:900});
let live=true;
if(fine) stage.addEventListener('pointermove',e=>{
if(!live) return;
const nx=e.clientX/g.W-.5, ny=e.clientY/g.H-.5;
rig.fx(nx*14);rig.fy(ny*10);rig.rx(nx*10);rig.ry(-ny*8);rig.ix(-nx*38);rig.iy(-ny*26);rig.cx(-nx*60);
});
/* scroll: the window opens into the whole screen (proxy 0→1, clip rebuilt from live geometry) */
const open={t:0}; const applyClip=()=>{win.style.clipPath=clipAt(open.t)};
ScrollTrigger.addEventListener('refresh',()=>{measure();applyClip()});
const tl=gsap.timeline({defaults:{ease:'none'},
scrollTrigger:{trigger:sec,start:'top top',end:'bottom bottom',scrub:1,invalidateOnRefresh:true,
onRefreshInit:measure,
onUpdate(self){
const p=self.progress, stop=p>.03;
if(stop===cycling){cycling=!stop}
live=p<.08;
nav.classList.toggle('oh-nav--over',p>.42&&p<.995);
if(!live){gsap.to([frame,wimgs,clouds],{x:0,y:0,rotationX:0,rotationY:0,duration:.6,overwrite:'auto'})}
}}});
tl.to(hint,{autoAlpha:0,duration:.04},0)
.to(hl,{xPercent:-60,autoAlpha:0,duration:.14,stagger:.01,ease:'power2.in'},.02)
.to(hr,{xPercent:60,autoAlpha:0,duration:.14,stagger:-.01,ease:'power2.in'},.02)
.to([board,intro],{autoAlpha:0,y:-24,duration:.1},.03)
.to(pass,{autoAlpha:0,y:40,duration:.1},.04)
.to(open,{t:1,ease:'power2.inOut',duration:.5,onUpdate:applyClip},.08)
.to(frame,{scale:()=>Math.max(g.W/g.ww,g.H/g.wh)*1.35,opacity:0,ease:'power2.in',duration:.42},.08)
.to(glare,{opacity:0,duration:.2},.1)
.fromTo(wimgs,{scale:1.18},{scale:1,ease:'power1.out',duration:.6,immediateRender:false},.08)
.to(clouds,{scale:4,opacity:0,ease:'power2.in',duration:.4},.12)
.to(vscrim,{opacity:1,duration:.25},.4)
.to(s2k,{opacity:1,duration:.06},.58)
.to(s2h,{yPercent:0,ease:'power3.out',duration:.1,stagger:.012},.6)
.to(s2s,{opacity:1,duration:.08},.72)
.to({},{duration:.2},.8);
/* ---------- intro on load ---------- */
applyClip(); schedule(); flyPlane();
if(scrollY<10){
const o={ease:'power3.out'};
gsap.from(bar,{...o,yPercent:-100,autoAlpha:0,duration:1,delay:.1,clearProps:'transform,opacity,visibility'});
gsap.from(frame,{...o,scale:.8,autoAlpha:0,duration:1.4,delay:.1});
gsap.to(blind,{yPercent:-102,duration:1.5,ease:'power3.inOut',delay:.9});
gsap.from(hl,{xPercent:40,autoAlpha:0,duration:1.2,ease:'power4.out',stagger:.07,delay:.5});
gsap.from(hr,{xPercent:-40,autoAlpha:0,duration:1.2,ease:'power4.out',stagger:.07,delay:.6});
gsap.from([board,intro],{...o,autoAlpha:0,y:20,duration:.9,delay:1.1,stagger:.1});
gsap.from(pass,{...o,autoAlpha:0,yPercent:60,duration:1,delay:1.3});
gsap.from(hint,{autoAlpha:0,duration:.9,delay:1.6});
} else gsap.set(blind,{yPercent:-102});
})();
</script>
```
## `sections/02-destinations.html`
```html
<section class="od" id="destinations" aria-labelledby="od-title">
<style>
.od{background:var(--bg);padding-block:clamp(80px,11vw,160px);overflow:hidden}
.od-head{display:grid;grid-template-columns:minmax(0,1.6fr) minmax(0,1fr);gap:clamp(28px,5vw,80px);align-items:end}
.od-eyebrow{margin:0 0 22px}
.od-title{font-size:clamp(40px,6.6vw,96px);max-width:14ch}
.od-side{display:flex;flex-direction:column;align-items:flex-start;gap:22px;padding-bottom:6px}
.od-side p{margin:0;color:var(--mute);font-size:clamp(15px,1.2vw,17px);max-width:38ch}
/* filters */
.od-bar{display:flex;align-items:center;justify-content:space-between;gap:16px 24px;flex-wrap:wrap;
margin-top:clamp(40px,6vw,72px);padding-top:22px;border-top:1px solid var(--line)}
.od-chips{display:flex;flex-wrap:wrap;gap:8px}
.od-chip{padding:9px 16px;border-radius:999px;font-size:14px;font-weight:600;color:var(--ink);
box-shadow:inset 0 0 0 1px var(--line);background:var(--surface);
transition:background .25s var(--ease),color .25s var(--ease),box-shadow .25s var(--ease)}
.od-chip:hover{box-shadow:inset 0 0 0 1px var(--plum);color:var(--plum)}
.od-chip[aria-pressed="true"]{background:var(--plum);color:#fff;box-shadow:none}
.od-count{margin:0;display:flex;align-items:baseline;gap:10px;font-family:var(--mono);font-size:12px;letter-spacing:.08em;text-transform:uppercase;color:var(--mute)}
.od-count__n{display:inline-block;overflow:hidden;height:1em;line-height:1;font-family:var(--display);font-size:clamp(28px,3vw,40px);
letter-spacing:-.03em;color:var(--plum);text-transform:none;min-width:1.2em}
.od-count__n span{display:inline-block}
/* grid */
.od-grid{list-style:none;margin:28px 0 0;padding:0;display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:clamp(14px,1.6vw,24px)}
.od-item{min-width:0}
.od-item[hidden]{display:none}
.od-card{position:relative;display:block;border-radius:var(--radius);overflow:hidden;text-decoration:none;color:#fff;
aspect-ratio:3/4;background:var(--lilac);isolation:isolate;
transition:transform .5s var(--ease),box-shadow .5s var(--ease)}
.od-media{position:absolute;inset:0;overflow:hidden}
.od-media img{width:100%;height:100%;object-fit:cover;transition:transform .9s ease}
.od-card::after{content:"";position:absolute;inset:0;z-index:1;pointer-events:none;
background:linear-gradient(to top,
color-mix(in srgb,var(--ink) 72%,transparent) 0%,
color-mix(in srgb,var(--ink) 30%,transparent) 38%,
color-mix(in srgb,var(--ink) 0%,transparent) 62%),
linear-gradient(to bottom,color-mix(in srgb,var(--ink) 28%,transparent) 0%,color-mix(in srgb,var(--ink) 0%,transparent) 24%)}
.od-top{position:absolute;z-index:2;inset:16px 16px auto 16px;display:flex;justify-content:space-between;align-items:flex-start;gap:8px}
.od-idx{font-family:var(--mono);font-size:12px;letter-spacing:.08em;color:#fff}
.od-body{position:absolute;z-index:2;left:18px;right:18px;bottom:18px}
.od-region{display:block;font-family:var(--mono);font-size:11px;letter-spacing:.1em;text-transform:uppercase;color:#fff;opacity:.85;margin-bottom:8px}
.od-name{font-size:clamp(22px,1.9vw,28px);line-height:1.02;color:#fff;padding-right:52px}
.od-go{position:absolute;z-index:2;right:16px;bottom:16px;width:44px;height:44px;border-radius:50%;display:grid;place-items:center;
background:var(--plum);color:#fff;font-size:18px;opacity:0;transform:scale(.6) rotate(-45deg);
transition:opacity .4s var(--ease),transform .5s var(--ease),background .25s var(--ease)}
.od-card:focus-visible .od-go{opacity:1;transform:none}
@media (hover:hover){
.od-card:hover{transform:translateY(-8px);box-shadow:0 24px 48px -24px color-mix(in srgb,var(--plum) 55%,transparent)}
.od-card:hover .od-media img{transform:scale(1.08)}
.od-card:hover .od-go{opacity:1;transform:none}
.od-go:hover{background:var(--plum-deep)}
}
@media (hover:none){.od-go{opacity:1;transform:none;width:38px;height:38px;font-size:16px}}
.od-empty{margin:24px 0 0;color:var(--mute)}
/* marquee */
.od-marquee{margin-top:clamp(56px,8vw,112px);overflow:hidden;border-block:1px solid var(--line);padding-block:clamp(16px,2vw,28px);max-width:100%}
.od-track{display:flex;width:max-content;will-change:transform}
.od-run{display:flex;align-items:center;flex-shrink:0}
.od-word{font-family:var(--display);font-weight:500;font-size:clamp(48px,8vw,128px);line-height:1;letter-spacing:-.03em;white-space:nowrap;
color:transparent;-webkit-text-stroke:1.5px var(--ink);padding-inline:clamp(18px,2.4vw,40px);transition:color .35s var(--ease),-webkit-text-stroke-color .35s var(--ease)}
.od-word:nth-child(4n+1){color:var(--plum);-webkit-text-stroke-color:var(--plum)}
.od-marquee:hover .od-word{-webkit-text-stroke-color:var(--plum)}
.od-sep{flex-shrink:0;width:clamp(12px,1.2vw,18px);height:clamp(12px,1.2vw,18px);border-radius:50%;background:var(--apricot)}
@media (max-width:1024px){
.od-grid{grid-template-columns:repeat(2,minmax(0,1fr))}
.od-head{grid-template-columns:1fr;align-items:start}
}
@media (max-width:599px){
.od-bar{align-items:flex-start}
.od-grid{display:flex;overflow-x:auto;scroll-snap-type:x mandatory;overscroll-behavior-x:contain;
margin-inline:calc(var(--gut)*-1);padding:6px var(--gut) 14px;scroll-padding-inline:var(--gut);scrollbar-width:none;gap:12px}
.od-grid::-webkit-scrollbar{display:none}
.od-item{flex:0 0 calc((100% - 12px) / 1.2);scroll-snap-align:start}
.od-word{-webkit-text-stroke-width:1px}
}
</style>
<div class="wrap">
<div class="od-head">
<div>
<p class="eyebrow od-eyebrow"><span class="dot"></span>Destinations — 08 places</p>
<h2 class="od-title" id="od-title">Handpicked places, <em class="it">walked</em> before we wrote about them</h2>
</div>
<div class="od-side">
<p>No listicles, no borrowed photos. Every guide here started with our own boots on the trail, a notebook in the pocket and a few wrong turns worth keeping.</p>
<a class="btn btn--line" href="#destinations">All destinations <span class="arr" aria-hidden="true">→</span></a>
</div>
</div>
<div class="od-bar">
<div class="od-chips" role="group" aria-label="Filter destinations by landscape">
<button class="od-chip" type="button" data-filter="all" aria-pressed="true">All</button>
<button class="od-chip" type="button" data-filter="mountains" aria-pressed="false">Mountains</button>
<button class="od-chip" type="button" data-filter="coast" aria-pressed="false">Coast</button>
<button class="od-chip" type="button" data-filter="desert" aria-pressed="false">Desert</button>
<button class="od-chip" type="button" data-filter="cities" aria-pressed="false">Cities</button>
</div>
<p class="od-count" aria-live="polite"><span class="od-count__n"><span>08</span></span><span class="od-count__l">places shown</span></p>
</div>
<ul class="od-grid">
<li class="od-item" data-cat="coast mountains">
<a class="od-card" href="#destinations" aria-label="Skógafoss and the South Coast, Iceland">
<div class="od-media"><img src="assets/d-iceland.jpg" alt="Skógafoss waterfall pouring over green cliffs on Iceland's south coast" loading="lazy" width="600" height="800"></div>
<div class="od-top"><span class="od-idx">01</span><span class="tag">Best: Jun–Aug</span></div>
<div class="od-body"><span class="od-region">Iceland</span><h3 class="od-name">Skógafoss & the South Coast</h3></div>
<span class="od-go" aria-hidden="true">→</span>
</a>
</li>
<li class="od-item" data-cat="cities">
<a class="od-card" href="#destinations" aria-label="Old-town temples, Japan">
<div class="od-media"><img src="assets/d-kyoto.jpg" alt="Wooden temple roofs rising above a quiet old-town lane in Kyoto" loading="lazy" width="600" height="800"></div>
<div class="od-top"><span class="od-idx">02</span><span class="tag">Best: Mar–Apr</span></div>
<div class="od-body"><span class="od-region">Japan</span><h3 class="od-name">Old-town temples</h3></div>
<span class="od-go" aria-hidden="true">→</span>
</a>
</li>
<li class="od-item" data-cat="mountains">
<a class="od-card" href="#destinations" aria-label="Torres del Paine, Chile">
<div class="od-media"><img src="assets/d-patagonia.jpg" alt="Granite towers of Torres del Paine above a glacial lake" loading="lazy" width="600" height="800"></div>
<div class="od-top"><span class="od-idx">03</span><span class="tag">Best: Nov–Mar</span></div>
<div class="od-body"><span class="od-region">Chile</span><h3 class="od-name">Torres del Paine</h3></div>
<span class="od-go" aria-hidden="true">→</span>
</a>
</li>
<li class="od-item" data-cat="desert">
<a class="od-card" href="#destinations" aria-label="Erg Chebbi dunes, Morocco">
<div class="od-media"><img src="assets/d-morocco.jpg" alt="Wind-shaped orange dunes of Erg Chebbi in the Moroccan Sahara" loading="lazy" width="600" height="800"></div>
<div class="od-top"><span class="od-idx">04</span><span class="tag">Best: Oct–Apr</span></div>
<div class="od-body"><span class="od-region">Morocco</span><h3 class="od-name">Erg Chebbi dunes</h3></div>
<span class="od-go" aria-hidden="true">→</span>
</a>
</li>
<li class="od-item" data-cat="coast">
<a class="od-card" href="#destinations" aria-label="Lofoten fishing villages, Norway">
<div class="od-media"><img src="assets/d-norway.jpg" alt="Red fishing cabins on stilts beneath steep Lofoten peaks" loading="lazy" width="600" height="800"></div>
<div class="od-top"><span class="od-idx">05</span><span class="tag">Best: Feb–Mar</span></div>
<div class="od-body"><span class="od-region">Norway</span><h3 class="od-name">Lofoten fishing villages</h3></div>
<span class="od-go" aria-hidden="true">→</span>
</a>
</li>
<li class="od-item" data-cat="mountains">
<a class="od-card" href="#destinations" aria-label="Rice terraces of Sidemen, Indonesia">
<div class="od-media"><img src="assets/d-bali.jpg" alt="Stepped green rice terraces in the Sidemen valley, Bali" loading="lazy" width="600" height="800"></div>
<div class="od-top"><span class="od-idx">06</span><span class="tag">Best: May–Sep</span></div>
<div class="od-body"><span class="od-region">Indonesia</span><h3 class="od-name">Rice terraces of Sidemen</h3></div>
<span class="od-go" aria-hidden="true">→</span>
</a>
</li>
<li class="od-item" data-cat="mountains">
<a class="od-card" href="#destinations" aria-label="Alpine lakes, Italy">
<div class="od-media"><img src="assets/d-dolomites.jpg" alt="Still alpine lake reflecting the jagged Dolomite peaks" loading="lazy" width="600" height="800"></div>
<div class="od-top"><span class="od-idx">07</span><span class="tag">Best: Jun–Sep</span></div>
<div class="od-body"><span class="od-region">Italy</span><h3 class="od-name">Alpine lakes</h3></div>
<span class="od-go" aria-hidden="true">→</span>
</a>
</li>
<li class="od-item" data-cat="coast cities">
<a class="od-card" href="#destinations" aria-label="Positano and the Amalfi Coast, Italy">
<div class="od-media"><img src="assets/d-amalfi.jpg" alt="Pastel houses of Positano stacked down the cliffs to the sea" loading="lazy" width="600" height="800"></div>
<div class="od-top"><span class="od-idx">08</span><span class="tag">Best: May–Jun</span></div>
<div class="od-body"><span class="od-region">Italy</span><h3 class="od-name">Positano & the Amalfi Coast</h3></div>
<span class="od-go" aria-hidden="true">→</span>
</a>
</li>
</ul>
</div>
<div class="od-marquee" aria-hidden="true">
<div class="od-track">
<div class="od-run">
<span class="od-word">Iceland</span><span class="od-sep"></span>
<span class="od-word">Kyoto</span><span class="od-sep"></span>
<span class="od-word">Patagonia</span><span class="od-sep"></span>
<span class="od-word">Erg Chebbi</span><span class="od-sep"></span>
<span class="od-word">Lofoten</span><span class="od-sep"></span>
<span class="od-word">Sidemen</span><span class="od-sep"></span>
<span class="od-word">Dolomites</span><span class="od-sep"></span>
<span class="od-word">Amalfi</span><span class="od-sep"></span>
</div>
</div>
</div>
<script>
(function(){
const root=document.getElementById('destinations');
if(!root)return;
const R=window.OS&&OS.reduced;
const hasG=typeof gsap!=='undefined';
const items=[...root.querySelectorAll('.od-item')];
const chips=[...root.querySelectorAll('.od-chip')];
const numWrap=root.querySelector('.od-count__n');
const numEl=numWrap.querySelector('span');
const pad=n=>String(n).padStart(2,'0');
/* heading */
if(window.OS)OS.reveal(root.querySelector('.od-title'));
/* ---------- scroll entrance ---------- */
const CLIP_OFF='inset(100% 0% 0% 0% round 18px)', CLIP_ON='inset(0% 0% 0% 0% round 18px)';
const pending=new Set();
if(hasG&&!R){
items.forEach(it=>{
const card=it.querySelector('.od-card');
gsap.set(card,{clipPath:CLIP_OFF});
gsap.set(it.querySelector('.od-media'),{scale:1.22});
pending.add(it);
});
ScrollTrigger.batch(items,{
start:'top 90%',once:true,
onEnter:batch=>{
const live=batch.filter(b=>pending.has(b));
live.forEach(b=>pending.delete(b));
if(!live.length)return;
gsap.to(live.map(b=>b.querySelector('.od-card')),{clipPath:CLIP_ON,duration:1.2,ease:'power4.inOut',stagger:.1,
onComplete(){gsap.set(this.targets(),{clearProps:'clipPath'})}});
gsap.to(live.map(b=>b.querySelector('.od-media')),{scale:1,duration:1.6,ease:'power3.out',stagger:.1,clearProps:'transform'});
}
});
}
/* cards that never scrolled in (e.g. filtered while hidden) are shown instantly */
function flushPending(){
pending.forEach(it=>{
gsap.set(it.querySelector('.od-card'),{clearProps:'clipPath'});
gsap.set(it.querySelector('.od-media'),{clearProps:'transform'});
});
pending.clear();
}
/* ---------- count roll ---------- */
let shown=items.length;
function setCount(n){
if(!hasG||R){numEl.textContent=pad(n);shown=n;return}
const from=shown;shown=n;
const o={v:from};
gsap.killTweensOf(numEl);
gsap.fromTo(numEl,{yPercent:n>from?100:-100},{yPercent:0,duration:.5,ease:'power3.out'});
gsap.to(o,{v:n,duration:.5,ease:'power2.out',onUpdate:()=>{numEl.textContent=pad(Math.round(o.v))}});
}
/* ---------- filter with hand-written FLIP ---------- */
let busy=null;
const match=(it,f)=>f==='all'||it.dataset.cat.split(' ').includes(f);
function apply(f){
chips.forEach(c=>c.setAttribute('aria-pressed',String(c.dataset.filter===f)));
const next=items.filter(it=>match(it,f));
const leaving=items.filter(it=>!it.hidden&&!next.includes(it));
const entering=next.filter(it=>it.hidden);
const staying=next.filter(it=>!it.hidden);
setCount(next.length);
if(!hasG||R){
items.forEach(it=>{it.hidden=!next.includes(it)});
return;
}
flushPending();
if(busy){busy.progress(1).kill();busy=null}
const out=gsap.timeline();
busy=out;
if(leaving.length){
out.to(leaving,{opacity:0,scale:.9,duration:.28,ease:'power2.in',stagger:.03});
}
out.add(()=>{
/* First: record rects of the cards that stay */
const first=new Map(staying.map(it=>[it,it.getBoundingClientRect()]));
/* Change layout */
leaving.forEach(it=>{it.hidden=true;gsap.set(it,{clearProps:'opacity,transform'})});
entering.forEach(it=>{it.hidden=false});
/* Last + Invert + Play */
const tl=gsap.timeline({onComplete:()=>{busy=null;ScrollTrigger.refresh()}});
busy=tl;
staying.forEach(it=>{
const a=first.get(it),b=it.getBoundingClientRect();
const dx=a.left-b.left,dy=a.top-b.top;
if(Math.abs(dx)<1&&Math.abs(dy)<1)return;
tl.fromTo(it,{x:dx,y:dy},{x:0,y:0,duration:.7,ease:'power3.inOut',clearProps:'transform'},0);
});
if(entering.length){
tl.fromTo(entering,{opacity:0,scale:.9,y:24},{opacity:1,scale:1,y:0,duration:.6,ease:'power3.out',stagger:.06,clearProps:'opacity,transform'},.15);
}
/* reset horizontal scroller on mobile */
const g=root.querySelector('.od-grid');
if(g.scrollLeft)g.scrollTo({left:0,behavior:'smooth'});
});
}
chips.forEach(c=>c.addEventListener('click',()=>{
if(c.getAttribute('aria-pressed')==='true')return;
apply(c.dataset.filter);
}));
/* ---------- marquee ---------- */
const mq=root.querySelector('.od-marquee');
const track=root.querySelector('.od-track');
const run=track.querySelector('.od-run');
/* clone enough copies to cover wide screens seamlessly */
const clone=run.cloneNode(true);track.appendChild(clone);
if(!hasG||R)return;
const loop=gsap.to(track,{xPercent:-50,duration:42,ease:'none',repeat:-1});
const skewTo=gsap.quickTo(track,'skewX',{duration:.5,ease:'power3.out'});
let hover=1,hoverT=1,vel=0;
mq.addEventListener('mouseenter',()=>{hoverT=0});
mq.addEventListener('mouseleave',()=>{hoverT=1});
ScrollTrigger.create({trigger:mq,start:'top bottom',end:'bottom top',
onUpdate:self=>{vel=self.getVelocity()},
onToggle:self=>{self.isActive?loop.play():loop.pause()}});
gsap.ticker.add(()=>{
hover+=(hoverT-hover)*.08;
vel*=.92;
const boost=Math.min(Math.abs(vel)/400,5);
loop.timeScale(hover*(1+boost));
skewTo(hover*gsap.utils.clamp(-8,8,-vel/300));
});
})();
</script>
</section>
```
## `sections/03-planner.html`
```html
<section class="op" id="planner" aria-labelledby="op-title">
<div class="wrap">
<div class="op-panel">
<div class="op-media" aria-hidden="true">
<img class="op-img" src="assets/plane.jpg" alt="" loading="lazy" decoding="async">
</div>
<div class="op-shade" aria-hidden="true"></div>
<div class="op-grid">
<div class="op-copy">
<p class="eyebrow op-eyebrow"><span class="dot"></span>Trip planner</p>
<h2 class="op-title" id="op-title">Find your next door</h2>
<p class="op-lede">Tell us how you like to travel. We'll pull three routes from our field notes — no bookings, no pop-ups, just ideas.</p>
</div>
<div class="op-card">
<form class="op-form" data-atom-form novalidate>
<div class="op-row op-row--two">
<label class="op-field">
<span class="op-label">From</span>
<input class="op-input" type="text" name="from" placeholder="Berlin" autocomplete="address-level2" maxlength="40">
</label>
<label class="op-field">
<span class="op-label">Where to</span>
<span class="op-select">
<select class="op-input" name="dest">
<option value="surprise">Anywhere surprising</option>
<option value="iceland">Iceland</option>
<option value="japan">Japan</option>
<option value="chile">Chile</option>
<option value="morocco">Morocco</option>
<option value="norway">Norway</option>
<option value="indonesia">Indonesia</option>
<option value="italy">Italy</option>
</select>
</span>
</label>
</div>
<fieldset class="op-field op-set">
<legend class="op-label">When</legend>
<div class="op-months">
<label class="op-chip"><input type="radio" name="month" value="0"><span>Jan</span></label>
<label class="op-chip"><input type="radio" name="month" value="1"><span>Feb</span></label>
<label class="op-chip"><input type="radio" name="month" value="2"><span>Mar</span></label>
<label class="op-chip"><input type="radio" name="month" value="3"><span>Apr</span></label>
<label class="op-chip"><input type="radio" name="month" value="4"><span>May</span></label>
<label class="op-chip"><input type="radio" name="month" value="5" checked><span>Jun</span></label>
<label class="op-chip"><input type="radio" name="month" value="6"><span>Jul</span></label>
<label class="op-chip"><input type="radio" name="month" value="7"><span>Aug</span></label>
<label class="op-chip"><input type="radio" name="month" value="8"><span>Sep</span></label>
<label class="op-chip"><input type="radio" name="month" value="9"><span>Oct</span></label>
<label class="op-chip"><input type="radio" name="month" value="10"><span>Nov</span></label>
<label class="op-chip"><input type="radio" name="month" value="11"><span>Dec</span></label>
</div>
</fieldset>
<div class="op-row op-row--two">
<div class="op-field">
<span class="op-label" id="op-trav-label">Travellers</span>
<div class="op-stepper" role="group" aria-labelledby="op-trav-label">
<button type="button" class="op-step" data-step="-1" aria-label="Fewer travellers">−</button>
<output class="op-count" name="travellers" aria-live="polite">2</output>
<button type="button" class="op-step" data-step="1" aria-label="More travellers">+</button>
</div>
</div>
<fieldset class="op-field op-set">
<legend class="op-label">Style</legend>
<div class="op-styles">
<label class="op-chip op-chip--wide"><input type="radio" name="style" value="Slow" checked><span>Slow</span></label>
<label class="op-chip op-chip--wide"><input type="radio" name="style" value="Wild"><span>Wild</span></label>
<label class="op-chip op-chip--wide"><input type="radio" name="style" value="Coastal"><span>Coastal</span></label>
<label class="op-chip op-chip--wide"><input type="radio" name="style" value="City"><span>City</span></label>
</div>
</fieldset>
</div>
<button type="submit" class="btn op-submit"><span class="op-submit-txt">Show my routes <span class="arr">→</span></span><span class="op-loading" aria-hidden="true">Pulling routes<i>.</i><i>.</i><i>.</i></span></button>
</form>
<div class="op-results" aria-live="polite" hidden>
<div class="op-results-in">
<div class="op-results-head">
<p class="op-results-title"></p>
<button type="button" class="op-reset">Start over</button>
</div>
<ol class="op-list"></ol>
</div>
</div>
</div>
</div>
</div>
<ul class="op-stats">
<li class="op-stat"><span class="op-num" data-to="42">42</span><span class="op-cap">countries logged</span></li>
<li class="op-stat"><span class="op-num" data-to="318">318</span><span class="op-cap">field notes</span></li>
<li class="op-stat"><span class="op-num" data-to="11">11</span><span class="op-cap">years on the road</span></li>
</ul>
</div>
<style>
.op{background:var(--bg);padding-block:clamp(64px,9vw,128px);overflow:hidden}
.op-panel{position:relative;border-radius:28px;overflow:hidden;background:var(--lilac);transform-origin:50% 0;isolation:isolate}
.op-media{position:absolute;inset:0;overflow:hidden;z-index:-2}
.op-img{position:absolute;left:0;top:-12%;width:100%;height:124%;max-width:none;object-fit:cover;will-change:transform}
.op-shade{position:absolute;inset:0;z-index:-1;pointer-events:none;
background:linear-gradient(100deg,color-mix(in srgb,var(--plum-deep) 62%,transparent) 0%,color-mix(in srgb,var(--plum) 28%,transparent) 48%,transparent 75%)}
.op-grid{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,500px);gap:clamp(28px,4vw,64px);align-items:start;
padding:clamp(28px,5vw,72px);min-height:720px}
.op-copy{color:#fff;max-width:560px;padding-top:clamp(4px,2vw,24px)}
.op-eyebrow{color:#fff;margin:0 0 20px}
.op-title{color:#fff;font-size:clamp(48px,7.4vw,112px);font-weight:500}
.op-lede{margin:24px 0 0;font-size:clamp(16px,1.3vw,18px);line-height:1.6;color:#fff;max-width:440px;
text-shadow:0 1px 12px color-mix(in srgb,var(--plum-deep) 45%,transparent)}
/* card + form */
.op-card{background:var(--surface);border-radius:22px;padding:clamp(20px,2.4vw,32px);
box-shadow:0 30px 60px -30px color-mix(in srgb,var(--plum-deep) 45%,transparent)}
.op-form{display:grid;gap:20px}
.op-row--two{display:grid;grid-template-columns:1fr 1fr;gap:16px}
.op-field{display:grid;gap:8px;min-width:0}
.op-set{border:0;margin:0;padding:0}
.op-label{font-family:var(--mono);font-size:11px;letter-spacing:.08em;text-transform:uppercase;color:var(--mute);padding:0;margin-bottom:8px}
label.op-field .op-label,div.op-field .op-label{margin-bottom:0}
.op-input{width:100%;min-height:48px;padding:12px 14px;border:1px solid var(--line);border-radius:12px;background:var(--surface);
font-size:15px;color:var(--ink);transition:border-color .2s var(--ease),box-shadow .2s var(--ease)}
.op-input::placeholder{color:color-mix(in srgb,var(--mute) 70%,transparent)}
.op-input:focus{outline:none;border-color:var(--apricot);box-shadow:0 0 0 3px color-mix(in srgb,var(--apricot) 35%,transparent)}
.op-select{position:relative;display:block}
.op-select select{appearance:none;-webkit-appearance:none;padding-right:38px;cursor:pointer}
.op-select::after{content:"";position:absolute;right:16px;top:50%;width:8px;height:8px;border-right:1.5px solid var(--ink);border-bottom:1.5px solid var(--ink);
transform:translateY(-70%) rotate(45deg);pointer-events:none}
.op-months{display:grid;grid-template-columns:repeat(6,minmax(0,1fr));gap:6px}
.op-styles{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:6px}
.op-chip{position:relative;display:block;cursor:pointer}
.op-chip input{position:absolute;inset:0;opacity:0;margin:0;cursor:pointer}
.op-chip span{display:flex;align-items:center;justify-content:center;min-height:44px;padding:0 6px;border:1px solid var(--line);border-radius:999px;
font-size:13px;font-weight:600;color:var(--ink);background:var(--surface);transition:background .2s var(--ease),border-color .2s var(--ease),color .2s var(--ease)}
.op-chip:hover span{border-color:var(--plum)}
.op-chip input:checked+span{background:var(--plum);border-color:var(--plum);color:#fff}
.op-chip input:focus-visible+span{box-shadow:0 0 0 3px color-mix(in srgb,var(--apricot) 55%,transparent);border-color:var(--apricot)}
.op-stepper{display:flex;align-items:center;justify-content:space-between;min-height:48px;border:1px solid var(--line);border-radius:12px;padding:2px}
.op-step{width:44px;height:44px;border-radius:10px;font-size:20px;line-height:1;color:var(--plum);transition:background .2s var(--ease)}
.op-step:hover:not(:disabled){background:var(--lilac)}
.op-step:disabled{color:var(--line);cursor:not-allowed}
.op-count{font-family:var(--display);font-size:22px;min-width:2ch;text-align:center}
.op-submit{width:100%;justify-content:center;min-height:52px;font-size:15px;margin-top:4px;position:relative}
.op-loading{display:none}
.op-submit.is-loading .op-submit-txt{display:none}
.op-submit.is-loading .op-loading{display:inline}
.op-submit.is-loading{pointer-events:none}
.op-loading i{font-style:normal;animation:op-dot 1s infinite}
.op-loading i:nth-child(2){animation-delay:.15s}
.op-loading i:nth-child(3){animation-delay:.3s}
@keyframes op-dot{0%,100%{opacity:.2}50%{opacity:1}}
/* results */
.op-results{overflow:hidden}
.op-results-in{padding-top:24px;margin-top:24px;border-top:1px solid var(--line)}
.op-results-head{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:14px}
.op-results-title{margin:0;font-family:var(--mono);font-size:11px;letter-spacing:.08em;text-transform:uppercase;color:var(--mute)}
.op-reset{min-height:44px;padding:0 4px;font-size:14px;font-weight:600;color:var(--plum);text-decoration:underline;text-underline-offset:4px;
text-decoration-color:color-mix(in srgb,var(--plum) 35%,transparent);flex-shrink:0}
.op-reset:hover{text-decoration-color:var(--apricot)}
.op-list{list-style:none;margin:0;padding:0;display:grid;gap:10px}
.op-route{padding:16px 18px;border-radius:16px;background:color-mix(in srgb,var(--lilac) 60%,#fff)}
.op-route-meta{display:flex;flex-wrap:wrap;gap:6px 10px;align-items:center;font-family:var(--mono);font-size:11px;letter-spacing:.06em;text-transform:uppercase;color:var(--plum)}
.op-route-path{margin:8px 0 4px;font-family:var(--display);font-size:20px;line-height:1.15;letter-spacing:-.02em;color:var(--ink)}
.op-route-note{margin:0;font-size:14px;line-height:1.5;color:var(--mute)}
/* stats */
.op-stats{list-style:none;margin:clamp(40px,5vw,72px) 0 0;padding:0;display:grid;grid-template-columns:repeat(3,1fr)}
.op-stat{padding:8px clamp(16px,3vw,40px);border-left:1px solid var(--line);display:grid;gap:6px}
.op-stat:first-child{border-left:0;padding-left:0}
.op-num{font-family:var(--display);font-size:clamp(56px,7vw,104px);line-height:.9;letter-spacing:-.04em;color:var(--plum);font-variant-numeric:tabular-nums}
.op-cap{font-size:15px;color:var(--mute)}
@media (max-width:1024px){
.op-grid{grid-template-columns:minmax(0,1fr) minmax(0,440px)}
}
@media (max-width:860px){
.op-grid{grid-template-columns:1fr;min-height:0;padding:28px 20px 20px;gap:36px}
.op-media{bottom:auto;height:520px}
.op-media::after{content:"";position:absolute;inset:auto 0 0;height:45%;background:linear-gradient(to bottom,transparent,var(--lilac))}
.op-shade{bottom:auto;height:520px;background:linear-gradient(180deg,color-mix(in srgb,var(--plum-deep) 58%,transparent) 0%,color-mix(in srgb,var(--plum) 22%,transparent) 55%,transparent 90%)}
.op-copy{padding-top:8px}
}
@media (max-width:560px){
.op-panel{border-radius:22px}
.op-grid{padding:24px 14px 14px}
.op-row--two{grid-template-columns:1fr}
.op-card{padding:18px 16px}
.op-months{gap:5px}
.op-chip span{font-size:12px;padding:0 2px}
.op-styles{grid-template-columns:repeat(4,minmax(0,1fr))}
.op-stats{grid-template-columns:1fr}
.op-stat{border-left:0;border-top:1px solid var(--line);padding:18px 0}
.op-stat:first-child{border-top:0}
.op-results-head{flex-wrap:wrap}
}
@media (prefers-reduced-motion:reduce){
.op-loading i{animation:none;opacity:1}
.op-img{top:0;height:100%}
}
</style>
<script>
(function(){
const root=document.getElementById('planner');if(!root)return;
const R=window.OS&&OS.reduced;
const panel=root.querySelector('.op-panel'),img=root.querySelector('.op-img');
const form=root.querySelector('.op-form'),res=root.querySelector('.op-results');
const list=root.querySelector('.op-list'),rTitle=root.querySelector('.op-results-title');
const submit=root.querySelector('.op-submit'),count=root.querySelector('.op-count');
const steps=root.querySelectorAll('.op-step');
/* default month = the current month */
const cur=form.querySelector('input[name="month"][value="'+new Date().getMonth()+'"]');if(cur)cur.checked=true;
/* travellers stepper */
let trav=2;
function setTrav(n){trav=Math.max(1,Math.min(8,n));count.textContent=trav;count.value=trav;
steps[0].disabled=trav<=1;steps[1].disabled=trav>=8}
steps.forEach(b=>b.addEventListener('click',()=>setTrav(trav+ +b.dataset.step)));
setTrav(2);
/* route data: 3 ideas per destination */
const MONTHS=['January','February','March','April','May','June','July','August','September','October','November','December'];
const D={
iceland:{name:'Iceland',routes:[
{days:9,path:'Reykjavík → Vík → Höfn',how:'by rental car',note:'two nights on the black sand coast'},
{days:7,path:'Akureyri → Mývatn → Húsavík',how:'on the northern ring',note:'hot pools and a whale-watching morning'},
{days:6,path:'Stykkishólmur → Snæfellsjökull → Arnarstapi',how:'along the peninsula',note:'cliff walks under the glacier'}]},
japan:{name:'Japan',routes:[
{days:10,path:'Kyoto → Nara → Koyasan',how:'by local train',note:'a night in a temple lodging on the mountain'},
{days:8,path:'Kanazawa → Shirakawa-go → Takayama',how:'by bus through the Alps',note:'thatched farmhouses and morning markets'},
{days:9,path:'Onomichi → Shimanami Kaido → Matsuyama',how:'by bicycle over the islands',note:'six bridges and a hot spring finish'}]},
chile:{name:'Chile',routes:[
{days:12,path:'Puerto Natales → Torres del Paine → El Calafate',how:'by bus and on foot',note:'the W trek with refugio nights'},
{days:9,path:'San Pedro de Atacama → Salar → Valle de la Luna',how:'by 4×4',note:'salt flats by day, clear skies by night'},
{days:8,path:'Valparaíso → Pichilemu → Colchagua',how:'down the coast road',note:'painted hills, surf towns and small vineyards'}]},
morocco:{name:'Morocco',routes:[
{days:8,path:'Marrakech → Aït Benhaddou → Merzouga',how:'over the High Atlas',note:'one night in a desert camp'},
{days:7,path:'Fes → Chefchaouen → Tangier',how:'by train and shared taxi',note:'the blue town in the Rif, slow mornings'},
{days:6,path:'Essaouira → Sidi Kaouki → Taghazout',how:'along the Atlantic',note:'fish grills and windy beach walks'}]},
norway:{name:'Norway',routes:[
{days:10,path:'Bergen → Flåm → Geiranger',how:'by train, ferry and bus',note:'a fjord night with the window open'},
{days:9,path:'Svolvær → Reine → Å',how:'through the Lofoten islands',note:'red cabins on stilts and midnight light'},
{days:7,path:'Oslo → Rjukan → Stavanger',how:'across the Telemark',note:'a morning on Preikestolen before the crowds'}]},
indonesia:{name:'Indonesia',routes:[
{days:11,path:'Ubud → Sidemen → Amed',how:'by scooter and driver',note:'rice terraces, then reef snorkelling'},
{days:9,path:'Labuan Bajo → Komodo → Padar',how:'on a liveaboard boat',note:'three nights on deck under the stars'},
{days:10,path:'Yogyakarta → Borobudur → Bromo',how:'by overnight train',note:'a sunrise over the volcano rim'}]},
italy:{name:'Italy',routes:[
{days:8,path:'Bolzano → Val di Funes → Cortina',how:'through the Dolomites',note:'hut-to-hut days with long lunches'},
{days:7,path:'Naples → Procida → Amalfi',how:'by ferry and bus',note:'a quiet island night before the coast'},
{days:9,path:'Bologna → Modena → Parma',how:'by regional train',note:'market mornings and one very long dinner'}]}
};
const SURPRISE=['chile','chile','japan','japan','italy','norway','iceland','iceland','italy','morocco','morocco','indonesia'];
const STYLE={
Slow:{d:3,line:'Fewer stops, longer stays — we added three days to linger.'},
Wild:{d:1,line:'Add a trail day off the map and a night somewhere with no signal.'},
Coastal:{d:0,line:'Keep the water on one side: pick rooms that face the sea.'},
City:{d:-2,line:'Base yourself in town and do the rest as day trips.'}
};
function el(tag,cls,txt){const e=document.createElement(tag);if(cls)e.className=cls;if(txt!=null)e.textContent=txt;return e}
function build(){
const fd=new FormData(form);
const from=(fd.get('from')||'').toString().trim()||'Berlin';
let key=fd.get('dest')||'surprise';
const m=+(fd.get('month')||0),style=fd.get('style')||'Slow';
const surprised=key==='surprise';if(surprised)key=SURPRISE[m];
const d=D[key],s=STYLE[style];
const who=trav===1?'solo':trav+' travellers';
rTitle.textContent=(surprised?'Surprise: ':'')+d.name+' · '+MONTHS[m]+' · from '+from+' · '+who;
list.textContent='';
d.routes.forEach((r,i)=>{
const li=el('li','op-route');
const meta=el('div','op-route-meta');
meta.appendChild(el('span',null,'Route '+(i+1)));
meta.appendChild(el('span',null,'· '+Math.max(4,r.days+s.d)+' days'));
meta.appendChild(el('span',null,'· '+style));
li.appendChild(meta);
li.appendChild(el('p','op-route-path',r.path));
li.appendChild(el('p','op-route-note',cap(r.how)+', '+r.note+'. '+s.line));
list.appendChild(li);
});
}
function cap(t){return t.charAt(0).toUpperCase()+t.slice(1)}
function refresh(){if(window.ScrollTrigger)ScrollTrigger.refresh()}
function open(){
const wasOpen=!res.hidden;res.hidden=false;
if(R){refresh();return}
const cards=list.children;
if(!wasOpen){
gsap.fromTo(res,{height:0},{height:'auto',duration:.7,ease:'power3.inOut',onComplete:()=>{res.style.height='';refresh()}});
}
gsap.fromTo(cards,{y:18,opacity:0},{y:0,opacity:1,duration:.55,ease:'power3.out',stagger:.08,delay:wasOpen?0:.25,clearProps:'transform,opacity'});
}
function close(){
const done=()=>{res.hidden=true;res.style.height='';list.textContent='';refresh()};
form.reset();setTrav(2);if(cur)cur.checked=true;
form.querySelector('.op-input').focus({preventScroll:true});
if(R){done();return}
gsap.to(res,{height:0,duration:.5,ease:'power3.inOut',onComplete:done});
}
form.addEventListener('submit',e=>{
e.preventDefault();
if(submit.classList.contains('is-loading'))return;
submit.classList.add('is-loading');submit.setAttribute('aria-busy','true');
setTimeout(()=>{
build();submit.classList.remove('is-loading');submit.removeAttribute('aria-busy');open();
},R?0:600);
});
root.querySelector('.op-reset').addEventListener('click',close);
/* headline reveal */
const title=root.querySelector('.op-title');
if(window.OS)OS.reveal(title);
if(R||!window.gsap)return;
/* panel grows from ~88% to full wrap width */
const mm=gsap.matchMedia();
mm.add({desk:'(min-width:861px)',mob:'(max-width:860px)'},ctx=>{
gsap.fromTo(panel,{scale:ctx.conditions.desk?.88:.94},{scale:1,ease:'none',
scrollTrigger:{trigger:root,start:'top bottom',end:'top 15%',scrub:true}});
});
/* photo parallax inside the panel */
gsap.fromTo(img,{yPercent:-8},{yPercent:8,ease:'none',
scrollTrigger:{trigger:panel,start:'top bottom',end:'bottom top',scrub:true}});
/* stats count up */
root.querySelectorAll('.op-num').forEach(n=>{
const to=+n.dataset.to,o={v:0};n.textContent='0';
gsap.to(o,{v:to,duration:1.8,ease:'power2.out',
onUpdate:()=>{n.textContent=Math.round(o.v)},
scrollTrigger:{trigger:n,start:'top 88%',once:true}});
});
})();
</script>
</section>
```
## `sections/04-story.html`
```html
<section class="os os-story" id="journal" aria-labelledby="os-story-title">
<style>
.os-story{background:var(--surface);padding-block:clamp(72px,11vw,160px);position:relative}
.os-story__feat{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:clamp(32px,6vw,110px);align-items:start}
/* --- sticky media --- */
.os-story__media{position:sticky;top:12vh;height:76vh;min-height:420px;border-radius:calc(var(--radius) + 10px);overflow:hidden;background:var(--lilac);isolation:isolate}
.os-story__media img{width:100%;height:100%;object-fit:cover;transform-origin:50% 50%;will-change:transform}
.os-story__chip{position:absolute;left:18px;bottom:18px;z-index:2;display:inline-flex;align-items:center;gap:.5em;padding:9px 14px;border-radius:999px;background:#fff;color:var(--ink);
font-family:var(--mono);font-size:11px;letter-spacing:.06em;text-transform:uppercase;box-shadow:0 8px 24px color-mix(in srgb,var(--plum) 18%,transparent)}
.os-story__chip .dot{margin-right:0}
/* --- text column --- */
.os-story__text{padding-top:clamp(0px,6vh,80px);max-width:640px}
.os-story__text .eyebrow{display:block;margin-bottom:22px}
.os-story__h2{font-size:clamp(36px,5.6vw,84px);color:var(--ink)}
.os-story__h2 em{font-style:normal;color:var(--plum)}
.os-story__by{display:flex;align-items:center;flex-wrap:wrap;gap:10px 16px;margin:34px 0 40px;padding-bottom:26px;border-bottom:1px solid var(--line);font-size:14px;color:var(--mute)}
.os-story__av{width:44px;height:44px;border-radius:50%;background:var(--lilac);color:var(--plum);display:grid;place-items:center;font-family:var(--display);font-weight:700;font-size:15px;letter-spacing:0;flex:none}
.os-story__name{color:var(--ink);font-weight:600}
.os-story__sep{width:4px;height:4px;border-radius:50%;background:var(--line)}
.os-story__p{font-size:clamp(16px,1.25vw,18px);line-height:1.75;color:color-mix(in srgb,var(--ink) 88%,var(--mute));margin:0 0 1.35em}
.os-story__p:first-of-type::first-letter{font-family:var(--display);float:left;font-size:4.1em;line-height:.82;padding:.06em .1em 0 0;color:var(--plum)}
.os-story__quote{position:relative;margin:clamp(40px,6vh,72px) 0;padding:0 0 0 clamp(8px,1vw,14px)}
.os-story__quote-mark{display:block;font-family:var(--display);font-size:clamp(90px,9vw,140px);line-height:.6;height:.42em;color:var(--apricot);font-weight:700}
.os-story__quote p{margin:0;font-family:var(--display);font-size:clamp(28px,3.2vw,46px);line-height:1.08;letter-spacing:-.025em;color:var(--plum)}
.os-story__quote cite{display:block;margin-top:18px;font-style:normal;font-family:var(--mono);font-size:12px;letter-spacing:.08em;text-transform:uppercase;color:var(--mute)}
.os-story__facts{margin:0 0 40px;border-top:1px solid var(--line)}
.os-story__fact{display:grid;grid-template-columns:minmax(120px,38%) 1fr;gap:16px;padding:16px 0;border-bottom:1px solid var(--line)}
.os-story__fact dt{font-family:var(--mono);font-size:12px;letter-spacing:.08em;text-transform:uppercase;color:var(--mute);padding-top:3px}
.os-story__fact dd{margin:0;font-weight:600;color:var(--ink)}
/* --- latest notes --- */
.os-story__latest{margin-top:clamp(96px,13vw,190px);padding-top:clamp(40px,5vw,64px);border-top:1px solid var(--line)}
.os-story__head{display:flex;align-items:flex-end;justify-content:space-between;gap:20px 32px;flex-wrap:wrap;margin-bottom:clamp(32px,4vw,56px)}
.os-story__head .eyebrow{display:block;margin-bottom:14px}
.os-story__h3{font-size:clamp(32px,4vw,56px)}
.os-story__grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:clamp(20px,2.4vw,36px);list-style:none;margin:0;padding:0}
.os-story__card{perspective:900px}
.os-story__link{display:block;text-decoration:none;color:inherit;transform-style:preserve-3d;will-change:transform}
.os-story__img{position:relative;aspect-ratio:4/3;border-radius:var(--radius);overflow:hidden;background:var(--lilac);
box-shadow:0 1px 0 color-mix(in srgb,var(--ink) 4%,transparent);transition:box-shadow .5s var(--ease)}
.os-story__img img{width:100%;height:100%;object-fit:cover;transition:transform .9s var(--ease)}
.os-story__img .tag{position:absolute;top:14px;left:14px;z-index:1}
.os-story__card-body{padding:20px 4px 0}
.os-story__card-title{font-family:var(--display);font-size:clamp(21px,1.7vw,26px);line-height:1.12;letter-spacing:-.02em;margin:0 0 12px;font-weight:500}
.os-story__card-title span{background:linear-gradient(var(--plum),var(--plum)) 0 100%/0 2px no-repeat;padding-bottom:2px;transition:background-size .55s var(--ease)}
.os-story__meta{font-family:var(--mono);font-size:12px;letter-spacing:.06em;text-transform:uppercase;color:var(--mute)}
.os-story__link:hover .os-story__img img,.os-story__link:focus-visible .os-story__img img{transform:scale(1.07)}
.os-story__link:hover .os-story__img,.os-story__link:focus-visible .os-story__img{box-shadow:0 26px 48px -22px color-mix(in srgb,var(--plum) 45%,transparent)}
.os-story__link:hover .os-story__card-title span,.os-story__link:focus-visible .os-story__card-title span{background-size:100% 2px}
@media (max-width:900px){
.os-story__feat{grid-template-columns:minmax(0,1fr);gap:36px}
.os-story__media{position:relative;top:auto;height:auto;min-height:0;aspect-ratio:4/5}
.os-story__text{padding-top:0;max-width:none}
.os-story__grid{grid-template-columns:minmax(0,1fr);gap:40px}
}
@media (max-width:480px){
.os-story .wrap{padding-inline:16px}
.os-story__fact{grid-template-columns:1fr;gap:4px}
.os-story__head .btn{width:100%;justify-content:center}
}
@media (prefers-reduced-motion:reduce){
.os-story__img img,.os-story__card-title span,.os-story__img{transition:none}
.os-story__link:hover .os-story__img img{transform:none}
}
</style>
<div class="wrap">
<article class="os-story__feat">
<figure class="os-story__media" style="margin:0">
<img src="assets/story.jpg" alt="Green aurora ribbons over a snowy valley at night" loading="lazy" width="1200" height="1500">
<figcaption class="os-story__chip"><span class="dot" aria-hidden="true"></span>Abisko, Swedish Lapland · 68°N</figcaption>
</figure>
<div class="os-story__text">
<span class="eyebrow"><span class="dot" aria-hidden="true"></span>Field note 118 · Featured</span>
<h2 class="os-story__h2" id="os-story-title">Nine nights under the aurora, and the one that <em>stayed quiet</em></h2>
<div class="os-story__by os-story__rise">
<span class="os-story__av" aria-hidden="true">MK</span>
<span class="os-story__name">Mara Kovač</span>
<span class="os-story__sep" aria-hidden="true"></span>
<span>8 min read</span>
<span class="os-story__sep" aria-hidden="true"></span>
<time datetime="2026-02">Feb 2026</time>
</div>
<p class="os-story__p os-story__rise">The cabin was a ten-minute walk from the station, which at minus twenty-six is long enough to stop feeling your eyelashes. I had booked nine nights because every forecast I read said the odds get better the longer you stay. What nobody tells you is how much of the waiting happens indoors, next to a kettle that never quite stops ticking.</p>
<p class="os-story__p os-story__rise">Most evenings went the same way. Wool socks, a thermos of tea, the torch switched to red, a slow walk down to the frozen lake. Some nights the sky gave everything: green curtains folding over each other, pale pink at the hems, so bright the snow threw shadows. Some nights it gave a faint grey smudge that I only believed in once the camera confirmed it.</p>
<p class="os-story__p os-story__rise">Night six was the clearest of the trip. No cloud, no moon, stars down to the horizon. I stood on the ice for three hours and nothing happened. Not a flicker. I walked back feeling oddly cheated, as if the weather had broken a promise it never made.</p>
<blockquote class="os-story__quote os-story__rise">
<span class="os-story__quote-mark" aria-hidden="true">“</span>
<p>The sky owes you nothing. That's exactly why it's worth waiting for.</p>
<cite>From the notebook, night six</cite>
</blockquote>
<p class="os-story__p os-story__rise">It took me until the train home to understand that the quiet night was the best one. It was the night I stopped chasing and simply watched: the cold, the silence, a village of a few hundred people asleep under a sky the size of a continent. Slow travel is not a style. It is agreeing to be somewhere long enough that the place can surprise you, including by doing nothing at all.</p>
<dl class="os-story__facts os-story__rise">
<div class="os-story__fact"><dt>Getting there</dt><dd>Night train from Stockholm, 17 h</dd></div>
<div class="os-story__fact"><dt>Stay</dt><dd>3 nights minimum</dd></div>
<div class="os-story__fact"><dt>Best window</dt><dd>Late Jan – mid Mar</dd></div>
</dl>
<a class="btn os-story__rise" href="#journal">Read the full note <span class="arr" aria-hidden="true">→</span></a>
</div>
</article>
<div class="os-story__latest">
<div class="os-story__head">
<div>
<span class="eyebrow"><span class="dot" aria-hidden="true"></span>From the journal</span>
<h2 class="os-story__h3">Latest notes</h2>
</div>
<a class="btn btn--line" href="#journal">The whole journal <span class="arr" aria-hidden="true">→</span></a>
</div>
<ul class="os-story__grid">
<li class="os-story__card">
<a class="os-story__link" href="#journal">
<div class="os-story__img"><span class="tag">Hiking</span><img src="assets/a1.jpg" alt="A narrow mountain ridge trail under low cloud" loading="lazy" width="800" height="600"></div>
<div class="os-story__card-body">
<h3 class="os-story__card-title"><span>The ridge walk nobody puts on a postcard</span></h3>
<p class="os-story__meta" style="margin:0">Jan 2026 · 6 min</p>
</div>
</a>
</li>
<li class="os-story__card">
<a class="os-story__link" href="#journal">
<div class="os-story__img"><span class="tag">Rail</span><img src="assets/a2.jpg" alt="A sleeper train carriage window at dusk" loading="lazy" width="800" height="600"></div>
<div class="os-story__card-body">
<h3 class="os-story__card-title"><span>Sleeper trains are back, and they're slow on purpose</span></h3>
<p class="os-story__meta" style="margin:0">Dec 2025 · 5 min</p>
</div>
</a>
</li>
<li class="os-story__card">
<a class="os-story__link" href="#journal">
<div class="os-story__img"><span class="tag">Food</span><img src="assets/a3.jpg" alt="Lantern-lit food stalls at a busy night market" loading="lazy" width="800" height="600"></div>
<div class="os-story__card-body">
<h3 class="os-story__card-title"><span>Eating our way through one night market, stall by stall</span></h3>
<p class="os-story__meta" style="margin:0">Nov 2025 · 7 min</p>
</div>
</a>
</li>
</ul>
</div>
</div>
<script>
(function(){
var root=document.getElementById('journal');
if(!root||!window.OS)return;
var q=function(s){return root.querySelectorAll(s)};
OS.reveal(root.querySelector('.os-story__h2'));
OS.reveal(root.querySelector('.os-story__h3'));
if(OS.reduced)return;
var mm=gsap.matchMedia();
var media=root.querySelector('.os-story__media');
var img=media.querySelector('img');
var chip=media.querySelector('.os-story__chip');
/* clip-path wipe on enter */
gsap.set(media,{clipPath:'inset(100% 0% 0% 0% round 28px)'});
gsap.set(chip,{autoAlpha:0,y:12});
gsap.timeline({scrollTrigger:{trigger:media,start:'top 82%',once:true}})
.to(media,{clipPath:'inset(0% 0% 0% 0% round 28px)',duration:1.4,ease:'power4.inOut'})
.to(chip,{autoAlpha:1,y:0,duration:.6,ease:'power3.out'},'-=.35');
/* scrubbed scale / parallax */
mm.add('(min-width:901px)',function(){
gsap.fromTo(img,{scale:1.15,yPercent:-3},{scale:1,yPercent:3,ease:'none',
scrollTrigger:{trigger:root.querySelector('.os-story__feat'),start:'top 80%',end:'bottom 20%',scrub:true}});
});
mm.add('(max-width:900px)',function(){
gsap.fromTo(img,{scale:1.15},{scale:1,ease:'none',
scrollTrigger:{trigger:media,start:'top bottom',end:'bottom top',scrub:true}});
});
/* paragraphs, quote, facts rise in */
q('.os-story__rise').forEach(function(el){
gsap.from(el,{autoAlpha:0,y:36,duration:1,ease:'power3.out',
scrollTrigger:{trigger:el,start:'top 88%',once:true}});
});
/* cards stagger in */
gsap.from(q('.os-story__card'),{autoAlpha:0,y:60,duration:1.1,ease:'power3.out',stagger:.12,
scrollTrigger:{trigger:root.querySelector('.os-story__grid'),start:'top 85%',once:true}});
/* pointer tilt (fine pointers only) */
mm.add('(hover:hover) and (pointer:fine)',function(){
var off=[];
q('.os-story__link').forEach(function(card){
var rx=gsap.quickTo(card,'rotationX',{duration:.6,ease:'power3.out'});
var ry=gsap.quickTo(card,'rotationY',{duration:.6,ease:'power3.out'});
function move(e){
var r=card.getBoundingClientRect();
var px=(e.clientX-r.left)/r.width-.5, py=(e.clientY-r.top)/r.height-.5;
ry(px*12); rx(-py*12);
}
function leave(){rx(0);ry(0)}
card.addEventListener('pointermove',move);
card.addEventListener('pointerleave',leave);
off.push(function(){card.removeEventListener('pointermove',move);card.removeEventListener('pointerleave',leave);gsap.set(card,{rotationX:0,rotationY:0})});
});
return function(){off.forEach(function(f){f()})};
});
})();
</script>
</section>
```
## `sections/05-moments.html`
```html
<section class="om" id="moments" aria-labelledby="om-title">
<div class="om-word" aria-hidden="true">moments</div>
<div class="om-track">
<div class="om-intro">
<p class="eyebrow"><span class="dot"></span>Moments</p>
<h2 class="om-title" id="om-title">Moments worth passing on</h2>
<p class="om-copy">Small things from the road that stuck with us. Send one to someone who needs a door opened.</p>
<p class="om-count" aria-live="polite"><span class="om-count-now">01</span> <span class="om-count-sep">/</span> 06</p>
<p class="om-hint" aria-hidden="true">Keep scrolling <span class="om-hint-arr">→</span></p>
</div>
<div class="om-rail" role="list">
<article class="om-card" role="listitem">
<div class="om-media"><img src="assets/m1.jpg" alt="A swimmer floating in clear turquoise water in the early morning" loading="lazy" decoding="async"></div>
<p class="om-cap">Floating off Lampedusa, 7 am</p>
<button class="om-share" type="button" aria-label="Share this moment"><svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="18" cy="5" r="2.6"/><circle cx="6" cy="12" r="2.6"/><circle cx="18" cy="19" r="2.6"/><path d="M8.3 10.8l7.4-4.4M8.3 13.2l7.4 4.4"/></svg></button>
</article>
<article class="om-card" role="listitem">
<div class="om-media"><img src="assets/m2.jpg" alt="Hot air balloons drifting over a valley at sunrise" loading="lazy" decoding="async"></div>
<p class="om-cap">Balloons over the valley, first light</p>
<button class="om-share" type="button" aria-label="Share this moment"><svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="18" cy="5" r="2.6"/><circle cx="6" cy="12" r="2.6"/><circle cx="18" cy="19" r="2.6"/><path d="M8.3 10.8l7.4-4.4M8.3 13.2l7.4 4.4"/></svg></button>
</article>
<article class="om-card" role="listitem">
<div class="om-media"><img src="assets/m3.jpg" alt="A camper van parked on an empty desert highway" loading="lazy" decoding="async"></div>
<p class="om-cap">Route 62, van, no signal</p>
<button class="om-share" type="button" aria-label="Share this moment"><svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="18" cy="5" r="2.6"/><circle cx="6" cy="12" r="2.6"/><circle cx="18" cy="19" r="2.6"/><path d="M8.3 10.8l7.4-4.4M8.3 13.2l7.4 4.4"/></svg></button>
</article>
<article class="om-card" role="listitem">
<div class="om-media"><img src="assets/m4.jpg" alt="Two lit tents under a sky full of stars" loading="lazy" decoding="async"></div>
<p class="om-cap">Two tents, a million stars</p>
<button class="om-share" type="button" aria-label="Share this moment"><svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="18" cy="5" r="2.6"/><circle cx="6" cy="12" r="2.6"/><circle cx="18" cy="19" r="2.6"/><path d="M8.3 10.8l7.4-4.4M8.3 13.2l7.4 4.4"/></svg></button>
</article>
<article class="om-card" role="listitem">
<div class="om-media"><img src="assets/m5.jpg" alt="A kayaker paddling beneath a towering glacier wall" loading="lazy" decoding="async"></div>
<p class="om-cap">Paddling under the glacier face</p>
<button class="om-share" type="button" aria-label="Share this moment"><svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="18" cy="5" r="2.6"/><circle cx="6" cy="12" r="2.6"/><circle cx="18" cy="19" r="2.6"/><path d="M8.3 10.8l7.4-4.4M8.3 13.2l7.4 4.4"/></svg></button>
</article>
<article class="om-card" role="listitem">
<div class="om-media"><img src="assets/m6.jpg" alt="Friends on a rooftop looking over city lights at dusk" loading="lazy" decoding="async"></div>
<p class="om-cap">Rooftop, last night in the city</p>
<button class="om-share" type="button" aria-label="Share this moment"><svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="18" cy="5" r="2.6"/><circle cx="6" cy="12" r="2.6"/><circle cx="18" cy="19" r="2.6"/><path d="M8.3 10.8l7.4-4.4M8.3 13.2l7.4 4.4"/></svg></button>
</article>
</div>
</div>
<div class="om-bar" aria-hidden="true"><span class="om-bar-fill"></span></div>
<div class="om-toast" role="status" aria-live="polite"></div>
</section>
<style>
/* ---------- 05 Moments: pinned horizontal strip ---------- */
.om{position:relative;background:var(--bg);overflow:hidden;padding:clamp(64px,10vw,120px) 0 clamp(56px,8vw,96px);isolation:isolate}
.om-word{position:absolute;left:var(--gut);top:clamp(40px,8vw,90px);z-index:-1;pointer-events:none;user-select:none;
font-family:var(--display);font-weight:700;letter-spacing:-.05em;line-height:.8;white-space:nowrap;
font-size:34vw;color:transparent;-webkit-text-stroke:1px var(--plum);opacity:.35;will-change:transform}
/* base = mobile / reduced motion: intro stacked, native scroll-snap rail */
.om-track{position:relative}
.om-intro{padding-inline:var(--gut);max-width:560px}
.om-title{font-size:clamp(40px,9vw,64px);margin:14px 0 16px}
.om-copy{color:var(--mute);margin:0 0 20px;max-width:34ch}
.om-count{font-family:var(--mono);font-size:13px;letter-spacing:.06em;color:var(--ink);margin:0}
.om-count-now{display:inline-block;min-width:2ch;color:var(--plum);font-weight:500}
.om-count-sep{color:var(--mute)}
.om-hint{display:none}
.om-rail{display:flex;gap:14px;margin-top:32px;padding:4px var(--gut) 12px;overflow-x:auto;overscroll-behavior-x:contain;
scroll-snap-type:x mandatory;scroll-padding-inline:var(--gut);scrollbar-width:none;-webkit-overflow-scrolling:touch}
.om-rail::-webkit-scrollbar{display:none}
.om-card{position:relative;flex:0 0 72vw;max-width:340px;aspect-ratio:3/4.3;border-radius:22px;overflow:hidden;
background:var(--lilac);scroll-snap-align:start;box-shadow:0 18px 40px -26px color-mix(in srgb,var(--plum) 55%,transparent)}
.om-media{position:absolute;inset:0;overflow:hidden}
.om-media img{width:100%;height:100%;object-fit:cover;transform-origin:50% 50%;will-change:transform}
.om-cap{position:absolute;left:12px;bottom:12px;right:64px;margin:0;width:max-content;max-width:calc(100% - 24px);
padding:7px 11px;border-radius:999px;background:#fff;color:var(--ink);
font-family:var(--mono);font-size:11px;line-height:1.3;letter-spacing:.02em}
.om-share{position:absolute;top:12px;right:12px;width:42px;height:42px;border-radius:50%;display:grid;place-items:center;
background:var(--plum);color:#fff;transition:background .25s var(--ease),transform .25s var(--ease)}
.om-share svg{width:18px;height:18px;fill:none;stroke:currentColor;stroke-width:1.8;stroke-linecap:round}
.om-share:hover{background:var(--plum-deep);transform:scale(1.08)}
.om-share.is-done{background:var(--apricot);color:var(--ink)}
.om-bar{position:relative;height:3px;margin:22px var(--gut) 0;border-radius:3px;overflow:hidden;
background:color-mix(in srgb,var(--plum) 14%,transparent)}
.om-bar-fill{position:absolute;inset:0;background:var(--apricot);transform:scaleX(0);transform-origin:0 50%}
.om-toast{position:fixed;left:50%;bottom:28px;z-index:60;translate:-50% 0;pointer-events:none;
padding:11px 18px;border-radius:999px;background:var(--surface);color:var(--ink);
font-family:var(--mono);font-size:12px;letter-spacing:.03em;white-space:nowrap;
box-shadow:0 0 0 1px var(--line),0 14px 30px -14px color-mix(in srgb,var(--plum) 45%,transparent);
opacity:0;transform:translateY(16px);transition:opacity .35s var(--ease),transform .35s var(--ease)}
.om-toast.is-on{opacity:1;transform:translateY(0)}
.om-toast::before{content:"";display:inline-block;width:7px;height:7px;border-radius:50%;background:var(--apricot);margin-right:.6em;vertical-align:middle}
/* desktop pinned strip — same condition as the JS matchMedia */
@media (min-width:900px) and (prefers-reduced-motion:no-preference){
.om{height:100vh;height:100svh;padding:0;display:flex;flex-direction:column;justify-content:center}
.om-word{top:50%;left:6vw;font-size:28vw;translate:0 -54%}
.om-track{display:flex;align-items:center;width:max-content;padding-left:var(--gut);padding-right:8vw;will-change:transform}
.om-intro{flex:0 0 auto;width:clamp(320px,30vw,460px);padding:0;margin-right:5vw;max-width:none}
.om-title{font-size:clamp(48px,5.4vw,88px)}
.om-count{font-size:14px;margin-top:8px}
.om-hint{display:block;margin:28px 0 0;font-family:var(--mono);font-size:11px;letter-spacing:.08em;text-transform:uppercase;color:var(--mute)}
.om-hint-arr{display:inline-block;animation:om-nudge 1.6s var(--ease) infinite}
.om-rail{display:flex;align-items:flex-start;gap:2.4vw;margin:0;padding:0;overflow:visible;scroll-snap-type:none}
.om-card{flex:0 0 auto;width:min(26vw,calc((100svh - 190px) * 3 / 4.3));max-width:none}
.om-card:nth-of-type(even){margin-top:6vh}
.om-cap{left:16px;bottom:16px;font-size:12px}
.om-share{top:16px;right:16px;width:46px;height:46px}
.om-bar{position:absolute;left:var(--gut);right:var(--gut);bottom:clamp(28px,5vh,52px);margin:0}
}
@keyframes om-nudge{0%,100%{transform:translateX(0)}50%{transform:translateX(5px)}}
@media (prefers-reduced-motion:reduce){
.om-toast{transition:opacity .2s linear;transform:none}
}
</style>
<script>
(function(){
const sec=document.getElementById('moments');
if(!sec)return;
const OSx=window.OS||{};
const track=sec.querySelector('.om-track');
const rail=sec.querySelector('.om-rail');
const cards=[...sec.querySelectorAll('.om-card')];
const word=sec.querySelector('.om-word');
const fill=sec.querySelector('.om-bar-fill');
const now=sec.querySelector('.om-count-now');
const toast=sec.querySelector('.om-toast');
const title=sec.querySelector('.om-title');
const pad=n=>String(n).padStart(2,'0');
if(OSx.reveal&&title)OSx.reveal(title);
/* which card is nearest the viewport centre */
let last=-1;
function syncCount(){
const mid=innerWidth/2;let best=0,bd=Infinity;
cards.forEach((c,i)=>{const r=c.getBoundingClientRect();const d=Math.abs(r.left+r.width/2-mid);if(d<bd){bd=d;best=i}});
if(best!==last){last=best;now.textContent=pad(best+1)}
}
/* ---------- share: copy to clipboard + toast ---------- */
let tt;
function say(msg){
toast.textContent=msg;toast.classList.add('is-on');
clearTimeout(tt);tt=setTimeout(()=>toast.classList.remove('is-on'),2200);
}
function legacyCopy(text){
try{const ta=document.createElement('textarea');ta.value=text;ta.setAttribute('readonly','');
ta.style.cssText='position:fixed;top:0;left:0;opacity:0;pointer-events:none';
document.body.appendChild(ta);ta.select();const ok=document.execCommand&&document.execCommand('copy');ta.remove();return !!ok}
catch(e){return false}
}
sec.addEventListener('click',e=>{
const btn=e.target.closest('.om-share');if(!btn)return;
const cap=btn.closest('.om-card').querySelector('.om-cap').textContent.trim();
const text='Otherside — '+cap;
const done=ok=>{
if(ok){say('Copied — pass it on');btn.classList.add('is-done');setTimeout(()=>btn.classList.remove('is-done'),1400)}
else say('Copy isn’t available here');
};
if(navigator.clipboard&&window.isSecureContext&&navigator.clipboard.writeText){
navigator.clipboard.writeText(text).then(()=>done(true),()=>done(legacyCopy(text)));
}else done(legacyCopy(text));
});
/* ---------- mobile / reduced: native rail drives count + bar ---------- */
function railSync(){
const max=rail.scrollWidth-rail.clientWidth;
fill.style.transform='scaleX('+(max>0?rail.scrollLeft/max:0)+')';
syncCount();
}
let raf=0;
function onRail(){if(raf)return;raf=requestAnimationFrame(()=>{raf=0;railSync()})}
if(!window.gsap||!window.ScrollTrigger){rail.addEventListener('scroll',onRail,{passive:true});railSync();return}
const mm=gsap.matchMedia();
mm.add('(min-width:900px) and (prefers-reduced-motion:no-preference)',()=>{
const dist=()=>Math.max(0,track.scrollWidth-innerWidth);
const tl=gsap.timeline({
defaults:{ease:'none'},
scrollTrigger:{
trigger:sec,start:'top top',end:()=>'+='+dist(),
pin:true,scrub:1,invalidateOnRefresh:true,anticipatePin:1,
onUpdate:syncCount,onRefresh:syncCount
}
});
tl.fromTo(track,{x:0},{x:()=>-dist(),duration:1},0)
.fromTo(word,{x:0},{x:()=>-dist()*0.32,duration:1},0)
.fromTo(fill,{scaleX:0},{scaleX:1,duration:1},0);
cards.forEach(card=>{
gsap.fromTo(card.querySelector('img'),{scale:1.12},{scale:1,ease:'none',
scrollTrigger:{trigger:card,containerAnimation:tl,start:'left right',end:'right left',scrub:true}});
});
return ()=>{last=-1;now.textContent='01'};
});
mm.add('(max-width:899.98px), (prefers-reduced-motion:reduce)',()=>{
rail.addEventListener('scroll',onRail,{passive:true});
railSync();
return ()=>{rail.removeEventListener('scroll',onRail);fill.style.transform=''};
});
})();
</script>
```
## `sections/06-footer.html`
```html
<footer class="of" id="contact">
<div class="wrap of-inner">
<!-- newsletter -->
<div class="of-news">
<div class="of-news-head">
<p class="eyebrow of-eyebrow"><span class="dot"></span>Postcards</p>
<h2 class="of-title">Postcards, not spam</h2>
</div>
<div class="of-news-body">
<p class="of-copy">One letter a month: a new place, a slow route, and one thing we got wrong. Unsubscribe with one click.</p>
<form class="of-form" data-atom-form novalidate>
<div class="of-field">
<label class="of-sr" for="of-email">Email address</label>
<input class="of-input" id="of-email" name="email" type="email" required autocomplete="email" placeholder="[email protected]" aria-describedby="of-status">
<button class="btn of-submit" type="submit">Subscribe <span class="arr">→</span></button>
</div>
<p class="of-status" id="of-status" role="status" aria-live="polite"></p>
<div class="of-done" hidden>
<svg class="of-check" viewBox="0 0 40 40" aria-hidden="true">
<circle class="of-check-ring" cx="20" cy="20" r="17"/>
<path class="of-check-tick" d="M12.5 20.5l5 5 10-11"/>
</svg>
<p class="of-done-msg">You're on the list. First postcard lands next month.</p>
</div>
</form>
</div>
</div>
<!-- link columns -->
<nav class="of-cols" aria-label="Footer">
<div class="of-col">
<p class="eyebrow of-colhead">Destinations</p>
<ul class="of-list">
<li><a class="of-link" href="#destinations">Iceland</a></li>
<li><a class="of-link" href="#destinations">Japan</a></li>
<li><a class="of-link" href="#destinations">Chile</a></li>
<li><a class="of-link" href="#destinations">Morocco</a></li>
<li><a class="of-link" href="#destinations">Norway</a></li>
<li><a class="of-link" href="#destinations">Italy</a></li>
</ul>
</div>
<div class="of-col">
<p class="eyebrow of-colhead">Journal</p>
<ul class="of-list">
<li><a class="of-link" href="#journal">Field notes</a></li>
<li><a class="of-link" href="#journal">Rail</a></li>
<li><a class="of-link" href="#journal">Food</a></li>
<li><a class="of-link" href="#journal">Hiking</a></li>
</ul>
</div>
<div class="of-col">
<p class="eyebrow of-colhead">Otherside</p>
<ul class="of-list">
<li><a class="of-link" href="#about">About</a></li>
<li><a class="of-link" href="#about">Our rules for writing</a></li>
<li><a class="of-link" href="#contact">Work with us</a></li>
<li><a class="of-link" href="#contact">Press</a></li>
</ul>
</div>
<div class="of-col">
<p class="eyebrow of-colhead">Say hello</p>
<ul class="of-list">
<li><a class="of-link" href="mailto:[email protected]">[email protected]</a></li>
<li><a class="of-link" href="#contact">Instagram</a></li>
</ul>
<p class="of-note">Replies within two days, from a human.</p>
</div>
</nav>
</div>
<!-- giant wordmark -->
<div class="of-mark" aria-label="Otherside">
<div class="of-mark-line" aria-hidden="true">Otherside</div>
</div>
<!-- bottom bar -->
<div class="wrap">
<div class="of-bar">
<span>© 2026 Otherside Journal</span>
<span>Photos: Pexels contributors</span>
<span>Made for slow travellers</span>
<button class="of-top" type="button" aria-label="Back to top">↑</button>
</div>
</div>
</footer>
<style>
.of{position:relative;margin-top:0;background:var(--lilac);color:var(--ink);border-radius:36px 36px 0 0;overflow:hidden;padding-top:clamp(56px,8vw,112px)}
.of-inner{display:grid;gap:clamp(56px,7vw,96px)}
/* newsletter */
.of-news{display:grid;grid-template-columns:1.1fr 1fr;gap:clamp(28px,5vw,80px);align-items:end}
.of-eyebrow{margin:0 0 18px;color:var(--plum)}
.of-title{font-size:clamp(44px,7.4vw,118px);font-weight:300;color:var(--ink)}
.of-copy{margin:0 0 22px;max-width:44ch;color:var(--mute);font-size:17px}
.of-sr{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}
.of-field{display:flex;gap:8px;padding:6px;border-radius:999px;background:var(--surface);
box-shadow:0 0 0 1px color-mix(in srgb,var(--plum) 14%,transparent);transition:box-shadow .25s var(--ease)}
.of-field:focus-within{box-shadow:0 0 0 2px var(--plum)}
.of-form.is-error .of-field{box-shadow:0 0 0 2px var(--plum)}
.of-input{flex:1;min-width:0;border:0;background:transparent;padding:10px 16px;font-size:16px;color:var(--ink);outline:none}
.of-input::placeholder{color:var(--mute)}
.of-submit{flex:none}
.of-status{min-height:1.5em;margin:10px 0 0 18px;font-size:14px;font-weight:600;color:var(--plum)}
.of-status:empty{visibility:hidden}
.of-done{display:flex;align-items:center;gap:14px;padding:14px 20px;border-radius:999px;background:var(--surface);
box-shadow:0 0 0 1px color-mix(in srgb,var(--plum) 14%,transparent)}
.of-done[hidden],.of-field[hidden],.of-status[hidden]{display:none}
.of-done-msg{margin:0;font-weight:600;color:var(--ink)}
.of-check{width:34px;height:34px;flex:none}
.of-check-ring{fill:color-mix(in srgb,var(--apricot) 28%,transparent);stroke:var(--apricot-deep);stroke-width:2;
stroke-dasharray:107;stroke-dashoffset:107;transition:stroke-dashoffset .7s var(--ease)}
.of-check-tick{fill:none;stroke:var(--plum);stroke-width:3;stroke-linecap:round;stroke-linejoin:round;
stroke-dasharray:26;stroke-dashoffset:26;transition:stroke-dashoffset .45s var(--ease) .5s}
.of-done.is-on .of-check-ring,.of-done.is-on .of-check-tick{stroke-dashoffset:0}
.of-done.is-on{animation:of-pop .5s var(--ease)}
@keyframes of-pop{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}
/* columns */
.of-cols{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:32px clamp(20px,3vw,48px);
padding-top:clamp(28px,4vw,48px);border-top:1px solid color-mix(in srgb,var(--plum) 20%,transparent)}
.of-colhead{margin:0 0 16px;color:var(--plum)}
.of-list{list-style:none;margin:0;padding:0;display:grid;gap:8px}
.of-link{position:relative;display:inline-block;text-decoration:none;color:var(--ink);font-weight:500;overflow-wrap:anywhere;transition:color .25s var(--ease)}
.of-link::after{content:"";position:absolute;left:0;right:0;bottom:-2px;height:1px;background:currentColor;
transform:scaleX(0);transform-origin:right;transition:transform .35s var(--ease)}
.of-link:hover,.of-link:focus-visible{color:var(--plum)}
.of-link:hover::after,.of-link:focus-visible::after{transform:scaleX(1);transform-origin:left}
.of-note{margin:14px 0 0;font-size:14px;color:var(--mute);max-width:24ch}
/* wordmark */
.of-mark{overflow:hidden;margin-top:clamp(56px,8vw,112px);padding-inline:var(--gut)}
.of-mark-line{font-family:var(--display);font-weight:300;font-size:19vw;line-height:.8;letter-spacing:-.05em;color:var(--plum);
white-space:nowrap;text-align:center;padding-bottom:.06em}
.of-l{display:inline-block;will-change:transform}
/* bottom bar */
.of-bar{display:flex;flex-wrap:wrap;align-items:center;gap:10px 28px;padding:20px 0 24px;
border-top:1px solid color-mix(in srgb,var(--plum) 24%,transparent);font-family:var(--mono);font-size:12px;color:var(--mute)}
.of-top{margin-left:auto;width:44px;height:44px;border-radius:50%;background:var(--plum);color:#fff;font-size:18px;
display:grid;place-items:center;transition:background .25s var(--ease),transform .25s var(--ease)}
.of-top:hover{background:var(--plum-deep);transform:translateY(-3px)}
@media (max-width:900px){
.of-news{grid-template-columns:1fr;align-items:start}
.of-cols{grid-template-columns:repeat(2,minmax(0,1fr))}
}
@media (max-width:560px){
.of{border-radius:28px 28px 0 0}
.of-field{flex-direction:column;border-radius:22px;padding:8px}
.of-input{padding:12px 12px;width:100%}
.of-submit{width:100%;justify-content:center}
.of-status{margin-left:6px}
.of-done{border-radius:22px}
.of-bar{gap:8px 18px}
}
@media (max-width:340px){
.of-cols{grid-template-columns:1fr}
}
@media (prefers-reduced-motion:reduce){
.of-check-ring,.of-check-tick{transition:none}
.of-done.is-on{animation:none}
.of-link::after,.of-top,.of-field{transition:none}
}
</style>
<script>
(function(){
const root=document.querySelector('.of');if(!root)return;
const OS=window.OS||{};
/* heading reveal */
const title=root.querySelector('.of-title');
if(title&&OS.reveal)OS.reveal(title);
/* newsletter form (no network) */
const form=root.querySelector('.of-form');
const input=form.querySelector('.of-input');
const status=form.querySelector('.of-status');
const field=form.querySelector('.of-field');
const done=form.querySelector('.of-done');
const ok=v=>/^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/.test(v);
form.addEventListener('submit',e=>{
e.preventDefault();
const v=input.value.trim();
if(!v||!ok(v)){
form.classList.add('is-error');input.setAttribute('aria-invalid','true');
status.textContent=v?'That address looks off. Try [email protected].':'Add your email so we know where to send it.';
input.focus();return;
}
form.classList.remove('is-error');input.removeAttribute('aria-invalid');status.textContent='';
field.hidden=true;status.hidden=true;done.hidden=false;
requestAnimationFrame(()=>requestAnimationFrame(()=>done.classList.add('is-on')));
});
input.addEventListener('input',()=>{
if(form.classList.contains('is-error')&&ok(input.value.trim())){
form.classList.remove('is-error');input.removeAttribute('aria-invalid');status.textContent='';
}
});
/* wordmark: split letters + fit to width */
const line=root.querySelector('.of-mark-line');
const word=line.textContent.trim();line.textContent='';
const letters=[...word].map(ch=>{const s=document.createElement('span');s.className='of-l';s.textContent=ch;line.appendChild(s);return s});
const fit=()=>{
line.style.fontSize='19vw';
const avail=line.parentElement.clientWidth-parseFloat(getComputedStyle(line.parentElement).paddingLeft)*2;
const w=line.scrollWidth;
if(w>avail&&w>0)line.style.fontSize=(parseFloat(getComputedStyle(line).fontSize)*avail/w)+'px';
};
fit();
if(document.fonts&&document.fonts.ready)document.fonts.ready.then(()=>{fit();if(window.ScrollTrigger)ScrollTrigger.refresh()});
let rt;addEventListener('resize',()=>{clearTimeout(rt);rt=setTimeout(fit,120)});
if(!OS.reduced&&window.gsap&&window.ScrollTrigger){
gsap.fromTo(letters,{yPercent:100},{yPercent:0,ease:'none',stagger:.08,
scrollTrigger:{trigger:root.querySelector('.of-mark'),start:'top bottom',end:'bottom bottom',scrub:.6}});
}
/* back to top */
root.querySelector('.of-top').addEventListener('click',()=>{
OS.lenis?OS.lenis.scrollTo(0):window.scrollTo({top:0,behavior:OS.reduced?'auto':'smooth'});
});
})();
</script>
```
## `template.html`
```html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="data:,">
<title>Otherside — Travel Journal & Destination Guides</title>
<meta name="description" content="Otherside is an independent travel journal. Every door leads somewhere: handpicked destinations, slow routes and field notes from the far side of the map.">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Bricolage+Grotesque:opsz,[email protected],300;12..96,400;12..96,500;12..96,700&family=Manrope:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.5/gsap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.5/ScrollTrigger.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/lenis.min.js"></script>
<style>
/* ---------- OTHERSIDE locked palette ---------- */
:root{
--bg:#F3F2F6; /* page: cool mist (never cream) */
--surface:#FFFFFF; /* cards, panels */
--ink:#1E1A24; /* text + hairlines only — never a background */
--mute:#6A6474; /* secondary text */
--line:#D9D6DE; /* hairlines */
--plum:#6B2E5F; /* primary: buttons, heading accents (white text ok) */
--plum-deep:#55224B; /* primary hover */
--apricot:#FF9F43; /* accent: tags, dots, highlights (ink text only) */
--apricot-deep:#F08A24;/* accent hover */
--lilac:#E7DEEE; /* soft primary tint: panels, footer */
--display:'Bricolage Grotesque', system-ui, sans-serif;
--body:'Manrope', system-ui, sans-serif;
--mono:'JetBrains Mono', ui-monospace, monospace;
--ease:cubic-bezier(.22,.8,.16,1);
--gut:clamp(16px,3vw,40px);
--radius:18px;
}
*,*::before,*::after{box-sizing:border-box}
html.lenis,html.lenis body{height:auto}
.lenis.lenis-smooth{scroll-behavior:auto !important}
.lenis.lenis-stopped{overflow:hidden}
body{margin:0;background:var(--bg);color:var(--ink);font-family:var(--body);font-size:16px;line-height:1.55;overflow-x:hidden;-webkit-font-smoothing:antialiased}
img{display:block;max-width:100%}
a{color:inherit}
button,input,select{font:inherit;color:inherit}
button{background:none;border:0;cursor:pointer}
:focus-visible{outline:2px solid var(--apricot);outline-offset:3px}
h1,h2,h3{font-family:var(--display);font-weight:400;letter-spacing:-.03em;line-height:.95;margin:0}
em.it{font-style:normal;color:var(--plum)}
/* ---------- shared atoms ---------- */
.wrap{max-width:1440px;margin-inline:auto;padding-inline:var(--gut)}
.eyebrow{font-family:var(--mono);font-size:12px;letter-spacing:.08em;text-transform:uppercase;color:var(--mute)}
.dot{display:inline-block;width:8px;height:8px;border-radius:50%;background:var(--apricot);vertical-align:middle;margin-right:.5em}
.btn{display:inline-flex;align-items:center;gap:.6em;padding:14px 22px;border-radius:999px;background:var(--plum);color:#fff;
font-weight:600;font-size:14px;text-decoration:none;transition:background .25s var(--ease),transform .25s var(--ease)}
.btn:hover{background:var(--plum-deep);transform:translateY(-2px)}
.btn--light{background:var(--surface);color:var(--ink)}
.btn--light:hover{background:var(--apricot);color:var(--ink)}
.btn--line{background:transparent;color:var(--ink);box-shadow:inset 0 0 0 1px var(--ink)}
.btn--line:hover{background:var(--plum);color:#fff;box-shadow:none}
.btn .arr{transition:transform .25s var(--ease)}
.btn:hover .arr{transform:translateX(3px)}
.tag{display:inline-block;padding:4px 10px;border-radius:999px;background:var(--apricot);color:var(--ink);font-family:var(--mono);font-size:11px;text-transform:uppercase;letter-spacing:.06em}
/* split-text words: JS wraps words in .w > span */
.w{display:inline-block;overflow:hidden;vertical-align:top;padding-bottom:.08em;margin-bottom:-.08em}
.w>span{display:inline-block;will-change:transform}
</style>
</head>
<body>
<script>
/* Shared runtime: window.OS
OS.lenis smooth scroll instance (null under reduced motion), synced to ScrollTrigger
OS.split(el) wraps each word of el in <span class="w"><span>word</span></span>, returns the inner spans
OS.reveal(el) split + words rise in when el enters the viewport (once)
OS.reduced true when reduced motion is requested */
window.OS=(function(){
gsap.registerPlugin(ScrollTrigger);
const reduced=matchMedia('(prefers-reduced-motion: reduce)').matches;
let lenis=null;
if(!reduced){
lenis=new Lenis({duration:1.15,easing:t=>Math.min(1,1.001-Math.pow(2,-10*t))});
lenis.on('scroll',ScrollTrigger.update);
gsap.ticker.add(t=>lenis.raf(t*1000));
gsap.ticker.lagSmoothing(0);
}
function split(el){
const walk=n=>{[...n.childNodes].forEach(c=>{
if(c.nodeType===3){const f=document.createDocumentFragment();
c.textContent.split(/(\s+)/).forEach(t=>{if(!t)return;if(/^\s+$/.test(t)){f.appendChild(document.createTextNode(t));return}
const o=document.createElement('span');o.className='w';const i=document.createElement('span');i.textContent=t;o.appendChild(i);f.appendChild(o)});
c.replaceWith(f)}
else if(c.nodeType===1&&c.tagName!=='BR'&&!c.classList.contains('w'))walk(c)})};
walk(el);return [...el.querySelectorAll('.w>span')];
}
function reveal(el,opts={}){
const s=split(el);if(reduced)return s;
gsap.from(s,{yPercent:110,duration:1,ease:'power4.out',stagger:.04,...opts,scrollTrigger:{trigger:el,start:'top 85%',once:true}});
return s;
}
return {lenis,split,reveal,reduced};
})();
</script>
<!-- @include sections/01-hero.html -->
<main>
<!-- @include sections/02-destinations.html -->
<!-- @include sections/03-planner.html -->
<!-- @include sections/04-story.html -->
<!-- @include sections/05-moments.html -->
</main>
<!-- @include sections/06-footer.html -->
<script>window.addEventListener('load',()=>ScrollTrigger.refresh())</script>
</body>
</html>
```