From af73854f427c2c6bea26598483e2f34929d7b48e Mon Sep 17 00:00:00 2001 From: MTRNord Date: Wed, 4 Mar 2026 08:47:59 +0100 Subject: [PATCH 01/17] add hosting provider pages and convert index to section Move hosting providers out of providers.toml into individual .md pages so each gets its own detail page. Rename index.md to _index.md to make the directory a Zola section and add page_template and sort_by. Signed-off-by: MTRNord --- .../ecosystem/hosting/{index.md => _index.md} | 6 ++--- content/ecosystem/hosting/communick.md | 14 +++++++++++ content/ecosystem/hosting/datanauten.md | 15 ++++++++++++ content/ecosystem/hosting/etke.md | 23 +++++++++++++++++++ content/ecosystem/hosting/fairkom.md | 23 +++++++++++++++++++ .../ecosystem/hosting/federated-computer.md | 14 +++++++++++ content/ecosystem/hosting/indiehosters.md | 15 ++++++++++++ content/ecosystem/hosting/ossrox.md | 14 +++++++++++ content/ecosystem/hosting/ungleich.md | 16 +++++++++++++ 9 files changed, 136 insertions(+), 4 deletions(-) rename content/ecosystem/hosting/{index.md => _index.md} (95%) create mode 100644 content/ecosystem/hosting/communick.md create mode 100644 content/ecosystem/hosting/datanauten.md create mode 100644 content/ecosystem/hosting/etke.md create mode 100644 content/ecosystem/hosting/fairkom.md create mode 100644 content/ecosystem/hosting/federated-computer.md create mode 100644 content/ecosystem/hosting/indiehosters.md create mode 100644 content/ecosystem/hosting/ossrox.md create mode 100644 content/ecosystem/hosting/ungleich.md diff --git a/content/ecosystem/hosting/index.md b/content/ecosystem/hosting/_index.md similarity index 95% rename from content/ecosystem/hosting/index.md rename to content/ecosystem/hosting/_index.md index 91bbf3c15..1b51d2364 100644 --- a/content/ecosystem/hosting/index.md +++ b/content/ecosystem/hosting/_index.md @@ -1,6 +1,8 @@ +++ title = "Hosting" template = "ecosystem/hosting.html" +page_template = "ecosystem/hosting-provider.html" +sort_by = "title" weight = 6 extra.summary = """ Everyone can host their Matrix server, but not everybody wants to. These are the providers the Matrix.org Foundation knows about. @@ -30,7 +32,3 @@ of the data of their organisation. The following providers have been brought to the attention of The Matrix.org Foundation. Please note that The Matrix.org Foundation doesn't endorse them, nor provides any warranties for using them. - -## Known providers - -{{ hosting_providers() }} diff --git a/content/ecosystem/hosting/communick.md b/content/ecosystem/hosting/communick.md new file mode 100644 index 000000000..f1985d60c --- /dev/null +++ b/content/ecosystem/hosting/communick.md @@ -0,0 +1,14 @@ ++++ +title = "Communick" + +[extra] +image = "communick.png" +website = "https://communick.com" +operating_since = "2018" +b2b = false +b2c = true ++++ + +Communick is a service provider for open source messaging and social media platforms. Hosted in Germany, it provides fully managed Synapse homeservers for those looking to be in control of their media presence and also provides a flagship instance at [matrix.communick.com](https://matrix.communick.com) for subscribers that value decentralization and are just looking for a stable, less popular server. As a form to support open source, Communick pledges to give 20% of its profits to the open source projects that it offers, so by becoming a customer you'll be also directly ensuring the continued development of the Matrix ecosystem. + +For information about different hosting plans, please check our [Matrix service webpage](https://communick.com/services/matrix). diff --git a/content/ecosystem/hosting/datanauten.md b/content/ecosystem/hosting/datanauten.md new file mode 100644 index 000000000..4c03fcdea --- /dev/null +++ b/content/ecosystem/hosting/datanauten.md @@ -0,0 +1,15 @@ ++++ +title = "Datanauten" + +[extra] +image = "datanauten.png" +website = "https://datanauten.de/" +operating_since = "2022" +b2b = true +b2c = true ++++ + +- Hosted Homeservers +- Bridge Solutions: Installation and configuration of Bridges including Slack, Teams, Telegram, and WhatsApp. + +The Datanauten Matrix Homeserver offers an easy entry into the open and interoperable communication system of the future. diff --git a/content/ecosystem/hosting/etke.md b/content/ecosystem/hosting/etke.md new file mode 100644 index 000000000..20d7c64f9 --- /dev/null +++ b/content/ecosystem/hosting/etke.md @@ -0,0 +1,23 @@ ++++ +title = "etke.cc" + +[extra] +image = "etke.cc.png" +website = "https://etke.cc" +operating_since = "2021" +b2b = true +b2c = true ++++ + +- Hosted Homeservers +- Cloud or on-premises (BYOS) +- Fully [FOSS](https://en.wikipedia.org/wiki/Free_and_open-source_software) (based on [matrix-docker-ansible-deploy](https://github.com/spantaleev/matrix-docker-ansible-deploy)). See [our code](https://gitlab.com/etke.cc) +- [Per-complexity](https://etke.cc/news/lnyorh4ggeh9xadrob_rtuuqu_jj-s-ojfvvfzlmxpm/#pricing-model-changes) pricing (not per-user) +- 15+ [Matrix bridges & bots](https://etke.cc/order/#matrix-bridges) +- [Matrix addons](https://etke.cc/order/#matrix-addons) (S3, SSO, workers, etc.) +- 15+ [Non-Matrix addons](https://etke.cc/order/#non-matrix-addons) (email, VPN, etc.) +- Fully-managed, but still yours (root SSH) + +etke.cc is the only fully FOSS-based Matrix Hosting service. It's operated by a [team](https://etke.cc/about/) of active members of the self-hosting community. Based on [matrix-docker-ansible-deploy](https://github.com/spantaleev/matrix-docker-ansible-deploy), but hassle-free (SaaS) and contains improvements, extra addons and [support](https://etke.cc/services/support/). Get hosted on rented servers or use your own infra (on any provider) in a non-vendor-lock-in way. + +[Matrix Space](https://matrix.to/#/#service:etke.cc) | [Announcements on Matrix](https://matrix.to/#/#news:etke.cc) diff --git a/content/ecosystem/hosting/fairkom.md b/content/ecosystem/hosting/fairkom.md new file mode 100644 index 000000000..11973cd26 --- /dev/null +++ b/content/ecosystem/hosting/fairkom.md @@ -0,0 +1,23 @@ ++++ +title = "fairkom" + +[extra] +image = "fairkom_4c_quer.svg" +website = "https://fairkom.eu" +operating_since = "2015" +b2b = true +b2c = false ++++ + +- Hosting (large) homeservers +- Consulting for Matrix based messenger solutions +- Customization of clients and apps +- Voice & video call integration +- Standard and custom bridges +- Scalable deployments on kubernetes +- On premise deployment and operations support +- Integration with Identity Management Systems + +fairkom has been building and hosting chat solutions with various open source frameworks. We are now focussing on Matrix based solutions as interoperability is a key demand of customers and users. Our largest deployment scales for half a million pupils, for whom we developed an app for smartphones with additional educational features. Hosting is climate neutral and GDPR compliant. + +[Try fairmatrix](https://www.fairkom.eu/fairmatrix) diff --git a/content/ecosystem/hosting/federated-computer.md b/content/ecosystem/hosting/federated-computer.md new file mode 100644 index 000000000..a219267bf --- /dev/null +++ b/content/ecosystem/hosting/federated-computer.md @@ -0,0 +1,14 @@ ++++ +title = "Federated Computer" + +[extra] +image = "federated.png" +website = "https://federated.computer" +operating_since = "2022" +b2b = true +b2c = true ++++ + +- Hosted Homeservers + +Federated Computer provides Element and Matrix hosting as part of a team collaboration suite (including Nextcloud, Jitsi, Listmonk, Vaultwarden, Baserow, Gitea, Caddy, Castopod, Wireguard, and single-sign-on LDAP). diff --git a/content/ecosystem/hosting/indiehosters.md b/content/ecosystem/hosting/indiehosters.md new file mode 100644 index 000000000..b46f2559b --- /dev/null +++ b/content/ecosystem/hosting/indiehosters.md @@ -0,0 +1,15 @@ ++++ +title = "IndieHosters" + +[extra] +image = "indiehosters.png" +website = "https://indiehosters.net" +operating_since = "2016" +b2b = true +b2c = false ++++ + +- Hosted Homeservers +- Hosted collaborative tools + +We are a French collective specialized in hosting FOSS software and aimed at building a digital common, with a particular emphasis on collaboration. diff --git a/content/ecosystem/hosting/ossrox.md b/content/ecosystem/hosting/ossrox.md new file mode 100644 index 000000000..f2e934f2e --- /dev/null +++ b/content/ecosystem/hosting/ossrox.md @@ -0,0 +1,14 @@ ++++ +title = "Ossrox" + +[extra] +image = "ossrox.svg" +website = "https://ossrox.org" +operating_since = "2022" +b2b = true +b2c = true ++++ + +- Hosted Homeservers + +Ossrox is a company from Germany dedicated to hosting open-source services. Not only has the company name been derived from the motto "Open-Source Software Rocks", but also their corporate culture. The three most important principles of the company are security, privacy and sustainability - thanks to open-source software. In addition to Matrix, Ossrox also provides many other solutions e.g. in the messaging, groupware and web meeting segments. diff --git a/content/ecosystem/hosting/ungleich.md b/content/ecosystem/hosting/ungleich.md new file mode 100644 index 000000000..c7638b11e --- /dev/null +++ b/content/ecosystem/hosting/ungleich.md @@ -0,0 +1,16 @@ ++++ +title = "Ungleich.ch" + +[extra] +image = "ungleich_zerocarbonmatrix.jpg" +website = "https://matrix.zerocarbon.shop" +operating_since = "2020" +b2b = false +b2c = true ++++ + +- Hosted Homeservers +- [Bridge solutions: bridging Slack, Telegram, Whatsapp, IRC and more](https://ungleich.ch/en-us/cms/matterbridge-matrix/) +- [Customised Matrix development: Custom Authentication/Login, Integration into private networks](https://ungleich.ch/u/products/matrix-custom-development/) + +Zero Carbon Matrix is provided by ungleich.ch in Switzerland, in their own Datacenter (datacenterlight.ch) with a strong focus on sustainability. Operating within a repurposed historic building, they utilise pre-owned server systems, passive cooling techniques, and 100% renewable energy. ungleich.ch also specialises in customised Matrix development for communities and organisations. From 5e6b2cb981f599c0928b54632e6a5ee95b6250cb Mon Sep 17 00:00:00 2001 From: MTRNord Date: Wed, 4 Mar 2026 08:47:59 +0100 Subject: [PATCH 02/17] redesign client detail and listing pages Replace the old popup overlay with a shared .ecosystem-detail layout: two-column grid (main + features aside), logo wrap, meta row, screenshot panel with CSS-only lightbox and a CTA actions row. The all-clients card deck gains filter dropdowns (platform, maturity, licence, features) backed by a small JS module. Add pill colour families for licence and language, maturity badges and a .title-row helper. Move all .client-card*, .platform-pill* and lightbox rules from _try-matrix.scss into _clients.scss so both pages share one implementation. Enhance .call-to-action with inline-flex and SVG icon handling used by the new CTA buttons. Signed-off-by: MTRNord --- sass/_base.scss | 30 +- sass/_clients.scss | 849 ++++++++++++++++++++++++++++++- sass/_projects.scss | 408 +++++++++------ templates/ecosystem/client.html | 227 ++++++--- templates/ecosystem/clients.html | 98 +++- templates/macros/clients.html | 55 +- 6 files changed, 1381 insertions(+), 286 deletions(-) diff --git a/sass/_base.scss b/sass/_base.scss index 95e1354d3..5e53e6e78 100644 --- a/sass/_base.scss +++ b/sass/_base.scss @@ -291,13 +291,39 @@ a>code { } .call-to-action { + display: inline-flex; + align-items: center; + gap: 0.4em; background-color: #000; border: 2px solid #000; color: #fff; font-size: 1.125rem; font-weight: 600; border-radius: 1000px; - padding: .6em 2em; + padding: 0.6em 2em; + + svg { + width: 1.1em; + height: 1.1em; + flex-shrink: 0; + + .fillme { + fill: currentColor; + stroke: currentColor; + } + + .fillme-inverse { + stroke: #000; + } + } + + &.secondary svg .fillme-inverse { + stroke: #fff; + } + + &.secondary:hover svg .fillme-inverse { + stroke: #000; + } &.inverted { border: 2px solid #fff; @@ -310,7 +336,7 @@ a>code { &:hover { background-color: #fff; - color: #000 + color: #000; } } } diff --git a/sass/_clients.scss b/sass/_clients.scss index 02e4524f3..4227803c7 100644 --- a/sass/_clients.scss +++ b/sass/_clients.scss @@ -1,7 +1,11 @@ +// ── Body lock when a client popup is open (legacy checkbox overlay) ─────────── + body:has(.projects-card-deck > div > .client-checkbox:checked) { overflow: hidden; } +// ── Clients section page layout ─────────────────────────────────────────────── + .clients_page { padding-top: 3rem; @@ -39,23 +43,606 @@ body:has(.projects-card-deck > div > .client-checkbox:checked) { display: flex; flex-direction: row; align-items: flex-start; - justify-content: center; - gap: 16px 4px; + justify-content: flex-start; + gap: 4px; flex-wrap: wrap; } +// ── Platform pills ───────────────────────────────────────────────────────────── +// Colored variants matching the Try Matrix design language. +// Used on both the ecosystem client list and inside
cards. + .platform-pill { - background: #E9E9E9; - border-radius: 16px; - padding: .375rem .625rem; + display: inline-block; + font-size: 0.72rem; + padding: 0.1rem 0.45rem; + border-radius: 9999px; + border: 1px solid; + white-space: nowrap; + font-weight: 500; +} + +.platform-pill--ios { + background: #e8f1ff; + color: #1a4db0; + border-color: #99bbee; +} + +.platform-pill--android { + background: #e6f4ea; + color: #1a6b32; + border-color: #7acc8a; +} + +.platform-pill--web { + background: #ede9fe; + color: #5b21b6; + border-color: #c4b5fd; +} + +.platform-pill--windows { + background: #e6f9f7; + color: #0a6b62; + border-color: #7fd6ce; +} + +.platform-pill--macos { + background: #f3f3f3; + color: #374151; + border-color: #d1d5db; +} + +.platform-pill--linux { + background: #fff4e5; + color: #92400e; + border-color: #fcd9a0; +} + +// ── Audience tags (hosting providers) ──────────────────────────────────────── +// Reused from Try Matrix provider compact cards. + +.audience-tag { + display: inline-block; + font-size: 0.72rem; + padding: 0.1rem 0.4rem; + border-radius: 9999px; + border: 1px solid; + white-space: nowrap; + font-weight: 500; + + &--consumer { + background: #e8f1ff; + color: #1a4db0; + border-color: #99bbee; + } + + &--business { + background: #f3f3f3; + color: #374151; + border-color: #d1d5db; + } +} + +// ── Shared
expand card component ──────────────────────────────────── +// Used on /ecosystem/clients/ (featured section) and /ecosystem/hosting/. +// Mirrors the .client-card design from _try-matrix.scss so both pages share +// the same design language. + +.ecosystem-client-cards, +.hosting-provider-cards { + display: flex; + flex-direction: column; + gap: 0.6rem; + margin-top: 1.25rem; + margin-bottom: 2rem; +} + +// The
card itself +.client-card { + border: 1px solid #d2d2d2; + border-radius: 14px; + overflow: hidden; + transition: border-color 0.2s ease; + + &[open] { + border-color: #aaa; + } - font-style: normal; + &:hover { + border-color: #888; + } +} + +// Summary row — always visible +.client-card-summary { + list-style: none; + cursor: pointer; + padding: 0.85rem 1.1rem; + user-select: none; + + @media (max-width: 600px) { + padding: 0.5rem 0.75rem; + } + + &::-webkit-details-marker { + display: none; + } + + &::marker { + display: none; + } + + &:hover { + background-color: #fafafa; + } +} + +.client-card-summary-inner { + display: grid; + // icon | header text | tags/platforms | chevron + grid-template-columns: 40px 1fr auto 0.75rem; + align-items: center; + gap: 0 0.85rem; + + @media (max-width: 600px) { + grid-template-columns: 36px 1fr 0.75rem; + } + + // Chevron via pseudo-element in the last column + &::after { + content: ""; + display: block; + justify-self: center; + width: 0.55rem; + height: 0.55rem; + border-right: 2px solid #666; + border-bottom: 2px solid #666; + transform: rotate(45deg); + transition: transform 0.2s ease; + } +} + +.client-card[open] .client-card-summary-inner::after { + transform: rotate(-135deg) translateY(-2px); +} + +.client-card-icon { + width: 40px; + height: 40px; + flex-shrink: 0; + border-radius: 8px; + object-fit: contain; + + @media (max-width: 600px) { + width: 100%; + height: auto; + aspect-ratio: 1; + border-radius: 10px; + } +} + +.client-card-header-text { + display: flex; + flex-direction: column; + gap: 0.15rem; + min-width: 0; +} + +.client-card-name { + font-weight: 700; + font-size: 1.05rem; + line-height: 1.2; + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 0.35rem; + + @media (max-width: 600px) { + font-size: 1rem; + } +} + +.client-card-anchor { + font-size: 0.85rem; font-weight: 400; + color: #aaa; + text-decoration: none; + margin-left: 0.1rem; + opacity: 0; + transition: opacity 0.15s ease; + + &:hover { + color: var(--color-link); + } +} + +.client-card-summary:hover .client-card-anchor { + opacity: 1; +} + +.client-card-description-short { + font-size: 0.85rem; + color: #555; + line-height: 1.3; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + + @media (max-width: 600px) { + font-size: 0.8rem; + white-space: normal; + } +} + +// "good for" subtext — used on try-matrix; available here too if needed +.client-card-good-for { + font-size: 0.85rem; + color: #555; + line-height: 1.3; + + @media (max-width: 600px) { + font-size: 0.8rem; + } +} + +.client-card-summary-platforms { + display: flex; + flex-wrap: wrap; + gap: 0.25rem; + justify-content: flex-end; + + @media (max-width: 600px) { + display: none; + } +} + +// ── Expanded card body ──────────────────────────────────────────────────────── + +.client-card-body { + display: grid; + grid-template-columns: 440px minmax(0, 1fr); + gap: 0; + border-top: 1px solid #e8e8e8; + + @media (max-width: 700px) { + grid-template-columns: 1fr; + } +} + +// Screenshot container — always full grid-row height in cards, 16:9 on detail pages +.client-card-screenshot-wrap { + // overflow: hidden is intentionally omitted here so the expand badge + // (::after) is never clipped by the container. Each screenshot trigger + // clips its own image via overflow: hidden instead. + position: relative; + background-color: #f5f5f5; + // Fill the full height of the .client-card-body grid row so the screenshot + // panel always matches the details column, regardless of description length. + // A min-height prevents collapse when the details side is very short. + align-self: stretch; + min-height: 200px; + display: flex; + align-items: center; + justify-content: center; + padding: 0.75rem; + gap: 0.5rem; + border-radius: 4px; + + // Expand badge anchored to the wrap so it is never clipped + &::after { + content: "⤢"; + position: absolute; + bottom: 1rem; + right: 1rem; + font-size: 0.75rem; + line-height: 1; + background: rgba(0, 0, 0, 0.55); + color: #fff; + padding: 0.2rem 0.35rem; + border-radius: 4px; + pointer-events: none; + opacity: 0.85; + z-index: 2; + } +} + +.ecosystem-detail-main .client-card-screenshot-wrap { + // On detail pages use a 16:9 frame. The trigger fills the whole frame and + // the image covers it from the top, so the screenshot is never cut off at + // the top and baked-in bottom whitespace is cropped away naturally. + aspect-ratio: 16 / 9; + max-width: 100%; + border-radius: 10px; + overflow: hidden; + padding: 0; + align-items: stretch; + + // Move the badge inside the rounded corner area + &::after { + bottom: 0.75rem; + right: 0.75rem; + } + + .client-card-screenshot-trigger { + flex: 1; + overflow: hidden; + border-radius: 0; + } + + .client-card-screenshot { + width: 100%; + height: 100%; + max-width: unset; + max-height: unset; + object-fit: cover; + object-position: top center; + border-radius: 0; + } +} + +.client-card-screenshot-trigger { + // Each trigger clips its own image; z-index keeps it above the wrap's + // ::after badge so a hovered trigger can still be a zoom target. + position: relative; + display: block; + cursor: zoom-in; + line-height: 0; + overflow: hidden; + border-radius: 4px; + // Fill the full height of the wrap so the image covers the whole panel. + flex: 1; + align-self: stretch; +} + +.client-card-screenshot { + display: block; + width: 100%; + height: 100%; + object-fit: cover; + object-position: top center; + border-radius: 0; +} + +// Multi-screenshot layout +.client-card-screenshot-wrap--multi { + padding: 0.5rem; + align-items: stretch; + overflow: hidden; + + // Hide the single wrap-level badge; each trigger has its own image + &::after { + display: none; + } + + .client-card-screenshot-trigger { + flex: 1; + min-width: 0; + overflow: hidden; + border-radius: 4px; + + // Per-trigger expand badge for multi-screenshot layouts + &::after { + content: "⤢"; + position: absolute; + bottom: 0.5rem; + right: 0.5rem; + font-size: 0.75rem; + line-height: 1; + background: rgba(0, 0, 0, 0.55); + color: #fff; + padding: 0.2rem 0.35rem; + border-radius: 4px; + pointer-events: none; + opacity: 0.85; + z-index: 2; + } + } + + .client-card-screenshot { + width: 100%; + height: 100%; + max-width: unset; + max-height: unset; + object-fit: cover; + object-position: top center; + border-radius: 0; + } +} + +// ── CSS-only lightbox ───────────────────────────────────────────────────────── + +.client-lightbox { + display: none; + position: fixed; + inset: 0; + z-index: 1000; + align-items: center; + justify-content: center; + + &:target { + display: flex; + } + + img { + position: relative; + z-index: 2; + max-width: min(90vw, 1200px); + max-height: 90vh; + width: auto; + height: auto; + object-fit: contain; + border-radius: 10px; + + margin: 0; + } +} + +.client-lightbox-backdrop { + position: fixed; + inset: 0; + z-index: 1; + background: rgba(0, 0, 0, 0.85); + cursor: zoom-out; +} + +.client-lightbox-close { + position: fixed; + top: 1rem; + right: 1.25rem; + z-index: 3; + font-size: 2rem; + line-height: 1; + color: #fff; + text-decoration: none; + opacity: 0.8; + cursor: pointer; + + &:hover { + opacity: 1; + } +} + +// ── Details column ──────────────────────────────────────────────────────────── + +.client-card-details { + padding: 1.25rem 1.5rem; + display: flex; + flex-direction: column; + gap: 1rem; + + @media (max-width: 700px) { + padding: 0.85rem 1rem; + gap: 0.75rem; + } +} + +.client-card-description { + margin: 0; font-size: 1rem; - line-height: 120%; - color: #000000; + line-height: 1.55; + color: var(--color-text); + + @media (max-width: 700px) { + font-size: 0.9rem; + } +} + +// Feature pills +.client-card-features { + display: flex; + flex-wrap: wrap; + gap: 0.35rem; +} + +.client-feat-pill { + display: inline-block; + padding: 0.2rem 0.6rem; + border-radius: 9999px; + font-size: 0.78rem; + font-weight: 500; + background-color: #e9f0ff; + color: #1a3a80; + border: 1px solid #bcd0f7; + white-space: nowrap; +} + +// Store badge download links row +.client-card-downloads { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; + align-items: center; + + a { + display: inline-block; + line-height: 0; + + img { + height: 36px; + width: auto; + max-width: none; + margin: 0; + border-radius: 6px; + + @media (max-width: 700px) { + height: 30px; + } + } + } +} + +// "Full details →" pill link +.client-card-more-link { + display: inline-block; + align-self: flex-start; + font-size: 0.85rem; + font-weight: 600; + color: var(--color-text); + background-color: #f0f0f0; + border: 1px solid #d2d2d2; + border-radius: 9999px; + padding: 0.3rem 0.9rem; + text-decoration: none; + transition: + background-color 0.15s ease, + border-color 0.15s ease; + + &:hover { + background-color: #e4e4e4; + border-color: #aaa; + } } +// ── Hosting provider card overrides ────────────────────────────────────────── +// The hosting page reuses the same .client-card shell but needs a wider logo +// placeholder instead of a square app icon, and the body is single-column +// (no screenshot panel). + +.hosting-provider-logo { + width: 40px; + height: 40px; + flex-shrink: 0; + display: flex; + align-items: center; + justify-content: center; + + img { + width: 100%; + height: 100%; + object-fit: contain; + border-radius: 4px; + } +} + +// Single-column body (no screenshot slot) +.hosting-provider-body { + grid-template-columns: 1fr !important; +} + +.hosting-provider-description { + font-size: 1rem; + line-height: 1.6; + color: var(--color-text); + + p, + ul, + ol { + margin-top: 0; + margin-bottom: 0.5rem; + } + + ul, + ol { + padding-left: 1.4rem; + } + + a { + color: var(--color-link); + } +} + +// ── Legacy popup overlay (all-clients list) ─────────────────────────────────── + .client-details { @media (max-width: 767px) { grid-template-columns: 1fr; @@ -75,7 +662,7 @@ body:has(.projects-card-deck > div > .client-checkbox:checked) { h3 { display: flex; - gap: .5rem; + gap: 0.5rem; margin: 0 auto; align-items: center; justify-content: center; @@ -89,7 +676,7 @@ body:has(.projects-card-deck > div > .client-checkbox:checked) { } .maturity { - padding-block: .3rem; + padding-block: 0.3rem; } } @@ -101,10 +688,9 @@ body:has(.projects-card-deck > div > .client-checkbox:checked) { .latest-release { margin-bottom: 0 auto; - font-size: .8rem; + font-size: 0.8rem; text-align: center; } - } .project-links { @@ -148,21 +734,21 @@ body:has(.projects-card-deck > div > .client-checkbox:checked) { .client-checkbox { display: none; - &:checked~.client-details-overlay { + &:checked ~ .client-details-overlay { position: fixed; z-index: 150; top: var(--navbar-height); right: 0; bottom: 0; left: 0; - background-color: rgba(0, 0, 0, .5); + background-color: rgba(0, 0, 0, 0.5); } - &:not(:checked)~.client-details-overlay { + &:not(:checked) ~ .client-details-overlay { display: none; } - &:not(:checked)~.client-details { + &:not(:checked) ~ .client-details { display: none; } } @@ -170,7 +756,7 @@ body:has(.projects-card-deck > div > .client-checkbox:checked) { .stores { display: flex; justify-content: center; - gap: .3rem 1rem; + gap: 0.3rem 1rem; flex-wrap: wrap; margin-block: 2rem; @@ -183,3 +769,232 @@ body:has(.projects-card-deck > div > .client-checkbox:checked) { display: block; } } + +// ── Shared ecosystem detail page layout ─────────────────────────────────────── +// Used by client.html and hosting-provider.html (and any future detail pages). + +.ecosystem-detail-page { + padding-top: 3rem; +} + +.ecosystem-detail { + display: grid; + grid-template-columns: 1fr 18rem; + grid-template-rows: auto 1fr; + grid-template-areas: + "header aside" + "main aside"; + column-gap: 2rem; + row-gap: 2rem; + max-width: 60rem; + margin-inline: auto; + padding-bottom: 3rem; + + @media (max-width: 767px) { + grid-template-columns: 1fr; + grid-template-areas: + "header" + "aside" + "main"; + } + + // Single-column variant for pages without a features aside (e.g. hosting) + &--single { + grid-template-columns: 1fr; + grid-template-areas: + "header" + "main"; + + .ecosystem-detail-aside { + display: none; + } + } +} + +.ecosystem-detail-header { + grid-area: header; + display: flex; + flex-direction: row; + align-items: center; + gap: 1.5rem; + + @media (max-width: 600px) { + flex-direction: column; + align-items: flex-start; + gap: 1rem; + } +} + +.ecosystem-detail-main { + grid-area: main; + display: flex; + flex-direction: column; + gap: 1.5rem; +} + +.ecosystem-detail-aside { + grid-area: aside; + position: sticky; + top: calc(var(--navbar-height) + 1.5rem); +} + +.ecosystem-detail-logo-wrap { + flex-shrink: 0; + width: 6rem; + height: 6rem; + display: flex; + align-items: center; + justify-content: center; + border: 1px solid #e8e8e8; + border-radius: 14px; + padding: 0.75rem; + background: #fafafa; + + @media (max-width: 600px) { + width: 5rem; + height: 5rem; + } +} + +.ecosystem-detail-logo { + width: 100%; + height: 100%; + object-fit: contain; + display: block; +} + +.ecosystem-detail-header-text { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.ecosystem-detail-name { + margin: 0; + font-size: 2rem; + font-weight: 700; + line-height: 1.1; + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 0.5rem; +} + +.ecosystem-detail-meta { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 0.25rem 0.75rem; +} + +.ecosystem-detail-meta-item { + font-size: 0.9rem; + color: #666; + + // Dot separator between items + & + &::before { + content: "·"; + margin-right: 0.75rem; + color: #ccc; + } +} + +.ecosystem-detail-description { + margin: 0; + font-size: 1rem; + line-height: 1.65; + color: var(--color-text); + + p { + margin-top: 0; + margin-bottom: 0.75rem; + } + + ul, + ol { + margin-top: 0; + margin-bottom: 0.75rem; + padding-left: 1.4rem; + } + + li { + margin-bottom: 0.25rem; + } + + a { + color: var(--color-link); + } +} + +// Feature aside heading +.ecosystem-detail-aside-title { + font-size: 0.9rem; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.05em; + color: #666; + margin: 0 0 0.6rem 0; +} + +// Feature support list as a definition list +.ecosystem-detail-features { + dl { + border: 1px solid var(--borders-color); + border-radius: 16px; + overflow: hidden; + margin: 0; + } + + .feature-row { + display: flex; + flex-direction: column; + gap: 0.15rem; + padding: 0.6rem 0.85rem; + + & + .feature-row { + border-top: 1px solid #e8e8e8; + } + } + + dt { + display: flex; + align-items: center; + gap: 0.4rem; + font-size: 0.9rem; + font-weight: 600; + line-height: 1.3; + } + + // Tick / cross indicator element + .feature-row-indicator { + display: inline-block; + width: 1em; + height: 1em; + flex-shrink: 0; + background-size: contain; + background-repeat: no-repeat; + background-position: center; + background-image: url("/assets/cross_unsupported.svg"); + } + + .feature-row.supported .feature-row-indicator { + background-image: url("/assets/tick_supported.svg"); + } + + dd { + margin: 0; + // Indent to align with the label text, past the indicator + padding-left: calc(1em + 0.4rem); + font-size: 0.8rem; + color: #666; + line-height: 1.4; + } +} + +// CTA + pill link row at the bottom +.ecosystem-detail-actions { + display: flex; + flex-wrap: wrap; + gap: 0.75rem; + align-items: center; +} diff --git a/sass/_projects.scss b/sass/_projects.scss index a2f7906d7..50c66eb02 100644 --- a/sass/_projects.scss +++ b/sass/_projects.scss @@ -7,7 +7,7 @@ margin-inline: auto; } -.projects-card-deck>div { +.projects-card-deck > div { height: 100%; width: 100%; } @@ -19,102 +19,232 @@ a.project-card { .project-card { display: flex; flex-direction: column; - justify-content: flex-start; - align-items: center; - padding: 24px; - gap: .75rem; + align-items: flex-start; + padding: 1.1rem 1.25rem 1.1rem; + gap: 0; + text-align: left; + min-height: 100%; - border: 1px solid #D2D2D2; + border: 1px solid #d2d2d2; border-radius: 16px; + transition: + border-color 0.15s ease, + box-shadow 0.15s ease; + + &:hover { + border-color: #999; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06); + } + // ── Logo + name header row ──────────────────────────────────────────────── img { - margin: 0; - width: 65px; - height: 65px; - box-sizing: content-box; + margin: 0 0 0.65rem; + width: 44px; + height: 44px; + object-fit: contain; + border-radius: 10px; + border: 1px solid #e8e8e8; + padding: 0.25rem; + background: #fafafa; + box-sizing: border-box; + flex-shrink: 0; } h3 { - margin: 0; + margin: 0 0 0.4rem; font-style: normal; font-weight: 700; - font-size: 1.6rem; - line-height: 1.15; - text-align: center; - overflow-wrap: break-word; - word-break: break-word; - hyphens: auto; + font-size: 1.05rem; + line-height: 1.25; + text-align: left; } - a { - color: var(--color-text-light); - text-decoration: none; + // ── Description ─────────────────────────────────────────────────────────── + p, + & > span { + font-size: 0.85rem; + line-height: 1.45; + color: #555; + margin: 0 0 0.75rem; + flex-shrink: 0; + text-align: left; } - .open-project { - margin: 0; - color: var(--color-text-light); - } - - p { - line-height: 1; - margin: 0; - - &:empty { - display: none; - } - } - - .metadata-pills, + // ── Pills row — always immediately above the CTA ────────────────────────── + .platform-links, .licence-language { display: flex; flex-direction: row; flex-wrap: wrap; + gap: 0.25rem; + margin-top: auto; + margin-bottom: 0.6rem; + align-self: stretch; justify-content: center; - gap: .3rem; } .pill { - background-color: #e9e9e9; - border-radius: 16px; - padding: .2rem .5rem; + display: inline-block; + font-size: 0.75rem; + background-color: #f3f3f3; + border: 1px solid #d1d5db; + border-radius: 9999px; + padding: 0.05rem 0.5rem; color: var(--color-text); - font-size: .85rem; - line-height: 1.2; - text-align: center; - - &.licence { - background-color: transparent; - border: 1px solid #888; - color: #555; - } + line-height: 1.6; + } + + // ── Licence pill colour families ────────────────────────────────────────── + // Permissive (MIT, Apache, ISC, BSD, CC0, Artistic, MIT-Modern-Variant) + .pill--licence-mit, + .pill--licence-mit-modern-variant, + .pill--licence-apache-2-0, + .pill--licence-isc, + .pill--licence-bsd, + .pill--licence-cc0-1-0, + .pill--licence-artistic-2-0 { + background-color: #e6f4ea; + border-color: #7acc8a; + color: #1a6b32; } - .subtitle { - font-size: .9rem; - color: var(--color-text-light); - text-align: center; + // Strong copyleft (GPL family, AGPL) + .pill--licence-gpl-1-0-or-later, + .pill--licence-gpl-3-0, + .pill--licence-gpl-3-0-only, + .pill--licence-gpl-3-0-or-later, + .pill--licence-agpl-3-0, + .pill--licence-agpl-3-0-only, + .pill--licence-agpl-3-0-or-later { + background-color: #fff4e5; + border-color: #fcd9a0; + color: #92400e; } - .description { - font-size: .9rem; - line-height: 1.4; - text-align: center; - margin: 0; - display: -webkit-box; - -webkit-line-clamp: 4; - -webkit-box-orient: vertical; - overflow: hidden; + // Weak copyleft (LGPL, MPL, EUPL) + .pill--licence-lgpl-2-1-only, + .pill--licence-lgpl-3-0-only, + .pill--licence-lgpl-3-0-or-later, + .pill--licence-mpl-2-0, + .pill--licence-eupl-1-2 { + background-color: #e8f1ff; + border-color: #99bbee; + color: #1a4db0; + } - * { - margin: 0; - padding: 0; - } + // Unknown / no licence + .pill--licence-none { + background-color: #f3f3f3; + border-color: #d1d5db; + color: #555; + } - &:empty { - display: none; - } + // ── Language pill colour families ───────────────────────────────────────── + // Systems (C, C++, Rust) + .pill--lang-c, + .pill--lang-cplusplus, + .pill--lang-rust { + background-color: #fff4e5; + border-color: #fcd9a0; + color: #92400e; + } + + // JVM (Java, Kotlin) + .pill--lang-java, + .pill--lang-kotlin { + background-color: #ede9fe; + border-color: #c4b5fd; + color: #5b21b6; + } + + // JavaScript / TypeScript + .pill--lang-javascript, + .pill--lang-typescript { + background-color: #fefce8; + border-color: #fde68a; + color: #854d0e; + } + + // Python + .pill--lang-python { + background-color: #e8f1ff; + border-color: #99bbee; + color: #1a4db0; + } + + // Go + .pill--lang-go { + background-color: #e6f9f7; + border-color: #7fd6ce; + color: #0a6b62; + } + + // Apple (Swift, Objective-C) + .pill--lang-swift, + .pill--lang-objective-c { + background-color: #fff0eb; + border-color: #fdb99a; + color: #9a3412; + } + + // Dynamic / functional (Ruby, Perl, Raku, Erlang, Elixir, Lisp, Racket) + .pill--lang-ruby, + .pill--lang-perl, + .pill--lang-raku, + .pill--lang-erlang, + .pill--lang-elixir, + .pill--lang-lisp, + .pill--lang-racket { + background-color: #fce7f3; + border-color: #f9a8d4; + color: #9d174d; + } + + // Web / cross-platform (PHP, Dart, Elm, Haxe) + .pill--lang-php, + .pill--lang-dart, + .pill--lang-elm, + .pill--lang-haxe { + background-color: #e6f4ea; + border-color: #7acc8a; + color: #1a6b32; + } + + // Shell / scripting (POSIX Shell, Nim) + .pill--lang-posix-shell, + .pill--lang-nim { + background-color: #f3f3f3; + border-color: #d1d5db; + color: #374151; + } + + // C# (separate from C/C++ — .NET ecosystem) + .pill--lang-csharp { + background-color: #eef0fb; + border-color: #8892d4; + color: #2c3894; + } + + // ── CTA label pinned to the bottom ──────────────────────────────────────── + // The whole card is already a link — this is a visual affordance only, + // not an interactive element, so it carries no hover state of its own. + a.open-project, + .open-project { + display: flex; + align-items: center; + justify-content: center; + align-self: stretch; + margin-top: 0; + font-size: 0.8rem; + font-weight: 600; + color: #555; + background-color: transparent; + border: 1px solid #d1d5db; + border-radius: 9999px; + padding: 0.2rem 0.75rem; + text-decoration: none; + pointer-events: none; } } @@ -139,10 +269,10 @@ a.project-card { display: flex; flex-direction: row; justify-content: space-between; - gap: .8rem; + gap: 0.8rem; z-index: 100; - padding: .5rem .8rem; + padding: 0.5rem 0.8rem; background-color: #fafafa; color: #000; @@ -159,7 +289,7 @@ a.project-card { } img { - height: .9em; + height: 0.9em; margin-block: auto; } @@ -186,7 +316,7 @@ a.project-card { max-width: 20rem; z-index: 200; - padding: .5rem 1rem; + padding: 0.5rem 1rem; background-color: #fafafa; border: 1px solid var(--borders-color); @@ -194,8 +324,8 @@ a.project-card { p { margin: 0; - margin-bottom: .5rem; - font-size: .8rem; + margin-bottom: 0.5rem; + font-size: 0.8rem; text-align: start; } @@ -210,7 +340,7 @@ a.project-card { } .reset-links { - font-size: .8rem; + font-size: 0.8rem; a { cursor: pointer; @@ -233,36 +363,37 @@ a.project-card { } .maturity { - border: 1px solid var(--borders-color); + border: 1px solid; border-radius: 9999px; - padding: 0 .6rem; - font-size: 1rem; - font-weight: 400; + padding: 0.1rem 0.45rem; + font-size: 0.72rem; + font-weight: 500; + line-height: 1.6; max-width: fit-content; justify-self: flex-end; &.stable { - background-color: #DFF6D5; - border-color: #245C0C; - color: #245C0C + background-color: #dff6d5; + border-color: #245c0c; + color: #245c0c; } &.beta { - background-color: #F6E7D5; - border-color: #6F420F; - color: #6F420F; + background-color: #f6e7d5; + border-color: #6f420f; + color: #6f420f; } &.alpha { - background-color: #F6D5D5; - border-color: #8B1212; - color: #8B1212; + background-color: #f6d5d5; + border-color: #8b1212; + color: #8b1212; } &.obsolete { - background-color: #E6E6E6; - border-color: #4A4A4A; - color: #4A4A4A; + background-color: #e6e6e6; + border-color: #4a4a4a; + color: #4a4a4a; } } @@ -270,19 +401,9 @@ a.project-card { width: 100%; max-width: 100%; display: flex; - flex-direction: column; - align-items: center; - gap: .3rem; - - h3 { - text-align: center; - } -} - -.card-links { - margin-top: auto; - display: flex; - flex-direction: row; + justify-content: space-between; + gap: 0.2rem; + align-items: start; flex-wrap: wrap; justify-content: center; gap: .5rem; @@ -296,7 +417,7 @@ a.project-card { bottom: 0; z-index: 100; - background-color: rgba(0, 0, 0, .5); + background-color: rgba(0, 0, 0, 0.5); &:not(.display) { display: none; @@ -306,45 +427,48 @@ a.project-card { .project-links { display: flex; flex-wrap: wrap; - gap: .3rem; + gap: 0.3rem; margin-bottom: 1rem; +} - .pill { - display: flex; - align-items: center; - gap: .2rem; +a.pill { + display: inline-flex; + align-items: center; + gap: 0.2rem; - border: 1px solid var(--color-text); - color: var(--color-text); - border-radius: 9999px; - padding: .1rem .4rem; + border: 1px solid var(--color-text); + color: var(--color-text); + border-radius: 9999px; + padding: 0.1rem 0.4rem; + text-decoration: none; - transition: background-color 0.3s ease; + svg { + flex-shrink: 0; + width: 1em; + height: 1em; + } - .fillme { - fill: var(--color-text); - stroke: var(--color-text); - transition: fill 0.3s ease, stroke 0.3s ease; - } + .fillme { + fill: var(--color-text); + stroke: var(--color-text); + } - .fillme-inverse { - stroke: #fff; - transition: stroke 0.3s ease; - } + .fillme-inverse { + stroke: #fff; + } - &:hover { - background-color: var(--color-text); - color: #fff; + &:hover { + background-color: var(--color-text); + color: #fff; - .fillme { - fill: #fff; - stroke: #fff; - } + .fillme { + fill: #fff; + stroke: #fff; + } - .fillme-inverse { - stroke: var(--color-text); - } + .fillme-inverse { + stroke: var(--color-text); } } } @@ -356,24 +480,16 @@ a.project-card { padding-inline: 1rem; li { - padding: .5rem; + padding: 0.5rem; line-height: 1.6; list-style-position: inside; - list-style-image: url('/assets/question_unknown.svg'); - - &.unsupported { - list-style-image: url('/assets/cross_unsupported.svg'); - } - - &.partial { - list-style-image: url('/assets/flask_partial.svg'); - } + list-style-image: url("/assets/cross_unsupported.svg"); &.supported { - list-style-image: url('/assets/tick_supported.svg'); + list-style-image: url("/assets/tick_supported.svg"); } - &+li { + & + li { border-top: 1px solid #d2d2d2; } } diff --git a/templates/ecosystem/client.html b/templates/ecosystem/client.html index 427554bcf..2fae0cf13 100644 --- a/templates/ecosystem/client.html +++ b/templates/ecosystem/client.html @@ -1,7 +1,11 @@ {% extends "page.html" %} {% import "macros/clients.html" as clients %} +{% import "macros/breadcrumb.html" as breadcrumb %} {% block head_extra %} -", to="") | replace(from="

", to="") }}"> + {% endblock head_extra %} {% block content %} {% if page.extra.thumbnail %} @@ -9,85 +13,180 @@ {% else %} {% set thumbnail = "client-placeholder.svg" %} {% endif %} -
-
-
- -
-

{{ page.title }}
{{ page.extra.maturity }} -
-

+
+ {{ breadcrumb::breadcrumb(l1_label="Ecosystem", l1_path="/ecosystem/", l2_label="Clients", l2_path="/ecosystem/clients/", l3_label=page.title) }} +
+
+
+
-

{{ page.content | markdown | safe }}

- + + -

Features

-
- {% for category, features in page.extra.features %} -
-
{{ clients::category_label(category_name=category) }}
-
    - {% for feature, supported in features %} -
  • - - {{ clients::feature_label(feature_name=feature) | trim }} - - -
    - {{ clients::feature_description(feature_name=feature) | trim }} -
    -
  • - {% endfor %} -
+ + +
+ {{ clients::feature_description(feature_name=feature) | trim }} +
+ + {% endfor %} + +
+ {% endfor %}
- {% endfor %} -
-

Something wrong? Report the issue here.

+
{% endblock content %} diff --git a/templates/ecosystem/clients.html b/templates/ecosystem/clients.html index d97537256..e48b50c09 100644 --- a/templates/ecosystem/clients.html +++ b/templates/ecosystem/clients.html @@ -1,14 +1,19 @@ {% extends "section.html" %} {% import "macros/clients.html" as clients %} +{% import "macros/breadcrumb.html" as breadcrumb %} {% block head_extra %} + {% endblock head_extra%} {% block content %} {% set clients = section.pages %} +{% set featured_clients = clients | filter(attribute="extra.featured", value=true) | sort(attribute="extra.featured_order") %} {% set licences = [] %} {% set features = [] %} {% for client in clients %} @@ -22,6 +27,7 @@ {% set licences = licences | unique | sort %} {% set features = features | unique %}
{% endblock content %} diff --git a/templates/ecosystem/hosting.html b/templates/ecosystem/hosting.html index 693511b4e..ab43426af 100644 --- a/templates/ecosystem/hosting.html +++ b/templates/ecosystem/hosting.html @@ -56,5 +56,6 @@

{{ provider.title }}

{% endfor %} {% endif %} +

Something missing? Report the issue here.

{% endblock content %} From 14db4ea9e4d230ee46eec3e337cf3c93ee5bf4b2 Mon Sep 17 00:00:00 2001 From: MTRNord Date: Thu, 9 Jul 2026 20:16:41 +0200 Subject: [PATCH 14/17] fix scroll behaviour Signed-off-by: MTRNord --- templates/ecosystem/clients.html | 2 +- templates/ecosystem/hosting.html | 6 ------ templates/try-matrix.html | 2 +- 3 files changed, 2 insertions(+), 8 deletions(-) diff --git a/templates/ecosystem/clients.html b/templates/ecosystem/clients.html index 48350e94a..6a14db1f3 100644 --- a/templates/ecosystem/clients.html +++ b/templates/ecosystem/clients.html @@ -8,7 +8,7 @@ {% endblock head_extra%} {% block content %} diff --git a/templates/ecosystem/hosting.html b/templates/ecosystem/hosting.html index ab43426af..6ada6ec86 100644 --- a/templates/ecosystem/hosting.html +++ b/templates/ecosystem/hosting.html @@ -2,12 +2,6 @@ {% import "macros/breadcrumb.html" as breadcrumb %} {% block head_extra %} - {% endblock head_extra %} {% block content %}