Tutorial Online Progress Widget (Auto-Detect Toggle)

Automatically check course names, provides a structured progress for discussions and assignments, also saves progress locally.

От 29.11.2025. Виж последната версия.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey, Greasemonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да инсталирате разширение, като например Tampermonkey .

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Userscripts.

За да инсталирате скрипта, трябва да инсталирате разширение като Tampermonkey.

За да инсталирате този скрипт, трябва да имате инсталиран скриптов мениджър.

(Вече имам скриптов мениджър, искам да го инсталирам!)

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

(Вече имам инсталиран мениджър на стиловете, искам да го инсталирам!)

// ==UserScript==
// @name         Tutorial Online Progress Widget (Auto-Detect Toggle)
// @namespace    http://tampermonkey.net/
// @version      2
// @description  Automatically check course names, provides a structured progress for discussions and assignments, also saves progress locally.
// @author       deoffuscated
// @match        https://elearning.ut.ac.id/*
// @grant        none
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    const APP_NAME = "Tutorial Online Progress Widget";
    const WIDGET_ICON = 'https://suopmkm.ut.ac.id/uo/statics/logo.png';
    const STORAGE_DATA_KEY = 'tuton_progress_checklist';
    const STORAGE_COURSES_KEY = 'tuton_course_cache_list';
    const STORAGE_AUTODETECT_KEY = 'tuton_auto_detect_enabled';
    const STATE_KEY = 'tuton_widget_minimized_state';
    const defaultCourses = ["CONTOH MATA KULIAH"];

    const colLabels = [
        "DISKUSI 1", "DISKUSI 2", "DISKUSI 3", "DISKUSI 4",
        "DISKUSI 5", "DISKUSI 6", "DISKUSI 7", "DISKUSI 8",
        "TUGAS 1",   "TUGAS 2",   "TUGAS 3"
    ];

    const script = document.createElement('script');
    script.src = 'https://cdn.jsdelivr.net/npm/[email protected]/dist/confetti.browser.min.js';
    document.head.appendChild(script);

    function loadCachedCourses() {
        const stored = localStorage.getItem(STORAGE_COURSES_KEY);
        return stored ? JSON.parse(stored) : defaultCourses;
    }
    function saveCachedCourses(courses) {
        localStorage.setItem(STORAGE_COURSES_KEY, JSON.stringify(courses));
    }
    function loadProgressData() { return JSON.parse(localStorage.getItem(STORAGE_DATA_KEY) || '{}'); }
    function saveProgressData(data) { localStorage.setItem(STORAGE_DATA_KEY, JSON.stringify(data)); }
    function loadWidgetState() { return localStorage.getItem(STATE_KEY) === 'true'; }
    function saveWidgetState(isMin) { localStorage.setItem(STATE_KEY, isMin); }
    function loadAutoDetectState() {
        const val = localStorage.getItem(STORAGE_AUTODETECT_KEY);
        return val === null ? true : val === 'true';
    }
    function saveAutoDetectState(isEnabled) {
        localStorage.setItem(STORAGE_AUTODETECT_KEY, isEnabled);
    }

    let courseList = loadCachedCourses();
    let progressData = loadProgressData();
    let isAutoDetectEnabled = loadAutoDetectState();

    function scanForCourses() {
        if (!isAutoDetectEnabled) return;

        const courseElements = document.querySelectorAll('.coursename .multiline');

        if (courseElements.length > 0) {
            const scannedNames = Array.from(courseElements).map(el => {
                let name = el.textContent.trim();
                return name.replace(/\s+\d+$/, '');
            });

            let isUpdated = false;
            scannedNames.forEach(name => {
                if (!courseList.includes(name)) {
                    courseList.push(name);
                    isUpdated = true;
                }
            });

            if (isUpdated) {
                console.log(`[${APP_NAME}] Mata kuliah baru ditambahkan (Auto-Detect).`);
                saveCachedCourses(courseList);
                const mainPanel = document.getElementById('ut-main-panel');
                if (mainPanel) {
                    reRenderTable();
                    renderSettingsList();
                }
            }
        }
    }

    const observer = new MutationObserver((mutations) => {
        if (isAutoDetectEnabled && document.querySelector('.dashboard-card-deck')) {
            scanForCourses();
        }
    });
    observer.observe(document.body, { childList: true, subtree: true });

    const style = document.createElement('style');
    style.innerHTML = `
        :root {
            --glass-bg: rgba(255, 255, 255, 0.95);
            --primary-color: #1859BC;
            --accent-tugas: #e67e22;
            --success-color: #2ecc71;
            --danger-color: #c0392b;
            --text-dark: #1a202c;
            --text-light: #4a5568;
            --border-color: rgba(0, 0, 0, 0.1);
        }

        #ut-helper-wrapper {
            position: fixed; z-index: 10000;
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
            transition: all 0.4s cubic-bezier(0.25, 0.8, 0.25, 1);
        }
        #ut-helper-wrapper.minimized { bottom: 30px; right: 30px; }
        #ut-helper-wrapper.expanded { bottom: 30px; right: 30px; }

        /* TRIGGER BUTTON */
        #ut-widget-trigger {
            width: 55px; height: 55px; border-radius: 50%;
            background: rgba(255, 255, 255, 0.9);
            box-shadow: 0 4px 15px rgba(0,0,0,0.2);
            display: flex; align-items: center; justify-content: center;
            cursor: pointer; border: 2px solid var(--primary-color);
            padding: 8px; box-sizing: border-box;
            transition: transform 0.2s;
        }
        #ut-widget-trigger:hover { transform: scale(1.05); }
        #ut-widget-trigger img { width: 100%; height: 100%; object-fit: contain; pointer-events: none; }

        /* MAIN PANEL */
        #ut-main-panel {
            background: var(--glass-bg);
            backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px);
            border: 1px solid rgba(255,255,255,0.4);
            box-shadow: 0 8px 32px rgba(0,0,0,0.15);
            border-radius: 12px; padding: 15px;
            display: none; flex-direction: column;
            min-width: 400px; max-width: 95vw;
            animation: slideUp 0.3s ease-out;
            max-height: 80vh; overflow-y: auto;
        }
        @keyframes slideUp { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } }

        /* HEADER */
        .ut-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; }
        .ut-title { font-weight: 700; color: var(--text-dark); font-size: 15px; display: flex; align-items: center; }
        .ut-title img { margin-right: 10px; height: 24px; }
        .ut-controls { display: flex; gap: 8px; }
        .ut-icon-btn {
            cursor: pointer; color: var(--text-dark);
            width: 28px; height: 28px; border-radius: 50%;
            display: flex; align-items: center; justify-content: center;
            transition: background 0.2s;
        }
        .ut-icon-btn:hover { background: rgba(0,0,0,0.1); }
        .ut-icon-btn.danger:hover { color: red; background: rgba(255,0,0,0.1); }

        /* TABS */
        .ut-nav-tabs { display: flex; background: rgba(0,0,0,0.05); padding: 4px; border-radius: 8px; margin-bottom: 10px; gap: 5px; }
        .ut-tab-item {
            flex: 1; text-align: center; padding: 6px; font-size: 12px; font-weight: 600;
            cursor: pointer; border-radius: 6px; color: var(--text-light); transition: 0.2s;
        }
        .ut-tab-item:hover { background: rgba(255,255,255,0.5); }
        .ut-tab-item.active { background: #fff; color: var(--primary-color); box-shadow: 0 2px 5px rgba(0,0,0,0.1); }

        /* TABLE */
        .ut-table-container { overflow-x: auto; }
        .ut-table { width: 100%; border-collapse: collapse; margin-bottom: 5px; }
        .ut-table tr { border-bottom: 1px solid var(--border-color); }
        .ut-table tr:hover:not(:first-child) { background-color: rgba(24, 89, 188, 0.05); }
        .ut-table td { padding: 6px 4px; text-align: center; vertical-align: middle; }
        .ut-table.mode-diskusi .type-tugas { display: none; }
        .ut-table.mode-tugas .type-diskusi { display: none; }

        .col-head { font-size: 10px; font-weight: 800; color: var(--primary-color); border-bottom: 2px solid var(--primary-color); }
        .col-head-tugas { font-size: 10px; font-weight: 800; color: var(--accent-tugas); border-bottom: 2px solid var(--accent-tugas); }
        .bg-tugas { background-color: rgba(230, 126, 34, 0.05); }
        .row-label { font-size: 11px; font-weight: 700; text-align: left !important; min-width: 120px; max-width: 180px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
        .btn-reset { cursor: pointer; font-size: 10px; font-weight: 900; color: var(--danger-color); border-bottom: 2px solid var(--primary-color) !important; }

        /* CHECKBOXES & PROGRESS */
        .ut-chk-wrap { display: inline-block; position: relative; cursor: pointer; width: 16px; height: 16px; top: 2px; }
        .ut-chk-wrap.disabled { opacity: 0.3; pointer-events: none; filter: grayscale(1); }
        .ut-chk-wrap input { opacity: 0; width: 0; height: 0; }
        .checkmark { position: absolute; top: 0; left: 0; height: 16px; width: 16px; background-color: rgba(255,255,255,0.6); border: 2px solid #718096; border-radius: 4px; }
        .ut-chk-wrap:hover .checkmark { border-color: var(--primary-color); }
        .ut-chk-wrap input:checked ~ .checkmark { background-color: var(--primary-color); border-color: var(--primary-color); }
        .chk-tugas input:checked ~ .checkmark { background-color: var(--accent-tugas); border-color: var(--accent-tugas); }
        .checkmark:after { content: ""; position: absolute; display: none; left: 4px; top: 1px; width: 3px; height: 8px; border: solid white; border-width: 0 2px 2px 0; transform: rotate(45deg); }
        .ut-chk-wrap input:checked ~ .checkmark:after { display: block; }
        .prog-cont { margin-top: 10px; }
        .prog-bg { background: rgba(0,0,0,0.1); border-radius: 20px; height: 6px; width: 100%; overflow: hidden; }
        .prog-fill { height: 100%; background: var(--success-color); width: 0%; transition: width 0.5s; border-radius: 20px; }
        .prog-text { font-size: 10px; text-align: right; margin-top: 4px; color: var(--text-dark); font-weight: 600; }

        /* SETTINGS PANEL */
        #ut-settings-view { display: none; flex-direction: column; gap: 10px; padding: 5px; }
        .st-setting-row { display: flex; justify-content: space-between; align-items: center; background: rgba(255,255,255,0.5); padding: 8px; border-radius: 6px; margin-bottom: 5px; border: 1px solid rgba(0,0,0,0.05); }
        .st-input-group { display: flex; gap: 5px; }
        .st-input { flex: 1; padding: 8px; border: 1px solid #ddd; border-radius: 6px; font-size: 12px; }
        .st-btn { padding: 8px 12px; border: none; border-radius: 6px; font-size: 12px; font-weight: 600; cursor: pointer; color: white; }
        .st-btn-add { background: var(--success-color); }
        .st-btn-back { background: var(--text-light); width: 100%; margin-top: 10px; }
        .st-course-list { max-height: 200px; overflow-y: auto; border: 1px solid #eee; border-radius: 6px; margin-top: 5px; }
        .st-course-item { display: flex; justify-content: space-between; align-items: center; padding: 8px; border-bottom: 1px solid #eee; background: white; font-size: 12px; }
        .st-course-item:last-child { border-bottom: none; }
        .st-del-btn { color: var(--danger-color); cursor: pointer; font-weight: bold; padding: 2px 6px; }
        .st-del-btn:hover { background: rgba(192, 57, 43, 0.1); border-radius: 4px; }
        .st-lbl { font-size: 12px; font-weight: 700; color: var(--text-dark); margin-top: 5px; }
        .hidden { display: none !important; }

        /* TOGGLE SWITCH CSS */
        .ut-switch { position: relative; display: inline-block; width: 34px; height: 20px; }
        .ut-switch input { opacity: 0; width: 0; height: 0; }
        .ut-slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #ccc; transition: .4s; border-radius: 20px; }
        .ut-slider:before { position: absolute; content: ""; height: 14px; width: 14px; left: 3px; bottom: 3px; background-color: white; transition: .4s; border-radius: 50%; }
        input:checked + .ut-slider { background-color: var(--primary-color); }
        input:checked + .ut-slider:before { transform: translateX(14px); }

        /* MODAL */
        #ut-modal-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.4); z-index: 10001; display: none; justify-content: center; align-items: center; }
        #ut-modal-box { background: white; padding: 25px; border-radius: 12px; width: 300px; text-align: center; box-shadow: 0 20px 50px rgba(0,0,0,0.3); }
        .ut-modal-btns { display: flex; gap: 10px; margin-top: 20px; }
        .ut-btn { flex: 1; border: none; padding: 10px; border-radius: 6px; cursor: pointer; font-weight: 600; }
        .btn-cancel { background: #edf2f7; } .btn-ok { background: var(--danger-color); color: white; }
    `;
    document.head.appendChild(style);

    function initUI() {
        const wrapper = document.createElement('div');
        wrapper.id = 'ut-helper-wrapper';

        const widgetTrigger = document.createElement('div');
        widgetTrigger.id = 'ut-widget-trigger';
        widgetTrigger.innerHTML = `<img src="${WIDGET_ICON}" alt="UT Helper">`;
        widgetTrigger.title = 'Buka Progress Widget';

        const mainPanel = document.createElement('div');
        mainPanel.id = 'ut-main-panel';

        mainPanel.innerHTML = `
            <div class="ut-header">
                <span class="ut-title"><img src="${WIDGET_ICON}" alt="icon"> Progress Widget v3.0</span>
                <div class="ut-controls">
                    <div class="ut-icon-btn" id="ut-btn-settings" title="Pengaturan">
                        <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path></svg>
                    </div>
                    <div class="ut-icon-btn danger" id="ut-btn-minimize" title="Tutup">
                        <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
                    </div>
                </div>
            </div>

            <!-- VIEW: CHECKLIST (DEFAULT) -->
            <div id="ut-checklist-view">
                <div class="ut-nav-tabs">
                    <div class="ut-tab-item active" data-mode="mode-all">Semua</div>
                    <div class="ut-tab-item" data-mode="mode-diskusi">Diskusi</div>
                    <div class="ut-tab-item" data-mode="mode-tugas">Tugas</div>
                </div>
                <div id="ut-table-wrapper" class="ut-table-container">
                    <!-- Table generated by JS -->
                </div>
                <div class="prog-cont">
                    <div class="prog-bg"><div class="prog-fill" id="ut-prog-bar"></div></div>
                    <div class="prog-text" id="ut-prog-lbl">0% Selesai</div>
                </div>
            </div>

            <!-- VIEW: SETTINGS -->
            <div id="ut-settings-view">
                <div class="st-setting-row">
                    <span style="font-size:12px; font-weight:600; color:var(--text-dark);">Auto-Detect Mata Kuliah</span>
                    <label class="ut-switch">
                        <input type="checkbox" id="st-auto-detect-toggle" ${isAutoDetectEnabled ? 'checked' : ''}>
                        <span class="ut-slider"></span>
                    </label>
                </div>

                <div class="st-lbl">Tambah Mata Kuliah Manual</div>
                <div class="st-input-group">
                    <input type="text" id="st-course-input" class="st-input" placeholder="Misal: Bahasa Inggris Niaga...">
                    <button id="st-add-btn" class="st-btn st-btn-add">Tambah</button>
                </div>
                <div class="st-lbl">Daftar Mata Kuliah (Aktif)</div>
                <div class="st-course-list" id="st-course-list-container">
                    <!-- List generated by JS -->
                </div>
                <button id="st-back-btn" class="st-btn st-btn-back">Kembali ke Progress</button>
            </div>
        `;

        wrapper.appendChild(widgetTrigger);
        wrapper.appendChild(mainPanel);
        document.body.appendChild(wrapper);

        const modalOverlay = document.createElement('div');
        modalOverlay.id = 'ut-modal-overlay';
        modalOverlay.innerHTML = `
            <div id="ut-modal-box">
                <div style="font-size:16px; font-weight:700; margin-bottom:5px;">Reset Checklist?</div>
                <div style="font-size:13px; color:#666;">Semua progress akan dihapus.</div>
                <div class="ut-modal-btns">
                    <button class="ut-btn btn-cancel" id="ut-modal-cancel">Batal</button>
                    <button class="ut-btn btn-ok" id="ut-modal-ok">Hapus</button>
                </div>
            </div>
        `;
        document.body.appendChild(modalOverlay);

        let isMinimized = loadWidgetState();

        window.reRenderTable = function() {
            const container = document.getElementById('ut-table-wrapper');
            let tableHTML = `<table class="ut-table mode-all" id="ut-checklist-table">`;

            tableHTML += `<tr><td class="btn-reset" id="ut-btn-reset" title="Reset Semua">RESET</td>`;
            colLabels.forEach((l, index) => {
                const isTugas = index >= 8;
                const headerClass = isTugas ? 'col-head-tugas' : 'col-head';
                const typeClass = isTugas ? 'type-tugas' : 'type-diskusi';
                tableHTML += `<td class="${headerClass} ${typeClass}">${l}</td>`;
            });
            tableHTML += `</tr>`;

            courseList.forEach(row => {
                const cleanRow = row.replace(/[^a-zA-Z0-9]/g, '');
                tableHTML += `<tr><td class="row-label" title="${row}">${row}</td>`;
                for(let i=1; i<=11; i++) {
                    const isTugas = i >= 9;
                    const typeClass = isTugas ? 'type-tugas' : 'type-diskusi';
                    const bgClass = isTugas ? 'bg-tugas' : '';
                    const chkClass = isTugas ? 'ut-chk-wrap chk-tugas' : 'ut-chk-wrap';
                    tableHTML += `<td class="${bgClass} ${typeClass}"><label class="${chkClass}"><input type="checkbox" data-id="${cleanRow}_${i}"><span class="checkmark"></span></label></td>`;
                }
                tableHTML += `</tr>`;
            });
            tableHTML += `</table>`;
            container.innerHTML = tableHTML;

            const tableEl = document.getElementById('ut-checklist-table');
            const chks = tableEl.querySelectorAll('input[type="checkbox"]');
            chks.forEach(chk => {
                const id = chk.getAttribute('data-id');
                if (progressData[id]) chk.checked = true;

                chk.addEventListener('change', (e) => {
                    progressData[id] = e.target.checked;
                    saveProgressData(progressData);
                    applyRules(true);
                });
            });

            document.getElementById('ut-btn-reset').addEventListener('click', () => {
                modalOverlay.style.display = 'flex';
            });

            const activeTab = document.querySelector('.ut-tab-item.active');
            if(activeTab) {
                const mode = activeTab.getAttribute('data-mode');
                tableEl.className = `ut-table ${mode}`;
            }

            applyRules(false);
        };

        function calculateProgress(isUserAction) {
            const allChks = document.querySelectorAll('#ut-checklist-table input[type="checkbox"]');
            const total = allChks.length;
            if (total === 0) return;
            const checked = Array.from(allChks).filter(c => c.checked).length;
            const pct = Math.round((checked / total) * 100);

            document.getElementById('ut-prog-bar').style.width = `${pct}%`;
            document.getElementById('ut-prog-lbl').innerText = `${pct}% Selesai`;

            if (pct === 100 && isUserAction) {
                if (typeof confetti === 'function') confetti({ particleCount: 100, spread: 70, origin: { y: 0.6 }, zIndex: 10002 });
            }
        }

        function applyRules(isUserAction) {
            courseList.forEach(row => {
                const cleanRow = row.replace(/[^a-zA-Z0-9]/g, '');
                for (let i = 1; i < 8; i++) toggleLinkedCheck(`${cleanRow}_${i}`, `${cleanRow}_${i+1}`);
                toggleLinkedCheck(`${cleanRow}_3`, `${cleanRow}_9`);
                toggleLinkedCheck(`${cleanRow}_5`, `${cleanRow}_10`);
                toggleLinkedCheck(`${cleanRow}_7`, `${cleanRow}_11`);
            });
            saveProgressData(progressData);
            calculateProgress(isUserAction);
        }

        function toggleLinkedCheck(srcId, targetId) {
            const src = document.querySelector(`input[data-id="${srcId}"]`);
            const tgt = document.querySelector(`input[data-id="${targetId}"]`);
            if(src && tgt) {
                const wrap = tgt.closest('.ut-chk-wrap');
                if(src.checked) {
                    tgt.disabled = false;
                    wrap.classList.remove('disabled');
                } else {
                    tgt.disabled = true;
                    wrap.classList.add('disabled');
                    if(tgt.checked) { tgt.checked = false; progressData[targetId] = false; }
                }
            }
        }

        window.renderSettingsList = function() {
            const listCont = document.getElementById('st-course-list-container');
            listCont.innerHTML = '';
            courseList.forEach((c, idx) => {
                const item = document.createElement('div');
                item.className = 'st-course-item';
                item.innerHTML = `<span>${c}</span> <span class="st-del-btn" data-idx="${idx}">Hapus</span>`;
                listCont.appendChild(item);
            });

            document.querySelectorAll('.st-del-btn').forEach(btn => {
                btn.addEventListener('click', (e) => {
                    const idx = e.target.getAttribute('data-idx');
                    courseList.splice(idx, 1);
                    saveCachedCourses(courseList);
                    renderSettingsList();
                });
            });
        };

        function toTitleCase(str) {
            return str.toLowerCase().split(' ').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(' ');
        }

        document.getElementById('st-add-btn').addEventListener('click', () => {
            const input = document.getElementById('st-course-input');
            let rawVal = input.value.trim();
            if (!rawVal) return;
            const val = toTitleCase(rawVal);
            if(val && !courseList.includes(val)) {
                courseList.push(val);
                saveCachedCourses(courseList);
                input.value = '';
                renderSettingsList();
            } else if (courseList.includes(val)) {
                alert('Mata kuliah sudah ada!');
            }
        });

        document.getElementById('st-auto-detect-toggle').addEventListener('change', (e) => {
            isAutoDetectEnabled = e.target.checked;
            saveAutoDetectState(isAutoDetectEnabled);
            if(isAutoDetectEnabled) {
                scanForCourses();
            }
        });

        const viewChecklist = document.getElementById('ut-checklist-view');
        const viewSettings = document.getElementById('ut-settings-view');

        document.getElementById('ut-btn-settings').addEventListener('click', () => {
            viewChecklist.classList.add('hidden');
            viewSettings.classList.remove('hidden');
            viewSettings.style.display = 'flex';
            renderSettingsList();
        });

        document.getElementById('st-back-btn').addEventListener('click', () => {
            viewSettings.classList.add('hidden');
            viewSettings.style.display = 'none';
            viewChecklist.classList.remove('hidden');
            reRenderTable();
        });

        const tabItems = document.querySelectorAll('.ut-tab-item');
        tabItems.forEach(tab => {
            tab.addEventListener('click', () => {
                tabItems.forEach(t => t.classList.remove('active'));
                tab.classList.add('active');
                const mode = tab.getAttribute('data-mode');
                document.getElementById('ut-checklist-table').className = `ut-table ${mode}`;
            });
        });

        function updateWidgetState() {
            if (isMinimized) {
                wrapper.className = 'minimized'; mainPanel.style.display = 'none'; widgetTrigger.style.display = 'flex';
            } else {
                wrapper.className = 'expanded'; mainPanel.style.display = 'flex'; widgetTrigger.style.display = 'none';
            }
            saveWidgetState(isMinimized);
        }
        widgetTrigger.addEventListener('click', () => { isMinimized = false; updateWidgetState(); });
        document.getElementById('ut-btn-minimize').addEventListener('click', () => { isMinimized = true; updateWidgetState(); });

        const closeModal = () => { modalOverlay.style.display = 'none'; };
        document.getElementById('ut-modal-cancel').addEventListener('click', closeModal);
        document.getElementById('ut-modal-ok').addEventListener('click', () => {
            progressData = {};
            saveProgressData(progressData);
            reRenderTable();
            closeModal();
        });

        updateWidgetState();
        reRenderTable();
    }

    scanForCourses();
    initUI();

})();