Commits: 6

Add solution Transip Email

new file mode 100644
index 0000000..3e7bc0c
--- /dev/null
+++ b/content/solutions/transip-mail/index.md
@@ -0,0 +1,25 @@
+---
+title: Transip E-mail
+  
+taxonomies:
+  solution_providers: [ "Transip" ]
+  solution_categories: [ "email provider" ]
+  features: 
+    - "open standards: imap, smtp"
+    - "just email"
+  esc_support: [ "consulting", "integration" ]
+---
+
+Transip E-mail is a great solution for those businesses that only want to send email and don't need all the extra's.
+
+<!-- more -->
+
+Esc Collective offers commercial support for business switching to Transip E-mail in form of consulting and integration. We can take care of DNS settings including DKIM and DMARC, and help migrate existing email from your current provider. But maybe most important of all, Esc Collective can help you find the email provider which meets your functional and business requirements.
+
+Transip E-mail might be just what your business needs: a simple mailbox on your own custom domain. You can use the service from their webclient or using IMAP / SMTP from any client on your own device, for example Thunderbird. Their product does not include an encryption functionality but OpenPGP can be implemented locally in a client software to provide end-to-end encryption.
+
+This solution for email is not expensive. It should be prefered if your business requirement is limited to email and you don't need other functions such as calendar and shared mailboxes.
+
+Transip is a company from the Netherlands, data is stored in their datacenters in the Netherlands. They have an excellent reputation, respecting data privacy and ensuring security. We also have positive experience with their support.
+
+Home page: <https://www.transip.nl/email-hosting/>

Improve the features index page

Content and template.

index 7f2bf71..f8b3470 100644
--- a/content/solutions/features/_index.md
+++ b/content/solutions/features/_index.md
@@ -1,8 +1,10 @@
=---
-title: Features definitions
-page_template: page.html
+# The content of this page is used in /features/ index page.
+# See /templates/features/list.html template
=render: false
=---
=
-These are features of digital solutions we review.
+We review different digital solution and annotate them with _features_ - aspects relevant to their use in a European <abbr title="Small and Medium Enterprises">SMEs</abbr>.
+
+The features are considered from this perspective. Below is a list of all the features we define, together with an explanation and digital solutions they apply to.
=
index 2446940..fea5cab 100644
--- a/templates/features/list.html
+++ b/templates/features/list.html
@@ -3,9 +3,12 @@
={\% import "components.html" as components %}
=
={\% block main_content %}
-  {\% for term in terms %}
+  {\% set section = get_section(path="solutions/features/_index.md") %}
+  {\{ section.content | safe \}}
+
+  {\% for feature in terms %}
=    <article>
-      {\% set feature_slug = term.name | slugify %}
+      {\% set feature_slug = feature.name | slugify %}
=      {\% set feature_path = "solutions/features/" ~ feature_slug ~ ".md" %}
=      {# TODO: Consider if a feature page should be required
=
@@ -19,13 +22,13 @@
=
=        <h2 style="text-transform: capitalize">
=          {\{ components::openmoji(moji=feature_page.extra.moji, size="2x") \}}
-          {\{ term.name \}}
+          {\{ feature.name \}}
=        </h2>
=        {\{ feature_page.summary | safe \}}
=      {\% else %}
=        <h2 style="text-transform: capitalize">
=          {\{ components::openmoji(moji="black star", size="2x") \}}
-          {\{ term.name \}}
+          {\{ feature.name \}}
=        </h2>
=        <p>This feature doesn't have a description yet.</p>
=        {\% if config.mode == "serve" %}
@@ -34,8 +37,8 @@
=      {\% endif %}
=
=      <p>
-        Solutions with the <em>{\{ term.name \}}</em> feature:
-        {\{ components::natural_list(items=term.pages | map (attribute="title")) \}}
+        Solutions with the <em>{\{ feature.name \}}</em> feature:
+        {\{ components::natural_list(items=feature.pages | map (attribute="title")) \}}
=      </p>
=    </article>
=  {\% endfor %}

Make feature cards links to feature pages

Required some wizardry to strip interactive tags. According to HTML spec interactive elements (like links and buttons) can't be nested.

index e362729..e498304 100644
--- a/templates/base.html
+++ b/templates/base.html
@@ -239,6 +239,12 @@
=        }
=      }
=
+      a:has(> article) {
+        display: block;
+        text-decoration: none;
+        color: inherit
+      }
+
=      /* UTILITY CLASSES */
=
=      .full-bleed-background {
index e7be6e5..c29303c 100644
--- a/templates/components.html
+++ b/templates/components.html
@@ -51,3 +51,23 @@
=  <i class="om om-{\{ moji | slugify \}} om-{\{ size \}}">
=  </i>
={\% endmacro openmoji %}
+
+{\% macro strip_interactive (html, tags="a button") %}
+{# Remove interactive tags (links and buttons).
+   Useful if content needs to be wrapped in a link.
+#}
+  {\% set_global output = html %}
+  {\% for tag in tags | split (pat=" ") %}
+    {\% for following_character in [ " ", "\n", ">" ] %}
+      {\% set from = "<" ~ tag ~ following_character %}
+      {\% set class = "stripped-" ~ tag %}
+      {\% set to = "<span class='" ~ class ~ "'" ~ following_character %}
+      {\% set_global output =
+        output
+        | replace(from=from, to=to)
+        | replace(from="</" ~ tag ~ ">", to="</span>")
+      %}
+    {\% endfor %}
+  {\% endfor %}
+  {\{ output | safe \}}
+{\% endmacro strip_interactive %}
index fea5cab..3d56b2e 100644
--- a/templates/features/list.html
+++ b/templates/features/list.html
@@ -7,39 +7,55 @@
=  {\{ section.content | safe \}}
=
=  {\% for feature in terms %}
-    <article>
-      {\% set feature_slug = feature.name | slugify %}
-      {\% set feature_path = "solutions/features/" ~ feature_slug ~ ".md" %}
-      {# TODO: Consider if a feature page should be required
-
-          As it is now, they can be skipped. But maybe we want to
-          ensure that every feature is defined? At the very least it
-          would prevent typos.
-      #}
-      {\% set feature_data = load_data (path=feature_path, required=false) %}
-      {\% if feature_data %}
-        {\% set feature_page = get_page(path=feature_path) %}
-
-        <h2 style="text-transform: capitalize">
-          {\{ components::openmoji(moji=feature_page.extra.moji, size="2x") \}}
-          {\{ feature.name \}}
-        </h2>
-        {\{ feature_page.summary | safe \}}
-      {\% else %}
-        <h2 style="text-transform: capitalize">
-          {\{ components::openmoji(moji="black star", size="2x") \}}
-          {\{ feature.name \}}
-        </h2>
-        <p>This feature doesn't have a description yet.</p>
-        {\% if config.mode == "serve" %}
-        <p><small>Create a file at <code>content/{\{ feature_path \}}</code> and describe it.</small></p>
-        {\% endif %}
-      {\% endif %}
+    <a
+      href="{\{ get_url (path=feature.path) \}}"
+      title="Read more about the {\{ feature.name \}} feature"
+    >
+      <article>
+        {\% set feature_slug = feature.name | slugify %}
+        {\% set feature_path = "solutions/features/" ~ feature_slug ~ ".md" %}
+        {# TODO: Consider if a feature page should be required
+
+            As it is now, they can be skipped. But maybe we want to
+            ensure that every feature is defined? At the very least it
+            would prevent typos.
+        #}
+        {\% set feature_data = load_data (path=feature_path, required=false) %}
+        {\% if feature_data %}
+          {\% set feature_page = get_page(path=feature_path) %}
=
-      <p>
-        Solutions with the <em>{\{ feature.name \}}</em> feature:
-        {\{ components::natural_list(items=feature.pages | map (attribute="title")) \}}
-      </p>
-    </article>
+          {\{ self::feature_heading(text=feature.name, moji=feature_page.extra.moji) \}}
+          <div>
+            {\{ components::strip_interactive (html=feature_page.summary) | safe \}}
+            {\{ self::solutions_list (feature=feature) \}}
+          </div>
+        {\% else %}
+          {\{ self::feature_heading(text=feature.name) \}}
+          <div>
+            <p>This feature doesn't have a description yet.</p>
+            {\% if config.mode == "serve" %}
+            <p><small>Create a file at <code>content/{\{ feature_path \}}</code> and describe it.</small></p>
+            {\% endif %}
+            {\{ self::solutions_list (feature=feature) \}}
+          </div>
+        {\% endif %}
+      </article>
+    </a>
=  {\% endfor %}
={\% endblock main_content %}
+
+
+{\% macro feature_heading (text, moji="black star") %}
+  <h2 style="text-transform: capitalize">
+    {\{ components::openmoji(moji=moji, size="2x") \}}
+    {\{ text \}}
+  </h2>
+{\% endmacro feature_heading %}
+
+{\% macro solutions_list (feature) %}
+  <p>
+    Solutions with the <em>{\{ feature.name \}}</em> feature:
+    {\{ components::natural_list(items=feature.pages | map (attribute="title")) \}}
+  </p>
+{\% endmacro feature_heading %}
+

Make solution cards links

Also implement a subtle raising animation for hovered card-links.

Also fix some weird sizing / whitespace issues.

index e498304..26335a1 100644
--- a/templates/base.html
+++ b/templates/base.html
@@ -229,6 +229,7 @@
=        display: flex;
=        flex-direction: column;
=        margin-bottom: 2rem;
+        transition: box-shadow 250ms;
=
=        p {
=          margin-top: 1em;
@@ -242,7 +243,19 @@
=      a:has(> article) {
=        display: block;
=        text-decoration: none;
-        color: inherit
+        color: inherit;
+
+        article {
+          height: 100%;
+          margin: 0;
+          box-sizing: border-box;
+        }
+
+        &:hover, &:focus {
+          article {
+            box-shadow: 0 10px 12px rgba(0, 0, 0, 0.1);
+          }
+        }
=      }
=
=      /* UTILITY CLASSES */
index c29303c..c93e9ee 100644
--- a/templates/components.html
+++ b/templates/components.html
@@ -1,6 +1,6 @@
={# These are reusable components (macros) #}
=
-{\% macro feature_bullet(feature) %}
+{\% macro feature_bullet(feature, link=true) %}
={\% set feature_slug = feature | slugify %}
={\% set feature_moji = "black star" %}
=
@@ -20,21 +20,28 @@
={\% endif %}
=
=  <li class="om om-{\{ feature_moji | slugify \}}">
-    <a href="{\{ get_url(path="/features/" ~ feature_slug) \}}">{\{ feature \}}</a>
+    {\% if link %}
+    <a href="{\{ get_url(path="/features/" ~ feature_slug) \}}">
+    {\% endif %}
+        {\{ feature \}}
+    {\% if link %}
+    </a>
+    {\% endif %}
=  </li>
={\% endmacro feature_bullet %}
=
={\% macro solution_card(solution, level=4) %}
-  <article>
+  <a href="{\{ get_url(path=solution.path) \}}">
+    <article>
=      <h{\{ level \}}>{\{ solution.title \}}</h{\{ level \}}>
-      <p>{\{ solution.summary | safe | striptags \}}</p>
-      <ul>
-          {\% for feature in solution.taxonomies.features %}
-          {\{ self::feature_bullet(feature=feature) \}}
-          {\% endfor %}
+      {\{ self::strip_interactive (html=solution.summary) \}}
+      <ul style="margin-top: auto">
+        {\% for feature in solution.taxonomies.features %}
+        {\{ self::feature_bullet(feature=feature, link=false) \}}
+        {\% endfor %}
=      </ul>
-      <p style="margin-top: auto"><a href="{\{ get_url(path=solution.path) \}}">Learn more</a></p>
-  </article>
+    </article>
+  </a>
={\% endmacro solution_card %}
=
={\% macro natural_list (items) %}
index 3d56b2e..93aaa79 100644
--- a/templates/features/list.html
+++ b/templates/features/list.html
@@ -10,6 +10,7 @@
=    <a
=      href="{\{ get_url (path=feature.path) \}}"
=      title="Read more about the {\{ feature.name \}} feature"
+      style="margin-bottom: 2em"
=    >
=      <article>
=        {\% set feature_slug = feature.name | slugify %}

Add solution gmail

new file mode 100644
index 0000000..0a0bac5
--- /dev/null
+++ b/content/solutions/gmail/index.md
@@ -0,0 +1,26 @@
+---
+title: Gmail for business
+  
+taxonomies:
+  solution_providers: [ "Alphabet" ]
+  solution_categories: [ "email provider" ]
+  features: 
+    - "part of Google Workspace suite"
+    - "US jurisdiction"
+    - "Active supporter of the anti-DEI movement"
+
+---
+
+Google offers email for business as part of its Google Workspace solution.
+
+<!-- more -->
+
+Esc Collective recommends against using Gmail for email in your business, for multiple reasons:
+- Google is based in the US, and is obliged to follow any court orders or presidential executive orders. Also if that involves violating international law, exposing your personal data or company secrets, or stopping business critical services without prior notice.
+- Big tech companies such as Google do not respect EU regulations such as those for privacy - the General Data Protection Regulation (GDPR). While European politicians still pretend the [EU-US data privacy framework (DPF)](https://ec.europa.eu/commission/presscorner/detail/en/qanda_23_3752) is in place, anyone reading the news about US politics concludes the DPF is defunct. This means any services provided by US companies, even when hosted in Europe, are no longer compliant with GDPR.
+- Google has built their business on your data, respect of privacy or (other) companies secrets have never been their concern.
+- Google has [strong ties to the increasingly hostile regime of Donald Trump](https://www.cnbc.com/2025/01/09/google-donates-1-million-to-trumps-inauguration-fund.html) and was one of the big tech companies that already early in 2025 ditched all their DEI initiatives.
+
+While Google offers a very complete set of services and offers high standards in security, providing access to your company data to this provider is a major concern seen their disrespect for privacy.
+
+Home page: <https://www.google.com/>

Display list of features on every solution's page

index c93e9ee..8bdc03d 100644
--- a/templates/components.html
+++ b/templates/components.html
@@ -78,3 +78,58 @@
=  {\% endfor %}
=  {\{ output | safe \}}
={\% endmacro strip_interactive %}
+
+
+{\% macro features_cards(features, level=2) %}
+  {\% for feature in features %}
+    <a
+      href="{\{ get_url (path=feature.path) \}}"
+      title="Read more about the {\{ feature.name \}} feature"
+      style="margin-bottom: 2em"
+    >
+      <article>
+        {\% set feature_slug = feature.name | slugify %}
+        {\% set feature_path = "solutions/features/" ~ feature_slug ~ ".md" %}
+        {# TODO: Consider if a feature page should be required
+
+            As it is now, they can be skipped. But maybe we want to
+            ensure that every feature is defined? At the very least it
+            would prevent typos.
+        #}
+        {\% set feature_data = load_data (path=feature_path, required=false) %}
+        {\% if feature_data %}
+          {\% set feature_page = get_page(path=feature_path) %}
+
+          {\{ self::feature_heading(text=feature.name, moji=feature_page.extra.moji, level=level) \}}
+          <div>
+            {\{ components::strip_interactive (html=feature_page.summary) | safe \}}
+            {\{ self::solutions_list (feature=feature) \}}
+          </div>
+        {\% else %}
+          {\{ self::feature_heading(text=feature.name, level=level) \}}
+          <div>
+            <p>This feature doesn't have a description yet.</p>
+            {\% if config.mode == "serve" %}
+            <p><small>Create a file at <code>content/{\{ feature_path \}}</code> and describe it.</small></p>
+            {\% endif %}
+            {\{ self::solutions_list (feature=feature) \}}
+          </div>
+        {\% endif %}
+      </article>
+    </a>
+  {\% endfor %}
+{\% endmacro features_cards %}
+
+{\% macro feature_heading (text, moji="black star", level=2) %}
+  <h{\{ level \}} style="text-transform: capitalize">
+    {\{ components::openmoji(moji=moji, size="2x") \}}
+    {\{ text \}}
+  </h{\{ level \}}>
+{\% endmacro feature_heading %}
+
+{\% macro solutions_list (feature) %}
+  <p>
+    Solutions with the <em>{\{ feature.name \}}</em> feature:
+    {\{ components::natural_list(items=feature.pages | map (attribute="title")) \}}
+  </p>
+{\% endmacro feature_heading %}
index 93aaa79..1044c51 100644
--- a/templates/features/list.html
+++ b/templates/features/list.html
@@ -6,57 +6,7 @@
=  {\% set section = get_section(path="solutions/features/_index.md") %}
=  {\{ section.content | safe \}}
=
-  {\% for feature in terms %}
-    <a
-      href="{\{ get_url (path=feature.path) \}}"
-      title="Read more about the {\{ feature.name \}} feature"
-      style="margin-bottom: 2em"
-    >
-      <article>
-        {\% set feature_slug = feature.name | slugify %}
-        {\% set feature_path = "solutions/features/" ~ feature_slug ~ ".md" %}
-        {# TODO: Consider if a feature page should be required
-
-            As it is now, they can be skipped. But maybe we want to
-            ensure that every feature is defined? At the very least it
-            would prevent typos.
-        #}
-        {\% set feature_data = load_data (path=feature_path, required=false) %}
-        {\% if feature_data %}
-          {\% set feature_page = get_page(path=feature_path) %}
-
-          {\{ self::feature_heading(text=feature.name, moji=feature_page.extra.moji) \}}
-          <div>
-            {\{ components::strip_interactive (html=feature_page.summary) | safe \}}
-            {\{ self::solutions_list (feature=feature) \}}
-          </div>
-        {\% else %}
-          {\{ self::feature_heading(text=feature.name) \}}
-          <div>
-            <p>This feature doesn't have a description yet.</p>
-            {\% if config.mode == "serve" %}
-            <p><small>Create a file at <code>content/{\{ feature_path \}}</code> and describe it.</small></p>
-            {\% endif %}
-            {\{ self::solutions_list (feature=feature) \}}
-          </div>
-        {\% endif %}
-      </article>
-    </a>
-  {\% endfor %}
+  {\{ components::features_cards (features=terms) \}}
={\% endblock main_content %}
=
=
-{\% macro feature_heading (text, moji="black star") %}
-  <h2 style="text-transform: capitalize">
-    {\{ components::openmoji(moji=moji, size="2x") \}}
-    {\{ text \}}
-  </h2>
-{\% endmacro feature_heading %}
-
-{\% macro solutions_list (feature) %}
-  <p>
-    Solutions with the <em>{\{ feature.name \}}</em> feature:
-    {\{ components::natural_list(items=feature.pages | map (attribute="title")) \}}
-  </p>
-{\% endmacro feature_heading %}
-
index 9913136..7f0320d 100644
--- a/templates/solution.html
+++ b/templates/solution.html
@@ -7,20 +7,32 @@
={\% block main_content %}
={\{ page.content | safe \}}
=
-{\%
-  set alternatives = []
-%}
-
=<p>
=  Do you use {\{ page.title \}} in your business?
=  {\% if page.taxonomies.esc_support %}
-  Esc Collective offers support in form of {\{ components::natural_list (items=page.taxonomies.esc_support) \}}. 
+  Esc Collective offers support in form of {\{ components::natural_list (items=page.taxonomies.esc_support) \}}.
=  {\% else %}
=  We can help you switch to one of the viable alternatives.
=  {\% endif %}
=</p>
=
=
+{\% set_global features = []  %}
+{\% for feature in get_taxonomy (kind="features") | get (key="items") %}
+  {\% if page.taxonomies.features is containing (feature.name) %}
+    {\% set_global features = features | concat (with=feature) %}
+  {\% endif %}
+{\% endfor %}
+
+{\% if features %}
+<h2>Features of {\{ page.title \}}</h2>
+{\{ components::features_cards (features=features, level=3) \}}
+{\% endif %}
+
+{\%
+  set alternatives = []
+%}
+
={# TODO: Do not print the heading if there are no alternatives. Print something else. #}
={\% for category in page.taxonomies.solution_categories %}
=  {\% set term = get_taxonomy_term(kind="solution_categories", term=category) %}