From da0317dda5ba6d96d85d6c28d5503b6cdc1b3757 Mon Sep 17 00:00:00 2001
From: Edward Twumasi <45181928+teckedd-code2save@users.noreply.github.com>
Date: Tue, 22 Sep 2026 02:41:29 +0000
Subject: [PATCH] Rebuild product journey and document discovery, enrollment
and next steps
---
MEDIA_CAPTURE.md | 17 +-
README.md | 7 +-
SITE_REVIEW.md | 33 +-
.../chatgpt-operated-my-deployment/page.tsx | 6 +-
app/components/NextSteps.tsx | 97 +
app/components/ProductTour.tsx | 118 +
app/components/SiteChrome.tsx | 53 +-
app/components/WorkloadModel.tsx | 47 +
app/docs/[slug]/page.tsx | 10 +-
app/docs/_components/DocsNav.tsx | 100 +-
app/docs/_components/EndpointBuilder.tsx | 2 +-
app/docs/page.tsx | 98 +-
app/globals.css | 2121 +++++++++++------
app/layout.tsx | 2 +-
app/page.test.tsx | 44 +
app/page.tsx | 382 +--
content/guide-index.ts | 79 +-
content/guide-parts.tsx | 47 +
content/guides.tsx | 162 +-
content/journey-guides.tsx | 738 ++++++
public/proof/discovery-desktop.jpg | Bin 0 -> 68482 bytes
public/proof/enrollment-desktop.jpg | Bin 0 -> 39312 bytes
public/proof/layout-desktop.jpg | Bin 0 -> 83987 bytes
23 files changed, 3032 insertions(+), 1131 deletions(-)
create mode 100644 app/components/NextSteps.tsx
create mode 100644 app/components/ProductTour.tsx
create mode 100644 app/components/WorkloadModel.tsx
create mode 100644 content/guide-parts.tsx
create mode 100644 content/journey-guides.tsx
create mode 100644 public/proof/discovery-desktop.jpg
create mode 100644 public/proof/enrollment-desktop.jpg
create mode 100644 public/proof/layout-desktop.jpg
diff --git a/MEDIA_CAPTURE.md b/MEDIA_CAPTURE.md
index 4149bf9..88918dc 100644
--- a/MEDIA_CAPTURE.md
+++ b/MEDIA_CAPTURE.md
@@ -2,11 +2,14 @@
The public site uses unaltered desktop captures from the operator's authenticated GroundControl interface, captured on 22 September 2026. No generated interface or chat image is presented as product evidence.
-| Asset | Product page | What it proves |
-|---|---|---|
-| `public/proof/agents-desktop.jpg` | Agents | MCP endpoint, active ChatGPT grant, five displayed scopes, eight allowed workloads |
-| `public/proof/configure-desktop.jpg` | Deployments / RentAWeekend / Configure | Explicit repository, branch, recorded revision and enabled deployment-specific merge automation |
+| Asset | Product page | What it proves |
+| --------------------------------------- | --------------------------------------------------- | --------------------------------------------------------------------------------------------------- |
+| `public/proof/agents-desktop.jpg` | Agents | MCP endpoint, active ChatGPT grant, five displayed scopes, eight allowed workloads |
+| `public/proof/configure-desktop.jpg` | Deployments / RentAWeekend / Configure | Explicit repository, branch, recorded revision and enabled deployment-specific merge automation |
| `public/proof/verification-desktop.jpg` | Deployments / RentAWeekend / Deploy / View evidence | Service image comparison, migration exit, public verification for the recorded 21 September release |
+| `public/proof/discovery-desktop.jpg` | Deployments / Discovered on this host | Existing Compose candidates, paths and explicit enrollment controls |
+| `public/proof/enrollment-desktop.jpg` | Deployments / Enrol into a project | Existing project choices and Create & link; the dialog was closed without enrolling anything |
+| `public/proof/layout-desktop.jpg` | Settings / Layout / Deployment discovery | Separate discovery root and template deployment root, using the running instance's actual values |
The public screenshot captions distinguish capture date from operation date. Images open full-size; their original aspect ratio is preserved. The previous mobile captures were removed.
@@ -22,4 +25,8 @@ The public screenshot captions distinguish capture date from operation date. Ima
ChatGPT's connection path was checked against https://developers.openai.com/plugins/deploy/connect-chatgpt on 22 September 2026. It currently describes Settings → Security and login → Developer mode, then Plugins → +. Older product copy refers to Apps / Connectors. Account and workspace availability can differ.
-GroundControl setup labels, installer options, claim behavior, publishing paths, OAuth scopes and revocation behavior were checked against product main `68e2c23` and the running UI. Docs are rendered natively from `content/guides.tsx`; `content/guide-index.ts` drives the sidebar and guide navigation.
+GroundControl setup labels, installer options, claim behavior, publishing paths, OAuth scopes and revocation behavior were checked against product main `68e2c23` and the running UI. The post-install follow-up also traced inventory discovery, enrollment defaults, project grouping, managed source-deploy guards and Templates. Discovery and enrollment captures did not start a deployment or change workload ownership.
+
+Docs are rendered natively from `content/guides.tsx` and `content/journey-guides.tsx`; `content/guide-index.ts` drives grouped navigation. The illustrated workload model is labeled as an example. The product-tour tabs display captured images, not a live connection to the reader's host.
+
+The documentation explicitly distinguishes tracking from managed deployment and from OAuth authorization. A newly discovered workload defaults to tracking, and the current UI has no general promotion control. Tracking is not documented as a universal read-only security boundary.
diff --git a/README.md b/README.md
index 7209a55..b347af2 100644
--- a/README.md
+++ b/README.md
@@ -78,12 +78,15 @@ MIT
## Native documentation and public evidence
-The complete adoption path lives at `/docs`: install/claim, HTTPS publishing, deployment enrollment, ChatGPT OAuth/MCP connection, deployment automation, maintenance, troubleshooting and evidence. No GitHub visit is required to follow a guide.
+The complete adoption path lives at `/docs`. Twelve guides cover the philosophy and workload model, install/claim, next steps after installation, HTTPS publishing, host discovery, existing application enrollment, new template deployments, ChatGPT OAuth/MCP, deployment automation, maintenance, troubleshooting and evidence. No GitHub visit is required to follow a guide.
- `content/guides.tsx` contains the guide sections, runnable examples and expected results.
+- `content/journey-guides.tsx` contains the philosophy, post-install, discovery and new-deployment guides. `content/guide-parts.tsx` supplies shared guide elements.
- `content/guide-index.ts` defines sidebar order and search terms.
+- `ProductTour` offers four views of the actual product with keyboard-accessible tabs. `NextSteps` provides separate session-only checklists for existing and new workloads; checking an item performs no host operation.
+- `WorkloadModel` is a labeled conceptual illustration, not a screenshot or live status display.
- `app/docs/_components/EndpointBuilder.tsx` formats the operator's HTTPS MCP URL entirely in the browser; it performs no network request.
- Product screenshots open at full size and preserve their aspect ratio.
- See `MEDIA_CAPTURE.md` for capture provenance, dates and claim boundaries.
-Validate guide links/anchors, assets, endpoint formatting and clipboard behavior with `npm test`. Before publishing, inspect the hosted preview at desktop and narrow viewport widths, including copy actions and the ChatGPT setup guide.
+Validate guide links/anchors, assets, endpoint formatting, clipboard behavior, product-tour keyboard navigation and checklist state with `npm test`. Review the actual rendered pages and capture any limits in `SITE_REVIEW.md`. The current browser runtime does not expose viewport emulation, so real phone testing remains outstanding.
diff --git a/SITE_REVIEW.md b/SITE_REVIEW.md
index b84de0d..243123b 100644
--- a/SITE_REVIEW.md
+++ b/SITE_REVIEW.md
@@ -1,22 +1,27 @@
-# Public site review — 22 September 2026
+# Public site review, 22 September 2026
-## What changed
+## Product journey revision
-The adoption path now stays on the public site: eight guides cover installation, claim, HTTPS, workload enrollment, ChatGPT OAuth/MCP, operation tracking, maintenance, troubleshooting and evidence. The article and homepage use fresh desktop product captures; the previous phone images are removed.
+The homepage now has a four-part product tour using actual desktop captures, a post-install sequence, an illustrated workload model, inspectable deployment proof and direct learning paths. Twelve native guides cover the GroundControl philosophy, installation, next steps, publishing, discovery, enrollment, new template deployments, agent access, automation, maintenance, troubleshooting and evidence.
-## Verification
+The post-install guide offers separate checklists for existing and new applications. Checkmarks are session-only progress aids; they do not perform or verify any server operation. Documentation navigation is grouped into Understand, Set up and Operate, with an explicit expandable menu on narrow screens.
-- Local lint, TypeScript, seven tests and production build passed. Tests validate all internal guide destinations, section anchors, image paths, connection instructions, endpoint validation and clipboard outcomes.
-- PR #17 passed GitHub CI and Vercel deployment. Its merged production revision is `dcaa1dbf37fb7f2d5de6c8804c564b4b287e314e`.
-- The public homepage and native OAuth guide were opened in the browser at 1363 CSS pixels. Both product images loaded; there was no horizontal document overflow. The guide's section links and search worked.
-- Entering `https://gc.example.org` produced `https://gc.example.org/mcp`, and the browser clipboard contained that exact value after Copy. An HTTP address showed the HTTPS validation error.
-- The guide's ChatGPT settings path was checked against OpenAI's current connection documentation. The GroundControl fields were checked against the product code and authenticated desktop UI. We did not create a separate new ChatGPT plugin or new VPS installation as part of this site review.
-- Fresh MCP health and a retrieved, existing GitHub-triggered operation are published as separate evidence records. The article does not describe the existing operation as a new ChatGPT-issued deployment.
+## Product verification
-## Visual follow-up
+- Installer, onboarding probe, inventory discovery, enrollment, project grouping, source deployment and Templates were traced against product main `68e2c23`.
+- Live UI review confirmed Settings → Layout → Deployment discovery, the two root fields, Discovered on this host and the enrollment dialog. No workload was enrolled, deployed or restarted during those captures.
+- The guides distinguish initial broad host probing from the inventory scan of configured roots. They explain Docker-label matching, conventional Compose names, bounded depth and common missing/duplicate candidate cases.
+- Existing candidates default to tracking. New template deployments become managed. The current UI has no general tracking-to-managed promotion control. OAuth permissions are separate, and tracking is not a universal read-only security boundary.
+- Loop, Daytona, public verification and source-to-image evidence limits remain explicit.
-Browser review caught external-link arrows rendered as emoji tiles. Text-presentation selectors correct them. The article's visible title now names GroundControl and agentic deployment, and the hero explains the repeated access/credential friction directly.
+## Automated verification
-The available browser does not expose mobile viewport emulation. The 1200/900/700-pixel responsive rules were inspected in code, including horizontal table/code scrolling and mobile guide navigation. Actual phone-browser testing remains unverified; desktop review is not a substitute for it.
+Local lint, TypeScript, nine tests and production build passed. Tests validate internal destinations and anchors across all twelve guides, image paths, OAuth connection instructions, endpoint validation, clipboard outcomes, product-tour keyboard navigation and independent checklist state.
-Vercel preview required a separate sign-in, so visual verification used the public production pages after the authorized merge.
+## Browser verification and limits
+
+The preceding public revision was reviewed at 1363 CSS pixels. The product images loaded, guide anchors and search worked, the endpoint builder produced the expected HTTPS MCP address and Copy placed it on the clipboard. There was no horizontal document overflow. The current journey revision requires a fresh public-page review after deployment; do not treat the preceding review as verification of this revision.
+
+The available browser does not expose mobile viewport emulation. Responsive rules at 1200, 950 and 700 pixels have been inspected, including local table/code scrolling, product-tour layout, accessible control sizes and collapsible guide navigation. Actual phone-browser testing remains unverified.
+
+Vercel preview requires a separate sign-in. Public production pages are available for visual verification after the authorized merge. This site revision does not establish that a separate fresh VPS installation or new ChatGPT plugin connection was completed.
diff --git a/app/articles/chatgpt-operated-my-deployment/page.tsx b/app/articles/chatgpt-operated-my-deployment/page.tsx
index 1a774cd..94a3c16 100644
--- a/app/articles/chatgpt-operated-my-deployment/page.tsx
+++ b/app/articles/chatgpt-operated-my-deployment/page.tsx
@@ -27,7 +27,7 @@ export default function ArticlePage() {
The useful moment was when the conversation could inspect my running
application, retrieve its deployment evidence and check the public
- result—all through a scoped connection.
+ result through a scoped connection.
Edward Kwabena Twumasi
@@ -247,8 +247,8 @@ export default function ArticlePage() {
Start the walkthrough ↗︎
-
- Already installed? Connect ChatGPT →
+
+ Already installed? Find your next step →
diff --git a/app/components/NextSteps.tsx b/app/components/NextSteps.tsx
new file mode 100644
index 0000000..aedddef
--- /dev/null
+++ b/app/components/NextSteps.tsx
@@ -0,0 +1,97 @@
+"use client";
+import Link from "next/link";
+import { useState } from "react";
+
+const paths = [
+ {
+ id: "existing",
+ title: "My apps already run here",
+ intro:
+ "Bring one existing workload into view before granting an agent access.",
+ steps: [
+ ["Confirm the active host and scan paths", "discovery#locations"],
+ ["Scan and inspect the correct candidate", "discovery#scan"],
+ ["Enrol it and verify its runtime identity", "first-deployment#enroll"],
+ ["Add its repository and public URL", "first-deployment#source"],
+ ["Connect ChatGPT and make a read-only call", "agent-access#first-call"],
+ ],
+ },
+ {
+ id: "new",
+ title: "I want to deploy a new app",
+ intro: "Use a template to validate, create and enroll a new deployment.",
+ steps: [
+ ["Choose a template for your application", "new-deployment#choose"],
+ ["Connect and verify the source", "new-deployment#source"],
+ ["Configure and review the proposed deployment", "new-deployment#review"],
+ ["Deploy and check the public result", "new-deployment#verify"],
+ ["Grant your agent access to that workload", "agent-access#consent"],
+ ],
+ },
+] as const;
+
+export default function NextSteps() {
+ const [path, setPath] = useState(0);
+ const [done, setDone] = useState([]);
+ const current = paths[path];
+ const completed = current.steps.filter((_, index) =>
+ done.includes(`${current.id}-${index}`),
+ ).length;
+ return (
+
+
+ {paths.map((item, i) => (
+ setPath(i)}
+ >
+ {item.title}
+ ↗︎
+
+ ))}
+
+
+
{current.intro}
+
+ {completed} / {current.steps.length} checked
+
+
+
+ {current.steps.map(([title, href], index) => {
+ const key = `${current.id}-${index}`;
+ return (
+
+
+
+ setDone((previous) =>
+ previous.includes(key)
+ ? previous.filter((item) => item !== key)
+ : [...previous, key],
+ )
+ }
+ />
+ {title}
+
+
+ Guide ↗︎
+
+
+ );
+ })}
+
+
+ Your checklist for this page session. Checking a box does not run an
+ action or verify a server.
+
+
+ );
+}
diff --git a/app/components/ProductTour.tsx b/app/components/ProductTour.tsx
new file mode 100644
index 0000000..068373c
--- /dev/null
+++ b/app/components/ProductTour.tsx
@@ -0,0 +1,118 @@
+"use client";
+
+import { useRef, useState } from "react";
+import Link from "next/link";
+import Capture from "./Capture";
+
+const stages = [
+ {
+ title: "Discover",
+ heading: "Start with what is already running.",
+ text: "GroundControl reads the active host, finds Compose folders and containers, and presents candidates you can inspect.",
+ fact: "A scan does not take ownership of a workload.",
+ file: "discovery-desktop.jpg",
+ alt: "GroundControl discovered workloads with source paths and Enrol deployment buttons",
+ href: "/docs/discovery",
+ link: "How discovery works",
+ },
+ {
+ title: "Enrol",
+ heading: "Choose what belongs in GroundControl.",
+ text: "Enrol one candidate, keep its files where they are, and optionally group it into a project. Confirm its source, runtime and public address.",
+ fact: "Tracking, managed deployment and agent permission are distinct.",
+ file: "enrollment-desktop.jpg",
+ alt: "GroundControl enrollment dialog showing optional project grouping",
+ href: "/docs/first-deployment",
+ link: "Follow the enrollment guide",
+ },
+ {
+ title: "Connect",
+ heading: "Give your agent a scoped connection.",
+ text: "Add your instance’s MCP endpoint in ChatGPT, sign in through OAuth and select the deployments the client may access.",
+ fact: "The grant names the capabilities and workloads.",
+ file: "agents-desktop.jpg",
+ alt: "GroundControl Agents workspace showing a real active ChatGPT grant",
+ href: "/docs/agent-access",
+ link: "Connect ChatGPT",
+ },
+ {
+ title: "Verify",
+ heading: "Follow the operation to its outcome.",
+ text: "Read runtime checks and the public endpoint result. A durable operation ID lets your agent return to the same work after a timeout.",
+ fact: "This capture shows a recorded release, not live status.",
+ file: "verification-desktop.jpg",
+ alt: "GroundControl runtime image verification and public endpoint evidence",
+ href: "/docs/evidence",
+ link: "Inspect the recorded evidence",
+ },
+] as const;
+
+export default function ProductTour() {
+ const [active, setActive] = useState(0);
+ const buttons = useRef<(HTMLButtonElement | null)[]>([]);
+ const stage = stages[active];
+ return (
+
+
+ {stages.map((item, index) => (
+ {
+ buttons.current[index] = node;
+ }}
+ type="button"
+ role="tab"
+ id={`tour-tab-${index}`}
+ aria-controls="tour-panel"
+ aria-selected={active === index}
+ tabIndex={active === index ? 0 : -1}
+ onClick={() => setActive(index)}
+ onKeyDown={(event) => {
+ let next = active;
+ if (event.key === "ArrowRight")
+ next = (active + 1) % stages.length;
+ else if (event.key === "ArrowLeft")
+ next = (active - 1 + stages.length) % stages.length;
+ else if (event.key === "Home") next = 0;
+ else if (event.key === "End") next = stages.length - 1;
+ else return;
+ event.preventDefault();
+ setActive(next);
+ buttons.current[next]?.focus();
+ }}
+ >
+ 0{index + 1}
+ {item.title}
+ ↗︎
+
+ ))}
+
+
+
+
Inside GroundControl
+
{stage.heading}
+
{stage.text}
+
{stage.fact}
+
+ {stage.link}
→
+
+
+
+
+
+ );
+}
diff --git a/app/components/SiteChrome.tsx b/app/components/SiteChrome.tsx
index 06c2de5..7bb5f07 100644
--- a/app/components/SiteChrome.tsx
+++ b/app/components/SiteChrome.tsx
@@ -4,20 +4,43 @@ export function Header() {
return (
-
- ◌
-
+
+
+
+
+
GroundControl
+ Product
+ Approach
Docs
Field notes
-
- View source ↗︎
-
-
- Connect ChatGPT ↗︎
+
+ Get started ↗︎
);
@@ -30,18 +53,20 @@ export function Footer() {
GroundControl
- A Serendepify product.
-
- Open source. Self-hosted. Yours.
+ Open-source operations for infrastructure you own.
+ A Serendepify product.
- Installation
+ After installation
Connect ChatGPT
+ The approach
Evidence
- View source ↗︎
+
+ View source ↗︎
+
- Built for infrastructure you own.
+ Safe, smooth, observable, agentic.
);
}
diff --git a/app/components/WorkloadModel.tsx b/app/components/WorkloadModel.tsx
new file mode 100644
index 0000000..fc6a75b
--- /dev/null
+++ b/app/components/WorkloadModel.tsx
@@ -0,0 +1,47 @@
+export default function WorkloadModel() {
+ return (
+
+
+ Example · one application, one deployment, several services
+
+
+ Project
+ Weekend planner
+ Optional organization
+
+
+
+ Deployment
+ weekend-production
+ On your VPS
+
+
+
+ webPublic application
+
+
+ apiApplication service
+
+
+ postgresPersistent data
+
+
+ redisCache / queue
+
+
+
+ Public route
+ app.example.com → web
+ Verified separately
+
+
+
+ An OAuth grant selects the deployment. Operations act on that identity
+ and record their results.
+
+
+ );
+}
diff --git a/app/docs/[slug]/page.tsx b/app/docs/[slug]/page.tsx
index 316b84f..885ade4 100644
--- a/app/docs/[slug]/page.tsx
+++ b/app/docs/[slug]/page.tsx
@@ -28,7 +28,15 @@ export default async function GuidePage({
const guide = guides[slug];
if (!guide) notFound();
const index = guideIndex.findIndex((g) => g.slug === slug);
- const next = guideIndex[index + 1];
+ const nextFor: Record = {
+ "after-install": "discovery",
+ publishing: "discovery",
+ "first-deployment": "agent-access",
+ "new-deployment": "agent-access",
+ };
+ const next = nextFor[slug]
+ ? guideIndex.find((item) => item.slug === nextFor[slug])
+ : guideIndex[index + 1];
return (
<>
diff --git a/app/docs/_components/DocsNav.tsx b/app/docs/_components/DocsNav.tsx
index 941ee5e..d475890 100644
--- a/app/docs/_components/DocsNav.tsx
+++ b/app/docs/_components/DocsNav.tsx
@@ -1,52 +1,80 @@
"use client";
import Link from "next/link";
import { useState } from "react";
-import { guideIndex } from "@/content/guide-index";
+import { guideGroups, guideIndex } from "@/content/guide-index";
export default function DocsNav({ current = "" }: { current?: string }) {
const [query, setQuery] = useState("");
- const matches = guideIndex.filter((g) =>
- `${g.title} ${g.description} ${g.keywords}`
+ const [expanded, setExpanded] = useState(false);
+ const matches = guideIndex.filter((guide) =>
+ `${guide.title} ${guide.description} ${guide.keywords}`
.toLowerCase()
.includes(query.toLowerCase()),
);
return (
- DOCUMENTATION
-
- Find a guide
-
- setQuery(e.target.value)}
- />
-
-
- Overview
-
- {matches.map((g) => (
+ setExpanded(!expanded)}
+ >
+ Browse documentation
+ {expanded ? "−" : "+"}
+
+
+
+ Find a guide
+
+
setQuery(e.target.value)}
+ />
+
- {g.title}
- {g.short}
+ Overview
- ))}
-
- {matches.length === 0 && (
-
- No guide found. Try “OAuth”, “install” or “health”.
-
- )}
-
- Start with a VPS.
-
- Finish with a verified agent connection.
+ {guideGroups.map((group) => {
+ const items = matches.filter((guide) => guide.group === group);
+ return (
+ items.length > 0 && (
+
+
{group}
+ {items.map((guide) => (
+
+ {guide.title}
+
+ ))}
+
+ )
+ );
+ })}
+
+ {matches.length === 0 && (
+
+ No guide found. Try “discovery”, “OAuth” or “health”.
+
+ )}
+
+ Your host. Your workloads.
+
+ Explicit access. Recorded outcomes.
+
);
diff --git a/app/docs/_components/EndpointBuilder.tsx b/app/docs/_components/EndpointBuilder.tsx
index 10b1539..9c16c03 100644
--- a/app/docs/_components/EndpointBuilder.tsx
+++ b/app/docs/_components/EndpointBuilder.tsx
@@ -44,7 +44,7 @@ export default function EndpointBuilder() {
{origin
? "Copy this into the MCP server URL field in ChatGPT."
- : "Example only — replace gc.example.com with your own hostname."}
+ : "Example only. Replace gc.example.com with your own hostname."}
);
diff --git a/app/docs/page.tsx b/app/docs/page.tsx
index f8a0de2..11a60b3 100644
--- a/app/docs/page.tsx
+++ b/app/docs/page.tsx
@@ -1,11 +1,11 @@
import Link from "next/link";
import { Header, Footer } from "@/app/components/SiteChrome";
import DocsNav from "./_components/DocsNav";
-import { guideIndex } from "@/content/guide-index";
+import { guideGroups, guideIndex } from "@/content/guide-index";
export const metadata = {
- title: "GroundControl docs — From VPS to connected agent",
+ title: "GroundControl docs | Understand, set up and operate",
description:
- "Follow the complete GroundControl installation, HTTPS publishing, ChatGPT plugin, OAuth consent and first deployment walkthrough.",
+ "Understand GroundControl, install it, discover and enroll workloads, connect ChatGPT through OAuth and MCP, and verify operations.",
};
export default function DocsPage() {
return (
@@ -15,55 +15,69 @@ export default function DocsPage() {
- GROUNDCONTROL DOCS
- From your VPS to your first agent operation.
+ The operator’s handbook
+
+ From your first scan
+
+ to a verified operation.
+
- Install the control plane, connect a workload, and give ChatGPT a
- verified path to operate it. Commands, expected results and
- recovery steps are all here.
+ Learn how GroundControl thinks about your infrastructure, then
+ follow the path that matches where you are.
-
+
-
START FROM SCRATCH
-
I have a VPS.
-
Install, claim and publish GroundControl.
-
Start installation →
+
01 / Start
+
I need to install it.
+
Install, claim and connect the local host.
+
Installation guide →
+
+
+
02 / Continue
+
It’s installed. Now what?
+
Review discovery and choose your first application.
+
Follow the next steps →
-
ALREADY INSTALLED?
-
Connect ChatGPT.
-
Add the MCP plugin and complete OAuth.
-
Open the connection guide →
+
03 / Connect
+
My workload is ready.
+
Add ChatGPT and approve its deployment scope.
+
Connect your agent →
-
-
-
The complete walkthrough
-
SET UP → OPERATE → MAINTAIN
+
+
Before the commands
+
+
Understand the GroundControl approach.
+
+ What is a deployment? How is a project different? What does an
+ agent actually control?
+
- {guideIndex.map((g, i) => (
-
-
- {String(i + 1).padStart(2, "0")}
+ ↗︎
+
+ {guideGroups.map((group) => (
+
+
+
{group}
+
+ {guideIndex.filter((g) => g.group === group).length} guides
-
-
{g.title}
-
{g.description}
-
-
↗︎
-
- ))}
-
-
-
What is being installed?
-
- GroundControl runs on your infrastructure. ChatGPT connects to
- your instance through MCP and OAuth. The public product site hosts
- these guides; each installation has its own login, credentials and
- grants.
-
-
+
+ {guideIndex
+ .filter((g) => g.group === group)
+ .map((g) => (
+
+
+
{g.title}
+
{g.description}
+
+ ↗︎
+
+ ))}
+
+ ))}
diff --git a/app/globals.css b/app/globals.css
index 6fae50f..f323cb9 100644
--- a/app/globals.css
+++ b/app/globals.css
@@ -1,16 +1,16 @@
@import "tailwindcss";
+
:root {
- --paper: #f4f3ed;
- --surface: #faf9f5;
- --ink: #202722;
- --muted: #626a60;
- --line: #d9ddd3;
- --dark: #141b17;
- --dark-line: #354037;
- --green: #30613b;
- --lime: #bfdf8f;
- --accent: #344d3a;
- --code: #dce7d6;
+ --paper: #f7f6f2;
+ --surface: #ffffff;
+ --ink: #202923;
+ --muted: #626a64;
+ --line: #dcdfd8;
+ --dark: #141c18;
+ --dark-line: #38473e;
+ --green: #326349;
+ --soft-green: #eaf0e8;
+ --code: #e1eadd;
--font-sans: var(--font-geist-sans);
--font-mono: var(--font-geist-mono);
}
@@ -19,7 +19,7 @@
}
html {
scroll-behavior: smooth;
- scroll-padding-top: 110px;
+ scroll-padding-top: 100px;
}
body {
margin: 0;
@@ -36,32 +36,27 @@ button,
input {
font: inherit;
}
-button,
-a,
-input,
-summary {
- -webkit-tap-highlight-color: transparent;
+button {
+ cursor: pointer;
+}
+button:disabled {
+ cursor: default;
}
a,
button {
touch-action: manipulation;
}
-button {
- cursor: pointer;
-}
a:focus-visible,
button:focus-visible,
input:focus-visible,
summary:focus-visible,
-pre:focus-visible {
+pre:focus-visible,
+[tabindex]:focus-visible {
outline: 3px solid var(--green);
outline-offset: 5px;
}
-button:disabled {
- cursor: default;
-}
::selection {
- background: var(--lime);
+ background: #d5e6d6;
color: var(--ink);
}
h1,
@@ -82,7 +77,7 @@ img {
height: auto;
}
.wrap {
- width: min(1400px, calc(100% - 96px));
+ width: min(1264px, calc(100% - 96px));
margin-inline: auto;
}
.eyebrow,
@@ -90,21 +85,21 @@ img {
font-family: var(--font-mono), monospace;
font-size: 11px;
font-weight: 500;
- letter-spacing: 0.08em;
+ letter-spacing: 0.045em;
}
.eyebrow {
color: var(--muted);
- line-height: 1.6;
+ line-height: 1.7;
}
.small {
+ color: var(--muted);
font-size: 13px;
line-height: 1.7;
- color: var(--muted);
}
.lead {
- font-size: 19px;
- line-height: 1.65;
color: var(--muted);
+ font-size: 20px;
+ line-height: 1.6;
}
.sr-only {
position: absolute;
@@ -117,599 +112,821 @@ img {
white-space: nowrap;
border: 0;
}
-.text-link {
- font-size: 14px;
- font-weight: 550;
- border-bottom: 1px solid var(--line);
- padding: 8px 0;
- display: inline-block;
+.skip-link {
+ position: fixed;
+ left: 20px;
+ top: -100px;
+ z-index: 99;
+ padding: 14px 22px;
+ background: var(--ink);
+ color: white;
}
-.text-link:hover {
- border-color: var(--green);
- color: var(--green);
+.skip-link:focus {
+ top: 12px;
+}
+.status-dot {
+ display: inline-block;
+ width: 7px;
+ height: 7px;
+ background: var(--green);
+ border-radius: 50%;
+ margin-right: 9px;
}
.button {
display: inline-flex;
align-items: center;
justify-content: space-between;
- gap: 36px;
+ gap: 30px;
+ min-height: 48px;
+ padding: 15px 21px;
background: var(--ink);
- color: var(--paper);
- padding: 17px 21px;
+ color: white;
+ border: 1px solid var(--ink);
+ border-radius: 4px;
font-size: 14px;
font-weight: 500;
- border: 1px solid var(--ink);
- border-radius: 3px;
- min-height: 48px;
- transition: background 0.18s;
+ transition: background 0.2s;
}
.button:hover {
background: var(--green);
}
.button.compact {
- padding: 11px 15px;
- gap: 25px;
- font-size: 12px;
min-height: 42px;
+ font-size: 12px;
+ padding: 10px 16px;
}
-.actions {
- display: flex;
- gap: 26px;
- align-items: center;
- flex-wrap: wrap;
-}
-.status-dot {
+.text-link {
display: inline-block;
- width: 7px;
- height: 7px;
- border-radius: 50%;
- background: var(--green);
- margin-right: 8px;
+ padding: 8px 0;
+ border-bottom: 1px solid var(--line);
+ font-size: 14px;
+ font-weight: 550;
}
-.good {
+.text-link:hover {
color: var(--green);
+ border-color: var(--green);
}
-.site-header {
- height: 83px;
- padding: 0 48px;
+.actions {
display: flex;
+ flex-wrap: wrap;
+ gap: 18px 25px;
align-items: center;
+}
+.site-header {
+ min-height: 82px;
+ padding: 0 max(32px, calc((100% - 1264px) / 2));
+ display: flex;
justify-content: space-between;
+ align-items: center;
gap: 28px;
- border-bottom: 1px solid var(--line);
position: sticky;
top: 0;
z-index: 30;
background: var(--paper);
+ border-bottom: 1px solid var(--line);
}
.wordmark {
display: inline-flex;
align-items: center;
- gap: 10px;
- font-size: 18px;
- font-weight: 600;
- letter-spacing: -0.7px;
+ gap: 11px;
white-space: nowrap;
+ font-size: 18px;
+ font-weight: 650;
+ letter-spacing: -0.65px;
}
.brand-mark {
- font-size: 37px;
- line-height: 1;
- font-weight: 400;
+ flex-shrink: 0;
}
.site-header nav {
display: flex;
- gap: 30px;
align-items: center;
+ gap: 28px;
font-size: 13px;
}
+.site-header nav a {
+ padding-block: 14px;
+}
.site-header nav a:hover {
color: var(--green);
}
.site-footer {
display: grid;
- grid-template-columns: 1fr 1fr auto;
- gap: 40px;
- border-top: 1px solid var(--line);
- padding-block: 54px 64px;
+ grid-template-columns: 1.25fr 1fr;
+ gap: 36px;
+ padding-block: 56px 28px;
}
.site-footer p {
+ margin-top: 15px;
font-size: 13px;
color: var(--muted);
- line-height: 1.7;
- margin-top: 17px;
+ line-height: 1.8;
}
.site-footer nav {
display: grid;
grid-template-columns: 1fr 1fr;
- align-content: start;
- gap: 18px;
+ gap: 12px 26px;
font-size: 13px;
}
+.site-footer nav a {
+ padding-block: 7px;
+}
+.site-footer nav a:hover {
+ color: var(--green);
+}
.site-footer > .mono {
+ grid-column: 1/-1;
+ padding-top: 26px;
+ border-top: 1px solid var(--line);
color: var(--muted);
font-size: 10px;
}
-.hero {
- padding-block: 90px 84px;
+
+/* Product story */
+.landing-hero {
display: grid;
- grid-template-columns: 1fr 1.18fr;
- gap: 58px;
- align-items: center;
+ grid-template-columns: 1.15fr 1fr;
+ gap: 8%;
+ padding-block: 78px 70px;
+ align-items: end;
}
-.hero h1 {
- font-size: clamp(49px, 4.8vw, 76px);
- line-height: 1.04;
- letter-spacing: -0.06em;
- margin: 27px 0;
+.hero-heading h1 {
+ font-size: clamp(54px, 5.65vw, 79px);
+ line-height: 1.05;
+ letter-spacing: -0.063em;
+ margin-top: 25px;
}
-.hero h1 em {
+.hero-heading em {
font-style: normal;
color: var(--green);
}
-.hero .lead {
- max-width: 495px;
- font-size: 17px;
- line-height: 1.8;
+.hero-intro {
+ padding-bottom: 5px;
}
-.hero .actions {
- margin-top: 31px;
+.hero-intro .lead {
+ color: var(--ink);
+ font-size: clamp(21px, 2vw, 27px);
+ letter-spacing: -0.025em;
+ line-height: 1.45;
}
-.hero-note {
- margin-top: 28px;
+.hero-intro > p + p {
+ font-size: 15px;
color: var(--muted);
- font-family: var(--font-mono);
+ line-height: 1.85;
+ margin-top: 22px;
+}
+.hero-intro .actions {
+ margin-top: 27px;
+ gap: 13px 22px;
+}
+.hero-intro .text-link {
+ font-size: 12px;
+}
+.hero-spec {
+ margin-top: 26px;
+ display: flex;
+ flex-wrap: wrap;
+ gap: 10px 19px;
font-size: 10px;
- line-height: 1.8;
+ font-family: var(--font-mono);
+ color: var(--muted);
}
-.hero-note span {
- padding-inline: 8px;
+.hero-spec span + span {
+ border-left: 1px solid var(--line);
+ padding-left: 19px;
}
-.proof-label {
+.section-label {
display: flex;
justify-content: space-between;
- gap: 14px;
- margin-bottom: 16px;
+ align-items: center;
+ gap: 25px;
+ margin-bottom: 24px;
+}
+.section-label .small {
font-size: 11px;
- color: var(--muted);
}
-.hero-proof {
- min-width: 0;
+.tour-section {
+ padding-bottom: 85px;
+ scroll-margin-top: 105px;
}
-.hero-proof .capture {
- background: var(--dark);
- padding: 9px;
+.product-tour {
border: 1px solid var(--dark-line);
- border-radius: 5px;
+ border-radius: 8px;
+ background: var(--dark);
+ color: #edf1e8;
+ overflow: hidden;
}
-.hero-proof .capture figcaption {
- color: #c2cbbc;
- font-size: 11px;
- border: 0;
- padding: 15px 9px 7px;
+.tour-tabs {
+ display: grid;
+ grid-template-columns: repeat(4, minmax(0, 1fr));
+ border-bottom: 1px solid var(--dark-line);
}
-.hero-proof-foot {
- padding-top: 17px;
+.tour-tabs button {
+ padding: 21px 23px;
+ min-height: 65px;
display: flex;
+ align-items: center;
+ gap: 17px;
+ font-size: 15px;
+ color: #bec9be;
+ border-bottom: 2px solid transparent;
+ text-align: left;
+}
+.tour-tabs button + button {
+ border-left: 1px solid var(--dark-line);
+}
+.tour-tabs button > span:first-child {
+ font-family: var(--font-mono);
+ font-size: 10px;
+ color: #a0afa4;
+}
+.tour-tabs button > span:last-child {
+ margin-left: auto;
+ font-size: 14px;
+}
+.tour-tabs button[aria-selected="true"] {
+ color: white;
+ background: #26372d;
+ border-bottom-color: #97bd9b;
+}
+.product-tour :focus-visible {
+ outline-color: #b5d2b5;
+ outline-offset: -5px;
+}
+.tour-panel {
+ display: grid;
+ grid-template-columns: 285px minmax(0, 1fr);
gap: 25px;
- justify-content: space-between;
- color: var(--muted);
- font-size: 11px;
- line-height: 1.5;
+ align-items: center;
+ padding: 32px 26px 25px 32px;
+ min-height: 555px;
}
-.hero-proof-foot b {
- color: var(--ink);
- font-weight: 600;
+.tour-copy .eyebrow {
+ color: #a3b4a6;
+ font-size: 10px;
}
-.hero-proof-foot a {
- color: var(--green);
+.tour-copy h2 {
+ font-size: 30px;
+ line-height: 1.2;
+ letter-spacing: -0.035em;
+ margin-top: 20px;
+}
+.tour-copy > p:not(.eyebrow) {
+ font-size: 14px;
+ line-height: 1.85;
+ margin-top: 19px;
+ color: #bcc8bd;
+}
+.tour-copy .tour-fact {
+ border-top: 1px solid var(--dark-line);
+ padding-top: 18px;
+ margin-top: 26px !important;
+ font-size: 12px !important;
+}
+.tour-copy > a {
+ display: inline-block;
+ font-size: 13px;
+ color: white;
+ border-bottom: 1px solid #64876a;
+ padding: 12px 0 6px;
+ margin-top: 12px;
+}
+.tour-panel .capture {
+ margin: 0;
+}
+.tour-panel .capture > a {
+ border-color: var(--dark-line);
+}
+.tour-panel .capture figcaption {
+ color: #aab9ae;
+ font-size: 10px;
}
.capture {
min-width: 0;
}
.capture > a {
display: block;
- cursor: zoom-in;
+ overflow: hidden;
+ border: 1px solid var(--line);
+ border-radius: 4px;
+ background: var(--dark);
}
.capture img {
width: 100%;
height: auto;
- border: 1px solid var(--dark-line);
- border-radius: 3px;
}
.capture figcaption {
display: flex;
justify-content: space-between;
+ align-items: flex-start;
gap: 20px;
font-size: 12px;
- line-height: 1.65;
+ line-height: 1.7;
color: var(--muted);
padding: 13px 0 0;
}
.capture figcaption a {
- white-space: nowrap;
- font-weight: 600;
-}
-.capture figcaption span {
- max-width: 850px;
+ flex-shrink: 0;
+ font-weight: 550;
+ min-height: 30px;
}
-.principles {
- padding: 64px 0;
- border-top: 1px solid var(--line);
- display: grid;
- grid-template-columns: 1fr 2.3fr;
- gap: 40px;
+.capture figcaption a:hover {
+ text-decoration: underline;
}
-.principles > div {
+.section-heading {
display: grid;
- grid-template-columns: 1fr 1fr;
- gap: 55px;
-}
-.principles h2 {
- font-size: 37px;
+ grid-template-columns: 1.2fr 1fr;
+ gap: 12%;
+ align-items: end;
+ margin-bottom: 39px;
+}
+.section-heading h2,
+.philosophy-grid h2,
+.learning-section h2,
+.start-grid h2 {
+ font-size: clamp(34px, 3.4vw, 48px);
+ line-height: 1.12;
letter-spacing: -0.045em;
- line-height: 1.17;
+ margin-top: 18px;
}
-.principles p:not(.eyebrow) {
- font-size: 15px;
+.section-heading > p {
+ font-size: 16px;
line-height: 1.8;
color: var(--muted);
+ max-width: 460px;
}
-.walkthrough {
- padding: 42px 0 90px;
+.onboarding-story {
+ padding-block: 16px 82px;
}
-.section-label {
- display: flex;
- justify-content: space-between;
- align-items: center;
- gap: 25px;
- margin-bottom: 30px;
+.route-list {
+ border-top: 1px solid var(--line);
+ margin-bottom: 22px;
}
-.journey-grid {
+.route-list > a {
display: grid;
- grid-template-columns: repeat(3, 1fr);
- border-top: 1px solid var(--line);
+ grid-template-columns: 45px 1fr 1.25fr 25px;
+ gap: 30px;
+ padding: 28px 0;
+ align-items: center;
border-bottom: 1px solid var(--line);
}
-.journey-grid > a {
- padding: 32px 35px 35px 0;
- border-right: 1px solid var(--line);
-}
-.journey-grid > a + a {
- padding-left: 35px;
-}
-.journey-grid > a:last-child {
- border-right: 0;
-}
-.journey-grid > a:hover h3 {
+.route-number {
+ font-family: var(--font-mono);
+ font-size: 12px;
color: var(--green);
}
-.journey-grid .mono {
- display: flex;
- justify-content: space-between;
- color: var(--muted);
-}
-.journey-grid h3 {
- font-size: 25px;
- letter-spacing: -0.03em;
- margin: 38px 0 12px;
+.route-list h3 {
+ font-size: 23px;
+ letter-spacing: -0.035em;
}
-.journey-grid p {
+.route-list p {
font-size: 14px;
line-height: 1.75;
color: var(--muted);
- max-width: 285px;
}
-.evidence-section {
- padding: 75px 0 85px;
- background: #e7eade;
- border-block: 1px solid #d1d8c7;
-}
-.evidence-heading {
- display: grid;
- grid-template-columns: 1fr 1fr;
- gap: 90px;
- margin: 42px 0;
-}
-.evidence-heading h2,
-.story-teaser h2,
-.install-section h2 {
- font-size: clamp(36px, 3.4vw, 52px);
- letter-spacing: -0.05em;
- line-height: 1.12;
+.route-list a:hover h3 {
+ color: var(--green);
}
-.evidence-heading > p {
- font-size: 16px;
- line-height: 1.8;
- max-width: 440px;
- color: var(--muted);
+.philosophy-band {
+ background: #e9eee7;
+ border-block: 1px solid #d4ded1;
+ padding-block: 75px;
}
-.evidence-layout {
+.philosophy-grid {
display: grid;
- grid-template-columns: 1.9fr 1fr;
- gap: 45px;
+ grid-template-columns: 1fr 1.05fr;
+ gap: 10%;
+ align-items: center;
}
-.evidence-receipt {
- padding: 23px 0 0;
+.philosophy-grid > div > p:not(.eyebrow) {
+ font-size: 15px;
+ color: #536151;
+ line-height: 1.85;
+ margin-top: 22px;
}
-.evidence-receipt h3 {
- font-size: 26px;
- margin: 13px 0 22px;
- letter-spacing: -0.03em;
+.philosophy-grid .text-link {
+ border-color: #bdcbbc;
+ margin-top: 20px;
}
-.evidence-receipt dl {
- margin: 0;
+.workload-model {
+ padding: 25px;
+ border: 1px solid #c7d4c5;
+ border-radius: 5px;
+ background: #f7faf3;
}
-.evidence-receipt dl > div {
- display: flex;
- justify-content: space-between;
- gap: 20px;
- border-bottom: 1px solid #ccd3c3;
- padding: 13px 0;
- font-size: 13px;
+.workload-model figcaption {
+ font-size: 11px;
+ line-height: 1.6;
+ color: #52694f;
+ padding-bottom: 22px;
}
-.evidence-receipt dt {
- color: var(--muted);
+.model-row {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 7px 18px;
+ padding: 17px;
+ background: white;
+ border: 1px solid #d0dccb;
+ align-items: center;
}
-.evidence-receipt dd {
- margin: 0;
+.model-row > span {
+ font-size: 10px;
font-family: var(--font-mono);
- font-size: 12px;
+ color: #63715e;
+ width: 80px;
}
-.evidence-receipt .code-block {
- margin-top: 26px;
-}
-.evidence-receipt .small {
- margin: 17px 0;
-}
-.evidence-receipt > a {
+.model-row strong {
font-size: 13px;
- text-decoration: underline;
- text-underline-offset: 4px;
+ font-weight: 550;
+}
+.model-row small {
+ width: 100%;
+ padding-left: 98px;
+ color: #687362;
+ font-size: 10px;
}
-.story-teaser {
- padding-block: 85px;
+.model-deployment {
+ border-left: 1px solid #b6c8b2;
+ margin: 0 0 0 15px;
+ padding: 20px 0 0 17px;
+}
+.model-services {
display: grid;
- grid-template-columns: 0.7fr 2fr auto;
- gap: 35px;
- border-bottom: 1px solid var(--line);
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 9px;
+ padding: 13px 0;
}
-.story-teaser p:not(.eyebrow) {
- margin: 23px 0 16px;
- max-width: 480px;
- font-size: 16px;
+.model-services > span {
+ border: 1px solid #d0dccb;
+ padding: 13px;
+ font-family: var(--font-mono);
+ font-size: 12px;
+ background: #eaf1e4;
+}
+.model-services small {
+ display: block;
+ font-family: var(--font-sans);
+ font-size: 10px;
+ color: #586b52;
+ margin-top: 7px;
+}
+.model-route {
+ background: transparent;
+}
+.workload-model > p {
+ font-size: 12px;
+ color: #52654b;
line-height: 1.8;
+ margin-top: 20px;
+}
+.proof-story {
+ padding-block: 84px 0;
+}
+.proof-grid {
+ display: grid;
+ grid-template-columns: minmax(0, 1.85fr) minmax(0, 1fr);
+ gap: 48px;
+ align-items: center;
+}
+.proof-details {
+ padding: 10px 0;
+}
+.proof-details h3 {
+ font-size: 27px;
+ letter-spacing: -0.03em;
+ margin-top: 18px;
+}
+.proof-details dl {
+ margin-block: 23px;
+}
+.proof-details dl > div {
+ display: flex;
+ justify-content: space-between;
+ gap: 20px;
+ border-bottom: 1px solid var(--line);
+ padding: 14px 0;
+ font-size: 12px;
+}
+.proof-details dt {
+ color: var(--muted);
+}
+.proof-details dd {
+ margin: 0;
+ font-weight: 550;
+}
+.proof-details > p:not(.eyebrow) {
color: var(--muted);
+ font-size: 13px;
+ line-height: 1.8;
}
-.story-mark {
- font-size: 80px;
+.proof-details > a {
+ display: block;
+ font-size: 13px;
color: var(--green);
- line-height: 1;
+ padding: 13px 0 0;
}
-.install-section {
- padding-block: 85px 95px;
+.article-strip {
display: grid;
- grid-template-columns: 1fr 1.2fr;
- gap: 80px;
+ grid-template-columns: 0.6fr 1.6fr auto;
+ gap: 25px;
align-items: center;
+ border-block: 1px solid var(--line);
+ padding: 30px 0;
+ margin-top: 50px;
}
-.install-section h2 {
- margin: 20px 0;
+.article-strip h3 {
+ font-size: 23px;
+ letter-spacing: -0.035em;
+ line-height: 1.4;
}
-.install-section p:not(.eyebrow) {
- max-width: 465px;
- margin: 15px 0;
- line-height: 1.7;
+.article-strip > span:last-child {
+ font-size: 12px;
+}
+.article-strip:hover h3 {
+ color: var(--green);
+}
+.learning-section {
+ display: grid;
+ grid-template-columns: 0.95fr 1.1fr;
+ gap: 11%;
+ padding-block: 88px;
+}
+.learning-section > div > p:not(.eyebrow) {
+ color: var(--muted);
+ font-size: 15px;
+ line-height: 1.8;
+ margin: 22px 0;
+ max-width: 440px;
+}
+.learning-links > a {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ gap: 25px;
+ border-top: 1px solid var(--line);
+ padding: 22px 0;
+}
+.learning-links > a:last-child {
+ border-bottom: 1px solid var(--line);
+}
+.learning-links h3 {
+ font-size: 18px;
+ letter-spacing: -0.025em;
+}
+.learning-links p {
color: var(--muted);
+ font-size: 13px;
+ line-height: 1.7;
+ margin-top: 7px;
+}
+.learning-links a:hover h3 {
+ color: var(--green);
+}
+.start-section {
+ background: var(--dark);
+ color: #f0f3ed;
+ padding-block: 65px;
}
+.start-grid {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 9%;
+ align-items: center;
+}
+.start-grid .eyebrow,
+.start-grid .small {
+ color: #a5b5a7;
+}
+.start-grid h2 {
+ font-size: 39px;
+}
+.start-grid > div > p:not(.eyebrow) {
+ color: #b9c8bc;
+ font-size: 14px;
+ line-height: 1.8;
+ margin-top: 20px;
+}
+.start-grid .button {
+ margin-top: 27px;
+ background: #eef2e9;
+ color: var(--ink);
+ border-color: #eef2e9;
+}
+.start-grid .code-block {
+ border-color: #425447;
+}
+
+/* Documentation controls and evidence */
.code-block {
- position: relative;
background: var(--dark);
color: var(--code);
border: 1px solid var(--dark-line);
border-radius: 4px;
overflow: hidden;
- margin: 22px 0;
+ min-width: 0;
+ position: relative;
}
.code-toolbar {
display: flex;
- align-items: center;
justify-content: space-between;
+ align-items: center;
gap: 16px;
border-bottom: 1px solid var(--dark-line);
- padding: 9px 16px;
+ padding: 9px 17px;
+ color: #b7c5b7;
font-family: var(--font-mono);
font-size: 10px;
- letter-spacing: 0.025em;
- color: #b6c2b0;
- min-height: 42px;
+ min-height: 48px;
}
.code-toolbar button {
- border: 0;
- background: transparent;
- color: var(--code);
- padding: 6px;
- min-height: 28px;
- font-size: 11px;
+ min-height: 32px;
+ min-width: 60px;
+ padding: 4px 0 4px 10px;
+ text-align: right;
}
.code-toolbar button:hover {
- color: var(--lime);
+ color: white;
+}
+.code-toolbar button:focus-visible,
+.code-block pre:focus-visible {
+ outline-color: #d4e7ce;
+ outline-offset: -4px;
}
.code-block pre {
+ font-family: var(--font-mono);
+ padding: 22px 18px;
margin: 0;
- padding: 22px 20px;
- overflow: auto;
- line-height: 1.85;
font-size: 12px;
- font-family: var(--font-mono);
- tab-size: 2;
+ line-height: 1.85;
+ white-space: pre;
+ overflow-x: auto;
}
-.code-block pre code {
- background: none;
- color: inherit;
- padding: 0;
- border: 0;
+.code-block code {
font: inherit;
- overflow-wrap: normal;
- white-space: pre;
+ background: none !important;
+ border: 0 !important;
+ padding: 0 !important;
+ color: inherit !important;
}
-.copy-status {
+.copy-status:not(:empty) {
display: block;
- color: var(--lime);
font-size: 11px;
- padding-inline: 20px;
+ padding: 0 18px 15px;
+ color: #c8dec0;
}
-.copy-status:not(:empty) {
- padding-bottom: 12px;
+.prose .code-block {
+ margin-block: 23px;
}
.endpoint-builder {
padding: 23px;
border: 1px solid var(--line);
background: var(--surface);
- margin: 26px 0;
+ border-radius: 5px;
+ margin: 25px 0;
}
.endpoint-builder label {
display: block;
font-size: 14px;
font-weight: 600;
- margin-bottom: 10px;
+ margin-bottom: 12px;
}
.endpoint-builder input {
width: 100%;
- padding: 13px 15px;
- border: 1px solid #929c8b;
+ min-height: 48px;
+ padding: 13px;
+ border: 1px solid #b6c0b5;
border-radius: 3px;
- background: white;
color: var(--ink);
font-family: var(--font-mono);
- font-size: 13px;
+ font-size: 14px;
}
.endpoint-builder > p {
font-size: 13px !important;
- line-height: 1.65 !important;
- color: var(--muted);
- margin-top: 12px !important;
+ line-height: 1.7 !important;
+ margin-top: 13px !important;
}
.endpoint-builder .input-error {
- color: #a53b2b !important;
+ color: #a03f2e !important;
}
.endpoint-builder .code-block {
- margin-bottom: 12px;
+ margin-bottom: 13px;
}
.docs-layout {
display: grid;
- grid-template-columns: 215px minmax(0, 1fr) 180px;
- gap: 46px;
- padding-block: 45px 90px;
+ grid-template-columns: 200px minmax(0, 1fr) 168px;
+ gap: 35px;
align-items: start;
- max-width: 1440px;
+ padding-block: 46px 85px;
}
.docs-sidebar {
position: sticky;
- top: 117px;
- max-height: calc(100svh - 142px);
+ top: 110px;
+ max-height: calc(100svh - 135px);
overflow-y: auto;
- padding-right: 8px;
+ padding-right: 5px;
}
-.docs-sidebar .eyebrow {
- font-size: 10px;
- margin-bottom: 18px;
+.docs-toggle {
+ display: none;
}
.docs-sidebar input {
width: 100%;
+ min-height: 44px;
padding: 11px 12px;
+ font-size: 12px;
border: 1px solid var(--line);
+ border-radius: 4px;
background: var(--surface);
- border-radius: 3px;
- font-size: 12px;
- color: var(--ink);
- margin-bottom: 22px;
+ margin-bottom: 17px;
}
.docs-sidebar nav {
display: flex;
flex-direction: column;
- gap: 4px;
+ gap: 8px;
}
.docs-sidebar nav a {
- padding: 10px 12px;
- font-size: 13px;
- color: var(--muted);
+ display: block;
+ padding: 10px 11px;
+ font-size: 12px;
+ line-height: 1.5;
border-left: 2px solid transparent;
- display: flex;
- flex-direction: column;
- gap: 5px;
-}
-.docs-sidebar nav a small {
- font-size: 10px;
color: var(--muted);
}
.docs-sidebar nav a:hover {
- background: #e9ecdf;
+ color: var(--ink);
+ background: #ecefe7;
}
.docs-sidebar nav a.active {
- background: #e5eadc;
- border-color: var(--green);
- color: var(--ink);
+ border-left-color: var(--green);
+ color: var(--green);
+ background: var(--soft-green);
font-weight: 600;
}
+.nav-group {
+ margin-top: 13px;
+}
+.nav-group > p {
+ font-size: 10px;
+ font-family: var(--font-mono);
+ color: #737a72;
+ padding: 7px 12px 8px;
+}
.sidebar-note {
- border-top: 1px solid var(--line);
- padding-top: 25px;
- margin: 25px 12px;
color: var(--muted);
- font-size: 12px;
+ font-size: 11px;
line-height: 1.8;
+ margin-top: 25px;
+ padding: 20px 12px 0;
+ border-top: 1px solid var(--line);
}
.doc-main {
min-width: 0;
- max-width: 800px;
+ max-width: 820px;
}
.breadcrumb {
display: flex;
- gap: 10px;
- font-size: 11px;
+ flex-wrap: wrap;
+ gap: 9px;
color: var(--muted);
- margin-bottom: 32px;
+ font-size: 11px;
+ margin-bottom: 27px;
}
.doc-header {
- padding: 0 0 35px;
+ padding-bottom: 32px;
border-bottom: 1px solid var(--line);
}
.doc-header h1 {
- font-size: clamp(34px, 3.4vw, 50px);
- line-height: 1.13;
- letter-spacing: -0.045em;
+ font-size: clamp(34px, 3.3vw, 48px);
+ line-height: 1.12;
+ letter-spacing: -0.046em;
margin: 18px 0;
}
.doc-header .lead {
- font-size: 16px;
- line-height: 1.8;
+ font-size: 17px;
+ line-height: 1.7;
}
.outcome {
display: flex;
- gap: 25px;
- align-items: flex-start;
- background: #e8edde;
+ flex-direction: column;
+ gap: 7px;
+ padding: 17px 20px;
+ background: var(--soft-green);
border-left: 2px solid var(--green);
- padding: 19px 20px;
- margin-top: 27px;
+ margin-top: 26px;
}
.outcome span {
+ color: var(--green);
font-family: var(--font-mono);
font-size: 9px;
- color: var(--green);
- width: 115px;
- flex-shrink: 0;
- line-height: 1.8;
+ letter-spacing: 0.045em;
}
.outcome p {
font-size: 13px;
- line-height: 1.7;
+ line-height: 1.8;
}
.toc {
position: sticky;
- top: 128px;
+ top: 122px;
border-left: 1px solid var(--line);
- padding-left: 18px;
+ padding-left: 17px;
}
.toc .eyebrow {
font-size: 9px;
@@ -717,12 +934,12 @@ img {
.toc nav {
display: flex;
flex-direction: column;
- gap: 15px;
- margin: 20px 0 30px;
+ gap: 16px;
+ margin: 22px 0 30px;
}
.toc a {
font-size: 11px;
- line-height: 1.6;
+ line-height: 1.65;
color: var(--muted);
}
.toc a:hover {
@@ -735,28 +952,29 @@ img {
display: none;
}
.prose section {
- padding-top: 44px;
- scroll-margin-top: 115px;
+ padding-top: 43px;
+ scroll-margin-top: 110px;
}
.prose h2 {
font-size: 27px;
+ line-height: 1.25;
letter-spacing: -0.035em;
- line-height: 1.3;
margin-bottom: 21px;
}
.prose h3 {
- font-size: 20px;
- line-height: 1.4;
- margin: 25px 0 15px;
+ font-size: 21px;
+ line-height: 1.35;
+ margin: 25px 0 14px;
+ letter-spacing: -0.025em;
}
.prose p,
.prose li {
- font-size: 15px;
- line-height: 1.9;
- color: #495247;
+ font-size: 16px;
+ line-height: 1.8;
+ color: #4c584e;
}
.prose p + p {
- margin-top: 18px;
+ margin-top: 19px;
}
.prose strong {
color: var(--ink);
@@ -768,130 +986,140 @@ img {
text-decoration: underline;
text-underline-offset: 3px;
}
-.prose ol,
.prose ul {
+ list-style: disc;
+}
+.prose ol {
+ list-style: decimal;
+}
+.prose ul,
+.prose ol {
padding-left: 23px;
- margin: 20px 0;
+ margin-block: 21px;
}
.prose li {
- padding-left: 6px;
- margin-bottom: 12px;
+ padding-left: 5px;
+ margin-bottom: 13px;
}
.prose code {
font-family: var(--font-mono);
font-size: 0.82em;
- background: #e7eadf;
- border: 1px solid #d9dfd0;
- border-radius: 3px;
+ background: #eaf0e5;
+ color: #375534;
+ border: 1px solid #dbe4d6;
padding: 2px 4px;
+ border-radius: 3px;
overflow-wrap: anywhere;
- color: #345033;
-}
-.prose .code-block code {
- color: var(--code);
}
-.prose .capture {
- margin: 28px 0;
+.prose .capture,
+.prose .workload-model {
+ margin-block: 28px;
}
.prose .small {
- font-size: 12px;
+ font-size: 13px;
line-height: 1.75;
}
+.prose .workload-model > p {
+ font-size: 12px;
+}
.callout {
- border-left: 2px solid #b6c1ad;
- background: #eaede3;
- padding: 19px 22px;
- margin: 25px 0;
+ padding: 19px 21px;
+ border-left: 2px solid #adbda5;
+ background: #eff1e9;
+ margin-block: 26px;
}
.callout > strong {
- font-size: 12px;
display: block;
- margin-bottom: 8px;
+ font-size: 12px;
+ font-weight: 650;
+ margin-bottom: 9px;
color: var(--ink);
}
.callout p,
.callout li {
- font-size: 13px;
+ font-size: 14px;
line-height: 1.8;
}
.callout.check {
- background: #e6eddc;
+ background: var(--soft-green);
border-color: var(--green);
}
.table-scroll {
overflow-x: auto;
- margin: 22px 0 26px;
border: 1px solid var(--line);
- border-radius: 3px;
+ border-radius: 4px;
+ margin: 25px 0;
}
table {
- border-collapse: collapse;
width: 100%;
+ border-collapse: collapse;
font-size: 13px;
- line-height: 1.65;
+ line-height: 1.7;
}
-th {
+th,
+td {
+ padding: 14px 17px;
text-align: left;
- background: #e9ece1;
- font-weight: 550;
- color: #3a4536;
-}
-td,
-th {
- padding: 13px 15px;
vertical-align: top;
border-bottom: 1px solid var(--line);
- overflow-wrap: anywhere;
+}
+th {
+ font-weight: 600;
+ background: #eaf0e5;
+ color: #42553d;
}
td {
- color: #505b4c;
+ color: #4f5c4c;
}
td + td,
th + th {
border-left: 1px solid var(--line);
}
+td:first-child {
+ font-weight: 500;
+ color: var(--ink);
+ min-width: 145px;
+}
tr:last-child td {
border-bottom: 0;
}
-th:first-child {
- min-width: 155px;
-}
.scope-map {
display: grid;
- grid-template-columns: repeat(3, 1fr);
- gap: 1px;
- background: var(--line);
+ grid-template-columns: repeat(3, minmax(0, 1fr));
border: 1px solid var(--line);
- margin: 27px 0;
+ margin-block: 25px;
}
.scope-map > div {
- padding: 20px 15px;
display: flex;
flex-direction: column;
- gap: 9px;
- background: var(--surface);
+ gap: 10px;
+ padding: 19px;
+ background: white;
+}
+.scope-map > div + div {
+ border-left: 1px solid var(--line);
}
.scope-map span {
+ font-size: 10px;
font-family: var(--font-mono);
- font-size: 9px;
color: var(--green);
}
.scope-map strong {
font-size: 13px;
}
.scope-map small {
- font-size: 11px;
+ font-size: 12px;
color: var(--muted);
- line-height: 1.65;
+ line-height: 1.6;
}
.doc-next {
- border-top: 1px solid var(--line);
- margin-top: 50px;
- padding-top: 26px;
display: flex;
- justify-content: space-between;
align-items: center;
+ justify-content: space-between;
gap: 30px;
+ padding-top: 25px;
+ border-top: 1px solid var(--line);
+ margin-top: 40px;
font-size: 13px;
}
.doc-next > a:last-child {
@@ -899,144 +1127,292 @@ th:first-child {
}
.doc-next small {
display: block;
- color: var(--muted);
- font-family: var(--font-mono);
font-size: 9px;
- margin-bottom: 8px;
+ font-family: var(--font-mono);
+ color: var(--muted);
+ margin-bottom: 7px;
}
.docs-index {
- grid-template-columns: 215px minmax(0, 960px);
+ grid-template-columns: 200px minmax(0, 1fr);
}
.docs-index .doc-main {
max-width: none;
}
.docs-index .doc-header {
- max-width: 760px;
- border-bottom: 0;
- padding-top: 18px;
+ border: 0;
+ max-width: 820px;
+ padding-top: 12px;
}
.docs-index .doc-header h1 {
- font-size: 54px;
- letter-spacing: -0.05em;
+ font-size: clamp(36px, 4vw, 54px);
}
-.start-paths {
+.docs-entrypoints {
display: grid;
- grid-template-columns: 1fr 1fr;
- gap: 20px;
- margin: 12px 0 55px;
-}
-.start-paths > a {
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+ gap: 0;
+ margin-block: 15px 27px;
border: 1px solid var(--line);
- padding: 27px;
- background: var(--surface);
- transition: border-color 0.2s;
+ background: white;
+ border-radius: 5px;
}
-.start-paths > a:hover {
- border-color: var(--green);
+.docs-entrypoints > a {
+ padding: 24px;
+ min-width: 0;
+ display: flex;
+ flex-direction: column;
}
-.start-paths > a:first-child {
- background: #e8eddd;
+.docs-entrypoints > a + a {
+ border-left: 1px solid var(--line);
}
-.start-paths span {
- font-size: 9px;
- font-family: var(--font-mono);
+.docs-entrypoints > a:nth-child(2) {
+ background: var(--soft-green);
+}
+.docs-entrypoints > a:hover h2 {
+ color: var(--green);
+}
+.docs-entrypoints span {
color: var(--muted);
- letter-spacing: 0.06em;
+ font-size: 10px;
+ font-family: var(--font-mono);
}
-.start-paths h2 {
- font-size: 27px;
+.docs-entrypoints h2 {
+ font-size: 23px;
letter-spacing: -0.035em;
- margin: 28px 0 12px;
+ line-height: 1.3;
+ margin: 28px 0 13px;
}
-.start-paths p {
- font-size: 13px;
+.docs-entrypoints p {
color: var(--muted);
+ font-size: 13px;
line-height: 1.7;
}
-.start-paths strong {
- display: block;
- margin-top: 25px;
+.docs-entrypoints b {
font-size: 12px;
font-weight: 550;
color: var(--green);
+ padding-top: 22px;
+ margin-top: auto;
+}
+.concept-banner {
+ display: flex;
+ gap: 23px;
+ padding: 24px;
+ background: var(--dark);
+ color: white;
+ border-radius: 5px;
+ align-items: center;
+ margin-bottom: 40px;
+}
+.concept-banner > span:first-child {
+ font-family: var(--font-mono);
+ font-size: 10px;
+ color: #afc3ab;
+ max-width: 85px;
+ flex-shrink: 0;
+}
+.concept-banner > span:last-child {
+ margin-left: auto;
+}
+.concept-banner h2 {
+ font-size: 19px;
+ line-height: 1.3;
+ letter-spacing: -0.02em;
+}
+.concept-banner p {
+ font-size: 12px;
+ line-height: 1.7;
+ color: #b6c2b3;
+ margin-top: 7px;
+}
+.guide-list {
+ margin-top: 35px;
+}
+.guide-list .section-label {
+ margin-bottom: 16px;
}
.guide-list .section-label h2 {
font-size: 22px;
- letter-spacing: -0.03em;
+ letter-spacing: -0.035em;
}
-.guide-list .section-label .mono {
- font-size: 9px;
+.guide-list .mono {
color: var(--muted);
+ font-size: 10px;
}
.guide-list > a {
display: grid;
- grid-template-columns: 35px 1fr auto;
- gap: 19px;
- padding: 23px 0;
+ grid-template-columns: 1fr 25px;
+ gap: 25px;
+ padding: 20px 0;
border-top: 1px solid var(--line);
align-items: center;
}
.guide-list > a:last-child {
border-bottom: 1px solid var(--line);
}
+.guide-list h3 {
+ font-size: 16px;
+ font-weight: 550;
+}
+.guide-list p {
+ font-size: 13px;
+ color: var(--muted);
+ line-height: 1.7;
+ margin-top: 6px;
+}
.guide-list > a:hover h3 {
color: var(--green);
}
-.guide-number {
- font-size: 11px;
+.next-steps {
+ border: 1px solid var(--line);
+ background: white;
+ border-radius: 5px;
+ margin-block: 26px;
+ overflow: hidden;
+}
+.path-switch {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+}
+.path-switch button {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 18px;
+ padding: 17px;
+ color: var(--muted);
+ font-size: 13px;
+ border-bottom: 2px solid var(--line);
+ line-height: 1.5;
+ text-align: left;
+}
+.path-switch button + button {
+ border-left: 1px solid var(--line);
+}
+.path-switch button[aria-pressed="true"] {
+ color: var(--green);
+ background: var(--soft-green);
+ border-bottom-color: var(--green);
+}
+.path-heading {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 25px;
+ padding: 20px;
+}
+.path-heading p {
+ font-size: 13px;
+}
+.path-heading > span {
+ flex-shrink: 0;
+ font-size: 10px;
font-family: var(--font-mono);
color: var(--muted);
}
-.guide-list h3 {
- font-size: 16px;
- font-weight: 550;
+.prose .path-checklist {
+ list-style: none;
+ margin: 0;
+ padding: 0 20px;
}
-.guide-list p {
+.prose .path-checklist li {
+ margin: 0;
+ padding: 13px 0;
+ border-top: 1px solid var(--line);
+ display: flex;
+ align-items: center;
+ gap: 15px;
+ justify-content: space-between;
+}
+.path-checklist label {
+ display: flex;
+ gap: 12px;
+ align-items: center;
+ cursor: pointer;
+ min-height: 35px;
+}
+.path-checklist input {
+ width: 17px;
+ height: 17px;
+ flex-shrink: 0;
+ accent-color: var(--green);
+}
+.path-checklist label span {
font-size: 13px;
+ line-height: 1.6;
+}
+.path-checklist li a {
+ font-size: 11px;
+ white-space: nowrap;
+ text-decoration: none;
+ min-height: 35px;
+ display: flex;
+ align-items: center;
+ gap: 7px;
+}
+.prose p.checklist-note {
+ margin: 0;
+ padding: 16px 20px;
+ font-size: 11px;
+ background: #f4f6f0;
color: var(--muted);
line-height: 1.7;
- margin-top: 5px;
}
-.docs-index .callout {
- margin-top: 35px;
+.principle-list > div {
+ display: grid;
+ grid-template-columns: 25px 1fr;
+ gap: 13px;
+ border-top: 1px solid var(--line);
+ padding: 22px 0;
}
-.docs-index .callout p {
+.principle-list > div > span {
+ color: var(--green);
+ font-family: var(--font-mono);
+ font-size: 10px;
+ padding-top: 5px;
+}
+.principle-list h3 {
margin: 0;
- max-width: 700px;
+ font-size: 20px;
}
+.principle-list p {
+ grid-column: 2;
+ font-size: 15px;
+}
+
+/* Field notes */
.article {
- max-width: 1230px;
- padding-block: 80px 100px;
+ max-width: 1190px;
+ padding-block: 75px 90px;
}
.article-header {
- max-width: 1000px;
- margin-bottom: 45px;
+ max-width: 1030px;
+ margin-bottom: 40px;
}
.article-header h1 {
- font-size: clamp(44px, 5.7vw, 82px);
- letter-spacing: -0.058em;
- line-height: 1.05;
- margin: 27px 0;
+ font-size: clamp(42px, 5.5vw, 76px);
+ letter-spacing: -0.055em;
+ line-height: 1.07;
+ margin: 25px 0;
}
.article-subtitle {
- font-size: 24px;
- line-height: 1.45;
+ font-size: 23px;
+ line-height: 1.5;
+ max-width: 880px;
letter-spacing: -0.025em;
- max-width: 850px;
- margin-bottom: 24px;
+ margin-bottom: 22px;
}
.article-header .lead {
- max-width: 720px;
+ max-width: 750px;
font-size: 17px;
}
.article-meta {
display: flex;
flex-wrap: wrap;
- gap: 25px;
- font-size: 11px;
- color: var(--muted);
- margin-top: 30px;
+ gap: 15px 25px;
font-family: var(--font-mono);
+ font-size: 10px;
+ color: var(--muted);
+ margin-top: 27px;
}
.article > .capture {
max-width: 1050px;
@@ -1044,20 +1420,20 @@ th:first-child {
.article-layout {
display: grid;
grid-template-columns: 180px minmax(0, 760px);
- gap: 70px;
+ gap: 65px;
margin-top: 55px;
}
.article-contents {
position: sticky;
- top: 125px;
+ top: 120px;
display: flex;
flex-direction: column;
gap: 17px;
align-self: start;
}
.article-contents a {
- font-size: 12px;
color: var(--muted);
+ font-size: 12px;
}
.article-contents .eyebrow {
font-size: 10px;
@@ -1074,30 +1450,29 @@ th:first-child {
line-height: 1.9;
}
.article-prose h2 {
- font-size: 31px;
- line-height: 1.2;
+ font-size: 30px;
}
.operation-record {
- padding: 27px;
- background: #e7ecdf;
- border: 1px solid #cdd6c2;
- margin: 25px 0;
+ padding: 25px;
+ background: var(--soft-green);
+ border: 1px solid #cedbc8;
+ margin-block: 25px;
}
.operation-record > code {
display: block;
- margin: 17px 0 25px;
border: 0;
padding: 0;
- font-size: 14px;
- background: none;
+ font-size: 13px;
+ background: transparent;
+ margin: 17px 0 23px;
}
.operation-record dl {
margin: 0;
}
.operation-record dl > div {
display: flex;
- gap: 25px;
- border-top: 1px solid #cdd6c2;
+ gap: 22px;
+ border-top: 1px solid #cdd9c8;
padding: 13px 0;
font-size: 13px;
}
@@ -1110,36 +1485,35 @@ th:first-child {
margin: 0;
}
.article-cta {
- margin-top: 35px;
display: flex;
flex-wrap: wrap;
gap: 20px;
align-items: center;
+ margin-top: 30px;
}
-@media (min-width: 1600px) {
- .hero {
- gap: 75px;
- }
- .hero h1 {
- font-size: 82px;
- }
-}
+
@media (max-width: 1200px) {
.wrap {
width: calc(100% - 64px);
}
- .site-header {
- padding-inline: 32px;
+ .landing-hero {
+ gap: 6%;
}
- .hero {
- gap: 35px;
+ .tour-panel {
+ grid-template-columns: 240px minmax(0, 1fr);
+ padding: 25px;
+ gap: 22px;
+ min-height: 475px;
+ }
+ .tour-copy h2 {
+ font-size: 27px;
}
- .hero h1 {
- font-size: 58px;
+ .tour-copy > p:not(.eyebrow) {
+ font-size: 13px;
}
.docs-layout {
grid-template-columns: 190px minmax(0, 1fr);
- gap: 35px;
+ gap: 32px;
}
.toc {
display: none;
@@ -1147,9 +1521,8 @@ th:first-child {
.mobile-toc {
display: block;
border-bottom: 1px solid var(--line);
- padding: 16px 0;
- color: var(--muted);
font-size: 13px;
+ padding: 16px 0;
}
.mobile-toc summary {
cursor: pointer;
@@ -1158,467 +1531,649 @@ th:first-child {
.mobile-toc nav {
display: flex;
flex-direction: column;
- gap: 12px;
- padding: 20px 0;
+ gap: 14px;
+ padding: 20px 0 8px;
}
- .principles {
- grid-template-columns: 1fr 3fr;
+ .docs-entrypoints > a {
+ padding: 20px;
}
- .evidence-layout {
- gap: 27px;
+ .docs-entrypoints h2 {
+ font-size: 21px;
}
- .hero-proof-foot {
- flex-direction: column;
- gap: 7px;
+ .section-heading {
+ gap: 8%;
}
- .site-footer {
- grid-template-columns: 1fr 1fr;
+ .route-list > a {
+ gap: 22px;
+ grid-template-columns: 32px 1fr 1.2fr 20px;
}
- .site-footer > .mono {
- grid-column: 1/-1;
+ .philosophy-grid {
+ gap: 6%;
}
- .article-layout {
- gap: 35px;
+ .proof-grid {
+ gap: 30px;
}
- .docs-index .doc-header h1 {
- font-size: 46px;
+ .article-strip {
+ grid-template-columns: 0.5fr 1.5fr auto;
}
}
-@media (max-width: 900px) {
- .hero {
+@media (max-width: 950px) {
+ .site-header nav {
+ gap: 18px;
+ }
+ .site-header .compact {
+ display: none;
+ }
+ .landing-hero {
grid-template-columns: 1fr;
- padding-top: 60px;
- gap: 44px;
+ gap: 32px;
+ padding-top: 50px;
}
- .hero h1 {
- font-size: 70px;
+ .hero-heading h1 {
+ font-size: 72px;
}
- .hero .lead {
- max-width: 650px;
+ .hero-intro {
+ max-width: 640px;
}
- .hero-proof {
- max-width: 800px;
+ .hero-intro .lead {
+ font-size: 24px;
}
- .hero-proof-foot {
- flex-direction: row;
- justify-content: space-between;
+ .tour-panel {
+ grid-template-columns: 1fr;
+ min-height: 0;
+ }
+ .tour-copy {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 13px 30px;
+ }
+ .tour-copy .eyebrow {
+ grid-column: 1;
+ }
+ .tour-copy h2 {
+ grid-column: 1;
+ grid-row: 2/4;
+ font-size: 32px;
+ margin: 0;
+ }
+ .tour-copy > p:not(.eyebrow) {
+ grid-column: 2;
+ margin: 0;
+ }
+ .tour-copy .tour-fact {
+ border: 0;
+ padding: 0;
+ margin-top: 0 !important;
}
- .principles {
+ .tour-copy > a {
+ grid-column: 2;
+ margin: 0;
+ width: fit-content;
+ }
+ .tour-panel .capture {
+ margin-top: 15px;
+ }
+ .section-heading {
grid-template-columns: 1fr;
gap: 25px;
}
- .principles > div {
- gap: 30px;
+ .section-heading > p {
+ max-width: 650px;
+ }
+ .route-list > a {
+ grid-template-columns: 32px 1fr 22px;
+ gap: 14px 20px;
+ }
+ .route-list p {
+ grid-column: 2;
+ grid-row: 2;
+ max-width: 650px;
+ }
+ .route-list > a > span:last-child {
+ grid-column: 3;
+ grid-row: 1/3;
}
- .evidence-layout {
+ .philosophy-grid {
grid-template-columns: 1fr;
+ gap: 38px;
}
- .evidence-receipt {
- max-width: 600px;
+ .philosophy-grid > div {
+ max-width: 650px;
+ }
+ .workload-model {
+ max-width: 700px;
+ }
+ .proof-grid {
+ grid-template-columns: 1fr;
}
- .evidence-receipt dl {
+ .proof-details {
+ max-width: 650px;
+ }
+ .proof-details dl {
display: flex;
- gap: 25px;
+ gap: 30px;
}
- .evidence-receipt dl > div {
- flex-direction: column;
+ .proof-details dl > div {
flex: 1;
- gap: 8px;
+ flex-direction: column;
+ gap: 10px;
+ }
+ .article-strip {
+ grid-template-columns: 1fr auto;
+ gap: 17px;
+ }
+ .article-strip .eyebrow {
+ grid-column: 1/-1;
}
- .evidence-heading {
+ .learning-section {
+ grid-template-columns: 1fr;
gap: 35px;
}
- .story-teaser {
+ .start-grid {
grid-template-columns: 1fr;
- gap: 25px;
+ gap: 35px;
}
- .story-mark {
- display: none;
+ .start-grid .code-block {
+ max-width: 750px;
}
- .install-section {
- gap: 40px;
+ .docs-entrypoints {
+ grid-template-columns: 1fr;
}
- .site-header .compact {
- display: none;
+ .docs-entrypoints > a + a {
+ border-left: 0;
+ border-top: 1px solid var(--line);
}
- .scope-map {
- grid-template-columns: 1fr;
+ .docs-entrypoints h2 {
+ margin-top: 15px;
}
- .scope-map > div {
- padding: 14px 18px;
- gap: 5px;
+ .concept-banner {
+ flex-wrap: wrap;
}
- .outcome {
- flex-direction: column;
- gap: 7px;
+ .concept-banner > span:first-child {
+ width: 100%;
+ max-width: none;
+ }
+ .concept-banner > div {
+ width: calc(100% - 45px);
}
- .outcome span {
- width: auto;
+ .scope-map {
+ grid-template-columns: 1fr;
+ }
+ .scope-map > div + div {
+ border-left: 0;
+ border-top: 1px solid var(--line);
}
.article-layout {
grid-template-columns: 1fr;
+ gap: 32px;
}
.article-contents {
position: static;
- display: flex;
flex-direction: row;
flex-wrap: wrap;
- gap: 15px;
- border-bottom: 1px solid var(--line);
+ gap: 16px;
padding-bottom: 25px;
+ border-bottom: 1px solid var(--line);
}
.article-contents .eyebrow {
width: 100%;
}
- .article-prose {
- max-width: 760px;
- }
- .start-paths {
- grid-template-columns: 1fr;
- }
- .docs-index .doc-header h1 {
- font-size: 39px;
- }
}
@media (max-width: 700px) {
.wrap {
width: calc(100% - 36px);
}
.site-header {
- height: auto;
- min-height: 76px;
- padding: 15px 18px;
+ padding: 14px 18px 10px;
+ gap: 8px;
flex-wrap: wrap;
- gap: 10px;
+ position: relative;
}
.wordmark {
- font-size: 16px;
+ font-size: 17px;
}
.brand-mark {
- font-size: 29px;
+ width: 25px;
+ height: 25px;
}
.site-header nav {
- gap: 16px;
- font-size: 11px;
+ width: 100%;
+ justify-content: space-between;
+ gap: 12px;
+ font-size: 12px;
}
- .hero {
- padding: 45px 0 52px;
+ .site-header nav a {
+ padding-block: 10px 7px;
}
- .hero h1 {
- font-size: clamp(48px, 10.5vw, 65px);
- margin: 22px 0;
+ .landing-hero {
+ padding-block: 38px 40px;
+ gap: 28px;
}
- .hero .lead {
- font-size: 16px;
+ .hero-heading h1 {
+ font-size: clamp(46px, 10.8vw, 68px);
+ line-height: 1.06;
+ margin-top: 20px;
}
- .hero .actions {
- gap: 18px;
+ .hero-intro .lead {
+ font-size: 22px;
}
- .hero-note {
- font-size: 9px;
+ .hero-intro > p + p {
+ font-size: 15px;
+ margin-top: 17px;
}
- .hero-proof-foot {
- font-size: 10px;
+ .hero-intro .actions {
gap: 13px;
}
- .proof-label {
- font-size: 10px;
+ .hero-intro .text-link {
+ font-size: 13px;
}
- .proof-label .mono {
+ .hero-spec {
+ font-size: 9px;
+ }
+ .eyebrow {
font-size: 10px;
}
- .capture figcaption {
+ .section-label {
+ align-items: flex-start;
+ gap: 10px;
+ flex-direction: column;
+ }
+ .section-label .small {
font-size: 11px;
- gap: 14px;
}
- .hero-proof .capture figcaption {
- font-size: 10px;
+ .tour-section {
+ padding-bottom: 55px;
}
- .principles {
- padding: 42px 0;
+ .tour-tabs {
+ grid-template-columns: repeat(2, minmax(0, 1fr));
}
- .principles > div {
- grid-template-columns: 1fr;
- gap: 23px;
+ .tour-tabs button {
+ padding: 15px;
+ min-height: 52px;
+ font-size: 13px;
+ gap: 12px;
}
- .principles h2 {
- font-size: 34px;
+ .tour-tabs button:nth-child(3) {
+ border-left: 0;
}
- .journey-grid {
- grid-template-columns: 1fr;
+ .tour-tabs button:nth-child(n + 3) {
+ border-top: 1px solid var(--dark-line);
}
- .journey-grid > a,
- .journey-grid > a + a {
- padding: 25px 0;
- border-right: 0;
- border-bottom: 1px solid var(--line);
+ .tour-panel {
+ padding: 22px 15px 17px;
+ gap: 10px;
}
- .journey-grid > a:last-child {
- border-bottom: 0;
+ .tour-copy {
+ display: block;
+ padding-inline: 3px;
}
- .journey-grid h3 {
- margin: 18px 0 10px;
+ .tour-copy h2 {
+ font-size: 29px;
+ margin-top: 15px;
+ max-width: 360px;
}
- .journey-grid p {
- max-width: none;
+ .tour-copy > p:not(.eyebrow) {
+ font-size: 14px;
+ margin-top: 15px;
}
- .walkthrough {
- padding: 30px 0 50px;
+ .tour-copy .tour-fact {
+ margin-top: 17px !important;
+ border-top: 1px solid var(--dark-line);
+ padding-top: 15px;
}
- .section-label {
- align-items: flex-start;
- flex-direction: column;
- gap: 10px;
+ .tour-copy > a {
+ margin-top: 10px;
}
- .section-label .text-link {
- font-size: 12px;
+ .capture figcaption {
+ font-size: 11px;
+ gap: 15px;
}
- .evidence-section {
- padding: 45px 0 55px;
+ .tour-panel .capture figcaption {
+ font-size: 10px;
}
- .evidence-heading {
- grid-template-columns: 1fr;
- gap: 22px;
- margin: 26px 0;
+ .capture figcaption a {
+ min-height: 44px;
+ display: flex;
+ align-items: flex-start;
}
- .evidence-heading h2,
- .story-teaser h2,
- .install-section h2 {
+ .section-heading h2,
+ .philosophy-grid h2,
+ .learning-section h2,
+ .start-grid h2 {
font-size: 36px;
}
- .evidence-heading p {
+ .section-heading > p {
font-size: 15px;
}
- .evidence-receipt dl {
- gap: 18px;
+ .onboarding-story {
+ padding-block: 0 50px;
+ }
+ .section-heading {
+ margin-bottom: 26px;
+ gap: 21px;
+ }
+ .route-list > a {
+ grid-template-columns: 24px 1fr 18px;
+ gap: 12px;
+ padding-block: 22px;
+ }
+ .route-list h3 {
+ font-size: 21px;
+ }
+ .route-list p {
+ font-size: 14px;
+ }
+ .route-number {
+ font-size: 10px;
}
- .evidence-receipt dl > div {
+ .philosophy-band {
+ padding-block: 47px;
+ }
+ .workload-model {
+ padding: 17px;
+ }
+ .model-row {
+ padding: 13px;
+ gap: 8px;
+ }
+ .model-row > span {
+ width: 100%;
+ font-size: 9px;
+ }
+ .model-row strong {
font-size: 12px;
+ overflow-wrap: anywhere;
}
- .evidence-receipt dd {
+ .model-row small {
+ padding-left: 0;
+ }
+ .model-deployment {
+ margin-left: 8px;
+ padding-left: 12px;
+ }
+ .model-services > span {
+ font-size: 11px;
+ padding: 10px;
+ }
+ .model-services small {
font-size: 10px;
}
- .story-teaser {
- padding-block: 52px;
+ .proof-story {
+ padding-top: 50px;
}
- .install-section {
+ .proof-details dl {
+ gap: 18px;
+ }
+ .proof-details dl > div {
+ font-size: 11px;
+ }
+ .proof-details dd {
+ line-height: 1.6;
+ }
+ .article-strip {
grid-template-columns: 1fr;
- padding-block: 50px 60px;
- gap: 20px;
+ padding-block: 23px;
+ margin-top: 30px;
+ }
+ .article-strip h3 {
+ font-size: 23px;
+ }
+ .learning-section {
+ padding-block: 50px;
+ }
+ .start-section {
+ padding-block: 45px;
+ }
+ .start-grid h2 {
+ font-size: 34px;
}
.code-block pre {
+ padding: 18px 14px;
font-size: 11px;
- padding: 18px 15px;
+ }
+ .code-toolbar {
+ padding-inline: 14px;
+ font-size: 9px;
}
.site-footer {
- padding-block: 40px;
- gap: 35px;
grid-template-columns: 1fr;
- }
- .site-footer nav {
- max-width: 400px;
+ gap: 27px;
+ padding-block: 40px 25px;
}
.site-footer > .mono {
font-size: 9px;
}
.docs-layout {
display: block;
- padding-top: 25px;
- padding-bottom: 50px;
+ padding-block: 22px 50px;
}
.docs-sidebar {
position: static;
max-height: none;
- margin-bottom: 32px;
+ margin-bottom: 28px;
padding: 0;
- border-bottom: 1px solid var(--line);
- padding-bottom: 15px;
+ border: 1px solid var(--line);
+ border-radius: 4px;
+ background: white;
}
- .docs-sidebar .eyebrow {
- margin-bottom: 10px;
+ .docs-toggle {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ width: 100%;
+ text-align: left;
+ padding: 15px;
+ min-height: 49px;
+ font-size: 13px;
}
- .docs-sidebar input {
- margin-bottom: 13px;
- padding: 12px;
+ .docs-nav-body {
+ display: none;
+ padding: 0 15px 17px;
}
- .docs-sidebar nav {
- flex-direction: row;
- overflow-x: auto;
- padding-bottom: 9px;
- gap: 6px;
+ .docs-nav-body.is-open {
+ display: block;
}
.docs-sidebar nav a {
- white-space: nowrap;
- padding: 10px 12px;
- border-left: 0;
- border-bottom: 2px solid transparent;
min-height: 44px;
- justify-content: center;
- background: var(--surface);
- }
- .docs-sidebar nav a small {
- display: none;
+ font-size: 13px;
+ padding: 12px;
}
- .docs-sidebar nav a.active {
- border-bottom-color: var(--green);
+ .nav-group {
+ margin-top: 10px;
}
.sidebar-note {
display: none;
}
.breadcrumb {
margin-bottom: 20px;
+ font-size: 10px;
}
.doc-header h1,
.docs-index .doc-header h1 {
- font-size: 35px;
+ font-size: 36px;
}
.doc-header .lead {
- font-size: 15px;
+ font-size: 16px;
+ }
+ .doc-header {
+ padding-bottom: 25px;
}
.docs-index .doc-header {
padding-top: 0;
}
- .doc-header {
- padding-bottom: 26px;
- }
.outcome {
- padding: 15px;
- margin-top: 21px;
+ margin-top: 22px;
+ padding: 16px;
}
.outcome p {
- font-size: 12px;
+ font-size: 13px;
+ }
+ .mobile-toc summary {
+ min-height: 32px;
+ }
+ .mobile-toc nav a {
+ min-height: 32px;
+ line-height: 1.7;
}
.prose section {
- padding-top: 35px;
+ padding-top: 34px;
+ scroll-margin-top: 24px;
}
.prose h2 {
- font-size: 24px;
+ font-size: 25px;
+ margin-bottom: 18px;
}
.prose p,
.prose li {
- font-size: 14px;
- line-height: 1.85;
+ font-size: 16px;
+ line-height: 1.8;
}
- .prose code {
- font-size: 0.85em;
+ .prose .small {
+ font-size: 13px;
}
.callout {
padding: 17px;
}
.callout p {
- font-size: 13px;
- }
- .table-scroll {
- font-size: 12px;
+ font-size: 14px;
}
table {
font-size: 12px;
+ min-width: 480px;
}
td,
th {
- padding: 11px 12px;
+ padding: 12px;
}
- th:first-child {
- min-width: 120px;
+ td:first-child {
+ min-width: 130px;
+ }
+ .table-scroll {
+ margin-block: 22px;
}
.endpoint-builder {
padding: 17px;
}
.endpoint-builder input {
- font-size: 12px;
+ font-size: 16px;
+ }
+ .docs-entrypoints > a {
+ padding: 22px;
+ }
+ .docs-entrypoints h2 {
+ font-size: 25px;
+ }
+ .concept-banner {
+ padding: 22px;
+ gap: 16px;
+ }
+ .concept-banner h2 {
+ font-size: 22px;
+ }
+ .guide-list > a {
+ gap: 18px;
+ }
+ .guide-list h3 {
+ font-size: 17px;
+ }
+ .guide-list .section-label {
+ flex-direction: row;
}
.doc-next {
- font-size: 12px;
gap: 20px;
+ font-size: 12px;
}
- .start-paths {
- margin-bottom: 35px;
- gap: 14px;
+ .path-switch {
+ grid-template-columns: 1fr;
}
- .start-paths > a {
- padding: 22px;
+ .path-switch button {
+ min-height: 48px;
+ font-size: 14px;
}
- .start-paths h2 {
- margin-top: 18px;
+ .path-switch button + button {
+ border-left: 0;
}
- .start-paths strong {
- margin-top: 19px;
+ .path-heading {
+ align-items: flex-start;
+ padding: 17px;
+ gap: 15px;
}
- .guide-list > a {
- grid-template-columns: 23px 1fr auto;
+ .path-heading p {
+ font-size: 13px;
+ }
+ .path-heading > span {
+ font-size: 9px;
+ }
+ .prose .path-checklist {
+ padding-inline: 17px;
+ }
+ .prose .path-checklist li {
gap: 12px;
}
- .guide-list p {
- font-size: 12px;
+ .path-checklist label {
+ gap: 10px;
+ min-height: 44px;
+ }
+ .path-checklist label span {
+ font-size: 13px;
+ }
+ .path-checklist input {
+ width: 19px;
+ height: 19px;
+ }
+ .path-checklist li a {
+ min-height: 44px;
+ font-size: 11px;
+ }
+ .principle-list h3 {
+ font-size: 21px;
+ }
+ .principle-list p {
+ font-size: 15px;
}
.article {
- padding-top: 43px;
- padding-bottom: 60px;
+ padding-block: 42px 60px;
}
.article-header h1 {
- font-size: 44px;
+ font-size: 43px;
}
.article-subtitle {
font-size: 20px;
}
.article-header .lead {
- font-size: 15px;
+ font-size: 16px;
}
.article-meta {
- gap: 12px;
font-size: 10px;
+ gap: 12px 22px;
}
- .article-header {
- margin-bottom: 30px;
- }
- .article-layout {
- margin-top: 35px;
- gap: 30px;
+ .article-prose p {
+ font-size: 16px;
}
.article-prose h2 {
- font-size: 27px;
+ font-size: 26px;
}
- .article-prose p {
- font-size: 16px;
+ .article-contents a {
+ min-height: 30px;
}
.operation-record {
- padding: 20px;
+ padding: 18px;
}
.operation-record > code {
- font-size: 12px;
+ font-size: 11px;
}
.operation-record dl > div {
font-size: 12px;
}
- .article-cta {
- align-items: flex-start;
- flex-direction: column;
- }
}
@media (prefers-reduced-motion: reduce) {
html {
scroll-behavior: auto;
}
- * {
- transition: none !important;
- animation: none !important;
+ *,
+ *::before,
+ *::after {
+ transition-duration: 0.01ms !important;
+ animation-duration: 0.01ms !important;
}
}
-.skip-link {
- position: fixed;
- z-index: 100;
- left: 15px;
- top: -70px;
- background: var(--ink);
- color: white;
- padding: 14px 22px;
-}
-.skip-link:focus {
- top: 10px;
-}
-.prose ol {
- list-style: decimal;
-}
-.prose ul {
- list-style: disc;
-}
diff --git a/app/layout.tsx b/app/layout.tsx
index 658c9c1..2bba021 100644
--- a/app/layout.tsx
+++ b/app/layout.tsx
@@ -13,7 +13,7 @@ const geistMono = Geist_Mono({
});
export const metadata: Metadata = {
- title: "GroundControl — Open-source agentic deployment with MCP + OAuth",
+ title: "GroundControl | Open-source agentic deployment with MCP + OAuth",
description:
"Open-source agentic deployment for Docker Compose infrastructure, with scoped OAuth and MCP access for ChatGPT and other software agents.",
icons: {
diff --git a/app/page.test.tsx b/app/page.test.tsx
index 357526e..6c77f36 100644
--- a/app/page.test.tsx
+++ b/app/page.test.tsx
@@ -13,6 +13,7 @@ import DocsPage from "@/app/docs/page";
import GuidePage from "@/app/docs/[slug]/page";
import EndpointBuilder from "@/app/docs/_components/EndpointBuilder";
import CodeBlock from "@/app/components/CodeBlock";
+import NextSteps from "@/app/components/NextSteps";
import { guideIndex } from "@/content/guide-index";
import { guides } from "@/content/guides";
@@ -110,6 +111,10 @@ describe("The self-contained adoption journey", () => {
});
it("replaces phone captures with current desktop evidence and clear provenance", () => {
render( );
+ expect(
+ screen.getByAltText(/GroundControl discovered workloads/),
+ ).toBeInTheDocument();
+ fireEvent.click(screen.getByRole("tab", { name: /Connect/ }));
expect(
screen.getByAltText(/real active ChatGPT grant/),
).toBeInTheDocument();
@@ -125,4 +130,43 @@ describe("The self-contained adoption journey", () => {
),
).toBe(true);
});
+ it("lets readers navigate the product walkthrough using the keyboard", () => {
+ render( );
+ const first = screen.getByRole("tab", { name: /Discover/ });
+ fireEvent.keyDown(first, { key: "ArrowRight" });
+ const enroll = screen.getByRole("tab", { name: /Enrol/ });
+ expect(enroll).toHaveAttribute("aria-selected", "true");
+ expect(enroll).toHaveFocus();
+ expect(screen.getByRole("tabpanel")).toHaveTextContent(
+ "Choose what belongs",
+ );
+ fireEvent.keyDown(enroll, { key: "End" });
+ expect(screen.getByRole("tab", { name: /Verify/ })).toHaveFocus();
+ expect(screen.getByRole("tabpanel")).toHaveTextContent("recorded release");
+ });
+ it("keeps the existing-app and new-app checklists independent", () => {
+ render( );
+ fireEvent.click(
+ screen.getByRole("checkbox", {
+ name: "Confirm the active host and scan paths",
+ }),
+ );
+ expect(screen.getByText("1 / 5 checked")).toBeInTheDocument();
+ fireEvent.click(screen.getByRole("button", { name: /deploy a new app/ }));
+ expect(screen.getByText("0 / 5 checked")).toBeInTheDocument();
+ expect(
+ screen.getByRole("link", { name: /Guide: Choose a template/ }),
+ ).toHaveAttribute("href", "/docs/new-deployment#choose");
+ fireEvent.click(
+ screen.getByRole("button", { name: /apps already run here/ }),
+ );
+ expect(
+ screen.getByRole("checkbox", {
+ name: "Confirm the active host and scan paths",
+ }),
+ ).toBeChecked();
+ expect(
+ screen.getByText(/does not run an action or verify a server/),
+ ).toBeInTheDocument();
+ });
});
diff --git a/app/page.tsx b/app/page.tsx
index 3d32370..62ad07c 100644
--- a/app/page.tsx
+++ b/app/page.tsx
@@ -2,227 +2,283 @@ import Link from "next/link";
import { Header, Footer } from "@/app/components/SiteChrome";
import Capture from "@/app/components/Capture";
import CodeBlock from "@/app/components/CodeBlock";
+import ProductTour from "@/app/components/ProductTour";
+import WorkloadModel from "@/app/components/WorkloadModel";
export default function Home() {
return (
<>
-
-
+
+
- OPEN-SOURCE AGENTIC DEPLOYMENT
+
+ Open-source agentic deployment
Give your agents
- a clear path
- to production.
+ the access to
+
+ follow through.
+
+
- GroundControl gives ChatGPT controlled access to manage
- deployments, so it can act on approved requests and verify the
- result without repeatedly asking you to provide access or
- credentials.
+ Discover your applications, choose what to manage, and let
+ ChatGPT help operate them.
+
+
+ GroundControl is a self-hosted control plane for your VPS. It
+ gives agents controlled access through OAuth and MCP, so approved
+ work can move from a request to a verified result without repeated
+ access and credential handoffs.
-
- Connect ChatGPT ↗︎
+
+ Install GroundControl ↗︎
-
- Install on your VPS →
+
+ Already installed? Start here →
-
- MCP + OAuth · Your infrastructure · {" "}
- Observable operations
-
-
-