Radish alpha
r
Radicle web interface
Radicle
Git (anonymous pull)
Log in to clone via SSH
Add Get started page and header CTA
brandonhaslegs wants to merge 4 commits into master · opened 21 days ago

Adds a /get-started landing page (CLI, Desktop, CI, AI agent sections) and a brand-blue "Get started with Radicle" button in the global header that links to it. Reuses the existing Button secondary variant; swaps its base/hover blues so the brighter shade is the resting state.

13 files changed +624 -2 ad8b0699 3ffabf18
added public/images/agent-skill.png
added public/images/ci.png
added public/images/cli.png
added public/images/desktop.png
added public/images/garden.png
modified src/App.svelte
@@ -14,6 +14,7 @@
  import LoadingBar from "./App/LoadingBar.svelte";

  import Commit from "@app/views/repos/Commit.svelte";
+
  import GetStarted from "@app/views/getStarted/View.svelte";
  import History from "@app/views/repos/History.svelte";
  import Issue from "@app/views/repos/Issue.svelte";
  import Issues from "@app/views/repos/Issues.svelte";
@@ -71,6 +72,8 @@
  </div>
{:else if $activeRouteStore.resource === "nodes"}
  <Nodes {...$activeRouteStore.params} />
+
{:else if $activeRouteStore.resource === "getStarted"}
+
  <GetStarted />
{:else if $activeRouteStore.resource === "users"}
  <Users {...$activeRouteStore.params} />
{:else if $activeRouteStore.resource === "repo.source"}
modified src/components/Button.svelte
@@ -163,7 +163,7 @@

  .secondary {
    color: var(--color-text-on-brand);
-
    background-color: var(--color-surface-brand-primary);
+
    background-color: var(--color-surface-brand-secondary);
  }

  .secondary[disabled] {
@@ -172,7 +172,7 @@
  }

  .secondary:not([disabled]):hover {
-
    background-color: var(--color-surface-brand-secondary);
+
    background-color: var(--color-surface-brand-primary);
  }

  .secondary-mobile {
added src/components/CopyCommand.svelte
@@ -0,0 +1,83 @@
+
<script lang="ts">
+
  import { onDestroy } from "svelte";
+
  import Icon from "@app/components/Icon.svelte";
+

+
  export let command: string = "curl -sSf https://radicle.xyz/install | sh";
+

+
  let isCopied = false;
+
  let resetCopyStateTimeout: ReturnType<typeof setTimeout> | undefined;
+

+
  async function copyToClipboard() {
+
    try {
+
      await navigator.clipboard.writeText(command);
+
      isCopied = true;
+
      if (resetCopyStateTimeout) clearTimeout(resetCopyStateTimeout);
+
      resetCopyStateTimeout = setTimeout(() => {
+
        isCopied = false;
+
      }, 2000);
+
    } catch (err) {
+
      console.error("Failed to copy to clipboard:", err);
+
    }
+
  }
+

+
  onDestroy(() => {
+
    if (resetCopyStateTimeout) clearTimeout(resetCopyStateTimeout);
+
  });
+
</script>
+

+
<style>
+
  .wrapper {
+
    display: block;
+
    width: 100%;
+
    overflow: hidden;
+
  }
+

+
  .codeblock {
+
    display: inline-flex;
+
    align-items: center;
+
    padding: 0.5rem 0.5rem 0.5rem 0.75rem;
+
    gap: 1.5rem;
+
    background: var(--color-surface-subtle);
+
    border-radius: var(--border-radius-sm);
+
    max-width: 100%;
+
    box-sizing: border-box;
+
  }
+

+
  code {
+
    font: var(--txt-code-regular);
+
    color: var(--color-text-secondary);
+
    white-space: nowrap;
+
    overflow: hidden;
+
    text-overflow: ellipsis;
+
  }
+

+
  .copy-button {
+
    display: flex;
+
    justify-content: center;
+
    align-items: center;
+
    padding: 0;
+
    flex-shrink: 0;
+
    width: 1rem;
+
    height: 1rem;
+
    background: none;
+
    border: none;
+
    cursor: pointer;
+
    color: var(--color-text-secondary);
+
  }
+

+
  .copy-button:hover {
+
    color: var(--color-text-primary);
+
  }
+
</style>
+

+
<span class="wrapper">
+
  <span class="codeblock">
+
    <code>{command}</code>
+
    <button
+
      class="copy-button"
+
      aria-label={isCopied ? "Copied to clipboard" : "Copy to clipboard"}
+
      on:click={copyToClipboard}>
+
      <Icon name={isCopied ? "checkmark" : "copy"} />
+
    </button>
+
  </span>
+
</span>
modified src/components/Header.svelte
@@ -1,6 +1,9 @@
<script lang="ts">
+
  import { activeRouteStore } from "@app/lib/router";
+

  import Settings from "@app/App/Settings.svelte";
  import Help from "@app/App/Help.svelte";
+
  import Button from "@app/components/Button.svelte";
  import Icon from "@app/components/Icon.svelte";
  import IconButton from "@app/components/IconButton.svelte";
  import Link from "@app/components/Link.svelte";
@@ -28,6 +31,8 @@
  .right-section {
    display: flex;
    justify-content: flex-end;
+
    align-items: center;
+
    gap: 0.75rem;
  }
  .divider {
    height: 1px;
@@ -50,6 +55,14 @@
  </div>

  <div class="right-section">
+
    {#if $activeRouteStore.resource !== "getStarted"}
+
      <Link
+
        route={{ resource: "getStarted" }}
+
        ariaLabel="Get started with Radicle">
+
        <Button variant="secondary">Get started with Radicle</Button>
+
      </Link>
+
    {/if}
+

    <Popover popoverPositionTop="2.5rem" popoverPositionRight="0">
      <IconButton
        slot="toggle"
modified src/lib/router.ts
@@ -115,6 +115,9 @@ function setTitle(loadedRoute: LoadedRoute) {
    title.push("Radicle");
  } else if (loadedRoute.resource === "users") {
    title.push(...userTitle(loadedRoute));
+
  } else if (loadedRoute.resource === "getStarted") {
+
    title.push("Get started");
+
    title.push("Radicle");
  } else if (loadedRoute.resource === "notFound") {
    title.push("Page not found");
    title.push("Radicle");
@@ -177,6 +180,9 @@ function urlToRoute(url: URL): Route | null {

  const resource = segments.shift();
  switch (resource) {
+
    case "get-started": {
+
      return { resource: "getStarted" };
+
    }
    case "nodes":
    case "seeds": {
      const hostAndPort = segments.shift();
@@ -220,6 +226,8 @@ export function routeToPath(route: Route): string {
    } else {
      return nodePath(route.params.baseUrl);
    }
+
  } else if (route.resource === "getStarted") {
+
    return "/get-started";
  } else if (route.resource === "users") {
    return userRouteToPath(route);
  } else if (
modified src/lib/router/definitions.ts
@@ -17,6 +17,10 @@ interface BootingRoute {
  resource: "booting";
}

+
export interface GetStartedRoute {
+
  resource: "getStarted";
+
}
+

export interface NotFoundRoute {
  resource: "notFound";
  params: { title: string; description?: string; baseUrl?: BaseUrl };
@@ -36,6 +40,7 @@ export interface ErrorRoute {

export type Route =
  | BootingRoute
+
  | GetStartedRoute
  | UserRoute
  | ErrorRoute
  | NotFoundRoute
@@ -44,6 +49,7 @@ export type Route =

export type LoadedRoute =
  | BootingRoute
+
  | GetStartedRoute
  | UserLoadedRoute
  | ErrorRoute
  | NotFoundRoute
added src/views/getStarted/View.svelte
@@ -0,0 +1,498 @@
+
<script lang="ts">
+
  import { slide } from "svelte/transition";
+

+
  import CopyCommand from "@app/components/CopyCommand.svelte";
+
  import Header from "@app/components/Header.svelte";
+
  import Icon from "@app/components/Icon.svelte";
+

+
  let showDownloads = false;
+
  let openItem = 0;
+

+
  const downloads = [
+
    {
+
      name: "Apple Silicon",
+
      description:
+
        "Run the command in your terminal to download and open the DMG file. Then drag the Radicle app to your Applications folder.",
+
      command:
+
        "curl --output ~/Downloads/radicle-desktop-aarch64.dmg https://files.radicle.xyz/releases/radicle-desktop/latest/radicle-desktop-aarch64.dmg && open ~/Downloads/radicle-desktop-aarch64.dmg",
+
    },
+
    {
+
      name: "Linux AppImage",
+
      description:
+
        "Download, make the file executable with chmod +x, and run it.",
+
      command:
+
        "curl --output radicle-desktop-amd64.AppImage https://files.radicle.xyz/releases/radicle-desktop/latest/radicle-desktop-amd64.AppImage",
+
    },
+
    {
+
      name: "Debian / Ubuntu",
+
      description: "Install the keyring, add the APT repository, then install.",
+
      command:
+
        'curl -LO https://radicle.xyz/apt/radicle-archive-keyring.deb && chmod a+r radicle-archive-keyring.deb && sudo apt install ./radicle-archive-keyring.deb && echo "deb [signed-by=/usr/share/radicle/radicle-archive-keyring.asc] https://radicle.xyz/apt release main" | sudo tee /etc/apt/sources.list.d/radicle.list && sudo apt update && sudo apt install radicle-desktop',
+
    },
+
    {
+
      name: "Arch Linux",
+
      description: "Available from the Arch User Repository.",
+
      command: "yay -S radicle-desktop",
+
    },
+
    {
+
      name: "NixOS",
+
      description:
+
        "Give it a try with nix run and if you like it, make it permanent with nix profile install.",
+
      command:
+
        "nix run 'git+https://seed.radicle.xyz/z4D5UCArafTzTQpDZNQRuqswh3ury.git'",
+
    },
+
    {
+
      name: "Windows WSL2",
+
      description:
+
        "Use WSL2 with any of the Linux install options to run Radicle Desktop on Windows.",
+
      command: "",
+
    },
+
  ];
+

+
  function toggleItem(index: number) {
+
    openItem = openItem === index ? -1 : index;
+
  }
+

+
  function handleDesktopAction() {
+
    if (window.matchMedia("(max-width: 64rem)").matches) {
+
      const installUrl = `${window.location.origin}/get-started`;
+
      const subject = encodeURIComponent("Install Radicle Desktop");
+
      const body = encodeURIComponent(
+
        `Open this page on your desktop:\n\n${installUrl}`,
+
      );
+
      window.location.href = `mailto:?subject=${subject}&body=${body}`;
+
      return;
+
    }
+

+
    showDownloads = !showDownloads;
+
  }
+
</script>
+

+
<style>
+
  .page {
+
    --install-inline-gap: 4rem;
+
    padding: 2.5rem var(--install-inline-gap) 4rem;
+
    max-width: 90rem;
+
    margin: 0 auto;
+
  }
+

+
  .page-header h1 {
+
    margin: 0;
+
    max-width: 50rem;
+
  }
+

+
  .tertiary {
+
    color: var(--color-text-tertiary);
+
  }
+

+
  .card-row {
+
    display: grid;
+
    grid-template-columns: 1fr 1fr;
+
    gap: var(--install-inline-gap);
+
  }
+

+
  .top-row {
+
    padding-top: var(--install-inline-gap);
+
  }
+

+
  .bottom-row {
+
    padding-top: var(--install-inline-gap);
+
  }
+

+
  .product-card {
+
    display: flex;
+
    flex-direction: column;
+
    gap: 1.5rem;
+
    margin: 0;
+
    min-width: 0;
+
  }
+

+
  .product-media {
+
    width: 100%;
+
    height: auto;
+
    border-radius: var(--border-radius-sm);
+
    background: var(--color-surface-subtle);
+
    aspect-ratio: 16 / 10;
+
    object-fit: cover;
+
  }
+

+
  .product-card-text {
+
    display: flex;
+
    flex-direction: column;
+
    gap: 0.75rem;
+
    align-items: flex-start;
+
    min-width: 0;
+
    width: 100%;
+
  }
+

+
  .product-title {
+
    margin: 0;
+
  }
+

+
  .product-description {
+
    margin: 0;
+
    color: var(--color-text-tertiary);
+
    max-width: 32rem;
+
  }
+

+
  .product-link {
+
    display: inline-flex;
+
    align-items: center;
+
    gap: 0.25rem;
+
    background: none;
+
    border: none;
+
    padding: 0;
+
    cursor: pointer;
+
    color: var(--color-text-primary);
+
    text-decoration: none;
+
  }
+

+
  .product-link:hover {
+
    color: var(--color-brand-hover);
+
  }
+

+
  .inline-link {
+
    display: inline-flex;
+
    align-items: center;
+
    gap: 0.25rem;
+
    color: var(--color-text-primary);
+
    text-decoration: none;
+
    font: var(--txt-body-l-medium);
+
    vertical-align: bottom;
+
  }
+

+
  .inline-link:hover {
+
    color: var(--color-brand-hover);
+
  }
+

+
  .download-panel {
+
    display: flex;
+
    flex-direction: row;
+
    align-items: flex-start;
+
    gap: 1.5rem;
+
    padding-right: 1.5rem;
+
    overflow: hidden;
+
  }
+

+
  .download-left {
+
    width: 20rem;
+
    flex-shrink: 0;
+
    padding: 1.25rem 1.5rem 1.25rem 0;
+
  }
+

+
  .download-left h3 {
+
    margin: 0;
+
  }
+

+
  .download-right {
+
    display: flex;
+
    flex-direction: column;
+
    flex-grow: 1;
+
    min-width: 0;
+
  }
+

+
  .download-divider {
+
    height: 1px;
+
    background: var(--color-border-subtle);
+
  }
+

+
  .download-item {
+
    border-radius: var(--border-radius-sm);
+
    min-width: 0;
+
  }
+

+
  .download-question {
+
    display: flex;
+
    flex-direction: row;
+
    align-items: center;
+
    justify-content: space-between;
+
    padding: 1.25rem 0;
+
    gap: 2rem;
+
    width: 100%;
+
    background: none;
+
    border: none;
+
    cursor: pointer;
+
    color: var(--color-text-primary);
+
    text-align: left;
+
  }
+

+
  .download-question:hover {
+
    color: var(--color-brand-hover);
+
  }
+

+
  .chevron {
+
    flex: none;
+
    color: var(--color-text-tertiary);
+
    transition: transform 0.2s ease;
+
  }
+

+
  .chevron.open {
+
    transform: rotate(180deg);
+
  }
+

+
  .download-answer {
+
    display: flex;
+
    flex-direction: column;
+
    gap: 1.5rem;
+
    padding-bottom: 2.5rem;
+
    min-width: 0;
+
  }
+

+
  .download-description {
+
    color: var(--color-text-tertiary);
+
    max-width: 45rem;
+
    margin: 0;
+
  }
+

+
  .download-label-desktop,
+
  .download-label-mobile {
+
    display: inline-flex;
+
    align-items: center;
+
    gap: 0.25rem;
+
  }
+

+
  .download-label-mobile {
+
    display: none;
+
  }
+

+
  .garden-banner {
+
    display: flex;
+
    flex-direction: column;
+
    justify-content: space-between;
+
    margin-top: var(--install-inline-gap);
+
    padding: 1.5rem;
+
    min-height: 25.5rem;
+
    background:
+
      url("/images/garden.png") no-repeat right center,
+
      var(--color-accent-citrus-500);
+
    background-size: cover;
+
    border-radius: var(--border-radius-sm);
+
  }
+

+
  .garden-title {
+
    margin: 0;
+
    max-width: 28rem;
+
    color: #00060f;
+
  }
+

+
  .garden-bottom {
+
    display: flex;
+
    flex-direction: row;
+
    justify-content: space-between;
+
    align-items: flex-end;
+
    gap: 1rem;
+
  }
+

+
  .garden-description {
+
    margin: 0;
+
    max-width: 25rem;
+
    color: #00060f;
+
  }
+

+
  .garden-button {
+
    display: inline-flex;
+
    align-items: center;
+
    gap: 0.25rem;
+
    padding: 0.375rem 0.75rem;
+
    background: #ffffff;
+
    border-radius: var(--border-radius-sm);
+
    color: #00060f;
+
    text-decoration: none;
+
    white-space: nowrap;
+
  }
+

+
  .garden-button:hover {
+
    opacity: 0.85;
+
  }
+

+
  @media (max-width: 64rem) {
+
    .page {
+
      --install-inline-gap: 1.5rem;
+
    }
+

+
    .page-header h1 {
+
      font: var(--txt-heading-xl);
+
    }
+

+
    .card-row {
+
      grid-template-columns: 1fr;
+
    }
+

+
    .download-label-desktop {
+
      display: none;
+
    }
+

+
    .download-label-mobile {
+
      display: inline-flex;
+
      align-items: center;
+
      gap: 0.25rem;
+
    }
+

+
    .download-panel {
+
      flex-direction: column;
+
      padding-right: 0;
+
    }
+

+
    .download-left {
+
      width: 100%;
+
      padding-right: 0;
+
      padding-bottom: 0;
+
    }
+

+
    .garden-banner {
+
      min-height: auto;
+
    }
+

+
    .garden-bottom {
+
      flex-direction: column;
+
      align-items: flex-start;
+
    }
+
  }
+
</style>
+

+
<Header />
+

+
<main class="page">
+
  <header class="page-header">
+
    <h1 class="txt-heading-xxl">
+
      Get started with Radicle. <span class="tertiary">
+
        Everything you need to collaborate on code, from the terminal, the
+
        desktop, or your CI pipeline.
+
      </span>
+
    </h1>
+
  </header>
+

+
  <div class="card-row top-row">
+
    <article id="cli" class="product-card">
+
      <img class="product-media" src="/images/cli.png" alt="CLI" />
+
      <div class="product-card-text">
+
        <h3 class="product-title txt-heading-l">CLI</h3>
+
        <p class="product-description txt-body-l-regular">
+
          Work directly from your terminal to manage code, issues, patches, CI
+
          or even your sovereign identity. <a
+
            href="https://radicle.dev/guides/user"
+
            target="_blank"
+
            rel="noopener noreferrer"
+
            class="inline-link">Read the user guide</a>.
+
        </p>
+
        <CopyCommand />
+
      </div>
+
    </article>
+

+
    <article id="desktop" class="product-card">
+
      <img class="product-media" src="/images/desktop.png" alt="Desktop" />
+
      <div class="product-card-text">
+
        <h3 class="product-title txt-heading-l">Desktop</h3>
+
        <p class="product-description txt-body-l-regular">
+
          Collaborate through a visual interface with patches, issues, CI and
+
          built-in notifications. Your local-first forge, directly on your
+
          computer.
+
        </p>
+
        <button
+
          class="product-link txt-body-l-medium"
+
          on:click={handleDesktopAction}>
+
          <span class="download-label-desktop">
+
            Download <Icon name="arrow-down" />
+
          </span>
+
          <span class="download-label-mobile">
+
            Send to my desktop <Icon name="open-external" />
+
          </span>
+
        </button>
+
      </div>
+
    </article>
+
  </div>
+

+
  {#if showDownloads}
+
    <section class="download-panel" transition:slide={{ duration: 300 }}>
+
      <div class="download-left">
+
        <h3 class="txt-heading-l">Select your OS and follow the instructions</h3>
+
      </div>
+
      <div class="download-right">
+
        {#each downloads as dl, i}
+
          {#if i > 0}
+
            <div class="download-divider"></div>
+
          {/if}
+
          <div class="download-item">
+
            <button class="download-question" on:click={() => toggleItem(i)}>
+
              <span class="txt-body-l-semibold">{dl.name}</span>
+
              <span class="chevron" class:open={openItem === i}>
+
                <Icon name="chevron-up" />
+
              </span>
+
            </button>
+
            {#if openItem === i}
+
              <div class="download-answer">
+
                <p class="download-description txt-body-l-regular">
+
                  {dl.description}
+
                </p>
+
                {#if dl.command}
+
                  <CopyCommand command={dl.command} />
+
                {/if}
+
              </div>
+
            {/if}
+
          </div>
+
        {/each}
+
      </div>
+
    </section>
+
  {/if}
+

+
  <div class="card-row bottom-row">
+
    <article id="radicle-ci" class="product-card">
+
      <img class="product-media" src="/images/ci.png" alt="Radicle CI" />
+
      <div class="product-card-text">
+
        <h3 class="product-title txt-heading-l">Radicle CI</h3>
+
        <p class="product-description txt-body-l-regular">
+
          Automate builds and tests when patches land. Results are stored as
+
          collaborative objects, so they replicate with everything else.
+
        </p>
+
        <a
+
          href="https://radicle.dev/2025/07/23/using-radicle-ci-for-development"
+
          target="_blank"
+
          rel="noopener noreferrer"
+
          class="product-link txt-body-l-medium">
+
          Read the guide <Icon name="arrow-right" />
+
        </a>
+
      </div>
+
    </article>
+

+
    <article class="product-card">
+
      <img
+
        class="product-media"
+
        src="/images/agent-skill.png"
+
        alt="AI agent integration" />
+
      <div class="product-card-text">
+
        <h3 class="product-title txt-heading-l">AI agents</h3>
+
        <p class="product-description txt-body-l-regular">
+
          Teach your AI agent to use Radicle. Break down issues into tasks, sync
+
          progress, and capture session context.
+
        </p>
+
        <a
+
          href="https://app.radicle.xyz/nodes/seed.radicle.garden/rad:zvBj4kByGeQSrSy2c4H7fyK42cS8"
+
          target="_blank"
+
          rel="noopener noreferrer"
+
          class="product-link txt-body-l-medium">
+
          View repo <Icon name="open-external" />
+
        </a>
+
      </div>
+
    </article>
+
  </div>
+

+
  <section class="garden-banner">
+
    <h2 class="garden-title txt-heading-xxl">
+
      Introducing Radicle Garden. The quickest way to get started on the
+
      network.
+
    </h2>
+
    <div class="garden-bottom">
+
      <p class="garden-description txt-body-l-regular">
+
        Get started in minutes—no servers required. Instantly share code and
+
        discover new projects.
+
      </p>
+
      <a
+
        href="https://radicle.garden"
+
        target="_blank"
+
        rel="noopener noreferrer"
+
        class="garden-button txt-body-m-semibold">
+
        Visit radicle.garden <Icon name="open-external" />
+
      </a>
+
    </div>
+
  </section>
+
</main>
modified src/views/repos/Layout.svelte
@@ -34,6 +34,10 @@
    display: block;
  }

+
  .mobile-header {
+
    display: none;
+
  }
+

  .mobile-footer {
    display: none;
  }
@@ -62,6 +66,9 @@
    .desktop-header {
      display: none;
    }
+
    .mobile-header {
+
      display: block;
+
    }
    .tab-bar {
      display: none;
    }
@@ -77,6 +84,10 @@
</style>

<div class="layout">
+
  <div class="mobile-header">
+
    <Header />
+
  </div>
+

  <div class="desktop-header">
    <Header>
      <svelte:fragment slot="breadcrumbs">