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.db

View File

@@ -5,7 +5,7 @@ import incubaid.herolib.schemas.openrpc
import os
// 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
// 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">
<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 {
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 {
font-size: 0.9rem;
color: var(--text-primary);
}
.param-table th,
.param-table td {
border-color: var(--border-color);
}
.code-block {
background-color: #f8f9fa;
border: 1px solid #e9ecef;
background-color: var(--code-bg);
border: 1px solid var(--border-color-light);
border-radius: 0.375rem;
padding: 1rem;
overflow-x: auto;
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 {
@@ -32,16 +134,17 @@
}
.method-endpoint {
background-color: #e3f2fd;
background-color: var(--method-endpoint-bg);
padding: 0.5rem;
border-radius: 0.25rem;
font-family: monospace;
font-size: 0.9rem;
color: var(--text-primary);
}
.toc {
background-color: #f8f9fa;
border: 1px solid #dee2e6;
background-color: var(--toc-bg);
border: 1px solid var(--border-color);
border-radius: 0.375rem;
padding: 1rem;
margin-bottom: 2rem;
@@ -53,22 +156,24 @@
.toc a {
text-decoration: none;
color: #495057;
color: var(--link-color);
}
.toc a:hover {
color: #007bff;
color: var(--link-hover-color);
}
pre {
margin: 0;
white-space: pre-wrap;
word-wrap: break-word;
background-color: var(--code-bg);
color: var(--code-text);
}
.curl-section {
background-color: #f8f9fa;
border: 1px solid #dee2e6;
background-color: var(--bg-secondary);
border: 1px solid var(--border-color);
border-radius: 0.375rem;
padding: 1rem;
margin-top: 1rem;
@@ -197,8 +302,8 @@
/* Method group section styles */
.method-group-section {
margin-bottom: 2rem;
background-color: #f8f9fa;
border: 1px solid #dee2e6;
background-color: var(--method-group-bg);
border: 1px solid var(--border-color);
border-radius: 0.25rem;
padding: 1rem;
}
@@ -224,13 +329,13 @@
margin: 0;
font-size: 1.25rem;
font-weight: 600;
color: #495057;
color: var(--text-primary);
}
.method-group-toggle {
transition: transform 0.3s ease;
font-size: 1.2rem;
color: #6c757d;
color: var(--text-secondary);
}
.method-group-toggle.collapsed {
@@ -242,21 +347,21 @@
}
.method-group-content .method-card {
background-color: #ffffff;
background-color: var(--card-bg);
}
/* TOC group styles */
.toc-group {
margin-bottom: 0.75rem;
background-color: #f8f9fa;
border: 1px solid #e9ecef;
background-color: var(--toc-group-bg);
border: 1px solid var(--border-color-light);
border-radius: 0.25rem;
padding: 0.5rem;
}
.toc-group-header {
font-weight: 600;
color: #495057;
color: var(--text-primary);
margin-bottom: 0;
cursor: pointer;
user-select: none;
@@ -267,13 +372,13 @@
}
.toc-group-header:hover {
color: #212529;
color: var(--text-secondary);
}
.toc-group-toggle {
transition: transform 0.3s ease;
font-size: 0.9rem;
color: #6c757d;
color: var(--text-secondary);
margin-right: 0.5rem;
}
@@ -290,10 +395,84 @@
.toc-group-methods li {
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>
</head>
<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">
<!-- Header -->
<div class="row">
@@ -657,6 +836,55 @@
<!-- Bootstrap JS Bundle (includes Popper) -->
<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 -->
<script>
function copyToClipboard(elementId, button) {

View File

@@ -7,320 +7,395 @@
<title>HeroServer - API Gateway</title>
<!-- 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>
:root {
--google-blue: #1a73e8;
--google-blue-hover: #1557b0;
--google-gray: #5f6368;
--google-light-gray: #f8f9fa;
--google-border: #dadce0;
--google-text: #202124;
--google-text-secondary: #5f6368;
/* Smooth transitions for theme changes */
body,
.card,
.alert,
.btn,
.navbar,
.dropdown-menu {
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;
color: var(--google-text);
line-height: 1.6;
/* Dark mode overrides */
body.dark-mode {
background-color: #1a1a1a;
color: #e9ecef;
}
.hero-section {
padding: 4rem 0;
margin-bottom: 3rem;
text-align: center;
body.dark-mode .navbar {
background-color: #2d2d2d !important;
border-bottom-color: #495057;
}
.hero-section h1 {
font-weight: 300;
font-size: 3.5rem;
margin-bottom: 1.5rem;
color: var(--google-text);
body.dark-mode .card {
background-color: #2d2d2d;
border-color: #495057;
color: #e9ecef;
}
.hero-section .lead {
font-weight: 400;
font-size: 1.25rem;
color: var(--google-text-secondary);
margin-bottom: 1rem;
body.dark-mode .bg-light {
background-color: #3a3a3a !important;
color: #e9ecef;
}
.hero-section p {
color: var(--google-text-secondary);
font-size: 1rem;
body.dark-mode pre,
body.dark-mode code {
background-color: #3a3a3a;
color: #e9ecef;
border-color: #495057;
}
.feature-card {
border: 1px solid var(--google-border);
border-radius: 8px;
padding: 1.5rem;
height: 100%;
background: white;
body.dark-mode .text-muted {
color: #adb5bd !important;
}
.feature-card h5 {
font-weight: 500;
color: var(--google-text);
margin-bottom: 1rem;
body.dark-mode .border,
body.dark-mode .border-top {
border-color: #495057 !important;
}
.feature-card p {
color: var(--google-text-secondary);
font-size: 0.95rem;
body.dark-mode .dropdown-menu {
background-color: #2d2d2d;
border-color: #495057;
}
.endpoint-card {
border: 1px solid var(--google-border);
border-radius: 8px;
padding: 1.5rem;
margin-bottom: 1rem;
background: white;
body.dark-mode .dropdown-item {
color: #e9ecef;
}
.endpoint-url {
background: var(--google-light-gray);
border: 1px solid var(--google-border);
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);
body.dark-mode .dropdown-item:hover {
background-color: #3a3a3a;
color: #e9ecef;
}
.badge-custom {
background: var(--google-blue);
color: white;
font-size: 0.75rem;
font-weight: 500;
padding: 0.4rem 0.8rem;
border-radius: 4px;
body.dark-mode .dropdown-header {
color: #adb5bd;
}
.btn-google-primary {
background: var(--google-blue);
border: 1px solid var(--google-blue);
color: white;
font-weight: 500;
padding: 0.5rem 1rem;
border-radius: 6px;
body.dark-mode .navbar-toggler {
border-color: #495057;
}
.btn-google-secondary {
background: white;
border: 1px solid var(--google-border);
color: var(--google-text);
font-weight: 500;
padding: 0.5rem 1rem;
border-radius: 6px;
body.dark-mode .navbar-toggler-icon {
filter: invert(1);
}
.section-spacing {
margin-bottom: 3rem;
body.dark-mode .nav-link {
color: #adb5bd;
}
.code-block {
background: var(--google-light-gray);
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;
body.dark-mode .nav-link:hover {
color: #6ea8fe;
}
.alert-google-info {
background: #e8f0fe;
border: 1px solid #d2e3fc;
border-radius: 8px;
padding: 1.5rem;
color: var(--google-text);
body.dark-mode .navbar-brand {
color: #e9ecef;
}
.alert-google-success {
background: #e6f4ea;
border: 1px solid #ceead6;
border-radius: 8px;
padding: 1.5rem;
color: var(--google-text);
/* Theme toggle button in navbar */
.theme-toggle-navbar {
background-color: transparent;
border: 1px solid #dee2e6;
border-radius: 50%;
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 {
border-top: 1px solid var(--google-border);
padding: 3rem 0;
margin-top: 6rem;
color: var(--google-text-secondary);
body.dark-mode .theme-toggle-navbar {
border-color: #495057;
}
h2 {
font-weight: 400;
color: var(--google-text);
margin-bottom: 2rem;
.theme-toggle-navbar:hover {
background-color: #f8f9fa;
transform: scale(1.05);
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 1rem;
body.dark-mode .theme-toggle-navbar:hover {
background-color: #3a3a3a;
}
.text-muted {
color: var(--google-text-secondary) !important;
}
/* 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;
.theme-toggle-navbar svg {
width: 1.25rem;
height: 1.25rem;
}
/* Responsive improvements */
@media (max-width: 768px) {
.hero-section {
padding: 3rem 0;
margin-bottom: 2rem;
}
.hero-section h1 {
font-size: 2.5rem;
}
.feature-card,
.endpoint-card {
margin-bottom: 1rem;
padding: 1rem;
}
.section-spacing {
margin-bottom: 2rem;
.theme-toggle-navbar {
margin-left: 0;
margin-top: 0.5rem;
}
}
</style>
</head>
<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 -->
<div class="hero-section">
<div class="py-5 text-center">
<div class="container">
<div class="row">
<div class="col-12">
<h1>HeroServer</h1>
<p class="lead">Modern JSON-RPC 2.0 API Gateway with Dynamic Documentation</p>
<p class="mb-4">A powerful, secure, and developer-friendly API server built with V language</p>
<h1 class="display-4 fw-light">HeroServer</h1>
<p class="lead">Production-Ready JSON-RPC 2.0 API Gateway</p>
<p class="text-muted">A high-performance, secure, and developer-friendly API server built with V
language</p>
</div>
</div>
</div>
</div>
<div class="container">
<!-- Features Section -->
<div class="row section-spacing">
<!-- What is JSON-RPC Section -->
<div class="row mb-5">
<div class="col-12">
<h2 class="text-center">Key Features</h2>
</div>
<div class="col-md-4 mb-4">
<div class="feature-card text-center">
<h5>JSON-RPC 2.0</h5>
<p>Full compliance with JSON-RPC 2.0 specification for reliable API communication</p>
<div class="card">
<div class="card-body p-4">
<h3 class="card-title">What is JSON-RPC?</h3>
<p class="card-text">
JSON-RPC is a stateless, light-weight remote procedure call (RPC) protocol. It defines
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 class="col-md-4 mb-4">
<div class="feature-card text-center">
<h5>Dynamic Documentation</h5>
<p>Auto-generated interactive documentation with curl examples and copy buttons</p>
</div>
<!-- Key Features Section -->
<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 class="col-md-4 mb-4">
<div class="feature-card text-center">
<h5>Secure Authentication</h5>
<p>Built-in cryptographic authentication with public key infrastructure</p>
<div class="col-md-4 mb-3">
<div class="card h-100 text-center">
<div class="card-body">
<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>
<!-- Available Endpoints Section -->
<div class="row section-spacing">
<div class="row mb-5" id="endpoints">
<div class="col-12">
<h2 class="mb-2">Available API Endpoints</h2>
<p class="text-muted mb-3">Explore the available API handlers and their documentation</p>
<h2 class="mb-3">Available API Endpoints</h2>
<p class="text-muted mb-4">Explore the available API handlers and their documentation</p>
</div>
</div>
<div class="row">
@for handler_name in server_info.handlers.keys()
<div class="col-md-6 mb-4">
<div class="endpoint-card">
<h5 class="mb-3">
<span class="badge-custom me-2">API</span>
${handler_name.title()}
</h5>
<div class="col-12 mb-4">
<div class="card">
<div class="card-body">
<h5 class="card-title mb-3">
<span class="badge bg-primary me-2">API</span>
${handler_name.to_upper()}
</h5>
<div class="endpoint-url">
<strong>API Endpoint:</strong> /api/${handler_name}
</div>
<div class="mb-2 p-2 bg-light rounded border">
<small><strong>API Endpoint:</strong> <code>/api/${handler_name}</code></small>
</div>
<div class="endpoint-url">
<strong>Documentation:</strong> /doc/${handler_name}
</div>
<div class="mb-2 p-2 bg-light rounded border">
<small><strong>Documentation:</strong> <code>/doc/${handler_name}</code></small>
</div>
<div class="endpoint-url">
<strong>JSON Info:</strong> /json/${handler_name}
</div>
<div class="mb-3 p-2 bg-light rounded border">
<small><strong>JSON Info:</strong> <code>/json/${handler_name}</code></small>
</div>
<p class="text-muted mb-3">
JSON-RPC 2.0 API for ${handler_name} operations with full CRUD support
</p>
<p class="card-text text-muted mb-3">
JSON-RPC 2.0 API for ${handler_name} operations with full CRUD support
</p>
<div class="d-flex gap-2">
<a href="/doc/${handler_name}" class="btn btn-google-primary btn-sm">
View Documentation
</a>
<a href="/json/${handler_name}" class="btn btn-google-secondary btn-sm">
JSON Info
</a>
<a href="/md/${handler_name}" class="btn btn-google-secondary btn-sm">
Markdown Docs
</a>
<a href="/api/${handler_name}" class="btn btn-google-secondary btn-sm"
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 class="d-flex flex-wrap gap-2">
<a href="/doc/${handler_name}" class="btn btn-primary btn-sm">
View Documentation
</a>
<a href="/json/${handler_name}" class="btn btn-outline-secondary btn-sm">
JSON Info
</a>
<a href="/md/${handler_name}" class="btn btn-outline-secondary btn-sm">
Markdown Docs
</a>
</div>
</div>
</div>
@end
</div>
@end
</div>
<!-- Quick Start Section -->
<div class="row section-spacing">
<div class="col-12">
<h2 class="mb-3">Quick Start</h2>
<div class="endpoint-card">
<h5 class="mb-2">Getting Handler Information</h5>
<p class="text-muted mb-3">Get detailed information about a specific API handler in JSON format:</p>
<!-- Quick Start Section -->
<div class="row mb-5">
<div class="col-12">
<h2 class="mb-3">Quick Start</h2>
<div class="card">
<div class="card-body p-4">
<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>curl ${server_info.base_url}/json/[handler_name]</pre>
</div>
<pre class="p-3 rounded"><code>curl ${server_info.base_url}/json/[handler_name]</code></pre>
<h5 class="mb-2 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>
<h5 class="card-title mt-4">Making API Calls</h5>
<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>curl -X POST ${server_info.base_url}/api/[handler_name] \
<pre class="p-3 rounded"><code>curl -X POST ${server_info.base_url}/api/[handler_name] \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
@@ -330,64 +405,111 @@
"param2": "value2"
},
"id": 1
}'</pre>
</div>
}'</code></pre>
<div class="mt-3">
<small class="text-muted">
<strong>Tip:</strong> Visit the documentation pages for specific examples with
copy-to-clipboard functionality
</small>
<div class="alert alert-info mt-3 mb-0">
<small><strong>Tip:</strong> Visit the documentation pages for specific examples with
copy-to-clipboard functionality</small>
</div>
</div>
</div>
</div>
</div>
</div>
@if server_info.auth_enabled
<!-- Authentication Section -->
<div class="row section-spacing">
<div class="col-12">
<h2 class="mb-3">Authentication</h2>
<div class="alert-google-info">
<h5 class="mb-2">Authentication Required</h5>
<p class="mb-0">
This server requires authentication. Please register your public key and obtain a session token
before making API calls. See the documentation for detailed authentication flow.
</p>
@if server_info.auth_enabled
<!-- Authentication Section -->
<div class="row mb-5">
<div class="col-12">
<h2 class="mb-3">Authentication</h2>
<div class="alert alert-warning">
<h5 class="alert-heading">Authentication Required</h5>
<p class="mb-0">
This server requires authentication. Please register your public key and obtain a session
token before making API calls. See the documentation for detailed authentication flow.
</p>
</div>
</div>
</div>
</div>
@else
<!-- No Authentication Notice -->
<div class="row section-spacing">
<div class="col-12">
<h2 class="mb-3">Authentication</h2>
<div class="alert-google-success">
<h5 class="mb-2">Open Access</h5>
<p class="mb-0">
Authentication is currently disabled. You can make API calls directly without authentication.
</p>
@else
<!-- No Authentication Notice -->
<div class="row mb-5">
<div class="col-12">
<h2 class="mb-3">Authentication</h2>
<div class="alert alert-success">
<h5 class="alert-heading">Open Access</h5>
<p class="mb-0">
Authentication is currently disabled. You can make API calls directly without
authentication.
</p>
</div>
</div>
</div>
@end
</div>
@end
</div>
<!-- Footer -->
<footer class="footer">
<div class="container text-center">
<p class="mb-0">
<strong>HeroServer</strong> - Built with V language •
<a href="https://github.com/incubaid/herolib" target="_blank" class="text-decoration-none">
View on GitHub
</a>
</p>
</div>
</footer>
<!-- Footer -->
<footer class="border-top py-4 mt-5">
<div class="container text-center">
<p class="text-muted mb-0">
<strong>HeroServer</strong> - Built with V language •
<a href="https://github.com/incubaid/herolib" target="_blank" class="text-decoration-none">
View on GitHub
</a>
</p>
</div>
</footer>
<!-- 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>
<!-- Bootstrap JS -->
<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>
</html>