793 lines
33 KiB
HTML
793 lines
33 KiB
HTML
<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
<title>herdr — tmux for agents</title>
|
|
<meta
|
|
name="description"
|
|
content="Persistent terminal multiplexer for coding agents. Detach and reattach, agents keep running. Built-in awareness for blocked, done, and working states. Lives inside your terminal."
|
|
/>
|
|
<meta name="theme-color" content="#11111b" />
|
|
<link rel="canonical" href="https://herdr.dev/" />
|
|
|
|
<meta property="og:type" content="website" />
|
|
<meta property="og:site_name" content="herdr" />
|
|
<meta property="og:title" content="herdr — tmux for agents" />
|
|
<meta
|
|
property="og:description"
|
|
content="Persistent terminal multiplexer for coding agents. Detach, reattach, agents keep running."
|
|
/>
|
|
<meta property="og:url" content="https://herdr.dev/" />
|
|
<meta
|
|
property="og:image"
|
|
content="https://herdr.dev/assets/og-card-v2.png"
|
|
/>
|
|
<meta property="og:image:width" content="1200" />
|
|
<meta property="og:image:height" content="630" />
|
|
|
|
<meta name="twitter:card" content="summary_large_image" />
|
|
<meta name="twitter:title" content="herdr — tmux for agents" />
|
|
<meta
|
|
name="twitter:description"
|
|
content="Persistent terminal multiplexer for coding agents. Detach, reattach, agents keep running."
|
|
/>
|
|
<meta
|
|
name="twitter:image"
|
|
content="https://herdr.dev/assets/og-card-v2.png"
|
|
/>
|
|
|
|
<link rel="icon" type="image/png" href="/assets/logo.png" />
|
|
<link rel="apple-touch-icon" href="/assets/logo.png" />
|
|
<link
|
|
rel="preload"
|
|
href="/assets/jbm.woff2"
|
|
as="font"
|
|
type="font/woff2"
|
|
crossorigin
|
|
/>
|
|
<link rel="stylesheet" href="/css/style.css?v=d0a150a" />
|
|
</head>
|
|
<body>
|
|
<!-- ── Hero ── -->
|
|
<section class="hero" id="hero">
|
|
<div class="hero-pasture" aria-hidden="true"></div>
|
|
<img src="/assets/logo.png" alt="herdr" class="hero-logo" />
|
|
<span class="hero-name">herdr</span>
|
|
<p class="hero-tagline">tmux for agents.</p>
|
|
<div class="hero-dots">
|
|
<span class="dot dot-blocked"></span>
|
|
<span class="dot dot-working"></span>
|
|
<span class="dot dot-done"></span>
|
|
<span class="dot dot-idle"></span>
|
|
</div>
|
|
<div class="manifesto">
|
|
<p>
|
|
<strong
|
|
>herdr is an agent multiplexer that lives in your
|
|
terminal.</strong
|
|
>
|
|
</p>
|
|
<p>
|
|
workspaces, tabs, panes. mouse-native: click, drag, split.
|
|
every agent at a glance:
|
|
<span class="text-blocked">blocked</span>,
|
|
<span class="text-working">working</span>,
|
|
<span class="text-done">done</span>. detach and reattach,
|
|
agents keep running. attach locally, over ssh, or as a thin
|
|
client to a remote server. no gui app, no electron, no
|
|
mac-only native wrapper. you see the agent's own terminal,
|
|
not someone's interpretation of it.
|
|
</p>
|
|
</div>
|
|
<div class="install-row">
|
|
<div class="install-box">
|
|
<span class="prompt">$</span>
|
|
<code id="install-cmd"
|
|
>curl -fsSL https://herdr.dev/install.sh | sh</code
|
|
>
|
|
</div>
|
|
<button class="copy-btn" id="copy-btn">copy</button>
|
|
</div>
|
|
<div class="hero-meta">
|
|
<a href="https://github.com/ogulcancelik/herdr">github</a> ·
|
|
<a
|
|
href="https://github.com/ogulcancelik/herdr/blob/master/CONFIGURATION.md"
|
|
>docs</a
|
|
>
|
|
·
|
|
<a
|
|
href="https://github.com/ogulcancelik/herdr/blob/master/SOCKET_API.md"
|
|
>api</a
|
|
>
|
|
· linux · macos · agpl-3.0
|
|
</div>
|
|
</section>
|
|
|
|
<div class="gap-lg"></div>
|
|
|
|
<!-- ── Demo ── -->
|
|
<div class="demo">
|
|
<div class="demo-frame">
|
|
<video
|
|
autoplay
|
|
muted
|
|
loop
|
|
playsinline
|
|
preload="auto"
|
|
poster="/assets/hero-screenshot.png"
|
|
>
|
|
<source src="/assets/demo.mp4" type="video/mp4" />
|
|
</video>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="gap-lg"></div>
|
|
<hr class="section-rule" />
|
|
<div class="gap"></div>
|
|
|
|
<!-- ── Comparison ── -->
|
|
<section class="section">
|
|
<div class="section-heading">
|
|
<span class="dot dot-idle"></span>
|
|
<h2>how it compares</h2>
|
|
</div>
|
|
<div class="table-wrap">
|
|
<table class="position-table">
|
|
<thead>
|
|
<tr>
|
|
<th></th>
|
|
<th>tmux</th>
|
|
<th>gui managers</th>
|
|
<th class="herdr-col">herdr</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr>
|
|
<td>persistent sessions</td>
|
|
<td><span class="mark-yes">✓</span></td>
|
|
<td><span class="mark">—</span></td>
|
|
<td class="herdr-col">
|
|
<span class="mark-yes">✓</span>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td>detach / reattach</td>
|
|
<td><span class="mark-yes">✓</span></td>
|
|
<td><span class="mark">—</span></td>
|
|
<td class="herdr-col">
|
|
<span class="mark-yes">✓</span>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td>panes, tabs, workspaces</td>
|
|
<td><span class="mark-yes">✓</span></td>
|
|
<td><span class="mark-yes">✓</span></td>
|
|
<td class="herdr-col">
|
|
<span class="mark-yes">✓</span>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td>agent awareness</td>
|
|
<td><span class="mark">—</span></td>
|
|
<td><span class="mark-yes">✓</span></td>
|
|
<td class="herdr-col">
|
|
<span class="mark-yes">✓</span>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td>lives in your terminal</td>
|
|
<td><span class="mark-yes">✓</span></td>
|
|
<td><span class="mark">—</span></td>
|
|
<td class="herdr-col">
|
|
<span class="mark-yes">✓</span>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td>real terminal views</td>
|
|
<td><span class="mark-yes">✓</span></td>
|
|
<td><span class="mark">—</span></td>
|
|
<td class="herdr-col">
|
|
<span class="mark-yes">✓</span>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td>mouse-native</td>
|
|
<td><span class="mark">—</span></td>
|
|
<td><span class="mark-yes">✓</span></td>
|
|
<td class="herdr-col">
|
|
<span class="mark-yes">✓</span>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td>lightweight binary</td>
|
|
<td><span class="mark-yes">✓</span></td>
|
|
<td><span class="mark">—</span></td>
|
|
<td class="herdr-col">
|
|
<span class="mark-yes">✓</span>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td>agents can orchestrate</td>
|
|
<td><span class="mark">?</span></td>
|
|
<td><span class="mark">?</span></td>
|
|
<td class="herdr-col">
|
|
<span class="mark-yes">✓</span>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</section>
|
|
|
|
<div class="gap"></div>
|
|
<hr class="section-rule" />
|
|
<div class="gap"></div>
|
|
|
|
<!-- ── Persistence ── -->
|
|
<section class="section">
|
|
<div class="section-heading">
|
|
<span class="dot dot-done"></span>
|
|
<h2>persistence</h2>
|
|
</div>
|
|
<div class="prose-block">
|
|
<p>
|
|
herdr runs as a background session server by default. run
|
|
your agents, split panes, do your work, then press
|
|
<strong>ctrl+b q</strong> to detach the client. close your
|
|
terminal or laptop; the panes keep running. open herdr again
|
|
and you reattach to the same session.
|
|
</p>
|
|
<p>
|
|
named sessions give each project or context its own panes,
|
|
tabs, workspaces, sockets, and runtime state while sharing
|
|
the same global config.
|
|
</p>
|
|
</div>
|
|
<div class="detach-flow" style="margin-top: 1.5rem">
|
|
<div class="detach-line">
|
|
<span class="prompt">$</span> <span class="cmd">herdr</span>
|
|
<span class="key">--session</span>
|
|
<span class="comment">work</span>
|
|
</div>
|
|
<div class="detach-line">
|
|
<span class="prompt">$</span> <span class="cmd">herdr</span>
|
|
<span class="key">session attach</span>
|
|
<span class="comment">side-project</span>
|
|
</div>
|
|
<div class="detach-line">
|
|
<span class="dim">
|
|
separate sessions, same config, persistent agents.</span
|
|
>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<div class="gap"></div>
|
|
<hr class="section-rule" />
|
|
<div class="gap"></div>
|
|
|
|
<!-- ── From anywhere ── -->
|
|
<section class="section">
|
|
<div class="section-heading">
|
|
<span class="dot dot-done"></span>
|
|
<h2>from anywhere</h2>
|
|
</div>
|
|
<div class="prose-block">
|
|
<p>
|
|
once the session is running, attach to it three ways. keep
|
|
the work on the machine that has the code, the keys, and the
|
|
agents. connect from wherever you are.
|
|
</p>
|
|
</div>
|
|
<div class="access-grid">
|
|
<div class="access-card">
|
|
<div class="access-kicker">server</div>
|
|
<h3>ssh in, run herdr</h3>
|
|
<p>
|
|
use it like tmux on a remote box. herdr starts the
|
|
session there, and your panes keep running after you
|
|
detach.
|
|
</p>
|
|
<div class="mini-flow">
|
|
<div>
|
|
<span class="prompt">$</span>
|
|
<span class="cmd">ssh</span>
|
|
<span class="comment">you@server</span>
|
|
</div>
|
|
<div>
|
|
<span class="prompt">$</span>
|
|
<span class="cmd">herdr</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="access-card">
|
|
<div class="access-kicker">phone</div>
|
|
<h3>ssh from mobile</h3>
|
|
<p>
|
|
narrow terminals get a responsive tui instead of a
|
|
squeezed desktop layout. spaces, tabs, and agents stay
|
|
reachable from a phone ssh client.
|
|
</p>
|
|
<div class="mini-flow">
|
|
<div>
|
|
<span class="prompt">$</span>
|
|
<span class="cmd">ssh</span>
|
|
<span class="comment">you@server</span>
|
|
</div>
|
|
<div>
|
|
<span class="prompt">$</span>
|
|
<span class="cmd">herdr</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="access-card">
|
|
<div class="access-kicker">thin client</div>
|
|
<h3>herdr --remote host</h3>
|
|
<p>
|
|
run herdr on your laptop and target any ssh config host.
|
|
herdr connects over ssh, bootstraps the remote server,
|
|
and streams the tui back to your local terminal.
|
|
</p>
|
|
<div class="mini-flow">
|
|
<div>
|
|
<span class="prompt">$</span>
|
|
<span class="cmd">herdr</span>
|
|
<span class="key">--remote</span>
|
|
<span class="comment">workbox</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="mobile-proof">
|
|
<figure class="phone-shot">
|
|
<div class="phone-shot-frame">
|
|
<img
|
|
src="/assets/mobile-terminal.jpeg"
|
|
alt="herdr responsive terminal view over ssh on a phone"
|
|
loading="lazy"
|
|
/>
|
|
</div>
|
|
<figcaption>terminal view over ssh</figcaption>
|
|
</figure>
|
|
<figure class="phone-shot">
|
|
<div class="phone-shot-frame">
|
|
<img
|
|
src="/assets/mobile-switcher.jpeg"
|
|
alt="herdr mobile switcher showing spaces tabs and agents on a phone"
|
|
loading="lazy"
|
|
/>
|
|
</div>
|
|
<figcaption>responsive switcher</figcaption>
|
|
</figure>
|
|
</div>
|
|
</section>
|
|
|
|
<div class="gap"></div>
|
|
<hr class="section-rule" />
|
|
<div class="gap"></div>
|
|
|
|
<!-- ── Awareness ── -->
|
|
<section class="section">
|
|
<div class="section-heading">
|
|
<span class="dot dot-working"></span>
|
|
<h2>agent awareness</h2>
|
|
</div>
|
|
<div class="awareness-split">
|
|
<div class="awareness-panel">
|
|
<div class="awareness-panel-img">
|
|
<img
|
|
src="/assets/sidebar-workspaces.png"
|
|
alt="workspaces"
|
|
/>
|
|
</div>
|
|
<div class="awareness-panel-text">
|
|
<h3>workspaces</h3>
|
|
<p>
|
|
each workspace rolls up to its most urgent state.
|
|
scan the full list in a glance.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<div class="awareness-panel">
|
|
<div class="awareness-panel-img">
|
|
<img src="/assets/sidebar-agents.png" alt="agents" />
|
|
</div>
|
|
<div class="awareness-panel-text">
|
|
<h3>agents</h3>
|
|
<p>
|
|
see which agents are running, blocked, or finished.
|
|
no checking panes one by one.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="state-bar">
|
|
<div class="state-bar-item">
|
|
<span class="dot dot-blocked"></span> blocked
|
|
</div>
|
|
<div class="state-bar-item">
|
|
<span class="dot dot-working"></span> working
|
|
</div>
|
|
<div class="state-bar-item">
|
|
<span class="dot dot-done"></span> done
|
|
</div>
|
|
<div class="state-bar-item">
|
|
<span class="dot dot-idle"></span> idle
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<div class="gap"></div>
|
|
<hr class="section-rule" />
|
|
<div class="gap"></div>
|
|
|
|
<!-- ── Terminal ── -->
|
|
<section class="section">
|
|
<div class="section-heading">
|
|
<span class="dot dot-idle"></span>
|
|
<h2>lives in your terminal</h2>
|
|
</div>
|
|
<div class="terminal-block">
|
|
<p>
|
|
not a gui window. not a web dashboard. not electron.
|
|
<strong
|
|
>herdr runs inside whatever terminal you already
|
|
use.</strong
|
|
>
|
|
a single rust binary, no dependencies, nothing to configure.
|
|
it even works inside tmux.
|
|
</p>
|
|
<div class="term-pills">
|
|
<span class="term-pill">ghostty</span>
|
|
<span class="term-pill">alacritty</span>
|
|
<span class="term-pill">kitty</span>
|
|
<span class="term-pill">wezterm</span>
|
|
<span class="term-pill">iterm2</span>
|
|
<span class="term-pill">tmux</span>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<div class="gap"></div>
|
|
<hr class="section-rule" />
|
|
<div class="gap"></div>
|
|
|
|
<!-- ── Agents ── -->
|
|
<section class="section">
|
|
<div class="section-heading">
|
|
<span class="dot dot-blocked"></span>
|
|
<h2>supported agents</h2>
|
|
</div>
|
|
<div class="table-wrap">
|
|
<table class="agents-table">
|
|
<thead>
|
|
<tr>
|
|
<th>agent</th>
|
|
<th>idle / done</th>
|
|
<th>working</th>
|
|
<th>blocked</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr>
|
|
<td><a href="https://pi.dev">pi</a></td>
|
|
<td><span class="check">✓</span></td>
|
|
<td><span class="check">✓</span></td>
|
|
<td><span class="partial">partial</span></td>
|
|
</tr>
|
|
<tr>
|
|
<td>
|
|
<a
|
|
href="https://docs.anthropic.com/en/docs/claude-code"
|
|
>claude code</a
|
|
>
|
|
</td>
|
|
<td><span class="check">✓</span></td>
|
|
<td><span class="check">✓</span></td>
|
|
<td><span class="check">✓</span></td>
|
|
</tr>
|
|
<tr>
|
|
<td>
|
|
<a href="https://github.com/openai/codex"
|
|
>codex</a
|
|
>
|
|
</td>
|
|
<td><span class="check">✓</span></td>
|
|
<td><span class="check">✓</span></td>
|
|
<td><span class="check">✓</span></td>
|
|
</tr>
|
|
<tr>
|
|
<td><a href="https://factory.ai">droid</a></td>
|
|
<td><span class="check">✓</span></td>
|
|
<td><span class="check">✓</span></td>
|
|
<td><span class="check">✓</span></td>
|
|
</tr>
|
|
<tr>
|
|
<td><a href="https://ampcode.com">amp</a></td>
|
|
<td><span class="check">✓</span></td>
|
|
<td><span class="check">✓</span></td>
|
|
<td><span class="check">✓</span></td>
|
|
</tr>
|
|
<tr>
|
|
<td>
|
|
<a href="https://github.com/anomalyco/opencode"
|
|
>opencode</a
|
|
>
|
|
</td>
|
|
<td><span class="check">✓</span></td>
|
|
<td><span class="check">✓</span></td>
|
|
<td><span class="check">✓</span></td>
|
|
</tr>
|
|
<tr>
|
|
<td>
|
|
<a
|
|
href="https://github.com/google-gemini/gemini-cli"
|
|
>gemini cli</a
|
|
>
|
|
</td>
|
|
<td colspan="3">
|
|
<span class="untested"
|
|
>detected, not fully tested</span
|
|
>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td>
|
|
<a href="https://cursor.com/cli"
|
|
>cursor agent</a
|
|
>
|
|
</td>
|
|
<td colspan="3">
|
|
<span class="untested"
|
|
>detected, not fully tested</span
|
|
>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td>
|
|
<a href="https://github.com/cline/cline"
|
|
>cline</a
|
|
>
|
|
</td>
|
|
<td colspan="3">
|
|
<span class="untested"
|
|
>detected, not fully tested</span
|
|
>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</section>
|
|
|
|
<div class="gap"></div>
|
|
<hr class="section-rule" />
|
|
<div class="gap"></div>
|
|
|
|
<!-- ── API ── -->
|
|
<section class="section">
|
|
<div class="section-heading">
|
|
<span class="dot dot-done"></span>
|
|
<h2>socket api</h2>
|
|
</div>
|
|
<div class="api-grid">
|
|
<div class="api-text">
|
|
<p>
|
|
<strong>agents can use herdr too.</strong> the local
|
|
unix socket lets agents create workspaces, split panes,
|
|
spawn other agents, read output, and wait for state
|
|
changes.
|
|
</p>
|
|
<p>
|
|
available as CLI commands or as a reusable agent skill.
|
|
</p>
|
|
<div class="api-links">
|
|
<a
|
|
href="https://github.com/ogulcancelik/herdr/blob/master/SOCKET_API.md"
|
|
>socket api →</a
|
|
>
|
|
<a
|
|
href="https://github.com/ogulcancelik/herdr/blob/master/SKILL.md"
|
|
>agent skill →</a
|
|
>
|
|
</div>
|
|
</div>
|
|
<div class="code-block">
|
|
<pre><span class="comment"># create a workspace and tab</span>
|
|
<span class="cmd">herdr</span> workspace create <span class="flag">--cwd</span> <span class="str">~/project</span> <span class="flag">--label</span> <span class="str">"api"</span>
|
|
<span class="cmd">herdr</span> tab create <span class="flag">--label</span> <span class="str">"logs"</span>
|
|
|
|
<span class="comment"># split a pane and run</span>
|
|
<span class="cmd">herdr</span> pane split <span class="str">1-1</span> <span class="flag">--direction</span> <span class="str">right</span>
|
|
<span class="cmd">herdr</span> pane run <span class="str">1-2</span> <span class="str">"npm test"</span>
|
|
|
|
<span class="comment"># wait for an agent</span>
|
|
<span class="cmd">herdr</span> wait agent-status <span class="str">1-1</span> <span class="flag">--status</span> <span class="str">done</span>
|
|
|
|
<span class="comment"># read output</span>
|
|
<span class="cmd">herdr</span> pane read <span class="str">1-2</span> <span class="flag">--source</span> <span class="str">recent</span> <span class="flag">--lines</span> <span class="str">50</span></pre>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<div class="gap-lg"></div>
|
|
|
|
<!-- ── Footer ── -->
|
|
<footer class="footer">
|
|
<p>
|
|
<a href="https://github.com/ogulcancelik/herdr">github</a> ·
|
|
<a
|
|
href="https://github.com/ogulcancelik/herdr/blob/master/CONFIGURATION.md"
|
|
>configuration</a
|
|
>
|
|
·
|
|
<a
|
|
href="https://github.com/ogulcancelik/herdr/blob/master/SOCKET_API.md"
|
|
>api</a
|
|
>
|
|
·
|
|
<a
|
|
href="https://github.com/ogulcancelik/herdr/releases"
|
|
>releases</a
|
|
>
|
|
</p>
|
|
</footer>
|
|
|
|
<script>
|
|
(function () {
|
|
"use strict";
|
|
|
|
var hero = document.getElementById("hero");
|
|
var pasture = hero.querySelector(".hero-pasture");
|
|
|
|
var SR = [
|
|
[" ,ww", " wWWWWWWW_)", " `WWWWWW'", " II II"],
|
|
[" ,ww", " wWWWWWWW_)", " `WWWWWW'", " II II"],
|
|
];
|
|
var SL = [
|
|
[" ww,", "(_WWWWWWWw", " 'WWWWWW`", " II II"],
|
|
[" ww,", "(_WWWWWWWw", " 'WWWWWW`", " II II"],
|
|
];
|
|
|
|
var heroW = hero.offsetWidth;
|
|
var heroH = hero.offsetHeight;
|
|
var heroRect = hero.getBoundingClientRect();
|
|
var mx = -9999,
|
|
my = -9999;
|
|
|
|
document.addEventListener("mousemove", function (e) {
|
|
mx = e.clientX - heroRect.left;
|
|
my = e.clientY - heroRect.top;
|
|
});
|
|
document.addEventListener("mouseleave", function () {
|
|
mx = -9999;
|
|
my = -9999;
|
|
});
|
|
|
|
window.addEventListener("resize", function () {
|
|
heroW = hero.offsetWidth;
|
|
heroH = hero.offsetHeight;
|
|
heroRect = hero.getBoundingClientRect();
|
|
});
|
|
window.addEventListener("scroll", function () {
|
|
heroRect = hero.getBoundingClientRect();
|
|
});
|
|
|
|
var flock = [];
|
|
var count = window.matchMedia("(max-width: 640px)").matches
|
|
? 3
|
|
: 5;
|
|
|
|
for (var i = 0; i < count; i++) {
|
|
var el = document.createElement("pre");
|
|
el.className = "sheep-el";
|
|
var opacity = 0.04 + Math.random() * 0.035;
|
|
var size = 9 + Math.random() * 3;
|
|
el.style.cssText =
|
|
"font-size:" +
|
|
size +
|
|
"px;color:rgba(203,166,247," +
|
|
opacity +
|
|
")";
|
|
pasture.appendChild(el);
|
|
|
|
flock.push({
|
|
el: el,
|
|
x: Math.random() * heroW,
|
|
y: 30 + Math.random() * (heroH - 80),
|
|
dir: Math.random() > 0.5 ? 1 : -1,
|
|
speed: 0.1 + Math.random() * 0.2,
|
|
frame: 0,
|
|
ft: 0,
|
|
fi: 400 + Math.random() * 300,
|
|
svx: 0,
|
|
svy: 0,
|
|
});
|
|
}
|
|
|
|
var last = 0;
|
|
function tick(ts) {
|
|
var dt = Math.min(ts - last, 100);
|
|
last = ts;
|
|
for (var i = 0; i < flock.length; i++) {
|
|
var s = flock[i];
|
|
s.ft += dt;
|
|
if (s.ft > s.fi) {
|
|
s.ft = 0;
|
|
s.frame ^= 1;
|
|
}
|
|
|
|
var dx = s.x - mx,
|
|
dy = s.y - my;
|
|
var d = Math.sqrt(dx * dx + dy * dy);
|
|
if (d < 120 && d > 1) {
|
|
var f = (1 - d / 120) * 0.4;
|
|
s.svx += (dx / d) * f;
|
|
s.svy += (dy / d) * f;
|
|
}
|
|
s.x += s.svx;
|
|
s.y += s.svy;
|
|
s.svx *= 0.97;
|
|
s.svy *= 0.97;
|
|
|
|
s.x += s.dir * s.speed * (dt / 16);
|
|
if (s.dir > 0 && s.x > heroW + 100) s.x = -100;
|
|
if (s.dir < 0 && s.x < -100) s.x = heroW + 100;
|
|
if (s.y < -40) s.y = heroH + 20;
|
|
if (s.y > heroH + 40) s.y = -20;
|
|
var frames = s.dir > 0 ? SR : SL;
|
|
s.el.textContent = frames[s.frame].join("\n");
|
|
s.el.style.transform =
|
|
"translate3d(" + s.x + "px," + s.y + "px,0)";
|
|
}
|
|
requestAnimationFrame(tick);
|
|
}
|
|
requestAnimationFrame(function (ts) {
|
|
last = ts;
|
|
tick(ts);
|
|
});
|
|
|
|
// Copy
|
|
var copyBtn = document.getElementById("copy-btn");
|
|
var installCmd = document.getElementById("install-cmd");
|
|
|
|
function fallbackCopy(text) {
|
|
var ta = document.createElement("textarea");
|
|
ta.value = text;
|
|
ta.style.cssText = "position:fixed;left:-9999px";
|
|
document.body.appendChild(ta);
|
|
ta.select();
|
|
document.execCommand("copy");
|
|
document.body.removeChild(ta);
|
|
}
|
|
|
|
function showCopied() {
|
|
copyBtn.textContent = "copied!";
|
|
copyBtn.classList.add("copied");
|
|
setTimeout(function () {
|
|
copyBtn.textContent = "copy";
|
|
copyBtn.classList.remove("copied");
|
|
}, 2000);
|
|
}
|
|
|
|
copyBtn.addEventListener("click", function () {
|
|
var text = installCmd.textContent;
|
|
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
navigator.clipboard
|
|
.writeText(text)
|
|
.then(showCopied)
|
|
.catch(function () {
|
|
fallbackCopy(text);
|
|
showCopied();
|
|
});
|
|
} else {
|
|
fallbackCopy(text);
|
|
showCopied();
|
|
}
|
|
});
|
|
})();
|
|
</script>
|
|
</body>
|
|
</html>
|