From 3fc1e2b76bbb31301609d559315599fe5826ec21 Mon Sep 17 00:00:00 2001 From: ZingerLittleBee <6970999@gmail.com> Date: Fri, 15 May 2026 19:38:17 +0800 Subject: [PATCH 01/14] docs: add landing page design spec Adds spec for replacing the docs home placeholder with a Next.js-style dark-only marketing landing page featuring hero, trust strip, three pillars, an 8-cell bento grid, how-it-works flow, and final CTA. Uses pure CSS keyframes + inline SVG animations, EN/CN i18n, and the existing Fumadocs HomeLayout. --- .../2026-05-15-docs-landing-page-design.md | 228 ++++++++++++++++++ 1 file changed, 228 insertions(+) create mode 100644 docs/superpowers/specs/2026-05-15-docs-landing-page-design.md diff --git a/docs/superpowers/specs/2026-05-15-docs-landing-page-design.md b/docs/superpowers/specs/2026-05-15-docs-landing-page-design.md new file mode 100644 index 00000000..c20f9349 --- /dev/null +++ b/docs/superpowers/specs/2026-05-15-docs-landing-page-design.md @@ -0,0 +1,228 @@ +# ServerBee Docs Landing Page Design + +**Date:** 2026-05-15 +**Status:** Draft +**Owner:** zingerbee +**Scope:** Replace the placeholder home page at `apps/docs/src/routes/$lang/index.tsx` with a Next.js-style marketing landing page that lives inside the existing Fumadocs site. + +--- + +## 1. Goals + +1. Present ServerBee to a first-time visitor in under one screen: what it is, why it's lightweight, how to install it. +2. Showcase the breadth of ServerBee's feature surface (monitoring, terminal, file manager, Docker, ping, alerts, themes, auto-upgrade) with **HTML/CSS animated demos**, not screenshots. +3. Match the visual language of modern Rust/JS project landing pages (Next.js, Bun, Vercel) — large gradient headlines, dark background, Bento grid, restrained motion. +4. Ship bilingual (`en`, `cn`) under the existing `$lang` segment with zero hard-coded copy. +5. Be **dark-mode only** on the landing route, regardless of the user's Fumadocs theme preference, without breaking the rest of the docs site. + +## 2. Non-Goals + +- No theme switcher on the landing page (always dark). +- No real-time data fetched from the live API — every animation is self-contained. +- No new runtime dependencies (no framer-motion, no lottie). Pure CSS keyframes + minimal inline SVG. +- No redesign of the `/$lang/docs/...` reader experience. +- No analytics integration in this iteration. + +## 3. Information Architecture + +The page is composed of 7 stacked sections inside the Fumadocs `HomeLayout` (which keeps the existing top nav + language switcher + GitHub link). Section anatomy from top to bottom: + +| # | Section | Purpose | Key visual element | +|---|---------|---------|--------------------| +| 1 | **Hero** | Headline + value prop + dual CTA + install command | Mini live dashboard animation on the right | +| 2 | **Trust strip** | 3 quick stats / labels reinforcing "small + fast + zero deps" | Static iconography, hover lift only | +| 3 | **Three pillars** | Three large feature cards covering the core narrative | One animation per card | +| 4 | **Bento grid (8 cells)** | Full feature surface, varying tile sizes | One micro-animation per tile | +| 5 | **How it works** | 3 numbered steps with flowing arrows | Animated light-band moving across arrows | +| 6 | **Final CTA** | Big "Read the Docs" / "Star on GitHub" + install command | Hex-glow background | +| 7 | **Footer** | Provided by Fumadocs `HomeLayout` | — | + +### 3.1 Three pillars (Section 3) + +1. **Lightweight Rust probe** — animation: a "binary file" tile slides in, expands, and a green status dot lights up. +2. **Real-time over WebSocket** — animation: particles travel both ways between a Server icon and an Agent icon, looping every ~3s. +3. **One agent, full control** — animation: terminal / file / docker icons orbit a central node, slowly rotating. + +### 3.2 Bento grid (Section 4 — 8 cells) + +Layout (12-col grid, 4 rows on desktop; collapses to 1-col on `< md` even though we are dark-only, the page must still be responsive): + +``` +┌──────────────────────────┬──────────────┬──────────────┐ +│ Network quality (2x2) │ Themes 1x1 │ Alerts 1x1 │ +│ ├──────────────┼──────────────┤ +│ │ Service monitors 1x2 │ +├──────────────────────────┼──────────────┴──────────────┤ +│ Web Terminal (2x2) │ File Manager 1x2 │ +│ ├──────────────┬──────────────┤ +│ │ Docker 1x1 │ Auto-upgrade │ +└──────────────────────────┴──────────────┴──────────────┘ +``` + +Per-cell animations: + +- **Network quality** — small line chart whose newest point ticks every 1s, plus a column of latency dots that pulse from green → amber when a "loss" event scripts in. +- **Web Terminal** — typewriter prints `serverbee agent --version` → multi-line output, then clears and loops. +- **File Manager** — file tree expanding one folder, an upload progress bar fills, resets. +- **Docker** — three stacked container cards; status dots cycle running → restart → running. +- **Themes** — OKLCH color ring rotates slowly; the swatch under "primary" changes through 4 presets. +- **Alerts** — bell icon shakes briefly every ~5s; chip badges for Webhook / Telegram / Bark / Email / APNs fade in sequentially. +- **Service monitors** — 5 dots labeled SSL/DNS/HTTP/TCP/WHOIS, each blinks green at staggered intervals. +- **Auto-upgrade** — circular arrow rotates; version label morphs `v0.2.9` → `v0.3.0` on each full revolution. + +### 3.3 Hero animation (Section 1) + +A self-contained "mini server card" composed of: + +- Two SVG donut rings (CPU, Memory) whose `stroke-dasharray` animates with `@keyframes` between 30% and 75% on a 4s loop. +- A network sparkline drawn with an inline `` whose `d` attribute is fixed; we translate a clip-path mask leftward to create the illusion of scrolling. +- A status dot that pulses via `box-shadow` keyframes. +- Background: layered radial gradients + a sparse honeycomb hex pattern (inline SVG, `position: absolute`, drifting upward at 0.5 px/frame via `transform: translateY` keyframes over 30s). + +## 4. Tech Choices + +| Concern | Choice | Reason | +|---------|--------|--------| +| Framework | Existing TanStack Start + Fumadocs `HomeLayout` | The page must remain part of the docs site so the nav and language switch are free. | +| Styling | Tailwind v4 (already imported) + a single `landing.css` for keyframes | Keeps animation primitives in one place, no JS overhead. | +| Animations | Pure CSS keyframes, inline SVG, `transform`/`opacity` only | Zero new deps; GPU-friendly; SSR-safe. | +| Icons | `lucide-react` (already a dep) | Consistent with the rest of docs. | +| Dark mode | Force `class="dark"` and `style="color-scheme: dark"` on the landing root | Fumadocs respects `.dark`; this scope is local to the page. | +| i18n | Local `translations.ts` keyed by `lang` param, no new i18n runtime | Matches the existing `$lang/index.tsx` pattern. | +| Copy-to-clipboard | `navigator.clipboard.writeText` behind a `'use client'`-equivalent island | TanStack Start hydrates the page; a small button component handles clicks. | + +### 4.1 Install command UX + +Hero and final CTA both show: + +``` +curl -fsSL https://serverbee.app/install.sh | sh +``` + +(Placeholder URL — to be replaced when the actual install script is published. Until then the command must remain visually present but the **click-to-copy** must copy whatever real command is documented in `quick-start.mdx`. The exact command will be sourced from `quick-start.mdx` during implementation; if not yet available there, fall back to `cargo install serverbee-server` as the placeholder that is also valid today.) + +A `` component shows the command in a monospace pill with a copy icon that swaps to a check mark for 1.5s after click. + +## 5. File / Component Layout + +``` +apps/docs/src/ + routes/$lang/index.tsx # rewrite: render + components/landing/ + index.tsx # composes all sections + translations.ts # { en: {...}, cn: {...} } strings + sections/ + hero.tsx + trust-strip.tsx + pillars.tsx + bento.tsx + how-it-works.tsx + final-cta.tsx + primitives/ + gradient-heading.tsx # reused gradient text + code-copy.tsx # copy-to-clipboard pill + section.tsx # outer wrapper with consistent padding + animations/ + mini-dashboard.tsx # Hero right-side + hex-background.tsx # repeated decorative bg + data-stream.tsx # Server↔Agent particles (pillar 2) + orbit-icons.tsx # pillar 3 + install-binary.tsx # pillar 1 + ping-chart.tsx # bento: network + terminal-demo.tsx # bento: terminal + file-tree.tsx # bento: file manager + docker-stack.tsx # bento: docker + color-ring.tsx # bento: themes + alert-bell.tsx # bento: alerts + monitor-dots.tsx # bento: service monitors + upgrade-loop.tsx # bento: auto-upgrade + light-band.tsx # how-it-works arrow flow + styles/landing.css # @keyframes + utility classes used by animations +``` + +`landing.css` is imported from `app.css` so it ships with every page but only the landing route uses the classes. Total CSS size budget: **≤ 8 KB min+gz** (see §12.4). + +## 6. i18n Strategy + +`translations.ts` exports a flat object per language, keyed by short dot-paths: + +```ts +export const t = { + en: { + hero: { eyebrow: 'Open source · MIT', headline: 'Self-hosted VPS monitoring, in a single binary.', sub: '...', primaryCta: 'Quick start', secondaryCta: 'View on GitHub' }, + trust: { binary: 'Single binary · ~10MB', realtime: 'Sub-second updates', deps: 'Zero external deps' }, + pillars: { /* ... */ }, + bento: { network: { title: 'Network quality', body: '...' }, /* ... */ }, + how: { step1: '...', step2: '...', step3: '...' }, + finalCta: { /* ... */ } + }, + cn: { /* mirror */ } +} as const +``` + +Components import `t` and read via `t[lang as 'en' | 'cn']`, with `en` as fallback. No new runtime, no extra deps. + +Translation guidelines: +- Chinese copy is concise and uses the marketing register already present in `cn/index.mdx`. +- Technical nouns (`WebSocket`, `Docker`, `SSL`, `WHOIS`) stay in English in both languages. +- Avoid colon-prefixed dynamic substitution (no `i18next`-style interpolation needed). + +## 7. Dark-mode Enforcement + +The landing component renders: + +```tsx +
+ {/* sections */} +
+``` + +The `.serverbee-landing` selector scopes any landing-specific CSS resets so we never bleed into the Fumadocs docs reader. Fumadocs' theme switcher continues to work everywhere else; on this page it remains visible in the nav but flipping it has no visual effect (we accept this minor inconsistency rather than mutating the global toggle). + +## 8. Responsiveness + +Even though we are dark-only, layout must remain usable on mobile: + +- Hero: stacks (text above, animation below). Mini dashboard scales to 100% width with a max-height clamp. +- Three pillars: 3-col → 1-col at `< md`. +- Bento grid: 12-col → 6-col at `md` → 1-col at `< sm`. Large tiles stay first. +- All animations respect `prefers-reduced-motion: reduce` by collapsing to a static end-state. + +## 9. Accessibility + +- All decorative SVGs use `aria-hidden="true"`. +- Each animated demo is wrapped in a region with a textual `aria-label` describing what it shows ("Animated demo of the ServerBee web terminal"). +- Color contrast: all body text meets WCAG AA against the dark background; primary gradient text has a solid fallback color via `-webkit-text-fill-color: transparent` + `color:` for older browsers. +- Focus rings: every CTA and copy button has a visible focus ring (`focus-visible:ring-2 ring-amber-400`). +- `prefers-reduced-motion: reduce` pauses every keyframe animation (`animation-play-state: paused`) and shows a static representative frame. + +## 10. Performance Budget + +- No new npm dependencies. +- Inline SVGs total `≤ 15 KB` uncompressed. +- Landing route LCP target: **< 1.5s** on a fast 3G simulation (the page is fully SSR-rendered; animations begin after hydration). +- All animations run on `transform` / `opacity` only — no layout thrashing. + +## 11. Out of Scope (Explicit) + +- A `/install.sh` script. The install command is shown verbatim; the actual script publication is a separate workstream. +- A blog or changelog feed on the landing page. +- A "Featured users" / logos section. We have none to publish. +- Light-mode design. We may revisit later; for now `prefers-color-scheme: light` is intentionally ignored on this route. + +## 12. Risks & Open Questions + +1. **`HomeLayout` chrome height** — Fumadocs' nav reserves vertical space. We will measure during implementation and adjust hero padding accordingly. +2. **Install command source of truth** — the exact recommended install command lives in `quick-start.mdx`. During implementation we will lift it from there to keep one canonical source. If `quick-start.mdx` shows multiple options, we use the first/most-common one. +3. **`prefers-reduced-motion` coverage** — we must verify every animation in this spec has a sensible reduced-motion fallback before merging. +4. **CSS bundle growth** — `landing.css` is shared with the docs reader; we will use cascade-layer scoping (`@layer landing`) and `.serverbee-landing` scoping so docs pages don't pay for unused keyframes at runtime (the bytes still ship, but they don't execute). Total added bytes target: **≤ 8 KB min+gz**. + +## 13. Acceptance Criteria + +- Navigating to `/` redirects to `/en` (existing behavior preserved). +- `/en` and `/cn` render the new landing page with every section, animation, and translated string. +- Fumadocs nav remains functional (lang switch, GitHub link, search trigger if present). +- `bun run typecheck` and `bun x ultracite check` pass. +- The page is fully usable with JS disabled (text + static end-state of every animation visible). +- Lighthouse mobile score ≥ 90 for Performance and Accessibility on the landing route. +- `prefers-reduced-motion: reduce` users see no animation but no broken layout. From 0c1a8f7d8c3f4c92e0bbe79638cd26ac23b53d8d Mon Sep 17 00:00:00 2001 From: ZingerLittleBee <6970999@gmail.com> Date: Fri, 15 May 2026 19:40:09 +0800 Subject: [PATCH 02/14] docs: add landing page implementation plan --- .../plans/2026-05-15-docs-landing-page.md | 1568 +++++++++++++++++ 1 file changed, 1568 insertions(+) create mode 100644 docs/superpowers/plans/2026-05-15-docs-landing-page.md diff --git a/docs/superpowers/plans/2026-05-15-docs-landing-page.md b/docs/superpowers/plans/2026-05-15-docs-landing-page.md new file mode 100644 index 00000000..3d09ba59 --- /dev/null +++ b/docs/superpowers/plans/2026-05-15-docs-landing-page.md @@ -0,0 +1,1568 @@ +# Docs Landing Page Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Replace the placeholder `apps/docs/src/routes/$lang/index.tsx` with a Next.js-style, dark-only, bilingual marketing landing page that showcases ServerBee's features via pure CSS / inline SVG animations — no new dependencies. + +**Architecture:** Single page composed of focused section components under `apps/docs/src/components/landing/`. All animations are HTML/CSS keyframes + inline SVG. A scoped `.serverbee-landing` wrapper forces dark mode and isolates landing CSS from the docs reader. i18n strings live in a single `translations.ts` keyed by the existing `$lang` route param. + +**Tech Stack:** TanStack Start + Fumadocs `HomeLayout` (already in place), React 19, Tailwind v4, `lucide-react` (already a dep), plain CSS keyframes. + +**Spec:** `docs/superpowers/specs/2026-05-15-docs-landing-page-design.md` + +**Testing approach:** UI work for a marketing page doesn't lend itself to unit tests. The verification loop in this plan is: +1. `bun run typecheck` after every code change (catches API mismatches). +2. `bun x ultracite check apps/docs` for lint. +3. Manual browser verification via `bun --filter @serverbee/docs dev` on `http://localhost:4000` at the end of each milestone. +4. A final `bun run build` (production build) to make sure SSR works. + +Commits land at the end of each task. The branch is already isolated (`toronto`). + +--- + +## File Structure + +Files this plan creates: + +``` +apps/docs/src/ + routes/$lang/index.tsx # REWRITE (existed) + styles/landing.css # NEW — keyframes, scoped utilities + styles/app.css # MODIFY — import landing.css + components/landing/ + index.tsx # NEW — composes all sections + translations.ts # NEW — en/cn strings + install command constant + primitives/ + section.tsx # NEW + gradient-heading.tsx # NEW + code-copy.tsx # NEW + hex-background.tsx # NEW + sections/ + hero.tsx # NEW + trust-strip.tsx # NEW + pillars.tsx # NEW + bento.tsx # NEW + how-it-works.tsx # NEW + final-cta.tsx # NEW + animations/ + mini-dashboard.tsx # NEW + install-binary.tsx # NEW + data-stream.tsx # NEW + orbit-icons.tsx # NEW + ping-chart.tsx # NEW + terminal-demo.tsx # NEW + file-tree.tsx # NEW + docker-stack.tsx # NEW + color-ring.tsx # NEW + alert-bell.tsx # NEW + monitor-dots.tsx # NEW + upgrade-loop.tsx # NEW + light-band.tsx # NEW +``` + +--- + +## Task 1: Foundation — translations, scoped CSS, and the landing entry point + +**Files:** +- Create: `apps/docs/src/styles/landing.css` +- Modify: `apps/docs/src/styles/app.css` +- Create: `apps/docs/src/components/landing/translations.ts` +- Create: `apps/docs/src/components/landing/index.tsx` +- Modify: `apps/docs/src/routes/$lang/index.tsx` + +- [ ] **Step 1.1 — Create `apps/docs/src/styles/landing.css`** + +```css +/* Landing-only keyframes and scoped utilities. + All rules nest under .serverbee-landing so they never affect the docs reader. */ +@layer landing { + .serverbee-landing { + --landing-bg: oklch(0.16 0.01 260); + --landing-bg-elevated: oklch(0.20 0.012 260); + --landing-fg: oklch(0.96 0.005 260); + --landing-fg-muted: oklch(0.72 0.01 260); + --landing-border: oklch(0.28 0.015 260); + --landing-amber: #ffb300; + --landing-amber-soft: #ffd166; + --landing-cyan: #4cc9f0; + background: var(--landing-bg); + color: var(--landing-fg); + } + + .serverbee-landing .gradient-text { + background: linear-gradient(120deg, #fff 0%, #ffd166 40%, #ffb300 70%, #ff8a3d 100%); + -webkit-background-clip: text; + background-clip: text; + -webkit-text-fill-color: transparent; + color: #ffd166; + } + + .serverbee-landing .hex-bg { + background-image: + radial-gradient(circle at 20% 10%, rgba(255, 179, 0, 0.10), transparent 40%), + radial-gradient(circle at 80% 30%, rgba(76, 201, 240, 0.08), transparent 40%); + } + + @keyframes landing-pulse { + 0%, 100% { box-shadow: 0 0 0 0 rgba(34, 197, 94, 0.55); } + 70% { box-shadow: 0 0 0 10px rgba(34, 197, 94, 0); } + } + .serverbee-landing .pulse-dot { + animation: landing-pulse 2s ease-out infinite; + } + + @keyframes landing-ring { + 0% { stroke-dashoffset: 220; } + 50% { stroke-dashoffset: 70; } + 100% { stroke-dashoffset: 220; } + } + .serverbee-landing .ring-anim { animation: landing-ring 4s ease-in-out infinite; } + + @keyframes landing-spark { + 0% { transform: translateX(0); } + 100% { transform: translateX(-50%); } + } + .serverbee-landing .spark-scroll { animation: landing-spark 6s linear infinite; } + + @keyframes landing-hex-drift { + 0% { transform: translateY(0); } + 100% { transform: translateY(-40px); } + } + .serverbee-landing .hex-drift { animation: landing-hex-drift 30s linear infinite; } + + @keyframes landing-orbit { + from { transform: rotate(0deg); } + to { transform: rotate(360deg); } + } + .serverbee-landing .orbit-anim { animation: landing-orbit 14s linear infinite; } + .serverbee-landing .orbit-counter { animation: landing-orbit 14s linear infinite reverse; } + + @keyframes landing-stream { + 0% { transform: translateX(-100%); opacity: 0; } + 10% { opacity: 1; } + 90% { opacity: 1; } + 100% { transform: translateX(100%); opacity: 0; } + } + .serverbee-landing .stream-particle { animation: landing-stream 3s linear infinite; } + + @keyframes landing-blink { + 0%, 60%, 100% { opacity: 1; } + 30% { opacity: 0; } + } + .serverbee-landing .blink { animation: landing-blink 1s steps(1) infinite; } + + @keyframes landing-shake { + 0%, 92%, 100% { transform: rotate(0); } + 94% { transform: rotate(-14deg); } + 96% { transform: rotate(12deg); } + 98% { transform: rotate(-6deg); } + } + .serverbee-landing .bell-shake { animation: landing-shake 5s ease-in-out infinite; } + + @keyframes landing-uprotate { + from { transform: rotate(0); } + to { transform: rotate(360deg); } + } + .serverbee-landing .upgrade-spin { animation: landing-uprotate 6s linear infinite; } + + @keyframes landing-band { + 0% { transform: translateX(-110%); } + 100% { transform: translateX(110%); } + } + .serverbee-landing .light-band { animation: landing-band 3.5s ease-in-out infinite; } + + @keyframes landing-fade-cycle { + 0%, 25% { opacity: 1; } + 35%, 90% { opacity: 0.25; } + 100% { opacity: 1; } + } + .serverbee-landing .fade-cycle { animation: landing-fade-cycle 4s ease-in-out infinite; } + + @keyframes landing-typewriter { + 0%, 5% { width: 0; } + 35%, 60% { width: 100%; } + 95%, 100% { width: 0; } + } + .serverbee-landing .typewriter { + display: inline-block; + overflow: hidden; + white-space: nowrap; + animation: landing-typewriter 8s steps(40, end) infinite; + } + + @media (prefers-reduced-motion: reduce) { + .serverbee-landing *, + .serverbee-landing *::before, + .serverbee-landing *::after { + animation-duration: 0.001ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.001ms !important; + } + } +} +``` + +- [ ] **Step 1.2 — Wire the new CSS into `apps/docs/src/styles/app.css`** + +Replace the entire file with: + +```css +@import "tailwindcss"; +@import "fumadocs-ui/css/neutral.css"; +@import "fumadocs-ui/css/preset.css"; +@import "./landing.css"; +``` + +- [ ] **Step 1.3 — Create `apps/docs/src/components/landing/translations.ts`** + +```ts +export const INSTALL_COMMAND = + 'curl -fsSL https://raw.githubusercontent.com/ZingerLittleBee/ServerBee/main/deploy/install.sh | sudo bash -s -- server' + +export type LandingLang = 'en' | 'cn' + +export const translations = { + en: { + hero: { + eyebrow: 'Open source · MIT · Built with Rust', + headline1: 'Self-hosted VPS monitoring,', + headline2: 'in a single binary.', + sub: 'ServerBee is a lightweight probe that streams CPU, memory, disk, network, and Docker metrics to a Rust dashboard in real time — no agents to babysit, no external database, no bloat.', + primaryCta: 'Quick start', + secondaryCta: 'View on GitHub', + installLabel: 'Install on Linux' + }, + trust: { + binary: 'Single binary · ~10 MB', + realtime: 'Sub-second WebSocket updates', + deps: 'Zero external dependencies' + }, + pillars: { + one: { + title: 'Lightweight Rust probe', + body: 'A statically linked binary you drop on any Linux host. No JVM, no Python, no daemons — just a tiny process that idles near zero CPU.' + }, + two: { + title: 'Realtime over WebSocket', + body: 'Agents stream metrics and events to the server with sub-second latency. The browser dashboard subscribes to the same fan-out, so what you see is what is actually happening.' + }, + three: { + title: 'One agent, full control', + body: 'Terminal sessions, file manager, Docker operations, and remote command execution all run through the same encrypted channel — gated by per-server capabilities.' + } + }, + bento: { + network: { + title: 'Network quality monitoring', + body: 'ICMP / TCP / HTTP probes, traceroute, packet loss and latency charts, CSV export, and preset targets — all from your agents.' + }, + terminal: { + title: 'Browser web terminal', + body: 'Full PTY sessions over WebSocket. Multi-tab, copy-paste friendly, and audit-logged.' + }, + file: { + title: 'File manager', + body: 'Browse, read, edit, upload and download files through path-sandboxed agents.' + }, + docker: { + title: 'Docker management', + body: 'Containers, stats, events, logs, networks, volumes — when the capability is enabled.' + }, + themes: { + title: 'Custom dashboards & themes', + body: 'Compose dashboards from widgets. Bring your own OKLCH palette, logo, favicon, and footer.' + }, + alerts: { + title: 'Alerts & notifications', + body: 'Thresholds, debounce, maintenance windows. Delivered through Webhook, Telegram, Bark, Email and APNs.' + }, + monitors: { + title: 'Service monitors', + body: 'SSL, DNS, HTTP keyword, TCP and WHOIS checks with history and notifications.' + }, + upgrade: { + title: 'Automatic upgrades', + body: 'Server and agents update themselves. One CLI command, zero downtime restarts.' + } + }, + how: { + title: 'Three commands to a live dashboard', + step1: { title: 'Install the server', body: 'Run the install script on one host. Systemd takes over from there.' }, + step2: { title: 'Bootstrap an agent', body: 'Drop the agent binary on every VPS you want to monitor. Pair it once.' }, + step3: { title: 'Open the dashboard', body: 'Sign in, watch metrics stream in, and start composing dashboards.' } + }, + finalCta: { + title: 'Ship a monitor in five minutes.', + sub: 'Open source, MIT licensed, and small enough to forget about.', + readDocs: 'Read the docs', + star: 'Star on GitHub' + } + }, + cn: { + hero: { + eyebrow: '开源 · MIT · Rust 构建', + headline1: '自托管的 VPS 监控,', + headline2: '只需一个二进制。', + sub: 'ServerBee 是一个轻量探针:实时把 CPU、内存、磁盘、网络、Docker 指标推送到 Rust 仪表盘,没有外部依赖、没有冗余服务、不用伺候它。', + primaryCta: '快速开始', + secondaryCta: '在 GitHub 查看', + installLabel: 'Linux 一键安装' + }, + trust: { + binary: '单二进制 · 约 10 MB', + realtime: '亚秒级 WebSocket 更新', + deps: '零外部依赖' + }, + pillars: { + one: { + title: 'Rust 编写的轻量探针', + body: '静态链接的二进制文件,丢到任何 Linux 主机上就能跑。没有 JVM、没有 Python、没有守护脚本,空载几乎不占 CPU。' + }, + two: { + title: 'WebSocket 实时通信', + body: 'Agent 把指标和事件以亚秒级延迟推到 Server。浏览器订阅同一份广播流,看到的就是当下真实发生的事。' + }, + three: { + title: '一个 Agent 全栈掌控', + body: '终端会话、文件管理、Docker 操作、远程命令执行都走同一条加密通道,并由每台服务器的能力位精细授权。' + } + }, + bento: { + network: { + title: '网络质量监控', + body: 'ICMP / TCP / HTTP 探测、traceroute、丢包与延迟图表、CSV 导出、预设目标 —— 全部由你的 Agent 完成。' + }, + terminal: { + title: '浏览器终端', + body: '基于 WebSocket 的完整 PTY 会话,多标签、易复制粘贴、全程审计日志。' + }, + file: { + title: '文件管理', + body: '通过路径沙箱化的 Agent 浏览、读取、编辑、上传和下载文件。' + }, + docker: { + title: 'Docker 管理', + body: '在开启能力位后管理容器、统计、事件、日志、网络与卷。' + }, + themes: { + title: '自定义仪表盘与主题', + body: '从组件拼装仪表盘,自带 OKLCH 调色板、Logo、favicon 和页脚配置。' + }, + alerts: { + title: '告警与通知', + body: '阈值、抖动抑制、维护窗口。通过 Webhook、Telegram、Bark、Email 和 APNs 推送。' + }, + monitors: { + title: '服务监控', + body: 'SSL、DNS、HTTP 关键字、TCP 与 WHOIS 检查,含历史与通知。' + }, + upgrade: { + title: '自动升级', + body: 'Server 和 Agent 自动更新。一条 CLI 命令,重启零停顿。' + } + }, + how: { + title: '三步上线一个实时仪表盘', + step1: { title: '安装 Server', body: '在一台主机上跑安装脚本,剩下交给 systemd。' }, + step2: { title: '部署 Agent', body: '把 Agent 二进制丢到每台要监控的 VPS 上,配对一次即可。' }, + step3: { title: '打开仪表盘', body: '登录,等指标自动流入,然后开始拼装你的仪表盘。' } + }, + finalCta: { + title: '五分钟跑起一个监控。', + sub: '开源、MIT 协议,小到你会忘了它在跑。', + readDocs: '阅读文档', + star: '在 GitHub 上 Star' + } + } +} as const + +export type Translations = typeof translations +export function t(lang: string): Translations['en'] { + return (translations as Record)[lang] ?? translations.en +} +``` + +- [ ] **Step 1.4 — Create `apps/docs/src/components/landing/index.tsx` as a minimal stub** + +```tsx +import type { LandingLang } from './translations' +import { t } from './translations' + +export function LandingPage({ lang }: { lang: LandingLang }) { + const copy = t(lang) + return ( +
+
+

{copy.hero.headline1}

+

{copy.hero.sub}

+
+
+ ) +} +``` + +(The stub is real enough to verify wiring; subsequent tasks fill it out.) + +- [ ] **Step 1.5 — Rewrite `apps/docs/src/routes/$lang/index.tsx`** + +```tsx +import { createFileRoute, useParams } from '@tanstack/react-router' +import { HomeLayout } from 'fumadocs-ui/layouts/home' + +import { LandingPage } from '@/components/landing' +import type { LandingLang } from '@/components/landing/translations' +import { baseOptions } from '@/lib/layout.shared' + +export const Route = createFileRoute('/$lang/')({ + component: Home +}) + +function Home() { + const { lang } = useParams({ from: '/$lang/' }) + const landingLang: LandingLang = lang === 'cn' ? 'cn' : 'en' + + return ( + + + + ) +} +``` + +- [ ] **Step 1.6 — Verify typecheck + lint** + +Run: +```bash +cd apps/docs && bun run types:check && bun x ultracite check src +``` +Expected: both pass. Fix any errors before moving on. + +- [ ] **Step 1.7 — Smoke test in browser** + +Run from repo root: +```bash +bun --filter @serverbee/docs dev +``` +Open `http://localhost:4000/en`. Expected: dark page, gradient headline visible, no console errors. Stop the dev server with `Ctrl+C`. + +- [ ] **Step 1.8 — Commit** + +```bash +git add apps/docs/src/styles/landing.css apps/docs/src/styles/app.css \ + apps/docs/src/components/landing/translations.ts \ + apps/docs/src/components/landing/index.tsx \ + apps/docs/src/routes/\$lang/index.tsx +git commit -m "feat(docs): scaffold landing page with i18n and scoped dark styles" +``` + +--- + +## Task 2: Primitives — section, gradient heading, code-copy, hex background + +**Files:** +- Create: `apps/docs/src/components/landing/primitives/section.tsx` +- Create: `apps/docs/src/components/landing/primitives/gradient-heading.tsx` +- Create: `apps/docs/src/components/landing/primitives/code-copy.tsx` +- Create: `apps/docs/src/components/landing/primitives/hex-background.tsx` + +- [ ] **Step 2.1 — `primitives/section.tsx`** + +```tsx +import type { PropsWithChildren } from 'react' + +import { cn } from '@/lib/cn' + +export function Section({ + id, + className, + children +}: PropsWithChildren<{ id?: string; className?: string }>) { + return ( +
+
{children}
+
+ ) +} +``` + +- [ ] **Step 2.2 — `primitives/gradient-heading.tsx`** + +```tsx +import type { PropsWithChildren } from 'react' + +import { cn } from '@/lib/cn' + +export function GradientHeading({ + as: Tag = 'h2', + className, + children +}: PropsWithChildren<{ as?: 'h1' | 'h2' | 'h3'; className?: string }>) { + return ( + + {children} + + ) +} +``` + +- [ ] **Step 2.3 — `primitives/code-copy.tsx`** + +```tsx +import { Check, Copy } from 'lucide-react' +import { useState } from 'react' + +import { cn } from '@/lib/cn' + +export function CodeCopy({ command, label, className }: { command: string; label?: string; className?: string }) { + const [copied, setCopied] = useState(false) + + const onCopy = async () => { + try { + await navigator.clipboard.writeText(command) + setCopied(true) + setTimeout(() => setCopied(false), 1500) + } catch { + // Clipboard API unavailable (e.g. insecure context). Silently no-op; the + // command is still visible and copy-selectable. + } + } + + return ( +
+ {label ? ( + + {label} + + ) : null} + + $ + {command} + + +
+ ) +} +``` + +- [ ] **Step 2.4 — `primitives/hex-background.tsx`** + +```tsx +import { cn } from '@/lib/cn' + +export function HexBackground({ className }: { className?: string }) { + // A sparse, drifting hexagon pattern rendered inline so we ship no extra request. + return ( +
+
+ + + + + + + + + + + + + + + +
+ ) +} +``` + +- [ ] **Step 2.5 — Verify typecheck** + +```bash +cd apps/docs && bun run types:check +``` +Expected: passes. + +- [ ] **Step 2.6 — Commit** + +```bash +git add apps/docs/src/components/landing/primitives +git commit -m "feat(docs): add landing primitives — section, gradient heading, code-copy, hex bg" +``` + +--- + +## Task 3: Hero section + mini-dashboard animation + +**Files:** +- Create: `apps/docs/src/components/landing/animations/mini-dashboard.tsx` +- Create: `apps/docs/src/components/landing/sections/hero.tsx` +- Modify: `apps/docs/src/components/landing/index.tsx` + +- [ ] **Step 3.1 — `animations/mini-dashboard.tsx`** + +```tsx +// A self-contained "mini server card" that visually mimics ServerBee's real +// server card: two donut rings (CPU/Memory) animated via stroke-dashoffset, +// a sparkline whose clip-path scrolls leftward, and a pulsing status dot. +export function MiniDashboard() { + return ( +
+
+
+ + edge-tokyo-01 +
+ linux/arm64 +
+ +
+ + +
+ +
+
+ Network + ↑ 2.1 MB/s · ↓ 318 KB/s +
+ +
+ +
+ + + +
+
+ ) +} + +function Ring({ label, value, color }: { label: string; value: number; color: string }) { + const dash = 220 + return ( +
+ + + + +
+
{label}
+
{value}%
+
+
+ ) +} + +function Sparkline() { + return ( +
+ + + + + + + + + + +
+ ) +} + +function Stat({ label, value }: { label: string; value: string }) { + return ( +
+
{label}
+
{value}
+
+ ) +} +``` + +- [ ] **Step 3.2 — `sections/hero.tsx`** + +```tsx +import { ArrowRight, Github } from 'lucide-react' + +import { MiniDashboard } from '../animations/mini-dashboard' +import { CodeCopy } from '../primitives/code-copy' +import { GradientHeading } from '../primitives/gradient-heading' +import { HexBackground } from '../primitives/hex-background' +import { Section } from '../primitives/section' +import { INSTALL_COMMAND, type LandingLang, t } from '../translations' + +export function Hero({ lang }: { lang: LandingLang }) { + const copy = t(lang).hero + const docsHref = `/${lang}/docs/quick-start` + return ( +
+ +
+
+ + {copy.eyebrow} + + + {copy.headline1} +
+ {copy.headline2} +
+

{copy.sub}

+ + + +
+ +
+
+ +
+ +
+
+
+ ) +} +``` + +- [ ] **Step 3.3 — Update `components/landing/index.tsx` to mount Hero** + +```tsx +import { Hero } from './sections/hero' +import type { LandingLang } from './translations' + +export function LandingPage({ lang }: { lang: LandingLang }) { + return ( +
+ +
+ ) +} +``` + +- [ ] **Step 3.4 — Typecheck + lint + browser smoke** + +```bash +cd apps/docs && bun run types:check && bun x ultracite check src +``` +Then run the dev server and visit `/en` and `/cn`. Expected: hero renders, install command pill is visible and copy button works, dashboard animations loop, no console errors. + +- [ ] **Step 3.5 — Commit** + +```bash +git add apps/docs/src/components/landing +git commit -m "feat(docs): add landing hero with mini-dashboard animation" +``` + +--- + +## Task 4: Trust strip + three pillars (with install-binary, data-stream, orbit-icons animations) + +**Files:** +- Create: `apps/docs/src/components/landing/sections/trust-strip.tsx` +- Create: `apps/docs/src/components/landing/animations/install-binary.tsx` +- Create: `apps/docs/src/components/landing/animations/data-stream.tsx` +- Create: `apps/docs/src/components/landing/animations/orbit-icons.tsx` +- Create: `apps/docs/src/components/landing/sections/pillars.tsx` +- Modify: `apps/docs/src/components/landing/index.tsx` + +- [ ] **Step 4.1 — `sections/trust-strip.tsx`** + +```tsx +import { Cpu, Gauge, PackageCheck } from 'lucide-react' + +import { Section } from '../primitives/section' +import { type LandingLang, t } from '../translations' + +export function TrustStrip({ lang }: { lang: LandingLang }) { + const copy = t(lang).trust + const items = [ + { Icon: PackageCheck, label: copy.binary }, + { Icon: Gauge, label: copy.realtime }, + { Icon: Cpu, label: copy.deps } + ] + return ( +
+
    + {items.map(({ Icon, label }) => ( +
  • + + {label} +
  • + ))} +
+
+ ) +} +``` + +- [ ] **Step 4.2 — `animations/install-binary.tsx`** + +```tsx +// Visual metaphor: a "binary file" tile slides up, expands, and a status dot +// lights green. Loops every ~6s via staggered CSS animations. +export function InstallBinaryAnim() { + return ( +
+
+
+ serverbee +
+ + + +
+ + systemd · active +
+
+
+ ) +} +``` + +- [ ] **Step 4.3 — `animations/data-stream.tsx`** + +```tsx +// Two endpoints (Server, Agent) with particles travelling both ways. +export function DataStreamAnim() { + return ( +
+ +
+ + + + +
+ +
+ ) +} + +function Endpoint({ label, color }: { label: string; color: string }) { + return ( +
+
+ {label} +
+ ) +} + +function Particle({ delay, colorClass, reverse }: { delay: string; colorClass: string; reverse?: boolean }) { + return ( + + ) +} +``` + +- [ ] **Step 4.4 — `animations/orbit-icons.tsx`** + +```tsx +import { FileCog, Layers, TerminalSquare } from 'lucide-react' + +export function OrbitIconsAnim() { + return ( +
+
+
+
+
+
+ } /> + } /> + } /> +
+
+
+ ) +} + +function OrbitItem({ angle, icon }: { angle: number; icon: React.ReactNode }) { + return ( +
+
+ {icon} +
+
+ ) +} +``` + +(Note: `.orbit-counter` re-applies the same animation in reverse on each icon so the icon itself doesn't appear to spin while the orbit ring rotates.) + +- [ ] **Step 4.5 — `sections/pillars.tsx`** + +```tsx +import { DataStreamAnim } from '../animations/data-stream' +import { InstallBinaryAnim } from '../animations/install-binary' +import { OrbitIconsAnim } from '../animations/orbit-icons' +import { Section } from '../primitives/section' +import { type LandingLang, t } from '../translations' + +export function Pillars({ lang }: { lang: LandingLang }) { + const copy = t(lang).pillars + const cards = [ + { ...copy.one, Anim: InstallBinaryAnim }, + { ...copy.two, Anim: DataStreamAnim }, + { ...copy.three, Anim: OrbitIconsAnim } + ] + return ( +
+
+ {cards.map(({ title, body, Anim }) => ( +
+ +

{title}

+

{body}

+
+ ))} +
+
+ ) +} +``` + +- [ ] **Step 4.6 — Update `components/landing/index.tsx`** + +```tsx +import { Hero } from './sections/hero' +import { Pillars } from './sections/pillars' +import { TrustStrip } from './sections/trust-strip' +import type { LandingLang } from './translations' + +export function LandingPage({ lang }: { lang: LandingLang }) { + return ( +
+ + + +
+ ) +} +``` + +- [ ] **Step 4.7 — Typecheck + lint + browser smoke** + +```bash +cd apps/docs && bun run types:check && bun x ultracite check src +``` +Browser: scroll past hero, verify the 3 cards render and animate. + +- [ ] **Step 4.8 — Commit** + +```bash +git add apps/docs/src/components/landing +git commit -m "feat(docs): add trust strip and three-pillar section with animations" +``` + +--- + +## Task 5: Bento grid scaffolding + 8 animation components + +This task creates all 8 micro-animations and the bento layout. Animations are intentionally small (≤ 40 lines each). + +**Files:** +- Create: `apps/docs/src/components/landing/animations/ping-chart.tsx` +- Create: `apps/docs/src/components/landing/animations/terminal-demo.tsx` +- Create: `apps/docs/src/components/landing/animations/file-tree.tsx` +- Create: `apps/docs/src/components/landing/animations/docker-stack.tsx` +- Create: `apps/docs/src/components/landing/animations/color-ring.tsx` +- Create: `apps/docs/src/components/landing/animations/alert-bell.tsx` +- Create: `apps/docs/src/components/landing/animations/monitor-dots.tsx` +- Create: `apps/docs/src/components/landing/animations/upgrade-loop.tsx` +- Create: `apps/docs/src/components/landing/sections/bento.tsx` +- Modify: `apps/docs/src/components/landing/index.tsx` + +- [ ] **Step 5.1 — `animations/ping-chart.tsx`** + +```tsx +export function PingChartAnim() { + return ( +
+
+ + + +
+
+
+ {Array.from({ length: 18 }).map((_, i) => ( + + ))} +
+
+ ) +} +``` + +- [ ] **Step 5.2 — `animations/terminal-demo.tsx`** + +```tsx +export function TerminalDemoAnim() { + return ( +
+
+ + + + edge-tokyo-01 ~ # +
+
+{`$ `}
+        serverbee agent --version
+        {`
+serverbee-agent 0.3.0 (linux/arm64)
+features: terminal,file,docker,ping,upgrade
+$ `}
+      
+
+ ) +} +``` + +- [ ] **Step 5.3 — `animations/file-tree.tsx`** + +```tsx +import { File, FolderOpen, FolderTree } from 'lucide-react' + +export function FileTreeAnim() { + return ( +
+
+ + + + +
+
+ +
+
+ ) +} + +function Row({ Icon, label, indent }: { Icon: React.ComponentType<{ className?: string }>; label: string; indent?: boolean }) { + return ( +
+ + {label} +
+ ) +} +``` + +- [ ] **Step 5.4 — `animations/docker-stack.tsx`** + +```tsx +import { Box } from 'lucide-react' + +export function DockerStackAnim() { + const containers = [ + { name: 'web', tag: 'caddy:2', delay: '0s' }, + { name: 'api', tag: 'rust:1.84', delay: '0.4s' }, + { name: 'cache', tag: 'redis:7', delay: '0.8s' } + ] + return ( +
+ {containers.map((c) => ( +
+ + {c.name} + {c.tag} + + + running + +
+ ))} +
+ ) +} +``` + +- [ ] **Step 5.5 — `animations/color-ring.tsx`** + +```tsx +export function ColorRingAnim() { + const stops = ['#ffb300', '#4cc9f0', '#22c55e', '#a855f7', '#ef4444', '#ffb300'] + const gradient = stops.map((c, i) => `${c} ${(i / (stops.length - 1)) * 360}deg`).join(', ') + return ( +
+
+
+
+
+
+
+ ) +} +``` + +- [ ] **Step 5.6 — `animations/alert-bell.tsx`** + +```tsx +import { Bell } from 'lucide-react' + +export function AlertBellAnim() { + const channels = ['Webhook', 'Telegram', 'Bark', 'Email', 'APNs'] + return ( +
+ +
+ {channels.map((c, i) => ( + + {c} + + ))} +
+
+ ) +} +``` + +- [ ] **Step 5.7 — `animations/monitor-dots.tsx`** + +```tsx +export function MonitorDotsAnim() { + const probes = ['SSL', 'DNS', 'HTTP', 'TCP', 'WHOIS'] + return ( +
+ {probes.map((p, i) => ( +
+ {p} + +
+ ))} +
+ ) +} +``` + +- [ ] **Step 5.8 — `animations/upgrade-loop.tsx`** + +```tsx +import { RotateCw } from 'lucide-react' + +export function UpgradeLoopAnim() { + return ( +
+ +
+ v0.2.9 + + v0.3.0 +
+
+ ) +} +``` + +- [ ] **Step 5.9 — `sections/bento.tsx`** + +The grid uses Tailwind's 12-col grid; tiles span columns and rows by responsibility. + +```tsx +import type { ComponentType, ReactNode } from 'react' + +import { AlertBellAnim } from '../animations/alert-bell' +import { ColorRingAnim } from '../animations/color-ring' +import { DockerStackAnim } from '../animations/docker-stack' +import { FileTreeAnim } from '../animations/file-tree' +import { MonitorDotsAnim } from '../animations/monitor-dots' +import { PingChartAnim } from '../animations/ping-chart' +import { TerminalDemoAnim } from '../animations/terminal-demo' +import { UpgradeLoopAnim } from '../animations/upgrade-loop' +import { GradientHeading } from '../primitives/gradient-heading' +import { Section } from '../primitives/section' +import { type LandingLang, t } from '../translations' + +type Tile = { + title: string + body: string + Anim: ComponentType + span: string +} + +export function Bento({ lang }: { lang: LandingLang }) { + const copy = t(lang).bento + const tiles: Tile[] = [ + { ...copy.network, Anim: PingChartAnim, span: 'md:col-span-6 md:row-span-2' }, + { ...copy.themes, Anim: ColorRingAnim, span: 'md:col-span-3' }, + { ...copy.alerts, Anim: AlertBellAnim, span: 'md:col-span-3' }, + { ...copy.monitors, Anim: MonitorDotsAnim, span: 'md:col-span-6' }, + { ...copy.terminal, Anim: TerminalDemoAnim, span: 'md:col-span-6 md:row-span-2' }, + { ...copy.file, Anim: FileTreeAnim, span: 'md:col-span-6' }, + { ...copy.docker, Anim: DockerStackAnim, span: 'md:col-span-3' }, + { ...copy.upgrade, Anim: UpgradeLoopAnim, span: 'md:col-span-3' } + ] + + return ( +
+ {title(lang)} +
+ {tiles.map(({ title: heading, body, Anim, span }) => ( + + + + ))} +
+
+ ) +} + +function Card({ title, body, span, children }: { title: string; body: string; span: string; children: ReactNode }) { + return ( +
+
{children}
+
+

{title}

+

{body}

+
+
+ ) +} + +function title(lang: LandingLang): string { + return lang === 'cn' ? '一个探针,覆盖运维的方方面面。' : 'One probe. Every job your VPS needs.' +} +``` + +- [ ] **Step 5.10 — Update `components/landing/index.tsx`** + +```tsx +import { Bento } from './sections/bento' +import { Hero } from './sections/hero' +import { Pillars } from './sections/pillars' +import { TrustStrip } from './sections/trust-strip' +import type { LandingLang } from './translations' + +export function LandingPage({ lang }: { lang: LandingLang }) { + return ( +
+ + + + +
+ ) +} +``` + +- [ ] **Step 5.11 — Typecheck + lint + browser smoke** + +```bash +cd apps/docs && bun run types:check && bun x ultracite check src +``` +Browser: verify all 8 tiles, no overlap on `md` and `lg`, each animation is visible. + +- [ ] **Step 5.12 — Commit** + +```bash +git add apps/docs/src/components/landing +git commit -m "feat(docs): add 8-tile bento grid with feature animations" +``` + +--- + +## Task 6: How-it-works + Final CTA + +**Files:** +- Create: `apps/docs/src/components/landing/animations/light-band.tsx` +- Create: `apps/docs/src/components/landing/sections/how-it-works.tsx` +- Create: `apps/docs/src/components/landing/sections/final-cta.tsx` +- Modify: `apps/docs/src/components/landing/index.tsx` + +- [ ] **Step 6.1 — `animations/light-band.tsx`** + +```tsx +export function LightBandArrow() { + return ( +
+ +
+ ) +} +``` + +- [ ] **Step 6.2 — `sections/how-it-works.tsx`** + +```tsx +import { LightBandArrow } from '../animations/light-band' +import { GradientHeading } from '../primitives/gradient-heading' +import { Section } from '../primitives/section' +import { type LandingLang, t } from '../translations' + +export function HowItWorks({ lang }: { lang: LandingLang }) { + const copy = t(lang).how + const steps = [copy.step1, copy.step2, copy.step3] + return ( +
+ {copy.title} +
+ {steps.map((s, i) => ( +
+
+
{`0${i + 1}`}
+

{s.title}

+

{s.body}

+
+ {i < steps.length - 1 ? : null} +
+ ))} +
+
+ ) +} +``` + +- [ ] **Step 6.3 — `sections/final-cta.tsx`** + +```tsx +import { ArrowRight, Github } from 'lucide-react' + +import { CodeCopy } from '../primitives/code-copy' +import { GradientHeading } from '../primitives/gradient-heading' +import { HexBackground } from '../primitives/hex-background' +import { Section } from '../primitives/section' +import { INSTALL_COMMAND, type LandingLang, t } from '../translations' + +export function FinalCta({ lang }: { lang: LandingLang }) { + const copy = t(lang).finalCta + const docsHref = `/${lang}/docs/quick-start` + return ( +
+ +
+ {copy.title} +

{copy.sub}

+ +
+ +
+
+
+ ) +} +``` + +- [ ] **Step 6.4 — Update `components/landing/index.tsx`** + +```tsx +import { Bento } from './sections/bento' +import { FinalCta } from './sections/final-cta' +import { Hero } from './sections/hero' +import { HowItWorks } from './sections/how-it-works' +import { Pillars } from './sections/pillars' +import { TrustStrip } from './sections/trust-strip' +import type { LandingLang } from './translations' + +export function LandingPage({ lang }: { lang: LandingLang }) { + return ( +
+ + + + + + +
+ ) +} +``` + +- [ ] **Step 6.5 — Typecheck + lint + browser smoke** + +```bash +cd apps/docs && bun run types:check && bun x ultracite check src +``` +Browser: visit `/en` and `/cn`, scroll the whole page, confirm all 6 sections render, animations loop, links work. + +- [ ] **Step 6.6 — Commit** + +```bash +git add apps/docs/src/components/landing +git commit -m "feat(docs): add how-it-works and final CTA sections" +``` + +--- + +## Task 7: Final QA — production build, reduced motion, accessibility + +**Files:** none (verification only, plus any small fixes that surface). + +- [ ] **Step 7.1 — Production build** + +```bash +cd apps/docs && bun run build +``` +Expected: builds successfully, no SSR errors. If `MiniDashboard` or any animation uses something hydration-incompatible (e.g. `Math.random()` in JSX), fix it in this step before continuing. + +- [ ] **Step 7.2 — Reduced-motion verification** + +In macOS System Settings → Accessibility → Display → enable "Reduce motion", then refresh `/en`. Expected: animations freeze on a static end-state and the page remains legible. If a tile collapses to an empty box (e.g. typewriter at `width: 0`), set its base width to `100%` in `landing.css` and revert via `@media (prefers-reduced-motion: no-preference)` only when motion is allowed. Disable Reduce motion when done. + +- [ ] **Step 7.3 — Keyboard / focus pass** + +Tab through the page. Expected: CTAs, copy buttons, and language switcher all show a visible amber focus ring. Fix any missing focus-visible state. + +- [ ] **Step 7.4 — Bilingual pass** + +Visit `/cn`. Expected: every translated string appears in Chinese; English-only technical nouns (Docker, WebSocket, SSL, etc.) remain English; no untranslated `t.xxx` fallback strings. + +- [ ] **Step 7.5 — Final commit (if any fixes landed)** + +```bash +git add -A && git commit -m "fix(docs): polish landing page accessibility and reduced-motion behavior" +``` + +If no fixes were needed, skip this step. + +- [ ] **Step 7.6 — Done** + +Run a final summary check: +```bash +git log --oneline -10 +``` +Expected: 6–7 landing-page commits stacked on top of `d4d30da`. + +--- + +## Self-Review Notes + +- **Spec coverage:** Every section in the spec (§3.1–§3.3, §4–§12) maps to a task above. Hero (Task 3), trust strip + pillars (Task 4), bento grid (Task 5), how-it-works + final CTA (Task 6), QA pass for accessibility/reduced motion/SSR (Task 7). +- **Placeholder scan:** No `TBD`/`TODO`/"implement later" entries. The install command is a real string sourced from `apps/docs/content/docs/en/quick-start.mdx`. +- **Type consistency:** `LandingLang` is defined once (Task 1.3) and reused. `t(lang)` always returns `Translations['en']` so consumers get the same shape regardless of language. All animation components are zero-prop (`() => JSX`) and used as `ComponentType` in `bento.tsx`. +- **Risk noted in spec §12.4 (CSS bundle growth):** `landing.css` is wrapped in `@layer landing` so its specificity stays predictable; everything is scoped under `.serverbee-landing` so docs reader pages don't inherit any of these styles. From 1392c13f918d3bbc7f5170e09fc7af90a90ab107 Mon Sep 17 00:00:00 2001 From: ZingerLittleBee <6970999@gmail.com> Date: Fri, 15 May 2026 19:51:09 +0800 Subject: [PATCH 03/14] feat(docs): scaffold landing page with i18n and scoped dark styles --- apps/docs/src/components/landing/index.tsx | 14 ++ .../src/components/landing/translations.ts | 171 +++++++++++++ apps/docs/src/routes/$lang/index.tsx | 22 +- apps/docs/src/styles/app.css | 1 + apps/docs/src/styles/landing.css | 237 ++++++++++++++++++ 5 files changed, 428 insertions(+), 17 deletions(-) create mode 100644 apps/docs/src/components/landing/index.tsx create mode 100644 apps/docs/src/components/landing/translations.ts create mode 100644 apps/docs/src/styles/landing.css diff --git a/apps/docs/src/components/landing/index.tsx b/apps/docs/src/components/landing/index.tsx new file mode 100644 index 00000000..ce4fa93f --- /dev/null +++ b/apps/docs/src/components/landing/index.tsx @@ -0,0 +1,14 @@ +import type { LandingLang } from './translations' +import { t } from './translations' + +export function LandingPage({ lang }: { lang: LandingLang }) { + const copy = t(lang) + return ( +
+
+

{copy.hero.headline1}

+

{copy.hero.sub}

+
+
+ ) +} diff --git a/apps/docs/src/components/landing/translations.ts b/apps/docs/src/components/landing/translations.ts new file mode 100644 index 00000000..716618dc --- /dev/null +++ b/apps/docs/src/components/landing/translations.ts @@ -0,0 +1,171 @@ +export const INSTALL_COMMAND = + 'curl -fsSL https://raw.githubusercontent.com/ZingerLittleBee/ServerBee/main/deploy/install.sh | sudo bash -s -- server' + +export type LandingLang = 'en' | 'cn' + +export const translations = { + en: { + hero: { + eyebrow: 'Open source · MIT · Built with Rust', + headline1: 'Self-hosted VPS monitoring,', + headline2: 'in a single binary.', + sub: 'ServerBee is a lightweight probe that streams CPU, memory, disk, network, and Docker metrics to a Rust dashboard in real time — no agents to babysit, no external database, no bloat.', + primaryCta: 'Quick start', + secondaryCta: 'View on GitHub', + installLabel: 'Install on Linux' + }, + trust: { + binary: 'Single binary · ~10 MB', + realtime: 'Sub-second WebSocket updates', + deps: 'Zero external dependencies' + }, + pillars: { + one: { + title: 'Lightweight Rust probe', + body: 'A statically linked binary you drop on any Linux host. No JVM, no Python, no daemons — just a tiny process that idles near zero CPU.' + }, + two: { + title: 'Realtime over WebSocket', + body: 'Agents stream metrics and events to the server with sub-second latency. The browser dashboard subscribes to the same fan-out, so what you see is what is actually happening.' + }, + three: { + title: 'One agent, full control', + body: 'Terminal sessions, file manager, Docker operations, and remote command execution all run through the same encrypted channel — gated by per-server capabilities.' + } + }, + bento: { + network: { + title: 'Network quality monitoring', + body: 'ICMP / TCP / HTTP probes, traceroute, packet loss and latency charts, CSV export, and preset targets — all from your agents.' + }, + terminal: { + title: 'Browser web terminal', + body: 'Full PTY sessions over WebSocket. Multi-tab, copy-paste friendly, and audit-logged.' + }, + file: { + title: 'File manager', + body: 'Browse, read, edit, upload and download files through path-sandboxed agents.' + }, + docker: { + title: 'Docker management', + body: 'Containers, stats, events, logs, networks, volumes — when the capability is enabled.' + }, + themes: { + title: 'Custom dashboards & themes', + body: 'Compose dashboards from widgets. Bring your own OKLCH palette, logo, favicon, and footer.' + }, + alerts: { + title: 'Alerts & notifications', + body: 'Thresholds, debounce, maintenance windows. Delivered through Webhook, Telegram, Bark, Email and APNs.' + }, + monitors: { + title: 'Service monitors', + body: 'SSL, DNS, HTTP keyword, TCP and WHOIS checks with history and notifications.' + }, + upgrade: { + title: 'Automatic upgrades', + body: 'Server and agents update themselves. One CLI command, zero downtime restarts.' + } + }, + how: { + title: 'Three commands to a live dashboard', + step1: { + title: 'Install the server', + body: 'Run the install script on one host. Systemd takes over from there.' + }, + step2: { + title: 'Bootstrap an agent', + body: 'Drop the agent binary on every VPS you want to monitor. Pair it once.' + }, + step3: { title: 'Open the dashboard', body: 'Sign in, watch metrics stream in, and start composing dashboards.' } + }, + finalCta: { + title: 'Ship a monitor in five minutes.', + sub: 'Open source, MIT licensed, and small enough to forget about.', + readDocs: 'Read the docs', + star: 'Star on GitHub' + } + }, + cn: { + hero: { + eyebrow: '开源 · MIT · Rust 构建', + headline1: '自托管的 VPS 监控,', + headline2: '只需一个二进制。', + sub: 'ServerBee 是一个轻量探针:实时把 CPU、内存、磁盘、网络、Docker 指标推送到 Rust 仪表盘,没有外部依赖、没有冗余服务、不用伺候它。', + primaryCta: '快速开始', + secondaryCta: '在 GitHub 查看', + installLabel: 'Linux 一键安装' + }, + trust: { + binary: '单二进制 · 约 10 MB', + realtime: '亚秒级 WebSocket 更新', + deps: '零外部依赖' + }, + pillars: { + one: { + title: 'Rust 编写的轻量探针', + body: '静态链接的二进制文件,丢到任何 Linux 主机上就能跑。没有 JVM、没有 Python、没有守护脚本,空载几乎不占 CPU。' + }, + two: { + title: 'WebSocket 实时通信', + body: 'Agent 把指标和事件以亚秒级延迟推到 Server。浏览器订阅同一份广播流,看到的就是当下真实发生的事。' + }, + three: { + title: '一个 Agent 全栈掌控', + body: '终端会话、文件管理、Docker 操作、远程命令执行都走同一条加密通道,并由每台服务器的能力位精细授权。' + } + }, + bento: { + network: { + title: '网络质量监控', + body: 'ICMP / TCP / HTTP 探测、traceroute、丢包与延迟图表、CSV 导出、预设目标 —— 全部由你的 Agent 完成。' + }, + terminal: { + title: '浏览器终端', + body: '基于 WebSocket 的完整 PTY 会话,多标签、易复制粘贴、全程审计日志。' + }, + file: { + title: '文件管理', + body: '通过路径沙箱化的 Agent 浏览、读取、编辑、上传和下载文件。' + }, + docker: { + title: 'Docker 管理', + body: '在开启能力位后管理容器、统计、事件、日志、网络与卷。' + }, + themes: { + title: '自定义仪表盘与主题', + body: '从组件拼装仪表盘,自带 OKLCH 调色板、Logo、favicon 和页脚配置。' + }, + alerts: { + title: '告警与通知', + body: '阈值、抖动抑制、维护窗口。通过 Webhook、Telegram、Bark、Email 和 APNs 推送。' + }, + monitors: { + title: '服务监控', + body: 'SSL、DNS、HTTP 关键字、TCP 与 WHOIS 检查,含历史与通知。' + }, + upgrade: { + title: '自动升级', + body: 'Server 和 Agent 自动更新。一条 CLI 命令,重启零停顿。' + } + }, + how: { + title: '三步上线一个实时仪表盘', + step1: { title: '安装 Server', body: '在一台主机上跑安装脚本,剩下交给 systemd。' }, + step2: { title: '部署 Agent', body: '把 Agent 二进制丢到每台要监控的 VPS 上,配对一次即可。' }, + step3: { title: '打开仪表盘', body: '登录,等指标自动流入,然后开始拼装你的仪表盘。' } + }, + finalCta: { + title: '五分钟跑起一个监控。', + sub: '开源、MIT 协议,小到你会忘了它在跑。', + readDocs: '阅读文档', + star: '在 GitHub 上 Star' + } + } +} as const + +export type Translations = typeof translations +export function t(lang: string): Translations['en'] { + const table = translations as unknown as Record + return table[lang] ?? translations.en +} diff --git a/apps/docs/src/routes/$lang/index.tsx b/apps/docs/src/routes/$lang/index.tsx index b42aa899..9632a7ef 100644 --- a/apps/docs/src/routes/$lang/index.tsx +++ b/apps/docs/src/routes/$lang/index.tsx @@ -1,33 +1,21 @@ -import { createFileRoute, Link, useParams } from '@tanstack/react-router' +import { createFileRoute, useParams } from '@tanstack/react-router' import { HomeLayout } from 'fumadocs-ui/layouts/home' +import { LandingPage } from '@/components/landing' +import type { LandingLang } from '@/components/landing/translations' import { baseOptions } from '@/lib/layout.shared' export const Route = createFileRoute('/$lang/')({ component: Home }) -const texts = { - en: { heading: 'ServerBee Documentation', cta: 'Open Docs' }, - cn: { heading: 'ServerBee 文档', cta: '打开文档' } -} as const - function Home() { const { lang } = useParams({ from: '/$lang/' }) - const t = texts[lang as keyof typeof texts] ?? texts.en + const landingLang: LandingLang = lang === 'cn' ? 'cn' : 'en' return ( -
-

{t.heading}

- - {t.cta} - -
+
) } diff --git a/apps/docs/src/styles/app.css b/apps/docs/src/styles/app.css index dbcc721d..34e53d10 100644 --- a/apps/docs/src/styles/app.css +++ b/apps/docs/src/styles/app.css @@ -1,3 +1,4 @@ @import "tailwindcss"; @import "fumadocs-ui/css/neutral.css"; @import "fumadocs-ui/css/preset.css"; +@import "./landing.css"; diff --git a/apps/docs/src/styles/landing.css b/apps/docs/src/styles/landing.css new file mode 100644 index 00000000..c3146c5f --- /dev/null +++ b/apps/docs/src/styles/landing.css @@ -0,0 +1,237 @@ +/* Landing-only keyframes and scoped utilities. + All rules nest under .serverbee-landing so they never affect the docs reader. */ +@layer landing { + .serverbee-landing { + --landing-bg: oklch(0.16 0.01 260); + --landing-bg-elevated: oklch(0.2 0.012 260); + --landing-fg: oklch(0.96 0.005 260); + --landing-fg-muted: oklch(0.72 0.01 260); + --landing-border: oklch(0.28 0.015 260); + --landing-amber: #ffb300; + --landing-amber-soft: #ffd166; + --landing-cyan: #4cc9f0; + color: var(--landing-fg); + background: var(--landing-bg); + } + + .serverbee-landing .gradient-text { + color: #ffd166; + -webkit-text-fill-color: transparent; + background: linear-gradient( + 120deg, + #fff 0%, + #ffd166 40%, + #ffb300 70%, + #ff8a3d 100% + ); + -webkit-background-clip: text; + background-clip: text; + } + + .serverbee-landing .hex-bg { + background-image: + radial-gradient( + circle at 20% 10%, + rgba(255, 179, 0, 0.1), + transparent 40% + ), + radial-gradient( + circle at 80% 30%, + rgba(76, 201, 240, 0.08), + transparent 40% + ); + } + + @keyframes landing-pulse { + 0%, + 100% { + box-shadow: 0 0 0 0 rgba(34, 197, 94, 0.55); + } + 70% { + box-shadow: 0 0 0 10px rgba(34, 197, 94, 0); + } + } + .serverbee-landing .pulse-dot { + animation: landing-pulse 2s ease-out infinite; + } + + @keyframes landing-ring { + 0% { + stroke-dashoffset: 220; + } + 50% { + stroke-dashoffset: 70; + } + 100% { + stroke-dashoffset: 220; + } + } + .serverbee-landing .ring-anim { + animation: landing-ring 4s ease-in-out infinite; + } + + @keyframes landing-spark { + 0% { + transform: translateX(0); + } + 100% { + transform: translateX(-50%); + } + } + .serverbee-landing .spark-scroll { + animation: landing-spark 6s linear infinite; + } + + @keyframes landing-hex-drift { + 0% { + transform: translateY(0); + } + 100% { + transform: translateY(-40px); + } + } + .serverbee-landing .hex-drift { + animation: landing-hex-drift 30s linear infinite; + } + + @keyframes landing-orbit { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } + } + .serverbee-landing .orbit-anim { + animation: landing-orbit 14s linear infinite; + } + .serverbee-landing .orbit-counter { + animation: landing-orbit 14s linear infinite reverse; + } + + @keyframes landing-stream { + 0% { + transform: translateX(-100%); + opacity: 0; + } + 10% { + opacity: 1; + } + 90% { + opacity: 1; + } + 100% { + transform: translateX(100%); + opacity: 0; + } + } + .serverbee-landing .stream-particle { + animation: landing-stream 3s linear infinite; + } + + @keyframes landing-blink { + 0%, + 60%, + 100% { + opacity: 1; + } + 30% { + opacity: 0; + } + } + .serverbee-landing .blink { + animation: landing-blink 1s steps(1) infinite; + } + + @keyframes landing-shake { + 0%, + 92%, + 100% { + transform: rotate(0); + } + 94% { + transform: rotate(-14deg); + } + 96% { + transform: rotate(12deg); + } + 98% { + transform: rotate(-6deg); + } + } + .serverbee-landing .bell-shake { + animation: landing-shake 5s ease-in-out infinite; + } + + @keyframes landing-uprotate { + from { + transform: rotate(0); + } + to { + transform: rotate(360deg); + } + } + .serverbee-landing .upgrade-spin { + animation: landing-uprotate 6s linear infinite; + } + + @keyframes landing-band { + 0% { + transform: translateX(-110%); + } + 100% { + transform: translateX(110%); + } + } + .serverbee-landing .light-band { + animation: landing-band 3.5s ease-in-out infinite; + } + + @keyframes landing-fade-cycle { + 0%, + 25% { + opacity: 1; + } + 35%, + 90% { + opacity: 0.25; + } + 100% { + opacity: 1; + } + } + .serverbee-landing .fade-cycle { + animation: landing-fade-cycle 4s ease-in-out infinite; + } + + @keyframes landing-typewriter { + 0%, + 5% { + width: 0; + } + 35%, + 60% { + width: 100%; + } + 95%, + 100% { + width: 0; + } + } + .serverbee-landing .typewriter { + display: inline-block; + overflow: hidden; + white-space: nowrap; + animation: landing-typewriter 8s steps(40, end) infinite; + } + + @media (prefers-reduced-motion: reduce) { + .serverbee-landing *, + .serverbee-landing *::before, + .serverbee-landing *::after { + transition-duration: 0.001ms !important; + animation-duration: 0.001ms !important; + animation-iteration-count: 1 !important; + } + } +} From 4e16188bcf29379fe852cf407a9a0dada66275ee Mon Sep 17 00:00:00 2001 From: ZingerLittleBee <6970999@gmail.com> Date: Fri, 15 May 2026 19:51:28 +0800 Subject: [PATCH 04/14] =?UTF-8?q?feat(docs):=20add=20landing=20primitives?= =?UTF-8?q?=20=E2=80=94=20section,=20gradient=20heading,=20code-copy,=20he?= =?UTF-8?q?x=20bg?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../landing/primitives/code-copy.tsx | 44 +++++++++++++++++++ .../landing/primitives/gradient-heading.tsx | 21 +++++++++ .../landing/primitives/hex-background.tsx | 36 +++++++++++++++ .../components/landing/primitives/section.tsx | 11 +++++ 4 files changed, 112 insertions(+) create mode 100644 apps/docs/src/components/landing/primitives/code-copy.tsx create mode 100644 apps/docs/src/components/landing/primitives/gradient-heading.tsx create mode 100644 apps/docs/src/components/landing/primitives/hex-background.tsx create mode 100644 apps/docs/src/components/landing/primitives/section.tsx diff --git a/apps/docs/src/components/landing/primitives/code-copy.tsx b/apps/docs/src/components/landing/primitives/code-copy.tsx new file mode 100644 index 00000000..35aa016c --- /dev/null +++ b/apps/docs/src/components/landing/primitives/code-copy.tsx @@ -0,0 +1,44 @@ +import { Check, Copy } from 'lucide-react' +import { useState } from 'react' + +import { cn } from '@/lib/cn' + +export function CodeCopy({ command, label, className }: { command: string; label?: string; className?: string }) { + const [copied, setCopied] = useState(false) + + const onCopy = async () => { + try { + await navigator.clipboard.writeText(command) + setCopied(true) + setTimeout(() => setCopied(false), 1500) + } catch { + // Clipboard API unavailable (e.g. insecure context). The command stays + // visible and selectable so users can still copy it manually. + } + } + + return ( +
+ {label ? ( + {label} + ) : null} + + $ + {command} + + +
+ ) +} diff --git a/apps/docs/src/components/landing/primitives/gradient-heading.tsx b/apps/docs/src/components/landing/primitives/gradient-heading.tsx new file mode 100644 index 00000000..8d48d072 --- /dev/null +++ b/apps/docs/src/components/landing/primitives/gradient-heading.tsx @@ -0,0 +1,21 @@ +import type { PropsWithChildren } from 'react' + +import { cn } from '@/lib/cn' + +export function GradientHeading({ + as: Tag = 'h2', + className, + children +}: PropsWithChildren<{ as?: 'h1' | 'h2' | 'h3'; className?: string }>) { + return ( + + {children} + + ) +} diff --git a/apps/docs/src/components/landing/primitives/hex-background.tsx b/apps/docs/src/components/landing/primitives/hex-background.tsx new file mode 100644 index 00000000..1d152130 --- /dev/null +++ b/apps/docs/src/components/landing/primitives/hex-background.tsx @@ -0,0 +1,36 @@ +import { cn } from '@/lib/cn' + +export function HexBackground({ className }: { className?: string }) { + return ( +
+
+ +
+ ) +} diff --git a/apps/docs/src/components/landing/primitives/section.tsx b/apps/docs/src/components/landing/primitives/section.tsx new file mode 100644 index 00000000..d131047a --- /dev/null +++ b/apps/docs/src/components/landing/primitives/section.tsx @@ -0,0 +1,11 @@ +import type { PropsWithChildren } from 'react' + +import { cn } from '@/lib/cn' + +export function Section({ id, className, children }: PropsWithChildren<{ id?: string; className?: string }>) { + return ( +
+
{children}
+
+ ) +} From 8f8878abd9f0afadb1ff17a39aca7e63df7d9899 Mon Sep 17 00:00:00 2001 From: ZingerLittleBee <6970999@gmail.com> Date: Fri, 15 May 2026 19:51:56 +0800 Subject: [PATCH 05/14] feat(docs): add landing hero with mini-dashboard animation --- .../landing/animations/mini-dashboard.tsx | 103 ++++++++++++++++++ apps/docs/src/components/landing/index.tsx | 10 +- .../src/components/landing/sections/hero.tsx | 56 ++++++++++ 3 files changed, 162 insertions(+), 7 deletions(-) create mode 100644 apps/docs/src/components/landing/animations/mini-dashboard.tsx create mode 100644 apps/docs/src/components/landing/sections/hero.tsx diff --git a/apps/docs/src/components/landing/animations/mini-dashboard.tsx b/apps/docs/src/components/landing/animations/mini-dashboard.tsx new file mode 100644 index 00000000..8c2400c2 --- /dev/null +++ b/apps/docs/src/components/landing/animations/mini-dashboard.tsx @@ -0,0 +1,103 @@ +export function MiniDashboard() { + return ( +
+
+
+ + edge-tokyo-01 +
+ linux/arm64 +
+ +
+ + +
+ +
+
+ Network + ↑ 2.1 MB/s · ↓ 318 KB/s +
+ +
+ +
+ + + +
+
+ ) +} + +function Ring({ label, value, color }: { label: string; value: number; color: string }) { + const dash = 220 + return ( +
+ +
+
{label}
+
{value}%
+
+
+ ) +} + +function Sparkline() { + return ( +
+ +
+ ) +} + +function Stat({ label, value }: { label: string; value: string }) { + return ( +
+
{label}
+
{value}
+
+ ) +} diff --git a/apps/docs/src/components/landing/index.tsx b/apps/docs/src/components/landing/index.tsx index ce4fa93f..6c0cedb0 100644 --- a/apps/docs/src/components/landing/index.tsx +++ b/apps/docs/src/components/landing/index.tsx @@ -1,14 +1,10 @@ +import { Hero } from './sections/hero' import type { LandingLang } from './translations' -import { t } from './translations' export function LandingPage({ lang }: { lang: LandingLang }) { - const copy = t(lang) return ( -
-
-

{copy.hero.headline1}

-

{copy.hero.sub}

-
+
+
) } diff --git a/apps/docs/src/components/landing/sections/hero.tsx b/apps/docs/src/components/landing/sections/hero.tsx new file mode 100644 index 00000000..7b5cdf94 --- /dev/null +++ b/apps/docs/src/components/landing/sections/hero.tsx @@ -0,0 +1,56 @@ +import { ArrowRight, Github } from 'lucide-react' + +import { MiniDashboard } from '../animations/mini-dashboard' +import { CodeCopy } from '../primitives/code-copy' +import { GradientHeading } from '../primitives/gradient-heading' +import { HexBackground } from '../primitives/hex-background' +import { Section } from '../primitives/section' +import { INSTALL_COMMAND, type LandingLang, t } from '../translations' + +export function Hero({ lang }: { lang: LandingLang }) { + const copy = t(lang).hero + const docsHref = `/${lang}/docs/quick-start` + return ( +
+ +
+
+ + {copy.eyebrow} + + + {copy.headline1} +
+ {copy.headline2} +
+

{copy.sub}

+ + + +
+ +
+
+ +
+ +
+
+
+ ) +} From 8cfc3d3181df095e449bc70cad5eb8c7dad85030 Mon Sep 17 00:00:00 2001 From: ZingerLittleBee <6970999@gmail.com> Date: Fri, 15 May 2026 19:53:22 +0800 Subject: [PATCH 06/14] feat(docs): add trust strip and three-pillar section with animations --- .../landing/animations/data-stream.tsx | 40 +++++++++++++++++++ .../landing/animations/install-binary.tsx | 28 +++++++++++++ .../landing/animations/orbit-icons.tsx | 36 +++++++++++++++++ apps/docs/src/components/landing/index.tsx | 4 ++ .../components/landing/sections/pillars.tsx | 30 ++++++++++++++ .../landing/sections/trust-strip.tsx | 28 +++++++++++++ 6 files changed, 166 insertions(+) create mode 100644 apps/docs/src/components/landing/animations/data-stream.tsx create mode 100644 apps/docs/src/components/landing/animations/install-binary.tsx create mode 100644 apps/docs/src/components/landing/animations/orbit-icons.tsx create mode 100644 apps/docs/src/components/landing/sections/pillars.tsx create mode 100644 apps/docs/src/components/landing/sections/trust-strip.tsx diff --git a/apps/docs/src/components/landing/animations/data-stream.tsx b/apps/docs/src/components/landing/animations/data-stream.tsx new file mode 100644 index 00000000..a1f379ef --- /dev/null +++ b/apps/docs/src/components/landing/animations/data-stream.tsx @@ -0,0 +1,40 @@ +export function DataStreamAnim() { + return ( +
+ +
+ + + + +
+ +
+ ) +} + +function Endpoint({ label, color }: { label: string; color: string }) { + return ( +
+
+ {label} +
+ ) +} + +function Particle({ delay, colorClass, reverse }: { delay: string; colorClass: string; reverse?: boolean }) { + return ( + + ) +} diff --git a/apps/docs/src/components/landing/animations/install-binary.tsx b/apps/docs/src/components/landing/animations/install-binary.tsx new file mode 100644 index 00000000..810f2b7c --- /dev/null +++ b/apps/docs/src/components/landing/animations/install-binary.tsx @@ -0,0 +1,28 @@ +export function InstallBinaryAnim() { + return ( +
+
+
+ serverbee +
+ +
+ + systemd · active +
+
+
+ ) +} diff --git a/apps/docs/src/components/landing/animations/orbit-icons.tsx b/apps/docs/src/components/landing/animations/orbit-icons.tsx new file mode 100644 index 00000000..16437796 --- /dev/null +++ b/apps/docs/src/components/landing/animations/orbit-icons.tsx @@ -0,0 +1,36 @@ +import { FileCog, Layers, TerminalSquare } from 'lucide-react' +import type { ReactNode } from 'react' + +export function OrbitIconsAnim() { + return ( +
+
+
+
+
+
+ } /> + } /> + } /> +
+
+
+ ) +} + +function OrbitItem({ angle, icon }: { angle: number; icon: ReactNode }) { + return ( +
+
+ {icon} +
+
+ ) +} diff --git a/apps/docs/src/components/landing/index.tsx b/apps/docs/src/components/landing/index.tsx index 6c0cedb0..c0dfd88e 100644 --- a/apps/docs/src/components/landing/index.tsx +++ b/apps/docs/src/components/landing/index.tsx @@ -1,10 +1,14 @@ import { Hero } from './sections/hero' +import { Pillars } from './sections/pillars' +import { TrustStrip } from './sections/trust-strip' import type { LandingLang } from './translations' export function LandingPage({ lang }: { lang: LandingLang }) { return (
+ +
) } diff --git a/apps/docs/src/components/landing/sections/pillars.tsx b/apps/docs/src/components/landing/sections/pillars.tsx new file mode 100644 index 00000000..18dd71a2 --- /dev/null +++ b/apps/docs/src/components/landing/sections/pillars.tsx @@ -0,0 +1,30 @@ +import { DataStreamAnim } from '../animations/data-stream' +import { InstallBinaryAnim } from '../animations/install-binary' +import { OrbitIconsAnim } from '../animations/orbit-icons' +import { Section } from '../primitives/section' +import { type LandingLang, t } from '../translations' + +export function Pillars({ lang }: { lang: LandingLang }) { + const copy = t(lang).pillars + const cards = [ + { ...copy.one, Anim: InstallBinaryAnim }, + { ...copy.two, Anim: DataStreamAnim }, + { ...copy.three, Anim: OrbitIconsAnim } + ] + return ( +
+
+ {cards.map(({ title, body, Anim }) => ( +
+ +

{title}

+

{body}

+
+ ))} +
+
+ ) +} diff --git a/apps/docs/src/components/landing/sections/trust-strip.tsx b/apps/docs/src/components/landing/sections/trust-strip.tsx new file mode 100644 index 00000000..f6940e63 --- /dev/null +++ b/apps/docs/src/components/landing/sections/trust-strip.tsx @@ -0,0 +1,28 @@ +import { Cpu, Gauge, PackageCheck } from 'lucide-react' + +import { Section } from '../primitives/section' +import { type LandingLang, t } from '../translations' + +export function TrustStrip({ lang }: { lang: LandingLang }) { + const copy = t(lang).trust + const items = [ + { Icon: PackageCheck, label: copy.binary }, + { Icon: Gauge, label: copy.realtime }, + { Icon: Cpu, label: copy.deps } + ] + return ( +
+
    + {items.map(({ Icon, label }) => ( +
  • + + {label} +
  • + ))} +
+
+ ) +} From 0a17e4d23e0911fe4ea83d5c26d2bc12ada60aac Mon Sep 17 00:00:00 2001 From: ZingerLittleBee <6970999@gmail.com> Date: Fri, 15 May 2026 19:53:36 +0800 Subject: [PATCH 07/14] feat(docs): add 8-tile bento grid with feature animations --- .../landing/animations/alert-bell.tsx | 25 +++++++ .../landing/animations/color-ring.tsx | 17 +++++ .../landing/animations/docker-stack.tsx | 30 +++++++++ .../landing/animations/file-tree.tsx | 35 ++++++++++ .../landing/animations/monitor-dots.tsx | 20 ++++++ .../landing/animations/ping-chart.tsx | 36 ++++++++++ .../landing/animations/terminal-demo.tsx | 25 +++++++ .../landing/animations/upgrade-loop.tsx | 18 +++++ apps/docs/src/components/landing/index.tsx | 2 + .../src/components/landing/sections/bento.tsx | 65 +++++++++++++++++++ 10 files changed, 273 insertions(+) create mode 100644 apps/docs/src/components/landing/animations/alert-bell.tsx create mode 100644 apps/docs/src/components/landing/animations/color-ring.tsx create mode 100644 apps/docs/src/components/landing/animations/docker-stack.tsx create mode 100644 apps/docs/src/components/landing/animations/file-tree.tsx create mode 100644 apps/docs/src/components/landing/animations/monitor-dots.tsx create mode 100644 apps/docs/src/components/landing/animations/ping-chart.tsx create mode 100644 apps/docs/src/components/landing/animations/terminal-demo.tsx create mode 100644 apps/docs/src/components/landing/animations/upgrade-loop.tsx create mode 100644 apps/docs/src/components/landing/sections/bento.tsx diff --git a/apps/docs/src/components/landing/animations/alert-bell.tsx b/apps/docs/src/components/landing/animations/alert-bell.tsx new file mode 100644 index 00000000..a36cce4e --- /dev/null +++ b/apps/docs/src/components/landing/animations/alert-bell.tsx @@ -0,0 +1,25 @@ +import { Bell } from 'lucide-react' + +export function AlertBellAnim() { + const channels = ['Webhook', 'Telegram', 'Bark', 'Email', 'APNs'] + return ( +
+ +
+ {channels.map((c, i) => ( + + {c} + + ))} +
+
+ ) +} diff --git a/apps/docs/src/components/landing/animations/color-ring.tsx b/apps/docs/src/components/landing/animations/color-ring.tsx new file mode 100644 index 00000000..8461a101 --- /dev/null +++ b/apps/docs/src/components/landing/animations/color-ring.tsx @@ -0,0 +1,17 @@ +export function ColorRingAnim() { + const stops = ['#ffb300', '#4cc9f0', '#22c55e', '#a855f7', '#ef4444', '#ffb300'] + const gradient = stops.map((c, i) => `${c} ${(i / (stops.length - 1)) * 360}deg`).join(', ') + return ( +
+
+
+
+
+
+
+ ) +} diff --git a/apps/docs/src/components/landing/animations/docker-stack.tsx b/apps/docs/src/components/landing/animations/docker-stack.tsx new file mode 100644 index 00000000..98e14f63 --- /dev/null +++ b/apps/docs/src/components/landing/animations/docker-stack.tsx @@ -0,0 +1,30 @@ +import { Box } from 'lucide-react' + +export function DockerStackAnim() { + const containers = [ + { name: 'web', tag: 'caddy:2', delay: '0s' }, + { name: 'api', tag: 'rust:1.84', delay: '0.4s' }, + { name: 'cache', tag: 'redis:7', delay: '0.8s' } + ] + return ( +
+ {containers.map((c) => ( +
+ + {c.name} + {c.tag} + + + running + +
+ ))} +
+ ) +} diff --git a/apps/docs/src/components/landing/animations/file-tree.tsx b/apps/docs/src/components/landing/animations/file-tree.tsx new file mode 100644 index 00000000..9b58e8b2 --- /dev/null +++ b/apps/docs/src/components/landing/animations/file-tree.tsx @@ -0,0 +1,35 @@ +import { File, FolderOpen, FolderTree } from 'lucide-react' +import type { ComponentType } from 'react' + +export function FileTreeAnim() { + return ( +
+
+ + + + +
+
+ +
+
+ ) +} + +function Row({ + Icon, + label, + indent +}: { + Icon: ComponentType<{ className?: string }> + label: string + indent?: boolean +}) { + return ( +
+ + {label} +
+ ) +} diff --git a/apps/docs/src/components/landing/animations/monitor-dots.tsx b/apps/docs/src/components/landing/animations/monitor-dots.tsx new file mode 100644 index 00000000..89699d5f --- /dev/null +++ b/apps/docs/src/components/landing/animations/monitor-dots.tsx @@ -0,0 +1,20 @@ +export function MonitorDotsAnim() { + const probes = ['SSL', 'DNS', 'HTTP', 'TCP', 'WHOIS'] + return ( +
+ {probes.map((p, i) => ( +
+ {p} + +
+ ))} +
+ ) +} diff --git a/apps/docs/src/components/landing/animations/ping-chart.tsx b/apps/docs/src/components/landing/animations/ping-chart.tsx new file mode 100644 index 00000000..1ded6759 --- /dev/null +++ b/apps/docs/src/components/landing/animations/ping-chart.tsx @@ -0,0 +1,36 @@ +export function PingChartAnim() { + return ( +
+
+ +
+
+
+ {Array.from({ length: 18 }).map((_, i) => ( + + ))} +
+
+ ) +} diff --git a/apps/docs/src/components/landing/animations/terminal-demo.tsx b/apps/docs/src/components/landing/animations/terminal-demo.tsx new file mode 100644 index 00000000..d7fe86c3 --- /dev/null +++ b/apps/docs/src/components/landing/animations/terminal-demo.tsx @@ -0,0 +1,25 @@ +export function TerminalDemoAnim() { + return ( +
+
+ + + + edge-tokyo-01 ~ # +
+
+        $ 
+        serverbee agent --version
+        {'\n'}
+        serverbee-agent 0.3.0 (linux/arm64){'\n'}
+        features: terminal,file,docker,ping,upgrade{'\n'}
+        $ 
+        
+      
+
+ ) +} diff --git a/apps/docs/src/components/landing/animations/upgrade-loop.tsx b/apps/docs/src/components/landing/animations/upgrade-loop.tsx new file mode 100644 index 00000000..b7ca2c99 --- /dev/null +++ b/apps/docs/src/components/landing/animations/upgrade-loop.tsx @@ -0,0 +1,18 @@ +import { RotateCw } from 'lucide-react' + +export function UpgradeLoopAnim() { + return ( +
+ +
+ v0.2.9 + + v0.3.0 +
+
+ ) +} diff --git a/apps/docs/src/components/landing/index.tsx b/apps/docs/src/components/landing/index.tsx index c0dfd88e..083212b2 100644 --- a/apps/docs/src/components/landing/index.tsx +++ b/apps/docs/src/components/landing/index.tsx @@ -1,3 +1,4 @@ +import { Bento } from './sections/bento' import { Hero } from './sections/hero' import { Pillars } from './sections/pillars' import { TrustStrip } from './sections/trust-strip' @@ -9,6 +10,7 @@ export function LandingPage({ lang }: { lang: LandingLang }) { +
) } diff --git a/apps/docs/src/components/landing/sections/bento.tsx b/apps/docs/src/components/landing/sections/bento.tsx new file mode 100644 index 00000000..bdebaacd --- /dev/null +++ b/apps/docs/src/components/landing/sections/bento.tsx @@ -0,0 +1,65 @@ +import type { ComponentType, ReactNode } from 'react' + +import { AlertBellAnim } from '../animations/alert-bell' +import { ColorRingAnim } from '../animations/color-ring' +import { DockerStackAnim } from '../animations/docker-stack' +import { FileTreeAnim } from '../animations/file-tree' +import { MonitorDotsAnim } from '../animations/monitor-dots' +import { PingChartAnim } from '../animations/ping-chart' +import { TerminalDemoAnim } from '../animations/terminal-demo' +import { UpgradeLoopAnim } from '../animations/upgrade-loop' +import { GradientHeading } from '../primitives/gradient-heading' +import { Section } from '../primitives/section' +import { type LandingLang, t } from '../translations' + +interface Tile { + Anim: ComponentType + body: string + span: string + title: string +} + +export function Bento({ lang }: { lang: LandingLang }) { + const copy = t(lang).bento + const tiles: Tile[] = [ + { ...copy.network, Anim: PingChartAnim, span: 'md:col-span-6 md:row-span-2' }, + { ...copy.themes, Anim: ColorRingAnim, span: 'md:col-span-3' }, + { ...copy.alerts, Anim: AlertBellAnim, span: 'md:col-span-3' }, + { ...copy.monitors, Anim: MonitorDotsAnim, span: 'md:col-span-6' }, + { ...copy.terminal, Anim: TerminalDemoAnim, span: 'md:col-span-6 md:row-span-2' }, + { ...copy.file, Anim: FileTreeAnim, span: 'md:col-span-6' }, + { ...copy.docker, Anim: DockerStackAnim, span: 'md:col-span-3' }, + { ...copy.upgrade, Anim: UpgradeLoopAnim, span: 'md:col-span-3' } + ] + + return ( +
+ {bentoTitle(lang)} +
+ {tiles.map(({ title, body, Anim, span }) => ( + + + + ))} +
+
+ ) +} + +function Card({ title, body, span, children }: { title: string; body: string; span: string; children: ReactNode }) { + return ( +
+
{children}
+
+

{title}

+

{body}

+
+
+ ) +} + +function bentoTitle(lang: LandingLang): string { + return lang === 'cn' ? '一个探针,覆盖运维的方方面面。' : 'One probe. Every job your VPS needs.' +} From 9aa965279d1dff4bd265c24c55743876349668c4 Mon Sep 17 00:00:00 2001 From: ZingerLittleBee <6970999@gmail.com> Date: Fri, 15 May 2026 19:57:02 +0800 Subject: [PATCH 08/14] feat(docs): add how-it-works and final CTA sections --- .../landing/animations/light-band.tsx | 7 ++++ apps/docs/src/components/landing/index.tsx | 4 ++ .../components/landing/sections/final-cta.tsx | 40 +++++++++++++++++++ .../landing/sections/how-it-works.tsx | 26 ++++++++++++ 4 files changed, 77 insertions(+) create mode 100644 apps/docs/src/components/landing/animations/light-band.tsx create mode 100644 apps/docs/src/components/landing/sections/final-cta.tsx create mode 100644 apps/docs/src/components/landing/sections/how-it-works.tsx diff --git a/apps/docs/src/components/landing/animations/light-band.tsx b/apps/docs/src/components/landing/animations/light-band.tsx new file mode 100644 index 00000000..7c4a3654 --- /dev/null +++ b/apps/docs/src/components/landing/animations/light-band.tsx @@ -0,0 +1,7 @@ +export function LightBandArrow() { + return ( +
+ +
+ ) +} diff --git a/apps/docs/src/components/landing/index.tsx b/apps/docs/src/components/landing/index.tsx index 083212b2..ada3d1c2 100644 --- a/apps/docs/src/components/landing/index.tsx +++ b/apps/docs/src/components/landing/index.tsx @@ -1,5 +1,7 @@ import { Bento } from './sections/bento' +import { FinalCta } from './sections/final-cta' import { Hero } from './sections/hero' +import { HowItWorks } from './sections/how-it-works' import { Pillars } from './sections/pillars' import { TrustStrip } from './sections/trust-strip' import type { LandingLang } from './translations' @@ -11,6 +13,8 @@ export function LandingPage({ lang }: { lang: LandingLang }) { + +
) } diff --git a/apps/docs/src/components/landing/sections/final-cta.tsx b/apps/docs/src/components/landing/sections/final-cta.tsx new file mode 100644 index 00000000..e3d75a47 --- /dev/null +++ b/apps/docs/src/components/landing/sections/final-cta.tsx @@ -0,0 +1,40 @@ +import { ArrowRight, Github } from 'lucide-react' + +import { CodeCopy } from '../primitives/code-copy' +import { GradientHeading } from '../primitives/gradient-heading' +import { HexBackground } from '../primitives/hex-background' +import { Section } from '../primitives/section' +import { INSTALL_COMMAND, type LandingLang, t } from '../translations' + +export function FinalCta({ lang }: { lang: LandingLang }) { + const copy = t(lang).finalCta + const docsHref = `/${lang}/docs/quick-start` + return ( +
+ +
+ {copy.title} +

{copy.sub}

+ +
+ +
+
+
+ ) +} diff --git a/apps/docs/src/components/landing/sections/how-it-works.tsx b/apps/docs/src/components/landing/sections/how-it-works.tsx new file mode 100644 index 00000000..b612a67d --- /dev/null +++ b/apps/docs/src/components/landing/sections/how-it-works.tsx @@ -0,0 +1,26 @@ +import { LightBandArrow } from '../animations/light-band' +import { GradientHeading } from '../primitives/gradient-heading' +import { Section } from '../primitives/section' +import { type LandingLang, t } from '../translations' + +export function HowItWorks({ lang }: { lang: LandingLang }) { + const copy = t(lang).how + const steps = [copy.step1, copy.step2, copy.step3] + return ( +
+ {copy.title} +
+ {steps.map((s, i) => ( +
+
+
{`0${i + 1}`}
+

{s.title}

+

{s.body}

+
+ {i < steps.length - 1 ? : null} +
+ ))} +
+
+ ) +} From ff36df49ff30b42ecd39a40a429691e94b0aabe7 Mon Sep 17 00:00:00 2001 From: ZingerLittleBee <6970999@gmail.com> Date: Fri, 15 May 2026 19:57:38 +0800 Subject: [PATCH 09/14] =?UTF-8?q?fix(docs):=20polish=20landing=20layout=20?= =?UTF-8?q?=E2=80=94=20wrap=20install=20command,=20richer=20bento=20tiles,?= =?UTF-8?q?=20balanced=20headings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../landing/animations/alert-bell.tsx | 5 ++- .../landing/animations/color-ring.tsx | 9 ++-- .../landing/animations/docker-stack.tsx | 24 ++++++----- .../landing/animations/file-tree.tsx | 42 ++++++++++++++----- .../landing/animations/monitor-dots.tsx | 25 +++++++---- .../landing/animations/ping-chart.tsx | 38 ++++++++++++++--- .../landing/animations/terminal-demo.tsx | 19 +++++++-- .../landing/animations/upgrade-loop.tsx | 8 ++-- .../landing/primitives/code-copy.tsx | 10 +++-- .../landing/primitives/gradient-heading.tsx | 2 +- .../src/components/landing/sections/bento.tsx | 6 +-- 11 files changed, 135 insertions(+), 53 deletions(-) diff --git a/apps/docs/src/components/landing/animations/alert-bell.tsx b/apps/docs/src/components/landing/animations/alert-bell.tsx index a36cce4e..fe0d44e4 100644 --- a/apps/docs/src/components/landing/animations/alert-bell.tsx +++ b/apps/docs/src/components/landing/animations/alert-bell.tsx @@ -8,7 +8,10 @@ export function AlertBellAnim() { className="flex h-full flex-col items-center justify-center gap-3" role="img" > - +
+ + +
{channels.map((c, i) => ( -
-
-
+
+
+
+
+
+ OKLCH
diff --git a/apps/docs/src/components/landing/animations/docker-stack.tsx b/apps/docs/src/components/landing/animations/docker-stack.tsx index 98e14f63..fe944394 100644 --- a/apps/docs/src/components/landing/animations/docker-stack.tsx +++ b/apps/docs/src/components/landing/animations/docker-stack.tsx @@ -2,26 +2,30 @@ import { Box } from 'lucide-react' export function DockerStackAnim() { const containers = [ - { name: 'web', tag: 'caddy:2', delay: '0s' }, - { name: 'api', tag: 'rust:1.84', delay: '0.4s' }, - { name: 'cache', tag: 'redis:7', delay: '0.8s' } + { name: 'web', tag: 'caddy:2', cpu: '0.4%', delay: '0s' }, + { name: 'api', tag: 'rust:1.84', cpu: '1.2%', delay: '0.4s' }, + { name: 'cache', tag: 'redis:7', cpu: '0.1%', delay: '0.8s' } ] return ( -
+
{containers.map((c) => (
- - {c.name} - {c.tag} - + + {c.name} + {c.tag} + - running + {c.cpu}
))} diff --git a/apps/docs/src/components/landing/animations/file-tree.tsx b/apps/docs/src/components/landing/animations/file-tree.tsx index 9b58e8b2..f71ff955 100644 --- a/apps/docs/src/components/landing/animations/file-tree.tsx +++ b/apps/docs/src/components/landing/animations/file-tree.tsx @@ -1,17 +1,28 @@ -import { File, FolderOpen, FolderTree } from 'lucide-react' +import { File, FolderOpen, FolderTree, Upload } from 'lucide-react' import type { ComponentType } from 'react' export function FileTreeAnim() { return ( -
-
+
+
- - - + + + + +
-
- +
+ + access.log → uploading +
+ +
+ 64%
) @@ -20,16 +31,27 @@ export function FileTreeAnim() { function Row({ Icon, label, - indent + indent, + indent2, + muted }: { Icon: ComponentType<{ className?: string }> label: string indent?: boolean + indent2?: boolean + muted?: string }) { + let pad = '' + if (indent2) { + pad = 'pl-6' + } else if (indent) { + pad = 'pl-3' + } return ( -
+
{label} + {muted ? {muted} : null}
) } diff --git a/apps/docs/src/components/landing/animations/monitor-dots.tsx b/apps/docs/src/components/landing/animations/monitor-dots.tsx index 89699d5f..f37fb416 100644 --- a/apps/docs/src/components/landing/animations/monitor-dots.tsx +++ b/apps/docs/src/components/landing/animations/monitor-dots.tsx @@ -1,18 +1,27 @@ export function MonitorDotsAnim() { - const probes = ['SSL', 'DNS', 'HTTP', 'TCP', 'WHOIS'] + const probes = [ + { name: 'SSL', meta: 'expires in 73d' }, + { name: 'DNS', meta: 'A · CNAME' }, + { name: 'HTTP', meta: '200 · keyword OK' }, + { name: 'TCP', meta: ':443 · 18 ms' }, + { name: 'WHOIS', meta: 'renews 2027-04' } + ] return (
{probes.map((p, i) => ( -
- {p} - +
+
+ + {p.name} +
+ {p.meta}
))}
diff --git a/apps/docs/src/components/landing/animations/ping-chart.tsx b/apps/docs/src/components/landing/animations/ping-chart.tsx index 1ded6759..560ed595 100644 --- a/apps/docs/src/components/landing/animations/ping-chart.tsx +++ b/apps/docs/src/components/landing/animations/ping-chart.tsx @@ -1,7 +1,8 @@ export function PingChartAnim() { + const cells = 60 return (
-
+
+
+ + 24 ms +
+
+
+ last 60 probes + + + ok + + + loss + +
-
- {Array.from({ length: 18 }).map((_, i) => ( +
+ {Array.from({ length: cells }).map((_, i) => ( ))} diff --git a/apps/docs/src/components/landing/animations/terminal-demo.tsx b/apps/docs/src/components/landing/animations/terminal-demo.tsx index d7fe86c3..998e489b 100644 --- a/apps/docs/src/components/landing/animations/terminal-demo.tsx +++ b/apps/docs/src/components/landing/animations/terminal-demo.tsx @@ -2,7 +2,7 @@ export function TerminalDemoAnim() { return (
@@ -11,12 +11,23 @@ export function TerminalDemoAnim() { edge-tokyo-01 ~ #
-
+      
         $ 
         serverbee agent --version
         {'\n'}
-        serverbee-agent 0.3.0 (linux/arm64){'\n'}
-        features: terminal,file,docker,ping,upgrade{'\n'}
+        serverbee-agent 0.3.0 (linux/arm64)
+        {'\n'}
+        features: 
+        terminal,file,docker,ping,upgrade
+        {'\n\n'}
+        $ 
+        serverbee server status
+        {'\n'}
+        ● serverbee-server.service · 
+        active (running)
+        {'\n'}
+         └─ uptime: 14d · agents: 7 · alerts: 0
+        {'\n\n'}
         $ 
         
       
diff --git a/apps/docs/src/components/landing/animations/upgrade-loop.tsx b/apps/docs/src/components/landing/animations/upgrade-loop.tsx index b7ca2c99..485c3c95 100644 --- a/apps/docs/src/components/landing/animations/upgrade-loop.tsx +++ b/apps/docs/src/components/landing/animations/upgrade-loop.tsx @@ -7,11 +7,13 @@ export function UpgradeLoopAnim() { className="flex h-full flex-col items-center justify-center gap-3" role="img" > - +
+ +
- v0.2.9 + v0.2.9 - v0.3.0 + v0.3.0
) diff --git a/apps/docs/src/components/landing/primitives/code-copy.tsx b/apps/docs/src/components/landing/primitives/code-copy.tsx index 35aa016c..696cbce5 100644 --- a/apps/docs/src/components/landing/primitives/code-copy.tsx +++ b/apps/docs/src/components/landing/primitives/code-copy.tsx @@ -20,20 +20,22 @@ export function CodeCopy({ command, label, className }: { command: string; label return (
{label ? ( - {label} + + {label} + ) : null} - + $ {command}