Tutorial Online Progress Widget

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

2025-11-29 기준 버전입니다. 최신 버전을 확인하세요.

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

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

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @name         Tutorial Online Progress Widget
// @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"> Tutorial Online Progress Widget</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();

})();