more minor fixes and additions

This commit is contained in:
Guy Sandler 2026-08-31 22:31:56 -07:00
parent eaa2a85295
commit 3e6a5927d7
4 changed files with 88 additions and 42 deletions

View File

@ -860,6 +860,13 @@
<span class="sub-text">px</span>
</div>
</div>
<div class="sub-option">
<div style="margin-top: 5px;display: flex;gap: 8px;align-items: center">
<span class="sub-text" style="width:75px;">Image Roundness</span>
<input type="number" id="imageRoundness" class="card-input" min="0" max="50" value="0" style="width:80px;">
<span class="sub-text">px</span>
</div>
</div>
<div class="sub-option">
<div style="margin-top: 5px;display: flex;gap: 8px;align-items: center">
<span class="sub-text" style="width:75px;">Extra Card Spacing</span>

View File

@ -57,7 +57,7 @@ chrome.runtime.onInstalled.addListener(function () {
"full_width": null,
"remlogo": null,
"gpa_calc_bounds": {
"A+": { "cutoff": 97, "gpa": 4.3 },
"A+": { "cutoff": 97, "gpa": 4.0 },
"A": { "cutoff": 93, "gpa": 4 },
"A-": { "cutoff": 90, "gpa": 3.7 },
"B+": { "cutoff": 87, "gpa": 3.3 },
@ -103,9 +103,10 @@ chrome.runtime.onInstalled.addListener(function () {
"custom_styles": "",
"imageSize": 100,
"cardRoundness": 5,
"imageRoundness": 0,
'cardSpacing': 0,
"cardWidth": 262,
"cardHeight": 250,
"cardHeight": 146,
"customCardStyles": false,
"customBackgroundLink": "",
"customBackgroundScale": 100,

View File

@ -995,6 +995,7 @@ function applyOptionsChanges(changes) {
break;
case "imageSize":
case "cardRoundness":
case "imageRoundness":
case "cardSpacing":
case "cardWidth":
case "cardHeight":
@ -3572,7 +3573,7 @@ function populateAssignments(iscompleted = false) {
<div style="width:calc(100% - 40px);height:80%;display:flex;flex-direction:column;gap:5px;padding-left:2px;box-sizing:border-box;overflow:hidden;position:relative;">
<div style="display:flex;flex-direction:column;gap:3px;">
<span style="color:${classNameColor};font-size:12px;margin-top:-2px;display:block;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%;box-sizing:border-box;padding-right:22px;">${item.context_name}</span>
<a href="${taskHref}" style="color:inherit;text-decoration:none;font-weight:bold;text-overflow:ellipsis;font-size:14px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-top:-5px;">${item.plannable.title}</a>
<a href="${taskHref}" style="color:inherit;text-decoration:none;font-weight:bold;font-size:14px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;box-sizing:border-box;padding-right:28px;margin-top:-5px;">${item.plannable.title}</a>
<span style="color:var(--bctext-0);font-size:12px;margin-top:-5px;">${convertToDueDate(item.plannable_date)}</span>
</div>
${editButtonSvg}
@ -4854,14 +4855,24 @@ Dashboard grades
// Map a percentage to a letter grade using the user's configurable GPA
// calculator cutoffs (A+ down to F). Returns null when no grade is present.
// Picks the letter with the HIGHEST cutoff the percent meets so the result
// doesn't depend on the key order of the stored bounds object — theme imports
// can reorder keys (e.g. alphabetically, where "A" precedes "A+"), which made
// "+" grades unreachable and displayed e.g. 100% as "A". Cutoffs are coerced
// with Number() so string values carried in by imported themes still match.
function percentToLetterGrade(percent) {
const bounds = options.gpa_calc_bounds;
if (!bounds || typeof percent !== "number") return null;
let best = null;
let bestCutoff = -Infinity;
for (const letter of Object.keys(bounds)) {
const cutoff = bounds[letter]?.cutoff;
if (typeof cutoff === "number" && percent >= cutoff) return letter;
const cutoff = Number(bounds[letter]?.cutoff);
if (Number.isFinite(cutoff) && percent >= cutoff && cutoff > bestCutoff) {
best = letter;
bestCutoff = cutoff;
}
}
return null;
return best;
}
function insertGrades() {
@ -5820,31 +5831,37 @@ function applyAestheticChanges() {
if (options.customCardStyles === true) {
if (options.imageSize !== undefined && options.imageSize !== 100) style.textContent += `.ic-DashboardCard__header_image {transform: scale(${options.imageSize / 100})!important; }`;
if (options.cardRoundness !== undefined && options.cardRoundness !== 5) style.textContent += `.ic-DashboardCard {border-radius: ${options.cardRoundness}px!important;}`;
// Rounds the header image band. The photo is the background of
// .ic-DashboardCard__header_image, and .ic-DashboardCard__header_hero (a
// child that covers the full photo with its colored overlay + 1px
// border) sits on top of it. border-radius only clips the element it's
// on — rounding the parent alone leaves the hero's square overlay
// covering the corners, and rounding the hero alone leaves the photo's
// square corners behind it — so both elements need the same radius.
// Default 0; guard skips the default so stock cards keep square corners.
if (options.imageRoundness !== undefined && options.imageRoundness !== 0) style.textContent += `.ic-DashboardCard__header_image, .ic-DashboardCard__header_hero {border-radius: ${options.imageRoundness}px!important;}`;
if (options.cardSpacing !== undefined && options.cardSpacing !== 0) style.textContent += `.ic-DashboardCard {margin-right: ${options.cardSpacing / 2}px!important; margin-bottom: ${options.cardSpacing / 2}px!important;}`;
if (options.cardWidth !== undefined && options.cardWidth !== 262) style.textContent += `.ic-DashboardCard {width: ${options.cardWidth}px!important;}`;
// Always emit a fixed card height when custom card styles are on. The old
// `!== 250` guard silently dropped the height rule when cardHeight matched
// the default, which is exactly what happens after importing a theme that
// carries the default cardHeight (250) — leaving cards content-sized.
// Content-sized cards plus the dashboard reflow loop trigger Firefox scroll
// anchoring to yank the viewport back up while scrolling. A fixed height
// (even the default 250px) keeps layout stable.
if (options.cardHeight !== undefined && options.cardHeight !== null && options.cardHeight !== "") {
style.textContent += `.ic-DashboardCard {height: ${options.cardHeight}px!important;}`;
// Canvas sets overflow:hidden on .ic-DashboardCard. With a fixed
// height that clips the appended .canvasrefined-card-assignment area
// (the assignment rows live at the bottom of the card), making the
// .canvasrefined-assignment-link anchors unclickable for users with
// custom card styles enabled. Allow overflow so those rows stay
// visible and interactive when card assignments are shown.
if (options.assignments_due === true) style.textContent += `.ic-DashboardCard {overflow: visible!important;}`;
// Card height sizes the image band via .ic-DashboardCard__header_hero —
// the element Canvas pins at 146px that actually drives the header image
// height (both .ic-DashboardCard and .ic-DashboardCard__header are
// content-sized, so a height on either just clips or adds dead space).
// Everything else — title, actions, and the appended
// .canvasrefined-card-assignment rows — flows below the hero, so the card
// grows with the assignment list and nothing gets clipped. Skipped when
// condensed cards is on, since that mode pins the hero at 60px.
if (options.condensed_cards !== true && options.cardHeight !== undefined && options.cardHeight !== null && options.cardHeight !== "") {
style.textContent += `.ic-DashboardCard__header_hero {height: ${options.cardHeight}px!important;}`;
}
// Inner card padding. Applied to the whole .ic-DashboardCard box (the
// element that holds the hero header, title, and action buttons) so the
// colored hero header and its content all get consistent breathing room
// from the card's edges. Guarded by !== 0 (default).
// Inner card padding. Applied to the whole .ic-DashboardCard box so the
// hero header, title, and action buttons all get breathing room from the
// card's edges. Canvas sizes the card with border-box + a fixed width,
// so padding alone squishes the content area (narrower image/rows)
// instead of expanding the card — switch to content-box so the padding
// grows the card outward and the content keeps its full width.
// Guarded by > 0 (default).
if (options.cardPadding !== undefined && Number(options.cardPadding) > 0) {
style.textContent += `.ic-DashboardCard {padding: ${options.cardPadding}px!important;}`;
style.textContent += `.ic-DashboardCard {padding: ${options.cardPadding}px!important; box-sizing: content-box!important;}`;
}
}

View File

@ -32,6 +32,7 @@ const syncedSubOptions = [
"customCardStyles",
"imageSize",
"cardRoundness",
"imageRoundness",
"cardSpacing",
"cardWidth",
"cardHeight",
@ -54,7 +55,7 @@ const localSwitches = [];
// Theme export only carries visual settings, never personal productivity data.
const exportDarkSchedule = ["auto_dark", "auto_dark_start", "auto_dark_end", "device_dark"];
const exportCardColorToggles = ["gradient_cards", "disable_color_overlay"];
const exportCardStyles = ["customCardStyles", "imageSize", "cardRoundness", "cardSpacing", "cardWidth", "cardHeight", "cardPadding"];
const exportCardStyles = ["customCardStyles", "imageSize", "cardRoundness", "imageRoundness", "cardSpacing", "cardWidth", "cardHeight", "cardPadding"];
const exportLayout = ["full_width", "center_cards", "condensed_cards", "equal_height_cards", "remlogo", "hide_new_canvas", "tab_icons"];
const exportSidebar = ["better_sidebar", "sidebar_scale"];
const exportTodo = ["better_todo", "todo_hide_feedback", "todo_full_height", "todo_confetti", "todo_progress_rings", "todo_timeframe", "todo_hr24", "todo_separate_scrollbar", "todo_alternate_colors", "todo_ignore_card_colors", "todo_remove_icons", "hover_preview"];
@ -135,7 +136,7 @@ const defaultOptions = {
"full_width": null,
"remlogo": null,
"gpa_calc_bounds": {
"A+": { "cutoff": 97, "gpa": 4.3 },
"A+": { "cutoff": 97, "gpa": 4.0 },
"A": { "cutoff": 93, "gpa": 4 },
"A-": { "cutoff": 90, "gpa": 3.7 },
"B+": { "cutoff": 87, "gpa": 3.3 },
@ -173,9 +174,10 @@ const defaultOptions = {
"card_limit": 25,
"imageSize": 100,
"cardRoundness": 5,
"imageRoundness": 0,
"cardSpacing": 0,
"cardWidth": 262,
"cardHeight": 250,
"cardHeight": 146,
"cardPadding": 0,
"customCardStyles": false,
"customBackgroundLink": "",
@ -401,6 +403,14 @@ function setupCardRoundnessInput(initial) {
});
}
function setupImageRoundnessInput(initial) {
let el = document.querySelector("#imageRoundness");
el.value = initial;
el.addEventListener("input", (e) => {
debouncedCardStyleSet("imageRoundness", e.target.value);
});
}
function setupCardSpacingInput(initial) {
let el = document.querySelector("#cardSpacing");
el.value = initial;
@ -1056,6 +1066,10 @@ function setup() {
identifier: "cardRoundness",
setup: (initial) => setupCardRoundnessInput(initial),
},
{
identifier: "imageRoundness",
setup: (initial) => setupImageRoundnessInput(initial),
},
{
identifier: "cardSpacing",
setup: (initial) => setupCardSpacingInput(initial),
@ -1534,7 +1548,7 @@ function setup() {
"A-": { "cutoff": 90, "gpa": 4.0 },
"B+": { "cutoff": 87, "gpa": 3.0 },
"B": { "cutoff": 83, "gpa": 3.0 },
"B-": { "cutoff": 89, "gpa": 3.0 },
"B-": { "cutoff": 80, "gpa": 3.0 },
"C+": { "cutoff": 77, "gpa": 2.0 },
"C": { "cutoff": 73, "gpa": 2.0 },
"C-": { "cutoff": 70, "gpa": 2.0 },
@ -1545,8 +1559,9 @@ function setup() {
});
});
// Note: the card-style number inputs (imageSize, cardRoundness, cardSpacing,
// cardWidth, cardHeight, cardPadding) are wired via their setup*Input
// Note: the card-style number inputs (imageSize, cardRoundness,
// imageRoundness, cardSpacing, cardWidth, cardHeight, cardPadding) are wired
// via their setup*Input
// handlers in menu.special above, which debounce the storage writes. The
// duplicate listeners that used to live here referenced nonexistent
// #*Value spans (threw on every input) and double-wrote to storage, which
@ -1619,11 +1634,9 @@ async function getExport(storage, options) {
case "custom_cards":
let arr = [];
Object.keys(storage["custom_cards"]).forEach(key => {
if (storage["custom_cards"][key].img !== "") arr.push(storage["custom_cards"][key].img);
const img = storage["custom_cards"][key].img;
if (img && img !== "none" && img.trim() !== "") arr.push(img);
});
if (arr.length === 0) {
arr = ["none"];
}
final["custom_cards"] = arr;
break;
case "card_colors":
@ -1767,6 +1780,7 @@ function saveCurrentTheme() {
"sidebar_scale": current["sidebar_scale"],
"imageSize": current["imageSize"],
"cardRoundness": current["cardRoundness"],
"imageRoundness": current["imageRoundness"],
"cardSpacing": current["cardSpacing"],
"cardWidth": current["cardWidth"],
"cardHeight": current["cardHeight"],
@ -1867,7 +1881,7 @@ function displaySavedThemes() {
let title = makeElement("p", btn, { "className": "theme-button-title", "textContent": `Theme ${index + 1}`});
let date = makeElement("p", btn, { "className": "theme-button-creator", "textContent": `${getRelativeDate(created).time} ago` });
let remove = makeElement("div", btn, { "className": "theme-button-remove", "textContent": "x" });
btn.style.backgroundImage = `linear-gradient(rgba(0, 0, 0, 0.44), rgba(0, 0, 0, 0.44)), url(${local["saved_themes"][key]["custom_cards"][0]})`;
btn.style.backgroundImage = `linear-gradient(rgba(0, 0, 0, 0.44), rgba(0, 0, 0, 0.44)), url(${local["saved_themes"][key]["custom_cards"]?.[0] || ""})`;
btn.addEventListener("click", () => {
importTheme(local["saved_themes"][key]);
});
@ -1906,16 +1920,23 @@ function importTheme(theme) {
case "card_colors":
sendFromPopup("setcolors", theme["card_colors"]);
break;
case "custom_cards":
case "custom_cards": {
// "none" is a legacy placeholder meaning "this theme has
// no custom card images" — treat it as such instead of
// writing the literal string into every card (which hides
// the card image on the dashboard). Fall back to "" so
// cards keep their default Canvas images.
const themeImgs = (theme["custom_cards"] || []).filter(u => u && u !== "none" && u.trim() !== "");
if (theme["custom_cards"].length > 0) {
let pos = 0;
Object.keys(sync["custom_cards"]).forEach(key => {
sync["custom_cards"][key].img = theme["custom_cards"][pos];
pos = (pos === theme["custom_cards"].length - 1) ? 0 : pos + 1;
sync["custom_cards"][key].img = themeImgs.length ? themeImgs[pos] : "";
pos = (pos === themeImgs.length - 1) ? 0 : pos + 1;
});
final["custom_cards"] = sync["custom_cards"];
}
final["custom_cards"] = sync["custom_cards"];
break;
}
default:
final[key] = theme[key];
break;