Skip to content

Commit d15d4e4

Browse files
committed
feat: complete landing page PMF rewrite + analyzer + playground
Complete landing page restructure based on CEO-level PMF review: - Hero: outcome-first ("Run Your Entire Backend on a $20 Server") with density wow-factor and beta badge - Single primary CTA everywhere ("Try in 60 Seconds") - Architecture deep-dive moved up as lead content with share link - Interactive cost calculator (Hetzner pricing, real-time slider) - 60-second quickstart terminal block - Live playground widget (zero-auth deploy via cloud API) - GitHub repo analyzer widget (paste URL, get compatibility report) - Expanded comparison table (+Docker Compose, +Kamal, migration row) - "Runs Anywhere" provider logos (Hetzner, OVH, DO, Vultr, etc.) - "Not for you if" honesty section with counter-positioning - Social proof bar (GitHub stars, 480+ tests, 300+ commits) - GitHub activity feed (live commits, localStorage cache) - Updated FAQ (no pricing contradiction, trust-building) - Pricing page removed (free during beta) - Blog post: "What Docker Actually Runs vs What You Need" Backend: - POST /api/v1/playground/start (zero-auth sandbox deploy) - POST /api/v1/analyze (GitHub repo compatibility analysis) - /pricing → permanent redirect to / - /blog/docker-vs-wasm route
1 parent 4eec557 commit d15d4e4

File tree

13 files changed

+2132
-357
lines changed

13 files changed

+2132
-357
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,5 @@ docs/GTM-BETA-LAUNCH-PLAN.md
1919
docs/IMPLEMENTATION-PLAN-PHASE2.md
2020
warpgrid.toml
2121
tests/fixtures/rust-sqlx-postgres-guest/target/
22+
.gstack/
23+
.context/

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/warpd/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ path = "src/main.rs"
1111

1212
[dependencies]
1313
warp-core.workspace = true
14+
warp-analyzer = { path = "../warp-analyzer" }
1415
warp-runtime = { path = "../warp-runtime" }
1516
warpgrid-state = { path = "../warpgrid-state" }
1617
warpgrid-scheduler = { path = "../warpgrid-scheduler" }
@@ -43,9 +44,9 @@ hex = "0.4"
4344
chrono = { version = "0.4", features = ["serde"] }
4445
toml = "0.8"
4546
libsql = "0.9"
47+
tempfile = "3"
4648

4749
[dev-dependencies]
4850
tower = { version = "0.5", features = ["util"] }
4951
axum = { version = "0.8", features = ["tokio"] }
5052
warpgrid-placement = { path = "../warpgrid-placement" }
51-
tempfile = "3"

crates/warpd/src/cloud/admin.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -832,6 +832,8 @@ mod tests {
832832
usage: UsageTracker::new(),
833833
logs: crate::cloud::routes::new_log_buffer(),
834834
admin_key: None,
835+
playground_rate_limit: crate::cloud::routes::new_playground_rate_limit(),
836+
analyze_rate_limit: crate::cloud::routes::new_playground_rate_limit(),
835837
}
836838
}
837839

crates/warpd/src/cloud/auth.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@ fn generate_api_key() -> String {
242242
}
243243

244244
/// Generate a random user ID.
245-
fn generate_user_id() -> String {
245+
pub fn generate_user_id() -> String {
246246
let mut rng = rand::thread_rng();
247247
let bytes: [u8; 8] = rng.r#gen();
248248
format!("usr_{}", hex::encode(bytes))

crates/warpd/src/cloud/console.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1031,6 +1031,8 @@ mod tests {
10311031
usage: UsageTracker::new(),
10321032
logs: crate::cloud::routes::new_log_buffer(),
10331033
admin_key: None,
1034+
playground_rate_limit: crate::cloud::routes::new_playground_rate_limit(),
1035+
analyze_rate_limit: crate::cloud::routes::new_playground_rate_limit(),
10341036
}
10351037
}
10361038

crates/warpd/src/cloud/landing.rs

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,21 @@
44
//! via `include_str!`. No filesystem access needed at runtime.
55
66
use axum::Router;
7-
use axum::response::Html;
7+
use axum::response::{Html, Redirect};
88
use axum::routing::get;
99

1010
const LANDING_HTML: &str = include_str!("../../../../landing/index.html");
1111
const BENCHMARKS_HTML: &str = include_str!("../../../../landing/benchmarks.html");
12-
const PRICING_HTML: &str = include_str!("../../../../landing/pricing.html");
12+
const BLOG_DOCKER_VS_WASM_HTML: &str =
13+
include_str!("../../../../landing/blog/docker-vs-wasm.html");
1314

1415
/// Build the landing page router.
1516
pub fn landing_router() -> Router {
1617
Router::new()
1718
.route("/", get(landing_page))
1819
.route("/benchmarks", get(benchmarks_page))
19-
.route("/pricing", get(pricing_page))
20+
.route("/pricing", get(pricing_redirect))
21+
.route("/blog/docker-vs-wasm", get(blog_docker_vs_wasm))
2022
}
2123

2224
async fn landing_page() -> Html<&'static str> {
@@ -27,8 +29,12 @@ async fn benchmarks_page() -> Html<&'static str> {
2729
Html(BENCHMARKS_HTML)
2830
}
2931

30-
async fn pricing_page() -> Html<&'static str> {
31-
Html(PRICING_HTML)
32+
async fn pricing_redirect() -> Redirect {
33+
Redirect::permanent("/")
34+
}
35+
36+
async fn blog_docker_vs_wasm() -> Html<&'static str> {
37+
Html(BLOG_DOCKER_VS_WASM_HTML)
3238
}
3339

3440
#[cfg(test)]
@@ -48,9 +54,8 @@ mod tests {
4854
}
4955

5056
#[test]
51-
fn pricing_page_has_proper_styling() {
52-
assert!(PRICING_HTML.contains("--accent: #00e5a0"));
53-
assert!(PRICING_HTML.contains("Outfit"));
54-
assert!(PRICING_HTML.contains("$29"));
57+
fn blog_post_has_content() {
58+
assert!(BLOG_DOCKER_VS_WASM_HTML.contains("Docker"));
59+
assert!(BLOG_DOCKER_VS_WASM_HTML.contains("WarpGrid"));
5560
}
5661
}

0 commit comments

Comments
 (0)