From ab76c3a4417a866c1489d952a8a9f7b77d042c76 Mon Sep 17 00:00:00 2001 From: Maurice Date: Thu, 5 Nov 2026 23:05:56 +0100 Subject: [PATCH] feat(native): equal plan cards, mark absent features with a dash, one-line unlimited label --- client/native-shell/main.ts | 9 +++++++-- client/native-shell/shell.css | 4 ++++ shared/src/i18n/de/native.ts | 2 +- shared/src/i18n/en/native.ts | 5 ++--- 4 files changed, 14 insertions(+), 6 deletions(-) diff --git a/client/native-shell/main.ts b/client/native-shell/main.ts index 48b3c9f16..a6503555e 100644 --- a/client/native-shell/main.ts +++ b/client/native-shell/main.ts @@ -374,6 +374,9 @@ function planCard(plan: CloudPlan, interval: 'month' | 'year', featured: boolean const price = interval === 'year' ? plan.yearlyCents : plan.monthlyCents; const per = interval === 'year' ? t('native.plans.perYear') : t('native.plans.perMonth'); const feat = (text: string) => el('span', {}, icon(I.check, 16), text); + // An absent feature (e.g. a plan with no AI documents): a muted dash, not a + // green tick, so "none" never reads as a perk. + const featOff = (text: string) => el('span', { className: 'off' }, icon('M5 12h14', 16), text); const sub = el('button', { type: 'button', className: 'btn' }, t('native.plans.subscribe')); sub.addEventListener('click', () => subscribe(plan, interval)); return el( @@ -390,7 +393,9 @@ function planCard(plan: CloudPlan, interval: 'month' | 'year', featured: boolean { className: 'plan-feats' }, feat(t('native.plans.unlimited')), feat(t('native.plans.storage', { n: String(plan.storageGb) })), - feat(plan.aiDocuments > 0 ? t('native.plans.aiDocs', { n: String(plan.aiDocuments) }) : t('native.plans.noAi')) + plan.aiDocuments > 0 + ? feat(t('native.plans.aiDocs', { n: String(plan.aiDocuments) })) + : featOff(t('native.plans.noAi')) ), sub ); @@ -414,7 +419,7 @@ function showPlans(opts: { back?: boolean } = {}): void { const list = plans; if (!list) return; content.className = 'plans'; - content.replaceChildren(...list.map((p, i) => planCard(p, interval, i === list.length - 1))); + content.replaceChildren(...list.map((p) => planCard(p, interval, false))); }; const choose = (next: 'month' | 'year') => { if (interval === next) return; diff --git a/client/native-shell/shell.css b/client/native-shell/shell.css index b4f76b574..7894df6b5 100644 --- a/client/native-shell/shell.css +++ b/client/native-shell/shell.css @@ -849,6 +849,10 @@ a.linkbtn { flex: none; color: var(--ok); } +.plan-feats .off, +.plan-feats .off svg { + color: var(--faint); +} .plan .btn { width: 100%; min-height: 48px; diff --git a/shared/src/i18n/de/native.ts b/shared/src/i18n/de/native.ts index 0cf6bc9aa..190d5d802 100644 --- a/shared/src/i18n/de/native.ts +++ b/shared/src/i18n/de/native.ts @@ -51,7 +51,7 @@ const native: TranslationStrings = { 'native.plans.aiDocs': '{n} KI-Dokumente im Monat', 'native.plans.noAi': 'Keine KI-Dokumente', 'native.plans.storage': '{n} GB Speicher', - 'native.plans.unlimited': 'Unbegrenzt Orte, Reisen und Personen', + 'native.plans.unlimited': 'Unbegrenzt Orte, Reisen & Personen', 'native.plans.subscribe': 'Abonnieren', 'native.plans.loadError': 'Die Tarife konnten nicht geladen werden. Prüfe deine Verbindung und versuch es erneut.', 'native.plans.retry': 'Erneut versuchen', diff --git a/shared/src/i18n/en/native.ts b/shared/src/i18n/en/native.ts index 5dbe4d7c5..79792c07b 100644 --- a/shared/src/i18n/en/native.ts +++ b/shared/src/i18n/en/native.ts @@ -25,8 +25,7 @@ const native: TranslationStrings = { 'native.settings.server': 'Server', 'native.settings.serverHint': 'This app is connected to {server}.', 'native.welcome.headline': 'Your Trip.\nYour Plan.', - 'native.welcome.sub': - 'Plan together in real time: tours, budgets, journals, photo book — with AI. Web, phone, offline.', + 'native.welcome.sub': 'Plan together in real time: tours, budgets, journals, photo book — with AI.', 'native.welcome.connect': 'Connect your TREK', 'native.welcome.connectSub': 'Open the address you already use', 'native.welcome.getCloud': 'Get TREK Cloud', @@ -51,7 +50,7 @@ const native: TranslationStrings = { 'native.plans.aiDocs': '{n} AI documents a month', 'native.plans.noAi': 'No AI documents', 'native.plans.storage': '{n} GB storage', - 'native.plans.unlimited': 'Unlimited places, trips and people', + 'native.plans.unlimited': 'Unlimited places, trips & people', 'native.plans.subscribe': 'Subscribe', 'native.plans.loadError': 'Could not load the plans. Check your connection and try again.', 'native.plans.retry': 'Try again',