<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Houston World Cup Drink Map | iDrinkBrew</title>
<!-- Fonts -->
<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=Outfit:wght@400;600;800;900&family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
<!-- Tailwind CSS -->
<script src="https://cdn.tailwindcss.com"></script>
<!-- FontAwesome Icons -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<!-- Leaflet CSS -->
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=" crossorigin=""/>
<script>
tailwind.config = {
darkMode: 'class',
theme: {
extend: {
fontFamily: {
sans: ['Inter', 'sans-serif'],
display: ['Outfit', 'sans-serif'],
},
colors: {
brand: {
bg: '#020617',
surface: 'rgba(30, 41, 59, 0.7)',
coffee: '#fbbf24',
tea: '#34d399',
beer: '#ec4899',
cyan: '#06b6d4',
purple: '#8b5cf6',
railRed: '#ef4444',
railGreen: '#22c55e',
railPurple: '#a855f7'
}
},
animation: {
'blob': 'blob 7s infinite',
'pulse-glow': 'pulse-glow 2s cubic-bezier(0.4, 0, 0.6, 1) infinite',
'stamp': 'stamp 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275) forwards'
},
keyframes: {
blob: {
'0%': { transform: 'translate(0px, 0px) scale(1)' },
'33%': { transform: 'translate(30px, -50px) scale(1.1)' },
'66%': { transform: 'translate(-20px, 20px) scale(0.9)' },
'100%': { transform: 'translate(0px, 0px) scale(1)' },
},
'pulse-glow': {
'0%, 100%': { opacity: 1, transform: 'scale(1)' },
'50%': { opacity: .7, transform: 'scale(1.05)', filter: 'brightness(1.2)' },
},
'stamp': {
'0%': { transform: 'scale(2) rotate(-15deg)', opacity: 0 },
'100%': { transform: 'scale(1) rotate(0deg)', opacity: 1 }
}
}
}
}
}
</script>
<style>
body {
background-color: #020617;
color: #f8fafc;
-webkit-tap-highlight-color: transparent;
overflow-x: hidden;
}
/* Subtle Soccer Ball Watermark Texture */
.bg-watermark {
background-image: url('data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"%3E%3Cpath fill="%23ffffff" d="M256 0a256 256 0 1 0 0 512A256 256 0 1 0 256 0zM122.9 330.4c-2.8-12-4.3-24.5-4.3-37.4c0-21.6 4.1-42.3 11.5-61.4l58.1 27.2L122.9 330.4zM256 128l46.2 63.6H209.8L256 128zM151.7 132.8l40.4 55.6-58.1 41.5c-6.8-12.8-11.8-26.6-14.7-41.2l32.4-55.9zM256 384l-46.2-63.6h92.4L256 384zm-64.8-100l-45.9-63.2 58.1-27.2 34.6 47.7-46.8 42.7zm129.6 0l-46.8-42.7 34.6-47.7 58.1 27.2-45.9 63.2zm-129.6-105.2h92.4l46.2 63.6-46.2 63.6h-92.4L191.2 242.4l46.2-63.6zM360.3 132.8l32.4 55.9c-2.9 14.6-7.9 28.4-14.7 41.2l-58.1-41.5 40.4-55.6zm17.3 160.2l58.1-27.2c7.4 19.1 11.5 39.8 11.5 61.4c0 12.9-1.5 25.4-4.3 37.4l-65.3-71.6zM191.2 443.2l40.4-55.6 58.1 41.5c-6.8 12.8-11.8 26.6-14.7 41.2l-32.4-55.9zm129.6 0l-32.4 55.9c-2.9-14.6-7.9-28.4-14.7-41.2l58.1-41.5-40.4 55.6z"/%3E%3C/svg%3E');
background-size: 150px 150px;
background-repeat: repeat;
}
.glass-panel {
background: rgba(15, 23, 42, 0.6);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border: 1px solid rgba(255, 255, 255, 0.1);
}
.glass-card:hover {
border-color: rgba(255, 255, 255, 0.3);
box-shadow: 0 0 20px rgba(6, 182, 212, 0.15);
}
#map {
height: 60vh;
min-height: 400px;
max-height: 600px;
width: 100%;
z-index: 10;
background: #0f172a;
}
/* Leaflet Popups */
.leaflet-popup-content-wrapper {
background: rgba(15, 23, 42, 0.95);
backdrop-filter: blur(10px);
color: white;
border: 1px solid rgba(255,255,255,0.1);
border-radius: 1rem;
box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.5);
}
.leaflet-popup-tip { background: rgba(15, 23, 42, 0.95); }
.leaflet-container a.leaflet-popup-close-button { color: #94a3b8; padding: 8px; }
/* Form styling */
.drink-checkbox:checked + div { transform: translateY(-2px); }
.drink-checkbox[value="coffee"]:checked + div { border-color: #fbbf24; background: rgba(251, 191, 36, 0.1); box-shadow: 0 0 15px rgba(251, 191, 36, 0.3); }
.drink-checkbox[value="tea"]:checked + div { border-color: #34d399; background: rgba(52, 211, 153, 0.1); box-shadow: 0 0 15px rgba(52, 211, 153, 0.3); }
.drink-checkbox[value="beer"]:checked + div { border-color: #ec4899; background: rgba(236, 72, 153, 0.1); box-shadow: 0 0 15px rgba(236, 72, 153, 0.3); }
select {
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%2394a3b8' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");
background-position: right 0.5rem center;
background-repeat: no-repeat;
background-size: 1.5em 1.5em;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
}
::-webkit-scrollbar { width: 8px; height: 8px; }
::-webkit-scrollbar-track { background: #020617; }
::-webkit-scrollbar-thumb { background: #334155; border-radius: 4px; }
::-webkit-scrollbar-thumb:hover { background: #475569; }
.hide-scrollbar::-webkit-scrollbar { display: none; }
.hide-scrollbar { -ms-overflow-style: none; scrollbar-width: none; }
.text-gradient {
background: linear-gradient(to right, #06b6d4, #ec4899, #fbbf24);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
background-size: 200% auto;
animation: textGradient 5s ease infinite;
}
@keyframes textGradient {
0% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
100% { background-position: 0% 50%; }
}
/* Passport Modal Styles */
.modal-overlay {
background: rgba(2, 6, 23, 0.85);
backdrop-filter: blur(8px);
opacity: 0;
pointer-events: none;
transition: opacity 0.3s ease;
}
.modal-overlay.active {
opacity: 1;
pointer-events: auto;
}
.stamp-card.stamped {
border-color: rgba(6, 182, 212, 0.6);
background: rgba(6, 182, 212, 0.1);
box-shadow: 0 0 20px rgba(6, 182, 212, 0.2);
}
.stamp-icon { filter: grayscale(100%) opacity(30%); transition: all 0.4s ease; }
.stamp-card.stamped .stamp-icon {
filter: grayscale(0%) opacity(100%);
animation: stamp 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275) forwards;
}
/* Route Line Animation */
.route-line-anim {
stroke-dasharray: 12, 12;
animation: dashAnim 1s linear infinite;
}
@keyframes dashAnim {
to { stroke-dashoffset: -24; }
}
</style>
</head>
<body class="antialiased selection:bg-brand-cyan selection:text-white relative">
<!-- Ambient Backgrounds & Watermark -->
<div class="fixed inset-0 bg-watermark opacity-[0.03] pointer-events-none z-0"></div>
<div class="fixed top-0 left-[-10%] w-96 h-96 bg-brand-cyan/20 rounded-full mix-blend-screen filter blur-[100px] opacity-50 animate-blob pointer-events-none z-0"></div>
<div class="fixed top-[20%] right-[-10%] w-96 h-96 bg-brand-beer/20 rounded-full mix-blend-screen filter blur-[100px] opacity-50 animate-blob animation-delay-2000 pointer-events-none z-0"></div>
<div class="fixed bottom-[-10%] left-[20%] w-96 h-96 bg-brand-purple/20 rounded-full mix-blend-screen filter blur-[100px] opacity-50 animate-blob animation-delay-4000 pointer-events-none z-0"></div>
<!-- Navigation -->
<nav class="sticky top-0 z-50 glass-panel border-b border-white/10">
<div class="max-w-6xl mx-auto px-4 py-4 flex justify-between items-center">
<a href="#" class="font-display font-black text-2xl tracking-tight flex items-center gap-2 text-white group">
<div class="w-8 h-8 rounded-lg bg-gradient-to-br from-brand-cyan to-brand-beer flex items-center justify-center group-hover:animate-pulse-glow">
<i class="fa-solid fa-mug-hot text-white text-sm"></i>
</div>
iDrinkBrew
</a>
<div class="flex items-center gap-3">
<div class="hidden sm:flex text-xs font-bold uppercase tracking-widest bg-white/10 border border-white/20 px-4 py-2 rounded-full backdrop-blur-md items-center gap-2">
<span class="w-2 h-2 rounded-full bg-brand-tea animate-pulse"></span> Houston '26
</div>
</div>
</div>
</nav>
<!-- Floating Passport Button -->
<button onclick="togglePassport()" class="fixed bottom-6 right-6 z-50 w-16 h-16 rounded-full bg-gradient-to-r from-brand-beer to-brand-purple shadow-[0_0_25px_rgba(236,72,153,0.5)] flex items-center justify-center text-white text-2xl hover:scale-110 transition-transform hover:animate-pulse-glow border-2 border-white/20">
<i class="fa-solid fa-trophy"></i>
</button>
<!-- Hero Section -->
<header class="max-w-4xl mx-auto px-4 pt-12 pb-10 text-center relative z-10">
<div class="inline-block mb-4 px-3 py-1 rounded-full border border-brand-cyan/30 bg-brand-cyan/10 text-brand-cyan text-sm font-semibold tracking-wide">
YOUR MATCHDAY ROUTES, UPGRADED.
</div>
<h1 class="font-display text-5xl md:text-7xl font-black tracking-tight text-white mb-6 leading-tight">
The Houston World Cup<br>
<span class="text-gradient">Brew Map.</span>
</h1>
<p class="text-slate-400 text-lg md:text-xl max-w-2xl mx-auto font-medium">
The ultimate fan guide to coffee, tea, and beer near the Fan Fest and NRG Stadium. Collect stamps, ride the rail, and build your route.
</p>
</header>
<main class="max-w-6xl mx-auto px-4 relative z-10 pb-32">
<!-- Planner Form -->
<section class="glass-panel rounded-3xl p-1 relative overflow-hidden mb-12 shadow-2xl shadow-brand-cyan/5">
<div class="absolute inset-0 bg-gradient-to-r from-brand-cyan via-brand-purple to-brand-beer opacity-30"></div>
<div class="relative bg-[#0b1120] rounded-[23px] p-6 md:p-10 z-10 h-full w-full">
<h2 class="font-display text-3xl font-bold mb-2 flex items-center gap-3">
<i class="fa-solid fa-location-crosshairs text-brand-cyan"></i> Plan Your Route
</h2>
<p class="text-sm text-slate-400 mb-8">Pick your matchday, choose your base area, then generate a coffee, tea, or beer route around Houston.</p>
<form id="planner-form" class="space-y-8">
<!-- Selectors Grid -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- Official Match Schedule Selector -->
<div>
<label class="block text-sm font-semibold text-slate-300 mb-2 uppercase tracking-wider">When?</label>
<select id="time-select" class="w-full bg-[#1e293b] border border-slate-700 text-white text-base rounded-xl focus:ring-2 focus:ring-brand-cyan focus:border-transparent block p-4 transition-all appearance-none cursor-pointer">
<option value="live">Right Now (Live / Non-Matchday)</option>
<optgroup label="June 14, 2026">
<option value="pre-match-jun14">June 14: Germany vs Curaçao, 12 PM CT, Pre-Match Energy</option>
<option value="post-match-jun14">June 14: Germany vs Curaçao, 12 PM CT, Post-Match Vibes</option>
</optgroup>
<optgroup label="June 17, 2026">
<option value="pre-match-jun17">June 17: Portugal vs Congo DR, 12 PM CT, Pre-Match Energy</option>
<option value="post-match-jun17">June 17: Portugal vs Congo DR, 12 PM CT, Post-Match Vibes</option>
</optgroup>
<optgroup label="June 20, 2026">
<option value="pre-match-jun20">June 20: Netherlands vs Sweden, 12 PM CT, Pre-Match Energy</option>
<option value="post-match-jun20">June 20: Netherlands vs Sweden, 12 PM CT, Post-Match Vibes</option>
</optgroup>
<optgroup label="June 23, 2026">
<option value="pre-match-jun23">June 23: Portugal vs Uzbekistan, 12 PM CT, Pre-Match Energy</option>
<option value="post-match-jun23">June 23: Portugal vs Uzbekistan, 12 PM CT, Post-Match Vibes</option>
</optgroup>
<optgroup label="June 26, 2026">
<option value="pre-match-jun26">June 26: Cabo Verde vs Saudi Arabia, 7 PM CT, Pre-Match Energy</option>
<option value="post-match-jun26">June 26: Cabo Verde vs Saudi Arabia, 7 PM CT, Post-Match Vibes</option>
</optgroup>
<optgroup label="Knockout Stages">
<option value="pre-match-jun29">June 29: Round of 32, 12 PM CT, Pre-Match Energy</option>
<option value="post-match-jun29">June 29: Round of 32, 12 PM CT, Post-Match Vibes</option>
<option value="pre-match-jul4">July 4: Round of 16, 12 PM CT, Pre-Match Energy</option>
<option value="post-match-jul4">July 4: Round of 16, 12 PM CT, Post-Match Vibes</option>
</optgroup>
</select>
</div>
<!-- Neighborhood Selector -->
<div>
<label class="block text-sm font-semibold text-slate-300 mb-2 uppercase tracking-wider">Fan Base Zone</label>
<select id="neighborhood-select" class="w-full bg-[#1e293b] border border-slate-700 text-white text-base rounded-xl focus:ring-2 focus:ring-brand-cyan focus:border-transparent block p-4 transition-all appearance-none cursor-pointer">
<option value="all">Houston Wide (Smart Routing)</option>
<option value="EaDo">EaDo (FIFA Fan Festival Area)</option>
<option value="Downtown">Downtown (Transit Hub)</option>
<option value="Midtown">Midtown (Nightlife)</option>
<option value="Montrose">Montrose (Culture)</option>
<option value="Museum District">Museum District</option>
<option value="NRG">NRG Area (Stadium Routes)</option>
</select>
</div>
</div>
<!-- Drink Preferences -->
<div>
<label class="block text-sm font-semibold text-slate-300 mb-3 uppercase tracking-wider">Choose Your Vibe</label>
<div class="grid grid-cols-3 gap-4">
<label class="cursor-pointer relative group">
<input type="checkbox" value="coffee" class="drink-checkbox peer sr-only" checked>
<div class="p-4 text-center border-2 border-slate-700 bg-slate-800/50 rounded-2xl transition-all duration-300">
<div class="w-10 h-10 md:w-12 md:h-12 mx-auto bg-slate-800 rounded-full flex items-center justify-center mb-2 group-hover:scale-110 transition-transform">
<i class="fa-solid fa-mug-hot text-xl md:text-2xl text-slate-400 peer-checked:text-brand-coffee"></i>
</div>
<span class="block text-xs md:text-sm font-bold text-slate-300">Coffee</span>
</div>
</label>
<label class="cursor-pointer relative group">
<input type="checkbox" value="tea" class="drink-checkbox peer sr-only" checked>
<div class="p-4 text-center border-2 border-slate-700 bg-slate-800/50 rounded-2xl transition-all duration-300">
<div class="w-10 h-10 md:w-12 md:h-12 mx-auto bg-slate-800 rounded-full flex items-center justify-center mb-2 group-hover:scale-110 transition-transform">
<i class="fa-solid fa-leaf text-xl md:text-2xl text-slate-400 peer-checked:text-brand-tea"></i>
</div>
<span class="block text-xs md:text-sm font-bold text-slate-300">Tea</span>
</div>
</label>
<label class="cursor-pointer relative group">
<input type="checkbox" value="beer" class="drink-checkbox peer sr-only" checked>
<div class="p-4 text-center border-2 border-slate-700 bg-slate-800/50 rounded-2xl transition-all duration-300">
<div class="w-10 h-10 md:w-12 md:h-12 mx-auto bg-slate-800 rounded-full flex items-center justify-center mb-2 group-hover:scale-110 transition-transform">
<i class="fa-solid fa-beer-mug-empty text-xl md:text-2xl text-slate-400 peer-checked:text-brand-beer"></i>
</div>
<span class="block text-xs md:text-sm font-bold text-slate-300">Beer</span>
</div>
</label>
</div>
</div>
<button type="button" id="btn-generate" class="w-full bg-gradient-to-r from-brand-cyan to-brand-purple hover:from-brand-cyan hover:to-brand-beer text-white font-black text-xl py-5 px-6 rounded-xl transition-all duration-300 hover:shadow-[0_0_30px_rgba(6,182,212,0.5)] active:scale-95 flex justify-center items-center gap-3">
<i class="fa-solid fa-bolt"></i> Generate Itinerary
</button>
</form>
</div>
</section>
<!-- Generated Itinerary -->
<section id="itinerary-results" class="hidden mb-16 animate-[fadeIn_0.5s_ease-out] scroll-mt-24">
<div class="flex flex-col sm:flex-row justify-between items-start sm:items-end mb-6 gap-4">
<h3 class="font-display text-3xl font-bold text-white flex items-center gap-2">
<i class="fa-solid fa-ticket text-brand-cyan"></i> Your Matchday VIP Plan
</h3>
</div>
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 relative mb-6" id="itinerary-cards">
<!-- Cards injected via JS -->
</div>
<!-- Action Bar (CTA Flow) -->
<div class="glass-panel rounded-2xl p-4 flex flex-col md:flex-row justify-between items-center gap-4">
<div class="text-sm font-medium text-slate-300 flex items-center gap-2">
<i class="fa-solid fa-check-double text-brand-tea"></i> Ready to roll?
</div>
<div class="flex flex-wrap items-center gap-3 w-full md:w-auto">
<button id="btn-maps" class="flex-1 md:flex-none bg-brand-cyan hover:bg-cyan-400 text-slate-900 font-bold px-5 py-2.5 rounded-xl transition-colors flex items-center justify-center gap-2 text-sm shadow-[0_0_15px_rgba(6,182,212,0.4)]">
<i class="fa-solid fa-location-arrow"></i> Open in Maps
</button>
<button id="btn-share" class="flex-1 md:flex-none bg-slate-700 hover:bg-slate-600 border border-slate-500 text-white font-bold px-5 py-2.5 rounded-xl transition-colors flex items-center justify-center gap-2 text-sm">
<i class="fa-solid fa-arrow-up-from-bracket"></i> Share with Group
</button>
<button id="btn-save" class="w-full md:w-auto bg-slate-800 hover:bg-slate-700 border border-slate-600 text-slate-300 font-bold px-5 py-2.5 rounded-xl transition-colors flex items-center justify-center gap-2 text-sm">
<i class="fa-solid fa-bookmark"></i> Save Plan
</button>
</div>
</div>
<div id="share-toast" class="hidden mt-4 bg-brand-cyan/20 border border-brand-cyan text-brand-cyan font-bold px-6 py-3 rounded-lg text-center backdrop-blur-md shadow-[0_0_20px_rgba(6,182,212,0.2)]">
<i class="fa-solid fa-check-circle mr-2"></i> Route link copied to clipboard!
</div>
</section>
<!-- Featured Matchday Stop -->
<section class="mb-8" id="featured-stop-container">
<!-- Injected via JS -->
</section>
<!-- Map & Filters Section -->
<section class="mb-16">
<div class="flex flex-col lg:flex-row justify-between lg:items-end mb-6 gap-4">
<div>
<h3 class="font-display text-3xl font-bold text-white">Map & Rail Guide</h3>
<div class="flex gap-3 mt-2 text-xs font-bold uppercase tracking-wider">
<span class="text-brand-railRed"><i class="fa-solid fa-train"></i> Red Line</span>
<span class="text-brand-railGreen"><i class="fa-solid fa-train"></i> Green Line</span>
<span class="text-brand-railPurple"><i class="fa-solid fa-train"></i> Purple Line</span>
</div>
<p class="text-[10px] text-slate-400 mt-2 max-w-sm">Rail lines shown are approximate. Confirm routes and service updates with Houston METRO before traveling.</p>
</div>
<!-- Jump to Venue Search -->
<div class="w-full lg:w-80 relative z-20">
<div class="absolute inset-y-0 left-0 pl-4 flex items-center pointer-events-none">
<i class="fa-solid fa-magnifying-glass text-brand-cyan"></i>
</div>
<select id="venue-jump-select" class="w-full bg-[#1e293b]/90 backdrop-blur-md border border-slate-700 text-white text-sm font-bold rounded-xl focus:ring-2 focus:ring-brand-cyan focus:border-transparent block pl-10 p-3.5 transition-all shadow-[0_0_15px_rgba(0,0,0,0.3)] cursor-pointer" style="background-image: none; appearance: none; -webkit-appearance: none;">
<option value="">Jump to a venue...</option>
</select>
<div class="absolute inset-y-0 right-0 pr-4 flex items-center pointer-events-none text-slate-400">
<i class="fa-solid fa-chevron-down text-xs"></i>
</div>
</div>
</div>
<!-- Quick Filters -->
<div class="flex gap-3 overflow-x-auto hide-scrollbar pb-2 mb-4 w-full">
<button class="filter-btn active whitespace-nowrap px-5 py-2.5 rounded-full text-sm font-bold bg-brand-cyan text-slate-900 border border-transparent shadow-[0_0_15px_rgba(6,182,212,0.4)] transition-all" data-filter="all">All Spots</button>
<button class="filter-btn whitespace-nowrap px-5 py-2.5 rounded-full text-sm font-bold glass-panel hover:bg-white/10 text-white transition-all" data-filter="usa-hub">USA Hubs</button>
<button class="filter-btn whitespace-nowrap px-5 py-2.5 rounded-full text-sm font-bold glass-panel hover:bg-white/10 text-white transition-all" data-filter="el-tri">El Tri Hubs</button>
<button class="filter-btn whitespace-nowrap px-5 py-2.5 rounded-full text-sm font-bold glass-panel hover:bg-white/10 text-white transition-all" data-filter="euro">Euro Pubs</button>
<button class="filter-btn whitespace-nowrap px-5 py-2.5 rounded-full text-sm font-bold glass-panel hover:bg-white/10 text-white transition-all" data-filter="south-american">SA Vibes</button>
<button class="filter-btn whitespace-nowrap px-5 py-2.5 rounded-full text-sm font-bold glass-panel hover:bg-white/10 text-white transition-all" data-filter="rowdy">Rowdy</button>
<button class="filter-btn whitespace-nowrap px-5 py-2.5 rounded-full text-sm font-bold glass-panel hover:bg-white/10 text-white transition-all" data-filter="quiet">Quiet Escape</button>
</div>
<div class="relative rounded-3xl overflow-hidden shadow-[0_0_40px_rgba(0,0,0,0.5)] border border-slate-700 p-1 bg-gradient-to-b from-slate-800 to-slate-900 z-10">
<div id="map" class="rounded-[20px]"></div>
<!-- Map Overlay Gradient -->
<div class="absolute inset-0 pointer-events-none rounded-3xl shadow-[inset_0_0_50px_rgba(2,6,23,0.8)] z-20"></div>
</div>
</section>
<!-- Neighborhood Guide -->
<section class="mb-16">
<h3 class="font-display text-3xl font-bold mb-6 text-white">Explore by Zone</h3>
<div class="grid grid-cols-2 md:grid-cols-3 gap-5">
<div class="glass-panel glass-card p-5 rounded-2xl cursor-pointer transition-all group" onclick="filterMapByNeighborhood('EaDo')">
<div class="w-12 h-12 rounded-full bg-brand-beer/20 flex items-center justify-center text-brand-beer text-xl mb-3 group-hover:scale-110 transition-transform"><i class="fa-solid fa-bullhorn"></i></div>
<h4 class="font-display font-bold text-xl text-white mb-1">EaDo</h4>
<p class="text-xs md:text-sm text-slate-400">Fan Fest Ground Zero. Breweries & vibes.</p>
</div>
<div class="glass-panel glass-card p-5 rounded-2xl cursor-pointer transition-all group" onclick="filterMapByNeighborhood('Midtown')">
<div class="w-12 h-12 rounded-full bg-brand-purple/20 flex items-center justify-center text-brand-purple text-xl mb-3 group-hover:scale-110 transition-transform"><i class="fa-solid fa-moon"></i></div>
<h4 class="font-display font-bold text-xl text-white mb-1">Midtown</h4>
<p class="text-xs md:text-sm text-slate-400">Massive beer gardens and electric nightlife.</p>
</div>
<div class="glass-panel glass-card p-5 rounded-2xl cursor-pointer transition-all group" onclick="filterMapByNeighborhood('Downtown')">
<div class="w-12 h-12 rounded-full bg-brand-cyan/20 flex items-center justify-center text-brand-cyan text-xl mb-3 group-hover:scale-110 transition-transform"><i class="fa-solid fa-city"></i></div>
<h4 class="font-display font-bold text-xl text-white mb-1">Downtown</h4>
<p class="text-xs md:text-sm text-slate-400">The transit hub. Taprooms and modern cafes.</p>
</div>
<div class="glass-panel glass-card p-5 rounded-2xl cursor-pointer transition-all group" onclick="filterMapByNeighborhood('Montrose')">
<div class="w-12 h-12 rounded-full bg-brand-coffee/20 flex items-center justify-center text-brand-coffee text-xl mb-3 group-hover:scale-110 transition-transform"><i class="fa-solid fa-palette"></i></div>
<h4 class="font-display font-bold text-xl text-white mb-1">Montrose</h4>
<p class="text-xs md:text-sm text-slate-400">Cultural epicenter. The best indie spots.</p>
</div>
<div class="glass-panel glass-card p-5 rounded-2xl cursor-pointer transition-all group" onclick="filterMapByNeighborhood('NRG')">
<div class="w-12 h-12 rounded-full bg-brand-tea/20 flex items-center justify-center text-brand-tea text-xl mb-3 group-hover:scale-110 transition-transform"><i class="fa-solid fa-futbol"></i></div>
<h4 class="font-display font-bold text-xl text-white mb-1">NRG Area</h4>
<p class="text-xs md:text-sm text-slate-400">Stadium zone. Hit these pre/post match.</p>
</div>
<div class="glass-panel glass-card p-5 rounded-2xl cursor-pointer transition-all group" onclick="filterMapByNeighborhood('Museum District')">
<div class="w-12 h-12 rounded-full bg-white/10 flex items-center justify-center text-white text-xl mb-3 group-hover:scale-110 transition-transform"><i class="fa-solid fa-tree"></i></div>
<h4 class="font-display font-bold text-xl text-white mb-1">Museum Dist.</h4>
<p class="text-xs md:text-sm text-slate-400">Quiet parks. Perfect for a serene break.</p>
</div>
</div>
</section>
<!-- SEO Content Section -->
<section class="glass-panel rounded-3xl p-6 md:p-8 border-t border-brand-cyan/20 bg-gradient-to-b from-slate-800/40 to-slate-900/80">
<h2 class="text-xl md:text-2xl font-display font-bold text-white mb-4">Houston World Cup 2026 Drink Map & Guide</h2>
<p class="text-slate-400 text-sm leading-relaxed mb-4">
Planning your Houston World Cup itinerary? Whether you need <strong>coffee near NRG Stadium</strong> for an early kickoff, or the best <strong>beer near the EaDo Fan Festival</strong> to celebrate a win, we've got you covered. This interactive map connects fans with the top <strong>Houston matchday bars</strong>, local breweries, and quiet tea escapes.
</p>
<p class="text-slate-400 text-sm leading-relaxed mb-6">
Filter by "USA Hubs," "El Tri Hubs," or "Euro Pubs" to find your crowd. Navigate seamlessly along the METRORail to find the best <strong>coffee, tea, and beer in Houston</strong> during the tournament. Map your route, check in to claim your passport stamps, and share your plan with your group.
</p>
<div class="p-4 bg-slate-900/60 rounded-xl border border-slate-700/50">
<p class="text-xs text-slate-500 font-medium leading-relaxed">
<i class="fa-solid fa-triangle-exclamation mr-1 text-brand-coffee"></i> <strong>Disclaimer:</strong> Venue hours, match schedules, transit routes, and event details may change due to crowds or private events. Always confirm with the venue, Houston METRO, and official FIFA World Cup sources before heading out.
</p>
</div>
</section>
</main>
<!-- World Cup Brew Passport Modal -->
<div id="passport-modal" class="modal-overlay fixed inset-0 z-[100] flex items-center justify-center px-4">
<div class="bg-gradient-to-b from-[#0f172a] to-[#020617] border border-brand-cyan/30 p-6 md:p-8 rounded-3xl w-full max-w-2xl relative shadow-[0_0_50px_rgba(6,182,212,0.2)]">
<button onclick="togglePassport()" class="absolute top-4 right-4 text-slate-400 hover:text-white w-8 h-8 flex items-center justify-center bg-slate-800 rounded-full">
<i class="fa-solid fa-times"></i>
</button>
<div class="text-center mb-8">
<h2 class="font-display text-4xl font-black text-transparent bg-clip-text bg-gradient-to-r from-brand-cyan to-brand-beer mb-2">Brew Passport</h2>
<p class="text-slate-400">Visit venues across Houston and click "Check In" to collect your neighborhood stamps. Collect all 6!</p>
</div>
<div class="grid grid-cols-2 md:grid-cols-3 gap-4" id="passport-grid">
<!-- Populated via JS -->
</div>
<div class="mt-8 text-center">
<p class="text-xs text-slate-500 uppercase tracking-widest font-bold"><span id="stamp-count">0</span> / 6 Zones Stamped</p>
<div class="w-full bg-slate-800 h-2 rounded-full mt-2 overflow-hidden">
<div id="stamp-progress" class="bg-gradient-to-r from-brand-cyan to-brand-beer h-full w-0 transition-all duration-500"></div>
</div>
</div>
</div>
</div>
<footer class="border-t border-white/10 bg-[#020617] text-slate-500 py-12 text-center px-4 relative z-10">
<h4 class="font-display font-black text-white text-2xl mb-3 flex justify-center items-center gap-2">
<i class="fa-solid fa-mug-hot text-brand-cyan"></i> iDrinkBrew
</h4>
<p class="text-sm max-w-md mx-auto mb-6">The independent, vibrant guide to Houston's best spots during the 2026 World Cup.</p>
<p class="text-xs">© 2026 iDrinkBrew. All rights reserved. Not affiliated with FIFA.</p>
</footer>
<!-- Leaflet JS -->
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js" integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin=""></script>
<script type="module">
import { initializeApp } from "https://www.gstatic.com/firebasejs/11.6.1/firebase-app.js";
import { getAuth, signInAnonymously, onAuthStateChanged, signInWithCustomToken } from "https://www.gstatic.com/firebasejs/11.6.1/firebase-auth.js";
import { getFirestore, doc, setDoc, getDoc, collection, onSnapshot, increment } from "https://www.gstatic.com/firebasejs/11.6.1/firebase-firestore.js";
// --- FIREBASE INIT ---
const firebaseConfig = typeof __firebase_config !== 'undefined' ? JSON.parse(__firebase_config) : null;
const appId = typeof __app_id !== 'undefined' ? __app_id : 'default-app-id';
let app, auth, db;
let currentUser = null;
let energyDataLocal = JSON.parse(localStorage.getItem('houstonVenueEnergy')) || {};
let energyData = {};
if (firebaseConfig && Object.keys(firebaseConfig).length > 0) {
app = initializeApp(firebaseConfig);
auth = getAuth(app);
db = getFirestore(app);
} else {
console.warn("Firebase config missing. Using LocalStorage for Fan Crowd Vote.");
energyData = energyDataLocal; // Use local storage if no cloud
}
const initAuth = async () => {
if (!auth) return;
if (typeof __initial_auth_token !== 'undefined' && __initial_auth_token) {
await signInWithCustomToken(auth, __initial_auth_token);
} else {
await signInAnonymously(auth);
}
};
initAuth();
if (auth) {
onAuthStateChanged(auth, async (user) => {
currentUser = user;
if (user && db) {
const passportRef = doc(db, 'artifacts', appId, 'users', user.uid, 'passport', 'stamps');
const docSnap = await getDoc(passportRef);
if (docSnap.exists()) {
userStamps = docSnap.data();
} else {
await setDoc(passportRef, userStamps);
}
renderPassport();
const energyColRef = collection(db, 'artifacts', appId, 'public', 'data', 'venue_energy');
onSnapshot(energyColRef, (snapshot) => {
snapshot.forEach(doc => { energyData[doc.id] = doc.data(); });
if(window.updateLiveEnergyUI) window.updateLiveEnergyUI();
}, (error) => console.error("Error fetching energy:", error));
}
});
}
// --- DATA WITH FAN VIBES ---
const venues = [
// EaDo
{ id: 1, name: "Brass Tacks", type: "coffee", neighborhood: "EaDo", lat: 29.7533, lng: -95.3483, desc: "Massive space, great cold brew. Perfect for group meetups before hitting the Fan Festival.", vibe: "Group Friendly", bestFor: ["all"] },
{ id: 2, name: "8th Wonder Brewery", type: "beer", neighborhood: "EaDo", lat: 29.7488, lng: -95.3556, desc: "Astrodome seating, huge patio. A must-visit pregame spot.", vibe: "USA Hub", bestFor: ["usa-hub", "rowdy"], featured: true },
{ id: 3, name: "True Anomaly Brewing", type: "beer", neighborhood: "EaDo", lat: 29.7496, lng: -95.3572, desc: "Space-themed brewery specializing in sours. Very close to the action.", vibe: "Craft Focus", bestFor: ["all"] },
{ id: 4, name: "Tout Suite", type: "coffee", neighborhood: "EaDo", lat: 29.7610, lng: -95.3524, desc: "Lively cafe with excellent coffee and macarons. Open late.", vibe: "Trendy", bestFor: ["all"] },
{ id: 21, name: "Pitch 25", type: "beer", neighborhood: "EaDo", lat: 29.7516, lng: -95.3541, desc: "Owned by a soccer legend. Indoor pitch, massive screens, and a legendary atmosphere.", vibe: "USA Hub", bestFor: ["usa-hub", "rowdy"], featured: true },
{ id: 22, name: "Truck Yard", type: "beer", neighborhood: "EaDo", lat: 29.7538, lng: -95.3540, desc: "Adult playground with a Ferris wheel, live music, and food trucks.", vibe: "Rowdy", bestFor: ["rowdy"] },
{ id: 23, name: "Moon Tower Inn", type: "beer", neighborhood: "EaDo", lat: 29.7505, lng: -95.3400, desc: "Wild game hot dogs and an incredible craft beer lineup on the East End.", vibe: "Local Secret", bestFor: ["all", "rowdy"] },
{ id: 24, name: "Coral Sword", type: "coffee", neighborhood: "EaDo", lat: 29.7390, lng: -95.3360, desc: "Coffee, board games, and chill vibes. Great spot to recharge.", vibe: "Nerdy", bestFor: ["all", "quiet"] },
// Downtown
{ id: 5, name: "Day 6 Coffee Co.", type: "coffee", neighborhood: "Downtown", lat: 29.7622, lng: -95.3644, desc: "Black-owned cafe in the heart of downtown. Excellent lattes.", vibe: "Local Gem", bestFor: ["all"] },
{ id: 6, name: "Flying Saucer", type: "beer", neighborhood: "Downtown", lat: 29.7601, lng: -95.3615, desc: "Hundreds of beers on tap. Right on the METRORail red line.", vibe: "Euro Pub", bestFor: ["euro", "rowdy"] },
{ id: 7, name: "Prelude Coffee & Tea", type: "tea", neighborhood: "Downtown", lat: 29.7628, lng: -95.3653, desc: "High-end matcha in a sleek downtown lobby.", vibe: "Quiet Escape", bestFor: ["quiet"] },
{ id: 8, name: "McIntyre's Downtown", type: "beer", neighborhood: "Downtown", lat: 29.7645, lng: -95.3590, desc: "Huge indoor/outdoor sports bar. Perfect for watch parties.", vibe: "USA Hub", bestFor: ["usa-hub", "rowdy"] },
{ id: 25, name: "Saint Arnold Brewing", type: "beer", neighborhood: "Downtown", lat: 29.7714, lng: -95.3486, desc: "Texas' oldest craft brewery. Unbeatable skyline views from the beer garden.", vibe: "Group Friendly", bestFor: ["all", "rowdy"] },
{ id: 26, name: "POST Houston", type: "coffee", neighborhood: "Downtown", lat: 29.7663, lng: -95.3651, desc: "Massive food hall with a rooftop park. Grab a coffee and enjoy the view.", vibe: "Scenic", bestFor: ["all"] },
{ id: 27, name: "La Carafe", type: "beer", neighborhood: "Downtown", lat: 29.7618, lng: -95.3621, desc: "Oldest bar in Houston. Cash only, incredibly historic, and famously haunted.", vibe: "Historic", bestFor: ["quiet"] },
// Midtown
{ id: 9, name: "Retrospect Coffee Bar", type: "coffee", neighborhood: "Midtown", lat: 29.7345, lng: -95.3703, desc: "Historic gas station turned crepe & coffee shop. Great outdoor seating.", vibe: "Patio Chill", bestFor: ["all"] },
{ id: 10, name: "Axelrad Beer Garden", type: "beer", neighborhood: "Midtown", lat: 29.7350, lng: -95.3734, desc: "Hammocks, pizza, and a massive beer list. The ultimate chill post-match hangout.", vibe: "SA Vibes", bestFor: ["south-american", "all"] },
{ id: 11, name: "Double Trouble", type: "coffee", neighborhood: "Midtown", lat: 29.7420, lng: -95.3800, desc: "Coffee by day, cocktails and beer by night. Right on the rail.", vibe: "Industry Vibe", bestFor: ["all"] },
{ id: 12, name: "The Teahouse", type: "tea", neighborhood: "Midtown", lat: 29.7348, lng: -95.3805, desc: "Classic Houston boba spot. Great for a refreshing afternoon break.", vibe: "Quiet Escape", bestFor: ["quiet"] },
{ id: 28, name: "Social Beer Garden", type: "beer", neighborhood: "Midtown", lat: 29.7425, lng: -95.3745, desc: "Massive outdoor screen, two stories of fun, and the ultimate soccer party atmosphere.", vibe: "USA Hub", bestFor: ["usa-hub", "rowdy"], featured: true },
{ id: 29, name: "Little Woodrow's Midtown", type: "beer", neighborhood: "Midtown", lat: 29.7460, lng: -95.3755, desc: "A Houston institution. Massive patio, tons of TVs, and bucket specials.", vibe: "Rowdy", bestFor: ["rowdy"] },
// Montrose
{ id: 13, name: "Agora", type: "coffee", neighborhood: "Montrose", lat: 29.7431, lng: -95.3984, desc: "Cozy, two-story Greek coffee house. Wine, beer, and late-night vibes.", vibe: "Euro Pub", bestFor: ["euro", "all"] },
{ id: 14, name: "Blacksmith", type: "coffee", neighborhood: "Montrose", lat: 29.7432, lng: -95.3905, desc: "Some of the best espresso in Houston. Get the biscuits.", vibe: "Morning Fuel", bestFor: ["all"] },
{ id: 15, name: "West Alabama Ice House", type: "beer", neighborhood: "Montrose", lat: 29.7380, lng: -95.3965, desc: "Quintessential Texas ice house. Cold cheap beer and basketball.", vibe: "El Tri Hub", bestFor: ["el-tri", "rowdy"] },
{ id: 16, name: "The Path of Tea", type: "tea", neighborhood: "Montrose", lat: 29.7335, lng: -95.4055, desc: "Organic tea house offering a serene escape from the matchday madness.", vibe: "Quiet Escape", bestFor: ["quiet"] },
{ id: 30, name: "Campesino Coffee House", type: "coffee", neighborhood: "Montrose", lat: 29.7450, lng: -95.3920, desc: "Latin American inspired coffee shop. Great empanadas and cortaditos.", vibe: "SA Vibes", bestFor: ["south-american", "quiet"] },
{ id: 31, name: "The Richmond Arms Pub", type: "beer", neighborhood: "Montrose", lat: 29.7325, lng: -95.4800, desc: "The legendary British pub for soccer fans. Get here early on matchdays.", vibe: "Euro Pub", bestFor: ["euro", "rowdy"] },
{ id: 32, name: "Siphon Coffee", type: "coffee", neighborhood: "Montrose", lat: 29.7385, lng: -95.3920, desc: "High-end coffee made with unique siphon brewers. Great patio.", vibe: "Craft Focus", bestFor: ["all"] },
{ id: 33, name: "Bar Boheme", type: "beer", neighborhood: "Montrose", lat: 29.7456, lng: -95.3855, desc: "Lush bohemian patio, great drinks, and a relaxed global vibe.", vibe: "Euro Pub", bestFor: ["euro"] },
// Museum District
{ id: 17, name: "Fiel Roasters", type: "coffee", neighborhood: "Museum District", lat: 29.7230, lng: -95.3860, desc: "Fantastic coffee near the museums and Hermann Park.", vibe: "Quiet Escape", bestFor: ["quiet"] },
{ id: 18, name: "Grand Prize Bar", type: "beer", neighborhood: "Museum District", lat: 29.7289, lng: -95.3861, desc: "Industry dive bar with a surprisingly great beer and cocktail list.", vibe: "Local Secret", bestFor: ["all"] },
{ id: 34, name: "Under the Volcano", type: "beer", neighborhood: "Museum District", lat: 29.7150, lng: -95.4150, desc: "Day of the Dead themed dive bar with killer frozen drinks and SA vibes.", vibe: "SA Vibes", bestFor: ["south-american", "rowdy"] },
// NRG Area
{ id: 19, name: "Glazed, The Doughnut Cafe", type: "coffee", neighborhood: "NRG", lat: 29.6953, lng: -95.4042, desc: "Open 24/7. Grab coffee and massive donuts pre or post-game.", vibe: "Late Night", bestFor: ["all"] },
{ id: 20, name: "Kirby Ice House", type: "beer", neighborhood: "NRG", lat: 29.7323, lng: -95.4182, desc: "Massive patio and dozens of taps. A short ride from the stadium.", vibe: "USA Hub", bestFor: ["usa-hub", "rowdy"] },
{ id: 35, name: "Taquerias Arandas", type: "coffee", neighborhood: "NRG", lat: 29.6800, lng: -95.4000, desc: "Grab a café de olla and some breakfast tacos before walking to the stadium.", vibe: "El Tri Hub", bestFor: ["el-tri"] }
];
// Inject a random featured spot on load
const loadFeaturedSpot = () => {
const featuredSpots = venues.filter(v => v.featured);
if(featuredSpots.length > 0) {
const randomSpot = featuredSpots[Math.floor(Math.random() * featuredSpots.length)];
document.getElementById('featured-stop-container').innerHTML = `
<div class="glass-panel border-t-2 border-brand-coffee rounded-2xl p-4 md:p-6 flex flex-col md:flex-row items-center justify-between gap-4 relative overflow-hidden group cursor-pointer" onclick="jumpToVenue(${randomSpot.id})">
<div class="absolute -right-10 -top-10 w-32 h-32 bg-brand-coffee/20 rounded-full blur-2xl"></div>
<div class="relative z-10 flex items-center gap-4 w-full">
<div class="w-12 h-12 md:w-14 md:h-14 bg-[#0f172a] border-2 border-brand-coffee text-brand-coffee rounded-full flex items-center justify-center text-xl shadow-[0_0_15px_rgba(251,191,36,0.3)] shrink-0 group-hover:scale-110 transition-transform">
<i class="fa-solid fa-star"></i>
</div>
<div class="flex-1">
<div class="text-[10px] font-bold text-amber-500 uppercase tracking-widest mb-1 flex items-center gap-1"><i class="fa-solid fa-bolt"></i> Featured Matchday Stop</div>
<h4 class="font-display font-bold text-xl md:text-2xl text-white leading-tight">${randomSpot.name}</h4>
<p class="text-xs md:text-sm text-slate-400 mt-1 line-clamp-1">${randomSpot.neighborhood} • ${randomSpot.desc}</p>
</div>
<div class="hidden md:flex shrink-0">
<button class="bg-brand-coffee hover:bg-amber-400 text-slate-900 font-bold px-4 py-2 rounded-xl text-sm transition-colors">View on Map</button>
</div>
</div>
</div>
`;
}
};
loadFeaturedSpot();
// --- PASSPORT LOGIC ---
let userStamps = JSON.parse(localStorage.getItem('houstonPassport')) || {
'EaDo': false, 'Midtown': false, 'Downtown': false,
'Montrose': false, 'Museum District': false, 'NRG': false
};
const renderPassport = () => {
const grid = document.getElementById('passport-grid');
grid.innerHTML = '';
let count = 0;
const iconMap = {
'EaDo': 'fa-bullhorn', 'Midtown': 'fa-moon', 'Downtown': 'fa-city',
'Montrose': 'fa-palette', 'Museum District': 'fa-tree', 'NRG': 'fa-futbol'
};
for(const [hood, stamped] of Object.entries(userStamps)) {
if(stamped) count++;
grid.innerHTML += `
<div class="stamp-card ${stamped ? 'stamped' : ''} border border-slate-700 bg-slate-800/50 p-4 rounded-2xl text-center relative overflow-hidden transition-all duration-300">
<div class="text-4xl mb-2 stamp-icon text-brand-cyan">
<i class="fa-solid ${iconMap[hood]}"></i>
</div>
<h4 class="font-bold text-white text-sm">${hood}</h4>
${stamped ? '<div class="absolute top-2 right-2 text-brand-beer text-xs"><i class="fa-solid fa-check-circle"></i></div>' : ''}
</div>
`;
}
document.getElementById('stamp-count').innerText = count;
document.getElementById('stamp-progress').style.width = `${(count/6)*100}%`;
};
window.togglePassport = () => {
renderPassport();
document.getElementById('passport-modal').classList.toggle('active');
};
window.checkIn = (neighborhood, btnElement) => {
userStamps[neighborhood] = true;
localStorage.setItem('houstonPassport', JSON.stringify(userStamps));
btnElement.innerHTML = '<i class="fa-solid fa-check mr-1"></i> Stamped!';
btnElement.classList.replace('bg-slate-700', 'bg-brand-cyan');
btnElement.classList.replace('text-white', 'text-slate-900');
setTimeout(() => { togglePassport(); }, 600);
if (currentUser && db) {
const passportRef = doc(db, 'artifacts', appId, 'users', currentUser.uid, 'passport', 'stamps');
setDoc(passportRef, userStamps, { merge: true }).catch(err => console.error("Cloud save failed", err));
}
};
// --- MAP INIT & RAIL LINES ---
const map = L.map('map', { zoomControl: false }).setView([29.74, -95.37], 13);
L.control.zoom({ position: 'bottomright' }).addTo(map);
L.tileLayer('https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png', {
attribution: '© OpenStreetMap © CARTO',
maxZoom: 20
}).addTo(map);
const redLineCoords = [[29.7640, -95.3610], [29.7520, -95.3680], [29.7340, -95.3800], [29.7150, -95.3900], [29.6900, -95.4050], [29.6800, -95.4020]];
const greenLineCoords = [[29.7620, -95.3640], [29.7600, -95.3580], [29.7530, -95.3400], [29.7400, -95.3100]];
const purpleLineCoords = [[29.7620, -95.3640], [29.7600, -95.3580], [29.7530, -95.3400], [29.7300, -95.3350], [29.7100, -95.3300]];
L.polyline(redLineCoords, { color: '#ef4444', weight: 4, opacity: 0.7, dashArray: '8, 8' }).addTo(map);
L.polyline(greenLineCoords, { color: '#22c55e', weight: 4, opacity: 0.7, dashArray: '8, 8' }).addTo(map);
L.polyline(purpleLineCoords, { color: '#a855f7', weight: 4, opacity: 0.7, dashArray: '8, 8' }).addTo(map);
const markersLayer = L.featureGroup().addTo(map);
let activeRouteLine = null;
const markerRefs = {};
const getMarkerStyle = (type) => {
if(type === 'coffee') return { color: '#fbbf24', shadow: 'rgba(251, 191, 36, 0.6)', icon: 'fa-mug-hot' };
if(type === 'tea') return { color: '#34d399', shadow: 'rgba(52, 211, 153, 0.6)', icon: 'fa-leaf' };
return { color: '#ec4899', shadow: 'rgba(236, 72, 153, 0.6)', icon: 'fa-beer-mug-empty' };
};
const getEnergyLevel = (venueId) => {
const data = energyData[`venue_${venueId}`] || { packed: 0, moderate: 0, dead: 0 };
const max = Math.max(data.packed, data.moderate, data.dead);
if (max === 0) return { label: "Vote Now", color: "text-slate-400", icon: "fa-circle-question" };
if (max === data.packed) return { label: "Packed", color: "text-brand-beer", icon: "fa-fire" };
if (max === data.moderate) return { label: "Moderate", color: "text-brand-coffee", icon: "fa-users" };
return { label: "Dead", color: "text-slate-500", icon: "fa-ghost" };
};
window.updateLiveEnergyUI = () => {
venues.forEach(venue => {
const el = document.getElementById(`energy-status-${venue.id}`);
if (el) {
const energy = getEnergyLevel(venue.id);
el.className = `${energy.color} transition-colors`;
el.innerHTML = `<i class="fa-solid ${energy.icon}"></i> ${energy.label}`;
}
});
};
window.voteEnergy = async (venueId, level, event) => {
let originalBg = '';
let btn = null;
if (event && event.currentTarget) {
btn = event.currentTarget;
originalBg = btn.className;
btn.classList.add('bg-brand-cyan/20', 'border-brand-cyan');
}
if (!db) {
// Fallback to local storage if Firebase is not connected
if (!energyDataLocal[`venue_${venueId}`]) energyDataLocal[`venue_${venueId}`] = { packed: 0, moderate: 0, dead: 0 };
energyDataLocal[`venue_${venueId}`][level]++;
localStorage.setItem('houstonVenueEnergy', JSON.stringify(energyDataLocal));
energyData = energyDataLocal;
window.updateLiveEnergyUI();
if (btn) setTimeout(() => { btn.className = originalBg; }, 500);
return;
}
// Cloud Save
const energyRef = doc(db, 'artifacts', appId, 'public', 'data', 'venue_energy', `venue_${venueId}`);
try {
await setDoc(energyRef, { [level]: increment(1) }, { merge: true });
if (btn) setTimeout(() => { btn.className = originalBg; }, 500);
} catch (error) {
console.error("Error voting:", error);
if (btn) btn.className = originalBg;
}
};
function renderMarkers(dataToRender) {
markersLayer.clearLayers();
for(let key in markerRefs) delete markerRefs[key];
dataToRender.forEach(venue => {
const style = getMarkerStyle(venue.type);
const energy = getEnergyLevel(venue.id);
const markerGlow = venue.featured ? 'box-shadow: 0 0 20px rgba(251, 191, 36, 0.9); border-color: #fbbf24;' : `box-shadow: 0 0 15px ${style.shadow}; border-color: ${style.color};`;
const iconColor = venue.featured ? 'color: #fbbf24;' : `color: ${style.color};`;
const customIcon = L.divIcon({
html: `
<div style="background-color: #0f172a; width: 40px; height: 40px; border-radius: 50%; display: flex; align-items: center; justify-content: center; ${iconColor} border: 2px solid transparent; ${markerGlow} transition: transform 0.2s;" class="hover:scale-110 hover:z-50 ${venue.featured ? 'animate-bounce' : 'animate-pulse-glow'}">
<i class="fa-solid ${venue.featured ? 'fa-star' : style.icon} text-lg"></i>
</div>
`,
className: 'custom-neon-marker',
iconSize: [40, 40], iconAnchor: [20, 20], popupAnchor: [0, -20]
});
const badgeBg = venue.type === 'coffee' ? 'text-amber-400' : venue.type === 'tea' ? 'text-emerald-400' : 'text-pink-400';
const isStamped = userStamps[venue.neighborhood];
const featuredBadge = venue.featured ? '<span class="text-[10px] font-bold px-2 py-1 rounded border border-amber-500/50 bg-amber-500/20 text-amber-400 uppercase"><i class="fa-solid fa-star mr-1"></i>Featured</span>' : '';
const popupContent = `
<div class="p-2 w-64">
<h4 class="font-display font-bold text-xl text-white leading-tight mb-2">${venue.name}</h4>
<div class="mb-3 flex flex-wrap gap-1">
<span class="text-[10px] font-bold px-2 py-1 rounded border border-slate-600 bg-slate-800 ${badgeBg} uppercase">${venue.type}</span>
<span class="text-[10px] font-bold px-2 py-1 rounded border border-slate-600 bg-slate-800 text-brand-cyan uppercase"><i class="fa-solid fa-fire mr-1"></i>${venue.vibe}</span>
${featuredBadge}
</div>
<div class="mb-3 p-2 bg-slate-800/80 rounded-lg border border-slate-700">
<div class="text-[10px] font-bold text-slate-400 uppercase mb-2 flex justify-between items-center">
<span>Fan Crowd Vote:</span>
<span id="energy-status-${venue.id}" class="${energy.color} transition-colors"><i class="fa-solid ${energy.icon}"></i> ${energy.label}</span>
</div>
<div class="flex gap-1">
<button onclick="voteEnergy(${venue.id}, 'packed', event)" class="flex-1 bg-slate-700 hover:bg-brand-beer/20 text-[10px] font-bold py-1.5 rounded transition-all text-slate-300 hover:text-brand-beer border border-transparent hover:border-brand-beer/30">🔥 Packed</button>
<button onclick="voteEnergy(${venue.id}, 'moderate', event)" class="flex-1 bg-slate-700 hover:bg-brand-coffee/20 text-[10px] font-bold py-1.5 rounded transition-all text-slate-300 hover:text-brand-coffee border border-transparent hover:border-brand-coffee/30">🚶 Mod</button>
<button onclick="voteEnergy(${venue.id}, 'dead', event)" class="flex-1 bg-slate-700 hover:bg-slate-600 text-[10px] font-bold py-1.5 rounded transition-all text-slate-300 border border-transparent">👻 Dead</button>
</div>
</div>
<p class="text-sm text-slate-300 mb-4 leading-relaxed">${venue.desc}</p>
<div class="flex gap-2">
<button onclick="checkIn('${venue.neighborhood}', this)" class="flex-1 ${isStamped ? 'bg-brand-cyan text-slate-900' : 'bg-slate-700 text-white'} font-bold py-2 rounded-lg text-xs transition-colors border border-transparent">
${isStamped ? '<i class="fa-solid fa-check mr-1"></i> Stamped!' : '<i class="fa-solid fa-stamp mr-1"></i> Check In'}
</button>
<a href="https://maps.google.com/?q=${venue.lat},${venue.lng}" target="_blank" class="w-10 flex items-center justify-center bg-slate-800 hover:bg-slate-700 text-white font-bold py-2 rounded-lg text-sm transition-colors border border-slate-600">
<i class="fa-solid fa-location-arrow"></i>
</a>
</div>
</div>
`;
const marker = L.marker([venue.lat, venue.lng], { icon: customIcon })
.bindPopup(popupContent)
.addTo(markersLayer);
markerRefs[venue.id] = marker;
});
}
// --- FILTERING & JUMP TO VENUE ---
let activeFilter = 'all';
window.jumpToVenue = (venueId) => {
if(!venueId) return;
document.querySelectorAll('.filter-btn').forEach(btn => {
btn.className = 'filter-btn whitespace-nowrap px-5 py-2.5 rounded-full text-sm font-bold glass-panel hover:bg-white/10 text-white transition-all';
});
const allBtn = document.querySelector('.filter-btn[data-filter="all"]');
if(allBtn) allBtn.className = 'filter-btn active whitespace-nowrap px-5 py-2.5 rounded-full text-sm font-bold bg-brand-cyan text-slate-900 border border-transparent shadow-[0_0_15px_rgba(6,182,212,0.4)] transition-all';
activeFilter = 'all';
if(activeRouteLine) { map.removeLayer(activeRouteLine); activeRouteLine = null; }
renderMarkers(venues);
const targetVenue = venues.find(v => v.id === venueId);
if(targetVenue && markerRefs[venueId]) {
map.flyTo([targetVenue.lat, targetVenue.lng], 16, { duration: 1 });
setTimeout(() => { markerRefs[venueId].openPopup(); }, 1000);
}
document.getElementById('map').scrollIntoView({ behavior: 'smooth', block: 'center' });
const jumpSelect = document.getElementById('venue-jump-select');
if(jumpSelect) jumpSelect.value = '';
};
const jumpSelect = document.getElementById('venue-jump-select');
if(jumpSelect) {
const sortedVenues = [...venues].sort((a, b) => a.name.localeCompare(b.name));
sortedVenues.forEach(v => {
const opt = document.createElement('option');
opt.value = v.id;
opt.textContent = `${v.name} (${v.neighborhood})`;
jumpSelect.appendChild(opt);
});
jumpSelect.addEventListener('change', (e) => {
jumpToVenue(parseInt(e.target.value));
});
}
function applyMapFilters() {
let filtered = venues;
if(activeFilter !== 'all') { filtered = filtered.filter(v => v.bestFor.includes(activeFilter)); }
if(activeRouteLine) { map.removeLayer(activeRouteLine); activeRouteLine = null; }
renderMarkers(filtered);
}
window.filterMapByNeighborhood = (neighborhood) => {
document.querySelectorAll('.filter-btn').forEach(btn => {
btn.className = 'filter-btn whitespace-nowrap px-5 py-2.5 rounded-full text-sm font-bold glass-panel hover:bg-white/10 text-white transition-all';
});
activeFilter = 'all';
if(activeRouteLine) { map.removeLayer(activeRouteLine); activeRouteLine = null; }
const filtered = venues.filter(v => v.neighborhood === neighborhood);
renderMarkers(filtered);
document.getElementById('map').scrollIntoView({ behavior: 'smooth', block: 'center' });
}
document.querySelectorAll('.filter-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
document.querySelectorAll('.filter-btn').forEach(b => {
b.className = 'filter-btn whitespace-nowrap px-5 py-2.5 rounded-full text-sm font-bold glass-panel hover:bg-white/10 text-white transition-all';
});
const target = e.target;
target.className = 'filter-btn active whitespace-nowrap px-5 py-2.5 rounded-full text-sm font-bold bg-brand-cyan text-slate-900 border border-transparent shadow-[0_0_15px_rgba(6,182,212,0.4)] transition-all';
activeFilter = target.dataset.filter;
applyMapFilters();
});
});
// --- SMART ITINERARY GENERATOR ---
let currentPreMatch = null;
let currentPostMatch = null;
document.getElementById('btn-generate').addEventListener('click', () => {
const time = document.getElementById('time-select').value;
const neighborhood = document.getElementById('neighborhood-select').value;
const selectedDrinks = Array.from(document.querySelectorAll('.drink-checkbox:checked')).map(cb => cb.value);
if(selectedDrinks.length === 0) return alert("Please select at least one drink vibe!");
// Smart Logic: Filter by Base Drink Preferences
let availableVenues = venues.filter(v => selectedDrinks.includes(v.type));
let preMatchVenues = [...availableVenues];
let postMatchVenues = [...availableVenues];
const isPre = time.includes('pre-match');
const isPost = time.includes('post-match');
// Apply Smart Time Logic
if(isPre) {
// Prioritize coffee, tea, morning fuel, rail accessible
preMatchVenues = availableVenues.filter(v => v.type === 'coffee' || v.type === 'tea' || v.vibe === 'Morning Fuel' || v.vibe === 'Group Friendly');
if(preMatchVenues.length === 0) preMatchVenues = availableVenues;
}
if(isPost) {
// Prioritize beer, rowdy, late night
postMatchVenues = availableVenues.filter(v => v.type === 'beer' || v.bestFor.includes('rowdy') || v.vibe.includes('Pub') || v.vibe === 'Late Night');
if(postMatchVenues.length === 0) postMatchVenues = availableVenues;
}
// Apply Smart Neighborhood Logic
if(neighborhood === 'NRG') {
// Force Red Line / Stadium proximity
preMatchVenues = preMatchVenues.filter(v => ['NRG', 'Midtown', 'Downtown'].includes(v.neighborhood));
postMatchVenues = postMatchVenues.filter(v => ['NRG', 'Midtown', 'Downtown'].includes(v.neighborhood));
} else if (neighborhood === 'EaDo') {
// Force Fan Festival proximity
preMatchVenues = preMatchVenues.filter(v => v.neighborhood === 'EaDo' || v.neighborhood === 'Downtown');
postMatchVenues = postMatchVenues.filter(v => v.neighborhood === 'EaDo');
} else if (neighborhood !== 'all') {
// Strict match for others
preMatchVenues = preMatchVenues.filter(v => v.neighborhood === neighborhood);
postMatchVenues = postMatchVenues.filter(v => v.neighborhood === neighborhood);
}
// Failsafe: if filters are too strict, open it up slightly
if(preMatchVenues.length === 0) preMatchVenues = availableVenues;
if(postMatchVenues.length === 0) postMatchVenues = availableVenues;
const shuffle = (arr) => arr.sort(() => 0.5 - Math.random());
currentPreMatch = shuffle([...preMatchVenues])[0];
let postMatchCandidates = shuffle([...postMatchVenues]).filter(v => v.id !== currentPreMatch.id);
// Smart Distance Logic: Don't cross the whole city unless user said "all"
if(neighborhood === 'all') {
currentPostMatch = postMatchCandidates[0] || currentPreMatch;
} else {
// Find a venue relatively close (heuristic: < ~3-4 miles approx)
currentPostMatch = postMatchCandidates.find(v => Math.hypot(v.lat - currentPreMatch.lat, v.lng - currentPreMatch.lng) < 0.08) || postMatchCandidates[0] || currentPreMatch;
}
const container = document.getElementById('itinerary-cards');
container.innerHTML = '';
const getStyle = (type) => {
if(type === 'coffee') return { color: 'text-brand-coffee', border: 'border-brand-coffee', bg: 'bg-brand-coffee/10', icon: 'fa-mug-hot' };
if(type === 'tea') return { color: 'text-brand-tea', border: 'border-brand-tea', bg: 'bg-brand-tea/10', icon: 'fa-leaf' };
return { color: 'text-brand-beer', border: 'border-brand-beer', bg: 'bg-brand-beer/10', icon: 'fa-beer-mug-empty' };
};
const createCard = (title, venue, step) => {
const s = getStyle(venue.type);
const isStamped = userStamps[venue.neighborhood];
return `
<div class="glass-panel rounded-2xl p-6 border-t-4 ${s.border} relative overflow-hidden group hover:-translate-y-1 transition-transform duration-300">
<div class="absolute -right-6 -top-6 w-24 h-24 ${s.bg} rounded-full blur-2xl group-hover:scale-150 transition-transform"></div>
<div class="relative z-10">
<div class="flex items-center justify-between mb-3">
<div class="flex items-center gap-2 text-xs font-bold text-slate-400 uppercase tracking-widest">
<span class="w-5 h-5 rounded-full bg-slate-800 flex items-center justify-center text-white border border-slate-600">${step}</span>
${title}
</div>
<span class="text-[10px] font-bold px-2 py-1 rounded bg-slate-800/80 text-brand-cyan border border-brand-cyan/30 uppercase"><i class="fa-solid fa-fire mr-1"></i>${venue.vibe}</span>
</div>
<h4 class="font-display font-bold text-2xl text-white mb-2 flex justify-between items-start">
${venue.name} <i class="fa-solid ${s.icon} ${s.color} text-xl"></i>
</h4>
<div class="inline-block text-xs font-bold px-2 py-1 bg-slate-800 border border-slate-700 rounded text-slate-300 mb-4">
<i class="fa-solid fa-location-dot mr-1 text-slate-500"></i> ${venue.neighborhood}
</div>
<p class="text-sm text-slate-400 leading-relaxed mb-4">${venue.desc}</p>
<button onclick="checkIn('${venue.neighborhood}', this)" class="w-full ${isStamped ? 'bg-brand-cyan text-slate-900' : 'bg-slate-700/50 hover:bg-slate-700 text-white'} font-bold py-2.5 rounded-xl text-sm transition-colors border border-white/10">
${isStamped ? '<i class="fa-solid fa-check mr-1"></i> Stamped in Passport!' : '<i class="fa-solid fa-stamp mr-1"></i> Check In to ' + venue.neighborhood}
</button>
</div>
</div>
`;
};
container.innerHTML += createCard("Pre-Match Stop", currentPreMatch, "1");
const watchZoneArea = neighborhood === 'all' ? 'EaDo Fan Festival Area' : `${neighborhood} Zones`;
let matchText = "Matchday Energy";
// Extract the date from the select input to show accurate match
const selectedOption = document.getElementById('time-select').options[document.getElementById('time-select').selectedIndex];
if (time === 'live') {
matchText = "Enjoy the City";
} else {
matchText = selectedOption.text.split(', Pre-')[0].split(', Post-')[0];
}
container.innerHTML += `
<div class="rounded-2xl p-6 relative overflow-hidden bg-gradient-to-br from-brand-cyan to-brand-purple flex flex-col justify-center text-center border border-white/20 shadow-[0_0_30px_rgba(139,92,246,0.3)] transform scale-[1.02] z-10">
<div class="absolute inset-0 bg-watermark opacity-[0.05]"></div>
<div class="relative z-10">
<div class="text-4xl mb-3 animate-bounce">⚽</div>
<div class="text-xs font-black text-white/70 uppercase tracking-widest mb-1">Step 2</div>
<h4 class="font-display font-black text-3xl text-white mb-2 drop-shadow-md">${watchZoneArea}</h4>
<p class="text-xs font-bold bg-black/20 py-1 px-3 rounded-full inline-block mt-2">${matchText}</p>
</div>
</div>
`;
container.innerHTML += createCard("Post-Match Vibes", currentPostMatch, "3");
document.getElementById('itinerary-results').classList.remove('hidden');
if(activeRouteLine) map.removeLayer(activeRouteLine);
activeRouteLine = L.polyline([
[currentPreMatch.lat, currentPreMatch.lng],
[currentPostMatch.lat, currentPostMatch.lng]
], { color: '#06b6d4', weight: 4, className: 'route-line-anim' }).addTo(map);
renderMarkers([currentPreMatch, currentPostMatch]);
setTimeout(() => { document.getElementById('itinerary-results').scrollIntoView({ behavior: 'smooth', block: 'start' }); }, 100);
});
// --- CTA FLOW ACTIONS ---
// 1. Open in Google Maps (Transit route between the two generated spots)
document.getElementById('btn-maps').addEventListener('click', () => {
if(!currentPreMatch || !currentPostMatch) return;
const mapsUrl = `https://www.google.com/maps/dir/?api=1&origin=${currentPreMatch.lat},${currentPreMatch.lng}&destination=${currentPostMatch.lat},${currentPostMatch.lng}&travelmode=transit`;
window.open(mapsUrl, '_blank');
});
// 2. Real Share API (with fallback)
document.getElementById('btn-share').addEventListener('click', () => {
if(!currentPreMatch || !currentPostMatch) return;
const shareTitle = "Houston Matchday Brew Route";
const shareText = `Check out our route for the match! Stop 1: ${currentPreMatch.name} ➡️ Stop 2: ${currentPostMatch.name}.`;
const shareUrl = window.location.href.split('?')[0] + `?pre=${currentPreMatch.id}&post=${currentPostMatch.id}&ref=share`;
if (navigator.share) {
navigator.share({
title: shareTitle,
text: shareText,
url: shareUrl
}).catch(err => console.error("Error sharing:", err));
} else {
// Fallback
try {
navigator.clipboard.writeText(shareText + " " + shareUrl);
const toast = document.getElementById('share-toast');
toast.classList.remove('hidden');
toast.classList.add('animate-[fadeIn_0.3s_ease-out]');
setTimeout(() => { toast.classList.add('hidden'); }, 3000);
} catch(e) {}
}
});
// 3. Save Plan (Future Email Hook)
document.getElementById('btn-save').addEventListener('click', (e) => {
const btn = e.currentTarget;
const originalText = btn.innerHTML;
btn.innerHTML = `<i class="fa-solid fa-check"></i> Plan Saved!`;
btn.classList.replace('bg-slate-800', 'bg-brand-cyan');
btn.classList.replace('text-slate-300', 'text-slate-900');
// Here you would hook into Mailchimp/Firestore to ask for an email
setTimeout(() => {
btn.innerHTML = originalText;
btn.classList.replace('bg-brand-cyan', 'bg-slate-800');
btn.classList.replace('text-slate-900', 'text-slate-300');
}, 3000);
});
// Add fadeIn keyframe inline
const style = document.createElement('style');
style.innerHTML = `@keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }`;
document.head.appendChild(style);
// --- INIT ---
renderMarkers(venues);
renderPassport();
</script>
</body>
</html>