<!-- Main Application Container -->
<div id="app-container" class="container mx-auto p-4 md:p-6 max-w-7xl hidden">
<!-- Header -->
<header class="flex flex-col md:flex-row justify-between items-center mb-6 border-b border-gray-700 pb-4">
<div>
<h1 class="font-display text-3xl text-white">MISSION_CONTROL</h1>
<p id="current-date-display" class="text-sm text-gray-400">CLASSIFIED_OPERATION</p>
</div>
<div class="text-center mt-4 md:mt-0">
<h2 id="session-title" class="font-display text-lg text-blue-300">SESSION ENDS IN:</h2>
<div id="session-countdown" class="font-display text-4xl text-white tracking-widest">00:00:00</div>
<button id="start-new-session-btn" class="hidden w-full bg-green-600 hover:bg-green-500 text-white font-bold py-2 px-4 rounded-lg transition mt-2">START_NEW_SESSION</button>
</div>
<div class="text-right mt-4 md:mt-0">
<h2 class="font-display text-lg text-blue-300">OPERATIVE_POINTS</h2>
<div id="points-display" class="font-display text-4xl text-white">0</div>
</div>
</header>
<main class="grid grid-cols-1 lg:grid-cols-3 gap-6">
<!-- Left Column: Tasks and Actions -->
<div class="lg:col-span-2">
<!-- Day Navigation -->
<div class="flex justify-between items-center mb-4">
<button id="prev-day-btn" class="bg-gray-700 hover:bg-gray-600 text-white font-bold py-2 px-4 rounded-lg transition">< PREV_DAY</button>
<h2 id="mission-day-title" class="font-display text-2xl text-white"></h2>
<button id="next-day-btn" class="bg-gray-700 hover:bg-gray-600 text-white font-bold py-2 px-4 rounded-lg transition">NEXT_DAY ></button>
</div>
<!-- Today's Mission -->
<div id="today-mission-board" class="bg-gray-800 p-4 rounded-lg border border-gray-700">
<h3 class="font-display text-xl text-blue-300 mb-4 border-b border-gray-600 pb-2">TODAY'S_MISSION_TARGETS</h3>
<div id="today-task-list" class="space-y-4">
<!-- Tasks for the day will be injected here -->
</div>
<p id="no-tasks-today" class="text-center text-gray-400 py-8">NO TARGETS ASSIGNED FOR TODAY. PREPARE FOR NEXT OPERATION.</p>
</div>
<!-- Mission Actions -->
<div class="bg-gray-800 p-4 rounded-lg border border-gray-700 mt-6">
<h3 class="font-display text-xl text-blue-300 mb-4">MISSION_ACTIONS</h3>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<button id="amend-mission-btn" class="w-full bg-gray-600 hover:bg-gray-500 text-white font-bold py-2 px-4 rounded-lg transition">AMEND_PLAN</button>
<button id="generate-communique-btn" class="w-full bg-blue-600 hover:bg-blue-500 text-white font-bold py-2 px-4 rounded-lg transition">GENERATE_REPORT</button>
</div>
</div>
</div>
<!-- Right Column: Thesis Progress and Task Bank -->
<div class="lg:col-span-1 space-y-6">
<!-- Overall Thesis Progress -->
<div class="bg-gray-800 p-4 rounded-lg border border-gray-700">
<h3 class="font-display text-xl text-blue-300 mb-4">THESIS_PROGRESS_OVERVIEW</h3>
<div id="thesis-progress-container" class="space-y-3">
<!-- Thesis subsections will be dynamically added here -->
<div class="text-center text-gray-500 p-4">No subsections defined. Add tasks to a subsection to track progress.</div>
</div>
</div>
<!-- Task Bank -->
<div class="bg-gray-800 p-4 rounded-lg border border-gray-700">
<h3 class="font-display text-xl text-blue-300 mb-4">INTEL_PACKET_BANK</h3>
<div id="task-bank-list" class="space-y-2 max-h-96 overflow-y-auto">
<!-- Content dynamically generated -->
</div>
<p id="no-tasks-in-bank" class="text-center text-gray-400 py-8 hidden">NO INTEL PACKETS. CREATE A NEW TASK.</p>
<button id="add-new-task-btn" class="w-full mt-4 bg-green-600 hover:bg-green-500 text-white font-bold py-2 px-4 rounded-lg transition">+ NEW_INTEL_PACKET</button>
</div>
</div>
</main>
</div>
<!-- Modals -->
<!-- Daily Briefing Modal -->
<div id="daily-briefing-modal" class="fixed inset-0 z-50 flex items-center justify-center p-4 modal-backdrop">
<div class="bg-gray-800 border border-blue-400 p-6 rounded-lg shadow-2xl max-w-lg w-full">
<h2 id="briefing-title" class="font-display text-2xl text-white mb-4">DAILY_MISSION_BRIEFING</h2>
<p id="briefing-subtitle" class="text-gray-400 mb-6">Welcome, Operative. Define your operational parameters for today.</p>
<div id="finish-time-wrapper" class="mb-4">
<label for="finish-time" class="block text-blue-300 mb-2">SESSION_EXTRACTION_TIME:</label>
<input type="time" id="finish-time" class="w-full bg-gray-700 text-white p-2 rounded border border-gray-600 focus:outline-none focus:ring-2 focus:ring-blue-500" required>
</div>
<div class="mb-6">
<h3 class="text-blue-300 mb-2">SELECT_TODAY'S_TARGETS:</h3>
<div id="briefing-task-selection" class="max-h-60 overflow-y-auto space-y-2 p-2 bg-gray-900 rounded border border-gray-700">
<!-- Available tasks for selection -->
</div>
</div>
<button id="update-mission-btn" class="w-full bg-blue-600 hover:bg-blue-500 text-white font-bold py-3 px-4 rounded-lg transition">ENGAGE</button>
<button id="cancel-briefing-btn" class="w-full bg-gray-600 hover:bg-gray-500 text-white font-bold py-2 px-4 rounded-lg transition mt-2 hidden">CANCEL</button>
</div>
</div>
<!-- Add/Edit Task Modal -->
<div id="task-modal" class="fixed inset-0 z-50 flex items-center justify-center p-4 modal-backdrop hidden">
<div class="bg-gray-800 border border-blue-400 p-6 rounded-lg shadow-2xl max-w-lg w-full">
<form id="task-form">
<h2 id="task-modal-title" class="font-display text-2xl text-white mb-6">NEW_INTEL_PACKET</h2>
<input type="hidden" id="task-id">
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="md:col-span-2">
<label for="task-name" class="block text-blue-300 mb-1">Packet Name:</label>
<input type="text" id="task-name" class="w-full bg-gray-700 text-white p-2 rounded border border-gray-600" required>
</div>
<div>
<label for="task-subsection" class="block text-blue-300 mb-1">Thesis Subsection:</label>
<input type="text" id="task-subsection" placeholder="e.g., Introduction" class="w-full bg-gray-700 text-white p-2 rounded border border-gray-600">
</div>
<div>
<label for="task-total" class="block text-blue-300 mb-1">Total Units (Optional):</label>
<input type="number" id="task-total" class="w-full bg-gray-700 text-white p-2 rounded border border-gray-600" min="1" placeholder="e.g., 2000">
</div>
<div>
<label for="task-deadline" class="block text-blue-300 mb-1">Deadline (Optional):</label>
<input type="date" id="task-deadline" class="w-full bg-gray-700 text-white p-2 rounded border border-gray-600">
</div>
<div>
<label for="task-expiry" class="block text-blue-300 mb-1">Expiry Timer (minutes):</label>
<input type="number" id="task-expiry" class="w-full bg-gray-700 text-white p-2 rounded border border-gray-600" min="1">
</div>
</div>
<div class="flex justify-end gap-4 mt-8">
<button type="button" id="cancel-task-btn" class="bg-gray-600 hover:bg-gray-500 text-white font-bold py-2 px-6 rounded-lg transition">CANCEL</button>
<button type="submit" class="bg-blue-600 hover:bg-blue-500 text-white font-bold py-2 px-6 rounded-lg transition">SAVE_PACKET</button>
</div>
</form>
</div>
</div>
<!-- Communiqué Modal -->
<div id="communique-modal" class="fixed inset-0 z-50 flex items-center justify-center p-4 modal-backdrop hidden">
<div class="bg-gray-800 border border-blue-400 p-6 rounded-lg shadow-2xl max-w-2xl w-full">
<h2 class="font-display text-2xl text-white mb-4">MISSION_DEBRIEF_REPORT</h2>
<p class="text-gray-400 mb-4">Copy this report to share your progress.</p>
<textarea id="communique-text" readonly class="w-full h-64 bg-gray-900 text-gray-300 p-3 rounded border border-gray-700 font-mono text-sm"></textarea>
<div class="flex justify-end gap-4 mt-4">
<button id="copy-communique-btn" class="bg-blue-600 hover:bg-blue-500 text-white font-bold py-2 px-6 rounded-lg transition">COPY_TO_CLIPBOARD</button>
<button id="close-communique-btn" class="bg-gray-600 hover:bg-gray-500 text-white font-bold py-2 px-6 rounded-lg transition">CLOSE</button>
</div>
</div>
</div>
<!-- Confirmation Modal -->
<div id="confirmation-modal" class="fixed inset-0 z-50 flex items-center justify-center p-4 modal-backdrop hidden">
<div class="bg-gray-800 border border-red-500 p-6 rounded-lg shadow-2xl max-w-md w-full">
<h2 id="confirmation-title" class="font-display text-2xl text-red-400 mb-4">CONFIRM_ACTION</h2>
<p id="confirmation-message" class="text-gray-300 mb-6">Are you sure you wish to proceed with this action? This cannot be undone.</p>
<div class="flex justify-end gap-4 mt-4">
<button id="cancel-confirmation-btn" class="bg-gray-600 hover:bg-gray-500 text-white font-bold py-2 px-6 rounded-lg transition">CANCEL</button>
<button id="confirm-action-btn" class="bg-red-700 hover:bg-red-600 text-white font-bold py-2 px-6 rounded-lg transition">CONFIRM</button>
</div>
</div>
</div>
<!-- Save Notification -->
<div id="save-notification" class="fixed bottom-5 right-5 bg-green-500 text-white py-2 px-5 rounded-lg shadow-lg opacity-0 transform translate-y-3">
Intel Packet Saved.
</div>
<script>
document.addEventListener('DOMContentLoaded', () => {
// --- STATE MANAGEMENT ---
let state = {
tasks: [],
points: 0,
sessionEndTime: null,
lastBriefingDate: null,
currentDisplayDate: getTodayDateString(),
};
let sessionCountdownInterval;
const taskExpiryIntervals = {};
let actionToConfirm = null;
// --- DOM ELEMENTS ---
const appContainer = document.getElementById('app-container');
const dailyBriefingModal = document.getElementById('daily-briefing-modal');
const finishTimeInput = document.getElementById('finish-time');
const briefingTaskSelection = document.getElementById('briefing-task-selection');
const updateMissionBtn = document.getElementById('update-mission-btn');
const cancelBriefingBtn = document.getElementById('cancel-briefing-btn');
const sessionTitle = document.getElementById('session-title');
const sessionCountdownDisplay = document.getElementById('session-countdown');
const startNewSessionBtn = document.getElementById('start-new-session-btn');
const pointsDisplay = document.getElementById('points-display');
const missionDayTitle = document.getElementById('mission-day-title');
const todayTaskList = document.getElementById('today-task-list');
const taskModal = document.getElementById('task-modal');
const taskForm = document.getElementById('task-form');
const taskModalTitle = document.getElementById('task-modal-title');
const addNewTaskBtn = document.getElementById('add-new-task-btn');
const cancelTaskBtn = document.getElementById('cancel-task-btn');
const taskBankList = document.getElementById('task-bank-list');
const thesisProgressContainer = document.getElementById('thesis-progress-container');
const prevDayBtn = document.getElementById('prev-day-btn');
const nextDayBtn = document.getElementById('next-day-btn');
const amendMissionBtn = document.getElementById('amend-mission-btn');
const communiqueModal = document.getElementById('communique-modal');
const generateCommuniqueBtn = document.getElementById('generate-communique-btn');
const closeCommuniqueBtn = document.getElementById('close-communique-btn');
const copyCommuniqueBtn = document.getElementById('copy-communique-btn');
const communiqueText = document.getElementById('communique-text');
const confirmationModal = document.getElementById('confirmation-modal');
const confirmationTitle = document.getElementById('confirmation-title');
const confirmationMessage = document.getElementById('confirmation-message');
const confirmActionBtn = document.getElementById('confirm-action-btn');
const cancelConfirmationBtn = document.getElementById('cancel-confirmation-btn');
// --- UTILITY FUNCTIONS ---
function getTodayDateString() {
const today = new Date();
const year = today.getFullYear();
const month = (today.getMonth() + 1).toString().padStart(2, '0');
const day = today.getDate().toString().padStart(2, '0');
return `${year}-${month}-${day}`;
}
function formatDate(dateString) {
const date = new Date(dateString + 'T00:00:00'); // Ensure it's treated as local time
const options = { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' };
return date.toLocaleDateString(undefined, options).toUpperCase();
}
// --- STATE PERSISTENCE --- function saveState() { localStorage.setItem('intelTrackerState', JSON.stringify(state)); }
function loadState() { const savedState = localStorage.getItem('intelTrackerState'); if (savedState) { try { const loadedState = JSON.parse(savedState); if (typeof loadedState === 'object' && loadedState !== null) { state.tasks = Array.isArray(loadedState.tasks) ? loadedState.tasks : []; state.points = typeof loadedState.points === 'number' ? loadedState.points : 0; state.sessionEndTime = loadedState.sessionEndTime || null; state.lastBriefingDate = loadedState.lastBriefingDate || null; } } catch (e) { console.error("STATE_CORRUPTION // Failed to load state from localStorage. Resetting.", e); localStorage.removeItem('intelTrackerState'); } } }
// --- CORE APPLICATION LOGIC --- function initializeApp() { loadState(); const today = getTodayDateString(); document.getElementById('current-date-display').textContent = formatDate(today);
if (state.lastBriefingDate !== today) {
showDailyBriefing();
} else {
startApp();
}
setInterval(handleExpiredPointReduction, 5000);
}
function showDailyBriefing(isAmendment = false, isNewSession = false) { renderBriefingTaskSelection(); const briefingTitle = document.getElementById('briefing-title'); const briefingSubtitle = document.getElementById('briefing-subtitle'); const finishTimeWrapper = document.getElementById('finish-time-wrapper');
cancelBriefingBtn.classList.toggle('hidden', !isAmendment && !isNewSession);
if (isNewSession) { briefingTitle.textContent = "NEWSESSIONBRIEFING"; briefingSubtitle.textContent = "Set your extraction time for this new session."; finishTimeWrapper.classList.remove('hidden'); updateMissionBtn.textContent = "ENGAGENEWSESSION"; } else if (isAmendment) { briefingTitle.textContent = "AMENDMISSIONPLAN"; briefingSubtitle.textContent = "Add new targets to today's operation."; finishTimeWrapper.classList.add('hidden'); updateMissionBtn.textContent = "UPDATEMISSIONPLAN"; } else { // Initial briefing briefingTitle.textContent = "DAILYMISSIONBRIEFING"; briefingSubtitle.textContent = "Welcome, Operative. Define your operational parameters for today."; finishTimeWrapper.classList.remove('hidden'); updateMissionBtn.textContent = "ENGAGE"; } dailyBriefingModal.classList.remove('hidden'); }
function startApp() { appContainer.classList.remove('hidden'); dailyBriefingModal.classList.add('hidden'); updateSessionDisplay(); renderAll(); }
function updateSessionDisplay() { if (state.sessionEndTime && new Date().getTime() < state.sessionEndTime) { // Active session sessionTitle.textContent = "SESSION ENDS IN:"; sessionCountdownDisplay.classList.remove('hidden'); startNewSessionBtn.classList.add('hidden'); startSessionCountdown(); } else { // No active session sessionTitle.textContent = "NOACTIVESESSION"; sessionCountdownDisplay.classList.add('hidden'); startNewSessionBtn.classList.remove('hidden'); if (sessionCountdownInterval) clearInterval(sessionCountdownInterval); } }
function renderAll() { renderMissionDay(); renderTaskBank(); renderThesisProgress(); pointsDisplay.textContent = state.points; }
function renderMissionDay() { missionDayTitle.textContent = formatDate(state.currentDisplayDate); todayTaskList.innerHTML = '';
const tasksForDay = state.tasks.filter(t => t.assignedDate === state.currentDisplayDate); document.getElementById('no-tasks-today').classList.toggle('hidden', tasksForDay.length > 0);
if (tasksForDay.length > 0) { tasksForDay.forEach(task => { const taskCard = createTaskCard(task); todayTaskList.appendChild(taskCard); if (task.expiryMinutes && !task.isCompleted) { startExpiryTimer(task.id); } }); } updateDayNavButtons(); }
function renderTaskBank() { taskBankList.innerHTML = ''; const unassignedTasks = state.tasks.filter(t => !t.assignedDate); document.getElementById('no-tasks-in-bank').classList.toggle('hidden', unassignedTasks.length > 0);
unassignedTasks.forEach(task => { const taskEl =
document.createElement('div'); taskEl.className = 'bg-gray-700 p-2
rounded flex justify-between items-center'; taskEl.innerHTML =
<span class="text-sm">${task.name}</span> <button class="edit-task-bank text-xs text-blue-400 hover:text-blue-300" data-id="${task.id}">EDIT</button>;
taskBankList.appendChild(taskEl); }); }
function renderThesisProgress() { thesisProgressContainer.innerHTML = ''; const subsections = [...new Set(state.tasks.map(t => t.subsection).filter(Boolean))];
if (subsections.length === 0) { thesisProgressContainer.innerHTML =
<div class="text-center text-gray-500 p-4">No subsections defined. Add tasks to a subsection to track progress.</div>;
return; }
subsections.forEach(subsection => { const tasksInSubsection = state.tasks.filter(t => t.subsection === subsection); const progressTasks = tasksInSubsection.filter(t => t.total > 0); const checklistTasks = tasksInSubsection.filter(t => !t.total || t.total <= 0);
const progressTotal = progressTasks.reduce((sum, t) => sum + t.total, 0);
const progressCurrent = progressTasks.reduce((sum, t) => sum + t.current, 0);
const checklistTotal = checklistTasks.length;
const checklistCompleted = checklistTasks.filter(t => t.isCompleted).length;
const totalUnits = progressTotal + checklistTotal;
const completedUnits = progressCurrent + checklistCompleted;
const percentage = totalUnits > 0 ? (completedUnits / totalUnits) * 100 : 0;
const progressEl = document.createElement('div');
progressEl.innerHTML = `
<div class="flex justify-between items-center text-sm mb-1">
<span class="text-gray-300">${subsection}</span>
<span class="text-blue-300">${Math.round(percentage)}%</span>
</div>
<div class="w-full bg-gray-700 rounded-full h-2.5">
<div class="bg-blue-500 h-2.5 rounded-full progress-bar-inner" style="width: ${percentage}%"></div>
</div>
`;
thesisProgressContainer.appendChild(progressEl);
}); }
function createTaskCard(task) { const card =
document.createElement('div'); card.id = task-${task.id};
card.className =
task-card bg-gray-900 p-4 rounded-lg shadow-lg ${task.isExpired ? 'expired' : ''} ${task.isCompleted ? 'completed' : ''};
const deadlineDate = task.deadline ? new Date(task.deadline +
'T00:00:00') : null; const timeDiff = deadlineDate ?
deadlineDate.getTime() - new Date().getTime() : 0; const daysLeft =
Math.ceil(timeDiff / (1000 * 3600 * 24)); const deadlineText =
deadlineDate ? (daysLeft > 0 ? ${daysLeft} days left :
(daysLeft === 0 ? DUE TODAY : OVERDUE)) : 'No
Deadline'; const deadlineColor = daysLeft < 3 && daysLeft
>= 0 ? 'text-red-400' : 'text-gray-400';
const isProgressTask = task.total && task.total > 0; let progressHTML = ''; let actionsHTML = '';
if (task.isCompleted) { actionsHTML =
<div class="text-center font-display text-green-400 mt-3">OBJECTIVE_COMPLETE</div>;
} else if (isProgressTask) { const progressPercentage = (task.current /
task.total) * 100; progressHTML =
<div class="my-3"> <div class="flex justify-between text-xs text-gray-400 mb-1"> <span>PROGRESS</span> <span>${task.current} / ${task.total}</span> </div> <div class="w-full bg-gray-700 rounded-full h-2"> <div class="bg-blue-500 h-2 rounded-full progress-bar-inner" style="width: ${progressPercentage}%"></div> </div> </div>;
actionsHTML =
<div class="flex flex-col md:flex-row justify-between items-center gap-2 mt-3"> <form class="update-form flex gap-2 w-full md:w-auto" data-id="${task.id}"> <input type="number" min="1" class="w-full bg-gray-700 text-white px-2 py-1 rounded border border-gray-600 text-sm" placeholder="+ units" required> <button type="submit" class="bg-gray-600 hover:bg-gray-500 text-white font-bold px-3 py-1 rounded text-sm">LOG</button> </form> <div class="flex items-center gap-2 text-sm"> <span class="expiry-timer text-gray-400" data-id="${task.id}"></span> <button class="urgency-challenge-btn bg-red-800 hover:bg-red-700 text-white text-xs font-bold px-2 py-1 rounded" data-id="${task.id}">URGENCY</button> </div> </div>;
} else { // Checklist task actionsHTML =
<div class="mt-3"> <button class="mark-complete-btn w-full bg-green-700 hover:bg-green-600 text-white font-bold py-2 rounded-lg transition" data-id="${task.id}">MARK_COMPLETE</button> </div>;
}
card.innerHTML =
<div class="flex justify-between items-start"> <div> <h4 class="font-bold text-lg text-white">${task.name}</h4> <p class="text-xs ${deadlineColor}">${deadlineText}</p> </div> <div class="flex gap-2"> ${!task.isCompleted ?: ''} </div> </div> ${progressHTML} ${actionsHTML};
return card; }
function startExpiryTimer(taskId) { if (taskExpiryIntervals[taskId]) clearInterval(taskExpiryIntervals[taskId]);
const task = state.tasks.find(t => t.id === taskId); if (!task || !task.expiryMinutes) return;
if (!task.expiryStartedAt) { task.expiryStartedAt = Date.now(); saveState(); }
const timerDisplay =
document.querySelector(.expiry-timer[data-id="${taskId}"]);
if (!timerDisplay) { return; }
taskExpiryIntervals[taskId] = setInterval(() => { const elapsedMs = Date.now() - task.expiryStartedAt; const remainingMs = (task.expiryMinutes * 60 * 1000) - elapsedMs;
if (remainingMs <= 0) {
timerDisplay.textContent = "EXPIRED";
timerDisplay.classList.add('text-red-500');
if (!task.isExpired) {
task.isExpired = true;
document.getElementById(`task-${task.id}`)?.classList.add('expired');
saveState();
}
} else {
const minutes = Math.floor((remainingMs / 1000 / 60) % 60).toString().padStart(2, '0');
const seconds = Math.floor((remainingMs / 1000) % 60).toString().padStart(2, '0');
timerDisplay.textContent = `${minutes}:${seconds}`;
}
}, 1000); }
function handleExpiredPointReduction() { let pointsChanged = false; state.tasks.forEach(task => { if (task.isExpired && !task.isCompleted) { state.points = Math.max(0, state.points - 1); pointsChanged = true; } }); if (pointsChanged) { pointsDisplay.textContent = state.points; saveState(); } }
// --- EVENT HANDLERS --- updateMissionBtn.addEventListener('click', () => { const isInitialBriefing = !state.lastBriefingDate || state.lastBriefingDate !== getTodayDateString();
// Always check for time input if the wrapper is visible
if (!finishTimeInput.closest('#finish-time-wrapper').classList.contains('hidden')) {
const finishTime = finishTimeInput.value;
if (!finishTime) {
alert('Please set your extraction time.');
return;
}
const [hours, minutes] = finishTime.split(':');
const now = new Date();
state.sessionEndTime = new Date(now.getFullYear(), now.getMonth(), now.getDate(), hours, minutes, 0).getTime();
if (state.sessionEndTime < now.getTime()) {
alert("Extraction time cannot be in the past.");
return;
}
}
const selectedTaskIds = [...briefingTaskSelection.querySelectorAll('input:checked')].map(input => parseInt(input.value));
const today = getTodayDateString();
state.tasks.forEach(task => {
if (selectedTaskIds.includes(task.id)) {
task.assignedDate = today;
}
});
if (isInitialBriefing) {
state.lastBriefingDate = today;
startApp();
} else {
dailyBriefingModal.classList.add('hidden');
updateSessionDisplay();
renderAll();
}
saveState();
});
startNewSessionBtn.addEventListener('click', () => showDailyBriefing(false, true));
amendMissionBtn.addEventListener('click', () => showDailyBriefing(true, false)); cancelBriefingBtn.addEventListener('click', () => dailyBriefingModal.classList.add('hidden'));
addNewTaskBtn.addEventListener('click', () => openTaskModal()); cancelTaskBtn.addEventListener('click', () => taskModal.classList.add('hidden'));
taskForm.addEventListener('submit', (e) => { e.preventDefault(); const id = document.getElementById('task-id').value; const totalVal = document.getElementById('task-total').value; const taskData = { name: document.getElementById('task-name').value.trim(), subsection: document.getElementById('task-subsection').value.trim(), total: totalVal ? parseInt(totalVal) : null, deadline: document.getElementById('task-deadline').value, expiryMinutes: document.getElementById('task-expiry').value ? parseInt(document.getElementById('task-expiry').value) : null, };
if (id) { const taskIndex = state.tasks.findIndex(t => t.id == id); state.tasks[taskIndex] = { ...state.tasks[taskIndex], ...taskData }; } else { state.tasks.push({ id: Date.now(), ...taskData, current: 0, isCompleted: false, assignedDate: null }); } saveState(); showSaveNotification(); renderAll(); taskModal.classList.add('hidden'); });
// --- Confirmation Modal Logic --- function showConfirmationModal(title, message, onConfirm) { confirmationTitle.textContent = title; confirmationMessage.textContent = message; actionToConfirm = onConfirm; confirmationModal.classList.remove('hidden'); }
function hideConfirmationModal() { actionToConfirm = null; confirmationModal.classList.add('hidden'); }
cancelConfirmationBtn.addEventListener('click', hideConfirmationModal); confirmActionBtn.addEventListener('click', () => { if (typeof actionToConfirm === 'function') { actionToConfirm(); } hideConfirmationModal(); });
// --- DELEGATED EVENT LISTENERS --- document.body.addEventListener('click', (e) => { if (e.target.classList.contains('edit-task-bank')) { openTaskModal(e.target.dataset.id); }
if (e.target.classList.contains('delete-task')) {
const taskId = parseInt(e.target.dataset.id);
const task = state.tasks.find(t => t.id === taskId);
if (task) {
showConfirmationModal(
'DELETE_INTEL_PACKET',
`Are you sure you want to permanently delete the task: "${task.name}"? This action cannot be undone.`,
() => {
state.tasks = state.tasks.filter(t => t.id !== taskId);
saveState();
renderAll();
}
);
}
}
if (e.target.classList.contains('mark-complete-btn')) {
const taskId = parseInt(e.target.dataset.id);
const task = state.tasks.find(t => t.id === taskId);
if (task) {
task.isCompleted = true;
state.points += 10; // Award 10 points for checklist completion
saveState();
renderAll();
}
}
if (e.target.classList.contains('urgency-challenge-btn')) {
const taskId = parseInt(e.target.dataset.id);
const task = state.tasks.find(t => t.id === taskId);
if (task && task.total) {
const remaining = task.total - task.current;
const time = prompt(`RAGE BAIT: Bet you can't complete the remaining ${remaining} units in this many minutes:`);
if (time && !isNaN(time) && time > 0) {
task.expiryMinutes = parseInt(time);
task.expiryStartedAt = Date.now();
task.isExpired = false;
document.getElementById(`task-${task.id}`)?.classList.remove('expired');
startExpiryTimer(task.id);
saveState();
}
} else if (task) {
alert("Urgency challenge can only be set for tasks with a 'Total Units' goal.")
}
}
});
document.body.addEventListener('submit', (e) => { if (e.target.classList.contains('update-form')) { e.preventDefault(); const form = e.target; const taskId = parseInt(form.dataset.id); const task = state.tasks.find(t => t.id === taskId); const input = form.querySelector('input'); const amount = parseInt(input.value);
if (task && amount > 0) {
task.current = Math.min(task.total, task.current + amount);
state.points += amount;
if (task.current >= task.total && !task.isCompleted) {
task.isCompleted = true;
state.points += task.total;
if (taskExpiryIntervals[task.id]) clearInterval(taskExpiryIntervals[task.id]);
}
saveState();
renderAll();
}
form.reset();
} });
function renderBriefingTaskSelection() {
briefingTaskSelection.innerHTML = ''; const availableTasks =
state.tasks.filter(t => !t.isCompleted && !t.assignedDate);
if (availableTasks.length === 0) { briefingTaskSelection.innerHTML =
<p class="text-gray-400 text-center p-4">No available tasks in the bank.</p>;
return; } availableTasks.forEach(task => { const div =
document.createElement('div'); div.className = 'flex items-center
bg-gray-700 p-2 rounded'; div.innerHTML =
<input type="checkbox" id="brief-task-${task.id}" value="${task.id}" class="h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500"> <label for="brief-task-${task.id}" class="ml-3 text-sm text-gray-200">${task.name}</label>;
briefingTaskSelection.appendChild(div); }); }
function openTaskModal(taskId = null) { taskForm.reset(); if (taskId) { const task = state.tasks.find(t => t.id == taskId); taskModalTitle.textContent = "EDITINTELPACKET"; document.getElementById('task-id').value = task.id; document.getElementById('task-name').value = task.name; document.getElementById('task-subsection').value = task.subsection || ''; document.getElementById('task-total').value = task.total || ''; document.getElementById('task-deadline').value = task.deadline || ''; document.getElementById('task-expiry').value = task.expiryMinutes || ''; } else { taskModalTitle.textContent = "NEWINTELPACKET"; document.getElementById('task-id').value = ''; } taskModal.classList.remove('hidden'); }
function startSessionCountdown() { if (sessionCountdownInterval) clearInterval(sessionCountdownInterval); if (!state.sessionEndTime) return;
sessionCountdownInterval = setInterval(() => { const now = new Date().getTime(); const distance = state.sessionEndTime - now;
if (distance < 0) {
clearInterval(sessionCountdownInterval);
state.sessionEndTime = null;
saveState();
updateSessionDisplay();
return;
}
const hours = Math.floor((distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)).toString().padStart(2, '0');
const minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60)).toString().padStart(2, '0');
const seconds = Math.floor((distance % (1000 * 60)) / 1000).toString().padStart(2, '0');
sessionCountdownDisplay.textContent = `${hours}:${minutes}:${seconds}`;
}, 1000); }
function changeDay(offset) { const currentDate = new Date(state.currentDisplayDate + 'T00:00:00'); currentDate.setDate(currentDate.getDate() + offset);
const year = currentDate.getFullYear(); const month =
(currentDate.getMonth() + 1).toString().padStart(2, '0'); const day =
currentDate.getDate().toString().padStart(2, '0');
state.currentDisplayDate = ${year}-${month}-${day};
renderMissionDay(); }
function updateDayNavButtons() { const today = getTodayDateString(); const isTodayOrFuture = state.currentDisplayDate >= today; nextDayBtn.disabled = isTodayOrFuture; nextDayBtn.classList.toggle('opacity-50', isTodayOrFuture); nextDayBtn.title = isTodayOrFuture ? "OPERATIONTIMELOCK // Cannot navigate to future dates." : "Advance to next day"; prevDayBtn.title = "Return to previous day"; }
prevDayBtn.addEventListener('click', () => changeDay(-1)); nextDayBtn.addEventListener('click', () => changeDay(1));
generateCommuniqueBtn.addEventListener('click', () => { const
tasksForDay = state.tasks.filter(t => t.assignedDate ===
state.currentDisplayDate && t.isCompleted); let report =
== MISSION DEBRIEF: ${formatDate(state.currentDisplayDate)} ==\n\n;
if (tasksForDay.length > 0) { report += "COMPLETED OBJECTIVES:\n";
tasksForDay.forEach(task => { const unitText = (task.total &&
task.total > 0) ? (${task.current}/${task.total} units)
: ''; report += - ${task.name}${unitText}\n; }); } else {
report += "No objectives completed on this date."; }
communiqueText.value = report;
communiqueModal.classList.remove('hidden'); });
closeCommuniqueBtn.addEventListener('click', () => communiqueModal.classList.add('hidden')); copyCommuniqueBtn.addEventListener('click', () => { communiqueText.select(); document.execCommand('copy'); copyCommuniqueBtn.textContent = 'COPIED!'; setTimeout(() => { copyCommuniqueBtn.textContent = 'COPYTOCLIPBOARD'; }, 2000); });
function showSaveNotification() { const notification = document.getElementById('save-notification'); notification.classList.remove('opacity-0', 'translate-y-3'); setTimeout(() => { notification.classList.add('opacity-0', 'translate-y-3'); }, 2000); }
// --- INITIALIZE --- initializeApp(); });