diff --git a/content/ecosystem/hosting/index.md b/content/ecosystem/hosting/_index.md similarity index 68% rename from content/ecosystem/hosting/index.md rename to content/ecosystem/hosting/_index.md index 91bbf3c15..16326a732 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. @@ -9,8 +11,8 @@ Everyone can host their Matrix server, but not everybody wants to. These are the ## Why not matrix.org? -Matrix could be described as what happens when email, instant messaging and -encryption meet. To be able to use Matrix, you need a Matrix account which is +[Matrix](/docs/matrix-concepts/elements-of-matrix/) could be described as what happens when email, instant messaging and +encryption meet. To be able to use Matrix, you need a [Matrix account](/docs/chat_basics/matrix-for-im/#creating-a-matrix-account) which is provided by... a provider! The Matrix accounts identifiers look like `@username:example.com`. People on different providers can talk to one another, in the same way you can send emails to someone using another email provider. @@ -24,13 +26,10 @@ or WhatsApp. **Organisations and community managers** will be interested in providers for two main reasons: they can proudly display their identity with their own domain -in username and rooms identifiers, and their administrators remain in control -of the data of their organisation. +in username and rooms identifiers when [getting started with a community](/docs/communities/getting-started/), +and their administrators remain in control of the data of their organisation +through [room administration](/docs/communities/administration/) tools. 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. 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/_bridges.scss b/sass/_bridges.scss index 5c4fa24da..9e14f5c1a 100644 --- a/sass/_bridges.scss +++ b/sass/_bridges.scss @@ -4,68 +4,264 @@ .platforms-grid { display: grid; - grid-template-columns: repeat(auto-fill, 17.6rem); - gap: .8rem; + grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr)); + gap: 0.75rem; margin-block: 2rem 3rem; margin-inline: auto; justify-content: center; +} + +.platform { + @extend .clickable-card; + + display: flex; + flex-direction: row; + align-items: center; + gap: 1rem; + padding: 0.85rem 1.1rem; + + color: var(--color-text); + text-decoration: none; + + border: 1px solid #d2d2d2; + border-radius: 16px; - .platform { - @extend .clickable-card; + img { + width: 52px; + height: 52px; + flex-shrink: 0; + object-fit: contain; + box-sizing: border-box; + } + .details { display: flex; - flex-direction: row; - gap: 1rem; - align-items: center; - padding-block: .5rem; - padding-inline: .5rem 1rem; - cursor: pointer; + flex-direction: column; + gap: 0.15rem; + min-width: 0; - color: var(--color-text); + h2 { + font-size: 1.2rem; + font-weight: 700; + margin: 0; + line-height: 1.2; + } - border: 1px solid #d2d2d2; - border-radius: 12px; - transition: border-color 0.3s ease, background-color 0.3s ease; + p { + margin: 0; + font-size: 0.9rem; + color: #555; + line-height: 1.3; + } + } +} - img { - width: 64px; - height: 64px; +.platform.no-bridge { + opacity: 0.35; + pointer-events: none; - border: 1px solid #d2d2d2; - border-radius: 15px; + img { + filter: grayscale(1); + } +} + +// ── Bridge network detail page ──────────────────────────────────────────────── + +.bridge-network-header { + max-width: 60rem; + margin-inline: auto; + padding-top: 1rem; + margin-bottom: 2rem; +} + +.bridge-network-back { + align-self: flex-start; +} + +.bridge-cards { + max-width: 60rem; + margin-inline: auto; + padding-bottom: 3rem; + display: flex; + flex-direction: column; + gap: 1.5rem; +} + +.bridge-card { + border: 1px solid #d2d2d2; + border-radius: 16px; + overflow: hidden; +} + +.bridge-card-report { + margin: 0; + padding: 0.85rem 1.5rem; + border-top: 1px solid #e8e8e8; + font-size: 0.85rem; + color: var(--color-text-light); +} + +.bridge-card-header { + padding: 1.25rem 1.5rem 1rem; + border-bottom: 1px solid #e8e8e8; + display: flex; + flex-direction: column; + gap: 0.4rem; +} + +.bridge-card-title-row { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 0.5rem 1rem; +} + +.bridge-card-name { + margin: 0; + font-size: 1.35rem; + font-weight: 700; + line-height: 1.2; + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 0.5rem; +} + +.bridge-card-anchor { + color: inherit; + text-decoration: none; + + &:hover { + text-decoration: underline; + } +} + +.bridge-card-recommended { + display: inline-block; + font-size: 0.78rem; + font-weight: 600; + color: #245c0c; + background-color: #dff6d5; + border: 1px solid #245c0c; + border-radius: 9999px; + padding: 0.1rem 0.55rem; + line-height: 1.6; +} + +.bridge-card-access { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 0.35rem; + margin-top: 0.1rem; +} + +.bridge-card-access-label { + font-size: 0.8rem; + font-weight: 600; + color: #666; + text-transform: uppercase; + letter-spacing: 0.03em; + margin-right: 0.15rem; +} + +.bridge-card-access-pill { + display: inline-flex; + align-items: center; + font-size: 0.8rem; + border-radius: 9999px; + padding: 0.1rem 0.6rem; + line-height: 1.6; + border: 1px solid; + + // Platform side — muted indigo/blue + &--platform { + background-color: #eef0fb; + border-color: #8892d4; + color: #2c3894; + } + + // Matrix side — Matrix green + &--matrix { + background-color: #dff6d5; + border-color: #4a9e2f; + color: #245c0c; + } +} + +.bridge-card-grid { + display: grid; + grid-template-columns: 1fr 15rem; + + @media (max-width: 700px) { + grid-template-columns: 1fr; + } +} + +.bridge-card-left { + // Left column holds both the header and main — the aside spans full height + display: flex; + flex-direction: column; + min-width: 0; +} + +.bridge-card-main { + padding: 1.25rem 1.5rem; + display: flex; + flex-direction: column; + gap: 1rem; + flex: 1; +} + +.bridge-card-features { + background-color: #fafafa; + border-left: 1px solid #e8e8e8; + padding: 1.25rem 1rem; + @media (max-width: 700px) { + border-left: none; + border-top: 1px solid #e8e8e8; + background-color: #fafafa; + } + + .ecosystem-detail-features { + dl { + border: none; + border-radius: 0; } - .details { - display: flex; - flex-direction: column; + .feature-row { + gap: 0.1rem; + padding: 0.4rem 0.5rem; + border-radius: 8px; - h2 { - font-size: 1.5rem; - font-weight: 700; - margin: 0; - line-height: 1.2; + & + .feature-row { + border-top: none; } - p { - margin: 0; - font-size: .875rem; - color: #545454; + &.supported { + background-color: transparent; + } + + &.unsupported { + opacity: 0.5; } } - &:hover { - background-color: #e9e9e9; - border-color: #000; + dt { + font-size: 0.85rem; + } + + dd { + font-size: 0.75rem; + padding-left: calc(1em + 0.4rem); + color: #666; } } } -.platform.no-bridge { - opacity: 0.15; - pointer-events: none; -} +// ── Legacy styles (kept for any other pages still referencing them) ──────────── -.bridges_implem> :nth-child(even) { +.bridges_implem > :nth-child(even) { background-color: #fafafa; } @@ -87,23 +283,19 @@ } .title-row { - display: flex; - flex-direction: row; align-items: center; justify-content: flex-start; - gap: .5rem; - width: auto; + gap: 0.5rem; h3 { - margin-block: .5rem; - text-align: left; + margin-block: 1rem; } } .recommended-chip { display: flex; align-items: center; - gap: .2rem; + gap: 0.2rem; max-width: fit-content; font-style: normal; border-bottom: 1px dotted var(--borders-color); @@ -113,23 +305,23 @@ .licence { display: flex; flex-wrap: wrap; - gap: .5rem; - margin-block: .5rem; + gap: 0.5rem; + margin-block: 0.5rem; margin-bottom: 1rem; } - .privileges>div, - .licence>div { + .privileges > div, + .licence > div { display: flex; flex-direction: row; flex-wrap: wrap; - gap: .5rem; + gap: 0.5rem; div, a { background-color: #e9e9e9; border-radius: 9999px; - padding-inline: .5rem; + padding-inline: 0.5rem; color: var(--color-text); } } diff --git a/sass/_clients.scss b/sass/_clients.scss index 02e4524f3..9bc19f9de 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,604 @@ 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 by the featured_client_card() macro on /try-matrix/ and the +// featured section of /ecosystem/clients/. + +.client-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; + } + + &: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; + } - font-style: normal; + &::-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 +660,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 +674,7 @@ body:has(.projects-card-deck > div > .client-checkbox:checked) { } .maturity { - padding-block: .3rem; + padding-block: 0.3rem; } } @@ -101,10 +686,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 +732,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 +754,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 +767,277 @@ 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-report { + max-width: 60rem; + margin-inline: auto; + padding-top: 1.5rem; + padding-bottom: 3rem; + border-top: 1px solid var(--borders-color); + font-size: 0.9rem; + color: var(--color-text-light); +} + +.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: 1.5rem; + + @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); + + // Stacked single-column layout on mobile — sticky here would float + // the aside mid-scroll between the header and main content. + @media (max-width: 767px) { + position: static; + } +} + +.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.4rem; + 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, grouped by category +.ecosystem-detail-features { + .features-category { + & + .features-category { + margin-top: 1rem; + } + } + + .features-category-title { + font-size: 0.8rem; + font-weight: 700; + color: #666; + margin: 0 0 0.4rem; + } + + 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; + } + + // Support-state 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/question_unknown.svg"); + } + + .feature-row.unsupported .feature-row-indicator { + background-image: url("/assets/cross_unsupported.svg"); + } + + .feature-row.partial .feature-row-indicator { + background-image: url("/assets/flask_partial.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; + + // These sit inline in a detail-page sidebar, not as hero CTAs — + // shrink the otherwise-global .call-to-action sizing to fit. + .call-to-action { + font-size: 0.9rem; + font-weight: 500; + padding: 0.4em 1.1em; + } +} diff --git a/sass/_distributions.scss b/sass/_distributions.scss index 648b031ea..c83be5ee4 100644 --- a/sass/_distributions.scss +++ b/sass/_distributions.scss @@ -1,70 +1,20 @@ .distributions { p { - text-align: center; - font-weight: 400; - font-size: 1.125rem; - line-height: 120%; - margin: 0 auto 4rem auto; + @extend %ecosystem-page-intro; } } .distribution-links { - margin-top: auto; - display: flex; - flex-direction: row; - flex-wrap: wrap; - justify-content: center; - gap: .7rem; - - a { - display: flex; - align-items: center; - gap: .3rem; - - border: 1px solid #777; - border-radius: 999px; - padding-inline: .5rem; - - font-size: .8rem; - - svg { - width: 1.2em; - height: 1.2em; - - * { - fill: #777; - } - - line { - stroke: #777; - } - } - } - - a:hover { - color: #fff; - background-color: #777; - - svg { - - * { - fill: #fff; - } - - line { - stroke: #fff; - } - } - } + @extend %eco-link-pills; } .standard { border: 1px solid var(--borders-color); border-radius: 9999px; - padding: 0 .6rem; + padding: 0 0.6rem; font-weight: 600; justify-self: flex-end; - background-color: #F6D5D5; - border-color: #8B1212; - color: #8B1212; + background-color: #f6d5d5; + border-color: #8b1212; + color: #8b1212; } diff --git a/sass/_hosting.scss b/sass/_hosting.scss index d5b4d3759..a55584545 100644 --- a/sass/_hosting.scss +++ b/sass/_hosting.scss @@ -1,9 +1,8 @@ +// ── Hosting section page ────────────────────────────────────────────────────── + .hosting_page { margin: 0 auto; - padding-block: 5.625rem 2rem; - @media (max-width: 767px) { - padding: 5.625rem 32px 2rem 32px; - } + padding-top: 3rem; h2 { text-align: center; @@ -12,57 +11,97 @@ margin-bottom: 1rem; } - .hosting_providers { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); - gap: 3.125rem; - justify-content: center; - align-items: stretch; + > p { + text-align: left; + font-weight: 400; + font-size: 1.125rem; + line-height: 1.6; + margin: 0 0 1rem 0; + } +} + +// ── Provider listing grid ───────────────────────────────────────────────────── +// Mirrors the .projects-card-deck pattern used by clients. + +.hosting-provider-cards { + display: grid; + grid-template-columns: repeat(auto-fill, 17.6rem); + justify-content: center; + gap: 1rem; + margin-inline: auto; + margin-bottom: 5rem; + + @media (max-width: 767px) { + max-width: 17.6rem; + } +} + +.hosting-provider-cards > div { + height: 100%; + width: 100%; +} + +.hosting-provider-card { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 1.5rem; + gap: 0.75rem; + min-height: 100%; + + border: 1px solid #d2d2d2; + border-radius: 16px; + color: var(--color-text); + text-decoration: none; + transition: border-color 0.2s ease; + + &:hover { + border-color: #888; + } + + .hosting-provider-logo-container { width: 100%; + height: 3.5rem; + display: flex; + align-items: center; + justify-content: center; + } + + img { + height: 3.5rem; + width: auto; + max-width: 100%; + object-fit: contain; + margin: 0; + } + + h3 { + margin: 0; + font-size: 1.3rem; + font-weight: 700; + text-align: center; + line-height: 1.2; + } + + .provider-since { + margin: 0; + font-size: 0.85rem; + color: #666; + line-height: 1; + } + + .provider-audience-tags { + display: flex; + flex-wrap: wrap; + gap: 0.3rem; + justify-content: center; + } - .provider_card { - display: flex; - flex-direction: column; - justify-content: space-between; - - padding: 32px; - width: 100%; - margin: 0 auto; - - border: 1px solid #d2d2d2; - border-radius: 16px; - - .details { - text-align: center; - - .provider_logo_container { - height: 10rem; - } - - h3 { - height: 5rem; - margin: 0; - text-align: center; - - font-style: normal; - font-weight: 700; - font-size: 2rem; - line-height: 120%; - } - - .operating_since { - height: 1.5rem; - font-size: 0.9rem; - } - } - - .call-to-action { - background-color: #000; - color: #fff; - border-radius: 1000px; - padding: 0.4rem 2rem; - align-self: center; - } - } + .open-project { + margin-top: auto; + color: var(--color-text-light); + font-size: 0.9rem; + line-height: 1; } } diff --git a/sass/_projects.scss b/sass/_projects.scss index a2f7906d7..18a10a842 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%; } @@ -22,99 +22,195 @@ a.project-card { justify-content: flex-start; align-items: center; padding: 24px; - gap: .75rem; + gap: 0.75rem; + text-align: center; + min-height: 100%; - border: 1px solid #D2D2D2; + border: 1px solid #d2d2d2; border-radius: 16px; + // ── Logo + name header row ──────────────────────────────────────────────── img { margin: 0; - width: 65px; - height: 65px; - box-sizing: content-box; + width: 52px; + height: 52px; + object-fit: contain; + box-sizing: border-box; + flex-shrink: 0; } h3 { margin: 0; font-style: normal; font-weight: 700; - font-size: 1.6rem; - line-height: 1.15; + font-size: 1.2rem; + line-height: 1.25; text-align: center; - overflow-wrap: break-word; - word-break: break-word; - hyphens: auto; - } - - a { - color: var(--color-text-light); - text-decoration: none; } - .open-project { + // ── Description ─────────────────────────────────────────────────────────── + p, + & > span { + font-size: 0.9rem; + line-height: 1.45; + color: #555; margin: 0; - color: var(--color-text-light); - } - - p { - line-height: 1; - margin: 0; - - &:empty { - display: none; - } + flex-shrink: 0; + text-align: center; } - .metadata-pills, - .licence-language { + // ── Pills row — always immediately above the CTA ────────────────────────── + .platform-links, + .pill-row { display: flex; flex-direction: row; flex-wrap: wrap; + gap: 0.25rem; + margin-top: auto; + align-self: stretch; justify-content: center; - gap: .3rem; } .pill { background-color: #e9e9e9; + border: 1px solid transparent; border-radius: 16px; - padding: .2rem .5rem; + padding: 0.2rem 0.5rem; color: var(--color-text); - font-size: .85rem; + font-size: 0.85rem; line-height: 1.2; text-align: center; &.licence { background-color: transparent; - border: 1px solid #888; + border-color: #888; color: #555; } } - .subtitle { - font-size: .9rem; - color: var(--color-text-light); - text-align: center; + // ── 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; } - .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; - - * { - margin: 0; - padding: 0; - } + // JVM (Java, Kotlin) + .pill--lang-java, + .pill--lang-kotlin { + background-color: #ede9fe; + border-color: #c4b5fd; + color: #5b21b6; + } - &:empty { - display: none; - } + // 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; + } + + // ── Deployment framework pill colour families ───────────────────────────── + // Kubernetes ecosystem (Helm, Helmfile, Kubernetes) + .pill--framework-helm, + .pill--framework-helmfile, + .pill--framework-kubernetes { + background-color: #e6f9f7; + border-color: #7fd6ce; + color: #0a6b62; + } + + // Containers (Docker, Docker Compose) + .pill--framework-docker, + .pill--framework-docker-compose { + background-color: #e8f1ff; + border-color: #99bbee; + color: #1a4db0; + } + + // Config management (Ansible) + .pill--framework-ansible { + background-color: #fde8e8; + border-color: #f0a8a8; + color: #a61b1b; + } + + // ── 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 { + margin-top: auto; + color: var(--color-text-light); + font-size: 0.9rem; + line-height: 1; + text-decoration: none; + pointer-events: none; } } @@ -139,10 +235,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 +255,7 @@ a.project-card { } img { - height: .9em; + height: 0.9em; margin-block: auto; } @@ -186,7 +282,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 +290,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 +306,7 @@ a.project-card { } .reset-links { - font-size: .8rem; + font-size: 0.8rem; a { cursor: pointer; @@ -233,36 +329,37 @@ a.project-card { } .maturity { - border: 1px solid var(--borders-color); + display: inline-block; + border: 1px solid; border-radius: 9999px; - padding: 0 .6rem; - font-size: 1rem; - font-weight: 400; + padding: 0.1rem 0.45rem; + font-size: 0.85rem; + 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; } } @@ -272,22 +369,13 @@ a.project-card { display: flex; flex-direction: column; align-items: center; - gap: .3rem; + gap: 0.3rem; h3 { text-align: center; } } -.card-links { - margin-top: auto; - display: flex; - flex-direction: row; - flex-wrap: wrap; - justify-content: center; - gap: .5rem; -} - #filters-overlay { position: fixed; top: var(--navbar-height); @@ -296,7 +384,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 +394,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,26 +447,160 @@ 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'); + list-style-image: url("/assets/cross_unsupported.svg"); - &.unsupported { - list-style-image: url('/assets/cross_unsupported.svg'); + &.supported { + list-style-image: url("/assets/tick_supported.svg"); } - &.partial { - list-style-image: url('/assets/flask_partial.svg'); + & + li { + border-top: 1px solid #d2d2d2; } + } + } +} - &.supported { - list-style-image: url('/assets/tick_supported.svg'); +// ── Breadcrumb navigation ───────────────────────────────────────────────────── +// Used on all ecosystem pages. Two contexts: +// 1. Inside .page-header (dark background) — text is white/muted-white +// 2. Inside .ecosystem-detail-page (light background) — text is muted grey + +.breadcrumb { + ol { + display: flex; + flex-wrap: wrap; + align-items: center; + list-style: none; + margin: 0; + padding: 0; + gap: 0.25rem; + font-size: 0.85rem; + } + + li { + display: flex; + align-items: center; + + // Chevron separator before every item except the first + & + li::before { + content: "/"; + margin-right: 0.25rem; + opacity: 0.45; + } + } + + a, + span { + text-decoration: none; + line-height: 1; + } + + // ── Dark context (inside .page-header) ─────────────────────────────────── + .page-header & { + margin-bottom: 0.75rem; + + a { + color: rgba(255, 255, 255, 0.7); + + &:hover { + color: #fff; } + } - &+li { - border-top: 1px solid #d2d2d2; + span { + color: rgba(255, 255, 255, 0.45); + } + + li + li::before { + color: rgba(255, 255, 255, 0.45); + } + } + + // ── Light context (inside .ecosystem-detail-page) ──────────────────────── + .ecosystem-detail-page & { + margin-bottom: 1.25rem; + + a { + color: #555; + + &:hover { + color: var(--color-text); } } + + span { + color: #aaa; + } + + li + li::before { + color: #ccc; + } + } +} + +// ── Shared ecosystem intro paragraph ───────────────────────────────────────── +// Used by .servers > p and .distributions > p (and any future listing pages) +// for the centred intro text that sits below the page heading. + +%ecosystem-page-intro { + text-align: center; + font-weight: 400; + font-size: 1.125rem; + line-height: 120%; + margin: 0 auto 4rem auto; +} + +// ── Shared ecosystem link-pill row ──────────────────────────────────────────── +// Wrapping flex row of pill-shaped links used by .server-links and +// .distribution-links. Extend this placeholder to get the full set of rules +// without duplicating them. + +%eco-link-pills { + margin-top: auto; + display: flex; + flex-direction: row; + flex-wrap: wrap; + justify-content: center; + gap: 0.35rem; + + a { + display: inline-flex; + align-items: center; + gap: 0.25rem; + + border: 1px solid var(--borders-color); + border-radius: 9999px; + padding: 0.1rem 0.5rem; + + font-size: 0.78rem; + color: var(--color-text); + text-decoration: none; + transition: + background-color 0.15s ease, + border-color 0.15s ease, + color 0.15s ease; + + svg { + width: 1em; + height: 1em; + flex-shrink: 0; + + * { + fill: currentColor; + } + + line { + stroke: currentColor; + } + } + } + + a:hover { + background-color: var(--color-text); + border-color: var(--color-text); + color: #fff; } } diff --git a/sass/_servers.scss b/sass/_servers.scss index 849744f85..cebddcbd3 100644 --- a/sass/_servers.scss +++ b/sass/_servers.scss @@ -1,63 +1,9 @@ .servers { p { - text-align: center; - font-weight: 400; - font-size: 1.125rem; - line-height: 120%; - margin: 0 auto 4rem auto; + @extend %ecosystem-page-intro; } } .server-links { - margin-top: auto; - display: flex; - flex-direction: row; - flex-wrap: wrap; - justify-content: center; - gap: .7rem; - - a { - display: flex; - align-items: center; - gap: .3rem; - - border: 1px solid #777; - border-radius: 999px; - padding-inline: .5rem; - - font-size: .8rem; - - transition: background-color 0.3s ease; - - svg { - width: 1.2em; - height: 1.2em; - - * { - fill: #777; - transition: fill 0.3s ease; - } - - line { - stroke: #777; - transition: stroke 0.3s ease; - } - } - } - - a:hover { - color: #fff; - background-color: #777; - - svg { - - * { - fill: #fff; - } - - line { - stroke: #fff; - } - } - } + @extend %eco-link-pills; } diff --git a/sass/_try-matrix.scss b/sass/_try-matrix.scss index cc05e6741..0fa33a837 100644 --- a/sass/_try-matrix.scss +++ b/sass/_try-matrix.scss @@ -160,7 +160,10 @@ } } - // ── Client cards (details/summary dropdowns) ────────────────────────────── + // ── Client cards ────────────────────────────────────────────────────────── + // All .client-card*, .platform-pill*, .client-lightbox* and + // .client-card-screenshot* rules live globally in _clients.scss. + // Only the cards container margin differs on this page. .client-cards { display: flex; @@ -170,420 +173,6 @@ 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 { @@ -844,24 +433,10 @@ justify-content: center; } + // .audience-tag colours are defined globally in _clients.scss; + // only override the font-size for the compact carousel context. .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; - } } } diff --git a/templates/ecosystem/bridge_implementations.html b/templates/ecosystem/bridge_implementations.html index 0f5d3f8e7..0024b5eb3 100644 --- a/templates/ecosystem/bridge_implementations.html +++ b/templates/ecosystem/bridge_implementations.html @@ -1,71 +1,166 @@ {% extends "section.html" %} +{% import "macros/bridges.html" as bridges %} +{% import "macros/breadcrumb.html" as breadcrumb %} {% block content %} - +{% set bridges_data = load_data(path=section.path~"bridges.toml") %} -
- {% set bridges_data = load_data(path=section.path~"bridges.toml") %} - {% for bridge in bridges_data.bridges %} -
-
-
- {% if bridge.featured %}{% endif %} -
-

{{ bridge.name }}

-
{{ bridge.maturity | title }}
-
-

{{ bridge.summary | markdown(inline=true) | safe }}

- {% endblock content %} diff --git a/templates/ecosystem/bridges.html b/templates/ecosystem/bridges.html index 1f16f8548..522cf6071 100644 --- a/templates/ecosystem/bridges.html +++ b/templates/ecosystem/bridges.html @@ -1,9 +1,11 @@ {% extends "section.html" %} +{% import "macros/breadcrumb.html" as breadcrumb %} {% block head_extra %} {% endblock head_extra %} {% block content %}