Add /download page and link in nav

Desktop (macOS, Windows, Linux with AppImage/DEB/RPM), Mobile (Android/iOS),
and Web & CLI sections. Simplify homepage download button to a single link
with "All download options" text link below.
This commit is contained in:
Mohamed Boudra
2026-03-25 23:06:35 +07:00
parent 9ce6b45cf9
commit 701480c11c
3 changed files with 748 additions and 347 deletions
@@ -255,6 +255,12 @@ function Nav() {
>
Changelog
</a>
<a
href="/download"
className="text-sm text-muted-foreground hover:text-foreground transition-colors"
>
Download
</a>
<a
href="https://discord.gg/jz8T2uahpH"
target="_blank"
@@ -515,7 +521,15 @@ function GetStarted() {
</a>
<ServerInstallButton />
</div>
<div className="flex items-center gap-2 pt-8">
<div className="pt-3">
<a
href="/download"
className="text-xs text-white/40 hover:text-white/70 transition-colors"
>
All download options
</a>
</div>
<div className="flex items-center gap-2 pt-6">
<span className="text-xs text-white/40">Supports</span>
<div className="flex items-center gap-1">
<AgentBadge name="Claude Code" icon={<ClaudeCodeIcon className="h-6 w-6" />} />
@@ -529,72 +543,19 @@ function GetStarted() {
function DownloadButton() {
const detectedPlatform = useDetectedPlatform();
const [open, setOpen] = React.useState(false);
const ref = React.useRef<HTMLDivElement>(null);
React.useEffect(() => {
function handleClickOutside(e: MouseEvent) {
if (ref.current && !ref.current.contains(e.target as Node)) {
setOpen(false);
}
}
document.addEventListener("mousedown", handleClickOutside);
return () => document.removeEventListener("mousedown", handleClickOutside);
}, []);
const primary = downloadOptions.find((o) => o.platform === detectedPlatform)!;
const secondaryOptions = downloadOptions.filter((o) => o.platform !== detectedPlatform);
const PrimaryIcon = primary.icon;
return (
<div className="relative" ref={ref}>
<div className="flex items-stretch">
<a
href={primary.href}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-2 rounded-l-lg bg-foreground px-4 py-2 text-sm font-medium text-background hover:bg-foreground/90 transition-colors"
>
<PrimaryIcon className="h-4 w-4" />
Download for {primary.label}
</a>
<button
onClick={() => setOpen(!open)}
className="inline-flex items-center justify-center rounded-r-lg border-l border-background/20 bg-foreground px-2 py-2 text-background hover:bg-foreground/90 transition-colors"
aria-label="More download options"
>
<ChevronDownIcon className="h-4 w-4" />
</button>
</div>
<AnimatePresence>
{open && (
<motion.div
initial={{ opacity: 0, y: -4 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -4 }}
transition={{ duration: 0.15, ease: "easeOut" }}
className="absolute left-0 top-full mt-1 z-50 min-w-[200px] rounded-lg border border-white/20 bg-black/90 backdrop-blur-sm py-1"
>
{secondaryOptions.map((option) => {
const Icon = option.icon;
return (
<a
key={option.platform}
href={option.href}
target="_blank"
rel="noopener noreferrer"
onClick={() => setOpen(false)}
className="flex items-center gap-2 px-4 py-2 text-sm text-white/80 hover:bg-white/10 transition-colors"
>
<Icon className="h-4 w-4" />
{option.label}
</a>
);
})}
</motion.div>
)}
</AnimatePresence>
</div>
<a
href={primary.href}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-2 rounded-lg bg-foreground px-4 py-2 text-sm font-medium text-background hover:bg-foreground/90 transition-colors"
>
<PrimaryIcon className="h-4 w-4" />
Download for {primary.label}
</a>
);
}
+305 -284
View File
@@ -8,332 +8,352 @@
// You should NOT make any changes in this file as it will be overwritten.
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
import { Route as rootRouteImport } from "./routes/__root";
import { Route as PrivacyRouteImport } from "./routes/privacy";
import { Route as OpencodeRouteImport } from "./routes/opencode";
import { Route as DocsRouteImport } from "./routes/docs";
import { Route as CodexRouteImport } from "./routes/codex";
import { Route as ClaudeCodeRouteImport } from "./routes/claude-code";
import { Route as ChangelogRouteImport } from "./routes/changelog";
import { Route as IndexRouteImport } from "./routes/index";
import { Route as DocsIndexRouteImport } from "./routes/docs/index";
import { Route as DocsWorktreesRouteImport } from "./routes/docs/worktrees";
import { Route as DocsVoiceRouteImport } from "./routes/docs/voice";
import { Route as DocsUpdatesRouteImport } from "./routes/docs/updates";
import { Route as DocsSecurityRouteImport } from "./routes/docs/security";
import { Route as DocsConfigurationRouteImport } from "./routes/docs/configuration";
import { Route as DocsCliRouteImport } from "./routes/docs/cli";
import { Route as DocsBestPracticesRouteImport } from "./routes/docs/best-practices";
import { Route as rootRouteImport } from './routes/__root'
import { Route as PrivacyRouteImport } from './routes/privacy'
import { Route as OpencodeRouteImport } from './routes/opencode'
import { Route as DownloadRouteImport } from './routes/download'
import { Route as DocsRouteImport } from './routes/docs'
import { Route as CodexRouteImport } from './routes/codex'
import { Route as ClaudeCodeRouteImport } from './routes/claude-code'
import { Route as ChangelogRouteImport } from './routes/changelog'
import { Route as IndexRouteImport } from './routes/index'
import { Route as DocsIndexRouteImport } from './routes/docs/index'
import { Route as DocsWorktreesRouteImport } from './routes/docs/worktrees'
import { Route as DocsVoiceRouteImport } from './routes/docs/voice'
import { Route as DocsUpdatesRouteImport } from './routes/docs/updates'
import { Route as DocsSecurityRouteImport } from './routes/docs/security'
import { Route as DocsConfigurationRouteImport } from './routes/docs/configuration'
import { Route as DocsCliRouteImport } from './routes/docs/cli'
import { Route as DocsBestPracticesRouteImport } from './routes/docs/best-practices'
const PrivacyRoute = PrivacyRouteImport.update({
id: "/privacy",
path: "/privacy",
id: '/privacy',
path: '/privacy',
getParentRoute: () => rootRouteImport,
} as any);
} as any)
const OpencodeRoute = OpencodeRouteImport.update({
id: "/opencode",
path: "/opencode",
id: '/opencode',
path: '/opencode',
getParentRoute: () => rootRouteImport,
} as any);
} as any)
const DownloadRoute = DownloadRouteImport.update({
id: '/download',
path: '/download',
getParentRoute: () => rootRouteImport,
} as any)
const DocsRoute = DocsRouteImport.update({
id: "/docs",
path: "/docs",
id: '/docs',
path: '/docs',
getParentRoute: () => rootRouteImport,
} as any);
} as any)
const CodexRoute = CodexRouteImport.update({
id: "/codex",
path: "/codex",
id: '/codex',
path: '/codex',
getParentRoute: () => rootRouteImport,
} as any);
} as any)
const ClaudeCodeRoute = ClaudeCodeRouteImport.update({
id: "/claude-code",
path: "/claude-code",
id: '/claude-code',
path: '/claude-code',
getParentRoute: () => rootRouteImport,
} as any);
} as any)
const ChangelogRoute = ChangelogRouteImport.update({
id: "/changelog",
path: "/changelog",
id: '/changelog',
path: '/changelog',
getParentRoute: () => rootRouteImport,
} as any);
} as any)
const IndexRoute = IndexRouteImport.update({
id: "/",
path: "/",
id: '/',
path: '/',
getParentRoute: () => rootRouteImport,
} as any);
} as any)
const DocsIndexRoute = DocsIndexRouteImport.update({
id: "/",
path: "/",
id: '/',
path: '/',
getParentRoute: () => DocsRoute,
} as any);
} as any)
const DocsWorktreesRoute = DocsWorktreesRouteImport.update({
id: "/worktrees",
path: "/worktrees",
id: '/worktrees',
path: '/worktrees',
getParentRoute: () => DocsRoute,
} as any);
} as any)
const DocsVoiceRoute = DocsVoiceRouteImport.update({
id: "/voice",
path: "/voice",
id: '/voice',
path: '/voice',
getParentRoute: () => DocsRoute,
} as any);
} as any)
const DocsUpdatesRoute = DocsUpdatesRouteImport.update({
id: "/updates",
path: "/updates",
id: '/updates',
path: '/updates',
getParentRoute: () => DocsRoute,
} as any);
} as any)
const DocsSecurityRoute = DocsSecurityRouteImport.update({
id: "/security",
path: "/security",
id: '/security',
path: '/security',
getParentRoute: () => DocsRoute,
} as any);
} as any)
const DocsConfigurationRoute = DocsConfigurationRouteImport.update({
id: "/configuration",
path: "/configuration",
id: '/configuration',
path: '/configuration',
getParentRoute: () => DocsRoute,
} as any);
} as any)
const DocsCliRoute = DocsCliRouteImport.update({
id: "/cli",
path: "/cli",
id: '/cli',
path: '/cli',
getParentRoute: () => DocsRoute,
} as any);
} as any)
const DocsBestPracticesRoute = DocsBestPracticesRouteImport.update({
id: "/best-practices",
path: "/best-practices",
id: '/best-practices',
path: '/best-practices',
getParentRoute: () => DocsRoute,
} as any);
} as any)
export interface FileRoutesByFullPath {
"/": typeof IndexRoute;
"/changelog": typeof ChangelogRoute;
"/claude-code": typeof ClaudeCodeRoute;
"/codex": typeof CodexRoute;
"/docs": typeof DocsRouteWithChildren;
"/opencode": typeof OpencodeRoute;
"/privacy": typeof PrivacyRoute;
"/docs/best-practices": typeof DocsBestPracticesRoute;
"/docs/cli": typeof DocsCliRoute;
"/docs/configuration": typeof DocsConfigurationRoute;
"/docs/security": typeof DocsSecurityRoute;
"/docs/updates": typeof DocsUpdatesRoute;
"/docs/voice": typeof DocsVoiceRoute;
"/docs/worktrees": typeof DocsWorktreesRoute;
"/docs/": typeof DocsIndexRoute;
'/': typeof IndexRoute
'/changelog': typeof ChangelogRoute
'/claude-code': typeof ClaudeCodeRoute
'/codex': typeof CodexRoute
'/docs': typeof DocsRouteWithChildren
'/download': typeof DownloadRoute
'/opencode': typeof OpencodeRoute
'/privacy': typeof PrivacyRoute
'/docs/best-practices': typeof DocsBestPracticesRoute
'/docs/cli': typeof DocsCliRoute
'/docs/configuration': typeof DocsConfigurationRoute
'/docs/security': typeof DocsSecurityRoute
'/docs/updates': typeof DocsUpdatesRoute
'/docs/voice': typeof DocsVoiceRoute
'/docs/worktrees': typeof DocsWorktreesRoute
'/docs/': typeof DocsIndexRoute
}
export interface FileRoutesByTo {
"/": typeof IndexRoute;
"/changelog": typeof ChangelogRoute;
"/claude-code": typeof ClaudeCodeRoute;
"/codex": typeof CodexRoute;
"/opencode": typeof OpencodeRoute;
"/privacy": typeof PrivacyRoute;
"/docs/best-practices": typeof DocsBestPracticesRoute;
"/docs/cli": typeof DocsCliRoute;
"/docs/configuration": typeof DocsConfigurationRoute;
"/docs/security": typeof DocsSecurityRoute;
"/docs/updates": typeof DocsUpdatesRoute;
"/docs/voice": typeof DocsVoiceRoute;
"/docs/worktrees": typeof DocsWorktreesRoute;
"/docs": typeof DocsIndexRoute;
'/': typeof IndexRoute
'/changelog': typeof ChangelogRoute
'/claude-code': typeof ClaudeCodeRoute
'/codex': typeof CodexRoute
'/download': typeof DownloadRoute
'/opencode': typeof OpencodeRoute
'/privacy': typeof PrivacyRoute
'/docs/best-practices': typeof DocsBestPracticesRoute
'/docs/cli': typeof DocsCliRoute
'/docs/configuration': typeof DocsConfigurationRoute
'/docs/security': typeof DocsSecurityRoute
'/docs/updates': typeof DocsUpdatesRoute
'/docs/voice': typeof DocsVoiceRoute
'/docs/worktrees': typeof DocsWorktreesRoute
'/docs': typeof DocsIndexRoute
}
export interface FileRoutesById {
__root__: typeof rootRouteImport;
"/": typeof IndexRoute;
"/changelog": typeof ChangelogRoute;
"/claude-code": typeof ClaudeCodeRoute;
"/codex": typeof CodexRoute;
"/docs": typeof DocsRouteWithChildren;
"/opencode": typeof OpencodeRoute;
"/privacy": typeof PrivacyRoute;
"/docs/best-practices": typeof DocsBestPracticesRoute;
"/docs/cli": typeof DocsCliRoute;
"/docs/configuration": typeof DocsConfigurationRoute;
"/docs/security": typeof DocsSecurityRoute;
"/docs/updates": typeof DocsUpdatesRoute;
"/docs/voice": typeof DocsVoiceRoute;
"/docs/worktrees": typeof DocsWorktreesRoute;
"/docs/": typeof DocsIndexRoute;
__root__: typeof rootRouteImport
'/': typeof IndexRoute
'/changelog': typeof ChangelogRoute
'/claude-code': typeof ClaudeCodeRoute
'/codex': typeof CodexRoute
'/docs': typeof DocsRouteWithChildren
'/download': typeof DownloadRoute
'/opencode': typeof OpencodeRoute
'/privacy': typeof PrivacyRoute
'/docs/best-practices': typeof DocsBestPracticesRoute
'/docs/cli': typeof DocsCliRoute
'/docs/configuration': typeof DocsConfigurationRoute
'/docs/security': typeof DocsSecurityRoute
'/docs/updates': typeof DocsUpdatesRoute
'/docs/voice': typeof DocsVoiceRoute
'/docs/worktrees': typeof DocsWorktreesRoute
'/docs/': typeof DocsIndexRoute
}
export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath;
fileRoutesByFullPath: FileRoutesByFullPath
fullPaths:
| "/"
| "/changelog"
| "/claude-code"
| "/codex"
| "/docs"
| "/opencode"
| "/privacy"
| "/docs/best-practices"
| "/docs/cli"
| "/docs/configuration"
| "/docs/security"
| "/docs/updates"
| "/docs/voice"
| "/docs/worktrees"
| "/docs/";
fileRoutesByTo: FileRoutesByTo;
| '/'
| '/changelog'
| '/claude-code'
| '/codex'
| '/docs'
| '/download'
| '/opencode'
| '/privacy'
| '/docs/best-practices'
| '/docs/cli'
| '/docs/configuration'
| '/docs/security'
| '/docs/updates'
| '/docs/voice'
| '/docs/worktrees'
| '/docs/'
fileRoutesByTo: FileRoutesByTo
to:
| "/"
| "/changelog"
| "/claude-code"
| "/codex"
| "/opencode"
| "/privacy"
| "/docs/best-practices"
| "/docs/cli"
| "/docs/configuration"
| "/docs/security"
| "/docs/updates"
| "/docs/voice"
| "/docs/worktrees"
| "/docs";
| '/'
| '/changelog'
| '/claude-code'
| '/codex'
| '/download'
| '/opencode'
| '/privacy'
| '/docs/best-practices'
| '/docs/cli'
| '/docs/configuration'
| '/docs/security'
| '/docs/updates'
| '/docs/voice'
| '/docs/worktrees'
| '/docs'
id:
| "__root__"
| "/"
| "/changelog"
| "/claude-code"
| "/codex"
| "/docs"
| "/opencode"
| "/privacy"
| "/docs/best-practices"
| "/docs/cli"
| "/docs/configuration"
| "/docs/security"
| "/docs/updates"
| "/docs/voice"
| "/docs/worktrees"
| "/docs/";
fileRoutesById: FileRoutesById;
| '__root__'
| '/'
| '/changelog'
| '/claude-code'
| '/codex'
| '/docs'
| '/download'
| '/opencode'
| '/privacy'
| '/docs/best-practices'
| '/docs/cli'
| '/docs/configuration'
| '/docs/security'
| '/docs/updates'
| '/docs/voice'
| '/docs/worktrees'
| '/docs/'
fileRoutesById: FileRoutesById
}
export interface RootRouteChildren {
IndexRoute: typeof IndexRoute;
ChangelogRoute: typeof ChangelogRoute;
ClaudeCodeRoute: typeof ClaudeCodeRoute;
CodexRoute: typeof CodexRoute;
DocsRoute: typeof DocsRouteWithChildren;
OpencodeRoute: typeof OpencodeRoute;
PrivacyRoute: typeof PrivacyRoute;
IndexRoute: typeof IndexRoute
ChangelogRoute: typeof ChangelogRoute
ClaudeCodeRoute: typeof ClaudeCodeRoute
CodexRoute: typeof CodexRoute
DocsRoute: typeof DocsRouteWithChildren
DownloadRoute: typeof DownloadRoute
OpencodeRoute: typeof OpencodeRoute
PrivacyRoute: typeof PrivacyRoute
}
declare module "@tanstack/react-router" {
declare module '@tanstack/react-router' {
interface FileRoutesByPath {
"/privacy": {
id: "/privacy";
path: "/privacy";
fullPath: "/privacy";
preLoaderRoute: typeof PrivacyRouteImport;
parentRoute: typeof rootRouteImport;
};
"/opencode": {
id: "/opencode";
path: "/opencode";
fullPath: "/opencode";
preLoaderRoute: typeof OpencodeRouteImport;
parentRoute: typeof rootRouteImport;
};
"/docs": {
id: "/docs";
path: "/docs";
fullPath: "/docs";
preLoaderRoute: typeof DocsRouteImport;
parentRoute: typeof rootRouteImport;
};
"/codex": {
id: "/codex";
path: "/codex";
fullPath: "/codex";
preLoaderRoute: typeof CodexRouteImport;
parentRoute: typeof rootRouteImport;
};
"/claude-code": {
id: "/claude-code";
path: "/claude-code";
fullPath: "/claude-code";
preLoaderRoute: typeof ClaudeCodeRouteImport;
parentRoute: typeof rootRouteImport;
};
"/changelog": {
id: "/changelog";
path: "/changelog";
fullPath: "/changelog";
preLoaderRoute: typeof ChangelogRouteImport;
parentRoute: typeof rootRouteImport;
};
"/": {
id: "/";
path: "/";
fullPath: "/";
preLoaderRoute: typeof IndexRouteImport;
parentRoute: typeof rootRouteImport;
};
"/docs/": {
id: "/docs/";
path: "/";
fullPath: "/docs/";
preLoaderRoute: typeof DocsIndexRouteImport;
parentRoute: typeof DocsRoute;
};
"/docs/worktrees": {
id: "/docs/worktrees";
path: "/worktrees";
fullPath: "/docs/worktrees";
preLoaderRoute: typeof DocsWorktreesRouteImport;
parentRoute: typeof DocsRoute;
};
"/docs/voice": {
id: "/docs/voice";
path: "/voice";
fullPath: "/docs/voice";
preLoaderRoute: typeof DocsVoiceRouteImport;
parentRoute: typeof DocsRoute;
};
"/docs/updates": {
id: "/docs/updates";
path: "/updates";
fullPath: "/docs/updates";
preLoaderRoute: typeof DocsUpdatesRouteImport;
parentRoute: typeof DocsRoute;
};
"/docs/security": {
id: "/docs/security";
path: "/security";
fullPath: "/docs/security";
preLoaderRoute: typeof DocsSecurityRouteImport;
parentRoute: typeof DocsRoute;
};
"/docs/configuration": {
id: "/docs/configuration";
path: "/configuration";
fullPath: "/docs/configuration";
preLoaderRoute: typeof DocsConfigurationRouteImport;
parentRoute: typeof DocsRoute;
};
"/docs/cli": {
id: "/docs/cli";
path: "/cli";
fullPath: "/docs/cli";
preLoaderRoute: typeof DocsCliRouteImport;
parentRoute: typeof DocsRoute;
};
"/docs/best-practices": {
id: "/docs/best-practices";
path: "/best-practices";
fullPath: "/docs/best-practices";
preLoaderRoute: typeof DocsBestPracticesRouteImport;
parentRoute: typeof DocsRoute;
};
'/privacy': {
id: '/privacy'
path: '/privacy'
fullPath: '/privacy'
preLoaderRoute: typeof PrivacyRouteImport
parentRoute: typeof rootRouteImport
}
'/opencode': {
id: '/opencode'
path: '/opencode'
fullPath: '/opencode'
preLoaderRoute: typeof OpencodeRouteImport
parentRoute: typeof rootRouteImport
}
'/download': {
id: '/download'
path: '/download'
fullPath: '/download'
preLoaderRoute: typeof DownloadRouteImport
parentRoute: typeof rootRouteImport
}
'/docs': {
id: '/docs'
path: '/docs'
fullPath: '/docs'
preLoaderRoute: typeof DocsRouteImport
parentRoute: typeof rootRouteImport
}
'/codex': {
id: '/codex'
path: '/codex'
fullPath: '/codex'
preLoaderRoute: typeof CodexRouteImport
parentRoute: typeof rootRouteImport
}
'/claude-code': {
id: '/claude-code'
path: '/claude-code'
fullPath: '/claude-code'
preLoaderRoute: typeof ClaudeCodeRouteImport
parentRoute: typeof rootRouteImport
}
'/changelog': {
id: '/changelog'
path: '/changelog'
fullPath: '/changelog'
preLoaderRoute: typeof ChangelogRouteImport
parentRoute: typeof rootRouteImport
}
'/': {
id: '/'
path: '/'
fullPath: '/'
preLoaderRoute: typeof IndexRouteImport
parentRoute: typeof rootRouteImport
}
'/docs/': {
id: '/docs/'
path: '/'
fullPath: '/docs/'
preLoaderRoute: typeof DocsIndexRouteImport
parentRoute: typeof DocsRoute
}
'/docs/worktrees': {
id: '/docs/worktrees'
path: '/worktrees'
fullPath: '/docs/worktrees'
preLoaderRoute: typeof DocsWorktreesRouteImport
parentRoute: typeof DocsRoute
}
'/docs/voice': {
id: '/docs/voice'
path: '/voice'
fullPath: '/docs/voice'
preLoaderRoute: typeof DocsVoiceRouteImport
parentRoute: typeof DocsRoute
}
'/docs/updates': {
id: '/docs/updates'
path: '/updates'
fullPath: '/docs/updates'
preLoaderRoute: typeof DocsUpdatesRouteImport
parentRoute: typeof DocsRoute
}
'/docs/security': {
id: '/docs/security'
path: '/security'
fullPath: '/docs/security'
preLoaderRoute: typeof DocsSecurityRouteImport
parentRoute: typeof DocsRoute
}
'/docs/configuration': {
id: '/docs/configuration'
path: '/configuration'
fullPath: '/docs/configuration'
preLoaderRoute: typeof DocsConfigurationRouteImport
parentRoute: typeof DocsRoute
}
'/docs/cli': {
id: '/docs/cli'
path: '/cli'
fullPath: '/docs/cli'
preLoaderRoute: typeof DocsCliRouteImport
parentRoute: typeof DocsRoute
}
'/docs/best-practices': {
id: '/docs/best-practices'
path: '/best-practices'
fullPath: '/docs/best-practices'
preLoaderRoute: typeof DocsBestPracticesRouteImport
parentRoute: typeof DocsRoute
}
}
}
interface DocsRouteChildren {
DocsBestPracticesRoute: typeof DocsBestPracticesRoute;
DocsCliRoute: typeof DocsCliRoute;
DocsConfigurationRoute: typeof DocsConfigurationRoute;
DocsSecurityRoute: typeof DocsSecurityRoute;
DocsUpdatesRoute: typeof DocsUpdatesRoute;
DocsVoiceRoute: typeof DocsVoiceRoute;
DocsWorktreesRoute: typeof DocsWorktreesRoute;
DocsIndexRoute: typeof DocsIndexRoute;
DocsBestPracticesRoute: typeof DocsBestPracticesRoute
DocsCliRoute: typeof DocsCliRoute
DocsConfigurationRoute: typeof DocsConfigurationRoute
DocsSecurityRoute: typeof DocsSecurityRoute
DocsUpdatesRoute: typeof DocsUpdatesRoute
DocsVoiceRoute: typeof DocsVoiceRoute
DocsWorktreesRoute: typeof DocsWorktreesRoute
DocsIndexRoute: typeof DocsIndexRoute
}
const DocsRouteChildren: DocsRouteChildren = {
@@ -345,9 +365,9 @@ const DocsRouteChildren: DocsRouteChildren = {
DocsVoiceRoute: DocsVoiceRoute,
DocsWorktreesRoute: DocsWorktreesRoute,
DocsIndexRoute: DocsIndexRoute,
};
}
const DocsRouteWithChildren = DocsRoute._addFileChildren(DocsRouteChildren);
const DocsRouteWithChildren = DocsRoute._addFileChildren(DocsRouteChildren)
const rootRouteChildren: RootRouteChildren = {
IndexRoute: IndexRoute,
@@ -355,18 +375,19 @@ const rootRouteChildren: RootRouteChildren = {
ClaudeCodeRoute: ClaudeCodeRoute,
CodexRoute: CodexRoute,
DocsRoute: DocsRouteWithChildren,
DownloadRoute: DownloadRoute,
OpencodeRoute: OpencodeRoute,
PrivacyRoute: PrivacyRoute,
};
}
export const routeTree = rootRouteImport
._addFileChildren(rootRouteChildren)
._addFileTypes<FileRouteTypes>();
._addFileTypes<FileRouteTypes>()
import type { getRouter } from "./router.tsx";
import type { createStart } from "@tanstack/react-start";
declare module "@tanstack/react-start" {
import type { getRouter } from './router.tsx'
import type { createStart } from '@tanstack/react-start'
declare module '@tanstack/react-start' {
interface Register {
ssr: true;
router: Awaited<ReturnType<typeof getRouter>>;
ssr: true
router: Awaited<ReturnType<typeof getRouter>>
}
}
+419
View File
@@ -0,0 +1,419 @@
import { Link, createFileRoute } from "@tanstack/react-router";
import { pageMeta } from "~/meta";
import websitePackage from "../../package.json";
import "~/styles.css";
const desktopVersion = websitePackage.version;
const releaseBase = `https://github.com/getpaseo/paseo/releases/download/v${desktopVersion}`;
export const Route = createFileRoute("/download")({
head: () => ({
meta: pageMeta(
"Download - Paseo",
"Download Paseo for macOS, Windows, Linux, iOS, and Android. Your dev environment, in your pocket.",
),
}),
component: Download,
});
function Download() {
return (
<div className="min-h-screen bg-background">
<div className="max-w-3xl mx-auto p-6 md:p-12">
<header className="flex items-center justify-between gap-4 mb-12">
<Link to="/" className="flex items-center gap-3">
<img src="/logo.svg" alt="Paseo" className="w-6 h-6" />
<span className="text-lg font-medium">Paseo</span>
</Link>
<div className="flex items-center gap-4">
<Link
to="/docs"
className="text-sm text-muted-foreground hover:text-foreground transition-colors"
>
Docs
</Link>
<Link
to="/changelog"
className="text-sm text-muted-foreground hover:text-foreground transition-colors"
>
Changelog
</Link>
<a
href="https://discord.gg/jz8T2uahpH"
target="_blank"
rel="noopener noreferrer"
aria-label="Discord"
className="text-muted-foreground hover:text-foreground transition-colors inline-flex items-center"
>
<svg
role="img"
xmlns="http://www.w3.org/2000/svg"
width="18"
height="18"
fill="currentColor"
viewBox="0 0 24 24"
>
<path d="M20.317 4.3698a19.7913 19.7913 0 00-4.8851-1.5152.0741.0741 0 00-.0785.0371c-.211.3753-.4447.8648-.6083 1.2495-1.8447-.2762-3.68-.2762-5.4868 0-.1636-.3933-.4058-.8742-.6177-1.2495a.077.077 0 00-.0785-.037 19.7363 19.7363 0 00-4.8852 1.515.0699.0699 0 00-.0321.0277C.5334 9.0458-.319 13.5799.0992 18.0578a.0824.0824 0 00.0312.0561c2.0528 1.5076 4.0413 2.4228 5.9929 3.0294a.0777.0777 0 00.0842-.0276c.4616-.6304.8731-1.2952 1.226-1.9942a.076.076 0 00-.0416-.1057c-.6528-.2476-1.2743-.5495-1.8722-.8923a.077.077 0 01-.0076-.1277c.1258-.0943.2517-.1923.3718-.2914a.0743.0743 0 01.0776-.0105c3.9278 1.7933 8.18 1.7933 12.0614 0a.0739.0739 0 01.0785.0095c.1202.099.246.1981.3728.2924a.077.077 0 01-.0066.1276 12.2986 12.2986 0 01-1.873.8914.0766.0766 0 00-.0407.1067c.3604.698.7719 1.3628 1.225 1.9932a.076.076 0 00.0842.0286c1.961-.6067 3.9495-1.5219 6.0023-3.0294a.077.077 0 00.0313-.0552c.5004-5.177-.8382-9.6739-3.5485-13.6604a.061.061 0 00-.0312-.0286zM8.02 15.3312c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9555-2.4189 2.157-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.9555 2.4189-2.1569 2.4189zm7.9748 0c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9554-2.4189 2.1569-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.946 2.4189-2.1568 2.4189Z" />
</svg>
</a>
<a
href="https://github.com/getpaseo/paseo"
target="_blank"
rel="noopener noreferrer"
aria-label="GitHub"
className="text-muted-foreground hover:text-foreground transition-colors inline-flex items-center"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="18"
height="18"
fill="currentColor"
viewBox="0 0 24 24"
>
<path d="M12 0C5.37 0 0 5.484 0 12.252c0 5.418 3.438 10.013 8.205 11.637.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.738-4.042-1.61-4.042-1.61-.546-1.403-1.333-1.776-1.333-1.776-1.089-.756.084-.741.084-.741 1.205.087 1.838 1.262 1.838 1.262 1.07 1.87 2.809 1.33 3.495 1.017.108-.79.417-1.33.76-1.636-2.665-.31-5.467-1.35-5.467-6.005 0-1.327.465-2.413 1.235-3.262-.124-.31-.535-1.556.117-3.243 0 0 1.008-.33 3.3 1.248a11.2 11.2 0 0 1 3.003-.404c1.02.005 2.045.138 3.003.404 2.29-1.578 3.297-1.248 3.297-1.248.653 1.687.242 2.933.118 3.243.77.85 1.233 1.935 1.233 3.262 0 4.667-2.807 5.692-5.48 5.995.43.38.823 1.133.823 2.285 0 1.65-.015 2.98-.015 3.386 0 .315.218.694.825.576C20.565 22.26 24 17.667 24 12.252 24 5.484 18.627 0 12 0z" />
</svg>
</a>
</div>
</header>
<h1 className="text-3xl md:text-4xl font-semibold tracking-tight mb-2">Download</h1>
<p className="text-muted-foreground mb-10">
v{desktopVersion}
</p>
{/* Desktop */}
<section className="rounded-xl border border-border bg-card/40 p-6 md:p-8 mb-6">
<div className="flex items-center justify-between mb-8">
<h2 className="text-2xl font-semibold">Desktop</h2>
<MonitorIcon className="h-5 w-5 text-muted-foreground" />
</div>
<div className="divide-y divide-border">
{/* macOS */}
<div className="flex items-center justify-between py-5 first:pt-0 last:pb-0">
<div className="flex items-center gap-3">
<AppleIcon className="h-5 w-5 text-foreground" />
<span className="font-medium">macOS</span>
</div>
<div className="flex flex-wrap gap-2">
<DownloadPill
href={`${releaseBase}/Paseo-${desktopVersion}-arm64.dmg`}
label="Apple Silicon"
/>
<DownloadPill
href={`${releaseBase}/Paseo-${desktopVersion}.dmg`}
label="Intel"
/>
</div>
</div>
{/* Windows */}
<div className="flex items-center justify-between py-5 first:pt-0 last:pb-0">
<div className="flex items-center gap-3">
<WindowsIcon className="h-5 w-5 text-foreground" />
<span className="font-medium">Windows</span>
</div>
<div className="flex flex-wrap gap-2">
<DownloadPill
href={`${releaseBase}/Paseo-Setup-${desktopVersion}.exe`}
label="Download"
/>
</div>
</div>
{/* Linux */}
<div className="flex items-center justify-between py-5 first:pt-0 last:pb-0">
<div className="flex items-center gap-3">
<LinuxIcon className="h-5 w-5 text-foreground" />
<span className="font-medium">Linux</span>
</div>
<div className="flex flex-wrap gap-2">
<DownloadPill
href={`${releaseBase}/Paseo-${desktopVersion}.AppImage`}
label="AppImage"
/>
<DownloadPill
href={`${releaseBase}/paseo_${desktopVersion}_amd64.deb`}
label="DEB"
/>
<DownloadPill
href={`${releaseBase}/paseo-${desktopVersion}-1.x86_64.rpm`}
label="RPM"
/>
</div>
</div>
</div>
</section>
{/* Mobile */}
<section className="rounded-xl border border-border bg-card/40 p-6 md:p-8 mb-6">
<div className="flex items-center justify-between mb-8">
<h2 className="text-2xl font-semibold">Mobile</h2>
<PhoneIcon className="h-5 w-5 text-muted-foreground" />
</div>
<div className="divide-y divide-border">
{/* Android */}
<div className="flex items-center justify-between py-5 first:pt-0 last:pb-0">
<div className="flex items-center gap-3">
<AndroidIcon className="h-5 w-5 text-foreground" />
<span className="font-medium">Android</span>
</div>
<div className="flex flex-wrap gap-2">
<DownloadPill
href="https://play.google.com/store/apps/details?id=sh.paseo"
label="Play Store"
external
/>
<DownloadPill
href={`${releaseBase}/paseo-${desktopVersion}.apk`}
label="APK"
/>
</div>
</div>
{/* iOS */}
<div className="flex items-center justify-between py-5 first:pt-0 last:pb-0">
<div className="flex items-center gap-3">
<AppleIcon className="h-5 w-5 text-foreground" />
<span className="font-medium">iOS</span>
</div>
<div className="flex flex-wrap gap-2">
<DownloadPill
href="https://apps.apple.com/app/paseo-pocket-engineer/id6758887924"
label="App Store"
external
/>
</div>
</div>
</div>
</section>
{/* Web & CLI */}
<section className="rounded-xl border border-border bg-card/40 p-6 md:p-8">
<div className="flex items-center justify-between mb-8">
<h2 className="text-2xl font-semibold">Web & CLI</h2>
<TerminalIcon className="h-5 w-5 text-muted-foreground" />
</div>
<div className="divide-y divide-border">
<div className="flex items-center justify-between py-5 first:pt-0 last:pb-0">
<div className="flex items-center gap-3">
<GlobeIcon className="h-5 w-5 text-foreground" />
<span className="font-medium">Web App</span>
</div>
<div className="flex flex-wrap gap-2">
<DownloadPill
href="https://app.paseo.sh"
label="Open"
external
/>
</div>
</div>
<div className="flex items-center justify-between py-5 first:pt-0 last:pb-0">
<div className="flex items-center gap-3">
<TerminalIcon className="h-5 w-5 text-foreground" />
<span className="font-medium">CLI</span>
</div>
<div className="flex flex-wrap gap-2">
<code className="text-sm text-muted-foreground font-mono bg-muted px-3 py-1.5 rounded-lg">
npx paseo
</code>
</div>
</div>
</div>
</section>
<p className="text-center text-xs text-muted-foreground mt-8">
All releases are available on{" "}
<a
href="https://github.com/getpaseo/paseo/releases"
target="_blank"
rel="noopener noreferrer"
className="underline hover:text-foreground transition-colors"
>
GitHub
</a>
.
</p>
</div>
</div>
);
}
function DownloadPill({
href,
label,
external,
}: {
href: string;
label: string;
external?: boolean;
}) {
return (
<a
href={href}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center justify-center rounded-full bg-foreground px-4 py-1.5 text-sm font-medium text-background hover:bg-foreground/85 transition-colors"
>
{label}
{external && (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="ml-1.5 h-3 w-3"
aria-hidden="true"
>
<path d="M7 17L17 7" />
<path d="M7 7h10v10" />
</svg>
)}
</a>
);
}
function AppleIcon(props: React.SVGProps<SVGSVGElement>) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
aria-hidden="true"
{...props}
>
<path d="M12.152 6.896c-.948 0-2.415-1.078-3.96-1.04-2.04.027-3.91 1.183-4.961 3.014-2.117 3.675-.546 9.103 1.519 12.09 1.013 1.454 2.208 3.09 3.792 3.039 1.52-.065 2.09-.987 3.935-.987 1.831 0 2.35.987 3.96.948 1.637-.026 2.676-1.48 3.676-2.948 1.156-1.688 1.636-3.325 1.662-3.415-.039-.013-3.182-1.221-3.22-4.857-.026-3.04 2.48-4.494 2.597-4.559-1.429-2.09-3.623-2.324-4.39-2.376-2-.156-3.675 1.09-4.61 1.09zM15.53 3.83c.843-1.012 1.4-2.427 1.245-3.83-1.207.052-2.662.805-3.532 1.818-.78.896-1.454 2.338-1.273 3.714 1.338.104 2.715-.688 3.559-1.701" />
</svg>
);
}
function AndroidIcon(props: React.SVGProps<SVGSVGElement>) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"
fill="currentColor"
aria-hidden="true"
{...props}
>
<path d="M380.91,199l42.47-73.57a8.63,8.63,0,0,0-3.12-11.76,8.52,8.52,0,0,0-11.71,3.12l-43,74.52c-32.83-15-69.78-23.35-109.52-23.35s-76.69,8.36-109.52,23.35l-43-74.52a8.6,8.6,0,1,0-14.88,8.64L131,199C57.8,238.64,8.19,312.77,0,399.55H512C503.81,312.77,454.2,238.64,380.91,199ZM138.45,327.65a21.46,21.46,0,1,1,21.46-21.46A21.47,21.47,0,0,1,138.45,327.65Zm235,0A21.46,21.46,0,1,1,395,306.19,21.47,21.47,0,0,1,373.49,327.65Z" />
</svg>
);
}
function WindowsIcon(props: React.SVGProps<SVGSVGElement>) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
aria-hidden="true"
{...props}
>
<path d="M0,0H11.377V11.372H0ZM12.623,0H24V11.372H12.623ZM0,12.623H11.377V24H0Zm12.623,0H24V24H12.623" />
</svg>
);
}
function LinuxIcon(props: React.SVGProps<SVGSVGElement>) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
aria-hidden="true"
{...props}
>
<path d="M8.996 4.497c.104-.076.1-.168.186-.158s.022.102-.098.207c-.12.104-.308.243-.46.323-.291.152-.631.336-.993.336s-.647-.167-.853-.33c-.102-.082-.186-.162-.248-.221-.11-.086-.096-.207-.052-.204.075.01.087.109.134.153.064.06.144.137.241.214.195.154.454.304.778.304s.702-.19.932-.32c.13-.073.297-.204.433-.304M7.34 3.781c.055-.02.123-.031.174-.003.011.006.024.021.02.034-.012.038-.074.032-.11.05-.032.017-.057.052-.093.054-.034 0-.086-.012-.09-.046-.007-.044.058-.072.1-.089m.581-.003c.05-.028.119-.018.173.003.041.017.106.045.1.09-.004.033-.057.046-.09.045-.036-.002-.062-.037-.093-.053-.036-.019-.098-.013-.11-.051-.004-.013.008-.028.02-.034" />
<path
fillRule="evenodd"
d="M8.446.019c2.521.003 2.38 2.66 2.364 4.093-.01.939.509 1.574 1.04 2.244.474.56 1.095 1.38 1.45 2.32.29.765.402 1.613.115 2.465a.8.8 0 0 1 .254.152l.001.002c.207.175.271.447.329.698.058.252.112.488.224.615.344.382.494.667.48.922-.015.254-.203.43-.435.57-.465.28-1.164.491-1.586 1.002-.443.527-.99.83-1.505.871a1.25 1.25 0 0 1-1.256-.716v-.001a1 1 0 0 1-.078-.21c-.67.038-1.252-.165-1.718-.128-.687.038-1.116.204-1.506.206-.151.331-.445.547-.808.63-.5.114-1.126 0-1.743-.324-.577-.306-1.31-.278-1.85-.39-.27-.057-.51-.157-.626-.384-.116-.226-.095-.538.07-.988.051-.16.012-.398-.026-.648a2.5 2.5 0 0 1-.037-.369c0-.133.022-.265.087-.386v-.002c.14-.266.368-.377.577-.451s.397-.125.53-.258c.143-.15.27-.374.443-.56q.036-.037.073-.07c-.081-.538.007-1.105.192-1.662.393-1.18 1.223-2.314 1.811-3.014.502-.713.65-1.287.701-2.016.042-.997-.705-3.974 2.112-4.2q.168-.015.321-.013m2.596 10.866-.03.016c-.223.121-.348.337-.427.656-.08.32-.107.733-.13 1.206v.001c-.023.37-.192.824-.31 1.267s-.176.862-.036 1.128v.002c.226.452.608.636 1.051.601s.947-.304 1.36-.795c.474-.576 1.218-.796 1.638-1.05.21-.126.324-.242.333-.4.009-.157-.097-.403-.425-.767-.17-.192-.217-.462-.274-.71-.056-.247-.122-.468-.26-.585l-.001-.001c-.18-.157-.356-.17-.565-.164q-.069.001-.14.005c-.239.275-.805.612-1.197.508-.359-.09-.562-.508-.587-.918m-7.204.03H3.83c-.189.002-.314.09-.44.225-.149.158-.276.382-.445.56v.002h-.002c-.183.184-.414.239-.61.31-.195.069-.353.143-.46.35v.002c-.085.155-.066.378-.029.624.038.245.096.507.018.746v.002l-.001.002c-.157.427-.155.678-.082.822.074.143.235.22.48.272.493.103 1.26.069 1.906.41.583.305 1.168.404 1.598.305.431-.098.712-.369.75-.867v-.002c.029-.292-.195-.673-.485-1.052-.29-.38-.633-.752-.795-1.09v-.002l-.61-1.11c-.21-.286-.43-.462-.68-.5a1 1 0 0 0-.106-.008M9.584 4.85c-.14.2-.386.37-.695.467-.147.048-.302.17-.495.28a1.3 1.3 0 0 1-.74.19.97.97 0 0 1-.582-.227c-.14-.113-.25-.237-.394-.322a3 3 0 0 1-.192-.126c-.063 1.179-.85 2.658-1.226 3.511a5.4 5.4 0 0 0-.43 1.917c-.68-.906-.184-2.066.081-2.568.297-.55.343-.701.27-.649-.266.436-.685 1.13-.848 1.844-.085.372-.1.749.01 1.097.11.349.345.67.766.931.573.351.963.703 1.193 1.015s.302.584.23.777a.4.4 0 0 1-.212.22.7.7 0 0 1-.307.056l.184.235c.094.124.186.249.266.375 1.179.805 2.567.496 3.568-.218.1-.342.197-.664.212-.903.024-.474.05-.896.136-1.245s.244-.634.53-.791a1 1 0 0 1 .138-.061q.005-.045.013-.087c.082-.546.569-.572 1.18-.303.588.266.81.499.71.814h.13c.122-.398-.133-.69-.822-1.025l-.137-.06a2.35 2.35 0 0 0-.012-1.113c-.188-.79-.704-1.49-1.098-1.838-.072-.003-.065.06.081.203.363.333 1.156 1.532.727 2.644a1.2 1.2 0 0 0-.342-.043c-.164-.907-.543-1.66-.735-2.014-.359-.668-.918-2.036-1.158-2.983M7.72 3.503a1 1 0 0 0-.312.053c-.268.093-.447.286-.559.391-.022.021-.05.04-.119.091s-.172.126-.321.238q-.198.151-.13.38c.046.15.192.325.459.476.166.098.28.23.41.334a1 1 0 0 0 .215.133.9.9 0 0 0 .298.066c.282.017.49-.068.673-.173s.34-.233.518-.29c.365-.115.627-.345.709-.564a.37.37 0 0 0-.01-.309c-.048-.096-.148-.187-.318-.257h-.001c-.354-.151-.507-.162-.705-.29-.321-.207-.587-.28-.807-.279m-.89-1.122h-.025a.4.4 0 0 0-.278.135.76.76 0 0 0-.191.334 1.2 1.2 0 0 0-.051.445v.001c.01.162.041.299.102.436.05.116.109.204.183.274l.089-.065.117-.09-.023-.018a.4.4 0 0 1-.11-.161.7.7 0 0 1-.054-.22v-.01a.7.7 0 0 1 .014-.234.4.4 0 0 1 .08-.179q.056-.069.126-.073h.013a.18.18 0 0 1 .123.05c.045.04.08.09.11.162a.7.7 0 0 1 .054.22v.01a.7.7 0 0 1-.002.17 1.1 1.1 0 0 1 .317-.143 1.3 1.3 0 0 0 .002-.194V3.23a1.2 1.2 0 0 0-.102-.437.8.8 0 0 0-.227-.31.4.4 0 0 0-.268-.102m1.95-.155a.63.63 0 0 0-.394.14.9.9 0 0 0-.287.376 1.2 1.2 0 0 0-.1.51v.015q0 .079.01.152c.114.027.278.074.406.138a1 1 0 0 1-.011-.172.8.8 0 0 1 .058-.278.5.5 0 0 1 .139-.2.26.26 0 0 1 .182-.069.26.26 0 0 1 .178.081c.055.054.094.12.124.21.029.086.042.17.04.27l-.002.012a.8.8 0 0 1-.057.277c-.024.059-.089.106-.122.145.046.016.09.03.146.052a5 5 0 0 1 .248.102 1.2 1.2 0 0 0 .244-.763 1.2 1.2 0 0 0-.11-.495.9.9 0 0 0-.294-.37.64.64 0 0 0-.39-.133z"
/>
</svg>
);
}
function MonitorIcon(props: React.SVGProps<SVGSVGElement>) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
aria-hidden="true"
{...props}
>
<rect width="20" height="14" x="2" y="3" rx="2" />
<line x1="8" x2="16" y1="21" y2="21" />
<line x1="12" x2="12" y1="17" y2="21" />
</svg>
);
}
function PhoneIcon(props: React.SVGProps<SVGSVGElement>) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
aria-hidden="true"
{...props}
>
<rect width="14" height="20" x="5" y="2" rx="2" ry="2" />
<path d="M12 18h.01" />
</svg>
);
}
function TerminalIcon(props: React.SVGProps<SVGSVGElement>) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
aria-hidden="true"
{...props}
>
<polyline points="4 17 10 11 4 5" />
<line x1="12" x2="20" y1="19" y2="19" />
</svg>
);
}
function GlobeIcon(props: React.SVGProps<SVGSVGElement>) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
aria-hidden="true"
{...props}
>
<circle cx="12" cy="12" r="9" />
<path d="M3 12h18" />
<path d="M12 3a15 15 0 0 1 0 18" />
<path d="M12 3a15 15 0 0 0 0 18" />
</svg>
);
}