// Firebase Configuration
const firebaseConfig = {
apiKey: "AIzaSyB5NffQxssjZKEAUyp8Wyt2Y3rJmJOVfx4",
authDomain: "holdspot-977d3.firebaseapp.com",
projectId: "holdspot-977d3",
storageBucket: "holdspot-977d3.firebasestorage.app",
messagingSenderId: "969993737434",
appId: "1:969993737434:web:17f43caab0bf46966701e3"
};
// Initialize Firebase
firebase.initializeApp(firebaseConfig);
const db = firebase.firestore();
// Icon Colors
const iconColors = {
'fa-tag': '#6d6875',
'fa-shopping-cart': '#1e3a8a',
'fa-right-left': '#5a189a',
'fa-gift': '#d00000',
'fa-file-contract': '#007200',
'fa-plane': '#023e8a',
'fa-bed': '#6d4c41',
'fa-map-marker-alt': '#e63946',
'fa-users': '#4a1e6b',
'fa-road': '#386641',
'fa-briefcase': '#003049',
'fa-user-tie': '#283618',
'fa-person-digging': '#2d6a4f',
'fa-heart': '#9d0208',
'fa-lightbulb': '#ff8500',
'fa-palette': '#9d4edd',
'fa-film': '#560bad',
'fa-hiking': '#004b23',
'fa-glass-cheers': '#ff6d00',
'fa-star': '#ffb700',
'fa-exclamation-triangle': '#dc2f02',
'fa-eye': '#480ca8',
'fa-clipboard-list': '#495057',
'fa-compass': '#774936',
'fa-bolt': '#ffd000'
};
// Subcategory Names
const subcategoryNames = {
'fa-tag': 'Sell',
'fa-shopping-cart': 'Buy',
'fa-right-left': 'Exchange',
'fa-gift': 'Gifts',
'fa-file-contract': 'Lease',
'fa-plane': 'Travel',
'fa-bed': 'Accommodation',
'fa-map-marker-alt': 'Places',
'fa-users': 'Communities',
'fa-road': 'Trips',
'fa-briefcase': 'Jobs',
'fa-user-tie': 'Professional',
'fa-concierge-bell': 'Services',
'fa-heart': 'Dating',
'fa-lightbulb': 'Ideas',
'fa-palette': 'Arts',
'fa-film': 'Entertainment',
'fa-hiking': 'Recreational',
'fa-glass-cheers': 'Gatherings',
'fa-star': 'Experiences',
'fa-exclamation-triangle': 'Alerts',
'fa-eye': 'Monitor',
'fa-clipboard-list': 'Reports',
'fa-compass': 'Orientation',
'fa-bolt': 'Actions'
};
// Icon Categories
const iconCategories = {
'Transactions': [
'fa-tag',
'fa-shopping-cart',
'fa-right-left',
'fa-gift',
'fa-file-contract'
],
'Movement': [
'fa-plane',
'fa-bed',
'fa-map-marker-alt',
'fa-users',
'fa-road'
],
'Connections': [
'fa-briefcase',
'fa-user-tie',
'fa-concierge-bell',
'fa-heart',
'fa-lightbulb'
],
'Leisure': [
'fa-palette',
'fa-film',
'fa-hiking',
'fa-glass-cheers',
'fa-star'
],
'Oversight': [
'fa-exclamation-triangle',
'fa-eye',
'fa-clipboard-list',
'fa-compass',
'fa-bolt'
]
};
// Archetype Mapping
const archetype = {
'Transactions': 'MERCHANT',
'Movement': 'EXPLORER',
'Connections': 'DIPLOMAT',
'Leisure': 'ARTISAN',
'Oversight': 'SENTINEL'
};
// Abilities Data
const abilitiesData = [
{ id: 'foundation', name: 'First Foundation', icon: 'fa-home', requirement: '2 markers created', effect: '+1 random token when create markers', prerequisite: [], row: 1, col: 1 },
{ id: 'territoryClaim', name: 'Territory Claim', icon: 'fa-flag', requirement: 'Subtract/add 10 Visibility points in battles', effect: 'Winning/losing in battles subtract/add +1 Visibility', prerequisite: ['foundation'], row: 2, col: 1 },
{ id: 'branchingPaths', name: 'Branching Paths', icon: 'fa-code-branch', requirement: 'Create 5 markers in the same category', effect: '+1 Visibility for new markers in Archetype', prerequisite: ['foundation'], row: 2, col: 2 },
{ id: 'hybridization', name: 'Wild Hybridization', icon: 'fa-dna', requirement: 'Gain 25 points in any metric', effect: '+1 Visibility for new markers', prerequisite: ['foundation'], row: 2, col: 3 },
{ id: 'headhunter', name: 'Lethal Headhunter', icon: 'fa-crosshairs', requirement: 'Win 25 battles', effect: 'Battle cooldown reduced by 10%', prerequisite: ['territoryClaim', 'branchingPaths'], row: 3, col: 1 },
{ id: 'mediator', name: 'Lost Mediator', icon: 'fa-balance-scale', requirement: 'Lost 25 battles', effect: 'Win chances in battle increased by 5%', prerequisite: ['territoryClaim', 'branchingPaths'], row: 3, col: 2 },
{ id: 'grandmaster', name: 'Gory Grandmaster', icon: 'fa-crown', requirement: 'Battle 10 Archetype markers', effect: 'Gain +1 random token when battle against Archetype markers', prerequisite: ['branchingPaths', 'hybridization'], row: 3, col: 3 },
{ id: 'manipulator', name: 'Tricky Manipulator', icon: 'fa-chess', requirement: 'Create 10 Archetype markers', effect: 'Gain +1 random token when create Archetype markers', prerequisite: ['branchingPaths', 'hybridization'], row: 3, col: 4 },
{ id: 'networker', name: 'Logical Networker', icon: 'fa-network-wired', requirement: 'Gain 25 in Link/Mail metric', effect: 'Gain +1 random token when click first time on Link or Mail markers', prerequisite: ['hybridization'], row: 3, col: 5 },
{ id: 'matchmaker', name: 'Lovely Matchmaker', icon: 'fa-heart', requirement: 'Gain 25 in Likes/Fav metric', effect: 'Gain +1 random token when first time click on Like or Fav markers', prerequisite: ['hybridization'], row: 3, col: 6 },
{ id: 'tactician', name: 'Brutal Tactician', icon: 'fa-brain', requirement: 'Lost 50 battles', effect: 'Gain +5 random tokens on each battle lost', prerequisite: ['headhunter', 'mediator'], row: 4, col: 1 },
{ id: 'battleExpert', name: 'Battle Expert', icon: 'fa-shield-alt', requirement: 'Win 50 battles', effect: 'Win chances in battle increased by 10%', prerequisite: ['headhunter', 'mediator'], row: 4, col: 2 },
{ id: 'pathfinder', name: 'Smart Pathfinder', icon: 'fa-compass', requirement: 'Subtract/add +10 Visibility points in battles', effect: 'Winning/losing in battles subtract/add +5 more Visibility', prerequisite: ['mediator', 'grandmaster'], row: 4, col: 3 },
{ id: 'overlord', name: 'Absolute Overlord', icon: 'fa-skull', requirement: 'Gain 50 in Share metric', effect: 'Gain +5 random tokens when first time click on Share for Archetype markers', prerequisite: ['grandmaster', 'manipulator'], row: 4, col: 4 },
{ id: 'emissary', name: 'Gloomy Emissary', icon: 'fa-dove', requirement: 'Lose against 50 non Archetype markers', effect: 'Gain +10 random tokens on each battle lost against non Archetype markers', prerequisite: ['manipulator', 'networker'], row: 4, col: 5 },
{ id: 'kingpin', name: 'Rude Kingpin', icon: 'fa-user-tie', requirement: 'Create 100 non Archetype markers', effect: 'Gain +10 random token when create markers that are not Archetype', prerequisite: ['networker', 'matchmaker'], row: 4, col: 6 },
{ id: 'hybridWarrior', name: 'Hybrid Warrior', icon: 'fa-fist-raised', requirement: 'Win against 50 non Archetype markers', effect: 'Gain +10 random tokens on each battle victory against non Archetype markers', prerequisite: ['matchmaker'], row: 4, col: 7 },
{ id: 'virtuoso', name: 'Imperious Virtuoso', icon: 'fa-star', requirement: 'Own over 500 points in Visibility', effect: 'Markers gain +1 Visibility when first time click on Like/Fav/Link/Mail or Share', prerequisite: ['matchmaker'], row: 4, col: 8 },
{ id: 'arbitrator', name: 'Arcane Arbitrator', icon: 'fa-gavel', requirement: 'Spend 250 tokens on shop items', effect: 'Shop items that influence battles have 25% increased effect duration', prerequisite: ['tactician', 'battleExpert'], row: 5, col: 1 },
{ id: 'legend', name: 'Undying Legend', icon: 'fa-trophy', requirement: 'Win 100 battles', effect: 'Gain +10 random tokens on each battle victory', prerequisite: ['battleExpert', 'pathfinder'], row: 5, col: 2 },
{ id: 'quartermaster', name: 'Tyrannical Quartermaster', icon: 'fa-box', requirement: 'Lose 100 battles', effect: 'Gain +10 random tokens on each battle lost', prerequisite: ['pathfinder', 'overlord'], row: 5, col: 3 },
{ id: 'commander', name: 'Sovereign Commander', icon: 'fa-chess-king', requirement: 'Win/Lose battle ratio is above 50', effect: 'Win chances in battles increased by 25%', prerequisite: ['overlord', 'emissary'], row: 5, col: 4 },
{ id: 'titan', name: 'Primordial Titan', icon: 'fa-mountain', requirement: 'Gain 250 points in Interaction metric', effect: 'Gain +10 random token and add +5 Visibility when first time click on markers', prerequisite: ['emissary', 'kingpin'], row: 5, col: 5 },
{ id: 'dominator', name: 'Omniscient Dominator', icon: 'fa-crown', requirement: 'Subtract/add 100 Visibility points in battles', effect: 'Winning/losing in battles subtract/add +10 more Visibility', prerequisite: ['kingpin', 'hybridWarrior'], row: 5, col: 6 },
{ id: 'visionary', name: 'Remote Visionary', icon: 'fa-eye', requirement: 'Gain 500 points in any metric', effect: 'Gain +10 random token when first time click on Like/Fav/Link/Mail or Share', prerequisite: ['hybridWarrior', 'virtuoso'], row: 5, col: 7 },
{ id: 'expert', name: 'Trustfull Expert', icon: 'fa-award', requirement: 'Create 50 Archetype markers', effect: '+10 random token when create Archetype markers', prerequisite: ['virtuoso'], row: 5, col: 8 },
{ id: 'pioneer', name: 'Spiritual Pioneer', icon: 'fa-flag', requirement: 'Create 100 markers', effect: '+10 random token when create markers', prerequisite: ['virtuoso'], row: 5, col: 9 },
{ id: 'mastermind', name: 'Intimate Mastermind', icon: 'fa-brain', requirement: 'Buy 50 items in shop', effect: 'Shop items are now 25% cheaper', prerequisite: [], row: 5, col: 10 }
];
// Shop Items
const shopItems = {
potions: [
{ id: 'potion1', name: 'Victory Elixir', value: 25, price: 250, time: 9000, type: 'liquidity', effect: '+25% win chance', description: 'Increases battle win chances temporarily' },
{ id: 'potion2', name: 'Archetype Brew', value: 25, price: 250, time: 9000, type: 'value', effect: '+25% win chance against Archetype markers', description: 'Increases chance to battle Archetype markers' },
{ id: 'potion3', name: 'Battle Serum', value: 5, price: 250, time: 9000, type: 'engagement', effect: 'x5 Visibility on battles', description: 'Improve battle gains' },
{ id: 'potion4', name: 'Holy Water', value: 50, price: 1500, time: 18000, type: 'value', effect: 'Each win in battles give additional 50 random tokens', description: 'Gain tokens in battles' }
],
utilities: [
{ id: 'util1', name: 'Visibility Boost', price: 500, type: 'liquidity', effect: '+100 Visibility to owned marker', description: 'Increase marker visibility' },
{ id: 'util2', name: 'Marker Relocator', price: 500, type: 'value', effect: 'Move owned marker to new location', description: 'Relocate your markers' },
{ id: 'util3', name: 'Marker Eraser', price: 500, type: 'engagement', effect: 'Delete owned marker', description: 'Remove your markers' },
{ id: 'util4', name: 'Visibility Crusher', price: 1000, type: 'value', effect: '-100 Visibility to any marker', description: 'Decrease any marker visibility' },
{ id: 'util5', name: 'Exchange Master', price: 1000, type: 'value', effect: 'Reset exchange cooldown', description: 'Reset exchange cooldown' }
],
boosters: [
{ id: 'boost1', name: 'Token Amplifier', value: 3, price: 1000, time: 6000, type: 'value', effect: 'Token gain tripled', description: 'Boost token gains' },
{ id: 'boost2', name: 'Abilities Surge', value: 3, price: 1000, time: 6000, type: 'engagement', effect: 'Abilities effects tripled', description: 'Boost abilities gains' },
{ id: 'boost3', name: 'Experience Multiplier', value: 2, price: 1000, time: 6000, type: 'liquidity', effect: 'Metric gain ', description: 'Double metric progression' },
{ id: 'boost4', name: 'Total Conquerer', value: 20, price: 2000, time: 6000, type: 'value', effect: 'Visibility from all sources increased', description: 'Visibility killer' }
]
};
// Global Variables
let map;
let markers = {};
let currentUser = null;
let userData = {
markersCreated: 0,
archetypeMarkersCreated: 0,
likesGiven: 0,
favoritesMade: 0,
interactions: 0,
shareClicked: 0,
linksClicked: 0,
emailsClicked: 0,
archetype: '',
liquidityTokensGained: 0,
liquidityTokensSpent: 0,
valueTokensGained: 0,
valueTokensSpent: 0,
engagementTokensGained: 0,
engagementTokensSpent: 0,
battlesWon: 0,
battlesLost: 0,
battleVisibilityChanged: 0,
battleWinRatio: 0,
engagement: 0,
lastInteraction: Date.now(),
abilities: [],
activeItems: [],
lastExchange: 0,
lastBattle: 0
};
let selectedMarker = null;
let addingMarker = false;
let filteredMarkers = [];
let utilityMode = null;
let utilityTarget = null;
// Initialize App
document.addEventListener('DOMContentLoaded', async () => {
initMap();
await initUser();
setupEventListeners();
loadUserData();
subscribeToMarkers();
// Initial token display
updateTokenDisplay();
});
// Initialize Map
function initMap() {
map = L.map('map', {
zoomControl: false,
attributionControl: false,
cursor: 'default',
doubleClickZoom: false,
minZoom: 2,
maxZoom: 10,
maxBounds: [[-60, -120], [60, 120]],
maxBoundsViscosity: 1,
center: [20, 0],
zoom: 2
});
L.tileLayer('https://{s}.basemaps.cartocdn.com/light_nolabels/{z}/{x}/{y}{r}.png', {
attribution: '© OpenStreetMap, © CartoDB',
subdomains: 'abcd',
minZoom: 2,
maxZoom: 10
}).addTo(map);
map.on('click', handleMapClick);
}
// Initialize User
async function initUser() {
// Check if user exists in localStorage
let userId = localStorage.getItem('userId');
if (!userId) {
// Generate new UUID
userId = generateUUID();
localStorage.setItem('userId', userId);
}
currentUser = {
id: userId,
liquidityTokens: 0,
valueTokens: 0,
engagementTokens: 0
};
// Check if user exists in Firestore
const userDoc = await db.collection('users').doc(userId).get();
if (!userDoc.exists) {
// Create new user document
await db.collection('users').doc(userId).set({
userID: userId,
liquidityTokens: 0,
valueTokens: 0,
engagementTokens: 0
});
} else {
// Load user data
const data = userDoc.data();
currentUser.liquidityTokens = data.liquidityTokens || 0;
currentUser.valueTokens = data.valueTokens || 0;
currentUser.engagementTokens = data.engagementTokens || 0;
}
}
// Generate UUID
function generateUUID() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
const r = Math.random() * 16 | 0;
const v = c == 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
}
// Setup Event Listeners
function setupEventListeners() {
// Theme Toggle
document.getElementById('theme-toggle').addEventListener('click', toggleTheme);
// Search Bar
document.getElementById('search-bar').addEventListener('input', handleSearch);
// Action Buttons
document.getElementById('dashboard-btn').addEventListener('click', () => openModal('dashboard'));
document.getElementById('battle-btn').addEventListener('click', () => openModal('battle'));
document.getElementById('shop-btn').addEventListener('click', () => openModal('shop'));
document.getElementById('add-marker-btn').addEventListener('click', startAddMarker);
// Modal Overlay
document.getElementById('modal-overlay').addEventListener('click', closeModal);
// Add Marker Form
document.getElementById('add-marker-form').addEventListener('submit', handleAddMarker);
// Dashboard Tabs
document.querySelectorAll('.tab-button').forEach(button => {
button.addEventListener('click', () => switchTab('dashboard', button.dataset.tab));
});
// Analytics Tabs
document.querySelectorAll('.analytics-tab-button').forEach(button => {
button.addEventListener('click', () => switchAnalyticsTab(button.dataset.analyticsTab));
});
// Shop Tabs
document.querySelectorAll('.shop-tab-button').forEach(button => {
button.addEventListener('click', () => switchTab('shop', button.dataset.shopTab));
});
// Battle Button
document.getElementById('start-battle-btn').addEventListener('click', startBattle);
// Category Selection for Add Marker
document.getElementById('marker-category').addEventListener('change', populateSubcategories);
// Populate initial subcategories
populateSubcategories();
}
// Toggle Theme
function toggleTheme() {
document.documentElement.classList.toggle('dark');
localStorage.setItem('theme', document.documentElement.classList.contains('dark') ? 'dark' : 'light');
}
// Handle Search
function handleSearch(e) {
const query = e.target.value.toLowerCase();
filteredMarkers = Object.values(markers).filter(marker =>
marker.title.toLowerCase().includes(query) ||
marker.body.toLowerCase().includes(query) ||
marker.subcategory.toLowerCase().includes(query)
);
// Update map markers
updateMapMarkers();
}
// Open Modal
function openModal(modalType) {
document.body.classList.add('modal-active');
document.getElementById('modal-overlay').classList.remove('hidden');
document.getElementById(`${modalType}-modal`).classList.remove('hidden');
// Load modal content
switch(modalType) {
case 'dashboard':
loadDashboardContent();
break;
case 'shop':
loadShopContent();
break;
case 'battle':
loadBattleContent();
break;
}
}
// Close Modal
function closeModal() {
document.body.classList.remove('modal-active');
document.getElementById('modal-overlay').classList.add('hidden');
document.querySelectorAll('[id$="-modal"]').forEach(modal => {
modal.classList.add('hidden');
});
// Reset utility mode if active
if (utilityMode) {
utilityMode = null;
utilityTarget = null;
showNotification('Utility mode cancelled', 'info');
updateMapMarkers();
}
}
// Switch Tab
function switchTab(modalType, tabName) {
// Hide all tabs
document.querySelectorAll(`#${modalType}-modal .tab-content`).forEach(tab => {
tab.classList.add('hidden');
});
// Remove active class from all buttons
document.querySelectorAll(`#${modalType}-modal .tab-button`).forEach(button => {
button.classList.remove('active');
});
// Show selected tab
document.getElementById(`${tabName}-tab`).classList.remove('hidden');
// Add active class to selected button
document.querySelector(`#${modalType}-modal .tab-button[data-tab="${tabName}"]`).classList.add('active');
}
// Switch Analytics Tab
function switchAnalyticsTab(tabName) {
// Hide all charts
document.querySelectorAll('.analytics-chart-container').forEach(container => {
container.classList.add('hidden');
});
// Remove active class from all buttons
document.querySelectorAll('.analytics-tab-button').forEach(button => {
button.classList.remove('active');
});
// Show selected chart
document.getElementById(`${tabName}-chart-container`).classList.remove('hidden');
// Add active class to selected button
document.querySelector(`.analytics-tab-button[data-analytics-tab="${tabName}"]`).classList.add('active');
// Load chart data
loadChartData(tabName);
}
// Handle Map Click
function handleMapClick(e) {
if (addingMarker) {
// Place marker
openModal('add-marker');
addingMarker = false;
document.getElementById('map').style.cursor = 'default';
}
}
// Start Add Marker Process
function startAddMarker() {
addingMarker = true;
document.getElementById('map').style.cursor = 'crosshair';
showNotification('Click on the map to place your marker', 'info');
}
// Populate Subcategories
function populateSubcategories() {
const category = document.getElementById('marker-category').value;
const subcategorySelect = document.getElementById('marker-subcategory');
// Clear existing options
subcategorySelect.innerHTML = '';
if (category && iconCategories[category]) {
iconCategories[category].forEach(icon => {
const option = document.createElement('option');
option.value = icon;
option.textContent = subcategoryNames[icon];
subcategorySelect.appendChild(option);
});
}
}
// Handle Add Marker Form Submission
async function handleAddMarker(e) {
e.preventDefault();
// Get form values
const title = document.getElementById('marker-title-input').value;
const body = document.getElementById('marker-body-input').value;
const category = document.getElementById('marker-category').value;
const subcategory = document.getElementById('marker-subcategory').value;
const email = document.getElementById('marker-email').value;
const link = document.getElementById('marker-link').value;
// Validate inputs
if (!title || !body || !category || !subcategory) {
showNotification('Please fill in all required fields', 'error');
return;
}
// Get map center for marker position
const center = map.getCenter();
// Calculate initial visibility based on abilities
let visibility = 100;
if (userData.abilities.includes('branchingPaths') && userData.archetype === archetype[category]) {
visibility += 1;
}
if (userData.abilities.includes('hybridization')) {
visibility += 1;
}
// Create marker object
const markerData = {
title,
body,
category,
subcategory,
email: email || null,
link: link || null,
lat: center.lat,
lng: center.lng,
likes: 0,
favorites: 0,
shares: 0,
userID: currentUser.id,
zDepth: visibility // Using zDepth as visibility
};
try {
// Save to Firestore
const docRef = await db.collection('markers').add(markerData);
// Update user metrics
userData.markersCreated++;
if (userData.archetype === archetype[category]) {
userData.archetypeMarkersCreated++;
}
// Award tokens based on abilities
let tokensAwarded = false;
if (userData.abilities.includes('foundation')) {
awardRandomToken();
tokensAwarded = true;
}
if (userData.abilities.includes('manipulator') && userData.archetype === archetype[category]) {
awardRandomToken();
tokensAwarded = true;
}
if (userData.abilities.includes('pioneer')) {
awardRandomToken();
tokensAwarded = true;
}
// Save user data
saveUserData();
// Update token display
updateTokenDisplay();
// Show success notification
showNotification(
`Marker created successfully! ${tokensAwarded ? 'Tokens awarded.' : ''}`,
'success'
);
// Close modal and reset form
closeModal();
document.getElementById('add-marker-form').reset();
} catch (error) {
console.error('Error creating marker:', error);
showNotification('Failed to create marker. Please try again.', 'error');
}
}
// Award Random Token
function awardRandomToken() {
const tokenTypes = ['liquidity', 'value', 'engagement'];
const tokenType = tokenTypes[Math.floor(Math.random() * tokenTypes.length)];
switch(tokenType) {
case 'liquidity':
currentUser.liquidityTokens++;
userData.liquidityTokensGained++;
break;
case 'value':
currentUser.valueTokens++;
userData.valueTokensGained++;
break;
case 'engagement':
currentUser.engagementTokens++;
userData.engagementTokensGained++;
break;
}
// Update Firestore
db.collection('users').doc(currentUser.id).update({
[`userData.${tokenType}Tokens`]: currentUser[`${tokenType}Tokens`]
});
}
// Load Dashboard Content
function loadDashboardContent() {
// Metrics Tab
const metricsTab = document.getElementById('metrics-tab');
metricsTab.innerHTML = `
Basic Metrics
Markers Created: ${userData.markersCreated}
Archetype Markers: ${userData.archetypeMarkersCreated}
Likes Given: ${userData.likesGiven}
Favorites Made: ${userData.favoritesMade}
Interactions: ${userData.interactions}
Engagement
Share Clicked: ${userData.shareClicked}
Links Clicked: ${userData.linksClicked}
Emails Clicked: ${userData.emailsClicked}
Archetype: ${userData.archetype || 'None'}
Engagement: ${userData.engagement}
Tokens
Liquidity: ${userData.liquidityTokensGained}/${userData.liquidityTokensSpent}
Value: ${userData.valueTokensGained}/${userData.valueTokensSpent}
Engagement: ${userData.engagementTokensGained}/${userData.engagementTokensSpent}
Battles
Won: ${userData.battlesWon}
Lost: ${userData.battlesLost}
Visibility Changed: ${userData.battleVisibilityChanged}
Win Ratio: ${userData.battleWinRatio.toFixed(2)}%
`;
// Analytics Tab - will be loaded when tab is switched
// Abilities Tab
loadAbilitiesContent();
// Exchange Tab
loadExchangeContent();
}
// Load Abilities Content
function loadAbilitiesContent() {
const abilitiesTab = document.getElementById('abilities-tab');
// Count active abilities
const activeCount = userData.abilities.length;
const inactiveCount = abilitiesData.length - activeCount;
// Create grid
let gridHTML = `
${activeCount} abilities active, ${inactiveCount} inactive
`;
// Sort abilities by row and column
const sortedAbilities = [...abilitiesData].sort((a, b) => {
if (a.row !== b.row) return a.row - b.row;
return a.col - b.col;
});
// Add abilities to grid
sortedAbilities.forEach(ability => {
const isActive = userData.abilities.includes(ability.id);
const isAvailable = checkAbilityPrerequisites(ability.prerequisite);
gridHTML += `
${ability.name}
`;
});
gridHTML += '
';
abilitiesTab.innerHTML = gridHTML;
}
// Check Ability Prerequisites
function checkAbilityPrerequisites(prerequisites) {
return prerequisites.every(req => userData.abilities.includes(req));
}
// Load Exchange Content
function loadExchangeContent() {
const exchangeTab = document.getElementById('exchange-tab');
// Calculate exchange rate (1:100/total markers)
const totalMarkers = Object.keys(markers).length;
const exchangeRate = totalMarkers > 0 ? (100 / totalMarkers).toFixed(2) : 0;
// Check cooldown
const now = Date.now();
const cooldownRemaining = Math.max(0, 86400000 - (now - userData.lastExchange));
const canExchange = cooldownRemaining <= 0;
exchangeTab.innerHTML = `
Current Exchange Rate: 1 token = ${exchangeRate} points
Total Markers on Map: ${totalMarkers}
Next exchange available in: ${formatTime(cooldownRemaining)}
`;
// Add event listener to exchange button
document.getElementById('exchange-btn').addEventListener('click', handleExchange);
}
// Format Time
function formatTime(ms) {
const seconds = Math.floor(ms / 1000);
const minutes = Math.floor(seconds / 60);
const hours = Math.floor(minutes / 60);
if (hours > 0) {
return `${hours}h ${minutes % 60}m`;
}
if (minutes > 0) {
return `${minutes}m ${seconds % 60}s`;
}
return `${seconds}s`;
}
// Handle Exchange
function handleExchange() {
const fromToken = document.getElementById('from-token').value;
const toToken = document.getElementById('to-token').value;
const amount = parseInt(document.getElementById('exchange-amount').value);
// Validate inputs
if (isNaN(amount) || amount <= 0) {
showNotification('Please enter a valid amount', 'error');
return;
}
// Check if user has enough tokens
if (currentUser[`${fromToken}Tokens`] < amount) {
showNotification('Insufficient tokens', 'error');
return;
}
// Calculate exchange rate
const totalMarkers = Object.keys(markers).length;
const exchangeRate = totalMarkers > 0 ? (100 / totalMarkers) : 0;
const receivedAmount = Math.floor(amount * exchangeRate);
// Perform exchange
currentUser[`${fromToken}Tokens`] -= amount;
currentUser[`${toToken}Tokens`] += receivedAmount;
// Update user data
userData[`${fromToken}TokensSpent`] += amount;
userData[`${toToken}TokensGained`] += receivedAmount;
userData.lastExchange = Date.now();
// Update Firestore
db.collection('users').doc(currentUser.id).update({
[`${fromToken}Tokens`]: currentUser[`${fromToken}Tokens`],
[`${toToken}Tokens`]: currentUser[`${toToken}Tokens`]
});
// Save user data
saveUserData();
// Update token display
updateTokenDisplay();
// Show success notification
showNotification(`Exchanged ${amount} ${fromToken} tokens for ${receivedAmount} ${toToken} tokens`, 'success');
// Reload exchange content
loadExchangeContent();
}
// Load Shop Content
function loadShopContent() {
// Potions Tab
loadShopTab('potions');
// Utilities Tab
loadShopTab('utilities');
// Boosters Tab
loadShopTab('boosters');
}
// Load Shop Tab
function loadShopTab(tabName) {
const tabElement = document.getElementById(`${tabName}-tab`);
const items = shopItems[tabName];
let html = '';
items.forEach(item => {
html += `
${item.name}
${item.description}
Effect: ${item.effect}
${item.value ? `
Value: ${item.value}
` : ''}
${item.time ? `
Duration: ${Math.floor(item.time/1000)}s
` : ''}
${item.price} ${item.type.charAt(0).toUpperCase() + item.type.slice(1)} Tokens
`;
});
tabElement.innerHTML = html;
// Add event listeners to buy buttons
document.querySelectorAll('.buy-btn').forEach(button => {
button.addEventListener('click', (e) => {
const itemId = e.target.dataset.itemId;
const itemType = e.target.dataset.itemType;
buyItem(itemId, itemType);
});
});
}
// Buy Item
function buyItem(itemId, itemType) {
const item = [...shopItems.potions, ...shopItems.utilities, ...shopItems.boosters]
.find(i => i.id === itemId);
// Check if user has enough tokens
if (currentUser[`${item.type}Tokens`] < item.price) {
showNotification('Insufficient tokens', 'error');
return;
}
// Deduct tokens
currentUser[`${item.type}Tokens`] -= item.price;
userData[`${item.type}TokensSpent`] += item.price;
// Update Firestore
db.collection('users').doc(currentUser.id).update({
[`${item.type}Tokens`]: currentUser[`${item.type}Tokens`]
});
// Save user data
saveUserData();
// Update token display
updateTokenDisplay();
// Handle item-specific actions
switch(itemType) {
case 'potions':
case 'boosters':
// Add to active items with expiration
userData.activeItems.push({
id: itemId,
type: itemType,
expiresAt: Date.now() + item.time
});
showNotification(`${item.name} activated!`, 'success');
break;
case 'utilities':
// Enter utility mode
utilityMode = itemId;
utilityTarget = null;
closeModal();
showNotification(`Select a marker to apply ${item.name}`, 'info');
updateMapMarkers();
break;
}
}
// Load Battle Content
function loadBattleContent() {
const battleInfo = document.getElementById('battle-info');
// Calculate battle stats
const totalMarkers = Object.keys(markers).length;
const archetypeMarkers = Object.values(markers).filter(m =>
archetype[m.category] === userData.archetype
).length;
// Calculate battle ratio
const battleRatio = totalMarkers > 0 ?
((userData.battlesWon + userData.battlesLost) / totalMarkers * 100).toFixed(1) : 0;
// Calculate win chance (base 50% with modifiers)
let winChance = 50;
// Apply potion modifiers
const victoryElixir = userData.activeItems.find(item => item.id === 'potion1');
if (victoryElixir) {
winChance += 25;
}
// Apply ability modifiers
if (userData.abilities.includes('mediator')) {
winChance += 5;
}
if (userData.abilities.includes('battleExpert')) {
winChance += 10;
}
if (userData.abilities.includes('commander')) {
winChance += 25;
}
// Clamp win chance between 5% and 95%
winChance = Math.max(5, Math.min(95, winChance));
// Check cooldown
const now = Date.now();
const cooldownRemaining = Math.max(0, 300000 - (now - userData.lastBattle));
const canBattle = cooldownRemaining <= 0;
battleInfo.innerHTML = `
Total Markers: ${totalMarkers}
Your Archetype Markers: ${archetypeMarkers}
Battle Ratio: ${battleRatio}%
Current Win Chance: ${winChance}%
Cooldown: ${formatTime(cooldownRemaining)}
`;
// Update cooldown timer
if (!canBattle) {
const cooldownInterval = setInterval(() => {
const remaining = Math.max(0, 300000 - (Date.now() - userData.lastBattle));
document.getElementById('battle-cooldown-inner').textContent = formatTime(remaining);
if (remaining <= 0) {
clearInterval(cooldownInterval);
document.getElementById('start-battle-btn-inner').disabled = false;
document.getElementById('start-battle-btn-inner').classList.remove('opacity-50', 'cursor-not-allowed');
}
}, 1000);
}
}
// Start Battle
async function startBattle() {
// Check cooldown
const now = Date.now();
if (now - userData.lastBattle < 300000) {
showNotification('Battle cooldown not ready yet', 'error');
return;
}
// Get all markers
const markerList = Object.values(markers);
if (markerList.length === 0) {
showNotification('No markers available to battle', 'error');
return;
}
// Select random marker
const targetMarker = markerList[Math.floor(Math.random() * markerList.length)];
// Determine win/loss (random with modifiers)
let winChance = 50;
// Apply potion modifiers
const victoryElixir = userData.activeItems.find(item => item.id === 'potion1');
if (victoryElixir) {
winChance += 25;
}
// Apply ability modifiers
if (userData.abilities.includes('mediator')) {
winChance += 5;
}
if (userData.abilities.includes('battleExpert')) {
winChance += 10;
}
if (userData.abilities.includes('commander')) {
winChance += 25;
}
// Clamp win chance
winChance = Math.max(5, Math.min(95, winChance));
// Determine result
const isWin = Math.random() * 100 < winChance;
// Update battle stats
if (isWin) {
userData.battlesWon++;
} else {
userData.battlesLost++;
}
// Update visibility
let visibilityChange = 0;
if (isWin) {
visibilityChange = -10;
if (userData.abilities.includes('territoryClaim')) {
visibilityChange -= 1;
}
if (userData.abilities.includes('pathfinder')) {
visibilityChange -= 5;
}
if (userData.abilities.includes('dominator')) {
visibilityChange -= 10;
}
} else {
visibilityChange = 10;
if (userData.abilities.includes('territoryClaim')) {
visibilityChange += 1;
}
if (userData.abilities.includes('pathfinder')) {
visibilityChange += 5;
}
if (userData.abilities.includes('dominator')) {
visibilityChange += 10;
}
}
userData.battleVisibilityChanged += Math.abs(visibilityChange);
// Update marker visibility
try {
await db.collection('markers').doc(targetMarker.id).update({
zDepth: Math.max(0, targetMarker.zDepth + visibilityChange)
});
} catch (error) {
console.error('Error updating marker visibility:', error);
}
// Award tokens for wins/losses
if (isWin) {
// Check for Holy Water effect
const holyWater = userData.activeItems.find(item => item.id === 'potion4');
if (holyWater) {
for (let i = 0; i < 50; i++) {
awardRandomToken();
}
}
// Check for abilities that give tokens on win
if (userData.abilities.includes('legend')) {
for (let i = 0; i < 10; i++) {
awardRandomToken();
}
}
// Check for Hybrid Warrior ability
if (userData.abilities.includes('hybridWarrior') &&
archetype[targetMarker.category] !== userData.archetype) {
for (let i = 0; i < 10; i++) {
awardRandomToken();
}
}
} else {
// Check for abilities that give tokens on loss
if (userData.abilities.includes('tactician')) {
for (let i = 0; i < 5; i++) {
awardRandomToken();
}
}
if (userData.abilities.includes('quartermaster')) {
for (let i = 0; i < 10; i++) {
awardRandomToken();
}
}
// Check for Emissary ability
if (userData.abilities.includes('emissary') &&
archetype[targetMarker.category] !== userData.archetype) {
for (let i = 0; i < 10; i++) {
awardRandomToken();
}
}
}
// Update last battle time
userData.lastBattle = now;
// Save user data
saveUserData();
// Update token display
updateTokenDisplay();
// Show battle result
const battleInfo = document.getElementById('battle-info');
battleInfo.innerHTML = `
${isWin ? 'Victory!' : 'Defeat!'}
You battled against:
${targetMarker.title}
Visibility changed by: ${visibilityChange > 0 ? '+' : ''}${visibilityChange}
`;
// Add event listener to close button
document.getElementById('close-battle-btn').addEventListener('click', closeModal);
// Reload battle content after delay
setTimeout(() => {
if (document.getElementById('battle-modal').classList.contains('hidden') === false) {
loadBattleContent();
}
}, 5000);
}
// Load Chart Data
function loadChartData(chartType) {
// This would typically load data from localStorage or Firestore
// For now, we'll create sample data
switch(chartType) {
case 'journey':
loadJourneyChart();
break;
case 'performance':
loadPerformanceChart();
break;
case 'battlegrounds':
loadBattlegroundsChart();
break;
}
}
// Load Journey Chart
function loadJourneyChart() {
const ctx = document.getElementById('journey-chart').getContext('2d');
// Sample data - in a real app this would come from user history
const labels = Array.from({length: 14}, (_, i) => `Day ${i+1}`);
const archetypeData = Array.from({length: 14}, () =>
Object.keys(archetype)[Math.floor(Math.random() * Object.keys(archetype).length)]
);
const battlesWon = Array.from({length: 14}, () => Math.floor(Math.random() * 10));
const battlesLost = Array.from({length: 14}, () => Math.floor(Math.random() * 10));
const valueTokens = Array.from({length: 14}, () => Math.floor(Math.random() * 50));
new Chart(ctx, {
type: 'bar',
data: {
labels: labels,
datasets: [
{
label: 'Battles Won',
data: battlesWon,
backgroundColor: 'rgba(75, 192, 192, 0.2)',
borderColor: 'rgba(75, 192, 192, 1)',
borderWidth: 1
},
{
label: 'Battles Lost',
data: battlesLost,
backgroundColor: 'rgba(255, 99, 132, 0.2)',
borderColor: 'rgba(255, 99, 132, 1)',
borderWidth: 1
},
{
label: 'Value Tokens Gained',
data: valueTokens,
type: 'line',
borderColor: 'rgba(153, 102, 255, 1)',
backgroundColor: 'rgba(153, 102, 255, 0.2)',
borderDash: [5, 5],
yAxisID: 'y1'
}
]
},
options: {
responsive: true,
scales: {
y: {
beginAtZero: true,
title: {
display: true,
text: 'Battles'
}
},
y1: {
position: 'right',
beginAtZero: true,
title: {
display: true,
text: 'Value Tokens'
},
grid: {
drawOnChartArea: false
}
}
}
}
});
}
// Load Performance Chart
function loadPerformanceChart() {
const ctx = document.getElementById('performance-chart').getContext('2d');
// Sample data
const data = Array.from({length: 20}, () => ({
x: Math.floor(Math.random() * 100),
y: Math.floor(Math.random() * 100),
r: Math.floor(Math.random() * 30) + 5,
archetype: Object.keys(archetype)[Math.floor(Math.random() * Object.keys(archetype).length)]
}));
new Chart(ctx, {
type: 'bubble',
data: {
datasets: [{
label: 'Daily Performance',
data: data,
backgroundColor: 'rgba(54, 162, 235, 0.5)'
}]
},
options: {
responsive: true,
scales: {
x: {
title: {
display: true,
text: 'Engagement'
}
},
y: {
title: {
display: true,
text: 'Battle Visibility Changed'
}
}
}
}
});
}
// Load Battlegrounds Chart
function loadBattlegroundsChart() {
const ctx = document.getElementById('battlegrounds-chart').getContext('2d');
// Sample data
const data = Array.from({length: 15}, () => ({
x: Math.random() * 100,
y: Math.random() * 2,
archetype: Object.keys(archetype)[Math.floor(Math.random() * Object.keys(archetype).length)]
}));
new Chart(ctx, {
type: 'scatter',
data: {
datasets: [{
label: 'Specialization vs. Battle Success',
data: data,
backgroundColor: 'rgba(255, 99, 132, 0.5)'
}]
},
options: {
responsive: true,
scales: {
x: {
title: {
display: true,
text: 'Archetype Markers Created (%)'
}
},
y: {
title: {
display: true,
text: 'Battle Win/Loss Ratio'
}
}
}
}
});
}
// Subscribe to Markers
function subscribeToMarkers() {
db.collection('markers').onSnapshot(snapshot => {
snapshot.docChanges().forEach(change => {
const doc = change.doc;
const data = {...doc.data(), id: doc.id};
if (change.type === 'added') {
addMarkerToMap(data);
} else if (change.type === 'modified') {
updateMarkerOnMap(data);
} else if (change.type === 'removed') {
removeMarkerFromMap(doc.id);
}
});
// Update filtered markers
if (document.getElementById('search-bar').value) {
handleSearch({target: {value: document.getElementById('search-bar').value}});
} else {
filteredMarkers = Object.values(markers);
}
});
}
// Add Marker to Map
function addMarkerToMap(markerData) {
// Create icon
const icon = L.divIcon({
className: 'marker-icon',
html: ``,
iconSize: [48, 48],
iconAnchor: [24, 24]
});
// Create marker
const marker = L.marker([markerData.lat, markerData.lng], {icon: icon});
// Add click event
marker.on('click', () => openMarkerDetails(markerData));
// Add to map
marker.addTo(map);
// Store reference
markers[markerData.id] = {
...markerData,
leafletMarker: marker
};
}
// Update Marker on Map
function updateMarkerOnMap(markerData) {
// Remove old marker if exists
if (markers[markerData.id]) {
map.removeLayer(markers[markerData.id].leafletMarker);
}
// Add updated marker
addMarkerToMap(markerData);
// If this was the selected marker, update details
if (selectedMarker && selectedMarker.id === markerData.id) {
selectedMarker = markerData;
if (!document.getElementById('marker-modal').classList.contains('hidden')) {
openMarkerDetails(markerData);
}
}
}
// Remove Marker from Map
function removeMarkerFromMap(markerId) {
if (markers[markerId]) {
map.removeLayer(markers[markerId].leafletMarker);
delete markers[markerId];
}
}
// Update Map Markers (for filtering)
function updateMapMarkers() {
// Clear all markers
Object.values(markers).forEach(marker => {
map.removeLayer(marker.leafletMarker);
});
// Add filtered markers
(filteredMarkers.length > 0 ? filteredMarkers : Object.values(markers)).forEach(marker => {
// Check if in utility mode and owned by user
if (utilityMode && marker.userID === currentUser.id) {
// Highlight owned markers
const icon = L.divIcon({
className: 'marker-icon',
html: ``,
iconSize: [48, 48],
iconAnchor: [24, 24]
});
marker.leafletMarker.setIcon(icon);
} else if (utilityMode) {
// Dim non-owned markers
const icon = L.divIcon({
className: 'marker-icon',
html: ``,
iconSize: [48, 48],
iconAnchor: [24, 24]
});
marker.leafletMarker.setIcon(icon);
} else {
// Normal icon
const icon = L.divIcon({
className: 'marker-icon',
html: ``,
iconSize: [48, 48],
iconAnchor: [24, 24]
});
marker.leafletMarker.setIcon(icon);
}
marker.leafletMarker.addTo(map);
// Add click handler if not in utility mode
if (!utilityMode) {
marker.leafletMarker.off('click').on('click', () => openMarkerDetails(marker));
} else if (marker.userID === currentUser.id) {
// Add utility click handler
marker.leafletMarker.off('click').on('click', () => handleUtilityMarkerClick(marker));
}
});
}
// Open Marker Details
function openMarkerDetails(markerData) {
selectedMarker = markerData;
// Update modal content
document.getElementById('marker-title').textContent = markerData.title;
// Create breadcrumbs
const breadcrumbs = document.getElementById('marker-breadcrumbs');
breadcrumbs.innerHTML = `
${markerData.category} >
${subcategoryNames[markerData.subcategory]} >
Visibility: ${markerData.zDepth} >
Likes: ${markerData.likes} >
Favorites: ${markerData.favorites}
`;
document.getElementById('marker-body').textContent = markerData.body;
// Create action buttons
const actions = document.getElementById('marker-actions');
actions.innerHTML = '';
// Only show actions that exist
if (markerData.link) {
const linkBtn = document.createElement('button');
linkBtn.className = 'p-2 rounded-full bg-gray-200 dark:bg-gray-700 hover:bg-gray-300 dark:hover:bg-gray-600';
linkBtn.innerHTML = '';
linkBtn.addEventListener('click', () => {
window.open(markerData.link, '_blank');
userData.linksClicked++;
userData.interactions++;
saveUserData();
updateTokenDisplay();
});
actions.appendChild(linkBtn);
}
if (markerData.email) {
const emailBtn = document.createElement('button');
emailBtn.className = 'p-2 rounded-full bg-gray-200 dark:bg-gray-700 hover:bg-gray-300 dark:hover:bg-gray-600';
emailBtn.innerHTML = '';
emailBtn.addEventListener('click', () => {
window.location.href = `mailto:${markerData.email}`;
userData.emailsClicked++;
userData.interactions++;
saveUserData();
updateTokenDisplay();
});
actions.appendChild(emailBtn);
}
const favoriteBtn = document.createElement('button');
favoriteBtn.className = 'p-2 rounded-full bg-gray-200 dark:bg-gray-700 hover:bg-gray-300 dark:hover:bg-gray-600';
favoriteBtn.innerHTML = '';
favoriteBtn.addEventListener('click', async () => {
try {
await db.collection('markers').doc(markerData.id).update({
favorites: markerData.favorites + 1
});
userData.favoritesMade++;
userData.interactions++;
saveUserData();
updateTokenDisplay();
showNotification('Added to favorites!', 'success');
} catch (error) {
console.error('Error updating favorites:', error);
showNotification('Failed to add to favorites', 'error');
}
});
actions.appendChild(favoriteBtn);
const likeBtn = document.createElement('button');
likeBtn.className = 'p-2 rounded-full bg-gray-200 dark:bg-gray-700 hover:bg-gray-300 dark:hover:bg-gray-600';
likeBtn.innerHTML = '';
likeBtn.addEventListener('click', async () => {
try {
await db.collection('markers').doc(markerData.id).update({
likes: markerData.likes + 1
});
userData.likesGiven++;
userData.interactions++;
saveUserData();
updateTokenDisplay();
showNotification('Liked!', 'success');
} catch (error) {
console.error('Error updating likes:', error);
showNotification('Failed to like', 'error');
}
});
actions.appendChild(likeBtn);
const shareBtn = document.createElement('button');
shareBtn.className = 'p-2 rounded-full bg-gray-200 dark:bg-gray-700 hover:bg-gray-300 dark:hover:bg-gray-600';
shareBtn.innerHTML = '';
shareBtn.addEventListener('click', () => {
const url = `${window.location.origin}?marker=${markerData.id}`;
navigator.clipboard.writeText(url);
userData.shareClicked++;
userData.interactions++;
saveUserData();
updateTokenDisplay();
showNotification('Link copied to clipboard!', 'success');
});
actions.appendChild(shareBtn);
// Open modal
openModal('marker');
}
// Handle Utility Marker Click
async function handleUtilityMarkerClick(marker) {
if (!utilityMode || marker.userID !== currentUser.id) return;
try {
switch(utilityMode) {
case 'util1': // Visibility Boost
await db.collection('markers').doc(marker.id).update({
zDepth: marker.zDepth + 100
});
showNotification('Visibility boosted by 100!', 'success');
break;
case 'util2': // Marker Relocator
if (!utilityTarget) {
utilityTarget = marker;
showNotification('Now click where you want to move the marker', 'info');
return;
} else {
// Move marker to new location
await db.collection('markers').doc(marker.id).update({
lat: utilityTarget.lat,
lng: utilityTarget.lng
});
showNotification('Marker relocated!', 'success');
}
break;
case 'util3': // Marker Eraser
await db.collection('markers').doc(marker.id).delete();
showNotification('Marker deleted!', 'success');
break;
case 'util4': // Visibility Crusher
// This utility affects any marker, not just owned ones
// We handle this differently in the marker click handler
break;
}
} catch (error) {
console.error('Error applying utility:', error);
showNotification('Failed to apply utility', 'error');
}
// Reset utility mode
utilityMode = null;
utilityTarget = null;
updateMapMarkers();
}
// Show Notification
function showNotification(message, type) {
const container = document.getElementById('notification-container');
const notification = document.createElement('div');
notification.className = `notification ${type}`;
notification.textContent = message;
container.appendChild(notification);
// Remove after delay
setTimeout(() => {
notification.remove();
}, type === 'error' ? 5000 : 3000);
}
// Update Token Display
function updateTokenDisplay() {
document.getElementById('liquidity-tokens').textContent = currentUser.liquidityTokens;
document.getElementById('value-tokens').textContent = currentUser.valueTokens;
document.getElementById('engagement-tokens').textContent = currentUser.engagementTokens;
}
// Load User Data
function loadUserData() {
const savedData = localStorage.getItem('userData');
if (savedData) {
userData = JSON.parse(savedData);
}
// Determine archetype
determineArchetype();
}
// Save User Data
function saveUserData() {
// Calculate derived metrics
userData.battleWinRatio = userData.battlesWon + userData.battlesLost > 0 ?
(userData.battlesWon / (userData.battlesWon + userData.battlesLost)) * 100 : 0;
// Calculate engagement (interactions per 24h)
const now = Date.now();
const oneDay = 24 * 60 * 60 * 1000;
if (now - userData.lastInteraction > oneDay) {
userData.engagement = userData.interactions;
userData.lastInteraction = now;
userData.interactions = 0;
}
localStorage.setItem('userData', JSON.stringify(userData));
}
// Determine Archetype
function determineArchetype() {
// In a real implementation, this would analyze user's markers
// For now, we'll randomly assign an archetype
const archetypes = Object.values(archetype);
userData.archetype = archetypes[Math.floor(Math.random() * archetypes.length)];
}