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