mirror of
https://github.com/arjunkomath/openclaw-railway-template.git
synced 2026-08-14 08:53:11 +00:00
Improve setup flow
This commit is contained in:
+19
-90
@@ -4,105 +4,34 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="refresh" content="3">
|
||||
<title>OpenClaw - Starting Up</title>
|
||||
<title>🦞 OpenClaw — Starting Up</title>
|
||||
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
|
||||
<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=Space+Grotesk:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
:root {
|
||||
--bg-primary: #0f0f0f;
|
||||
--text-primary: #fafafa;
|
||||
--text-muted: #737373;
|
||||
--accent: #ef4444;
|
||||
<style type="text/tailwindcss">
|
||||
@theme {
|
||||
--font-sans: 'Space Grotesk', ui-sans-serif, system-ui, -apple-system, sans-serif;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
:root {
|
||||
--bg-primary: #fafafa;
|
||||
--text-primary: #171717;
|
||||
--text-muted: #737373;
|
||||
--accent: #dc2626;
|
||||
}
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--bg-primary);
|
||||
color: var(--text-primary);
|
||||
font-family: 'Space Grotesk', ui-sans-serif, system-ui, -apple-system, sans-serif;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.container {
|
||||
text-align: center;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.logo {
|
||||
width: 72px;
|
||||
height: 72px;
|
||||
margin: 0 auto 1.5rem;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 2px solid transparent;
|
||||
border-top-color: var(--accent);
|
||||
border-radius: 50%;
|
||||
animation: spin 0.8s linear infinite;
|
||||
margin: 0 auto 2rem;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.75rem;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
color: var(--text-muted);
|
||||
font-size: 0.875rem;
|
||||
.animate-spin-slow {
|
||||
animation: spin 0.8s linear infinite;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<svg class="logo" viewBox="0 0 120 120" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<linearGradient id="lobster-gradient" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" stop-color="#ff4d4d"/>
|
||||
<stop offset="100%" stop-color="#991b1b"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path d="M60 10 C30 10 15 35 15 55 C15 75 30 95 45 100 L45 110 L55 110 L55 100 C55 100 60 102 65 100 L65 110 L75 110 L75 100 C90 95 105 75 105 55 C105 35 90 10 60 10Z" fill="url(#lobster-gradient)"/>
|
||||
<path d="M20 45 C5 40 0 50 5 60 C10 70 20 65 25 55 C28 48 25 45 20 45Z" fill="url(#lobster-gradient)"/>
|
||||
<path d="M100 45 C115 40 120 50 115 60 C110 70 100 65 95 55 C92 48 95 45 100 45Z" fill="url(#lobster-gradient)"/>
|
||||
<path d="M45 15 Q35 5 30 8" stroke="#ff4d4d" stroke-width="3" stroke-linecap="round"/>
|
||||
<path d="M75 15 Q85 5 90 8" stroke="#ff4d4d" stroke-width="3" stroke-linecap="round"/>
|
||||
<circle cx="45" cy="35" r="6" fill="#050810"/>
|
||||
<circle cx="75" cy="35" r="6" fill="#050810"/>
|
||||
<circle cx="46" cy="34" r="2.5" fill="#00e5cc"/>
|
||||
<circle cx="76" cy="34" r="2.5" fill="#00e5cc"/>
|
||||
</svg>
|
||||
<div class="spinner"></div>
|
||||
<h1>OpenClaw is starting up</h1>
|
||||
<p class="subtitle">This page will auto-refresh when the gateway is ready.</p>
|
||||
<body class="bg-neutral-100 dark:bg-neutral-950 text-neutral-900 dark:text-neutral-100 min-h-screen font-sans flex items-center justify-center">
|
||||
<div class="text-center px-6">
|
||||
<div class="flex justify-center mb-6">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/openclaw/openclaw/main/docs/assets/openclaw-logo-text-dark.png">
|
||||
<img src="https://raw.githubusercontent.com/openclaw/openclaw/main/docs/assets/openclaw-logo-text.png" alt="OpenClaw" class="max-w-[200px] h-auto">
|
||||
</picture>
|
||||
</div>
|
||||
<div class="w-10 h-10 border-2 border-transparent border-t-red-500 rounded-full animate-spin-slow mx-auto mb-8"></div>
|
||||
<h1 class="text-xl font-semibold tracking-tight mb-3">OpenClaw is starting up</h1>
|
||||
<p class="text-neutral-500 text-sm">This page will auto-refresh when the gateway is ready.</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+266
-133
@@ -3,13 +3,22 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>OpenClaw Setup</title>
|
||||
<title>🦞 OpenClaw — Personal AI Assistant</title>
|
||||
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link rel="stylesheet" href="/setup/styles.css" />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
<style type="text/tailwindcss">
|
||||
@theme {
|
||||
--font-sans: 'Space Grotesk', ui-sans-serif, system-ui, -apple-system, sans-serif;
|
||||
}
|
||||
[x-cloak] { display: none !important; }
|
||||
</style>
|
||||
<script>
|
||||
document.addEventListener('alpine:init', () => {
|
||||
Alpine.data('setupApp', () => ({
|
||||
step: 1,
|
||||
totalSteps: 3,
|
||||
status: 'Loading...',
|
||||
configured: false,
|
||||
openclawVersion: '',
|
||||
@@ -79,6 +88,14 @@
|
||||
}
|
||||
},
|
||||
|
||||
nextStep() {
|
||||
if (this.step < this.totalSteps) this.step++;
|
||||
},
|
||||
|
||||
prevStep() {
|
||||
if (this.step > 1) this.step--;
|
||||
},
|
||||
|
||||
async runSetup() {
|
||||
const payload = {
|
||||
flow: this.flow,
|
||||
@@ -141,9 +158,15 @@
|
||||
body: JSON.stringify({ channel: channel, code: code })
|
||||
});
|
||||
const t = await res.text();
|
||||
this.log += t + '\n';
|
||||
let result;
|
||||
try { result = JSON.parse(t); } catch (_e) { result = { ok: false, output: t }; }
|
||||
if (result.ok) {
|
||||
this.log += '✓ ' + (result.output || 'Pairing approved successfully') + '\n';
|
||||
} else {
|
||||
this.log += '✗ Failed: ' + (result.output || result.error || 'Unknown error') + '\n';
|
||||
}
|
||||
} catch (e) {
|
||||
this.log += 'Error: ' + String(e) + '\n';
|
||||
this.log += '✗ Error: ' + String(e) + '\n';
|
||||
}
|
||||
},
|
||||
|
||||
@@ -168,147 +191,257 @@
|
||||
</script>
|
||||
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.14.8/dist/cdn.min.js"></script>
|
||||
</head>
|
||||
<body x-data="setupApp()">
|
||||
<div class="logo">
|
||||
<svg viewBox="0 0 120 120" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<linearGradient id="lobster-gradient" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" stop-color="#ff4d4d"/>
|
||||
<stop offset="100%" stop-color="#991b1b"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path d="M60 10 C30 10 15 35 15 55 C15 75 30 95 45 100 L45 110 L55 110 L55 100 C55 100 60 102 65 100 L65 110 L75 110 L75 100 C90 95 105 75 105 55 C105 35 90 10 60 10Z" fill="url(#lobster-gradient)"/>
|
||||
<path d="M20 45 C5 40 0 50 5 60 C10 70 20 65 25 55 C28 48 25 45 20 45Z" fill="url(#lobster-gradient)"/>
|
||||
<path d="M100 45 C115 40 120 50 115 60 C110 70 100 65 95 55 C92 48 95 45 100 45Z" fill="url(#lobster-gradient)"/>
|
||||
<path d="M45 15 Q35 5 30 8" stroke="#ff4d4d" stroke-width="3" stroke-linecap="round"/>
|
||||
<path d="M75 15 Q85 5 90 8" stroke="#ff4d4d" stroke-width="3" stroke-linecap="round"/>
|
||||
<circle cx="45" cy="35" r="6" fill="#050810"/>
|
||||
<circle cx="75" cy="35" r="6" fill="#050810"/>
|
||||
<circle cx="46" cy="34" r="2.5" fill="#00e5cc"/>
|
||||
<circle cx="76" cy="34" r="2.5" fill="#00e5cc"/>
|
||||
</svg>
|
||||
</div>
|
||||
<h1>OpenClaw Setup</h1>
|
||||
<p class="muted subtitle">This wizard configures OpenClaw by running the same onboarding command it uses in the terminal, but from the browser.</p>
|
||||
|
||||
<div class="card">
|
||||
<h2>Status <span x-show="openclawVersion" x-text="openclawVersion" class="muted" style="font-size: 0.75em; font-weight: normal;"></span></h2>
|
||||
<div x-text="status">Loading...</div>
|
||||
<div x-show="configured" style="margin-top: 0.75rem">
|
||||
<a href="/openclaw" target="_blank">Open OpenClaw UI</a>
|
||||
<body class="bg-neutral-100 dark:bg-neutral-950 text-neutral-900 dark:text-neutral-100 min-h-screen font-sans" x-data="setupApp()">
|
||||
<div class="max-w-3xl mx-auto px-4 py-12 min-w-[400px]">
|
||||
<div class="flex justify-center mb-6">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/openclaw/openclaw/main/docs/assets/openclaw-logo-text-dark.png">
|
||||
<img src="https://raw.githubusercontent.com/openclaw/openclaw/main/docs/assets/openclaw-logo-text.png" alt="OpenClaw" class="max-w-[280px] h-auto">
|
||||
</picture>
|
||||
</div>
|
||||
</div>
|
||||
<h1 class="text-2xl font-semibold text-center tracking-tight mb-2">Personal AI Assistant</h1>
|
||||
<p class="text-neutral-500 text-center text-sm mb-8">Configure your AI assistant in a few simple steps</p>
|
||||
|
||||
<div class="card" x-show="isReady">
|
||||
<h2>1) Model/auth provider</h2>
|
||||
<p class="muted">Matches the groups shown in the terminal onboarding.</p>
|
||||
<label>Provider group</label>
|
||||
<select x-model="selectedGroup" @change="updateAuthChoices()">
|
||||
<template x-for="g in authGroups" :key="g.value">
|
||||
<option :value="g.value" x-text="g.label + (g.hint ? ' - ' + g.hint : '')"></option>
|
||||
</template>
|
||||
</select>
|
||||
|
||||
<label>Auth method</label>
|
||||
<select x-model="selectedAuth">
|
||||
<template x-for="o in authChoices" :key="o.value">
|
||||
<option :value="o.value" x-text="o.label + (o.hint ? ' - ' + o.hint : '')"></option>
|
||||
</template>
|
||||
</select>
|
||||
|
||||
<label>Key / Token (if required)</label>
|
||||
<input x-model="authSecret" type="password" placeholder="Paste API key / token if applicable" />
|
||||
|
||||
<label>Model</label>
|
||||
<input x-model="model" type="text" placeholder="e.g. openai/gpt-4.1, anthropic/claude-sonnet-4" />
|
||||
<div class="muted" style="margin-top: 0.25rem">
|
||||
Format: provider/model-name (e.g. openai/gpt-4.1, anthropic/claude-sonnet-4, google/gemini-2.5-pro)
|
||||
<div class="bg-white dark:bg-neutral-900 border border-neutral-200 dark:border-neutral-800 rounded-xl p-5 mb-6 shadow-sm dark:shadow-none">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<span class="text-xs uppercase tracking-wide text-neutral-500 font-medium">🦞 OpenClaw</span>
|
||||
<span x-show="openclawVersion" x-text="openclawVersion" class="text-xs text-neutral-400 dark:text-neutral-600 ml-2"></span>
|
||||
</div>
|
||||
<span x-text="status" class="text-sm" :class="configured ? 'text-emerald-600 dark:text-emerald-400' : 'text-amber-600 dark:text-amber-400'"></span>
|
||||
</div>
|
||||
<div x-show="configured" class="mt-3 pt-3 border-t border-neutral-200 dark:border-neutral-800">
|
||||
<a href="/openclaw" target="_blank" class="inline-flex items-center gap-2 text-sm text-teal-600 dark:text-teal-400 hover:text-teal-500 dark:hover:text-teal-300 transition-colors">
|
||||
Open OpenClaw UI
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"/></svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<label>Wizard flow</label>
|
||||
<select x-model="flow">
|
||||
<option value="quickstart">quickstart</option>
|
||||
<option value="advanced">advanced</option>
|
||||
<option value="manual">manual</option>
|
||||
</select>
|
||||
</div>
|
||||
<div x-show="isReady && configured" x-cloak>
|
||||
<div class="bg-white dark:bg-neutral-900 border border-neutral-200 dark:border-neutral-800 rounded-xl p-6 shadow-sm dark:shadow-none">
|
||||
<h2 class="text-lg font-semibold mb-1">Setup Complete</h2>
|
||||
<p class="text-neutral-500 text-sm mb-6">Your OpenClaw instance is configured and running</p>
|
||||
|
||||
<div class="card" x-show="isReady">
|
||||
<h2>2) Optional: Channels</h2>
|
||||
<p class="muted">You can also add channels later inside OpenClaw, but this helps you get messaging working immediately.</p>
|
||||
<div class="flex flex-wrap gap-3 mb-4">
|
||||
<button @click="openPairingModal()" :disabled="loading" class="bg-neutral-100 dark:bg-neutral-800 hover:bg-neutral-200 dark:hover:bg-neutral-700 disabled:opacity-50 disabled:cursor-not-allowed text-neutral-700 dark:text-neutral-300 px-4 py-3 rounded-lg font-medium border border-neutral-300 dark:border-neutral-700 transition-colors">
|
||||
Approve Pairing
|
||||
</button>
|
||||
<button @click="resetSetup()" :disabled="loading" class="text-red-600 dark:text-red-400 hover:bg-red-50 dark:hover:bg-red-900/20 disabled:opacity-50 disabled:cursor-not-allowed px-4 py-3 rounded-lg font-medium transition-colors">
|
||||
Reset Setup
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<label>Telegram bot token (optional)</label>
|
||||
<input x-model="telegramToken" type="password" placeholder="123456:ABC..." />
|
||||
<div class="muted" style="margin-top: 0.25rem">
|
||||
Get it from BotFather: open Telegram, message <code>@BotFather</code>, run <code>/newbot</code>, then copy the token.
|
||||
<div x-show="log" class="bg-neutral-100 dark:bg-neutral-950 border border-neutral-200 dark:border-neutral-800 rounded-lg p-4 font-mono text-xs text-neutral-600 dark:text-neutral-400 whitespace-pre-wrap max-h-64 overflow-y-auto" x-text="log"></div>
|
||||
|
||||
<p class="text-xs text-neutral-500 dark:text-neutral-600 mt-4">Reset deletes the config file so you can rerun onboarding. Pairing approval grants DM access when dmPolicy=pairing.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<label>Discord bot token (optional)</label>
|
||||
<input x-model="discordToken" type="password" placeholder="Bot token" />
|
||||
<div class="muted" style="margin-top: 0.25rem">
|
||||
Get it from the Discord Developer Portal: create an application, add a Bot, then copy the Bot Token.<br/>
|
||||
<strong>Important:</strong> Enable <strong>MESSAGE CONTENT INTENT</strong> in Bot → Privileged Gateway Intents, or the bot will crash on startup.
|
||||
<div x-show="isReady && !configured" x-cloak>
|
||||
<div class="flex items-center justify-center gap-2 mb-8">
|
||||
<template x-for="s in totalSteps" :key="s">
|
||||
<div class="flex items-center">
|
||||
<div
|
||||
class="w-8 h-8 rounded-full flex items-center justify-center text-sm font-medium transition-all cursor-pointer"
|
||||
:class="step === s ? 'bg-red-600 text-white' : (step > s ? 'bg-emerald-600 text-white' : 'bg-neutral-200 dark:bg-neutral-800 text-neutral-500')"
|
||||
@click="step = s"
|
||||
x-text="s"
|
||||
></div>
|
||||
<div x-show="s < totalSteps" class="w-12 h-0.5 mx-1" :class="step > s ? 'bg-emerald-600' : 'bg-neutral-200 dark:bg-neutral-800'"></div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div x-show="step === 1" x-transition:enter="transition ease-out duration-200" x-transition:enter-start="opacity-0 translate-x-4" x-transition:enter-end="opacity-100 translate-x-0">
|
||||
<div class="bg-white dark:bg-neutral-900 border border-neutral-200 dark:border-neutral-800 rounded-xl p-6 shadow-sm dark:shadow-none">
|
||||
<h2 class="text-lg font-semibold mb-1">Model & Authentication</h2>
|
||||
<p class="text-neutral-500 text-sm mb-6">Choose your AI provider and authentication method</p>
|
||||
|
||||
<div class="space-y-5">
|
||||
<div>
|
||||
<label class="block text-xs uppercase tracking-wide text-neutral-500 font-medium mb-2">Provider Group</label>
|
||||
<select x-model="selectedGroup" @change="updateAuthChoices()" class="w-full bg-neutral-50 dark:bg-neutral-800 border border-neutral-300 dark:border-neutral-700 rounded-lg px-4 py-3 text-sm focus:outline-none focus:border-red-500 transition-colors">
|
||||
<template x-for="g in authGroups" :key="g.value">
|
||||
<option :value="g.value" x-text="g.label + (g.hint ? ' — ' + g.hint : '')"></option>
|
||||
</template>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-xs uppercase tracking-wide text-neutral-500 font-medium mb-2">Auth Method</label>
|
||||
<select x-model="selectedAuth" class="w-full bg-neutral-50 dark:bg-neutral-800 border border-neutral-300 dark:border-neutral-700 rounded-lg px-4 py-3 text-sm focus:outline-none focus:border-red-500 transition-colors">
|
||||
<template x-for="o in authChoices" :key="o.value">
|
||||
<option :value="o.value" x-text="o.label + (o.hint ? ' — ' + o.hint : '')"></option>
|
||||
</template>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-xs uppercase tracking-wide text-neutral-500 font-medium mb-2">API Key / Token</label>
|
||||
<input x-model="authSecret" type="password" placeholder="Paste your API key or token" class="w-full bg-neutral-50 dark:bg-neutral-800 border border-neutral-300 dark:border-neutral-700 rounded-lg px-4 py-3 text-sm placeholder-neutral-400 dark:placeholder-neutral-600 focus:outline-none focus:border-red-500 transition-colors" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-xs uppercase tracking-wide text-neutral-500 font-medium mb-2">Model</label>
|
||||
<input x-model="model" type="text" placeholder="e.g. openai/gpt-4.1, anthropic/claude-sonnet-4" class="w-full bg-neutral-50 dark:bg-neutral-800 border border-neutral-300 dark:border-neutral-700 rounded-lg px-4 py-3 text-sm placeholder-neutral-400 dark:placeholder-neutral-600 focus:outline-none focus:border-red-500 transition-colors" />
|
||||
<p class="text-xs text-neutral-500 dark:text-neutral-600 mt-2">Format: provider/model-name</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-xs uppercase tracking-wide text-neutral-500 font-medium mb-2">Wizard Flow</label>
|
||||
<select x-model="flow" class="w-full bg-neutral-50 dark:bg-neutral-800 border border-neutral-300 dark:border-neutral-700 rounded-lg px-4 py-3 text-sm focus:outline-none focus:border-red-500 transition-colors">
|
||||
<option value="quickstart">Quickstart</option>
|
||||
<option value="advanced">Advanced</option>
|
||||
<option value="manual">Manual</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div x-show="step === 2" x-transition:enter="transition ease-out duration-200" x-transition:enter-start="opacity-0 translate-x-4" x-transition:enter-end="opacity-100 translate-x-0">
|
||||
<div class="bg-white dark:bg-neutral-900 border border-neutral-200 dark:border-neutral-800 rounded-xl p-6 shadow-sm dark:shadow-none">
|
||||
<h2 class="text-lg font-semibold mb-1">Channels</h2>
|
||||
<p class="text-neutral-500 text-sm mb-6">Connect messaging platforms (optional — can be added later)</p>
|
||||
|
||||
<div class="space-y-5">
|
||||
<div class="bg-sky-50 dark:bg-neutral-800/50 rounded-lg p-4 border border-sky-200 dark:border-neutral-800">
|
||||
<div class="flex items-center gap-3 mb-3">
|
||||
<div class="w-8 h-8 bg-sky-500/20 rounded-lg flex items-center justify-center">
|
||||
<svg class="w-5 h-5 text-sky-500 dark:text-sky-400" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm4.64 6.8c-.15 1.58-.8 5.42-1.13 7.19-.14.75-.42 1-.68 1.03-.58.05-1.02-.38-1.58-.75-.88-.58-1.38-.94-2.23-1.5-.99-.65-.35-1.01.22-1.59.15-.15 2.71-2.48 2.76-2.69a.2.2 0 00-.05-.18c-.06-.05-.14-.03-.21-.02-.09.02-1.49.95-4.22 2.79-.4.27-.76.41-1.08.4-.36-.01-1.04-.2-1.55-.37-.63-.2-1.12-.31-1.08-.66.02-.18.27-.36.74-.55 2.92-1.27 4.86-2.11 5.83-2.51 2.78-1.16 3.35-1.36 3.73-1.36.08 0 .27.02.39.12.1.08.13.19.14.27-.01.06.01.24 0 .38z"/></svg>
|
||||
</div>
|
||||
<span class="font-medium">Telegram</span>
|
||||
</div>
|
||||
<input x-model="telegramToken" type="password" placeholder="Bot token from @BotFather" class="w-full bg-white dark:bg-neutral-900 border border-neutral-300 dark:border-neutral-700 rounded-lg px-4 py-3 text-sm placeholder-neutral-400 dark:placeholder-neutral-600 focus:outline-none focus:border-sky-500 transition-colors" />
|
||||
<p class="text-xs text-neutral-500 dark:text-neutral-600 mt-2">Message <code class="bg-neutral-200 dark:bg-neutral-800 px-1.5 py-0.5 rounded text-sky-600 dark:text-sky-400">@BotFather</code> on Telegram and run <code class="bg-neutral-200 dark:bg-neutral-800 px-1.5 py-0.5 rounded text-sky-600 dark:text-sky-400">/newbot</code></p>
|
||||
</div>
|
||||
|
||||
<div class="bg-indigo-50 dark:bg-neutral-800/50 rounded-lg p-4 border border-indigo-200 dark:border-neutral-800">
|
||||
<div class="flex items-center gap-3 mb-3">
|
||||
<div class="w-8 h-8 bg-indigo-500/20 rounded-lg flex items-center justify-center">
|
||||
<svg class="w-5 h-5 text-indigo-500 dark:text-indigo-400" viewBox="0 0 24 24" fill="currentColor"><path d="M20.317 4.37a19.791 19.791 0 00-4.885-1.515.074.074 0 00-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 00-5.487 0 12.64 12.64 0 00-.617-1.25.077.077 0 00-.079-.037A19.736 19.736 0 003.677 4.37a.07.07 0 00-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 00.031.057 19.9 19.9 0 005.993 3.03.078.078 0 00.084-.028c.462-.63.874-1.295 1.226-1.994a.076.076 0 00-.041-.106 13.107 13.107 0 01-1.872-.892.077.077 0 01-.008-.128 10.2 10.2 0 00.372-.292.074.074 0 01.077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 01.078.01c.12.098.246.198.373.292a.077.077 0 01-.006.127 12.299 12.299 0 01-1.873.892.077.077 0 00-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 00.084.028 19.839 19.839 0 006.002-3.03.077.077 0 00.032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 00-.031-.03zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.956-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.955-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.946 2.418-2.157 2.418z"/></svg>
|
||||
</div>
|
||||
<span class="font-medium">Discord</span>
|
||||
</div>
|
||||
<input x-model="discordToken" type="password" placeholder="Bot token from Developer Portal" class="w-full bg-white dark:bg-neutral-900 border border-neutral-300 dark:border-neutral-700 rounded-lg px-4 py-3 text-sm placeholder-neutral-400 dark:placeholder-neutral-600 focus:outline-none focus:border-indigo-500 transition-colors" />
|
||||
<p class="text-xs text-neutral-500 dark:text-neutral-600 mt-2">Enable <strong class="text-amber-600 dark:text-amber-400">MESSAGE CONTENT INTENT</strong> in Bot → Privileged Gateway Intents</p>
|
||||
</div>
|
||||
|
||||
<div class="bg-green-50 dark:bg-neutral-800/50 rounded-lg p-4 border border-green-200 dark:border-neutral-800">
|
||||
<div class="flex items-center gap-3 mb-3">
|
||||
<div class="w-8 h-8 bg-green-500/20 rounded-lg flex items-center justify-center">
|
||||
<svg class="w-5 h-5 text-green-600 dark:text-green-400" viewBox="0 0 24 24" fill="currentColor"><path d="M5.042 15.165a2.528 2.528 0 01-2.52 2.523A2.528 2.528 0 010 15.165a2.527 2.527 0 012.522-2.52h2.52v2.52zm1.271 0a2.527 2.527 0 012.521-2.52 2.527 2.527 0 012.521 2.52v6.313A2.528 2.528 0 018.834 24a2.528 2.528 0 01-2.521-2.522v-6.313zM8.834 5.042a2.528 2.528 0 01-2.521-2.52A2.528 2.528 0 018.834 0a2.528 2.528 0 012.521 2.522v2.52H8.834zm0 1.271a2.528 2.528 0 012.521 2.521 2.528 2.528 0 01-2.521 2.521H2.522A2.528 2.528 0 010 8.834a2.528 2.528 0 012.522-2.521h6.312zm10.124 2.521a2.528 2.528 0 012.522-2.521A2.528 2.528 0 0124 8.834a2.528 2.528 0 01-2.52 2.521h-2.522V8.834zm-1.271 0a2.528 2.528 0 01-2.521 2.521 2.528 2.528 0 01-2.521-2.521V2.522A2.528 2.528 0 0115.166 0a2.528 2.528 0 012.521 2.522v6.312zm-2.521 10.124a2.528 2.528 0 012.521 2.522A2.528 2.528 0 0115.166 24a2.528 2.528 0 01-2.521-2.522v-2.522h2.521zm0-1.271a2.528 2.528 0 01-2.521-2.521 2.528 2.528 0 012.521-2.521h6.312A2.528 2.528 0 0124 15.165a2.528 2.528 0 01-2.52 2.521h-6.313z"/></svg>
|
||||
</div>
|
||||
<span class="font-medium">Slack</span>
|
||||
</div>
|
||||
<div class="space-y-3">
|
||||
<input x-model="slackBotToken" type="password" placeholder="Bot token (xoxb-...)" class="w-full bg-white dark:bg-neutral-900 border border-neutral-300 dark:border-neutral-700 rounded-lg px-4 py-3 text-sm placeholder-neutral-400 dark:placeholder-neutral-600 focus:outline-none focus:border-green-500 transition-colors" />
|
||||
<input x-model="slackAppToken" type="password" placeholder="App token (xapp-...)" class="w-full bg-white dark:bg-neutral-900 border border-neutral-300 dark:border-neutral-700 rounded-lg px-4 py-3 text-sm placeholder-neutral-400 dark:placeholder-neutral-600 focus:outline-none focus:border-green-500 transition-colors" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div x-show="step === 3" x-transition:enter="transition ease-out duration-200" x-transition:enter-start="opacity-0 translate-x-4" x-transition:enter-end="opacity-100 translate-x-0">
|
||||
<div class="bg-white dark:bg-neutral-900 border border-neutral-200 dark:border-neutral-800 rounded-xl p-6 shadow-sm dark:shadow-none">
|
||||
<h2 class="text-lg font-semibold mb-1">Run Setup</h2>
|
||||
<p class="text-neutral-500 text-sm mb-6">Review your configuration and start OpenClaw</p>
|
||||
|
||||
<div class="bg-neutral-50 dark:bg-neutral-800/50 rounded-lg p-4 border border-neutral-200 dark:border-neutral-800 mb-6">
|
||||
<h3 class="text-sm font-medium text-neutral-600 dark:text-neutral-400 mb-3">Configuration Summary</h3>
|
||||
<dl class="space-y-2 text-sm">
|
||||
<div class="flex justify-between">
|
||||
<dt class="text-neutral-500">Provider</dt>
|
||||
<dd x-text="selectedGroup || '—'" class="text-neutral-700 dark:text-neutral-300"></dd>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<dt class="text-neutral-500">Auth</dt>
|
||||
<dd x-text="selectedAuth || '—'" class="text-neutral-700 dark:text-neutral-300"></dd>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<dt class="text-neutral-500">Model</dt>
|
||||
<dd x-text="model || 'Default'" class="text-neutral-700 dark:text-neutral-300"></dd>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<dt class="text-neutral-500">Channels</dt>
|
||||
<dd class="text-neutral-700 dark:text-neutral-300">
|
||||
<span x-show="telegramToken" class="inline-flex items-center gap-1 bg-sky-100 dark:bg-sky-500/20 text-sky-600 dark:text-sky-400 px-2 py-0.5 rounded text-xs mr-1">Telegram</span>
|
||||
<span x-show="discordToken" class="inline-flex items-center gap-1 bg-indigo-100 dark:bg-indigo-500/20 text-indigo-600 dark:text-indigo-400 px-2 py-0.5 rounded text-xs mr-1">Discord</span>
|
||||
<span x-show="slackBotToken && slackAppToken" class="inline-flex items-center gap-1 bg-green-100 dark:bg-green-500/20 text-green-600 dark:text-green-400 px-2 py-0.5 rounded text-xs">Slack</span>
|
||||
<span x-show="!telegramToken && !discordToken && !(slackBotToken && slackAppToken)" class="text-neutral-500">None</span>
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap gap-3 mb-4">
|
||||
<button @click="runSetup()" :disabled="loading" class="bg-red-600 hover:bg-red-700 disabled:opacity-50 disabled:cursor-not-allowed text-white px-6 py-3 rounded-lg font-medium transition-colors">
|
||||
<span x-show="!loading">Run Setup</span>
|
||||
<span x-show="loading" class="flex items-center gap-2">
|
||||
<svg class="animate-spin w-4 h-4" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"/><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"/></svg>
|
||||
Running...
|
||||
</span>
|
||||
</button>
|
||||
<button @click="openPairingModal()" :disabled="loading" class="bg-neutral-100 dark:bg-neutral-800 hover:bg-neutral-200 dark:hover:bg-neutral-700 disabled:opacity-50 disabled:cursor-not-allowed text-neutral-700 dark:text-neutral-300 px-4 py-3 rounded-lg font-medium border border-neutral-300 dark:border-neutral-700 transition-colors">
|
||||
Approve Pairing
|
||||
</button>
|
||||
<button @click="resetSetup()" :disabled="loading" class="text-neutral-500 hover:text-neutral-700 dark:hover:text-neutral-300 hover:bg-neutral-100 dark:hover:bg-neutral-800 disabled:opacity-50 disabled:cursor-not-allowed px-4 py-3 rounded-lg font-medium transition-colors">
|
||||
Reset
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div x-show="log" class="bg-neutral-100 dark:bg-neutral-950 border border-neutral-200 dark:border-neutral-800 rounded-lg p-4 font-mono text-xs text-neutral-600 dark:text-neutral-400 whitespace-pre-wrap max-h-64 overflow-y-auto" x-text="log"></div>
|
||||
|
||||
<p class="text-xs text-neutral-500 dark:text-neutral-600 mt-4">Reset deletes the config file so you can rerun onboarding. Pairing approval grants DM access when dmPolicy=pairing.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-between mt-6">
|
||||
<button
|
||||
x-show="step > 1"
|
||||
@click="prevStep()"
|
||||
class="flex items-center gap-2 text-neutral-500 hover:text-neutral-700 dark:text-neutral-400 dark:hover:text-neutral-200 transition-colors"
|
||||
>
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/></svg>
|
||||
Back
|
||||
</button>
|
||||
<div x-show="step === 1"></div>
|
||||
<button
|
||||
x-show="step < totalSteps"
|
||||
@click="nextStep()"
|
||||
class="flex items-center gap-2 bg-neutral-200 dark:bg-neutral-800 hover:bg-neutral-300 dark:hover:bg-neutral-700 text-neutral-800 dark:text-neutral-200 px-5 py-2.5 rounded-lg font-medium transition-colors ml-auto"
|
||||
>
|
||||
Next
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<label>Slack bot token (optional)</label>
|
||||
<input x-model="slackBotToken" type="password" placeholder="xoxb-..." />
|
||||
|
||||
<label>Slack app token (optional)</label>
|
||||
<input x-model="slackAppToken" type="password" placeholder="xapp-..." />
|
||||
</div>
|
||||
|
||||
<div class="card" x-show="isReady">
|
||||
<h2>3) Run onboarding</h2>
|
||||
<button @click="runSetup()" :disabled="loading" class="primary-button">Run setup</button>
|
||||
<button @click="openPairingModal()" class="secondary-button" :disabled="loading">Approve pairing</button>
|
||||
<button @click="resetSetup()" class="tertiary-button" :disabled="loading">Reset setup</button>
|
||||
<pre x-text="log"></pre>
|
||||
<p class="muted">Reset deletes the OpenClaw config file so you can rerun onboarding. Pairing approval lets you grant DM access when dmPolicy=pairing.</p>
|
||||
</div>
|
||||
|
||||
<div x-show="showPairingModal" x-cloak class="modal-overlay" @click.self="showPairingModal = false">
|
||||
<div class="modal">
|
||||
<h3>Approve Pairing</h3>
|
||||
<label>Channel</label>
|
||||
<select x-model="pairingChannel">
|
||||
<option value="">Select channel...</option>
|
||||
<option value="telegram">telegram</option>
|
||||
<option value="discord">discord</option>
|
||||
</select>
|
||||
<label>Pairing code</label>
|
||||
<input x-model="pairingCode" type="text" placeholder="e.g. 3EY4PUYS" />
|
||||
<div style="margin-top: 1rem; display: flex; gap: 0.5rem;">
|
||||
<button @click="approvePairing()" class="primary-button">Approve</button>
|
||||
<button @click="showPairingModal = false" class="secondary-button">Cancel</button>
|
||||
<div x-show="showPairingModal" x-cloak class="fixed inset-0 bg-black/50 dark:bg-black/70 flex items-center justify-center z-50" @click.self="showPairingModal = false">
|
||||
<div class="bg-white dark:bg-neutral-900 border border-neutral-200 dark:border-neutral-800 rounded-xl p-6 w-full max-w-sm mx-4 shadow-xl dark:shadow-none">
|
||||
<h3 class="text-lg font-semibold mb-4">Approve Pairing</h3>
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<label class="block text-xs uppercase tracking-wide text-neutral-500 font-medium mb-2">Channel</label>
|
||||
<select x-model="pairingChannel" class="w-full bg-neutral-50 dark:bg-neutral-800 border border-neutral-300 dark:border-neutral-700 rounded-lg px-4 py-3 text-sm focus:outline-none focus:border-red-500 transition-colors">
|
||||
<option value="">Select channel...</option>
|
||||
<option value="telegram">Telegram</option>
|
||||
<option value="discord">Discord</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs uppercase tracking-wide text-neutral-500 font-medium mb-2">Pairing Code</label>
|
||||
<input x-model="pairingCode" type="text" placeholder="e.g. 3EY4PUYS" class="w-full bg-neutral-50 dark:bg-neutral-800 border border-neutral-300 dark:border-neutral-700 rounded-lg px-4 py-3 text-sm placeholder-neutral-400 dark:placeholder-neutral-600 focus:outline-none focus:border-red-500 transition-colors" />
|
||||
</div>
|
||||
<div class="flex gap-3 pt-2">
|
||||
<button @click="approvePairing()" class="flex-1 bg-red-600 hover:bg-red-700 text-white px-4 py-3 rounded-lg font-medium transition-colors">Approve</button>
|
||||
<button @click="showPairingModal = false" class="flex-1 bg-neutral-100 dark:bg-neutral-800 hover:bg-neutral-200 dark:hover:bg-neutral-700 text-neutral-700 dark:text-neutral-300 px-4 py-3 rounded-lg font-medium border border-neutral-300 dark:border-neutral-700 transition-colors">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
[x-cloak] { display: none !important; }
|
||||
.modal-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: var(--overlay-bg);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 1000;
|
||||
}
|
||||
.modal {
|
||||
background: var(--modal-bg);
|
||||
padding: 1.5rem;
|
||||
border-radius: 8px;
|
||||
min-width: 300px;
|
||||
border: 1px solid var(--modal-border);
|
||||
}
|
||||
.modal h3 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
</style>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,228 +0,0 @@
|
||||
@import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&display=swap');
|
||||
|
||||
:root {
|
||||
--bg-primary: #0f0f0f;
|
||||
--bg-card: #1a1a1a;
|
||||
--bg-input: #262626;
|
||||
--bg-code: #262626;
|
||||
--bg-pre: #1a1a1a;
|
||||
--border-color: #2a2a2a;
|
||||
--border-input: #3a3a3a;
|
||||
--text-primary: #fafafa;
|
||||
--text-muted: #737373;
|
||||
--text-code: #00e5cc;
|
||||
--accent: #ef4444;
|
||||
--accent-hover: #dc2626;
|
||||
--secondary-bg: #262626;
|
||||
--secondary-hover: #333333;
|
||||
--tertiary-bg: #333333;
|
||||
--tertiary-hover: #404040;
|
||||
--modal-bg: #1a1a1a;
|
||||
--modal-border: #2a2a2a;
|
||||
--overlay-bg: rgba(0, 0, 0, 0.7);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
:root {
|
||||
--bg-primary: #fafafa;
|
||||
--bg-card: #ffffff;
|
||||
--bg-input: #f5f5f5;
|
||||
--bg-code: #f0f0f0;
|
||||
--bg-pre: #f5f5f5;
|
||||
--border-color: #e5e5e5;
|
||||
--border-input: #d4d4d4;
|
||||
--text-primary: #171717;
|
||||
--text-muted: #737373;
|
||||
--text-code: #0d9488;
|
||||
--accent: #dc2626;
|
||||
--accent-hover: #b91c1c;
|
||||
--secondary-bg: #f0f0f0;
|
||||
--secondary-hover: #e5e5e5;
|
||||
--tertiary-bg: #e5e5e5;
|
||||
--tertiary-hover: #d4d4d4;
|
||||
--modal-bg: #ffffff;
|
||||
--modal-border: #e5e5e5;
|
||||
--overlay-bg: rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Space Grotesk', ui-sans-serif, system-ui, -apple-system, sans-serif;
|
||||
margin: 2rem auto;
|
||||
max-width: 900px;
|
||||
padding: 0 1rem;
|
||||
background: var(--bg-primary);
|
||||
color: var(--text-primary);
|
||||
transition: background 0.2s, color 0.2s;
|
||||
}
|
||||
|
||||
.logo {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.logo svg {
|
||||
width: 72px;
|
||||
height: 72px;
|
||||
}
|
||||
|
||||
.card {
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 12px;
|
||||
padding: 1.5rem;
|
||||
margin: 1rem 0;
|
||||
background: var(--bg-card);
|
||||
transition: background 0.2s, border-color 0.2s;
|
||||
}
|
||||
|
||||
.card > *:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin-top: 1rem;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
input,
|
||||
select {
|
||||
width: 100%;
|
||||
padding: 0.75rem 1rem;
|
||||
margin-top: 0.5rem;
|
||||
background: var(--bg-input);
|
||||
border: 1px solid var(--border-input);
|
||||
border-radius: 8px;
|
||||
color: var(--text-primary);
|
||||
font-family: 'Space Grotesk', ui-sans-serif, system-ui, -apple-system, sans-serif;
|
||||
font-size: 0.9375rem;
|
||||
box-sizing: border-box;
|
||||
transition: background 0.2s, border-color 0.2s, color 0.2s;
|
||||
}
|
||||
|
||||
input:focus,
|
||||
select:focus {
|
||||
outline: none;
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
input::placeholder {
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 0.75rem 1.5rem;
|
||||
border-radius: 8px;
|
||||
border: 0;
|
||||
background: var(--secondary-bg);
|
||||
color: var(--text-primary);
|
||||
font-family: 'Space Grotesk', ui-sans-serif, system-ui, -apple-system, sans-serif;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background: var(--secondary-hover);
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.primary-button {
|
||||
background: var(--accent);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.primary-button:hover {
|
||||
background: var(--accent-hover);
|
||||
}
|
||||
|
||||
.secondary-button {
|
||||
background: var(--secondary-bg);
|
||||
color: var(--text-primary);
|
||||
margin-left: 0.5rem;
|
||||
border: 1px solid var(--border-input);
|
||||
}
|
||||
|
||||
.secondary-button:hover {
|
||||
background: var(--secondary-hover);
|
||||
}
|
||||
|
||||
.tertiary-button {
|
||||
background: transparent;
|
||||
color: var(--text-muted);
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
||||
.tertiary-button:hover {
|
||||
background: var(--tertiary-bg);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
code {
|
||||
background: var(--bg-code);
|
||||
color: var(--text-code);
|
||||
padding: 0.15rem 0.4rem;
|
||||
border-radius: 4px;
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
||||
font-size: 0.875em;
|
||||
}
|
||||
|
||||
pre {
|
||||
white-space: pre-wrap;
|
||||
background: var(--bg-pre);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
padding: 1rem;
|
||||
margin-top: 1rem;
|
||||
color: var(--text-muted);
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
||||
font-size: 0.8125rem;
|
||||
line-height: 1.6;
|
||||
overflow-x: auto;
|
||||
transition: background 0.2s, border-color 0.2s, color 0.2s;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--text-code);
|
||||
text-decoration: none;
|
||||
transition: color 0.2s;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.muted {
|
||||
color: var(--text-muted);
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
h1, h2, h3 {
|
||||
color: var(--text-primary);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
h1 {
|
||||
text-align: center;
|
||||
font-size: 1.75rem;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1.125rem;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
text-align: center;
|
||||
}
|
||||
Reference in New Issue
Block a user