Files
vasyansk 2ee2aaaffb
Build / Build & push landing (push) Successful in 14s
Build / Deploy to prod (push) Successful in 7s
Build / Notify Max (push) Successful in 2s
Update version to 0.1.10
Add deepseek to resume commands

Rename app to spaceshell

Add SurfacePicker component for preset panel configuration

Extract agent selection logic to shared agents.ts

Update landing
2026-06-15 17:25:53 +07:00

1189 lines
33 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>spaceshell — терминал-воркспейс для AI-агентов на macOS</title>
<meta name="description" content="Запускай Claude Code, Codex, Gemini и shell параллельно. Фоновый демон держит сессии живыми: закрыл окно — агенты работают. Скачать для macOS.">
<link rel="canonical" href="https://spaceshell.ru">
<meta property="og:title" content="spaceshell — терминал-воркспейс для AI-агентов">
<meta property="og:description" content="Десяток AI-агентов параллельно. Демон держит сессии живыми — закрой окно, агенты работают.">
<meta property="og:url" content="https://spaceshell.ru">
<meta property="og:image" content="https://spaceshell.ru/og.png">
<meta property="og:type" content="website">
<meta name="theme-color" content="#0A0D12">
<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&family=JetBrains+Mono:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
:root {
--bg-base: #0A0D12;
--bg-panel: #0E1116;
--bg-elevated: #11161F;
--border-subtle: #232A33;
--border-strong: #323C49;
--text-primary: #E6EDF3;
--text-secondary: #8B97A6;
--text-muted: #5A6573;
--accent-primary: #34D3C2;
--accent-secondary: #4F9CF9;
--status-work: #4C8DFF;
--status-wait: #F2B84B;
--status-done: #3FB950;
--status-error: #F4544E;
--font-mono: 'JetBrains Mono', ui-monospace, monospace;
--font-body: 'Inter', -apple-system, system-ui, sans-serif;
--radius-sm: 8px;
--radius-md: 12px;
--radius-lg: 14px;
}
html { scroll-behavior: smooth; }
body {
font-family: var(--font-body);
background: var(--bg-base);
color: var(--text-primary);
line-height: 1.6;
-webkit-font-smoothing: antialiased;
}
/* Scanlines texture */
body::before {
content: '';
position: fixed;
inset: 0;
background: repeating-linear-gradient(
0deg,
transparent,
transparent 2px,
rgba(255,255,255,0.008) 2px,
rgba(255,255,255,0.008) 4px
);
pointer-events: none;
z-index: 9999;
}
a { color: inherit; text-decoration: none; }
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 24px;
}
/* Header */
.header {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 100;
padding: 16px 0;
transition: background 0.3s, backdrop-filter 0.3s;
}
.header.scrolled {
background: rgba(10, 13, 18, 0.85);
backdrop-filter: blur(12px);
border-bottom: 1px solid var(--border-subtle);
}
.header-inner {
display: flex;
align-items: center;
justify-content: space-between;
gap: 32px;
}
.logo {
font-family: var(--font-mono);
font-size: 20px;
font-weight: 600;
color: var(--text-primary);
display: flex;
align-items: center;
gap: 8px;
}
.logo-icon {
width: 28px;
height: 28px;
background: var(--bg-elevated);
border: 1px solid var(--border-strong);
border-radius: 6px;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
color: var(--accent-primary);
}
.nav {
display: flex;
align-items: center;
gap: 32px;
}
.nav a {
font-size: 14px;
color: var(--text-secondary);
transition: color 0.2s;
}
.nav a:hover { color: var(--text-primary); }
.btn {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 10px 20px;
border-radius: var(--radius-sm);
font-size: 14px;
font-weight: 500;
cursor: pointer;
border: none;
transition: all 0.2s;
}
.btn-primary {
background: var(--accent-primary);
color: var(--bg-base);
}
.btn-primary:hover {
background: #45e0d0;
box-shadow: 0 0 24px rgba(52, 211, 194, 0.3);
}
.btn-secondary {
background: transparent;
color: var(--text-secondary);
border: 1px solid var(--border-strong);
}
.btn-secondary:hover {
border-color: var(--text-secondary);
color: var(--text-primary);
}
.btn-large {
padding: 14px 28px;
font-size: 16px;
}
/* Hero */
.hero {
min-height: 100vh;
padding: 140px 0 80px;
display: flex;
align-items: center;
}
.hero-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 64px;
align-items: center;
}
.hero-eyebrow {
font-family: var(--font-mono);
font-size: 13px;
color: var(--text-muted);
text-transform: uppercase;
letter-spacing: 0.05em;
margin-bottom: 16px;
}
.hero-title {
font-family: var(--font-mono);
font-size: clamp(32px, 4vw, 48px);
font-weight: 700;
line-height: 1.2;
margin-bottom: 24px;
}
.hero-title .accent { color: var(--accent-primary); }
.hero-subtitle {
font-size: 18px;
color: var(--text-secondary);
line-height: 1.7;
margin-bottom: 32px;
max-width: 520px;
}
.hero-buttons {
display: flex;
gap: 16px;
margin-bottom: 20px;
}
.hero-meta {
font-size: 13px;
color: var(--text-muted);
}
.hero-meta span {
margin: 0 8px;
opacity: 0.5;
}
/* Terminal mockup */
.terminal-mockup {
background: var(--bg-panel);
border: 1px solid var(--border-subtle);
border-radius: var(--radius-lg);
overflow: hidden;
position: relative;
}
.terminal-header {
background: var(--bg-elevated);
padding: 12px 16px;
display: flex;
align-items: center;
gap: 8px;
border-bottom: 1px solid var(--border-subtle);
}
.terminal-dots {
display: flex;
gap: 6px;
}
.terminal-dot {
width: 12px;
height: 12px;
border-radius: 50%;
background: var(--border-strong);
}
.terminal-title {
flex: 1;
text-align: center;
font-family: var(--font-mono);
font-size: 12px;
color: var(--text-muted);
}
.terminal-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1px;
background: var(--border-subtle);
}
.terminal-pane {
background: var(--bg-base);
padding: 16px;
min-height: 140px;
position: relative;
}
.pane-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12px;
}
.pane-name {
font-family: var(--font-mono);
font-size: 11px;
color: var(--text-muted);
display: flex;
align-items: center;
gap: 8px;
}
.status-ring {
width: 8px;
height: 8px;
border-radius: 50%;
animation: pulse 2s infinite;
}
.status-work { background: var(--status-work); box-shadow: 0 0 8px var(--status-work); }
.status-wait { background: var(--status-wait); box-shadow: 0 0 8px var(--status-wait); }
.status-done { background: var(--status-done); box-shadow: 0 0 8px var(--status-done); }
.status-idle { background: var(--text-muted); }
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
.pane-content {
font-family: var(--font-mono);
font-size: 11px;
color: var(--text-secondary);
line-height: 1.6;
}
.pane-content .typed-line {
overflow: hidden;
white-space: nowrap;
}
.cursor {
display: inline-block;
width: 8px;
height: 14px;
background: var(--accent-primary);
animation: blink 1s step-end infinite;
vertical-align: text-bottom;
margin-left: 2px;
}
@keyframes blink {
0%, 50% { opacity: 1; }
51%, 100% { opacity: 0; }
}
.daemon-badge {
position: absolute;
bottom: 12px;
right: 12px;
background: var(--bg-elevated);
border: 1px solid var(--status-done);
border-radius: 4px;
padding: 4px 8px;
font-family: var(--font-mono);
font-size: 10px;
color: var(--status-done);
display: flex;
align-items: center;
gap: 6px;
}
.daemon-dot {
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--status-done);
animation: pulse 1.5s infinite;
}
/* Agents strip */
.agents-strip {
padding: 48px 0;
border-top: 1px solid var(--border-subtle);
border-bottom: 1px solid var(--border-subtle);
}
.agents-inner {
display: flex;
align-items: center;
justify-content: center;
gap: 12px;
flex-wrap: wrap;
}
.agents-label {
font-size: 14px;
color: var(--text-muted);
}
.agent-tag {
font-family: var(--font-mono);
font-size: 13px;
color: var(--text-secondary);
background: var(--bg-panel);
border: 1px solid var(--border-subtle);
padding: 8px 14px;
border-radius: var(--radius-sm);
}
/* Problem/Solution */
.problem-section {
padding: 100px 0;
}
.problem-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 48px;
}
.problem-card, .solution-card {
padding: 32px;
border-radius: var(--radius-md);
}
.problem-card {
background: var(--bg-panel);
border: 1px solid var(--border-subtle);
}
.solution-card {
background: linear-gradient(135deg, rgba(52, 211, 194, 0.08), rgba(79, 156, 249, 0.08));
border: 1px solid rgba(52, 211, 194, 0.2);
}
.card-label {
font-family: var(--font-mono);
font-size: 12px;
text-transform: uppercase;
letter-spacing: 0.05em;
margin-bottom: 16px;
}
.problem-card .card-label { color: var(--status-error); }
.solution-card .card-label { color: var(--accent-primary); }
.card-title {
font-size: 20px;
font-weight: 600;
margin-bottom: 12px;
}
.card-text {
color: var(--text-secondary);
line-height: 1.7;
}
/* Features */
.features-section {
padding: 100px 0;
}
.section-header {
text-align: center;
margin-bottom: 64px;
}
.section-title {
font-family: var(--font-mono);
font-size: clamp(28px, 3vw, 36px);
font-weight: 600;
margin-bottom: 16px;
}
.section-subtitle {
font-size: 18px;
color: var(--text-secondary);
max-width: 600px;
margin: 0 auto;
}
.features-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 24px;
}
.feature-card {
background: var(--bg-panel);
border: 1px solid var(--border-subtle);
border-radius: var(--radius-md);
padding: 28px;
transition: all 0.3s;
}
.feature-card:hover {
border-color: var(--border-strong);
transform: translateY(-2px);
box-shadow: 0 0 32px rgba(52, 211, 194, 0.08);
}
.feature-icon {
font-family: var(--font-mono);
font-size: 12px;
color: var(--accent-primary);
background: rgba(52, 211, 194, 0.1);
padding: 6px 10px;
border-radius: 4px;
display: inline-block;
margin-bottom: 16px;
}
.feature-title {
font-family: var(--font-mono);
font-size: 16px;
font-weight: 600;
margin-bottom: 12px;
}
.feature-text {
font-size: 14px;
color: var(--text-secondary);
line-height: 1.7;
}
/* How it works */
.how-section {
padding: 100px 0;
background: var(--bg-panel);
border-top: 1px solid var(--border-subtle);
border-bottom: 1px solid var(--border-subtle);
}
.steps-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 48px;
}
.step {
text-align: center;
}
.step-number {
font-family: var(--font-mono);
font-size: 48px;
font-weight: 700;
color: var(--border-strong);
margin-bottom: 24px;
}
.step-title {
font-family: var(--font-mono);
font-size: 18px;
font-weight: 600;
margin-bottom: 12px;
}
.step-text {
font-size: 14px;
color: var(--text-secondary);
line-height: 1.7;
}
.step-diagram {
margin-top: 16px;
font-family: var(--font-mono);
font-size: 11px;
color: var(--text-muted);
background: var(--bg-base);
padding: 12px;
border-radius: var(--radius-sm);
border: 1px solid var(--border-subtle);
}
/* Roadmap */
.roadmap-section {
padding: 80px 0;
}
.roadmap-inner {
background: var(--bg-panel);
border: 1px solid var(--border-subtle);
border-radius: var(--radius-md);
padding: 32px;
}
.roadmap-label {
font-family: var(--font-mono);
font-size: 12px;
color: var(--status-wait);
text-transform: uppercase;
letter-spacing: 0.05em;
margin-bottom: 16px;
}
.roadmap-title {
font-size: 20px;
font-weight: 600;
margin-bottom: 20px;
}
.roadmap-items {
display: flex;
gap: 24px;
flex-wrap: wrap;
}
.roadmap-item {
font-size: 14px;
color: var(--text-secondary);
display: flex;
align-items: center;
gap: 8px;
}
.roadmap-dot {
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--status-wait);
}
/* Tech strip */
.tech-strip {
padding: 64px 0;
border-top: 1px solid var(--border-subtle);
}
.tech-inner {
display: flex;
align-items: center;
justify-content: center;
gap: 48px;
flex-wrap: wrap;
}
.tech-item {
font-family: var(--font-mono);
font-size: 14px;
color: var(--text-muted);
}
.tech-highlight {
background: var(--bg-panel);
border: 1px solid var(--border-subtle);
padding: 12px 20px;
border-radius: var(--radius-sm);
color: var(--text-secondary);
}
/* Final CTA */
.cta-section {
padding: 120px 0;
text-align: center;
}
.cta-title {
font-family: var(--font-mono);
font-size: clamp(24px, 3vw, 32px);
font-weight: 600;
margin-bottom: 32px;
}
.cta-buttons {
display: flex;
justify-content: center;
gap: 16px;
margin-bottom: 32px;
}
.cta-github {
font-size: 14px;
color: var(--text-secondary);
transition: color 0.2s;
}
.cta-github:hover { color: var(--accent-primary); }
/* Footer */
.footer {
padding: 48px 0;
border-top: 1px solid var(--border-subtle);
}
.footer-inner {
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
gap: 24px;
}
.footer-left {
display: flex;
align-items: center;
gap: 24px;
}
.footer-logo {
font-family: var(--font-mono);
font-size: 16px;
font-weight: 600;
}
.footer-copy {
font-size: 13px;
color: var(--text-muted);
}
.footer-links {
display: flex;
gap: 24px;
}
.footer-links a {
font-size: 13px;
color: var(--text-secondary);
transition: color 0.2s;
}
.footer-links a:hover { color: var(--text-primary); }
.footer-tagline {
font-size: 13px;
color: var(--text-muted);
font-style: italic;
}
/* Mobile nav */
.mobile-nav-toggle {
display: none;
background: none;
border: none;
color: var(--text-primary);
font-size: 24px;
cursor: pointer;
}
/* Responsive */
@media (max-width: 1024px) {
.hero-grid {
grid-template-columns: 1fr;
gap: 48px;
}
.features-grid {
grid-template-columns: repeat(2, 1fr);
}
.steps-grid {
grid-template-columns: 1fr;
gap: 32px;
}
}
@media (max-width: 768px) {
.nav { display: none; }
.mobile-nav-toggle { display: block; }
.hero { padding: 120px 0 60px; }
.hero-buttons {
flex-direction: column;
}
.problem-grid {
grid-template-columns: 1fr;
}
.features-grid {
grid-template-columns: 1fr;
}
.terminal-grid {
grid-template-columns: 1fr;
}
.tech-inner {
gap: 24px;
}
.footer-inner {
flex-direction: column;
text-align: center;
}
.footer-left {
flex-direction: column;
}
}
/* Reduced motion */
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
/* Scroll reveal */
.reveal {
opacity: 0;
transform: translateY(20px);
transition: opacity 0.6s, transform 0.6s;
}
.reveal.visible {
opacity: 1;
transform: translateY(0);
}
</style>
</head>
<body>
<!-- Header -->
<header class="header" id="header">
<div class="container">
<div class="header-inner">
<a href="/" class="logo">
<span class="logo-icon">>_</span>
spaceshell
</a>
<nav class="nav">
<a href="#features">Возможности</a>
<a href="#how">Как работает</a>
<a href="#cli">CLI</a>
<a href="https://git.realmanual.ru/pub/spaceshell" target="_blank" rel="noopener">GitHub</a>
</nav>
<a href="https://spaceshell.ru/download/spacesh.dmg" download class="btn btn-primary">Скачать для macOS</a>
<button class="mobile-nav-toggle" aria-label="Меню"></button>
</div>
</div>
</header>
<!-- Hero -->
<section class="hero">
<div class="container">
<div class="hero-grid">
<div class="hero-content">
<p class="hero-eyebrow">Терминал-воркспейс для AI-агентов · macOS</p>
<h1 class="hero-title">
Гоняй десяток AI-агентов параллельно. <span class="accent">Не теряй ни одного.</span>
</h1>
<p class="hero-subtitle">
spaceshell держит живые сессии Claude Code, Codex, Gemini и shell в фоновом демоне.
Закрыл окно, обновил приложение, словил краш — агенты продолжают работать.
</p>
<div class="hero-buttons">
<a href="https://spaceshell.ru/download/spacesh.dmg" download class="btn btn-primary btn-large">
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
<path d="M8 0a8 8 0 100 16A8 8 0 008 0zm3.5 9l-3 3a.7.7 0 01-1 0l-3-3a.7.7 0 011-1L7 9.5V4a1 1 0 012 0v5.5L10.5 8a.7.7 0 011 1z"/>
</svg>
Скачать для macOS
</a>
<a href="#how" class="btn btn-secondary btn-large">Как это работает</a>
</div>
<p class="hero-meta">
macOS 13+ <span>·</span> Apple Silicon и Intel <span>·</span> открытый исходник <span>·</span> early access
</p>
</div>
<div class="terminal-mockup">
<div class="terminal-header">
<div class="terminal-dots">
<span class="terminal-dot"></span>
<span class="terminal-dot"></span>
<span class="terminal-dot"></span>
</div>
<span class="terminal-title">spaceshell — workspace</span>
</div>
<div class="terminal-grid">
<div class="terminal-pane">
<div class="pane-header">
<span class="pane-name">
<span class="status-ring status-work"></span>
claude-code
</span>
</div>
<div class="pane-content">
<div class="typed-line" id="typed1"></div>
</div>
</div>
<div class="terminal-pane">
<div class="pane-header">
<span class="pane-name">
<span class="status-ring status-done"></span>
codex
</span>
</div>
<div class="pane-content">
<span style="color: var(--status-done);"></span> Refactored auth module<br>
<span style="color: var(--text-muted);">3 files changed</span>
</div>
</div>
<div class="terminal-pane">
<div class="pane-header">
<span class="pane-name">
<span class="status-ring status-wait"></span>
gemini
</span>
</div>
<div class="pane-content">
<span style="color: var(--status-wait);"></span> Waiting for API...<br>
<span style="color: var(--text-muted);">rate limit cooldown</span>
</div>
</div>
<div class="terminal-pane">
<div class="pane-header">
<span class="pane-name">
<span class="status-ring status-idle"></span>
shell
</span>
</div>
<div class="pane-content">
<span style="color: var(--text-muted);">~/projects/app $</span> <span class="cursor"></span>
</div>
</div>
</div>
<div class="daemon-badge">
<span class="daemon-dot"></span>
daemon · alive
</div>
</div>
</div>
</div>
</section>
<!-- Agents strip -->
<section class="agents-strip">
<div class="container">
<div class="agents-inner">
<span class="agents-label">Работает с:</span>
<span class="agent-tag">Claude Code</span>
<span class="agent-tag">Codex</span>
<span class="agent-tag">Gemini</span>
<span class="agent-tag">opencode</span>
<span class="agent-tag">deepseek</span>
<span class="agent-tag">shell</span>
</div>
</div>
</section>
<!-- Problem/Solution -->
<section class="problem-section">
<div class="container">
<div class="problem-grid reveal">
<div class="problem-card">
<p class="card-label">Проблема</p>
<h3 class="card-title">Обычный терминал привязывает агента к окну.</h3>
<p class="card-text">
Закрыл вкладку, перезапустил приложение, упал GUI — длинная сессия агента умирает вместе с ним.
</p>
</div>
<div class="solution-card">
<p class="card-label">Решение</p>
<h3 class="card-title">spaceshell разрывает эту связь.</h3>
<p class="card-text">
Сессиями владеет фоновый демон, а не окно. Интерфейс — всего лишь вид поверх него.
</p>
</div>
</div>
</div>
</section>
<!-- Features -->
<section class="features-section" id="features">
<div class="container">
<div class="section-header reveal">
<h2 class="section-title">Возможности</h2>
</div>
<div class="features-grid">
<div class="feature-card reveal">
<span class="feature-icon">daemon</span>
<h3 class="feature-title">Демон — источник истины</h3>
<p class="feature-text">
spaceshd владеет живыми PTY-сессиями. GUI и CLI — тонкие клиенты поверх одного Unix-сокета.
Убей интерфейс — агент жив. Открой заново — экран восстановится из снапшота за доли секунды.
</p>
</div>
<div class="feature-card reveal">
<span class="feature-icon">grid</span>
<h3 class="feature-title">Параллельные агенты в одной сетке</h3>
<p class="feature-text">
Несколько агентов в раскладке-гриде: сплиты, зум панели, перетаскивание, пресеты (2×2, 1+2, 2×3…),
воркспейсы и избранное.
</p>
</div>
<div class="feature-card reveal">
<span class="feature-icon">status</span>
<h3 class="feature-title">Статусы без догадок</h3>
<p class="feature-text">
work · wait · done · error · idle приходят пушем — от хуков агентов, маркеров OSC 133 и паттернов.
Кольца, бейджи, центр событий и нативные уведомления macOS.
</p>
</div>
<div class="feature-card reveal" id="cli">
<span class="feature-icon">search</span>
<h3 class="feature-title">Гибридный терминал</h3>
<p class="feature-text">
xterm.js рисует, грид alacritty в демоне анализирует. Отсюда — поиск по скроллбэку (⌘F) с подсветкой,
извлечение последней команды и мгновенные снапшоты для reattach.
</p>
</div>
<div class="feature-card reveal">
<span class="feature-icon">cli</span>
<h3 class="feature-title">CLI как первый класс</h3>
<p class="feature-text">
spacesh status --json, focus, new-surface, notify — те же команды, что и в интерфейсе, плюс shell-completions.
Встраивай spaceshell в свои пайплайны.
</p>
</div>
<div class="feature-card reveal">
<span class="feature-icon">theme</span>
<h3 class="feature-title">Под себя</h3>
<p class="feature-text">
Тёмная и светлая темы, акцентные цвета, шрифт и размер терминала, дефолтный shell —
всё хранится демоном в config.toml и применяется на лету ко всем окнам.
</p>
</div>
</div>
</div>
</section>
<!-- How it works -->
<section class="how-section" id="how">
<div class="container">
<div class="section-header reveal">
<h2 class="section-title">Как это работает</h2>
</div>
<div class="steps-grid">
<div class="step reveal">
<div class="step-number">01</div>
<h3 class="step-title">Запуск</h3>
<p class="step-text">
Создаёшь воркспейс и панели — демон спавнит PTY-сессии под агентов.
</p>
<div class="step-diagram">
spawn → spaceshd → PTY
</div>
</div>
<div class="step reveal">
<div class="step-number">02</div>
<h3 class="step-title">Демон владеет</h3>
<p class="step-text">
Байты летают GUI ↔ демон ↔ PTY по одному сокету. Интерфейс состояния не хранит — только команды и события.
</p>
<div class="step-diagram">
GUI ⇄ Unix socket ⇄ PTY
</div>
</div>
<div class="step reveal">
<div class="step-number">03</div>
<h3 class="step-title">Reattach</h3>
<p class="step-text">
Закрыл и открыл приложение — демон отдаёт снапшот экрана, окно перерисовывается мгновенно.
</p>
<div class="step-diagram">
snapshot → restore → live
</div>
</div>
</div>
</div>
</section>
<!-- Roadmap -->
<section class="roadmap-section">
<div class="container">
<div class="roadmap-inner reveal">
<p class="roadmap-label">В планах</p>
<h3 class="roadmap-title">Над чем работаем</h3>
<div class="roadmap-items">
<span class="roadmap-item">
<span class="roadmap-dot"></span>
Внешние уведомления в Telegram и MAX
</span>
<span class="roadmap-item">
<span class="roadmap-dot"></span>
diff-просмотр изменений агента
</span>
<span class="roadmap-item">
<span class="roadmap-dot"></span>
Удалённая работа через SSH-туннель к демону
</span>
</div>
</div>
</div>
</section>
<!-- Tech strip -->
<section class="tech-strip">
<div class="container">
<div class="tech-inner">
<span class="tech-item">Rust</span>
<span class="tech-item">Tauri 2</span>
<span class="tech-item">tokio</span>
<span class="tech-item">xterm.js</span>
<span class="tech-item">alacritty</span>
<span class="tech-highlight">< 16 мс на нажатие клавиши</span>
</div>
</div>
</section>
<!-- Final CTA -->
<section class="cta-section" id="download">
<div class="container">
<h2 class="cta-title reveal">Готов гонять агентов пачками?</h2>
<div class="cta-buttons reveal">
<a href="https://spaceshell.ru/download/spacesh.dmg" download class="btn btn-primary btn-large">
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
<path d="M8 0a8 8 0 100 16A8 8 0 008 0zm3.5 9l-3 3a.7.7 0 01-1 0l-3-3a.7.7 0 011-1L7 9.5V4a1 1 0 012 0v5.5L10.5 8a.7.7 0 011 1z"/>
</svg>
Скачать для macOS
</a>
</div>
<a href="https://git.realmanual.ru/pub/spaceshell" target="_blank" rel="noopener" class="cta-github">
Исходники на GitHub →
</a>
</div>
</section>
<!-- Footer -->
<footer class="footer">
<div class="container">
<div class="footer-inner">
<div class="footer-left">
<span class="footer-logo">spaceshell.ru</span>
<span class="footer-copy">© 2026 spaceshell</span>
</div>
<div class="footer-links">
<a href="https://git.realmanual.ru/pub/spaceshell" target="_blank" rel="noopener">GitHub</a>
<a href="#">Документация</a>
<a href="#">Лицензия</a>
</div>
<p class="footer-tagline">Сделано для тех, кто запускает агентов пачками.</p>
</div>
</div>
</footer>
<script>
// Header scroll effect
const header = document.getElementById('header');
window.addEventListener('scroll', () => {
header.classList.toggle('scrolled', window.scrollY > 50);
});
// Scroll reveal
const reveals = document.querySelectorAll('.reveal');
const revealObserver = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('visible');
}
});
}, { threshold: 0.1 });
reveals.forEach(el => revealObserver.observe(el));
// Typewriter effect
const typedEl = document.getElementById('typed1');
const lines = [
'Analyzing codebase structure...',
'Found 47 components',
'Refactoring payment module...',
'✓ Updated PaymentService.ts',
'Running tests...'
];
let lineIndex = 0;
let charIndex = 0;
function type() {
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
typedEl.innerHTML = lines[0] + '<span class="cursor"></span>';
return;
}
const currentLine = lines[lineIndex];
if (charIndex < currentLine.length) {
typedEl.innerHTML = currentLine.substring(0, charIndex + 1) + '<span class="cursor"></span>';
charIndex++;
setTimeout(type, 30 + Math.random() * 40);
} else {
setTimeout(() => {
charIndex = 0;
lineIndex = (lineIndex + 1) % lines.length;
typedEl.innerHTML = '<span class="cursor"></span>';
setTimeout(type, 500);
}, 2000);
}
}
type();
</script>
</body>
</html>