Files
the-other-dude/docs/website/index.html

737 lines
25 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>The Other Dude — MikroTik Fleet Management</title>
<meta name="description" content="MikroTik fleet management. Self-hosted. Source-available. Monitor devices, push configuration, track changes in git.">
<meta name="keywords" content="MikroTik, RouterOS, fleet management, network management, WinBox browser, MikroTik monitoring, MikroTik configuration, router management, self-hosted, open source, source-available">
<meta name="author" content="The Other Dude">
<meta name="robots" content="index, follow">
<meta name="google-site-verification" content="d2QVuWrLJlzOQPnA-SAJuvajEHGYbusvJ4eDdZbWSBU">
<meta name="theme-color" content="#eae7de">
<link rel="canonical" href="https://theotherdude.net/">
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'><rect x='2' y='2' width='60' height='60' rx='8' fill='none' stroke='%238B1A1A' stroke-width='2'/><rect x='6' y='6' width='52' height='52' rx='5' fill='none' stroke='%23F5E6C8' stroke-width='1.5'/><rect x='8' y='8' width='48' height='48' rx='4' fill='%238B1A1A' opacity='0.15'/><path d='M32 8 L56 32 L32 56 L8 32 Z' fill='none' stroke='%238B1A1A' stroke-width='2'/><path d='M32 13 L51 32 L32 51 L13 32 Z' fill='none' stroke='%23F5E6C8' stroke-width='1.5'/><path d='M32 18 L46 32 L32 46 L18 32 Z' fill='%238B1A1A'/><path d='M32 19 L38 32 L32 45 L26 32 Z' fill='%232A9D8F'/><path d='M19 32 L32 26 L45 32 L32 38 Z' fill='%23F5E6C8'/><circle cx='32' cy='32' r='5' fill='%238B1A1A'/><circle cx='32' cy='32' r='2.5' fill='%232A9D8F'/><path d='M10 10 L16 10 L10 16 Z' fill='%232A9D8F' opacity='0.7'/><path d='M54 10 L54 16 L48 10 Z' fill='%232A9D8F' opacity='0.7'/><path d='M10 54 L16 54 L10 48 Z' fill='%232A9D8F' opacity='0.7'/><path d='M54 54 L48 54 L54 48 Z' fill='%232A9D8F' opacity='0.7'/></svg>">
<!-- Open Graph -->
<meta property="og:type" content="website">
<meta property="og:title" content="The Other Dude — MikroTik Fleet Management">
<meta property="og:description" content="MikroTik fleet management. Self-hosted. Source-available.">
<meta property="og:url" content="https://theotherdude.net/">
<meta property="og:image" content="https://theotherdude.net/assets/og-image.png">
<meta property="og:site_name" content="The Other Dude">
<meta property="og:locale" content="en_US">
<!-- Twitter Card -->
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="The Other Dude — MikroTik Fleet Management">
<meta name="twitter:description" content="MikroTik fleet management. Self-hosted. Source-available.">
<meta name="twitter:image" content="https://theotherdude.net/assets/og-image.png">
<!-- Structured Data -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "SoftwareApplication",
"name": "The Other Dude",
"applicationCategory": "NetworkApplication",
"operatingSystem": "Linux, Docker",
"description": "MikroTik RouterOS fleet management. Self-hosted. Source-available under BSL 1.1.",
"url": "https://theotherdude.net",
"offers": {
"@type": "Offer",
"price": "0",
"priceCurrency": "USD"
},
"featureList": [
"Monitor device state across fleet",
"Push configuration with automatic rollback",
"Track config changes in git",
"Manage firmware versions",
"WinBox in the browser",
"VPN overlay for NAT traversal",
"Multi-tenant with row-level security",
"Zero-knowledge authentication (SRP-6a)"
],
"softwareRequirements": "Docker, PostgreSQL 17, Redis, NATS",
"softwareVersion": "9.7.1",
"license": "https://mariadb.com/bsl11/"
}
</script>
<!-- Organization Schema -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Organization",
"name": "The Other Dude",
"url": "https://theotherdude.net",
"logo": "https://theotherdude.net/assets/og-image.png",
"sameAs": [
"https://github.com/staack/the-other-dude"
]
}
</script>
<link rel="apple-touch-icon" href="/apple-touch-icon.png">
<link rel="stylesheet" href="style.css?v=3">
<style>
/* ================================================================
Warm Precision — Homepage overrides
Light mode only. Overrides the Deep Space base theme.
================================================================ */
:root {
--bg-deep: #eae7de;
--bg-primary: #eae7de;
--bg-surface: #f6f4ec;
--bg-elevated: #f0ede4;
--text-primary: #1a1810;
--text-secondary: #5e5a4e;
--text-muted: #8a8578;
--accent: #8a7a48;
--accent-hover: #7a6a38;
--accent-glow: rgba(138, 122, 72, 0.10);
--accent-secondary: #8a7a48;
--border: rgba(40, 36, 28, 0.12);
--border-accent: rgba(40, 36, 28, 0.20);
}
body {
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
font-size: 16px;
background: #eae7de;
color: #1a1810;
}
h1, h2, h3, h4, h5, h6 {
font-family: "Manrope", system-ui, sans-serif;
color: #1a1810;
}
::selection {
background: rgba(138, 122, 72, 0.20);
color: #1a1810;
}
/* ---- Nav overrides ---- */
.site-nav--dark {
background: rgba(234, 231, 222, 0.92);
backdrop-filter: blur(12px) saturate(180%);
-webkit-backdrop-filter: blur(12px) saturate(180%);
border-bottom: 1px solid rgba(40, 36, 28, 0.12);
}
.nav-logo { color: #1a1810; }
.nav-link { color: #5e5a4e; opacity: 1; }
.nav-link:hover { color: #1a1810; }
/* ---- Header section ---- */
.wp-header {
padding: 80px 0 48px;
text-align: left;
}
.wp-header h1 {
font-size: 1.75rem;
font-weight: 700;
letter-spacing: -0.02em;
margin-bottom: 12px;
color: #1a1810;
}
.wp-header .wp-tagline {
font-size: 1.05rem;
color: #5e5a4e;
margin-bottom: 24px;
line-height: 1.6;
}
.wp-header-links {
display: flex;
gap: 24px;
flex-wrap: wrap;
}
.wp-header-links a {
font-size: 0.9rem;
font-weight: 500;
color: #8a7a48;
text-decoration: none;
border-bottom: 1px solid transparent;
transition: border-color 0.15s ease;
}
.wp-header-links a:hover {
border-bottom-color: #8a7a48;
}
/* ---- Sections ---- */
.wp-section {
padding: 48px 0;
border-top: 1px solid rgba(40, 36, 28, 0.10);
}
.wp-section h2 {
font-size: 1.15rem;
font-weight: 600;
letter-spacing: -0.01em;
margin-bottom: 20px;
color: #1a1810;
}
.wp-section p {
color: #5e5a4e;
line-height: 1.7;
max-width: 640px;
}
/* ---- Capability list ---- */
.wp-list {
list-style: none;
padding: 0;
margin: 0;
max-width: 520px;
}
.wp-list li {
position: relative;
padding-left: 20px;
margin-bottom: 10px;
color: #5e5a4e;
font-size: 0.95rem;
line-height: 1.6;
}
.wp-list li::before {
content: "";
position: absolute;
left: 0;
top: 10px;
width: 6px;
height: 6px;
border-radius: 50%;
background: #8a7a48;
}
/* ---- Screenshots ---- */
.wp-screenshots {
padding: 48px 0;
border-top: 1px solid rgba(40, 36, 28, 0.10);
}
.wp-screenshots h2 {
font-size: 1.15rem;
font-weight: 600;
margin-bottom: 24px;
color: #1a1810;
}
.wp-screenshot-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 24px;
}
.wp-screenshot-card {
background: #f6f4ec;
border: 1px solid rgba(40, 36, 28, 0.12);
border-radius: 2px;
overflow: hidden;
position: relative;
cursor: pointer;
}
.wp-screenshot-card .ss-light,
.wp-screenshot-card .ss-dark {
width: 100%;
display: block;
}
.wp-screenshot-card .ss-dark {
position: absolute;
top: 0; left: 0;
opacity: 0;
transition: opacity 150ms linear;
}
.wp-screenshot-card:hover .ss-dark {
opacity: 1;
}
.wp-screenshot-caption {
padding: 8px 14px;
font-size: 0.75rem;
color: #8a8578;
font-weight: 500;
}
.wp-screenshot-expanded {
display: none;
position: fixed;
inset: 0;
z-index: 100;
background: rgba(14,13,11,0.85);
padding: 40px;
cursor: pointer;
align-items: center;
justify-content: center;
gap: 12px;
}
.wp-screenshot-expanded.active { display: flex; }
.wp-screenshot-expanded img {
max-width: 48%;
max-height: 90vh;
border-radius: 2px;
border: 1px solid rgba(180,170,150,0.15);
}
.wp-screenshot-expanded .ss-label {
position: absolute;
bottom: 16px;
font-size: 11px;
color: #948e80;
font-family: 'IBM Plex Mono', monospace;
}
.wp-screenshot-expanded .ss-label-left { left: 40px; }
.wp-screenshot-expanded .ss-label-right { right: 40px; }
/* ---- Caveats / what-it-is-not ---- */
.wp-caveats {
list-style: none;
padding: 0;
margin: 0;
}
.wp-caveats li {
color: #5e5a4e;
font-size: 0.95rem;
line-height: 1.6;
margin-bottom: 8px;
padding-left: 20px;
position: relative;
}
.wp-caveats li::before {
content: "\2014";
position: absolute;
left: 0;
color: #8a8578;
}
/* ---- Status table ---- */
.wp-status-table {
border-collapse: collapse;
font-size: 0.9rem;
max-width: 400px;
}
.wp-status-table td {
padding: 6px 0;
vertical-align: top;
}
.wp-status-table td:first-child {
color: #8a8578;
padding-right: 24px;
white-space: nowrap;
}
.wp-status-table td:last-child {
color: #1a1810;
}
/* ---- Quick start ---- */
.wp-quickstart {
background: #f6f4ec;
border: 1px solid rgba(40, 36, 28, 0.12);
border-radius: 2px;
padding: 20px 24px;
max-width: 640px;
margin-top: 16px;
overflow-x: auto;
}
.wp-quickstart code {
font-family: "IBM Plex Mono", "SF Mono", monospace;
font-size: 0.85rem;
color: #1a1810;
display: block;
line-height: 1.8;
white-space: pre;
}
.wp-quickstart .wp-comment {
color: #8a8578;
}
.wp-requirements {
margin-top: 12px;
font-size: 0.85rem;
color: #8a8578;
}
/* ---- Footer overrides ---- */
.site-footer {
background: #f0ede4;
border-top: 1px solid rgba(40, 36, 28, 0.12);
}
.footer-brand { color: #1a1810; }
.footer-copy { color: #8a8578; }
.footer-links a { color: #5e5a4e; }
.footer-links a:hover { color: #1a1810; }
/* ---- Lightbox ---- */
.lightbox {
background: rgba(26, 24, 16, 0.92);
}
/* ---- Responsive ---- */
@media (max-width: 768px) {
.wp-screenshot-grid {
grid-template-columns: 1fr;
}
.wp-header {
padding: 48px 0 32px;
}
.wp-header h1 {
font-size: 1.5rem;
}
}
@media (max-width: 480px) {
.wp-header-links {
gap: 16px;
}
.wp-section {
padding: 32px 0;
}
}
/* ---- Kill inherited dark-theme artifacts ---- */
.hero, .hero-bg, .hero-bg::before, .hero-bg::after,
.testing-banner, .features-section, .cta-section,
.quickstart-section, .screenshots-section {
display: none !important;
}
/* Skip link */
.skip-link {
color: #1a1810;
background: #f6f4ec;
}
</style>
</head>
<body>
<a href="#main-content" class="skip-link">Skip to main content</a>
<!-- Navigation -->
<nav class="site-nav" aria-label="Main navigation">
<div class="nav-inner container">
<a href="index.html" class="nav-logo">
<svg class="nav-logo-mark" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="32" height="32" aria-hidden="true">
<rect x="2" y="2" width="60" height="60" rx="8" fill="none" stroke="#8B1A1A" stroke-width="2"/>
<rect x="6" y="6" width="52" height="52" rx="5" fill="none" stroke="#F5E6C8" stroke-width="1.5"/>
<rect x="8" y="8" width="48" height="48" rx="4" fill="#8B1A1A" opacity="0.15"/>
<path d="M32 8 L56 32 L32 56 L8 32 Z" fill="none" stroke="#8B1A1A" stroke-width="2"/>
<path d="M32 13 L51 32 L32 51 L13 32 Z" fill="none" stroke="#F5E6C8" stroke-width="1.5"/>
<path d="M32 18 L46 32 L32 46 L18 32 Z" fill="#8B1A1A"/>
<path d="M32 19 L38 32 L32 45 L26 32 Z" fill="#2A9D8F"/>
<path d="M19 32 L32 26 L45 32 L32 38 Z" fill="#F5E6C8"/>
<circle cx="32" cy="32" r="5" fill="#8B1A1A"/>
<circle cx="32" cy="32" r="2.5" fill="#2A9D8F"/>
<path d="M10 10 L16 10 L10 16 Z" fill="#2A9D8F" opacity="0.7"/>
<path d="M54 10 L54 16 L48 10 Z" fill="#2A9D8F" opacity="0.7"/>
<path d="M10 54 L16 54 L10 48 Z" fill="#2A9D8F" opacity="0.7"/>
<path d="M54 54 L48 54 L54 48 Z" fill="#2A9D8F" opacity="0.7"/>
</svg>
<span>The Other Dude</span>
</a>
<div class="nav-links">
<a href="docs.html" class="nav-link">Docs</a>
<a href="blog/" class="nav-link">Blog</a>
<a href="https://github.com/staack/the-other-dude" class="nav-link" rel="noopener">GitHub</a>
</div>
</div>
</nav>
<main id="main-content">
<!-- 1. Header -->
<div class="container">
<header class="wp-header">
<h1>The Other Dude</h1>
<p class="wp-tagline">MikroTik fleet management. Self-hosted. Source-available.</p>
<div class="wp-header-links">
<a href="https://github.com/staack/the-other-dude" rel="noopener">GitHub</a>
<a href="docs.html">Documentation</a>
<a href="blog/">Blog</a>
</div>
</header>
<!-- 2. What it does -->
<section class="wp-section" id="what-it-does">
<h2>What it does</h2>
<ul class="wp-list">
<li>Monitor device state across your fleet</li>
<li>Push configuration with automatic rollback</li>
<li>Track config changes in git</li>
<li>Manage firmware versions</li>
<li>WinBox in the browser</li>
<li>VPN overlay for NAT traversal</li>
<li>Multi-tenant with row-level security</li>
<li>Zero-knowledge authentication (SRP-6a)</li>
</ul>
</section>
</div>
<!-- 3. Screenshots -->
<section class="wp-screenshots">
<div class="container">
<h2>Screenshots</h2>
<div class="wp-screenshot-grid">
<figure class="wp-screenshot-card" data-light="assets/01-overview-light.png" data-dark="assets/01-overview-dark.png" onclick="expandScreenshot(this)">
<img class="ss-light" src="assets/01-overview-light.png" alt="Fleet overview" loading="lazy" width="1440" height="900">
<img class="ss-dark" src="assets/01-overview-dark.png" alt="Fleet overview — dark" loading="lazy" width="1440" height="900">
<figcaption class="wp-screenshot-caption">Fleet overview</figcaption>
</figure>
<figure class="wp-screenshot-card" data-light="assets/02-device-detail-light.png" data-dark="assets/02-device-detail-dark.png" onclick="expandScreenshot(this)">
<img class="ss-light" src="assets/02-device-detail-light.png" alt="Device management" loading="lazy" width="1440" height="900">
<img class="ss-dark" src="assets/02-device-detail-dark.png" alt="Device management — dark" loading="lazy" width="1440" height="900">
<figcaption class="wp-screenshot-caption">Device management</figcaption>
</figure>
<figure class="wp-screenshot-card" data-light="assets/03-interfaces-light.png" data-dark="assets/03-interfaces-dark.png" onclick="expandScreenshot(this)">
<img class="ss-light" src="assets/03-interfaces-light.png" alt="Interface configuration" loading="lazy" width="1440" height="900">
<img class="ss-dark" src="assets/03-interfaces-dark.png" alt="Interface configuration — dark" loading="lazy" width="1440" height="900">
<figcaption class="wp-screenshot-caption">Interface configuration</figcaption>
</figure>
<figure class="wp-screenshot-card" data-light="assets/04-firmware-light.png" data-dark="assets/04-firmware-dark.png" onclick="expandScreenshot(this)">
<img class="ss-light" src="assets/04-firmware-light.png" alt="Firmware management" loading="lazy" width="1440" height="900">
<img class="ss-dark" src="assets/04-firmware-dark.png" alt="Firmware management — dark" loading="lazy" width="1440" height="900">
<figcaption class="wp-screenshot-caption">Firmware management</figcaption>
</figure>
</div>
<div class="wp-screenshot-expanded" id="ss-expanded" onclick="this.classList.remove('active')">
<img id="ss-exp-light" src="" alt="Light mode">
<img id="ss-exp-dark" src="" alt="Dark mode">
<span class="ss-label ss-label-left">light</span>
<span class="ss-label ss-label-right">dark</span>
</div>
</div>
</div>
</section>
<div class="container">
<!-- 4. What it is not -->
<section class="wp-section">
<h2>What it is not</h2>
<ul class="wp-caveats">
<li>Not finished</li>
<li>Not stable</li>
<li>Not for everyone</li>
<li>Things break, APIs change. That is intentional before v11.</li>
</ul>
</section>
<!-- 5. Status -->
<section class="wp-section">
<h2>Status</h2>
<table class="wp-status-table">
<tr><td>Version</td><td>9.7.1</td></tr>
<tr><td>License</td><td>BSL 1.1 (converts to Apache 2.0 in 2030)</td></tr>
<tr><td>Free tier</td><td>250 devices</td></tr>
<tr><td>Stability</td><td>Breaking changes expected before v11</td></tr>
</table>
</section>
<!-- 6. Setup -->
<section class="wp-section">
<h2>Setup</h2>
<p>Requires Docker and PostgreSQL. See the <a href="docs.html#quickstart" style="color:#8a7a48;text-decoration:underline;text-underline-offset:3px">documentation</a> for full setup instructions.</p>
<div class="wp-quickstart">
<code><span class="wp-comment"># clone and run the setup wizard</span>
git clone https://github.com/staack/the-other-dude.git
cd the-other-dude
python3 setup.py</code>
</div>
<p class="wp-requirements">The setup wizard handles database, cryptographic keys, OpenBao, reverse proxy, and Docker images.</p>
</section>
</div>
</main>
<!-- Footer -->
<footer class="site-footer">
<div class="footer-inner container">
<div class="footer-brand">
<span class="footer-logo">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="24" height="24" aria-hidden="true" style="vertical-align: middle; margin-right: 8px;">
<rect x="2" y="2" width="60" height="60" rx="8" fill="none" stroke="#8B1A1A" stroke-width="2"/>
<rect x="6" y="6" width="52" height="52" rx="5" fill="none" stroke="#F5E6C8" stroke-width="1.5"/>
<rect x="8" y="8" width="48" height="48" rx="4" fill="#8B1A1A" opacity="0.15"/>
<path d="M32 8 L56 32 L32 56 L8 32 Z" fill="none" stroke="#8B1A1A" stroke-width="2"/>
<path d="M32 13 L51 32 L32 51 L13 32 Z" fill="none" stroke="#F5E6C8" stroke-width="1.5"/>
<path d="M32 18 L46 32 L32 46 L18 32 Z" fill="#8B1A1A"/>
<path d="M32 19 L38 32 L32 45 L26 32 Z" fill="#2A9D8F"/>
<path d="M19 32 L32 26 L45 32 L32 38 Z" fill="#F5E6C8"/>
<circle cx="32" cy="32" r="5" fill="#8B1A1A"/>
<circle cx="32" cy="32" r="2.5" fill="#2A9D8F"/>
</svg>
The Other Dude
</span>
<span class="footer-copy">&copy; 2026 The Other Dude. All rights reserved.</span>
</div>
<nav class="footer-links" aria-label="Footer navigation">
<a href="docs.html">Docs</a>
<a href="blog/">Blog</a>
<a href="https://github.com/staack/the-other-dude" rel="noopener">GitHub</a>
<a href="mailto:license@theotherdude.net">Licensing</a>
</nav>
<p style="margin-top:12px;font-size:0.75em;color:#8a8578;text-align:center;">This site uses self-hosted, cookie-free analytics to measure page views and engagement. No personal data is collected or shared with third parties.</p>
</div>
</footer>
<!-- Lightbox -->
<div class="lightbox" id="lightbox" role="dialog" aria-label="Image preview">
<button class="lightbox-close" aria-label="Close image preview">&times;</button>
<img src="" alt="Screenshot preview">
<div class="lightbox-caption"></div>
</div>
<script src="script.js"></script>
<script>
(function() {
var lb = document.getElementById('lightbox');
var lbImg = lb.querySelector('img');
var lbCap = lb.querySelector('.lightbox-caption');
var lbClose = lb.querySelector('.lightbox-close');
function openLightbox(img) {
lbImg.src = img.src;
lbImg.alt = img.alt;
var card = img.closest('.screenshot-card');
var cap = card.querySelector('figcaption') || card.querySelector('div > div:first-child');
lbCap.textContent = cap ? cap.textContent : '';
lb.classList.add('active');
lbClose.focus();
}
function closeLightbox() {
lb.classList.remove('active');
}
document.querySelectorAll('.screenshot-card img').forEach(function(img) {
img.setAttribute('tabindex', '0');
img.setAttribute('role', 'button');
img.addEventListener('click', function() { openLightbox(img); });
img.addEventListener('keydown', function(e) {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
openLightbox(img);
}
});
});
lbClose.addEventListener('click', function(e) {
e.stopPropagation();
closeLightbox();
});
lb.addEventListener('click', closeLightbox);
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
closeLightbox();
document.getElementById('ss-expanded').classList.remove('active');
}
});
})();
function expandScreenshot(card) {
var overlay = document.getElementById('ss-expanded');
document.getElementById('ss-exp-light').src = card.dataset.light;
document.getElementById('ss-exp-dark').src = card.dataset.dark;
overlay.classList.add('active');
}
</script>
<script>
(function() {
var h = 'https://telemetry.theotherdude.net';
var p = location.pathname;
var t = document.title;
var r = document.referrer;
// Session page count via sessionStorage.
var sc = parseInt(sessionStorage.getItem('_tc_sc') || '0', 10) + 1;
sessionStorage.setItem('_tc_sc', sc);
// UTM params.
var sp = new URLSearchParams(location.search);
var us = sp.get('utm_source') || '';
var um = sp.get('utm_medium') || '';
var uc = sp.get('utm_campaign') || '';
// Pixel URL with all params.
var params = new URLSearchParams({
p: p, t: t, r: r,
sw: screen.width, sh: screen.height,
vw: innerWidth, vh: innerHeight,
tz: new Date().getTimezoneOffset(),
dpr: devicePixelRatio || 1,
touch: navigator.maxTouchPoints > 0 ? 1 : 0,
cd: screen.colorDepth,
plt: Math.round(performance.now()),
sc: sc
});
if (us) params.set('us', us);
if (um) params.set('um', um);
if (uc) params.set('uc', uc);
var ct = navigator.connection ? navigator.connection.effectiveType : '';
if (ct) params.set('ct', ct);
new Image().src = h + '/px?' + params.toString();
// Engagement tracking.
var startTime = performance.now();
var maxScroll = 0;
function getScrollDepth() {
var scrollTop = window.pageYOffset || document.documentElement.scrollTop;
var docHeight = Math.max(document.body.scrollHeight, document.documentElement.scrollHeight);
var winHeight = innerHeight;
if (docHeight <= winHeight) return 100;
var pct = Math.round((scrollTop + winHeight) / docHeight * 100);
return Math.min(pct, 100);
}
window.addEventListener('scroll', function() {
var d = getScrollDepth();
if (d > maxScroll) maxScroll = d;
}, {passive: true});
// Send beacon on page hide.
function sendBeacon() {
var top = Math.round(performance.now() - startTime);
var data = new URLSearchParams({p: p, top: top, sd: maxScroll});
navigator.sendBeacon(h + '/px/beacon', data);
}
document.addEventListener('visibilitychange', function() {
if (document.visibilityState === 'hidden') sendBeacon();
});
window.addEventListener('pagehide', sendBeacon);
})();
</script>
</body>
</html>