diff --git a/app/(landing)/_components/landing-header.tsx b/app/(landing)/_components/landing-header.tsx index a9aab11..10ffe44 100644 --- a/app/(landing)/_components/landing-header.tsx +++ b/app/(landing)/_components/landing-header.tsx @@ -12,22 +12,17 @@ import { useLocale, useTranslations } from "next-intl"; import { type ReactNode, useState } from "react"; const landingHeaderCanvasWidthClassName = - "mx-auto grid h-12 w-full grid-cols-[148px_minmax(0,1fr)_88px] min-[640px]:grid-cols-[148px_minmax(0,1fr)_224px] min-[640px]:h-16 min-[768px]:max-w-[768px] min-[769px]:max-w-[1280px] min-[769px]:grid-cols-[152px_minmax(0,1fr)_224px]"; -const monoDisplayClassName = "font-[family-name:var(--font-mono-display)]"; + "mx-auto grid h-12 w-full grid-cols-[148px_minmax(0,1fr)_88px] min-[640px]:grid-cols-[148px_minmax(0,1fr)_88px] min-[640px]:h-16 min-[768px]:max-w-[768px] min-[769px]:max-w-[1280px] min-[769px]:grid-cols-[152px_minmax(0,1fr)_88px]"; type LandingNavItem = { href: string; - labelKey: "comparison" | "docs" | "github" | "playground" | "pricing" | "blog"; + labelKey: "docs" | "github"; external?: boolean; }; const landingNavItems: LandingNavItem[] = [ - { href: "#comparison", labelKey: "comparison" }, - { href: "https://notebook.knowhereto.ai", labelKey: "playground", external: true }, - { href: "#pricing", labelKey: "pricing" }, { href: "https://docs.knowhereto.ai/", labelKey: "docs", external: true }, { href: "/github", labelKey: "github" }, - { href: "https://blog.knowhereto.ai/", labelKey: "blog", external: true }, ]; const localeLabels = { @@ -36,12 +31,8 @@ const localeLabels = { } as const; const landingNavCtaIds: Record = { - blog: "blog_external", - comparison: "comparison", docs: "docs", github: "github", - playground: "playground_external", - pricing: "pricing", }; const LandingHeaderLink = ({ @@ -101,7 +92,7 @@ const LandingHeaderLink = ({ export const LandingHeader = () => { const activeSection = useActiveSection({ - ids: ["comparison", "pricing"], + ids: [], }); const locale = useLocale(); const t = useTranslations("Landing.header"); @@ -161,18 +152,6 @@ export const LandingHeader = () => { > - - {t("cta")} - {mobileMenuOpen ? ( diff --git a/app/(landing)/_components/landing-home.tsx b/app/(landing)/_components/landing-home.tsx index 160a6dc..b4bf62b 100644 --- a/app/(landing)/_components/landing-home.tsx +++ b/app/(landing)/_components/landing-home.tsx @@ -1,1015 +1,50 @@ "use client"; -import { ComparisonShowcase } from "@app/(landing)/_components/comparison-showcase"; -import { HeroPlayground } from "@app/(landing)/_components/hero-playground"; -import { IntegrateCodePanel } from "@app/(landing)/_components/integrate-code-panel"; import { LandingBrand } from "@app/(landing)/_components/landing-brand"; import { LandingHeader } from "@app/(landing)/_components/landing-header"; -import { - type ChallengeCard, - comingSoonFormats, - type FormatChip, - fileLimits, - getChallengeCards, - getEnterpriseItems, - getFaqItems, - getIntegrationSteps, - getPricingExamples, - getTransformMetrics, - getTransformSteps, - type MetricCard, - supportedFormats, - type TransformStep, -} from "@app/(landing)/_components/landing-home-data"; -import { - LandingTrackedAnchor, - LandingTrackedLink, -} from "@app/(landing)/_components/landing-tracked-link"; -import { NewsletterSubscribePrompt } from "@app/(landing)/_components/newsletter-subscribe-prompt"; -import { KnowhereIcon } from "@components/ui/knowhere-icon"; +import { LandingTrackedLink } from "@app/(landing)/_components/landing-tracked-link"; import { cn } from "@lib/utils"; -import Image from "next/image"; import { useTranslations } from "next-intl"; -import type { CSSProperties, ReactNode } from "react"; +import type { ReactNode } from "react"; -const sectionFrameClassName = - "overflow-hidden border-b border-l border-r border-zinc-200 bg-white dark:border-[#3f3f46] dark:bg-[#18181b]"; -const sectionPaddingClassName = "px-[48px] max-[639px]:px-[18px] min-[640px]:max-[767px]:px-[46px]"; -const heroSectionPaddingClassName = "px-16 max-[639px]:px-5"; -const landingCanvasWidthClassName = - "mx-auto flex w-full flex-col min-[768px]:max-w-[768px] min-[769px]:max-w-[976px] [&>*+*]:-mt-px"; const footerPaddingClassName = "px-12 py-6 max-[639px]:px-[18px] max-[639px]:py-[18px] min-[640px]:max-[767px]:px-[46px] min-[640px]:max-[767px]:py-6"; +const landingCanvasWidthClassName = + "mx-auto flex w-full flex-col min-[768px]:max-w-[768px] min-[769px]:max-w-[976px]"; const monoDisplayClassName = "font-[family-name:var(--font-mono-display)]"; -const monoReadableClassName = "font-[family-name:var(--font-mono-readable)]"; -const accentClassName = "font-[family-name:var(--font-accent)]"; -// const geistSansClassName = "font-[family-name:var(--font-geist-sans)]"; -// const anuphanClassName = "font-[family-name:var(--font-anuphan)]"; -const mobileActionLinkClassName = "h-[52px] px-7 text-[18px] leading-6"; - -const stripePattern = (color: string, thickness = 1, size = 8): CSSProperties => ({ - backgroundImage: `repeating-linear-gradient(-45deg, transparent 0 ${size - thickness}px, ${color} ${size - thickness}px ${size}px)`, -}); - -const cardStripePattern = (color: string): CSSProperties => ({ - backgroundImage: `repeating-linear-gradient(-45deg, ${color} 0 1px, transparent 1px 7px)`, -}); - -type ActionLinkProps = { - children: ReactNode; - href: string; - ctaId: string; - sourceSection: string; - variant?: "primary" | "secondary"; - size?: "sm" | "md"; - className?: string; - external?: boolean; -}; - -const ActionLink = ({ - children, - href, - ctaId, - sourceSection, - variant = "primary", - size = "md", - className, - external = false, -}: ActionLinkProps) => { - const sizeClassName = size === "sm" ? "h-16 px-7 text-base" : "h-[72px] px-9 text-xl"; - - const variantClassName = - variant === "primary" - ? "border border-b-[6px] border-[#7f22fe] bg-[#8e51ff] text-[#f5f3ff] [--btn-bottom:6px] hover:border-[#7008e7] hover:bg-[#7f22fe] hover:border-b-[8px] hover:[--btn-bottom:8px] active:border-[#7008e7] active:bg-[#7008e7] active:border-b-[6px] active:[--btn-bottom:6px]" - : "border-x-2 border-t-2 border-b-[6px] border-zinc-200 bg-[#fafaf9] text-zinc-800 [--btn-bottom:6px] hover:border-zinc-200 hover:bg-[#f5f5f4] hover:border-b-[8px] hover:[--btn-bottom:8px] active:border-[#e7e5e4] active:bg-[#e7e5e4] active:border-b-[6px] active:[--btn-bottom:6px] dark:border-[#3f3f46] dark:bg-[#27272a] dark:text-[#fafafa] dark:hover:border-[#52525b] dark:hover:bg-[#3f3f46] dark:active:border-[#3f3f46] dark:active:bg-[#27272a]"; +const RegisterLoginButton = ({ children }: { children: ReactNode }) => { const linkClassName = cn( - "group inline-flex items-center justify-center rounded-full transition-[background-color,border-color,border-bottom-width]", - monoDisplayClassName, - sizeClassName, - variantClassName, - className + "group inline-flex h-[72px] items-center justify-center rounded-full border border-b-[6px] border-[#7f22fe] bg-[#8e51ff] px-9 text-xl text-[#f5f3ff] transition-[background-color,border-color,border-bottom-width] hover:border-[#7008e7] hover:bg-[#7f22fe] hover:border-b-[8px] active:border-[#7008e7] active:bg-[#7008e7] active:border-b-[6px]", + monoDisplayClassName ); - const linkChildren = ( - - {children} - - ); - - if (href.startsWith("mailto:")) { - return ( - - {linkChildren} - - ); - } - return ( - {linkChildren} - - ); -}; - -type HeroAnnouncementNoticeProps = { - children: ReactNode; - ctaId: string; - href: string; - linkLabel: ReactNode; - sourceSection: string; - ariaLabel?: string; - external?: boolean; -}; - -const HeroAnnouncementNotice = ({ - children, - ctaId, - href, - linkLabel, - sourceSection, - ariaLabel, - external = false, -}: HeroAnnouncementNoticeProps) => ( -
-
- {children} -
-
- - - - {linkLabel} - - + + {children} -
-); - -const SectionTitle = ({ - title, - description, - className, - descriptionClassName, -}: { - title: ReactNode; - description?: ReactNode; - className?: string; - descriptionClassName?: string; -}) => ( -
-

- {title} -

- {description ? ( -

- {description} -

- ) : null} -
-); - -const StatsItem = ({ - icon, - leading, - trailing, - leadingClassName, -}: { - icon: ReactNode; - leading: string; - trailing: string; - leadingClassName?: string; -}) => ( -
-
- {icon} -
-
- - {leading} - - {trailing} -
-
-); - -const CornerLines = ({ color }: { color: string }) => ( - <> - - - - - -); - -const FormatBadge = ({ - chip, - muted = false, - value, -}: { - chip: FormatChip; - muted?: boolean; - value?: string; -}) => ( -
-
-
- - {chip.label} - - -
- {value ? {value} : null} -
-); - -const NumberBadge = ({ number }: { number: string }) => ( -
- {number} -
-); - -const ChallengeIcon = ({ card }: { card: ChallengeCard }) => { - const iconPathByType: Record = { - agentic: "/icons/landing/challenges/1.svg", - adaptive: "/icons/landing/challenges/2.svg", - format: "/icons/landing/challenges/3.svg", - trace: "/icons/landing/challenges/4.svg", - deploy: "/icons/landing/challenges/5.svg", - api: "/icons/landing/challenges/6.svg", - }; - - return ( - ); }; -const TransformStepCard = ({ step }: { step: TransformStep }) => ( -
-
- - {step.number} - -
-
-
- - {step.title} - - - {step.description} - -
-
-
-
-
-
-); - -const MetricPanel = ({ card }: { card: MetricCard }) => ( -
- - {card.value} - - - {card.label} - -
-); - -const PricingBurst = ({ label }: { label: string }) => ( -
-
-
-
- $1.5 -
-
- {label} -
-
-
-); - -const EnterpriseCheckItem = ({ label }: { label: string }) => ( -
-
- -
- - {label} - -
-); - -const FaqRow = ({ question, answer }: { question: string; answer: string }) => ( -
-
- ? -
-
-

- {question} -

-

- {answer} -

-
-
-); - -const FooterChip = ({ color, children }: { color: string; children: string }) => ( -

- {`{ `} - {children} - {` }`} -

-); - export const LandingHome = () => { - const t = useTranslations("Landing.home"); - const tData = useTranslations("Landing.data"); - const challengeCards = getChallengeCards(tData); - const enterpriseItems = getEnterpriseItems(tData); - const faqItems = getFaqItems(tData); - const integrationSteps = getIntegrationSteps(tData); - const pricingExamples = getPricingExamples(tData); - const transformMetrics = getTransformMetrics(tData); - const transformSteps = getTransformSteps(tData); + const t = useTranslations("Landing.header"); + const tHome = useTranslations("Landing.home"); return (
-
-
-
-
-
- -
- - {t("mcpAnnouncement.title")} - - - {t("mcpAnnouncement.description")} - -
-
-
- -
-
-

- {t("hero.titleStart")} {t("hero.clean")} - , {t("hero.structured")}{" "} - {t("hero.titleEnd")} -

-

- {t("hero.description")} -

-
-
- -
- - {t("actions.startFreeTrial")} - - - {t("actions.viewDocs")} - -
-
-
- -
- - } - trailing={t("stats.noCardRequiredTrailing")} - leading={t("stats.noCardRequiredLeading")} - leadingClassName="font-bold" - /> -
- -
- - -
-
- -
-
- -
- {supportedFormats.map((chip) => ( -
-
- - {chip.label} - - -
- ))} -
-
- -
-

- {t("formats.comingSoon")} -

-
- {comingSoonFormats.map((chip) => ( -
-
- - {chip.label} - - -
- ))} -
-
-
- -
-
- - -
-
- {integrationSteps.map((step) => ( -
- -
-

- {step.title} -

-

- {step.description} -

-
-
- ))} -
- - -
-
-
- -
-
- - - -
-
- -
-
- - {t("challenges.titleStart")}{" "} - {t("challenges.titleHighlight")} - - } - /> -
- -
- {challengeCards.map((card, index) => ( -
-
-
-
- -
-
-

- {card.title} -

-

- {card.description} -

-
-
-
- ))} -
-
- -
-
- - {t("transform.titleStart")}{" "} - {t("transform.titleHighlight")} - - } - /> - -
-
- {transformSteps.map((step, index) => ( -
- - {index < transformSteps.length - 1 ? ( - - : - - ) : null} -
- ))} -
- -
- {transformMetrics.map((metric) => ( - - ))} -
-
-
-
- -
-
-
-

- - {t("pricing.titleStart")}{" "} - - {t("pricing.titleHighlight")} -

-

- {t("pricing.description")} -

-
- -
-
-
- -
-
-

{t("pricing.bodyLineOne")}

-

{t("pricing.bodyLineTwo")}

-
-
- -
-
- {pricingExamples.map((example, index) => ( -
-
-
- - {example.value} - -

- {example.label} -

-
-
- ))} -
-
-
-
-

- {t("fileLimits.title")} -

-

- {t("fileLimits.contactStart")}{" "} - - @knowhereto.ai - {" "} - {t("fileLimits.contactEnd")} -

-
-
- -
- {fileLimits.map((limit, index) => ( -
- -
- ))} -
-
-
-
-
-
- -
-
-
-
-
-
- - {t("enterprise.badge")} - -
-

- {t("enterprise.titleStart")}{" "} - {t("enterprise.titleHighlight")}? -

-
-

- {t("enterprise.description")} -

-
-
- - {t("actions.contactSales")} - -
-
- -
- {enterpriseItems.map((item) => ( - - ))} -
-
- -
-
- - {t("faq.titleStart")}{" "} - {t("faq.titleHighlight")} - - } - /> -
- {faqItems.map((faq) => ( - - ))} -
-
-
- -
-
- - {t("finalCta.titleStart")}{" "} - {t("finalCta.titleHighlight")}? - - } - /> - -
- - {t("actions.startFreeTrial")} - - - {t("actions.bookDemo")} - -
- -
- {t("finalCta.noCreditCard")} - {t("finalCta.freeTrial")} - {t("finalCta.cancelAnytime")} -
+
+
+ {t("cta")}
@@ -1021,9 +56,8 @@ export const LandingHome = () => { )} > -

{t("footer.copyright")}

+

{tHome("footer.copyright")}

-
); }; diff --git a/i18n/locales/en.json b/i18n/locales/en.json index c14bf3b..29357a6 100644 --- a/i18n/locales/en.json +++ b/i18n/locales/en.json @@ -523,7 +523,7 @@ "github": "GitHub", "blog": "Blog" }, - "cta": "GET API KEY", + "cta": "Register / Log in", "openNavigationMenu": "Open navigation menu" }, "newsletter": { diff --git a/i18n/locales/zh.json b/i18n/locales/zh.json index ed7526c..da42e5f 100644 --- a/i18n/locales/zh.json +++ b/i18n/locales/zh.json @@ -524,7 +524,7 @@ "github": "GitHub", "blog": "博客" }, - "cta": "获取 API Key", + "cta": "注册 / 登录", "openNavigationMenu": "打开导航菜单" }, "newsletter": { diff --git a/lib/landing-figma-design.test.ts b/lib/landing-figma-design.test.ts index 4fe6493..0a5cde7 100644 --- a/lib/landing-figma-design.test.ts +++ b/lib/landing-figma-design.test.ts @@ -13,24 +13,19 @@ describe("landing contracts", () => { expect(landingPageSource).not.toContain("ClawPage"); }); - it("keeps the landing header connected to the product playground and repository", () => { + it("keeps the landing header connected to the documentation and repository", () => { const landingHeaderSource: string = readFileSync( join(process.cwd(), "app/(landing)/_components/landing-header.tsx"), "utf8" ); expect(landingHeaderSource).toContain( - '{ href: "https://notebook.knowhereto.ai", labelKey: "playground", external: true }' + '{ href: "https://docs.knowhereto.ai/", labelKey: "docs", external: true }' ); expect(landingHeaderSource).toContain('{ href: "/github", labelKey: "github" }'); - expect(landingHeaderSource).toContain( - '{ href: "https://blog.knowhereto.ai/", labelKey: "blog", external: true }' - ); - expect(landingHeaderSource.indexOf('{ href: "/github", labelKey: "github" }')).toBeLessThan( - landingHeaderSource.indexOf( - '{ href: "https://blog.knowhereto.ai/", labelKey: "blog", external: true }' - ) - ); + expect(landingHeaderSource).not.toContain('labelKey: "playground"'); + expect(landingHeaderSource).not.toContain('labelKey: "pricing"'); + expect(landingHeaderSource).not.toContain('labelKey: "blog"'); }); it("keeps the playground sample area annotated with the drag-to-parse cue", () => {