Rebrand app to CrawlerX / MetatronCube theme
Some checks failed
CI / ci (push) Has been cancelled
CI / docker-build (push) Has been cancelled
Publish Docker image / docker (push) Has been cancelled
Upload sourcemaps / upload (push) Has been cancelled

- DaisyUI themes openseo/openseo-dark -> crawlerx/crawlerx-dark on the
  MetatronCube palette (primary #3779b9, secondary #0f1d34, accent #4faede)
- Load Inter as the app font; add --brand-gradient / .brand-gradient
- Cube mark + "CrawlerX" wordmark in the sidebar and mobile top bar
- Add public/crawlerx-mark.png; point transparent-logo.png at the cube mark
- <title>, site.webmanifest, and all user-facing "OpenSEO" copy across
  src/client and src/routes -> "CrawlerX"

Deferred: favicons (need resizing), agent-facing identity (MCP server
name, SAM/onboarding prompts, fact sheet), web/ marketing site.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
metatroncubeswdev 2026-09-09 00:00:27 -04:00
parent ea8becd433
commit b489f78129
42 changed files with 173 additions and 138 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

BIN
public/crawlerx-mark.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

View File

@ -1,6 +1,6 @@
{ {
"name": "OpenSEO", "name": "CrawlerX",
"short_name": "OpenSEO", "short_name": "CrawlerX",
"icons": [ "icons": [
{ {
"src": "/android-chrome-192x192.png", "src": "/android-chrome-192x192.png",
@ -13,7 +13,7 @@
"type": "image/png" "type": "image/png"
} }
], ],
"theme_color": "#0a0f0d", "theme_color": "#3779b9",
"background_color": "#0a0f0d", "background_color": "#ffffff",
"display": "standalone" "display": "standalone"
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

After

Width:  |  Height:  |  Size: 74 KiB

View File

@ -11,7 +11,7 @@ type Props = {
/** /**
* Shared Markdown renderer with explicit per-element Tailwind classes. * Shared Markdown renderer with explicit per-element Tailwind classes.
* *
* OpenSEO doesn't ship `@tailwindcss/typography`, so `prose` classes are * CrawlerX doesn't ship `@tailwindcss/typography`, so `prose` classes are
* no-ops every block element is styled here instead. Tables use daisyUI's * no-ops every block element is styled here instead. Tables use daisyUI's
* `table table-sm` so model- and strategy-generated tables stay readable. * `table table-sm` so model- and strategy-generated tables stay readable.
* *

View File

@ -129,9 +129,14 @@ export function Sidebar({ projectId, onNavigate, onClose }: SidebarProps) {
<Link <Link
to="/" to="/"
onClick={onNavigate} onClick={onNavigate}
className="text-base font-semibold text-base-content" className="flex items-center gap-2 text-base font-semibold text-base-content"
> >
OpenSEO <img
src="/crawlerx-mark.png"
alt=""
className="size-6 rounded bg-white ring-1 ring-black/5"
/>
CrawlerX
</Link> </Link>
{onClose ? ( {onClose ? (
<button <button

View File

@ -79,7 +79,7 @@ const toolCategories: ToolCategory[] = [
{ {
name: "save_keywords", name: "save_keywords",
title: "Save keywords", title: "Save keywords",
description: "Save keywords back to OpenSEO.", description: "Save keywords back to CrawlerX.",
}, },
], ],
}, },

View File

@ -102,7 +102,7 @@ export function AuthPageCard({
<div className="text-center space-y-3"> <div className="text-center space-y-3">
<img <img
src="/transparent-logo.png" src="/transparent-logo.png"
alt="OpenSEO" alt="CrawlerX"
className="mx-auto size-10 rounded-lg" className="mx-auto size-10 rounded-lg"
/> />
<div> <div>

View File

@ -58,7 +58,7 @@ export function FreePlanBanner() {
return ( return (
<BannerShell variant="error"> <BannerShell variant="error">
You&rsquo;ve used all your credits. {creditsActionLink} to continue You&rsquo;ve used all your credits. {creditsActionLink} to continue
using OpenSEO. using CrawlerX.
</BannerShell> </BannerShell>
); );
} }
@ -67,7 +67,7 @@ export function FreePlanBanner() {
return ( return (
<BannerShell variant="warning"> <BannerShell variant="warning">
You&rsquo;re running low on credits. {creditsActionLink} to keep using You&rsquo;re running low on credits. {creditsActionLink} to keep using
OpenSEO. CrawlerX.
</BannerShell> </BannerShell>
); );
} }
@ -75,7 +75,7 @@ export function FreePlanBanner() {
if (isFreePlan) { if (isFreePlan) {
return ( return (
<BannerShell variant="info"> <BannerShell variant="info">
We hope you&rsquo;re enjoying OpenSEO!{" "} We hope you&rsquo;re enjoying CrawlerX!{" "}
<Link <Link
to={SUBSCRIBE_ROUTE} to={SUBSCRIBE_ROUTE}
search={{ upgrade: true }} search={{ upgrade: true }}

View File

@ -40,7 +40,7 @@ const HERO_COPY: Record<
}, },
mcp: { mcp: {
title: "Connect your AI agent", title: "Connect your AI agent",
body: "OpenSEO is built to be used from agents like Claude. Connect once, then ask it to use OpenSEO to help build your SEO strategy.", body: "CrawlerX is built to be used from agents like Claude. Connect once, then ask it to use CrawlerX to help build your SEO strategy.",
cta: "Show me how", cta: "Show me how",
}, },
gsc: { gsc: {

View File

@ -8,8 +8,8 @@ import { dismissDashboardMcpCard } from "@/serverFunctions/dashboard";
function firstPrompts(domain: string | null): string[] { function firstPrompts(domain: string | null): string[] {
const site = domain ?? "my site"; const site = domain ?? "my site";
return [ return [
`Review ${site}. Ideas for what keywords we could target? Use OpenSEO`, `Review ${site}. Ideas for what keywords we could target? Use CrawlerX`,
"Research my competitors top pages and keywords and tell me what's working. Use OpenSEO", "Research my competitors top pages and keywords and tell me what's working. Use CrawlerX",
]; ];
} }
@ -93,14 +93,14 @@ export function McpConnectCard({
</ul> </ul>
<p className="text-xs text-base-content/50"> <p className="text-xs text-base-content/50">
Waiting for your first call this card disappears once your agent Waiting for your first call this card disappears once your agent
talks to OpenSEO. talks to CrawlerX.
</p> </p>
</> </>
) : ( ) : (
<> <>
<div className="space-y-2 text-sm text-base-content/70"> <div className="space-y-2 text-sm text-base-content/70">
<p> <p>
OpenSEO is designed to give your AI agent the data it needs to CrawlerX is designed to give your AI agent the data it needs to
build a great SEO strategy and help you execute it. build a great SEO strategy and help you execute it.
</p> </p>
<p> <p>

View File

@ -106,7 +106,7 @@ export function GscReEngagementModal({
New: Connect Google Search Console New: Connect Google Search Console
</h2> </h2>
<p className="text-sm text-base-content/70"> <p className="text-sm text-base-content/70">
Bring your real clicks, impressions, and rankings into OpenSEO and Bring your real clicks, impressions, and rankings into CrawlerX and
query them from Claude or Codex over MCP. It never uses credits. query them from Claude or Codex over MCP. It never uses credits.
</p> </p>
</div> </div>

View File

@ -14,8 +14,8 @@ export function GoogleOAuthSetupWarning({
<div className="space-y-1"> <div className="space-y-1">
<p className="font-medium">Google OAuth client not configured</p> <p className="font-medium">Google OAuth client not configured</p>
<p className="text-base-content/70"> <p className="text-base-content/70">
Add your Google client ID and secret to this OpenSEO deployment before Add your Google client ID and secret to this CrawlerX deployment
connecting {integrationName}. before connecting {integrationName}.
</p> </p>
<SafeExternalLink <SafeExternalLink
url={docsUrl} url={docsUrl}

View File

@ -31,7 +31,7 @@ export function googleAuthErrorCopy(
return { return {
title: "Google account already connected", title: "Google account already connected",
description: description:
"That Google account is already linked to a different OpenSEO account. Disconnect it there first, or contact support and we'll move it over.", "That Google account is already linked to a different CrawlerX account. Disconnect it there first, or contact support and we'll move it over.",
}; };
default: default:
return { return {

View File

@ -86,7 +86,7 @@ function SiteForm({ projectId }: { projectId: string }) {
<div className="space-y-3 text-center"> <div className="space-y-3 text-center">
<img <img
src="/transparent-logo.png" src="/transparent-logo.png"
alt="OpenSEO" alt="CrawlerX"
className="mx-auto size-10 rounded-lg" className="mx-auto size-10 rounded-lg"
/> />
<h1 className="text-xl font-semibold">Tell us about your website.</h1> <h1 className="text-xl font-semibold">Tell us about your website.</h1>

View File

@ -65,8 +65,8 @@ const resolveToolLabel: ResolveToolLabel = (partType) =>
TOOL_LABELS[partType] ?? null; TOOL_LABELS[partType] ?? null;
const SUGGESTED_QUESTIONS = [ const SUGGESTED_QUESTIONS = [
"How will OpenSEO help me get more traffic?", "How will CrawlerX help me get more traffic?",
"Compare OpenSEO and Claude", "Compare CrawlerX and Claude",
"What do I get after I upgrade?", "What do I get after I upgrade?",
"How does the Google Search Console integration work?", "How does the Google Search Console integration work?",
"Right fit for consultants and agencies?", "Right fit for consultants and agencies?",

View File

@ -65,10 +65,10 @@ export function WelcomeMessage({
</div> </div>
<div className="min-w-0 flex-1 space-y-3 pt-0.5 text-sm"> <div className="min-w-0 flex-1 space-y-3 pt-0.5 text-sm">
<div className="space-y-3 text-base-content/80"> <div className="space-y-3 text-base-content/80">
<p>Hey, Im Sam welcome to OpenSEO.</p> <p>Hey, Im Sam welcome to CrawlerX.</p>
<p> <p>
To get full access to OpenSEO, you need to upgrade to the paid plan. To get full access to CrawlerX, you need to upgrade to the paid
But, Im here if you have any questions. plan. But, Im here if you have any questions.
</p> </p>
<p> <p>
You can also{" "} You can also{" "}
@ -146,7 +146,9 @@ export function UpgradeSidebar({
<Globe className="size-4" /> <Globe className="size-4" />
</span> </span>
<div className="min-w-0"> <div className="min-w-0">
<p className="font-medium text-base-content/80">Previewing OpenSEO</p> <p className="font-medium text-base-content/80">
Previewing CrawlerX
</p>
<p className="truncate" title={domain}> <p className="truncate" title={domain}>
{domain} {domain}
</p> </p>
@ -187,7 +189,7 @@ export function UpgradeSidebar({
{isStartingCheckout ? "Redirecting..." : "Upgrade to continue"} {isStartingCheckout ? "Redirecting..." : "Upgrade to continue"}
</button> </button>
<p className="text-center text-xs leading-relaxed text-base-content/55"> <p className="text-center text-xs leading-relaxed text-base-content/55">
Want advice from other OpenSEO users?{" "} Want advice from other CrawlerX users?{" "}
<a <a
href={DISCORD_URL} href={DISCORD_URL}
target="_blank" target="_blank"
@ -231,7 +233,7 @@ export function ChatGate({
Thats all {FREE_ONBOARDING_QUESTION_LIMIT} free questions Thats all {FREE_ONBOARDING_QUESTION_LIMIT} free questions
</p> </p>
<p className="mx-auto mt-1 max-w-md text-xs text-base-content/70"> <p className="mx-auto mt-1 max-w-md text-xs text-base-content/70">
Upgrade to keep working with Sam and unlock the full OpenSEO app. Upgrade to keep working with Sam and unlock the full CrawlerX app.
</p> </p>
<button <button
type="button" type="button"
@ -252,7 +254,7 @@ export function ChatGate({
export function ChatComposer({ export function ChatComposer({
busy, busy,
onSend, onSend,
placeholder = "Ask Sam about your strategy or OpenSEO…", placeholder = "Ask Sam about your strategy or CrawlerX…",
}: { }: {
busy: boolean; busy: boolean;
onSend: (text: string) => void; onSend: (text: string) => void;

View File

@ -61,7 +61,7 @@ export function PostSignupOnboarding({
<div className="text-center space-y-3"> <div className="text-center space-y-3">
<img <img
src="/transparent-logo.png" src="/transparent-logo.png"
alt="OpenSEO" alt="CrawlerX"
className="mx-auto size-10 rounded-lg" className="mx-auto size-10 rounded-lg"
/> />
<p className="text-xs font-medium uppercase tracking-wide text-base-content/50"> <p className="text-xs font-medium uppercase tracking-wide text-base-content/50">
@ -70,8 +70,8 @@ export function PostSignupOnboarding({
<h1 className="text-xl font-semibold"> <h1 className="text-xl font-semibold">
{title ?? {title ??
(firstName (firstName
? `Welcome to OpenSEO, ${firstName}!` ? `Welcome to CrawlerX, ${firstName}!`
: "Welcome to OpenSEO!")} : "Welcome to CrawlerX!")}
</h1> </h1>
<p className="text-sm text-base-content/60"> <p className="text-sm text-base-content/60">
{helperText ?? "A few quick answers to set things up."} {helperText ?? "A few quick answers to set things up."}
@ -116,7 +116,7 @@ export function PostSignupOnboarding({
/> />
) : step === 2 ? ( ) : step === 2 ? (
<OnboardingChoiceGroup <OnboardingChoiceGroup
title="How did you find OpenSEO?" title="How did you find CrawlerX?"
options={[...SOURCE_OPTIONS]} options={[...SOURCE_OPTIONS]}
selectedValues={answers.source ? [answers.source] : []} selectedValues={answers.source ? [answers.source] : []}
onToggle={(source) => updateAnswers({ source })} onToggle={(source) => updateAnswers({ source })}

View File

@ -46,8 +46,8 @@ export function SearchConsoleOnboardingStep() {
{project ? <GscConnect projectId={project.id} /> : <Checking />} {project ? <GscConnect projectId={project.id} /> : <Checking />}
<p className="hidden sm:block text-xs leading-relaxed text-base-content/55"> <p className="hidden sm:block text-xs leading-relaxed text-base-content/55">
For now, Search Console data flows through the OpenSEO MCP. We're For now, Search Console data flows through the CrawlerX MCP. We're
building it into the OpenSEO app soon too. building it into the CrawlerX app soon too.
</p> </p>
</div> </div>

View File

@ -136,7 +136,7 @@ export function SamChat({
<div className="space-y-1"> <div className="space-y-1">
<p className="text-lg font-medium">What should we work on?</p> <p className="text-lg font-medium">What should we work on?</p>
<p className="max-w-sm text-sm text-base-content/60"> <p className="max-w-sm text-sm text-base-content/60">
SAM is your in-app SEO agent with access to every OpenSEO research SAM is your in-app SEO agent with access to every CrawlerX research
tool. Start a chat to get going. tool. Start a chat to get going.
</p> </p>
</div> </div>

View File

@ -20,10 +20,10 @@ export function SamSetupGate({
<div className="max-w-3xl space-y-1.5"> <div className="max-w-3xl space-y-1.5">
<h2 className="text-xl font-semibold">Enable AI Features</h2> <h2 className="text-xl font-semibold">Enable AI Features</h2>
<div className="text-sm text-base-content/68"> <div className="text-sm text-base-content/68">
SAM, OpenSEO's in-app AI agent, needs an OpenRouter API key. SAM, CrawlerX's in-app AI agent, needs an OpenRouter API key.
Create a key on OpenRouter, set it as the{" "} Create a key on OpenRouter, set it as the{" "}
<code>OPENROUTER_API_KEY</code> environment variable, restart <code>OPENROUTER_API_KEY</code> environment variable, restart
OpenSEO, then confirm here. CrawlerX, then confirm here.
</div> </div>
<div className="text-xs text-base-content/50"> <div className="text-xs text-base-content/50">
Step-by-step instructions for every deployment are in the{" "} Step-by-step instructions for every deployment are in the{" "}

View File

@ -37,7 +37,7 @@ function BetaNotice() {
</button> </button>
</div> </div>
<p className="mt-1.5 text-xs text-base-content/70"> <p className="mt-1.5 text-xs text-base-content/70">
For more powerful AI workflows, use the OpenSEO MCP with your own agent For more powerful AI workflows, use the CrawlerX MCP with your own agent
like Claude Code or Hermes. like Claude Code or Hermes.
</p> </p>
<Link to="/ai" className="link link-primary mt-1.5 inline-block text-xs"> <Link to="/ai" className="link link-primary mt-1.5 inline-block text-xs">

View File

@ -180,8 +180,16 @@ function MobileTopBar({
> >
<Menu className="h-5 w-5" /> <Menu className="h-5 w-5" />
</button> </button>
<Link to="/" className="ml-1 font-semibold text-base-content"> <Link
OpenSEO to="/"
className="ml-1 flex items-center gap-2 font-semibold text-base-content"
>
<img
src="/crawlerx-mark.png"
alt=""
className="size-6 rounded bg-white ring-1 ring-black/5"
/>
CrawlerX
</Link> </Link>
</div> </div>
); );

View File

@ -19,7 +19,7 @@ function SeoApiStatusBanners({
<div className="alert alert-warning"> <div className="alert alert-warning">
<AlertTriangle className="size-4 shrink-0" /> <AlertTriangle className="size-4 shrink-0" />
<span className="text-sm"> <span className="text-sm">
Setup needed: add your DataForSEO API key to use OpenSEO Setup needed: add your DataForSEO API key to use CrawlerX
features. See the quick steps on the{" "} features. See the quick steps on the{" "}
<Link <Link
{...dataforseoHelpLinkOptions} {...dataforseoHelpLinkOptions}
@ -119,7 +119,7 @@ const MissingSeoSetupModal = React.forwardRef<
id="dataforseo-setup-description" id="dataforseo-setup-description"
className="text-sm text-base-content/75" className="text-sm text-base-content/75"
> >
Add your DataForSEO API key to start using OpenSEO. Add your DataForSEO API key to start using CrawlerX.
</p> </p>
</div> </div>
</div> </div>

View File

@ -7,7 +7,7 @@ import {
describe("getStandardErrorMessage", () => { describe("getStandardErrorMessage", () => {
it("maps known error codes to standard copy", () => { it("maps known error codes to standard copy", () => {
expect(getStandardErrorMessage(new Error("PAYMENT_REQUIRED"))).toBe( expect(getStandardErrorMessage(new Error("PAYMENT_REQUIRED"))).toBe(
"An active hosted subscription is required before you can use OpenSEO.", "An active hosted subscription is required before you can use CrawlerX.",
); );
}); });

View File

@ -4,9 +4,9 @@ import { isErrorCode, type ErrorCode } from "@/shared/error-codes";
const STANDARD_MESSAGES: Record<ErrorCode, string> = { const STANDARD_MESSAGES: Record<ErrorCode, string> = {
UNAUTHENTICATED: "Please sign in and try again.", UNAUTHENTICATED: "Please sign in and try again.",
AUTH_CONFIG_MISSING: AUTH_CONFIG_MISSING:
"OpenSEO auth is not configured. Follow the README setup steps for Cloudflare Access.", "CrawlerX auth is not configured. Follow the README setup steps for Cloudflare Access.",
PAYMENT_REQUIRED: PAYMENT_REQUIRED:
"An active hosted subscription is required before you can use OpenSEO.", "An active hosted subscription is required before you can use CrawlerX.",
INSUFFICIENT_CREDITS: INSUFFICIENT_CREDITS:
"You've run out of credits. Add more credits or upgrade your plan to continue.", "You've run out of credits. Add more credits or upgrade your plan to continue.",
FORBIDDEN: "You do not have access to this resource.", FORBIDDEN: "You do not have access to this resource.",

View File

@ -2,8 +2,8 @@ import * as React from "react";
export type ThemePreference = "system" | "light" | "dark"; export type ThemePreference = "system" | "light" | "dark";
const LIGHT_THEME_NAME = "openseo"; const LIGHT_THEME_NAME = "crawlerx";
const DARK_THEME_NAME = "openseo-dark"; const DARK_THEME_NAME = "crawlerx-dark";
const THEME_STORAGE_KEY = "theme-preference"; const THEME_STORAGE_KEY = "theme-preference";
const THEME_CHANGE_EVENT = "theme-preference-change"; const THEME_CHANGE_EVENT = "theme-preference-change";

View File

@ -1,7 +1,26 @@
@import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap");
@import "tailwindcss"; @import "tailwindcss";
@theme { @theme {
--container-8xl: 88rem; --container-8xl: 88rem;
--font-sans:
"Inter", ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto,
"Helvetica Neue", Arial, sans-serif;
}
/* CrawlerX brand gradient (MetatronCube blue deep black), used on the
sign-in surface and the sidebar header. */
:root {
--brand-gradient: linear-gradient(
135deg,
#3779b9 0%,
#0f1d34 70%,
#00060c 100%
);
}
.brand-gradient {
background-image: var(--brand-gradient);
} }
:root { :root {
@ -19,26 +38,26 @@
exclude: properties; exclude: properties;
} }
/* Light theme */ /* Light theme — CrawlerX / MetatronCube brand (primary #3779b9) */
@plugin "daisyui/theme" { @plugin "daisyui/theme" {
name: "openseo"; name: "crawlerx";
default: true; default: true;
prefersdark: false; prefersdark: false;
color-scheme: light; color-scheme: light;
--color-base-100: oklch(100% 0 0); --color-base-100: #ffffff;
--color-base-200: oklch(97% 0 0); --color-base-200: #f6f8fa;
--color-base-300: oklch(92% 0 0); --color-base-300: #e6eaef;
--color-base-content: oklch(20% 0 0); --color-base-content: #151515;
--color-primary: oklch(50% 0.12 262); --color-primary: #3779b9;
--color-primary-content: oklch(100% 0 0); --color-primary-content: #ffffff;
--color-secondary: oklch(55% 0.15 145); --color-secondary: #0f1d34;
--color-secondary-content: oklch(100% 0 0); --color-secondary-content: #ffffff;
--color-accent: oklch(50% 0.12 262); --color-accent: #4faede;
--color-accent-content: oklch(100% 0 0); --color-accent-content: #06263a;
--color-neutral: oklch(25% 0 0); --color-neutral: #151515;
--color-neutral-content: oklch(100% 0 0); --color-neutral-content: #ffffff;
--color-info: oklch(70% 0.15 230); --color-info: #3779b9;
--color-info-content: oklch(100% 0 0); --color-info-content: #ffffff;
--color-success: oklch(65% 0.18 145); --color-success: oklch(65% 0.18 145);
--color-success-content: oklch(100% 0 0); --color-success-content: oklch(100% 0 0);
--color-warning: oklch(80% 0.15 80); --color-warning: oklch(80% 0.15 80);
@ -57,23 +76,23 @@
/* Dark theme - auto-activates via prefers-color-scheme */ /* Dark theme - auto-activates via prefers-color-scheme */
@plugin "daisyui/theme" { @plugin "daisyui/theme" {
name: "openseo-dark"; name: "crawlerx-dark";
prefersdark: true; prefersdark: true;
color-scheme: dark; color-scheme: dark;
--color-base-100: oklch(18% 0 0); --color-base-100: #141821;
--color-base-200: oklch(12% 0 0); --color-base-200: #0e121b;
--color-base-300: oklch(27% 0 0); --color-base-300: #232a37;
--color-base-content: oklch(92% 0 0); --color-base-content: #e8ebf0;
--color-primary: oklch(66% 0.12 262); --color-primary: #3f8ad4;
--color-primary-content: oklch(100% 0 0); --color-primary-content: #ffffff;
--color-secondary: oklch(60% 0.15 145); --color-secondary: #9fb3c8;
--color-secondary-content: oklch(100% 0 0); --color-secondary-content: #0e121b;
--color-accent: oklch(66% 0.12 262); --color-accent: #4faede;
--color-accent-content: oklch(100% 0 0); --color-accent-content: #06263a;
--color-neutral: oklch(85% 0 0); --color-neutral: #e8ebf0;
--color-neutral-content: oklch(20% 0 0); --color-neutral-content: #141821;
--color-info: oklch(70% 0.15 230); --color-info: #3f8ad4;
--color-info-content: oklch(100% 0 0); --color-info-content: #ffffff;
--color-success: oklch(65% 0.18 145); --color-success: oklch(65% 0.18 145);
--color-success-content: oklch(100% 0 0); --color-success-content: oklch(100% 0 0);
--color-warning: oklch(80% 0.15 80); --color-warning: oklch(80% 0.15 80);
@ -153,7 +172,7 @@ select {
base-200, which is darker than the base-100 content panel in dark mode, base-200, which is darker than the base-100 content panel in dark mode,
so they read as sunken/disabled. Colored variants (primary, error, ...) so they read as sunken/disabled. Colored variants (primary, error, ...)
set their own --btn-color and are excluded. */ set their own --btn-color and are excluded. */
html[data-theme="openseo-dark"] html[data-theme="crawlerx-dark"]
.btn:not( .btn:not(
.btn-primary, .btn-primary,
.btn-error, .btn-error,
@ -168,7 +187,7 @@ html[data-theme="openseo-dark"]
/* Light mode: the base-200 default button is nearly invisible on the /* Light mode: the base-200 default button is nearly invisible on the
base-100 content panel; use a white surface with a visible border, base-100 content panel; use a white surface with a visible border,
matching inputs/selects. */ matching inputs/selects. */
html[data-theme="openseo"] html[data-theme="crawlerx"]
.btn:not( .btn:not(
.btn-primary, .btn-primary,
.btn-error, .btn-error,
@ -251,28 +270,28 @@ html[data-theme="openseo"]
--tw-ring-color: color-mix(in oklab, #d946ef 30%, transparent); --tw-ring-color: color-mix(in oklab, #d946ef 30%, transparent);
} }
html[data-theme="openseo-dark"] .tag-chip-slate { html[data-theme="crawlerx-dark"] .tag-chip-slate {
color: #cbd5e1; color: #cbd5e1;
} }
html[data-theme="openseo-dark"] .tag-chip-rose { html[data-theme="crawlerx-dark"] .tag-chip-rose {
color: #fda4af; color: #fda4af;
} }
html[data-theme="openseo-dark"] .tag-chip-amber { html[data-theme="crawlerx-dark"] .tag-chip-amber {
color: #fcd34d; color: #fcd34d;
} }
html[data-theme="openseo-dark"] .tag-chip-lime { html[data-theme="crawlerx-dark"] .tag-chip-lime {
color: #d9f99d; color: #d9f99d;
} }
html[data-theme="openseo-dark"] .tag-chip-emerald { html[data-theme="crawlerx-dark"] .tag-chip-emerald {
color: #6ee7b7; color: #6ee7b7;
} }
html[data-theme="openseo-dark"] .tag-chip-sky { html[data-theme="crawlerx-dark"] .tag-chip-sky {
color: #7dd3fc; color: #7dd3fc;
} }
html[data-theme="openseo-dark"] .tag-chip-violet { html[data-theme="crawlerx-dark"] .tag-chip-violet {
color: #c4b5fd; color: #c4b5fd;
} }
html[data-theme="openseo-dark"] .tag-chip-fuchsia { html[data-theme="crawlerx-dark"] .tag-chip-fuchsia {
color: #f5d0fe; color: #f5d0fe;
} }
@ -323,29 +342,29 @@ html[data-theme="openseo-dark"] .tag-chip-fuchsia {
--tw-ring-color: color-mix(in oklab, #b91c1c 32%, transparent); --tw-ring-color: color-mix(in oklab, #b91c1c 32%, transparent);
} }
html[data-theme="openseo-dark"] .score-tier-na { html[data-theme="crawlerx-dark"] .score-tier-na {
color: #cbd5e1; color: #cbd5e1;
} }
html[data-theme="openseo-dark"] .score-tier-1 { html[data-theme="crawlerx-dark"] .score-tier-1 {
color: #6ee7b7; color: #6ee7b7;
} }
html[data-theme="openseo-dark"] .score-tier-2 { html[data-theme="crawlerx-dark"] .score-tier-2 {
color: #bef264; color: #bef264;
} }
html[data-theme="openseo-dark"] .score-tier-3 { html[data-theme="crawlerx-dark"] .score-tier-3 {
color: #fde047; color: #fde047;
} }
html[data-theme="openseo-dark"] .score-tier-4 { html[data-theme="crawlerx-dark"] .score-tier-4 {
color: #fdba74; color: #fdba74;
} }
html[data-theme="openseo-dark"] .score-tier-5 { html[data-theme="crawlerx-dark"] .score-tier-5 {
color: #fca5a5; color: #fca5a5;
} }
html[data-theme="openseo-dark"] .score-tier-6 { html[data-theme="crawlerx-dark"] .score-tier-6 {
color: #fda4af; color: #fda4af;
} }
html[data-theme="openseo-dark"] { html[data-theme="crawlerx-dark"] {
--trend-grid-color: oklch(78% 0.01 255 / 0.22); --trend-grid-color: oklch(78% 0.01 255 / 0.22);
--trend-axis-color: oklch(84% 0.01 255 / 0.62); --trend-axis-color: oklch(84% 0.01 255 / 0.62);
--trend-fill-start-opacity: 0.24; --trend-fill-start-opacity: 0.24;
@ -355,18 +374,18 @@ html[data-theme="openseo-dark"] {
--trend-tooltip-shadow: oklch(0% 0 0 / 0.35); --trend-tooltip-shadow: oklch(0% 0 0 / 0.35);
} }
html[data-theme="openseo-dark"] .alert-warning { html[data-theme="crawlerx-dark"] .alert-warning {
@apply border-warning/60 bg-warning/20; @apply border-warning/60 bg-warning/20;
} }
html[data-theme="openseo-dark"] .alert-info { html[data-theme="crawlerx-dark"] .alert-info {
@apply border-info/60 bg-info/20; @apply border-info/60 bg-info/20;
} }
html[data-theme="openseo-dark"] .alert-success { html[data-theme="crawlerx-dark"] .alert-success {
@apply border-success/60 bg-success/20; @apply border-success/60 bg-success/20;
} }
html[data-theme="openseo-dark"] .alert-error { html[data-theme="crawlerx-dark"] .alert-error {
@apply border-error/60 bg-error/20; @apply border-error/60 bg-error/20;
} }

View File

@ -37,7 +37,7 @@ export const Route = createRootRoute({
head: () => ({ head: () => ({
meta: [ meta: [
{ {
title: "OpenSEO", title: "CrawlerX",
}, },
{ {
charSet: "utf-8", charSet: "utf-8",

View File

@ -53,8 +53,9 @@ function AiPage() {
<div className="mx-auto max-w-3xl"> <div className="mx-auto max-w-3xl">
<h1 className="text-2xl font-semibold">AI & MCP</h1> <h1 className="text-2xl font-semibold">AI & MCP</h1>
<p className="mt-2 text-sm text-base-content/70 leading-relaxed"> <p className="mt-2 text-sm text-base-content/70 leading-relaxed">
Connect your AI agent to OpenSEO. Run keyword research, SERP analysis, Connect your AI agent to CrawlerX. Run keyword research, SERP
domain lookups, and backlink reviews from your editor or chat. analysis, domain lookups, and backlink reviews from your editor or
chat.
</p> </p>
{getAuthMode(import.meta.env.AUTH_MODE) === "cloudflare_access" ? ( {getAuthMode(import.meta.env.AUTH_MODE) === "cloudflare_access" ? (
@ -92,9 +93,9 @@ function AiPage() {
</code> </code>
</div> </div>
<p className="mt-2.5 text-xs text-base-content/55 leading-relaxed"> <p className="mt-2.5 text-xs text-base-content/55 leading-relaxed">
Paste this into any MCP client. This URL points at the OpenSEO Paste this into any MCP client. This URL points at the CrawlerX
instance you are using now, whether hosted, self-hosted, or local. instance you are using now, whether hosted, self-hosted, or local.
Sign in with OpenSEO when prompted. Sign in with CrawlerX when prompted.
</p> </p>
{isHostedClientAuthMode() ? ( {isHostedClientAuthMode() ? (
<p className="mt-2 text-xs text-base-content/55"> <p className="mt-2 text-xs text-base-content/55">
@ -154,9 +155,9 @@ function AiPage() {
. .
</li> </li>
<li>Paste the MCP URL above and click Add.</li> <li>Paste the MCP URL above and click Add.</li>
<li>Approve the OpenSEO login when prompted.</li> <li>Approve the CrawlerX login when prompted.</li>
<li> <li>
Optional: after OpenSEO connects, click{" "} Optional: after CrawlerX connects, click{" "}
<span className="font-medium text-base-content"> <span className="font-medium text-base-content">
Configure Configure
</span> </span>
@ -216,17 +217,17 @@ function AiPage() {
. .
</li> </li>
<li>Paste the MCP URL above.</li> <li>Paste the MCP URL above.</li>
<li>Approve the OpenSEO login when prompted.</li> <li>Approve the CrawlerX login when prompted.</li>
</ol> </ol>
</Collapsible> </Collapsible>
</div> </div>
</section> </section>
<section className="mt-12"> <section className="mt-12">
<h2 className="text-base font-semibold">OpenSEO Skills</h2> <h2 className="text-base font-semibold">CrawlerX Skills</h2>
<p className="mt-1.5 text-sm text-base-content/70 leading-relaxed"> <p className="mt-1.5 text-sm text-base-content/70 leading-relaxed">
Skills give Codex and Claude Code reusable SEO workflows that can Skills give Codex and Claude Code reusable SEO workflows that can
call your OpenSEO MCP tools when live SERP, keyword, backlink, or call your CrawlerX MCP tools when live SERP, keyword, backlink, or
domain data is needed. domain data is needed.
</p> </p>
<div className="mt-4 divide-y divide-base-300 overflow-hidden rounded-lg border border-base-300 bg-base-200"> <div className="mt-4 divide-y divide-base-300 overflow-hidden rounded-lg border border-base-300 bg-base-200">
@ -237,7 +238,7 @@ function AiPage() {
> >
<CodeBlock code={SKILLS_INSTALL} /> <CodeBlock code={SKILLS_INSTALL} />
<p className="text-sm text-base-content/70"> <p className="text-sm text-base-content/70">
You can also auto-accept each OpenSEO skill: You can also auto-accept each CrawlerX skill:
</p> </p>
<CodeBlock code={ALL_SKILLS_INSTALL} /> <CodeBlock code={ALL_SKILLS_INSTALL} />
</Collapsible> </Collapsible>
@ -320,7 +321,7 @@ function AiPage() {
{ {
title: "In-app SEO Research Agent", title: "In-app SEO Research Agent",
description: description:
"Ask questions and run research without leaving OpenSEO", "Ask questions and run research without leaving CrawlerX",
}, },
{ {
title: "Content Assistant", title: "Content Assistant",

View File

@ -196,7 +196,7 @@ function BillingPage() {
</div> </div>
<ul className="space-y-1.5"> <ul className="space-y-1.5">
{[ {[
"Access to all OpenSEO features", "Access to all CrawlerX features",
"Includes $10.00 of Usage Credits each month", "Includes $10.00 of Usage Credits each month",
].map((item) => ( ].map((item) => (
<li <li

View File

@ -16,7 +16,7 @@ function DataforseoApiKeyHelpPage() {
Set up your DataForSEO API key Set up your DataForSEO API key
</h1> </h1>
<p className="text-sm text-base-content/70"> <p className="text-sm text-base-content/70">
OpenSEO needs the <code>DATAFORSEO_API_KEY</code> secret before CrawlerX needs the <code>DATAFORSEO_API_KEY</code> secret before
keyword, domain, and SEO data workflows can run. keyword, domain, and SEO data workflows can run.
</p> </p>
</div> </div>
@ -62,7 +62,7 @@ function DataforseoApiKeyHelpPage() {
<li> <li>
In Cloudflare, go to <code>Compute</code> -&gt;{" "} In Cloudflare, go to <code>Compute</code> -&gt;{" "}
<code>Workers &amp; Pages</code> <code>Workers &amp; Pages</code>
and open your OpenSEO Worker. and open your CrawlerX Worker.
</li> </li>
<li> <li>
Open <code>Settings</code>. Open <code>Settings</code>.

View File

@ -16,9 +16,9 @@ function OpenrouterApiKeyHelpPage() {
Set up your OpenRouter API key Set up your OpenRouter API key
</h1> </h1>
<p className="text-sm text-base-content/70"> <p className="text-sm text-base-content/70">
OpenSEO needs the <code>OPENROUTER_API_KEY</code> secret before AI CrawlerX needs the <code>OPENROUTER_API_KEY</code> secret before
features like SAM, the in-app SEO agent, can run. It is optional AI features like SAM, the in-app SEO agent, can run. It is
everything else in OpenSEO works without it. optional everything else in CrawlerX works without it.
</p> </p>
</div> </div>
</div> </div>
@ -64,7 +64,7 @@ function OpenrouterApiKeyHelpPage() {
</li> </li>
</ul> </ul>
</li> </li>
<li>Restart OpenSEO.</li> <li>Restart CrawlerX.</li>
</ol> </ol>
</div> </div>
</div> </div>
@ -78,7 +78,7 @@ function OpenrouterApiKeyHelpPage() {
<li> <li>
In Cloudflare, go to <code>Compute</code> -&gt;{" "} In Cloudflare, go to <code>Compute</code> -&gt;{" "}
<code>Workers &amp; Pages</code> <code>Workers &amp; Pages</code>
and open your OpenSEO Worker. and open your CrawlerX Worker.
</li> </li>
<li> <li>
Open <code>Settings</code>. Open <code>Settings</code>.

View File

@ -76,7 +76,7 @@ function IndexRedirect() {
return ( return (
<div className="flex items-center justify-center h-full p-4"> <div className="flex items-center justify-center h-full p-4">
<UnauthenticatedErrorCard <UnauthenticatedErrorCard
message="Please sign in to access your OpenSEO organization." message="Please sign in to access your CrawlerX organization."
onRetry={() => { onRetry={() => {
void refetch(); void refetch();
}} }}

View File

@ -95,7 +95,7 @@ function PersonalSettings() {
</h2> </h2>
<div className="flex items-start justify-between gap-6"> <div className="flex items-start justify-between gap-6">
<div> <div>
<p className="text-sm">Help improve OpenSEO</p> <p className="text-sm">Help improve CrawlerX</p>
<p className="mt-1 text-sm text-base-content/60"> <p className="mt-1 text-sm text-base-content/60">
Share analytics and usage data. Share analytics and usage data.
</p> </p>

View File

@ -32,7 +32,7 @@ function SupportPage() {
</h1> </h1>
<p className="mt-2 text-sm text-base-content/60"> <p className="mt-2 text-sm text-base-content/60">
We want to talk to you! We're super open to feedback and want to learn We want to talk to you! We're super open to feedback and want to learn
how you work so we can make OpenSEO better. how you work so we can make CrawlerX better.
</p> </p>
<div className="mt-8 space-y-3"> <div className="mt-8 space-y-3">

View File

@ -87,7 +87,7 @@ function SignUpPage() {
redirect_to: redirectTo, redirect_to: redirectTo,
}); });
const resolvedName = const resolvedName =
value.name.trim() || email.split("@")[0] || "OpenSEO User"; value.name.trim() || email.split("@")[0] || "CrawlerX User";
const verificationCallbackURL = new URL( const verificationCallbackURL = new URL(
"/verify-email", "/verify-email",
window.location.origin, window.location.origin,

View File

@ -11,7 +11,7 @@ export const Route = createFileRoute("/_authenticated/oauth-consent")({
const SCOPES = [ const SCOPES = [
{ {
icon: Database, icon: Database,
label: "Read your OpenSEO data", label: "Read your CrawlerX data",
description: "Projects, keyword reports, and audit results.", description: "Projects, keyword reports, and audit results.",
}, },
{ {
@ -74,12 +74,12 @@ function OAuthConsentPage() {
<div className="flex flex-col items-center text-center"> <div className="flex flex-col items-center text-center">
<img <img
src="/transparent-logo.png" src="/transparent-logo.png"
alt="OpenSEO" alt="CrawlerX"
className="size-10 rounded-lg" className="size-10 rounded-lg"
/> />
<h1 className="mt-5 text-xl font-semibold">Authorize MCP access</h1> <h1 className="mt-5 text-xl font-semibold">Authorize MCP access</h1>
<p className="mt-2 text-sm text-base-content/70"> <p className="mt-2 text-sm text-base-content/70">
An MCP client is requesting access to your OpenSEO workspace. An MCP client is requesting access to your CrawlerX workspace.
</p> </p>
</div> </div>

View File

@ -136,7 +136,7 @@ function SubscribePage() {
<div className="w-full max-w-xs space-y-4 text-center"> <div className="w-full max-w-xs space-y-4 text-center">
<img <img
src="/transparent-logo.png" src="/transparent-logo.png"
alt="OpenSEO" alt="CrawlerX"
className="mx-auto size-10 rounded-lg" className="mx-auto size-10 rounded-lg"
/> />
<h1 className="text-xl font-semibold"> <h1 className="text-xl font-semibold">
@ -163,7 +163,7 @@ function SubscribePage() {
<div className="text-center space-y-3"> <div className="text-center space-y-3">
<img <img
src="/transparent-logo.png" src="/transparent-logo.png"
alt="OpenSEO" alt="CrawlerX"
className="mx-auto size-10 rounded-lg" className="mx-auto size-10 rounded-lg"
/> />
<h1 className="text-xl font-semibold">Billing unavailable</h1> <h1 className="text-xl font-semibold">Billing unavailable</h1>
@ -223,15 +223,15 @@ function SubscribePage() {
<div className="text-center space-y-3"> <div className="text-center space-y-3">
<img <img
src="/transparent-logo.png" src="/transparent-logo.png"
alt="OpenSEO" alt="CrawlerX"
className="mx-auto size-10 rounded-lg" className="mx-auto size-10 rounded-lg"
/> />
<h1 className="text-xl font-semibold"> <h1 className="text-xl font-semibold">
{isUpgradeFlow {isUpgradeFlow
? "Upgrade your plan" ? "Upgrade your plan"
: firstName : firstName
? `Welcome to OpenSEO, ${firstName}!` ? `Welcome to CrawlerX, ${firstName}!`
: "Welcome to OpenSEO!"} : "Welcome to CrawlerX!"}
</h1> </h1>
<p className="text-sm text-base-content/60"> <p className="text-sm text-base-content/60">
SEO on your terms. All your SEO tools in one place at a fair price. SEO on your terms. All your SEO tools in one place at a fair price.

View File

@ -39,7 +39,7 @@ function SignedOutInvitationCard({ invitationId }: { invitationId: string }) {
return ( return (
<AuthPageCard title="You&rsquo;re invited"> <AuthPageCard title="You&rsquo;re invited">
<p className="text-sm text-base-content/70"> <p className="text-sm text-base-content/70">
You&rsquo;ve been invited to join an organization on OpenSEO. Sign in You&rsquo;ve been invited to join an organization on CrawlerX. Sign in
with the email address that received the invitation to accept it. with the email address that received the invitation to accept it.
</p> </p>
<div className="space-y-2"> <div className="space-y-2">
@ -212,7 +212,7 @@ function InvitationCard({
<span className="font-medium"> <span className="font-medium">
{invitationQuery.data.organizationName} {invitationQuery.data.organizationName}
</span>{" "} </span>{" "}
on OpenSEO. on CrawlerX.
</p> </p>
{actionError ? <p className="text-sm text-error">{actionError}</p> : null} {actionError ? <p className="text-sm text-error">{actionError}</p> : null}
<div className="space-y-2"> <div className="space-y-2">

View File

@ -39,7 +39,7 @@ function AuthErrorPage() {
} }
> >
<Link to="/" className="btn btn-soft w-full"> <Link to="/" className="btn btn-soft w-full">
Back to OpenSEO Back to CrawlerX
</Link> </Link>
</AuthPageCard> </AuthPageCard>
</AuthPageShell> </AuthPageShell>