-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmain.js
More file actions
422 lines (361 loc) · 15.5 KB
/
main.js
File metadata and controls
422 lines (361 loc) · 15.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
// Main entry point for ParaSight
import { initializeGame, getAvailableDates } from "./js/game-controller.js?v=1.1";
import { setupHelpButton } from "./assets/js/help-modal.js";
// Initialize the game when the window loads
window.onload = async () => {
// Set the date display to today's date on page load
const dateElementInit = document.getElementById("current-date");
if (dateElementInit) {
const today = new Date();
const options = { year: 'numeric', month: 'long', day: 'numeric' };
// Fix: use correct type for year/month/day
dateElementInit.textContent = today.toLocaleDateString('en-US', {
year: 'numeric', month: 'long', day: 'numeric'
});
}
// Initialize authentication system first
try {
await window.authManager.initialize();
await window.streakTracker.initialize();
await window.authUI.initialize();
console.log('✅ Authentication system initialized');
} catch (error) {
console.error('❌ Failed to initialize authentication system:', error);
// Continue with game initialization even if auth fails
}
// Then initialize the game
initializeGame();
// Then set up header controls
const settingsButton = document.getElementById("settings-button");
const helpButton = document.getElementById("help-button");
const dateElement = document.getElementById("current-date");
const arrows = document.querySelectorAll(".arrow");
// Settings button handler
if (settingsButton) {
settingsButton.addEventListener("click", () => {
// TODO: Implement settings modal
alert("Settings coming soon!");
});
}
// Setup help button handler
setupHelpButton();
// Initialize current date from the element or default to today
let currentDate =
dateElement && dateElement.textContent
? new Date(dateElement.textContent)
: new Date();
// Update the date display
function updateDateDisplay(date) {
if (dateElement) {
dateElement.textContent = date.toLocaleDateString("en-US", {
month: "long",
day: "numeric",
year: "numeric",
});
}
}
updateDateDisplay(currentDate);
// Set up custom calendar
const calendarButton = document.getElementById("calendar-button");
const dateSelector = document.querySelector(".date-selector");
const customCalendar = document.getElementById("custom-calendar");
const monthYearDisplay = document.getElementById("month-year");
const prevMonthBtn = document.getElementById("prev-month");
const nextMonthBtn = document.getElementById("next-month");
const calendarDaysContainer = document.getElementById("calendar-days");
// Calendar state variables
let calendarCurrentDate = new Date(currentDate);
let calendarCurrentMonth = calendarCurrentDate.getMonth();
let calendarCurrentYear = calendarCurrentDate.getFullYear();
// Days with content - we'll fetch this from our data later
const daysWithContent = [];
// Play history: "YYYY-MM-DD" → score (populated when calendar opens)
let playedDates = {};
async function loadPlayHistory() {
if (!window.authManager || !window.authManager.isAuthenticated()) return;
console.log('📅 loadPlayHistory: fetching activity history...');
const result = await window.streakTracker.getActivityHistory({ limit: 365 });
console.log('📅 loadPlayHistory result:', result);
if (!result.success) {
console.error('❌ Failed to load play history:', result.error);
return;
}
playedDates = {};
result.activities.forEach(a => {
const pct = a.max_possible_score > 0
? Math.round((a.score / a.max_possible_score) * 100)
: a.score;
console.log(`📅 ${a.activity_date}: raw=${a.score}, max=${a.max_possible_score}, pct=${pct}`);
playedDates[a.activity_date] = pct;
});
console.log('📅 playedDates after load:', playedDates);
// Re-render the calendar if it's currently visible
if (customCalendar.classList.contains('show')) {
renderCalendarDays();
}
}
// Expose loadPlayHistory globally so it can be called after game completion
window.loadPlayHistory = loadPlayHistory;
function getScoreDotClass(score) {
if (score >= 80) return "played-green";
if (score >= 50) return "played-yellow";
return "played-red";
}
// Build content dates from the already-loaded index (no network requests needed)
// getAvailableDates() now returns MMDD strings like "0225"
function buildContentDates() {
const mmddKeys = getAvailableDates();
mmddKeys.forEach(key => {
// Convert MMDD "0225" → MM-DD "02-25" for calendar matching
if (/^\d{4}$/.test(key)) {
daysWithContent.push(`${key.slice(0, 2)}-${key.slice(2)}`);
}
});
}
// Load a specific date's puzzle file and reinitialize the game
async function loadParagraphForDate(mmDD) {
// The game-controller reads the date display element to pick the file;
// updateDateDisplay already set it before this call, so just reinitialize.
initializeGame();
}
// Function to generate and render calendar days
function renderCalendarDays() {
calendarDaysContainer.innerHTML = "";
// Update month and year display
monthYearDisplay.textContent = new Date(calendarCurrentYear, calendarCurrentMonth, 1)
.toLocaleDateString("en-US", { month: "long", year: "numeric" });
// Enforce 60-day history window
const minDate = new Date();
minDate.setDate(minDate.getDate() - 60);
const minDateStr = `${minDate.getFullYear()}-${String(minDate.getMonth() + 1).padStart(2, '0')}-${String(minDate.getDate()).padStart(2, '0')}`;
const minYear = minDate.getFullYear();
const minMonth = minDate.getMonth();
if (prevMonthBtn) {
prevMonthBtn.disabled = (calendarCurrentYear < minYear) ||
(calendarCurrentYear === minYear && calendarCurrentMonth <= minMonth);
}
// Get the first day of the month
const firstDay = new Date(calendarCurrentYear, calendarCurrentMonth, 1);
const startingDay = firstDay.getDay(); // 0 (Sunday) to 6 (Saturday)
// Get the last day of the month
const lastDay = new Date(calendarCurrentYear, calendarCurrentMonth + 1, 0);
const totalDays = lastDay.getDate();
// Create empty cells for days before the first day of month
for (let i = 0; i < startingDay; i++) {
const emptyDay = document.createElement("div");
emptyDay.className = "calendar-day empty";
calendarDaysContainer.appendChild(emptyDay);
}
// Format current selected date for comparison
const selectedDateStr = currentDate.toISOString().split('T')[0];
const todayDateStr = new Date().toISOString().split('T')[0];
// Create cells for all days of the month
for (let day = 1; day <= totalDays; day++) {
const dayElement = document.createElement("div");
dayElement.className = "calendar-day";
dayElement.textContent = day;
// Format this calendar day as YYYY-MM-DD for comparison
// Use local date parts directly to avoid UTC timezone shift
const thisDate = new Date(calendarCurrentYear, calendarCurrentMonth, day);
const thisDateStr = `${calendarCurrentYear}-${String(calendarCurrentMonth + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
// Add special classes
if (thisDateStr === selectedDateStr) {
dayElement.classList.add("selected");
}
if (thisDateStr === todayDateStr) {
dayElement.classList.add("today");
}
// Check if this date is in the future or too old (outside 60-day window)
const isFutureDate = thisDateStr > todayDateStr;
const isTooOld = thisDateStr < minDateStr;
// Extract MM-DD from this date for year-agnostic matching
const mmDD = thisDateStr.substring(5); // Get MM-DD from YYYY-MM-DD
// Mark days that have content and handle clickability
// Check both full date (YYYY-MM-DD) and year-agnostic (MM-DD)
if (daysWithContent.includes(thisDateStr) || daysWithContent.includes(mmDD)) {
dayElement.classList.add("has-content");
// If it's a future date, disable it
if (isFutureDate) {
dayElement.classList.add("future-date");
dayElement.title = "Cannot access future dates";
} else if (isTooOld) {
dayElement.classList.add("too-old");
dayElement.title = "Only the last 60 days are available";
} else {
// Add click handler to select date (only for dates with content and not in future)
dayElement.addEventListener("click", () => {
// More comprehensive check if game is in progress
const isGameInProgress = document.querySelectorAll('#clues-list li.found').length > 0 ||
document.querySelectorAll('.letter-tile.purchased').length > 0 ||
document.querySelectorAll('.letter-tile.selected').length > 0;
const clickedMmDD = `${String(calendarCurrentMonth + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
if (isGameInProgress) {
// Ask for confirmation before changing date and resetting game
if (confirm("Changing the date will reset your current game progress. Continue?")) {
currentDate = new Date(calendarCurrentYear, calendarCurrentMonth, day);
updateDateDisplay(currentDate);
console.log(`Selected date: ${currentDate.toISOString().split('T')[0]}`);
customCalendar.classList.remove("show");
loadParagraphForDate(clickedMmDD);
updateArrowStates();
}
} else {
// No game in progress, proceed without confirmation
currentDate = new Date(calendarCurrentYear, calendarCurrentMonth, day);
updateDateDisplay(currentDate);
console.log(`Selected date: ${currentDate.toISOString().split('T')[0]}`);
customCalendar.classList.remove("show");
loadParagraphForDate(clickedMmDD);
updateArrowStates();
}
});
}
} else {
// For dates without content, add a class to show they're not clickable
dayElement.classList.add("no-content");
}
// Apply score color to the entire date circle for played days (authenticated users only)
if (playedDates.hasOwnProperty(thisDateStr)) {
const score = playedDates[thisDateStr];
dayElement.classList.add(getScoreDotClass(score));
dayElement.title = `Score: ${score}%`;
}
calendarDaysContainer.appendChild(dayElement);
}
}
// Function to show/hide calendar
function toggleCalendar() {
const isVisible = customCalendar.classList.toggle("show");
if (isVisible) {
// Set calendar to current month/year and render days
calendarCurrentMonth = currentDate.getMonth();
calendarCurrentYear = currentDate.getFullYear();
renderCalendarDays();
loadPlayHistory().then(() => renderCalendarDays());
// Build content dates from already-loaded index (no network requests)
if (daysWithContent.length === 0) {
buildContentDates();
renderCalendarDays();
}
// Add click outside to close
setTimeout(() => {
document.addEventListener("click", closeCalendarOnClickOutside);
}, 10);
}
}
// Function to close calendar when clicking outside
function closeCalendarOnClickOutside(e) {
if (!customCalendar.contains(e.target) &&
!dateSelector.contains(e.target)) {
customCalendar.classList.remove("show");
document.removeEventListener("click", closeCalendarOnClickOutside);
}
}
// Set up event listeners for the calendar
if (dateSelector && customCalendar) {
// Toggle calendar on date or button click
dateSelector.addEventListener("click", (e) => {
e.stopPropagation();
toggleCalendar();
});
// Navigate to previous month
if (prevMonthBtn) {
prevMonthBtn.addEventListener("click", (e) => {
e.stopPropagation();
calendarCurrentMonth--;
if (calendarCurrentMonth < 0) {
calendarCurrentMonth = 11;
calendarCurrentYear--;
}
renderCalendarDays();
});
}
// Navigate to next month
if (nextMonthBtn) {
nextMonthBtn.addEventListener("click", (e) => {
e.stopPropagation();
calendarCurrentMonth++;
if (calendarCurrentMonth > 11) {
calendarCurrentMonth = 0;
calendarCurrentYear++;
}
renderCalendarDays();
});
}
}
// Helper function to check if a date is today or in the future
function isDateTodayOrFuture(date) {
const today = new Date();
const todayStr = today.toISOString().split('T')[0];
const dateStr = date.toISOString().split('T')[0];
return dateStr >= todayStr;
}
// Helper function to check if a date is in the future
function isDateInFuture(date) {
const today = new Date();
const todayStr = today.toISOString().split('T')[0];
const dateStr = date.toISOString().split('T')[0];
return dateStr > todayStr;
}
// Function to update arrow states based on current date
function updateArrowStates() {
const leftArrow = document.querySelector('.arrow:first-child');
const rightArrow = document.querySelector('.arrow:last-child');
if (rightArrow) {
const isCurrentDateToday = isDateTodayOrFuture(currentDate) && !isDateInFuture(currentDate);
if (isCurrentDateToday) {
rightArrow.classList.add('disabled');
rightArrow.setAttribute('aria-disabled', 'true');
rightArrow.title = 'Cannot navigate to future dates';
} else {
rightArrow.classList.remove('disabled');
rightArrow.removeAttribute('aria-disabled');
rightArrow.title = '';
}
}
}
// Add date navigation handlers
arrows.forEach((arrow) => {
arrow.addEventListener("click", (e) => {
// Check if the arrow is disabled
if (e.target.classList.contains('disabled')) {
return;
}
// Check if game is complete (victory message shown)
const isGameComplete = document.querySelector('.game-over-message') !== null;
// Check if game is in progress (but not complete)
const isGameInProgress = !isGameComplete && (
document.querySelectorAll('#clues-list li.found').length > 0 ||
document.querySelectorAll('.letter-tile.purchased').length > 0 ||
document.querySelectorAll('.letter-tile.selected').length > 0
);
// Only ask for confirmation if game is in progress but not complete
if (isGameInProgress) {
if (!confirm("Changing the date will reset your current game progress. Continue?")) {
return; // Cancel if user doesn't confirm
}
}
const newDate = new Date(currentDate);
const isLeft = e.target.textContent.includes("←");
if (isLeft) {
newDate.setDate(newDate.getDate() - 1);
} else {
// Additional check to prevent future navigation
if (isDateTodayOrFuture(currentDate) && !isDateInFuture(currentDate)) {
return; // Don't allow navigation to future from today
}
newDate.setDate(newDate.getDate() + 1);
}
currentDate = newDate;
updateDateDisplay(currentDate);
updateArrowStates();
// Log the navigated date for debugging
const dateStr = currentDate.toISOString().split('T')[0];
console.log(`Navigated to date: ${dateStr}`);
// Reinitialize game with new date
initializeGame();
});
});
// Initialize arrow states after setting up navigation
updateArrowStates();
};