Files
meshcore-bot/website/index.html
T
agessaman 7984fa3af6 feat: Enhance command messaging with repeat tracking and transmission monitoring
- Updated `send_dm` and `send_channel_message` methods to include an optional `command_id` for tracking message repeats.
- Integrated transmission tracking to record and manage repeat messages, improving message handling and response accuracy.
- Enhanced `capture_command` method in `BotIntegration` to store repeat information for better analysis in the web viewer.
- Added favicon support and improved web viewer templates to display repeat information effectively.
- Implemented JavaScript updates in the web viewer to handle command updates and display repeat counts dynamically.
2026-01-11 20:30:02 -08:00

1394 lines
50 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>HowlBot🤖 - Command Reference</title>
<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=Outfit:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
<style>
:root {
--bg-primary: #0a0e14;
--bg-secondary: #111820;
--bg-card: #151c25;
--bg-card-hover: #1a232e;
--accent-blue: #00d4ff;
--accent-cyan: #00ffc8;
--accent-orange: #ff8a00;
--accent-purple: #a855f7;
--accent-red: #ff4757;
--accent-yellow: #ffd700;
--text-primary: #e8edf4;
--text-secondary: #8892a4;
--text-muted: #4a5568;
--border-subtle: rgba(255,255,255,0.06);
--glow-blue: rgba(0, 212, 255, 0.15);
--glow-cyan: rgba(0, 255, 200, 0.1);
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html {
scroll-behavior: smooth;
}
body {
font-family: 'Outfit', sans-serif;
background: var(--bg-primary);
color: var(--text-primary);
min-height: 100vh;
overflow-x: hidden;
line-height: 1.6;
}
/* Atmospheric background */
.atmosphere {
position: fixed;
inset: 0;
background:
radial-gradient(ellipse 80% 50% at 20% 0%, rgba(0, 212, 255, 0.08) 0%, transparent 50%),
radial-gradient(ellipse 60% 40% at 80% 100%, rgba(0, 255, 200, 0.05) 0%, transparent 50%),
radial-gradient(ellipse 100% 100% at 50% 50%, var(--bg-primary) 0%, #060a0f 100%);
pointer-events: none;
z-index: -1;
}
/* Subtle grid overlay */
.grid-overlay {
position: fixed;
inset: 0;
background-image:
linear-gradient(rgba(255,255,255,0.01) 1px, transparent 1px),
linear-gradient(90deg, rgba(255,255,255,0.01) 1px, transparent 1px);
background-size: 60px 60px;
pointer-events: none;
z-index: -1;
}
.container {
max-width: 1600px;
margin: 0 auto;
padding: 3rem 2rem;
min-height: 100vh;
position: relative;
z-index: 1;
display: grid;
grid-template-columns: 280px 1fr;
gap: 3rem;
}
.sidebar-nav {
position: sticky;
top: 2rem;
height: fit-content;
max-height: calc(100vh - 4rem);
overflow-y: auto;
background: var(--bg-card);
border-radius: 16px;
border: 1px solid var(--border-subtle);
padding: 1.5rem;
z-index: 100;
transition: left 0.3s cubic-bezier(0.4, 0, 0.2, 1);
opacity: 1;
filter: none;
}
.mobile-menu-toggle {
display: none;
position: fixed;
top: 1.5rem;
right: 1.5rem;
z-index: 1001;
background: var(--bg-card);
border: 1px solid var(--border-subtle);
border-radius: 12px;
padding: 0.75rem;
cursor: pointer;
flex-direction: column;
gap: 5px;
align-items: center;
justify-content: center;
width: 48px;
height: 48px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
transition: all 0.3s ease;
}
.mobile-menu-toggle:hover {
background: var(--bg-card-hover);
border-color: var(--accent-cyan);
}
.mobile-menu-toggle.active {
background: var(--bg-card-hover);
border-color: var(--accent-cyan);
}
.hamburger {
width: 24px;
height: 2px;
background: var(--accent-cyan);
border-radius: 2px;
transition: all 0.3s ease;
}
.mobile-menu-toggle.active .hamburger:nth-child(1) {
transform: rotate(45deg) translate(7px, 7px);
}
.mobile-menu-toggle.active .hamburger:nth-child(2) {
opacity: 0;
}
.mobile-menu-toggle.active .hamburger:nth-child(3) {
transform: rotate(-45deg) translate(7px, -7px);
}
.sidebar-overlay {
display: none;
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.7);
z-index: 1000;
backdrop-filter: none;
-webkit-backdrop-filter: none;
pointer-events: none;
}
.sidebar-overlay.visible {
pointer-events: auto;
}
@media (max-width: 1200px) {
.sidebar-overlay.visible {
/* Don't cover the sidebar area - start overlay after sidebar width */
left: 280px !important;
}
}
@media (min-width: 1201px) {
.sidebar-overlay {
backdrop-filter: blur(4px);
-webkit-backdrop-filter: blur(4px);
}
}
@media (max-width: 1200px) {
.sidebar-overlay {
backdrop-filter: none !important;
-webkit-backdrop-filter: none !important;
z-index: 1000 !important;
}
.sidebar-overlay.visible {
pointer-events: auto !important;
}
/* Ensure sidebar is always clickable above overlay */
.sidebar-nav {
pointer-events: auto !important;
}
.sidebar-nav * {
pointer-events: auto !important;
}
}
.sidebar-overlay.visible {
display: block;
}
.sidebar-nav::-webkit-scrollbar {
width: 6px;
}
.sidebar-nav::-webkit-scrollbar-track {
background: var(--bg-secondary);
border-radius: 3px;
}
.sidebar-nav::-webkit-scrollbar-thumb {
background: var(--border-subtle);
border-radius: 3px;
}
.sidebar-nav::-webkit-scrollbar-thumb:hover {
background: rgba(255,255,255,0.15);
}
.nav-header {
margin-bottom: 1.5rem;
padding-bottom: 1rem;
border-bottom: 1px solid var(--border-subtle);
}
.nav-header h3 {
font-size: 1rem;
font-weight: 600;
color: var(--text-primary);
text-transform: uppercase;
letter-spacing: 0.05em;
font-size: 0.85rem;
}
.nav-list {
list-style: none;
padding: 0;
margin: 0;
}
.nav-section-header {
font-size: 0.75rem;
text-transform: uppercase;
letter-spacing: 0.1em;
color: var(--text-muted);
font-weight: 600;
margin-top: 1.5rem;
margin-bottom: 0.75rem;
padding-left: 0.5rem;
}
.nav-section-header:first-child {
margin-top: 0;
}
.nav-sublist {
list-style: none;
padding: 0;
margin: 0 0 1rem 0;
padding-left: 0.5rem;
}
.nav-link {
display: block;
padding: 0.6rem 0.75rem;
color: var(--text-secondary);
text-decoration: none;
border-radius: 8px;
font-size: 0.9rem;
transition: all 0.2s ease;
margin-bottom: 0.25rem;
cursor: pointer;
-webkit-tap-highlight-color: rgba(0, 255, 200, 0.2);
}
.nav-link:hover {
background: var(--bg-card-hover);
color: var(--accent-cyan);
transform: translateX(4px);
}
.nav-link:active,
.nav-link.active {
background: rgba(0, 255, 200, 0.1);
color: var(--accent-cyan);
border-left: 3px solid var(--accent-cyan);
padding-left: calc(0.75rem - 3px);
}
.nav-sublink {
padding-left: 1.25rem;
font-size: 0.85rem;
color: var(--text-muted);
}
.nav-sublink:hover {
color: var(--accent-blue);
}
.main-content {
min-width: 0;
}
header {
margin-bottom: 4rem;
padding: 0;
}
.header-content {
background: var(--bg-card);
border-radius: 20px;
border: 1px solid var(--border-subtle);
padding: 3rem 2.5rem;
position: relative;
overflow: hidden;
}
.header-content::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 3px;
background: linear-gradient(90deg, var(--accent-blue), var(--accent-cyan));
}
.header-title {
display: flex;
align-items: center;
gap: 1rem;
margin-bottom: 1.5rem;
}
.logo-icon {
width: 56px;
height: 56px;
background: linear-gradient(135deg, var(--accent-blue), var(--accent-cyan));
border-radius: 14px;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.75rem;
box-shadow: 0 4px 20px var(--glow-blue);
flex-shrink: 0;
}
h1 {
font-size: 3rem;
font-weight: 600;
margin: 0;
color: var(--accent-cyan);
letter-spacing: -0.02em;
font-family: 'Outfit', sans-serif;
}
.intro {
font-size: 1.1rem;
color: var(--text-secondary);
max-width: 900px;
line-height: 1.8;
margin-top: 0.5rem;
}
.channel-highlight {
color: var(--accent-cyan);
font-weight: 500;
}
.category-section {
margin-bottom: 3rem;
}
.category-title {
font-size: 1.75rem;
font-weight: 600;
margin-bottom: 1.5rem;
color: var(--text-primary);
padding-bottom: 1rem;
border-bottom: 1px solid var(--border-subtle);
letter-spacing: -0.01em;
}
.category-title .anchor-link,
.channel-category-title .anchor-link {
color: inherit;
text-decoration: none;
position: relative;
display: inline-block;
}
.category-title .anchor-link:hover,
.channel-category-title .anchor-link:hover {
color: var(--accent-cyan);
}
.category-title .anchor-link::before {
content: '#';
position: absolute;
left: -1.5rem;
opacity: 0;
color: var(--accent-blue);
font-weight: 400;
transition: opacity 0.2s ease;
}
.category-title:hover .anchor-link::before,
.channel-category-title:hover .anchor-link::before {
opacity: 0.5;
}
.category-section,
.channel-category {
scroll-margin-top: 2rem;
}
.commands-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
gap: 1.25rem;
margin-bottom: 2rem;
}
.command-card {
background: var(--bg-card);
border-radius: 16px;
padding: 1.5rem;
border: 1px solid var(--border-subtle);
transition: all 0.3s ease;
position: relative;
overflow: hidden;
}
.command-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 3px;
background: linear-gradient(90deg, var(--accent-blue), transparent);
opacity: 0;
transition: opacity 0.3s ease;
}
.command-card:hover {
background: var(--bg-card-hover);
border-color: rgba(255,255,255,0.1);
transform: translateY(-2px);
}
.command-card:hover::before {
opacity: 1;
}
.command-header {
display: flex;
flex-direction: column;
gap: 0.75rem;
margin-bottom: 1rem;
}
.command-name {
font-size: 1.5rem;
font-weight: 600;
color: var(--accent-blue);
margin: 0;
letter-spacing: -0.01em;
}
.command-keywords {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
}
.keyword-badge {
background: rgba(0, 212, 255, 0.1);
color: var(--accent-cyan);
padding: 0.35rem 0.75rem;
border-radius: 8px;
font-size: 0.8rem;
font-weight: 500;
border: 1px solid rgba(0, 212, 255, 0.2);
font-family: 'JetBrains Mono', monospace;
}
.keyword-expand {
cursor: pointer;
transition: all 0.2s ease;
}
.keyword-expand:hover {
background: rgba(0, 212, 255, 0.2);
border-color: var(--accent-cyan);
transform: scale(1.05);
}
.keyword-expand.expanded {
display: none;
}
.keyword-hidden {
display: none;
}
.keyword-hidden.visible {
display: inline-block;
}
.command-description {
color: var(--text-secondary);
line-height: 1.7;
margin-bottom: 0.75rem;
font-size: 0.95rem;
}
.command-channels {
color: var(--accent-cyan);
font-size: 0.85rem;
margin-top: 0.75rem;
padding: 0.6rem 0.9rem;
background: rgba(0, 255, 200, 0.08);
border-radius: 8px;
border-left: 3px solid var(--accent-cyan);
font-family: 'JetBrains Mono', monospace;
font-weight: 500;
}
.channels-intro {
color: var(--text-secondary);
font-size: 1rem;
margin-bottom: 2rem;
line-height: 1.7;
}
.channel-category {
margin-bottom: 2.5rem;
}
.channel-category-title {
font-size: 1.25rem;
font-weight: 600;
color: var(--accent-blue);
margin-bottom: 1rem;
letter-spacing: -0.01em;
}
.channel-category-title .anchor-link::before {
content: '#';
position: absolute;
left: -1.2rem;
opacity: 0;
color: var(--accent-blue);
font-weight: 400;
transition: opacity 0.2s ease;
}
.channels-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 1rem;
}
.channel-card {
background: var(--bg-card);
border-radius: 12px;
padding: 1.25rem;
border: 1px solid var(--border-subtle);
transition: all 0.3s ease;
}
.channel-card:hover {
background: var(--bg-card-hover);
border-color: rgba(0, 212, 255, 0.3);
transform: translateY(-2px);
}
.channel-name {
font-family: 'JetBrains Mono', monospace;
font-size: 1.1rem;
font-weight: 600;
color: var(--accent-cyan);
margin-bottom: 0.5rem;
}
.channel-description {
color: var(--text-secondary);
font-size: 0.9rem;
line-height: 1.6;
}
footer {
text-align: center;
margin-top: 4rem;
padding: 2rem;
color: var(--text-muted);
font-size: 0.9rem;
border-top: 1px solid var(--border-subtle);
}
@media (max-width: 1200px) {
.commands-grid {
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
}
}
@media (max-width: 1200px) {
/* Disable all backdrop filters on mobile */
* {
backdrop-filter: none !important;
-webkit-backdrop-filter: none !important;
}
.container {
grid-template-columns: 1fr !important;
}
.mobile-menu-toggle {
display: flex;
}
.sidebar-nav {
position: fixed !important;
top: 0 !important;
left: -320px !important;
width: 280px !important;
height: 100vh !important;
max-height: 100vh !important;
margin: 0 !important;
padding-top: 4rem !important;
border-radius: 0 !important;
border-left: none !important;
border-top: none !important;
border-bottom: none !important;
border-right: 1px solid var(--border-subtle) !important;
transition: left 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important;
z-index: 1002 !important;
opacity: 1 !important;
filter: none !important;
backdrop-filter: none !important;
-webkit-backdrop-filter: none !important;
transform: translateZ(0) !important;
will-change: left !important;
isolation: isolate !important;
background: var(--bg-card) !important;
visibility: visible !important;
-webkit-font-smoothing: antialiased !important;
-moz-osx-font-smoothing: grayscale !important;
overflow-y: auto !important;
overflow-x: hidden !important;
-webkit-overflow-scrolling: touch !important;
pointer-events: auto !important;
touch-action: pan-y !important;
-webkit-transform: translateZ(0) !important;
transform: translateZ(0) !important;
}
.sidebar-nav.open {
left: 0 !important;
}
.sidebar-nav * {
opacity: 1 !important;
filter: none !important;
backdrop-filter: none !important;
-webkit-backdrop-filter: none !important;
visibility: visible !important;
-webkit-font-smoothing: antialiased !important;
-moz-osx-font-smoothing: grayscale !important;
pointer-events: auto !important;
}
.sidebar-nav .nav-header h3 {
color: #e8edf4 !important;
opacity: 1 !important;
text-shadow: none !important;
}
.sidebar-nav .nav-link {
color: #8892a4 !important;
opacity: 1 !important;
text-shadow: none !important;
pointer-events: auto !important;
cursor: pointer !important;
-webkit-tap-highlight-color: rgba(0, 255, 200, 0.2) !important;
}
.sidebar-nav .nav-link:hover,
.sidebar-nav .nav-link:active {
color: #00ffc8 !important;
opacity: 1 !important;
}
.sidebar-nav .nav-section-header {
color: #8892a4 !important;
opacity: 1 !important;
text-shadow: none !important;
}
.main-content {
margin-top: 0;
}
}
@media (min-width: 1201px) {
.sidebar-nav {
position: sticky !important;
top: 2rem !important;
left: auto !important;
width: auto !important;
height: fit-content !important;
max-height: calc(100vh - 4rem) !important;
margin: 0 !important;
padding: 1.5rem !important;
border-radius: 16px !important;
border: 1px solid var(--border-subtle) !important;
z-index: 100 !important;
opacity: 1 !important;
filter: none !important;
backdrop-filter: none !important;
-webkit-backdrop-filter: none !important;
transform: none !important;
will-change: auto !important;
isolation: auto !important;
background: var(--bg-card) !important;
visibility: visible !important;
}
}
@media (max-width: 768px) {
.container {
padding: 2rem 1rem;
}
.header-content {
padding: 2rem 1.5rem;
}
.header-title {
flex-direction: column;
align-items: flex-start;
gap: 0.75rem;
}
.logo-icon {
width: 48px;
height: 48px;
font-size: 1.5rem;
}
h1 {
font-size: 2.25rem;
}
.commands-grid {
grid-template-columns: 1fr;
}
.sidebar-nav {
padding: 1rem;
width: calc(100vw - 3rem);
left: calc(-100vw + 3rem);
}
.sidebar-nav.open {
left: 0;
}
.mobile-menu-toggle {
top: 1rem;
right: 1rem;
}
}
</style>
</head>
<body>
<div class="atmosphere"></div>
<div class="grid-overlay"></div>
<div class="sidebar-overlay"></div>
<button class="mobile-menu-toggle" aria-label="Toggle navigation menu">
<span class="hamburger"></span>
<span class="hamburger"></span>
<span class="hamburger"></span>
</button>
<div class="container">
<nav class="sidebar-nav">
<div class="nav-header">
<h3>Navigation</h3>
</div>
<ul class="nav-list">
<li class="nav-section-header">Commands</li>
<ul class="nav-sublist">
<li><a href="#commands-basic" class="nav-link nav-sublink">Basic Commands</a></li>
<li><a href="#commands-analytics" class="nav-link nav-sublink">Analytics</a></li>
<li><a href="#commands-emergency" class="nav-link nav-sublink">Emergency</a></li>
<li><a href="#commands-entertainment" class="nav-link nav-sublink">Entertainment</a></li>
<li><a href="#commands-fun" class="nav-link nav-sublink">Fun Commands</a></li>
<li><a href="#commands-games" class="nav-link nav-sublink">Games &amp; Entertainment</a></li>
<li><a href="#commands-meshcore-info" class="nav-link nav-sublink">Mesh Network Info</a></li>
<li><a href="#commands-solar" class="nav-link nav-sublink">Solar &amp; Astronomical</a></li>
<li><a href="#commands-sports" class="nav-link nav-sublink">Sports</a></li>
<li><a href="#commands-weather" class="nav-link nav-sublink">Weather Commands</a></li>
</ul>
<li class="nav-section-header">Channels</li>
<ul class="nav-sublist">
<li><a href="#channels" class="nav-link nav-sublink">Available Channels</a></li>
<li><a href="#channels-general" class="nav-link nav-sublink">General Channels</a></li>
<li><a href="#channels-emergency" class="nav-link nav-sublink">Emergency</a></li>
<li><a href="#channels-seattle" class="nav-link nav-sublink">Seattle</a></li>
<li><a href="#channels-sports" class="nav-link nav-sublink">Sports</a></li>
<li><a href="#channels-vancouver" class="nav-link nav-sublink">Vancouver</a></li>
</ul>
</ul>
</nav>
<div class="main-content">
<header>
<div class="header-content">
<div class="header-title">
<div class="logo-icon">🤖</div>
<h1>HowlBot🤖</h1>
</div>
<div class="intro">
Hi, I&#x27;m HowlBot🤖! I provide various commands to help you interact with the mesh network. Use the commands below to get started. I'll answer you if you send a message in <span class="channel-highlight">#BotTest</span> or <span class="channel-highlight">#bot</span>.
</div>
</div>
</header>
<main>
<div class="category-section" id="commands-basic">
<h2 class="category-title"><a href="#commands-basic" class="anchor-link">Basic Commands</a></h2>
<div class="commands-grid">
<div class="command-card">
<div class="command-header">
<h3 class="command-name">test</h3>
<div class="command-keywords">
<span class="keyword-badge">t</span>
</div>
</div>
<p class="command-description">Responds to &#x27;test&#x27; or &#x27;t&#x27; with connection info</p>
</div>
<div class="command-card">
<div class="command-header">
<h3 class="command-name">hello</h3>
<div class="command-keywords">
<span class="keyword-badge">hi</span>
<span class="keyword-badge">hey</span>
<span class="keyword-badge">howdy</span>
<span class="keyword-badge">greetings</span>
<span class="keyword-badge">salutations</span>
<span class="keyword-badge keyword-expand" data-hidden="good morning,good afternoon,good evening,good night,yo,sup,whats up,what&#x27;s up,morning,afternoon,evening,night,gday,g&#x27;day,hola,bonjour,ciao,namaste,aloha,shalom,konnichiwa,guten tag,buenos dias,buenas tardes,buenas noches" data-command="hello">+25 more</span>
</div>
</div>
<p class="command-description">Responds to greetings with robot-themed responses</p>
</div>
<div class="command-card">
<div class="command-header">
<h3 class="command-name">ping</h3>
</div>
<p class="command-description">Responds to &#x27;ping&#x27; with &#x27;Pong!&#x27;</p>
</div>
<div class="command-card">
<div class="command-header">
<h3 class="command-name">help</h3>
</div>
<p class="command-description">Shows commands. Use &#x27;help &lt;command&gt;&#x27; for details.</p>
</div>
<div class="command-card">
<div class="command-header">
<h3 class="command-name">channels</h3>
<div class="command-keywords">
<span class="keyword-badge">channel</span>
</div>
</div>
<p class="command-description">Lists hashtag channels with sub-categories. Use &#x27;channels&#x27; for general, &#x27;channels list&#x27; for all categories, &#x27;channels &lt;category&gt;&#x27; for specific categories, &#x27;channels #channel&#x27; for specific channel info.</p>
</div>
<div class="command-card">
<div class="command-header">
<h3 class="command-name">cmd</h3>
<div class="command-keywords">
<span class="keyword-badge">commands</span>
</div>
</div>
<p class="command-description">Lists available commands in compact format</p>
</div>
</div>
</div>
<div class="category-section" id="commands-analytics">
<h2 class="category-title"><a href="#commands-analytics" class="anchor-link">Analytics</a></h2>
<div class="commands-grid">
<div class="command-card">
<div class="command-header">
<h3 class="command-name">stats</h3>
</div>
<p class="command-description">Show statistics for past 24 hours. Use &#x27;stats messages&#x27;, &#x27;stats channels&#x27;, or &#x27;stats paths&#x27; for specific stats.</p>
</div>
</div>
</div>
<div class="category-section" id="commands-emergency">
<h2 class="category-title"><a href="#commands-emergency" class="anchor-link">Emergency</a></h2>
<div class="commands-grid">
<div class="command-card">
<div class="command-header">
<h3 class="command-name">alert</h3>
<div class="command-keywords">
<span class="keyword-badge">alerts</span>
<span class="keyword-badge">incident</span>
<span class="keyword-badge">incidents</span>
</div>
</div>
<p class="command-description">Get active emergency incidents (usage: alert seattle, alert 98258, alert 178th seattle, alert seattle all)</p>
</div>
</div>
</div>
<div class="category-section" id="commands-entertainment">
<h2 class="category-title"><a href="#commands-entertainment" class="anchor-link">Entertainment</a></h2>
<div class="commands-grid">
<div class="command-card">
<div class="command-header">
<h3 class="command-name">joke</h3>
<div class="command-keywords">
<span class="keyword-badge">jokes</span>
</div>
</div>
<p class="command-description">Get a random joke or joke from specific category (usage: joke [category])</p>
<div class="command-channels">Channel: #jokes</div>
</div>
</div>
</div>
<div class="category-section" id="commands-fun">
<h2 class="category-title"><a href="#commands-fun" class="anchor-link">Fun Commands</a></h2>
<div class="commands-grid">
<div class="command-card">
<div class="command-header">
<h3 class="command-name">hacker</h3>
<div class="command-keywords">
<span class="keyword-badge">sudo</span>
<span class="keyword-badge">ps aux</span>
<span class="keyword-badge">grep</span>
<span class="keyword-badge">ls -l</span>
<span class="keyword-badge">ls -la</span>
<span class="keyword-badge keyword-expand" data-hidden="echo $PATH,rm,rm -rf,cat,whoami,top,htop,netstat,ss,kill,killall,chmod,find,history,passwd,su,ssh,wget,curl,df -h,free,ifconfig,ip addr,uname -a" data-command="hacker">+24 more</span>
</div>
</div>
<p class="command-description">Simulates hacking a supervillain&#x27;s mainframe with hilarious error messages</p>
</div>
<div class="command-card">
<div class="command-header">
<h3 class="command-name">dadjoke</h3>
<div class="command-keywords">
<span class="keyword-badge">dad joke</span>
<span class="keyword-badge">dadjokes</span>
<span class="keyword-badge">dad jokes</span>
</div>
</div>
<p class="command-description">Get a random dad joke from icanhazdadjoke.com</p>
<div class="command-channels">Channel: #jokes</div>
</div>
</div>
</div>
<div class="category-section" id="commands-games">
<h2 class="category-title"><a href="#commands-games" class="anchor-link">Games &amp; Entertainment</a></h2>
<div class="commands-grid">
<div class="command-card">
<div class="command-header">
<h3 class="command-name">dice</h3>
</div>
<p class="command-description">Roll dice for D&amp;D and tabletop games. Use &#x27;dice&#x27; for d6, &#x27;dice d20&#x27; for d20, &#x27;dice 2d6&#x27; for 2d6, &#x27;dice d10 d6&#x27; for mixed dice, &#x27;dice decade&#x27; for decade die (00-90), etc.</p>
</div>
<div class="command-card">
<div class="command-header">
<h3 class="command-name">magic8</h3>
</div>
<p class="command-description">Emulates the classic Magic 8-ball toy&#x27;</p>
</div>
<div class="command-card">
<div class="command-header">
<h3 class="command-name">roll</h3>
</div>
<p class="command-description">Roll a random number between 1 and X (default 100). Use &#x27;roll&#x27; for 1-100, &#x27;roll 50&#x27; for 1-50, etc.</p>
</div>
</div>
</div>
<div class="category-section" id="commands-meshcore-info">
<h2 class="category-title"><a href="#commands-meshcore-info" class="anchor-link">Mesh Network Info</a></h2>
<div class="commands-grid">
<div class="command-card">
<div class="command-header">
<h3 class="command-name">path</h3>
<div class="command-keywords">
<span class="keyword-badge">decode</span>
<span class="keyword-badge">route</span>
<span class="keyword-badge">p</span>
</div>
</div>
<p class="command-description">Decode hex path data to show which repeaters were involved in message routing</p>
</div>
<div class="command-card">
<div class="command-header">
<h3 class="command-name">prefix</h3>
<div class="command-keywords">
<span class="keyword-badge">repeater</span>
<span class="keyword-badge">lookup</span>
</div>
</div>
<p class="command-description">Look up repeaters by two-character prefix (e.g., &#x27;prefix 1A&#x27;)</p>
</div>
<div class="command-card">
<div class="command-header">
<h3 class="command-name">multitest</h3>
<div class="command-keywords">
<span class="keyword-badge">mt</span>
</div>
</div>
<p class="command-description">Listens for 6 seconds and collects all unique paths from incoming messages</p>
</div>
</div>
</div>
<div class="category-section" id="commands-solar">
<h2 class="category-title"><a href="#commands-solar" class="anchor-link">Solar &amp; Astronomical</a></h2>
<div class="commands-grid">
<div class="command-card">
<div class="command-header">
<h3 class="command-name">hfcond</h3>
</div>
<p class="command-description">Get HF band conditions for ham radio</p>
</div>
<div class="command-card">
<div class="command-header">
<h3 class="command-name">satpass</h3>
</div>
<p class="command-description">Get satellite pass info: satpass &lt;NORAD_number_or_shortcut&gt; [visual]</p>
</div>
<div class="command-card">
<div class="command-header">
<h3 class="command-name">moon</h3>
</div>
<p class="command-description">Get moon phase, rise/set times and position</p>
</div>
<div class="command-card">
<div class="command-header">
<h3 class="command-name">sun</h3>
</div>
<p class="command-description">Get sunrise/sunset times</p>
</div>
<div class="command-card">
<div class="command-header">
<h3 class="command-name">solar</h3>
</div>
<p class="command-description">Get current solar conditions and HF band info</p>
</div>
<div class="command-card">
<div class="command-header">
<h3 class="command-name">solarforecast</h3>
<div class="command-keywords">
<span class="keyword-badge">sf</span>
</div>
</div>
<p class="command-description">Get solar panel production forecast (usage: sf &lt;location|repeater_name|coordinates|zipcode&gt; [panel_size] [azimuth, 0=south] [angle])</p>
</div>
</div>
</div>
<div class="category-section" id="commands-sports">
<h2 class="category-title"><a href="#commands-sports" class="anchor-link">Sports</a></h2>
<div class="commands-grid">
<div class="command-card">
<div class="command-header">
<h3 class="command-name">sports</h3>
<div class="command-keywords">
<span class="keyword-badge">score</span>
<span class="keyword-badge">scores</span>
</div>
</div>
<p class="command-description">Get sports scores and schedules (usage: sports [team/league])</p>
<div class="command-channels">Channels: #BotTest, #bot, #sounders, #seahawks</div>
</div>
</div>
</div>
<div class="category-section" id="commands-weather">
<h2 class="category-title"><a href="#commands-weather" class="anchor-link">Weather Commands</a></h2>
<div class="commands-grid">
<div class="command-card">
<div class="command-header">
<h3 class="command-name">wx</h3>
<div class="command-keywords">
<span class="keyword-badge">weather</span>
<span class="keyword-badge">wxa</span>
<span class="keyword-badge">wxalert</span>
</div>
</div>
<p class="command-description">Get weather information for a zip code (usage: wx 12345)</p>
</div>
<div class="command-card">
<div class="command-header">
<h3 class="command-name">aqi</h3>
<div class="command-keywords">
<span class="keyword-badge">air</span>
<span class="keyword-badge">airquality</span>
<span class="keyword-badge">air_quality</span>
</div>
</div>
<p class="command-description">Get Air Quality Index for a location (usage: aqi seattle, aqi greenwood, aqi vancouver canada, aqi 47.6,-122.3, or aqi help)</p>
</div>
<div class="command-card">
<div class="command-header">
<h3 class="command-name">gwx</h3>
<div class="command-keywords">
<span class="keyword-badge">globalweather</span>
<span class="keyword-badge">gwxa</span>
</div>
</div>
<p class="command-description">Get weather information for any global location (usage: gwx Tokyo)</p>
</div>
</div>
</div>
<div class="category-section" id="channels">
<h2 class="category-title"><a href="#channels" class="anchor-link">Available Channels</a></h2>
<p class="channels-intro">These are semi-public channels that are in use on our local mesh! Join them to connect with others with common interests! <br/> To add these channels to your client, click on the three dot menu and select "Add Channel".</p>
<div class="channel-category" id="channels-general">
<h3 class="channel-category-title"><a href="#channels-general" class="anchor-link">General Channels</a></h3>
<div class="channels-grid">
<div class="channel-card">
<div class="channel-name">#bot</div>
<div class="channel-description">Channel for bot testing, diagnostics, and bot-related chat</div>
</div>
<div class="channel-card">
<div class="channel-name">#cats</div>
<div class="channel-description">Cat facts, stories, and feline-related conversation</div>
</div>
<div class="channel-card">
<div class="channel-name">#dev</div>
<div class="channel-description">Mesh developer chat for the Cascadia region</div>
</div>
<div class="channel-card">
<div class="channel-name">#eastside</div>
<div class="channel-description">Bellevue, Redmond, Kirkland, etc. chat</div>
</div>
<div class="channel-card">
<div class="channel-name">#emergency</div>
<div class="channel-description">Channel for emergency communications and urgent information</div>
</div>
<div class="channel-card">
<div class="channel-name">#hamradio</div>
<div class="channel-description">Amateur radio topics, licensing, and general ham discussion</div>
</div>
<div class="channel-card">
<div class="channel-name">#mesh</div>
<div class="channel-description">Mesh networking topics, node setup, and troubleshooting</div>
</div>
<div class="channel-card">
<div class="channel-name">#olympia</div>
<div class="channel-description">Olympia, WA chat</div>
</div>
<div class="channel-card">
<div class="channel-name">#pierce</div>
<div class="channel-description">Pierce County, WA chat</div>
</div>
<div class="channel-card">
<div class="channel-name">#queer</div>
<div class="channel-description">LGBTQ+ community chat and support</div>
</div>
<div class="channel-card">
<div class="channel-name">#seattle</div>
<div class="channel-description">Seattle, WA chat</div>
</div>
<div class="channel-card">
<div class="channel-name">#snoco</div>
<div class="channel-description">Snohomish County, WA chat</div>
</div>
<div class="channel-card">
<div class="channel-name">#sounders</div>
<div class="channel-description">Seattle Sounders FC soccer discussion and updates</div>
</div>
<div class="channel-card">
<div class="channel-name">#tacoma</div>
<div class="channel-description">Tacoma, WA chat</div>
</div>
</div>
</div>
<div class="channel-category" id="channels-emergency">
<h3 class="channel-category-title"><a href="#channels-emergency" class="anchor-link">Emergency</a></h3>
<div class="channels-grid">
<div class="channel-card">
<div class="channel-name">#emergency</div>
<div class="channel-description">Emergency communications and alerts</div>
</div>
<div class="channel-card">
<div class="channel-name">#hamradio</div>
<div class="channel-description">Amateur radio emergency net coordination</div>
</div>
<div class="channel-card">
<div class="channel-name">#weather</div>
<div class="channel-description">Weather updates and local conditions</div>
</div>
</div>
</div>
<div class="channel-category" id="channels-seattle">
<h3 class="channel-category-title"><a href="#channels-seattle" class="anchor-link">Seattle</a></h3>
<div class="channels-grid">
<div class="channel-card">
<div class="channel-name">#capitolhill</div>
<div class="channel-description">Capitol Hill neighborhood chat and local info</div>
</div>
<div class="channel-card">
<div class="channel-name">#kraken</div>
<div class="channel-description">Seattle Kraken chat for Seattle area users</div>
</div>
<div class="channel-card">
<div class="channel-name">#mariners</div>
<div class="channel-description">Seattle Mariners chat for Seattle area users</div>
</div>
<div class="channel-card">
<div class="channel-name">#queer</div>
<div class="channel-description">LGBTQ+ chat for Seattle area</div>
</div>
<div class="channel-card">
<div class="channel-name">#seahawks</div>
<div class="channel-description">Seattle Seahawks chat for Seattle area users</div>
</div>
<div class="channel-card">
<div class="channel-name">#seattle</div>
<div class="channel-description">General chat for Seattle area</div>
</div>
<div class="channel-card">
<div class="channel-name">#sounders</div>
<div class="channel-description">Seattle Sounders FC chat for Seattle area users</div>
</div>
<div class="channel-card">
<div class="channel-name">#tech</div>
<div class="channel-description">Technology topics and tech chat for Seattle area</div>
</div>
<div class="channel-card">
<div class="channel-name">#transit</div>
<div class="channel-description">Seattle Transit chat and bus schedules</div>
</div>
</div>
</div>
<div class="channel-category" id="channels-sports">
<h3 class="channel-category-title"><a href="#channels-sports" class="anchor-link">Sports</a></h3>
<div class="channels-grid">
<div class="channel-card">
<div class="channel-name">#huskies</div>
<div class="channel-description">Washington Huskies sports discussion</div>
</div>
<div class="channel-card">
<div class="channel-name">#kraken</div>
<div class="channel-description">Seattle Kraken NHL hockey discussion</div>
</div>
<div class="channel-card">
<div class="channel-name">#mariners</div>
<div class="channel-description">Seattle Mariners MLB baseball chat</div>
</div>
<div class="channel-card">
<div class="channel-name">#reign</div>
<div class="channel-description">OL Reign NWSL soccer discussion</div>
</div>
<div class="channel-card">
<div class="channel-name">#seahawks</div>
<div class="channel-description">Seattle Seahawks NFL football discussion</div>
</div>
<div class="channel-card">
<div class="channel-name">#sounders</div>
<div class="channel-description">Seattle Sounders FC soccer chat</div>
</div>
<div class="channel-card">
<div class="channel-name">#storm</div>
<div class="channel-description">Seattle Storm WNBA basketball chat</div>
</div>
</div>
</div>
<div class="channel-category" id="channels-vancouver">
<h3 class="channel-category-title"><a href="#channels-vancouver" class="anchor-link">Vancouver</a></h3>
<div class="channels-grid">
<div class="channel-card">
<div class="channel-name">#canucks</div>
<div class="channel-description">Vancouver Canucks NHL hockey discussion</div>
</div>
<div class="channel-card">
<div class="channel-name">#vancouverbc</div>
<div class="channel-description">General chat for Vancouver, BC area</div>
</div>
<div class="channel-card">
<div class="channel-name">#whitecaps</div>
<div class="channel-description">Vancouver Whitecaps MLS soccer chat</div>
</div>
</div>
</div>
</div>
</main>
<footer>
<p>Generated command reference for HowlBot🤖</p>
</footer>
</div>
</div>
<script>
// Handle mobile menu toggle
(function() {
'use strict';
const menuToggle = document.querySelector('.mobile-menu-toggle');
const sidebar = document.querySelector('.sidebar-nav');
const overlay = document.querySelector('.sidebar-overlay');
if (menuToggle && sidebar) {
function toggleMenu() {
const isOpen = sidebar.classList.contains('open');
if (isOpen) {
menuToggle.classList.remove('active');
sidebar.classList.remove('open');
if (overlay) {
overlay.classList.remove('visible');
}
} else {
menuToggle.classList.add('active');
sidebar.classList.add('open');
if (overlay) {
overlay.classList.add('visible');
}
}
}
function closeMenu() {
menuToggle.classList.remove('active');
sidebar.classList.remove('open');
if (overlay) {
overlay.classList.remove('visible');
}
}
menuToggle.addEventListener('click', function(e) {
e.stopPropagation();
e.preventDefault();
toggleMenu();
return false;
});
// Handle overlay clicks - overlay now only covers area after sidebar
if (overlay) {
overlay.addEventListener('click', function(e) {
closeMenu();
});
}
// Handle nav link clicks - don't prevent default, let browser handle navigation
const navLinks = sidebar.querySelectorAll('.nav-link');
navLinks.forEach(link => {
link.addEventListener('click', function(e) {
if (window.innerWidth <= 1200) {
// Close menu after a delay to allow navigation
setTimeout(function() {
closeMenu();
}, 200);
}
});
});
}
// Handle keyword expansion
const expandButtons = document.querySelectorAll('.keyword-expand');
expandButtons.forEach(button => {
button.addEventListener('click', function() {
const hiddenAliases = this.getAttribute('data-hidden').split(',');
const commandKeywords = this.closest('.command-keywords');
// Hide the expand button
this.classList.add('expanded');
// Add hidden aliases as visible badges
hiddenAliases.forEach(alias => {
const badge = document.createElement('span');
badge.className = 'keyword-badge keyword-hidden visible';
badge.textContent = alias.trim();
commandKeywords.appendChild(badge);
});
});
});
})();
</script>
</body>
</html>