From 48756e694a5ce59a1a56f0faa60645c1e5aed8cf Mon Sep 17 00:00:00 2001 From: Majid Achhoud Date: Mon, 7 Sep 2026 11:46:46 +0200 Subject: [PATCH 1/8] Add a plan comparison table to the pricing page --- assets/css/custom.css | 17 +++-- assets/css/theme.css | 1 + data/pricing_comparison.yaml | 90 +++++++++++++++++++++++++ i18n/de.yaml | 65 ++++++++++++++++++ i18n/en.yaml | 65 ++++++++++++++++++ layouts/partials/nav.html | 3 +- layouts/pricing/single.html | 124 ++++++++++++++++++++++++++++++++++- 7 files changed, 358 insertions(+), 7 deletions(-) create mode 100644 data/pricing_comparison.yaml diff --git a/assets/css/custom.css b/assets/css/custom.css index d011359f7..0863a4529 100644 --- a/assets/css/custom.css +++ b/assets/css/custom.css @@ -259,6 +259,12 @@ ul.list-primary li::marker { @apply rounded-sm shadow-sm bg-white; } +/* The container's overflow-x-clip leaves the y axis visible, so it cannot clip + the opaque corner cells to its own radius — they round themselves. */ +.compare-th { + @apply sticky top-[var(--nav-height,48px)] z-10 align-bottom border-b border-gray-200 first:rounded-tl-sm last:rounded-tr-sm; +} + .input-box { @apply rounded-sm border border-gray-300 focus:ring-0 focus:border-secondary px-3 py-2; } @@ -317,9 +323,9 @@ textarea:read-only { left: 50%; } -/* offsetting anchor targets to adjust for fixed navbar; the offset grows when the - nav banner strip (.nav-promo-strip) is rendered, detected via :has() below; - tooltips carry an id only for aria-describedby, not as scroll anchors */ +/* offsetting anchor targets to adjust for fixed navbar; the offset grows with the + nav banner strip, see the :has() rule below; tooltips carry an id only for + aria-describedby, not as scroll anchors */ [id]:not([role="tooltip"])::before { content: ''; display: block; @@ -328,6 +334,9 @@ textarea:read-only { visibility: hidden; } -body:has(.nav-promo-strip) { +/* The webinar strip stays in the DOM when Alpine hides it, so the marker class — + not its presence — decides whether the nav is one strip taller. */ +body:has(.nav-promo-strip:not(.nav-promo-strip-collapsed)) { --anchor-offset: 138px; + --nav-height: 72px; } diff --git a/assets/css/theme.css b/assets/css/theme.css index e16b0104d..5b3ba3a5c 100644 --- a/assets/css/theme.css +++ b/assets/css/theme.css @@ -114,6 +114,7 @@ --color-secondary: #008a7b; --color-tertiary: #005e71; --color-hub: #354960; + --color-hub-l2: #ebedef; --color-dark: #1e2b33; --font-*: initial; diff --git a/data/pricing_comparison.yaml b/data/pricing_comparison.yaml new file mode 100644 index 000000000..f98bd3abc --- /dev/null +++ b/data/pricing_comparison.yaml @@ -0,0 +1,90 @@ +# Shared across languages: a plan capability is a product fact, not a +# translation. Booleans render as a check or a dash, numbers render as they are, +# and every other cell is an i18n key. +- SectionKey: "pricing_compare_section_basics" + Rows: + - LabelKey: "pricing_compare_seats" + Cryptomator: 1 + Community: 5 + Standard: "pricing_compare_unlimited" + Enterprise: "pricing_compare_unlimited" + - LabelKey: "pricing_compare_unlimited_vaults" + Cryptomator: true + Community: true + Standard: true + Enterprise: true + - LabelKey: "pricing_compare_any_cloud" + TooltipKey: "pricing_individual_benefit_2_tooltip" + Cryptomator: true + Community: true + Standard: true + Enterprise: true + - LabelKey: "pricing_compare_desktop_access" + Cryptomator: true + Community: true + Standard: true + Enterprise: true +- SectionKey: "pricing_compare_section_collaboration" + Rows: + - LabelKey: "pricing_compare_user_management" + Cryptomator: false + Community: true + Standard: true + Enterprise: true + - LabelKey: "pricing_compare_key_management" + Cryptomator: false + Community: true + Standard: true + Enterprise: true + - LabelKey: "pricing_compare_mobile_access" + Cryptomator: "pricing_compare_one_time_purchase" + Community: "pricing_compare_one_time_purchase" + Standard: true + Enterprise: true +- SectionKey: "pricing_compare_section_hosting" + Rows: + - LabelKey: "pricing_compare_self_hosted" + Cryptomator: false + Community: true + Standard: true + Enterprise: true + - LabelKey: "pricing_compare_managed_hosting" + TooltipKey: "pricing_compare_managed_hosting_tooltip" + Cryptomator: false + Community: false + Standard: true + Enterprise: true +- SectionKey: "pricing_compare_section_security" + Rows: + - LabelKey: "pricing_compare_audit_logs" + Cryptomator: false + Community: false + Standard: true + Enterprise: true + - LabelKey: "pricing_compare_emergency_access" + Cryptomator: false + Community: false + Standard: false + Enterprise: "pricing_compare_coming_soon" +- SectionKey: "pricing_compare_section_support" + Rows: + - LabelKey: "pricing_compare_community_support" + Cryptomator: true + Community: true + Standard: true + Enterprise: true + - LabelKey: "pricing_compare_email_support" + Cryptomator: false + Community: false + Standard: true + Enterprise: "pricing_compare_priority" + - LabelKey: "pricing_compare_expert_training" + Cryptomator: false + Community: false + Standard: false + Enterprise: true + - LabelKey: "pricing_compare_account_manager" + Cryptomator: false + Community: false + Standard: false + Enterprise: true diff --git a/i18n/de.yaml b/i18n/de.yaml index 433a45c32..4e8b13553 100644 --- a/i18n/de.yaml +++ b/i18n/de.yaml @@ -1124,6 +1124,8 @@ translation: "Community" - id: pricing_hub_community_subtitle translation: "Kostenlos und Open Source für kleine Teams. Auf eigener Infrastruktur deployen mit voller Kontrolle über die Daten." +- id: pricing_hub_community_price_description + translation: "Self-Hosted" - id: pricing_hub_community_benefit_1 translation: "Inklusive 5 Sitze" - id: pricing_hub_community_benefit_2 @@ -1171,6 +1173,69 @@ - id: pricing_hub_enterprise_benefit_5 translation: "Vorrangiger E-Mail-Support" +- id: pricing_compare_title + translation: "Alle Funktionen im Vergleich" +- id: pricing_compare_subtitle + translation: "Jeder Plan enthält alles aus den vorherigen Plänen." +- id: more_information + translation: "Weitere Informationen" +- id: pricing_compare_features + translation: "Funktionen" +- id: pricing_compare_included + translation: "Enthalten" +- id: pricing_compare_not_included + translation: "Nicht enthalten" +- id: pricing_compare_managed_hosting_tooltip + translation: "Gewartet und aktualisiert von unserem Team in einem ISO/IEC 27001:2022-zertifizierten Rechenzentrum in der EU." +- id: pricing_compare_section_basics + translation: "Grundlagen" +- id: pricing_compare_section_collaboration + translation: "Teamarbeit" +- id: pricing_compare_section_hosting + translation: "Hosting" +- id: pricing_compare_section_security + translation: "Sicherheit & Compliance" +- id: pricing_compare_section_support + translation: "Support" +- id: pricing_compare_seats + translation: "Sitze" +- id: pricing_compare_unlimited_vaults + translation: "Unbegrenzte Tresore" +- id: pricing_compare_any_cloud + translation: "Kompatibel mit jedem Cloud-Speicher" +- id: pricing_compare_desktop_access + translation: "Voller Zugriff über Desktop-App" +- id: pricing_compare_user_management + translation: "Benutzer- und Gruppenverwaltung" +- id: pricing_compare_key_management + translation: "Zero-Knowledge-Schlüsselverwaltung" +- id: pricing_compare_mobile_access + translation: "Voller Zugriff über Mobile-Apps für alle Nutzer" +- id: pricing_compare_self_hosted + translation: "Self-Hosted-Deployment" +- id: pricing_compare_managed_hosting + translation: "Managed Hosting verfügbar" +- id: pricing_compare_audit_logs + translation: "Audit-Logs" +- id: pricing_compare_emergency_access + translation: "Notfallzugriff" +- id: pricing_compare_community_support + translation: "Community-Support" +- id: pricing_compare_email_support + translation: "E-Mail-Support" +- id: pricing_compare_expert_training + translation: "Beratung und Unterstützung durch Experten" +- id: pricing_compare_account_manager + translation: "Dedizierter Kundenbetreuer" +- id: pricing_compare_unlimited + translation: "Unbegrenzt" +- id: pricing_compare_one_time_purchase + translation: "Einmalkauf" +- id: pricing_compare_coming_soon + translation: "Bald verfügbar" +- id: pricing_compare_priority + translation: "Vorrangig" + - id: pricing_support_us_title translation: "Möchtest du unser Open-Source-Projekt unterstützen?" - id: pricing_support_us_description diff --git a/i18n/en.yaml b/i18n/en.yaml index f7f7b33a5..09224cecb 100644 --- a/i18n/en.yaml +++ b/i18n/en.yaml @@ -1125,6 +1125,8 @@ translation: "Community" - id: pricing_hub_community_subtitle translation: "Free and open source for small teams. Deploy on your own infrastructure with full control over your data." +- id: pricing_hub_community_price_description + translation: "self-hosted" - id: pricing_hub_community_benefit_1 translation: "Includes 5 seats" - id: pricing_hub_community_benefit_2 @@ -1172,6 +1174,69 @@ - id: pricing_hub_enterprise_benefit_5 translation: "Priority email support" +- id: pricing_compare_title + translation: "Compare All Features" +- id: pricing_compare_subtitle + translation: "Every plan includes everything from the plans before it." +- id: more_information + translation: "More information" +- id: pricing_compare_features + translation: "Features" +- id: pricing_compare_included + translation: "Included" +- id: pricing_compare_not_included + translation: "Not included" +- id: pricing_compare_managed_hosting_tooltip + translation: "Maintained and updated by our team in an ISO/IEC 27001:2022 certified data center in the EU." +- id: pricing_compare_section_basics + translation: "Basics" +- id: pricing_compare_section_collaboration + translation: "Team Collaboration" +- id: pricing_compare_section_hosting + translation: "Hosting" +- id: pricing_compare_section_security + translation: "Security & Compliance" +- id: pricing_compare_section_support + translation: "Support" +- id: pricing_compare_seats + translation: "Seats" +- id: pricing_compare_unlimited_vaults + translation: "Unlimited vaults" +- id: pricing_compare_any_cloud + translation: "Works with any cloud storage" +- id: pricing_compare_desktop_access + translation: "Full access via Desktop app" +- id: pricing_compare_user_management + translation: "User and group management" +- id: pricing_compare_key_management + translation: "Zero-knowledge key management" +- id: pricing_compare_mobile_access + translation: "Full access via Mobile apps for all users" +- id: pricing_compare_self_hosted + translation: "Self-hosted deployment" +- id: pricing_compare_managed_hosting + translation: "Managed hosting available" +- id: pricing_compare_audit_logs + translation: "Audit logs" +- id: pricing_compare_emergency_access + translation: "Emergency Access" +- id: pricing_compare_community_support + translation: "Community support" +- id: pricing_compare_email_support + translation: "Email support" +- id: pricing_compare_expert_training + translation: "Expert training and assistance" +- id: pricing_compare_account_manager + translation: "Dedicated account manager" +- id: pricing_compare_unlimited + translation: "Unlimited" +- id: pricing_compare_one_time_purchase + translation: "One-time purchase" +- id: pricing_compare_coming_soon + translation: "Coming Soon" +- id: pricing_compare_priority + translation: "Priority" + - id: pricing_support_us_title translation: "Want to support our open-source project?" - id: pricing_support_us_description diff --git a/layouts/partials/nav.html b/layouts/partials/nav.html index 5aff3c9d4..b425605a5 100644 --- a/layouts/partials/nav.html +++ b/layouts/partials/nav.html @@ -11,7 +11,8 @@ {{- else if .Scratch.Get "webinarBannerActive" -}} {{- $webinarPath := printf "%s/webinar/" .Site.LanguagePrefix -}} - + {{/* Feature Comparison Section */}} +
+
+

{{ i18n "pricing_compare_title" . }}

+

{{ i18n "pricing_compare_subtitle" . }}

+
+
+ + {{- $cryptomatorBrand := dict "brand" "Cryptomator" "logo" "/img/logo.svg" "logoAspect" "aspect-1110/942" "brandColor" "text-primary" }} + {{- $hubBrand := dict "brand" "Cryptomator Hub" "logo" "/img/zero-knowledge-bot.svg" "logoAspect" "aspect-988/877" "brandColor" "text-hub" }} + {{- $plans := slice + (merge $cryptomatorBrand (dict "dataKey" "Cryptomator" "titleKey" "pricing_individual_title" "highlight" false)) + (merge $hubBrand (dict "dataKey" "Community" "titleKey" "pricing_hub_community_title" "highlight" false)) + (merge $hubBrand (dict "dataKey" "Standard" "titleKey" "pricing_hub_standard_title" "highlight" true)) + (merge $hubBrand (dict "dataKey" "Enterprise" "titleKey" "pricing_hub_enterprise_title" "highlight" false)) + }} + + + + {{- range $plans }} + + {{- end }} + + + + {{- range $sectionIndex, $section := hugo.Data.pricing_comparison }} + + + {{- /* empty cells exist solely to carry the highlighted column's tint across section rows */}} + {{- range $plans }} + + {{- end }} + + {{- range $rowIndex, $row := .Rows }} + + + {{- range $plans }} + {{- $plan := . }} + {{- $cell := index $row $plan.dataKey }} + + {{- end }} + + {{- end }} + {{- end }} + + {{- /* The tfoot below is hand-written per plan, so it silently desyncs if $plans changes. */}} + {{- $footerPlans := "Cryptomator:false,Community:false,Standard:true,Enterprise:false" }} + {{- $actualParts := slice }} + {{- range $plans }}{{ $actualParts = $actualParts | append (printf "%v:%v" .dataKey .highlight) }}{{ end }} + {{- $actualPlans := delimit $actualParts "," }} + {{- if ne $actualPlans $footerPlans }}{{ errorf "pricing_comparison: $plans is %q but the tfoot is hand-written for %q — update the tfoot cells to match" $actualPlans $footerPlans }}{{ end }} + + + + + + + + + +
+ {{ i18n "pricing_compare_features" . }} + + + {{ .brand }} + {{ i18n .titleKey $ }} +
{{ i18n .SectionKey $ }}
+ {{ i18n .LabelKey $ }} + {{- with .TooltipKey }} + {{- $tooltipId := printf "tooltip-compare-%d-%d" $sectionIndex $rowIndex }} +
+ + +
+ {{- end }} +
+ {{- if eq $cell true }} + + {{ i18n "pricing_compare_included" $ }} + {{- else if eq $cell false }} + + {{ i18n "pricing_compare_not_included" $ }} + {{- else if eq (printf "%T" $cell) "string" }} + {{ i18n $cell $ }} + {{- else if $cell }} + {{ $cell }} + {{- else }} + {{- errorf "pricing_comparison: row %q is missing a value for plan %q" $row.LabelKey $plan.dataKey }} + {{- end }} +
+ {{ i18n "pricing_free" . }} + {{ i18n "pricing_individual_price_description" . }} + + {{ i18n "pricing_individual_cta" . }} + + + {{ i18n "pricing_free" . }} + {{ i18n "pricing_hub_community_price_description" . }} + + {{ i18n "pricing_hub_self_hosted_cta" . }} + + + + {{ i18n "pricing_hub_standard_price_description" . }} + + {{ i18n "pricing_hub_managed_cta" . }} + + + {{ i18n "pricing_hub_self_hosted_cta" . }} + + + {{ i18n "pricing_hub_enterprise_price" . }} + {{ i18n "pricing_hub_enterprise_price_description" . }} + + {{ i18n "pricing_hub_enterprise_cta" . }} + +
+
+
+ {{/* Support Us Section */}} + {{- $data := index hugo.Data $.Site.Language.Lang }}

{{ i18n "pricing_support_us_title" . }}

{{ i18n "pricing_support_us_description" . }}

@@ -366,7 +487,6 @@

{{ i18n "pricing_support_us_title" . }}

{{ i18n "faq_title" . }}

- {{- $data := index hugo.Data $.Site.Language.Lang }}

From 839fa79d9afa41a2601b6d0a2cbf4fa5db31cf9e Mon Sep 17 00:00:00 2001 From: Tobias Hagemann Date: Thu, 17 Sep 2026 15:50:15 +0200 Subject: [PATCH 2/8] Fix pricing comparison labels, contrast, and Hub styling --- assets/css/theme.css | 1 - data/pricing_comparison.yaml | 2 +- i18n/de.yaml | 4 +--- i18n/en.yaml | 2 -- layouts/pricing/single.html | 13 +++++++------ 5 files changed, 9 insertions(+), 13 deletions(-) diff --git a/assets/css/theme.css b/assets/css/theme.css index 5b3ba3a5c..e16b0104d 100644 --- a/assets/css/theme.css +++ b/assets/css/theme.css @@ -114,7 +114,6 @@ --color-secondary: #008a7b; --color-tertiary: #005e71; --color-hub: #354960; - --color-hub-l2: #ebedef; --color-dark: #1e2b33; --font-*: initial; diff --git a/data/pricing_comparison.yaml b/data/pricing_comparison.yaml index f98bd3abc..23c6d23db 100644 --- a/data/pricing_comparison.yaml +++ b/data/pricing_comparison.yaml @@ -65,7 +65,7 @@ Cryptomator: false Community: false Standard: false - Enterprise: "pricing_compare_coming_soon" + Enterprise: "coming_soon" - SectionKey: "pricing_compare_section_support" Rows: - LabelKey: "pricing_compare_community_support" diff --git a/i18n/de.yaml b/i18n/de.yaml index 4e8b13553..2ace7a2c3 100644 --- a/i18n/de.yaml +++ b/i18n/de.yaml @@ -1224,15 +1224,13 @@ - id: pricing_compare_email_support translation: "E-Mail-Support" - id: pricing_compare_expert_training - translation: "Beratung und Unterstützung durch Experten" + translation: "Schulungen und Unterstützung durch Experten" - id: pricing_compare_account_manager translation: "Dedizierter Kundenbetreuer" - id: pricing_compare_unlimited translation: "Unbegrenzt" - id: pricing_compare_one_time_purchase translation: "Einmalkauf" -- id: pricing_compare_coming_soon - translation: "Bald verfügbar" - id: pricing_compare_priority translation: "Vorrangig" diff --git a/i18n/en.yaml b/i18n/en.yaml index 09224cecb..050a98b02 100644 --- a/i18n/en.yaml +++ b/i18n/en.yaml @@ -1232,8 +1232,6 @@ translation: "Unlimited" - id: pricing_compare_one_time_purchase translation: "One-time purchase" -- id: pricing_compare_coming_soon - translation: "Coming Soon" - id: pricing_compare_priority translation: "Priority" diff --git a/layouts/pricing/single.html b/layouts/pricing/single.html index 8c4e97fcc..0f0309acd 100644 --- a/layouts/pricing/single.html +++ b/layouts/pricing/single.html @@ -368,7 +368,7 @@

{{ i18n "pricing_compare_ {{ i18n "pricing_compare_features" . }} {{- range $plans }} - + {{ .brand }} {{ i18n .titleKey $ }} @@ -379,7 +379,7 @@

{{ i18n "pricing_compare_ {{- range $sectionIndex, $section := hugo.Data.pricing_comparison }} - {{ i18n .SectionKey $ }} + {{ i18n .SectionKey $ }} {{- /* empty cells exist solely to carry the highlighted column's tint across section rows */}} {{- range $plans }} @@ -431,21 +431,22 @@

{{ i18n "pricing_compare_ {{ i18n "pricing_free" . }} - {{ i18n "pricing_individual_price_description" . }} + {{ i18n "pricing_individual_price_description" . }} {{ i18n "pricing_individual_cta" . }} {{ i18n "pricing_free" . }} - {{ i18n "pricing_hub_community_price_description" . }} + {{ i18n "pricing_hub_community_price_description" . }} {{ i18n "pricing_hub_self_hosted_cta" . }} - {{ i18n "pricing_hub_standard_price_description" . }} + {{ i18n "pricing_vat_excluded" . }} + {{ i18n "pricing_hub_standard_price_description" . }} {{ i18n "pricing_hub_managed_cta" . }} @@ -455,7 +456,7 @@

{{ i18n "pricing_compare_ {{ i18n "pricing_hub_enterprise_price" . }} - {{ i18n "pricing_hub_enterprise_price_description" . }} + {{ i18n "pricing_hub_enterprise_price_description" . }} {{ i18n "pricing_hub_enterprise_cta" . }} From 6681ab5b0a25b5df988cd5f7511e5bd83c694fc1 Mon Sep 17 00:00:00 2001 From: Tobias Hagemann Date: Thu, 17 Sep 2026 15:59:16 +0200 Subject: [PATCH 3/8] Fix pricing comparison header gap below desktop widths --- assets/css/custom.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/css/custom.css b/assets/css/custom.css index 0863a4529..495e677d3 100644 --- a/assets/css/custom.css +++ b/assets/css/custom.css @@ -262,7 +262,7 @@ ul.list-primary li::marker { /* The container's overflow-x-clip leaves the y axis visible, so it cannot clip the opaque corner cells to its own radius — they round themselves. */ .compare-th { - @apply sticky top-[var(--nav-height,48px)] z-10 align-bottom border-b border-gray-200 first:rounded-tl-sm last:rounded-tr-sm; + @apply lg:sticky lg:top-[var(--nav-height,48px)] z-10 align-bottom border-b border-gray-200 first:rounded-tl-sm last:rounded-tr-sm; } .input-box { From 368b7805c6887cb8648a94b2af2c8fc709ba8a66 Mon Sep 17 00:00:00 2001 From: Tobias Hagemann Date: Thu, 17 Sep 2026 16:29:11 +0200 Subject: [PATCH 4/8] Adapt pricing comparison to phone and tablet layouts --- assets/css/custom.css | 4 +- i18n/de.yaml | 8 +- i18n/en.yaml | 8 +- .../partials/pricing/comparison-action.html | 13 ++ .../partials/pricing/comparison-price.html | 7 + .../partials/pricing/comparison-value.html | 14 ++ layouts/partials/pricing/comparison.html | 155 ++++++++++++++++++ layouts/pricing/single.html | 121 +------------- 8 files changed, 205 insertions(+), 125 deletions(-) create mode 100644 layouts/partials/pricing/comparison-action.html create mode 100644 layouts/partials/pricing/comparison-price.html create mode 100644 layouts/partials/pricing/comparison-value.html create mode 100644 layouts/partials/pricing/comparison.html diff --git a/assets/css/custom.css b/assets/css/custom.css index 495e677d3..e506cc033 100644 --- a/assets/css/custom.css +++ b/assets/css/custom.css @@ -259,10 +259,8 @@ ul.list-primary li::marker { @apply rounded-sm shadow-sm bg-white; } -/* The container's overflow-x-clip leaves the y axis visible, so it cannot clip - the opaque corner cells to its own radius — they round themselves. */ .compare-th { - @apply lg:sticky lg:top-[var(--nav-height,48px)] z-10 align-bottom border-b border-gray-200 first:rounded-tl-sm last:rounded-tr-sm; + @apply @min-[72rem]:sticky @min-[72rem]:top-[var(--nav-height,48px)] z-10 whitespace-nowrap border-b border-gray-200 bg-white align-bottom font-medium first:rounded-tl-sm last:rounded-tr-sm; } .input-box { diff --git a/i18n/de.yaml b/i18n/de.yaml index 2ace7a2c3..eab68c53e 100644 --- a/i18n/de.yaml +++ b/i18n/de.yaml @@ -1176,7 +1176,13 @@ - id: pricing_compare_title translation: "Alle Funktionen im Vergleich" - id: pricing_compare_subtitle - translation: "Jeder Plan enthält alles aus den vorherigen Plänen." + translation: "Finde den passenden Plan – Funktion für Funktion." +- id: pricing_compare_choose + translation: "Wähle zwei Pläne für den Vergleich." +- id: pricing_compare_first_plan + translation: "Erster Plan" +- id: pricing_compare_second_plan + translation: "Zweiter Plan" - id: more_information translation: "Weitere Informationen" - id: pricing_compare_features diff --git a/i18n/en.yaml b/i18n/en.yaml index 050a98b02..6cc7b1cc8 100644 --- a/i18n/en.yaml +++ b/i18n/en.yaml @@ -1177,7 +1177,13 @@ - id: pricing_compare_title translation: "Compare All Features" - id: pricing_compare_subtitle - translation: "Every plan includes everything from the plans before it." + translation: "See which plan fits your needs, feature by feature." +- id: pricing_compare_choose + translation: "Choose two plans to compare." +- id: pricing_compare_first_plan + translation: "First plan" +- id: pricing_compare_second_plan + translation: "Second plan" - id: more_information translation: "More information" - id: pricing_compare_features diff --git a/layouts/partials/pricing/comparison-action.html b/layouts/partials/pricing/comparison-action.html new file mode 100644 index 000000000..1c4fb4e3f --- /dev/null +++ b/layouts/partials/pricing/comparison-action.html @@ -0,0 +1,13 @@ +{{- $page := .page }} +{{- if eq .plan.dataKey "Standard" }} + + {{ i18n "pricing_hub_managed_cta" $page }} + + + {{ i18n "pricing_hub_self_hosted_cta" $page }} + +{{- else }} + + {{ i18n .plan.ctaKey $page }} + +{{- end }} diff --git a/layouts/partials/pricing/comparison-price.html b/layouts/partials/pricing/comparison-price.html new file mode 100644 index 000000000..9bdde711f --- /dev/null +++ b/layouts/partials/pricing/comparison-price.html @@ -0,0 +1,7 @@ +{{- if eq .plan.dataKey "Standard" }} +

+

{{ i18n "pricing_vat_excluded" .page }}

+{{- else }} +

{{ i18n .plan.priceKey .page }}

+{{- end }} +

{{ i18n .plan.priceDescriptionKey .page }}

diff --git a/layouts/partials/pricing/comparison-value.html b/layouts/partials/pricing/comparison-value.html new file mode 100644 index 000000000..453733059 --- /dev/null +++ b/layouts/partials/pricing/comparison-value.html @@ -0,0 +1,14 @@ +{{- $cell := index .row .plan.dataKey }} +{{- if eq $cell true }} + +{{ i18n "pricing_compare_included" .page }} +{{- else if eq $cell false }} + +{{ i18n "pricing_compare_not_included" .page }} +{{- else if eq (printf "%T" $cell) "string" }} +{{ i18n $cell .page }} +{{- else if $cell }} +{{ $cell }} +{{- else }} +{{- errorf "pricing_comparison: row %q is missing a value for plan %q" .row.LabelKey .plan.dataKey }} +{{- end }} diff --git a/layouts/partials/pricing/comparison.html b/layouts/partials/pricing/comparison.html new file mode 100644 index 000000000..e7f841233 --- /dev/null +++ b/layouts/partials/pricing/comparison.html @@ -0,0 +1,155 @@ +{{- $cryptomatorBrand := dict "logo" "/img/logo.svg" "logoAspect" "aspect-1110/942" "brandColor" "text-primary" }} +{{- $hubBrand := dict "logo" "/img/zero-knowledge-bot.svg" "logoAspect" "aspect-988/877" "brandColor" "text-hub" }} +{{- $plans := slice + (merge $cryptomatorBrand (dict "dataKey" "Cryptomator" "brand" "Cryptomator" "titleKey" "pricing_individual_title" "priceKey" "pricing_free" "priceDescriptionKey" "pricing_individual_price_description" "href" "/downloads/" "ctaKey" "pricing_individual_cta" "event" "pricing-compare-individual-cta")) + (merge $hubBrand (dict "dataKey" "Community" "brand" "Cryptomator Hub" "titleKey" "pricing_hub_community_title" "priceKey" "pricing_free" "priceDescriptionKey" "pricing_hub_community_price_description" "href" "/hub/self-hosted/" "ctaKey" "pricing_hub_self_hosted_cta" "event" "pricing-compare-hub-community-self-hosted-cta")) + (merge $hubBrand (dict "dataKey" "Standard" "brand" "Cryptomator Hub" "titleKey" "pricing_hub_standard_title" "priceDescriptionKey" "pricing_hub_standard_price_description")) + (merge $hubBrand (dict "dataKey" "Enterprise" "brand" "Cryptomator Hub" "titleKey" "pricing_hub_enterprise_title" "priceKey" "pricing_hub_enterprise_price" "priceDescriptionKey" "pricing_hub_enterprise_price_description" "href" "/contact-sales/" "ctaKey" "pricing_hub_enterprise_cta" "event" "pricing-compare-hub-enterprise-cta")) +}} +
+
+

{{ i18n "pricing_compare_title" . }}

+

{{ i18n "pricing_compare_subtitle" . }}

+
+
+
+

{{ i18n "pricing_compare_choose" . }}

+
+
+
{{ i18n "pricing_compare_features" . }}
+ {{- range $side := slice "first" "second" }} +
+ {{- range $plans }} +
+ +

{{ .brand }}

+
+ {{- end }} + +
+ + +
+
+ {{- end }} +
+ {{- range hugo.Data.pricing_comparison }} +
+

{{ i18n .SectionKey $ }}

+
+ {{- range .Rows }} + {{- $row := . }} +
+
+ {{ i18n .LabelKey $ }} + {{- with .TooltipKey }} +

{{ i18n . $ | safeHTML }}

+ {{- end }} +
+ {{- range $side := slice "first" "second" }} +
+ {{- range $plans }} +
+ {{ .brand }} {{ i18n .titleKey $ }}: + {{ partial "pricing/comparison-value.html" (dict "row" $row "plan" . "page" $) }} +
+ {{- end }} +
+ {{- end }} +
+ {{- end }} +
+
+ {{- end }} +
+
+ {{- range $side := slice "first" "second" }} +
+ {{- range $plans }} +
+

{{ i18n .titleKey $ }}

+
+ {{ partial "pricing/comparison-price.html" (dict "plan" . "page" $) }} +
+ {{ partial "pricing/comparison-action.html" (dict "plan" . "page" $) }} +
+ {{- end }} +
+ {{- end }} +
+
+
+ {{/* Without Alpine, the full table stays available as a scrollable fallback. */}} +
+
+ + + + + {{- range $plans }} + + {{- end }} + + + {{- range $sectionIndex, $section := hugo.Data.pricing_comparison }} + + + + {{- range $plans }} + + {{- end }} + + {{- range $rowIndex, $row := .Rows }} + + + {{- range $plans }} + + {{- end }} + + {{- end }} + + {{- end }} + + + + {{- range $plans }} + + {{- end }} + + + + {{- range $plans }} + + {{- end }} + + +
{{ i18n "pricing_compare_features" . }} + +

{{ .brand }}

+

{{ i18n .titleKey $ }}

+
+

{{ i18n .SectionKey $ }}

+
+ {{ i18n .LabelKey $ }} + {{- with .TooltipKey }} + {{- $tooltipId := printf "tooltip-compare-%d-%d" $sectionIndex $rowIndex }} +
+ + +
+ {{- end }} +
+ {{ partial "pricing/comparison-value.html" (dict "row" $row "plan" . "page" $) }} +
+ {{ partial "pricing/comparison-price.html" (dict "plan" . "page" $) }} +
+ {{ partial "pricing/comparison-action.html" (dict "plan" . "page" $) }} +
+
+
+
+
diff --git a/layouts/pricing/single.html b/layouts/pricing/single.html index 0f0309acd..c04f3cfd9 100644 --- a/layouts/pricing/single.html +++ b/layouts/pricing/single.html @@ -346,126 +346,7 @@

- {{/* Feature Comparison Section */}} -
-
-

{{ i18n "pricing_compare_title" . }}

-

{{ i18n "pricing_compare_subtitle" . }}

-
-
- - {{- $cryptomatorBrand := dict "brand" "Cryptomator" "logo" "/img/logo.svg" "logoAspect" "aspect-1110/942" "brandColor" "text-primary" }} - {{- $hubBrand := dict "brand" "Cryptomator Hub" "logo" "/img/zero-knowledge-bot.svg" "logoAspect" "aspect-988/877" "brandColor" "text-hub" }} - {{- $plans := slice - (merge $cryptomatorBrand (dict "dataKey" "Cryptomator" "titleKey" "pricing_individual_title" "highlight" false)) - (merge $hubBrand (dict "dataKey" "Community" "titleKey" "pricing_hub_community_title" "highlight" false)) - (merge $hubBrand (dict "dataKey" "Standard" "titleKey" "pricing_hub_standard_title" "highlight" true)) - (merge $hubBrand (dict "dataKey" "Enterprise" "titleKey" "pricing_hub_enterprise_title" "highlight" false)) - }} - - - - {{- range $plans }} - - {{- end }} - - - - {{- range $sectionIndex, $section := hugo.Data.pricing_comparison }} - - - {{- /* empty cells exist solely to carry the highlighted column's tint across section rows */}} - {{- range $plans }} - - {{- end }} - - {{- range $rowIndex, $row := .Rows }} - - - {{- range $plans }} - {{- $plan := . }} - {{- $cell := index $row $plan.dataKey }} - - {{- end }} - - {{- end }} - {{- end }} - - {{- /* The tfoot below is hand-written per plan, so it silently desyncs if $plans changes. */}} - {{- $footerPlans := "Cryptomator:false,Community:false,Standard:true,Enterprise:false" }} - {{- $actualParts := slice }} - {{- range $plans }}{{ $actualParts = $actualParts | append (printf "%v:%v" .dataKey .highlight) }}{{ end }} - {{- $actualPlans := delimit $actualParts "," }} - {{- if ne $actualPlans $footerPlans }}{{ errorf "pricing_comparison: $plans is %q but the tfoot is hand-written for %q — update the tfoot cells to match" $actualPlans $footerPlans }}{{ end }} - - - - - - - - - -
- {{ i18n "pricing_compare_features" . }} - - - {{ .brand }} - {{ i18n .titleKey $ }} -
{{ i18n .SectionKey $ }}
- {{ i18n .LabelKey $ }} - {{- with .TooltipKey }} - {{- $tooltipId := printf "tooltip-compare-%d-%d" $sectionIndex $rowIndex }} -
- - -
- {{- end }} -
- {{- if eq $cell true }} - - {{ i18n "pricing_compare_included" $ }} - {{- else if eq $cell false }} - - {{ i18n "pricing_compare_not_included" $ }} - {{- else if eq (printf "%T" $cell) "string" }} - {{ i18n $cell $ }} - {{- else if $cell }} - {{ $cell }} - {{- else }} - {{- errorf "pricing_comparison: row %q is missing a value for plan %q" $row.LabelKey $plan.dataKey }} - {{- end }} -
- {{ i18n "pricing_free" . }} - {{ i18n "pricing_individual_price_description" . }} - - {{ i18n "pricing_individual_cta" . }} - - - {{ i18n "pricing_free" . }} - {{ i18n "pricing_hub_community_price_description" . }} - - {{ i18n "pricing_hub_self_hosted_cta" . }} - - - - {{ i18n "pricing_vat_excluded" . }} - {{ i18n "pricing_hub_standard_price_description" . }} - - {{ i18n "pricing_hub_managed_cta" . }} - - - {{ i18n "pricing_hub_self_hosted_cta" . }} - - - {{ i18n "pricing_hub_enterprise_price" . }} - {{ i18n "pricing_hub_enterprise_price_description" . }} - - {{ i18n "pricing_hub_enterprise_cta" . }} - -
-
-
+ {{ partial "pricing/comparison.html" . }} {{/* Support Us Section */}} {{- $data := index hugo.Data $.Site.Language.Lang }} From 77ca50477025f9c15127f257f7e2cfaf1ace2a36 Mon Sep 17 00:00:00 2001 From: Tobias Hagemann Date: Thu, 17 Sep 2026 17:41:29 +0200 Subject: [PATCH 5/8] Keep pricing plan menus anchored to their selectors --- layouts/partials/pricing/comparison.html | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/layouts/partials/pricing/comparison.html b/layouts/partials/pricing/comparison.html index e7f841233..49b85d24a 100644 --- a/layouts/partials/pricing/comparison.html +++ b/layouts/partials/pricing/comparison.html @@ -25,14 +25,24 @@

{{ i18n "pri

{{ .brand }}

{{- end }} - -
- - + + +
{{- end }} From 4d0d0122e7e76a3a2b41d6b9e98c7b979e68af9c Mon Sep 17 00:00:00 2001 From: Majid Achhoud Date: Thu, 17 Sep 2026 18:43:35 +0200 Subject: [PATCH 6/8] Center pricing comparison labels on phones, replace the plan selects, and shorten the trial CTA --- i18n/de.yaml | 2 + i18n/en.yaml | 2 + .../partials/pricing/comparison-action.html | 2 +- layouts/partials/pricing/comparison.html | 40 ++++++++++++++----- 4 files changed, 34 insertions(+), 12 deletions(-) diff --git a/i18n/de.yaml b/i18n/de.yaml index eab68c53e..2af7e7064 100644 --- a/i18n/de.yaml +++ b/i18n/de.yaml @@ -1191,6 +1191,8 @@ translation: "Enthalten" - id: pricing_compare_not_included translation: "Nicht enthalten" +- id: pricing_compare_hub_managed_cta + translation: "Gratis testen" - id: pricing_compare_managed_hosting_tooltip translation: "Gewartet und aktualisiert von unserem Team in einem ISO/IEC 27001:2022-zertifizierten Rechenzentrum in der EU." - id: pricing_compare_section_basics diff --git a/i18n/en.yaml b/i18n/en.yaml index 6cc7b1cc8..5b090c289 100644 --- a/i18n/en.yaml +++ b/i18n/en.yaml @@ -1192,6 +1192,8 @@ translation: "Included" - id: pricing_compare_not_included translation: "Not included" +- id: pricing_compare_hub_managed_cta + translation: "Try for Free" - id: pricing_compare_managed_hosting_tooltip translation: "Maintained and updated by our team in an ISO/IEC 27001:2022 certified data center in the EU." - id: pricing_compare_section_basics diff --git a/layouts/partials/pricing/comparison-action.html b/layouts/partials/pricing/comparison-action.html index 1c4fb4e3f..69350ea92 100644 --- a/layouts/partials/pricing/comparison-action.html +++ b/layouts/partials/pricing/comparison-action.html @@ -1,7 +1,7 @@ {{- $page := .page }} {{- if eq .plan.dataKey "Standard" }} - {{ i18n "pricing_hub_managed_cta" $page }} + {{ i18n "pricing_compare_hub_managed_cta" $page }} {{ i18n "pricing_hub_self_hosted_cta" $page }} diff --git a/layouts/partials/pricing/comparison.html b/layouts/partials/pricing/comparison.html index e7f841233..dbb422e75 100644 --- a/layouts/partials/pricing/comparison.html +++ b/layouts/partials/pricing/comparison.html @@ -7,9 +7,9 @@ (merge $hubBrand (dict "dataKey" "Enterprise" "brand" "Cryptomator Hub" "titleKey" "pricing_hub_enterprise_title" "priceKey" "pricing_hub_enterprise_price" "priceDescriptionKey" "pricing_hub_enterprise_price_description" "href" "/contact-sales/" "ctaKey" "pricing_hub_enterprise_cta" "event" "pricing-compare-hub-enterprise-cta")) }}
-
+

{{ i18n "pricing_compare_title" . }}

-

{{ i18n "pricing_compare_subtitle" . }}

+

{{ i18n "pricing_compare_subtitle" . }}

@@ -25,29 +25,47 @@

{{ i18n "pri

{{ .brand }}

{{- end }} - -
- - +
{{- end }}
{{- range hugo.Data.pricing_comparison }}
-

{{ i18n .SectionKey $ }}

+

{{ i18n .SectionKey $ }}

{{- range .Rows }} {{- $row := . }}
-
+
{{ i18n .LabelKey $ }} {{- with .TooltipKey }} -

{{ i18n . $ | safeHTML }}

+

{{ i18n . $ | safeHTML }}

{{- end }}
{{- range $side := slice "first" "second" }} From 70f364e2d883ec0b91db2a255700b55c263229ed Mon Sep 17 00:00:00 2001 From: Majid Achhoud Date: Thu, 17 Sep 2026 18:55:23 +0200 Subject: [PATCH 7/8] Shorten the self-hosted CTA in the comparison and rotate the plan menu chevron --- i18n/de.yaml | 2 ++ i18n/en.yaml | 2 ++ layouts/partials/pricing/comparison-action.html | 2 +- layouts/partials/pricing/comparison.html | 4 ++-- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/i18n/de.yaml b/i18n/de.yaml index 521b00504..af37e566a 100644 --- a/i18n/de.yaml +++ b/i18n/de.yaml @@ -1193,6 +1193,8 @@ translation: "Enthalten" - id: pricing_compare_not_included translation: "Nicht enthalten" +- id: pricing_compare_hub_self_hosted_cta + translation: "Einrichten" - id: pricing_compare_hub_managed_cta translation: "Gratis testen" - id: pricing_compare_managed_hosting_tooltip diff --git a/i18n/en.yaml b/i18n/en.yaml index f7d566321..22ff139b8 100644 --- a/i18n/en.yaml +++ b/i18n/en.yaml @@ -1194,6 +1194,8 @@ translation: "Included" - id: pricing_compare_not_included translation: "Not included" +- id: pricing_compare_hub_self_hosted_cta + translation: "Deploy Now" - id: pricing_compare_hub_managed_cta translation: "Try for Free" - id: pricing_compare_managed_hosting_tooltip diff --git a/layouts/partials/pricing/comparison-action.html b/layouts/partials/pricing/comparison-action.html index 69350ea92..8a7cc4dc0 100644 --- a/layouts/partials/pricing/comparison-action.html +++ b/layouts/partials/pricing/comparison-action.html @@ -4,7 +4,7 @@ {{ i18n "pricing_compare_hub_managed_cta" $page }}
- {{ i18n "pricing_hub_self_hosted_cta" $page }} + {{ i18n "pricing_compare_hub_self_hosted_cta" $page }} {{- else }} diff --git a/layouts/partials/pricing/comparison.html b/layouts/partials/pricing/comparison.html index 9a8d1f3aa..c55b040e6 100644 --- a/layouts/partials/pricing/comparison.html +++ b/layouts/partials/pricing/comparison.html @@ -2,7 +2,7 @@ {{- $hubBrand := dict "logo" "/img/zero-knowledge-bot.svg" "logoAspect" "aspect-988/877" "brandColor" "text-hub" }} {{- $plans := slice (merge $cryptomatorBrand (dict "dataKey" "Cryptomator" "brand" "Cryptomator" "titleKey" "pricing_individual_title" "priceKey" "pricing_free" "priceDescriptionKey" "pricing_individual_price_description" "href" "/downloads/" "ctaKey" "pricing_individual_cta" "event" "pricing-compare-individual-cta")) - (merge $hubBrand (dict "dataKey" "Community" "brand" "Cryptomator Hub" "titleKey" "pricing_hub_community_title" "priceKey" "pricing_free" "priceDescriptionKey" "pricing_hub_community_price_description" "href" "/hub/self-hosted/" "ctaKey" "pricing_hub_self_hosted_cta" "event" "pricing-compare-hub-community-self-hosted-cta")) + (merge $hubBrand (dict "dataKey" "Community" "brand" "Cryptomator Hub" "titleKey" "pricing_hub_community_title" "priceKey" "pricing_free" "priceDescriptionKey" "pricing_hub_community_price_description" "href" "/hub/self-hosted/" "ctaKey" "pricing_compare_hub_self_hosted_cta" "event" "pricing-compare-hub-community-self-hosted-cta")) (merge $hubBrand (dict "dataKey" "Standard" "brand" "Cryptomator Hub" "titleKey" "pricing_hub_standard_title" "priceDescriptionKey" "pricing_hub_standard_price_description")) (merge $hubBrand (dict "dataKey" "Enterprise" "brand" "Cryptomator Hub" "titleKey" "pricing_hub_enterprise_title" "priceKey" "pricing_hub_enterprise_price" "priceDescriptionKey" "pricing_hub_enterprise_price_description" "href" "/contact-sales/" "ctaKey" "pricing_hub_enterprise_cta" "event" "pricing-compare-hub-enterprise-cta")) }} @@ -31,7 +31,7 @@

{{ i18n "pri {{- range $plans }} {{ i18n .titleKey $ }} {{- end }} - +