quantum-dashboard / index.html
Toowired's picture
Update index.html
a7b8d19 verified
Raw
History Blame Contribute Delete
73.7 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Quantum API Dashboard</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
@import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400;500;600;700&family=Major+Mono+Display&display=swap');
:root {
--primary-hue: 210;
--primary-saturation: 80%;
--primary-lightness: 50%;
}
body {
font-family: 'Orbitron', sans-serif;
background: radial-gradient(circle at center, #0f172a 0%, #020617 100%);
overflow: hidden;
color: #e2e8f0;
touch-action: none;
}
.dashboard {
position: relative;
width: 100vw;
height: 100vh;
perspective: 2000px;
transform-style: preserve-3d;
}
.button-3d {
position: absolute;
width: 140px;
height: 140px;
border-radius: 20px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
cursor: move;
user-select: none;
transform-style: preserve-3d;
transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.5);
z-index: 10;
border: 1px solid rgba(255, 255, 255, 0.1);
backdrop-filter: blur(5px);
background: linear-gradient(145deg, rgba(255,255,255,0.1), rgba(255,255,255,0.05));
}
.button-3d:hover {
transform: translateY(-8px) rotateX(15deg) scale(1.05);
box-shadow: 0 20px 40px -10px rgba(0, 0, 0, 0.6);
z-index: 20;
}
.button-3d:active {
transform: translateY(0) rotateX(0) scale(0.98);
}
.button-3d::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
border-radius: inherit;
background: linear-gradient(135deg, rgba(255,255,255,0.2) 0%, transparent 50%);
opacity: 0;
transition: opacity 0.3s;
}
.button-3d:hover::before {
opacity: 1;
}
.button-icon {
font-size: 3rem;
margin-bottom: 12px;
text-shadow: 0 4px 8px rgba(0,0,0,0.4);
transition: all 0.3s;
}
.button-label {
font-size: 1rem;
font-weight: 600;
text-shadow: 0 2px 4px rgba(0,0,0,0.4);
text-align: center;
max-width: 90%;
word-break: break-word;
}
.button-status {
position: absolute;
bottom: 10px;
right: 10px;
width: 12px;
height: 12px;
border-radius: 50%;
background: #4ade80;
box-shadow: 0 0 10px #4ade80;
}
.modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(2, 6, 23, 0.95);
backdrop-filter: blur(20px);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
opacity: 0;
pointer-events: none;
transition: opacity 0.4s, transform 0.4s;
transform: scale(0.9);
}
.modal-content {
background: linear-gradient(145deg, rgba(30, 41, 59, 0.9), rgba(15, 23, 42, 0.9));
border-radius: 24px;
width: 85%;
max-width: 800px;
max-height: 90vh;
overflow-y: auto;
padding: 2.5rem;
box-shadow: 0 35px 60px -15px rgba(0, 0, 0, 0.7);
border: 1px solid rgba(255, 255, 255, 0.15);
transform: translateY(30px);
transition: transform 0.4s;
position: relative;
}
.modal.active {
opacity: 1;
pointer-events: all;
transform: scale(1);
}
.modal.active .modal-content {
transform: translateY(0);
}
.control-panel {
position: fixed;
bottom: 3rem;
right: 3rem;
z-index: 500;
display: flex;
flex-direction: column;
gap: 1rem;
}
.control-button {
width: 70px;
height: 70px;
border-radius: 50%;
background: linear-gradient(145deg, hsl(var(--primary-hue), var(--primary-saturation), var(--primary-lightness)),
hsl(var(--primary-hue), calc(var(--primary-saturation) - 10%), calc(var(--primary-lightness) - 10%)));
display: flex;
justify-content: center;
align-items: center;
font-size: 1.8rem;
color: white;
box-shadow: 0 15px 25px -5px rgba(0, 0, 0, 0.4);
cursor: pointer;
transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
position: relative;
overflow: hidden;
}
.control-button::after {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(45deg, rgba(255,255,255,0.2) 0%, transparent 100%);
opacity: 0;
transition: opacity 0.3s;
}
.control-button:hover {
transform: translateY(-5px) scale(1.1);
box-shadow: 0 20px 30px -5px rgba(0, 0, 0, 0.5);
}
.control-button:hover::after {
opacity: 1;
}
.control-button:active {
transform: translateY(0) scale(0.98);
}
.grid-lines {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image:
linear-gradient(rgba(255, 255, 255, 0.03) 1px, transparent 1px),
linear-gradient(90deg, rgba(255, 255, 255, 0.03) 1px, transparent 1px);
background-size: 60px 60px;
z-index: 0;
opacity: 0.7;
}
.particles {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1;
pointer-events: none;
}
.particle {
position: absolute;
width: 2px;
height: 2px;
background: rgba(255,255,255,0.5);
border-radius: 50%;
animation: float 15s infinite linear;
}
@keyframes float {
0% {
transform: translateY(0) translateX(0);
opacity: 0;
}
10% {
opacity: 1;
}
90% {
opacity: 1;
}
100% {
transform: translateY(-100vh) translateX(20px);
opacity: 0;
}
}
/* Custom scrollbar */
::-webkit-scrollbar {
width: 10px;
}
::-webkit-scrollbar-track {
background: rgba(255, 255, 255, 0.05);
border-radius: 10px;
}
::-webkit-scrollbar-thumb {
background: hsl(var(--primary-hue), var(--primary-saturation), var(--primary-lightness));
border-radius: 10px;
}
/* Form elements */
input, textarea, select {
background-color: rgba(15, 23, 42, 0.7);
border: 1px solid rgba(255, 255, 255, 0.15);
border-radius: 12px;
padding: 0.9rem 1.2rem;
color: white;
width: 100%;
margin-bottom: 1rem;
transition: all 0.3s;
font-family: 'Orbitron', sans-serif;
}
input:focus, textarea:focus, select:focus {
outline: none;
border-color: hsl(var(--primary-hue), var(--primary-saturation), var(--primary-lightness));
box-shadow: 0 0 0 3px hsla(var(--primary-hue), var(--primary-saturation), var(--primary-lightness), 0.3);
background-color: rgba(15, 23, 42, 0.9);
}
.form-label {
display: block;
margin-bottom: 0.7rem;
font-weight: 600;
font-size: 0.95rem;
letter-spacing: 0.5px;
}
.btn {
background: linear-gradient(145deg, hsl(var(--primary-hue), var(--primary-saturation), var(--primary-lightness)),
hsl(var(--primary-hue), calc(var(--primary-saturation) - 10%), calc(var(--primary-lightness) - 10%)));
color: white;
border: none;
padding: 1rem 2rem;
border-radius: 12px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
font-family: 'Orbitron', sans-serif;
letter-spacing: 0.5px;
position: relative;
overflow: hidden;
}
.btn::after {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(45deg, rgba(255,255,255,0.2) 0%, transparent 100%);
opacity: 0;
transition: opacity 0.3s;
}
.btn:hover {
transform: translateY(-3px);
box-shadow: 0 15px 25px -5px rgba(0, 0, 0, 0.4);
}
.btn:hover::after {
opacity: 1;
}
.btn:active {
transform: translateY(0);
}
.btn-secondary {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
}
.btn-secondary:hover {
background: rgba(255, 255, 255, 0.15);
}
.color-option {
width: 30px;
height: 30px;
border-radius: 50%;
cursor: pointer;
transition: all 0.3s;
border: 2px solid transparent;
}
.color-option:hover {
transform: scale(1.1);
}
.color-option.active {
transform: scale(1.2);
border: 2px solid white;
box-shadow: 0 0 10px rgba(255,255,255,0.5);
}
.tab {
padding: 0.8rem 1.5rem;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s;
font-weight: 500;
background: transparent;
color: rgba(255,255,255,0.7);
}
.tab.active {
background: rgba(255,255,255,0.1);
color: white;
font-weight: 600;
}
.tab:hover:not(.active) {
background: rgba(255,255,255,0.05);
}
.tab-content {
display: none;
}
.tab-content.active {
display: block;
animation: fadeIn 0.4s;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
.response-tabs {
display: flex;
border-bottom: 1px solid rgba(255,255,255,0.1);
margin-bottom: 1.5rem;
}
.response-tab {
padding: 0.7rem 1.5rem;
cursor: pointer;
border-bottom: 3px solid transparent;
transition: all 0.3s;
font-size: 0.9rem;
color: rgba(255,255,255,0.7);
}
.response-tab.active {
border-bottom: 3px solid hsl(var(--primary-hue), var(--primary-saturation), var(--primary-lightness));
color: white;
}
.response-tab:hover:not(.active) {
color: white;
}
.response-content {
display: none;
}
.response-content.active {
display: block;
}
.code-block {
background: rgba(0,0,0,0.3);
border-radius: 8px;
padding: 1rem;
font-family: 'Major Mono Display', monospace;
font-size: 0.85rem;
line-height: 1.6;
overflow-x: auto;
margin-bottom: 1rem;
}
.connection-line {
position: absolute;
background: rgba(255,255,255,0.2);
height: 2px;
transform-origin: left center;
z-index: 5;
pointer-events: none;
}
.connection-line::after {
content: '';
position: absolute;
right: 0;
top: 50%;
width: 10px;
height: 10px;
background: hsl(var(--primary-hue), var(--primary-saturation), var(--primary-lightness));
border-radius: 50%;
transform: translateY(-50%);
box-shadow: 0 0 10px hsl(var(--primary-hue), var(--primary-saturation), var(--primary-lightness));
}
.connection-mode {
position: absolute;
background: rgba(15, 23, 42, 0.9);
border: 1px solid rgba(255,255,255,0.2);
padding: 0.3rem 0.6rem;
border-radius: 20px;
font-size: 0.7rem;
z-index: 6;
pointer-events: none;
transform: translate(-50%, -50%);
}
.mini-map {
position: fixed;
bottom: 3rem;
left: 3rem;
width: 200px;
height: 200px;
background: rgba(15, 23, 42, 0.7);
border: 1px solid rgba(255,255,255,0.1);
border-radius: 8px;
z-index: 500;
overflow: hidden;
backdrop-filter: blur(5px);
}
.mini-map-viewport {
position: absolute;
border: 2px solid hsl(var(--primary-hue), var(--primary-saturation), var(--primary-lightness));
background: rgba(59, 130, 246, 0.2);
z-index: 2;
}
.mini-map-content {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
transform-origin: 0 0;
}
.mini-map-button {
position: absolute;
width: 6px;
height: 6px;
border-radius: 50%;
transform: translate(-50%, -50%);
}
.context-menu {
position: absolute;
background: rgba(30, 41, 59, 0.95);
border: 1px solid rgba(255,255,255,0.1);
border-radius: 8px;
box-shadow: 0 10px 25px -5px rgba(0,0,0,0.5);
z-index: 1000;
backdrop-filter: blur(10px);
display: none;
overflow: hidden;
}
.context-menu-item {
padding: 0.8rem 1.5rem;
cursor: pointer;
transition: all 0.2s;
display: flex;
align-items: center;
gap: 0.8rem;
}
.context-menu-item:hover {
background: rgba(255,255,255,0.1);
}
.context-menu-item i {
width: 20px;
text-align: center;
}
.dashboard-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 5;
pointer-events: none;
}
.dashboard-title {
position: absolute;
top: 2rem;
left: 2rem;
font-size: 1.8rem;
font-weight: 700;
z-index: 10;
background: linear-gradient(to right, #fff, #aaa);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
text-shadow: 0 2px 10px rgba(0,0,0,0.3);
}
.dashboard-subtitle {
position: absolute;
top: 4.5rem;
left: 2rem;
font-size: 1rem;
color: rgba(255,255,255,0.7);
z-index: 10;
max-width: 300px;
}
.dashboard-bg {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: radial-gradient(circle at 20% 30%, hsla(var(--primary-hue), 50%, 30%, 0.3) 0%, transparent 50%),
radial-gradient(circle at 80% 70%, hsla(calc(var(--primary-hue) + 60), 50%, 30%, 0.3) 0%, transparent 50%);
z-index: 0;
transition: all 1s;
}
.connection-mode-selector {
position: absolute;
top: 0;
left: 50%;
transform: translateX(-50%);
background: rgba(30, 41, 59, 0.9);
border-radius: 0 0 8px 8px;
padding: 0.5rem 1rem;
display: flex;
gap: 0.5rem;
z-index: 100;
border: 1px solid rgba(255,255,255,0.1);
border-top: none;
}
.connection-mode-btn {
padding: 0.3rem 0.8rem;
border-radius: 4px;
cursor: pointer;
font-size: 0.8rem;
transition: all 0.2s;
}
.connection-mode-btn.active {
background: hsl(var(--primary-hue), var(--primary-saturation), var(--primary-lightness));
color: white;
}
.connection-mode-btn:not(.active):hover {
background: rgba(255,255,255,0.1);
}
.button-tags {
position: absolute;
top: 10px;
left: 10px;
display: flex;
gap: 5px;
flex-wrap: wrap;
max-width: 80%;
}
.button-tag {
background: rgba(0,0,0,0.3);
border-radius: 4px;
padding: 2px 6px;
font-size: 0.6rem;
color: rgba(255,255,255,0.8);
white-space: nowrap;
}
.button-actions {
position: absolute;
top: 10px;
right: 10px;
display: none;
gap: 5px;
}
.button-3d:hover .button-actions {
display: flex;
}
.button-action {
width: 20px;
height: 20px;
border-radius: 4px;
background: rgba(0,0,0,0.3);
display: flex;
align-items: center;
justify-content: center;
font-size: 0.7rem;
cursor: pointer;
transition: all 0.2s;
}
.button-action:hover {
background: rgba(255,255,255,0.2);
}
.button-group {
position: absolute;
border: 2px dashed rgba(255,255,255,0.3);
border-radius: 8px;
z-index: 4;
background: rgba(59, 130, 246, 0.1);
display: none;
}
.button-group.active {
display: block;
}
.button-group-label {
position: absolute;
top: -25px;
left: 0;
background: rgba(30, 41, 59, 0.9);
padding: 0.3rem 0.6rem;
border-radius: 4px;
font-size: 0.8rem;
white-space: nowrap;
}
.dashboard-toolbar {
position: absolute;
top: 2rem;
right: 2rem;
display: flex;
gap: 1rem;
z-index: 10;
}
.toolbar-btn {
padding: 0.5rem 1rem;
border-radius: 6px;
background: rgba(255,255,255,0.1);
backdrop-filter: blur(5px);
cursor: pointer;
transition: all 0.2s;
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 0.9rem;
}
.toolbar-btn:hover {
background: rgba(255,255,255,0.2);
}
.toolbar-btn.active {
background: hsl(var(--primary-hue), var(--primary-saturation), var(--primary-lightness));
color: white;
}
.search-bar {
position: absolute;
top: 2rem;
left: 50%;
transform: translateX(-50%);
width: 300px;
z-index: 10;
}
.search-input {
width: 100%;
padding: 0.7rem 1.2rem;
border-radius: 30px;
background: rgba(30, 41, 59, 0.8);
border: 1px solid rgba(255,255,255,0.1);
color: white;
font-size: 0.9rem;
transition: all 0.3s;
backdrop-filter: blur(5px);
}
.search-input:focus {
outline: none;
border-color: hsl(var(--primary-hue), var(--primary-saturation), var(--primary-lightness));
box-shadow: 0 0 0 3px hsla(var(--primary-hue), var(--primary-saturation), var(--primary-lightness), 0.3);
width: 400px;
}
.search-results {
position: absolute;
top: 100%;
left: 0;
width: 100%;
background: rgba(30, 41, 59, 0.95);
border-radius: 0 0 12px 12px;
max-height: 300px;
overflow-y: auto;
display: none;
box-shadow: 0 10px 25px -5px rgba(0,0,0,0.5);
border: 1px solid rgba(255,255,255,0.1);
border-top: none;
z-index: 100;
}
.search-result-item {
padding: 0.8rem 1.2rem;
cursor: pointer;
transition: all 0.2s;
border-bottom: 1px solid rgba(255,255,255,0.05);
}
.search-result-item:hover {
background: rgba(255,255,255,0.1);
}
.search-result-item:last-child {
border-bottom: none;
}
.search-result-icon {
margin-right: 0.8rem;
color: hsl(var(--primary-hue), var(--primary-saturation), var(--primary-lightness));
}
.search-input.active + .search-results {
display: block;
}
.dashboard-stats {
position: absolute;
bottom: 2rem;
left: 50%;
transform: translateX(-50%);
display: flex;
gap: 1.5rem;
background: rgba(30, 41, 59, 0.8);
border-radius: 30px;
padding: 0.7rem 1.5rem;
z-index: 10;
backdrop-filter: blur(5px);
border: 1px solid rgba(255,255,255,0.1);
}
.stat-item {
display: flex;
flex-direction: column;
align-items: center;
}
.stat-value {
font-size: 1.2rem;
font-weight: 600;
color: hsl(var(--primary-hue), var(--primary-saturation), var(--primary-lightness));
}
.stat-label {
font-size: 0.7rem;
opacity: 0.8;
margin-top: 0.2rem;
}
.dashboard-help {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: rgba(30, 41, 59, 0.95);
border-radius: 16px;
padding: 2rem;
max-width: 600px;
width: 80%;
max-height: 80vh;
overflow-y: auto;
z-index: 1000;
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.7);
border: 1px solid rgba(255, 255, 255, 0.15);
display: none;
}
.dashboard-help.active {
display: block;
animation: fadeIn 0.4s;
}
.help-close {
position: absolute;
top: 1rem;
right: 1rem;
cursor: pointer;
font-size: 1.2rem;
color: rgba(255,255,255,0.7);
transition: all 0.2s;
}
.help-close:hover {
color: white;
}
.help-section {
margin-bottom: 2rem;
}
.help-title {
font-size: 1.5rem;
margin-bottom: 1rem;
color: hsl(var(--primary-hue), var(--primary-saturation), var(--primary-lightness));
}
.help-content {
line-height: 1.6;
color: rgba(255,255,255,0.8);
}
.help-shortcut {
display: flex;
justify-content: space-between;
padding: 0.8rem 0;
border-bottom: 1px solid rgba(255,255,255,0.1);
}
.help-shortcut:last-child {
border-bottom: none;
}
.shortcut-keys {
display: flex;
gap: 0.5rem;
}
.shortcut-key {
background: rgba(255,255,255,0.1);
padding: 0.3rem 0.6rem;
border-radius: 4px;
font-family: 'Major Mono Display', monospace;
}
.api-visualizer {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1000;
display: none;
}
.api-visualizer.active {
display: block;
}
.visualizer-canvas {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.visualizer-controls {
position: absolute;
bottom: 2rem;
left: 50%;
transform: translateX(-50%);
display: flex;
gap: 1rem;
z-index: 1001;
}
.visualizer-btn {
padding: 0.8rem 1.5rem;
border-radius: 30px;
background: rgba(30, 41, 59, 0.8);
border: 1px solid rgba(255,255,255,0.1);
color: white;
cursor: pointer;
transition: all 0.2s;
backdrop-filter: blur(5px);
}
.visualizer-btn:hover {
background: rgba(255,255,255,0.1);
}
.visualizer-btn i {
margin-right: 0.5rem;
}
.dashboard-tour {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 10000;
pointer-events: none;
}
.tour-highlight {
position: absolute;
border-radius: 8px;
box-shadow: 0 0 0 9999px rgba(0,0,0,0.8), 0 0 0 3px hsl(var(--primary-hue), var(--primary-saturation), var(--primary-lightness));
z-index: 10001;
transition: all 0.3s;
}
.tour-tooltip {
position: absolute;
background: rgba(30, 41, 59, 0.95);
border-radius: 8px;
padding: 1.5rem;
max-width: 300px;
box-shadow: 0 10px 25px -5px rgba(0,0,0,0.5);
border: 1px solid rgba(255,255,255,0.1);
z-index: 10002;
pointer-events: auto;
}
.tour-tooltip-title {
font-size: 1.2rem;
margin-bottom: 0.8rem;
color: hsl(var(--primary-hue), var(--primary-saturation), var(--primary-lightness));
}
.tour-tooltip-content {
margin-bottom: 1.5rem;
line-height: 1.5;
}
.tour-tooltip-footer {
display: flex;
justify-content: space-between;
align-items: center;
}
.tour-progress {
font-size: 0.8rem;
opacity: 0.7;
}
.tour-btn {
padding: 0.5rem 1rem;
border-radius: 4px;
background: hsl(var(--primary-hue), var(--primary-saturation), var(--primary-lightness));
color: white;
cursor: pointer;
transition: all 0.2s;
}
.tour-btn:hover {
opacity: 0.9;
}
.tour-btn-secondary {
background: rgba(255,255,255,0.1);
}
.dashboard-loading {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: #0f172a;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
z-index: 10000;
}
.loading-spinner {
width: 50px;
height: 50px;
border: 5px solid rgba(255,255,255,0.1);
border-radius: 50%;
border-top-color: hsl(var(--primary-hue), var(--primary-saturation), var(--primary-lightness));
animation: spin 1s linear infinite;
margin-bottom: 1.5rem;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.loading-text {
font-size: 1.2rem;
letter-spacing: 2px;
background: linear-gradient(to right, #fff, #aaa);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.loading-progress {
width: 200px;
height: 4px;
background: rgba(255,255,255,0.1);
border-radius: 2px;
margin-top: 1.5rem;
overflow: hidden;
}
.loading-progress-bar {
height: 100%;
background: hsl(var(--primary-hue), var(--primary-saturation), var(--primary-lightness));
width: 0%;
transition: width 0.3s;
}
/* Responsive adjustments */
@media (max-width: 768px) {
.dashboard-title, .dashboard-subtitle {
left: 1rem;
}
.dashboard-toolbar {
right: 1rem;
top: 1rem;
}
.control-panel {
right: 1rem;
bottom: 1rem;
}
.mini-map {
left: 1rem;
bottom: 1rem;
width: 150px;
height: 150px;
}
.dashboard-stats {
bottom: 1rem;
padding: 0.5rem 1rem;
gap: 1rem;
}
.search-bar {
top: 1rem;
width: 200px;
}
.search-input:focus {
width: 250px;
}
.modal-content {
width: 90%;
padding: 1.5rem;
}
.button-3d {
width: 100px;
height: 100px;
}
.button-icon {
font-size: 2rem;
}
.button-label {
font-size: 0.8rem;
}
}
</style>
</head>
<body>
<div class="dashboard-loading">
<div class="loading-spinner"></div>
<div class="loading-text">QUANTUM DASHBOARD</div>
<div class="loading-progress">
<div class="loading-progress-bar" id="loadingBar"></div>
</div>
</div>
<div class="dashboard" id="dashboard">
<div class="dashboard-bg"></div>
<div class="grid-lines"></div>
<div class="particles" id="particles"></div>
<div class="dashboard-overlay"></div>
<div class="dashboard-title">Quantum API Dashboard</div>
<div class="dashboard-subtitle">Visualize, connect and manage your API ecosystem in 3D space</div>
<div class="dashboard-toolbar">
<div class="toolbar-btn" id="groupModeBtn">
<i class="fas fa-object-group"></i> Group Mode
</div>
<div class="toolbar-btn" id="connectionModeBtn">
<i class="fas fa-plug"></i> Connect APIs
</div>
<div class="toolbar-btn" id="visualizerBtn">
<i class="fas fa-project-diagram"></i> Visualizer
</div>
<div class="toolbar-btn" id="helpBtn">
<i class="fas fa-question-circle"></i> Help
</div>
</div>
<div class="search-bar">
<input type="text" class="search-input" placeholder="Search APIs, endpoints, tags..." id="searchInput">
<div class="search-results" id="searchResults"></div>
</div>
<div class="dashboard-stats">
<div class="stat-item">
<div class="stat-value" id="apiCount">0</div>
<div class="stat-label">APIs</div>
</div>
<div class="stat-item">
<div class="stat-value" id="connectionCount">0</div>
<div class="stat-label">Connections</div>
</div>
<div class="stat-item">
<div class="stat-value" id="groupCount">0</div>
<div class="stat-label">Groups</div>
</div>
<div class="stat-item">
<div class="stat-value" id="statusValue">100%</div>
<div class="stat-label">Status</div>
</div>
</div>
<!-- 3D Buttons will be added here dynamically -->
<!-- Connection lines will be added here dynamically -->
<!-- Button groups will be added here dynamically -->
<!-- Connection mode selector (hidden by default) -->
<div class="connection-mode-selector" id="connectionModeSelector" style="display: none;">
<div class="connection-mode-btn active" data-mode="sequential">Sequential</div>
<div class="connection-mode-btn" data-mode="parallel">Parallel</div>
<div class="connection-mode-btn" data-mode="conditional">Conditional</div>
<div class="connection-mode-btn" data-mode="fallback">Fallback</div>
</div>
<!-- Control Panel -->
<div class="control-panel">
<div class="control-button" id="settingsButton">
<i class="fas fa-cog"></i>
</div>
<div class="control-button" id="addButton">
<i class="fas fa-plus"></i>
</div>
<div class="control-button" id="minimapButton">
<i class="fas fa-map"></i>
</div>
</div>
<!-- Mini Map -->
<div class="mini-map" id="miniMap" style="display: none;">
<div class="mini-map-content" id="miniMapContent"></div>
<div class="mini-map-viewport" id="miniMapViewport"></div>
</div>
</div>
<!-- Context Menu -->
<div class="context-menu" id="contextMenu">
<div class="context-menu-item" data-action="edit">
<i class="fas fa-edit"></i> Edit
</div>
<div class="context-menu-item" data-action="delete">
<i class="fas fa-trash"></i> Delete
</div>
<div class="context-menu-item" data-action="duplicate">
<i class="fas fa-copy"></i> Duplicate
</div>
<div class="context-menu-item" data-action="test">
<i class="fas fa-bolt"></i> Test API
</div>
<div class="context-menu-item" data-action="document">
<i class="fas fa-book"></i> Documentation
</div>
<div class="context-menu-item" data-action="group">
<i class="fas fa-object-group"></i> Create Group
</div>
</div>
<!-- Settings Modal -->
<div class="modal" id="settingsModal">
<div class="modal-content">
<div class="flex justify-between items-center mb-6">
<h2 class="text-2xl font-bold">Dashboard Settings</h2>
<button class="text-gray-400 hover:text-white" id="closeSettings">
<i class="fas fa-times"></i>
</button>
</div>
<div class="response-tabs mb-6">
<div class="response-tab active" data-tab="appearance">Appearance</div>
<div class="response-tab" data-tab="behavior">Behavior</div>
<div class="response-tab" data-tab="shortcuts">Shortcuts</div>
<div class="response-tab" data-tab="importexport">Import/Export</div>
</div>
<div class="tab-content active" data-tab-content="appearance">
<div class="mb-8">
<h3 class="text-xl font-semibold mb-4">Color Theme</h3>
<div class="grid grid-cols-2 md:grid-cols-4 gap-4 mb-6">
<div class="color-theme-option" data-theme="blue">
<div class="color-theme-preview bg-blue-500"></div>
<div class="color-theme-label">Ocean</div>
</div>
<div class="color-theme-option" data-theme="purple">
<div class="color-theme-preview bg-purple-500"></div>
<div class="color-theme-label">Royal</div>
</div>
<div class="color-theme-option" data-theme="green">
<div class="color-theme-preview bg-green-500"></div>
<div class="color-theme-label">Emerald</div>
</div>
<div class="color-theme-option" data-theme="red">
<div class="color-theme-preview bg-red-500"></div>
<div class="color-theme-label">Ruby</div>
</div>
<div class="color-theme-option" data-theme="yellow">
<div class="color-theme-preview bg-yellow-500"></div>
<div class="color-theme-label">Gold</div>
</div>
<div class="color-theme-option" data-theme="pink">
<div class="color-theme-preview bg-pink-500"></div>
<div class="color-theme-label">Rose</div>
</div>
<div class="color-theme-option" data-theme="indigo">
<div class="color-theme-preview bg-indigo-500"></div>
<div class="color-theme-label">Sapphire</div>
</div>
<div class="color-theme-option" data-theme="teal">
<div class="color-theme-preview bg-teal-500"></div>
<div class="color-theme-label">Aqua</div>
</div>
</div>
<h3 class="text-xl font-semibold mb-4">Dashboard Appearance</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6">
<div>
<label class="form-label">Background Intensity</label>
<input type="range" id="bgIntensity" min="0" max="100" value="50" class="w-full">
</div>
<div>
<label class="form-label">Grid Visibility</label>
<select id="gridVisibility" class="bg-gray-800">
<option value="visible">Visible</option>
<option value="hidden">Hidden</option>
</select>
</div>
<div>
<label class="form-label">Particle Density</label>
<input type="range" id="particleDensity" min="0" max="100" value="30" class="w-full">
</div>
<div>
<label class="form-label">Animation Speed</label>
<select id="animationSpeed" class="bg-gray-800">
<option value="slow">Slow</option>
<option value="normal" selected>Normal</option>
<option value="fast">Fast</option>
</select>
</div>
</div>
</div>
<div class="flex space-x-3">
<button class="btn">Save Changes</button>
<button class="btn btn-secondary">Reset Defaults</button>
</div>
</div>
<div class="tab-content" data-tab-content="behavior">
<div class="mb-8">
<h3 class="text-xl font-semibold mb-4">Interaction Settings</h3>
<div class="space-y-4 mb-6">
<div class="flex items-center justify-between">
<label class="form-label">Enable Hover Effects</label>
<label class="switch">
<input type="checkbox" checked>
<span class="slider"></span>
</label>
</div>
<div class="flex items-center justify-between">
<label class="form-label">Enable Dragging</label>
<label class="switch">
<input type="checkbox" checked>
<span class="slider"></span>
</label>
</div>
<div class="flex items-center justify-between">
<label class="form-label">Enable Context Menu</label>
<label class="switch">
<input type="checkbox" checked>
<span class="slider"></span>
</label>
</div>
<div class="flex items-center justify-between">
<label class="form-label">Auto-arrange on Add</label>
<label class="switch">
<input type="checkbox">
<span class="slider"></span>
</label>
</div>
</div>
<h3 class="text-xl font-semibold mb-4">API Behavior</h3>
<div class="space-y-4 mb-6">
<div class="flex items-center justify-between">
<label class="form-label">Auto-test on Click</label>
<label class="switch">
<input type="checkbox">
<span class="slider"></span>
</label>
</div>
<div class="flex items-center justify-between">
<label class="form-label">Show Response by Default</label>
<label class="switch">
<input type="checkbox">
<span class="slider"></span>
</label>
</div>
<div class="flex items-center justify-between">
<label class="form-label">Cache Responses</label>
<label class="switch">
<input type="checkbox" checked>
<span class="slider"></span>
</label>
</div>
</div>
</div>
<div class="flex space-x-3">
<button class="btn">Save Changes</button>
<button class="btn btn-secondary">Reset Defaults</button>
</div>
</div>
<div class="tab-content" data-tab-content="shortcuts">
<div class="mb-8">
<h3 class="text-xl font-semibold mb-4">Keyboard Shortcuts</h3>
<div class="space-y-2">
<div class="help-shortcut">
<div>Add New API</div>
<div class="shortcut-keys">
<span class="shortcut-key">Ctrl</span>
<span class="shortcut-key">N</span>
</div>
</div>
<div class="help-shortcut">
<div>Toggle Group Mode</div>
<div class="shortcut-keys">
<span class="shortcut-key">G</span>
</div>
</div>
<div class="help-shortcut">
<div>Toggle Connection Mode</div>
<div class="shortcut-keys">
<span class="shortcut-key">C</span>
</div>
</div>
<div class="help-shortcut">
<div>Open Settings</div>
<div class="shortcut-keys">
<span class="shortcut-key">Ctrl</span>
<span class="shortcut-key">,</span>
</div>
</div>
<div class="help-shortcut">
<div>Search APIs</div>
<div class="shortcut-keys">
<span class="shortcut-key">Ctrl</span>
<span class="shortcut-key">K</span>
</div>
</div>
<div class="help-shortcut">
<div>Toggle Mini Map</div>
<div class="shortcut-keys">
<span class="shortcut-key">M</span>
</div>
</div>
</div>
</div>
<div class="flex space-x-3">
<button class="btn">Save Changes</button>
<button class="btn btn-secondary">Reset Defaults</button>
</div>
</div>
<div class="tab-content" data-tab-content="importexport">
<div class="mb-8">
<h3 class="text-xl font-semibold mb-4">Import Configuration</h3>
<div class="mb-6">
<label class="form-label">Import JSON Configuration</label>
<textarea class="w-full h-32 font-mono text-sm" id="importJson" placeholder="Paste your JSON configuration here..."></textarea>
</div>
<div class="mb-6">
<label class="form-label">Or Upload File</label>
<div class="border-2 border-dashed border-gray-700 rounded-lg p-8 text-center">
<i class="fas fa-file-upload text-3xl mb-2 text-gray-500"></i>
<p class="text-gray-400 mb-4">Drag & drop your JSON file here, or click to browse</p>
<button class="btn btn-secondary">Select File</button>
</div>
</div>
<h3 class="text-xl font-semibold mb-4">Export Configuration</h3>
<div class="mb-6">
<label class="form-label">Current Configuration</label>
<textarea class="w-full h-32 font-mono text-sm" id="exportJson" readonly></textarea>
</div>
<div class="grid grid-cols-2 gap-4">
<button class="btn btn-secondary">Copy to Clipboard</button>
<button class="btn">Download JSON</button>
</div>
</div>
</div>
</div>
</div>
<!-- API Browser Modal -->
<div class="modal" id="apiModal">
<div class="modal-content">
<div class="flex justify-between items-center mb-6">
<h2 class="text-2xl font-bold" id="apiModalTitle">API Endpoint</h2>
<button class="text-gray-400 hover:text-white" id="closeApiModal">
<i class="fas fa-times"></i>
</button>
</div>
<div class="mb-4">
<div class="flex items-center space-x-2 mb-2">
<span class="px-3 py-1 rounded-full bg-green-100 text-green-800 text-sm" id="apiMethod">GET</span>
<span class="text-gray-300 text-sm truncate" id="apiUrl">https://api.val.town/v1/run/...</span>
</div>
<p class="text-gray-400" id="apiDescription">Accessing this API endpoint</p>
</div>
<div class="response-tabs mb-6">
<div class="response-tab active" data-tab="request">Request</div>
<div class="response-tab" data-tab="documentation">Documentation</div>
<div class="response-tab" data-tab="history">History</div>
<div class="response-tab" data-tab="settings">Settings</div>
</div>
<div class="tab-content active" data-tab-content="request">
<div class="mb-6">
<h3 class="text-lg font-semibold mb-3">Request Parameters</h3>
<div class="space-y-3" id="parametersContainer">
<div class="flex items-center space-x-2">
<input type="text" placeholder="Parameter" class="flex-1" value="user_id">
<input type="text" placeholder="Value" class="flex-1" value="12345">
<button class="text-red-400 hover:text-red-300">
<i class="fas fa-trash"></i>
</button>
</div>
</div>
<button class="text-blue-400 hover:text-blue-300 text-sm mt-2 flex items-center" id="addParameter">
<i class="fas fa-plus mr-1"></i> Add Parameter
</button>
</div>
<div class="mb-6">
<h3 class="text-lg font-semibold mb-3">Headers</h3>
<div class="space-y-3" id="headersContainer">
<div class="flex items-center space-x-2">
<input type="text" value="Content-Type" class="flex-1 bg-gray-800">
<input type="text" value="application/json" class="flex-1 bg-gray-800">
<button class="text-red-400 hover:text-red-300">
<i class="fas fa-trash"></i>
</button>
</div>
<div class="flex items-center space-x-2">
<input type="text" value="Authorization" class="flex-1 bg-gray-800">
<input type="text" placeholder="Bearer token..." class="flex-1">
<button class="text-red-400 hover:text-red-300">
<i class="fas fa-trash"></i>
</button>
</div>
</div>
<button class="text-blue-400 hover:text-blue-300 text-sm mt-2 flex items-center" id="addHeader">
<i class="fas fa-plus mr-1"></i> Add Header
</button>
</div>
<div class="mb-6">
<h3 class="text-lg font-semibold mb-3">Request Body</h3>
<textarea class="w-full h-32 font-mono text-sm" id="requestBody" placeholder="Enter JSON body..."></textarea>
</div>
<div class="flex justify-end space-x-3">
<button class="btn btn-secondary" id="testRequest">
<i class="fas fa-bolt mr-2"></i> Test Request
</button>
<button class="btn" id="sendRequest">
<i class="fas fa-paper-plane mr-2"></i> Send
</button>
</div>
<div class="mt-8 border-t border-gray-700 pt-6" id="responseSection" style="display: none;">
<div class="response-tabs mb-4">
<div class="response-tab active" data-response-tab="body">Body</div>
<div class="response-tab" data-response-tab="headers">Headers</div>
<div class="response-tab" data-response-tab="cookies">Cookies</div>
<div class="response-tab" data-response-tab="timing">Timing</div>
</div>
<div class="response-content active" data-response-content="body">
<div class="flex items-center space-x-2 mb-3">
<span class="px-3 py-1 rounded-full bg-green-100 text-green-800 text-sm">200 OK</span>
<span class="text-gray-400 text-sm">1.2s</span>
<span class="text-gray-400 text-sm">2.4 KB</span>
</div>
<pre class="bg-gray-800 p-4 rounded-lg overflow-x-auto text-sm font-mono" id="responseBody">{
"status": "success",
"data": {
"user_id": 12345,
"name": "John Doe",
"email": "john@example.com"
}
}</pre>
</div>
<div class="response-content" data-response-content="headers">
<div class="code-block">
HTTP/1.1 200 OK<br>
Content-Type: application/json<br>
Content-Length: 2345<br>
Date: Wed, 21 Oct 2023 07:28:00 GMT<br>
Server: nginx/1.18.0<br>
Cache-Control: no-cache<br>
Connection: keep-alive
</div>
</div>
<div class="response-content" data-response-content="cookies">
<div class="code-block">
No cookies received
</div>
</div>
<div class="response-content" data-response-content="timing">
<div class="code-block">
DNS Lookup: 120ms<br>
TCP Connection: 80ms<br>
TLS Handshake: 220ms<br>
Request Sent: 5ms<br>
Waiting (TTFB): 350ms<br>
Content Download: 15ms<br>
<br>
Total: 790ms
</div>
</div>
</div>
</div>
<div class="tab-content" data-tab-content="documentation">
<div class="mb-6">
<h3 class="text-lg font-semibold mb-3">Endpoint Description</h3>
<div class="bg-gray-800 p-4 rounded-lg">
<p>This endpoint retrieves user profile information based on the provided user ID.</p>
</div>
</div>
<div class="mb-6">
<h3 class="text-lg font-semibold mb-3">Parameters</h3>
<div class="overflow-x-auto">
<table class="w-full bg-gray-800 rounded-lg overflow-hidden">
<thead>
<tr class="bg-gray-700">
<th class="p-3 text-left">Name</th>
<th class="p-3 text-left">Type</th>
<th class="p-3 text-left">Required</th>
<th class="p-3 text-left">Description</th>
</tr>
</thead>
<tbody>
<tr class="border-b border-gray-700">
<td class="p-3">user_id</td>
<td class="p-3">integer</td>
<td class="p-3">Yes</td>
<td class="p-3">The ID of the user to retrieve</td>
</tr>
<tr class="border-b border-gray-700">
<td class="p-3">fields</td>
<td class="p-3">string</td>
<td class="p-3">No</td>
<td class="p-3">Comma-separated list of fields to return</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="mb-6">
<h3 class="text-lg font-semibold mb-3">Response</h3>
<div class="bg-gray-800 p-4 rounded-lg">
<h4 class="font-semibold mb-2">Success Response (200 OK)</h4>
<pre class="font-mono text-sm">{
"status": "success",
"data": {
"user_id": 12345,
"name": "John Doe",
"email": "john@example.com",
"created_at": "2023-01-15T08:30:00Z"
}
}</pre>
<h4 class="font-semibold mt-4 mb-2">Error Response (404 Not Found)</h4>
<pre class="font-mono text-sm">{
"status": "error",
"message": "User not found",
"code": 404
}</pre>
</div>
</div>
<div class="mb-6">
<h3 class="text-lg font-semibold mb-3">Example Code</h3>
<div class="bg-gray-800 p-4 rounded-lg">
<h4 class="font-semibold mb-2">JavaScript (Fetch)</h4>
<pre class="font-mono text-sm">fetch('https://api.example.com/users/12345')
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));</pre>
<h4 class="font-semibold mt-4 mb-2">cURL</h4>
<pre class="font-mono text-sm">curl -X GET "https://api.example.com/users/12345" \
-H "Authorization: Bearer YOUR_TOKEN"</pre>
</div>
</div>
</div>
<div class="tab-content" data-tab-content="history">
<div class="mb-6">
<h3 class="text-lg font-semibold mb-3">Request History</h3>
<div class="bg-gray-800 rounded-lg overflow-hidden">
<div class="history-item p-4 border-b border-gray-700">
<div class="flex justify-between items-center mb-2">
<div class="flex items-center space-x-2">
<span class="px-2 py-1 rounded-full bg-green-100 text-green-800 text-xs">200</span>
<span class="text-sm">GET /users/12345</span>
</div>
<span class="text-xs text-gray-400">2 minutes ago</span>
</div>
<div class="text-xs text-gray-400 truncate">Response: {"status":"success","data":{"user_id":12345,"name":"John Doe"}}</div>
</div>
<div class="history-item p-4 border-b border-gray-700">
<div class="flex justify-between items-center mb-2">
<div class="flex items-center space-x-2">
<span class="px-2 py-1 rounded-full bg-red-100 text-red-800 text-xs">404</span>
<span class="text-sm">GET /users/99999</span>
</div>
<span class="text-xs text-gray-400">1 hour ago</span>
</div>
<div class="text-xs text-gray-400 truncate">Response: {"status":"error","message":"User not found"}</div>
</div>
<div class="history-item p-4">
<div class="flex justify-between items-center mb-2">
<div class="flex items-center space-x-2">
<span class="px-2 py-1 rounded-full bg-green-100 text-green-800 text-xs">200</span>
<span class="text-sm">GET /users/12345</span>
</div>
<span class="text-xs text-gray-400">Yesterday</span>
</div>
<div class="text-xs text-gray-400 truncate">Response: {"status":"success","data":{"user_id":12345,"name":"John Doe"}}</div>
</div>
</div>
</div>
</div>
<div class="tab-content" data-tab-content="settings">
<div class="mb-6">
<h3 class="text-lg font-semibold mb-3">Endpoint Settings</h3>
<div class="space-y-4">
<div>
<label class="form-label">Endpoint Name</label>
<input type="text" value="Get User Profile" class="w-full">
</div>
<div>
<label class="form-label">Endpoint URL</label>
<input type="text" value="https://api.example.com/users/{user_id}" class="w-full">
</div>
<div>
<label class="form-label">HTTP Method</label>
<select class="w-full">
<option>GET</option>
<option>POST</option>
<option>PUT</option>
<option>DELETE</option>
<option>PATCH</option>
</select>
</div>
<div class="flex items-center justify-between">
<label class="form-label">Cache Responses</label>
<label class="switch">
<input type="checkbox" checked>
<span class="slider"></span>
</label>
</div>
</div>
</div>
<div class="mb-6">
<h3 class="text-lg font-semibold mb-3">Button Appearance</h3>
<div class="grid grid-cols-2 md:grid-cols-4 gap-4 mb-4">
<div>
<label class="form-label">Icon</label>
<select class="w-full">
<option value="fa-user">User</option>
<option value="fa-database">Database</option>
<option value="fa-plug">API</option>
<option value="fa-bell">Notification</option>
</select>
</div>
<div>
<label class="form-label">Color</label>
<div class="flex space-x-2">
<div class="color-option bg-blue-500 active" data-color="blue"></div>
<div class="color-option bg-purple-500" data-color="purple"></div>
<div class="color-option bg-green-500" data-color="green"></div>
<div class="color-option bg-red-500" data-color="red"></div>
</div>
</div>
</div>
</div>
<div class="flex space-x-3">
<button class="btn">Save Changes</button>
<button class="btn btn-secondary">Reset</button>
</div>
</div>
</div>
</div>
<!-- Help Modal -->
<div class="dashboard-help" id="helpModal">
<div class="help-close" id="closeHelp">
<i class="fas fa-times"></i>
</div>
<div class="help-section">
<h2 class="help-title">Quantum API Dashboard Guide</h2>
<div class="help-content">
<p>Welcome to the Quantum API Dashboard, a revolutionary way to visualize, connect and manage your API ecosystem in 3D space.</p>
</div>
</div>
<div class="help-section">
<h3 class="help-title">Getting Started</h3>
<div class="help-content">
<p>To begin, add your first API endpoint by clicking the <strong>+</strong> button in the control panel. Configure the endpoint details and click "Add".</p>
<p>You can then click on any API button to view and test the endpoint, or drag buttons to arrange them in your preferred layout.</p>
</div>
</div>
<div class="help-section">
<h3 class="help-title">Key Features</h3>
<div class="help-content">
<p><strong>3D Visualization:</strong> APIs are represented as interactive 3D buttons that respond to your mouse movements.</p>
<p><strong>Connection Mode:</strong> Create visual connections between APIs to represent workflows and dependencies.</p>
<p><strong>Grouping:</strong> Organize related APIs into logical groups for better organization.</p>
<p><strong>Mini Map:</strong> Navigate large dashboards easily with the overview mini map.</p>
<p><strong>Visualizer:</strong> See your API ecosystem in a dynamic 3D graph visualization.</p>
</div>
</div>
<div class="help-section">
<h3 class="help-title">Keyboard Shortcuts</h3>
<div class="help-content">
<div class="help-shortcut">
<div>Add New API</div>
<div class="shortcut-keys">
<span class="shortcut-key">Ctrl</span>
<span class="shortcut-key">N</span>
</div>
</div>
<div class="help-shortcut">
<div>Toggle Group Mode</div>
<div class="shortcut-keys">
<span class="shortcut-key">G</span>
</div>
</div>
<div class="help-shortcut">
<div>Toggle Connection Mode</div>
<div class="shortcut-keys">
<span class="shortcut-key">C</span>
</div>
</div>
<div class="help-shortcut">
<div>Open Settings</div>
<div class="shortcut-keys">
<span class="shortcut-key">Ctrl</span>
<span class="shortcut-key">,</span>
</div>
</div>
<div class="help-shortcut">
<div>Search APIs</div>
<div class="shortcut-keys">
<span class="shortcut-key">Ctrl</span>
<span class="shortcut-key">K</span>
</div>
</div>
<!-- final shortcut row -->
<div class="help-shortcut">
<div>Toggle Mini Map</div>
<div class="shortcut-keys">
<span class="shortcut-key">M</span>
</div>
</div>
</div> <!-- .help-content -->
</div> <!-- .help-section (Keyboard Shortcuts) -->
</div> <!-- #helpModal.dashboard-help -->
<!-- ────────────────────── EXTRA STYLES & CORE SCRIPT ────────────────────── -->
<style>
/* Toggle-switch styling (used in Settings modal) */
.switch{position:relative;display:inline-block;width:42px;height:24px}
.switch input{display:none}
.slider{position:absolute;cursor:pointer;top:0;left:0;right:0;bottom:0;border-radius:24px;
background:rgba(255,255,255,.2);transition:.4s}
.slider:before{content:"";position:absolute;height:18px;width:18px;left:3px;bottom:3px;
background:#fff;border-radius:50%;transition:.4s}
.switch input:checked + .slider{
background:hsl(var(--primary-hue),var(--primary-saturation),var(--primary-lightness))
}
.switch input:checked + .slider:before{transform:translateX(18px)}
</style>
<script>
document.addEventListener('DOMContentLoaded', () => {
/* ─── Loading screen ─────────────────────────────────────────────── */
const loader = document.querySelector('.dashboard-loading');
const bar = document.getElementById('loadingBar');
let pct = 0, id = setInterval(() => {
pct += 4;
bar.style.width = pct + '%';
if (pct >= 100) { clearInterval(id);
loader.style.opacity = 0;
setTimeout(() => loader.remove(), 400);
}
}, 30);
/* ─── Modal toggles ──────────────────────────────────────────────── */
const toggleModal = (btnId, modalId, closeBtnId) => {
const btn = document.getElementById(btnId);
const modal = document.getElementById(modalId);
const close = document.getElementById(closeBtnId);
if (!btn || !modal) return;
btn.addEventListener('click', () => modal.classList.add('active'));
(close || modal).addEventListener('click', e => {
if (e.target === modal || e.target === close)
modal.classList.remove('active');
});
};
toggleModal('helpBtn', 'helpModal', 'closeHelp');
toggleModal('settingsButton','settingsModal', 'closeSettings');
/* ─── Tab & subtabs (Settings / API modal) ──────────────────────── */
document.querySelectorAll('.response-tab').forEach(tab => {
tab.addEventListener('click', () => {
const siblings = tab.parentElement.querySelectorAll('.response-tab');
siblings.forEach(t => t.classList.remove('active'));
tab.classList.add('active');
const wrapper = tab.closest('.modal-content') || document;
wrapper.querySelectorAll('.tab-content, .response-content')
.forEach(c => c.classList.remove('active'));
const attr = tab.dataset.tabContent ? 'tabContent' :
tab.dataset.tab ? 'tab' :
'responseTab';
const selector = `[data-${attr.toLowerCase()}="${tab.dataset[attr]}"]`;
const dest = wrapper.querySelector(selector);
dest && dest.classList.add('active');
});
});
});
</script>
</body>
</html>