diff options
| author | Dax Raad <[email protected]> | 2025-08-15 19:29:24 -0400 |
|---|---|---|
| committer | Dax Raad <[email protected]> | 2025-08-15 19:29:42 -0400 |
| commit | 07cf8847fb1908ff5dc47a771f57d23926baa1ce (patch) | |
| tree | aef73a8ac42e755404cb56107137a7fc4eff3ffd /cloud | |
| parent | 650e67f1dfd4790152c70864da6c1ade4884ab58 (diff) | |
| download | opencode-07cf8847fb1908ff5dc47a771f57d23926baa1ce.tar.gz opencode-07cf8847fb1908ff5dc47a771f57d23926baa1ce.zip | |
wip: cloud stuff
Diffstat (limited to 'cloud')
28 files changed, 1057 insertions, 0 deletions
diff --git a/cloud/app/.gitignore b/cloud/app/.gitignore new file mode 100644 index 000000000..751513ce1 --- /dev/null +++ b/cloud/app/.gitignore @@ -0,0 +1,28 @@ +dist +.wrangler +.output +.vercel +.netlify +.vinxi +app.config.timestamp_*.js + +# Environment +.env +.env*.local + +# dependencies +/node_modules + +# IDEs and editors +/.idea +.project +.classpath +*.launch +.settings/ + +# Temp +gitignore + +# System Files +.DS_Store +Thumbs.db diff --git a/cloud/app/.opencode/agent/css.md b/cloud/app/.opencode/agent/css.md new file mode 100644 index 000000000..58c59da73 --- /dev/null +++ b/cloud/app/.opencode/agent/css.md @@ -0,0 +1,61 @@ +--- +description: use whenever you are styling a ui with css +--- + +you are very good at writing clean maintainable css using modern techniques + +css is structured like this + +```css +[data-page="home"] { + [data-component="header"] { + [data-slot="logo"] { + } + } +} +``` + +top level pages are scoped using `data-page` + +pages can break down into components using `data-component` + +components can break down into slots using `data-slot` + +structure things so that this hierarchy is followed - you should rarely need to +nest components inside other components. you should NEVER nest components inside +slots. you should NEVER nest slots inside other slots. + +thei hierarchy in css file does NOT have to match the hierarchy in the dom - you +can put components or slots at the same level even if one goes inside another. + +it is more important to follow the pages -> components -> slots structure + +use data attributes to represent different states of the component + +```css +[data-component="modal"] { + opacity: 0; + + &[data-state="open"] { + opacity: 1; + } +} +``` + +this will allow jsx to control the syling + +avoid selectors that just target an element type like `> span` you should assign +it a slot name. it's ok to do this sometimes where it makes sense semantically +like targeting `li` elements in a list + +in terms of file structure `./src/style/` contains all universal styling rules. +these should not contain anything specific to a page + +`./src/style/token` contains all the tokens used in the project + +`./src/style/component` is for reusable components like buttons or inputs + +page specific styles should go next to the page they are styling so +`./src/routes/about.tsx` should have its styles in `./src/routes/about.css` + +`about.css` should be scoped using `data-page="about"` diff --git a/cloud/app/README.md b/cloud/app/README.md new file mode 100644 index 000000000..9337430cf --- /dev/null +++ b/cloud/app/README.md @@ -0,0 +1,32 @@ +# SolidStart + +Everything you need to build a Solid project, powered by [`solid-start`](https://start.solidjs.com); + +## Creating a project + +```bash +# create a new project in the current directory +npm init solid@latest + +# create a new project in my-app +npm init solid@latest my-app +``` + +## Developing + +Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server: + +```bash +npm run dev + +# or start the server and open the app in a new browser tab +npm run dev -- --open +``` + +## Building + +Solid apps are built with _presets_, which optimise your project for deployment to different environments. + +By default, `npm run build` will generate a Node app that you can run with `npm start`. To use a different preset, add it to the `devDependencies` in `package.json` and specify in your `app.config.js`. + +## This project was created with the [Solid CLI](https://github.com/solidjs-community/solid-cli) diff --git a/cloud/app/app.config.ts b/cloud/app/app.config.ts new file mode 100644 index 000000000..0ffa557f9 --- /dev/null +++ b/cloud/app/app.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from "@solidjs/start/config" + +export default defineConfig({ + vite: { + server: { + allowedHosts: true, + }, + }, +}) diff --git a/cloud/app/package.json b/cloud/app/package.json new file mode 100644 index 000000000..59556e0ba --- /dev/null +++ b/cloud/app/package.json @@ -0,0 +1,21 @@ +{ + "name": "@opencode/cloud-app", + "type": "module", + "scripts": { + "dev": "vinxi dev --host 0.0.0.0", + "build": "vinxi build", + "start": "vinxi start", + "version": "vinxi version" + }, + "dependencies": { + "@ibm/plex": "6.4.1", + "@solidjs/meta": "^0.29.4", + "@solidjs/router": "^0.15.0", + "@solidjs/start": "^1.1.0", + "solid-js": "^1.9.5", + "vinxi": "^0.5.7" + }, + "engines": { + "node": ">=22" + } +} diff --git a/cloud/app/public/favicon.ico b/cloud/app/public/favicon.ico Binary files differnew file mode 100644 index 000000000..fb282da07 --- /dev/null +++ b/cloud/app/public/favicon.ico diff --git a/cloud/app/src/app.css b/cloud/app/src/app.css new file mode 100644 index 000000000..c0261c422 --- /dev/null +++ b/cloud/app/src/app.css @@ -0,0 +1 @@ +@import "./style/index.css"; diff --git a/cloud/app/src/app.tsx b/cloud/app/src/app.tsx new file mode 100644 index 000000000..04c569b91 --- /dev/null +++ b/cloud/app/src/app.tsx @@ -0,0 +1,21 @@ +import { MetaProvider, Title } from "@solidjs/meta"; +import { Router } from "@solidjs/router"; +import { FileRoutes } from "@solidjs/start/router"; +import { Suspense } from "solid-js"; +import "@ibm/plex/css/ibm-plex.css"; +import "./app.css"; + +export default function App() { + return ( + <Router + root={props => ( + <MetaProvider> + <Title>SolidStart - Basic</Title> + <Suspense>{props.children}</Suspense> + </MetaProvider> + )} + > + <FileRoutes /> + </Router> + ); +} diff --git a/cloud/app/src/asset/logo-ornate-dark.svg b/cloud/app/src/asset/logo-ornate-dark.svg new file mode 100644 index 000000000..2efda934d --- /dev/null +++ b/cloud/app/src/asset/logo-ornate-dark.svg @@ -0,0 +1,19 @@ +<svg width="289" height="50" viewBox="0 0 289 50" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path d="M8.5 16.5H24.5V33H8.5V16.5Z" fill="white" fill-opacity="0.2"/> +<path d="M48.5 16.5H64.5V33H48.5V16.5Z" fill="white" fill-opacity="0.2"/> +<path d="M120.5 16.5H136.5V33H120.5V16.5Z" fill="white" fill-opacity="0.2"/> +<path d="M160.5 16.5H176.5V33H160.5V16.5Z" fill="white" fill-opacity="0.2"/> +<path d="M192.5 16.5H208.5V33H192.5V16.5Z" fill="white" fill-opacity="0.2"/> +<path d="M232.5 16.5H248.5V33H232.5V16.5Z" fill="white" fill-opacity="0.2"/> +<path d="M264.5 0H288.5V8.5H272.5V16.5H288.5V25H272.5V33H288.5V41.5H264.5V0Z" fill="white" fill-opacity="0.95"/> +<path d="M248.5 0H224.5V41.5H248.5V33H232.5V8.5H248.5V0Z" fill="white" fill-opacity="0.95"/> +<path d="M256.5 8.5H248.5V33H256.5V8.5Z" fill="white" fill-opacity="0.95"/> +<path fill-rule="evenodd" clip-rule="evenodd" d="M184.5 0H216.5V41.5H184.5V0ZM208.5 8.5H192.5V33H208.5V8.5Z" fill="white" fill-opacity="0.95"/> +<path d="M144.5 8.5H136.5V41.5H144.5V8.5Z" fill="white" fill-opacity="0.5"/> +<path d="M136.5 0H112.5V41.5H120.5V8.5H136.5V0Z" fill="white" fill-opacity="0.5"/> +<path d="M80.5 0H104.5V8.5H88.5V16.5H104.5V25H88.5V33H104.5V41.5H80.5V0Z" fill="white" fill-opacity="0.5"/> +<path fill-rule="evenodd" clip-rule="evenodd" d="M40.5 0H72.5V41.5H48.5V49.5H40.5V0ZM64.5 8.5H48.5V33H64.5V8.5Z" fill="white" fill-opacity="0.5"/> +<path fill-rule="evenodd" clip-rule="evenodd" d="M0.5 0H32.5V41.5955H0.5V0ZM24.5 8.5H8.5V33H24.5V8.5Z" fill="white" fill-opacity="0.5"/> +<path d="M152.5 0H176.5V8.5H160.5V33H176.5V41.5H152.5V0Z" fill="white" fill-opacity="0.95"/> +</svg> + diff --git a/cloud/app/src/asset/screenshot-github.webp b/cloud/app/src/asset/screenshot-github.webp Binary files differnew file mode 100644 index 000000000..fda74e641 --- /dev/null +++ b/cloud/app/src/asset/screenshot-github.webp diff --git a/cloud/app/src/asset/screenshot-splash.webp b/cloud/app/src/asset/screenshot-splash.webp Binary files differnew file mode 100644 index 000000000..e900673ef --- /dev/null +++ b/cloud/app/src/asset/screenshot-splash.webp diff --git a/cloud/app/src/asset/screenshot-vscode.webp b/cloud/app/src/asset/screenshot-vscode.webp Binary files differnew file mode 100644 index 000000000..b8966a6b8 --- /dev/null +++ b/cloud/app/src/asset/screenshot-vscode.webp diff --git a/cloud/app/src/component/icon.tsx b/cloud/app/src/component/icon.tsx new file mode 100644 index 000000000..5a565ab9a --- /dev/null +++ b/cloud/app/src/component/icon.tsx @@ -0,0 +1,24 @@ + +import { JSX } from "solid-js" + + +export function IconCopy(props: JSX.SvgSVGAttributes<SVGSVGElement>) { + return ( + <svg + {...props} + viewBox="0 0 512 512" > + <rect width="336" height="336" x="128" y="128" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32" rx="57" ry="57"></rect> + <path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="m383.5 128l.5-24a56.16 56.16 0 0 0-56-56H112a64.19 64.19 0 0 0-64 64v216a56.16 56.16 0 0 0 56 56h24"></path> + </svg> + ) +} + +export function IconCheck(props: JSX.SvgSVGAttributes<SVGSVGElement>) { + return ( + <svg + {...props} + viewBox="0 0 24 24" > + <path fill="currentColor" d="M9 16.17L5.53 12.7a.996.996 0 1 0-1.41 1.41l4.18 4.18c.39.39 1.02.39 1.41 0L20.29 7.71a.996.996 0 1 0-1.41-1.41z"></path> + </svg> + ) +} diff --git a/cloud/app/src/entry-client.tsx b/cloud/app/src/entry-client.tsx new file mode 100644 index 000000000..0ca4e3c30 --- /dev/null +++ b/cloud/app/src/entry-client.tsx @@ -0,0 +1,4 @@ +// @refresh reload +import { mount, StartClient } from "@solidjs/start/client"; + +mount(() => <StartClient />, document.getElementById("app")!); diff --git a/cloud/app/src/entry-server.tsx b/cloud/app/src/entry-server.tsx new file mode 100644 index 000000000..eb8aea1e8 --- /dev/null +++ b/cloud/app/src/entry-server.tsx @@ -0,0 +1,21 @@ +// @refresh reload +import { createHandler, StartServer } from "@solidjs/start/server"; + +export default createHandler(() => ( + <StartServer + document={({ assets, children, scripts }) => ( + <html lang="en"> + <head> + <meta charset="utf-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1" /> + <link rel="icon" href="/favicon.ico" /> + {assets} + </head> + <body data-color-mode="dark"> + <div id="app">{children}</div> + {scripts} + </body> + </html> + )} + /> +)); diff --git a/cloud/app/src/global.d.ts b/cloud/app/src/global.d.ts new file mode 100644 index 000000000..dc6f10c22 --- /dev/null +++ b/cloud/app/src/global.d.ts @@ -0,0 +1 @@ +/// <reference types="@solidjs/start/env" /> diff --git a/cloud/app/src/routes/[...404].tsx b/cloud/app/src/routes/[...404].tsx new file mode 100644 index 000000000..4ea71ec7f --- /dev/null +++ b/cloud/app/src/routes/[...404].tsx @@ -0,0 +1,19 @@ +import { Title } from "@solidjs/meta"; +import { HttpStatusCode } from "@solidjs/start"; + +export default function NotFound() { + return ( + <main> + <Title>Not Found</Title> + <HttpStatusCode code={404} /> + <h1>Page Not Found</h1> + <p> + Visit{" "} + <a href="https://start.solidjs.com" target="_blank"> + start.solidjs.com + </a>{" "} + to learn how to build SolidStart apps. + </p> + </main> + ); +} diff --git a/cloud/app/src/routes/index.css b/cloud/app/src/routes/index.css new file mode 100644 index 000000000..e3b11c605 --- /dev/null +++ b/cloud/app/src/routes/index.css @@ -0,0 +1,264 @@ +[data-page="home"] { + --color-bg: oklch(0.2097 0.008 274.53); + --color-border: oklch(0.46 0.02 269.88); + --color-text: #ffffff; + --color-text-secondary: oklch(0.72 0.01 270.15); + --color-text-dimmed: hsl(224, 7%, 46%); + padding: var(--space-6); + font-family: var(--font-mono); + color: var(--color-text); + + a { + color: var(--color-text); + text-decoration: underline; + text-underline-offset: 0.1875rem; + } + + background: var(--color-bg); + position: fixed; + overflow-y: scroll; + inset: 0; + + [data-component="content"] { + max-width: 67.5rem; + margin: 0 auto; + border: 2px solid var(--color-border); + } + + [data-component="top"] { + padding: var(--space-12); + display: flex; + flex-direction: column; + align-items: start; + gap: var(--space-4); + + [data-slot="logo"] { + height: 70px; + } + + [data-slot="title"] { + font-size: var(--font-size-2xl); + text-transform: uppercase; + } + } + + [data-component="cta"] { + height: var(--space-19); + border-top: 2px solid var(--color-border); + display: flex; + + [data-slot="left"] { + display: flex; + padding: 0 var(--space-12); + text-transform: uppercase; + text-decoration: underline; + align-items: center; + justify-content: center; + text-underline-offset: 0.1875rem; + border-right: 2px solid var(--color-border); + + a { + color: var(--color-text); + text-decoration: underline; + } + } + + [data-slot="right"] { + flex: 1; + display: flex; + align-items: center; + justify-content: center; + gap: 0.625rem; + padding: 0 var(--space-6); + } + + [data-slot="command"] { + all: unset; + display: flex; + align-items: center; + cursor: pointer; + color: var(--color-text-secondary); + font-size: 1.125rem; + font-family: var(--font-mono); + gap: var(--space-2); + } + + [data-slot="highlight"] { + color: var(--color-text); + font-weight: 500; + } + } + + [data-component="features"] { + border-top: 2px solid var(--color-border); + padding: var(--space-12); + + [data-slot="list"] { + padding-left: var(--space-4); + margin: 0; + list-style: disc; + + li { + margin-bottom: var(--space-4); + + strong { + text-transform: uppercase; + font-weight: 600; + } + } + + li:last-child { + margin-bottom: 0; + } + } + } + + [data-component="install"] { + border-top: 2px solid var(--color-border); + display: grid; + grid-template-columns: 1fr 1fr; + grid-template-rows: 1fr 1fr; + + @media (max-width: 40rem) { + grid-template-columns: 1fr; + grid-template-rows: auto; + } + } + + [data-component="title"] { + letter-spacing: -0.03125rem; + text-transform: uppercase; + font-weight: 400; + font-size: var(--font-size-md); + flex-shrink: 0; + color: oklch(0.55 0.02 269.87); + } + + [data-component="method"] { + padding: var(--space-4) var(--space-6); + display: flex; + flex-direction: column; + align-items: start; + gap: var(--space-3); + + &:nth-child(1) {} + + &:nth-child(2) { + border-left: 2px solid var(--color-border); + } + + &:nth-child(3) { + border-top: 2px solid var(--color-border); + } + + &:nth-child(4) { + border-top: 2px solid var(--color-border); + border-left: 2px solid var(--color-border); + } + + [data-slot="button"] { + all: unset; + cursor: pointer; + display: flex; + align-items: center; + color: var(--color-text-secondary); + gap: var(--space-2); + + strong { + color: var(--color-text); + font-weight: 500; + } + } + } + + [data-component="screenshots"] { + border-top: 2px solid var(--color-border); + display: grid; + grid-template-columns: 1fr 1fr; + gap: 0; + + [data-slot="left"] { + padding: var(--space-8) var(--space-6); + display: flex; + flex-direction: column; + + img { + width: 100%; + height: "auto"; + } + } + + [data-slot="right"] { + display: grid; + grid-template-rows: 1fr 1fr; + border-left: 2px solid var(--color-border); + } + + [data-slot="filler"] { + display: flex; + flex-grow: 1; + align-items: center; + justify-content: center; + } + + [data-slot="cell"] { + padding: var(--space-8) var(--space-6); + display: flex; + flex-direction: column; + gap: var(--space-4); + + &:nth-child(2) { + border-top: 2px solid var(--color-border); + } + + img { + width: 80%; + height: "auto"; + } + } + } + + [data-component="copy-status"] { + [data-slot="copy"] { + display: block; + width: 16px; + height: 16px; + color: var(--color-text-dimmed); + + [data-copied] & { + display: none; + } + } + + [data-slot="check"] { + display: none; + width: 16px; + height: 16px; + color: white; + + [data-copied] & { + display: block; + } + } + } + + [data-component="footer"] { + border-top: 2px solid var(--color-border); + display: grid; + grid-template-columns: 1fr 1fr 1fr; + font-size: var(--font-size-lg); + height: var(--space-20); + + [data-slot="cell"] { + display: flex; + align-items: center; + justify-content: center; + border-right: 2px solid var(--color-border); + text-transform: uppercase; + + &:last-child { + border-right: none; + } + } + } +} diff --git a/cloud/app/src/routes/index.tsx b/cloud/app/src/routes/index.tsx new file mode 100644 index 000000000..90062d0b5 --- /dev/null +++ b/cloud/app/src/routes/index.tsx @@ -0,0 +1,169 @@ +import { Title } from "@solidjs/meta" +import { onCleanup, onMount } from "solid-js" +import "./index.css" +import logo from "../asset/logo-ornate-dark.svg" +import IMG_SPLASH from "../asset/screenshot-splash.webp" +import IMG_VSCODE from "../asset/screenshot-vscode.webp" +import IMG_GITHUB from "../asset/screenshot-github.webp" +import { IconCopy, IconCheck } from "../component/icon" + +function CopyStatus() { + return ( + <div data-component="copy-status"> + <IconCopy data-slot="copy" /> + <IconCheck data-slot="check" /> + </div> + ) +} + +export default function Home() { + onMount(() => { + const commands = document.querySelectorAll("[data-copy]") + for (const button of commands) { + const callback = () => { + const text = button.textContent + alert(text) + if (text) { + navigator.clipboard.writeText(text) + button.setAttribute("data-copied", "") + setTimeout(() => { + button.removeAttribute("data-copied") + }, 1500) + } + } + button.addEventListener("click", callback) + onCleanup(() => { + button.removeEventListener("click", callback) + }) + } + }) + + return ( + <main data-page="home"> + <Title>opencode | AI coding agent built for the terminal</Title> + <div data-component="content"> + <section data-component="top"> + <img data-slot="logo" src={logo} alt="logo" /> + <h1 data-slot="title">The AI coding agent built for the terminal.</h1> + </section> + + <section data-component="cta"> + <div data-slot="left"> + <a href="/docs">Get Started</a> + </div> + <div data-slot="right"> + <button data-copy data-slot="command" data-command="curl -fsSL https://opencode.ai/install | bash"> + <span> + <span>curl -fsSL </span> + <span data-slot="protocol">https://</span> + <span data-slot="highlight">opencode.ai/install</span> + | bash + </span> + <CopyStatus /> + </button> + </div> + </section> + + <section data-component="features"> + <ul data-slot="list"> + <li> + <strong>Native TUI</strong>: A responsive, native, themeable terminal UI. + </li> + <li> + <strong>LSP enabled</strong>: Automatically loads the right LSPs for the LLM. + </li> + <li> + <strong>Multi-session</strong>: Start multiple agents in parallel on the same project. + </li> + <li> + <strong>Shareable links</strong>: Share a link to any sessions for reference or to debug. + </li> + <li> + <strong>Claude Pro</strong>: Log in with Anthropic to use your Claude Pro or Max account. + </li> + <li> + <strong>Use any model</strong>: Supports 75+ LLM providers through{" "} + <a href="https://models.dev">Models.dev</a>, including local models. + </li> + </ul> + </section> + + <section data-component="install"> + <div data-component="method"> + <h3 data-component="title">npm</h3> + <button data-copy data-slot="button"> + <span> + npm install -g <strong>opencode-ai</strong> + </span> + <CopyStatus /> + </button> + </div> + <div data-component="method"> + <h3 data-component="title">bun</h3> + <button data-copy data-slot="button"> + <span> + bun install -g <strong>opencode-ai</strong> + </span> + <CopyStatus /> + </button> + </div> + <div data-component="method"> + <h3 data-component="title">homebrew</h3> + <button data-copy data-slot="button"> + <span> + brew install <strong>sst/tap/opencode</strong> + </span> + <CopyStatus /> + </button> + </div> + <div data-component="method"> + <h3 data-component="title">paru</h3> + <button data-copy data-slot="button"> + <span> + paru -S <strong>opencode-bin</strong> + </span> + <CopyStatus /> + </button> + </div> + </section> + + <section data-component="screenshots"> + <div data-slot="left"> + <div data-component="title">opencode TUI with tokyonight theme</div> + <div data-slot="filler"> + <img src={IMG_SPLASH} alt="opencode TUI with tokyonight theme" /> + </div> + </div> + <div data-slot="right"> + <div data-slot="cell"> + <div data-component="title">opencode in VS Code</div> + <div data-slot="filler"> + <img src={IMG_VSCODE} alt="opencode in VS Code" /> + </div> + </div> + <div data-slot="cell"> + <div data-component="title">opencode in GitHub</div> + <div data-slot="filler"> + <img src={IMG_GITHUB} alt="opencode in GitHub" /> + </div> + </div> + </div> + </section> + + <footer data-component="footer"> + <div data-slot="cell"> + <a href="https://github.com/sst/opencode">GitHub</a> + </div> + <div data-slot="cell"> + <a href="https://opencode.ai/discord">Discord</a> + </div> + <div data-slot="cell"> + <span> + ©2025 <a href="https://anoma.ly">Anomaly Innovations</a> + </span> + </div> + </footer> + </div> + </main> + ) +} diff --git a/cloud/app/src/style/base.css b/cloud/app/src/style/base.css new file mode 100644 index 000000000..2c95cdbb7 --- /dev/null +++ b/cloud/app/src/style/base.css @@ -0,0 +1,8 @@ +html { + color-scheme: dark; + line-height: 1; +} + +body { + font-family: var(--font-sans); +} diff --git a/cloud/app/src/style/component/button.css b/cloud/app/src/style/component/button.css new file mode 100644 index 000000000..d10f7af53 --- /dev/null +++ b/cloud/app/src/style/component/button.css @@ -0,0 +1,102 @@ +[data-component="button"] { + display: inline-flex; + align-items: center; + justify-content: center; + gap: var(--space-2); + padding: var(--space-3) var(--space-4); + border: 1px solid transparent; + border-radius: var(--space-2); + font-family: var(--font-sans); + font-size: var(--font-size-md); + font-weight: 500; + line-height: 1.25; + cursor: pointer; + transition: all 0.2s ease-in-out; + text-decoration: none; + user-select: none; + + &:disabled { + opacity: 0.5; + cursor: not-allowed; + } + + &:focus { + outline: none; + box-shadow: 0 0 0 2px var(--color-primary); + } + + &[data-color="primary"] { + background-color: var(--color-primary); + color: var(--color-primary-text); + border-color: var(--color-primary); + + &:hover:not(:disabled) { + background-color: var(--color-primary-hover); + border-color: var(--color-primary-hover); + } + + &:active:not(:disabled) { + background-color: var(--color-primary-active); + border-color: var(--color-primary-active); + } + } + + &[data-color="danger"] { + background-color: var(--color-danger); + color: var(--color-danger-text); + border-color: var(--color-danger); + + &:hover:not(:disabled) { + background-color: var(--color-danger-hover); + border-color: var(--color-danger-hover); + } + + &:active:not(:disabled) { + background-color: var(--color-danger-active); + border-color: var(--color-danger-active); + } + + &:focus { + box-shadow: 0 0 0 2px var(--color-danger); + } + } + + &[data-color="warning"] { + background-color: var(--color-warning); + color: var(--color-warning-text); + border-color: var(--color-warning); + + &:hover:not(:disabled) { + background-color: var(--color-warning-hover); + border-color: var(--color-warning-hover); + } + + &:active:not(:disabled) { + background-color: var(--color-warning-active); + border-color: var(--color-warning-active); + } + + &:focus { + box-shadow: 0 0 0 2px var(--color-warning); + } + } + + &[data-size="small"] { + padding: var(--space-2) var(--space-3); + font-size: var(--font-size-sm); + gap: var(--space-1-5); + } + + &[data-size="large"] { + padding: var(--space-4) var(--space-6); + font-size: var(--font-size-lg); + gap: var(--space-3); + } + + [data-slot="icon"] { + display: flex; + align-items: center; + width: 1em; + height: 1em; + } +} diff --git a/cloud/app/src/style/index.css b/cloud/app/src/style/index.css new file mode 100644 index 000000000..832a901e8 --- /dev/null +++ b/cloud/app/src/style/index.css @@ -0,0 +1,8 @@ +@import "./token/color.css"; +@import "./token/font.css"; +@import "./token/space.css"; + +@import "./component/button.css"; + +@import "./reset.css"; +@import "./base.css"; diff --git a/cloud/app/src/style/reset.css b/cloud/app/src/style/reset.css new file mode 100644 index 000000000..d331ed724 --- /dev/null +++ b/cloud/app/src/style/reset.css @@ -0,0 +1,76 @@ +/* 1. Use a more-intuitive box-sizing model */ +*, +*::before, +*::after { + box-sizing: border-box; +} + +/* 2. Remove default margin */ +* { + margin: 0; +} + +/* 3. Enable keyword animations */ +@media (prefers-reduced-motion: no-preference) { + html { + interpolate-size: allow-keywords; + } +} + +body { + /* 4. Add accessible line-height */ + line-height: 1.5; + /* 5. Improve text rendering */ + -webkit-font-smoothing: antialiased; +} + +/* 6. Improve media defaults */ +img, +picture, +video, +canvas, +svg { + display: block; + max-width: 100%; +} + +/* 7. Inherit fonts for form controls */ +input, +button, +textarea, +select { + font: inherit; +} + +/* 8. Avoid text overflows */ +p, +h1, +h2, +h3, +h4, +h5, +h6 { + overflow-wrap: break-word; +} + +/* 9. Improve line wrapping */ +p { + text-wrap: pretty; +} + +h1, +h2, +h3, +h4, +h5, +h6 { + text-wrap: balance; +} + +/* + 10. Create a root stacking context +*/ +#root, +#__next { + isolation: isolate; +} diff --git a/cloud/app/src/style/token/color.css b/cloud/app/src/style/token/color.css new file mode 100644 index 000000000..5382321e3 --- /dev/null +++ b/cloud/app/src/style/token/color.css @@ -0,0 +1,90 @@ +body { + --color-white: #ffffff; + --color-black: #000000; +} + +[data-color-mode="dark"] { + /* OpenCode theme colors */ + --color-bg: #0c0c0e; + --color-bg-surface: #161618; + --color-bg-elevated: #1c1c1f; + + --color-text: #ffffff; + --color-text-muted: #a1a1a6; + --color-text-disabled: #68686f; + + --color-accent: #007aff; + --color-accent-hover: #0056b3; + --color-accent-active: #004085; + + --color-success: #30d158; + --color-warning: #ff9f0a; + --color-danger: #ff453a; + + --color-border: #38383a; + --color-border-muted: #2c2c2e; + + /* Button colors */ + --color-primary: var(--color-accent); + --color-primary-hover: var(--color-accent-hover); + --color-primary-active: var(--color-accent-active); + --color-primary-text: #ffffff; + + --color-danger: #ff453a; + --color-danger-hover: #d70015; + --color-danger-active: #a50011; + --color-danger-text: #ffffff; + + --color-warning: #ff9f0a; + --color-warning-hover: #cc7f08; + --color-warning-active: #995f06; + --color-warning-text: #000000; + + /* Surface colors */ + --color-surface: var(--color-bg-surface); + --color-surface-hover: var(--color-bg-elevated); + --color-border: var(--color-border); +} + +[data-color-mode="light"] { + /* OpenCode light theme colors */ + --color-bg: #ffffff; + --color-bg-surface: #f5f5f7; + --color-bg-elevated: #ffffff; + + --color-text: #1d1d1f; + --color-text-muted: #6e6e73; + --color-text-disabled: #86868b; + + --color-accent: #007aff; + --color-accent-hover: #0056b3; + --color-accent-active: #004085; + + --color-success: #30d158; + --color-warning: #ff9f0a; + --color-danger: #ff3b30; + + --color-border: #d2d2d7; + --color-border-muted: #e5e5ea; + + /* Button colors */ + --color-primary: var(--color-accent); + --color-primary-hover: var(--color-accent-hover); + --color-primary-active: var(--color-accent-active); + --color-primary-text: #ffffff; + + --color-danger: #ff3b30; + --color-danger-hover: #d70015; + --color-danger-active: #a50011; + --color-danger-text: #ffffff; + + --color-warning: #ff9f0a; + --color-warning-hover: #cc7f08; + --color-warning-active: #995f06; + --color-warning-text: #000000; + + /* Surface colors */ + --color-surface: var(--color-bg-surface); + --color-surface-hover: var(--color-bg-elevated); + --color-border: var(--color-border); +} diff --git a/cloud/app/src/style/token/font.css b/cloud/app/src/style/token/font.css new file mode 100644 index 000000000..1852af5b0 --- /dev/null +++ b/cloud/app/src/style/token/font.css @@ -0,0 +1,18 @@ +body { + --font-size-2xs: 0.6875rem; + --font-size-xs: 0.75rem; + --font-size-sm: 0.8125rem; + --font-size-md: 0.9375rem; + --font-size-lg: 1.125rem; + --font-size-xl: 1.25rem; + --font-size-2xl: 1.5rem; + --font-size-3xl: 1.875rem; + --font-size-4xl: 2.25rem; + --font-size-5xl: 3rem; + --font-size-6xl: 3.75rem; + --font-size-7xl: 4.5rem; + --font-size-8xl: 6rem; + --font-size-9xl: 8rem; + --font-mono: IBM Plex Mono; + --font-sans: Inter; +} diff --git a/cloud/app/src/style/token/space.css b/cloud/app/src/style/token/space.css new file mode 100644 index 000000000..633c1238c --- /dev/null +++ b/cloud/app/src/style/token/space.css @@ -0,0 +1,41 @@ +body { + --space-0: 0; + --space-px: 1px; + --space-0-5: 0.125rem; + --space-1: 0.25rem; + --space-1-5: 0.375rem; + --space-2: 0.5rem; + --space-2-5: 0.625rem; + --space-3: 0.75rem; + --space-3-5: 0.875rem; + --space-4: 1rem; + --space-4-5: 1.125rem; + --space-5: 1.25rem; + --space-6: 1.5rem; + --space-7: 1.75rem; + --space-8: 2rem; + --space-9: 2.25rem; + --space-10: 2.5rem; + --space-11: 2.75rem; + --space-12: 3rem; + --space-14: 3.5rem; + --space-16: 4rem; + --space-17: 4.25rem; + --space-18: 4.5rem; + --space-19: 4.75rem; + --space-20: 5rem; + --space-24: 6rem; + --space-28: 7rem; + --space-32: 8rem; + --space-36: 9rem; + --space-40: 10rem; + --space-44: 11rem; + --space-48: 12rem; + --space-52: 13rem; + --space-56: 14rem; + --space-60: 15rem; + --space-64: 16rem; + --space-72: 18rem; + --space-80: 20rem; + --space-96: 24rem; +} diff --git a/cloud/app/tsconfig.json b/cloud/app/tsconfig.json new file mode 100644 index 000000000..7d5871a07 --- /dev/null +++ b/cloud/app/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "ESNext", + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "jsx": "preserve", + "jsxImportSource": "solid-js", + "allowJs": true, + "strict": true, + "noEmit": true, + "types": ["vinxi/types/client"], + "isolatedModules": true, + "paths": { + "~/*": ["./src/*"] + } + } +} diff --git a/cloud/web/src/ui/style/token/space.css b/cloud/web/src/ui/style/token/space.css index b1e492f49..4a061d756 100644 --- a/cloud/web/src/ui/style/token/space.css +++ b/cloud/web/src/ui/style/token/space.css @@ -20,6 +20,7 @@ --space-12: 3rem; --space-14: 3.5rem; --space-16: 4rem; + --space-18: 4.5rem; --space-20: 5rem; --space-24: 6rem; --space-28: 7rem; |
