feat: add website components and pages for LexAI
Some checks failed
CI — Chrome / Chrome — typecheck, test, build (pull_request) Has been cancelled
CI — CLI / CLI — typecheck, build, smoke (pull_request) Has been cancelled
CI — Website / Website — typecheck, build (pull_request) Has been cancelled
CI — VS Code / VS Code — typecheck, build (pull_request) Has been cancelled
Some checks failed
CI — Chrome / Chrome — typecheck, test, build (pull_request) Has been cancelled
CI — CLI / CLI — typecheck, build, smoke (pull_request) Has been cancelled
CI — Website / Website — typecheck, build (pull_request) Has been cancelled
CI — VS Code / VS Code — typecheck, build (pull_request) Has been cancelled
- Implemented SiteHeader and SiteFooter components for consistent navigation and footer across the website. - Created CommunityPage and ContributePage to provide information on community involvement and contribution guidelines. - Developed DocsPage to serve as the main documentation hub, detailing features and installation instructions. - Added HomePage to introduce LexAI and its functionalities, highlighting key features and privacy aspects. - Established links.ts for centralized management of external links used throughout the website. - Configured main.tsx for application entry point with React Router for navigation. - Introduced styles.css for consistent styling across the website. - Set up TypeScript configuration files for improved type checking and development experience. - Configured Vite for building and serving the website.
This commit is contained in:
@@ -10,7 +10,15 @@
|
||||
|
||||
---
|
||||
|
||||
## A. Install on your machine (sideload)
|
||||
## A. Install from the Marketplace (recommended)
|
||||
|
||||
[LexAI on the Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=JuanKibin.lexai-vscode) — search **LexAI** or:
|
||||
|
||||
```powershell
|
||||
code --install-extension JuanKibin.lexai-vscode
|
||||
```
|
||||
|
||||
## B. Install on your machine (sideload)
|
||||
|
||||
### VS Code
|
||||
|
||||
|
||||
@@ -78,7 +78,7 @@ Change in Settings or via option chips in the suggestion zone.
|
||||
|
||||
## Quick start
|
||||
|
||||
1. Install this extension (Marketplace, Open VSX, or **Install from VSIX**).
|
||||
1. Install from the [VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=JuanKibin.lexai-vscode), or **Install from VSIX**.
|
||||
2. Command Palette → **LexAI: Open Settings** → provider, model, API key.
|
||||
3. Select text or code → click **LexAI** on the line → pick an action or **Code Assist**.
|
||||
4. Review in the suggestion zone → Accept to replace the selection (or Copy from the sidebar / panel).
|
||||
@@ -99,6 +99,7 @@ Install / publish details: [DEPLOY.md](./DEPLOY.md).
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| **This extension** | [VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=JuanKibin.lexai-vscode) (`JuanKibin.lexai-vscode`) |
|
||||
| **Chrome extension** | [Chrome Web Store](https://chromewebstore.google.com/detail/bagpcheidbkfgijnnmolnkgagibbjfnk) |
|
||||
| **Source** | [LexAI repository](https://git.juankibin.space/kibin/LexAI) |
|
||||
| **Publisher** | JuanKibin |
|
||||
|
||||
50
packages/website/README.md
Normal file
50
packages/website/README.md
Normal file
@@ -0,0 +1,50 @@
|
||||
# LexAI website
|
||||
|
||||
Marketing site for the LexAI monorepo: Home, Docs, Contribute, Community.
|
||||
|
||||
## Develop
|
||||
|
||||
```bash
|
||||
# from repo root
|
||||
npm run website:install
|
||||
npm run website:dev
|
||||
```
|
||||
|
||||
Or inside this package:
|
||||
|
||||
```bash
|
||||
npm install
|
||||
npm run dev
|
||||
```
|
||||
|
||||
## Build
|
||||
|
||||
```bash
|
||||
npm run website:build
|
||||
# → packages/website/dist/
|
||||
```
|
||||
|
||||
Preview production build: `npm run website:preview`.
|
||||
|
||||
Uses HashRouter so the site works on static hosts without server-side rewrite rules.
|
||||
|
||||
## Deploy on Coolify
|
||||
|
||||
This package is a **static** Vite build (`dist/`). No LexAI API keys or env vars.
|
||||
|
||||
1. In Coolify: **+ New** → **Resource** → **Private Repository (with deploy key)** (Gitea).
|
||||
2. Repo SSH URL, for example `git@git.juankibin.space:kibin/LexAI.git`. If Gitea SSH is not on port 22, use `ssh://git@git.juankibin.space:PORT/kibin/LexAI.git` (Coolify sometimes needs `ssh:///` with three slashes).
|
||||
3. Add Coolify’s public deploy key to the Gitea repo: **Settings → Deploy / Access Keys** (read-only).
|
||||
4. Branch: `dev` or `main`.
|
||||
5. **Build Pack:** Nixpacks.
|
||||
6. **Base Directory:** `/packages/website` — required. Root `npm run build` builds Chrome, not this site.
|
||||
7. Enable **Is it a static site?**
|
||||
8. **Publish Directory:** `/dist`
|
||||
9. **Install command** (if the build says `vite: not found`): `npm ci` (do not omit devDependencies — Vite lives there).
|
||||
10. Optional env: `NIXPACKS_NODE_VERSION=22`
|
||||
11. Optional **Watch Paths:** `packages/website` so Chrome/VS Code/CLI commits do not redeploy.
|
||||
12. Set the domain, **Deploy**, then open `https://your-domain/#/docs`.
|
||||
|
||||
Auto-deploy: Coolify resource → **Webhooks** → copy the URL + secret into Gitea **Settings → Webhooks** (push events).
|
||||
|
||||
Do not add `LEXAI_API_KEY` or any provider key to this Coolify app.
|
||||
23
packages/website/index.html
Normal file
23
packages/website/index.html
Normal file
@@ -0,0 +1,23 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta
|
||||
name="description"
|
||||
content="LexAI — bring-your-own-LLM writing and code assist. Chrome, VS Code, and CLI. No LexAI servers, no subscription."
|
||||
/>
|
||||
<link rel="icon" type="image/svg+xml" href="/icon.svg" />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<title>LexAI — BYO-LLM writing & code assist</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
8
packages/website/nixpacks.toml
Normal file
8
packages/website/nixpacks.toml
Normal file
@@ -0,0 +1,8 @@
|
||||
[phases.setup]
|
||||
nixPkgs = ["nodejs_22"]
|
||||
|
||||
[phases.install]
|
||||
cmds = ["npm ci"]
|
||||
|
||||
[phases.build]
|
||||
cmds = ["npm run build"]
|
||||
1951
packages/website/package-lock.json
generated
Normal file
1951
packages/website/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
27
packages/website/package.json
Normal file
27
packages/website/package.json
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "lexai-website",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"engines": {
|
||||
"node": ">=22"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc --noEmit && vite build",
|
||||
"preview": "vite preview",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-router-dom": "^6.29.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.3.1",
|
||||
"@types/react-dom": "^18.3.1",
|
||||
"@vitejs/plugin-react": "^4.3.4",
|
||||
"typescript": "^5.7.3",
|
||||
"vite": "^6.1.0"
|
||||
}
|
||||
}
|
||||
4
packages/website/public/icon.svg
Normal file
4
packages/website/public/icon.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128" width="128" height="128" role="img" aria-label="LexAI">
|
||||
<rect x="8" y="8" width="112" height="112" rx="28" fill="#4f46e5"/>
|
||||
<polygon points="72,22 42,68 62,68 56,106 86,60 66,60" fill="#f5c14a"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 269 B |
25
packages/website/src/App.tsx
Normal file
25
packages/website/src/App.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import { Navigate, Route, Routes } from 'react-router-dom';
|
||||
import { SiteHeader } from './components/SiteHeader';
|
||||
import { SiteFooter } from './components/SiteFooter';
|
||||
import { HomePage } from './pages/HomePage';
|
||||
import { DocsPage } from './pages/DocsPage';
|
||||
import { ContributePage } from './pages/ContributePage';
|
||||
import { CommunityPage } from './pages/CommunityPage';
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<div className="shell">
|
||||
<SiteHeader />
|
||||
<main>
|
||||
<Routes>
|
||||
<Route path="/" element={<HomePage />} />
|
||||
<Route path="/docs" element={<DocsPage />} />
|
||||
<Route path="/contribute" element={<ContributePage />} />
|
||||
<Route path="/community" element={<CommunityPage />} />
|
||||
<Route path="*" element={<Navigate to="/" replace />} />
|
||||
</Routes>
|
||||
</main>
|
||||
<SiteFooter />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
21
packages/website/src/components/LogoMark.tsx
Normal file
21
packages/website/src/components/LogoMark.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
type LogoMarkProps = {
|
||||
size?: number;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
/** Flat LexAI mark — indigo tile + gold bolt, for the light site. */
|
||||
export function LogoMark({ size = 28, className }: LogoMarkProps) {
|
||||
return (
|
||||
<svg
|
||||
className={className}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 128 128"
|
||||
width={size}
|
||||
height={size}
|
||||
aria-hidden="true"
|
||||
>
|
||||
<rect x="8" y="8" width="112" height="112" rx="28" fill="#4f46e5" />
|
||||
<polygon points="72,22 42,68 62,68 56,106 86,60 66,60" fill="#f5c14a" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
20
packages/website/src/components/SiteFooter.tsx
Normal file
20
packages/website/src/components/SiteFooter.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import { Link } from 'react-router-dom';
|
||||
import { REPO } from '../links';
|
||||
|
||||
export function SiteFooter() {
|
||||
return (
|
||||
<footer className="footer">
|
||||
<div className="wrap footer-inner">
|
||||
<p style={{ margin: 0 }}>© {new Date().getFullYear()} LexAI · MIT</p>
|
||||
<div style={{ display: 'flex', gap: '1rem', flexWrap: 'wrap' }}>
|
||||
<Link to="/docs">Docs</Link>
|
||||
<Link to="/contribute">Contribute</Link>
|
||||
<Link to="/community">Community</Link>
|
||||
<a href={REPO} target="_blank" rel="noreferrer">
|
||||
Source
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
28
packages/website/src/components/SiteHeader.tsx
Normal file
28
packages/website/src/components/SiteHeader.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
import { Link, NavLink } from 'react-router-dom';
|
||||
import { LogoMark } from './LogoMark';
|
||||
import { REPO } from '../links';
|
||||
|
||||
export function SiteHeader() {
|
||||
return (
|
||||
<header className="header">
|
||||
<div className="wrap header-inner">
|
||||
<Link to="/" className="brand" aria-label="LexAI home">
|
||||
<LogoMark size={22} />
|
||||
LexAI
|
||||
</Link>
|
||||
<nav className="nav" aria-label="Primary">
|
||||
<NavLink to="/docs">Docs</NavLink>
|
||||
<NavLink to="/contribute" className="hide-sm">
|
||||
Contribute
|
||||
</NavLink>
|
||||
<NavLink to="/community" className="hide-sm">
|
||||
Community
|
||||
</NavLink>
|
||||
<a className="btn btn-primary" href={REPO} target="_blank" rel="noreferrer">
|
||||
Star on Gitea
|
||||
</a>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
9
packages/website/src/links.ts
Normal file
9
packages/website/src/links.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
export const CHROME_STORE =
|
||||
'https://chromewebstore.google.com/detail/bagpcheidbkfgijnnmolnkgagibbjfnk';
|
||||
export const VSCODE_MARKETPLACE =
|
||||
'https://marketplace.visualstudio.com/items?itemName=JuanKibin.lexai-vscode';
|
||||
export const REPO = 'https://git.juankibin.space/kibin/LexAI';
|
||||
export const REPO_ISSUES = `${REPO}/issues/new`;
|
||||
export const REPO_CONTRIBUTING = `${REPO}/src/branch/main/CONTRIBUTING.md`;
|
||||
export const REPO_CONDUCT = `${REPO}/src/branch/main/CODE_OF_CONDUCT.md`;
|
||||
export const REPO_LICENSE = `${REPO}/src/branch/main/LICENSE`;
|
||||
13
packages/website/src/main.tsx
Normal file
13
packages/website/src/main.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import { StrictMode } from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import { HashRouter } from 'react-router-dom';
|
||||
import App from './App';
|
||||
import './styles.css';
|
||||
|
||||
createRoot(document.getElementById('root')!).render(
|
||||
<StrictMode>
|
||||
<HashRouter>
|
||||
<App />
|
||||
</HashRouter>
|
||||
</StrictMode>,
|
||||
);
|
||||
83
packages/website/src/pages/CommunityPage.tsx
Normal file
83
packages/website/src/pages/CommunityPage.tsx
Normal file
@@ -0,0 +1,83 @@
|
||||
import { Link } from 'react-router-dom';
|
||||
import {
|
||||
CHROME_STORE,
|
||||
REPO,
|
||||
REPO_CONDUCT,
|
||||
REPO_ISSUES,
|
||||
REPO_LICENSE,
|
||||
VSCODE_MARKETPLACE,
|
||||
} from '../links';
|
||||
|
||||
export function CommunityPage() {
|
||||
return (
|
||||
<div className="wrap page">
|
||||
<article className="docs-content">
|
||||
<h1>Community</h1>
|
||||
<p className="intro">
|
||||
LexAI is maintained in the open. Use these links to install, report issues, and follow the
|
||||
project.
|
||||
</p>
|
||||
|
||||
<section className="docs-block" id="source">
|
||||
<h2>Source & discussion</h2>
|
||||
<div className="package-grid">
|
||||
<a className="package-card" href={REPO} target="_blank" rel="noreferrer">
|
||||
<strong>Repository</strong>
|
||||
<p>Source, issues, and pull requests on Gitea.</p>
|
||||
<span className="more">git.juankibin.space/kibin/LexAI</span>
|
||||
</a>
|
||||
<a className="package-card" href={REPO_ISSUES} target="_blank" rel="noreferrer">
|
||||
<strong>Bug report</strong>
|
||||
<p>Use the issue template. Redact API keys.</p>
|
||||
<span className="more">Open an issue</span>
|
||||
</a>
|
||||
<a className="package-card" href={REPO_CONDUCT} target="_blank" rel="noreferrer">
|
||||
<strong>Code of Conduct</strong>
|
||||
<p>Contributor Covenant. Be kind and stay on topic.</p>
|
||||
<span className="more">Read</span>
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="docs-block" id="install">
|
||||
<h2>Install channels</h2>
|
||||
<ul>
|
||||
<li>
|
||||
<a href={CHROME_STORE} target="_blank" rel="noreferrer">
|
||||
Chrome Web Store
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href={VSCODE_MARKETPLACE} target="_blank" rel="noreferrer">
|
||||
VS Code Marketplace
|
||||
</a>{' '}
|
||||
— <code>JuanKibin.lexai-vscode</code>
|
||||
</li>
|
||||
<li>
|
||||
CLI — build from the repo (<Link to="/docs">docs</Link>)
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section className="docs-block" id="audience">
|
||||
<h2>Who it’s for</h2>
|
||||
<p>
|
||||
People who already hold an LLM API key and want inline writing or prompt help without a
|
||||
SaaS subscription. Developers using Chrome, VS Code / Cursor, or a terminal agent loop.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="docs-block" id="license">
|
||||
<h2>License</h2>
|
||||
<p>
|
||||
LexAI is released under the{' '}
|
||||
<a href={REPO_LICENSE} target="_blank" rel="noreferrer">
|
||||
MIT License
|
||||
</a>
|
||||
.
|
||||
</p>
|
||||
</section>
|
||||
</article>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
88
packages/website/src/pages/ContributePage.tsx
Normal file
88
packages/website/src/pages/ContributePage.tsx
Normal file
@@ -0,0 +1,88 @@
|
||||
import { REPO, REPO_CONDUCT, REPO_CONTRIBUTING } from '../links';
|
||||
|
||||
export function ContributePage() {
|
||||
return (
|
||||
<div className="wrap page">
|
||||
<article className="docs-content">
|
||||
<h1>Contribute</h1>
|
||||
<p className="intro">
|
||||
LexAI is a monorepo: Chrome, VS Code, CLI, website, and a shared library. Please follow
|
||||
the{' '}
|
||||
<a href={REPO_CONDUCT} target="_blank" rel="noreferrer">
|
||||
Code of Conduct
|
||||
</a>
|
||||
.
|
||||
</p>
|
||||
|
||||
<section className="docs-block" id="ways">
|
||||
<h2>Ways to help</h2>
|
||||
<ul>
|
||||
<li>Bug reports with reproduction steps</li>
|
||||
<li>Documentation fixes</li>
|
||||
<li>Small, focused pull requests (one concern per PR)</li>
|
||||
<li>Design discussion when a change is large or security-sensitive</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section className="docs-block" id="setup">
|
||||
<h2>Development setup</h2>
|
||||
<p>Node.js 22+ and npm 11+.</p>
|
||||
<pre className="mono">{`git clone https://git.juankibin.space/kibin/LexAI.git
|
||||
cd LexAI
|
||||
npm run install:all`}</pre>
|
||||
<ul>
|
||||
<li>
|
||||
<code>npm run chrome:dev</code> / <code>chrome:test</code> /{' '}
|
||||
<code>chrome:typecheck</code>
|
||||
</li>
|
||||
<li>
|
||||
<code>npm run vscode:build</code> / <code>vscode:typecheck</code>
|
||||
</li>
|
||||
<li>
|
||||
<code>npm run cli:build</code> / <code>cli:typecheck</code>
|
||||
</li>
|
||||
<li>
|
||||
<code>npm run website:dev</code>
|
||||
</li>
|
||||
</ul>
|
||||
<p>
|
||||
Shared code lives in <code>src/lib/</code>. Do not import <code>@lib/crypto</code> or{' '}
|
||||
<code>@lib/messaging</code> from VS Code or CLI.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="docs-block" id="prs">
|
||||
<h2>Pull requests</h2>
|
||||
<ol>
|
||||
<li>
|
||||
Branch from <code>main</code> or <code>develop</code>.
|
||||
</li>
|
||||
<li>Keep the change scoped. Update docs when behavior changes.</li>
|
||||
<li>Run the checks for the package you touched.</li>
|
||||
<li>
|
||||
Use the PR template in <code>.gitea/PULL_REQUEST_TEMPLATE.md</code>.
|
||||
</li>
|
||||
<li>Never commit API keys, tokens, or personal data.</li>
|
||||
</ol>
|
||||
</section>
|
||||
|
||||
<section className="docs-block" id="security">
|
||||
<h2>Security</h2>
|
||||
<p>
|
||||
If you find a vulnerability around API-key handling or data exfiltration, do not open a
|
||||
public issue with exploit details. Contact the maintainer privately (repository owner /
|
||||
Chrome Web Store listing).
|
||||
</p>
|
||||
<div className="docs-meta">
|
||||
<a className="btn btn-primary" href={REPO} target="_blank" rel="noreferrer">
|
||||
Open the repository
|
||||
</a>
|
||||
<a className="btn btn-ghost" href={REPO_CONTRIBUTING} target="_blank" rel="noreferrer">
|
||||
CONTRIBUTING.md
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
</article>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
278
packages/website/src/pages/DocsPage.tsx
Normal file
278
packages/website/src/pages/DocsPage.tsx
Normal file
@@ -0,0 +1,278 @@
|
||||
import { useEffect } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { CHROME_STORE, REPO, VSCODE_MARKETPLACE } from '../links';
|
||||
|
||||
function scrollToSection(id: string) {
|
||||
document.getElementById(id)?.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
}
|
||||
|
||||
export function DocsPage() {
|
||||
useEffect(() => {
|
||||
const fromPath = window.location.href.match(/docs[#/]+([\w-]+)/i)?.[1];
|
||||
const target = fromPath && fromPath !== 'docs' ? fromPath : undefined;
|
||||
if (target) {
|
||||
window.setTimeout(() => scrollToSection(target), 80);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const nav = [
|
||||
['overview', 'Overview'],
|
||||
['chrome', 'Chrome'],
|
||||
['vscode', 'VS Code'],
|
||||
['cli', 'CLI'],
|
||||
['shared', 'Shared core'],
|
||||
['install', 'Install'],
|
||||
['privacy', 'Privacy'],
|
||||
] as const;
|
||||
|
||||
return (
|
||||
<div className="wrap docs-layout">
|
||||
<aside className="docs-nav" aria-label="Docs sections">
|
||||
{nav.map(([id, label]) => (
|
||||
<button key={id} type="button" className="nav-link-btn" onClick={() => scrollToSection(id)}>
|
||||
{label}
|
||||
</button>
|
||||
))}
|
||||
</aside>
|
||||
|
||||
<article className="docs-content">
|
||||
<h1>Documentation</h1>
|
||||
<p className="intro">
|
||||
LexAI is an open-source, bring-your-own-LLM assistant. This page covers every package and
|
||||
its features. Contributor setup lives on the{' '}
|
||||
<Link to="/contribute">Contribute</Link> page.
|
||||
</p>
|
||||
|
||||
<section className="docs-block" id="overview">
|
||||
<h2>Overview</h2>
|
||||
<p>
|
||||
Three clients share one provider/prompt core. There is no LexAI backend — you supply an
|
||||
API key for OpenAI, Anthropic, Groq, or OpenRouter.
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
<strong>Chrome</strong> — writing help on any webpage
|
||||
</li>
|
||||
<li>
|
||||
<strong>VS Code / Cursor</strong> — writing actions + workspace-aware Code Assist
|
||||
</li>
|
||||
<li>
|
||||
<strong>CLI</strong> — Prompt Builder for the terminal
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section className="docs-block" id="chrome">
|
||||
<h2>Chrome extension</h2>
|
||||
<p>
|
||||
Manifest V3. Select text on any page, run an action, Replace or Copy. API key encrypted
|
||||
locally with tweetnacl secretbox.
|
||||
</p>
|
||||
<h3>Setup</h3>
|
||||
<p>
|
||||
Options page → choose provider (OpenAI, Anthropic, Groq, or OpenRouter) and paste your
|
||||
API key. There are no environment variables.
|
||||
</p>
|
||||
<h3>Features</h3>
|
||||
<ul>
|
||||
<li>Fix Grammar, Rephrase, Shorten, Expand, Explain, Make Prompt</li>
|
||||
<li>Writing styles: Formal, Casual, Academic, Creative, Concise</li>
|
||||
<li>Floating toolbar, right-click context menu, popup editor</li>
|
||||
<li>Prompt Builder — patterns, persona, output format</li>
|
||||
<li>Providers: OpenAI · Anthropic · Groq · OpenRouter</li>
|
||||
<li>Works in text boxes, email composers, forms, and contenteditable pages</li>
|
||||
</ul>
|
||||
<div className="docs-meta">
|
||||
<a className="btn btn-primary" href={CHROME_STORE} target="_blank" rel="noreferrer">
|
||||
Chrome Web Store
|
||||
</a>
|
||||
<a
|
||||
className="btn btn-ghost"
|
||||
href={`${REPO}/src/branch/main/packages/chrome`}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
Package README
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="docs-block" id="vscode">
|
||||
<h2>VS Code / Cursor extension</h2>
|
||||
<p>
|
||||
Editor twin of LexAI. Select text or code → action or Code Assist → Accept / Regenerate
|
||||
/ Discard. Key lives in VS Code Secret Storage.
|
||||
</p>
|
||||
<p>
|
||||
Published listing:{' '}
|
||||
<a href={VSCODE_MARKETPLACE} target="_blank" rel="noreferrer">
|
||||
JuanKibin.lexai-vscode
|
||||
</a>
|
||||
</p>
|
||||
<h3>Setup</h3>
|
||||
<p>
|
||||
Command Palette → <code>LexAI: Open Settings</code> → provider, model, and API key.
|
||||
The key is stored in VS Code Secret Storage. There are no environment variables.
|
||||
</p>
|
||||
<h3>Code Assist</h3>
|
||||
<ul>
|
||||
<li>Freeform instruction in an inline prompt above the selection</li>
|
||||
<li>
|
||||
Workspace context: surrounding code, imports, and symbol definitions from other files
|
||||
</li>
|
||||
<li>Entry points: CodeLens, context menu, Command Palette, Activity Bar sidebar</li>
|
||||
</ul>
|
||||
<h3>Writing actions</h3>
|
||||
<ul>
|
||||
<li>Fix Grammar · Rephrase · Shorten · Expand · Explain · Make Prompt</li>
|
||||
<li>Writing styles + Prompt Builder option chips in the suggestion zone</li>
|
||||
<li>Suggestion UI: zone (default), side panel, or both</li>
|
||||
<li>Status bar: ready / processing / not configured</li>
|
||||
<li>Sidebar workspace panel for paste → run → Copy</li>
|
||||
</ul>
|
||||
<div className="docs-meta">
|
||||
<a className="btn btn-primary" href={VSCODE_MARKETPLACE} target="_blank" rel="noreferrer">
|
||||
VS Code Marketplace
|
||||
</a>
|
||||
<a
|
||||
className="btn btn-ghost"
|
||||
href={`${REPO}/src/branch/main/packages/vscode`}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
Package README
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="docs-block" id="cli">
|
||||
<h2>CLI — Prompt Builder</h2>
|
||||
<p>
|
||||
Improves a rough idea into a paste-ready engineered prompt before you send it to another
|
||||
agent.
|
||||
</p>
|
||||
<h3>Setup</h3>
|
||||
<p>Environment variables (PowerShell shown; bash uses <code>export NAME=value</code>):</p>
|
||||
<pre className="mono">{`$env:LEXAI_API_KEY = "sk-..." # required
|
||||
$env:LEXAI_PROVIDER = "openai" # optional: openai | anthropic | groq | openrouter
|
||||
$env:LEXAI_MODEL = "gpt-4o" # optional`}</pre>
|
||||
<p>
|
||||
Optional defaults file (never put the API key here): <code>~/.lexai/config.json</code>
|
||||
</p>
|
||||
<pre className="mono">{`{
|
||||
"provider": "openai",
|
||||
"model": "gpt-4o",
|
||||
"pattern": "role",
|
||||
"persona": "Expert Developer",
|
||||
"format": "Markdown",
|
||||
"style": "Concise"
|
||||
}`}</pre>
|
||||
<p>
|
||||
Precedence: flags → config file → env → defaults. The key is always{' '}
|
||||
<code>LEXAI_API_KEY</code>.
|
||||
</p>
|
||||
<h3>Features</h3>
|
||||
<ul>
|
||||
<li>
|
||||
Command: <code>lexai prompt</code>
|
||||
</li>
|
||||
<li>
|
||||
Input from args, stdin, or <code>-f/--file</code>
|
||||
</li>
|
||||
<li>Flags: pattern, persona, format, style, provider, model</li>
|
||||
<li>
|
||||
Also <code>--list</code> (patterns, personas, formats, styles) and{' '}
|
||||
<code>--help</code>
|
||||
</li>
|
||||
<li>Engineered prompt on stdout (pipe-safe); progress and errors on stderr</li>
|
||||
</ul>
|
||||
<h3>Examples</h3>
|
||||
<pre className="mono">{`lexai prompt "add rate limiting to the express auth routes"
|
||||
|
||||
echo "debug flaky playwright on CI" | lexai prompt --pattern role
|
||||
|
||||
lexai prompt -f draft.txt --format Markdown --model gpt-4o`}</pre>
|
||||
<div className="docs-meta">
|
||||
<a
|
||||
className="btn btn-ghost"
|
||||
href={`${REPO}/src/branch/main/packages/cli`}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
Package README
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="docs-block" id="shared">
|
||||
<h2>Shared core</h2>
|
||||
<ul>
|
||||
<li>
|
||||
Location: <code>src/lib</code> (<code>providers</code>, <code>actions</code>,{' '}
|
||||
<code>types</code>)
|
||||
</li>
|
||||
<li>Chrome-only modules: crypto (key encryption), messaging helpers</li>
|
||||
<li>VS Code and CLI must not import Chrome-only modules</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section className="docs-block" id="install">
|
||||
<h2>Install</h2>
|
||||
<p>Published builds:</p>
|
||||
<ul>
|
||||
<li>
|
||||
Chrome —{' '}
|
||||
<a href={CHROME_STORE} target="_blank" rel="noreferrer">
|
||||
Chrome Web Store
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
VS Code / Cursor —{' '}
|
||||
<a href={VSCODE_MARKETPLACE} target="_blank" rel="noreferrer">
|
||||
Visual Studio Marketplace
|
||||
</a>{' '}
|
||||
(<code>JuanKibin.lexai-vscode</code>)
|
||||
</li>
|
||||
</ul>
|
||||
<p style={{ marginTop: '1rem' }}>From source (Node.js 22+):</p>
|
||||
<pre className="mono">{`git clone https://git.juankibin.space/kibin/LexAI.git
|
||||
cd LexAI
|
||||
npm run install:all
|
||||
|
||||
# Chrome
|
||||
npm run chrome:dev # or chrome:build → load packages/chrome/.output/chrome-mv3
|
||||
|
||||
# VS Code
|
||||
npm run vscode:build # or install from Marketplace
|
||||
|
||||
# CLI
|
||||
npm run cli:build
|
||||
export LEXAI_API_KEY=sk-... # required
|
||||
export LEXAI_PROVIDER=openai # optional: openai | anthropic | groq | openrouter
|
||||
export LEXAI_MODEL=gpt-4o # optional
|
||||
node packages/cli/out/cli.js prompt "your rough idea"
|
||||
|
||||
# Website
|
||||
npm run website:dev`}</pre>
|
||||
</section>
|
||||
|
||||
<section className="docs-block" id="privacy">
|
||||
<h2>Privacy</h2>
|
||||
<ul>
|
||||
<li>No LexAI backend, account, or subscription</li>
|
||||
<li>No LexAI telemetry</li>
|
||||
<li>
|
||||
Chrome: encrypted key in <code>chrome.storage.local</code>
|
||||
</li>
|
||||
<li>VS Code: Secret Storage</li>
|
||||
<li>
|
||||
CLI: <code>LEXAI_API_KEY</code> required; optional <code>LEXAI_PROVIDER</code> and{' '}
|
||||
<code>LEXAI_MODEL</code>. Never store the key in <code>~/.lexai/config.json</code>
|
||||
</li>
|
||||
<li>Text is sent only to the LLM provider you configure</li>
|
||||
</ul>
|
||||
</section>
|
||||
</article>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
175
packages/website/src/pages/HomePage.tsx
Normal file
175
packages/website/src/pages/HomePage.tsx
Normal file
@@ -0,0 +1,175 @@
|
||||
import { Link, useNavigate } from 'react-router-dom';
|
||||
import { LogoMark } from '../components/LogoMark';
|
||||
import { CHROME_STORE, REPO, VSCODE_MARKETPLACE } from '../links';
|
||||
|
||||
function openDocsSection(navigate: ReturnType<typeof useNavigate>, id: string) {
|
||||
navigate('/docs');
|
||||
window.setTimeout(() => {
|
||||
document.getElementById(id)?.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
}, 60);
|
||||
}
|
||||
|
||||
const FEATURES = [
|
||||
{
|
||||
title: 'Your key. Your provider.',
|
||||
body: 'OpenAI, Anthropic, Groq, or OpenRouter. No LexAI account or servers.',
|
||||
},
|
||||
{
|
||||
title: 'Writing help, like Grammarly',
|
||||
body: 'Select text on any page → Fix, Rephrase, Shorten, Expand, Explain, or Make Prompt.',
|
||||
},
|
||||
{
|
||||
title: 'Coding assist, like Copilot',
|
||||
body: 'VS Code and Cursor: select code, give an instruction, apply a suggestion with file context.',
|
||||
},
|
||||
{
|
||||
title: 'Prompt Builder',
|
||||
body: (
|
||||
<>
|
||||
Turn a rough idea into a paste-ready prompt — in the app or via <code>lexai prompt</code>.
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Private by design',
|
||||
body: 'Keys stay on your device. Text goes only to the provider you choose.',
|
||||
},
|
||||
{
|
||||
title: 'Open source (MIT)',
|
||||
body: 'Chrome, VS Code, and CLI share one core. Audit it, fork it, ship your own build.',
|
||||
},
|
||||
];
|
||||
|
||||
export function HomePage() {
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<>
|
||||
<section className="hero">
|
||||
<div className="wrap">
|
||||
<div className="hero-lockup">
|
||||
<LogoMark size={48} />
|
||||
<p className="hero-wordmark">
|
||||
Lex<span>AI</span>
|
||||
</p>
|
||||
</div>
|
||||
<p className="hero-kicker">Bring your own LLM</p>
|
||||
<h1>Skip Grammarly and Copilot subscriptions</h1>
|
||||
<p className="hero-lead">
|
||||
Writing help on any webpage. Coding assist in VS Code or Cursor. You bring the API key —
|
||||
LexAI has no account, no servers, and no fee.
|
||||
</p>
|
||||
<div className="cta-row">
|
||||
<a className="btn btn-primary" href={CHROME_STORE} target="_blank" rel="noreferrer">
|
||||
Chrome Web Store
|
||||
</a>
|
||||
<a className="btn btn-ghost" href={VSCODE_MARKETPLACE} target="_blank" rel="noreferrer">
|
||||
VS Code Marketplace
|
||||
</a>
|
||||
<a className="btn btn-ghost" href={REPO} target="_blank" rel="noreferrer">
|
||||
Repository
|
||||
</a>
|
||||
<Link className="btn btn-ghost" to="/docs">
|
||||
Docs
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="section" id="why">
|
||||
<div className="wrap">
|
||||
<p className="section-kicker">Why try it</p>
|
||||
<h2>The jobs you already pay for — without a second subscription</h2>
|
||||
<p className="section-lead">
|
||||
Same idea as the tools you know. You pay your LLM provider, not LexAI.
|
||||
</p>
|
||||
<div className="compare-grid">
|
||||
<div className="package-card">
|
||||
<strong>Instead of Grammarly</strong>
|
||||
<p>
|
||||
Fix grammar, rephrase, shorten, expand, or explain on any webpage. Your key stays on
|
||||
your device.
|
||||
</p>
|
||||
<span className="more">Chrome extension</span>
|
||||
</div>
|
||||
<div className="package-card">
|
||||
<strong>Instead of GitHub Copilot</strong>
|
||||
<p>
|
||||
Select code, type what you want, and apply a suggestion with context from other
|
||||
files. Not tab-complete — instruct and review.
|
||||
</p>
|
||||
<span className="more">VS Code / Cursor</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="section" id="features">
|
||||
<div className="wrap">
|
||||
<p className="section-kicker">Product</p>
|
||||
<h2>What LexAI does</h2>
|
||||
<p className="section-lead">
|
||||
One family across browser, editor, and CLI. Same providers. No LexAI backend.
|
||||
</p>
|
||||
<ul className="feature-list">
|
||||
{FEATURES.map((f) => (
|
||||
<li key={f.title}>
|
||||
<h3>{f.title}</h3>
|
||||
<p>{f.body}</p>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="section" id="start">
|
||||
<div className="wrap">
|
||||
<p className="section-kicker">Getting started</p>
|
||||
<h2>Install in minutes</h2>
|
||||
<p className="section-lead">Published builds, or clone the repo if you want to develop.</p>
|
||||
<div className="package-grid">
|
||||
<a className="package-card" href={CHROME_STORE} target="_blank" rel="noreferrer">
|
||||
<strong>Chrome</strong>
|
||||
<p>Install from the Chrome Web Store. Set provider and API key in Options.</p>
|
||||
<span className="more">Chrome Web Store</span>
|
||||
</a>
|
||||
<a className="package-card" href={VSCODE_MARKETPLACE} target="_blank" rel="noreferrer">
|
||||
<strong>VS Code</strong>
|
||||
<p>
|
||||
Install <code>JuanKibin.lexai-vscode</code>. Open Settings, add your key, select
|
||||
code.
|
||||
</p>
|
||||
<span className="more">Marketplace</span>
|
||||
</a>
|
||||
<button
|
||||
type="button"
|
||||
className="package-card"
|
||||
onClick={() => openDocsSection(navigate, 'cli')}
|
||||
>
|
||||
<strong>CLI</strong>
|
||||
<p>
|
||||
Build <code>lexai prompt</code>, set <code>LEXAI_API_KEY</code> and optional{' '}
|
||||
<code>LEXAI_PROVIDER</code> / <code>LEXAI_MODEL</code>.
|
||||
</p>
|
||||
<span className="more">CLI docs</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="section">
|
||||
<div className="wrap privacy">
|
||||
<p className="section-kicker">Privacy</p>
|
||||
<h2>No LexAI cloud</h2>
|
||||
<ul>
|
||||
<li>Your API key never leaves your device except to the provider you configure.</li>
|
||||
<li>No LexAI telemetry and no LexAI servers in the path.</li>
|
||||
<li>
|
||||
Open source — inspect the shared core in <code>src/lib</code>.
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
</>
|
||||
);
|
||||
}
|
||||
536
packages/website/src/styles.css
Normal file
536
packages/website/src/styles.css
Normal file
@@ -0,0 +1,536 @@
|
||||
:root {
|
||||
--bg: #f7f7f5;
|
||||
--surface: #ffffff;
|
||||
--ink: #171717;
|
||||
--muted: #5c5c57;
|
||||
--line: #e6e6e2;
|
||||
--indigo: #4f46e5;
|
||||
--indigo-deep: #4338ca;
|
||||
--bolt: #f5c14a;
|
||||
--focus: #4f46e5;
|
||||
--radius: 8px;
|
||||
--font: 'Inter', 'Segoe UI', system-ui, sans-serif;
|
||||
--mono: ui-monospace, 'SF Mono', Menlo, Consolas, monospace;
|
||||
color-scheme: light;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
min-height: 100vh;
|
||||
font-family: var(--font);
|
||||
font-size: 16px;
|
||||
line-height: 1.6;
|
||||
color: var(--ink);
|
||||
background: var(--bg);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--ink);
|
||||
text-underline-offset: 0.15em;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
button {
|
||||
font: inherit;
|
||||
}
|
||||
|
||||
.shell {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
main {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.wrap {
|
||||
width: min(920px, calc(100% - 2.5rem));
|
||||
margin-inline: auto;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.header {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 20;
|
||||
background: rgba(247, 247, 245, 0.86);
|
||||
backdrop-filter: blur(10px);
|
||||
border-bottom: 1px solid var(--line);
|
||||
}
|
||||
|
||||
.header-inner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
padding: 0.85rem 0;
|
||||
}
|
||||
|
||||
.brand {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
color: var(--ink);
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
font-size: 0.95rem;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
|
||||
.brand:hover {
|
||||
color: var(--ink);
|
||||
}
|
||||
|
||||
.brand svg {
|
||||
display: block;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.nav {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 0.15rem 1.15rem;
|
||||
}
|
||||
|
||||
.nav a:not(.btn),
|
||||
.nav-link-btn {
|
||||
color: var(--muted);
|
||||
text-decoration: none;
|
||||
font-weight: 500;
|
||||
font-size: 0.875rem;
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.nav a:not(.btn):hover,
|
||||
.nav-link-btn:hover,
|
||||
.nav a[aria-current='page']:not(.btn) {
|
||||
color: var(--ink);
|
||||
}
|
||||
|
||||
/* Buttons */
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 40px;
|
||||
padding: 0.5rem 0.95rem;
|
||||
border-radius: 6px;
|
||||
border: 1px solid transparent;
|
||||
font-weight: 550;
|
||||
font-size: 0.875rem;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
transition: background 0.12s ease, border-color 0.12s ease, color 0.12s ease;
|
||||
}
|
||||
|
||||
.btn:focus-visible {
|
||||
outline: 2px solid var(--focus);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: var(--indigo);
|
||||
color: #fff;
|
||||
border-color: var(--indigo);
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background: var(--indigo-deep);
|
||||
color: var(--bolt);
|
||||
border-color: var(--indigo-deep);
|
||||
}
|
||||
|
||||
.btn-ghost {
|
||||
background: transparent;
|
||||
border-color: var(--line);
|
||||
color: var(--ink);
|
||||
}
|
||||
|
||||
.btn-ghost:hover {
|
||||
border-color: #cfcfc9;
|
||||
color: var(--ink);
|
||||
}
|
||||
|
||||
/* Hero */
|
||||
.hero {
|
||||
padding: clamp(4.5rem, 12vw, 7.5rem) 0 4rem;
|
||||
}
|
||||
|
||||
.hero-lockup {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
margin: 0 0 1.5rem;
|
||||
}
|
||||
|
||||
.hero-lockup svg {
|
||||
display: block;
|
||||
border-radius: 14px;
|
||||
}
|
||||
|
||||
.hero-wordmark {
|
||||
font-weight: 600;
|
||||
font-size: 1.35rem;
|
||||
letter-spacing: -0.03em;
|
||||
}
|
||||
|
||||
.hero-wordmark span {
|
||||
color: var(--indigo);
|
||||
}
|
||||
|
||||
.hero-kicker {
|
||||
margin: 0 0 1rem;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.hero h1 {
|
||||
margin: 0 0 1rem;
|
||||
font-weight: 560;
|
||||
font-size: clamp(2rem, 4.6vw, 2.75rem);
|
||||
line-height: 1.15;
|
||||
letter-spacing: -0.035em;
|
||||
max-width: 18ch;
|
||||
}
|
||||
|
||||
.hero-lead {
|
||||
margin: 0 0 1.75rem;
|
||||
max-width: 42ch;
|
||||
color: var(--muted);
|
||||
font-size: 1.05rem;
|
||||
}
|
||||
|
||||
.cta-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.6rem;
|
||||
}
|
||||
|
||||
/* Sections */
|
||||
.section {
|
||||
padding: 3.5rem 0;
|
||||
}
|
||||
|
||||
.section + .section {
|
||||
border-top: 1px solid var(--line);
|
||||
}
|
||||
|
||||
.section-kicker {
|
||||
margin: 0 0 0.4rem;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
color: var(--indigo);
|
||||
}
|
||||
|
||||
.section h2 {
|
||||
margin: 0 0 0.5rem;
|
||||
font-weight: 560;
|
||||
font-size: 1.45rem;
|
||||
letter-spacing: -0.025em;
|
||||
}
|
||||
|
||||
.section-lead {
|
||||
margin: 0 0 2rem;
|
||||
max-width: 48ch;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.feature-list {
|
||||
display: grid;
|
||||
gap: 0;
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
@media (min-width: 700px) {
|
||||
.feature-list {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
column-gap: 3rem;
|
||||
}
|
||||
}
|
||||
|
||||
.feature-list li {
|
||||
padding: 1.15rem 0;
|
||||
border-top: 1px solid var(--line);
|
||||
}
|
||||
|
||||
.feature-list h3 {
|
||||
margin: 0 0 0.3rem;
|
||||
font-size: 0.98rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.feature-list p {
|
||||
margin: 0;
|
||||
color: var(--muted);
|
||||
font-size: 0.92rem;
|
||||
}
|
||||
|
||||
.compare-grid {
|
||||
display: grid;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
@media (min-width: 700px) {
|
||||
.compare-grid {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.package-grid {
|
||||
display: grid;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
@media (min-width: 760px) {
|
||||
.package-grid {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
a.package-card {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.package-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.55rem;
|
||||
padding: 1.2rem 1.15rem 1.25rem;
|
||||
border-radius: var(--radius);
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--line);
|
||||
color: inherit;
|
||||
min-height: 100%;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.package-card:hover {
|
||||
border-color: #c8c8c2;
|
||||
}
|
||||
|
||||
.package-card strong {
|
||||
font-size: 0.98rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.package-card p {
|
||||
margin: 0;
|
||||
flex: 1;
|
||||
color: var(--muted);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.package-card .more {
|
||||
font-weight: 500;
|
||||
font-size: 0.82rem;
|
||||
color: var(--ink);
|
||||
}
|
||||
|
||||
.privacy {
|
||||
padding-top: 0.25rem;
|
||||
}
|
||||
|
||||
.privacy h2 {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.privacy ul {
|
||||
margin: 0.75rem 0 0;
|
||||
padding-left: 1.1rem;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
/* Inner pages (Contribute, Community) */
|
||||
.page {
|
||||
padding: 2.75rem 0 4.5rem;
|
||||
}
|
||||
|
||||
.docs-block ol {
|
||||
margin: 0.45rem 0 0;
|
||||
padding-left: 1.15rem;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.docs-block ol li + li {
|
||||
margin-top: 0.22rem;
|
||||
}
|
||||
|
||||
/* Docs */
|
||||
.docs-layout {
|
||||
display: grid;
|
||||
gap: 2.25rem;
|
||||
padding: 2.75rem 0 4.5rem;
|
||||
}
|
||||
|
||||
@media (min-width: 860px) {
|
||||
.docs-layout {
|
||||
grid-template-columns: 160px 1fr;
|
||||
gap: 3rem;
|
||||
align-items: start;
|
||||
}
|
||||
}
|
||||
|
||||
.docs-nav {
|
||||
position: sticky;
|
||||
top: 4.25rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.15rem;
|
||||
}
|
||||
|
||||
.docs-nav .nav-link-btn {
|
||||
color: var(--muted);
|
||||
font-weight: 500;
|
||||
font-size: 0.875rem;
|
||||
padding: 0.3rem 0;
|
||||
text-align: left;
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.docs-nav .nav-link-btn:hover {
|
||||
color: var(--ink);
|
||||
}
|
||||
|
||||
.docs-content h1 {
|
||||
margin: 0 0 0.65rem;
|
||||
font-weight: 560;
|
||||
font-size: clamp(1.75rem, 3.5vw, 2.15rem);
|
||||
letter-spacing: -0.03em;
|
||||
}
|
||||
|
||||
.docs-content .intro {
|
||||
margin: 0 0 1.75rem;
|
||||
color: var(--muted);
|
||||
max-width: 58ch;
|
||||
}
|
||||
|
||||
.docs-block {
|
||||
scroll-margin-top: 5rem;
|
||||
padding: 1.6rem 0;
|
||||
border-top: 1px solid var(--line);
|
||||
}
|
||||
|
||||
.docs-block h2 {
|
||||
margin: 0 0 0.45rem;
|
||||
font-weight: 560;
|
||||
font-size: 1.25rem;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
|
||||
.docs-block h3 {
|
||||
margin: 1.15rem 0 0.35rem;
|
||||
font-size: 0.82rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.04em;
|
||||
text-transform: uppercase;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.docs-block > p {
|
||||
color: var(--muted);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.docs-block ul {
|
||||
margin: 0.45rem 0 0;
|
||||
padding-left: 1.15rem;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.docs-block li + li {
|
||||
margin-top: 0.22rem;
|
||||
}
|
||||
|
||||
.docs-meta {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
code,
|
||||
.mono {
|
||||
font-family: var(--mono);
|
||||
font-size: 0.86em;
|
||||
color: var(--ink);
|
||||
background: #eeebe6;
|
||||
padding: 0.08em 0.32em;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
pre.mono {
|
||||
display: block;
|
||||
padding: 0.95rem 1rem;
|
||||
overflow-x: auto;
|
||||
border-radius: 6px;
|
||||
border: 1px solid var(--line);
|
||||
background: var(--surface);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
.footer {
|
||||
border-top: 1px solid var(--line);
|
||||
padding: 1.35rem 0 1.75rem;
|
||||
color: var(--muted);
|
||||
font-size: 0.82rem;
|
||||
}
|
||||
|
||||
.footer-inner {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.65rem 1.25rem;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.footer a {
|
||||
color: var(--muted);
|
||||
text-decoration: none;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.footer a:hover {
|
||||
color: var(--ink);
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.nav .hide-sm {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
1
packages/website/src/vite-env.d.ts
vendored
Normal file
1
packages/website/src/vite-env.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/// <reference types="vite/client" />
|
||||
21
packages/website/tsconfig.json
Normal file
21
packages/website/tsconfig.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
"moduleResolution": "Bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"isolatedModules": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noEmitOnError": true
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
12
packages/website/tsconfig.node.json
Normal file
12
packages/website/tsconfig.node.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"lib": ["ES2023"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
"moduleResolution": "Bundler",
|
||||
"strict": true,
|
||||
"noEmit": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
||||
7
packages/website/vite.config.ts
Normal file
7
packages/website/vite.config.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { defineConfig } from 'vite';
|
||||
import react from '@vitejs/plugin-react';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
base: './',
|
||||
});
|
||||
Reference in New Issue
Block a user