diff --git a/packages/landing/scripts/gen-landing-snippets.ts b/packages/landing/scripts/gen-landing-snippets.ts index b3bd5a8c4..76af0194a 100644 --- a/packages/landing/scripts/gen-landing-snippets.ts +++ b/packages/landing/scripts/gen-landing-snippets.ts @@ -127,18 +127,35 @@ function githubTreeUrl(slug: string): string { /* -------------------------------------------------------------------------- */ /** - * Slice the leading `import ... from "@lobu/cli/config";` block out of a config file. - * Returns the import statement lines (the first `import` through its closing - * `from "...";`), or an empty array if none is found. + * Slice the leading import section out of a config file: every contiguous + * `import ...` statement (including `import type X from "./*.ts"`), tolerating + * blank lines between them. Capturing the type imports too means generic + * references shown in the snippet (e.g. `connectorFromFile`) resolve + * to a visible import rather than reading as undefined symbols. Returns an + * empty array if no import is found. */ function sliceImportBlock(raw: string): string[] { const lines = raw.split("\n"); const start = lines.findIndex((l) => /^\s*import\b/.test(l)); if (start < 0) return []; let end = start; + let inStatement = true; for (let i = start; i < lines.length; i++) { - end = i; - if (/;\s*$/.test(lines[i])) break; + const line = lines[i]; + if (inStatement) { + end = i; + if (/;\s*$/.test(line)) inStatement = false; + continue; + } + if (/^\s*import\b/.test(line)) { + inStatement = true; + end = i; + if (/;\s*$/.test(line)) inStatement = false; + } else if (line.trim() === "") { + // tolerate blank lines between import statements + } else { + break; + } } return lines.slice(start, end + 1); } @@ -253,12 +270,28 @@ function fileSnippet( }; } -/** The imports + the first `defineAgent({...})` block, the representative - * agent slice for the landing "Agents" section. */ +/** The imports + `defineAgent` + a real `defineWatcher` + the `defineConfig` + * manifest that wires connectors, entities, watchers, and agents together. + * Only the repetitive entity/relationship definitions are elided (behind a + * comment), so the snippet shows the differentiated piece (a watcher's prompt + * + extraction schema) and stays one coherent control-plane file. The + * homepage shows this as the single "it's real" anchor. */ function agentConfigSlice(raw: string): string { const imports = sliceImportBlock(raw); const agent = sliceDefineCall(raw, "defineAgent"); - return [...imports, "", ...agent].join("\n"); + const watcher = sliceDefineCall(raw, "defineWatcher"); + const config = sliceDefineCall(raw, "defineConfig"); + return [ + ...imports, + "", + ...agent, + "", + "// entity types and relationships defined here…", + "", + ...watcher, + "", + ...config, + ].join("\n"); } function entitySlice(raw: string): string { diff --git a/packages/landing/src/components/CodeBlock.tsx b/packages/landing/src/components/CodeBlock.tsx index 60cab8f34..100247d73 100644 --- a/packages/landing/src/components/CodeBlock.tsx +++ b/packages/landing/src/components/CodeBlock.tsx @@ -1,4 +1,5 @@ import type { ComponentChildren } from "preact"; +import { useState } from "preact/hooks"; export type CodeSnippet = { code: string; @@ -15,6 +16,14 @@ type CodeBlockProps = { badge?: string; /** Extra footer text rendered next to the GitHub link. */ footnote?: ComponentChildren; + /** + * When true, the code body collapses behind a "show the code" toggle so the + * page reads value-first for non-TS skimmers. The tab bar (filename + line + * count) stays visible as the affordance. Defaults to false (always open). + */ + collapsible?: boolean; + /** Initial open state when `collapsible`. Defaults to false (collapsed). */ + defaultOpen?: boolean; }; type Token = { kind: TokenKind; text: string }; @@ -443,63 +452,110 @@ export function CodeBlock({ tabLabel, badge, footnote, + collapsible = false, + defaultOpen = false, }: CodeBlockProps) { const lines = highlight(snippet.code, snippet.language); + const [open, setOpen] = useState(defaultOpen); + const showBody = !collapsible || open; - return ( + const tabBar = (
-
- {tabLabel ?? snippet.path} + {tabLabel ?? snippet.path} + {badge ? ( {badge} ) : null} -
- -
-        
-          {lines.map((toks, idx) => (
-            
-              {tokensToJsx(toks)}
-              {idx < lines.length - 1 ? "\n" : ""}
+        {collapsible ? (
+          
+            {open ? "hide code" : "show the code"}
+            
+          
+        ) : null}
+      
+    
+ ); + + return ( +
+ {collapsible ? ( + + ) : ( + tabBar + )} + + {showBody ? ( + <> +
+            
+              {lines.map((toks, idx) => (
+                
+                  {tokensToJsx(toks)}
+                  {idx < lines.length - 1 ? "\n" : ""}
+                
+              ))}
+            
+          
+ +
+ {lines.length} lines + + {footnote} + + see on github → + - ))} - - - -
- {lines.length} lines - - {footnote} - - see on github → - - -
+
+ + ) : null}
); } diff --git a/packages/landing/src/components/LandingPage.tsx b/packages/landing/src/components/LandingPage.tsx index 22ffd8b2c..95e0b8160 100644 --- a/packages/landing/src/components/LandingPage.tsx +++ b/packages/landing/src/components/LandingPage.tsx @@ -6,6 +6,7 @@ import { ArchitectureDiagram } from "./ArchitectureDiagram"; import { CodeBlock, type CodeSnippet } from "./CodeBlock"; import { CTA } from "./CTA"; import { LatestBlogPosts, type LatestBlogPost } from "./LatestBlogPosts"; +import { ProactiveLoop } from "./ProactiveLoop"; type ExampleEntry = { slug: string; @@ -48,6 +49,9 @@ Lobu is an open-source event-sourced backend for AI agents: connectors emit even const GITHUB_URL = "https://github.com/lobu-ai/lobu"; +// The canonical "test it" command, kept in sync with InstallSection. +const QUICKSTART_CMD = "npx @lobu/cli@latest init my-agent"; + export function LandingPage(props: { latestPosts?: LatestBlogPost[]; defaultUseCaseId?: string; @@ -62,17 +66,40 @@ export function LandingPage(props: { const memorySchemaSnippet = uc?.memorySchema ?? snippets.memorySchema; const watcherSnippet = uc?.watcher ?? snippets.watcher; + // The canonical homepage stays benefit-led: outcome artifact + a plain + // 3-step explanation, with the deep code living in the docs. The + // /for/ and /connect-from SEO pages keep the per-primitive code + // sections, which is their whole purpose. + const isHome = !props.defaultUseCaseId; + return ( <> - - - - - - - - + {isHome ? ( + <> + + + + + + ) : ( + <> + + + + + + + + + + )} @@ -132,66 +159,63 @@ function SectionHeading(props: { /* -------------------------------------------------------------------------- */ function Hero() { - const [copied, setCopied] = useState(false); + // Tracks which of the two copy actions fired last, so each shows its own + // confirmation: the quickstart command (primary) or the setup prompt (sub). + const [copied, setCopied] = useState<"cmd" | "prompt" | null>(null); - const onCopy = async () => { + const copy = async (which: "cmd" | "prompt") => { try { - await navigator.clipboard.writeText(SETUP_PROMPT); - setCopied(true); - window.setTimeout(() => setCopied(false), 2200); + await navigator.clipboard.writeText( + which === "cmd" ? QUICKSTART_CMD : SETUP_PROMPT + ); + setCopied(which); + window.setTimeout(() => setCopied(null), 2200); } catch { - setCopied(false); + setCopied(null); } }; return (
- - Open source · TypeScript · Postgres · Multi-tenant · BYO model -

Build{" "} proactive {" "} - agents on a + AI agents on a graph
+ that{" "} - self-building - {" "} - knowledge graph + builds itself +

- An automated pipeline streams every event into the graph. Agents react - to changes, take action, and reach people as bots, or other agents - over MCP and HTTP. + Connect your company's data in real time, plug in your model, and let + your agents act the moment something changes, as a bot, an API, or + another agent.

-
+
- or paste the prompt into claude code,{" "} + Paste it into claude code,{" "} cursor, or{" "} - opencode, it'll scaffold the project - for you + opencode, and it scaffolds the project + for you. +

+

+ Or start it yourself: + + {copied === "cmd" ? copied : null}

@@ -329,6 +373,142 @@ function BrowseExamplesSection() { ); } +/* -------------------------------------------------------------------------- */ +/* How it works: the benefit-led, mostly-code-free homepage explainer. */ +/* Three plain steps + a single collapsed lobu.config.ts as the "it's real" */ +/* anchor. The per-primitive code deep-dives live in the docs (linked). */ +/* -------------------------------------------------------------------------- */ + +function HowItWorks() { + const steps: Array<{ + n: string; + title: string; + body: preact.ComponentChildren; + link: { href: string; label: string }; + }> = [ + { + n: "1", + title: "Connect your data, in real time", + body: ( + <> + Stream company data the moment it happens: 50+ built-in connectors, + any MCP server, or your own in TypeScript. On-device connectors even + capture context no cloud agent can see. + + ), + link: { + href: "/getting-started/connector-sdk/", + label: "Connecting data", + }, + }, + { + n: "2", + title: "It builds itself into memory", + body: ( + <> + Watchers turn the raw stream into typed, queryable records, the moment + events arrive or on a schedule. You describe what to track in plain + language; there's no ETL to maintain. + + ), + link: { href: "/getting-started/memory/", label: "Watchers & memory" }, + }, + { + n: "3", + title: "Agents act where your team works", + body: ( + <> + On the model you choose, agents respond and flag what matters the + moment memory changes, right where your team already works, as a Slack + bot, an API, or another agent. + + ), + link: { href: "/getting-started/", label: "Building agents" }, + }, + ]; + + return ( + +
+ How it works + + From your data to an agent that acts. + +

+ Three steps. No data pipeline to wire up, no glue code to maintain. +

+
+ +
+ {steps.map((step) => ( +
+
+ {step.n} +
+

+ {step.title} +

+

+ {step.body} +

+ {step.link.label} +
+ ))} +
+ +
+

+ It's all one typed file.{" "} + lobu apply deploys it. +

+ +
+ +
+

+ Curious how Lobu stacks up against other agent runtimes?{" "} + + See the comparison + +

+
+
+ ); +} + /* -------------------------------------------------------------------------- */ /* Product sections (Connectors / Memory / Watchers / Agents) */ /* -------------------------------------------------------------------------- */ @@ -474,7 +654,7 @@ function ConnectorsSection({ } code={
- +
} @@ -583,7 +763,7 @@ function MemorySection({ } code={
- +
} @@ -615,7 +795,8 @@ function WatchersSection({ A watcher is a prompt +{" "} extraction_schema. Lobu runs the LLM, validates, and persists the output to memory.{" "} - No application code: fire on events, or run on cron. + No application code for extraction: fire on events, or run + on cron.

- + Watchers guide Reaction SDK docs
- + } code={ @@ -658,6 +843,7 @@ function WatchersSection({ @@ -725,7 +911,7 @@ function SkillsSection() { } code={
} diff --git a/packages/landing/src/components/ProactiveLoop.tsx b/packages/landing/src/components/ProactiveLoop.tsx new file mode 100644 index 000000000..2b5a1f851 --- /dev/null +++ b/packages/landing/src/components/ProactiveLoop.tsx @@ -0,0 +1,176 @@ +/** + * Outcome artifact for the homepage: shows the proactive loop end to end + * WITHOUT any code, so a non-TS reader gets the value in one glance. + * + * raw events ──► graph derives a typed entity ──► agent acts (surfaces in chat) + * + * The graph (left) is the substance; the chat (right) is just one surface. + * Reuses SampleChat with a custom proactive (bot-first) message list. + */ + +import type { UseCase } from "../types"; +import { SampleChat, SLACK_THEME } from "./SampleChat"; + +// Proactive: the agent speaks first, unprompted, because the graph changed. +const PROACTIVE_SCENARIO: UseCase = { + id: "proactive-renewal", + tabLabel: "Renewal risk", + title: "Proactive renewal risk", + description: "The agent flags churn risk before anyone asks.", + settingsLabel: "", + chatLabel: "", + botName: "Revenue agent", + botInitial: "R", + botColor: "#36c5ab", + messages: [ + { + role: "bot", + text: "Heads up: Acme Corp is trending toward churn. Logins are down 38% over 14 days and their renewal is in 21 days.\n\nWant me to draft a check-in for their CSM?", + buttons: [{ label: "Draft the email", action: "link" }], + }, + { + role: "user", + text: "Yes, and include the usage drop.", + }, + { + role: "bot", + text: "Drafted. Saved it to the Acme account and pinged @dana.", + }, + ], +}; + +// The "what the graph knew" record that triggered the message above. Field +// names match the sales example's entity types (account / renewal-risk). +const GRAPH_FIELDS: ReadonlyArray = [ + ["account", "Acme Corp"], + ["health", "at risk"], + ["signal", "logins −38% / 14d"], + ["renewal_in", "21 days"], +]; + +function GraphCard() { + return ( +
+
+ + Memory · entity + + + account + +
+ +
+ {GRAPH_FIELDS.map((row, idx) => ( +
+ + {row[0]} + + {row[1]} +
+ ))} +
+ +
+ derived from 1,204 raw events by a watcher, no app code +
+
+ ); +} + +// Small labelled arrow: "graph changed → agent acts". Horizontal on desktop, +// vertical (rotated) on mobile so the two cards stack cleanly. +function FlowArrow() { + return ( +
+ + acts + + +
+ ); +} + +export function ProactiveLoop() { + return ( +
+
+
+ What it does +
+

+ The graph notices. The agent acts. +

+

+ Raw events stream in and build a typed record. When it crosses a line + you set, an agent flags it on its own and proposes the next step. Chat + is just one surface; the same loop fires over MCP or HTTP. +

+
+ +
+ + + +
+
+ ); +} diff --git a/packages/landing/src/generated/landing-snippets.json b/packages/landing/src/generated/landing-snippets.json index 33b96cd4b..f92e32650 100644 --- a/packages/landing/src/generated/landing-snippets.json +++ b/packages/landing/src/generated/landing-snippets.json @@ -24,7 +24,7 @@ "language": "typescript" }, "agentConfig": { - "code": "import {\n connectorFromFile,\n defineAgent,\n defineConfig,\n defineEntityType,\n defineRelationshipType,\n defineWatcher,\n reactionFromFile,\n secret,\n} from \"@lobu/cli/config\";\n\nconst sales = defineAgent({\n id: \"sales\",\n name: \"sales\",\n description:\n \"Help revenue teams track account health, rollout progress, and renewal signals\",\n dir: \".\",\n providers: [\n {\n id: \"anthropic\",\n model: \"claude/sonnet-4-5\",\n key: secret(\"ANTHROPIC_API_KEY\"),\n },\n ],\n network: {\n allowed: [\n \"github.com\",\n \".github.com\",\n \".githubusercontent.com\",\n \"registry.npmjs.org\",\n \".npmjs.org\",\n ],\n },\n});", + "code": "import {\n connectorFromFile,\n defineAgent,\n defineConfig,\n defineEntityType,\n defineRelationshipType,\n defineWatcher,\n reactionFromFile,\n secret,\n} from \"@lobu/cli/config\";\nimport type SalesforcePipelineConnector from \"./salesforce-pipeline.connector.ts\";\nimport type accountHealthMonitorReaction from \"./account-health-monitor.reaction.ts\";\n\nconst sales = defineAgent({\n id: \"sales\",\n name: \"sales\",\n description:\n \"Help revenue teams track account health, rollout progress, and renewal signals\",\n dir: \".\",\n providers: [\n {\n id: \"anthropic\",\n model: \"claude/sonnet-4-5\",\n key: secret(\"ANTHROPIC_API_KEY\"),\n },\n ],\n network: {\n allowed: [\n \"github.com\",\n \".github.com\",\n \".githubusercontent.com\",\n \"registry.npmjs.org\",\n \".npmjs.org\",\n ],\n },\n});\n\n// entity types and relationships defined here…\n\nconst accountHealthMonitor = defineWatcher({\n agent: sales,\n slug: \"account-health-monitor\",\n name: \"Account health monitor\",\n schedule: \"0 */12 * * *\",\n notification: { priority: \"high\", channel: \"both\" },\n tags: [\"sales\", \"health\", \"renewals\"],\n minCooldownSeconds: 1800,\n reaction: reactionFromFile(\n \"./account-health-monitor.reaction.ts\"\n ),\n prompt:\n \"Poll CRM data for tracked accounts. Track expansion progress, risk level changes, and renewal timeline.\\n\",\n extractionSchema: {\n type: \"object\",\n required: [\n \"risk_level\",\n \"expansion_status\",\n \"renewal_blockers\",\n \"activity_delta\",\n ],\n properties: {\n risk_level: { type: \"string\" },\n expansion_status: { type: \"string\" },\n renewal_blockers: { type: \"array\", items: { type: \"string\" } },\n activity_delta: { type: \"string\" },\n },\n },\n});\n\nexport default defineConfig({\n connectors: [\n connectorFromFile(\n \"./salesforce-pipeline.connector.ts\"\n ),\n ],\n org: \"sales\",\n orgName: \"Sales\",\n orgDescription:\n \"Help revenue teams track account health, rollout progress, and renewal signals\",\n agents: [sales],\n entities: [organization, product, region, renewalRisk, team],\n relationships: [affects, expandedInto, runs],\n watchers: [accountHealthMonitor],\n});", "path": "lobu.config.ts", "githubUrl": "https://github.com/lobu-ai/lobu/blob/main/examples/sales/lobu.config.ts", "language": "typescript" diff --git a/packages/landing/src/globals.css b/packages/landing/src/globals.css index 82161f1dd..b1e41127c 100644 --- a/packages/landing/src/globals.css +++ b/packages/landing/src/globals.css @@ -411,7 +411,6 @@ button.chat-action-btn:hover { .hero-rise-2 { animation-delay: 80ms; } .hero-rise-3 { animation-delay: 160ms; } .hero-rise-4 { animation-delay: 240ms; } -.hero-rise-5 { animation-delay: 320ms; } /* Scroll-reveal: applied to elements observed by useReveal(). Only transform/opacity — no layout shift. */ @@ -435,8 +434,7 @@ button.chat-action-btn:hover { .hero-rise-1, .hero-rise-2, .hero-rise-3, - .hero-rise-4, - .hero-rise-5 { + .hero-rise-4 { animation: none; opacity: 1; transform: none; diff --git a/packages/landing/src/layouts/BaseLayout.astro b/packages/landing/src/layouts/BaseLayout.astro index 53fc5fb82..23bdb7850 100644 --- a/packages/landing/src/layouts/BaseLayout.astro +++ b/packages/landing/src/layouts/BaseLayout.astro @@ -13,7 +13,7 @@ interface Props { } const { - title = "Lobu - Build proactive agents on a self-building knowledge graph", + title = "Lobu - Open-source backend for proactive AI agents", description = "Open-source backend for proactive AI agents. Connectors stream events into an append-only memory. LLM watchers shape them into a self-building knowledge graph your agent can search and cite.", ogType = "website", article, diff --git a/packages/landing/src/use-case-showcases.ts b/packages/landing/src/use-case-showcases.ts index 8b2e9572c..05acadb3c 100644 --- a/packages/landing/src/use-case-showcases.ts +++ b/packages/landing/src/use-case-showcases.ts @@ -41,23 +41,6 @@ type CampaignMeta = { ctaLabel: string; }; -type SurfaceId = "landing" | "skills" | "memory"; - -export type SurfaceHeroCopy = { - title: string; - highlight?: string; - description: string; -}; - -type SurfaceHeroCopyOverride = Partial & { - title: string; -}; - -type SurfaceHeroCopyConfig = { - default: SurfaceHeroCopy; - byUseCase?: Partial>; -}; - type ShowcaseSkillWorkspacePreview = SkillWorkspacePreviewData & { useCaseId: LandingUseCaseId; examplePath: string; @@ -2106,163 +2089,7 @@ export const landingUseCaseRouteEntries: Array<{ { routeId: "market-intelligence", useCaseId: "market" }, ]; -export const DEFAULT_LANDING_USE_CASE_ID: LandingUseCaseId = "market"; - -const surfaceHeroCopy: Record = { - landing: { - default: { - title: "Open-source backend for multi-user AI agents", - highlight: "multi-user AI agents", - description: - "Multi-user agents with isolated workers, connected sources, shared memory, and secrets agents never see.", - }, - byUseCase: { - legal: { - title: "Trusted AI agents for contract review", - highlight: "contract review", - description: - "Sandboxed legal agents with durable contract memory, secure tool access, and full control in your infrastructure.", - }, - finance: { - title: "Finance agents for close and reconciliation", - highlight: "close and reconciliation", - description: - "Run finance workflows with agents that preserve account context, track exceptions, and operate safely inside your environment.", - }, - sales: { - title: "Revenue agents for every account", - highlight: "every account", - description: - "Track pilots, buying signals, renewal risk, and account history with agents that keep shared deal context over time.", - }, - delivery: { - title: "Delivery agents for project execution", - highlight: "project execution", - description: - "Give teams agents that track milestones, blockers, ownership, and reporting context across the full rollout lifecycle.", - }, - leadership: { - title: "Leadership agents for decision support", - highlight: "decision support", - description: - "Turn documents, decisions, blockers, and assignments into reusable context for faster executive follow-through.", - }, - "agent-community": { - title: "Community agents for member matching", - highlight: "member matching", - description: - "Build agents that understand member identity, interests, relationships, and intent across your community's real activity.", - }, - ecommerce: { - title: "Ecommerce agents for customer operations", - highlight: "customer operations", - description: - "Run ecommerce workflows with agents that connect store systems, preserve customer context, and act with current operational state.", - }, - market: { - title: "Investment agents for deal flow", - highlight: "deal flow", - description: - "Track firms, partners, deals, and diligence signals with agents that keep investment context structured and reusable.", - }, - }, - }, - skills: { - default: { - title: "Build reliable agents with skills", - highlight: "skills", - description: - "A skill isn't a prompt template, it's a full sandboxed computer. All capabilities bundled into one installable unit.", - }, - byUseCase: { - legal: { title: "Skills for secure legal workflows" }, - finance: { title: "Skills for finance workflows" }, - sales: { title: "Skills for account and pipeline agents" }, - delivery: { title: "Skills for rollout and status workflows" }, - leadership: { title: "Skills for executive workflows" }, - "agent-community": { title: "Skills for community workflows" }, - ecommerce: { title: "Skills for ecommerce workflows" }, - market: { title: "Skills for sourcing and diligence agents" }, - }, - }, - memory: { - default: { - title: "Build long-term collective memory", - highlight: "collective memory", - description: - "Lobu gives all your agents the same durable graph: connectors, recall, and managed auth without leaking credentials to the runtime.", - }, - byUseCase: { - legal: { title: "Contract memory for legal agents" }, - finance: { title: "Structured finance memory for every close" }, - sales: { title: "Account memory for revenue teams" }, - delivery: { title: "Project memory for delivery teams" }, - leadership: { title: "Decision memory for leadership agents" }, - "agent-community": { title: "Member memory for community agents" }, - ecommerce: { title: "Customer memory for store agents" }, - market: { title: "Deal memory for venture teams" }, - }, - }, -}; - -type LandingUseCaseRole = "departments" | "personal" | "public"; - -const useCaseRoleMap: Record = { - legal: "departments", - finance: "departments", - sales: "departments", - delivery: "departments", - ecommerce: "departments", - leadership: "personal", - market: "personal", - "agent-community": "public", -}; - -const useCaseEmojiMap: Record = { - legal: "\u2696\uFE0F", - finance: "\uD83D\uDCCA", - sales: "\uD83D\uDCC8", - delivery: "\uD83D\uDCE6", - leadership: "\uD83E\uDDED", - ecommerce: "\uD83D\uDED2", - market: "\uD83D\uDCBC", - "agent-community": "\uD83E\uDD1D", -}; - -const landingUseCaseRoleMeta: Array<{ - id: LandingUseCaseRole; - label: string; - description: string; -}> = [ - { - id: "departments", - label: "Company", - description: "Team agents with shared memory across roles and tools.", - }, - { - id: "personal", - label: "Personal", - description: "Solo memory: your own decisions, deals, and context.", - }, - { - id: "public", - label: "Public", - description: "Community-scale memory: members, markets, open knowledge.", - }, -]; - -export const landingUseCaseGroupedOptions = landingUseCaseRoleMeta - .map((role) => ({ - ...role, - useCases: landingUseCaseShowcases - .filter((uc) => useCaseRoleMap[uc.id] === role.id) - .map((uc) => ({ - id: uc.id, - label: uc.label, - emoji: useCaseEmojiMap[uc.id], - })), - })) - .filter((group) => group.useCases.length > 0); +const DEFAULT_LANDING_USE_CASE_ID: LandingUseCaseId = "market"; export function getLandingUseCaseShowcase( useCaseId?: string @@ -2276,19 +2103,6 @@ export function getLandingUseCaseShowcase( ); } -export function getSurfaceHeroCopy( - surface: SurfaceId, - useCaseId?: LandingUseCaseId -): SurfaceHeroCopy { - const config = surfaceHeroCopy[surface]; - const useCaseCopy = useCaseId ? config.byUseCase?.[useCaseId] : undefined; - - return { - ...config.default, - ...useCaseCopy, - }; -} - const LOBU_ZONE = (import.meta.env.PUBLIC_LOBU_ZONE as string | undefined) || "lobu.ai"; const LOBU_APP_OVERRIDE = ( @@ -2313,10 +2127,6 @@ function getLobuOrgSlug(useCaseId?: LandingUseCaseId) { return "lobuOrg" in def ? def.lobuOrg : undefined; } -export function getLobuUrl(useCaseId?: LandingUseCaseId) { - return buildOrgUrl(getLobuOrgSlug(useCaseId)); -} - export function getLobuMcpUrl() { return `${LOBU_APP_BASE_URL}/mcp`; }