You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@apisix.apache.org by we...@apache.org on 2026/06/22 06:23:35 UTC
(apisix-website) branch feat/seo-homepage-rewrite created (now 3a7f7dd6a3a)
This is an automated email from the ASF dual-hosted git repository.
moonming pushed a change to branch feat/seo-homepage-rewrite
in repository https://gitbox.apache.org/repos/asf/apisix-website.git
at 3a7f7dd6a3a feat(seo): rewrite homepage for CTR + funnel to docs/pillars/plugins
This branch includes the following new commits:
new 3a7f7dd6a3a feat(seo): rewrite homepage for CTR + funnel to docs/pillars/plugins
The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
(apisix-website) 01/01: feat(seo): rewrite homepage for CTR + funnel to docs/pillars/plugins
Posted by we...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
moonming pushed a commit to branch feat/seo-homepage-rewrite
in repository https://gitbox.apache.org/repos/asf/apisix-website.git
commit 3a7f7dd6a3a74ff756b38a21223ad4552f3b69ce
Author: Ming Wen <mo...@gmail.com>
AuthorDate: Mon Jun 22 14:23:24 2026 +0800
feat(seo): rewrite homepage for CTR + funnel to docs/pillars/plugins
- Add 'Trusted by teams everywhere' auto-scrolling logo marquee (pure-CSS
transform, hover-pause, reduced-motion safe; logos pluggable via CDN).
- Add 'Integrates with your stack' grid reusing the real plugin-sprite icons
(Prometheus, Datadog, SkyWalking, Zipkin, Kafka, gRPC, OIDC, Keycloak...).
- Add intent 'Pathways' cards routing to docs, learning center, and plugin hub.
- Slim the hero: drop the WebGL HeroCanvas (load-speed win), add eyebrow,
second CTA, and a trust-stats row; center the text layout.
- Compose: hero -> trusted-by -> pathways -> product -> integrations -> ...
---
website/src/components/sections/HeroSection.tsx | 70 ++++++-------
website/src/components/sections/Integrations.tsx | 95 +++++++++++++++++
website/src/components/sections/Pathways.tsx | 67 ++++++++++++
website/src/components/sections/TrustedBy.tsx | 80 +++++++++++++++
website/src/css/landing-sections/hero.scss | 114 ++++++++++++++-------
website/src/css/landing-sections/integrations.scss | 97 ++++++++++++++++++
website/src/css/landing-sections/pathways.scss | 62 +++++++++++
website/src/css/landing-sections/trusted-by.scss | 89 ++++++++++++++++
website/src/pages/index.tsx | 6 ++
9 files changed, 603 insertions(+), 77 deletions(-)
diff --git a/website/src/components/sections/HeroSection.tsx b/website/src/components/sections/HeroSection.tsx
index f30f108b146..3dab2641758 100644
--- a/website/src/components/sections/HeroSection.tsx
+++ b/website/src/components/sections/HeroSection.tsx
@@ -2,61 +2,49 @@ import type { FC } from 'react';
import React from 'react';
import Link from '@docusaurus/Link';
import Translate from '@docusaurus/Translate';
-
-import BrowserOnly from '@docusaurus/BrowserOnly';
-import useWindowType from '@theme/hooks/useWindowSize';
import useBaseUrl from '@docusaurus/useBaseUrl';
-import ArrowAnim from '../ArrowAnim';
import '../../css/landing-sections/hero.scss';
-const LazyLoadHeroCanvas = () => {
- const windowType = useWindowType();
- if (windowType === 'mobile') return null;
-
- return (
- <BrowserOnly>
- {() => {
- // eslint-disable-next-line @typescript-eslint/no-var-requires, global-require
- const HeroCanvas = require('../HeroCanvas').default;
- return <HeroCanvas />;
- }}
- </BrowserOnly>
- );
-};
+const STATS: { value: string; label: JSX.Element }[] = [
+ { value: '100+', label: <Translate id="hero.stats.plugins">plugins</Translate> },
+ { value: '~18k', label: <Translate id="hero.stats.qps">QPS / core</Translate> },
+ { value: '0.2 ms', label: <Translate id="hero.stats.latency">added latency</Translate> },
+ { value: 'Apache 2.0', label: <Translate id="hero.stats.license">licensed</Translate> },
+];
const HeroSection: FC = () => (
- <div className="hero-sec-wrap" style={{ width: '100%' }}>
+ <header className="hero-sec-wrap">
<div className="hero-text">
+ <span className="hero-eyebrow">
+ <Translate id="hero.component.eyebrow">Apache Software Foundation top-level project</Translate>
+ </span>
<h1 className="hero-title">
- <Translate id="hero.component.title.fragment1">API Gateway & AI Gateway for APIs and AI Agents</Translate>
+ <Translate id="hero.component.title.main">The open-source API Gateway & AI Gateway</Translate>
</h1>
- <h2
- className="hero-subtitle"
- style={{
- color: '#E8433E', fontSize: 32, fontWeight: 700, lineHeight: 1.2,
- }}
- >
- <Translate id="hero.component.title.fragment2">
- Open-Source, Community-Driven, Future-Ready
- </Translate>
- </h2>
- <h3 className="hero-subtitle">
- <Translate id="hero.component.subtitle.content">
- APISIX API Gateway provides rich traffic management features like load balancing, dynamic
- upstream, canary release, circuit breaking, auth, and observability.
+ <p className="hero-subtitle">
+ <Translate id="hero.component.subtitle.main">
+ High-performance traffic management for APIs, microservices, and LLM workloads —
+ dynamic routing, load balancing, authentication, observability, and 100+ plugins.
</Translate>
- </h3>
+ </p>
<div className="hero-ctas">
<Link target="_parent" to={useBaseUrl('docs/apisix/getting-started')} className="btn btn-download">
- <Translate id="hero.component.download.btn">Getting Started</Translate>
+ <Translate id="hero.component.download.btn">Get started</Translate>
+ </Link>
+ <Link target="_parent" to={useBaseUrl('plugins/')} className="btn btn-secondary">
+ <Translate id="hero.component.plugins.btn">Browse 100+ plugins</Translate>
</Link>
- <ArrowAnim />
</div>
+ <ul className="hero-stats">
+ {STATS.map((stat) => (
+ <li className="hero-stats__item" key={stat.value}>
+ <span className="hero-stats__value">{stat.value}</span>
+ <span className="hero-stats__label">{stat.label}</span>
+ </li>
+ ))}
+ </ul>
</div>
- <div className="add-margin">
- <LazyLoadHeroCanvas />
- </div>
- </div>
+ </header>
);
export default HeroSection;
diff --git a/website/src/components/sections/Integrations.tsx b/website/src/components/sections/Integrations.tsx
new file mode 100644
index 00000000000..65254a2c673
--- /dev/null
+++ b/website/src/components/sections/Integrations.tsx
@@ -0,0 +1,95 @@
+import type { FC } from 'react';
+import React from 'react';
+import Head from '@docusaurus/Head';
+import Link from '@docusaurus/Link';
+import Translate from '@docusaurus/Translate';
+
+import '../../css/landing-sections/integrations.scss';
+
+interface Integration {
+ name: string;
+ /** Plugin name whose brand-colored icon lives in the shared plugin sprite. */
+ icon?: string;
+ /**
+ * Optional logo URL, for ecosystem logos that are not plugin icons (e.g.
+ * Kubernetes, OpenTelemetry, LLM providers). Host official colored SVGs on
+ * the API7 CDN and set this; it takes precedence over `icon`.
+ */
+ logo?: string;
+}
+
+const GROUPS: { title: string; items: Integration[] }[] = [
+ {
+ title: 'Observability & tracing',
+ items: [
+ { name: 'Prometheus', icon: 'prometheus' },
+ { name: 'Datadog', icon: 'datadog' },
+ { name: 'Apache SkyWalking', icon: 'skywalking' },
+ { name: 'Zipkin', icon: 'zipkin' },
+ { name: 'Google Cloud', icon: 'google-cloud-logging' },
+ ],
+ },
+ {
+ title: 'Authentication & security',
+ items: [
+ { name: 'OpenID Connect', icon: 'openid-connect' },
+ { name: 'Keycloak', icon: 'authz-keycloak' },
+ { name: 'Casbin', icon: 'authz-casbin' },
+ ],
+ },
+ {
+ title: 'Streaming & protocols',
+ items: [
+ { name: 'Apache Kafka', icon: 'kafka-logger' },
+ { name: 'gRPC', icon: 'grpc-transcode' },
+ ],
+ },
+];
+
+const IntegrationTile: FC<{ item: Integration }> = ({ item }) => (
+ <div className="integrations__tile" title={item.name}>
+ {item.logo ? (
+ <img className="integrations__img" src={item.logo} alt={item.name} loading="lazy" width={40} height={40} />
+ ) : (
+ <svg className="integrations__icon" aria-hidden="true">
+ <use xlinkHref={`#icon${item.icon}`} />
+ </svg>
+ )}
+ <span className="integrations__name">{item.name}</span>
+ </div>
+);
+
+const Integrations: FC = () => (
+ <section className="integrations" aria-labelledby="integrations-heading">
+ <Head>
+ {/* Injects the shared plugin icon sprite used by #icon<name> references. */}
+ <script src="/js/plugin-icon.js" defer />
+ </Head>
+ <h2 id="integrations-heading" className="integrations__heading">
+ <Translate id="home.integrations.title">Integrates with your stack</Translate>
+ </h2>
+ <p className="integrations__subtitle">
+ <Translate id="home.integrations.subtitle">
+ Connect APISIX to your observability, security, and streaming tools through 100+ plugins.
+ </Translate>
+ </p>
+ <div className="integrations__groups">
+ {GROUPS.map((group) => (
+ <div className="integrations__group" key={group.title}>
+ <div className="integrations__group-title">{group.title}</div>
+ <div className="integrations__tiles">
+ {group.items.map((item) => (
+ <IntegrationTile item={item} key={item.name} />
+ ))}
+ </div>
+ </div>
+ ))}
+ </div>
+ <Link className="integrations__cta" to="/plugins/">
+ <Translate id="home.integrations.cta">Explore all 100+ plugins</Translate>
+ {' →'}
+ </Link>
+ </section>
+);
+
+export default Integrations;
diff --git a/website/src/components/sections/Pathways.tsx b/website/src/components/sections/Pathways.tsx
new file mode 100644
index 00000000000..76105343df6
--- /dev/null
+++ b/website/src/components/sections/Pathways.tsx
@@ -0,0 +1,67 @@
+import type { FC } from 'react';
+import React from 'react';
+import Link from '@docusaurus/Link';
+import Translate from '@docusaurus/Translate';
+
+import '../../css/landing-sections/pathways.scss';
+
+interface Pathway {
+ id: string;
+ title: JSX.Element;
+ description: JSX.Element;
+ cta: JSX.Element;
+ to: string;
+}
+
+const PATHWAYS: Pathway[] = [
+ {
+ id: 'build',
+ to: '/docs/apisix/getting-started/',
+ title: <Translate id="home.pathways.build.title">Get started</Translate>,
+ description: (
+ <Translate id="home.pathways.build.desc">
+ Install APISIX and configure your first route in minutes.
+ </Translate>
+ ),
+ cta: <Translate id="home.pathways.build.cta">Read the docs</Translate>,
+ },
+ {
+ id: 'learn',
+ to: '/learning-center/',
+ title: <Translate id="home.pathways.learn.title">Learn the concepts</Translate>,
+ description: (
+ <Translate id="home.pathways.learn.desc">
+ API gateway, AI gateway, rate limiting, mTLS, Kubernetes and more.
+ </Translate>
+ ),
+ cta: <Translate id="home.pathways.learn.cta">Explore the learning center</Translate>,
+ },
+ {
+ id: 'plugins',
+ to: '/plugins/',
+ title: <Translate id="home.pathways.plugins.title">Browse plugins</Translate>,
+ description: (
+ <Translate id="home.pathways.plugins.desc">
+ 100+ plugins for auth, security, traffic control, observability and AI.
+ </Translate>
+ ),
+ cta: <Translate id="home.pathways.plugins.cta">Open the plugin hub</Translate>,
+ },
+];
+
+const Pathways: FC = () => (
+ <section className="pathways" aria-label="Get started with Apache APISIX">
+ {PATHWAYS.map((pathway) => (
+ <Link className="pathways__card" to={pathway.to} key={pathway.id}>
+ <h3 className="pathways__title">{pathway.title}</h3>
+ <p className="pathways__desc">{pathway.description}</p>
+ <span className="pathways__cta">
+ {pathway.cta}
+ {' →'}
+ </span>
+ </Link>
+ ))}
+ </section>
+);
+
+export default Pathways;
diff --git a/website/src/components/sections/TrustedBy.tsx b/website/src/components/sections/TrustedBy.tsx
new file mode 100644
index 00000000000..59f0cefb7d4
--- /dev/null
+++ b/website/src/components/sections/TrustedBy.tsx
@@ -0,0 +1,80 @@
+import type { FC } from 'react';
+import React, { useState } from 'react';
+import Link from '@docusaurus/Link';
+import Translate, { translate } from '@docusaurus/Translate';
+
+import '../../css/landing-sections/trusted-by.scss';
+
+interface TrustedUser {
+ name: string;
+ link: string;
+ /**
+ * Optional logo URL. When omitted, a clean text wordmark is shown instead, so
+ * the section renders correctly before official logos are available.
+ * Host official customer logos on the API7 CDN (where the showcase logos
+ * already live) and set this to e.g.
+ * `https://static.apiseven.com/uploads/users/nasa.svg`.
+ */
+ logo?: string;
+}
+
+const USERS: TrustedUser[] = [
+ { name: 'NASA', link: 'https://www.jpl.nasa.gov/' },
+ { name: 'Zoom', link: 'https://zoom.us/' },
+ { name: 'HPE', link: 'https://www.hpe.com/' },
+ { name: 'Tencent', link: 'https://www.tencent.com/en-us/' },
+ { name: 'Hisense', link: 'https://global.hisense.com/' },
+ { name: 'k6', link: 'https://k6.io/' },
+ { name: 'Citi', link: 'https://www.citigroup.com/' },
+ { name: 'API7.ai', link: 'https://api7.ai/' },
+ { name: "McDonald's", link: 'https://www.mcdonalds.com/' },
+ { name: 'KFC', link: 'https://global.kfc.com/' },
+];
+
+const UserLogo: FC<{ user: TrustedUser }> = ({ user }) => {
+ const [failed, setFailed] = useState(!user.logo);
+ return (
+ <Link
+ className="trusted-by__item"
+ to={user.link}
+ target="_blank"
+ rel="noopener noreferrer"
+ aria-label={user.name}
+ >
+ {failed ? (
+ <span className="trusted-by__wordmark">{user.name}</span>
+ ) : (
+ <img
+ className="trusted-by__logo"
+ src={user.logo}
+ alt={user.name}
+ loading="lazy"
+ width={132}
+ height={36}
+ onError={() => setFailed(true)}
+ />
+ )}
+ </Link>
+ );
+};
+
+const TrustedBy: FC = () => (
+ <section
+ className="trusted-by"
+ aria-label={translate({ id: 'home.trustedBy.label', message: 'Organizations using Apache APISIX' })}
+ >
+ <div className="trusted-by__eyebrow">
+ <Translate id="home.trustedBy.title">Trusted by teams everywhere</Translate>
+ </div>
+ <div className="trusted-by__viewport">
+ {/* Duplicated once so the CSS marquee loops seamlessly at translateX(-50%). */}
+ <div className="trusted-by__track">
+ {[...USERS, ...USERS].map((user, index) => (
+ <UserLogo key={`${user.name}-${index}`} user={user} />
+ ))}
+ </div>
+ </div>
+ </section>
+);
+
+export default TrustedBy;
diff --git a/website/src/css/landing-sections/hero.scss b/website/src/css/landing-sections/hero.scss
index 61bdaa5005f..29fecfbbe81 100644
--- a/website/src/css/landing-sections/hero.scss
+++ b/website/src/css/landing-sections/hero.scss
@@ -23,57 +23,49 @@
.hero-subtitle {
z-index: 100;
- font-size: 1.1rem;
+ font-size: 1.15rem;
font-family: MaisonNeue-Light, sans-serif;
position: relative;
- color: #615d5d;
- line-height: 30px;
+ color: #5f5d5d;
+ line-height: 1.6;
letter-spacing: 0.2px;
- margin: 25px 0;
- padding-right: 20px;
- animation-delay: 0.25s;
+ max-width: 620px;
+ margin: 1.25rem 0 0;
+ animation-delay: 0.2s;
}
.hero-ctas {
display: flex;
align-items: center;
- animation-delay: 0.5s;
+ justify-content: center;
+ flex-wrap: wrap;
+ gap: 12px;
+ margin-top: 1.75rem;
+ animation-delay: 0.4s;
}
.hero-sec-wrap {
display: flex;
- background: #f4f4f4ad;
- height: 100vh;
-}
-
-.homeCanvas-overlay {
- position: absolute;
- width: 50vw;
- height: 100vh;
- background: #fff;
- top: -1px;
- right: 0;
-}
-
-.homeCanvas {
- width: 50vw;
- height: 100vh;
+ justify-content: center;
+ background: #f7f7f8;
+ padding: clamp(3rem, 9vh, 7rem) 24px clamp(2.5rem, 7vh, 5rem);
}
.hero-text {
- height: 100%;
+ width: 100%;
+ max-width: 780px;
display: flex;
- flex-wrap: wrap;
- align-content: center;
- width: 50%;
- padding: 0 0 0 6vw;
+ flex-direction: column;
+ align-items: center;
+ text-align: center;
}
.hero-title {
font-family: MaisonNeue-Bold, sans-serif;
color: #121212;
- width: 42vw;
- font-size: 4.2rem;
+ max-width: 780px;
+ font-size: clamp(2.4rem, 1rem + 4.2vw, 4rem);
+ line-height: 1.08;
letter-spacing: 0.2px;
margin: 0;
}
@@ -150,12 +142,7 @@
@include respond-between(md, lg) {
.hero-title {
- width: 40vw;
- font-size: 5.2rem;
- }
-
- .hero-subtitle {
- width: 40vw;
+ font-size: clamp(3rem, 1rem + 5vw, 4.5rem);
}
}
@@ -205,3 +192,58 @@
animation: none;
}
}
+
+.hero-eyebrow {
+ display: inline-block;
+ font-size: 0.82rem;
+ font-weight: 600;
+ color: #a32d2d;
+ background: #fcebeb;
+ padding: 5px 14px;
+ border-radius: 20px;
+ margin-bottom: 1.25rem;
+}
+
+.btn-secondary {
+ background: #fff;
+ color: #1c1c1c;
+ border: 1px solid #d6d6d6;
+}
+
+.btn-secondary:hover {
+ background: #f1f1f1;
+ color: #1c1c1c;
+ border-color: #c0c0c0;
+}
+
+.hero-stats {
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: center;
+ gap: 1.5rem 2.5rem;
+ list-style: none;
+ width: 100%;
+ max-width: 640px;
+ margin: 2.25rem 0 0;
+ padding: 1.5rem 0 0;
+ border-top: 1px solid #e6e6e6;
+}
+
+.hero-stats__item {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+}
+
+.hero-stats__value {
+ font-size: 1.5rem;
+ font-weight: 700;
+ color: #e8433e;
+ line-height: 1.1;
+}
+
+.hero-stats__label {
+ font-size: 0.85rem;
+ color: #777;
+ margin-top: 2px;
+}
diff --git a/website/src/css/landing-sections/integrations.scss b/website/src/css/landing-sections/integrations.scss
new file mode 100644
index 00000000000..4fb36e7b630
--- /dev/null
+++ b/website/src/css/landing-sections/integrations.scss
@@ -0,0 +1,97 @@
+.integrations {
+ max-width: 960px;
+ margin: 0 auto;
+ padding: 3.5rem var(--ifm-spacing-horizontal) 1rem;
+ text-align: center;
+}
+
+.integrations__heading {
+ font-size: clamp(1.6rem, 1rem + 2vw, 2.25rem);
+ font-weight: 700;
+ margin-bottom: 0.5rem;
+}
+
+.integrations__subtitle {
+ color: #6b6b6b;
+ max-width: 620px;
+ margin: 0 auto 2.25rem;
+ font-size: 1rem;
+ line-height: 1.6;
+}
+
+.integrations__groups {
+ display: flex;
+ flex-direction: column;
+ gap: 1.75rem;
+}
+
+.integrations__group-title {
+ font-size: 0.78rem;
+ letter-spacing: 0.07em;
+ text-transform: uppercase;
+ color: #9a9a9a;
+ margin-bottom: 0.85rem;
+}
+
+.integrations__tiles {
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: center;
+ gap: 0.75rem;
+}
+
+.integrations__tile {
+ display: flex;
+ align-items: center;
+ gap: 0.6rem;
+ padding: 0.65rem 1.1rem;
+ border: 1px solid #ececec;
+ border-radius: 0.75rem;
+ background: #fff;
+ transition: transform 0.18s ease, border-color 0.18s ease, box-shadow 0.18s ease;
+
+ &:hover {
+ transform: translateY(-3px);
+ border-color: #f3b6b3;
+ box-shadow: 0 6px 18px rgba(232, 67, 62, 0.08);
+ }
+}
+
+.integrations__icon {
+ width: 30px;
+ height: 30px;
+ flex-shrink: 0;
+}
+
+.integrations__img {
+ width: 30px;
+ height: 30px;
+ object-fit: contain;
+ flex-shrink: 0;
+}
+
+.integrations__name {
+ font-size: 0.95rem;
+ font-weight: 500;
+ color: #2c2c2a;
+ white-space: nowrap;
+}
+
+.integrations__cta {
+ display: inline-block;
+ margin-top: 2rem;
+ font-size: 1rem;
+ font-weight: 600;
+ color: #e8433e;
+
+ &:hover {
+ color: #c7352f;
+ text-decoration: none;
+ }
+}
+
+@media (prefers-reduced-motion: reduce) {
+ .integrations__tile:hover {
+ transform: none;
+ }
+}
diff --git a/website/src/css/landing-sections/pathways.scss b/website/src/css/landing-sections/pathways.scss
new file mode 100644
index 00000000000..01312e5d666
--- /dev/null
+++ b/website/src/css/landing-sections/pathways.scss
@@ -0,0 +1,62 @@
+.pathways {
+ max-width: 1000px;
+ margin: 0 auto;
+ padding: 1rem var(--ifm-spacing-horizontal) 1.5rem;
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ gap: 1.25rem;
+}
+
+@media (max-width: 820px) {
+ .pathways {
+ grid-template-columns: 1fr;
+ }
+}
+
+.pathways__card {
+ display: flex;
+ flex-direction: column;
+ background: #fff;
+ border: 1px solid #ececec;
+ border-top: 3px solid #e8433e;
+ border-radius: 0.75rem;
+ padding: 1.6rem 1.4rem;
+ color: inherit;
+ transition: transform 0.18s ease, box-shadow 0.18s ease, border-color 0.18s ease;
+
+ &:hover {
+ transform: translateY(-4px);
+ box-shadow: 0 10px 28px rgba(0, 0, 0, 0.07);
+ border-color: #f3b6b3;
+ border-top-color: #e8433e;
+ color: inherit;
+ text-decoration: none;
+ }
+}
+
+.pathways__title {
+ font-size: 1.25rem;
+ font-weight: 700;
+ margin: 0 0 0.5rem;
+ color: #1c1c1c;
+}
+
+.pathways__desc {
+ font-size: 0.95rem;
+ line-height: 1.55;
+ color: #6b6b6b;
+ margin: 0 0 1rem;
+ flex: 1;
+}
+
+.pathways__cta {
+ font-size: 0.95rem;
+ font-weight: 600;
+ color: #e8433e;
+}
+
+@media (prefers-reduced-motion: reduce) {
+ .pathways__card:hover {
+ transform: none;
+ }
+}
diff --git a/website/src/css/landing-sections/trusted-by.scss b/website/src/css/landing-sections/trusted-by.scss
new file mode 100644
index 00000000000..fc2a5c28004
--- /dev/null
+++ b/website/src/css/landing-sections/trusted-by.scss
@@ -0,0 +1,89 @@
+.trusted-by {
+ padding: 3rem 0 2.5rem;
+ text-align: center;
+}
+
+.trusted-by__eyebrow {
+ font-size: 0.8rem;
+ letter-spacing: 0.09em;
+ text-transform: uppercase;
+ color: #8a8a8a;
+ margin-bottom: 1.5rem;
+}
+
+.trusted-by__viewport {
+ overflow: hidden;
+ // Fade the logos in/out at both edges instead of cutting them off hard.
+ -webkit-mask-image: linear-gradient(90deg, transparent 0, #000 7%, #000 93%, transparent 100%);
+ mask-image: linear-gradient(90deg, transparent 0, #000 7%, #000 93%, transparent 100%);
+}
+
+.trusted-by__track {
+ display: flex;
+ width: max-content;
+ align-items: center;
+ gap: 3.5rem;
+ // Compositor-only animation (transform). The track holds two identical
+ // copies of the list, so translateX(-50%) lands exactly on the loop seam.
+ animation: trusted-by-marquee 45s linear infinite;
+ will-change: transform;
+}
+
+.trusted-by:hover .trusted-by__track {
+ animation-play-state: paused;
+}
+
+.trusted-by__item {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+
+ &:hover {
+ text-decoration: none;
+ }
+}
+
+.trusted-by__logo {
+ height: 34px;
+ width: auto;
+ object-fit: contain;
+ filter: grayscale(1);
+ opacity: 0.65;
+ transition: filter 0.2s ease, opacity 0.2s ease;
+
+ .trusted-by__item:hover & {
+ filter: grayscale(0);
+ opacity: 1;
+ }
+}
+
+.trusted-by__wordmark {
+ font-size: 1.15rem;
+ font-weight: 700;
+ white-space: nowrap;
+ color: #7a7975;
+ transition: color 0.2s ease;
+
+ .trusted-by__item:hover & {
+ color: #e8433e;
+ }
+}
+
+@keyframes trusted-by-marquee {
+ from {
+ transform: translateX(0);
+ }
+ to {
+ transform: translateX(-50%);
+ }
+}
+
+@media (prefers-reduced-motion: reduce) {
+ .trusted-by__track {
+ animation: none;
+ width: auto;
+ flex-wrap: wrap;
+ justify-content: center;
+ gap: 2rem 3rem;
+ }
+}
diff --git a/website/src/pages/index.tsx b/website/src/pages/index.tsx
index a57c738c3c3..672772387c0 100644
--- a/website/src/pages/index.tsx
+++ b/website/src/pages/index.tsx
@@ -13,6 +13,9 @@ import Benefits from '../components/sections/Benefits';
import Comparison from '../components/sections/Comparison';
import OpensourcePromo from '../components/sections/OpensourcePromo';
import EndCTA from '../components/sections/Endcta';
+import TrustedBy from '../components/sections/TrustedBy';
+import Pathways from '../components/sections/Pathways';
+import Integrations from '../components/sections/Integrations';
// Structured data for the homepage. Organization + WebSite are already injected
// globally by config/schema-org.js; these add product-level (SoftwareApplication)
@@ -136,8 +139,11 @@ const Index: FC = () => (
<script type="application/ld+json">{JSON.stringify(HOMEPAGE_FAQ_SCHEMA)}</script>
</Head>
<HeroSection />
+ <TrustedBy />
+ <Pathways />
<Architecture />
<Features />
+ <Integrations />
<Benefits />
<Comparison />
<OpensourcePromo />