diff --git a/index.html b/index.html index 82dc1c2..6ad3399 100644 --- a/index.html +++ b/index.html @@ -178,12 +178,14 @@ white-space: nowrap; } .badge-pass { background: var(--success-light); color: #065F46; } + .badge-comp { background: var(--danger-light); color: #991B1B; } .badge-fail { background: var(--danger-light); color: #991B1B; } .badge-prog { background: var(--warning-light); color: #92400E; } .badge-info { background: var(--primary-light); color: #3730A3; } .badge-cycle { background: var(--gray-100); color: var(--gray-600); font-size: 10px; } :root.dark .badge-pass { background: #022c22; color: #6ee7b7; } + :root.dark .badge-comp { background: #450a0a; color: #fca5a5; } :root.dark .badge-fail { background: #450a0a; color: #fca5a5; } :root.dark .badge-prog { background: #451a03; color: #fcd34d; } :root.dark .badge-info { background: #1e1b4b; color: #a5b4fc; } @@ -371,6 +373,14 @@ } .switch input:checked ~ .switch-thumb { transform: translateX(18px); } + /* ── Pass/Fail segmented control ── */ + .pf-seg { display:flex; border:1.5px solid var(--gray-200); border-radius:10px; overflow:hidden; margin-bottom:14px; } + .pf-seg-btn { flex:1; padding:9px 4px; font-size:13px; font-weight:500; background:var(--bg-surface); border:none; cursor:pointer; color:var(--gray-500); transition:background .15s,color .15s; } + .pf-seg-btn:not(:last-child) { border-right:1.5px solid var(--gray-200); } + .pf-seg-btn.seg-default { background:var(--gray-100); color:var(--gray-800); } + .pf-seg-btn.seg-passed { background:var(--success-light); color:#065F46; } + .pf-seg-btn.seg-failed { background:var(--danger-light); color:var(--danger); } + /* ── File input ── */ .file-input-label { display: flex; align-items: center; justify-content: center; gap: 8px; @@ -411,7 +421,7 @@ .wk-head { display: flex; border-bottom: 1px solid var(--gray-200); background: var(--bg-surface); position: sticky; top: 0; z-index: 5; } .wk-htime { width: 40px; flex-shrink: 0; } .wk-hday { flex: 1; text-align: center; font-size: 11px; font-weight: 700; color: var(--gray-500); padding: 7px 2px; text-transform: uppercase; letter-spacing: 0.04em; } - .wk-scroll { overflow-y: auto; max-height: 60vh; } + .wk-scroll { overflow-y: auto; flex: 1; min-height: 0; } .wk-body { display: flex; } .wk-tcol { width: 40px; flex-shrink: 0; position: relative; } .wk-tlbl { position: absolute; font-size: 10px; color: var(--gray-400); right: 5px; transform: translateY(-50%); white-space: nowrap; } @@ -485,6 +495,10 @@ let S = { studyPlans: [], blocks: [], subBlocks: [], semesters: [], courses: [], let settings = { minCreditsBachelor: 180, minCreditsMaster: 120, allSeasons: false, showOptional: false }; let page = 'dashboard'; let dashView = 'plans'; +let collapsed = {}; +let editModeBlock = null; +let _syncConflicts = []; +let _syncMergedBase = null; function uid() { return Date.now().toString(36) + Math.random().toString(36).slice(2, 7); } @@ -548,18 +562,22 @@ function blockMean(blockId) { } function status(course) { - if (course.passFail) return 'pass'; + if (course.passFail === 'passed' || course.passFail === true) return 'pass'; + if (course.passFail === 'failed') return 'fail'; if (course.grade == null || course.grade === '') return 'prog'; if (+course.grade >= 4) return 'pass'; if (course.blockId) { const m = blockMean(course.blockId); - if (m !== null && m >= 4) return 'pass'; + if (m !== null && m >= 4) return 'comp'; // compensation pass } return 'fail'; } +function isPass(st) { return st === 'pass' || st === 'comp'; } + function badge(st) { if (st === 'pass') return 'Passed'; + if (st === 'comp') return 'Passed'; if (st === 'fail') return 'Failed'; return 'In progress'; } @@ -570,7 +588,8 @@ function gradeClass(g) { } function fmtGrade(g, passFail) { - if (passFail) return 'P/F'; + if (passFail === 'passed' || passFail === true) return 'Pass'; + if (passFail === 'failed') return 'Fail'; if (g == null || g === '') return '—'; const n = +g; return Number.isInteger(n) ? String(n) : n.toFixed(2).replace(/0+$/, '').replace(/\.$/, ''); @@ -617,8 +636,8 @@ function render(p) { function updateHeaderSub() { const total = S.courses.length; - const passed = S.courses.filter(c => status(c) === 'pass').length; - const credits = S.courses.filter(c => status(c) === 'pass').reduce((s, c) => s + (+c.credits || 0), 0); + const passed = S.courses.filter(c => isPass(status(c))).length; + const credits = S.courses.filter(c => isPass(status(c))).reduce((s, c) => s + (+c.credits || 0), 0); document.getElementById('header-sub').textContent = total ? `${credits} credits earned · ${passed}/${total} courses passed` : 'No courses yet'; } @@ -747,13 +766,13 @@ function renderDash() { function renderDashStats() { const all = S.courses; const graded = all.filter(c => !c.passFail && c.grade != null && c.grade !== ''); - const passed = all.filter(c => status(c) === 'pass'); + const passed = all.filter(c => isPass(status(c))); const totCr = all.reduce((s, c) => s + (+c.credits || 0), 0); const passCr = passed.reduce((s, c) => s + (+c.credits || 0), 0); const sumW = graded.reduce((s, c) => s + +c.grade * (+c.credits || 0), 0); const sumC = graded.reduce((s, c) => s + (+c.credits || 0), 0); const mean = sumC > 0 ? sumW / sumC : null; - const passedGraded = graded.filter(c => status(c) === 'pass').length; + const passedGraded = graded.filter(c => isPass(status(c))).length; const rate = graded.length ? Math.round(passedGraded / graded.length * 100) : null; document.getElementById('dash-stats').innerHTML = `