@ -691,57 +691,178 @@
z-index: 998;
}
/* Admin Panel Styles */
/* Admin Panel Styles - Mobile First */
.admin-setting-group {
padding: 15px 10px;
}
.admin-input-row {
display: flex;
align-items: center ;
margin-bottom: 1 2px;
gap: 10 px;
flex-direction: column ;
margin-bottom: 20 px;
gap: 8 px;
}
.admin-input-row label {
flex: 0 0 180px;
color: #ddd;
font-size: 13px;
font-weight: 500;
color: #FFA726;
font-size: 14px;
font-weight: 600;
letter-spacing: 0.3px;
margin-bottom: 4px;
}
.admin-input-row input[type="number"] {
width: 80px;
padding: 6px 8px;
.admin-input-wrapper {
display: flex;
align-items: center;
gap: 10px;
}
.admin-input-row input[type="number"],
.admin-input-row input[type="range"] {
flex: 1;
min-height: 44px;
padding: 10px 12px;
background: #2a2a2a;
color: white;
border: 1px solid #444;
border-radius: 4px;
font-size: 13px;
border: 2 px solid #444;
border-radius: 8 px;
font-size: 16 px;
text-align: center;
-webkit-appearance: none;
}
.admin-input-row input[type="number"]:focus {
.admin-input-row input[type="checkbox"] {
width: 24px;
height: 24px;
cursor: pointer;
}
.admin-input-row input[type="number"]:focus,
.admin-input-row input[type="range"]:focus {
outline: none;
border-color: #4CAF50;
background: #333;
box-shadow: 0 0 0 3px rgba(76, 175, 80, 0.2);
}
.admin-input-row .unit {
color: #999;
font-size: 12px;
font-style: italic;
min-width: 50px;
color: #aaa;
font-size: 14px;
white-space: nowrap;
min-width: 60px;
text-align: right;
}
.admin-range-value {
background: #333;
color: #FFA726;
padding: 8px 12px;
border-radius: 6px;
font-weight: bold;
min-width: 60px;
text-align: center;
border: 1px solid #555;
}
/* Desktop layout */
@media (min-width: 768px) {
.admin-input-row {
flex-direction: row;
align-items: center;
}
.admin-input-row label {
flex: 0 0 200px;
margin-bottom: 0;
}
.admin-input-row input[type="number"],
.admin-input-row input[type="range"] {
flex: 0 0 auto;
width: 120px;
}
}
.admin-button-group {
padding: 15px 10px;
padding: 20 px 10px;
display: flex;
gap: 10px;
flex-wrap: wrap;
flex-direction: column ;
gap: 12px ;
}
.admin-button-group button {
flex: 1;
min-width: 140px;
font-size: 13px;
padding: 10px;
min-height: 48px;
padding: 12px 20px;
background: #4CAF50;
color: white;
border: none;
border-radius: 8px;
cursor: pointer;
font-size: 15px;
font-weight: 600;
transition: all 0.2s;
box-shadow: 0 2px 4px rgba(0,0,0,0.2);
letter-spacing: 0.5px;
}
.admin-button-group button:active {
transform: translateY(1px);
box-shadow: 0 1px 2px rgba(0,0,0,0.2);
}
.admin-button-group button:hover {
background: #45a049;
box-shadow: 0 4px 8px rgba(0,0,0,0.3);
}
.admin-button-group button.danger {
background: #f44336;
}
.admin-button-group button.danger:hover {
background: #da190b;
}
@media (min-width: 768px) {
.admin-button-group {
flex-direction: row;
flex-wrap: wrap;
}
.admin-button-group button {
min-height: 40px;
flex: 1;
min-width: 140px;
}
}
/* Save indicator */
.admin-save-indicator {
position: fixed;
top: 70px;
right: 20px;
background: #4CAF50;
color: white;
padding: 10px 20px;
border-radius: 20px;
box-shadow: 0 2px 8px rgba(0,0,0,0.3);
opacity: 0;
transform: translateY(-10px);
transition: all 0.3s;
z-index: 10000;
pointer-events: none;
}
.admin-save-indicator.show {
opacity: 1;
transform: translateY(0);
}
/* Collapsible sections */
.section.collapsible .section-title {
cursor: pointer;
user-select: none;
position: relative;
padding-right: 30px;
}
.section.collapsible .section-title:after {
content: '▼';
position: absolute;
right: 10px;
top: 50%;
transform: translateY(-50%);
transition: transform 0.3s;
color: #FFA726;
}
.section.collapsed .section-title:after {
transform: translateY(-50%) rotate(-90deg);
}
.section-content {
max-height: 1000px;
overflow: hidden;
transition: max-height 0.3s ease;
}
.section.collapsed .section-content {
max-height: 0;
}
@keyframes slideUp {
from { transform: translateX(-50%) translateY(100%); }
to { transform: translateX(-50%) translateY(0); }
@ -907,14 +1028,19 @@
< / div >
< div class = "tab-content" id = "adminContent" >
< div class = "section" >
< div class = "section collapsible" data-section = "geocache " >
< div class = "section-title" > Geocache Settings< / div >
< div class = "admin-setting-group" >
< div class = "admin-input-row" >
< label > Interaction Range:< / label >
< input type = "number" id = "geocacheRange" min = "1" max = "50" value = "5" >
< span class = "unit" > meters< / span >
< / div >
< div class = "section-content" >
< div class = "admin-setting-group" >
< div class = "admin-input-row" >
< label > Interaction Range:< / label >
< div class = "admin-input-wrapper" >
< input type = "range" id = "geocacheRangeSlider" min = "1" max = "50" value = "5" >
< span class = "admin-range-value" id = "geocacheRangeValue" > 5< / span >
< span class = "unit" > meters< / span >
< / div >
< input type = "number" id = "geocacheRange" min = "1" max = "50" value = "5" style = "display:none;" >
< / div >
< div class = "admin-input-row" >
< label > Alert Distance:< / label >
< input type = "number" id = "geocacheAlertRange" min = "1" max = "50" value = "5" >
@ -1032,6 +1158,9 @@
< / div >
< / div >
<!-- Admin Save Indicator -->
< div id = "adminSaveIndicator" class = "admin-save-indicator" > Settings Saved ✓< / div >
<!-- Geocache dialog -->
< div id = "geocacheDialog" class = "geocache-dialog" style = "display: none;" >
< div class = "geocache-dialog-content" >
@ -2051,8 +2180,39 @@
}
}
// Debounce function for saving
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
// Show save indicator
function showSaveIndicator(message = 'Settings Saved ✓') {
const indicator = document.getElementById('adminSaveIndicator');
if (indicator) {
indicator.textContent = message;
indicator.classList.add('show');
setTimeout(() => {
indicator.classList.remove('show');
}, 2000);
}
}
// Debounced save function
const debouncedSave = debounce(() => {
saveAdminSettings();
showSaveIndicator();
}, 500);
function setupAdminInputListeners() {
// Add change listeners to all admin inputs
// Add change and input listeners to all admin inputs
const adminInputs = [
{ id: 'geocacheRange', setting: 'geocacheRange', type: 'number' },
{ id: 'geocacheAlertRange', setting: 'geocacheAlertRange', type: 'number' },
@ -2065,10 +2225,30 @@
{ id: 'proximityCheckInterval', setting: 'proximityCheckInterval', type: 'number' }
];
// Setup range sliders if they exist
const sliderPairs = [
{ slider: 'geocacheRangeSlider', input: 'geocacheRange', value: 'geocacheRangeValue' }
];
sliderPairs.forEach(pair => {
const slider = document.getElementById(pair.slider);
const input = document.getElementById(pair.input);
const valueDisplay = document.getElementById(pair.value);
if (slider & & input & & valueDisplay) {
slider.addEventListener('input', function() {
input.value = this.value;
valueDisplay.textContent = this.value;
input.dispatchEvent(new Event('input'));
});
}
});
adminInputs.forEach(input => {
const element = document.getElementById(input.id);
if (element) {
element.addEventListener('change', function() {
// Use both input and change events for better responsiveness
const updateHandler = function() {
let value;
if (input.type === 'checkbox') {
value = this.checked;
@ -2078,9 +2258,41 @@
value = this.value;
}
adminSettings[input.setting] = value;
saveAdminSettings();
updateStatus(`${input.setting} updated to ${value}`);
debouncedSave();
};
if (input.type === 'checkbox') {
element.addEventListener('change', updateHandler);
} else {
element.addEventListener('input', updateHandler);
element.addEventListener('change', updateHandler);
}
}
});
// Setup collapsible sections
document.querySelectorAll('.section.collapsible').forEach(section => {
const title = section.querySelector('.section-title');
if (title) {
title.addEventListener('click', () => {
section.classList.toggle('collapsed');
// Save collapsed state
const sectionName = section.dataset.section;
if (sectionName) {
const collapsedSections = JSON.parse(localStorage.getItem('collapsedSections') || '{}');
collapsedSections[sectionName] = section.classList.contains('collapsed');
localStorage.setItem('collapsedSections', JSON.stringify(collapsedSections));
}
});
// Restore collapsed state
const sectionName = section.dataset.section;
if (sectionName) {
const collapsedSections = JSON.parse(localStorage.getItem('collapsedSections') || '{}');
if (collapsedSections[sectionName]) {
section.classList.add('collapsed');
}
}
}
});
}