From d2ce4467bbd4a489922991debfd293f761de5c84 Mon Sep 17 00:00:00 2001 From: Krishna Jain Date: Mon, 12 Oct 2026 17:44:46 +0530 Subject: [PATCH 01/28] feat(olake-go): rebuild the OLake Go page from the new Figma design Replace the hero, architecture diagram, cloud problem section, feature selector, benchmark card and closing CTA with the new design: - Hero on a strata-line background with Github / Visit Docs buttons, a scrolling "Available Sources" strip and the Jobs dashboard mock that fades into the page. - "Problem Statement" frame with four illustrated problem cards. - Features card with pill tabs and the blue CDC card holding the job editor mock. - Dark full-bleed benchmark section: latest-release strip, comparison table (full load / CDC, five sources), "How OLake Go does this" notes and a link bar to the detailed benchmarks. - Blue "Get in touch" card and a restyled FAQ accordion (same questions, so the FAQPage JSON-LD still matches). Product mocks are drawn at design size and scaled to fit with CSS only. Assets are exported from Figma into static/img/landing/go. Geist Mono is self-hosted for the benchmark notes. Text greys that fail WCAG AA in the design use the existing --olake-muted tokens. --- src/components/landing/go/FeatureVisual.tsx | 143 ------ src/components/landing/go/GoArchitecture.tsx | 152 ------- src/components/landing/go/GoBenchmark.tsx | 425 ++++++++++-------- src/components/landing/go/GoCta.tsx | 50 ++- src/components/landing/go/GoDashboardMock.tsx | 230 ++++++++++ src/components/landing/go/GoFaq.tsx | 69 +-- src/components/landing/go/GoFeatureMock.tsx | 215 +++++++++ src/components/landing/go/GoFeatures.tsx | 162 ++++--- src/components/landing/go/GoHero.tsx | 256 +++++++++-- src/components/landing/go/GoPage.tsx | 28 +- src/components/landing/go/GoProblem.tsx | 197 +++++--- src/components/landing/go/go.css | 254 +++-------- src/components/landing/go/mock.tsx | 60 +++ src/components/landing/go/useDiagramLinks.ts | 82 ---- src/components/landing/go/useFeatureCycle.ts | 59 --- src/components/landing/go/useGoBenchmark.ts | 23 +- src/css/fonts.css | 27 +- src/data/landing/go/architecture.ts | 30 -- src/data/landing/go/benchmark.ts | 74 ++- src/data/landing/go/cta.ts | 8 +- src/data/landing/go/faq.ts | 6 +- src/data/landing/go/features.ts | 40 +- src/data/landing/go/hero.ts | 28 +- src/data/landing/go/problem.ts | 48 +- static/fonts/LICENSE-geist-mono.txt | 93 ++++ .../geist-mono-latin-ext-wght-normal.woff2 | Bin 0 -> 14696 bytes .../fonts/geist-mono-latin-wght-normal.woff2 | Bin 0 -> 23128 bytes .../landing/go/benchmark-arrow-square-out.svg | 5 + static/img/landing/go/cta-dot-wave.svg | 1 + .../img/landing/go/dashboard-git-commit.svg | 10 + .../go/dashboard-highlight-actions.svg | 3 + .../landing/go/dashboard-highlight-check.svg | 5 + .../go/dashboard-highlight-destination.svg | 19 + .../landing/go/dashboard-highlight-source.svg | 5 + static/img/landing/go/dashboard-plus.svg | 5 + .../img/landing/go/dashboard-row-actions.svg | 5 + static/img/landing/go/dashboard-row-check.svg | 5 + .../landing/go/dashboard-row-destination.svg | 19 + .../img/landing/go/dashboard-row-source.svg | 5 + .../img/landing/go/dashboard-sidebar-help.svg | 5 + .../img/landing/go/dashboard-sidebar-jobs.svg | 5 + .../landing/go/dashboard-sidebar-settings.svg | 5 + .../landing/go/dashboard-sidebar-sparkle.svg | 3 + .../landing/go/dashboard-sidebar-toggle.svg | 5 + static/img/landing/go/faq-arrow-closed.svg | 5 + static/img/landing/go/faq-arrow-open.svg | 5 + .../img/landing/go/feature-cdc-background.png | Bin 0 -> 211575 bytes static/img/landing/go/feature-glow.svg | 12 + .../img/landing/go/feature-mock-checkbox.svg | 6 + static/img/landing/go/feature-mock-pencil.svg | 5 + static/img/landing/go/feature-mock-search.svg | 10 + .../landing/go/feature-mock-sidebar-help.svg | 5 + .../landing/go/feature-mock-sidebar-jobs.svg | 5 + .../go/feature-mock-sidebar-settings.svg | 5 + .../go/feature-mock-sidebar-sparkle.svg | 3 + .../go/feature-mock-sidebar-toggle.svg | 5 + .../landing/go/feature-mock-tab-config.svg | 5 + .../go/feature-mock-tab-partitioning.svg | 5 + .../landing/go/feature-mock-tab-schema.svg | 10 + static/img/landing/go/hero-glow.svg | 12 + static/img/landing/go/hero-strata-lines.svg | 29 ++ static/img/landing/go/icon-github.svg | 10 + .../img/landing/go/problem-cdc-pipeline.svg | 7 + static/img/landing/go/problem-cdc-stale.svg | 6 + .../landing/go/problem-legacy-etl-costs.svg | 16 + .../img/landing/go/problem-schema-drift.svg | 13 + static/img/landing/go/problem-slow-syncs.svg | 11 + static/img/landing/go/source-db2.png | Bin 0 -> 4042 bytes static/img/landing/go/source-kafka.png | Bin 0 -> 3242 bytes static/img/landing/go/source-mongodb.svg | 11 + static/img/landing/go/source-mssql.png | Bin 0 -> 6216 bytes static/img/landing/go/source-mysql.svg | 10 + static/img/landing/go/source-oracle.png | Bin 0 -> 5138 bytes static/img/landing/go/source-postgres.svg | 8 + static/img/landing/go/source-s3.svg | 19 + static/img/landing/go/sources-live-dot.svg | 17 + static/img/landing/shared/parquet-icon.webp | Bin 3546 -> 0 bytes 77 files changed, 1922 insertions(+), 1202 deletions(-) delete mode 100644 src/components/landing/go/FeatureVisual.tsx delete mode 100644 src/components/landing/go/GoArchitecture.tsx create mode 100644 src/components/landing/go/GoDashboardMock.tsx create mode 100644 src/components/landing/go/GoFeatureMock.tsx create mode 100644 src/components/landing/go/mock.tsx delete mode 100644 src/components/landing/go/useDiagramLinks.ts delete mode 100644 src/components/landing/go/useFeatureCycle.ts delete mode 100644 src/data/landing/go/architecture.ts create mode 100644 static/fonts/LICENSE-geist-mono.txt create mode 100644 static/fonts/geist-mono-latin-ext-wght-normal.woff2 create mode 100644 static/fonts/geist-mono-latin-wght-normal.woff2 create mode 100644 static/img/landing/go/benchmark-arrow-square-out.svg create mode 100644 static/img/landing/go/cta-dot-wave.svg create mode 100644 static/img/landing/go/dashboard-git-commit.svg create mode 100644 static/img/landing/go/dashboard-highlight-actions.svg create mode 100644 static/img/landing/go/dashboard-highlight-check.svg create mode 100644 static/img/landing/go/dashboard-highlight-destination.svg create mode 100644 static/img/landing/go/dashboard-highlight-source.svg create mode 100644 static/img/landing/go/dashboard-plus.svg create mode 100644 static/img/landing/go/dashboard-row-actions.svg create mode 100644 static/img/landing/go/dashboard-row-check.svg create mode 100644 static/img/landing/go/dashboard-row-destination.svg create mode 100644 static/img/landing/go/dashboard-row-source.svg create mode 100644 static/img/landing/go/dashboard-sidebar-help.svg create mode 100644 static/img/landing/go/dashboard-sidebar-jobs.svg create mode 100644 static/img/landing/go/dashboard-sidebar-settings.svg create mode 100644 static/img/landing/go/dashboard-sidebar-sparkle.svg create mode 100644 static/img/landing/go/dashboard-sidebar-toggle.svg create mode 100644 static/img/landing/go/faq-arrow-closed.svg create mode 100644 static/img/landing/go/faq-arrow-open.svg create mode 100644 static/img/landing/go/feature-cdc-background.png create mode 100644 static/img/landing/go/feature-glow.svg create mode 100644 static/img/landing/go/feature-mock-checkbox.svg create mode 100644 static/img/landing/go/feature-mock-pencil.svg create mode 100644 static/img/landing/go/feature-mock-search.svg create mode 100644 static/img/landing/go/feature-mock-sidebar-help.svg create mode 100644 static/img/landing/go/feature-mock-sidebar-jobs.svg create mode 100644 static/img/landing/go/feature-mock-sidebar-settings.svg create mode 100644 static/img/landing/go/feature-mock-sidebar-sparkle.svg create mode 100644 static/img/landing/go/feature-mock-sidebar-toggle.svg create mode 100644 static/img/landing/go/feature-mock-tab-config.svg create mode 100644 static/img/landing/go/feature-mock-tab-partitioning.svg create mode 100644 static/img/landing/go/feature-mock-tab-schema.svg create mode 100644 static/img/landing/go/hero-glow.svg create mode 100644 static/img/landing/go/hero-strata-lines.svg create mode 100644 static/img/landing/go/icon-github.svg create mode 100644 static/img/landing/go/problem-cdc-pipeline.svg create mode 100644 static/img/landing/go/problem-cdc-stale.svg create mode 100644 static/img/landing/go/problem-legacy-etl-costs.svg create mode 100644 static/img/landing/go/problem-schema-drift.svg create mode 100644 static/img/landing/go/problem-slow-syncs.svg create mode 100644 static/img/landing/go/source-db2.png create mode 100644 static/img/landing/go/source-kafka.png create mode 100644 static/img/landing/go/source-mongodb.svg create mode 100644 static/img/landing/go/source-mssql.png create mode 100644 static/img/landing/go/source-mysql.svg create mode 100644 static/img/landing/go/source-oracle.png create mode 100644 static/img/landing/go/source-postgres.svg create mode 100644 static/img/landing/go/source-s3.svg create mode 100644 static/img/landing/go/sources-live-dot.svg delete mode 100644 static/img/landing/shared/parquet-icon.webp diff --git a/src/components/landing/go/FeatureVisual.tsx b/src/components/landing/go/FeatureVisual.tsx deleted file mode 100644 index f37b5bba..00000000 --- a/src/components/landing/go/FeatureVisual.tsx +++ /dev/null @@ -1,143 +0,0 @@ -import React from 'react' -import { cn } from '@site/src/lib/utils' -import type { GoFeatureArt } from '@site/src/data/landing/go/features' - -/** - * The small product mock-ups inside the feature card, drawn in the same grey panel + white sheet - * the home page uses for its feature cards. They are decorative (aria-hidden); the card's heading - * and body say the same thing in words. - */ - -const Panel = ({ children }: { children: React.ReactNode }) => ( - -) - -const Caption = ({ children }: { children: React.ReactNode }) => ( -

{children}

-) - -const Bar = ({ className }: { className?: string }) => ( - -) - -const Tiered = () => ( - -
-
- Full -
- - - -
-

one-time load

-
-
- Incremental -
- - - -
-

on schedule

-
-
- CDC -
- - -
-

real-time

-
-
-
-) - -const Row = ({ className }: { className?: string }) => ( - -) - -const Decay = () => ( - -
-
- Source table -
- - - -
-
- -
- Iceberg table -
- - -
- - - + NEW - -
-
-
-
-
-) - -const Chunk = () => ( - -
-
-
- {['chunk 1', 'chunk 2', 'chunk 3', 'chunk 4'].map((label) => ( -
- {label} -
- ))} -
-

read in parallel

- -) - -const Resume = () => ( - -
- - - - ✓ checkpoint - - -
-
- interrupted - resumes here → -
-
-) - -const VISUALS: Record React.JSX.Element> = { - tiered: Tiered, - decay: Decay, - chunk: Chunk, - resume: Resume -} - -export default function FeatureVisual({ art }: { art: GoFeatureArt }) { - const Visual = VISUALS[art] - return -} diff --git a/src/components/landing/go/GoArchitecture.tsx b/src/components/landing/go/GoArchitecture.tsx deleted file mode 100644 index 8c1aac54..00000000 --- a/src/components/landing/go/GoArchitecture.tsx +++ /dev/null @@ -1,152 +0,0 @@ -import React, { useRef } from 'react' -import Section from '../ui/Section' -import Card from '../ui/Card' -import { cn } from '@site/src/lib/utils' -import { - GO_ARCHITECTURE_LABELS, - GO_DESTINATIONS, - GO_SOURCES, - GO_SYNC_MODES -} from '@site/src/data/landing/go/architecture' -import { useDiagramLinks } from './useDiagramLinks' - -const Label = ({ children }: { children: React.ReactNode }) => ( -

{children}

-) - -const DownArrow = () => ( - -) - -/** Sources, the OLake Go node and the two destinations. The lines are drawn after mount (see useDiagramLinks). */ -export default function GoArchitecture() { - const containerRef = useRef(null) - const nodeRef = useRef(null) - const diagram = useDiagramLinks(containerRef, nodeRef) - - return ( -
- -
- {diagram && ( - - )} - -
- -
    - {GO_SOURCES.map((name) => ( -
  • -
  • - ))} -
-
- - - -
-
- {GO_ARCHITECTURE_LABELS.node} -
-

{GO_ARCHITECTURE_LABELS.node}

-
    - {GO_SYNC_MODES.map((mode, i) => ( -
  • - {mode} -
  • - ))} -
-
- - - -
- -
    - {GO_DESTINATIONS.map((dest) => ( -
  • - {dest.icon.alt} - {dest.label} -
  • - ))} -
-
-
-
-
- ) -} diff --git a/src/components/landing/go/GoBenchmark.tsx b/src/components/landing/go/GoBenchmark.tsx index 5affa4e4..81db2780 100644 --- a/src/components/landing/go/GoBenchmark.tsx +++ b/src/components/landing/go/GoBenchmark.tsx @@ -1,259 +1,298 @@ -import React, { useState } from 'react' +import React from 'react' import Link from '@docusaurus/Link' -import SectionHeading from '../ui/SectionHeading' +import useDocusaurusContext from '@docusaurus/useDocusaurusContext' import { cn } from '@site/src/lib/utils' import { - GO_BENCHMARK_INFO, + GO_BENCHMARK_HOW, GO_BENCHMARK_INTRO, GO_BENCHMARK_LINK, GO_BENCHMARK_MODES, - GO_BENCHMARK_SUMMARY + GO_BENCHMARK_SUMMARY, + GO_RELEASE_STRIP } from '@site/src/data/landing/go/benchmark' import { useGoBenchmark } from './useGoBenchmark' -const ExternalMark = () => ( - -) - -const Chevron = ({ open }: { open: boolean }) => ( - -) - const FOCUS = - 'focus-visible:rounded-[4px] focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-olake-blue-on-dark' + 'focus-visible:rounded-[4px] focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-olake-blue-on-dark' -const BenchLink = ({ className }: { className?: string }) => ( - void + children: React.ReactNode +}) => ( + ) -/** The "OLake Advantage" block: the dark benchmark card with mode and source switching, and the "how" disclosure. */ +/** "7.5 hours (failed sync)" reads as the figure with its remark on a smaller second line. */ +const Figure = ({ value }: { value: string }) => { + const at = value.indexOf(' (') + if (at < 0) return <>{value} + return ( + <> + {value.slice(0, at)} + {value.slice(at + 1)} + + ) +} + +/** Column widths of the design's table (198, 172, 147, 148, 147 and 145 of its 957px); it leaves 45px free on the right. */ +const COLUMNS = ['20.69%', '17.97%', '15.36%', '15.47%', '15.36%', '15.15%'] +/** Rules on the dark section. It goes first in cn(): a later border-0 would drop the side classes. */ +const RULE = 'border-0 border-solid border-olake-line-dark' + +/** + * "Benchmarks": the dark full-bleed section with the latest-release strip, the comparison table + * (switchable between full load and CDC and across sources), how OLake Go gets there, and a bar + * linking to the full benchmarks. + */ export default function GoBenchmark() { const t = useGoBenchmark() - const [infoOpen, setInfoOpen] = useState(false) + const { siteConfig } = useDocusaurusContext() + const releaseLabel = siteConfig.customFields?.latestOlakeReleaseLabel as string | undefined + const releasePath = + (siteConfig.customFields?.latestOlakeReleasePath as string | undefined) || + '/docs/release/ingestion' return ( -
-
-
- - part.strong ? ( - +
+
+ {releaseLabel && ( +
+ {GO_RELEASE_STRIP.eyebrow} + + {GO_RELEASE_STRIP.lead} {releaseLabel} + + + {GO_RELEASE_STRIP.cta} + +
+ )} + +
+

+ {GO_BENCHMARK_INTRO.eyebrow} +

+

+ {GO_BENCHMARK_INTRO.title} +

+

+ {GO_BENCHMARK_SUMMARY.map((part, i) => + part.muted ? ( + {part.text} - + ) : ( {part.text} ) )} - className='[&_p:last-child]:max-w-[640px]' - /> +

-
-
-
-
+
+
+

+ {t.caption[0]} +
+ {t.caption[1]} +

+
+
{GO_BENCHMARK_MODES.map((m) => ( - + + ))} +
+
+ {t.connectors.map((c) => ( + t.setActiveConnector(c.id)} + > + {c.name} + ))}
- -
- -
- {t.connectors.map((c) => ( - - ))}
+
- {t.hasData ? ( -
- - - - -
- Metrics - - - {t.olakeLabel} - - - {t.olakeSub} - + {t.hasData ? ( +
+ + + {COLUMNS.map((width, i) => ( + + ))} + + + + + + {t.competitors.map((name, i) => ( + - {t.competitors.map((name) => ( - - ))} - - - - {t.rows.map((row) => ( + ))} + + + + {t.rows.map((row, r) => { + const cell = 'px-[8px] pt-[29px] pb-0 align-top text-[14px] leading-[1.3]' + return ( - - {row.competitors.map((value, i) => ( ))} - ))} - -
+ Metrics + + + OLake + Go + + + {t.olakeSub} + + + {name} - {name} -
- - {row.label} - + + {row.label} {row.note && ( - + {row.note} )} - {row.olake} + +
- {value} +
-
- ) : ( -
- -

- {t.sourceName} benchmarks coming soon -

-

- We're running head-to-head {t.sourceName} ingestion tests now. Check back - shortly for the full comparison. -

-
- )} - -
- + ) + })} + +
-
- -
- + ) : ( + )} +
+ +
+

+ {GO_BENCHMARK_HOW.title} +

+
+ {GO_BENCHMARK_HOW.items.map((item) => ( +

+ {item.term} / {item.body} +

+ ))}
+ + + {GO_BENCHMARK_LINK.label} + +
) diff --git a/src/components/landing/go/GoCta.tsx b/src/components/landing/go/GoCta.tsx index bac7c04c..0d82ea3c 100644 --- a/src/components/landing/go/GoCta.tsx +++ b/src/components/landing/go/GoCta.tsx @@ -1,34 +1,44 @@ import React from 'react' import Button from '../ui/Button' -import Section from '../ui/Section' import { GO_CTA } from '@site/src/data/landing/go/cta' -/** - * Closing call-to-action. It follows the shared CtaBanner (outlined card, copy on top, one primary - * button) but crops its 16:9 iceberg picture into a band, which CtaBanner's image slot cannot do. - */ +const BUTTON = 'h-[36px] rounded-[8px] px-[10px] text-[16px] leading-[1.3]' + +/** Closing call to action: the blue card with a field of dots rising behind the buttons. */ export default function GoCta() { return ( -
-
-
-

- {GO_CTA.title} -

- -
+
+
+ {/* The dot wave, upside down as in the design, its top 267px showing under the buttons. */} +

{GO_CTA.eyebrow}

+

+ {GO_CTA.title} +

+
+ + +
-
+
) } diff --git a/src/components/landing/go/GoDashboardMock.tsx b/src/components/landing/go/GoDashboardMock.tsx new file mode 100644 index 00000000..d20964e3 --- /dev/null +++ b/src/components/landing/go/GoDashboardMock.tsx @@ -0,0 +1,230 @@ +import React from 'react' +import { cn } from '@site/src/lib/utils' +import { MockFit, MockIcon as Icon, STROKE } from './mock' + +/** + * The OLake Go "Jobs" screen under the hero, drawn at its design size (1018 × 471) and scaled to + * fit. It is a picture of the product, so it is hidden from assistive tech; its lower half fades + * into the page. + */ + +const TABS = ['Active jobs', 'Inactive jobs', 'Saved jobs', 'Failed jobs'] + +const COLUMNS = [ + { label: 'Actions', className: 'w-[92.224px] text-center' }, + { label: 'Job Name', className: 'w-[139.131px]' }, + { label: 'Source', className: 'flex-1' }, + { label: 'Destination', className: 'w-[136.746px]' }, + { label: 'Last sync', className: 'flex-1' }, + { label: 'Last sync status', className: 'flex-1' } +] + +/** The rows under the highlighted one; the last two sit in the fade. */ +const ROWS = [ + { + name: 'Table_name_test_3', + source: 'MySQL', + destination: 'Dropbox', + lastSync: '2 days ago', + status: 'Failed', + opacity: 'opacity-70' + }, + { + name: 'Table_name_test_4', + source: 'PostgreSQL', + destination: 'Google Drive', + lastSync: '1 day ago', + status: 'Success', + opacity: 'opacity-70' + }, + { + name: 'Table_name_test_5', + source: 'MongoDB', + destination: 'OneDrive', + lastSync: '3 days ago', + status: 'Pending', + opacity: 'opacity-40' + }, + { + name: 'Table_name_test_6', + source: 'SQLite', + destination: 'Box', + lastSync: '4 hours ago', + status: 'Failed', + opacity: 'opacity-40' + } +] + +const ROW_TEXT = 'whitespace-nowrap text-[11.782px] leading-[20.197px]' + +export default function GoDashboardMock() { + return ( + +
+ {/* Page header */} +
+
+
+ +

Jobs

+
+

+ A list of all your jobs stacked at one place for you to see +

+
+ + + + Create job + + +
+ + {/* Tabs */} +
+ {TABS.map((tab, i) => ( + + {tab} + + ))} +
+ + {/* Jobs table */} +
+
+
+ {COLUMNS.map((col) => ( + + {col.label} + + ))} +
+
+ + {ROWS.map((row, i) => ( +
+ + + {row.name} + + + + {row.source} + + + + {row.destination} + + + {row.lastSync} + + + + + {row.status} + + + +
+ ))} +
+ + {/* The highlighted job, lifted over the table */} +
+ + + Table_name_test_1 + + + + + MongoDB + + + + + + Amazon S3 + + + + 21 hours ago + + + + + Success + + +
+ + {/* Navigation rail */} +
+
+ +
+ + + + + + + + +
+
+
+
+
+ ) +} diff --git a/src/components/landing/go/GoFaq.tsx b/src/components/landing/go/GoFaq.tsx index f36408c9..7065a6cc 100644 --- a/src/components/landing/go/GoFaq.tsx +++ b/src/components/landing/go/GoFaq.tsx @@ -1,38 +1,34 @@ import React, { useState } from 'react' -import Section from '../ui/Section' -import SectionHeading from '../ui/SectionHeading' import { cn } from '@site/src/lib/utils' -import { GO_FAQS, GO_FAQ_TITLE } from '@site/src/data/landing/go/faq' - -const Plus = ({ open }: { open: boolean }) => ( - -) +import { GO_FAQS, GO_FAQ_EYEBROW, GO_FAQ_TITLE } from '@site/src/data/landing/go/faq' /** - * Accordion, one answer open at a time. Answers stay in the DOM (hidden) so the text the FAQPage - * JSON-LD describes is always on the page. + * Accordion, one answer open at a time (the first one to start with). Answers stay in the DOM + * (hidden) so the text the FAQPage JSON-LD describes is always on the page. */ export default function GoFaq() { - const [open, setOpen] = useState(null) + const [open, setOpen] = useState(0) return ( -
-
- -
    +
    +
    +
    +

    + {GO_FAQ_EYEBROW} +

    +

    + {GO_FAQ_TITLE} +

    +
    + +
      {GO_FAQS.map((faq, i) => { const isOpen = open === i return ( -
    • +
    • @@ -62,6 +73,6 @@ export default function GoFaq() { })}
    -
    +
) } diff --git a/src/components/landing/go/GoFeatureMock.tsx b/src/components/landing/go/GoFeatureMock.tsx new file mode 100644 index 00000000..a5247fb3 --- /dev/null +++ b/src/components/landing/go/GoFeatureMock.tsx @@ -0,0 +1,215 @@ +import React from 'react' +import { cn } from '@site/src/lib/utils' +import { MockFit, MockIcon as Icon, STROKE } from './mock' + +/** + * The job editor inside the blue feature card: the stream list, the selected stream's settings and + * the "Sync mode" picker floating over them. Drawn at its design size (514 × 438) and scaled to fit; + * a picture of the product, hidden from assistive tech. + */ + +/** Stream rows under the selected one, with their heights in the design (they alternate 25 and 26px). */ +const STREAMS: [string, number][] = [ + ['_airbyte_destination_state_airbyte_destinati...', 25], + ['public_raw_stream', 26], + ['private_encrypted_stream', 25], + ['real_time_data_feed', 25], + ['archived_data_storage', 26], + ['cloud_backup_service', 26], + ['real_time_analysis', 26], + ['user_access_management', 26], + ['data_encryption_protocol', 26], + ['version_control_system', 26], + ['compliance_tracking_tool', 26], + ['automated_report_generation', 26] +] + +const SEGMENTS = [ + { label: 'Config', icon: 'feature-mock-tab-config' }, + { label: 'Schema', icon: 'feature-mock-tab-schema' }, + { label: 'Partitioning', icon: 'feature-mock-tab-partitioning' } +] + +/** The design's radio: a 9.2px circle, its label at the size the design renders it (8.07px). */ +const Radio = ({ label, checked }: { label: string; checked?: boolean }) => ( + + + + {checked && ( + + )} + + + + {label} + + +) + +export default function GoFeatureMock({ className }: { className?: string }) { + return ( + + {/* The editor panel */} +
+ + + {/* Streams, dimmed behind the selected one */} +
+ {STREAMS.map(([name, height]) => ( +
+ + + {name} + +
+ ))} +
+ + + + {/* Search and destination */} +
+
+
+ + Search streams + +
+
+ +
+
+
+ + Postgres_Retail / Sales + + + DestinationDB + + +
+
+ + {/* The selected stream */} +
+ + + + + + Payments + + +
+ + {/* The stream's settings */} +
+

Payments

+
+ {SEGMENTS.map((segment, i) => ( + + + + {segment.label} + + + ))} +
+
+ +
+ + Full Load Filter + + + + +
+ + + + Destination Table: W87erT_01992ij + + + + {/* Navigation rail */} +
+
+ +
+ + + + + + + + +
+
+
+
+ + {/* The "Sync mode" picker */} +
+

+ Sync mode +

+ +
+
+ + +
+
+ + +
+
+
+
+ ) +} diff --git a/src/components/landing/go/GoFeatures.tsx b/src/components/landing/go/GoFeatures.tsx index efaa4fa7..f5aee709 100644 --- a/src/components/landing/go/GoFeatures.tsx +++ b/src/components/landing/go/GoFeatures.tsx @@ -1,63 +1,52 @@ -import React, { useEffect, useRef, useState } from 'react' -import { PiArrowClockwise, PiChartBar, PiColumns, PiTable } from 'react-icons/pi' -import Section from '../ui/Section' -import SectionHeading from '../ui/SectionHeading' -import Card from '../ui/Card' +import React, { useRef, useState } from 'react' import { cn } from '@site/src/lib/utils' -import { - GO_FEATURES, - GO_FEATURES_INTRO, - type GoFeatureArt -} from '@site/src/data/landing/go/features' -import FeatureVisual from './FeatureVisual' -import { useFeatureCycle } from './useFeatureCycle' +import { GO_FEATURES, GO_FEATURES_INTRO } from '@site/src/data/landing/go/features' +import GoFeatureMock from './GoFeatureMock' -const ICONS: Record> = { - tiered: PiChartBar, - decay: PiTable, - chunk: PiColumns, - resume: PiArrowClockwise -} +const IMG = '/img/landing/go' -/** The feature selector: four tabs on the left, the active capability's card on the right. */ +/** + * "Features": a pill tab strip over the blue feature card. The card keeps the job-editor picture + * for every tab; its heading and copy follow the selected capability. + */ export default function GoFeatures() { - const { ref, active, progress, select } = useFeatureCycle(GO_FEATURES.length) + const [active, setActive] = useState(0) const tabRefs = useRef<(HTMLButtonElement | null)[]>([]) - // The card slides in when the feature changes, but not on the first paint. - const [changed, setChanged] = useState(false) - const first = useRef(true) - useEffect(() => { - if (first.current) { - first.current = false - return - } - setChanged(true) - }, [active]) + + const select = (index: number) => { + setActive(index) + tabRefs.current[index]?.focus() + } const onKeyDown = (event: React.KeyboardEvent, index: number) => { const last = GO_FEATURES.length - 1 - let next = index - if (event.key === 'ArrowDown' || event.key === 'ArrowRight') next = index === last ? 0 : index + 1 - else if (event.key === 'ArrowUp' || event.key === 'ArrowLeft') next = index === 0 ? last : index - 1 - else if (event.key === 'Home') next = 0 - else if (event.key === 'End') next = last + if (event.key === 'ArrowRight') select(index === last ? 0 : index + 1) + else if (event.key === 'ArrowLeft') select(index === 0 ? last : index - 1) + else if (event.key === 'Home') select(0) + else if (event.key === 'End') select(last) else return event.preventDefault() - select(next) - tabRefs.current[next]?.focus() } const feature = GO_FEATURES[active] - const Icon = ICONS[feature.art] return ( -
- -
-
+
+
+
+

+ {GO_FEATURES_INTRO.eyebrow} +

+

+ {GO_FEATURES_INTRO.title} +

+
+ +
{GO_FEATURES.map((item, i) => { const selected = i === active return ( @@ -72,60 +61,61 @@ export default function GoFeatures() { aria-selected={selected} aria-controls='go-feature-panel' tabIndex={selected ? 0 : -1} - onClick={() => select(i)} + onClick={() => setActive(i)} onKeyDown={(event) => onKeyDown(event, i)} className={cn( - 'go-tab group cursor-pointer border-0 bg-transparent p-0 text-left', - 'rounded-[8px] focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-olake-blue' + 'shrink-0 cursor-pointer rounded-[16px] border-0 px-[18px] py-[4px] text-[16px] leading-[1.3] whitespace-nowrap transition-colors', + 'focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-olake-blue', + selected + ? 'bg-[#eeeeee] text-[rgba(0,0,0,0.88)]' + : 'bg-transparent text-olake-muted hover:text-olake-ink' )} > - - {item.title} - -
- +
+ {/* Dithered blue field, anchored to the top right, and the blue glow at the bottom left */} + + +
- -
- -
-
-

- {feature.title} -

-

- {feature.body} -

-
+

+ {feature.title} +

+

{feature.body}

- + + +
-
+
) } diff --git a/src/components/landing/go/GoHero.tsx b/src/components/landing/go/GoHero.tsx index 35e2edeb..0c27eeb5 100644 --- a/src/components/landing/go/GoHero.tsx +++ b/src/components/landing/go/GoHero.tsx @@ -1,34 +1,238 @@ import React from 'react' import Button from '../ui/Button' -import { GO_HERO } from '@site/src/data/landing/go/hero' +import { cn } from '@site/src/lib/utils' +import { + GO_HERO, + GO_SOURCES, + GO_SOURCES_LABEL, + type GoSourceId +} from '@site/src/data/landing/go/hero' +import GoDashboardMock from './GoDashboardMock' -/** Hero of the OLake Go page. Its wrapper (`.lakeside-hero-bg`) lives in LakesidePage. */ +const IMG = '/img/landing/go' + +/** Each source's logo as the design places it: some are SVGs at their own size, the rest are cropped PNGs. */ +const LOGOS: Record = { + kafka: ( + + + + ), + postgres: ( + + + + ), + mysql: ( + + ), + mongodb: ( + + ), + s3: ( + + ), + mssql: ( + + + + ), + db2: ( + + + + ), + oracle: ( + + + + ) +} + +const SourceList = ({ hidden }: { hidden?: boolean }) => ( +
    + {GO_SOURCES.map((source) => ( +
  • + {LOGOS[source.id]} + + {source.name} + +
  • + ))} +
+) + +/** + * "Available Sources": a tab-shaped strip whose logos scroll past the label (paused on hover and for + * reduced motion). From md up it follows the design, the label pinned left over a fade; below md the + * label sits above a full-width row. + */ +function SourcesStrip() { + return ( +
+

+ + {GO_SOURCES_LABEL} +

+
+ {/* The strip's grey behind the logos lets the DB2 mark multiply onto it, as in the design. */} +
+ +
+
+
+ ) +} + +const BUTTON = 'h-[36px] px-[10px] text-[16px] leading-[1.3]' + +/** Hero of the OLake Go page: strata background, copy, the two calls to action, sources and the product shot. */ export default function GoHero() { return ( -
-
-

- {/* First image on the page and above the fold, so it is not lazy-loaded. */} - - {GO_HERO.badge} -

-

- {GO_HERO.headline} -

-
- - +
+ {/* Background: the strata lines, cleared behind the copy by a soft white glow. */} + + +
+
+

+ {GO_HERO.eyebrow} +

+

+ {GO_HERO.headline} +

+

+ {GO_HERO.body} +

+
+ + +
+
+ +
+ +
+ +
diff --git a/src/components/landing/go/GoPage.tsx b/src/components/landing/go/GoPage.tsx index 9724f8d1..63916e28 100644 --- a/src/components/landing/go/GoPage.tsx +++ b/src/components/landing/go/GoPage.tsx @@ -1,34 +1,26 @@ import React from 'react' import LakesidePage from '../ui/LakesidePage' import GoHero from './GoHero' -import GoArchitecture from './GoArchitecture' import GoProblem from './GoProblem' import GoFeatures from './GoFeatures' import GoBenchmark from './GoBenchmark' -import GoFaq from './GoFaq' import GoCta from './GoCta' +import GoFaq from './GoFaq' import './go.css' -/** The OLake Go product page: hero, diagram, problem, features, benchmarks, a closing CTA and the FAQ. */ +/** The OLake Go product page: hero, problem, features, benchmarks, a closing CTA and the FAQ. */ export default function GoPage({ title, description }: { title: string; description: string }) { return ( - } - > - {/* Infima gives headings, paragraphs and lists bottom margins and lists a left padding; - the lakeside reset is zero-specificity and loses, so it is undone here for the whole page. */} -
- + + {/* The negative top margin slides the page (and its grain) under the floating navbar, as the + other landing heroes do. Infima gives headings, paragraphs and lists bottom margins and + lists a left padding; the lakeside reset is zero-specificity and loses, so it is undone + here for the whole page. */} +
+ -
-
- -
-
+
diff --git a/src/components/landing/go/GoProblem.tsx b/src/components/landing/go/GoProblem.tsx index fe1148b3..e9633ed1 100644 --- a/src/components/landing/go/GoProblem.tsx +++ b/src/components/landing/go/GoProblem.tsx @@ -1,79 +1,138 @@ import React from 'react' -import Section from '../ui/Section' -import SectionHeading from '../ui/SectionHeading' -import { cn } from '@site/src/lib/utils' -import { GO_PROBLEM } from '@site/src/data/landing/go/problem' +import { GO_PROBLEMS, GO_PROBLEM_INTRO, type GoProblemArt } from '@site/src/data/landing/go/problem' -/** The cloud outline, stretched to the box it sits in. */ -const CLOUD_PATH = - 'M205,278 C150,278 108,246 100,206 C60,200 34,168 34,132 C34,96 62,66 100,60 C108,26 146,2 192,2 C224,2 253,15 272,37 C296,16 331,4 370,4 C424,4 470,32 486,72 C505,58 530,50 557,50 C607,50 650,80 662,120 C712,120 752,152 752,192 C752,228 720,258 678,262 C670,290 636,310 596,310 C572,310 550,303 533,290 C512,306 481,316 447,316 C420,316 396,310 377,299 C356,314 328,323 297,323 C258,323 224,308 205,278 Z' +const IMG = '/img/landing/go' -/** - * "The Problem": four statements tilted inside a cloud, the key words larger and in the brand blue, - * then the answer headline under the cloud. Each statement drifts up and down slowly (go.css). - */ +/** Each problem's line drawing, at its own size and where the design places it in the card's 203px art column. */ +const ART: Record = { + 'slow-syncs': ( + + ), + 'legacy-costs': ( + + ), + 'cdc-breaks': ( + <> + + {/* The stale-source mark stands upright in the design: rotated a quarter turn in its 48 × 74 slot. */} + + + + + ), + 'schema-drift': ( + + ) +} + +/** The frame's dashed outline (22px dashes, as in the design), drawn on the pixel grid so it stays crisp. */ +const DashedFrame = () => ( + +) + +/** One of the frame's two vertical dashed guides. */ +const Guide = ({ className }: { className: string }) => ( +
) } diff --git a/src/components/landing/go/go.css b/src/components/landing/go/go.css index 8484885c..5bcc2b3a 100644 --- a/src/components/landing/go/go.css +++ b/src/components/landing/go/go.css @@ -1,100 +1,65 @@ /* ============================================================================================= - * OLake Go page: the few pieces Tailwind utilities do not express well (the cloud, the tinted - * features background, keyframes). Every colour is a token or a color-mix of tokens. Motion only - * runs for visitors without a reduced-motion preference and moves opacity or transform. + * OLake Go page: its palette and the pieces Tailwind utilities do not express well (the paper + * grain, the sources marquee, the product mock-ups scaled to fit). Motion only runs for visitors + * without a reduced-motion preference and moves opacity or transform. * ========================================================================================== */ .go-page { - --go-cloud-fill: color-mix(in srgb, var(--olake-blue) 3%, var(--olake-surface)); - --go-cloud-line: color-mix(in srgb, var(--olake-blue) 18%, var(--olake-line)); - --go-tint: color-mix(in srgb, var(--olake-blue) 4%, var(--olake-surface)); + /* The OLake Go design's blues, which the --olake-* tokens do not have. Its near-black, greys and + rules use the closest tokens; text greys that fail WCAG AA in the design (ACCESSIBILITY.md) use + --olake-muted on light surfaces and --olake-muted-on-dark / --olake-blue-on-dark on the dark one. */ + --go-blue: #0029ce; /* primary buttons */ + --go-blue-hover: #0022ad; + --go-blue-deep: #0c36bf; /* the feature card and the closing card */ + --go-mono: 'Geist Mono', var(--olake-font-mono); + + /* The design lays a faint monotone grain over the page (Figma noise, #ececE6 at up to 17%). */ + background-color: var(--olake-surface); + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='g' x='0' y='0' color-interpolation-filters='sRGB'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 0.925 0 0 0 0 0.925 0 0 0 0 0.902 0 0 0 0.425 -0.1275'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23g)'/%3E%3C/svg%3E"); + background-size: 160px 160px; } -/* ---- The cloud --------------------------------------------------------------------------- */ -.go-cloud { - position: relative; - width: 100%; - max-width: 920px; - height: 300px; - margin-inline: auto; +.go-mono { + font-family: var(--go-mono); } -.go-cloud-shape { - position: absolute; - inset: -6% -4%; - width: 108%; - height: 112%; +/* ---- Product mock-ups ---------------------------------------------------------------------- + * The dashboard under the hero and the job editor in the feature card are drawn at their design + * size (--go-fit-w x --go-fit-h, unitless px) and scaled down to the width they are given, so they + * read as one picture at every width. tan(atan2(a, b)) is a / b as a plain number. The wrapper's + * position comes from its utility classes (MockFit makes it `relative`). */ +.go-fit { + container-type: inline-size; + max-width: calc(var(--go-fit-w) * 1px); + aspect-ratio: var(--go-fit-w) / var(--go-fit-h); } -.go-cloud-line { +.go-fit > * { position: absolute; - white-space: nowrap; - transform: translate(-50%, -50%) rotate(var(--go-rot, 0deg)); -} - -.go-cloud-line p { - font-size: 13px; - line-height: 1.1; - letter-spacing: -0.005em; -} - -.go-cloud-line .go-key { - font-size: 16px; -} - -@media (min-width: 640px) { - .go-cloud { - height: 400px; - } - - .go-cloud-line p { - font-size: calc(var(--go-size, 24) * 1px); - } - - .go-cloud-line .go-key { - font-size: calc((var(--go-size, 24) + 7) * 1px); - } + top: 0; + left: 0; + width: calc(var(--go-fit-w) * 1px); + height: calc(var(--go-fit-h) * 1px); + transform-origin: 0 0; + transform: scale(tan(atan2(100cqw, calc(var(--go-fit-w) * 1px)))); } -/* ---- Features: a very light brand tint that fades in and out ------------------------------- */ -.go-features-tint { - background: linear-gradient( - 180deg, - transparent 0, - var(--go-tint) 96px, - var(--go-tint) calc(100% - 96px), - transparent 100% - ); +/* The dashboard fades into the page from 293px down (design px, so it scales with the mock). */ +.go-dashboard-fade { + -webkit-mask-image: linear-gradient(to bottom, #000 293px, transparent 470.6px); + mask-image: linear-gradient(to bottom, #000 293px, transparent 470.6px); } -.go-feature-tile { - background: linear-gradient( - 135deg, - var(--olake-blue), - color-mix(in srgb, var(--olake-blue) 72%, var(--olake-surface)) - ); +/* ---- The "Available Sources" marquee -------------------------------------------------------- */ +.go-marquee-track { + display: flex; + width: max-content; } -/* ---- Motion ------------------------------------------------------------------------------ */ @media (prefers-reduced-motion: no-preference) { - @keyframes go-word-float { - 0%, - 100% { - transform: translateY(0); - } - 50% { - transform: translateY(-5px); - } - } - - @keyframes go-fade-slide-in { - from { - opacity: 0; - transform: translateX(14px); - } + @keyframes go-marquee { to { - opacity: 1; - transform: translateX(0); + transform: translateX(-50%); } } @@ -107,135 +72,16 @@ } } - @keyframes go-pulse-ring { - 0%, - 100% { - opacity: 0.4; - transform: scale(1); - } - 50% { - opacity: 0; - transform: scale(1.22); - } - } - - /* each sync mode lights up in turn: Full Load, then CDC, then Incremental */ - @keyframes go-light-1 { - 0%, - 8% { - opacity: 0; - } - 16%, - 88% { - opacity: 1; - } - 96%, - 100% { - opacity: 0; - } - } - - @keyframes go-light-2 { - 0%, - 32% { - opacity: 0; - } - 40%, - 88% { - opacity: 1; - } - 96%, - 100% { - opacity: 0; - } - } - - @keyframes go-light-3 { - 0%, - 56% { - opacity: 0; - } - 64%, - 88% { - opacity: 1; - } - 96%, - 100% { - opacity: 0; - } - } - - /* each cloud statement drifts up and down, offset from the others */ - .go-cloud-float { - animation: go-word-float 5s ease-in-out infinite; - animation-delay: var(--go-delay, 0s); - } - - /* the ring around the OLake Go node */ - .go-node { - position: relative; - } - - .go-node::after { - content: ''; - position: absolute; - inset: -2px; - border-radius: inherit; - border: 2px solid var(--olake-blue); - pointer-events: none; - animation: go-pulse-ring 2.4s ease-in-out infinite; + .go-marquee-track { + animation: go-marquee 36s linear infinite; } - .go-chip { - position: relative; - isolation: isolate; - overflow: hidden; + .go-marquee:hover .go-marquee-track { + animation-play-state: paused; } - .go-chip::before { - content: ''; - position: absolute; - z-index: -1; - inset: 0; - opacity: 0; - background: var(--olake-blue-tint); - } - - .go-chip-1::before { - animation: go-light-1 4s ease-in-out infinite; - } - - .go-chip-2::before { - animation: go-light-2 4s ease-in-out infinite; - } - - .go-chip-3::before { - animation: go-light-3 4s ease-in-out infinite; - } - - /* the feature card slides in when the active feature changes */ - .go-card-in { - animation: go-fade-slide-in 500ms var(--olake-ease-out) both; - } - - /* opened FAQ answers and the "how" disclosure fade in */ - .go-reveal { - animation: go-fade-in 220ms var(--olake-ease-out) both; - } - - /* the live CDC dot in the first feature preview */ - .go-live-dot { - animation: go-word-float 1.6s ease-in-out infinite; - } - - .go-tab-fill { - transition: transform 100ms linear; - } -} - -/* With reduced motion nothing cycles, so the selected feature shows a full underline. */ -@media (prefers-reduced-motion: reduce) { - .go-tab[aria-selected='true'] .go-tab-fill { - transform: scaleX(1) !important; + /* the feature card copy and opened FAQ answers fade in */ + .go-fade-in { + animation: go-fade-in 240ms var(--olake-ease-out) both; } } diff --git a/src/components/landing/go/mock.tsx b/src/components/landing/go/mock.tsx new file mode 100644 index 00000000..8df0c0e4 --- /dev/null +++ b/src/components/landing/go/mock.tsx @@ -0,0 +1,60 @@ +import React from 'react' +import { cn } from '@site/src/lib/utils' + +/** + * Shared pieces of the two product mock-ups (GoDashboardMock, GoFeatureMock). They are pictures of + * the OLake UI, so they keep the UI's own colours and type rather than the page tokens. + */ + +/** A frame's inside stroke, painted over its children the way the design draws it. Add the width and colour. */ +export const STROKE = + "after:pointer-events-none after:absolute after:inset-0 after:rounded-[inherit] after:border-solid after:content-['']" + +/** An exported icon from /img/landing/go at its own size (`h` defaults to `w`). */ +export function MockIcon({ + name, + w, + h = w, + className +}: { + name: string + w: number + h?: number + className?: string +}) { + return ( + + ) +} + +/** Wraps a mock drawn at `width` × `height` design px so it scales down to fit (`.go-fit` in go.css). */ +export function MockFit({ + width, + height, + className, + innerClassName, + children +}: { + width: number + height: number + className?: string + innerClassName?: string + children: React.ReactNode +}) { + return ( + + ) +} diff --git a/src/components/landing/go/useDiagramLinks.ts b/src/components/landing/go/useDiagramLinks.ts deleted file mode 100644 index b5f01a85..00000000 --- a/src/components/landing/go/useDiagramLinks.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { useEffect, useState, type RefObject } from 'react' - -export interface DiagramLink { - d: string - /** Seconds before the travelling dot starts, so the dots do not move in lockstep. */ - begin: number - side: 'in' | 'out' -} - -export interface DiagramLinks { - width: number - height: number - links: DiagramLink[] - /** False when the visitor prefers reduced motion: the lines stay, the dots do not travel. */ - animate: boolean -} - -/** - * Measures the source chips, the OLake Go node and the destination chips (found by `data-src` and - * `data-dest`) and returns the bezier paths that join them. It runs after mount only, so the first - * render matches the server's. On narrow screens the diagram stacks and no links are returned. - */ -export function useDiagramLinks( - containerRef: RefObject, - nodeRef: RefObject -): DiagramLinks | null { - const [state, setState] = useState(null) - - useEffect(() => { - const container = containerRef.current - const node = nodeRef.current - if (!container || !node) return - - const measure = () => { - const srcs = Array.from(container.querySelectorAll('[data-src]')) - const dests = Array.from(container.querySelectorAll('[data-dest]')) - const c = container.getBoundingClientRect() - const n = node.getBoundingClientRect() - // Stacked layout (mobile): the first source is not to the left of the node. - if (!srcs.length || !dests.length || srcs[0].getBoundingClientRect().right > n.left + 4) { - setState(null) - return - } - const nodeL = { x: n.left - c.left + 10, y: n.top - c.top + n.height / 2 } - const nodeR = { x: n.right - c.left - 10, y: n.top - c.top + n.height / 2 } - const links: DiagramLink[] = [] - srcs.forEach((el, i) => { - const r = el.getBoundingClientRect() - const p = { x: r.right - c.left - 8, y: r.top - c.top + r.height / 2 } - const dx = Math.max(28, (nodeL.x - p.x) * 0.5) - links.push({ - side: 'in', - begin: i * 0.28, - d: `M${p.x},${p.y} C${p.x + dx},${p.y} ${nodeL.x - dx},${nodeL.y} ${nodeL.x},${nodeL.y}` - }) - }) - dests.forEach((el, i) => { - const r = el.getBoundingClientRect() - const p = { x: r.left - c.left + 8, y: r.top - c.top + r.height / 2 } - const dx = Math.max(28, (p.x - nodeR.x) * 0.5) - links.push({ - side: 'out', - begin: 0.6 + i * 0.6, - d: `M${nodeR.x},${nodeR.y} C${nodeR.x + dx},${nodeR.y} ${p.x - dx},${p.y} ${p.x},${p.y}` - }) - }) - setState({ - width: c.width, - height: c.height, - links, - animate: !window.matchMedia('(prefers-reduced-motion: reduce)').matches - }) - } - - measure() - const ro = new ResizeObserver(measure) - ro.observe(container) - return () => ro.disconnect() - }, [containerRef, nodeRef]) - - return state -} diff --git a/src/components/landing/go/useFeatureCycle.ts b/src/components/landing/go/useFeatureCycle.ts deleted file mode 100644 index 71733976..00000000 --- a/src/components/landing/go/useFeatureCycle.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { useCallback, useEffect, useRef, useState } from 'react' - -const TICK_MS = 100 -/** 100 ticks of 100ms: each feature is on screen for ten seconds before the next one. */ -const TICKS_PER_FEATURE = 100 - -/** - * Drives the feature selector: once the section scrolls into view the active feature advances every - * ten seconds and `progress` (0 to 100) fills its underline. Picking a feature by hand stops the - * cycle for good, and it never starts for visitors who prefer reduced motion. - */ -export function useFeatureCycle(count: number) { - const ref = useRef(null) - const [active, setActive] = useState(0) - const [progress, setProgress] = useState(0) - const [running, setRunning] = useState(false) - const [manual, setManual] = useState(false) - - useEffect(() => { - const el = ref.current - if (!el || manual) return - if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return - if (!('IntersectionObserver' in window)) { - setRunning(true) - return - } - const observer = new IntersectionObserver( - ([entry]) => { - if (entry.isIntersecting) { - setRunning(true) - observer.disconnect() - } - }, - { threshold: 0.3 } - ) - observer.observe(el) - return () => observer.disconnect() - }, [manual]) - - useEffect(() => { - if (!running || manual) return - const id = window.setInterval(() => { - setProgress((p) => { - if (p + 1 < TICKS_PER_FEATURE) return p + 1 - setActive((a) => (a + 1) % count) - return 0 - }) - }, TICK_MS) - return () => window.clearInterval(id) - }, [running, manual, count]) - - const select = useCallback((index: number) => { - setActive(index) - setProgress(TICKS_PER_FEATURE) - setManual(true) - }, []) - - return { ref, active, progress, select, manual } -} diff --git a/src/components/landing/go/useGoBenchmark.ts b/src/components/landing/go/useGoBenchmark.ts index 33a53926..d5ab0bd3 100644 --- a/src/components/landing/go/useGoBenchmark.ts +++ b/src/components/landing/go/useGoBenchmark.ts @@ -3,12 +3,16 @@ import { CONNECTORS, CONNECTOR_BENCHMARKS, CONNECTOR_CDC_BENCHMARKS, - CONNECTOR_METRIC_LABELS, TOOLS, type BenchmarkMode, type ConnectorId } from '@site/src/data/benchmarkData' -import { GO_BENCHMARK_COST_NOTE } from '@site/src/data/landing/go/benchmark' +import { + GO_BENCHMARK_CONNECTORS, + GO_BENCHMARK_COST_NOTE, + GO_BENCHMARK_METRIC_LABELS, + goBenchmarkCaption +} from '@site/src/data/landing/go/benchmark' /** Row order of the table, top to bottom. */ const METRICS = ['rowsSynced', 'elapsedTime', 'speed', 'comparison', 'cost'] as const @@ -27,7 +31,7 @@ export interface GoBenchmarkRow { note?: string olake: string competitors: string[] - /** The comparison row is tinted. */ + /** The comparison row is set in blue. */ highlight: boolean } @@ -36,12 +40,13 @@ export interface GoBenchmarkRow { * connector keeps its tab: the ones without published numbers show a "coming soon" panel. */ export function useGoBenchmark() { - const [activeConnector, setActiveConnector] = useState(CONNECTORS[0].id) + const [activeConnector, setActiveConnector] = useState(GO_BENCHMARK_CONNECTORS[0]) const [mode, setMode] = useState('full_load') return useMemo(() => { const dataset = mode === 'cdc' ? CONNECTOR_CDC_BENCHMARKS : CONNECTOR_BENCHMARKS - const connector = CONNECTORS.find((c) => c.id === activeConnector) ?? CONNECTORS[0] + const connectors = GO_BENCHMARK_CONNECTORS.map((id) => CONNECTORS.find((c) => c.id === id)!) + const connector = connectors.find((c) => c.id === activeConnector) ?? connectors[0] const bench = dataset[connector.id] const keys = competitorKeys(bench.rowsSynced.flink !== undefined) @@ -49,24 +54,24 @@ export function useGoBenchmark() { const row = bench[metric] as unknown as Record const isComparison = metric === 'comparison' return { - label: CONNECTOR_METRIC_LABELS[metric], + label: GO_BENCHMARK_METRIC_LABELS[metric], note: metric === 'cost' ? GO_BENCHMARK_COST_NOTE : undefined, // OLake is the baseline of the comparison row, so it shows a dash instead of a multiplier. - olake: isComparison ? '–' : (row.olake ?? '-'), + olake: isComparison ? '-' : (row.olake ?? '-'), competitors: keys.map((key) => row[key] ?? '-'), highlight: isComparison } }) return { - connectors: CONNECTORS.map(({ id, name }) => ({ id, name })), + connectors: connectors.map(({ id, name }) => ({ id, name })), activeConnector: connector.id, setActiveConnector, mode, setMode, competitors: keys.map((key) => TOOLS[key].name), - olakeLabel: 'OLake Go', olakeSub: TOOLS.olake.description ?? '', + caption: goBenchmarkCaption(mode, bench.rowsSynced.olake, connector.name), rows, hasData: bench.hasData, sourceName: connector.name diff --git a/src/css/fonts.css b/src/css/fonts.css index 152b25b9..28311520 100644 --- a/src/css/fonts.css +++ b/src/css/fonts.css @@ -2,8 +2,9 @@ * Generated from the @fontsource-variable packages; only the latin and latin-ext subsets are * shipped, and the browser downloads a subset only when a page uses characters from its * unicode-range. font-display: swap keeps text visible while a font loads. - * Weight ranges are limited to what the site uses: Geist 400-600 (the same weights the old Google - * request provided, so marketing pages render exactly as before), JetBrains Mono 400-700. */ + * Weight ranges are limited to what the site uses: Geist 300-600 (400-600 are the weights the old + * Google request provided; 300 is the light weight of the OLake Go benchmark table), JetBrains Mono + * 400-700, and Geist Mono 300-400 (the "How OLake Go does this" notes on /olake-go). */ @font-face { font-family: 'JetBrains Mono'; @@ -27,7 +28,7 @@ font-family: 'Geist'; font-style: normal; font-display: swap; - font-weight: 400 600; + font-weight: 300 600; src: url('/fonts/geist-latin-ext-wght-normal.woff2') format('woff2'); unicode-range: U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF; } @@ -36,7 +37,25 @@ font-family: 'Geist'; font-style: normal; font-display: swap; - font-weight: 400 600; + font-weight: 300 600; src: url('/fonts/geist-latin-wght-normal.woff2') format('woff2'); unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD; } + +@font-face { + font-family: 'Geist Mono'; + font-style: normal; + font-display: swap; + font-weight: 300 400; + src: url('/fonts/geist-mono-latin-ext-wght-normal.woff2') format('woff2'); + unicode-range: U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF; +} + +@font-face { + font-family: 'Geist Mono'; + font-style: normal; + font-display: swap; + font-weight: 300 400; + src: url('/fonts/geist-mono-latin-wght-normal.woff2') format('woff2'); + unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD; +} diff --git a/src/data/landing/go/architecture.ts b/src/data/landing/go/architecture.ts deleted file mode 100644 index 4d2a42fd..00000000 --- a/src/data/landing/go/architecture.ts +++ /dev/null @@ -1,30 +0,0 @@ -/** The sources → OLake Go → destinations diagram under the hero. */ -export const GO_SOURCES = ['Postgres', 'MongoDB', 'MySQL', 'Kafka', 'S3', 'Oracle', 'MSSQL', 'DB2 LUW'] - -/** Sync modes shown under the OLake Go node. */ -export const GO_SYNC_MODES = ['Full Load', 'CDC', 'Incremental'] - -export interface GoDestination { - label: string - icon: { src: string; alt: string; width: number; height: number } - /** The first destination is the highlighted one in the diagram. */ - primary?: boolean -} - -export const GO_DESTINATIONS: GoDestination[] = [ - { - label: 'Iceberg Tables', - icon: { src: '/img/landing/shared/iceberg-icon.webp', alt: 'Iceberg', width: 256, height: 70 }, - primary: true - }, - { - label: 'Parquet Files', - icon: { src: '/img/landing/shared/parquet-icon.webp', alt: 'Parquet', width: 128, height: 128 } - } -] - -export const GO_ARCHITECTURE_LABELS = { - sources: 'Sources', - destinations: 'Destinations', - node: 'OLake Go' -} diff --git a/src/data/landing/go/benchmark.ts b/src/data/landing/go/benchmark.ts index ff1dc8a4..059f4357 100644 --- a/src/data/landing/go/benchmark.ts +++ b/src/data/landing/go/benchmark.ts @@ -1,19 +1,19 @@ -import type { BenchmarkMode } from '@site/src/data/benchmarkData' +import type { BenchmarkMode, ConnectorId } from '@site/src/data/benchmarkData' -/** Copy of the "OLake Advantage" benchmark block. The numbers themselves live in src/data/benchmarkData.ts. */ +/** Copy of the dark "Benchmarks" section. The numbers themselves live in src/data/benchmarkData.ts. */ export const GO_BENCHMARK_INTRO = { - eyebrow: 'The OLake Advantage', + eyebrow: 'Benchmarks', title: 'Get the OLake Go Advantage' } -/** Bold runs are marked with `strong: true`. */ -export const GO_BENCHMARK_SUMMARY: { text: string; strong?: boolean }[] = [ +/** The design greys the figures and keeps the prose white: those runs are marked `muted: true`. */ +export const GO_BENCHMARK_SUMMARY: { text: string; muted?: boolean }[] = [ { text: 'OLake Go replicates up to ' }, - { text: '12.5× faster', strong: true }, - { text: ' than Fivetran and ' }, - { text: '35–1000× faster', strong: true }, - { text: ' than other open-source tools, syncing ' }, - { text: '50M CDC changes in 15 minutes', strong: true }, + { text: '12.5× faster than Fivetran', muted: true }, + { text: ' and ' }, + { text: '35–1000× faster than other open-source tools, ', muted: true }, + { text: 'syncing ' }, + { text: '50M CDC changes in 15 minutes', muted: true }, { text: '.' } ] @@ -22,25 +22,65 @@ export const GO_BENCHMARK_MODES: { id: BenchmarkMode; label: string }[] = [ { id: 'cdc', label: 'CDC' } ] +/** + * Two-line caption above the table, e.g. "Time to move 4.01 billion rows," / "Postgres to Apache + * Iceberg, full load". `rows` is the OLake row count, '-' while a source has no published numbers. + */ +export function goBenchmarkCaption( + mode: BenchmarkMode, + rows: string, + source: string +): [string, string] { + const amount = rows && rows !== '-' ? `${rows.toLowerCase()} ` : '' + return mode === 'cdc' + ? [`Time to sync ${amount}CDC changes,`, `${source} to Apache Iceberg`] + : [`Time to move ${amount}rows,`, `${source} to Apache Iceberg, full load`] +} + +/** Source tabs of the table, in the design's order. */ +export const GO_BENCHMARK_CONNECTORS: ConnectorId[] = [ + 'postgres', + 'mysql', + 'kafka', + 'mssql', + 'mongodb' +] + +/** Row labels, in the design's title case. */ +export const GO_BENCHMARK_METRIC_LABELS = { + rowsSynced: 'Rows Synced', + elapsedTime: 'Elapsed Time', + speed: 'Speed (Rows/s)', + comparison: 'Comparison', + cost: 'Cost' +} + export const GO_BENCHMARK_LINK = { - label: 'View all performance benchmarks', + label: 'View detailed benchmarks', href: '/docs/benchmarks/ingestion' } -export const GO_BENCHMARK_COST_NOTE = 'OLake is OSS and self-hosted — only pay for your infrastructure.' +export const GO_BENCHMARK_COST_NOTE = + 'OLake is OSS and self-hosted — only pay for your infrastructure.' -/** The disclosure under the table. */ -export const GO_BENCHMARK_INFO = { +/** The notes under the table. */ +export const GO_BENCHMARK_HOW = { title: 'How OLake Go does this', - icon: { src: '/img/landing/shared/benchmark-info-icon.webp', width: 96, height: 96 }, items: [ { term: 'Parallel chunking', - body: '— OLake Go splits large tables into chunks and loads them concurrently for maximum throughput.' + body: 'OLake Go splits large tables into chunks and loads them concurrently for maximum throughput.' }, { term: 'Native Iceberg writes', - body: '— data lands directly in open Apache Iceberg tables with no proprietary staging layer.' + body: 'data lands directly in open Apache Iceberg tables with no proprietary staging layer.' } ] } + +/** The release strip on top of the section; the version comes from docusaurus.config.js. */ +export const GO_RELEASE_STRIP = { + eyebrow: 'Latest Release', + lead: 'We just released our', + cta: 'Read More' +} diff --git a/src/data/landing/go/cta.ts b/src/data/landing/go/cta.ts index 155cc0f3..b9a7d054 100644 --- a/src/data/landing/go/cta.ts +++ b/src/data/landing/go/cta.ts @@ -1,5 +1,7 @@ +/** The blue "Get in touch" card above the FAQ. */ export const GO_CTA = { - title: 'Start replicating to Iceberg today!', - cta: { label: "Try, it's free!", href: '/docs/getting-started/quickstart/' }, - image: { src: '/img/landing/shared/iceberg-backdrop.webp', width: 1280, height: 720 } + eyebrow: 'Get in touch', + title: 'Achieve Sub-Second Data Ingestion', + primary: { label: 'Get Started', href: '/docs/getting-started/quickstart/' }, + secondary: { label: 'Request Callback', href: '/contact/' } } diff --git a/src/data/landing/go/faq.ts b/src/data/landing/go/faq.ts index 3560253a..9aa29613 100644 --- a/src/data/landing/go/faq.ts +++ b/src/data/landing/go/faq.ts @@ -7,7 +7,9 @@ export interface GoFaq { a: string } -export const GO_FAQ_TITLE = 'Frequently Asked Questions' +export const GO_FAQ_EYEBROW = 'FAQs' + +export const GO_FAQ_TITLE = 'You might have some questions' export const GO_FAQS: GoFaq[] = [ { @@ -32,6 +34,6 @@ export const GO_FAQS: GoFaq[] = [ }, { q: 'What data platforms and tools does OLake Go integrate with?', - a: "As of now, we integrate with Apache Iceberg and S3 as destinations. You can query it using popular query engines like Spark, AWS Athena, Snowflake, Databricks, and BigQuery." + a: 'As of now, we integrate with Apache Iceberg and S3 as destinations. You can query it using popular query engines like Spark, AWS Athena, Snowflake, Databricks, and BigQuery.' } ] diff --git a/src/data/landing/go/features.ts b/src/data/landing/go/features.ts index 7e82e5df..a480cfbc 100644 --- a/src/data/landing/go/features.ts +++ b/src/data/landing/go/features.ts @@ -1,41 +1,39 @@ -/** "The Fundamental": the four capabilities in the feature selector. */ -export type GoFeatureArt = 'tiered' | 'decay' | 'chunk' | 'resume' - +/** "Features": the four capabilities behind the tab strip. `tab` is the pill label, `title` the card heading. */ export interface GoFeature { id: string + tab: string title: string body: string - art: GoFeatureArt } export const GO_FEATURES_INTRO = { - eyebrow: 'The Fundamental', + eyebrow: 'Features', title: 'Experience the most seamless workflow' } export const GO_FEATURES: GoFeature[] = [ { - id: 'syncs', - title: 'Full, incremental & CDC syncs', - body: 'Run full loads, incremental pulls, or real-time change data capture, whatever each table needs, all from a single tool.', - art: 'tiered' + id: 'cdc', + tab: 'CDC', + title: 'Full, Incremental & CDC Syncs', + body: 'Run full loads, incremental pulls, or real-time change data capture, whatever each table needs, all from a single tool.' }, { - id: 'evolution', - title: 'Schema & partition evolution', - body: 'Source schemas change and partitions grow. OLake Go evolves your Iceberg tables automatically so pipelines never break.', - art: 'decay' + id: 'schema-evolution', + tab: 'Schema Evolution', + title: 'Schema & Partition Evolution', + body: 'Source schemas change and partitions grow. OLake Go evolves your Iceberg tables automatically so pipelines never break.' }, { - id: 'chunking', - title: 'Parallelised chunking', - body: 'Large collections are split into virtual chunks read in parallel, dramatically cutting the time for full snapshots of big datasets.', - art: 'chunk' + id: 'parallel-chunking', + tab: 'Parallel Chunking', + title: 'Parallelised Chunking', + body: 'Large collections are split into virtual chunks read in parallel, dramatically cutting the time for full snapshots of big datasets.' }, { - id: 'resumable', - title: 'Stateful, resumable syncs', - body: 'Syncs checkpoint their progress and resume automatically after crashes or network failures; never from scratch.', - art: 'resume' + id: 'resumable-syncs', + tab: 'Resumable Syncs', + title: 'Stateful, Resumable Syncs', + body: 'Syncs checkpoint their progress and resume automatically after crashes or network failures; never from scratch.' } ] diff --git a/src/data/landing/go/hero.ts b/src/data/landing/go/hero.ts index 82855f37..adbc002e 100644 --- a/src/data/landing/go/hero.ts +++ b/src/data/landing/go/hero.ts @@ -1,7 +1,27 @@ +import { GITHUB_REPO_URL } from '@site/src/components/landing/chrome/navItems' + /** Hero copy and calls to action of the OLake Go page. */ export const GO_HERO = { - badge: 'OLake Go', - headline: 'Fastest Replication to Iceberg & S3', - primary: { label: 'Get Started', href: '/docs/getting-started/quickstart/' }, - secondary: { label: 'Talk to us', href: '/contact/' } + eyebrow: 'OLake Go', + headline: 'Fastest Replication into Apache Iceberg & S3', + body: 'OLake Go helps you ingest data from 8 different sources into Apache Iceberg and S3', + primary: { label: 'Github', href: GITHUB_REPO_URL }, + secondary: { label: 'Visit Docs', href: '/docs' } } + +export type GoSourceId = + 'kafka' | 'postgres' | 'mysql' | 'mongodb' | 's3' | 'mssql' | 'db2' | 'oracle' + +/** The "Available Sources" strip under the hero buttons, in the order it scrolls. */ +export const GO_SOURCES_LABEL = 'Available Sources' + +export const GO_SOURCES: { id: GoSourceId; name: string }[] = [ + { id: 'kafka', name: 'Kafka' }, + { id: 'postgres', name: 'Postgres' }, + { id: 'mysql', name: 'MySQL' }, + { id: 'mongodb', name: 'MongoDB' }, + { id: 's3', name: 'S3' }, + { id: 'mssql', name: 'MSSQL' }, + { id: 'db2', name: 'DB2' }, + { id: 'oracle', name: 'Oracle' } +] diff --git a/src/data/landing/go/problem.ts b/src/data/landing/go/problem.ts index 15a46633..5712e429 100644 --- a/src/data/landing/go/problem.ts +++ b/src/data/landing/go/problem.ts @@ -1,34 +1,20 @@ -/** "The Problem": four pain points and the answer headline. Emphasised words render larger and in the brand blue. */ -export interface ProblemSentence { - words: string[] - emphasis: string[] - /** Where the statement sits in the cloud: percent from the top and left, tilt in degrees, float delay in seconds, base font size in px. */ - layout: { top: number; left: number; rot: number; delay: number; size: number } +/** "Problem Statement": the four pain points OLake Go removes, each with its line illustration. */ +export type GoProblemArt = 'slow-syncs' | 'legacy-costs' | 'cdc-breaks' | 'schema-drift' + +export interface GoProblem { + label: string + title: string + art: GoProblemArt } -export const GO_PROBLEM = { - eyebrow: 'The Problem', - headline: 'Replicate any source into your lakehouse, seamlessly', - sentences: [ - { - words: ['Slow', 'syncs', 'BLOCK', 'your', 'analytics'], - emphasis: ['Slow', 'syncs', 'BLOCK'], - layout: { top: 22, left: 42, rot: -3, delay: 0, size: 28 } - }, - { - words: ['Legacy', 'ETL', 'costs', 'PILE UP', 'fast'], - emphasis: ['costs', 'PILE UP'], - layout: { top: 43, left: 57, rot: 2, delay: 0.6, size: 24 } - }, - { - words: ['CDC', 'pipelines', 'BREAK', 'silently', 'in', 'production'], - emphasis: ['CDC', 'BREAK'], - layout: { top: 64, left: 46, rot: -2, delay: 1.2, size: 25 } - }, - { - words: ['Schema', 'drift', 'stalls', 'ingestion'], - emphasis: ['Schema', 'drift'], - layout: { top: 85, left: 55, rot: 3, delay: 0.3, size: 23 } - } - ] as ProblemSentence[] +export const GO_PROBLEM_INTRO = { + eyebrow: 'Problem Statement', + title: 'Still doubling back on your ingestion jobs?' } + +export const GO_PROBLEMS: GoProblem[] = [ + { label: 'Problem I', title: 'Slow syncs block your analytics', art: 'slow-syncs' }, + { label: 'Problem II', title: 'Legacy ETL costs pile up fast', art: 'legacy-costs' }, + { label: 'Problem III', title: 'CDC pipelines break silently in production', art: 'cdc-breaks' }, + { label: 'Problem IV', title: 'Schema drift stalls ingestion', art: 'schema-drift' } +] diff --git a/static/fonts/LICENSE-geist-mono.txt b/static/fonts/LICENSE-geist-mono.txt new file mode 100644 index 00000000..2e4aef1e --- /dev/null +++ b/static/fonts/LICENSE-geist-mono.txt @@ -0,0 +1,93 @@ +Copyright 2024 The Geist Project Authors (https://github.com/vercel/geist-font.git) GeistMono-Italic[wght].ttf: Copyright 2024 The Geist Project Authors (https://github.com/vercel/geist-font.git) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/static/fonts/geist-mono-latin-ext-wght-normal.woff2 b/static/fonts/geist-mono-latin-ext-wght-normal.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..ea22fd948e867f6b9e6c474e8bcf01f7ffe35808 GIT binary patch literal 14696 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 + + + + diff --git a/static/img/landing/go/cta-dot-wave.svg b/static/img/landing/go/cta-dot-wave.svg new file mode 100644 index 00000000..84c42647 --- /dev/null +++ b/static/img/landing/go/cta-dot-wave.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/landing/go/dashboard-git-commit.svg b/static/img/landing/go/dashboard-git-commit.svg new file mode 100644 index 00000000..a0cf34a2 --- /dev/null +++ b/static/img/landing/go/dashboard-git-commit.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/static/img/landing/go/dashboard-highlight-actions.svg b/static/img/landing/go/dashboard-highlight-actions.svg new file mode 100644 index 00000000..3575ca13 --- /dev/null +++ b/static/img/landing/go/dashboard-highlight-actions.svg @@ -0,0 +1,3 @@ + + + diff --git a/static/img/landing/go/dashboard-highlight-check.svg b/static/img/landing/go/dashboard-highlight-check.svg new file mode 100644 index 00000000..ec22a22f --- /dev/null +++ b/static/img/landing/go/dashboard-highlight-check.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/static/img/landing/go/dashboard-highlight-destination.svg b/static/img/landing/go/dashboard-highlight-destination.svg new file mode 100644 index 00000000..a4f3cf5a --- /dev/null +++ b/static/img/landing/go/dashboard-highlight-destination.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/static/img/landing/go/dashboard-highlight-source.svg b/static/img/landing/go/dashboard-highlight-source.svg new file mode 100644 index 00000000..e1682f04 --- /dev/null +++ b/static/img/landing/go/dashboard-highlight-source.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/static/img/landing/go/dashboard-plus.svg b/static/img/landing/go/dashboard-plus.svg new file mode 100644 index 00000000..71cc124a --- /dev/null +++ b/static/img/landing/go/dashboard-plus.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/static/img/landing/go/dashboard-row-actions.svg b/static/img/landing/go/dashboard-row-actions.svg new file mode 100644 index 00000000..40825ada --- /dev/null +++ b/static/img/landing/go/dashboard-row-actions.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/static/img/landing/go/dashboard-row-check.svg b/static/img/landing/go/dashboard-row-check.svg new file mode 100644 index 00000000..b5ac8511 --- /dev/null +++ b/static/img/landing/go/dashboard-row-check.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/static/img/landing/go/dashboard-row-destination.svg b/static/img/landing/go/dashboard-row-destination.svg new file mode 100644 index 00000000..babf4b9d --- /dev/null +++ b/static/img/landing/go/dashboard-row-destination.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/static/img/landing/go/dashboard-row-source.svg b/static/img/landing/go/dashboard-row-source.svg new file mode 100644 index 00000000..2bd9ecd4 --- /dev/null +++ b/static/img/landing/go/dashboard-row-source.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/static/img/landing/go/dashboard-sidebar-help.svg b/static/img/landing/go/dashboard-sidebar-help.svg new file mode 100644 index 00000000..5f18b034 --- /dev/null +++ b/static/img/landing/go/dashboard-sidebar-help.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/static/img/landing/go/dashboard-sidebar-jobs.svg b/static/img/landing/go/dashboard-sidebar-jobs.svg new file mode 100644 index 00000000..68f38520 --- /dev/null +++ b/static/img/landing/go/dashboard-sidebar-jobs.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/static/img/landing/go/dashboard-sidebar-settings.svg b/static/img/landing/go/dashboard-sidebar-settings.svg new file mode 100644 index 00000000..044bceb5 --- /dev/null +++ b/static/img/landing/go/dashboard-sidebar-settings.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/static/img/landing/go/dashboard-sidebar-sparkle.svg b/static/img/landing/go/dashboard-sidebar-sparkle.svg new file mode 100644 index 00000000..1184ca9b --- /dev/null +++ b/static/img/landing/go/dashboard-sidebar-sparkle.svg @@ -0,0 +1,3 @@ + + + diff --git a/static/img/landing/go/dashboard-sidebar-toggle.svg b/static/img/landing/go/dashboard-sidebar-toggle.svg new file mode 100644 index 00000000..11679a2e --- /dev/null +++ b/static/img/landing/go/dashboard-sidebar-toggle.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/static/img/landing/go/faq-arrow-closed.svg b/static/img/landing/go/faq-arrow-closed.svg new file mode 100644 index 00000000..c2e0950e --- /dev/null +++ b/static/img/landing/go/faq-arrow-closed.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/static/img/landing/go/faq-arrow-open.svg b/static/img/landing/go/faq-arrow-open.svg new file mode 100644 index 00000000..38097c98 --- /dev/null +++ b/static/img/landing/go/faq-arrow-open.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/static/img/landing/go/feature-cdc-background.png b/static/img/landing/go/feature-cdc-background.png new file mode 100644 index 0000000000000000000000000000000000000000..f9551a099e9521da9abd494903f684ef409a098e GIT binary patch literal 211575 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
diff --git a/src/data/landing/go/features.ts b/src/data/landing/go/features.ts index a480cfbc..c41fd52f 100644 --- a/src/data/landing/go/features.ts +++ b/src/data/landing/go/features.ts @@ -33,7 +33,7 @@ export const GO_FEATURES: GoFeature[] = [ { id: 'resumable-syncs', tab: 'Resumable Syncs', - title: 'Stateful, Resumable Syncs', + title: 'Stateful Resumable Syncs', body: 'Syncs checkpoint their progress and resume automatically after crashes or network failures; never from scratch.' } ] diff --git a/static/img/landing/go/feature-background.png b/static/img/landing/go/feature-background.png new file mode 100644 index 0000000000000000000000000000000000000000..2ad238fd404dba86b80b66b0e424ab4e6d5a414a GIT binary patch literal 153604 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ΜX&g3OY@#~Tg!Jgr=E`9@!+*9|Q>0iP$GIvKt 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 X!@!PBZV&%83w}J^yuM<78FK7DX&BdL literal 0 HcmV?d00001 diff --git a/static/img/landing/go/feature-chunking-dot-lg.svg b/static/img/landing/go/feature-chunking-dot-lg.svg new file mode 100644 index 00000000..165cd3fa --- /dev/null +++ b/static/img/landing/go/feature-chunking-dot-lg.svg @@ -0,0 +1,3 @@ + + + diff --git a/static/img/landing/go/feature-chunking-dot.svg b/static/img/landing/go/feature-chunking-dot.svg new file mode 100644 index 00000000..1b5d8f80 --- /dev/null +++ b/static/img/landing/go/feature-chunking-dot.svg @@ -0,0 +1,3 @@ + + + diff --git a/static/img/landing/go/feature-panel-pencil.svg b/static/img/landing/go/feature-panel-pencil.svg new file mode 100644 index 00000000..d5e86521 --- /dev/null +++ b/static/img/landing/go/feature-panel-pencil.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/static/img/landing/go/feature-panel-search.svg b/static/img/landing/go/feature-panel-search.svg new file mode 100644 index 00000000..5a95aa00 --- /dev/null +++ b/static/img/landing/go/feature-panel-search.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/static/img/landing/go/feature-panel-sidebar-help.svg b/static/img/landing/go/feature-panel-sidebar-help.svg new file mode 100644 index 00000000..318705e2 --- /dev/null +++ b/static/img/landing/go/feature-panel-sidebar-help.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/static/img/landing/go/feature-panel-sidebar-jobs.svg b/static/img/landing/go/feature-panel-sidebar-jobs.svg new file mode 100644 index 00000000..4b73e2fb --- /dev/null +++ b/static/img/landing/go/feature-panel-sidebar-jobs.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/static/img/landing/go/feature-panel-sidebar-settings.svg b/static/img/landing/go/feature-panel-sidebar-settings.svg new file mode 100644 index 00000000..5f11f919 --- /dev/null +++ b/static/img/landing/go/feature-panel-sidebar-settings.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/static/img/landing/go/feature-panel-sidebar-sparkle.svg b/static/img/landing/go/feature-panel-sidebar-sparkle.svg new file mode 100644 index 00000000..95928f80 --- /dev/null +++ b/static/img/landing/go/feature-panel-sidebar-sparkle.svg @@ -0,0 +1,3 @@ + + + diff --git a/static/img/landing/go/feature-panel-sidebar-toggle.svg b/static/img/landing/go/feature-panel-sidebar-toggle.svg new file mode 100644 index 00000000..8127c07f --- /dev/null +++ b/static/img/landing/go/feature-panel-sidebar-toggle.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/static/img/landing/go/feature-resumable-arrow-ccw.svg b/static/img/landing/go/feature-resumable-arrow-ccw.svg new file mode 100644 index 00000000..a5da37dc --- /dev/null +++ b/static/img/landing/go/feature-resumable-arrow-ccw.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/static/img/landing/go/feature-resumable-check-circle.svg b/static/img/landing/go/feature-resumable-check-circle.svg new file mode 100644 index 00000000..544dfa2a --- /dev/null +++ b/static/img/landing/go/feature-resumable-check-circle.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/static/img/landing/go/feature-resumable-plugs.svg b/static/img/landing/go/feature-resumable-plugs.svg new file mode 100644 index 00000000..29a98911 --- /dev/null +++ b/static/img/landing/go/feature-resumable-plugs.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/static/img/landing/go/feature-resumable-table.svg b/static/img/landing/go/feature-resumable-table.svg new file mode 100644 index 00000000..c8760817 --- /dev/null +++ b/static/img/landing/go/feature-resumable-table.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/static/img/landing/go/feature-schema-checkbox-all.svg b/static/img/landing/go/feature-schema-checkbox-all.svg new file mode 100644 index 00000000..dc179f40 --- /dev/null +++ b/static/img/landing/go/feature-schema-checkbox-all.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/static/img/landing/go/feature-schema-checkbox-highlight.svg b/static/img/landing/go/feature-schema-checkbox-highlight.svg new file mode 100644 index 00000000..c571b9ca --- /dev/null +++ b/static/img/landing/go/feature-schema-checkbox-highlight.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/static/img/landing/go/feature-schema-checkbox.svg b/static/img/landing/go/feature-schema-checkbox.svg new file mode 100644 index 00000000..c667b5a6 --- /dev/null +++ b/static/img/landing/go/feature-schema-checkbox.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/static/img/landing/go/feature-schema-search.svg b/static/img/landing/go/feature-schema-search.svg new file mode 100644 index 00000000..89b4577b --- /dev/null +++ b/static/img/landing/go/feature-schema-search.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/static/img/landing/go/feature-schema-tab-config.svg b/static/img/landing/go/feature-schema-tab-config.svg new file mode 100644 index 00000000..8006dbc4 --- /dev/null +++ b/static/img/landing/go/feature-schema-tab-config.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/static/img/landing/go/feature-schema-tab-partitioning.svg b/static/img/landing/go/feature-schema-tab-partitioning.svg new file mode 100644 index 00000000..2995ec72 --- /dev/null +++ b/static/img/landing/go/feature-schema-tab-partitioning.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/static/img/landing/go/feature-schema-tab-schema.svg b/static/img/landing/go/feature-schema-tab-schema.svg new file mode 100644 index 00000000..29e401be --- /dev/null +++ b/static/img/landing/go/feature-schema-tab-schema.svg @@ -0,0 +1,10 @@ + + + + + + + + + + From 9895a2d249ed953fcf937008c9736c6849f48385 Mon Sep 17 00:00:00 2001 From: Krishna Jain Date: Mon, 12 Oct 2026 18:44:46 +0530 Subject: [PATCH 05/28] fix(olake-go): hide benchmark sources that have no numbers in the chosen mode Kafka and MSSQL only have full-load benchmarks, so with CDC selected their tabs opened the "coming soon" panel. Follow the home page table: list a source only in the modes it has data for, and when the selected source has none in the new mode fall back to the first one that does. The choice returns when switching back to Full Load. --- src/components/landing/go/useGoBenchmark.ts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/components/landing/go/useGoBenchmark.ts b/src/components/landing/go/useGoBenchmark.ts index d5ab0bd3..5e3fc836 100644 --- a/src/components/landing/go/useGoBenchmark.ts +++ b/src/components/landing/go/useGoBenchmark.ts @@ -36,17 +36,21 @@ export interface GoBenchmarkRow { } /** - * State and table data of the OLake Go benchmark block. Unlike the home page's table, every - * connector keeps its tab: the ones without published numbers show a "coming soon" panel. + * State and table data of the OLake Go benchmark block. Like the home page's table, a source only + * gets a tab in the modes it has published numbers for (Kafka and MSSQL have full-load numbers but + * no CDC ones), and switching to a mode the selected source lacks falls back to the first source + * that has them. */ export function useGoBenchmark() { - const [activeConnector, setActiveConnector] = useState(GO_BENCHMARK_CONNECTORS[0]) + const [selectedConnector, setActiveConnector] = useState(GO_BENCHMARK_CONNECTORS[0]) const [mode, setMode] = useState('full_load') return useMemo(() => { const dataset = mode === 'cdc' ? CONNECTOR_CDC_BENCHMARKS : CONNECTOR_BENCHMARKS - const connectors = GO_BENCHMARK_CONNECTORS.map((id) => CONNECTORS.find((c) => c.id === id)!) - const connector = connectors.find((c) => c.id === activeConnector) ?? connectors[0] + const all = GO_BENCHMARK_CONNECTORS.map((id) => CONNECTORS.find((c) => c.id === id)!) + const withData = all.filter((c) => dataset[c.id]?.hasData) + const connectors = withData.length ? withData : all + const connector = connectors.find((c) => c.id === selectedConnector) ?? connectors[0] const bench = dataset[connector.id] const keys = competitorKeys(bench.rowsSynced.flink !== undefined) @@ -76,5 +80,5 @@ export function useGoBenchmark() { hasData: bench.hasData, sourceName: connector.name } - }, [activeConnector, mode]) + }, [selectedConnector, mode]) } From 9254f1c18d9644f5bf53357bbdf99b113ad7f080 Mon Sep 17 00:00:00 2001 From: Krishna Jain Date: Tue, 13 Oct 2026 11:15:24 +0530 Subject: [PATCH 06/28] refactor(landing): share the product page building blocks Pull the pieces the OLake Go page shares with the upcoming OLake Fusion design into src/components/landing/product: the page shell with the palette and paper grain, the hero copy and buttons, the feature tab strip and blue card, the dark benchmark shell (release strip, notes, link bar), the closing CTA card, the FAQ accordion, and the mock-up helpers (scale-to-fit, fade, shared navigation rail). Icons both pages use move to static/img/landing/product. The Go page renders pixel-identically at 1440px and 390px. --- src/components/landing/go/GoBenchmark.tsx | 356 +++++++----------- src/components/landing/go/GoCta.tsx | 37 +- src/components/landing/go/GoDashboardMock.tsx | 34 +- src/components/landing/go/GoFaq.tsx | 76 +--- src/components/landing/go/GoFeatureMock.tsx | 27 +- src/components/landing/go/GoFeaturePanels.tsx | 38 +- src/components/landing/go/GoFeatures.tsx | 153 ++------ src/components/landing/go/GoHero.tsx | 107 ++---- src/components/landing/go/GoPage.tsx | 24 +- src/components/landing/go/go.css | 66 +--- src/components/landing/go/mock.tsx | 60 --- .../landing/product/BenchmarkShell.tsx | 118 ++++++ src/components/landing/product/ProductCta.tsx | 57 +++ src/components/landing/product/ProductFaq.tsx | 92 +++++ .../landing/product/ProductFeatures.tsx | 148 ++++++++ .../landing/product/ProductHero.tsx | 88 +++++ .../landing/product/ProductPage.tsx | 31 ++ src/components/landing/product/mock.tsx | 120 ++++++ src/components/landing/product/product.css | 68 ++++ src/data/landing/go/benchmark.ts | 7 - .../{go => product}/faq-arrow-closed.svg | 0 .../{go => product}/faq-arrow-open.svg | 0 .../landing/{go => product}/icon-github.svg | 0 .../link-arrow-square-out.svg} | 0 .../mock-sidebar-help.svg} | 0 .../mock-sidebar-jobs.svg} | 0 .../mock-sidebar-settings.svg} | 0 .../mock-sidebar-sparkle.svg} | 0 .../mock-sidebar-toggle.svg} | 0 29 files changed, 992 insertions(+), 715 deletions(-) delete mode 100644 src/components/landing/go/mock.tsx create mode 100644 src/components/landing/product/BenchmarkShell.tsx create mode 100644 src/components/landing/product/ProductCta.tsx create mode 100644 src/components/landing/product/ProductFaq.tsx create mode 100644 src/components/landing/product/ProductFeatures.tsx create mode 100644 src/components/landing/product/ProductHero.tsx create mode 100644 src/components/landing/product/ProductPage.tsx create mode 100644 src/components/landing/product/mock.tsx create mode 100644 src/components/landing/product/product.css rename static/img/landing/{go => product}/faq-arrow-closed.svg (100%) rename static/img/landing/{go => product}/faq-arrow-open.svg (100%) rename static/img/landing/{go => product}/icon-github.svg (100%) rename static/img/landing/{go/benchmark-arrow-square-out.svg => product/link-arrow-square-out.svg} (100%) rename static/img/landing/{go/feature-mock-sidebar-help.svg => product/mock-sidebar-help.svg} (100%) rename static/img/landing/{go/feature-mock-sidebar-jobs.svg => product/mock-sidebar-jobs.svg} (100%) rename static/img/landing/{go/feature-mock-sidebar-settings.svg => product/mock-sidebar-settings.svg} (100%) rename static/img/landing/{go/feature-mock-sidebar-sparkle.svg => product/mock-sidebar-sparkle.svg} (100%) rename static/img/landing/{go/feature-mock-sidebar-toggle.svg => product/mock-sidebar-toggle.svg} (100%) diff --git a/src/components/landing/go/GoBenchmark.tsx b/src/components/landing/go/GoBenchmark.tsx index 81db2780..dcff16cf 100644 --- a/src/components/landing/go/GoBenchmark.tsx +++ b/src/components/landing/go/GoBenchmark.tsx @@ -1,20 +1,15 @@ import React from 'react' -import Link from '@docusaurus/Link' -import useDocusaurusContext from '@docusaurus/useDocusaurusContext' import { cn } from '@site/src/lib/utils' +import BenchmarkShell, { FOCUS_ON_DARK as FOCUS, RULE } from '../product/BenchmarkShell' import { GO_BENCHMARK_HOW, GO_BENCHMARK_INTRO, GO_BENCHMARK_LINK, GO_BENCHMARK_MODES, - GO_BENCHMARK_SUMMARY, - GO_RELEASE_STRIP + GO_BENCHMARK_SUMMARY } from '@site/src/data/landing/go/benchmark' import { useGoBenchmark } from './useGoBenchmark' -const FOCUS = - 'focus-visible:rounded-[4px] focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-olake-blue-on-dark' - /** Mode and source switches: 12px labels with a 24px tall hit area. */ const Switch = ({ pressed, @@ -53,8 +48,6 @@ const Figure = ({ value }: { value: string }) => { /** Column widths of the design's table (198, 172, 147, 148, 147 and 145 of its 957px); it leaves 45px free on the right. */ const COLUMNS = ['20.69%', '17.97%', '15.36%', '15.47%', '15.36%', '15.15%'] -/** Rules on the dark section. It goes first in cn(): a later border-0 would drop the side classes. */ -const RULE = 'border-0 border-solid border-olake-line-dark' /** * "Benchmarks": the dark full-bleed section with the latest-release strip, the comparison table @@ -63,237 +56,178 @@ const RULE = 'border-0 border-solid border-olake-line-dark' */ export default function GoBenchmark() { const t = useGoBenchmark() - const { siteConfig } = useDocusaurusContext() - const releaseLabel = siteConfig.customFields?.latestOlakeReleaseLabel as string | undefined - const releasePath = - (siteConfig.customFields?.latestOlakeReleasePath as string | undefined) || - '/docs/release/ingestion' - return ( -
-
- {releaseLabel && ( -
- {GO_RELEASE_STRIP.eyebrow} - - {GO_RELEASE_STRIP.lead} {releaseLabel} - - + part.muted ? ( + + {part.text} + + ) : ( + {part.text} + ) + )} + how={{ + title: GO_BENCHMARK_HOW.title, + body: ( +
+ {GO_BENCHMARK_HOW.items.map((item) => ( +

+ {item.term} / {item.body} +

+ ))} +
+ ) + }} + link={GO_BENCHMARK_LINK} + > +
+
+

+ {t.caption[0]} +
+ {t.caption[1]} +

+
+
+ {GO_BENCHMARK_MODES.map((m) => ( + t.setMode(m.id)}> + {m.label} + + ))} +
+
- {GO_RELEASE_STRIP.cta} - + {t.connectors.map((c) => ( + t.setActiveConnector(c.id)} + > + {c.name} + + ))} +
- )} - -
-

- {GO_BENCHMARK_INTRO.eyebrow} -

-

- {GO_BENCHMARK_INTRO.title} -

-

- {GO_BENCHMARK_SUMMARY.map((part, i) => - part.muted ? ( - - {part.text} - - ) : ( - {part.text} - ) - )} -

-
-
-

- {t.caption[0]} -
- {t.caption[1]} -

-
-
- {GO_BENCHMARK_MODES.map((m) => ( - t.setMode(m.id)}> - {m.label} - + {t.hasData ? ( +
+ + + {COLUMNS.map((width, i) => ( + ))} - -
- {t.connectors.map((c) => ( - t.setActiveConnector(c.id)} + +
+ + + ) + })} + +
- {c.name} - - ))} - - - - - {t.hasData ? ( -
- - - {COLUMNS.map((width, i) => ( - - ))} - - - + Metrics + + + {t.competitors.map((name, i) => ( - + + {t.rows.map((row, r) => { + const cell = 'px-[8px] pt-[29px] pb-0 align-top text-[14px] leading-[1.3]' + return ( + - - OLake - Go - - - {t.olakeSub} - - - {t.competitors.map((name, i) => ( - ))} - - - - {t.rows.map((row, r) => { - const cell = 'px-[8px] pt-[29px] pb-0 align-top text-[14px] leading-[1.3]' - return ( - + {row.note} + )} - > - + {row.competitors.map((value, i) => ( + - {row.competitors.map((value, i) => ( - - ))} - - ) - })} - -
+ + OLake + Go + + {t.olakeSub} + - Metrics + {name} +
- {name} -
+ +
+
- {row.label} - {row.note && ( - - {row.note} - - )} - - -
+
-
-
-
- ) : ( -
-

- {t.sourceName} benchmarks coming soon -

-

- We're running head-to-head {t.sourceName} ingestion tests now. Check back - shortly for the full comparison. -

-
- )} - - -
-

- {GO_BENCHMARK_HOW.title} -

-
- {GO_BENCHMARK_HOW.items.map((item) => ( -

- {item.term} / {item.body} -

- ))} + ))} +
-
- - - {GO_BENCHMARK_LINK.label} - - + ) : ( +
+

+ {t.sourceName} benchmarks coming soon +

+

+ We're running head-to-head {t.sourceName} ingestion tests now. Check back shortly + for the full comparison. +

+
+ )}
-
+ ) } diff --git a/src/components/landing/go/GoCta.tsx b/src/components/landing/go/GoCta.tsx index 0d82ea3c..b5eb4303 100644 --- a/src/components/landing/go/GoCta.tsx +++ b/src/components/landing/go/GoCta.tsx @@ -1,15 +1,15 @@ import React from 'react' -import Button from '../ui/Button' +import ProductCta from '../product/ProductCta' import { GO_CTA } from '@site/src/data/landing/go/cta' -const BUTTON = 'h-[36px] rounded-[8px] px-[10px] text-[16px] leading-[1.3]' - -/** Closing call to action: the blue card with a field of dots rising behind the buttons. */ +/** Closing call to action of the OLake Go page: the blue card with a field of dots rising behind the buttons. */ export default function GoCta() { return ( -
-
- {/* The dot wave, upside down as in the design, its top 267px showing under the buttons. */} + -

{GO_CTA.eyebrow}

-

- {GO_CTA.title} -

-
- - -
-
-
+ } + /> ) } diff --git a/src/components/landing/go/GoDashboardMock.tsx b/src/components/landing/go/GoDashboardMock.tsx index c5f2c3ea..7649780f 100644 --- a/src/components/landing/go/GoDashboardMock.tsx +++ b/src/components/landing/go/GoDashboardMock.tsx @@ -1,6 +1,6 @@ import React from 'react' import { cn } from '@site/src/lib/utils' -import { MockFit, MockIcon as Icon, STROKE } from './mock' +import { MockFit, MockIcon as Icon, STROKE } from '../product/mock' /** * The OLake Go "Jobs" screen under the hero, drawn at its design size (1018 × 471) and scaled to @@ -59,7 +59,7 @@ const ROW_TEXT = 'whitespace-nowrap text-[11.782px] leading-[20.197px]' export default function GoDashboardMock() { return ( - +
- +

Jobs

@@ -79,7 +79,7 @@ export default function GoDashboardMock() {

- + Create job @@ -128,7 +128,7 @@ export default function GoDashboardMock() { style={{ top: `${142.25 + i * 49.71}px` }} > - + {row.source} - + {row.destination} - + {row.status} @@ -177,7 +177,7 @@ export default function GoDashboardMock() { )} > - + MongoDB - + Amazon S3 @@ -201,7 +201,7 @@ export default function GoDashboardMock() { 21 hours ago - + Success @@ -211,16 +211,16 @@ export default function GoDashboardMock() { {/* Navigation rail */}
- +
- + - + - - + +
diff --git a/src/components/landing/go/GoFaq.tsx b/src/components/landing/go/GoFaq.tsx index 7065a6cc..5ba2e5ab 100644 --- a/src/components/landing/go/GoFaq.tsx +++ b/src/components/landing/go/GoFaq.tsx @@ -1,78 +1,10 @@ -import React, { useState } from 'react' -import { cn } from '@site/src/lib/utils' +import React from 'react' +import ProductFaq from '../product/ProductFaq' import { GO_FAQS, GO_FAQ_EYEBROW, GO_FAQ_TITLE } from '@site/src/data/landing/go/faq' -/** - * Accordion, one answer open at a time (the first one to start with). Answers stay in the DOM - * (hidden) so the text the FAQPage JSON-LD describes is always on the page. - */ +/** The OLake Go FAQ. Its questions are mirrored word for word by the FAQPage JSON-LD in seo.ts. */ export default function GoFaq() { - const [open, setOpen] = useState(0) - return ( -
-
-
-

- {GO_FAQ_EYEBROW} -

-

- {GO_FAQ_TITLE} -

-
- -
    - {GO_FAQS.map((faq, i) => { - const isOpen = open === i - return ( -
  • -

    - -

    - -
  • - ) - })} -
-
-
+ ) } diff --git a/src/components/landing/go/GoFeatureMock.tsx b/src/components/landing/go/GoFeatureMock.tsx index a5247fb3..649effe7 100644 --- a/src/components/landing/go/GoFeatureMock.tsx +++ b/src/components/landing/go/GoFeatureMock.tsx @@ -1,6 +1,6 @@ import React from 'react' import { cn } from '@site/src/lib/utils' -import { MockFit, MockIcon as Icon, STROKE } from './mock' +import { MockFit, MockIcon as Icon, MockRail, STROKE } from '../product/mock' /** * The job editor inside the blue feature card: the stream list, the selected stream's settings and @@ -91,7 +91,7 @@ export default function GoFeatureMock({ className }: { className?: string }) {
- +
@@ -102,7 +102,7 @@ export default function GoFeatureMock({ className }: { className?: string }) { DestinationDB @@ -119,7 +119,7 @@ export default function GoFeatureMock({ className }: { className?: string }) { > - + Payments @@ -140,7 +140,7 @@ export default function GoFeatureMock({ className }: { className?: string }) { 'border-[0.409px] border-solid border-[#203fdd] bg-white shadow-[0_0.818px_0.818px_rgba(0,0,0,0.02),0_0.409px_1.226px_rgba(0,0,0,0.02),0_0.409px_0.409px_rgba(0,0,0,0.03)]' )} > - + - {/* Navigation rail */} -
-
- -
- - - - - - - - -
-
-
+
{/* The "Sync mode" picker */} diff --git a/src/components/landing/go/GoFeaturePanels.tsx b/src/components/landing/go/GoFeaturePanels.tsx index 3565506b..64b55c27 100644 --- a/src/components/landing/go/GoFeaturePanels.tsx +++ b/src/components/landing/go/GoFeaturePanels.tsx @@ -1,6 +1,6 @@ import React from 'react' import { cn } from '@site/src/lib/utils' -import { MockFit, MockIcon as Icon, STROKE } from './mock' +import { MockFit, MockIcon as Icon, STROKE } from '../product/mock' /** * The job-editor panels of the Schema Evolution, Parallel Chunking and Resumable Syncs tabs (the @@ -39,7 +39,7 @@ function Panel({
- +
@@ -50,7 +50,7 @@ function Panel({ DestinationDB @@ -62,16 +62,16 @@ function Panel({ {/* Navigation rail */}
- +
- + - + - - + +
@@ -131,7 +131,7 @@ export function SchemaMock({ className }: { className?: string }) { : 'opacity-30' )} > - +
- +
- + Column name Destination Column @@ -175,7 +175,7 @@ export function SchemaMock({ className }: { className?: string }) { style={{ top: 206.17 + i * 36.255 }} > - + {column} @@ -191,7 +191,7 @@ export function SchemaMock({ className }: { className?: string }) { {/* The highlighted column, lifted over the list */}
- + User_ID @@ -266,7 +266,7 @@ export function ChunkingMock({ className }: { className?: string }) { {chunk.done} - + {chunk.rate} @@ -289,7 +289,7 @@ export function ChunkingMock({ className }: { className?: string }) { 750K / 1M - + 25K rows/s @@ -318,7 +318,7 @@ function CheckpointCard({ table, top, muted }: { table: string; top: number; mut >
- + {table} Checkpoint restored · Snapshot resumed @@ -396,13 +396,13 @@ function CheckpointCard({ table, top, muted }: { table: string; top: number; mut ))} - + diff --git a/src/components/landing/go/GoFeatures.tsx b/src/components/landing/go/GoFeatures.tsx index d0c3cebd..9b0d9c79 100644 --- a/src/components/landing/go/GoFeatures.tsx +++ b/src/components/landing/go/GoFeatures.tsx @@ -1,5 +1,6 @@ -import React, { useRef, useState } from 'react' +import React from 'react' import { cn } from '@site/src/lib/utils' +import ProductFeatures, { type ProductFeatureArt } from '../product/ProductFeatures' import { GO_FEATURES, GO_FEATURES_INTRO } from '@site/src/data/landing/go/features' import GoFeatureMock from './GoFeatureMock' import { ChunkingMock, ResumableMock, SchemaMock } from './GoFeaturePanels' @@ -21,20 +22,24 @@ const field = (className: string) => ( /> ) +/** The blue glow at the bottom left that every tab shares. */ +const Glow = () => ( + +) + /** * Each tab's picture (the design's go-1 to go-4 frames): the background field, where the copy sits * from lg up (its top, and the body's width) and the editor panel with its lg position. */ -const ART: Record< - string, - { - background: React.ReactNode - copyTop: number - bodyWidth: number - Mock: React.ComponentType<{ className?: string }> - mockClassName: string - } -> = { +const ART: Record = { 'cdc': { background: ( ), + glow: , copyTop: 338, bodyWidth: 323, Mock: GoFeatureMock, @@ -54,6 +60,7 @@ const ART: Record< }, 'schema-evolution': { background: field('right-[-281px]'), + glow: , copyTop: 338, bodyWidth: 323, Mock: SchemaMock, @@ -61,6 +68,7 @@ const ART: Record< }, 'parallel-chunking': { background: field('right-[-129px] -scale-x-100'), + glow: , copyTop: 354, bodyWidth: 288, Mock: ChunkingMock, @@ -68,6 +76,7 @@ const ART: Record< }, 'resumable-syncs': { background: field('right-[-129px] -scale-y-100'), + glow: , copyTop: 333, bodyWidth: 288, Mock: ResumableMock, @@ -75,122 +84,14 @@ const ART: Record< } } -/** - * "Features": a pill tab strip over the blue feature card. Each tab shows its own picture of the - * job editor; the heading and copy follow the selected capability. - */ +/** "Features" of the OLake Go page: the design's go-1 to go-4 frames behind a pill tab strip. */ export default function GoFeatures() { - const [active, setActive] = useState(0) - const tabRefs = useRef<(HTMLButtonElement | null)[]>([]) - - const select = (index: number) => { - setActive(index) - tabRefs.current[index]?.focus() - } - - const onKeyDown = (event: React.KeyboardEvent, index: number) => { - const last = GO_FEATURES.length - 1 - if (event.key === 'ArrowRight') select(index === last ? 0 : index + 1) - else if (event.key === 'ArrowLeft') select(index === 0 ? last : index - 1) - else if (event.key === 'Home') select(0) - else if (event.key === 'End') select(last) - else return - event.preventDefault() - } - - const feature = GO_FEATURES[active] - const art = ART[feature.id] - return ( -
-
-
-

- {GO_FEATURES_INTRO.eyebrow} -

-

- {GO_FEATURES_INTRO.title} -

-
- -
- {GO_FEATURES.map((item, i) => { - const selected = i === active - return ( - - ) - })} -
- -
- {/* The tab's dotted field, and the blue glow at the bottom left that every tab shares */} - {art.background} - - -
-

- {feature.title} -

-

- {feature.body} -

-
- - -
-
-
+ ) } diff --git a/src/components/landing/go/GoHero.tsx b/src/components/landing/go/GoHero.tsx index dc4d7802..ede18f92 100644 --- a/src/components/landing/go/GoHero.tsx +++ b/src/components/landing/go/GoHero.tsx @@ -1,6 +1,5 @@ import React from 'react' -import Button from '../ui/Button' -import { cn } from '@site/src/lib/utils' +import ProductHero from '../product/ProductHero' import { GO_HERO, GO_SOURCES, @@ -146,85 +145,41 @@ function SourcesStrip() { ) } -const BUTTON = 'h-[36px] px-[10px] text-[16px] leading-[1.3]' +/** The strata lines, cleared behind the copy by a soft white glow. */ +const Background = () => ( + +) /** Hero of the OLake Go page: strata background, copy, the two calls to action, sources and the product shot. */ export default function GoHero() { return ( -
- {/* Background: the strata lines, cleared behind the copy by a soft white glow. */} - - -
-
-

- {GO_HERO.eyebrow} -

-

- {GO_HERO.headline} -

-

- {GO_HERO.body} -

-
- - -
-
- -
- -
- -
+ } className='lg:pt-[209px]'> +
+ +
+
-
+ ) } diff --git a/src/components/landing/go/GoPage.tsx b/src/components/landing/go/GoPage.tsx index 63916e28..d5faf62e 100644 --- a/src/components/landing/go/GoPage.tsx +++ b/src/components/landing/go/GoPage.tsx @@ -1,5 +1,5 @@ import React from 'react' -import LakesidePage from '../ui/LakesidePage' +import ProductPage from '../product/ProductPage' import GoHero from './GoHero' import GoProblem from './GoProblem' import GoFeatures from './GoFeatures' @@ -11,19 +11,13 @@ import './go.css' /** The OLake Go product page: hero, problem, features, benchmarks, a closing CTA and the FAQ. */ export default function GoPage({ title, description }: { title: string; description: string }) { return ( - - {/* The negative top margin slides the page (and its grain) under the floating navbar, as the - other landing heroes do. Infima gives headings, paragraphs and lists bottom margins and - lists a left padding; the lakeside reset is zero-specificity and loses, so it is undone - here for the whole page. */} -
- - - - - - -
-
+ + + + + + + + ) } diff --git a/src/components/landing/go/go.css b/src/components/landing/go/go.css index 5bcc2b3a..0aa55adb 100644 --- a/src/components/landing/go/go.css +++ b/src/components/landing/go/go.css @@ -1,55 +1,9 @@ /* ============================================================================================= - * OLake Go page: its palette and the pieces Tailwind utilities do not express well (the paper - * grain, the sources marquee, the product mock-ups scaled to fit). Motion only runs for visitors - * without a reduced-motion preference and moves opacity or transform. + * OLake Go page: the sources marquee under the hero. The page's palette, grain and mock-up scaling + * are shared with the other product pages (../product/product.css). Motion only runs for visitors + * without a reduced-motion preference. * ========================================================================================== */ -.go-page { - /* The OLake Go design's blues, which the --olake-* tokens do not have. Its near-black, greys and - rules use the closest tokens; text greys that fail WCAG AA in the design (ACCESSIBILITY.md) use - --olake-muted on light surfaces and --olake-muted-on-dark / --olake-blue-on-dark on the dark one. */ - --go-blue: #0029ce; /* primary buttons */ - --go-blue-hover: #0022ad; - --go-blue-deep: #0c36bf; /* the feature card and the closing card */ - --go-mono: 'Geist Mono', var(--olake-font-mono); - - /* The design lays a faint monotone grain over the page (Figma noise, #ececE6 at up to 17%). */ - background-color: var(--olake-surface); - background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='g' x='0' y='0' color-interpolation-filters='sRGB'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 0.925 0 0 0 0 0.925 0 0 0 0 0.902 0 0 0 0.425 -0.1275'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23g)'/%3E%3C/svg%3E"); - background-size: 160px 160px; -} - -.go-mono { - font-family: var(--go-mono); -} - -/* ---- Product mock-ups ---------------------------------------------------------------------- - * The dashboard under the hero and the job editor in the feature card are drawn at their design - * size (--go-fit-w x --go-fit-h, unitless px) and scaled down to the width they are given, so they - * read as one picture at every width. tan(atan2(a, b)) is a / b as a plain number. The wrapper's - * position comes from its utility classes (MockFit makes it `relative`). */ -.go-fit { - container-type: inline-size; - max-width: calc(var(--go-fit-w) * 1px); - aspect-ratio: var(--go-fit-w) / var(--go-fit-h); -} - -.go-fit > * { - position: absolute; - top: 0; - left: 0; - width: calc(var(--go-fit-w) * 1px); - height: calc(var(--go-fit-h) * 1px); - transform-origin: 0 0; - transform: scale(tan(atan2(100cqw, calc(var(--go-fit-w) * 1px)))); -} - -/* The dashboard fades into the page from 293px down (design px, so it scales with the mock). */ -.go-dashboard-fade { - -webkit-mask-image: linear-gradient(to bottom, #000 293px, transparent 470.6px); - mask-image: linear-gradient(to bottom, #000 293px, transparent 470.6px); -} - /* ---- The "Available Sources" marquee -------------------------------------------------------- */ .go-marquee-track { display: flex; @@ -63,15 +17,6 @@ } } - @keyframes go-fade-in { - from { - opacity: 0; - } - to { - opacity: 1; - } - } - .go-marquee-track { animation: go-marquee 36s linear infinite; } @@ -79,9 +24,4 @@ .go-marquee:hover .go-marquee-track { animation-play-state: paused; } - - /* the feature card copy and opened FAQ answers fade in */ - .go-fade-in { - animation: go-fade-in 240ms var(--olake-ease-out) both; - } } diff --git a/src/components/landing/go/mock.tsx b/src/components/landing/go/mock.tsx deleted file mode 100644 index 8df0c0e4..00000000 --- a/src/components/landing/go/mock.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import React from 'react' -import { cn } from '@site/src/lib/utils' - -/** - * Shared pieces of the two product mock-ups (GoDashboardMock, GoFeatureMock). They are pictures of - * the OLake UI, so they keep the UI's own colours and type rather than the page tokens. - */ - -/** A frame's inside stroke, painted over its children the way the design draws it. Add the width and colour. */ -export const STROKE = - "after:pointer-events-none after:absolute after:inset-0 after:rounded-[inherit] after:border-solid after:content-['']" - -/** An exported icon from /img/landing/go at its own size (`h` defaults to `w`). */ -export function MockIcon({ - name, - w, - h = w, - className -}: { - name: string - w: number - h?: number - className?: string -}) { - return ( - - ) -} - -/** Wraps a mock drawn at `width` × `height` design px so it scales down to fit (`.go-fit` in go.css). */ -export function MockFit({ - width, - height, - className, - innerClassName, - children -}: { - width: number - height: number - className?: string - innerClassName?: string - children: React.ReactNode -}) { - return ( - - ) -} diff --git a/src/components/landing/product/BenchmarkShell.tsx b/src/components/landing/product/BenchmarkShell.tsx new file mode 100644 index 00000000..efd95650 --- /dev/null +++ b/src/components/landing/product/BenchmarkShell.tsx @@ -0,0 +1,118 @@ +import React from 'react' +import Link from '@docusaurus/Link' +import useDocusaurusContext from '@docusaurus/useDocusaurusContext' +import { cn } from '@site/src/lib/utils' + +/** Rules on the dark section. It goes first in cn(): a later border-0 would drop the side classes. */ +export const RULE = 'border-0 border-solid border-olake-line-dark' + +/** Focus ring for controls on the dark section. */ +export const FOCUS_ON_DARK = + 'focus-visible:rounded-[4px] focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-olake-blue-on-dark' + +/** The latest-release strip; the version comes from docusaurus.config.js. */ +function ReleaseStrip() { + const { siteConfig } = useDocusaurusContext() + const label = siteConfig.customFields?.latestOlakeReleaseLabel as string | undefined + const path = + (siteConfig.customFields?.latestOlakeReleasePath as string | undefined) || + '/docs/release/ingestion' + if (!label) return null + return ( +
+ Latest Release + We just released our {label} + + Read More + +
+ ) +} + +/** + * The dark, full-bleed benchmark section of a product page: a ruled column with (optionally) the + * latest-release strip, the eyebrow, title and summary, the page's comparison (`children`), the + * "how it does this" notes set in Geist Mono, and a bar linking to the detailed benchmarks. + */ +export default function BenchmarkShell({ + release, + eyebrow, + title, + summary, + how, + link, + children +}: { + /** Show the latest-release strip on top. */ + release?: boolean + eyebrow: string + title: string + summary: React.ReactNode + how: { title: string; body: React.ReactNode; className?: string } + link: { label: string; href: string } + children: React.ReactNode +}) { + return ( +
+
+ {release && } + +
+

+ {eyebrow} +

+

+ {title} +

+

+ {summary} +

+
+ + {children} + +
+

{how.title}

+
+ {how.body} +
+
+ + + {link.label} + + +
+
+ ) +} diff --git a/src/components/landing/product/ProductCta.tsx b/src/components/landing/product/ProductCta.tsx new file mode 100644 index 00000000..37524159 --- /dev/null +++ b/src/components/landing/product/ProductCta.tsx @@ -0,0 +1,57 @@ +import React from 'react' +import Button from '../ui/Button' +import { cn } from '@site/src/lib/utils' + +const BUTTON = 'h-[36px] rounded-[8px] px-[10px] text-[16px] leading-[1.3]' + +/** + * Closing call to action of a product page: a dark-blue card with the page's art rising behind the + * two buttons. `className` sets the card's background colour. + */ +export default function ProductCta({ + eyebrow, + title, + primary, + secondary, + art, + className +}: { + eyebrow: string + title: string + primary: { label: string; href: string } + secondary: { label: string; href: string } + art: React.ReactNode + className?: string +}) { + return ( +
+
+ {art} +

{eyebrow}

+

+ {title} +

+
+ + +
+
+
+ ) +} diff --git a/src/components/landing/product/ProductFaq.tsx b/src/components/landing/product/ProductFaq.tsx new file mode 100644 index 00000000..ac3b0cf9 --- /dev/null +++ b/src/components/landing/product/ProductFaq.tsx @@ -0,0 +1,92 @@ +import React, { useState } from 'react' +import { cn } from '@site/src/lib/utils' + +export interface ProductFaqItem { + q: string + a: string +} + +/** + * The FAQ card of a product page: accordion, one answer open at a time (the first one to start + * with). Answers stay in the DOM (hidden) so the text the page's FAQPage JSON-LD describes is + * always on the page. + */ +export default function ProductFaq({ + idPrefix, + eyebrow, + title, + faqs +}: { + /** Prefix of the question and answer ids, e.g. 'go-faq'. */ + idPrefix: string + eyebrow: string + title: string + faqs: ProductFaqItem[] +}) { + const [open, setOpen] = useState(0) + + return ( +
+
+
+

{eyebrow}

+

+ {title} +

+
+ +
    + {faqs.map((faq, i) => { + const isOpen = open === i + return ( +
  • +

    + +

    + +
  • + ) + })} +
+
+
+ ) +} diff --git a/src/components/landing/product/ProductFeatures.tsx b/src/components/landing/product/ProductFeatures.tsx new file mode 100644 index 00000000..1a1dbcc5 --- /dev/null +++ b/src/components/landing/product/ProductFeatures.tsx @@ -0,0 +1,148 @@ +import React, { useRef, useState } from 'react' +import { cn } from '@site/src/lib/utils' + +/** A capability behind the tab strip. `tab` is the pill label, `title` the card heading. */ +export interface ProductFeature { + id: string + tab: string + title: string + /** Line breaks ("\n") are kept. */ + body: string +} + +/** + * A tab's picture (one design frame): the background field, the glow at the bottom left, where the + * copy sits from lg up (its top, and the body's width) and the editor mock-up with its lg position. + */ +export interface ProductFeatureArt { + background: React.ReactNode + glow: React.ReactNode + copyTop: number + bodyWidth: number + Mock: React.ComponentType<{ className?: string }> + mockClassName: string +} + +/** + * "Features": a pill tab strip over the blue feature card. Each tab shows its own picture of the + * product; the heading and copy follow the selected capability. Arrow keys, Home and End move + * between tabs. + */ +export default function ProductFeatures({ + idPrefix, + intro, + features, + art +}: { + /** Prefix of the tab and panel ids, e.g. 'go-feature'. */ + idPrefix: string + intro: { eyebrow: string; title: string } + features: ProductFeature[] + art: Record +}) { + const [active, setActive] = useState(0) + const tabRefs = useRef<(HTMLButtonElement | null)[]>([]) + + const select = (index: number) => { + setActive(index) + tabRefs.current[index]?.focus() + } + + const onKeyDown = (event: React.KeyboardEvent, index: number) => { + const last = features.length - 1 + if (event.key === 'ArrowRight') select(index === last ? 0 : index + 1) + else if (event.key === 'ArrowLeft') select(index === 0 ? last : index - 1) + else if (event.key === 'Home') select(0) + else if (event.key === 'End') select(last) + else return + event.preventDefault() + } + + const feature = features[active] + const frame = art[feature.id] + const panelId = `${idPrefix}-panel` + + return ( +
+
+
+

+ {intro.eyebrow} +

+

+ {intro.title} +

+
+ +
+ {features.map((item, i) => { + const selected = i === active + return ( + + ) + })} +
+ +
+ {frame.background} + {frame.glow} + +
+

+ {feature.title} +

+

+ {feature.body} +

+
+ + +
+
+
+ ) +} diff --git a/src/components/landing/product/ProductHero.tsx b/src/components/landing/product/ProductHero.tsx new file mode 100644 index 00000000..e941c54b --- /dev/null +++ b/src/components/landing/product/ProductHero.tsx @@ -0,0 +1,88 @@ +import React from 'react' +import Button from '../ui/Button' +import { cn } from '@site/src/lib/utils' + +export interface ProductHeroCopy { + eyebrow: string + headline: string + body: string + /** The GitHub repository (opens in a new tab) and the docs. */ + primary: { label: string; href: string } + secondary: { label: string; href: string } +} + +const BUTTON = 'h-[36px] px-[10px] text-[16px] leading-[1.3]' + +/** + * Hero of a product page: the page's background art, the eyebrow, headline and copy, the GitHub + * and docs buttons, then `children` (the product shot). `className` sets the top padding from lg + * up, which differs per design. + */ +export default function ProductHero({ + copy, + background, + className, + children +}: { + copy: ProductHeroCopy + background: React.ReactNode + className?: string + children?: React.ReactNode +}) { + return ( +
+ {background} + +
+
+

+ {copy.eyebrow} +

+

+ {copy.headline} +

+

+ {copy.body} +

+
+ + +
+
+ + {children} +
+
+ ) +} diff --git a/src/components/landing/product/ProductPage.tsx b/src/components/landing/product/ProductPage.tsx new file mode 100644 index 00000000..48f5b839 --- /dev/null +++ b/src/components/landing/product/ProductPage.tsx @@ -0,0 +1,31 @@ +import React from 'react' +import LakesidePage from '../ui/LakesidePage' +import './product.css' + +/** + * Shell of a product page (OLake Go, OLake Fusion): the Lakeside page with its floating navbar and + * footer, and the product palette and paper grain over everything between them. + */ +export default function ProductPage({ + title, + description, + activePath, + children +}: { + title: string + description: string + activePath: string + children: React.ReactNode +}) { + return ( + + {/* The negative top margin slides the page (and its grain) under the floating navbar, as the + other landing heroes do. Infima gives headings, paragraphs and lists bottom margins and + lists a left padding; the lakeside reset is zero-specificity and loses, so it is undone + here for the whole page. */} +
+ {children} +
+
+ ) +} diff --git a/src/components/landing/product/mock.tsx b/src/components/landing/product/mock.tsx new file mode 100644 index 00000000..92f25c44 --- /dev/null +++ b/src/components/landing/product/mock.tsx @@ -0,0 +1,120 @@ +import React from 'react' +import { cn } from '@site/src/lib/utils' + +/** + * Shared pieces of the product mock-ups (the hero dashboards and the feature-card editors of the + * OLake Go and OLake Fusion pages). They are pictures of the OLake UI, so they keep the UI's own + * colours and type rather than the page tokens. + */ + +/** A frame's inside stroke, painted over its children the way the design draws it. Add the width and colour. */ +export const STROKE = + "after:pointer-events-none after:absolute after:inset-0 after:rounded-[inherit] after:border-solid after:content-['']" + +/** + * An exported icon at its own size (`h` defaults to `w`). `name` is its path under /img/landing + * without the extension, e.g. `go/dashboard-plus` or `product/mock-sidebar-help`. + */ +export function MockIcon({ + name, + w, + h = w, + className +}: { + name: string + w: number + h?: number + className?: string +}) { + return ( + + ) +} + +/** + * Wraps a mock-up drawn at `width` × `height` design px so it scales down to fit (`.product-fit`). + * `fade` fades it out between two depths in design px, as under the hero heading. + */ +export function MockFit({ + width, + height, + fade, + className, + innerClassName, + children +}: { + width: number + height: number + fade?: [number, number] + className?: string + innerClassName?: string + children: React.ReactNode +}) { + return ( + + ) +} + +/** The mock-ups' collapsed navigation rail (10.8px icons), as drawn in the feature-card editors. */ +export function MockRail({ + className, + activeIndex = 0 +}: { + className?: string + activeIndex?: 0 | 1 +}) { + const box = 'flex w-full items-center overflow-hidden rounded-[3.241px] p-[2.161px]' + return ( +
+
+ +
+ + + + + + + + +
+
+
+ ) +} diff --git a/src/components/landing/product/product.css b/src/components/landing/product/product.css new file mode 100644 index 00000000..7c5f9f12 --- /dev/null +++ b/src/components/landing/product/product.css @@ -0,0 +1,68 @@ +/* ============================================================================================= + * The product pages (OLake Go, OLake Fusion): their palette and the pieces Tailwind utilities do + * not express well (the paper grain, the product mock-ups scaled to fit, the fade under the hero + * mock-up). Motion only runs for visitors without a reduced-motion preference. + * ========================================================================================== */ + +.product-page { + /* The product designs' blues, which the --olake-* tokens do not have. Their near-black, greys and + rules use the closest tokens; text greys that fail WCAG AA in the designs (ACCESSIBILITY.md) use + --olake-muted on light surfaces and --olake-muted-on-dark / --olake-blue-on-dark on dark ones. */ + --product-blue: #0029ce; /* primary buttons */ + --product-blue-hover: #0022ad; + --product-blue-deep: #0c36bf; /* the feature cards and OLake Go's closing card */ + --product-mono: 'Geist Mono', var(--olake-font-mono); + + /* The designs lay a faint monotone grain over the page (Figma noise, #ececE6 at up to 17%). */ + background-color: var(--olake-surface); + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='g' x='0' y='0' color-interpolation-filters='sRGB'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 0.925 0 0 0 0 0.925 0 0 0 0 0.902 0 0 0 0.425 -0.1275'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23g)'/%3E%3C/svg%3E"); + background-size: 160px 160px; +} + +.product-mono { + font-family: var(--product-mono); +} + +/* ---- Product mock-ups ---------------------------------------------------------------------- + * The dashboards under the heroes and the editors in the feature cards are drawn at their design + * size (--fit-w x --fit-h, unitless px) and scaled down to the width they are given, so they read + * as one picture at every width. tan(atan2(a, b)) is a / b as a plain number. The wrapper's + * position comes from its utility classes (MockFit makes it `relative`). */ +.product-fit { + container-type: inline-size; + max-width: calc(var(--fit-w) * 1px); + aspect-ratio: var(--fit-w) / var(--fit-h); +} + +.product-fit > * { + position: absolute; + top: 0; + left: 0; + width: calc(var(--fit-w) * 1px); + height: calc(var(--fit-h) * 1px); + transform-origin: 0 0; + transform: scale(tan(atan2(100cqw, calc(var(--fit-w) * 1px)))); +} + +/* The hero mock-up fades into the page between --fade-from and --fade-to (design px, so it scales + with the mock-up). */ +.product-fade { + -webkit-mask-image: linear-gradient(to bottom, #000 var(--fade-from), transparent var(--fade-to)); + mask-image: linear-gradient(to bottom, #000 var(--fade-from), transparent var(--fade-to)); +} + +@media (prefers-reduced-motion: no-preference) { + @keyframes product-fade-in { + from { + opacity: 0; + } + to { + opacity: 1; + } + } + + /* the feature card copy and opened FAQ answers fade in */ + .product-fade-in { + animation: product-fade-in 240ms var(--olake-ease-out) both; + } +} diff --git a/src/data/landing/go/benchmark.ts b/src/data/landing/go/benchmark.ts index 059f4357..fc9d1135 100644 --- a/src/data/landing/go/benchmark.ts +++ b/src/data/landing/go/benchmark.ts @@ -77,10 +77,3 @@ export const GO_BENCHMARK_HOW = { } ] } - -/** The release strip on top of the section; the version comes from docusaurus.config.js. */ -export const GO_RELEASE_STRIP = { - eyebrow: 'Latest Release', - lead: 'We just released our', - cta: 'Read More' -} diff --git a/static/img/landing/go/faq-arrow-closed.svg b/static/img/landing/product/faq-arrow-closed.svg similarity index 100% rename from static/img/landing/go/faq-arrow-closed.svg rename to static/img/landing/product/faq-arrow-closed.svg diff --git a/static/img/landing/go/faq-arrow-open.svg b/static/img/landing/product/faq-arrow-open.svg similarity index 100% rename from static/img/landing/go/faq-arrow-open.svg rename to static/img/landing/product/faq-arrow-open.svg diff --git a/static/img/landing/go/icon-github.svg b/static/img/landing/product/icon-github.svg similarity index 100% rename from static/img/landing/go/icon-github.svg rename to static/img/landing/product/icon-github.svg diff --git a/static/img/landing/go/benchmark-arrow-square-out.svg b/static/img/landing/product/link-arrow-square-out.svg similarity index 100% rename from static/img/landing/go/benchmark-arrow-square-out.svg rename to static/img/landing/product/link-arrow-square-out.svg diff --git a/static/img/landing/go/feature-mock-sidebar-help.svg b/static/img/landing/product/mock-sidebar-help.svg similarity index 100% rename from static/img/landing/go/feature-mock-sidebar-help.svg rename to static/img/landing/product/mock-sidebar-help.svg diff --git a/static/img/landing/go/feature-mock-sidebar-jobs.svg b/static/img/landing/product/mock-sidebar-jobs.svg similarity index 100% rename from static/img/landing/go/feature-mock-sidebar-jobs.svg rename to static/img/landing/product/mock-sidebar-jobs.svg diff --git a/static/img/landing/go/feature-mock-sidebar-settings.svg b/static/img/landing/product/mock-sidebar-settings.svg similarity index 100% rename from static/img/landing/go/feature-mock-sidebar-settings.svg rename to static/img/landing/product/mock-sidebar-settings.svg diff --git a/static/img/landing/go/feature-mock-sidebar-sparkle.svg b/static/img/landing/product/mock-sidebar-sparkle.svg similarity index 100% rename from static/img/landing/go/feature-mock-sidebar-sparkle.svg rename to static/img/landing/product/mock-sidebar-sparkle.svg diff --git a/static/img/landing/go/feature-mock-sidebar-toggle.svg b/static/img/landing/product/mock-sidebar-toggle.svg similarity index 100% rename from static/img/landing/go/feature-mock-sidebar-toggle.svg rename to static/img/landing/product/mock-sidebar-toggle.svg From 0a5b6ddc0e9e471f29a56433a3db18cdd15052e0 Mon Sep 17 00:00:00 2001 From: Krishna Jain Date: Tue, 13 Oct 2026 11:43:49 +0530 Subject: [PATCH 07/28] feat(olake-fusion): rebuild the Fusion page from the new design Rebuild /olake-fusion on the shared product page blocks used by OLake Go: - Hero with the contour rings, the Run History dashboard and the lifted run-metrics card; on narrower screens the whole picture is fitted so the card is not cut off. The PostHog A/B headline is kept. - Problem Statement as a ruled column with the four line drawings. - Features: Tiered Compaction, Omit Decay, Easy Configuration and Self-Hosted, each with its own product mock-up. - Benchmarks table against Spark compaction and the "How OLake Fusion does this" notes; the table fits phone widths without side scrolling. - Get in touch CTA and the FAQ (existing answers kept for the JSON-LD). ProductFeatures takes a className for the section's spacing. The old Fusion components, fusion.css and the shared images only they used are removed. Copy fixes from the design: Maintenance, Self-Hosted, "10x simpler" for the parameter count, and "Status" for the deployment state. --- src/components/landing/fusion/Benchmark.tsx | 165 ----- src/components/landing/fusion/Faq.tsx | 63 -- .../landing/fusion/FeatureVisual.tsx | 101 --- src/components/landing/fusion/Features.tsx | 168 ----- .../landing/fusion/FusionBenchmark.tsx | 133 ++++ src/components/landing/fusion/FusionCta.tsx | 55 +- .../landing/fusion/FusionDashboardMock.tsx | 402 +++++++++++ src/components/landing/fusion/FusionFaq.tsx | 8 + .../landing/fusion/FusionFeaturePanels.tsx | 671 ++++++++++++++++++ .../landing/fusion/FusionFeatures.tsx | 82 +++ src/components/landing/fusion/FusionHero.tsx | 49 ++ src/components/landing/fusion/FusionPage.tsx | 31 + .../landing/fusion/FusionProblem.tsx | 168 +++++ src/components/landing/fusion/Hero.tsx | 35 - src/components/landing/fusion/ProductFlow.tsx | 104 --- src/components/landing/fusion/SilentTax.tsx | 96 --- src/components/landing/fusion/fusion.css | 250 ------- .../landing/product/ProductFeatures.tsx | 10 +- src/data/landing/fusion/benchmark.ts | 58 +- src/data/landing/fusion/cta.ts | 9 +- src/data/landing/fusion/faqs.ts | 4 +- src/data/landing/fusion/features.ts | 63 +- src/data/landing/fusion/hero.ts | 20 +- src/data/landing/fusion/problems.ts | 50 +- src/pages/olake-fusion.tsx | 30 +- static/img/landing/fusion/cta-glow.svg | 12 + .../img/landing/fusion/cta-quantity-skew.svg | 238 +++++++ .../landing/fusion/dashboard-caret-open.svg | 5 + .../landing/fusion/dashboard-caret-system.svg | 5 + static/img/landing/fusion/dashboard-caret.svg | 5 + .../landing/fusion/dashboard-nav-catalogs.svg | 5 + .../landing/fusion/dashboard-nav-compute.svg | 5 + .../img/landing/fusion/dashboard-nav-help.svg | 5 + .../fusion/dashboard-nav-ingestion.svg | 5 + .../fusion/dashboard-nav-optimisation.svg | 5 + .../landing/fusion/dashboard-nav-settings.svg | 5 + .../landing/fusion/dashboard-nav-tables.svg | 5 + .../img/landing/fusion/dashboard-search.svg | 5 + .../fusion/dashboard-sidebar-toggle.svg | 5 + .../fusion/dashboard-status-failed.svg | 5 + .../fusion/dashboard-status-running.svg | 5 + .../fusion/dashboard-status-success.svg | 5 + .../img/landing/fusion/feature-background.png | Bin 0 -> 182351 bytes .../landing/fusion/feature-config-caret.svg | 5 + .../landing/fusion/feature-config-info.svg | 13 + .../fusion/feature-config-question.svg | 13 + .../landing/fusion/feature-config-select.svg | 5 + .../landing/fusion/feature-config-table.svg | 5 + .../landing/fusion/feature-decay-active.svg | 5 + .../landing/fusion/feature-decay-banner.svg | 5 + .../img/landing/fusion/feature-decay-done.svg | 5 + static/img/landing/fusion/feature-glow.svg | 12 + .../landing/fusion/feature-tables-actions.svg | 5 + .../landing/fusion/feature-tables-arrow.svg | 5 + .../fusion/feature-tables-dropdown.svg | 5 + .../landing/fusion/feature-tables-failed.svg | 5 + .../landing/fusion/feature-tables-running.svg | 5 + .../landing/fusion/feature-tables-success.svg | 5 + .../landing/fusion/feature-tiers-failed.svg | 5 + .../landing/fusion/feature-tiers-running.svg | 5 + .../landing/fusion/feature-tiers-success.svg | 5 + static/img/landing/fusion/hero-contours.svg | 8 + static/img/landing/fusion/hero-glow.svg | 12 + .../fusion/problem-compaction-debugging.svg | 133 ++++ .../fusion/problem-invisible-costs.svg | 33 + .../landing/fusion/problem-slower-queries.svg | 8 + .../landing/fusion/problem-small-files.svg | 11 + .../landing/shared/benchmark-info-icon.webp | Bin 4042 -> 0 bytes .../img/landing/shared/iceberg-backdrop.webp | Bin 11870 -> 0 bytes static/img/landing/shared/iceberg-icon.webp | Bin 7368 -> 0 bytes 70 files changed, 2318 insertions(+), 1150 deletions(-) delete mode 100644 src/components/landing/fusion/Benchmark.tsx delete mode 100644 src/components/landing/fusion/Faq.tsx delete mode 100644 src/components/landing/fusion/FeatureVisual.tsx delete mode 100644 src/components/landing/fusion/Features.tsx create mode 100644 src/components/landing/fusion/FusionBenchmark.tsx create mode 100644 src/components/landing/fusion/FusionDashboardMock.tsx create mode 100644 src/components/landing/fusion/FusionFaq.tsx create mode 100644 src/components/landing/fusion/FusionFeaturePanels.tsx create mode 100644 src/components/landing/fusion/FusionFeatures.tsx create mode 100644 src/components/landing/fusion/FusionHero.tsx create mode 100644 src/components/landing/fusion/FusionPage.tsx create mode 100644 src/components/landing/fusion/FusionProblem.tsx delete mode 100644 src/components/landing/fusion/Hero.tsx delete mode 100644 src/components/landing/fusion/ProductFlow.tsx delete mode 100644 src/components/landing/fusion/SilentTax.tsx delete mode 100644 src/components/landing/fusion/fusion.css create mode 100644 static/img/landing/fusion/cta-glow.svg create mode 100644 static/img/landing/fusion/cta-quantity-skew.svg create mode 100644 static/img/landing/fusion/dashboard-caret-open.svg create mode 100644 static/img/landing/fusion/dashboard-caret-system.svg create mode 100644 static/img/landing/fusion/dashboard-caret.svg create mode 100644 static/img/landing/fusion/dashboard-nav-catalogs.svg create mode 100644 static/img/landing/fusion/dashboard-nav-compute.svg create mode 100644 static/img/landing/fusion/dashboard-nav-help.svg create mode 100644 static/img/landing/fusion/dashboard-nav-ingestion.svg create mode 100644 static/img/landing/fusion/dashboard-nav-optimisation.svg create mode 100644 static/img/landing/fusion/dashboard-nav-settings.svg create mode 100644 static/img/landing/fusion/dashboard-nav-tables.svg create mode 100644 static/img/landing/fusion/dashboard-search.svg create mode 100644 static/img/landing/fusion/dashboard-sidebar-toggle.svg create mode 100644 static/img/landing/fusion/dashboard-status-failed.svg create mode 100644 static/img/landing/fusion/dashboard-status-running.svg create mode 100644 static/img/landing/fusion/dashboard-status-success.svg create mode 100644 static/img/landing/fusion/feature-background.png create mode 100644 static/img/landing/fusion/feature-config-caret.svg create mode 100644 static/img/landing/fusion/feature-config-info.svg create mode 100644 static/img/landing/fusion/feature-config-question.svg create mode 100644 static/img/landing/fusion/feature-config-select.svg create mode 100644 static/img/landing/fusion/feature-config-table.svg create mode 100644 static/img/landing/fusion/feature-decay-active.svg create mode 100644 static/img/landing/fusion/feature-decay-banner.svg create mode 100644 static/img/landing/fusion/feature-decay-done.svg create mode 100644 static/img/landing/fusion/feature-glow.svg create mode 100644 static/img/landing/fusion/feature-tables-actions.svg create mode 100644 static/img/landing/fusion/feature-tables-arrow.svg create mode 100644 static/img/landing/fusion/feature-tables-dropdown.svg create mode 100644 static/img/landing/fusion/feature-tables-failed.svg create mode 100644 static/img/landing/fusion/feature-tables-running.svg create mode 100644 static/img/landing/fusion/feature-tables-success.svg create mode 100644 static/img/landing/fusion/feature-tiers-failed.svg create mode 100644 static/img/landing/fusion/feature-tiers-running.svg create mode 100644 static/img/landing/fusion/feature-tiers-success.svg create mode 100644 static/img/landing/fusion/hero-contours.svg create mode 100644 static/img/landing/fusion/hero-glow.svg create mode 100644 static/img/landing/fusion/problem-compaction-debugging.svg create mode 100644 static/img/landing/fusion/problem-invisible-costs.svg create mode 100644 static/img/landing/fusion/problem-slower-queries.svg create mode 100644 static/img/landing/fusion/problem-small-files.svg delete mode 100644 static/img/landing/shared/benchmark-info-icon.webp delete mode 100644 static/img/landing/shared/iceberg-backdrop.webp delete mode 100644 static/img/landing/shared/iceberg-icon.webp diff --git a/src/components/landing/fusion/Benchmark.tsx b/src/components/landing/fusion/Benchmark.tsx deleted file mode 100644 index 60c3545d..00000000 --- a/src/components/landing/fusion/Benchmark.tsx +++ /dev/null @@ -1,165 +0,0 @@ -import React, { useState } from 'react' -import Link from '@docusaurus/Link' -import { PiArrowRight, PiCaretDown } from 'react-icons/pi' -import Section from '../ui/Section' -import SectionHeading from '../ui/SectionHeading' -import { BENCHMARK, BENCHMARK_INFO } from '@site/src/data/landing/fusion/benchmark' -import { cn } from '@site/src/lib/utils' - -const FOCUS = - 'focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-olake-blue' -const RULE = 'border-0 border-r border-solid border-olake-line-rule bg-transparent' - -/** The "How Fusion does this" disclosure under the table. */ -function HowFusionDoesThis() { - const [open, setOpen] = useState(false) - return ( -
-

- -

- {open && ( -
    - {BENCHMARK_INFO.items.map((item) => ( -
  • - {item.term} — {item.text} -
  • - ))} -
- )} -
- ) -} - -export default function Benchmark() { - const b = BENCHMARK - return ( -
- - {b.lead.before} - {b.lead.boldSpeed} - {b.lead.middle} - {b.lead.boldCost} - {b.lead.after} - - } - /> - -
- - - - - - - - - - {b.rows.map((row, i) => { - const last = i === b.rows.length - 1 - return ( - - - - - - ) - })} - -
- {b.headers.metric} - - {b.headers.spark} - - {b.headers.fusionBrand} - {b.headers.fusionProduct} -
- {row.metric} - - {row.spark} - - - {row.fusion} - - {row.delta} - - -
-
- - - - - {b.link.label} -
- ) -} diff --git a/src/components/landing/fusion/Faq.tsx b/src/components/landing/fusion/Faq.tsx deleted file mode 100644 index 65182214..00000000 --- a/src/components/landing/fusion/Faq.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import React, { useState } from 'react' -import { PiCaretDown } from 'react-icons/pi' -import Section from '../ui/Section' -import SectionHeading from '../ui/SectionHeading' -import { FAQS, FAQ_TITLE } from '@site/src/data/landing/fusion/faqs' -import { cn } from '@site/src/lib/utils' - -const FOCUS = - 'focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-olake-blue' - -/** One-open-at-a-time accordion. Answers stay in the DOM (hidden) so they remain readable to crawlers. */ -export default function Faq() { - const [open, setOpen] = useState(-1) - return ( -
-
- -
    - {FAQS.map((faq, i) => { - const isOpen = open === i - return ( -
  • -

    - -

    - -
  • - ) - })} -
-
-
- ) -} diff --git a/src/components/landing/fusion/FeatureVisual.tsx b/src/components/landing/fusion/FeatureVisual.tsx deleted file mode 100644 index 7b0e8ed5..00000000 --- a/src/components/landing/fusion/FeatureVisual.tsx +++ /dev/null @@ -1,101 +0,0 @@ -import React from 'react' -import { - CONFIG_COMPARE, - DECAY_LABELS, - HOSTS, - TIERS, - type FeatureKind -} from '@site/src/data/landing/fusion/features' -import { cn } from '@site/src/lib/utils' - -/** Fragment sizes (px) for the "fragmented files" cluster. */ -const FRAGMENTS = [14, 10, 16, 9, 13, 11, 15, 8] -const TIER_FILL = ['bg-olake-blue/40', 'bg-olake-blue/70', 'bg-olake-blue'] -const CAPTION = 'text-[11px] text-olake-muted' - -const Tiered = () => ( -
    - {TIERS.map((tier, i) => ( -
  • - {tier.label} - - - - {tier.note} -
  • - ))} -
-) - -const Decay = () => ( -
-
-
- {FRAGMENTS.map((size, i) => ( - - ))} -
-

{DECAY_LABELS.before}

-
- -
-
- - -
-

{DECAY_LABELS.after}

-
-
-) - -const Config = () => ( -
-
-

{CONFIG_COMPARE.fusion.value}

-

{CONFIG_COMPARE.fusion.label}

-
-
-

{CONFIG_COMPARE.spark.value}

-

{CONFIG_COMPARE.spark.label}

-
-
-) - -const Hosted = () => ( -
    - {HOSTS.map((host) => ( -
  • -
  • - ))} -
-) - -const VISUALS: Record = { - tiered: Tiered, - decay: Decay, - config: Config, - hosted: Hosted -} - -/** The small product mock-up above each feature's copy. Values are the page's sample content. */ -export default function FeatureVisual({ kind }: { kind: FeatureKind }) { - const Visual = VISUALS[kind] - return ( -
-
- -
-
- ) -} diff --git a/src/components/landing/fusion/Features.tsx b/src/components/landing/fusion/Features.tsx deleted file mode 100644 index a3471e5d..00000000 --- a/src/components/landing/fusion/Features.tsx +++ /dev/null @@ -1,168 +0,0 @@ -import React, { useCallback, useEffect, useRef, useState } from 'react' -import Section from '../ui/Section' -import SectionHeading from '../ui/SectionHeading' -import Card from '../ui/Card' -import FeatureVisual from './FeatureVisual' -import { - FEATURES, - FEATURES_EYEBROW, - FEATURES_TITLE, - FEATURE_STEPS, - FEATURE_TICK_MS -} from '@site/src/data/landing/fusion/features' -import { cn } from '@site/src/lib/utils' - -const FOCUS = - 'focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-olake-blue' - -/** - * Feature selector with auto-advance: the active feature's underline fills over 10 s and then the - * next feature takes over. The rotation starts once the section is 40% visible and stops for good - * when a visitor picks a feature (or when they prefer reduced motion). - */ -function useFeatureRotation(count: number) { - const [active, setActive] = useState(0) - const [progress, setProgress] = useState(0) - const [paused, setPaused] = useState(false) - const [started, setStarted] = useState(false) - const sectionRef = useRef(null) - - useEffect(() => { - if (window.matchMedia?.('(prefers-reduced-motion: reduce)').matches) return - const el = sectionRef.current - if (!el || !('IntersectionObserver' in window)) { - setStarted(true) - return - } - const observer = new IntersectionObserver( - (entries) => { - if (entries[0].isIntersecting) { - setStarted(true) - observer.disconnect() - } - }, - { threshold: 0.4 } - ) - observer.observe(el) - return () => observer.disconnect() - }, []) - - useEffect(() => { - if (!started || paused) return - const timer = setInterval(() => { - setProgress((p) => { - if (p + 1 >= FEATURE_STEPS) { - setActive((a) => (a + 1) % count) - return 0 - } - return p + 1 - }) - }, FEATURE_TICK_MS) - return () => clearInterval(timer) - }, [started, paused, count]) - - const select = useCallback((i: number) => { - setActive(i) - setProgress(FEATURE_STEPS) - setPaused(true) - }, []) - - return { active, progress, select, sectionRef } -} - -export default function Features() { - const { active, progress, select, sectionRef } = useFeatureRotation(FEATURES.length) - const tabRefs = useRef<(HTMLButtonElement | null)[]>([]) - - const onKeyDown = (e: React.KeyboardEvent) => { - const last = FEATURES.length - 1 - let next = -1 - if (e.key === 'ArrowDown' || e.key === 'ArrowRight') next = active === last ? 0 : active + 1 - else if (e.key === 'ArrowUp' || e.key === 'ArrowLeft') next = active === 0 ? last : active - 1 - else if (e.key === 'Home') next = 0 - else if (e.key === 'End') next = last - if (next < 0) return - e.preventDefault() - select(next) - tabRefs.current[next]?.focus() - } - - return ( -
-
- - -
-
- {FEATURES.map((feature, i) => { - const selected = i === active - return ( - - ) - })} -
- - {/* Every panel sits in the same grid cell so the card keeps the height of the tallest one. */} -
- {FEATURES.map((feature, i) => ( -
- - -
-

- {feature.title} -

-

- {feature.body} -

-
-
-
- ))} -
-
-
-
- ) -} diff --git a/src/components/landing/fusion/FusionBenchmark.tsx b/src/components/landing/fusion/FusionBenchmark.tsx new file mode 100644 index 00000000..4e747d35 --- /dev/null +++ b/src/components/landing/fusion/FusionBenchmark.tsx @@ -0,0 +1,133 @@ +import React from 'react' +import { cn } from '@site/src/lib/utils' +import BenchmarkShell, { RULE } from '../product/BenchmarkShell' +import { BENCHMARK, BENCHMARK_HOW } from '@site/src/data/landing/fusion/benchmark' + +/** Column widths of the design's table (254, 380 and 367 of its 1001px). */ +/** Column widths: the design's from sm up, a wider label column on phones so the table fits. */ +const COLUMNS = ['w-[34%] sm:w-[25.37%]', 'w-[36%] sm:w-[37.96%]', 'w-[30%] sm:w-[36.67%]'] + +/** "Benchmarks" of the OLake Fusion page: Fusion against Spark compaction on the same workload. */ +export default function FusionBenchmark() { + const { headers } = BENCHMARK + return ( + ( + + {part.text} + + ))} + how={{ + title: BENCHMARK_HOW.title, + className: 'lg:pt-[38px] lg:pb-[62px]', + body: ( +
+

{BENCHMARK_HOW.lead}

+
    + {BENCHMARK_HOW.tiers.map((tier) => ( +
  • + {tier.name} {tier.text} +
  • + ))} +
+
+ {BENCHMARK_HOW.outro.map((line) => ( +

{line}

+ ))} +
+
+ ) + }} + link={BENCHMARK.link} + > +
+

+ {BENCHMARK.caption} +

+
+ + + {COLUMNS.map((width) => ( + + ))} + + + + + + + + + + {BENCHMARK.rows.map((row, r) => { + const cell = 'px-[8px] pt-[22px] align-top text-[14px] leading-[1.3]' + return ( + + + + + + ) + })} + +
+ {headers.metric} + + + {headers.fusionBrand}{' '} + {headers.fusionProduct} + + + {headers.fusionSub} + + + {headers.spark} +
+ {row.metric} + + + {row.fusion} + + {row.delta} + + + + {row.spark} +
+
+
+
+ ) +} diff --git a/src/components/landing/fusion/FusionCta.tsx b/src/components/landing/fusion/FusionCta.tsx index 113a55e2..188103fc 100644 --- a/src/components/landing/fusion/FusionCta.tsx +++ b/src/components/landing/fusion/FusionCta.tsx @@ -1,31 +1,38 @@ import React from 'react' -import Button from '../ui/Button' -import Section from '../ui/Section' +import ProductCta from '../product/ProductCta' import { FUSION_CTA } from '@site/src/data/landing/fusion/cta' -/** Closing call to action: headline and button beside the iceberg picture. */ +const IMG = '/img/landing/fusion' + +/** Closing call to action of the OLake Fusion page: the navy card with skewed data points under the buttons. */ export default function FusionCta() { return ( -
-
-
-

- {FUSION_CTA.title} -

- -
- -
-
+ + {/* A navy glow that softens the points under the heading */} + + + + } + /> ) } diff --git a/src/components/landing/fusion/FusionDashboardMock.tsx b/src/components/landing/fusion/FusionDashboardMock.tsx new file mode 100644 index 00000000..02d725cd --- /dev/null +++ b/src/components/landing/fusion/FusionDashboardMock.tsx @@ -0,0 +1,402 @@ +import React from 'react' +import { cn } from '@site/src/lib/utils' +import { MockIcon, STROKE } from '../product/mock' + +/** + * The OLake Fusion "Run History" screen under the hero with the run-metrics card lifted over its + * right edge, drawn at the design size (1018 px wide, 510 px showing) and scaled to fit. The card + * reaches 75px past the screen, so the fade wraps both at 1093px. Where the page is too narrow for + * the card to hang past the centred screen (below 1200px), the whole 1093px picture is fitted + * instead. A picture of the product, hidden from assistive tech. + */ + +const Icon = ({ name, ...rest }: React.ComponentProps) => ( + +) + +const MUTED = 'text-[rgba(0,0,0,0.65)]' + +/** A sidebar entry: icon, label and an optional trailing node. */ +const NavItem = ({ + icon, + label, + trailing, + className +}: { + icon: string + label: React.ReactNode + trailing?: React.ReactNode + className?: string +}) => ( +
+ + + + {label} + + + {trailing} +
+) + +/** The caret drawn pointing right (the design rotates its down caret a quarter turn). */ +const CaretRight = ({ name = 'dashboard-caret' }: { name?: string }) => ( + + + +) + +const STATUS = { + success: { + label: 'Success', + icon: 'dashboard-status-success', + className: 'bg-[#f6ffed] text-[#52c41a]', + left: 103.23 + }, + running: { + label: 'Running', + icon: 'dashboard-status-running', + className: 'bg-[#fff7e6] text-[#fa8c16]', + left: 103.93 + }, + failed: { + label: 'Failed', + icon: 'dashboard-status-failed', + className: 'bg-[#fff1f0] text-[#f5222d]', + left: 108.18 + } +} as const + +const RUNS: { + id: string + status: keyof typeof STATUS + type: string + start: string + duration: string +}[] = [ + { id: '005/261', status: 'success', type: 'Minor', start: '11:15:00', duration: '25 Mins' }, + { id: '001/261', status: 'running', type: 'Major', start: '15:10:00', duration: '32 Mins' }, + { id: '004/261', status: 'failed', type: 'Major', start: '12:00:00', duration: '30 Mins' } +] + +/** Header cells: the first sits at its left edge, the others are centred on their x. */ +const COLUMNS: [string, number][] = [ + ['Status', 131.07], + ['Type', 248.48], + ['Start Time', 366.95], + ['Duration', 497.07], + ['Metrics', 615.98], + ['Logs', 735.41] +] + +const CELL = + 'absolute top-[19.81px] -translate-y-1/2 text-[9.898px] leading-[16.969px] whitespace-nowrap text-[rgba(0,0,0,0.88)]' + +const ViewButton = ({ left }: { left: number }) => ( + + + View + + +) + +/** The run-metrics card. Values are right-aligned on a common edge, as the design sets them. */ +const METRICS: { + label: string + value: string + labelTop: number + valueTop: number +}[] = [ + { label: 'Input Data Files', value: '06', labelTop: 108.9, valueTop: 107.43 }, + { + label: 'Input Data Size', + value: '35.9 GB', + labelTop: 155.99, + valueTop: 150.26 + }, + { + label: 'Input Equality Delete Files', + value: '01', + labelTop: 203.08, + valueTop: 200.26 + }, + { + label: 'Output Data Files', + value: '03', + labelTop: 250.17, + valueTop: 248.26 + }, + { + label: 'Output Data Size', + value: '28.9 GB', + labelTop: 299.17, + valueTop: 296.26 + } +] + +function RunMetricsCard() { + return ( +
+
+

+ Run Metrics for Run ID 005/261 +

+

+ View run metrics for the last run 2 days ago +

+
+ {[82.41, 135.39, 188.37, 235.46, 282.55, 331.55].map((top) => ( + + ))} + {METRICS.map((m) => ( + + + {m.label} + + + {m.value} + + + ))} +
+ ) +} + +export default function FusionDashboardMock() { + return ( + + ) +} diff --git a/src/components/landing/fusion/FusionFaq.tsx b/src/components/landing/fusion/FusionFaq.tsx new file mode 100644 index 00000000..0bea7194 --- /dev/null +++ b/src/components/landing/fusion/FusionFaq.tsx @@ -0,0 +1,8 @@ +import React from 'react' +import ProductFaq from '../product/ProductFaq' +import { FAQS, FAQ_EYEBROW, FAQ_TITLE } from '@site/src/data/landing/fusion/faqs' + +/** The OLake Fusion FAQ. Its questions are mirrored word for word by the FAQPage JSON-LD in seo.ts. */ +export default function FusionFaq() { + return +} diff --git a/src/components/landing/fusion/FusionFeaturePanels.tsx b/src/components/landing/fusion/FusionFeaturePanels.tsx new file mode 100644 index 00000000..adf9d925 --- /dev/null +++ b/src/components/landing/fusion/FusionFeaturePanels.tsx @@ -0,0 +1,671 @@ +import React from 'react' +import { cn } from '@site/src/lib/utils' +import { MockFit, MockIcon, MockRail, STROKE } from '../product/mock' + +/** + * The OLake UI pictures in the four feature tabs of the OLake Fusion page (the design's fusion-1 to + * fusion-4 frames): the tables list with its compaction tiers, the maintenance checklist, the + * cleanup schedule and the deployment card. Each is drawn at the design size (507 × 437) and + * scaled to fit; pictures of the product, hidden from assistive tech. + */ + +const Icon = ({ name, ...rest }: React.ComponentProps) => ( + +) + +/** The editor panel with the collapsed rail (Optimisation selected). */ +function Panel({ className, children }: { className?: string; children: React.ReactNode }) { + return ( + +
+ {children} + +
+
+ ) +} + +/** "Optimisation / Tables /" */ +const Breadcrumb = ({ className }: { className: string }) => ( +
+ Optimisation + / + Tables + / +
+) + +/** Page title and its one-line description. */ +const Heading = ({ title, className }: { title: string; className: string }) => ( +
+ {title} + + Select Catalog & Database to view tables & run optimisation + +
+) + +/** The light-blue "OLake" tag after a table name. */ +const OlakeTag = () => ( + + OLake + +) + +/* ---- Tiered Compaction (fusion-1) ----------------------------------------------------------- */ + +/** A table's last run of each tier (Quick, Standard, Deep), shown as Q, S and D tags. */ +type TierState = { + q: keyof typeof STATE_ICON + s: keyof typeof STATE_ICON + d: keyof typeof STATE_ICON +} +const STATE_ICON = { + running: { icon: 'feature-tables-running', className: 'bg-[#fff7e6] text-[#fa8c16]' }, + failed: { icon: 'feature-tables-failed', className: 'bg-[#fff1f0] text-[#f5222d]' }, + success: { icon: 'feature-tables-success', className: 'bg-[#f6ffed] text-[#52c41a]' } +} + +/** Tables under the selected one: name, size before and after, tier states (or not optimised), switch. */ +const TABLES: { + name: string + before: string + after: string + tiers?: TierState + on: boolean + tag?: boolean +}[] = [ + { name: 'Products_Table_Cerulean', before: '15 GB', after: '12 GB', on: false }, + { + name: 'Customers_Table_Azure', + before: '20 GB', + after: '16 GB', + tiers: { q: 'running', s: 'failed', d: 'failed' }, + on: true + }, + { + name: 'Shipments_Table_Indigo', + before: '25 GB', + after: '20 GB', + tiers: { q: 'success', s: 'success', d: 'success' }, + on: false + }, + { name: 'Payments_Table_Fuchsia', before: '30 GB', after: '24 GB', on: false }, + { name: 'Reviews_Table_Scarlet', before: '35 GB', after: '28 GB', on: false, tag: true } +] + +const TierTags = ({ tiers }: { tiers: TierState }) => ( + + {(['q', 's', 'd'] as const).map((key) => { + const state = STATE_ICON[tiers[key]] + return ( + + + {key.toUpperCase()} + + ) + })} + +) + +const RowButton = ({ + left, + width, + children +}: { + left: number + width?: number + children: string +}) => ( + + + {children} + + +) + +const Switch = ({ on }: { on: boolean }) => ( + + + + + +) + +const Health = ({ before, after, left }: { before: string; after: string; left: number }) => ( + + {before} + + {after} + +) + +const NotOptimised = ({ top }: { top: number }) => ( + + Not Optimised + +) + +const TABLE_HEADERS: [string, number, boolean][] = [ + ['Actions', 30.66, true], + ['Table', 60.33, false], + ['Health Score', 236.57, true], + ['Last Run Status', 344.66, true], + ['Metrics', 447.15, true], + ['Optimisation', 534.14, true], + ['Status', 615.61, true] +] + +/** The compaction tiers of the selected table: last run and state of each. */ +const TIERS = [ + { + name: 'Quick', + last: 'last run 6 hr ago', + state: 'Running', + icon: 'feature-tiers-running', + color: 'text-[#fa8c16]', + top: 8.4 + }, + { + name: 'Standard', + last: 'last run 32 min ago', + state: 'Failed', + icon: 'feature-tiers-failed', + color: 'text-[#f5222d]', + top: 36.76 + }, + { + name: 'Deep', + last: 'last run 32 min ago', + state: 'Success', + icon: 'feature-tiers-success', + color: 'text-[#52c41a]', + top: 65.12 + } +] + +export function TieredMock({ className }: { className?: string }) { + return ( + + +
+ +
+ {[ + ['OLake-default', 'text-[rgba(0,0,0,0.88)]'], + ['Select Database', 'text-[rgba(0,0,0,0.25)]'] + ].map(([label, color]) => ( + + + + {label} + + + + + ))} +
+
+ +
+
+ {TABLE_HEADERS.map(([label, x, centred]) => ( + + {label} + + ))} +
+ + {/* The selected table, lifted */} +
+ + + + Orders_Table_Veridian + + + + + + View Metrics + + Configure + + +
+ + {TABLES.map((table, i) => ( +
+ + {table.tag ? ( + + + {table.name} + + + + ) : ( + + {table.name} + + )} + + {table.tiers ? ( + + ) : ( + + )} + View Metrics + + Configure + + +
+ ))} +
+ + {/* The selected table's tiers */} +
+ {TIERS.map((tier) => ( +
+ + + {tier.name} + + + {tier.last} + + + + + {tier.state} + +
+ ))} + + +
+ + {/* The table runs on under a white fade at the panel's right edge */} + +
+ ) +} + +/* ---- Omit Decay (fusion-2) ------------------------------------------------------------------ */ + +/** The maintenance steps, each card tucked under the one above. */ +const STEPS = [ + { + label: 'Metadata cleanup', + note: '1.6 Gb cleaned up', + noteLeft: 609.09, + top: 236.05, + done: false, + bg: 'bg-[#f0f0f0]', + text: 'text-[#a7a7a7]' + }, + { + label: 'Delete-file resolution', + note: 'Deleted 23m ago', + noteLeft: 616.36, + top: 181.51, + done: false, + bg: 'bg-[#f9f9f9]', + text: 'text-[#092b00]' + }, + { + label: 'Small-file compaction', + note: '12.7 GB cleaned up', + noteLeft: 598.18, + top: 123.32, + done: true, + bg: 'bg-white', + text: 'text-[#092b00]' + } +] + +export function DecayMock({ className }: { className?: string }) { + return ( + + {STEPS.map((step) => ( +
+ + + {step.label} + + + {step.note} + +
+ ))} + + + Products_Table_Cerulean + + + 10 GB + + +
+ + + + Scheduled Maintenance Completed + + + + Last maintained · 2 minutes ago. + +
+
+ ) +} + +/* ---- Easy Configuration (fusion-3) ---------------------------------------------------------- */ + +const Select = ({ value, className }: { value: string; className?: string }) => ( + + + {value} + + + + + +) + +/** A field label, set at the size the design renders it (its 5.3px spec is an instance scale). */ +const Label = ({ children }: { children: React.ReactNode }) => ( + + {children} + +) + +export function ConfigMock({ className }: { className?: string }) { + return ( + + + Configure Optimization + +
+ + + Products_Table_Cerulean + + 10 GB +
+ +
+
+ Optimize + + Cleanup + +
+
+ +
+ + Cleanup Cron + + +
+ + + + + + + + diff --git a/src/components/landing/go/GoFeaturePanels.tsx b/src/components/landing/go/GoFeaturePanels.tsx index 64b55c27..72045636 100644 --- a/src/components/landing/go/GoFeaturePanels.tsx +++ b/src/components/landing/go/GoFeaturePanels.tsx @@ -289,7 +289,7 @@ export function ChunkingMock({ className }: { className?: string }) { 750K / 1M - + 25K rows/s diff --git a/static/img/landing/fusion/dashboard-caret-open.webp b/static/img/landing/fusion/dashboard-caret-open.webp deleted file mode 100644 index 21d39b778c401b8984a731cb032d799ce08731b2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 110 zcmV-!0FnPvNk&Fy00012MM6+kP&iCl0000l7Jvm1Fxs|_|G&HK*l+}+NV1*m&;KjQ zzzG##DLoZ>i_@% diff --git a/static/img/landing/fusion/feature-config-info.webp b/static/img/landing/fusion/feature-config-info.webp deleted file mode 100644 index 9e0fef04a4bd48f936379fdc3857c04cf418c992..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 226 zcmV<803H8QNk&H600012MM6+kP&iD^0000l5C8-a3I=W42r_@#+jGeMBHYHdt<=&k zPUP{I$lcz{kOJJH0oTU_RN;LjYRgHyabS1SATkET4AD3)uvI{9{z5BUIUTD{)H}!U z8bM{F)G2HVB~;^l5GsgnV%VRF26~9ML#1&P%K^Cw@3k0YDvGoB#j- diff --git a/static/img/landing/go/feature-chunking-dot-lg.webp b/static/img/landing/go/feature-chunking-dot-lg.webp deleted file mode 100644 index ef1bf237d9653c637bc339b957453862ebbe20d5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 84 zcmWIYbaV4zU| Date: Wed, 14 Oct 2026 10:55:22 +0530 Subject: [PATCH 10/28] fix(olake-go): give each job in the hero dashboard its own logos and status The Jobs table under the OLake Go hero repeated one source logo, one destination logo and the green "Success" tag on every row, so a MySQL job showed the MongoDB mark and a failed job looked successful. - Each source and destination now shows its own logo, reusing the sources strip artwork (MySQL, PostgreSQL, MongoDB, Oracle, Amazon S3) plus the Apache Iceberg mark. - Failed rows get a red tag and Pending rows an orange one; only Success keeps the green check. - The placeholder destinations (Dropbox, Google Drive, OneDrive, Box) and SQLite are replaced with what OLake Go supports (Apache Iceberg, Amazon S3, Oracle), so every label has a real logo. - The failed and running status icons move to shared product/ so the Go and Fusion dashboards use the same files; the four per-row logo files they replace are removed. - Fusion's Omit Decay icons were named the wrong way round (decay-active was the done tick); renamed to decay-done and decay-pending. No visual change. --- .../landing/fusion/FusionDashboardMock.tsx | 8 +- .../landing/fusion/FusionFeaturePanels.tsx | 2 +- src/components/landing/go/GoDashboardMock.tsx | 73 ++++++++++++++---- .../landing/fusion/feature-decay-active.webp | Bin 978 -> 0 bytes .../landing/fusion/feature-decay-done.webp | Bin 912 -> 978 bytes .../landing/fusion/feature-decay-pending.webp | Bin 0 -> 912 bytes .../go/dashboard-highlight-destination.webp | Bin 568 -> 0 bytes .../go/dashboard-highlight-source.webp | Bin 344 -> 0 bytes .../landing/go/dashboard-row-destination.webp | Bin 516 -> 0 bytes .../img/landing/go/dashboard-row-source.webp | Bin 326 -> 0 bytes .../img/landing/go/destination-iceberg.webp | Bin 0 -> 1230 bytes .../status-failed.webp} | Bin .../status-running.webp} | Bin 13 files changed, 63 insertions(+), 20 deletions(-) delete mode 100644 static/img/landing/fusion/feature-decay-active.webp create mode 100644 static/img/landing/fusion/feature-decay-pending.webp delete mode 100644 static/img/landing/go/dashboard-highlight-destination.webp delete mode 100644 static/img/landing/go/dashboard-highlight-source.webp delete mode 100644 static/img/landing/go/dashboard-row-destination.webp delete mode 100644 static/img/landing/go/dashboard-row-source.webp create mode 100644 static/img/landing/go/destination-iceberg.webp rename static/img/landing/{fusion/dashboard-status-failed.webp => product/status-failed.webp} (100%) rename static/img/landing/{fusion/dashboard-status-running.webp => product/status-running.webp} (100%) diff --git a/src/components/landing/fusion/FusionDashboardMock.tsx b/src/components/landing/fusion/FusionDashboardMock.tsx index 2696f51c..4f32d285 100644 --- a/src/components/landing/fusion/FusionDashboardMock.tsx +++ b/src/components/landing/fusion/FusionDashboardMock.tsx @@ -51,19 +51,19 @@ const CaretRight = ({ name = 'dashboard-caret' }: { name?: string }) => ( const STATUS = { success: { label: 'Success', - icon: 'dashboard-status-success', + icon: 'fusion/dashboard-status-success', className: 'bg-[#f6ffed] text-[#52c41a]', left: 103.23 }, running: { label: 'Running', - icon: 'dashboard-status-running', + icon: 'product/status-running', className: 'bg-[#fff7e6] text-[#fa8c16]', left: 103.93 }, failed: { label: 'Failed', - icon: 'dashboard-status-failed', + icon: 'product/status-failed', className: 'bg-[#fff1f0] text-[#f5222d]', left: 108.18 } @@ -286,7 +286,7 @@ export default function FusionDashboardMock() { )} style={{ left: status.left }} > - + {status.label} diff --git a/src/components/landing/fusion/FusionFeaturePanels.tsx b/src/components/landing/fusion/FusionFeaturePanels.tsx index 33167720..2dc51bed 100644 --- a/src/components/landing/fusion/FusionFeaturePanels.tsx +++ b/src/components/landing/fusion/FusionFeaturePanels.tsx @@ -408,7 +408,7 @@ export function DecayMock({ className }: { className?: string }) { style={{ top: step.top }} > diff --git a/src/components/landing/go/GoDashboardMock.tsx b/src/components/landing/go/GoDashboardMock.tsx index 7649780f..f53a8325 100644 --- a/src/components/landing/go/GoDashboardMock.tsx +++ b/src/components/landing/go/GoDashboardMock.tsx @@ -19,12 +19,50 @@ const COLUMNS = [ { label: 'Last sync status', className: 'flex-1' } ] +/** + * The mark of each source and destination a job can name: the artwork of the hero's sources strip, + * so a row's logo always matches its label. + */ +const LOGOS = { + 'MySQL': 'go/source-mysql', + 'PostgreSQL': 'go/source-postgres', + 'MongoDB': 'go/source-mongodb', + 'Oracle': 'go/source-oracle', + 'Amazon S3': 'go/source-s3', + 'Apache Iceberg': 'go/destination-iceberg' +} as const + +type Endpoint = keyof typeof LOGOS + +/** A "Last sync status" tag: Success is green with a check, Failed red, Pending orange. */ +const STATUS = { + Success: { + icon: 'go/dashboard-row-check', + className: 'bg-[#f6ffed] text-[#389e0d]' + }, + Failed: { + icon: 'product/status-failed', + className: 'bg-[#fff1f0] text-[#cf1322]' + }, + Pending: { + icon: 'product/status-running', + className: 'bg-[#fff7e6] text-[#d46b08]' + } +} as const + /** The rows under the highlighted one; the last two sit in the fade. */ -const ROWS = [ +const ROWS: { + name: string + source: Endpoint + destination: Endpoint + lastSync: string + status: keyof typeof STATUS + opacity: string +}[] = [ { name: 'Table_name_test_3', source: 'MySQL', - destination: 'Dropbox', + destination: 'Apache Iceberg', lastSync: '2 days ago', status: 'Failed', opacity: 'opacity-70' @@ -32,7 +70,7 @@ const ROWS = [ { name: 'Table_name_test_4', source: 'PostgreSQL', - destination: 'Google Drive', + destination: 'Amazon S3', lastSync: '1 day ago', status: 'Success', opacity: 'opacity-70' @@ -40,15 +78,15 @@ const ROWS = [ { name: 'Table_name_test_5', source: 'MongoDB', - destination: 'OneDrive', + destination: 'Apache Iceberg', lastSync: '3 days ago', status: 'Pending', opacity: 'opacity-40' }, { name: 'Table_name_test_6', - source: 'SQLite', - destination: 'Box', + source: 'Oracle', + destination: 'Amazon S3', lastSync: '4 hours ago', status: 'Failed', opacity: 'opacity-40' @@ -141,12 +179,12 @@ export default function GoDashboardMock() { > {row.name} - - + + {row.source} - + {row.destination} {row.lastSync} - - - + + + {row.status} @@ -185,14 +228,14 @@ export default function GoDashboardMock() { Table_name_test_1 - - + + MongoDB - + Amazon S3 diff --git a/static/img/landing/fusion/feature-decay-active.webp b/static/img/landing/fusion/feature-decay-active.webp deleted file mode 100644 index c341dff6c9fc347f3953fa8ad49c552b888a84c0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 978 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 G$zlLo-w&Mt diff --git a/static/img/landing/go/dashboard-highlight-source.webp b/static/img/landing/go/dashboard-highlight-source.webp deleted file mode 100644 index e0d40c0da50edd8a180b963aa2bb8d75ffce8a7d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 344 zcmV-e0jK^_Nk&Fc0RRA3MM6+kP&iCP0RR9mDL@Jk55lI6BuU zYyZ2F(sQIyh7tXr0P@A%mz`w6Oky_g8tF`cZP-NM2mpt-g$l%~f#Fyh_w{Pto+iu68JG$kzqJ{+)#Y diff --git a/static/img/landing/go/dashboard-row-destination.webp b/static/img/landing/go/dashboard-row-destination.webp deleted file mode 100644 index 18ff07ccbd4fd89396fc6189308688a9b325d174..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 516 zcmV+f0{i_^Nk&He0RRA3MM6+kP&iER0RR9mAwUQaFT*yHBquZX|ELVG@ClW4F<)F7 z4kJlcmD#=jad*j$6ba1T;T%Vj$g{;F-JYjc2tJ4MuQId!hoRh?P||q#^u)1Mowr`R5KS5M!vL$rIOjC3eNd!U2io zAB7Ao3n>gKB3h1<0U%lcF;p<)sI=`;0!S1=YjvxrHsvS)L2Gwl0VG020R*@(6i_h) z@evH*0{oKtfP3Ld@=fZQPztC6?&O~g|CMsj?|L?){I|VDD*K>o>C}X$|MF9I5<(Ll zd{lM)qEJTd9rB&z0_x2msH-}QcChy_|f>7|S&t1+A=kF(f zVK{Gp<*2;0+E{LYZrir;Cbo0F*tTu<@_#FT= z=l3^nG*^!=>-F=~HH8n}Zy(MueG-$L?X*@?_2Jdk#@yjJhj+)9dqG=29}nW=g*-g@ Gp9%*ohw!@q diff --git a/static/img/landing/go/dashboard-row-source.webp b/static/img/landing/go/dashboard-row-source.webp deleted file mode 100644 index 4a52256806ca8e64b3a6375dd3b69795cdec31e4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 326 zcmV-M0lEHCNk&FK0RRA3MM6+kP&iC70RR9mC%_31|G_Adk#UKK+khDRg;EDQkJ26D(lR&U|KEu{6q_oa3Bn`oW=_FtXgQJetsI#J|Q z)O1oFJ<-%jd)!4!Cvp@|QPOc2Q56}bhA0B-kWzdHl!1rSt^)Z|%++?he^>&wu9K;Ex}Hd}1Q1`U;(Q6^D3tfb3aHO}0m&UW`SA zniIhtAlkN-Hk$wc5zBf&fFvzdQg@~9?(XfdAjr0zwuzbP%VB1Y86_Fj{V%CdYym)+ zym?#3-ICqFt5cok&IK4pU{sLoDsR)+Qq(Yg=zQshEO%Ou^ren1VOG<)x#2bHC40ol_oSm0j9V?h1FUvmY@9 zfMN>%!yW5VJ!##|^nbtx1xeC>K1LX|y8n}Cu-MgrM{knga`~teVbfm`M zZK4khF$G`pO>U+I!}&=M+~`hw>GVSD>qL0P=yYO1FM&kel~j)JJI$+|aI=HlJ=+Nv zJIK|15g8GyeH$LD^kA(Xxbra`@>h=yH5VYVC-YVr7$X%8_V`YY`PcXl zgxF?WZT@~D&9qYkqYcODZV#AI&R{b@_+)uNOrn|N{t)F1+)yhFqm z7rj6te?PUkB>mkhQj0}#gfNp~rdMQGd69c3&}_(iR~r{*^sjN*6G1H_=HUm4X}`+{@+=4{#* ztGH;JzMp16K;yZJ3NND=O`=@}QO0eSHi{HD>3Cr2OnmCS_`2cyVJZqpR<5F=qO(mg z6B*b~(+?M}^A-X!urEfp$$Q1=PDke!J0Cs#m=?ggg@~RZqPL{_N=l_YGuG;?!~tEQ zx5{~m^uO&EVPKuPtofaXdw&jbTnWPR}=o=|5S zV}K5c*Cs-hW}fQ|C_Uc!36TMyefphjuDKwB6}R@I5*hQ%TyI1%6N!vpCDqFuT9vtT z>O6tsf?4Jf_%3S&O=zR+$-FBNNY7+oe-Ihif52B><_tDHjr1gd_R83srG<^dq(2&O sha8-ef#qv!Wngb1o2LEIc)v&;=Q8$Y6_%|!Zy{ReEyU`)xd2*)0JQj2 Date: Wed, 14 Oct 2026 12:12:30 +0530 Subject: [PATCH 11/28] fix(olake-go): size source and destination logos alike in the hero table --- src/components/landing/go/GoDashboardMock.tsx | 42 ++++++++++++------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/src/components/landing/go/GoDashboardMock.tsx b/src/components/landing/go/GoDashboardMock.tsx index f53a8325..18f027d4 100644 --- a/src/components/landing/go/GoDashboardMock.tsx +++ b/src/components/landing/go/GoDashboardMock.tsx @@ -21,19 +21,33 @@ const COLUMNS = [ /** * The mark of each source and destination a job can name: the artwork of the hero's sources strip, - * so a row's logo always matches its label. + * so a row's logo always matches its label. `scale` enlarges artwork that sits small in its canvas + * (the S3 bucket fills 62% of it) so every mark reads at about the same size. */ const LOGOS = { - 'MySQL': 'go/source-mysql', - 'PostgreSQL': 'go/source-postgres', - 'MongoDB': 'go/source-mongodb', - 'Oracle': 'go/source-oracle', - 'Amazon S3': 'go/source-s3', - 'Apache Iceberg': 'go/destination-iceberg' -} as const + 'MySQL': { src: 'go/source-mysql' }, + 'PostgreSQL': { src: 'go/source-postgres' }, + 'MongoDB': { src: 'go/source-mongodb' }, + 'Oracle': { src: 'go/source-oracle' }, + 'Amazon S3': { src: 'go/source-s3', scale: 1.2 }, + 'Apache Iceberg': { src: 'go/destination-iceberg' } +} as const satisfies Record type Endpoint = keyof typeof LOGOS +/** A logo in a square slot of `size` px, so sources and destinations line up and size alike. */ +function Logo({ name, size }: { name: Endpoint; size: number }) { + const logo: { src: string; scale?: number } = LOGOS[name] + return ( + + + + ) +} + /** A "Last sync status" tag: Success is green with a check, Failed red, Pending orange. */ const STATUS = { Success: { @@ -180,11 +194,11 @@ export default function GoDashboardMock() { {row.name} - + {row.source} - - + + {row.destination} - + MongoDB - - + + Amazon S3 From b27d9d91cea46bb5a1df89ff81bcb7b48f383482 Mon Sep 17 00:00:00 2001 From: krishnajain-15 Date: Wed, 14 Oct 2026 12:12:30 +0530 Subject: [PATCH 12/28] feat(landing): keep every feature's copy in the page, hide the inactive tabs --- .../landing/product/ProductFeatures.tsx | 65 ++++++++++--------- 1 file changed, 36 insertions(+), 29 deletions(-) diff --git a/src/components/landing/product/ProductFeatures.tsx b/src/components/landing/product/ProductFeatures.tsx index def1361e..6f1bc5c5 100644 --- a/src/components/landing/product/ProductFeatures.tsx +++ b/src/components/landing/product/ProductFeatures.tsx @@ -25,8 +25,10 @@ export interface ProductFeatureArt { /** * "Features": a pill tab strip over the blue feature card. Each tab shows its own picture of the - * product; the heading and copy follow the selected capability. Arrow keys, Home and End move - * between tabs. + * product; the heading and copy follow the selected capability. Every capability's heading and copy + * stay in the DOM (the inactive ones hidden), as ProductFaq keeps its answers; only the selected + * tab's picture is rendered, since the text is cheap and the picture is not. Arrow keys, Home and + * End move between tabs. */ export default function ProductFeatures({ idPrefix, @@ -61,9 +63,7 @@ export default function ProductFeatures({ event.preventDefault() } - const feature = features[active] - const frame = art[feature.id] - const panelId = `${idPrefix}-panel` + const frame = art[features[active].id] return (
-
- {frame.background} +
+ {frame.background} {frame.glow} -
-

- {feature.title} -

-

- {feature.body} -

-
+ {features.map((item, i) => { + const selected = i === active + return ( + + ) + })} Date: Wed, 14 Oct 2026 12:42:32 +0530 Subject: [PATCH 13/28] perf(landing): draw the hero and feature glows in CSS instead of images --- src/components/landing/fusion/FusionCta.tsx | 31 +++++--------- .../landing/fusion/FusionFeatures.tsx | 23 ++++++----- src/components/landing/fusion/FusionHero.tsx | 12 +++--- src/components/landing/go/GoFeatures.tsx | 23 ++++++----- src/components/landing/go/GoHero.tsx | 12 +++--- src/components/landing/product/Glow.tsx | 39 ++++++++++++++++++ static/img/landing/fusion/cta-glow.webp | Bin 9208 -> 0 bytes static/img/landing/fusion/feature-glow.webp | Bin 8882 -> 0 bytes static/img/landing/fusion/hero-glow.webp | Bin 9644 -> 0 bytes static/img/landing/go/feature-glow.webp | Bin 6194 -> 0 bytes static/img/landing/go/hero-glow.webp | Bin 21098 -> 0 bytes 11 files changed, 87 insertions(+), 53 deletions(-) create mode 100644 src/components/landing/product/Glow.tsx delete mode 100644 static/img/landing/fusion/cta-glow.webp delete mode 100644 static/img/landing/fusion/feature-glow.webp delete mode 100644 static/img/landing/fusion/hero-glow.webp delete mode 100644 static/img/landing/go/feature-glow.webp delete mode 100644 static/img/landing/go/hero-glow.webp diff --git a/src/components/landing/fusion/FusionCta.tsx b/src/components/landing/fusion/FusionCta.tsx index 81535364..9872cd64 100644 --- a/src/components/landing/fusion/FusionCta.tsx +++ b/src/components/landing/fusion/FusionCta.tsx @@ -11,27 +11,16 @@ export default function FusionCta() { {...FUSION_CTA} className='bg-[#0a0923]' art={ - <> - {/* A navy glow that softens the points under the heading */} - - - + // The design's navy glow under the heading is the card's own colour, so it is left out. + } /> ) diff --git a/src/components/landing/fusion/FusionFeatures.tsx b/src/components/landing/fusion/FusionFeatures.tsx index 92d87d8f..439eeb33 100644 --- a/src/components/landing/fusion/FusionFeatures.tsx +++ b/src/components/landing/fusion/FusionFeatures.tsx @@ -1,5 +1,6 @@ import React from 'react' import ProductFeatures, { type ProductFeatureArt } from '../product/ProductFeatures' +import Glow from '../product/Glow' import { FEATURES, FEATURES_INTRO } from '@site/src/data/landing/fusion/features' import { ConfigMock, DecayMock, HostedMock, TieredMock } from './FusionFeaturePanels' @@ -18,15 +19,15 @@ const Field = () => ( ) /** The deep blue glow at the bottom left. */ -const Glow = () => ( - ( + ) @@ -36,7 +37,7 @@ const MOCK = 'lg:top-[52.22px] lg:right-[42.07px] lg:w-[507px]' const ART: Record = { tiered: { background: , - glow: , + glow: , copyTop: 332, bodyWidth: 323, Mock: TieredMock, @@ -44,7 +45,7 @@ const ART: Record = { }, decay: { background: , - glow: , + glow: , copyTop: 377, bodyWidth: 323, Mock: DecayMock, @@ -52,7 +53,7 @@ const ART: Record = { }, config: { background: , - glow: , + glow: , copyTop: 395, bodyWidth: 323, Mock: ConfigMock, @@ -60,7 +61,7 @@ const ART: Record = { }, hosted: { background: , - glow: , + glow: , copyTop: 395, bodyWidth: 323, Mock: HostedMock, diff --git a/src/components/landing/fusion/FusionHero.tsx b/src/components/landing/fusion/FusionHero.tsx index 8612cfe0..d8f0dedf 100644 --- a/src/components/landing/fusion/FusionHero.tsx +++ b/src/components/landing/fusion/FusionHero.tsx @@ -1,5 +1,6 @@ import React from 'react' import ProductHero from '../product/ProductHero' +import Glow from '../product/Glow' import { HERO } from '@site/src/data/landing/fusion/hero' import FusionDashboardMock from './FusionDashboardMock' @@ -22,13 +23,14 @@ const Background = () => ( decoding='async' className='absolute top-[-791.39px] left-[calc(50%-8.26px)] block max-w-none -translate-x-1/2 -scale-y-100' /> -
) diff --git a/src/components/landing/go/GoFeatures.tsx b/src/components/landing/go/GoFeatures.tsx index 6e20a238..02d57031 100644 --- a/src/components/landing/go/GoFeatures.tsx +++ b/src/components/landing/go/GoFeatures.tsx @@ -1,6 +1,7 @@ import React from 'react' import { cn } from '@site/src/lib/utils' import ProductFeatures, { type ProductFeatureArt } from '../product/ProductFeatures' +import Glow from '../product/Glow' import { GO_FEATURES, GO_FEATURES_INTRO } from '@site/src/data/landing/go/features' import GoFeatureMock from './GoFeatureMock' import { ChunkingMock, ResumableMock, SchemaMock } from './GoFeaturePanels' @@ -23,15 +24,15 @@ const field = (className: string) => ( ) /** The blue glow at the bottom left that every tab shares. */ -const Glow = () => ( - ( + ) @@ -52,7 +53,7 @@ const ART: Record = { className='pointer-events-none absolute top-[-41px] right-[-1px] -z-10 block h-[1107px] w-[2639px] max-w-none object-cover' /> ), - glow: , + glow: , copyTop: 338, bodyWidth: 323, Mock: GoFeatureMock, @@ -60,7 +61,7 @@ const ART: Record = { }, 'schema-evolution': { background: field('right-[-281px]'), - glow: , + glow: , copyTop: 338, bodyWidth: 323, Mock: SchemaMock, @@ -68,7 +69,7 @@ const ART: Record = { }, 'parallel-chunking': { background: field('right-[-129px] -scale-x-100'), - glow: , + glow: , copyTop: 354, bodyWidth: 288, Mock: ChunkingMock, @@ -76,7 +77,7 @@ const ART: Record = { }, 'resumable-syncs': { background: field('right-[-129px] -scale-y-100'), - glow: , + glow: , copyTop: 333, bodyWidth: 288, Mock: ResumableMock, diff --git a/src/components/landing/go/GoHero.tsx b/src/components/landing/go/GoHero.tsx index 362b2890..aeef66d9 100644 --- a/src/components/landing/go/GoHero.tsx +++ b/src/components/landing/go/GoHero.tsx @@ -1,5 +1,6 @@ import React from 'react' import ProductHero from '../product/ProductHero' +import Glow from '../product/Glow' import { GO_HERO, GO_SOURCES, @@ -159,13 +160,14 @@ const Background = () => ( decoding='async' className='absolute top-[22px] left-1/2 block h-auto w-[max(1511.67px,105%)] max-w-none -translate-x-1/2' /> -
) diff --git a/src/components/landing/product/Glow.tsx b/src/components/landing/product/Glow.tsx new file mode 100644 index 00000000..39755355 --- /dev/null +++ b/src/components/landing/product/Glow.tsx @@ -0,0 +1,39 @@ +import React from 'react' +import { cn } from '@site/src/lib/utils' + +/** + * A soft glow drawn in CSS rather than loaded as an image: the design's blurred ellipse layer, an + * `rx` × `ry` ellipse in `color` blurred by `blur` px (the Gaussian's standard deviation, as the + * design's layer blur). `className` places the layer's `width` × `height` frame the way the design + * does; the ellipse sits at its centre. + */ +export default function Glow({ + width, + height, + rx, + ry, + blur, + color, + className +}: { + width: number + height: number + rx: number + ry: number + blur: number + color: string + className?: string +}) { + return ( +
diff --git a/src/components/landing/go/GoHero.tsx b/src/components/landing/go/GoHero.tsx index aeef66d9..9162e2b6 100644 --- a/src/components/landing/go/GoHero.tsx +++ b/src/components/landing/go/GoHero.tsx @@ -20,6 +20,7 @@ const LOGOS: Record = { alt='' width={44} height={71} + loading='lazy' decoding='async' className='block h-full w-full object-cover' /> @@ -32,6 +33,7 @@ const LOGOS: Record = { alt='' width={16.7779} height={17.2977} + loading='lazy' decoding='async' className='absolute top-[8.49%] left-[8.48%] block' /> @@ -43,6 +45,7 @@ const LOGOS: Record = { alt='' width={20.8125} height={20.8125} + loading='lazy' decoding='async' className='block' /> @@ -53,6 +56,7 @@ const LOGOS: Record = { alt='' width={20.8125} height={20.8125} + loading='lazy' decoding='async' className='block' /> @@ -63,6 +67,7 @@ const LOGOS: Record = { alt='' width={23.6607} height={23.6607} + loading='lazy' decoding='async' className='block' /> @@ -74,6 +79,7 @@ const LOGOS: Record = { alt='' width={120} height={67} + loading='lazy' decoding='async' className='absolute top-0 left-[-42.62%] block h-full w-[184.43%] max-w-none' /> @@ -86,6 +92,7 @@ const LOGOS: Record = { alt='' width={160} height={93} + loading='lazy' decoding='async' className='absolute top-[-9.38%] left-[-41.67%] block h-[119.97%] w-[184.46%] max-w-none' /> @@ -98,6 +105,7 @@ const LOGOS: Record = { alt='' width={72} height={72} + loading='lazy' decoding='async' className='absolute top-[0.72%] left-[0.71%] block h-[100.33%] w-[100.33%] max-w-none object-cover' /> diff --git a/src/components/landing/product/BenchmarkShell.tsx b/src/components/landing/product/BenchmarkShell.tsx index db5fda4d..815971b4 100644 --- a/src/components/landing/product/BenchmarkShell.tsx +++ b/src/components/landing/product/BenchmarkShell.tsx @@ -2,6 +2,7 @@ import React from 'react' import Link from '@docusaurus/Link' import useDocusaurusContext from '@docusaurus/useDocusaurusContext' import { cn } from '@site/src/lib/utils' +import { MockIcon } from './mock' /** Rules on the dark section. It goes first in cn(): a later border-0 would drop the side classes. */ export const RULE = 'border-0 border-solid border-olake-line-dark' @@ -102,15 +103,7 @@ export default function BenchmarkShell({ )} > {link.label} - +
diff --git a/src/components/landing/product/ProductFaq.tsx b/src/components/landing/product/ProductFaq.tsx index d481bc17..7adba209 100644 --- a/src/components/landing/product/ProductFaq.tsx +++ b/src/components/landing/product/ProductFaq.tsx @@ -1,5 +1,6 @@ import React, { useState } from 'react' import { cn } from '@site/src/lib/utils' +import { MockIcon } from './mock' export interface ProductFaqItem { q: string @@ -58,12 +59,9 @@ export default function ProductFaq({ > {faq.q} {/* Centred on the whole item, so it sits between question and answer when open. */} - - + {copy.primary.label} @@ -74,7 +75,7 @@ export default function ProductFaq({ role='region' aria-labelledby={`${idPrefix}-q-${i}`} hidden={!isOpen} - className={cn('product-fade-in', !isOpen && 'hidden')} + className='product-fade-in' >

{faq.a} diff --git a/src/components/landing/product/ProductFeatures.tsx b/src/components/landing/product/ProductFeatures.tsx index 6f1bc5c5..cf11d197 100644 --- a/src/components/landing/product/ProductFeatures.tsx +++ b/src/components/landing/product/ProductFeatures.tsx @@ -128,10 +128,7 @@ export default function ProductFeatures({ id={`${idPrefix}-panel-${item.id}`} aria-labelledby={`${idPrefix}-tab-${item.id}`} hidden={!selected} - className={cn( - 'product-fade-in px-[24px] pt-[28px] lg:absolute lg:top-(--copy-top) lg:left-[49px] lg:w-[323px] lg:p-0', - !selected && 'hidden' - )} + className='product-fade-in px-[24px] pt-[28px] lg:absolute lg:top-(--copy-top) lg:left-[49px] lg:w-[323px] lg:p-0' style={{ '--copy-top': `${art[item.id].copyTop}px` } as React.CSSProperties} >

diff --git a/src/components/landing/product/mockIcons.ts b/src/components/landing/product/mockIcons.ts index 25fcc14f..d4be3859 100644 --- a/src/components/landing/product/mockIcons.ts +++ b/src/components/landing/product/mockIcons.ts @@ -9,7 +9,6 @@ import { } from 'react-icons/ai' import { PiArrowCounterClockwise, - PiArrowDownBold, PiArrowRight, PiArrowSquareOut, PiArrowsInCardinal, @@ -345,16 +344,6 @@ export const MOCK_ICONS: Record< color: '#52c41a', viewBox: '1.45 1.45 252.69 252.69' }, - 'product/faq-arrow-closed': { - icon: PiArrowDownBold, - color: '#939393', - viewBox: '0 0 256 256' - }, - 'product/faq-arrow-open': { - icon: PiArrowDownBold, - color: '#333333', - viewBox: '0 0 256 256' - }, 'product/icon-github': { icon: PiGithubLogo, color: '#fcfcfb', From e617095645b6a4f52efea399a760e0dd2836ee32 Mon Sep 17 00:00:00 2001 From: krishnajain-15 Date: Wed, 14 Oct 2026 14:31:48 +0530 Subject: [PATCH 20/28] refactor(olake-go): drop the unreachable benchmarks coming-soon message --- src/components/landing/go/GoBenchmark.tsx | 161 +++++++++----------- src/components/landing/go/useGoBenchmark.ts | 4 +- 2 files changed, 73 insertions(+), 92 deletions(-) diff --git a/src/components/landing/go/GoBenchmark.tsx b/src/components/landing/go/GoBenchmark.tsx index dcff16cf..fc3f7020 100644 --- a/src/components/landing/go/GoBenchmark.tsx +++ b/src/components/landing/go/GoBenchmark.tsx @@ -117,116 +117,99 @@ export default function GoBenchmark() {

- {t.hasData ? ( -
- - - {COLUMNS.map((width, i) => ( - - ))} - - - +
+
+ + {COLUMNS.map((width, i) => ( + + ))} + + + + + + {t.competitors.map((name, i) => ( - + + {t.rows.map((row, r) => { + const cell = 'px-[8px] pt-[29px] pb-0 align-top text-[14px] leading-[1.3]' + return ( + - - OLake - Go - - {t.olakeSub} - - {t.competitors.map((name, i) => ( - ))} - - - - {t.rows.map((row, r) => { - const cell = 'px-[8px] pt-[29px] pb-0 align-top text-[14px] leading-[1.3]' - return ( - + {row.note} + )} - > - + {row.competitors.map((value, i) => ( + - {row.competitors.map((value, i) => ( - - ))} - - ) - })} - -
+ Metrics + + + OLake + Go + + {t.olakeSub} + - Metrics + {name} +
- {name} -
+ +
+
- {row.label} - {row.note && ( - - {row.note} - - )} - - -
+
-
-
-
- ) : ( -
-

- {t.sourceName} benchmarks coming soon -

-

- We're running head-to-head {t.sourceName} ingestion tests now. Check back shortly - for the full comparison. -

-
- )} + ))} + + ) + })} + + + ) diff --git a/src/components/landing/go/useGoBenchmark.ts b/src/components/landing/go/useGoBenchmark.ts index 5e3fc836..468042fa 100644 --- a/src/components/landing/go/useGoBenchmark.ts +++ b/src/components/landing/go/useGoBenchmark.ts @@ -76,9 +76,7 @@ export function useGoBenchmark() { competitors: keys.map((key) => TOOLS[key].name), olakeSub: TOOLS.olake.description ?? '', caption: goBenchmarkCaption(mode, bench.rowsSynced.olake, connector.name), - rows, - hasData: bench.hasData, - sourceName: connector.name + rows } }, [selectedConnector, mode]) } From 91567fa4996300e50e8b48251386164fc2f21b03 Mon Sep 17 00:00:00 2001 From: krishnajain-15 Date: Wed, 14 Oct 2026 15:06:47 +0530 Subject: [PATCH 21/28] perf(landing): show the product mock-ups as images instead of hand-built markup --- .../landing/fusion/FusionDashboardMock.tsx | 395 ----------- .../landing/fusion/FusionFeaturePanels.tsx | 671 ------------------ .../landing/fusion/FusionFeatures.tsx | 29 +- src/components/landing/fusion/FusionHero.tsx | 14 +- src/components/landing/go/GoDashboardMock.tsx | 293 -------- src/components/landing/go/GoFeatureMock.tsx | 200 ------ src/components/landing/go/GoFeaturePanels.tsx | 422 ----------- src/components/landing/go/GoFeatures.tsx | 30 +- src/components/landing/go/GoHero.tsx | 11 +- src/components/landing/go/logos.ts | 10 +- .../landing/product/BenchmarkShell.tsx | 9 +- .../landing/product/ProductFeatures.tsx | 16 +- .../landing/product/ProductHero.tsx | 4 +- src/components/landing/product/mock.tsx | 139 ---- src/components/landing/product/mockIcons.ts | 393 ---------- src/components/landing/product/product.css | 30 +- static/img/landing/fusion/feature-config.webp | Bin 0 -> 20160 bytes static/img/landing/fusion/feature-decay.webp | Bin 0 -> 19460 bytes static/img/landing/fusion/feature-hosted.webp | Bin 0 -> 16336 bytes static/img/landing/fusion/feature-tiered.webp | Bin 0 -> 30270 bytes static/img/landing/fusion/hero-dashboard.webp | Bin 0 -> 62840 bytes .../img/landing/go/destination-iceberg.webp | Bin 1230 -> 0 bytes static/img/landing/go/feature-cdc.webp | Bin 0 -> 21634 bytes .../img/landing/go/feature-mock-checkbox.webp | Bin 308 -> 0 bytes .../landing/go/feature-parallel-chunking.webp | Bin 0 -> 18914 bytes .../landing/go/feature-resumable-syncs.webp | Bin 0 -> 17950 bytes .../go/feature-schema-checkbox-all.webp | Bin 306 -> 0 bytes .../go/feature-schema-checkbox-highlight.webp | Bin 304 -> 0 bytes .../landing/go/feature-schema-checkbox.webp | Bin 306 -> 0 bytes .../landing/go/feature-schema-evolution.webp | Bin 0 -> 24058 bytes static/img/landing/go/hero-dashboard.webp | Bin 0 -> 63950 bytes 31 files changed, 95 insertions(+), 2571 deletions(-) delete mode 100644 src/components/landing/fusion/FusionDashboardMock.tsx delete mode 100644 src/components/landing/fusion/FusionFeaturePanels.tsx delete mode 100644 src/components/landing/go/GoDashboardMock.tsx delete mode 100644 src/components/landing/go/GoFeatureMock.tsx delete mode 100644 src/components/landing/go/GoFeaturePanels.tsx delete mode 100644 src/components/landing/product/mock.tsx delete mode 100644 src/components/landing/product/mockIcons.ts create mode 100644 static/img/landing/fusion/feature-config.webp create mode 100644 static/img/landing/fusion/feature-decay.webp create mode 100644 static/img/landing/fusion/feature-hosted.webp create mode 100644 static/img/landing/fusion/feature-tiered.webp create mode 100644 static/img/landing/fusion/hero-dashboard.webp delete mode 100644 static/img/landing/go/destination-iceberg.webp create mode 100644 static/img/landing/go/feature-cdc.webp delete mode 100644 static/img/landing/go/feature-mock-checkbox.webp create mode 100644 static/img/landing/go/feature-parallel-chunking.webp create mode 100644 static/img/landing/go/feature-resumable-syncs.webp delete mode 100644 static/img/landing/go/feature-schema-checkbox-all.webp delete mode 100644 static/img/landing/go/feature-schema-checkbox-highlight.webp delete mode 100644 static/img/landing/go/feature-schema-checkbox.webp create mode 100644 static/img/landing/go/feature-schema-evolution.webp create mode 100644 static/img/landing/go/hero-dashboard.webp diff --git a/src/components/landing/fusion/FusionDashboardMock.tsx b/src/components/landing/fusion/FusionDashboardMock.tsx deleted file mode 100644 index 11ce88dd..00000000 --- a/src/components/landing/fusion/FusionDashboardMock.tsx +++ /dev/null @@ -1,395 +0,0 @@ -import React from 'react' -import { cn } from '@site/src/lib/utils' -import { MockIcon, STROKE } from '../product/mock' - -/** - * The OLake Fusion "Run History" screen under the hero with the run-metrics card lifted over its - * right edge, drawn at the design size (1018 px wide, 510 px showing) and scaled to fit. The card - * reaches 75px past the screen, so the fade wraps both at 1093px. Where the page is too narrow for - * the card to hang past the centred screen (below 1200px), the whole 1093px picture is fitted - * instead. A picture of the product, hidden from assistive tech. - */ - -const Icon = ({ name, ...rest }: React.ComponentProps) => ( - -) - -const MUTED = 'text-[rgba(0,0,0,0.65)]' - -/** A sidebar entry: icon, label and an optional trailing node. */ -const NavItem = ({ - icon, - label, - trailing, - className -}: { - icon: string - label: React.ReactNode - trailing?: React.ReactNode - className?: string -}) => ( -
- - - - {label} - - - {trailing} -
-) - -/** The caret drawn pointing right (the design rotates its down caret a quarter turn). */ -const CaretRight = () => ( - - - -) - -const STATUS = { - success: { - label: 'Success', - icon: 'fusion/dashboard-status-success', - className: 'bg-[#f6ffed] text-[#52c41a]', - left: 103.23 - }, - running: { - label: 'Running', - icon: 'product/status-running', - className: 'bg-[#fff7e6] text-[#fa8c16]', - left: 103.93 - }, - failed: { - label: 'Failed', - icon: 'product/status-failed', - className: 'bg-[#fff1f0] text-[#f5222d]', - left: 108.18 - } -} as const - -const RUNS: { - id: string - status: keyof typeof STATUS - type: string - start: string - duration: string -}[] = [ - { id: '005/261', status: 'success', type: 'Minor', start: '11:15:00', duration: '25 Mins' }, - { id: '001/261', status: 'running', type: 'Major', start: '15:10:00', duration: '32 Mins' }, - { id: '004/261', status: 'failed', type: 'Major', start: '12:00:00', duration: '30 Mins' } -] - -/** Header cells: the first sits at its left edge, the others are centred on their x. */ -const COLUMNS: [string, number][] = [ - ['Status', 131.07], - ['Type', 248.48], - ['Start Time', 366.95], - ['Duration', 497.07], - ['Metrics', 615.98], - ['Logs', 735.41] -] - -const CELL = - 'absolute top-[19.81px] -translate-y-1/2 text-[9.898px] leading-[16.969px] whitespace-nowrap text-[rgba(0,0,0,0.88)]' - -const ViewButton = ({ left }: { left: number }) => ( - - - View - - -) - -/** The run-metrics card. Values are right-aligned on a common edge, as the design sets them. */ -const METRICS: { - label: string - value: string - labelTop: number - valueTop: number -}[] = [ - { label: 'Input Data Files', value: '06', labelTop: 108.9, valueTop: 107.43 }, - { - label: 'Input Data Size', - value: '35.9 GB', - labelTop: 155.99, - valueTop: 150.26 - }, - { - label: 'Input Equality Delete Files', - value: '01', - labelTop: 203.08, - valueTop: 200.26 - }, - { - label: 'Output Data Files', - value: '03', - labelTop: 250.17, - valueTop: 248.26 - }, - { - label: 'Output Data Size', - value: '28.9 GB', - labelTop: 299.17, - valueTop: 296.26 - } -] - -function RunMetricsCard() { - return ( -
-
-

- Run Metrics for Run ID 005/261 -

-

- View run metrics for the last run 2 days ago -

-
- {[82.41, 135.39, 188.37, 235.46, 282.55, 331.55].map((top) => ( - - ))} - {METRICS.map((m) => ( - - - {m.label} - - - {m.value} - - - ))} -
- ) -} - -export default function FusionDashboardMock() { - return ( - - ) -} diff --git a/src/components/landing/fusion/FusionFeaturePanels.tsx b/src/components/landing/fusion/FusionFeaturePanels.tsx deleted file mode 100644 index 2dc51bed..00000000 --- a/src/components/landing/fusion/FusionFeaturePanels.tsx +++ /dev/null @@ -1,671 +0,0 @@ -import React from 'react' -import { cn } from '@site/src/lib/utils' -import { MockFit, MockIcon, MockRail, STROKE } from '../product/mock' - -/** - * The OLake UI pictures in the four feature tabs of the OLake Fusion page (the design's fusion-1 to - * fusion-4 frames): the tables list with its compaction tiers, the maintenance checklist, the - * cleanup schedule and the deployment card. Each is drawn at the design size (507 × 437) and - * scaled to fit; pictures of the product, hidden from assistive tech. - */ - -const Icon = ({ name, ...rest }: React.ComponentProps) => ( - -) - -/** The editor panel with the collapsed rail (Optimisation selected). */ -function Panel({ className, children }: { className?: string; children: React.ReactNode }) { - return ( - -
- {children} - -
-
- ) -} - -/** "Optimisation / Tables /" */ -const Breadcrumb = ({ className }: { className: string }) => ( -
- Optimisation - / - Tables - / -
-) - -/** Page title and its one-line description. */ -const Heading = ({ title, className }: { title: string; className: string }) => ( -
- {title} - - Select Catalog & Database to view tables & run optimisation - -
-) - -/** The light-blue "OLake" tag after a table name. */ -const OlakeTag = () => ( - - OLake - -) - -/* ---- Tiered Compaction (fusion-1) ----------------------------------------------------------- */ - -/** A table's last run of each tier (Quick, Standard, Deep), shown as Q, S and D tags. */ -type TierState = { - q: keyof typeof STATE_ICON - s: keyof typeof STATE_ICON - d: keyof typeof STATE_ICON -} -const STATE_ICON = { - running: { icon: 'feature-tables-running', className: 'bg-[#fff7e6] text-[#fa8c16]' }, - failed: { icon: 'feature-tables-failed', className: 'bg-[#fff1f0] text-[#f5222d]' }, - success: { icon: 'feature-tables-success', className: 'bg-[#f6ffed] text-[#52c41a]' } -} - -/** Tables under the selected one: name, size before and after, tier states (or not optimised), switch. */ -const TABLES: { - name: string - before: string - after: string - tiers?: TierState - on: boolean - tag?: boolean -}[] = [ - { name: 'Products_Table_Cerulean', before: '15 GB', after: '12 GB', on: false }, - { - name: 'Customers_Table_Azure', - before: '20 GB', - after: '16 GB', - tiers: { q: 'running', s: 'failed', d: 'failed' }, - on: true - }, - { - name: 'Shipments_Table_Indigo', - before: '25 GB', - after: '20 GB', - tiers: { q: 'success', s: 'success', d: 'success' }, - on: false - }, - { name: 'Payments_Table_Fuchsia', before: '30 GB', after: '24 GB', on: false }, - { name: 'Reviews_Table_Scarlet', before: '35 GB', after: '28 GB', on: false, tag: true } -] - -const TierTags = ({ tiers }: { tiers: TierState }) => ( - - {(['q', 's', 'd'] as const).map((key) => { - const state = STATE_ICON[tiers[key]] - return ( - - - {key.toUpperCase()} - - ) - })} - -) - -const RowButton = ({ - left, - width, - children -}: { - left: number - width?: number - children: string -}) => ( - - - {children} - - -) - -const Switch = ({ on }: { on: boolean }) => ( - - - - - -) - -const Health = ({ before, after, left }: { before: string; after: string; left: number }) => ( - - {before} - - {after} - -) - -const NotOptimised = ({ top }: { top: number }) => ( - - Not Optimised - -) - -const TABLE_HEADERS: [string, number, boolean][] = [ - ['Actions', 30.66, true], - ['Table', 60.33, false], - ['Health Score', 236.57, true], - ['Last Run Status', 344.66, true], - ['Metrics', 447.15, true], - ['Optimisation', 534.14, true], - ['Status', 615.61, true] -] - -/** The compaction tiers of the selected table: last run and state of each. */ -const TIERS = [ - { - name: 'Quick', - last: 'last run 6 hr ago', - state: 'Running', - icon: 'feature-tiers-running', - color: 'text-[#fa8c16]', - top: 8.4 - }, - { - name: 'Standard', - last: 'last run 32 min ago', - state: 'Failed', - icon: 'feature-tiers-failed', - color: 'text-[#f5222d]', - top: 36.76 - }, - { - name: 'Deep', - last: 'last run 32 min ago', - state: 'Success', - icon: 'feature-tiers-success', - color: 'text-[#52c41a]', - top: 65.12 - } -] - -export function TieredMock({ className }: { className?: string }) { - return ( - - -
- -
- {[ - ['OLake-default', 'text-[rgba(0,0,0,0.88)]'], - ['Select Database', 'text-[rgba(0,0,0,0.25)]'] - ].map(([label, color]) => ( - - - - {label} - - - - - ))} -
-
- -
-
- {TABLE_HEADERS.map(([label, x, centred]) => ( - - {label} - - ))} -
- - {/* The selected table, lifted */} -
- - - - Orders_Table_Veridian - - - - - - View Metrics - - Configure - - -
- - {TABLES.map((table, i) => ( -
- - {table.tag ? ( - - - {table.name} - - - - ) : ( - - {table.name} - - )} - - {table.tiers ? ( - - ) : ( - - )} - View Metrics - - Configure - - -
- ))} -
- - {/* The selected table's tiers */} -
- {TIERS.map((tier) => ( -
- - - {tier.name} - - - {tier.last} - - - - - {tier.state} - -
- ))} - - -
- - {/* The table runs on under a white fade at the panel's right edge */} - -
- ) -} - -/* ---- Omit Decay (fusion-2) ------------------------------------------------------------------ */ - -/** The maintenance steps, each card tucked under the one above. */ -const STEPS = [ - { - label: 'Metadata cleanup', - note: '1.6 Gb cleaned up', - noteLeft: 609.09, - top: 236.05, - done: false, - bg: 'bg-[#f0f0f0]', - text: 'text-[#a7a7a7]' - }, - { - label: 'Delete-file resolution', - note: 'Deleted 23m ago', - noteLeft: 616.36, - top: 181.51, - done: false, - bg: 'bg-[#f9f9f9]', - text: 'text-[#092b00]' - }, - { - label: 'Small-file compaction', - note: '12.7 GB cleaned up', - noteLeft: 598.18, - top: 123.32, - done: true, - bg: 'bg-white', - text: 'text-[#092b00]' - } -] - -export function DecayMock({ className }: { className?: string }) { - return ( - - {STEPS.map((step) => ( -
- - - {step.label} - - - {step.note} - -
- ))} - - - Products_Table_Cerulean - - - 10 GB - - -
- - - - Scheduled Maintenance Completed - - - - Last maintained · 2 minutes ago. - -
-
- ) -} - -/* ---- Easy Configuration (fusion-3) ---------------------------------------------------------- */ - -const Select = ({ value, className }: { value: string; className?: string }) => ( - - - {value} - - - - - -) - -/** A field label, set at the size the design renders it (its 5.3px spec is an instance scale). */ -const Label = ({ children }: { children: React.ReactNode }) => ( - - {children} - -) - -export function ConfigMock({ className }: { className?: string }) { - return ( - - - Configure Optimization - -
- - - Products_Table_Cerulean - - 10 GB -
- -
-
- Optimize - - Cleanup - -
-
- -
- - Cleanup Cron - - -
- - - - - - - -