<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Interactive Inventory Map</title> <style> .inventory-map-container { position: relative; width: 100%; max-width: 100%; margin: 0 auto; background: #fff; border-radius: 8px; overflow: hidden; box-shadow: 0 2px 10px rgba(0,0,0,0.1); } .map-image { width: 100%; height: auto; display: block; } .dot-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; } .inventory-dot { position: absolute; width: 20px; height: 20px; background: #28a745; border: 3px solid white; border-radius: 50%; cursor: pointer; pointer-events: all; transform: translate(-50%, -50%); transition: all 0.2s ease; box-shadow: 0 2px 8px rgba(0,0,0,0.3); user-select: none; } .inventory-dot:hover { transform: translate(-50%, -50%) scale(1.2); box-shadow: 0 4px 12px rgba(0,0,0,0.4); } .edit-modal { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.7); z-index: 10000; padding: 20px; } .modal-content { background: white; border-radius: 8px; max-width: 400px; width: 100%; margin: 50px auto; padding: 24px; position: relative; box-shadow: 0 10px 30px rgba(0,0,0,0.3); } .modal-header { margin-bottom: 20px; } .modal-title { margin: 0; font-size: 20px; color: #333; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; } .close-btn { position: absolute; top: 12px; right: 16px; background: none; border: none; font-size: 24px; cursor: pointer; color: #666; padding: 0; width: 32px; height: 32px; display: flex; align-items: center; justify-content: center; } .close-btn:hover { color: #333; } .form-group { margin-bottom: 16px; } .form-label { display: block; margin-bottom: 6px; font-weight: 500; color: #333; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; } .form-input { width: 100%; padding: 10px; border: 2px solid #ddd; border-radius: 4px; font-size: 16px; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; } .form-input:focus { outline: none; border-color: #007bff; } .btn-group { display: flex; gap: 12px; margin-top: 24px; } .btn { flex: 1; padding: 12px; border: none; border-radius: 4px; font-size: 16px; font-weight: 500; cursor: pointer; transition: all 0.2s ease; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; } .btn-primary { background: #007bff; color: white; } .btn-primary:hover:not(:disabled) { background: #0056b3; } .btn-secondary { background: #6c757d; color: white; } .btn-secondary:hover:not(:disabled) { background: #545b62; } .btn:disabled { opacity: 0.6; cursor: not-allowed; } .error-message { background: #f8d7da; color: #721c24; padding: 12px; border-radius: 4px; margin-bottom: 16px; border: 1px solid #f5c6cb; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; } .success-message { background: #d4edda; color: #155724; padding: 12px; border-radius: 4px; margin-bottom: 16px; border: 1px solid #c3e6cb; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; } @media (max-width: 768px) { .inventory-dot { width: 24px; height: 24px; } .modal-content { margin: 20px auto; padding: 20px; } .inventory-map-container { border-radius: 0; box-shadow: none; } } .inventory-map-container { user-select: none; } .inventory-map-container img { user-select: none; } </style> </head> <body> <!-- Upload your background image to Squarespace and replace the src URL below --> <div class="inventory-map-container"> <img class="map-image" src="interactive_map" alt="Simulator Layout"> <div class="dot-overlay" id="dotOverlay"></div> </div> <div class="edit-modal" id="editModal"> <div class="modal-content"> <button class="close-btn" onclick="closeEditModal()">×</button> <div class="modal-header"> <h2 class="modal-title" id="modalTitle">Edit Inventory</h2> </div> <div id="modalBody"> <div class="form-group"> <label class="form-label">Current Stock:</label> <input type="number" class="form-input" id="stockInput" min="0" placeholder="Enter stock quantity"> </div> <div class="btn-group"> <button class="btn btn-secondary" onclick="closeEditModal()">Cancel</button> <button class="btn btn-primary" id="saveBtn" onclick="saveInventory()">Save</button> </div> </div> </div> </div> <script> // Replace this URL with your Google Apps Script Web App URL var API_URL = 'https://script.google.com/macros/s/AKfycbyvMU6UcVSWG0s2PNpvyDLlNHy1VpQxb5iByLZEB4hxneNuzD5c_LQSXm_3zMkhqee-/exec'; // Dot positions - customize these coordinates for your layout var DOT_POSITIONS = { 'Dot 1': { top: '8.2%', left: '51%', name: 'Front & Rear Emblem Lights' }, 'Dot 2': { top: '8.3%', left: '30.7%', name: 'Front Vent Lights' }, 'Dot 3': { top: '13.4%', left: '13.3%', name: 'Tactile Speakers' }, 'Dot 4': { top: '14%', left: '46.3%', name: '9070 XT EGPU' }, 'Dot 5': { top: '14.9%', left: '64%', name: 'Wall Outlet Power Strip' }, 'Dot 6': { top: '16.4%', left: '30.5%', name: 'Fanatec Power Supply' }, 'Dot 7': { top: '23.8%', left: '47%', name: 'Analog to Digital Audio Converter' }, 'Dot 8': { top: '23.9%', left: '12.8%', name: '6.5" Side Speakers' }, 'Dot 9': { top: '24.9%', left: '32.7%', name: 'Component 9' }, 'Dot 10': { top: '32.9%', left: '44.3%', name: 'Component 10' }, 'Dot 11': { top: '33.8%', left: '26.5%', name: 'Component 11' }, 'Dot 12': { top: '36.7%', left: '46.8%', name: 'Component 12' }, 'Dot 13': { top: '37.9%', left: '87.7%', name: 'Component 13' }, 'Dot 14': { top: '40.1%', left: '14.2%', name: 'Component 14' }, 'Dot 15': { top: '41.6%', left: '26.8%', name: 'Component 15' }, 'Dot 16': { top: '45.3%', left: '49%', name: 'Component 16' }, 'Dot 17': { top: '48.7%', left: '63.7%', name: 'Component 17' }, 'Dot 18': { top: '48.7%', left: '76.7%', name: 'Component 18' }, 'Dot 19': { top: '50.9%', left: '27.2%', name: 'Component 19' }, 'Dot 20': { top: '57.3%', left: '74.5%', name: 'Component 20' }, 'Dot 21': { top: '62.1%', left: '47.7%', name: 'Component 21' }, 'Dot 22': { top: '81.6%', left: '64.7%', name: 'Component 22' }, 'Dot 23': { top: '81.7%', left: '34.3%', name: 'Component 23' }, 'Dot 24': { top: '81.8%', left: '52.2%', name: 'Component 24' }, 'Dot 25': { top: '86.3%', left: '51.7%', name: 'Component 25' }, 'Dot 26': { top: '86.6%', left: '61.2%', name: 'Component 26' }, 'Dot 27': { top: '87.6%', left: '69.2%', name: 'Component 27' }, 'Dot 28': { top: '94.4%', left: '33%', name: 'Component 28' } }; var inventoryData = {}; var currentEditingDot = null; var longPressTimer = null; var isLongPress = false; function createDots() { var overlay = document.getElementById('dotOverlay'); for (var dotId in DOT_POSITIONS) { var dotData = DOT_POSITIONS[dotId]; var dot = document.createElement('div'); dot.className = 'inventory-dot'; dot.id = dotId; dot.style.top = dotData.top; dot.style.left = dotData.left; dot.title = dotData.name; setupDotEvents(dot, dotId); overlay.appendChild(dot); } } function setupDotEvents(dot, dotId) { // Click/tap to open modal dot.addEventListener('click', function(e) { e.preventDefault(); openEditModal(dotId); }); // Disable right-click context menu dot.addEventListener('contextmenu', function(e) { e.preventDefault(); }); // Touch events for mobile dot.addEventListener('touchstart', function(e) { e.preventDefault(); }); dot.addEventListener('touchend', function(e) { e.preventDefault(); if (!isLongPress) { openEditModal(dotId); } }); } function loadInventoryData() { fetch(API_URL, { method: 'GET', headers: { 'Accept': 'application/json', }, mode: 'cors' }).then(function(response) { if (!response.ok) { throw new Error('Network response was not ok'); } return response.json(); }).then(function(data) { inventoryData = data; }).catch(function(error) { console.error('Error loading inventory data:', error); }); } function openEditModal(dotId) { currentEditingDot = dotId; var dotData = DOT_POSITIONS[dotId]; var currentStock = inventoryData[dotId] || 0; document.getElementById('modalTitle').textContent = 'Edit ' + dotData.name; document.getElementById('stockInput').value = currentStock; document.getElementById('editModal').style.display = 'block'; setTimeout(function() { document.getElementById('stockInput').focus(); document.getElementById('stockInput').select(); }, 100); } function closeEditModal() { document.getElementById('editModal').style.display = 'none'; currentEditingDot = null; var existingMessages = document.querySelectorAll('.error-message, .success-message'); for (var i = 0; i < existingMessages.length; i++) { existingMessages[i].remove(); } } function saveInventory() { if (!currentEditingDot) return; var newStock = parseInt(document.getElementById('stockInput').value); if (isNaN(newStock) || newStock < 0) { showModalMessage('Please enter a valid number (0 or greater)', 'error'); return; } var saveBtn = document.getElementById('saveBtn'); var originalText = saveBtn.textContent; saveBtn.disabled = true; saveBtn.textContent = 'Saving...'; var url = API_URL + '?dotId=' + encodeURIComponent(currentEditingDot) + '&stock=' + newStock; fetch(url, { method: 'POST', headers: { 'Accept': 'application/json', }, mode: 'cors' }).then(function(response) { if (!response.ok) { throw new Error('Network response was not ok'); } return response.json(); }).then(function(result) { if (result.success) { inventoryData[currentEditingDot] = newStock; showModalMessage('Inventory updated successfully!', 'success'); setTimeout(function() { closeEditModal(); }, 1500); } else { throw new Error(result.error || 'Failed to save inventory'); } }).catch(function(error) { console.error('Error saving inventory:', error); showModalMessage('Error saving inventory: ' + error.message, 'error'); }).finally(function() { saveBtn.disabled = false; saveBtn.textContent = originalText; }); } function showModalMessage(message, type) { var existingMessages = document.querySelectorAll('.error-message, .success-message'); for (var i = 0; i < existingMessages.length; i++) { existingMessages[i].remove(); } var messageDiv = document.createElement('div'); messageDiv.className = type + '-message'; messageDiv.textContent = message; var modalBody = document.getElementById('modalBody'); modalBody.insertBefore(messageDiv, modalBody.firstChild); } function setupEventListeners() { var container = document.querySelector('.inventory-map-container'); container.addEventListener('contextmenu', function(e) { e.preventDefault(); }); var modal = document.getElementById('editModal'); modal.addEventListener('click', function(e) { if (e.target === this) { closeEditModal(); } }); document.addEventListener('keydown', function(e) { if (e.key === 'Escape') { closeEditModal(); } if (e.key === 'Enter' && modal.style.display === 'block') { saveInventory(); } }); } // Initialize the application when DOM is ready document.addEventListener('DOMContentLoaded', function() { createDots(); loadInventoryData(); setupEventListeners(); }); </script> </body> </html>