diff --git a/src/gui/src/UI/Dashboard/TabHome.js b/src/gui/src/UI/Dashboard/TabHome.js index 1f20c83e1..55ae12371 100644 --- a/src/gui/src/UI/Dashboard/TabHome.js +++ b/src/gui/src/UI/Dashboard/TabHome.js @@ -91,6 +91,7 @@ function buildUsageHTML() { ''; h += ''; h += ''; + h += ''; h += ''; h += ''; @@ -133,13 +134,21 @@ function buildUsageHTML() { return h; } -// Trial end date in two lengths: `short` for the badge, `long` for the -// sentence under it. Returns null when the subscription carries no end date, +// Trial end date in two lengths — `short` for the note under the badge, +// `long` for anywhere with room for a sentence — plus `countdown`, which is +// what the badge carries: a date says when, only a countdown says how long +// there is to decide. Returns null when the subscription carries no end date, // so callers fall back to unqualified "Free trial" wording. function formatTrialEnd(trialEndsAt) { if (!Number.isFinite(trialEndsAt)) return null; const date = new Date(trialEndsAt); if (Number.isNaN(date.getTime())) return null; + // Rounded up, so a trial with any time on it never reads "0 days left" + // before the day it actually converts. + const days = Math.max( + 0, + Math.ceil((date.getTime() - Date.now()) / 86400000), + ); return { short: date.toLocaleDateString(undefined, { month: 'short', @@ -150,6 +159,13 @@ function formatTrialEnd(trialEndsAt) { month: 'long', day: 'numeric', }), + days, + countdown: + days === 0 + ? 'ends today' + : days === 1 + ? '1 day left' + : `${days} days left`, }; } @@ -457,8 +473,8 @@ const TabHome = { const $warning = $el_window .find('.bento-plan-warning') .hide() - .removeClass('info') .text(''); + const $note = $el_window.find('.bento-plan-note').hide().text(''); if (hasSubscription) { if (pastDue) { @@ -469,22 +485,24 @@ const TabHome = { ) .show(); } else if (trialing) { - // A trial grants the full tier and then continues as a paid - // plan, so say when that happens and where to opt out. + // This is a glance surface sitting between two usage + // meters, so the trial gets the same two lines they get: + // how much is left, then the one consequence worth + // knowing. Billing carries the full sentence and the + // opt-out — "Manage →" is directly below. $badge .text( trialEnds - ? `Free trial — ends ${trialEnds.short}` + ? `Free trial · ${trialEnds.countdown}` : 'Free trial', ) .addClass('trial'); - $warning + $note .text( trialEnds - ? `Your free trial ends on ${trialEnds.long}, after which the plan continues at the usual monthly price. Cancel any time before then under Billing.` - : 'When your free trial ends the plan continues at the usual monthly price. Cancel any time before then under Billing.', + ? `First charge ${trialEnds.short} unless you cancel.` + : 'Continues as a paid plan when the trial ends.', ) - .addClass('info') .show(); } else { $badge.text('Current').addClass('active'); diff --git a/src/gui/src/css/dashboard.css b/src/gui/src/css/dashboard.css index cd3e3a752..e6f5b5b0a 100644 --- a/src/gui/src/css/dashboard.css +++ b/src/gui/src/css/dashboard.css @@ -5197,9 +5197,10 @@ body.myapps-reordering .myapps-tile { gap: 24px; } -/* Three even columns need real width. Below that the plan column lands around - 230px, which wraps its badge and renewal notice into a tall ribbon — so give - the plan its own row and let the two usage meters share the next one. */ +/* Three even columns need real width. Below that, give the plan its own row + and let the two usage meters share the next one — they are the pair that + reads as a set, and splitting them to put the plan beside one of them makes + the row look accidental. */ @media (max-width: 1200px) { .dashboard .bento-usage-grid { grid-template-columns: 1fr 1fr; @@ -5272,12 +5273,18 @@ body.myapps-reordering .myapps-tile { line-height: 1.4; } -/* Time-boxed but not broken — a trial notice reads as advisory, not as the - red dunning banner and not as an amber warning either. */ -.dashboard .bento-plan-warning.info { - color: var(--dashboard-notice-text); - background: var(--dashboard-notice-background); - border-color: var(--dashboard-notice-border); +/* Time-boxed but not broken. A boxed, tinted, bordered panel is the shape of + an alarm, and in a ~230px column it wrapped a trial into a five-line ribbon + that stood twice as tall as the storage and resources meters beside it. A + trial is a date, not a fault: it reads as the quiet detail line under the + badge, exactly where those two columns put their "n% of X". The box stays + for the dunning banner, which has earned it. */ +.dashboard .bento-plan-note { + display: block; + margin-top: 6px; + font-size: 13px; + line-height: 1.45; + color: var(--dashboard-text-hint); } .dashboard .bento-plan-upgrade {