Files
the-other-dude/docs/website/index.html
Jason Staack def4392c93 fix(a11y): WCAG 2.1 AA compliance fixes for website
Contrast:
- Increase --text-muted from #64748B to #8494A7 (~5.3:1 on dark bg)
- Darken --docs-text-muted from #94A3B8 to #6B7B8D (~5.3:1 on white)
- Increase sidebar section title opacity from 0.5 to 0.7

Keyboard & focus:
- Add skip navigation link to both pages
- Make screenshot images keyboard-focusable (tabindex, role=button)
- Add visible lightbox close button with focus styles
- Fix search input focus: proper outline instead of outline:none

Structure:
- Fix heading hierarchy: single h1 per page, demote sections to h2/h3
- Replace sidebar h4 headings with p.sidebar-section-title
- Add aria-labels to all nav landmarks (main, sidebar, footer)
- Fix broken footer anchor links (#security-model, #api-endpoints)

Accessibility:
- Add sr-only label for docs search input
- Add aria-label to back-to-top button
- Change nav logo SVG from aria-label to aria-hidden (redundant)
- Add role=dialog and aria-label to lightbox
- Fix docs.html theme-color meta to #FAFBFC (matches light theme)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 20:53:50 -05:00

483 lines
27 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 — Fleet Management for MikroTik RouterOS</title>
<meta name="description" content="Manage hundreds of MikroTik routers from one dashboard. Secure configuration management, monitoring, and backups built for MSPs.">
<meta name="keywords" content="MikroTik, RouterOS, fleet management, MSP, network management, WinBox alternative, WinBox in browser, WinBox browser, MikroTik monitoring, MikroTik configuration, router management, network monitoring tool, MikroTik dashboard, multi-tenant network management, MikroTik VPN, WireGuard MikroTik, MikroTik certificate authority, MikroTik PDF reports, SRP-6a authentication, zero-knowledge security">
<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="#0F172A">
<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 for MSPs | Monitor, Configure, Secure">
<meta property="og:description" content="Manage hundreds of MikroTik routers from a single pane of glass. Zero-knowledge security, real-time monitoring, and configuration management — built for MSPs.">
<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 for MSPs">
<meta name="twitter:description" content="Manage hundreds of MikroTik routers from a single pane of glass. Zero-knowledge security, real-time monitoring, and configuration management.">
<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": "Open-source MikroTik RouterOS fleet management platform for MSPs. Real-time monitoring, zero-knowledge security, configuration management, and multi-tenant support.",
"url": "https://theotherdude.net",
"offers": {
"@type": "Offer",
"price": "0",
"priceCurrency": "USD"
},
"featureList": [
"MikroTik RouterOS fleet management",
"Real-time device monitoring via SSE",
"Zero-knowledge SRP-6a authentication",
"Per-tenant envelope encryption with OpenBao",
"Two-phase configuration push with panic-revert",
"Multi-tenant PostgreSQL Row-Level Security",
"Internal Certificate Authority",
"Firmware management and audit trail",
"Browser-based WinBox and SSH terminal",
"WireGuard VPN onboarding for NAT traversal",
"PDF reports for fleet, security, and performance"
],
"softwareRequirements": "Docker, PostgreSQL 17, Redis, NATS"
}
</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>
<!-- Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;700&family=Fira+Code:wght@400;500&family=Outfit:wght@400;500;600;700;800&display=swap" rel="stylesheet">
<!-- Styles -->
<link rel="apple-touch-icon" href="/apple-touch-icon.png">
<link rel="stylesheet" href="style.css">
</head>
<body>
<a href="#main-content" class="skip-link">Skip to main content</a>
<!-- ═══════════════════════════════════════════ -->
<!-- Navigation -->
<!-- ═══════════════════════════════════════════ -->
<nav class="site-nav site-nav--dark" 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="#what-it-does" class="nav-link">Features</a>
<a href="#architecture" class="nav-link">Architecture</a>
<a href="#screenshots" class="nav-link">Screenshots</a>
<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>
<a href="docs.html#quickstart" class="nav-cta">Get Started</a>
</div>
</div>
</nav>
<!-- ═══════════════════════════════════════════ -->
<!-- Testing Banner -->
<!-- ═══════════════════════════════════════════ -->
<div class="testing-banner">
<div class="container">
<strong>Early Access</strong> &mdash; This software is in active development and testing. It is not yet ready for production use.
</div>
</div>
<main id="main-content">
<!-- ═══════════════════════════════════════════ -->
<!-- Hero -->
<!-- ═══════════════════════════════════════════ -->
<section class="hero">
<div class="hero-bg"></div>
<div class="hero-content container">
<div class="hero-rosette">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="96" height="96" 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>
</div>
<h1 class="hero-title"><span class="gradient-text">Centralized Management for MikroTik Networks</span></h1>
<p class="hero-subtitle">Managing MikroTik routers shouldn&rsquo;t require dozens of WinBox tabs, SSH scripts, and crossed fingers.</p>
<p class="hero-subtitle">The Other Dude gives you centralized visibility and control over your entire MikroTik fleet.</p>
<p class="hero-subtitle hero-subtitle--muted">Monitor devices, push configuration safely, and operate your network from a single interface.</p>
<div class="hero-actions">
<a href="docs.html#quickstart" class="btn btn-primary">Get Started Managing MikroTik Routers</a>
<a href="https://github.com/staack/the-other-dude" class="btn btn-secondary" rel="noopener">View on GitHub</a>
</div>
</div>
</section>
<!-- ═══════════════════════════════════════════ -->
<!-- MikroTik SEO summary -->
<!-- ═══════════════════════════════════════════ -->
<section class="features-section">
<div class="container">
<h2 class="section-title">MikroTik Router Fleet Management</h2>
<p class="section-desc">The Other Dude is a centralized management platform designed for MikroTik RouterOS deployments. It allows MSPs and network teams to monitor router health, push configuration safely, manage backups, and maintain visibility across hundreds of devices from a single dashboard.</p>
<p class="section-desc">Instead of logging into routers individually with WinBox or SSH, The Other Dude provides a fleet-level view of your MikroTik infrastructure with real-time status, configuration tracking, and automated management tools.</p>
</div>
</section>
<!-- ═══════════════════════════════════════════ -->
<!-- Features -->
<!-- ═══════════════════════════════════════════ -->
<section id="what-it-does" class="features-section">
<div class="container">
<span class="section-label">WHAT THE OTHER DUDE DOES</span>
<h2 class="section-title">Everything you need to manage MikroTik routers at scale</h2>
<div class="features-grid">
<div class="feature-card" id="feature-fleet">
<div class="feature-icon">&#x1F4CA;</div>
<h3 class="feature-title">Fleet Management</h3>
<p class="feature-desc">Dashboard with device health, uptime sparklines, virtual-scrolled fleet table, geographic map, and subnet discovery. Monitor your entire MikroTik fleet from a single view.</p>
</div>
<div class="feature-card" id="feature-config">
<div class="feature-icon">&#x2699;</div>
<h3 class="feature-title">Configuration Management</h3>
<p class="feature-desc">Two-phase config push with automatic panic-revert protection. Templates, diff and history, git-backed backups, and one-click restore. Push changes across your fleet with confidence.</p>
</div>
<div class="feature-card" id="feature-monitoring">
<div class="feature-icon">&#x1F4C8;</div>
<h3 class="feature-title">Real-Time Monitoring</h3>
<p class="feature-desc">Live CPU, memory, disk, interface traffic, and wireless metrics (signal, CCQ, client count) via Server-Sent Events. Configurable alert rules with email, webhook, and Slack notifications. Dashboard highlights APs needing attention.</p>
</div>
<div class="feature-card" id="feature-security">
<div class="feature-icon">&#x1F512;</div>
<h3 class="feature-title">Zero-Knowledge Security</h3>
<p class="feature-desc">SRP-6a authentication means the server never sees your password. Per-tenant envelope encryption via Transit KMS. Emergency Kit export for account recovery.</p>
</div>
<div class="feature-card" id="feature-winbox">
<div class="feature-icon">&#x1F5A5;</div>
<h3 class="feature-title">WinBox in the Browser</h3>
<p class="feature-desc">Launch WinBox sessions directly from the web interface without installing the desktop app. Browser-based SSH terminal. WireGuard VPN overlay for one-click access to devices behind NAT.</p>
</div>
<div class="feature-card" id="feature-certs">
<div class="feature-icon">&#x1F6E1;</div>
<h3 class="feature-title">Certificate Authority</h3>
<p class="feature-desc">Issue and deploy TLS certificates to RouterOS devices via SFTP. Three-tier TLS fallback for maximum compatibility across firmware versions.</p>
</div>
<div class="feature-card" id="feature-reports">
<div class="feature-icon">&#x1F4C4;</div>
<h3 class="feature-title">PDF Reports</h3>
<p class="feature-desc">Fleet summary, device detail, security audit, and performance reports generated server-side. Export and share operational data without screenshots.</p>
</div>
<div class="feature-card" id="feature-tenants">
<div class="feature-icon">&#x1F3E2;</div>
<h3 class="feature-title">Multi-Tenant Isolation</h3>
<p class="feature-desc">PostgreSQL Row-Level Security enforces full data separation at the database layer. Four roles: super_admin, admin, operator, viewer.</p>
</div>
</div>
</div>
</section>
<!-- ═══════════════════════════════════════════ -->
<!-- Who This Is For -->
<!-- ═══════════════════════════════════════════ -->
<section class="features-section">
<div class="container">
<span class="section-label">WHO THIS IS FOR</span>
<h2 class="section-title">Built for real operators</h2>
<p class="section-desc">The Other Dude is designed for people who operate MikroTik networks in the real world:</p>
<ul class="content-list">
<li>Managed Service Providers managing many client networks</li>
<li>Wireless ISPs running large router fleets</li>
<li>Network engineers operating distributed infrastructure</li>
<li>Homelab operators managing larger environments</li>
</ul>
</div>
</section>
<!-- ═══════════════════════════════════════════ -->
<!-- Architecture -->
<!-- ═══════════════════════════════════════════ -->
<section id="architecture" class="features-section features-section--alt">
<div class="container">
<span class="section-label">ARCHITECTURE</span>
<h2 class="section-title">Event-driven, designed for reliability and horizontal scaling</h2>
<div class="arch-flow">
<div class="arch-flow-step">Routers</div>
<div class="arch-flow-arrow"></div>
<div class="arch-flow-step">Go Pollers</div>
<div class="arch-flow-arrow"></div>
<div class="arch-flow-step">NATS Event Bus</div>
<div class="arch-flow-arrow"></div>
<div class="arch-flow-step">FastAPI Backend</div>
<div class="arch-flow-arrow"></div>
<div class="arch-flow-step">PostgreSQL + TimescaleDB</div>
<div class="arch-flow-arrow"></div>
<div class="arch-flow-step">React Web Interface</div>
</div>
<p class="section-desc">This architecture allows the system to scale horizontally while keeping polling, processing, and the user interface separated.</p>
</div>
</section>
<!-- ═══════════════════════════════════════════ -->
<!-- Screenshots -->
<!-- ═══════════════════════════════════════════ -->
<section id="screenshots" class="screenshots-section">
<div class="container">
<span class="section-label">IN ACTION</span>
<h2 class="section-title">See it in action</h2>
</div>
<div class="screenshots-scroll">
<div class="screenshots-track">
<figure class="screenshot-card">
<img src="assets/login.png" alt="The Other Dude zero-knowledge SRP-6a login page" loading="lazy" width="800" height="500">
<figcaption>Zero-Knowledge SRP-6a Login</figcaption>
</figure>
<figure class="screenshot-card">
<img src="assets/dashboard-lebowski-lanes.png" alt="Fleet dashboard showing 117 devices across Lebowski Lanes with bandwidth charts and event feed" loading="lazy" width="800" height="500">
<figcaption>Fleet Dashboard &mdash; Lebowski Lanes</figcaption>
</figure>
<figure class="screenshot-card">
<img src="assets/device-list.png" alt="Device fleet list showing MikroTik models, status, uptime, and firmware versions" loading="lazy" width="800" height="500">
<figcaption>Device Fleet List</figcaption>
</figure>
<figure class="screenshot-card">
<img src="assets/device-detail.png" alt="Device detail showing interface traffic charts for ether1, ether2, and SFP+ ports with WinBox and SSH buttons" loading="lazy" width="800" height="500">
<figcaption>Interface Traffic &mdash; dude-core-01</figcaption>
</figure>
<figure class="screenshot-card">
<img src="assets/device-health.png" alt="Device health metrics showing CPU load, memory usage, disk usage, and temperature charts" loading="lazy" width="800" height="500">
<figcaption>Device Health Metrics &mdash; bowling-ap-01</figcaption>
</figure>
<figure class="screenshot-card">
<img src="assets/alerts.png" alt="Active alerts showing CPU warnings and device offline criticals with acknowledge and silence controls" loading="lazy" width="800" height="500">
<figcaption>Active Alerts</figcaption>
</figure>
<figure class="screenshot-card">
<img src="assets/topology.png" alt="Network topology map with automatic device discovery" loading="lazy" width="800" height="500">
<figcaption>Network Topology Map</figcaption>
</figure>
<figure class="screenshot-card">
<img src="assets/config-editor.png" alt="RouterOS configuration editor with diff preview" loading="lazy" width="800" height="500">
<figcaption>Configuration Editor</figcaption>
</figure>
<figure class="screenshot-card">
<img src="assets/dashboard-strangers-ranch.png" alt="Multi-tenant dashboard showing The Stranger's Ranch with tenant switcher" loading="lazy" width="800" height="500">
<figcaption>Multi-Tenant &mdash; The Stranger&rsquo;s Ranch</figcaption>
</figure>
</div>
</div>
</section>
<!-- ═══════════════════════════════════════════ -->
<!-- Quick Start -->
<!-- ═══════════════════════════════════════════ -->
<section class="quickstart-section">
<div class="container">
<span class="section-label">QUICK START</span>
<h2 class="section-title">Deploy The Other Dude with two commands</h2>
<div class="code-window">
<div class="code-window-header">
<span class="code-dot code-dot--red"></span>
<span class="code-dot code-dot--yellow"></span>
<span class="code-dot code-dot--green"></span>
<span class="code-window-title">Terminal</span>
</div>
<pre class="code-window-body"><code><span class="code-comment"># Clone and run the setup wizard</span>
<span class="code-cmd">git clone https://github.com/staack/the-other-dude.git</span>
<span class="code-cmd">cd the-other-dude</span>
<span class="code-cmd">python3 setup.py</span></code></pre>
</div>
<p class="section-desc" style="margin-top: 24px;">The setup wizard configures your database, generates cryptographic keys, bootstraps OpenBao, sets up your reverse proxy, builds the Docker images, and starts everything. No manual .env editing required.</p>
</div>
</section>
<!-- ═══════════════════════════════════════════ -->
<!-- CTA -->
<!-- ═══════════════════════════════════════════ -->
<section class="cta-section">
<div class="container">
<span class="section-label">OPEN SOURCE</span>
<h2 class="cta-title">The Other Dude is open source and self-hosted</h2>
<p class="cta-desc">Run it yourself, modify it, or contribute improvements.</p>
<div class="cta-actions">
<a href="https://github.com/staack/the-other-dude" class="btn btn-primary" rel="noopener">View on GitHub</a>
</div>
<div class="cta-closing">
<p class="cta-closing-title">Centralized management for MikroTik networks.</p>
<p class="cta-tagline">Less time juggling tools.<br>More time running your network.</p>
</div>
</div>
</section>
<!-- ═══════════════════════════════════════════ -->
<!-- Footer -->
<!-- ═══════════════════════════════════════════ -->
</main>
<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="#what-it-does">Features</a>
<a href="docs.html#security">Security</a>
<a href="docs.html#api">API Reference</a>
<a href="https://github.com/staack/the-other-dude" rel="noopener">GitHub</a>
<a href="mailto:license@theotherdude.net">Licensing</a>
<a href="mailto:support@theotherdude.net">Support</a>
</nav>
</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="The Other Dude dashboard managing multiple MikroTik routers">
<div class="lightbox-caption"></div>
</div>
<script src="script.js"></script>
<!-- Cloudflare Web Analytics --><script defer src='https://static.cloudflareinsights.com/beacon.min.js' data-cf-beacon='{"token": "d5f1e31cb9744c998a8f7d1303c6efef"}'></script><!-- End Cloudflare Web Analytics -->
<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 cap = img.closest('.screenshot-card').querySelector('figcaption');
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();
});
})();
</script>
</body>
</html>