Add masonry layout calc to project grid
This commit is contained in:
parent
d5b17016f7
commit
54c6fef694
@ -48,6 +48,7 @@ let fireflyTimer = null;
|
|||||||
let fireflyActive = false;
|
let fireflyActive = false;
|
||||||
let titleClicks = [];
|
let titleClicks = [];
|
||||||
let easterEggCooling = false;
|
let easterEggCooling = false;
|
||||||
|
let pendingImport = null;
|
||||||
|
|
||||||
// --- Service Worker ---
|
// --- Service Worker ---
|
||||||
if ('serviceWorker' in navigator) {
|
if ('serviceWorker' in navigator) {
|
||||||
@ -262,7 +263,7 @@ async function handleImport(event) {
|
|||||||
const text = await file.text();
|
const text = await file.text();
|
||||||
const data = JSON.parse(text);
|
const data = JSON.parse(text);
|
||||||
if (!data.projects || !Array.isArray(data.projects)) throw new Error('Invalid file');
|
if (!data.projects || !Array.isArray(data.projects)) throw new Error('Invalid file');
|
||||||
projects = data.projects;
|
pendingImport = data.projects;
|
||||||
if (typeof data.isDarkMode === 'boolean') {
|
if (typeof data.isDarkMode === 'boolean') {
|
||||||
isDarkMode = data.isDarkMode;
|
isDarkMode = data.isDarkMode;
|
||||||
localStorage.setItem('crochetDarkMode', isDarkMode);
|
localStorage.setItem('crochetDarkMode', isDarkMode);
|
||||||
@ -271,11 +272,7 @@ async function handleImport(event) {
|
|||||||
animationsEnabled = data.animationsEnabled;
|
animationsEnabled = data.animationsEnabled;
|
||||||
localStorage.setItem('crochetAnimations', animationsEnabled);
|
localStorage.setItem('crochetAnimations', animationsEnabled);
|
||||||
}
|
}
|
||||||
localStorage.setItem('crochetCounters', JSON.stringify(projects));
|
showImportSelection();
|
||||||
applyTheme();
|
|
||||||
render();
|
|
||||||
closeSaveModal();
|
|
||||||
await showAlert({ title: 'Import complete', text: `${projects.length} project${projects.length === 1 ? '' : 's'} loaded.` });
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
showAlert({ title: 'Import failed', text: err.message });
|
showAlert({ title: 'Import failed', text: err.message });
|
||||||
}
|
}
|
||||||
@ -286,6 +283,47 @@ if (importInput) {
|
|||||||
importInput.addEventListener('change', handleImport);
|
importInput.addEventListener('change', handleImport);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function showImportSelection() {
|
||||||
|
if (!pendingImport || !Array.isArray(pendingImport) || !saveOverlay) return;
|
||||||
|
const importSelection = document.getElementById('importSelection');
|
||||||
|
const importList = document.getElementById('importList');
|
||||||
|
if (!importSelection || !importList) return;
|
||||||
|
importList.innerHTML = '';
|
||||||
|
pendingImport.forEach((p, idx) => {
|
||||||
|
const item = document.createElement('label');
|
||||||
|
item.className = 'save-item';
|
||||||
|
item.innerHTML = `
|
||||||
|
<input type="checkbox" checked data-idx="${idx}">
|
||||||
|
<span>${p.name || 'Project ' + (idx + 1)}</span>
|
||||||
|
`;
|
||||||
|
importList.appendChild(item);
|
||||||
|
});
|
||||||
|
importSelection.classList.remove('hidden');
|
||||||
|
saveOverlay.classList.add('active');
|
||||||
|
}
|
||||||
|
|
||||||
|
function cancelImportSelection() {
|
||||||
|
pendingImport = null;
|
||||||
|
const importSelection = document.getElementById('importSelection');
|
||||||
|
const importList = document.getElementById('importList');
|
||||||
|
if (importSelection) importSelection.classList.add('hidden');
|
||||||
|
if (importList) importList.innerHTML = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyImportSelection() {
|
||||||
|
const importList = document.getElementById('importList');
|
||||||
|
if (!importList || !pendingImport) { cancelImportSelection(); return; }
|
||||||
|
const inputs = importList.querySelectorAll('input[type="checkbox"]');
|
||||||
|
const selected = Array.from(inputs).filter(i => i.checked).map(i => Number(i.dataset.idx));
|
||||||
|
if (selected.length === 0) { cancelImportSelection(); return; }
|
||||||
|
const selectedProjects = pendingImport.filter((_, idx) => selected.includes(idx));
|
||||||
|
projects = projects.concat(selectedProjects);
|
||||||
|
localStorage.setItem('crochetCounters', JSON.stringify(projects));
|
||||||
|
render();
|
||||||
|
cancelImportSelection();
|
||||||
|
closeSaveModal();
|
||||||
|
showAlert({ title: 'Import complete', text: `${selectedProjects.length} project(s) added.` });
|
||||||
|
}
|
||||||
function openSaveModal() {
|
function openSaveModal() {
|
||||||
if (!saveOverlay || !saveList) return;
|
if (!saveOverlay || !saveList) return;
|
||||||
saveList.innerHTML = '';
|
saveList.innerHTML = '';
|
||||||
@ -407,6 +445,24 @@ function handleAmbientDrift() {
|
|||||||
}
|
}
|
||||||
handleAmbientDrift();
|
handleAmbientDrift();
|
||||||
|
|
||||||
|
function applyMasonry() {
|
||||||
|
const grid = document.querySelector('.projects-grid');
|
||||||
|
if (!grid) return;
|
||||||
|
const rowHeight = 12; // matches grid-auto-rows
|
||||||
|
const gap = 20; // matches grid gap
|
||||||
|
grid.querySelectorAll('.project-container').forEach(card => {
|
||||||
|
const height = card.getBoundingClientRect().height;
|
||||||
|
const span = Math.ceil((height + gap) / (rowHeight + gap));
|
||||||
|
card.style.gridRowEnd = `span ${span}`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let resizeTimer = null;
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
clearTimeout(resizeTimer);
|
||||||
|
resizeTimer = setTimeout(applyMasonry, 150);
|
||||||
|
});
|
||||||
|
|
||||||
const logoIcon = document.querySelector('.brand-icon');
|
const logoIcon = document.querySelector('.brand-icon');
|
||||||
if (logoIcon) {
|
if (logoIcon) {
|
||||||
logoIcon.addEventListener('click', () => {
|
logoIcon.addEventListener('click', () => {
|
||||||
@ -801,6 +857,7 @@ function render() {
|
|||||||
lastCountPulse = null;
|
lastCountPulse = null;
|
||||||
lastFinishedId = null;
|
lastFinishedId = null;
|
||||||
app.appendChild(grid);
|
app.appendChild(grid);
|
||||||
|
applyMasonry();
|
||||||
}
|
}
|
||||||
|
|
||||||
render();
|
render();
|
||||||
|
|||||||
@ -247,8 +247,9 @@ h1 {
|
|||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
|
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
|
||||||
gap: 20px;
|
gap: 20px;
|
||||||
align-items: start; /* Prevent shorter cards from stretching to tallest row mate */
|
align-items: start;
|
||||||
grid-auto-flow: row dense; /* Fill gaps when possible */
|
grid-auto-rows: 12px; /* Base row height for masonry calc */
|
||||||
|
grid-auto-flow: dense;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
@ -262,7 +263,7 @@ h1 {
|
|||||||
background: var(--card-bg);
|
background: var(--card-bg);
|
||||||
border-radius: 18px;
|
border-radius: 18px;
|
||||||
padding: 5px 15px 15px 15px;
|
padding: 5px 15px 15px 15px;
|
||||||
margin-bottom: 2rem;
|
margin: 0;
|
||||||
box-shadow: var(--shadow);
|
box-shadow: var(--shadow);
|
||||||
transition: background-color 0.3s;
|
transition: background-color 0.3s;
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user