breakingbad / index.html
dumpsters's picture
Update index.html
f546407 verified
Raw History Blame Contribute Delete
56.8 kB
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="color-scheme" content="light dark">
<meta name="description" content="Daymark — a working, self-contained demo of a student-aware assessment planner.">
<title>Daymark — Assessment planner</title>
<style>
:root{--canvas:#f7f8fa;--surface:#fff;--soft:#f3f5f8;--text:#232932;--muted:#626c7a;--border:#e1e5eb;--input-border:#8290a2;--blue:#245dcc;--blue-soft:#eaf0fc;--green:#28714f;--green-soft:#edf6f0;--amber:#8a5a0b;--amber-soft:#fcf5e5;--red:#b04840;--red-soft:#fbefed;--shadow:0 2px 5px #14203905;--radius:12px}
*{box-sizing:border-box}body{margin:0;background:var(--canvas);color:var(--text);font:16px/1.5 -apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;-webkit-font-smoothing:antialiased}button,input,select{font:inherit}button{cursor:pointer}button:disabled{cursor:not-allowed}button,a,input,select{touch-action:manipulation}button{color:inherit}button:focus-visible,a:focus-visible,input:focus-visible,select:focus-visible{outline:3px solid var(--blue);outline-offset:3px}a{color:var(--blue)}button,input,select{transition:background .15s,border-color .15s}svg{width:20px;height:20px;flex-shrink:0;fill:none;stroke:currentColor;stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round}h1,h2,h3,p{margin:0}button svg{pointer-events:none}button{border:0;background:none}input,select{min-width:0}button[hidden],[hidden]{display:none!important}.skip{position:fixed;left:16px;top:-100px;background:var(--surface);padding:12px 20px;z-index:100}.skip:focus{top:16px}.app{min-height:100vh;display:grid;grid-template-columns:216px minmax(0,1fr)}
.sidebar{background:var(--surface);border-right:1px solid var(--border);padding:30px 16px 20px;display:flex;flex-direction:column;position:sticky;top:0;height:100vh;gap:32px}.brand{display:flex;gap:11px;align-items:center;padding:0 14px;font-size:25px;font-weight:730;letter-spacing:-1.1px}.brand-mark{width:29px;height:29px;position:relative;flex-shrink:0}.brand-mark i{position:absolute;background:var(--blue);border-radius:3px;width:12px;height:12px}.brand-mark i:nth-child(1){top:0;left:0}.brand-mark i:nth-child(2){top:0;right:0;opacity:.48}.brand-mark i:nth-child(3){bottom:0;left:0;opacity:.48}.brand-mark i:nth-child(4){bottom:0;right:0}.workspace{padding:0 14px}.workspace strong{display:block;font-size:14px;font-weight:600}.workspace span{font-size:14px;color:var(--muted)}.nav{display:grid;gap:6px}.nav-button{display:flex;align-items:center;gap:12px;text-align:left;padding:12px 14px;min-height:48px;border-radius:8px;font-size:15px;font-weight:550;width:100%;color:var(--muted)}.nav-button:hover{background:var(--soft);color:var(--text)}.nav-button.active{background:var(--blue-soft);color:var(--blue)}.nav-count{margin-left:auto;font-size:14px;font-weight:600;border:1px solid var(--border);border-radius:5px;min-width:24px;text-align:center;background:var(--surface)}.section-label{font-size:14px;font-weight:600;color:var(--muted);padding:0 14px;margin-bottom:10px}.class-list{display:grid;gap:4px}.class-button{min-height:50px;text-align:left;border-radius:8px;padding:10px 14px;display:flex;gap:11px;align-items:center;width:100%}.class-button:hover,.class-button.selected{background:var(--soft)}.class-button.selected{color:var(--blue)}.class-dot{width:8px;height:8px;background:currentColor;border-radius:2px;flex-shrink:0;opacity:.8}.class-button span:last-child{line-height:1.4;font-size:14px}.class-button small{display:block;font-size:14px;color:var(--muted);font-weight:400}.sidebar-bottom{margin-top:auto;border-top:1px solid var(--border);padding-top:16px}.identity{display:flex;align-items:center;gap:10px;padding:16px 14px 0;font-size:14px}.avatar{width:34px;height:34px;border-radius:50%;background:var(--soft);display:grid;place-items:center;font-weight:650;border:1px solid var(--border)}.identity small{display:block;font-size:14px;color:var(--muted)}
main{min-width:0;padding:34px 36px 28px;max-width:1480px;width:100%;margin:auto}.page-top{display:flex;justify-content:space-between;align-items:center;gap:16px;margin-bottom:28px}.eyebrow{font-size:14px;color:var(--muted);display:flex;align-items:center;gap:8px}.eyebrow svg{width:16px;height:16px}.demo-tag{display:inline-flex;align-items:center;gap:7px;color:var(--muted);font-size:14px;border:1px solid var(--border);border-radius:6px;padding:5px 9px;background:var(--surface);white-space:nowrap}.demo-tag i{width:6px;height:6px;background:var(--muted);border-radius:50%}.title-row{display:flex;justify-content:space-between;align-items:end;gap:16px;margin-bottom:28px}h1{font-size:34px;letter-spacing:-1.1px;font-weight:660;line-height:1.2}.lead{margin-top:9px;color:var(--muted);font-size:15px}.term-label{font-size:14px;color:var(--muted);white-space:nowrap;padding-bottom:2px}.planner-grid{display:grid;grid-template-columns:minmax(0,1fr) 364px;gap:24px;align-items:start}.calendar-side{min-width:0}.calendar{border:1px solid var(--border);border-radius:var(--radius);background:var(--surface);box-shadow:var(--shadow);overflow:hidden}.calendar-toolbar{padding:22px 24px;display:flex;justify-content:space-between;align-items:center;gap:8px}.calendar-toolbar h2{font-size:20px;letter-spacing:-.5px;font-weight:650}.calendar-toolbar p{font-size:14px;color:var(--muted);margin-top:2px}.calendar-controls{display:flex;gap:4px;align-items:center}.icon-button{min-width:44px;min-height:44px;border-radius:7px;display:inline-flex;align-items:center;justify-content:center;flex-shrink:0}.icon-button:hover{background:var(--soft)}.icon-button:disabled{opacity:.3}.small-button{font-size:14px;min-height:44px;padding:8px 11px;border:1px solid var(--border);border-radius:7px}.small-button:hover{background:var(--soft)}.calendar-weekdays{display:grid;grid-template-columns:repeat(5,minmax(0,1fr));background:var(--soft);border-block:1px solid var(--border)}.calendar-weekdays div{padding:10px 8px;text-align:center;font-size:14px;color:var(--muted);font-weight:550}.calendar-days{display:grid;grid-template-columns:repeat(5,minmax(0,1fr));gap:1px;background:var(--border)}.day{min-height:88px;background:var(--surface);text-align:left;padding:12px 12px 10px;position:relative;display:flex;flex-direction:column;gap:8px;min-width:0}.day:not(:disabled):hover{background:var(--soft)}.day.selected{background:var(--blue-soft)!important;box-shadow:inset 0 0 0 2px var(--blue);z-index:1}.day.blank{background:var(--soft)}.day:disabled .date-number{color:var(--muted);opacity:.6}.day-top{display:flex;align-items:center;justify-content:space-between;gap:4px;width:100%}.date-number{font-size:15px;font-weight:550}.day.selected .date-number{color:var(--blue);font-weight:700}.health-dot{width:7px;height:7px;flex-shrink:0;border-radius:50%;background:var(--green)}.health-dot.amber{background:var(--amber)}.health-dot.red{background:var(--red)}.load-label{font-size:14px;line-height:1.3;letter-spacing:-.2px;color:var(--muted);white-space:nowrap}.load-label.red{color:var(--red)}.load-label.amber{color:var(--amber)}.load-label.green{color:var(--green)}.saved-mark{width:4px;height:4px;border-radius:1px;background:var(--blue);position:absolute;bottom:7px;left:12px}.calendar-footer{padding:14px 20px;display:flex;align-items:center;justify-content:space-between;gap:10px;border-top:1px solid var(--border);font-size:14px;color:var(--muted)}.legend{display:flex;align-items:center;gap:14px;flex-wrap:wrap}.legend span{display:flex;align-items:center;gap:6px}.key-note{font-size:14px;color:var(--muted);margin:12px 2px 0;display:flex;align-items:start;gap:7px}.key-note svg{width:16px;height:16px;margin-top:2px}.recommendations{margin-top:24px}.section-heading{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:12px}.section-heading h2{font-size:16px;letter-spacing:-.2px;font-weight:650}.section-heading span{font-size:14px;color:var(--muted)}.recommendation-list{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px}.recommendation{border:1px solid var(--border);border-radius:9px;padding:14px;background:var(--surface);text-align:left;min-width:0;display:flex;flex-direction:column;gap:4px}.recommendation:hover{border-color:var(--blue);background:var(--blue-soft)}.recommendation strong{font-size:15px;font-weight:600;display:flex;align-items:center;justify-content:space-between;gap:4px}.recommendation strong svg{width:16px;height:16px;color:var(--muted)}.recommendation span{font-size:14px;color:var(--green)}.recommendation span.amber{color:var(--amber)}.recommendation span.red{color:var(--red)}.recommendation small{font-size:14px;color:var(--muted)}.recommendation-empty{color:var(--muted);font-size:14px;background:var(--surface);border:1px solid var(--border);padding:16px;border-radius:8px;grid-column:1/-1}
.plan-panel{border:1px solid var(--border);border-radius:var(--radius);background:var(--surface);box-shadow:var(--shadow);overflow:hidden;scroll-margin-top:16px}.panel-heading{padding:22px 24px 18px;border-bottom:1px solid var(--border);display:flex;justify-content:space-between;align-items:center;gap:6px}.panel-heading h2{font-size:18px;letter-spacing:-.4px;font-weight:650}.panel-heading .icon-button{min-width:28px;min-height:44px;color:var(--muted)}.plan-form{padding:20px 24px 22px}.field{display:block;margin-bottom:14px}.field>span{display:block;font-size:14px;font-weight:550;margin-bottom:6px}.input{height:44px;width:100%;border:1px solid var(--input-border);background:var(--surface);border-radius:6px;padding:9px 11px;color:var(--text);font-size:14px}.input::placeholder{color:var(--muted)}.input:focus{border-color:var(--blue)}select.input{padding-right:4px}.form-row{display:grid;grid-template-columns:.8fr 1.25fr;gap:12px}.form-error{font-size:14px;color:var(--red);background:var(--red-soft);border-radius:6px;padding:10px 12px;margin:4px 0 12px}.impact-header{display:flex;align-items:center;justify-content:space-between;gap:8px;margin:4px 0 12px}.impact-header h3{font-size:14px;font-weight:650}.impact-header span{font-size:14px;color:var(--muted)}.impact-summary{border-radius:8px;padding:12px 14px;margin-bottom:12px;background:var(--red-soft);color:var(--red);display:flex;gap:10px;align-items:start}.impact-summary.green{background:var(--green-soft);color:var(--green)}.impact-summary.amber{background:var(--amber-soft);color:var(--amber)}.impact-summary svg{margin-top:1px;width:18px;height:18px}.impact-summary strong{font-size:14px;font-weight:650;display:block;line-height:1.4}.impact-summary p{font-size:14px;line-height:1.45;margin-top:4px}.impact-rows{display:grid;gap:2px}.impact-row{display:flex;align-items:center;gap:12px;padding:8px 0}.impact-number{display:grid;place-items:center;font-size:20px;letter-spacing:-.5px;font-weight:650;min-width:43px;height:43px;border-radius:7px;background:var(--red-soft);color:var(--red);font-variant-numeric:tabular-nums}.impact-row.amber .impact-number{background:var(--amber-soft);color:var(--amber)}.impact-row.green .impact-number{background:var(--green-soft);color:var(--green)}.impact-row strong{display:block;font-size:14px;font-weight:600;line-height:1.4}.impact-row small{font-size:14px;color:var(--muted);display:block;line-height:1.4}.impact-note{font-size:14px;color:var(--muted);margin-top:10px;line-height:1.5}.form-divider{border:0;border-top:1px solid var(--border);margin:18px 0}.primary{border:1px solid var(--blue);background:var(--blue);color:#fff;border-radius:7px;font-size:14px;font-weight:600;min-height:46px;padding:11px 16px;display:inline-flex;justify-content:center;align-items:center;gap:9px}.primary:hover{filter:brightness(.94)}.primary:disabled{opacity:.45}.wide{width:100%}.primary svg{width:18px;height:18px}.secondary{background:var(--surface);border:1px solid var(--input-border);padding:10px 16px;min-height:46px;border-radius:7px;font-size:14px;font-weight:550}.secondary:hover{background:var(--soft)}.danger-button{background:var(--red);border-color:var(--red);color:#fff}.cancel-edit{width:100%;margin-top:8px}.privacy-note{font-size:14px;color:var(--muted);display:flex;align-items:center;justify-content:center;gap:6px;padding:12px 24px;border-top:1px solid var(--border);background:var(--soft)}.privacy-note svg{width:16px;height:16px}.page-footer{display:flex;justify-content:space-between;align-items:center;gap:12px;color:var(--muted);font-size:14px;margin-top:28px}.page-footer button{font-size:14px;text-decoration:underline;text-underline-offset:3px;min-height:44px;padding:0 4px}.mobile-review{display:none}
.assessments-toolbar{display:flex;align-items:center;gap:12px;margin-bottom:20px}.search-wrap{flex:1;position:relative}.search-wrap svg{position:absolute;left:12px;top:13px;width:18px;height:18px;color:var(--muted)}.search-wrap input{padding-left:38px}.assessments-toolbar select{max-width:220px}.assessment-list{background:var(--surface);border:1px solid var(--border);border-radius:12px;overflow:hidden}.assessment-row{display:grid;grid-template-columns:54px minmax(0,1fr) auto auto;align-items:center;gap:20px;padding:22px 24px;border-bottom:1px solid var(--border)}.assessment-row:last-child{border-bottom:0}.date-tile{background:var(--soft);border:1px solid var(--border);border-radius:8px;min-height:64px;display:flex;align-items:center;justify-content:center;flex-direction:column;line-height:1.2}.date-tile span{font-size:14px;color:var(--muted)}.date-tile strong{font-size:24px;font-weight:600}.assessment-name{font-size:16px;font-weight:600;overflow-wrap:anywhere}.assessment-meta{font-size:14px;color:var(--muted);margin-top:4px}.badge{padding:5px 9px;border-radius:6px;font-size:14px;background:var(--green-soft);color:var(--green);white-space:nowrap}.badge.red{background:var(--red-soft);color:var(--red)}.badge.amber{background:var(--amber-soft);color:var(--amber)}.row-actions{display:flex;gap:4px}.row-actions .edit-button{min-height:44px;padding:8px 12px;border-radius:7px;font-size:14px;font-weight:550;color:var(--blue)}.row-actions .edit-button:hover{background:var(--blue-soft)}.row-actions .delete-button{color:var(--muted)}.empty-state{text-align:center;padding:64px 24px}.empty-state>svg{width:32px;height:32px;color:var(--muted);margin-bottom:16px}.empty-state h2{font-size:20px}.empty-state p{color:var(--muted);font-size:15px;margin:8px 0 20px}.list-note{font-size:14px;color:var(--muted);margin-top:16px}.list-count{font-size:14px;color:var(--muted);margin-bottom:12px}
dialog{border:1px solid var(--border);border-radius:14px;padding:0;max-width:520px;width:calc(100% - 32px);color:var(--text);background:var(--surface);box-shadow:0 16px 80px #10182826;max-height:85dvh;overflow:auto}dialog::backdrop{background:#10182860;backdrop-filter:blur(3px)}.dialog-header{padding:22px 24px 14px;display:flex;align-items:center;justify-content:space-between;gap:12px}.dialog-header h2{font-size:22px;letter-spacing:-.5px}.dialog-body{padding:0 24px 24px}.dialog-body>p{font-size:15px;color:var(--muted);margin:8px 0 18px}.rules{display:grid;gap:12px;margin:20px 0}.rule{display:flex;gap:12px;font-size:14px}.rule .health-dot{margin-top:7px}.rule strong{display:block}.rule p{color:var(--muted);margin-top:2px}.dialog-note{font-size:14px;padding:14px;background:var(--soft);border-radius:8px;color:var(--muted);line-height:1.6}.dialog-note strong{color:var(--text)}.dialog-actions{display:flex;justify-content:end;gap:10px;margin-top:24px;flex-wrap:wrap}.dialog-note+.dialog-note{margin-top:12px}.toast{position:fixed;left:50%;bottom:24px;transform:translateX(-50%);z-index:20;max-width:calc(100% - 32px);background:var(--text);color:var(--surface);border-radius:9px;padding:14px 20px;font-size:14px;box-shadow:0 4px 24px #0002;display:flex;align-items:center;gap:10px}.toast svg{width:18px;height:18px}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}
@media(min-width:1600px){main{padding-inline:48px}.planner-grid{grid-template-columns:minmax(0,1fr) 390px}.day{min-height:98px}}
@media(max-width:1190px){.app{grid-template-columns:184px minmax(0,1fr)}.sidebar{padding-inline:12px}.brand{padding-inline:8px}main{padding:28px 24px}.planner-grid{grid-template-columns:minmax(0,1fr) 338px;gap:18px}.calendar-toolbar{padding:18px 16px}.calendar-toolbar h2{font-size:18px}.calendar-controls{gap:0}.calendar-controls .small-button{display:none}.day{padding:10px 8px;min-height:90px}.load-label{font-size:14px}.plan-form{padding:18px 20px}.panel-heading{padding-inline:20px}.recommendation{padding:12px 10px}.recommendation-list{gap:8px}.section-heading{align-items:start}.section-heading span{max-width:100px;text-align:right}.page-footer{font-size:14px}.calendar-footer{padding:14px 12px}}
@media(max-width:1040px){.app{grid-template-columns:172px minmax(0,1fr)}main{padding:26px 20px}.planner-grid{grid-template-columns:minmax(0,1fr)}.plan-panel{max-width:none}.plan-form{display:grid;grid-template-columns:1fr 1fr;column-gap:28px}.form-fields{grid-column:1}.impact-block{grid-column:2;grid-row:1/3}.form-submit{grid-column:1;grid-row:2;align-self:end}.form-divider{margin-top:6px}.calendar-toolbar h2{font-size:20px}.day{min-height:85px;padding:12px}.recommendations{margin-bottom:6px}.recommendation strong{font-size:16px}.sidebar .brand{font-size:23px;gap:8px}.sidebar .brand-mark{width:26px;height:26px}.calendar-controls .small-button{display:block}.page-footer{flex-wrap:wrap}.term-label{display:none}.assessment-row{grid-template-columns:54px minmax(0,1fr) auto;gap:14px}.assessment-row .badge{grid-column:2;grid-row:2;justify-self:start}.assessment-row .row-actions{grid-column:3;grid-row:1/3}.assessment-row .date-tile{grid-row:1/3}}
@media(max-width:700px){.app{display:block}.sidebar{position:static;height:auto;padding:18px 20px 0;border-right:0;border-bottom:1px solid var(--border);gap:16px}.brand{padding:0!important;font-size:25px!important}.sidebar-brand-row{display:flex;justify-content:space-between;align-items:center}.mobile-demo{display:inline-flex!important}.workspace,.class-section,.identity{display:none}.sidebar .nav{display:flex;gap:8px;flex:1}.nav-button{min-height:46px;padding:11px 12px;justify-content:center;white-space:nowrap;width:auto;flex:1}.sidebar-bottom{margin:0;border:0;padding:0}.sidebar-bottom .nav-button{min-width:44px;min-height:46px;padding:10px}.sidebar-bottom .nav-button span{display:none}.sidebar-nav-row{display:flex;gap:8px;padding-bottom:12px}main{padding:24px 20px}.page-top{display:none}.title-row{margin-bottom:22px;align-items:start}h1{font-size:29px;letter-spacing:-.8px}.lead{font-size:15px;line-height:1.6;margin-top:8px;max-width:320px}.title-row .primary{padding:11px;font-size:14px}.title-row .primary span{display:none}.planner-grid{gap:20px}.calendar-toolbar{padding:16px}.calendar-toolbar h2{font-size:19px}.calendar-controls .small-button{display:none}.calendar-toolbar p{font-size:14px}.calendar-weekdays div{font-size:14px;padding:9px 0}.day{min-height:74px;padding:9px 7px;gap:8px}.date-number{font-size:15px}.health-dot{width:6px;height:6px}.load-label{font-size:14px;white-space:normal;line-height:1.2}.calendar-footer{padding:12px}.legend{gap:16px;font-size:14px}.key-note{font-size:14px;margin-top:10px}.section-heading{align-items:center}.section-heading span{font-size:14px;max-width:none}.recommendations{margin-top:22px}.recommendation-list{gap:8px}.recommendation{padding:12px 10px}.recommendation strong{font-size:14px;display:block;line-height:1.5}.recommendation strong svg{display:none}.recommendation span{font-size:14px}.recommendation small{font-size:14px;line-height:1.4}.plan-form{display:block;padding:20px}.panel-heading{padding:18px 20px}.input{font-size:16px}.form-divider{margin:20px 0}.privacy-note{font-size:14px;padding:12px 16px}.impact-header{margin-top:8px}.impact-summary{margin-bottom:12px}.impact-row{padding:9px 0}.page-footer{margin:20px 0 70px;gap:2px;display:block;line-height:1.6}.page-footer button{display:block;padding:0}.mobile-review{position:fixed;bottom:0;left:0;right:0;z-index:8;background:var(--surface);border-top:1px solid var(--border);padding:10px 20px max(10px,env(safe-area-inset-bottom));display:flex;align-items:center;justify-content:space-between;gap:10px;box-shadow:0 -4px 16px #10182805}.mobile-review strong{font-size:14px;display:block}.mobile-review small{font-size:14px;color:var(--muted)}.mobile-review button{min-height:44px}.toast{bottom:88px;width:max-content}.assessments-toolbar{flex-direction:column;align-items:stretch}.assessments-toolbar select{max-width:none}.assessment-row{padding:20px 16px;grid-template-columns:46px minmax(0,1fr);gap:12px}.assessment-row .date-tile{grid-column:1;grid-row:1/3;align-self:start;min-height:60px}.assessment-row .badge{grid-column:2;grid-row:2}.assessment-row .row-actions{grid-column:2;grid-row:3;justify-self:start}.assessment-name{font-size:16px}.assessment-meta{line-height:1.6}.row-actions .edit-button{padding-left:0;padding-right:16px}.list-note{line-height:1.6}.dialog-header{padding:18px 20px 12px}.dialog-body{padding:0 20px 20px}.dialog-header h2{font-size:21px}.dialog-actions{gap:8px}.dialog-actions button{flex:1}.dialog-note{font-size:14px}}
@media(max-width:700px){.mobile-review{position:static;border:1px solid var(--border);border-radius:8px;padding:12px;margin-top:16px;box-shadow:none}.page-footer{margin-bottom:24px}.toast{bottom:16px}.recommendations{margin-top:20px}}
@media(prefers-color-scheme:dark){:root{--canvas:#191b1f;--surface:#202329;--soft:#292d34;--text:#edf0f5;--muted:#adb5c2;--border:#424954;--input-border:#717f93;--blue:#86adff;--blue-soft:#283750;--green:#8cd5ae;--green-soft:#263b32;--amber:#e8bd71;--amber-soft:#3e3526;--red:#f1a39c;--red-soft:#402e2f;--shadow:none}.primary{color:#152239}.danger-button{color:#321d1c}.day:disabled .date-number{opacity:.75}dialog::backdrop{background:#0009}}
@media(prefers-reduced-motion:reduce){*,*::before,*::after{transition:none!important;scroll-behavior:auto!important}}
</style>
</head>
<body>
<a class="skip" href="#main">Skip to planner</a>
<svg xmlns="http://www.w3.org/2000/svg" style="position:absolute;width:0;height:0;overflow:hidden" aria-hidden="true"><defs>
<symbol id="i-calendar" viewBox="0 0 24 24"><rect x="3" y="5" width="18" height="16" rx="2"/><path d="M16 3v4M8 3v4M3 11h18M8 15h2M14 15h2"/></symbol>
<symbol id="i-list" viewBox="0 0 24 24"><rect x="5" y="4" width="14" height="17" rx="2"/><path d="M9 4V3h6v1M9 10h6M9 14h6M9 18h3"/></symbol>
<symbol id="i-help" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M9.5 9a2.5 2.5 0 0 1 5 .5c0 2-2.5 2-2.5 3.5M12 17h.01"/></symbol>
<symbol id="i-left" viewBox="0 0 24 24"><path d="m14 6-6 6 6 6"/></symbol>
<symbol id="i-right" viewBox="0 0 24 24"><path d="m10 6 6 6-6 6"/></symbol>
<symbol id="i-arrow" viewBox="0 0 24 24"><path d="M5 12h14m-6-6 6 6-6 6"/></symbol>
<symbol id="i-plus" viewBox="0 0 24 24"><path d="M12 5v14M5 12h14"/></symbol>
<symbol id="i-check" viewBox="0 0 24 24"><path d="m5 12 4 4L19 6"/></symbol>
<symbol id="i-shield" viewBox="0 0 24 24"><path d="m12 3 8 3v6c0 5-8 9-8 9s-8-4-8-9V6l8-3Z"/><path d="m8 12 3 3 5-6"/></symbol>
<symbol id="i-warning" viewBox="0 0 24 24"><path d="m10.2 4.5-8 14A1.7 1.7 0 0 0 3.7 21h16.6a1.7 1.7 0 0 0 1.5-2.5l-8-14a2 2 0 0 0-3.6 0Z"/><path d="M12 9v5M12 17h.01"/></symbol>
<symbol id="i-clock" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/></symbol>
<symbol id="i-close" viewBox="0 0 24 24"><path d="m6 6 12 12M6 18 18 6"/></symbol>
<symbol id="i-trash" viewBox="0 0 24 24"><path d="M3 6h18M9 6V3h6v3M5 6l1 15h12l1-15M10 10v7M14 10v7"/></symbol>
<symbol id="i-search" viewBox="0 0 24 24"><circle cx="10.5" cy="10.5" r="6.5"/><path d="m16 16 5 5"/></symbol>
</defs></svg>
<div class="app">
<aside class="sidebar" aria-label="Workspace navigation">
<div class="sidebar-brand-row"><div class="brand"><span class="brand-mark" aria-hidden="true"><i></i><i></i><i></i><i></i></span>daymark</div><span class="demo-tag mobile-demo" style="display:none"><i></i> Demo</span></div>
<div class="workspace"><strong>Westbrook High</strong><span>Sample school</span></div>
<div class="sidebar-nav-row"><nav class="nav" aria-label="Main navigation">
<button class="nav-button active" data-view="planner" aria-current="page"><svg aria-hidden="true"><use href="#i-calendar"/></svg>Planner</button>
<button class="nav-button" data-view="assessments"><svg aria-hidden="true"><use href="#i-list"/></svg>Assessments <span class="nav-count" id="nav-count">3</span></button>
</nav><div class="sidebar-bottom mobile-help" style="display:none"></div></div>
<section class="class-section"><h2 class="section-label">Your classes</h2><div class="class-list" id="class-list"></div></section>
<div class="sidebar-bottom" id="sidebar-bottom"><button class="nav-button" id="help-button" title="How it works"><svg aria-hidden="true"><use href="#i-help"/></svg><span>How it works</span></button><div class="identity"><span class="avatar">T</span><div>Teacher workspace<small>Demo account</small></div></div></div>
</aside>
<main id="main" tabindex="-1">
<div class="page-top"><span class="eyebrow"><svg aria-hidden="true"><use href="#i-calendar"/></svg>Teaching / Assessment planning</span><span class="demo-tag"><i></i> Interactive demo</span></div>
<div class="title-row"><div><h1 id="page-title">Good days for big tests.</h1><p class="lead" id="page-lead">A little planning. A lighter day for your students.</p></div><span class="term-label" id="term-label">Fall term · 2026</span><button class="primary" id="new-assessment" aria-label="New assessment" hidden><svg aria-hidden="true"><use href="#i-plus"/></svg><span>New assessment</span></button></div>
<section id="planner-view" aria-label="Assessment planner">
<div class="planner-grid">
<div class="calendar-side">
<section class="calendar" aria-label="Weekday assessment calendar">
<div class="calendar-toolbar"><div><h2 id="month-title">September 2026</h2><p id="calendar-class">Algebra II · Period 2</p></div><div class="calendar-controls"><button class="small-button" id="selected-month" title="Show the selected date">Selected</button><button class="icon-button" id="prev-month" aria-label="Previous month"><svg aria-hidden="true"><use href="#i-left"/></svg></button><button class="icon-button" id="next-month" aria-label="Next month"><svg aria-hidden="true"><use href="#i-right"/></svg></button></div></div>
<div class="calendar-weekdays" aria-hidden="true"><div>Mon</div><div>Tue</div><div>Wed</div><div>Thu</div><div>Fri</div></div>
<div class="calendar-days" id="calendar-days" aria-label="Choose a date"></div>
<div class="calendar-footer"><div class="legend"><span><i class="health-dot"></i>Clear</span><span><i class="health-dot amber"></i>Overlap</span><span><i class="health-dot red"></i>Heavy</span></div></div>
</section>
<p class="key-note"><svg aria-hidden="true"><use href="#i-plus"/></svg>Day colors include the assessment you’re planning.</p>
<section class="recommendations" aria-label="Suggested dates"><div class="section-heading"><h2 id="recommendation-title">Lighter days nearby</h2><span>Next 2 weeks</span></div><div class="recommendation-list" id="recommendation-list"></div></section>
</div>
<section class="plan-panel" id="plan-panel" aria-labelledby="form-title">
<div class="panel-heading"><h2 id="form-title">Plan an assessment</h2><svg aria-hidden="true" style="color:var(--muted)"><use href="#i-plus"/></svg></div>
<form class="plan-form" id="plan-form" novalidate>
<div class="form-fields">
<label class="field"><span>Assessment name</span><input class="input" id="assessment-title" name="title" placeholder="e.g. Unit 2 · Quadratic functions" maxlength="100" autocomplete="off" required></label>
<label class="field"><span>Class</span><select class="input" id="class-select" name="class"></select></label>
<div class="form-row"><label class="field"><span>Type</span><select class="input" id="assessment-type" name="type"><option>Test</option><option>Quiz</option></select></label><label class="field"><span>Date</span><input class="input" id="assessment-date" name="date" type="date" min="2026-09-14" max="2026-11-30" required aria-describedby="form-error"></label></div>
<p class="form-error" id="form-error" role="alert" hidden></p>
</div>
<div class="impact-block" id="impact-block" aria-live="polite" aria-atomic="true"></div>
<div class="form-submit"><hr class="form-divider"><button type="submit" class="primary wide" id="schedule-button"><svg aria-hidden="true"><use href="#i-plus"/></svg><span>Schedule assessment</span></button><button type="button" class="secondary cancel-edit" id="cancel-edit" hidden>Cancel changes</button></div>
</form>
<div class="privacy-note"><svg aria-hidden="true"><use href="#i-shield"/></svg>Student names stay out of the picture.</div>
</section>
</div>
</section>
<section id="assessments-view" aria-label="Scheduled assessments" hidden>
<div class="assessments-toolbar"><div class="search-wrap"><svg aria-hidden="true"><use href="#i-search"/></svg><label for="assessment-search" class="sr-only">Search assessments</label><input id="assessment-search" class="input" type="search" placeholder="Search assessments…"></div><label for="list-class" class="sr-only">Filter by class</label><select class="input" id="list-class"><option value="all">All classes</option></select></div>
<p class="list-count" id="list-count"></p><div class="assessment-list" id="assessment-list"></div><p class="list-note">Load checks include this assessment and all other sample assessments on the same day. Other sample classes’ assessments are used for load checks but are not listed here.</p>
</section>
<footer class="page-footer"><span>Fictional schedules. Changes are saved in this browser only.</span><button type="button" id="demo-details">About this demo</button></footer>
</main>
</div>
<div class="mobile-review" id="mobile-review"><div><strong id="mobile-date">Tue, Sep 15</strong><small id="mobile-load">15 heavy · 8 overlap</small></div><button class="primary" id="review-day">Check this day <svg aria-hidden="true"><use href="#i-arrow"/></svg></button></div>
<dialog id="info-dialog" aria-labelledby="info-title"><div class="dialog-header"><h2 id="info-title">A little less test-day chaos.</h2><button class="icon-button" data-close="info-dialog" aria-label="Close explanation"><svg aria-hidden="true"><use href="#i-close"/></svg></button></div><div class="dialog-body"><p>Choose a class and a date. Daymark checks each student’s other assessments, then shows the class totals—never a list of names.</p><div class="rules"><div class="rule"><i class="health-dot"></i><div><strong>Green · No overlap</strong><p>No other assessments. This would be their only one.</p></div></div><div class="rule"><i class="health-dot amber"></i><div><strong>Yellow · Some overlap</strong><p>One other assessment. This would be their second.</p></div></div><div class="rule"><i class="health-dot red"></i><div><strong>Red · Heavy load</strong><p>Two or more other assessments. This would be their third or more.</p></div></div></div><div class="dialog-note"><strong>The day takes the highest load color.</strong> Even one heavily impacted student makes it red. Suggested dates prioritize fewer heavily impacted students, then fewer overlaps, then the nearest date. Tests and quizzes count equally in this demo. Teachers can override a warning after reviewing it.</div><div class="dialog-note"><strong>A prototype, not a connected school system.</strong> All rosters and schedules are fictional. The demo term is September 14–November 30, 2026, weekdays only; holidays are not modeled. Edits stay in this browser, with no account or syncing. Resetting restores the three sample assessments.</div><div class="dialog-note"><strong>Before using real student data:</strong> add school sign-in, roster/calendar integrations, role-based permissions, server-side conflict checks, and simultaneous-edit protection. Keep individual schedules off the client. Aggregate counts alone do not guarantee anonymity; small groups need suppression and school-approved privacy rules.</div><div class="dialog-actions"><button class="secondary" id="reset-demo">Reset demo</button><button class="primary" data-close="info-dialog">Got it</button></div></div></dialog>
<dialog id="confirm-dialog" aria-labelledby="confirm-title"><div class="dialog-header"><h2 id="confirm-title">Review this date</h2><button class="icon-button" data-close="confirm-dialog" aria-label="Close confirmation"><svg aria-hidden="true"><use href="#i-close"/></svg></button></div><div class="dialog-body"><p id="confirm-description"></p><div class="dialog-note" id="confirm-note"></div><div class="dialog-actions"><button class="secondary" id="confirm-cancel">Choose another day</button><button class="primary" id="confirm-action">Schedule anyway</button></div></div></dialog>
<div class="toast" id="toast" role="status" aria-live="polite" hidden></div>
<script>
'use strict';
/*
DAYMARK — SINGLE-FILE PROTOTYPE
No libraries, network calls, real student data, or build step.
Open this file in a modern browser. Local storage is optional; if unavailable,
changes last for the current tab. The fixed demo term makes results reproducible.
Production boundary: never deliver real per-student schedules to a browser.
Replace the synthetic engine with an authenticated server that returns aggregates,
uses school-approved small-group suppression, and rechecks at commit time.
*/
const $=id=>document.getElementById(id);
const icon=name=>`<svg aria-hidden="true"><use href="#i-${name}"/></svg>`;
const esc=s=>String(s).replace(/[&<>"']/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
const range=(a,b)=>Array.from({length:b-a+1},(_,i)=>'sample-'+String(a+i).padStart(3,'0'));
const CLASSES=[{id:'algebra',name:'Algebra II',period:2,students:range(1,30)},{id:'english',name:'English 10',period:3,students:range(11,38)},{id:'biology',name:'Biology',period:4,students:range(21,46)}];
const START='2026-09-14',END='2026-11-30',DEFAULT_DATE='2026-09-15';
const STORAGE_KEY='daymark.sample.assessments.v1';
const parseDate=s=>new Date(s+'T12:00:00');
const dateKey=d=>d.getFullYear()+'-'+String(d.getMonth()+1).padStart(2,'0')+'-'+String(d.getDate()).padStart(2,'0');
const addDays=(s,n)=>{const d=parseDate(s);d.setDate(d.getDate()+n);return dateKey(d)};
const fmt=(s,opts)=>parseDate(s).toLocaleDateString('en-US',opts);
const shortDate=s=>fmt(s,{weekday:'short',month:'short',day:'numeric'});
const fullDate=s=>fmt(s,{weekday:'long',month:'long',day:'numeric',year:'numeric'});
const validDate=s=>/^\d{4}-\d{2}-\d{2}$/.test(s)&&!isNaN(parseDate(s))&&dateKey(parseDate(s))===s;
const isWeekday=s=>{const d=parseDate(s).getDay();return d!==0&&d!==6};
const availableDate=s=>validDate(s)&&s>=START&&s<=END&&isWeekday(s);
const course=id=>CLASSES.find(c=>c.id===id);
const initialAssessments=()=>[
{id:'demo-1',title:'Functions & graphs',classId:'algebra',type:'Test',date:'2026-09-22'},
{id:'demo-2',title:'Cell structure',classId:'biology',type:'Quiz',date:'2026-09-17'},
{id:'demo-3',title:'Personal narrative',classId:'english',type:'Test',date:'2026-09-24'}];
let storageAvailable=true;
function loadAssessments(){try{const raw=localStorage.getItem(STORAGE_KEY);if(!raw)return initialAssessments();const data=JSON.parse(raw);if(!Array.isArray(data)||data.length>1000)throw new Error('Invalid saved demo');const ids=new Set();for(const a of data){if(!a||typeof a.id!=='string'||ids.has(a.id)||typeof a.title!=='string'||!a.title.trim()||a.title.length>100||!course(a.classId)||!['Test','Quiz'].includes(a.type)||!availableDate(a.date))throw new Error('Invalid assessment');ids.add(a.id)}return data}catch(e){storageAvailable=false;return initialAssessments()}}
const state={classId:'algebra',date:DEFAULT_DATE,month:'2026-09',view:'planner',editingId:null,assessments:loadAssessments()};
// Synthetic school assessments with stable, overlapping fictional class rosters.
// The Sep 15 fixture produces exactly 15 heavy / 8 some / 7 no overlap in Algebra II.
const otherGroups=[range(1,15),range(1,23),range(24,38),range(8,29),range(31,46),range(16,30)];
const schoolEvents=[];
function hash(s){let h=2166136261;for(const c of s){h^=c.charCodeAt(0);h=Math.imul(h,16777619)}return h>>>0}
for(let day=START;day<=END;day=addDays(day,1)){if(!isWeekday(day))continue;let groups=otherGroups.map((_,i)=>i).filter(i=>hash(day+':'+i)%100<25);if(day==='2026-09-15')groups=[0,1,4];if(day==='2026-09-16')groups=[4];if(day==='2026-09-18')groups=[2];for(const group of groups)schoolEvents.push({date:day,studentIds:otherGroups[group]})}
function impact(classId,date,excludeId=null){const students=course(classId).students;const counts=new Map(students.map(id=>[id,0]));for(const a of schoolEvents){if(a.date!==date)continue;for(const id of a.studentIds)if(counts.has(id))counts.set(id,counts.get(id)+1)}for(const a of state.assessments){if(a.date!==date||a.id===excludeId)continue;for(const id of course(a.classId).students)if(counts.has(id))counts.set(id,counts.get(id)+1)}let heavy=0,some=0,clear=0;for(const n of counts.values()){if(n>=2)heavy++;else if(n===1)some++;else clear++}return{heavy,some,clear,total:students.length,color:heavy?'red':some?'amber':'green'}}
function alternatives(){const options=[];for(let i=1;i<=14;i++){const date=addDays(state.date,i);if(!availableDate(date))continue;options.push({date,...impact(state.classId,date,state.editingId)})}const current=impact(state.classId,state.date,state.editingId);const candidates=current.heavy||current.some?options.filter(o=>o.heavy<current.heavy||(o.heavy===current.heavy&&o.some<current.some)):options.filter(o=>!o.heavy&&!o.some);return candidates.sort((a,b)=>a.heavy-b.heavy||a.some-b.some||a.date.localeCompare(b.date)).slice(0,3).sort((a,b)=>a.date.localeCompare(b.date))}
function saveStorage(){try{localStorage.setItem(STORAGE_KEY,JSON.stringify(state.assessments));storageAvailable=true}catch(e){storageAvailable=false}updateStorageNote()}
function updateStorageNote(){document.querySelector('.page-footer>span').textContent=storageAvailable?'Fictional schedules. Changes are saved in this browser only.':'Fictional schedules. Browser storage is unavailable; changes last for this tab.'}
function notify(message){$('toast').innerHTML=icon('check')+'<span>'+esc(message)+'</span>';$('toast').hidden=false;clearTimeout(notify.timer);notify.timer=setTimeout(()=>$('toast').hidden=true,5500)}
function updateClassButtons(){document.querySelectorAll('[data-class]').forEach(b=>{const selected=b.dataset.class===state.classId;b.classList.toggle('selected',selected);b.setAttribute('aria-pressed',String(selected))});$('class-select').value=state.classId}
function renderCalendar(){const [year,month]=state.month.split('-').map(Number);$('month-title').textContent=new Date(year,month-1,1).toLocaleDateString('en-US',{month:'long',year:'numeric'});const c=course(state.classId);$('calendar-class').textContent=c.name+' · Period '+c.period;$('prev-month').disabled=state.month<=START.slice(0,7);$('next-month').disabled=state.month>=END.slice(0,7);const first=new Date(year,month-1,1,12);const last=new Date(year,month,0,12);const monday=new Date(first);monday.setDate(monday.getDate()-((monday.getDay()+6)%7));const lastFriday=new Date(last);lastFriday.setDate(lastFriday.getDate()+(4-((lastFriday.getDay()+6)%7)));const cells=[];for(let d=new Date(monday);d<=lastFriday;d.setDate(d.getDate()+1)){if(d.getDay()===0||d.getDay()===6)continue;const key=dateKey(d);if(d.getMonth()!==month-1){cells.push('<div class="day blank" aria-hidden="true"></div>');continue}if(!availableDate(key)){cells.push(`<button class="day" disabled aria-label="${esc(fullDate(key))}, outside demo planning dates"><span class="date-number">${d.getDate()}</span></button>`);continue}const p=impact(state.classId,key,state.editingId);const label=p.heavy?p.heavy+' heavy':p.some?p.some+' overlap':'Clear';const saved=state.assessments.some(a=>a.classId===state.classId&&a.date===key&&a.id!==state.editingId);cells.push(`<button class="day${key===state.date?' selected':''}" data-date="${key}" aria-pressed="${key===state.date}" aria-label="${esc(fullDate(key))}: ${p.heavy} heavy, ${p.some} some overlap, ${p.clear} no overlap${saved?'; assessment scheduled':''}"><span class="day-top"><span class="date-number">${d.getDate()}</span><i class="health-dot ${p.color}" aria-hidden="true"></i></span><span class="load-label ${p.color}">${label}</span>${saved?'<span class="saved-mark" aria-hidden="true"></span>':''}</button>`)}$('calendar-days').innerHTML=cells.join('')}
function renderImpact(){const p=impact(state.classId,state.date,state.editingId);const heading=p.heavy?'This day is looking busy.':p.some?'Some overlap. No heavy loads.':'A little breathing room.';const description=p.heavy?`${p.heavy} of ${p.total} students would have 3 or more assessments.`:p.some?`${p.some} of ${p.total} students would have a second assessment.`:`All ${p.total} students have no other assessments on this day.`;$('impact-block').innerHTML=`<div class="impact-header"><h3>Student impact</h3><span>${p.total} students</span></div><div class="impact-summary ${p.color}">${icon(p.heavy?'warning':p.some?'clock':'check')}<div><strong>${heading}</strong><p>${description}</p></div></div><div class="impact-rows"><div class="impact-row red"><span class="impact-number">${p.heavy}</span><div><strong>Heavy load</strong><small>3+ assessments with this one</small></div></div><div class="impact-row amber"><span class="impact-number">${p.some}</span><div><strong>Some overlap</strong><small>2 assessments with this one</small></div></div><div class="impact-row green"><span class="impact-number">${p.clear}</span><div><strong>No overlap</strong><small>Only this assessment</small></div></div></div><p class="impact-note">Includes the ${$('assessment-type').value.toLowerCase()} you’re planning. Tests and quizzes count equally.</p>`;$('mobile-date').textContent=shortDate(state.date);$('mobile-load').textContent=p.heavy+' heavy · '+p.some+' overlap';$('schedule-button').innerHTML=icon(state.editingId?'check':'plus')+'<span>'+(state.editingId?'Save changes':p.heavy?'Review & schedule':'Schedule assessment')+'</span>'}
function renderRecommendations(){const p=impact(state.classId,state.date,state.editingId);const options=alternatives();const lighter=options.some(o=>o.heavy<p.heavy||(o.heavy===p.heavy&&o.some<p.some));$('recommendation-title').textContent=lighter?'Lighter days nearby':p.heavy||p.some?'Nearby school days':'Other low-conflict days';$('recommendation-list').innerHTML=options.length?options.map(o=>`<button class="recommendation" data-date="${o.date}" aria-label="Choose ${esc(fullDate(o.date))}: ${o.heavy} heavy, ${o.some} overlap"><strong>${esc(fmt(o.date,{weekday:'short',day:'numeric',month:'short'}))}${icon('arrow')}</strong><span class="${o.color}">${o.heavy} heavy</span><small>${o.some} overlap</small></button>`).join(''):'<p class="recommendation-empty">No lighter or equally clear dates in the next two weeks of this demo term. Browse the calendar to compare other dates.</p>'}
function renderPlanner(){$('schedule-button').disabled=false;updateClassButtons();renderCalendar();renderImpact();renderRecommendations();$('assessment-date').value=state.date;$('form-title').textContent=state.editingId?'Edit assessment':'Plan an assessment';$('cancel-edit').hidden=!state.editingId;$('nav-count').textContent=state.assessments.length;updateStorageNote()}
function clearError(){$('form-error').hidden=true;$('assessment-title').removeAttribute('aria-invalid');$('assessment-date').removeAttribute('aria-invalid')}
function showError(message,field){$('form-error').textContent=message;$('form-error').hidden=false;if(field){$(field).setAttribute('aria-invalid','true');$(field).focus()}}
function chooseDate(date){if(!availableDate(date))return;state.date=date;state.month=date.slice(0,7);clearError();$('schedule-button').disabled=false;renderPlanner()}
function changeClass(id){if(!course(id))return;state.classId=id;clearError();renderPlanner()}
function changeMonth(delta){const d=parseDate(state.month+'-01');d.setMonth(d.getMonth()+delta);const next=dateKey(d).slice(0,7);if(next<START.slice(0,7)||next>END.slice(0,7))return;state.month=next;renderCalendar()}
function resetForm(){state.editingId=null;$('assessment-title').value='';$('assessment-type').value='Test';$('schedule-button').disabled=false;clearError();renderPlanner()}
function showView(view){state.view=view;const planning=view==='planner';$('planner-view').hidden=!planning;$('assessments-view').hidden=planning;$('new-assessment').hidden=planning;$('term-label').hidden=!planning;$('mobile-review').hidden=!planning;$('page-title').textContent=planning?'Good days for big tests.':'On the calendar.';$('page-lead').textContent=planning?'A little planning. A lighter day for your students.':'Your assessments, with room to move things around.';document.querySelectorAll('[data-view]').forEach(b=>{b.classList.toggle('active',b.dataset.view===view);if(b.dataset.view===view)b.setAttribute('aria-current','page');else b.removeAttribute('aria-current')});if(planning)renderPlanner();else renderAssessments()}
function renderAssessments(){const q=$('assessment-search').value.trim().toLowerCase();const selected=$('list-class').value;const list=[...state.assessments].filter(a=>(selected==='all'||a.classId===selected)&&(a.title+' '+course(a.classId).name+' '+a.type).toLowerCase().includes(q)).sort((a,b)=>a.date.localeCompare(b.date)||a.title.localeCompare(b.title));$('nav-count').textContent=state.assessments.length;$('list-count').textContent=list.length+' assessment'+(list.length===1?'':'')+(q||selected!=='all'?' matching your filters':' scheduled');if(!list.length){$('assessment-list').innerHTML=`<div class="empty-state">${icon('calendar')}<h2>${state.assessments.length?'Nothing matches just yet.':'A clear calendar.'}</h2><p>${state.assessments.length?'Try another search or show all classes.':'Plan your first assessment and give everyone a little notice.'}</p><button class="secondary" id="empty-action">${state.assessments.length?'Clear filters':'Plan an assessment'}</button></div>`;return}$('assessment-list').innerHTML=list.map(a=>{const c=course(a.classId),p=impact(a.classId,a.date,a.id);const badge=p.heavy?p.heavy+' heavy':p.some?p.some+' overlap':'No overlap';return`<article class="assessment-row"><div class="date-tile" aria-label="${esc(fullDate(a.date))}"><span>${esc(fmt(a.date,{month:'short'}))}</span><strong>${parseDate(a.date).getDate()}</strong></div><div><h2 class="assessment-name">${esc(a.title)}</h2><p class="assessment-meta">${esc(c.name)} · Period ${c.period} &nbsp;·&nbsp; ${esc(a.type)} &nbsp;·&nbsp; ${esc(fmt(a.date,{weekday:'long'}))}</p></div><span class="badge ${p.color}" title="${p.heavy} heavy, ${p.some} some overlap, ${p.clear} no overlap">${badge}</span><div class="row-actions"><button class="edit-button" data-edit="${esc(a.id)}" aria-label="Edit ${esc(a.title)}">Edit</button><button class="icon-button delete-button" data-delete="${esc(a.id)}" aria-label="Delete ${esc(a.title)}">${icon('trash')}</button></div></article>`}).join('')}
let pendingAction=null;
function confirmAction({title,description,note,cancel,action,danger=false,onConfirm}){$('confirm-title').textContent=title;$('confirm-description').textContent=description;$('confirm-note').textContent=note;$('confirm-cancel').textContent=cancel;$('confirm-action').textContent=action;$('confirm-action').classList.toggle('danger-button',danger);pendingAction=onConfirm;$('confirm-dialog').showModal();$('confirm-cancel').focus()}
function validateForm(){clearError();if(!$('assessment-title').value.trim()){showError('Give this assessment a name first.','assessment-title');return false}const date=$('assessment-date').value;if(!validDate(date)){showError('Choose a valid assessment date.','assessment-date');return false}if(date<START||date>END){showError('This demo covers September 14–November 30, 2026.','assessment-date');return false}if(!isWeekday(date)){showError('Choose a school day, Monday through Friday.','assessment-date');return false}state.date=date;return true}
function commitAssessment(){if(!validateForm())return;const a={id:state.editingId||('local-'+(globalThis.crypto?.randomUUID?.()||Date.now()+'-'+Math.random().toString(16).slice(2))),title:$('assessment-title').value.trim(),classId:state.classId,type:$('assessment-type').value,date:state.date};if(state.assessments.some(x=>x.id!==a.id&&x.classId===a.classId&&x.date===a.date&&x.type===a.type&&x.title.toLowerCase()===a.title.toLowerCase())){showError('That assessment is already scheduled for this class and date.','assessment-title');return}const wasEdit=Boolean(state.editingId);if(wasEdit)state.assessments=state.assessments.map(x=>x.id===a.id?a:x);else state.assessments.push(a);saveStorage();resetForm();$('assessment-search').value='';$('list-class').value='all';showView('assessments');notify((wasEdit?'Assessment updated':'Assessment scheduled')+' · '+shortDate(a.date)+(storageAvailable?'':' · This tab only'));$('page-title').setAttribute('tabindex','-1');$('page-title').focus()}
function submitAssessment(e){e.preventDefault();if(!validateForm())return;renderPlanner();const p=impact(state.classId,state.date,state.editingId);if(p.heavy)confirmAction({title:'A quick workload check.',description:`${p.heavy} of ${p.total} students would have 3 or more assessments on ${shortDate(state.date)}.`,note:'You can still use this date if needed, or go back and choose a lighter day. No student names are shown.',cancel:'Choose another day',action:state.editingId?'Save anyway':'Schedule anyway',onConfirm:commitAssessment});else commitAssessment()}
function editAssessment(id){const a=state.assessments.find(a=>a.id===id);if(!a)return;state.editingId=a.id;state.classId=a.classId;state.date=a.date;state.month=a.date.slice(0,7);$('assessment-title').value=a.title;$('assessment-type').value=a.type;clearError();$('schedule-button').disabled=false;showView('planner');$('plan-panel').scrollIntoView({behavior:'auto',block:'start'});$('assessment-title').focus({preventScroll:true})}
function deleteAssessment(id){const a=state.assessments.find(a=>a.id===id);if(!a)return;confirmAction({title:'Remove this assessment?',description:a.title+' · '+shortDate(a.date),note:'It will be removed from your demo calendar and all student workload calculations. This cannot be undone.',cancel:'Keep assessment',action:'Remove',danger:true,onConfirm:()=>{state.assessments=state.assessments.filter(x=>x.id!==id);if(state.editingId===id)resetForm();saveStorage();renderAssessments();renderPlanner();notify('Assessment removed.');$('page-title').focus()}})}
// Populate static controls once, so inputs retain focus while previews refresh.
$('class-list').innerHTML=CLASSES.map(c=>`<button class="class-button" data-class="${c.id}"><span class="class-dot" aria-hidden="true"></span><span>${esc(c.name)}<small>Period ${c.period}</small></span></button>`).join('');
for(const c of CLASSES){const o=document.createElement('option');o.value=c.id;o.textContent=c.name+' · Period '+c.period;$('class-select').append(o);const filter=o.cloneNode(true);$('list-class').append(filter)}
$('assessment-title').value='Unit 2 · Quadratic functions';
$('class-select').addEventListener('change',e=>changeClass(e.target.value));
$('class-list').addEventListener('click',e=>{const b=e.target.closest('[data-class]');if(b){changeClass(b.dataset.class);showView('planner')}});
$('calendar-days').addEventListener('click',e=>{const b=e.target.closest('[data-date]');if(b){chooseDate(b.dataset.date);document.querySelector(`#calendar-days [data-date="${state.date}"]`)?.focus({preventScroll:true})}});
$('recommendation-list').addEventListener('click',e=>{const b=e.target.closest('[data-date]');if(b){chooseDate(b.dataset.date);$('assessment-date').focus({preventScroll:true});notify('Previewing '+shortDate(state.date)+'. Not scheduled yet.')}});
$('prev-month').addEventListener('click',()=>changeMonth(-1));$('next-month').addEventListener('click',()=>changeMonth(1));$('selected-month').addEventListener('click',()=>{state.month=state.date.slice(0,7);renderCalendar()});
$('assessment-date').addEventListener('change',e=>{const v=e.target.value;if(!availableDate(v)){showError(!validDate(v)?'Choose a valid date.':v<START||v>END?'This demo covers September 14–November 30, 2026.':'Choose a school day, Monday through Friday.');$('assessment-date').setAttribute('aria-invalid','true');$('schedule-button').disabled=true;$('impact-block').innerHTML='<div class="dialog-note">Choose a valid school day to see student impact. No assessment has been scheduled.</div>';return}chooseDate(v)});
$('assessment-type').addEventListener('change',()=>{if(availableDate($('assessment-date').value))renderImpact()});
$('assessment-title').addEventListener('input',()=>{if($('assessment-title').value.trim()&&availableDate($('assessment-date').value))clearError()});
$('plan-form').addEventListener('submit',submitAssessment);$('cancel-edit').addEventListener('click',()=>{resetForm();notify('Changes discarded.')});
document.querySelectorAll('[data-view]').forEach(b=>b.addEventListener('click',()=>showView(b.dataset.view)));
$('new-assessment').addEventListener('click',()=>{resetForm();showView('planner');$('assessment-title').focus()});
$('assessment-search').addEventListener('input',renderAssessments);$('list-class').addEventListener('change',renderAssessments);
$('assessment-list').addEventListener('click',e=>{const edit=e.target.closest('[data-edit]'),del=e.target.closest('[data-delete]');if(edit)editAssessment(edit.dataset.edit);else if(del)deleteAssessment(del.dataset.delete);else if(e.target.closest('#empty-action')){if(state.assessments.length){$('assessment-search').value='';$('list-class').value='all';renderAssessments()}else{resetForm();showView('planner');$('assessment-title').focus()}}});
for(const id of ['help-button','demo-details'])$(id).addEventListener('click',()=>$('info-dialog').showModal());
document.querySelectorAll('[data-close]').forEach(b=>b.addEventListener('click',()=>$(b.dataset.close).close()));
$('confirm-cancel').addEventListener('click',()=>$('confirm-dialog').close());
$('confirm-action').addEventListener('click',()=>{const action=pendingAction;pendingAction=null;$('confirm-dialog').close();if(action)action()});
$('confirm-dialog').addEventListener('close',()=>{pendingAction=null});
$('reset-demo').addEventListener('click',()=>{$('info-dialog').close();confirmAction({title:'Start fresh?',description:'Reset this browser’s demo to the original sample schedules.',note:'Your added assessments and edits will be removed. The three starting assessments will be restored.',cancel:'Keep my changes',action:'Reset demo',danger:true,onConfirm:()=>{state.assessments=initialAssessments();state.classId='algebra';state.date=DEFAULT_DATE;state.month='2026-09';saveStorage();resetForm();$('assessment-title').value='Unit 2 · Quadratic functions';$('assessment-search').value='';$('list-class').value='all';showView('planner');notify('Demo reset. Ready for a fresh start.')}})});
$('review-day').addEventListener('click',()=>{$('plan-panel').scrollIntoView({behavior:matchMedia('(prefers-reduced-motion: reduce)').matches?'auto':'smooth',block:'start'});$('assessment-title').focus({preventScroll:true})});
// Move the help button into the compact navigation on phones without duplicating controls.
const mobileQuery=matchMedia('(max-width:700px)');function adaptNav(){const target=mobileQuery.matches?document.querySelector('.mobile-help'):$('sidebar-bottom');if(mobileQuery.matches)target.append($('help-button'));else target.prepend($('help-button'));document.querySelector('.mobile-help').style.display=mobileQuery.matches?'block':'none';$('sidebar-bottom').hidden=mobileQuery.matches}adaptNav();mobileQuery.addEventListener('change',adaptNav);
// Same-browser tabs stay in sync. A server must provide this guarantee across users.
window.addEventListener('storage',e=>{if(e.key===STORAGE_KEY){state.assessments=loadAssessments();if(state.editingId&&!state.assessments.some(a=>a.id===state.editingId))resetForm();renderPlanner();if(state.view==='assessments')renderAssessments();notify('Demo schedules updated from another tab.')}});
document.querySelector('.key-note').after($('mobile-review'));
renderPlanner();
</script>
</body>
</html>