KadWatch

Neopets Kadoatery tracker that looks cute and allows for splitting times.

Vous devrez installer une extension telle que Tampermonkey, Greasemonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Userscripts pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension de gestionnaire de script utilisateur pour installer ce script.

(J'ai déjà un gestionnaire de scripts utilisateur, laissez-moi l'installer !)

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

(J'ai déjà un gestionnaire de style utilisateur, laissez-moi l'installer!)

// ==UserScript==
// @name         KadWatch
// @version      0.47
// @description  Neopets Kadoatery tracker that looks cute and allows for splitting times.
// @author       Ryan (ext1nct)
// @match        http*://*.neopets.com/games/kadoatery/*
// @icon         https://itemdb.com.br/api/cache/preview/7f18f78e35daa6.png
// @grant        GM_setValue
// @grant        GM_getValue
// @license      MIT
// @namespace    https://greasyfork.org/users/custom
// ==/UserScript==
/* eslint-env jquery */

/**
 * Stored data keys
 */
const LAST_REFRESH_TIME_KEY = "lastRefreshTime"
const MAIN_KAD_DATA_KEY = "mainKadData"
const MAIN_HISTORY_KEY = "mainHistory"
const MINI_REFRESH_TIMES_KEY = "miniRefreshTimes"
const KAD_STATES_KEY = "kadStates"
const NOTIFICATION_OPT_IN_KEY = "notificationOptIn"
const KAD_FIRST_RUN_KEY = "kadFirstRun"
const PENDING_DROP_KEY = "pendingDrop"

// Settings Keys
const KAD_THEME_KEY = "kadTheme"
const KAD_AUDIO_PING_KEY = "kadAudioPing"
const KAD_WARNING_TIME_KEY = "kadWarningTime"
const KAD_24H_TIME_KEY = "kad24hTime"
const KAD_INCLUDE_FOOD_KEY = "kadIncludeFood"
const KAD_AUTO_CENSOR_KEY = "kadAutoCensor"

const DURATION_UNTIL_START_OF_MAIN_WINDOW_MS = 2100000 // 35 minutes
const DURATION_OF_MAIN_WINDOW_MS = 60000 // 60 seconds
const PEND_INTERVAL_MS = 420000 // 7 minutes

let notificationsTriggered = { main: 0, minis: {} };
let userIsLockedOut = false;
let editingTarget = null; 

let isDraggingModal = false;
let dragTarget = null;
let dragOffsetX = 0;
let dragOffsetY = 0;
let firstClickedIndex = null; // Used for range selection

function censorFood(name) {
    let doCensor = GM_getValue(KAD_AUTO_CENSOR_KEY, true);
    if (!doCensor) return name;
    
    return name
        .replace(/ball/gi, "b.all")
        .replace(/crack/gi, "crac.k")
        .replace(/weed/gi, "w.eed")
        .replace(/rape/gi, "r.ape")
        .replace(/cum/gi, "c.um");
}

// Inject KadWatch CSS with Vibrant Theme Variables
const STYLES = `
<style>
    :root {
        --kw-primary: #f472b6;
        --kw-secondary: #dbeafe;
        --kw-accent: #60a5fa;
        --kw-bg: #ffffff;
        --kw-panel: #fdf2f8;
        --kw-shadow: rgba(244, 114, 182, 0.15);
        --kw-text: #334155;
    }
    
    [data-kad-theme="kadwatch"] {
        --kw-primary: #f472b6; --kw-secondary: #dbeafe; --kw-accent: #60a5fa; --kw-panel: #fdf2f8; --kw-shadow: rgba(244, 114, 182, 0.15);
    }
    [data-kad-theme="green"] {
        --kw-primary: #22c55e; --kw-secondary: #bbf7d0; --kw-accent: #15803d; --kw-panel: #f0fdf4; --kw-shadow: rgba(34, 197, 94, 0.2);
    }
    [data-kad-theme="island"] {
        --kw-primary: #8b5a2b; --kw-secondary: #e6d5b8; --kw-accent: #2e8b57; --kw-panel: #fdfaf6; --kw-shadow: rgba(139, 90, 43, 0.2);
    }
    [data-kad-theme="spotted"] {
        --kw-primary: #171717; --kw-secondary: #fef08a; --kw-accent: #404040; --kw-panel: #fef9c3; --kw-shadow: rgba(23, 23, 23, 0.2);
    }
    [data-kad-theme="white"] {
        --kw-primary: #cbd5e1; --kw-secondary: #f1f5f9; --kw-accent: #64748b; --kw-panel: #f8fafc; --kw-shadow: rgba(100, 116, 139, 0.1);
    }
    [data-kad-theme="void"] {
        --kw-primary: #7c3aed; --kw-secondary: #334155; --kw-accent: #8b5cf6; --kw-bg: #0f172a; --kw-panel: #1e293b; --kw-shadow: rgba(0, 0, 0, 0.4); --kw-text: #f8fafc;
    }
    [data-kad-theme="rainbow"] {
        --kw-primary: #3b82f6; --kw-secondary: #e2e8f0; --kw-accent: #ec4899; --kw-bg: #ffffff; --kw-panel: #f8fafc; --kw-shadow: rgba(59, 130, 246, 0.2);
    }

    .kad-modern-wrapper { margin: 8px auto 16px auto; max-width: 850px; font-family: system-ui, -apple-system, sans-serif; font-size: 13px; color: var(--kw-text); }
    .kad-panel { background: var(--kw-bg); border: 1px solid var(--kw-secondary); border-top: 3px solid var(--kw-primary); box-shadow: 0 2px 6px var(--kw-shadow); padding: 8px 12px; display: flex; flex-direction: column; gap: 6px; border-radius: 4px; }
    .kad-alert { background: #fef2f2; color: #991b1b; padding: 6px; border-radius: 4px; border: 1px solid #fecaca; margin-bottom: 6px; text-align: center; font-weight: 500; font-size: 12px; }
    .kad-alert-warning { background: #fef9c3; color: #854d0e; border-color: #fde047; }

    .kad-toolbar { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 8px; border-bottom: 1px solid var(--kw-secondary); padding-bottom: 8px; margin-bottom: 2px; }
    .kad-toolbar-group { display: flex; align-items: center; gap: 8px; }

    .kad-tracker-block { background: var(--kw-bg); border: 1px solid var(--kw-secondary); border-radius: 4px; padding: 14px 12px 12px 12px; position: relative; margin-top: 4px; display: flex; flex-direction: column; align-items: center; border-left: 3px solid var(--kw-accent); }
    .kad-tracker-block.kad-main-block { background: var(--kw-panel); border-left: 3px solid var(--kw-primary); border-color: var(--kw-secondary); }
    .kad-block-label { position: absolute; top: 6px; left: 10px; font-weight: 700; font-size: 11px; text-transform: uppercase; letter-spacing: 0.5px; max-width: calc(100% - 160px); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; color: var(--kw-text); opacity: 0.8;}
    .kad-last-refresh { font-size: 11px; opacity: 0.6; margin-top: 4px; }

    .kad-countdown-box { font-size: 18px; font-weight: 700; margin-top: 6px; display: flex; align-items: center; justify-content: center; gap: 8px; width: 100%; color: var(--kw-text); }
    .kad-time-hl { color: #ef4444; font-size: 22px; font-variant-numeric: tabular-nums; letter-spacing: 0.5px; }
    .kad-time-sub { font-size: 13px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; opacity: 0.7;}
    .kad-divider { opacity: 0.3; font-weight: 300; margin: 0 4px; }
    .kad-window-active { color: #10b981; font-size: 22px; font-weight: 700; }

    .kad-btn { background: var(--kw-accent); color: #fff; border: none; padding: 4px 8px; border-radius: 4px; cursor: pointer; font-weight: 600; transition: opacity 0.15s; font-size: 12px; }
    .kad-btn:hover { opacity: 0.85; }
    .kad-btn-primary { background: var(--kw-primary); color: #fff; text-transform: uppercase; letter-spacing: 0.5px; }
    .kad-btn-red { background: #ef4444; padding: 2px 6px; font-size: 11px; }
    
    .kad-btn-action { position: absolute; top: 6px; right: 28px; padding: 2px 6px; font-size: 10px; opacity: 0.9; }
    .kad-btn-edit { right: 88px; background: var(--kw-text); opacity: 0.6; color: var(--kw-bg); }
    .kad-btn-edit:hover { opacity: 0.8; }
    .kad-btn-remove { position: absolute; top: 6px; right: 6px; padding: 2px 6px; font-size: 10px; }

    .kad-input { border: 1px solid var(--kw-secondary); border-radius: 4px; padding: 3px 6px; text-align: center; font-size: 12px; width: 65px; outline: none; background: var(--kw-bg); color: var(--kw-text); transition: border 0.15s; }
    .kad-input:focus { border-color: var(--kw-primary); }
    .kad-select { border: 1px solid var(--kw-secondary); border-radius: 4px; padding: 4px; font-size: 12px; background: var(--kw-bg); color: var(--kw-text); outline: none; cursor: pointer; width: 100%; }
    .kad-checkbox { accent-color: var(--kw-primary); cursor: pointer; margin-right: 6px; }

    /* Modal Styles */
    .kad-modal { background: var(--kw-bg); border: 1px solid var(--kw-secondary); border-top: 3px solid var(--kw-primary); box-shadow: 0 8px 24px rgba(0,0,0,0.15); display: flex; flex-direction: column; color: var(--kw-text); position: absolute; z-index: 9999; border-radius: 6px; }
    .kad-modal-header { font-size: 14px; font-weight: 700; text-transform: uppercase; letter-spacing: 1px; border-bottom: 1px solid var(--kw-secondary); padding: 10px 14px; display: flex; justify-content: space-between; align-items: center; cursor: move; user-select: none; background: var(--kw-panel); border-top-left-radius: 4px; border-top-right-radius: 4px; }
    .kad-modal-content { padding: 16px; display: flex; flex-direction: column; gap: 12px; }
    .kad-modal-row { display: flex; flex-direction: column; gap: 4px; }
    .kad-modal-row-group { display: flex; gap: 12px; }
    .kad-modal-row-group .kad-modal-row { flex: 1; }
    .kad-modal-label { font-size: 11px; font-weight: 700; text-transform: uppercase; opacity: 0.7; }
    .kad-modal-textarea { border: 1px solid var(--kw-secondary); padding: 6px; font-family: system-ui; font-size: 12px; background: var(--kw-bg); color: var(--kw-text); resize: vertical; min-height: 60px; outline: none; border-radius: 4px; }
    .kad-modal-textarea:focus { border-color: var(--kw-primary); }
    .kad-modal-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 8px; }
    
    .kad-settings-section { display: flex; flex-direction: column; gap: 6px; padding: 8px; border: 1px solid var(--kw-secondary); border-radius: 4px; background: var(--kw-panel); }
    .kad-settings-title { font-size: 12px; font-weight: 700; color: var(--kw-primary); text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 2px; }

    /* Rainbow Theme Specific Overrides */
    [data-kad-theme="rainbow"] .kad-panel, 
    [data-kad-theme="rainbow"] .kad-modal {
        border-top: 3px solid transparent;
        border-image: linear-gradient(to right, #ef4444, #f97316, #eab308, #22c55e, #3b82f6, #a855f7, #ec4899) 1;
    }
    [data-kad-theme="rainbow"] .kad-tracker-block {
        border-left: 3px solid transparent;
        border-image: linear-gradient(to bottom, #ef4444, #eab308, #22c55e, #3b82f6, #a855f7) 1;
    }

    .kad-selectable-cell { cursor: crosshair !important; transition: background 0.15s; }
    .kad-selectable-cell:hover { outline: 1px dotted var(--kw-primary); background: var(--kw-panel) !important; }

    .kad-food-copy { cursor: pointer; color: var(--kw-text) !important; border-bottom: 1px dotted var(--kw-accent); transition: all 0.15s; }
    .kad-food-copy:hover { color: var(--kw-primary) !important; border-bottom: 1px solid var(--kw-primary); }
</style>
`;

runScript()

function runScript() {
    $('head').append(STYLES);
    loadThemePreference();
    hideHeaderText();
    addIdToKadaotiesTable();

    injectDashboard();
    injectModals();
    setupDragLogic();

    checkBoardLockState();
    addClickToCopyFeature();
    setupNotificationSupport();

    checkForKadaotieRefresh();
    
    // Explicitly render pending drop on page load so it persists after feeding
    renderPendingDrop();

    setInterval(tickTimers, 1000);
    tickTimers();
}

function loadThemePreference() {
    let savedTheme = GM_getValue(KAD_THEME_KEY, "kadwatch");
    let validThemes = ['kadwatch', 'green', 'island', 'rainbow', 'spotted', 'white', 'void'];
    if (!validThemes.includes(savedTheme)) {
        savedTheme = 'kadwatch';
        GM_setValue(KAD_THEME_KEY, savedTheme);
    }
    document.documentElement.setAttribute("data-kad-theme", savedTheme);
}

function hideHeaderText() {
    let headerStrong = $('.content').find('strong:contains("The Kadoatery")').first();
    if (headerStrong.length) {
        headerStrong.hide();
        let parent = headerStrong.parent();
        let started = false;
        
        parent.contents().each(function() {
            if (this === headerStrong[0]) {
                started = true;
                return true;
            }
            if (started) {
                if (this.nodeType === Node.ELEMENT_NODE && (this.tagName === 'DIV' || this.tagName === 'TABLE' || this.tagName === 'FORM' || this.id === 'kad-dashboard-wrapper')) {
                    return false; 
                }
                if (this.nodeType === Node.TEXT_NODE) {
                    this.nodeValue = ''; 
                } else {
                    $(this).hide();
                }
            }
        });
    }
}

function addIdToKadaotiesTable() {
    $('.content div table').first().attr("id","kadaotiesTable");
}

function injectDashboard() {
    let dashboardHtml = `
        <div id="kad-dashboard-wrapper" class="kad-modern-wrapper">
            <div id='alreadyFedAlert' class='kad-alert kad-alert-warning' style='display:none;'>
                ⚠️ <strong>You are locked out!</strong> A Mini refreshed, but your previously fed Kad is still on the board. Do not buy!
            </div>
            <div id='pendingDropAlert' class='kad-alert kad-alert-warning' style='display:none;'></div>

            <div id="kad-dashboard" class="kad-panel">
                <div class="kad-toolbar">
                    <div class="kad-toolbar-group kad-quick-links">
                        <button id="openSettingsBtn" class="kad-btn" title="Settings" style="padding: 4px 6px; font-size: 14px; background: transparent; color: var(--kw-text); border: 1px solid var(--kw-secondary);">⚙️</button>
                        &bull; <a href="/market.phtml?type=wizard" target="_blank" style="color: inherit; font-weight: 600;">SW</a> 
                        &bull; <a href="/safetydeposit.phtml" target="_blank" style="color: inherit; font-weight: 600;">SDB</a>
                    </div>

                    <div class="kad-toolbar-group" style="border-left: 1px solid var(--kw-secondary); border-right: 1px solid var(--kw-secondary); padding: 0 10px;">
                        <input type="text" id="manualTimeInput" class="kad-input" placeholder="HH:MM" />
                        <button id="setMainBtn" class="kad-btn">Log Main</button>
                        <button id="addMiniBtn" class="kad-btn">Log Mini</button>
                    </div>

                    <div class="kad-toolbar-group">
                        <button id="toggleNotifyBtn" class="kad-btn" style="display: none;"></button>
                        <button id="copyBoardBtn" class="kad-btn kad-btn-primary">📋 Copy Post</button>
                    </div>
                </div>

                <div id="mainContainer"></div>
                <div id="minisContainer" style="display: flex; flex-direction: column; gap: 4px;"></div>
            </div>
        </div>
    `;

    $(dashboardHtml).insertBefore('#kadaotiesTable');

    $('#openSettingsBtn').on('click', function(e) { openSettingsModal(e); });

    $("#setMainBtn").on("click", () => handleManualTime('main'));
    $("#addMiniBtn").on("click", () => handleManualTime('mini'));
    $("#copyBoardBtn").on("click", copyBoardTimes);

    $(document).on("click", ".demote-main-btn", demoteMainToMini);
    $(document).on("click", ".promote-mini-btn", function() { promoteMiniToMain($(this).data('index')); });
    $(document).on("click", ".remove-main-btn", function() { GM_setValue(LAST_REFRESH_TIME_KEY, 0); tickTimers(); });
    $(document).on("click", ".remove-mini-btn", function() { removeMini($(this).data('index')); });
    
    $(document).on("click", ".edit-main-btn", function(e) { openEditModal('main', 0, e); });
    $(document).on("click", ".edit-mini-btn", function(e) { openEditModal('mini', $(this).data('index'), e); });

    $(document).on("click", ".merge-main-btn", function() { mergePendingDrop('main'); });
    $(document).on("click", ".merge-mini-btn", function() { mergePendingDrop('mini', $(this).data('index')); });
    $(document).on("click", "#discardDropBtn", discardPendingDrop);
}

function injectModals() {
    let editModalHtml = `
        <div id="kadEditModal" class="kad-modal" style="display:none; width: 420px;">
            <div class="kad-modal-header kad-modal-drag">
                <div>
                    <span id="editModalTitle">Data Override</span>
                    <span id="editModalHeaderHint" style="font-size: 10px; color: var(--kw-accent); font-weight: 600; margin-left: 8px;">[Click board to replace]</span>
                </div>
                <button class="minimize-modal-btn" data-target="#kadEditModalContent" style="background:transparent; border:none; color:var(--kw-text); font-weight:900; font-size:16px; cursor:pointer; line-height: 1;">−</button>
            </div>
            
            <div id="kadEditModalContent" class="kad-modal-content">
                <div class="kad-modal-row-group">
                    <div class="kad-modal-row">
                        <label class="kad-modal-label">Count</label>
                        <input type="number" id="editCount" class="kad-input" style="width: 100%;" min="1" max="20" />
                    </div>
                    <div class="kad-modal-row">
                        <label class="kad-modal-label">Seconds (0-59)</label>
                        <input type="number" id="editSeconds" class="kad-input" style="width: 100%;" min="0" max="59" />
                    </div>
                </div>

                <div class="kad-modal-row">
                    <label class="kad-modal-label">Kad Names (Comma Separated)</label>
                    <textarea id="editNames" class="kad-modal-textarea" placeholder="Select a range on the board..."></textarea>
                </div>

                <div class="kad-modal-row">
                    <label class="kad-modal-label">Foods (Comma Separated)</label>
                    <textarea id="editFoods" class="kad-modal-textarea"></textarea>
                </div>

                <div class="kad-modal-actions">
                    <button id="cancelEditBtn" class="kad-btn" style="background: transparent; color: inherit; border: 1px solid var(--kw-secondary);">Cancel</button>
                    <button id="saveEditBtn" class="kad-btn kad-btn-primary">Save Changes</button>
                </div>
            </div>
        </div>
    `;

    let currentTheme = GM_getValue(KAD_THEME_KEY, "kadwatch");
    let warnTime = GM_getValue(KAD_WARNING_TIME_KEY, 10);
    
    let settingsModalHtml = `
        <div id="kadSettingsModal" class="kad-modal" style="display:none; width: 380px;">
            <div class="kad-modal-header kad-modal-drag">
                <span>KadWatch Settings</span>
                <button class="minimize-modal-btn" data-target="#kadSettingsModalContent" style="background:transparent; border:none; color:var(--kw-text); font-weight:900; font-size:16px; cursor:pointer; line-height: 1;">−</button>
            </div>
            
            <div id="kadSettingsModalContent" class="kad-modal-content">
                
                <div class="kad-settings-section">
                    <div class="kad-settings-title">Appearance</div>
                    <div class="kad-modal-row">
                        <select id="settingTheme" class="kad-select">
                            <option value="kadwatch" ${currentTheme === 'kadwatch' ? 'selected' : ''}>🎀 KadWatch</option>
                            <option value="green" ${currentTheme === 'green' ? 'selected' : ''}>🐢 Green Kad</option>
                            <option value="island" ${currentTheme === 'island' ? 'selected' : ''}>🏝️ Island Kad</option>
                            <option value="rainbow" ${currentTheme === 'rainbow' ? 'selected' : ''}>🌈 Rainbow Kad</option>
                            <option value="spotted" ${currentTheme === 'spotted' ? 'selected' : ''}>🐆 Spotted Kad</option>
                            <option value="white" ${currentTheme === 'white' ? 'selected' : ''}>☁️ White Kad</option>
                            <option value="void" ${currentTheme === 'void' ? 'selected' : ''}>🌌 Void</option>
                        </select>
                    </div>
                </div>

                <div class="kad-settings-section">
                    <div class="kad-settings-title">Alerts</div>
                    <label style="display: flex; align-items: center; font-size: 12px; cursor: pointer;">
                        <input type="checkbox" id="settingDesktopNotif" class="kad-checkbox" ${GM_getValue(NOTIFICATION_OPT_IN_KEY, false) ? 'checked' : ''} />
                        Desktop Notifications
                    </label>
                    <label style="display: flex; align-items: center; font-size: 12px; cursor: pointer; margin-top: 6px;">
                        <input type="checkbox" id="settingAudioPing" class="kad-checkbox" ${GM_getValue(KAD_AUDIO_PING_KEY, false) ? 'checked' : ''} />
                        Play Audio Ping
                    </label>
                    <div style="display: flex; align-items: center; justify-content: space-between; margin-top: 4px;">
                        <span style="font-size: 12px;">Warning Time:</span>
                        <select id="settingWarnTime" class="kad-select" style="width: auto;">
                            <option value="10" ${warnTime === 10 ? 'selected' : ''}>10 Seconds</option>
                            <option value="30" ${warnTime === 30 ? 'selected' : ''}>30 Seconds</option>
                            <option value="60" ${warnTime === 60 ? 'selected' : ''}>60 Seconds</option>
                        </select>
                    </div>
                </div>

                <div class="kad-settings-section">
                    <div class="kad-settings-title">Clipboard Options</div>
                    <label style="display: flex; align-items: center; font-size: 12px; cursor: pointer;">
                        <input type="checkbox" id="setting24h" class="kad-checkbox" ${GM_getValue(KAD_24H_TIME_KEY, false) ? 'checked' : ''} />
                        Use 24-Hour Time
                    </label>
                    <label style="display: flex; align-items: center; font-size: 12px; cursor: pointer; margin-top: 6px;">
                        <input type="checkbox" id="settingIncludeFood" class="kad-checkbox" ${GM_getValue(KAD_INCLUDE_FOOD_KEY, true) ? 'checked' : ''} />
                        Include Food List in Copy
                    </label>
                    <label style="display: flex; align-items: center; font-size: 12px; cursor: pointer; margin-top: 6px;">
                        <input type="checkbox" id="settingAutoCensor" class="kad-checkbox" ${GM_getValue(KAD_AUTO_CENSOR_KEY, true) ? 'checked' : ''} />
                        Auto-Censor Words (e.g. crac.k)
                    </label>
                </div>
                
                <div class="kad-settings-section" style="border-color: #ef4444; background: #fef2f2;">
                    <div class="kad-settings-title" style="color: #ef4444;">Danger Zone</div>
                    <button id="nukeBoardBtn" class="kad-btn kad-btn-red" style="width: 100%; padding: 6px;">Clear All Timers</button>
                </div>

                <div class="kad-modal-actions">
                    <button id="closeSettingsBtn" class="kad-btn kad-btn-primary" style="width: 100%;">Done</button>
                </div>
            </div>
        </div>
    `;

    $('body').append(editModalHtml).append(settingsModalHtml);

    // Edit Modal Events
    $('#cancelEditBtn').on('click', closeEditModal);
    $('#saveEditBtn').on('click', saveEditModal);
    
    // Settings Modal Events
    $('#closeSettingsBtn').on('click', () => $('#kadSettingsModal').hide());
    
    $('#settingTheme').on('change', function() {
        let val = $(this).val();
        GM_setValue(KAD_THEME_KEY, val);
        document.documentElement.setAttribute("data-kad-theme", val);
    });

    $('#settingAudioPing').on('change', function() { GM_setValue(KAD_AUDIO_PING_KEY, this.checked); });
    $('#setting24h').on('change', function() { GM_setValue(KAD_24H_TIME_KEY, this.checked); tickTimers(); });
    $('#settingIncludeFood').on('change', function() { GM_setValue(KAD_INCLUDE_FOOD_KEY, this.checked); });
    $('#settingAutoCensor').on('change', function() { GM_setValue(KAD_AUTO_CENSOR_KEY, this.checked); });
    $('#settingWarnTime').on('change', function() { GM_setValue(KAD_WARNING_TIME_KEY, parseInt($(this).val(), 10)); });
    
    $('#nukeBoardBtn').on('click', function() {
        if(confirm("Are you sure you want to wipe all tracked timers? This cannot be undone.")) {
            GM_setValue(LAST_REFRESH_TIME_KEY, 0);
            GM_setValue(MAIN_KAD_DATA_KEY, "{}");
            GM_setValue(MINI_REFRESH_TIMES_KEY, "[]");
            GM_setValue(PENDING_DROP_KEY, "{}");
            $('#pendingDropAlert').hide().empty();
            $('#kadSettingsModal').hide();
            tickTimers();
        }
    });

    // Universal Minimize
    $('.minimize-modal-btn').on('click', function() {
        let target = $($(this).data('target'));
        target.slideToggle(200);
    });
    
    // Range Selection logic for Edit Modal
    $(document).on('click', '#kadaotiesTable td', function(e) {
        if ($('#kadEditModal').is(':visible')) {
            e.preventDefault();
            e.stopPropagation();

            let targetCell = $(this).closest('td');
            let idx = $("#kadaotiesTable td").index(targetCell);
            if (idx === -1) return;

            if (firstClickedIndex === null) {
                // First click sets the anchor
                firstClickedIndex = idx;
                let kadName = targetCell.find('strong').first().text().trim() || "[Blank]";
                
                $('#editNames').val(kadName);
                $('#editCount').val(1);
                
                $('#kadaotiesTable td').css({'outline': '', 'background': ''});
                targetCell.css({'outline': '2px solid var(--kw-primary)', 'background': 'var(--kw-panel)'});
                
                $('#editModalTitle').text("Select last Kad...");
                $('#editModalHeaderHint').text("[Click last Kad]");
            } else {
                // Second click selects the range
                let start = Math.min(firstClickedIndex, idx);
                let end = Math.max(firstClickedIndex, idx);
                
                let names = [];
                $('#kadaotiesTable td').css({'outline': '', 'background': ''});
                
                $("#kadaotiesTable td").slice(start, end + 1).each(function() {
                    let kName = $(this).find('strong').first().text().trim() || "[Blank]";
                    names.push(kName);
                    $(this).css({'outline': '2px solid var(--kw-primary)', 'background': 'var(--kw-panel)'});
                });
                
                $('#editNames').val(names.join(', '));
                $('#editCount').val(names.length);
                
                firstClickedIndex = null;
                $('#editModalTitle').text("Data Override");
                $('#editModalHeaderHint').text("[Range selected]");
                
                setTimeout(() => {
                    if (firstClickedIndex === null && $('#kadEditModal').is(':visible')) {
                        $('#editModalHeaderHint').text("[Click board to replace]");
                    }
                }, 2000);
            }
        }
    });
}

function setupDragLogic() {
    $(document).on('mousedown', '.kad-modal-drag', function(e) {
        if ($(e.target).is('button') || $(e.target).closest('button').length) return;
        isDraggingModal = true;
        dragTarget = $(this).closest('.kad-modal');
        
        dragOffsetX = e.pageX - dragTarget.offset().left;
        dragOffsetY = e.pageY - dragTarget.offset().top;
        $('body').css('user-select', 'none'); 
    });

    $(document).on('mousemove', function(e) {
        if (isDraggingModal && dragTarget) {
            dragTarget.css({
                left: (e.pageX - dragOffsetX) + 'px',
                top: (e.pageY - dragOffsetY) + 'px'
            });
        }
    });

    $(document).on('mouseup', function() {
        if (isDraggingModal) {
            isDraggingModal = false;
            dragTarget = null;
            $('body').css('user-select', '');
        }
    });
}

function openSettingsModal(e) {
    if (e && e.currentTarget) {
        let btnOffset = $(e.currentTarget).offset();
        $('#kadSettingsModal').css({
            top: (btnOffset.top + 30) + 'px',
            left: btnOffset.left + 'px',
            display: 'flex'
        });
    } else {
        $('#kadSettingsModal').css({ top: '20%', left: '50%', display: 'flex' });
    }
    $('#kadSettingsModalContent').show();
}

function openEditModal(type, index = 0, e = null) {
    editingTarget = { type, index };
    let data = {};
    let timeMs = 0;

    if (type === 'main') {
        timeMs = GM_getValue(LAST_REFRESH_TIME_KEY, 0);
        try { data = JSON.parse(GM_getValue(MAIN_KAD_DATA_KEY, "{}")); } catch(err){}
        $('#editModalTitle').text("Data Override // MAIN");
    } else {
        let minis = getMinis();
        if (minis[index]) {
            timeMs = minis[index].time;
            data = minis[index];
        }
        $('#editModalTitle').text(`Data Override // MINI ${index + 1}`);
    }

    firstClickedIndex = null;
    $('#editModalHeaderHint').text("[Click board to replace]");

    let d = new Date(timeMs);
    let seconds = timeMs > 0 ? d.getSeconds() : 0;

    let names = data.names || [];
    let foods = data.foods || [];

    $('#editCount').val(data.count || 0);
    $('#editSeconds').val(seconds);
    $('#editNames').val(names.join(', '));
    $('#editFoods').val(foods.join(', '));

    $('#kadaotiesTable td').each(function() {
        let cellName = $(this).find('strong').first().text().trim() || "[Blank]";
        if (names.includes(cellName)) {
            $(this).css({'outline': '2px solid var(--kw-primary)', 'background': 'var(--kw-panel)'});
        } else {
            $(this).css({'outline': '', 'background': ''});
        }
        $(this).addClass('kad-selectable-cell');
    });

    if (e && e.currentTarget) {
        let targetBtn = $(e.currentTarget);
        let btnOffset = targetBtn.offset();
        let modalLeft = btnOffset.left - 420 + targetBtn.outerWidth();
        if (modalLeft < 10) modalLeft = 10;

        $('#kadEditModal').css({
            top: (btnOffset.top + 30) + 'px',
            left: modalLeft + 'px',
            display: 'flex'
        });
    } else {
        $('#kadEditModal').css({ top: '20%', left: '50%', display: 'flex' });
    }
    
    $('#kadEditModalContent').show();
}

function closeEditModal() {
    $('#kadEditModal').hide();
    firstClickedIndex = null;
    $('#kadaotiesTable td').css({'outline': '', 'background': ''}).removeClass('kad-selectable-cell');
}

function checkBoardLockState() {
    let username = "";
    let modernNav = $('.nav-profile-dropdown-text').first().text().trim();
    if (modernNav) username = modernNav;
    else {
        let classicHref = $("a[href^='/userlookup.phtml?user=']").first().attr("href");
        if (classicHref) {
            let match = classicHref.match(/user=([^&]+)/);
            if (match) username = match[1];
        }
    }

    userIsLockedOut = false;
    if (username) {
        let fedItText = username + " has been fed";
        let hasHungryKads = false;

        $("#kadaotiesTable td").each(function() {
            let text = $(this).text();
            if (text.includes(fedItText)) userIsLockedOut = true;
            if (text.includes("is very sad")) hasHungryKads = true;
        });
        if (userIsLockedOut && hasHungryKads) $('#alreadyFedAlert').show();
        else $('#alreadyFedAlert').hide();
    }
}

function formatKadLabel(type, count, first, last) {
    if (!count || count === 0) return type;
    if (count === 1) return `${type} (1) ${first}`;
    return `${type} (${count}) ${first} › ${last}`;
}

function getCurrentKadStates() {
    let states = {};
    $("#kadaotiesTable td").slice(0, 20).each(function(index) {
        let text = $(this).text();
        let strongs = $(this).find('strong');
        let kadName = strongs.first().text().trim() || "[Blank]";

        if (text.includes("is very sad")) {
            let foodName = strongs.length > 1 ? strongs.last().text().trim() : "";
            states[index] = { name: kadName, food: foodName, status: "sad" };
        } else if (text.includes("has been fed")) {
            states[index] = { name: kadName, food: "", status: "fed" };
        }
    });
    return states;
}

function checkForKadaotieRefresh() {
    let currentState = getCurrentKadStates();
    let prevStateString = GM_getValue(KAD_STATES_KEY, "{}");
    let prevState = JSON.parse(prevStateString);
    let isInitialLoad = GM_getValue(KAD_FIRST_RUN_KEY, true);
    GM_setValue(KAD_FIRST_RUN_KEY, false);

    let minIdx = 99;
    let maxIdx = -1;

    for (let i = 0; i < 20; i++) {
        if (currentState[i] && currentState[i].status === "sad") {
            let isNew = !prevState[i] || prevState[i].status !== "sad" || prevState[i].name !== currentState[i].name;
            if (isNew) {
                if (i < minIdx) minIdx = i;
                if (i > maxIdx) maxIdx = i;
            }
        }
    }

    GM_setValue(KAD_STATES_KEY, JSON.stringify(currentState));

    if (isInitialLoad || minIdx > maxIdx) {
        return; 
    }

    let now = new Date().getTime();

    // 5-minute buffer check to prevent false splits immediately after feeding
    let mainTime = GM_getValue(LAST_REFRESH_TIME_KEY, 0);
    let minis = getMinis();
    let highestLoggedTime = mainTime;
    for (let m of minis) {
        if (m.time > highestLoggedTime) highestLoggedTime = m.time;
    }
    
    if (now - highestLoggedTime < 300000) {
        return; 
    }

    let activeDrop = {};
    try { activeDrop = JSON.parse(GM_getValue(PENDING_DROP_KEY, "{}")); } catch(e) {}
    if (activeDrop.time && (now - activeDrop.time < 300000)) {
        return;
    }

    let dropNames = [];
    let dropFoods = [];
    
    for (let i = minIdx; i <= maxIdx; i++) {
        dropNames.push(currentState[i].name);
        dropFoods.push(currentState[i].food || "Fed");
    }
    let count = (maxIdx - minIdx) + 1;

    GM_setValue(PENDING_DROP_KEY, JSON.stringify({
        time: now,
        count: count,
        names: dropNames,
        foods: dropFoods,
        indices: [minIdx, maxIdx]
    }));
    
    renderPendingDrop();
}

function renderPendingDrop() {
    let drop = {};
    try { drop = JSON.parse(GM_getValue(PENDING_DROP_KEY, "{}")); } catch(e) {}

    let alert = $('#pendingDropAlert');
    if (!drop.time) { alert.hide().empty(); return; }

    let minis = getMinis();
    let mergeButtons = '';
    
    mergeButtons += `<button class="kad-btn merge-main-btn" style="margin: 0 3px;" title="Assign to Main">→ Main</button>`;

    minis.forEach((mini, i) => {
        mergeButtons += `<button class="kad-btn merge-mini-btn" data-index="${i}" style="margin: 0 3px;" title="Assign to Mini ${i+1}">→ Mini ${i+1}</button>`;
    });

    let firstKad = drop.names[0] || '';
    let lastKad = drop.count > 1 ? ` › ${drop.names[drop.count - 1]}` : '';
    let kadSummary = `${drop.count} Kad${drop.count !== 1 ? 's' : ''} (${firstKad}${lastKad})`;

    alert.html(`
        🐱 <strong>New drop detected:</strong> ${kadSummary}
        ${mergeButtons}
        <button id="discardDropBtn" class="kad-btn kad-btn-red" style="margin: 0 3px;">Discard</button>
    `).show();
}

function pushMainToHistory() {
    let currentMainTime = GM_getValue(LAST_REFRESH_TIME_KEY, 0);
    if (currentMainTime <= 0) return;
    let history = JSON.parse(GM_getValue(MAIN_HISTORY_KEY, "[]"));
    let currentData = JSON.parse(GM_getValue(MAIN_KAD_DATA_KEY, "{}"));
    history.push({ time: currentMainTime, data: currentData });
    if (history.length > 5) history.shift();
    GM_setValue(MAIN_HISTORY_KEY, JSON.stringify(history));
}

function mergePendingDrop(type, miniIndex) {
    let drop = {};
    try { drop = JSON.parse(GM_getValue(PENDING_DROP_KEY, "{}")); } catch(e) {}
    if (!drop.time) return;

    if (type === 'main') {
        pushMainToHistory();
        GM_setValue(LAST_REFRESH_TIME_KEY, drop.time);
        GM_setValue(MAIN_KAD_DATA_KEY, JSON.stringify({
            count: drop.count, 
            first: drop.names[0], 
            last: drop.names[drop.count - 1],
            names: drop.names, 
            foods: drop.foods
        }));
    } else {
        let minis = getMinis();
        let mini = minis[miniIndex];
        if (!mini) return;
        minis[miniIndex] = { 
            ...mini, 
            time: drop.time, 
            count: drop.count, 
            first: drop.names[0], 
            last: drop.names[drop.count - 1], 
            names: drop.names, 
            foods: drop.foods 
        };
        saveMinis(minis);
    }

    GM_setValue(PENDING_DROP_KEY, "{}");
    renderPendingDrop();
    tickTimers();
}

function discardPendingDrop() {
    GM_setValue(PENDING_DROP_KEY, "{}");
    renderPendingDrop();
}

function saveEditModal() {
    if (!editingTarget) return;

    let count = parseInt($('#editCount').val(), 10) || 0;
    let seconds = parseInt($('#editSeconds').val(), 10) || 0;
    
    let namesRaw = $('#editNames').val();
    let foodsRaw = $('#editFoods').val();
    
    let names = namesRaw ? namesRaw.split(',').map(s => s.trim()).filter(s => s) : [];
    let foods = foodsRaw ? foodsRaw.split(',').map(s => s.trim()).filter(s => s) : [];

    let first = names.length > 0 ? names[0] : "";
    let last = names.length > 0 ? names[names.length - 1] : "";

    if (editingTarget.type === 'main') {
        let timeMs = GM_getValue(LAST_REFRESH_TIME_KEY, 0);
        if (timeMs > 0) {
            let d = new Date(timeMs);
            d.setSeconds(seconds);
            GM_setValue(LAST_REFRESH_TIME_KEY, d.getTime());
        }
        GM_setValue(MAIN_KAD_DATA_KEY, JSON.stringify({ count, first, last, names, foods }));
    } else {
        let minis = getMinis();
        let m = minis[editingTarget.index];
        if (m) {
            let d = new Date(m.time);
            d.setSeconds(seconds);
            minis[editingTarget.index] = { ...m, time: d.getTime(), count, first, last, names, foods };
            saveMinis(minis);
        }
    }

    closeEditModal();
    tickTimers();
}

function demoteMainToMini() {
    let mainTime = GM_getValue(LAST_REFRESH_TIME_KEY, 0);
    if (!mainTime) return;

    let mainData = {};
    try { mainData = JSON.parse(GM_getValue(MAIN_KAD_DATA_KEY, "{}")); } catch(e){}

    addMini(mainTime, mainData.count || 0, mainData.first || "", mainData.last || "", mainData.names || [], mainData.foods || []);
    
    let history = JSON.parse(GM_getValue(MAIN_HISTORY_KEY, "[]"));
    if (history.length > 0) {
        let previous = history.pop();
        GM_setValue(MAIN_HISTORY_KEY, JSON.stringify(history));
        GM_setValue(LAST_REFRESH_TIME_KEY, previous.time);
        GM_setValue(MAIN_KAD_DATA_KEY, JSON.stringify(previous.data));
    } else {
        GM_setValue(LAST_REFRESH_TIME_KEY, 0);
        GM_setValue(MAIN_KAD_DATA_KEY, "{}");
    }
    tickTimers();
}

function promoteMiniToMain(index) {
    let minis = getMinis();
    if (!minis[index]) return;

    pushMainToHistory();
    let mini = minis[index];
    minis.splice(index, 1);
    saveMinis(minis);

    GM_setValue(LAST_REFRESH_TIME_KEY, mini.time);
    GM_setValue(MAIN_KAD_DATA_KEY, JSON.stringify({
        count: mini.count,
        first: mini.first,
        last: mini.last,
        names: mini.names || [],
        foods: mini.foods || []
    }));
    tickTimers();
}

function getMinis() {
    try {
        let minis = JSON.parse(GM_getValue(MINI_REFRESH_TIMES_KEY, "[]"));
        if (!Array.isArray(minis)) return [];
        return minis.map(m => (typeof m === 'number') ? { time: m, count: 0, first: "", last: "", names: [], foods: [] } : m);
    } catch(e) { return []; }
}

function saveMinis(minisArr) {
    GM_setValue(MINI_REFRESH_TIMES_KEY, JSON.stringify(minisArr));
    tickTimers();
}

function addMini(timeMs, count = 0, first = "", last = "", names = [], foods = []) {
    let minis = getMinis();
    minis.push({ time: timeMs, count, first, last, names, foods });
    minis.sort((a, b) => a.time - b.time);
    saveMinis(minis);
}

function removeMini(index) {
    let minis = getMinis();
    minis.splice(index, 1);
    saveMinis(minis);
}

function tickTimers() {
    let nowTime = new Date().getTime();
    updateMainUI(nowTime);
    renderMinis(nowTime);
}

function getTimerState(lastRefreshTime, currentTime) {
    if (!lastRefreshTime || lastRefreshTime === 0) return { status: "expired" };

    let mainWindowStart = lastRefreshTime + DURATION_UNTIL_START_OF_MAIN_WINDOW_MS;
    let timeSinceMainStart = currentTime - mainWindowStart;
    
    if (timeSinceMainStart < 0) {
        return { status: "waiting", nextStart: mainWindowStart, countdown: mainWindowStart - currentTime };
    }

    let currentCycle = Math.floor(timeSinceMainStart / PEND_INTERVAL_MS);
    let currentWindowStart = mainWindowStart + (currentCycle * PEND_INTERVAL_MS);
    let currentWindowEnd = currentWindowStart + DURATION_OF_MAIN_WINDOW_MS;

    if (currentTime >= currentWindowStart && currentTime < currentWindowEnd) {
        return { status: "active", timeRemaining: currentWindowEnd - currentTime };
    }

    let nextWindowStart = mainWindowStart + ((currentCycle + 1) * PEND_INTERVAL_MS);
    return { status: "waiting", nextStart: nextWindowStart, countdown: nextWindowStart - currentTime };
}

function renderTimerHTML(state) {
    if (state.status === "expired") return '<span style="color: #ef4444; font-size: 14px;">Log time to start.</span>';
    if (state.status === "waiting") return `<span class="kad-time-sub">Next:</span> <span>${formatWindowTime(new Date(state.nextStart))}</span> <span class="kad-divider">|</span> <span class="kad-time-sub">In:</span> <span class="kad-time-hl">${formatCountdown(new Date(state.countdown))}</span>`;
    if (state.status === "active") return `<span class="kad-window-active">WINDOW ACTIVE!</span> <span class="kad-divider">|</span> <span class="kad-time-sub">Closes in:</span> <span class="kad-window-active">${Math.round(state.timeRemaining / 1000)}s</span>`;
    return '<span style="font-size: 14px; opacity: 0.6;">Unknown state.</span>';
}

function updateMainUI(nowTime) {
    let lastTime = GM_getValue(LAST_REFRESH_TIME_KEY, 0);
    let state = getTimerState(lastTime, nowTime);
    let container = $('#mainContainer');

    if (state.status === "expired" && lastTime === 0) {
        container.empty();
        return;
    }

    let label = "Main";
    try {
        let mainData = JSON.parse(GM_getValue(MAIN_KAD_DATA_KEY, "{}"));
        label = formatKadLabel("Main", mainData.count, mainData.first, mainData.last);
    } catch(e) {}

    let lastRefreshStr = formatLastRefresh(lastTime);
    container.html(`
        <div class="kad-tracker-block kad-main-block">
            <div class="kad-block-label" title="${label}">${label}</div>
            <div class="kad-countdown-box">${renderTimerHTML(state)}</div>
            <div class="kad-last-refresh">Last refresh: ${lastRefreshStr}</div>
            <button class="kad-btn kad-btn-action kad-btn-edit edit-main-btn" title="Edit Properties">✏️ Edit</button>
            <button class="kad-btn kad-btn-action demote-main-btn" title="Convert to Mini">↓ Demote</button>
            <button class="kad-btn kad-btn-red kad-btn-remove remove-main-btn" title="Remove Main">✕</button>
        </div>
    `);
}

function renderMinis(nowTime) {
    let minis = getMinis();
    let container = $('#minisContainer');
    container.empty();

    minis.forEach((miniObj, index) => {
        let state = getTimerState(miniObj.time, nowTime);
        if (state.status === "expired") return;

        let label = formatKadLabel(`Mini ${index + 1}`, miniObj.count, miniObj.first, miniObj.last);
        let miniLastStr = formatLastRefresh(miniObj.time);
        container.append(`
            <div class="kad-tracker-block">
                <div class="kad-block-label" title="${label}">${label}</div>
                <div class="kad-countdown-box">${renderTimerHTML(state)}</div>
                <div class="kad-last-refresh">Last refresh: ${miniLastStr}</div>
                <button class="kad-btn kad-btn-action kad-btn-edit edit-mini-btn" data-index="${index}" title="Edit Properties">✏️ Edit</button>
                <button class="kad-btn kad-btn-action promote-mini-btn" data-index="${index}" title="Set as Main">↑ Promote</button>
                <button class="kad-btn kad-btn-red kad-btn-remove remove-mini-btn" data-index="${index}" title="Remove Mini">✕</button>
            </div>
        `);
    });
}

function handleManualTime(type) {
    let val = $('#manualTimeInput').val();
    let newTime = parseTimeInput(val);
    if(newTime) {
        if (type === 'main') {
            pushMainToHistory();
            GM_setValue(LAST_REFRESH_TIME_KEY, newTime.getTime());
            GM_setValue(MAIN_KAD_DATA_KEY, "{}");
        } else {
            addMini(newTime.getTime());
        }
        $('#manualTimeInput').val('');
        tickTimers();
    }
}

function parseTimeInput(val) {
    let inputtedTimes = val.split(":");
    if (inputtedTimes.length < 2) return null;

    let hour = parseInt(inputtedTimes[0], 10);
    let min  = parseInt(inputtedTimes[1], 10);
    let sec  = inputtedTimes.length > 2 ? parseInt(inputtedTimes[2], 10) : 0;

    if (isNaN(hour) || isNaN(min) || isNaN(sec)) return null;

    let now = new Date();
    
    let formatter = new Intl.DateTimeFormat('en-US', {
        timeZone: 'America/Los_Angeles',
        year: 'numeric', month: '2-digit', day: '2-digit',
        hour: '2-digit', minute: '2-digit', second: '2-digit',
        hour12: false
    });
    
    let parts = formatter.formatToParts(now);
    let p = {};
    for (let part of parts) {
        p[part.type] = part.value;
    }
    
    let currentNstHour = parseInt(p.hour, 10);
    if (currentNstHour === 24) currentNstHour = 0;
    
    if (hour < 12 && currentNstHour >= 12) {
        if (Math.abs((hour + 12) - currentNstHour) < Math.abs(hour - currentNstHour)) {
            hour += 12;
        }
    }

    let pad = (num) => String(num).padStart(2, '0');
    let nstIsoString = `${p.year}-${p.month}-${p.day}T${pad(currentNstHour)}:${p.minute}:${p.second}`;
    
    let nstSimulatedNow = new Date(nstIsoString);
    let nstSimulatedTarget = new Date(nstIsoString);
    
    nstSimulatedTarget.setHours(hour, min, sec, 0);
    
    if (currentNstHour <= 1 && hour >= 22) {
        nstSimulatedTarget.setDate(nstSimulatedTarget.getDate() - 1);
    } else if (currentNstHour >= 22 && hour <= 1) {
        nstSimulatedTarget.setDate(nstSimulatedTarget.getDate() + 1);
    }

    let diffMs = nstSimulatedTarget.getTime() - nstSimulatedNow.getTime();
    return new Date(now.getTime() + diffMs);
}

function generateBoardString(label, lastTimeMs, foodNames, now) {
    if (!lastTimeMs || lastTimeMs === 0) return null;
    let last = new Date(lastTimeMs);
    let mainWindowStart = lastTimeMs + DURATION_UNTIL_START_OF_MAIN_WINDOW_MS;

    let timesToDisplay = [];
    
    let timeSinceMainStart = now - mainWindowStart;
    let currentCycle = Math.floor(timeSinceMainStart / PEND_INTERVAL_MS);
    if (currentCycle < 0) currentCycle = 0;

    for (let i = currentCycle; i <= currentCycle + 10; i++) {
        let windowStart = mainWindowStart + (i * PEND_INTERVAL_MS);
        if (now < windowStart + DURATION_OF_MAIN_WINDOW_MS) {
            timesToDisplay.push(new Date(windowStart));
        }
        if (timesToDisplay.length >= 6) break;
    }

    if (timesToDisplay.length === 0) return null;

    let lastStr = formatWindowTime(last);
    let nextStr = formatWindowTime(timesToDisplay[0]);

    let pends = [];
    for(let i = 1; i < timesToDisplay.length; i++) {
        pends.push(":" + formatTwoDigits(timesToDisplay[i].getMinutes()));
    }

    let pendsStr = pends.length > 0 ? " / " + pends.join(" / ") : "";
    let includeFood = GM_getValue(KAD_INCLUDE_FOOD_KEY, true);

    let foodLine = (includeFood && foodNames && foodNames.length > 0)
        ? "\n\n" + foodNames.map(censorFood).reduce((acc, food, i) => acc + (i > 0 ? (i % 4 === 0 ? "\n\n" : "\n") : "") + food, "")
        : '';
        
    return `${label} @ ${lastStr}\nNext ${nextStr}${pendsStr}${foodLine}`;
}

function copyBoardTimes() {
    let mainTime = GM_getValue(LAST_REFRESH_TIME_KEY, 0);
    let minis = getMinis();
    let now = new Date().getTime();
    let postArray = [];

    let mainState = getTimerState(mainTime, now);
    if(mainState.status !== "expired" && mainTime > 0) {
        let mainData = {};
        try { mainData = JSON.parse(GM_getValue(MAIN_KAD_DATA_KEY, "{}")); } catch(e){}
        let label = formatKadLabel("Main", mainData.count, mainData.first, mainData.last);
        postArray.push(generateBoardString(label, mainTime, mainData.foods || [], now));
    }

    minis.forEach((miniObj, index) => {
        let state = getTimerState(miniObj.time, now);
        if(state.status !== "expired") {
            let label = formatKadLabel(`Mini ${index + 1}`, miniObj.count, miniObj.first, miniObj.last);
            postArray.push(generateBoardString(label, miniObj.time, miniObj.foods || [], now));
        }
    });
    if(postArray.length === 0) {
        let btn = $('#copyBoardBtn');
        btn.text("Nothing to copy");
        setTimeout(() => btn.text("📋 Copy Post"), 1800);
        return;
    }

    navigator.clipboard.writeText(postArray.join("\n\n")).then(() => {
        let btn = $('#copyBoardBtn');
        let oldText = btn.text();
        btn.text("✅ Copied!");
        setTimeout(() => { btn.text(oldText); }, 1500);
    });
}

function addClickToCopyFeature() {
    $("#kadaotiesTable td:contains('is very sad')").each(function() {
        let foodTag = $(this).find('strong').last();
        if (foodTag.length && !foodTag.hasClass('kad-food-copy')) {
            foodTag.addClass('kad-food-copy');
            foodTag.attr('title', userIsLockedOut ? 'You are locked out!' : 'Click to copy to clipboard');

            foodTag.on('click', function(e) {
                if ($('#kadEditModal').is(':visible') && $('#kadEditModalContent').is(':visible')) return;

                e.preventDefault();

                if (userIsLockedOut) {
                    let originalText = $(this).text();
                    $(this).text('Locked!');
                    $(this).css({ 'color': '#ef4444', 'border-color': '#ef4444' });
                    setTimeout(() => {
                        $(this).text(originalText);
                        $(this).css({ 'color': '', 'border-color': '' });
                    }, 1000);
                    return;
                }

                let foodName = $(this).text();
                navigator.clipboard.writeText(foodName).then(() => {
                    $(this).text('Copied!');
                    $(this).css({ 'color': '#10b981', 'border-color': '#10b981' });
                    setTimeout(() => {
                        $(this).text(foodName);
                        $(this).css({ 'color': '', 'border-color': '' });
                    }, 800);
                });
            });
        }
    });
}

function updateNotifyUI() {
    let optedIn = GM_getValue(NOTIFICATION_OPT_IN_KEY, false);
    $('#settingDesktopNotif').prop('checked', optedIn);
    if (optedIn) {
        $('#toggleNotifyBtn').text('🔔 Alerts: ON').css({'background':'#10b981', 'color':'#fff', 'border':'none'}).show();
    } else {
        $('#toggleNotifyBtn').text('🔕 Alerts: OFF').css({'background':'transparent', 'color':'var(--kw-text)', 'border':'1px solid var(--kw-secondary)'}).show();
    }
}

function setupNotificationSupport() {
    if ("Notification" in window) {
        updateNotifyUI();
        
        $('#toggleNotifyBtn').off('click').on('click', function() {
            let current = GM_getValue(NOTIFICATION_OPT_IN_KEY, false);
            if (!current) {
                if (Notification.permission === "granted") {
                    GM_setValue(NOTIFICATION_OPT_IN_KEY, true);
                    updateNotifyUI();
                } else if (Notification.permission !== "denied") {
                    Notification.requestPermission().then((permission) => {
                        if (permission === "granted") {
                            GM_setValue(NOTIFICATION_OPT_IN_KEY, true);
                            updateNotifyUI();
                        }
                    });
                } else {
                    alert("You have blocked notifications for Neopets in your browser settings. Please enable them to use this feature.");
                }
            } else {
                GM_setValue(NOTIFICATION_OPT_IN_KEY, false);
                updateNotifyUI();
            }
        });

        $('#settingDesktopNotif').off('change').on('change', function() {
            if (this.checked) {
                if (Notification.permission === "granted") {
                    GM_setValue(NOTIFICATION_OPT_IN_KEY, true);
                    updateNotifyUI();
                } else if (Notification.permission !== "denied") {
                    Notification.requestPermission().then((permission) => {
                        if (permission === "granted") {
                            GM_setValue(NOTIFICATION_OPT_IN_KEY, true);
                        } else {
                            GM_setValue(NOTIFICATION_OPT_IN_KEY, false);
                            $(this).prop('checked', false);
                        }
                        updateNotifyUI();
                    });
                } else {
                    alert("Notifications are blocked in your browser settings.");
                    $(this).prop('checked', false);
                }
            } else {
                GM_setValue(NOTIFICATION_OPT_IN_KEY, false);
                updateNotifyUI();
            }
        });
    }
    
    setInterval(() => {
        let nowTime = new Date().getTime();
        let mainTime = GM_getValue(LAST_REFRESH_TIME_KEY, 0);
        let minis = getMinis();
        
        let state = getTimerState(mainTime, nowTime);
        if (state.status === "waiting") checkAndTriggerNotification('main', state.countdown);
        
        minis.forEach((miniObj, index) => {
            let mState = getTimerState(miniObj.time, nowTime);
            if (mState.status === "waiting") checkAndTriggerNotification(`mini_${index}`, mState.countdown);
        });
    }, 1000);
}

function checkAndTriggerNotification(type, countdownRemainingMs) {
    if (!GM_getValue(NOTIFICATION_OPT_IN_KEY, false)) return;
    
    let warningTimeMs = GM_getValue(KAD_WARNING_TIME_KEY, 10) * 1000;
    
    if (countdownRemainingMs <= warningTimeMs && countdownRemainingMs > (warningTimeMs - 1000)) {
        let nowMs = new Date().getTime();
        if (nowMs - (notificationsTriggered[type] || 0) > 240000) {
            notificationsTriggered[type] = nowMs;
            let title = type === 'main' ? "Main Refresh Incoming!" : "Mini Refresh Incoming!";
            
            if ("Notification" in window && Notification.permission === "granted") {
                showNotification(title, `Window starts in ${warningTimeMs / 1000} seconds.`);
            }
            
            if (GM_getValue(KAD_AUDIO_PING_KEY, false)) {
                let ping = new Audio('https://actions.google.com/sounds/v1/alarms/beep_short.ogg');
                ping.volume = 0.5;
                ping.play().catch(()=>{});
            }
        }
    }
}

function showNotification(title, body) {
    let notification = new Notification(title, {
        body: body,
        icon: "https://itemdb.com.br/api/cache/preview/7f18f78e35daa6.png"
    });
    notification.onclick = () => {
        notification.close();
        window.focus();
    }
}

function formatTwoDigits(n) { return n < 10 ? '0' + n : n; }
function formatCountdown(d) { return formatTwoDigits(d.getMinutes()) + ":" + formatTwoDigits(d.getSeconds()); }

function formatWindowTime(d) {
    let is24h = GM_getValue(KAD_24H_TIME_KEY, false);
    let nstStr = d.toLocaleString("en-US", { timeZone: "America/Los_Angeles", hour: "numeric", minute: "2-digit", second: "2-digit", hour12: !is24h });
    return nstStr.replace(/ /g, '').toLowerCase();
}

function formatLastRefresh(ms) {
    if (!ms || ms === 0) return '';
    let d = new Date(ms);
    let is24h = GM_getValue(KAD_24H_TIME_KEY, false);
    let nstStr = d.toLocaleString("en-US", { timeZone: "America/Los_Angeles", hour: "numeric", minute: "2-digit", second: "2-digit", hour12: !is24h });
    return nstStr.replace(/ /g, '').toLowerCase();
}