Tutorial Online Progress Widget (Auto-Detect Toggle)

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

Versión del día 29/11/2025. Echa un vistazo a la versión más reciente.

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

You will need to install an extension such as Tampermonkey to install this script.

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Necesitará instalar una extensión como Tampermonkey para instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

// ==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();

})();