Files
pyMC_Repeater/repeater/templates/style.css
Lloyd 97256eb132 Initial commit: PyMC Repeater Daemon
This commit sets up the initial project structure for the PyMC Repeater Daemon.
It includes base configuration files, dependency definitions, and scaffolding
for the main daemon service responsible for handling PyMC repeating operations.
2025-10-24 23:13:48 +01:00

1849 lines
39 KiB
CSS

/* ============================================================================
LoRa Repeater Dashboard - Professional Design System
Design Philosophy:
- Modern, minimal aesthetic inspired by contemporary design systems
- Consistent spacing, subtle depth, and refined typography
- Accessible colour palette with WCAG AA+ contrast ratios
- Responsive, mobile-first approach
============================================================================ */
/* ============================================================================
COLOUR PALETTE & DESIGN TOKENS
============================================================================ */
:root {
/* Colour Palette */
--color-bg-primary: #0f1419;
--color-bg-secondary: #1a1f2e;
--color-bg-tertiary: #252d3d;
--color-bg-hover: #2d3547;
--color-text-primary: #f1f3f5;
--color-text-secondary: #a8b1c3;
--color-text-tertiary: #7d8599;
--color-border: #3a4454;
--color-border-light: #2d3547;
/* Brand Accent */
--color-accent-primary: #00d4ff;
--color-accent-primary-hover: #00e5ff;
--color-accent-primary-dim: rgba(0, 212, 255, 0.15);
/* Status Colours (accessible) */
--color-success: #10b981;
--color-success-dim: rgba(16, 185, 129, 0.15);
--color-warning: #f59e0b;
--color-warning-dim: rgba(245, 158, 11, 0.15);
--color-error: #ef4444;
--color-error-dim: rgba(239, 68, 68, 0.15);
--color-info: #3b82f6;
--color-info-dim: rgba(59, 130, 246, 0.15);
/* Spacing Scale (8px base) */
--spacing-xs: 0.25rem; /* 4px */
--spacing-sm: 0.5rem; /* 8px */
--spacing-md: 1rem; /* 16px */
--spacing-lg: 1.5rem; /* 24px */
--spacing-xl: 2rem; /* 32px */
--spacing-2xl: 3rem; /* 48px */
/* Sizing */
--size-sidebar: 280px;
--size-header-height: 64px;
/* Shadows */
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
--shadow-md: 0 4px 6px rgba(0, 0, 0, 0.1);
--shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.1);
--shadow-xl: 0 20px 25px rgba(0, 0, 0, 0.15);
/* Border Radius */
--radius-sm: 0.375rem; /* 6px */
--radius-md: 0.5rem; /* 8px */
--radius-lg: 0.75rem; /* 12px */
/* Transitions */
--transition-fast: 150ms cubic-bezier(0.4, 0, 0.2, 1);
--transition-base: 250ms cubic-bezier(0.4, 0, 0.2, 1);
--transition-slow: 350ms cubic-bezier(0.4, 0, 0.2, 1);
/* Typography */
--font-family-base: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Helvetica Neue", sans-serif;
--font-family-mono: "SFMono-Regular", "Consolas", "Liberation Mono", "Menlo", monospace;
--font-size-xs: 0.75rem; /* 12px */
--font-size-sm: 0.875rem; /* 14px */
--font-size-base: 1rem; /* 16px */
--font-size-lg: 1.125rem; /* 18px */
--font-size-xl: 1.25rem; /* 20px */
--font-size-2xl: 1.5rem; /* 24px */
--font-size-3xl: 2rem; /* 32px */
--font-weight-regular: 400;
--font-weight-medium: 500;
--font-weight-semibold: 600;
--font-weight-bold: 700;
}
/* ============================================================================
RESET & GLOBAL STYLES
============================================================================ */
*,
*::before,
*::after {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html {
scroll-behavior: smooth;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
body {
font-family: var(--font-family-base);
font-size: var(--font-size-base);
font-weight: var(--font-weight-regular);
line-height: 1.5;
color: var(--color-text-primary);
background: var(--color-bg-primary);
height: 100vh;
}
/* Reset link styling globally */
a {
color: inherit;
text-decoration: none;
}
a:visited {
color: inherit;
}
/* ============================================================================
LAYOUT: SIDEBAR + CONTENT
============================================================================ */
.layout {
display: flex;
height: 100vh;
overflow: hidden;
}
/* Desktop layout - no extra padding */
@media (min-width: 769px) {
.layout {
padding-top: 0;
}
}
/* SIDEBAR */
.sidebar {
width: var(--size-sidebar);
background: var(--color-bg-secondary);
border-right: 1px solid var(--color-border);
display: flex;
flex-direction: column;
overflow-y: auto;
overflow-x: hidden;
}
.sidebar::-webkit-scrollbar {
width: 6px;
}
.sidebar::-webkit-scrollbar-track {
background: var(--color-bg-secondary);
}
.sidebar::-webkit-scrollbar-thumb {
background: var(--color-border);
border-radius: var(--radius-sm);
}
.sidebar::-webkit-scrollbar-thumb:hover {
background: var(--color-border-light);
}
/* SIDEBAR HEADER */
.sidebar-header {
padding: var(--spacing-lg);
border-bottom: 1px solid var(--color-border);
background: linear-gradient(
135deg,
rgba(0, 212, 255, 0.1) 0%,
rgba(59, 130, 246, 0.05) 100%
);
}
.sidebar-header h1 {
font-size: var(--font-size-xl);
font-weight: var(--font-weight-bold);
color: var(--color-accent-primary);
margin-bottom: var(--spacing-xs);
letter-spacing: -0.5px;
}
.sidebar-header .node-name {
font-size: var(--font-size-sm);
color: var(--color-text-secondary);
font-weight: var(--font-weight-regular);
}
.sidebar-header .node-pubkey {
font-size: 0.7rem;
color: var(--color-text-tertiary);
font-family: 'Courier New', monospace;
margin-top: 0.25rem;
word-break: break-all;
}
/* Hide menu toggle on desktop */
.menu-toggle {
display: none !important;
}
/* ============================================================================
MODERN SIDEBAR NAVIGATION
============================================================================ */
.sidebar-nav {
flex-grow: 1;
display: flex;
flex-direction: column;
gap: 2rem;
padding: 1rem;
}
.nav-section {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.nav-section-title {
font-size: 0.75rem;
text-transform: uppercase;
letter-spacing: 0.08em;
font-weight: 600;
color: var(--color-text-tertiary);
margin-bottom: 0.5rem;
padding: 0;
}
.nav-item {
display: flex;
align-items: center;
gap: 0.75rem;
color: var(--color-text-secondary);
text-decoration: none;
padding: 0.85rem 1rem;
border-radius: 12px;
transition: background 250ms ease, color 250ms ease;
font-size: 0.95rem;
font-weight: 500;
cursor: pointer;
user-select: none;
}
.nav-item:hover {
background: rgba(255, 255, 255, 0.08);
color: #60a5fa;
}
.nav-item.active {
background: #3b82f6;
color: #ffffff;
font-weight: 600;
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.3);
}
.nav-item .icon {
width: 1.5rem;
height: 1.5rem;
flex-shrink: 0;
stroke: currentColor;
stroke-width: 1.5;
opacity: 0.9;
transition: opacity 250ms ease;
}
.nav-item:hover .icon,
.nav-item.active .icon {
opacity: 1;
}
.nav-action {
background: rgba(16, 185, 129, 0.15) !important;
border: 1px solid rgba(16, 185, 129, 0.4);
cursor: pointer;
color: #ffffff;
}
.nav-action:hover {
background: rgba(16, 185, 129, 0.25) !important;
color: #ffffff;
border-color: rgba(16, 185, 129, 0.6);
}
.nav-action:active {
background: rgba(16, 185, 129, 0.35) !important;
color: #ffffff;
}
.nav-action:disabled {
opacity: 0.6;
cursor: not-allowed;
background: rgba(16, 185, 129, 0.1) !important;
color: rgba(255, 255, 255, 0.6);
}
.nav-button {
display: flex;
align-items: center;
gap: 0.75rem;
color: #ffffff;
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
border: none;
padding: 0.85rem 1rem;
border-radius: 12px;
transition: all 250ms ease;
font-size: 0.95rem;
font-weight: 600;
cursor: pointer;
user-select: none;
width: 100%;
text-align: left;
box-shadow: 0 2px 8px rgba(16, 185, 129, 0.3);
}
.nav-button:hover {
background: linear-gradient(135deg, #059669 0%, #047857 100%);
box-shadow: 0 4px 12px rgba(16, 185, 129, 0.4);
transform: translateY(-1px);
}
.nav-button:active {
transform: translateY(0);
box-shadow: 0 2px 6px rgba(16, 185, 129, 0.3);
}
.nav-button .icon {
width: 1.5rem;
height: 1.5rem;
flex-shrink: 0;
stroke: currentColor;
stroke-width: 1.5;
}
svg.icon {
display: inline-block;
width: 1.5rem !important;
height: 1.5rem !important;
min-width: 1.5rem;
min-height: 1.5rem;
vertical-align: middle;
flex-shrink: 0;
}
/* SIDEBAR CONTENT WRAPPER - Desktop: transparent wrapper, doesn't affect layout */
.sidebar-content-wrapper {
display: contents; /* Makes wrapper invisible on desktop, children act as if wrapper doesn't exist */
}
/* SIDEBAR FOOTER */
.sidebar-footer {
margin-top: auto; /* Push footer to bottom of sidebar */
padding: var(--spacing-lg);
border-top: 1px solid var(--color-border);
background: var(--color-bg-tertiary);
}
.status-badge {
display: inline-block;
background: var(--color-success-dim);
color: var(--color-success);
padding: var(--spacing-xs) var(--spacing-sm);
border-radius: var(--radius-sm);
font-size: var(--font-size-xs);
font-weight: var(--font-weight-semibold);
}
.version-badge {
display: inline-block;
background: rgba(100, 116, 139, 0.15);
color: var(--color-text-secondary);
padding: var(--spacing-xs) var(--spacing-sm);
border-radius: var(--radius-sm);
font-size: var(--font-size-xs);
font-weight: var(--font-weight-semibold);
font-family: monospace;
}
/* Control Buttons */
.control-buttons {
display: flex;
flex-direction: column;
gap: var(--spacing-sm);
margin-bottom: var(--spacing-md);
}
.control-btn {
display: flex;
align-items: center;
gap: var(--spacing-sm);
padding: var(--spacing-sm) var(--spacing-md);
background: var(--color-bg-secondary);
border: 1px solid var(--color-border);
border-radius: var(--radius-md);
color: var(--color-text-secondary);
cursor: pointer;
transition: all var(--transition-base);
font-family: var(--font-family-base);
font-size: var(--font-size-sm);
width: 100%;
text-align: left;
}
.control-btn:hover:not(.control-btn-active):not(.control-btn-warning) {
background: var(--color-bg-hover);
border-color: var(--color-accent-primary);
transform: translateY(-1px);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.control-btn.control-btn-active:hover,
.control-btn.control-btn-warning:hover {
transform: translateY(-1px);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
filter: brightness(1.1);
}
.control-btn:active {
transform: translateY(0);
}
.control-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
transform: none;
}
.control-btn .icon {
width: 1.25rem;
height: 1.25rem;
flex-shrink: 0;
stroke: currentColor;
}
.control-label {
display: flex;
flex-direction: column;
gap: 2px;
flex: 1;
}
.control-title {
font-size: var(--font-size-xs);
color: var(--color-text-tertiary);
text-transform: uppercase;
letter-spacing: 0.5px;
font-weight: var(--font-weight-semibold);
}
.control-value {
font-size: var(--font-size-sm);
color: var(--color-text-primary);
font-weight: var(--font-weight-semibold);
}
.control-btn.control-btn-active {
border-color: var(--color-success) !important;
background: var(--color-success-dim) !important;
}
.control-btn.control-btn-active .icon {
stroke: var(--color-success) !important;
}
.control-btn.control-btn-active .control-title {
color: var(--color-text-secondary) !important;
}
.control-btn.control-btn-active .control-value {
color: var(--color-success) !important;
}
.control-btn.control-btn-warning {
border-color: var(--color-warning) !important;
background: var(--color-warning-dim) !important;
}
.control-btn.control-btn-warning .icon {
stroke: var(--color-warning) !important;
}
.control-btn.control-btn-warning .control-title {
color: var(--color-text-secondary) !important;
}
.control-btn.control-btn-warning .control-value {
color: var(--color-warning) !important;
}
.duty-cycle-stats {
margin: var(--spacing-md) 0;
}
.duty-cycle-bar-container {
width: 100%;
height: 6px;
background: var(--color-bg-secondary);
border-radius: 3px;
overflow: hidden;
margin-bottom: var(--spacing-xs);
}
.duty-cycle-bar {
height: 100%;
background: var(--color-success);
transition: width 0.3s ease, background-color 0.3s ease;
border-radius: 3px;
}
.duty-cycle-text {
display: block;
color: var(--color-text-secondary);
font-size: var(--font-size-xs);
line-height: 1.4;
}
.duty-cycle-text strong {
color: var(--color-text-primary);
font-weight: var(--font-weight-semibold);
}
.sidebar-footer small {
display: block;
color: var(--color-text-tertiary);
font-size: var(--font-size-xs);
line-height: 1.6;
}
/* CONTENT AREA */
.content {
flex: 1;
overflow-y: auto;
overflow-x: hidden;
padding: var(--spacing-2xl);
background: var(--color-bg-primary);
}
.content::-webkit-scrollbar {
width: 8px;
}
.content::-webkit-scrollbar-track {
background: var(--color-bg-primary);
}
.content::-webkit-scrollbar-thumb {
background: var(--color-border);
border-radius: var(--radius-sm);
}
.content::-webkit-scrollbar-thumb:hover {
background: var(--color-text-tertiary);
}
/* ============================================================================
HEADER & TYPOGRAPHY
============================================================================ */
header {
margin-bottom: var(--spacing-2xl);
border-bottom: 1px solid var(--color-border);
padding-bottom: var(--spacing-lg);
}
h1 {
font-size: var(--font-size-3xl);
font-weight: var(--font-weight-bold);
color: var(--color-text-primary);
margin-bottom: var(--spacing-md);
letter-spacing: -0.5px;
}
h2 {
font-size: var(--font-size-2xl);
font-weight: var(--font-weight-bold);
color: var(--color-text-primary);
margin: var(--spacing-2xl) 0 var(--spacing-lg) 0;
letter-spacing: -0.25px;
}
h3 {
font-size: var(--font-size-lg);
font-weight: var(--font-weight-semibold);
color: var(--color-text-primary);
}
.header-info {
display: flex;
justify-content: space-between;
align-items: center;
font-size: var(--font-size-sm);
color: var(--color-text-secondary);
}
/* ============================================================================
STAT CARDS
============================================================================ */
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
gap: var(--spacing-lg);
margin-bottom: var(--spacing-2xl);
}
.stat-card {
background: var(--color-bg-secondary);
padding: var(--spacing-lg);
border-radius: var(--radius-lg);
border: 1px solid var(--color-border);
box-shadow: var(--shadow-sm);
transition: all var(--transition-base);
border-left: 3px solid var(--color-accent-primary);
}
.stat-card:hover {
border-color: var(--color-border-light);
box-shadow: var(--shadow-md);
transform: translateY(-2px);
}
.stat-card.success {
border-left-color: var(--color-success);
}
.stat-card.warning {
border-left-color: var(--color-warning);
}
.stat-card.error {
border-left-color: var(--color-error);
}
.stat-label {
display: block;
font-size: var(--font-size-xs);
font-weight: var(--font-weight-semibold);
color: var(--color-text-secondary);
text-transform: uppercase;
letter-spacing: 0.5px;
margin-bottom: var(--spacing-md);
}
.stat-value {
font-size: var(--font-size-3xl);
font-weight: var(--font-weight-bold);
color: var(--color-accent-primary);
line-height: 1;
margin-bottom: var(--spacing-sm);
}
.stat-unit {
font-size: var(--font-size-sm);
color: var(--color-text-tertiary);
margin-left: var(--spacing-xs);
}
/* ============================================================================
CHARTS
============================================================================ */
.charts-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(480px, 1fr));
gap: var(--spacing-lg);
margin-bottom: var(--spacing-2xl);
}
.chart-card {
background: var(--color-bg-secondary);
padding: var(--spacing-lg);
border-radius: var(--radius-lg);
border: 1px solid var(--color-border);
box-shadow: var(--shadow-sm);
transition: all var(--transition-base);
}
.chart-card:hover {
border-color: var(--color-border-light);
box-shadow: var(--shadow-md);
}
.chart-card h3 {
font-size: var(--font-size-sm);
font-weight: var(--font-weight-semibold);
color: var(--color-text-primary);
text-transform: uppercase;
letter-spacing: 0.5px;
margin-bottom: var(--spacing-lg);
}
.chart-container {
position: relative;
height: 240px;
}
/* Chart.js customization */
.chart-container canvas {
max-height: 240px;
}
/* ============================================================================
TABLES
============================================================================ */
.table-container {
overflow-x: auto;
border-radius: var(--radius-lg);
box-shadow: var(--shadow-sm);
}
table {
width: 100%;
border-collapse: collapse;
background: var(--color-bg-secondary);
border: 1px solid var(--color-border);
border-radius: var(--radius-lg);
overflow: hidden;
}
thead {
background: linear-gradient(
90deg,
rgba(0, 212, 255, 0.15) 0%,
rgba(59, 130, 246, 0.1) 100%
);
border-bottom: 1px solid var(--color-border);
}
th {
padding: var(--spacing-md);
text-align: left;
font-weight: var(--font-weight-semibold);
font-size: var(--font-size-xs);
color: var(--color-text-primary);
text-transform: uppercase;
letter-spacing: 0.5px;
}
td {
padding: var(--spacing-md);
border-bottom: 1px solid var(--color-border-light);
font-size: var(--font-size-sm);
color: var(--color-text-secondary);
}
tbody tr {
transition: background-color var(--transition-fast);
}
tbody tr:hover {
background: var(--color-bg-tertiary);
}
tbody tr:last-child td {
border-bottom: none;
}
/* Table cell styles */
.packet-type {
color: var(--color-accent-primary);
font-weight: var(--font-weight-medium);
font-size: var(--font-size-xs);
}
.route-flood {
color: var(--color-warning);
background: var(--color-warning-dim);
padding: var(--spacing-xs) var(--spacing-sm);
border-radius: var(--radius-sm);
font-weight: var(--font-weight-medium);
font-size: var(--font-size-xs);
display: inline-block;
}
.route-direct {
color: var(--color-success);
background: var(--color-success-dim);
padding: var(--spacing-xs) var(--spacing-sm);
border-radius: var(--radius-sm);
font-weight: var(--font-weight-medium);
font-size: var(--font-size-xs);
display: inline-block;
}
.score {
color: var(--color-accent-primary);
font-weight: var(--font-weight-semibold);
}
.status-tx {
color: var(--color-success);
font-weight: var(--font-weight-medium);
}
.status-wait {
color: var(--color-warning);
font-weight: var(--font-weight-medium);
}
.status-drop {
color: var(--color-error);
font-weight: var(--font-weight-medium);
}
/* Path and hash styling */
.path-hash,
.src-dst-hash {
font-family: 'Courier New', monospace;
font-size: 0.85rem;
letter-spacing: 0.3px;
word-break: break-all;
line-height: 1.5;
display: block;
margin: var(--spacing-sm) 0;
}
.path-hash {
color: #e0e0e0;
background: #2a2a2a;
padding: var(--spacing-sm) var(--spacing-md);
border-radius: 4px;
overflow-wrap: break-word;
border: 1px solid #404040;
}
.src-dst-hash {
color: #ffffff;
background: #1e3a8a;
padding: var(--spacing-sm) var(--spacing-md);
border-radius: 4px;
font-weight: var(--font-weight-bold);
border: 1px solid #3b82f6;
}
.my-hash {
background: #dc2626;
color: #ffffff;
padding: 2px 6px;
border-radius: 3px;
font-weight: var(--font-weight-bold);
display: inline-block;
font-size: 0.8rem;
border: 1px solid #ef4444;
}
.path-transform {
color: #999999;
font-size: 0.75rem;
display: inline;
}
.dupe-badge {
background: #991b1b;
color: #fca5a5;
padding: 3px 8px;
border-radius: 3px;
font-size: 0.7rem;
font-weight: var(--font-weight-bold);
display: inline-block;
margin-left: var(--spacing-sm);
border: 1px solid #dc2626;
}
.drop-reason {
color: #fca5a5;
font-size: 0.75rem;
display: block;
margin-top: 3px;
}
.na {
color: var(--color-text-tertiary);
font-style: italic;
}
.empty-message {
text-align: center;
color: var(--color-text-tertiary);
padding: var(--spacing-2xl) var(--spacing-lg);
font-style: italic;
font-size: var(--font-size-sm);
}
/* ============================================================================
CONFIGURATION SECTION
============================================================================ */
.config-section {
background: var(--color-bg-secondary);
padding: var(--spacing-lg);
border-radius: var(--radius-lg);
border: 1px solid var(--color-border);
margin-bottom: var(--spacing-lg);
box-shadow: var(--shadow-sm);
}
.config-section h3 {
margin-top: 0;
margin-bottom: var(--spacing-lg);
padding-bottom: var(--spacing-md);
border-bottom: 1px solid var(--color-border);
}
.config-item {
display: grid;
grid-template-columns: 180px 1fr;
gap: var(--spacing-lg);
margin-bottom: var(--spacing-lg);
align-items: flex-start;
}
.config-item:last-child {
margin-bottom: 0;
}
.config-label {
font-size: var(--font-size-sm);
font-weight: var(--font-weight-semibold);
color: var(--color-text-primary);
}
.config-value {
background: var(--color-bg-tertiary);
padding: var(--spacing-md);
border-radius: var(--radius-md);
border: 1px solid var(--color-border);
color: var(--color-accent-primary);
font-family: var(--font-family-mono);
font-size: var(--font-size-sm);
word-break: break-all;
line-height: 1.6;
}
.config-help {
grid-column: 2;
font-size: var(--font-size-xs);
color: var(--color-text-secondary);
margin-top: var(--spacing-xs);
font-style: italic;
line-height: 1.4;
}
.info-box {
background: var(--color-accent-primary-dim);
border: 1px solid rgba(0, 212, 255, 0.3);
border-left: 3px solid var(--color-accent-primary);
padding: var(--spacing-lg);
border-radius: var(--radius-lg);
margin-bottom: var(--spacing-lg);
color: var(--color-text-secondary);
font-size: var(--font-size-sm);
line-height: 1.6;
}
.score-formula {
margin-bottom: var(--spacing-xl);
}
.score-formula code {
background: var(--color-bg-tertiary);
border: 1px solid var(--color-border);
padding: 2px 6px;
border-radius: var(--radius-sm);
font-size: 0.9em;
color: var(--color-accent-primary);
font-family: var(--font-family-mono);
}
.score-formula table {
width: 100%;
border-collapse: collapse;
}
.score-formula ul {
margin-left: var(--spacing-lg);
margin-top: var(--spacing-sm);
margin-bottom: var(--spacing-md);
}
.score-formula li {
margin-bottom: var(--spacing-sm);
color: var(--color-text-secondary);
}
/* ============================================================================
LOG VIEWER
============================================================================ */
.log-container {
background: var(--color-bg-tertiary);
border: 1px solid var(--color-border);
border-radius: var(--radius-lg);
padding: var(--spacing-lg);
font-family: var(--font-family-mono);
font-size: var(--font-size-sm);
max-height: 500px;
overflow-y: auto;
box-shadow: var(--shadow-sm);
line-height: 1.7;
}
.log-container::-webkit-scrollbar {
width: 6px;
}
.log-container::-webkit-scrollbar-track {
background: var(--color-bg-tertiary);
}
.log-container::-webkit-scrollbar-thumb {
background: var(--color-border);
border-radius: var(--radius-sm);
}
.log-line {
display: flex;
gap: var(--spacing-md);
margin-bottom: var(--spacing-sm);
padding: var(--spacing-sm) 0;
border-bottom: 1px solid var(--color-border-light);
}
.log-line:last-child {
border-bottom: none;
margin-bottom: 0;
}
.log-time {
color: var(--color-text-tertiary);
min-width: 100px;
flex-shrink: 0;
}
.log-level {
display: inline-block;
min-width: 70px;
padding: var(--spacing-xs) var(--spacing-sm);
border-radius: var(--radius-sm);
font-weight: var(--font-weight-semibold);
font-size: var(--font-size-xs);
text-transform: uppercase;
text-align: center;
flex-shrink: 0;
}
.log-level.info {
background: var(--color-info-dim);
color: var(--color-info);
}
.log-level.warning {
background: var(--color-warning-dim);
color: var(--color-warning);
}
.log-level.error {
background: var(--color-error-dim);
color: var(--color-error);
}
.log-level.debug {
background: var(--color-accent-primary-dim);
color: var(--color-accent-primary);
}
.log-msg {
color: var(--color-text-secondary);
word-break: break-word;
flex: 1;
}
/* ============================================================================
UTILITY CLASSES
============================================================================ */
.refresh-info {
text-align: right;
margin-top: var(--spacing-lg);
font-size: var(--font-size-xs);
color: var(--color-text-tertiary);
}
/* ============================================================================
RESPONSIVE DESIGN
============================================================================ */
/* Tablet: 768px and below */
@media (max-width: 768px) {
:root {
--size-sidebar: 100%;
}
.layout {
flex-direction: column;
padding-top: 60px;
}
.sidebar {
width: 100%;
height: 60px;
flex-direction: row;
border-right: none;
border-bottom: 1px solid var(--color-border);
overflow: hidden;
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 1000;
transition: none;
}
.sidebar.menu-open {
height: 100vh;
height: 100dvh; /* Use dynamic viewport height for better mobile support */
max-height: none;
overflow: hidden; /* Changed from auto - we'll scroll the content instead */
position: fixed;
flex-direction: column;
display: flex;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.layout {
padding-top: 60px;
}
.content {
margin-top: 0;
}
.sidebar-header {
border-right: none;
border-bottom: 1px solid var(--color-border);
padding: 0 var(--spacing-md);
display: flex;
align-items: center;
min-width: auto;
width: 100%;
height: 60px;
background: var(--color-bg-secondary);
position: relative;
flex-shrink: 0; /* Prevent header from shrinking */
}
.sidebar.menu-open .sidebar-header {
border-right: none;
border-bottom: 1px solid var(--color-border);
background: var(--color-bg-secondary);
position: sticky; /* Make header sticky at top */
top: 0;
z-index: 10;
}
/* Wrapper for scrollable content on mobile */
.sidebar-content-wrapper {
display: contents; /* On mobile closed state, act transparent like desktop */
}
.sidebar.menu-open .sidebar-content-wrapper {
display: flex;
flex-direction: column;
flex: 1;
overflow-y: auto;
overflow-x: hidden;
-webkit-overflow-scrolling: touch;
min-height: 0;
position: relative;
}
.sidebar-header h1 {
font-size: var(--font-size-lg);
margin-bottom: 0;
color: var(--color-accent-primary);
padding-right: var(--spacing-2xl);
}
.sidebar-header .node-name {
display: none;
}
.sidebar-header .node-pubkey {
display: none;
}
/* Show menu toggle on mobile */
.menu-toggle {
display: flex !important;
align-items: center;
justify-content: center;
width: 44px;
height: 44px;
background: none;
border: none;
color: var(--color-text-secondary);
cursor: pointer;
padding: 0;
margin: 0;
flex-shrink: 0;
position: absolute;
right: 8px;
top: 50%;
transform: translateY(-50%);
}
.menu-toggle svg {
width: 24px;
height: 24px;
}
.sidebar-nav {
display: none;
padding: 0;
gap: 0;
width: 100%;
margin: 0;
flex-direction: column;
}
.sidebar.menu-open .sidebar-nav {
display: flex;
flex-direction: column;
flex-shrink: 0; /* Don't let nav shrink, let wrapper handle scroll */
}
.nav-section {
border-bottom: 1px solid var(--color-border-light);
padding: var(--spacing-md) var(--spacing-lg) 0;
}
.nav-section:first-child {
padding-top: var(--spacing-lg);
}
.nav-section:last-child {
padding-bottom: var(--spacing-lg);
}
.nav-section-title {
font-size: 0.7rem;
}
.nav-item {
border-radius: 8px;
margin-bottom: var(--spacing-sm);
font-size: 0.95rem;
padding: 1rem var(--spacing-lg);
}
/* Hide footer on mobile by default */
.sidebar-footer {
display: none;
}
/* Show footer only when menu is open */
.sidebar.menu-open .sidebar-footer {
display: flex !important; /* Force display as flex when menu is open */
flex-direction: column;
gap: var(--spacing-md);
margin-top: 0 !important; /* Remove auto margin on mobile */
flex-shrink: 0; /* Don't shrink the footer */
padding: var(--spacing-md) var(--spacing-lg);
border-top: 1px solid var(--color-border);
background: var(--color-bg-secondary);
}
.content {
padding: var(--spacing-lg);
overflow-y: auto;
width: 100%;
}
h1 {
font-size: var(--font-size-2xl);
}
h2 {
font-size: var(--font-size-xl);
margin: var(--spacing-xl) 0 var(--spacing-lg) 0;
}
.stats-grid {
grid-template-columns: 1fr;
gap: var(--spacing-md);
margin-bottom: var(--spacing-xl);
}
/* Improve stat card readability on tablets */
.stat-card {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--spacing-md);
padding: var(--spacing-md) var(--spacing-lg);
}
.stat-label {
font-size: var(--font-size-sm);
margin-bottom: 0;
flex: 1;
text-align: left;
}
.stat-value {
font-size: var(--font-size-2xl);
margin-bottom: 0;
flex-shrink: 0;
}
.charts-grid {
grid-template-columns: 1fr;
gap: var(--spacing-md);
margin-bottom: var(--spacing-xl);
}
.config-item {
grid-template-columns: 1fr;
gap: var(--spacing-sm);
align-items: stretch;
}
/* Mobile table: card-based layout - structured design */
.table-container {
overflow-x: visible;
}
table {
display: block;
width: 100%;
}
thead {
display: none;
}
tbody {
display: block;
width: 100%;
}
tbody tr {
display: block;
background: var(--color-bg-secondary);
border: 1px solid var(--color-border);
border-radius: var(--radius-lg);
margin-bottom: var(--spacing-lg);
padding: 0;
overflow: hidden;
}
tbody tr:hover {
background: var(--color-bg-tertiary);
border-color: var(--color-accent-primary);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
}
/* Card header with time and type */
td:nth-child(1), /* Time */
td:nth-child(2) /* Type */ {
display: inline-block;
padding: var(--spacing-md);
margin: 0;
border: none;
background: transparent;
}
td:nth-child(1) {
font-size: var(--font-size-lg);
font-weight: var(--font-weight-semibold);
color: var(--color-text-primary);
width: auto;
margin-right: var(--spacing-md);
}
td:nth-child(1)::before {
content: attr(data-label);
font-size: 0.65rem;
color: var(--color-text-tertiary);
text-transform: uppercase;
letter-spacing: 0.5px;
display: block;
margin-bottom: 4px;
font-weight: var(--font-weight-semibold);
}
td:nth-child(2) {
float: right;
padding: var(--spacing-md);
}
td:nth-child(2)::before {
content: attr(data-label);
font-size: 0.65rem;
color: var(--color-text-tertiary);
text-transform: uppercase;
letter-spacing: 0.5px;
display: block;
margin-bottom: 4px;
text-align: right;
font-weight: var(--font-weight-semibold);
}
/* Path/Hashes section - full width with label */
td:nth-child(5) {
display: block;
width: 100%;
padding: var(--spacing-md);
margin: 0;
border-top: 1px solid var(--color-border);
background: rgba(0, 0, 0, 0.2);
clear: both;
}
td:nth-child(5)::before {
content: attr(data-label);
font-size: 0.65rem;
color: var(--color-text-tertiary);
text-transform: uppercase;
letter-spacing: 0.5px;
display: block;
margin-bottom: 8px;
font-weight: var(--font-weight-semibold);
}
/* Route badge on right side */
td:nth-child(3) {
position: absolute;
top: var(--spacing-md);
right: var(--spacing-md);
margin: 0;
padding: 0;
}
td:nth-child(3)::before {
display: none;
}
/* Metrics row - RSSI, SNR, SCORE, TX DELAY */
td:nth-child(6), /* RSSI */
td:nth-child(7), /* SNR */
td:nth-child(8), /* SCORE */
td:nth-child(9) /* TX DELAY */ {
display: inline-block;
width: calc(50% - 8px);
padding: var(--spacing-sm) var(--spacing-md);
margin: 0;
margin-right: 8px;
border: none;
text-align: left;
}
td:nth-child(6)::before,
td:nth-child(7)::before,
td:nth-child(8)::before,
td:nth-child(9)::before {
content: attr(data-label);
font-size: 0.6rem;
color: var(--color-text-tertiary);
text-transform: uppercase;
letter-spacing: 0.5px;
display: block;
margin-bottom: 2px;
font-weight: var(--font-weight-semibold);
}
td:nth-child(6),
td:nth-child(8) {
margin-right: 8px;
}
td:nth-child(7),
td:nth-child(9) {
margin-right: 0;
}
/* Status section - full width */
td:nth-child(10) {
display: block;
width: 100%;
padding: var(--spacing-md);
margin: 0;
border-top: 1px solid var(--color-border);
}
td:nth-child(10)::before {
content: attr(data-label);
font-size: 0.65rem;
color: var(--color-text-tertiary);
text-transform: uppercase;
letter-spacing: 0.5px;
display: block;
margin-bottom: 6px;
font-weight: var(--font-weight-semibold);
}
/* Hide LEN - not critical for mobile */
td:nth-child(4) {
display: none;
}
/* Better card positioning with relative parent */
tbody tr {
position: relative;
}
/* Path hash styling on mobile */
.path-hash,
.src-dst-hash {
font-size: 0.85rem;
padding: 4px 8px;
line-height: 1.4;
display: inline-block;
margin: 2px;
}
.path-hash {
font-family: 'Courier New', monospace;
letter-spacing: 0.2px;
}
.my-hash {
padding: 4px 8px;
font-size: 0.85rem;
display: inline-block;
}
.path-arrow {
font-size: 1em;
padding: 0 4px;
}
.stat-value {
font-size: var(--font-size-2xl);
}
header {
margin-bottom: var(--spacing-xl);
}
}
/* Tablet: 480px - 768px range */
@media (max-width: 600px) {
/* Compact stat cards for tablet - horizontal layout */
.stat-card {
padding: var(--spacing-md) var(--spacing-lg);
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--spacing-md);
}
.stat-label {
font-size: var(--font-size-sm);
margin-bottom: 0;
flex: 1;
text-align: left;
}
.stat-value {
font-size: var(--font-size-2xl);
margin-bottom: 0;
flex-shrink: 0;
}
.stat-unit {
font-size: var(--font-size-sm);
}
/* Show all details compactly - inline with labels */
tbody tr {
padding: var(--spacing-md);
}
td {
display: inline-flex;
align-items: center;
gap: var(--spacing-xs);
margin-bottom: var(--spacing-xs);
margin-right: var(--spacing-md);
font-size: 0.7rem;
}
td::before {
font-size: 0.55rem;
padding: 1px 4px;
}
/* Path/Hashes wraps to next line */
td:nth-child(5) {
display: block;
width: 100%;
margin-right: 0;
margin-bottom: var(--spacing-sm);
}
/* Optimize path hash display for tablet */
.path-hash,
.src-dst-hash {
font-size: 0.78rem;
word-break: break-all;
padding: var(--spacing-sm) var(--spacing-md);
}
.my-hash {
padding: 2px 5px;
font-size: 0.72rem;
}
/* Status on its own line */
td:nth-child(10) {
display: block;
width: 100%;
margin-right: 0;
padding-top: var(--spacing-xs);
border-top: 1px solid var(--color-border-light);
}
}
/* Mobile: 480px and below */
@media (max-width: 480px) {
.content {
padding: var(--spacing-md);
}
h1 {
font-size: var(--font-size-xl);
}
/* Compact stat cards for mobile - horizontal layout */
.stat-card {
padding: var(--spacing-md);
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--spacing-md);
}
.stat-label {
font-size: var(--font-size-sm);
margin-bottom: 0;
flex: 1;
text-align: left;
}
.stat-value {
font-size: var(--font-size-xl);
margin-bottom: 0;
flex-shrink: 0;
}
.stat-unit {
font-size: var(--font-size-xs);
}
.chart-card {
padding: var(--spacing-md);
}
.chart-container {
height: 200px;
}
.log-container {
max-height: 300px;
font-size: var(--font-size-xs);
}
.log-line {
flex-direction: column;
gap: var(--spacing-sm);
}
.log-time {
min-width: auto;
}
.log-level {
min-width: auto;
width: fit-content;
}
/* Very compact table with all details */
tbody tr {
padding: var(--spacing-sm);
}
td {
display: inline-flex;
align-items: center;
gap: var(--spacing-xs);
margin-bottom: var(--spacing-xs);
margin-right: var(--spacing-sm);
font-size: 0.65rem;
}
td::before {
font-size: 0.5rem;
padding: 1px 3px;
}
/* Full width items */
td:nth-child(5),
td:nth-child(10) {
display: block;
width: 100%;
margin-right: 0;
margin-bottom: var(--spacing-xs);
}
td:nth-child(10) {
padding-top: var(--spacing-xs);
border-top: 1px solid var(--color-border-light);
}
td::before {
margin-bottom: 2px;
}
/* Ultra-compact path hash for tiny screens */
.path-hash,
.src-dst-hash {
font-size: 0.75rem;
padding: var(--spacing-sm) var(--spacing-md);
line-height: 1.4;
word-break: break-all;
white-space: normal;
}
.path-hash {
font-family: 'Courier New', monospace;
letter-spacing: 0.1px;
}
.my-hash {
padding: 2px 4px;
font-size: 0.7rem;
display: inline-block;
}
.src-dst-hash {
font-size: 0.75rem;
}
.path-transform {
font-size: 0.65rem;
display: block;
}
.dupe-badge {
font-size: 0.65rem;
padding: 2px 6px;
}
.drop-reason {
font-size: 0.7rem;
}
/* Mobile filter buttons */
#filterContainer {
flex-direction: column !important;
}
.filter-btn {
width: 100%;
justify-content: center;
}
#selectAllBtn,
#clearAllBtn {
width: 100%;
}
}
/* ============================================================================
ANIMATION & TRANSITIONS
============================================================================ */
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}
/* Smooth transitions for interactive elements */
button,
a,
input,
select,
textarea {
transition: all var(--transition-fast);
}
/* ============================================================================
ACCESSIBILITY
============================================================================ */
/* Focus states for keyboard navigation */
a:focus-visible,
button:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible {
outline: 2px solid var(--color-accent-primary);
outline-offset: 2px;
}
/* High contrast mode support */
@media (prefers-contrast: more) {
--color-border: #4a5568;
--color-text-secondary: #cbd5e0;
--color-accent-primary: #00e5ff;
}
/* Dark mode preference (already applied by default) */
@media (prefers-color-scheme: dark) {
/* Already dark - no changes needed */
}