Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 93 additions & 0 deletions docs/app/(home)/Openclaw-OS/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import styles from "../page.module.css";
import { FeaturesSection, OPENCLAW_FEATURES } from "../sections/FeaturesSection/FeaturesSection";
import { Footer } from "../sections/Footer/Footer";
import { GradientDivider } from "../sections/GradientDivider/GradientDivider";
import { HeroSection } from "../sections/HeroSection/HeroSection";
import heroStyles from "../sections/HeroSection/HeroSection.module.css";
import { PossibilitiesSection } from "../sections/PossibilitiesSection/PossibilitiesSection";
import { StuckInChatSection } from "../sections/StuckInChatSection/StuckInChatSection";

const INSTALL_COMMAND = "curl -fsSL https://openui.com/openclaw-os/install.sh | bash";

export default function OpenClawOSPage() {
return (
<div className={styles.page}>
<div className={styles.heroShell}>
<HeroSection
title={
<>
OpenClaw <span className={heroStyles.titleAccent}>OS</span>
</>
}
subtitle={
<>
The Default workspace for{" "}
<span className={heroStyles.subtitleLogoTile} aria-hidden="true">
<img src="/openclaw-dark.svg" alt="" />
</span>
OpenClaw.
</>
}
command={INSTALL_COMMAND}
compact
showBanner={false}
showPlaygroundButton={false}
desktopPreviewImage="/OpenclawOS-hero.png"
desktopPreviewImageAlt="OpenClaw OS desktop preview"
desktopPreviewImageWidth={3200}
desktopPreviewImageHeight={1036}
mobilePreviewImage="/openclaw-os-mobile-hero.png"
mobilePreviewImageAlt="OpenClaw OS mobile preview"
mobilePreviewImageWidth={804}
mobilePreviewImageHeight={880}
mobilePreviewImageCropTopPercent={20}
showGitHubBanner={false}
widePreview
showTagline
taglineCompact
tagline={
<>
OpenClaw OS is a workspace for managing and operating your OpenClaw agent.{" "}
<br className={heroStyles.taglineBreak} />
Generate interactive apps and artifacts, instantly for any use case.
</>
}
/>
</div>
<div className={styles.contentSection}>
<div className={`${styles.contentShell} ${styles.contentShellTight}`}>
<PossibilitiesSection
title="Generate an app for that..."
tagline="With OpenClaw OS, any use case becomes a working app, instantly generated and always updated with live data."
cards={[
{
titlePrefix: "An app to",
title: "track company sales.",
image: "/business-health.png",
},
{
titlePrefix: "An app to",
title: "monitor sprint progress.",
image: "/engineering-board.png",
},
{
titlePrefix: "An app to",
title: "observe social media.",
image: "/marketing-dashboard.png",
},
{
titlePrefix: "An app to",
title: "track stock market.",
image: "/stocks-tracker.png",
},
]}
/>
<FeaturesSection features={OPENCLAW_FEATURES} showCta={false} />
<StuckInChatSection installCommand={INSTALL_COMMAND} />
</div>
<GradientDivider direction="up" />
</div>
<Footer />
</div>
);
}
61 changes: 47 additions & 14 deletions docs/app/(home)/components/Button/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { useEffect, useRef, useState, type ButtonHTMLAttributes, type ReactNode
import styles from "./Button.module.css";

type ButtonType = ButtonHTMLAttributes<HTMLButtonElement>["type"];
const COPY_FEEDBACK_MS = 3000;
const COPY_FEEDBACK_MS = 1800;

function CopyIcon({ color = "white" }: { color?: string }) {
return <Copy className={styles.copyIcon} color={color} strokeWidth={1.75} />;
Expand Down Expand Up @@ -49,6 +49,37 @@ function CopyStatusIcon({
);
}

async function copyText(text: string): Promise<boolean> {
if (typeof navigator !== "undefined" && navigator.clipboard?.writeText) {
try {
await navigator.clipboard.writeText(text);
return true;
} catch {
// fall through to execCommand fallback
}
}

if (typeof document === "undefined") return false;

try {
const textarea = document.createElement("textarea");
textarea.value = text;
textarea.setAttribute("readonly", "");
textarea.style.position = "fixed";
textarea.style.top = "0";
textarea.style.left = "0";
textarea.style.opacity = "0";
textarea.style.pointerEvents = "none";
document.body.appendChild(textarea);
textarea.select();
const ok = document.execCommand("copy");
document.body.removeChild(textarea);
return ok;
} catch {
return false;
}
}

interface ClipboardCommandButtonProps {
command: string;
children: ReactNode;
Expand All @@ -58,6 +89,7 @@ interface ClipboardCommandButtonProps {
iconPosition?: "start" | "end";
copyIconColor?: string;
type?: ButtonType;
onCopyChange?: (copied: boolean) => void;
}

export function ClipboardCommandButton({
Expand All @@ -69,6 +101,7 @@ export function ClipboardCommandButton({
iconPosition = "end",
copyIconColor = "white",
type = "button",
onCopyChange,
}: ClipboardCommandButtonProps) {
const [copied, setCopied] = useState(false);
const resetTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
Expand All @@ -82,20 +115,20 @@ export function ClipboardCommandButton({
}, []);

const handleClick = async () => {
if (copied) return;

try {
await navigator.clipboard.writeText(command);
setCopied(true);
if (resetTimeoutRef.current) {
clearTimeout(resetTimeoutRef.current);
}
resetTimeoutRef.current = setTimeout(() => {
setCopied(false);
}, COPY_FEEDBACK_MS);
} catch {
setCopied(false);
const ok = await copyText(command);
if (!ok) {
onCopyChange?.(false);
return;
}
setCopied(true);
onCopyChange?.(true);
if (resetTimeoutRef.current) {
clearTimeout(resetTimeoutRef.current);
}
resetTimeoutRef.current = setTimeout(() => {
setCopied(false);
onCopyChange?.(false);
}, COPY_FEEDBACK_MS);
};

const icon = (
Expand Down
3 changes: 3 additions & 0 deletions docs/app/(home)/components/FeatureList/FeatureList.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
}

.featureIconSvg {
display: inline-flex;
align-items: center;
justify-content: center;
height: 18px;
width: 18px;
color: var(--openui-text-neutral-primary);
Expand Down
38 changes: 10 additions & 28 deletions docs/app/(home)/components/FeatureList/FeatureList.tsx
Original file line number Diff line number Diff line change
@@ -1,42 +1,32 @@
"use client";

import type { ReactNode } from "react";
import styles from "./FeatureList.module.css";

export interface FeatureListItem {
title: string;
description: string;
iconPath: string;
icon: ReactNode;
}

interface FeatureListProps {
items: FeatureListItem[];
}

function FeatureIcon({ path, index }: { path: string; index: number }) {
const clipId = `clip_feat_${index}`;

function FeatureIcon({ icon }: { icon: ReactNode }) {
return (
<div className={styles.featureIcon}>
<svg className={styles.featureIconSvg} fill="none" viewBox="0 0 18 18">
<g clipPath={`url(#${clipId})`}>
<path d={path} fill="currentColor" />
</g>
<defs>
<clipPath id={clipId}>
<rect fill="white" height="18" width="18" />
</clipPath>
</defs>
</svg>
<span className={styles.featureIconSvg}>{icon}</span>
</div>
);
}

function DesktopFeatureRow({ item, index }: { item: FeatureListItem; index: number }) {
function DesktopFeatureRow({ item }: { item: FeatureListItem }) {
return (
<div className={styles.desktopRow}>
<div className={styles.desktopRowLead}>
<div>
<FeatureIcon path={item.iconPath} index={index} />
<FeatureIcon icon={item.icon} />
</div>
<span className={styles.desktopTitle}>{item.title}</span>
</div>
Expand All @@ -45,23 +35,15 @@ function DesktopFeatureRow({ item, index }: { item: FeatureListItem; index: numb
);
}

function MobileFeatureRow({
item,
index,
iconIndexOffset,
}: {
item: FeatureListItem;
index: number;
iconIndexOffset: number;
}) {
function MobileFeatureRow({ item }: { item: FeatureListItem }) {
return (
<div className={styles.mobileRow}>
<div className={styles.mobileCopy}>
<span className={styles.mobileTitle}>{item.title}</span>
<span className={styles.mobileDescription}>{item.description}</span>
</div>
<div>
<FeatureIcon path={item.iconPath} index={index + iconIndexOffset} />
<FeatureIcon icon={item.icon} />
</div>
</div>
);
Expand All @@ -79,7 +61,7 @@ export function FeatureList({ items }: FeatureListProps) {
<div className={styles.desktopList}>
{items.map((item, index) => (
<div key={item.title}>
<DesktopFeatureRow item={item} index={index} />
<DesktopFeatureRow item={item} />
{index < lastItemIndex && <Divider className={styles.desktopDivider} />}
</div>
))}
Expand All @@ -88,7 +70,7 @@ export function FeatureList({ items }: FeatureListProps) {
<div className={styles.mobileList}>
{items.map((item, index) => (
<div key={item.title}>
<MobileFeatureRow item={item} index={index} iconIndexOffset={items.length} />
<MobileFeatureRow item={item} />
{index < lastItemIndex && <Divider className={styles.mobileDivider} />}
</div>
))}
Expand Down
9 changes: 4 additions & 5 deletions docs/app/(home)/components/TweetWall/TweetWall.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,8 @@ export function TweetWall() {
const columns = splitIntoColumns(HOME_TWEETS, columnCount);

useEffect(() => {
if (window.twttr?.widgets?.createTweet) {
setScriptReady(true);
}
if (!window.twttr?.widgets?.createTweet) return;
queueMicrotask(() => setScriptReady(true));
}, []);

useEffect(() => {
Expand All @@ -91,11 +90,11 @@ export function TweetWall() {
);

if (!shouldRehydrate) {
setIsWallReady(true);
queueMicrotask(() => setIsWallReady(true));
return;
}

setIsWallReady(false);
queueMicrotask(() => setIsWallReady(false));

async function hydrateEmbeds() {
await Promise.allSettled(
Expand Down
8 changes: 8 additions & 0 deletions docs/app/(home)/page.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,18 @@
background: var(--openui-background);
}

.contentShellTight {
gap: 4rem;
}

@media (min-width: 768px) {
.contentShell {
gap: 15rem;
}

.contentShellTight {
gap: 6rem;
}
}

@media (min-width: 1024px) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
.section {
width: 100%;
padding-inline: 1.25rem;
padding: 2rem 1.25rem;
}

.container {
Expand Down Expand Up @@ -57,7 +57,7 @@

@media (min-width: 1024px) {
.section {
padding-inline: 2rem;
padding: 3rem 2rem;
}

.ctaWrap {
Expand Down
Loading