GALACTIC EDITION
GUIDE MK II · 001
EARTH · SOL SECTOR 3
MOSTLY HARMLESS
DON'T
PANIC!
THE HITCHHIKER'S GUIDE TO
(function () { const root = document.getElementById('guide-demo'); if (!root || root.dataset.started) return; root.dataset.started = 'yes'; const panic = root.querySelector('.panic-screen'); const guide = root.querySelector('.guide-screen'); const heading = root.querySelector('#guide-heading'); const subtitle = root.querySelector('#guide-subtitle'); const entries = root.querySelector('#guide-entries'); const lines = [ ["01", "WHERE & WHEN", "#where-when"], ["02", "ACCOMMODATION", "#accommodation"], ["03", "ADVICE FOR GALACTIC HITCHHIKERS", "#advice"] ]; let timers = []; function later(fn, delay) { timers.push(setTimeout(fn, delay)); } /* One shared cursor */ const cursor = document.createElement('span'); cursor.className = 'typing-cursor'; cursor.textContent = '▌'; function typeText(element, text, speed, done) { element.innerHTML = ''; element.appendChild(cursor); let i = 0; function next() { const character = document.createElement('span'); character.textContent = text[i++]; element.insertBefore(character, cursor); if (i < text.length) { later(next, speed); } else if (done) { later(done, 400); } } next(); } function startMenu() { panic.style.display = 'none'; guide.style.display = 'block'; requestAnimationFrame(() => { guide.style.opacity = '1'; }); typeText(heading, "STEVE & JEN'S", 130, () => { typeText(subtitle, 'WEDDING', 45, () => { let index = 0; function addEntry() { if (index >= lines.length) { cursor.remove(); return; } const [number, label, href] = lines[index++]; const link = document.createElement('a'); link.href = href; const numberSpan = document.createElement('span'); numberSpan.textContent = number; const labelSpan = document.createElement('span'); const arrow = document.createElement('b'); arrow.textContent = '›'; link.append(numberSpan, labelSpan, arrow); entries.appendChild(link); typeText(labelSpan, label, 45, () => { if (index < lines.length) { later(addEntry, 350); } else { cursor.remove(); } }); } addEntry(); }); }); } function start() { timers.forEach(clearTimeout); timers = []; cursor.remove(); /* Detect a page refresh */ let isRefresh = false; try { const navigation = performance.getEntriesByType('navigation')[0]; isRefresh = navigation && navigation.type === 'reload'; } catch (e) { isRefresh = false; } /* Check whether the intro has already played */ let introAlreadyPlayed = false; try { introAlreadyPlayed = sessionStorage.getItem('guideIntroPlayed') === 'yes' && !isRefresh; sessionStorage.setItem('guideIntroPlayed', 'yes'); } catch (e) { introAlreadyPlayed = false; } /* Skip intro when returning from another section */ if (introAlreadyPlayed) { panic.style.display = 'none'; guide.style.display = 'none'; guide.style.opacity = '0'; heading.innerHTML = ''; subtitle.innerHTML = ''; entries.innerHTML = ''; startMenu(); return; } /* First visit or refresh: play full intro */ panic.style.display = 'flex'; panic.style.opacity = '0'; panic.style.animation = 'none'; guide.style.display = 'none'; guide.style.opacity = '0'; heading.innerHTML = ''; subtitle.innerHTML = ''; entries.innerHTML = ''; /* Fade in DON'T PANIC */ requestAnimationFrame(() => { panic.style.animation = 'panic-fade-in 1.5s ease forwards'; }); /* Hold, then fade out */ later(() => { panic.style.transition = 'opacity 1s ease'; panic.style.opacity = '0'; later(() => { startMenu(); }, 1000); }, 3800); } start(); })();
≡ GUIDE INDEX
(function () { const root = document.getElementById('where-when-guide'); if (!root) return; const back = root.querySelector('.ww-back'); /* Check whether the typing sequence finished on a previous visit */ let previouslyCompleted = root.dataset.complete === 'yes'; try { previouslyCompleted = previouslyCompleted || sessionStorage.getItem('whereWhenComplete') === 'yes'; } catch (e) {} if (previouslyCompleted) { back.classList.add('ww-back-visible'); } /* Prevent the typing sequence from starting twice */ if (root.dataset.started) return; const kicker = root.querySelector('.ww-kicker'); const heading = root.querySelector('.ww-heading'); const date = root.querySelector('.ww-date'); const intro = root.querySelector('.ww-intro'); const venues = root.querySelector('.ww-venues'); const parking = root.querySelector('.ww-parking'); const backLink = root.querySelector('.ww-back a'); /* Keep the Guide intro from replaying when returning from this section */ backLink.addEventListener('click', function () { try { sessionStorage.setItem('guideIntroPlayed', 'yes'); } catch (e) {} }); const cursor = document.createElement('span'); cursor.className = 'ww-cursor'; cursor.textContent = '▌'; let timers = []; function later(fn, delay) { timers.push(setTimeout(fn, delay)); } function typeText(element, text, speed, done) { element.innerHTML = ''; element.appendChild(cursor); let i = 0; function next() { const character = document.createElement('span'); character.textContent = text[i++]; element.insertBefore(character, cursor); if (i < text.length) { later(next, speed); } else if (done) { later(done, 400); } } next(); } function addVenue(number, label, time, name, description, mapUrl, done) { const block = document.createElement('div'); block.className = 'ww-venue'; const meta = document.createElement('div'); meta.className = 'ww-venue-meta'; const metaLabel = document.createElement('span'); const metaTime = document.createElement('span'); meta.append(metaLabel, metaTime); const venueName = document.createElement('div'); venueName.className = 'ww-venue-name'; const venueDescription = document.createElement('div'); venueDescription.className = 'ww-venue-description'; const map = document.createElement('a'); map.className = 'ww-map'; map.href = mapUrl; map.target = '_blank'; map.rel = 'noopener'; block.append(meta, venueName, venueDescription, map); venues.appendChild(block); typeText(metaLabel, number + ' / ' + label, 35, () => { typeText(metaTime, time, 55, () => { typeText(venueName, name, 65, () => { typeText(venueDescription, description, 22, () => { typeText(map, 'OPEN LOCATION IN MAPS ↗', 35, done); }); }); }); }); } function finishPage() { typeText( parking, 'Parking is available at both locations for spaceships and Earth transport vehicles. Please do not engage Infinite Improbability Drives within 2 light-years of either venue. We accept no responsibility for unexpected weddings, duplicated guests, spontaneously appearing penguins or guests arriving before they left.', 22, () => { /* Finished */ cursor.remove(); root.dataset.complete = 'yes'; try { sessionStorage.setItem('whereWhenComplete', 'yes'); } catch (e) {} } ); } function start() { if (root.dataset.started) return; root.dataset.started = 'yes'; /* Begin the page typing sequence */ typeText(kicker, "THE HITCHHIKER'S GUIDE TO THE WEDDING", 35, () => { typeText(heading, 'WHERE & WHEN', 100, () => { typeText(date, 'SATURDAY 15 JANUARY 2028', 45, () => { typeText( intro, 'The ceremony and reception will both be held on Earth, an utterly insignificant little blue-green planet, roughly 92 million miles from a small, unregarded yellow sun, in the uncharted backwaters of the unfashionable end of the western spiral arm of the Galaxy.', 22, () => { addVenue( '01', 'THE CEREMONY', '16:00 · CEREMONY START', 'Shaw House', 'Church Road, Shaw, Newbury, Berkshire, RG14 2DR', 'https://www.google.com/maps/search/?api=1&query=Shaw+House+Newbury', () => { addVenue( '02', 'THE RECEPTION', '~17:30 · ARRIVAL', 'The Hare & Hounds', 'Bath Road, Speen, Newbury, Berkshire, RG14 1QY', 'https://www.google.com/maps/search/?api=1&query=Hare+and+Hounds+Newbury', finishPage ); } ); } ); }); }); }); } /* Start when the section becomes visible */ if ('IntersectionObserver' in window) { const observer = new IntersectionObserver((items) => { if (items.some(item => item.isIntersecting)) { observer.disconnect(); start(); } }, { threshold: 0.1 }); observer.observe(root); } else { start(); } })();
≡ GUIDE INDEX
(function () { const root = document.getElementById('accommodation-guide'); if (!root) return; const back = root.querySelector('.ac-back'); /* Check whether the typing sequence finished previously */ let previouslyCompleted = root.dataset.complete === 'yes'; try { previouslyCompleted = previouslyCompleted || sessionStorage.getItem('accommodationComplete') === 'yes'; } catch (e) {} if (previouslyCompleted) { back.classList.add('ac-back-visible'); } /* Prevent the typing sequence from starting twice */ if (root.dataset.started) return; const kicker = root.querySelector('.ac-kicker'); const heading = root.querySelector('.ac-heading'); const date = root.querySelector('.ac-date'); const intro = root.querySelector('.ac-intro'); const rooms = root.querySelector('.ac-rooms'); const booking = root.querySelector('.ac-booking'); const backLink = root.querySelector('.ac-back a'); /* Prevent the Guide intro replaying when returning */ backLink.addEventListener('click', function () { try { sessionStorage.setItem('guideIntroPlayed', 'yes'); } catch (e) {} }); const cursor = document.createElement('span'); cursor.className = 'ac-cursor'; cursor.textContent = '▌'; let timers = []; function later(fn, delay) { timers.push(setTimeout(fn, delay)); } function typeText(element, text, speed, done) { element.innerHTML = ''; element.appendChild(cursor); let i = 0; function next() { const character = document.createElement('span'); character.textContent = text[i++]; element.insertBefore(character, cursor); if (i < text.length) { later(next, speed); } else if (done) { later(done, 400); } } next(); } function addRoom(number, name, price, description, done) { const block = document.createElement('div'); block.className = 'ac-room'; const meta = document.createElement('div'); meta.className = 'ac-room-meta'; const roomName = document.createElement('div'); roomName.className = 'ac-room-name'; const roomPrice = document.createElement('div'); roomPrice.className = 'ac-room-price'; const roomDescription = document.createElement('div'); roomDescription.className = 'ac-room-description'; block.append(meta, roomName, roomPrice, roomDescription); rooms.appendChild(block); typeText(meta, number, 35, () => { typeText(roomName, name, 55, () => { typeText(roomPrice, price, 55, () => { typeText(roomDescription, description, 35, done); }); }); }); } function finishPage() { typeText( booking, 'To reserve a room, please call 01635 521152 and quote our wedding date, Saturday 15 January 2028, and the names Steve & Jen. Rooms are being held for our guests until 16 June 2027. Full payment for your room is required when booking.', 22, () => { cursor.remove(); root.dataset.complete = 'yes'; try { sessionStorage.setItem('accommodationComplete', 'yes'); } catch (e) {} } ); } function start() { if (root.dataset.started) return; root.dataset.started = 'yes'; typeText(kicker, "THE HITCHHIKER'S GUIDE TO THE WEDDING", 35, () => { typeText(heading, 'ACCOMMODATION', 100, () => { typeText(date, 'SATURDAY 15 JANUARY 2028', 45, () => { typeText( intro, 'For those wishing to remain on Earth after the celebrations, a limited number of rooms have been reserved at the reception venue. Both options include breakfast.', 22, () => { addRoom( '01 / STANDARD', 'King Room', '£125.00', 'Bed & breakfast', () => { addRoom( '02 / DELUXE', 'Deluxe King Room', '£145.00', 'Bed & breakfast', finishPage ); } ); } ); }); }); }); } /* Start when the section becomes visible */ if ('IntersectionObserver' in window) { const observer = new IntersectionObserver((items) => { if (items.some(item => item.isIntersecting)) { observer.disconnect(); start(); } }, { threshold: 0.1 }); observer.observe(root); } else { start(); } })();
≡ GUIDE INDEX
(function () { const root = document.getElementById('advice-guide'); if (!root) return; const back = root.querySelector('.ad-back'); /* Check whether the typing sequence finished previously */ let previouslyCompleted = root.dataset.complete === 'yes'; try { previouslyCompleted = previouslyCompleted || sessionStorage.getItem('adviceComplete') === 'yes'; } catch (e) {} if (previouslyCompleted) { back.classList.add('ad-back-visible'); } /* Prevent the typing sequence from starting twice */ if (root.dataset.started) return; const kicker = root.querySelector('.ad-kicker'); const heading = root.querySelector('.ad-heading'); const date = root.querySelector('.ad-date'); const intro = root.querySelector('.ad-intro'); const faq = root.querySelector('.ad-faq'); const backLink = root.querySelector('.ad-back a'); /* Keep the Guide intro from replaying when returning */ backLink.addEventListener('click', function () { try { sessionStorage.setItem('guideIntroPlayed', 'yes'); } catch (e) {} }); const cursor = document.createElement('span'); cursor.className = 'ad-cursor'; cursor.textContent = '▌'; let timers = []; function later(fn, delay) { timers.push(setTimeout(fn, delay)); } function typeText(element, text, speed, done) { element.innerHTML = ''; element.appendChild(cursor); let i = 0; function next() { const character = document.createElement('span'); character.textContent = text[i++]; element.insertBefore(character, cursor); if (i < text.length) { later(next, speed); } else if (done) { later(done, 400); } } next(); } function addQuestion(number, question, answer, done) { const block = document.createElement('div'); block.className = 'ad-question'; const questionNumber = document.createElement('div'); questionNumber.className = 'ad-question-number'; const questionTitle = document.createElement('div'); questionTitle.className = 'ad-question-title'; const answerText = document.createElement('div'); answerText.className = 'ad-answer'; block.append(questionNumber, questionTitle, answerText); faq.appendChild(block); typeText(questionNumber, 'QUESTION ' + number, 35, () => { typeText(questionTitle, question, 55, () => { typeText(answerText, answer, 22, done); }); }); } function finishPage() { cursor.remove(); root.dataset.complete = 'yes'; try { sessionStorage.setItem('adviceComplete', 'yes'); } catch (e) {} } function start() { if (root.dataset.started) return; root.dataset.started = 'yes'; typeText(kicker, "THE HITCHHIKER'S GUIDE TO THE WEDDING", 35, () => { typeText(heading, 'ADVICE FOR GALACTIC HITCHHIKERS', 75, () => { typeText(date, 'A FEW THINGS YOU SHOULD KNOW', 45, () => { typeText( intro, 'Travelling to an unfamiliar planet can be a bewildering experience. Fortunately, the Guide has answers to some of the more important questions.', 22, () => { addQuestion( '01', 'Do I really have to bring a towel?', 'No. If you\'re staying overnight, the hotel will provide towels. However, any self-respecting intergalactic hitchhiker should always know where their towel is.', () => { addQuestion( '02', 'So, what\'s Earth like?', 'Mostly harmless.', () => { addQuestion( '03', 'Will there be food?', 'Yes. Further details of the culinary arrangements will be added to this Guide once the local authorities have finalised their plans.', () => { addQuestion( '04', 'What should I wear?', 'Something appropriate for a wedding. Fancy dress is not required, although intergalactic attire is unlikely to cause alarm.', finishPage ); } ); } ); } ); } ); }); }); }); } /* Start when the section becomes visible */ if ('IntersectionObserver' in window) { const observer = new IntersectionObserver((items) => { if (items.some(item => item.isIntersecting)) { observer.disconnect(); start(); } }, { threshold: 0.1 }); observer.observe(root); } else { start(); } })();