Skip to content
VibeFather
Join

2 cases built from this template

Same prompt, different [VARIABLES]. Click one to preview it above.

About this template

A single-page travel journal built around a dotted 3D globe that follows the reader. The writer's trip is stored as six stops in one content file; as each journal entry scrolls past, the globe turns to that city, a warm amber route arcs in from the previous stop with a small light running at its tip, and the city pin starts to pulse. Click any pin, or the link under an entry, and a photo story card opens with a large film-grain photograph, the place name, its coordinates, a short story and buttons to step to the next or previous city. The look is a night atlas: deep ink-navy, a globe made of soft off-white land dots, one amber accent for every route and pin, and a day/night line that drifts slowly across the globe with faint city lights on the dark side. Headlines use an editorial serif with italics, body text a quiet sans, and coordinates and trip days a small mono. A stats strip works out countries, cities and great-circle kilometers from the route data, and a legend filters the arcs by leg of the trip. On phones, and for readers who prefer reduced motion, three.js is never loaded. A flat dotted map in SVG takes its place, with the same arcs drawing in as entries scroll into view, so the page stays light and readable. It suits travel writers, photographers, expedition teams, slow-travel newsletters and anyone telling a story that moves from place to place. To adapt it, change the journal name, the writer, the journey and the focus of the writing, then replace the six stops, coordinates, photos and stories in the content file. The globe, arcs, stats and map rebuild from that data, and nothing else needs to change.

The template prompt

The live demo was built from exactly this text — nothing added. The other cases only change the values of the [VARIABLES].

# Interactive 3D Globe Travel Journal

Build this website exactly as described below. Replace the [VARIABLES] with your own values; keep everything else.

## Brief
A single-page travel journal for an invented writer, built around a dotted three.js globe: as the reader scrolls through six journal entries, the globe turns to each city, an amber route arcs in from the previous stop and the pin lights; pins open photo story cards; a day/night terminator drifts across the globe; phones and reduced-motion readers get a flat animated SVG map with the same arcs.

- Audience: Readers who follow slow, overland travel writing and might subscribe to the writer's monthly letter; also editors and collaborators checking the writer's work.
- Goal: Make the trip feel like one continuous journey the reader travels by scrolling, then turn that attention into monthly-letter signups.
- Tone: Quiet, observant and warm; specific names, numbers and small human moments; never gushing travel-brochure language.

## Stack
- Framework: Vite 5 + vanilla TypeScript
- Styling: Tailwind CSS 3.4
- Libraries: three (r16x, WebGL globe, instanced dots, shaders) (MIT), gsap + gsap/ScrollTrigger (GSAP Standard License (free, commercial use allowed)), lenis (smooth scroll) (MIT), world-atlas (land-110m.json, Natural Earth public-domain data) (ISC), topojson-client (ISC), d3-geo (geoContains, used only by the build-time dot script) (ISC), lucide (vanilla icons) (ISC), @fontsource-variable/fraunces (OFL-1.1), @fontsource-variable/manrope (OFL-1.1), @fontsource/ibm-plex-mono (OFL-1.1), tailwindcss, postcss, autoprefixer (MIT), vite (MIT), typescript (Apache-2.0)
- Build: `npm run build` → static files in `dist/`. No backend, no server code, no external APIs.
- Setup:
  - Scaffold with npm create vite@latest using the vanilla-ts template. One HTML entry at the project root: index.html loading src/main.ts. No framework.
  - vite.config.ts: base './', build { outDir 'dist', sourcemap false, assetsInlineLimit 0, chunkSizeWarningLimit 900 }. Do not copy src/ into dist/.
  - package.json scripts: "dots": "node scripts/build-dots.mjs", "predev": "npm run dots", "prebuild": "npm run dots", "dev": "vite", "build": "tsc --noEmit && vite build". world-atlas, topojson-client and d3-geo are devDependencies used only by the script; three, gsap, lenis and lucide are dependencies.
  - scripts/build-dots.mjs (Node ESM, deterministic): read node_modules/world-atlas/land-110m.json, convert with topojson-client feature(topo, topo.objects.land). (1) land-sphere.json: a Fibonacci sphere of N = 24000 points (golden angle 2.399963 rad; lat = asin(1 - 2(i + 0.5)/N), lon = wrapped i * golden angle in degrees), keep points where d3-geo geoContains(land, [lon, lat]) is true, write a flat number array [lat, lon, lat, lon, ...] rounded to 2 decimals (about 7,000 dots). (2) land-grid.json: a regular grid every 1.8 degrees of longitude and latitude from lat -56 to 82, keep land points, same flat format, for the flat map. (3) city-lights.json: every 9th entry of land-sphere between lat -45 and 65, used for night lights. Write the three files to src/data/ and commit them so a build without the script still works.
  - Tailwind 3.4 through PostCSS, content ['./index.html', './src/**/*.ts']. In tailwind.config.ts extend colors with every color under Design (kebab-case keys: ink, navy, panel, hairline, chalk, muted, amber, amber-soft, atmosphere, night) and fontFamily display ['Fraunces Variable', 'Georgia', 'serif'], sans ['Manrope Variable', 'system-ui', 'sans-serif'], mono ['IBM Plex Mono', 'ui-monospace', 'monospace']. maxWidth { page: '1360px' }.
  - src/fonts.ts imports '@fontsource-variable/fraunces', '@fontsource-variable/fraunces/wght-italic.css', '@fontsource-variable/manrope', '@fontsource/ibm-plex-mono/400.css' and '@fontsource/ibm-plex-mono/500.css'; main.ts imports src/fonts.ts and src/style.css first.
  - File layout: src/content.ts (brand, writer, hero, every stop, legs, stats labels, about, signup and footer copy, typed; the only place copy and coordinates live), src/lib/geo.ts (latLonToVec3, slerp great-circle points, haversineKm with R = 6371, routeStats), src/lib/motion.ts (prefersReducedMotion, canUseWebGL, useGlobe()), src/globe/globe.ts (createGlobe(container, stops, legs) returning { goTo, setArcProgress, setLegFilter, highlight, dispose }), src/globe/shaders.ts (ocean, dots, atmosphere, arc GLSL strings), src/flatmap/flatmap.ts (createFlatMap(container, stops, legs) with the same API), src/ui/journal.ts (ScrollTriggers that drive whichever map is mounted), src/ui/storyCard.ts, src/ui/legend.ts, src/ui/stats.ts, src/ui/signup.ts, src/ui/header.ts.
  - Mode choice in main.ts: useGlobe = matchMedia('(min-width: 768px)').matches && !matchMedia('(prefers-reduced-motion: reduce)').matches && canUseWebGL(). Only then dynamic-import('./globe/globe') so three.js is never downloaded on phones or for reduced motion; otherwise mount the flat map. Listen to both media queries' change events: dispose the mounted map (renderer.dispose, geometries, materials, ScrollTriggers) and mount the other one.
  - Lenis only when motion is allowed: new Lenis({ lerp: 0.09 }); lenis.on('scroll', ScrollTrigger.update); gsap.ticker.add(t => lenis.raf(t * 1000)); gsap.ticker.lagSmoothing(0). Pause Lenis while the story card is open.
  - Coordinate convention used everywhere: latLonToVec3(lat, lon, r) = (r cos(lat) sin(lon), r sin(lat), r cos(lat) cos(lon)) in radians, so lat 0 / lon 0 faces the camera on +Z. To face a stop, tween globeGroup.rotation.y to -lon and rotation.x to clamp(lat, -35deg, 35deg) (default XYZ order), unwrapping rotation.y to the shortest path.
  - Before the first build, download every image listed under Assets into public/images/ with curl -L -o using the exact file names given. The build itself never fetches anything.
  - index.html: lang en, <meta name='theme-color' content='#0A1220'>, <link rel='icon' href='./favicon.svg' type='image/svg+xml'>, <title>[BRAND] — a travel journal by [WRITER]</title>. public/favicon.svg as described under Assets.
  - Write ASSETS.md at the project root as described under Assets. dist/ holds index.html, hashed JS/CSS (three.js in its own lazy chunk), woff2 fonts, favicon.svg and the seven images; keep it under 8 MB.

## Variables
- [BRAND] — Journal name (default: "Slow Meridian"). Invented name of the journal or newsletter. Used in the wordmark, title, footer and signup copy. Never a real publication.
- [WRITER] — Writer's name (default: "Noor Halvorsen"). Invented full name of the writer. Used in the hero kicker, the about section, the portrait alt text and the signup line. Never a real person.
- [JOURNEY] — The journey (default: "eleven months overland from Lisbon to Hanoi"). One lowercase phrase: duration, mode and start/end. Drives the hero headline and decides the six stops, their real coordinates and the three legs.
- [FOCUS] — What the writing is about (default: "night trains, market kitchens and the people who keep them running"). One lowercase phrase naming the subjects of the journal. Drives the hero promise, the entry angles and the about bio.
- [CREDIT] — keep a small "Template by VibeFather" link to https://vibefather.ai/t/interactive-3d-globe-travel-journal in the footer (default: yes; delete this line to remove it)

The copy under Pages & Sections is written for the default values. If your values differ, rewrite every headline, paragraph, label and image for them — same length, tone and structure. Layout, colors, type and motion stay as described.
Keep all copy, image paths and links in one content file (for example `src/content.ts`), keyed by section, so the content can be swapped later without touching components.

Pro template. The design, sections, motion and assets parts of this prompt are for Pro members. The live demo and DESIGN.md stay free.

Join Pro early access →

DESIGN.md

The design system on its own — colors, type, spacing and motion. Apply this look to a project you already have.

---
name: "Interactive 3D Globe Travel Journal"
colors:
  ink: "#0A1220"
  navy: "#0F1B2D"
  panel: "#131F33"
  hairline: "#24324A"
  chalk: "#E8E2D4"
  muted: "#8E9AAE"
  amber: "#F2A541"
  amber_soft: "#FFD08A"
  atmosphere: "#7FA8D9"
  night: "#050A14"
typography:
  display_hero_h1_city_names_section_titles_wordmark_italic_hero_300_at_clamp_44px_6_2vw_96px_line_height_1_02_letter_spacing_0_02em_city_names_400_at_56px_desktop_38px_mobile_italics_carry_emphasis_words_in_amber: { family: "Fraunces", weights: [300, 400, 600] }
  text_body_17px_1_7_desktop_and_16px_mobile_ui_labels_14px_600_buttons_15px_600: { family: "Manrope", weights: [400, 500, 600] }
  mono_coordinates_trip_day_labels_kickers_stat_units_legend_chips_12px_uppercase_with_letter_spacing_0_12em: { family: "IBM Plex Mono", weights: [400, 500] }
spacing: "8px base. Page container max-width 1360px with 40px side padding (20px mobile). Sections 120-140px vertical padding on desktop, 72-88px on mobile. Journal entries min-height 92vh desktop, 80vh mobile. Text measure 60-66ch."
radius: "Buttons and chips 999px; cards, dialog, thumbnails and portrait 10px (thumbnails 6px); inputs 8px. Everything else square."
---

# Interactive 3D Globe Travel Journal — Design System

Apply this design system to an existing project. It describes the look and feel only; for the full site, use prompt.md.

## Colors
- **Ink** `#0A1220` — Page background and renderer clear color
- **Navy** `#0F1B2D` — Ocean sphere day side, header when scrolled (at 78% with backdrop blur 14px)
- **Panel** `#131F33` — Story card, entry cards, stats band, inputs
- **Hairline** `#24324A` — 1px rules, card borders, inactive legend chips, flat-map graticule
- **Chalk** `#E8E2D4` — Land dots, headlines and body text
- **Muted** `#8E9AAE` — Secondary text, captions, coordinates, inactive pins
- **Amber** `#F2A541` — The only accent: route arcs, pins, pulse rings, primary button, active legend chip, focus rings
- **Amber Soft** `#FFD08A` — Arc light head, night-side city lights, hover on amber elements
- **Atmosphere** `#7FA8D9` — Fresnel rim glow around the globe (additive, max 35% intensity)
- **Night** `#050A14` — Night side of the ocean sphere and the flat-map night polygon (at 55%)

## Typography
- **Display: hero H1, city names, section titles, wordmark (italic). Hero 300 at clamp(44px, 6.2vw, 96px), line-height 1.02, letter-spacing -0.02em; city names 400 at 56px desktop / 38px mobile; italics carry emphasis words in Amber** — Fraunces (OFL), weights 300/400/600. Self-host the font files.
- **Text: body 17px/1.7 desktop and 16px mobile, UI labels 14px 600, buttons 15px 600** — Manrope (OFL), weights 400/500/600. Self-host the font files.
- **Mono: coordinates, trip-day labels, kickers, stat units, legend chips; 12px uppercase with letter-spacing 0.12em** — IBM Plex Mono (OFL), weights 400/500. Self-host the font files.

## Spacing & shape
- Spacing: 8px base. Page container max-width 1360px with 40px side padding (20px mobile). Sections 120-140px vertical padding on desktop, 72-88px on mobile. Journal entries min-height 92vh desktop, 80vh mobile. Text measure 60-66ch.
- Radius: Buttons and chips 999px; cards, dialog, thumbnails and portrait 10px (thumbnails 6px); inputs 8px. Everything else square.

## Motion
- Easing: --ease-out cubic-bezier(0.22, 1, 0.36, 1) for reveals and cards; --ease-io cubic-bezier(0.65, 0, 0.35, 1) for globe turns. Motion is slow and cinematic; nothing bounces.
- Hero intro: the globe fades in from opacity 0 and scale 0.92 to 1 over 1800ms; land dots appear in a latitude sweep (shader uniform uReveal 0 to 1 over 2200ms, dots below the threshold have alpha 0); H1 lines rise 24px with 120ms stagger; the scroll cue's chevron bobs 6px every 2.4s.
- Idle spin: in the hero the globe rotates around Y at 0.045 rad/s with a 12deg X tilt; the spin eases out (600ms) as soon as the journal section starts.
- Day/night: uniform uSunDir rotates around the Y axis once every 120 s (independent of the globe's rotation, in world space), with a fixed 18deg declination; the terminator is a soft band smoothstep(-0.12, 0.12, dot(normal, sunDir)).
- Journal scroll (desktop): for each entry i >= 1, a ScrollTrigger (trigger the entry article, start 'top 80%', end 'top 30%', scrub 0.8) maps progress p to arcProgress[i-1] = p and to a globe rotation interpolated from stop i-1 to stop i with --ease-io applied to p; the light head sits at the arc tip while 0 < p < 1; when p reaches 1 the stop's pin switches from Muted to Amber and fires one 700ms ring burst. Entry 0 turns the globe from the idle spin to the first stop over the same scroll range. Scrolling back reverses everything exactly.
- Globe layout: one fixed full-viewport canvas behind the content. In the hero the globe is centered at 62% of the viewport width with a radius of 40% of the viewport height; across the first 30% of the journal section (scrub) it slides to 70% of the width and shrinks to 34%; after the journal ends the canvas fades to 18% opacity under the stats band and to 0 before the about section.
- Pins: amber disc 0.012 radius; active pins emit two rings (scale 1 to 2.6, opacity 0.8 to 0, 2.2s loop, offset 1.1s); hover scales the disc 1.4 and shows an HTML label (city, mono coordinates) projected next to it, cursor pointer.
- Story card: opens with the backdrop fading to Ink at 72% (240ms) and the card rising 24px with opacity (420ms --ease-out); the globe tweens to the opened stop (1.2s --ease-io); prev/next crossfade the photo (300ms) and slide the text 16px in the travel direction.
- Reveals: entry text blocks, stats and about content start at opacity 0, translateY(18px) and settle over 800ms --ease-out at 'top 85%' (once), siblings staggered 80ms. Stats numbers count up over 1400ms with tabular figures.
- Flat map (mobile and reduced-motion): arcs draw by stroke-dashoffset over 1200ms --ease-out when their entry crosses 60% of the viewport; pins pulse with a CSS keyframe; the night polygon drifts 1deg of longitude per second. With prefers-reduced-motion: no Lenis, no pulses, no drift, no count-ups, no reveals; all arcs are drawn and all pins Amber from the start; card opens without movement.

## Imagery
Seven photographs, all treated as one set: six journey photos in a muted teal-and-amber 35mm film look (dusk, blue hour and warm low light, fine grain, no faces in close-up, no readable signs or logos) plus a fictional portrait of the writer in the same light. Every photo gets the same treatment in CSS: filter saturate(0.9) contrast(1.05), and a ::after film-grain overlay made from an inline SVG feTurbulence (baseFrequency 0.9, numOctaves 2) data URI at 14% opacity with mix-blend-mode overlay, plus a 1px inner border in Chalk at 8%. Everything else is drawn in code: the dotted globe, arcs, pins, atmosphere, stars, the flat SVG map, the favicon and the legend swatches. No map tiles, no Earth textures, no satellite imagery.

## Tone
Quiet, observant and warm; specific names, numbers and small human moments; never gushing travel-brochure language.

---
— Prompt by VibeFather · https://vibefather.ai/t/interactive-3d-globe-travel-journal · CC BY-NC 4.0 (non-commercial; commercial use needs a VibeFather Pro license) · Request removal: https://vibefather.ai/takedown