From 0540e3d01b79071fec7d887cf1f84ee59bedc013 Mon Sep 17 00:00:00 2001 From: ipolyzos Date: Tue, 28 Apr 2026 14:42:42 +0300 Subject: [PATCH 01/28] initial proposal --- website/docusaurus.config.ts | 114 +- .../src/components/HomepageFeatures/index.tsx | 133 +- .../HomepageFeatures/styles.module.css | 214 +- .../components/HomepageIntroduce/index.tsx | 74 +- .../HomepageIntroduce/styles.module.css | 113 +- website/src/css/custom.css | 2881 ++++++++++++++++- website/src/pages/index.module.css | 897 ++++- website/src/pages/index.tsx | 886 ++++- website/src/utils/prismDark.ts | 77 + website/src/utils/prismLight.ts | 32 +- 10 files changed, 5054 insertions(+), 367 deletions(-) create mode 100644 website/src/utils/prismDark.ts diff --git a/website/docusaurus.config.ts b/website/docusaurus.config.ts index a7853f1ef7..5d416fbcd5 100644 --- a/website/docusaurus.config.ts +++ b/website/docusaurus.config.ts @@ -15,19 +15,81 @@ * limitations under the License. */ -import {themes as prismThemes} from 'prism-react-renderer'; import type {Config} from '@docusaurus/types'; import type * as Preset from '@docusaurus/preset-classic'; import lightTheme from './src/utils/prismLight'; +import darkTheme from './src/utils/prismDark'; import versionReplace from './src/plugins/remark-version-replace/index'; import { loadVersionData } from './src/utils/versionData'; const { versionsMap, latestVersion } = loadVersionData(); const config: Config = { title: 'Apache Fluss™ (Incubating)', - tagline: 'Streaming Storage for Real-Time Analytics & AI', + tagline: 'The streaming storage layer for real-time analytics and the lakehouse', favicon: 'img/logo/fluss_favicon.svg', + headTags: [ + { + tagName: 'meta', + attributes: { + name: 'description', + content: + 'Apache Fluss is an open-source columnar streaming storage system. Sub-second freshness, primary-key tables, first-class Apache Flink integration, and native tiering to Apache Iceberg and Apache Paimon.', + }, + }, + { + tagName: 'meta', + attributes: { + property: 'og:title', + content: 'Apache Fluss — Streaming Storage for the Real-Time Lakehouse', + }, + }, + { + tagName: 'meta', + attributes: { + property: 'og:description', + content: + 'Open-source columnar streaming storage with sub-second freshness, primary-key tables, Flink integration, and native tiering to Iceberg and Paimon.', + }, + }, + { + tagName: 'meta', + attributes: { + property: 'og:type', + content: 'website', + }, + }, + { + tagName: 'meta', + attributes: { + name: 'twitter:card', + content: 'summary_large_image', + }, + }, + { + tagName: 'meta', + attributes: { + name: 'twitter:title', + content: 'Apache Fluss — Streaming Storage for the Real-Time Lakehouse', + }, + }, + { + tagName: 'meta', + attributes: { + name: 'twitter:description', + content: + 'Open-source columnar streaming storage with sub-second freshness, primary-key tables, Flink integration, and native tiering to Iceberg and Paimon.', + }, + }, + { + tagName: 'meta', + attributes: { + name: 'theme-color', + content: '#0B1E47', + }, + }, + ], + // Set the production url of your site here url: 'https://fluss.apache.org/', // Set the // pathname under which your site is served @@ -160,13 +222,15 @@ const config: Config = { image: 'img/logo/png/colored_logo.png', colorMode: { defaultMode: 'light', - disableSwitch: true, + disableSwitch: false, + respectPrefersColorScheme: true, }, navbar: { title: '', logo: { alt: 'Fluss', src: 'img/logo/svg/colored_logo.svg', + srcDark: 'img/logo/svg/white_color_logo.svg', }, items: [ { @@ -220,6 +284,48 @@ const config: Config = { }, footer: { style: 'dark', + links: [ + { + title: 'Product', + items: [ + {label: 'Documentation', to: '/docs/quickstart/flink'}, + {label: 'Quickstart', to: '/docs/quickstart/flink'}, + {label: 'Roadmap', to: '/roadmap'}, + {label: 'Downloads', to: '/downloads'}, + {label: 'Blog', to: '/blog'}, + ], + }, + { + title: 'Community', + items: [ + {label: 'GitHub', href: 'https://github.com/apache/fluss'}, + {label: 'Slack', href: 'https://join.slack.com/t/apache-fluss/shared_invite/zt-33wlna581-QAooAiCmnYboJS8D_JUcYw'}, + {label: 'Welcome', to: '/community/welcome'}, + {label: 'Contribute', to: '/community/welcome'}, + ], + }, + { + title: 'Resources', + items: [ + {label: 'Talks', to: '/learn/talks'}, + {label: 'Videos', to: '/learn/videos'}, + {label: 'Issues', href: 'https://github.com/apache/fluss/issues'}, + {label: 'Releases', href: 'https://github.com/apache/fluss/releases'}, + ], + }, + { + title: 'Apache', + items: [ + {label: 'Foundation', href: 'https://www.apache.org/'}, + {label: 'License', href: 'https://www.apache.org/licenses/'}, + {label: 'Events', href: 'https://events.apache.org'}, + {label: 'Donate', href: 'https://www.apache.org/foundation/sponsorship.html'}, + {label: 'Sponsors', href: 'https://www.apache.org/foundation/thanks.html'}, + {label: 'Security', href: 'https://www.apache.org/security/'}, + {label: 'Privacy', href: 'https://privacy.apache.org/policies/privacy-policy-public.html'}, + ], + }, + ], logo: { width: 200, src: "/img/apache-incubator.svg", @@ -232,7 +338,7 @@ const config: Config = { }, prism: { theme: lightTheme, - darkTheme: prismThemes.dracula, + darkTheme: darkTheme, additionalLanguages: ['java', 'bash', 'scala'] }, algolia: { diff --git a/website/src/components/HomepageFeatures/index.tsx b/website/src/components/HomepageFeatures/index.tsx index 09671c2ce9..a8b426283e 100644 --- a/website/src/components/HomepageFeatures/index.tsx +++ b/website/src/components/HomepageFeatures/index.tsx @@ -20,79 +20,108 @@ import React from 'react'; import styles from './styles.module.css'; import Heading from '@theme/Heading'; - -type FeatureItem = { - title: string; - content: string; - Svg: React.ComponentType>; +type Pillar = { + number: string; + title: string; + summary: string; + body: string; + basis: string; + Svg: React.ComponentType>; }; -const FeatureList: FeatureItem[] = [ +const PILLARS: Pillar[] = [ { - title: 'Sub-Second Data Freshness', - content: - 'Continuous ingestion and immediate availability of data enable low-latency analytics and real-time decision-making at scale.', - Svg: require('@site/static/img/feature_real_time.svg').default + number: '01', + title: 'Unified Architecture', + summary: 'One system for messaging, applications, analytics, and AI.', + body: 'Consolidates the roles previously played by a message queue, a key-value store, and an OLAP engine — collapsing five systems into one substrate.', + basis: 'Dual representation of PK Tables: append-only log + leader-side RocksDB KV.', + Svg: require('@site/static/img/feature_update.svg').default, }, { + number: '02', title: 'Streaming & Lakehouse Unification', - content: - 'Streaming-native storage with low-latency access on top of the lakehouse, using tables as a single abstraction to unify real-time and historical data across engines.', - Svg: require('@site/static/img/feature_lake.svg').default + summary: 'One copy of data across the real-time and batch layers.', + body: 'The hot Fluss tier and the cold open-format tier share a logical schema and are queryable as a single substrate, with synchronised metadata.', + basis: 'Tiering Service + Union Read across Iceberg, Paimon, and Lance.', + Svg: require('@site/static/img/feature_lake.svg').default, }, { - title: 'Columnar Streaming', - content: - 'Based on Apache Arrow it allows database primitives on data streams and techniques like column pruning and predicate pushdown. This ensures engines read only the data they need, minimizing I/O and network costs.', - Svg: require('@site/static/img/feature_column.svg').default + number: '03', + title: 'Compute–Storage Separation', + summary: 'Lean, elastic, stateless compute with fast recovery.', + body: 'State lives on the Fluss leader, not in Flink task slots. Recovery collapses from minutes to seconds; cost runs up to 85% lower than comparable Kafka-based topologies.', + basis: 'Stateless compute model with leader-resident state and KV snapshots.', + Svg: require('@site/static/img/feature_real_time.svg').default, }, { - title: 'Compute–Storage Separation', - content: - 'Stream processors focus on pure computation while Fluss manages state and storage, with features like deduplication, partial updates, delta joins, and aggregation merge engines.', - Svg: require('@site/static/img/feature_update.svg').default + number: '04', + title: 'Columnar Streaming Analytics', + summary: 'Pruning that compounds.', + body: 'Server-side projection pushdown over Apache Arrow columnar buffers. Column projection, predicate pushdown, and partition pruning compound into order-of-magnitude reductions in I/O and network.', + basis: 'ARROW log format + compound pruning stack on the TabletServer.', + Svg: require('@site/static/img/feature_column.svg').default, }, { - title: 'ML & AI–Ready Storage', - content: - 'A unified storage layer supporting row-based, columnar, vector, and multi-modal data, enabling real-time feature stores and a centralized data repository for ML and AI systems.', - Svg: require('@site/static/img/feature_query.svg').default + number: '05', + title: 'Feature & Context Stores', + summary: 'Multi-modal data on one substrate — ready for ML and AI.', + body: 'Row, columnar, and vector formats on the same store. Online feature serving, RAG-ready semantic context, and real-time entity profiles collapse into one PK Table accessed through different views.', + basis: 'Unified substrate spanning structured features and vector context.', + Svg: require('@site/static/img/feature_query.svg').default, }, { - title: 'Changelogs & Decision Tracking', - content: - 'Built-in changelog generation provides an append-only history of state and decision evolution, enabling auditing, reproducibility, and deep system observability.', - Svg: require('@site/static/img/feature_changelog.svg').default + number: '06', + title: 'Ecosystem Openness', + summary: 'Open formats. No vendor lock-in.', + body: 'Shared open storage readable by Apache Flink, Spark, Trino, StarRocks, and DuckDB. Native connectors for the hot Fluss tier; open Iceberg, Paimon, and Lance formats for the cold tier.', + basis: 'Open lake formats end-to-end, governed at the Apache Software Foundation.', + Svg: require('@site/static/img/feature_changelog.svg').default, }, ]; -function Feature({ title, content, Svg }: FeatureItem) { +function PillarCard({number, title, summary, body, basis, Svg}: Pillar) { return ( -
-
- -
-
-
{title}
-
{content}
+
+
+ +
-
+ {title} +

{summary}

+

{body}

+

+ Architectural basis + {basis} +

+ ); } export default function HomepageFeatures(): JSX.Element { - return ( -
-
-
- Key Features -
-
- {FeatureList.map((props, idx) => ( - - ))} -
-
-
- ); + return ( +
+
+
+ Six capability pillars + + The benefits, grounded in the architecture. + +

+ Each pillar is a direct consequence of a specific architectural + mechanism — not a marketing claim. Together they collapse the + fragmented real-time stack into a single coherent foundation. +

+
+ +
+ {PILLARS.map((p) => ( + + ))} +
+
+
+ ); } diff --git a/website/src/components/HomepageFeatures/styles.module.css b/website/src/components/HomepageFeatures/styles.module.css index cbb3123740..4592a36417 100644 --- a/website/src/components/HomepageFeatures/styles.module.css +++ b/website/src/components/HomepageFeatures/styles.module.css @@ -16,51 +16,175 @@ */ .features { + padding: var(--fluss-space-24) 0; + background: var(--fluss-paper); + border-top: 1px solid var(--fluss-ink-100); +} + +.container { + max-width: var(--fluss-container-max); + margin: 0 auto; + padding: 0 var(--fluss-container-pad); +} + +.header { + text-align: left; + max-width: 720px; + margin-bottom: var(--fluss-space-16); +} + +.eyebrow { + display: inline-block; + color: var(--fluss-blue-700); + font-size: 0.8125rem; + font-weight: 600; + letter-spacing: 0.06em; + text-transform: uppercase; + margin-bottom: var(--fluss-space-3); +} + +.heading { + font-family: var(--fluss-font-display); + font-size: clamp(2rem, 3vw + 1rem, 3rem); + font-weight: 700; + line-height: 1.1; + letter-spacing: -0.02em; + text-wrap: balance; + color: var(--fluss-ink-950); + margin: 0 0 var(--fluss-space-4); +} + +.lead { + font-size: 1.125rem; + line-height: 1.65; + color: var(--fluss-ink-500); + margin: 0; + text-wrap: pretty; +} + +.grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: var(--fluss-space-4); +} + +@media (max-width: 996px) { + .grid { grid-template-columns: repeat(2, 1fr); } +} + +@media (max-width: 600px) { + .grid { grid-template-columns: 1fr; } +} + +.card { + display: flex; + flex-direction: column; + gap: var(--fluss-space-3); + padding: var(--fluss-space-8) var(--fluss-space-6) var(--fluss-space-6); + background: var(--fluss-paper); + border: 1px solid var(--fluss-ink-100); + border-radius: var(--fluss-radius-lg); + position: relative; + overflow: hidden; + transition: transform var(--fluss-motion-base) var(--fluss-ease-out), + box-shadow var(--fluss-motion-base) var(--fluss-ease-out), + border-color var(--fluss-motion-base) var(--fluss-ease-out); +} + +.card::before { + content: ''; + position: absolute; + inset: 0 0 auto 0; + height: 2px; + background: linear-gradient(90deg, var(--fluss-blue-600), var(--fluss-cyan)); + opacity: 0; + transition: opacity var(--fluss-motion-base) var(--fluss-ease-out); +} + +.card:hover { + transform: translateY(-3px); + box-shadow: var(--fluss-shadow-md); + border-color: var(--fluss-blue-300); +} + +.card:hover::before { + opacity: 1; +} + +.cardTop { display: flex; align-items: center; - padding: 2rem 0; - width: 100%; - background: #F7F9FE; -} - -.featureSvg { - height: 48px; - width: 48px; -} - -.core_features { - background-color: white; - padding: 2.5rem 2rem 3.375rem; - border-radius: 8px; - height: calc(100% - 5rem); - text-align: left; -} - -.core_features_title { - font-size: 1.25rem; - font-weight: 500; - line-height: 1.6; - margin-bottom: 0.75rem; -} - -.core_features_content { - font-size: 1rem; - font-weight: 400; - line-height: 1.875; -} - -.core_features_icon { - width: 4rem; - height: 4rem; - background-color: white; - position: relative; - transform: translateY(50%); - border-radius: 50%; - left: 1.5rem; - svg { - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - } + justify-content: space-between; + margin-bottom: var(--fluss-space-2); +} + +.iconWrap { + width: 44px; + height: 44px; + display: inline-flex; + align-items: center; + justify-content: center; + border-radius: var(--fluss-radius-md); + background: var(--fluss-blue-50); + color: var(--fluss-blue-700); +} + +.icon { + width: 24px; + height: 24px; +} + +.number { + font-family: var(--fluss-font-mono); + font-size: 0.8125rem; + font-weight: 600; + letter-spacing: 0.08em; + color: var(--fluss-ink-300); +} + +.title { + font-family: var(--fluss-font-display); + font-size: 1.25rem; + font-weight: 600; + letter-spacing: -0.01em; + text-wrap: balance; + color: var(--fluss-ink-950); + margin: 0; +} + +.summary { + font-size: 1rem; + line-height: 1.5; + color: var(--fluss-ink-700); + font-weight: 500; + margin: 0; + text-wrap: pretty; +} + +.body { + font-size: 0.9375rem; + line-height: 1.65; + color: var(--fluss-ink-500); + margin: 0; + text-wrap: pretty; +} + +.basis { + margin: var(--fluss-space-3) 0 0; + padding-top: var(--fluss-space-3); + border-top: 1px dashed var(--fluss-ink-100); + font-size: 0.8125rem; + line-height: 1.55; + color: var(--fluss-ink-500); +} + +.basisLabel { + display: block; + font-family: var(--fluss-font-display); + font-size: 0.6875rem; + font-weight: 700; + letter-spacing: 0.08em; + text-transform: uppercase; + color: var(--fluss-blue-700); + margin-bottom: 2px; } diff --git a/website/src/components/HomepageIntroduce/index.tsx b/website/src/components/HomepageIntroduce/index.tsx index 6d747f23dc..055a2c9427 100644 --- a/website/src/components/HomepageIntroduce/index.tsx +++ b/website/src/components/HomepageIntroduce/index.tsx @@ -17,37 +17,45 @@ import clsx from 'clsx'; import Heading from '@theme/Heading'; -import Link from '@docusaurus/Link'; import styles from './styles.module.css'; -type IntroduceItem = { +type ValueProp = { title: string; - description: JSX.Element; - Svg: React.ComponentType>; + body: string; + Icon: React.ComponentType>; }; -const IntroduceList: IntroduceItem[] = [ +const VALUE_PROPS: ValueProp[] = [ { - description: ( - <> - Apache Fluss (Incubating) is a streaming storage built for real-time analytics & AI which can serve as the real-time data layer for Lakehouse architectures. With its columnar stream and real-time update capabilities, Fluss integrates seamlessly with Apache Flink to enable high-throughput, low-latency, cost-effective streaming data warehouses tailored for real-time applications. - - ), - image: require('@site/static/img/fluss.png').default, - } + title: 'Sub-second freshness', + body: 'Columnar streaming storage means analytics, features, and agents see new rows in seconds — no commit lag, no batch window, no lambda layer.', + Icon: require('@site/static/img/feature_real_time.svg').default, + }, + { + title: 'One copy across hot and cold', + body: 'The hot Fluss tier and the cold open-format tier (Iceberg, Paimon, Lance) share a logical schema. One union read; one source of truth.', + Icon: require('@site/static/img/feature_lake.svg').default, + }, + { + title: 'Logs + tables on one substrate', + body: 'Append-only logs for events. Primary-key tables for live state. Sub-millisecond point lookups served from the same leader that owns the log.', + Icon: require('@site/static/img/feature_update.svg').default, + }, + { + title: 'Stateless, elastic compute', + body: 'State lives on the Fluss leader, not in Flink task slots. Recovery collapses from minutes to seconds; topologies cost up to 85% less than Kafka-based equivalents.', + Icon: require('@site/static/img/feature_lookup.svg').default, + }, ]; - -function Introduce({title, description, image}: IntroduceItem) { +function ValuePropCard({title, body, Icon}: ValueProp) { return ( -
-
- {title} -

{description}

-
-
- +
+ + {title} +

{body}

); } @@ -55,13 +63,25 @@ function Introduce({title, description, image}: IntroduceItem) { export default function HomepageIntroduce(): JSX.Element { return (
-
-
-
- {IntroduceList.map((props, idx) => ( - +
+
+ What is Apache Fluss? + + One substrate. Streams, tables, and the lake. + +

+ Apache Fluss (Incubating) is an open-source columnar streaming + storage system. It collapses the message broker, online KV store, + stream-processing state backend, and lakehouse offline store into a + single coherent foundation — so the systems above it don't have + to keep them in sync. +

+
+ +
+ {VALUE_PROPS.map((vp) => ( + ))} -
diff --git a/website/src/components/HomepageIntroduce/styles.module.css b/website/src/components/HomepageIntroduce/styles.module.css index c61445411b..6a18d885b3 100644 --- a/website/src/components/HomepageIntroduce/styles.module.css +++ b/website/src/components/HomepageIntroduce/styles.module.css @@ -16,12 +16,115 @@ */ .introduce { + padding: var(--fluss-space-24) 0; + background: var(--fluss-canvas); +} + +.container { + max-width: var(--fluss-container-max); + margin: 0 auto; + padding: 0 var(--fluss-container-pad); +} + +.header { + text-align: center; + max-width: 760px; + margin: 0 auto var(--fluss-space-16); +} + +.eyebrow { + display: inline-block; + color: var(--fluss-blue-700); + font-size: 0.8125rem; + font-weight: 600; + letter-spacing: 0.06em; + text-transform: uppercase; + margin-bottom: var(--fluss-space-3); +} + +.title { + font-family: var(--fluss-font-display); + font-size: clamp(2rem, 3vw + 1rem, 3rem); + font-weight: 700; + line-height: 1.1; + letter-spacing: -0.02em; + text-wrap: balance; + color: var(--fluss-ink-950); + margin: 0 0 var(--fluss-space-4); +} + +.lead { + font-size: 1.125rem; + line-height: 1.65; + color: var(--fluss-ink-700); + margin: 0; + text-wrap: pretty; +} + +.grid { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: var(--fluss-space-4); +} + +@media (max-width: 1100px) { + .grid { grid-template-columns: repeat(2, 1fr); } +} + +@media (max-width: 600px) { + .grid { grid-template-columns: 1fr; } +} + +.card { display: flex; + flex-direction: column; + gap: var(--fluss-space-3); + padding: var(--fluss-space-8) var(--fluss-space-6); + background: var(--fluss-paper); + border: 1px solid var(--fluss-ink-100); + border-radius: var(--fluss-radius-lg); + box-shadow: var(--fluss-shadow-sm); + transition: transform var(--fluss-motion-base) var(--fluss-ease-out), + box-shadow var(--fluss-motion-base) var(--fluss-ease-out), + border-color var(--fluss-motion-base) var(--fluss-ease-out); +} + +.card:hover { + transform: translateY(-2px); + box-shadow: var(--fluss-shadow-md); + border-color: var(--fluss-blue-300); +} + +.cardIcon { + width: 44px; + height: 44px; + display: inline-flex; align-items: center; - padding: 2rem 0; - width: 100%; + justify-content: center; + border-radius: var(--fluss-radius-md); + background: var(--fluss-blue-50); + color: var(--fluss-blue-700); +} + +.cardIcon svg { + width: 24px; + height: 24px; +} + +.cardTitle { + font-family: var(--fluss-font-display); + font-size: 1.125rem; + font-weight: 600; + letter-spacing: -0.01em; + text-wrap: balance; + color: var(--fluss-ink-950); + margin: 0; +} - h1 { - font-size: 2.5rem; - } +.cardBody { + font-size: 0.9375rem; + line-height: 1.6; + color: var(--fluss-ink-500); + margin: 0; + text-wrap: pretty; } diff --git a/website/src/css/custom.css b/website/src/css/custom.css index eaa837553a..1773de2c6b 100644 --- a/website/src/css/custom.css +++ b/website/src/css/custom.css @@ -31,64 +31,148 @@ @import '@fontsource/roboto/500.css'; @import '@fontsource/roboto/700.css'; -/* You can override the default Infima variables here. */ +/* ========================================================================= + Fluss Design Tokens (2026 redesign) + ========================================================================= */ :root { - /* Use Roboto consistently across the platform */ - --ifm-font-family-base: 'Roboto', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif; + /* --- Brand: Blue scale --- */ + --fluss-blue-950: #06122B; + --fluss-blue-900: #0B1E47; + --fluss-blue-800: #11317A; + --fluss-blue-700: #1A47B8; + --fluss-blue-600: #2563EB; + --fluss-blue-500: #3B82F6; + --fluss-blue-300: #93C5FD; + --fluss-blue-100: #DBEAFE; + --fluss-blue-50: #EFF6FF; - /* Increase line height for a more "airy" and professional feel */ - --ifm-line-height-base: 1.65; + /* --- Accents (use sparingly) --- */ + --fluss-cyan: #22D3EE; + --fluss-violet: #7C3AED; + --fluss-lime: #A3E635; - /* Refine primary colors to a slightly more professional "Brand Blue" */ - --ifm-color-primary: #0062cc; - --ifm-color-primary-dark: #0056b3; - --ifm-color-primary-darker: #004d99; - --ifm-color-primary-darkest: #003d7a; - --ifm-color-primary-light: #1a7ae0; - --ifm-color-primary-lighter: #338df2; - --ifm-color-primary-lightest: #66abf7; + /* --- Neutrals --- */ + --fluss-ink-950: #0A0F1C; + --fluss-ink-700: #334155; + --fluss-ink-500: #64748B; + --fluss-ink-300: #CBD5E1; + --fluss-ink-100: #F1F5F9; + --fluss-paper: #FFFFFF; + --fluss-canvas: #FAFBFD; - /* Adjust heading weights */ - --ifm-heading-font-weight: 700; + /* --- Semantic --- */ + --fluss-success: #10B981; + --fluss-warning: #F59E0B; + --fluss-danger: #EF4444; + + /* --- Spacing (8px base) --- */ + --fluss-space-1: 4px; + --fluss-space-2: 8px; + --fluss-space-3: 12px; + --fluss-space-4: 16px; + --fluss-space-5: 20px; + --fluss-space-6: 24px; + --fluss-space-8: 32px; + --fluss-space-10: 40px; + --fluss-space-12: 48px; + --fluss-space-16: 64px; + --fluss-space-20: 80px; + --fluss-space-24: 96px; + --fluss-space-32: 128px; + + /* --- Radius --- */ + --fluss-radius-sm: 6px; + --fluss-radius-md: 10px; + --fluss-radius-lg: 14px; + --fluss-radius-xl: 20px; + --fluss-radius-2xl: 28px; + --fluss-radius-pill: 999px; + + /* --- Shadows (cool blue-tinted) --- */ + --fluss-shadow-sm: 0 1px 2px rgba(11, 30, 71, 0.06); + --fluss-shadow-md: 0 4px 16px rgba(11, 30, 71, 0.08); + --fluss-shadow-lg: 0 16px 48px rgba(11, 30, 71, 0.12); + --fluss-shadow-glow: 0 0 0 1px rgba(37, 99, 235, 0.25), 0 12px 40px rgba(37, 99, 235, 0.18); + + /* --- Motion --- */ + --fluss-motion-fast: 120ms; + --fluss-motion-base: 240ms; + --fluss-motion-slow: 480ms; + --fluss-ease-out: cubic-bezier(0.2, 0.8, 0.2, 1); + --fluss-ease-inout: cubic-bezier(0.4, 0, 0.2, 1); + + /* --- Typography --- */ + --fluss-font-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif; + --fluss-font-display: 'Inter', var(--fluss-font-sans); + --fluss-font-mono: ui-monospace, 'JetBrains Mono', 'SF Mono', Menlo, Consolas, 'Liberation Mono', monospace; + + /* --- Layout --- */ + --fluss-container-max: 1240px; + --fluss-container-pad: 24px; + + /* ===================================================================== + Infima overrides — wire tokens into Docusaurus + ===================================================================== */ + --ifm-font-family-base: var(--fluss-font-sans); + --ifm-heading-font-family: var(--fluss-font-display); + --ifm-font-family-monospace: var(--fluss-font-mono); + --ifm-line-height-base: 1.6; + --ifm-heading-font-weight: 600; + --ifm-heading-color: var(--fluss-ink-950); + --ifm-font-color-base: var(--fluss-ink-700); + + --ifm-color-primary: #2563EB; + --ifm-color-primary-dark: #1A47B8; + --ifm-color-primary-darker: #11317A; + --ifm-color-primary-darkest: #0B1E47; + --ifm-color-primary-light: #3B82F6; + --ifm-color-primary-lighter: #60A5FA; + --ifm-color-primary-lightest: #93C5FD; --ifm-code-font-size: 90%; --docusaurus-highlighted-code-line-bg: #E2E9F3; - --ifm-menu-color-background-active: #edeefa99; - --ifm-menu-color-background-hover: #edeefa99; + --ifm-menu-color-background-active: #EFF6FF; + --ifm-menu-color-background-hover: #EFF6FF; + + --ifm-navbar-shadow: 0 1px 0 rgba(11, 30, 71, 0.06); + --ifm-footer-background-color: var(--fluss-blue-950); + --ifm-footer-color: var(--fluss-ink-300); + --ifm-footer-link-color: var(--fluss-ink-300); + --ifm-footer-title-color: #FFFFFF; +} + +/* ========================================================================= + Base typographic refinement + ========================================================================= */ +html { + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +body { + background: var(--fluss-canvas); } +h1, h2, h3, h4, h5, h6 { + letter-spacing: -0.011em; +} .source_code_button { margin-left: 20px; } - +/* ========================================================================= + Navbar + ========================================================================= */ .navbar__brand { - font-family: 'Roboto', sans-serif; + font-family: var(--fluss-font-display); font-weight: 700; letter-spacing: -0.01em; color: inherit; } -.hero__title { - font-family: 'Roboto', sans-serif; - font-weight: 700; - font-size: 3.5rem; - letter-spacing: -0.02em; - line-height: 1.1; -} - -.hero__subtitle { - font-family: 'Roboto', sans-serif; - font-weight: 400; - font-size: 1.5rem; - opacity: 0.9; - max-width: 800px; - margin: 0 auto; - padding-bottom: 2rem; -} - .header-github-link:hover { opacity: 0.6; } @@ -102,7 +186,9 @@ no-repeat; } - +/* ========================================================================= + Sidebar / menu + ========================================================================= */ .menu__list-item { font-size: 0.95rem; font-weight: 500; @@ -110,7 +196,7 @@ .menu__link { font-weight: 500; - color: #57606a; + color: var(--fluss-ink-500); } .menu__link--active { @@ -122,39 +208,35 @@ background: var(--ifm-menu-link-sublist-icon) 50% / 1.5rem 1.5rem; } - +/* ========================================================================= + Markdown content + ========================================================================= */ .markdown { padding-left: 1rem; - h1, - h2, - h3, - h4, - h5, - h6 { - color: #1d1d1d; + h1, h2, h3, h4, h5, h6 { + color: var(--fluss-ink-950); margin-bottom: 0.3125rem; - font-weight: 700; + font-weight: 600; } - b, - strong { + b, strong { font-weight: 700; - color: #1d1d1d; + color: var(--fluss-ink-950); } - h1, - h1:first-child { + h1, h1:first-child { font-size: 2.5rem; margin-bottom: 1.5rem; margin-top: 0; } h2 { - font-size: 2rem; - margin-bottom: 1.25rem; - margin-top: 2rem; - padding-top: 2rem; - border-top: 1px solid #e6e7e9; + font-size: 1.875rem; + margin-bottom: 1rem; + margin-top: 2.5rem; + padding-top: 0; + border-top: none; + letter-spacing: -0.018em; } h3 { @@ -162,8 +244,9 @@ margin-bottom: 1.25rem; margin-top: 1rem; } + p { - line-height: 1.875; + line-height: 1.8; code { border-radius: 4px; @@ -280,6 +363,35 @@ background: var(--ifm-menu-color-background-active); } +/* ========================================================================= + Footer + ========================================================================= */ +.footer { + padding: var(--fluss-space-20) 0 var(--fluss-space-12); +} + +.footer__title { + font-family: var(--fluss-font-display); + font-weight: 600; + font-size: 0.875rem; + text-transform: uppercase; + letter-spacing: 0.06em; + color: #FFFFFF; + margin-bottom: var(--fluss-space-4); +} + +.footer__link-item { + color: var(--fluss-ink-300); + opacity: 0.85; + transition: opacity var(--fluss-motion-fast) var(--fluss-ease-out), color var(--fluss-motion-fast) var(--fluss-ease-out); +} + +.footer__link-item:hover { + color: #FFFFFF; + opacity: 1; + text-decoration: none; +} + .footer__copyright { color: #dfe5f0; font-size: .75rem; @@ -289,7 +401,9 @@ width: 98%; } -/* Hide blog sidebar on individual blog post pages to increase content width */ +/* ========================================================================= + Blog post layout + ========================================================================= */ .blog-post-page-no-sidebar aside.col.col--3 { display: none; } @@ -307,3 +421,2650 @@ .hidden { display: none !important; } + +/* ========================================================================= + Kapa AI widget hide rules + ========================================================================= */ +#kapa-widget-container > button, +#kapa-widget-container > div:first-child:not([role="dialog"]) > button { + display: none !important; +} + +/* ========================================================================= + "Ask AI" navbar pill + ========================================================================= */ +.navbar-ask-ai { + position: relative; + display: inline-flex; + align-items: center; + gap: 7px; + margin: 0 8px; + padding: 7px 16px; + border: 0; + border-radius: var(--fluss-radius-pill); + background: linear-gradient(120deg, var(--fluss-cyan) 0%, var(--fluss-blue-600) 50%, var(--fluss-blue-500) 100%); + background-size: 180% 180%; + color: #ffffff; + font-family: inherit; + font-size: 0.9rem; + font-weight: 700; + letter-spacing: 0.02em; + cursor: pointer; + box-shadow: 0 2px 10px rgba(6, 182, 212, 0.45), + 0 0 0 1px rgba(255, 255, 255, 0.1) inset; + animation: navbar-ask-ai-shimmer 5s ease-in-out infinite, + navbar-ask-ai-pulse 2.6s ease-in-out infinite; + transition: transform 0.15s ease, box-shadow 0.15s ease, filter 0.15s ease; +} + +.navbar-ask-ai::before { + content: ''; + width: 14px; + height: 14px; + background-color: #ffffff; + -webkit-mask-image: url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 1.5 L13.6 8.4 L20.5 10 L13.6 11.6 L12 18.5 L10.4 11.6 L3.5 10 L10.4 8.4 Z M19 15 L19.8 17.2 L22 18 L19.8 18.8 L19 21 L18.2 18.8 L16 18 L18.2 17.2 Z' fill='white'/%3E%3C/svg%3E"); + mask-image: url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 1.5 L13.6 8.4 L20.5 10 L13.6 11.6 L12 18.5 L10.4 11.6 L3.5 10 L10.4 8.4 Z M19 15 L19.8 17.2 L22 18 L19.8 18.8 L19 21 L18.2 18.8 L16 18 L18.2 17.2 Z' fill='white'/%3E%3C/svg%3E"); + -webkit-mask-repeat: no-repeat; + mask-repeat: no-repeat; + -webkit-mask-size: contain; + mask-size: contain; + flex-shrink: 0; +} + +.navbar-ask-ai:hover { + transform: translateY(-1px); + box-shadow: 0 6px 18px rgba(6, 182, 212, 0.65), + 0 0 0 1px rgba(255, 255, 255, 0.18) inset; + filter: brightness(1.08) saturate(1.1); + animation-play-state: paused; +} + +.navbar-ask-ai:focus-visible { + outline: 2px solid #ffffff; + outline-offset: 2px; + box-shadow: 0 0 0 4px rgba(6, 182, 212, 0.5); +} + +@keyframes navbar-ask-ai-shimmer { + 0%, 100% { background-position: 0% 50%; } + 50% { background-position: 100% 50%; } +} + +@keyframes navbar-ask-ai-pulse { + 0%, 100% { box-shadow: 0 2px 10px rgba(6, 182, 212, 0.45), + 0 0 0 1px rgba(255, 255, 255, 0.1) inset; } + 50% { box-shadow: 0 2px 18px rgba(6, 182, 212, 0.75), + 0 0 0 1px rgba(255, 255, 255, 0.14) inset; } +} + +@media (prefers-reduced-motion: reduce) { + .navbar-ask-ai { animation: none; } +} + +@media (max-width: 996px) { + .navbar-ask-ai { + padding: 5px 10px; + font-size: 0.85rem; + } +} + +/* ========================================================================= + Global focus ring (accessibility) + ========================================================================= */ +*:focus-visible { + outline: 2px solid var(--fluss-blue-500); + outline-offset: 2px; + border-radius: var(--fluss-radius-sm); +} + +/* ========================================================================= + Reduced-motion safety net + ========================================================================= */ +@media (prefers-reduced-motion: reduce) { + *, *::before, *::after { + animation-duration: 0.001ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.001ms !important; + scroll-behavior: auto !important; + } +} + +/* ========================================================================= + ======================================================================== + SITE-WIDE CHROME (docs, blog, community, learn, 404) + Below this line: theming for everything that is NOT the homepage. + ======================================================================== + ========================================================================= */ + +/* ------------------------------------------------------------------------- + Navbar (non-homepage solid state) + ------------------------------------------------------------------------- */ +.navbar { + height: 64px; + border-bottom: 1px solid var(--fluss-ink-100); +} + +.navbar__inner { + max-width: var(--fluss-container-max); + margin: 0 auto; +} + +.navbar__items { + gap: 2px; +} + +.navbar__link { + font-size: 0.9375rem; + font-weight: 500; + color: var(--fluss-ink-700); + padding: 0 12px; + border-radius: var(--fluss-radius-md); + transition: color var(--fluss-motion-fast) var(--fluss-ease-out), + background-color var(--fluss-motion-fast) var(--fluss-ease-out); +} + +.navbar__link:hover { + color: var(--fluss-blue-700); + background: var(--fluss-blue-50); + text-decoration: none; +} + +.navbar__link--active { + color: var(--fluss-blue-700); + font-weight: 600; +} + +.navbar__title { + font-family: var(--fluss-font-display); + font-weight: 700; + letter-spacing: -0.01em; + color: var(--fluss-ink-950); +} + +/* Dropdown menus */ +.dropdown__menu { + border: 1px solid var(--fluss-ink-100); + border-radius: var(--fluss-radius-lg); + box-shadow: var(--fluss-shadow-lg); + padding: 8px; + min-width: 200px; + background: var(--fluss-paper); +} + +.dropdown__link { + border-radius: var(--fluss-radius-sm); + padding: 8px 12px; + font-size: 0.9375rem; + font-weight: 500; + color: var(--fluss-ink-700); + transition: color var(--fluss-motion-fast) var(--fluss-ease-out), + background-color var(--fluss-motion-fast) var(--fluss-ease-out); +} + +.dropdown__link:hover, +.dropdown__link--active { + color: var(--fluss-blue-700); + background: var(--fluss-blue-50); +} + +/* Algolia DocSearch button */ +.DocSearch-Button { + height: 36px; + padding: 0 12px; + border-radius: var(--fluss-radius-md); + background: var(--fluss-ink-100); + border: 1px solid transparent; + transition: border-color var(--fluss-motion-fast) var(--fluss-ease-out), + background-color var(--fluss-motion-fast) var(--fluss-ease-out); +} + +.DocSearch-Button:hover { + border-color: var(--fluss-blue-300); + background: var(--fluss-paper); + box-shadow: none; +} + +.DocSearch-Button-Placeholder { + font-size: 0.875rem; + color: var(--fluss-ink-500); +} + +.DocSearch-Search-Icon { + color: var(--fluss-ink-500); +} + +.DocSearch-Button-Keys { + background: var(--fluss-paper); + border: 1px solid var(--fluss-ink-300); + color: var(--fluss-ink-500); + font-family: var(--fluss-font-mono); + font-size: 0.75rem; + padding: 0 6px; + border-radius: var(--fluss-radius-sm); + height: 22px; + box-shadow: none; +} + +/* Version dropdown badge */ +.navbar .navbar__item--version, +.navbar .badge { + font-family: var(--fluss-font-display); + font-size: 0.8125rem; + font-weight: 600; +} + +/* ------------------------------------------------------------------------- + Mobile navbar drawer + ------------------------------------------------------------------------- */ +.navbar-sidebar { + background: var(--fluss-paper); +} + +.navbar-sidebar__brand { + border-bottom: 1px solid var(--fluss-ink-100); +} + +.navbar-sidebar__items .menu__link { + font-size: 0.9375rem; +} + +/* ------------------------------------------------------------------------- + Announcement bar + ------------------------------------------------------------------------- */ +div[class*='announcementBar'] { + background: linear-gradient(120deg, var(--fluss-blue-700) 0%, var(--fluss-blue-600) 100%); + color: #FFFFFF; + font-weight: 500; + border-bottom: none; +} + +div[class*='announcementBar'] a { + color: var(--fluss-blue-100); + text-decoration: underline; +} + +/* ------------------------------------------------------------------------- + Doc page layout — sidebar / main / TOC + ------------------------------------------------------------------------- */ +.theme-doc-sidebar-container { + border-right: 1px solid var(--fluss-ink-100); + background: var(--fluss-canvas); +} + +.theme-doc-sidebar-menu { + padding: var(--fluss-space-3) var(--fluss-space-2); +} + +.menu { + background: transparent; + font-size: 0.9375rem; +} + +.menu__list .menu__list { + margin-left: 8px; + padding-left: 8px; + border-left: 1px solid var(--fluss-ink-100); +} + +.menu__link { + border-radius: var(--fluss-radius-sm); + padding: 6px 10px; + font-weight: 500; + color: var(--fluss-ink-700); + transition: color var(--fluss-motion-fast) var(--fluss-ease-out), + background-color var(--fluss-motion-fast) var(--fluss-ease-out); +} + +.menu__link:hover { + background: var(--fluss-blue-50); + color: var(--fluss-blue-700); +} + +.menu__link--active { + color: var(--fluss-blue-700); + background: var(--fluss-blue-50); + font-weight: 600; +} + +.menu__link--sublist { + font-weight: 600; + color: var(--fluss-ink-950); +} + +.menu__caret:hover { + background: var(--fluss-blue-50); +} + +/* Sidebar toggle button at the bottom */ +.theme-doc-sidebar-container button { + border-color: var(--fluss-ink-100); + color: var(--fluss-ink-500); + border-radius: var(--fluss-radius-sm); +} + +.theme-doc-sidebar-container button:hover { + border-color: var(--fluss-blue-300); + background: var(--fluss-blue-50); +} + +/* ------------------------------------------------------------------------- + Doc breadcrumbs + ------------------------------------------------------------------------- */ +.breadcrumbs__link { + font-size: 0.875rem; + font-weight: 500; + color: var(--fluss-ink-500); + border-radius: var(--fluss-radius-sm); + padding: 4px 8px; + transition: color var(--fluss-motion-fast) var(--fluss-ease-out), + background-color var(--fluss-motion-fast) var(--fluss-ease-out); +} + +.breadcrumbs__link:hover { + color: var(--fluss-blue-700); + background: var(--fluss-blue-50); +} + +.breadcrumbs__item--active .breadcrumbs__link { + color: var(--fluss-ink-950); + font-weight: 600; + background: var(--fluss-ink-100); +} + +.breadcrumbs__item:not(:last-child)::after { + color: var(--fluss-ink-300); +} + +/* ------------------------------------------------------------------------- + Table of contents (right rail) + ------------------------------------------------------------------------- */ +.table-of-contents { + border-left: 1px solid var(--fluss-ink-100); + font-size: 0.8125rem; + padding-left: var(--fluss-space-3); +} + +.table-of-contents__title, +.table-of-contents > *:first-child { + font-family: var(--fluss-font-display); + font-weight: 600; + font-size: 0.75rem; + text-transform: uppercase; + letter-spacing: 0.06em; + color: var(--fluss-ink-500); + margin-bottom: var(--fluss-space-3); +} + +.table-of-contents__link { + color: var(--fluss-ink-500); + font-weight: 500; + padding: 4px 0; + display: block; + transition: color var(--fluss-motion-fast) var(--fluss-ease-out); +} + +.table-of-contents__link:hover { + color: var(--fluss-blue-700); + text-decoration: none; +} + +.table-of-contents__link--active { + color: var(--fluss-blue-700); + font-weight: 600; +} + +/* ------------------------------------------------------------------------- + Doc content typography polish + ------------------------------------------------------------------------- */ +.theme-doc-markdown { + font-size: 1rem; + line-height: 1.7; + color: var(--fluss-ink-700); +} + +.theme-doc-markdown a:not(.button):not(.card) { + color: var(--fluss-blue-700); + text-decoration-color: rgba(37, 99, 235, 0.3); + text-decoration-thickness: 1px; + text-underline-offset: 3px; + transition: text-decoration-color var(--fluss-motion-fast) var(--fluss-ease-out); +} + +.theme-doc-markdown a:not(.button):not(.card):hover { + text-decoration-color: var(--fluss-blue-700); +} + +.markdown a.hash-link { + color: var(--fluss-ink-300); + text-decoration: none; + transition: color var(--fluss-motion-fast) var(--fluss-ease-out); +} + +.markdown a.hash-link:hover { + color: var(--fluss-blue-600); +} + +/* Doc page title (h1) — refined */ +header.theme-doc-markdown h1, +.theme-doc-markdown > article > header > h1 { + font-family: var(--fluss-font-display); + font-size: 2.5rem; + font-weight: 700; + letter-spacing: -0.022em; + line-height: 1.1; + color: var(--fluss-ink-950); +} + +/* "Edit this page" / last updated row */ +.theme-doc-footer { + margin-top: var(--fluss-space-12); + padding-top: var(--fluss-space-6); + border-top: 1px solid var(--fluss-ink-100); +} + +.theme-edit-this-page { + display: inline-flex; + align-items: center; + gap: 6px; + color: var(--fluss-blue-700); + font-weight: 500; + font-size: 0.875rem; +} + +.theme-edit-this-page:hover { + color: var(--fluss-blue-600); + text-decoration: underline; +} + +.theme-last-updated { + font-size: 0.8125rem; + color: var(--fluss-ink-500); +} + +/* ------------------------------------------------------------------------- + Pagination (prev / next at the bottom of doc pages) + ------------------------------------------------------------------------- */ +.pagination-nav__link { + border: 1px solid var(--fluss-ink-100); + border-radius: var(--fluss-radius-lg); + padding: var(--fluss-space-4) var(--fluss-space-5); + background: var(--fluss-paper); + transition: transform var(--fluss-motion-base) var(--fluss-ease-out), + border-color var(--fluss-motion-base) var(--fluss-ease-out), + box-shadow var(--fluss-motion-base) var(--fluss-ease-out); +} + +.pagination-nav__link:hover { + border-color: var(--fluss-blue-300); + box-shadow: var(--fluss-shadow-sm); + transform: translateY(-1px); + text-decoration: none; +} + +.pagination-nav__sublabel { + font-family: var(--fluss-font-display); + font-size: 0.75rem; + font-weight: 600; + letter-spacing: 0.06em; + text-transform: uppercase; + color: var(--fluss-ink-500); + margin-bottom: 4px; +} + +.pagination-nav__label { + color: var(--fluss-ink-950); + font-weight: 600; + font-size: 1rem; +} + +/* ------------------------------------------------------------------------- + Admonitions (note, tip, info, warning, danger, caution) + ------------------------------------------------------------------------- */ +.theme-admonition, +.alert, +[class*='admonition_'] { + border-radius: var(--fluss-radius-lg); + border: 1px solid var(--fluss-ink-100); + border-left-width: 4px; + background: var(--fluss-paper); + padding: var(--fluss-space-4) var(--fluss-space-5); + box-shadow: var(--fluss-shadow-sm); + margin: var(--fluss-space-6) 0; +} + +.alert--secondary, +.theme-admonition-note { + background: var(--fluss-ink-100); + border-color: var(--fluss-ink-300); + border-left-color: var(--fluss-ink-500); +} + +.alert--info, +.theme-admonition-info { + background: var(--fluss-blue-50); + border-color: var(--fluss-blue-300); + border-left-color: var(--fluss-blue-600); +} + +.alert--success, +.theme-admonition-tip { + background: #ECFDF5; + border-color: #A7F3D0; + border-left-color: var(--fluss-success); +} + +.alert--warning, +.theme-admonition-warning, +.theme-admonition-caution { + background: #FFFBEB; + border-color: #FCD34D; + border-left-color: var(--fluss-warning); +} + +.alert--danger, +.theme-admonition-danger { + background: #FEF2F2; + border-color: #FCA5A5; + border-left-color: var(--fluss-danger); +} + +/* Admonition heading row */ +[class*='admonitionHeading'] { + font-family: var(--fluss-font-display); + font-weight: 600; + font-size: 0.8125rem; + text-transform: uppercase; + letter-spacing: 0.06em; + margin-bottom: 6px; + color: var(--fluss-ink-950); +} + +/* ------------------------------------------------------------------------- + Inline code and code blocks + ------------------------------------------------------------------------- */ +code { + font-family: var(--fluss-font-mono); + font-size: 0.875em; + background: var(--fluss-blue-50); + border: 1px solid rgba(37, 99, 235, 0.12); + color: var(--fluss-blue-800); + padding: 1px 6px; + border-radius: var(--fluss-radius-sm); +} + +a code, +.theme-doc-markdown a code { + color: inherit; + background: rgba(37, 99, 235, 0.12); + border-color: transparent; +} + +/* Code block container */ +.theme-code-block, +div[class*='codeBlockContainer'] { + border-radius: var(--fluss-radius-lg); + border: 1px solid var(--fluss-ink-100); + box-shadow: var(--fluss-shadow-sm); + overflow: hidden; + margin: var(--fluss-space-5) 0; +} + +div[class*='codeBlockTitle'] { + background: var(--fluss-ink-100); + border-bottom: 1px solid var(--fluss-ink-300); + font-family: var(--fluss-font-mono); + font-size: 0.8125rem; + color: var(--fluss-ink-700); + padding: 8px 14px; +} + +div[class*='codeBlockContent'] pre { + background: #F8FAFC; +} + +pre[class*='language-'] { + font-size: 0.875rem; + line-height: 1.65; + padding: var(--fluss-space-4) var(--fluss-space-5); +} + +/* Copy-to-clipboard button */ +button[class*='copyButton'] { + background: var(--fluss-paper); + border: 1px solid var(--fluss-ink-100); + color: var(--fluss-ink-500); + border-radius: var(--fluss-radius-sm); + transition: color var(--fluss-motion-fast) var(--fluss-ease-out), + background-color var(--fluss-motion-fast) var(--fluss-ease-out), + border-color var(--fluss-motion-fast) var(--fluss-ease-out); +} + +button[class*='copyButton']:hover { + color: var(--fluss-blue-700); + background: var(--fluss-blue-50); + border-color: var(--fluss-blue-300); +} + +/* Word-wrap toggle */ +button[class*='wordWrapButton'] { + background: var(--fluss-paper); + border: 1px solid var(--fluss-ink-100); + color: var(--fluss-ink-500); + border-radius: var(--fluss-radius-sm); +} + +/* Highlighted lines */ +.theme-code-block-highlighted-line, +.docusaurus-highlight-code-line { + background: rgba(37, 99, 235, 0.08); + border-left: 3px solid var(--fluss-blue-600); + padding-left: calc(var(--fluss-space-5) - 3px); + display: block; + margin: 0 calc(-1 * var(--fluss-space-5)); +} + +/* Language label corner */ +div[class*='codeBlockTitle']::before { + content: ''; + display: inline-block; + width: 6px; + height: 6px; + border-radius: 50%; + background: var(--fluss-cyan); + margin-right: 8px; + vertical-align: middle; +} + +/* ------------------------------------------------------------------------- + Tabs (used in docs for SQL / Java / etc. examples) + ------------------------------------------------------------------------- */ +.tabs { + border-bottom: 1px solid var(--fluss-ink-100); + margin-bottom: 0; + gap: 4px; +} + +.tabs__item { + font-family: var(--fluss-font-display); + font-size: 0.875rem; + font-weight: 600; + color: var(--fluss-ink-500); + padding: 10px 14px; + border-radius: var(--fluss-radius-sm) var(--fluss-radius-sm) 0 0; + border-bottom: 2px solid transparent; + margin-bottom: -1px; + transition: color var(--fluss-motion-fast) var(--fluss-ease-out), + border-color var(--fluss-motion-fast) var(--fluss-ease-out); +} + +.tabs__item:hover { + background: var(--fluss-blue-50); + color: var(--fluss-blue-700); +} + +.tabs__item--active { + color: var(--fluss-blue-700); + border-bottom-color: var(--fluss-blue-600); + background: transparent; +} + +/* ------------------------------------------------------------------------- + Details / Summary (collapsibles) + ------------------------------------------------------------------------- */ +details { + border: 1px solid var(--fluss-ink-100); + border-radius: var(--fluss-radius-md); + background: var(--fluss-paper); + padding: var(--fluss-space-3) var(--fluss-space-4); + margin: var(--fluss-space-4) 0; +} + +details > summary { + cursor: pointer; + font-weight: 600; + color: var(--fluss-ink-950); + font-family: var(--fluss-font-display); + list-style: none; + display: flex; + align-items: center; + gap: 8px; +} + +details > summary::-webkit-details-marker { + display: none; +} + +details > summary::before { + content: ''; + width: 8px; + height: 8px; + border-right: 2px solid var(--fluss-ink-500); + border-bottom: 2px solid var(--fluss-ink-500); + transform: rotate(-45deg); + transition: transform var(--fluss-motion-base) var(--fluss-ease-out); +} + +details[open] > summary::before { + transform: rotate(45deg); +} + +/* ------------------------------------------------------------------------- + Headings — anchor offset for sticky navbar + ------------------------------------------------------------------------- */ +.theme-doc-markdown h1, +.theme-doc-markdown h2, +.theme-doc-markdown h3, +.theme-doc-markdown h4 { + scroll-margin-top: 80px; +} + +/* ------------------------------------------------------------------------- + Doc cards (used in some index pages) + ------------------------------------------------------------------------- */ +article.card, +.theme-doc-card { + border: 1px solid var(--fluss-ink-100); + border-radius: var(--fluss-radius-lg); + background: var(--fluss-paper); + box-shadow: var(--fluss-shadow-sm); + transition: transform var(--fluss-motion-base) var(--fluss-ease-out), + box-shadow var(--fluss-motion-base) var(--fluss-ease-out), + border-color var(--fluss-motion-base) var(--fluss-ease-out); +} + +article.card:hover, +.theme-doc-card:hover { + transform: translateY(-2px); + box-shadow: var(--fluss-shadow-md); + border-color: var(--fluss-blue-300); +} + +/* ------------------------------------------------------------------------- + Version banner (unmaintained / unreleased) + ------------------------------------------------------------------------- */ +div[class*='docVersionBanner'] { + border-radius: var(--fluss-radius-lg); + border: 1px solid var(--fluss-blue-300); + background: var(--fluss-blue-50); + color: var(--fluss-ink-950); + padding: var(--fluss-space-4) var(--fluss-space-5); + margin-bottom: var(--fluss-space-6); + font-size: 0.9375rem; +} + +div[class*='docVersionBanner'] b { + color: var(--fluss-blue-700); +} + +div[class*='docVersionBanner'] a { + color: var(--fluss-blue-700); + font-weight: 600; +} + +/* ------------------------------------------------------------------------- + Blog — list page (cards) + ------------------------------------------------------------------------- */ +.blog-list-page article, +article[itemtype='https://schema.org/BlogPosting'] { + border: 1px solid var(--fluss-ink-100); + border-radius: var(--fluss-radius-lg); + padding: var(--fluss-space-8); + background: var(--fluss-paper); + margin-bottom: var(--fluss-space-6); + box-shadow: var(--fluss-shadow-sm); + transition: transform var(--fluss-motion-base) var(--fluss-ease-out), + box-shadow var(--fluss-motion-base) var(--fluss-ease-out), + border-color var(--fluss-motion-base) var(--fluss-ease-out); +} + +.blog-list-page article:hover, +article[itemtype='https://schema.org/BlogPosting']:hover { + transform: translateY(-2px); + box-shadow: var(--fluss-shadow-md); + border-color: var(--fluss-blue-300); +} + +article[itemtype='https://schema.org/BlogPosting'] header h2 a, +.blog-list-page article header h2 a { + font-family: var(--fluss-font-display); + font-weight: 700; + color: var(--fluss-ink-950); + letter-spacing: -0.01em; + text-decoration: none; +} + +article[itemtype='https://schema.org/BlogPosting'] header h2 a:hover, +.blog-list-page article header h2 a:hover { + color: var(--fluss-blue-700); +} + +/* Blog post meta line */ +.blog-post-page-no-sidebar .blog-post-page, +article[itemtype='https://schema.org/BlogPosting'] header > div { + color: var(--fluss-ink-500); + font-size: 0.875rem; +} + +/* Blog tags */ +a.tag, +a[class*='tag_'] { + display: inline-flex; + align-items: center; + height: 26px; + padding: 0 10px; + border-radius: var(--fluss-radius-pill); + background: var(--fluss-blue-50); + border: 1px solid transparent; + color: var(--fluss-blue-700); + font-size: 0.8125rem; + font-weight: 600; + text-decoration: none; + transition: background-color var(--fluss-motion-fast) var(--fluss-ease-out), + border-color var(--fluss-motion-fast) var(--fluss-ease-out); +} + +a.tag:hover, +a[class*='tag_']:hover { + background: var(--fluss-blue-100); + border-color: var(--fluss-blue-300); + color: var(--fluss-blue-800); + text-decoration: none; +} + +/* Blog "Read more" button */ +.button.button--secondary { + background: var(--fluss-blue-600); + color: #FFFFFF; + border: none; + border-radius: var(--fluss-radius-md); + font-weight: 600; + padding: 10px 16px; + box-shadow: var(--fluss-shadow-sm); + transition: background-color var(--fluss-motion-fast) var(--fluss-ease-out), + transform var(--fluss-motion-fast) var(--fluss-ease-out); +} + +.button.button--secondary:hover { + background: var(--fluss-blue-700); + color: #FFFFFF; + transform: translateY(-1px); +} + +/* ------------------------------------------------------------------------- + Blog sidebar (recent posts) + ------------------------------------------------------------------------- */ +aside.col[class*='blogSidebar'], +nav[class*='sidebar'] aside { + font-size: 0.875rem; +} + +nav[class*='sidebar'] h3 { + font-family: var(--fluss-font-display); + font-weight: 600; + font-size: 0.75rem; + text-transform: uppercase; + letter-spacing: 0.06em; + color: var(--fluss-ink-500); + margin-bottom: var(--fluss-space-3); +} + +/* ------------------------------------------------------------------------- + Blog authors row + ------------------------------------------------------------------------- */ +[class*='authorPhoto'] img, +[class*='authorAvatar'] img { + border: 2px solid var(--fluss-paper); + box-shadow: 0 0 0 1px var(--fluss-ink-100); +} + +/* ------------------------------------------------------------------------- + Blog post page header + ------------------------------------------------------------------------- */ +.blog-post-page-no-sidebar h1, +.blog-post-page article > header h1 { + font-family: var(--fluss-font-display); + font-size: clamp(2rem, 3vw + 1rem, 3rem); + font-weight: 700; + letter-spacing: -0.022em; + line-height: 1.1; + color: var(--fluss-ink-950); +} + +.blog-post-page-no-sidebar aside.col.col--3 { + display: none; +} + +.blog-post-page-no-sidebar main.col.col--7 { + flex-basis: calc(9 / 12 * 100%); + max-width: calc(9 / 12 * 100%); +} + +.blog-post-page-no-sidebar .col.col--2 { + flex-basis: calc(3 / 12 * 100%); + max-width: calc(3 / 12 * 100%); +} + +/* ------------------------------------------------------------------------- + Markdown tables in docs / blog (override Infima default for dark headers) + ------------------------------------------------------------------------- */ +.theme-doc-markdown table, +.markdown table { + border: 1px solid var(--fluss-ink-100); + border-radius: var(--fluss-radius-md); + overflow: hidden; + box-shadow: var(--fluss-shadow-sm); +} + +.theme-doc-markdown thead, +.markdown thead { + background: var(--fluss-blue-50); +} + +.theme-doc-markdown thead th, +.markdown thead th { + font-family: var(--fluss-font-display); + font-weight: 600; + color: var(--fluss-ink-950); + border-color: var(--fluss-blue-100); +} + +.theme-doc-markdown tbody tr:nth-child(even), +.markdown tbody tr:nth-child(even) { + background: var(--fluss-canvas); +} + +/* ------------------------------------------------------------------------- + Block-quote + ------------------------------------------------------------------------- */ +.theme-doc-markdown blockquote, +.markdown blockquote { + border-left: 3px solid var(--fluss-blue-600); + background: var(--fluss-blue-50); + border-radius: var(--fluss-radius-md); + padding: var(--fluss-space-4) var(--fluss-space-5); + color: var(--fluss-ink-700); + margin: var(--fluss-space-5) 0; +} + +/* ------------------------------------------------------------------------- + 404 page + ------------------------------------------------------------------------- */ +main.container .row .col h1.hero__title { + font-family: var(--fluss-font-display); + font-size: 4rem; + font-weight: 700; + letter-spacing: -0.022em; + color: var(--fluss-ink-950); +} + +/* ------------------------------------------------------------------------- + Buttons (Infima primary / outline) — global refresh + ------------------------------------------------------------------------- */ +.button.button--primary { + background: var(--fluss-blue-600); + border: none; + color: #FFFFFF; + border-radius: var(--fluss-radius-md); + font-weight: 600; + padding: 10px 18px; + box-shadow: var(--fluss-shadow-sm); + transition: background-color var(--fluss-motion-fast) var(--fluss-ease-out), + transform var(--fluss-motion-fast) var(--fluss-ease-out), + box-shadow var(--fluss-motion-fast) var(--fluss-ease-out); +} + +.button.button--primary:hover { + background: var(--fluss-blue-700); + color: #FFFFFF; + transform: translateY(-1px); + box-shadow: var(--fluss-shadow-md); +} + +.button.button--outline.button--primary { + background: transparent; + border: 1px solid var(--fluss-blue-600); + color: var(--fluss-blue-700); +} + +.button.button--outline.button--primary:hover { + background: var(--fluss-blue-50); + color: var(--fluss-blue-800); +} + +/* ------------------------------------------------------------------------- + Selection + ------------------------------------------------------------------------- */ +::selection { + background: rgba(37, 99, 235, 0.18); + color: var(--fluss-ink-950); +} + +/* ------------------------------------------------------------------------- + Scrollbars (subtle, consistent across content areas) + ------------------------------------------------------------------------- */ +.theme-doc-markdown pre::-webkit-scrollbar, +.theme-doc-sidebar-container::-webkit-scrollbar, +.table-of-contents::-webkit-scrollbar { + width: 10px; + height: 10px; +} + +.theme-doc-markdown pre::-webkit-scrollbar-thumb, +.theme-doc-sidebar-container::-webkit-scrollbar-thumb, +.table-of-contents::-webkit-scrollbar-thumb { + background: var(--fluss-ink-300); + border-radius: var(--fluss-radius-pill); + border: 2px solid transparent; + background-clip: padding-box; +} + +.theme-doc-markdown pre::-webkit-scrollbar-thumb:hover, +.theme-doc-sidebar-container::-webkit-scrollbar-thumb:hover, +.table-of-contents::-webkit-scrollbar-thumb:hover { + background: var(--fluss-ink-500); + background-clip: padding-box; +} + +/* ------------------------------------------------------------------------- + Skip-to-content link (accessibility) + ------------------------------------------------------------------------- */ +.skipToContent_node_modules-\@docusaurus-theme-classic-lib-theme-SkipToContent-styles-module { + background: var(--fluss-blue-700); + color: #FFFFFF; +} + +/* ========================================================================= + ======================================================================== + PREMIUM POLISH LAYER + Targeted enhancements so docs/blog stop feeling like default Docusaurus. + ======================================================================== + ========================================================================= */ + +/* ------------------------------------------------------------------------- + Subtle page background (very faint dot grid for warmth) + ------------------------------------------------------------------------- */ +main[class*='docMainContainer'], +main[class*='blogPostPage'], +main[class*='docItemContainer'], +.docs-wrapper, +.blog-wrapper { + position: relative; + background: + radial-gradient(circle at 1px 1px, rgba(11, 30, 71, 0.04) 1px, transparent 0) 0 0 / 32px 32px, + var(--fluss-canvas); +} + +/* ------------------------------------------------------------------------- + Doc / blog-post page header band (eyebrow + title + breadcrumbs). + Scoped to single doc/post pages, NOT to the blog list (where each card + also has its own
and we don't want this treatment on cards). + ------------------------------------------------------------------------- */ +main[class*='docMainContainer'] article > header:first-of-type, +main[class*='docItemContainer'] article > header:first-of-type, +main[class*='blogPostPage'] article > header:first-of-type, +.blog-post-page-no-sidebar article > header:first-of-type { + position: relative; + margin: 0 0 var(--fluss-space-10); + padding-bottom: var(--fluss-space-6); + border-bottom: 1px solid var(--fluss-ink-100); +} + +main[class*='docMainContainer'] article > header:first-of-type::before, +main[class*='docItemContainer'] article > header:first-of-type::before, +main[class*='blogPostPage'] article > header:first-of-type::before, +.blog-post-page-no-sidebar article > header:first-of-type::before { + content: ''; + position: absolute; + top: -1px; + left: 0; + width: 56px; + height: 3px; + border-radius: var(--fluss-radius-pill); + background: linear-gradient(90deg, var(--fluss-blue-600), var(--fluss-cyan)); +} + +main[class*='docMainContainer'] article > header:first-of-type h1, +main[class*='docItemContainer'] article > header:first-of-type h1, +main[class*='blogPostPage'] article > header:first-of-type h1, +.blog-post-page-no-sidebar article > header:first-of-type h1, +.theme-doc-markdown header h1 { + font-family: var(--fluss-font-display); + font-size: clamp(2.25rem, 2.5vw + 1.5rem, 3.25rem); + font-weight: 700; + letter-spacing: -0.022em; + line-height: 1.08; + color: var(--fluss-ink-950); + margin: var(--fluss-space-3) 0 0; +} + +/* Lead paragraph: the first

after the doc title gets a larger, calmer treatment */ +.theme-doc-markdown > p:first-of-type, +article > header:first-of-type + .markdown > p:first-of-type { + font-size: 1.1875rem; + line-height: 1.65; + color: var(--fluss-ink-700); + margin-bottom: var(--fluss-space-8); +} + +/* Inline ifm h1 inside the first markdown block also gets the display treatment */ +.markdown > h1:first-child { + font-family: var(--fluss-font-display); + font-size: clamp(2.25rem, 2.5vw + 1.5rem, 3.25rem); + font-weight: 700; + letter-spacing: -0.022em; + line-height: 1.08; +} + +/* ------------------------------------------------------------------------- + Markdown body — generous reading rhythm + ------------------------------------------------------------------------- */ +.theme-doc-markdown, +.markdown { + font-size: 1.0625rem; + line-height: 1.75; + color: var(--fluss-ink-700); +} + +.markdown p, +.theme-doc-markdown p { + margin-bottom: 1.25em; +} + +.markdown ul, +.markdown ol, +.theme-doc-markdown ul, +.theme-doc-markdown ol { + margin-bottom: 1.25em; +} + +.markdown li + li { + margin-top: 6px; +} + +/* Heading rhythm + display family */ +.markdown h2, +.theme-doc-markdown h2 { + font-family: var(--fluss-font-display); + font-size: 1.75rem; + font-weight: 700; + letter-spacing: -0.018em; + line-height: 1.2; + color: var(--fluss-ink-950); + margin-top: 3rem; + margin-bottom: 1rem; + position: relative; +} + +.markdown h2::before, +.theme-doc-markdown h2::before { + content: ''; + position: absolute; + left: -16px; + top: 0.55em; + width: 8px; + height: 8px; + border-radius: 50%; + background: var(--fluss-blue-600); + opacity: 0; + transition: opacity var(--fluss-motion-base) var(--fluss-ease-out); +} + +.markdown h2:hover::before, +.theme-doc-markdown h2:hover::before { + opacity: 0.8; +} + +.markdown h3, +.theme-doc-markdown h3 { + font-family: var(--fluss-font-display); + font-size: 1.375rem; + font-weight: 600; + letter-spacing: -0.014em; + color: var(--fluss-ink-950); + margin-top: 2.25rem; + margin-bottom: 0.625rem; +} + +.markdown h4, +.theme-doc-markdown h4 { + font-family: var(--fluss-font-display); + font-size: 1.0625rem; + font-weight: 600; + color: var(--fluss-ink-950); + margin-top: 1.5rem; + margin-bottom: 0.5rem; +} + +/* Anchor link reveal on heading hover */ +.markdown h2 .hash-link, +.markdown h3 .hash-link, +.markdown h4 .hash-link, +.theme-doc-markdown h2 .hash-link, +.theme-doc-markdown h3 .hash-link, +.theme-doc-markdown h4 .hash-link { + margin-left: 6px; + opacity: 0; + font-weight: 400; + transition: opacity var(--fluss-motion-base) var(--fluss-ease-out), + transform var(--fluss-motion-base) var(--fluss-ease-out); + transform: translateX(-4px); + display: inline-block; +} + +.markdown h2:hover .hash-link, +.markdown h3:hover .hash-link, +.markdown h4:hover .hash-link, +.theme-doc-markdown h2:hover .hash-link, +.theme-doc-markdown h3:hover .hash-link, +.theme-doc-markdown h4:hover .hash-link { + opacity: 1; + transform: translateX(0); +} + +/* Animated link underline in body prose */ +.theme-doc-markdown a:not(.button):not(.card):not(.hash-link), +.markdown a:not(.button):not(.card):not(.hash-link) { + background-image: linear-gradient(0deg, var(--fluss-blue-600) 0, var(--fluss-blue-600) 100%); + background-position: 0 100%; + background-repeat: no-repeat; + background-size: 0 1px; + transition: background-size var(--fluss-motion-base) var(--fluss-ease-out), + color var(--fluss-motion-fast) var(--fluss-ease-out); + text-decoration: none; +} + +.theme-doc-markdown a:not(.button):not(.card):not(.hash-link):hover, +.markdown a:not(.button):not(.card):not(.hash-link):hover { + background-size: 100% 1px; + text-decoration: none; +} + +/* External-link indicator (auto, before the link) */ +.theme-doc-markdown a[href^='http']:not([href*='fluss.apache.org'])::after, +.markdown a[href^='http']:not([href*='fluss.apache.org'])::after { + content: ''; + display: inline-block; + width: 0.7em; + height: 0.7em; + margin-left: 4px; + background-color: currentColor; + -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M7 17L17 7M17 7H8M17 7V16'/%3E%3C/svg%3E"); + mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M7 17L17 7M17 7H8M17 7V16'/%3E%3C/svg%3E"); + -webkit-mask-size: contain; + mask-size: contain; + -webkit-mask-repeat: no-repeat; + mask-repeat: no-repeat; + opacity: 0.65; + vertical-align: 0.05em; +} + +/* ------------------------------------------------------------------------- + styling + ------------------------------------------------------------------------- */ +kbd { + display: inline-block; + padding: 1px 6px; + border-radius: var(--fluss-radius-sm); + background: var(--fluss-paper); + border: 1px solid var(--fluss-ink-300); + border-bottom-width: 2px; + color: var(--fluss-ink-700); + font-family: var(--fluss-font-mono); + font-size: 0.8125em; + line-height: 1.4; + vertical-align: 0.05em; + box-shadow: var(--fluss-shadow-sm); +} + +/* ------------------------------------------------------------------------- + Premium sidebar — sliding active indicator + ------------------------------------------------------------------------- */ +.theme-doc-sidebar-container { + background: + linear-gradient(180deg, transparent 0, transparent 64px, var(--fluss-canvas) 64px), + var(--fluss-canvas); +} + +.menu__list { + position: relative; +} + +.menu__link { + position: relative; + font-size: 0.9rem; +} + +.menu__link--active { + background: linear-gradient(90deg, var(--fluss-blue-50) 0%, transparent 100%); +} + +.menu__link--active::before { + content: ''; + position: absolute; + left: 0; + top: 6px; + bottom: 6px; + width: 3px; + border-radius: 0 var(--fluss-radius-sm) var(--fluss-radius-sm) 0; + background: linear-gradient(180deg, var(--fluss-blue-600), var(--fluss-cyan)); + box-shadow: 0 0 12px rgba(37, 99, 235, 0.4); +} + +/* Top-level category labels — Linear-ish eyebrow style */ +.theme-doc-sidebar-menu > .menu__list-item > .menu__link--sublist, +.theme-doc-sidebar-menu > .menu__list-item > .menu__list-item-collapsible > .menu__link { + font-family: var(--fluss-font-display); + font-weight: 700; + font-size: 0.78rem; + letter-spacing: 0.06em; + text-transform: uppercase; + color: var(--fluss-ink-500); + padding-top: 14px; + padding-bottom: 6px; +} + +.theme-doc-sidebar-menu > .menu__list-item:not(:first-child) { + margin-top: 4px; + border-top: 1px dashed var(--fluss-ink-100); + padding-top: 4px; +} + +/* ------------------------------------------------------------------------- + Sticky TOC card (right rail) + ------------------------------------------------------------------------- */ +.theme-doc-toc-desktop, +[class*='tableOfContents'] { + border-radius: var(--fluss-radius-lg); + background: var(--fluss-paper); + border: 1px solid var(--fluss-ink-100); + padding: var(--fluss-space-5); + box-shadow: var(--fluss-shadow-sm); + position: sticky; + top: 96px; +} + +.theme-doc-toc-desktop .table-of-contents, +[class*='tableOfContents'] .table-of-contents { + border-left: none; + padding-left: 0; + margin: 0; +} + +.theme-doc-toc-desktop::before, +[class*='tableOfContents']::before { + content: 'On this page'; + display: block; + font-family: var(--fluss-font-display); + font-weight: 700; + font-size: 0.72rem; + letter-spacing: 0.08em; + text-transform: uppercase; + color: var(--fluss-ink-500); + margin-bottom: var(--fluss-space-3); + padding-bottom: var(--fluss-space-3); + border-bottom: 1px solid var(--fluss-ink-100); +} + +.table-of-contents__link { + position: relative; + padding-left: var(--fluss-space-3); + border-left: 2px solid transparent; + transition: color var(--fluss-motion-fast) var(--fluss-ease-out), + border-color var(--fluss-motion-fast) var(--fluss-ease-out); +} + +.table-of-contents__link--active { + border-left-color: var(--fluss-blue-600); + background: var(--fluss-blue-50); + border-radius: 0 var(--fluss-radius-sm) var(--fluss-radius-sm) 0; +} + +/* ------------------------------------------------------------------------- + Doc footer — "edit this page" + last-updated as buttons + ------------------------------------------------------------------------- */ +.theme-doc-footer { + padding: var(--fluss-space-5); + border: 1px solid var(--fluss-ink-100); + border-radius: var(--fluss-radius-lg); + background: var(--fluss-paper); + margin-top: var(--fluss-space-12); + display: flex; + justify-content: space-between; + align-items: center; + flex-wrap: wrap; + gap: var(--fluss-space-3); +} + +.theme-edit-this-page { + display: inline-flex !important; + align-items: center; + gap: 6px; + height: 36px; + padding: 0 14px; + border-radius: var(--fluss-radius-md); + background: var(--fluss-blue-50); + color: var(--fluss-blue-700) !important; + font-weight: 600; + font-size: 0.875rem; + text-decoration: none !important; + border: 1px solid transparent; + transition: background-color var(--fluss-motion-fast) var(--fluss-ease-out), + border-color var(--fluss-motion-fast) var(--fluss-ease-out); +} + +.theme-edit-this-page:hover { + background: var(--fluss-blue-100); + border-color: var(--fluss-blue-300); +} + +/* ------------------------------------------------------------------------- + Premium code blocks + ------------------------------------------------------------------------- */ +div[class*='codeBlockContainer'] { + position: relative; + box-shadow: var(--fluss-shadow-sm); + transition: box-shadow var(--fluss-motion-base) var(--fluss-ease-out); +} + +div[class*='codeBlockContainer']:hover { + box-shadow: var(--fluss-shadow-md); +} + +/* Always-visible copy button (instead of fade-on-hover) */ +button[class*='copyButton'] { + opacity: 0.85; + transition: opacity var(--fluss-motion-fast) var(--fluss-ease-out), + color var(--fluss-motion-fast) var(--fluss-ease-out), + background-color var(--fluss-motion-fast) var(--fluss-ease-out), + border-color var(--fluss-motion-fast) var(--fluss-ease-out); +} + +div[class*='codeBlockContainer'] button[class*='copyButton']:not(:hover) { + opacity: 0.85; +} + +/* Language label in the corner — synthetic via data-attr if title not set */ +div[class*='codeBlockContainer']::before { + content: attr(data-language); + position: absolute; + top: 8px; + right: 56px; + font-family: var(--fluss-font-mono); + font-size: 0.6875rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.08em; + color: var(--fluss-ink-500); + pointer-events: none; + z-index: 1; +} + +/* ------------------------------------------------------------------------- + Blog list — featured first post + editorial polish + ------------------------------------------------------------------------- */ +main[class*='blogListPage'] article:first-of-type, +.blog-list-page > article:first-of-type { + padding: var(--fluss-space-10); + background: + radial-gradient(800px 300px at 100% 0%, rgba(34, 211, 238, 0.06), transparent 60%), + radial-gradient(600px 300px at 0% 100%, rgba(37, 99, 235, 0.06), transparent 60%), + var(--fluss-paper); + border-color: var(--fluss-blue-100); +} + +main[class*='blogListPage'] article:first-of-type header h2, +.blog-list-page > article:first-of-type header h2 { + font-size: 2.25rem; + font-weight: 700; + letter-spacing: -0.022em; + line-height: 1.1; +} + +/* "Featured" eyebrow on the first card */ +main[class*='blogListPage'] article:first-of-type::before, +.blog-list-page > article:first-of-type::before { + content: 'Latest post'; + display: inline-block; + margin-bottom: var(--fluss-space-3); + padding: 4px 10px; + background: linear-gradient(120deg, var(--fluss-blue-600), var(--fluss-cyan)); + color: #FFFFFF; + border-radius: var(--fluss-radius-pill); + font-family: var(--fluss-font-display); + font-size: 0.7rem; + font-weight: 700; + letter-spacing: 0.08em; + text-transform: uppercase; +} + +/* Blog post titles — display font in cards */ +article[itemtype='https://schema.org/BlogPosting'] header h2, +.blog-list-page article header h2 { + font-family: var(--fluss-font-display); + letter-spacing: -0.018em; +} + +/* Blog post meta row — compact, calm */ +.blog-list-page article header > div:not([class*='blogPostTags']), +article[itemtype='https://schema.org/BlogPosting'] header > div:not([class*='blogPostTags']) { + font-size: 0.85rem; + color: var(--fluss-ink-500); +} + +/* "Read more" link with arrow */ +.button.button--secondary::after, +a[class*='readMore']::after { + content: ' →'; + display: inline-block; + margin-left: 4px; + transition: transform var(--fluss-motion-base) var(--fluss-ease-out); +} + +.button.button--secondary:hover::after, +a[class*='readMore']:hover::after { + transform: translateX(3px); +} + +/* ------------------------------------------------------------------------- + Blog post page — editorial header + ------------------------------------------------------------------------- */ +.blog-post-page-no-sidebar article > header, +main[class*='blogPostPage'] article > header { + position: relative; + margin-bottom: var(--fluss-space-10); + padding-bottom: var(--fluss-space-6); + border-bottom: 1px solid var(--fluss-ink-100); +} + +.blog-post-page-no-sidebar article > header h1, +main[class*='blogPostPage'] article > header h1 { + font-family: var(--fluss-font-display); + font-size: clamp(2.25rem, 3vw + 1rem, 3.5rem); + font-weight: 700; + letter-spacing: -0.022em; + line-height: 1.05; + margin-top: var(--fluss-space-3); + color: var(--fluss-ink-950); +} + +/* Author row — bigger, card-like */ +.blog-post-page-no-sidebar article > header > div[class*='authorRow'], +main[class*='blogPostPage'] article > header > div[class*='authorRow'] { + margin-top: var(--fluss-space-5); + padding-top: var(--fluss-space-4); + border-top: 1px solid var(--fluss-ink-100); +} + +/* ------------------------------------------------------------------------- + Pagination card refinement + ------------------------------------------------------------------------- */ +.pagination-nav { + margin-top: var(--fluss-space-12); +} + +.pagination-nav__link { + position: relative; + overflow: hidden; +} + +.pagination-nav__link::before { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 3px; + height: 100%; + background: linear-gradient(180deg, var(--fluss-blue-600), var(--fluss-cyan)); + opacity: 0; + transition: opacity var(--fluss-motion-base) var(--fluss-ease-out); +} + +.pagination-nav__link:hover::before { + opacity: 1; +} + +.pagination-nav__link--prev .pagination-nav__sublabel::before { + content: '← '; +} + +.pagination-nav__link--next .pagination-nav__sublabel::after { + content: ' →'; +} + +/* ------------------------------------------------------------------------- + Admonition icons (color tinting) + ------------------------------------------------------------------------- */ +[class*='admonitionIcon'] svg { + filter: saturate(1.1); +} + +/* ------------------------------------------------------------------------- + Featured quote / pull-quote (use blockquote with .pull class via mdx) + Falls back gracefully for any blockquote. + ------------------------------------------------------------------------- */ +.markdown blockquote, +.theme-doc-markdown blockquote { + position: relative; + padding-left: var(--fluss-space-8); +} + +.markdown blockquote::before, +.theme-doc-markdown blockquote::before { + content: '\201C'; + position: absolute; + left: var(--fluss-space-3); + top: -0.1em; + font-family: var(--fluss-font-display); + font-size: 3rem; + font-weight: 700; + color: var(--fluss-blue-300); + line-height: 1; +} + +/* ------------------------------------------------------------------------- + Page-load fade-in for main content + ------------------------------------------------------------------------- */ +@keyframes flussFadeUp { + from { opacity: 0; transform: translateY(6px); } + to { opacity: 1; transform: translateY(0); } +} + +main[class*='docMainContainer'] article, +main[class*='blogPostPage'] article, +main[class*='blogListPage'] article { + animation: flussFadeUp var(--fluss-motion-slow) var(--fluss-ease-out) both; +} + +@media (prefers-reduced-motion: reduce) { + main[class*='docMainContainer'] article, + main[class*='blogPostPage'] article, + main[class*='blogListPage'] article { + animation: none; + } +} + +/* ------------------------------------------------------------------------- + Increase content max-width slightly for editorial breathability + (only inside docs / blog, not on the homepage) + ------------------------------------------------------------------------- */ +@media (min-width: 997px) { + main[class*='docItemContainer'] article, + main[class*='blogPostPage'] article { + max-width: 760px; + margin-left: auto; + margin-right: auto; + } +} + +/* ------------------------------------------------------------------------- + Inline code refinements (override generic Infima) + ------------------------------------------------------------------------- */ +.markdown p code, +.markdown li code, +.theme-doc-markdown p code, +.theme-doc-markdown li code { + font-size: 0.85em; + padding: 1px 6px; + background: var(--fluss-blue-50); + border: 1px solid rgba(37, 99, 235, 0.15); + color: var(--fluss-blue-800); + border-radius: var(--fluss-radius-sm); +} + +/* ------------------------------------------------------------------------- + Tag cloud / blog tags page + ------------------------------------------------------------------------- */ +ul[class*='tag'] { + display: flex; + flex-wrap: wrap; + gap: var(--fluss-space-2); + list-style: none; + padding-left: 0; +} + +ul[class*='tag'] li { + margin: 0; + list-style: none; +} + +/* ========================================================================= + ======================================================================== + HOMEPAGE NAVBAR — transparent over hero, solid otherwise + Driven by body.fluss-on-hero (set by IntersectionObserver in index.tsx). + ======================================================================== + ========================================================================= */ + +.navbar { + background-color: var(--ifm-navbar-background-color); + box-shadow: var(--ifm-navbar-shadow); + transition: background-color var(--fluss-motion-base) var(--fluss-ease-out), + backdrop-filter var(--fluss-motion-base) var(--fluss-ease-out), + box-shadow var(--fluss-motion-base) var(--fluss-ease-out), + border-color var(--fluss-motion-base) var(--fluss-ease-out); +} + +/* When the hero is on screen (homepage only), the navbar floats over it. */ +body.fluss-on-hero .navbar { + background-color: transparent; + box-shadow: none; + border-bottom-color: transparent; + -webkit-backdrop-filter: none; + backdrop-filter: none; +} + +body.fluss-on-hero .navbar__link, +body.fluss-on-hero .navbar__brand, +body.fluss-on-hero .navbar__title { + color: #FFFFFF; + text-shadow: 0 1px 3px rgba(6, 18, 43, 0.35); +} + +body.fluss-on-hero .navbar__link:hover { + background: rgba(255, 255, 255, 0.1); + color: #FFFFFF; +} + +body.fluss-on-hero .navbar__toggle, +body.fluss-on-hero .navbar__toggle svg { + color: #FFFFFF; + fill: #FFFFFF; +} + +/* Hero is dark, so always show the white logo on it. + srcDark logo asset is preferred (white_color_logo.svg). */ +body.fluss-on-hero[data-theme='light'] .navbar__logo img { + content: url('/img/logo/svg/white_color_logo.svg'); +} + +/* Search button on the hero — translucent glass */ +body.fluss-on-hero .DocSearch-Button { + background: rgba(255, 255, 255, 0.12); + border-color: rgba(255, 255, 255, 0.2); +} + +body.fluss-on-hero .DocSearch-Button:hover { + background: rgba(255, 255, 255, 0.2); + border-color: rgba(255, 255, 255, 0.35); +} + +body.fluss-on-hero .DocSearch-Button-Placeholder, +body.fluss-on-hero .DocSearch-Search-Icon { + color: rgba(255, 255, 255, 0.85); +} + +body.fluss-on-hero .DocSearch-Button-Keys { + background: rgba(255, 255, 255, 0.1); + border-color: rgba(255, 255, 255, 0.2); + color: rgba(255, 255, 255, 0.85); +} + +/* GitHub icon white on hero */ +body.fluss-on-hero .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-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12'/%3E%3C/svg%3E") no-repeat !important; +} + +/* When NOT on hero, on homepage, allow a frosted look so the navbar + feels less heavy as it appears. Outside the homepage, the default + solid navbar stays. */ +body.fluss-home:not(.fluss-on-hero) .navbar { + background-color: rgba(255, 255, 255, 0.85); + -webkit-backdrop-filter: blur(12px) saturate(1.4); + backdrop-filter: blur(12px) saturate(1.4); +} + +[data-theme='dark'] body.fluss-home:not(.fluss-on-hero) .navbar, +body.fluss-home:not(.fluss-on-hero)[data-theme='dark'] .navbar { + background-color: rgba(11, 30, 71, 0.85); +} + +/* ========================================================================= + ======================================================================== + COLOR MODE TOGGLE — branded sun/moon button + ======================================================================== + ========================================================================= */ + +.theme-toggle, +button[class*='colorModeToggle'], +button.clean-btn[class*='toggle'] { + width: 36px; + height: 36px; + border-radius: var(--fluss-radius-pill); + background: var(--fluss-ink-100); + border: 1px solid transparent; + color: var(--fluss-ink-700); + display: inline-flex; + align-items: center; + justify-content: center; + margin: 0 6px; + transition: background-color var(--fluss-motion-fast) var(--fluss-ease-out), + border-color var(--fluss-motion-fast) var(--fluss-ease-out), + transform var(--fluss-motion-fast) var(--fluss-ease-out), + color var(--fluss-motion-fast) var(--fluss-ease-out); +} + +button[class*='colorModeToggle']:hover, +button.clean-btn[class*='toggle']:hover { + background: var(--fluss-blue-50); + border-color: var(--fluss-blue-300); + color: var(--fluss-blue-700); + transform: translateY(-1px); +} + +button[class*='colorModeToggle'] svg, +button.clean-btn[class*='toggle'] svg { + width: 18px; + height: 18px; +} + +/* On hero (homepage), make it glassy and white-iconed */ +body.fluss-on-hero button[class*='colorModeToggle'], +body.fluss-on-hero button.clean-btn[class*='toggle'] { + background: rgba(255, 255, 255, 0.12); + border-color: rgba(255, 255, 255, 0.2); + color: #FFFFFF; +} + +body.fluss-on-hero button[class*='colorModeToggle']:hover, +body.fluss-on-hero button.clean-btn[class*='toggle']:hover { + background: rgba(255, 255, 255, 0.22); + border-color: rgba(255, 255, 255, 0.4); + color: #FFFFFF; +} + +/* ========================================================================= + ======================================================================== + DARK MODE — full token remap + component overrides + Triggered by [data-theme='dark'] on . + ======================================================================== + ========================================================================= */ + +[data-theme='dark'] { + /* Surfaces */ + --fluss-canvas: #06122B; + --fluss-paper: #0B1E47; + + /* Inks (inverted) */ + --fluss-ink-950: #F8FAFC; + --fluss-ink-700: #CBD5E1; + --fluss-ink-500: #94A3B8; + --fluss-ink-300: rgba(203, 213, 225, 0.22); + --fluss-ink-100: rgba(203, 213, 225, 0.10); + + /* Blue tints recalibrated for legibility on dark */ + --fluss-blue-50: rgba(37, 99, 235, 0.12); + --fluss-blue-100: rgba(37, 99, 235, 0.18); + --fluss-blue-300: #60A5FA; + --fluss-blue-500: #60A5FA; + --fluss-blue-600: #3B82F6; + --fluss-blue-700: #93C5FD; + --fluss-blue-800: #DBEAFE; + + /* Shadows on dark — softer, warmer */ + --fluss-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4); + --fluss-shadow-md: 0 4px 16px rgba(0, 0, 0, 0.4); + --fluss-shadow-lg: 0 16px 48px rgba(0, 0, 0, 0.5); + --fluss-shadow-glow: 0 0 0 1px rgba(96, 165, 250, 0.35), 0 12px 40px rgba(37, 99, 235, 0.35); + + /* Wire into Infima dark vars */ + --ifm-background-color: #06122B; + --ifm-background-surface-color: #0B1E47; + --ifm-color-primary: #60A5FA; + --ifm-color-primary-dark: #3B82F6; + --ifm-color-primary-darker: #2563EB; + --ifm-color-primary-darkest: #1A47B8; + --ifm-color-primary-light: #93C5FD; + --ifm-color-primary-lighter: #BFDBFE; + --ifm-color-primary-lightest: #DBEAFE; + + --ifm-heading-color: #F8FAFC; + --ifm-font-color-base: #CBD5E1; + + --ifm-navbar-background-color: rgba(11, 30, 71, 0.92); + --ifm-navbar-shadow: 0 1px 0 rgba(255, 255, 255, 0.06); + --ifm-navbar-link-color: #CBD5E1; + + --ifm-toc-border-color: rgba(255, 255, 255, 0.08); + --ifm-color-emphasis-200: rgba(255, 255, 255, 0.08); + --ifm-color-emphasis-300: rgba(255, 255, 255, 0.12); + --ifm-color-emphasis-600: #94A3B8; + --ifm-color-emphasis-700: #CBD5E1; + --ifm-color-emphasis-800: #F8FAFC; + + --ifm-menu-color-background-active: rgba(37, 99, 235, 0.14); + --ifm-menu-color-background-hover: rgba(37, 99, 235, 0.1); + + --docusaurus-highlighted-code-line-bg: rgba(37, 99, 235, 0.18); +} + +/* ----- Backgrounds & body ----- */ +[data-theme='dark'] body { + background: var(--fluss-canvas); + color: var(--fluss-ink-700); +} + +[data-theme='dark'] main[class*='docMainContainer'], +[data-theme='dark'] main[class*='blogPostPage'], +[data-theme='dark'] main[class*='docItemContainer'] { + background: + radial-gradient(circle at 1px 1px, rgba(255, 255, 255, 0.04) 1px, transparent 0) 0 0 / 32px 32px, + var(--fluss-canvas); +} + +/* ----- Navbar ----- */ +[data-theme='dark'] .navbar { + border-bottom-color: rgba(255, 255, 255, 0.06); +} + +[data-theme='dark'] .navbar__link { + color: #CBD5E1; +} + +[data-theme='dark'] .navbar__link:hover { + background: rgba(96, 165, 250, 0.12); + color: #BFDBFE; +} + +[data-theme='dark'] .navbar__link--active { + color: #93C5FD; +} + +[data-theme='dark'] .navbar__title { + color: #F8FAFC; +} + +/* DocSearch button (dark) */ +[data-theme='dark'] .DocSearch-Button { + background: rgba(255, 255, 255, 0.06); + border-color: rgba(255, 255, 255, 0.08); +} + +[data-theme='dark'] .DocSearch-Button:hover { + background: rgba(255, 255, 255, 0.1); + border-color: rgba(96, 165, 250, 0.4); +} + +[data-theme='dark'] .DocSearch-Button-Placeholder, +[data-theme='dark'] .DocSearch-Search-Icon { + color: #94A3B8; +} + +[data-theme='dark'] .DocSearch-Button-Keys { + background: rgba(255, 255, 255, 0.05); + border-color: rgba(255, 255, 255, 0.1); + color: #CBD5E1; +} + +/* GitHub icon (dark) */ +[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='%23F8FAFC' 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-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12'/%3E%3C/svg%3E") no-repeat !important; +} + +/* ----- Sidebar (dark) ----- */ +[data-theme='dark'] .theme-doc-sidebar-container { + background: var(--fluss-canvas); + border-right-color: rgba(255, 255, 255, 0.06); +} + +[data-theme='dark'] .menu__link { + color: #CBD5E1; +} + +[data-theme='dark'] .menu__link:hover { + background: rgba(96, 165, 250, 0.12); + color: #BFDBFE; +} + +[data-theme='dark'] .menu__link--active { + background: linear-gradient(90deg, rgba(96, 165, 250, 0.18) 0%, transparent 100%); + color: #BFDBFE; +} + +[data-theme='dark'] .menu__list .menu__list { + border-left-color: rgba(255, 255, 255, 0.08); +} + +[data-theme='dark'] .theme-doc-sidebar-menu > .menu__list-item:not(:first-child) { + border-top-color: rgba(255, 255, 255, 0.06); +} + +/* ----- Breadcrumbs (dark) ----- */ +[data-theme='dark'] .breadcrumbs__link { + color: #94A3B8; +} + +[data-theme='dark'] .breadcrumbs__link:hover { + color: #BFDBFE; + background: rgba(96, 165, 250, 0.12); +} + +[data-theme='dark'] .breadcrumbs__item--active .breadcrumbs__link { + color: #F8FAFC; + background: rgba(255, 255, 255, 0.06); +} + +/* ----- TOC card (dark) ----- */ +[data-theme='dark'] .theme-doc-toc-desktop, +[data-theme='dark'] [class*='tableOfContents'] { + background: var(--fluss-paper); + border-color: rgba(255, 255, 255, 0.08); +} + +[data-theme='dark'] .table-of-contents__link { + color: #94A3B8; +} + +[data-theme='dark'] .table-of-contents__link:hover { + color: #BFDBFE; +} + +[data-theme='dark'] .table-of-contents__link--active { + color: #BFDBFE; + background: rgba(96, 165, 250, 0.12); +} + +/* ----- Doc body (dark) ----- */ +[data-theme='dark'] .theme-doc-markdown, +[data-theme='dark'] .markdown { + color: #CBD5E1; +} + +[data-theme='dark'] .markdown h1, +[data-theme='dark'] .markdown h2, +[data-theme='dark'] .markdown h3, +[data-theme='dark'] .markdown h4, +[data-theme='dark'] .markdown h5, +[data-theme='dark'] .markdown h6, +[data-theme='dark'] .theme-doc-markdown h1, +[data-theme='dark'] .theme-doc-markdown h2, +[data-theme='dark'] .theme-doc-markdown h3 { + color: #F8FAFC; +} + +[data-theme='dark'] .markdown b, +[data-theme='dark'] .markdown strong { + color: #F8FAFC; +} + +[data-theme='dark'] .theme-doc-markdown a:not(.button):not(.card):not(.hash-link), +[data-theme='dark'] .markdown a:not(.button):not(.card):not(.hash-link) { + color: #93C5FD; + background-image: linear-gradient(0deg, #93C5FD 0, #93C5FD 100%); +} + +[data-theme='dark'] .markdown a.hash-link { + color: rgba(255, 255, 255, 0.18); +} + +[data-theme='dark'] .markdown a.hash-link:hover { + color: #93C5FD; +} + +/* ----- Tables (dark) ----- */ +[data-theme='dark'] .markdown table, +[data-theme='dark'] .theme-doc-markdown table { + border-color: rgba(255, 255, 255, 0.08); + background: var(--fluss-paper); +} + +[data-theme='dark'] .markdown thead, +[data-theme='dark'] .theme-doc-markdown thead { + background: rgba(96, 165, 250, 0.1); +} + +[data-theme='dark'] .markdown thead th, +[data-theme='dark'] .theme-doc-markdown thead th { + color: #F8FAFC; + border-color: rgba(255, 255, 255, 0.08); + background: transparent; +} + +[data-theme='dark'] .markdown tbody tr, +[data-theme='dark'] .theme-doc-markdown tbody tr { + background: transparent; +} + +[data-theme='dark'] .markdown tbody tr:nth-child(even), +[data-theme='dark'] .theme-doc-markdown tbody tr:nth-child(even) { + background: rgba(255, 255, 255, 0.03); +} + +[data-theme='dark'] .markdown table tr td, +[data-theme='dark'] .markdown table tr th { + border-color: rgba(255, 255, 255, 0.06); +} + +[data-theme='dark'] .markdown p code, +[data-theme='dark'] .markdown li code, +[data-theme='dark'] .markdown td code, +[data-theme='dark'] .theme-doc-markdown p code, +[data-theme='dark'] .theme-doc-markdown li code, +[data-theme='dark'] .theme-doc-markdown td code, +[data-theme='dark'] code { + background: rgba(96, 165, 250, 0.14); + border-color: rgba(96, 165, 250, 0.25); + color: #BFDBFE; +} + +/* ----- Blockquotes (dark) ----- */ +[data-theme='dark'] .markdown blockquote, +[data-theme='dark'] .theme-doc-markdown blockquote { + background: rgba(96, 165, 250, 0.08); + border-left-color: #60A5FA; + color: #CBD5E1; +} + +[data-theme='dark'] .markdown blockquote::before, +[data-theme='dark'] .theme-doc-markdown blockquote::before { + color: rgba(96, 165, 250, 0.4); +} + +/* ----- Admonitions (dark) ----- */ +[data-theme='dark'] .theme-admonition, +[data-theme='dark'] .alert, +[data-theme='dark'] [class*='admonition_'] { + background: var(--fluss-paper); + border-color: rgba(255, 255, 255, 0.1); + color: #CBD5E1; +} + +[data-theme='dark'] .alert--secondary, +[data-theme='dark'] .theme-admonition-note { + background: rgba(255, 255, 255, 0.04); + border-left-color: #94A3B8; +} + +[data-theme='dark'] .alert--info, +[data-theme='dark'] .theme-admonition-info { + background: rgba(37, 99, 235, 0.12); + border-left-color: #60A5FA; +} + +[data-theme='dark'] .alert--success, +[data-theme='dark'] .theme-admonition-tip { + background: rgba(16, 185, 129, 0.12); + border-left-color: #34D399; +} + +[data-theme='dark'] .alert--warning, +[data-theme='dark'] .theme-admonition-warning, +[data-theme='dark'] .theme-admonition-caution { + background: rgba(245, 158, 11, 0.12); + border-left-color: #FBBF24; +} + +[data-theme='dark'] .alert--danger, +[data-theme='dark'] .theme-admonition-danger { + background: rgba(239, 68, 68, 0.12); + border-left-color: #F87171; +} + +/* ----- Code blocks (dark) ----- */ +[data-theme='dark'] div[class*='codeBlockContainer'] { + border-color: rgba(255, 255, 255, 0.08); +} + +[data-theme='dark'] div[class*='codeBlockContent'] pre { + background: #06122B; +} + +[data-theme='dark'] div[class*='codeBlockTitle'] { + background: rgba(255, 255, 255, 0.04); + border-bottom-color: rgba(255, 255, 255, 0.08); + color: #CBD5E1; +} + +[data-theme='dark'] button[class*='copyButton'] { + background: rgba(255, 255, 255, 0.06); + border-color: rgba(255, 255, 255, 0.1); + color: #CBD5E1; +} + +[data-theme='dark'] button[class*='copyButton']:hover { + background: rgba(96, 165, 250, 0.18); + border-color: rgba(96, 165, 250, 0.45); + color: #DBEAFE; +} + +[data-theme='dark'] .theme-code-block-highlighted-line, +[data-theme='dark'] .docusaurus-highlight-code-line { + background: rgba(96, 165, 250, 0.14); + border-left-color: #60A5FA; +} + +/* ----- Tabs (dark) ----- */ +[data-theme='dark'] .tabs { + border-bottom-color: rgba(255, 255, 255, 0.08); +} + +[data-theme='dark'] .tabs__item { + color: #94A3B8; +} + +[data-theme='dark'] .tabs__item:hover { + background: rgba(96, 165, 250, 0.12); + color: #BFDBFE; +} + +[data-theme='dark'] .tabs__item--active { + color: #BFDBFE; + border-bottom-color: #60A5FA; +} + +/* ----- Pagination (dark) ----- */ +[data-theme='dark'] .pagination-nav__link { + background: var(--fluss-paper); + border-color: rgba(255, 255, 255, 0.08); +} + +[data-theme='dark'] .pagination-nav__link:hover { + border-color: rgba(96, 165, 250, 0.45); +} + +[data-theme='dark'] .pagination-nav__sublabel { + color: #94A3B8; +} + +[data-theme='dark'] .pagination-nav__label { + color: #F8FAFC; +} + +/* ----- Doc footer (dark) ----- */ +[data-theme='dark'] .theme-doc-footer { + background: var(--fluss-paper); + border-color: rgba(255, 255, 255, 0.08); +} + +[data-theme='dark'] .theme-edit-this-page { + background: rgba(96, 165, 250, 0.12); + color: #93C5FD !important; +} + +[data-theme='dark'] .theme-edit-this-page:hover { + background: rgba(96, 165, 250, 0.2); + border-color: rgba(96, 165, 250, 0.4); +} + +[data-theme='dark'] .theme-last-updated { + color: #94A3B8; +} + +/* ----- Blog list / blog post (dark) ----- */ +[data-theme='dark'] article[itemtype='https://schema.org/BlogPosting'], +[data-theme='dark'] .blog-list-page article { + background: var(--fluss-paper); + border-color: rgba(255, 255, 255, 0.08); +} + +[data-theme='dark'] article[itemtype='https://schema.org/BlogPosting'] header h2 a, +[data-theme='dark'] .blog-list-page article header h2 a { + color: #F8FAFC; +} + +[data-theme='dark'] article[itemtype='https://schema.org/BlogPosting'] header h2 a:hover, +[data-theme='dark'] .blog-list-page article header h2 a:hover { + color: #93C5FD; +} + +[data-theme='dark'] main[class*='blogListPage'] article:first-of-type, +[data-theme='dark'] .blog-list-page > article:first-of-type { + background: + radial-gradient(800px 300px at 100% 0%, rgba(34, 211, 238, 0.1), transparent 60%), + radial-gradient(600px 300px at 0% 100%, rgba(37, 99, 235, 0.12), transparent 60%), + var(--fluss-paper); + border-color: rgba(96, 165, 250, 0.3); +} + +[data-theme='dark'] a.tag, +[data-theme='dark'] a[class*='tag_'] { + background: rgba(96, 165, 250, 0.14); + color: #BFDBFE; + border-color: transparent; +} + +[data-theme='dark'] a.tag:hover, +[data-theme='dark'] a[class*='tag_']:hover { + background: rgba(96, 165, 250, 0.22); + border-color: rgba(96, 165, 250, 0.4); + color: #DBEAFE; +} + +/* ----- Doc cards (dark) ----- */ +[data-theme='dark'] article.card, +[data-theme='dark'] .theme-doc-card { + background: var(--fluss-paper); + border-color: rgba(255, 255, 255, 0.08); +} + +[data-theme='dark'] article.card:hover, +[data-theme='dark'] .theme-doc-card:hover { + border-color: rgba(96, 165, 250, 0.45); +} + +/* ----- Version banner (dark) ----- */ +[data-theme='dark'] div[class*='docVersionBanner'] { + background: rgba(96, 165, 250, 0.12); + border-color: rgba(96, 165, 250, 0.4); + color: #DBEAFE; +} + +[data-theme='dark'] div[class*='docVersionBanner'] b, +[data-theme='dark'] div[class*='docVersionBanner'] a { + color: #BFDBFE; +} + +/* ----- Dropdown menus (dark) ----- */ +[data-theme='dark'] .dropdown__menu { + background: var(--fluss-paper); + border-color: rgba(255, 255, 255, 0.1); +} + +[data-theme='dark'] .dropdown__link { + color: #CBD5E1; +} + +[data-theme='dark'] .dropdown__link:hover, +[data-theme='dark'] .dropdown__link--active { + background: rgba(96, 165, 250, 0.14); + color: #BFDBFE; +} + +/* ----- KBD (dark) ----- */ +[data-theme='dark'] kbd { + background: var(--fluss-paper); + border-color: rgba(255, 255, 255, 0.16); + color: #DBEAFE; +} + +/* ----- Color toggle (dark) ----- */ +[data-theme='dark'] button[class*='colorModeToggle'], +[data-theme='dark'] button.clean-btn[class*='toggle'] { + background: rgba(255, 255, 255, 0.08); + color: #DBEAFE; +} + +[data-theme='dark'] button[class*='colorModeToggle']:hover, +[data-theme='dark'] button.clean-btn[class*='toggle']:hover { + background: rgba(96, 165, 250, 0.18); + border-color: rgba(96, 165, 250, 0.4); + color: #DBEAFE; +} + +/* ----- Mobile drawer (dark) ----- */ +[data-theme='dark'] .navbar-sidebar { + background: var(--fluss-canvas); +} + +[data-theme='dark'] .navbar-sidebar__brand { + border-bottom-color: rgba(255, 255, 255, 0.06); +} + +/* ----- Footer logo invert if needed (dark) ----- */ +[data-theme='dark'] .footer__logo { + filter: brightness(1.1); +} + +/* ----- Scrollbars (dark) ----- */ +[data-theme='dark'] .theme-doc-markdown pre::-webkit-scrollbar-thumb, +[data-theme='dark'] .theme-doc-sidebar-container::-webkit-scrollbar-thumb, +[data-theme='dark'] .table-of-contents::-webkit-scrollbar-thumb { + background: rgba(255, 255, 255, 0.16); +} + +[data-theme='dark'] .theme-doc-markdown pre::-webkit-scrollbar-thumb:hover, +[data-theme='dark'] .theme-doc-sidebar-container::-webkit-scrollbar-thumb:hover, +[data-theme='dark'] .table-of-contents::-webkit-scrollbar-thumb:hover { + background: rgba(255, 255, 255, 0.3); +} + +/* ----- Selection (dark) ----- */ +[data-theme='dark'] ::selection { + background: rgba(96, 165, 250, 0.32); + color: #F8FAFC; +} + +/* ========================================================================= + ======================================================================== + RESPONSIVE — small viewport refinements (any device) + ======================================================================== + ========================================================================= */ + +@media (max-width: 996px) { + /* Sidebar / TOC card padding tightened */ + .theme-doc-toc-desktop, + [class*='tableOfContents'] { + padding: var(--fluss-space-4); + } + + /* Doc/blog content has full width on mobile */ + main[class*='docItemContainer'] article, + main[class*='blogPostPage'] article { + max-width: 100%; + } +} + +@media (max-width: 768px) { + /* Tighten section vertical padding on small screens */ + .theme-doc-markdown, + .markdown { + font-size: 1rem; + line-height: 1.7; + } + + /* Heading dot indicator hidden on mobile (no margin-room) */ + .markdown h2::before, + .theme-doc-markdown h2::before { + display: none; + } + + /* Pagination cards stack neatly */ + .pagination-nav { + grid-template-columns: 1fr; + gap: var(--fluss-space-3); + } + + /* Footer columns: tighter gap */ + .footer .col { + margin-bottom: var(--fluss-space-6); + } + + /* Doc footer wraps */ + .theme-doc-footer { + flex-direction: column; + align-items: flex-start; + } + + /* Smaller anchor offset */ + .theme-doc-markdown h1, + .theme-doc-markdown h2, + .theme-doc-markdown h3, + .theme-doc-markdown h4 { + scroll-margin-top: 72px; + } +} + +@media (max-width: 600px) { + /* Smaller hero internal title in case window is very narrow */ + .navbar__title { + font-size: 0.95rem; + } + + /* Hide kbd from being too cramped */ + kbd { + font-size: 0.75em; + } + + /* Tag pill smaller */ + a.tag, + a[class*='tag_'] { + height: 22px; + font-size: 0.75rem; + padding: 0 8px; + } + + /* Compare/markdown tables become horizontally scrollable comfortably */ + .markdown table, + .theme-doc-markdown table { + display: block; + overflow-x: auto; + white-space: nowrap; + } +} + +/* Very small screens — squeeze the navbar items so they don't overflow */ +@media (max-width: 480px) { + .navbar__items--right .navbar-ask-ai { + padding: 4px 8px; + font-size: 0.75rem; + } +} + +/* ========================================================================= + ======================================================================== + HOMEPAGE — dark-mode adjustments for sections that hardcode blue-950 + ======================================================================== + ========================================================================= */ + +/* In dark mode, the canvas itself is --fluss-blue-950, so sections that + intentionally use that color blend in. Lift them slightly so they read + as distinct surfaces. CSS module class names are matched by suffix. */ +[data-theme='dark'] section[class*='sectionDark'] { + background: linear-gradient(180deg, #0B1E47 0%, #06122B 100%); + border-top: 1px solid rgba(255, 255, 255, 0.05); + border-bottom: 1px solid rgba(255, 255, 255, 0.05); +} + +[data-theme='dark'] section[class*='ctaBand'] { + background: + radial-gradient(800px 400px at 100% 0%, rgba(34, 211, 238, 0.18), transparent 60%), + radial-gradient(600px 400px at 0% 100%, rgba(124, 58, 237, 0.18), transparent 60%), + linear-gradient(180deg, #0B1E47 0%, #06122B 100%); +} + +/* Wash sections in dark mode — replace the very faint blue tint with a + slightly more visible blue glow so the band is legible. */ +[data-theme='dark'] section[class*='sectionWash'] { + background: + radial-gradient(900px 500px at 50% 0%, rgba(37, 99, 235, 0.12), transparent 60%), + var(--fluss-canvas); +} + +/* Comparison table on dark mode: paper bg, refined column highlight */ +[data-theme='dark'] div[class*='compareWrap'] { + background: var(--fluss-paper); + border-color: rgba(255, 255, 255, 0.08); +} + +[data-theme='dark'] table[class*='compareTable'] thead th { + background: rgba(255, 255, 255, 0.04); + color: #F8FAFC; + border-bottom-color: rgba(255, 255, 255, 0.1); +} + +[data-theme='dark'] table[class*='compareTable'] thead th[class*='colHighlight'] { + background: linear-gradient(180deg, var(--fluss-blue-700) 0%, var(--fluss-blue-600) 100%); +} + +[data-theme='dark'] table[class*='compareTable'] tbody td { + border-top-color: rgba(255, 255, 255, 0.05); + color: #CBD5E1; +} + +[data-theme='dark'] table[class*='compareTable'] tbody td:first-child { + background: rgba(255, 255, 255, 0.03); + color: #F8FAFC; +} + +[data-theme='dark'] table[class*='compareTable'] tbody td[class*='colHighlight'] { + background: rgba(96, 165, 250, 0.1); + color: #DBEAFE; +} + +/* Freshness tiles in dark mode */ +[data-theme='dark'] div[class*='freshTile']:not([class*='freshTileHighlight']) { + background: var(--fluss-paper); + border-color: rgba(255, 255, 255, 0.08); +} + +[data-theme='dark'] div[class*='freshTile']:not([class*='freshTileHighlight']):hover { + border-color: rgba(96, 165, 250, 0.45); +} + +[data-theme='dark'] div[class*='freshLatency'] { + color: #F8FAFC; +} + +[data-theme='dark'] div[class*='freshTile']:not([class*='freshTileHighlight']) [class*='freshSub'] { + color: #CBD5E1; +} + +/* Code card in dark mode — slightly lifted so it stands above canvas */ +[data-theme='dark'] div[class*='codeCard'] { + background: linear-gradient(180deg, #0B1E47 0%, #06122B 100%); + border-color: rgba(255, 255, 255, 0.08); +} + +/* Section eyebrows / titles / leads in dark mode */ +[data-theme='dark'] span[class*='eyebrow'] { + color: #93C5FD; +} + +[data-theme='dark'] h2[class*='sectionTitle'] { + color: #F8FAFC; +} + +[data-theme='dark'] p[class*='sectionLead'] { + color: #CBD5E1; +} + +/* Dark-mode hover/border on hover-lift cards */ +[data-theme='dark'] div[class*='card']:hover, +[data-theme='dark'] article[class*='card']:hover { + border-color: rgba(96, 165, 250, 0.45); +} + +[data-theme='dark'] div[class*='cardIcon'], +[data-theme='dark'] div[class*='iconWrap'] { + background: rgba(96, 165, 250, 0.14); + color: #BFDBFE; +} + +/* Version-badge chips on Flink section */ +[data-theme='dark'] span[class*='badge'] { + background: rgba(96, 165, 250, 0.18); + color: #DBEAFE; +} + +/* Multiple Systems Tax section in dark mode */ +[data-theme='dark'] section[class*='taxSection'] { + background: + radial-gradient(900px 500px at 50% 0%, rgba(96, 165, 250, 0.14), transparent 60%), + var(--fluss-canvas); + border-color: rgba(255, 255, 255, 0.06); +} + +[data-theme='dark'] div[class*='taxStackItem'] { + background: var(--fluss-paper); + border-color: rgba(255, 255, 255, 0.08); + border-left-color: #FB7185; +} + +[data-theme='dark'] div[class*='taxStackTitle'] { + color: #F8FAFC; +} + +[data-theme='dark'] div[class*='taxStackSub'] { + color: #94A3B8; +} + +[data-theme='dark'] span[class*='taxStackIndex'] { + color: rgba(255, 255, 255, 0.22); +} + +[data-theme='dark'] span[class*='taxLabelBefore'] { + background: rgba(244, 63, 94, 0.14); + color: #FDA4AF; + border-color: rgba(244, 63, 94, 0.3); +} + +[data-theme='dark'] p[class*='taxFootnote'] { +} diff --git a/website/src/pages/index.module.css b/website/src/pages/index.module.css index 7ea638a7e2..91b5974fd8 100644 --- a/website/src/pages/index.module.css +++ b/website/src/pages/index.module.css @@ -16,87 +16,896 @@ */ /** - * CSS files with the .module.css suffix will be treated as CSS modules - * and scoped locally. + * Homepage CSS modules. Marketing-only styles for the redesigned home page. */ +.homepageWrapper { + background: var(--fluss-canvas); +} + +/* ========================================================================= + Hero + ========================================================================= */ .heroBanner { - padding: 5rem 0 11rem 0; - text-align: center; position: relative; overflow: hidden; - margin-left: -1px; margin-top: -60px; - padding-top: calc(5rem + 60px); + padding: calc(var(--fluss-space-32) + 60px) 0 var(--fluss-space-24); + background: + radial-gradient(1200px 600px at 80% 0%, rgba(34, 211, 238, 0.18), transparent 60%), + radial-gradient(900px 500px at 10% 100%, rgba(59, 130, 246, 0.25), transparent 60%), + linear-gradient(180deg, var(--fluss-blue-950) 0%, var(--fluss-blue-900) 60%, var(--fluss-blue-800) 100%); + color: #FFFFFF; +} + +.heroBanner::before { + content: ''; + position: absolute; + inset: 0; + background-image: + linear-gradient(rgba(255, 255, 255, 0.04) 1px, transparent 1px), + linear-gradient(90deg, rgba(255, 255, 255, 0.04) 1px, transparent 1px); + background-size: 56px 56px; + mask-image: radial-gradient(ellipse at center, black 50%, transparent 80%); + -webkit-mask-image: radial-gradient(ellipse at center, black 50%, transparent 80%); + pointer-events: none; +} + +.heroInner { + position: relative; + display: grid; + grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr); + gap: var(--fluss-space-12); + align-items: center; +} + +.heroEyebrow { + display: inline-flex; + align-items: center; + gap: var(--fluss-space-2); + padding: 6px 12px; + border-radius: var(--fluss-radius-pill); + background: rgba(255, 255, 255, 0.08); + border: 1px solid rgba(255, 255, 255, 0.16); + color: var(--fluss-blue-100); + font-size: 0.8125rem; + font-weight: 500; + letter-spacing: 0.04em; + text-transform: uppercase; + backdrop-filter: blur(8px); + margin-bottom: var(--fluss-space-6); +} + +.heroEyebrow .dot { + width: 6px; + height: 6px; + border-radius: 50%; + background: var(--fluss-cyan); + box-shadow: 0 0 0 4px rgba(34, 211, 238, 0.2); +} + +.heroTitle { + font-family: var(--fluss-font-display); + font-size: clamp(2.5rem, 5vw + 1rem, 4.25rem); + font-weight: 700; + line-height: 1.05; + letter-spacing: -0.022em; + text-wrap: balance; + margin: 0 0 var(--fluss-space-6); + color: #FFFFFF; +} + +.heroTitle .accent { + background: linear-gradient(120deg, var(--fluss-cyan) 0%, var(--fluss-blue-300) 60%, #FFFFFF 100%); + -webkit-background-clip: text; + background-clip: text; + color: transparent; +} + +.heroSubtitle { + font-size: 1.0625rem; + line-height: 1.65; + color: rgba(219, 234, 254, 0.88); + max-width: 580px; + margin: 0 0 var(--fluss-space-8); + text-wrap: pretty; +} + +.heroCtas { + display: flex; + flex-wrap: wrap; + gap: var(--fluss-space-3); + align-items: center; +} + +.btnPrimary { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 8px; + height: 48px; + padding: 0 22px; + border-radius: var(--fluss-radius-md); + background: var(--fluss-blue-600); + color: #FFFFFF; + font-weight: 600; + font-size: 0.9375rem; + letter-spacing: 0.005em; + box-shadow: var(--fluss-shadow-glow); + transition: transform var(--fluss-motion-fast) var(--fluss-ease-out), + box-shadow var(--fluss-motion-fast) var(--fluss-ease-out), + background-color var(--fluss-motion-fast) var(--fluss-ease-out); +} + +.btnPrimary:hover { + transform: translateY(-1px); + background: var(--fluss-blue-500); + color: #FFFFFF; + text-decoration: none; + box-shadow: 0 0 0 1px rgba(59, 130, 246, 0.35), 0 16px 48px rgba(37, 99, 235, 0.32); +} + +.btnSecondary { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 8px; + height: 48px; + padding: 0 18px; + border-radius: var(--fluss-radius-md); + background: rgba(255, 255, 255, 0.06); + color: #FFFFFF; + font-weight: 600; + font-size: 0.9375rem; + border: 1px solid rgba(255, 255, 255, 0.18); + backdrop-filter: blur(10px); + transition: background-color var(--fluss-motion-fast) var(--fluss-ease-out), + border-color var(--fluss-motion-fast) var(--fluss-ease-out), + transform var(--fluss-motion-fast) var(--fluss-ease-out); +} + +.btnSecondary:hover { + background: rgba(255, 255, 255, 0.12); + border-color: rgba(255, 255, 255, 0.32); + color: #FFFFFF; + text-decoration: none; + transform: translateY(-1px); +} + +.btnGhost { + display: inline-flex; + align-items: center; + gap: 6px; + height: 48px; + padding: 0 8px; + color: var(--fluss-blue-100); + font-weight: 500; + font-size: 0.9375rem; + transition: color var(--fluss-motion-fast) var(--fluss-ease-out); +} + +.btnGhost:hover { + color: #FFFFFF; + text-decoration: none; +} + +.btnIcon { + width: 18px; + height: 18px; +} + +/* Hero diagram (right column) — aspect-ratio matches the SVG viewBox (620:480) */ +.heroDiagram { + position: relative; + width: 100%; + max-width: 620px; + aspect-ratio: 620 / 480; + display: flex; + align-items: center; + justify-content: center; +} + +.heroDiagram svg { + width: 100%; + height: 100%; + overflow: visible; + filter: drop-shadow(0 24px 60px rgba(34, 211, 238, 0.25)); +} + +/* Trust strip below hero */ +.trustStrip { + position: relative; + margin-top: var(--fluss-space-16); + padding: var(--fluss-space-5) var(--fluss-space-6); + border-radius: var(--fluss-radius-lg); + background: rgba(255, 255, 255, 0.04); + border: 1px solid rgba(255, 255, 255, 0.1); + display: flex; + flex-wrap: wrap; + gap: var(--fluss-space-6); + align-items: center; + justify-content: space-between; + color: rgba(219, 234, 254, 0.85); + font-size: 0.875rem; + backdrop-filter: blur(10px); +} + +.trustItem { + display: inline-flex; + align-items: center; + gap: 8px; +} + +.trustDivider { + width: 1px; + height: 18px; + background: rgba(255, 255, 255, 0.12); +} - @media screen and (min-width: 997px) { - background-image: url("@site/static/img/new_banner.png"); - background-size: cover; - background-position: center center; - background-repeat: no-repeat; +@media (max-width: 996px) { + .heroBanner { + padding-top: calc(var(--fluss-space-20) + 60px); + padding-bottom: var(--fluss-space-20); + } + .heroInner { + grid-template-columns: 1fr; + gap: var(--fluss-space-16); + } + .heroDiagram { + order: -1; + max-width: 480px; + margin: 0 auto; + } + .trustStrip { + flex-direction: column; + align-items: flex-start; + gap: var(--fluss-space-3); } + .trustDivider { + display: none; + } +} + +/* ========================================================================= + Generic section primitives + ========================================================================= */ +.section { + padding: var(--fluss-space-24) 0; +} + +.sectionTight { + padding: var(--fluss-space-16) 0; +} + +.sectionDark { + background: var(--fluss-blue-950); + color: rgba(219, 234, 254, 0.88); +} + +.sectionWash { + background: var(--fluss-blue-50); +} + +.eyebrow { + display: inline-block; + color: var(--fluss-blue-700); + font-size: 0.8125rem; + font-weight: 600; + letter-spacing: 0.06em; + text-transform: uppercase; + margin-bottom: var(--fluss-space-3); +} + +.sectionDark .eyebrow { + color: var(--fluss-cyan); +} + +.sectionTitle { + font-family: var(--fluss-font-display); + font-size: clamp(2rem, 3vw + 1rem, 3rem); + font-weight: 700; + line-height: 1.1; + letter-spacing: -0.02em; + text-wrap: balance; + margin: 0 0 var(--fluss-space-4); + color: var(--fluss-ink-950); +} + +.sectionDark .sectionTitle { + color: #FFFFFF; +} + +.sectionLead { + font-size: 1.125rem; + line-height: 1.65; + color: var(--fluss-ink-500); + max-width: 720px; + margin: 0 0 var(--fluss-space-12); + text-wrap: pretty; +} + +.sectionDark .sectionLead { + color: rgba(219, 234, 254, 0.78); +} + +.sectionHeader { + text-align: left; + margin-bottom: var(--fluss-space-12); +} + +.sectionHeaderCenter { + text-align: center; + max-width: 760px; + margin-left: auto; + margin-right: auto; +} + +.sectionHeaderCenter .sectionLead { + margin-left: auto; + margin-right: auto; +} + +/* ========================================================================= + Comparison matrix + ========================================================================= */ +.compareWrap { + border-radius: var(--fluss-radius-xl); + overflow: hidden; + border: 1px solid var(--fluss-ink-100); + background: var(--fluss-paper); + box-shadow: var(--fluss-shadow-md); +} + +.compareTable { + width: 100%; + border-collapse: collapse; + font-size: 0.9375rem; +} + +.compareTable thead th { + background: var(--fluss-ink-100); + color: var(--fluss-ink-950); + font-weight: 600; + text-align: left; + padding: var(--fluss-space-4) var(--fluss-space-5); + border-bottom: 1px solid var(--fluss-ink-300); + white-space: nowrap; +} + +.compareTable thead th.colHighlight { + background: linear-gradient(180deg, var(--fluss-blue-700) 0%, var(--fluss-blue-600) 100%); + color: #FFFFFF; +} + +.compareTable tbody td { + padding: var(--fluss-space-4) var(--fluss-space-5); + border-top: 1px solid var(--fluss-ink-100); + vertical-align: top; + color: var(--fluss-ink-700); +} + +.compareTable tbody td:first-child { + font-weight: 600; + color: var(--fluss-ink-950); + background: var(--fluss-canvas); + width: 28%; +} + +.compareTable tbody td.colHighlight { + background: var(--fluss-blue-50); + color: var(--fluss-ink-950); + font-weight: 500; +} - @media screen and (max-width: 996px) { - background: var(--ifm-color-primary-darkest); +@media (max-width: 800px) { + .compareWrap { + overflow-x: auto; + } + .compareTable { + min-width: 720px; } +} +/* ========================================================================= + Flink integration band + ========================================================================= */ +.flinkBand { + display: grid; + grid-template-columns: minmax(0, 1fr) minmax(0, 1.1fr); + gap: var(--fluss-space-12); + align-items: center; } -@media screen and (max-width: 996px) { - .heroBanner { - padding: 2rem; +@media (max-width: 996px) { + .flinkBand { + grid-template-columns: 1fr; } +} + +.versionBadges { + display: flex; + flex-wrap: wrap; + gap: var(--fluss-space-2); + margin-top: var(--fluss-space-6); +} + +.badge { + display: inline-flex; + align-items: center; + height: 28px; + padding: 0 12px; + border-radius: var(--fluss-radius-pill); + background: var(--fluss-blue-100); + color: var(--fluss-blue-800); + font-size: 0.8125rem; + font-weight: 600; + letter-spacing: 0.01em; +} - .heroBanner :global(.hero__title) { - font-size: 2.5rem; +/* ========================================================================= + Code block (homepage snippet) + ========================================================================= */ +.codeCard { + border-radius: var(--fluss-radius-lg); + background: var(--fluss-blue-950); + border: 1px solid rgba(255, 255, 255, 0.06); + box-shadow: var(--fluss-shadow-lg); + overflow: hidden; +} + +.codeChrome { + display: flex; + gap: 6px; + padding: 12px 14px; + background: rgba(255, 255, 255, 0.02); + border-bottom: 1px solid rgba(255, 255, 255, 0.06); +} + +.codeChrome span { + width: 10px; + height: 10px; + border-radius: 50%; + background: rgba(255, 255, 255, 0.12); +} + +.codeChrome span:first-child { background: #FF5F57; } +.codeChrome span:nth-child(2) { background: #FEBC2E; } +.codeChrome span:nth-child(3) { background: #28C840; } + +.codeBody { + padding: var(--fluss-space-5) var(--fluss-space-6); + color: rgba(219, 234, 254, 0.92); + font-family: var(--fluss-font-mono); + font-size: 0.875rem; + line-height: 1.7; + overflow-x: auto; + margin: 0; + background: transparent; +} + +.codeBody .tk-keyword { color: #93C5FD; } +.codeBody .tk-string { color: #A3E635; } +.codeBody .tk-comment { color: #64748B; font-style: italic; } +.codeBody .tk-fn { color: #22D3EE; } + +/* ========================================================================= + Freshness diagram (CSS-only timeline) + ========================================================================= */ +.freshness { + margin-top: var(--fluss-space-8); + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: var(--fluss-space-4); +} + +@media (max-width: 800px) { + .freshness { + grid-template-columns: 1fr; } +} + +.freshTile { + padding: var(--fluss-space-6); + border-radius: var(--fluss-radius-lg); + background: var(--fluss-paper); + border: 1px solid var(--fluss-ink-100); + transition: transform var(--fluss-motion-base) var(--fluss-ease-out), + box-shadow var(--fluss-motion-base) var(--fluss-ease-out), + border-color var(--fluss-motion-base) var(--fluss-ease-out); +} + +.freshTile:hover { + transform: translateY(-2px); + box-shadow: var(--fluss-shadow-md); + border-color: var(--fluss-blue-300); +} + +.freshTileHighlight { + background: linear-gradient(180deg, var(--fluss-blue-700) 0%, var(--fluss-blue-800) 100%); + border-color: transparent; + color: #FFFFFF; + box-shadow: var(--fluss-shadow-glow); +} + +.freshLatency { + font-family: var(--fluss-font-display); + font-size: 1.875rem; + font-weight: 700; + letter-spacing: -0.02em; + margin-bottom: var(--fluss-space-1); + color: var(--fluss-ink-950); +} + +.freshTileHighlight .freshLatency { + color: #FFFFFF; +} + +.freshLabel { + font-size: 0.875rem; + color: var(--fluss-ink-500); +} + +.freshTileHighlight .freshLabel { + color: rgba(219, 234, 254, 0.85); +} + +.freshSub { + margin-top: var(--fluss-space-3); + font-size: 0.9375rem; + color: var(--fluss-ink-700); +} + +.freshTileHighlight .freshSub { + color: rgba(255, 255, 255, 0.95); + font-weight: 500; +} + +/* ========================================================================= + Stats / community + ========================================================================= */ +.statsRow { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: var(--fluss-space-4); + margin-top: var(--fluss-space-12); +} - .heroBanner :global(.hero__subtitle) { - font-size: 1.2rem; +@media (max-width: 800px) { + .statsRow { + grid-template-columns: repeat(2, 1fr); } } -.heroBanner :global(.hero__title) { - font-family: 'Roboto', sans-serif; - font-size: 4rem; +.statCard { + padding: var(--fluss-space-6); + border-radius: var(--fluss-radius-lg); + background: rgba(255, 255, 255, 0.04); + border: 1px solid rgba(255, 255, 255, 0.08); + text-align: left; +} + +.statValue { + font-family: var(--fluss-font-display); + font-size: 2.25rem; font-weight: 700; + color: #FFFFFF; letter-spacing: -0.02em; + margin-bottom: var(--fluss-space-1); + line-height: 1; +} + +.statLabel { + font-size: 0.875rem; + color: rgba(219, 234, 254, 0.7); +} + +.communityGrid { + margin-top: var(--fluss-space-12); + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: var(--fluss-space-4); +} + +@media (max-width: 800px) { + .communityGrid { + grid-template-columns: 1fr; + } +} + +.communityCard { + display: flex; + flex-direction: column; + padding: var(--fluss-space-6); + border-radius: var(--fluss-radius-lg); + background: rgba(255, 255, 255, 0.04); + border: 1px solid rgba(255, 255, 255, 0.08); + color: rgba(219, 234, 254, 0.88); + text-decoration: none; + transition: transform var(--fluss-motion-base) var(--fluss-ease-out), + background-color var(--fluss-motion-base) var(--fluss-ease-out), + border-color var(--fluss-motion-base) var(--fluss-ease-out); +} + +.communityCard:hover { + transform: translateY(-2px); + background: rgba(255, 255, 255, 0.07); + border-color: rgba(34, 211, 238, 0.4); + color: #FFFFFF; + text-decoration: none; +} + +.communityTitle { + font-family: var(--fluss-font-display); + font-size: 1.125rem; + font-weight: 600; + color: #FFFFFF; + margin-bottom: var(--fluss-space-2); +} + +.communityArrow { + margin-top: auto; + padding-top: var(--fluss-space-4); + color: var(--fluss-cyan); + font-weight: 600; + font-size: 0.875rem; +} + +/* ========================================================================= + Final CTA band + ========================================================================= */ +.ctaBand { + position: relative; + overflow: hidden; + padding: var(--fluss-space-24) 0; + background: + radial-gradient(800px 400px at 100% 0%, rgba(34, 211, 238, 0.18), transparent 60%), + radial-gradient(600px 400px at 0% 100%, rgba(124, 58, 237, 0.15), transparent 60%), + linear-gradient(180deg, var(--fluss-blue-900) 0%, var(--fluss-blue-950) 100%); + color: #FFFFFF; + text-align: center; +} + +.ctaTitle { + font-family: var(--fluss-font-display); + font-size: clamp(2rem, 4vw + 1rem, 3.5rem); + font-weight: 700; line-height: 1.1; - text-shadow: 0 2px 10px rgba(0, 0, 0, 0.3); + letter-spacing: -0.02em; + text-wrap: balance; + color: #FFFFFF; + margin: 0 0 var(--fluss-space-4); } -.heroBanner :global(.hero__subtitle) { - font-family: 'Roboto', sans-serif; - font-size: 1.5rem; - font-weight: 400; - letter-spacing: 0.01em; - text-shadow: 0 1px 8px rgba(0, 0, 0, 0.2); - margin-top: 1rem; - opacity: 0.9; +.ctaSub { + font-size: 1.125rem; + line-height: 1.6; + color: rgba(219, 234, 254, 0.78); + max-width: 640px; + margin: 0 auto var(--fluss-space-8); + text-wrap: pretty; +} + +.ctaActions { + display: inline-flex; + flex-wrap: wrap; + gap: var(--fluss-space-3); + justify-content: center; +} + +/* ========================================================================= + Container helper (matches Infima but tunable) + ========================================================================= */ +.container { + max-width: var(--fluss-container-max); + margin: 0 auto; + padding: 0 var(--fluss-container-pad); } -.buttons { +/* ========================================================================= + Multiple Systems Tax (Before / After) + ========================================================================= */ +.taxSection { + padding: var(--fluss-space-24) 0; + background: + radial-gradient(900px 500px at 50% 0%, rgba(37, 99, 235, 0.06), transparent 60%), + var(--fluss-canvas); + border-top: 1px solid var(--fluss-ink-100); + border-bottom: 1px solid var(--fluss-ink-100); +} + +.taxGrid { + display: grid; + grid-template-columns: 1fr 60px 1fr; + gap: var(--fluss-space-6); + align-items: start; +} + +@media (max-width: 996px) { + .taxGrid { + grid-template-columns: 1fr; + gap: var(--fluss-space-12); + } +} + +.taxColumn { display: flex; + flex-direction: column; + gap: var(--fluss-space-4); + min-width: 0; +} + +.taxLabel { + display: inline-flex; align-items: center; - justify-content: center; - flex-wrap: wrap; - gap: 20px; - margin-top: 2rem; + align-self: flex-start; + gap: 8px; + padding: 6px 14px; + border-radius: var(--fluss-radius-pill); + font-family: var(--fluss-font-display); + font-size: 0.75rem; + font-weight: 700; + letter-spacing: 0.06em; + text-transform: uppercase; +} + +.taxLabelBefore { + background: rgba(244, 63, 94, 0.08); + color: #BE123C; + border: 1px solid rgba(244, 63, 94, 0.2); +} + +.taxLabelAfter { + background: linear-gradient(120deg, var(--fluss-blue-600), var(--fluss-cyan)); + color: #FFFFFF; + border: 1px solid transparent; +} + +/* Before stack */ +.taxStack { + display: flex; + flex-direction: column; + gap: var(--fluss-space-2); +} + +.taxStackItem { + position: relative; + display: grid; + grid-template-columns: 32px 1fr; + gap: var(--fluss-space-3); + align-items: flex-start; + padding: var(--fluss-space-4) var(--fluss-space-5); + border-radius: var(--fluss-radius-md); + background: var(--fluss-paper); + border: 1px solid var(--fluss-ink-100); + border-left: 3px solid #FB7185; + transition: transform var(--fluss-motion-base) var(--fluss-ease-out), + border-color var(--fluss-motion-base) var(--fluss-ease-out); +} + +.taxStackItem:hover { + transform: translateX(2px); } -.buttonWidth { - width: 200px; +.taxStackIndex { + font-family: var(--fluss-font-mono); + font-size: 0.75rem; + font-weight: 700; + color: var(--fluss-ink-300); + line-height: 1.5; + padding-top: 2px; +} + +.taxStackTitle { + font-family: var(--fluss-font-display); + font-size: 0.9375rem; + font-weight: 600; + color: var(--fluss-ink-950); + line-height: 1.3; +} + +.taxStackSub { + font-size: 0.8125rem; + line-height: 1.45; + color: var(--fluss-ink-500); + margin-top: 2px; } -.buttonWithIcon { +/* Arrow column */ +.taxArrow { display: flex; align-items: center; justify-content: center; + height: 100%; + min-height: 200px; +} + +.taxArrow svg { + width: 60px; + height: 24px; + filter: drop-shadow(0 2px 12px rgba(34, 211, 238, 0.4)); +} + +@media (max-width: 996px) { + .taxArrow { + min-height: 0; + } + .taxArrow svg { + width: 40px; + transform: rotate(90deg); + } +} + +/* After card */ +.taxAfterCard { + padding: var(--fluss-space-6); + border-radius: var(--fluss-radius-lg); + background: + radial-gradient(600px 300px at 100% 0%, rgba(34, 211, 238, 0.12), transparent 60%), + linear-gradient(180deg, var(--fluss-blue-700) 0%, var(--fluss-blue-800) 100%); + color: #FFFFFF; + box-shadow: var(--fluss-shadow-glow); + border: 1px solid rgba(255, 255, 255, 0.1); +} + +.taxAfterHeader { + margin-bottom: var(--fluss-space-5); + padding-bottom: var(--fluss-space-4); + border-bottom: 1px solid rgba(255, 255, 255, 0.12); +} + +.taxAfterTitle { + font-family: var(--fluss-font-display); + font-size: 1.5rem; + font-weight: 700; + letter-spacing: -0.018em; + color: #FFFFFF; + margin-bottom: 6px; +} + +.taxAfterSub { + font-size: 0.9375rem; + line-height: 1.55; + color: rgba(219, 234, 254, 0.85); +} + +.taxAfterList { + list-style: none; + padding: 0; + margin: 0; + display: flex; + flex-direction: column; + gap: var(--fluss-space-2); } -.buttonIcon { - margin-right: 8px; - width: 20px; - height: 20px; +.taxAfterList li { + display: flex; + align-items: flex-start; + gap: var(--fluss-space-2); + font-size: 0.9375rem; + line-height: 1.55; + color: rgba(255, 255, 255, 0.95); +} + +.taxCheck { + flex-shrink: 0; + display: inline-flex; + align-items: center; + justify-content: center; + width: 18px; + height: 18px; + margin-top: 2px; + border-radius: 50%; + background: rgba(163, 230, 53, 0.2); + color: var(--fluss-lime); + font-size: 0.75rem; + font-weight: 700; } +.taxFootnote { + margin: var(--fluss-space-2) 0 0; + font-family: var(--fluss-font-mono); + font-size: 0.75rem; + letter-spacing: 0.04em; + color: var(--fluss-ink-500); + text-align: center; +} diff --git a/website/src/pages/index.tsx b/website/src/pages/index.tsx index 0e648d04f6..37ee5d0b6a 100644 --- a/website/src/pages/index.tsx +++ b/website/src/pages/index.tsx @@ -17,145 +17,795 @@ import clsx from 'clsx'; import Link from '@docusaurus/Link'; -import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; import Layout from '@theme/Layout'; import HomepageFeatures from '@site/src/components/HomepageFeatures'; import HomepageIntroduce from '@site/src/components/HomepageIntroduce'; -import Heading from '@theme/Heading'; -import {useEffect, useState} from 'react'; +import {useEffect, useRef} from 'react'; import styles from './index.module.css'; -function HomepageHeader() { - const {siteConfig} = useDocusaurusContext(); +/** + * Toggle a body-level class while the hero is in view, so we can drive the + * navbar's transparent → solid transition entirely from CSS, without timing + * tricks or pixel-based scroll thresholds. Works on any viewport size. + */ +function useHeroVisibilityClass(ref: React.RefObject) { + useEffect(() => { + const el = ref.current; + if (!el || typeof window === 'undefined') return; + // Mark as on-hero immediately on mount so the navbar starts transparent. + document.body.classList.add('fluss-on-hero'); + + const observer = new IntersectionObserver( + ([entry]) => { + if (entry.isIntersecting) { + document.body.classList.add('fluss-on-hero'); + } else { + document.body.classList.remove('fluss-on-hero'); + } + }, + { + // Trigger when the hero has scrolled out far enough that + // ~64px (one navbar height) of it remains under the navbar. + rootMargin: '-64px 0px 0px 0px', + threshold: 0, + }, + ); + observer.observe(el); + return () => { + observer.disconnect(); + document.body.classList.remove('fluss-on-hero'); + }; + }, [ref]); +} + +const SLACK_INVITE = + 'https://join.slack.com/t/apache-fluss/shared_invite/zt-33wlna581-QAooAiCmnYboJS8D_JUcYw'; + +function HeroDiagram() { + // Inline SVG illustrating: producers → Fluss (log + PK + lakehouse) → Flink/Spark/Iceberg + // Layout uses a 620×480 grid with three columns: + // - Producers x=20 → 145 (width 125) + // - Fluss core x=215 → 450 (width 235) + // - Consumers x=475 → 615 (width 140) return ( -

-
- - {siteConfig.title} - -

{siteConfig.tagline}

-
- - Quick Start - + + Apache Fluss data flow + + Producers stream events into Apache Fluss columnar storage. Apache + Flink and SQL engines like Spark query the data with sub-second + freshness; Fluss tiers data natively to Apache Iceberg, Apache + Paimon, and Lance. + - - GitHub + + + + + + + + + + + + + + + + + + + {/* ----- Producers (left column) ----- */} + + {[80, 160, 240].map((y, i) => ( + + + + + {['CDC', 'Events', 'IoT'][i]} + + + ))} + + PRODUCERS + + + + {/* ----- Flow lines: producers → Fluss ----- */} + + + + + + + {/* ----- Fluss core (center column) ----- */} + + + + Apache Fluss + + + COLUMNAR STREAMING STORAGE + + + {/* Internal lanes */} + + {/* Append-only Log */} + + + Append-only Log + + + {/* Primary-Key Table */} + + + Primary-Key Table + + + {/* Lakehouse-Native Storage (taller, two-line) */} + + + Lakehouse-Native Storage + + + Iceberg · Paimon · Lance + + + + + {/* ----- Flow lines: Fluss → consumers ----- */} + + + + + + + {/* ----- Consumers (right column) ----- */} + + {[ + {y: 90, label: 'Apache Flink'}, + {y: 200, label: 'Apache Spark'}, + {y: 310, label: 'Iceberg · Paimon'}, + ].map((c, i) => ( + + - GitHub + + + {c.label} + + + ))} + + CONSUMERS / LAKE + + + + ); +} + +function HomepageHeader({heroRef}: {heroRef: React.RefObject}) { + return ( +
+
+
+
+ + + Apache Software Foundation · Incubating · Apache 2.0 + + +

+ Streaming Storage for{' '} + Real-Time Analytics & AI +

+ +

+ Apache Fluss (Incubating) is a streaming storage built + for real-time analytics & AI which can serve as the + real-time data layer for Lakehouse architectures. With + its columnar stream and real-time update capabilities, + Fluss integrates seamlessly with Apache Flink to enable + high-throughput, low-latency, cost-effective streaming + data warehouses tailored for real-time applications. +

+ +
+ + Get Started + + + + + + View on GitHub + + + + Read the Docs ↗ + +
+
+ +
+ +
+
+ +
+ + Apache 2.0 licensed + +
+
+
+ ); +} + +function SystemsTaxSection() { + const beforeStack = [ + { + label: 'Message broker', + sub: 'Kafka — for streaming event transport.', + }, + { + label: 'Stream processor', + sub: 'Flink or Spark — for derived features and aggregations.', + }, + { + label: 'Online store', + sub: 'Redis or DynamoDB — for sub-millisecond feature lookup.', + }, + { + label: 'Offline store', + sub: 'Iceberg or Parquet on S3 — for training and history.', + }, + { + label: 'Sync layer', + sub: 'Bespoke pipelines + freshness monitors that drift silently.', + }, + ]; + + const afterRequirements = [ + 'Event log — durable, replayable, offset-ordered.', + 'KV store — sub-millisecond point lookups on the same leader.', + 'Streaming compute substrate — leader-resident state, no Flink slot state.', + 'Cold archive — open-format tiering to Iceberg / Paimon / Lance.', + 'Vector-compatible layer — multi-modal context for ML and AI.', + 'First-class audit trail — deterministic, replayable by design.', + ]; + + return ( +
+
+
+ The multiple-systems tax +

+ Five systems, four seams, continuous engineering tax. +

+

+ A conventional real-time AI stack stitches together a message broker, + a stream processor, an online store, an offline store, and a + synchronisation layer. Every boundary is a seam where data silently + diverges. Apache Fluss collapses that stack into one substrate. +

+
+ +
+
+ + Before — fragmented stack + +
+ {beforeStack.map((s, i) => ( +
+ +
+
{s.label}
+
{s.sub}
+
+
+ ))} +
+

+ 5 systems · 4 sync boundaries · continuous engineering tax +

+
+ + + +
+ + After — unified substrate + +
+
+
Apache Fluss
+
+ One columnar streaming store that natively speaks + every requirement of a real-time AI platform. +
+
+
    + {afterRequirements.map((r, i) => ( +
  • + + {r} +
  • + ))} +
+
+

+ 1 substrate · 0 sync boundaries · single source of truth +

+
+
+
+
+ ); +} + +function CompareSection() { + const rows = [ + { + label: 'Storage model', + kafka: 'Append-only log, row-oriented', + lake: 'Columnar tables (batch-friendly)', + fluss: 'Columnar log + primary-key tables', + }, + { + label: 'Freshness', + kafka: 'Seconds (logs only)', + lake: 'Minutes to hours (commit-bound)', + fluss: 'Sub-second, end-to-end', + }, + { + label: 'Direct analytical query', + kafka: 'No (consume + transform)', + lake: 'Yes, but on cold data', + fluss: 'Yes, on live + cold data', + }, + { + label: 'Lake integration', + kafka: 'Via separate pipeline', + lake: 'Native (it is the lake)', + fluss: 'Native tiering to Iceberg & Paimon', + }, + { + label: 'State / lookup', + kafka: 'External KV store required', + lake: 'Not designed for it', + fluss: 'Built-in lookups & upserts', + }, + { + label: 'Governance', + kafka: 'Vendor / ASF (Kafka)', + lake: 'ASF (Iceberg/Paimon)', + fluss: 'Apache Software Foundation', + }, + ]; + + return ( +
+
+
+ Where Fluss fits +

+ Streams, tables, and the lake — in one storage layer. +

+

+ Kafka is great for transport. The lakehouse is great for analytics. + Apache Fluss closes the gap between them with columnar streaming + storage that is queryable in seconds and tiers natively to your lake. +

+
+ +
+ + + + + + + + + + + {rows.map((r) => ( + + + + + + + ))} + +
KafkaIceberg / Paimon aloneApache Fluss
{r.label}{r.kafka}{r.lake}{r.fluss}
+
+
+
+ ); +} + +function FlinkSection() { + return ( +
+
+
+
+ Apache Flink integration +

Flink's natural storage layer.

+

+ Apache Fluss is designed to be a first-class storage layer for + Apache Flink — as a source, sink, lookup-join target, and + CDC-friendly substrate. Define a catalog, point at Fluss, and + you have streaming and analytical access on the same tables. +

+ + Run the Flink quickstart + + +
+ Flink 1.18 + Flink 1.19 + Flink 1.20 + Flink 2.x +
+
+ +
+ +
+{`-- Register Apache Fluss as a Flink catalog
+`}CREATE CATALOG{` fluss `}WITH{` (
+  `}'type'{`             = `}'fluss'{`,
+  `}'bootstrap.servers'{` = `}'fluss-server:9123'{`
+);
+
+`}USE CATALOG{` fluss;
+
+`}-- Stream + query the same table, with sub-second freshness{`
+`}SELECT{` user_id, `}SUM{`(amount)
+`}FROM{`   orders
+`}GROUP BY{` user_id;`}
+                        
+
+
+
+
+ ); +} + +function FreshnessSection() { + return ( +
+
+
+ Lakehouse freshness +

+ Close the lakehouse freshness gap. +

+

+ Traditional lake pipelines trade freshness for scale. Apache Fluss + keeps lake-grade economics while moving end-to-end latency from + hours to seconds. +

+
+ +
+
+
Hours
+
Traditional batch
+
+ Periodic ETL into the lake. Dashboards lag by hours. +
+
+ +
+
Minutes
+
Micro-batch / streaming ETL
+
+ Lower latency, but at the cost of duplicated pipelines and + small-file overhead. +
+
+ +
+
Seconds
+
Apache Fluss + Iceberg / Paimon
+
+ One storage layer for hot and cold. Live queries on streaming + data, with native lake tiering. +
+
+
+
+
+ ); +} + +function QuickstartSection() { + return ( +
+
+
+ Developer quickstart +

Run Apache Fluss in minutes.

+

+ Stand up a local cluster, register a catalog, and start streaming. + The full quickstart walks you through Flink, Spark, and the Java + client. +

+
+ +
+ +
+{`# 1. Start a local cluster
+`}./bin/local-cluster.sh{` start
+
+`}# 2. Open the Flink SQL client and register Fluss{`
+`}./bin/sql-client.sh{`
+
+`}CREATE CATALOG{` fluss `}WITH{` (
+  `}'type'{` = `}'fluss'{`,
+  `}'bootstrap.servers'{` = `}'localhost:9123'{`
+);
+
+`}# 3. You're streaming and querying — on the same table.
+                    
+
+ +
+ + Open the full quickstart + +
+
+
+ ); +} + +function CommunitySection() { + return ( +
+
+
+ Community +

+ Built in the open, governed by the ASF. +

+

+ Apache Fluss is developed openly by a global community of + contributors. Join the discussion, file an issue, or send a patch. +

+
+ +
+
+
Apache 2.0
+
Open-source license
+
+
+
ASF
+
Apache Software Foundation governance
+
+
+
Flink-first
+
Streaming integration
+
+
+
Lake-native
+
Iceberg & Paimon tiering
+
+
- +
+ +
GitHub
+
Source code, issues, and pull requests.
+
Open repository →
+ + + +
Slack
+
Real-time chat with users and committers.
+
Join the workspace →
+ + + +
Contribute
+
Welcome guide, mailing lists, and how to send your first patch.
+
Get started →
+ +
+
+
+ ); +} + +function FinalCta() { + return ( +
+
+

+ Start streaming. Start querying. Same storage. +

+

+ Apache Fluss is free, open-source, and Apache 2.0 licensed. + Drop it into your data platform today. +

+
+ + Get Started + + + - Slack + Star on GitHub
-
+ ); } export default function Home(): JSX.Element { - const {siteConfig} = useDocusaurusContext(); - const [isScrolled, setIsScrolled] = useState(false); - - useEffect(() => { - const handleScroll = () => { - // Change navbar style when scrolled past 500 (past most of the hero section) - setIsScrolled(window.scrollY > 500); - }; - - window.addEventListener('scroll', handleScroll); - return () => window.removeEventListener('scroll', handleScroll); - }, []); + const heroRef = useRef(null); + useHeroVisibilityClass(heroRef); return ( - <> - {/* Inline styles for homepage navbar with scroll-based transitions */} -