added settings menu that saves and reads to/from the browser localStorage
minor design changes
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,2 +1,2 @@
|
|||||||
node_modules/
|
node_modules/
|
||||||
positions.db
|
positions.db
|
||||||
@@ -4,9 +4,9 @@
|
|||||||
"fruits": [
|
"fruits": [
|
||||||
"Brandon Brunson",
|
"Brandon Brunson",
|
||||||
"Eric Smithson",
|
"Eric Smithson",
|
||||||
"John Hammer",
|
|
||||||
"Seth Lima",
|
"Seth Lima",
|
||||||
"Rick Sanchez"
|
"Rick Sanchez",
|
||||||
|
"John Hammer"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
BIN
positions.db
BIN
positions.db
Binary file not shown.
@@ -9,13 +9,29 @@
|
|||||||
<body>
|
<body>
|
||||||
<header class="header">
|
<header class="header">
|
||||||
<div class="logo">
|
<div class="logo">
|
||||||
<img src="logo.png" alt="Logo" /> <!-- put it in public -->
|
<img src="logo.png" alt="Logo" />
|
||||||
<span>Cuberoo</span>
|
<span>Cuberoo</span>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- Sidebar toggle for mobile -->
|
||||||
|
<button id="sidebarToggle" style="display:none;margin-left:16px;font-size:1.6em;background:none;border:none;color:#d103f9;z-index:1100;">☰</button>
|
||||||
</header>
|
</header>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<!-- wrapper for the draggable map class -->
|
<!-- wrapper for the draggable map class -->
|
||||||
<div id="mapWrapper" class="map-wrapper">
|
<div id="mapWrapper" class="map-wrapper">
|
||||||
|
<!-- Settings button in top right -->
|
||||||
|
<button id="settingsBtn" class="settings-btn" title="Settings">
|
||||||
|
<span></span>
|
||||||
|
</button>
|
||||||
|
<!-- Settings menu (hidden by default) -->
|
||||||
|
<div id="settingsMenu" class="settings-menu" style="display:none;">
|
||||||
|
<div class="settings-menu-content">
|
||||||
|
<!-- Square Style toggle -->
|
||||||
|
<div style="margin-bottom:18px;">
|
||||||
|
<label for="squareStyleToggle" style="color:white;vertical-align:middle;">Highlight by</label>
|
||||||
|
<button id="squareStyleToggle" type="button" style="margin-left:10px;vertical-align:middle;"></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<!-- class for the squares -->
|
<!-- class for the squares -->
|
||||||
<div id="map" class="map"></div>
|
<div id="map" class="map"></div>
|
||||||
<!-- zoom controls -->
|
<!-- zoom controls -->
|
||||||
@@ -84,6 +100,81 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
loadCategories();
|
loadCategories();
|
||||||
|
|
||||||
|
// Settings button/menu logic
|
||||||
|
const settingsBtn = document.getElementById('settingsBtn');
|
||||||
|
const settingsMenu = document.getElementById('settingsMenu');
|
||||||
|
|
||||||
|
settingsBtn.addEventListener('click', () => {
|
||||||
|
settingsMenu.style.display = 'block';
|
||||||
|
});
|
||||||
|
// Optional: click outside menu closes it
|
||||||
|
document.addEventListener('mousedown', (e) => {
|
||||||
|
if (settingsMenu.style.display === 'block' &&
|
||||||
|
!settingsMenu.contains(e.target) &&
|
||||||
|
e.target !== settingsBtn) {
|
||||||
|
settingsMenu.style.display = 'none';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Filled/Outline setting logic (toggle)
|
||||||
|
function applySquareStyle(style) {
|
||||||
|
document.body.setAttribute('data-square-style', style);
|
||||||
|
const btn = document.getElementById('squareStyleToggle');
|
||||||
|
if (btn) {
|
||||||
|
btn.textContent = style === 'filled' ? 'filling' : 'outlining';
|
||||||
|
btn.className = style === 'filled' ? 'toggle-filled' : 'toggle-outline';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function saveSquareStyle(style) {
|
||||||
|
localStorage.setItem('squareStyle', style);
|
||||||
|
}
|
||||||
|
function loadSquareStyle() {
|
||||||
|
return localStorage.getItem('squareStyle') || 'filled';
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupSquareStyleSetting() {
|
||||||
|
const toggleBtn = document.getElementById('squareStyleToggle');
|
||||||
|
if (!toggleBtn) return;
|
||||||
|
let style = loadSquareStyle();
|
||||||
|
applySquareStyle(style);
|
||||||
|
toggleBtn.addEventListener('click', () => {
|
||||||
|
style = (style === 'filled') ? 'outline' : 'filled';
|
||||||
|
applySquareStyle(style);
|
||||||
|
saveSquareStyle(style);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', setupSquareStyleSetting);
|
||||||
|
if (document.getElementById('squareStyleToggle')) setupSquareStyleSetting();
|
||||||
|
|
||||||
|
// Sidebar toggle for mobile
|
||||||
|
function setupSidebarToggle() {
|
||||||
|
const sidebar = document.getElementById('sidebar');
|
||||||
|
const toggleBtn = document.getElementById('sidebarToggle');
|
||||||
|
function updateSidebarDisplay() {
|
||||||
|
if (window.innerWidth <= 900) {
|
||||||
|
toggleBtn.style.display = '';
|
||||||
|
sidebar.classList.remove('open');
|
||||||
|
} else {
|
||||||
|
toggleBtn.style.display = 'none';
|
||||||
|
sidebar.classList.remove('open');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
toggleBtn.addEventListener('click', () => {
|
||||||
|
sidebar.classList.toggle('open');
|
||||||
|
});
|
||||||
|
// Close sidebar when clicking outside on mobile
|
||||||
|
document.addEventListener('mousedown', (e) => {
|
||||||
|
if (window.innerWidth > 900) return;
|
||||||
|
if (sidebar.classList.contains('open') && !sidebar.contains(e.target) && e.target !== toggleBtn) {
|
||||||
|
sidebar.classList.remove('open');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
window.addEventListener('resize', updateSidebarDisplay);
|
||||||
|
updateSidebarDisplay();
|
||||||
|
}
|
||||||
|
document.addEventListener('DOMContentLoaded', setupSidebarToggle);
|
||||||
</script>
|
</script>
|
||||||
<script src="/socket.io/socket.io.js"></script>
|
<script src="/socket.io/socket.io.js"></script>
|
||||||
<script src="script.js"></script>
|
<script src="script.js"></script>
|
||||||
|
|||||||
162
public/script.js
162
public/script.js
@@ -114,6 +114,34 @@ function showCustomFruitInput(squareEl) {
|
|||||||
|
|
||||||
// Wait for user to click a category, then send to server
|
// Wait for user to click a category, then send to server
|
||||||
function waitForCategorySelection(customName, squareEl, squareNumber) {
|
function waitForCategorySelection(customName, squareEl, squareNumber) {
|
||||||
|
// --- Check if fruit name already exists ---
|
||||||
|
const fruitExists = Array.from(document.querySelectorAll('.fruit'))
|
||||||
|
.some(fruitEl => fruitEl.dataset.fruit && fruitEl.dataset.fruit.toLowerCase() === customName.toLowerCase());
|
||||||
|
if (fruitExists) {
|
||||||
|
// If fruit is already in a different square, move it
|
||||||
|
const existingSquare = document.querySelector(`.square[data-fruit="${customName}"]`);
|
||||||
|
if (existingSquare && existingSquare !== squareEl) {
|
||||||
|
existingSquare.innerHTML = '';
|
||||||
|
delete existingSquare.dataset.fruit;
|
||||||
|
const existingSquareId = existingSquare.dataset.squareId;
|
||||||
|
fetch('/api/positions', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ squareId: existingSquareId, fruit: '' }),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// Place the fruit in the new square
|
||||||
|
placeFruitInSquare(squareEl, customName);
|
||||||
|
// Save the new fruit's position to the database
|
||||||
|
const squareId = squareEl.dataset.squareId;
|
||||||
|
fetch('/api/positions', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ squareId, fruit: customName }),
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Highlight categories and show a message
|
// Highlight categories and show a message
|
||||||
document.querySelectorAll('.categories').forEach(cat => {
|
document.querySelectorAll('.categories').forEach(cat => {
|
||||||
cat.classList.add('category-selectable');
|
cat.classList.add('category-selectable');
|
||||||
@@ -243,6 +271,14 @@ function initializeFruitAndCategoryEvents() {
|
|||||||
document.querySelectorAll('.fruit').forEach(el => {
|
document.querySelectorAll('.fruit').forEach(el => {
|
||||||
el.addEventListener('dragstart', e => {
|
el.addEventListener('dragstart', e => {
|
||||||
e.dataTransfer.setData('text/plain', el.dataset.fruit);
|
e.dataTransfer.setData('text/plain', el.dataset.fruit);
|
||||||
|
// Also store the source category name for moving
|
||||||
|
const catDiv = el.closest('.categories');
|
||||||
|
if (catDiv) {
|
||||||
|
const catHeader = catDiv.querySelector('.category-header h4');
|
||||||
|
if (catHeader) {
|
||||||
|
e.dataTransfer.setData('source-category', catHeader.textContent);
|
||||||
|
}
|
||||||
|
}
|
||||||
const dragImg = createDragImage(el);
|
const dragImg = createDragImage(el);
|
||||||
e.dataTransfer.setDragImage(dragImg, dragImg.offsetWidth / 2, dragImg.offsetHeight / 2);
|
e.dataTransfer.setDragImage(dragImg, dragImg.offsetWidth / 2, dragImg.offsetHeight / 2);
|
||||||
// cleanup after drag
|
// cleanup after drag
|
||||||
@@ -298,6 +334,44 @@ function initializeFruitAndCategoryEvents() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// --- Enable dropping fruits into other categories ---
|
||||||
|
document.querySelectorAll('.category-content').forEach(content => {
|
||||||
|
content.addEventListener('dragover', e => {
|
||||||
|
// Only allow drop if dragging a fruit
|
||||||
|
if (e.dataTransfer.types.includes('text/plain')) {
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
content.addEventListener('drop', async e => {
|
||||||
|
e.preventDefault();
|
||||||
|
const fruit = e.dataTransfer.getData('text/plain');
|
||||||
|
const sourceCategory = e.dataTransfer.getData('source-category');
|
||||||
|
// Find the target category name
|
||||||
|
const catDiv = content.closest('.categories');
|
||||||
|
const catHeader = catDiv ? catDiv.querySelector('.category-header h4') : null;
|
||||||
|
const targetCategory = catHeader ? catHeader.textContent : null;
|
||||||
|
if (!fruit || !sourceCategory || !targetCategory || sourceCategory === targetCategory) return;
|
||||||
|
|
||||||
|
// Move fruit: remove from old category, add to new
|
||||||
|
// 1. Remove from old
|
||||||
|
await fetch('/api/delete-fruit', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ category: sourceCategory, fruit })
|
||||||
|
});
|
||||||
|
// 2. Add to new
|
||||||
|
await fetch('/api/add-fruit', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ category: targetCategory, fruit })
|
||||||
|
});
|
||||||
|
// 3. Reload categories and re-init events
|
||||||
|
if (window.loadCategories) await window.loadCategories();
|
||||||
|
initializeFruitAndCategoryEvents();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
// --- end drag-to-category ---
|
||||||
|
|
||||||
// make each square a drop target
|
// make each square a drop target
|
||||||
document.querySelectorAll('.square').forEach(sq => {
|
document.querySelectorAll('.square').forEach(sq => {
|
||||||
sq.addEventListener('dragover', e => e.preventDefault());
|
sq.addEventListener('dragover', e => e.preventDefault());
|
||||||
@@ -682,3 +756,91 @@ function showDeleteFruitPopup(fruitName, categoryName) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Touch support for map pan/zoom ---
|
||||||
|
(() => {
|
||||||
|
const mapWrapper = document.getElementById('mapWrapper');
|
||||||
|
const map = document.getElementById('map');
|
||||||
|
let isDragging = false;
|
||||||
|
let startX, startY, startPanX, startPanY;
|
||||||
|
let panX = 0, panY = 0;
|
||||||
|
let currentScale = 1;
|
||||||
|
const minScale = 0.5, maxScale = 3;
|
||||||
|
let lastTouchDist = null;
|
||||||
|
|
||||||
|
function updateTransform() {
|
||||||
|
map.style.transform = `translate(${panX}px, ${panY}px) scale(${currentScale})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
mapWrapper.addEventListener('touchstart', (e) => {
|
||||||
|
if (e.target.closest('.fruit') || e.target.closest('.dropped-fruit')) return;
|
||||||
|
if (e.touches.length === 1) {
|
||||||
|
isDragging = true;
|
||||||
|
startX = e.touches[0].clientX;
|
||||||
|
startY = e.touches[0].clientY;
|
||||||
|
startPanX = panX;
|
||||||
|
startPanY = panY;
|
||||||
|
map.style.cursor = 'grabbing';
|
||||||
|
} else if (e.touches.length === 2) {
|
||||||
|
isDragging = false;
|
||||||
|
lastTouchDist = Math.hypot(
|
||||||
|
e.touches[0].clientX - e.touches[1].clientX,
|
||||||
|
e.touches[0].clientY - e.touches[1].clientY
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
mapWrapper.addEventListener('touchmove', (e) => {
|
||||||
|
if (e.touches.length === 1 && isDragging) {
|
||||||
|
const deltaX = e.touches[0].clientX - startX;
|
||||||
|
const deltaY = e.touches[0].clientY - startY;
|
||||||
|
panX = startPanX + deltaX;
|
||||||
|
panY = startPanY + deltaY;
|
||||||
|
updateTransform();
|
||||||
|
} else if (e.touches.length === 2) {
|
||||||
|
// Pinch zoom
|
||||||
|
const dist = Math.hypot(
|
||||||
|
e.touches[0].clientX - e.touches[1].clientX,
|
||||||
|
e.touches[0].clientY - e.touches[1].clientY
|
||||||
|
);
|
||||||
|
if (lastTouchDist) {
|
||||||
|
let scaleChange = dist / lastTouchDist;
|
||||||
|
let newScale = currentScale * scaleChange;
|
||||||
|
newScale = Math.min(maxScale, Math.max(minScale, newScale));
|
||||||
|
currentScale = newScale;
|
||||||
|
updateTransform();
|
||||||
|
}
|
||||||
|
lastTouchDist = dist;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
mapWrapper.addEventListener('touchend', (e) => {
|
||||||
|
isDragging = false;
|
||||||
|
lastTouchDist = null;
|
||||||
|
map.style.cursor = 'grab';
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
|
||||||
|
// --- Touch support for fruit drag (basic fallback for mobile) ---
|
||||||
|
function enableTouchFruitDrag() {
|
||||||
|
document.querySelectorAll('.fruit').forEach(el => {
|
||||||
|
el.addEventListener('touchstart', function(e) {
|
||||||
|
if (e.touches.length !== 1) return;
|
||||||
|
const fruitName = el.dataset.fruit;
|
||||||
|
// Find first empty square
|
||||||
|
const emptySq = Array.from(document.querySelectorAll('.square')).find(sq => !sq.dataset.fruit && !sq.querySelector('.dropped-fruit'));
|
||||||
|
if (emptySq) {
|
||||||
|
placeFruitInSquare(emptySq, fruitName);
|
||||||
|
// Save to server
|
||||||
|
const squareId = emptySq.dataset.squareId;
|
||||||
|
fetch('/api/positions', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ squareId, fruit: fruitName }),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
e.preventDefault();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
document.addEventListener('DOMContentLoaded', enableTouchFruitDrag);
|
||||||
|
|||||||
193
public/style.css
193
public/style.css
@@ -80,6 +80,7 @@ body, html {
|
|||||||
|
|
||||||
.square.highlight {
|
.square.highlight {
|
||||||
border: 1px solid purple; /* highlight color when hovered over in sidebar */
|
border: 1px solid purple; /* highlight color when hovered over in sidebar */
|
||||||
|
background-color: purple;
|
||||||
}
|
}
|
||||||
|
|
||||||
.square.highlight-update {
|
.square.highlight-update {
|
||||||
@@ -128,7 +129,7 @@ body, html {
|
|||||||
cursor: grab;
|
cursor: grab;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
padding: 5px 10px;
|
padding: 5px 10px;
|
||||||
border: 1px solid white; /* cells inside sidebar border color */
|
border: 1px solid rgba(255, 255, 255, 0.5); /* cells inside sidebar border color */
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
color: white;
|
color: white;
|
||||||
@@ -271,5 +272,193 @@ body, html {
|
|||||||
#delete-fruit-popup {
|
#delete-fruit-popup {
|
||||||
/* see JS for inline styles, but you can add more here if needed */
|
/* see JS for inline styles, but you can add more here if needed */
|
||||||
box-shadow: 0 4px 32px #000b;
|
box-shadow: 0 4px 32px #000b;
|
||||||
z-index: 2000;
|
}
|
||||||
|
|
||||||
|
/* Settings button in top right of map */
|
||||||
|
.settings-btn {
|
||||||
|
position: absolute;
|
||||||
|
top: 16px;
|
||||||
|
right: 16px;
|
||||||
|
z-index: 101;
|
||||||
|
background: rgba(30,30,30,0.95);
|
||||||
|
border: 1px solid #d103f9;
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 44px;
|
||||||
|
height: 44px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: #d103f9;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
cursor: pointer;
|
||||||
|
box-shadow: 0 2px 8px #0007;
|
||||||
|
transition: background 0.2s, border-color 0.2s;
|
||||||
|
}
|
||||||
|
.settings-btn:hover {
|
||||||
|
background: #2a003a;
|
||||||
|
border-color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Settings menu styles */
|
||||||
|
.settings-menu {
|
||||||
|
position: absolute;
|
||||||
|
top: 70px;
|
||||||
|
right: 16px;
|
||||||
|
z-index: 102;
|
||||||
|
background: #181818;
|
||||||
|
border: 1px solid #d103f9;
|
||||||
|
border-radius: 10px;
|
||||||
|
box-shadow: 0 4px 32px #000b;
|
||||||
|
padding: 0;
|
||||||
|
color: white;
|
||||||
|
animation: fadeInSettings 0.2s;
|
||||||
|
}
|
||||||
|
@keyframes fadeInSettings {
|
||||||
|
from { opacity: 0; transform: translateY(-10px);}
|
||||||
|
to { opacity: 1; transform: translateY(0);}
|
||||||
|
}
|
||||||
|
.settings-menu-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 14px 18px 10px 18px;
|
||||||
|
border-bottom: 1px solid #333;
|
||||||
|
font-size: 1.1em;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #d103f9;
|
||||||
|
}
|
||||||
|
.close-settings-btn {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: #d103f9;
|
||||||
|
font-size: 1.2em;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0 4px;
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: background 0.2s;
|
||||||
|
}
|
||||||
|
.close-settings-btn:hover {
|
||||||
|
background: #2a003a;
|
||||||
|
}
|
||||||
|
.settings-menu-content {
|
||||||
|
padding: 20px;
|
||||||
|
padding-bottom: 0px;
|
||||||
|
color: white;
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Square Style toggle button */
|
||||||
|
#squareStyleToggle {
|
||||||
|
padding: 6px 18px;
|
||||||
|
border-radius: 6px;
|
||||||
|
border: 1.5px solid #d103f9;
|
||||||
|
background: #191919;
|
||||||
|
color: #d103f9;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 1em;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.15s, color 0.15s, border-color 0.15s;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
#squareStyleToggle.toggle-filled {
|
||||||
|
background: #d103f9;
|
||||||
|
color: white;
|
||||||
|
border-color: #d103f9;
|
||||||
|
}
|
||||||
|
#squareStyleToggle.toggle-outline {
|
||||||
|
background: #191919;
|
||||||
|
color: #d103f9;
|
||||||
|
border-color: #d103f9;
|
||||||
|
}
|
||||||
|
#squareStyleToggle:hover {
|
||||||
|
background: #2a003a;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Filled/Outline toggle logic */
|
||||||
|
body[data-square-style="filled"] .square.highlight {
|
||||||
|
border: 1px solid purple;
|
||||||
|
background-color: purple;
|
||||||
|
}
|
||||||
|
body[data-square-style="filled"] .square.highlight-update {
|
||||||
|
border: 1px solid purple;
|
||||||
|
box-shadow: 0 0 10px 2px purple;
|
||||||
|
}
|
||||||
|
body[data-square-style="outline"] .square.highlight {
|
||||||
|
border: 1px solid #d103f9;
|
||||||
|
background-color: transparent !important;
|
||||||
|
}
|
||||||
|
body[data-square-style="outline"] .square.highlight-update {
|
||||||
|
border: 1px solid #d103f9;
|
||||||
|
background-color: transparent !important;
|
||||||
|
box-shadow: 0 0 10px 2px #d103f9;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive adjustments for mobile */
|
||||||
|
@media (max-width: 900px) {
|
||||||
|
.container {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.sidebar {
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 80vw;
|
||||||
|
max-width: 350px;
|
||||||
|
height: 100vh;
|
||||||
|
z-index: 1001;
|
||||||
|
background: #181818;
|
||||||
|
transform: translateX(-100%);
|
||||||
|
transition: transform 0.3s;
|
||||||
|
box-shadow: 2px 0 16px #000a;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.sidebar.open {
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
.map-wrapper {
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
#zoomControls {
|
||||||
|
right: 10px;
|
||||||
|
bottom: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Make squares and map responsive */
|
||||||
|
@media (max-width: 900px) {
|
||||||
|
.map-row > .square,
|
||||||
|
.map-row > .empty {
|
||||||
|
width: 13vw;
|
||||||
|
height: 13vw;
|
||||||
|
min-width: 48px;
|
||||||
|
min-height: 48px;
|
||||||
|
max-width: 80px;
|
||||||
|
max-height: 80px;
|
||||||
|
}
|
||||||
|
.square {
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Touch-friendly buttons and inputs */
|
||||||
|
button, input, .fruit, .clear-btn {
|
||||||
|
touch-action: manipulation;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clear-btn {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Make popup and overlays scrollable on mobile */
|
||||||
|
#delete-fruit-popup, .settings-menu {
|
||||||
|
max-width: 95vw;
|
||||||
|
box-sizing: border-box;
|
||||||
|
word-break: break-word;
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user