diff --git a/app/(main)/map/map.module.css b/app/(main)/map/map.module.css
index 60455a7..34d213c 100644
--- a/app/(main)/map/map.module.css
+++ b/app/(main)/map/map.module.css
@@ -85,6 +85,7 @@
height: fit-content; /* Allow natural height */
display: block; /* Change to block for proper scrolling */
overflow: visible; /* Show content beyond boundaries */
+ z-index: 5; /* Ensure it's above the background */
}
.svg-container svg {
@@ -95,7 +96,12 @@
object-fit: none; /* Don't constrain the SVG */
}
-/* Map background - display as full-page map */
+/* Remove the default map background when we have the processed SVG */
+.svg-container + .map-background {
+ display: none;
+}
+
+/* Map background - only shown when SVG is loading */
.map-background {
position: absolute;
top: 0;
@@ -106,7 +112,7 @@
background-size: auto; /* Don't resize the background */
background-position: top right; /* Position at top right */
background-repeat: no-repeat;
- z-index: 5;
+ z-index: 3; /* Lower z-index than the processed SVG */
}
/* Circle elements in the SVG map - attach to a container class */
@@ -119,6 +125,70 @@
fill: #ffb74d !important; /* Only change color on hover, no movement */
}
+/* Circle elements in the SVG map - attach to a container class */
+.svg-container circle.clue-point {
+ cursor: pointer;
+ transition: fill 0.3s ease, stroke-width 0.3s ease;
+ pointer-events: all; /* Ensure clicks are detected */
+ stroke-width: 2px;
+ r: 8; /* Set a minimum radius for all clue points */
+}
+
+/* Improve circle visibility and interactivity */
+.svg-container circle.clue-point {
+ cursor: pointer !important;
+ transition: all 0.3s ease;
+ pointer-events: all !important;
+ stroke-width: 2px;
+ r: 12px; /* Larger radius for better visibility and easier clicks */
+ z-index: 10;
+ position: relative;
+}
+
+/* Enhance hover effect */
+.svg-container circle.clue-point:hover {
+ fill: #ffb74d !important; /* Orange hover color */
+ stroke: #ff6d00 !important;
+ stroke-width: 4px !important;
+ transform: scale(1.2);
+ z-index: 20;
+ filter: drop-shadow(0px 0px 4px rgba(0, 0, 0, 0.5));
+}
+
+/* Invisible click areas for easier interaction */
+.svg-container circle.clue-point-click-area {
+ pointer-events: all !important;
+ cursor: pointer !important;
+ opacity: 0.01; /* Nearly invisible but still interactive */
+ z-index: 5;
+}
+
+/* Create custom animation for the current clue */
+@keyframes clue-point-pulse {
+ 0% {
+ stroke-width: 3px;
+ transform: scale(1);
+ filter: drop-shadow(0px 0px 2px rgba(255, 152, 0, 0.7));
+ }
+ 50% {
+ stroke-width: 5px;
+ transform: scale(1.2);
+ filter: drop-shadow(0px 0px 8px rgba(255, 152, 0, 0.9));
+ }
+ 100% {
+ stroke-width: 3px;
+ transform: scale(1);
+ filter: drop-shadow(0px 0px 2px rgba(255, 152, 0, 0.7));
+ }
+}
+
+.svg-container circle.clue-point:hover {
+ fill: #ffb74d !important; /* Orange hover color */
+ stroke: #ff6d00;
+ stroke-width: 3px;
+ transform: scale(1.1);
+}
+
/* Remaining styles unchanged */
.map-svg {
width: 100% !important; /* Override Next.js Image default width */
@@ -133,8 +203,13 @@
flex-direction: column;
align-items: center;
justify-content: center;
- height: 100vh;
+ height: 100%;
width: 100%;
+ position: absolute;
+ top: 0;
+ left: 0;
+ z-index: 50;
+ background-color: rgba(105, 181, 168, 0.8);
}
.loading-spinner {
diff --git a/app/(main)/map/page.js b/app/(main)/map/page.js
index 3ea89c8..924f76e 100644
--- a/app/(main)/map/page.js
+++ b/app/(main)/map/page.js
@@ -127,6 +127,74 @@ export default function MapPage() {
}
}, []);
+ // Enhanced function to populate the map with clue points
+ const enhanceSvgMap = useCallback(() => {
+ if (!mapContainerRef.current) return;
+
+ // Find the SVG element inside the map container
+ const svgElement = mapContainerRef.current.querySelector("svg");
+ if (!svgElement) {
+ console.error("No SVG element found in map container");
+ setMapError(true);
+ return;
+ }
+
+ console.log("SVG element found, starting enhancement process");
+
+ // Clear out any existing map-background elements to ensure we only show one map
+ const existingMapBgs = document.querySelectorAll(
+ `.${styles["map-background"]}`
+ );
+ existingMapBgs.forEach((bg) => (bg.style.display = "none"));
+
+ // First populate the map with basic clue points
+ populateMap(svgElement)
+ .then((success) => {
+ if (success) {
+ console.log("Map populated with clue points successfully");
+
+ // Process the clues to determine which are available
+ const processedClues = clues.map((clue, index, arr) => {
+ // Find the first unfound clue's index
+ const firstUnfoundIndex = arr.findIndex((c) => !c.found);
+
+ // A clue is available if:
+ // 1. It's already found, OR
+ // 2. It's the next unfound clue in the sequence
+ const available = clue.found || index === firstUnfoundIndex;
+
+ return { ...clue, available };
+ });
+
+ // Debug info about clue statuses
+ processedClues.forEach((clue, index) => {
+ console.log(
+ `Clue ${clue.id} (index ${index}): Available: ${clue.available}, Found: ${clue.found}`
+ );
+ });
+
+ // Update the clue point appearances based on their statuses
+ updateCluePointStatus(svgElement, processedClues);
+
+ // Define a direct navigation function that will be used for click handling
+ const navigateToClue = (clueId) => {
+ console.log(`Navigating to clue ${clueId}`);
+ router.push(`/clue/${clueId}`);
+ };
+
+ // Attach click handlers to all clue points and click areas
+ attachClueClickHandlers(svgElement, processedClues, navigateToClue);
+ } else {
+ console.error("Failed to populate map with clue points");
+ setMapError(true);
+ }
+ })
+ .catch((error) => {
+ console.error("Error enhancing SVG map:", error);
+ setMapError(true);
+ });
+ }, [clues, router, styles]);
+
// Function to load and enhance the SVG map
const loadAndEnhanceMap = useCallback(async () => {
try {
@@ -143,7 +211,7 @@ export default function MapPage() {
}
const svgText = await response.text();
- console.log("SVG loaded, length:", svgText.length);
+ console.log(`SVG loaded, length: ${svgText.length} characters`);
// Ensure SVG has correct namespaces and viewBox
let fixedSvg = svgText.replace(
@@ -174,42 +242,6 @@ export default function MapPage() {
}
}, []);
- // Enhanced function to populate the map with clue points
- const enhanceSvgMap = useCallback(() => {
- if (!mapContainerRef.current) return;
-
- // Find the SVG element inside the map container
- const svgElement = mapContainerRef.current.querySelector("svg");
- if (!svgElement) {
- console.error("No SVG element found in map container");
- return;
- }
-
- console.log("SVG element found, starting enhancement");
-
- // Use our utility to populate the map with clue points
- populateMap(svgElement)
- .then((success) => {
- if (success) {
- console.log("Map populated successfully");
-
- // After populating the map, update the status of each clue point
- updateCluePointStatus(svgElement, clues);
-
- // Attach click handlers for navigation
- attachClueClickHandlers(svgElement, clues, (clueId) => {
- router.push(`/clue/${clueId}`);
- });
- } else {
- console.error("Failed to populate map");
- }
- })
- .catch((error) => {
- console.error("Error enhancing SVG map:", error);
- setMapError(true);
- });
- }, [clues, router]);
-
// Function to check hunt status and timer
async function checkHuntAndTimer() {
if (!isMounted.current) return;
@@ -538,23 +570,23 @@ export default function MapPage() {
// Only try to enhance the map if both the SVG and clues are loaded
if (!mapSvg || !clues.length) return;
- console.log("Both SVG and clues loaded, enhancing map");
+ console.log("Both SVG and clues are loaded, preparing to enhance map");
- // Use requestAnimationFrame to ensure the DOM has been updated with the SVG
+ // Ensure the SVG is fully loaded in the DOM before enhancing
requestAnimationFrame(() => {
- // Short timeout to ensure the SVG is fully rendered in the DOM
+ // Longer timeout to ensure the SVG is fully rendered in the DOM
setTimeout(() => {
if (
mapContainerRef.current &&
mapContainerRef.current.querySelector("svg")
) {
- console.log("SVG found in DOM, enhancing map");
+ console.log("SVG found in DOM, enhancing map now");
enhanceSvgMap();
} else {
- console.error("SVG not found in DOM after rendering");
+ console.error("SVG not found in DOM after rendering timeout");
setMapError(true);
}
- }, 100);
+ }, 500); // Increased timeout for better reliability on slower devices
});
}, [mapSvg, clues, enhanceSvgMap]);
@@ -627,14 +659,12 @@ export default function MapPage() {
console.log("SVG container loaded")}
/>
) : (
-
+
)}
diff --git a/app/api/clues/count/route.js b/app/api/clues/count/route.js
index ae98af7..ec5efdc 100644
--- a/app/api/clues/count/route.js
+++ b/app/api/clues/count/route.js
@@ -17,13 +17,16 @@ export async function GET(request) {
return Response.json({ error: "Unauthorized access" }, { status: 401 });
}
- // Get the total count of clues
+ // Get the total count of clues with a direct SQL COUNT query
const result = await queryOne("SELECT COUNT(*) as count FROM clues");
if (!result) {
return Response.json({ error: "Failed to count clues" }, { status: 500 });
}
+ // Log the count to help with debugging
+ console.log(`API: Returning clue count: ${result.count}`);
+
return Response.json(
{ count: result.count },
{
diff --git a/clue_hunt.db b/clue_hunt.db
index c157722..991e92d 100644
Binary files a/clue_hunt.db and b/clue_hunt.db differ
diff --git a/utils/map-populate.js b/utils/map-populate.js
index 7a1315c..a766ccf 100644
--- a/utils/map-populate.js
+++ b/utils/map-populate.js
@@ -1,8 +1,79 @@
/**
* Utility module for processing and populating the treasure map SVG.
- * Handles clue points, their status, and interaction.
*/
+// Global variable to track if we've added our custom styles
+let stylesAdded = false;
+
+/**
+ * Adds custom styles to the document head for clue points
+ */
+function addCustomStyles() {
+ if (stylesAdded) return;
+
+ const style = document.createElement("style");
+ style.id = "map-custom-styles";
+ style.textContent = `
+ @keyframes cluePointPulse {
+ 0% { stroke-width: 3px; transform: scale(1); filter: drop-shadow(0px 0px 3px rgba(255, 152, 0, 0.9)); }
+ 50% { stroke-width: 6px; transform: scale(1.2); filter: drop-shadow(0px 0px 10px rgba(255, 152, 0, 1)); }
+ 100% { stroke-width: 3px; transform: scale(1); filter: drop-shadow(0px 0px 3px rgba(255, 152, 0, 0.9)); }
+ }
+
+ /* Ensure SVG elements use CSS z-index correctly */
+ svg * {
+ transform-box: fill-box;
+ stroke-width: 2px;
+ pointer-events: all !important;
+ transition: fill 0.2s ease, stroke-width 0.2s ease, transform 0.2s ease !important;
+ z-index: 100;
+ position: relative;
+ }
+
+ .clue-point:hover {
+ fill: #ffb74d !important;
+ stroke: #ff6d00 !important;
+ stroke-width: 4px !important;
+ transform: scale(1.2) !important;
+ z-index: 200 !important;
+ filter: drop-shadow(0px 0px 4px rgba(0, 0, 0, 0.5)) !important;
+ }
+
+ .clue-point-found {
+ fill: #2e7d32 !important;
+ stroke: #1b5e20 !important;
+ }
+
+ .clue-point-current {
+ fill: #ff9800 !important;
+ stroke: #e65100 !important;
+ animation: cluePointPulse 2s infinite !important;
+ }
+
+ .clue-point-skipped {
+ fill: #1976d2 !important;
+ stroke: #0d47a1 !important;
+ }
+
+ .clue-point-locked {
+ fill: #9e9e9e !important;
+ stroke: #757575 !important;
+ }
+
+ .clue-click-area {
+ opacity: 0;
+ fill: transparent;
+ stroke: transparent;
+ pointer-events: all !important;
+ cursor: pointer !important;
+ }
+ `;
+
+ document.head.appendChild(style);
+ stylesAdded = true;
+ console.log("Custom map styles added to document");
+}
+
/**
* Populates the SVG map with clue points
* @param {SVGElement} svgElement - The SVG DOM element to process
@@ -10,62 +81,139 @@
*/
export async function populateMap(svgElement) {
try {
+ console.log("Starting map population process");
+
+ // Add custom styles to document if not already added
+ addCustomStyles();
+
if (!svgElement) {
console.error("No SVG element provided");
return false;
}
- console.log("Starting map population process");
+ // Clear any existing clue points and click areas to avoid duplicates
+ const existingCluePoints = svgElement.querySelectorAll(
+ ".clue-point, .clue-click-area"
+ );
+ existingCluePoints.forEach((element) => {
+ if (element.parentNode) {
+ element.parentNode.removeChild(element);
+ }
+ });
- // 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`);
+ console.log("Cleared existing clue points");
+
+ // Find all circles in the SVG
+ const allCircles = Array.from(svgElement.querySelectorAll("circle"));
+ console.log(`Found ${allCircles.length} circles in SVG`);
+
+ if (allCircles.length === 0) {
+ console.error("No circles found in SVG");
+ return false;
+ }
// Hide all existing circles initially
- existingCircles.forEach((circle) => {
- circle.style.display = "none";
- });
+ allCircles.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",
- });
+ try {
+ // Fetch the number of clues from the API
+ const response = await fetch("/api/clues/count", {
+ method: "GET",
+ credentials: "include",
+ headers: {
+ "Cache-Control": "no-cache, no-store, must-revalidate",
+ Pragma: "no-cache",
+ },
+ });
- if (!clueCountResponse.ok) {
- throw new Error(
- `Failed to fetch clue count: ${clueCountResponse.status}`
+ if (!response.ok) {
+ throw new Error(`Failed to fetch clue count: ${response.status}`);
+ }
+
+ const data = await response.json();
+ const clueCount = data.count;
+ console.log(`API returned clue count: ${clueCount}`);
+
+ if (!clueCount || clueCount <= 0) {
+ console.error("Invalid clue count returned from API");
+ return false;
+ }
+
+ // Sort circles by vertical position (top to bottom)
+ const sortedCircles = allCircles.sort((a, b) => {
+ const aY = parseFloat(a.getAttribute("cy") || "0");
+ const bY = parseFloat(b.getAttribute("cy") || "0");
+ return aY - bY;
+ });
+
+ // Determine how many circles to show
+ const circlesToShow = Math.min(sortedCircles.length, clueCount);
+ console.log(
+ `Will create ${circlesToShow} clue points from sorted circles`
);
+
+ const svgNamespace = "http://www.w3.org/2000/svg";
+
+ // Create a dedicated overlay group for clue points to ensure they're on top
+ let overlayGroup = svgElement.querySelector(".clue-points-overlay");
+ if (!overlayGroup) {
+ overlayGroup = document.createElementNS(svgNamespace, "g");
+ overlayGroup.setAttribute("class", "clue-points-overlay");
+ overlayGroup.style.pointerEvents = "all";
+ // Append to the end of SVG to ensure it's rendered last (on top)
+ svgElement.appendChild(overlayGroup);
+ }
+
+ // Create interactive clue points by cloning and enhancing each original circle
+ for (let i = 0; i < circlesToShow; i++) {
+ const originalCircle = sortedCircles[i];
+
+ // Get original circle attributes
+ const cx = originalCircle.getAttribute("cx") || "0";
+ const cy = originalCircle.getAttribute("cy") || "0";
+ const r = Math.max(
+ parseFloat(originalCircle.getAttribute("r") || "5"),
+ 12
+ );
+
+ // Create a click area (larger invisible circle for easier clicking)
+ const clickArea = document.createElementNS(svgNamespace, "circle");
+ clickArea.setAttribute("cx", cx);
+ clickArea.setAttribute("cy", cy);
+ clickArea.setAttribute("r", (r * 2).toString());
+ clickArea.setAttribute("class", "clue-click-area");
+ clickArea.setAttribute("data-clue-id", (i + 1).toString());
+ clickArea.setAttribute("data-index", i.toString());
+ // Ensure click area has high z-index
+ clickArea.style.zIndex = "1000";
+
+ // Create a new visible circle for the clue point
+ const cluePoint = document.createElementNS(svgNamespace, "circle");
+ cluePoint.setAttribute("cx", cx);
+ cluePoint.setAttribute("cy", cy);
+ cluePoint.setAttribute("r", r.toString());
+ cluePoint.setAttribute("class", "clue-point clue-point-locked");
+ cluePoint.setAttribute("data-clue-id", (i + 1).toString());
+ cluePoint.setAttribute("data-index", i.toString());
+ // Ensure clue point has high z-index
+ cluePoint.style.zIndex = "1001";
+
+ // Add a tooltip for accessibility
+ const tooltip = document.createElementNS(svgNamespace, "title");
+ tooltip.textContent = `Clue #${i + 1}`;
+ cluePoint.appendChild(tooltip);
+
+ // Add the new elements to the overlay group instead of original parent
+ overlayGroup.appendChild(clickArea);
+ overlayGroup.appendChild(cluePoint);
+ }
+
+ console.log(`Successfully created ${circlesToShow} clue points`);
+ return true;
+ } catch (error) {
+ console.error("Error fetching clue count:", error);
+ return false;
}
-
- 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;
@@ -73,62 +221,92 @@ export async function populateMap(svgElement) {
}
/**
- * Updates the status of each clue point based on clues data
+ * Updates the clue points based on the latest clue 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;
+ if (!svgElement || !clues || !Array.isArray(clues) || clues.length === 0) {
+ console.error("Invalid parameters for updateCluePointStatus");
+ return false;
}
- // Find the first unfound clue (the current one)
- const currentClueIndex = clues.findIndex((clue) => !clue.found);
- const currentClueId =
- currentClueIndex !== -1 ? clues[currentClueIndex].id : null;
+ console.log(`Updating status for ${clues.length} clues`);
- // Process each clue point
- cluePoints.forEach((circle, index) => {
+ // Find all clue points
+ const cluePoints = svgElement.querySelectorAll(".clue-point");
+ const clickAreas = svgElement.querySelectorAll(".clue-click-area");
+
+ if (cluePoints.length === 0) {
+ console.error("No clue points found to update");
+ return false;
+ }
+
+ console.log(
+ `Found ${cluePoints.length} clue points and ${clickAreas.length} click areas to update`
+ );
+
+ // Find the first unfound clue (the current clue)
+ const currentIndex = clues.findIndex((clue) => !clue.found);
+ const currentClueId = currentIndex !== -1 ? clues[currentIndex].id : null;
+
+ console.log(`Current clue ID: ${currentClueId}`);
+
+ // Update each clue point's status
+ cluePoints.forEach((point, index) => {
if (index >= clues.length) return;
const clue = clues[index];
const clueId = clue.id;
- // Determine status and apply appropriate color
+ // Remove all status classes first
+ point.classList.remove(
+ "clue-point-found",
+ "clue-point-current",
+ "clue-point-skipped",
+ "clue-point-locked"
+ );
+
+ // Update tooltip
+ const tooltip =
+ point.querySelector("title") ||
+ document.createElementNS("http://www.w3.org/2000/svg", "title");
+
+ // Determine the clue's status and apply appropriate class
if (clue.found) {
- // Found/completed clue - Dark Green
- circle.style.fill = "#2e7d32"; // Dark green
+ point.classList.add("clue-point-found");
+ tooltip.textContent = `Clue ${clueId}: ${clue.title || "Completed"}`;
} else if (clueId === currentClueId) {
- // Current clue - Orange
- circle.style.fill = "#ff9800"; // Orange
+ point.classList.add("clue-point-current");
+ tooltip.textContent = `Current Clue: ${
+ clue.title || "Go to this location"
+ }`;
} else if (clueId < currentClueId) {
- // Skipped clue - Blue
- circle.style.fill = "#1976d2"; // Blue
+ point.classList.add("clue-point-skipped");
+ tooltip.textContent = `Skipped Clue ${clueId}: ${
+ clue.title || "Skipped"
+ }`;
} else {
- // Not accessible yet - Grey
- circle.style.fill = "#9e9e9e"; // Grey
+ point.classList.add("clue-point-locked");
+ tooltip.textContent = `Locked: Complete previous clues first`;
}
- // Mark whether this clue point is available
+ // Store availability data
const isAvailable = clue.found || clueId === currentClueId;
- circle.setAttribute("data-available", isAvailable.toString());
+ point.setAttribute("data-available", isAvailable.toString());
+
+ // Make sure tooltip is attached
+ if (!point.contains(tooltip)) {
+ point.appendChild(tooltip);
+ }
});
- console.log("Clue points updated successfully");
+ console.log("Clue point status updated successfully");
+ return true;
} catch (error) {
- console.error("Error updating clue status:", error);
+ console.error("Error updating clue point status:", error);
+ return false;
}
}
@@ -139,39 +317,89 @@ export function updateCluePointStatus(svgElement, clues) {
* @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");
+ if (!svgElement || !clues || !navigationHandler) {
+ console.error("Invalid parameters for attachClueClickHandlers");
+ return false;
+ }
- cluePoints.forEach((circle, index) => {
- if (index >= clues.length) return;
+ // Get all interactive elements (clue points and their click areas)
+ const cluePoints = Array.from(svgElement.querySelectorAll(".clue-point"));
+ const clickAreas = Array.from(
+ svgElement.querySelectorAll(".clue-click-area")
+ );
+
+ console.log(
+ `Attaching click handlers to ${cluePoints.length} clue points and ${clickAreas.length} click areas`
+ );
+
+ // Find the current active clue
+ const currentIndex = clues.findIndex((clue) => !clue.found);
+
+ // Helper function to process a click on a clue point
+ const processClueClick = (element) => {
+ const index = parseInt(element.getAttribute("data-index"), 10);
+ if (isNaN(index) || index < 0 || index >= clues.length) {
+ console.error(`Invalid clue index: ${index}`);
+ return;
+ }
const clue = clues[index];
- const isAvailable = circle.getAttribute("data-available") === "true";
+ const isAvailable = clue.found || index === currentIndex;
- // Set cursor style based on availability
- circle.style.cursor = isAvailable ? "pointer" : "not-allowed";
+ console.log(`Clue ${clue.id} clicked, available: ${isAvailable}`);
- // Add click handler
- circle.onclick = (e) => {
+ if (isAvailable) {
+ // Show click feedback
+ const cluePoint = cluePoints[index];
+ if (cluePoint) {
+ // Briefly change color for visual feedback
+ const originalClasses = [...cluePoint.classList];
+ cluePoint.classList.add("click-feedback");
+
+ setTimeout(() => {
+ // Navigate to the clue page
+ navigationHandler(clue.id);
+ }, 100);
+ } else {
+ navigationHandler(clue.id);
+ }
+ } else {
+ // Alert about locked clue
+ alert("Complete previous clues to unlock this one!");
+ }
+ };
+
+ // Attach new click handlers to clue points
+ cluePoints.forEach((point) => {
+ // Remove existing handlers
+ point.onclick = null;
+
+ // Add new click handler
+ point.addEventListener("click", (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
- }
- };
+ processClueClick(point);
+ });
});
+
+ // Attach click handlers to click areas (larger invisible circles)
+ clickAreas.forEach((area) => {
+ // Remove existing handlers
+ area.onclick = null;
+
+ // Add new click handler - these use the same handler as clue points
+ area.addEventListener("click", (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ processClueClick(area);
+ });
+ });
+
+ console.log("Click handlers attached successfully");
+ return true;
} catch (error) {
console.error("Error attaching click handlers:", error);
+ return false;
}
}
diff --git a/utils/map-visibility.js b/utils/map-visibility.js
new file mode 100644
index 0000000..e69de29