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:
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user