Commit d43b626

Eric Bower  ·  2026-05-09 11:37:27 -0400 EDT
parent b1eeb51
feat: website
2 files changed,  +897, -1
M pico.sh
+1, -1
1@@ -1,7 +1,7 @@
2 #!/usr/bin/env bash
3 set -euo pipefail
4 
5-ZMX_SESSION_PREFIX="${ZMX_SESSION_PREFIX:-ci.}"
6+export ZMX_SESSION_PREFIX="${ZMX_SESSION_PREFIX:-ci.pici}"
7 JOB_ID="${PICO_CI_JOB_ID:-local}"
8 REPO="${PICO_CI_REPO:-unknown}"
9 EVENT_TYPE="${PICO_CI_EVENT_TYPE:-manual}"
A website/index.html
+896, -0
  1@@ -0,0 +1,896 @@
  2+<!DOCTYPE html>
  3+<html lang="en">
  4+<head>
  5+<meta charset="UTF-8">
  6+<meta name="viewport" content="width=device-width, initial-scale=1.0">
  7+<title>pici — Your Build Isn't a Log. It's a Terminal.</title>
  8+<style>
  9+/* ============================================================
 10+   pici landing page — terminal aesthetic
 11+   Palette: Catppuccin Mocha + terminal green accents
 12+   ============================================================ */
 13+
 14+*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
 15+
 16+:root {
 17+  --bg:        #1e1e2e;
 18+  --bg-deep:   #11111b;
 19+  --surface:   #313244;
 20+  --text:      #cdd6f4;
 21+  --text-dim:  #a6adc8;
 22+  --blue:      #89b4fa;
 23+  --green:     #a6e3a1;
 24+  --red:       #f38ba8;
 25+  --yellow:    #f9e2af;
 26+  --mauve:     #f5c2e7;
 27+  --mono:      ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
 28+}
 29+
 30+html { scroll-behavior: smooth; }
 31+
 32+body {
 33+  font-family: var(--mono);
 34+  background: var(--bg);
 35+  color: var(--text);
 36+  font-size: 15px;
 37+  line-height: 1.6;
 38+  overflow-x: hidden;
 39+}
 40+
 41+/* Scanline overlay */
 42+body::after {
 43+  content: "";
 44+  position: fixed;
 45+  inset: 0;
 46+  pointer-events: none;
 47+  background: repeating-linear-gradient(
 48+    0deg,
 49+    transparent,
 50+    transparent 2px,
 51+    rgba(0, 0, 0, 0.03) 2px,
 52+    rgba(0, 0, 0, 0.03) 4px
 53+  );
 54+  z-index: 9999;
 55+}
 56+
 57+a { color: var(--blue); text-decoration: none; }
 58+a:hover { text-decoration: underline; }
 59+
 60+/* ============================================================
 61+   Layout
 62+   ============================================================ */
 63+
 64+.container {
 65+  max-width: 960px;
 66+  margin: 0 auto;
 67+  padding: 0 1.5rem;
 68+}
 69+
 70+section {
 71+  padding: 5rem 0;
 72+  border-bottom: 1px solid var(--surface);
 73+}
 74+
 75+/* ============================================================
 76+   Navigation
 77+   ============================================================ */
 78+
 79+nav {
 80+  position: sticky;
 81+  top: 0;
 82+  z-index: 100;
 83+  background: var(--bg-deep);
 84+  border-bottom: 1px solid var(--surface);
 85+  padding: 0.75rem 0;
 86+}
 87+
 88+nav .container {
 89+  display: flex;
 90+  align-items: center;
 91+  justify-content: space-between;
 92+}
 93+
 94+nav .logo {
 95+  font-size: 1.1rem;
 96+  font-weight: 700;
 97+  color: var(--green);
 98+}
 99+
100+nav .logo span { color: var(--text-dim); font-weight: 400; }
101+
102+nav ul {
103+  list-style: none;
104+  display: flex;
105+  gap: 1.5rem;
106+}
107+
108+nav ul a {
109+  color: var(--text-dim);
110+  font-size: 0.85rem;
111+  text-transform: uppercase;
112+  letter-spacing: 0.05em;
113+}
114+
115+nav ul a:hover { color: var(--text); text-decoration: none; }
116+
117+/* ============================================================
118+   Hero
119+   ============================================================ */
120+
121+.hero {
122+  padding: 7rem 0 5rem;
123+  text-align: center;
124+  border-bottom: 1px solid var(--surface);
125+}
126+
127+.hero h1 {
128+  font-size: clamp(1.8rem, 5vw, 2.8rem);
129+  font-weight: 700;
130+  line-height: 1.2;
131+  margin-bottom: 1rem;
132+}
133+
134+.hero h1 .accent { color: var(--green); }
135+
136+.hero .subtitle {
137+  font-size: 1.05rem;
138+  color: var(--text-dim);
139+  max-width: 600px;
140+  margin: 0 auto 2rem;
141+}
142+
143+.hero .cta-row {
144+  display: flex;
145+  gap: 1rem;
146+  justify-content: center;
147+  flex-wrap: wrap;
148+}
149+
150+.btn {
151+  display: inline-block;
152+  padding: 0.7rem 1.5rem;
153+  border-radius: 4px;
154+  font-family: var(--mono);
155+  font-size: 0.9rem;
156+  font-weight: 600;
157+  cursor: pointer;
158+  border: 1px solid transparent;
159+  transition: all 0.15s ease;
160+}
161+
162+.btn-primary {
163+  background: var(--green);
164+  color: var(--bg-deep);
165+}
166+
167+.btn-primary:hover {
168+  background: #b8f0b3;
169+  text-decoration: none;
170+}
171+
172+.btn-secondary {
173+  background: transparent;
174+  color: var(--text);
175+  border-color: var(--surface);
176+}
177+
178+.btn-secondary:hover {
179+  border-color: var(--text-dim);
180+  text-decoration: none;
181+}
182+
183+/* ============================================================
184+   Terminal window component
185+   ============================================================ */
186+
187+.terminal {
188+  background: var(--bg-deep);
189+  border: 1px solid var(--surface);
190+  border-radius: 8px;
191+  overflow: hidden;
192+  margin: 2rem 0;
193+}
194+
195+.terminal-bar {
196+  display: flex;
197+  align-items: center;
198+  gap: 0.5rem;
199+  padding: 0.6rem 1rem;
200+  background: var(--surface);
201+  font-size: 0.75rem;
202+  color: var(--text-dim);
203+}
204+
205+.terminal-bar .dot {
206+  width: 10px;
207+  height: 10px;
208+  border-radius: 50%;
209+  display: inline-block;
210+}
211+
212+.terminal-bar .dot.red    { background: var(--red); }
213+.terminal-bar .dot.yellow { background: var(--yellow); }
214+.terminal-bar .dot.green  { background: var(--green); }
215+
216+.terminal-body {
217+  padding: 1rem 1.25rem;
218+  font-size: 0.85rem;
219+  line-height: 1.7;
220+  overflow-x: auto;
221+}
222+
223+.terminal-body .prompt { color: var(--green); }
224+.terminal-body .cmd    { color: var(--text); }
225+.terminal-body .output { color: var(--text-dim); }
226+.terminal-body .error  { color: var(--red); }
227+.terminal-body .info   { color: var(--blue); }
228+.terminal-body .comment { color: #6c7086; }
229+.terminal-body .cursor {
230+  display: inline-block;
231+  width: 8px;
232+  height: 1em;
233+  background: var(--green);
234+  vertical-align: text-bottom;
235+  animation: blink 1s step-end infinite;
236+}
237+
238+@keyframes blink {
239+  50% { opacity: 0; }
240+}
241+
242+/* ============================================================
243+   Section headers
244+   ============================================================ */
245+
246+.section-label {
247+  font-size: 0.75rem;
248+  text-transform: uppercase;
249+  letter-spacing: 0.1em;
250+  color: var(--mauve);
251+  margin-bottom: 0.5rem;
252+}
253+
254+h2 {
255+  font-size: 1.5rem;
256+  font-weight: 700;
257+  margin-bottom: 1rem;
258+  color: var(--text);
259+}
260+
261+h3 {
262+  font-size: 1.1rem;
263+  font-weight: 600;
264+  margin-bottom: 0.5rem;
265+  color: var(--blue);
266+}
267+
268+p { margin-bottom: 1rem; }
269+
270+/* ============================================================
271+   Two-column layout
272+   ============================================================ */
273+
274+.two-col {
275+  display: grid;
276+  grid-template-columns: 1fr 1fr;
277+  gap: 3rem;
278+  align-items: start;
279+}
280+
281+@media (max-width: 700px) {
282+  .two-col { grid-template-columns: 1fr; }
283+}
284+
285+/* ============================================================
286+   Feature grid
287+   ============================================================ */
288+
289+.feature-grid {
290+  display: grid;
291+  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
292+  gap: 1.5rem;
293+  margin-top: 2rem;
294+}
295+
296+.feature-card {
297+  background: var(--bg-deep);
298+  border: 1px solid var(--surface);
299+  border-radius: 8px;
300+  padding: 1.5rem;
301+}
302+
303+.feature-card h3 { margin-top: 0.5rem; }
304+.feature-card .icon { font-size: 1.5rem; }
305+
306+/* ============================================================
307+   Image placeholder
308+   ============================================================ */
309+
310+.img-placeholder {
311+  background: var(--bg-deep);
312+  border: 2px dashed var(--surface);
313+  border-radius: 8px;
314+  display: flex;
315+  flex-direction: column;
316+  align-items: center;
317+  justify-content: center;
318+  min-height: 250px;
319+  color: var(--text-dim);
320+  font-size: 0.8rem;
321+  text-align: center;
322+  padding: 2rem;
323+}
324+
325+.img-placeholder .icon { font-size: 2rem; margin-bottom: 0.5rem; }
326+.img-placeholder .label { font-size: 0.85rem; color: var(--mauve); }
327+.img-placeholder .dims { font-size: 0.7rem; color: #6c7086; margin-top: 0.25rem; }
328+
329+/* ============================================================
330+   Code comparison (old vs new)
331+   ============================================================ */
332+
333+.code-compare {
334+  display: grid;
335+  grid-template-columns: 1fr 1fr;
336+  gap: 1.5rem;
337+}
338+
339+@media (max-width: 700px) {
340+  .code-compare { grid-template-columns: 1fr; }
341+}
342+
343+.code-compare .label {
344+  font-size: 0.7rem;
345+  text-transform: uppercase;
346+  letter-spacing: 0.1em;
347+  color: var(--text-dim);
348+  margin-bottom: 0.5rem;
349+}
350+
351+.code-compare .label.bad { color: var(--red); }
352+.code-compare .label.good { color: var(--green); }
353+
354+/* ============================================================
355+   CTA / signup section
356+   ============================================================ */
357+
358+.cta-section {
359+  text-align: center;
360+  padding: 5rem 0;
361+  border-bottom: none;
362+}
363+
364+.cta-section h2 {
365+  font-size: 1.8rem;
366+  margin-bottom: 0.5rem;
367+}
368+
369+.cta-section .subtitle {
370+  color: var(--text-dim);
371+  margin-bottom: 2rem;
372+}
373+
374+.signup-form {
375+  display: flex;
376+  gap: 0.5rem;
377+  justify-content: center;
378+  flex-wrap: wrap;
379+  max-width: 500px;
380+  margin: 0 auto;
381+}
382+
383+.signup-form input[type="email"] {
384+  flex: 1;
385+  min-width: 200px;
386+  padding: 0.7rem 1rem;
387+  border-radius: 4px;
388+  border: 1px solid var(--surface);
389+  background: var(--bg-deep);
390+  color: var(--text);
391+  font-family: var(--mono);
392+  font-size: 0.9rem;
393+  outline: none;
394+}
395+
396+.signup-form input[type="email"]:focus {
397+  border-color: var(--green);
398+}
399+
400+.signup-form input[type="email"]::placeholder {
401+  color: #6c7086;
402+}
403+
404+.signup-form .btn {
405+  white-space: nowrap;
406+}
407+
408+.signup-note {
409+  font-size: 0.75rem;
410+  color: #6c7086;
411+  margin-top: 1rem;
412+}
413+
414+/* ============================================================
415+   Footer
416+   ============================================================ */
417+
418+footer {
419+  padding: 2rem 0;
420+  border-top: 1px solid var(--surface);
421+  text-align: center;
422+  font-size: 0.8rem;
423+  color: var(--text-dim);
424+}
425+
426+footer a { color: var(--blue); }
427+
428+/* ============================================================
429+   Inline code
430+   ============================================================ */
431+
432+code {
433+  background: var(--surface);
434+  color: var(--mauve);
435+  padding: 0.15em 0.4em;
436+  border-radius: 3px;
437+  font-size: 0.85em;
438+  font-family: var(--mono);
439+}
440+
441+/* ============================================================
442+   Highlighted list
443+   ============================================================ */
444+
445+.check-list {
446+  list-style: none;
447+  padding: 0;
448+}
449+
450+.check-list li {
451+  padding: 0.3rem 0;
452+  padding-left: 1.5rem;
453+  position: relative;
454+}
455+
456+.check-list li::before {
457+  content: "✓";
458+  position: absolute;
459+  left: 0;
460+  color: var(--green);
461+  font-weight: 700;
462+}
463+
464+/* ============================================================
465+   Diagram / flow
466+   ============================================================ */
467+
468+.flow {
469+  display: flex;
470+  align-items: center;
471+  justify-content: center;
472+  gap: 0.5rem;
473+  flex-wrap: wrap;
474+  margin: 2rem 0;
475+  font-size: 0.85rem;
476+}
477+
478+.flow .node {
479+  background: var(--surface);
480+  padding: 0.5rem 1rem;
481+  border-radius: 4px;
482+  font-size: 0.8rem;
483+}
484+
485+.flow .arrow {
486+  color: var(--text-dim);
487+  font-size: 1.2rem;
488+}
489+</style>
490+</head>
491+<body>
492+
493+<!-- ============================================================
494+     NAV
495+     ============================================================ -->
496+<nav>
497+  <div class="container">
498+    <div class="logo">pici <span>— terminal-first CI</span></div>
499+    <ul>
500+      <li><a href="#how">How It Works</a></li>
501+      <li><a href="#features">Features</a></li>
502+      <li><a href="#beta">Beta</a></li>
503+    </ul>
504+  </div>
505+</nav>
506+
507+<!-- ============================================================
508+     HERO
509+     ============================================================ -->
510+<section class="hero">
511+  <div class="container">
512+    <h1>Your Build Isn't a Log.<br><span class="accent">It's a Terminal.</span></h1>
513+    <p class="subtitle">
514+      <code>pici</code> is a CI system where every build step runs in an attachable terminal session.
515+      Your pipeline is a bash script. It runs the same locally and in CI.
516+    </p>
517+    <div class="cta-row">
518+      <a href="#beta" class="btn btn-primary">Sign Up for Beta</a>
519+      <a href="https://github.com/picosh/pici" class="btn btn-secondary">View on GitHub</a>
520+    </div>
521+  </div>
522+</section>
523+
524+<!-- ============================================================
525+     PROBLEM — The CI Pain
526+     ============================================================ -->
527+<section id="problem">
528+  <div class="container">
529+    <div class="two-col">
530+      <div>
531+        <p class="section-label">The Problem</p>
532+        <h2>CI gives you logs. Not answers.</h2>
533+        <p>
534+          A build fails. You scroll through 2,000 lines of log output. You guess what went wrong.
535+          You re-run the job and wait 10 minutes to find out you were wrong. Repeat.
536+        </p>
537+        <p>
538+          Debugging CI should not be an archaeological dig through log files in a browser.
539+        </p>
540+      </div>
541+      <div class="terminal">
542+        <div class="terminal-bar">
543+          <span class="dot red"></span>
544+          <span class="dot yellow"></span>
545+          <span class="dot green"></span>
546+          <span style="margin-left: 0.5rem;">ci-myrepo-test — build log</span>
547+        </div>
548+        <div class="terminal-body">
549+          <div class="output">Line 1847: ...</div>
550+          <div class="output">Line 1848: ...</div>
551+          <div class="output">Line 1849: ...</div>
552+          <div class="error">Line 1850: FAIL: test_connect (db.TimeoutError)</div>
553+          <div class="output">Line 1851: ...</div>
554+          <div class="output">Line 1852: ...</div>
555+          <div class="output">...</div>
556+          <div class="comment"># scroll up... scroll up... scroll up...</div>
557+          <div class="comment"># what was the state before this?</div>
558+          <div class="comment"># re-run the job? wait 10 more minutes?</div>
559+        </div>
560+      </div>
561+    </div>
562+  </div>
563+</section>
564+
565+<!-- ============================================================
566+     SOLUTION — Attach
567+     ============================================================ -->
568+<section id="how">
569+  <div class="container">
570+    <p class="section-label">The Solution</p>
571+    <h2>Jump into your failing build.</h2>
572+    <div class="two-col">
573+      <div>
574+        <p>
575+          Every build step in pici runs as a <strong>real terminal session</strong> — a PTY you can attach to.
576+          No "enable debugging" flag. No "re-run with SSH" button. No waiting for a tunnel.
577+        </p>
578+        <p>
579+          A test fails? Attach to the session, press <code>↑</code> + <code>Enter</code> to rerun the command,
580+          inspect the environment, fix the issue. You're already there.
581+        </p>
582+        <ul class="check-list">
583+          <li><code>zmx attach ci.myrepo.test</code> — you're in</li>
584+          <li>Press <code>↑</code> to rerun the failing command</li>
585+          <li>Inspect files, env vars, network state</li>
586+          <li>No debug mode — it's just a terminal</li>
587+        </ul>
588+      </div>
589+      <div>
590+        <!-- IMAGE PLACEHOLDER: screenshot of zmx attach to a failing session -->
591+        <div class="img-placeholder">
592+          <div class="icon">🖥️</div>
593+          <div class="label">Screenshot: attaching to a failing build session</div>
594+          <div class="dims">~600×400 — show terminal with zmx attach output</div>
595+        </div>
596+      </div>
597+    </div>
598+  </div>
599+</section>
600+
601+<!-- ============================================================
602+     JOB ENGINE — zmx as the parallel task runner
603+     ============================================================ -->
604+<section id="engine">
605+  <div class="container">
606+    <p class="section-label">Job Engine</p>
607+    <h2>Parallel tasks. Zero config. Same commands everywhere.</h2>
608+    <div class="two-col">
609+      <div class="terminal">
610+        <div class="terminal-bar">
611+          <span class="dot red"></span>
612+          <span class="dot yellow"></span>
613+          <span class="dot green"></span>
614+          <span style="margin-left: 0.5rem;">pico.sh</span>
615+        </div>
616+        <div class="terminal-body">
617+          <div><span class="comment">#!/usr/bin/env bash</span></div>
618+          <div><span class="cmd">set -euo pipefail</span></div>
619+          <div>&nbsp;</div>
620+          <div><span class="comment"># These run in parallel — no config needed</span></div>
621+          <div><span class="cmd">zmx run lint docker run golangci-lint run</span></div>
622+          <div><span class="output">  TODO: add example output</span></div>
623+          <div>&nbsp;</div>
624+          <div><span class="cmd">zmx run test go test ./...</span></div>
625+          <div><span class="output">  TODO: add example output</span></div>
626+          <div>&nbsp;</div>
627+          <div><span class="cmd">zmx run build to build -o bin/pici .</span></div>
628+          <div><span class="output">  TODO: add example output</span></div>
629+          <div>&nbsp;</div>
630+          <div><span class="comment"># Wait for all to finish</span></div>
631+          <div><span class="cmd">zmx wait "*"</span></div>
632+          <div>&nbsp;</div>
633+          <div><span class="comment"># Runs identically locally and in CI</span></div>
634+        </div>
635+      </div>
636+      <div>
637+        <p>
638+          Your pipeline is <code>pico.sh</code>: a bash script that runs <strong>identically on your machine
639+          and on the CI runner</strong>. No YAML, no DSL, no "it works on my machine" gap.
640+        </p>
641+        <p>
642+          <strong>zmx</strong> is the job engine. Each <code>zmx run</code> spawns a parallel terminal session.
643+          <code>zmx wait "*"</code> blocks until they all finish. Run tasks sequentially by just calling them
644+          one after another, or in parallel; it's bash, you control the flow.
645+        </p>
646+        <ul class="check-list">
647+          <li>Same <code>zmx run</code> commands locally and in CI</li>
648+          <li>Parallel by default, sequential when you want it</li>
649+          <li>Every step is an attachable terminal session</li>
650+          <li>No YAML matrices, no <code>run: parallel</code> keywords</li>
651+        </ul>
652+      </div>
653+    </div>
654+  </div>
655+</section>
656+
657+<!-- ============================================================
658+     GIT IS OPTIONAL
659+     ============================================================ -->
660+<section id="no-git">
661+  <div class="container">
662+    <p class="section-label">Trigger Model</p>
663+    <h2>Git is optional. rsync + ssh pubsub is the core.</h2>
664+    <div class="two-col">
665+      <div>
666+        <p>
667+          Most CI systems are glued to Git webhooks. pici isn't. The core loop is simple:
668+          <strong>rsync a workspace</strong> and <strong>publish an event over SSH</strong>.
669+        </p>
670+        <p>
671+          Git post-receive hooks are just one trigger. You can fire builds from anything:
672+          a cron job, a file watcher, a webhook receiver on your own server, a button press.
673+        </p>
674+        <div class="flow">
675+          <div class="node">rsync workspace</div>
676+          <div class="arrow">→</div>
677+          <div class="node">ssh pub event</div>
678+          <div class="arrow">→</div>
679+          <div class="node">pico.sh runs</div>
680+          <div class="arrow">→</div>
681+          <div class="node">artifacts synced</div>
682+        </div>
683+      </div>
684+      <div>
685+        <!-- IMAGE PLACEHOLDER: diagram of the rsync + SSH pubsub flow -->
686+        <div class="img-placeholder">
687+          <div class="icon">📡</div>
688+          <div class="label">Diagram: rsync + SSH pubsub trigger flow</div>
689+          <div class="dims">~600×300 — show workspace → rsync → pipe.pico.sh → runner</div>
690+        </div>
691+      </div>
692+    </div>
693+  </div>
694+</section>
695+
696+<!-- ============================================================
697+     SELF-HOST OR MANAGED
698+     ============================================================ -->
699+<section id="deploy">
700+  <div class="container">
701+    <p class="section-label">Deployment</p>
702+    <h2>Self-host or use our managed service.</h2>
703+    <div class="two-col">
704+      <div class="terminal">
705+        <div class="terminal-bar">
706+          <span class="dot red"></span>
707+          <span class="dot yellow"></span>
708+          <span class="dot green"></span>
709+          <span style="margin-left: 0.5rem;">self-hosted</span>
710+        </div>
711+        <div class="terminal-body">
712+          <div><span class="prompt">$ </span><span class="cmd">go build -o pici .</span></div>
713+          <div><span class="prompt">$ </span><span class="cmd">./pici runner --event '...'</span></div>
714+          <div><span class="info">🚀 starting job ci.myrepo.a3f2b8c1</span></div>
715+          <div><span class="info">📦 syncing workspace</span></div>
716+          <div><span class="info">✅ workspace ready</span></div>
717+          <div><span class="info">🔍 found pico.sh</span></div>
718+          <div><span class="info">🏃 launching sessions...</span></div>
719+          <div><span class="info">✅ job launched</span></div>
720+        </div>
721+      </div>
722+      <div class="terminal">
723+        <div class="terminal-bar">
724+          <span class="dot red"></span>
725+          <span class="dot yellow"></span>
726+          <span class="dot green"></span>
727+          <span style="margin-left: 0.5rem;">ci.pico.sh</span>
728+        </div>
729+        <div class="terminal-body">
730+          <div><span class="prompt">$ </span><span class="cmd">echo '{"type":"release"}' | ssh pipe.pico.sh pub build.event</span></div>
731+          <div><span class="output">subscribe to this channel: ssh pipe.pico.sh sub build.event</span></div>
732+          <div>&nbsp;</div>
733+          <div><span class="info">🚀 starting job ci.myrepo.a3f2b8c1</span></div>
734+          <div><span class="info">📦 syncing workspace</span></div>
735+          <div><span class="info">✅ workspace ready</span></div>
736+          <div><span class="info">🏃 launching sessions...</span></div>
737+          <div><span class="info">✅ job launched</span></div>
738+          <div>&nbsp;</div>
739+          <div><span class="comment"># same pico.sh, same zmx sessions</span></div>
740+          <div><span class="comment"># same attachable debugging</span></div>
741+        </div>
742+      </div>
743+    </div>
744+    <p style="text-align:center; margin-top: 1.5rem; color: var(--text-dim);">
745+      Same <code>pico.sh</code>. Same <code>zmx attach</code>. Same experience. Your infra or ours.
746+    </p>
747+  </div>
748+</section>
749+
750+<!-- ============================================================
751+     FEATURES GRID
752+     ============================================================ -->
753+<section id="features">
754+  <div class="container">
755+    <p class="section-label">Features</p>
756+    <h2>Everything else just works.</h2>
757+    <div class="feature-grid">
758+      <div class="feature-card">
759+        <div class="icon">🔌</div>
760+        <h3>SSH-First</h3>
761+        <p>No HTTP APIs. No webhooks to configure. No OAuth tokens. SSH keys are your auth, SSH pubsub is your event bus.</p>
762+      </div>
763+      <div class="feature-card">
764+        <div class="icon">📊</div>
765+        <h3>JSONL Status Stream</h3>
766+        <p><code>pici monitor | curl -sd"$line" $WEBHOOK</code> — pipe build status to Discord, Slack, or any endpoint.</p>
767+      </div>
768+      <div class="feature-card">
769+        <div class="icon">🧹</div>
770+        <h3>Auto-Cancel on Push</h3>
771+        <p>New commit? The old job is killed automatically. No stale builds wasting resources.</p>
772+      </div>
773+      <div class="feature-card">
774+        <div class="icon">🗑️</div>
775+        <h3>Auto Garbage Collection</h3>
776+        <p>Old sessions cleaned up automatically. No manual cleanup scripts.</p>
777+      </div>
778+      <div class="feature-card">
779+        <div class="icon">📦</div>
780+        <h3>Live Artifacts</h3>
781+        <p>HTML session pages generated during the build, not just after. See progress in real-time.</p>
782+      </div>
783+      <div class="feature-card">
784+        <div class="icon">🔐</div>
785+        <h3>Build Attestation</h3>
786+        <p>Automatic provenance: runner hostname, OS, arch, repo, branch, commit, workspace checksum.</p>
787+      </div>
788+    </div>
789+  </div>
790+</section>
791+
792+<!-- ============================================================
793+     COMPARISON — Other CI vs pici
794+     ============================================================ -->
795+<section id="compare">
796+  <div class="container">
797+    <p class="section-label">Comparison</p>
798+    <h2>Other CI gives you logs. pici gives you a shell.</h2>
799+    <div class="code-compare">
800+      <div>
801+        <div class="label bad">Other CI</div>
802+        <div class="terminal">
803+          <div class="terminal-bar">
804+            <span class="dot red"></span>
805+            <span class="dot yellow"></span>
806+            <span class="dot green"></span>
807+            <span style="margin-left: 0.5rem;">build log</span>
808+          </div>
809+          <div class="terminal-body">
810+            <div class="output">$ npm test</div>
811+            <div class="output">PASS src/utils.test.js</div>
812+            <div class="error">FAIL src/db.test.js</div>
813+            <div class="error">  ● connects to database</div>
814+            <div class="error">    TimeoutError: connect ETIMED OUT</div>
815+            <div class="output">...</div>
816+            <div class="comment"># ...that's all you get.</div>
817+            <div class="comment"># Re-run the job? Wait 12 min.</div>
818+            <div class="comment"># Enable "debug mode"? Where's that?</div>
819+          </div>
820+        </div>
821+      </div>
822+      <div>
823+        <div class="label good">pici</div>
824+        <div class="terminal">
825+          <div class="terminal-bar">
826+            <span class="dot red"></span>
827+            <span class="dot yellow"></span>
828+            <span class="dot green"></span>
829+            <span style="margin-left: 0.5rem;">zmx attach ci.myrepo.test</span>
830+          </div>
831+          <div class="terminal-body">
832+            <div class="output">$ npm test</div>
833+            <div class="output">PASS src/utils.test.js</div>
834+            <div class="error">FAIL src/db.test.js</div>
835+            <div class="error">  ● connects to database</div>
836+            <div class="error">    TimeoutError: connect ETIMED OUT</div>
837+            <div>&nbsp;</div>
838+            <div><span class="comment"># you're already in the session</span></div>
839+            <div><span class="prompt">$ </span><span class="cmd">env | grep DB_HOST</span></div>
840+            <div class="output">DB_HOST=postgres.internal:5432</div>
841+            <div><span class="prompt">$ </span><span class="cmd">nc -zv postgres.internal 5432</span></div>
842+            <div class="output">Connection refused</div>
843+            <div><span class="comment"># ↑ found it — network config issue</span></div>
844+          </div>
845+        </div>
846+      </div>
847+    </div>
848+  </div>
849+</section>
850+
851+<!-- ============================================================
852+     CTA — Sign up for beta
853+     ============================================================ -->
854+<section class="cta-section" id="beta">
855+  <div class="container">
856+    <h2>Get early access to pici.</h2>
857+    <p class="subtitle">
858+      We're opening the managed beta at <code>ci.pico.sh</code>.<br>
859+      Self-hosted is available now on <a href="https://github.com/picosh/pici">GitHub</a>.
860+    </p>
861+    <form class="signup-form" action="#" method="POST" onsubmit="handleSignup(event)">
862+      <input type="email" name="email" placeholder="you@example.com" required autocomplete="email">
863+      <button type="submit" class="btn btn-primary">Sign Up for Beta</button>
864+    </form>
865+    <p class="signup-note">No spam. We'll email you when beta slots open.</p>
866+  </div>
867+</section>
868+
869+<!-- ============================================================
870+     FOOTER
871+     ============================================================ -->
872+<footer>
873+  <div class="container">
874+    <p>
875+      <a href="https://github.com/picosh/pici">GitHub</a> &middot;
876+      <a href="https://pico.sh">pico.sh</a> &middot;
877+      <a href="mailto:hello@pico.sh">hello@pico.sh</a>
878+    </p>
879+    <p style="margin-top: 0.5rem;">
880+      Built by <a href="https://pico.sh">pico</a>. Self-hostable. SSH-first. Terminal-native.
881+    </p>
882+  </div>
883+</footer>
884+
885+<script>
886+function handleSignup(e) {
887+  e.preventDefault();
888+  const form = e.target;
889+  const email = form.email.value;
890+  // TODO: wire up to your backend / pipe.pico.sh / etc.
891+  // For now, just show a confirmation
892+  form.innerHTML = `<p style="color: var(--green); padding: 0.7rem 0;">✓ You're on the list, ${email}.</p>`;
893+}
894+</script>
895+
896+</body>
897+</html>