- )
+ );
} catch (error) {
- console.error('Failed to load docs index:', error)
+ console.error("Failed to load docs index:", error);
+
return (
-
-
No Documentation Found
-
- We could not find any documentation files. Please ensure content is synced to src/content/doc.
+
+
+ No Documentation Found
+
+
+ We could not find any documentation files. Please ensure content is
+ synced to src/content/doc.
- )
+ );
}
}
diff --git a/src/app/globals.css b/src/app/globals.css
index 795bcfc..db7e724 100644
--- a/src/app/globals.css
+++ b/src/app/globals.css
@@ -161,6 +161,156 @@ button {
font-family: var(--font-editorial-display);
letter-spacing: -0.04em;
}
+
+ .public-doc-prose {
+ max-width: none;
+ color: var(--color-text-muted);
+ font-size: 1rem;
+ line-height: 1.85;
+ }
+
+ .public-doc-prose > :first-child {
+ margin-top: 0;
+ }
+
+ .public-doc-prose > :last-child {
+ margin-bottom: 0;
+ }
+
+ .public-doc-prose h1,
+ .public-doc-prose h2,
+ .public-doc-prose h3,
+ .public-doc-prose h4,
+ .public-doc-prose h5,
+ .public-doc-prose h6 {
+ scroll-margin-top: calc(var(--app-shell-nav-offset) + 1rem);
+ margin-top: 2.75rem;
+ margin-bottom: 0.9rem;
+ color: var(--color-heading);
+ font-weight: 600;
+ letter-spacing: -0.04em;
+ line-height: 1.08;
+ }
+
+ .public-doc-prose h1 {
+ font-size: clamp(2rem, 4vw, 2.9rem);
+ }
+
+ .public-doc-prose h2 {
+ font-size: clamp(1.55rem, 2.2vw, 2rem);
+ }
+
+ .public-doc-prose h3 {
+ font-size: clamp(1.25rem, 1.8vw, 1.5rem);
+ }
+
+ .public-doc-prose p,
+ .public-doc-prose ul,
+ .public-doc-prose ol,
+ .public-doc-prose blockquote,
+ .public-doc-prose pre,
+ .public-doc-prose table {
+ margin: 1rem 0;
+ }
+
+ .public-doc-prose p,
+ .public-doc-prose li {
+ color: var(--color-text-muted);
+ line-height: 1.85;
+ }
+
+ .public-doc-prose strong {
+ color: var(--color-heading);
+ font-weight: 600;
+ }
+
+ .public-doc-prose a {
+ color: var(--color-primary);
+ font-weight: 600;
+ text-decoration: none;
+ }
+
+ .public-doc-prose a:hover {
+ text-decoration: underline;
+ }
+
+ .public-doc-prose ul,
+ .public-doc-prose ol {
+ padding-left: 1.35rem;
+ }
+
+ .public-doc-prose li + li {
+ margin-top: 0.35rem;
+ }
+
+ .public-doc-prose code {
+ border: 1px solid rgba(15, 23, 42, 0.08);
+ border-radius: 999px;
+ background: #f8f4ec;
+ padding: 0.12rem 0.42rem;
+ color: var(--color-heading);
+ font-family: var(--font-geist-mono);
+ font-size: 0.92em;
+ }
+
+ .public-doc-prose pre {
+ overflow-x: auto;
+ border: 1px solid rgba(15, 23, 42, 0.08);
+ border-radius: 1.25rem;
+ background: #f5f2eb;
+ padding: 1rem 1.15rem;
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.5);
+ }
+
+ .public-doc-prose pre code {
+ border: none;
+ border-radius: 0;
+ background: transparent;
+ padding: 0;
+ color: inherit;
+ font-size: 0.92rem;
+ }
+
+ .public-doc-prose blockquote {
+ border-left: 2px solid rgba(51, 102, 255, 0.24);
+ border-radius: 0 1rem 1rem 0;
+ background: rgba(248, 244, 236, 0.78);
+ padding: 0.75rem 0 0.75rem 1rem;
+ color: var(--color-heading);
+ }
+
+ .public-doc-prose hr {
+ margin: 2rem 0;
+ border: none;
+ border-top: 1px solid var(--color-divider);
+ }
+
+ .public-doc-prose table {
+ width: 100%;
+ border-collapse: collapse;
+ overflow: hidden;
+ border: 1px solid rgba(15, 23, 42, 0.08);
+ border-radius: 1rem;
+ }
+
+ .public-doc-prose th,
+ .public-doc-prose td {
+ border-bottom: 1px solid rgba(15, 23, 42, 0.08);
+ padding: 0.8rem 1rem;
+ text-align: left;
+ vertical-align: top;
+ }
+
+ .public-doc-prose th {
+ background: rgba(248, 244, 236, 0.82);
+ color: var(--color-heading);
+ font-weight: 600;
+ }
+
+ .public-doc-prose img {
+ border: 1px solid rgba(15, 23, 42, 0.08);
+ border-radius: 1rem;
+ }
}
@media (max-width: 1023px) {
diff --git a/src/app/services/page.tsx b/src/app/services/page.tsx
index d884dc0..2bbc566 100644
--- a/src/app/services/page.tsx
+++ b/src/app/services/page.tsx
@@ -7,77 +7,56 @@ import {
Bot,
Box,
CloudCog,
+ Command,
Database,
FileEdit,
Gauge,
+ type LucideIcon,
MessageCircle,
Network,
} from "lucide-react";
-import Footer from "../../components/Footer";
-import UnifiedNavigation from "../../components/UnifiedNavigation";
-import { useLanguage } from "../../i18n/LanguageProvider";
-import { useViewStore } from "../../components/theme/viewStore";
-import Material3Layout from "./Material3Layout";
+
+import {
+ PublicPageIntro,
+ PublicPageShell,
+} from "@/components/public/PublicPageShell";
+import { useLanguage } from "@/i18n/LanguageProvider";
+import { cn } from "@/lib/utils";
const placeholderCount = 1;
+
type ServiceCardData = {
key: string;
name: string;
description: string;
href: string;
- icon: any;
+ icon: LucideIcon;
external?: boolean;
};
-const ServiceCard = ({
+function ServiceCard({
service,
- view,
isChinese,
}: {
service: ServiceCardData;
- view: "classic" | "material";
isChinese: boolean;
-}) => {
- const isMaterial = view === "material";
-
- const cardContent = (
-
-
-
+}) {
+ const content = (
+
+
+
-
-
+
+
{service.name}
-
-
+
+
{service.description}
-
+
{isChinese ? "打开" : "Open"}
@@ -92,111 +71,44 @@ const ServiceCard = ({
rel="noopener noreferrer"
className="block"
>
- {cardContent}
+ {content}
);
}
return (
- {cardContent}
+ {content}
);
-};
-
-const PlaceholderCard = ({
- view,
- isChinese,
-}: {
- view: "classic" | "material";
- isChinese: boolean;
-}) => {
- const isMaterial = view === "material";
- const placeholderLabel = isChinese
- ? "更多服务即将上线"
- : "More services coming soon";
- const placeholderDescription = isChinese
- ? "预留卡片位置,持续扩充入口。"
- : "Reserved slots for new service entries.";
+}
+function PlaceholderCard({ isChinese }: { isChinese: boolean }) {
return (
-
-
-
+
+
+
-
- {placeholderLabel}
+
+
+ {isChinese ? "更多服务即将上线" : "More services coming soon"}
+
+
+ {isChinese
+ ? "预留卡片位置,持续扩充入口。"
+ : "Reserved slots for new service entries."}
+
-
- {placeholderDescription}
-
-
+
{isChinese ? "敬请期待" : "Stay tuned"}
);
-};
-
-const ServiceGrid = ({
- view,
- services,
- isChinese,
-}: {
- view: "classic" | "material";
- services: ServiceCardData[];
- isChinese: boolean;
-}) => {
- return (
-
- {services.map((service) => (
-
- ))}
- {Array.from({ length: placeholderCount }).map((_, index) => (
-
- ))}
-
- );
-};
-
-const ClawdbotLogo = (props: any) => (
-
-);
+}
export default function ServicesPage() {
- const { view, isHydrated } = useViewStore();
const { language } = useLanguage();
const isChinese = language === "zh";
@@ -299,71 +211,83 @@ export default function ServicesPage() {
external: true,
},
{
- key: "moltbot",
+ key: "xworkmate",
name: "XWorkmate",
description: isChinese
? "在线版 XWorkmate 工作区,底层由 OpenClaw gateway 驱动。"
: "Online XWorkmate workspace powered by the OpenClaw gateway.",
href: "/xworkmate",
- icon: ClawdbotLogo,
+ icon: Command,
},
];
- if (!isHydrated) {
- return null;
- }
-
- if (view === "material") {
- return (
-
-
-
- Service Overview
-
-
- Real-time metrics and system health for your current production
- environment.
-
-
-
-
- );
- }
-
return (
-
-
-
-
-
-
-
- {isChinese ? "更多服务" : "More services"}
-
-
- {isChinese ? "扩展服务与工具箱" : "Extended Services & Toolbox"}
-
-
- {isChinese
- ? "汇聚开发辅助、运维监控与核心制品,构建无缝衔接的云原生工作台。"
- : "A unified hub for development aids, operations monitoring, and core artifacts."}
-
-
-
+
+
+
+
+ {isChinese ? "页面原则" : "Page rhythm"}
+
+
+ {isChinese
+ ? "保持结构不变,但去掉 classic / material 的风格分裂。"
+ : "Keep the structure, remove the classic/material visual split."}
+
+
+
+
+
+
+
+
+
+ {isChinese ? "服务目录" : "Service directory"}
+
+
+ {isChinese
+ ? "每个入口都使用同一种卡片语法:白底、细边框、轻阴影、明确标题。"
+ : "Every entry now follows the same card grammar: pale surface, fine border, light shadow, and clear hierarchy."}
+
+
+
+ {services.length} {isChinese ? "个入口" : "entries"}
+
+
+
+
+ {services.map((service) => (
+
+ ))}
+ {Array.from({ length: placeholderCount }).map((_, index) => (
+
+ ))}
+
+
+
);
}
diff --git a/src/components/doc/DocArticle.tsx b/src/components/doc/DocArticle.tsx
index fc0af0d..fb7e9b1 100644
--- a/src/components/doc/DocArticle.tsx
+++ b/src/components/doc/DocArticle.tsx
@@ -1,17 +1,17 @@
-import { marked } from 'marked'
+import { marked } from "marked";
interface DocArticleProps {
- content: string
+ content: string;
}
export default async function DocArticle({ content }: DocArticleProps) {
// Convert markdown to HTML
- const htmlContent = await marked(content)
+ const htmlContent = await marked(content);
return (
- )
+ );
}
diff --git a/src/components/doc/DocMetaPanel.tsx b/src/components/doc/DocMetaPanel.tsx
index bec7fb4..41407a4 100644
--- a/src/components/doc/DocMetaPanel.tsx
+++ b/src/components/doc/DocMetaPanel.tsx
@@ -1,29 +1,40 @@
-import ClientTime from '@/app/components/ClientTime'
+import ClientTime from "@/app/components/ClientTime";
interface DocMetaPanelProps {
- description?: string
- updatedAt?: string
- tags?: string[]
+ description?: string;
+ updatedAt?: string;
+ tags?: string[];
}
-export default function DocMetaPanel({ description, updatedAt, tags }: DocMetaPanelProps) {
+export default function DocMetaPanel({
+ description,
+ updatedAt,
+ tags,
+}: DocMetaPanelProps) {
return (
-
- {description &&
{description}
}
- {tags && tags.length > 0 && (
+
+ {description ? (
+
{description}
+ ) : null}
+
+ {tags && tags.length > 0 ? (
{tags.map((tag) => (
-
+
{tag}
))}
- )}
- {updatedAt && (
-
+ ) : null}
+
+ {updatedAt ? (
+
Updated
- )}
+ ) : null}
- )
+ );
}
diff --git a/src/components/public/PublicPageShell.tsx b/src/components/public/PublicPageShell.tsx
new file mode 100644
index 0000000..b4b360a
--- /dev/null
+++ b/src/components/public/PublicPageShell.tsx
@@ -0,0 +1,82 @@
+import Footer from "@/components/Footer";
+import UnifiedNavigation from "@/components/UnifiedNavigation";
+import { cn } from "@/lib/utils";
+
+type PublicPageShellProps = {
+ children: React.ReactNode;
+ mainClassName?: string;
+ containerClassName?: string;
+};
+
+type PublicPageIntroProps = {
+ eyebrow?: string;
+ title: string;
+ subtitle?: string;
+ titleClassName?: string;
+ className?: string;
+};
+
+export function PublicPageShell({
+ children,
+ mainClassName,
+ containerClassName,
+}: PublicPageShellProps) {
+ return (
+
+
+
+
+
+
+ {children}
+
+
+
+
+
+ );
+}
+
+export function PublicPageIntro({
+ eyebrow,
+ title,
+ subtitle,
+ titleClassName,
+ className,
+}: PublicPageIntroProps) {
+ return (
+
+ {eyebrow ? (
+
+ {eyebrow}
+
+ ) : null}
+
+ {title}
+
+ {subtitle ? (
+
+ {subtitle}
+
+ ) : null}
+
+ );
+}