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 c536a0f68dc171e576dcb5709a01ba1da10d1e0e Author: Xuanwo <[email protected]> AuthorDate: Fri May 29 19:19:14 2026 +0800 feat(website): add design system and landing page Add a token-based design system in src/css/custom.css (color, typography, spacing, elevation, motion, and the OpenDAL bar motif), projected onto Infima so docs and blog inherit it, and document it in DESIGN_SYSTEM.md. Add a developer-focused landing page at "/" (hero, adopters, value props, the Operator contract, services, bindings, layers, community, CTA). Move docs from "/" to "/docs/" with backward-compatible redirects for legacy URLs, and enable the light/dark theme toggle. --- website/DESIGN_SYSTEM.md | 230 ++++++++ website/docusaurus.config.js | 23 +- website/src/components/landing/CodeTabs.jsx | 112 ++++ website/src/components/landing/data.js | 311 +++++++++++ website/src/components/landing/sections.jsx | 382 +++++++++++++ website/src/components/landing/styles.module.css | 681 +++++++++++++++++++++++ website/src/css/custom.css | 446 +++++++++++++-- website/src/pages/index.jsx | 53 ++ 8 files changed, 2177 insertions(+), 61 deletions(-) diff --git a/website/DESIGN_SYSTEM.md b/website/DESIGN_SYSTEM.md new file mode 100644 index 000000000..e4636087f --- /dev/null +++ b/website/DESIGN_SYSTEM.md @@ -0,0 +1,230 @@ +<!-- + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +--> + +# OpenDAL Design System + +> **One Layer, All Storage.** A precise, engineering-grade visual language for +> Apache OpenDAL™ — built to feel as dependable, fast and composable as the +> library itself. + +This document is the source of truth for OpenDAL's web visual language. The +landing page (`src/pages/index.jsx`) is its first full application; docs and +blog inherit it through Infima variable mapping. + +--- + +## 1. Design principles + +The visual system is a direct translation of OpenDAL's engineering values. + +| Library value | Visual expression | +| ------------------------- | ---------------------------------------------------------------------------- | +| **One Layer, All Storage**| One accent, one type voice, one grid — convergence over decoration. | +| **Solid Foundation** | Hairline structure, precise alignment, restraint. Nothing is decorative-only.| +| **Fast Access (zero-cost)**| System fonts, no web-font requests, CSS-only motion. The page costs nothing it doesn't need. | +| **Object Storage First** | Content-first layout; code and capabilities lead, ornament follows. | +| **Extensible Architecture**| Tokens compose; components are presentational and reusable. | + +Operating posture: **engineering-minimal, light-first** (dark mode is a +first-class, fully designed peer), near-monochrome **ink** with a single +**precise-blue** accent, and the signature solid **bar** lifted from the +OpenDAL wordmark. + +--- + +## 2. Where the system lives + +| Concern | Location | +| ------------------------------- | ----------------------------------------- | +| Tokens + Infima mapping + motif | `src/css/custom.css` | +| Landing sections | `src/components/landing/sections.jsx` | +| Code window / language tabs | `src/components/landing/CodeTabs.jsx` | +| Landing styles | `src/components/landing/styles.module.css`| +| Content model (copy, catalogs) | `src/components/landing/data.js` | + +All design decisions are encoded as CSS custom properties prefixed `--odl-*`. +Components never hardcode raw hex; they consume tokens. + +--- + +## 3. Color + +### 3.1 Primitives + +**Ink** — a cool, neutral near-black → white ramp (the monochrome backbone): + +`--odl-ink-{0,50,100,150,200,300,400,500,600,700,800,850,900,950}` +from `#ffffff` to `#0a0d11`. + +**Precise blue** — the single brand accent: + +`--odl-blue-{50…900}` from `#edf3ff` to `#163172`; `--odl-blue-600 #1e54e0` is +the canonical light-mode accent. + +Functional signals (`--odl-green/amber/red-*`) exist mostly so inherited docs +UI (admonitions, etc.) stay on-system. They are intentionally muted. + +### 3.2 Semantic roles (theme-aware) + +Components consume **semantic** tokens, not primitives: + +| Token | Light | Dark | Use | +| --------------------- | -------------------- | ----------------- | ---------------------------- | +| `--odl-bg` | `ink-0` | `#0a0d11` | Page background | +| `--odl-bg-subtle` | `ink-50` | `#0e1218` | Alternating section bands | +| `--odl-surface` | `ink-0` | `#11161c` | Cards, code window | +| `--odl-fg` | `ink-900` | `#e7ebf0` | Body text | +| `--odl-fg-muted` | `ink-600` | `#9aa6b3` | Secondary text | +| `--odl-border` | `ink-200` | `#242c35` | Hairlines, dividers | +| `--odl-bar` | `ink-950` | `#f6f8fa` | The bar motif | +| `--odl-accent` | `blue-600` | `blue-300` | Links, emphasis | +| `--odl-action-bg` | `blue-600` | `blue-500` | Solid CTA background | + +### 3.3 Rules + +- **Black/white leads, blue accents.** Blue is for interaction, emphasis and + code highlight — never large fills (except the solid CTA). +- **Contrast:** body and accent text meet WCAG AA (≥ 4.5:1) in both themes; + white-on-`action-bg` passes for buttons. Dark mode lifts the accent to a + lighter blue rather than inverting it. +- **Never rely on color alone** — pair with the bar, an icon, or a label. + +--- + +## 4. Typography + +### 4.1 Stacks & rationale + +``` +--odl-font-sans: system-ui, -apple-system, "Segoe UI", Roboto, … +--odl-font-mono: ui-monospace, "SF Mono", "Cascadia Code", "JetBrains Mono", … +``` + +System stacks are a **deliberate** choice, not a fallback: zero third-party +requests (ASF privacy-friendly), zero layout shift, instant and globally +consistent rendering (OpenDAL has a large global user base). The wordmark +already carries the brand letterforms. + +**Monospace is promoted to a brand voice.** It marks anything structural or +technical — eyebrows/kickers, section indices, statistics, service names, code. +This gives the minimal system its engineering character without a custom font. + +> To adopt **IBM Plex** instead: self-host via `@fontsource/ibm-plex-sans` and +> `@fontsource/ibm-plex-mono`, `@import` the weights in `custom.css`, and set +> `--odl-font-sans`/`--odl-font-mono` accordingly. The rest of the system needs +> no changes. + +### 4.2 Scale + +`--odl-text-2xs (11px)` · `xs (12)` · `sm (14)` · `base (16)` · `md (18)` · +`lg (22)` · `xl (28)` · `2xl (36)` · `3xl (48)` · `display clamp(44–80px)`. + +Headings: weight `650–700`, tracking `-0.022em`. Body: `400`, line-height +`1.6`. Mono labels: uppercase, tracking `0.12em`. + +--- + +## 5. Spacing & layout + +- **4px rhythm:** `--odl-space-1…10` (`0.25rem` → `8rem`). +- **Container:** `--odl-container 1200px`, `--odl-container-narrow 880px`, + fluid `--odl-gutter clamp(1.25–2.5rem)`. Use the global `.odl-container`. +- **Section rhythm:** vertical padding `clamp(3.5rem, 7rem)`, every section + separated by a single hairline (`border-top: 1px var(--odl-border)`), + alternating with `--odl-bg-subtle` bands. + +--- + +## 6. Radius & elevation + +- **Radius:** `--odl-radius-xs 3 / sm 5 / md 8 / lg 12 / pill`. Restrained by + design. **The bar motif is always sharp (radius 0).** +- **Elevation:** `--odl-shadow-sm/md/lg`. Prefer **border-based** separation; + reserve real shadow for floating surfaces (the code window). + +--- + +## 7. Motion + +- **Tokens:** `--odl-dur-fast 120ms / dur 200ms / dur-slow 320ms`; + `--odl-ease-out cubic-bezier(0.16,1,0.3,1)`, `--odl-ease`. +- **Micro-interactions** 120–200ms (hover, tab change, button press). +- **Reveal-on-scroll** (`.reveal`) is **pure CSS** via `animation-timeline: + view()` — progressive enhancement: browsers without support show content + immediately, and `prefers-reduced-motion` disables it entirely. +- Animate only `opacity`/`transform`. Never block input. + +--- + +## 8. The bar motif + +The solid rectangle from the OpenDAL wordmark is the system's signature mark. + +- `.odl-bar` — a sharp solid rectangle for dividers/emphasis. +- `.odl-eyebrow` — the canonical section kicker: monospace, uppercase, led by a + small **accent bar**. Used above every section title. +- As a top-border accent on principle cards, and as the section-title marker. + +Used consistently, the bar makes the minimal system unmistakably OpenDAL. + +--- + +## 9. Component patterns + +| Component | Class(es) | Notes | +| ---------------- | ------------------------------------------ | ------------------------------------------------ | +| Primary button | `.btn .btnPrimary` | Solid `action-bg`; one primary action per view. | +| Secondary button | `.btn .btnSecondary` | Hairline outline, ink text. | +| On-dark buttons | `.btnOnDark` / `.btnOnDarkGhost` | For the dark CTA panel. | +| Eyebrow | `.odl-eyebrow` (global) | Mono + accent bar; section kicker. | +| Code window | `CodeTabs` | Window chrome + language tabs + Prism highlight. | +| Value card | `.valueCard` | Mono index, title, body; 1px-gap grid. | +| Service chip | `.serviceChip` | Mono name + 15px icon, grouped by family. | +| Binding / layer | `.bindingCard` / `.layerItem` | Icon + mono label in a hairline grid. | + +Layout grids use a **1px gap over a border-colored background** to render crisp +hairline dividers between cells — a precise, technical signature. + +--- + +## 10. Do / Don't + +**Do** +- Lead with ink; use blue for interaction and emphasis. +- Use monospace for labels, indices, figures and code. +- Keep one primary CTA per view; separate secondary actions. +- Reach for hairlines and spacing before shadows and fills. +- Design and test light **and** dark together. + +**Don't** +- Introduce a second accent hue or gradients-as-decoration. +- Use emoji as icons (use the existing SVG sets in `static/img/*`). +- Hardcode hex in components — consume `--odl-*` tokens. +- Add motion that conveys nothing or ignores `prefers-reduced-motion`. + +--- + +## 11. Accessibility checklist + +- [ ] Text/accent contrast ≥ 4.5:1 (AA) in light and dark. +- [ ] Visible `:focus-visible` ring (accent, 2px) on every interactive element. +- [ ] Decorative icons use empty `alt`; meaning carried by adjacent text. +- [ ] Code tabs are a proper `tablist`/`tab`/`tabpanel` with arrow-key support. +- [ ] `prefers-reduced-motion` respected globally. +- [ ] Layout holds at 375 / 768 / 1024 / 1440px without horizontal scroll. diff --git a/website/docusaurus.config.js b/website/docusaurus.config.js index d32ff7f18..72e659482 100644 --- a/website/docusaurus.config.js +++ b/website/docusaurus.config.js @@ -96,7 +96,7 @@ const config = { /** @type {import('@docusaurus/preset-classic').Options} */ ({ docs: { - routeBasePath: "/", + routeBasePath: "docs", sidebarPath: require.resolve("./docs/sidebars.js"), editUrl: "https://github.com/apache/opendal/tree/main/website/", showLastUpdateAuthor: true, @@ -136,10 +136,6 @@ const config = { "@docusaurus/plugin-client-redirects", { redirects: [ - { - from: "/docs/vision", - to: "/vision", - }, { from: "/discord", to: "https://discord.com/invite/XQy8yGR2dg", @@ -149,6 +145,20 @@ const config = { to: "https://lists.apache.org/[email protected]", }, ], + // The landing page now owns "/", so docs moved from "/" to "/docs/". + // Keep every legacy doc URL working by redirecting the old prefix-less + // path to its new /docs/ location. The docs home ("/docs/") is excluded + // so it never collides with the landing page at "/". + createRedirects(existingPath) { + if (existingPath.startsWith("/docs/") && existingPath !== "/docs/") { + const legacy = existingPath.replace(/^\/docs\//, "/"); + // Avoid colliding with root-level pages that are not docs. + if (legacy !== "/download/") { + return [legacy]; + } + } + return undefined; + }, }, ], require.resolve("docusaurus-lunr-search"), @@ -273,7 +283,8 @@ const config = { // image: 'img/opendal-social-card.jpg', colorMode: { defaultMode: "light", - disableSwitch: true, + disableSwitch: false, + respectPrefersColorScheme: true, }, navbar: { logo: { diff --git a/website/src/components/landing/CodeTabs.jsx b/website/src/components/landing/CodeTabs.jsx new file mode 100644 index 000000000..cff47a506 --- /dev/null +++ b/website/src/components/landing/CodeTabs.jsx @@ -0,0 +1,112 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React, { useEffect, useId, useRef, useState } from "react"; +import CodeBlock from "@theme/CodeBlock"; +import styles from "./styles.module.css"; + +// A framed "code window" with language tabs. Wraps Docusaurus' CodeBlock so the +// snippets get the same Prism highlighting as the docs. +// +// The header doubles as a terminal-style prompt: a sample's `install` command +// is shown there (tracking the active tab), so install lives in the title bar +// rather than a separate band. With `equalize`, the code area animates its +// height to fit the active snippet — no dead space for short snippets, and the +// left hero column never reflows (its width is locked and it is the taller +// column, so only the code window resizes). +export default function CodeTabs({ samples, title, equalize = false }) { + const [active, setActive] = useState(0); + const baseId = useId(); + const current = samples[active]; + const heading = current.install || title; + + const innerRef = useRef(null); + const [bodyHeight, setBodyHeight] = useState(undefined); + + useEffect(() => { + if (!equalize || !innerRef.current) return undefined; + const measure = () => setBodyHeight(innerRef.current?.offsetHeight); + measure(); + const ro = + typeof ResizeObserver !== "undefined" + ? new ResizeObserver(measure) + : null; + if (ro && innerRef.current) ro.observe(innerRef.current); + return () => ro?.disconnect(); + }, [active, equalize]); + + function onKeyDown(event) { + if (event.key !== "ArrowRight" && event.key !== "ArrowLeft") return; + event.preventDefault(); + const dir = event.key === "ArrowRight" ? 1 : -1; + setActive((i) => (i + dir + samples.length) % samples.length); + } + + return ( + <div className={styles.codeWindow}> + <div className={styles.windowBar}> + <div className={styles.windowDots} aria-hidden="true"> + <span /> + <span /> + <span /> + </div> + <span className={styles.windowTitle}>{heading}</span> + </div> + + <div + className={styles.codeTabs} + role="tablist" + aria-label="Choose a language" + onKeyDown={onKeyDown} + > + {samples.map((sample, i) => ( + <button + key={sample.id} + type="button" + role="tab" + id={`${baseId}-tab-${sample.id}`} + aria-selected={i === active} + aria-controls={`${baseId}-panel`} + tabIndex={i === active ? 0 : -1} + className={`${styles.codeTab} ${ + i === active ? styles.codeTabActive : "" + }`} + onClick={() => setActive(i)} + > + {sample.label} + </button> + ))} + </div> + + <div + className={`${styles.codeBody} ${equalize ? styles.codeBodyAnimated : ""}`} + style={ + equalize && bodyHeight != null ? { height: bodyHeight } : undefined + } + role="tabpanel" + id={`${baseId}-panel`} + aria-labelledby={`${baseId}-tab-${current.id}`} + > + <div ref={innerRef}> + <CodeBlock language={current.language}>{current.code}</CodeBlock> + </div> + </div> + </div> + ); +} diff --git a/website/src/components/landing/data.js b/website/src/components/landing/data.js new file mode 100644 index 000000000..78c82c455 --- /dev/null +++ b/website/src/components/landing/data.js @@ -0,0 +1,311 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +// Content model for the landing page. All copy and catalog data lives here so +// the section components stay presentational. Code samples are taken from the +// real binding APIs so the page never drifts from the libraries it advertises. + +export const REPO_URL = "https://github.com/apache/opendal"; +export const DOCS_URL = "/docs/"; +export const DISCORD_URL = "https://discord.gg/XQy8yGR2dg"; + +export const heroStats = [ + { value: "50+", label: "services" }, + { value: "17", label: "languages" }, + { value: "5k+", label: "stars" }, + { value: "10M+", label: "downloads" }, +]; + +// Same Operator contract, one mental model, every ecosystem. +export const codeSamples = [ + { + id: "rust", + label: "Rust", + language: "rust", + install: "$ cargo add opendal", + code: `use opendal::services::S3; +use opendal::Operator; + +// Configure a backend once, then use one Operator. +let builder = S3::default().bucket("data"); +let op = Operator::new(builder)?.finish(); + +op.write("hello.txt", "Hello, World!").await?; +let bytes = op.read("hello.txt").await?;`, + }, + { + id: "python", + label: "Python", + language: "python", + install: "$ pip install opendal", + code: `import opendal + +# Configure a backend once, then use one Operator. +op = opendal.Operator("s3", bucket="data") + +op.write("hello.txt", b"Hello, World!") +data = op.read("hello.txt")`, + }, + { + id: "go", + label: "Go", + language: "go", + install: "$ go get github.com/apache/opendal/bindings/go", + code: `import ( + "github.com/apache/opendal-go-services/s3" + opendal "github.com/apache/opendal/bindings/go" +) + +// Configure a backend once, then use one Operator. +op, _ := opendal.NewOperator(s3.Scheme, opendal.OperatorOptions{ + "bucket": "data", +}) + +op.Write("hello.txt", []byte("Hello, World!")) +data, _ := op.Read("hello.txt")`, + }, + { + id: "java", + label: "Java", + language: "java", + install: "org.apache.opendal:opendal-java", + code: `import org.apache.opendal.AsyncOperator; +import java.util.Map; + +// Configure a backend once, then use one Operator. +var conf = Map.of("bucket", "data"); +try (var op = AsyncOperator.of("s3", conf)) { + op.write("hello.txt", "Hello, World!").join(); + byte[] data = op.read("hello.txt").join(); +}`, + }, + { + id: "node", + label: "Node.js", + language: "javascript", + install: "$ npm install opendal", + code: `import { Operator } from "opendal"; + +// Configure a backend once, then use one Operator. +const op = new Operator("s3", { bucket: "data" }); + +await op.write("hello.txt", "Hello, World!"); +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", + title: "One API, all storage", + body: "Object storage, file systems, cloud SaaS, databases, protocols and key-value services reached through a single Operator and one mental model.", + }, + { + index: "02", + title: "Zero-cost core", + body: "Built in Rust with composable services and layers. Compile in only the backends and capabilities you use, and pay for nothing else.", + }, + { + index: "03", + title: "Production-ready by composition", + body: "Stack retry, timeout, logging, tracing, metrics, throttling and concurrency limits as reusable layers — no rewrites, no glue code.", + }, + { + index: "04", + title: "Open and extensible", + body: "Add services, layers and language bindings without forking the model. Developed in the open and governed the Apache Way.", + }, +]; + +// Real adopters, drawn from the project vision. Rendered as a wordmark wall. +export const adopters = [ + { name: "Databend", href: "https://github.com/databendlabs/databend" }, + { name: "RisingWave", href: "https://github.com/risingwavelabs/risingwave" }, + { name: "GreptimeDB", href: "https://github.com/GreptimeTeam/greptimedb" }, + { name: "Apache Iceberg Rust", href: "https://github.com/apache/iceberg-rust" }, + { name: "Vector", href: "https://github.com/vectordotdev/vector" }, + { name: "sccache", href: "https://github.com/mozilla/sccache" }, + { name: "Pants", href: "https://github.com/pantsbuild/pants" }, + { name: "Shuttle", href: "https://github.com/shuttle-hq/shuttle" }, +]; + +// Curated, icon-backed slice of the 50+ supported services, grouped by family. +export const serviceGroups = [ + { + category: "Object Storage", + services: [ + { name: "s3", icon: "/img/services/s3.svg" }, + { name: "gcs", icon: "/img/services/gcs.png" }, + { name: "azblob", icon: "/img/services/azure.svg" }, + { name: "oss", icon: "/img/services/oss.svg" }, + { name: "cos", icon: "/img/services/cos.svg" }, + { name: "obs", icon: "/img/services/obs.png" }, + { name: "b2", icon: "/img/services/backblaze.png" }, + { name: "tos", icon: "/img/services/volcengine.png" }, + ], + }, + { + category: "File Storage", + services: [ + { name: "fs", icon: "/img/services/opendal.svg" }, + { name: "hdfs", icon: "/img/services/hadoop.ico" }, + { name: "alluxio", icon: "/img/services/alluxio.svg" }, + { name: "lakefs", icon: "/img/services/lakefs.ico" }, + { name: "ipfs", icon: "/img/services/ipfs.ico" }, + { name: "dbfs", icon: "/img/services/databricks.png" }, + ], + }, + { + category: "Cloud SaaS", + services: [ + { name: "gdrive", icon: "/img/services/gdrive.png" }, + { name: "dropbox", icon: "/img/services/dropbox.ico" }, + { name: "onedrive", icon: "/img/services/onedrive.svg" }, + { name: "hf", icon: "/img/services/huggingface.ico" }, + { name: "github", icon: "/img/services/github.svg" }, + { name: "koofr", icon: "/img/services/koofr.ico" }, + ], + }, + { + category: "Protocols", + services: [ + { name: "http", icon: "/img/services/http.png" }, + { name: "ftp", icon: "/img/services/ftp.png" }, + { name: "webdav", icon: "/img/services/webdav.png" }, + { name: "sftp", icon: "/img/services/sftp.png" }, + ], + }, + { + category: "Databases", + services: [ + { name: "sqlite", icon: "/img/services/sqlite.ico" }, + { name: "mysql", icon: "/img/services/mysql.ico" }, + { name: "postgresql", icon: "/img/services/postgresql.ico" }, + { name: "mongodb", icon: "/img/services/mongodb.ico" }, + { name: "surrealdb", icon: "/img/services/surrealdb.svg" }, + { name: "d1", icon: "/img/services/cloudflare.ico" }, + ], + }, + { + category: "Key-Value", + services: [ + { name: "redis", icon: "/img/services/redis.png" }, + { name: "etcd", icon: "/img/services/etcd.png" }, + { name: "rocksdb", icon: "/img/services/rocksdb.png" }, + { name: "memcached", icon: "/img/services/memcached.png" }, + { name: "tikv", icon: "/img/services/tikv.png" }, + { name: "foundationdb", icon: "/img/services/foundationdb.png" }, + ], + }, +]; + +export const bindings = [ + { name: "Rust", icon: "/img/bindings/rust.svg" }, + { name: "Python", icon: "/img/bindings/python.svg" }, + { name: "Java", icon: "/img/bindings/java.svg" }, + { name: "Go", icon: "/img/bindings/go.svg" }, + { name: "Node.js", icon: "/img/bindings/nodejs.svg" }, + { name: "C", icon: "/img/bindings/c.svg" }, + { name: "C++", icon: "/img/bindings/cpp.svg" }, + { name: ".NET", icon: "/img/bindings/dotnet.svg" }, + { name: "Ruby", icon: "/img/bindings/ruby.svg" }, + { name: "PHP", icon: "/img/bindings/php.svg" }, + { name: "Swift", icon: "/img/bindings/swift.svg" }, + { name: "Haskell", icon: "/img/bindings/haskell.svg" }, + { name: "OCaml", icon: "/img/bindings/ocaml.svg" }, + { name: "Lua", icon: "/img/bindings/lua.svg" }, + { name: "Dart", icon: "/img/bindings/dart.svg" }, + { name: "D", icon: "/img/bindings/d.svg" }, + { name: "Zig", icon: "/img/bindings/zig.svg" }, +]; + +export const layers = [ + { + name: "RetryLayer", + icon: "/img/layers/retry.svg", + desc: "Recover from transient failures automatically.", + }, + { + name: "TimeoutLayer", + icon: "/img/layers/timeout.svg", + desc: "Bound slow or hanging operations.", + }, + { + name: "LoggingLayer", + icon: "/img/layers/logging.svg", + desc: "Emit structured operation logs.", + }, + { + name: "TracingLayer", + icon: "/img/layers/tracing.svg", + desc: "Trace requests across systems.", + }, + { + name: "MetricsLayer", + icon: "/img/layers/metrics.svg", + desc: "Export operation metrics.", + }, + { + 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.", + }, +]; + +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.", + }, + { + title: "Extensible Architecture", + body: "Independent services, layers and bindings you can stack and extend.", + }, +]; diff --git a/website/src/components/landing/sections.jsx b/website/src/components/landing/sections.jsx new file mode 100644 index 000000000..f553ca2f7 --- /dev/null +++ b/website/src/components/landing/sections.jsx @@ -0,0 +1,382 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from "react"; +import Link from "@docusaurus/Link"; +import { useBaseUrlUtils } from "@docusaurus/useBaseUrl"; +import CodeTabs from "./CodeTabs"; +import styles from "./styles.module.css"; +import { + REPO_URL, + DOCS_URL, + DISCORD_URL, + heroStats, + codeSamples, + valueProps, + adopters, + serviceGroups, + bindings, + layers, + layeredCode, + principles, +} from "./data"; + +const operatorContract = [ + { + verb: "write · read", + text: "Write and read whole objects, or stream byte ranges.", + }, + { verb: "stat", text: "Fetch metadata without transferring the body." }, + { verb: "list", text: "Walk entries lazily — scoped or fully recursive." }, + { verb: "delete", text: "Remove single keys or whole batches in one call." }, +]; + +export function Hero() { + return ( + <header className={styles.hero}> + <div className={`${styles.heroGrid} odl-grid-bg`} aria-hidden="true" /> + <div className="odl-container"> + <div className={styles.heroInner}> + <div> + <span className="odl-eyebrow"> + Apache OpenDAL™ — Open Data Access Layer + </span> + <h1 className={styles.heroTitle}> + One Layer, + <br /> + <span className={styles.heroTitleAccent}>All Storage.</span> + </h1> + <p className={styles.heroLede}> + One zero-cost Operator for object storage, file systems, databases + and more — in the language you already ship. + </p> + <div className={styles.heroActions}> + <Link + className={`${styles.btn} ${styles.btnPrimary}`} + to={DOCS_URL} + > + Get started <span className={styles.btnArrow}>→</span> + </Link> + <Link + className={`${styles.btn} ${styles.btnSecondary}`} + to={REPO_URL} + > + View on GitHub + </Link> + </div> + <div className={styles.heroStats}> + {heroStats.map((stat) => ( + <div className={styles.heroStat} key={stat.label}> + <span className={styles.heroStatValue}>{stat.value}</span> + <span className={styles.heroStatLabel}>{stat.label}</span> + </div> + ))} + </div> + </div> + <div className={styles.heroAside}> + <CodeTabs samples={codeSamples} title="quickstart" equalize /> + </div> + </div> + </div> + </header> + ); +} + +export function UsedBy() { + return ( + <section className={styles.usedBy}> + <div className="odl-container"> + <div className={styles.usedByLabel}> + <span className="odl-eyebrow">Trusted in production by</span> + </div> + <div className={styles.usedByTrack}> + {adopters.map((a) => ( + <Link key={a.name} className={styles.adopter} to={a.href}> + {a.name} + </Link> + ))} + </div> + </div> + </section> + ); +} + +export function ValueProps() { + return ( + <section className={styles.section}> + <div className="odl-container"> + <div className={styles.sectionHead}> + <span className="odl-eyebrow">Why OpenDAL</span> + <h2 className={styles.sectionTitle}> + A storage layer you can build production on. + </h2> + <p className={styles.sectionLede}> + OpenDAL turns one vision — <em>One Layer, All Storage</em> — into a + practical foundation for applications, libraries and data systems. + </p> + </div> + <div className={`${styles.valueGrid} ${styles.reveal}`}> + {valueProps.map((v) => ( + <article className={styles.valueCard} key={v.index}> + <span className={styles.valueIndex}>{v.index}</span> + <h3 className={styles.valueCardTitle}>{v.title}</h3> + <p className={styles.valueCardBody}>{v.body}</p> + </article> + ))} + </div> + </div> + </section> + ); +} + +export function OperatorSection() { + return ( + <section className={`${styles.section} ${styles.sectionSubtle}`}> + <div className="odl-container"> + <div className={styles.operatorInner}> + <div> + <span className="odl-eyebrow">The Operator</span> + <h2 className={styles.sectionTitle}>Configure once. Access anything.</h2> + <p className={styles.sectionLede}> + Every backend is reached through one Operator with the same verbs. + Switch storage or switch language — your logic does not change. + </p> + <div className={styles.operatorContract}> + {operatorContract.map((row) => ( + <div className={styles.contractRow} key={row.verb}> + <span className={styles.contractVerb}>{row.verb}</span> + <span className={styles.contractText}>{row.text}</span> + </div> + ))} + </div> + </div> + <CodeTabs samples={codeSamples} title="the same contract, every language" equalize /> + </div> + </div> + </section> + ); +} + +export function Services() { + const { withBaseUrl } = useBaseUrlUtils(); + return ( + <section className={styles.section}> + <div className="odl-container"> + <div className={styles.sectionHead}> + <span className="odl-eyebrow">Services</span> + <h2 className={styles.sectionTitle}> + 50+ storage services, one interface. + </h2> + <p className={styles.sectionLede}> + Enable only the backends your application needs. The Operator + contract stays identical across every one. + </p> + </div> + <div className={`${styles.serviceGroups} ${styles.reveal}`}> + {serviceGroups.map((group) => ( + <div className={styles.serviceGroup} key={group.category}> + <h3 className={styles.serviceGroupTitle}>{group.category}</h3> + <div className={styles.serviceChips}> + {group.services.map((s) => ( + <span className={styles.serviceChip} key={s.name}> + <img + src={withBaseUrl(s.icon)} + alt="" + width="15" + height="15" + loading="lazy" + /> + {s.name} + </span> + ))} + </div> + </div> + ))} + </div> + <div className={styles.servicesFoot}> + <span className={styles.servicesMore}> + …and many more, from HDFS to Hugging Face. + </span> + <Link + className={`${styles.btn} ${styles.btnSecondary}`} + to={DOCS_URL} + > + Browse all services <span className={styles.btnArrow}>→</span> + </Link> + </div> + </div> + </section> + ); +} + +export function Bindings() { + const { withBaseUrl } = useBaseUrlUtils(); + return ( + <section className={`${styles.section} ${styles.sectionSubtle}`}> + <div className="odl-container"> + <div className={styles.sectionHead}> + <span className="odl-eyebrow">Bindings</span> + <h2 className={styles.sectionTitle}>Ship it in your language.</h2> + <p className={styles.sectionLede}> + Each binding exposes the same OpenDAL service model while following + its own ecosystem's conventions. + </p> + </div> + <div className={`${styles.bindingGrid} ${styles.reveal}`}> + {bindings.map((b) => ( + <div className={styles.bindingCard} key={b.name}> + <img + src={withBaseUrl(b.icon)} + alt="" + width="30" + height="30" + loading="lazy" + /> + <span className={styles.bindingName}>{b.name}</span> + </div> + ))} + </div> + </div> + </section> + ); +} + +export function Layers() { + const { withBaseUrl } = useBaseUrlUtils(); + 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> + <h2 className={styles.sectionTitle}> + Built by a community, for the commons. + </h2> + <p className={styles.sectionLede}> + Apache OpenDAL™ graduated to a top-level project in 2024. Five + principles guide every decision we make. + </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> + ))} + </div> + </div> + </section> + ); +} + +export function FinalCta() { + return ( + <section className={styles.section}> + <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 + </span> + <h2 className={styles.finalCtaTitle}> + One layer for all your storage. + </h2> + <p className={styles.finalCtaLede}> + Join the infrastructure builders, platform teams and application + developers accessing data freely, painlessly and efficiently. + </p> + <div className={styles.finalCtaActions}> + <Link + className={`${styles.btn} ${styles.btnOnDark}`} + to={DOCS_URL} + > + Get started <span className={styles.btnArrow}>→</span> + </Link> + <Link + className={`${styles.btn} ${styles.btnOnDarkGhost}`} + to={REPO_URL} + > + Star on GitHub + </Link> + <Link + className={`${styles.btn} ${styles.btnOnDarkGhost}`} + to={DISCORD_URL} + > + Join Discord + </Link> + </div> + </div> + </div> + </div> + </section> + ); +} diff --git a/website/src/components/landing/styles.module.css b/website/src/components/landing/styles.module.css new file mode 100644 index 000000000..edf7b5d05 --- /dev/null +++ b/website/src/components/landing/styles.module.css @@ -0,0 +1,681 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/* ===== Shared building blocks ============================================ */ +.section { + padding-block: clamp(3.5rem, 2rem + 7vw, 7rem); + border-top: 1px solid var(--odl-border); +} +.sectionSubtle { + background: var(--odl-bg-subtle); +} + +.sectionHead { + max-width: 46rem; + margin-bottom: var(--odl-space-7); +} +.sectionTitle { + font-size: var(--odl-text-2xl); + line-height: var(--odl-leading-snug); + font-weight: 650; + color: var(--odl-fg-strong); + margin: var(--odl-space-4) 0 0; +} +.sectionLede { + font-size: var(--odl-text-md); + line-height: var(--odl-leading-normal); + color: var(--odl-fg-muted); + margin: var(--odl-space-3) 0 0; + max-width: 40rem; +} + +/* Buttons --------------------------------------------------------------- */ +.btn { + display: inline-flex; + align-items: center; + justify-content: center; + gap: var(--odl-space-2); + height: 2.875rem; + padding-inline: var(--odl-space-5); + border-radius: var(--odl-radius-md); + border: 1px solid transparent; + font-size: var(--odl-text-sm); + font-weight: 600; + letter-spacing: -0.005em; + text-decoration: none; + cursor: pointer; + transition: background-color var(--odl-dur) var(--odl-ease), + border-color var(--odl-dur) var(--odl-ease), + color var(--odl-dur) var(--odl-ease), transform var(--odl-dur-fast) + var(--odl-ease); +} +.btn:hover { + text-decoration: none; +} +.btn:active { + transform: translateY(1px); +} +.btnPrimary { + background: var(--odl-action-bg); + color: var(--odl-action-fg); +} +.btnPrimary:hover { + background: var(--odl-action-bg-hover); + color: var(--odl-action-fg); +} +.btnSecondary { + background: transparent; + color: var(--odl-fg-strong); + border-color: var(--odl-border-strong); +} +.btnSecondary:hover { + color: var(--odl-fg-strong); + border-color: var(--odl-fg-strong); + background: var(--odl-surface-2); +} +.btnArrow { + transition: transform var(--odl-dur) var(--odl-ease-out); +} +.btn:hover .btnArrow { + transform: translateX(3px); +} + +/* ===== Hero ============================================================== */ +.hero { + position: relative; + overflow: hidden; + padding-top: clamp(1.75rem, 1rem + 2.4vw, 3rem); + padding-bottom: clamp(3rem, 2rem + 5vw, 5.5rem); +} +.heroGrid { + position: absolute; + inset: 0; + -webkit-mask-image: radial-gradient( + 120% 90% at 70% 0%, + #000 0%, + transparent 72% + ); + mask-image: radial-gradient(120% 90% at 70% 0%, #000 0%, transparent 72%); + pointer-events: none; +} +.heroInner { + position: relative; + display: grid; + grid-template-columns: minmax(0, 1.05fr) minmax(0, 0.95fr); + gap: clamp(2rem, 1rem + 4vw, 4.5rem); + /* Column widths are locked by minmax(0, fr), so the left column never + reflows; center the columns to balance the fixed-height code window. */ + align-items: center; +} +.heroTitle { + font-size: var(--odl-text-display); + line-height: var(--odl-leading-tight); + font-weight: 700; + letter-spacing: var(--odl-tracking-tight); + color: var(--odl-fg-strong); + margin: var(--odl-space-5) 0 0; +} +.heroTitleAccent { + color: var(--odl-accent); +} +.heroLede { + font-size: var(--odl-text-md); + line-height: var(--odl-leading-normal); + color: var(--odl-fg-muted); + margin: var(--odl-space-5) 0 0; + max-width: 34rem; +} +.heroActions { + display: flex; + flex-wrap: wrap; + gap: var(--odl-space-3); + margin-top: var(--odl-space-6); +} +.heroStats { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: var(--odl-space-4); + margin-top: var(--odl-space-8); + border-top: 1px solid var(--odl-border); + padding-top: var(--odl-space-5); +} +.heroStat { + display: flex; + flex-direction: column; + gap: 2px; +} +.heroStatValue { + font-family: var(--odl-font-mono); + font-size: var(--odl-text-xl); + font-weight: 600; + color: var(--odl-fg-strong); + font-variant-numeric: tabular-nums; +} +.heroStatLabel { + font-size: var(--odl-text-xs); + color: var(--odl-fg-subtle); +} + +/* ===== Code window (shared by Hero & Operator) ========================== */ +.codeWindow { + border: 1px solid var(--odl-border); + border-radius: var(--odl-radius-lg); + background: var(--odl-surface); + box-shadow: var(--odl-shadow-lg); + overflow: hidden; +} +.windowBar { + display: flex; + align-items: center; + gap: var(--odl-space-3); + padding: var(--odl-space-3) var(--odl-space-4); + border-bottom: 1px solid var(--odl-border); + background: var(--odl-surface-2); +} +.windowDots { + display: flex; + gap: 6px; +} +.windowDots span { + width: 11px; + height: 11px; + border-radius: var(--odl-radius-pill); + background: var(--odl-border-strong); +} +.windowTitle { + flex: 1 1 auto; + min-width: 0; + font-family: var(--odl-font-mono); + font-size: var(--odl-text-xs); + color: var(--odl-fg-muted); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +.codeTabs { + display: flex; + gap: 2px; + padding: var(--odl-space-2) var(--odl-space-3) 0; + border-bottom: 1px solid var(--odl-border); + background: var(--odl-surface-2); + overflow-x: auto; + scrollbar-width: none; +} +.codeTabs::-webkit-scrollbar { + display: none; +} +.codeTab { + appearance: none; + background: transparent; + border: 0; + border-bottom: 2px solid transparent; + padding: var(--odl-space-2) var(--odl-space-3) var(--odl-space-3); + font-family: var(--odl-font-mono); + font-size: var(--odl-text-xs); + color: var(--odl-fg-subtle); + cursor: pointer; + white-space: nowrap; + transition: color var(--odl-dur) var(--odl-ease), + border-color var(--odl-dur) var(--odl-ease); +} +.codeTab:hover { + color: var(--odl-fg); +} +.codeTabActive { + color: var(--odl-fg-strong); + border-bottom-color: var(--odl-accent); +} +/* The code area animates its height to fit the active snippet: no dead space + for short snippets, and switching language glides between sizes. The left + hero column never moves (its width is locked and it is the taller column), + so only this window resizes. */ +.codeBodyAnimated { + overflow: hidden; + background: var(--odl-code-bg); + transition: height var(--odl-dur-slow) var(--odl-ease); +} +@media (prefers-reduced-motion: reduce) { + .codeBodyAnimated { + transition: none; + } +} + +/* Tighten the embedded Docusaurus CodeBlock to fit the window chrome */ +.codeBody :global(.theme-code-block), +.codeBody :global(div[class*="codeBlockContainer"]) { + margin: 0; + border: 0; + border-radius: 0; + box-shadow: none; +} +.codeBody :global(pre) { + border: 0 !important; + border-radius: 0 !important; + margin: 0; + background: var(--odl-code-bg) !important; + font-size: 0.8125rem; +} + +/* ===== Used-by wall ===================================================== */ +.usedBy { + padding-block: clamp(2.25rem, 1.5rem + 3vw, 3.25rem); + border-top: 1px solid var(--odl-border); +} +.usedByLabel { + text-align: center; + margin-bottom: var(--odl-space-5); +} +.usedByTrack { + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: center; + gap: var(--odl-space-4) var(--odl-space-7); +} +.adopter { + font-family: var(--odl-font-mono); + font-size: var(--odl-text-md); + font-weight: 500; + color: var(--odl-fg-muted); + text-decoration: none; + transition: color var(--odl-dur) var(--odl-ease); + white-space: nowrap; +} +.adopter:hover { + color: var(--odl-fg-strong); + text-decoration: none; +} + +/* ===== Value props ====================================================== */ +.valueGrid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 1px; + background: var(--odl-border); + border: 1px solid var(--odl-border); + border-radius: var(--odl-radius-lg); + overflow: hidden; +} +.valueCard { + background: var(--odl-surface); + padding: clamp(1.5rem, 1rem + 2vw, 2.5rem); + display: flex; + flex-direction: column; + gap: var(--odl-space-3); + transition: background-color var(--odl-dur) var(--odl-ease); +} +.valueCard:hover { + background: var(--odl-surface-2); +} +.valueIndex { + font-family: var(--odl-font-mono); + font-size: var(--odl-text-xs); + font-weight: 500; + letter-spacing: var(--odl-tracking-label); + color: var(--odl-accent); +} +.valueCardTitle { + font-size: var(--odl-text-lg); + font-weight: 650; + color: var(--odl-fg-strong); + margin: 0; + letter-spacing: var(--odl-tracking-tight); +} +.valueCardBody { + font-size: var(--odl-text-base); + line-height: var(--odl-leading-normal); + color: var(--odl-fg-muted); + margin: 0; +} + +/* ===== Operator section ================================================= */ +.operatorInner { + display: grid; + grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.1fr); + gap: clamp(2rem, 1rem + 4vw, 4rem); + align-items: center; +} +.operatorContract { + margin-top: var(--odl-space-6); + display: flex; + flex-direction: column; + gap: var(--odl-space-4); +} +.contractRow { + display: flex; + align-items: baseline; + gap: var(--odl-space-4); + padding-bottom: var(--odl-space-4); + border-bottom: 1px solid var(--odl-border); +} +.contractRow:last-child { + border-bottom: 0; + padding-bottom: 0; +} +.contractVerb { + font-family: var(--odl-font-mono); + font-size: var(--odl-text-sm); + font-weight: 600; + color: var(--odl-accent); + min-width: 5.5rem; +} +.contractText { + font-size: var(--odl-text-base); + color: var(--odl-fg-muted); + line-height: var(--odl-leading-snug); +} + +/* ===== Services ========================================================= */ +.serviceGroups { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 1px; + background: var(--odl-border); + border: 1px solid var(--odl-border); + border-radius: var(--odl-radius-lg); + overflow: hidden; +} +.serviceGroup { + background: var(--odl-surface); + padding: clamp(1.25rem, 1rem + 1vw, 1.75rem); +} +.serviceGroupTitle { + display: flex; + align-items: center; + gap: var(--odl-space-3); + font-family: var(--odl-font-mono); + font-size: var(--odl-text-xs); + font-weight: 500; + letter-spacing: var(--odl-tracking-label); + text-transform: uppercase; + color: var(--odl-fg-subtle); + margin-bottom: var(--odl-space-4); +} +.serviceGroupTitle::before { + content: ""; + width: 0.875rem; + height: 0.375rem; + background: var(--odl-accent); + flex: none; +} +.serviceChips { + display: flex; + flex-wrap: wrap; + gap: var(--odl-space-2); +} +.serviceChip { + display: inline-flex; + align-items: center; + gap: var(--odl-space-2); + padding: 0.3rem 0.6rem; + border: 1px solid var(--odl-border); + border-radius: var(--odl-radius-sm); + background: var(--odl-bg); + font-family: var(--odl-font-mono); + font-size: var(--odl-text-xs); + color: var(--odl-fg); +} +.serviceChip img { + width: 15px; + height: 15px; + object-fit: contain; +} +.servicesFoot { + margin-top: var(--odl-space-6); + display: flex; + align-items: center; + justify-content: space-between; + flex-wrap: wrap; + gap: var(--odl-space-3); +} +.servicesMore { + font-size: var(--odl-text-sm); + color: var(--odl-fg-muted); +} + +/* ===== Bindings ========================================================= */ +.bindingGrid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(118px, 1fr)); + gap: 1px; + background: var(--odl-border); + border: 1px solid var(--odl-border); + border-radius: var(--odl-radius-lg); + overflow: hidden; +} +.bindingCard { + background: var(--odl-surface); + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: var(--odl-space-3); + padding: var(--odl-space-5) var(--odl-space-3); + transition: background-color var(--odl-dur) var(--odl-ease); +} +.bindingCard:hover { + background: var(--odl-surface-2); +} +.bindingCard img { + width: 30px; + height: 30px; + object-fit: contain; +} +.bindingName { + font-family: var(--odl-font-mono); + font-size: var(--odl-text-xs); + color: var(--odl-fg-muted); +} + +/* ===== Layers =========================================================== */ +.layersInner { + display: grid; + grid-template-columns: minmax(0, 1.05fr) minmax(0, 0.95fr); + gap: clamp(2rem, 1rem + 4vw, 4rem); + align-items: start; +} +.layerGrid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 1px; + background: var(--odl-border); + border: 1px solid var(--odl-border); + border-radius: var(--odl-radius-lg); + overflow: hidden; +} +.layerItem { + background: var(--odl-surface); + padding: var(--odl-space-4); + display: flex; + gap: var(--odl-space-3); + align-items: flex-start; +} +.layerIcon { + width: 26px; + height: 26px; + flex: none; + object-fit: contain; + margin-top: 2px; +} +.layerName { + font-family: var(--odl-font-mono); + font-size: var(--odl-text-sm); + font-weight: 600; + color: var(--odl-fg-strong); + margin: 0; +} +.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; +} + +/* ===== Final CTA ======================================================== */ +.finalCta { + position: relative; + overflow: hidden; + background: var(--odl-ink-950); + color: #fff; + border-radius: var(--odl-radius-lg); + 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; +} +.finalCtaTitle { + font-size: clamp(2rem, 1.3rem + 3vw, 3.25rem); + font-weight: 700; + letter-spacing: var(--odl-tracking-tight); + margin: var(--odl-space-4) 0 0; + color: #fff; +} +.finalCtaLede { + font-size: var(--odl-text-md); + color: rgba(255, 255, 255, 0.7); + margin: var(--odl-space-4) auto 0; + max-width: 34rem; +} +.finalCtaActions { + display: flex; + flex-wrap: wrap; + gap: var(--odl-space-3); + justify-content: center; + margin-top: var(--odl-space-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; + flex-direction: column; + align-items: center; +} + +/* ===== Reveal-on-scroll — pure CSS, progressive enhancement ============= */ +/* Content is never hidden without support: browsers lacking scroll-driven + animations (or users who prefer reduced motion) simply see it immediately. */ +@media (prefers-reduced-motion: no-preference) { + @supports (animation-timeline: view()) { + .reveal { + animation: odlReveal linear both; + animation-timeline: view(); + animation-range: entry 2% cover 18%; + } + } +} +@keyframes odlReveal { + from { + opacity: 0; + transform: translateY(18px); + } + to { + opacity: 1; + transform: none; + } +} + +/* ===== Responsive ======================================================= */ +@media (max-width: 996px) { + .heroInner, + .operatorInner, + .layersInner { + grid-template-columns: 1fr; + } + .serviceGroups { + grid-template-columns: repeat(2, 1fr); + } + .heroAside { + order: 2; + } +} + +@media (max-width: 640px) { + .valueGrid, + .serviceGroups { + grid-template-columns: 1fr; + } + .heroStats { + grid-template-columns: repeat(2, 1fr); + gap: var(--odl-space-5) var(--odl-space-4); + } + .heroActions .btn { + flex: 1 1 auto; + } +} diff --git a/website/src/css/custom.css b/website/src/css/custom.css index ff706266e..2ee0a5aa6 100644 --- a/website/src/css/custom.css +++ b/website/src/css/custom.css @@ -17,86 +17,422 @@ * under the License. */ -/** - * Any CSS included here will be global. The classic template - * bundles Infima by default. Infima is a CSS framework designed to - * work well for content-centric websites. - */ +/* ========================================================================== + OpenDAL Design System + -------------------------------------------------------------------------- + "One Layer, All Storage." A precise, engineering-grade visual system built + on a near-monochrome ink scale, a single precise-blue accent, and the + signature solid "bar" lifted from the OpenDAL wordmark. + + Token layers: + 1. Primitives — raw, theme-agnostic values (--odl-ink-*, --odl-blue-*) + 2. Semantic — meaning-bound, theme-aware roles (--odl-bg, --odl-fg ...) + 3. Infima map — semantic tokens projected onto Docusaurus/Infima vars so + docs, blog and community pages inherit the same system. + + Typography uses native system stacks on purpose: zero third-party requests + (ASF privacy-friendly), zero layout shift, instant global rendering. The + monospace stack is promoted to a brand motif (labels, indices, figures). + To adopt IBM Plex instead, self-host it and swap --odl-font-* below. + ========================================================================== */ -/* You can override the default Infima variables here. */ +/* ---- 1. Primitive tokens ------------------------------------------------- */ :root { - --ifm-color-primary: #44A1F6; - --ifm-color-primary-dark: #2692F5; - --ifm-color-primary-darker: #178AF4; - --ifm-color-primary-darkest: #0A72D2; - --ifm-color-primary-light: #62B0F7; - --ifm-color-primary-lighter: #71B8F8; - --ifm-color-primary-lightest: #9ECEFA; - --ifm-background-color: #ffffff; - --ifm-code-font-size: 95%; - --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1); - - --footer-font-color: black; + /* Ink — a cool, neutral near-black→white ramp */ + --odl-ink-0: #ffffff; + --odl-ink-50: #f6f8fa; + --odl-ink-100: #edf1f5; + --odl-ink-150: #e4e9ef; + --odl-ink-200: #d8dee6; + --odl-ink-300: #c2cad4; + --odl-ink-400: #98a2b0; + --odl-ink-500: #6e7886; + --odl-ink-600: #4f5864; + --odl-ink-700: #39414b; + --odl-ink-800: #262c34; + --odl-ink-850: #1b2027; + --odl-ink-900: #12161b; + --odl-ink-950: #0a0d11; + + /* Precise blue — the single brand accent */ + --odl-blue-50: #edf3ff; + --odl-blue-100: #d8e5ff; + --odl-blue-200: #b4ccff; + --odl-blue-300: #85a9ff; + --odl-blue-400: #5384ff; + --odl-blue-500: #2c63f5; + --odl-blue-600: #1e54e0; + --odl-blue-700: #1846be; + --odl-blue-800: #163a97; + --odl-blue-900: #163172; + + /* Functional signals (kept restrained, mostly for inherited docs UI) */ + --odl-green-500: #1a8f5e; + --odl-green-400: #36c08a; + --odl-amber-500: #b7791f; + --odl-amber-400: #e0a93b; + --odl-red-500: #d23b3b; + --odl-red-400: #f06363; + + /* Type families — system stacks with a clear engineering character */ + --odl-font-sans: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", + Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, + "Apple Color Emoji", "Segoe UI Emoji"; + --odl-font-mono: ui-monospace, "SF Mono", SFMono-Regular, "Cascadia Code", + "JetBrains Mono", "Roboto Mono", Menlo, Consolas, "Liberation Mono", + monospace; + + /* Type scale */ + --odl-text-2xs: 0.6875rem; /* 11px — mono micro-labels */ + --odl-text-xs: 0.75rem; /* 12px */ + --odl-text-sm: 0.875rem; /* 14px */ + --odl-text-base: 1rem; /* 16px */ + --odl-text-md: 1.125rem; /* 18px */ + --odl-text-lg: 1.375rem; /* 22px */ + --odl-text-xl: 1.75rem; /* 28px */ + --odl-text-2xl: 2.25rem; /* 36px */ + --odl-text-3xl: 3rem; /* 48px */ + --odl-text-display: clamp(2.75rem, 1.6rem + 5.2vw, 5rem); + --odl-leading-tight: 1.08; + --odl-leading-snug: 1.28; + --odl-leading-normal: 1.6; + --odl-tracking-tight: -0.022em; + --odl-tracking-label: 0.12em; + + /* Spacing — 4px rhythm */ + --odl-space-1: 0.25rem; + --odl-space-2: 0.5rem; + --odl-space-3: 0.75rem; + --odl-space-4: 1rem; + --odl-space-5: 1.5rem; + --odl-space-6: 2rem; + --odl-space-7: 3rem; + --odl-space-8: 4rem; + --odl-space-9: 6rem; + --odl-space-10: 8rem; + + /* Radius — restrained; the bar motif stays sharp (0) */ + --odl-radius-xs: 3px; + --odl-radius-sm: 5px; + --odl-radius-md: 8px; + --odl-radius-lg: 12px; + --odl-radius-pill: 999px; + + /* Layout */ + --odl-container: 1200px; + --odl-container-narrow: 880px; + --odl-gutter: clamp(1.25rem, 1rem + 2vw, 2.5rem); + + /* Motion */ + --odl-ease-out: cubic-bezier(0.16, 1, 0.3, 1); + --odl-ease: cubic-bezier(0.4, 0, 0.2, 1); + --odl-dur-fast: 120ms; + --odl-dur: 200ms; + --odl-dur-slow: 320ms; +} + +/* ---- 2. Semantic tokens — light (default) -------------------------------- */ +:root { + --odl-bg: var(--odl-ink-0); + --odl-bg-subtle: var(--odl-ink-50); + --odl-bg-inset: var(--odl-ink-100); + --odl-surface: var(--odl-ink-0); + --odl-surface-2: var(--odl-ink-50); + --odl-fg: var(--odl-ink-900); + --odl-fg-strong: var(--odl-ink-950); + --odl-fg-muted: var(--odl-ink-600); + --odl-fg-subtle: var(--odl-ink-500); + --odl-border: var(--odl-ink-200); + --odl-border-strong: var(--odl-ink-300); + --odl-bar: var(--odl-ink-950); + + --odl-accent: var(--odl-blue-600); + --odl-accent-hover: var(--odl-blue-700); + --odl-accent-subtle: var(--odl-blue-50); + --odl-accent-border: var(--odl-blue-200); + --odl-link: var(--odl-blue-600); + + /* Solid action surface — vivid blue with white text in both themes */ + --odl-action-bg: var(--odl-blue-600); + --odl-action-bg-hover: var(--odl-blue-700); + --odl-action-fg: #ffffff; + + --odl-code-bg: var(--odl-ink-50); + --odl-code-border: var(--odl-ink-150); + --odl-selection: var(--odl-blue-100); + + --odl-grid-line: rgba(18, 22, 27, 0.05); + --odl-shadow-sm: 0 1px 2px rgba(10, 13, 17, 0.06), + 0 1px 1px rgba(10, 13, 17, 0.04); + --odl-shadow-md: 0 4px 12px rgba(10, 13, 17, 0.07), + 0 1px 3px rgba(10, 13, 17, 0.05); + --odl-shadow-lg: 0 18px 40px rgba(10, 13, 17, 0.1), + 0 4px 12px rgba(10, 13, 17, 0.06); +} +/* ---- 2b. Semantic tokens — dark ------------------------------------------ */ +[data-theme="dark"] { + --odl-bg: #0a0d11; + --odl-bg-subtle: #0e1218; + --odl-bg-inset: #12161d; + --odl-surface: #11161c; + --odl-surface-2: #161c24; + --odl-fg: #e7ebf0; + --odl-fg-strong: #f6f8fa; + --odl-fg-muted: #9aa6b3; + --odl-fg-subtle: #76828f; + --odl-border: #242c35; + --odl-border-strong: #313b46; + --odl-bar: #f6f8fa; + + --odl-accent: var(--odl-blue-300); + --odl-accent-hover: var(--odl-blue-200); + --odl-accent-subtle: rgba(44, 99, 245, 0.14); + --odl-accent-border: rgba(133, 169, 255, 0.32); + --odl-link: var(--odl-blue-300); + + --odl-action-bg: var(--odl-blue-500); + --odl-action-bg-hover: var(--odl-blue-400); + --odl-action-fg: #ffffff; + + --odl-code-bg: #0e131a; + --odl-code-border: #232b35; + --odl-selection: rgba(44, 99, 245, 0.36); + + --odl-grid-line: rgba(255, 255, 255, 0.045); + --odl-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4); + --odl-shadow-md: 0 6px 18px rgba(0, 0, 0, 0.5); + --odl-shadow-lg: 0 22px 48px rgba(0, 0, 0, 0.6); +} + +/* ---- 3. Project onto Infima so docs/blog inherit the system -------------- */ +:root { + --ifm-color-primary: #1e54e0; + --ifm-color-primary-dark: #1b4bca; + --ifm-color-primary-darker: #1846be; + --ifm-color-primary-darkest: #163a9c; + --ifm-color-primary-light: #3568ef; + --ifm-color-primary-lighter: #4c79f1; + --ifm-color-primary-lightest: #79a0f5; + + --ifm-background-color: var(--odl-bg); + --ifm-background-surface-color: var(--odl-surface); + --ifm-font-color-base: var(--odl-fg); + --ifm-heading-color: var(--odl-fg-strong); + --ifm-font-family-base: var(--odl-font-sans); + --ifm-heading-font-family: var(--odl-font-sans); + --ifm-font-family-monospace: var(--odl-font-mono); + --ifm-font-size-base: 16px; + --ifm-line-height-base: 1.6; + --ifm-link-color: var(--odl-link); + --ifm-link-hover-color: var(--odl-accent-hover); + + --ifm-heading-font-weight: 650; + --ifm-font-weight-semibold: 600; + + --ifm-navbar-background-color: rgba(255, 255, 255, 0.82); + --ifm-navbar-shadow: inset 0 -1px 0 var(--odl-border); + --ifm-navbar-link-color: var(--odl-fg-muted); + --ifm-navbar-link-hover-color: var(--odl-fg-strong); + + --ifm-toc-border-color: var(--odl-border); + --ifm-color-emphasis-200: var(--odl-border); + --ifm-color-emphasis-300: var(--odl-border-strong); + + --ifm-code-font-size: 0.9em; + --ifm-code-background: var(--odl-code-bg); + --ifm-code-border-radius: var(--odl-radius-sm); + --ifm-pre-background: var(--odl-code-bg); + + --ifm-button-border-radius: var(--odl-radius-md); + --ifm-global-radius: var(--odl-radius-md); + + --docusaurus-highlighted-code-line-bg: rgba(30, 84, 224, 0.1); + + --footer-font-color: var(--odl-fg-muted); --footer-column-width: unset; - --footer-row-max-width: 1000px; + --footer-row-max-width: 1100px; } -/* For readability concerns, you should choose a lighter palette in dark mode. */ -[data-theme='dark'] { - --ifm-color-primary: #25c2a0; - --ifm-color-primary-dark: #21af90; - --ifm-color-primary-darker: #1fa588; - --ifm-color-primary-darkest: #1a8870; - --ifm-color-primary-light: #29d5b0; - --ifm-color-primary-lighter: #32d8b4; - --ifm-color-primary-lightest: #4fddbf; - --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3); +[data-theme="dark"] { + --ifm-color-primary: #5384ff; + --ifm-color-primary-dark: #3568ef; + --ifm-color-primary-darker: #2c63f5; + --ifm-color-primary-darkest: #1e54e0; + --ifm-color-primary-light: #79a0f5; + --ifm-color-primary-lighter: #95b5f8; + --ifm-color-primary-lightest: #b4ccff; - --footer-font-color: white; + --ifm-navbar-background-color: rgba(10, 13, 17, 0.78); + --ifm-navbar-shadow: inset 0 -1px 0 var(--odl-border); + --docusaurus-highlighted-code-line-bg: rgba(83, 132, 255, 0.16); } -.header-github-link:hover { - opacity: 0.6; +/* ---- 4. Base element styling --------------------------------------------- */ +html { + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + text-rendering: optimizeLegibility; } -.header-github-link::before { - content: ''; - width: 24px; - height: 24px; - display: flex; - background: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.52 [...] - no-repeat; +body { + background-color: var(--odl-bg); + color: var(--odl-fg); + font-feature-settings: "cv05" 1, "ss01" 1; } -[data-theme='dark'] .header-github-link::before { - background: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill='white' d='M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135- [...] - no-repeat; +h1, +h2, +h3, +h4 { + letter-spacing: var(--odl-tracking-tight); } +::selection { + background: var(--odl-selection); +} + +:focus-visible { + outline: 2px solid var(--odl-accent); + outline-offset: 2px; + border-radius: var(--odl-radius-xs); +} + +/* Translucent, blurred navbar for a precise, layered feel */ +.navbar { + backdrop-filter: saturate(180%) blur(12px); + -webkit-backdrop-filter: saturate(180%) blur(12px); +} + +.navbar__logo { + height: 1.85rem; +} +.navbar__logo img { + height: 100%; +} +.navbar__title { + font-weight: 650; + letter-spacing: var(--odl-tracking-tight); +} + +/* Tabs/Prism code surfaces align to the system */ +.theme-code-block, +pre[class*="language-"] { + border: 1px solid var(--odl-code-border); + border-radius: var(--odl-radius-md); +} + +/* ---- 5. Brand motif: the solid "bar" ------------------------------------- */ +/* The signature OpenDAL bar — a sharp solid rectangle used as a structural + marker, divider and emphasis throughout the system. */ +.odl-bar { + display: inline-block; + width: 2.25rem; + height: 0.5rem; + background: var(--odl-bar); + border-radius: 0; +} + +/* Eyebrow / kicker label: monospace, uppercase, led by a small accent bar */ +.odl-eyebrow { + display: inline-flex; + align-items: center; + gap: var(--odl-space-3); + font-family: var(--odl-font-mono); + font-size: var(--odl-text-xs); + font-weight: 500; + letter-spacing: var(--odl-tracking-label); + text-transform: uppercase; + color: var(--odl-fg-muted); +} +.odl-eyebrow::before { + content: ""; + width: 1.5rem; + height: 0.5rem; + background: var(--odl-accent); + flex: none; +} + +/* Shared layout helpers used by landing sections */ +.odl-container { + width: 100%; + max-width: var(--odl-container); + margin-inline: auto; + padding-inline: var(--odl-gutter); +} + +/* Faint blueprint grid — engineering texture for select surfaces */ +.odl-grid-bg { + background-image: linear-gradient( + var(--odl-grid-line) 1px, + transparent 1px + ), + linear-gradient(90deg, var(--odl-grid-line) 1px, transparent 1px); + background-size: 44px 44px; +} + +/* Tabular figures for any numeric/metric display */ +.odl-tabular { + font-family: var(--odl-font-mono); + font-variant-numeric: tabular-nums; +} + +/* ---- 6. Header icons (GitHub / Discord) ---------------------------------- */ +.header-github-link:hover, .header-discord-link:hover { opacity: 0.6; } -/* Already suitable for dark mode */ -.header-discord-link::before { - content: ''; - width: 24px; - height: 24px; +.header-github-link::before { + content: ""; + width: 22px; + height: 22px; display: flex; - background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' fill='currentColor' class='bi bi-discord' viewBox='0 0 16 16'%3E%3Cpath d='M13.545 2.907a13.227 13.227 0 0 0-3.257-1.011.05.05 0 0 0-.052.025c-.141.25-.297.577-.406.833a12.19 12.19 0 0 0-3.658 0 8.258 8.258 0 0 0-.412-.833.051.051 0 0 0-.052-.025c-1.125.194-2.22.534-3.257 1.011a.041.041 0 0 0-.021.018C.356 6.024-.213 9.047.066 12.032c.001.014.01.028.021.037a13.276 13.276 0 0 0 3.995 2.0 [...] - no-repeat; + background-color: var(--odl-fg-muted); + -webkit-mask: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1. [...] + no-repeat center / contain; + mask: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105- [...] + no-repeat center / contain; } -/* set copyright font size */ -.footer__copyright { - font-size: .7em; +.header-discord-link::before { + content: ""; + width: 22px; + height: 22px; + display: flex; + background-color: var(--odl-fg-muted); + -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' fill='currentColor' viewBox='0 0 16 16'%3E%3Cpath d='M13.545 2.907a13.227 13.227 0 0 0-3.257-1.011.05.05 0 0 0-.052.025c-.141.25-.297.577-.406.833a12.19 12.19 0 0 0-3.658 0 8.258 8.258 0 0 0-.412-.833.051.051 0 0 0-.052-.025c-1.125.194-2.22.534-3.257 1.011a.041.041 0 0 0-.021.018C.356 6.024-.213 9.047.066 12.032c.001.014.01.028.021.037a13.276 13.276 0 0 0 3.995 2.02.05.05 0 0 0 .056-. [...] + no-repeat center / contain; + mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' fill='currentColor' viewBox='0 0 16 16'%3E%3Cpath d='M13.545 2.907a13.227 13.227 0 0 0-3.257-1.011.05.05 0 0 0-.052.025c-.141.25-.297.577-.406.833a12.19 12.19 0 0 0-3.658 0 8.258 8.258 0 0 0-.412-.833.051.051 0 0 0-.052-.025c-1.125.194-2.22.534-3.257 1.011a.041.041 0 0 0-.021.018C.356 6.024-.213 9.047.066 12.032c.001.014.01.028.021.037a13.276 13.276 0 0 0 3.995 2.02.05.05 0 0 0 .056-.019c.308 [...] + no-repeat center / contain; } -.navbar__logo img { - height: 120%; +/* ---- 7. Footer ----------------------------------------------------------- */ +.footer { + background-color: var(--odl-bg-subtle); + border-top: 1px solid var(--odl-border); +} +.footer__copyright { + font-size: 0.75em; + color: var(--odl-fg-subtle); + margin-top: var(--odl-space-4); } - .footer__logo { margin-top: unset; + max-width: 280px; +} +[data-theme="dark"] .footer__logo { + filter: brightness(0) invert(1) opacity(0.7); +} + +/* ---- 8. Reduced motion --------------------------------------------------- */ +@media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + scroll-behavior: auto !important; + } } diff --git a/website/src/pages/index.jsx b/website/src/pages/index.jsx new file mode 100644 index 000000000..d1ff32bde --- /dev/null +++ b/website/src/pages/index.jsx @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from "react"; +import Layout from "@theme/Layout"; +import { + Hero, + UsedBy, + ValueProps, + OperatorSection, + Services, + Bindings, + Layers, + Community, + FinalCta, +} from "../components/landing/sections"; + +export default function Home() { + return ( + <Layout + title="One Layer, All Storage" + description="Apache OpenDAL™ is an Open Data Access Layer that gives every language a unified, zero-cost way to access object storage, file systems, cloud SaaS, databases, protocols and key-value services." + > + <main> + <Hero /> + <UsedBy /> + <ValueProps /> + <OperatorSection /> + <Services /> + <Bindings /> + <Layers /> + <Community /> + <FinalCta /> + </main> + </Layout> + ); +}
