diff options
| author | Dax Raad <[email protected]> | 2025-05-30 20:47:56 -0400 |
|---|---|---|
| committer | Dax Raad <[email protected]> | 2025-05-30 20:48:36 -0400 |
| commit | f3da73553c45f17e04b1e77cb13eb0fca714d1bd (patch) | |
| tree | a24317a19e1ab2a89da50db669dc6894f15d00d1 /packages/web/src/components/Lander.astro | |
| parent | 9a26b3058ffc1023e5c7e54b6d571c903d15888e (diff) | |
| download | opencode-f3da73553c45f17e04b1e77cb13eb0fca714d1bd.tar.gz opencode-f3da73553c45f17e04b1e77cb13eb0fca714d1bd.zip | |
sync
Diffstat (limited to 'packages/web/src/components/Lander.astro')
| -rw-r--r-- | packages/web/src/components/Lander.astro | 269 |
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} <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> |
