<!DOCTYPE html>
<html lang="en" data-theme="dark">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>StudyBuddy</title>
<link rel="preconnect" href="https://fonts.googleapis.com"/>
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin/>
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,wght@0,300;0,400;0,500;0,600;1,300&family=DM+Mono:wght@400;500&family=Playfair+Display:wght@400;500&display=swap" rel="stylesheet"/>
<style>
/* ══ THEME ══ */
:root {
--bg:#0f1117; --bg2:#161820; --bg3:#1e2028; --bg4:#252730;
--border:rgba(255,255,255,0.07); --border2:rgba(255,255,255,0.13);
--text:#e8eaf0; --text2:#9095a8; --text3:#5a5f72;
--shadow:0 2px 16px rgba(0,0,0,0.4);
--accent:#6c8eff; --accent2:#4a6adb; --accent-bg:rgba(108,142,255,0.1);
--green:#4ade80; --green-bg:rgba(74,222,128,0.1);
--amber:#fbbf24; --amber-bg:rgba(251,191,36,0.1);
--red:#f87171; --red-bg:rgba(248,113,113,0.1);
--purple:#c084fc; --purple-bg:rgba(192,132,252,0.1);
--teal:#2dd4bf; --teal-bg:rgba(45,212,191,0.1);
--radius:12px; --radius-sm:8px;
--sidebar:228px; --font:'DM Sans',sans-serif;
--mono:'DM Mono',monospace; --display:'Playfair Display',serif;
--tr:0.18s ease;
}
[data-theme="light"] {
--bg:#eef0f5; --bg2:#ffffff; --bg3:#e8eaf0; --bg4:#d8dae6;
--border:rgba(0,0,0,0.10); --border2:rgba(0,0,0,0.18);
--text:#14172b; --text2:#404560; --text3:#7078a0;
--shadow:0 2px 16px rgba(0,0,0,0.10);
--accent-bg:rgba(108,142,255,0.10);
--green-bg:rgba(22,160,60,0.10); --amber-bg:rgba(180,130,0,0.10);
--red-bg:rgba(200,50,50,0.10); --purple-bg:rgba(140,60,200,0.10);
--teal-bg:rgba(0,140,130,0.10);
}
[data-theme="light"] .stat-card,
[data-theme="light"] .card,
[data-theme="light"] .course-card,
[data-theme="light"] .course-full-card,
[data-theme="light"] .insight-card,
[data-theme="light"] .resource-card,
[data-theme="light"] .flag-card { box-shadow: 0 1px 4px rgba(0,0,0,0.07); }
[data-theme="light"] .asgn-table tr:hover td { background: var(--bg3); }
[data-theme="light"] .flag-card.alert { background:rgba(200,50,50,0.07); border-color:rgba(200,50,50,0.2); }
[data-theme="light"] .flag-card.warn { background:rgba(180,130,0,0.07); border-color:rgba(180,130,0,0.2); }
[data-theme="light"] .type-badge,
[data-theme="light"] .status-chip,
[data-theme="light"] .severity-badge,
[data-theme="light"] .resource-status { filter: saturate(1.3) brightness(0.75); }
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0;}
body{font-family:var(--font);background:var(--bg);color:var(--text);min-height:100vh;display:flex;overflow:hidden;font-size:14px;line-height:1.5;transition:background var(--tr),color var(--tr);}
::-webkit-scrollbar{width:4px;} ::-webkit-scrollbar-track{background:transparent;} ::-webkit-scrollbar-thumb{background:var(--bg4);border-radius:4px;}
/* ══ SIDEBAR ══ */
.sidebar{width:var(--sidebar);min-height:100vh;background:var(--bg2);border-right:1px solid var(--border);display:flex;flex-direction:column;flex-shrink:0;transition:background var(--tr),border-color var(--tr);}
.sidebar-logo{padding:20px 16px 16px;border-bottom:1px solid var(--border);margin-bottom:6px;display:flex;align-items:center;gap:9px;}
.logo-icon{width:30px;height:30px;flex-shrink:0;}
.logo-text .wordmark{font-family:var(--display);font-size:16px;font-weight:500;letter-spacing:-0.02em;line-height:1;}
.logo-text .tagline{font-size:9px;color:var(--text3);letter-spacing:0.06em;text-transform:uppercase;margin-top:1px;}
.nav-section{padding:8px 12px 3px;font-size:9px;font-weight:700;letter-spacing:0.12em;text-transform:uppercase;color:var(--text3);}
.nav-item{display:flex;align-items:center;gap:9px;padding:8px 13px;margin:1px 7px;border-radius:var(--radius-sm);cursor:pointer;color:var(--text2);font-size:12.5px;font-weight:400;transition:background 0.15s,color 0.15s;}
.nav-item:hover{background:var(--bg3);color:var(--text);}
.nav-item.active{background:var(--accent-bg);color:var(--accent);font-weight:500;}
.nav-item .icon{width:14px;height:14px;flex-shrink:0;opacity:0.7;}
.nav-item.active .icon{opacity:1;}
.nav-badge{margin-left:auto;background:var(--red);color:#fff;font-size:9px;font-weight:700;padding:1px 5px;border-radius:20px;min-width:16px;text-align:center;}
.sidebar-bottom{margin-top:auto;padding:10px 7px 14px;border-top:1px solid var(--border);}
.user-pill{display:flex;align-items:center;gap:9px;padding:9px 10px;border-radius:var(--radius-sm);cursor:pointer;transition:background 0.15s;}
.user-pill:hover{background:var(--bg3);}
.avatar{width:28px;height:28px;border-radius:50%;background:linear-gradient(135deg,var(--accent),var(--accent2));display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:600;color:#fff;flex-shrink:0;}
.user-info .name{font-size:12px;font-weight:500;}
.user-info .plan{font-size:10px;color:var(--text3);}
.user-pill-right{margin-left:auto;font-size:10px;color:var(--text3);}
/* ══ MAIN ══ */
.main{flex:1;overflow-y:auto;height:100vh;}
.page{display:none;}
.page.active{display:block;animation:pageIn 0.2s ease;}
@keyframes pageIn{from{opacity:0;transform:translateY(5px);}to{opacity:1;transform:translateY(0);}}
.page-header{padding:24px 28px 0;display:flex;align-items:flex-start;justify-content:space-between;gap:12px;flex-wrap:wrap;}
.page-title{font-family:var(--display);font-size:21px;font-weight:400;letter-spacing:-0.02em;}
.page-sub{font-size:12px;color:var(--text2);margin-top:2px;}
.page-body{padding:18px 28px 40px;}
/* ══ BUTTONS ══ */
.btn{display:inline-flex;align-items:center;gap:5px;padding:6px 13px;border-radius:var(--radius-sm);font-family:var(--font);font-size:12px;font-weight:500;cursor:pointer;border:none;transition:all 0.15s;white-space:nowrap;}
.btn-primary{background:var(--accent);color:#fff;} .btn-primary:hover{background:var(--accent2);transform:translateY(-1px);}
.btn-ghost{background:transparent;color:var(--text2);border:1px solid var(--border2);} .btn-ghost:hover{background:var(--bg3);color:var(--text);}
.btn-danger{background:var(--red-bg);color:var(--red);border:1px solid rgba(248,113,113,0.25);} .btn-danger:hover{background:rgba(248,113,113,0.2);}
.btn-success{background:var(--green-bg);color:var(--green);border:1px solid rgba(74,222,128,0.25);}
.btn-sm{padding:4px 10px;font-size:11px;}
.btn-icon{width:25px;height:25px;padding:0;justify-content:center;background:var(--bg3);color:var(--text2);border:1px solid var(--border);border-radius:6px;font-size:11px;}
.btn-icon:hover{background:var(--bg4);color:var(--text);}
/* ══ LAYOUT ══ */
.card{background:var(--bg2);border:1px solid var(--border);border-radius:var(--radius);padding:16px;transition:background var(--tr);}
.card-sm{padding:11px 13px;}
.grid-2{display:grid;grid-template-columns:1fr 1fr;gap:13px;}
.grid-3{display:grid;grid-template-columns:repeat(3,1fr);gap:13px;}
.grid-4{display:grid;grid-template-columns:repeat(4,1fr);gap:13px;}
.divider{height:1px;background:var(--border);margin:14px 0;}
.section-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:10px;}
.section-title{font-size:10px;font-weight:700;letter-spacing:0.1em;text-transform:uppercase;color:var(--text3);}
.empty-state{text-align:center;padding:32px 16px;color:var(--text3);}
.empty-state .ei{font-size:26px;margin-bottom:7px;}
.empty-state p{font-size:12px;}
/* ══ STAT CARDS ══ */
.stat-card{background:var(--bg2);border:1px solid var(--border);border-radius:var(--radius);padding:14px 16px;transition:border-color 0.15s,transform 0.15s,background var(--tr);}
.stat-card.clickable{cursor:pointer;} .stat-card.clickable:hover{border-color:var(--border2);transform:translateY(-2px);}
.stat-label{font-size:9px;font-weight:700;letter-spacing:0.09em;text-transform:uppercase;color:var(--text3);margin-bottom:5px;}
.stat-value{font-size:24px;font-weight:300;font-family:var(--display);letter-spacing:-0.03em;line-height:1;}
.stat-change{font-size:10px;color:var(--text3);margin-top:3px;}
.stat-change.up{color:var(--green);} .stat-change.down{color:var(--red);}
.stat-hint{font-size:9px;color:var(--accent);margin-top:4px;}
/* ══ COURSE CARDS ══ */
.course-card{background:var(--bg2);border:1px solid var(--border);border-radius:var(--radius);padding:13px 15px;transition:border-color 0.2s,transform 0.2s,background var(--tr);cursor:pointer;}
.course-card:hover{border-color:var(--border2);transform:translateY(-2px);}
.course-name{font-weight:500;font-size:13px;}
.course-code{font-size:9px;color:var(--text3);margin-top:1px;font-family:var(--mono);}
.course-grade{font-family:var(--display);font-size:19px;font-weight:400;letter-spacing:-0.03em;}
.progress-bar-wrap{height:3px;background:var(--bg4);border-radius:4px;overflow:hidden;margin:8px 0 6px;}
.progress-bar{height:100%;border-radius:4px;transition:width 0.5s ease;}
.course-meta{display:flex;justify-content:space-between;font-size:9px;color:var(--text3);}
.course-full-card{background:var(--bg2);border:1px solid var(--border);border-radius:var(--radius);overflow:hidden;transition:border-color 0.2s,background var(--tr);}
.course-full-card:hover{border-color:var(--border2);}
.course-full-header{padding:13px 15px 11px;border-bottom:1px solid var(--border);display:flex;align-items:center;gap:9px;}
.course-full-color{width:32px;height:32px;border-radius:var(--radius-sm);display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:700;flex-shrink:0;}
.course-full-body{padding:11px 15px;}
.course-stat-row{display:flex;gap:14px;margin-bottom:10px;}
.course-stat-label{font-size:9px;color:var(--text3);text-transform:uppercase;letter-spacing:0.07em;margin-bottom:2px;}
.course-stat-val{font-size:15px;font-weight:300;font-family:var(--display);}
.grade-bar-wrap{height:4px;background:var(--bg4);border-radius:6px;overflow:hidden;margin:7px 0 5px;}
/* ══ ASSIGNMENT TABLE ══ */
.asgn-table{width:100%;border-collapse:collapse;}
.asgn-table th{text-align:left;padding:8px 11px;font-size:9px;font-weight:700;letter-spacing:0.09em;text-transform:uppercase;color:var(--text3);border-bottom:1px solid var(--border);}
.asgn-table td{padding:9px 11px;border-bottom:1px solid var(--border);font-size:12px;vertical-align:middle;}
.asgn-table tr:last-child td{border-bottom:none;}
.asgn-table tr:hover td{background:var(--bg3);}
.type-badge{display:inline-block;padding:2px 6px;border-radius:20px;font-size:8px;font-weight:700;letter-spacing:0.05em;text-transform:uppercase;}
.status-chip{display:inline-flex;align-items:center;gap:3px;padding:2px 6px;border-radius:20px;font-size:9px;font-weight:600;}
.row-actions{display:flex;gap:3px;opacity:0;transition:opacity 0.15s;}
.asgn-table tr:hover .row-actions{opacity:1;}
/* ══ FLAG CARDS ══ */
.flag-card{display:flex;gap:9px;padding:9px 11px;border-radius:var(--radius-sm);margin-bottom:6px;border:1px solid transparent;}
.flag-card.alert{background:var(--red-bg);border-color:rgba(248,113,113,0.2);}
.flag-card.warn{background:var(--amber-bg);border-color:rgba(251,191,36,0.2);}
.flag-card.info{background:var(--accent-bg);border-color:rgba(108,142,255,0.2);}
.flag-icon{font-size:12px;flex-shrink:0;margin-top:1px;}
.flag-text{font-size:11.5px;line-height:1.45;flex:1;color:var(--text);}
.flag-dismiss{background:none;border:none;color:var(--text3);cursor:pointer;font-size:14px;flex-shrink:0;padding:0 2px;line-height:1;transition:color 0.15s;}
.flag-dismiss:hover{color:var(--text);}
/* ══ TIMER ══ */
.timer-wrap{display:flex;flex-direction:column;align-items:center;padding:10px 14px 0;}
.timer-ring{position:relative;width:190px;height:190px;margin-bottom:18px;}
.timer-ring svg{transform:rotate(-90deg);}
.ring-bg{fill:none;stroke:var(--bg4);stroke-width:8;}
.ring-fg{fill:none;stroke:var(--accent);stroke-width:8;stroke-linecap:round;transition:stroke-dashoffset 1s linear;}
.timer-display{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;}
.timer-time{font-family:var(--mono);font-size:34px;font-weight:400;letter-spacing:-0.02em;line-height:1;}
.timer-label{font-size:10px;color:var(--text3);margin-top:2px;}
.timer-controls{display:flex;gap:9px;margin-bottom:13px;}
.timer-btn{width:42px;height:42px;border-radius:50%;border:none;cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:15px;transition:transform 0.15s;}
.timer-btn:active{transform:scale(0.93);}
.timer-btn-primary{background:var(--accent);color:#fff;}
.timer-btn-ghost{background:var(--bg3);color:var(--text2);}
.timer-presets{display:flex;gap:5px;flex-wrap:wrap;justify-content:center;margin-bottom:7px;}
.custom-row{display:flex;align-items:center;gap:5px;margin-bottom:5px;}
.session-row{display:flex;align-items:center;gap:9px;padding:6px 0;border-bottom:1px solid var(--border);font-size:11px;}
.session-row:last-child{border-bottom:none;}
.session-dot{width:5px;height:5px;border-radius:50%;flex-shrink:0;}
/* ══ CHAT ══ */
.chat-layout{display:flex;height:100vh;flex-direction:column;}
.chat-header{padding:14px 24px 12px;border-bottom:1px solid var(--border);display:flex;align-items:center;gap:9px;flex-shrink:0;}
.chat-avatar-wrap{position:relative;}
.chat-avatar{width:36px;height:36px;border-radius:12px;background:linear-gradient(135deg,#6c8eff,#c084fc);display:flex;align-items:center;justify-content:center;font-size:18px;}
.chat-online-dot{position:absolute;bottom:-1px;right:-1px;width:9px;height:9px;background:var(--green);border-radius:50%;border:2px solid var(--bg2);}
.chat-info .name{font-weight:600;font-size:13px;}
.chat-info .status{font-size:10px;color:var(--green);}
.chat-messages{flex:1;overflow-y:auto;padding:14px 24px;display:flex;flex-direction:column;gap:11px;}
.msg{display:flex;gap:7px;max-width:80%;animation:msgIn 0.17s ease;}
@keyframes msgIn{from{opacity:0;transform:translateY(4px);}to{opacity:1;transform:translateY(0);}}
.msg.user{align-self:flex-end;flex-direction:row-reverse;}
.msg-avatar{width:24px;height:24px;border-radius:8px;flex-shrink:0;display:flex;align-items:center;justify-content:center;font-size:10px;font-weight:600;margin-top:2px;}
.msg-avatar.ai{background:linear-gradient(135deg,#6c8eff,#c084fc);color:#fff;font-size:12px;}
.msg-avatar.user-av{background:var(--accent2);color:#fff;}
.msg-bubble{padding:8px 12px;border-radius:12px;font-size:12.5px;line-height:1.6;}
.msg.ai .msg-bubble{background:var(--bg3);border:1px solid var(--border);border-top-left-radius:3px;}
.msg.user .msg-bubble{background:var(--accent2);color:#fff;border-top-right-radius:3px;}
.msg-bubble p{margin-bottom:4px;} .msg-bubble p:last-child{margin-bottom:0;}
.msg-bubble ul{padding-left:13px;} .msg-bubble li{margin-bottom:2px;}
.msg-bubble strong{font-weight:600;}
.msg-time{font-size:9px;color:var(--text3);margin-top:2px;text-align:right;}
.msg.ai .msg-time{text-align:left;}
.quick-prompts{padding:0 24px 7px;display:flex;gap:5px;flex-wrap:wrap;flex-shrink:0;}
.quick-chip{padding:4px 9px;background:var(--bg3);border:1px solid var(--border2);border-radius:20px;font-size:11px;color:var(--text2);cursor:pointer;transition:all 0.15s;white-space:nowrap;}
.quick-chip:hover{background:var(--bg4);color:var(--accent);border-color:rgba(108,142,255,0.3);}
.chat-input-wrap{padding:8px 24px 15px;border-top:1px solid var(--border);display:flex;gap:7px;align-items:flex-end;flex-shrink:0;}
.chat-input{flex:1;background:var(--bg3);border:1px solid var(--border2);border-radius:var(--radius);padding:9px 13px;color:var(--text);font-family:var(--font);font-size:12.5px;resize:none;outline:none;min-height:40px;max-height:100px;transition:border-color 0.2s,background var(--tr);}
.chat-input:focus{border-color:rgba(108,142,255,0.4);}
.chat-input::placeholder{color:var(--text3);}
.send-btn{width:36px;height:36px;border-radius:var(--radius-sm);background:var(--accent);border:none;color:#fff;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:background 0.15s,transform 0.1s;flex-shrink:0;}
.send-btn:hover{background:var(--accent2);} .send-btn:active{transform:scale(0.93);}
.typing-indicator{display:flex;gap:3px;padding:9px 11px;background:var(--bg3);border:1px solid var(--border);border-radius:12px;border-top-left-radius:3px;width:fit-content;}
.typing-dot{width:5px;height:5px;background:var(--text3);border-radius:50%;animation:tb 1.2s ease-in-out infinite;}
.typing-dot:nth-child(2){animation-delay:0.2s;} .typing-dot:nth-child(3){animation-delay:0.4s;}
@keyframes tb{0%,80%,100%{transform:translateY(0);opacity:.4;}40%{transform:translateY(-5px);opacity:1;}}
/* ══ INSIGHTS ══ */
.insight-card{background:var(--bg2);border:1px solid var(--border);border-radius:var(--radius);padding:13px 15px;margin-bottom:8px;display:flex;gap:11px;transition:border-color 0.2s,background var(--tr);}
.insight-card:hover{border-color:var(--border2);}
.insight-icon-wrap{width:34px;height:34px;border-radius:var(--radius-sm);display:flex;align-items:center;justify-content:center;font-size:15px;flex-shrink:0;}
.insight-title{font-weight:600;font-size:13px;margin-bottom:2px;}
.insight-desc{font-size:12px;color:var(--text2);line-height:1.5;}
.insight-meta{display:flex;align-items:center;gap:7px;margin-top:8px;font-size:10px;flex-wrap:wrap;}
.severity-badge{padding:2px 6px;border-radius:20px;font-size:8px;font-weight:700;letter-spacing:0.06em;text-transform:uppercase;}
.insight-dismiss{background:none;border:1px solid var(--border2);color:var(--text3);cursor:pointer;font-size:10px;padding:2px 8px;border-radius:20px;font-family:var(--font);transition:all 0.15s;}
.insight-dismiss:hover{background:var(--bg3);color:var(--text);}
/* ══ TASKS ══ */
.task-row{display:flex;align-items:center;gap:9px;padding:9px 13px;border-bottom:1px solid var(--border);transition:background 0.15s;}
.task-row:last-child{border-bottom:none;}
.task-row:hover{background:var(--bg3);}
.task-row.completed-row{opacity:0.6;}
.task-check{width:15px;height:15px;border-radius:50%;border:2px solid var(--border2);cursor:pointer;flex-shrink:0;display:flex;align-items:center;justify-content:center;transition:all 0.15s;}
.task-check.done{background:var(--green);border-color:var(--green);}
.task-check.done::after{content:'✓';font-size:8px;color:#fff;}
.task-info{flex:1;min-width:0;}
.task-title-txt{font-size:12.5px;font-weight:500;}
.task-done-txt{text-decoration:line-through;color:var(--text3);}
.task-sub{font-size:10px;color:var(--text3);margin-top:1px;}
.priority-dot{width:6px;height:6px;border-radius:50%;flex-shrink:0;}
.task-duration{font-size:9px;color:var(--teal);font-family:var(--mono);white-space:nowrap;}
.completed-section-header{padding:10px 13px 6px;background:var(--bg3);font-size:9px;font-weight:700;letter-spacing:0.1em;text-transform:uppercase;color:var(--text3);display:flex;justify-content:space-between;align-items:center;}
/* ══ RESOURCES ══ */
.resource-card{background:var(--bg2);border:1px solid var(--border);border-radius:var(--radius);padding:13px 15px;margin-bottom:8px;display:flex;align-items:flex-start;gap:11px;transition:border-color 0.2s,background var(--tr);}
.resource-card:hover{border-color:var(--border2);}
.resource-icon{width:34px;height:34px;border-radius:var(--radius-sm);display:flex;align-items:center;justify-content:center;font-size:15px;flex-shrink:0;}
.resource-body{flex:1;min-width:0;}
.resource-title{font-weight:600;font-size:13px;margin-bottom:2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
.resource-meta{font-size:11px;color:var(--text2);}
.resource-link{color:var(--accent);text-decoration:none;font-size:11px;}
.resource-link:hover{text-decoration:underline;}
.resource-status{padding:2px 7px;border-radius:20px;font-size:9px;font-weight:700;text-transform:uppercase;letter-spacing:0.05em;}
.obtained-divider{margin:18px 0 12px;padding:8px 0 6px;border-top:1px solid var(--border);display:flex;align-items:center;gap:8px;}
.obtained-divider-label{font-size:9px;font-weight:700;letter-spacing:0.1em;text-transform:uppercase;color:var(--green);white-space:nowrap;}
.obtained-divider-line{flex:1;height:1px;background:rgba(74,222,128,0.2);}
/* ══ PROGRAMS ══ */
.program-card{background:var(--bg2);border:1px solid var(--border);border-radius:var(--radius);overflow:hidden;transition:border-color 0.2s,background var(--tr);}
.program-card:hover{border-color:var(--border2);}
.program-header{padding:14px 16px 12px;display:flex;align-items:center;gap:10px;border-bottom:1px solid var(--border);}
.program-badge{width:38px;height:38px;border-radius:var(--radius-sm);display:flex;align-items:center;justify-content:center;font-size:16px;font-weight:700;flex-shrink:0;}
.program-body{padding:12px 16px;}
.program-stat-row{display:flex;gap:16px;margin-bottom:10px;}
.program-stat{flex:1;}
/* ══ CONTACTS PAGE ══ */
.contact-card{display:flex;align-items:center;gap:11px;padding:10px 13px;border-bottom:1px solid var(--border);}
.contact-card:hover{background:var(--bg3);}
.contact-card:last-child{border-bottom:none;}
.contact-avatar{width:32px;height:32px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:600;flex-shrink:0;}
.contact-info{flex:1;}
.contact-name{font-size:12.5px;font-weight:500;}
.contact-meta{font-size:10px;color:var(--text3);}
.contact-role-badge{padding:2px 7px;border-radius:20px;font-size:9px;font-weight:600;text-transform:uppercase;letter-spacing:0.05em;}
/* ══ SETTINGS PANEL ══ */
.settings-overlay{position:fixed;inset:0;z-index:200;display:none;}
.settings-overlay.open{display:flex;}
.settings-backdrop{position:absolute;inset:0;background:rgba(0,0,0,0.5);}
.settings-panel{position:absolute;right:0;top:0;bottom:0;width:320px;background:var(--bg2);border-left:1px solid var(--border2);display:flex;flex-direction:column;animation:slideIn 0.2s ease;}
@keyframes slideIn{from{transform:translateX(100%);}to{transform:translateX(0);}}
.settings-header{padding:18px 20px 14px;border-bottom:1px solid var(--border);display:flex;align-items:center;justify-content:space-between;}
.settings-title{font-family:var(--display);font-size:17px;font-weight:500;}
.settings-body{flex:1;overflow-y:auto;padding:16px 20px 24px;}
.settings-section{margin-bottom:20px;}
.settings-section-title{font-size:9px;font-weight:700;letter-spacing:0.12em;text-transform:uppercase;color:var(--text3);margin-bottom:10px;}
.settings-row{display:flex;align-items:center;justify-content:space-between;padding:8px 0;border-bottom:1px solid var(--border);}
.settings-row:last-child{border-bottom:none;}
.settings-row-label{font-size:12.5px;font-weight:500;}
.settings-row-sub{font-size:10px;color:var(--text3);margin-top:1px;}
.toggle-switch{position:relative;width:36px;height:20px;cursor:pointer;}
.toggle-switch input{opacity:0;width:0;height:0;}
.toggle-track{position:absolute;inset:0;background:var(--bg4);border-radius:20px;transition:background 0.2s;}
.toggle-track::after{content:'';position:absolute;width:14px;height:14px;left:3px;top:3px;background:#fff;border-radius:50%;transition:transform 0.2s;}
.toggle-switch input:checked ~ .toggle-track{background:var(--accent);}
.toggle-switch input:checked ~ .toggle-track::after{transform:translateX(16px);}
.widget-toggle{display:flex;align-items:center;gap:8px;padding:6px 10px;border-radius:var(--radius-sm);border:1px solid var(--border2);cursor:pointer;transition:all 0.15s;margin-bottom:5px;}
.widget-toggle.on{background:var(--accent-bg);border-color:rgba(108,142,255,0.3);color:var(--accent);}
.widget-toggle.off{color:var(--text3);}
.widget-toggle-label{font-size:12px;font-weight:500;}
/* ══ MODAL ══ */
.modal-overlay{position:fixed;inset:0;background:rgba(0,0,0,0.65);backdrop-filter:blur(4px);display:none;align-items:center;justify-content:center;z-index:150;}
.modal-overlay.open{display:flex;}
.modal{background:var(--bg2);border:1px solid var(--border2);border-radius:16px;padding:22px;width:460px;max-width:95vw;max-height:88vh;overflow-y:auto;animation:modalIn 0.2s cubic-bezier(.34,1.56,.64,1);}
.modal-sm{width:340px;}
.modal-lg{width:560px;}
@keyframes modalIn{from{opacity:0;transform:scale(0.94) translateY(8px);}to{opacity:1;transform:scale(1) translateY(0);}}
.modal-title{font-family:var(--display);font-size:16px;font-weight:500;margin-bottom:14px;}
.form-group{margin-bottom:11px;}
.form-label{font-size:11px;font-weight:600;color:var(--text2);margin-bottom:4px;display:block;letter-spacing:0.04em;}
.form-input,.form-select{width:100%;background:var(--bg3);border:1px solid var(--border2);color:var(--text);padding:7px 10px;border-radius:var(--radius-sm);font-family:var(--font);font-size:12px;outline:none;transition:border-color 0.2s,background var(--tr);}
.form-input:focus,.form-select:focus{border-color:rgba(108,142,255,0.5);}
.form-input::placeholder{color:var(--text3);}
.form-row{display:grid;grid-template-columns:1fr 1fr;gap:8px;}
.form-row-3{display:grid;grid-template-columns:1fr 1fr 1fr;gap:8px;}
.modal-actions{display:flex;gap:7px;justify-content:flex-end;margin-top:14px;flex-wrap:wrap;}
.modal-actions-split{display:flex;gap:7px;justify-content:space-between;margin-top:14px;flex-wrap:wrap;align-items:center;}
.color-picker{display:flex;gap:6px;flex-wrap:wrap;}
.color-swatch{width:19px;height:19px;border-radius:50%;cursor:pointer;border:2px solid transparent;transition:transform 0.15s;}
.color-swatch:hover{transform:scale(1.2);}
.color-swatch.selected{border-color:#fff;box-shadow:0 0 0 2px rgba(255,255,255,0.25);}
.confirm-modal{background:var(--bg2);border:1px solid var(--border2);border-radius:14px;padding:20px;width:320px;max-width:94vw;animation:modalIn 0.2s cubic-bezier(.34,1.56,.64,1);}
.confirm-title{font-weight:600;font-size:14px;margin-bottom:6px;}
.confirm-desc{font-size:12px;color:var(--text2);margin-bottom:16px;line-height:1.5;}
.tags-input-wrap{display:flex;flex-wrap:wrap;gap:4px;padding:5px 7px;background:var(--bg3);border:1px solid var(--border2);border-radius:var(--radius-sm);min-height:34px;cursor:text;}
.tags-input-wrap:focus-within{border-color:rgba(108,142,255,0.5);}
.tag{display:inline-flex;align-items:center;gap:3px;padding:2px 7px;background:var(--accent-bg);color:var(--accent);border-radius:20px;font-size:10px;font-weight:500;}
.tag-remove{cursor:pointer;opacity:0.6;} .tag-remove:hover{opacity:1;}
.tags-input{border:none;background:transparent;color:var(--text);font-family:var(--font);font-size:11.5px;outline:none;flex:1;min-width:70px;}
.autocomplete-wrap{position:relative;}
.autocomplete-list{position:absolute;background:var(--bg2);border:1px solid var(--border2);border-radius:var(--radius-sm);max-height:140px;overflow-y:auto;z-index:220;width:100%;top:calc(100% + 3px);left:0;box-shadow:var(--shadow);}
.autocomplete-item{padding:6px 10px;font-size:11.5px;cursor:pointer;}
.autocomplete-item:hover{background:var(--bg3);}
.dashboard-report-card{background:var(--bg2);border:1px solid var(--border);border-radius:var(--radius);padding:14px 16px;margin-bottom:10px;}
.report-row{display:flex;align-items:center;justify-content:space-between;padding:5px 0;border-bottom:1px solid var(--border);font-size:11.5px;}
.report-row:last-child{border-bottom:none;}
.report-label{color:var(--text2);}
.report-val{font-family:var(--mono);color:var(--text);font-weight:500;}
/* ══ RESPONSIVE ══ */
@media(max-width:760px){
.sidebar{width:46px;}
.sidebar-logo .logo-text,.nav-section,.user-info,.user-pill-right,.nav-item span{display:none;}
.sidebar-logo{padding:12px 7px;justify-content:center;}
.logo-icon{margin:0 auto;}
.nav-item{padding:10px;justify-content:center;margin:1px 3px;}
.page-body{padding:10px 10px 30px;}
.page-header{padding:14px 10px 0;}
.grid-4{grid-template-columns:1fr 1fr;}
.grid-3{grid-template-columns:1fr 1fr;}
.grid-2{grid-template-columns:1fr;}
}
</style>
</head>
<body>
<!-- ══ SIDEBAR ══ -->
<nav class="sidebar">
<div class="sidebar-logo">
<svg class="logo-icon" viewBox="0 0 30 30" xmlns="http://www.w3.org/2000/svg">
<rect width="30" height="30" rx="8" fill="#6c8eff"/>
<path d="M7 10h16M7 15h10M7 20h13" stroke="white" stroke-width="2" stroke-linecap="round"/>
<circle cx="23" cy="20" r="4" fill="#4ade80"/>
<path d="M21.5 20l1 1 2-2" stroke="white" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<div class="logo-text">
<div class="wordmark">StudyBuddy</div>
<div class="tagline">AI Academic Advisor</div>
</div>
</div>
<div class="nav-section">Overview</div>
<div class="nav-item active" onclick="nav('dashboard')"><svg class="icon" viewBox="0 0 16 16" fill="currentColor"><path d="M2 2h5v5H2V2zm7 0h5v5H9V2zm-7 7h5v5H2V9zm7 0h5v5H9V9z"/></svg><span>Dashboard</span></div>
<div class="nav-item" onclick="nav('programs')"><svg class="icon" viewBox="0 0 16 16" fill="currentColor"><path d="M1 2a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 01-1 1H2a1 1 0 01-1-1V2zm5 0a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 01-1 1H7a1 1 0 01-1-1V2zm5 0a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 01-1 1h-2a1 1 0 01-1-1V2zM1 7a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 01-1 1H2a1 1 0 01-1-1V7zm5 0a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 01-1 1H7a1 1 0 01-1-1V7zm5 0a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 01-1 1h-2a1 1 0 01-1-1V7zM1 12a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 01-1 1H2a1 1 0 01-1-1v-2zm5 0a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 01-1 1H7a1 1 0 01-1-1v-2zm5 0a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 01-1 1h-2a1 1 0 01-1-1v-2z"/></svg><span>Programs</span></div>
<div class="nav-item" onclick="nav('courses')"><svg class="icon" viewBox="0 0 16 16" fill="currentColor"><path d="M1 2.5A1.5 1.5 0 012.5 1h3A1.5 1.5 0 017 2.5v3A1.5 1.5 0 015.5 7h-3A1.5 1.5 0 011 5.5v-3zm8 0A1.5 1.5 0 0110.5 1h3A1.5 1.5 0 0115 2.5v3A1.5 1.5 0 0113.5 7h-3A1.5 1.5 0 019 5.5v-3zm-8 8A1.5 1.5 0 012.5 9h3A1.5 1.5 0 017 10.5v3A1.5 1.5 0 015.5 15h-3A1.5 1.5 0 011 13.5v-3zm8 0A1.5 1.5 0 0110.5 9h3a1.5 1.5 0 011.5 1.5v3a1.5 1.5 0 01-1.5 1.5h-3A1.5 1.5 0 019 13.5v-3z"/></svg><span>Courses</span></div>
<div class="nav-item" onclick="nav('assignments')"><svg class="icon" viewBox="0 0 16 16" fill="currentColor"><path d="M3 0h10a2 2 0 012 2v12a2 2 0 01-2 2H3a2 2 0 01-2-2V2a2 2 0 012-2zm0 1a1 1 0 00-1 1v12a1 1 0 001 1h10a1 1 0 001-1V2a1 1 0 00-1-1H3z"/><path d="M5.5 5h5a.5.5 0 000-1h-5a.5.5 0 000 1zm0 3h5a.5.5 0 000-1h-5a.5.5 0 000 1zm0 3h3a.5.5 0 000-1h-3a.5.5 0 000 1z"/></svg><span>Assignments</span></div>
<div class="nav-section">Tools</div>
<div class="nav-item" onclick="nav('timer')"><svg class="icon" viewBox="0 0 16 16" fill="currentColor"><path d="M8 3.5a.5.5 0 00-1 0V9a.5.5 0 00.252.434l3.5 2a.5.5 0 00.496-.868L8 8.71V3.5z"/><path d="M8 16A8 8 0 108 0a8 8 0 000 16z"/></svg><span>Study Timer</span></div>
<div class="nav-item" onclick="nav('tasks')"><svg class="icon" viewBox="0 0 16 16" fill="currentColor"><path d="M14 1a1 1 0 011 1v12a1 1 0 01-1 1H2a1 1 0 01-1-1V2a1 1 0 011-1h12zM2 0a2 2 0 00-2 2v12a2 2 0 002 2h12a2 2 0 002-2V2a2 2 0 00-2-2H2z"/><path d="M10.97 4.97a.75.75 0 010 1.06l-3.5 3.5a.75.75 0 01-1.06 0l-1.5-1.5a.75.75 0 011.06-1.06l.97.97 2.97-2.97a.75.75 0 011.06 0z"/></svg><span>Tasks</span></div>
<div class="nav-item" onclick="nav('resources')"><svg class="icon" viewBox="0 0 16 16" fill="currentColor"><path d="M1 2.5A1.5 1.5 0 012.5 1h11A1.5 1.5 0 0115 2.5v1A1.5 1.5 0 0113.5 5h-11A1.5 1.5 0 011 3.5v-1zM1 7.5A1.5 1.5 0 012.5 6h11A1.5 1.5 0 0115 7.5v1A1.5 1.5 0 0113.5 10h-11A1.5 1.5 0 011 8.5v-1zm0 5A1.5 1.5 0 012.5 11h11a1.5 1.5 0 011.5 1.5v1a1.5 1.5 0 01-1.5 1.5h-11A1.5 1.5 0 011 13.5v-1z"/></svg><span>Resources</span></div>
<div class="nav-item" onclick="nav('chat')"><svg class="icon" viewBox="0 0 16 16" fill="currentColor"><path d="M14 1a1 1 0 011 1v8a1 1 0 01-1 1H4.414A2 2 0 003 11.586l-2 2V2a1 1 0 011-1h12z"/></svg><span>Lyra AI</span></div>
<div class="nav-item" onclick="nav('contacts')"><svg class="icon" viewBox="0 0 16 16" fill="currentColor"><path d="M7 14s-1 0-1-1 1-4 5-4 5 3 5 4-1 1-1 1H7zm4-6a3 3 0 100-6 3 3 0 000 6z"/><path fill-rule="evenodd" d="M5.216 14A2.238 2.238 0 015 13c0-1.355.68-2.75 1.936-3.72A6.325 6.325 0 005 9c-4 0-5 3-5 4s1 1 1 1h4.216z"/><path d="M4.5 8a2.5 2.5 0 100-5 2.5 2.5 0 000 5z"/></svg><span>Contacts</span></div>
<div class="nav-item" onclick="nav('insights')"><svg class="icon" viewBox="0 0 16 16" fill="currentColor"><path d="M8 16a2 2 0 002-2H6a2 2 0 002 2zm.995-14.901a1 1 0 10-1.99 0A5.002 5.002 0 003 6c0 1.098-.5 6-2 7h14c-1.5-1-2-5.902-2-7 0-2.42-1.72-4.44-4.005-4.901z"/></svg><span>Insights</span><span class="nav-badge" id="insights-badge">3</span></div>
<div class="sidebar-bottom">
<div class="user-pill" onclick="openSettings()">
<div class="avatar">JD</div>
<div class="user-info"><div class="name">Jamie Davis</div><div class="plan">Free plan</div></div>
<span class="user-pill-right">⚙</span>
</div>
</div>
</nav>
<!-- ══ MAIN ══ -->
<div class="main">
<!-- DASHBOARD -->
<div id="page-dashboard" class="page active">
<div class="page-header">
<div><div class="page-title" id="dash-greeting">Good evening, Jamie 👋</div><div class="page-sub">Spring 2026 · Week 9 of 16</div></div>
<button class="btn btn-primary btn-sm" onclick="openCourseModal()">+ Add Course</button>
</div>
<div class="page-body">
<div id="dash-stats-row" style="margin-bottom:16px"></div>
<div class="grid-2" style="gap:16px" id="dash-main-grid">
<div>
<div class="section-header"><div class="section-title">Current Courses</div><button class="btn btn-ghost btn-sm" onclick="nav('courses')">View all</button></div>
<div id="dash-courses"></div>
</div>
<div>
<div class="section-header"><div class="section-title">⚡ Active Flags</div><button class="btn btn-ghost btn-sm" onclick="nav('insights')">All flags</button></div>
<div id="dash-flags"></div>
<div class="divider"></div>
<div class="section-header"><div class="section-title">Upcoming Deadlines</div></div>
<div class="card card-sm" id="dash-upcoming"></div>
</div>
</div>
<!-- Reports row -->
<div id="dash-reports" style="margin-top:16px"></div>
</div>
</div>
<!-- PROGRAMS -->
<div id="page-programs" class="page">
<div class="page-header">
<div><div class="page-title">Programs</div><div class="page-sub">Degree programs, certificates, and short courses</div></div>
<button class="btn btn-primary" onclick="openProgramModal()">+ Add Program</button>
</div>
<div class="page-body"><div class="grid-2" id="programs-grid"></div></div>
</div>
<!-- COURSES -->
<div id="page-courses" class="page">
<div class="page-header">
<div><div class="page-title">Courses</div><div class="page-sub" id="courses-sub">All courses</div></div>
<button class="btn btn-primary" onclick="openCourseModal()">+ Add Course</button>
</div>
<div class="page-body"><div class="grid-2" id="courses-grid"></div></div>
</div>
<!-- ASSIGNMENTS -->
<div id="page-assignments" class="page">
<div class="page-header">
<div><div class="page-title">Assignments</div><div class="page-sub">All tasks across your courses</div></div>
<button class="btn btn-primary" onclick="openAssignmentModal()">+ Add Assignment</button>
</div>
<div class="page-body">
<div style="display:flex;gap:5px;margin-bottom:11px;flex-wrap:wrap" id="filter-bar">
<button class="btn btn-ghost btn-sm filter-btn active" data-filter="all">All</button>
<button class="btn btn-ghost btn-sm filter-btn" data-filter="pending">Pending</button>
<button class="btn btn-ghost btn-sm filter-btn" data-filter="done">Completed</button>
<button class="btn btn-ghost btn-sm filter-btn" data-filter="overdue">Overdue</button>
</div>
<div class="card" style="padding:0;overflow:hidden">
<table class="asgn-table">
<thead><tr><th>Assignment</th><th>Course</th><th>Type</th><th>Due</th><th>Weight</th><th>Score</th><th>Status</th><th>Time</th><th></th></tr></thead>
<tbody id="asgn-tbody"></tbody>
</table>
</div>
</div>
</div>
<!-- TIMER -->
<div id="page-timer" class="page">
<div class="page-header"><div><div class="page-title">Study Timer</div><div class="page-sub">Track sessions and build habits</div></div></div>
<div class="page-body">
<div style="display:flex;gap:22px;flex-wrap:wrap;align-items:flex-start">
<div class="timer-wrap" style="flex:0 0 auto">
<div class="form-row" style="width:260px;margin-bottom:9px">
<select class="form-select" id="timer-course-select" onchange="loadTimerAssignments()"><option value="">Select course...</option></select>
<select class="form-select" id="timer-asgn-select"><option value="">Link assignment...</option></select>
</div>
<input class="form-input" id="timer-session-label" placeholder="Session label (optional)" style="width:260px;margin-bottom:9px"/>
<div class="timer-ring">
<svg width="190" height="190" viewBox="0 0 190 190"><circle class="ring-bg" cx="95" cy="95" r="83"/><circle class="ring-fg" id="timer-ring-fg" cx="95" cy="95" r="83" stroke-dasharray="521.5" stroke-dashoffset="0"/></svg>
<div class="timer-display"><div class="timer-time" id="timer-display">25:00</div><div class="timer-label" id="timer-label">Focus session</div></div>
</div>
<div class="timer-controls">
<button class="timer-btn timer-btn-ghost" title="Reset" onclick="resetTimer()">↺</button>
<button class="timer-btn timer-btn-primary" id="timer-toggle" onclick="toggleTimer()">▶</button>
<button class="timer-btn timer-btn-ghost" title="Skip" onclick="skipTimer()">⏭</button>
</div>
<div class="timer-presets">
<button class="btn btn-ghost btn-sm" onclick="setTimer(25,'Focus session')">25 min</button>
<button class="btn btn-ghost btn-sm" onclick="setTimer(50,'Deep work')">50 min</button>
<button class="btn btn-ghost btn-sm" onclick="setTimer(5,'Short break')">5 min</button>
<button class="btn btn-ghost btn-sm" onclick="setTimer(15,'Long break')">15 min</button>
</div>
<div class="custom-row">
<input class="form-input" type="number" id="custom-mins" placeholder="45" min="1" max="360" style="width:54px;text-align:center"/>
<span style="font-size:11px;color:var(--text3)">min</span>
<input class="form-input" type="text" id="custom-label-inp" placeholder="label" style="width:90px"/>
<button class="btn btn-ghost btn-sm" onclick="setCustomTimer()">Set</button>
</div>
<label style="font-size:11px;color:var(--text2);margin-top:8px;display:flex;align-items:center;gap:5px;cursor:pointer"><input type="checkbox" id="timer-sound-on" checked/> Sound on complete</label>
</div>
<div style="flex:1;min-width:220px;padding-top:2px">
<div class="section-title" style="margin-bottom:9px">Recent Sessions</div>
<div id="session-log-list" style="margin-bottom:12px"></div>
<div class="divider"></div>
<div class="section-title" style="margin-bottom:9px">This Week</div>
<div class="grid-2" style="gap:7px" id="timer-stats"></div>
</div>
</div>
</div>
</div>
<!-- TASKS -->
<div id="page-tasks" class="page">
<div class="page-header">
<div><div class="page-title">Task List</div><div class="page-sub">Personal to-dos and reminders</div></div>
<div style="display:flex;gap:7px">
<label style="display:flex;align-items:center;gap:5px;font-size:12px;color:var(--text2);cursor:pointer;padding:6px 10px;border:1px solid var(--border2);border-radius:var(--radius-sm)">
<input type="checkbox" id="show-completed-toggle" onchange="renderTasks()"/> Show completed
</label>
<button class="btn btn-primary" onclick="openTaskModal()">+ Add Task</button>
</div>
</div>
<div class="page-body">
<div style="display:flex;gap:5px;margin-bottom:11px;flex-wrap:wrap" id="task-filter-bar">
<button class="btn btn-ghost btn-sm task-filter-btn active" data-tf="all">All</button>
<button class="btn btn-ghost btn-sm task-filter-btn" data-tf="pending">Pending</button>
<button class="btn btn-ghost btn-sm task-filter-btn" data-tf="high">High Priority</button>
</div>
<div class="card" style="padding:0;overflow:hidden" id="task-list"></div>
</div>
</div>
<!-- RESOURCES -->
<div id="page-resources" class="page">
<div class="page-header">
<div><div class="page-title">Resources</div><div class="page-sub">Textbooks, links, tools — linked to your courses</div></div>
<button class="btn btn-primary" onclick="openResourceModal()">+ Add Resource</button>
</div>
<div class="page-body">
<div style="display:flex;gap:5px;margin-bottom:11px;flex-wrap:wrap" id="res-filter-bar">
<button class="btn btn-ghost btn-sm res-filter-btn active" data-rf="all">All</button>
<button class="btn btn-ghost btn-sm res-filter-btn" data-rf="Book">Books</button>
<button class="btn btn-ghost btn-sm res-filter-btn" data-rf="Link">Links</button>
<button class="btn btn-ghost btn-sm res-filter-btn" data-rf="Tool">Tools</button>
<button class="btn btn-ghost btn-sm res-filter-btn" data-rf="needed">Needed</button>
</div>
<div id="resources-needed-list"></div>
<div id="resources-obtained-section" style="display:none">
<div class="obtained-divider"><div class="obtained-divider-label">✓ Obtained</div><div class="obtained-divider-line"></div></div>
<div id="resources-obtained-list"></div>
</div>
</div>
</div>
<!-- AI CHAT — Lyra -->
<div id="page-chat" class="page">
<div class="chat-layout">
<div class="chat-header">
<div class="chat-avatar-wrap"><div class="chat-avatar">✦</div><div class="chat-online-dot"></div></div>
<div class="chat-info"><div class="name">Lyra <span style="font-size:10px;color:var(--text3);font-weight:400">by StudyBuddy</span></div><div class="status">● Online · knows your academic profile</div></div>
<button class="btn btn-ghost btn-sm" style="margin-left:auto" onclick="clearChat()">Clear</button>
</div>
<div class="chat-messages" id="chat-messages"></div>
<div class="quick-prompts" id="quick-prompts">
<div class="quick-chip" onclick="sendQuick(this)">What grade am I likely to get in Statistics?</div>
<div class="quick-chip" onclick="sendQuick(this)">Which course needs most attention?</div>
<div class="quick-chip" onclick="sendQuick(this)">Build me a study schedule for this week</div>
<div class="quick-chip" onclick="sendQuick(this)">Am I on track for my GPA target?</div>
</div>
<div class="chat-input-wrap">
<textarea class="chat-input" id="chat-input" rows="1" placeholder="Ask Lyra anything..." onkeydown="handleChatKey(event)"></textarea>
<button class="send-btn" onclick="sendChat()"><svg width="13" height="13" viewBox="0 0 16 16" fill="currentColor"><path d="M15.854.146a.5.5 0 01.11.54l-5.819 14.547a.75.75 0 01-1.329.124l-3.178-4.995L.643 7.184a.75.75 0 01.124-1.33L15.314.037a.5.5 0 01.54.11z"/></svg></button>
</div>
</div>
</div>
<!-- CONTACTS -->
<div id="page-contacts" class="page">
<div class="page-header">
<div><div class="page-title">Contacts</div><div class="page-sub">Professors, TAs, and group members</div></div>
<button class="btn btn-primary" onclick="openContactModal()">+ Add Contact</button>
</div>
<div class="page-body">
<div style="display:flex;gap:5px;margin-bottom:11px;flex-wrap:wrap" id="contact-filter-bar">
<button class="btn btn-ghost btn-sm contact-filter-btn active" data-cf="all">All</button>
<button class="btn btn-ghost btn-sm contact-filter-btn" data-cf="professor">Professors</button>
<button class="btn btn-ghost btn-sm contact-filter-btn" data-cf="ta">TAs</button>
<button class="btn btn-ghost btn-sm contact-filter-btn" data-cf="member">Group Members</button>
</div>
<div class="card" style="padding:0;overflow:hidden" id="contacts-list"></div>
</div>
</div>
<!-- INSIGHTS -->
<div id="page-insights" class="page">
<div class="page-header"><div><div class="page-title">Insights & Flags</div><div class="page-sub">AI-generated alerts and academic intelligence</div></div></div>
<div class="page-body" id="insights-body"></div>
</div>
</div><!-- /.main -->
<!-- ══ MODALS ══ -->
<!-- Program modal -->
<div class="modal-overlay" id="modal-program"><div class="modal">
<div class="modal-title" id="prog-modal-title">Add Program</div>
<input type="hidden" id="ep-id"/>
<div class="form-group"><label class="form-label">Program Name</label><input class="form-input" id="fp-name" placeholder="e.g. BSc Computer Science"/></div>
<div class="form-row">
<div class="form-group"><label class="form-label">Institution</label><input class="form-input" id="fp-institution" placeholder="University name"/></div>
<div class="form-group"><label class="form-label">Type</label><select class="form-select" id="fp-type"><option>Degree</option><option>Certificate</option><option>Short Course</option><option>Bootcamp</option><option>Self-study</option></select></div>
</div>
<div class="form-row">
<div class="form-group"><label class="form-label">Start Year</label><select class="form-select" id="fp-start"></select></div>
<div class="form-group"><label class="form-label">End Year (expected)</label><select class="form-select" id="fp-end"></select></div>
</div>
<div class="form-group"><label class="form-label">Color</label><div class="color-picker" id="prog-color-picker">
<div class="color-swatch selected" data-color="#6c8eff" style="background:#6c8eff"></div>
<div class="color-swatch" data-color="#4ade80" style="background:#4ade80"></div>
<div class="color-swatch" data-color="#fbbf24" style="background:#fbbf24"></div>
<div class="color-swatch" data-color="#f87171" style="background:#f87171"></div>
<div class="color-swatch" data-color="#c084fc" style="background:#c084fc"></div>
<div class="color-swatch" data-color="#2dd4bf" style="background:#2dd4bf"></div>
</div></div>
<div class="modal-actions-split">
<button class="btn btn-danger btn-sm" id="del-prog-btn" onclick="confirmDeleteProgram()" style="display:none">🗑 Delete</button>
<div style="display:flex;gap:7px;margin-left:auto">
<button class="btn btn-ghost" onclick="closeModal('program')">Cancel</button>
<button class="btn btn-primary" onclick="saveProgram()" id="prog-save-btn">Add Program</button>
</div>
</div>
</div></div>
<!-- Course modal -->
<div class="modal-overlay" id="modal-course"><div class="modal">
<div class="modal-title" id="course-modal-title">Add Course</div>
<input type="hidden" id="ec-id"/>
<div class="form-group"><label class="form-label">Course Name</label><input class="form-input" id="fc-name" placeholder="e.g. Introduction to Statistics"/></div>
<div class="form-row">
<div class="form-group"><label class="form-label">Course Code</label><input class="form-input" id="fc-code" placeholder="STAT 301"/></div>
<div class="form-group"><label class="form-label">Credits</label><input class="form-input" type="number" id="fc-credits" placeholder="3"/></div>
</div>
<div class="form-row">
<div class="form-group"><label class="form-label">Target Grade %</label><input class="form-input" type="number" id="fc-target" placeholder="85"/></div>
<div class="form-group"><label class="form-label">Difficulty (1–5)</label><select class="form-select" id="fc-difficulty"><option value="1">1 — Easy</option><option value="2">2</option><option value="3" selected>3 — Medium</option><option value="4">4</option><option value="5">5 — Hard</option></select></div>
</div>
<div class="form-row">
<div class="form-group"><label class="form-label">Term</label><select class="form-select" id="fc-term"><option>Fall</option><option>Winter</option><option selected>Spring</option><option>Summer</option><option>Semester 1</option><option>Semester 2</option><option>Trimester 1</option><option>Trimester 2</option><option>Trimester 3</option></select></div>
<div class="form-group"><label class="form-label">Year</label><select class="form-select" id="fc-year"></select></div>
</div>
<div class="form-group"><label class="form-label">Program (optional)</label><select class="form-select" id="fc-program"><option value="">— None —</option></select></div>
<div class="form-group"><label class="form-label">Professor</label>
<div class="autocomplete-wrap"><input class="form-input" id="fc-professor" placeholder="Prof. Smith" autocomplete="off" oninput="acFilter('professor',this.value)"/><div class="autocomplete-list" id="ac-professor" style="display:none"></div></div>
</div>
<div class="form-group"><label class="form-label">Teaching Assistant (optional)</label>
<div class="autocomplete-wrap"><input class="form-input" id="fc-ta" placeholder="TA name" autocomplete="off" oninput="acFilter('ta',this.value)"/><div class="autocomplete-list" id="ac-ta" style="display:none"></div></div>
</div>
<div class="form-group"><label class="form-label">Color</label><div class="color-picker" id="color-picker">
<div class="color-swatch selected" data-color="#6c8eff" style="background:#6c8eff"></div>
<div class="color-swatch" data-color="#4ade80" style="background:#4ade80"></div>
<div class="color-swatch" data-color="#fbbf24" style="background:#fbbf24"></div>
<div class="color-swatch" data-color="#f87171" style="background:#f87171"></div>
<div class="color-swatch" data-color="#c084fc" style="background:#c084fc"></div>
<div class="color-swatch" data-color="#2dd4bf" style="background:#2dd4bf"></div>
<div class="color-swatch" data-color="#fb923c" style="background:#fb923c"></div>
</div></div>
<div class="modal-actions-split">
<button class="btn btn-danger btn-sm" id="del-course-btn" onclick="confirmDeleteCourse()" style="display:none">🗑 Delete</button>
<div style="display:flex;gap:7px;margin-left:auto"><button class="btn btn-ghost" onclick="closeModal('course')">Cancel</button><button class="btn btn-primary" onclick="saveCourse()" id="course-save-btn">Add Course</button></div>
</div>
</div></div>
<!-- Assignment modal -->
<div class="modal-overlay" id="modal-assignment"><div class="modal">
<div class="modal-title" id="asgn-modal-title">Add Assignment</div>
<input type="hidden" id="ea-id"/>
<div class="form-group"><label class="form-label">Title</label><input class="form-input" id="fa-title" placeholder="e.g. Midterm Exam"/></div>
<div class="form-row">
<div class="form-group"><label class="form-label">Course</label><select class="form-select" id="fa-course" onchange="loadAsgnProgramTask()"></select></div>
<div class="form-group"><label class="form-label">Type</label><select class="form-select" id="fa-type"><option>Exam</option><option>Essay</option><option>Homework</option><option>Quiz</option><option>Project</option><option>Lab</option></select></div>
</div>
<div class="form-group"><label class="form-label">Program Task (optional)</label><select class="form-select" id="fa-program-task"><option value="">— None —</option></select></div>
<div class="form-row">
<div class="form-group"><label class="form-label">Due Date <span style="color:var(--red)">*</span></label><input class="form-input" type="date" id="fa-due"/></div>
<div class="form-group"><label class="form-label">Weight (%)</label><input class="form-input" type="number" id="fa-weight" placeholder="20"/></div>
</div>
<div class="form-row">
<div class="form-group"><label class="form-label">Score Achieved</label><input class="form-input" type="number" id="fa-score" placeholder="Blank if not graded"/></div>
<div class="form-group"><label class="form-label">Score Possible</label><input class="form-input" type="number" id="fa-possible" placeholder="100"/></div>
</div>
<div class="form-row">
<div class="form-group"><label class="form-label">Status</label><select class="form-select" id="fa-status"><option value="pending">Pending</option><option value="done">Done</option></select></div>
<div class="form-group"><label class="form-label">Assignment Type</label><select class="form-select" id="fa-group-type"><option value="individual">Individual</option><option value="group">Group</option></select></div>
</div>
<div class="form-group"><label class="form-label">Notes (optional)</label><input class="form-input" id="fa-notes" placeholder="Any notes..."/></div>
<div class="modal-actions-split">
<button class="btn btn-danger btn-sm" id="del-asgn-btn" onclick="confirmDeleteAssignmentById(null,true)" style="display:none">🗑 Delete</button>
<div style="display:flex;gap:7px;margin-left:auto"><button class="btn btn-ghost" onclick="closeModal('assignment')">Cancel</button><button class="btn btn-primary" onclick="saveAssignment()" id="asgn-save-btn">Add Assignment</button></div>
</div>
</div></div>
<!-- Extend due date -->
<div class="modal-overlay" id="modal-extend"><div class="modal modal-sm">
<div class="modal-title">Extend Due Date</div>
<input type="hidden" id="extend-id"/>
<div class="form-group"><label class="form-label">Assignment</label><div id="extend-title-display" style="font-weight:500;font-size:12.5px;padding:5px 0"></div></div>
<div class="form-group"><label class="form-label">New Due Date</label><input class="form-input" type="date" id="extend-date"/></div>
<div class="form-group"><label class="form-label">Reason (optional)</label><input class="form-input" id="extend-reason" placeholder="e.g. Extension approved by professor"/></div>
<div class="modal-actions"><button class="btn btn-ghost" onclick="closeModal('extend')">Cancel</button><button class="btn btn-primary" onclick="saveExtend()">Save</button></div>
</div></div>
<!-- Task modal -->
<div class="modal-overlay" id="modal-task"><div class="modal modal-sm">
<div class="modal-title" id="task-modal-title">Add Task</div>
<input type="hidden" id="et-id"/>
<div class="form-group"><label class="form-label">Task</label><input class="form-input" id="ft-title" placeholder="e.g. Review lecture notes"/></div>
<div class="form-row">
<div class="form-group"><label class="form-label">Due Date</label><input class="form-input" type="date" id="ft-due"/></div>
<div class="form-group"><label class="form-label">Priority</label><select class="form-select" id="ft-priority"><option value="low">Low</option><option value="medium" selected>Medium</option><option value="high">High</option></select></div>
</div>
<div class="form-group"><label class="form-label">Link to Course</label><select class="form-select" id="ft-course"><option value="">— None —</option></select></div>
<div class="form-group"><label class="form-label">Notes</label><input class="form-input" id="ft-notes" placeholder="optional"/></div>
<div class="modal-actions-split">
<button class="btn btn-danger btn-sm" id="del-task-btn" onclick="confirmDeleteTask()" style="display:none">🗑 Delete</button>
<div style="display:flex;gap:7px;margin-left:auto"><button class="btn btn-ghost" onclick="closeModal('task')">Cancel</button><button class="btn btn-primary" onclick="saveTask()" id="task-save-btn">Add Task</button></div>
</div>
</div></div>
<!-- Resource modal -->
<div class="modal-overlay" id="modal-resource"><div class="modal">
<div class="modal-title" id="res-modal-title">Add Resource</div>
<input type="hidden" id="er-id"/>
<div class="form-group"><label class="form-label">Title / Name</label><input class="form-input" id="fr-title" placeholder="e.g. Organic Chemistry by Clayden"/></div>
<div class="form-row">
<div class="form-group"><label class="form-label">Type</label><select class="form-select" id="fr-type"><option>Book</option><option>Link</option><option>Tool</option><option>Note</option><option>Video</option><option>Other</option></select></div>
<div class="form-group"><label class="form-label">Status</label><select class="form-select" id="fr-status"><option value="needed">Needed</option><option value="pending">Ordered / Pending</option><option value="obtained">Obtained</option></select></div>
</div>
<div class="form-group"><label class="form-label">URL / Link</label><input class="form-input" id="fr-url" placeholder="https://..."/></div>
<div class="form-row">
<div class="form-group"><label class="form-label">Course</label><select class="form-select" id="fr-course" onchange="loadResAssignments()"><option value="">— None —</option></select></div>
<div class="form-group"><label class="form-label">Assignment</label><select class="form-select" id="fr-assignment"><option value="">— None —</option></select></div>
</div>
<div class="form-group"><label class="form-label">Notes</label><input class="form-input" id="fr-notes" placeholder="ISBN, cost, notes..."/></div>
<div class="modal-actions-split">
<button class="btn btn-danger btn-sm" id="del-res-btn" onclick="confirmDeleteResource()" style="display:none">🗑 Delete</button>
<div style="display:flex;gap:7px;margin-left:auto"><button class="btn btn-ghost" onclick="closeModal('resource')">Cancel</button><button class="btn btn-primary" onclick="saveResource()" id="res-save-btn">Add Resource</button></div>
</div>
</div></div>
<!-- Contact modal -->
<div class="modal-overlay" id="modal-contact"><div class="modal modal-sm">
<div class="modal-title" id="contact-modal-title">Add Contact</div>
<input type="hidden" id="econ-id"/>
<div class="form-group"><label class="form-label">Name</label><input class="form-input" id="fcon-name" placeholder="Full name"/></div>
<div class="form-row">
<div class="form-group"><label class="form-label">Role</label><select class="form-select" id="fcon-role"><option value="professor">Professor</option><option value="ta">Teaching Assistant</option><option value="member">Group Member</option><option value="other">Other</option></select></div>
<div class="form-group"><label class="form-label">Email</label><input class="form-input" id="fcon-email" placeholder="email@uni.edu"/></div>
</div>
<div class="form-group"><label class="form-label">Department / Notes</label><input class="form-input" id="fcon-notes" placeholder="e.g. Statistics Dept."/></div>
<div class="modal-actions-split">
<button class="btn btn-danger btn-sm" id="del-contact-btn" onclick="confirmDeleteContact()" style="display:none">🗑 Delete</button>
<div style="display:flex;gap:7px;margin-left:auto"><button class="btn btn-ghost" onclick="closeModal('contact')">Cancel</button><button class="btn btn-primary" onclick="saveContact()" id="contact-save-btn">Add Contact</button></div>
</div>
</div></div>
<!-- Confirm modal -->
<div class="modal-overlay" id="modal-confirm"><div class="confirm-modal">
<div class="confirm-title" id="confirm-title">Delete?</div>
<div class="confirm-desc" id="confirm-desc">This cannot be undone.</div>
<div style="display:flex;gap:7px;justify-content:flex-end"><button class="btn btn-ghost" onclick="closeModal('confirm')">Cancel</button><button class="btn btn-danger" id="confirm-ok">Delete</button></div>
</div></div>
<!-- Settings panel -->
<div class="settings-overlay" id="settings-overlay">
<div class="settings-backdrop" onclick="closeSettings()"></div>
<div class="settings-panel">
<div class="settings-header">
<div>
<div class="settings-title">Settings</div>
<div style="font-size:11px;color:var(--text3);margin-top:2px">Jamie Davis · Free plan</div>
</div>
<button class="btn-icon" onclick="closeSettings()">✕</button>
</div>
<div class="settings-body">
<div class="settings-section">
<div class="settings-section-title">Appearance</div>
<div class="settings-row">
<div><div class="settings-row-label">Dark Mode</div><div class="settings-row-sub">Switch between dark and light theme</div></div>
<label class="toggle-switch"><input type="checkbox" id="settings-dark-toggle" onchange="toggleThemeFromSettings(this)"/><div class="toggle-track"></div></label>
</div>
</div>
<div class="settings-section">
<div class="settings-section-title">Study Goals</div>
<div class="form-group">
<label class="form-label">Weekly study goal (hours)</label>
<input class="form-input" type="number" id="settings-study-goal" placeholder="18" min="1" max="100" onchange="saveStudyGoal(this.value)"/>
</div>
<div class="form-group">
<label class="form-label">GPA target</label>
<input class="form-input" type="number" id="settings-gpa-target" placeholder="3.7" min="0" max="4" step="0.1" onchange="saveGpaTarget(this.value)"/>
</div>
</div>
<div class="settings-section">
<div class="settings-section-title">Dashboard Widgets</div>
<div style="font-size:11px;color:var(--text3);margin-bottom:9px">Toggle which cards appear on your dashboard</div>
<div id="widget-toggles"></div>
</div>
<div class="settings-section">
<div class="settings-section-title">Notifications</div>
<div class="settings-row">
<div><div class="settings-row-label">Timer sound alerts</div></div>
<label class="toggle-switch"><input type="checkbox" id="settings-sound" checked onchange="document.getElementById('timer-sound-on').checked=this.checked"/><div class="toggle-track"></div></label>
</div>
<div class="settings-row">
<div><div class="settings-row-label">Show completed tasks</div></div>
<label class="toggle-switch"><input type="checkbox" id="settings-show-completed" onchange="syncShowCompleted(this)"/><div class="toggle-track"></div></label>
</div>
</div>
<div class="settings-section">
<div class="settings-section-title">Account</div>
<div class="settings-row"><div><div class="settings-row-label">Name</div></div><input class="form-input" value="Jamie Davis" style="width:140px"/></div>
<div class="settings-row"><div><div class="settings-row-label">Email</div></div><input class="form-input" value="jamie@uni.edu" style="width:160px"/></div>
</div>
</div>
</div>
</div>
<script>
// ══════════════════════════════════════════════════════
// SETTINGS & THEME
// ══════════════════════════════════════════════════════
const SETTINGS = {
theme: localStorage.getItem('sb-theme')||'dark',
studyGoal: parseFloat(localStorage.getItem('sb-study-goal'))||18,
gpaTarget: parseFloat(localStorage.getItem('sb-gpa-target'))||3.7,
showCompleted: localStorage.getItem('sb-show-completed')==='true',
widgets: JSON.parse(localStorage.getItem('sb-widgets')||'null') || {gpa:true,studyhrs:true,assignments:true,flags:true,reports:true},
};
document.documentElement.dataset.theme = SETTINGS.theme;
function toggleTheme(){
const isDark = document.documentElement.dataset.theme==='dark';
document.documentElement.dataset.theme = isDark?'light':'dark';
SETTINGS.theme = document.documentElement.dataset.theme;
localStorage.setItem('sb-theme',SETTINGS.theme);
const st=document.getElementById('settings-dark-toggle'); if(st) st.checked=!isDark;
}
function toggleThemeFromSettings(cb){ SETTINGS.theme=cb.checked?'dark':'light'; document.documentElement.dataset.theme=SETTINGS.theme; localStorage.setItem('sb-theme',SETTINGS.theme); }
function saveStudyGoal(v){ SETTINGS.studyGoal=parseFloat(v)||18; localStorage.setItem('sb-study-goal',SETTINGS.studyGoal); renderDashboard(); }
function saveGpaTarget(v){ SETTINGS.gpaTarget=parseFloat(v)||3.7; localStorage.setItem('sb-gpa-target',SETTINGS.gpaTarget); renderDashboard(); }
function syncShowCompleted(cb){ SETTINGS.showCompleted=cb.checked; localStorage.setItem('sb-show-completed',cb.checked); document.getElementById('show-completed-toggle').checked=cb.checked; renderTasks(); }
function openSettings(){
document.getElementById('settings-overlay').classList.add('open');
document.getElementById('settings-dark-toggle').checked = SETTINGS.theme==='dark';
document.getElementById('settings-study-goal').value = SETTINGS.studyGoal;
document.getElementById('settings-gpa-target').value = SETTINGS.gpaTarget;
document.getElementById('settings-show-completed').checked = SETTINGS.showCompleted;
// Widget toggles
const wt=document.getElementById('widget-toggles');
const widgets=[
{key:'gpa',label:'Current GPA'},{key:'studyhrs',label:'Study Hours'},{key:'assignments',label:'Assignments Due'},
{key:'flags',label:'Active Flags'},{key:'reports',label:'Activity Reports'},
];
wt.innerHTML=widgets.map(w=>`<div class="widget-toggle ${SETTINGS.widgets[w.key]?'on':'off'}" onclick="toggleWidget('${w.key}',this)">
<span style="font-size:12px">${SETTINGS.widgets[w.key]?'✓':'○'}</span>
<span class="widget-toggle-label">${w.label}</span>
</div>`).join('');
}
function closeSettings(){ document.getElementById('settings-overlay').classList.remove('open'); }
function toggleWidget(key,el){
SETTINGS.widgets[key]=!SETTINGS.widgets[key];
localStorage.setItem('sb-widgets',JSON.stringify(SETTINGS.widgets));
el.className='widget-toggle '+(SETTINGS.widgets[key]?'on':'off');
el.querySelector('span').textContent=SETTINGS.widgets[key]?'✓':'○';
renderDashboard();
}
// ══════════════════════════════════════════════════════
// DATA
// ══════════════════════════════════════════════════════
let selectedColor='#6c8eff', selectedProgColor='#6c8eff';
let timerInterval=null,timerRunning=false,timerSeconds=25*60,timerTotal=25*60,timerMode='Focus session',timerStartTime=null;
let activeFilter='all',activeTaskFilter='all',activeResFilter='all',activeContactFilter='all';
let editingCourseId=null,editingAssignmentId=null,editingTaskId=null,editingResourceId=null,editingContactId=null,editingProgramId=null;
const today=new Date();
const fmt=d=>{const x=new Date(d);return x.toISOString().split('T')[0];};
const addDays=n=>{const d=new Date(today);d.setDate(d.getDate()+n);return fmt(d);};
const fmtDuration=mins=>{if(!mins||mins<1)return null;if(mins<60)return `${Math.round(mins)}m`;return `${Math.floor(mins/60)}h ${Math.round(mins%60)}m`;};
const daysDiff=ms=>Math.round((new Date()-new Date(ms))/86400000);
let CONTACTS=[
{id:1,name:'Prof. Martinez',role:'professor',email:'martinez@uni.edu',notes:'Statistics Dept.'},
{id:2,name:'Prof. Chen',role:'professor',email:'chen@uni.edu',notes:'Chemistry Dept.'},
{id:3,name:'Dr. Williams',role:'professor',email:'williams@uni.edu',notes:'History Dept.'},
{id:4,name:'Alice Nguyen',role:'ta',email:'alice@uni.edu',notes:''},
{id:5,name:'Bob Okafor',role:'ta',email:'bob@uni.edu',notes:''},
];
let PROGRAMS=[
{id:1,name:'BSc Data Science',institution:'State University',type:'Degree',start:2023,end:2027,color:'#6c8eff'},
];
let PROGRAM_TASKS=[
{id:1,programId:1,title:'Year 2 Statistics',done:false},
{id:2,programId:1,title:'Year 2 Chemistry',done:false},
{id:3,programId:1,title:'Core Writing Requirement',done:true},
];
let COURSES=[
{id:1,name:'Introduction to Statistics',code:'STAT 301',credits:3,color:'#6c8eff',target:85,difficulty:4,term:'Spring',year:2026,professor:'Prof. Martinez',ta:'',programId:1},
{id:2,name:'Organic Chemistry II',code:'CHEM 202',credits:4,color:'#f87171',target:80,difficulty:5,term:'Spring',year:2026,professor:'Prof. Chen',ta:'Alice Nguyen',programId:1},
{id:3,name:'World History 1800–Present',code:'HIST 210',credits:3,color:'#4ade80',target:90,difficulty:2,term:'Spring',year:2026,professor:'Dr. Williams',ta:'',programId:null},
{id:4,name:'Linear Algebra',code:'MATH 340',credits:3,color:'#fbbf24',target:82,difficulty:4,term:'Spring',year:2026,professor:'Prof. Martinez',ta:'Bob Okafor',programId:1},
{id:5,name:'Technical Writing',code:'ENGL 215',credits:3,color:'#2dd4bf',target:88,difficulty:2,term:'Spring',year:2026,professor:'',ta:'',programId:null},
];
let ASSIGNMENTS=[
{id:1,courseId:1,title:'Homework 3 – Probability',type:'Homework',due:addDays(-3),weight:10,status:'pending',score:null,possible:100,notes:'',groupType:'individual',createdAt:addDays(-14),completedAt:null},
{id:2,courseId:1,title:'Midterm Exam',type:'Exam',due:addDays(-14),weight:30,status:'done',score:68,possible:100,notes:'',groupType:'individual',createdAt:addDays(-30),completedAt:addDays(-14),programTaskId:null},
{id:3,courseId:1,title:'Final Exam',type:'Exam',due:addDays(42),weight:40,status:'pending',score:null,possible:100,notes:'',groupType:'individual',createdAt:addDays(-7),completedAt:null},
{id:4,courseId:2,title:'Lab Report 4',type:'Lab',due:addDays(-1),weight:15,status:'pending',score:null,possible:100,notes:'',groupType:'group',createdAt:addDays(-10),completedAt:null},
{id:5,courseId:2,title:'Reaction Mechanism Quiz',type:'Quiz',due:addDays(5),weight:10,status:'pending',score:null,possible:50,notes:'',groupType:'individual',createdAt:addDays(-5),completedAt:null},
{id:6,courseId:2,title:'Final Exam',type:'Exam',due:addDays(44),weight:35,status:'pending',score:null,possible:100,notes:'',groupType:'individual',createdAt:addDays(-7),completedAt:null},
{id:7,courseId:3,title:'Research Essay',type:'Essay',due:addDays(8),weight:25,status:'pending',score:null,possible:100,notes:'',groupType:'individual',createdAt:addDays(-5),completedAt:null},
{id:8,courseId:3,title:'Midterm',type:'Exam',due:addDays(-21),weight:30,status:'done',score:91,possible:100,notes:'',groupType:'individual',createdAt:addDays(-40),completedAt:addDays(-21)},
{id:9,courseId:4,title:'Problem Set 5',type:'Homework',due:addDays(3),weight:8,status:'pending',score:null,possible:40,notes:'',groupType:'individual',createdAt:addDays(-4),completedAt:null},
{id:10,courseId:4,title:'Midterm',type:'Exam',due:addDays(-10),weight:30,status:'done',score:74,possible:100,notes:'',groupType:'individual',createdAt:addDays(-28),completedAt:addDays(-10)},
{id:11,courseId:5,title:'Technical Report Draft',type:'Essay',due:addDays(6),weight:20,status:'pending',score:null,possible:100,notes:'',groupType:'individual',createdAt:addDays(-3),completedAt:null},
{id:12,courseId:5,title:'Presentation',type:'Project',due:addDays(-5),weight:25,status:'done',score:95,possible:100,notes:'',groupType:'group',createdAt:addDays(-20),completedAt:addDays(-5)},
];
let SESSIONS=[
{id:1,courseId:1,date:addDays(-1),planned:25,actual:25,label:'Focus session'},
{id:2,courseId:2,date:addDays(-2),planned:50,actual:48,label:'Deep work'},
{id:3,courseId:3,date:addDays(-2),planned:25,actual:25,label:'Focus session'},
{id:4,courseId:4,date:addDays(-3),planned:25,actual:22,label:'Focus session'},
{id:5,courseId:1,date:addDays(-4),planned:50,actual:50,label:'Deep work'},
{id:6,courseId:2,date:addDays(-5),planned:50,actual:45,label:'Deep work'},
];
let INSIGHTS=[
{id:1,type:'flag',severity:'alert',courseId:2,dismissed:false,title:'Grade at risk in Organic Chemistry',desc:'Projected grade 64.2% — below passing threshold. Final exam worth 35%, need 82%+ to pass.'},
{id:2,type:'flag',severity:'alert',courseId:1,dismissed:false,title:'"Homework 3" is overdue',desc:'STAT 301 HW3 (10%) due 3 days ago. Submit ASAP — late credit beats zero.'},
{id:3,type:'flag',severity:'warn',courseId:4,dismissed:false,title:'Statistics grade below target',desc:'13.6 points below 85% target in STAT 301. Need 93.2% average on remaining work.'},
{id:4,type:'suggestion',severity:'info',courseId:2,dismissed:false,title:'Increase study time for Chemistry',desc:'Averaging 2.6 hrs/week for a difficulty-5 course. Aim for 4–5 hrs.'},
{id:5,type:'prediction',severity:'info',courseId:5,dismissed:false,title:'On track to exceed target in Technical Writing',desc:'Current 91.3% vs 88% target. Maintain consistency.'},
];
let TASKS=[
{id:1,title:'Email Prof. Martinez about HW3 extension',due:addDays(1),priority:'high',courseId:1,status:'pending',notes:'',createdAt:addDays(-1),completedAt:null},
{id:2,title:'Order Organic Chemistry textbook',due:null,priority:'high',courseId:2,status:'pending',notes:'ISBN 978-0199270293',createdAt:addDays(-3),completedAt:null},
{id:3,title:'Form study group for Linear Algebra',due:addDays(5),priority:'medium',courseId:4,status:'pending',notes:'',createdAt:addDays(-2),completedAt:null},
{id:4,title:'Back up all class notes to cloud',due:null,priority:'low',courseId:null,status:'done',notes:'',createdAt:addDays(-10),completedAt:addDays(-2)},
];
let RESOURCES=[
{id:1,title:'Organic Chemistry by Clayden',type:'Book',status:'needed',url:'',courseId:2,assignmentId:null,notes:'~$80 new'},
{id:2,title:'Khan Academy – Statistics',type:'Link',status:'obtained',url:'https://khanacademy.org/statistics',courseId:1,assignmentId:null,notes:'Free'},
{id:3,title:'Wolfram Alpha',type:'Tool',status:'obtained',url:'https://wolframalpha.com',courseId:4,assignmentId:null,notes:'Check algebra'},
{id:4,title:'HIST 210 Course Reader',type:'Book',status:'obtained',url:'',courseId:3,assignmentId:null,notes:'Library reserve'},
];
// ══════════════════════════════════════════════════════
// HELPERS
// ══════════════════════════════════════════════════════
const getCourse=id=>COURSES.find(c=>c.id===id);
const gradeColor=g=>!g&&g!==0?'var(--text2)':g>=90?'var(--green)':g>=80?'var(--teal)':g>=70?'var(--amber)':'var(--red)';
const isOverdue=a=>a.status!=='done'&&new Date(a.due)<today;
function computeGrade(courseId){
const graded=ASSIGNMENTS.filter(a=>a.courseId===courseId&&a.score!==null&&a.status==='done');
if(!graded.length)return null;
const tw=graded.reduce((s,a)=>s+a.weight,0);
const ws=graded.reduce((s,a)=>s+(a.score/a.possible)*a.weight,0);
return tw>0?+(ws/tw*100).toFixed(1):null;
}
function dueDateLabel(dateStr){
const d=new Date(dateStr),diff=Math.round((d-today)/86400000);
if(diff<-1)return`<span style="color:var(--red)">${Math.abs(diff)}d overdue</span>`;
if(diff===-1)return`<span style="color:var(--red)">Yesterday</span>`;
if(diff===0)return`<span style="color:var(--amber)">Today</span>`;
if(diff===1)return`<span style="color:var(--amber)">Tomorrow</span>`;
if(diff<=7)return`<span style="color:var(--amber)">In ${diff}d</span>`;
return`<span style="color:var(--text2)">${d.toLocaleDateString('en-US',{month:'short',day:'numeric'})}</span>`;
}
function typeBadgeStyle(t){return({Exam:'background:var(--red-bg);color:var(--red)',Essay:'background:var(--purple-bg);color:var(--purple)',Homework:'background:var(--accent-bg);color:var(--accent)',Quiz:'background:var(--amber-bg);color:var(--amber)',Project:'background:var(--teal-bg);color:var(--teal)',Lab:'background:var(--green-bg);color:var(--green)'})[t]||'background:var(--bg4);color:var(--text2)';}
function activeInsightCount(){return INSIGHTS.filter(i=>!i.dismissed).length;}
function updateBadge(){const n=activeInsightCount(),b=document.getElementById('insights-badge');b.textContent=n;b.style.display=n?'':'none';}
// ══════════════════════════════════════════════════════
// NAV
// ══════════════════════════════════════════════════════
function nav(page,opts={}){
document.querySelectorAll('.page').forEach(p=>p.classList.remove('active'));
document.querySelectorAll('.nav-item').forEach(n=>n.classList.remove('active'));
document.getElementById('page-'+page).classList.add('active');
document.querySelectorAll('.nav-item').forEach(n=>{if(n.getAttribute('onclick')===`nav('${page}')`)n.classList.add('active');});
if(opts.filter)activeFilter=opts.filter;
({dashboard:renderDashboard,programs:renderPrograms,courses:renderCourses,assignments:renderAssignments,timer:renderTimer,tasks:renderTasks,resources:renderResources,chat:initChat,contacts:renderContacts,insights:renderInsights})[page]?.();
}
// ══════════════════════════════════════════════════════
// DASHBOARD
// ══════════════════════════════════════════════════════
function renderDashboard(){
const overdueList=ASSIGNMENTS.filter(isOverdue);
const dueSoon=ASSIGNMENTS.filter(a=>{const d=(new Date(a.due)-today)/86400000;return d>=0&&d<=7&&a.status!=='done';});
const flags=activeInsightCount();
const totalMins=SESSIONS.reduce((s,x)=>s+x.actual,0);
const hrsThisWeek=(totalMins/60).toFixed(1);
// Stat cards — only show enabled widgets
const cards=[];
if(SETTINGS.widgets.gpa)cards.push(`<div class="stat-card"><div class="stat-label">Current GPA</div><div class="stat-value" style="color:var(--green)">3.4</div><div class="stat-change up">↑ 0.2 · target ${SETTINGS.gpaTarget}</div></div>`);
if(SETTINGS.widgets.studyhrs)cards.push(`<div class="stat-card"><div class="stat-label">Study Hrs / Week</div><div class="stat-value">${hrsThisWeek}</div><div class="stat-change">Goal: ${SETTINGS.studyGoal} hrs</div></div>`);
if(SETTINGS.widgets.assignments)cards.push(`<div class="stat-card clickable" onclick="nav('assignments',{filter:'overdue'})" title="View overdue assignments"><div class="stat-label">Assignments Due</div><div class="stat-value" style="color:${overdueList.length>0?'var(--red)':dueSoon.length>0?'var(--amber)':'var(--green)'}">${overdueList.length+dueSoon.length}</div><div class="stat-change ${overdueList.length?'down':''}">${overdueList.length} overdue — click to view</div><div class="stat-hint">↗ Opens Assignments · Overdue tab</div></div>`);
if(SETTINGS.widgets.flags)cards.push(`<div class="stat-card clickable" onclick="nav('insights')" title="View flags"><div class="stat-label">Active Flags</div><div class="stat-value" style="color:${flags>0?'var(--red)':'var(--green)'}">${flags}</div><div class="stat-change">${flags>0?'Click to view':'All clear 🎉'}</div><div class="stat-hint">${flags>0?'↗ Opens Insights':''}</div></div>`);
const colCount=cards.length||1;
const gridCols=colCount>=4?'grid-4':colCount===3?'grid-3':'grid-2';
document.getElementById('dash-stats-row').innerHTML=`<div class="${gridCols}">${cards.join('')}</div>`;
document.getElementById('dash-courses').innerHTML=COURSES.map(c=>{
const g=computeGrade(c.id);
return`<div class="course-card" onclick="nav('courses')" style="margin-bottom:8px">
<div style="display:flex;align-items:flex-start;justify-content:space-between;margin-bottom:8px">
<div><span style="display:inline-block;width:6px;height:6px;border-radius:50%;background:${c.color};margin-right:5px"></span><span class="course-name">${c.name}</span><div class="course-code">${c.code}</div></div>
<div class="course-grade" style="color:${gradeColor(g)}">${g!==null?g+'%':'—'}</div>
</div>
<div class="progress-bar-wrap"><div class="progress-bar" style="width:${g||0}%;background:${c.color}"></div></div>
<div class="course-meta"><span>Target: ${c.target}%</span><span>${g!==null?(g>=c.target?'✓ On track':`${(c.target-g).toFixed(1)}% below`):'No grades yet'}</span></div>
</div>`;
}).join('');
document.getElementById('dash-flags').innerHTML=INSIGHTS.filter(i=>!i.dismissed&&i.severity!=='info').slice(0,3).map(i=>`
<div class="flag-card ${i.severity}" id="dflag-${i.id}">
<span class="flag-icon">${i.severity==='alert'?'🚨':'⚠️'}</span>
<span class="flag-text">${i.desc}</span>
<button class="flag-dismiss" onclick="dismissInsight(${i.id})" title="Dismiss alert only">×</button>
</div>`).join('')||'<div style="font-size:11px;color:var(--text3);padding:4px 0">No active flags 🎉</div>';
const up=[...ASSIGNMENTS].filter(a=>a.status!=='done').sort((a,b)=>new Date(a.due)-new Date(b.due)).slice(0,5);
document.getElementById('dash-upcoming').innerHTML=up.length?up.map(a=>{
const c=getCourse(a.courseId),urgency=(new Date(a.due)-today)/86400000;
return`<div style="display:flex;align-items:center;gap:9px;padding:7px 0;border-bottom:1px solid var(--border)">
<div style="width:6px;height:6px;border-radius:50%;flex-shrink:0;background:${isOverdue(a)?'var(--red)':urgency<=3?'var(--amber)':'var(--text3)'}"></div>
<div style="flex:1;min-width:0"><div style="font-size:12px;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis">${a.title}</div><div style="font-size:9px;color:var(--text3)">${c?.code}</div></div>
<div style="font-size:10px">${dueDateLabel(a.due)}</div>
<span style="font-size:9px;color:var(--text3)">${a.weight}%</span>
</div>`;
}).join('')+'<div style="height:2px"></div>':'<div class="empty-state" style="padding:20px"><div class="ei">✅</div><p>Nothing due soon!</p></div>';
// Reports widget
if(SETTINGS.widgets.reports){
const doneTasks=TASKS.filter(t=>t.status==='done'&&t.completedAt);
const doneAsgn=ASSIGNMENTS.filter(a=>a.status==='done'&&a.completedAt);
const avgTaskDays=doneTasks.length?Math.round(doneTasks.reduce((s,t)=>s+Math.max(0,(new Date(t.completedAt)-new Date(t.createdAt))/86400000),0)/doneTasks.length*10)/10:null;
const avgAsgnDays=doneAsgn.length?Math.round(doneAsgn.reduce((s,a)=>s+Math.max(0,(new Date(a.completedAt)-new Date(a.createdAt))/86400000),0)/doneAsgn.length*10)/10:null;
document.getElementById('dash-reports').innerHTML=`
<div class="section-header"><div class="section-title">📊 Activity Reports</div></div>
<div class="grid-3">
<div class="dashboard-report-card">
<div class="section-title" style="margin-bottom:8px">Assignments</div>
<div class="report-row"><span class="report-label">Completed</span><span class="report-val">${doneAsgn.length}</span></div>
<div class="report-row"><span class="report-label">Pending</span><span class="report-val">${ASSIGNMENTS.filter(a=>a.status==='pending'&&!isOverdue(a)).length}</span></div>
<div class="report-row"><span class="report-label">Overdue</span><span class="report-val" style="color:var(--red)">${ASSIGNMENTS.filter(isOverdue).length}</span></div>
${avgAsgnDays!==null?`<div class="report-row"><span class="report-label">Avg completion</span><span class="report-val">${avgAsgnDays}d</span></div>`:''}
</div>
<div class="dashboard-report-card">
<div class="section-title" style="margin-bottom:8px">Tasks</div>
<div class="report-row"><span class="report-label">Completed</span><span class="report-val">${TASKS.filter(t=>t.status==='done').length}</span></div>
<div class="report-row"><span class="report-label">Pending</span><span class="report-val">${TASKS.filter(t=>t.status==='pending').length}</span></div>
<div class="report-row"><span class="report-label">High priority</span><span class="report-val" style="color:var(--red)">${TASKS.filter(t=>t.priority==='high'&&t.status==='pending').length}</span></div>
${avgTaskDays!==null?`<div class="report-row"><span class="report-label">Avg completion</span><span class="report-val">${avgTaskDays}d</span></div>`:''}
</div>
<div class="dashboard-report-card">
<div class="section-title" style="margin-bottom:8px">Study Sessions</div>
<div class="report-row"><span class="report-label">This week</span><span class="report-val">${SESSIONS.length}</span></div>
<div class="report-row"><span class="report-label">Total mins</span><span class="report-val">${totalMins}</span></div>
<div class="report-row"><span class="report-label">Goal progress</span><span class="report-val" style="color:${parseFloat(hrsThisWeek)>=SETTINGS.studyGoal?'var(--green)':'var(--amber)'}">${Math.round(parseFloat(hrsThisWeek)/SETTINGS.studyGoal*100)}%</span></div>
</div>
</div>`;
} else { document.getElementById('dash-reports').innerHTML=''; }
}
// ══════════════════════════════════════════════════════
// PROGRAMS
// ══════════════════════════════════════════════════════
function renderPrograms(){
document.getElementById('programs-grid').innerHTML=PROGRAMS.length?PROGRAMS.map(p=>{
const courses=COURSES.filter(c=>c.programId===p.id);
const tasks=PROGRAM_TASKS.filter(t=>t.programId===p.id);
const done=tasks.filter(t=>t.done).length;
return`<div class="program-card">
<div class="program-header">
<div class="program-badge" style="background:${p.color}22;color:${p.color}">${p.name.split(' ').map(w=>w[0]).join('').slice(0,3).toUpperCase()}</div>
<div style="flex:1"><div style="font-weight:600;font-size:13.5px">${p.name}</div><div style="font-size:10px;color:var(--text3)">${p.institution} · ${p.type} · ${p.start}–${p.end}</div></div>
<button class="btn-icon" onclick="openEditProgram(${p.id})" title="Edit">✎</button>
</div>
<div class="program-body">
<div class="program-stat-row">
<div class="program-stat"><div class="course-stat-label">Courses</div><div class="course-stat-val">${courses.length}</div></div>
<div class="program-stat"><div class="course-stat-label">Tasks</div><div class="course-stat-val">${done}/${tasks.length}</div></div>
</div>
<div style="margin-bottom:10px">
<div style="font-size:10px;color:var(--text3);margin-bottom:6px;font-weight:600;letter-spacing:0.07em;text-transform:uppercase">Program Tasks</div>
${tasks.map(t=>`<div style="display:flex;align-items:center;gap:7px;padding:4px 0;font-size:12px;border-bottom:1px solid var(--border)">
<div style="width:13px;height:13px;border-radius:50%;border:2px solid ${t.done?'var(--green)':'var(--border2)'};background:${t.done?'var(--green)':'transparent'};flex-shrink:0;display:flex;align-items:center;justify-content:center;font-size:7px;color:#fff">${t.done?'✓':''}</div>
<span style="${t.done?'text-decoration:line-through;color:var(--text3)':''}">${t.title}</span>
</div>`).join('')}
</div>
<div style="display:flex;gap:6px;flex-wrap:wrap">
<button class="btn btn-ghost btn-sm" onclick="openCourseModal(null,${p.id})">+ Course</button>
<button class="btn btn-ghost btn-sm" onclick="addProgramTask(${p.id})">+ Task</button>
</div>
</div>
</div>`;
}).join(''):`<div class="empty-state" style="grid-column:1/-1"><div class="ei">🎓</div><p>No programs yet. Add your degree, certificate, or short course.</p></div>`;
}
// ══════════════════════════════════════════════════════
// COURSES
// ══════════════════════════════════════════════════════
function renderCourses(){
const credits=COURSES.reduce((s,c)=>s+c.credits,0);
document.getElementById('courses-sub').textContent=`${COURSES.length} courses · ${credits} credit hours`;
document.getElementById('courses-grid').innerHTML=COURSES.map(c=>{
const g=computeGrade(c.id),done=ASSIGNMENTS.filter(a=>a.courseId===c.id&&a.status==='done').length;
const total=ASSIGNMENTS.filter(a=>a.courseId===c.id).length,overdue=ASSIGNMENTS.filter(a=>a.courseId===c.id&&isOverdue(a)).length;
const sess=SESSIONS.filter(s=>s.courseId===c.id),avgMins=sess.length?Math.round(sess.reduce((s,x)=>s+x.actual,0)/sess.length):null;
const abbr=c.name.split(' ').filter(w=>w.length>3).slice(0,2).map(w=>w[0]).join('').toUpperCase()||c.code.slice(0,2);
const prog=PROGRAMS.find(p=>p.id===c.programId);
return`<div class="course-full-card">
<div class="course-full-header">
<div class="course-full-color" style="background:${c.color}22;color:${c.color}">${abbr}</div>
<div style="flex:1">
<div style="font-weight:600;font-size:13px">${c.name}</div>
<div style="font-size:9px;color:var(--text3);font-family:var(--mono)">${c.code} · ${c.credits}cr · ${c.term} ${c.year}${prog?` · <span style="color:${prog.color}">${prog.name}</span>`:''}</div>
${c.professor?`<div style="font-size:9px;color:var(--text3);margin-top:1px">👤 ${c.professor}${c.ta?' · TA: '+c.ta:''}</div>`:''}
</div>
<div style="display:flex;align-items:center;gap:6px">
<div style="font-family:var(--display);font-size:19px;color:${gradeColor(g)}">${g!==null?g+'%':'—'}</div>
<button class="btn-icon" onclick="openEditCourse(${c.id})" title="Edit">✎</button>
</div>
</div>
<div class="course-full-body">
<div class="grade-bar-wrap"><div class="progress-bar" style="width:${g||0}%;background:${c.color}"></div></div>
<div style="display:flex;justify-content:space-between;font-size:9px;color:var(--text3);margin-bottom:10px"><span>Graded: ${g!==null?g+'%':'—'}</span><span>Target: ${c.target}%</span></div>
<div class="course-stat-row">
<div class="program-stat"><div class="course-stat-label">Tasks</div><div class="course-stat-val">${done}/${total}</div></div>
<div class="program-stat"><div class="course-stat-label">Avg session</div><div class="course-stat-val">${avgMins?avgMins+'m':'—'}</div></div>
<div class="program-stat"><div class="course-stat-label">Overdue</div><div class="course-stat-val" style="color:${overdue?'var(--red)':'var(--green)'}">${overdue}</div></div>
</div>
<div style="display:flex;gap:5px;flex-wrap:wrap">
<button class="btn btn-ghost btn-sm" onclick="openAssignmentModal(${c.id})">+ Assignment</button>
<button class="btn btn-ghost btn-sm" onclick="askLyraAbout('${c.name}')">Ask Lyra</button>
</div>
</div>
</div>`;
}).join('')||'<div class="empty-state" style="grid-column:1/-1"><div class="ei">📚</div><p>No courses yet!</p></div>';
}
// ══════════════════════════════════════════════════════
// ASSIGNMENTS — overdue is purely computed
// ══════════════════════════════════════════════════════
function renderAssignments(){
let list=[...ASSIGNMENTS];
if(activeFilter==='pending')list=list.filter(a=>a.status==='pending'&&!isOverdue(a));
if(activeFilter==='done')list=list.filter(a=>a.status==='done');
if(activeFilter==='overdue')list=list.filter(a=>isOverdue(a));
list.sort((a,b)=>new Date(a.due)-new Date(b.due));
const statS={done:'background:var(--green-bg);color:var(--green)',pending:'background:var(--bg4);color:var(--text2)'};
const statL={done:'✓ Done',pending:'● Pending'};
document.getElementById('asgn-tbody').innerHTML=list.map(a=>{
const c=getCourse(a.courseId),ov=isOverdue(a);
const sStyle=ov?'background:var(--red-bg);color:var(--red)':statS[a.status]||'';
const sLabel=ov?'⚠ Overdue':statL[a.status]||a.status;
const scoreStr=a.score!==null?`<span style="color:${gradeColor(a.score/a.possible*100)}">${a.score}/${a.possible}</span>`:'<span style="color:var(--text3)">—</span>';
const duration=a.completedAt&&a.createdAt?fmtDuration(Math.max(0,(new Date(a.completedAt)-new Date(a.createdAt))/60000)):null;
const gtBadge=a.groupType==='group'?`<span style="font-size:8px;color:var(--teal);margin-left:4px">👥</span>`:'';
return`<tr>
<td><div style="font-weight:500">${a.title}${gtBadge}</div></td>
<td><span style="display:inline-flex;align-items:center;gap:4px"><span style="width:5px;height:5px;border-radius:50%;background:${c?.color};display:inline-block"></span>${c?.code}</span></td>
<td><span class="type-badge" style="${typeBadgeStyle(a.type)}">${a.type}</span></td>
<td>${dueDateLabel(a.due)}</td>
<td><span style="font-family:var(--mono);color:var(--text2)">${a.weight}%</span></td>
<td>${scoreStr}</td>
<td><span class="status-chip" style="${sStyle}">${sLabel}</span></td>
<td>${duration?`<span class="task-duration">${duration}</span>`:'<span style="color:var(--text3);font-size:9px">—</span>'}</td>
<td><div class="row-actions">
${a.status!=='done'?`<button class="btn-icon btn-success" onclick="markAsgnDone(${a.id})" title="Mark complete" style="color:var(--green);border-color:rgba(74,222,128,0.3)">✓</button>`:''}
<button class="btn-icon" onclick="openExtendModal(${a.id})" title="Extend due date">📅</button>
<button class="btn-icon" onclick="openEditAssignment(${a.id})" title="Edit">✎</button>
<button class="btn-icon" onclick="confirmDeleteAssignmentById(${a.id})" title="Delete" style="color:var(--red)">✕</button>
</div></td>
</tr>`;
}).join('')||`<tr><td colspan="9"><div class="empty-state"><div class="ei">📋</div><p>${activeFilter==='overdue'?'No overdue assignments 🎉':'No assignments found'}</p></div></td></tr>`;
document.querySelectorAll('.filter-btn').forEach(b=>b.classList.toggle('active',b.dataset.filter===activeFilter));
}
document.getElementById('filter-bar').addEventListener('click',e=>{const b=e.target.closest('.filter-btn');if(b){activeFilter=b.dataset.filter;renderAssignments();}});
function markAsgnDone(id){
const a=ASSIGNMENTS.find(x=>x.id===id);if(!a)return;
a.status='done';a.completedAt=fmt(today);
renderAssignments();renderDashboard();renderCourses();
}
function openExtendModal(id){
const a=ASSIGNMENTS.find(x=>x.id===id);if(!a)return;
document.getElementById('extend-id').value=id;
document.getElementById('extend-title-display').textContent=a.title;
document.getElementById('extend-date').value=a.due;
document.getElementById('extend-reason').value='';
openModal('extend');
}
function saveExtend(){
const id=parseInt(document.getElementById('extend-id').value);
const nd=document.getElementById('extend-date').value;if(!nd)return;
const a=ASSIGNMENTS.find(x=>x.id===id);if(!a)return;
a.due=nd;if(a.status!=='done')a.status='pending';
closeModal('extend');renderAssignments();renderDashboard();
}
// ══════════════════════════════════════════════════════
// TIMER
// ══════════════════════════════════════════════════════
function loadTimerAssignments(){
const cid=parseInt(document.getElementById('timer-course-select').value)||null;
const sel=document.getElementById('timer-asgn-select');
const asgns=cid?ASSIGNMENTS.filter(a=>a.courseId===cid&&a.status!=='done'):[];
sel.innerHTML='<option value="">Link assignment...</option>'+asgns.map(a=>`<option value="${a.id}">${a.title}</option>`).join('');
}
function renderTimer(){
const sel=document.getElementById('timer-course-select');
sel.innerHTML='<option value="">Select course...</option>'+COURSES.map(c=>`<option value="${c.id}">${c.name}</option>`).join('');
loadTimerAssignments();
renderSessionLog();
const totalMins=SESSIONS.reduce((s,x)=>s+x.actual,0);
document.getElementById('timer-stats').innerHTML=`
<div class="stat-card"><div class="stat-label">Total This Week</div><div class="stat-value" style="font-size:19px">${(totalMins/60).toFixed(1)}h</div></div>
<div class="stat-card"><div class="stat-label">Sessions</div><div class="stat-value" style="font-size:19px">${SESSIONS.length}</div></div>`;
updateTimerDisplay();
}
function renderSessionLog(){
document.getElementById('session-log-list').innerHTML=[...SESSIONS].reverse().slice(0,8).map(s=>{
const c=getCourse(s.courseId);
return`<div class="session-row"><div class="session-dot" style="background:${c?.color||'var(--text3)'}"></div><span style="flex:1;font-weight:500">${c?.code||'—'}</span><span style="color:var(--text2);font-family:var(--mono)">${s.actual}m</span><span style="color:var(--text3)">${s.label}</span><span style="color:var(--text3)">${new Date(s.date).toLocaleDateString('en-US',{month:'short',day:'numeric'})}</span></div>`;
}).join('')||'<div style="font-size:11px;color:var(--text3);padding:6px 0">No sessions yet</div>';
}
function setTimer(mins,label){timerSeconds=mins*60;timerTotal=timerSeconds;timerMode=label;clearInterval(timerInterval);timerRunning=false;timerStartTime=null;document.getElementById('timer-toggle').textContent='▶';updateTimerDisplay();}
function setCustomTimer(){const m=parseInt(document.getElementById('custom-mins').value);const l=document.getElementById('custom-label-inp').value.trim()||'Custom session';if(!m||m<1)return;setTimer(m,l);}
function updateTimerDisplay(){
const m=Math.floor(timerSeconds/60).toString().padStart(2,'0'),s=(timerSeconds%60).toString().padStart(2,'0');
document.getElementById('timer-display').textContent=`${m}:${s}`;
document.getElementById('timer-label').textContent=timerMode;
const circ=2*Math.PI*83,offset=circ*(1-timerSeconds/timerTotal);
const fg=document.getElementById('timer-ring-fg');
if(fg){fg.style.strokeDashoffset=offset;fg.style.stroke=timerSeconds/timerTotal<0.25?'var(--amber)':'var(--accent)';}
}
function toggleTimer(){
if(timerRunning){clearInterval(timerInterval);timerRunning=false;document.getElementById('timer-toggle').textContent='▶';}
else{if(!timerStartTime)timerStartTime=Date.now();timerRunning=true;document.getElementById('timer-toggle').textContent='⏸';timerInterval=setInterval(()=>{if(timerSeconds>0){timerSeconds--;updateTimerDisplay();}else{onTimerComplete();}},1000);}
}
function onTimerComplete(){
clearInterval(timerInterval);timerRunning=false;
document.getElementById('timer-toggle').textContent='▶';
document.getElementById('timer-label').textContent='Session complete! 🎉';
if(document.getElementById('timer-sound-on').checked)playBeep();
const courseId=parseInt(document.getElementById('timer-course-select').value)||null;
const label=document.getElementById('timer-session-label').value.trim()||timerMode;
SESSIONS.push({id:Date.now(),courseId,date:fmt(today),planned:Math.round(timerTotal/60),actual:Math.round(timerTotal/60),label});
renderSessionLog();
const totalMins=SESSIONS.reduce((s,x)=>s+x.actual,0);
document.getElementById('timer-stats').innerHTML=`<div class="stat-card"><div class="stat-label">Total This Week</div><div class="stat-value" style="font-size:19px">${(totalMins/60).toFixed(1)}h</div></div><div class="stat-card"><div class="stat-label">Sessions</div><div class="stat-value" style="font-size:19px">${SESSIONS.length}</div></div>`;
timerStartTime=null;
}
function playBeep(){
try{const ctx=new(window.AudioContext||window.webkitAudioContext)();const pl=(f,t,d)=>{const o=ctx.createOscillator(),g=ctx.createGain();o.connect(g);g.connect(ctx.destination);o.type='sine';o.frequency.value=f;g.gain.setValueAtTime(0.35,t);g.gain.exponentialRampToValueAtTime(0.001,t+d);o.start(t);o.stop(t+d);};const t=ctx.currentTime;pl(880,t,.15);pl(1100,t+.18,.15);pl(1320,t+.36,.25);}catch(e){}
}
function resetTimer(){clearInterval(timerInterval);timerRunning=false;timerSeconds=timerTotal;timerStartTime=null;document.getElementById('timer-toggle').textContent='▶';updateTimerDisplay();}
function skipTimer(){if(!timerRunning&&timerSeconds===timerTotal)return;onTimerComplete();}
// ══════════════════════════════════════════════════════
// TASKS — completed in separate section, with duration
// ══════════════════════════════════════════════════════
function renderTasks(){
const showCompleted=document.getElementById('show-completed-toggle').checked;
let pending=[...TASKS];
if(activeTaskFilter==='pending')pending=pending.filter(t=>t.status==='pending');
if(activeTaskFilter==='high')pending=pending.filter(t=>t.priority==='high'&&t.status==='pending');
pending=pending.filter(t=>t.status!=='done');
pending.sort((a,b)=>{const pd={high:0,medium:1,low:2};return pd[a.priority]-pd[b.priority];});
const completed=TASKS.filter(t=>t.status==='done');
const priorColor={high:'var(--red)',medium:'var(--amber)',low:'var(--text3)'};
const taskRow=t=>{
const c=getCourse(t.courseId);
const duration=t.completedAt&&t.createdAt?fmtDuration(Math.max(0,(new Date(t.completedAt)-new Date(t.createdAt))/60000)):null;
return`<div class="task-row ${t.status==='done'?'completed-row':''}">
<div class="task-check ${t.status==='done'?'done':''}" onclick="toggleTask(${t.id})"></div>
<div class="priority-dot" style="background:${priorColor[t.priority]}"></div>
<div class="task-info">
<div class="task-title-txt ${t.status==='done'?'task-done-txt':''}">${t.title}</div>
<div class="task-sub">${c?`<span style="color:${c.color}">${c.code}</span> · `:''}${t.due?dueDateLabel(t.due):'No due date'}${t.notes?` · ${t.notes}`:''}</div>
</div>
${duration?`<span class="task-duration">⏱ ${duration}</span>`:''}
<div class="row-actions">
<button class="btn-icon" onclick="openEditTask(${t.id})" title="Edit">✎</button>
<button class="btn-icon" onclick="confirmDeleteTaskById(${t.id})" title="Delete" style="color:var(--red)">✕</button>
</div>
</div>`;
};
let html=pending.map(taskRow).join('');
if(!pending.length)html='<div class="empty-state" style="padding:20px"><div class="ei">✅</div><p>No tasks here!</p></div>';
if(showCompleted&&completed.length){
html+=`<div class="completed-section-header"><span>✓ Completed (${completed.length})</span><button class="btn btn-ghost btn-sm" onclick="if(confirm('Clear all completed tasks?'))clearCompleted()">Clear all</button></div>`;
html+=completed.map(taskRow).join('');
}
document.getElementById('task-list').innerHTML=html;
document.querySelectorAll('.task-filter-btn').forEach(b=>b.classList.toggle('active',b.dataset.tf===activeTaskFilter));
}
function clearCompleted(){TASKS=TASKS.filter(t=>t.status!=='done');renderTasks();}
document.getElementById('task-filter-bar').addEventListener('click',e=>{const b=e.target.closest('.task-filter-btn');if(b){activeTaskFilter=b.dataset.tf;renderTasks();}});
function toggleTask(id){
const t=TASKS.find(x=>x.id===id);if(!t)return;
t.status=t.status==='done'?'pending':'done';
if(t.status==='done')t.completedAt=fmt(today);
else t.completedAt=null;
renderTasks();renderDashboard();
}
// ══════════════════════════════════════════════════════
// RESOURCES — obtained in separate section
// Course→Assignment cascade
// ══════════════════════════════════════════════════════
const resTypeIcon={Book:'📚',Link:'🔗',Tool:'🛠️',Note:'📝',Video:'🎬',Other:'📦'};
const resStatusStyle={needed:'background:var(--red-bg);color:var(--red)',pending:'background:var(--amber-bg);color:var(--amber)',obtained:'background:var(--green-bg);color:var(--green)'};
const resStatusLabel={needed:'Needed',pending:'Ordered',obtained:'Obtained'};
function renderResources(){
let list=[...RESOURCES];
if(activeResFilter==='needed')list=list.filter(r=>r.status==='needed');
else if(activeResFilter!=='all')list=list.filter(r=>r.type===activeResFilter);
const notObtained=list.filter(r=>r.status!=='obtained');
const obtained=list.filter(r=>r.status==='obtained');
const resCard=r=>{
const c=getCourse(r.courseId),a=r.assignmentId?ASSIGNMENTS.find(x=>x.id===r.assignmentId):null;
return`<div class="resource-card">
<div class="resource-icon" style="background:var(--bg3)">${resTypeIcon[r.type]||'📦'}</div>
<div class="resource-body">
<div class="resource-title">${r.title}</div>
<div class="resource-meta">${c?`<span style="display:inline-flex;align-items:center;gap:3px;margin-right:7px"><span style="width:5px;height:5px;border-radius:50%;background:${c.color};display:inline-block"></span>${c.code}</span>`:''}${a?`<span style="color:var(--text3);margin-right:7px">→ ${a.title}</span>`:''}${r.url?`<a class="resource-link" href="${r.url}" target="_blank" rel="noopener">🔗 Open</a>`:''}</div>
${r.notes?`<div style="font-size:10px;color:var(--text3);margin-top:2px">${r.notes}</div>`:''}
<div style="margin-top:7px;display:flex;align-items:center;gap:7px;flex-wrap:wrap">
<span class="resource-status" style="${resStatusStyle[r.status]||''}">${resStatusLabel[r.status]||r.status}</span>
<button class="btn btn-ghost btn-sm" onclick="cycleResourceStatus(${r.id})">Change status</button>
<button class="btn-icon" onclick="openEditResource(${r.id})" title="Edit">✎</button>
<button class="btn-icon" onclick="confirmDeleteResourceById(${r.id})" title="Delete" style="color:var(--red)">✕</button>
</div>
</div>
</div>`;
};
document.getElementById('resources-needed-list').innerHTML=notObtained.length?notObtained.map(resCard).join(''):'<div class="empty-state"><div class="ei">📚</div><p>No resources here. Add a textbook, link, or tool!</p></div>';
if(obtained.length){
document.getElementById('resources-obtained-section').style.display='block';
document.getElementById('resources-obtained-list').innerHTML=obtained.map(resCard).join('');
} else { document.getElementById('resources-obtained-section').style.display='none'; }
document.querySelectorAll('.res-filter-btn').forEach(b=>b.classList.toggle('active',b.dataset.rf===activeResFilter));
}
document.getElementById('res-filter-bar').addEventListener('click',e=>{const b=e.target.closest('.res-filter-btn');if(b){activeResFilter=b.dataset.rf;renderResources();}});
function cycleResourceStatus(id){const r=RESOURCES.find(x=>x.id===id);if(!r)return;r.status={needed:'pending',pending:'obtained',obtained:'needed'}[r.status]||'needed';renderResources();}
function loadResAssignments(){
const cid=parseInt(document.getElementById('fr-course').value)||null;
const sel=document.getElementById('fr-assignment');
const asgns=cid?ASSIGNMENTS.filter(a=>a.courseId===cid):[];
sel.innerHTML='<option value="">— None —</option>'+asgns.map(a=>`<option value="${a.id}">${a.title}</option>`).join('');
}
// ══════════════════════════════════════════════════════
// CONTACTS
// ══════════════════════════════════════════════════════
const roleColors={professor:'background:var(--accent-bg);color:var(--accent)',ta:'background:var(--teal-bg);color:var(--teal)',member:'background:var(--amber-bg);color:var(--amber)',other:'background:var(--bg4);color:var(--text2)'};
const roleLabels={professor:'Professor',ta:'TA',member:'Group Member',other:'Other'};
function renderContacts(){
let list=[...CONTACTS];
if(activeContactFilter!=='all')list=list.filter(c=>c.role===activeContactFilter);
document.getElementById('contacts-list').innerHTML=list.length?list.map(c=>{
const initials=c.name.split(' ').slice(0,2).map(w=>w[0]).join('').toUpperCase();
const hue={professor:240,ta:180,member:40,other:0}[c.role]||200;
return`<div class="contact-card">
<div class="contact-avatar" style="background:hsl(${hue},60%,${document.documentElement.dataset.theme==='dark'?'30%':'70%'});color:hsl(${hue},80%,${document.documentElement.dataset.theme==='dark'?'80%':'20%'})">${initials}</div>
<div class="contact-info">
<div class="contact-name">${c.name}</div>
<div class="contact-meta">${c.email||''}${c.notes?' · '+c.notes:''}</div>
</div>
<span class="contact-role-badge" style="${roleColors[c.role]||''}">${roleLabels[c.role]||c.role}</span>
<div class="row-actions" style="opacity:1">
<button class="btn-icon" onclick="openEditContact(${c.id})" title="Edit">✎</button>
<button class="btn-icon" onclick="confirmDeleteContactById(${c.id})" title="Delete" style="color:var(--red)">✕</button>
</div>
</div>`;
}).join(''):'<div class="empty-state"><div class="ei">👥</div><p>No contacts yet</p></div>';
document.querySelectorAll('.contact-filter-btn').forEach(b=>b.classList.toggle('active',b.dataset.cf===activeContactFilter));
}
document.getElementById('contact-filter-bar').addEventListener('click',e=>{const b=e.target.closest('.contact-filter-btn');if(b){activeContactFilter=b.dataset.cf;renderContacts();}});
// ══════════════════════════════════════════════════════
// AI CHAT — Lyra
// ══════════════════════════════════════════════════════
function initChat(){
const c=document.getElementById('chat-messages');
if(c.children.length===0)appendMessage('ai',`Hi Jamie! I'm **Lyra** — your personal academic advisor. 👋
Here's your snapshot for this week:\n\n**📊 STAT 301** — 68% (midterm only). Big 40% final ahead.\n**⚗️ CHEM 202** — Most urgent. At-risk of failing.\n**✅ ENGL 215 & HIST 210** — Both on or above target.\n**🔢 MATH 340** — Small gap vs. target, closeable.\n\n**${ASSIGNMENTS.filter(isOverdue).length} overdue assignments** need attention. What would you like to work through?`);
}
function appendMessage(role,content){
const c=document.getElementById('chat-messages');
const div=document.createElement('div');div.className=`msg ${role==='user'?'user':'ai'}`;
const fmtd=content.replace(/\*\*(.+?)\*\*/g,'<strong>$1</strong>').replace(/\n\n/g,'</p><p>').replace(/\n/g,'<br>');
div.innerHTML=`<div class="msg-avatar ${role==='user'?'user-av':'ai'}">${role==='user'?'JD':'✦'}</div><div><div class="msg-bubble"><p>${fmtd}</p></div><div class="msg-time">${new Date().toLocaleTimeString('en-US',{hour:'2-digit',minute:'2-digit'})}</div></div>`;
c.appendChild(div);c.scrollTop=c.scrollHeight;
}
function showTyping(){const c=document.getElementById('chat-messages');const d=document.createElement('div');d.className='msg ai';d.id='typing-msg';d.innerHTML=`<div class="msg-avatar ai">✦</div><div class="typing-indicator"><div class="typing-dot"></div><div class="typing-dot"></div><div class="typing-dot"></div></div>`;c.appendChild(d);c.scrollTop=c.scrollHeight;}
function hideTyping(){const e=document.getElementById('typing-msg');if(e)e.remove();}
const AI_R={
stat:`Here's your **STAT 301** breakdown:\n\n**Graded:** Midterm (30%) scored 68% → contributes 20.4%\n**Remaining:** HW3 overdue (10%), Final Exam (40%)\n**Projected: ~68%** at current trajectory\n\nTo hit **85%**: need ~96% on Final + full HW3 credit — ambitious but possible.\nMore realistic: **74–78%**.\n\nShall I map out a study plan for the final?`,
final:`**Grade math for STAT 301** (40% final remaining):\n\n| Goal | Final score needed |\n|---|---|\n| 90% (A) | ~99% — unlikely |\n| 85% (B) | ~87% — stretch |\n| 80% (B-) | ~74% — achievable |\n| 75% (C) | ~61% — within reach |\n\nFocus on probability distributions and hypothesis testing.`,
attention:`Priority order right now:\n\n1. 🚨 **CHEM 202** — Below passing, 2 overdue, difficulty-5\n2. ⚠️ **STAT 301** — Below target, big final coming\n3. 📐 **MATH 340** — Minor gap, close with consistency\n4. ✅ **HIST 210 & ENGL 215** — Don't over-invest`,
schedule:`**Suggested schedule this week:**\n\nMon–Tue: CHEM 202 (2h/day) — mechanisms + Lab Report 4\nWed: STAT 301 (2h) + MATH 340 (1h) — HW3 + Problem Set 5\nThu: CHEM quiz prep (1.5h) + ENGL draft (1.5h)\nFri–Weekend: HIST essay outline (2h) + STAT review (1.5h)\n\n**Total: ~16.5 hrs** ↑ from current pace`,
gpa:`**Projected semester GPA at current trajectory:**\n\nSTAT 301 (C) + CHEM 202 (D) + HIST 210 (B+) + MATH 340 (C+) + ENGL 215 (A-)\n\n**Projected: ~2.37** — would pull your cumulative GPA down from 3.4.\n\nChemistry is make-or-break. Getting a C instead of D → ~2.64 semester GPA.`
};
async function sendChat(){
const inp=document.getElementById('chat-input');const msg=inp.value.trim();if(!msg)return;
inp.value='';appendMessage('user',msg);showTyping();
document.getElementById('quick-prompts').style.display='none';
await new Promise(r=>setTimeout(r,700+Math.random()*500));hideTyping();
const l=msg.toLowerCase();
const reply=l.includes('stat')||l.includes('likely')?AI_R.stat:l.includes('final')||l.includes('need')?AI_R.final:l.includes('attention')||l.includes('most')?AI_R.attention:l.includes('schedule')||l.includes('plan')||l.includes('week')?AI_R.schedule:l.includes('gpa')||l.includes('track')?AI_R.gpa:`I've reviewed your data. Most urgent: **CHEM 202** and **STAT 301**.\n\nTry: "Which course needs most attention?" or "Build a study schedule"`;
appendMessage('ai',reply);
}
function sendQuick(el){document.getElementById('chat-input').value=el.textContent;sendChat();}
function handleChatKey(e){if(e.key==='Enter'&&!e.shiftKey){e.preventDefault();sendChat();}}
function clearChat(){document.getElementById('chat-messages').innerHTML='';document.getElementById('quick-prompts').style.display='flex';initChat();}
function askLyraAbout(name){nav('chat');setTimeout(()=>{document.getElementById('chat-input').value=`What's my projected grade in ${name} and how can I improve it?`;document.getElementById('chat-input').focus();},80);}
// ══════════════════════════════════════════════════════
// INSIGHTS
// ══════════════════════════════════════════════════════
function dismissInsight(id){
const i=INSIGHTS.find(x=>x.id===id);if(!i)return;i.dismissed=true;
['dflag-'+id,'insight-'+id].forEach(eid=>{const el=document.getElementById(eid);if(el){el.style.transition='opacity 0.25s';el.style.opacity='0';setTimeout(()=>el.remove(),260);}});
updateBadge();
setTimeout(()=>{renderDashboard();if(document.getElementById('page-insights').classList.contains('active'))renderInsights();},300);
}
function renderInsights(){
const active=INSIGHTS.filter(i=>!i.dismissed);
const sections=[
{label:'🚨 Alerts',items:active.filter(i=>i.severity==='alert'),color:'var(--red)',bg:'var(--red-bg)'},
{label:'⚠️ Warnings',items:active.filter(i=>i.severity==='warn'),color:'var(--amber)',bg:'var(--amber-bg)'},
{label:'💡 Insights',items:active.filter(i=>i.severity==='info'),color:'var(--accent)',bg:'var(--accent-bg)'},
];
document.getElementById('insights-body').innerHTML=sections.map(sec=>`
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:9px"><div class="section-title">${sec.label}</div><span style="font-size:9px;color:var(--text3)">${sec.items.length} item${sec.items.length!==1?'s':''}</span></div>
${sec.items.length?sec.items.map(i=>{const c=getCourse(i.courseId);return`<div class="insight-card" id="insight-${i.id}">
<div class="insight-icon-wrap" style="background:${sec.bg}"><span>${i.severity==='alert'?'🚨':i.severity==='warn'?'⚠️':i.type==='prediction'?'🔮':'💡'}</span></div>
<div style="flex:1"><div class="insight-title">${i.title}</div><div class="insight-desc">${i.desc}</div>
<div class="insight-meta">${c?`<span style="display:inline-flex;align-items:center;gap:3px"><span style="width:5px;height:5px;border-radius:50%;background:${c.color};display:inline-block"></span>${c.code}</span>`:''}<span class="severity-badge" style="background:${sec.bg};color:${sec.color}">${i.severity.toUpperCase()}</span><button class="btn btn-ghost btn-sm" onclick="askLyraAbout('${c?.name||''}')">Ask Lyra →</button><button class="insight-dismiss" onclick="dismissInsight(${i.id})">Dismiss ✕</button></div>
</div>
</div>`;}).join(''):`<div style="font-size:11px;color:var(--text3);padding:5px 0 12px">All clear ✅</div>`}
<div style="height:4px"></div>`).join('');
}
// ══════════════════════════════════════════════════════
// MODALS
// ══════════════════════════════════════════════════════
function openModal(n){document.getElementById('modal-'+n).classList.add('open');}
function closeModal(n){document.getElementById('modal-'+n).classList.remove('open');}
document.querySelectorAll('.modal-overlay').forEach(m=>{m.addEventListener('click',e=>{if(e.target===m)m.classList.remove('open');});});
// Color pickers
document.getElementById('color-picker').addEventListener('click',e=>{const sw=e.target.closest('.color-swatch');if(!sw)return;document.querySelectorAll('#color-picker .color-swatch').forEach(s=>s.classList.remove('selected'));sw.classList.add('selected');selectedColor=sw.dataset.color;});
document.getElementById('prog-color-picker').addEventListener('click',e=>{const sw=e.target.closest('.color-swatch');if(!sw)return;document.querySelectorAll('#prog-color-picker .color-swatch').forEach(s=>s.classList.remove('selected'));sw.classList.add('selected');selectedProgColor=sw.dataset.color;});
// Year dropdowns
(()=>{[['fc-year',2026],['fp-start',2023],['fp-end',2027]].forEach(([id,def])=>{const s=document.getElementById(id);for(let y=2018;y<=2035;y++){const o=document.createElement('option');o.value=y;o.textContent=y;if(y===def)o.selected=true;s.appendChild(o);}});})();
// Autocomplete
function acFilter(type,val){
if(!val||val.length<1){document.getElementById('ac-'+type).style.display='none';return;}
const roles={professor:['professor'],ta:['ta'],member:['professor','ta','member']};
const matches=CONTACTS.filter(c=>(roles[type]||[]).includes(c.role)&&c.name.toLowerCase().includes(val.toLowerCase())).slice(0,6);
const list=document.getElementById('ac-'+type);
if(!matches.length){list.style.display='none';return;}
list.innerHTML=matches.map(c=>`<div class="autocomplete-item" onclick="acSelect('${type}','${c.name}')">${c.name}${c.email?` <span style="color:var(--text3);font-size:9px">${c.email}</span>`:''}</div>`).join('');
list.style.display='block';
}
function acSelect(type,val){if(type==='professor')document.getElementById('fc-professor').value=val;else if(type==='ta')document.getElementById('fc-ta').value=val;document.getElementById('ac-'+type).style.display='none';}
document.addEventListener('click',e=>{if(!e.target.closest('.autocomplete-wrap'))document.querySelectorAll('.autocomplete-list').forEach(l=>l.style.display='none');});
// ══ PROGRAM CRUD ══
function openProgramModal(){
editingProgramId=null;selectedProgColor='#6c8eff';
document.getElementById('prog-modal-title').textContent='Add Program';
document.getElementById('prog-save-btn').textContent='Add Program';
document.getElementById('del-prog-btn').style.display='none';
document.getElementById('ep-id').value='';
['fp-name','fp-institution'].forEach(id=>document.getElementById(id).value='');
document.getElementById('fp-type').value='Degree';
document.getElementById('fp-start').value=2023; document.getElementById('fp-end').value=2027;
document.querySelectorAll('#prog-color-picker .color-swatch').forEach(s=>s.classList.toggle('selected',s.dataset.color===selectedProgColor));
openModal('program');
}
function openEditProgram(id){
const p=PROGRAMS.find(x=>x.id===id);if(!p)return;
editingProgramId=id;selectedProgColor=p.color;
document.getElementById('prog-modal-title').textContent='Edit Program';
document.getElementById('prog-save-btn').textContent='Save';
document.getElementById('del-prog-btn').style.display='inline-flex';
document.getElementById('ep-id').value=id;
document.getElementById('fp-name').value=p.name;
document.getElementById('fp-institution').value=p.institution;
document.getElementById('fp-type').value=p.type;
document.getElementById('fp-start').value=p.start; document.getElementById('fp-end').value=p.end;
document.querySelectorAll('#prog-color-picker .color-swatch').forEach(s=>s.classList.toggle('selected',s.dataset.color===p.color));
openModal('program');
}
function saveProgram(){
const name=document.getElementById('fp-name').value.trim();if(!name)return;
const data={name,institution:document.getElementById('fp-institution').value.trim(),type:document.getElementById('fp-type').value,start:parseInt(document.getElementById('fp-start').value),end:parseInt(document.getElementById('fp-end').value),color:selectedProgColor};
if(editingProgramId)Object.assign(PROGRAMS.find(x=>x.id===editingProgramId),data);
else PROGRAMS.push({id:Date.now(),...data});
closeModal('program');renderPrograms();
}
function confirmDeleteProgram(){
const p=PROGRAMS.find(x=>x.id===editingProgramId);if(!p)return;
document.getElementById('confirm-title').textContent=`Delete "${p.name}"?`;
document.getElementById('confirm-desc').textContent='Courses linked to this program will be unlinked.';
document.getElementById('confirm-ok').onclick=()=>{PROGRAMS=PROGRAMS.filter(x=>x.id!==editingProgramId);COURSES.forEach(c=>{if(c.programId===editingProgramId)c.programId=null;});closeModal('program');closeModal('confirm');renderPrograms();};
openModal('confirm');
}
function addProgramTask(programId){
const title=prompt('Program task name:'); if(!title)return;
PROGRAM_TASKS.push({id:Date.now(),programId,title,done:false});renderPrograms();
}
// ══ COURSE CRUD ══
function openCourseModal(preselectedId,preselectedProgramId){
editingCourseId=null;selectedColor='#6c8eff';
document.getElementById('course-modal-title').textContent='Add Course';
document.getElementById('course-save-btn').textContent='Add Course';
document.getElementById('del-course-btn').style.display='none';
document.getElementById('ec-id').value='';
['fc-name','fc-code','fc-professor','fc-ta'].forEach(id=>document.getElementById(id).value='');
document.getElementById('fc-credits').value=''; document.getElementById('fc-target').value='';
document.getElementById('fc-difficulty').value='3';
document.getElementById('fc-term').value='Spring'; document.getElementById('fc-year').value=2026;
document.getElementById('fc-program').innerHTML='<option value="">— None —</option>'+PROGRAMS.map(p=>`<option value="${p.id}"${p.id===preselectedProgramId?' selected':''}>${p.name}</option>`).join('');
document.querySelectorAll('#color-picker .color-swatch').forEach(s=>s.classList.toggle('selected',s.dataset.color===selectedColor));
openModal('course');
}
function openEditCourse(id){
const c=getCourse(id);if(!c)return;
editingCourseId=id;selectedColor=c.color;
document.getElementById('course-modal-title').textContent='Edit Course';
document.getElementById('course-save-btn').textContent='Save';
document.getElementById('del-course-btn').style.display='inline-flex';
document.getElementById('ec-id').value=id;
document.getElementById('fc-name').value=c.name; document.getElementById('fc-code').value=c.code;
document.getElementById('fc-credits').value=c.credits; document.getElementById('fc-target').value=c.target;
document.getElementById('fc-difficulty').value=c.difficulty; document.getElementById('fc-term').value=c.term||'Spring';
document.getElementById('fc-year').value=c.year||2026; document.getElementById('fc-professor').value=c.professor||'';
document.getElementById('fc-ta').value=c.ta||'';
document.getElementById('fc-program').innerHTML='<option value="">— None —</option>'+PROGRAMS.map(p=>`<option value="${p.id}"${p.id===c.programId?' selected':''}>${p.name}</option>`).join('');
document.querySelectorAll('#color-picker .color-swatch').forEach(s=>s.classList.toggle('selected',s.dataset.color===c.color));
openModal('course');
}
function saveCourse(){
const name=document.getElementById('fc-name').value.trim();if(!name)return;
const prof=document.getElementById('fc-professor').value.trim();
if(prof&&!CONTACTS.find(c=>c.name===prof))CONTACTS.push({id:Date.now(),name:prof,role:'professor',email:'',notes:''});
const ta=document.getElementById('fc-ta').value.trim();
if(ta&&!CONTACTS.find(c=>c.name===ta))CONTACTS.push({id:Date.now()+1,name:ta,role:'ta',email:'',notes:''});
const data={name,code:document.getElementById('fc-code').value.trim(),credits:parseInt(document.getElementById('fc-credits').value)||3,target:parseInt(document.getElementById('fc-target').value)||85,difficulty:parseInt(document.getElementById('fc-difficulty').value)||3,term:document.getElementById('fc-term').value,year:parseInt(document.getElementById('fc-year').value)||2026,professor:prof,ta,color:selectedColor,programId:parseInt(document.getElementById('fc-program').value)||null};
if(editingCourseId)Object.assign(getCourse(editingCourseId),data);
else COURSES.push({id:Date.now(),...data});
closeModal('course');renderDashboard();renderCourses();
}
function confirmDeleteCourse(){
const c=getCourse(editingCourseId);if(!c)return;
document.getElementById('confirm-title').textContent=`Delete "${c.name}"?`;
document.getElementById('confirm-desc').textContent='All assignments for this course will also be removed.';
document.getElementById('confirm-ok').onclick=()=>{COURSES=COURSES.filter(x=>x.id!==editingCourseId);ASSIGNMENTS=ASSIGNMENTS.filter(a=>a.courseId!==editingCourseId);closeModal('course');closeModal('confirm');renderDashboard();renderCourses();};
openModal('confirm');
}
// ══ ASSIGNMENT CRUD ══
function loadAsgnProgramTask(){
const cid=parseInt(document.getElementById('fa-course').value)||null;
const c=getCourse(cid); const pid=c?.programId;
const tasks=pid?PROGRAM_TASKS.filter(t=>t.programId===pid):[];
document.getElementById('fa-program-task').innerHTML='<option value="">— None —</option>'+tasks.map(t=>`<option value="${t.id}">${t.title}</option>`).join('');
}
function openAssignmentModal(preselectedCourseId){
editingAssignmentId=null;
document.getElementById('asgn-modal-title').textContent='Add Assignment';
document.getElementById('asgn-save-btn').textContent='Add Assignment';
document.getElementById('del-asgn-btn').style.display='none';
document.getElementById('ea-id').value='';
document.getElementById('fa-title').value=''; document.getElementById('fa-due').value=fmt(today);
document.getElementById('fa-weight').value=''; document.getElementById('fa-score').value='';
document.getElementById('fa-possible').value='100'; document.getElementById('fa-status').value='pending';
document.getElementById('fa-type').value='Exam'; document.getElementById('fa-notes').value='';
document.getElementById('fa-group-type').value='individual';
document.getElementById('fa-course').innerHTML=COURSES.map(c=>`<option value="${c.id}"${c.id===preselectedCourseId?' selected':''}>${c.name}</option>`).join('');
loadAsgnProgramTask();
openModal('assignment');
}
function openEditAssignment(id){
const a=ASSIGNMENTS.find(x=>x.id===id);if(!a)return;
editingAssignmentId=id;
document.getElementById('asgn-modal-title').textContent='Edit Assignment';
document.getElementById('asgn-save-btn').textContent='Save';
document.getElementById('del-asgn-btn').style.display='inline-flex';
document.getElementById('ea-id').value=id;
document.getElementById('fa-title').value=a.title; document.getElementById('fa-due').value=a.due;
document.getElementById('fa-weight').value=a.weight; document.getElementById('fa-score').value=a.score??'';
document.getElementById('fa-possible').value=a.possible; document.getElementById('fa-status').value=a.status==='done'?'done':'pending';
document.getElementById('fa-type').value=a.type; document.getElementById('fa-notes').value=a.notes||'';
document.getElementById('fa-group-type').value=a.groupType||'individual';
document.getElementById('fa-course').innerHTML=COURSES.map(c=>`<option value="${c.id}"${c.id===a.courseId?' selected':''}>${c.name}</option>`).join('');
loadAsgnProgramTask();
openModal('assignment');
}
function saveAssignment(){
const title=document.getElementById('fa-title').value.trim();if(!title)return;
const due=document.getElementById('fa-due').value;if(!due){alert('Due date is required — it\'s used to calculate overdue status automatically.');return;}
const scoreVal=document.getElementById('fa-score').value;
const status=document.getElementById('fa-status').value;
const data={title,courseId:parseInt(document.getElementById('fa-course').value),type:document.getElementById('fa-type').value,due,weight:parseFloat(document.getElementById('fa-weight').value)||10,score:scoreVal!==''?parseFloat(scoreVal):null,possible:parseFloat(document.getElementById('fa-possible').value)||100,status,notes:document.getElementById('fa-notes').value.trim(),groupType:document.getElementById('fa-group-type').value,programTaskId:parseInt(document.getElementById('fa-program-task').value)||null,completedAt:status==='done'?fmt(today):null};
if(editingAssignmentId)Object.assign(ASSIGNMENTS.find(x=>x.id===editingAssignmentId),data);
else ASSIGNMENTS.push({id:Date.now(),createdAt:fmt(today),...data});
closeModal('assignment');renderAssignments();renderDashboard();renderCourses();
}
function confirmDeleteAssignmentById(id,fromModal){
const aid=id||(fromModal?parseInt(document.getElementById('ea-id').value):null);if(!aid)return;
const a=ASSIGNMENTS.find(x=>x.id===aid);if(!a)return;
document.getElementById('confirm-title').textContent=`Delete "${a.title}"?`;
document.getElementById('confirm-desc').textContent='Grade data will be removed.';
document.getElementById('confirm-ok').onclick=()=>{ASSIGNMENTS=ASSIGNMENTS.filter(x=>x.id!==aid);closeModal('assignment');closeModal('confirm');renderAssignments();renderDashboard();renderCourses();};
openModal('confirm');
}
// ══ TASK CRUD ══
function openTaskModal(){
editingTaskId=null;
document.getElementById('task-modal-title').textContent='Add Task';
document.getElementById('task-save-btn').textContent='Add Task';
document.getElementById('del-task-btn').style.display='none';
document.getElementById('et-id').value='';
document.getElementById('ft-title').value=''; document.getElementById('ft-due').value='';
document.getElementById('ft-priority').value='medium'; document.getElementById('ft-notes').value='';
document.getElementById('ft-course').innerHTML='<option value="">— None —</option>'+COURSES.map(c=>`<option value="${c.id}">${c.name}</option>`).join('');
openModal('task');
}
function openEditTask(id){
const t=TASKS.find(x=>x.id===id);if(!t)return;
editingTaskId=id;
document.getElementById('task-modal-title').textContent='Edit Task';
document.getElementById('task-save-btn').textContent='Save';
document.getElementById('del-task-btn').style.display='inline-flex';
document.getElementById('et-id').value=id;
document.getElementById('ft-title').value=t.title; document.getElementById('ft-due').value=t.due||'';
document.getElementById('ft-priority').value=t.priority; document.getElementById('ft-notes').value=t.notes||'';
document.getElementById('ft-course').innerHTML='<option value="">— None —</option>'+COURSES.map(c=>`<option value="${c.id}"${c.id===t.courseId?' selected':''}>${c.name}</option>`).join('');
openModal('task');
}
function saveTask(){
const title=document.getElementById('ft-title').value.trim();if(!title)return;
const data={title,due:document.getElementById('ft-due').value||null,priority:document.getElementById('ft-priority').value,courseId:parseInt(document.getElementById('ft-course').value)||null,notes:document.getElementById('ft-notes').value.trim(),status:'pending',completedAt:null};
if(editingTaskId)Object.assign(TASKS.find(x=>x.id===editingTaskId),data);
else TASKS.push({id:Date.now(),createdAt:fmt(today),...data});
closeModal('task');renderTasks();renderDashboard();
}
function confirmDeleteTask(){confirmDeleteTaskById(editingTaskId,true);}
function confirmDeleteTaskById(id,fromModal){
const tid=id||(fromModal?parseInt(document.getElementById('et-id').value):null);if(!tid)return;
const t=TASKS.find(x=>x.id===tid);if(!t)return;
document.getElementById('confirm-title').textContent=`Delete "${t.title}"?`;
document.getElementById('confirm-desc').textContent='This task will be permanently removed.';
document.getElementById('confirm-ok').onclick=()=>{TASKS=TASKS.filter(x=>x.id!==tid);closeModal('task');closeModal('confirm');renderTasks();renderDashboard();};
openModal('confirm');
}
// ══ RESOURCE CRUD ══
function openResourceModal(){
editingResourceId=null;
document.getElementById('res-modal-title').textContent='Add Resource';
document.getElementById('res-save-btn').textContent='Add Resource';
document.getElementById('del-res-btn').style.display='none';
document.getElementById('er-id').value='';
['fr-title','fr-url','fr-notes'].forEach(id=>document.getElementById(id).value='');
document.getElementById('fr-type').value='Book'; document.getElementById('fr-status').value='needed';
document.getElementById('fr-course').innerHTML='<option value="">— None —</option>'+COURSES.map(c=>`<option value="${c.id}">${c.name}</option>`).join('');
document.getElementById('fr-assignment').innerHTML='<option value="">— None —</option>';
openModal('resource');
}
function openEditResource(id){
const r=RESOURCES.find(x=>x.id===id);if(!r)return;
editingResourceId=id;
document.getElementById('res-modal-title').textContent='Edit Resource';
document.getElementById('res-save-btn').textContent='Save';
document.getElementById('del-res-btn').style.display='inline-flex';
document.getElementById('er-id').value=id;
document.getElementById('fr-title').value=r.title; document.getElementById('fr-url').value=r.url||'';
document.getElementById('fr-notes').value=r.notes||''; document.getElementById('fr-type').value=r.type;
document.getElementById('fr-status').value=r.status;
document.getElementById('fr-course').innerHTML='<option value="">— None —</option>'+COURSES.map(c=>`<option value="${c.id}"${c.id===r.courseId?' selected':''}>${c.name}</option>`).join('');
const asgns=r.courseId?ASSIGNMENTS.filter(a=>a.courseId===r.courseId):[];
document.getElementById('fr-assignment').innerHTML='<option value="">— None —</option>'+asgns.map(a=>`<option value="${a.id}"${a.id===r.assignmentId?' selected':''}>${a.title}</option>`).join('');
openModal('resource');
}
function saveResource(){
const title=document.getElementById('fr-title').value.trim();if(!title)return;
const data={title,type:document.getElementById('fr-type').value,status:document.getElementById('fr-status').value,url:document.getElementById('fr-url').value.trim(),courseId:parseInt(document.getElementById('fr-course').value)||null,assignmentId:parseInt(document.getElementById('fr-assignment').value)||null,notes:document.getElementById('fr-notes').value.trim()};
if(editingResourceId)Object.assign(RESOURCES.find(x=>x.id===editingResourceId),data);
else RESOURCES.push({id:Date.now(),...data});
closeModal('resource');renderResources();
}
function confirmDeleteResource(){confirmDeleteResourceById(editingResourceId,true);}
function confirmDeleteResourceById(id,fromModal){
const rid=id||(fromModal?parseInt(document.getElementById('er-id').value):null);if(!rid)return;
const r=RESOURCES.find(x=>x.id===rid);if(!r)return;
document.getElementById('confirm-title').textContent=`Delete "${r.title}"?`;
document.getElementById('confirm-desc').textContent='This resource will be permanently removed.';
document.getElementById('confirm-ok').onclick=()=>{RESOURCES=RESOURCES.filter(x=>x.id!==rid);closeModal('resource');closeModal('confirm');renderResources();};
openModal('confirm');
}
// ══ CONTACT CRUD ══
function openContactModal(){
editingContactId=null;
document.getElementById('contact-modal-title').textContent='Add Contact';
document.getElementById('contact-save-btn').textContent='Add Contact';
document.getElementById('del-contact-btn').style.display='none';
document.getElementById('econ-id').value='';
['fcon-name','fcon-email','fcon-notes'].forEach(id=>document.getElementById(id).value='');
document.getElementById('fcon-role').value='professor';
openModal('contact');
}
function openEditContact(id){
const c=CONTACTS.find(x=>x.id===id);if(!c)return;
editingContactId=id;
document.getElementById('contact-modal-title').textContent='Edit Contact';
document.getElementById('contact-save-btn').textContent='Save';
document.getElementById('del-contact-btn').style.display='inline-flex';
document.getElementById('econ-id').value=id;
document.getElementById('fcon-name').value=c.name; document.getElementById('fcon-email').value=c.email||'';
document.getElementById('fcon-notes').value=c.notes||''; document.getElementById('fcon-role').value=c.role;
openModal('contact');
}
function saveContact(){
const name=document.getElementById('fcon-name').value.trim();if(!name)return;
const data={name,role:document.getElementById('fcon-role').value,email:document.getElementById('fcon-email').value.trim(),notes:document.getElementById('fcon-notes').value.trim()};
if(editingContactId)Object.assign(CONTACTS.find(x=>x.id===editingContactId),data);
else CONTACTS.push({id:Date.now(),...data});
closeModal('contact');renderContacts();
}
function confirmDeleteContact(){confirmDeleteContactById(editingContactId);}
function confirmDeleteContactById(id){
const c=CONTACTS.find(x=>x.id===id);if(!c)return;
document.getElementById('confirm-title').textContent=`Delete "${c.name}"?`;
document.getElementById('confirm-desc').textContent='They will be removed from your contacts list.';
document.getElementById('confirm-ok').onclick=()=>{CONTACTS=CONTACTS.filter(x=>x.id!==id);closeModal('contact');closeModal('confirm');renderContacts();};
openModal('confirm');
}
// ══════════════════════════════════════════════════════
// INIT
// ══════════════════════════════════════════════════════
document.getElementById('show-completed-toggle').checked=SETTINGS.showCompleted;
renderDashboard();
updateBadge();
updateTimerDisplay();
const h=new Date().getHours();
document.getElementById('dash-greeting').textContent=`${h<12?'Good morning':h<17?'Good afternoon':'Good evening'}, Jamie 👋`;
</script>
</body>
</html>