From 2b785b7996f0a475642e0eb37411db2887f2be45 Mon Sep 17 00:00:00 2001 From: Vaibhav Katyal Date: Thu, 9 Oct 2025 07:35:11 +0530 Subject: [PATCH 1/3] Fixes #12- Enhanced the User Interface of main and auth page, gave it modern clean and aligned look --- dist/main.js | 950 +++++--------------------------------- frontend/public/auth.html | 267 ++++++++--- frontend/public/main.html | 571 +++++++++++++---------- 3 files changed, 628 insertions(+), 1160 deletions(-) diff --git a/dist/main.js b/dist/main.js index 7a09ebc..f332e55 100644 --- a/dist/main.js +++ b/dist/main.js @@ -1,50 +1,6 @@ "use strict"; const memoryList = []; let leafletMap = null; - -// API Configuration -const API_BASE_URL = 'http://localhost:8080'; - -// Authentication Manager -class AuthManager { - static getToken() { - return localStorage.getItem('authToken'); - } - - static getUserData() { - const userData = localStorage.getItem('userData'); - return userData ? JSON.parse(userData) : null; - } - - static isAuthenticated() { - return !!this.getToken(); - } - - static logout() { - localStorage.removeItem('authToken'); - localStorage.removeItem('userData'); - window.location.href = 'auth.html'; - } - - static checkAuthAndRedirect() { - if (!this.isAuthenticated()) { - window.location.href = 'auth.html'; - return false; - } - return true; - } - - static getAuthHeaders() { - const token = this.getToken(); - return token ? { - 'Authorization': `Bearer ${token}`, - 'Content-Type': 'application/json' - } : { - 'Content-Type': 'application/json' - }; - } -} - // Dark mode functionality class MemoryMapDarkModeManager { constructor() { @@ -55,7 +11,8 @@ class MemoryMapDarkModeManager { const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches; if (savedMode === 'dark' || (!savedMode && prefersDark)) { this.enableDarkMode(); - } else { + } + else { this.disableDarkMode(); } const toggleBtn = document.getElementById('darkModeToggle'); @@ -74,39 +31,23 @@ class MemoryMapDarkModeManager { toggleDarkMode() { if (document.documentElement.classList.contains('dark')) { this.disableDarkMode(); - } else { + } + else { this.enableDarkMode(); } } } - // Map functionality class MemoryMap { constructor() { - // Check authentication first - if (!AuthManager.checkAuthAndRedirect()) { - return; - } - this.selectedLocation = null; - this.tempMarker = null; - this.currentUser = AuthManager.getUserData(); this.initializeEventListeners(); - this.displayWelcomeMessage(); } - initializeEventListeners() { const startMappingBtn = document.getElementById('startMappingBtn'); if (startMappingBtn) { startMappingBtn.addEventListener('click', () => this.openMapModal()); } - - // Logout button - const logoutBtn = document.getElementById('logoutBtn'); - if (logoutBtn) { - logoutBtn.addEventListener('click', () => AuthManager.logout()); - } - const closeMapBtn = document.getElementById('closeMapBtn'); const mapModal = document.getElementById('mapModal'); if (closeMapBtn) { @@ -114,41 +55,29 @@ class MemoryMap { } if (mapModal) { mapModal.addEventListener('click', (e) => { - if (e.target === mapModal) this.closeMapModal(); + if (e.target === mapModal) + this.closeMapModal(); }); } const closeFormBtn = document.getElementById('closeFormBtn'); const cancelMemoryBtn = document.getElementById('cancelMemoryBtn'); const addMemoryForm = document.getElementById('addMemoryForm'); - const closeCardsBtn = document.getElementById('closeCardsBtn'); - if (closeFormBtn) { closeFormBtn.addEventListener('click', () => this.hideMemoryForm()); } if (cancelMemoryBtn) { cancelMemoryBtn.addEventListener('click', () => this.hideMemoryForm()); } - if (closeCardsBtn) { - closeCardsBtn.addEventListener('click', () => this.hidePhotoCards()); - } if (addMemoryForm) { addMemoryForm.addEventListener('submit', (e) => this.handleMemorySubmit(e)); } - - // Add photo preview functionality - const photoInput = document.getElementById('memoryPhoto'); - if (photoInput) { - photoInput.addEventListener('change', (e) => this.handlePhotoPreview(e)); - } - document.addEventListener('keydown', (e) => { if (e.key === 'Escape') { + this.closeMapModal(); this.hideMemoryForm(); - this.hidePhotoCards(); } }); } - openMapModal() { const modal = document.getElementById('mapModal'); if (modal) { @@ -158,412 +87,130 @@ class MemoryMap { this.initializeMap(); } setTimeout(() => { - if (leafletMap) leafletMap.invalidateSize(); + if (leafletMap) + leafletMap.invalidateSize(); }, 100); } - closeMapModal() { const modal = document.getElementById('mapModal'); if (modal) { modal.classList.add('hidden'); } this.hideMemoryForm(); - this.hidePhotoCards(); } - initializeMap() { leafletMap = L.map('realMap').setView([40.7128, -74.0060], 2); L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { attribution: '© OpenStreetMap contributors', maxZoom: 19 }).addTo(leafletMap); - - // Load existing memories from database - this.loadExistingMemories(); - + this.addSampleMemories(); leafletMap.on('click', (e) => { this.handleMapClick(e); }); this.getCurrentLocation(); } - - displayWelcomeMessage() { - const userData = this.currentUser; - if (userData) { - // Update user info in navigation - const userInfo = document.getElementById('userInfo'); - const userName = document.getElementById('userName'); - - if (userInfo && userName) { - userName.textContent = userData.username; - userInfo.classList.remove('hidden'); - userInfo.classList.add('flex'); - } - - this.showMessage(`Welcome back, ${userData.username}! 🎉`, 'success'); - } - } - - async loadExistingMemories() { - try { - console.log('Loading personalized memories...'); - - const response = await fetch(`${API_BASE_URL}/posts`, { - method: 'GET', - headers: AuthManager.getAuthHeaders() - }); - - if (response.status === 401) { - // Token expired or invalid - AuthManager.logout(); - return; - } - - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - - const memories = await response.json(); - console.log('Loaded memories:', memories); - - if (Array.isArray(memories)) { - memories.forEach(memory => { - this.addMemoryToMap(memory); - }); - - if (memories.length > 0) { - const ownMemories = memories.filter(m => m.isOwnPost).length; - const taggedMemories = memories.filter(m => !m.isOwnPost).length; - - let message = `Loaded ${memories.length} memories`; - if (ownMemories > 0 && taggedMemories > 0) { - message += ` (${ownMemories} yours, ${taggedMemories} tagged)`; - } else if (ownMemories > 0) { - message += ` (all yours)`; - } else if (taggedMemories > 0) { - message += ` (all tagged)`; - } - - this.showMessage(message, 'info'); - } else { - this.showMessage('No memories found. Start creating your first memory!', 'info'); - } - } - } catch (error) { - console.error('Error loading memories:', error); - this.showMessage('Failed to load memories', 'error'); - } - } - getCurrentLocation() { if ("geolocation" in navigator) { - navigator.geolocation.getCurrentPosition( - (position) => { - const lat = position.coords.latitude; - const lng = position.coords.longitude; - leafletMap.setView([lat, lng], 13); - }, - (error) => { - console.log("Location access denied or unavailable"); - } - ); + navigator.geolocation.getCurrentPosition((position) => { + const lat = position.coords.latitude; + const lng = position.coords.longitude; + leafletMap.setView([lat, lng], 10); + const currentLocationIcon = L.divIcon({ + className: 'current-location-marker', + html: '
', + iconSize: [16, 16], + iconAnchor: [8, 8] + }); + L.marker([lat, lng], { icon: currentLocationIcon }) + .addTo(leafletMap) + .bindPopup("You are here!"); + }); } } - + addSampleMemories() { + const sampleMemories = [ + { + lat: 48.8566, + lng: 2.3522, + title: "Paris Adventure", + description: "Amazing view from the Eiffel Tower!", + friends: ["Sarah", "Mike"], + date: "2024-06-15" + }, + { + lat: 35.6762, + lng: 139.6503, + title: "Tokyo Food Tour", + description: "Best ramen I've ever had in Shibuya", + friends: ["Yuki", "Emma"], + date: "2024-07-20" + }, + { + lat: 40.7589, + lng: -73.9851, + title: "Central Park Morning", + description: "Perfect sunrise jog through the park", + friends: [], + date: "2024-08-01" + }, + { + lat: 51.5074, + lng: -0.1278, + title: "London Bridge Walk", + description: "Crossing the Thames at sunset", + friends: ["James", "Lucy"], + date: "2024-05-12" + } + ]; + sampleMemories.forEach(memory => { + this.addMemoryToMap(memory); + memoryList.push(memory); + }); + } addMemoryToMap(memory) { const customIcon = L.divIcon({ className: 'custom-memory-marker', html: `
+ ">
`, - iconSize: [24, 24], - iconAnchor: [12, 24] + iconSize: [20, 20], + iconAnchor: [10, 20] }); - - // Format date nicely - const formattedDate = memory.date ? new Date(memory.date).toLocaleDateString('en-US', { - year: 'numeric', - month: 'short', - day: 'numeric' - }) : 'Unknown date'; - - // Format friends/tags - const friendsChips = memory.friends && memory.friends.length > 0 - ? memory.friends.map(friend => - `${friend}` - ).join('') + const friendsText = memory.friends.length > 0 + ? `

With: ${memory.friends.join(', ')}

` : ''; - - // Create beautiful popup content const popupContent = ` -
- -
-
- -
-
-

${memory.user?.username || 'Anonymous Explorer'}

-

- - ${formattedDate} -

-
-
- Memory -
-
- - -
-

${memory.title}

- ${memory.description ? ` -

${memory.description}

- ` : ''} -
- - - ${memory.imageData ? ` -
-
- ${memory.title} -
- - Photo -
-
-
- ` : ''} - - - ${friendsChips ? ` -
-
- - Tagged -
-
${friendsChips}
-
- ` : ''} - - -
-
- - - -
- -
-

- - Delete action requires confirmation -

-
-
+
+

${memory.title}

+

${memory.description}

+ ${friendsText} +

${memory.date}

`; - const marker = L.marker([memory.lat, memory.lng], { icon: customIcon }) .addTo(leafletMap) - .bindPopup(popupContent, { - maxWidth: 320, - className: 'custom-popup', - closeButton: true, - autoClose: false, - closeOnEscapeKey: true - }); - - // Enhanced click event - show both popup and photo cards - marker.on('click', () => { - // Open the popup first - marker.openPopup(); - - // Then show photo cards after a short delay for better UX - setTimeout(() => { - this.showPhotoCardsForLocation(memory.lat, memory.lng); - }, 300); - }); - + .bindPopup(popupContent); return marker; } - handleMapClick(e) { this.selectedLocation = e.latlng; this.showMemoryForm(); @@ -585,28 +232,24 @@ class MemoryMap { this.tempMarker = L.marker([e.latlng.lat, e.latlng.lng], { icon: tempIcon }) .addTo(leafletMap); } - showMemoryForm() { const form = document.getElementById('memoryForm'); if (form) { form.classList.remove('hidden'); - form.style.transform = 'translateX(0)'; - form.style.opacity = '1'; + form.classList.add('show-fixed'); setTimeout(() => { - const usernameInput = document.getElementById('memoryUsername'); - if (usernameInput) { - usernameInput.focus(); + const titleInput = document.getElementById('memoryTitle'); + if (titleInput) { + titleInput.focus(); } }, 100); } } - hideMemoryForm() { const form = document.getElementById('memoryForm'); if (form) { form.classList.add('hidden'); - form.style.transform = 'translateX(100%)'; - form.style.opacity = '0'; + form.classList.remove('show-fixed'); } if (this.tempMarker) { leafletMap.removeLayer(this.tempMarker); @@ -616,417 +259,46 @@ class MemoryMap { if (addMemoryForm) { addMemoryForm.reset(); } - - // Clear photo preview - const previewContainer = document.getElementById('photoPreview'); - const previewImage = document.getElementById('previewImage'); - if (previewContainer) { - previewContainer.classList.add('hidden'); - } - if (previewImage) { - previewImage.src = ''; - } - this.selectedLocation = null; } - - handlePhotoPreview(e) { - const file = e.target.files[0]; - const previewContainer = document.getElementById('photoPreview'); - const previewImage = document.getElementById('previewImage'); - const removeBtn = document.getElementById('removePreview'); - - if (file) { - // Validate file type - if (!file.type.startsWith('image/')) { - this.showMessage('Please select a valid image file', 'error'); - e.target.value = ''; - return; - } - - // Validate file size (10MB limit) - if (file.size > 10 * 1024 * 1024) { - this.showMessage('File size must be less than 10MB', 'error'); - e.target.value = ''; - return; - } - - // Create preview - const reader = new FileReader(); - reader.onload = (event) => { - previewImage.src = event.target.result; - previewContainer.classList.remove('hidden'); - }; - reader.readAsDataURL(file); - - // Add remove functionality - if (removeBtn) { - removeBtn.onclick = () => { - e.target.value = ''; - previewContainer.classList.add('hidden'); - previewImage.src = ''; - }; - } - } else { - // Hide preview if no file selected - previewContainer.classList.add('hidden'); - previewImage.src = ''; - } - } - - async handleMemorySubmit(e) { + handleMemorySubmit(e) { e.preventDefault(); - if (!this.selectedLocation) return; - + if (!this.selectedLocation) + return; const titleInput = document.getElementById('memoryTitle'); const descriptionInput = document.getElementById('memoryDescription'); const friendsInput = document.getElementById('memoryFriends'); - const photoInput = document.getElementById('memoryPhoto'); - const title = titleInput?.value.trim() || ''; const description = descriptionInput?.value.trim() || ''; const friendsStr = friendsInput?.value.trim() || ''; const friends = friendsStr ? friendsStr.split(',').map(f => f.trim()) : []; - const photoFile = photoInput?.files[0]; - if (!title) { - this.showMessage('Please enter a title for this memory', 'error'); + alert('Please enter a title for your memory!'); return; } - - if (!photoFile) { - this.showMessage('Please select a photo', 'error'); - return; - } - - this.showLoadingSpinner(true); - - try { - const uploadResult = await this.uploadImage(photoFile); - - // Add to local display - const memory = { - lat: this.selectedLocation.lat, - lng: this.selectedLocation.lng, - title, - description, - friends, - date: new Date().toISOString().split('T')[0], - user: this.currentUser, - isOwnPost: true - }; - - if (this.tempMarker) { - leafletMap.removeLayer(this.tempMarker); - } - - this.addMemoryToMap(memory); - this.hideMemoryForm(); - this.showSuccessMessage(memory.title); - - } catch (error) { - console.error('Error uploading memory:', error); - this.showMessage(error.message || 'Failed to upload memory', 'error'); - } finally { - this.showLoadingSpinner(false); - } - } - - async uploadImage(file) { - const formData = new FormData(); - formData.append('image', file); - - // Add memory metadata to the form data - const title = document.getElementById('memoryTitle')?.value.trim() || ''; - const description = document.getElementById('memoryDescription')?.value.trim() || ''; - const friendsStr = document.getElementById('memoryFriends')?.value.trim() || ''; - - formData.append('title', title); - formData.append('caption', description); - formData.append('tags', friendsStr); // Using tags field for friends - formData.append('latitude', this.selectedLocation.lat.toString()); - formData.append('longitude', this.selectedLocation.lng.toString()); - - const response = await fetch(`${API_BASE_URL}/upload`, { - method: 'POST', - headers: { - 'Authorization': `Bearer ${AuthManager.getToken()}` - }, - body: formData, - }); - - if (response.status === 401) { - AuthManager.logout(); - return; - } - - if (!response.ok) { - const errorData = await response.json(); - throw new Error(errorData.error || 'Upload failed'); + const memory = { + lat: this.selectedLocation.lat, + lng: this.selectedLocation.lng, + title, + description, + friends, + date: new Date().toISOString().split('T')[0] + }; + memoryList.push(memory); + if (this.tempMarker) { + leafletMap.removeLayer(this.tempMarker); + this.tempMarker = null; } - - return await response.json(); + this.addMemoryToMap(memory); + this.hideMemoryForm(); + this.showSuccessMessage(memory.title); + console.log('Memory added:', memory); + console.log('All memories:', memoryList); } - showSuccessMessage(title) { - this.showMessage(`Memory "${title}" added successfully!`, 'success'); - } - - showLoadingSpinner(show) { - const spinner = document.getElementById('loadingSpinner'); - if (spinner) { - spinner.classList.toggle('hidden', !show); - } - } - - showMessage(message, type = 'info') { - const container = document.getElementById('messageContainer') || this.createMessageContainer(); - - const messageEl = document.createElement('div'); - messageEl.className = `p-3 rounded-lg shadow-lg transition-all duration-300 transform translate-x-full ${ - type === 'success' ? 'bg-green-500 text-white' : - type === 'error' ? 'bg-red-500 text-white' : - 'bg-blue-500 text-white' - }`; - messageEl.textContent = message; - - container.appendChild(messageEl); - - setTimeout(() => { - messageEl.classList.remove('translate-x-full'); - }, 10); - - setTimeout(() => { - messageEl.classList.add('translate-x-full'); - setTimeout(() => { - if (messageEl.parentNode) { - messageEl.parentNode.removeChild(messageEl); - } - }, 300); - }, 3000); - } - - createMessageContainer() { - const container = document.createElement('div'); - container.id = 'messageContainer'; - container.className = 'fixed top-20 right-4 z-[60] space-y-2'; - document.body.appendChild(container); - return container; - } - - // Photo Cards Functions - async showPhotoCardsForLocation(lat, lng) { - try { - // Fetch photos for this location (within a small radius) - const radius = 0.001; // Very small radius for nearby photos - const response = await fetch(`${API_BASE_URL}/posts/location/${lat}/${lng}?radius=${radius}`); - - if (!response.ok) { - throw new Error('Failed to fetch photos for location'); - } - - const photos = await response.json(); - this.displayPhotoCards(photos); - } catch (error) { - console.error('Error fetching photos for location:', error); - this.showMessage('Failed to load photos for this location', 'error'); - } - } - - displayPhotoCards(photos) { - const container = document.getElementById('photoCardsContainer'); - const cardsList = document.getElementById('photoCardsList'); - - if (!container || !cardsList) return; - - if (photos.length === 0) { - cardsList.innerHTML = ` -
- -

No memories found at this location

-
- `; - } else { - cardsList.innerHTML = photos.map(photo => this.createPhotoCard(photo)).join(''); - } - - // Show the container - container.classList.remove('hidden'); - setTimeout(() => { - container.style.transform = 'translateX(0)'; - container.style.opacity = '1'; - }, 10); - } - - createPhotoCard(photo) { - const friendsChips = photo.friends && photo.friends.length > 0 - ? photo.friends.map(friend => `${friend}`).join('') - : ''; - - const userInfo = photo.user ? ` -
- - ${photo.user.username} - - ${photo.date} -
- ` : ''; - - return ` -
-
- ${photo.title} -
-
-

${photo.title}

-

${photo.description}

- ${friendsChips ? `
${friendsChips}
` : ''} - ${userInfo} -
-
- `; - } - - async deleteMemory(memoryId, lat, lng) { - // Show confirmation dialog - const confirmed = await this.showDeleteConfirmation(); - if (!confirmed) return; - - try { - this.showLoadingSpinner(true); - - // Send delete request to backend - const response = await fetch(`${API_BASE_URL}/posts/${memoryId}`, { - method: 'DELETE', - headers: AuthManager.getAuthHeaders() - }); - - if (response.status === 401) { - AuthManager.logout(); - return; - } - - if (!response.ok) { - const errorData = await response.json(); - throw new Error(errorData.error || 'Failed to delete memory'); - } - - // Remove marker from map - this.removeMarkerFromMap(lat, lng); - - // Close any open popups - leafletMap.closePopup(); - - // Hide photo cards if open - this.hidePhotoCards(); - - // Show success message - this.showMessage('Memory deleted successfully!', 'success'); - - } catch (error) { - console.error('Error deleting memory:', error); - this.showMessage(error.message || 'Failed to delete memory', 'error'); - } finally { - this.showLoadingSpinner(false); - } - } - - showDeleteConfirmation() { - return new Promise((resolve) => { - // Create and show custom confirmation modal - const modal = document.createElement('div'); - modal.className = 'fixed inset-0 bg-black bg-opacity-50 z-[9999] flex items-center justify-center'; - modal.innerHTML = ` -
-
-
- -
-

Delete Memory

-

- Are you sure you want to delete this memory? This action cannot be undone and will permanently remove the memory from the database. -

-
- - -
-
-
- `; - - document.body.appendChild(modal); - - // Add event listeners - const cancelBtn = modal.querySelector('#cancelDelete'); - const confirmBtn = modal.querySelector('#confirmDelete'); - - const cleanup = () => { - document.body.removeChild(modal); - }; - - cancelBtn.addEventListener('click', () => { - cleanup(); - resolve(false); - }); - - confirmBtn.addEventListener('click', () => { - cleanup(); - resolve(true); - }); - - // Close on escape key - const handleEscape = (e) => { - if (e.key === 'Escape') { - cleanup(); - resolve(false); - document.removeEventListener('keydown', handleEscape); - } - }; - document.addEventListener('keydown', handleEscape); - - // Close on backdrop click - modal.addEventListener('click', (e) => { - if (e.target === modal) { - cleanup(); - resolve(false); - } - }); - }); - } - - removeMarkerFromMap(lat, lng) { - // Find and remove the marker at the specified coordinates - leafletMap.eachLayer((layer) => { - if (layer instanceof L.Marker) { - const markerLat = layer.getLatLng().lat; - const markerLng = layer.getLatLng().lng; - - // Check if this marker is at the same location (within small tolerance) - const tolerance = 0.0001; - if (Math.abs(markerLat - lat) < tolerance && Math.abs(markerLng - lng) < tolerance) { - leafletMap.removeLayer(layer); - } - } - }); - } - - hidePhotoCards() { - const container = document.getElementById('photoCardsContainer'); - if (container) { - container.style.transform = 'translateX(-100%)'; - container.style.opacity = '0'; - setTimeout(() => { - container.classList.add('hidden'); - }, 300); - } + alert(`Memory "${title}" added successfully!`); } } - // Interactive map pins (existing functionality) document.querySelectorAll('.map-pin').forEach(pin => { pin.addEventListener('click', function () { @@ -1034,12 +306,8 @@ document.querySelectorAll('.map-pin').forEach(pin => { console.log(`Clicked on ${location}`); }); }); - // Initialize the app when DOM is loaded -let memoryMap; // Global reference for onclick handlers - document.addEventListener('DOMContentLoaded', function () { new MemoryMapDarkModeManager(); - memoryMap = new MemoryMap(); + new MemoryMap(); }); - diff --git a/frontend/public/auth.html b/frontend/public/auth.html index 6d50eaf..bc391d8 100644 --- a/frontend/public/auth.html +++ b/frontend/public/auth.html @@ -7,40 +7,90 @@ @@ -111,113 +214,123 @@
+
-
+
-
-
- +
+
+ +
+

Memory Map

+

Capture and share your precious moments

+
+
+
+
-

Memory Map

-

Capture and share your precious moments

-
+
-
- -
-
+
-
-
-
- - -
-

- - Start mapping your memories today -

+
+
+

+ + Start mapping your memories today +

+
- + \ No newline at end of file diff --git a/frontend/public/main.html b/frontend/public/main.html index 5653c4d..81c5f94 100644 --- a/frontend/public/main.html +++ b/frontend/public/main.html @@ -18,57 +18,80 @@ - + -
-
-

- Your Adventures, Beautifully Mapped +
+
+
+

✨ YOUR JOURNEY STARTS + HERE

+
+

+ Your Adventures,
+ Beautifully + Mapped

-

+

Create visual journals of your travels. Share memories with friends, discover new places, and build a personal collection of life's adventures.

-
+
-
-
+
+
-
+ class="relative h-96 bg-white dark:bg-slate-800 rounded-3xl border-2 border-slate-200 dark:border-slate-700 overflow-hidden transition-all duration-300 shadow-2xl hover:shadow-3xl group"> +
@@ -126,67 +160,79 @@

-
-
+ class="absolute bottom-6 left-6 bg-white/95 dark:bg-slate-700/95 backdrop-blur-xl rounded-2xl p-5 border border-slate-200 dark:border-slate-600 transition-all duration-300 shadow-2xl hover:scale-105"> +
+
+
-

Paris +

Paris Adventure

-

5 photos • 2 friends -

+

5 photos + • 2 friends

+ +
+ 4 Memories +
-
-
-
-

Why Memory Map? -

-

Every journey deserves to be - remembered beautifully

+
+
+
+
+

WHY CHOOSE US

+
+

+ Why Memory Map?

+

Every journey + deserves to be remembered beautifully

-
+
- + class="w-16 h-16 bg-gradient-to-br from-rose-500 to-pink-600 rounded-2xl flex items-center justify-center mx-auto mb-6 transition-all duration-300 shadow-lg shadow-rose-500/30 group-hover:shadow-rose-500/50 group-hover:scale-110"> +
-

Visual +

Visual Storytelling

-

+

Upload photos, add notes, and create rich visual stories of your adventures. Every memory becomes a beautiful piece of your journey.

-
+
- + class="w-16 h-16 bg-gradient-to-br from-blue-500 to-indigo-600 rounded-2xl flex items-center justify-center mx-auto mb-6 transition-all duration-300 shadow-lg shadow-blue-500/30 group-hover:shadow-blue-500/50 group-hover:scale-110"> +
-

+

Collaborative Memories

-

+

Invite friends to contribute their photos and stories. Build shared memories and see adventures from multiple perspectives.

-
+
- + class="w-16 h-16 bg-gradient-to-br from-emerald-500 to-teal-600 rounded-2xl flex items-center justify-center mx-auto mb-6 transition-all duration-300 shadow-lg shadow-emerald-500/30 group-hover:shadow-emerald-500/50 group-hover:scale-110"> +
-

+

Interactive Maps

-

+

Visualize your travels on beautiful interactive maps. Discover patterns, relive moments, and plan your next adventure.

@@ -196,137 +242,175 @@

-
-
-

Recent Memories -

-

Discover amazing adventures from - our community

+
+
+
+
+

COMMUNITY + HIGHLIGHTS

+
+

+ Recent Memories

+

Discover amazing + adventures from our community

-
+
-
-
-
-
+ class="bg-white dark:bg-slate-800 rounded-2xl border-2 border-slate-200 dark:border-slate-700 overflow-hidden transition-all duration-300 hover:scale-105 hover:shadow-2xl group"> +
+
+
+
+
+
+
+
-
- Sarah Chen
-

2 days ago

+
Sarah + Chen
+

2 + days ago

-

+

Santorini Sunset

-

The most beautiful - sunset I've ever witnessed...

+

The + most beautiful sunset I've ever witnessed...

-
+
-
+
- +3 + +3
-
- - 24 +
+ + 24
-
-
-
-
+ class="bg-white dark:bg-slate-800 rounded-2xl border-2 border-slate-200 dark:border-slate-700 overflow-hidden transition-all duration-300 hover:scale-105 hover:shadow-2xl group"> +
+
+
+
+
+
+
+
-
- Alex Rivera
-

1 week ago

+
Alex + Rivera
+

1 + week ago

-

+

Hiking in Patagonia

-

Epic 5-day trek - through pristine wilderness...

+

+ Epic 5-day trek through pristine wilderness...

- Solo + Solo adventure
-
- - 18 +
+ + 18
-
-
-
-
+ class="bg-white dark:bg-slate-800 rounded-2xl border-2 border-slate-200 dark:border-slate-700 overflow-hidden transition-all duration-300 hover:scale-105 hover:shadow-2xl group"> +
+
+
+
+
+
+
+
-
- Maya Patel
-

3 days ago

+
Maya + Patel
+

3 + days ago

-

- Tokyo Food Tour

-

Incredible street - food discoveries in Shibuya...

+

Tokyo + Food Tour

+

+ Incredible street food discoveries in Shibuya...

-
+
-
+
- +2 + +2
-
- - 31 +
+ + 31
-
-
-
-
+ class="bg-white dark:bg-slate-800 rounded-2xl border-2 border-slate-200 dark:border-slate-700 overflow-hidden transition-all duration-300 hover:scale-105 hover:shadow-2xl group"> +
+
+
+
+
+
+
+
-
- James Wilson
-

5 days ago

+
James + Wilson
+

5 + days ago

-

+

Northern Lights

-

Finally caught the - aurora in Iceland...

+

+ Finally caught the aurora in Iceland...

- with + with Emma
-
- - 42 +
+ + 42
@@ -336,76 +420,82 @@

-
-

Start Your Memory Map Today

-

+

+
+
+
+

Start Your Memory Map Today

+

Join thousands of adventurers who are already creating beautiful visual journals of their travels. Your next great story is waiting to be mapped.

-
+
-