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 titleClicks = [];
|
||||
let easterEggCooling = false;
|
||||
let pendingImport = null;
|
||||
|
||||
// --- Service Worker ---
|
||||
if ('serviceWorker' in navigator) {
|
||||
@ -262,7 +263,7 @@ async function handleImport(event) {
|
||||
const text = await file.text();
|
||||
const data = JSON.parse(text);
|
||||
if (!data.projects || !Array.isArray(data.projects)) throw new Error('Invalid file');
|
||||
projects = data.projects;
|
||||
pendingImport = data.projects;
|
||||
if (typeof data.isDarkMode === 'boolean') {
|
||||
isDarkMode = data.isDarkMode;
|
||||
localStorage.setItem('crochetDarkMode', isDarkMode);
|
||||
@ -271,11 +272,7 @@ async function handleImport(event) {
|
||||
animationsEnabled = data.animationsEnabled;
|
||||
localStorage.setItem('crochetAnimations', animationsEnabled);
|
||||
}
|
||||
localStorage.setItem('crochetCounters', JSON.stringify(projects));
|
||||
applyTheme();
|
||||
render();
|
||||
closeSaveModal();
|
||||
await showAlert({ title: 'Import complete', text: `${projects.length} project${projects.length === 1 ? '' : 's'} loaded.` });
|
||||
showImportSelection();
|
||||
} catch (err) {
|
||||
showAlert({ title: 'Import failed', text: err.message });
|
||||
}
|
||||
@ -286,6 +283,47 @@ if (importInput) {
|
||||
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() {
|
||||
if (!saveOverlay || !saveList) return;
|
||||
saveList.innerHTML = '';
|
||||
@ -407,6 +445,24 @@ function 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');
|
||||
if (logoIcon) {
|
||||
logoIcon.addEventListener('click', () => {
|
||||
@ -801,6 +857,7 @@ function render() {
|
||||
lastCountPulse = null;
|
||||
lastFinishedId = null;
|
||||
app.appendChild(grid);
|
||||
applyMasonry();
|
||||
}
|
||||
|
||||
render();
|
||||
|
||||
@ -247,8 +247,9 @@ h1 {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
|
||||
gap: 20px;
|
||||
align-items: start; /* Prevent shorter cards from stretching to tallest row mate */
|
||||
grid-auto-flow: row dense; /* Fill gaps when possible */
|
||||
align-items: start;
|
||||
grid-auto-rows: 12px; /* Base row height for masonry calc */
|
||||
grid-auto-flow: dense;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
@ -262,7 +263,7 @@ h1 {
|
||||
background: var(--card-bg);
|
||||
border-radius: 18px;
|
||||
padding: 5px 15px 15px 15px;
|
||||
margin-bottom: 2rem;
|
||||
margin: 0;
|
||||
box-shadow: var(--shadow);
|
||||
transition: background-color 0.3s;
|
||||
border: 1px solid var(--border);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user