FlatMMO Update Log Beautifier

Makes FlatMMO update log beautiful and organized by month with clean formatting

// ==UserScript==
// @name         FlatMMO Update Log Beautifier
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  Makes FlatMMO update log beautiful and organized by month with clean formatting
// @author       Pizza1337
// @match        *://flatmmo.com/updatelog*
// @icon         https://flatmmo.com/favicon.ico
// @grant        none
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // Wait for the page to load
    window.addEventListener('load', function() {
        beautifyUpdateLog();
    });

    function beautifyUpdateLog() {
        // Get the raw text content
        const rawText = document.body.innerText || document.body.textContent;

        // Parse the updates
        const updates = parseUpdates(rawText);

        // Group by month
        const groupedUpdates = groupUpdatesByMonth(updates);

        // Create the new beautiful interface
        createBeautifulInterface(groupedUpdates);
    }

    function parseUpdates(text) {
        const lines = text.split('\n');
        const updates = [];
        let currentUpdate = null;

        const monthNames = ['January', 'February', 'March', 'April', 'May', 'June',
                           'July', 'August', 'September', 'October', 'November', 'December'];

        for (let line of lines) {
            line = line.trim();
            if (!line) continue;

            // Check if it's a date line - now handles ordinal suffixes (1st, 2nd, 3rd, 4th, etc.)
            const dateMatch = line.match(/^(January|February|March|April|May|June|July|August|September|October|November|December)\s+(\d{1,2})(?:st|nd|rd|th)?,?\s+(\d{4})(?:\s*\(part\s*(\d+)\))?/i);

            if (dateMatch) {
                if (currentUpdate && currentUpdate.items.length > 0) {
                    updates.push(currentUpdate);
                }

                currentUpdate = {
                    date: line,
                    month: dateMatch[1],
                    day: parseInt(dateMatch[2]),
                    year: parseInt(dateMatch[3]),
                    part: dateMatch[4] || '',
                    items: [],
                    notes: []
                };
            } else if (currentUpdate) {
                // Check if it's a bullet point
                if (line.startsWith('*')) {
                    let item = line.substring(1).trim();
                    // Capitalize first letter if it starts with a lowercase letter
                    if (item.length > 0 && /^[a-z]/.test(item[0])) {
                        item = item.charAt(0).toUpperCase() + item.slice(1);
                    }
                    currentUpdate.items.push(item);
                } else if (line.startsWith('-') && !line.startsWith('---')) {
                    let item = line.substring(1).trim();
                    // Capitalize first letter if it starts with a lowercase letter
                    if (item.length > 0 && /^[a-z]/.test(item[0])) {
                        item = item.charAt(0).toUpperCase() + item.slice(1);
                    }
                    currentUpdate.items.push(item);
                } else if (line.startsWith('->')) {
                    // Sub-item - capitalize it too if needed
                    let subItem = line.substring(2).trim();
                    if (subItem.length > 0 && /^[a-z]/.test(subItem[0])) {
                        subItem = subItem.charAt(0).toUpperCase() + subItem.slice(1);
                    }
                    if (currentUpdate.items.length > 0) {
                        currentUpdate.items[currentUpdate.items.length - 1] += '\n    → ' + subItem;
                    }
                } else if (line.includes('***') || line.includes('---')) {
                    // Note separator
                    continue;
                } else {
                    // Could be a note or continuation
                    if (line.length > 10 && !line.match(/^(January|February|March|April|May|June|July|August|September|October|November|December)/i)) {
                        currentUpdate.notes.push(line);
                    }
                }
            }
        }

        // Don't forget the last update
        if (currentUpdate && currentUpdate.items.length > 0) {
            updates.push(currentUpdate);
        }

        return updates;
    }

    function groupUpdatesByMonth(updates) {
        const grouped = {};

        for (let update of updates) {
            const key = `${update.month} ${update.year}`;
            if (!grouped[key]) {
                grouped[key] = {
                    month: update.month,
                    year: update.year,
                    updates: []
                };
            }
            grouped[key].updates.push(update);
        }

        // Sort updates within each month by day (descending)
        for (let key in grouped) {
            grouped[key].updates.sort((a, b) => {
                if (a.day === b.day) {
                    // If same day, sort by part number
                    const partA = parseInt(a.part) || 0;
                    const partB = parseInt(b.part) || 0;
                    return partB - partA;
                }
                return b.day - a.day;
            });
        }

        return grouped;
    }

    function createBeautifulInterface(groupedUpdates) {
        // Clear the current page
        document.body.innerHTML = '';

        // Add styles
        const style = document.createElement('style');
        style.textContent = `
            @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');

            * {
                margin: 0;
                padding: 0;
                box-sizing: border-box;
            }

            body {
                font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
                background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
                min-height: 100vh;
                padding: 15px;
                line-height: 1.5;
            }

            .container {
                max-width: 1200px;
                margin: 0 auto;
                animation: fadeIn 0.5s ease-out;
            }

            @keyframes fadeIn {
                from { opacity: 0; transform: translateY(20px); }
                to { opacity: 1; transform: translateY(0); }
            }

            .header {
                background: rgba(255, 255, 255, 0.98);
                border-radius: 20px;
                padding: 30px;
                margin-bottom: 20px;
                box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
                backdrop-filter: blur(10px);
            }

            .header h1 {
                font-size: 2.5em;
                font-weight: 700;
                background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
                -webkit-background-clip: text;
                -webkit-text-fill-color: transparent;
                margin-bottom: 5px;
            }

            .header .subtitle {
                color: #6b7280;
                font-size: 1.2em;
            }

            .controls {
                background: rgba(255, 255, 255, 0.98);
                border-radius: 15px;
                padding: 15px;
                margin-bottom: 20px;
                box-shadow: 0 10px 30px rgba(0, 0, 0, 0.08);
                display: flex;
                gap: 15px;
                align-items: center;
                flex-wrap: wrap;
            }

            .search-box {
                flex: 1;
                min-width: 250px;
                position: relative;
            }

            .search-box input {
                width: 100%;
                padding: 10px 18px 10px 40px;
                border: 2px solid #e5e7eb;
                border-radius: 8px;
                font-size: 0.95em;
                transition: all 0.3s ease;
            }

            .search-box input:focus {
                outline: none;
                border-color: #667eea;
                box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
            }

            .search-box::before {
                content: "🔍";
                position: absolute;
                left: 12px;
                top: 50%;
                transform: translateY(-50%);
                font-size: 1em;
            }

            .filter-buttons {
                display: flex;
                gap: 10px;
            }

            .filter-btn {
                padding: 8px 16px;
                border: 2px solid #e5e7eb;
                background: white;
                border-radius: 8px;
                cursor: pointer;
                transition: all 0.3s ease;
                font-weight: 500;
                font-size: 0.95em;
            }

            .filter-btn:hover {
                border-color: #667eea;
                background: #f9fafb;
            }

            .filter-btn.active {
                background: #667eea;
                color: white;
                border-color: #667eea;
            }

            .stats {
                display: flex;
                gap: 12px;
                padding: 8px 16px;
                background: #f9fafb;
                border-radius: 8px;
            }

            .stat {
                display: flex;
                flex-direction: column;
                align-items: center;
            }

            .stat-value {
                font-size: 1.3em;
                font-weight: 700;
                color: #667eea;
            }

            .stat-label {
                font-size: 0.85em;
                color: #6b7280;
            }

            .month-section {
                background: rgba(255, 255, 255, 0.98);
                border-radius: 15px;
                margin-bottom: 15px;
                overflow: hidden;
                box-shadow: 0 10px 30px rgba(0, 0, 0, 0.08);
                transition: all 0.3s ease;
            }

            .month-section:hover {
                box-shadow: 0 12px 35px rgba(0, 0, 0, 0.1);
                transform: translateY(-1px);
            }

            .month-header {
                background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
                color: white;
                padding: 15px 25px;
                cursor: pointer;
                display: flex;
                justify-content: space-between;
                align-items: center;
                user-select: none;
            }

            .month-header h2 {
                font-size: 1.3em;
                font-weight: 600;
            }

            .month-header .toggle-icon {
                font-size: 1.5em;
                transition: transform 0.3s ease;
                transform: rotate(180deg);
            }

            .month-section.collapsed .toggle-icon {
                transform: rotate(0deg);
            }

            .month-content {
                max-height: none;
                overflow: hidden;
                transition: max-height 0.5s ease;
            }

            .month-section.collapsed .month-content {
                max-height: 0;
            }

            .update-day {
                padding: 15px 25px;
                border-bottom: 1px solid #e5e7eb;
            }

            .update-day:last-child {
                border-bottom: none;
            }

            .update-date {
                font-weight: 600;
                color: #374151;
                margin-bottom: 10px;
                font-size: 1em;
                display: flex;
                align-items: center;
                gap: 10px;
            }

            .update-date .day-badge {
                background: #f3f4f6;
                padding: 2px 8px;
                border-radius: 12px;
                font-size: 0.85em;
                font-weight: 500;
                color: #6b7280;
            }

            .update-items {
                list-style: none;
                padding-left: 0;
            }

            .update-item {
                padding: 8px 15px 8px 35px;
                margin-bottom: 5px;
                background: #f9fafb;
                border-radius: 8px;
                transition: all 0.3s ease;
                position: relative;
                list-style: none;
            }

            .update-item::before {
                content: "•";
                position: absolute;
                left: 15px;
                font-size: 1.1em;
                font-weight: bold;
                color: #9ca3af;
            }

            .update-item:hover {
                background: #f3f4f6;
                transform: translateX(3px);
            }

            .update-item:hover::before {
                color: #667eea;
            }

            .update-item.feature::before {
                color: #10b981;
            }

            .update-item.fix::before {
                color: #f59e0b;
            }

            .update-item.balance::before {
                color: #3b82f6;
            }

            .sub-item {
                display: block;
                margin-left: 15px;
                margin-top: 5px;
                padding-left: 10px;
                color: #6b7280;
                font-size: 0.95em;
            }

            .update-note {
                background: #fef3c7;
                border: 1px solid #fcd34d;
                border-radius: 8px;
                padding: 10px 15px;
                margin-top: 10px;
                color: #92400e;
                font-size: 0.95em;
            }

            .tag {
                display: inline-block;
                padding: 1px 6px;
                border-radius: 3px;
                font-size: 0.8em;
                font-weight: 500;
                margin-right: 6px;
            }

            .tag.fix {
                background: #fef3c7;
                color: #92400e;
            }

            .tag.balance {
                background: #dbeafe;
                color: #1e40af;
            }

            .highlight {
                background: #fef08a;
                padding: 2px 4px;
                border-radius: 3px;
            }

            .no-results {
                text-align: center;
                padding: 60px;
                color: #6b7280;
                font-size: 1.2em;
            }

            @media (max-width: 768px) {
                .header h1 {
                    font-size: 2em;
                }

                .controls {
                    flex-direction: column;
                }

                .search-box {
                    width: 100%;
                }

                .filter-buttons {
                    width: 100%;
                    justify-content: center;
                }
            }

            /* Dark mode support */
            @media (prefers-color-scheme: dark) {
                body {
                    background: linear-gradient(135deg, #1e293b 0%, #334155 100%);
                }

                .header, .controls, .month-section {
                    background: rgba(30, 41, 59, 0.98);
                }

                .header h1 {
                    background: linear-gradient(135deg, #818cf8 0%, #c084fc 100%);
                    -webkit-background-clip: text;
                    -webkit-text-fill-color: transparent;
                }

                .header .subtitle {
                    color: #94a3b8;
                }

                .search-box input, .filter-btn {
                    background: #1e293b;
                    border-color: #475569;
                    color: #e2e8f0;
                }

                .search-box input:focus {
                    border-color: #818cf8;
                    box-shadow: 0 0 0 3px rgba(129, 140, 248, 0.2);
                }

                .filter-btn.active {
                    background: #818cf8;
                }

                .stats {
                    background: #1e293b;
                }

                .stat-value {
                    color: #818cf8;
                }

                .month-header {
                    background: linear-gradient(135deg, #818cf8 0%, #c084fc 100%);
                }

                .update-day {
                    border-bottom-color: #334155;
                }

                .update-date {
                    color: #e2e8f0;
                }

                .update-date .day-badge {
                    background: #334155;
                    color: #94a3b8;
                }

                .update-item {
                    background: #1e293b;
                    color: #e2e8f0;
                }

                .update-item::before {
                    color: #64748b;
                }

                .update-item:hover {
                    background: #334155;
                }

                .update-item:hover::before {
                    color: #818cf8;
                }

                .update-item.feature::before {
                    color: #10b981;
                }

                .update-item.fix::before {
                    color: #f59e0b;
                }

                .update-item.balance::before {
                    color: #3b82f6;
                }

                .sub-item {
                    color: #94a3b8;
                }

                .update-note {
                    background: #422006;
                    border-color: #92400e;
                    color: #fef3c7;
                }
            }
        `;
        document.head.appendChild(style);

        // Create container
        const container = document.createElement('div');
        container.className = 'container';

        // Create header
        const header = document.createElement('div');
        header.className = 'header';
        header.innerHTML = `
            <h1>FlatMMO Update Log</h1>
            <div class="subtitle">Track all game updates and improvements</div>
        `;
        container.appendChild(header);

        // Create controls
        const controls = document.createElement('div');
        controls.className = 'controls';

        const searchBox = document.createElement('div');
        searchBox.className = 'search-box';
        searchBox.innerHTML = `
            <input type="text" id="searchInput" placeholder="Search updates...">
        `;
        controls.appendChild(searchBox);

        const filterButtons = document.createElement('div');
        filterButtons.className = 'filter-buttons';
        filterButtons.innerHTML = `
            <button class="filter-btn active" data-filter="all">All</button>
            <button class="filter-btn" data-filter="feature">Features</button>
            <button class="filter-btn" data-filter="fix">Fixes</button>
            <button class="filter-btn" data-filter="balance">Balance</button>
        `;
        controls.appendChild(filterButtons);

        // Calculate stats
        let totalUpdates = 0;
        let totalItems = 0;
        for (let key in groupedUpdates) {
            totalUpdates += groupedUpdates[key].updates.length;
            for (let update of groupedUpdates[key].updates) {
                totalItems += update.items.length;
            }
        }

        const stats = document.createElement('div');
        stats.className = 'stats';
        stats.innerHTML = `
            <div class="stat">
                <div class="stat-value">${Object.keys(groupedUpdates).length}</div>
                <div class="stat-label">Months</div>
            </div>
            <div class="stat">
                <div class="stat-value">${totalUpdates}</div>
                <div class="stat-label">Updates</div>
            </div>
            <div class="stat">
                <div class="stat-value">${totalItems}</div>
                <div class="stat-label">Changes</div>
            </div>
        `;
        controls.appendChild(stats);

        container.appendChild(controls);

        // Create update sections
        const updatesContainer = document.createElement('div');
        updatesContainer.id = 'updatesContainer';

        // Sort months (most recent first)
        const sortedMonths = Object.keys(groupedUpdates).sort((a, b) => {
            const [monthA, yearA] = a.split(' ');
            const [monthB, yearB] = b.split(' ');
            const monthOrder = ['January', 'February', 'March', 'April', 'May', 'June',
                               'July', 'August', 'September', 'October', 'November', 'December'];

            if (yearA !== yearB) {
                return parseInt(yearB) - parseInt(yearA);
            }
            return monthOrder.indexOf(monthB) - monthOrder.indexOf(monthA);
        });

        sortedMonths.forEach((monthKey, index) => {
            const monthData = groupedUpdates[monthKey];
            const monthSection = createMonthSection(monthData, true); // All months expanded by default
            updatesContainer.appendChild(monthSection);
        });

        container.appendChild(updatesContainer);
        document.body.appendChild(container);

        // Add event listeners
        setupEventListeners();
    }

    function createMonthSection(monthData, expanded = false) {
        const section = document.createElement('div');
        section.className = `month-section ${expanded ? '' : 'collapsed'}`;

        const header = document.createElement('div');
        header.className = 'month-header';
        header.innerHTML = `
            <h2>${monthData.month} ${monthData.year}</h2>
            <span class="toggle-icon">⬇</span>
        `;
        section.appendChild(header);

        const content = document.createElement('div');
        content.className = 'month-content';

        monthData.updates.forEach(update => {
            const dayDiv = document.createElement('div');
            dayDiv.className = 'update-day';

            const dateDiv = document.createElement('div');
            dateDiv.className = 'update-date';
            dateDiv.innerHTML = `
                ${update.month} ${update.day}, ${update.year}
                ${update.part ? `<span class="day-badge">Part ${update.part}</span>` : ''}
            `;
            dayDiv.appendChild(dateDiv);

            const itemsList = document.createElement('ul');
            itemsList.className = 'update-items';

            update.items.forEach(item => {
                const li = document.createElement('li');
                li.className = 'update-item';

                // Determine item type and add appropriate class
                const lowerItem = item.toLowerCase();
                if (lowerItem.includes('fix') || lowerItem.includes('bug')) {
                    li.classList.add('fix');
                } else if (lowerItem.includes('balance') || lowerItem.includes('buff') || lowerItem.includes('nerf')) {
                    li.classList.add('balance');
                } else {
                    li.classList.add('feature');
                }

                // Add tags for fixes and balance only
                let taggedItem = item;
                if (lowerItem.includes('fix') && lowerItem.indexOf('fix') < 20) {
                    taggedItem = `<span class="tag fix">FIX</span> ${taggedItem}`;
                } else if (lowerItem.includes('balance:') || lowerItem.startsWith('balance')) {
                    taggedItem = `<span class="tag balance">BALANCE</span> ${taggedItem}`;
                }

                // Format sub-items with proper indentation
                taggedItem = taggedItem.replace(/\n    →/g, '<br><span class="sub-item">→');
                taggedItem = taggedItem.replace(/<br><span class="sub-item">→/g, '<br><span class="sub-item">→ ') + (taggedItem.includes('<span class="sub-item">') ? '</span>' : '');

                li.innerHTML = taggedItem;
                itemsList.appendChild(li);
            });

            dayDiv.appendChild(itemsList);

            // Add notes if any
            if (update.notes.length > 0) {
                update.notes.forEach(note => {
                    const noteDiv = document.createElement('div');
                    noteDiv.className = 'update-note';
                    noteDiv.textContent = note;
                    dayDiv.appendChild(noteDiv);
                });
            }

            content.appendChild(dayDiv);
        });

        section.appendChild(content);
        return section;
    }

    function setupEventListeners() {
        // Month section toggle
        document.querySelectorAll('.month-header').forEach(header => {
            header.addEventListener('click', function() {
                const section = this.parentElement;
                section.classList.toggle('collapsed');
            });
        });

        // Search functionality
        const searchInput = document.getElementById('searchInput');
        searchInput.addEventListener('input', function() {
            const searchTerm = this.value.toLowerCase();
            filterUpdates(searchTerm);
        });

        // Filter buttons
        document.querySelectorAll('.filter-btn').forEach(btn => {
            btn.addEventListener('click', function() {
                document.querySelectorAll('.filter-btn').forEach(b => b.classList.remove('active'));
                this.classList.add('active');
                applyFilter(this.dataset.filter);
            });
        });

        // Expand/Collapse all shortcut (Ctrl+E)
        document.addEventListener('keydown', function(e) {
            if (e.ctrlKey && e.key === 'e') {
                e.preventDefault();
                const sections = document.querySelectorAll('.month-section');
                const anyExpanded = Array.from(sections).some(s => !s.classList.contains('collapsed'));

                sections.forEach(section => {
                    if (anyExpanded) {
                        section.classList.add('collapsed');
                    } else {
                        section.classList.remove('collapsed');
                    }
                });
            }
        });
    }

    function filterUpdates(searchTerm) {
        const items = document.querySelectorAll('.update-item');
        let visibleCount = 0;

        items.forEach(item => {
            const text = item.textContent.toLowerCase();
            if (text.includes(searchTerm)) {
                item.style.display = 'block';
                visibleCount++;

                // Highlight search term
                if (searchTerm) {
                    const regex = new RegExp(`(${searchTerm})`, 'gi');
                    item.innerHTML = item.innerHTML.replace(/<span class="highlight">([^<]+)<\/span>/g, '$1');
                    item.innerHTML = item.innerHTML.replace(regex, '<span class="highlight">$1</span>');
                }
            } else {
                item.style.display = 'none';
            }
        });

        // Hide empty sections
        document.querySelectorAll('.update-day').forEach(day => {
            const visibleItems = day.querySelectorAll('.update-item:not([style*="display: none"])');
            day.style.display = visibleItems.length > 0 ? 'block' : 'none';
        });

        document.querySelectorAll('.month-section').forEach(section => {
            const visibleDays = section.querySelectorAll('.update-day:not([style*="display: none"])');
            section.style.display = visibleDays.length > 0 ? 'block' : 'none';
        });

        // Show no results message if needed
        const container = document.getElementById('updatesContainer');
        const existingMessage = container.querySelector('.no-results');

        if (visibleCount === 0 && searchTerm) {
            if (!existingMessage) {
                const noResults = document.createElement('div');
                noResults.className = 'no-results';
                noResults.innerHTML = `No updates found matching "${searchTerm}"`;
                container.appendChild(noResults);
            }
        } else if (existingMessage) {
            existingMessage.remove();
        }
    }

    function applyFilter(filterType) {
        const items = document.querySelectorAll('.update-item');

        items.forEach(item => {
            if (filterType === 'all') {
                item.style.display = 'block';
            } else {
                item.style.display = item.classList.contains(filterType) ? 'block' : 'none';
            }
        });

        // Hide empty sections
        document.querySelectorAll('.update-day').forEach(day => {
            const visibleItems = day.querySelectorAll('.update-item:not([style*="display: none"])');
            day.style.display = visibleItems.length > 0 ? 'block' : 'none';
        });

        document.querySelectorAll('.month-section').forEach(section => {
            const visibleDays = section.querySelectorAll('.update-day:not([style*="display: none"])');
            section.style.display = visibleDays.length > 0 ? 'block' : 'none';
        });
    }
})();