Dashboard: let the trial be a detail line, not an alarm

The Usage card gives each of its three columns a meter and two lines. The
plan column had a boxed, tinted, bordered paragraph instead — and at the
~230px a third of that card comes to, thirty words of it wrapped into a
five-line ribbon standing twice as tall as the storage and resources meters
beside it. The box is the shape we use for the dunning banner. A trial is a
date you should know about, not a fault, and it was reading as the loudest
thing on the card.

So it takes the slot its neighbours use for "n% of 100 GB": a quiet hint-
coloured line under the badge. All three columns now measure the same.

The copy stopped repeating itself while it was there. The badge said "ends
Sep 20" and the sentence beneath it said "ends on September 20, 2026" — two
elements, one fact, and neither of them answering the question someone
actually has, which is how much room is left to decide. The badge now
carries "31 days left" and the line under it carries the consequence:
"First charge Sep 20 unless you cancel." Billing keeps the full sentence and
the opt-out, and Manage is directly below.
This commit is contained in:
Nariman Jelveh
2026-08-20 16:59:07 -07:00
parent fef1337b2b
commit 76bd05f228
2 changed files with 44 additions and 19 deletions
+28 -10
View File
@@ -91,6 +91,7 @@ function buildUsageHTML() {
'<span class="bento-usage-card-details bento-plan-details"><span class="bento-plan-badge"></span></span>';
h += '</div>';
h += '<div class="bento-plan-warning" style="display: none;"></div>';
h += '<div class="bento-plan-note" style="display: none;"></div>';
h +=
'<a href="#" class="bento-plan-upgrade" style="display: none;">Upgrade →</a>';
h += '</div>';
@@ -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');
+16 -9
View File
@@ -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 {