Week 25 of 2025

Development log of Esc Collective website

7 items
  1. Implement a smarter title capitalization macro
  2. Fix indentation in components.html
  3. Fix main nav URLs in branch previews
  4. Use Patricia's and Joachim's copy on the home page
  5. Display a table of contents on home page
  6. Spread content on the home page vertically
  7. Separate debug component

Implement a smarter title capitalization macro

On by Tad Lispy

Mostly to fix ERP capitalization, deal with abbreviations in general and other kinks.

index 8bdc03d..0b0a020 100644
--- a/templates/components.html
+++ b/templates/components.html
@@ -133,3 +133,64 @@
=    {{ components::natural_list(items=feature.pages | map (attribute="title")) }}
=  </p>
={% endmacro feature_heading %}
+
+
+{# Roughly follow the Associated Press style for titles capitalization #}
+{% macro title (text) %}
+  {% set ignored_words = [
+    "a",
+    "and",
+    "as",
+    "at",
+    "but",
+    "by",
+    "for",
+    "from",
+    "if",
+    "in",
+    "into",
+    "like",
+    "near",
+    "nor",
+    "of",
+    "off ",
+    "on",
+    "once",
+    "onto",
+    "or",
+    "so",
+    "than",
+    "that",
+    "to",
+    "upon",
+    "when",
+    "with",
+    "yet"
+  ] %}
+
+  {% for token in text | split(pat=" ") %}
+    {% set word = token
+       | replace (from=",", to="")
+       | replace (from=".", to="")
+       | replace (from=":", to="")
+       | replace (from=";", to="")
+       | replace (from="?", to="")
+       | replace (from="!", to="")
+       | replace (from="(", to="")
+       | replace (from=")", to="")
+       | replace (from='"', to="")
+       | replace (from="'", to="")
+    %}
+    {# Never capitalize words that contain any uppercase letter, number or special characters #}
+    {% if word is matching ("[A-Z0-9@#$\^&\*_\+\`><]") %}
+      {{ token }}
+    {# Otherwise always capitalize first and last word #}
+    {% elif loop.first or loop.last %}
+      {{ token | title }}
+    {% elif ignored_words is containing (word) %}
+      {{ token }}
+    {% else %}
+      {{ token | title }}
+    {% endif %}
+  {% endfor %}
+{% endmacro title %}
index f87f027..6b31327 100644
--- a/templates/features/single.html
+++ b/templates/features/single.html
@@ -32,7 +32,7 @@
=    {% endif %}
=  {% endfor %}
=  {% if solutions %}
-    <h3>{{ category.name | title }}</h3>
+    <h3>{{ components::title(text=category.name) }}</h3>
=    <section class="card-grid">
=      {% for solution in solutions %}
=        {{ components::solution_card(solution=solution) }}
index 1deb8ac..e33e7da 100644
--- a/templates/solutions.html
+++ b/templates/solutions.html
@@ -9,7 +9,7 @@
=
=  {% set taxonomy = get_taxonomy(kind="solution_categories") %}
=  {% for category in taxonomy.items %}
-    <h2>{{ category.name | title }}</h2>
+    <h2>{{ components::title(text=category.name) }}</h2>
=
=    <section class="card-grid">
=      {% for solution in category.pages %}

Fix indentation in components.html

On by Tad Lispy

index 0b0a020..37f6807 100644
--- a/templates/components.html
+++ b/templates/components.html
@@ -1,29 +1,29 @@
={# These are reusable components (macros) #}
=
={% macro feature_bullet(feature, link=true) %}
-{% set feature_slug = feature | slugify %}
-{% set feature_moji = "black star" %}
-
-{% 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) %}
-  {% if feature_page.extra.moji %}
-  {% set feature_moji = feature_page.extra.moji %}
+  {% set feature_slug = feature | slugify %}
+  {% set feature_moji = "black star" %}
+
+  {% 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) %}
+    {% if feature_page.extra.moji %}
+    {% set feature_moji = feature_page.extra.moji %}
+    {% endif %}
=  {% endif %}
-{% endif %}
=
=  <li class="om om-{{ feature_moji | slugify }}">
=    {% if link %}
=    <a href="{{ get_url(path="/features/" ~ feature_slug) }}">
=    {% endif %}
-        {{ feature }}
+      {{ feature }}
=    {% if link %}
=    </a>
=    {% endif %}

Fix main nav URLs in branch previews

On by Tad Lispy

They were pointing to main branch.

index 26335a1..f55bb0f 100644
--- a/templates/base.html
+++ b/templates/base.html
@@ -402,7 +402,7 @@
=          {%- set item_section = get_section(path=item.path) -%}
=          {%- set is_active = item_section.path == current_path %}
=          <li class="{{ item.class }} {% if is_active %}active{% endif %}">
-            <a href="{{ item_section.path }}">{{ item.label }}</a>
+            <a href="{{ get_url(path=item_section.path) }}">{{ item.label }}</a>
=          </li>
=          {% endfor -%}
=

Use Patricia's and Joachim's copy on the home page

On by Tad Lispy

index a834d7f..5c874d2 100644
--- a/content/_index.md
+++ b/content/_index.md
@@ -1,23 +1,72 @@
=---
=---
=
-We are an international network of IT professionals and entrepreneurs dedicated to promote and support free software and use of services respecting human rights, dignity and privacy.
+Move away from big tech. Stay in control of your tools, your data, and your values.
=
-At the heart of our services lies a commitment to Free and Open Source Software (FOSS) and EU cloud solutions, ensuring privacy and adherence to open standards.
+# Why make the switch?
=
-Based entirely in Europe, [our team](@/people/_index.md) is on-shore, allowing us to provide localized, high-quality services. We offer assistance in multiple languages, including English, Dutch, French, Polish, Galician, Spanish, Portuguese, German, and Hungarian.
+Most organisations rely on Google or Microsoft tools because they feel like the default.
=
+But that choice comes with trade-offs:
=
-# Our Services
+- Your data lives on servers you don't control
+- Recurring fees add up over time
+- Tools can change without notice
+- Your values may not align with the platforms you depend on
=
-Our primary focus is on helping European Small and Medium-sized Enterprises (SMEs), Non-Governmental Organizations (NGOs), local governments, and educational institutions with implementing free, open-source and European software solutions. We provide
+For many mission-led teams, that's no longer acceptable.
=
-🌤️ Cloud assistance
+# What we do
=
-💻 Software Development
+ESC Collective helps you move to European and open source alternatives without the complexity.
=
-💬 Consulting
+We guide you through the transition from proprietary platforms to ethical tools that:
=
-📖 Training and coaching
+- Guarantee confidentiality and intellectual property
+- Are cost-effective over the long term
+- Support open standards, reducing the risk of vendor lock-in
+- Are easy to learn and use without in-house IT
=
-[Read more about our services](@/services/_index.md)
+[You can read more about our services here](@/services/_index.md).
+
+# How it works
+
+We don't drop a list of tools and walk away. We work with you from start to finish.
+
+1. Understand
+
+    We start with a simple conversation about what you use now, what's not working, and what you'd like instead.
+
+2. Plan
+
+    We map out your best-fit alternative setup based on your workflows, needs, and resources.
+
+3. Set up
+
+    We configure your new tools, migrate your data, and make sure everything works as expected.
+
+4. Support
+
+    We train your team in plain language and stay available for any follow-up questions or support.
+
+# Who we work with
+
+We're built for organisations with long-term goals, and who care about autonomy, privacy, and sustainability:
+
+- Small and Medium-sized Enterprises (SME)
+- Non-Governmental Organisations (NGO) and Activist Groups
+- Schools and Educational Projects
+- Local governments and public sector organisations
+
+# Why ESC Collective?
+
+* We're a cooperative, not a corporation
+* We rely on cloud infrastructure in countries with strict privacy regulations
+* We offer support in multiple languages
+* We do the migration work so you don't have to
+
+You're not stuck. There are better tools. And you don't need to figure them out alone.
+
+Book a free 30-minute intro call to see what switching away from big tech could look like for you.
+
+[Book a Call](https://cal.com/tad-lispy/esc-co-intro)

Display a table of contents on home page

On by Tad Lispy

Implement a table of contents component and shortcode

The shortcode uses a hack described here:

https://github.com/getzola/zola/issues/584

index 5c874d2..3efaddd 100644
--- a/content/_index.md
+++ b/content/_index.md
@@ -3,6 +3,8 @@
=
=Move away from big tech. Stay in control of your tools, your data, and your values.
=
+{{ table_of_contents() }}
+
=# Why make the switch?
=
=Most organisations rely on Google or Microsoft tools because they feel like the default.
index f55bb0f..f612011 100644
--- a/templates/base.html
+++ b/templates/base.html
@@ -69,6 +69,8 @@
=        --red: oklch(from var(--yellow) L C 29deg);
=        --green: oklch(from var(--yellow) L C 142deg);
=        --blue: oklch(from var(--yellow) L C 264deg);
+
+        scroll-behavior: smooth;
=      }
=
=
@@ -159,7 +161,25 @@
=            color: var(--dark-color);
=          }
=        }
+      }
+
+      /* Table of contents navigation */
+      nav.table-of-contents > ul {
+        display: flex;
+        justify-content: start;
+        flex-wrap: wrap;
+        gap: 1rem;
+        list-style: none;
+        padding: 0;
+
+        li {
+          font-weight: bold;
+        }
+
=
+        @media (max-width: 36rem) {
+          flex-direction: column;
+        }
=      }
=
=      hgroup h1 {
index bb9e6d0..7d2a03f 100644
--- a/templates/components.html
+++ b/templates/components.html
@@ -211,3 +211,13 @@
=  {% endfor %}
=  {{ demoted | safe }}
={% endmacro table_of_contents %}
+
+{% macro table_of_contents (toc) %}
+  <nav class="table-of-contents">
+    <ul title="Jump to section">
+      {% for item in toc %}
+      <li><a href="#{{ item.id }}">{{ item.title }}</a></li>
+      {% endfor %}
+    </ul>
+  </nav>
+{% endmacro table_of_contents %}
index 374bb55..bd9baf9 100644
--- a/templates/index.html
+++ b/templates/index.html
@@ -1,7 +1,10 @@
={% extends "base.html" %}
=
+{% import "components.html" as components %}
+
={%- block main_content -%}
={{- components::demote_headings (content=section.content)
+    | replace(from="<!-- table-of-contents -->", to=components::table_of_contents(toc=section.toc))
=    | safe
=-}}
={%- endblock main_content -%}
new file mode 100644
index 0000000..65027a4
--- /dev/null
+++ b/templates/shortcodes/table_of_contents.html
@@ -0,0 +1 @@
+<!-- table-of-contents -->

Spread content on the home page vertically

On by Tad Lispy

Allow templates to set root class (set on the html element in base template). The home page set's it to "home". CSS contains a section to make home page vertically sparse, so that each section takes at least one screen height. Also fonts are bigger. All in all it has a bit of slideshow vibe.

I think it's better, because there is a lot of text on home page and to me it looks intimidating on first glance. Now sections are presented separately.

index f612011..32e3716 100644
--- a/templates/base.html
+++ b/templates/base.html
@@ -3,6 +3,10 @@
=  fallback logic.
=#}
=
+{% block variables %}
+{% set root_class = "" %}
+{% endblock variables %}
+
={%- if not title -%}
=  {%- if section and section.title -%}
=    {%- set title = section.title ~ " - " ~ config.title -%}
@@ -24,7 +28,7 @@
={%- endif -%}
=
=<!doctype html>
-<html class="no-js" lang="en">
+<html class="no-js {{ root_class }}" lang="en">
=  <head>
=    <meta charset="utf-8" />
=    <meta http-equiv="x-ua-compatible" content="ie=edge" />
@@ -73,6 +77,31 @@
=        scroll-behavior: smooth;
=      }
=
+      /* Special treatment for landing page */
+      .home {
+
+        main {
+          font-size: 150%;
+        }
+
+        main h2 {
+          padding-top: 25vh;
+          margin-top: 25vh;
+        }
+
+        body > footer {
+          margin-top: 20vh;
+        }
+
+        main > p:first-of-type {
+          padding-top: 15vh;
+          font-size: 200%;
+        }
+
+        .table-of-contents {
+          font-size: initial;
+        }
+      }
=
=      body {
=        font-family: sans-serif;
@@ -127,7 +156,8 @@
=        --accent-color: var(--blue)
=      }
=
-      nav > ul {
+      /* Main navigation */
+      body > header nav > ul {
=        display: flex;
=        justify-content: end;
=        flex-wrap: wrap;
index bd9baf9..da1c01c 100644
--- a/templates/index.html
+++ b/templates/index.html
@@ -2,6 +2,11 @@
=
={% import "components.html" as components %}
=
+{% block variables %}
+{{ super() }}
+{% set root_class = "home" %}
+{% endblock variables %}
+
={%- block main_content -%}
={{- components::demote_headings (content=section.content)
=    | replace(from="<!-- table-of-contents -->", to=components::table_of_contents(toc=section.toc))

Separate debug component

On by Tad Lispy

So it can be re-used in macros and shortcodes.

index 32e3716..56e41c5 100644
--- a/templates/base.html
+++ b/templates/base.html
@@ -427,12 +427,7 @@
=      </p>
=    <![endif]-->
=
-    {%- if config.mode == "serve" %}
-    <details style="overflow: scroll">
-      <summary>Rendering context</summary>
-      <pre>{{ __tera_context | escape | safe }}</pre>
-    </details>
-    {% endif -%}
+    {% include "shortcodes/debug.html" %}
=
=
=    <header>
new file mode 100644
index 0000000..cef40cd
--- /dev/null
+++ b/templates/shortcodes/debug.html
@@ -0,0 +1,6 @@
+{%- if config.mode == "serve" %}
+<details style="overflow: scroll">
+  <summary>Rendering context</summary>
+  <pre>{{ __tera_context | escape | safe }}</pre>
+</details>
+{% endif -%}