diff --git a/CONTENT.md b/CONTENT.md index d44fb09949..195846b5d0 100644 --- a/CONTENT.md +++ b/CONTENT.md @@ -31,7 +31,6 @@ transparent = true render = false insert_anchor_links = "left" +++ - ``` Once this structure is in place, you need to write the actual blog post. It is a single text file ending with the `.md` extension. There are two sections in the file: a _frontmatter_ containing all the metadata (e.g. title of the post, date of publication, author, section), and the content itself. @@ -100,9 +99,9 @@ In your markdown file, add this line to embed the YouTube player in a way that r {{ youtube_player(video_id="S1nBXjWWHoU") }} ``` -You can also provide a start time using `start` and noscript-text using `noscript_text`, e.g. +You can also provide a start time using `start` and noscript-text using `noscript_text`, e.g. -```jinja +```jinja {{ youtube_player(video_id="Xje32fIIUyg",start="1240",noscript_text="Matrix Live S11E05 - Project Hydra") }} ``` @@ -144,6 +143,12 @@ matrix_room = "#your-matrix-room:example.com" licence = "PICK ONE identifier from https://spdx.org/licenses/" latest_release = "2022-11-02" featured = false +# Used with featured = true to have a fixed order. +# featured_order = 1 +# Screenshots are expected to be in the same folder +screenshots = [] +# Should describe the target user group +good_for = "Useful for users coming from another platform" [extra.features] e2ee = false @@ -170,15 +175,15 @@ Supercharge your communications with Example Client. ``` - For the `maturity`, please pick one of - - `Stable` if your software has been running in production for a little while and if you are confident in the sustainability of the project - - `Beta` if there are rough edges but people won't lose any data - - `Alpha` if your software is still experimental - - `Obsolete` if your software is no longer maintained + - `Stable` if your software has been running in production for a little while and if you are confident in the sustainability of the project + - `Beta` if there are rough edges but people won't lose any data + - `Alpha` if your software is still experimental + - `Obsolete` if your software is no longer maintained - For the `licence`, please use [one of the identifiers listed by the SPDX](https://spdx.org/licenses/) - `featured` should be left to false. We are working on [processes to formalise which project should be featured or not](https://github.com/matrix-org/matrix.org/issues/1584). - For `extra.features`, see the descriptions in [clients.html](/templates/macros/clients.html) - All of the properties under `extra.packages` are optional: only add the installation methods your project supports! - - In case your option is not available please let us know by opening an issue. + - In case your option is not available please let us know by opening an issue. ### Bridges diff --git a/content/ecosystem/clients/cinny-screenshot.avif b/content/ecosystem/clients/cinny-screenshot.avif new file mode 100644 index 0000000000..c76ba81125 Binary files /dev/null and b/content/ecosystem/clients/cinny-screenshot.avif differ diff --git a/content/ecosystem/clients/cinny-screenshot.xcf b/content/ecosystem/clients/cinny-screenshot.xcf new file mode 100644 index 0000000000..0d24c6976e Binary files /dev/null and b/content/ecosystem/clients/cinny-screenshot.xcf differ diff --git a/content/ecosystem/clients/cinny.md b/content/ecosystem/clients/cinny.md index 10b3ca26c1..dc0118414d 100644 --- a/content/ecosystem/clients/cinny.md +++ b/content/ecosystem/clients/cinny.md @@ -1,5 +1,6 @@ +++ title = "Cinny" + [extra] thumbnail = "cinny.svg" maintainer = "Ajay Bura" @@ -11,6 +12,10 @@ repo = "https://github.com/cinnyapp/cinny" website = "https://cinny.in" matrix_room = "#cinny:matrix.org" featured = true +featured_order = 4 +screenshots = ["cinny-screenshot.avif"] +good_for = "Users coming from Discord who want a familiar layout" + [extra.features] e2ee = true spaces = true diff --git a/content/ecosystem/clients/element-web-screenshot.avif b/content/ecosystem/clients/element-web-screenshot.avif new file mode 100644 index 0000000000..c258d43345 Binary files /dev/null and b/content/ecosystem/clients/element-web-screenshot.avif differ diff --git a/content/ecosystem/clients/element-web-screenshot.xcf b/content/ecosystem/clients/element-web-screenshot.xcf new file mode 100644 index 0000000000..ac3d6ebca5 Binary files /dev/null and b/content/ecosystem/clients/element-web-screenshot.xcf differ diff --git a/content/ecosystem/clients/element-x-screenshot-roomlist.avif b/content/ecosystem/clients/element-x-screenshot-roomlist.avif new file mode 100644 index 0000000000..40cee91e35 Binary files /dev/null and b/content/ecosystem/clients/element-x-screenshot-roomlist.avif differ diff --git a/content/ecosystem/clients/element-x-screenshot-timeline.avif b/content/ecosystem/clients/element-x-screenshot-timeline.avif new file mode 100644 index 0000000000..4fbed422eb Binary files /dev/null and b/content/ecosystem/clients/element-x-screenshot-timeline.avif differ diff --git a/content/ecosystem/clients/element-x-screenshot.xcf b/content/ecosystem/clients/element-x-screenshot.xcf new file mode 100644 index 0000000000..8127d3612d Binary files /dev/null and b/content/ecosystem/clients/element-x-screenshot.xcf differ diff --git a/content/ecosystem/clients/element-x.md b/content/ecosystem/clients/element-x.md index 65b163f170..4365864f76 100644 --- a/content/ecosystem/clients/element-x.md +++ b/content/ecosystem/clients/element-x.md @@ -10,6 +10,9 @@ matrix_room = "#element-x-ios:matrix.org" licence = "AGPL-3.0-or-later OR Element Commercial License" latest_release = "2025-09-23" featured = true +featured_order = 2 +screenshots = ["element-x-screenshot-roomlist.avif", "element-x-screenshot-timeline.avif"] +good_for = "Mobile-first users who want a fast, modern experience" [extra.features] e2ee = true diff --git a/content/ecosystem/clients/element.md b/content/ecosystem/clients/element.md index ce0831c9f0..0a8c7930e8 100644 --- a/content/ecosystem/clients/element.md +++ b/content/ecosystem/clients/element.md @@ -10,6 +10,10 @@ matrix_room = "#element-web:matrix.org" licence = "AGPL-3.0 OR GPL-3.0 OR Element Commercial License" latest_release = "2025-09-23" featured = true +featured_order = 1 +screenshots = ["element-web-screenshot.avif"] +good_for = "New users and those coming from Slack or Teams" + [extra.features] e2ee = true spaces = true diff --git a/content/ecosystem/clients/fluffychat-screenshot-roomlist.avif b/content/ecosystem/clients/fluffychat-screenshot-roomlist.avif new file mode 100644 index 0000000000..9bbe8e5e98 Binary files /dev/null and b/content/ecosystem/clients/fluffychat-screenshot-roomlist.avif differ diff --git a/content/ecosystem/clients/fluffychat-screenshot-timeline.avif b/content/ecosystem/clients/fluffychat-screenshot-timeline.avif new file mode 100644 index 0000000000..2b21c85662 Binary files /dev/null and b/content/ecosystem/clients/fluffychat-screenshot-timeline.avif differ diff --git a/content/ecosystem/clients/fluffychat-screenshot.xcf b/content/ecosystem/clients/fluffychat-screenshot.xcf new file mode 100644 index 0000000000..59c598781f Binary files /dev/null and b/content/ecosystem/clients/fluffychat-screenshot.xcf differ diff --git a/content/ecosystem/clients/fluffychat.md b/content/ecosystem/clients/fluffychat.md index 102fe8c8d8..46ded6b5eb 100644 --- a/content/ecosystem/clients/fluffychat.md +++ b/content/ecosystem/clients/fluffychat.md @@ -12,6 +12,10 @@ repo = "https://github.com/krille-chan/fluffychat" website = "https://fluffychat.im" matrix_room = "#fluffychat:matrix.org" featured = true +featured_order = 3 +screenshots = ["fluffychat-screenshot-roomlist.avif", "fluffychat-screenshot-timeline.avif"] +good_for = "Users who want something simple and approachable, especially on mobile" + [extra.features] e2ee = true spaces = true diff --git a/content/ecosystem/clients/nheko-screenshot.avif b/content/ecosystem/clients/nheko-screenshot.avif new file mode 100644 index 0000000000..7bee08071a Binary files /dev/null and b/content/ecosystem/clients/nheko-screenshot.avif differ diff --git a/content/ecosystem/clients/nheko-screenshot.xcf b/content/ecosystem/clients/nheko-screenshot.xcf new file mode 100644 index 0000000000..ddae4619b5 Binary files /dev/null and b/content/ecosystem/clients/nheko-screenshot.xcf differ diff --git a/content/ecosystem/clients/nheko.md b/content/ecosystem/clients/nheko.md index 44fbec0806..a8420d4ece 100644 --- a/content/ecosystem/clients/nheko.md +++ b/content/ecosystem/clients/nheko.md @@ -1,5 +1,6 @@ +++ title = "Nheko" + [extra] thumbnail = "nheko.svg" maintainer = "red_sky, deepbluev7" @@ -11,6 +12,10 @@ repo = "https://github.com/Nheko-Reborn/nheko" website = "https://nheko-reborn.github.io/" matrix_room = "#nheko:nheko.im" featured = true +featured_order = 5 +screenshots = ["nheko-screenshot.avif"] +good_for = "Power users and tinkerers who prefer a lean native desktop app" + [extra.features] e2ee = true spaces = true diff --git a/content/ecosystem/hosting/providers.toml b/content/ecosystem/hosting/providers.toml index a83cbeef92..e5160b106c 100644 --- a/content/ecosystem/hosting/providers.toml +++ b/content/ecosystem/hosting/providers.toml @@ -8,6 +8,8 @@ description = """ For information about different hosting plans, please check our [Matrix service webpage](https://communick.com/services/matrix). """ +b2b = false +b2c = true [[providers]] name = "fairkom" @@ -27,6 +29,8 @@ description = """ 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) """ +b2b = true +b2c = false [[providers]] name = "Ungleich.ch" @@ -45,6 +49,8 @@ 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. """ +b2b = false +b2c = true [[providers]] name = "etke.cc" @@ -65,6 +71,8 @@ etke.cc is the only fully FOSS-based Matrix Hosting service. It's operated by a [Matrix Space](https://matrix.to/#/#service:etke.cc) | [Announcements on Matrix](https://matrix.to/#/#news:etke.cc) """ +b2b = true +b2c = true [[providers]] name = "Ossrox" @@ -76,6 +84,8 @@ description = """ 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. """ +b2b = true +b2c = true [[providers]] name = "Federated Computer" @@ -87,6 +97,8 @@ description = """ 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). """ +b2b = true +b2c = true [[providers]] name = "IndieHosters" @@ -99,6 +111,8 @@ description = """ We are a French collective specialized in hosting FOSS software and aimed at building a digital common, with a particular emphasis on collaboration. """ +b2b = true +b2c = false [[providers]] name = "Datanauten" @@ -111,3 +125,5 @@ description = """ The Datanauten Matrix Homeserver offers an easy entry into the open and interoperable communication system of the future. """ +b2b = true +b2c = true diff --git a/content/try-matrix.md b/content/try-matrix.md index 12580f38e0..d8e23c8e22 100644 --- a/content/try-matrix.md +++ b/content/try-matrix.md @@ -2,8 +2,10 @@ title = "Try Matrix" template = "try-matrix.html" extra.summary = "Join the Matrix ecosystem!" +# Set to true to show the Free/Premium plan comparison for matrix.org accounts +extra.matrixorg_premium = true extra.meta_description = """ -To try Matrix, pick a client, pick a server, register an account and start -chatting! +Get started with Matrix: choose an app, create a free account on any provider, +and start chatting. Open source, end-to-end encrypted, works on all platforms. """ +++ diff --git a/sass/_try-matrix.scss b/sass/_try-matrix.scss index 32b368c387..cc05e6741c 100644 --- a/sass/_try-matrix.scss +++ b/sass/_try-matrix.scss @@ -1,3 +1,17 @@ +.hide-on-mobile { + @media (max-width: 767px) { + display: none !important; + } +} + +.show-primary-on-mobile { + @media (max-width: 767px) { + background-color: #000 !important; + color: #fff !important; + border-color: #000 !important; + } +} + .try_matrix { display: flex; flex-direction: column; @@ -12,6 +26,14 @@ &.coloured { background-color: #fafafa; } + + &.quickstart { + min-height: calc( + 100svh - var(--page-header-height) - var(--navbar-height) + ); + display: flex; + align-items: center; + } } .step { @@ -31,17 +53,17 @@ padding-block: 1rem; - h3 { - font-size: 2.2rem; + h2 { + font-size: clamp(1.7rem, 5vw, 2.2rem); margin-top: 0; } p { - font-size: 1.3rem; + font-size: clamp(1rem, 2.5vw, 1.1rem); } } - img { + > img { max-height: 220px; max-width: 360px; @@ -54,10 +76,802 @@ } } + .step.no-image { + grid-template-columns: 1fr; + + .instructions { + max-width: 60rem; + margin-inline: auto; + // Explicit width so child grids have a known container width + width: 100%; + } + } + .cta-wrapper { display: flex; flex-wrap: wrap; gap: 1rem; justify-content: center; + margin-top: 1.5rem; + + .call-to-action { + @media (max-width: 767px) { + font-size: clamp(0.85rem, 4vw, 1.125rem); + text-align: center; + } + } + } + + // ── Quick-start (TLDR) block ────────────────────────────────────────────── + + .quickstart-instructions { + text-align: center; + align-items: center; + .desktop-only { + display: block; + } + .mobile-only { + display: none; + } + + h2 { + font-size: clamp(1.6rem, 6vw, 2.8rem); + margin-bottom: 0.5rem; + } + + > p { + max-width: 42rem; + font-size: clamp(1rem, 2.5vw, 1.1rem); + } + + @media (max-width: 767px) { + .desktop-only { + display: none; + } + .mobile-only { + display: block; + } + } + } + + .scroll-down-indicator { + display: inline-block; + margin-top: 1.5rem; + + img { + width: 2rem; + height: 2rem; + opacity: 0.45; + animation: scroll-bounce 2s ease-in-out infinite; + } + + &:hover img { + opacity: 0.75; + } + } + + @keyframes scroll-bounce { + 0%, + 100% { + transform: translateY(0); + } + 50% { + transform: translateY(6px); + } + } + + // ── Client cards (details/summary dropdowns) ────────────────────────────── + + .client-cards { + display: flex; + flex-direction: column; + gap: 0.6rem; + margin-top: 1.25rem; + margin-bottom: 0.5rem; + } + + .client-card { + border: 1px solid #d2d2d2; + border-radius: 14px; + overflow: hidden; + transition: border-color 0.2s ease; + + &[open] { + border-color: #aaa; + } + + &: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; + } + + // Remove default browser marker + &::-webkit-details-marker { + display: none; + } + &::marker { + display: none; + } + + &:hover { + background-color: #fafafa; + } + } + + .client-card-summary-inner { + display: grid; + // icon | header text | 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; + + @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.3rem; + opacity: 0; + transition: opacity 0.15s ease; + + &:hover { + color: var(--color-link); + } + } + + .client-card-summary:hover .client-card-anchor { + opacity: 1; + } + + .client-card-good-for { + font-size: 0.95rem; + color: #555; + line-height: 1.3; + + @media (max-width: 600px) { + font-size: 0.875rem; + } + } + + .client-card-summary-platforms { + display: flex; + flex-wrap: wrap; + gap: 0.25rem; + justify-content: center; + + @media (max-width: 600px) { + display: none; + } + } + + // Expanded 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 column — always 4:3 + .client-card-screenshot-wrap { + background-color: #f5f5f5; + aspect-ratio: 4 / 3; + overflow: hidden; + display: flex; + align-items: center; + justify-content: center; + padding: 0.75rem; + gap: 0.5rem; + } + + .client-card-screenshot-trigger { + position: relative; + display: block; + cursor: zoom-in; + line-height: 0; + + // Expand badge — visible in bottom-right corner on all devices + &::after { + content: "⤢"; + position: absolute; + bottom: 0.4rem; + right: 0.4rem; + font-size: 1.1rem; + line-height: 1; + background: rgba(0, 0, 0, 0.55); + color: #fff; + padding: 0.3rem 0.5rem; + border-radius: 4px; + pointer-events: none; + opacity: 0.85; + z-index: 1; + } + } + + .client-card-screenshot { + display: block; + max-width: 100%; + max-height: 100%; + width: auto; + height: auto; + object-fit: contain; + object-position: center; + border-radius: 4px; + } + + // Multi-screenshot layout — images sit side by side, each fills its slot + // with cover so the 4:3 container is always full + .client-card-screenshot-wrap--multi { + padding: 0.5rem; + align-items: stretch; + + .client-card-screenshot-trigger { + flex: 1; + min-width: 0; + overflow: hidden; + border-radius: 4px; + } + + .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 via :target ───────────────────────────────────────── + + .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; + box-shadow: 0 8px 40px rgba(0, 0, 0, 0.6); + 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; + } + } + + .client-card-screenshot-placeholder { + span { + font-size: 0.85rem; + color: #999; + font-style: italic; + } + } + + // Details column (description, features, downloads) + .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: 1.55; + color: var(--color-text); + + @media (max-width: 700px) { + font-size: 0.9rem; + } + } + + // Feature pills inside the card + .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: 44px; + } + } + } + } + + .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; + } + } + + // Platform pill colours (used inside summary platforms row) + .platform-pill { + 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; + } + + // ── Account tiers (Create an account section) ───────────────────────────── + + .account-tiers { + display: flex; + flex-direction: column; + gap: 1.25rem; + margin-top: 1.5rem; + } + + .account-tier { + border: 1px solid #d2d2d2; + border-radius: 16px; + padding: 1.5rem 2rem; + + @media (max-width: 767px) { + padding: 1rem; + } + + // Description paragraph + > p { + margin-top: 0.5rem; + margin-bottom: 1rem; + } + + // Direct CTA (when no plan cards shown) + > .call-to-action { + display: inline-block; + } + + .account-tier-header { + display: flex; + align-items: center; + gap: 0.75rem; + + h3 { + margin: 0; + font-size: 1.4rem; + font-weight: 700; + } + } + } + + .tier-badge { + border-radius: 9999px; + padding: 0.15rem 0.7rem; + font-size: 0.85rem; + font-weight: 500; + white-space: nowrap; + + &.foundation { + background-color: #e9e9e9; + border: 1px solid #888; + color: #333; + } + + &.commercial { + background-color: #e9e9e9; + border: 1px solid #888; + color: #333; + } + + &.community { + background-color: #d5e8f6; + border: 1px solid #0c3c5c; + color: #0c3c5c; + } + } + + // ── matrix.org Free / Premium plan cards ───────────────────────────────── + + .matrixorg-plans { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 1rem; + + @media (max-width: 767px) { + grid-template-columns: 1fr; + } + + .plan-card { + display: flex; + flex-direction: column; + gap: 0.75rem; + padding: 1.25rem 1.5rem; + border: 1px solid #d2d2d2; + border-radius: 12px; + + @media (max-width: 767px) { + border-radius: 0; + border-left: 0; + border-right: 0; + .call-to-action { + font-size: clamp(0.85rem, 4vw, 1.125rem); + } + } + + &.featured { + border-color: #245c0c; + background-color: #f7fff5; + } + + h4 { + margin: 0; + font-size: 1.1rem; + font-weight: 700; + } + + ul { + margin: 0; + padding-left: 1.25rem; + flex: 1; + + li { + font-size: 1rem; + line-height: 1.7; + } + } + + .call-to-action { + align-self: stretch; + text-align: center; + } + } + } + + // ── Provider marquee ────────────────────────────────────────────────────── + // + // Each .provider-compact is independently animated with a staggered delay + // so cards stream from right to left with no HTML duplication. + // Approach based on: https://developer.chrome.com/blog/infinite-marquee + // + // --items is a fallback only; the correct count is injected at build time + // by Zola as an inline style on the .providers-carousel element, so this + // value only applies if the inline style is somehow missing. + + .providers-carousel { + --items: 6; + --carousel-duration: 30s; + --item-width: 190px; + --item-gap: 1rem; + + position: relative; + height: 11rem; + overflow: clip; + margin-bottom: 0.25rem; + + mask-image: linear-gradient( + to right, + transparent, + black 6%, + black 94%, + transparent + ); + -webkit-mask-image: linear-gradient( + to right, + transparent, + black 6%, + black 94%, + transparent + ); + + cursor: grab; + &:active { + cursor: grabbing; + } + + // Pause all cards on carousel hover + &:hover .provider-compact { + animation-play-state: paused; + } + } + + @keyframes providers-marquee { + to { + transform: translateX( + calc(var(--items) * (var(--item-width) + var(--item-gap)) * -1) + ); + } + } + + // ── Compact provider cards (managed hosting tier) ───────────────────────── + + .provider-compact { + position: absolute; + top: 0; + left: calc(100% + var(--item-gap)); + width: var(--item-width); + height: 100%; + + // Stagger each card evenly across one full duration cycle + animation: providers-marquee var(--carousel-duration) linear infinite; + animation-delay: calc( + var(--carousel-duration) / var(--items) * var(--i) * -1 + ); + + &:nth-child(1) { + --i: 0; + } + &:nth-child(2) { + --i: 1; + } + &:nth-child(3) { + --i: 2; + } + &:nth-child(4) { + --i: 3; + } + &:nth-child(5) { + --i: 4; + } + &:nth-child(6) { + --i: 5; + } + + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 0.5rem; + padding: 1.25rem 1rem; + + border: 1px solid #d2d2d2; + border-radius: 16px; + + color: var(--color-text); + text-decoration: none; + transition: + border-color 0.3s ease, + background-color 0.3s ease; + + &:hover { + border-color: #000; + background-color: #f5f5f5; + } + + img { + height: 3rem; + width: 100%; + object-fit: contain; + } + + .provider-name { + font-size: 1rem; + font-weight: 600; + text-align: center; + line-height: 1.2; + } + + .provider-since { + font-size: 0.8rem; + color: #666; + } + + .provider-audience { + display: flex; + flex-wrap: wrap; + gap: 0.2rem; + justify-content: center; + } + + .audience-tag { + font-size: 0.7rem; + padding: 0.1rem 0.4rem; + border-radius: 9999px; + border: 1px solid; + white-space: nowrap; + + &--consumer { + background: #e8f1ff; + color: #1a4db0; + border-color: #99bbee; + } + + &--business { + background: #f3f3f3; + color: #374151; + border-color: #d1d5db; + } + } + } + + .homeserver-info-link { + margin-top: 1rem; + font-size: 1rem !important; + text-align: right; + + a { + color: var(--color-text-light); + } } } diff --git a/static/js/providers-carousel.js b/static/js/providers-carousel.js new file mode 100644 index 0000000000..f457d10007 --- /dev/null +++ b/static/js/providers-carousel.js @@ -0,0 +1,133 @@ +/* + * SPDX-FileCopyrightText: 2026 The Matrix.org Foundation C.I.C. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +(function () { + const carousel = document.querySelector(".providers-carousel"); + if (!carousel) return; + + const items = [...carousel.querySelectorAll(".provider-compact")]; + const NUM_ITEMS = items.length; + const DRAG_THRESHOLD = 5; // px of movement before treating as drag, not click + + function getCSSNumber(prop) { + const raw = getComputedStyle(carousel).getPropertyValue(prop).trim(); + // Strip known units: s, px, rem, em + return parseFloat(raw); + } + + function getCSSPx(prop) { + const raw = getComputedStyle(carousel).getPropertyValue(prop).trim(); + if (raw.endsWith("rem")) { + const remPx = parseFloat( + getComputedStyle(document.documentElement).fontSize, + ); + return parseFloat(raw) * remPx; + } + if (raw.endsWith("em")) { + const emPx = parseFloat(getComputedStyle(carousel).fontSize); + return parseFloat(raw) * emPx; + } + // px or bare number + return parseFloat(raw); + } + + function getDurationS() { + // --carousel-duration is in seconds (e.g. "30s") + return getCSSNumber("--carousel-duration"); + } + + function getItemCyclePx() { + return getCSSPx("--item-width") + getCSSPx("--item-gap"); + } + + function baseDelay(i) { + return -(getDurationS() / NUM_ITEMS) * i; + } + + // Both wheel and drag feed into a single pixel offset; updating the + // animation-delay of each card shifts its phase without pausing anything. + let totalOffset = 0; + + function applyOffset() { + const durationS = getDurationS(); + const totalCyclePx = NUM_ITEMS * getItemCyclePx(); + const timePerPx = durationS / totalCyclePx; + items.forEach((item, i) => { + item.style.animationDelay = `${baseDelay(i) - totalOffset * timePerPx}s`; + }); + } + + // ── Mouse wheel / trackpad ────────────────────────────────────────────── + + carousel.addEventListener( + "wheel", + (e) => { + e.preventDefault(); + totalOffset += e.deltaX !== 0 ? e.deltaX : e.deltaY; + applyOffset(); + }, + { passive: false }, + ); + + // ── Drag (mouse + touch) ──────────────────────────────────────────────── + + let dragging = false; + let hasDragged = false; + let dragStartX = 0; + let offsetAtDragStart = 0; + + function onDragStart(clientX) { + dragging = true; + hasDragged = false; + dragStartX = clientX; + offsetAtDragStart = totalOffset; + } + + function onDragMove(clientX) { + if (!dragging) return; + const dx = clientX - dragStartX; + if (Math.abs(dx) > DRAG_THRESHOLD) hasDragged = true; + // Dragging left advances the carousel (same feel as scrolling down) + totalOffset = offsetAtDragStart - dx; + applyOffset(); + } + + function onDragEnd() { + dragging = false; + } + + // Swallow click events that were actually drag gestures + carousel.addEventListener( + "click", + (e) => { + if (hasDragged) { + e.preventDefault(); + e.stopPropagation(); + hasDragged = false; + } + }, + true, + ); + + carousel.addEventListener("mousedown", (e) => { + onDragStart(e.clientX); + e.preventDefault(); + }); + window.addEventListener("mousemove", (e) => onDragMove(e.clientX)); + window.addEventListener("mouseup", () => onDragEnd()); + + carousel.addEventListener( + "touchstart", + (e) => onDragStart(e.touches[0].clientX), + { passive: true }, + ); + carousel.addEventListener( + "touchmove", + (e) => onDragMove(e.touches[0].clientX), + { passive: true }, + ); + carousel.addEventListener("touchend", () => onDragEnd()); +})(); diff --git a/templates/macros/clients.html b/templates/macros/clients.html index 4b4b18675e..aa6de0d6e9 100644 --- a/templates/macros/clients.html +++ b/templates/macros/clients.html @@ -128,10 +128,10 @@ {% endmacro %} {% macro platforms(packages) %} -{% if packages.apple_app_store %}
iOS
{% endif %} -{% if packages.google_play_store or packages.f_droid %}
Android
{% endif %} -{% if packages.windows_installer %}
Windows
{% endif %} -{% if packages.macos_installer %}
macOS
{% endif %} -{% if packages.flathub or packages.other_linux_link %}
Linux
{% endif %} -{% if packages.webapp %}
Web
{% endif %} +{% if packages.apple_app_store %}
iOS
{% endif %} +{% if packages.google_play_store or packages.f_droid %}
Android
{% endif %} +{% if packages.windows_installer %}
Windows
{% endif %} +{% if packages.macos_installer %}
macOS
{% endif %} +{% if packages.flathub or packages.other_linux_link %}
Linux
{% endif %} +{% if packages.webapp %}
Web
{% endif %} {% endmacro %} diff --git a/templates/try-matrix.html b/templates/try-matrix.html index c0682cfdf6..3e59b41557 100644 --- a/templates/try-matrix.html +++ b/templates/try-matrix.html @@ -1,5 +1,14 @@ {% extends "page.html" %} +{% import "macros/clients.html" as clients %} +{% block head_extra %} + +{% endblock head_extra %} {% block content %} +{% set clients_section = get_section(path="ecosystem/clients/_index.md") %} +{% set featured_clients = clients_section.pages | filter(attribute="extra.featured", value=true) | sort(attribute="extra.featured_order") %} +{% set providers_data = load_data(path="content/ecosystem/hosting/providers.toml") %}