-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.html
More file actions
198 lines (167 loc) · 11.4 KB
/
Copy pathindex.html
File metadata and controls
198 lines (167 loc) · 11.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Live Event Directory</title>
<!-- Tailwind CSS for Responsive Layouts -->
<script src="https://cdn.tailwindcss.com"></script>
<!-- Mapbox GL JS CSS & JS -->
<link href="https://api.mapbox.com/mapbox-gl-js/v3.3.0/mapbox-gl.css" rel="stylesheet">
<script src="https://api.mapbox.com/mapbox-gl-js/v3.3.0/mapbox-gl.js"></script>
<style>
/* Hide scrollbars for a clean look on horizontal chip filters */
.no-scrollbar::-webkit-scrollbar { display: none; }
.no-scrollbar { -ms-overflow-style: none; scrollbar-width: none; }
</style>
</head>
<body class="bg-gray-50 font-sans text-gray-900 antialiased m-0 p-0 overflow-hidden h-screen w-screen">
<!-- Main Container -->
<div class="flex flex-col lg:flex-row h-full w-full relative">
<!-- LEFT PANEL: Filter Navigation & Event Feed Feed -->
<div class="w-full lg:w-1/2 flex flex-col h-1/2 lg:h-full bg-white z-10 shadow-xl overflow-hidden">
<!-- Header & Filter Subsystem -->
<div class="p-4 border-b border-gray-200 bg-white flex-shrink-0">
<h1 class="text-xl font-bold tracking-tight text-gray-900 mb-3">Explore Local Events</h1>
<!-- Horizontal Filter Navigation (Mobile-Friendly) -->
<div id="filter-bar" class="flex gap-2 overflow-x-auto no-scrollbar pb-1">
<button onclick="filterCategory('All')" class="filter-btn px-4 py-1.5 rounded-full text-sm font-medium bg-indigo-600 text-white whitespace-nowrap transition">All Events</button>
<button onclick="filterCategory('Music')" class="filter-btn px-4 py-1.5 rounded-full text-sm font-medium bg-gray-100 text-gray-600 hover:bg-gray-200 whitespace-nowrap transition">Music</button>
<button onclick="filterCategory('Food')" class="filter-btn px-4 py-1.5 rounded-full text-sm font-medium bg-gray-100 text-gray-600 hover:bg-gray-200 whitespace-nowrap transition">Food & Drink</button>
<button onclick="filterCategory('Art')" class="filter-btn px-4 py-1.5 rounded-full text-sm font-medium bg-gray-100 text-gray-600 hover:bg-gray-200 whitespace-nowrap transition">Art & Culture</button>
<button onclick="filterCategory('Tech')" class="filter-btn px-4 py-1.5 rounded-full text-sm font-medium bg-gray-100 text-gray-600 hover:bg-gray-200 whitespace-nowrap transition">Tech & Business</button>
</div>
</div>
<!-- Scrollable Event Feed Grid -->
<div id="event-feed" class="flex-1 overflow-y-auto p-4 space-y-4 bg-gray-50 no-scrollbar">
<!-- Dynamic cards hydrate here -->
</div>
</div>
<!-- RIGHT PANEL: Fixed Map Sticky Viewport -->
<div class="w-full lg:w-1/2 h-1/2 lg:h-full relative">
<div id="map" class="absolute inset-0 w-full h-full"></div>
</div>
</div>
<!-- Frontend Core Application Controller -->
<script>
// ⚠️ REPLACE WITH YOUR SECURE PUBLIC ACCESS TOKEN FROM MAPBOX DASHBOARD
mapboxgl.accessToken = 'pk.eyJ1Ijoic2FtcGxldXNlciIsImEiOiJjbHNleW1zeG0wMXRtMmtvM3NndTNmYmkyIn0.sample_token_X';
// Sample Unified Mock Database Schema matching standard CMS Taxonomy
const mockDatabase = [
{ id: 1, title: "Summer Sonic Bass Festival", category: "Music", date: "June 15, 2026", time: "18:00", price: "$45", url: "#", tags: ["Outdoor", "Concert"], img: "https://images.unsplash.com/photo-1506157786151-b8491531f063?q=80&w=400", lng: -122.4194, lat: 37.7749 },
{ id: 2, title: "Artisan Night Food Market", category: "Food", date: "June 18, 2026", time: "17:30", price: "Free Admission", url: "#", tags: ["Foodie", "Local"], img: "https://images.unsplash.com/photo-1555396273-367ea4eb4db5?q=80&w=400", lng: -122.4312, lat: 37.7630 },
{ id: 3, title: "Modern Abstract Exhibition", category: "Art", date: "June 22, 2026", time: "10:00", price: "$15", url: "#", tags: ["Gallery", "Museum"], img: "https://images.unsplash.com/photo-1579783900882-c0d3dad7b119?q=80&w=400", lng: -122.4088, lat: 37.7879 },
{ id: 4, title: "Next-Gen AI & Web3 Summit", category: "Tech", date: "June 29, 2026", time: "09:00", price: "$299", url: "#", tags: ["Networking", "Tech"], img: "https://images.unsplash.com/photo-1540575467063-178a50c2df87?q=80&w=400", lng: -122.4220, lat: 37.7550 }
];
let currentActiveCategory = "All";
let mapMarkersArray = [];
// Global Map Canvas Initialization
const map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/light-v11', // Clean minimalist developer canvas
center: [-122.4194, 37.7749], // San Francisco Default Anchor Coordinates
zoom: 12.5
});
map.addControl(new mapboxgl.NavigationControl(), 'top-right');
// Hydrate Application state inside Map Load Event Loop
map.on('load', () => {
renderDirectoryState(mockDatabase);
});
// Main Rendering Pipeline: Builds cards and synchronizes pins
function renderDirectoryState(data) {
const feedContainer = document.getElementById('event-feed');
feedContainer.innerHTML = '';
// Clear active hardware markers from map viewport canvas
mapMarkersArray.forEach(m => m.remove());
mapMarkersArray = [];
if (data.length === 0) {
feedContainer.innerHTML = `<div class="text-center py-12 text-gray-500 font-medium">No active events found inside this taxonomy parameter.</div>`;
return;
}
const bounds = new mapboxgl.LngLatBounds();
data.forEach(event => {
// 1. Create a modern custom DOM element for the map pin anchor
const markerEl = document.createElement('div');
markerEl.id = `pin-${event.id}`;
markerEl.className = 'w-7 h-7 bg-white border-2 border-indigo-600 rounded-full flex items-center justify-center shadow-lg cursor-pointer transform transition duration-200 hover:scale-125 z-0';
markerEl.innerHTML = `<div class="w-2.5 h-2.5 bg-indigo-600 rounded-full"></div>`;
// Add popups on click
const popup = new mapboxgl.Popup({ offset: 25 }).setHTML(`
<div class="p-1 font-sans">
<p class="font-bold text-sm text-gray-900">${event.title}</p>
<p class="text-xs text-indigo-600 font-medium">${event.date} • ${event.price}</p>
</div>
`);
const markerInstance = new mapboxgl.Marker(markerEl)
.setLngLat([event.lng, event.lat])
.setPopup(popup)
.addTo(map);
mapMarkersArray.push(markerInstance);
bounds.extend([event.lng, event.lat]);
// 2. Generate Responsive Material Card Structure
const card = document.createElement('div');
card.className = 'bg-white rounded-xl shadow-sm border border-gray-100 p-4 flex gap-4 cursor-pointer hover:shadow-md transition duration-200 border-l-4 border-l-transparent';
card.id = `card-${event.id}`;
// Active Hover Event System linking Card -> Map Pin Highlight Interaction
card.addEventListener('mouseenter', () => highlightMapPin(event.id, true));
card.addEventListener('mouseleave', () => highlightMapPin(event.id, false));
card.addEventListener('click', () => {
map.flyTo({ center: [event.lng, event.lat], zoom: 14.5, essential: true });
markerInstance.togglePopup();
});
card.innerHTML = `
<img src="${event.img}" alt="Event Visual Preview" class="w-24 h-24 object-cover rounded-lg flex-shrink-0 bg-gray-100">
<div class="flex-1 min-w-0 flex flex-col justify-between">
<div>
<span class="inline-block px-2 py-0.5 bg-indigo-50 text-indigo-700 font-semibold text-[10px] uppercase tracking-wider rounded-md mb-1">${event.category}</span>
<h3 class="font-bold text-base text-gray-900 truncate mb-0.5">${event.title}</h3>
<p class="text-xs text-gray-500 font-medium">${event.date} at ${event.time}</p>
</div>
<div class="flex items-center justify-between mt-2">
<span class="text-sm font-bold text-gray-900">${event.price}</span>
<div class="flex gap-1">
${event.tags.map(tag => `<span class="text-[10px] bg-gray-100 text-gray-600 px-2 py-0.5 rounded">${tag}</span>`).join('')}
</div>
</div>
</div>
`;
feedContainer.appendChild(card);
});
// Adjust viewport to perfectly frame visible data sets without breaking layouts
map.fitBounds(bounds, { padding: 60, maxZoom: 14, duration: 1200 });
}
// Mutation Handler for Syncing Pin States
function highlightMapPin(id, isHighlighted) {
const pin = document.getElementById(`pin-${id}`);
const card = document.getElementById(`card-${id}`);
if (!pin) return;
if (isHighlighted) {
pin.className = 'w-9 h-9 bg-indigo-600 border-2 border-white rounded-full flex items-center justify-center shadow-2xl cursor-pointer transform scale-125 z-50 transition duration-150';
pin.innerHTML = `<div class="w-3 h-3 bg-white rounded-full"></div>`;
card.classList.add('border-l-indigo-600', 'bg-indigo-50/20');
} else {
pin.className = 'w-7 h-7 bg-white border-2 border-indigo-600 rounded-full flex items-center justify-center shadow-lg cursor-pointer transform scale-100 z-0 transition duration-150';
pin.innerHTML = `<div class="w-2.5 h-2.5 bg-indigo-600 rounded-full"></div>`;
card.classList.remove('border-l-indigo-600', 'bg-indigo-50/20');
}
}
// Frontend Database Filtering Sub-routine
function filterCategory(category) {
currentActiveCategory = category;
// Visual Chip Active CSS State Manager
const buttons = document.querySelectorAll('.filter-btn');
buttons.forEach(btn => {
if(btn.innerText.includes(category)) {
btn.className = "filter-btn px-4 py-1.5 rounded-full text-sm font-medium bg-indigo-600 text-white whitespace-nowrap transition";
} else {
btn.className = "filter-btn px-4 py-1.5 rounded-full text-sm font-medium bg-gray-100 text-gray-600 hover:bg-gray-200 whitespace-nowrap transition";
}
});
const filteredData = category === "All"
? mockDatabase
: mockDatabase.filter(item => item.category === category);
renderDirectoryState(filteredData);
}
</script>
</body>
</html>