Files
alpine-router/public/style.css
2026-04-13 18:56:13 +03:00

1819 lines
42 KiB
CSS
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
:root {
--bg: #070b14;
--bg-deep: #040710;
--surface: rgba(12, 22, 45, 0.85);
--surface-2: rgba(18, 35, 65, 0.7);
--surface-3: rgba(25, 50, 85, 0.5);
--border: rgba(0, 200, 255, 0.1);
--border-hi: rgba(0, 200, 255, 0.25);
--text: #e0f0ff;
--text-dim: #7aa2cc;
--muted: #4a6d8c;
--accent: #00d4ff;
--accent-h: #40e0ff;
--accent-glow:rgba(0, 212, 255, 0.25);
--success: #00ff88;
--danger: #ff3366;
--warning: #ffaa00;
--radius: 14px;
--radius-sm: 8px;
--shadow: 0 4px 30px rgba(0,0,0,.5);
--glow: 0 0 20px rgba(0, 212, 255, 0.15);
--glow-sm: 0 0 8px rgba(0, 212, 255, 0.12);
}
html { font-size: 15px; }
body {
font-family: "Inter", "SF Pro Display", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
background: var(--bg);
background-image:
radial-gradient(ellipse at 20% 0%, rgba(0, 212, 255, 0.06) 0%, transparent 60%),
radial-gradient(ellipse at 80% 100%, rgba(0, 255, 136, 0.04) 0%, transparent 50%);
color: var(--text);
min-height: 100vh;
line-height: 1.5;
overflow-x: hidden;
}
::selection {
background: rgba(0, 212, 255, 0.25);
color: #fff;
}
::-webkit-scrollbar { width: 6px; height: 6px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: rgba(0, 212, 255, 0.2); border-radius: 3px; }
::-webkit-scrollbar-thumb:hover { background: rgba(0, 212, 255, 0.4); }
/* ── Header ── */
header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 28px;
background: rgba(8, 14, 28, 0.92);
backdrop-filter: blur(20px);
border-bottom: 1px solid var(--border);
position: sticky;
top: 0;
z-index: 100;
}
.header-left { display: flex; align-items: center; gap: 12px; }
.header-right { display: flex; align-items: center; gap: 14px; }
.logo {
width: 32px; height: 32px;
stroke: var(--accent);
filter: drop-shadow(0 0 6px var(--accent-glow));
}
h1 {
font-size: 1.15rem;
font-weight: 700;
color: var(--text);
letter-spacing: 0.02em;
text-shadow: 0 0 20px rgba(0, 212, 255, 0.2);
}
.hostname {
font-size: .78rem;
color: var(--muted);
font-family: "JetBrains Mono", "Fira Code", monospace;
background: var(--surface-2);
padding: 3px 10px;
border-radius: 20px;
border: 1px solid var(--border);
}
/* ── Buttons ── */
.btn {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 7px 16px;
border-radius: var(--radius-sm);
border: none;
cursor: pointer;
font-size: .85rem;
font-weight: 600;
transition: all .2s ease;
position: relative;
overflow: hidden;
}
.btn:disabled { opacity: .35; cursor: not-allowed; filter: saturate(0.3); }
.btn-primary {
background: linear-gradient(135deg, #0090b3, #00d4ff);
color: #fff;
box-shadow: 0 2px 12px rgba(0, 212, 255, 0.2);
}
.btn-primary:hover:not(:disabled) {
background: linear-gradient(135deg, #00a8d6, #40e0ff);
box-shadow: 0 4px 20px rgba(0, 212, 255, 0.35);
transform: translateY(-1px);
}
.btn-success {
background: linear-gradient(135deg, #00b86e, #00ff88);
color: #fff;
box-shadow: 0 2px 12px rgba(0, 255, 136, 0.15);
}
.btn-success:hover:not(:disabled) {
box-shadow: 0 4px 20px rgba(0, 255, 136, 0.3);
transform: translateY(-1px);
}
.btn-danger {
background: linear-gradient(135deg, #cc1a44, #ff3366);
color: #fff;
box-shadow: 0 2px 12px rgba(255, 51, 102, 0.15);
}
.btn-danger:hover:not(:disabled) {
box-shadow: 0 4px 20px rgba(255, 51, 102, 0.3);
transform: translateY(-1px);
}
.btn-ghost {
background: var(--surface-3);
color: var(--text-dim);
border: 1px solid var(--border);
}
.btn-ghost:hover:not(:disabled) {
background: var(--surface-2);
color: var(--text);
border-color: var(--border-hi);
transform: translateY(-1px);
}
.btn-sm { padding: 5px 11px; font-size: .8rem; border-radius: 6px; }
.btn-icon {
background: none; border: none; color: var(--muted);
cursor: pointer; font-size: 1rem; padding: 4px 8px;
border-radius: var(--radius-sm);
transition: all .2s ease;
}
.btn-icon:hover {
color: var(--accent);
background: var(--surface-3);
text-shadow: 0 0 8px var(--accent-glow);
}
/* ── Pending banner ── */
.pending-banner {
display: flex;
align-items: center;
gap: 10px;
padding: 12px 28px;
background: rgba(255, 170, 0, 0.08);
border-bottom: 1px solid rgba(255, 170, 0, 0.2);
color: var(--warning);
font-size: .875rem;
animation: bannerPulse 2s ease-in-out infinite;
}
@keyframes bannerPulse {
0%, 100% { background: rgba(255, 170, 0, 0.06); }
50% { background: rgba(255, 170, 0, 0.12); }
}
.pending-banner strong { color: #ffcc44; }
.banner-actions { display: flex; gap: 8px; margin-left: auto; }
/* ── Main grid ── */
main { padding: 28px; }
.iface-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(380px, 1fr));
gap: 20px;
}
/* ── Interface card ── */
.iface-card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
overflow: hidden;
transition: all .3s ease;
backdrop-filter: blur(10px);
position: relative;
}
.iface-card::before {
content: '';
position: absolute;
top: 0; left: 0; right: 0;
height: 2px;
background: linear-gradient(90deg, transparent, var(--accent), transparent);
opacity: 0;
transition: opacity .3s ease;
}
.iface-card:hover {
border-color: var(--border-hi);
box-shadow: var(--glow);
transform: translateY(-2px);
}
.iface-card:hover::before { opacity: 1; }
.iface-card.has-pending {
border-color: rgba(255, 170, 0, 0.4);
animation: cardPendingPulse 2s ease-in-out infinite;
}
@keyframes cardPendingPulse {
0%, 100% { box-shadow: 0 0 0 rgba(255,170,0,0); }
50% { box-shadow: 0 0 15px rgba(255,170,0,0.15); }
}
.card-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 14px 16px 10px;
}
.card-name {
display: flex;
align-items: center;
gap: 10px;
font-size: 1rem;
font-weight: 700;
letter-spacing: 0.01em;
}
.state-dot {
width: 10px; height: 10px;
border-radius: 50%;
flex-shrink: 0;
position: relative;
}
.state-dot.up {
background: var(--success);
box-shadow: 0 0 8px var(--success), 0 0 20px rgba(0, 255, 136, 0.2);
animation: dotPulse 2s ease-in-out infinite;
}
.state-dot.down {
background: var(--danger);
box-shadow: 0 0 4px rgba(255, 51, 102, 0.3);
}
.state-dot.unknown {
background: var(--muted);
}
@keyframes dotPulse {
0%, 100% { box-shadow: 0 0 8px var(--success), 0 0 20px rgba(0, 255, 136, 0.15); }
50% { box-shadow: 0 0 14px var(--success), 0 0 30px rgba(0, 255, 136, 0.3); }
}
.mode-badge {
font-size: .68rem;
font-weight: 700;
letter-spacing: .08em;
text-transform: uppercase;
padding: 3px 10px;
border-radius: 20px;
border: 1px solid;
}
.mode-badge.dhcp {
border-color: rgba(0, 212, 255, 0.4);
color: var(--accent);
background: rgba(0, 212, 255, 0.08);
text-shadow: 0 0 8px rgba(0, 212, 255, 0.3);
}
.mode-badge.static {
border-color: rgba(255, 170, 0, 0.4);
color: var(--warning);
background: rgba(255, 170, 0, 0.08);
}
.mode-badge.loopback {
border-color: rgba(74, 109, 140, 0.4);
color: var(--muted);
background: rgba(74, 109, 140, 0.08);
}
.pending-badge {
font-size: .68rem;
padding: 2px 8px;
border-radius: 20px;
background: rgba(255, 170, 0, 0.12);
color: var(--warning);
border: 1px solid rgba(255, 170, 0, 0.3);
animation: badgePulse 2s ease-in-out infinite;
}
@keyframes badgePulse {
0%, 100% { opacity: 1; }
50% { opacity: .7; }
}
/* ── Card info ── */
.card-info {
padding: 0 16px 14px;
display: flex;
flex-direction: column;
gap: 5px;
}
.info-row {
display: flex;
align-items: baseline;
gap: 8px;
font-size: .85rem;
}
.info-label {
color: var(--muted);
min-width: 60px;
font-size: .76rem;
text-transform: uppercase;
letter-spacing: 0.05em;
}
.info-val {
font-family: "JetBrains Mono", "Fira Code", monospace;
color: var(--text);
font-size: .85rem;
word-break: break-all;
}
.info-val.none { color: var(--muted); font-style: italic; font-family: inherit; }
/* ── Traffic stats ── */
.traffic-row {
display: flex;
gap: 16px;
margin-top: 6px;
padding-top: 10px;
border-top: 1px solid var(--border);
}
.traffic-item {
flex: 1;
display: flex;
flex-direction: column;
gap: 2px;
}
.traffic-label {
font-size: .7rem;
color: var(--muted);
text-transform: uppercase;
letter-spacing: .06em;
}
.traffic-val {
font-size: .9rem;
font-family: "JetBrains Mono", "Fira Code", monospace;
color: var(--accent);
text-shadow: 0 0 6px rgba(0, 212, 255, 0.15);
}
/* ── Card actions ── */
.card-actions {
display: flex;
gap: 6px;
padding: 10px 16px 14px;
flex-wrap: wrap;
}
/* ── Loading ── */
.loading {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 14px;
height: 200px;
color: var(--muted);
}
.spinner {
width: 36px; height: 36px;
border: 2px solid var(--border);
border-top-color: var(--accent);
border-radius: 50%;
animation: spin .8s linear infinite;
box-shadow: 0 0 15px rgba(0, 212, 255, 0.1);
}
@keyframes spin { to { transform: rotate(360deg); } }
/* ── Modal ── */
.modal { position: fixed; inset: 0; z-index: 200; }
.modal-backdrop {
position: absolute; inset: 0;
background: rgba(0, 0, 0, .7);
backdrop-filter: blur(8px);
}
.modal-box {
position: absolute;
top: 50%; left: 50%;
transform: translate(-50%, -50%);
background: rgba(10, 18, 40, 0.95);
border: 1px solid var(--border-hi);
border-radius: var(--radius);
box-shadow: var(--shadow), 0 0 40px rgba(0, 212, 255, 0.08);
width: min(480px, calc(100vw - 40px));
max-height: calc(100vh - 60px);
overflow-y: auto;
animation: modalIn .2s ease;
}
@keyframes modalIn {
from { opacity: 0; transform: translate(-50%, -50%) scale(0.95); }
to { opacity: 1; transform: translate(-50%, -50%) scale(1); }
}
.modal-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 18px 20px 14px;
border-bottom: 1px solid var(--border);
}
.modal-header h2 {
font-size: 1rem;
font-weight: 700;
background: linear-gradient(135deg, var(--text), var(--accent));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.modal-footer {
display: flex;
justify-content: flex-end;
gap: 8px;
padding: 14px 20px 18px;
border-top: 1px solid var(--border);
}
/* ── Form ── */
form { padding: 18px 20px; display: flex; flex-direction: column; gap: 14px; }
.form-row { display: flex; flex-direction: column; gap: 5px; }
.form-row > label {
font-size: .76rem;
color: var(--text-dim);
text-transform: uppercase;
letter-spacing: .06em;
}
input[type="text"], input[type="number"], input[type="search"] {
background: var(--bg-deep);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
color: var(--text);
padding: 9px 12px;
font-size: .9rem;
font-family: "JetBrains Mono", "Fira Code", monospace;
outline: none;
transition: all .2s ease;
width: 100%;
}
input:focus {
border-color: var(--accent);
box-shadow: 0 0 0 2px rgba(0, 212, 255, 0.15);
}
input::placeholder { color: var(--muted); }
/* Form divider & hint */
.form-divider {
border: none;
border-top: 1px solid var(--border);
margin: 2px 0;
}
.form-hint {
font-size: .8rem;
color: var(--warning);
padding: 4px 0 2px;
}
.form-hint code {
font-family: "JetBrains Mono", monospace;
background: rgba(255,170,0,0.1);
border-radius: 4px;
padding: 1px 5px;
}
/* Checkbox */
.checkbox-label {
display: flex;
align-items: center;
gap: 8px;
cursor: pointer;
font-size: .9rem;
color: var(--text);
}
input[type="checkbox"] {
width: 16px; height: 16px;
accent-color: var(--accent);
cursor: pointer;
}
/* Segmented control */
.segmented {
display: flex;
background: var(--bg-deep);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
padding: 3px;
gap: 2px;
}
.seg-btn {
flex: 1;
padding: 7px;
border: none;
border-radius: calc(var(--radius-sm) - 2px);
background: transparent;
color: var(--muted);
cursor: pointer;
font-size: .85rem;
font-weight: 600;
transition: all .2s ease;
}
.seg-btn.active {
background: linear-gradient(135deg, #0090b3, #00d4ff);
color: #fff;
box-shadow: 0 2px 8px rgba(0, 212, 255, 0.2);
}
.seg-btn:not(.active):hover { background: var(--surface-3); color: var(--text); }
/* ── Toast ── */
.toast {
position: fixed;
bottom: 24px;
right: 24px;
padding: 12px 18px;
border-radius: var(--radius-sm);
font-size: .875rem;
font-weight: 600;
box-shadow: var(--shadow);
z-index: 300;
animation: slideUp .25s ease;
max-width: 340px;
backdrop-filter: blur(10px);
}
.toast.success {
background: rgba(0, 184, 110, 0.9);
color: #fff;
border: 1px solid rgba(0, 255, 136, 0.4);
box-shadow: 0 4px 20px rgba(0, 255, 136, 0.2);
}
.toast.error {
background: rgba(204, 26, 68, 0.9);
color: #fff;
border: 1px solid rgba(255, 51, 102, 0.4);
box-shadow: 0 4px 20px rgba(255, 51, 102, 0.2);
}
.toast.info {
background: rgba(0, 144, 179, 0.9);
color: #fff;
border: 1px solid rgba(0, 212, 255, 0.4);
box-shadow: 0 4px 20px rgba(0, 212, 255, 0.2);
}
@keyframes slideUp {
from { opacity: 0; transform: translateY(12px); }
to { opacity: 1; transform: translateY(0); }
}
/* ── Utilities ── */
.hidden { display: none !important; }
/* ── Tab navigation ── */
.tab-nav {
display: flex;
gap: 0;
padding: 0 28px;
background: rgba(8, 14, 28, 0.92);
backdrop-filter: blur(20px);
border-bottom: 1px solid var(--border);
}
.tab-link {
display: inline-flex;
align-items: center;
gap: 7px;
padding: 12px 20px;
font-size: .85rem;
font-weight: 600;
color: var(--muted);
text-decoration: none;
border-bottom: 2px solid transparent;
transition: all .2s ease;
margin-bottom: -1px;
position: relative;
}
.tab-link:hover {
color: var(--text-dim);
}
.tab-link.active {
color: var(--accent);
border-bottom-color: var(--accent);
text-shadow: 0 0 12px rgba(0, 212, 255, 0.3);
}
.tab-link svg {
filter: opacity(0.7);
transition: filter .2s ease;
}
.tab-link.active svg {
filter: opacity(1) drop-shadow(0 0 4px rgba(0, 212, 255, 0.4));
}
/* ── DHCP page layout ── */
.dhcp-main { padding: 28px; display: flex; flex-direction: column; gap: 20px; }
/* ── Alert banners ── */
.alert {
display: flex;
align-items: flex-start;
gap: 12px;
padding: 14px 18px;
border-radius: var(--radius);
font-size: .875rem;
line-height: 1.5;
backdrop-filter: blur(10px);
}
.alert-error {
background: rgba(255, 51, 102, 0.08);
border: 1px solid rgba(255, 51, 102, 0.25);
color: #ff8899;
}
.alert code {
font-family: "JetBrains Mono", monospace;
background: rgba(0,0,0,.3);
padding: 2px 6px;
border-radius: 4px;
font-size: .85rem;
}
/* ── Service status bar ── */
.dhcp-status-bar {
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
gap: 12px;
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 14px 20px;
backdrop-filter: blur(10px);
}
.status-info { display: flex; align-items: center; gap: 12px; }
.status-label { font-size: .9rem; font-weight: 700; }
.status-actions { display: flex; align-items: center; gap: 14px; }
.svc-badge {
font-size: .72rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: .06em;
padding: 3px 12px;
border-radius: 20px;
border: 1px solid;
}
.svc-badge.running {
border-color: rgba(0, 255, 136, 0.4);
color: var(--success);
background: rgba(0, 255, 136, 0.08);
box-shadow: 0 0 10px rgba(0, 255, 136, 0.1);
}
.svc-badge.stopped {
border-color: rgba(74, 109, 140, 0.3);
color: var(--muted);
background: rgba(74, 109, 140, 0.05);
}
/* Toggle switch */
.toggle-label {
display: flex;
align-items: center;
gap: 9px;
cursor: pointer;
font-size: .875rem;
color: var(--text);
user-select: none;
}
.toggle-label input[type="checkbox"] {
position: absolute;
opacity: 0;
width: 0;
height: 0;
}
.toggle-slider {
position: relative;
display: inline-block;
width: 40px;
height: 22px;
background: var(--surface-3);
border: 1px solid var(--border);
border-radius: 11px;
transition: all .2s ease;
flex-shrink: 0;
}
.toggle-slider::after {
content: '';
position: absolute;
top: 3px; left: 3px;
width: 15px; height: 15px;
background: var(--text-dim);
border-radius: 50%;
transition: all .2s ease;
}
.toggle-label input:checked + .toggle-slider {
background: rgba(0, 212, 255, 0.2);
border-color: rgba(0, 212, 255, 0.4);
}
.toggle-label input:checked + .toggle-slider::after {
transform: translateX(17px);
background: var(--accent);
box-shadow: 0 0 8px rgba(0, 212, 255, 0.4);
}
/* ── Pools grid ── */
.dhcp-section { display: flex; flex-direction: column; gap: 16px; }
.section-header { }
.section-header h2 {
font-size: 1rem;
font-weight: 700;
margin-bottom: 4px;
background: linear-gradient(135deg, var(--text), var(--accent));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.section-desc { font-size: .83rem; color: var(--muted); line-height: 1.5; }
.pools-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(360px, 1fr));
gap: 16px;
}
.pool-card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
overflow: hidden;
transition: all .3s ease;
backdrop-filter: blur(10px);
position: relative;
}
.pool-card:hover {
border-color: var(--border-hi);
box-shadow: var(--glow-sm);
transform: translateY(-1px);
}
.pool-card--active {
border-color: rgba(0, 212, 255, 0.3);
box-shadow: 0 0 15px rgba(0, 212, 255, 0.08);
}
.pool-card--wan { opacity: .6; }
.pool-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 13px 16px 10px;
}
.pool-iface { display: flex; align-items: center; gap: 9px; }
.pool-iface-name { font-size: 1rem; font-weight: 700; }
.tag-gw {
font-size: .68rem;
font-weight: 700;
padding: 2px 8px;
border-radius: 20px;
background: rgba(255, 51, 102, 0.1);
color: #ff6688;
border: 1px solid rgba(255, 51, 102, 0.3);
text-transform: uppercase;
letter-spacing: .04em;
}
.tag-active {
font-size: .68rem;
font-weight: 700;
padding: 2px 8px;
border-radius: 20px;
background: rgba(0, 212, 255, 0.1);
color: var(--accent);
border: 1px solid rgba(0, 212, 255, 0.3);
text-transform: uppercase;
letter-spacing: .04em;
text-shadow: 0 0 6px rgba(0, 212, 255, 0.2);
}
.pool-info {
padding: 0 16px 14px;
display: flex;
flex-direction: column;
gap: 5px;
}
.pool-footer {
display: flex;
align-items: center;
gap: 8px;
padding: 10px 16px 13px;
border-top: 1px solid var(--border);
}
.muted { color: var(--muted); font-style: italic; }
/* ── Pool modal inline pairs ── */
.inline-pair {
display: flex;
align-items: center;
gap: 8px;
}
.inline-pair input { flex: 1; }
.pair-sep { color: var(--muted); font-size: .85rem; white-space: nowrap; }
.font-mono { font-family: "JetBrains Mono", "Fira Code", ui-monospace, monospace !important; }
/* ── Empty state ── */
.empty-state {
text-align: center;
color: var(--muted);
padding: 48px 20px;
font-size: .9rem;
}
/* ── Clients page ── */
.clients-main { padding: 28px; display: flex; flex-direction: column; gap: 16px; }
.clients-toolbar {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
flex-wrap: wrap;
}
.clients-summary {
display: flex;
align-items: center;
gap: 16px;
font-size: .875rem;
}
.cl-stat { display: flex; align-items: center; gap: 6px; font-weight: 600; }
.cl-stat--muted { color: var(--muted); font-weight: 400; }
.clients-search {
background: var(--bg-deep);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
color: var(--text);
padding: 8px 14px;
font-size: .875rem;
outline: none;
width: 260px;
transition: all .2s ease;
}
.clients-search:focus {
border-color: var(--accent);
box-shadow: 0 0 0 2px rgba(0, 212, 255, 0.12);
}
.clients-search::placeholder { color: var(--muted); }
.clients-table-wrap {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
overflow: hidden;
overflow-x: auto;
backdrop-filter: blur(10px);
}
.clients-table {
width: 100%;
border-collapse: collapse;
font-size: .875rem;
}
.clients-table thead {
background: rgba(0, 212, 255, 0.03);
border-bottom: 1px solid var(--border);
}
.clients-table th {
text-align: left;
padding: 12px 14px;
font-size: .72rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: .06em;
color: var(--muted);
white-space: nowrap;
}
.clients-table td {
padding: 11px 14px;
border-bottom: 1px solid rgba(255, 255, 255, 0.03);
vertical-align: middle;
}
.clients-table tr:last-child td { border-bottom: none; }
.clients-table tbody tr {
transition: background .15s ease;
}
.clients-table tbody tr:hover { background: rgba(0, 212, 255, 0.03); }
.row-offline td { opacity: .45; }
.col-status { width: 24px; padding-right: 4px; }
.col-host { min-width: 140px; }
.col-ip { min-width: 130px; }
.col-mac { min-width: 150px; }
.col-iface { min-width: 80px; }
.col-type { min-width: 80px; }
.col-lease { min-width: 100px; }
.client-host { font-weight: 600; }
.mono { font-family: "JetBrains Mono", "Fira Code", ui-monospace, monospace; font-size: .83rem; }
.client-badge {
display: inline-block;
font-size: .68rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: .04em;
padding: 2px 8px;
border-radius: 20px;
border: 1px solid;
}
.client-badge.dhcp {
background: rgba(0, 212, 255, 0.08);
border-color: rgba(0, 212, 255, 0.35);
color: var(--accent);
}
.client-badge.arp {
background: rgba(74, 109, 140, 0.08);
border-color: rgba(74, 109, 140, 0.25);
color: var(--muted);
}
.lease-val { font-variant-numeric: tabular-nums; }
.lease-expired { color: var(--danger); }
.lease-soon { color: var(--warning); }
.col-tx, .col-rx { min-width: 90px; text-align: right; }
.col-activity { min-width: 130px; }
.traffic-num { font-family: "JetBrains Mono", "Fira Code", ui-monospace, monospace; font-size: .83rem; font-variant-numeric: tabular-nums; }
.activity-val { font-variant-numeric: tabular-nums; }
.active-now { color: var(--success); text-shadow: 0 0 6px rgba(0, 255, 136, 0.3); }
/* ── Client blocked row ── */
.row-blocked { background: rgba(255, 51, 102, 0.04); }
.row-blocked:hover { background: rgba(255, 51, 102, 0.08); }
.client-row { cursor: pointer; transition: all .15s ease; }
.client-row:hover { background: rgba(0, 212, 255, 0.04); }
.client-row.row-blocked:hover { background: rgba(255, 51, 102, 0.12); }
.blocked-badge {
margin-left: 6px;
background: rgba(255, 51, 102, 0.12) !important;
border-color: rgba(255, 51, 102, 0.5) !important;
color: var(--danger) !important;
}
.cl-stat--blocked { color: var(--danger); font-weight: 600; }
.static-badge {
background: rgba(0, 212, 255, 0.1) !important;
border-color: rgba(0, 212, 255, 0.4) !important;
color: var(--accent) !important;
}
.ip-info-row { display: flex; align-items: baseline; gap: 8px; }
.ip-current-label { font-size: .78rem; color: var(--muted); }
.form-val { font-size: .9rem; color: var(--text); }
.toggle-blocked .toggle-slider {
background: rgba(255, 51, 102, 0.2) !important;
border-color: rgba(255, 51, 102, 0.4) !important;
}
/* ── Proxy page ── */
.proxy-main { padding: 28px; display: flex; flex-direction: column; gap: 20px; }
.proxy-tabs {
display: flex;
gap: 0;
border-bottom: 1px solid var(--border);
background: rgba(8, 14, 28, 0.92);
border-radius: var(--radius-sm);
overflow: hidden;
}
.ptab {
padding: 10px 20px;
font-size: .85rem;
font-weight: 600;
color: var(--muted);
background: transparent;
border: none;
cursor: pointer;
transition: all .2s ease;
border-bottom: 2px solid transparent;
}
.ptab:hover { color: var(--text-dim); }
.ptab.active {
color: var(--accent);
border-bottom-color: var(--accent);
text-shadow: 0 0 12px rgba(0, 212, 255, 0.3);
}
.ptab-content {
min-height: 200px;
}
.proxy-list {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(380px, 1fr));
gap: 16px;
}
.proxy-card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
overflow: hidden;
backdrop-filter: blur(10px);
transition: all .3s ease;
position: relative;
}
.proxy-card:hover {
border-color: var(--border-hi);
box-shadow: var(--glow-sm);
transform: translateY(-1px);
}
.proxy-card-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 16px;
border-bottom: 1px solid var(--border);
}
.proxy-name {
font-weight: 700;
font-size: .95rem;
color: var(--text);
}
.proxy-card-info {
padding: 10px 16px 14px;
display: flex;
flex-direction: column;
gap: 4px;
}
.rules-list {
display: flex;
flex-direction: column;
gap: 6px;
}
.rule-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 14px;
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
font-size: .85rem;
transition: all .2s ease;
}
.rule-item:hover {
border-color: var(--border-hi);
box-shadow: var(--glow-sm);
}
.rule-info {
display: flex;
align-items: center;
gap: 10px;
flex: 1;
min-width: 0;
}
.rule-type {
font-size: .68rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: .04em;
padding: 2px 8px;
border-radius: 20px;
background: rgba(0, 212, 255, 0.1);
border: 1px solid rgba(0, 212, 255, 0.3);
color: var(--accent);
white-space: nowrap;
}
.rule-value {
font-family: "JetBrains Mono", "Fira Code", monospace;
font-size: .83rem;
color: var(--text);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.rule-target {
font-size: .8rem;
font-weight: 600;
color: var(--success);
}
.rule-flags {
font-size: .7rem;
color: var(--muted);
}
.proxy-form {
display: flex;
flex-direction: column;
gap: 14px;
}
.proxy-form h3 { margin-bottom: 0; }
textarea {
background: var(--bg-deep);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
color: var(--text);
padding: 9px 12px;
font-size: .85rem;
font-family: "JetBrains Mono", "Fira Code", monospace;
outline: none;
transition: all .2s ease;
width: 100%;
resize: vertical;
}
textarea:focus {
border-color: var(--accent);
box-shadow: 0 0 0 2px rgba(0, 212, 255, 0.15);
}
textarea::placeholder { color: var(--muted); }
select {
appearance: none;
-webkit-appearance: none;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath d='M2 4l4 4 4-4' fill='none' stroke='%237aa2cc' stroke-width='1.5'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 10px center;
padding-right: 30px;
}
/* ── VLAN section inside interface card ── */
.vlan-section {
border-top: 1px solid var(--border);
padding: 10px 16px 12px;
background: rgba(0, 0, 0, 0.15);
}
.vlan-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 8px;
}
.vlan-title {
font-size: .75rem;
font-weight: 700;
letter-spacing: .08em;
text-transform: uppercase;
color: var(--text-dim);
}
.btn-xs {
padding: 3px 9px;
font-size: .75rem;
border-radius: 5px;
}
.vlan-list {
display: flex;
flex-direction: column;
gap: 6px;
}
.vlan-row {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 8px;
padding: 7px 10px;
background: var(--surface-2);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
transition: border-color .2s;
}
.vlan-row:hover { border-color: var(--border-hi); }
.vlan-row-left {
display: flex;
align-items: center;
gap: 7px;
flex: 1 1 auto;
min-width: 0;
}
.vlan-iface-name {
font-size: .85rem;
font-weight: 700;
font-family: "JetBrains Mono", "Fira Code", monospace;
color: var(--accent);
}
.vlan-id-tag {
font-size: .68rem;
font-weight: 700;
letter-spacing: .06em;
text-transform: uppercase;
padding: 2px 8px;
border-radius: 20px;
background: rgba(0, 212, 255, 0.06);
border: 1px solid rgba(0, 212, 255, 0.2);
color: var(--text-dim);
white-space: nowrap;
}
.vlan-row-info {
font-size: .8rem;
color: var(--text-dim);
font-family: "JetBrains Mono", "Fira Code", monospace;
flex: 0 1 auto;
}
.vlan-row-actions {
display: flex;
gap: 5px;
flex-shrink: 0;
}
.vlan-empty {
font-size: .8rem;
color: var(--muted);
text-align: center;
padding: 8px 0 4px;
font-style: italic;
}
/* Modal hint inline */
.form-hint-inline {
font-size: .78rem;
color: var(--muted);
font-weight: 400;
}
/* ── Firewall page ── */
.fw-main {
padding: 28px;
display: flex;
flex-direction: column;
gap: 18px;
}
.fw-toolbar {
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
gap: 12px;
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 14px 20px;
backdrop-filter: blur(10px);
}
.fw-toolbar-left { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
.fw-toolbar-right { display: flex; align-items: center; gap: 10px; }
.fw-hint { font-size: .78rem; color: var(--muted); }
.fw-card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
overflow: hidden;
backdrop-filter: blur(10px);
}
.fw-table-wrap { overflow-x: auto; }
.fw-table {
width: 100%;
border-collapse: collapse;
font-size: .85rem;
}
.fw-table thead th {
padding: 10px 12px;
text-align: left;
font-size: .72rem;
font-weight: 700;
letter-spacing: .07em;
text-transform: uppercase;
color: var(--text-dim);
border-bottom: 1px solid var(--border);
white-space: nowrap;
background: rgba(0,0,0,.15);
}
.fw-table tbody td {
padding: 9px 12px;
vertical-align: middle;
border-bottom: 1px solid rgba(0,200,255,.04);
}
.fw-table tbody tr:last-child td { border-bottom: none; }
.fw-row { transition: background .15s; }
.fw-row:hover { background: var(--surface-2); }
.fw-row.fw-row-disabled { opacity: .45; }
.fw-row.dragging { opacity: .4; background: rgba(0,212,255,.05); }
.fw-row.drag-over { border-top: 2px solid var(--accent); }
/* Column widths */
.col-drag { width: 28px; }
.col-num { width: 32px; color: var(--muted); font-size: .78rem; }
.col-en { width: 44px; }
.col-action { width: 110px; }
.col-proto { width: 80px; }
.col-iface { width: 120px; font-family: "JetBrains Mono","Fira Code",monospace; font-size: .8rem; }
.col-addr { min-width: 160px; font-family: "JetBrains Mono","Fira Code",monospace; font-size: .8rem; }
.col-comment { color: var(--text-dim); font-size: .82rem; }
.col-btns { width: 80px; white-space: nowrap; }
.drag-handle {
cursor: grab;
color: var(--muted);
font-size: 1.1rem;
user-select: none;
display: inline-block;
line-height: 1;
padding: 2px 4px;
}
.drag-handle:active { cursor: grabbing; }
/* Action badges */
.action-badge {
display: inline-block;
font-size: .7rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: .07em;
padding: 3px 10px;
border-radius: 20px;
border: 1px solid;
}
.fw-accept {
border-color: rgba(0, 255, 136, 0.4);
color: var(--success);
background: rgba(0, 255, 136, 0.08);
}
.fw-drop {
border-color: rgba(255, 51, 102, 0.4);
color: var(--danger);
background: rgba(255, 51, 102, 0.08);
}
.fw-reject {
border-color: rgba(255, 170, 0, 0.4);
color: var(--warning);
background: rgba(255, 170, 0, 0.08);
}
/* Empty state */
.fw-empty {
text-align: center;
padding: 40px 20px;
color: var(--muted);
font-style: italic;
}
/* Policy note */
.fw-policy-note {
display: flex;
align-items: center;
gap: 8px;
padding: 10px 16px;
font-size: .78rem;
color: var(--muted);
border-top: 1px solid var(--border);
background: rgba(0,0,0,.1);
}
.fw-policy-note strong { color: var(--danger); }
/* Mini toggle (inline in table) */
.mini-toggle {
display: inline-flex;
align-items: center;
cursor: pointer;
}
.mini-toggle input { display: none; }
.mini-slider {
display: inline-block;
width: 30px; height: 16px;
background: var(--surface-3);
border: 1px solid var(--border);
border-radius: 8px;
position: relative;
transition: all .2s;
}
.mini-slider::after {
content: '';
position: absolute;
top: 2px; left: 2px;
width: 11px; height: 11px;
background: var(--muted);
border-radius: 50%;
transition: all .2s;
}
.mini-toggle input:checked + .mini-slider {
background: rgba(0,255,136,.2);
border-color: rgba(0,255,136,.4);
}
.mini-toggle input:checked + .mini-slider::after {
transform: translateX(14px);
background: var(--success);
}
/* Comment display */
.fw-comment { color: var(--text-dim); }
/* Wider modal for rule editing */
.modal-wide { max-width: 680px; }
/* Two-column form grid */
.form-grid-2 {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 0 16px;
}
@media (max-width: 560px) {
.form-grid-2 { grid-template-columns: 1fr; }
}
/* ── Icon-only action buttons ── */
.btn-icon-accent {
color: var(--accent);
}
.btn-icon-accent:hover {
color: var(--accent-h);
background: rgba(0, 212, 255, 0.1);
text-shadow: 0 0 8px var(--accent-glow);
}
.btn-icon-danger {
color: var(--muted);
}
.btn-icon-danger:hover {
color: var(--danger);
background: rgba(255, 51, 102, 0.1);
}
/* ── Card power toggle ── */
.iface-power-toggle {
gap: 8px;
}
.iface-toggle-label {
font-size: .78rem;
font-weight: 600;
color: var(--text-dim);
min-width: 28px;
}
/* ── Small toggle (VLAN rows) ── */
.toggle-sm {
width: 30px !important;
height: 17px !important;
border-radius: 9px !important;
}
.toggle-sm::after {
width: 11px !important;
height: 11px !important;
}
.toggle-label input:checked + .toggle-sm::after {
transform: translateX(13px) !important;
}
/* ── Card name with label ── */
.card-name-stack {
display: flex;
flex-direction: column;
gap: 1px;
}
.card-label-text {
font-size: 1rem;
font-weight: 700;
color: var(--text);
}
.card-iface-sub {
font-size: .72rem;
color: var(--muted);
font-family: "JetBrains Mono", "Fira Code", monospace;
}
.card-iface-name {
font-size: 1rem;
font-weight: 700;
}
/* ── VLAN label in row ── */
.vlan-name-stack {
display: flex;
flex-direction: column;
gap: 0;
}
.vlan-label-text {
font-size: .82rem;
font-weight: 700;
color: var(--text);
}
.vlan-iface-name {
font-size: .72rem;
color: var(--muted);
font-family: "JetBrains Mono", "Fira Code", monospace;
}
/* ── Proxy form helpers ── */
.field-select {
background: var(--bg-deep);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
color: var(--text);
padding: 9px 12px;
font-size: .9rem;
width: 100%;
}
.mono-ta {
background: var(--bg-deep);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
color: var(--text);
padding: 9px 12px;
font-size: .85rem;
font-family: 'JetBrains Mono', 'Fira Code', monospace;
width: 100%;
resize: vertical;
}
.settings-section-title {
color: var(--accent);
font-size: .85rem;
font-weight: 700;
letter-spacing: .05em;
text-transform: uppercase;
margin-bottom: 12px;
}
.pf-section-title {
font-size: .8rem;
font-weight: 700;
color: var(--accent);
letter-spacing: .05em;
text-transform: uppercase;
margin-bottom: 10px;
margin-top: 4px;
}
.pf-subsection-title {
font-size: .78rem;
font-weight: 600;
color: var(--text-muted);
letter-spacing: .04em;
margin-bottom: 8px;
margin-top: 2px;
}
/* ── Scanline overlay (subtle futuristic effect) ── */
body::after {
content: '';
position: fixed;
inset: 0;
pointer-events: none;
z-index: 9999;
background: repeating-linear-gradient(
0deg,
transparent,
transparent 2px,
rgba(0, 212, 255, 0.008) 2px,
rgba(0, 212, 255, 0.008) 4px
);
}
/* ── Dashboard ── */
.dash-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16px;
margin-bottom: 20px;
}
@media (max-width: 768px) {
.dash-grid { grid-template-columns: 1fr; }
}
.dash-card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 18px;
backdrop-filter: blur(10px);
}
.dash-card-title {
font-size: .85rem;
font-weight: 700;
color: var(--accent);
letter-spacing: .05em;
text-transform: uppercase;
margin-bottom: 14px;
}
.dash-traffic-card {
border-color: rgba(0, 212, 255, 0.15);
}
.dash-traffic-row {
display: flex;
gap: 24px;
margin-bottom: 8px;
}
.dash-traffic-item {
display: flex;
flex-direction: column;
gap: 2px;
flex: 1;
}
.dash-traffic-label {
font-size: .72rem;
color: var(--muted);
text-transform: uppercase;
letter-spacing: .06em;
}
.dash-traffic-val {
font-family: 'JetBrains Mono', 'Fira Code', monospace;
font-size: 1.1rem;
color: var(--accent);
text-shadow: 0 0 8px rgba(0, 212, 255, 0.15);
font-variant-numeric: tabular-nums;
}
.dash-traffic-total {
font-size: .95rem;
color: var(--text-dim);
text-shadow: none;
}
.dash-mem-row {
display: flex;
align-items: center;
gap: 12px;
padding-top: 8px;
border-top: 1px solid var(--border);
margin-top: 4px;
}
.dash-mode-switch {
display: flex;
background: var(--bg-deep);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
padding: 3px;
gap: 2px;
}
.dash-mode-switch .seg-btn {
flex: 1;
padding: 8px 12px;
border: none;
border-radius: calc(var(--radius-sm) - 2px);
background: transparent;
color: var(--muted);
cursor: pointer;
font-size: .85rem;
font-weight: 600;
transition: all .2s ease;
}
.dash-mode-switch .seg-btn.active {
background: linear-gradient(135deg, #0090b3, #00d4ff);
color: #fff;
box-shadow: 0 2px 8px rgba(0, 212, 255, 0.2);
}
.dash-mode-switch .seg-btn:not(.active):hover { background: var(--surface-3); color: var(--text); }
.dash-info-row {
display: flex;
align-items: baseline;
gap: 8px;
}
.dash-section {
margin-bottom: 20px;
}
.dash-section-header {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 12px;
}
.dash-section-header h3 {
font-size: 1rem;
font-weight: 700;
background: linear-gradient(135deg, var(--text), var(--accent));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.dash-group-list {
display: flex;
flex-direction: column;
gap: 12px;
}
.dash-group-card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
overflow: hidden;
backdrop-filter: blur(10px);
transition: all .3s ease;
}
.dash-group-card:hover {
border-color: var(--border-hi);
box-shadow: var(--glow-sm);
}
.dash-group-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 16px;
border-bottom: 1px solid var(--border);
}
.dash-group-title {
display: flex;
align-items: center;
gap: 8px;
}
.dash-group-name {
font-weight: 700;
font-size: .95rem;
color: var(--text);
}
.dash-proxy-list {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 6px;
padding: 10px 14px;
}
.dash-proxy-item {
display: flex;
align-items: center;
gap: 6px;
padding: 6px 10px;
border-radius: var(--radius-sm);
background: var(--surface-2);
border: 1px solid transparent;
cursor: pointer;
transition: all .15s ease;
font-size: .82rem;
}
.dash-proxy-item:hover {
border-color: var(--border-hi);
background: var(--surface-3);
transform: translateY(-1px);
}
.dash-proxy-item.dash-proxy-active {
border-color: rgba(0, 255, 136, 0.4);
background: rgba(0, 255, 136, 0.06);
}
.dash-proxy-name {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
color: var(--text);
font-weight: 600;
}
.dash-proxy-type {
font-size: .68rem;
color: var(--muted);
}
.dash-delay {
font-family: 'JetBrains Mono', 'Fira Code', monospace;
font-size: .78rem;
font-weight: 600;
white-space: nowrap;
font-variant-numeric: tabular-nums;
}
.dash-delay-good { color: var(--success); }
.dash-delay-medium { color: var(--warning); }
.dash-delay-slow { color: var(--danger); }
.dash-delay-unknown { color: var(--muted); }
.dash-conn-count {
font-size: .78rem;
font-weight: 700;
color: var(--accent);
background: rgba(0, 212, 255, 0.1);
border: 1px solid rgba(0, 212, 255, 0.3);
padding: 2px 8px;
border-radius: 12px;
font-variant-numeric: tabular-nums;
}
.dash-conn-table-wrap {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
overflow-x: auto;
backdrop-filter: blur(10px);
}
.dash-conn-table {
width: 100%;
border-collapse: collapse;
font-size: .82rem;
}
.dash-conn-table thead {
background: rgba(0, 212, 255, 0.03);
border-bottom: 1px solid var(--border);
}
.dash-conn-table th {
text-align: left;
padding: 10px 12px;
font-size: .72rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: .06em;
color: var(--muted);
white-space: nowrap;
}
.dash-conn-table td {
padding: 8px 12px;
border-bottom: 1px solid rgba(255, 255, 255, 0.03);
vertical-align: middle;
}
.dash-conn-table tbody tr:hover { background: rgba(0, 212, 255, 0.03); }
.dash-conn-host {
font-family: 'JetBrains Mono', 'Fira Code', monospace;
color: var(--text);
max-width: 200px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.dash-conn-chain {
color: var(--text-dim);
max-width: 220px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.dash-conn-traffic {
font-family: 'JetBrains Mono', 'Fira Code', monospace;
color: var(--text-dim);
font-size: .78rem;
font-variant-numeric: tabular-nums;
text-align: right;
}