<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Documentation · Distribute</title>
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link href="https://fonts.googleapis.com/css2?family=Manrope:wght@400;600;700;800&family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
    <link rel="stylesheet" href="/static/css/style.css">
    <style>
:root{--bg:#faf9f7;--sl:#ffffff;--s:#edeeeb;--sh:#e6e9e6;--card:#ffffff;--dark:#2f3331;--primary:#5f5e5e;--on-primary:#faf7f6;--secondary:#675e4f;--sec-ctr:#eee1ce;--on-surface:#2f3331;--muted:#5c605d;--outline:rgba(175,179,176,0.18);--r:8px}
*,*::before,*::after{box-sizing:border-box}
body{background:var(--bg);color:var(--on-surface);font-family:'Inter',sans-serif;-webkit-font-smoothing:antialiased;margin:0}
h1,h2,h3{font-family:'Manrope',sans-serif}
.docs-layout{display:flex;min-height:100vh}
/* Sidebar */
.docs-sidebar{width:256px;flex-shrink:0;position:sticky;top:0;height:100vh;overflow-y:auto;background:var(--bg);border-right:1px solid var(--sl);display:flex;flex-direction:column;padding:28px 20px 32px;z-index:10}
.docs-sidebar-brand{margin-bottom:28px;padding-left:4px}
.docs-sidebar-brand h2{font-size:14px;font-weight:800;letter-spacing:-0.01em;color:var(--on-surface);margin:0 0 2px}
.docs-sidebar-brand p{font-size:11px;color:var(--muted);margin:0}
.docs-nav{flex:1;display:flex;flex-direction:column;gap:2px}
.docs-nav-group{font-size:9px;font-weight:800;letter-spacing:.14em;text-transform:uppercase;color:var(--muted);padding:14px 12px 6px;margin-top:4px}
.docs-nav-group:first-child{padding-top:0;margin-top:0}
.docs-nav-link{display:flex;align-items:center;gap:10px;padding:8px 12px;border-radius:var(--r);font-size:13px;font-weight:500;color:var(--muted);text-decoration:none;cursor:pointer;border:none;background:transparent;text-align:left;width:100%;transition:all .15s;font-family:'Inter',sans-serif}
.docs-nav-link svg{color:var(--muted);flex-shrink:0;transition:color .15s}
.docs-nav-link:hover{background:var(--sl);color:var(--on-surface)}
.docs-nav-link:hover svg{color:var(--secondary)}
.docs-nav-link.active{background:var(--card);color:var(--on-surface);font-weight:700;transform:translateX(3px);box-shadow:0 1px 4px rgba(47,51,49,.06)}
.docs-nav-link.active svg{color:var(--on-surface)}
.docs-sidebar-foot{padding-top:20px;border-top:1px solid var(--sl);display:flex;flex-direction:column;gap:4px;margin-top:16px}
.docs-cta-btn{width:100%;padding:9px;background:var(--sec-ctr);color:var(--secondary);border:none;border-radius:var(--r);font-size:11px;font-weight:800;letter-spacing:.06em;text-transform:uppercase;cursor:pointer;font-family:inherit;transition:opacity .15s;margin-bottom:8px}
.docs-cta-btn:hover{opacity:.8}
/* Main */
.docs-main{flex:1;min-width:0;background:var(--bg)}
.docs-content-wrap {
  max-width: 100% !important;
  margin: 0 auto;
  padding: 52px 40px 100px;
}
/* Breadcrumb */
.docs-breadcrumb{display:flex;align-items:center;gap:6px;font-size:10px;font-weight:700;letter-spacing:.14em;text-transform:uppercase;color:var(--muted);margin-bottom:32px}
.docs-breadcrumb a{color:var(--muted);text-decoration:none}
.docs-breadcrumb-sep{color:#afb3b0}
.docs-breadcrumb-cur{color:var(--on-surface)}
/* Hero header */
.docs-hero{display:flex;align-items:flex-end;justify-content:space-between;gap:32px;margin-bottom:52px;flex-wrap:wrap}
.docs-hero-text h1{font-size:clamp(2.2rem,4vw,3rem);font-weight:800;letter-spacing:-0.05em;color:var(--on-surface);margin:0 0 14px;line-height:1.05}
.docs-hero-text p{font-size:15px;color:var(--muted);line-height:1.7;margin:0;max-width:420px}
.docs-hero-visual{width:96px;height:96px;border-radius:18px;flex-shrink:0;background:linear-gradient(135deg,#5f5e5e 0%,#d6d4d3 100%);display:flex;align-items:center;justify-content:center}
.docs-hero-visual svg{color:var(--on-primary)}
/* Bento */
.docs-bento{display:grid;grid-template-columns:2fr 1fr;gap:12px;margin-bottom:52px}
.docs-bento-card{background:var(--sl);border-radius:14px;padding:28px 28px 24px}
.docs-bento-card--white{background:var(--card)}
.docs-bento-card-label{font-size:9px;font-weight:800;letter-spacing:.14em;text-transform:uppercase;color:var(--muted);margin-bottom:14px;display:flex;align-items:center;justify-content:space-between}
.docs-version-badge{padding:2px 9px;background:var(--sec-ctr);color:var(--secondary);border-radius:99px;font-size:9px;font-weight:800;letter-spacing:.08em}
.docs-bento-card p{font-size:13px;color:var(--muted);line-height:1.7;margin:0 0 16px}
/* Terminal */
.term-block{background:var(--dark);border-radius:10px;overflow:hidden;position:relative}
.term-block-bar{display:flex;align-items:center;justify-content:space-between;padding:10px 14px 8px;background:rgba(255,255,255,.04);border-bottom:1px solid rgba(255,255,255,.06)}
.term-dots{display:flex;gap:6px}
.term-dot{width:9px;height:9px;border-radius:50%}
.term-dot-r{background:rgba(255,95,86,.6)}.term-dot-y{background:rgba(255,189,46,.6)}.term-dot-g{background:rgba(39,201,63,.6)}
.term-copy{background:none;border:none;cursor:pointer;color:rgba(255,255,255,.35);font-size:11px;font-weight:600;font-family:'Inter',sans-serif;padding:2px 8px;border-radius:4px;transition:color .15s}
.term-copy:hover{color:rgba(255,255,255,.7)}
.term-body{padding:18px 20px 20px;font-family:'SF Mono','Fira Code','Consolas',monospace;font-size:12.5px;line-height:1.75;color:rgba(250,249,247,.85);overflow-x:auto}
.term-line{display:flex;align-items:baseline;white-space:pre;padding:2px 0}
.term-line + .term-line{margin-top:2px}
.t-prompt{color:rgba(255,255,255,.3);margin-right:8px}
.t-cmd{color:#faf9f7}
.t-out{color:rgba(250,249,247,.6);display:block}
.t-ok{color:#a8d8a8;display:block}
.t-url{color:#d4c4a8;text-decoration:underline;text-underline-offset:2px}
.t-cmt{color:rgba(255,255,255,.28);font-style:italic;display:block}
.t-err{color:#f48c8c;display:block}
/* Sections */
.docs-section{margin-bottom:56px;padding-bottom:56px;border-bottom:1px solid var(--sl)}
.docs-section:last-child{border-bottom:none}
.docs-table{width:100%;border-collapse:collapse;font-size:.85rem;margin:12px 0 20px}
.docs-table th{text-align:left;padding:8px 12px;font-size:.72rem;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:var(--muted);border-bottom:1.5px solid var(--sl)}
.docs-table td{padding:8px 12px;border-bottom:1px solid var(--sl);color:var(--muted);vertical-align:top}
.docs-table tr:last-child td{border-bottom:none}
.docs-section h2{font-size:1.6rem;font-weight:800;letter-spacing:-0.03em;margin:0 0 20px;color:var(--on-surface)}
.docs-section h3{font-size:1rem;font-weight:700;margin:28px 0 12px;color:var(--on-surface)}
.docs-section p{font-size:.9rem;color:var(--muted);line-height:1.7;margin:0 0 16px}
.code-inline{background:var(--sl);padding:2px 7px;border-radius:5px;font-family:'SF Mono','Fira Code','Consolas',monospace;font-size:.82em;color:var(--on-surface)}
/* Note box */
.note-box{background:var(--sec-ctr);border-radius:10px;padding:16px 20px;margin:16px 0}
.note-box p{font-size:.85rem;color:var(--secondary);margin:0;line-height:1.6}
/* Command table */
.cmd-table{width:100%;border-collapse:collapse;margin:16px 0;font-size:.85rem}
.cmd-table th{text-align:left;padding:10px 14px;font-size:9px;font-weight:800;letter-spacing:.12em;text-transform:uppercase;color:var(--muted);border-bottom:2px solid var(--sh)}
.cmd-table td{padding:10px 14px;border-bottom:1px solid var(--sl);vertical-align:top}
.cmd-table td:first-child{font-family:'SF Mono','Fira Code','Consolas',monospace;font-size:.82em;color:var(--on-surface);white-space:nowrap}
.cmd-table td:last-child{color:var(--muted);line-height:1.55}
/* Footer */
.docs-footer{padding-top:40px;border-top:1px solid var(--sl);display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:16px}
.docs-footer p{font-size:.8rem;color:var(--muted);margin:0}
.docs-footer-links{display:flex;gap:20px}
.docs-footer-links a{font-size:.8rem;color:var(--muted);text-decoration:none}
.docs-footer-links a:hover{color:var(--on-surface)}
    </style>
</head>
<body>

<div class="docs-layout">

  <!-- Sidebar -->
  <aside class="docs-sidebar">
    <div class="docs-sidebar-brand">
      <h2>Distribute Docs</h2>
      <p>v3.0 · Self-hosted platform</p>
    </div>
    <nav class="docs-nav">
      <div class="docs-nav-group">Getting Started</div>
      <button class="docs-nav-link active" onclick="scrollTo('intro')">
        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M12 8v4M12 16h.01"/></svg>
        Introduction
      </button>
      <button class="docs-nav-link" onclick="scrollTo('installation')">
        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
        Installation
      </button>
      <button class="docs-nav-link" onclick="scrollTo('authentication')">
        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0110 0v4"/></svg>
        Authentication
      </button>
      <div class="docs-nav-group">Deploying</div>
      <button class="docs-nav-link" onclick="scrollTo('deploying')">
        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M5 12h14M12 5l7 7-7 7"/></svg>
        Deploy Apps
      </button>
      <button class="docs-nav-link" onclick="scrollTo('ignorefile')">
        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>
        .distributeignore
      </button>
      <button class="docs-nav-link" onclick="scrollTo('lifecycle')">
        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>
        App Lifecycle
      </button>
      <div class="docs-nav-group">Automation</div>
      <button class="docs-nav-link" onclick="scrollTo('pipelines')">
        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="6" cy="6" r="3"/><circle cx="6" cy="18" r="3"/><circle cx="18" cy="12" r="3"/><path d="M6 9v6M9 6h3a6 6 0 010 12H9"/></svg>
        Pipelines (CI/CD)
      </button>
      <button class="docs-nav-link" onclick="scrollTo('functions')">
        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 17l6-6-6-6M12 19h8"/></svg>
        Functions
      </button>
      <button class="docs-nav-link" onclick="scrollTo('api-gateway')">
        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M8 9l3 3-3 3"/><line x1="13" y1="15" x2="16" y2="15"/><rect x="3" y="3" width="18" height="18" rx="2"/></svg>
        API Gateway
      </button>
      <div class="docs-nav-group">Tunnels</div>
      <button class="docs-nav-link" onclick="scrollTo('tunnel-http')">
        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 9l9-7 9 7v11a2 2 0 01-2 2H5a2 2 0 01-2-2z"/></svg>
        HTTP Tunnel
      </button>
      <button class="docs-nav-link" onclick="scrollTo('tunnel-tcp')">
        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 16.92v3a2 2 0 01-2.18 2 19.79 19.79 0 01-8.63-3.07A19.5 19.5 0 013.07 9.81a19.79 19.79 0 01-3.07-8.67A2 2 0 012 1h3a2 2 0 012 1.72 12.84 12.84 0 00.7 2.81 2 2 0 01-.45 2.11L6.91 8.92a16 16 0 006.29 6.29l1.28-1.28a2 2 0 012.11-.45 12.84 12.84 0 002.81.7A2 2 0 0122 16.92z"/></svg>
        TCP/TLS Tunnel
      </button>
      <div class="docs-nav-group">Features</div>
      <button class="docs-nav-link" onclick="scrollTo('terminal')">
        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="4 17 10 11 4 5"/><line x1="12" y1="19" x2="20" y2="19"/></svg>
        Terminal
      </button>
      <button class="docs-nav-link" onclick="scrollTo('pwa')">
        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="5" y="2" width="14" height="20" rx="2"/><line x1="12" y1="18" x2="12.01" y2="18"/></svg>
        PWA Builder
      </button>
      <button class="docs-nav-link" onclick="scrollTo('domains')">
        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 014 10 15.3 15.3 0 01-4 10 15.3 15.3 0 01-4-10 15.3 15.3 0 014-10z"/></svg>
        Custom Domains
      </button>
      <div class="docs-nav-group">Reference</div>
      <button class="docs-nav-link" onclick="scrollTo('commands')">
        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="4 17 10 11 4 5"/><line x1="12" y1="19" x2="20" y2="19"/></svg>
        CLI Reference
      </button>
      <button class="docs-nav-link" onclick="scrollTo('troubleshooting')">
        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>
        Troubleshooting
      </button>

      <div class="docs-nav-group">Programmatic</div>
      <a class="docs-nav-link" href="/docs/api" style="text-decoration:none">
        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/></svg>
        REST API
      </a>
      <a class="docs-nav-link" href="/docs/api#mcp" style="text-decoration:none">
        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"/></svg>
        MCP server
      </a>
      <a class="docs-nav-link" href="/dashboard#tokens" style="text-decoration:none">
        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 2l-2 2m-7.61 7.61a5.5 5.5 0 11-7.778 7.778 5.5 5.5 0 017.777-7.777zm0 0L15.5 7.5m0 0l3 3L22 7l-3-3m-3.5 3.5L19 4"/></svg>
        API tokens
      </a>
    </nav>
    <div class="docs-sidebar-foot">
      <button class="docs-cta-btn" onclick="window.location='/dashboard'">Open Dashboard</button>
    </div>
  </aside>

  <!-- Main -->
  <main class="docs-main">
    <div class="docs-content-wrap">

      <!-- Breadcrumb -->
      <div class="docs-breadcrumb">
        <a href="/">Distribute</a>
        <span class="docs-breadcrumb-sep">›</span>
        <span class="docs-breadcrumb-cur">Documentation</span>
      </div>

      <!-- Hero -->
      <div class="docs-hero">
        <div class="docs-hero-text">
          <h1>Documentation</h1>
          <p>Everything you need to deploy apps, expose local servers, manage infrastructure, and ship PWAs · from your terminal.</p>
        </div>
        <div class="docs-hero-visual">
          <svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><polyline points="4 17 10 11 4 5"/><line x1="12" y1="19" x2="20" y2="19"/></svg>
        </div>
      </div>

      <!-- Bento overview -->
      <div class="docs-bento">
        <div class="docs-bento-card">
          <div class="docs-bento-card-label">
            Quick start
            <span class="docs-version-badge">v1.0</span>
          </div>
          <p>Install the CLI, log in once, and deploy from any project folder. The platform handles containerization, SSL, and routing automatically.</p>
          <div class="term-block">
            <div class="term-block-bar"><div class="term-dots"><div class="term-dot term-dot-r"></div><div class="term-dot term-dot-y"></div><div class="term-dot term-dot-g"></div></div><button class="term-copy" onclick="copyTerm(this)">Copy</button></div>
            <div class="term-body">
<div class="term-line"><span class="t-prompt">$</span><span class="t-cmd">curl -fsSL https://distribute.app/install.sh | sh</span></div>
<div class="term-line"><span class="t-prompt">$</span><span class="t-cmd">distribute login</span></div>
<div class="term-line"><span class="t-prompt">$</span><span class="t-cmd">distribute .</span></div>
            </div>
          </div>
        </div>
        <div class="docs-bento-card docs-bento-card--white">
          <div class="docs-bento-card-label">What's included</div>
          <p>✓ Deploy any stack<br>✓ HTTP / TCP / TLS tunnels<br>✓ Browser terminal<br>✓ PWA builder<br>✓ Custom domains + SSL<br>✓ Start / stop / restart<br>✓ Admin panel</p>
        </div>
      </div>

      <!-- INTRODUCTION -->
      <div class="docs-section" id="intro">
        <h2>Introduction</h2>
        <p>Distribute is a self-hosted application deployment platform. You run it on your own server; your team uses the CLI or web dashboard to deploy, manage, and debug apps. It uses Docker for containerization and built-in reverse proxy for routing and SSL.</p>
        <p>There are four main workflows:</p>
        <p><strong>Deploy</strong> · package a local folder and deploy it as a Docker container with a public URL and SSL certificate.</p>
        <p><strong>Tunnel</strong> · expose a local server (running on your laptop) to the internet via a secure public URL, without deploying it. Works for HTTP apps, databases, and any TCP/TLS service.</p>
        <p><strong>Terminal</strong> · open a browser-based shell inside any deployed container to run commands, debug, or inspect logs.</p>
        <p><strong>PWA Builder</strong> · analyze any deployed web app and generate a PWA package (manifest, service worker, icons) plus Android and iOS builds.</p>

        <div style="background:#eee1ce;border-radius:10px;padding:18px 22px;margin-top:28px;display:flex;gap:14px;align-items:flex-start">
          <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#675e4f" stroke-width="2" style="flex-shrink:0;margin-top:2px"><polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/></svg>
          <div>
            <div style="font-family:'Manrope',sans-serif;font-weight:800;color:#2f3331;font-size:.96rem;margin-bottom:4px">Building on Distribute? Use the API.</div>
            <p style="margin:0;font-size:.86rem;color:#5c605d;line-height:1.6">
              The CLI shown on this page is one client of the public REST API. If you're scripting deployments from CI, integrating with Claude Desktop / Cursor via MCP, or building your own SDK, see the
              <a href="/docs/api" style="color:#2f3331;font-weight:700;text-decoration:underline">Developer API docs</a>
              for the <code style="background:rgba(103,94,79,.15);padding:1px 5px;border-radius:4px;font-family:monospace;font-size:.78rem">/api/v1/*</code> endpoints, scoped Bearer tokens, and the zero-dependency MCP server.
            </p>
          </div>
        </div>
      </div>

      <!-- INSTALLATION -->
      <div class="docs-section" id="installation">
        <h2>Installation</h2>
        <h3>macOS / Linux</h3>
        <div class="term-block">
          <div class="term-block-bar"><div class="term-dots"><div class="term-dot term-dot-r"></div><div class="term-dot term-dot-y"></div><div class="term-dot term-dot-g"></div></div><button class="term-copy" onclick="copyTerm(this,'curl -fsSL https://distribute.app/install.sh | sh')">Copy</button></div>
          <div class="term-body"><span class="t-prompt">$</span><span class="t-cmd">curl -fsSL https://distribute.app/install.sh | sh</span></div>
        </div>
        <p>The installer detects your OS, downloads the latest CLI from your Distribute server, installs it to <span class="code-inline">/usr/local/bin/distribute</span>, and saves your server URL to <span class="code-inline">~/.distribute/config.json</span>.</p>
        <h3>Manual install</h3>
        <div class="term-block">
          <div class="term-block-bar"><div class="term-dots"><div class="term-dot term-dot-r"></div><div class="term-dot term-dot-y"></div><div class="term-dot term-dot-g"></div></div></div>
          <div class="term-body">
<span class="t-cmt"># Download CLI directly</span>
<span class="t-prompt">$</span><span class="t-cmd">curl -fsSL https://distribute.app/cli/distribute -o distribute</span>
<span class="t-prompt">$</span><span class="t-cmd">chmod +x distribute && sudo mv distribute /usr/local/bin/</span>
          </div>
        </div>
        <div class="note-box"><p><strong>Requires:</strong> Python 3.8+, curl. The CLI is a single Python script with no external dependencies beyond the standard library and <span class="code-inline">requests</span>.</p></div>
      </div>

      <!-- AUTHENTICATION -->
      <div class="docs-section" id="authentication">
        <h2>Authentication</h2>
        <div class="term-block">
          <div class="term-block-bar"><div class="term-dots"><div class="term-dot term-dot-r"></div><div class="term-dot term-dot-y"></div><div class="term-dot term-dot-g"></div></div><button class="term-copy" onclick="copyTerm(this,'distribute login')">Copy</button></div>
          <div class="term-body"><span class="t-prompt">$</span><span class="t-cmd">distribute login</span></div>
        </div>
        <p>Running <span class="code-inline">distribute login</span> opens a browser window to your Distribute server. After signing in with Google, GitHub, or username/password, the tab closes automatically and the CLI stores a Bearer token in <span class="code-inline">~/.distribute/config.json</span>.</p>
        <p>The token is persisted on the server across restarts · you only need to log in once.</p>
        <div class="note-box"><p><strong>Token security:</strong> Your token is stored locally at <span class="code-inline">~/.distribute/config.json</span>. Keep this file private. To revoke access, delete the token from your dashboard under Account Settings.</p></div>
      </div>

      <!-- DEPLOYING -->
      <div class="docs-section" id="deploying">
        <h2>Deploying Apps</h2>
        <h3>Interactive deploy</h3>
        <p>Run <span class="code-inline">distribute .</span> from your project folder. The CLI connects to the server, lists your existing apps, and lets you redeploy one or create a new deployment.</p>
        <div class="term-block">
          <div class="term-block-bar"><div class="term-dots"><div class="term-dot term-dot-r"></div><div class="term-dot term-dot-y"></div><div class="term-dot term-dot-g"></div></div><button class="term-copy" onclick="copyTerm(this,'distribute .')">Copy</button></div>
          <div class="term-body">
<span class="t-prompt">$</span><span class="t-cmd">distribute .</span>
<span class="t-out">  Connected. 3 app(s) deployed.</span>
<span class="t-out">   1. ● my-api</span>
<span class="t-out">   2. ● storefront</span>
<span class="t-out">   3. + Deploy as new app</span>
<span class="t-out">  Select [1-3]: </span>
          </div>
        </div>
        <h3>Deploy with a specific name (skips picker)</h3>
        <div class="term-block">
          <div class="term-block-bar"><div class="term-dots"><div class="term-dot term-dot-r"></div><div class="term-dot term-dot-y"></div><div class="term-dot term-dot-g"></div></div></div>
          <div class="term-body"><span class="t-prompt">$</span><span class="t-cmd">distribute . --name my-awesome-api</span></div>
        </div>
        <h3>Supported stacks (auto-detected)</h3>
        <p>Docker Compose, Node.js, Python/Flask/Django, PHP, React, Vue, Angular, Next.js, Vite, Static HTML. If a <span class="code-inline">docker-compose.yml</span> or <span class="code-inline">Dockerfile</span> is present it takes precedence.</p>
      </div>

      <!-- DISTRIBUTEIGNORE -->
      <div class="docs-section" id="ignorefile">
        <h2>.distributeignore</h2>
        <p>Create a <span class="code-inline">.distributeignore</span> file in your project root to exclude files and directories from packaging. One pattern per line. Reduces upload size significantly.</p>
        <div class="term-block">
          <div class="term-block-bar"><div class="term-dots"><div class="term-dot term-dot-r"></div><div class="term-dot term-dot-y"></div><div class="term-dot term-dot-g"></div></div></div>
          <div class="term-body">
<span class="t-cmt"># .distributeignore</span>
<span class="t-out">node_modules</span>
<span class="t-out">.next</span>
<span class="t-out">dist</span>
<span class="t-out">public/videos</span>
<span class="t-out">.env.local</span>
<span class="t-out">*.log</span>
<span class="t-out">coverage</span>
          </div>
        </div>
        <p>The CLI also automatically skips: <span class="code-inline">node_modules</span> <span class="code-inline">.git</span> <span class="code-inline">__pycache__</span> <span class="code-inline">.next</span> <span class="code-inline">.nuxt</span> <span class="code-inline">venv</span> <span class="code-inline">android</span> <span class="code-inline">ios</span> <span class="code-inline">Pods</span> and files over 50 MB.</p>
      </div>

      <!-- LIFECYCLE -->
      <div class="docs-section" id="lifecycle">
        <h2>App Lifecycle</h2>
        <p>Each deployed app can be started, stopped, restarted, or deleted from the dashboard. These actions control the underlying Docker container directly.</p>
        <table class="cmd-table">
          <thead><tr><th>Action</th><th>Effect</th></tr></thead>
          <tbody>
            <tr><td>Start</td><td>Starts the Docker container. built-in reverse proxy begins routing traffic to it.</td></tr>
            <tr><td>Stop</td><td>Stops the container. built-in reverse proxy stops routing · no traffic reaches the app.</td></tr>
            <tr><td>Restart</td><td>Restarts the container in place. Brief interruption (~1-2s).</td></tr>
            <tr><td>Delete</td><td>Removes the container and all app metadata. Cannot be undone.</td></tr>
          </tbody>
        </table>
        <div class="note-box"><p><strong>Domain uniqueness:</strong> No two apps can share the same custom domain. Attempting to configure a domain already used by another app returns a 409 error.</p></div>
      </div>

      <!-- TUNNEL HTTP -->
      <div class="docs-section" id="tunnel-http">
        <h2>HTTP Tunnel</h2>
        <p>Expose a local HTTP server to the internet. Useful for sharing dev builds, testing webhooks, or demoing to clients without deploying.</p>
        <div class="term-block">
          <div class="term-block-bar"><div class="term-dots"><div class="term-dot term-dot-r"></div><div class="term-dot term-dot-y"></div><div class="term-dot term-dot-g"></div></div><button class="term-copy" onclick="copyTerm(this,'distribute http 3000')">Copy</button></div>
          <div class="term-body">
<span class="t-prompt">$</span><span class="t-cmd">distribute http 3000</span>
<span class="t-out"></span>
<span class="t-out">  Tunnel open</span>
<span class="t-out">  ────────────────────────────────────────────</span>
<span class="t-out">  Public URL  →  https://distribute.app/t/a3f82c1b</span>
<span class="t-out">  Forwarding  →  localhost:3000</span>
<span class="t-out">  Press Ctrl+C to stop</span>
<span class="t-out"></span>
<span class="t-out">  GET /  → 200</span>
<span class="t-out">  POST /api/login  → 201</span>
          </div>
        </div>
        <h3>How it works</h3>
        <p>The CLI opens a persistent long-polling connection to the server. Incoming HTTP requests to your public URL are forwarded to the CLI, which makes the request locally and returns the response. The server rewrites HTML responses to fix asset paths, <span class="code-inline">fetch()</span> calls, and redirects so that SPAs work correctly end-to-end.</p>
        <h3>Framework compatibility</h3>
        <table class="cmd-table">
          <thead><tr><th>Framework</th><th>Notes</th></tr></thead>
          <tbody>
            <tr><td>React CRA</td><td>Works. Static assets rewritten automatically.</td></tr>
            <tr><td>Vite (React/Vue)</td><td>Works. <span class="code-inline">/assets/</span> paths rewritten.</td></tr>
            <tr><td>Angular</td><td>Works. <span class="code-inline">&lt;base href&gt;</span> injected, absolute paths rewritten.</td></tr>
            <tr><td>Next.js</td><td>Works. <span class="code-inline">/_next/</span> paths rewritten.</td></tr>
            <tr><td>Vue CLI</td><td>Works. Static assets rewritten.</td></tr>
            <tr><td>Any HTML server</td><td>Works. Plain HTTP proxied as-is.</td></tr>
          </tbody>
        </table>
        <div class="note-box"><p><strong>HMR / live reload:</strong> WebSocket-based hot module reload is neutralized (replaced with a no-op) because it cannot be proxied through HTTP polling. Pages load correctly · they just won't auto-reload on file changes.</p></div>
        <p>Tunnels auto-expire after 1 hour. Ctrl+C closes the tunnel immediately.</p>
      </div>

      <!-- TUNNEL TCP -->
      <div class="docs-section" id="tunnel-tcp">
        <h2>TCP / TLS Tunnel</h2>
        <p>Expose any raw TCP or TLS service · databases, SSH, message brokers · via a public HTTP-based proxy. The tunnel bridges raw byte streams over HTTP polling.</p>
        <h3>TCP (plain)</h3>
        <div class="term-block">
          <div class="term-block-bar"><div class="term-dots"><div class="term-dot term-dot-r"></div><div class="term-dot term-dot-y"></div><div class="term-dot term-dot-g"></div></div></div>
          <div class="term-body">
<span class="t-cmt"># PostgreSQL</span>
<span class="t-prompt">$</span><span class="t-cmd">distribute tcp 5432</span>
<span class="t-out">  TCP Tunnel open</span>
<span class="t-out">  Public URL  →  https://distribute.app/tcp/f1c90a22</span>
<span class="t-out">  Forwarding  →  localhost:5432</span>
<span class="t-out">  ↔  Connection c3a8b1f0 opened → localhost:5432</span>
          </div>
        </div>
        <h3>TLS (SSL-wrapped)</h3>
        <div class="term-block">
          <div class="term-block-bar"><div class="term-dots"><div class="term-dot term-dot-r"></div><div class="term-dot term-dot-y"></div><div class="term-dot term-dot-g"></div></div></div>
          <div class="term-body">
<span class="t-cmt"># HTTPS local server</span>
<span class="t-prompt">$</span><span class="t-cmd">distribute tls 8443</span>
<span class="t-out">  TLS Tunnel open</span>
<span class="t-out">  Public URL  →  https://distribute.app/tcp/9d2e4b1a</span>
<span class="t-out">  Forwarding  →  localhost:8443 (TLS wrapped)</span>
          </div>
        </div>
        <h3>Common use cases</h3>
        <table class="cmd-table">
          <thead><tr><th>Command</th><th>Service</th></tr></thead>
          <tbody>
            <tr><td>distribute tcp 5432</td><td>PostgreSQL</td></tr>
            <tr><td>distribute tcp 3306</td><td>MySQL / MariaDB</td></tr>
            <tr><td>distribute tcp 6379</td><td>Redis</td></tr>
            <tr><td>distribute tcp 27017</td><td>MongoDB</td></tr>
            <tr><td>distribute tcp 22</td><td>SSH</td></tr>
            <tr><td>distribute tls 8443</td><td>Local HTTPS server</td></tr>
            <tr><td>distribute tls 5671</td><td>AMQPS (RabbitMQ)</td></tr>
            <tr><td>distribute tls 8883</td><td>MQTTS (MQTT broker)</td></tr>
          </tbody>
        </table>
      </div>

      <!-- TERMINAL -->
      <div class="docs-section" id="terminal">
        <h2>Browser Terminal</h2>
        <p>Every deployed app has a built-in terminal accessible from the dashboard. Click the <strong>&gt;_</strong> button on any app card to open a shell inside that container.</p>
        <div class="term-block">
          <div class="term-block-bar"><div class="term-dots"><div class="term-dot term-dot-r"></div><div class="term-dot term-dot-y"></div><div class="term-dot term-dot-g"></div></div></div>
          <div class="term-body">
<span class="t-ok">✓ Connected to distribute-my-api</span>
<span class="t-prompt">$</span><span class="t-cmd">python manage.py migrate</span>
<span class="t-out">Applying sessions.0001_initial... OK</span>
<span class="t-prompt">$</span><span class="t-cmd">cat .env | grep DATABASE</span>
<span class="t-out">DATABASE_URL=postgres://...</span>
<span class="t-prompt">$</span><span class="t-cmd">pip install psycopg2-binary</span>
<span class="t-out">Successfully installed psycopg2-binary-2.9.9</span>
          </div>
        </div>
        <p>The terminal starts in the app's working directory (typically <span class="code-inline">/app</span>). It uses HTTP polling so it works reliably behind proxies, CDNs, and restrictive firewalls · no WebSocket required.</p>
        <p>Press <span class="code-inline">Ctrl+C</span> to interrupt a running process. Close the modal to end the session.</p>
      </div>

      <!-- PWA BUILDER -->
      <div class="docs-section" id="pwa">
        <h2>PWA Builder</h2>
        <p>Navigate to <strong>Dashboard → PWA Builder</strong> and paste the URL of any web app. Distribute analyzes the site and generates a complete PWA package in seconds.</p>
        <h3>What gets generated</h3>
        <table class="cmd-table">
          <thead><tr><th>Output</th><th>Description</th></tr></thead>
          <tbody>
            <tr><td>manifest.json</td><td>Web App Manifest with name, icons, theme color, display mode</td></tr>
            <tr><td>sw.js</td><td>Service worker with offline-first cache strategy</td></tr>
            <tr><td>Icons (6 sizes)</td><td>192×192, 512×512, Apple touch icons, favicon</td></tr>
            <tr><td>Android APK</td><td>Installable APK for sideloading on Android devices</td></tr>
            <tr><td>iOS project</td><td>Capacitor project ready for Xcode build and App Store submission</td></tr>
          </tbody>
        </table>
        <p>The builder auto-detects the framework (React, Vue, Angular, Next.js, plain HTML) and adjusts the service worker caching strategy accordingly.</p>
      </div>

      <!-- CUSTOM DOMAINS -->
      <div class="docs-section" id="domains">
        <h2>Custom Domains</h2>
        <p>Each app can have one or more custom domains. Add them from the dashboard by clicking <strong>Domains</strong> on any app card.</p>
        <h3>DNS setup</h3>
        <p>Point your domain to your server with an A record:</p>
        <div class="term-block">
          <div class="term-block-bar"><div class="term-dots"><div class="term-dot term-dot-r"></div><div class="term-dot term-dot-y"></div><div class="term-dot term-dot-g"></div></div></div>
          <div class="term-body">
<span class="t-cmt"># In your DNS provider</span>
<span class="t-out">myapp.com     A    →  YOUR_SERVER_IP</span>
<span class="t-out">www.myapp.com CNAME →  myapp.com</span>
          </div>
        </div>
        <p>SSL is issued automatically via Let's Encrypt TLS-ALPN-01 challenge. No port 80 required. Certificate issuance typically completes within 2 minutes of DNS propagation.</p>
        <div class="note-box"><p><strong>Domain uniqueness:</strong> Each domain can only be assigned to one app. Attempting to use a domain already assigned elsewhere returns an error. Contact an admin to reassign.</p></div>
      </div>

      <!-- CLI REFERENCE -->
      <div class="docs-section" id="commands">
        <h2>CLI Reference</h2>
        <table class="cmd-table">
          <thead><tr><th>Command</th><th>Description</th></tr></thead>
          <tbody>
            <tr><td>distribute login</td><td>Authenticate via browser (device flow). Stores token locally.</td></tr>
            <tr><td>distribute .</td><td>Package current directory, pick app interactively, deploy.</td></tr>
            <tr><td>distribute . --name &lt;n&gt;</td><td>Deploy current directory with a specific app name (skips picker).</td></tr>
            <tr><td>distribute /path</td><td>Package and deploy a specific directory.</td></tr>
            <tr><td>distribute http &lt;port&gt;</td><td>Open an HTTP tunnel to localhost:&lt;port&gt;.</td></tr>
            <tr><td>distribute tcp &lt;port&gt;</td><td>Open a TCP tunnel (databases, SSH, etc.).</td></tr>
            <tr><td>distribute tls &lt;port&gt;</td><td>Open a TLS tunnel (SSL-wrapped services).</td></tr>
            <tr><td>distribute ls</td><td>List your deployed apps with status.</td></tr>
            <tr><td>distribute logs &lt;app&gt;</td><td>Tail container logs.</td></tr>
            <tr><td>distribute delete &lt;app&gt;</td><td>Delete an app and its container.</td></tr>
            <tr><td>distribute open &lt;app&gt;</td><td>Open the app URL in your browser.</td></tr>
          </tbody>
        </table>
        <h3>Config file</h3>
        <p>Stored at <span class="code-inline">~/.distribute/config.json</span>:</p>
        <div class="term-block">
          <div class="term-block-bar"><div class="term-dots"><div class="term-dot term-dot-r"></div><div class="term-dot term-dot-y"></div><div class="term-dot term-dot-g"></div></div></div>
          <div class="term-body">
<span class="t-out">{</span>
<span class="t-out">  "base_url": "https://distribute.app",</span>
<span class="t-out">  "token":    "your-auth-token",</span>
<span class="t-out">  "username": "acme_corp"</span>
<span class="t-out">}</span>
          </div>
        </div>
      </div>

      <!-- TROUBLESHOOTING -->
      <div class="docs-section" id="pipelines">
        <h2>Pipelines (CI/CD)</h2>
        <p>Pipelines auto-deploy your app on every git push. Connect your repository from the Pipelines panel or the CLI and choose a branch trigger.</p>

        <h3>Connect via dashboard</h3>
        <p>Go to <strong>Pipelines</strong> in the sidebar, click <strong>Connect a provider</strong>, authorize GitHub/GitLab/Bitbucket, then click <strong>Deploy</strong> or <strong>Link to app</strong> next to a repository.</p>
        <p>A webhook is automatically created on the repository. No manual configuration needed.</p>

        <h3>Deploy from a repo (CLI)</h3>
        <pre><code>distribute repo owner/myproject
distribute repo owner/myproject --name my-api
distribute repo owner/myproject --trigger main</code></pre>

        <h3>Link an existing app to a repo (CLI)</h3>
        <pre><code>distribute pipeline my-api link owner/myproject
distribute pipeline my-api link owner/myproject --trigger main
distribute pipeline my-api link owner/myproject --trigger any</code></pre>

        <h3>Trigger options</h3>
        <table class="docs-table"><thead><tr><th>Value</th><th>Behaviour</th></tr></thead><tbody>
          <tr><td><code>manual</code></td><td>Only via dashboard button or <code>distribute pipeline &lt;app&gt; trigger</code></td></tr>
          <tr><td><code>main</code></td><td>Auto-deploy on push to <code>main</code></td></tr>
          <tr><td><code>master</code></td><td>Auto-deploy on push to <code>master</code></td></tr>
          <tr><td><code>any</code></td><td>Auto-deploy on push to any branch</td></tr>
        </tbody></table>

        <h3>Pipeline commands (CLI)</h3>
        <pre><code>distribute pipeline my-api              # show status + recent builds
distribute pipeline my-api trigger       # trigger manual build
distribute pipeline my-api link o/repo   # link to repo
distribute pipeline my-api unlink        # remove pipeline</code></pre>

        <h3>Build flow</h3>
        <p>Each build: <code>git clone --depth=1</code> &rarr; <code>docker build</code> (no cache) &rarr; image tagged with commit SHA &rarr; old container replaced &rarr; new container started with same built-in reverse proxy labels. No downtime for built-in reverse proxy routing.</p>
      </div>

      <div class="docs-section" id="functions">
        <h2>Functions (Serverless)</h2>
        <p>Functions let you deploy lightweight event-driven code without managing containers. Each function runs in isolation and is invoked via HTTP.</p>

        <h3>Create a function</h3>
        <p>Go to <strong>Functions</strong> in the sidebar and click <strong>New function</strong>. Write your handler code in the editor. Functions support Python and Node.js.</p>

        <h3>Invoke a function</h3>
        <pre><code>POST https://distribute.app/function/&lt;name&gt;
Content-Type: application/json

{"key": "value"}</code></pre>

        <h3>CLI commands</h3>
        <pre><code>distribute fn ls                        # list functions
distribute fn logs my-fn                # view logs
distribute fn invoke my-fn              # invoke (empty payload)
distribute fn invoke my-fn --data '{"user": "alice"}'</code></pre>

        <h3>Expose a function as an API endpoint</h3>
        <p>Go to <strong>APIs</strong> and click <strong>New endpoint</strong>. Select type <strong>Function</strong>, pick your function, configure auth and the public URL. The endpoint will appear in the endpoints list.</p>
      </div>

      <div class="docs-section" id="api-gateway">
        <h2>API Gateway</h2>
        <p>The API Gateway lets you register, document and secure your deployed apps and functions as named API endpoints. Each endpoint can have rate limiting, API key auth and a custom URL.</p>

        <h3>Create an endpoint</h3>
        <p>Go to <strong>APIs</strong> and click <strong>New endpoint</strong>. Choose a type:</p>
        <table class="docs-table"><thead><tr><th>Type</th><th>Use case</th></tr></thead><tbody>
          <tr><td><strong>App</strong></td><td>Expose a deployed Docker app</td></tr>
          <tr><td><strong>Function</strong></td><td>Expose a serverless function</td></tr>
          <tr><td><strong>External</strong></td><td>Proxy to an external URL or internal service</td></tr>
        </tbody></table>
        <p>Set a custom endpoint name, choose a domain (subdomain or custom domain) and optional path prefix. Auth can be set to Public or API key required.</p>

        <h3>API Keys</h3>
        <p>Generate named API keys from the <strong>APIs</strong> panel or via CLI. Keys are shown once on creation.</p>
        <pre><code>distribute api keys                     # list keys
distribute api key create mobile-app   # generate new key
distribute api key delete &lt;id&gt;         # delete key</code></pre>

        <h3>Rate limiting</h3>
        <p>Click <strong>Configure</strong> on any endpoint to set requests-per-second and burst limits. Rate limiting is enforced by built-in reverse proxy and applied immediately.</p>

        <h3>CLI commands</h3>
        <pre><code>distribute api ls                       # list your endpoints</code></pre>
      </div>

      <div class="docs-section" id="env-vars">
        <h2>Environment Variables</h2>
        <p>Manage environment variables for your apps from the CLI or dashboard (Applications &rarr; app card &rarr; Settings).</p>
        <pre><code>distribute env my-api set DATABASE_URL=postgres://... SECRET=abc
distribute env my-api ls</code></pre>
        <p>Variables are injected into the container at runtime. The app will restart automatically after an update.</p>
      </div>

      <div class="docs-section" id="troubleshooting">
        <h2>Troubleshooting</h2>

        <h3>SSL certificate error on upload</h3>
        <div class="term-block">
          <div class="term-block-bar"><div class="term-dots"><div class="term-dot term-dot-r"></div><div class="term-dot term-dot-y"></div><div class="term-dot term-dot-g"></div></div></div>
          <div class="term-body">
<span class="t-err">✗ SSLError: certificate verify failed</span>
<span class="t-cmt"># Fix:</span>
<span class="t-prompt">$</span><span class="t-cmd">pip3 install --upgrade certifi</span>
          </div>
        </div>

        <h3>Upload fails with SSL EOF</h3>
        <div class="term-block">
          <div class="term-block-bar"><div class="term-dots"><div class="term-dot term-dot-r"></div><div class="term-dot term-dot-y"></div><div class="term-dot term-dot-g"></div></div></div>
          <div class="term-body">
<span class="t-err">✗ SSLEOFError: EOF occurred in violation of protocol</span>
<span class="t-cmt"># Caused by large package or slow upload. The CLI uses curl</span>
<span class="t-cmt"># automatically. If curl is missing, install it:</span>
<span class="t-prompt">$</span><span class="t-cmd">brew install curl  # macOS</span>
<span class="t-cmt"># Then reduce package size with .distributeignore</span>
          </div>
        </div>

        <h3>Tunnel assets return 404 (Angular/React)</h3>
        <p>This happens when the app uses absolute paths like <span class="code-inline">/styles.css</span> which don't include the tunnel prefix. The CLI server rewrites HTML automatically, but if you see 404s on assets try adding a <span class="code-inline">&lt;base href="/"&gt;</span> tag explicitly in your <span class="code-inline">index.html</span> · the tunnel proxy will replace it with the correct prefix.</p>

        <h3>Command not found</h3>
        <div class="term-block">
          <div class="term-block-bar"><div class="term-dots"><div class="term-dot term-dot-r"></div><div class="term-dot term-dot-y"></div><div class="term-dot term-dot-g"></div></div></div>
          <div class="term-body">
<span class="t-err">distribute: command not found</span>
<span class="t-cmt"># Fix:</span>
<span class="t-prompt">$</span><span class="t-cmd">export PATH="/usr/local/bin:$PATH"</span>
<span class="t-prompt">$</span><span class="t-cmd">source ~/.zshrc  # or ~/.bashrc</span>
          </div>
        </div>

        <h3>Already logged in but CLI says "not logged in"</h3>
        <p>The server was restarted and a previous version lost the in-memory token. Since v1.0 tokens are persisted to disk · after a fresh deploy this should not occur. If it does, simply run <span class="code-inline">distribute login</span> again.</p>
      </div>

      <!-- Footer -->
      <footer class="docs-footer">
        <p>Crafted with Intention · Distribute Platform</p>
        <div class="docs-footer-links">
          <a href="/">Home</a>
          <a href="/billing">Billing</a>
          <a href="/explore">Explore</a>
          <a href="/dashboard">Dashboard</a>
        </div>
      </footer>

    </div>
  </main>
</div>

<script>
function scrollTo(id) {
    const el = document.getElementById(id);
    if (el) el.scrollIntoView({ behavior: 'smooth', block: 'start' });
    document.querySelectorAll('.docs-nav-link').forEach(l => l.classList.remove('active'));
    event.currentTarget.classList.add('active');
}
window.addEventListener('scroll', () => {
    const ids = ['intro','installation','authentication','deploying','ignorefile','lifecycle','tunnel-http','tunnel-tcp','terminal','pwa','domains','commands','troubleshooting'];
    let current = '';
    ids.forEach(id => {
        const el = document.getElementById(id);
        if (el && el.getBoundingClientRect().top < 140) current = id;
    });
    document.querySelectorAll('.docs-nav-link').forEach(l => {
        const onclick = l.getAttribute('onclick') || '';
        const m = onclick.match(/scrollTo\('([^']+)'\)/);
        if (m) l.classList.toggle('active', m[1] === current);
    });
});
function copyTerm(btn, text) {
    if (!text) {
        const block = btn.closest('.term-block').querySelector('.term-body');
        // Build the command list from the .t-cmd spans so the "$" prompts are
        // never copied and each command lands on its OWN line. Scraping
        // innerText merged the lines, so pasting produced one concatenated
        // command like `sh distribute login distribute .` which fails.
        const cmds = block.querySelectorAll('.t-cmd');
        if (cmds.length) {
            text = Array.prototype.map.call(cmds, function (el) {
                return el.textContent.trim();
            }).filter(Boolean).join('\n');
        } else {
            text = block.innerText.replace(/^\s*\$\s?/mg, '').trim();
        }
    }
    navigator.clipboard.writeText(text).then(() => {
        const orig = btn.textContent;
        btn.textContent = '✓';
        setTimeout(() => btn.textContent = orig, 2000);
    });
}
</script>
</body>
</html>