// ==UserScript== // @name TG任务助手前台面板 // @namespace tg-task-monitor-ui // @version 2.0.0 // @description 读取 TG任务状态后台扫描器 的共享结果,在 tg.zcst.edu.cn 页面右下角显示任务助手抽屉 // @author ChatGPT // @match https://tg.zcst.edu.cn/* // @storageName tg-exam-monitor-shared // @grant GM_getValue // @grant GM_setValue // @grant GM_deleteValue // @grant GM_addStyle // @grant GM_registerMenuCommand // @run-at document-idle // ==/UserScript== (function () { "use strict"; const MANUAL_LOGIN = ""; const STORE_KEY = "TG_EXAM_MONITOR_RESULT"; const STORE_KEY_LAST_ERROR = "TG_EXAM_MONITOR_LAST_ERROR"; const STORE_KEY_LAST_RUNNING = "TG_EXAM_MONITOR_LAST_RUNNING"; const STORE_KEY_FILTER_YEAR_MONTH = "TG_TASK_FILTER_YEAR_MONTH"; const STORE_KEY_AUTO_LOGIN = "TG_TASK_MONITOR_AUTO_LOGIN"; const STORE_KEY_COURSE_COLLAPSE = "TG_TASK_COURSE_COLLAPSE"; const STORE_KEY_SECTION_COLLAPSE = "TG_TASK_SECTION_COLLAPSE"; const STORE_KEY_PANEL_STATE = "TG_TASK_PANEL_STATE"; const STORE_KEY_REFRESH_REQUEST = "TG_TASK_REFRESH_REQUEST"; const STORE_KEY_REFRESH_HANDLED = "TG_TASK_REFRESH_HANDLED"; const ROOT_ID = "__tg_task_assistant_root__"; const BUTTON_ID = "__tg_task_assistant_button__"; const DRAWER_ID = "__tg_task_assistant_drawer__"; const SVG_FILTER_ID = "__tg_liquid_glass_filter__"; const OPEN_KEY = "TG_TASK_ASSISTANT_OPEN"; const FILTER_KEY = "TG_TASK_ASSISTANT_FILTER"; let countdownIntervalId = null; let pollTimerId = null; let resizeObserverAttached = false; let cacheDeletedNotice = ""; let latestResultLogin = ""; let jumpTaskRegistry = new Map(); let jumpTaskSeq = 0; let pollWasRunning = false; let latestRenderedScanTimestamp = 0; let resizeApplyTimer = null; const css = ` #${BUTTON_ID}, #${DRAWER_ID} { --tg-cyan: #67e8f9; --tg-cyan-soft: rgba(103, 232, 249, .22); --tg-violet: #8b5cf6; --tg-violet-soft: rgba(139, 92, 246, .2); --tg-pink: #f0abfc; --tg-pink-soft: rgba(240, 171, 252, .18); --tg-red: #fb7185; --tg-amber: #fbbf24; --tg-green: #86efac; --tg-text: rgba(244, 247, 251, .94); --tg-muted: rgba(203, 213, 225, .68); --tg-faint: rgba(148, 163, 184, .48); --tg-glass: rgba(12, 18, 30, .64); --tg-glass-strong: rgba(15, 23, 42, .76); --tg-line: rgba(148, 227, 255, .18); font-family: Inter, ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", Arial, "Microsoft YaHei", sans-serif; color: var(--tg-text); box-sizing: border-box; letter-spacing: 0; } #${BUTTON_ID} *, #${DRAWER_ID} * { box-sizing: border-box; letter-spacing: 0; } #${BUTTON_ID} { position: fixed; right: 22px; bottom: 24px; z-index: 2147483646; width: 62px; height: 62px; border: 1px solid rgba(103, 232, 249, .34); border-radius: 50%; background: radial-gradient(circle at 32% 24%, rgba(255,255,255,.34), transparent 24%), radial-gradient(circle at 70% 76%, rgba(139,92,246,.34), transparent 34%), linear-gradient(145deg, rgba(12,18,30,.82), rgba(8,12,22,.66)); color: rgba(242, 251, 255, .96); box-shadow: 0 22px 52px rgba(0, 0, 0, .44), 0 0 34px rgba(103, 232, 249, .18), inset 0 1px 0 rgba(255,255,255,.24), inset 0 -18px 42px rgba(139,92,246,.12); backdrop-filter: blur(22px) saturate(145%); -webkit-backdrop-filter: blur(22px) saturate(145%); cursor: pointer; display: flex; align-items: center; justify-content: center; font-size: 12px; font-weight: 760; line-height: 1.16; text-align: center; overflow: visible; user-select: none; touch-action: none; transition: transform .38s cubic-bezier(.16, 1, .3, 1), border-color .38s ease, box-shadow .38s ease; animation: tgFloatButton 7s ease-in-out infinite; } #${BUTTON_ID}::before { content: ""; position: absolute; inset: -9px; border-radius: inherit; background: conic-gradient(from 140deg, transparent, rgba(103,232,249,.22), transparent, rgba(240,171,252,.16), transparent); filter: blur(12px); opacity: .7; z-index: -1; } #${BUTTON_ID}:hover { transform: translateY(-4px) scale(1.035); border-color: rgba(103, 232, 249, .62); box-shadow: 0 28px 70px rgba(0, 0, 0, .52), 0 0 48px rgba(103, 232, 249, .26), 0 0 58px rgba(139, 92, 246, .16), inset 0 1px 0 rgba(255,255,255,.32); } #${BUTTON_ID} .tg-task-button-badge { position: absolute; right: -4px; top: -5px; min-width: 22px; height: 22px; padding: 0 6px; border-radius: 999px; border: 1px solid rgba(255,255,255,.44); background: linear-gradient(135deg, rgba(251,113,133,.94), rgba(240,171,252,.74)); color: #fff; font-size: 11px; line-height: 20px; box-shadow: 0 0 20px rgba(251,113,133,.34); } #${DRAWER_ID} { position: fixed; right: 18px; top: 18px; z-index: 2147483647; width: min(520px, calc(100vw - 28px)); height: min(760px, calc(100vh - 48px)); min-width: 390px; min-height: 480px; max-width: calc(100vw - 40px); max-height: calc(100vh - 40px); resize: both; display: flex; flex-direction: column; overflow: hidden; border-radius: 30px; color: var(--tg-text); background: linear-gradient(145deg, rgba(14, 21, 36, .72), rgba(6, 10, 18, .68)), radial-gradient(circle at 18% 0%, rgba(103,232,249,.12), transparent 38%), radial-gradient(circle at 92% 8%, rgba(139,92,246,.16), transparent 42%); border: 1px solid rgba(160, 231, 255, .2); box-shadow: -34px 28px 90px rgba(0,0,0,.56), 0 0 0 1px rgba(255,255,255,.035) inset, 0 0 56px rgba(103,232,249,.12), 0 0 88px rgba(139,92,246,.1); backdrop-filter: blur(34px) saturate(150%); -webkit-backdrop-filter: blur(34px) saturate(150%); transform: translateX(calc(100% + 38px)) scale(.985); opacity: .42; transition: transform .58s cubic-bezier(.16, 1, .3, 1), opacity .38s ease, box-shadow .45s ease; } #${DRAWER_ID}.tg-open { transform: translateX(0) scale(1); opacity: 1; } #${DRAWER_ID}::before { content: ""; position: absolute; inset: 0; pointer-events: none; background-image: radial-gradient(circle at 14% 16%, rgba(103,232,249,.22) 0 1px, transparent 2px), radial-gradient(circle at 84% 22%, rgba(240,171,252,.18) 0 1px, transparent 2px), radial-gradient(circle at 64% 74%, rgba(139,92,246,.16) 0 1px, transparent 2px), linear-gradient(rgba(255,255,255,.035) 1px, transparent 1px), linear-gradient(90deg, rgba(255,255,255,.024) 1px, transparent 1px); background-size: 128px 128px, 184px 184px, 148px 148px, 44px 44px, 44px 44px; mask-image: linear-gradient(to bottom, rgba(0,0,0,.92), rgba(0,0,0,.26)); opacity: .42; animation: tgParticleDrift 22s linear infinite; } #${DRAWER_ID}::after { content: ""; position: absolute; inset: 1px; pointer-events: none; border-radius: 29px; background: linear-gradient(135deg, rgba(255,255,255,.18), transparent 24%), linear-gradient(315deg, rgba(103,232,249,.1), transparent 28%), radial-gradient(circle at 78% 14%, rgba(240,171,252,.12), transparent 34%); mix-blend-mode: screen; opacity: .55; } #${DRAWER_ID} .tg-ambient { position: absolute; inset: 0; overflow: hidden; pointer-events: none; z-index: 0; } #${DRAWER_ID} .tg-orb, #${DRAWER_ID} .tg-ring { position: absolute; display: block; border-radius: 999px; filter: blur(.2px); opacity: .7; transform: translate3d(0,0,0); } #${DRAWER_ID} .tg-orb-a { width: 150px; height: 150px; right: -42px; top: 70px; background: radial-gradient(circle at 34% 30%, rgba(255,255,255,.38), rgba(103,232,249,.18) 28%, rgba(103,232,249,.04) 68%, transparent 74%); filter: blur(1px); animation: tgSlowFloatA 15s ease-in-out infinite; } #${DRAWER_ID} .tg-orb-b { width: 92px; height: 92px; left: 32px; bottom: 92px; background: radial-gradient(circle at 34% 28%, rgba(255,255,255,.25), rgba(240,171,252,.18) 38%, transparent 72%); filter: blur(1.5px); animation: tgSlowFloatB 18s ease-in-out infinite; } #${DRAWER_ID} .tg-ring-a { width: 178px; height: 178px; left: -78px; top: 178px; border: 1px solid rgba(103,232,249,.16); box-shadow: inset 0 0 34px rgba(103,232,249,.08), 0 0 30px rgba(139,92,246,.08); transform: rotate(-18deg); animation: tgRingDrift 21s ease-in-out infinite; } #${DRAWER_ID} .tg-header, #${DRAWER_ID} .tg-body, #${DRAWER_ID} .tg-actions { position: relative; z-index: 1; } #${DRAWER_ID} .tg-header { flex: 0 0 auto; min-height: 78px; padding: 18px 18px 14px; border-bottom: 1px solid rgba(148, 227, 255, .13); background: linear-gradient(180deg, rgba(255,255,255,.055), rgba(255,255,255,.018)); display: flex; align-items: center; justify-content: space-between; gap: 14px; } #${DRAWER_ID} .tg-title { min-width: 0; } #${DRAWER_ID} .tg-title-main { font-size: 22px; font-weight: 760; line-height: 1.15; color: rgba(248, 250, 252, .98); text-shadow: 0 0 22px rgba(103,232,249,.12); } #${DRAWER_ID} .tg-title-sub { margin-top: 6px; color: var(--tg-muted); font-size: 12px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } #${DRAWER_ID} .tg-header-actions { display: flex; gap: 8px; flex: 0 0 auto; } #${DRAWER_ID} .tg-icon-btn { width: 34px; height: 34px; border: 1px solid rgba(148, 227, 255, .18); background: rgba(255,255,255,.055); color: rgba(235, 245, 255, .9); border-radius: 12px; cursor: pointer; font-size: 13px; line-height: 32px; text-align: center; box-shadow: inset 0 1px 0 rgba(255,255,255,.12); transition: transform .28s cubic-bezier(.16, 1, .3, 1), border-color .28s ease, background .28s ease, box-shadow .28s ease; } #${DRAWER_ID} .tg-icon-btn:hover { transform: translateY(-2px); border-color: rgba(103,232,249,.44); background: rgba(103,232,249,.09); box-shadow: 0 0 22px rgba(103,232,249,.12), inset 0 1px 0 rgba(255,255,255,.18); } #${DRAWER_ID} .tg-top-refresh { width: auto; min-width: 74px; padding: 0 11px; font-size: 12px; white-space: nowrap; } #${DRAWER_ID} .tg-body { flex: 1 1 auto; overflow-y: auto; padding: 16px; scrollbar-width: thin; scrollbar-color: rgba(103,232,249,.28) transparent; } #${DRAWER_ID} .tg-body::-webkit-scrollbar { width: 9px; } #${DRAWER_ID} .tg-body::-webkit-scrollbar-thumb { background: rgba(103,232,249,.2); border: 3px solid transparent; border-radius: 999px; background-clip: padding-box; } #${DRAWER_ID} .tg-meta, #${DRAWER_ID} .tg-alert, #${DRAWER_ID} .tg-empty, #${DRAWER_ID} .tg-date-filter, #${DRAWER_ID} .tg-summary-item, #${DRAWER_ID} .tg-card { position: relative; overflow: hidden; border: 1px solid rgba(148, 227, 255, .15); background: linear-gradient(145deg, rgba(255,255,255,.09), rgba(255,255,255,.035)), rgba(8, 13, 24, .42); box-shadow: 0 18px 42px rgba(0,0,0,.25), inset 0 1px 0 rgba(255,255,255,.09), inset 0 -1px 0 rgba(255,255,255,.035); backdrop-filter: blur(20px) saturate(145%); -webkit-backdrop-filter: blur(20px) saturate(145%); } #${DRAWER_ID} .tg-meta::before, #${DRAWER_ID} .tg-alert::before, #${DRAWER_ID} .tg-empty::before, #${DRAWER_ID} .tg-date-filter::before, #${DRAWER_ID} .tg-summary-item::before, #${DRAWER_ID} .tg-card::before { content: ""; position: absolute; inset: 0; pointer-events: none; background-image: radial-gradient(circle at 18% 0%, rgba(255,255,255,.12), transparent 34%), repeating-linear-gradient(0deg, rgba(255,255,255,.025) 0 1px, transparent 1px 4px); opacity: .38; mix-blend-mode: screen; } #${DRAWER_ID} .tg-meta, #${DRAWER_ID} .tg-alert, #${DRAWER_ID} .tg-empty { border-radius: 22px; padding: 14px; margin-bottom: 12px; color: var(--tg-muted); font-size: 12px; line-height: 1.65; } #${DRAWER_ID} .tg-meta { padding: 15px; } #${DRAWER_ID} .tg-refresh-status { border-radius: 20px; padding: 12px; margin-bottom: 12px; border: 1px solid rgba(148, 227, 255, .14); background: linear-gradient(145deg, rgba(255,255,255,.075), rgba(255,255,255,.025)), rgba(8, 13, 24, .38); box-shadow: 0 16px 36px rgba(0,0,0,.22), inset 0 1px 0 rgba(255,255,255,.08); backdrop-filter: blur(18px) saturate(140%); -webkit-backdrop-filter: blur(18px) saturate(140%); } #${DRAWER_ID} .tg-refresh-row { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; margin-bottom: 9px; } #${DRAWER_ID} .tg-refresh-title { color: rgba(245,250,255,.92); font-size: 12px; font-weight: 760; } #${DRAWER_ID} .tg-refresh-stage { color: var(--tg-muted); font-size: 11px; line-height: 1.5; overflow-wrap: anywhere; } #${DRAWER_ID} .tg-progress-track { height: 7px; overflow: hidden; border-radius: 999px; border: 1px solid rgba(148, 227, 255, .12); background: rgba(255,255,255,.045); box-shadow: inset 0 1px 4px rgba(0,0,0,.28); } #${DRAWER_ID} .tg-progress-fill { height: 100%; width: 0%; border-radius: inherit; background: linear-gradient(90deg, rgba(103,232,249,.72), rgba(139,92,246,.62), rgba(240,171,252,.52)); box-shadow: 0 0 18px rgba(103,232,249,.22); transition: width .42s cubic-bezier(.16, 1, .3, 1); } #${DRAWER_ID} .tg-meta-kicker { color: rgba(103,232,249,.86); font-size: 11px; font-weight: 720; margin-bottom: 12px; } #${DRAWER_ID} .tg-meta-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 9px; } #${DRAWER_ID} .tg-meta-chip { border: 1px solid rgba(148, 227, 255, .12); background: rgba(255,255,255,.045); border-radius: 16px; padding: 9px 10px; min-width: 0; } #${DRAWER_ID} .tg-meta-label { display: block; color: var(--tg-faint); font-size: 11px; margin-bottom: 4px; } #${DRAWER_ID} .tg-meta-value { display: block; color: var(--tg-text); font-size: 12px; font-weight: 680; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } #${DRAWER_ID} .tg-alert { color: rgba(255, 228, 230, .95); background: linear-gradient(145deg, rgba(251,113,133,.16), rgba(255,255,255,.035)), rgba(40, 9, 20, .34); border-color: rgba(251,113,133,.26); box-shadow: 0 18px 44px rgba(77, 10, 30, .25), inset 0 1px 0 rgba(255,255,255,.09); } #${DRAWER_ID} .tg-empty { color: rgba(203, 213, 225, .74); text-align: center; padding: 18px; } #${DRAWER_ID} .tg-summary { display: grid; grid-template-columns: repeat(6, minmax(0, 1fr)); gap: 9px; margin-bottom: 12px; } #${DRAWER_ID} .tg-summary-item { min-width: 0; width: 100%; border-radius: 20px; padding: 12px 8px 11px; text-align: left; cursor: pointer; color: inherit; font: inherit; appearance: none; -webkit-appearance: none; transition: transform .36s cubic-bezier(.16, 1, .3, 1), border-color .36s ease, box-shadow .36s ease, background .36s ease; } #${DRAWER_ID} .tg-summary-item:hover { transform: translateY(-4px); border-color: rgba(103,232,249,.34); background: linear-gradient(145deg, rgba(103,232,249,.1), rgba(255,255,255,.04)), rgba(9, 16, 28, .54); box-shadow: 0 24px 54px rgba(0,0,0,.3), 0 0 26px rgba(103,232,249,.1), inset 0 1px 0 rgba(255,255,255,.13); } #${DRAWER_ID} .tg-summary-item.tg-selected { border-color: rgba(103,232,249,.48); background: linear-gradient(145deg, rgba(103,232,249,.16), rgba(139,92,246,.09)), rgba(9, 16, 28, .62); box-shadow: 0 24px 58px rgba(0,0,0,.34), 0 0 32px rgba(103,232,249,.18), 0 0 34px rgba(139,92,246,.11), inset 0 1px 0 rgba(255,255,255,.15); } #${DRAWER_ID} .tg-summary-item.tg-selected .tg-summary-num { color: rgba(165,243,252,.98); text-shadow: 0 0 18px rgba(103,232,249,.18); } #${DRAWER_ID} .tg-summary-item[data-tooltip]::after { content: attr(data-tooltip); position: absolute; left: 50%; bottom: calc(100% + 10px); width: min(230px, 72vw); transform: translate(-50%, 8px); opacity: 0; pointer-events: none; color: rgba(235,245,255,.94); background: linear-gradient(145deg, rgba(18, 27, 44, .94), rgba(8, 13, 24, .9)), rgba(8, 13, 24, .92); border: 1px solid rgba(148, 227, 255, .18); border-radius: 12px; padding: 8px 10px; font-size: 11px; line-height: 1.45; box-shadow: 0 16px 36px rgba(0,0,0,.38), 0 0 24px rgba(103,232,249,.1), inset 0 1px 0 rgba(255,255,255,.08); backdrop-filter: blur(18px) saturate(145%); -webkit-backdrop-filter: blur(18px) saturate(145%); transition: opacity .22s ease, transform .22s cubic-bezier(.16, 1, .3, 1); z-index: 6; } #${DRAWER_ID} .tg-summary-item[data-tooltip]:hover::after { opacity: 1; transform: translate(-50%, 0); } #${DRAWER_ID} .tg-summary-num { position: relative; font-size: 24px; font-weight: 780; line-height: 1; margin-bottom: 8px; color: rgba(248,250,252,.96); } #${DRAWER_ID} .tg-summary-label { position: relative; color: var(--tg-muted); font-size: 11px; white-space: nowrap; } #${DRAWER_ID} .tg-current-filter { margin: -2px 2px 12px; color: rgba(203, 213, 225, .76); font-size: 12px; font-weight: 650; } #${DRAWER_ID} .tg-current-filter strong { color: rgba(165,243,252,.94); font-weight: 760; } #${DRAWER_ID} .tg-date-filter { border-radius: 22px; padding: 13px; margin-bottom: 12px; } #${DRAWER_ID} .tg-date-filter-row { position: relative; display: flex; align-items: center; gap: 8px; color: rgba(235,245,255,.9); font-size: 12px; font-weight: 650; } #${DRAWER_ID} .tg-time-mark { position: relative; width: 28px; height: 28px; flex: 0 0 auto; border-radius: 10px; border: 1px solid rgba(103,232,249,.22); background: linear-gradient(180deg, rgba(103,232,249,.14), rgba(139,92,246,.08)), rgba(255,255,255,.04); box-shadow: 0 0 18px rgba(103,232,249,.1), inset 0 1px 0 rgba(255,255,255,.16); } #${DRAWER_ID} .tg-time-mark::before, #${DRAWER_ID} .tg-time-mark::after { content: ""; position: absolute; display: block; } #${DRAWER_ID} .tg-time-mark::before { left: 7px; right: 7px; top: 8px; height: 2px; border-radius: 999px; background: rgba(103,232,249,.62); box-shadow: 0 7px 0 rgba(103,232,249,.2); } #${DRAWER_ID} .tg-time-mark::after { left: 8px; top: 6px; width: 12px; height: 14px; border: 1px solid rgba(245,250,255,.28); border-radius: 4px; } #${DRAWER_ID} .tg-date-filter select { height: 34px; min-width: 84px; border: 1px solid rgba(148, 227, 255, .18); background: rgba(2, 6, 23, .42); color: rgba(245, 250, 255, .94); border-radius: 12px; padding: 0 10px; font-size: 12px; outline: none; box-shadow: inset 0 1px 0 rgba(255,255,255,.08); transition: border-color .24s ease, box-shadow .24s ease, background .24s ease; } #${DRAWER_ID} .tg-date-filter select:focus, #${DRAWER_ID} .tg-date-filter select:hover { border-color: rgba(103,232,249,.42); box-shadow: 0 0 24px rgba(103,232,249,.1), inset 0 1px 0 rgba(255,255,255,.12); } #${DRAWER_ID} .tg-date-filter select option { background: #0f172a; color: rgba(245,250,255,.94); } #${DRAWER_ID} .tg-date-filter-hint { position: relative; margin-top: 9px; color: var(--tg-muted); font-size: 12px; } #${DRAWER_ID} .tg-action-btn, #${DRAWER_ID} .tg-detail-link { min-height: 34px; border: 1px solid rgba(148, 227, 255, .14); background: rgba(255,255,255,.045); color: rgba(226, 232, 240, .86); border-radius: 14px; cursor: pointer; padding: 0 9px; font-size: 12px; font-weight: 650; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; box-shadow: inset 0 1px 0 rgba(255,255,255,.07); transition: transform .28s cubic-bezier(.16, 1, .3, 1), border-color .28s ease, background .28s ease, box-shadow .28s ease, color .28s ease; } #${DRAWER_ID} .tg-action-btn:hover, #${DRAWER_ID} .tg-detail-link:hover { transform: translateY(-2px); border-color: rgba(103,232,249,.42); color: rgba(245,250,255,.98); background: linear-gradient(135deg, rgba(103,232,249,.14), rgba(139,92,246,.1)), rgba(255,255,255,.055); box-shadow: 0 0 24px rgba(103,232,249,.12), inset 0 1px 0 rgba(255,255,255,.12); } #${DRAWER_ID} .tg-section { margin-bottom: 16px; } #${DRAWER_ID} .tg-course-group { margin-bottom: 16px; border: 1px solid rgba(148, 227, 255, .13); background: linear-gradient(145deg, rgba(255,255,255,.06), rgba(255,255,255,.025)), rgba(8, 13, 24, .34); border-radius: 24px; padding: 12px; box-shadow: 0 18px 42px rgba(0,0,0,.22), inset 0 1px 0 rgba(255,255,255,.07); backdrop-filter: blur(18px) saturate(135%); -webkit-backdrop-filter: blur(18px) saturate(135%); } #${DRAWER_ID} .tg-course-head { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 12px; align-items: center; color: rgba(248,250,252,.96); font-size: 14px; font-weight: 760; margin: 2px 2px 10px; overflow-wrap: anywhere; cursor: pointer; } #${DRAWER_ID} .tg-course-title { min-width: 0; overflow-wrap: anywhere; } #${DRAWER_ID} .tg-course-stats { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; } #${DRAWER_ID} .tg-course-stat { color: rgba(203,213,225,.76); background: rgba(255,255,255,.045); border: 1px solid rgba(148, 227, 255, .1); border-radius: 999px; padding: 3px 7px; font-size: 11px; font-weight: 620; } #${DRAWER_ID} .tg-course-toggle { width: 34px; height: 34px; border-radius: 13px; border: 1px solid rgba(148, 227, 255, .16); background: rgba(255,255,255,.045); color: rgba(235,245,255,.9); cursor: pointer; box-shadow: inset 0 1px 0 rgba(255,255,255,.08); transition: transform .28s cubic-bezier(.16, 1, .3, 1), border-color .28s ease, box-shadow .28s ease; } #${DRAWER_ID} .tg-course-toggle:hover { transform: translateY(-2px); border-color: rgba(103,232,249,.4); box-shadow: 0 0 22px rgba(103,232,249,.12), inset 0 1px 0 rgba(255,255,255,.12); } #${DRAWER_ID} .tg-course-content { display: grid; grid-template-rows: 1fr; transition: grid-template-rows .34s cubic-bezier(.16, 1, .3, 1), opacity .28s ease; opacity: 1; } #${DRAWER_ID} .tg-course-content-inner { overflow: hidden; } #${DRAWER_ID} .tg-course-group.tg-course-collapsed .tg-course-content { grid-template-rows: 0fr; opacity: 0; } #${DRAWER_ID} .tg-course-subgroup { margin-top: 10px; border: 1px solid rgba(148, 227, 255, .09); border-radius: 18px; background: rgba(255,255,255,.025); padding: 9px; } #${DRAWER_ID} .tg-subgroup-title { display: flex; align-items: center; justify-content: space-between; gap: 10px; color: rgba(203,213,225,.76); font-size: 12px; font-weight: 720; margin: 0 2px; cursor: pointer; user-select: none; } #${DRAWER_ID} .tg-subgroup-title-main { min-width: 0; color: rgba(235,245,255,.88); } #${DRAWER_ID} .tg-subgroup-count { flex: 0 0 auto; color: var(--tg-muted); border: 1px solid rgba(148, 227, 255, .1); background: rgba(255,255,255,.04); border-radius: 999px; padding: 2px 7px; font-size: 11px; } #${DRAWER_ID} .tg-subgroup-content { display: grid; grid-template-rows: 1fr; margin-top: 8px; transition: grid-template-rows .32s cubic-bezier(.16, 1, .3, 1), opacity .26s ease; opacity: 1; } #${DRAWER_ID} .tg-subgroup-content-inner { overflow: hidden; } #${DRAWER_ID} .tg-section-collapsed .tg-subgroup-content { grid-template-rows: 0fr; opacity: 0; } #${DRAWER_ID} .tg-section-title { display: flex; justify-content: space-between; align-items: baseline; gap: 8px; color: rgba(245,250,255,.92); font-size: 13px; font-weight: 760; margin: 15px 3px 9px; } #${DRAWER_ID} .tg-section-title::before { content: ""; width: 7px; height: 7px; border-radius: 50%; background: var(--tg-cyan); box-shadow: 0 0 14px rgba(103,232,249,.52); margin-right: 1px; } #${DRAWER_ID} .tg-section-title span:first-child { flex: 1 1 auto; } #${DRAWER_ID} .tg-section-count { color: var(--tg-muted); font-size: 12px; font-weight: 560; } #${DRAWER_ID} .tg-section-urgent .tg-section-title::before { background: var(--tg-red); box-shadow: 0 0 16px rgba(251,113,133,.42); } #${DRAWER_ID} .tg-card { border-radius: 22px; padding: 13px; margin-bottom: 10px; line-height: 1.55; transition: transform .36s cubic-bezier(.16, 1, .3, 1), border-color .36s ease, box-shadow .36s ease, background .36s ease; } #${DRAWER_ID} .tg-card:hover { transform: translateY(-4px); border-color: rgba(103,232,249,.32); background: linear-gradient(145deg, rgba(255,255,255,.105), rgba(255,255,255,.045)), rgba(10, 17, 30, .54); box-shadow: 0 28px 64px rgba(0,0,0,.34), 0 0 34px rgba(103,232,249,.1), inset 0 1px 0 rgba(255,255,255,.13); } #${DRAWER_ID} .tg-card.tg-state-ok { border-color: rgba(134,239,172,.18); } #${DRAWER_ID} .tg-card.tg-state-warn { border-color: rgba(251,191,36,.22); } #${DRAWER_ID} .tg-card.tg-state-bad { border-color: rgba(251,113,133,.28); box-shadow: 0 20px 48px rgba(54, 8, 24, .26), 0 0 32px rgba(251,113,133,.08), inset 0 1px 0 rgba(255,255,255,.1); } #${DRAWER_ID} .tg-card.tg-state-muted { border-color: rgba(148,163,184,.14); } #${DRAWER_ID} .tg-card-head { position: relative; display: flex; gap: 10px; justify-content: space-between; align-items: flex-start; margin-bottom: 9px; } #${DRAWER_ID} .tg-card-title { min-width: 0; color: rgba(248,250,252,.96); font-size: 14px; font-weight: 720; line-height: 1.45; overflow-wrap: anywhere; } #${DRAWER_ID} .tg-card-title a { color: inherit; text-decoration: none; } #${DRAWER_ID} .tg-card-title a:hover { color: rgba(165,243,252,.98); } #${DRAWER_ID} .tg-type { flex: 0 0 auto; border: 1px solid rgba(148, 227, 255, .18); border-radius: 999px; padding: 3px 8px; color: rgba(225, 245, 255, .82); font-size: 11px; font-weight: 680; background: rgba(255,255,255,.045); box-shadow: inset 0 1px 0 rgba(255,255,255,.08); } #${DRAWER_ID} .tg-status { position: relative; font-size: 13px; font-weight: 760; margin: 7px 0 6px; } #${DRAWER_ID} .tg-status-row { position: relative; display: flex; align-items: center; justify-content: space-between; gap: 10px; flex-wrap: wrap; } #${DRAWER_ID} .tg-helper-countdown { display: inline-flex; align-items: center; justify-content: center; min-height: 26px; border-radius: 999px; border: 1px solid rgba(251,191,36,.34); background: linear-gradient(135deg, rgba(251,191,36,.16), rgba(251,113,133,.1)), rgba(255,255,255,.045); color: rgba(255, 237, 213, .96); padding: 3px 9px; font-size: 11px; font-weight: 760; box-shadow: 0 0 24px rgba(251,191,36,.13), inset 0 1px 0 rgba(255,255,255,.1); white-space: nowrap; animation: tgCountdownPulse 2.8s ease-in-out infinite; } #${DRAWER_ID} .tg-ok { color: var(--tg-green); } #${DRAWER_ID} .tg-warn { color: var(--tg-amber); } #${DRAWER_ID} .tg-bad { color: var(--tg-red); } #${DRAWER_ID} .tg-muted { color: var(--tg-muted); } #${DRAWER_ID} .tg-small, #${DRAWER_ID} .tg-task-meta { position: relative; color: var(--tg-muted); font-size: 12px; line-height: 1.7; overflow-wrap: anywhere; } #${DRAWER_ID} .tg-task-meta { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 7px; margin-top: 10px; } #${DRAWER_ID} .tg-task-meta span { display: block; border: 1px solid rgba(148, 227, 255, .1); background: rgba(255,255,255,.035); border-radius: 13px; padding: 7px 8px; } #${DRAWER_ID} .tg-card-footer { position: relative; display: flex; justify-content: flex-end; margin-top: 11px; } #${DRAWER_ID} .tg-detail-link { display: inline-flex; align-items: center; justify-content: center; min-height: 30px; text-decoration: none; padding: 0 12px; border: 1px solid rgba(103,232,249,.22); border-radius: 999px; color: rgba(225,245,255,.92); background: rgba(255,255,255,.045); cursor: pointer; font: inherit; font-size: 12px; transition: transform .25s cubic-bezier(.16, 1, .3, 1), border-color .25s ease, box-shadow .25s ease; } #${DRAWER_ID} .tg-detail-link:hover { transform: translateY(-1px); border-color: rgba(103,232,249,.45); box-shadow: 0 0 18px rgba(103,232,249,.12); } #${DRAWER_ID} .tg-actions { flex: 0 0 auto; display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.25fr); gap: 10px; padding: 12px 16px 16px; border-top: 1px solid rgba(148, 227, 255, .13); background: linear-gradient(180deg, rgba(8,13,24,.44), rgba(8,13,24,.78)); backdrop-filter: blur(24px) saturate(145%); -webkit-backdrop-filter: blur(24px) saturate(145%); } #${DRAWER_ID} .tg-action-btn { min-height: 40px; border-radius: 16px; } #${DRAWER_ID} [data-copy-all-json] { border-color: rgba(103,232,249,.28); background: linear-gradient(135deg, rgba(103,232,249,.13), rgba(139,92,246,.11)), rgba(255,255,255,.055); color: rgba(245,250,255,.96); } #${DRAWER_ID} [data-delete-cache] { border-color: rgba(251,113,133,.22); background: linear-gradient(135deg, rgba(251,113,133,.1), rgba(251,191,36,.06)), rgba(255,255,255,.045); color: rgba(255,228,230,.94); } #${DRAWER_ID} textarea.tg-json-box { width: 100%; height: 260px; margin-top: 10px; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, Monaco, monospace; font-size: 12px; line-height: 1.5; border: 1px solid rgba(148, 227, 255, .16); border-radius: 18px; padding: 12px; resize: vertical; color: rgba(235,245,255,.94); background: rgba(2,6,23,.58); box-shadow: inset 0 1px 0 rgba(255,255,255,.08); } @keyframes tgFloatButton { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-5px); } } @keyframes tgParticleDrift { from { background-position: 0 0, 0 0, 0 0, 0 0, 0 0; } to { background-position: 128px 70px, -184px 120px, 148px -90px, 44px 44px, -44px 44px; } } @keyframes tgSlowFloatA { 0%, 100% { transform: translate3d(0,0,0) scale(1); opacity: .58; } 50% { transform: translate3d(-18px,24px,0) scale(1.06); opacity: .76; } } @keyframes tgSlowFloatB { 0%, 100% { transform: translate3d(0,0,0) scale(1); opacity: .44; } 50% { transform: translate3d(22px,-18px,0) scale(1.08); opacity: .66; } } @keyframes tgRingDrift { 0%, 100% { transform: rotate(-18deg) translate3d(0,0,0); opacity: .36; } 50% { transform: rotate(8deg) translate3d(18px,10px,0); opacity: .52; } } @keyframes tgCountdownPulse { 0%, 100% { box-shadow: 0 0 20px rgba(251,191,36,.11), inset 0 1px 0 rgba(255,255,255,.1); } 50% { box-shadow: 0 0 30px rgba(251,191,36,.22), 0 0 18px rgba(251,113,133,.12), inset 0 1px 0 rgba(255,255,255,.14); } } /* iOS 26 Liquid Glass practical skin */ #${BUTTON_ID}, #${DRAWER_ID} { --tg-liquid-bg: rgba(255,255,255,.16); --tg-liquid-bg-strong: rgba(20, 25, 36, .72); --tg-liquid-card: rgba(255,255,255,.13); --tg-liquid-card-strong: rgba(255,255,255,.2); --tg-liquid-border: rgba(255,255,255,.35); --tg-liquid-border-soft: rgba(255,255,255,.22); --tg-liquid-shadow: 0 20px 60px rgba(0,0,0,.25); --tg-text: rgba(255,255,255,.96); --tg-muted: rgba(232,238,247,.76); --tg-faint: rgba(220,228,240,.58); --tg-cyan: #8ee8ff; --tg-violet: #b8a7ff; --tg-pink: #ffc5e8; --tg-red: #ff6b7f; --tg-amber: #ffd166; --tg-green: #8ce99a; text-shadow: 0 1px 1px rgba(0,0,0,.24); } #${BUTTON_ID} { width: auto; min-width: 132px; height: 48px; padding: 0 18px; border-radius: 999px; border: 1px solid var(--tg-liquid-border); background: linear-gradient(135deg, rgba(255,255,255,.32), rgba(255,255,255,.12)), rgba(255,255,255,.16); color: rgba(255,255,255,.96); box-shadow: var(--tg-liquid-shadow), inset 0 1px 0 rgba(255,255,255,.42), inset 0 -1px 0 rgba(255,255,255,.14); backdrop-filter: blur(22px) saturate(180%); -webkit-backdrop-filter: blur(22px) saturate(180%); font-size: 13px; font-weight: 780; line-height: 1; animation: none; transition: transform .18s cubic-bezier(.2, .8, .2, 1), border-color .18s ease, box-shadow .18s ease, background .18s ease; } #${BUTTON_ID}::before { inset: 1px; border-radius: 999px; background: linear-gradient(120deg, transparent 12%, rgba(255,255,255,.36) 32%, transparent 56%); filter: none; opacity: .34; transform: translateX(-42%); transition: transform .22s ease, opacity .18s ease; } #${BUTTON_ID}:hover { transform: translateY(-2px); border-color: rgba(255,255,255,.58); background: linear-gradient(135deg, rgba(255,255,255,.4), rgba(255,255,255,.18)), rgba(255,255,255,.2); box-shadow: 0 24px 64px rgba(0,0,0,.3), inset 0 1px 0 rgba(255,255,255,.5); } #${BUTTON_ID}:hover::before { transform: translateX(36%); opacity: .55; } #${BUTTON_ID} .tg-task-button-badge { right: 4px; top: -8px; min-width: 24px; height: 24px; border-radius: 999px; border: 1px solid rgba(255,255,255,.62); background: rgba(255, 78, 112, .84); color: #fff; line-height: 22px; box-shadow: 0 10px 24px rgba(255,78,112,.24), inset 0 1px 0 rgba(255,255,255,.38); backdrop-filter: blur(14px) saturate(180%); -webkit-backdrop-filter: blur(14px) saturate(180%); } #${DRAWER_ID} { border-radius: 28px; color: var(--tg-text); background: linear-gradient(180deg, rgba(22,27,38,.72), rgba(10,14,22,.68)), rgba(255,255,255,.16); border: 1px solid var(--tg-liquid-border); box-shadow: var(--tg-liquid-shadow), inset 0 1px 0 rgba(255,255,255,.32), inset 0 0 0 1px rgba(255,255,255,.08); backdrop-filter: blur(22px) saturate(180%); -webkit-backdrop-filter: blur(22px) saturate(180%); transition: transform .2s cubic-bezier(.2, .8, .2, 1), opacity .18s ease, box-shadow .18s ease; } #${DRAWER_ID}.tg-open { background: linear-gradient(180deg, rgba(20,25,36,.78), rgba(8,12,20,.74)), rgba(255,255,255,.18); } #${DRAWER_ID}::before { background: linear-gradient(115deg, rgba(255,255,255,.2), transparent 24%), radial-gradient(circle at 18% 0%, rgba(255,255,255,.16), transparent 30%); opacity: .55; animation: none; mask-image: none; } #${DRAWER_ID}::after { border-radius: 27px; background: linear-gradient(135deg, rgba(255,255,255,.2), transparent 28%), linear-gradient(315deg, rgba(255,255,255,.08), transparent 34%); opacity: .45; } #${DRAWER_ID} .tg-ambient { display: none; } #${DRAWER_ID} .tg-header { min-height: 76px; border-bottom: 1px solid rgba(255,255,255,.18); background: rgba(255,255,255,.08); backdrop-filter: blur(16px) saturate(160%); -webkit-backdrop-filter: blur(16px) saturate(160%); } #${DRAWER_ID} .tg-title-main, #${DRAWER_ID} .tg-card-title, #${DRAWER_ID} .tg-course-head, #${DRAWER_ID} .tg-section-title { color: rgba(255,255,255,.98); text-shadow: 0 1px 2px rgba(0,0,0,.28); } #${DRAWER_ID} .tg-icon-btn, #${DRAWER_ID} .tg-action-btn, #${DRAWER_ID} .tg-detail-link, #${DRAWER_ID} .tg-course-toggle, #${DRAWER_ID} .tg-subgroup-count, #${DRAWER_ID} .tg-course-stat, #${DRAWER_ID} .tg-type, #${DRAWER_ID} .tg-date-filter select { border-radius: 999px; border-color: var(--tg-liquid-border-soft); background: rgba(255,255,255,.13); color: rgba(255,255,255,.92); box-shadow: inset 0 1px 0 rgba(255,255,255,.22); backdrop-filter: blur(14px) saturate(170%); -webkit-backdrop-filter: blur(14px) saturate(170%); transition: transform .16s ease, border-color .16s ease, background .16s ease, box-shadow .16s ease; } #${DRAWER_ID} .tg-top-refresh { min-width: 78px; } #${DRAWER_ID} .tg-icon-btn:hover, #${DRAWER_ID} .tg-action-btn:hover, #${DRAWER_ID} .tg-detail-link:hover, #${DRAWER_ID} .tg-course-toggle:hover, #${DRAWER_ID} .tg-date-filter select:hover, #${DRAWER_ID} .tg-date-filter select:focus { transform: translateY(-1px); border-color: rgba(255,255,255,.48); background: rgba(255,255,255,.2); box-shadow: 0 10px 26px rgba(0,0,0,.18), inset 0 1px 0 rgba(255,255,255,.34); } #${DRAWER_ID} .tg-meta, #${DRAWER_ID} .tg-alert, #${DRAWER_ID} .tg-empty, #${DRAWER_ID} .tg-date-filter, #${DRAWER_ID} .tg-summary-item, #${DRAWER_ID} .tg-refresh-status, #${DRAWER_ID} .tg-course-group, #${DRAWER_ID} .tg-course-subgroup, #${DRAWER_ID} .tg-card { border: 1px solid var(--tg-liquid-border-soft); background: linear-gradient(180deg, rgba(255,255,255,.16), rgba(255,255,255,.08)), rgba(255,255,255,.1); box-shadow: 0 12px 34px rgba(0,0,0,.18), inset 0 1px 0 rgba(255,255,255,.24); backdrop-filter: blur(18px) saturate(170%); -webkit-backdrop-filter: blur(18px) saturate(170%); } #${DRAWER_ID} .tg-meta::before, #${DRAWER_ID} .tg-alert::before, #${DRAWER_ID} .tg-empty::before, #${DRAWER_ID} .tg-date-filter::before, #${DRAWER_ID} .tg-summary-item::before, #${DRAWER_ID} .tg-card::before { background: linear-gradient(120deg, rgba(255,255,255,.18), transparent 38%); opacity: .42; mix-blend-mode: normal; } #${DRAWER_ID} .tg-summary-item, #${DRAWER_ID} .tg-card { transition: transform .18s cubic-bezier(.2, .8, .2, 1), border-color .18s ease, box-shadow .18s ease, background .18s ease; } #${DRAWER_ID} .tg-summary-item:hover, #${DRAWER_ID} .tg-card:hover { transform: translateY(-2px); border-color: rgba(255,255,255,.46); background: linear-gradient(180deg, rgba(255,255,255,.22), rgba(255,255,255,.1)), rgba(255,255,255,.14); box-shadow: 0 16px 42px rgba(0,0,0,.22), inset 0 1px 0 rgba(255,255,255,.3); } #${DRAWER_ID} .tg-summary-item.tg-selected { border-color: rgba(142,232,255,.62); background: linear-gradient(180deg, rgba(142,232,255,.2), rgba(255,255,255,.12)), rgba(255,255,255,.16); box-shadow: 0 16px 42px rgba(0,0,0,.22), 0 0 0 1px rgba(142,232,255,.18), inset 0 1px 0 rgba(255,255,255,.32); } #${DRAWER_ID} .tg-summary-item[data-tooltip]::after, #${DRAWER_ID} .tg-status[data-tooltip]::after { color: rgba(255,255,255,.96); background: linear-gradient(180deg, rgba(34,40,54,.86), rgba(12,16,25,.82)), rgba(255,255,255,.16); border: 1px solid rgba(255,255,255,.32); box-shadow: 0 16px 42px rgba(0,0,0,.24), inset 0 1px 0 rgba(255,255,255,.22); backdrop-filter: blur(18px) saturate(180%); -webkit-backdrop-filter: blur(18px) saturate(180%); } #${DRAWER_ID} .tg-status[data-tooltip] { cursor: help; } #${DRAWER_ID} .tg-status[data-tooltip]::after { content: attr(data-tooltip); position: absolute; left: 0; bottom: calc(100% + 8px); width: min(240px, 72vw); transform: translateY(6px); opacity: 0; pointer-events: none; border-radius: 14px; padding: 8px 10px; font-size: 11px; line-height: 1.45; font-weight: 560; transition: opacity .16s ease, transform .16s ease; z-index: 8; } #${DRAWER_ID} .tg-status[data-tooltip]:hover::after { opacity: 1; transform: translateY(0); } #${DRAWER_ID} .tg-progress-track { height: 5px; border-radius: 999px; border: 1px solid rgba(255,255,255,.2); background: rgba(255,255,255,.12); } #${DRAWER_ID} .tg-progress-fill { background: linear-gradient(90deg, rgba(142,232,255,.92), rgba(184,167,255,.82), rgba(255,197,232,.76)); box-shadow: 0 0 16px rgba(142,232,255,.18); transition: width .18s ease, opacity .18s ease; } #${DRAWER_ID} .tg-refresh-status.tg-refresh-done { animation: tgProgressFade .9s ease .8s forwards; } #${DRAWER_ID} .tg-task-meta span { border-color: rgba(255,255,255,.16); background: rgba(255,255,255,.08); color: rgba(238,244,252,.82); backdrop-filter: blur(10px) saturate(150%); -webkit-backdrop-filter: blur(10px) saturate(150%); } #${DRAWER_ID} .tg-actions { border-top: 1px solid rgba(255,255,255,.18); background: rgba(10,14,22,.52); backdrop-filter: blur(18px) saturate(180%); -webkit-backdrop-filter: blur(18px) saturate(180%); } #${DRAWER_ID} textarea.tg-json-box { color: rgba(255,255,255,.94); background: rgba(12,16,25,.68); border-color: rgba(255,255,255,.22); } @supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) { #${BUTTON_ID} { background: rgba(32, 38, 50, .94); } #${DRAWER_ID} { background: rgba(20, 24, 34, .96); } #${DRAWER_ID} .tg-meta, #${DRAWER_ID} .tg-alert, #${DRAWER_ID} .tg-empty, #${DRAWER_ID} .tg-date-filter, #${DRAWER_ID} .tg-summary-item, #${DRAWER_ID} .tg-refresh-status, #${DRAWER_ID} .tg-course-group, #${DRAWER_ID} .tg-course-subgroup, #${DRAWER_ID} .tg-card { background: rgba(35, 42, 56, .92); } } @keyframes tgProgressFade { to { opacity: .42; transform: translateY(-1px); } } /* Liquid Glass Pro light skin: CSS + SVG filter, optimized for white web pages */ #${BUTTON_ID}, #${DRAWER_ID} { --tg-liquid-surface: rgba(255,255,255,.58); --tg-liquid-card: rgba(255,255,255,.42); --tg-liquid-card-hover: rgba(255,255,255,.58); --tg-liquid-border: rgba(255,255,255,.75); --tg-liquid-line: rgba(15,23,42,.08); --tg-liquid-shadow: 0 20px 60px rgba(15,23,42,.18); --tg-liquid-ease: cubic-bezier(.22, 1, .36, 1); --tg-text: #111827; --tg-muted: rgba(31,41,55,.72); --tg-faint: rgba(75,85,99,.58); --tg-cyan: #0891b2; --tg-violet: #6d28d9; --tg-pink: #be185d; --tg-red: #dc2626; --tg-amber: #b45309; --tg-green: #15803d; color: var(--tg-text); text-shadow: none; } #${BUTTON_ID} { min-width: 136px; height: 50px; border-radius: 999px; border: 1px solid var(--tg-liquid-border); background: radial-gradient(circle at var(--tg-mouse-x, 28%) var(--tg-mouse-y, 18%), rgba(255,255,255,.96), transparent 36%), linear-gradient(135deg, rgba(255,255,255,.78), rgba(255,255,255,.38)), rgba(255,255,255,.58); color: #111827; box-shadow: 0 18px 46px rgba(15,23,42,.16), inset 0 1px 0 rgba(255,255,255,.86), inset 0 -1px 0 rgba(255,255,255,.42); backdrop-filter: blur(26px) saturate(180%); -webkit-backdrop-filter: blur(26px) saturate(180%); transition: transform .18s var(--tg-liquid-ease), border-color .18s ease, box-shadow .18s ease, background .18s ease; } #${BUTTON_ID}::before { inset: -1px; border-radius: 999px; background: linear-gradient(120deg, transparent 18%, rgba(255,255,255,.82) 38%, transparent 58%), radial-gradient(circle at 50% 0%, rgba(255,255,255,.74), transparent 44%); filter: url(#${SVG_FILTER_ID}); opacity: .48; transform: translateX(-28%); transition: transform .22s var(--tg-liquid-ease), opacity .18s ease; } #${BUTTON_ID}:hover { transform: translateY(-2px); border-color: rgba(255,255,255,.92); background: radial-gradient(circle at var(--tg-mouse-x, 28%) var(--tg-mouse-y, 18%), rgba(255,255,255,1), transparent 38%), linear-gradient(135deg, rgba(255,255,255,.86), rgba(255,255,255,.5)), rgba(255,255,255,.66); box-shadow: 0 22px 54px rgba(15,23,42,.2), inset 0 1px 0 rgba(255,255,255,.95); } #${BUTTON_ID}:active { transform: translateY(0) scale(.985); } #${BUTTON_ID}:hover::before { transform: translateX(22%); opacity: .68; } #${BUTTON_ID} .tg-task-button-badge { background: rgba(239, 68, 68, .88); color: #fff; border-color: rgba(255,255,255,.9); box-shadow: 0 8px 20px rgba(239,68,68,.24), inset 0 1px 0 rgba(255,255,255,.42); } #${DRAWER_ID} { color: #111827; background: radial-gradient(circle at var(--tg-mouse-x, 72%) var(--tg-mouse-y, 12%), rgba(255,255,255,.92), transparent 30%), linear-gradient(180deg, rgba(255,255,255,.68), rgba(255,255,255,.48)), rgba(255,255,255,.58); border: 1px solid var(--tg-liquid-border); box-shadow: var(--tg-liquid-shadow), inset 0 1px 0 rgba(255,255,255,.8), inset 0 -1px 0 rgba(255,255,255,.32); backdrop-filter: blur(26px) saturate(180%); -webkit-backdrop-filter: blur(26px) saturate(180%); transition: transform .28s var(--tg-liquid-ease), opacity .22s ease, box-shadow .22s ease; } #${DRAWER_ID}.tg-open { background: radial-gradient(circle at var(--tg-mouse-x, 72%) var(--tg-mouse-y, 12%), rgba(255,255,255,.98), transparent 32%), linear-gradient(180deg, rgba(255,255,255,.72), rgba(255,255,255,.54)), rgba(255,255,255,.58); } #${DRAWER_ID}::before { background: linear-gradient(125deg, rgba(255,255,255,.92), transparent 24%, rgba(255,255,255,.22) 58%, transparent 76%), radial-gradient(circle at var(--tg-mouse-x, 80%) var(--tg-mouse-y, 8%), rgba(255,255,255,.72), transparent 26%); filter: url(#${SVG_FILTER_ID}); opacity: .48; mix-blend-mode: screen; animation: none; mask-image: none; } #${DRAWER_ID}::after { border-radius: 27px; background: linear-gradient(135deg, rgba(255,255,255,.72), transparent 26%), linear-gradient(315deg, rgba(255,255,255,.32), transparent 34%); opacity: .58; mix-blend-mode: screen; } #${DRAWER_ID} .tg-header { border-bottom: 1px solid rgba(15,23,42,.08); background: rgba(255,255,255,.36); backdrop-filter: blur(18px) saturate(180%); -webkit-backdrop-filter: blur(18px) saturate(180%); } #${DRAWER_ID} .tg-title-main, #${DRAWER_ID} .tg-card-title, #${DRAWER_ID} .tg-course-head, #${DRAWER_ID} .tg-section-title, #${DRAWER_ID} .tg-meta-value, #${DRAWER_ID} .tg-refresh-title { color: #111827; text-shadow: none; } #${DRAWER_ID} .tg-title-sub, #${DRAWER_ID} .tg-meta-label, #${DRAWER_ID} .tg-refresh-stage, #${DRAWER_ID} .tg-small, #${DRAWER_ID} .tg-task-meta, #${DRAWER_ID} .tg-summary-label, #${DRAWER_ID} .tg-date-filter-hint, #${DRAWER_ID} .tg-current-filter, #${DRAWER_ID} .tg-section-count, #${DRAWER_ID} .tg-course-stat, #${DRAWER_ID} .tg-subgroup-title, #${DRAWER_ID} .tg-subgroup-count { color: var(--tg-muted); text-shadow: none; } #${DRAWER_ID} .tg-meta, #${DRAWER_ID} .tg-alert, #${DRAWER_ID} .tg-empty, #${DRAWER_ID} .tg-date-filter, #${DRAWER_ID} .tg-summary-item, #${DRAWER_ID} .tg-refresh-status, #${DRAWER_ID} .tg-course-group, #${DRAWER_ID} .tg-course-subgroup, #${DRAWER_ID} .tg-card { color: #111827; border: 1px solid var(--tg-liquid-line); background: var(--tg-liquid-card); box-shadow: 0 10px 30px rgba(15,23,42,.08), inset 0 1px 0 rgba(255,255,255,.72); backdrop-filter: none; -webkit-backdrop-filter: none; transition: transform .18s var(--tg-liquid-ease), border-color .18s ease, box-shadow .18s ease, background .18s ease; } #${DRAWER_ID} .tg-meta, #${DRAWER_ID} .tg-alert, #${DRAWER_ID} .tg-empty, #${DRAWER_ID} .tg-date-filter, #${DRAWER_ID} .tg-summary-item, #${DRAWER_ID} .tg-refresh-status, #${DRAWER_ID} .tg-course-group { backdrop-filter: blur(12px) saturate(160%); -webkit-backdrop-filter: blur(12px) saturate(160%); } #${DRAWER_ID} .tg-meta::before, #${DRAWER_ID} .tg-alert::before, #${DRAWER_ID} .tg-empty::before, #${DRAWER_ID} .tg-date-filter::before, #${DRAWER_ID} .tg-summary-item::before, #${DRAWER_ID} .tg-card::before { background: linear-gradient(135deg, rgba(255,255,255,.68), transparent 38%); opacity: .45; mix-blend-mode: normal; } #${DRAWER_ID} .tg-summary-item:hover, #${DRAWER_ID} .tg-card:hover, #${DRAWER_ID} .tg-course-group:hover { transform: translateY(-2px); border-color: rgba(15,23,42,.13); background: var(--tg-liquid-card-hover); box-shadow: 0 14px 34px rgba(15,23,42,.12), inset 0 1px 0 rgba(255,255,255,.86); } #${DRAWER_ID} .tg-summary-item:active, #${DRAWER_ID} .tg-card:active { transform: translateY(0) scale(.995); } #${DRAWER_ID} .tg-summary-item.tg-selected { border-color: rgba(8,145,178,.28); background: rgba(236,254,255,.66); box-shadow: 0 14px 34px rgba(8,145,178,.12), inset 0 1px 0 rgba(255,255,255,.88); } #${DRAWER_ID} .tg-summary-item.tg-selected .tg-summary-num { color: #075985; text-shadow: none; } #${DRAWER_ID} .tg-icon-btn, #${DRAWER_ID} .tg-action-btn, #${DRAWER_ID} .tg-detail-link, #${DRAWER_ID} .tg-course-toggle, #${DRAWER_ID} .tg-type, #${DRAWER_ID} .tg-date-filter select { position: relative; overflow: hidden; color: #111827; border: 1px solid rgba(15,23,42,.1); background: rgba(255,255,255,.48); box-shadow: 0 8px 22px rgba(15,23,42,.07), inset 0 1px 0 rgba(255,255,255,.78); backdrop-filter: blur(14px) saturate(170%); -webkit-backdrop-filter: blur(14px) saturate(170%); transition: transform .18s var(--tg-liquid-ease), border-color .18s ease, background .18s ease, box-shadow .18s ease; } #${DRAWER_ID} .tg-icon-btn::before, #${DRAWER_ID} .tg-action-btn::before, #${DRAWER_ID} .tg-detail-link::before, #${DRAWER_ID} .tg-course-toggle::before { content: ""; position: absolute; inset: 0; pointer-events: none; background: linear-gradient(120deg, transparent, rgba(255,255,255,.72), transparent); transform: translateX(-120%); transition: transform .22s var(--tg-liquid-ease); } #${DRAWER_ID} .tg-icon-btn:hover, #${DRAWER_ID} .tg-action-btn:hover, #${DRAWER_ID} .tg-detail-link:hover, #${DRAWER_ID} .tg-course-toggle:hover, #${DRAWER_ID} .tg-date-filter select:hover, #${DRAWER_ID} .tg-date-filter select:focus { transform: translateY(-1px); border-color: rgba(15,23,42,.16); background: rgba(255,255,255,.68); box-shadow: 0 12px 28px rgba(15,23,42,.11), inset 0 1px 0 rgba(255,255,255,.92); } #${DRAWER_ID} .tg-icon-btn:hover::before, #${DRAWER_ID} .tg-action-btn:hover::before, #${DRAWER_ID} .tg-detail-link:hover::before, #${DRAWER_ID} .tg-course-toggle:hover::before { transform: translateX(120%); } #${DRAWER_ID} .tg-icon-btn:active, #${DRAWER_ID} .tg-action-btn:active, #${DRAWER_ID} .tg-detail-link:active, #${DRAWER_ID} .tg-course-toggle:active { transform: translateY(0) scale(.98); } #${DRAWER_ID} .tg-liquid-ripple { position: absolute; width: 10px; height: 10px; border-radius: 999px; pointer-events: none; background: rgba(8,145,178,.16); transform: translate(-50%, -50%) scale(1); animation: tgLiquidRipple .55s var(--tg-liquid-ease) forwards; z-index: 0; } #${DRAWER_ID} .tg-progress-track { height: 5px; border: 1px solid rgba(15,23,42,.08); background: rgba(255,255,255,.48); box-shadow: inset 0 1px 2px rgba(15,23,42,.05); } #${DRAWER_ID} .tg-progress-fill { background: linear-gradient(90deg, rgba(8,145,178,.68), rgba(99,102,241,.58), rgba(190,24,93,.42)); box-shadow: 0 0 16px rgba(8,145,178,.16); filter: url(#${SVG_FILTER_ID}); transition: width .18s ease, opacity .18s ease; } #${DRAWER_ID} .tg-summary-item[data-tooltip]::after, #${DRAWER_ID} .tg-status[data-tooltip]::after { color: #111827; background: linear-gradient(180deg, rgba(255,255,255,.88), rgba(255,255,255,.72)), rgba(255,255,255,.78); border: 1px solid rgba(15,23,42,.1); box-shadow: 0 16px 40px rgba(15,23,42,.14), inset 0 1px 0 rgba(255,255,255,.88); backdrop-filter: blur(18px) saturate(180%); -webkit-backdrop-filter: blur(18px) saturate(180%); text-shadow: none; } #${DRAWER_ID} .tg-summary-item[data-tooltip]::before, #${DRAWER_ID} .tg-status[data-tooltip]::before { content: ""; position: absolute; width: 9px; height: 9px; background: rgba(255,255,255,.78); border-right: 1px solid rgba(15,23,42,.08); border-bottom: 1px solid rgba(15,23,42,.08); transform: rotate(45deg); opacity: 0; pointer-events: none; transition: opacity .16s ease; z-index: 7; } #${DRAWER_ID} .tg-summary-item[data-tooltip]::before { left: 50%; bottom: calc(100% + 5px); margin-left: -4px; } #${DRAWER_ID} .tg-status[data-tooltip]::before { left: 18px; bottom: calc(100% + 3px); } #${DRAWER_ID} .tg-summary-item[data-tooltip]:hover::before, #${DRAWER_ID} .tg-status[data-tooltip]:hover::before { opacity: 1; } #${DRAWER_ID} .tg-alert { color: #7f1d1d; background: rgba(254,242,242,.72); border-color: rgba(220,38,38,.14); } #${DRAWER_ID} .tg-empty { color: #374151; } #${DRAWER_ID} .tg-task-meta span, #${DRAWER_ID} .tg-course-stat, #${DRAWER_ID} .tg-subgroup-count { border-color: rgba(15,23,42,.06); background: rgba(255,255,255,.38); color: rgba(31,41,55,.72); backdrop-filter: none; -webkit-backdrop-filter: none; } #${DRAWER_ID} .tg-actions { border-top: 1px solid rgba(15,23,42,.08); background: rgba(255,255,255,.38); backdrop-filter: blur(18px) saturate(180%); -webkit-backdrop-filter: blur(18px) saturate(180%); } #${DRAWER_ID} textarea.tg-json-box { color: #111827; background: rgba(255,255,255,.72); border-color: rgba(15,23,42,.1); } @supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) { #${BUTTON_ID}, #${DRAWER_ID} { background: rgba(255,255,255,.94); } #${DRAWER_ID} .tg-meta, #${DRAWER_ID} .tg-alert, #${DRAWER_ID} .tg-empty, #${DRAWER_ID} .tg-date-filter, #${DRAWER_ID} .tg-summary-item, #${DRAWER_ID} .tg-refresh-status, #${DRAWER_ID} .tg-course-group, #${DRAWER_ID} .tg-course-subgroup, #${DRAWER_ID} .tg-card { background: rgba(255,255,255,.9); } } @keyframes tgLiquidRipple { to { opacity: 0; transform: translate(-50%, -50%) scale(34); } } /* Readable dark console skin: higher contrast, less white haze */ #${BUTTON_ID}, #${DRAWER_ID} { --tg-liquid-surface: rgba(10, 14, 24, .78); --tg-liquid-card: rgba(17, 24, 39, .74); --tg-liquid-card-hover: rgba(24, 34, 52, .86); --tg-liquid-border: rgba(125, 211, 252, .24); --tg-liquid-line: rgba(148, 163, 184, .2); --tg-liquid-shadow: 0 22px 70px rgba(0, 0, 0, .46); --tg-text: rgba(248, 250, 252, .96); --tg-muted: rgba(203, 213, 225, .78); --tg-faint: rgba(148, 163, 184, .7); --tg-cyan: #22d3ee; --tg-violet: #8b5cf6; --tg-pink: #f472b6; --tg-red: #fb7185; --tg-amber: #fbbf24; --tg-green: #4ade80; color: var(--tg-text); } #${BUTTON_ID} { border: 1px solid rgba(125, 211, 252, .32); background: radial-gradient(circle at var(--tg-mouse-x, 28%) var(--tg-mouse-y, 18%), rgba(34,211,238,.18), transparent 36%), linear-gradient(135deg, rgba(31,41,55,.92), rgba(8,13,24,.82)), rgba(10,14,24,.82); color: rgba(248,250,252,.96); box-shadow: 0 18px 48px rgba(0,0,0,.42), 0 0 0 1px rgba(255,255,255,.04) inset, inset 0 1px 0 rgba(255,255,255,.16); backdrop-filter: blur(24px) saturate(170%); -webkit-backdrop-filter: blur(24px) saturate(170%); } #${BUTTON_ID}::before { background: linear-gradient(120deg, transparent 18%, rgba(125,211,252,.28) 38%, transparent 58%), radial-gradient(circle at 50% 0%, rgba(167,139,250,.18), transparent 44%); opacity: .36; } #${BUTTON_ID}:hover { border-color: rgba(125, 211, 252, .56); background: radial-gradient(circle at var(--tg-mouse-x, 28%) var(--tg-mouse-y, 18%), rgba(34,211,238,.26), transparent 38%), linear-gradient(135deg, rgba(38,50,72,.96), rgba(12,18,31,.9)), rgba(12,18,31,.9); box-shadow: 0 24px 60px rgba(0,0,0,.5), 0 0 28px rgba(34,211,238,.14), inset 0 1px 0 rgba(255,255,255,.2); } #${DRAWER_ID} { color: var(--tg-text); background: radial-gradient(circle at var(--tg-mouse-x, 72%) var(--tg-mouse-y, 12%), rgba(34,211,238,.13), transparent 30%), radial-gradient(circle at 100% 0%, rgba(139,92,246,.12), transparent 34%), linear-gradient(145deg, rgba(17,24,39,.84), rgba(3,7,18,.82)), rgba(10,14,24,.78); border: 1px solid rgba(125, 211, 252, .26); box-shadow: var(--tg-liquid-shadow), 0 0 42px rgba(34,211,238,.08), 0 0 74px rgba(139,92,246,.07), inset 0 1px 0 rgba(255,255,255,.12); backdrop-filter: blur(24px) saturate(170%); -webkit-backdrop-filter: blur(24px) saturate(170%); } #${DRAWER_ID}.tg-open { background: radial-gradient(circle at var(--tg-mouse-x, 72%) var(--tg-mouse-y, 12%), rgba(34,211,238,.15), transparent 32%), radial-gradient(circle at 100% 0%, rgba(139,92,246,.14), transparent 35%), linear-gradient(145deg, rgba(17,24,39,.88), rgba(3,7,18,.86)), rgba(10,14,24,.84); } #${DRAWER_ID}::before { background: linear-gradient(125deg, rgba(255,255,255,.14), transparent 24%, rgba(34,211,238,.08) 58%, transparent 76%), radial-gradient(circle at var(--tg-mouse-x, 80%) var(--tg-mouse-y, 8%), rgba(125,211,252,.16), transparent 26%); opacity: .42; mix-blend-mode: screen; } #${DRAWER_ID}::after { background: linear-gradient(135deg, rgba(255,255,255,.13), transparent 28%), linear-gradient(315deg, rgba(34,211,238,.08), transparent 36%); opacity: .42; } #${DRAWER_ID} .tg-header { border-bottom: 1px solid rgba(148, 163, 184, .18); background: rgba(15, 23, 42, .58); backdrop-filter: blur(18px) saturate(160%); -webkit-backdrop-filter: blur(18px) saturate(160%); } #${DRAWER_ID} .tg-title-main, #${DRAWER_ID} .tg-card-title, #${DRAWER_ID} .tg-course-head, #${DRAWER_ID} .tg-section-title, #${DRAWER_ID} .tg-meta-value, #${DRAWER_ID} .tg-refresh-title, #${DRAWER_ID} .tg-summary-num { color: rgba(248,250,252,.98); text-shadow: none; } #${DRAWER_ID} .tg-title-sub, #${DRAWER_ID} .tg-meta-label, #${DRAWER_ID} .tg-refresh-stage, #${DRAWER_ID} .tg-small, #${DRAWER_ID} .tg-task-meta, #${DRAWER_ID} .tg-summary-label, #${DRAWER_ID} .tg-date-filter-hint, #${DRAWER_ID} .tg-current-filter, #${DRAWER_ID} .tg-section-count, #${DRAWER_ID} .tg-course-stat, #${DRAWER_ID} .tg-subgroup-title, #${DRAWER_ID} .tg-subgroup-count { color: var(--tg-muted); text-shadow: none; } #${DRAWER_ID} .tg-current-filter strong, #${DRAWER_ID} .tg-meta-kicker { color: #67e8f9; } #${DRAWER_ID} .tg-meta, #${DRAWER_ID} .tg-alert, #${DRAWER_ID} .tg-empty, #${DRAWER_ID} .tg-date-filter, #${DRAWER_ID} .tg-summary-item, #${DRAWER_ID} .tg-refresh-status, #${DRAWER_ID} .tg-course-group, #${DRAWER_ID} .tg-course-subgroup, #${DRAWER_ID} .tg-card { color: var(--tg-text); border: 1px solid var(--tg-liquid-line); background: linear-gradient(180deg, rgba(30,41,59,.72), rgba(15,23,42,.66)), rgba(15, 23, 42, .72); box-shadow: 0 12px 34px rgba(0,0,0,.28), inset 0 1px 0 rgba(255,255,255,.08); backdrop-filter: none; -webkit-backdrop-filter: none; } #${DRAWER_ID} .tg-meta, #${DRAWER_ID} .tg-alert, #${DRAWER_ID} .tg-empty, #${DRAWER_ID} .tg-date-filter, #${DRAWER_ID} .tg-summary-item, #${DRAWER_ID} .tg-refresh-status, #${DRAWER_ID} .tg-course-group { backdrop-filter: blur(10px) saturate(145%); -webkit-backdrop-filter: blur(10px) saturate(145%); } #${DRAWER_ID} .tg-meta::before, #${DRAWER_ID} .tg-alert::before, #${DRAWER_ID} .tg-empty::before, #${DRAWER_ID} .tg-date-filter::before, #${DRAWER_ID} .tg-summary-item::before, #${DRAWER_ID} .tg-card::before { background: linear-gradient(135deg, rgba(255,255,255,.1), transparent 38%); opacity: .32; } #${DRAWER_ID} .tg-summary-item:hover, #${DRAWER_ID} .tg-card:hover, #${DRAWER_ID} .tg-course-group:hover { border-color: rgba(125,211,252,.34); background: linear-gradient(180deg, rgba(38,50,72,.84), rgba(17,24,39,.78)), rgba(17,24,39,.82); box-shadow: 0 16px 42px rgba(0,0,0,.36), 0 0 26px rgba(34,211,238,.08), inset 0 1px 0 rgba(255,255,255,.12); } #${DRAWER_ID} .tg-summary-item.tg-selected { border-color: rgba(34,211,238,.5); background: linear-gradient(180deg, rgba(14,116,144,.32), rgba(17,24,39,.82)), rgba(17,24,39,.86); box-shadow: 0 16px 42px rgba(0,0,0,.36), 0 0 28px rgba(34,211,238,.16), inset 0 1px 0 rgba(255,255,255,.14); } #${DRAWER_ID} .tg-summary-item.tg-selected .tg-summary-num { color: #67e8f9; } #${DRAWER_ID} .tg-icon-btn, #${DRAWER_ID} .tg-action-btn, #${DRAWER_ID} .tg-detail-link, #${DRAWER_ID} .tg-course-toggle, #${DRAWER_ID} .tg-type, #${DRAWER_ID} .tg-date-filter select { color: rgba(248,250,252,.94); border: 1px solid rgba(148,163,184,.22); background: rgba(15,23,42,.7); box-shadow: 0 8px 22px rgba(0,0,0,.24), inset 0 1px 0 rgba(255,255,255,.08); backdrop-filter: blur(12px) saturate(145%); -webkit-backdrop-filter: blur(12px) saturate(145%); } #${DRAWER_ID} .tg-icon-btn::before, #${DRAWER_ID} .tg-action-btn::before, #${DRAWER_ID} .tg-detail-link::before, #${DRAWER_ID} .tg-course-toggle::before { background: linear-gradient(120deg, transparent, rgba(125,211,252,.2), transparent); } #${DRAWER_ID} .tg-icon-btn:hover, #${DRAWER_ID} .tg-action-btn:hover, #${DRAWER_ID} .tg-detail-link:hover, #${DRAWER_ID} .tg-course-toggle:hover, #${DRAWER_ID} .tg-date-filter select:hover, #${DRAWER_ID} .tg-date-filter select:focus { border-color: rgba(125,211,252,.42); background: rgba(30,41,59,.82); box-shadow: 0 12px 30px rgba(0,0,0,.32), 0 0 22px rgba(34,211,238,.08), inset 0 1px 0 rgba(255,255,255,.12); } #${DRAWER_ID} .tg-date-filter select option { background: #0f172a; color: rgba(248,250,252,.94); } #${DRAWER_ID} [data-copy-all-json] { border-color: rgba(34,211,238,.36); background: rgba(8,47,73,.72); color: rgba(236,254,255,.98); } #${DRAWER_ID} [data-delete-cache] { border-color: rgba(251,113,133,.36); background: rgba(76, 29, 38, .72); color: rgba(255,228,230,.96); } #${DRAWER_ID} .tg-task-meta span, #${DRAWER_ID} .tg-course-stat, #${DRAWER_ID} .tg-subgroup-count { border-color: rgba(148,163,184,.14); background: rgba(15,23,42,.48); color: rgba(203,213,225,.82); } #${DRAWER_ID} .tg-progress-track { border: 1px solid rgba(148,163,184,.18); background: rgba(15,23,42,.72); } #${DRAWER_ID} .tg-progress-fill { background: linear-gradient(90deg, rgba(34,211,238,.9), rgba(99,102,241,.78), rgba(139,92,246,.68)); box-shadow: 0 0 18px rgba(34,211,238,.18); } #${DRAWER_ID} .tg-summary-item[data-tooltip]::after, #${DRAWER_ID} .tg-status[data-tooltip]::after { color: rgba(248,250,252,.96); background: linear-gradient(180deg, rgba(30,41,59,.94), rgba(15,23,42,.92)), rgba(15,23,42,.92); border: 1px solid rgba(125,211,252,.22); box-shadow: 0 16px 40px rgba(0,0,0,.34), inset 0 1px 0 rgba(255,255,255,.1); } #${DRAWER_ID} .tg-summary-item[data-tooltip]::before, #${DRAWER_ID} .tg-status[data-tooltip]::before { background: rgba(30,41,59,.94); border-right: 1px solid rgba(125,211,252,.18); border-bottom: 1px solid rgba(125,211,252,.18); } #${DRAWER_ID} .tg-alert { color: #fecdd3; background: linear-gradient(180deg, rgba(127,29,29,.42), rgba(15,23,42,.72)), rgba(76,29,38,.66); border-color: rgba(251,113,133,.25); } #${DRAWER_ID} .tg-empty { color: rgba(203,213,225,.84); } #${DRAWER_ID} .tg-actions { border-top: 1px solid rgba(148,163,184,.18); background: rgba(3,7,18,.72); backdrop-filter: blur(18px) saturate(150%); -webkit-backdrop-filter: blur(18px) saturate(150%); } #${DRAWER_ID} textarea.tg-json-box { color: rgba(248,250,252,.94); background: rgba(2,6,23,.82); border-color: rgba(148,163,184,.2); } #${DRAWER_ID} .tg-ok { color: #4ade80; } #${DRAWER_ID} .tg-warn { color: #fbbf24; } #${DRAWER_ID} .tg-bad { color: #fb7185; } #${DRAWER_ID} .tg-muted { color: rgba(203,213,225,.78); } #${DRAWER_ID} .tg-state-ok { border-color: rgba(74,222,128,.22); } #${DRAWER_ID} .tg-state-warn { border-color: rgba(251,191,36,.28); } #${DRAWER_ID} .tg-state-bad { border-color: rgba(251,113,133,.34); } @supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) { #${BUTTON_ID}, #${DRAWER_ID} { background: rgba(15,23,42,.96); } #${DRAWER_ID} .tg-meta, #${DRAWER_ID} .tg-alert, #${DRAWER_ID} .tg-empty, #${DRAWER_ID} .tg-date-filter, #${DRAWER_ID} .tg-summary-item, #${DRAWER_ID} .tg-refresh-status, #${DRAWER_ID} .tg-course-group, #${DRAWER_ID} .tg-course-subgroup, #${DRAWER_ID} .tg-card { background: rgba(17,24,39,.96); } } @media (max-width: 520px) { #${BUTTON_ID} { right: 16px; bottom: 18px; } #${DRAWER_ID} { right: 8px; top: 8px; bottom: 8px; width: calc(100vw - 16px); border-radius: 24px; } #${DRAWER_ID} .tg-summary { grid-template-columns: repeat(2, minmax(0, 1fr)); } #${DRAWER_ID} .tg-meta-grid, #${DRAWER_ID} .tg-task-meta { grid-template-columns: repeat(2, minmax(0, 1fr)); } #${DRAWER_ID} .tg-date-filter-row { flex-wrap: wrap; } } `; if (typeof GM_addStyle === "function") { GM_addStyle(css); } else { const style = document.createElement("style"); style.textContent = css; document.head.appendChild(style); } async function getValue(key, defaultValue) { if (typeof GM !== "undefined" && GM.getValue) { return await GM.getValue(key, defaultValue); } return GM_getValue(key, defaultValue); } async function setValue(key, value) { if (typeof GM !== "undefined" && GM.setValue) { await GM.setValue(key, value); return; } GM_setValue(key, value); } async function deleteValue(key) { if (typeof GM !== "undefined" && GM.deleteValue) { await GM.deleteValue(key); return; } GM_deleteValue(key); } function extractLoginFromText(text) { if (!text) return ""; const patterns = [ /user_\d+/i, /"login"\s*:\s*"([^"]+)"/i, /"username"\s*:\s*"([^"]+)"/i, /zzud=([^&"'\s]+)/i, /username=([^&"'\s]+)/i ]; for (const pattern of patterns) { const match = String(text).match(pattern); if (match) { const value = match[1] || match[0]; if (value && /^user_\d+$/i.test(value)) { return value; } } } return ""; } function detectLoginFromPage() { const manualLogin = String(MANUAL_LOGIN || "").trim(); if (manualLogin) return manualLogin; const candidates = []; candidates.push(location.href); if (document && document.documentElement) { candidates.push(document.documentElement.innerHTML); } try { for (let i = 0; i < localStorage.length; i++) { const key = localStorage.key(i); candidates.push(key); candidates.push(localStorage.getItem(key)); } } catch (e) {} try { for (let i = 0; i < sessionStorage.length; i++) { const key = sessionStorage.key(i); candidates.push(key); candidates.push(sessionStorage.getItem(key)); } } catch (e) {} for (const text of candidates) { const login = extractLoginFromText(text); if (login) return login; } return ""; } function saveAutoLoginIfDetected() { if (location.hostname !== "tg.zcst.edu.cn") { return ""; } const login = detectLoginFromPage(); if (login) { GM_setValue(STORE_KEY_AUTO_LOGIN, login); return login; } return ""; } function escapeHtml(text) { return String(text ?? "") .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """); } function formatDateTime(value) { if (!value) return "--"; try { const normalized = String(value).replace(/-/g, "/"); const d = new Date(normalized); if (Number.isNaN(d.getTime())) return String(value); const y = d.getFullYear(); const m = String(d.getMonth() + 1).padStart(2, "0"); const day = String(d.getDate()).padStart(2, "0"); const h = String(d.getHours()).padStart(2, "0"); const min = String(d.getMinutes()).padStart(2, "0"); return `${y}-${m}-${day} ${h}:${min}`; } catch (e) { return String(value); } } function normalizeTasks(data) { if (Array.isArray(data?.tasks)) return data.tasks; const tasks = []; for (const course of data?.courses || []) { for (const exam of course.exams || []) tasks.push({ ...exam, taskType: "exercise", taskTypeText: "考试/小测试" }); for (const homework of course.homeworks || []) tasks.push({ ...homework, taskType: "common_homework", taskTypeText: "图文作业" }); for (const experiment of course.experiments || []) tasks.push({ ...experiment, taskType: "classroom_experiment", taskTypeText: "课堂实验" }); } return tasks; } function getTaskDate(task) { const candidates = [ task?.endTime, task?.publishTime, task?.scanTime, task?.startAt, task?.endAt ]; for (const v of candidates) { if (!v) continue; const d = new Date(String(v).replace(/-/g, "/")); if (!Number.isNaN(d.getTime())) return d; } return null; } function getTaskEndDate(task) { const candidates = [ task?.endTime, task?.endAt ]; for (const v of candidates) { if (!v) continue; const d = new Date(String(v).replace(/-/g, "/")); if (!Number.isNaN(d.getTime())) return d; } return null; } function parseChineseRemainingToMs(text) { if (!text) return null; const str = String(text); let ms = 0; const day = str.match(/(\d+)\s*天/); const hour = str.match(/(\d+)\s*小时/); const minute = str.match(/(\d+)\s*分/); const second = str.match(/(\d+)\s*秒/); if (day) ms += Number(day[1]) * 24 * 60 * 60 * 1000; if (hour) ms += Number(hour[1]) * 60 * 60 * 1000; if (minute) ms += Number(minute[1]) * 60 * 1000; if (second) ms += Number(second[1]) * 1000; return ms || null; } function parseTaskDeadline(task) { if (task?.endTime) { const d = new Date(String(task.endTime).replace(/-/g, "/")); if (!Number.isNaN(d.getTime())) return d; } if (task?.deadlineAt) { const d = new Date(String(task.deadlineAt).replace(/-/g, "/")); if (!Number.isNaN(d.getTime())) return d; } if (task?.deadlineRemaining && task?.scanTime) { const base = new Date(String(task.scanTime).replace(/-/g, "/")); const ms = parseChineseRemainingToMs(task.deadlineRemaining); if (!Number.isNaN(base.getTime()) && ms != null) { return new Date(base.getTime() + ms); } } return null; } function formatCountdown(deadline) { const diff = deadline.getTime() - Date.now(); if (diff <= 0) return "已截止"; const totalSeconds = Math.floor(diff / 1000); const days = Math.floor(totalSeconds / 86400); const hours = Math.floor((totalSeconds % 86400) / 3600); const minutes = Math.floor((totalSeconds % 3600) / 60); const seconds = totalSeconds % 60; const pad = n => String(n).padStart(2, "0"); if (days > 0) { return `剩余 ${days}天 ${pad(hours)}:${pad(minutes)}:${pad(seconds)}`; } return `剩余 ${pad(hours)}:${pad(minutes)}:${pad(seconds)}`; } function updateCountdowns() { document.querySelectorAll(".tg-helper-countdown[data-deadline]").forEach(el => { const ts = Number(el.dataset.deadline); if (!ts) return; el.textContent = formatCountdown(new Date(ts)); }); } function updateCountdownsOnly() { updateCountdowns(); } function ensureCountdownTimer() { if (countdownIntervalId) return; countdownIntervalId = setInterval(updateCountdownsOnly, 1000); } function stopCountdownTimer() { if (!countdownIntervalId) return; clearInterval(countdownIntervalId); countdownIntervalId = null; } function getFilterStartDate(year, month) { return new Date(year, month - 1, 1, 0, 0, 0); } function filterTasksByYearMonth(tasks, year, month) { const startDate = getFilterStartDate(year, month); return tasks.filter(task => { const d = getTaskDate(task); if (!d) return true; return d >= startDate; }); } function getDefaultFilterYearMonth() { const d = new Date(); d.setDate(1); d.setHours(0, 0, 0, 0); d.setMonth(d.getMonth() - 5); return { year: d.getFullYear(), month: d.getMonth() + 1 }; } async function loadFilterYearMonth() { const saved = await getValue(STORE_KEY_FILTER_YEAR_MONTH, null); const year = Number(saved?.year); const month = Number(saved?.month); if (year && month >= 1 && month <= 12) { return { year, month }; } return getDefaultFilterYearMonth(); } async function loadCourseCollapseState() { const saved = await getValue(STORE_KEY_COURSE_COLLAPSE, {}); return saved && typeof saved === "object" ? saved : {}; } async function loadSectionCollapseState() { const saved = await getValue(STORE_KEY_SECTION_COLLAPSE, {}); return saved && typeof saved === "object" ? saved : {}; } function isDangerTask(task) { if (task?.completed) return false; if (task?.ended) return false; const text = String(task?.statusText || ""); const endDate = getTaskEndDate(task); const now = new Date(); if (endDate) { if (endDate < now) return false; const diffDays = (endDate.getTime() - now.getTime()) / 86400000; if (diffDays <= 10) return true; } return task?.taskType === "exercise" && text.includes("进行中") && !task.ended; } function taskPriority(task) { if (isDangerTask(task)) return 1; if (!task.completed && task.ended) return 2; if (!task.completed) return 3; return 5; } function sortTasks(tasks) { return [...tasks].sort((a, b) => taskPriority(a) - taskPriority(b)); } function calculateSummaryFromTasks(tasks) { const courseCount = new Set(tasks.map(task => task.courseId || task.courseIdentifier || task.courseName).filter(Boolean)).size; const exerciseCount = tasks.filter(task => task.taskType === "exercise").length; const homeworkCount = tasks.filter(task => task.taskType === "common_homework").length; const experimentCount = tasks.filter(task => task.taskType === "classroom_experiment").length; const unfinishedCount = tasks.filter(task => !task.completed).length; const dangerCount = tasks.filter(task => !task.completed && isDangerTask(task)).length; return { courseCount, taskCount: tasks.length, exerciseCount, homeworkCount, experimentCount, unfinishedCount, dangerCount }; } function statusTone(task) { const text = task.statusText || ""; if (task.completed) return { card: "tg-state-ok", text: "tg-ok" }; if (task.ended || text.includes("已截止")) return { card: "tg-state-bad", text: "tg-bad" }; if (isDangerTask(task) || text.includes("快截止") || text.includes("进行中")) return { card: "tg-state-warn", text: "tg-warn" }; return { card: "tg-state-muted", text: "tg-muted" }; } function currentFilter() { const filter = localStorage.getItem(FILTER_KEY) || "all"; if (filter === "homework") return "common_homework"; if (filter === "experiment") return "classroom_experiment"; return filter; } function setCurrentFilter(filter) { localStorage.setItem(FILTER_KEY, filter); } function isOpen() { return localStorage.getItem(OPEN_KEY) === "1"; } function setOpen(value) { localStorage.setItem(OPEN_KEY, value ? "1" : "0"); } function clamp(value, min, max) { return Math.min(Math.max(value, min), max); } function getDefaultPanelState() { return { buttonX: Math.max(18, window.innerWidth - 84), buttonY: Math.max(18, window.innerHeight - 86), panelWidth: 520, panelHeight: Math.min(760, Math.max(480, window.innerHeight - 48)) }; } async function loadPanelState() { const saved = await getValue(STORE_KEY_PANEL_STATE, {}); return { ...getDefaultPanelState(), ...(saved && typeof saved === "object" ? saved : {}) }; } async function savePanelStatePatch(patch) { const current = await loadPanelState(); await setValue(STORE_KEY_PANEL_STATE, { ...current, ...patch }); } async function applyPanelState(panelState) { const button = document.getElementById(BUTTON_ID); const drawer = document.getElementById(DRAWER_ID); const state = panelState || await loadPanelState(); if (button) { const x = clamp(Number(state.buttonX) || 22, -42, window.innerWidth - 20); const y = clamp(Number(state.buttonY) || 24, -42, window.innerHeight - 20); button.style.left = `${x}px`; button.style.top = `${y}px`; button.style.right = "auto"; button.style.bottom = "auto"; } if (drawer) { const maxWidth = Math.min(760, window.innerWidth - 40); const maxHeight = window.innerHeight - 40; const width = clamp(Number(state.panelWidth) || 520, 390, maxWidth); const height = clamp(Number(state.panelHeight) || Math.min(760, window.innerHeight - 48), 480, maxHeight); drawer.style.width = `${width}px`; drawer.style.height = `${height}px`; drawer.style.maxWidth = `${maxWidth}px`; drawer.style.maxHeight = `${maxHeight}px`; drawer.style.right = "18px"; drawer.style.top = "18px"; } } async function loadData() { return await getValue(STORE_KEY, null); } async function loadRunningState() { return await getValue(STORE_KEY_LAST_RUNNING, null); } async function loadLastError() { return await getValue(STORE_KEY_LAST_ERROR, null); } async function loadRefreshRequest() { return await getValue(STORE_KEY_REFRESH_REQUEST, null); } async function loadRefreshHandled() { return await getValue(STORE_KEY_REFRESH_HANDLED, 0); } function normalizeFilterYearMonth(saved) { const year = Number(saved?.year); const month = Number(saved?.month); if (year && month >= 1 && month <= 12) { return { year, month }; } return getDefaultFilterYearMonth(); } function normalizeObject(value, fallback = {}) { return value && typeof value === "object" ? value : fallback; } async function loadFrontendState() { const [ result, lastRunning, lastError, filterYearMonthRaw, panelStateRaw, courseCollapse, sectionCollapse, autoLogin, refreshRequest, refreshHandled ] = await Promise.all([ getValue(STORE_KEY, null), getValue(STORE_KEY_LAST_RUNNING, null), getValue(STORE_KEY_LAST_ERROR, null), getValue(STORE_KEY_FILTER_YEAR_MONTH, null), getValue(STORE_KEY_PANEL_STATE, null), getValue(STORE_KEY_COURSE_COLLAPSE, {}), getValue(STORE_KEY_SECTION_COLLAPSE, {}), getValue(STORE_KEY_AUTO_LOGIN, ""), getValue(STORE_KEY_REFRESH_REQUEST, null), getValue(STORE_KEY_REFRESH_HANDLED, 0) ]); return { result, lastRunning, lastError, filterYearMonth: normalizeFilterYearMonth(filterYearMonthRaw), panelState: { ...getDefaultPanelState(), ...normalizeObject(panelStateRaw) }, courseCollapse: normalizeObject(courseCollapse), sectionCollapse: normalizeObject(sectionCollapse), autoLogin: String(autoLogin || "").trim(), refreshRequest, refreshHandled, currentFilter: currentFilter() }; } function applyCurrentFilter(tasks, filter) { return tasks.filter(task => taskMatchesFilter(task, filter)); } function groupTasksByCourseAndType(tasks) { const groups = new Map(); for (const task of tasks) { const key = getCourseKey(task); if (!groups.has(key)) { groups.set(key, { key, courseName: task.courseName || "未识别课程", tasks: [], exams: [], homeworks: [], experiments: [], unfinished: 0, danger: 0 }); } const group = groups.get(key); group.tasks.push(task); if (!task.completed) group.unfinished += 1; if (isDangerTask(task)) group.danger += 1; if (task.taskType === "exercise") { group.exams.push(task); } else if (task.taskType === "common_homework") { group.homeworks.push(task); } else if (task.taskType === "classroom_experiment") { group.experiments.push(task); } } return Array.from(groups.values()); } function buildViewModel(result, state) { const allTasks = sortTasks(normalizeTasks(result)); const filteredByMonth = filterTasksByYearMonth(allTasks, state.filterYearMonth.year, state.filterYearMonth.month); const stats = calculateSummaryFromTasks(filteredByMonth); const visibleTasks = applyCurrentFilter(filteredByMonth, state.currentFilter); const dangerTasks = visibleTasks.filter(isDangerTask); const regularTasks = visibleTasks.filter(task => !isDangerTask(task)); return { allTasks, filteredTasks: filteredByMonth, visibleTasks, dangerTasks, regularTasks, stats, courseGroups: groupTasksByCourseAndType(regularTasks) }; } function createRoot() { let root = document.getElementById(ROOT_ID); if (root) return root; root = document.createElement("div"); root.id = ROOT_ID; root.innerHTML = ` `; document.body.appendChild(root); const button = root.querySelector(`#${BUTTON_ID}`); root.addEventListener("click", handlePanelClick); root.addEventListener("change", handlePanelChange); root.addEventListener("mousemove", handleLiquidPointerMove, { passive: true }); attachButtonDrag(button); attachDrawerResize(root.querySelector(`#${DRAWER_ID}`)); applyPanelState(); setDrawerOpen(isOpen()); return root; } async function handlePanelClick(event) { const actionEl = event.target.closest("[data-action], [data-course-toggle], [data-section-toggle], [data-jump-id], [data-summary-filter]"); if (!actionEl) return; const action = actionEl.dataset.action || ""; createLiquidRipple(actionEl, event); if (action === "open-panel") { const button = document.getElementById(BUTTON_ID); if (button?.dataset.dragged === "1") { button.dataset.dragged = "0"; return; } setDrawerOpen(true); render(); return; } if (action === "close-panel") { setDrawerOpen(false); return; } if (action === "request-refresh") { await requestBackendRefresh("frontend-header-button"); startRunningPoll(); render(); return; } if (action === "delete-cache") { deleteTaskCache(); return; } if (action === "copy-json") { copyAllJson(); return; } if (action === "set-filter" || actionEl.dataset.summaryFilter) { setCurrentFilter(actionEl.dataset.summaryFilter); render(); return; } if (action === "open-detail" || actionEl.dataset.jumpId) { event.preventDefault(); event.stopPropagation(); const task = jumpTaskRegistry.get(actionEl.dataset.jumpId); if (!task) { alert("跳转失败:任务数据已失效,请刷新面板后重试"); return; } jumpToTask(task); return; } if (action === "toggle-course" || actionEl.hasAttribute("data-course-toggle")) { event.preventDefault(); event.stopPropagation(); const group = actionEl.closest(".tg-course-group"); if (!group) return; const key = group.dataset.courseKey; const collapseState = await loadCourseCollapseState(); collapseState[key] = !group.classList.contains("tg-course-collapsed"); await setValue(STORE_KEY_COURSE_COLLAPSE, collapseState); render(); return; } if (action === "toggle-section" || actionEl.hasAttribute("data-section-toggle")) { event.preventDefault(); event.stopPropagation(); const section = actionEl.closest(".tg-course-subgroup"); if (!section) return; const key = section.dataset.sectionKey; const collapseState = await loadSectionCollapseState(); collapseState[key] = !section.classList.contains("tg-section-collapsed"); await setValue(STORE_KEY_SECTION_COLLAPSE, collapseState); render(); } } async function handlePanelChange(event) { const target = event.target; if (!target?.matches?.("[data-filter-year], [data-filter-month]")) return; const root = document.getElementById(ROOT_ID); const year = Number(root?.querySelector("[data-filter-year]")?.value); const month = Number(root?.querySelector("[data-filter-month]")?.value); if (!year || !month) return; await setValue(STORE_KEY_FILTER_YEAR_MONTH, { year, month }); render(); } function handleLiquidPointerMove(event) { const target = event.target.closest(`#${BUTTON_ID}, #${DRAWER_ID}`); if (!target) return; const rect = target.getBoundingClientRect(); const x = clamp(((event.clientX - rect.left) / Math.max(rect.width, 1)) * 100, 0, 100); const y = clamp(((event.clientY - rect.top) / Math.max(rect.height, 1)) * 100, 0, 100); target.style.setProperty("--tg-mouse-x", `${x}%`); target.style.setProperty("--tg-mouse-y", `${y}%`); } function createLiquidRipple(target, event) { if (!target || !target.matches("button, .tg-detail-link, .tg-summary-item, .tg-course-toggle, .tg-action-btn, .tg-icon-btn")) return; if (target.dataset.action === "open-panel") return; const rect = target.getBoundingClientRect(); const ripple = document.createElement("span"); ripple.className = "tg-liquid-ripple"; ripple.style.left = `${event.clientX - rect.left}px`; ripple.style.top = `${event.clientY - rect.top}px`; target.appendChild(ripple); setTimeout(() => ripple.remove(), 560); } function setDrawerOpen(open) { const drawer = document.getElementById(DRAWER_ID); if (!drawer) return; drawer.classList.toggle("tg-open", open); setOpen(open); if (open) { applyPanelState(); requestBackendRefresh("frontend-panel-open"); startRunningPoll(); } else { stopRunningPoll(); stopCountdownTimer(); } } function attachButtonDrag(button) { if (!button || button.dataset.dragReady === "1") return; button.dataset.dragReady = "1"; let startX = 0; let startY = 0; let originX = 0; let originY = 0; let dragging = false; const onMove = event => { if (!dragging) return; const nextX = clamp(originX + event.clientX - startX, -42, window.innerWidth - 20); const nextY = clamp(originY + event.clientY - startY, -42, window.innerHeight - 20); button.style.left = `${nextX}px`; button.style.top = `${nextY}px`; button.style.right = "auto"; button.style.bottom = "auto"; if (Math.abs(event.clientX - startX) + Math.abs(event.clientY - startY) > 5) { button.dataset.dragged = "1"; } }; const onUp = async () => { if (!dragging) return; dragging = false; document.removeEventListener("mousemove", onMove); document.removeEventListener("mouseup", onUp); await savePanelStatePatch({ buttonX: Math.round(button.getBoundingClientRect().left), buttonY: Math.round(button.getBoundingClientRect().top) }); setTimeout(() => { if (button.dataset.dragged === "1") button.dataset.dragged = "0"; }, 80); }; button.addEventListener("mousedown", event => { if (event.button !== 0 || isOpen()) return; const rect = button.getBoundingClientRect(); startX = event.clientX; startY = event.clientY; originX = rect.left; originY = rect.top; dragging = true; document.addEventListener("mousemove", onMove); document.addEventListener("mouseup", onUp); event.preventDefault(); }); } function attachDrawerResize(drawer) { if (!drawer || resizeObserverAttached || typeof ResizeObserver === "undefined") return; resizeObserverAttached = true; let saveTimer = null; const observer = new ResizeObserver(entries => { const entry = entries[0]; if (!entry || !isOpen()) return; clearTimeout(saveTimer); saveTimer = setTimeout(() => { const rect = entry.target.getBoundingClientRect(); savePanelStatePatch({ panelWidth: Math.round(rect.width), panelHeight: Math.round(rect.height) }); }, 260); }); observer.observe(drawer); } async function requestBackendRefresh(source) { await setValue(STORE_KEY_REFRESH_REQUEST, { requestedAt: Date.now(), requestedAtText: new Date().toLocaleString(), source }); } function startRunningPoll() { if (pollTimerId) return; pollWasRunning = false; pollTimerId = setInterval(async () => { if (!isOpen()) { stopRunningPoll(); return; } const runningState = await loadRunningState(); const isRunning = runningState?.running === true; if (isRunning) { pollWasRunning = true; render(); return; } if (pollWasRunning) { pollWasRunning = false; render(); stopRunningPoll(); return; } render(); stopRunningPoll(); }, 1000); } function stopRunningPoll() { if (!pollTimerId) return; clearInterval(pollTimerId); pollTimerId = null; } async function deleteTaskCache() { if (!confirm("确定要删除 TG任务助手缓存吗?这会清空当前扫描结果、运行状态、错误信息和复制用 JSON。")) { return; } await deleteValue(STORE_KEY); await deleteValue(STORE_KEY_LAST_RUNNING); await deleteValue(STORE_KEY_LAST_ERROR); cacheDeletedNotice = "缓存已删除"; render(); } function updateFloatingBadge(summary) { const badge = document.querySelector(`#${BUTTON_ID} .tg-task-button-badge`); if (!badge) return; const count = summary?.dangerCount || summary?.unfinishedCount || 0; badge.textContent = String(count > 99 ? "99+" : count); badge.style.display = count > 0 ? "block" : "none"; } function getFilterTip(filter) { const tips = { all: "显示当前年月范围内的全部任务。", unfinished: "显示尚未完成或尚未提交的任务。", danger: "显示需要重点关注的任务,包括进行中未提交,或临近截止的未完成任务。", exercise: "显示考试、小测试、在线作业类任务。", common_homework: "显示普通图文提交类作业。", classroom_experiment: "显示实训 / 课堂实验任务。" }; return tips[filter] || ""; } function getFilterLabel(filter) { const labels = { all: "全部任务", unfinished: "未完成", danger: "危险任务", exercise: "考试", common_homework: "作业", classroom_experiment: "实验" }; return labels[filter] || "全部任务"; } function renderSummaryCard(filter, numberHtml, label, selectedFilter) { const selected = selectedFilter === filter; return ` `; } function renderYearMonthFilter(year, month) { const yearOptions = [2024, 2025, 2026, 2027] .map(y => ``) .join(""); const monthOptions = Array.from({ length: 12 }, (_, i) => i + 1) .map(m => ``) .join(""); return `
@storageName tg-exam-monitor-shared。