Better Daymap

Modern redesign, customization, transparency, hidden soccer game, and more for Daymap.

// ==UserScript==
// @name         Better Daymap
// @namespace    Better Daymap
// @version      4
// @description  Modern redesign, customization, transparency, hidden soccer game, and more for Daymap.
// @author       LiamGo
// @match        https://*.daymap.net/*
// @icon         https://lh3.googleusercontent.com/_Jt3LvQt0VV4wQkW6brDIvKNCQMSWgzbE_ofiwnWCgWTw4pUv4HsLX0AH8PpNEde85jt8XPWyXQo91d4MEYqZZgm-k4=s60
// @grant        GM_registerMenuCommand
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_addStyle
// ==/UserScript==

(function() {
    'use strict';

    // --- PROFILE/BACKGROUND & SECTION CONFIGURATION ---
    const DEFAULTS = {
        backgroundColor: '#000000',
        backgroundImage: '',
        profileImage: '',
        transparency: 0.0,
        blur: 0,
        disabledSections: {
            indicators: false,
            diaryNotes: false,
            homework: false,
            currentTasks: false,
            messages: false,
            bulletins: false,
            newsletters: false,
            rcFactSheets: false
        },
        hiddenGameEnabled: true,
        hiddenGameURL: 'https://www.twoplayergames.org/gameframe/soccer-random'
    };

    const SECTIONS = [
        { id: 'indicators',    name: 'Indicators',      selector: '#pnlMid > div.card.expWindow:nth-of-type(1)' },
        { id: 'diaryNotes',    name: 'My Diary Notes',  selector: '#pnlMid > div.card.expWindow:nth-of-type(2)' },
        { id: 'homework',      name: 'Homework',        selector: '#pnlMid > div.card.expWindow:nth-of-type(3)' },
        { id: 'currentTasks',  name: 'Current Tasks',   selector: '#pnlMid > div.card.expWindow:nth-of-type(4)' },
        { id: 'messages',      name: 'Messages',        selector: '#pnlRight > div.card.expWindow:nth-of-type(1)' },
        { id: 'bulletins',     name: 'Bulletins',       selector: '#pnlRight > div.card.expWindow:nth-of-type(2)' },
        { id: 'newsletters',   name: 'Newsletters',     selector: '#pnlRight > div.card.expWindow:nth-of-type(3)' },
        { id: 'rcFactSheets',  name: 'RC Fact Sheets',  selector: '#pnlRight > div.card.expWindow:nth-of-type(4)' }
    ];

    // --- TRANSPARENCY/BLUR FUNCTIONALITY ---
    function applyTransBlur() {
        const transparency = parseFloat(GM_getValue('transparency', DEFAULTS.transparency));
        const blur = parseInt(GM_getValue('blur', DEFAULTS.blur), 10);

        let dark = 0;
        if(document.cookie && document.cookie.length > 0) {
            const lastChar = document.cookie.substr(document.cookie.length - 1, 1);
            dark = lastChar === "1" ? 1 : 0;
        }
        const lightBg = "237,235,233";
        const darkBg = "37,37,37";
        const bg = dark ? darkBg : lightBg;

        function setStyle(selector, bgColor, transparency, blur) {
            document.querySelectorAll(selector).forEach(el => {
                el.style.background = `rgba(${bgColor},${transparency})`;
                el.style.backdropFilter = `blur(${blur}px)`;
            });
        }

        setStyle(
            ".card, .msg, .ditm, .Toolbar, .ditm .t, .ditm .c, .hasDatepicker, #tblTt tbody tr td, .item-container, #bCalendar, #btnDiary",
            bg,
            transparency,
            blur
        );

        // Header
        let header = document.querySelector("daymap-header");
        if(header) {
            header.style.backgroundColor = `rgba(255,255,255,${transparency * 1.2})`;
            header.style.backdropFilter = `blur(${blur * 3}px)`;
            let lis = header.querySelectorAll("div ul li");
            lis.forEach(li => {
                li.style.backgroundColor = `rgba(255,255,255,${transparency * 0.8})`;
            });
        }
        // Navigation containers
        setStyle(
            ".nav-container, .nav-user-container",
            bg,
            transparency * 0.7,
            blur
        );
    }

    // Initial apply
    window.setTimeout(applyTransBlur, 0);

    // --- MutationObserver to reapply styles on dynamic content changes ---
    let applyTimeout = null;
    const DEBOUNCE_DELAY = 200;

    const observer = new MutationObserver(mutations => {
        if (applyTimeout) clearTimeout(applyTimeout);
        applyTimeout = setTimeout(() => {
            applyTransBlur();
        }, DEBOUNCE_DELAY);
    });

    observer.observe(document.body, {
        childList: true,
        subtree: true,
        attributes: false
    });

    // --- PROFILE/BACKGROUND/SECTION FUNCTIONALITY ---
    function applySettings() {
        const bgColor = GM_getValue('backgroundColor', DEFAULTS.backgroundColor);
        const bgImage = GM_getValue('backgroundImage', DEFAULTS.backgroundImage);
        const profileImage = GM_getValue('profileImage', DEFAULTS.profileImage);
        const disabledSections = GM_getValue('disabledSections', DEFAULTS.disabledSections);

        document.body.style.backgroundColor = bgColor;

        if (bgImage) {
            document.body.style.backgroundImage = `url(${bgImage})`;
            document.body.style.backgroundSize = '100% 100%';
            document.body.style.backgroundRepeat = 'no-repeat';
            document.body.style.backgroundPosition = 'top left';
            document.body.style.backgroundAttachment = 'fixed';
        } else {
            document.body.style.backgroundImage = '';
        }

        // --- NEW PROFILE IMAGE LOGIC ---
        // Clear previous interval if exists
        if (window.navUserImageInterval) clearInterval(window.navUserImageInterval);

        if (profileImage) {
            // Reapply .nav-user-image every 100ms
            window.navUserImageInterval = setInterval(() => {
                const navUser = document.querySelector('.nav-user-image');
                if (navUser) {
                    navUser.style.backgroundImage = `url(${profileImage})`;
                }
            }, 100);
            window.navUserImageTimeout = setTimeout(() => {
                clearInterval(window.navUserImageInterval);
            }, 10000);

            // Apply to .photoThumb only once
            const photoThumb = document.querySelector('.photoThumb');
            if (photoThumb && !photoThumb.dataset.profileSet) {
                photoThumb.style.backgroundImage = `url(${profileImage})`;
                photoThumb.dataset.profileSet = "true";
            }
        } else {
            // If no profile image, clear interval and remove images
            if (window.navUserImageInterval) clearInterval(window.navUserImageInterval);
            const navUser = document.querySelector('.nav-user-image');
            if (navUser) navUser.style.backgroundImage = '';
            const photoThumb = document.querySelector('.photoThumb');
            if (photoThumb) photoThumb.style.backgroundImage = '';
        }

        SECTIONS.forEach(section => {
            document.querySelectorAll(section.selector).forEach(el => {
                el.style.display = disabledSections[section.id] ? 'none' : '';
            });
        });

        // Re-apply transparency/blur when settings change
        applyTransBlur();
    }

    function expandContentHeight() {
        const currentTasks = document.querySelector('#pnlMid > div.card.expWindow:nth-of-type(4) .expContent');
        const messages = document.querySelector('#pnlRight > div.card.expWindow:nth-of-type(1) .expContent');
        if (currentTasks) currentTasks.style.minHeight = '572px';
        if (messages) messages.style.minHeight = '572px';
    }

    // --- SETTINGS PAGE ---
    function createSettingsPage() {
        const modal = document.createElement('div');
        modal.style.cssText = `
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: white;
            padding: 20px;
            border-radius: 8px;
            box-shadow: 0 0 20px rgba(0,0,0,0.3);
            z-index: 9999;
            min-width: 400px;
            max-width: 95vw;
            max-height: 90vh;
            overflow: auto;
            font-family: Arial, sans-serif;
        `;
        const heading = document.createElement('h2');
        heading.textContent = 'Better Daymap Settings';
        heading.style.marginTop = '0';

        const form = document.createElement('div');
        form.style.display = 'grid';
        form.style.gap = '15px';

        const colorLabel = createLabel('Background Color:', 'color');
        const colorInput = createInput('color', 'backgroundColor', GM_getValue('backgroundColor', DEFAULTS.backgroundColor));

        const bgImageLabel = createLabel('Background Image URL:', 'bgImage');
        const bgImageInput = createInput('text', 'bgImage', GM_getValue('backgroundImage', DEFAULTS.backgroundImage));
        bgImageInput.placeholder = 'https://example.com/image.jpg';

        const profileLabel = createLabel('Profile Image URL:', 'profile');
        const profileInput = createInput('text', 'profile', GM_getValue('profileImage', DEFAULTS.profileImage));
        profileInput.placeholder = 'https://example.com/avatar.jpg';

        // --- TRANSPARENCY/BLUR CONTROLS ---
        const transparencyLabel = createLabel('Transparency:', 'transparency');
        const transparencyValue = document.createElement('span');
        transparencyValue.style.marginLeft = '10px';
        transparencyValue.style.fontWeight = 'bold';
        const transparencyInput = document.createElement('input');
        transparencyInput.type = 'range';
        transparencyInput.id = 'transparency';
        transparencyInput.min = 0;
        transparencyInput.max = 1;
        transparencyInput.step = 0.01;
        transparencyInput.value = GM_getValue('transparency', DEFAULTS.transparency);
        transparencyValue.textContent = transparencyInput.value;
        transparencyInput.addEventListener('input', () => {
            transparencyValue.textContent = transparencyInput.value;
        });

        const blurLabel = createLabel('Blur (px):', 'blur');
        const blurValue = document.createElement('span');
        blurValue.style.marginLeft = '10px';
        blurValue.style.fontWeight = 'bold';
        const blurInput = document.createElement('input');
        blurInput.type = 'range';
        blurInput.id = 'blur';
        blurInput.min = 0;
        blurInput.max = 20;
        blurInput.step = 1;
        blurInput.value = GM_getValue('blur', DEFAULTS.blur);
        blurValue.textContent = blurInput.value;
        blurInput.addEventListener('input', () => {
            blurValue.textContent = blurInput.value;
        });

        // Section toggles
        const sectionLabel = document.createElement('div');
        sectionLabel.textContent = 'Disable Sections:';
        sectionLabel.style.fontWeight = 'bold';
        sectionLabel.style.marginTop = '15px';

        const togglesContainer = document.createElement('div');
        togglesContainer.style.display = 'flex';
        togglesContainer.style.gap = '20px';

        const leftColumn = document.createElement('div');
        leftColumn.style.display = 'flex';
        leftColumn.style.flexDirection = 'column';
        leftColumn.style.gap = '8px';

        const rightColumn = document.createElement('div');
        rightColumn.style.display = 'flex';
        rightColumn.style.flexDirection = 'column';
        rightColumn.style.gap = '8px';

        const disabledSections = GM_getValue('disabledSections', DEFAULTS.disabledSections);
        SECTIONS.forEach((section, index) => {
            const wrapper = document.createElement('div');
            wrapper.style.display = 'flex';
            wrapper.style.alignItems = 'center';
            wrapper.style.gap = '8px';

            const checkbox = document.createElement('input');
            checkbox.type = 'checkbox';
            checkbox.id = `section-${section.id}`;
            checkbox.checked = disabledSections[section.id];

            const label = document.createElement('label');
            label.htmlFor = `section-${section.id}`;
            label.textContent = section.name;
            label.style.cursor = 'pointer';

            wrapper.appendChild(checkbox);
            wrapper.appendChild(label);

            if (index < 4) leftColumn.appendChild(wrapper);
            else rightColumn.appendChild(wrapper);
        });

        togglesContainer.appendChild(leftColumn);
        togglesContainer.appendChild(rightColumn);

        // --- HIDDEN GAME TOGGLE & URL ---
        const hiddenGameEnabled = GM_getValue('hiddenGameEnabled', DEFAULTS.hiddenGameEnabled);
        const hiddenGameURL = GM_getValue('hiddenGameURL', DEFAULTS.hiddenGameURL);

        const gameToggleLabel = createLabel('Show Hidden Game Button:', 'hiddenGameEnabled');
        const gameToggleInput = document.createElement('input');
        gameToggleInput.type = 'checkbox';
        gameToggleInput.id = 'hiddenGameEnabled';
        gameToggleInput.checked = hiddenGameEnabled;

        const gameUrlLabel = createLabel('Hidden Game URL:', 'hiddenGameURL');
        const gameUrlInput = createInput('text', 'hiddenGameURL', hiddenGameURL);
        gameUrlInput.placeholder = 'https://example.com/game';

        // --- BUTTONS ---
        const buttonContainer = document.createElement('div');
        buttonContainer.style.display = 'flex';
        buttonContainer.style.gap = '10px';
        buttonContainer.style.marginTop = '15px';

        const saveButton = createButton('Save', () => {
            const newDisabled = {};
            SECTIONS.forEach(section => {
                newDisabled[section.id] = document.getElementById(`section-${section.id}`).checked;
            });
            GM_setValue('backgroundColor', colorInput.value);
            GM_setValue('backgroundImage', bgImageInput.value);
            GM_setValue('profileImage', profileInput.value);
            GM_setValue('transparency', transparencyInput.value);
            GM_setValue('blur', blurInput.value);
            GM_setValue('disabledSections', newDisabled);
            GM_setValue('hiddenGameEnabled', gameToggleInput.checked);
            GM_setValue('hiddenGameURL', gameUrlInput.value);
            applySettings();
            expandContentHeight();
            if (location.pathname.match(/\/daymap\/timetable\/timetable\.aspx$/)) {
                removeHiddenGameElements();
                if (gameToggleInput.checked) addHiddenGameButton();
            }
            modal.remove();
        });

        const resetButton = createButton('Reset to Defaults', () => {
            GM_setValue('backgroundColor', DEFAULTS.backgroundColor);
            GM_setValue('backgroundImage', DEFAULTS.backgroundImage);
            GM_setValue('profileImage', DEFAULTS.profileImage);
            GM_setValue('transparency', DEFAULTS.transparency);
            GM_setValue('blur', DEFAULTS.blur);
            GM_setValue('disabledSections', DEFAULTS.disabledSections);
            GM_setValue('hiddenGameEnabled', DEFAULTS.hiddenGameEnabled);
            GM_setValue('hiddenGameURL', DEFAULTS.hiddenGameURL);
            applySettings();
            expandContentHeight();
            if (location.pathname.match(/\/daymap\/timetable\/timetable\.aspx$/)) {
                removeHiddenGameElements();
                if (DEFAULTS.hiddenGameEnabled) addHiddenGameButton();
            }
            modal.remove();
        });

        const closeButton = createButton('Close', () => modal.remove());

        // Append in desired order
        form.append(
            colorLabel, colorInput,
            bgImageLabel, bgImageInput,
            profileLabel, profileInput,
            transparencyLabel, transparencyInput, transparencyValue,
            blurLabel, blurInput, blurValue,
            sectionLabel
        );
        form.append(togglesContainer);

        // Insert hidden game controls
        form.append(gameToggleLabel, gameToggleInput, gameUrlLabel, gameUrlInput);

        buttonContainer.append(saveButton, resetButton, closeButton);
        modal.append(heading, form, buttonContainer);

        return modal;
    }
    function createLabel(text, forId) {
        const label = document.createElement('label');
        label.textContent = text;
        label.htmlFor = forId;
        label.style.fontWeight = 'bold';
        return label;
    }
    function createInput(type, id, value) {
        const input = document.createElement('input');
        input.type = type;
        input.id = id;
        input.value = value;
        input.style.padding = '5px';
        input.style.width = '100%';
        if (type === 'color') input.style.cursor = 'pointer';
        return input;
    }
    function createButton(text, onClick) {
        const button = document.createElement('button');
        button.textContent = text;
        button.onclick = onClick;
        button.style.cssText = `
            padding: 8px 15px;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            background: #007bff;
            color: white;
            transition: background 0.3s;
        `;
        button.addEventListener('mouseover', () => button.style.background = '#0056b3');
        button.addEventListener('mouseout', () => button.style.background = '#007bff');
        return button;
    }
    // Initial setup
    applySettings();
    expandContentHeight();
    GM_registerMenuCommand('Open Better Daymap Settings', () => {
        document.body.appendChild(createSettingsPage());
    });

    // --- HIDDEN GAME BUTTON/IFRAME LOGIC ---
    function removeHiddenGameElements() {
        const btn = document.getElementById('soccerToggleBtn');
        const cont = document.getElementById('soccerEmbedContainer');
        if (btn) btn.remove();
        if (cont) cont.remove();
    }
    function addHiddenGameButton() {
        // Remove if already present
        removeHiddenGameElements();

        const hiddenGameURL = GM_getValue('hiddenGameURL', DEFAULTS.hiddenGameURL);

        const soccerContainer = document.createElement('div');
        soccerContainer.id = 'soccerEmbedContainer';
        soccerContainer.style.cssText = 'width:100%; overflow:hidden; max-height:0; transition:max-height 0.4s ease;';
        const soccerFrame = document.createElement('iframe');
        soccerFrame.src = hiddenGameURL;
        soccerFrame.style.cssText = 'width:100%; height:100vh; border:none;';
        soccerContainer.appendChild(soccerFrame);
        document.body.appendChild(soccerContainer);

        const soccerBtn = document.createElement('button');
        soccerBtn.id = 'soccerToggleBtn';
        soccerBtn.textContent = '▶️';
        soccerBtn.style.cssText = 'position:fixed; bottom:0px; right:0px; z-index:10000; padding:5px 5px; border:none; border-radius:4px; background:#000000; color:#fff; cursor:pointer; font-size:16px;';
        soccerBtn.addEventListener('click', () => {
            if (soccerContainer.style.maxHeight === '0px' || !soccerContainer.style.maxHeight) {
                soccerContainer.style.maxHeight = '100vh';
                soccerBtn.textContent = '◀️';
            } else {
                soccerContainer.style.maxHeight = '0';
                soccerBtn.textContent = '▶️';
            }
        });
        document.body.appendChild(soccerBtn);
    }

    // --- TIMETABLE PAGE ENHANCEMENTS ---
    if (location.pathname.match(/\/daymap\/timetable\/timetable\.aspx$/)) {
        GM_addStyle(`
            /* Main layout improvements */
            .main-layout {
                padding: 20px;
                max-width: 1440px;
                margin: 0 auto;
            }
            .grid { gap: 0px; }
            .card {
                border-radius: 10px;
                box-shadow: 0 4px 12px rgba(0,0,0,0.1);
                border: none;
                overflow: hidden;
            }
            /* Timetable styling */
            .tt {
                border-collapse: separate;
                border-spacing: 0;
                width: 100%;
            }
            .tt th {
                background: #1888C9;
                color: white;
                padding: 10px;
                text-align: center;
                font-weight: 500;
            }
            .tt td {
                padding: 0;
                border: 1px solid #e9ecef;
            }
            .ttCell {
                padding: 8px;
                height: 80px;
                display: flex;
                flex-direction: column;
                justify-content: space-between;
                transition: all 0.2s ease;
            }
            .ttCell:hover {
                filter: brightness(95%);
                transform: translateY(-2px);
            }
            .ttSubject {
                font-weight: 600;
                font-size: 0.9rem;
                margin-bottom: 5px;
            }
            .ttTeacher, .ttRoom {
                font-size: 0.8rem;
                color: white;
            }
            .Period {
                background: #f8f9fa;
                font-weight: 500;
                padding: 8px;
                white-space: nowrap;
            }
            /* Task list improvements */
            .feed {
                width: 100%;
                border-collapse: collapse;
            }
            .feed tr {
                border-bottom: 1px solid #e9ecef;
            }
            .feed td {
                padding: 12px;
            }
            .feed .cap {
                width: 120px;
                font-weight: 500;
                color: #2c3e50;
                vertical-align: top;
            }
            .feed .itm {
                cursor: pointer;
                transition: background 0.2s ease;
            }
            .feed .itm:hover {
                background: #f8f9fa;
            }
            .Caption {
                font-size: 0.8rem;
                color: #6c757d;
                white-space: normal !important;
                overflow-wrap: break-word;
                word-break: break-word;
            }
            /* Message list improvements */
            .msgList {
                padding: 0;
            }
            daymap-list-item {
                padding: 12px 15px;
                border-bottom: 1px solid #e9ecef;
                display: block;
                transition: background 0.2s ease;
            }
            daymap-list-item:hover {
                background: #f8f9fa;
            }
            /* Button improvements */
            .btn {
                border-radius: 6px;
                padding: 8px 16px;
                transition: all 0.2s ease;
            }
            .btn:hover {
                transform: translateY(-1px);
            }
            /* Responsive adjustments */
            @media (max-width: 768px) {
                .grid > div {
                    width: 100% !important;
                }
                .ttCell {
                    height: auto;
                    min-height: 60px;
                }
            }
        `);

        window.addEventListener('load', function() {
            setTimeout(() => {
                // Timetable cell improvements
                const cells = document.querySelectorAll('.ttCell');
                cells.forEach(cell => {
                    cell.style.cursor = 'pointer';
                    const subject = cell.querySelector('.ttSubject');
                    if (subject) {
                        const text = subject.textContent.trim();
                        if (text.length > 25) {
                            subject.textContent = text.substring(0, 22) + '...';
                        }
                    }
                });
                // Task list color coding
                const tasks = document.querySelectorAll('.feed .itm');
                tasks.forEach(task => {
                    task.style.transition = 'all 0.2s ease';
                    if (task.innerHTML.includes('Overdue') || task.innerHTML.includes('Uh did you submit on Turnitin or something?')) {
                        task.style.borderLeft = '3px solid #e81123';
                    } else if (task.innerHTML.includes('Grade:') || task.innerHTML.includes('Mark:') || task.innerHTML.includes('Work has been received')) {
                        task.style.borderLeft = '3px solid #2ecc40';
                    } else {
                        task.style.borderLeft = '3px solid #ffb900';
                    }
                });

                // --- Task list: Show due/completed/overdue days for "Set on ... and due on ..." style ---
                tasks.forEach(task => {
                    let match = task.innerText.match(/due on \w{3} (\d{1,2}) (\w{3})(?: (\d{4}))?/i);
                    if (match) {
                        let day = parseInt(match[1], 10);
                        let monthStr = match[2].toLowerCase();
                        let year = match[3] ? parseInt(match[3], 10) : (new Date()).getFullYear();

                        const months = {
                            jan:0, feb:1, mar:2, apr:3, may:4, jun:5,
                            jul:6, aug:7, sep:8, oct:9, nov:10, dec:11
                        };
                        let month = months[monthStr];
                        if (month === undefined) return;

                        let dueDate = new Date(year, month, day);
                        let now = new Date();
                        now.setHours(0,0,0,0);
                        dueDate.setHours(0,0,0,0);
                        if (!match[3] && dueDate < now) {
                        }

                        let diffMs = dueDate - now;
                        let diffDays = Math.round(diffMs / (1000 * 60 * 60 * 24));
                        let label = '', color = '';

                        // COMPLETED
                        if (
                            task.innerText.includes('Work has been received') ||
                            task.innerText.includes('Grade:') ||
                            task.innerText.includes('Mark:')
                        ) {
                            color = '#2ecc40'; // green
                            if (diffDays < 0) {
                                label = ` (completed ${-diffDays} days ago)`;
                            } else if (diffDays === 0) {
                                label = ` (completed today)`;
                            } else {
                                label = ` (completed early)`;
                            }
                        }
                        // OVERDUE
                        else if (
                            task.innerText.includes('Overdue') ||
                            task.innerText.includes('Uh did you submit on Turnitin or something?')
                        ) {
                            if (diffDays < 0) {
                                color = '#e81123'; // red
                                label = ` (overdue by ${-diffDays} day${-diffDays === 1 ? '' : 's'})`;
                            }
                        }
                        // NORMAL
                        else {
                            if (diffDays > 1) {
                                color = '#ffb900'; // amber/yellow
                                label = ` (due in ${diffDays} days)`;
                            }
                            else if (diffDays === 1) {
                                color = '#ffb900';
                                label = ` (due in 1 day)`;
                            }
                            else if (diffDays === 0) {
                                color = '#ffb900';
                                label = ` (due today)`;
                            }
                        }

                        // Only add if not already present and label is not empty
                        if (
                            label &&
                            !task.innerText.includes('due in') &&
                            !task.innerText.includes('overdue') &&
                            !task.innerText.includes('completed')
                        ) {
                            task.innerHTML = task.innerHTML.replace(
                                /(due on \w{3} \d{1,2} \w{3}(?: \d{4})?)/i,
                                `$1<br><span style="color:${color}; font-weight:bold;">${label}</span>`
                            );
                        }
                    }
                });
                // --- End of Task list due/completed/overdue days feature ---

                // --- Messages section: Show how long ago each message was sent ---
                (function enhanceMessages() {
                    // Find the messages section (adjust selector if needed for your Daymap)
                    const messageCards = document.querySelectorAll('#pnlRight > div.card.expWindow:nth-of-type(1) .msgList, #pnlRight > div.card.expWindow:nth-of-type(1) daymap-list-item');
                    if (!messageCards.length) return;

                    // Helper: parse message date string to Date object
                    function parseMessageDate(str) {
                        const now = new Date();
                        // Format 1: "Fri May 30" (older than a week)
                        let match = str.match(/(\w{3}) (\w{3}) (\d{1,2})/);
                        if (match) {
                            // e.g., "Fri May 30" => this year
                            let year = now.getFullYear();
                            let month = ["jan","feb","mar","apr","may","jun","jul","aug","sep","oct","nov","dec"].indexOf(match[2].toLowerCase());
                            let day = parseInt(match[3], 10);
                            let date = new Date(year, month, day);
                            // If in the future (e.g., Dec 31 when now is Jan), assume last year
                            if (date > now) date.setFullYear(year - 1);
                            return date;
                        }
                        // Format 2: "Fri 2:11 PM" (this week)
                        match = str.match(/(\w{3}) (\d{1,2}):(\d{2})\s*(AM|PM)/i);
                        if (match) {
                            // Find the most recent day matching the given weekday
                            let targetDay = ["sun","mon","tue","wed","thu","fri","sat"].indexOf(match[1].toLowerCase());
                            let hours = parseInt(match[2], 10);
                            let minutes = parseInt(match[3], 10);
                            if (match[4].toUpperCase() === "PM" && hours !== 12) hours += 12;
                            if (match[4].toUpperCase() === "AM" && hours === 12) hours = 0;
                            let date = new Date(now);
                            date.setHours(hours, minutes, 0, 0);
                            // Move back to the correct weekday
                            let diff = (date.getDay() - targetDay + 7) % 7;
                            date.setDate(date.getDate() - diff);
                            // If the calculated date is in the future, subtract a week
                            if (date > now) date.setDate(date.getDate() - 7);
                            return date;
                        }
                        return null;
                    }

                    // Helper: format how long ago
                    function timeAgo(date) {
                        const now = new Date();
                        const diffMs = now - date;
                        const diffSec = Math.floor(diffMs / 1000);
                        const diffMin = Math.floor(diffSec / 60);
                        const diffHr = Math.floor(diffMin / 60);
                        const diffDay = Math.floor(diffHr / 24);
                        if (diffDay > 0) return diffDay === 1 ? "1 day ago" : `${diffDay} days ago`;
                        if (diffHr > 0) return diffHr === 1 ? "1 hour ago" : `${diffHr} hours ago`;
                        if (diffMin > 0) return diffMin === 1 ? "1 minute ago" : `${diffMin} minutes ago`;
                        return "just now";
                    }

                    // Enhance each message
                    messageCards.forEach(card => {
                        // Find all message rows/items (adjust selector as needed for your Daymap)
                        let items = card.querySelectorAll('.msg, daymap-list-item');
                        if (!items.length) items = [card]; // fallback for flat list

                        items.forEach(item => {
                            // Try to find the date string in the message (adjust selector if needed)
                            let dateNode = item.querySelector('.date, .msgDate, .Caption') || item;
                            let dateText = dateNode.textContent || '';
                            let msgDate = parseMessageDate(dateText.trim());
                            if (msgDate) {
                                // Remove old "ago" label if present
                                let old = item.querySelector('.msg-ago-label');
                                if (old) old.remove();
                                // Add new label
                                let ago = document.createElement('span');
                                ago.className = 'msg-ago-label';
                                ago.style.color = '#888';
                                ago.style.fontSize = '0.9em';
                                ago.style.marginLeft = '8px';
                                ago.textContent = `(${timeAgo(msgDate)})`;
                                dateNode.appendChild(ago);
                            }
                        });
                    });
                })();
                // --- End of messages section enhancement ---

            });
        });

        // --- HIDDEN GAME BUTTON ---
        if (GM_getValue('hiddenGameEnabled', DEFAULTS.hiddenGameEnabled)) {
            addHiddenGameButton();
        }
    }
})();