/** * Utility module for processing and populating the treasure map SVG. * Handles clue points, their status, and interaction. */ /** * Populates the SVG map with clue points * @param {SVGElement} svgElement - The SVG DOM element to process * @returns {Promise} - Success flag */ export async function populateMap(svgElement) { try { if (!svgElement) { console.error("No SVG element provided"); return false; } console.log("Starting map population process"); // First, find all existing circle elements in the SVG const existingCircles = Array.from(svgElement.querySelectorAll("circle")); console.log(`Found ${existingCircles.length} existing circles in SVG`); // Hide all existing circles initially existingCircles.forEach((circle) => { circle.style.display = "none"; }); // Query the number of clues via API const clueCountResponse = await fetch("/api/clues/count", { credentials: "include", cache: "no-cache", }); if (!clueCountResponse.ok) { throw new Error( `Failed to fetch clue count: ${clueCountResponse.status}` ); } const { count: clueCount } = await clueCountResponse.json(); console.log(`Total clues: ${clueCount}`); // Determine how many circles to display (min of existing circles and clue count) const circlesToShow = Math.min(existingCircles.length, clueCount); console.log(`Will display ${circlesToShow} circles`); // Sort circles by vertical position (top to bottom) const sortedCircles = existingCircles.sort((a, b) => { const aY = parseFloat(a.getAttribute("cy")); const bY = parseFloat(b.getAttribute("cy")); return aY - bY; }); // Display only the needed circles and add clue-point class for (let i = 0; i < circlesToShow; i++) { const circle = sortedCircles[i]; circle.style.display = ""; circle.classList.add("clue-point"); circle.setAttribute("data-clue-id", i + 1); // Set default appearance circle.style.fill = "#9e9e9e"; // Default grey color circle.style.stroke = "#757575"; circle.style.strokeWidth = "2"; } return true; } catch (error) { console.error("Error populating map:", error); return false; } } /** * Updates the status of each clue point based on clues data * @param {SVGElement} svgElement - The SVG DOM element * @param {Array} clues - Array of clue objects with their status */ export function updateCluePointStatus(svgElement, clues) { if (!svgElement || !clues || !clues.length) { console.error("Missing required parameters for updating clue status"); return; } console.log("Updating clue point status"); try { // Get all clue points const cluePoints = svgElement.querySelectorAll(".clue-point"); if (!cluePoints.length) { console.error("No clue points found in SVG"); return; } // Find the first unfound clue (the current one) const currentClueIndex = clues.findIndex((clue) => !clue.found); const currentClueId = currentClueIndex !== -1 ? clues[currentClueIndex].id : null; // Process each clue point cluePoints.forEach((circle, index) => { if (index >= clues.length) return; const clue = clues[index]; const clueId = clue.id; // Determine status and apply appropriate color if (clue.found) { // Found/completed clue - Dark Green circle.style.fill = "#2e7d32"; // Dark green } else if (clueId === currentClueId) { // Current clue - Orange circle.style.fill = "#ff9800"; // Orange } else if (clueId < currentClueId) { // Skipped clue - Blue circle.style.fill = "#1976d2"; // Blue } else { // Not accessible yet - Grey circle.style.fill = "#9e9e9e"; // Grey } // Mark whether this clue point is available const isAvailable = clue.found || clueId === currentClueId; circle.setAttribute("data-available", isAvailable.toString()); }); console.log("Clue points updated successfully"); } catch (error) { console.error("Error updating clue status:", error); } } /** * Attaches click handlers to clue points * @param {SVGElement} svgElement - The SVG DOM element * @param {Array} clues - Array of clue objects with their status * @param {Function} navigationHandler - Function to handle navigation */ export function attachClueClickHandlers(svgElement, clues, navigationHandler) { if (!svgElement || !clues || !navigationHandler) { console.error("Missing required parameters for attaching click handlers"); return; } try { const cluePoints = svgElement.querySelectorAll(".clue-point"); cluePoints.forEach((circle, index) => { if (index >= clues.length) return; const clue = clues[index]; const isAvailable = circle.getAttribute("data-available") === "true"; // Set cursor style based on availability circle.style.cursor = isAvailable ? "pointer" : "not-allowed"; // Add click handler circle.onclick = (e) => { e.preventDefault(); e.stopPropagation(); if (isAvailable) { console.log(`Clicked on clue ${clue.id}`); navigationHandler(clue.id); } else { // Alert or other feedback if clue is not accessible console.log("Clue not available yet"); // Could show a tooltip or message here } }; }); } catch (error) { console.error("Error attaching click handlers:", error); } }