fix(website): smooth docs navigation and improve lighthouse
This commit is contained in:
@@ -52,7 +52,7 @@ export function CommandDialog({
|
||||
|
||||
return (
|
||||
<div className="relative" ref={ref}>
|
||||
<button type="button" onClick={handleToggle}>
|
||||
<button type="button" aria-label={title} onClick={handleToggle}>
|
||||
{trigger}
|
||||
</button>
|
||||
<AnimatePresence>
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
import * as React from "react";
|
||||
import ReactMarkdown, { type Components } from "react-markdown";
|
||||
import { getDocsHighlighter, docsRehypePlugins, docsRemarkPlugins } from "~/docs-rehype";
|
||||
|
||||
function getCodeText(children: React.ReactNode): string {
|
||||
return React.Children.toArray(children).join("");
|
||||
}
|
||||
|
||||
function getLanguage(className: string | undefined): string | undefined {
|
||||
return className?.match(/language-([^\s]+)/)?.[1];
|
||||
}
|
||||
|
||||
function DocsPre({
|
||||
children,
|
||||
node: _node,
|
||||
...props
|
||||
}: React.ComponentProps<"pre"> & { node?: unknown }) {
|
||||
const codeElement = React.Children.only(children);
|
||||
const codeProps = React.isValidElement<React.ComponentProps<"code">>(codeElement)
|
||||
? codeElement.props
|
||||
: undefined;
|
||||
const code = getCodeText(codeProps?.children);
|
||||
const language = getLanguage(codeProps?.className);
|
||||
const [highlightedHtml, setHighlightedHtml] = React.useState<string | null>(null);
|
||||
|
||||
React.useEffect(() => {
|
||||
let cancelled = false;
|
||||
|
||||
setHighlightedHtml(null);
|
||||
if (!language) return;
|
||||
const lang = language;
|
||||
|
||||
async function highlight() {
|
||||
try {
|
||||
const highlighter = await getDocsHighlighter();
|
||||
if (cancelled) return;
|
||||
setHighlightedHtml(
|
||||
highlighter.codeToHtml(code.replace(/\n$/, ""), {
|
||||
lang,
|
||||
theme: "catppuccin-mocha",
|
||||
}),
|
||||
);
|
||||
} catch {
|
||||
if (!cancelled) setHighlightedHtml(null);
|
||||
}
|
||||
}
|
||||
|
||||
void highlight();
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [code, language]);
|
||||
|
||||
const highlightedMarkup = React.useMemo(
|
||||
() => (highlightedHtml ? { __html: highlightedHtml } : undefined),
|
||||
[highlightedHtml],
|
||||
);
|
||||
|
||||
if (highlightedMarkup) {
|
||||
return <div dangerouslySetInnerHTML={highlightedMarkup} />;
|
||||
}
|
||||
|
||||
return <pre {...props}>{children}</pre>;
|
||||
}
|
||||
|
||||
const docsMarkdownComponents: Components = {
|
||||
pre: DocsPre,
|
||||
};
|
||||
|
||||
export function DocsMarkdown({ children }: { children: string }) {
|
||||
return (
|
||||
<ReactMarkdown
|
||||
remarkPlugins={docsRemarkPlugins}
|
||||
rehypePlugins={docsRehypePlugins}
|
||||
components={docsMarkdownComponents}
|
||||
>
|
||||
{children}
|
||||
</ReactMarkdown>
|
||||
);
|
||||
}
|
||||
@@ -894,12 +894,15 @@ function GetStarted() {
|
||||
<ServerInstallButton />
|
||||
</div>
|
||||
<div className="pt-3">
|
||||
<a href="/download" className="text-xs text-white/40 hover:text-white/70 transition-colors">
|
||||
<a
|
||||
href="/download"
|
||||
className="text-xs text-muted-foreground hover:text-foreground 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>
|
||||
<span className="text-xs text-muted-foreground">Supports</span>
|
||||
<div className="flex items-center gap-1">
|
||||
<AgentBadge name="Claude Code" icon={CLAUDE_CODE_BADGE_ICON} />
|
||||
<AgentBadge name="Codex" icon={CODEX_BADGE_ICON} />
|
||||
@@ -1338,8 +1341,14 @@ function PhoneShowcase() {
|
||||
{/* Left phone — rotated to face inward */}
|
||||
<motion.div style={leftPhoneStyle} className="w-[160px] md:w-[240px] absolute">
|
||||
<img
|
||||
src="/phone-1.png"
|
||||
src="/phone-1-480.webp"
|
||||
srcSet="/phone-1-320.webp 320w, /phone-1-480.webp 480w"
|
||||
sizes="(min-width: 768px) 240px, 160px"
|
||||
alt="Paseo sessions list"
|
||||
width={480}
|
||||
height={1044}
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
className="w-full rounded-[40px] shadow-2xl border-[3px] border-black outline-[3px] outline-white/20"
|
||||
/>
|
||||
</motion.div>
|
||||
@@ -1352,8 +1361,14 @@ function PhoneShowcase() {
|
||||
className="w-[220px] md:w-[240px] relative z-10"
|
||||
>
|
||||
<img
|
||||
src="/phone-2.png"
|
||||
src="/phone-2-480.webp"
|
||||
srcSet="/phone-2-320.webp 320w, /phone-2-480.webp 480w"
|
||||
sizes="(min-width: 768px) 240px, 220px"
|
||||
alt="Paseo agent chat"
|
||||
width={480}
|
||||
height={1044}
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
className="w-full rounded-[40px] shadow-2xl border-[3px] border-black outline-[3px] outline-white/20"
|
||||
/>
|
||||
</motion.div>
|
||||
@@ -1361,8 +1376,14 @@ function PhoneShowcase() {
|
||||
{/* Right phone — rotated to face inward */}
|
||||
<motion.div style={rightPhoneStyle} className="w-[160px] md:w-[240px] absolute">
|
||||
<img
|
||||
src="/phone-3.png"
|
||||
src="/phone-3-480.webp"
|
||||
srcSet="/phone-3-320.webp 320w, /phone-3-480.webp 480w"
|
||||
sizes="(min-width: 768px) 240px, 160px"
|
||||
alt="Paseo diff view"
|
||||
width={480}
|
||||
height={1044}
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
className="w-full rounded-[40px] shadow-2xl border-[3px] border-black outline-[3px] outline-white/20"
|
||||
/>
|
||||
</motion.div>
|
||||
@@ -1425,7 +1446,7 @@ function CLISection() {
|
||||
|
||||
<a
|
||||
href="/docs/cli"
|
||||
className="inline-flex items-center gap-1.5 text-xs text-white/40 hover:text-white/70 transition-colors"
|
||||
className="inline-flex items-center gap-1.5 text-xs text-muted-foreground hover:text-foreground transition-colors"
|
||||
>
|
||||
Full CLI reference
|
||||
<svg
|
||||
|
||||
@@ -13,30 +13,39 @@ export function SiteFooter({ width = "default" }: SiteFooterProps) {
|
||||
<div className="space-y-3">
|
||||
<p className="text-white/60 font-medium">Product</p>
|
||||
<div className="space-y-2">
|
||||
<a href="/blog" className="block text-white/40 hover:text-white/60 transition-colors">
|
||||
<a
|
||||
href="/blog"
|
||||
className="block text-muted-foreground hover:text-foreground transition-colors"
|
||||
>
|
||||
Blog
|
||||
</a>
|
||||
<a href="/docs" className="block text-white/40 hover:text-white/60 transition-colors">
|
||||
<a
|
||||
href="/docs"
|
||||
className="block text-muted-foreground hover:text-foreground transition-colors"
|
||||
>
|
||||
Docs
|
||||
</a>
|
||||
<a
|
||||
href="/changelog"
|
||||
className="block text-white/40 hover:text-white/60 transition-colors"
|
||||
className="block text-muted-foreground hover:text-foreground transition-colors"
|
||||
>
|
||||
Changelog
|
||||
</a>
|
||||
<a href="/cloud" className="block text-white/40 hover:text-white/60 transition-colors">
|
||||
<a
|
||||
href="/cloud"
|
||||
className="block text-muted-foreground hover:text-foreground transition-colors"
|
||||
>
|
||||
Cloud
|
||||
</a>
|
||||
<a
|
||||
href="/docs/cli"
|
||||
className="block text-white/40 hover:text-white/60 transition-colors"
|
||||
className="block text-muted-foreground hover:text-foreground transition-colors"
|
||||
>
|
||||
CLI
|
||||
</a>
|
||||
<a
|
||||
href="/privacy"
|
||||
className="block text-white/40 hover:text-white/60 transition-colors"
|
||||
className="block text-muted-foreground hover:text-foreground transition-colors"
|
||||
>
|
||||
Privacy
|
||||
</a>
|
||||
@@ -47,16 +56,19 @@ export function SiteFooter({ width = "default" }: SiteFooterProps) {
|
||||
<div className="space-y-2">
|
||||
<a
|
||||
href="/claude-code"
|
||||
className="block text-white/40 hover:text-white/60 transition-colors"
|
||||
className="block text-muted-foreground hover:text-foreground transition-colors"
|
||||
>
|
||||
Claude Code
|
||||
</a>
|
||||
<a href="/codex" className="block text-white/40 hover:text-white/60 transition-colors">
|
||||
<a
|
||||
href="/codex"
|
||||
className="block text-muted-foreground hover:text-foreground transition-colors"
|
||||
>
|
||||
Codex
|
||||
</a>
|
||||
<a
|
||||
href="/opencode"
|
||||
className="block text-white/40 hover:text-white/60 transition-colors"
|
||||
className="block text-muted-foreground hover:text-foreground transition-colors"
|
||||
>
|
||||
OpenCode
|
||||
</a>
|
||||
@@ -69,7 +81,7 @@ export function SiteFooter({ width = "default" }: SiteFooterProps) {
|
||||
href="https://discord.gg/jz8T2uahpH"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="block text-white/40 hover:text-white/60 transition-colors"
|
||||
className="block text-muted-foreground hover:text-foreground transition-colors"
|
||||
>
|
||||
Discord
|
||||
</a>
|
||||
@@ -77,7 +89,7 @@ export function SiteFooter({ width = "default" }: SiteFooterProps) {
|
||||
href="https://github.com/getpaseo/paseo"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="block text-white/40 hover:text-white/60 transition-colors"
|
||||
className="block text-muted-foreground hover:text-foreground transition-colors"
|
||||
>
|
||||
GitHub
|
||||
</a>
|
||||
@@ -90,7 +102,7 @@ export function SiteFooter({ width = "default" }: SiteFooterProps) {
|
||||
href={appStoreUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="block text-white/40 hover:text-white/60 transition-colors"
|
||||
className="block text-muted-foreground hover:text-foreground transition-colors"
|
||||
>
|
||||
App Store
|
||||
</a>
|
||||
@@ -98,7 +110,7 @@ export function SiteFooter({ width = "default" }: SiteFooterProps) {
|
||||
href={playStoreUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="block text-white/40 hover:text-white/60 transition-colors"
|
||||
className="block text-muted-foreground hover:text-foreground transition-colors"
|
||||
>
|
||||
Google Play
|
||||
</a>
|
||||
@@ -106,7 +118,7 @@ export function SiteFooter({ width = "default" }: SiteFooterProps) {
|
||||
href="https://github.com/getpaseo/paseo/releases"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="block text-white/40 hover:text-white/60 transition-colors"
|
||||
className="block text-muted-foreground hover:text-foreground transition-colors"
|
||||
>
|
||||
Desktop
|
||||
</a>
|
||||
@@ -114,7 +126,7 @@ export function SiteFooter({ width = "default" }: SiteFooterProps) {
|
||||
href={webAppUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="block text-white/40 hover:text-white/60 transition-colors"
|
||||
className="block text-muted-foreground hover:text-foreground transition-colors"
|
||||
>
|
||||
Web App
|
||||
</a>
|
||||
|
||||
@@ -48,7 +48,7 @@ export function SiteHeader() {
|
||||
className="text-muted-foreground hover:text-foreground transition-colors inline-flex items-center"
|
||||
>
|
||||
<svg
|
||||
role="img"
|
||||
aria-hidden="true"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="18"
|
||||
height="18"
|
||||
@@ -62,7 +62,7 @@ export function SiteHeader() {
|
||||
href="https://github.com/getpaseo/paseo"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
aria-label="GitHub"
|
||||
aria-label={stars ? `GitHub, ${stars} stars` : "GitHub"}
|
||||
className="text-muted-foreground hover:text-foreground transition-colors inline-flex items-center gap-1.5"
|
||||
>
|
||||
<svg
|
||||
|
||||
Reference in New Issue
Block a user