Revert "feat: add vercel deployment, remove old landing page (#10686)"
This reverts commit 51d5c7648852cdc2674d3b00b43ee0300cca62c3.
25
.github/workflows/deploy-site.yml
vendored
@ -1,12 +1,11 @@
|
|||||||
name: Deploy Site
|
name: Deploy Site
|
||||||
|
|
||||||
on:
|
on:
|
||||||
release:
|
|
||||||
types: [published]
|
|
||||||
push:
|
push:
|
||||||
branches: [main]
|
branches: [main]
|
||||||
paths:
|
paths:
|
||||||
- 'website/**'
|
- 'website/**'
|
||||||
|
- 'landingpage/**'
|
||||||
- 'skills/**'
|
- 'skills/**'
|
||||||
- 'optional-skills/**'
|
- 'optional-skills/**'
|
||||||
- '.github/workflows/deploy-site.yml'
|
- '.github/workflows/deploy-site.yml'
|
||||||
@ -21,14 +20,8 @@ concurrency:
|
|||||||
cancel-in-progress: false
|
cancel-in-progress: false
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
deploy-vercel:
|
build-and-deploy:
|
||||||
if: github.event_name == 'release'
|
# Only run on the upstream repository, not on forks
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Trigger Vercel Deploy
|
|
||||||
run: curl -X POST "${{ secrets.VERCEL_DEPLOY_HOOK }}"
|
|
||||||
|
|
||||||
deploy-docs:
|
|
||||||
if: github.repository == 'NousResearch/hermes-agent'
|
if: github.repository == 'NousResearch/hermes-agent'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
environment:
|
environment:
|
||||||
@ -69,10 +62,20 @@ jobs:
|
|||||||
run: npm run build
|
run: npm run build
|
||||||
working-directory: website
|
working-directory: website
|
||||||
|
|
||||||
|
- name: Stage deployment
|
||||||
|
run: |
|
||||||
|
mkdir -p _site/docs
|
||||||
|
# Landing page at root
|
||||||
|
cp -r landingpage/* _site/
|
||||||
|
# Docusaurus at /docs/
|
||||||
|
cp -r website/build/* _site/docs/
|
||||||
|
# CNAME so GitHub Pages keeps the custom domain between deploys
|
||||||
|
echo "hermes-agent.nousresearch.com" > _site/CNAME
|
||||||
|
|
||||||
- name: Upload artifact
|
- name: Upload artifact
|
||||||
uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa # v3
|
uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa # v3
|
||||||
with:
|
with:
|
||||||
path: website/build
|
path: _site
|
||||||
|
|
||||||
- name: Deploy to GitHub Pages
|
- name: Deploy to GitHub Pages
|
||||||
id: deploy
|
id: deploy
|
||||||
|
|||||||
BIN
landingpage/apple-touch-icon.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
landingpage/favicon-16x16.png
Normal file
|
After Width: | Height: | Size: 870 B |
BIN
landingpage/favicon-32x32.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
landingpage/favicon.ico
Normal file
|
After Width: | Height: | Size: 7.9 KiB |
BIN
landingpage/hermes-agent-banner.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
landingpage/icon-192.png
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
landingpage/icon-512.png
Normal file
|
After Width: | Height: | Size: 134 KiB |
665
landingpage/index.html
Normal file
@ -0,0 +1,665 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Hermes Agent — An Agent That Grows With You</title>
|
||||||
|
<meta
|
||||||
|
name="description"
|
||||||
|
content="An open-source agent that grows with you — learns your projects, builds its own skills, and reaches you wherever you are. By Nous Research."
|
||||||
|
/>
|
||||||
|
<meta name="theme-color" content="#0A0E1A" />
|
||||||
|
|
||||||
|
<meta property="og:title" content="Hermes Agent — AI Agent Framework" />
|
||||||
|
<meta
|
||||||
|
property="og:description"
|
||||||
|
content="An open-source agent that grows with you. Install it, give it your messaging accounts, and it becomes a persistent personal agent — learning your projects, building its own skills, and reaching you wherever you are."
|
||||||
|
/>
|
||||||
|
<meta property="og:type" content="website" />
|
||||||
|
<meta property="og:url" content="https://hermes-agent.nousresearch.com" />
|
||||||
|
<meta
|
||||||
|
property="og:image"
|
||||||
|
content="https://hermes-agent.nousresearch.com/hermes-agent-banner.png"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<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@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap"
|
||||||
|
rel="stylesheet"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<script
|
||||||
|
src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"
|
||||||
|
defer
|
||||||
|
></script>
|
||||||
|
<link rel="stylesheet" href="style.css" />
|
||||||
|
<link rel="icon" type="image/x-icon" href="favicon.ico" />
|
||||||
|
<link rel="icon" type="image/png" sizes="32x32" href="favicon-32x32.png" />
|
||||||
|
<link rel="icon" type="image/png" sizes="16x16" href="favicon-16x16.png" />
|
||||||
|
<link rel="apple-touch-icon" sizes="180x180" href="apple-touch-icon.png" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<canvas id="noise-overlay"></canvas>
|
||||||
|
|
||||||
|
<div class="ambient-glow glow-1"></div>
|
||||||
|
<div class="ambient-glow glow-2"></div>
|
||||||
|
|
||||||
|
<nav class="nav">
|
||||||
|
<div class="nav-inner">
|
||||||
|
<a href="#" class="nav-logo">
|
||||||
|
<img src="nous-logo.png" alt="Nous Research" class="nav-nous-logo" />
|
||||||
|
<span class="nav-brand"
|
||||||
|
>Hermes Agent <span class="nav-by">by Nous Research</span></span
|
||||||
|
>
|
||||||
|
</a>
|
||||||
|
<div class="nav-links">
|
||||||
|
<a href="#install">Install</a>
|
||||||
|
<a href="#features">Features</a>
|
||||||
|
<a href="/docs/">Docs</a>
|
||||||
|
<a
|
||||||
|
href="https://github.com/NousResearch/hermes-agent"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener"
|
||||||
|
>GitHub</a
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
href="https://discord.gg/NousResearch"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener"
|
||||||
|
>Discord</a
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
class="nav-hamburger"
|
||||||
|
id="nav-hamburger"
|
||||||
|
onclick="toggleMobileNav()"
|
||||||
|
aria-label="Toggle menu"
|
||||||
|
>
|
||||||
|
<span class="hamburger-bar"></span>
|
||||||
|
<span class="hamburger-bar"></span>
|
||||||
|
<span class="hamburger-bar"></span>
|
||||||
|
</button>
|
||||||
|
<div class="nav-mobile" id="nav-mobile">
|
||||||
|
<a href="#install" onclick="toggleMobileNav()">Install</a>
|
||||||
|
<a href="#features" onclick="toggleMobileNav()">Features</a>
|
||||||
|
<a href="/docs/">Docs</a>
|
||||||
|
<a
|
||||||
|
href="https://github.com/NousResearch/hermes-agent"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener"
|
||||||
|
>GitHub</a
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
href="https://discord.gg/NousResearch"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener"
|
||||||
|
>Discord</a
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<section class="hero">
|
||||||
|
<div class="hero-content">
|
||||||
|
<div class="hero-badge">
|
||||||
|
<span class="badge-dot"></span>
|
||||||
|
Open Source • MIT License
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- prettier-ignore -->
|
||||||
|
<pre class="hero-ascii" aria-hidden="true" style="font-family: monospace; line-height: 1.1">
|
||||||
|
██╗ ██╗███████╗██████╗ ███╗ ███╗███████╗███████╗ █████╗ ██████╗ ███████╗███╗ ██╗████████╗
|
||||||
|
██║ ██║██╔════╝██╔══██╗████╗ ████║██╔════╝██╔════╝ ██╔══██╗██╔════╝ ██╔════╝████╗ ██║╚══██╔══╝
|
||||||
|
███████║█████╗ ██████╔╝██╔████╔██║█████╗ ███████╗ ███████║██║ ███╗█████╗ ██╔██╗ ██║ ██║
|
||||||
|
██╔══██║██╔══╝ ██╔══██╗██║╚██╔╝██║██╔══╝ ╚════██║ ██╔══██║██║ ██║██╔══╝ ██║╚██╗██║ ██║
|
||||||
|
██║ ██║███████╗██║ ██║██║ ╚═╝ ██║███████╗███████║ ██║ ██║╚██████╔╝███████╗██║ ╚████║ ██║
|
||||||
|
╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝╚══════╝ ╚═╝ ╚═╝ ╚═════╝ ╚══════╝╚═╝ ╚═══╝ ╚═╝
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<h1 class="hero-title">
|
||||||
|
An agent that<br />
|
||||||
|
<span class="hero-gradient">grows with you.</span>
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<p class="hero-subtitle">
|
||||||
|
It's not a coding copilot tethered to an IDE or a chatbot wrapper
|
||||||
|
around a single API. It's an <strong>autonomous agent</strong> that
|
||||||
|
lives on your server, remembers what it learns, and gets more capable
|
||||||
|
the longer it runs.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="hero-install">
|
||||||
|
<div class="install-widget">
|
||||||
|
<div class="install-widget-header">
|
||||||
|
<div class="install-dots">
|
||||||
|
<span class="dot dot-red"></span>
|
||||||
|
<span class="dot dot-yellow"></span>
|
||||||
|
<span class="dot dot-green"></span>
|
||||||
|
</div>
|
||||||
|
<div class="install-tabs">
|
||||||
|
<button
|
||||||
|
class="install-tab active"
|
||||||
|
data-platform="linux"
|
||||||
|
onclick="switchPlatform('linux')"
|
||||||
|
>
|
||||||
|
Linux / macOS / WSL
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="install-widget-body">
|
||||||
|
<span class="install-prompt" id="install-prompt">$</span>
|
||||||
|
<code id="install-command"
|
||||||
|
>curl -fsSL
|
||||||
|
https://raw.githubusercontent.com/NousResearch/hermes-agent/main/scripts/install.sh
|
||||||
|
| bash</code
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="copy-btn"
|
||||||
|
onclick="copyInstall()"
|
||||||
|
title="Copy to clipboard"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
>
|
||||||
|
<rect x="9" y="9" width="13" height="13" rx="2" ry="2" />
|
||||||
|
<path
|
||||||
|
d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<span class="copy-text">Copy</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p class="install-note" id="install-note">
|
||||||
|
Works on Linux, macOS & WSL2 · No prerequisites · Installs
|
||||||
|
everything automatically
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="hero-links">
|
||||||
|
<a
|
||||||
|
href="https://portal.nousresearch.com"
|
||||||
|
class="btn btn-primary"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
width="20"
|
||||||
|
height="20"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
>
|
||||||
|
<path d="M15 3h4a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-4" />
|
||||||
|
<polyline points="10 17 15 12 10 7" />
|
||||||
|
<line x1="15" y1="12" x2="3" y2="12" />
|
||||||
|
</svg>
|
||||||
|
Sign Up on Nous Portal
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="section section-install" id="install">
|
||||||
|
<div class="container">
|
||||||
|
<div class="section-header">
|
||||||
|
<h2>Get started in 60 seconds</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="install-steps">
|
||||||
|
<div class="install-step">
|
||||||
|
<div class="step-number">1</div>
|
||||||
|
<div class="step-content">
|
||||||
|
<h4>Install</h4>
|
||||||
|
<div class="code-block">
|
||||||
|
<div class="code-header">
|
||||||
|
<div class="code-tabs">
|
||||||
|
<button
|
||||||
|
class="code-tab active"
|
||||||
|
data-platform="linux"
|
||||||
|
onclick="switchStepPlatform('linux')"
|
||||||
|
>
|
||||||
|
Linux / macOS / WSL
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
class="copy-btn"
|
||||||
|
id="step1-copy"
|
||||||
|
onclick="copyText(this)"
|
||||||
|
data-text="curl -fsSL https://raw.githubusercontent.com/NousResearch/hermes-agent/main/scripts/install.sh | bash"
|
||||||
|
>
|
||||||
|
Copy
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<pre><code id="step1-command">curl -fsSL https://raw.githubusercontent.com/NousResearch/hermes-agent/main/scripts/install.sh | bash</code></pre>
|
||||||
|
</div>
|
||||||
|
<p class="step-note" id="step1-note">
|
||||||
|
Installs uv, Python 3.11, clones the repo, sets up everything.
|
||||||
|
No sudo needed.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="install-step">
|
||||||
|
<div class="step-number">2</div>
|
||||||
|
<div class="step-content">
|
||||||
|
<h4>Configure</h4>
|
||||||
|
<div class="code-block">
|
||||||
|
<div class="code-header">
|
||||||
|
<span>bash</span>
|
||||||
|
<button
|
||||||
|
class="copy-btn"
|
||||||
|
onclick="copyText(this)"
|
||||||
|
data-text="hermes setup"
|
||||||
|
>
|
||||||
|
Copy
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<pre><code><span class="code-comment"># Interactive setup wizard</span>
|
||||||
|
hermes setup
|
||||||
|
|
||||||
|
<span class="code-comment"># Or choose your model</span>
|
||||||
|
hermes model</code></pre>
|
||||||
|
</div>
|
||||||
|
<p class="step-note">
|
||||||
|
Connect to Nous Portal (OAuth), OpenRouter (API key), or your
|
||||||
|
own endpoint.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="install-step">
|
||||||
|
<div class="step-number">3</div>
|
||||||
|
<div class="step-content">
|
||||||
|
<h4>Start chatting</h4>
|
||||||
|
<div class="code-block">
|
||||||
|
<div class="code-header">
|
||||||
|
<span>bash</span>
|
||||||
|
<button
|
||||||
|
class="copy-btn"
|
||||||
|
onclick="copyText(this)"
|
||||||
|
data-text="hermes"
|
||||||
|
>
|
||||||
|
Copy
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<pre><code>hermes</code></pre>
|
||||||
|
</div>
|
||||||
|
<p class="step-note">
|
||||||
|
That's it. Full interactive CLI with tools, memory, and skills.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="install-step">
|
||||||
|
<div class="step-number">4</div>
|
||||||
|
<div class="step-content">
|
||||||
|
<h4>
|
||||||
|
Go multi-platform <span class="step-optional">(optional)</span>
|
||||||
|
</h4>
|
||||||
|
<div class="code-block">
|
||||||
|
<div class="code-header">
|
||||||
|
<span>bash</span>
|
||||||
|
<button
|
||||||
|
class="copy-btn"
|
||||||
|
onclick="copyText(this)"
|
||||||
|
data-text="hermes gateway setup"
|
||||||
|
>
|
||||||
|
Copy
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<pre><code><span class="code-comment"># Interactive gateway setup wizard</span>
|
||||||
|
hermes gateway setup
|
||||||
|
|
||||||
|
<span class="code-comment"># Start the messaging gateway</span>
|
||||||
|
hermes gateway
|
||||||
|
|
||||||
|
<span class="code-comment"># Install as a system service</span>
|
||||||
|
hermes gateway install</code></pre>
|
||||||
|
</div>
|
||||||
|
<p class="step-note">
|
||||||
|
Walk through connecting Telegram, Discord, Slack, or WhatsApp.
|
||||||
|
Runs as a systemd service.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="install-step">
|
||||||
|
<div class="step-number">5</div>
|
||||||
|
<div class="step-content">
|
||||||
|
<h4>Keep it up to date</h4>
|
||||||
|
<div class="code-block">
|
||||||
|
<div class="code-header">
|
||||||
|
<span>bash</span>
|
||||||
|
<button
|
||||||
|
class="copy-btn"
|
||||||
|
onclick="copyText(this)"
|
||||||
|
data-text="hermes update"
|
||||||
|
>
|
||||||
|
Copy
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<pre><code>hermes update</code></pre>
|
||||||
|
</div>
|
||||||
|
<p class="step-note">
|
||||||
|
Pulls the latest changes and reinstalls dependencies. Run
|
||||||
|
anytime to get new features and fixes.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="install-windows">
|
||||||
|
<p>
|
||||||
|
Native Windows support is extremely experimental and unsupported.
|
||||||
|
Please install
|
||||||
|
<a
|
||||||
|
href="https://learn.microsoft.com/en-us/windows/wsl/install"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener"
|
||||||
|
>WSL2</a
|
||||||
|
>
|
||||||
|
and run Hermes Agent from there.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Terminal Demo -->
|
||||||
|
<section class="section section-demo" id="demo">
|
||||||
|
<div class="container">
|
||||||
|
<div class="section-header">
|
||||||
|
<h2>See it in action</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="terminal-window">
|
||||||
|
<div class="terminal-header">
|
||||||
|
<div class="terminal-dots">
|
||||||
|
<span class="dot dot-red"></span>
|
||||||
|
<span class="dot dot-yellow"></span>
|
||||||
|
<span class="dot dot-green"></span>
|
||||||
|
</div>
|
||||||
|
<span class="terminal-title">hermes</span>
|
||||||
|
</div>
|
||||||
|
<div class="terminal-body" id="terminal-demo"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Features + Specs -->
|
||||||
|
<section class="section" id="features">
|
||||||
|
<div class="container">
|
||||||
|
<div class="section-header">
|
||||||
|
<h2>Features</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="features-grid">
|
||||||
|
<div class="feature-card">
|
||||||
|
<div class="feature-header">
|
||||||
|
<div class="feature-icon">
|
||||||
|
<svg
|
||||||
|
width="20"
|
||||||
|
height="20"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<h3>Lives Where You Do</h3>
|
||||||
|
</div>
|
||||||
|
<p>
|
||||||
|
Telegram, Discord, Slack, WhatsApp, and CLI from a single gateway
|
||||||
|
— start on one, pick up on another.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="feature-card">
|
||||||
|
<div class="feature-header">
|
||||||
|
<div class="feature-icon">
|
||||||
|
<svg
|
||||||
|
width="20"
|
||||||
|
height="20"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
>
|
||||||
|
<polyline points="22 7 13.5 15.5 8.5 10.5 2 17" />
|
||||||
|
<polyline points="16 7 22 7 22 13" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<h3>Grows the Longer It Runs</h3>
|
||||||
|
</div>
|
||||||
|
<p>
|
||||||
|
Persistent memory and auto-generated skills — it learns your
|
||||||
|
projects and never forgets how it solved a problem.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="feature-card">
|
||||||
|
<div class="feature-header">
|
||||||
|
<div class="feature-icon">
|
||||||
|
<svg
|
||||||
|
width="20"
|
||||||
|
height="20"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
>
|
||||||
|
<circle cx="12" cy="12" r="10" />
|
||||||
|
<polyline points="12 6 12 12 16 14" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<h3>Scheduled Automations</h3>
|
||||||
|
</div>
|
||||||
|
<p>
|
||||||
|
Natural language cron scheduling for reports, backups, and
|
||||||
|
briefings — running unattended through the gateway.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="feature-card">
|
||||||
|
<div class="feature-header">
|
||||||
|
<div class="feature-icon">
|
||||||
|
<svg
|
||||||
|
width="20"
|
||||||
|
height="20"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
>
|
||||||
|
<circle cx="18" cy="18" r="3" />
|
||||||
|
<circle cx="6" cy="6" r="3" />
|
||||||
|
<path d="M6 21V9a9 9 0 0 0 9 9" />
|
||||||
|
<path d="M18 3v12a9 9 0 0 1-9-9" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<h3>Delegates & Parallelizes</h3>
|
||||||
|
</div>
|
||||||
|
<p>
|
||||||
|
Isolated subagents with their own conversations, terminals, and
|
||||||
|
Python RPC scripts for zero-context-cost pipelines.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="feature-card">
|
||||||
|
<div class="feature-header">
|
||||||
|
<div class="feature-icon">
|
||||||
|
<svg
|
||||||
|
width="20"
|
||||||
|
height="20"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
>
|
||||||
|
<rect x="3" y="11" width="18" height="11" rx="2" ry="2" />
|
||||||
|
<path d="M7 11V7a5 5 0 0 1 10 0v4" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<h3>Real Sandboxing</h3>
|
||||||
|
</div>
|
||||||
|
<p>
|
||||||
|
Five backends — local, Docker, SSH, Singularity, Modal — with
|
||||||
|
container hardening and namespace isolation.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="feature-card">
|
||||||
|
<div class="feature-header">
|
||||||
|
<div class="feature-icon">
|
||||||
|
<svg
|
||||||
|
width="20"
|
||||||
|
height="20"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
>
|
||||||
|
<circle cx="12" cy="12" r="10" />
|
||||||
|
<line x1="2" y1="12" x2="22" y2="12" />
|
||||||
|
<path
|
||||||
|
d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<h3>Full Web & Browser Control</h3>
|
||||||
|
</div>
|
||||||
|
<p>
|
||||||
|
Web search, browser automation, vision, image generation,
|
||||||
|
text-to-speech, and multi-model reasoning.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="features-more">
|
||||||
|
<button class="more-toggle" onclick="toggleSpecs()" id="specs-toggle">
|
||||||
|
<span class="toggle-label">More details</span>
|
||||||
|
<svg
|
||||||
|
class="more-chevron"
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
>
|
||||||
|
<polyline points="6 9 12 15 18 9" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="specs-wrapper" id="specs-wrapper">
|
||||||
|
<div class="specs-list">
|
||||||
|
<div class="spec-row">
|
||||||
|
<h3 class="spec-label">Tools</h3>
|
||||||
|
<p class="spec-value">
|
||||||
|
40+ built-in — web search, terminal, file system, browser
|
||||||
|
automation, vision, image generation, text-to-speech, code
|
||||||
|
execution, subagent delegation, memory, task planning, cron
|
||||||
|
scheduling, multi-model reasoning, and more.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="spec-row">
|
||||||
|
<h3 class="spec-label">Platforms</h3>
|
||||||
|
<p class="spec-value">
|
||||||
|
Telegram, Discord, Slack, WhatsApp, Signal, Email, and CLI — all
|
||||||
|
from a single gateway. Connect to
|
||||||
|
<a
|
||||||
|
href="https://portal.nousresearch.com"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener"
|
||||||
|
>Nous Portal</a
|
||||||
|
>, OpenRouter, or any OpenAI-compatible API.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="spec-row">
|
||||||
|
<h3 class="spec-label">Environments</h3>
|
||||||
|
<p class="spec-value">
|
||||||
|
Run locally, in Docker, over SSH, on Modal, Daytona, or
|
||||||
|
Singularity. Container hardening with read-only root, dropped
|
||||||
|
capabilities, and namespace isolation.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="spec-row">
|
||||||
|
<h3 class="spec-label">Skills</h3>
|
||||||
|
<p class="spec-value">
|
||||||
|
40+ bundled skills covering MLOps, GitHub workflows, research,
|
||||||
|
and more. The agent creates new skills on the fly and shares
|
||||||
|
them via the open
|
||||||
|
<a href="https://agentskills.io" target="_blank" rel="noopener"
|
||||||
|
>agentskills.io</a
|
||||||
|
>
|
||||||
|
format. Install community skills from
|
||||||
|
<a href="https://clawhub.ai" target="_blank" rel="noopener"
|
||||||
|
>ClawHub</a
|
||||||
|
>,
|
||||||
|
<a href="https://lobehub.com" target="_blank" rel="noopener"
|
||||||
|
>LobeHub</a
|
||||||
|
>, and GitHub.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="spec-row">
|
||||||
|
<h3 class="spec-label">Research</h3>
|
||||||
|
<p class="spec-value">
|
||||||
|
Batch trajectory generation with parallel workers and
|
||||||
|
checkpointing. Atropos integration for RL training. Export to
|
||||||
|
ShareGPT for fine-tuning with trajectory compression.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<footer class="footer">
|
||||||
|
<div class="container">
|
||||||
|
<p class="footer-copy">
|
||||||
|
Built by
|
||||||
|
<a href="https://nousresearch.com" target="_blank" rel="noopener"
|
||||||
|
>Nous Research</a
|
||||||
|
>
|
||||||
|
· MIT License · 2026
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<script src="script.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
BIN
landingpage/nous-logo.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
521
landingpage/script.js
Normal file
@ -0,0 +1,521 @@
|
|||||||
|
// =========================================================================
|
||||||
|
// Hermes Agent Landing Page — Interactions
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
// --- Platform install commands ---
|
||||||
|
const PLATFORMS = {
|
||||||
|
linux: {
|
||||||
|
command:
|
||||||
|
"curl -fsSL https://raw.githubusercontent.com/NousResearch/hermes-agent/main/scripts/install.sh | bash",
|
||||||
|
prompt: "$",
|
||||||
|
note: "Works on Linux, macOS & WSL2 · No prerequisites · Installs everything automatically",
|
||||||
|
stepNote:
|
||||||
|
"Installs uv, Python 3.11, clones the repo, sets up everything. No sudo needed.",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
function detectPlatform() {
|
||||||
|
return "linux";
|
||||||
|
}
|
||||||
|
|
||||||
|
function switchPlatform(platform) {
|
||||||
|
const cfg = PLATFORMS[platform];
|
||||||
|
if (!cfg) return;
|
||||||
|
|
||||||
|
// Update hero install widget
|
||||||
|
const commandEl = document.getElementById("install-command");
|
||||||
|
const promptEl = document.getElementById("install-prompt");
|
||||||
|
const noteEl = document.getElementById("install-note");
|
||||||
|
|
||||||
|
if (commandEl) commandEl.textContent = cfg.command;
|
||||||
|
if (promptEl) promptEl.textContent = cfg.prompt;
|
||||||
|
if (noteEl) noteEl.textContent = cfg.note;
|
||||||
|
|
||||||
|
// Update active tab in hero
|
||||||
|
document.querySelectorAll(".install-tab").forEach((tab) => {
|
||||||
|
tab.classList.toggle("active", tab.dataset.platform === platform);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Sync the step section tabs too
|
||||||
|
switchStepPlatform(platform);
|
||||||
|
}
|
||||||
|
|
||||||
|
function switchStepPlatform(platform) {
|
||||||
|
const cfg = PLATFORMS[platform];
|
||||||
|
if (!cfg) return;
|
||||||
|
|
||||||
|
const commandEl = document.getElementById("step1-command");
|
||||||
|
const copyBtn = document.getElementById("step1-copy");
|
||||||
|
const noteEl = document.getElementById("step1-note");
|
||||||
|
|
||||||
|
if (commandEl) commandEl.textContent = cfg.command;
|
||||||
|
if (copyBtn) copyBtn.setAttribute("data-text", cfg.command);
|
||||||
|
if (noteEl) noteEl.textContent = cfg.stepNote;
|
||||||
|
|
||||||
|
// Update active tab in step section
|
||||||
|
document.querySelectorAll(".code-tab").forEach((tab) => {
|
||||||
|
tab.classList.toggle("active", tab.dataset.platform === platform);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleMobileNav() {
|
||||||
|
document.getElementById("nav-mobile").classList.toggle("open");
|
||||||
|
document.getElementById("nav-hamburger").classList.toggle("open");
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleSpecs() {
|
||||||
|
const wrapper = document.getElementById("specs-wrapper");
|
||||||
|
const btn = document.getElementById("specs-toggle");
|
||||||
|
const label = btn.querySelector(".toggle-label");
|
||||||
|
const isOpen = wrapper.classList.contains("open");
|
||||||
|
|
||||||
|
if (isOpen) {
|
||||||
|
wrapper.style.maxHeight = wrapper.scrollHeight + "px";
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
wrapper.style.maxHeight = "0";
|
||||||
|
});
|
||||||
|
wrapper.classList.remove("open");
|
||||||
|
btn.classList.remove("open");
|
||||||
|
if (label) label.textContent = "More details";
|
||||||
|
} else {
|
||||||
|
wrapper.classList.add("open");
|
||||||
|
wrapper.style.maxHeight = wrapper.scrollHeight + "px";
|
||||||
|
btn.classList.add("open");
|
||||||
|
if (label) label.textContent = "Less";
|
||||||
|
wrapper.addEventListener(
|
||||||
|
"transitionend",
|
||||||
|
() => {
|
||||||
|
if (wrapper.classList.contains("open")) {
|
||||||
|
wrapper.style.maxHeight = "none";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ once: true }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Copy to clipboard ---
|
||||||
|
function copyInstall() {
|
||||||
|
const text = document.getElementById("install-command").textContent;
|
||||||
|
navigator.clipboard.writeText(text).then(() => {
|
||||||
|
const btn = document.querySelector(".install-widget-body .copy-btn");
|
||||||
|
const original = btn.querySelector(".copy-text").textContent;
|
||||||
|
btn.querySelector(".copy-text").textContent = "Copied!";
|
||||||
|
btn.style.color = "var(--primary-light)";
|
||||||
|
setTimeout(() => {
|
||||||
|
btn.querySelector(".copy-text").textContent = original;
|
||||||
|
btn.style.color = "";
|
||||||
|
}, 2000);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function copyText(btn) {
|
||||||
|
const text = btn.getAttribute("data-text");
|
||||||
|
navigator.clipboard.writeText(text).then(() => {
|
||||||
|
const original = btn.textContent;
|
||||||
|
btn.textContent = "Copied!";
|
||||||
|
btn.style.color = "var(--primary-light)";
|
||||||
|
setTimeout(() => {
|
||||||
|
btn.textContent = original;
|
||||||
|
btn.style.color = "";
|
||||||
|
}, 2000);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Scroll-triggered fade-in ---
|
||||||
|
function initScrollAnimations() {
|
||||||
|
const elements = document.querySelectorAll(
|
||||||
|
".feature-card, .install-step, " +
|
||||||
|
".section-header, .terminal-window",
|
||||||
|
);
|
||||||
|
|
||||||
|
elements.forEach((el) => el.classList.add("fade-in"));
|
||||||
|
|
||||||
|
const observer = new IntersectionObserver(
|
||||||
|
(entries) => {
|
||||||
|
entries.forEach((entry) => {
|
||||||
|
if (entry.isIntersecting) {
|
||||||
|
// Stagger children within grids
|
||||||
|
const parent = entry.target.parentElement;
|
||||||
|
if (parent) {
|
||||||
|
const siblings = parent.querySelectorAll(".fade-in");
|
||||||
|
let idx = Array.from(siblings).indexOf(entry.target);
|
||||||
|
if (idx < 0) idx = 0;
|
||||||
|
setTimeout(() => {
|
||||||
|
entry.target.classList.add("visible");
|
||||||
|
}, idx * 60);
|
||||||
|
} else {
|
||||||
|
entry.target.classList.add("visible");
|
||||||
|
}
|
||||||
|
observer.unobserve(entry.target);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{ threshold: 0.1, rootMargin: "0px 0px -40px 0px" },
|
||||||
|
);
|
||||||
|
|
||||||
|
elements.forEach((el) => observer.observe(el));
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Terminal Demo ---
|
||||||
|
const CURSOR = '<span class="terminal-cursor">█</span>';
|
||||||
|
|
||||||
|
const demoSequence = [
|
||||||
|
{ type: "prompt", text: "❯ " },
|
||||||
|
{
|
||||||
|
type: "type",
|
||||||
|
text: "Research the latest approaches to GRPO training and write a summary",
|
||||||
|
delay: 30,
|
||||||
|
},
|
||||||
|
{ type: "pause", ms: 600 },
|
||||||
|
{
|
||||||
|
type: "output",
|
||||||
|
lines: [
|
||||||
|
"",
|
||||||
|
'<span class="t-dim"> web_search "GRPO reinforcement learning 2026" 1.2s</span>',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ type: "pause", ms: 400 },
|
||||||
|
{
|
||||||
|
type: "output",
|
||||||
|
lines: [
|
||||||
|
'<span class="t-dim"> web_extract arxiv.org/abs/2402.03300 3.1s</span>',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ type: "pause", ms: 400 },
|
||||||
|
{
|
||||||
|
type: "output",
|
||||||
|
lines: [
|
||||||
|
'<span class="t-dim"> web_search "GRPO vs PPO ablation results" 0.9s</span>',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ type: "pause", ms: 400 },
|
||||||
|
{
|
||||||
|
type: "output",
|
||||||
|
lines: [
|
||||||
|
'<span class="t-dim"> web_extract huggingface.co/blog/grpo 2.8s</span>',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ type: "pause", ms: 400 },
|
||||||
|
{
|
||||||
|
type: "output",
|
||||||
|
lines: [
|
||||||
|
'<span class="t-dim"> write_file ~/research/grpo-summary.md 0.1s</span>',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ type: "pause", ms: 500 },
|
||||||
|
{
|
||||||
|
type: "output",
|
||||||
|
lines: [
|
||||||
|
"",
|
||||||
|
'<span class="t-text">Done! I\'ve written a summary covering:</span>',
|
||||||
|
"",
|
||||||
|
'<span class="t-text"> <span class="t-green">✓</span> GRPO\'s group-relative advantage (no critic model needed)</span>',
|
||||||
|
'<span class="t-text"> <span class="t-green">✓</span> Comparison with PPO/DPO on reasoning benchmarks</span>',
|
||||||
|
'<span class="t-text"> <span class="t-green">✓</span> Implementation notes for Axolotl and TRL</span>',
|
||||||
|
"",
|
||||||
|
'<span class="t-text">Saved to</span> <span class="t-accent">~/research/grpo-summary.md</span>',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ type: "pause", ms: 2500 },
|
||||||
|
|
||||||
|
{ type: "clear" },
|
||||||
|
{ type: "prompt", text: "❯ " },
|
||||||
|
{
|
||||||
|
type: "type",
|
||||||
|
text: "Review the PR at NousResearch/hermes-agent#42 and fix any issues",
|
||||||
|
delay: 30,
|
||||||
|
},
|
||||||
|
{ type: "pause", ms: 600 },
|
||||||
|
{
|
||||||
|
type: "output",
|
||||||
|
lines: [
|
||||||
|
"",
|
||||||
|
'<span class="t-dim"> delegate_task "review PR #42 changes" 2.1s</span>',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ type: "pause", ms: 500 },
|
||||||
|
{
|
||||||
|
type: "output",
|
||||||
|
lines: [
|
||||||
|
'<span class="t-dim"> git diff main..pr-42 0.4s</span>',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ type: "pause", ms: 400 },
|
||||||
|
{
|
||||||
|
type: "output",
|
||||||
|
lines: [
|
||||||
|
'<span class="t-dim"> patch tools/registry.py 0.1s</span>',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ type: "pause", ms: 400 },
|
||||||
|
{
|
||||||
|
type: "output",
|
||||||
|
lines: [
|
||||||
|
'<span class="t-dim"> python -m pytest tests/ -x 3.2s</span>',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ type: "pause", ms: 400 },
|
||||||
|
{
|
||||||
|
type: "output",
|
||||||
|
lines: [
|
||||||
|
'<span class="t-dim"> git commit -m "fix: handle empty tool schemas" 0.3s</span>',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ type: "pause", ms: 500 },
|
||||||
|
{
|
||||||
|
type: "output",
|
||||||
|
lines: [
|
||||||
|
"",
|
||||||
|
'<span class="t-text">Found 2 issues in the PR and fixed both:</span>',
|
||||||
|
"",
|
||||||
|
'<span class="t-text"> <span class="t-green">✓</span> Empty tool schema crash in registry.py — added guard</span>',
|
||||||
|
'<span class="t-text"> <span class="t-green">✓</span> Missing error handling in delegate_tool.py — added try/except</span>',
|
||||||
|
"",
|
||||||
|
'<span class="t-text">Tests pass. Committed the fix and pushed to the PR branch.</span>',
|
||||||
|
'<span class="t-text">I also saved a</span> <span class="t-accent">skill</span> <span class="t-text">for this PR review pattern.</span>',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ type: "pause", ms: 2500 },
|
||||||
|
|
||||||
|
{ type: "clear" },
|
||||||
|
{ type: "prompt", text: "❯ " },
|
||||||
|
{
|
||||||
|
type: "type",
|
||||||
|
text: "How did we fix that Docker networking issue?",
|
||||||
|
delay: 35,
|
||||||
|
},
|
||||||
|
{ type: "pause", ms: 500 },
|
||||||
|
{
|
||||||
|
type: "output",
|
||||||
|
lines: [
|
||||||
|
"",
|
||||||
|
'<span class="t-dim"> session_search "Docker networking" 1.4s</span>',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ type: "pause", ms: 500 },
|
||||||
|
{
|
||||||
|
type: "output",
|
||||||
|
lines: [
|
||||||
|
"",
|
||||||
|
'<span class="t-text">Found it — from a session on February 12th:</span>',
|
||||||
|
"",
|
||||||
|
'<span class="t-text">The containers couldn\'t reach each other because the compose</span>',
|
||||||
|
'<span class="t-text">file was using the default bridge network. We switched to a</span>',
|
||||||
|
'<span class="t-text">custom network with</span> <span class="t-accent">driver: overlay</span><span class="t-text">, added explicit</span>',
|
||||||
|
'<span class="t-text">aliases, and set</span> <span class="t-accent">dns: 8.8.8.8</span> <span class="t-text">as a fallback.</span>',
|
||||||
|
"",
|
||||||
|
'<span class="t-text">The fix was committed in</span> <span class="t-accent">docker-compose.prod.yml</span><span class="t-text">.</span>',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ type: "pause", ms: 3000 },
|
||||||
|
];
|
||||||
|
|
||||||
|
class TerminalDemo {
|
||||||
|
constructor(container) {
|
||||||
|
this.container = container;
|
||||||
|
this.running = false;
|
||||||
|
this.content = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
async start() {
|
||||||
|
if (this.running) return;
|
||||||
|
this.running = true;
|
||||||
|
|
||||||
|
while (this.running) {
|
||||||
|
for (const step of demoSequence) {
|
||||||
|
if (!this.running) return;
|
||||||
|
await this.execute(step);
|
||||||
|
}
|
||||||
|
this.clear();
|
||||||
|
await this.sleep(1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stop() {
|
||||||
|
this.running = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async execute(step) {
|
||||||
|
switch (step.type) {
|
||||||
|
case "prompt":
|
||||||
|
this.append(`<span class="t-prompt">${step.text}</span>`);
|
||||||
|
break;
|
||||||
|
case "type":
|
||||||
|
for (const char of step.text) {
|
||||||
|
if (!this.running) return;
|
||||||
|
this.append(`<span class="t-cmd">${char}</span>`);
|
||||||
|
await this.sleep(step.delay || 30);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "output":
|
||||||
|
for (const line of step.lines) {
|
||||||
|
if (!this.running) return;
|
||||||
|
this.append("\n" + line);
|
||||||
|
await this.sleep(50);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "pause":
|
||||||
|
await this.sleep(step.ms);
|
||||||
|
break;
|
||||||
|
case "clear":
|
||||||
|
this.clear();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
append(html) {
|
||||||
|
this.content += html;
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
this.container.innerHTML = this.content + CURSOR;
|
||||||
|
this.container.scrollTop = this.container.scrollHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
clear() {
|
||||||
|
this.content = "";
|
||||||
|
this.container.innerHTML = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
sleep(ms) {
|
||||||
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Noise Overlay (ported from hermes-chat NoiseOverlay) ---
|
||||||
|
function initNoiseOverlay() {
|
||||||
|
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;
|
||||||
|
if (typeof THREE === "undefined") return;
|
||||||
|
|
||||||
|
const canvas = document.getElementById("noise-overlay");
|
||||||
|
if (!canvas) return;
|
||||||
|
|
||||||
|
const vertexShader = `
|
||||||
|
varying vec2 vUv;
|
||||||
|
void main() {
|
||||||
|
vUv = uv;
|
||||||
|
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const fragmentShader = `
|
||||||
|
uniform vec2 uRes;
|
||||||
|
uniform float uDpr, uSize, uDensity, uOpacity;
|
||||||
|
uniform vec3 uColor;
|
||||||
|
varying vec2 vUv;
|
||||||
|
|
||||||
|
float hash(vec2 p) {
|
||||||
|
vec3 p3 = fract(vec3(p.xyx) * 0.1031);
|
||||||
|
p3 += dot(p3, p3.yzx + 33.33);
|
||||||
|
return fract((p3.x + p3.y) * p3.z);
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
float n = hash(floor(vUv * uRes / (uSize * uDpr)));
|
||||||
|
gl_FragColor = vec4(uColor, step(1.0 - uDensity, n)) * uOpacity;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
function hexToVec3(hex) {
|
||||||
|
const c = hex.replace("#", "");
|
||||||
|
return new THREE.Vector3(
|
||||||
|
parseInt(c.substring(0, 2), 16) / 255,
|
||||||
|
parseInt(c.substring(2, 4), 16) / 255,
|
||||||
|
parseInt(c.substring(4, 6), 16) / 255,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const renderer = new THREE.WebGLRenderer({
|
||||||
|
alpha: true,
|
||||||
|
canvas,
|
||||||
|
premultipliedAlpha: false,
|
||||||
|
});
|
||||||
|
renderer.setClearColor(0x000000, 0);
|
||||||
|
|
||||||
|
const scene = new THREE.Scene();
|
||||||
|
const camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);
|
||||||
|
const geo = new THREE.PlaneGeometry(2, 2);
|
||||||
|
|
||||||
|
const mat = new THREE.ShaderMaterial({
|
||||||
|
vertexShader,
|
||||||
|
fragmentShader,
|
||||||
|
transparent: true,
|
||||||
|
uniforms: {
|
||||||
|
uColor: { value: hexToVec3("#8090BB") },
|
||||||
|
uDensity: { value: 0.1 },
|
||||||
|
uDpr: { value: 1 },
|
||||||
|
uOpacity: { value: 0.4 },
|
||||||
|
uRes: { value: new THREE.Vector2() },
|
||||||
|
uSize: { value: 1.0 },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
scene.add(new THREE.Mesh(geo, mat));
|
||||||
|
|
||||||
|
function resize() {
|
||||||
|
const dpr = window.devicePixelRatio;
|
||||||
|
const w = window.innerWidth;
|
||||||
|
const h = window.innerHeight;
|
||||||
|
renderer.setSize(w, h);
|
||||||
|
renderer.setPixelRatio(dpr);
|
||||||
|
mat.uniforms.uRes.value.set(w * dpr, h * dpr);
|
||||||
|
mat.uniforms.uDpr.value = dpr;
|
||||||
|
}
|
||||||
|
|
||||||
|
resize();
|
||||||
|
window.addEventListener("resize", resize);
|
||||||
|
|
||||||
|
function loop() {
|
||||||
|
requestAnimationFrame(loop);
|
||||||
|
renderer.render(scene, camera);
|
||||||
|
}
|
||||||
|
loop();
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Initialize ---
|
||||||
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
const detectedPlatform = detectPlatform();
|
||||||
|
switchPlatform(detectedPlatform);
|
||||||
|
|
||||||
|
initScrollAnimations();
|
||||||
|
initNoiseOverlay();
|
||||||
|
|
||||||
|
const terminalEl = document.getElementById("terminal-demo");
|
||||||
|
|
||||||
|
if (terminalEl) {
|
||||||
|
const demo = new TerminalDemo(terminalEl);
|
||||||
|
|
||||||
|
const observer = new IntersectionObserver(
|
||||||
|
(entries) => {
|
||||||
|
entries.forEach((entry) => {
|
||||||
|
if (entry.isIntersecting) {
|
||||||
|
demo.start();
|
||||||
|
} else {
|
||||||
|
demo.stop();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{ threshold: 0.3 },
|
||||||
|
);
|
||||||
|
|
||||||
|
observer.observe(document.querySelector(".terminal-window"));
|
||||||
|
}
|
||||||
|
|
||||||
|
const nav = document.querySelector(".nav");
|
||||||
|
let ticking = false;
|
||||||
|
window.addEventListener("scroll", () => {
|
||||||
|
if (!ticking) {
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
if (window.scrollY > 50) {
|
||||||
|
nav.style.borderBottomColor = "rgba(48, 80, 255, 0.15)";
|
||||||
|
} else {
|
||||||
|
nav.style.borderBottomColor = "";
|
||||||
|
}
|
||||||
|
ticking = false;
|
||||||
|
});
|
||||||
|
ticking = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||