summaryrefslogtreecommitdiffhomepage
path: root/packages/web/src/components/Lander.astro
diff options
context:
space:
mode:
authorDax Raad <[email protected]>2025-05-30 20:47:56 -0400
committerDax Raad <[email protected]>2025-05-30 20:48:36 -0400
commitf3da73553c45f17e04b1e77cb13eb0fca714d1bd (patch)
treea24317a19e1ab2a89da50db669dc6894f15d00d1 /packages/web/src/components/Lander.astro
parent9a26b3058ffc1023e5c7e54b6d571c903d15888e (diff)
downloadopencode-f3da73553c45f17e04b1e77cb13eb0fca714d1bd.tar.gz
opencode-f3da73553c45f17e04b1e77cb13eb0fca714d1bd.zip
sync
Diffstat (limited to 'packages/web/src/components/Lander.astro')
-rw-r--r--packages/web/src/components/Lander.astro269
1 files changed, 269 insertions, 0 deletions
diff --git a/packages/web/src/components/Lander.astro b/packages/web/src/components/Lander.astro
new file mode 100644
index 000000000..d27358f8f
--- /dev/null
+++ b/packages/web/src/components/Lander.astro
@@ -0,0 +1,269 @@
+---
+import { Image } from 'astro:assets';
+import config from "virtual:starlight/user-config";
+import type { Props } from '@astrojs/starlight/props';
+
+import CopyIcon from "../assets/lander/copy.svg";
+import CheckIcon from "../assets/lander/check.svg";
+
+const { data } = Astro.locals.starlightRoute.entry;
+const { title = data.title, tagline, image, actions = [] } = data.hero || {};
+
+const imageAttrs = {
+ loading: 'eager' as const,
+ decoding: 'async' as const,
+ width: 400,
+ alt: image?.alt || '',
+};
+
+const github = config.social.filter(s => s.icon === 'github')[0];
+
+const command = "npm i -g";
+const pkg = "opencode";
+
+let darkImage: ImageMetadata | undefined;
+let lightImage: ImageMetadata | undefined;
+let rawHtml: string | undefined;
+if (image) {
+ if ('file' in image) {
+ darkImage = image.file;
+ } else if ('dark' in image) {
+ darkImage = image.dark;
+ lightImage = image.light;
+ } else {
+ rawHtml = image.html;
+ }
+}
+---
+<div class="hero">
+ <section class="top">
+ <div class="logo">
+ <Image
+ src={darkImage}
+ {...imageAttrs}
+ class:list={{ 'light:sl-hidden': Boolean(lightImage) }}
+ />
+ <Image src={lightImage} {...imageAttrs} class="dark:sl-hidden" />
+ </div>
+ <h1>The AI coding agent built for the terminal.</h1>
+ </section>
+
+ <section class="cta">
+ <div class="col1">
+ <a href="/docs">View the docs</a>
+ </div>
+ <div class="col2">
+ <button class="command" data-command={`${command} ${pkg}`}>
+ <code>{command}&nbsp;<span class="highlight">{pkg}</span></code>
+ <span class="copy">
+ <CopyIcon />
+ <CheckIcon />
+ </span>
+ </button>
+ </div>
+ <div class="col3">
+ <a href={github.href}>Star on GitHub</a>
+ </div>
+ </section>
+
+ <section class="content">
+ <ul>
+ <li><b>Native TUI</b>: A native terminal UI for a smoother, snappier experience.</li>
+ <li><b>LSP enabled</b>: Loads the right LSPs for your codebase. Helps the LLM make fewer mistakes.</li>
+ <li><b>Multi-session</b>: Start multiple conversations in a project to have agents working in parallel.</li>
+ <li><b>Use any model</b>: Supports all the models from OpenAI, Anthropic, Google, OpenRouter, and more.</li>
+ <li><b>Change tracking</b>: View the file changes from the current conversation in the sidebar.</li>
+ <li><b>Edit with Vim</b>: Use Vim as an external editor to compose longer messages.</li>
+ </ul>
+ </section>
+
+ <section class="footer">
+ <div class="col1">
+ <span>Version: Beta</span>
+ </div>
+ <div class="col2">
+ <span>Author: <a href="https://sst.dev">SST</a></span>
+ </div>
+ </section>
+</div>
+
+<style>
+.hero {
+ --padding: 3rem;
+ --vertical-padding: 2rem;
+ --heading-font-size: var(--sl-text-3xl);
+
+ margin: 1rem;
+ border: 2px solid var(--sl-color-white);
+}
+@media (max-width: 30rem) {
+ .hero {
+ --padding: 1rem;
+ --vertical-padding: 1rem;
+ --heading-font-size: var(--sl-text-2xl);
+
+ margin: 0.5rem;
+ }
+}
+
+section.top {
+ padding: var(--padding);
+
+ h1 {
+ margin-top: calc(var(--vertical-padding) / 8);
+ font-size: var(--heading-font-size);
+ line-height: 1.25;
+ text-transform: uppercase;
+ }
+
+ img {
+ height: auto;
+ width: clamp(200px, 70vw, 400px);
+ }
+}
+
+section.cta {
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+ border-top: 2px solid var(--sl-color-white);
+
+ & > div {
+ flex: 1;
+ line-height: 1.4;
+ padding: calc(var(--padding) / 2) 0.5rem;
+ }
+ & > div:not(.col2) {
+ text-align: center;
+ text-transform: uppercase;
+ }
+
+ @media (max-width: 30rem) {
+ & > div {
+ padding-bottom: calc(var(--padding) / 2 + 4px);
+ }
+ }
+
+ & > div + div {
+ border-left: 2px solid var(--sl-color-white);
+ }
+
+ .command {
+ all: unset;
+ display: flex;
+ align-items: center;
+ gap: 0.625rem;
+ justify-content: center;
+ cursor: pointer;
+ width: 100%;
+
+ code {
+ color: var(--sl-color-text-secondary);
+ font-size: 1.125rem;
+ }
+ code .highlight {
+ color: var(--sl-color-text);
+ font-weight: 500;
+ }
+
+ .copy {
+ line-height: 1;
+ padding: 0;
+ }
+ .copy svg {
+ width: 1rem;
+ height: 1rem;
+ vertical-align: middle;
+ }
+ .copy svg:first-child {
+ color: var(--sl-color-text-dimmed);
+ }
+ .copy svg:last-child {
+ color: var(--sl-color-text);
+ display: none;
+ }
+ &.success .copy {
+ pointer-events: none;
+ }
+ &.success .copy svg:first-child {
+ display: none;
+ }
+ &.success .copy svg:last-child {
+ display: inline;
+ }
+ }
+}
+
+section.content {
+ border-top: 2px solid var(--sl-color-white);
+ padding: var(--padding);
+
+ ul {
+ padding-left: 1rem;
+
+ li + li {
+ margin-top: calc(var(--vertical-padding) / 2);
+ }
+
+ li b {
+ text-transform: uppercase;
+ }
+ }
+}
+
+section.approach {
+ border-top: 2px solid var(--sl-color-white);
+ padding: var(--padding);
+
+ p + p {
+ margin-top: var(--vertical-padding);
+ }
+}
+
+section.footer {
+ border-top: 2px solid var(--sl-color-white);
+ display: flex;
+ flex-direction: row;
+
+ & > div {
+ flex: 1;
+ text-align: center;
+ text-transform: uppercase;
+ padding: calc(var(--padding) / 2) 0.5rem;
+ }
+
+ & > div + div {
+ border-left: 2px solid var(--sl-color-white);
+ }
+}
+</style>
+
+<style is:global>
+:root[data-has-hero] {
+ header.header {
+ display: none;
+ }
+ .main-frame {
+ padding-top: 0;
+
+ .main-pane > main {
+ padding: 0;
+ }
+ }
+ main > .content-panel .sl-markdown-content {
+ margin-top: 0;
+ }
+}
+</style>
+
+<script>
+ const button = document.querySelector("button.command") as HTMLButtonElement;
+
+ button?.addEventListener("click", () => {
+ navigator.clipboard.writeText(button.dataset.command!);
+ button.classList.toggle("success");
+ setTimeout(() => {
+ button.classList.toggle("success");
+ }, 1500);
+ });
+</script>