Spaces:
Running
Running
Delete index.html
Browse files- index.html +0 -672
index.html
DELETED
|
@@ -1,672 +0,0 @@
|
|
| 1 |
-
<!DOCTYPE html>
|
| 2 |
-
<html lang="en">
|
| 3 |
-
<head>
|
| 4 |
-
<meta charset="UTF-8">
|
| 5 |
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
-
<title>AR City Explorer | Smart Glasses Companion</title>
|
| 7 |
-
<script src="https://cdn.tailwindcss.com"></script>
|
| 8 |
-
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/aframe.min.js"></script>
|
| 9 |
-
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/mindar-image-aframe.prod.js"></script>
|
| 10 |
-
<script src="https://cdn.jsdelivr.net/npm/speech-recognition@latest/dist/speech-recognition.min.js"></script>
|
| 11 |
-
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
| 12 |
-
<style>
|
| 13 |
-
@keyframes pulse {
|
| 14 |
-
0% { opacity: 0.6; }
|
| 15 |
-
50% { opacity: 1; }
|
| 16 |
-
100% { opacity: 0.6; }
|
| 17 |
-
}
|
| 18 |
-
.pulse {
|
| 19 |
-
animation: pulse 2s infinite;
|
| 20 |
-
}
|
| 21 |
-
.ar-overlay {
|
| 22 |
-
position: fixed;
|
| 23 |
-
top: 0;
|
| 24 |
-
left: 0;
|
| 25 |
-
width: 100%;
|
| 26 |
-
height: 100%;
|
| 27 |
-
background-color: rgba(0,0,0,0.8);
|
| 28 |
-
z-index: 9999;
|
| 29 |
-
display: flex;
|
| 30 |
-
flex-direction: column;
|
| 31 |
-
justify-content: center;
|
| 32 |
-
align-items: center;
|
| 33 |
-
color: white;
|
| 34 |
-
}
|
| 35 |
-
.ar-object {
|
| 36 |
-
position: absolute;
|
| 37 |
-
background-color: rgba(255,255,255,0.2);
|
| 38 |
-
border: 2px solid rgba(255,255,255,0.5);
|
| 39 |
-
border-radius: 8px;
|
| 40 |
-
padding: 10px;
|
| 41 |
-
color: white;
|
| 42 |
-
font-size: 14px;
|
| 43 |
-
pointer-events: all;
|
| 44 |
-
transition: all 0.3s ease;
|
| 45 |
-
}
|
| 46 |
-
.ar-object:hover {
|
| 47 |
-
background-color: rgba(255,255,255,0.3);
|
| 48 |
-
transform: scale(1.05);
|
| 49 |
-
}
|
| 50 |
-
.ai-message {
|
| 51 |
-
max-width: 80%;
|
| 52 |
-
margin-bottom: 10px;
|
| 53 |
-
padding: 12px;
|
| 54 |
-
border-radius: 18px;
|
| 55 |
-
position: relative;
|
| 56 |
-
}
|
| 57 |
-
.user-message {
|
| 58 |
-
background-color: #3b82f6;
|
| 59 |
-
color: white;
|
| 60 |
-
align-self: flex-end;
|
| 61 |
-
border-bottom-right-radius: 4px;
|
| 62 |
-
}
|
| 63 |
-
.assistant-message {
|
| 64 |
-
background-color: #f3f4f6;
|
| 65 |
-
color: #111827;
|
| 66 |
-
align-self: flex-start;
|
| 67 |
-
border-bottom-left-radius: 4px;
|
| 68 |
-
}
|
| 69 |
-
.glasses-pairing {
|
| 70 |
-
background: linear-gradient(135deg, #1e3a8a 0%, #3b82f6 100%);
|
| 71 |
-
}
|
| 72 |
-
.object-highlight {
|
| 73 |
-
box-shadow: 0 0 20px 5px rgba(59, 130, 246, 0.7);
|
| 74 |
-
}
|
| 75 |
-
#arViewport {
|
| 76 |
-
width: 100%;
|
| 77 |
-
height: 100%;
|
| 78 |
-
position: relative;
|
| 79 |
-
overflow: hidden;
|
| 80 |
-
}
|
| 81 |
-
.loading-spinner {
|
| 82 |
-
width: 50px;
|
| 83 |
-
height: 50px;
|
| 84 |
-
border: 5px solid rgba(255,255,255,0.3);
|
| 85 |
-
border-radius: 50%;
|
| 86 |
-
border-top-color: #fff;
|
| 87 |
-
animation: spin 1s ease-in-out infinite;
|
| 88 |
-
}
|
| 89 |
-
@keyframes spin {
|
| 90 |
-
to { transform: rotate(360deg); }
|
| 91 |
-
}
|
| 92 |
-
</style>
|
| 93 |
-
</head>
|
| 94 |
-
<body class="bg-gray-100 font-sans">
|
| 95 |
-
<!-- Main App Container -->
|
| 96 |
-
<div class="max-w-md mx-auto bg-white shadow-lg rounded-lg overflow-hidden min-h-screen flex flex-col">
|
| 97 |
-
<!-- Header -->
|
| 98 |
-
<header class="bg-indigo-900 text-white p-4 flex justify-between items-center">
|
| 99 |
-
<div class="flex items-center space-x-2">
|
| 100 |
-
<i class="fas fa-glasses text-2xl"></i>
|
| 101 |
-
<h1 class="text-xl font-bold">AR City Explorer</h1>
|
| 102 |
-
</div>
|
| 103 |
-
<div class="flex space-x-3">
|
| 104 |
-
<button id="arToggle" class="bg-indigo-700 hover:bg-indigo-600 px-3 py-1 rounded-full text-sm flex items-center">
|
| 105 |
-
<i class="fas fa-vr-cardboard mr-1"></i> AR Mode
|
| 106 |
-
</button>
|
| 107 |
-
<button id="settingsBtn" class="bg-indigo-700 hover:bg-indigo-600 px-3 py-1 rounded-full text-sm">
|
| 108 |
-
<i class="fas fa-cog"></i>
|
| 109 |
-
</button>
|
| 110 |
-
</div>
|
| 111 |
-
</header>
|
| 112 |
-
|
| 113 |
-
<!-- Main Content Area -->
|
| 114 |
-
<main class="flex-1 overflow-hidden">
|
| 115 |
-
<!-- AR Viewport (hidden by default) -->
|
| 116 |
-
<div id="arViewport" class="hidden relative">
|
| 117 |
-
<div id="arScene" class="absolute inset-0"></div>
|
| 118 |
-
<div id="arOverlay" class="ar-overlay hidden">
|
| 119 |
-
<div class="loading-spinner"></div>
|
| 120 |
-
<p class="mt-4">Initializing AR environment...</p>
|
| 121 |
-
<p class="text-sm text-gray-300 mt-2">Point your device at objects to scan</p>
|
| 122 |
-
</div>
|
| 123 |
-
<div id="arObjects" class="absolute inset-0"></div>
|
| 124 |
-
</div>
|
| 125 |
-
|
| 126 |
-
<!-- Normal View (shown by default) -->
|
| 127 |
-
<div id="normalView" class="h-full flex flex-col">
|
| 128 |
-
<!-- AI Assistant Chat -->
|
| 129 |
-
<div id="aiAssistant" class="flex-1 overflow-y-auto p-4 bg-gray-50">
|
| 130 |
-
<div class="flex flex-col space-y-2">
|
| 131 |
-
<div class="ai-message assistant-message">
|
| 132 |
-
<div class="font-bold text-indigo-800 mb-1">AR Guide</div>
|
| 133 |
-
<p>Hello! I'm your AR assistant. How can I help you explore the city today?</p>
|
| 134 |
-
<div class="mt-2 flex flex-wrap gap-2">
|
| 135 |
-
<button class="quick-action bg-indigo-100 text-indigo-800 px-3 py-1 rounded-full text-xs">
|
| 136 |
-
Nearby landmarks
|
| 137 |
-
</button>
|
| 138 |
-
<button class="quick-action bg-indigo-100 text-indigo-800 px-3 py-1 rounded-full text-xs">
|
| 139 |
-
Find restaurants
|
| 140 |
-
</button>
|
| 141 |
-
<button class="quick-action bg-indigo-100 text-indigo-800 px-3 py-1 rounded-full text-xs">
|
| 142 |
-
History tour
|
| 143 |
-
</button>
|
| 144 |
-
</div>
|
| 145 |
-
</div>
|
| 146 |
-
</div>
|
| 147 |
-
</div>
|
| 148 |
-
|
| 149 |
-
<!-- User Input -->
|
| 150 |
-
<div class="p-4 border-t border-gray-200 bg-white">
|
| 151 |
-
<div class="flex space-x-2">
|
| 152 |
-
<input type="text" id="userInput" placeholder="Ask about places, history, or services..."
|
| 153 |
-
class="flex-1 border border-gray-300 rounded-full px-4 py-2 focus:outline-none focus:ring-2 focus:ring-indigo-500">
|
| 154 |
-
<button id="sendBtn" class="bg-indigo-600 text-white rounded-full w-10 h-10 flex items-center justify-center">
|
| 155 |
-
<i class="fas fa-paper-plane"></i>
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
<script>
|
| 159 |
-
// DOM Elements
|
| 160 |
-
const arToggle = document.getElementById('arToggle');
|
| 161 |
-
const arViewport = document.getElementById('arViewport');
|
| 162 |
-
const normalView = document.getElementById('normalView');
|
| 163 |
-
const arOverlay = document.getElementById('arOverlay');
|
| 164 |
-
const glassesModal = document.getElementById('glassesModal');
|
| 165 |
-
const pairBtn = document.getElementById('pairBtn');
|
| 166 |
-
const pairingStatus = document.getElementById('pairingStatus');
|
| 167 |
-
const closeGlassesModal = document.getElementById('closeGlassesModal');
|
| 168 |
-
const glassesBtn = document.getElementById('glassesBtn');
|
| 169 |
-
const objectModal = document.getElementById('objectModal');
|
| 170 |
-
const closeObjectModal = document.getElementById('closeObjectModal');
|
| 171 |
-
const settingsBtn = document.getElementById('settingsBtn');
|
| 172 |
-
const settingsModal = document.getElementById('settingsModal');
|
| 173 |
-
const closeSettingsModal = document.getElementById('closeSettingsModal');
|
| 174 |
-
const manageGlassesBtn = document.getElementById('manageGlassesBtn');
|
| 175 |
-
const userInput = document.getElementById('userInput');
|
| 176 |
-
const sendBtn = document.getElementById('sendBtn');
|
| 177 |
-
const voiceBtn = document.getElementById('voiceBtn');
|
| 178 |
-
const aiAssistant = document.getElementById('aiAssistant');
|
| 179 |
-
|
| 180 |
-
// Sample AR objects data (in a real app, this would come from an API)
|
| 181 |
-
const arObjects = [
|
| 182 |
-
{
|
| 183 |
-
id: 'landmark-1',
|
| 184 |
-
type: 'landmark',
|
| 185 |
-
title: 'Old Town Hall',
|
| 186 |
-
description: 'Built in 1892, this historic building served as the city hall until 1978. Notable for its Victorian architecture and the clock tower that still keeps perfect time.',
|
| 187 |
-
image: 'https://images.unsplash.com/photo-1564501049412-61c2a3083791?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1000&q=80',
|
| 188 |
-
position: { x: 30, y: 40 },
|
| 189 |
-
vendors: []
|
| 190 |
-
},
|
| 191 |
-
{
|
| 192 |
-
id: 'landmark-2',
|
| 193 |
-
type: 'landmark',
|
| 194 |
-
title: 'Liberty Statue',
|
| 195 |
-
description: 'Erected in 1921 to commemorate the city\'s role in the independence movement. The statue stands 15 meters tall and is made of bronze.',
|
| 196 |
-
image: 'https://images.unsplash.com/photo-1582721478829-efeab1c87aec?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1000&q=80',
|
| 197 |
-
position: { x: 70, y: 60 },
|
| 198 |
-
vendors: []
|
| 199 |
-
},
|
| 200 |
-
{
|
| 201 |
-
id: 'sign-1',
|
| 202 |
-
type: 'sign',
|
| 203 |
-
title: 'Historic District Sign',
|
| 204 |
-
description: 'Marks the boundary of the city\'s historic preservation area. The district contains over 50 buildings from the 19th century.',
|
| 205 |
-
image: 'https://images.unsplash.com/photo-1614624532983-4fe9c6aff2f5?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1000&q=80',
|
| 206 |
-
position: { x: 50, y: 20 },
|
| 207 |
-
vendors: []
|
| 208 |
-
},
|
| 209 |
-
{
|
| 210 |
-
id: 'vendor-1',
|
| 211 |
-
type: 'vendor',
|
| 212 |
-
title: 'Heritage Café',
|
| 213 |
-
description: 'A charming café housed in a restored 19th-century building. Known for its artisanal coffee and homemade pastries.',
|
| 214 |
-
image: 'https://images.unsplash.com/photo-1555396273-367ea4eb4db5?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1000&q=80',
|
| 215 |
-
position: { x: 40, y: 70 },
|
| 216 |
-
vendors: [
|
| 217 |
-
{ id: 'item-1', name: 'Artisan Coffee', price: '$4.50', description: 'Locally roasted coffee beans' },
|
| 218 |
-
{ id: 'item-2', name: 'Croissant', price: '$3.25', description: 'Freshly baked butter croissant' },
|
| 219 |
-
{ id: 'item-3', name: 'Sandwich', price: '$7.50', description: 'Daily special with local ingredients' }
|
| 220 |
-
]
|
| 221 |
-
},
|
| 222 |
-
{
|
| 223 |
-
id: 'vendor-2',
|
| 224 |
-
type: 'vendor',
|
| 225 |
-
title: 'Souvenir Shop',
|
| 226 |
-
description: 'Offers locally made crafts, postcards, and memorabilia that celebrate the city\'s history.',
|
| 227 |
-
image: 'https://images.unsplash.com/photo-1607083206869-4c767bc49c4b?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1000&q=80',
|
| 228 |
-
position: { x: 60, y: 50 },
|
| 229 |
-
vendors: [
|
| 230 |
-
{ id: 'item-4', name: 'City Guidebook', price: '$12.99', description: 'Detailed history and walking tours' },
|
| 231 |
-
{ id: 'item-5', name: 'Postcard Set', price: '$5.50', description: '6 vintage-style postcards' },
|
| 232 |
-
{ id: 'item-6', name: 'Handmade Mug', price: '$15.00', description: 'Ceramic mug with city skyline' }
|
| 233 |
-
]
|
| 234 |
-
}
|
| 235 |
-
];
|
| 236 |
-
|
| 237 |
-
// Sample AI responses
|
| 238 |
-
const aiResponses = [
|
| 239 |
-
"The Old Town Hall is located about 200 meters north of your current position. It was built in 1892 and features remarkable Victorian architecture.",
|
| 240 |
-
"The Liberty Statue commemorates the city's role in the independence movement. It's a popular photo spot for visitors.",
|
| 241 |
-
"You're currently in the historic district which contains over 50 preserved buildings from the 19th century.",
|
| 242 |
-
"The Heritage Café is known for its artisanal coffee and homemade pastries. Would you like to see their menu?",
|
| 243 |
-
"The Souvenir Shop offers locally made crafts and memorabilia. They have guidebooks, postcards, and handmade items."
|
| 244 |
-
];
|
| 245 |
-
|
| 246 |
-
// Initialize AR
|
| 247 |
-
let arActive = false;
|
| 248 |
-
let glassesPaired = false;
|
| 249 |
-
|
| 250 |
-
// Toggle AR Mode
|
| 251 |
-
arToggle.addEventListener('click', () => {
|
| 252 |
-
if (!glassesPaired) {
|
| 253 |
-
showGlassesModal();
|
| 254 |
-
return;
|
| 255 |
-
}
|
| 256 |
-
|
| 257 |
-
arActive = !arActive;
|
| 258 |
-
|
| 259 |
-
if (arActive) {
|
| 260 |
-
// Enter AR Mode
|
| 261 |
-
normalView.classList.add('hidden');
|
| 262 |
-
arViewport.classList.remove('hidden');
|
| 263 |
-
arOverlay.classList.remove('hidden');
|
| 264 |
-
arToggle.innerHTML = '<i class="fas fa-times mr-1"></i> Exit AR';
|
| 265 |
-
|
| 266 |
-
// Simulate AR initialization
|
| 267 |
-
setTimeout(() => {
|
| 268 |
-
arOverlay.classList.add('hidden');
|
| 269 |
-
initializeARScene();
|
| 270 |
-
}, 2000);
|
| 271 |
-
} else {
|
| 272 |
-
// Exit AR Mode
|
| 273 |
-
normalView.classList.remove('hidden');
|
| 274 |
-
arViewport.classList.add('hidden');
|
| 275 |
-
arToggle.innerHTML = '<i class="fas fa-vr-cardboard mr-1"></i> AR Mode';
|
| 276 |
-
clearARScene();
|
| 277 |
-
}
|
| 278 |
-
});
|
| 279 |
-
|
| 280 |
-
// Initialize AR Scene
|
| 281 |
-
function initializeARScene() {
|
| 282 |
-
const arObjectsContainer = document.getElementById('arObjects');
|
| 283 |
-
|
| 284 |
-
// Clear previous objects
|
| 285 |
-
arObjectsContainer.innerHTML = '';
|
| 286 |
-
|
| 287 |
-
// Create AR objects
|
| 288 |
-
arObjects.forEach(obj => {
|
| 289 |
-
const arElement = document.createElement('div');
|
| 290 |
-
arElement.className = 'ar-object';
|
| 291 |
-
arElement.style.left = `${obj.position.x}%`;
|
| 292 |
-
arElement.style.top = `${obj.position.y}%`;
|
| 293 |
-
arElement.dataset.id = obj.id;
|
| 294 |
-
|
| 295 |
-
// Set icon based on type
|
| 296 |
-
let icon, bgColor;
|
| 297 |
-
if (obj.type === 'landmark') {
|
| 298 |
-
icon = '<i class="fas fa-landmark mr-1"></i>';
|
| 299 |
-
bgColor = 'rgba(220, 38, 38, 0.7)';
|
| 300 |
-
} else if (obj.type === 'sign') {
|
| 301 |
-
icon = '<i class="fas fa-sign mr-1"></i>';
|
| 302 |
-
bgColor = 'rgba(234, 88, 12, 0.7)';
|
| 303 |
-
} else if (obj.type === 'vendor') {
|
| 304 |
-
icon = '<i class="fas fa-store mr-1"></i>';
|
| 305 |
-
bgColor = 'rgba(22, 163, 74, 0.7)';
|
| 306 |
-
}
|
| 307 |
-
|
| 308 |
-
arElement.style.backgroundColor = bgColor;
|
| 309 |
-
arElement.innerHTML = `${icon} ${obj.title}`;
|
| 310 |
-
|
| 311 |
-
// Add click event
|
| 312 |
-
arElement.addEventListener('click', () => showObjectInfo(obj));
|
| 313 |
-
|
| 314 |
-
arObjectsContainer.appendChild(arElement);
|
| 315 |
-
});
|
| 316 |
-
// --- Modal Functionality ---
|
| 317 |
-
|
| 318 |
-
const objectModal = document.getElementById('objectModal');
|
| 319 |
-
const objectTitle = document.getElementById('objectTitle');
|
| 320 |
-
const objectDescription = document.getElementById('objectDescription');
|
| 321 |
-
const objectImage = document.getElementById('objectImage');
|
| 322 |
-
const closeObjectModal = document.getElementById('closeObjectModal');
|
| 323 |
-
|
| 324 |
-
|
| 325 |
-
function showObjectInfo(poiId) {
|
| 326 |
-
// Find the specific POI from our data array using its ID
|
| 327 |
-
const poi = poisData.find(p => p.id === poiId);
|
| 328 |
-
if (!poi) {
|
| 329 |
-
console.error("Could not find POI data for ID:", poiId);
|
| 330 |
-
return;
|
| 331 |
-
}
|
| 332 |
-
|
| 333 |
-
console.log("Showing info for:", poi.name);
|
| 334 |
-
|
| 335 |
-
// Populate the modal with data from the POI object
|
| 336 |
-
objectTitle.textContent = poi.name;
|
| 337 |
-
objectDescription.textContent = poi.description || "No description available.";
|
| 338 |
-
|
| 339 |
-
// Use a placeholder image if no asset_url is provided
|
| 340 |
-
// In a real app, your 'asset_url' might point to a specific image for the landmark
|
| 341 |
-
objectImage.src = `https://via.placeholder.com/300x200?text=${encodeURIComponent(poi.name)}`;
|
| 342 |
-
|
| 343 |
-
// --- Future Vendor Logic ---
|
| 344 |
-
// This is where you would check for vendor info from your POI data
|
| 345 |
-
// and show/hide the vendorSection just like your original code intended.
|
| 346 |
-
// For now, we'll keep it hidden.
|
| 347 |
-
document.getElementById('vendorSection').classList.add('hidden');
|
| 348 |
-
|
| 349 |
-
// Display the modal by removing the 'hidden' class
|
| 350 |
-
objectModal.classList.remove('hidden');
|
| 351 |
-
}
|
| 352 |
-
|
| 353 |
-
// Add an event listener to the modal's close button
|
| 354 |
-
closeObjectModal.addEventListener('click', () => {
|
| 355 |
-
objectModal.classList.add('hidden');
|
| 356 |
-
});
|
| 357 |
-
|
| 358 |
-
// Also close the modal if the user clicks on the dark background
|
| 359 |
-
objectModal.addEventListener('click', (e) => {
|
| 360 |
-
if (e.target === objectModal) {
|
| 361 |
-
objectModal.classList.add('hidden');
|
| 362 |
-
}
|
| 363 |
-
});
|
| 364 |
-
|
| 365 |
-
// Simulate object recognition highlighting
|
| 366 |
-
setTimeout(() => {
|
| 367 |
-
const firstObject = document.querySelector('.ar-object');
|
| 368 |
-
if (firstObject) {
|
| 369 |
-
firstObject.classList.add('object-highlight');
|
| 370 |
-
setTimeout(() => {
|
| 371 |
-
firstObject.classList.remove('object-highlight');
|
| 372 |
-
}, 3000);
|
| 373 |
-
}
|
| 374 |
-
}, 1000);
|
| 375 |
-
}
|
| 376 |
-
|
| 377 |
-
// Clear AR Scene
|
| 378 |
-
function clearARScene() {
|
| 379 |
-
const arObjectsContainer = document.getElementById('arObjects');
|
| 380 |
-
arObjectsContainer.innerHTML = '';
|
| 381 |
-
}
|
| 382 |
-
|
| 383 |
-
// Show object info modal
|
| 384 |
-
function showObjectInfo(obj) {
|
| 385 |
-
document.getElementById('objectTitle').textContent = obj.title;
|
| 386 |
-
document.getElementById('objectImage').src = obj.image;
|
| 387 |
-
document.getElementById('objectDescription').textContent = obj.description;
|
| 388 |
-
|
| 389 |
-
// Show vendor section if applicable
|
| 390 |
-
const vendorSection = document.getElementById('vendorSection');
|
| 391 |
-
if (obj.vendors && obj.vendors.length > 0) {
|
| 392 |
-
vendorSection.classList.remove('hidden');
|
| 393 |
-
const vendorItemsContainer = vendorSection.querySelector('.space-y-2');
|
| 394 |
-
vendorItemsContainer.innerHTML = '';
|
| 395 |
-
|
| 396 |
-
obj.vendors.forEach(vendor => {
|
| 397 |
-
const itemElement = document.createElement('div');
|
| 398 |
-
itemElement.className = 'flex justify-between items-center p-2 bg-gray-100 rounded';
|
| 399 |
-
itemElement.innerHTML = `
|
| 400 |
-
<div>
|
| 401 |
-
<div class="font-medium">${vendor.name}</div>
|
| 402 |
-
<div class="text-sm text-gray-600">${vendor.description}</div>
|
| 403 |
-
</div>
|
| 404 |
-
<div class="font-bold">${vendor.price}</div>
|
| 405 |
-
`;
|
| 406 |
-
vendorItemsContainer.appendChild(itemElement);
|
| 407 |
-
});
|
| 408 |
-
} else {
|
| 409 |
-
vendorSection.classList.add('hidden');
|
| 410 |
-
}
|
| 411 |
-
|
| 412 |
-
objectModal.classList.remove('hidden');
|
| 413 |
-
}
|
| 414 |
-
|
| 415 |
-
// Glasses pairing functionality
|
| 416 |
-
function showGlassesModal() {
|
| 417 |
-
glassesModal.classList.remove('hidden');
|
| 418 |
-
}
|
| 419 |
-
|
| 420 |
-
closeGlassesModal.addEventListener('click', () => {
|
| 421 |
-
glassesModal.classList.add('hidden');
|
| 422 |
-
});
|
| 423 |
-
|
| 424 |
-
pairBtn.addEventListener('click', () => {
|
| 425 |
-
pairingStatus.classList.remove('hidden');
|
| 426 |
-
pairBtn.disabled = true;
|
| 427 |
-
|
| 428 |
-
// Simulate pairing process
|
| 429 |
-
setTimeout(() => {
|
| 430 |
-
pairingStatus.innerHTML = '<div class="flex items-center justify-center text-green-300"><i class="fas fa-check-circle mr-2"></i> Successfully paired!</div>';
|
| 431 |
-
setTimeout(() => {
|
| 432 |
-
glassesModal.classList.add('hidden');
|
| 433 |
-
pairingStatus.classList.add('hidden');
|
| 434 |
-
pairBtn.disabled = false;
|
| 435 |
-
glassesPaired = true;
|
| 436 |
-
|
| 437 |
-
// Update glasses button
|
| 438 |
-
const glassesIcon = glassesBtn.querySelector('i');
|
| 439 |
-
glassesIcon.classList.remove('text-gray-500');
|
| 440 |
-
glassesIcon.classList.add('text-indigo-600');
|
| 441 |
-
|
| 442 |
-
// Show success message
|
| 443 |
-
addAIMessage("Your AR glasses have been successfully paired! You can now enter AR mode.");
|
| 444 |
-
}, 1000);
|
| 445 |
-
}, 2000);
|
| 446 |
-
});
|
| 447 |
-
|
| 448 |
-
glassesBtn.addEventListener('click', showGlassesModal);
|
| 449 |
-
|
| 450 |
-
// Settings modal
|
| 451 |
-
settingsBtn.addEventListener('click', () => {
|
| 452 |
-
settingsModal.classList.remove('hidden');
|
| 453 |
-
});
|
| 454 |
-
|
| 455 |
-
closeSettingsModal.addEventListener('click', () => {
|
| 456 |
-
settingsModal.classList.add('hidden');
|
| 457 |
-
});
|
| 458 |
-
<body class="bg-gray-100 font-sans">
|
| 459 |
-
<div class="max-w-md mx-auto bg-white shadow-lg rounded-lg overflow-hidden min-h-screen flex flex-col">
|
| 460 |
-
<header class="bg-indigo-900 text-white p-4 flex justify-between items-center">
|
| 461 |
-
</header>
|
| 462 |
-
|
| 463 |
-
<main class="flex-1 overflow-hidden">
|
| 464 |
-
<div id="arViewport" class="hidden relative">
|
| 465 |
-
|
| 466 |
-
<a-scene id="arScene" vr-mode-ui="enabled: false" arjs='sourceType: webcam; videoTexture: true; debugUIEnabled: false;'>
|
| 467 |
-
<a-camera gps-new-camera='gpsMinDistance: 5'></a-camera>
|
| 468 |
-
</a-scene>
|
| 469 |
-
|
| 470 |
-
<div id="arOverlay" class="ar-overlay hidden">
|
| 471 |
-
</div>
|
| 472 |
-
</div>
|
| 473 |
-
|
| 474 |
-
<div id="normalView" class="h-full flex flex-col">
|
| 475 |
-
</div>
|
| 476 |
-
</main>
|
| 477 |
-
|
| 478 |
-
<nav class="bg-white border-t border-gray-200 p-2 flex justify-around">
|
| 479 |
-
</nav>
|
| 480 |
-
</div>
|
| 481 |
-
|
| 482 |
-
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/aframe.min.js"></script>
|
| 483 |
-
<script src="https://raw.githack.com/AR-js-org/AR.js/master/aframe/build/aframe-ar-nft.js"></script>
|
| 484 |
-
|
| 485 |
-
<script>
|
| 486 |
-
document.addEventListener('DOMContentLoaded', () => {
|
| 487 |
-
|
| 488 |
-
// --- CONFIGURATION ---
|
| 489 |
-
const config = {
|
| 490 |
-
// FIX: USE YOUR FULL AND CORRECT CLOUDFLARE URL HERE
|
| 491 |
-
backendUrl: 'https://ar-city-explorer-backend.<your-subdomain>.workers.dev'
|
| 492 |
-
};
|
| 493 |
-
|
| 494 |
-
// --- ELEMENT SELECTORS ---
|
| 495 |
-
const arViewport = document.getElementById('arViewport');
|
| 496 |
-
const normalView = document.getElementById('normalView');
|
| 497 |
-
const arToggle = document.getElementById('arToggle');
|
| 498 |
-
const aiAssistant = document.getElementById('aiAssistant');
|
| 499 |
-
const userInput = document.getElementById('userInput');
|
| 500 |
-
const sendBtn = document.getElementById('sendBtn');
|
| 501 |
-
// ... (add all your other getElementById selectors here for modals, buttons etc.)
|
| 502 |
-
|
| 503 |
-
let poisData = []; // To store data from the backend
|
| 504 |
-
|
| 505 |
-
// --- CORE FUNCTIONS ---
|
| 506 |
-
|
| 507 |
-
function toggleARView(showAR) {
|
| 508 |
-
if (showAR) {
|
| 509 |
-
normalView.classList.add('hidden');
|
| 510 |
-
arViewport.classList.remove('hidden');
|
| 511 |
-
// Only fetch data if we don't have it already
|
| 512 |
-
if (poisData.length === 0) {
|
| 513 |
-
fetchPoisAndCreateAREntities();
|
| 514 |
-
}
|
| 515 |
-
} else {
|
| 516 |
-
arViewport.classList.add('hidden');
|
| 517 |
-
normalView.classList.remove('hidden');
|
| 518 |
-
}
|
| 519 |
-
}
|
| 520 |
-
|
| 521 |
-
function fetchPoisAndCreateAREntities() {
|
| 522 |
-
console.log('Fetching POIs from backend...');
|
| 523 |
-
|
| 524 |
-
fetch(`${config.backendUrl}/api/pois`)
|
| 525 |
-
.then(response => {
|
| 526 |
-
if (!response.ok) {
|
| 527 |
-
throw new Error(`Network response was not ok: ${response.statusText}`);
|
| 528 |
-
}
|
| 529 |
-
return response.json();
|
| 530 |
-
})
|
| 531 |
-
.then(pois => {
|
| 532 |
-
console.log('POIs received:', pois);
|
| 533 |
-
poisData = pois; // Store the data
|
| 534 |
-
const scene = document.querySelector('a-scene');
|
| 535 |
-
|
| 536 |
-
if (!scene) {
|
| 537 |
-
console.error('Could not find <a-scene> element.');
|
| 538 |
-
return;
|
| 539 |
-
}
|
| 540 |
-
|
| 541 |
-
pois.forEach(poi => {
|
| 542 |
-
const entity = document.createElement('a-entity');
|
| 543 |
-
entity.setAttribute('gps-new-entity-place', {
|
| 544 |
-
latitude: poi.latitude,
|
| 545 |
-
longitude: poi.longitude
|
| 546 |
-
});
|
| 547 |
-
|
| 548 |
-
// Add visual components (e.g., a box)
|
| 549 |
-
const box = document.createElement('a-box');
|
| 550 |
-
box.setAttribute('material', 'color: red');
|
| 551 |
-
box.setAttribute('scale', '10 10 10');
|
| 552 |
-
box.setAttribute('position', '0 5 0');
|
| 553 |
-
|
| 554 |
-
// Make the box clickable
|
| 555 |
-
box.setAttribute('data-poi-id', poi.id); // Store poi id on the box
|
| 556 |
-
|
| 557 |
-
entity.appendChild(box);
|
| 558 |
-
|
| 559 |
-
const text = document.createElement('a-text');
|
| 560 |
-
text.setAttribute('value', poi.name);
|
| 561 |
-
text.setAttribute('look-at', '[gps-new-camera]');
|
| 562 |
-
text.setAttribute('scale', '50 50 50');
|
| 563 |
-
text.setAttribute('position', '0 15 0');
|
| 564 |
-
entity.appendChild(text);
|
| 565 |
-
|
| 566 |
-
scene.appendChild(entity);
|
| 567 |
-
});
|
| 568 |
-
})
|
| 569 |
-
.catch(error => {
|
| 570 |
-
console.error('Failed to load or process POIs:', error);
|
| 571 |
-
alert('Could not load city data. Please check your connection and try again.');
|
| 572 |
-
toggleARView(false); // Switch back to normal view on error
|
| 573 |
-
});
|
| 574 |
-
}
|
| 575 |
-
|
| 576 |
-
function showObjectInfo(poiId) {
|
| 577 |
-
const poi = poisData.find(p => p.id === poiId);
|
| 578 |
-
if(!poi) return;
|
| 579 |
-
|
| 580 |
-
console.log("Showing info for:", poi.name);
|
| 581 |
-
// This is where you would populate and show your objectModal
|
| 582 |
-
// document.getElementById('objectTitle').textContent = poi.name;
|
| 583 |
-
// document.getElementById('objectDescription').textContent = poi.description;
|
| 584 |
-
// ... etc.
|
| 585 |
-
}
|
| 586 |
-
|
| 587 |
-
|
| 588 |
-
// --- EVENT LISTENERS ---
|
| 589 |
-
|
| 590 |
-
arToggle.addEventListener('click', () => toggleARView(true));
|
| 591 |
-
|
| 592 |
-
// Add a listener for clicks within the AR scene
|
| 593 |
-
document.querySelector('a-scene').addEventListener('click', (event) => {
|
| 594 |
-
// Check if the clicked element is one of our boxes
|
| 595 |
-
if (event.target.hasAttribute('data-poi-id')) {
|
| 596 |
-
const poiId = parseInt(event.target.getAttribute('data-poi-id'), 10);
|
| 597 |
-
showObjectInfo(poiId);
|
| 598 |
-
}
|
| 599 |
-
});
|
| 600 |
-
|
| 601 |
-
// Add all your other event listeners for the UI here
|
| 602 |
-
// sendBtn.addEventListener('click', () => { ... });
|
| 603 |
-
// voiceBtn.addEventListener('click', () => { ... });
|
| 604 |
-
// --- AI Assistant and Search Functionality ---
|
| 605 |
-
|
| 606 |
-
function addUserMessage(message) {
|
| 607 |
-
const chatContainer = aiAssistant.querySelector('.flex-col');
|
| 608 |
-
const messageElement = document.createElement('div');
|
| 609 |
-
messageElement.className = 'ai-message user-message';
|
| 610 |
-
messageElement.innerHTML = `<p>${message}</p>`;
|
| 611 |
-
chatContainer.appendChild(messageElement);
|
| 612 |
-
aiAssistant.scrollTop = aiAssistant.scrollHeight; // Auto-scroll
|
| 613 |
-
}
|
| 614 |
-
|
| 615 |
-
function addAIMessage(message) {
|
| 616 |
-
const chatContainer = aiAssistant.querySelector('.flex-col');
|
| 617 |
-
const messageElement = document.createElement('div');
|
| 618 |
-
messageElement.className = 'ai-message assistant-message';
|
| 619 |
-
messageElement.innerHTML = `
|
| 620 |
-
<div class="font-bold text-indigo-800 mb-1">AR Guide</div>
|
| 621 |
-
<p>${message}</p>
|
| 622 |
-
`;
|
| 623 |
-
chatContainer.appendChild(messageElement);
|
| 624 |
-
aiAssistant.scrollTop = aiAssistant.scrollHeight; // Auto-scroll
|
| 625 |
-
}
|
| 626 |
-
|
| 627 |
-
// Event listener for the send button
|
| 628 |
-
sendBtn.addEventListener('click', () => {
|
| 629 |
-
const searchTerm = userInput.value.trim();
|
| 630 |
-
if (!searchTerm) return;
|
| 631 |
-
|
| 632 |
-
addUserMessage(searchTerm);
|
| 633 |
-
userInput.value = '';
|
| 634 |
-
|
| 635 |
-
// --- NEW SEARCH LOGIC ---
|
| 636 |
-
// Make the search case-insensitive
|
| 637 |
-
const lowerCaseSearchTerm = searchTerm.toLowerCase();
|
| 638 |
-
|
| 639 |
-
// Filter the POI data we got from the backend
|
| 640 |
-
const results = poisData.filter(poi =>
|
| 641 |
-
poi.name.toLowerCase().includes(lowerCaseSearchTerm) ||
|
| 642 |
-
(poi.description && poi.description.toLowerCase().includes(lowerCaseSearchTerm))
|
| 643 |
-
);
|
| 644 |
-
|
| 645 |
-
// Respond based on the search results
|
| 646 |
-
if (results.length > 0) {
|
| 647 |
-
let responseMessage = `I found ${results.length} location(s) matching your search:<ul>`;
|
| 648 |
-
results.forEach(poi => {
|
| 649 |
-
responseMessage += `<li class="mt-2 list-disc list-inside">${poi.name}</li>`;
|
| 650 |
-
});
|
| 651 |
-
responseMessage += "</ul>";
|
| 652 |
-
addAIMessage(responseMessage);
|
| 653 |
-
} else {
|
| 654 |
-
addAIMessage(`Sorry, I couldn't find any locations matching "${searchTerm}". Try searching for something else.`);
|
| 655 |
-
}
|
| 656 |
-
});
|
| 657 |
-
|
| 658 |
-
// Also allow pressing Enter to search
|
| 659 |
-
userInput.addEventListener('keypress', (e) => {
|
| 660 |
-
if (e.key === 'Enter') {
|
| 661 |
-
sendBtn.click();
|
| 662 |
-
}
|
| 663 |
-
});
|
| 664 |
-
});
|
| 665 |
-
</script>
|
| 666 |
-
|
| 667 |
-
</body>
|
| 668 |
-
</html>
|
| 669 |
-
</body>
|
| 670 |
-
</html>
|
| 671 |
-
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=privateuserh/smarar" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
|
| 672 |
-
</html>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|