feat: Implement dark mode theme and improve UI

- Add CSS variables for theming
- Implement dark mode toggle functionality
- Refactor styles for better organization and readability
- Update navigation bar with theme toggle button
- Enhance hero section with display-4 font size
- Adjust card styles for consistent appearance
- Improve alert and badge styling
- Make hero server title bold and larger
- Use Bootstrap 5 classes for consistent styling
- Add prefetch for Bootstrap JS
- Update `auth_enabled` default to false in server creation
This commit is contained in:
Mahmoud-Emad
2025-10-21 23:32:25 +03:00
parent 63c2efc921
commit 37f0aa0e96
4 changed files with 639 additions and 289 deletions

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env -S v -n -w -gc none -cc gcc -d use_openssl -enable-globals -no-skip-unused run #!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals -no-skip-unused run
import incubaid.herolib.hero.heromodels import incubaid.herolib.hero.heromodels
import incubaid.herolib.hero.db import incubaid.herolib.hero.db

View File

@@ -5,7 +5,7 @@ import incubaid.herolib.schemas.openrpc
import os import os
// 1. Create a new server instance // 1. Create a new server instance
mut server := heroserver.new(port: 8080)! mut server := heroserver.new(port: 8080, auth_enabled: false)!
// 2. Create and register your OpenRPC handlers // 2. Create and register your OpenRPC handlers
// These handlers must conform to the `openrpc.OpenRPCHandler` interface. // These handlers must conform to the `openrpc.OpenRPCHandler` interface.

View File

@@ -10,21 +10,123 @@
<link href="https://cdn.jsdelivr.net/npm/bootstrap@@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet"> <link href="https://cdn.jsdelivr.net/npm/bootstrap@@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<style> <style>
/* CSS Variables for Light and Dark Themes */
:root {
/* Light mode colors */
--bg-primary: #ffffff;
--bg-secondary: #f8f9fa;
--bg-tertiary: #e9ecef;
--text-primary: #212529;
--text-secondary: #6c757d;
--text-muted: #6c757d;
--border-color: #dee2e6;
--border-color-light: #e9ecef;
--link-color: #0d6efd;
--link-hover-color: #0a58ca;
--code-bg: #f8f9fa;
--code-text: #212529;
--card-bg: #ffffff;
--card-header-bg: #f8f9fa;
--method-group-bg: #f8f9fa;
--method-endpoint-bg: #e3f2fd;
--toc-bg: #f8f9fa;
--toc-group-bg: #f8f9fa;
--alert-info-bg: #cfe2ff;
--alert-info-border: #b6d4fe;
--alert-info-text: #084298;
--badge-bg: #0d6efd;
--badge-text: #ffffff;
}
/* Dark mode colors */
body.dark-mode {
--bg-primary: #1a1a1a;
--bg-secondary: #2d2d2d;
--bg-tertiary: #3a3a3a;
--text-primary: #e9ecef;
--text-secondary: #adb5bd;
--text-muted: #adb5bd;
--border-color: #495057;
--border-color-light: #3a3a3a;
--link-color: #6ea8fe;
--link-hover-color: #9ec5fe;
--code-bg: #2d2d2d;
--code-text: #e9ecef;
--card-bg: #2d2d2d;
--card-header-bg: #3a3a3a;
--method-group-bg: #2d2d2d;
--method-endpoint-bg: #1e3a5f;
--toc-bg: #2d2d2d;
--toc-group-bg: #3a3a3a;
--alert-info-bg: #052c65;
--alert-info-border: #084298;
--alert-info-text: #6ea8fe;
--badge-bg: #0d6efd;
--badge-text: #ffffff;
}
/* Smooth transitions for theme changes */
body,
.card,
.card-header,
.method-group-section,
.toc,
.toc-group,
.code-block,
.alert,
pre,
.method-endpoint {
transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
line-height: 1.6;
background-color: var(--bg-primary);
color: var(--text-primary);
}
.method-card { .method-card {
margin-bottom: 1.5rem; margin-bottom: 1.5rem;
background-color: var(--card-bg);
border-color: var(--border-color);
}
.card-header {
background-color: var(--card-header-bg);
border-color: var(--border-color);
color: var(--text-primary);
}
.card-body {
background-color: var(--card-bg);
color: var(--text-primary);
} }
.param-table { .param-table {
font-size: 0.9rem; font-size: 0.9rem;
color: var(--text-primary);
}
.param-table th,
.param-table td {
border-color: var(--border-color);
} }
.code-block { .code-block {
background-color: #f8f9fa; background-color: var(--code-bg);
border: 1px solid #e9ecef; border: 1px solid var(--border-color-light);
border-radius: 0.375rem; border-radius: 0.375rem;
padding: 1rem; padding: 1rem;
overflow-x: auto; overflow-x: auto;
font-family: 'Courier New', monospace; font-family: 'Courier New', monospace;
color: var(--code-text);
}
.code-block pre {
background-color: var(--code-bg);
color: var(--code-text);
margin: 0;
} }
.object-section { .object-section {
@@ -32,16 +134,17 @@
} }
.method-endpoint { .method-endpoint {
background-color: #e3f2fd; background-color: var(--method-endpoint-bg);
padding: 0.5rem; padding: 0.5rem;
border-radius: 0.25rem; border-radius: 0.25rem;
font-family: monospace; font-family: monospace;
font-size: 0.9rem; font-size: 0.9rem;
color: var(--text-primary);
} }
.toc { .toc {
background-color: #f8f9fa; background-color: var(--toc-bg);
border: 1px solid #dee2e6; border: 1px solid var(--border-color);
border-radius: 0.375rem; border-radius: 0.375rem;
padding: 1rem; padding: 1rem;
margin-bottom: 2rem; margin-bottom: 2rem;
@@ -53,22 +156,24 @@
.toc a { .toc a {
text-decoration: none; text-decoration: none;
color: #495057; color: var(--link-color);
} }
.toc a:hover { .toc a:hover {
color: #007bff; color: var(--link-hover-color);
} }
pre { pre {
margin: 0; margin: 0;
white-space: pre-wrap; white-space: pre-wrap;
word-wrap: break-word; word-wrap: break-word;
background-color: var(--code-bg);
color: var(--code-text);
} }
.curl-section { .curl-section {
background-color: #f8f9fa; background-color: var(--bg-secondary);
border: 1px solid #dee2e6; border: 1px solid var(--border-color);
border-radius: 0.375rem; border-radius: 0.375rem;
padding: 1rem; padding: 1rem;
margin-top: 1rem; margin-top: 1rem;
@@ -197,8 +302,8 @@
/* Method group section styles */ /* Method group section styles */
.method-group-section { .method-group-section {
margin-bottom: 2rem; margin-bottom: 2rem;
background-color: #f8f9fa; background-color: var(--method-group-bg);
border: 1px solid #dee2e6; border: 1px solid var(--border-color);
border-radius: 0.25rem; border-radius: 0.25rem;
padding: 1rem; padding: 1rem;
} }
@@ -224,13 +329,13 @@
margin: 0; margin: 0;
font-size: 1.25rem; font-size: 1.25rem;
font-weight: 600; font-weight: 600;
color: #495057; color: var(--text-primary);
} }
.method-group-toggle { .method-group-toggle {
transition: transform 0.3s ease; transition: transform 0.3s ease;
font-size: 1.2rem; font-size: 1.2rem;
color: #6c757d; color: var(--text-secondary);
} }
.method-group-toggle.collapsed { .method-group-toggle.collapsed {
@@ -242,21 +347,21 @@
} }
.method-group-content .method-card { .method-group-content .method-card {
background-color: #ffffff; background-color: var(--card-bg);
} }
/* TOC group styles */ /* TOC group styles */
.toc-group { .toc-group {
margin-bottom: 0.75rem; margin-bottom: 0.75rem;
background-color: #f8f9fa; background-color: var(--toc-group-bg);
border: 1px solid #e9ecef; border: 1px solid var(--border-color-light);
border-radius: 0.25rem; border-radius: 0.25rem;
padding: 0.5rem; padding: 0.5rem;
} }
.toc-group-header { .toc-group-header {
font-weight: 600; font-weight: 600;
color: #495057; color: var(--text-primary);
margin-bottom: 0; margin-bottom: 0;
cursor: pointer; cursor: pointer;
user-select: none; user-select: none;
@@ -267,13 +372,13 @@
} }
.toc-group-header:hover { .toc-group-header:hover {
color: #212529; color: var(--text-secondary);
} }
.toc-group-toggle { .toc-group-toggle {
transition: transform 0.3s ease; transition: transform 0.3s ease;
font-size: 0.9rem; font-size: 0.9rem;
color: #6c757d; color: var(--text-secondary);
margin-right: 0.5rem; margin-right: 0.5rem;
} }
@@ -290,10 +395,84 @@
.toc-group-methods li { .toc-group-methods li {
margin-bottom: 0.25rem; margin-bottom: 0.25rem;
} }
/* Theme toggle button */
.theme-toggle {
position: fixed;
top: 1rem;
right: 1rem;
z-index: 1000;
background-color: var(--card-bg);
border: 1px solid var(--border-color);
border-radius: 50%;
width: 3rem;
height: 3rem;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
transition: all 0.3s ease;
}
.theme-toggle:hover {
transform: scale(1.1);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
}
.theme-toggle svg {
width: 1.5rem;
height: 1.5rem;
fill: var(--text-primary);
}
.text-muted {
color: var(--text-muted) !important;
}
h1,
h2,
h3,
h4,
h5,
h6 {
color: var(--text-primary);
}
a {
color: var(--link-color);
}
a:hover {
color: var(--link-hover-color);
}
footer {
background-color: var(--bg-secondary) !important;
color: var(--text-primary);
border-top: 1px solid var(--border-color);
}
.badge {
background-color: var(--badge-bg) !important;
color: var(--badge-text) !important;
}
</style> </style>
</head> </head>
<body> <body>
<!-- Theme Toggle Button -->
<button class="theme-toggle" onclick="toggleTheme()" aria-label="Toggle dark mode" title="Toggle dark/light mode">
<svg id="theme-icon-sun" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" style="display: none;">
<path
d="M12 18C8.68629 18 6 15.3137 6 12C6 8.68629 8.68629 6 12 6C15.3137 6 18 8.68629 18 12C18 15.3137 15.3137 18 12 18ZM12 16C14.2091 16 16 14.2091 16 12C16 9.79086 14.2091 8 12 8C9.79086 8 8 9.79086 8 12C8 14.2091 9.79086 16 12 16ZM11 1H13V4H11V1ZM11 20H13V23H11V20ZM3.51472 4.92893L4.92893 3.51472L7.05025 5.63604L5.63604 7.05025L3.51472 4.92893ZM16.9497 18.364L18.364 16.9497L20.4853 19.0711L19.0711 20.4853L16.9497 18.364ZM19.0711 3.51472L20.4853 4.92893L18.364 7.05025L16.9497 5.63604L19.0711 3.51472ZM5.63604 16.9497L7.05025 18.364L4.92893 20.4853L3.51472 19.0711L5.63604 16.9497ZM23 11V13H20V11H23ZM4 11V13H1V11H4Z" />
</svg>
<svg id="theme-icon-moon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path
d="M10 7C10 10.866 13.134 14 17 14C18.9584 14 20.729 13.1957 21.9995 11.8995C22 11.933 22 11.9665 22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C12.0335 2 12.067 2 12.1005 2.00049C10.8043 3.27098 10 5.04157 10 7ZM4 12C4 16.4183 7.58172 20 12 20C15.0583 20 17.7158 18.2839 19.062 15.7621C18.3945 15.9187 17.7035 16 17 16C12.0294 16 8 11.9706 8 7C8 6.29648 8.08133 5.60547 8.2379 4.938C5.71611 6.28423 4 8.9417 4 12Z" />
</svg>
</button>
<div class="container mt-4"> <div class="container mt-4">
<!-- Header --> <!-- Header -->
<div class="row"> <div class="row">
@@ -657,6 +836,55 @@
<!-- Bootstrap JS Bundle (includes Popper) --> <!-- Bootstrap JS Bundle (includes Popper) -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@@5.3.3/dist/js/bootstrap.bundle.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
<!-- Theme Toggle Functionality -->
<script>
// Theme management
function toggleTheme() {
const body = document.body;
const isDark = body.classList.toggle('dark-mode');
// Save preference to localStorage
localStorage.setItem('theme', isDark ? 'dark' : 'light');
// Update icon visibility
updateThemeIcon(isDark);
}
function updateThemeIcon(isDark) {
const sunIcon = document.getElementById('theme-icon-sun');
const moonIcon = document.getElementById('theme-icon-moon');
if (isDark) {
sunIcon.style.display = 'block';
moonIcon.style.display = 'none';
} else {
sunIcon.style.display = 'none';
moonIcon.style.display = 'block';
}
}
function initializeTheme() {
// Check localStorage for saved preference
const savedTheme = localStorage.getItem('theme');
// If no saved preference, check system preference
const prefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
// Apply theme
const shouldBeDark = savedTheme === 'dark' || (!savedTheme && prefersDark);
if (shouldBeDark) {
document.body.classList.add('dark-mode');
}
// Update icon
updateThemeIcon(shouldBeDark);
}
// Initialize theme on page load
initializeTheme();
</script>
<!-- Copy to Clipboard Functionality --> <!-- Copy to Clipboard Functionality -->
<script> <script>
function copyToClipboard(elementId, button) { function copyToClipboard(elementId, button) {

View File

@@ -7,320 +7,395 @@
<title>HeroServer - API Gateway</title> <title>HeroServer - API Gateway</title>
<!-- Bootstrap CSS --> <!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet"> <link href="https://cdn.jsdelivr.net/npm/bootstrap@@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<style> <style>
:root { /* Smooth transitions for theme changes */
--google-blue: #1a73e8; body,
--google-blue-hover: #1557b0; .card,
--google-gray: #5f6368; .alert,
--google-light-gray: #f8f9fa; .btn,
--google-border: #dadce0; .navbar,
--google-text: #202124; .dropdown-menu {
--google-text-secondary: #5f6368; transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease;
} }
body { /* Dark mode overrides */
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; body.dark-mode {
color: var(--google-text); background-color: #1a1a1a;
line-height: 1.6; color: #e9ecef;
} }
.hero-section { body.dark-mode .navbar {
padding: 4rem 0; background-color: #2d2d2d !important;
margin-bottom: 3rem; border-bottom-color: #495057;
text-align: center;
} }
.hero-section h1 { body.dark-mode .card {
font-weight: 300; background-color: #2d2d2d;
font-size: 3.5rem; border-color: #495057;
margin-bottom: 1.5rem; color: #e9ecef;
color: var(--google-text);
} }
.hero-section .lead { body.dark-mode .bg-light {
font-weight: 400; background-color: #3a3a3a !important;
font-size: 1.25rem; color: #e9ecef;
color: var(--google-text-secondary);
margin-bottom: 1rem;
} }
.hero-section p { body.dark-mode pre,
color: var(--google-text-secondary); body.dark-mode code {
font-size: 1rem; background-color: #3a3a3a;
color: #e9ecef;
border-color: #495057;
} }
.feature-card { body.dark-mode .text-muted {
border: 1px solid var(--google-border); color: #adb5bd !important;
border-radius: 8px;
padding: 1.5rem;
height: 100%;
background: white;
} }
.feature-card h5 { body.dark-mode .border,
font-weight: 500; body.dark-mode .border-top {
color: var(--google-text); border-color: #495057 !important;
margin-bottom: 1rem;
} }
.feature-card p { body.dark-mode .dropdown-menu {
color: var(--google-text-secondary); background-color: #2d2d2d;
font-size: 0.95rem; border-color: #495057;
} }
.endpoint-card { body.dark-mode .dropdown-item {
border: 1px solid var(--google-border); color: #e9ecef;
border-radius: 8px;
padding: 1.5rem;
margin-bottom: 1rem;
background: white;
} }
.endpoint-url { body.dark-mode .dropdown-item:hover {
background: var(--google-light-gray); background-color: #3a3a3a;
border: 1px solid var(--google-border); color: #e9ecef;
padding: 0.75rem;
border-radius: 6px;
font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace;
font-size: 0.875rem;
margin-bottom: 0.75rem;
color: var(--google-text);
} }
.badge-custom { body.dark-mode .dropdown-header {
background: var(--google-blue); color: #adb5bd;
color: white;
font-size: 0.75rem;
font-weight: 500;
padding: 0.4rem 0.8rem;
border-radius: 4px;
} }
.btn-google-primary { body.dark-mode .navbar-toggler {
background: var(--google-blue); border-color: #495057;
border: 1px solid var(--google-blue);
color: white;
font-weight: 500;
padding: 0.5rem 1rem;
border-radius: 6px;
} }
.btn-google-secondary { body.dark-mode .navbar-toggler-icon {
background: white; filter: invert(1);
border: 1px solid var(--google-border);
color: var(--google-text);
font-weight: 500;
padding: 0.5rem 1rem;
border-radius: 6px;
} }
.section-spacing { body.dark-mode .nav-link {
margin-bottom: 3rem; color: #adb5bd;
} }
.code-block { body.dark-mode .nav-link:hover {
background: var(--google-light-gray); color: #6ea8fe;
border: 1px solid var(--google-border);
border-radius: 8px;
padding: 1.5rem;
font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace;
font-size: 0.875rem;
overflow-x: auto;
} }
.alert-google-info { body.dark-mode .navbar-brand {
background: #e8f0fe; color: #e9ecef;
border: 1px solid #d2e3fc;
border-radius: 8px;
padding: 1.5rem;
color: var(--google-text);
} }
.alert-google-success { /* Theme toggle button in navbar */
background: #e6f4ea; .theme-toggle-navbar {
border: 1px solid #ceead6; background-color: transparent;
border-radius: 8px; border: 1px solid #dee2e6;
padding: 1.5rem; border-radius: 50%;
color: var(--google-text); width: 2.5rem;
height: 2.5rem;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.3s ease;
margin-left: 1rem;
} }
.footer { body.dark-mode .theme-toggle-navbar {
border-top: 1px solid var(--google-border); border-color: #495057;
padding: 3rem 0;
margin-top: 6rem;
color: var(--google-text-secondary);
} }
h2 { .theme-toggle-navbar:hover {
font-weight: 400; background-color: #f8f9fa;
color: var(--google-text); transform: scale(1.05);
margin-bottom: 2rem;
} }
.container { body.dark-mode .theme-toggle-navbar:hover {
max-width: 1200px; background-color: #3a3a3a;
margin: 0 auto;
padding: 0 1rem;
} }
.text-muted { .theme-toggle-navbar svg {
color: var(--google-text-secondary) !important; width: 1.25rem;
} height: 1.25rem;
/* Ensure proper spacing and alignment */
.row {
margin-left: 0;
margin-right: 0;
}
.col-12,
.col-md-4,
.col-md-6 {
padding-left: 0.75rem;
padding-right: 0.75rem;
}
/* Fix button spacing */
.d-flex.gap-2 {
gap: 0.5rem !important;
} }
/* Responsive improvements */ /* Responsive improvements */
@media (max-width: 768px) { @media (max-width: 768px) {
.hero-section { .theme-toggle-navbar {
padding: 3rem 0; margin-left: 0;
margin-bottom: 2rem; margin-top: 0.5rem;
}
.hero-section h1 {
font-size: 2.5rem;
}
.feature-card,
.endpoint-card {
margin-bottom: 1rem;
padding: 1rem;
}
.section-spacing {
margin-bottom: 2rem;
} }
} }
</style> </style>
</head> </head>
<body> <body>
<!-- Navigation Bar -->
<nav class="navbar navbar-expand-lg navbar-light bg-light border-bottom sticky-top">
<div class="container">
<a class="navbar-brand fw-semibold" href="/">
HeroServer
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav"
aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav ms-auto align-items-center">
@if server_info.handlers.len > 0
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button"
data-bs-toggle="dropdown" aria-expanded="false">
API Documentation
</a>
<ul class="dropdown-menu" aria-labelledby="navbarDropdown">
@for handler_name, _ in server_info.handlers
<li>
<h6 class="dropdown-header">${handler_name.to_upper()}</h6>
</li>
<li><a class="dropdown-item" href="/doc/${handler_name}">Documentation</a></li>
<li><a class="dropdown-item" href="/json/${handler_name}">OpenRPC JSON</a></li>
<li><a class="dropdown-item" href="/md/${handler_name}">Markdown Docs</a></li>
@end
</ul>
</li>
@end
<li class="nav-item">
<a class="nav-link" href="#features">Features</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#endpoints">Endpoints</a>
</li>
<li class="nav-item">
<button class="theme-toggle-navbar" onclick="toggleTheme()" aria-label="Toggle dark mode"
title="Toggle dark/light mode">
<svg id="theme-icon-sun" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"
style="display: none;">
<path
d="M12 18C8.68629 18 6 15.3137 6 12C6 8.68629 8.68629 6 12 6C15.3137 6 18 8.68629 18 12C18 15.3137 15.3137 18 12 18ZM12 16C14.2091 16 16 14.2091 16 12C16 9.79086 14.2091 8 12 8C9.79086 8 8 9.79086 8 12C8 14.2091 9.79086 16 12 16ZM11 1H13V4H11V1ZM11 20H13V23H11V20ZM3.51472 4.92893L4.92893 3.51472L7.05025 5.63604L5.63604 7.05025L3.51472 4.92893ZM16.9497 18.364L18.364 16.9497L20.4853 19.0711L19.0711 20.4853L16.9497 18.364ZM19.0711 3.51472L20.4853 4.92893L18.364 7.05025L16.9497 5.63604L19.0711 3.51472ZM5.63604 16.9497L7.05025 18.364L4.92893 20.4853L3.51472 19.0711L5.63604 16.9497ZM23 11V13H20V11H23ZM4 11V13H1V11H4Z" />
</svg>
<svg id="theme-icon-moon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path
d="M10 7C10 10.866 13.134 14 17 14C18.9584 14 20.729 13.1957 21.9995 11.8995C22 11.933 22 11.9665 22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C12.0335 2 12.067 2 12.1005 2.00049C10.8043 3.27098 10 5.04157 10 7ZM4 12C4 16.4183 7.58172 20 12 20C15.0583 20 17.7158 18.2839 19.062 15.7621C18.3945 15.9187 17.7035 16 17 16C12.0294 16 8 11.9706 8 7C8 6.29648 8.08133 5.60547 8.2379 4.938C5.71611 6.28423 4 8.9417 4 12Z" />
</svg>
</button>
</li>
</ul>
</div>
</div>
</nav>
<!-- Hero Section --> <!-- Hero Section -->
<div class="hero-section"> <div class="py-5 text-center">
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<div class="col-12"> <div class="col-12">
<h1>HeroServer</h1> <h1 class="display-4 fw-light">HeroServer</h1>
<p class="lead">Modern JSON-RPC 2.0 API Gateway with Dynamic Documentation</p> <p class="lead">Production-Ready JSON-RPC 2.0 API Gateway</p>
<p class="mb-4">A powerful, secure, and developer-friendly API server built with V language</p> <p class="text-muted">A high-performance, secure, and developer-friendly API server built with V
language</p>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="container"> <div class="container">
<!-- Features Section --> <!-- What is JSON-RPC Section -->
<div class="row section-spacing"> <div class="row mb-5">
<div class="col-12"> <div class="col-12">
<h2 class="text-center">Key Features</h2> <div class="card">
</div> <div class="card-body p-4">
<div class="col-md-4 mb-4"> <h3 class="card-title">What is JSON-RPC?</h3>
<div class="feature-card text-center"> <p class="card-text">
<h5>JSON-RPC 2.0</h5> JSON-RPC is a stateless, light-weight remote procedure call (RPC) protocol. It defines
<p>Full compliance with JSON-RPC 2.0 specification for reliable API communication</p> several
data structures and the rules around their processing. It is transport agnostic in that the
concepts can be used within the same process, over sockets, over HTTP, or in many various
message passing environments.
</p>
<p class="card-text">
<strong>Why JSON-RPC 2.0?</strong> Unlike REST APIs, JSON-RPC provides a simpler, more
predictable interface with built-in error handling, batch requests, and a standardized
specification that ensures consistency across implementations.
</p>
</div>
</div> </div>
</div> </div>
<div class="col-md-4 mb-4"> </div>
<div class="feature-card text-center">
<h5>Dynamic Documentation</h5> <!-- Key Features Section -->
<p>Auto-generated interactive documentation with curl examples and copy buttons</p> <div class="row mb-5" id="features">
<div class="col-12">
<h2 class="text-center mb-4">Key Features</h2>
</div>
<div class="col-md-4 mb-3">
<div class="card h-100 text-center">
<div class="card-body">
<h5 class="card-title">High Performance</h5>
<p class="card-text">Built with V language for exceptional speed and low memory footprint.
Handles thousands of concurrent requests efficiently.</p>
</div>
</div> </div>
</div> </div>
<div class="col-md-4 mb-4"> <div class="col-md-4 mb-3">
<div class="feature-card text-center"> <div class="card h-100 text-center">
<h5>Secure Authentication</h5> <div class="card-body">
<p>Built-in cryptographic authentication with public key infrastructure</p> <h5 class="card-title">JSON-RPC 2.0 Compliant</h5>
<p class="card-text">Full compliance with JSON-RPC 2.0 specification including batch requests,
notifications, and standardized error codes.</p>
</div>
</div>
</div>
<div class="col-md-4 mb-3">
<div class="card h-100 text-center">
<div class="card-body">
<h5 class="card-title">Auto-Generated Docs</h5>
<p class="card-text">Interactive OpenRPC-based documentation with live examples, curl commands,
and schema validation.</p>
</div>
</div>
</div>
<div class="col-md-4 mb-3">
<div class="card h-100 text-center">
<div class="card-body">
<h5 class="card-title">Secure by Default</h5>
<p class="card-text">Built-in cryptographic authentication with public key infrastructure and
session management.</p>
</div>
</div>
</div>
<div class="col-md-4 mb-3">
<div class="card h-100 text-center">
<div class="card-body">
<h5 class="card-title">Type-Safe Schema</h5>
<p class="card-text">JSON Schema validation for all requests and responses ensures data
integrity and API reliability.</p>
</div>
</div>
</div>
<div class="col-md-4 mb-3">
<div class="card h-100 text-center">
<div class="card-body">
<h5 class="card-title">Developer-Friendly</h5>
<p class="card-text">Clean API design, comprehensive error messages, and multiple documentation
formats (HTML, JSON, Markdown).</p>
</div>
</div>
</div>
</div>
<!-- Getting Started Section -->
<div class="row mb-5">
<div class="col-12">
<div class="card">
<div class="card-body p-4">
<h3 class="card-title">Getting Started</h3>
<p class="card-text">
HeroServer provides a simple and intuitive API interface. All requests are sent as HTTP POST
requests to the server endpoint with a JSON-RPC 2.0 formatted payload.
</p>
<h5 class="mt-4 mb-3">Basic Request Structure:</h5>
<pre class="p-3 rounded"><code>{
"jsonrpc": "2.0",
"method": "method_name",
"params": { /* your parameters */ },
"id": 1
}</code></pre>
<h5 class="mt-4 mb-3">Quick Start Steps:</h5>
<ul class="list-unstyled">
<li class="mb-2"><strong>Explore the API:</strong> Browse the available endpoints below to
see what methods are available</li>
<li class="mb-2"><strong>Read the Documentation:</strong> Click on "API Documentation" in
the navbar to view detailed API specifications</li>
<li class="mb-2"><strong>Test with curl:</strong> Use the provided curl examples to test API
calls directly</li>
<li class="mb-2"><strong>Integrate:</strong> Use any HTTP client library in your preferred
programming language</li>
</ul>
</div>
</div> </div>
</div> </div>
</div> </div>
<!-- Available Endpoints Section --> <!-- Available Endpoints Section -->
<div class="row section-spacing"> <div class="row mb-5" id="endpoints">
<div class="col-12"> <div class="col-12">
<h2 class="mb-2">Available API Endpoints</h2> <h2 class="mb-3">Available API Endpoints</h2>
<p class="text-muted mb-3">Explore the available API handlers and their documentation</p> <p class="text-muted mb-4">Explore the available API handlers and their documentation</p>
</div> </div>
</div> </div>
<div class="row"> <div class="row">
@for handler_name in server_info.handlers.keys() @for handler_name in server_info.handlers.keys()
<div class="col-md-6 mb-4"> <div class="col-12 mb-4">
<div class="endpoint-card"> <div class="card">
<h5 class="mb-3"> <div class="card-body">
<span class="badge-custom me-2">API</span> <h5 class="card-title mb-3">
${handler_name.title()} <span class="badge bg-primary me-2">API</span>
</h5> ${handler_name.to_upper()}
</h5>
<div class="endpoint-url"> <div class="mb-2 p-2 bg-light rounded border">
<strong>API Endpoint:</strong> /api/${handler_name} <small><strong>API Endpoint:</strong> <code>/api/${handler_name}</code></small>
</div> </div>
<div class="endpoint-url"> <div class="mb-2 p-2 bg-light rounded border">
<strong>Documentation:</strong> /doc/${handler_name} <small><strong>Documentation:</strong> <code>/doc/${handler_name}</code></small>
</div> </div>
<div class="endpoint-url"> <div class="mb-3 p-2 bg-light rounded border">
<strong>JSON Info:</strong> /json/${handler_name} <small><strong>JSON Info:</strong> <code>/json/${handler_name}</code></small>
</div> </div>
<p class="text-muted mb-3"> <p class="card-text text-muted mb-3">
JSON-RPC 2.0 API for ${handler_name} operations with full CRUD support JSON-RPC 2.0 API for ${handler_name} operations with full CRUD support
</p> </p>
<div class="d-flex gap-2"> <div class="d-flex flex-wrap gap-2">
<a href="/doc/${handler_name}" class="btn btn-google-primary btn-sm"> <a href="/doc/${handler_name}" class="btn btn-primary btn-sm">
View Documentation View Documentation
</a> </a>
<a href="/json/${handler_name}" class="btn btn-google-secondary btn-sm"> <a href="/json/${handler_name}" class="btn btn-outline-secondary btn-sm">
JSON Info JSON Info
</a> </a>
<a href="/md/${handler_name}" class="btn btn-google-secondary btn-sm"> <a href="/md/${handler_name}" class="btn btn-outline-secondary btn-sm">
Markdown Docs Markdown Docs
</a> </a>
<a href="/api/${handler_name}" class="btn btn-google-secondary btn-sm" </div>
onclick="alert('This is a JSON-RPC endpoint. Use POST requests with JSON-RPC 2.0 format. See documentation for examples.'); return false;">
API Endpoint
</a>
</div> </div>
</div> </div>
@end
</div> </div>
@end
</div>
<!-- Quick Start Section --> <!-- Quick Start Section -->
<div class="row section-spacing"> <div class="row mb-5">
<div class="col-12"> <div class="col-12">
<h2 class="mb-3">Quick Start</h2> <h2 class="mb-3">Quick Start</h2>
<div class="endpoint-card"> <div class="card">
<h5 class="mb-2">Getting Handler Information</h5> <div class="card-body p-4">
<p class="text-muted mb-3">Get detailed information about a specific API handler in JSON format:</p> <h5 class="card-title">Getting Handler Information</h5>
<p class="card-text text-muted mb-3">Get detailed information about a specific API handler
in
JSON format:</p>
<div class="code-block"> <pre class="p-3 rounded"><code>curl ${server_info.base_url}/json/[handler_name]</code></pre>
<pre>curl ${server_info.base_url}/json/[handler_name]</pre>
</div>
<h5 class="mb-2 mt-4">Making API Calls</h5> <h5 class="card-title mt-4">Making API Calls</h5>
<p class="text-muted mb-3">All API endpoints use JSON-RPC 2.0 format. Here's a basic example:</p> <p class="card-text text-muted mb-3">All API endpoints use JSON-RPC 2.0 format. Here's a
basic
example:</p>
<div class="code-block"> <pre class="p-3 rounded"><code>curl -X POST ${server_info.base_url}/api/[handler_name] \
<pre>curl -X POST ${server_info.base_url}/api/[handler_name] \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d '{ -d '{
"jsonrpc": "2.0", "jsonrpc": "2.0",
@@ -330,64 +405,111 @@
"param2": "value2" "param2": "value2"
}, },
"id": 1 "id": 1
}'</pre> }'</code></pre>
</div>
<div class="mt-3"> <div class="alert alert-info mt-3 mb-0">
<small class="text-muted"> <small><strong>Tip:</strong> Visit the documentation pages for specific examples with
<strong>Tip:</strong> Visit the documentation pages for specific examples with copy-to-clipboard functionality</small>
copy-to-clipboard functionality </div>
</small> </div>
</div> </div>
</div> </div>
</div> </div>
</div>
@if server_info.auth_enabled @if server_info.auth_enabled
<!-- Authentication Section --> <!-- Authentication Section -->
<div class="row section-spacing"> <div class="row mb-5">
<div class="col-12"> <div class="col-12">
<h2 class="mb-3">Authentication</h2> <h2 class="mb-3">Authentication</h2>
<div class="alert-google-info"> <div class="alert alert-warning">
<h5 class="mb-2">Authentication Required</h5> <h5 class="alert-heading">Authentication Required</h5>
<p class="mb-0"> <p class="mb-0">
This server requires authentication. Please register your public key and obtain a session token This server requires authentication. Please register your public key and obtain a session
before making API calls. See the documentation for detailed authentication flow. token before making API calls. See the documentation for detailed authentication flow.
</p> </p>
</div>
</div> </div>
</div> </div>
</div> @else
@else <!-- No Authentication Notice -->
<!-- No Authentication Notice --> <div class="row mb-5">
<div class="row section-spacing"> <div class="col-12">
<div class="col-12"> <h2 class="mb-3">Authentication</h2>
<h2 class="mb-3">Authentication</h2> <div class="alert alert-success">
<div class="alert-google-success"> <h5 class="alert-heading">Open Access</h5>
<h5 class="mb-2">Open Access</h5> <p class="mb-0">
<p class="mb-0"> Authentication is currently disabled. You can make API calls directly without
Authentication is currently disabled. You can make API calls directly without authentication. authentication.
</p> </p>
</div>
</div> </div>
</div> </div>
@end
</div> </div>
@end
</div>
<!-- Footer --> <!-- Footer -->
<footer class="footer"> <footer class="border-top py-4 mt-5">
<div class="container text-center"> <div class="container text-center">
<p class="mb-0"> <p class="text-muted mb-0">
<strong>HeroServer</strong> - Built with V language • <strong>HeroServer</strong> - Built with V language •
<a href="https://github.com/incubaid/herolib" target="_blank" class="text-decoration-none"> <a href="https://github.com/incubaid/herolib" target="_blank" class="text-decoration-none">
View on GitHub View on GitHub
</a> </a>
</p> </p>
</div> </div>
</footer> </footer>
<!-- Bootstrap JS --> <!-- Bootstrap JS -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@@5.3.3/dist/js/bootstrap.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
<!-- Theme Toggle Functionality -->
<script>
// Theme management
function toggleTheme() {
const body = document.body;
const isDark = body.classList.toggle('dark-mode');
// Save preference to localStorage
localStorage.setItem('theme', isDark ? 'dark' : 'light');
// Update icon visibility
updateThemeIcon(isDark);
}
function updateThemeIcon(isDark) {
const sunIcon = document.getElementById('theme-icon-sun');
const moonIcon = document.getElementById('theme-icon-moon');
if (isDark) {
sunIcon.style.display = 'block';
moonIcon.style.display = 'none';
} else {
sunIcon.style.display = 'none';
moonIcon.style.display = 'block';
}
}
function initializeTheme() {
// Check localStorage for saved preference
const savedTheme = localStorage.getItem('theme');
// If no saved preference, check system preference
const prefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
// Apply theme
const shouldBeDark = savedTheme === 'dark' || (!savedTheme && prefersDark);
if (shouldBeDark) {
document.body.classList.add('dark-mode');
}
// Update icon
updateThemeIcon(shouldBeDark);
}
// Initialize theme on page load
initializeTheme();
</script>
</body> </body>
</html> </html>