🏔️

#NorrisAdventures

Aaron + Caroline · Mountains, Mischief, Marriage

Jul 25 – Aug 1, 2026 · Canadian Rockies

🎂 Banff 40 + 14th Anniversary
✈️ Overview
Duration
8 Days
Jul 25 – Aug 1
Travelers
Aaron + Caroline
Park Pass
FREE ✓
Canada Strong Pass
Car
RAM 1500+
Primary: Caroline
🇨🇦 Canada Strong Pass FREE
No pass purchase needed. FREE park admission Jun 19 – Sep 7, 2026. Just arrive at the gate.

✈️ Flights — American Airlines

🛫
OUT — Sat Jul 25 BHM 6:33 AM → ORD → YYC 1:33 PM
AA 4181 + AA 3183 · Reference saved in Private Trip Vault
🛬
BACK — Sat Aug 1 YYC 7:00 AM → ORD → BHM 3:02 PM
AA 1633 + AA 3971 · Reference saved in Private Trip Vault
💳
Chase booking saved in Private Trip Vault Chase Sapphire Reserve · Hilton Diamond status

🚗 Car — Enterprise YYC

📋
Confirmation saved in Private Trip Vault RAM 1500 or similar pickup
Chase booking saved privately
👤
Primary Driver: Caroline Norris Ask to add Aaron as additional driver (spouse — often free)
📅
Pickup: 2:00 PM Jul 25 · Drop: 11:30 PM Jul 31 Counter hours: 7 AM–11:59 PM & midnight–1 AM. CLOSED 1–7 AM.
📞
833-619-3654 Enterprise YYC · Ask for SUV swap (pickup bed is open, luggage rides in cab)

🏨 Hotels

🔴
Jul 25 — BOOK TONIGHT BOOK NOW 🔴
Canmore (recommended). See options below ↓
🏔️
Jul 26–29 · Peaks Hotel & Suites
218 Lynx St, Banff · 3 nights
Confirmation saved privately · 403-762-2226
🌲
Jul 29–31 · Paradise Lodge & Bungalows
Wilson Cabin · Lake Louise area · 2 nights
Confirmation saved privately · 403-522-3595
🔴
Jul 31 — BOOK TONIGHT BOOK NOW 🔴
YYC in-terminal hotel (Marriott Bonvoy)
🔐
Private Trip Vault
Airline, car, and hotel confirmation details are encrypted instead of exposed on the public page.

🏕️ Jul 25 Hotel — Canmore Options

UNVERIFIED ⚠️

Call to book — availability unverified. Canmore is ~15 min before Banff on Hwy 1.

#1 Blackstone Mountain Lodge (Autograph/Marriott)
170 Kananaskis Way, Canmore
403-678-2626
🥈
#2 Solara Resort
187 Kananaskis Way, Canmore
403-609-0086
🥉
#3 Falcon Crest
190 Kananaskis Way, Canmore
403-678-6150

🛫 Jul 31 Hotel — YYC In-Terminal

UNVERIFIED ⚠️

Both inside terminal — no shuttle needed. Book via Marriott Bonvoy.

#1 Calgary Airport Marriott In-Terminal
marriott.com/en-us/hotels/yycxa
🥈
#2 Delta Hotels Calgary Airport In-Terminal
marriott.com · Both are Marriott Bonvoy properties

🐻 Bear Spray — BUY DAY 1

REQUIRED
Cannot fly with bear spray. Purchase on arrival at:
Monod Sports
129 Banff Ave · 403-762-4571
Open 10 AM – 8 PM daily

⛵ Moraine Lake — Shuttle Required

BOOK NOW 🔴
Closed to personal vehicles. Shuttle only.
Book at 8:00 AM MT on Saturday Jul 25 (from ORD layover)
Departs: Lake Louise Park & Ride, 1 Whitehorn Rd
Every 30 min · 6:30 AM – 6:00 PM
🆘 Emergency Contacts
📅 Day-by-Day Plan
SAT JUL 25 · Day 1

🛬 Arrival Day — Calgary

Land 1:33 PM · Drive to Canmore
BOOK TONIGHT 🔴 LODGING UNBOOKED ⚠️
  • Land YYC 1:33 PM MT · Clear customs
  • 🚗 Enterprise counter — pick up car at 2:00 PM
    Caroline signs as primary. Ask to add Aaron as additional driver (spouse — often free).
  • 💡 Ask for SUV swap — RAM 1500 pickup bed is open; luggage rides in cab. Request mid-size/standard SUV if available.
  • 🛒 Drive west on Trans-Canada Hwy 1 → Canmore (~1h15m, 105 km)
    Last real grocery store before park prices. Stock up!
  • 🐻 Buy bear spray in Canmore if an outfitter is open
  • 🏨 Check into hotel (call tonight if not already booked)
  • 🌙 EARLY NIGHT — you've been up since ~4 AM CT. Rest up.

🔴 Book Tonight — Hotel Options

#1 Blackstone Mountain Lodge — 403-678-2626
#2 Solara Resort — 403-609-0086
#3 Falcon Crest — 403-678-6150

⏰ DO THIS FROM ORD LAYOVER

8:00 AM MT — Book Moraine Lake shuttle at reservation.pc.gc.ca or 1-877-737-3783 for Mon Jul 27.
SUN JUL 26 · Day 2

🏔️ Check Into Banff

Peaks Hotel check-in 4 PM
  • Wide open day before 4 PM — relax, explore
  • 🌡️ Banff Upper Hot Springs — 10 AM–10 PM daily. Go late for fewer crowds + best mountain view at dusk.
  • 🎿 Bow Falls → Surprise Corner viewpoint → Banff Ave stroll
  • 🌅 Vermilion Lakes — stunning sunset, only 5 min from town
  • 4:00 PM → Check into Peaks Hotel & Suites, 218 Lynx St

🚡 Option: Banff Gondola + Sky Bistro

BOOK IN ADVANCE UNVERIFIED HOURS ⚠️
Banff Gondola · 100 Mountain Ave · 403-762-7486
Sky Bistro at summit — 11 AM–8:30 PM (hours unverified 2026) · OpenTable
Must book gondola ticket separately from restaurant.
MON JUL 27 · Day 3

⭐ Moraine Lake Sunrise — THE DAY

Shuttle required · Book at 8 AM MT Saturday
SHUTTLE REQUIRED 🔴 ⭐ SIGNATURE DAY
  • 🚌 Depart Lake Louise Park & Ride (1 Whitehorn Rd) on earliest shuttle
    Shuttles every 30 min, 6:30 AM–6:00 PM. Earlier = fewer crowds + sunrise light.
  • 📸 Rockpile Trail — Ten Peaks iconic shot. Short walk, incredible payoff. Aaron: Insta360 X5 wide angle!
  • 🚶 Lake Connector Trail → Lake Louise shoreline
  • 🚣 Canoe at Fairmont Boathouse — 111 Lake Louise Dr · 403-522-3511
    ~C$165–170/hr UNVERIFIED 2026 ⚠️
  • Drive time Banff → Lake Louise Park & Ride: ~45 min

🍵 Option: Lake Agnes Teahouse Add-On

CASH ONLY 💵
7 km RT · ~380 m gain · ~2.5 hrs · No reservations
Trail from Lake Louise. Best payoff-to-effort ratio.
TUE JUL 28 · Day 4

🗺️ Adventure Day

Big hike or Icefields Parkway
⚠️ Grizzly House CLOSED TUESDAYS — save for another night

🥾 Option A: Plain of Six Glaciers

11.4 km RT · 595 m gain · 3–5 hrs · Quieter than Lake Agnes
Teahouse at summit — CASH ONLY 💵

🚗 Option B: Icefields Parkway Sampler

Herbert Lake → Bow Lake → Peyto Lake/Bow Summit (stunning!) → Mistaya Canyon
Turn at Saskatchewan River Crossing (~1h30m each way) or go full day to Columbia Icefield (~2h15m).

🌊 Option C: Johnston Canyon + Lakes

Johnston Canyon (5.2 km RT catwalks — go before 9 AM!) + Lake Minnewanka + Two Jack Lake
WED JUL 29 · Day 5

🌲 Banff → Lake Louise Transfer

Peaks checkout 11 AM · Paradise check-in ~4 PM
  • Checkout Peaks Hotel by 11 AM
  • 🛣️ Drive Bow Valley Parkway (Hwy 1A) instead of Hwy 1 — slower, far prettier, best wildlife odds
    Verify current access restrictions before departing.
  • 🏔️ Stop: Johnston Canyon (if not done yet), Castle Mountain viewpoint
  • Lake Louise village lunch
  • ~4 PM → Check into Paradise Lodge & Bungalows — Wilson Cabin
    Confirmation saved in Private Trip Vault · 403-522-3595
  • 🌅 After 7 PM → Lake Louise lakeshore walk — tour buses gone, best light of day
THU JUL 30 · Day 6

🏔️ Lake Louise Base Day

Your choice — hike, Yoho, or rest

🥾 Option A: Big Hike

Whichever signature hike you haven't done yet — Plain of Six Glaciers or Johnston Canyon

🌿 Option B: Yoho National Park

~40 min from Lake Louise (still Mountain Time)
• Natural Bridge
• Emerald Lake — 5.2 km flat loop (~1.5 hrs)
• Takakkaw Falls
• Lunch at Emerald Lake Lodge · 250-343-6321

😴 Option C: Rest Day

Sleep in · Lakeshore walk · Spa · Early dinner — you've earned it
FRI JUL 31 · Day 7

🎂 Last Day + Drive to YYC

Paradise checkout 11 AM · Car drop 11:30 PM
⚠️ HARD DEADLINES — DO NOT MISS
Leave Banff by 8:30 PM → 1h30m drive to YYC → Drop car 11:30 PM
Enterprise CLOSED 1–7 AM. 11:30 PM is the correct slot. Do not change it.
Elk on highway at night — do not let the day run long.
  • Checkout Paradise Lodge by 11 AM
  • 🌅 Morning at Lake Louise lakeshore
  • 🚗 Drive back toward Banff
  • 🎂 Birthday dinner in Banff or Canmore
    The Bison (Banff) or The Grizzly House (Banff) are great. Leave Banff by 8:30 PM at latest.
  • 🛣️ Depart Banff ≤ 8:30 PM → YYC via Hwy 1
  • 🚗 Drop car at Enterprise YYC — 11:30 PM
  • 🏨 Check into YYC in-terminal hotel (booked via Marriott Bonvoy)
SAT AUG 1 · Day 8

🛫 Home — Bon Voyage!

YYC 7:00 AM → ORD → BHM 3:02 PM
  • ~4:15 AM — hotel shuttle or walk to terminal (you're in-terminal!)
  • In terminal by 4:45–5:00 AM
  • 🇺🇸 US Preclearance at YYC — clear before boarding
  • ✈️ YYC 7:00 AM → ORD · AA 1633
  • ✈️ ORD → BHM 3:02 PM · AA 3971 · airline reference saved in Private Trip Vault
  • 🏡 BHM 3:02 PM — home sweet home
🍽️ Restaurants

The Bison

⭐ BEST DINNER BANFF
📍 211 Bear St #213, Banff 403-762-5550
🕐 4:00 PM – 9:30 PM daily
Rocky Mountain cuisine. Best overall dinner in Banff. Book early — fills fast.
BOOK NOW 🔴
Reserve via opentable.ca (search "The Bison Banff") or walk-in early (before 5:30 PM).

Sky Bistro

BOOK IN ADVANCE 🔴
📍 Banff Gondola summit, 100 Mountain Ave 403-762-7486
Hours unverified 2026 ⚠️ ~11 AM–8:30 PM
Dining at the gondola summit. Must book gondola ticket separately. Reserve via OpenTable.

The Grizzly House

CLOSED TUESDAYS ⚠️
📍 207 Banff Ave 403-762-4055
Fondue + exotic meats. Fun, memorable night out. Do NOT go on Tuesday. Available Sun / Mon / Wed / Thu / Fri / Sat.

Rimrock Banff (Emblems Collection)

CALL TO CONFIRM ⚠️
Now rebranded "Rimrock Banff, Emblems Collection" (Accor). Call to confirm fine dining availability. Eden restaurant may be replaced — verify when booking.

Lake Agnes Teahouse

CASH ONLY 💵
🥾 7 km RT from Lake Louise · ~2.5 hrs
No reservations. Cash only. Hike up and earn your tea. Best payoff-to-effort in the park.

Plain of Six Glaciers Teahouse

CASH ONLY 💵
🥾 11.4 km RT from Lake Louise · 3–5 hrs
No reservations. Cash only. Quieter than Lake Agnes. Stunning glacier views at the teahouse.

Emerald Lake Lodge

📍 1 Emerald Lake Rd, Field BC 250-343-6321
Lunch on your Yoho National Park day. Perfect stop after Natural Bridge. Rustic mountain dining.
🥾 Hikes

Lake Agnes Teahouse

Moderate
📏 7 km RT
⬆️ 380 m gain
⏱ ~2.5 hrs
📍 Lake Louise
Start from Lake Louise Lakeshore trailhead. Iconic teahouse reward at the top. CASH ONLY 💵 No reservations.
⭐ Best payoff-to-effort 🌫️ Check smoke conditions

Plain of Six Glaciers

Moderate
📏 11.4 km RT
⬆️ 595 m gain
⏱ 3–5 hrs
📍 Lake Louise
Start from Lake Louise Lakeshore. Quieter than Lake Agnes. Stunning glacier views. CASH ONLY 💵
🌫️ Check smoke conditions

Moraine Lake Rockpile

Easy SHUTTLE REQUIRED 🔴
📏 0.8 km
⏱ 15 min
📍 Moraine Lake
The iconic Ten Peaks viewpoint. No personal vehicles — shuttle only. Book at reservation.pc.gc.ca.
⭐ THE SHOT

Emerald Lake Loop (Yoho)

Easy
📏 5.2 km
⬆️ Flat
⏱ ~1.5 hrs
📍 Field, BC (~40 min)
Perfect rest-day hike. Stunning turquoise lake in Yoho National Park. Pair with Natural Bridge & Takakkaw Falls.

Johnston Canyon

Easy
📏 5.2 km RT
⏱ 2 hrs
📍 Bow Valley Pkwy
Catwalk trail through dramatic canyon. Go before 9 AM — gets very crowded. Great on transfer day (Hwy 1A).
💡 Before 9 AM only

Peyto Lake / Bow Summit

Easy
🚗 Drive-up viewpoint
📏 Short paved walk
📍 Icefields Parkway
Highest paved point on Icefields Parkway. Wolf-head lake view is otherworldly. Stop on Icefields Parkway day.

Tunnel Mountain

Easy
📏 4.6 km RT
⏱ ~2 hrs
📍 In-town Banff
In-town hike with panoramic Banff valley views. Good sunset hike. Walk from your hotel.
✅ Master Checklist

🔴 Book Immediately

🍽️ Dining Reservations

✈️ Travel

🐻 Safety

🚗 Car & Arrival

🎉

Happy 40th, Aaron!

Banff & Lake Louise with your favorite person. This is the one.

💞 14 Years
🥂

Second Honeymoon Energy

One milestone birthday, fourteen years married, and a mountain-sized excuse to flirt like newlyweds.

🎲 Date-Night Spinner

Tap for a low-pressure anniversary mission.

❤️ The 14-Minute Check-In

  • Four minutes: favorite moment from today
  • Four minutes: something you appreciate about the other
  • Four minutes: what would make tomorrow great
  • Two minutes: a no-phone kiss

🏆 Winner’s Privilege

Today’s game winner chooses one mutually welcome reward: dessert, music, who gets a massage, the sunset stop, or the first card drawn in Norris Nights. Winning never overrides a boundary.

🔏 Private by design

The adults-only deck and travel confirmations are encrypted. Your passphrase is never stored or uploaded. Private ratings stay encrypted on this phone, the app relocks when hidden, and screenshots cannot be prevented on a web app.

🤠 Aaron
0
pts
VS
🌸 Caroline
0
pts
Me
today
vs
Them
today
Scoring: 1 pt per 1,000 steps · +10 pts if ≥10,000 · +25 pts if ≥20,000 · +5 pts daily winner

Apple Shortcut: Shortcuts app → + → Get Health Samples (Steps) → Get variable → Open URLs with https://norris-banff-2026.pages.dev/?player=aaron&steps=[count]
Trip History
Your progress 0/20 · 0 pts
Complete a row, col, or diagonal = +25 pts
Lines: 0 · Pts: 0
First to claim each iconic shot wins +5 pts. No backsies.
🤠 Aaron: 0 🌸 Caroline: 0
Complete each day's challenge = +10 pts
Spot each plate first = +3 pts. Tap to claim. Canadian clean sweep (+20 pts bonus)!
🤠 0 🌸 0
Guess the elevation (in meters) before revealing. Closest wins +5 pts.
Draft Canadian snacks to try. You pick it = you're buying it for both of you to try.
Ready for a challenge?
Tap Generate to get a quirky Banff challenge.
🏔️

#NorrisAdventures

Who's holding this phone?

Log Steps

0
// ═══════════════════════════════════════════════════════════════════ // #NORRISADVENTURES — GAME ENGINE // ═══════════════════════════════════════════════════════════════════ // ── DATA ────────────────────────────────────────────────────────── const TRIP_DATES = ['2026-07-25','2026-07-26','2026-07-27','2026-07-28','2026-07-29','2026-07-30','2026-07-31','2026-08-01']; const HUNT_ITEMS = [ {id:'h1',em:'🦌',name:'Elk in Banff Townsite',desc:'Spotted elk wandering main streets or near hotel at dawn/dusk'}, {id:'h2',em:'🐑',name:'Bighorn Sheep on the Road',desc:'Sheep literally stopping traffic near Lake Minnewanka'}, {id:'h3',em:'🐿️',name:'Ground Squirrel Says Hi',desc:'One approaches you begging on a trail'}, {id:'h4',em:'🏰',name:'Fairmont Castle Shot',desc:'Perfect framed photo of Fairmont Banff Springs'}, {id:'h5',em:'💧',name:'Johnston Canyon Spray Zone',desc:'Stood inside the Upper Falls spray alcove and got soaked'}, {id:'h6',em:'🛶',name:'Paddled a Glacial Lake',desc:'Canoe or kayak on Moraine, Louise, or Minnewanka'}, {id:'h7',em:'🌡️',name:'Upper Hot Springs Soak',desc:'Actually got in the sulfur pool — not just looked at it'}, {id:'h8',em:'☕',name:'Backcountry Teahouse Tea',desc:'Lake Agnes or Plain of Six Glaciers (must hike there)'}, {id:'h9',em:'🧊',name:'Touched Real Glacier Ice',desc:'Foot on the Athabasca Glacier on Icefields Parkway'}, {id:'h10',em:'🚡',name:'Gondola Summit Selfie',desc:'Selfie from Sulphur Mountain boardwalk above the clouds'}, {id:'h11',em:'🌄',name:'Perfect Lake Reflection',desc:'Flawless mountain mirror image in still water'}, {id:'h12',em:'🍁',name:'Real Maple Leaf Found',desc:'On an actual tree — not a flag, souvenir, or logo'}, {id:'h13',em:'🍺',name:'Canadian Craft Beer + Mountains',desc:'Local Alberta beer with peaks in the background'}, {id:'h14',em:'🦫',name:'Hoary Marmot Spotted',desc:'Fat furry marmot on a high-elevation trail'}, {id:'h15',em:'🌙',name:'Stars / Milky Way',desc:'Clear night sky clearly visible from Banff area'}, {id:'h16',em:'🪨',name:'Touched a Hoodoo',desc:'Physical contact with a hoodoo rock on Hoodoos Trail'}, {id:'h17',em:'🍽️',name:'Ate Wild Game',desc:'Elk, bison, venison, or caribou on an actual plate'}, {id:'h18',em:'🌊',name:'Two Waterfalls in One Day',desc:'Visited two separate waterfalls the same day'}, {id:'h19',em:'🇨🇦',name:'First Canadian Purchase',desc:'Documented the receipt — your first CAD transaction'}, {id:'h20',em:'🏔️',name:'10km+ Hike Completed',desc:'Any single hike over 10 km round trip'} ]; const BINGO_CELLS = [ {em:'🦌',name:'Elk'},{em:'🐻',name:'Grizzly'},{em:'🐨',name:'Black Bear'},{em:'🐑',name:'Bighorn'},{em:'🐐',name:'Mtn Goat'}, {em:'🦌',name:'Mule Deer'},{em:'🐺',name:'Coyote'},{em:'🦔',name:'Marmot'},{em:'🦫',name:'Beaver'},{em:'🦦',name:'Otter'}, {em:'🦅',name:'Golden Eagle'},{em:'🦅',name:'Osprey'},{em:'🦢',name:'Common Loon'},{em:'🐦‍⬛',name:'Raven'},{em:'🐦',name:"Nutcracker"}, {em:'🐿️',name:'Ground Squirrel'},{em:'🐭',name:'Pika'},{em:'🦔',name:'Porcupine'},{em:'🦉',name:'Grey Owl'},{em:'🐦',name:'Am. Dipper'}, {em:'🐟',name:'Cutthroat'},{em:'🐦',name:'Mtn Bluebird'},{em:'🐦',name:'Gray Jay'},{em:'🦅',name:'Bald Eagle'},{em:'🦃',name:'Wild Turkey'} ]; const PHOTO_SPOTS = [ {id:'p1',em:'🏔️',name:'Moraine Lake Valley of Ten Peaks',desc:'The classic postcard shot'}, {id:'p2',em:'💎',name:'Lake Louise Turquoise Shoreline',desc:'At the water with Chateau in background'}, {id:'p3',em:'🏰',name:'Banff Springs from Bow River',desc:'Full castle reflection or silhouette'}, {id:'p4',em:'🚡',name:'Sulphur Mountain Summit 360°',desc:'Panorama from Gondola boardwalk'}, {id:'p5',em:'💧',name:'Johnston Canyon Upper Falls',desc:'Inside the alcove spray zone'}, {id:'p6',em:'🛤️',name:'Banff Ave with Mountains Both Sides',desc:'Looking down the main street'}, {id:'p7',em:'🦌',name:'Wild Animal Clearly in the Wild',desc:'Any wildlife shot where the animal is free'}, {id:'p8',em:'🌊',name:'Perfect Mountain Reflection',desc:'Any lake mirror shot — Two Jack at sunset is best'}, {id:'p9',em:'🐺',name:'Peyto Lake Wolf Shape',desc:'From the viewpoint where it looks like a wolf'}, {id:'p10',em:'🧊',name:'Standing on Athabasca Glacier',desc:'On actual glacier ice'} ]; const DAILY_CHALLENGES = [ {date:'2026-07-25',day:'Travel Day',title:'First Canadian Thing',desc:'Document the first Canadian thing you eat, drink, or experience. Photo evidence required.'}, {date:'2026-07-26',day:'Drive to Banff',title:'Wildlife Before Noon',desc:'Spot any wild animal on the Calgary→Banff drive before noon. No zoo animals.'}, {date:'2026-07-27',day:'Banff Day 1',title:'Trail Before 7 AM',desc:'Start any hike before 7:00 AM Mountain Time. Johnston Canyon recommended.'}, {date:'2026-07-28',day:'Lake Louise',title:'Paddle 10 Minutes',desc:'Canoe or kayak on Moraine Lake or Lake Louise for at least 10 consecutive minutes.'}, {date:'2026-07-29',day:'Icefields Day',title:'Off-Script Discovery',desc:"Go somewhere that's NOT on the itinerary — find it independently. Bonus if it's something Aaron wouldn't normally suggest."}, {date:'2026-07-30',day:'Lake Louise Base',title:'Try Something New',desc:"Eat or drink something you've never tried before. Must be able to describe it."}, {date:'2026-07-31',day:'Last Full Day',title:'Best Alberta Sunset',desc:'Capture the best sunset photo of the entire trip. Winner gets permanent bragging rights.'}, {date:'2026-08-01',day:'Fly Home',title:'Lowest Last Purchase',desc:'Final Canadian purchase before leaving YYC — lowest total amount wins. Budget challenge.'} ]; const PLATES = [ {code:'AB',name:'Alberta'},{code:'BC',name:'Brit. Columbia'},{code:'SK',name:'Saskatchewan'}, {code:'MB',name:'Manitoba'},{code:'ON',name:'Ontario'},{code:'QC',name:'Quebec'}, {code:'NB',name:'New Brunswick'},{code:'NS',name:'Nova Scotia'},{code:'PE',name:'PEI'}, {code:'NL',name:'Newfoundland'},{code:'NT',name:'NW Territories'},{code:'YT',name:'Yukon'}, {code:'NU',name:'Nunavut'},{code:'US',name:'Any US State'},{code:'INT',name:'International'} ]; const ELEV_SPOTS = [ {id:'e1',name:'Banff Townsite',hint:'Main street elevation',actual:1383,actualFt:4537}, {id:'e2',name:'Lake Louise Shoreline',hint:'The turquoise lake itself',actual:1731,actualFt:5679}, {id:'e3',name:'Moraine Lake',hint:'Valley of the Ten Peaks',actual:1885,actualFt:6184}, {id:'e4',name:'Sulphur Mountain Summit',hint:'Top of the Banff Gondola',actual:2281,actualFt:7484}, {id:'e5',name:'Athabasca Glacier Base',hint:'Columbia Icefield area',actual:1900,actualFt:6234} ]; const SNACK_ITEMS = [ {id:'s1',em:'🍁',name:'Butter Tart',desc:'Quintessential Canadian pastry'}, {id:'s2',em:'🍫',name:'Coffee Crisp',desc:'Canadian chocolate bar — can\'t get it in the US'}, {id:'s3',em:'🍟',name:'Poutine',desc:'Fries + gravy + cheese curds'}, {id:'s4',em:'🍬',name:'Rockets Candy',desc:'Canadian version of Smarties'}, {id:'s5',em:'🥤',name:'Caesar Cocktail',desc:"Canada's national cocktail — clamato + vodka"}, {id:'s6',em:'🍪',name:'Nanaimo Bar',desc:'No-bake chocolate coconut bar'}, {id:'s7',em:'🫐',name:'Saskatoon Berry',desc:'Jam, pie, or just found in the wild'}, {id:'s8',em:'🧀',name:'Canadian Old Cheddar',desc:'From a local dairy or cheese shop'}, {id:'s9',em:'🍺',name:'Big Rock Traditional Ale',desc:'Calgary craft beer since 1985'}, {id:'s10',em:'🥞',name:'Bannock',desc:'Indigenous fry bread — get it from a local vendor'}, {id:'s11',em:'🍁',name:'Maple Cream Cookies',desc:'Tim Hortons or local bakery'}, {id:'s12',em:'🥩',name:'Wild Game Jerky',desc:'Elk or bison — from a Banff shop'} ]; const RANDOM_CHALLENGES = [ 'Take a photo with a stranger who has an interesting jacket.', "Find the best WiFi spot in the mountains. Time limit: 3 minutes.", "Say 'eh' genuinely in context before 5 PM.", 'Order something at a restaurant neither of you has tried before.', 'Find a smooth flat rock and skip it. Best skip wins.', 'Make a 30-second video of your best Canadian accent impression.', 'Find the oldest car in any parking lot today.', 'Find something heart-shaped in nature.', "Have a 5-minute conversation with another couple you've never met.", 'Find the steepest incline you can walk up and commit to 3 minutes.', 'Do one touristy thing you would normally skip.', 'Take a panorama that shows sky, mountain, water, and forest in one shot.', 'Find someone who has lived in Banff for 10+ years. Ask their favorite secret spot.', 'Create the most dramatic possible caption for a photo of a parking lot.', 'Spot a license plate from outside North America.', 'Find the best-smelling thing in nature today.', 'Stand completely still for 60 seconds and just listen to the mountains.', 'Take the same photo from the same exact spot in the morning and evening.', 'Ask a park ranger their favorite hidden gem — actually go there.', 'End the day with no plan for 2 hours. See what happens.' ]; // ── STATE ───────────────────────────────────────────────────────── function gs(k){try{return JSON.parse(localStorage.getItem('na_'+k))||null}catch{return null}} function ss(k,v){try{localStorage.setItem('na_'+k,JSON.stringify(v))}catch{}} function getMe(){return gs('me')} function getOpp(){return getMe()==='aaron'?'caroline':'aaron'} function fmtDate(d){const x=new Date(d+'T12:00:00');return x.toLocaleDateString('en-US',{weekday:'short',month:'short',day:'numeric'})} function today(){const d=new Date();return d.getFullYear()+'-'+String(d.getMonth()+1).padStart(2,'0')+'-'+String(d.getDate()).padStart(2,'0')} function toast(msg){const t=document.getElementById('game-toast');t.textContent=msg;t.classList.add('show');setTimeout(()=>t.classList.remove('show'),2200)} // ── SCORING ─────────────────────────────────────────────────────── function stepsToPoints(s){let p=Math.floor(s/1000);if(s>=20000)p+=25;else if(s>=10000)p+=10;return p} function countBingoLines(claimed){ const g=Array(5).fill(null).map(()=>Array(5).fill(false)); claimed.forEach(i=>{g[Math.floor(i/5)][i%5]=true}); let l=0; for(let r=0;r<5;r++)if(g[r].every(Boolean))l++; for(let c=0;c<5;c++)if(g.map(r=>r[c]).every(Boolean))l++; if([0,1,2,3,4].map(i=>g[i][i]).every(Boolean))l++; if([0,1,2,3,4].map(i=>g[i][4-i]).every(Boolean))l++; return l; } function calcScore(player){ let s=0; // Steps const steps=gs('steps')||{}; Object.entries(steps).forEach(([date,data])=>{ const mine=data[player]||0,theirs=data[player==='aaron'?'caroline':'aaron']||0; s+=stepsToPoints(mine); if(mine>theirs&&mine>0)s+=5; }); // Hunt const hunt=gs('hunt')||{aaron:[],caroline:[]}; s+=(hunt[player]||[]).length*5; // Bingo const bingo=gs('bingo')||{aaron:[],caroline:[]}; s+=countBingoLines(bingo[player]||[])*25; // Photos const photos=gs('photos')||{}; Object.values(photos).forEach(c=>{if(c===player)s+=5}); // Challenges const ch=gs('challenges')||{aaron:[],caroline:[]}; s+=(ch[player]||[]).length*10; // Plates (3 pts each) const pl=gs('plates')||{}; Object.values(pl).forEach(c=>{if(c===player)s+=3}); return s; } function updateScores(){ document.getElementById('score-aaron').textContent=calcScore('aaron'); document.getElementById('score-caroline').textContent=calcScore('caroline'); } // ── IDENTITY ────────────────────────────────────────────────────── function setPlayer(who){ ss('me',who); document.getElementById('play-id-overlay').classList.remove('show'); renderPlay(); } function checkIdentity(){ if(!getMe()){document.getElementById('play-id-overlay').classList.add('show');} } // ── GAME TABS ───────────────────────────────────────────────────── let activeGame='steps'; function switchGame(name,btn){ activeGame=name; document.querySelectorAll('.game-panel').forEach(p=>p.classList.remove('active')); document.querySelectorAll('.gtab').forEach(b=>b.classList.remove('active')); document.getElementById('gp-'+name).classList.add('active'); if(btn)btn.classList.add('active'); renderGame(name); } function renderGame(name){ if(name==='steps')renderSteps(); else if(name==='hunt')renderHunt(); else if(name==='bingo')renderBingo(); else if(name==='photos')renderPhotos(); else if(name==='daily')renderDaily(); else if(name==='plates')renderPlates(); else if(name==='elevation')renderElev(); else if(name==='snack')renderSnack(); } // ── STEPS ───────────────────────────────────────────────────────── let stepBuffer=''; function openStepModal(){ const me=getMe();if(!me)return; const todaySteps=(gs('steps')||{})[today()]||{}; stepBuffer=todaySteps[me]?String(todaySteps[me]):''; document.getElementById('steps-sheet-title').textContent='Log '+(me==='aaron'?'🤠 Aaron':'🌸 Caroline')+' Steps'; document.getElementById('steps-sheet-date').textContent=fmtDate(today()); updateStepNumpad(); document.getElementById('steps-modal').classList.add('open'); } function closeStepModal(){document.getElementById('steps-modal').classList.remove('open')} function nk(n){if(stepBuffer.length>=6)return;stepBuffer+=String(n);updateStepNumpad()} function nkDel(){stepBuffer=stepBuffer.slice(0,-1);updateStepNumpad()} function nkClear(){stepBuffer='';updateStepNumpad()} function updateStepNumpad(){const v=parseInt(stepBuffer)||0;document.getElementById('steps-numpad-val').textContent=v>0?v.toLocaleString():'0'} function saveSteps(){ const v=parseInt(stepBuffer)||0; if(!v){toast('Enter your step count first!');return} const me=getMe();const d=today(); const steps=gs('steps')||{}; if(!steps[d])steps[d]={}; steps[d][me]=v; ss('steps',steps); closeStepModal(); toast('✅ '+v.toLocaleString()+' steps saved!'); renderSteps();updateScores(); } function renderSteps(){ const me=getMe()||'aaron';const opp=me==='aaron'?'caroline':'aaron'; const todaySteps=(gs('steps')||{})[today()]||{}; const mySteps=todaySteps[me]||0,theirSteps=todaySteps[opp]||0; document.getElementById('steps-me-name').textContent=me==='aaron'?'🤠 Aaron':'🌸 Caroline'; document.getElementById('steps-them-name').textContent=opp==='aaron'?'🤠 Aaron':'🌸 Caroline'; document.getElementById('steps-me-val').textContent=mySteps>0?mySteps.toLocaleString():'—'; document.getElementById('steps-them-val').textContent=theirSteps>0?theirSteps.toLocaleString():'—'; document.getElementById('steps-me-side').className='steps-side me'; // History const steps=gs('steps')||{}; const rows=TRIP_DATES.filter(d=>steps[d]); const hist=document.getElementById('steps-history'); if(!rows.length){hist.innerHTML='

No steps logged yet.

';return} hist.innerHTML=rows.map(d=>{ const s=steps[d]||{};const a=s.aaron||0,c=s.caroline||0; const win=a>c&&a>0?'🤠':c>a&&c>0?'🌸':''; return `
${fmtDate(d)}
🤠
${a>0?a.toLocaleString():'—'}
🌸
${c>0?c.toLocaleString():'—'}
${win}
`; }).join(''); } // ── HUNT ────────────────────────────────────────────────────────── function renderHunt(){ const me=getMe()||'aaron'; const hunt=gs('hunt')||{aaron:[],caroline:[]}; const mine=hunt[me]||[]; document.getElementById('hunt-count').textContent=mine.length; document.getElementById('hunt-pts').textContent=mine.length*5; document.getElementById('hunt-prog').style.width=(mine.length/HUNT_ITEMS.length*100)+'%'; document.getElementById('hunt-list').innerHTML=HUNT_ITEMS.map(item=>{ const done=mine.includes(item.id); return `
${done?'✓':''}
${item.em}
${item.name}
${item.desc}
+5
`; }).join(''); } function toggleHunt(id){ const me=getMe();if(!me){toast('Set your identity first!');checkIdentity();return} const hunt=gs('hunt')||{aaron:[],caroline:[]}; const arr=hunt[me]||[]; const i=arr.indexOf(id); if(i===-1){arr.push(id);toast('🔍 Found! +5 pts')}else{arr.splice(i,1)} hunt[me]=arr;ss('hunt',hunt);renderHunt();updateScores(); } // ── BINGO ───────────────────────────────────────────────────────── function getWinCells(claimed){ const g=Array(5).fill(null).map(()=>Array(5).fill(false)); claimed.forEach(i=>{g[Math.floor(i/5)][i%5]=true}); const w=new Set(); for(let r=0;r<5;r++)if(g[r].every(Boolean))for(let c=0;c<5;c++)w.add(r*5+c); for(let c=0;c<5;c++)if(g.map(r=>r[c]).every(Boolean))for(let r=0;r<5;r++)w.add(r*5+c); if([0,1,2,3,4].map(i=>g[i][i]).every(Boolean))[0,1,2,3,4].forEach(i=>w.add(i*5+i)); if([0,1,2,3,4].map(i=>g[i][4-i]).every(Boolean))[0,1,2,3,4].forEach(i=>w.add(i*5+(4-i))); return w; } function renderBingo(){ const me=getMe()||'aaron'; const bingo=gs('bingo')||{aaron:[],caroline:[]}; const mine=bingo[me]||[]; const prevLines=countBingoLines(mine); const winCells=getWinCells(mine); document.getElementById('bingo-lines').textContent=prevLines; document.getElementById('bingo-pts').textContent=prevLines*25; document.getElementById('bingo-grid').innerHTML=BINGO_CELLS.map((cell,i)=>{ const spotted=mine.includes(i);const win=winCells.has(i); return `
${cell.em}
${cell.name}
`; }).join(''); } function toggleBingo(idx){ const me=getMe();if(!me){toast('Set your identity first!');checkIdentity();return} const bingo=gs('bingo')||{aaron:[],caroline:[]}; const arr=bingo[me]||[]; const prev=countBingoLines(arr); const i=arr.indexOf(idx); if(i===-1)arr.push(idx);else arr.splice(i,1); bingo[me]=arr; const now=countBingoLines(arr); if(now>prev)toast('🎯 BINGO! +25 pts! 🏆'); else if(i===-1)toast('🐾 Spotted!'); ss('bingo',bingo);renderBingo();updateScores(); } // ── PHOTOS ──────────────────────────────────────────────────────── function renderPhotos(){ const me=getMe()||'aaron'; const photos=gs('photos')||{}; let ac=0,cc=0; Object.values(photos).forEach(c=>{if(c==='aaron')ac++;if(c==='caroline')cc++}); document.getElementById('photos-aaron').textContent=ac; document.getElementById('photos-caroline').textContent=cc; document.getElementById('photos-list').innerHTML=PHOTO_SPOTS.map(spot=>{ const claimer=photos[spot.id]; const cls=claimer==='aaron'?'aaron-win':claimer==='caroline'?'caroline-win':''; const claimerTxt=claimer==='aaron'?'🤠 Aaron claimed! +5 pts':claimer==='caroline'?'🌸 Caroline claimed! +5 pts':'UNCLAIMED — tap to claim +5 pts'; const claimerColor=claimer==='aaron'?'var(--blue)':claimer==='caroline'?'#ff69b4':'var(--gray)'; return `
${spot.em}
${spot.name}
${spot.desc}
${claimerTxt}
`; }).join(''); } function claimPhoto(id){ const me=getMe();if(!me){toast('Set your identity first!');checkIdentity();return} const photos=gs('photos')||{}; if(photos[id]){toast('Already claimed!');return} photos[id]=me;ss('photos',photos);toast('📸 CLAIMED! +5 pts');renderPhotos();updateScores(); } // ── DAILY CHALLENGES ────────────────────────────────────────────── function renderDaily(){ const me=getMe()||'aaron'; const ch=gs('challenges')||{aaron:[],caroline:[]}; const mine=ch[me]||[]; const t=today(); document.getElementById('daily-list').innerHTML=DAILY_CHALLENGES.map(c=>{ const done=mine.includes(c.date); const isToday=c.date===t; const isPast=c.date
${fmtDate(c.date)} · ${c.day}
${isToday?'⭐ ':''}${c.title}
${c.desc}
${!done&&(isToday||isPast)?``:''} ${done?'
✅ Done! +10 pts
':''} `; }).join(''); } function completeChallenge(date){ const me=getMe();if(!me)return; const ch=gs('challenges')||{aaron:[],caroline:[]}; const arr=ch[me]||[]; if(!arr.includes(date)){arr.push(date);toast('⚡ Challenge complete! +10 pts')} ch[me]=arr;ss('challenges',ch);renderDaily();updateScores(); } // ── LICENSE PLATES ──────────────────────────────────────────────── function renderPlates(){ const me=getMe()||'aaron'; const plates=gs('plates')||{}; let ac=0,cc=0; Object.values(plates).forEach(c=>{if(c==='aaron')ac++;if(c==='caroline')cc++}); document.getElementById('plates-aaron').textContent=ac; document.getElementById('plates-caroline').textContent=cc; document.getElementById('plates-grid').innerHTML=PLATES.map(p=>{ const claimer=plates[p.code]; const cls=claimer==='aaron'?'aaron-p':claimer==='caroline'?'caroline-p':''; return `
${p.code}
${p.name}
${claimer?`
${claimer==='aaron'?'🤠':'🌸'}
`:''}
`; }).join(''); } function claimPlate(code){ const me=getMe();if(!me){toast('Set your identity first!');checkIdentity();return} const plates=gs('plates')||{}; if(plates[code]){toast('Already claimed!');return} plates[code]=me;ss('plates',plates); // Check Canadian clean sweep const caCodes=['AB','BC','SK','MB','ON','QC','NB','NS','PE','NL','NT','YT','NU']; const myPlates=Object.entries(plates).filter(([k,v])=>v===me&&caCodes.includes(k)); if(myPlates.length===13)toast('🍁 CANADIAN CLEAN SWEEP! +20 bonus pts!'); else toast('🚗 Claimed! +3 pts'); renderPlates();updateScores(); } // ── ELEVATION GUESSING ──────────────────────────────────────────── function renderElev(){ const elev=gs('elevation')||{}; document.getElementById('elev-list').innerHTML=ELEV_SPOTS.map(s=>{ const data=elev[s.id]||{}; const revealed=data.revealed; return `
${s.name}
${s.hint}
${!revealed?`
`: `
Actual: ${s.actual.toLocaleString()}m (${s.actualFt.toLocaleString()} ft)
${data.winner?`
${data.winner==='tie'?'🤝 Tie! +3 pts each':(data.winner==='aaron'?'🤠 Aaron':'🌸 Caroline')+' wins! +5 pts'}
`:''}
🤠 guessed: ${data.aaron||'—'}m · 🌸 guessed: ${data.caroline||'—'}m
` }
`; }).join(''); } function revealElev(id,actual){ const a=parseInt(document.getElementById('ea-'+id)?.value)||0; const c=parseInt(document.getElementById('ec-'+id)?.value)||0; if(!a&&!c){toast('Enter at least one guess first!');return} const da=Math.abs(a-actual),dc=Math.abs(c-actual); let winner='tie',msg=''; if(a&&c){winner=da!allPicked.includes(s.id)); const round=snack.round||0; const isAaronTurn=round%2===0; const picker=isAaronTurn?'aaron':'caroline'; status.innerHTML=remaining.length>0? `${isAaronTurn?'🤠 Aaron':'🌸 Caroline'} picks next`: '✅ Draft complete! Go find and try these!'; pool.innerHTML=`
🤠 Aaron's picks
${snack.aaron.map(id=>{const s=SNACK_ITEMS.find(x=>x.id===id);return s?`
${s.em} ${s.name}
`:''}).join('')||'
None yet
'}
🌸 Caroline's picks
${snack.caroline.map(id=>{const s=SNACK_ITEMS.find(x=>x.id===id);return s?`
${s.em} ${s.name}
`:''}).join('')||'
None yet
'}
${remaining.length>0?`
Available (${getMe()===picker?'your pick!':'their pick'}):
${remaining.map(s=>`
${s.em}
${s.name}
${s.desc}
`).join('')}
`:''}`; } function startSnackDraft(){ const snack={aaron:[],caroline:[],round:0,started:true}; ss('snack',snack);renderSnack(); } function pickSnack(id){ const me=getMe();if(!me)return; const snack=gs('snack')||{aaron:[],caroline:[],round:0,started:true}; const round=snack.round||0;const picker=round%2===0?'aaron':'caroline'; if(me!==picker){toast('Not your pick yet!');return} snack[picker]=[...(snack[picker]||[]),id]; snack.round=round+1; ss('snack',snack);toast('🍁 Picked! Now the other person's turn.');renderSnack(); } // ── RANDOM CHALLENGE ────────────────────────────────────────────── function genRandom(){ const i=Math.floor(Math.random()*RANDOM_CHALLENGES.length); const r=document.getElementById('random-result'); r.innerHTML=`
Your Challenge 🎯
${RANDOM_CHALLENGES[i]}
`; } // ── PLAY TAB INIT ───────────────────────────────────────────────── function renderPlay(){ updateScores(); renderGame(activeGame); // URL params for step import const params=new URLSearchParams(window.location.search); const pPlayer=params.get('player');const pSteps=parseInt(params.get('steps')); if(pPlayer&&pSteps>0&&(pPlayer==='aaron'||pPlayer==='caroline')){ const t=today();const steps=gs('steps')||{}; if(!steps[t])steps[t]={}; const existing=steps[t][pPlayer]||0; if(pSteps>existing){steps[t][pPlayer]=pSteps;ss('steps',steps);toast('✅ Steps imported: '+pSteps.toLocaleString())} } } // Hook into showTab const _origShowTab=window.showTab; window.showTab=function(name){ _origShowTab(name); if(name==='play'){checkIdentity();renderPlay();} }; // Init on load document.addEventListener('DOMContentLoaded',()=>{ updateScores(); // If already on play tab, render it if(document.getElementById('tab-play')?.classList.contains('active'))renderPlay(); });