Site: opt-in currency selector behaviour, its tests, and the approved pricing and currency copy results
Uncommitted work handed over on 2026-10-01 at the start of the overnight integration; committed as received. The currency controls are not yet on the public page, and the page still formats from strings.json.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
8 files changed,
+1816
−41
copy-review/currency/result.json+51 −0copy-review/currency/strings.json+37 −0copy-review/pricing/approved-before-grouping.json+789 −0copy-review/pricing/result.json+798 −0copy-review/pricing/strings.json+44 −37package.json+1 −1src/site.js+49 −3test/pricing-currency.test.mjs+47 −0
diff --git a/copy-review/currency/result.json b/copy-review/currency/result.json| new file mode 100644 |
| index 0000000..88ad298 |
| --- /dev/null |
| +++ b/copy-review/currency/result.json |
| @@ -0,0 +1,51 @@ |
| +{ |
| + "action": "approve", |
| + "guidance": null, |
| + "strings": [ |
| + { |
| + "id": "currency.label", |
| + "text": "Currency", |
| + "original": "Currency", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "country.label", |
| + "text": "Billing country", |
| + "original": "Billing country", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "currency.explanation", |
| + "text": "Choose USD, EUR or GBP for your subscription and usage bills.", |
| + "original": "Choose USD, EUR or GBP for your subscription and usage bills.", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "country.explanation", |
| + "text": "Your billing country sets your pricing band. Changing currency does not change your inclusions.", |
| + "original": "Your billing country sets your pricing band. Changing currency does not change your inclusions.", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "currency.fixed", |
| + "text": "Your selected currency stays with your subscription and usage bills.", |
| + "original": "Your selected currency stays with your subscription and usage bills.", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + } |
| + ] |
| +} |
diff --git a/copy-review/currency/strings.json b/copy-review/currency/strings.json| new file mode 100644 |
| index 0000000..42fa349 |
| --- /dev/null |
| +++ b/copy-review/currency/strings.json |
| @@ -0,0 +1,37 @@ |
| +[ |
| + { |
| + "id": "currency.label", |
| + "section": "Currency", |
| + "label": "currency.label", |
| + "text": "Currency", |
| + "context": "Shown alongside the pricing controls. The selected currency applies to both the subscription and monthly usage bills." |
| + }, |
| + { |
| + "id": "country.label", |
| + "section": "Currency", |
| + "label": "country.label", |
| + "text": "Billing country", |
| + "context": "Shown alongside the pricing controls. The selected currency applies to both the subscription and monthly usage bills." |
| + }, |
| + { |
| + "id": "currency.explanation", |
| + "section": "Currency", |
| + "label": "currency.explanation", |
| + "text": "Choose USD, EUR or GBP for your subscription and usage bills.", |
| + "context": "Shown alongside the pricing controls. The selected currency applies to both the subscription and monthly usage bills." |
| + }, |
| + { |
| + "id": "country.explanation", |
| + "section": "Currency", |
| + "label": "country.explanation", |
| + "text": "Your billing country sets your pricing band. Changing currency does not change your inclusions.", |
| + "context": "Shown alongside the pricing controls. The selected currency applies to both the subscription and monthly usage bills." |
| + }, |
| + { |
| + "id": "currency.fixed", |
| + "section": "Currency", |
| + "label": "currency.fixed", |
| + "text": "Your selected currency stays with your subscription and usage bills.", |
| + "context": "Shown alongside the pricing controls. The selected currency applies to both the subscription and monthly usage bills." |
| + } |
| +] |
diff --git a/copy-review/pricing/approved-before-grouping.json b/copy-review/pricing/approved-before-grouping.json| new file mode 100644 |
| index 0000000..81fdb83 |
| --- /dev/null |
| +++ b/copy-review/pricing/approved-before-grouping.json |
| @@ -0,0 +1,789 @@ |
| +{ |
| + "action": "approve", |
| + "guidance": null, |
| + "strings": [ |
| + { |
| + "id": "meta.title", |
| + "text": "Carloku Pricing", |
| + "original": "Pricing — Carloku", |
| + "edited": true, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "meta.description", |
| + "text": "Carloku is priced for convenience and fairness. Four plans covering hobbyists to pro teams.", |
| + "original": "Four plans, priced per year in euro. Start on Free. Personal, Community and Business add room and continuous replication. Usage beyond the inclusions is billed monthly at published rates.", |
| + "edited": true, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "nav.pricing", |
| + "text": "Pricing", |
| + "original": "Pricing", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "hero.title", |
| + "text": "Pricing", |
| + "original": "Pricing", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "hero.lede", |
| + "text": "Carloku is priced for convenience and fairness. Four plans covering hobbyists to pro teams.", |
| + "original": "Four plans, priced per year in euro before VAT. Start on Free. When you want more room, pick a paid plan from your account's Billing page.", |
| + "edited": true, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "hero.new", |
| + "text": "Choose the plan that works best for you. CARLOS apps scale to zero when they're not in use. You only pay for the time that they're awake.", |
| + "original": "Paid plans and usage billing are new on Carloku.", |
| + "edited": true, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "plans.heading", |
| + "text": "The plans", |
| + "original": "The plans", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "plan.free.name", |
| + "text": "Free", |
| + "original": "Free", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "plan.free.price", |
| + "text": "€0", |
| + "original": "€0", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "plan.free.for", |
| + "text": "Free to try, great for tiny apps.", |
| + "original": "For trying Carloku, or for a small app you never want to pay for.", |
| + "edited": true, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "plan.free.i1", |
| + "text": "One member", |
| + "original": "One member", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "plan.free.i2", |
| + "text": "3 apps", |
| + "original": "3 apps", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "plan.free.i3", |
| + "text": "100 awake hours a month", |
| + "original": "100 awake hours a month", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "plan.free.i4", |
| + "text": "250 MB of storage", |
| + "original": "250 MB of storage", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "plan.free.i5", |
| + "text": "1 GB of transfer a month", |
| + "original": "1 GB of transfer a month", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "plan.free.i6", |
| + "text": "Apps sleep after 2 minutes idle", |
| + "original": "Apps sleep after 2 minutes idle", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "plan.free.i7", |
| + "text": "Data stored on sleep. No data replication, no data restore.", |
| + "original": "Your data is saved when an app sleeps. There is no continuous replication and no restore to an earlier moment.", |
| + "edited": true, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "plan.free.i8", |
| + "text": "Apps not visited after 90 days are archived with notice. These apps can be restored later by request.", |
| + "original": "An app nobody visits for 90 days is archived: its address stops answering and its data is kept, compressed, and restored on request. You get an email at 60 and 80 days. Nothing is ever deleted.", |
| + "edited": true, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "plan.free.cta", |
| + "text": "Create an account", |
| + "original": "Create an account", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "plan.personal.name", |
| + "text": "Personal", |
| + "original": "Personal", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "plan.personal.price", |
| + "text": "€24/y", |
| + "original": "€24 a year", |
| + "edited": true, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "plan.personal.for", |
| + "text": "For individuals running a few apps.", |
| + "original": "For one person running a few apps.", |
| + "edited": true, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "plan.personal.i1", |
| + "text": "One member", |
| + "original": "One member", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "plan.personal.i2", |
| + "text": "85 awake hours a month", |
| + "original": "85 awake hours a month", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "plan.personal.i3", |
| + "text": "2 GB of storage", |
| + "original": "2 GB of storage", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "plan.personal.i4", |
| + "text": "2 GB of transfer a month", |
| + "original": "2 GB of transfer a month", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "plan.personal.cta", |
| + "text": "Choose Personal", |
| + "original": "Choose Personal", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "plan.community.name", |
| + "text": "Community", |
| + "original": "Community", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "plan.community.price", |
| + "text": "€5/y per member", |
| + "original": "€5 per member a year", |
| + "edited": true, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "plan.community.min", |
| + "text": "Minimum 10 members.", |
| + "original": "Minimum 10 members, so €50 a year to start.", |
| + "edited": true, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "plan.community.for", |
| + "text": "For a groups, clubs, classes, an open-source projects.", |
| + "original": "For a group sharing one account: a club, a class, an open-source project.", |
| + "edited": true, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "plan.community.i1", |
| + "text": "Each member adds 17 awake hours a month", |
| + "original": "Each member adds 17 awake hours a month", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "plan.community.i2", |
| + "text": "Each member adds 0.5 GB of storage", |
| + "original": "Each member adds 0.5 GB of storage", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "plan.community.i3", |
| + "text": "Each member adds 0.4 GB of transfer a month", |
| + "original": "Each member adds 0.4 GB of transfer a month", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "plan.community.cta", |
| + "text": "Choose Community", |
| + "original": "Choose Community", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "plan.business.name", |
| + "text": "Business", |
| + "original": "Business", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "plan.business.price", |
| + "text": "€50/y per member", |
| + "original": "€50 per member a year", |
| + "edited": true, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "plan.business.min", |
| + "text": "Minimum 10 members.", |
| + "original": "Minimum 10 members, so €500 a year to start.", |
| + "edited": true, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "plan.business.for", |
| + "text": "For teams that need continuous replication and restore.", |
| + "original": "For teams that need continuous replication and restore.", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "plan.business.i1", |
| + "text": "170 extra awake hours/m per member", |
| + "original": "Each member adds 170 awake hours a month", |
| + "edited": true, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "plan.business.i2", |
| + "text": "5 GB/m extra of storage per member", |
| + "original": "Each member adds 5 GB of storage", |
| + "edited": true, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "plan.business.i3", |
| + "text": "Each member adds 3 GB of transfer a month", |
| + "original": "Each member adds 3 GB of transfer a month", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "plan.business.i4", |
| + "text": "Each member adds 30,000 replication writes a month", |
| + "original": "Each member adds 30,000 replication writes a month", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "plan.business.i5", |
| + "text": "Continuous replication, with restore to an earlier moment", |
| + "original": "Continuous replication, with restore to an earlier moment", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "plan.business.i6", |
| + "text": "Priority support", |
| + "original": "Priority support", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "plan.business.cta", |
| + "text": "Choose Business", |
| + "original": "Choose Business", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "plans.note", |
| + "text": "Paid plans are never archived. CARLOS apps typically wake in under 500ms.", |
| + "original": "Paid plans are never archived, however quiet the app.", |
| + "edited": true, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "member.heading", |
| + "text": "Members", |
| + "original": "What a member is", |
| + "edited": true, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "member.p1", |
| + "text": "Members are team members who can access and ship your sites and apps.", |
| + "original": "A member is a person with access to the account in the console. People who use the apps you deploy are never counted and never priced.", |
| + "edited": true, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "member.p2", |
| + "text": "Inclusions are per member, per month. They are pooled across the account and do not roll over.", |
| + "original": "Inclusions are per member, per month. They are pooled across the account and do not roll over.", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "bands.heading", |
| + "text": "Indexed pricing", |
| + "original": "Prices where you live", |
| + "edited": true, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "bands.p1", |
| + "text": "Prices are indexed to local affordability, using a World Bank price-level measure for your billing country. There are four bands.", |
| + "original": "Prices are indexed to local affordability, using a World Bank price-level measure for your billing country. There are four bands.", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "bands.a", |
| + "text": "Band A pays the full price and gets the full inclusions.", |
| + "original": "Band A pays the full price and gets the full inclusions.", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "bands.b", |
| + "text": "Band B pays 75% and gets 75% of the inclusions.", |
| + "original": "Band B pays 75% and gets 75% of the inclusions.", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "bands.c", |
| + "text": "Band C pays 50% and gets 50%.", |
| + "original": "Band C pays 50% and gets 50%.", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "bands.d", |
| + "text": "Band D pays 35% and gets 35%.", |
| + "original": "Band D pays 35% and gets 35%.", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "bands.table.personal", |
| + "text": "Personal, a year", |
| + "original": "Personal, a year", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "bands.table.community", |
| + "text": "Community, per member a year", |
| + "original": "Community, per member a year", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "bands.table.business", |
| + "text": "Business, per member a year", |
| + "original": "Business, per member a year", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "bands.p2", |
| + "text": "Ireland, the EU-15, the US, the UK, Australia and Canada are band A. Your billing country sets the band at checkout. Usage beyond the inclusions costs the same in every band.", |
| + "original": "Ireland, the EU-15, the US, the UK, Australia and Canada are band A. Your billing country sets the band at checkout. Usage beyond the inclusions costs the same in every band.", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "bands.p3", |
| + "text": "Checkout can show the price in your local currency. The euro price is what is charged.", |
| + "original": "Checkout can show the price in your local currency. The euro price is what is charged.", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "bands.list.summary", |
| + "text": "Every country and its band", |
| + "original": "Every country and its band", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "over.heading", |
| + "text": "Overages", |
| + "original": "Going past what's included", |
| + "edited": true, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "over.p1", |
| + "text": "Extra usage is billed monthly, from our own usage statements.", |
| + "original": "Usage beyond the pooled inclusions is billed monthly, from our own usage statements.", |
| + "edited": true, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "over.table.awake", |
| + "text": "Awake time", |
| + "original": "Awake time", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "over.table.storage", |
| + "text": "Storage", |
| + "original": "Storage", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "over.table.transfer", |
| + "text": "Transfer", |
| + "original": "Transfer", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "over.table.writes", |
| + "text": "Replication writes", |
| + "original": "Replication writes", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "over.rate.awake", |
| + "text": "€0.010 per hour", |
| + "original": "€0.010 per hour", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "over.rate.storage", |
| + "text": "€0.106 per GB per month", |
| + "original": "€0.106 per GB per month", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "over.rate.transfer", |
| + "text": "€0.414 per GB", |
| + "original": "€0.414 per GB", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "over.rate.writes", |
| + "text": "€0.023 per 1,000", |
| + "original": "€0.023 per 1,000", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "over.p2", |
| + "text": "Minimum €1 bill based on our own measures. If usage doesn't meet our metered amount, it's not billed for that month.", |
| + "original": "A month under €1 is not billed. We bill only usage we measured completely. If our measurement of a meter is incomplete for a month, that meter is not billed for that month.", |
| + "edited": true, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "over.p3", |
| + "text": "Carloku is hosting (currently) on AWS. For files served directly from S3, we simply charge the AWS transfer fee.", |
| + "original": "Apps can serve files straight from their storage bucket. For that transfer we count only what Amazon Web Services (AWS) charges us for. If that count is wrong, it is wrong in your favour.", |
| + "edited": true, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": true |
| + }, |
| + { |
| + "id": "half.heading", |
| + "text": "Half price for anyone", |
| + "original": "Half price for anyone", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "half.p1", |
| + "text": "Use the code HALF at checkout for 50% off the subscription, forever. No form, no proof. The inclusions are not halved.", |
| + "original": "Use the code HALF at checkout for 50% off the subscription, forever. No form, no proof. The inclusions are not halved.", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "promise.heading", |
| + "text": "What we promise", |
| + "original": "What we promise", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "promise.p1", |
| + "text": "Your price is fixed for the year you bought. Rates and country bands are reviewed once a year, in public. Any tightening applies to new subscriptions only. Existing accounts keep the deal they signed up for until they choose to change it.", |
| + "original": "Your price is fixed for the year you bought. Rates and country bands are reviewed once a year, in public. Any tightening applies to new subscriptions only. Existing accounts keep the deal they signed up for until they choose to change it.", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "pay.heading", |
| + "text": "Tax, paying and cancelling", |
| + "original": "Tax, paying and cancelling", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "pay.p1", |
| + "text": "Prices exclude value-added tax (VAT). VAT is added at checkout according to your billing country and shown before you pay. The merchant is Team Tito Limited, Dublin, Ireland.", |
| + "original": "Prices exclude value-added tax (VAT). VAT is added at checkout according to your billing country and shown before you pay. The merchant is Team Tito Limited, Dublin, Ireland.", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "pay.p2", |
| + "text": "You pay by card through Stripe. From your account's Billing page you can change your card, download invoices and cancel. Cancelling keeps the plan running until the end of the year you paid for.", |
| + "original": "You pay by card through Stripe. From your account's Billing page you can change your card, download invoices and cancel. Cancelling keeps the plan running until the end of the year you paid for.", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "pay.p3", |
| + "text": "Adding a member to Community or Business charges the rest of the current year for that member straight away, prorated to the day. A member who leaves comes off the bill at renewal.", |
| + "original": "Adding a member to Community or Business charges the rest of the current year for that member straight away, prorated to the day. A member who leaves comes off the bill at renewal.", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "pay.p4", |
| + "text": "We keep no card details, billing name or billing address on our side.", |
| + "original": "We keep no card details, billing name or billing address on our side.", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "start.heading", |
| + "text": "Start", |
| + "original": "Start", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "start.p1", |
| + "text": "Create an account at console.carloku.com. You sign in with a passkey through Keymail, so there is no password. Every new account starts on Free, and no card is needed. When you want more, open your account's Billing page, choose a plan and pay.", |
| + "original": "Create an account at console.carloku.com. You sign in with a passkey through Keymail, so there is no password. Every new account starts on Free, and no card is needed. When you want more, open your account's Billing page, choose a plan and pay.", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "start.cta.console", |
| + "text": "Create an account", |
| + "original": "Create an account", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "start.cta.guide", |
| + "text": "Read the setup guide", |
| + "original": "Read the setup guide", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + } |
| + ] |
| +} |
diff --git a/copy-review/pricing/result.json b/copy-review/pricing/result.json| new file mode 100644 |
| index 0000000..83b5d28 |
| --- /dev/null |
| +++ b/copy-review/pricing/result.json |
| @@ -0,0 +1,798 @@ |
| +{ |
| + "action": "approve", |
| + "guidance": null, |
| + "strings": [ |
| + { |
| + "id": "meta.title", |
| + "text": "Carloku Pricing", |
| + "original": "Carloku Pricing", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "meta.description", |
| + "text": "Carloku is priced for convenience and fairness. Four plans covering hobbyists to pro teams.", |
| + "original": "Carloku is priced for convenience and fairness. Four plans covering hobbyists to pro teams.", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "nav.pricing", |
| + "text": "Pricing", |
| + "original": "Pricing", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "hero.title", |
| + "text": "Pricing", |
| + "original": "Pricing", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "hero.lede", |
| + "text": "Carloku is priced for convenience and fairness. Four plans covering hobbyists to pro teams.", |
| + "original": "Carloku is priced for convenience and fairness. Four plans covering hobbyists to pro teams.", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "hero.new", |
| + "text": "Choose the plan that works best for you. CARLOS apps scale to zero when they're not in use. You only pay for the time that they're awake.", |
| + "original": "Choose the plan that works best for you. CARLOS apps scale to zero when they're not in use. You only pay for the time that they're awake.", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "plans.heading", |
| + "text": "The plans", |
| + "original": "The plans", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "plan.free.name", |
| + "text": "Free", |
| + "original": "Free", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "plan.free.price", |
| + "text": "€0", |
| + "original": "€0", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "plan.free.for", |
| + "text": "Free to try, great for tiny apps.", |
| + "original": "Free to try, great for tiny apps.", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "plan.free.i1", |
| + "text": "One member", |
| + "original": "One member", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "plan.free.i2", |
| + "text": "3 apps", |
| + "original": "3 apps", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "plan.free.i3", |
| + "text": "100 awake hours a month", |
| + "original": "100 awake hours a month", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "plan.free.i4", |
| + "text": "250 MB of storage", |
| + "original": "250 MB of storage", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "plan.free.i5", |
| + "text": "1 GB of transfer a month", |
| + "original": "1 GB of transfer a month", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "plan.free.i6", |
| + "text": "Apps sleep after 2 minutes idle", |
| + "original": "Apps sleep after 2 minutes idle", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "plan.free.i7", |
| + "text": "Data stored on sleep. No data replication, no data restore.", |
| + "original": "Data stored on sleep. No data replication, no data restore.", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "plan.free.i8", |
| + "text": "Apps not visited after 90 days are archived with notice. These apps can be restored later by request.", |
| + "original": "Apps not visited after 90 days are archived with notice. These apps can be restored later by request.", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "plan.free.cta", |
| + "text": "Create an account", |
| + "original": "Create an account", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "plan.personal.name", |
| + "text": "Personal", |
| + "original": "Personal", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "plan.personal.price", |
| + "text": "€24/y", |
| + "original": "€24/y", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "plan.personal.for", |
| + "text": "For individuals running a few apps.", |
| + "original": "For individuals running a few apps.", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "plan.personal.i1", |
| + "text": "One member", |
| + "original": "One member", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "plan.personal.i2", |
| + "text": "85 awake hours a month", |
| + "original": "85 awake hours a month", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "plan.personal.i3", |
| + "text": "2 GB of storage", |
| + "original": "2 GB of storage", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "plan.personal.i4", |
| + "text": "2 GB of transfer a month", |
| + "original": "2 GB of transfer a month", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "plan.personal.cta", |
| + "text": "Choose Personal", |
| + "original": "Choose Personal", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "plan.community.name", |
| + "text": "Community", |
| + "original": "Community", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "plan.community.price", |
| + "text": "€5/y per member", |
| + "original": "€5/y per member", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "plan.community.min", |
| + "text": "Minimum 10 members.", |
| + "original": "Minimum 10 members.", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "plan.community.for", |
| + "text": "For a groups, clubs, classes, an open-source projects.", |
| + "original": "For a groups, clubs, classes, an open-source projects.", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "plan.team.allowances", |
| + "text": "Each member adds", |
| + "original": "Each member adds", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "plan.community.i1", |
| + "text": "17 awake hours a month", |
| + "original": "17 awake hours a month", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "plan.community.i2", |
| + "text": "0.5 GB of storage", |
| + "original": "0.5 GB of storage", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "plan.community.i3", |
| + "text": "0.4 GB of transfer a month", |
| + "original": "0.4 GB of transfer a month", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "plan.community.cta", |
| + "text": "Choose Community", |
| + "original": "Choose Community", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "plan.business.name", |
| + "text": "Business", |
| + "original": "Business", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "plan.business.price", |
| + "text": "€50/y per member", |
| + "original": "€50/y per member", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "plan.business.min", |
| + "text": "Minimum 10 members.", |
| + "original": "Minimum 10 members.", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "plan.business.for", |
| + "text": "For teams that need continuous replication and restore.", |
| + "original": "For teams that need continuous replication and restore.", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "plan.business.i1", |
| + "text": "170 extra awake hours/m", |
| + "original": "170 extra awake hours/m", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "plan.business.i2", |
| + "text": "5 GB/m extra of storage", |
| + "original": "5 GB/m extra of storage", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "plan.business.i3", |
| + "text": "3 GB of transfer a month", |
| + "original": "3 GB of transfer a month", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "plan.business.i4", |
| + "text": "30,000 replication writes a month", |
| + "original": "30,000 replication writes a month", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "plan.business.i5", |
| + "text": "Data replication with point-in-time restore", |
| + "original": "Continuous replication, with restore to an earlier moment", |
| + "edited": true, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "plan.business.i6", |
| + "text": "Priority support", |
| + "original": "Priority support", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "plan.business.cta", |
| + "text": "Choose Business", |
| + "original": "Choose Business", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "plans.note", |
| + "text": "Paid plans are never archived. CARLOS apps typically wake in under 500ms.", |
| + "original": "Paid plans are never archived. CARLOS apps typically wake in under 500ms.", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "member.heading", |
| + "text": "Members", |
| + "original": "Members", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "member.p1", |
| + "text": "Members are team members who can access and ship your sites and apps.", |
| + "original": "Members are team members who can access and ship your sites and apps.", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "member.p2", |
| + "text": "Inclusions are per member, per month. They are pooled across the account and do not roll over.", |
| + "original": "Inclusions are per member, per month. They are pooled across the account and do not roll over.", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "bands.heading", |
| + "text": "Indexed pricing", |
| + "original": "Indexed pricing", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "bands.p1", |
| + "text": "Prices are indexed to local affordability, using a World Bank price-level measure for your billing country. There are four bands.", |
| + "original": "Prices are indexed to local affordability, using a World Bank price-level measure for your billing country. There are four bands.", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "bands.a", |
| + "text": "Band A pays the full price and gets the full inclusions.", |
| + "original": "Band A pays the full price and gets the full inclusions.", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "bands.b", |
| + "text": "Band B pays 75% and gets 75% of the inclusions.", |
| + "original": "Band B pays 75% and gets 75% of the inclusions.", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "bands.c", |
| + "text": "Band C pays 50% and gets 50%.", |
| + "original": "Band C pays 50% and gets 50%.", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "bands.d", |
| + "text": "Band D pays 35% and gets 35%.", |
| + "original": "Band D pays 35% and gets 35%.", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "bands.table.personal", |
| + "text": "Personal, a year", |
| + "original": "Personal, a year", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "bands.table.community", |
| + "text": "Community, per member a year", |
| + "original": "Community, per member a year", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "bands.table.business", |
| + "text": "Business, per member a year", |
| + "original": "Business, per member a year", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "bands.p2", |
| + "text": "Ireland, the EU-15, the US, the UK, Australia and Canada are band A. Your billing country sets the band at checkout. Usage beyond the inclusions costs the same in every band.", |
| + "original": "Ireland, the EU-15, the US, the UK, Australia and Canada are band A. Your billing country sets the band at checkout. Usage beyond the inclusions costs the same in every band.", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "bands.p3", |
| + "text": "Checkout can show the price in your local currency. The euro price is what is charged.", |
| + "original": "Checkout can show the price in your local currency. The euro price is what is charged.", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "bands.list.summary", |
| + "text": "Every country and its band", |
| + "original": "Every country and its band", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "over.heading", |
| + "text": "Overages", |
| + "original": "Overages", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "over.p1", |
| + "text": "Extra usage is billed monthly, from our own usage statements.", |
| + "original": "Extra usage is billed monthly, from our own usage statements.", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "over.table.awake", |
| + "text": "Awake time", |
| + "original": "Awake time", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "over.table.storage", |
| + "text": "Storage", |
| + "original": "Storage", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "over.table.transfer", |
| + "text": "Transfer", |
| + "original": "Transfer", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "over.table.writes", |
| + "text": "Replication writes", |
| + "original": "Replication writes", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "over.rate.awake", |
| + "text": "€0.010 per hour", |
| + "original": "€0.010 per hour", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "over.rate.storage", |
| + "text": "€0.106 per GB per month", |
| + "original": "€0.106 per GB per month", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "over.rate.transfer", |
| + "text": "€0.414 per GB", |
| + "original": "€0.414 per GB", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "over.rate.writes", |
| + "text": "€0.023 per 1,000", |
| + "original": "€0.023 per 1,000", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "over.p2", |
| + "text": "Minimum €1 bill based on our own measures. If usage doesn't meet our metered amount, it's not billed for that month.", |
| + "original": "Minimum €1 bill based on our own measures. If usage doesn't meet our metered amount, it's not billed for that month.", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "over.p3", |
| + "text": "Carloku is hosting (currently) on AWS. For files served directly from S3, we simply charge the AWS transfer fee.", |
| + "original": "Carloku is hosting (currently) on AWS. For files served directly from S3, we simply charge the AWS transfer fee.", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "half.heading", |
| + "text": "No questions asked discounts", |
| + "original": "Half price for anyone", |
| + "edited": true, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "half.p1", |
| + "text": "We don't have a sales team and we don't want one. We'd rather you paid the full price, but in case you can't (or won't), use discount \"HALF\" to get an instant 50% reduction. No negotiation required.", |
| + "original": "Use the code HALF at checkout for 50% off the subscription, forever. No form, no proof. The inclusions are not halved.", |
| + "edited": true, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "promise.heading", |
| + "text": "The promise", |
| + "original": "What we promise", |
| + "edited": true, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "promise.p1", |
| + "text": "Prices are fixed for a year. We’ll review pricing bands annually. Any changes apply to new subscriptions. You keep your deal until you decide to change.", |
| + "original": "Your price is fixed for the year you bought. Rates and country bands are reviewed once a year, in public. Any tightening applies to new subscriptions only. Existing accounts keep the deal they signed up for until they choose to change it.", |
| + "edited": true, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "pay.heading", |
| + "text": "Taxes etc.", |
| + "original": "Tax, paying and cancelling", |
| + "edited": true, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "pay.p1", |
| + "text": "Prices exclude value-added tax (VAT). VAT is added at checkout according to your billing country and shown before you pay. The merchant is Team Tito Limited, Dublin, Ireland.", |
| + "original": "Prices exclude value-added tax (VAT). VAT is added at checkout according to your billing country and shown before you pay. The merchant is Team Tito Limited, Dublin, Ireland.", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "pay.p2", |
| + "text": "Stripe handles payments, and you can manage your subscription via the portal they provide.", |
| + "original": "You pay by card through Stripe. From your account's Billing page you can change your card, download invoices and cancel. Cancelling keeps the plan running until the end of the year you paid for.", |
| + "edited": true, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "pay.p3", |
| + "text": "Members on Community or Business accounts are charged immediately, prorated to the day that year. If a member is removed, it comes off the bill at renewal time.", |
| + "original": "Adding a member to Community or Business charges the rest of the current year for that member straight away, prorated to the day. A member who leaves comes off the bill at renewal.", |
| + "edited": true, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "pay.p4", |
| + "text": "Billing details stored securely by Stripe, not by us.", |
| + "original": "We keep no card details, billing name or billing address on our side.", |
| + "edited": true, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "start.heading", |
| + "text": "Ready?", |
| + "original": "Start", |
| + "edited": true, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "start.p1", |
| + "text": "Create an account in seconds, deploy your first CARLOS app in minutes.", |
| + "original": "Create an account at console.carloku.com. You sign in with a passkey through Keymail, so there is no password. Every new account starts on Free, and no card is needed. When you want more, open your account's Billing page, choose a plan and pay.", |
| + "edited": true, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "start.cta.console", |
| + "text": "Create an account", |
| + "original": "Create an account", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + }, |
| + { |
| + "id": "start.cta.guide", |
| + "text": "Read the setup guide", |
| + "original": "Read the setup guide", |
| + "edited": false, |
| + "rerolled": false, |
| + "rerolls": [], |
| + "done": false |
| + } |
| + ] |
| +} |
diff --git a/copy-review/pricing/strings.json b/copy-review/pricing/strings.json| index c4ece3c..e722b3d 100644 |
| --- a/copy-review/pricing/strings.json |
| +++ b/copy-review/pricing/strings.json |
| @@ -3,14 +3,14 @@ |
| "id": "meta.title", |
| "section": "Page", |
| "label": "Browser tab title", |
| - "text": "Pricing — Carloku", |
| + "text": "Carloku Pricing", |
| "context": "The browser tab title and the title shown when the page is shared. Follows the pattern of the other pages: Get started — Carloku." |
| }, |
| { |
| "id": "meta.description", |
| "section": "Page", |
| "label": "Search description", |
| - "text": "Four plans, priced per year in euro. Start on Free. Personal, Community and Business add room and continuous replication. Usage beyond the inclusions is billed monthly at published rates.", |
| + "text": "Carloku is priced for convenience and fairness. Four plans covering hobbyists to pro teams.", |
| "context": "The description search engines and link previews show under the page title. Not visible on the page itself." |
| }, |
| { |
| @@ -31,14 +31,14 @@ |
| "id": "hero.lede", |
| "section": "Opening", |
| "label": "Lede", |
| - "text": "Four plans, priced per year in euro before VAT. Start on Free. When you want more room, pick a paid plan from your account's Billing page.", |
| + "text": "Carloku is priced for convenience and fairness. Four plans covering hobbyists to pro teams.", |
| "context": "The two or three sentences under the heading. They say what the page holds and how buying works." |
| }, |
| { |
| "id": "hero.new", |
| "section": "Opening", |
| "label": "New notice", |
| - "text": "Paid plans and usage billing are new on Carloku.", |
| + "text": "Choose the plan that works best for you. CARLOS apps scale to zero when they're not in use. You only pay for the time that they're awake.", |
| "context": "A quiet one-line note under the lede. It says once that this is new, without apology." |
| }, |
| { |
| @@ -66,7 +66,7 @@ |
| "id": "plan.free.for", |
| "section": "The plans", |
| "label": "Free, who it is for", |
| - "text": "For trying Carloku, or for a small app you never want to pay for.", |
| + "text": "Free to try, great for tiny apps.", |
| "context": "One sentence under the price on the Free card." |
| }, |
| { |
| @@ -115,14 +115,14 @@ |
| "id": "plan.free.i7", |
| "section": "The plans", |
| "label": "Free inclusion 7", |
| - "text": "Your data is saved when an app sleeps. There is no continuous replication and no restore to an earlier moment.", |
| + "text": "Data stored on sleep. No data replication, no data restore.", |
| "context": "The durability line on the Free card. It has to be honest about what Free does not get without sounding reckless." |
| }, |
| { |
| "id": "plan.free.i8", |
| "section": "The plans", |
| "label": "Free inclusion 8", |
| - "text": "An app nobody visits for 90 days is archived: its address stops answering and its data is kept, compressed, and restored on request. You get an email at 60 and 80 days. Nothing is ever deleted.", |
| + "text": "Apps not visited after 90 days are archived with notice. These apps can be restored later by request.", |
| "context": "The dormancy line on the Free card." |
| }, |
| { |
| @@ -143,14 +143,14 @@ |
| "id": "plan.personal.price", |
| "section": "The plans", |
| "label": "Personal price", |
| - "text": "€24 a year", |
| + "text": "€24/y", |
| "context": "The price line on the Personal card." |
| }, |
| { |
| "id": "plan.personal.for", |
| "section": "The plans", |
| "label": "Personal, who it is for", |
| - "text": "For one person running a few apps.", |
| + "text": "For individuals running a few apps.", |
| "context": "One line in the plan card's list of what the plan includes." |
| }, |
| { |
| @@ -199,43 +199,50 @@ |
| "id": "plan.community.price", |
| "section": "The plans", |
| "label": "Community price", |
| - "text": "€5 per member a year", |
| + "text": "€5/y per member", |
| "context": "The price line on the Community card." |
| }, |
| { |
| "id": "plan.community.min", |
| "section": "The plans", |
| "label": "Community minimum", |
| - "text": "Minimum 10 members, so €50 a year to start.", |
| + "text": "Minimum 10 members.", |
| "context": "Small print under the Community price." |
| }, |
| { |
| "id": "plan.community.for", |
| "section": "The plans", |
| "label": "Community, who it is for", |
| - "text": "For a group sharing one account: a club, a class, an open-source project.", |
| + "text": "For a groups, clubs, classes, an open-source projects.", |
| "context": "One line in the plan card's list of what the plan includes." |
| }, |
| + { |
| + "id": "plan.team.allowances", |
| + "section": "Team plans", |
| + "label": "Allowance heading", |
| + "text": "Each member adds", |
| + "context": "Appears once above the allowances in each Community and Business plan card." |
| + }, |
| { |
| "id": "plan.community.i1", |
| "section": "The plans", |
| "label": "Community inclusion 1", |
| - "text": "Each member adds 17 awake hours a month", |
| - "context": "One line in the plan card's list of what the plan includes." |
| + "text": "17 awake hours a month", |
| + "context": "An allowance in the team plan card, under the shared heading “Each member adds”." |
| }, |
| { |
| "id": "plan.community.i2", |
| "section": "The plans", |
| "label": "Community inclusion 2", |
| - "text": "Each member adds 0.5 GB of storage", |
| - "context": "One line in the plan card's list of what the plan includes." |
| + "text": "0.5 GB of storage", |
| + "context": "An allowance in the team plan card, under the shared heading “Each member adds”." |
| }, |
| { |
| "id": "plan.community.i3", |
| "section": "The plans", |
| "label": "Community inclusion 3", |
| - "text": "Each member adds 0.4 GB of transfer a month", |
| - "context": "One line in the plan card's list of what the plan includes." |
| + "text": "0.4 GB of transfer a month", |
| + "context": "An allowance in the team plan card, under the shared heading “Each member adds”." |
| }, |
| { |
| "id": "plan.community.cta", |
| @@ -255,14 +262,14 @@ |
| "id": "plan.business.price", |
| "section": "The plans", |
| "label": "Business price", |
| - "text": "€50 per member a year", |
| + "text": "€50/y per member", |
| "context": "The price line on the Business card." |
| }, |
| { |
| "id": "plan.business.min", |
| "section": "The plans", |
| "label": "Business minimum", |
| - "text": "Minimum 10 members, so €500 a year to start.", |
| + "text": "Minimum 10 members.", |
| "context": "Small print under the Business price." |
| }, |
| { |
| @@ -276,29 +283,29 @@ |
| "id": "plan.business.i1", |
| "section": "The plans", |
| "label": "Business inclusion 1", |
| - "text": "Each member adds 170 awake hours a month", |
| - "context": "One line in the plan card's list of what the plan includes." |
| + "text": "170 extra awake hours/m", |
| + "context": "An allowance in the team plan card, under the shared heading “Each member adds”." |
| }, |
| { |
| "id": "plan.business.i2", |
| "section": "The plans", |
| "label": "Business inclusion 2", |
| - "text": "Each member adds 5 GB of storage", |
| - "context": "One line in the plan card's list of what the plan includes." |
| + "text": "5 GB/m extra of storage", |
| + "context": "An allowance in the team plan card, under the shared heading “Each member adds”." |
| }, |
| { |
| "id": "plan.business.i3", |
| "section": "The plans", |
| "label": "Business inclusion 3", |
| - "text": "Each member adds 3 GB of transfer a month", |
| - "context": "One line in the plan card's list of what the plan includes." |
| + "text": "3 GB of transfer a month", |
| + "context": "An allowance in the team plan card, under the shared heading “Each member adds”." |
| }, |
| { |
| "id": "plan.business.i4", |
| "section": "The plans", |
| "label": "Business inclusion 4", |
| - "text": "Each member adds 30,000 replication writes a month", |
| - "context": "One line in the plan card's list of what the plan includes." |
| + "text": "30,000 replication writes a month", |
| + "context": "An allowance in the team plan card, under the shared heading “Each member adds”." |
| }, |
| { |
| "id": "plan.business.i5", |
| @@ -325,21 +332,21 @@ |
| "id": "plans.note", |
| "section": "The plans", |
| "label": "Under the cards", |
| - "text": "Paid plans are never archived, however quiet the app.", |
| + "text": "Paid plans are never archived. CARLOS apps typically wake in under 500ms.", |
| "context": "One line under the four cards." |
| }, |
| { |
| "id": "member.heading", |
| "section": "What a member is", |
| "label": "Section heading", |
| - "text": "What a member is", |
| + "text": "Members", |
| "context": "One line in the plan card's list of what the plan includes." |
| }, |
| { |
| "id": "member.p1", |
| "section": "What a member is", |
| "label": "Paragraph 1", |
| - "text": "A member is a person with access to the account in the console. People who use the apps you deploy are never counted and never priced.", |
| + "text": "Members are team members who can access and ship your sites and apps.", |
| "context": "The sentence that stops a reader pricing their app's users as members." |
| }, |
| { |
| @@ -353,7 +360,7 @@ |
| "id": "bands.heading", |
| "section": "Prices where you live", |
| "label": "Section heading", |
| - "text": "Prices where you live", |
| + "text": "Indexed pricing", |
| "context": "One line in the plan card's list of what the plan includes." |
| }, |
| { |
| @@ -437,14 +444,14 @@ |
| "id": "over.heading", |
| "section": "Going past what's included", |
| "label": "Section heading", |
| - "text": "Going past what's included", |
| + "text": "Overages", |
| "context": "One line in the plan card's list of what the plan includes." |
| }, |
| { |
| "id": "over.p1", |
| "section": "Going past what's included", |
| "label": "Paragraph 1", |
| - "text": "Usage beyond the pooled inclusions is billed monthly, from our own usage statements.", |
| + "text": "Extra usage is billed monthly, from our own usage statements.", |
| "context": "One line in the plan card's list of what the plan includes." |
| }, |
| { |
| @@ -507,14 +514,14 @@ |
| "id": "over.p2", |
| "section": "Going past what's included", |
| "label": "Paragraph 2", |
| - "text": "A month under €1 is not billed. We bill only usage we measured completely. If our measurement of a meter is incomplete for a month, that meter is not billed for that month.", |
| + "text": "Minimum €1 bill based on our own measures. If usage doesn't meet our metered amount, it's not billed for that month.", |
| "context": "The completeness promise. It is what lets us hold billing when evidence is missing instead of guessing." |
| }, |
| { |
| "id": "over.p3", |
| "section": "Going past what's included", |
| "label": "Paragraph 3", |
| - "text": "Apps can serve files straight from their storage bucket. For that transfer we count only what Amazon Web Services (AWS) charges us for. If that count is wrong, it is wrong in your favour.", |
| + "text": "Carloku is hosting (currently) on AWS. For files served directly from S3, we simply charge the AWS transfer fee.", |
| "context": "The object-storage transfer contract Paul settled: only transfer AWS charges Team Tito for." |
| }, |
| { |
| @@ -608,4 +615,4 @@ |
| "text": "Read the setup guide", |
| "context": "The quiet button beside it. Goes to /get-started/." |
| } |
| -] |
| \ No newline at end of file |
| +] |
diff --git a/package.json b/package.json| index 059597f..36ef92d 100644 |
| --- a/package.json |
| +++ b/package.json |
| @@ -5,7 +5,7 @@ |
| "scripts": { |
| "build": "eleventy", |
| "serve": "eleventy --serve", |
| - "check": "eleventy && node hack/check-docs.mjs" |
| + "check": "eleventy && node hack/check-docs.mjs && node --test test/*.test.mjs" |
| }, |
| "devDependencies": { |
| "@11ty/eleventy": "^3.0.0", |
diff --git a/src/site.js b/src/site.js| index 4b4c3e5..01d230d 100644 |
| --- a/src/site.js |
| +++ b/src/site.js |
| @@ -1,6 +1,4 @@ |
| -// 🤖 Carloku — hand-written, dependency-free. Two jobs only: |
| -// 1. the hero terminal types its session once (skipped under reduced motion), |
| -// 2. the sticky header grows its hairline once the page scrolls. |
| +// 🤖 Carloku |
| (function () { |
| "use strict"; |
| @@ -13,6 +11,54 @@ |
| onScroll(); |
| } |
| + var currencySelect = document.querySelector("[data-pricing-currency]"); |
| + if (currencySelect) { |
| + var supported = ["USD", "EUR", "GBP"]; |
| + var eu = "AT BE BG HR CY CZ DK EE FI FR DE GR HU IE IT LV LT LU MT NL PL PT RO SK SI ES SE".split(" "); |
| + var countrySelect = document.querySelector("[data-pricing-country]"); |
| + var explicit = false; |
| + function countryCurrency(country) { |
| + return country === "GB" ? "GBP" : eu.indexOf(country) !== -1 ? "EUR" : "USD"; |
| + } |
| + function renderCurrency(currency) { |
| + if (supported.indexOf(currency) === -1) return; |
| + currencySelect.value = currency; |
| + document.querySelectorAll("[data-currency-values]").forEach(function (el) { |
| + try { |
| + var values = JSON.parse(el.getAttribute("data-currency-values")); |
| + if (typeof values[currency] === "string") el.textContent = values[currency]; |
| + } catch (_) {} |
| + }); |
| + document.querySelectorAll("[data-pricing-signup]").forEach(function (el) { |
| + var url = new URL(el.href, window.location.href); |
| + url.searchParams.set("currency", currency.toLowerCase()); |
| + el.href = url.href; |
| + }); |
| + } |
| + var initial = "USD"; |
| + try { |
| + var requested = new URL(window.location.href).searchParams.get("currency"); |
| + requested = requested && requested.toUpperCase(); |
| + if (supported.indexOf(requested) !== -1) { |
| + initial = requested; |
| + explicit = true; |
| + } else if (countrySelect && countrySelect.value) { |
| + initial = countryCurrency(countrySelect.value); |
| + } else { |
| + var region = new Intl.Locale(navigator.language).region; |
| + initial = countryCurrency(region); |
| + } |
| + } catch (_) {} |
| + renderCurrency(initial); |
| + currencySelect.addEventListener("change", function () { |
| + explicit = true; |
| + renderCurrency(currencySelect.value); |
| + }); |
| + if (countrySelect) countrySelect.addEventListener("change", function () { |
| + if (!explicit) renderCurrency(countryCurrency(countrySelect.value)); |
| + }); |
| + } |
| + |
| var body = document.querySelector(".term-body"); |
| if (!body) return; |
| if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return; |
diff --git a/test/pricing-currency.test.mjs b/test/pricing-currency.test.mjs| new file mode 100644 |
| index 0000000..92b7af9 |
| --- /dev/null |
| +++ b/test/pricing-currency.test.mjs |
| @@ -0,0 +1,47 @@ |
| +import { readFileSync } from 'node:fs'; |
| +import { runInNewContext } from 'node:vm'; |
| +import assert from 'node:assert/strict'; |
| +import { test } from 'node:test'; |
| +const source = readFileSync(new URL('../src/site.js', import.meta.url), 'utf8'); |
| +function page(language, query = '', country = '') { |
| + const listeners = {}; |
| + const selector = { value: 'USD', addEventListener: (name, fn) => listeners.currency = fn }; |
| + const countrySelector = { value: country, addEventListener: (name, fn) => listeners.country = fn }; |
| + const amount = { textContent: '$28', getAttribute: () => JSON.stringify({ USD: '$28', EUR: '€24', GBP: '£22' }) }; |
| + const signup = { href: 'https://console.carloku.com/?plan=personal' }; |
| + runInNewContext(source, { |
| + document: { |
| + querySelector: key => key === '[data-pricing-currency]' ? selector : key === '[data-pricing-country]' ? countrySelector : null, |
| + querySelectorAll: key => key === '[data-currency-values]' ? [amount] : key === '[data-pricing-signup]' ? [signup] : [] |
| + }, |
| + window: { location: { href: 'https://carloku.com/pricing/' + query } }, |
| + navigator: { language }, Intl, URL |
| + }); |
| + return { selector, countrySelector, amount, signup, listeners }; |
| +} |
| +test('currency defaults from explicit locale region; language alone is not a country', () => { |
| + for (const [locale, expected] of [['en-US','USD'],['en-GB','GBP'],['en-IE','EUR'],['pl-PL','EUR'],['fr-CA','USD'],['en','USD'],['bad_locale','USD']]) { |
| + assert.equal(page(locale).selector.value, expected, locale); |
| + } |
| +}); |
| +test('explicit choice overrides region and persists when billing country changes', () => { |
| + const p = page('en-IE', '?currency=gbp', 'IE'); |
| + assert.equal(p.amount.textContent, '£22'); |
| + assert.equal(new URL(p.signup.href).searchParams.get('plan'), 'personal'); |
| + assert.equal(new URL(p.signup.href).searchParams.get('currency'), 'gbp'); |
| + p.countrySelector.value = 'US'; p.listeners.country(); |
| + assert.equal(p.selector.value, 'GBP'); |
| + p.selector.value = 'USD'; p.listeners.currency(); |
| + assert.equal(p.amount.textContent, '$28'); |
| +}); |
| +test('country sets default until a currency is explicitly chosen', () => { |
| + const p = page('en-US', '', 'IE'); |
| + assert.equal(p.selector.value, 'EUR'); |
| + p.countrySelector.value = 'GB'; p.listeners.country(); |
| + assert.equal(p.selector.value, 'GBP'); |
| +}); |
| +test('unsupported URL currency is ignored and never forwarded', () => { |
| + const p = page('en-US', '?currency=xyz'); |
| + assert.equal(p.selector.value, 'USD'); |
| + assert.equal(new URL(p.signup.href).searchParams.get('currency'), 'usd'); |
| +}); |