فهرست منبع

wip: cloud stuff

Dax Raad 7 ماه پیش
والد
کامیت
07cf8847fb

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 379 - 36
bun.lock


+ 28 - 0
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

+ 61 - 0
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"`

+ 32 - 0
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)

+ 9 - 0
cloud/app/app.config.ts

@@ -0,0 +1,9 @@
+import { defineConfig } from "@solidjs/start/config"
+
+export default defineConfig({
+  vite: {
+    server: {
+      allowedHosts: true,
+    },
+  },
+})

+ 21 - 0
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"
+  }
+}

BIN
cloud/app/public/favicon.ico


+ 1 - 0
cloud/app/src/app.css

@@ -0,0 +1 @@
+@import "./style/index.css";

+ 21 - 0
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>
+  );
+}

+ 19 - 0
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>
+

BIN
cloud/app/src/asset/screenshot-github.webp


BIN
cloud/app/src/asset/screenshot-splash.webp


BIN
cloud/app/src/asset/screenshot-vscode.webp


+ 24 - 0
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>
+  )
+}

+ 4 - 0
cloud/app/src/entry-client.tsx

@@ -0,0 +1,4 @@
+// @refresh reload
+import { mount, StartClient } from "@solidjs/start/client";
+
+mount(() => <StartClient />, document.getElementById("app")!);

+ 21 - 0
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>
+    )}
+  />
+));

+ 1 - 0
cloud/app/src/global.d.ts

@@ -0,0 +1 @@
+/// <reference types="@solidjs/start/env" />

+ 19 - 0
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>
+  );
+}

+ 264 - 0
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;
+      }
+    }
+  }
+}

+ 169 - 0
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&nbsp;</span>
+                <span data-slot="protocol">https://</span>
+                <span data-slot="highlight">opencode.ai/install</span>
+                &nbsp;| 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&nbsp;<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&nbsp;<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&nbsp;<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&nbsp;<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>
+  )
+}

+ 8 - 0
cloud/app/src/style/base.css

@@ -0,0 +1,8 @@
+html {
+  color-scheme: dark;
+  line-height: 1;
+}
+
+body {
+  font-family: var(--font-sans);
+}

+ 102 - 0
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;
+  }
+}

+ 8 - 0
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";

+ 76 - 0
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;
+}

+ 90 - 0
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);
+}

+ 18 - 0
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;
+}

+ 41 - 0
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;
+}

+ 19 - 0
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/*"]
+    }
+  }
+}

+ 1 - 0
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;

برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است