179 lines
6.3 KiB
JavaScript
179 lines
6.3 KiB
JavaScript
document.addEventListener('DOMContentLoaded', () => {
|
|
// DOM Elements
|
|
const taskForm = document.getElementById('task-form');
|
|
const taskInput = document.getElementById('task-input');
|
|
const taskList = document.getElementById('task-list');
|
|
const garden = document.getElementById('garden');
|
|
const soundToggle = document.getElementById('sound-toggle');
|
|
const ambientSound = document.getElementById('ambient-sound');
|
|
const soundOnIcon = document.getElementById('sound-on-icon');
|
|
const soundOffIcon = document.getElementById('sound-off-icon');
|
|
|
|
// App State
|
|
let tasks = JSON.parse(localStorage.getItem('tasks')) || [];
|
|
let gardenElements = JSON.parse(localStorage.getItem('gardenElements')) || [];
|
|
const flowerColors = ['pink', 'yellow', 'purple', 'white'];
|
|
|
|
// --- Initialization ---
|
|
function init() {
|
|
tasks.forEach(renderTask);
|
|
// Filter out garden elements whose tasks are no longer completed or no longer exist
|
|
const completedTaskIds = new Set(tasks.filter(t => t.completed).map(t => t.id));
|
|
gardenElements = gardenElements.filter(el => completedTaskIds.has(el.taskId));
|
|
gardenElements.forEach(renderGardenElement);
|
|
saveState(); // Resync localStorage with the filtered elements
|
|
}
|
|
|
|
// --- State Management ---
|
|
function saveState() {
|
|
localStorage.setItem('tasks', JSON.stringify(tasks));
|
|
localStorage.setItem('gardenElements', JSON.stringify(gardenElements));
|
|
}
|
|
|
|
// --- Task Management ---
|
|
taskForm.addEventListener('submit', e => {
|
|
e.preventDefault();
|
|
const title = taskInput.value.trim();
|
|
if (title) {
|
|
const newTask = { id: Date.now(), title, completed: false };
|
|
tasks.push(newTask);
|
|
renderTask(newTask);
|
|
saveState();
|
|
taskInput.value = '';
|
|
}
|
|
});
|
|
|
|
function renderTask(task) {
|
|
const li = document.createElement('li');
|
|
li.dataset.id = task.id;
|
|
if (task.completed) {
|
|
li.classList.add('completed');
|
|
}
|
|
li.innerHTML = `
|
|
<input type="checkbox" ${task.completed ? 'checked' : ''}>
|
|
<span>${escapeHTML(task.title)}</span>
|
|
<button class="delete-btn">×</button>
|
|
`;
|
|
taskList.appendChild(li);
|
|
|
|
li.querySelector('input[type="checkbox"]').addEventListener('change', (e) => {
|
|
toggleTaskCompletion(task.id, e.target.checked);
|
|
});
|
|
li.querySelector('.delete-btn').addEventListener('click', () => {
|
|
deleteTask(task.id);
|
|
});
|
|
}
|
|
|
|
function toggleTaskCompletion(id, isCompleted) {
|
|
const taskIndex = tasks.findIndex(t => t.id === id);
|
|
if (taskIndex > -1) {
|
|
tasks[taskIndex].completed = isCompleted;
|
|
const taskElement = taskList.querySelector(`[data-id='${id}']`);
|
|
if (taskElement) {
|
|
taskElement.classList.toggle('completed', isCompleted);
|
|
}
|
|
|
|
if (isCompleted) {
|
|
addGardenElement(id);
|
|
} else {
|
|
removeGardenElement(id);
|
|
}
|
|
saveState();
|
|
}
|
|
}
|
|
|
|
function deleteTask(id) {
|
|
tasks = tasks.filter(t => t.id !== id);
|
|
const taskElement = taskList.querySelector(`[data-id='${id}']`);
|
|
if (taskElement) {
|
|
taskElement.remove();
|
|
}
|
|
removeGardenElement(id);
|
|
saveState();
|
|
}
|
|
|
|
// --- Garden Management ---
|
|
function addGardenElement(taskId) {
|
|
if (gardenElements.some(el => el.taskId === taskId)) return;
|
|
|
|
const newElement = {
|
|
id: Date.now(),
|
|
taskId: taskId,
|
|
x: Math.random() * 80 + 10, // 10% to 90% to avoid edges
|
|
color: flowerColors[Math.floor(Math.random() * flowerColors.length)]
|
|
};
|
|
gardenElements.push(newElement);
|
|
renderGardenElement(newElement);
|
|
saveState();
|
|
}
|
|
|
|
function renderGardenElement(element) {
|
|
const el = document.createElement('div');
|
|
el.dataset.id = element.id;
|
|
el.classList.add('garden-element', 'grow');
|
|
el.style.left = `${element.x}%`;
|
|
|
|
el.innerHTML = `
|
|
<div class="flower-daisy">
|
|
<div class="stem"></div>
|
|
<div class="leaf left"></div>
|
|
<div class="leaf right"></div>
|
|
<div class="flower-head">
|
|
<div class="flower-center"></div>
|
|
<div class="petal petal-${element.color}">
|
|
<div class="p1"></div>
|
|
<div class="p2"></div>
|
|
<div class="p3"></div>
|
|
<div class="p4"></div>
|
|
<div class="p5"></div>
|
|
<div class="p6"></div>
|
|
<div class="p7"></div>
|
|
<div class="p8"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
garden.appendChild(el);
|
|
}
|
|
|
|
function removeGardenElement(taskId) {
|
|
const elementToRemoveIdx = gardenElements.findIndex(el => el.taskId === taskId);
|
|
if (elementToRemoveIdx > -1) {
|
|
const elementToRemove = gardenElements[elementToRemoveIdx];
|
|
const el = garden.querySelector(`[data-id='${elementToRemove.id}']`);
|
|
if (el) {
|
|
el.classList.remove('grow');
|
|
el.classList.add('shrink');
|
|
setTimeout(() => {
|
|
el.remove();
|
|
}, 500);
|
|
}
|
|
gardenElements.splice(elementToRemoveIdx, 1);
|
|
saveState();
|
|
}
|
|
}
|
|
|
|
// --- Controls ---
|
|
soundToggle.addEventListener('click', () => {
|
|
if (ambientSound.paused) {
|
|
ambientSound.play().catch(e => console.error("Audio play failed: ", e));
|
|
soundOnIcon.style.display = 'block';
|
|
soundOffIcon.style.display = 'none';
|
|
} else {
|
|
ambientSound.pause();
|
|
soundOnIcon.style.display = 'none';
|
|
soundOffIcon.style.display = 'block';
|
|
}
|
|
});
|
|
|
|
// --- Utility ---
|
|
function escapeHTML(str) {
|
|
return str.replace(/[&<>"']/g, s => ({
|
|
'&': '&', '<': '<', '>': '>', '"': '"', "'" : '''
|
|
}[s]));
|
|
}
|
|
|
|
// --- Run App ---
|
|
init();
|
|
}); |