diff --git a/_locales/en/messages.json b/_locales/en/messages.json
index d124913..32501ff 100644
--- a/_locales/en/messages.json
+++ b/_locales/en/messages.json
@@ -17,6 +17,15 @@
"ago": {
"message": "ago"
},
+ "align_center": {
+ "message": "Center"
+ },
+ "align_left": {
+ "message": "Left"
+ },
+ "align_right": {
+ "message": "Right"
+ },
"always_show_likes": {
"message": "Always show like count"
},
@@ -821,6 +830,9 @@
"gpa_settings": {
"message": "GPA Settings"
},
+ "gpa_text_align": {
+ "message": "Text alignment"
+ },
"gpa_type_away": {
"message": "Type away!"
},
diff --git a/css/content.css b/css/content.css
index 48fd9dd..400204c 100644
--- a/css/content.css
+++ b/css/content.css
@@ -497,7 +497,7 @@
--cr-gpa-course-cols: 30px minmax(0,1fr) 80px 58px 118px 16px minmax(88px,auto);
--cr-gpa-accent: var(--ic-brand-primary,#0374b5);
position:fixed;inset:0;z-index:2147483100;background:rgba(15,25,40,.5);backdrop-filter:blur(3px);
- display:flex;align-items:flex-start;justify-content:center;padding:7vh 16px 16px;
+ display:flex;align-items:center;justify-content:center;padding:16px;
}
.cr-gpa-modal {width:min(1080px,96vw);max-height:86vh;display:flex;flex-direction:column;background:var(--bcbackground-0,#fff);color:var(--bctext-0,#2d3b45);border:1px solid var(--bcborders,#c7cdd1);border-radius:16px;box-shadow:0 24px 70px rgba(0,0,0,.4);overflow:hidden;font-size:13px;}
.cr-gpa-head {display:flex;align-items:center;gap:16px;flex-wrap:wrap;padding:16px 20px;border-bottom:1px solid var(--bcborders,#c7cdd1);}
@@ -557,7 +557,7 @@
.cr-gpa-suffix {flex:none;padding:0 8px 0 2px;font-size:11px;color:var(--bctext-2,#9aa3ab);}
.cr-gpa-note {color:var(--bctext-2,#9aa3ab);font-size:12px;line-height:1.5;margin:14px 0 0;padding-top:12px;border-top:1px solid var(--bcborders,#c7cdd1);}
@media (max-width:720px) {
- #canvasrefined-gpa-modal {--cr-gpa-course-cols:26px minmax(0,1fr) 72px 52px 108px 15px 88px;padding:4vh 8px 8px;}
+ #canvasrefined-gpa-modal {--cr-gpa-course-cols:26px minmax(0,1fr) 72px 52px 108px 15px 88px;padding:8px;}
.cr-gpa-body {grid-template-columns:1fr;}
.cr-gpa-pane-scale {border-left:none;border-top:1px solid var(--bcborders,#c7cdd1);}
}
@@ -659,11 +659,21 @@
.canvasrefined-breathing.container-mode .cr-breath-display {align-self:stretch;padding-left:var(--cr-breath-gap);padding-right:0;}
.canvasrefined-breathing.container-mode .cr-timer-presets {padding-top:0;}
.canvasrefined-breathing.container-mode .cr-timer-controls {padding-bottom:0;}
+/* Daily Quote (height on auto): the original width-driven sizes. The wrapper is
+ display:contents, so mark/quote/author lay out exactly as before the fit mode. */
.canvasrefined-quotes {container-type:inline-size;background:linear-gradient(150deg, color-mix(in srgb, #b98cff 14%, var(--bcbackground-1,#fff)), var(--bcbackground-1,#fff));}
.cr-quote-body {padding:clamp(12px,4.5cqw,26px);display:flex;flex-direction:column;gap:clamp(4px,1.6cqw,12px);flex:1;justify-content:center;}
+.cr-quote-content {display:contents;}
.cr-quote-mark {font-size:clamp(20px,11cqw,44px);line-height:.5;font-weight:800;color:color-mix(in srgb, var(--bclinks,#0374b5) 55%, transparent);}
.cr-quote-text {margin:0;font-size:clamp(14px,7.5cqw,26px);line-height:1.45;font-style:italic;color:var(--bctext-0,#2d3b45);}
.cr-quote-author {font-size:clamp(14px,4.2cqw,20px);font-weight:700;color:var(--bctext-2,#9aa3ab);align-self:flex-end;letter-spacing:0.01em;}
+/* Daily Quote (fixed height): constant base sizes; the content script scales the
+ whole group with a transform so the mark, quote and author use the whole widget. */
+.canvasrefined-quotes.cr-quote-fit .cr-quote-body {padding:18px;display:flex;align-items:center;justify-content:center;flex:1;min-height:0;overflow:hidden;}
+.canvasrefined-quotes.cr-quote-fit .cr-quote-content {display:flex;flex-direction:column;gap:10px;flex:none;transform-origin:center center;}
+.canvasrefined-quotes.cr-quote-fit .cr-quote-mark {font-size:44px;line-height:.5;font-weight:800;color:color-mix(in srgb, var(--bclinks,#0374b5) 55%, transparent);}
+.canvasrefined-quotes.cr-quote-fit .cr-quote-text {margin:0;font-size:26px;line-height:1.45;font-style:italic;color:var(--bctext-0,#2d3b45);}
+.canvasrefined-quotes.cr-quote-fit .cr-quote-author {font-size:20px;font-weight:700;color:var(--bctext-2,#9aa3ab);align-self:flex-end;letter-spacing:0.01em;}
/* Quick Links: editable link grid with theme-tinted preset icons */
.canvasrefined-quick-links .cr-widget-head {gap:8px;}
diff --git a/html/popup.html b/html/popup.html
index 1fe236c..544fc94 100644
--- a/html/popup.html
+++ b/html/popup.html
@@ -1224,6 +1224,14 @@
px
+
+
+
+
diff --git a/js/content.js b/js/content.js
index 95322fd..d86f89d 100644
--- a/js/content.js
+++ b/js/content.js
@@ -1351,6 +1351,7 @@ function applyOptionsChanges(changes) {
case "gpa_calc_prepend":
case "gpa_calc_position":
case "gpa_calc_height":
+ case "gpa_calc_align":
case "gpa_calc_weighted":
case "gpa_calc_cumulative":
case "gpa_calc_show_on_hover":
@@ -7772,6 +7773,8 @@ function setupGPACalc() {
}
container2.style.display = options.gpa_calc === true ? "inline-block" : "none";
+ // Text alignment dropdown (Widgets tab): default stays left.
+ container2.style.textAlign = options.gpa_calc_align === "center" || options.gpa_calc_align === "right" ? options.gpa_calc_align : "";
if (!alreadyRendered) {
// Render the shell now (with "--") and fill the numbers once grades resolve.
@@ -8588,9 +8591,11 @@ function dailyQuoteDefault() {
function dailyQuoteHtml() { return `
${getMsg("daily_quote", "Daily Quote")}
-
“
-
${dailyQuoteDefault().quote}
-
— ${dailyQuoteDefault().author}
+
+
“
+
${dailyQuoteDefault().quote}
+
— ${dailyQuoteDefault().author}
+
`; }
@@ -8603,6 +8608,61 @@ function renderDailyQuote(el, data) {
const authorEl = el.querySelector(".cr-quote-author");
if (textEl) textEl.textContent = quote;
if (authorEl) authorEl.textContent = author ? "\u2014 " + author : "";
+ // New text means new metrics: refit so the group fills the widget again.
+ requestAnimationFrame(() => fitDailyQuote(el));
+}
+
+/* Scale the whole quote group (mark, quote, author) so it uses the widget's space.
+ The base sizes are the constants in content.css; the fit spends the available
+ width up front by laying the text out at availW/scale, then binary-searches the
+ largest scale whose scaled height still fits. */
+const CR_QUOTE_MIN_SCALE = 0.15;
+const CR_QUOTE_MAX_SCALE = 2.5;
+
+let crQuoteResizeObserver = null;
+function crQuoteObserver() {
+ if (!crQuoteResizeObserver) {
+ crQuoteResizeObserver = new ResizeObserver(entries => {
+ for (const entry of entries) fitDailyQuote(entry.target);
+ });
+ }
+ return crQuoteResizeObserver;
+}
+
+// Fixed-height mode only. With auto height the widget's height comes from the
+// content itself, so a fit would feed its own height back in — keep the original
+// width-driven sizing instead. Read the heights the layout code actually applied
+// (crApplyCardHeight for in-cards, applyWidgetRowLayout for Top/Bottom rows).
+function crQuoteFitEnabled(el) {
+ if (el.classList.contains("container-mode")) {
+ const row = el.closest(".canvasrefined-widget-row");
+ return !!(row && parseInt(row.style.height, 10) > 0);
+ }
+ return parseInt(el.style.height, 10) > 0;
+}
+
+function fitDailyQuote(el) {
+ const body = el.querySelector(".cr-quote-body");
+ const content = el.querySelector(".cr-quote-content");
+ if (!body || !content) return;
+ const fit = crQuoteFitEnabled(el);
+ el.classList.toggle("cr-quote-fit", fit);
+ content.style.transform = "";
+ content.style.width = "";
+ if (!fit || !el.isConnected || el.style.display === "none") return;
+ const cs = getComputedStyle(body);
+ const availW = body.clientWidth - parseFloat(cs.paddingLeft) - parseFloat(cs.paddingRight);
+ const availH = body.clientHeight - parseFloat(cs.paddingTop) - parseFloat(cs.paddingBottom);
+ if (availW < 40 || availH < 40) return;
+ let lo = CR_QUOTE_MIN_SCALE, hi = CR_QUOTE_MAX_SCALE, best = CR_QUOTE_MIN_SCALE;
+ while (hi - lo > 0.01) {
+ const mid = (lo + hi) / 2;
+ content.style.width = (availW / mid) + "px";
+ if (content.offsetHeight * mid <= availH) { best = mid; lo = mid; }
+ else hi = mid;
+ }
+ content.style.width = (availW / best) + "px";
+ content.style.transform = "scale(" + best + ")";
}
// Ask the background worker to refresh; it caches per calendar day.
@@ -8631,7 +8691,13 @@ function loadDailyQuotesWidget() {
crLoadWidget("daily_quotes", {
className: "canvasrefined-quotes",
html: dailyQuoteHtml(),
- wire: refreshDailyQuoteWidget,
+ wire: (el) => {
+ // Refit on every size change (height setting, row height, window resize).
+ crQuoteObserver().observe(el);
+ // Web fonts land after first paint and change the metrics.
+ try { document.fonts.ready.then(() => fitDailyQuote(el)); } catch (e) {}
+ refreshDailyQuoteWidget(el);
+ },
update: refreshDailyQuoteWidget,
});
}
diff --git a/js/popup.js b/js/popup.js
index 90e7db0..3bf79b2 100644
--- a/js/popup.js
+++ b/js/popup.js
@@ -20,6 +20,7 @@ const syncedSubOptions = [
"gpa_calc_cumulative",
"gpa_calc_weighted",
"gpa_calc_show_on_hover",
+ "gpa_calc_align",
"focus_timer_position",
"focus_timer_preset_1",
"focus_timer_preset_2",
@@ -218,7 +219,7 @@ const exportCardStyles = ["customCardStyles", "imageSize", "cardRoundness", "ima
const exportLayout = ["full_width", "center_cards", "condensed_cards", "equal_height_cards", "remlogo", "hide_new_canvas", "hide_navbar", "hide_sidebar", "tab_icons"];
const exportSidebar = ["better_sidebar", "sidebar_scale"];
const exportTodo = ["better_todo", "todo_hide_feedback", "todo_hide_read", "todo_full_height", "todo_celebrations_enabled", "todo_celebrations", "todo_celebration_amount", "todo_progress_rings", "todo_timeframe", "todo_timeframe_custom_past", "todo_timeframe_custom_future", "todo_hr24", "todo_separate_scrollbar", "todo_alternate_colors", "todo_ignore_card_colors", "todo_remove_icons", "todo_show_scores", "hover_preview"];
-const exportGpa = ["gpa_calc", "gpa_calc_prepend", "gpa_calc_cumulative", "gpa_calc_weighted", "gpa_calc_show_on_hover"];
+const exportGpa = ["gpa_calc", "gpa_calc_prepend", "gpa_calc_cumulative", "gpa_calc_weighted", "gpa_calc_show_on_hover", "gpa_calc_align"];
const exportBackground = ["customBackgroundLink", "customBackgroundScale", "customBackgroundDaily", "customBackgroundNasaDaily", "customBackgroundColor", "customBackgroundColorTransparent", "fitImageToScreen", "card_transparency", "bg_opacity", "sidebar_opacity", "bg_blur", "sidebar_blur", "card_opacity", "card_blur"];
// Master "On/off toggles": every visual toggle, minus GPA/schedule/productivity.
const exportToggles = ["dark_mode", "quiz_safe_mode", "grade_analytics", "grade_analytics_zones"].concat(exportCardColorToggles, exportLayout, exportSidebar, exportTodo);
@@ -268,6 +269,7 @@ const defaultOptions = {
"dashboard_notes_mode": "edit",
"dashboard_notes_position": "top",
"gpa_calc_position": "cards_end",
+ "gpa_calc_align": "left",
"gpa_calc_show_on_hover": false,
"focus_timer": false,
"focus_timer_position": "cards_end",
@@ -1528,6 +1530,7 @@ function setupWidgetPosition() {
[
{ select: "dashboard_notes_position", key: "dashboard_notes_position" },
{ select: "gpa_calc_position", key: "gpa_calc_position" },
+ { select: "gpa_calc_align", key: "gpa_calc_align" },
{ select: "focus_timer_position", key: "focus_timer_position" },
{ select: "breathing_position", key: "breathing_position" },
{ select: "daily_quotes_position", key: "daily_quotes_position" },