<!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>