@subvalue/cli 0.1.4 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/PRE_RELEASE.md +1 -1
- package/README.md +2 -2
- package/dist/packages/cli/src/server.js +1 -1
- package/dist/packages/core/src/summary.js +16 -14
- package/dist/packages/core/src/types.js +2 -1
- package/dist/packages/receipt/src/index.js +7 -1
- package/dist/packages/ui/src/brand.js +1 -0
- package/dist/public/app.css +25 -2
- package/dist/public/apps/web/src/landing.js +12 -3
- package/dist/public/apps/web/src/main.js +45 -45
- package/dist/public/landing.css +80 -29
- package/dist/public/packages/receipt/src/index.js +7 -1
- package/dist/public/packages/ui/src/brand.js +1 -0
- package/package.json +1 -1
package/PRE_RELEASE.md
CHANGED
|
@@ -20,7 +20,7 @@ The first command checks detection without reading session contents. The second
|
|
|
20
20
|
## Check the app
|
|
21
21
|
|
|
22
22
|
1. Confirm the detected providers. A detected provider may have no usage in the selected period.
|
|
23
|
-
2.
|
|
23
|
+
2. Choose your subscription plan or **No subscription** for each provider. A custom amount is also available. These are declarations, never inferred from credentials.
|
|
24
24
|
3. Check Overview, 7D/30D/Month, Receipt and Settings. Pricing coverage describes imported usage, not every request ever made.
|
|
25
25
|
4. Export PNG. Check readable amounts and the absence of project names, paths, prompts and session IDs.
|
|
26
26
|
5. Use **Refresh usage** at the top of Overview after new activity. It updates local metadata without restarting the app. Period controls only filter cached data.
|
package/README.md
CHANGED
|
@@ -42,7 +42,7 @@ npm run demo
|
|
|
42
42
|
- **Receipt:** local canvas preview and PNG export of the exact same pixels. No projects, paths, requests or sessions in exports.
|
|
43
43
|
- **Settings:** Sources, Billing, USD, Privacy / data. Inactive providers stay here, with optional manual inclusion.
|
|
44
44
|
|
|
45
|
-
First run shows detected history, then
|
|
45
|
+
First run shows detected history, then offers subscription plans directly for each provider with imported usage. Choose a plan, a custom USD amount, or **No subscription** independently for Codex and Claude Code. No subscription requires no amount and never implies API billing. **Set up later** leaves billing unknown.
|
|
46
46
|
|
|
47
47
|
Month opens a floating calendar-month picker (including historical months). Previous/next arrows move by one calendar month, seven or thirty days for rolling presets, or the selected custom duration. Comparisons use the full declared monthly price for each complete calendar month selected. The current month is marked In progress. 7D, 30D and partial custom ranges show API-equivalent usage without subscription comparisons. Custom ranges covering whole calendar months compare the corresponding number of monthly charges. Amounts and API prices are USD; no invented exchange rates. Billing declarations do not overwrite historical records or prove their original billing mode. Settings offers bundled, verified ChatGPT/Claude plan presets and a custom amount. Annual presets retain the exact annual price divided by 12, with per-seat plans labeled.
|
|
48
48
|
|
|
@@ -68,7 +68,7 @@ Codex excludes inherited ordinal prefixes, uses cumulative deltas, suppresses re
|
|
|
68
68
|
|
|
69
69
|
The versioned catalog supports mappings with confidence and primary-source provenance, half-open effective intervals, cache reads/writes, Claude cache durations, and long-context bands. Rates and release evidence were checked against [OpenAI pricing](https://developers.openai.com/api/docs/pricing) and [Claude pricing](https://platform.claude.com/docs/en/about-claude/pricing) on **2026-10-05**. Historical versions include the July 30 Luna/Terra reductions and August 21 Sol promotion. Exact public GPT-5.6/6 and Claude identifiers carry mapping provenance; long-context rates use the verified 272k request threshold. New-model launch days use their documented initial API-equivalent rate. Calendar-dated changes between rates remain unknown because their exact UTC switch time is not documented. Sol pricing after the confirmed promotion remains unavailable. See [the pricing evidence ledger](dist/PRICING.md). Internal/unknown model identifiers are never mapped by resemblance.
|
|
70
70
|
|
|
71
|
-
Overview and receipt display the sum of priceable events; the compact priced percentage identifies partial pricing, and unpriced model identities remain visible in Details. No guessed price or zero is assigned to unknown events. Both views share a display comparison of the known priced amount with the declared full-month subscription: Observed Value / You Saved is their difference, and Value Multiple is their ratio. The normalized summary still marks the full-usage cost and comparison unknown when pricing is incomplete. Partial coverage remains visible; these observed values do not infer a certain outcome or an exact break-even date.
|
|
71
|
+
Overview and receipt display the sum of priceable events; the compact priced percentage identifies partial pricing, and unpriced model identities remain visible in Details. No guessed price or zero is assigned to unknown events. Both views share a display comparison of the known priced amount with the declared full-month subscription: Observed Value / You Saved is their difference, and Value Multiple is their ratio. The normalized summary still marks the full-usage cost and comparison unknown when pricing is incomplete. Partial coverage remains visible; these observed values do not infer a certain outcome or an exact break-even date. No subscription, unknown billing, and legacy API/mixed settings have no subscription comparison. Combined subscription comparisons require a declared subscription for every active provider.
|
|
72
72
|
|
|
73
73
|
## Architecture
|
|
74
74
|
|
|
@@ -25,7 +25,7 @@ const mime = {
|
|
|
25
25
|
'.woff2': 'font/woff2',
|
|
26
26
|
'.txt': 'text/plain; charset=utf-8',
|
|
27
27
|
};
|
|
28
|
-
const billingModes = ['UNKNOWN', 'SUBSCRIPTION', 'API', 'MIXED'] ;
|
|
28
|
+
const billingModes = ['UNKNOWN', 'SUBSCRIPTION', 'NO_SUBSCRIPTION', 'API', 'MIXED'] ;
|
|
29
29
|
const isBillingMode = (value ) =>
|
|
30
30
|
billingModes.some((mode) => mode === value);
|
|
31
31
|
|
|
@@ -205,19 +205,21 @@ export function summarize(
|
|
|
205
205
|
? subscriptionForRange(billing.monthly, range)
|
|
206
206
|
: null;
|
|
207
207
|
let comparison = neutral(
|
|
208
|
-
billing.mode === '
|
|
209
|
-
? '
|
|
210
|
-
: billing.mode === '
|
|
211
|
-
? '
|
|
212
|
-
: billing.mode === '
|
|
213
|
-
? '
|
|
214
|
-
:
|
|
215
|
-
? '
|
|
216
|
-
:
|
|
217
|
-
? '
|
|
218
|
-
:
|
|
219
|
-
? '
|
|
220
|
-
:
|
|
208
|
+
billing.mode === 'NO_SUBSCRIPTION'
|
|
209
|
+
? 'No subscription'
|
|
210
|
+
: billing.mode === 'API'
|
|
211
|
+
? 'API usage'
|
|
212
|
+
: billing.mode === 'MIXED'
|
|
213
|
+
? 'Mixed billing'
|
|
214
|
+
: billing.mode === 'UNKNOWN'
|
|
215
|
+
? 'Billing not set'
|
|
216
|
+
: fullCalendarMonths(range) === null
|
|
217
|
+
? 'Choose a full month'
|
|
218
|
+
: subscription === null
|
|
219
|
+
? 'Subscription price not set'
|
|
220
|
+
: !completePricing
|
|
221
|
+
? 'Pricing unavailable'
|
|
222
|
+
: 'Partial history',
|
|
221
223
|
subscription,
|
|
222
224
|
);
|
|
223
225
|
if (completePricing && subscription !== null) {
|
|
@@ -311,7 +313,7 @@ export function summarize(
|
|
|
311
313
|
const apiEquivalent = count > 0 && pricedRecords === count ? knownSubtotal : null;
|
|
312
314
|
const subs = active.filter((p) => p.mode === 'SUBSCRIPTION');
|
|
313
315
|
let comparison = neutral(
|
|
314
|
-
active.some((p) => p.mode
|
|
316
|
+
active.some((p) => p.mode !== 'SUBSCRIPTION')
|
|
315
317
|
? 'Subscription comparison unavailable'
|
|
316
318
|
: 'Partial history',
|
|
317
319
|
);
|
|
@@ -216,7 +216,12 @@ export function drawReceipt(
|
|
|
216
216
|
canvas ,
|
|
217
217
|
model ,
|
|
218
218
|
theme = 'dark',
|
|
219
|
+
presentation = 'standard',
|
|
219
220
|
) {
|
|
221
|
+
// A public example needs larger print when viewed inside the dashboard preview.
|
|
222
|
+
// The paper, layout and export renderer remain shared with the local application.
|
|
223
|
+
const printSize = (size ) =>
|
|
224
|
+
presentation === 'preview' ? Math.max(13, size * 1.12) : size;
|
|
220
225
|
const light = theme === 'light';
|
|
221
226
|
const ink = (color ) =>
|
|
222
227
|
light
|
|
@@ -288,6 +293,7 @@ export function drawReceipt(
|
|
|
288
293
|
bold = false,
|
|
289
294
|
maxWidth = width - 48,
|
|
290
295
|
) => {
|
|
296
|
+
size = printSize(size);
|
|
291
297
|
c.font = `${bold ? '600 ' : ''}${size}px ${mono}`;
|
|
292
298
|
while (c.measureText(value).width > maxWidth && size > 8) {
|
|
293
299
|
size -= 0.5;
|
|
@@ -335,7 +341,7 @@ export function drawReceipt(
|
|
|
335
341
|
? '#e1e2e4'
|
|
336
342
|
: '#bdc0c6';
|
|
337
343
|
const valueSize = multiple ? 25 : row.accent ? 22 : row.strong ? 18 : 16;
|
|
338
|
-
c.font = `${valueSize}px ${mono}`;
|
|
344
|
+
c.font = `${printSize(valueSize)}px ${mono}`;
|
|
339
345
|
const valueWidth = c.measureText(row.right).width;
|
|
340
346
|
text(
|
|
341
347
|
label,
|
package/dist/public/app.css
CHANGED
|
@@ -295,6 +295,29 @@ p {
|
|
|
295
295
|
gap: 20px;
|
|
296
296
|
margin-bottom: 22px;
|
|
297
297
|
}
|
|
298
|
+
.overview-title {
|
|
299
|
+
display: flex;
|
|
300
|
+
flex-wrap: wrap;
|
|
301
|
+
align-items: baseline;
|
|
302
|
+
gap: 6px 14px;
|
|
303
|
+
min-width: 0;
|
|
304
|
+
}
|
|
305
|
+
.selected-period {
|
|
306
|
+
color: var(--green);
|
|
307
|
+
font-size: 17px;
|
|
308
|
+
font-weight: 550;
|
|
309
|
+
letter-spacing: -0.3px;
|
|
310
|
+
}
|
|
311
|
+
@media (max-width: 760px) {
|
|
312
|
+
.overview-title {
|
|
313
|
+
flex-direction: column;
|
|
314
|
+
align-items: start;
|
|
315
|
+
gap: 5px;
|
|
316
|
+
}
|
|
317
|
+
.selected-period {
|
|
318
|
+
font-size: 15px;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
298
321
|
.eyebrow {
|
|
299
322
|
font: 9px var(--mono);
|
|
300
323
|
letter-spacing: 1.7px;
|
|
@@ -2223,7 +2246,7 @@ dialog::backdrop {
|
|
|
2223
2246
|
}
|
|
2224
2247
|
}
|
|
2225
2248
|
.billing-row {
|
|
2226
|
-
grid-template-columns: 100px
|
|
2249
|
+
grid-template-columns: 100px minmax(0, 1fr);
|
|
2227
2250
|
gap: 16px;
|
|
2228
2251
|
align-items: start;
|
|
2229
2252
|
}
|
|
@@ -2242,7 +2265,7 @@ dialog::backdrop {
|
|
|
2242
2265
|
.amount-field input[readonly] {
|
|
2243
2266
|
color: var(--muted);
|
|
2244
2267
|
}
|
|
2245
|
-
.
|
|
2268
|
+
.billing-amount.hidden {
|
|
2246
2269
|
display: none;
|
|
2247
2270
|
}
|
|
2248
2271
|
.compact-details a {
|
|
@@ -60,8 +60,13 @@ const example = {
|
|
|
60
60
|
demo: true,
|
|
61
61
|
};
|
|
62
62
|
document.querySelector('#landing') .innerHTML = /* HTML */ `<header class="landing-nav">
|
|
63
|
-
<a class="brand" href="#"><span class="brand-mark">${productMark}</span>${productNameMarkup}</a
|
|
64
|
-
|
|
63
|
+
<a class="brand" href="#"><span class="brand-mark">${productMark}</span>${productNameMarkup}</a>
|
|
64
|
+
<nav class="landing-links" aria-label="Site navigation">
|
|
65
|
+
<a class="install-link" href="#release">Install <span>↓</span></a>
|
|
66
|
+
<a class="github-link" href="${product.repository}" target="_blank" rel="noreferrer"
|
|
67
|
+
>GitHub <span>↗</span></a
|
|
68
|
+
>
|
|
69
|
+
</nav>
|
|
65
70
|
</header>
|
|
66
71
|
<main>
|
|
67
72
|
<section class="hero">
|
|
@@ -81,7 +86,9 @@ document.querySelector('#landing') .innerHTML = /* HTML */ `<header class="landi
|
|
|
81
86
|
</button>
|
|
82
87
|
</div>
|
|
83
88
|
<div class="trust-row">
|
|
84
|
-
<span>✓ Local</span><span>✓ Read-only</span
|
|
89
|
+
<span>✓ Local</span><span>✓ Read-only</span>
|
|
90
|
+
<a href="${product.repository}" target="_blank" rel="noreferrer">✓ Open source</a>
|
|
91
|
+
<span>✓ No telemetry</span>
|
|
85
92
|
</div>
|
|
86
93
|
<span class="release-note">Node.js 24.13+ · Windows · macOS · Linux</span>
|
|
87
94
|
</section>
|
|
@@ -182,6 +189,8 @@ await receiptFonts();
|
|
|
182
189
|
drawReceipt(
|
|
183
190
|
document.querySelector ('#example-receipt') ,
|
|
184
191
|
receiptModel(example, new Date(2026, 10, 1, 9, 30)),
|
|
192
|
+
'dark',
|
|
193
|
+
'preview',
|
|
185
194
|
);
|
|
186
195
|
document.querySelectorAll ('.copy-command').forEach((b) =>
|
|
187
196
|
b.addEventListener('click', async () => {
|
|
@@ -572,7 +572,10 @@ function overview() {
|
|
|
572
572
|
const months = fullCalendarMonths(summary.range),
|
|
573
573
|
showSubs = months !== null && active.length && active.every((p) => p.mode === 'SUBSCRIPTION');
|
|
574
574
|
return /* HTML */ `<div class="page-heading">
|
|
575
|
-
<div
|
|
575
|
+
<div class="overview-title">
|
|
576
|
+
<h1>Overview</h1>
|
|
577
|
+
<span class="selected-period" aria-live="polite">${esc(periodLabel())}</span>
|
|
578
|
+
</div>
|
|
576
579
|
${refreshButton()}
|
|
577
580
|
</div>
|
|
578
581
|
${coverage()}
|
|
@@ -614,38 +617,40 @@ function receiptPage() {
|
|
|
614
617
|
}
|
|
615
618
|
function billingFields(p , firstRun = false) {
|
|
616
619
|
const b = settings.billing[p],
|
|
617
|
-
plan = subscriptionPlan(p, b.planId)
|
|
620
|
+
plan = subscriptionPlan(p, b.planId),
|
|
621
|
+
subscribed = b.mode === 'SUBSCRIPTION',
|
|
622
|
+
selection = subscribed
|
|
623
|
+
? (plan?.id ?? 'custom')
|
|
624
|
+
: b.mode === 'NO_SUBSCRIPTION' || b.mode === 'API'
|
|
625
|
+
? 'none'
|
|
626
|
+
: '';
|
|
618
627
|
return /* HTML */ `<div class="billing-row">
|
|
619
|
-
<label class="provider-setting-name" for="
|
|
620
|
-
|
|
621
|
-
id="mode-${p}"
|
|
622
|
-
name="mode-${p}"
|
|
623
|
-
data-billing-mode="${p}"
|
|
624
|
-
${firstRun ? 'required data-first-run="true"' : ''}
|
|
625
|
-
>
|
|
626
|
-
<option value="${firstRun ? '' : 'UNKNOWN'}" ${b.mode === 'UNKNOWN' ? 'selected' : ''}>
|
|
627
|
-
${firstRun ? 'Choose usage' : 'Not set'}
|
|
628
|
-
</option>
|
|
629
|
-
<option value="SUBSCRIPTION" ${b.mode === 'SUBSCRIPTION' ? 'selected' : ''}>
|
|
630
|
-
Subscription
|
|
631
|
-
</option>
|
|
632
|
-
<option value="API" ${b.mode === 'API' ? 'selected' : ''}>API</option>
|
|
633
|
-
<option value="MIXED" ${b.mode === 'MIXED' ? 'selected' : ''}>Mixed / not sure</option>
|
|
634
|
-
</select>
|
|
635
|
-
<div class="subscription-fields ${b.mode === 'SUBSCRIPTION' ? '' : 'hidden'}" id="amount-${p}">
|
|
628
|
+
<label class="provider-setting-name" for="plan-${p}">${name(p)}</label>
|
|
629
|
+
<div class="subscription-fields">
|
|
636
630
|
<label class="sr-only" for="plan-${p}">${name(p)} subscription plan</label
|
|
637
|
-
><select
|
|
638
|
-
|
|
631
|
+
><select
|
|
632
|
+
id="plan-${p}"
|
|
633
|
+
name="plan-${p}"
|
|
634
|
+
data-subscription-plan="${p}"
|
|
635
|
+
${firstRun ? 'required data-first-run="true"' : ''}
|
|
636
|
+
>
|
|
637
|
+
<option value="" ${selection === '' ? 'selected' : ''}>
|
|
638
|
+
${firstRun ? 'Choose subscription' : 'Not set'}
|
|
639
|
+
</option>
|
|
640
|
+
<option value="none" ${selection === 'none' ? 'selected' : ''}>No subscription</option>
|
|
639
641
|
${subscriptionPlans
|
|
640
642
|
.filter((plan) => plan.provider === p)
|
|
641
643
|
.map(
|
|
642
644
|
(option) =>
|
|
643
|
-
/* HTML */ `<option value="${option.id}" ${
|
|
645
|
+
/* HTML */ `<option value="${option.id}" ${selection === option.id ? 'selected' : ''}>
|
|
644
646
|
${esc(subscriptionPlanLabel(option))}
|
|
645
647
|
</option>`,
|
|
646
648
|
)
|
|
647
|
-
.join('')}
|
|
648
|
-
|
|
649
|
+
.join('')}
|
|
650
|
+
<option value="custom" ${selection === 'custom' ? 'selected' : ''}>
|
|
651
|
+
Custom amount
|
|
652
|
+
</option></select
|
|
653
|
+
><label class="amount-field billing-amount ${subscribed ? '' : 'hidden'}" id="amount-${p}"
|
|
649
654
|
><span class="sr-only">${name(p)} monthly subscription in USD</span><span>$</span
|
|
650
655
|
><input
|
|
651
656
|
name="monthly-${p}"
|
|
@@ -655,7 +660,8 @@ function billingFields(p , firstRun = false) {
|
|
|
655
660
|
step="0.01"
|
|
656
661
|
value="${plan ? plan.monthly.toFixed(2) : (b.monthly ?? '')}"
|
|
657
662
|
${plan ? 'readonly' : ''}
|
|
658
|
-
${
|
|
663
|
+
${!subscribed ? 'disabled' : ''}
|
|
664
|
+
${firstRun && subscribed ? 'required' : ''}
|
|
659
665
|
placeholder="Amount"
|
|
660
666
|
/><span>/ month</span></label
|
|
661
667
|
>
|
|
@@ -754,8 +760,8 @@ function settingsPage() {
|
|
|
754
760
|
</p>
|
|
755
761
|
<p>
|
|
756
762
|
These are your declarations, not inferred billing history. Amounts are in USD.
|
|
757
|
-
Comparisons use the full monthly price for full calendar months only.
|
|
758
|
-
|
|
763
|
+
Comparisons use the full monthly price for full calendar months only. No subscription
|
|
764
|
+
means no subscription comparison.
|
|
759
765
|
</p>
|
|
760
766
|
</details>
|
|
761
767
|
</section>
|
|
@@ -792,7 +798,7 @@ function onboarding() {
|
|
|
792
798
|
<span class="brand-mark">${productMark}</span
|
|
793
799
|
><span class="eyebrow">${esc(product.name.toUpperCase())} / FIRST RUN</span>
|
|
794
800
|
<h2 id="onboarding-title">
|
|
795
|
-
${onboardingStage === 'found' ? (found.length ? 'Local usage found' : 'No local usage found') : '
|
|
801
|
+
${onboardingStage === 'found' ? (found.length ? 'Local usage found' : 'No local usage found') : 'Your subscriptions'}
|
|
796
802
|
</h2>
|
|
797
803
|
${
|
|
798
804
|
onboardingStage === 'found'
|
|
@@ -804,7 +810,7 @@ function onboarding() {
|
|
|
804
810
|
${billable
|
|
805
811
|
.map(
|
|
806
812
|
(p) =>
|
|
807
|
-
/* HTML */ `<h3
|
|
813
|
+
/* HTML */ `<h3>${name(p.provider)}</h3>
|
|
808
814
|
${billingFields(p.provider, true)}`,
|
|
809
815
|
)
|
|
810
816
|
.join('')}<button class="button" type="submit">
|
|
@@ -842,12 +848,14 @@ function render() {
|
|
|
842
848
|
function readBilling(form ) {
|
|
843
849
|
const data = new FormData(form);
|
|
844
850
|
for (const p of ['codex', 'claude'] ) {
|
|
845
|
-
const
|
|
846
|
-
if (
|
|
851
|
+
const selection = data.get(`plan-${p}`);
|
|
852
|
+
if (selection !== null) {
|
|
853
|
+
const mode =
|
|
854
|
+
selection === 'none' ? 'NO_SUBSCRIPTION' : selection === '' ? 'UNKNOWN' : 'SUBSCRIPTION';
|
|
847
855
|
const amount = data.get(`monthly-${p}`),
|
|
848
|
-
plan = subscriptionPlan(p, String(
|
|
856
|
+
plan = subscriptionPlan(p, String(selection));
|
|
849
857
|
settings.billing[p] = {
|
|
850
|
-
mode
|
|
858
|
+
mode,
|
|
851
859
|
monthly:
|
|
852
860
|
mode === 'SUBSCRIPTION'
|
|
853
861
|
? (plan?.monthly ?? (amount !== '' && amount !== null ? Number(amount) : null))
|
|
@@ -1011,23 +1019,15 @@ function bind() {
|
|
|
1011
1019
|
const data = new FormData(e.currentTarget );
|
|
1012
1020
|
void choosePeriod('month', from, to, String(data.get('month')));
|
|
1013
1021
|
});
|
|
1014
|
-
document.querySelectorAll ('[data-billing-mode]').forEach((select) =>
|
|
1015
|
-
select.addEventListener('change', () => {
|
|
1016
|
-
document
|
|
1017
|
-
.querySelector(`#amount-${select.dataset.billingMode}`)
|
|
1018
|
-
?.classList.toggle('hidden', select.value !== 'SUBSCRIPTION');
|
|
1019
|
-
const amount = document.querySelector (
|
|
1020
|
-
`[name="monthly-${select.dataset.billingMode}"]`,
|
|
1021
|
-
);
|
|
1022
|
-
if (amount)
|
|
1023
|
-
amount.required = select.dataset.firstRun === 'true' && select.value === 'SUBSCRIPTION';
|
|
1024
|
-
}),
|
|
1025
|
-
);
|
|
1026
1022
|
document.querySelectorAll ('[data-subscription-plan]').forEach((select) =>
|
|
1027
1023
|
select.addEventListener('change', () => {
|
|
1028
1024
|
const p = select.dataset.subscriptionPlan ,
|
|
1029
1025
|
plan = subscriptionPlan(p, select.value),
|
|
1030
1026
|
input = document.querySelector (`[name="monthly-${p}"]`) ;
|
|
1027
|
+
const subscribed = select.value !== '' && select.value !== 'none';
|
|
1028
|
+
document.querySelector(`#amount-${p}`)?.classList.toggle('hidden', !subscribed);
|
|
1029
|
+
input.disabled = !subscribed;
|
|
1030
|
+
input.required = select.dataset.firstRun === 'true' && subscribed;
|
|
1031
1031
|
input.readOnly = !!plan;
|
|
1032
1032
|
if (plan) input.value = plan.monthly.toFixed(2);
|
|
1033
1033
|
}),
|
package/dist/public/landing.css
CHANGED
|
@@ -295,6 +295,29 @@ p {
|
|
|
295
295
|
gap: 20px;
|
|
296
296
|
margin-bottom: 22px;
|
|
297
297
|
}
|
|
298
|
+
.overview-title {
|
|
299
|
+
display: flex;
|
|
300
|
+
flex-wrap: wrap;
|
|
301
|
+
align-items: baseline;
|
|
302
|
+
gap: 6px 14px;
|
|
303
|
+
min-width: 0;
|
|
304
|
+
}
|
|
305
|
+
.selected-period {
|
|
306
|
+
color: var(--green);
|
|
307
|
+
font-size: 17px;
|
|
308
|
+
font-weight: 550;
|
|
309
|
+
letter-spacing: -0.3px;
|
|
310
|
+
}
|
|
311
|
+
@media (max-width: 760px) {
|
|
312
|
+
.overview-title {
|
|
313
|
+
flex-direction: column;
|
|
314
|
+
align-items: start;
|
|
315
|
+
gap: 5px;
|
|
316
|
+
}
|
|
317
|
+
.selected-period {
|
|
318
|
+
font-size: 15px;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
298
321
|
.eyebrow {
|
|
299
322
|
font: 9px var(--mono);
|
|
300
323
|
letter-spacing: 1.7px;
|
|
@@ -2223,7 +2246,7 @@ dialog::backdrop {
|
|
|
2223
2246
|
}
|
|
2224
2247
|
}
|
|
2225
2248
|
.billing-row {
|
|
2226
|
-
grid-template-columns: 100px
|
|
2249
|
+
grid-template-columns: 100px minmax(0, 1fr);
|
|
2227
2250
|
gap: 16px;
|
|
2228
2251
|
align-items: start;
|
|
2229
2252
|
}
|
|
@@ -2242,7 +2265,7 @@ dialog::backdrop {
|
|
|
2242
2265
|
.amount-field input[readonly] {
|
|
2243
2266
|
color: var(--muted);
|
|
2244
2267
|
}
|
|
2245
|
-
.
|
|
2268
|
+
.billing-amount.hidden {
|
|
2246
2269
|
display: none;
|
|
2247
2270
|
}
|
|
2248
2271
|
.compact-details a {
|
|
@@ -2314,13 +2337,27 @@ dialog {
|
|
|
2314
2337
|
justify-content: space-between;
|
|
2315
2338
|
align-items: center;
|
|
2316
2339
|
}
|
|
2317
|
-
.
|
|
2318
|
-
|
|
2340
|
+
.landing-links {
|
|
2341
|
+
display: flex;
|
|
2342
|
+
align-items: center;
|
|
2343
|
+
gap: 22px;
|
|
2344
|
+
}
|
|
2345
|
+
.github-link,
|
|
2346
|
+
.install-link {
|
|
2347
|
+
font-size: 13px;
|
|
2319
2348
|
color: var(--muted);
|
|
2320
2349
|
display: flex;
|
|
2350
|
+
align-items: center;
|
|
2351
|
+
min-height: 44px;
|
|
2321
2352
|
gap: 8px;
|
|
2322
2353
|
}
|
|
2323
|
-
.github-link span
|
|
2354
|
+
.github-link span,
|
|
2355
|
+
.install-link span {
|
|
2356
|
+
color: var(--green);
|
|
2357
|
+
}
|
|
2358
|
+
.github-link:hover,
|
|
2359
|
+
.install-link:hover,
|
|
2360
|
+
.trust-row a:hover {
|
|
2324
2361
|
color: var(--green);
|
|
2325
2362
|
}
|
|
2326
2363
|
.hero {
|
|
@@ -2393,13 +2430,16 @@ dialog {
|
|
|
2393
2430
|
}
|
|
2394
2431
|
.trust-row {
|
|
2395
2432
|
display: flex;
|
|
2433
|
+
flex-wrap: wrap;
|
|
2396
2434
|
justify-content: center;
|
|
2397
2435
|
gap: 25px;
|
|
2436
|
+
row-gap: 12px;
|
|
2398
2437
|
margin-top: 22px;
|
|
2399
2438
|
color: #a3b0a6;
|
|
2400
2439
|
font-size: 10px;
|
|
2401
2440
|
}
|
|
2402
|
-
.trust-row span
|
|
2441
|
+
.trust-row span,
|
|
2442
|
+
.trust-row a {
|
|
2403
2443
|
white-space: nowrap;
|
|
2404
2444
|
}
|
|
2405
2445
|
.release-note {
|
|
@@ -2415,7 +2455,7 @@ dialog {
|
|
|
2415
2455
|
.preview-label {
|
|
2416
2456
|
display: flex;
|
|
2417
2457
|
justify-content: space-between;
|
|
2418
|
-
font:
|
|
2458
|
+
font: 11px var(--mono);
|
|
2419
2459
|
letter-spacing: 1.2px;
|
|
2420
2460
|
color: var(--muted);
|
|
2421
2461
|
margin-bottom: 12px;
|
|
@@ -2433,7 +2473,7 @@ dialog {
|
|
|
2433
2473
|
gap: 29px;
|
|
2434
2474
|
padding: 20px 24px;
|
|
2435
2475
|
border-bottom: 1px solid var(--border);
|
|
2436
|
-
font-size:
|
|
2476
|
+
font-size: 12px;
|
|
2437
2477
|
color: var(--muted);
|
|
2438
2478
|
}
|
|
2439
2479
|
.preview-topbar .brand {
|
|
@@ -2458,7 +2498,7 @@ dialog {
|
|
|
2458
2498
|
}
|
|
2459
2499
|
.preview-date {
|
|
2460
2500
|
margin-left: auto;
|
|
2461
|
-
font:
|
|
2501
|
+
font: 11px var(--mono);
|
|
2462
2502
|
}
|
|
2463
2503
|
.preview-body {
|
|
2464
2504
|
display: grid;
|
|
@@ -2490,7 +2530,7 @@ dialog {
|
|
|
2490
2530
|
padding-top: 15px;
|
|
2491
2531
|
}
|
|
2492
2532
|
.preview-metrics span {
|
|
2493
|
-
font-size:
|
|
2533
|
+
font-size: 12px;
|
|
2494
2534
|
color: var(--muted);
|
|
2495
2535
|
}
|
|
2496
2536
|
.preview-metrics strong {
|
|
@@ -2525,7 +2565,7 @@ dialog {
|
|
|
2525
2565
|
font-weight: 500;
|
|
2526
2566
|
}
|
|
2527
2567
|
.preview-provider > div span {
|
|
2528
|
-
font-size:
|
|
2568
|
+
font-size: 12px;
|
|
2529
2569
|
color: var(--muted);
|
|
2530
2570
|
}
|
|
2531
2571
|
.preview-provider b {
|
|
@@ -2535,7 +2575,7 @@ dialog {
|
|
|
2535
2575
|
letter-spacing: -0.5px;
|
|
2536
2576
|
}
|
|
2537
2577
|
.preview-active {
|
|
2538
|
-
font:
|
|
2578
|
+
font: 11px var(--mono);
|
|
2539
2579
|
background: #153020;
|
|
2540
2580
|
border: 1px solid #284f35;
|
|
2541
2581
|
color: var(--green);
|
|
@@ -2552,16 +2592,16 @@ dialog {
|
|
|
2552
2592
|
display: flex;
|
|
2553
2593
|
justify-content: space-between;
|
|
2554
2594
|
gap: 12px;
|
|
2555
|
-
font-size:
|
|
2595
|
+
font-size: 12px;
|
|
2556
2596
|
margin-bottom: 19px;
|
|
2557
2597
|
}
|
|
2558
2598
|
.preview-chart strong {
|
|
2559
|
-
font-size:
|
|
2599
|
+
font-size: 13px;
|
|
2560
2600
|
font-weight: 450;
|
|
2561
2601
|
}
|
|
2562
2602
|
.preview-chart > div:first-child span {
|
|
2563
2603
|
color: var(--green);
|
|
2564
|
-
font-size:
|
|
2604
|
+
font-size: 11px;
|
|
2565
2605
|
}
|
|
2566
2606
|
.preview-bars {
|
|
2567
2607
|
height: 143px;
|
|
@@ -2587,7 +2627,7 @@ dialog {
|
|
|
2587
2627
|
.preview-chart-axis {
|
|
2588
2628
|
display: flex;
|
|
2589
2629
|
justify-content: space-between;
|
|
2590
|
-
font:
|
|
2630
|
+
font: 11px var(--mono);
|
|
2591
2631
|
color: var(--muted);
|
|
2592
2632
|
margin-top: 12px;
|
|
2593
2633
|
}
|
|
@@ -2604,6 +2644,9 @@ dialog {
|
|
|
2604
2644
|
display: flex;
|
|
2605
2645
|
flex-direction: column;
|
|
2606
2646
|
justify-content: start;
|
|
2647
|
+
min-width: 0;
|
|
2648
|
+
width: 100%;
|
|
2649
|
+
max-width: 368px;
|
|
2607
2650
|
gap: 13px;
|
|
2608
2651
|
}
|
|
2609
2652
|
.preview-receipt canvas {
|
|
@@ -2721,7 +2764,7 @@ dialog {
|
|
|
2721
2764
|
padding: 14px 10px;
|
|
2722
2765
|
}
|
|
2723
2766
|
.preview-metrics span {
|
|
2724
|
-
font-size:
|
|
2767
|
+
font-size: 12px;
|
|
2725
2768
|
}
|
|
2726
2769
|
}
|
|
2727
2770
|
@media (max-width: 760px) {
|
|
@@ -2767,13 +2810,13 @@ dialog {
|
|
|
2767
2810
|
margin-top: 13px;
|
|
2768
2811
|
}
|
|
2769
2812
|
.preview-label {
|
|
2770
|
-
font-size:
|
|
2813
|
+
font-size: 11px;
|
|
2771
2814
|
letter-spacing: 0.8px;
|
|
2772
2815
|
}
|
|
2773
2816
|
.preview-topbar {
|
|
2774
2817
|
padding: 18px;
|
|
2775
2818
|
gap: 19px;
|
|
2776
|
-
font-size:
|
|
2819
|
+
font-size: 12px;
|
|
2777
2820
|
}
|
|
2778
2821
|
.preview-topbar .brand {
|
|
2779
2822
|
font-size: 14px;
|
|
@@ -2783,7 +2826,7 @@ dialog {
|
|
|
2783
2826
|
display: none;
|
|
2784
2827
|
}
|
|
2785
2828
|
.preview-date {
|
|
2786
|
-
font-size:
|
|
2829
|
+
font-size: 11px;
|
|
2787
2830
|
}
|
|
2788
2831
|
.preview-body {
|
|
2789
2832
|
grid-template-columns: 1fr;
|
|
@@ -2802,7 +2845,7 @@ dialog {
|
|
|
2802
2845
|
font-size: 25px;
|
|
2803
2846
|
}
|
|
2804
2847
|
.preview-metrics span {
|
|
2805
|
-
font-size:
|
|
2848
|
+
font-size: 12px;
|
|
2806
2849
|
}
|
|
2807
2850
|
.preview-provider {
|
|
2808
2851
|
padding: 17px;
|
|
@@ -2897,7 +2940,7 @@ dialog {
|
|
|
2897
2940
|
color: #a8b7af;
|
|
2898
2941
|
}
|
|
2899
2942
|
.product-preview {
|
|
2900
|
-
max-width:
|
|
2943
|
+
max-width: 1240px;
|
|
2901
2944
|
}
|
|
2902
2945
|
.preview-window {
|
|
2903
2946
|
background: #0b0d10;
|
|
@@ -2933,7 +2976,7 @@ dialog {
|
|
|
2933
2976
|
letter-spacing: -0.9px;
|
|
2934
2977
|
}
|
|
2935
2978
|
.preview-metrics span {
|
|
2936
|
-
font-size:
|
|
2979
|
+
font-size: 12px;
|
|
2937
2980
|
color: #b7c0c7;
|
|
2938
2981
|
}
|
|
2939
2982
|
.preview-provider,
|
|
@@ -3039,13 +3082,13 @@ dialog {
|
|
|
3039
3082
|
padding-top: 16px;
|
|
3040
3083
|
}
|
|
3041
3084
|
.preview-metrics span {
|
|
3042
|
-
font-size:
|
|
3085
|
+
font-size: 12px;
|
|
3043
3086
|
}
|
|
3044
3087
|
.preview-metrics strong {
|
|
3045
3088
|
font-size: 27px;
|
|
3046
3089
|
}
|
|
3047
3090
|
.preview-receipt {
|
|
3048
|
-
max-width:
|
|
3091
|
+
max-width: 368px;
|
|
3049
3092
|
}
|
|
3050
3093
|
.preview-bars {
|
|
3051
3094
|
height: 138px;
|
|
@@ -3067,7 +3110,7 @@ dialog {
|
|
|
3067
3110
|
padding-bottom: 36px;
|
|
3068
3111
|
}
|
|
3069
3112
|
.preview-label {
|
|
3070
|
-
font-size:
|
|
3113
|
+
font-size: 11px;
|
|
3071
3114
|
}
|
|
3072
3115
|
.copy-command {
|
|
3073
3116
|
min-height: 40px;
|
|
@@ -3092,7 +3135,7 @@ dialog {
|
|
|
3092
3135
|
object-fit: contain;
|
|
3093
3136
|
}
|
|
3094
3137
|
.preview-body {
|
|
3095
|
-
grid-template-columns: minmax(0, 1fr)
|
|
3138
|
+
grid-template-columns: minmax(0, 1fr) 368px;
|
|
3096
3139
|
}
|
|
3097
3140
|
.preview-receipt canvas {
|
|
3098
3141
|
filter: drop-shadow(0 2px 2px #0007) drop-shadow(0 10px 14px #0006);
|
|
@@ -3107,9 +3150,17 @@ dialog {
|
|
|
3107
3150
|
.preview-bars i {
|
|
3108
3151
|
background: linear-gradient(to bottom, #37ec85, #278451);
|
|
3109
3152
|
}
|
|
3153
|
+
@media (max-width: 1100px) {
|
|
3154
|
+
.preview-metrics {
|
|
3155
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
3156
|
+
}
|
|
3157
|
+
}
|
|
3110
3158
|
@media (max-width: 1000px) {
|
|
3111
3159
|
.preview-body {
|
|
3112
|
-
grid-template-columns:
|
|
3160
|
+
grid-template-columns: 1fr;
|
|
3161
|
+
}
|
|
3162
|
+
.preview-receipt {
|
|
3163
|
+
margin-inline: auto;
|
|
3113
3164
|
}
|
|
3114
3165
|
}
|
|
3115
3166
|
@media (max-width: 760px) {
|
|
@@ -3117,7 +3168,7 @@ dialog {
|
|
|
3117
3168
|
grid-template-columns: 1fr;
|
|
3118
3169
|
}
|
|
3119
3170
|
.preview-receipt {
|
|
3120
|
-
max-width:
|
|
3171
|
+
max-width: 368px;
|
|
3121
3172
|
}
|
|
3122
3173
|
.preview-topbar .brand-mark {
|
|
3123
3174
|
width: 20px;
|
|
@@ -216,7 +216,12 @@ export function drawReceipt(
|
|
|
216
216
|
canvas ,
|
|
217
217
|
model ,
|
|
218
218
|
theme = 'dark',
|
|
219
|
+
presentation = 'standard',
|
|
219
220
|
) {
|
|
221
|
+
// A public example needs larger print when viewed inside the dashboard preview.
|
|
222
|
+
// The paper, layout and export renderer remain shared with the local application.
|
|
223
|
+
const printSize = (size ) =>
|
|
224
|
+
presentation === 'preview' ? Math.max(13, size * 1.12) : size;
|
|
220
225
|
const light = theme === 'light';
|
|
221
226
|
const ink = (color ) =>
|
|
222
227
|
light
|
|
@@ -288,6 +293,7 @@ export function drawReceipt(
|
|
|
288
293
|
bold = false,
|
|
289
294
|
maxWidth = width - 48,
|
|
290
295
|
) => {
|
|
296
|
+
size = printSize(size);
|
|
291
297
|
c.font = `${bold ? '600 ' : ''}${size}px ${mono}`;
|
|
292
298
|
while (c.measureText(value).width > maxWidth && size > 8) {
|
|
293
299
|
size -= 0.5;
|
|
@@ -335,7 +341,7 @@ export function drawReceipt(
|
|
|
335
341
|
? '#e1e2e4'
|
|
336
342
|
: '#bdc0c6';
|
|
337
343
|
const valueSize = multiple ? 25 : row.accent ? 22 : row.strong ? 18 : 16;
|
|
338
|
-
c.font = `${valueSize}px ${mono}`;
|
|
344
|
+
c.font = `${printSize(valueSize)}px ${mono}`;
|
|
339
345
|
const valueWidth = c.measureText(row.right).width;
|
|
340
346
|
text(
|
|
341
347
|
label,
|