From 9ab7d16bd9ef51431e389fede8112f665e551cc9 Mon Sep 17 00:00:00 2001 From: Guy Sandler Date: Sat, 29 Aug 2026 14:25:26 -0700 Subject: [PATCH] light mode fixes --- _locales/en/messages.json | 3 + css/content.css | 23 ++++++++ html/popup.html | 6 ++ js/background.js | 1 + js/content.js | 116 ++++++++++++++++++++++++++++++++++---- js/popup.js | 7 ++- 6 files changed, 143 insertions(+), 13 deletions(-) diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 29d47e8..fff3f88 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -316,5 +316,8 @@ }, "grade_analytics_desc": { "message": "Adds an Analytics toggle to the left side of course grade pages with a score-distribution chart and your overall grade over time." + }, + "grade_analytics_zones": { + "message": "Colored grade zones" } } \ No newline at end of file diff --git a/css/content.css b/css/content.css index 90474a4..a1d5da0 100644 --- a/css/content.css +++ b/css/content.css @@ -366,3 +366,26 @@ padding: 0 4px; color: var(--bctext-1, #5b6770); } +/* ===== Grades table: scale down with narrow windows ===== + Canvas lays out #grades_summary with content-driven auto sizing plus hard + min-widths (title 150px, details 80px) and nowrap cells (due, score, + details), giving it a ~780px floor below which the whole table overflows + the page instead of shrinking. Switch it to fixed layout with percentage + column widths (set on the header row, which drives fixed layout) and let + tight cells wrap, so the header, body and rows all scale with the + viewport. #assignments scrolls horizontally as a last resort (e.g. while a + what-if score input is open). */ +#grades_summary { table-layout: fixed; width: 100%; } +#grades_summary thead th:nth-child(1) { width: 30%; } /* Name */ +#grades_summary thead th:nth-child(2) { width: 12%; } /* Due */ +#grades_summary thead th:nth-child(3) { width: 10%; } /* Submitted */ +#grades_summary thead th:nth-child(4) { width: 7%; } /* Status */ +#grades_summary thead th:nth-child(5) { width: 11%; } /* Score */ +#grades_summary thead th:nth-child(6) { width: 3%; } /* asset processors */ +#grades_summary thead th:nth-child(7) { width: 15%; } /* Details */ +#grades_summary thead th:nth-child(8) { width: 3%; } /* progress */ +#grades_summary td.due, +#grades_summary td.assignment_score, +#grades_summary td.details { white-space: normal; } +#assignments { overflow-x: auto; } + diff --git a/html/popup.html b/html/popup.html index b5166f0..d4dfc9a 100644 --- a/html/popup.html +++ b/html/popup.html @@ -231,6 +231,12 @@ Adds an Analytics toggle to the left side of course grade pages with a score-distribution chart and your overall grade over time. +
+
+ + +
+
diff --git a/js/background.js b/js/background.js index 2029a6f..f244748 100644 --- a/js/background.js +++ b/js/background.js @@ -118,6 +118,7 @@ chrome.runtime.onInstalled.addListener(function () { "sidebar_blur": 0, "global_search": false, "grade_analytics": true, + "grade_analytics_zones": false, } }; diff --git a/js/content.js b/js/content.js index 8201a45..d9917ae 100644 --- a/js/content.js +++ b/js/content.js @@ -956,6 +956,10 @@ function applyOptionsChanges(changes) { case "grade_analytics": watchGradeAnalytics(); break; + case "grade_analytics_zones": + // Colored 10% zones on the line chart — just redraw the charts. + if (gradeAnalyticsActive() && gaOpen && gaData) renderGradeAnalytics(); + break; case "quiz_safe_mode": // Toggling safe mode changes which features run on quiz pages; reload // so the gating is applied cleanly. @@ -1089,6 +1093,18 @@ async function applyCustomBackground() { background-color: color-mix(in srgb, var(--bcbackground-0), transparent ${bgTransparent}%); border-radius: 5px; } + /* Native left nav column: #left-side > #sticky-container.ic-sticky-frame + (the course/account/group menu links). Tint the whole #left-side column + rather than the inner .ic-sticky-frame, whose height only wraps its + links — the column spans the full viewport height like the other + sidebars, at the same bg_opacity/bg_blur as the Better Todo List + panel. Without this, a custom background (most visible in light mode) + shows through untinted behind the nav links. */ + #left-side { + backdrop-filter: blur(${bgBlur}px) !important; + -webkit-backdrop-filter: blur(${bgBlur}px) !important; + background-color: color-mix(in srgb, var(--bcbackground-0), transparent ${bgTransparent}%) !important; + } /* Recent feedback lives in #right-side. The dark-mode CSS (darkmodecss.js) recolors its text, but those rules are dark-mode- only — so in light mode + custom background the sub-text (context, @@ -1305,6 +1321,20 @@ async function applyCustomBackground() { -webkit-backdrop-filter: blur(${cardBlur}px) saturate(120%) !important;` : `background: var(--bcbackground-0) !important;`} } + /* Card header strip (the course-nickname bar under the hero). Canvas + paints it a solid light color ($ic-color-light) and nothing overrides + that in light mode, so with card transparency on it reads as a solid + band across an otherwise translucent card. Mirror the card surface: + transparent cards drop the strip's background so the card's glass + (tint + blur already applied to .ic-DashboardCard) shows through; + opaque cards paint it the same solid theme color as the card body. + Dark mode already flattens this strip via darkmodecss.js, so this + is inert there (same value). */ + .ic-DashboardCard__header_content { + ${cardTransparency + ? `background: none !important;` + : `background: var(--bcbackground-0) !important;`} + } tr.student_assignment.assignment_graded.editable > * { border:none!important }`; @@ -3740,8 +3770,15 @@ async function setupBetterSidebar(mode = getSidebarLayoutMode()) { contentMain?.style.setProperty("backdrop-filter", `blur(${Math.max(0, Math.min(30, Number(options.bg_blur ?? 8)))}px)`, "important"); contentMain?.style.setProperty("-webkit-backdrop-filter", `blur(${Math.max(0, Math.min(30, Number(options.bg_blur ?? 8)))}px)`, "important"); } - const sidebarParent = layoutMode === "course" && leftSide ? leftSide : mainWrapper; - if (layoutMode === "course" && leftSide) { + // The rail must always render leftmost. Course-layout pages already + // prepend it into #left-side; dash-layout pages that still have a + // native left nav (accounts, groups, etc.) must too — otherwise the + // native #left-side column (made position:static above) flows before + // #not_right_side and shows up to the LEFT of the Better Sidebar, + // looking like a competing sidebar once the custom background tints + // it. Prepending keeps the order: [Better Sidebar rail][native nav]. + const sidebarParent = leftSide ? leftSide : mainWrapper; + if (leftSide) { leftSide.style.display = "flex"; leftSide.style.flexDirection = "row"; leftSide.style.alignItems = "stretch"; @@ -3749,9 +3786,6 @@ async function setupBetterSidebar(mode = getSidebarLayoutMode()) { leftSide.style.gap = "0"; } document.querySelector(".ic-app-nav-toggle-and-crumbs")?.style.setProperty("display", "none"); - if (layoutMode !== "course") { - document.getElementById("left-side")?.style.removeProperty("display"); - } if (layoutMode == "dash") { document.getElementById("header")?.style.setProperty("display", "none"); } @@ -6333,6 +6367,22 @@ const GA_BUCKETS = [ { label: "0-9", min: 0, max: 10, color: "#7f1d1d" }, ]; const GA_UNGRADED_COLOR = "#6b7280"; +// 5%-wide zone colors for the line chart background: the doughnut's bucket +// colors interpolated at 5% steps (dark red at 0 → green at 100), so every +// 5% band gets its own shade. +const GA_ZONE_COLORS = (() => { + const stops = GA_BUCKETS.slice().reverse(); // 0-9 (dark red) → 90+ (green) + const rgb = (h) => [parseInt(h.slice(1, 3), 16), parseInt(h.slice(3, 5), 16), parseInt(h.slice(5, 7), 16)]; + const lerp = (a, b, t) => Math.round(a + (b - a) * t); + return Array.from({ length: 20 }, (_, i) => { + const m = i * 5 + 2.5; // band midpoint + const k = Math.min(stops.length - 1, Math.floor(m / 10)); + if (k >= stops.length - 1) return stops[stops.length - 1].color; + const t = (m - k * 10) / 10; + const c1 = rgb(stops[k].color), c2 = rgb(stops[k + 1].color); + return `rgb(${lerp(c1[0], c2[0], t)},${lerp(c1[1], c2[1], t)},${lerp(c1[2], c2[2], t)})`; + }); +})(); const GA_OPEN_KEY = "grade_analytics_open"; async function getGradeAnalyticsOpenState() { @@ -6877,6 +6927,22 @@ function gaDrawLine(canvas, tooltip) { // can cheaply redraw with a highlight on the active dot. const draw = (hover) => { ctx.clearRect(0, 0, w, h); + // Optional colored 5% zones behind the plot ("Colored grade zones" + // popup option), tinted red→green. With Fit Y axis on, bands are + // clipped to the visible range. + if (options.grade_analytics_zones) { + GA_ZONE_COLORS.forEach((color, i) => { + const top = Math.min(yMax, (i + 1) * 5); + const bottom = Math.max(yMin, i * 5); + if (top <= bottom) return; + const y1 = pad.t + (1 - (top - yMin) / (yMax - yMin)) * (h - pad.t - pad.b); + const y2 = pad.t + (1 - (bottom - yMin) / (yMax - yMin)) * (h - pad.t - pad.b); + ctx.globalAlpha = 0.3; + ctx.fillStyle = color; + ctx.fillRect(pad.l, y1, w - pad.l - pad.r, y2 - y1); + ctx.globalAlpha = 1; + }); + } // Y grid: 5 evenly spaced lines across the current range. ctx.font = "11px Lato, sans-serif"; ctx.textAlign = "right"; ctx.textBaseline = "middle"; @@ -6913,8 +6979,11 @@ function gaDrawLine(canvas, tooltip) { }; // Subtle tick under every data point on sparse charts. if (pts.length <= 25) pts.forEach((_, i) => tick(X(i))); + // Month boundaries: the first point of each calendar month. Points are + // already in chronological order, so a walking month counter (wrapping + // across Dec → Jan) maps each point to an absolute month. + const boundaries = []; let prevM = null, absM = null; - let lastLabeled = null; pts.forEach((p, i) => { const m = months.indexOf((p.due || "").trim().slice(0, 3)); if (m < 0) return; // no due date on this point @@ -6922,13 +6991,38 @@ function gaDrawLine(canvas, tooltip) { else if (m >= prevM) absM += m - prevM; else absM += 12 - prevM + m; // wrapped to a new year prevM = m; - if (absM === lastLabeled) return; - lastLabeled = absM; - tick(X(i)); + const last = boundaries[boundaries.length - 1]; + if (!last || last.absM !== absM) { + boundaries.push({ i, absM, label: (p.due || "").trim().slice(0, 3) }); + } + }); + boundaries.forEach(b => tick(X(b.i))); + // Collision-aware labels: greedily keep a month label only if it fits + // after the previous kept one (labels are centered, so compare against + // half-widths plus a 6px gap). The first boundary always gets a label; + // so does the last — if it doesn't fit, earlier labels are dropped to + // make room, so the axis ends on a real month instead of mid-run. + const GAP = 6; + const kept = []; + boundaries.forEach((b, idx) => { + const x = X(b.i); + const w = ctx.measureText(b.label).width; + if (idx === 0 || x - w / 2 > kept[kept.length - 1].right + GAP) { + kept.push({ ...b, x, right: x + w / 2 }); + } + }); + const lastB = boundaries[boundaries.length - 1]; + if (lastB && kept[kept.length - 1].i !== lastB.i) { + const x = X(lastB.i); + const w = ctx.measureText(lastB.label).width; + while (kept.length && kept[kept.length - 1].right + GAP > x - w / 2) kept.pop(); + kept.push({ ...lastB, x, right: x + w / 2 }); + } + kept.forEach((b, k) => { // Anchor the edge labels inward so they don't clip. - ctx.textAlign = i === 0 ? "left" : (i === pts.length - 1 ? "right" : "center"); + ctx.textAlign = k === 0 ? "left" : (b.i === pts.length - 1 ? "right" : "center"); ctx.fillStyle = text; - ctx.fillText((p.due || "").trim().slice(0, 3), X(i), h - pad.b + 6); + ctx.fillText(b.label, b.x, h - pad.b + 6); }); // Hover indicator: dashed vertical guide plus a halo ring around the // hovered dot so it's obvious which point the tooltip describes. diff --git a/js/popup.js b/js/popup.js index 2e14cf2..5b31fd6 100644 --- a/js/popup.js +++ b/js/popup.js @@ -1,5 +1,6 @@ const syncedSwitches = ['remind', 'tab_icons', 'dark_mode', 'remlogo', 'full_width', 'auto_dark', 'assignments_due', 'gpa_calc', 'gradient_cards', 'disable_color_overlay', 'dashboard_grades', 'dashboard_notes', 'better_todo', 'better_sidebar', 'condensed_cards', 'hide_new_canvas', 'hide_sequence_footer', 'center_cards', 'quiz_safe_mode', 'global_search', 'grade_analytics']; const syncedSubOptions = [ + "grade_analytics_zones", "todo_hide_feedback", "todo_full_height", "todo_confetti", @@ -151,6 +152,7 @@ const defaultOptions = { "equal_height_cards": false, "hide_new_canvas": true, "hide_sequence_footer": false, + "grade_analytics_zones": false, "quiz_safe_mode": false, "dark_mode_fix": [], "assignment_states": {}, @@ -570,7 +572,7 @@ function toggleAlternateColorsVisibility(darkModeOn) { // Hide a toggle's sub-options when it's off; auto_dark only hides its time clocks. function toggleSubOptionsVisibility(option, isOn) { - const togglesWithSubOptions = ["gpa_calc", "assignments_due", "better_todo", "auto_dark"]; + const togglesWithSubOptions = ["gpa_calc", "assignments_due", "better_todo", "auto_dark", "grade_analytics"]; if (!togglesWithSubOptions.includes(option)) return; const optionEl = document.getElementById(option); if (!optionEl) return; @@ -953,6 +955,7 @@ function setup() { "fitImageToScreen", "card_transparency", "customCardStyles", + "grade_analytics_zones", ], tabs: { "advanced-settings": { @@ -1110,7 +1113,7 @@ function setup() { }); }); toggleBetterSidebarSubOptions(sync["better_sidebar"] === true); - ["gpa_calc", "assignments_due", "better_todo", "auto_dark"].forEach(opt => { + ["gpa_calc", "assignments_due", "better_todo", "auto_dark", "grade_analytics"].forEach(opt => { toggleSubOptionsVisibility(opt, sync[opt] === true); }); toggleAlternateColorsVisibility(sync["dark_mode"] === true);