Compare commits
5 Commits
39d9ad9748
...
d765b79aa5
| Author | SHA1 | Date | |
|---|---|---|---|
| d765b79aa5 | |||
| a6938f390b | |||
| a58c4c290c | |||
| edf5175236 | |||
| e552026efa |
200
assets/app.js
200
assets/app.js
@ -2,6 +2,16 @@
|
||||
let projects = JSON.parse(localStorage.getItem('crochetCounters')) || [];
|
||||
// New Earthy/Woodland Palette extracted from image vibes
|
||||
const colors = [
|
||||
'#a17d63', // Soft oak
|
||||
'#7a8c6a', // Moss sage
|
||||
'#c7a272', // Warm amber
|
||||
'#b88b8a', // Rose clay
|
||||
'#7b9189', // Sage teal
|
||||
'#aa9a7a', // Wheat linen
|
||||
'#5f6d57', // Forest dusk
|
||||
'#b07d6f' // Terra blush
|
||||
];
|
||||
const oldColors = [
|
||||
'#b56b54', // Rust/Mushroom
|
||||
'#7a8b4f', // Olive Green
|
||||
'#cba052', // Mustard/Daisy center
|
||||
@ -20,6 +30,13 @@ const modalInput = document.getElementById('modalInput');
|
||||
const modalTitle = document.getElementById('modalTitle');
|
||||
const hapticTick = () => { if ('vibrate' in navigator) navigator.vibrate(12); };
|
||||
const installBtn = document.getElementById('installBtn');
|
||||
const importInput = document.getElementById('importFile');
|
||||
const motionBtn = document.getElementById('motionBtn');
|
||||
const colorOverlay = document.getElementById('colorOverlay');
|
||||
const colorGrid = document.getElementById('colorGrid');
|
||||
const saveOverlay = document.getElementById('saveOverlay');
|
||||
const saveList = document.getElementById('saveList');
|
||||
let pendingSaveSelection = [];
|
||||
let lastCountPulse = null;
|
||||
let lastFinishedId = null;
|
||||
let fireflyTimer = null;
|
||||
@ -138,27 +155,156 @@ if (isDarkMode === null) {
|
||||
isDarkMode = false;
|
||||
}
|
||||
}
|
||||
let animationsEnabled = JSON.parse(localStorage.getItem('crochetAnimations'));
|
||||
if (animationsEnabled === null) animationsEnabled = true;
|
||||
|
||||
function applyTheme() {
|
||||
if (isDarkMode) {
|
||||
document.body.classList.add('dark-mode');
|
||||
document.getElementById('themeBtn').innerHTML = '🌙';
|
||||
document.getElementById('themeBtn').innerHTML = '<i class="fa-solid fa-moon"></i>';
|
||||
} else {
|
||||
document.body.classList.remove('dark-mode');
|
||||
document.getElementById('themeBtn').innerHTML = '☀️';
|
||||
document.getElementById('themeBtn').innerHTML = '<i class="fa-solid fa-sun"></i>';
|
||||
}
|
||||
handleAmbientDrift();
|
||||
updateMotionBtn();
|
||||
}
|
||||
|
||||
function toggleTheme() {
|
||||
isDarkMode = !isDarkMode;
|
||||
localStorage.setItem('crochetDarkMode', isDarkMode);
|
||||
applyTheme();
|
||||
if (animationsEnabled) {
|
||||
document.body.classList.add('theme-animating');
|
||||
setTimeout(() => document.body.classList.remove('theme-animating'), 750);
|
||||
}
|
||||
}
|
||||
applyTheme();
|
||||
|
||||
function updateMotionBtn() {
|
||||
if (!motionBtn) return;
|
||||
motionBtn.innerHTML = animationsEnabled ? '<i class="fa-solid fa-wand-magic-sparkles"></i>' : '<i class="fa-solid fa-ban"></i>';
|
||||
motionBtn.title = animationsEnabled ? 'Toggle Animations' : 'Animations disabled';
|
||||
}
|
||||
|
||||
function toggleAnimations() {
|
||||
animationsEnabled = !animationsEnabled;
|
||||
localStorage.setItem('crochetAnimations', animationsEnabled);
|
||||
updateMotionBtn();
|
||||
if (!animationsEnabled) {
|
||||
stopAmbientDrift();
|
||||
document.body.classList.remove('theme-animating');
|
||||
} else {
|
||||
handleAmbientDrift();
|
||||
}
|
||||
}
|
||||
|
||||
function openColorPicker(pId, partId) {
|
||||
if (!colorOverlay || !colorGrid) return;
|
||||
const project = projects.find(p => p.id === pId);
|
||||
const part = project.parts.find(pt => pt.id === partId);
|
||||
colorGrid.innerHTML = colors.map(c => `
|
||||
<button class="color-swatch" style="background:${c}" onclick="setPartColor(${pId}, ${partId}, '${c}'); closeColorPicker();"></button>
|
||||
`).join('');
|
||||
colorOverlay.classList.add('active');
|
||||
colorOverlay.dataset.projectId = pId;
|
||||
colorOverlay.dataset.partId = partId;
|
||||
}
|
||||
|
||||
function closeColorPicker() {
|
||||
if (!colorOverlay) return;
|
||||
colorOverlay.classList.remove('active');
|
||||
colorGrid.innerHTML = '';
|
||||
colorOverlay.dataset.projectId = '';
|
||||
colorOverlay.dataset.partId = '';
|
||||
}
|
||||
|
||||
function exportData(selectedProjects = projects) {
|
||||
const payload = {
|
||||
projects: selectedProjects,
|
||||
isDarkMode,
|
||||
animationsEnabled
|
||||
};
|
||||
const names = selectedProjects.map(p => p.name || 'Project').join('_').replace(/\s+/g, '-').slice(0, 50) || 'projects';
|
||||
const filename = `toadstool_${names}.json`;
|
||||
const blob = new Blob([JSON.stringify(payload, null, 2)], { type: 'application/json' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = filename;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
function triggerImport() {
|
||||
if (!importInput) return;
|
||||
importInput.value = '';
|
||||
importInput.click();
|
||||
}
|
||||
|
||||
async function handleImport(event) {
|
||||
const file = event.target.files[0];
|
||||
if (!file) return;
|
||||
try {
|
||||
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;
|
||||
if (typeof data.isDarkMode === 'boolean') {
|
||||
isDarkMode = data.isDarkMode;
|
||||
localStorage.setItem('crochetDarkMode', isDarkMode);
|
||||
}
|
||||
if (typeof data.animationsEnabled === 'boolean') {
|
||||
animationsEnabled = data.animationsEnabled;
|
||||
localStorage.setItem('crochetAnimations', animationsEnabled);
|
||||
}
|
||||
localStorage.setItem('crochetCounters', JSON.stringify(projects));
|
||||
applyTheme();
|
||||
render();
|
||||
} catch (err) {
|
||||
showAlert({ title: 'Import failed', text: err.message });
|
||||
}
|
||||
event.target.value = '';
|
||||
}
|
||||
|
||||
if (importInput) {
|
||||
importInput.addEventListener('change', handleImport);
|
||||
}
|
||||
|
||||
function openSaveModal() {
|
||||
if (!saveOverlay || !saveList) return;
|
||||
saveList.innerHTML = '';
|
||||
pendingSaveSelection = projects.map(p => p.id);
|
||||
projects.forEach(p => {
|
||||
const item = document.createElement('label');
|
||||
item.className = 'save-item';
|
||||
item.innerHTML = `
|
||||
<input type="checkbox" checked data-id="${p.id}">
|
||||
<span>${p.name}</span>
|
||||
`;
|
||||
saveList.appendChild(item);
|
||||
});
|
||||
saveOverlay.classList.add('active');
|
||||
}
|
||||
|
||||
function closeSaveModal() {
|
||||
if (!saveOverlay) return;
|
||||
saveOverlay.classList.remove('active');
|
||||
saveList.innerHTML = '';
|
||||
}
|
||||
|
||||
function exportSelected() {
|
||||
if (!saveOverlay) return;
|
||||
const inputs = saveList.querySelectorAll('input[type="checkbox"]');
|
||||
const selectedIds = Array.from(inputs).filter(i => i.checked).map(i => Number(i.dataset.id));
|
||||
if (selectedIds.length === 0) { closeSaveModal(); return; }
|
||||
const selectedProjects = projects.filter(p => selectedIds.includes(p.id));
|
||||
exportData(selectedProjects);
|
||||
closeSaveModal();
|
||||
}
|
||||
|
||||
// --- Firefly Animation ---
|
||||
function spawnFirefly({ markActive = false, source = 'ambient' } = {}) {
|
||||
const wrap = document.createElement('div');
|
||||
@ -226,6 +372,7 @@ function stopAmbientDrift() {
|
||||
function scheduleAmbientDrift() {
|
||||
const delay = 26000 + Math.random() * 18000; // 26–44s
|
||||
fireflyTimer = setTimeout(() => {
|
||||
if (!animationsEnabled) { stopAmbientDrift(); return; }
|
||||
if (isDarkMode) {
|
||||
spawnFirefly();
|
||||
} else {
|
||||
@ -237,6 +384,7 @@ function scheduleAmbientDrift() {
|
||||
|
||||
function handleAmbientDrift() {
|
||||
stopAmbientDrift();
|
||||
if (!animationsEnabled) return;
|
||||
if (isDarkMode) {
|
||||
spawnFirefly();
|
||||
} else {
|
||||
@ -249,7 +397,7 @@ handleAmbientDrift();
|
||||
const logoIcon = document.querySelector('.brand-icon');
|
||||
if (logoIcon) {
|
||||
logoIcon.addEventListener('click', () => {
|
||||
if (fireflyActive) return;
|
||||
if (!animationsEnabled || fireflyActive) return;
|
||||
if (isDarkMode) {
|
||||
spawnFirefly({ markActive: true, source: 'logo' });
|
||||
} else {
|
||||
@ -257,6 +405,20 @@ if (logoIcon) {
|
||||
}
|
||||
});
|
||||
}
|
||||
if (colorOverlay) {
|
||||
colorOverlay.addEventListener('click', (e) => {
|
||||
if (e.target === colorOverlay) closeColorPicker();
|
||||
});
|
||||
}
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Escape' && colorOverlay && colorOverlay.classList.contains('active')) {
|
||||
closeColorPicker();
|
||||
}
|
||||
});
|
||||
const importBtn = document.getElementById('importBtn');
|
||||
if (importBtn) {
|
||||
importBtn.addEventListener('click', triggerImport);
|
||||
}
|
||||
|
||||
const titleEl = document.getElementById('appTitle');
|
||||
if (titleEl) {
|
||||
@ -276,6 +438,7 @@ if (titleEl) {
|
||||
}
|
||||
|
||||
function triggerBurst() {
|
||||
if (!animationsEnabled) return;
|
||||
const burstCount = 8;
|
||||
const spawner = isDarkMode ? spawnFirefly : spawnSeed;
|
||||
for (let i = 0; i < burstCount; i++) {
|
||||
@ -295,10 +458,15 @@ if (projects.length > 0) {
|
||||
projects.forEach((p, index) => {
|
||||
if (!p.parts) { p.parts = []; changed = true; }
|
||||
if (!p.color) { p.color = colors[index % colors.length]; changed = true; }
|
||||
const oldIdx = oldColors.indexOf(p.color);
|
||||
if (oldIdx !== -1) { p.color = colors[oldIdx % colors.length]; changed = true; }
|
||||
if (p.note === undefined) { p.note = ''; changed = true; }
|
||||
p.parts.forEach(pt => {
|
||||
if (pt.max === undefined) { pt.max = null; changed = true; }
|
||||
if (pt.note === undefined) { pt.note = ''; changed = true; }
|
||||
if (!pt.color) { pt.color = p.color; changed = true; }
|
||||
const oldPartIdx = oldColors.indexOf(pt.color);
|
||||
if (oldPartIdx !== -1) { pt.color = colors[oldPartIdx % colors.length]; changed = true; }
|
||||
});
|
||||
});
|
||||
if (changed) { localStorage.setItem('crochetCounters', JSON.stringify(projects)); }
|
||||
@ -375,6 +543,12 @@ document.addEventListener('visibilitychange', async () => {
|
||||
part.locked = !part.locked;
|
||||
save();
|
||||
}
|
||||
function setPartColor(pId, partId, color) {
|
||||
const project = projects.find(p => p.id === pId);
|
||||
const part = project.parts.find(pt => pt.id === partId);
|
||||
part.color = color;
|
||||
save();
|
||||
}
|
||||
function togglePartFinish(pId, partId) {
|
||||
const project = projects.find(p => p.id === pId);
|
||||
const part = project.parts.find(pt => pt.id === partId);
|
||||
@ -450,11 +624,11 @@ function closeModal() {
|
||||
if (modalState.type === 'addProject') {
|
||||
const nextColor = colors[projects.length % colors.length];
|
||||
projects.push({ id: Date.now(), name: val, color: nextColor, collapsed: false, parts: [] });
|
||||
projects[projects.length-1].parts.push({ id: Date.now() + 1, name: 'Part 1', count: 0, locked: false, finished: false, minimized: false, max: null });
|
||||
projects[projects.length-1].parts.push({ id: Date.now() + 1, name: 'Part 1', count: 0, locked: false, finished: false, minimized: false, max: null, color: nextColor });
|
||||
}
|
||||
else if (modalState.type === 'addPart') {
|
||||
const project = projects.find(p => p.id === modalState.pId);
|
||||
project.parts.push({ id: Date.now(), name: val, count: 0, locked: false, finished: false, minimized: false, max: null });
|
||||
project.parts.push({ id: Date.now(), name: val, count: 0, locked: false, finished: false, minimized: false, max: null, color: project.color });
|
||||
project.collapsed = false;
|
||||
}
|
||||
else if (modalState.type === 'renamePart') {
|
||||
@ -526,10 +700,11 @@ function render() {
|
||||
|
||||
let partsHtml = '';
|
||||
sortedParts.forEach(part => {
|
||||
const accent = part.color || project.color;
|
||||
const isLocked = part.locked ? 'is-locked' : '';
|
||||
const isFinished = part.finished ? 'is-finished' : '';
|
||||
const isMinimized = part.minimized ? 'is-minimized' : '';
|
||||
const lockIcon = part.locked ? '🔒' : '🔓';
|
||||
const lockIcon = part.locked ? '<i class="fa-solid fa-lock"></i>' : '<i class="fa-solid fa-lock-open"></i>';
|
||||
const lockBtnClass = part.locked ? 'btn-lock locked-active' : 'btn-lock';
|
||||
const controlsDimmed = (part.locked || part.finished) ? 'dimmed' : '';
|
||||
const hideControls = (part.finished || part.minimized) ? 'hidden-controls' : '';
|
||||
@ -544,21 +719,22 @@ function render() {
|
||||
const partCardFullClass = `${isLocked} ${isFinished} ${isMinimized} ${finishPulseClass}`;
|
||||
const lockDisabled = part.locked ? 'disabled' : '';
|
||||
const actionsHtml = part.minimized
|
||||
? `<div class="part-actions"><button class="icon-btn btn-toggle-part" onclick="togglePartMinimize(${project.id}, ${part.id})" title="Expand">▼</button></div>`
|
||||
? `<div class="part-actions"><button class="icon-btn btn-toggle-part" onclick="togglePartMinimize(${project.id}, ${part.id})" title="Expand"><i class="fa-solid fa-chevron-down"></i></button></div>`
|
||||
: `<div class="part-actions">
|
||||
<button class="icon-btn btn-reset-part" onclick="resetCount(${project.id}, ${part.id})" ${isFinished || part.locked ? 'disabled' : ''}>↺</button>
|
||||
<button class="icon-btn btn-delete-part" onclick="deletePart(${project.id}, ${part.id})" ${lockDisabled}>🗑️</button>
|
||||
<button class="icon-btn btn-toggle-part" onclick="togglePartMinimize(${project.id}, ${part.id})" title="Minimize">▼</button>
|
||||
<button class="btn-color" style="--project-color: ${accent}" onclick="openColorPicker(${project.id}, ${part.id})" title="Set color"></button>
|
||||
<button class="icon-btn btn-reset-part" onclick="resetCount(${project.id}, ${part.id})" ${isFinished || part.locked ? 'disabled' : ''}><i class="fa-solid fa-rotate-left"></i></button>
|
||||
<button class="icon-btn btn-delete-part" onclick="deletePart(${project.id}, ${part.id})" ${lockDisabled}><i class="fa-solid fa-trash"></i></button>
|
||||
<button class="icon-btn btn-toggle-part" onclick="togglePartMinimize(${project.id}, ${part.id})" title="Minimize"><i class="fa-solid fa-chevron-down"></i></button>
|
||||
</div>`;
|
||||
const countSubtext = part.minimized ? '' : `
|
||||
<div class="count-subtext">
|
||||
${part.max !== null ? `<strong>${part.count}</strong> / ${part.max}` : 'No max set'}
|
||||
<button class="icon-btn ${showSetMax}" onclick="openModal('setMax', ${project.id}, ${part.id})" title="Set max" ${lockDisabled}>⚙️</button>
|
||||
<button class="icon-btn ${showSetMax}" onclick="openModal('setMax', ${project.id}, ${part.id})" title="Set max" ${lockDisabled}><i class="fa-solid fa-gear"></i></button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
partsHtml += `
|
||||
<div class="part-card ${partCardFullClass}" id="${partCardId}">
|
||||
<div class="part-card ${partCardFullClass}" id="${partCardId}" style="--project-color: ${accent}">
|
||||
<div class="part-header">
|
||||
<div class="part-name-group">
|
||||
<label class="check-container">
|
||||
|
||||
123
assets/style.css
123
assets/style.css
@ -1,14 +1,14 @@
|
||||
:root {
|
||||
/* --- Woodland Theme (Light) --- */
|
||||
--bg: #e4e7d4; /* Sage background from image */
|
||||
--card-bg: #f8f5e6; /* Creamy yarn color */
|
||||
--header-bg: #866f5a; /* Wood hook color */
|
||||
--header-text: #f8f5e6; /* Cream text for header */
|
||||
--text: #4a3b2a; /* Deep sepia brown outlines */
|
||||
--text-muted: #7a6b5a;
|
||||
--border: #d1c7b7;
|
||||
--shadow: 0 4px 8px rgba(74, 59, 42, 0.1); /* Warmer shadow */
|
||||
--seed-opacity: 0.09;
|
||||
--bg: #e6e0d0; /* parchment */
|
||||
--card-bg: #f9f4e6; /* cream */
|
||||
--header-bg: #8a6b52; /* warm oak */
|
||||
--header-text: #fdf8f0; /* linen */
|
||||
--text: #433628; /* cocoa */
|
||||
--text-muted: #7a6d5c;
|
||||
--border: #d6cabc;
|
||||
--shadow: 0 4px 10px rgba(67, 54, 40, 0.12);
|
||||
--seed-opacity: 0.06;
|
||||
|
||||
|
||||
--modal-bg: #f8f5e6;
|
||||
@ -32,13 +32,13 @@
|
||||
/* --- Woodland Theme (Dark) --- */
|
||||
body.dark-mode {
|
||||
--bg: #2c3327; /* Deep forest background */
|
||||
--card-bg: #3d362d; /* Dark wood card */
|
||||
--header-bg: #4a3b2a; /* Darker wood header */
|
||||
--header-text: #e4e8d5;
|
||||
--text: #e4e8d5; /* Cream text */
|
||||
--text-muted: #a8a095;
|
||||
--border: #594e3f;
|
||||
--shadow: 0 4px 8px rgba(0,0,0,0.4);
|
||||
--card-bg: #3b342c; /* Dark wood card */
|
||||
--header-bg: #4b3829; /* Darker wood header */
|
||||
--header-text: #e9e4d7;
|
||||
--text: #e9e4d7; /* Cream text */
|
||||
--text-muted: #b5aa9b;
|
||||
--border: #5f5245;
|
||||
--shadow: 0 4px 10px rgba(0,0,0,0.45);
|
||||
|
||||
--modal-bg: #3d362d;
|
||||
--input-bg: #2c2720;
|
||||
@ -127,12 +127,88 @@ h1 {
|
||||
color: var(--header-text);
|
||||
transition: transform 0.18s ease, box-shadow 0.18s ease, background-color 0.2s;
|
||||
transform: translateY(0);
|
||||
font-family: inherit;
|
||||
}
|
||||
.header-btn:hover { transform: translateY(-1px) scale(1.03); box-shadow: 0 6px 14px rgba(0,0,0,0.12); }
|
||||
.header-btn:active { transform: translateY(0) scale(0.96); box-shadow: none; }
|
||||
|
||||
.header-btn.is-active { background: var(--header-text); color: var(--header-bg); }
|
||||
.hidden { display: none !important; }
|
||||
.hidden-input { display: none; }
|
||||
.color-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(44, 35, 25, 0.55);
|
||||
display: none;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 210;
|
||||
padding: 20px;
|
||||
backdrop-filter: blur(2px);
|
||||
}
|
||||
.color-overlay.active { display: flex; }
|
||||
.color-modal {
|
||||
background: var(--card-bg);
|
||||
color: var(--text);
|
||||
border-radius: 16px;
|
||||
padding: 18px 18px 14px;
|
||||
width: min(420px, 90vw);
|
||||
box-shadow: 0 10px 30px rgba(0,0,0,0.25);
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
.color-title { margin: 0 0 12px; font-family: 'Cormorant Garamond', Georgia, serif; }
|
||||
.color-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(60px, 1fr));
|
||||
gap: 10px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.color-swatch {
|
||||
height: 44px;
|
||||
border-radius: 12px;
|
||||
border: 2px solid var(--border);
|
||||
cursor: pointer;
|
||||
box-shadow: inset 0 0 0 2px rgba(255,255,255,0.55);
|
||||
transition: transform 0.15s ease, box-shadow 0.15s ease;
|
||||
}
|
||||
.color-swatch:hover { transform: translateY(-1px) scale(1.02); box-shadow: inset 0 0 0 2px rgba(255,255,255,0.8); }
|
||||
.color-swatch:active { transform: scale(0.98); }
|
||||
|
||||
.save-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(44, 35, 25, 0.55);
|
||||
display: none;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 210;
|
||||
padding: 20px;
|
||||
backdrop-filter: blur(2px);
|
||||
}
|
||||
.save-overlay.active { display: flex; }
|
||||
.save-modal {
|
||||
background: var(--card-bg);
|
||||
color: var(--text);
|
||||
border-radius: 16px;
|
||||
padding: 18px 18px 14px;
|
||||
width: min(420px, 90vw);
|
||||
box-shadow: 0 10px 30px rgba(0,0,0,0.25);
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
.save-subtext { margin: 0 0 8px; color: var(--text-muted); }
|
||||
.save-list { display: grid; gap: 8px; max-height: 220px; overflow-y: auto; margin-bottom: 10px; }
|
||||
.save-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 8px 10px;
|
||||
border-radius: 10px;
|
||||
background: var(--input-bg);
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
.save-item input { width: 18px; height: 18px; }
|
||||
.save-actions { display: flex; justify-content: flex-end; gap: 8px; }
|
||||
.icon-woodland { width: 22px; height: 22px; }
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
@ -210,6 +286,21 @@ h1 {
|
||||
.btn-rename-project:hover { color: var(--project-color); }
|
||||
.icon-pencil { display: inline-block; transform: scaleX(-1); }
|
||||
|
||||
.btn-color {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 50%;
|
||||
border: 2px solid var(--border);
|
||||
background: var(--project-color);
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: inset 0 0 0 2px var(--card-bg);
|
||||
}
|
||||
.btn-color:hover { box-shadow: inset 0 0 0 2px var(--project-color); }
|
||||
|
||||
/* --- PART CARD --- */
|
||||
.part-card {
|
||||
background: var(--card-bg);
|
||||
|
||||
31
index.html
31
index.html
@ -7,6 +7,7 @@
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:wght@500;700&family=Quicksand:wght@400;600;700&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/all.min.css" crossorigin="anonymous" referrerpolicy="no-referrer">
|
||||
<link rel="icon" href="assets/icons/favicon.ico">
|
||||
<link rel="icon" type="image/png" sizes="96x96" href="assets/icons/favicon-96x96.png">
|
||||
<link rel="icon" type="image/svg+xml" href="assets/icons/favicon.svg">
|
||||
@ -23,11 +24,14 @@
|
||||
<h1 id="appTitle">Toadstool Cottage Counter</h1>
|
||||
</div>
|
||||
<div class="header-controls">
|
||||
<button class="header-btn hidden" id="installBtn" title="Install app">⬇️</button>
|
||||
<button class="header-btn" id="themeBtn" onclick="toggleTheme()" title="Toggle Dark Mode">🌓</button>
|
||||
<button class="header-btn" id="focusBtn" onclick="toggleFocusMode()" title="Focus Mode (Keeps Screen On)">👁️</button>
|
||||
<button class="header-btn hidden" id="installBtn" title="Install app"><i class="fa-solid fa-download"></i></button>
|
||||
<button class="header-btn" id="motionBtn" onclick="toggleAnimations()" title="Toggle Animations"><i class="fa-solid fa-wand-magic-sparkles"></i></button>
|
||||
<button class="header-btn" id="themeBtn" onclick="toggleTheme()" title="Toggle Dark Mode"><i class="fa-solid fa-moon"></i></button>
|
||||
<button class="header-btn" id="focusBtn" onclick="toggleFocusMode()" title="Focus Mode (Keeps Screen On)"><i class="fa-solid fa-eye"></i></button>
|
||||
<button class="header-btn" id="saveLoadBtn" onclick="openSaveModal()" title="Save/Load"><i class="fa-solid fa-floppy-disk"></i></button>
|
||||
</div>
|
||||
</header>
|
||||
<input type="file" id="importFile" accept="application/json" class="hidden-input" />
|
||||
|
||||
<div class="container" id="app"></div>
|
||||
|
||||
@ -44,6 +48,27 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="color-overlay" id="colorOverlay">
|
||||
<div class="color-modal">
|
||||
<h3 class="color-title">Pick a color</h3>
|
||||
<div class="color-grid" id="colorGrid"></div>
|
||||
<button class="modal-btn btn-cancel" onclick="closeColorPicker()">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="save-overlay" id="saveOverlay">
|
||||
<div class="save-modal">
|
||||
<h3 class="color-title">Save or Load</h3>
|
||||
<p class="save-subtext">Choose projects to include:</p>
|
||||
<div class="save-list" id="saveList"></div>
|
||||
<div class="save-actions">
|
||||
<button class="modal-btn btn-cancel" onclick="closeSaveModal()">Cancel</button>
|
||||
<button class="modal-btn btn-save" onclick="exportSelected()">Save</button>
|
||||
<button class="modal-btn btn-save" onclick="triggerImport()">Load</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="assets/app.js"></script>
|
||||
<footer class="footer-bg" aria-hidden="true"></footer>
|
||||
</body>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user