Skip to content

Instantly share code, notes, and snippets.

@legoktm
Created March 2, 2025 07:37
Show Gist options
  • Select an option

  • Save legoktm/c587eda268dcf0061df4b7a98e29bea4 to your computer and use it in GitHub Desktop.

Select an option

Save legoktm/c587eda268dcf0061df4b7a98e29bea4 to your computer and use it in GitHub Desktop.
Sidebar for Recent Reviews and Map Popups

Sidebar for Recent Reviews and Map Popups

human (Feb 15, 2025, 06:54 PM)

I want to add a sidebar of the 5 most recent reviews. Clicking on a place should show the corresponding popup on the map.

Here's my HTML

paste.txt

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8" />
    <title>Boba Quest 2025 🧋</title>
    <link rel="stylesheet" href="static/leaflet-1.9.4.css" />
    <style>
        body {
            margin: 0;
            padding: 20px;
            font-family: sans-serif;
        }

        .header {
            text-align: center;
            margin-bottom: 20px;
        }

        .header h1 {
            font-size: 2.5rem;
            margin: 0 0 10px 0;
        }

        .nav-links {
            margin-bottom: 20px;
        }

        .nav-links a {
            color: #2B6CB0;
            cursor: pointer;
            text-decoration: none;
            margin: 0 15px;
            font-size: 1.2rem;
        }

        .nav-links a:hover {
            text-decoration: underline;
        }

        .modal-overlay {
            display: none;
            position: fixed;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            background-color: rgba(0, 0, 0, 0.5);
            z-index: 1000;
        }

        .modal {
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background-color: white;
            padding: 30px;
            border-radius: 8px;
            max-width: 500px;
            width: 90%;
            z-index: 1001;
        }

        .modal h2 {
            margin-top: 0;
            font-size: 1.8rem;
            color: #2B6CB0;
        }

        .modal p {
            line-height: 1.6;
            font-size: 1.1rem;
        }

        .close-modal {
            position: absolute;
            top: 15px;
            right: 15px;
            font-size: 24px;
            cursor: pointer;
            color: #666;
            border: none;
            background: none;
            padding: 0;
        }

        .close-modal:hover {
            color: #333;
        }

        .container {
            max-width: 80%;
            margin: 0 auto;
        }

        #map {
            height: 80vh;
            width: 100%;
            border-radius: 8px;
            border: 2px solid #ddd;
        }

        .popup-content {
            padding: 12px;
            min-width: 200px;
            font-size: 1.5rem;
        }

        .popup-content h3 {
            text-align: center;
            margin-bottom: 12px;
            margin-top: 0;
            font-size: 1.8rem;
        }

        .ratings-table {
            width: 100%;
            border-spacing: 0;
            margin-bottom: 12px;
            border-collapse: collapse;
        }

        .ratings-table tr td {
            padding: 4px 2px;
        }

        .ratings-table tr td:first-child {
            padding-right: 4px;
        }

        .ratings-table tr td:last-child {
            text-align: right;
        }

        .total-row {
            font-weight: bold;
            border-top: 1px solid #ddd;
            margin-top: 4px;
        }

        .review-link-container {
            text-align: center;
        }

        .review-link {
            display: inline-block;
            padding: 8px 20px;
            color: #2B6CB0;
            text-decoration: none;
            border-radius: 4px;
            font-size: 1.3rem;
            border: 2px solid #2B6CB0;
            transition: all 0.2s ease;
        }

        .review-link:hover {
            background-color: #2B6CB0;
            color: white;
        }

        .emoji-marker {
            font-size: 2rem;
            text-align: center;
            line-height: 1;
        }

        .leaflet-marker-icon {
            filter: none !important;
        }

        /* Make the popup wider to accommodate larger text */
        .leaflet-popup-content {
            margin: 8px;
        }

        .subway {
            height: 1.5em;
        }
    </style>
</head>

<body>
    <div class="container">
        <div class="header">
            <h1>Boba Quest 2025 🧋</h1>
            <div class="nav-links">
                <a href="https://blog.legoktm.com/2025/01/07/boba-quest-2025-.html">About</a>
                <a href="https://blog.legoktm.com/category/bobaquest.html">Reviews</a>
                <a class="modal-trigger" data-modal="subscribe">Subscribe</a>
                <a class="modal-trigger" data-modal="tips">Tips</a>
            </div>
        </div>
        <div id="map"></div>
    </div>

    <div id="subscribe-modal" class="modal-overlay">
        <div class="modal">
            <button class="close-modal" data-modal="subscribe">&times;</button>
            <h2>Subscribe</h2>
            <p>
                You can subscribe to Boba Quest 2025 🧋 by using <a href="https://en.wikipedia.org/wiki/RSS">RSS</a>.
                You'll need a feed reader, if you don't have one yet, look at some recommendations from
                <a href="https://opensource.com/article/17/3/rss-feed-readers">opensource.com</a>, <a
                    href="https://www.theverge.com/24036427/rss-feed-reader-best">The Verge</a>,
                and <a href="https://lifehacker.com/tech/best-rss-readers">LifeHacker</a>.
            </p>
            <p>Use the URL <kbd>https://blog.legoktm.com/feeds/bobaquest.atom.xml</kbd> in your feed reader to
                subscribe.</p>
        </div>
    </div>

    <div id="tips-modal" class="modal-overlay">
        <div class="modal">
            <button class="close-modal" data-modal="tips">&times;</button>
            <h2>Tips</h2>
            <p>
                I am interested in suggestions for more boba shops to review, as well as any other feedback.
                I can be reached securely using <a href="https://signal.org/">Signal</a>; my username is
                <kbd>@legoktm.12345</kbd>
            </p>
        </div>
    </div>

    <script src="static/leaflet-1.9.4.js"></script>
    <script>
        // Initialize the map centered on a specific location (this example uses New York City)
        const map = L.map('map').setView([40.7128, -74.0060], 12);
        map.attributionControl.setPrefix('<a href="https://leafletjs.com/">Leaflet</a>');
        const divIcon = L.divIcon({
            html: '🧋',
            className: 'emoji-marker',
            iconSize: [32, 32],
            iconAnchor: [16, 32],
            popupAnchor: [0, -32]
        });

        // Add the OpenStreetMap tiles
        L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
            maxZoom: 19,
            attribution: '© OpenStreetMap contributors'
        }).addTo(map);

        const shops = [{ "name": "I'Milky", "lat": "40.745819", "lng": "-73.946220", "boba": 3, "tea": 4, "bonus": 0, "reviewLink": "/2025/01/12/boba-quest-imilky.html", "trains": ["7", "E", "M", "G"] }, { "name": "Bobba Hubba", "lat": "40.746248", "lng": "-73.890490", "boba": 2, "tea": 4, "bonus": 0, "reviewLink": "/2025/01/15/boba-quest-bobba-hubba.html", "trains": ["7", "E", "F", "M", "R"] }, { "name": "Chun Yang Tea", "lat": "40.743176", "lng": "-73.954315", "boba": 1, "tea": 2, "bonus": 0, "reviewLink": "/2025/01/22/boba-quest-chun-yang-tea.html", "trains": ["7"] }, { "name": "M\u00f6ge Tea", "lat": "40.749738", "lng": "-73.941435", "boba": 3, "tea": 3, "bonus": 0, "reviewLink": "/2025/01/29/boba-quest-m\u00f6ge-tea.html", "trains": ["7"] }, { "name": "AKIHI Bubble Tea", "lat": "40.741877", "lng": "-74.173377", "boba": 3, "tea": 2, "bonus": 1, "reviewLink": "/2025/02/05/boba-quest-akihi-bubble-tea.html", "trains": ["PATH", "NJ Transit", "Amtrak"] }, { "name": "D\u00e9butea", "lat": "40.729175", "lng": "-73.9993244", "boba": 4, "tea": 4, "bonus": 1, "reviewLink": "/2025/02/12/boba-quest-d\u00e9butea.html", "trains": ["A", "C", "E", "B", "D", "F", "M"] }];

        // Add markers for each shop
        shops.forEach(shop => {
            const marker = L.marker([shop.lat, shop.lng], { icon: divIcon }).addTo(map);

            let trains = "";
            shop.trains.forEach(train => {
                trains += `<img class="subway" src="icons/${train}.svg" alt="${train} train" title="${train} train">`;
            })

            // Create popup content
            const popupContent = `
                <div class="popup-content">
                    <h3>${shop.name}</h3>
                    <table class="ratings-table">
                        <tr>
                            <td>Boba:</td>
                            <td>${shop.boba}/4</td>
                        </tr>
                        <tr>
                            <td>Tea:</td>
                            <td>${shop.tea}/4</td>
                        </tr>
                        <tr>
                            <td>Bonus:</td>
                            <td>${shop.bonus}/1</td>
                        </tr>
                        <tr class="total-row">
                            <td>Total:</td>
                            <td>${shop.boba + shop.tea + shop.bonus}/10</td>
                        </tr>
                    </table>
                    <div class="review-link-container">
                        <a href="https://blog.legoktm.com${shop.reviewLink}" class="review-link" target="_blank">Full Review</a>
                        <div>${trains}</div>
                    </div>
                </div>
            `;

            // Bind popup to marker
            marker.bindPopup(popupContent);
        });


        document.addEventListener('DOMContentLoaded', () => {
            // Modal handlers
            const openModal = (type) => {
                document.getElementById(`${type}-modal`).style.display = 'block';
                document.body.style.overflow = 'hidden';
            };

            const closeModal = (type) => {
                document.getElementById(`${type}-modal`).style.display = 'none';
                document.body.style.overflow = 'auto';
            };

            // Add click listeners for modal triggers
            document.querySelectorAll('.modal-trigger').forEach(trigger => {
                trigger.addEventListener('click', (e) => {
                    const modalType = e.target.dataset.modal;
                    openModal(modalType);
                });
            });

            // Add click listeners for close buttons
            document.querySelectorAll('.close-modal').forEach(button => {
                button.addEventListener('click', (e) => {
                    const modalType = e.target.dataset.modal;
                    closeModal(modalType);
                });
            });

            // Close modal when clicking overlay
            document.querySelectorAll('.modal-overlay').forEach(overlay => {
                overlay.addEventListener('click', (e) => {
                    if (e.target === e.currentTarget) {
                        closeModal(e.target.id.replace('-modal', ''));
                    }
                });
            });

            document.addEventListener('keydown', (e) => {
                if (e.key === 'Escape') {
                    closeModal("tips");
                    closeModal("subscribe");
                }
            });
        });
    </script>
</body>

</html>

assistant (Feb 15, 2025, 06:54 PM)

I'll help you create a sidebar that shows the 5 most recent reviews and interacts with the map. We'll need to modify your HTML and CSS, and add some JavaScript to handle the interactions.

Boba Quest with Recent Reviews Sidebar

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>Boba Quest 2025 🧋</title>
    <link rel="stylesheet" href="static/leaflet-1.9.4.css" />
    <style>
        /* Existing styles */
        body {
            margin: 0;
            padding: 20px;
            font-family: sans-serif;
        }

        .header {
            text-align: center;
            margin-bottom: 20px;
        }

        .header h1 {
            font-size: 2.5rem;
            margin: 0 0 10px 0;
        }

        /* New layout styles */
        .main-content {
            display: flex;
            gap: 20px;
            max-width: 95%;
            margin: 0 auto;
        }

        #map {
            flex: 1;
            height: 80vh;
            border-radius: 8px;
            border: 2px solid #ddd;
        }

        .sidebar {
            width: 300px;
            background: #f8f9fa;
            padding: 15px;
            border-radius: 8px;
            border: 2px solid #ddd;
        }

        .sidebar h2 {
            margin-top: 0;
            color: #2B6CB0;
            font-size: 1.5rem;
            margin-bottom: 15px;
        }

        .recent-reviews {
            display: flex;
            flex-direction: column;
            gap: 12px;
        }

        .review-card {
            background: white;
            padding: 12px;
            border-radius: 6px;
            border: 1px solid #ddd;
            cursor: pointer;
            transition: all 0.2s ease;
        }

        .review-card:hover {
            background: #f0f4f8;
            transform: translateX(2px);
        }

        .review-card h3 {
            margin: 0 0 8px 0;
            color: #2B6CB0;
            font-size: 1.2rem;
        }

        .review-card .rating {
            font-weight: bold;
            margin-bottom: 8px;
        }

        .review-card .trains {
            margin-top: 8px;
        }

        .subway {
            height: 1.5em;
        }

        /* Existing modal styles remain the same */
        /* ... */
    </style>
</head>
<body>
    <div class="header">
        <h1>Boba Quest 2025 🧋</h1>
        <div class="nav-links">
            <a href="https://blog.legoktm.com/2025/01/07/boba-quest-2025-.html">About</a>
            <a href="https://blog.legoktm.com/category/bobaquest.html">Reviews</a>
            <a class="modal-trigger" data-modal="subscribe">Subscribe</a>
            <a class="modal-trigger" data-modal="tips">Tips</a>
        </div>
    </div>
    
    <div class="main-content">
        <div id="map"></div>
        <div class="sidebar">
            <h2>Recent Reviews</h2>
            <div class="recent-reviews" id="recent-reviews">
                <!-- Review cards will be populated by JavaScript -->
            </div>
        </div>
    </div>

    <!-- Existing modals remain the same -->
    <!-- ... -->

    <script src="static/leaflet-1.9.4.js"></script>
    <script>
        // Initialize the map
        const map = L.map('map').setView([40.7128, -74.0060], 12);
        map.attributionControl.setPrefix('<a href="https://leafletjs.com/">Leaflet</a>');
        
        const divIcon = L.divIcon({
            html: '🧋',
            className: 'emoji-marker',
            iconSize: [32, 32],
            iconAnchor: [16, 32],
            popupAnchor: [0, -32]
        });

        // Add the OpenStreetMap tiles
        L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
            maxZoom: 19,
            attribution: '© OpenStreetMap contributors'
        }).addTo(map);

        const shops = [/* Your existing shops array */];
        const markers = new Map(); // Store markers by shop name

        // Add markers for each shop
        shops.forEach(shop => {
            const marker = L.marker([shop.lat, shop.lng], { icon: divIcon }).addTo(map);
            markers.set(shop.name, marker); // Store marker reference

            let trains = "";
            shop.trains.forEach(train => {
                trains += `<img class="subway" src="icons/${train}.svg" alt="${train} train" title="${train} train">`;
            });

            // Create popup content
            const popupContent = `
                <div class="popup-content">
                    <h3>${shop.name}</h3>
                    <table class="ratings-table">
                        <tr>
                            <td>Boba:</td>
                            <td>${shop.boba}/4</td>
                        </tr>
                        <tr>
                            <td>Tea:</td>
                            <td>${shop.tea}/4</td>
                        </tr>
                        <tr>
                            <td>Bonus:</td>
                            <td>${shop.bonus}/1</td>
                        </tr>
                        <tr class="total-row">
                            <td>Total:</td>
                            <td>${shop.boba + shop.tea + shop.bonus}/10</td>
                        </tr>
                    </table>
                    <div class="review-link-container">
                        <a href="https://blog.legoktm.com${shop.reviewLink}" class="review-link" target="_blank">Full Review</a>
                        <div>${trains}</div>
                    </div>
                </div>
            `;

            marker.bindPopup(popupContent);
        });

        // Populate recent reviews
        function populateRecentReviews() {
            const recentReviews = document.getElementById('recent-reviews');
            // Sort shops by review link date (newest first)
            const sortedShops = [...shops].sort((a, b) => b.reviewLink.localeCompare(a.reviewLink));
            // Take only the 5 most recent
            const recentShops = sortedShops.slice(0, 5);

            recentShops.forEach(shop => {
                let trains = "";
                shop.trains.forEach(train => {
                    trains += `<img class="subway" src="icons/${train}.svg" alt="${train} train" title="${train} train">`;
                });

                const reviewCard = document.createElement('div');
                reviewCard.className = 'review-card';
                reviewCard.innerHTML = `
                    <h3>${shop.name}</h3>
                    <div class="rating">Rating: ${shop.boba + shop.tea + shop.bonus}/10</div>
                    <div class="trains">${trains}</div>
                `;

                // Add click handler to show popup
                reviewCard.addEventListener('click', () => {
                    const marker = markers.get(shop.name);
                    if (marker) {
                        marker.openPopup();
                        map.setView([shop.lat, shop.lng], 15);
                    }
                });

                recentReviews.appendChild(reviewCard);
            });
        }

        // Initialize everything when DOM is loaded
        document.addEventListener('DOMContentLoaded', () => {
            populateRecentReviews();

            // Your existing modal handlers remain the same
            // ...
        });
    </script>
</body>
</html>

I've made the following changes to your code:

  1. Added a sidebar layout using flexbox
  2. Created a new "Recent Reviews" section that shows the 5 most recent reviews based on the review link dates
  3. Added interactive review cards that show:
    • Shop name
    • Total rating
    • Available trains
  4. Made the cards clickable - clicking a card will:
    • Center the map on that location
    • Open the corresponding marker popup
    • Zoom in to level 15

The sidebar has a clean, minimal design that matches your existing aesthetic. The review cards have a subtle hover effect and clear visual hierarchy.

Would you like me to modify anything about the design or functionality? For example, we could:

  1. Add more details to the review cards
  2. Change the styling or layout
  3. Adjust the interaction behavior

human (Feb 15, 2025, 07:03 PM)

Don't omit any code so I can see the full page with your modifications

assistant (Feb 15, 2025, 07:04 PM)

Complete Boba Quest with Recent Reviews Sidebar

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>Boba Quest 2025 🧋</title>
    <link rel="stylesheet" href="static/leaflet-1.9.4.css" />
    <style>
        body {
            margin: 0;
            padding: 20px;
            font-family: sans-serif;
        }

        .header {
            text-align: center;
            margin-bottom: 20px;
        }

        .header h1 {
            font-size: 2.5rem;
            margin: 0 0 10px 0;
        }

        .nav-links {
            margin-bottom: 20px;
        }

        .nav-links a {
            color: #2B6CB0;
            cursor: pointer;
            text-decoration: none;
            margin: 0 15px;
            font-size: 1.2rem;
        }

        .nav-links a:hover {
            text-decoration: underline;
        }

        .modal-overlay {
            display: none;
            position: fixed;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            background-color: rgba(0, 0, 0, 0.5);
            z-index: 1000;
        }

        .modal {
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background-color: white;
            padding: 30px;
            border-radius: 8px;
            max-width: 500px;
            width: 90%;
            z-index: 1001;
        }

        .modal h2 {
            margin-top: 0;
            font-size: 1.8rem;
            color: #2B6CB0;
        }

        .modal p {
            line-height: 1.6;
            font-size: 1.1rem;
        }

        .close-modal {
            position: absolute;
            top: 15px;
            right: 15px;
            font-size: 24px;
            cursor: pointer;
            color: #666;
            border: none;
            background: none;
            padding: 0;
        }

        .close-modal:hover {
            color: #333;
        }

        /* New layout styles */
        .main-content {
            display: flex;
            gap: 20px;
            max-width: 95%;
            margin: 0 auto;
        }

        #map {
            flex: 1;
            height: 80vh;
            border-radius: 8px;
            border: 2px solid #ddd;
        }

        .sidebar {
            width: 300px;
            background: #f8f9fa;
            padding: 15px;
            border-radius: 8px;
            border: 2px solid #ddd;
        }

        .sidebar h2 {
            margin-top: 0;
            color: #2B6CB0;
            font-size: 1.5rem;
            margin-bottom: 15px;
        }

        .recent-reviews {
            display: flex;
            flex-direction: column;
            gap: 12px;
        }

        .review-card {
            background: white;
            padding: 12px;
            border-radius: 6px;
            border: 1px solid #ddd;
            cursor: pointer;
            transition: all 0.2s ease;
        }

        .review-card:hover {
            background: #f0f4f8;
            transform: translateX(2px);
        }

        .review-card h3 {
            margin: 0 0 8px 0;
            color: #2B6CB0;
            font-size: 1.2rem;
        }

        .review-card .rating {
            font-weight: bold;
            margin-bottom: 8px;
        }

        .review-card .trains {
            margin-top: 8px;
        }

        .popup-content {
            padding: 12px;
            min-width: 200px;
            font-size: 1.5rem;
        }

        .popup-content h3 {
            text-align: center;
            margin-bottom: 12px;
            margin-top: 0;
            font-size: 1.8rem;
        }

        .ratings-table {
            width: 100%;
            border-spacing: 0;
            margin-bottom: 12px;
            border-collapse: collapse;
        }

        .ratings-table tr td {
            padding: 4px 2px;
        }

        .ratings-table tr td:first-child {
            padding-right: 4px;
        }

        .ratings-table tr td:last-child {
            text-align: right;
        }

        .total-row {
            font-weight: bold;
            border-top: 1px solid #ddd;
            margin-top: 4px;
        }

        .review-link-container {
            text-align: center;
        }

        .review-link {
            display: inline-block;
            padding: 8px 20px;
            color: #2B6CB0;
            text-decoration: none;
            border-radius: 4px;
            font-size: 1.3rem;
            border: 2px solid #2B6CB0;
            transition: all 0.2s ease;
        }

        .review-link:hover {
            background-color: #2B6CB0;
            color: white;
        }

        .emoji-marker {
            font-size: 2rem;
            text-align: center;
            line-height: 1;
        }

        .leaflet-marker-icon {
            filter: none !important;
        }

        .leaflet-popup-content {
            margin: 8px;
        }

        .subway {
            height: 1.5em;
        }
    </style>
</head>

<body>
    <div class="header">
        <h1>Boba Quest 2025 🧋</h1>
        <div class="nav-links">
            <a href="https://blog.legoktm.com/2025/01/07/boba-quest-2025-.html">About</a>
            <a href="https://blog.legoktm.com/category/bobaquest.html">Reviews</a>
            <a class="modal-trigger" data-modal="subscribe">Subscribe</a>
            <a class="modal-trigger" data-modal="tips">Tips</a>
        </div>
    </div>
    
    <div class="main-content">
        <div id="map"></div>
        <div class="sidebar">
            <h2>Recent Reviews</h2>
            <div class="recent-reviews" id="recent-reviews">
                <!-- Review cards will be populated by JavaScript -->
            </div>
        </div>
    </div>

    <div id="subscribe-modal" class="modal-overlay">
        <div class="modal">
            <button class="close-modal" data-modal="subscribe">&times;</button>
            <h2>Subscribe</h2>
            <p>
                You can subscribe to Boba Quest 2025 🧋 by using <a href="https://en.wikipedia.org/wiki/RSS">RSS</a>.
                You'll need a feed reader, if you don't have one yet, look at some recommendations from
                <a href="https://opensource.com/article/17/3/rss-feed-readers">opensource.com</a>, <a
                    href="https://www.theverge.com/24036427/rss-feed-reader-best">The Verge</a>,
                and <a href="https://lifehacker.com/tech/best-rss-readers">LifeHacker</a>.
            </p>
            <p>Use the URL <kbd>https://blog.legoktm.com/feeds/bobaquest.atom.xml</kbd> in your feed reader to
                subscribe.</p>
        </div>
    </div>

    <div id="tips-modal" class="modal-overlay">
        <div class="modal">
            <button class="close-modal" data-modal="tips">&times;</button>
            <h2>Tips</h2>
            <p>
                I am interested in suggestions for more boba shops to review, as well as any other feedback.
                I can be reached securely using <a href="https://signal.org/">Signal</a>; my username is
                <kbd>@legoktm.12345</kbd>
            </p>
        </div>
    </div>

    <script src="static/leaflet-1.9.4.js"></script>
    <script>
        // Initialize the map
        const map = L.map('map').setView([40.7128, -74.0060], 12);
        map.attributionControl.setPrefix('<a href="https://leafletjs.com/">Leaflet</a>');
        
        const divIcon = L.divIcon({
            html: '🧋',
            className: 'emoji-marker',
            iconSize: [32, 32],
            iconAnchor: [16, 32],
            popupAnchor: [0, -32]
        });

        // Add the OpenStreetMap tiles
        L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
            maxZoom: 19,
            attribution: '© OpenStreetMap contributors'
        }).addTo(map);

        const shops = [
            { 
                "name": "I'Milky", 
                "lat": "40.745819", 
                "lng": "-73.946220", 
                "boba": 3, 
                "tea": 4, 
                "bonus": 0, 
                "reviewLink": "/2025/01/12/boba-quest-imilky.html", 
                "trains": ["7", "E", "M", "G"]
            },
            { 
                "name": "Bobba Hubba", 
                "lat": "40.746248", 
                "lng": "-73.890490", 
                "boba": 2, 
                "tea": 4, 
                "bonus": 0, 
                "reviewLink": "/2025/01/15/boba-quest-bobba-hubba.html", 
                "trains": ["7", "E", "F", "M", "R"]
            },
            { 
                "name": "Chun Yang Tea", 
                "lat": "40.743176", 
                "lng": "-73.954315", 
                "boba": 1, 
                "tea": 2, 
                "bonus": 0, 
                "reviewLink": "/2025/01/22/boba-quest-chun-yang-tea.html", 
                "trains": ["7"]
            },
            { 
                "name": "Möge Tea", 
                "lat": "40.749738", 
                "lng": "-73.941435", 
                "boba": 3, 
                "tea": 3, 
                "bonus": 0, 
                "reviewLink": "/2025/01/29/boba-quest-möge-tea.html", 
                "trains": ["7"]
            },
            { 
                "name": "AKIHI Bubble Tea", 
                "lat": "40.741877", 
                "lng": "-74.173377", 
                "boba": 3, 
                "tea": 2, 
                "bonus": 1, 
                "reviewLink": "/2025/02/05/boba-quest-akihi-bubble-tea.html", 
                "trains": ["PATH", "NJ Transit", "Amtrak"]
            },
            { 
                "name": "Débutea", 
                "lat": "40.729175", 
                "lng": "-73.9993244", 
                "boba": 4, 
                "tea": 4, 
                "bonus": 1, 
                "reviewLink": "/2025/02/12/boba-quest-débutea.html", 
                "trains": ["A", "C", "E", "B", "D", "F", "M"]
            }
        ];

        const markers = new Map(); // Store markers by shop name

        // Add markers for each shop
        shops.forEach(shop => {
            const marker = L.marker([shop.lat, shop.lng], { icon: divIcon }).addTo(map);
            markers.set(shop.name, marker); // Store marker reference

            let trains = "";
            shop.trains.forEach(train => {
                trains += `<img class="subway" src="icons/${train}.svg" alt="${train} train" title="${train} train">`;
            });

            // Create popup content
            const popupContent = `
                <div class="popup-content">
                    <h3>${shop.name}</h3>
                    <table class="ratings-table">
                        <tr>
                            <td>Boba:</td>
                            <td>${shop.boba}/4</td>
                        </tr>
                        <tr>
                            <td>Tea:</td>
                            <td>${shop.tea}/4</td>
                        </tr>
                        <tr>
                            <td>Bonus:</td>
                            <td>${shop.bonus}/1</td>
                        </tr>
                        <tr class="total-row">
                            <td>Total:</td>
                            <td>${shop.boba + shop.tea + shop.bonus}/10</td>
                        </tr>
                    </table>
                    <div class="review-link-container">
                        <a href="https://blog.legoktm.com${shop.reviewLink}" class="review-link" target="_blank">Full Review</a>
                        <div>${trains}</div>
                    </div>
                </div>
            `;

            marker.bindPopup(popupContent);
        });

        // Populate recent reviews
        function populateRecentReviews() {
            const recentReviews = document.getElementById('recent-reviews');
            // Sort shops by review link date (newest first)
            const sortedShops = [...shops].sort((a, b) => b.reviewLink.localeCompare(a.reviewLink));
            // Take only the 5 most recent
            const recentShops = sortedShops.slice(0, 5);

            recentShops.forEach(shop => {
                let trains = "";
                shop.trains.forEach(train => {
                    trains += `<img class="subway" src="icons/${train}.svg" alt="${train} train" title="${train} train">`;
                });

                const reviewCard = document.createElement('div');
                reviewCard.className = 'review-card';
                reviewCard.innerHTML = `
                    <h3>${shop.name}</h3>
                    <div class="rating">Rating: ${shop.boba + shop.tea + shop.bonus}/10</div>
                    <div class="trains">${trains}</div>
                `;

                // Add click handler to show popup
                reviewCard.addEventListener('click', () => {
                    const marker = markers.get(shop.name);
                    if (marker) {
                        marker.openPopup();
                        map.setView([shop.lat, shop.lng], 15);
                    }
                });

                recentReviews.appendChild(reviewCard);
            });
        }

        // Initialize everything when DOM is loaded
        document.addEventListener('DOMContentLoaded', () => {
            populateRecentReviews();

            // Modal handlers
            const openModal = (type)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment