From a3edde11e24276e9b3517f361997d09fbc3b04cf Mon Sep 17 00:00:00 2001 From: Vigneshraj Sekar Babu Date: Sun, 26 Apr 2026 14:40:32 -0700 Subject: [PATCH 1/8] docs: redesign home page with workshop-precision aesthetic Restructures the home page around a calmer, more focused narrative: hero with copyable install command, contrast section reframed as a structural shift, capabilities with a copyable lifecycle.yaml, two-doors split into "run it" and "community". Adds new sections (capabilities, contrast, two-doors, site-footer) and removes the duplicate Features grid that overlapped with Capabilities. Tightens nav hover, theme config, and supporting copy. --- src/components/home/bg/index.tsx | 36 +-- src/components/home/capabilities/index.tsx | 269 +++++++++++++++++++ src/components/home/contrast/index.tsx | 173 ++++++++++++ src/components/home/features/FeatureCard.tsx | 49 ---- src/components/home/features/data.ts | 70 ----- src/components/home/features/index.tsx | 55 ---- src/components/home/features/types.ts | 24 -- src/components/home/hero/HeroContent.tsx | 127 +++++++-- src/components/home/hero/index.tsx | 43 ++- src/components/home/how-it-works/Step.tsx | 154 ++++++++--- src/components/home/how-it-works/data.ts | 16 +- src/components/home/how-it-works/index.tsx | 40 ++- src/components/home/index.tsx | 5 +- src/components/home/two-doors/index.tsx | 161 +++++++++++ src/components/site-footer/index.tsx | 104 +++++++ src/pages/_app.tsx | 5 +- src/pages/_meta.ts | 2 +- src/pages/docs/setup/prerequisites.mdx | 67 ++--- src/pages/index.mdx | 14 +- src/styles/globals.css | 19 +- src/theme.config.tsx | 99 ++----- 21 files changed, 1085 insertions(+), 447 deletions(-) create mode 100644 src/components/home/capabilities/index.tsx create mode 100644 src/components/home/contrast/index.tsx delete mode 100644 src/components/home/features/FeatureCard.tsx delete mode 100644 src/components/home/features/data.ts delete mode 100644 src/components/home/features/index.tsx delete mode 100644 src/components/home/features/types.ts create mode 100644 src/components/home/two-doors/index.tsx create mode 100644 src/components/site-footer/index.tsx diff --git a/src/components/home/bg/index.tsx b/src/components/home/bg/index.tsx index 0b0284bb..39469260 100644 --- a/src/components/home/bg/index.tsx +++ b/src/components/home/bg/index.tsx @@ -18,31 +18,17 @@ import { GridPattern } from "@/components/home/bg/grid"; export const Bg = () => { return ( -
-
-
- -
- -
+ ); }; diff --git a/src/components/home/capabilities/index.tsx b/src/components/home/capabilities/index.tsx new file mode 100644 index 00000000..c803ff32 --- /dev/null +++ b/src/components/home/capabilities/index.tsx @@ -0,0 +1,269 @@ +/** + * Copyright 2025 GoodRx, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +"use client"; + +import { useState } from "react"; +import { motion, type Variants } from "framer-motion"; +import { Check, Copy } from "lucide-react"; +import { cn } from "@/lib/utils"; + +const easeOutQuart = [0.25, 1, 0.5, 1] as const; + +type CopyState = "idle" | "copied" | "failed"; + +const lifecycleYaml = `version: "1.0.0" + +environment: + defaultServices: + - name: "api" + - name: "postgres" + +services: + - name: "api" + github: + repository: "acme/api" + branchName: "main" + docker: + defaultTag: "main" + app: + dockerfilePath: "Dockerfile" + ports: [8080] + env: + DATABASE_URL: "postgresql://app:pw@{{{postgres_internalHostname}}}:5432/appdb" + + - name: "postgres" + docker: + dockerImage: "postgres" + defaultTag: "15-alpine" + ports: [5432] + env: + POSTGRES_USER: "app" + POSTGRES_PASSWORD: "pw" + POSTGRES_DB: "appdb"`; + +const headerStagger: Variants = { + hidden: {}, + visible: { transition: { staggerChildren: 0.08, delayChildren: 0.05 } }, +}; + +const headerItem: Variants = { + hidden: { opacity: 0, y: 10 }, + visible: { + opacity: 1, + y: 0, + transition: { duration: 0.6, ease: easeOutQuart }, + }, +}; + +const capabilities = [ + { + label: "Auto-deploy on pull request", + body: "Push, get an environment. No imperative steps.", + }, + { + label: "Cross-repo composition", + body: "Compose services from multiple repos in one env.", + }, + { + label: "Automatic cleanup", + body: "Merge or close › environment is reclaimed.", + }, + { + label: "Webhooks & automation", + body: "Hook env events into your existing CI/CD.", + }, + { + label: "Mission-control comments", + body: "URLs, status, logs delivered into the pull request.", + }, + { + label: "Debug from chat", + body: "Triage build and deploy failures in a chat-driven agent session.", + }, +]; + +export function Capabilities() { + const [copyState, setCopyState] = useState("idle"); + + const handleCopy = async () => { + try { + await navigator.clipboard.writeText(lifecycleYaml); + setCopyState("copied"); + setTimeout(() => setCopyState("idle"), 1800); + } catch { + setCopyState("failed"); + setTimeout(() => setCopyState("idle"), 2400); + } + }; + + return ( +
+
+ +
+ + {"// what it does"} + + + Connected multi-service. +
+ From one config. +
+
+ + Most preview tools start a container. Lifecycle starts the topology: + frontend, APIs, queues, databases. Each on ephemeral DNS, wired like + prod. + +
+ + +
+ lifecycle.yaml + +
+ +
+            {lifecycleYaml}
+          
+ +

+ {copyState === "copied" + ? "Copied lifecycle.yaml" + : copyState === "failed" + ? "copy blocked · use ⌘C / Ctrl+C" + : ""} +

+ + + + result › + {" "} + + one pull request builds api + postgres, wired through ephemeral + DNS. + + +
+ +
    + {capabilities.map((cap, i) => ( + + + {String(i + 1).padStart(2, "0")} + +
    +

    + {cap.label} +

    +

    + {cap.body} +

    +
    +
    + ))} +
+ + + Self-hosted on your Kubernetes · brings its own controller · no SaaS + lock-in + +
+
+ ); +} + +export default Capabilities; diff --git a/src/components/home/contrast/index.tsx b/src/components/home/contrast/index.tsx new file mode 100644 index 00000000..ead23a21 --- /dev/null +++ b/src/components/home/contrast/index.tsx @@ -0,0 +1,173 @@ +/** + * Copyright 2025 GoodRx, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +"use client"; + +import { motion, type Variants } from "framer-motion"; +import { Check, X } from "lucide-react"; + +const easeOutQuart = [0.25, 1, 0.5, 1] as const; + +const without = [ + "One shared staging. One queue at a time.", + "Local works. Staging doesn't. The drift is the norm.", + "Twelve services, wired by hand, for one PR.", + "Old envs linger. Costs creep. Cleanup is manual.", +]; + +const with_ = [ + "Each pull request opens its own isolated environment.", + "Run the exact branch, against the exact dependencies.", + "Multi-service topology builds itself from one config.", + "Merge or close. The env is gone. No janitor needed.", +]; + +const headerVariants: Variants = { + hidden: { opacity: 0, y: 10 }, + visible: { + opacity: 1, + y: 0, + transition: { duration: 0.6, ease: easeOutQuart }, + }, +}; + +const columnVariants = (delay: number): Variants => ({ + hidden: {}, + visible: { + transition: { + delayChildren: delay, + staggerChildren: 0.07, + }, + }, +}); + +const itemVariants: Variants = { + hidden: { opacity: 0, y: 6 }, + visible: { + opacity: 1, + y: 0, + transition: { duration: 0.45, ease: easeOutQuart }, + }, +}; + +export function Contrast() { + return ( +
+
+ +

+ {"// the shift"} +

+

+ Stop sharing one staging. +
+ + Start shipping in parallel. + +

+
+ +
+ + + + Without Lifecycle + +
    + {without.map((line, i) => ( + + + ))} +
+
+ + + + + With Lifecycle + +
    + {with_.map((line, i) => ( + + + ))} +
+
+
+
+
+ ); +} + +export default Contrast; diff --git a/src/components/home/features/FeatureCard.tsx b/src/components/home/features/FeatureCard.tsx deleted file mode 100644 index c9b49976..00000000 --- a/src/components/home/features/FeatureCard.tsx +++ /dev/null @@ -1,49 +0,0 @@ -/** - * Copyright 2025 GoodRx, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -"use client"; - -import { motion } from "framer-motion"; -import type { Feature } from "./types"; - -interface FeatureCardProps { - feature: Feature; - index: number; -} - -export function FeatureCard({ feature, index }: FeatureCardProps) { - const Icon = feature.icon; - - return ( - -
- -
-

- {feature.title} -

-

- {feature.description} -

-
- ); -} diff --git a/src/components/home/features/data.ts b/src/components/home/features/data.ts deleted file mode 100644 index f19a5614..00000000 --- a/src/components/home/features/data.ts +++ /dev/null @@ -1,70 +0,0 @@ -/** - * Copyright 2025 GoodRx, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - GitPullRequest, - Network, - Trash2, - GitFork, - Webhook, - MessageSquare, -} from "lucide-react"; -import type { Feature } from "./types"; - -export const features: Feature[] = [ - { - id: "auto-deploy", - title: "Auto-deploy on PR", - description: - "Every pull request automatically gets its own isolated environment. Simple config setup.", - icon: GitPullRequest, - }, - { - id: "multi-service", - title: "Connected Multi-Service", - description: - "Spin up your entire stack - frontend, backend, databases - all connected and working together.", - icon: Network, - }, - { - id: "auto-cleanup", - title: "Automatic Cleanup", - description: - "Environments are automatically torn down when PRs are merged or closed. No resource waste.", - icon: Trash2, - }, - { - id: "cross-repo", - title: "Cross-Repo Composition", - description: - "Test changes across multiple repositories in a single unified environment.", - icon: GitFork, - }, - { - id: "webhooks", - title: "Webhooks & Automation", - description: - "Integrate with your existing CI/CD pipelines and trigger custom workflows on environment events.", - icon: Webhook, - }, - { - id: "mission-control", - title: "Mission Control Comments", - description: - "Get environment URLs, status updates, and deployment logs directly in your PR comments.", - icon: MessageSquare, - }, -]; diff --git a/src/components/home/features/index.tsx b/src/components/home/features/index.tsx deleted file mode 100644 index b8e4d905..00000000 --- a/src/components/home/features/index.tsx +++ /dev/null @@ -1,55 +0,0 @@ -/** - * Copyright 2025 GoodRx, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -"use client"; - -import { motion } from "framer-motion"; -import { FeatureCard } from "./FeatureCard"; -import { features } from "./data"; - -export function Features() { - return ( -
-
- -

- Everything you need for ephemeral environments -

-

- Lifecycle provides all the tools to create, manage, and scale your - development environments automatically. -

-
- -
- {features.map((feature, index) => ( - - ))} -
-
-
- ); -} - -export { FeatureCard } from "./FeatureCard"; -export { features } from "./data"; -export type { Feature } from "./types"; diff --git a/src/components/home/features/types.ts b/src/components/home/features/types.ts deleted file mode 100644 index 72c2bdb1..00000000 --- a/src/components/home/features/types.ts +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Copyright 2025 GoodRx, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import type { LucideIcon } from "lucide-react"; - -export interface Feature { - id: string; - title: string; - description: string; - icon: LucideIcon; -} diff --git a/src/components/home/hero/HeroContent.tsx b/src/components/home/hero/HeroContent.tsx index 3a002f09..26cdb108 100644 --- a/src/components/home/hero/HeroContent.tsx +++ b/src/components/home/hero/HeroContent.tsx @@ -17,46 +17,133 @@ "use client"; import Link from "next/link"; +import { useState } from "react"; import { motion } from "framer-motion"; -import { ArrowRight, Github } from "lucide-react"; +import { ArrowRight, Check, Copy, Github } from "lucide-react"; import { buttonVariants } from "@/components/ui/button"; import { cn } from "@/lib/utils"; +const installCommand = "git clone https://github.com/GoodRxOSS/lifecycle"; +const easeOutQuart = [0.25, 1, 0.5, 1] as const; + +type CopyState = "idle" | "copied" | "failed"; + export function HeroContent() { + const [copyState, setCopyState] = useState("idle"); + + const handleCopy = async () => { + try { + await navigator.clipboard.writeText(installCommand); + setCopyState("copied"); + setTimeout(() => setCopyState("idle"), 1800); + } catch { + setCopyState("failed"); + setTimeout(() => setCopyState("idle"), 2400); + } + }; + return ( -
+
+ + + - Enterprise-grade ephemeral environments{" "} - that grow with you + Every pull request gets a{" "} + real environment. - Instantly spin up connected multi-service development environments from - any pull request. Review, test, and iterate faster than ever before. + A multi-service env per pull request. Builds itself. Tears itself down + on merge. +
+ + $ + + + {installCommand} + + +
+

+ {copyState === "failed" ? "copy blocked · use ⌘C / Ctrl+C" : " "} +

+
+ + - Get Started - + Get started + diff --git a/src/components/home/hero/index.tsx b/src/components/home/hero/index.tsx index c1b7c9e9..d87e40ed 100644 --- a/src/components/home/hero/index.tsx +++ b/src/components/home/hero/index.tsx @@ -18,21 +18,48 @@ import { motion } from "framer-motion"; import { HeroContent } from "./HeroContent"; -import { Iframe } from "@/components/iframe"; + +const easeOutQuart = [0.25, 1, 0.5, 1] as const; export function Hero() { return ( -
+
- -