This is an automated email from the ASF dual-hosted git repository.

Xuanwo pushed a commit to branch xuanwo/website-redesign
in repository https://gitbox.apache.org/repos/asf/opendal.git

commit f4337bdc2b0f38a4f0f5ce0e81e3d9d95afed2d7
Author: Xuanwo <[email protected]>
AuthorDate: Sat May 30 01:56:52 2026 +0800

    feat(website): make layers an interactive explorer and polish landing 
sections
---
 website/src/components/landing/data.js           | 118 +++++++++++++---------
 website/src/components/landing/sections.jsx      | 118 +++++++++-------------
 website/src/components/landing/styles.module.css | 123 ++++++++---------------
 website/src/pages/index.jsx                      |   2 -
 4 files changed, 157 insertions(+), 204 deletions(-)

diff --git a/website/src/components/landing/data.js 
b/website/src/components/landing/data.js
index 83935b611..3a7b786fd 100644
--- a/website/src/components/landing/data.js
+++ b/website/src/components/landing/data.js
@@ -73,9 +73,8 @@ data = op.read("hello.txt")`,
 )
 
 // Configure a backend once, then use one Operator.
-op, _ := opendal.NewOperator(s3.Scheme, opendal.OperatorOptions{
-    "bucket": "data",
-})
+opts := opendal.OperatorOptions{"bucket": "data"}
+op, _ := opendal.NewOperator(s3.Scheme, opts)
 
 op.Write("hello.txt", []byte("Hello, World!"))
 data, _ := op.Read("hello.txt")`,
@@ -110,15 +109,6 @@ const data = await op.read("hello.txt");`,
   },
 ];
 
-// Layered example used in the Layers section.
-export const layeredCode = `use opendal::layers::{LoggingLayer, RetryLayer};
-use opendal::Operator;
-
-// Cross-cutting behavior composes onto any operator, in order.
-let op = op
-    .layer(RetryLayer::new())
-    .layer(LoggingLayer::default());`;
-
 export const valueProps = [
   {
     index: "01",
@@ -147,6 +137,7 @@ export const valueProps = [
 // read / write / manage APIs (verified against types/operator + 
types/options).
 const RS = "https://docs.rs/opendal/latest/opendal";;
 const opDoc = (method) => `${RS}/struct.Operator.html#method.${method}`;
+const layerDoc = (name) => `${RS}/layers/struct.${name}.html`;
 
 export const capabilityThemes = [
   {
@@ -215,7 +206,8 @@ op.copy("draft.md", "final.md").await?;
 // Recursively delete a subtree.
 op.delete_with("tmp/").recursive(true).await?;
 // Presign a temporary, shareable URL.
-let url = op.presign_read("report.csv", Duration::from_secs(3600)).await?;`,
+let ttl = Duration::from_secs(3600);
+let url = op.presign_read("report.csv", ttl).await?;`,
   },
 ];
 
@@ -359,65 +351,93 @@ export const bindings = [
 export const layers = [
   {
     name: "RetryLayer",
-    icon: "/img/layers/retry.svg",
     desc: "Recover from transient failures automatically.",
+    doc: layerDoc("RetryLayer"),
+    code: `use opendal::layers::RetryLayer;
+
+// Exponential backoff with jitter; interrupted
+// reads and writes resume where they left off.
+let op = op.layer(
+    RetryLayer::new().with_max_times(5).with_jitter(),
+);`,
   },
   {
     name: "TimeoutLayer",
-    icon: "/img/layers/timeout.svg",
     desc: "Bound slow or hanging operations.",
+    doc: layerDoc("TimeoutLayer"),
+    code: `use opendal::layers::TimeoutLayer;
+use std::time::Duration;
+
+// Abort operations that stall past a deadline.
+let op = op.layer(
+    TimeoutLayer::new()
+        .with_timeout(Duration::from_secs(10)),
+);`,
   },
   {
     name: "LoggingLayer",
-    icon: "/img/layers/logging.svg",
     desc: "Emit structured operation logs.",
+    doc: layerDoc("LoggingLayer"),
+    code: `use opendal::layers::LoggingLayer;
+
+// Structured logs for every operation, via the
+// standard log crate facade.
+let op = op.layer(LoggingLayer::default());`,
   },
   {
     name: "TracingLayer",
-    icon: "/img/layers/tracing.svg",
     desc: "Trace requests across systems.",
+    doc: layerDoc("TracingLayer"),
+    code: `use opendal::layers::TracingLayer;
+
+// One span per operation, into whatever
+// tracing subscriber your app installs.
+let op = op.layer(TracingLayer::new());`,
   },
   {
     name: "MetricsLayer",
-    icon: "/img/layers/metrics.svg",
     desc: "Export operation metrics.",
+    doc: layerDoc("MetricsLayer"),
+    code: `use opendal::layers::MetricsLayer;
+
+// Latency and throughput via the metrics crate
+// facade — plug in any exporter you like.
+let op = op.layer(MetricsLayer::default());`,
   },
   {
     name: "PrometheusLayer",
-    icon: "/img/layers/prometheus.svg",
     desc: "Expose Prometheus metrics.",
-  },
-  {
-    name: "Traffic Control",
-    icon: "/img/layers/traffic-control.svg",
-    desc: "Throttle and cap concurrency.",
-  },
-  {
-    name: "FoyerLayer",
-    icon: "/img/layers/cache.svg",
-    desc: "Add hybrid cache behavior.",
-  },
-];
+    doc: layerDoc("PrometheusLayer"),
+    code: `use opendal::layers::PrometheusLayer;
 
-export const principles = [
-  {
-    title: "Open Community",
-    body: "An ASF project governed by its PMC. Community over code, the Apache 
Way.",
-  },
-  {
-    title: "Solid Foundation",
-    body: "A dependable base you can trust for real-world storage operations.",
-  },
-  {
-    title: "Fast Access",
-    body: "Zero-overhead access — as fast as, or faster than, each native 
SDK.",
-  },
-  {
-    title: "Object Storage First",
-    body: "Designed and optimized for modern object storage from the ground 
up.",
+// Register operation metrics into a Prometheus
+// registry you already expose.
+let registry = prometheus::default_registry();
+let op = op.layer(
+    PrometheusLayer::builder().register(registry)?,
+);`,
+  },
+  {
+    name: "ConcurrentLimitLayer",
+    desc: "Cap in-flight concurrency.",
+    doc: layerDoc("ConcurrentLimitLayer"),
+    code: `use opendal::layers::ConcurrentLimitLayer;
+
+// Cap how many operations hit the backend at once —
+// back-pressure for the whole Operator.
+let op = op.layer(ConcurrentLimitLayer::new(1024));`,
   },
   {
-    title: "Extensible Architecture",
-    body: "Independent services, layers and bindings you can stack and 
extend.",
+    name: "ThrottleLayer",
+    desc: "Throttle I/O bandwidth.",
+    doc: layerDoc("ThrottleLayer"),
+    code: `use opendal::layers::ThrottleLayer;
+
+// Token-bucket bandwidth limit: ~10 MiB/s steady,
+// with headroom to burst for short spikes.
+let op = op.layer(ThrottleLayer::new(
+    10 * 1024 * 1024,
+    32 * 1024 * 1024,
+));`,
   },
 ];
diff --git a/website/src/components/landing/sections.jsx 
b/website/src/components/landing/sections.jsx
index cfb1d7d16..f3f65506d 100644
--- a/website/src/components/landing/sections.jsx
+++ b/website/src/components/landing/sections.jsx
@@ -36,8 +36,6 @@ import {
   serviceGroups,
   bindings,
   layers,
-  layeredCode,
-  principles,
 } from "./data";
 
 export function Hero() {
@@ -313,79 +311,60 @@ export function Bindings() {
 }
 
 export function Layers() {
-  const { withBaseUrl } = useBaseUrlUtils();
+  const [active, setActive] = useState(layers[0]);
   return (
     <section className={styles.section}>
-      <div className="odl-container">
-        <div className={styles.layersInner}>
-          <div>
-            <span className="odl-eyebrow">Layers</span>
-            <h2 className={styles.sectionTitle}>
-              Production behavior, composed — not coded.
-            </h2>
-            <p className={styles.sectionLede}>
-              Stack cross-cutting concerns as reusable layers. The order is
-              explicit and the core stays zero-cost.
-            </p>
-            <div style={{ marginTop: "var(--odl-space-6)" }}>
-              <CodeTabs
-                samples={[
-                  {
-                    id: "layered",
-                    label: "Rust",
-                    language: "rust",
-                    code: layeredCode,
-                  },
-                ]}
-                title="compose layers onto any operator"
-              />
-            </div>
-          </div>
-          <div className={`${styles.layerGrid} ${styles.reveal}`}>
-            {layers.map((l) => (
-              <div className={styles.layerItem} key={l.name}>
-                <img
-                  className={styles.layerIcon}
-                  src={withBaseUrl(l.icon)}
-                  alt=""
-                  width="26"
-                  height="26"
-                  loading="lazy"
-                />
-                <div>
-                  <p className={styles.layerName}>{l.name}</p>
-                  <p className={styles.layerDesc}>{l.desc}</p>
-                </div>
-              </div>
-            ))}
-          </div>
-        </div>
-      </div>
-    </section>
-  );
-}
-
-export function Community() {
-  return (
-    <section className={`${styles.section} ${styles.sectionSubtle}`}>
       <div className="odl-container">
         <div className={styles.sectionHead}>
-          <span className="odl-eyebrow">Open the Apache Way</span>
+          <span className="odl-eyebrow">Layers</span>
           <h2 className={styles.sectionTitle}>
-            Built by a community, for the commons.
+            Production behavior, composed — not coded.
           </h2>
           <p className={styles.sectionLede}>
-            Apache OpenDAL™ graduated to a top-level project in 2024. Five
-            principles guide every decision we make.
+            Stack cross-cutting concerns as reusable layers. The order is
+            explicit and the core stays zero-cost.
           </p>
         </div>
-        <div className={styles.principleGrid}>
-          {principles.map((p) => (
-            <div className={styles.principle} key={p.title}>
-              <h3 className={styles.principleTitle}>{p.title}</h3>
-              <p className={styles.principleBody}>{p.body}</p>
+        <div className={styles.layerExplorer}>
+          <div className={styles.layerGrid}>
+            {layers.map((l) => {
+              const selected = active.name === l.name;
+              return (
+                <Link
+                  key={l.name}
+                  className={`${styles.layerItem} ${
+                    selected ? styles.layerItemActive : ""
+                  }`}
+                  to={l.doc}
+                  target="_blank"
+                  rel="noreferrer"
+                  aria-current={selected ? "true" : undefined}
+                  onMouseEnter={() => setActive(l)}
+                  onFocus={() => setActive(l)}
+                >
+                  <span className={styles.layerName}>{l.name}</span>
+                  <span className={styles.layerDesc}>{l.desc}</span>
+                </Link>
+              );
+            })}
+          </div>
+          <div className={styles.capabilityPreview}>
+            <div className={styles.codeWindow}>
+              <div className={styles.windowBar}>
+                <div className={styles.windowDots} aria-hidden="true">
+                  <span />
+                  <span />
+                  <span />
+                </div>
+                <span className={styles.windowTitle}>{active.name}</span>
+              </div>
+              <div className={`${styles.codeBody} ${styles.layerCodeBody}`}>
+                <div className={styles.capabilityCodeFade} key={active.name}>
+                  <CodeBlock language="rust">{active.code}</CodeBlock>
+                </div>
+              </div>
             </div>
-          ))}
+          </div>
         </div>
       </div>
     </section>
@@ -394,10 +373,9 @@ export function Community() {
 
 export function FinalCta() {
   return (
-    <section className={styles.section}>
+    <section className={`${styles.section} ${styles.sectionSubtle}`}>
       <div className="odl-container">
         <div className={styles.finalCta}>
-          <div className={styles.finalGrid} aria-hidden="true" />
           <div className={`${styles.finalCtaInner} ${styles.finalCenter}`}>
             <span className={`odl-eyebrow ${styles.finalEyebrow}`}>
               Start building
@@ -411,19 +389,19 @@ export function FinalCta() {
             </p>
             <div className={styles.finalCtaActions}>
               <Link
-                className={`${styles.btn} ${styles.btnOnDark}`}
+                className={`${styles.btn} ${styles.btnPrimary}`}
                 to={DOCS_URL}
               >
                 Get started <span className={styles.btnArrow}>→</span>
               </Link>
               <Link
-                className={`${styles.btn} ${styles.btnOnDarkGhost}`}
+                className={`${styles.btn} ${styles.btnSecondary}`}
                 to={REPO_URL}
               >
                 Star on GitHub
               </Link>
               <Link
-                className={`${styles.btn} ${styles.btnOnDarkGhost}`}
+                className={`${styles.btn} ${styles.btnSecondary}`}
                 to={DISCORD_URL}
               >
                 Join Discord
diff --git a/website/src/components/landing/styles.module.css 
b/website/src/components/landing/styles.module.css
index b76032d14..8b1559bbe 100644
--- a/website/src/components/landing/styles.module.css
+++ b/website/src/components/landing/styles.module.css
@@ -270,6 +270,14 @@
   margin: 0;
   background: var(--odl-code-bg) !important;
   font-size: 0.8125rem;
+  /* Wrap long lines so code windows never scroll horizontally. */
+  white-space: pre-wrap;
+  overflow-wrap: anywhere;
+}
+.codeBody :global(pre code),
+.codeBody :global(.token-line) {
+  white-space: inherit;
+  overflow-wrap: inherit;
 }
 
 /* ===== Used-by wall ===================================================== */
@@ -459,7 +467,7 @@
   top: 5rem;
 }
 .capabilityCodeBody {
-  height: 18rem;
+  height: 19rem;
   overflow: auto;
   background: var(--odl-code-bg);
 }
@@ -580,11 +588,11 @@
   color: var(--odl-fg-muted);
 }
 
-/* ===== Layers =========================================================== */
-.layersInner {
+/* ===== Layers (tile grid left, live code preview right) ================= */
+.layerExplorer {
   display: grid;
-  grid-template-columns: minmax(0, 1.05fr) minmax(0, 0.95fr);
-  gap: clamp(2rem, 1rem + 4vw, 4rem);
+  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
+  gap: clamp(2rem, 1rem + 3vw, 3.5rem);
   align-items: start;
 }
 .layerGrid {
@@ -597,83 +605,51 @@
   overflow: hidden;
 }
 .layerItem {
-  background: var(--odl-surface);
-  padding: var(--odl-space-4);
   display: flex;
-  gap: var(--odl-space-3);
-  align-items: flex-start;
+  flex-direction: column;
+  gap: 4px;
+  padding: var(--odl-space-4);
+  background: var(--odl-surface);
+  text-decoration: none;
+  transition: background-color var(--odl-dur) var(--odl-ease);
 }
-.layerIcon {
-  width: 26px;
-  height: 26px;
-  flex: none;
-  object-fit: contain;
-  margin-top: 2px;
+.layerItem:hover {
+  background: var(--odl-surface-2);
+  text-decoration: none;
+}
+.layerItemActive {
+  background: var(--odl-surface-2);
 }
 .layerName {
   font-family: var(--odl-font-mono);
   font-size: var(--odl-text-sm);
   font-weight: 600;
   color: var(--odl-fg-strong);
-  margin: 0;
+  transition: color var(--odl-dur) var(--odl-ease);
+}
+.layerItemActive .layerName {
+  color: var(--odl-accent);
 }
 .layerDesc {
   font-size: var(--odl-text-xs);
   line-height: var(--odl-leading-snug);
   color: var(--odl-fg-muted);
-  margin: 2px 0 0;
 }
-
-/* ===== Community / principles =========================================== */
-.principleGrid {
-  display: grid;
-  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
-  gap: var(--odl-space-5);
-  margin-top: var(--odl-space-7);
-}
-.principle {
-  border-top: 2px solid var(--odl-bar);
-  padding-top: var(--odl-space-4);
-}
-.principleTitle {
-  font-size: var(--odl-text-md);
-  font-weight: 650;
-  color: var(--odl-fg-strong);
-  margin: 0 0 var(--odl-space-2);
-  letter-spacing: var(--odl-tracking-tight);
-}
-.principleBody {
-  font-size: var(--odl-text-sm);
-  line-height: var(--odl-leading-snug);
-  color: var(--odl-fg-muted);
-  margin: 0;
+.layerCodeBody {
+  height: 13rem;
+  overflow: auto;
+  background: var(--odl-code-bg);
 }
 
 /* ===== Final CTA ======================================================== */
 .finalCta {
-  position: relative;
-  overflow: hidden;
-  background: var(--odl-ink-950);
-  color: #fff;
+  background: var(--odl-surface);
+  border: 1px solid var(--odl-border);
   border-radius: var(--odl-radius-lg);
+  box-shadow: var(--odl-shadow-sm);
   padding: clamp(2.5rem, 1.5rem + 5vw, 5rem);
   text-align: center;
 }
-[data-theme="dark"] .finalCta {
-  border: 1px solid var(--odl-border);
-}
-.finalGrid {
-  position: absolute;
-  inset: 0;
-  background-image: linear-gradient(
-      rgba(255, 255, 255, 0.05) 1px,
-      transparent 1px
-    ),
-    linear-gradient(90deg, rgba(255, 255, 255, 0.05) 1px, transparent 1px);
-  background-size: 44px 44px;
-  -webkit-mask-image: radial-gradient(80% 80% at 50% 0%, #000, transparent 
75%);
-  mask-image: radial-gradient(80% 80% at 50% 0%, #000, transparent 75%);
-}
 .finalCtaInner {
   position: relative;
 }
@@ -682,11 +658,11 @@
   font-weight: 700;
   letter-spacing: var(--odl-tracking-tight);
   margin: var(--odl-space-4) 0 0;
-  color: #fff;
+  color: var(--odl-fg-strong);
 }
 .finalCtaLede {
   font-size: var(--odl-text-md);
-  color: rgba(255, 255, 255, 0.7);
+  color: var(--odl-fg-muted);
   margin: var(--odl-space-4) auto 0;
   max-width: 34rem;
 }
@@ -699,25 +675,6 @@
 }
 .finalEyebrow {
   justify-content: center;
-  color: rgba(255, 255, 255, 0.75);
-}
-.btnOnDark {
-  background: #fff;
-  color: var(--odl-ink-950);
-}
-.btnOnDark:hover {
-  background: rgba(255, 255, 255, 0.88);
-  color: var(--odl-ink-950);
-}
-.btnOnDarkGhost {
-  background: transparent;
-  color: #fff;
-  border-color: rgba(255, 255, 255, 0.28);
-}
-.btnOnDarkGhost:hover {
-  color: #fff;
-  border-color: rgba(255, 255, 255, 0.6);
-  background: rgba(255, 255, 255, 0.06);
 }
 .finalCenter {
   display: flex;
@@ -751,8 +708,8 @@
 /* ===== Responsive ======================================================= */
 @media (max-width: 996px) {
   .heroInner,
-  .layersInner,
-  .capabilityExplorer {
+  .capabilityExplorer,
+  .layerExplorer {
     grid-template-columns: 1fr;
   }
   .capabilityPreview {
diff --git a/website/src/pages/index.jsx b/website/src/pages/index.jsx
index 9d8259343..b6b6c7b88 100644
--- a/website/src/pages/index.jsx
+++ b/website/src/pages/index.jsx
@@ -27,7 +27,6 @@ import {
   Services,
   Bindings,
   Layers,
-  Community,
   FinalCta,
 } from "../components/landing/sections";
 
@@ -45,7 +44,6 @@ export default function Home() {
         <Services />
         <Bindings />
         <Layers />
-        <Community />
         <FinalCta />
       </main>
     </Layout>

Reply via email to