light mode fixes

This commit is contained in:
Guy Sandler 2026-08-29 14:25:26 -07:00
parent 4184b717e8
commit 9ab7d16bd9
6 changed files with 143 additions and 13 deletions

View File

@ -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"
}
}

View File

@ -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; }

View File

@ -231,6 +231,12 @@
<span class="cr-info-bubble-text" data-i18n="grade_analytics_desc">Adds an Analytics toggle to the left side of course grade pages with a score-distribution chart and your overall grade over time.</span>
</span>
</div>
<div class="sub-options">
<div class="sub-option" style="margin-top:5px">
<input type="checkbox" id="grade_analytics_zones" name="grade_analytics_zones">
<label for="grade_analytics_zones" class="sub-text" data-i18n="grade_analytics_zones">Colored grade zones</label>
</div>
</div>
</div>
<div class="social-buttons">
<a class="discord-button" href="https://discord.gg/GjFh4JA3wh" target="_blank" rel="noopener noreferrer" title="Join our Discord">

View File

@ -118,6 +118,7 @@ chrome.runtime.onInstalled.addListener(function () {
"sidebar_blur": 0,
"global_search": false,
"grade_analytics": true,
"grade_analytics_zones": false,
}
};

View File

@ -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.

View File

@ -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);