A Serendepify product · Self-hosted and open source.
-
-
GITHUB
-
SERENDEPIFY
+
+
+
+
INSTALL
+
Put GroundControl on your VPS.
+
+
+ Start with the installer, complete setup on your instance, then connect GitHub, Cloudflare or additional servers only when your workflow needs them.
+
+
+
+
+
+ TRY IT ON YOUR OWN VPS
+ Build there. Deploy there. Operate there.
+
+
+
+
);
From dbbf56c5a29fcadd929ca72c652b1bbccaa3ebb0 Mon Sep 17 00:00:00 2001
From: Edward Twumasi <45181928+teckedd-code2save@users.noreply.github.com>
Date: Sat, 29 Aug 2026 10:48:36 +0000
Subject: [PATCH 2/7] style: replace concept graphics with product-led landing
layout
---
app/globals.css | 317 +++++++++++++++++++++---------------------------
1 file changed, 138 insertions(+), 179 deletions(-)
diff --git a/app/globals.css b/app/globals.css
index a9903f9..63b3386 100644
--- a/app/globals.css
+++ b/app/globals.css
@@ -1,19 +1,18 @@
@import "tailwindcss";
:root {
- --ink: #f2f3f0;
- --muted: #8b918b;
- --dim: #5d635e;
- --line: #282d29;
- --line-strong: #363d37;
+ --ink: #f5f7f5;
+ --muted: #9aa29b;
+ --dim: #6d756e;
+ --line: #252b27;
+ --line-strong: #343c36;
--black: #090b0a;
--deep: #0d100e;
- --panel: #121613;
- --panel-soft: #171c18;
- --accent: #596ee8;
- --accent-bright: #7185f5;
- --success: #79d29c;
- --warning: #e1b35d;
+ --panel: #111512;
+ --panel-soft: #151a16;
+ --accent: #6d7df0;
+ --accent-bright: #8794ff;
+ --success: #77d39c;
}
* { box-sizing: border-box; }
@@ -29,192 +28,152 @@ a { color: inherit; text-decoration: none; }
button, a { -webkit-tap-highlight-color: transparent; }
button { font: inherit; }
+.section-shell { width: min(1220px, calc(100% - 48px)); margin: 0 auto; }
+.eyebrow { margin: 0; color: var(--dim); font: 600 10px/1.4 var(--font-geist-mono); letter-spacing: .14em; }
+
.site-header {
- position: absolute;
- z-index: 20;
+ position: sticky;
+ z-index: 30;
top: 0;
- left: 0;
- right: 0;
- height: 74px;
+ height: 70px;
display: flex;
align-items: center;
justify-content: space-between;
- padding: 0 clamp(24px, 5vw, 72px);
- border-bottom: 1px solid rgba(255,255,255,.07);
+ padding: 0 clamp(24px, 4vw, 64px);
+ background: rgba(9, 11, 10, .9);
+ border-bottom: 1px solid var(--line);
+ backdrop-filter: blur(18px);
}
-.wordmark { display: inline-flex; align-items: center; gap: 11px; font: 600 11px/1 var(--font-geist-mono); letter-spacing: .16em; }
-.brand-mark { position: relative; width: 20px; height: 20px; border: 1px solid rgba(255,255,255,.75); display: inline-block; }
-.brand-mark::before { content: ""; position: absolute; width: 8px; height: 8px; right: -1px; top: -1px; background: var(--accent); }
-.brand-mark::after { content: ""; position: absolute; width: 5px; height: 5px; left: 4px; bottom: 4px; border: 1px solid rgba(255,255,255,.55); }
-.brand-mark--small { width: 15px; height: 15px; }
-.site-header nav { display: flex; align-items: center; gap: clamp(18px, 3vw, 40px); }
-.site-header nav a { font: 500 10px/1 var(--font-geist-mono); color: #aeb3ae; letter-spacing: .1em; transition: color .2s; }
+.wordmark { display: inline-flex; align-items: center; gap: 10px; font: 650 12px/1 var(--font-geist-mono); letter-spacing: .04em; }
+.brand-mark { width: 28px; height: 28px; display: inline-grid; place-items: center; border: 1px solid var(--line-strong); color: var(--accent-bright); font: 700 8px/1 var(--font-geist-mono); }
+.site-header nav { display: flex; align-items: center; gap: clamp(16px, 3vw, 34px); }
+.site-header nav a, footer a { color: #aeb5af; font: 550 10px/1 var(--font-geist-mono); letter-spacing: .04em; transition: color .2s; }
.site-header nav a:hover, footer a:hover { color: white; }
-.hero { position: relative; min-height: 820px; height: 100svh; overflow: hidden; display: flex; align-items: center; padding: 112px clamp(24px, 8vw, 128px) 84px; background: radial-gradient(circle at 75% 45%, #202d2b 0, #141a18 28%, #0b0e0c 67%); }
-.hero::after { content: ""; position: absolute; inset: 0; background: linear-gradient(90deg, rgba(7,9,8,.96) 0%, rgba(7,9,8,.76) 43%, rgba(7,9,8,.06) 80%); }
-.hero-grid, .final-grid { position: absolute; inset: 0; opacity: .18; background-image: linear-gradient(rgba(255,255,255,.08) 1px, transparent 1px), linear-gradient(90deg, rgba(255,255,255,.08) 1px, transparent 1px); background-size: 96px 96px; mask-image: linear-gradient(to right, transparent, black 55%, transparent); }
-.hero-mesh { position: absolute; width: min(700px, 64vw); aspect-ratio: 1; right: -5%; top: 7%; filter: blur(8px); }
-.hero-mesh span { position: absolute; border-radius: 999px; mix-blend-mode: screen; filter: blur(45px); opacity: .42; animation: drift 12s ease-in-out infinite alternate; }
-.hero-mesh span:nth-child(1) { width: 64%; height: 64%; right: 5%; top: 5%; background: #4f6a67; }
-.hero-mesh span:nth-child(2) { width: 52%; height: 52%; left: 3%; bottom: 8%; background: #2c3d79; animation-delay: -4s; }
-.hero-mesh span:nth-child(3) { width: 35%; height: 35%; right: 18%; bottom: 4%; background: #775464; animation-delay: -8s; }
-@keyframes drift { to { transform: translate3d(-32px, 25px, 0) scale(1.08); } }
-.hero-content { position: relative; z-index: 2; max-width: 760px; }
-.eyebrow { margin: 0; font: 500 10px/1.4 var(--font-geist-mono); letter-spacing: .15em; color: var(--muted); }
-.hero-eyebrow { display: flex; align-items: center; gap: 9px; color: #c7cbc7; }
-.live-dot { display: inline-block; width: 6px; height: 6px; border-radius: 50%; background: var(--success); box-shadow: 0 0 12px rgba(121,210,156,.45); }
-.hero h1 { margin: 30px 0 26px; font-size: clamp(54px, 7.1vw, 104px); line-height: .92; letter-spacing: -.065em; font-weight: 490; }
-.hero h1 span { color: var(--accent-bright); }
-.hero-copy { max-width: 610px; margin: 0 0 38px; color: #a3a9a3; font-size: clamp(16px, 1.5vw, 20px); line-height: 1.65; }
-.hero-actions { display: flex; flex-wrap: wrap; gap: 10px; }
-.button { min-height: 48px; display: inline-flex; align-items: center; justify-content: center; gap: 20px; padding: 0 20px; border: 1px solid var(--line-strong); font: 600 10px/1 var(--font-geist-mono); letter-spacing: .09em; transition: background .2s, border-color .2s, transform .2s; }
+.hero {
+ min-height: calc(100svh - 70px);
+ padding-top: clamp(72px, 10vw, 140px);
+ padding-bottom: 90px;
+ display: grid;
+ grid-template-columns: minmax(0, 1fr) minmax(420px, .88fr);
+ gap: clamp(42px, 7vw, 96px);
+ align-items: center;
+}
+.hero-copy-block { max-width: 720px; }
+.hero h1 { margin: 23px 0 28px; font-size: clamp(48px, 6.1vw, 88px); line-height: .97; letter-spacing: -.06em; font-weight: 520; }
+.hero-copy { max-width: 680px; margin: 0; color: var(--muted); font-size: clamp(17px, 1.6vw, 21px); line-height: 1.62; }
+.hero-actions { margin-top: 36px; display: flex; flex-wrap: wrap; gap: 10px; }
+.button { min-height: 50px; display: inline-flex; align-items: center; justify-content: center; gap: 16px; padding: 0 20px; border: 1px solid var(--line-strong); font: 650 10px/1 var(--font-geist-mono); letter-spacing: .045em; transition: transform .2s, background .2s, border-color .2s; }
.button:hover { transform: translateY(-2px); }
-.button--primary { color: white; border-color: var(--accent); background: var(--accent); }
+.button--primary { background: var(--accent); border-color: var(--accent); color: white; }
.button--primary:hover { background: var(--accent-bright); border-color: var(--accent-bright); }
-.button--secondary { background: rgba(255,255,255,.02); color: #c6cac6; }
-.button--secondary:hover { border-color: #59615a; background: rgba(255,255,255,.05); }
-.hero-proof { position: absolute; z-index: 2; left: clamp(24px, 8vw, 128px); right: clamp(24px, 5vw, 72px); bottom: 34px; display: flex; gap: 30px; color: #697069; font: 500 9px/1 var(--font-geist-mono); letter-spacing: .11em; }
-
-.section-shell { width: min(1180px, calc(100% - 48px)); margin: 0 auto; }
-.preview-section { padding: 140px 0 130px; }
-.section-intro { max-width: 760px; margin-bottom: 62px; }
-.section-intro h2 { margin: 20px 0 0; font-size: clamp(38px, 5vw, 66px); line-height: 1.02; letter-spacing: -.052em; font-weight: 480; }
-.section-intro p:not(.eyebrow) { margin: 0; color: var(--muted); font-size: 16px; line-height: 1.7; max-width: 560px; }
-.section-intro--split { max-width: none; display: grid; grid-template-columns: minmax(180px, .7fr) minmax(0, 1.55fr); gap: 50px; align-items: start; }
-.section-intro--split h2 { margin-top: 0; margin-bottom: 24px; }
-
-.product-preview { border: 1px solid var(--line-strong); background: #0b0e0c; box-shadow: 0 40px 100px rgba(0,0,0,.4); overflow: hidden; }
-.preview-topbar { height: 48px; display: flex; align-items: center; justify-content: space-between; padding: 0 18px; border-bottom: 1px solid var(--line); background: #0e110f; }
-.preview-brand, .preview-topmeta { display: flex; align-items: center; gap: 9px; color: #838983; font: 600 8px/1 var(--font-geist-mono); letter-spacing: .12em; }
-.preview-body { min-height: 590px; display: flex; }
-.preview-rail { width: 58px; border-right: 1px solid var(--line); display: flex; flex-direction: column; align-items: center; gap: 24px; padding-top: 28px; }
-.rail-line { width: 17px; height: 2px; background: #39403a; }
-.rail-line--active { width: 23px; background: var(--accent-bright); }
-.preview-main { flex: 1; min-width: 0; padding: clamp(20px, 3vw, 40px); }
-.preview-heading { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 28px; }
-.preview-heading h2 { margin: 7px 0 0; font-size: 27px; letter-spacing: -.04em; font-weight: 500; }
-.preview-clock { color: #606760; font: 500 8px/1 var(--font-geist-mono); }
-.signal-panel { display: grid; grid-template-columns: 1.25fr .75fr; border: 1px solid var(--line-strong); }
-.signal-copy { min-height: 260px; padding: clamp(24px, 3vw, 38px); border-right: 1px solid var(--line); display: flex; flex-direction: column; justify-content: center; }
-.status-pill { width: fit-content; display: flex; align-items: center; gap: 7px; padding: 6px 8px; color: var(--success); border: 1px solid rgba(121,210,156,.24); background: rgba(121,210,156,.05); font: 600 7px/1 var(--font-geist-mono); letter-spacing: .08em; }
-.signal-copy h3 { margin: 22px 0 10px; font-size: clamp(24px, 3.2vw, 42px); line-height: 1; letter-spacing: -.05em; font-weight: 480; }
-.signal-copy p { color: #707770; font-size: 12px; }
-.signal-actions { display: flex; gap: 8px; margin-top: 26px; }
-.signal-actions span { padding: 8px 10px; color: #9ea49e; border: 1px solid var(--line); font: 600 7px/1 var(--font-geist-mono); }
-.metric-grid { display: grid; grid-template-columns: 1fr 1fr; }
-.metric { min-height: 130px; padding: 22px; display: flex; flex-direction: column; border-bottom: 1px solid var(--line); border-right: 1px solid var(--line); }
-.metric:nth-child(2n) { border-right: 0; }
-.metric:nth-child(n+3) { border-bottom: 0; }
-.metric span, .metric small, .card-label { color: #606760; font: 500 7px/1.4 var(--font-geist-mono); letter-spacing: .08em; }
-.metric strong { margin: auto 0 5px; font-size: 26px; font-weight: 500; letter-spacing: -.04em; }
-.preview-lower { display: grid; grid-template-columns: 1.4fr .6fr; gap: 14px; margin-top: 14px; }
-.telemetry-card, .runtime-card { min-height: 180px; padding: 18px; border: 1px solid var(--line); background: #0d100e; }
-.card-label { display: flex; justify-content: space-between; }
-.healthy-text { color: var(--success); }
-.chart { position: relative; height: 108px; margin-top: 23px; overflow: hidden; background-image: linear-gradient(rgba(255,255,255,.04) 1px, transparent 1px), linear-gradient(90deg, rgba(255,255,255,.04) 1px, transparent 1px); background-size: 25% 33%; }
-.chart-fill { position: absolute; inset: 32% -5% -20% -5%; background: linear-gradient(180deg, rgba(89,110,232,.26), transparent); clip-path: polygon(0 50%, 10% 42%, 20% 58%, 31% 35%, 42% 44%, 52% 22%, 62% 30%, 72% 18%, 81% 31%, 91% 15%, 100% 24%, 100% 100%, 0 100%); }
-.chart-line { position: absolute; inset: 29% -5% 0; border-top: 1px solid var(--accent-bright); transform: skewY(-5deg); opacity: .8; }
-.runtime-card { padding-bottom: 10px; }
-.service-row { height: 31px; display: grid; grid-template-columns: 13px 1fr auto; align-items: center; border-bottom: 1px solid #1d221e; color: #b9bdb9; font: 500 9px/1 var(--font-geist-mono); }
-.service-row small { font-size: 6px; color: #666d66; }
-
-.capabilities { padding: 50px 0 150px; }
-.capability-grid { border-top: 1px solid var(--line-strong); }
-.capability-grid article { min-height: 220px; display: grid; grid-template-columns: 80px 1fr auto; gap: 34px; align-items: center; border-bottom: 1px solid var(--line); transition: background .25s; }
-.capability-grid article:hover { background: linear-gradient(90deg, transparent, rgba(255,255,255,.018), transparent); }
-.capability-number { align-self: start; padding-top: 46px; color: var(--accent-bright); font: 500 10px/1 var(--font-geist-mono); }
-.capability-grid h3 { margin: 0 0 13px; font-size: clamp(25px, 3vw, 38px); font-weight: 480; letter-spacing: -.04em; }
-.capability-grid p { max-width: 640px; margin: 0; color: var(--muted); line-height: 1.65; }
-.capability-grid small { color: #606660; font: 500 8px/1 var(--font-geist-mono); letter-spacing: .08em; text-transform: uppercase; }
-
-.principle-band { padding: 130px 0; background: var(--panel-soft); border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); }
-.principle-inner blockquote { max-width: 1040px; margin: 30px 0 56px; font-size: clamp(42px, 6vw, 76px); line-height: 1.05; letter-spacing: -.055em; font-weight: 440; }
-.principle-meta { display: flex; gap: 36px; color: #6d746e; font: 500 8px/1 var(--font-geist-mono); letter-spacing: .1em; }
-
-.install-section { padding: 150px 0 130px; }
-.install-console { margin-top: 70px; border: 1px solid var(--line-strong); background: #0b0d0c; }
+.button--secondary { background: transparent; color: #c9cfca; }
+.button--secondary:hover { border-color: #59625b; background: rgba(255,255,255,.025); }
+.hero-notes { margin-top: 34px; display: flex; flex-wrap: wrap; gap: 8px; }
+.hero-notes span { padding: 7px 9px; border: 1px solid var(--line); color: var(--dim); font: 550 8px/1 var(--font-geist-mono); letter-spacing: .06em; text-transform: uppercase; }
+
+.hero-media { margin: 0; border: 1px solid var(--line-strong); background: var(--deep); overflow: hidden; box-shadow: 0 28px 80px rgba(0,0,0,.35); }
+.hero-media img { width: 100%; height: auto; display: block; }
+.hero-media figcaption { padding: 18px 20px 20px; border-top: 1px solid var(--line); color: var(--muted); font-size: 13px; line-height: 1.55; }
+.hero-media figcaption strong { color: var(--ink); font-weight: 600; }
+
+.proof-band { padding: 100px 0; background: var(--panel-soft); border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); }
+.proof-grid { display: grid; grid-template-columns: minmax(160px, .55fr) minmax(0, 1.45fr); gap: 48px; }
+.proof-grid h2, .section-heading h2, .ownership-grid h2, .final-cta h2 { margin: 0; font-size: clamp(38px, 5vw, 68px); line-height: 1.01; letter-spacing: -.052em; font-weight: 500; }
+.proof-grid p:not(.eyebrow), .section-heading > p, .section-heading--split > p, .workflow-copy > p:not(.eyebrow), .ownership-grid > p:not(.eyebrow) { color: var(--muted); font-size: 16px; line-height: 1.72; }
+.proof-grid h2 + p { max-width: 800px; margin: 24px 0 0; }
+
+.features, .templates, .install-section { padding: 130px 0; }
+.section-heading { max-width: 900px; margin-bottom: 58px; }
+.section-heading h2 { margin-top: 18px; }
+.section-heading--split { max-width: none; display: grid; grid-template-columns: minmax(0, 1.25fr) minmax(280px, .75fr); gap: 64px; align-items: end; }
+.section-heading--split > p { margin: 0; max-width: 520px; }
+
+.feature-grid { display: grid; grid-template-columns: repeat(3, 1fr); border-top: 1px solid var(--line-strong); border-left: 1px solid var(--line-strong); }
+.feature-grid article { min-height: 260px; padding: 30px; display: flex; flex-direction: column; border-right: 1px solid var(--line-strong); border-bottom: 1px solid var(--line-strong); background: var(--deep); }
+.feature-grid h3 { margin: 0 0 16px; font-size: 25px; letter-spacing: -.035em; font-weight: 520; }
+.feature-grid p { margin: 0; color: var(--muted); font-size: 14px; line-height: 1.68; }
+.feature-grid small { margin-top: auto; padding-top: 28px; color: var(--dim); font: 550 8px/1.45 var(--font-geist-mono); text-transform: uppercase; letter-spacing: .065em; }
+
+.workflow-section { padding: 120px 0; border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); background: #0b0e0c; }
+.workflow-grid { display: grid; grid-template-columns: minmax(0, .9fr) minmax(420px, 1.1fr); gap: clamp(56px, 8vw, 110px); align-items: start; }
+.workflow-copy { margin: 0; position: sticky; top: 112px; }
+.workflow-copy > p:not(.eyebrow) { max-width: 600px; margin: 24px 0 0; }
+.workflow-steps { list-style: none; margin: 0; padding: 0; border-top: 1px solid var(--line-strong); }
+.workflow-steps li { min-height: 150px; display: grid; grid-template-columns: 54px 1fr; gap: 24px; padding: 28px 0; border-bottom: 1px solid var(--line); }
+.workflow-steps li > span { color: var(--accent-bright); font: 600 9px/1.4 var(--font-geist-mono); }
+.workflow-steps strong { display: block; margin-bottom: 9px; font-size: 22px; font-weight: 520; letter-spacing: -.025em; }
+.workflow-steps p { margin: 0; color: var(--muted); font-size: 14px; line-height: 1.65; }
+
+.template-grid { display: grid; grid-template-columns: 1fr 1fr; border-top: 1px solid var(--line-strong); }
+.template-grid article { padding: 30px 0; border-bottom: 1px solid var(--line); }
+.template-grid article:nth-child(odd) { padding-right: 32px; border-right: 1px solid var(--line); }
+.template-grid article:nth-child(even) { padding-left: 32px; }
+.template-topline { display: flex; align-items: flex-start; justify-content: space-between; gap: 20px; }
+.template-grid h3 { margin: 0; font-size: 22px; font-weight: 520; letter-spacing: -.03em; }
+.template-grid code { color: var(--accent-bright); font: 500 8px/1.4 var(--font-geist-mono); text-align: right; }
+.template-grid p { margin: 18px 0 0; color: var(--muted); font-size: 14px; line-height: 1.68; max-width: 530px; }
+
+.ownership-section { padding: 120px 0; background: var(--panel-soft); border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); }
+.ownership-grid { display: grid; grid-template-columns: .45fr 1.55fr; gap: 48px; }
+.ownership-grid h2 { max-width: 920px; }
+.ownership-grid > p:last-child { grid-column: 2; max-width: 720px; margin: 28px 0 0; }
+
+.install-console { margin-top: 58px; border: 1px solid var(--line-strong); background: #080a09; }
.install-tabs { display: grid; grid-template-columns: repeat(3, 1fr); border-bottom: 1px solid var(--line); }
-.install-tab { min-height: 88px; padding: 18px 22px; border: 0; border-right: 1px solid var(--line); color: #717771; background: transparent; text-align: left; cursor: pointer; transition: background .2s, color .2s; }
+.install-tab { min-height: 78px; padding: 17px 20px; border: 0; border-right: 1px solid var(--line); background: transparent; color: var(--dim); text-align: left; cursor: pointer; }
.install-tab:last-child { border-right: 0; }
-.install-tab span { display: block; margin-bottom: 8px; font-size: 13px; font-weight: 550; }
-.install-tab small { font: 500 8px/1 var(--font-geist-mono); letter-spacing: .07em; }
-.install-tab--active { color: var(--ink); background: #141815; box-shadow: inset 0 -2px var(--accent-bright); }
-.command-shell { padding: 22px; }
-.command-header { display: flex; justify-content: space-between; color: #525852; font: 500 7px/1 var(--font-geist-mono); letter-spacing: .09em; }
-.terminal-dot { display: inline-block; width: 7px; height: 7px; margin-right: 6px; border-radius: 50%; }
-.terminal-dot--red { background: #9d5757; }.terminal-dot--gold { background: #9c824c; }.terminal-dot--green { background: #4f8963; }
-.command-line { min-height: 122px; display: grid; grid-template-columns: auto minmax(0, 1fr) auto; gap: 13px; align-items: center; margin-top: 15px; padding: 20px; background: #080a09; border: 1px solid #20241f; }
-.prompt { color: var(--accent-bright); font: 500 12px/1 var(--font-geist-mono); }
-.command-line code { overflow-wrap: anywhere; color: #c2c7c2; font: 500 12px/1.7 var(--font-geist-mono); }
-.command-line button { border: 1px solid #303630; background: #101410; color: #969c96; padding: 9px 10px; font: 600 8px/1 var(--font-geist-mono); cursor: pointer; }
-.command-line button:hover { color: white; border-color: #525a52; }
-.command-note { display: flex; justify-content: space-between; margin-top: 14px; color: #515751; font: 500 7px/1 var(--font-geist-mono); letter-spacing: .08em; }
-.install-steps { display: grid; grid-template-columns: repeat(3, 1fr); margin-top: 70px; border-top: 1px solid var(--line); }
-.install-steps article { padding: 30px 34px 10px 0; border-right: 1px solid var(--line); }
-.install-steps article:not(:first-child) { padding-left: 34px; }
-.install-steps article:last-child { border-right: 0; }
-.install-steps span { color: var(--accent-bright); font: 500 9px/1 var(--font-geist-mono); }
-.install-steps h3 { margin: 30px 0 12px; font-size: 18px; font-weight: 520; }
-.install-steps p { margin: 0; color: var(--muted); font-size: 13px; line-height: 1.7; }
-
-.final-cta { position: relative; min-height: 560px; display: grid; place-items: center; overflow: hidden; border-top: 1px solid var(--line); background: radial-gradient(circle at center, #1c2623, #0b0e0c 62%); text-align: center; }
-.final-cta > div:not(.final-grid) { position: relative; z-index: 2; }
-.final-cta h2 { margin: 22px 0 38px; font-size: clamp(50px, 8vw, 100px); line-height: .9; letter-spacing: -.065em; font-weight: 470; }
-footer { min-height: 110px; display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; gap: 30px; padding: 0 clamp(24px, 5vw, 72px); border-top: 1px solid var(--line); background: #080a09; }
-footer p { margin: 0; color: #555b55; font-size: 11px; }
-footer > div { justify-self: end; display: flex; gap: 24px; color: #6e746e; font: 500 8px/1 var(--font-geist-mono); letter-spacing: .09em; }
-
-@media (max-width: 800px) {
- .site-header nav a:first-child { display: none; }
- .hero { min-height: 760px; padding-left: 24px; padding-right: 24px; }
- .hero h1 { font-size: clamp(48px, 14vw, 74px); }
- .hero-proof { left: 24px; gap: 15px; flex-wrap: wrap; }
- .section-shell { width: min(100% - 32px, 1180px); }
- .section-intro--split { grid-template-columns: 1fr; gap: 24px; }
- .preview-section, .install-section { padding-top: 100px; }
- .preview-body { min-height: 0; }
- .preview-rail { display: none; }
- .signal-panel { grid-template-columns: 1fr; }
- .signal-copy { border-right: 0; border-bottom: 1px solid var(--line); }
- .preview-lower { grid-template-columns: 1fr; }
- .runtime-card { display: none; }
- .capability-grid article { grid-template-columns: 45px 1fr; gap: 16px; padding: 32px 0; }
- .capability-number { padding-top: 5px; }
- .capability-grid small { grid-column: 2; }
- .principle-meta { flex-wrap: wrap; gap: 16px; }
- .install-steps { grid-template-columns: 1fr; }
- .install-steps article, .install-steps article:not(:first-child) { padding: 28px 0; border-right: 0; border-bottom: 1px solid var(--line); }
- footer { grid-template-columns: 1fr; padding-top: 34px; padding-bottom: 34px; text-align: center; }
- footer .wordmark, footer > div { justify-self: center; }
+.install-tab span { display: block; color: inherit; font: 600 10px/1 var(--font-geist-mono); }
+.install-tab small { display: block; margin-top: 8px; font: 500 9px/1 var(--font-geist-mono); }
+.install-tab--active { background: var(--panel); color: var(--ink); box-shadow: inset 0 2px 0 var(--accent-bright); }
+.command-shell { min-height: 110px; padding: 24px; display: grid; grid-template-columns: auto 1fr auto; gap: 14px; align-items: start; }
+.prompt { color: var(--success); font: 600 12px/1.6 var(--font-geist-mono); }
+.command-shell code { min-width: 0; color: #c8cec9; font: 500 12px/1.65 var(--font-geist-mono); overflow-wrap: anywhere; }
+.command-shell button { border: 1px solid var(--line-strong); background: transparent; color: var(--muted); padding: 8px 10px; cursor: pointer; font: 600 8px/1 var(--font-geist-mono); }
+.command-shell button:hover { color: white; border-color: #59625b; }
+
+.final-cta { padding: 110px 0 125px; text-align: center; }
+.final-cta h2 { max-width: 900px; margin: 20px auto 0; }
+.final-cta .hero-actions { justify-content: center; }
+
+footer { min-height: 110px; display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; gap: 30px; border-top: 1px solid var(--line); }
+footer p { margin: 0; color: var(--dim); font-size: 11px; text-align: center; }
+footer > div { justify-self: end; display: flex; gap: 24px; }
+
+@media (max-width: 980px) {
+ .hero { grid-template-columns: 1fr; min-height: auto; padding-top: 90px; }
+ .hero-copy-block { max-width: 820px; }
+ .hero-media { max-width: 820px; }
+ .feature-grid { grid-template-columns: 1fr 1fr; }
+ .workflow-grid { grid-template-columns: 1fr; }
+ .workflow-copy { position: static; }
+ .ownership-grid { grid-template-columns: 1fr; }
+ .ownership-grid > p:last-child { grid-column: 1; }
}
-@media (max-width: 560px) {
- .site-header { height: 64px; padding: 0 18px; }
- .site-header nav { gap: 16px; }
- .site-header nav a { font-size: 8px; }
- .hero { min-height: 700px; padding-top: 95px; }
- .hero-copy { font-size: 15px; }
- .hero-proof span:last-child { display: none; }
- .preview-section { width: 100%; }
- .preview-section > .section-intro { width: calc(100% - 32px); margin-left: auto; margin-right: auto; }
- .product-preview { border-left: 0; border-right: 0; }
- .preview-main { padding: 16px; }
- .signal-copy { min-height: 230px; }
- .metric { min-height: 104px; padding: 15px; }
- .metric strong { font-size: 21px; }
- .preview-lower { display: none; }
+@media (max-width: 720px) {
+ .section-shell { width: min(100% - 32px, 1220px); }
+ .site-header { height: 64px; padding: 0 16px; }
+ .site-header nav a:nth-child(1), .site-header nav a:nth-child(2) { display: none; }
+ .hero { padding-top: 70px; padding-bottom: 70px; }
+ .hero h1 { font-size: clamp(44px, 13vw, 62px); }
+ .proof-grid, .section-heading--split { grid-template-columns: 1fr; gap: 24px; }
+ .features, .templates, .install-section { padding: 90px 0; }
+ .feature-grid, .template-grid { grid-template-columns: 1fr; }
+ .feature-grid article { min-height: 230px; }
+ .template-grid article:nth-child(odd), .template-grid article:nth-child(even) { padding: 26px 0; border-right: 0; }
+ .template-topline { display: block; }
+ .template-grid code { display: block; margin-top: 9px; text-align: left; }
+ .workflow-section, .ownership-section, .proof-band { padding: 86px 0; }
.install-tabs { grid-template-columns: 1fr; }
- .install-tab { min-height: 66px; border-right: 0; border-bottom: 1px solid var(--line); }
- .command-line { grid-template-columns: auto 1fr; }
- .command-line button { grid-column: 2; justify-self: start; }
- .command-note { gap: 20px; line-height: 1.5; }
- .principle-band { padding: 90px 0; }
- .final-cta { min-height: 460px; }
+ .install-tab, .install-tab:last-child { border-right: 0; border-bottom: 1px solid var(--line); }
+ .command-shell { grid-template-columns: auto 1fr; }
+ .command-shell button { grid-column: 2; justify-self: start; }
+ footer { grid-template-columns: 1fr; padding: 34px 0; text-align: center; }
+ footer .wordmark, footer > div { justify-self: center; }
}
@media (prefers-reduced-motion: reduce) {
html { scroll-behavior: auto; }
- .hero-mesh span { animation: none; }
*, *::before, *::after { transition-duration: .01ms !important; }
}
From 2f28520860761e20f045b2a64c4d2e97655a1c51 Mon Sep 17 00:00:00 2001
From: Edward Twumasi <45181928+teckedd-code2save@users.noreply.github.com>
Date: Sat, 29 Aug 2026 10:49:33 +0000
Subject: [PATCH 3/7] fix: keep landing page media truthful until real captures
are added
---
app/page.tsx | 238 +++++++++++----------------------------------------
1 file changed, 50 insertions(+), 188 deletions(-)
diff --git a/app/page.tsx b/app/page.tsx
index 68acb80..adeed18 100644
--- a/app/page.tsx
+++ b/app/page.tsx
@@ -8,57 +8,18 @@ const BOOTSTRAP_URL =
"https://raw.githubusercontent.com/teckedd-code2save/groundcontrol/main/scripts/bootstrap";
const commands = [
- {
- id: "remote",
- label: "Remote",
- hint: "From your computer",
- command: `curl -fsSL ${BOOTSTRAP_URL} | bash -s -- -i ~/.ssh/id_ed25519 root@YOUR_VPS_IP`,
- },
- {
- id: "guided",
- label: "Guided",
- hint: "Interactive setup",
- command: `curl -fsSL ${BOOTSTRAP_URL} | bash -s -- --interactive`,
- },
- {
- id: "local",
- label: "Local",
- hint: "Already on the VPS",
- command: `curl -fsSL ${BOOTSTRAP_URL} | bash`,
- },
+ { id: "remote", label: "Remote", hint: "From your computer", command: `curl -fsSL ${BOOTSTRAP_URL} | bash -s -- -i ~/.ssh/id_ed25519 root@YOUR_VPS_IP` },
+ { id: "guided", label: "Guided", hint: "Interactive setup", command: `curl -fsSL ${BOOTSTRAP_URL} | bash -s -- --interactive` },
+ { id: "local", label: "Local", hint: "Already on the VPS", command: `curl -fsSL ${BOOTSTRAP_URL} | bash` },
] as const;
const features = [
- {
- title: "Deployments",
- copy: "Build and deploy from connected repositories to Docker Compose, static sites, k3s, Cloud Run and Terraform-backed targets.",
- meta: "Build · deploy · verify · redeploy",
- },
- {
- title: "Runtime",
- copy: "Inspect containers, services, health, logs, processes and host telemetry from the same place you deploy them.",
- meta: "Docker · services · logs · health",
- },
- {
- title: "Terminal",
- copy: "Use a real browser terminal against the VPS, including host-level commands when GroundControl itself is running in Docker.",
- meta: "Shell · host operations · AI commands",
- },
- {
- title: "Intelligence",
- copy: "Investigate broken customer journeys from live evidence, connect changes to failures and prepare the next safe action.",
- meta: "Evidence · investigation · recovery",
- },
- {
- title: "Domains & edge",
- copy: "Connect Cloudflare, provision DNS, route through Caddy and verify the public HTTPS path as part of deployment.",
- meta: "Cloudflare · Caddy · DNS · TLS",
- },
- {
- title: "Your infrastructure",
- copy: "GroundControl is self-hosted and single-tenant. Your instance, data and operational credentials stay on infrastructure you control.",
- meta: "Open source · self-hosted · single tenant",
- },
+ { title: "Deployments", copy: "Build and deploy from connected repositories to Docker Compose, static sites, k3s, Cloud Run and Terraform-backed targets.", meta: "Build · deploy · verify · redeploy" },
+ { title: "Runtime", copy: "Inspect containers, services, health, logs, processes and host telemetry from the same place you deploy them.", meta: "Docker · services · logs · health" },
+ { title: "Terminal", copy: "Use a real browser terminal against the VPS, including host-level commands when GroundControl itself is running in Docker.", meta: "Shell · host operations · AI commands" },
+ { title: "Intelligence", copy: "Investigate broken customer journeys from live evidence, connect changes to failures and prepare the next safe action.", meta: "Evidence · investigation · recovery" },
+ { title: "Domains & edge", copy: "Connect Cloudflare, provision DNS, route through Caddy and verify the public HTTPS path as part of deployment.", meta: "Cloudflare · Caddy · DNS · TLS" },
+ { title: "Your infrastructure", copy: "GroundControl is self-hosted and single-tenant. Your instance, data and operational credentials stay on infrastructure you control.", meta: "Open source · self-hosted · single tenant" },
] as const;
const templates = [
@@ -70,9 +31,7 @@ const templates = [
["Microservices + ops", "caddy-secure-microservices-observability", "Web, API, worker, PostgreSQL, Redis, object storage, logs and uptime checks in one Caddy-first stack."],
] as const;
-function Arrow() {
- return
↗ ;
-}
+function Arrow() { return
↗ ; }
function InstallConsole() {
const [selected, setSelected] = useState<(typeof commands)[number]["id"]>("remote");
@@ -80,9 +39,8 @@ function InstallConsole() {
const active = commands.find((command) => command.id === selected) ?? commands[0];
async function copyCommand() {
- if (navigator.clipboard?.writeText) {
- await navigator.clipboard.writeText(active.command);
- } else {
+ if (navigator.clipboard?.writeText) await navigator.clipboard.writeText(active.command);
+ else {
const textArea = document.createElement("textarea");
textArea.value = active.command;
textArea.setAttribute("readonly", "");
@@ -101,28 +59,16 @@ function InstallConsole() {
{commands.map((command) => (
- {
- setSelected(command.id);
- setCopied(false);
- }}
- >
- {command.label}
- {command.hint}
+ onClick={() => { setSelected(command.id); setCopied(false); }}>
+ {command.label} {command.hint}
))}
- $
- {active.command}
-
- {copied ? "COPIED" : "COPY"}
-
+ $ {active.command}
+ {copied ? "COPIED" : "COPY"}
);
@@ -132,14 +78,9 @@ export default function Home() {
return (
@@ -148,138 +89,59 @@ export default function Home() {
SELF-HOSTED VPS OPERATIONS
Build, deploy and operate your VPS from the tools you already use.
-
- GroundControl gives people and AI agents controlled access to deployment, runtime, terminal, domains and infrastructure operations without handing the conversation your server credentials.
-
+
GroundControl gives people and AI agents controlled access to deployment, runtime, terminal, domains and infrastructure operations without handing the conversation your server credentials.
-
- Open source
- Self-hosted
- Docker native
- Agent ready
-
+
Open source Self-hosted Docker native Agent ready
-
-
-
- Real session. ChatGPT authenticated to GroundControl, then used the capabilities exposed by the product while GroundControl handled the VPS connection.
-
-
-
-
-
-
-
WHY GROUND CONTROL
-
-
Authenticate once. Keep building.
-
- In ChatGPT, GroundControl can surface its login flow, let you authenticate, and then expose the operational tools the session is allowed to use. From there the work can continue through deployments, the terminal, runtime inspection and verification without a local build machine or manual SSH hop for every task.
-
+
+ REAL WORKFLOW · CHATGPT × GROUND CONTROL
+ Authenticate once, then keep the work inside the conversation.
+
+
01 ChatGPT identifies GroundControl's login flow and brings up authentication.
+
02 You sign in to your GroundControl instance. Server credentials are not pasted into chat.
+
03 ChatGPT uses the tools GroundControl exposes to build, deploy, inspect and operate services.
+
04 GroundControl's onboard terminal and live host state stay available for diagnosis and verification.
-
+
Open the live product
+
+
WHY GROUND CONTROL
No local build box required. Work can continue through deployments, runtime inspection, domains and the terminal without spinning up resources on your laptop, manually SSHing for every step, or jumping into GitHub just to trigger a deployment.
+
-
-
THE PRODUCT
-
One control plane for the full life of an app on your VPS.
-
-
- {features.map((feature) => (
-
- {feature.title}
- {feature.copy}
- {feature.meta}
-
- ))}
-
+ THE PRODUCT
One control plane for the full life of an app on your VPS.
+ {features.map((feature) =>
{feature.title} {feature.copy}
{feature.meta} )}
-
-
-
-
FROM REQUEST TO RUNNING SERVICE
-
Less tab-switching. More finished work.
-
- Use the GroundControl UI directly, or let an authenticated agent work through the tools GroundControl exposes. Repositories, build context, deployment targets, live host state and the terminal meet in one operating surface.
-
-
-
- 01 Connect Install GroundControl on your VPS and connect the services you actually need.
- 02 Describe or configure Choose a template, use an existing Compose file, or tell an agent what you want deployed.
- 03 Build and deploy GroundControl runs the deployment against your infrastructure and keeps the evidence visible.
- 04 Verify and operate Check the public route, inspect runtime state, use the terminal, investigate failures and redeploy.
-
-
-
+
+
FROM REQUEST TO RUNNING SERVICE
Less tab-switching. More finished work. Use the GroundControl UI directly, or let an authenticated agent work through the tools GroundControl exposes. Repositories, build context, deployment targets, live host state and the terminal meet in one operating surface.
+
+ 01 Connect Install GroundControl on your VPS and connect only the services your workflow needs.
+ 02 Describe or configure Choose a template, use an existing Compose file, or tell an agent what you want deployed.
+ 03 Build and deploy GroundControl runs the deployment against your infrastructure and keeps the evidence visible.
+ 04 Verify and operate Check the public route, inspect runtime state, use the terminal, investigate failures and redeploy.
+
+
-
-
-
PRODUCTION TEMPLATES
-
Start from a real deployment shape, not an empty form.
-
-
- Templates encode service relationships, domains, health checks, secrets and supporting infrastructure so common production stacks can be deployed intentionally.
-
-
-
- {templates.map(([name, id, copy]) => (
-
-
-
{name}
- {id}
-
- {copy}
-
- ))}
-
+ PRODUCTION TEMPLATES
Start from a real deployment shape, not an empty form. Templates encode service relationships, domains, health checks, secrets and supporting infrastructure so common production stacks can be deployed intentionally.
+ {templates.map(([name, id, copy]) =>
{name} {id}{copy}
)}
-
-
-
CAPABILITIES, NOT CREDENTIALS
-
Your infrastructure credentials stay with GroundControl. The client gets the operations you choose to expose.
-
- GroundControl is single-tenant and self-hosted. It can run on the same VPS it manages, talk to additional hosts, and perform host-level work through its own controlled execution layer.
-
-
-
+
CAPABILITIES, NOT CREDENTIALS
Your infrastructure credentials stay with GroundControl. The client gets the operations you choose to expose. GroundControl is single-tenant and self-hosted. It can run on the same VPS it manages, talk to additional hosts, and perform host-level work through its controlled execution layer.
-
-
-
INSTALL
-
Put GroundControl on your VPS.
-
-
- Start with the installer, complete setup on your instance, then connect GitHub, Cloudflare or additional servers only when your workflow needs them.
-
-
+ INSTALL
Put GroundControl on your VPS. Start with the installer, complete setup on your instance, then connect GitHub, Cloudflare or additional servers only when your workflow needs them.
-
- TRY IT ON YOUR OWN VPS
- Build there. Deploy there. Operate there.
-
-
+
TRY IT ON YOUR OWN VPS
Build there. Deploy there. Operate there.
-
+
);
}
From 69d5d734ab5f13daf0c613fbdd8311799d719f37 Mon Sep 17 00:00:00 2001
From: Edward Twumasi <45181928+teckedd-code2save@users.noreply.github.com>
Date: Sat, 29 Aug 2026 10:49:50 +0000
Subject: [PATCH 4/7] style: add real workflow proof panel
---
app/session.css | 41 +++++++++++++++++++++++++++++++++++++++++
1 file changed, 41 insertions(+)
create mode 100644 app/session.css
diff --git a/app/session.css b/app/session.css
new file mode 100644
index 0000000..0b151b5
--- /dev/null
+++ b/app/session.css
@@ -0,0 +1,41 @@
+.hero-session {
+ padding: clamp(28px, 4vw, 42px);
+ border: 1px solid var(--line-strong);
+ background: var(--deep);
+ box-shadow: 0 28px 80px rgba(0,0,0,.35);
+}
+.hero-session h2 {
+ margin: 18px 0 28px;
+ font-size: clamp(28px, 3.1vw, 44px);
+ line-height: 1.03;
+ letter-spacing: -.045em;
+ font-weight: 520;
+}
+.session-steps { border-top: 1px solid var(--line); }
+.session-steps p {
+ margin: 0;
+ padding: 17px 0;
+ display: grid;
+ grid-template-columns: 36px 1fr;
+ gap: 14px;
+ border-bottom: 1px solid var(--line);
+ color: var(--muted);
+ font-size: 13px;
+ line-height: 1.55;
+}
+.session-steps span {
+ color: var(--accent-bright);
+ font: 600 9px/1.5 var(--font-geist-mono);
+}
+.hero-session > a {
+ display: inline-flex;
+ gap: 10px;
+ margin-top: 24px;
+ color: #d5d9d5;
+ font: 600 10px/1 var(--font-geist-mono);
+}
+.hero-session > a:hover { color: white; }
+
+@media (max-width: 980px) {
+ .hero-session { max-width: 820px; }
+}
From 213a96105fdb47db79eb6708aa0065c9010a645a Mon Sep 17 00:00:00 2001
From: Edward Twumasi <45181928+teckedd-code2save@users.noreply.github.com>
Date: Sat, 29 Aug 2026 10:49:59 +0000
Subject: [PATCH 5/7] chore: update product metadata and session styles
---
app/layout.tsx | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/app/layout.tsx b/app/layout.tsx
index def2c8e..21239c9 100644
--- a/app/layout.tsx
+++ b/app/layout.tsx
@@ -1,6 +1,7 @@
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";
+import "./session.css";
const geistSans = Geist({
variable: "--font-geist-sans",
@@ -13,9 +14,9 @@ const geistMono = Geist_Mono({
});
export const metadata: Metadata = {
- title: "GroundControl — Self-hosted VPS operations",
+ title: "GroundControl — Build, deploy and operate your VPS",
description:
- "Install a self-hosted control plane for deployments, runtime evidence and infrastructure operations on your VPS.",
+ "Open-source, self-hosted VPS operations for deployments, runtime, terminal, domains, templates and AI-agent workflows.",
other: {
"codex-preview": "development",
},
From f62bee98880f70681a98d4ccc6df971d09a755d8 Mon Sep 17 00:00:00 2001
From: Edward Twumasi <45181928+teckedd-code2save@users.noreply.github.com>
Date: Sat, 29 Aug 2026 10:50:11 +0000
Subject: [PATCH 6/7] test: cover product-led homepage content
---
app/page.test.tsx | 57 ++++++++++++++++++++++-------------------------
1 file changed, 27 insertions(+), 30 deletions(-)
diff --git a/app/page.test.tsx b/app/page.test.tsx
index 383d727..cdbcc85 100644
--- a/app/page.test.tsx
+++ b/app/page.test.tsx
@@ -3,70 +3,67 @@ import { fireEvent, render, screen, waitFor } from "@testing-library/react";
import Home from "@/app/page";
const GITHUB_URL = "https://github.com/teckedd-code2save/groundcontrol";
+const PRODUCT_URL = "https://groundcontrol.serendepify.com";
describe("Home page", () => {
- it("renders the hero heading and primary install CTA", () => {
+ it("renders the product-led hero and live product CTA", () => {
render(
);
expect(
- screen.getByRole("heading", { level: 1, name: /operational co-pilot/i }),
+ screen.getByRole("heading", { level: 1, name: /build, deploy and operate your vps/i }),
).toBeInTheDocument();
- expect(
- screen.getByRole("link", { name: /install on your vps/i }),
- ).toHaveAttribute("href", "#install");
+ expect(screen.getAllByRole("link", { name: /open groundcontrol/i })[0]).toHaveAttribute(
+ "href",
+ PRODUCT_URL,
+ );
});
it("points every GitHub link at the GroundControl product repo", () => {
render(
);
const githubLinks = screen.getAllByRole("link", { name: /github/i });
expect(githubLinks.length).toBeGreaterThan(0);
- for (const link of githubLinks) {
- expect(link).toHaveAttribute("href", GITHUB_URL);
- }
+ for (const link of githubLinks) expect(link).toHaveAttribute("href", GITHUB_URL);
+ });
+
+ it("markets real GroundControl capabilities", () => {
+ render(
);
+ expect(screen.getByText("Deployments")).toBeInTheDocument();
+ expect(screen.getByText("Runtime")).toBeInTheDocument();
+ expect(screen.getByText("Terminal")).toBeInTheDocument();
+ expect(screen.getByText("Intelligence")).toBeInTheDocument();
+ expect(screen.getByText("Domains & edge")).toBeInTheDocument();
});
- it("renders the three capability cards", () => {
+ it("shows production deployment templates", () => {
render(
);
- expect(screen.getByText("See the whole host")).toBeInTheDocument();
- expect(screen.getByText("Deploy with context")).toBeInTheDocument();
- expect(screen.getByText("Investigate from evidence")).toBeInTheDocument();
+ expect(screen.getByText("Existing Compose")).toBeInTheDocument();
+ expect(screen.getByText("Odoo Community")).toBeInTheDocument();
+ expect(screen.getByText("Next.js SaaS")).toBeInTheDocument();
+ expect(screen.getByText("FastAPI + worker")).toBeInTheDocument();
});
it("shows the remote install command by default", () => {
render(
);
- expect(screen.getByRole("tab", { name: /remote/i })).toHaveAttribute(
- "aria-selected",
- "true",
- );
+ expect(screen.getByRole("tab", { name: /remote/i })).toHaveAttribute("aria-selected", "true");
expect(screen.getByText(/root@YOUR_VPS_IP/)).toBeInTheDocument();
});
it("switches the install command when another tab is selected", () => {
render(
);
fireEvent.click(screen.getByRole("tab", { name: /guided/i }));
- expect(screen.getByRole("tab", { name: /guided/i })).toHaveAttribute(
- "aria-selected",
- "true",
- );
+ expect(screen.getByRole("tab", { name: /guided/i })).toHaveAttribute("aria-selected", "true");
expect(screen.getByText(/--interactive/)).toBeInTheDocument();
expect(screen.queryByText(/root@YOUR_VPS_IP/)).not.toBeInTheDocument();
});
it("copies the active command to the clipboard", async () => {
const writeText = vi.fn().mockResolvedValue(undefined);
- Object.defineProperty(navigator, "clipboard", {
- value: { writeText },
- configurable: true,
- });
+ Object.defineProperty(navigator, "clipboard", { value: { writeText }, configurable: true });
render(
);
- fireEvent.click(
- screen.getByRole("button", { name: "Copy installation command" }),
- );
+ fireEvent.click(screen.getByRole("button", { name: "Copy installation command" }));
await waitFor(() => expect(writeText).toHaveBeenCalledTimes(1));
- expect(writeText).toHaveBeenCalledWith(
- expect.stringContaining("root@YOUR_VPS_IP"),
- );
+ expect(writeText).toHaveBeenCalledWith(expect.stringContaining("root@YOUR_VPS_IP"));
expect(screen.getByText("COPIED")).toBeInTheDocument();
});
});
From 09faff95c80ea57a92f3a24018ba9e5dcb245c5c Mon Sep 17 00:00:00 2001
From: Edward Twumasi <45181928+teckedd-code2save@users.noreply.github.com>
Date: Sat, 29 Aug 2026 10:50:26 +0000
Subject: [PATCH 7/7] docs: define truthful GroundControl product media capture
plan
---
MEDIA_CAPTURE.md | 80 ++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 80 insertions(+)
create mode 100644 MEDIA_CAPTURE.md
diff --git a/MEDIA_CAPTURE.md b/MEDIA_CAPTURE.md
new file mode 100644
index 0000000..2436c64
--- /dev/null
+++ b/MEDIA_CAPTURE.md
@@ -0,0 +1,80 @@
+# GroundControl marketing media capture plan
+
+The public site should only use real product captures. Do not add invented dashboards, fake metrics, synthetic terminal output, or decorative UI mockups.
+
+## Capture set
+
+### 1. Agent workflow hero
+Use the real ChatGPT + GroundControl session showing the authenticated GroundControl terminal while ChatGPT is working through deployment/investigation tasks.
+
+Purpose: prove the core story that a user can authenticate once and let ChatGPT use GroundControl capabilities without pasting VPS credentials into the conversation.
+
+Recommended format: 16:10 or 3:2 screenshot, 1400–1800px wide, WebP. Crop browser chrome only if it does not remove important context.
+
+### 2. Templates
+Route: `/templates`
+
+Capture:
+- template browser / choose step
+- `vps-caddy-existing-compose`
+- Odoo Community template
+- one production-shaped SaaS template such as Next.js + PostgreSQL + Redis
+- review/deploy step with real fields and environment requirements visible
+
+Purpose: demonstrate that templates are deployment workflows, not boilerplate cards.
+
+### 3. Deployment progress
+Route: `/deployments`
+
+Capture a real deployment moving through source/config/build/deploy/verify, preferably one that finishes successfully and shows the public route verification.
+
+Recommended format: short 8–15 second MP4/WebM plus a still frame.
+
+### 4. Runtime
+Route: `/runtime`
+
+Capture a real running service with container state, ports, health and resource details visible.
+
+Purpose: show the connection between what was deployed and what is actually running.
+
+### 5. Terminal
+Route: `/terminal`
+
+Capture the onboard terminal running a harmless real host command such as container listing, process inspection or checking a service.
+
+Do not expose secrets, SSH material, tokens, private environment values or sensitive host data.
+
+### 6. Intelligence
+Route: `/intelligence`
+
+Capture a real investigation with:
+- selected failing endpoint or service
+- visible evidence gathering
+- exact failure reason
+- actionable next step / recovery action
+- verification after the action where available
+
+Purpose: sell GroundControl as an operational system, not merely a dashboard.
+
+### 7. Domains / Cloudflare
+Capture a real deployment showing domain configuration and external HTTPS verification. Hide account identifiers or zone details that should not be public.
+
+## Editing rules
+
+- Prefer real screenshots and short recordings over illustrations.
+- Crop for focus, not to manufacture a different product state.
+- Redact secrets rather than replacing them with fictional values.
+- Never fabricate success states, counts or metrics for marketing.
+- Keep typography and UI untouched inside screenshots so captures remain recognizable as GroundControl.
+- Compress stills to WebP and recordings to MP4/WebM before committing to `public/product/`.
+
+## Suggested page order once media is available
+
+1. Hero copy + real ChatGPT/GroundControl session
+2. Real deployment clip
+3. Product capability grid
+4. Templates capture strip
+5. Runtime + terminal paired captures
+6. Intelligence investigation clip
+7. Ownership/security explanation
+8. Installer + open-source CTA