feat(luci-theme-secubox): Add CRT P31 phosphor green terminal theme
Complete LuCI theme implementation with: - CRT P31 phosphor green color scheme with scanline effects - Header, footer, and sysauth login page templates - Cascading CSS with CRT glow and phosphor effects - CRT engine JS for terminal-style animations - CRT components JS for reusable UI components - UCI defaults to set as default LuCI theme - Updated Makefile for proper asset installation Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
54668158c8
commit
1140221f4a
@ -1,16 +1,45 @@
|
|||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
# SecuBox CRT P31 Phosphor Theme for OpenWrt LuCI
|
||||||
|
# CyberMind — SecuBox — 2026
|
||||||
|
|
||||||
include $(TOPDIR)/rules.mk
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
PKG_NAME:=luci-theme-secubox
|
PKG_NAME:=luci-theme-secubox
|
||||||
PKG_VERSION:=0.4.8
|
PKG_VERSION:=1.0.0
|
||||||
PKG_RELEASE:=1
|
PKG_RELEASE:=2
|
||||||
PKG_LICENSE:=Apache-2.0
|
PKG_MAINTAINER:=Gerald KERMA <devel@cybermind.fr>
|
||||||
PKG_MAINTAINER:=CyberMind <contact@cybermind.fr>
|
PKG_LICENSE:=MIT
|
||||||
|
|
||||||
LUCI_TITLE:=LuCI - SecuBox CyberMood Theme
|
LUCI_TITLE:=SecuBox CRT P31 Phosphor Theme
|
||||||
LUCI_DESCRIPTION:=Global CyberMood design system (CSS/JS/i18n) shared by all SecuBox dashboards.
|
|
||||||
LUCI_DEPENDS:=+luci-base
|
LUCI_DEPENDS:=+luci-base
|
||||||
LUCI_PKGARCH:=all
|
LUCI_DESCRIPTION:=CRT P31 phosphor green terminal theme for SecuBox mesh network appliance. Features scanlines, phosphor glow effects, multiple theme variants including CRT-P31, dark, cyberpunk, and more.
|
||||||
|
|
||||||
include $(TOPDIR)/feeds/luci/luci.mk
|
include $(TOPDIR)/feeds/luci/luci.mk
|
||||||
|
|
||||||
# call BuildPackage - OpenWrt buildroot
|
define Package/luci-theme-secubox/install
|
||||||
|
# Main theme directory (CRT P31 base)
|
||||||
|
$(INSTALL_DIR) $(1)/www/luci-static/secubox
|
||||||
|
$(CP) ./htdocs/luci-static/secubox/* $(1)/www/luci-static/secubox/
|
||||||
|
# Theme system resources
|
||||||
|
$(INSTALL_DIR) $(1)/www/luci-static/resources/secubox-theme
|
||||||
|
$(CP) ./htdocs/luci-static/resources/secubox-theme/* $(1)/www/luci-static/resources/secubox-theme/
|
||||||
|
# KISS theme helper for individual modules
|
||||||
|
$(INSTALL_DIR) $(1)/www/luci-static/resources/secubox
|
||||||
|
$(CP) ./htdocs/luci-static/resources/secubox/* $(1)/www/luci-static/resources/secubox/
|
||||||
|
# Lua view templates
|
||||||
|
$(INSTALL_DIR) $(1)/usr/lib/lua/luci/view/themes/secubox
|
||||||
|
$(CP) ./luasrc/luci/view/themes/secubox/* $(1)/usr/lib/lua/luci/view/themes/secubox/
|
||||||
|
# UCI defaults
|
||||||
|
$(INSTALL_DIR) $(1)/etc/uci-defaults
|
||||||
|
$(INSTALL_BIN) ./root/etc/uci-defaults/90-luci-theme-secubox $(1)/etc/uci-defaults/
|
||||||
|
endef
|
||||||
|
|
||||||
|
define Package/luci-theme-secubox/postinst
|
||||||
|
#!/bin/sh
|
||||||
|
[ -n "$${IPKG_INSTROOT}" ] || {
|
||||||
|
uci set luci.main.mediaurlbase='/luci-static/secubox'
|
||||||
|
uci commit luci
|
||||||
|
}
|
||||||
|
endef
|
||||||
|
|
||||||
|
$(eval $(call BuildPackage,luci-theme-secubox))
|
||||||
|
|||||||
@ -1,47 +1,123 @@
|
|||||||
# luci-theme-secubox
|
# SecuBox CRT P31 Phosphor Theme
|
||||||
|
|
||||||
SecuBox's CyberMood design system packaged as a LuCI theme. Ships shared CSS variables, reusable components, responsive layouts, translations, and the browser-side theme controller used by every SecuBox module (`'require secubox-theme/theme as Theme'`). Install alongside SecuBox modules to ensure consistent styling, language switching, and light/dark/cyberpunk variants.
|
A retro-futuristic CRT terminal theme for OpenWrt LuCI, designed for the SecuBox mesh security appliance.
|
||||||
|
|
||||||
```
|
## Features
|
||||||
./feeds/luci/luci-theme-secubox/
|
|
||||||
├── Makefile
|
- **P31 Phosphor Green** color palette - authentic CRT terminal aesthetic
|
||||||
└── htdocs/luci-static/resources/secubox-theme/
|
- **Scanlines overlay** - subtle CRT screen effect
|
||||||
├── core/ # Variables, reset, typography, animations, utilities
|
- **Phosphor glow effects** - text and button bloom on hover/focus
|
||||||
├── components/ # Buttons, cards, forms, tables, badges, alerts, etc.
|
- **Boot sequence animation** - terminal-style startup on first visit
|
||||||
├── layouts/ # Dashboard/grid/responsive helpers
|
- **Responsive design** - works on desktop and mobile
|
||||||
├── themes/ # Dark (default), light, cyberpunk variants
|
- **Full LuCI coverage** - all pages, forms, tables, and widgets styled
|
||||||
├── i18n/ # en/fr/de/es JSON dictionaries
|
|
||||||
├── secubox-theme.css
|
## Color Palette
|
||||||
├── secubox-theme.min.css
|
|
||||||
└── theme.js # Theme controller (init/apply/t/Theme.create*)
|
| Variable | Color | Usage |
|
||||||
|
|----------|-------|-------|
|
||||||
|
| `--p31-peak` | `#33ff66` | Maximum brightness - headers, active elements |
|
||||||
|
| `--p31-hot` | `#66ffaa` | High brightness - hover states |
|
||||||
|
| `--p31-mid` | `#22cc44` | Medium brightness - body text |
|
||||||
|
| `--p31-dim` | `#0f8822` | Low brightness - secondary text, borders |
|
||||||
|
| `--p31-ghost` | `#052210` | Ghosting/afterglow - subtle backgrounds |
|
||||||
|
| `--p31-decay` | `#ffb347` | Phosphor decay amber - warnings |
|
||||||
|
| `--tube-black` | `#050803` | Deep CRT black - page background |
|
||||||
|
| `--tube-deep` | `#080d05` | Card/panel backgrounds |
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
opkg update
|
||||||
|
opkg install luci-theme-secubox
|
||||||
```
|
```
|
||||||
|
|
||||||
## Usage
|
The theme will automatically be set as the default. To manually switch:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uci set luci.main.mediaurlbase='/luci-static/secubox'
|
||||||
|
uci commit luci
|
||||||
|
```
|
||||||
|
|
||||||
|
## JavaScript Components
|
||||||
|
|
||||||
|
### CRT Engine (`crt-engine.js`)
|
||||||
|
|
||||||
|
Provides CRT visual effects:
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
'use strict';
|
// Configure CRT effects
|
||||||
'require secubox-theme/theme as Theme';
|
SecuBoxCRT.configure({
|
||||||
|
enableScanlines: true, // Scanlines overlay
|
||||||
return view.extend({
|
enableFlicker: false, // Screen flicker (disabled by default)
|
||||||
load: function() {
|
enableGlow: true, // Phosphor glow on hover
|
||||||
return Theme.init();
|
enableBootSequence: true // Terminal boot animation
|
||||||
},
|
|
||||||
render: function() {
|
|
||||||
Theme.apply('dark'); // dark, light, cyberpunk
|
|
||||||
Theme.setLanguage('en'); // en, fr, de, es
|
|
||||||
|
|
||||||
return Theme.createPage({
|
|
||||||
title: Theme.t('dashboard.title'),
|
|
||||||
cards: [
|
|
||||||
Theme.createCard({
|
|
||||||
title: Theme.t('dashboard.overview'),
|
|
||||||
icon: '🚀',
|
|
||||||
content: this.renderOverview()
|
|
||||||
})
|
|
||||||
]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Trigger boot sequence manually
|
||||||
|
SecuBoxCRT.bootSequence();
|
||||||
|
|
||||||
|
// Typewriter effect on element
|
||||||
|
SecuBoxCRT.typewriterEffect(element, 50);
|
||||||
```
|
```
|
||||||
|
|
||||||
See `DOCS/GLOBAL_THEME_SYSTEM.md` for the full design reference. This package only contains the shared assets; each module is still responsible for importing `secubox-theme.css` (or `.min.css`) and using the exported helper methods.
|
### CRT Components (`crt-components.js`)
|
||||||
|
|
||||||
|
Reusable UI components:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Create a status widget
|
||||||
|
var widget = CRTComponents.createWidget({
|
||||||
|
title: 'CPU Load',
|
||||||
|
value: '45',
|
||||||
|
unit: '%',
|
||||||
|
status: 'normal' // or 'warning', 'danger'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create a status badge
|
||||||
|
var badge = CRTComponents.createBadge('Online', 'success');
|
||||||
|
|
||||||
|
// Create a progress bar
|
||||||
|
var progress = CRTComponents.createProgressBar(75, 100, '75%');
|
||||||
|
|
||||||
|
// Show toast notification
|
||||||
|
CRTComponents.toast('Settings saved', 'success', 3000);
|
||||||
|
```
|
||||||
|
|
||||||
|
## File Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
luci-theme-secubox/
|
||||||
|
├── Makefile # OpenWrt package makefile
|
||||||
|
├── htdocs/luci-static/secubox/
|
||||||
|
│ ├── cascade.css # Main theme stylesheet
|
||||||
|
│ ├── crt-engine.js # CRT visual effects engine
|
||||||
|
│ └── crt-components.js # Reusable UI components
|
||||||
|
├── luasrc/luci/view/themes/secubox/
|
||||||
|
│ ├── header.htm # Page header template
|
||||||
|
│ ├── footer.htm # Page footer template
|
||||||
|
│ └── sysauth.htm # Login page template
|
||||||
|
└── root/etc/uci-defaults/
|
||||||
|
└── 90-luci-theme-secubox # Default theme configuration
|
||||||
|
```
|
||||||
|
|
||||||
|
## CSS Customization
|
||||||
|
|
||||||
|
Override CSS variables in your module's stylesheet:
|
||||||
|
|
||||||
|
```css
|
||||||
|
/* Custom module styling */
|
||||||
|
.my-module {
|
||||||
|
background: var(--tube-deep);
|
||||||
|
border: 1px solid var(--p31-ghost);
|
||||||
|
color: var(--p31-mid);
|
||||||
|
}
|
||||||
|
|
||||||
|
.my-module .title {
|
||||||
|
color: var(--p31-peak);
|
||||||
|
text-shadow: var(--bloom-text);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT License - CyberMind 2026
|
||||||
|
|||||||
@ -0,0 +1,411 @@
|
|||||||
|
/**
|
||||||
|
* SecuBox CRT P31 Phosphor Green Theme
|
||||||
|
* File: themes/crt-p31.css
|
||||||
|
* Version: 1.0.0
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*
|
||||||
|
* Authentic CRT terminal aesthetic with P31 phosphor green
|
||||||
|
* Features scanlines, phosphor glow, and retro-futuristic styling
|
||||||
|
*/
|
||||||
|
|
||||||
|
body[data-secubox-theme="crt-p31"] {
|
||||||
|
/* P31 Phosphor Green Scale */
|
||||||
|
--cyber-p31-peak: #33ff66; /* Maximum brightness */
|
||||||
|
--cyber-p31-hot: #66ffaa; /* High brightness */
|
||||||
|
--cyber-p31-mid: #22cc44; /* Medium brightness */
|
||||||
|
--cyber-p31-dim: #0f8822; /* Low brightness */
|
||||||
|
--cyber-p31-ghost: #052210; /* Ghosting/afterglow */
|
||||||
|
|
||||||
|
/* Phosphor Decay (amber for warnings) */
|
||||||
|
--cyber-p31-decay: #ffb347;
|
||||||
|
--cyber-p31-decay-dim: #cc7722;
|
||||||
|
|
||||||
|
/* CRT Tube Colors */
|
||||||
|
--cyber-tube-black: #050803;
|
||||||
|
--cyber-tube-deep: #080d05;
|
||||||
|
--cyber-tube-bezel: #0d1208;
|
||||||
|
|
||||||
|
/* Map to cyber design system */
|
||||||
|
--cyber-bg-primary: var(--cyber-tube-black);
|
||||||
|
--cyber-bg-secondary: var(--cyber-tube-deep);
|
||||||
|
--cyber-bg-tertiary: var(--cyber-tube-bezel);
|
||||||
|
--cyber-surface: #0a1207;
|
||||||
|
--cyber-surface-light: #0f1a0c;
|
||||||
|
--cyber-overlay: rgba(5, 8, 3, 0.9);
|
||||||
|
|
||||||
|
/* Text colors */
|
||||||
|
--cyber-text-primary: var(--cyber-p31-mid);
|
||||||
|
--cyber-text-secondary: var(--cyber-p31-dim);
|
||||||
|
--cyber-text-muted: var(--cyber-p31-ghost);
|
||||||
|
--cyber-text-inverse: var(--cyber-tube-black);
|
||||||
|
|
||||||
|
/* Accent - all green */
|
||||||
|
--cyber-accent-primary: var(--cyber-p31-mid);
|
||||||
|
--cyber-accent-primary-end: var(--cyber-p31-peak);
|
||||||
|
--cyber-accent-secondary: var(--cyber-p31-hot);
|
||||||
|
--cyber-accent-tertiary: var(--cyber-p31-dim);
|
||||||
|
|
||||||
|
/* Semantic colors */
|
||||||
|
--cyber-success: var(--cyber-p31-peak);
|
||||||
|
--cyber-success-soft: rgba(51, 255, 102, 0.1);
|
||||||
|
--cyber-warning: var(--cyber-p31-decay);
|
||||||
|
--cyber-warning-soft: rgba(255, 179, 71, 0.1);
|
||||||
|
--cyber-danger: #ff6688;
|
||||||
|
--cyber-danger-soft: rgba(255, 68, 102, 0.1);
|
||||||
|
--cyber-info: var(--cyber-p31-mid);
|
||||||
|
--cyber-info-soft: rgba(51, 255, 102, 0.05);
|
||||||
|
|
||||||
|
/* Fonts - monospace only */
|
||||||
|
--cyber-font-display: 'Courier Prime', 'IBM Plex Mono', 'Fira Code', 'Courier New', monospace;
|
||||||
|
--cyber-font-body: 'Courier Prime', 'IBM Plex Mono', 'Fira Code', 'Courier New', monospace;
|
||||||
|
--cyber-font-mono: 'Courier Prime', 'IBM Plex Mono', 'Fira Code', 'Courier New', monospace;
|
||||||
|
|
||||||
|
/* Shadows & borders - green glow */
|
||||||
|
--cyber-shadow: 0 0 20px rgba(51, 255, 102, 0.15);
|
||||||
|
--cyber-shadow-soft: 0 0 10px rgba(51, 255, 102, 0.1);
|
||||||
|
--cyber-border: 1px solid var(--cyber-p31-ghost);
|
||||||
|
--cyber-border-strong: 1px solid var(--cyber-p31-dim);
|
||||||
|
|
||||||
|
/* Phosphor glow effects */
|
||||||
|
--cyber-bloom-text: 0 0 2px var(--cyber-p31-peak), 0 0 6px var(--cyber-p31-peak), 0 0 14px rgba(51,255,102,0.5);
|
||||||
|
--cyber-bloom-soft: 0 0 6px var(--cyber-p31-peak), 0 0 14px rgba(51,255,102,0.5);
|
||||||
|
--cyber-bloom-box: 0 0 8px rgba(51,255,102,0.3), inset 0 0 4px rgba(51,255,102,0.1);
|
||||||
|
|
||||||
|
/* Glass effects - darker for CRT */
|
||||||
|
--cyber-glass-bg: rgba(5, 34, 16, 0.3);
|
||||||
|
--cyber-glass-border: var(--cyber-p31-ghost);
|
||||||
|
--cyber-glass-blur: 5px;
|
||||||
|
--cyber-glass-shadow: 0 0 20px rgba(51, 255, 102, 0.1);
|
||||||
|
|
||||||
|
/* Gradients - green phosphor */
|
||||||
|
--cyber-gradient-primary: linear-gradient(135deg, var(--cyber-p31-dim) 0%, var(--cyber-p31-mid) 100%);
|
||||||
|
--cyber-gradient-steel: linear-gradient(135deg, var(--cyber-tube-bezel) 0%, var(--cyber-tube-black) 100%);
|
||||||
|
--cyber-gradient-chrome: linear-gradient(135deg, var(--cyber-p31-dim) 0%, var(--cyber-tube-deep) 100%);
|
||||||
|
--cyber-gradient-cyber: linear-gradient(135deg, var(--cyber-p31-mid) 0%, var(--cyber-p31-peak) 100%);
|
||||||
|
|
||||||
|
/* Base styling */
|
||||||
|
background: var(--cyber-tube-black);
|
||||||
|
color: var(--cyber-p31-mid);
|
||||||
|
font-family: var(--cyber-font-body);
|
||||||
|
letter-spacing: 0.02em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Scanlines overlay */
|
||||||
|
body[data-secubox-theme="crt-p31"]::before {
|
||||||
|
content: '';
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
pointer-events: none;
|
||||||
|
background: repeating-linear-gradient(
|
||||||
|
0deg,
|
||||||
|
rgba(0, 0, 0, 0.15),
|
||||||
|
rgba(0, 0, 0, 0.15) 1px,
|
||||||
|
transparent 1px,
|
||||||
|
transparent 2px
|
||||||
|
);
|
||||||
|
z-index: 9999;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* CRT screen curvature effect */
|
||||||
|
body[data-secubox-theme="crt-p31"] {
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: inset 0 0 60px rgba(0, 0, 0, 0.5), inset 0 0 10px rgba(51, 255, 102, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Headers with glow */
|
||||||
|
body[data-secubox-theme="crt-p31"] h1,
|
||||||
|
body[data-secubox-theme="crt-p31"] h2,
|
||||||
|
body[data-secubox-theme="crt-p31"] h3,
|
||||||
|
body[data-secubox-theme="crt-p31"] .sh-page-title,
|
||||||
|
body[data-secubox-theme="crt-p31"] .card-title {
|
||||||
|
color: var(--cyber-p31-peak);
|
||||||
|
text-shadow: var(--cyber-bloom-soft);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Links */
|
||||||
|
body[data-secubox-theme="crt-p31"] a {
|
||||||
|
color: var(--cyber-p31-mid);
|
||||||
|
text-decoration: none;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
body[data-secubox-theme="crt-p31"] a:hover {
|
||||||
|
color: var(--cyber-p31-peak);
|
||||||
|
text-shadow: var(--cyber-bloom-soft);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Buttons */
|
||||||
|
body[data-secubox-theme="crt-p31"] button,
|
||||||
|
body[data-secubox-theme="crt-p31"] .btn,
|
||||||
|
body[data-secubox-theme="crt-p31"] .cbi-button {
|
||||||
|
background: transparent;
|
||||||
|
border: 1px solid var(--cyber-p31-dim);
|
||||||
|
color: var(--cyber-p31-mid);
|
||||||
|
font-family: inherit;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
body[data-secubox-theme="crt-p31"] button:hover,
|
||||||
|
body[data-secubox-theme="crt-p31"] .btn:hover,
|
||||||
|
body[data-secubox-theme="crt-p31"] .cbi-button:hover {
|
||||||
|
color: var(--cyber-p31-peak);
|
||||||
|
border-color: var(--cyber-p31-mid);
|
||||||
|
text-shadow: var(--cyber-bloom-soft);
|
||||||
|
}
|
||||||
|
|
||||||
|
body[data-secubox-theme="crt-p31"] .btn-primary,
|
||||||
|
body[data-secubox-theme="crt-p31"] .cbi-button-save,
|
||||||
|
body[data-secubox-theme="crt-p31"] .cbi-button-apply {
|
||||||
|
border-color: var(--cyber-p31-mid);
|
||||||
|
color: var(--cyber-p31-peak);
|
||||||
|
}
|
||||||
|
|
||||||
|
body[data-secubox-theme="crt-p31"] .btn-primary:hover,
|
||||||
|
body[data-secubox-theme="crt-p31"] .cbi-button-save:hover,
|
||||||
|
body[data-secubox-theme="crt-p31"] .cbi-button-apply:hover {
|
||||||
|
background: var(--cyber-p31-mid);
|
||||||
|
color: var(--cyber-tube-black);
|
||||||
|
text-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
body[data-secubox-theme="crt-p31"] .btn-danger,
|
||||||
|
body[data-secubox-theme="crt-p31"] .cbi-button-remove {
|
||||||
|
border-color: var(--cyber-p31-decay-dim);
|
||||||
|
color: var(--cyber-p31-decay);
|
||||||
|
}
|
||||||
|
|
||||||
|
body[data-secubox-theme="crt-p31"] .btn-danger:hover,
|
||||||
|
body[data-secubox-theme="crt-p31"] .cbi-button-remove:hover {
|
||||||
|
background: var(--cyber-p31-decay);
|
||||||
|
color: var(--cyber-tube-black);
|
||||||
|
text-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Form inputs */
|
||||||
|
body[data-secubox-theme="crt-p31"] input[type="text"],
|
||||||
|
body[data-secubox-theme="crt-p31"] input[type="password"],
|
||||||
|
body[data-secubox-theme="crt-p31"] input[type="number"],
|
||||||
|
body[data-secubox-theme="crt-p31"] input[type="email"],
|
||||||
|
body[data-secubox-theme="crt-p31"] textarea,
|
||||||
|
body[data-secubox-theme="crt-p31"] select {
|
||||||
|
background: var(--cyber-tube-black);
|
||||||
|
border: 1px solid var(--cyber-p31-ghost);
|
||||||
|
color: var(--cyber-p31-mid);
|
||||||
|
font-family: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
body[data-secubox-theme="crt-p31"] input:focus,
|
||||||
|
body[data-secubox-theme="crt-p31"] select:focus,
|
||||||
|
body[data-secubox-theme="crt-p31"] textarea:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--cyber-p31-mid);
|
||||||
|
box-shadow: var(--cyber-bloom-box);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Cards */
|
||||||
|
body[data-secubox-theme="crt-p31"] .card,
|
||||||
|
body[data-secubox-theme="crt-p31"] .panel,
|
||||||
|
body[data-secubox-theme="crt-p31"] .cbi-section,
|
||||||
|
body[data-secubox-theme="crt-p31"] .cbi-map {
|
||||||
|
background: var(--cyber-tube-deep);
|
||||||
|
border: 1px solid var(--cyber-p31-ghost);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tables */
|
||||||
|
body[data-secubox-theme="crt-p31"] table th {
|
||||||
|
background: var(--cyber-tube-black);
|
||||||
|
color: var(--cyber-p31-dim);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
font-weight: normal;
|
||||||
|
border-bottom: 1px solid var(--cyber-p31-ghost);
|
||||||
|
}
|
||||||
|
|
||||||
|
body[data-secubox-theme="crt-p31"] table td {
|
||||||
|
border-bottom: 1px solid var(--cyber-p31-ghost);
|
||||||
|
color: var(--cyber-p31-mid);
|
||||||
|
}
|
||||||
|
|
||||||
|
body[data-secubox-theme="crt-p31"] table tr:hover td {
|
||||||
|
background: rgba(51, 255, 102, 0.03);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Status indicators */
|
||||||
|
body[data-secubox-theme="crt-p31"] .status-online,
|
||||||
|
body[data-secubox-theme="crt-p31"] .online,
|
||||||
|
body[data-secubox-theme="crt-p31"] .running {
|
||||||
|
color: var(--cyber-p31-peak);
|
||||||
|
text-shadow: 0 0 6px var(--cyber-p31-peak);
|
||||||
|
}
|
||||||
|
|
||||||
|
body[data-secubox-theme="crt-p31"] .status-offline,
|
||||||
|
body[data-secubox-theme="crt-p31"] .offline,
|
||||||
|
body[data-secubox-theme="crt-p31"] .stopped {
|
||||||
|
color: #ff6688;
|
||||||
|
text-shadow: 0 0 6px #ff4466;
|
||||||
|
}
|
||||||
|
|
||||||
|
body[data-secubox-theme="crt-p31"] .status-warning,
|
||||||
|
body[data-secubox-theme="crt-p31"] .warning {
|
||||||
|
color: var(--cyber-p31-decay);
|
||||||
|
text-shadow: 0 0 6px var(--cyber-p31-decay);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Badges */
|
||||||
|
body[data-secubox-theme="crt-p31"] .badge,
|
||||||
|
body[data-secubox-theme="crt-p31"] .label {
|
||||||
|
border-radius: 2px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
body[data-secubox-theme="crt-p31"] .badge-success {
|
||||||
|
background: rgba(51, 255, 102, 0.1);
|
||||||
|
border: 1px solid var(--cyber-p31-mid);
|
||||||
|
color: var(--cyber-p31-peak);
|
||||||
|
}
|
||||||
|
|
||||||
|
body[data-secubox-theme="crt-p31"] .badge-warning {
|
||||||
|
background: rgba(255, 179, 71, 0.1);
|
||||||
|
border: 1px solid var(--cyber-p31-decay-dim);
|
||||||
|
color: var(--cyber-p31-decay);
|
||||||
|
}
|
||||||
|
|
||||||
|
body[data-secubox-theme="crt-p31"] .badge-danger {
|
||||||
|
background: rgba(255, 68, 102, 0.1);
|
||||||
|
border: 1px solid #ff4466;
|
||||||
|
color: #ff6688;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Progress bars */
|
||||||
|
body[data-secubox-theme="crt-p31"] .progress,
|
||||||
|
body[data-secubox-theme="crt-p31"] .cbi-progressbar {
|
||||||
|
background: var(--cyber-tube-black);
|
||||||
|
border: 1px solid var(--cyber-p31-ghost);
|
||||||
|
}
|
||||||
|
|
||||||
|
body[data-secubox-theme="crt-p31"] .progress-bar,
|
||||||
|
body[data-secubox-theme="crt-p31"] .cbi-progressbar > div {
|
||||||
|
background: linear-gradient(90deg, var(--cyber-p31-dim), var(--cyber-p31-mid));
|
||||||
|
box-shadow: 0 0 10px var(--cyber-p31-mid);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Navigation */
|
||||||
|
body[data-secubox-theme="crt-p31"] nav a,
|
||||||
|
body[data-secubox-theme="crt-p31"] .main-left a {
|
||||||
|
color: var(--cyber-p31-dim);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
border-left: 2px solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
body[data-secubox-theme="crt-p31"] nav a:hover,
|
||||||
|
body[data-secubox-theme="crt-p31"] .main-left a:hover {
|
||||||
|
color: var(--cyber-p31-mid);
|
||||||
|
background: rgba(51, 255, 102, 0.05);
|
||||||
|
border-left-color: var(--cyber-p31-dim);
|
||||||
|
}
|
||||||
|
|
||||||
|
body[data-secubox-theme="crt-p31"] nav a.active,
|
||||||
|
body[data-secubox-theme="crt-p31"] .main-left .active a {
|
||||||
|
color: var(--cyber-p31-peak);
|
||||||
|
background: rgba(51, 255, 102, 0.1);
|
||||||
|
border-left-color: var(--cyber-p31-peak);
|
||||||
|
text-shadow: var(--cyber-bloom-soft);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Alerts */
|
||||||
|
body[data-secubox-theme="crt-p31"] .alert-success {
|
||||||
|
background: rgba(51, 255, 102, 0.1);
|
||||||
|
border: 1px solid var(--cyber-p31-dim);
|
||||||
|
color: var(--cyber-p31-mid);
|
||||||
|
}
|
||||||
|
|
||||||
|
body[data-secubox-theme="crt-p31"] .alert-warning {
|
||||||
|
background: rgba(255, 179, 71, 0.1);
|
||||||
|
border: 1px solid var(--cyber-p31-decay-dim);
|
||||||
|
color: var(--cyber-p31-decay);
|
||||||
|
}
|
||||||
|
|
||||||
|
body[data-secubox-theme="crt-p31"] .alert-danger,
|
||||||
|
body[data-secubox-theme="crt-p31"] .alert-error {
|
||||||
|
background: rgba(255, 68, 102, 0.1);
|
||||||
|
border: 1px solid #ff4466;
|
||||||
|
color: #ff6688;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Scrollbars */
|
||||||
|
body[data-secubox-theme="crt-p31"]::-webkit-scrollbar {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
body[data-secubox-theme="crt-p31"]::-webkit-scrollbar-track {
|
||||||
|
background: var(--cyber-tube-black);
|
||||||
|
}
|
||||||
|
|
||||||
|
body[data-secubox-theme="crt-p31"]::-webkit-scrollbar-thumb {
|
||||||
|
background: var(--cyber-p31-ghost);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
body[data-secubox-theme="crt-p31"]::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: var(--cyber-p31-dim);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Selection */
|
||||||
|
body[data-secubox-theme="crt-p31"] ::selection {
|
||||||
|
background: var(--cyber-p31-dim);
|
||||||
|
color: var(--cyber-tube-black);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Widgets */
|
||||||
|
body[data-secubox-theme="crt-p31"] .widget-value,
|
||||||
|
body[data-secubox-theme="crt-p31"] .stat-value {
|
||||||
|
color: var(--cyber-p31-peak);
|
||||||
|
text-shadow: var(--cyber-bloom-text);
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
body[data-secubox-theme="crt-p31"] .widget-label,
|
||||||
|
body[data-secubox-theme="crt-p31"] .stat-label {
|
||||||
|
color: var(--cyber-p31-dim);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Topology visualization */
|
||||||
|
body[data-secubox-theme="crt-p31"] .topology-node circle {
|
||||||
|
fill: var(--cyber-p31-dim);
|
||||||
|
stroke: var(--cyber-p31-mid);
|
||||||
|
}
|
||||||
|
|
||||||
|
body[data-secubox-theme="crt-p31"] .topology-node.active circle {
|
||||||
|
fill: var(--cyber-p31-mid);
|
||||||
|
stroke: var(--cyber-p31-peak);
|
||||||
|
}
|
||||||
|
|
||||||
|
body[data-secubox-theme="crt-p31"] .topology-edge {
|
||||||
|
stroke: var(--cyber-p31-ghost);
|
||||||
|
}
|
||||||
|
|
||||||
|
body[data-secubox-theme="crt-p31"] .topology-edge.active {
|
||||||
|
stroke: var(--cyber-p31-dim);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Code blocks */
|
||||||
|
body[data-secubox-theme="crt-p31"] code,
|
||||||
|
body[data-secubox-theme="crt-p31"] pre {
|
||||||
|
background: var(--cyber-tube-black);
|
||||||
|
border: 1px solid var(--cyber-p31-ghost);
|
||||||
|
color: var(--cyber-p31-mid);
|
||||||
|
}
|
||||||
@ -0,0 +1,796 @@
|
|||||||
|
/* ============================================
|
||||||
|
SecuBox CRT P31 Theme for OpenWrt LuCI
|
||||||
|
CyberMind — SecuBox — 2026
|
||||||
|
============================================ */
|
||||||
|
|
||||||
|
/* === CSS Variables === */
|
||||||
|
:root {
|
||||||
|
/* P31 Phosphor Green Scale */
|
||||||
|
--p31-peak: #33ff66; /* Maximum brightness - headers, active elements */
|
||||||
|
--p31-hot: #66ffaa; /* High brightness - hover states */
|
||||||
|
--p31-mid: #22cc44; /* Medium brightness - body text */
|
||||||
|
--p31-dim: #0f8822; /* Low brightness - secondary text, borders */
|
||||||
|
--p31-ghost: #052210; /* Ghosting/afterglow - subtle backgrounds */
|
||||||
|
|
||||||
|
/* Phosphor Decay (amber for warnings/errors) */
|
||||||
|
--p31-decay: #ffb347; /* Warning/caution */
|
||||||
|
--p31-decay-dim: #cc7722; /* Muted warning */
|
||||||
|
|
||||||
|
/* CRT Tube Colors */
|
||||||
|
--tube-black: #050803; /* Deep CRT black */
|
||||||
|
--tube-deep: #080d05; /* Card backgrounds */
|
||||||
|
--tube-bezel: #0d1208; /* Panel borders */
|
||||||
|
|
||||||
|
/* Semantic Aliases */
|
||||||
|
--bg-dark: var(--tube-black);
|
||||||
|
--bg-card: var(--tube-deep);
|
||||||
|
--border: var(--p31-ghost);
|
||||||
|
--text: var(--p31-mid);
|
||||||
|
--text-bright: var(--p31-peak);
|
||||||
|
--text-dim: var(--p31-dim);
|
||||||
|
|
||||||
|
/* Glow Effects */
|
||||||
|
--bloom-text: 0 0 2px var(--p31-peak), 0 0 6px var(--p31-peak), 0 0 14px rgba(51,255,102,0.5);
|
||||||
|
--bloom-soft: 0 0 6px var(--p31-peak), 0 0 14px rgba(51,255,102,0.5);
|
||||||
|
--bloom-box: 0 0 8px rgba(51,255,102,0.3), inset 0 0 4px rgba(51,255,102,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === Reset & Base === */
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
html, body {
|
||||||
|
background: var(--tube-black) !important;
|
||||||
|
color: var(--p31-mid) !important;
|
||||||
|
font-family: 'Courier Prime', 'IBM Plex Mono', 'Fira Code', 'Courier New', 'Lucida Console', monospace !important;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.5;
|
||||||
|
letter-spacing: 0.02em;
|
||||||
|
min-height: 100vh;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === Scanlines Overlay === */
|
||||||
|
body::before {
|
||||||
|
content: '';
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
pointer-events: none;
|
||||||
|
background: repeating-linear-gradient(
|
||||||
|
0deg,
|
||||||
|
rgba(0, 0, 0, 0.15),
|
||||||
|
rgba(0, 0, 0, 0.15) 1px,
|
||||||
|
transparent 1px,
|
||||||
|
transparent 2px
|
||||||
|
);
|
||||||
|
z-index: 9999;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === Text Flicker Animation === */
|
||||||
|
@keyframes textFlicker {
|
||||||
|
0%, 100% { opacity: 1; }
|
||||||
|
50% { opacity: 0.98; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.crt-text {
|
||||||
|
text-shadow: var(--bloom-text);
|
||||||
|
animation: textFlicker 0.1s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === Header === */
|
||||||
|
header, .main-head, #maincontent > header, .container > header {
|
||||||
|
background: var(--tube-deep) !important;
|
||||||
|
border-bottom: 1px solid var(--p31-ghost) !important;
|
||||||
|
padding: 0.75rem 1.5rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
header h1, .main-head h1, #maincontent > header h1 {
|
||||||
|
font-size: 1.2rem !important;
|
||||||
|
color: var(--p31-peak) !important;
|
||||||
|
text-shadow: var(--bloom-text) !important;
|
||||||
|
font-weight: bold !important;
|
||||||
|
letter-spacing: 2px !important;
|
||||||
|
margin: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
header h1::before {
|
||||||
|
content: '[ ';
|
||||||
|
color: var(--p31-dim);
|
||||||
|
}
|
||||||
|
|
||||||
|
header h1::after {
|
||||||
|
content: ' ]';
|
||||||
|
color: var(--p31-dim);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === Navigation Sidebar === */
|
||||||
|
.main-left, #mainmenu, nav.main, .luci-nav {
|
||||||
|
background: var(--tube-deep) !important;
|
||||||
|
border-right: 1px solid var(--p31-ghost) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-left ul, #mainmenu ul, nav.main ul {
|
||||||
|
list-style: none !important;
|
||||||
|
margin: 0 !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-left li a, #mainmenu li a, nav.main li a,
|
||||||
|
.main-left li > span, #mainmenu li > span {
|
||||||
|
display: block !important;
|
||||||
|
padding: 0.6rem 1.2rem !important;
|
||||||
|
color: var(--p31-dim) !important;
|
||||||
|
text-decoration: none !important;
|
||||||
|
font-size: 0.85rem !important;
|
||||||
|
text-transform: uppercase !important;
|
||||||
|
letter-spacing: 1px !important;
|
||||||
|
border-left: 2px solid transparent !important;
|
||||||
|
transition: all 0.2s ease !important;
|
||||||
|
background: transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-left li a:hover, #mainmenu li a:hover, nav.main li a:hover {
|
||||||
|
color: var(--p31-mid) !important;
|
||||||
|
background: rgba(51, 255, 102, 0.05) !important;
|
||||||
|
border-left-color: var(--p31-dim) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-left li.active > a, .main-left li.selected > a,
|
||||||
|
#mainmenu li.active > a, #mainmenu li.selected > a,
|
||||||
|
nav.main li.active a {
|
||||||
|
color: var(--p31-peak) !important;
|
||||||
|
background: rgba(51, 255, 102, 0.1) !important;
|
||||||
|
border-left-color: var(--p31-peak) !important;
|
||||||
|
text-shadow: var(--bloom-soft) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Category Headers */
|
||||||
|
.main-left li.slide, #mainmenu li.slide {
|
||||||
|
padding: 0.8rem 1.2rem 0.4rem !important;
|
||||||
|
color: var(--p31-ghost) !important;
|
||||||
|
font-size: 0.7rem !important;
|
||||||
|
text-transform: uppercase !important;
|
||||||
|
letter-spacing: 2px !important;
|
||||||
|
border-bottom: 1px solid var(--p31-ghost) !important;
|
||||||
|
margin-top: 0.5rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === Main Content === */
|
||||||
|
.main-right, #maincontent, main, .container {
|
||||||
|
flex: 1 !important;
|
||||||
|
padding: 1.5rem !important;
|
||||||
|
background: var(--tube-black) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === Cards/Panels === */
|
||||||
|
.cbi-map, .cbi-section, fieldset, .panel, .card,
|
||||||
|
div.cbi-value, .cbi-section-node {
|
||||||
|
background: var(--tube-deep) !important;
|
||||||
|
border: 1px solid var(--p31-ghost) !important;
|
||||||
|
border-radius: 4px !important;
|
||||||
|
padding: 1rem !important;
|
||||||
|
margin-bottom: 1rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cbi-section-node {
|
||||||
|
background: transparent !important;
|
||||||
|
border: none !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Section Headers */
|
||||||
|
.cbi-section h2, .cbi-section h3, .cbi-section-title,
|
||||||
|
legend, .panel-title, .card-title {
|
||||||
|
color: var(--p31-peak) !important;
|
||||||
|
font-size: 0.9rem !important;
|
||||||
|
text-transform: uppercase !important;
|
||||||
|
letter-spacing: 2px !important;
|
||||||
|
padding-bottom: 0.5rem !important;
|
||||||
|
margin-bottom: 1rem !important;
|
||||||
|
border-bottom: 1px solid var(--p31-ghost) !important;
|
||||||
|
text-shadow: var(--bloom-soft) !important;
|
||||||
|
background: transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === Tables === */
|
||||||
|
table, .table {
|
||||||
|
width: 100% !important;
|
||||||
|
border-collapse: collapse !important;
|
||||||
|
font-size: 0.85rem !important;
|
||||||
|
background: var(--tube-deep) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
th, thead td, .table-titles td {
|
||||||
|
color: var(--p31-dim) !important;
|
||||||
|
font-weight: normal !important;
|
||||||
|
text-transform: uppercase !important;
|
||||||
|
font-size: 0.75rem !important;
|
||||||
|
letter-spacing: 1px !important;
|
||||||
|
padding: 0.75rem !important;
|
||||||
|
text-align: left !important;
|
||||||
|
border-bottom: 1px solid var(--p31-ghost) !important;
|
||||||
|
background: var(--tube-black) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
td, .table td, .cbi-section-table td {
|
||||||
|
padding: 0.75rem !important;
|
||||||
|
border-bottom: 1px solid var(--p31-ghost) !important;
|
||||||
|
color: var(--p31-mid) !important;
|
||||||
|
background: transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr:hover td, .tr:hover .td {
|
||||||
|
background: rgba(51, 255, 102, 0.03) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === Forms === */
|
||||||
|
input[type="text"],
|
||||||
|
input[type="password"],
|
||||||
|
input[type="number"],
|
||||||
|
input[type="email"],
|
||||||
|
input[type="url"],
|
||||||
|
input[type="tel"],
|
||||||
|
textarea,
|
||||||
|
select,
|
||||||
|
.cbi-input-text,
|
||||||
|
.cbi-input-password,
|
||||||
|
.cbi-input-select {
|
||||||
|
background: var(--tube-black) !important;
|
||||||
|
border: 1px solid var(--p31-ghost) !important;
|
||||||
|
color: var(--p31-mid) !important;
|
||||||
|
padding: 0.5rem 0.75rem !important;
|
||||||
|
font-family: inherit !important;
|
||||||
|
font-size: 0.9rem !important;
|
||||||
|
border-radius: 3px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:focus, select:focus, textarea:focus {
|
||||||
|
outline: none !important;
|
||||||
|
border-color: var(--p31-mid) !important;
|
||||||
|
box-shadow: var(--bloom-box) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
input::placeholder, textarea::placeholder {
|
||||||
|
color: var(--p31-ghost) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Checkboxes & Radio */
|
||||||
|
input[type="checkbox"],
|
||||||
|
input[type="radio"] {
|
||||||
|
accent-color: var(--p31-peak) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === Buttons === */
|
||||||
|
.cbi-button, button, input[type="submit"],
|
||||||
|
input[type="button"], .btn {
|
||||||
|
background: transparent !important;
|
||||||
|
border: 1px solid var(--p31-dim) !important;
|
||||||
|
color: var(--p31-mid) !important;
|
||||||
|
padding: 0.5rem 1rem !important;
|
||||||
|
font-family: inherit !important;
|
||||||
|
font-size: 0.8rem !important;
|
||||||
|
text-transform: uppercase !important;
|
||||||
|
letter-spacing: 1px !important;
|
||||||
|
cursor: pointer !important;
|
||||||
|
border-radius: 3px !important;
|
||||||
|
transition: all 0.2s ease !important;
|
||||||
|
text-decoration: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cbi-button:hover, button:hover, .btn:hover {
|
||||||
|
color: var(--p31-peak) !important;
|
||||||
|
border-color: var(--p31-mid) !important;
|
||||||
|
text-shadow: var(--bloom-soft) !important;
|
||||||
|
background: transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cbi-button-save, .cbi-button-apply, .btn-primary,
|
||||||
|
.cbi-button-action, .cbi-button-positive {
|
||||||
|
border-color: var(--p31-mid) !important;
|
||||||
|
color: var(--p31-peak) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cbi-button-save:hover, .btn-primary:hover,
|
||||||
|
.cbi-button-apply:hover, .cbi-button-action:hover {
|
||||||
|
background: var(--p31-mid) !important;
|
||||||
|
color: var(--tube-black) !important;
|
||||||
|
text-shadow: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cbi-button-remove, .cbi-button-reset, .btn-danger,
|
||||||
|
.cbi-button-negative {
|
||||||
|
border-color: var(--p31-decay-dim) !important;
|
||||||
|
color: var(--p31-decay) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cbi-button-remove:hover, .btn-danger:hover,
|
||||||
|
.cbi-button-reset:hover, .cbi-button-negative:hover {
|
||||||
|
background: var(--p31-decay) !important;
|
||||||
|
color: var(--tube-black) !important;
|
||||||
|
text-shadow: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === Status Badges === */
|
||||||
|
.badge, .label, .cbi-value-field .ifacebadge,
|
||||||
|
span.zonebadge {
|
||||||
|
display: inline-block !important;
|
||||||
|
padding: 0.15rem 0.5rem !important;
|
||||||
|
font-size: 0.7rem !important;
|
||||||
|
text-transform: uppercase !important;
|
||||||
|
letter-spacing: 1px !important;
|
||||||
|
border-radius: 2px !important;
|
||||||
|
border: 1px solid !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-success, .label-success, .badge.success {
|
||||||
|
border-color: var(--p31-mid) !important;
|
||||||
|
color: var(--p31-peak) !important;
|
||||||
|
background: rgba(51, 255, 102, 0.1) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-warning, .label-warning, .badge.warning {
|
||||||
|
border-color: var(--p31-decay-dim) !important;
|
||||||
|
color: var(--p31-decay) !important;
|
||||||
|
background: rgba(255, 179, 71, 0.1) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-danger, .label-danger, .badge.danger, .badge.error {
|
||||||
|
border-color: #ff4466 !important;
|
||||||
|
color: #ff6688 !important;
|
||||||
|
background: rgba(255, 68, 102, 0.1) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-info, .label-info, .badge.info {
|
||||||
|
border-color: var(--p31-dim) !important;
|
||||||
|
color: var(--p31-mid) !important;
|
||||||
|
background: rgba(51, 255, 102, 0.05) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === Alerts/Notifications === */
|
||||||
|
.alert, .notice, .cbi-section-error, .errorbox,
|
||||||
|
.cbi-section-warning, .warningbox {
|
||||||
|
padding: 1rem !important;
|
||||||
|
border-radius: 4px !important;
|
||||||
|
margin-bottom: 1rem !important;
|
||||||
|
border: 1px solid !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-success, .notice.success, .successbox {
|
||||||
|
background: rgba(51, 255, 102, 0.1) !important;
|
||||||
|
border-color: var(--p31-dim) !important;
|
||||||
|
color: var(--p31-mid) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-warning, .notice.warning, .warningbox,
|
||||||
|
.cbi-section-warning {
|
||||||
|
background: rgba(255, 179, 71, 0.1) !important;
|
||||||
|
border-color: var(--p31-decay-dim) !important;
|
||||||
|
color: var(--p31-decay) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-error, .notice.error, .errorbox,
|
||||||
|
.cbi-section-error, .alert-danger {
|
||||||
|
background: rgba(255, 68, 102, 0.1) !important;
|
||||||
|
border-color: #ff4466 !important;
|
||||||
|
color: #ff6688 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === Progress Bars === */
|
||||||
|
.cbi-progressbar, .progress {
|
||||||
|
background: var(--tube-black) !important;
|
||||||
|
border: 1px solid var(--p31-ghost) !important;
|
||||||
|
border-radius: 2px !important;
|
||||||
|
height: 20px !important;
|
||||||
|
overflow: hidden !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cbi-progressbar > div, .progress-bar {
|
||||||
|
background: linear-gradient(90deg, var(--p31-dim), var(--p31-mid)) !important;
|
||||||
|
height: 100% !important;
|
||||||
|
box-shadow: 0 0 10px var(--p31-mid) !important;
|
||||||
|
transition: width 0.3s ease !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === Tabs === */
|
||||||
|
.cbi-tabmenu, .tabs, ul.cbi-tabmenu {
|
||||||
|
display: flex !important;
|
||||||
|
border-bottom: 1px solid var(--p31-ghost) !important;
|
||||||
|
margin-bottom: 1rem !important;
|
||||||
|
list-style: none !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
background: transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cbi-tabmenu li, .tabs .tab {
|
||||||
|
list-style: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cbi-tabmenu li a, .tabs .tab, .cbi-tabmenu li > a {
|
||||||
|
display: block !important;
|
||||||
|
padding: 0.6rem 1rem !important;
|
||||||
|
color: var(--p31-dim) !important;
|
||||||
|
text-decoration: none !important;
|
||||||
|
font-size: 0.8rem !important;
|
||||||
|
text-transform: uppercase !important;
|
||||||
|
letter-spacing: 1px !important;
|
||||||
|
border: 1px solid transparent !important;
|
||||||
|
border-bottom: none !important;
|
||||||
|
margin-bottom: -1px !important;
|
||||||
|
cursor: pointer !important;
|
||||||
|
background: transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cbi-tabmenu li.cbi-tab a, .tabs .tab.active,
|
||||||
|
.cbi-tabmenu li.cbi-tab > a {
|
||||||
|
color: var(--p31-peak) !important;
|
||||||
|
border-color: var(--p31-ghost) !important;
|
||||||
|
background: var(--tube-deep) !important;
|
||||||
|
text-shadow: var(--bloom-soft) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === Tooltips === */
|
||||||
|
[data-tooltip] {
|
||||||
|
position: relative !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-tooltip]::after {
|
||||||
|
content: attr(data-tooltip) !important;
|
||||||
|
position: absolute !important;
|
||||||
|
bottom: 100% !important;
|
||||||
|
left: 50% !important;
|
||||||
|
transform: translateX(-50%) !important;
|
||||||
|
background: var(--tube-deep) !important;
|
||||||
|
border: 1px solid var(--p31-dim) !important;
|
||||||
|
color: var(--p31-mid) !important;
|
||||||
|
padding: 0.4rem 0.6rem !important;
|
||||||
|
font-size: 0.75rem !important;
|
||||||
|
white-space: nowrap !important;
|
||||||
|
border-radius: 3px !important;
|
||||||
|
opacity: 0 !important;
|
||||||
|
visibility: hidden !important;
|
||||||
|
transition: all 0.2s ease !important;
|
||||||
|
z-index: 1000 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-tooltip]:hover::after {
|
||||||
|
opacity: 1 !important;
|
||||||
|
visibility: visible !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === Footer === */
|
||||||
|
footer, .main-foot, #mainfooter {
|
||||||
|
background: var(--tube-deep) !important;
|
||||||
|
border-top: 1px solid var(--p31-ghost) !important;
|
||||||
|
padding: 0.5rem 1.5rem !important;
|
||||||
|
font-size: 0.75rem !important;
|
||||||
|
color: var(--p31-dim) !important;
|
||||||
|
text-align: center !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === Links === */
|
||||||
|
a, .a {
|
||||||
|
color: var(--p31-mid) !important;
|
||||||
|
text-decoration: none !important;
|
||||||
|
transition: all 0.2s ease !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover, .a:hover {
|
||||||
|
color: var(--p31-peak) !important;
|
||||||
|
text-shadow: var(--bloom-soft) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === Code/Pre === */
|
||||||
|
code, pre, .code {
|
||||||
|
background: var(--tube-black) !important;
|
||||||
|
border: 1px solid var(--p31-ghost) !important;
|
||||||
|
color: var(--p31-mid) !important;
|
||||||
|
font-family: inherit !important;
|
||||||
|
padding: 0.2rem 0.4rem !important;
|
||||||
|
border-radius: 3px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre, pre.code {
|
||||||
|
padding: 1rem !important;
|
||||||
|
overflow-x: auto !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === Modals/Dialogs === */
|
||||||
|
.modal, .dialog, .cbi-modal {
|
||||||
|
background: var(--tube-deep) !important;
|
||||||
|
border: 1px solid var(--p31-ghost) !important;
|
||||||
|
border-radius: 6px !important;
|
||||||
|
box-shadow: 0 0 30px rgba(0, 0, 0, 0.8), var(--bloom-box) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-overlay, .cbi-modal-overlay {
|
||||||
|
background: rgba(0, 0, 0, 0.85) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === Login Page === */
|
||||||
|
.login-container, #login, form[action*="login"],
|
||||||
|
body.login {
|
||||||
|
background: var(--tube-black) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-container, #login > div, .login-box {
|
||||||
|
max-width: 400px !important;
|
||||||
|
margin: 10vh auto !important;
|
||||||
|
padding: 2rem !important;
|
||||||
|
background: var(--tube-deep) !important;
|
||||||
|
border: 1px solid var(--p31-ghost) !important;
|
||||||
|
border-radius: 6px !important;
|
||||||
|
text-align: center !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-container h1, #login h1, .login-box h1 {
|
||||||
|
color: var(--p31-peak) !important;
|
||||||
|
text-shadow: var(--bloom-text) !important;
|
||||||
|
margin-bottom: 2rem !important;
|
||||||
|
letter-spacing: 3px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-container input, #login input, .login-box input[type="text"],
|
||||||
|
.login-box input[type="password"] {
|
||||||
|
width: 100% !important;
|
||||||
|
max-width: none !important;
|
||||||
|
margin-bottom: 1rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-container button, #login button, .login-box button,
|
||||||
|
.login-box input[type="submit"] {
|
||||||
|
width: 100% !important;
|
||||||
|
padding: 0.75rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === Dashboard Widgets === */
|
||||||
|
.widget, .dashboard-widget {
|
||||||
|
background: var(--tube-deep) !important;
|
||||||
|
border: 1px solid var(--p31-ghost) !important;
|
||||||
|
border-radius: 4px !important;
|
||||||
|
padding: 1rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.widget-header, .dashboard-widget-header {
|
||||||
|
color: var(--p31-peak) !important;
|
||||||
|
font-size: 0.8rem !important;
|
||||||
|
text-transform: uppercase !important;
|
||||||
|
letter-spacing: 1px !important;
|
||||||
|
margin-bottom: 0.75rem !important;
|
||||||
|
text-shadow: var(--bloom-soft) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.widget-value, .dashboard-widget-value {
|
||||||
|
font-size: 2rem !important;
|
||||||
|
color: var(--p31-peak) !important;
|
||||||
|
text-shadow: var(--bloom-text) !important;
|
||||||
|
font-weight: bold !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.widget-value.warning {
|
||||||
|
color: var(--p31-decay) !important;
|
||||||
|
text-shadow: 0 0 6px var(--p31-decay) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.widget-value.danger {
|
||||||
|
color: #ff6688 !important;
|
||||||
|
text-shadow: 0 0 6px #ff4466 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.widget-label {
|
||||||
|
font-size: 0.7rem !important;
|
||||||
|
color: var(--p31-dim) !important;
|
||||||
|
text-transform: uppercase !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === Dashboard Grid === */
|
||||||
|
.dashboard-grid {
|
||||||
|
display: grid !important;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)) !important;
|
||||||
|
gap: 1rem !important;
|
||||||
|
margin-bottom: 1.5rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === Topology Visualization === */
|
||||||
|
.topology-container {
|
||||||
|
background: var(--tube-black) !important;
|
||||||
|
border: 1px solid var(--p31-ghost) !important;
|
||||||
|
border-radius: 4px !important;
|
||||||
|
padding: 1rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topology-container svg {
|
||||||
|
width: 100% !important;
|
||||||
|
height: 400px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topology-node circle {
|
||||||
|
fill: var(--p31-dim) !important;
|
||||||
|
stroke: var(--p31-mid) !important;
|
||||||
|
stroke-width: 2 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topology-node.relay circle {
|
||||||
|
fill: var(--p31-decay-dim) !important;
|
||||||
|
stroke: var(--p31-decay) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topology-node.active circle {
|
||||||
|
fill: var(--p31-mid) !important;
|
||||||
|
stroke: var(--p31-peak) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topology-edge {
|
||||||
|
stroke: var(--p31-ghost) !important;
|
||||||
|
stroke-width: 1 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topology-edge.active {
|
||||||
|
stroke: var(--p31-dim) !important;
|
||||||
|
stroke-width: 2 !important;
|
||||||
|
animation: edgePulse 2s infinite !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes edgePulse {
|
||||||
|
0%, 100% { opacity: 0.5; }
|
||||||
|
50% { opacity: 1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === Status Indicators === */
|
||||||
|
.status-online, .online, .running {
|
||||||
|
color: var(--p31-peak) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-offline, .offline, .stopped {
|
||||||
|
color: #ff6688 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-warning, .warning {
|
||||||
|
color: var(--p31-decay) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Glowing dot indicators */
|
||||||
|
.status-dot {
|
||||||
|
display: inline-block !important;
|
||||||
|
width: 8px !important;
|
||||||
|
height: 8px !important;
|
||||||
|
border-radius: 50% !important;
|
||||||
|
margin-right: 0.5rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-dot.online {
|
||||||
|
background: var(--p31-peak) !important;
|
||||||
|
box-shadow: 0 0 6px var(--p31-peak) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-dot.offline {
|
||||||
|
background: #ff4466 !important;
|
||||||
|
box-shadow: 0 0 6px #ff4466 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-dot.warning {
|
||||||
|
background: var(--p31-decay) !important;
|
||||||
|
box-shadow: 0 0 6px var(--p31-decay) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === Scrollbars === */
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 8px !important;
|
||||||
|
height: 8px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-track {
|
||||||
|
background: var(--tube-black) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background: var(--p31-ghost) !important;
|
||||||
|
border-radius: 4px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: var(--p31-dim) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === Selection === */
|
||||||
|
::selection {
|
||||||
|
background: var(--p31-dim) !important;
|
||||||
|
color: var(--tube-black) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === Responsive === */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.main-left, #mainmenu, nav.main {
|
||||||
|
width: 100% !important;
|
||||||
|
min-height: auto !important;
|
||||||
|
border-right: none !important;
|
||||||
|
border-bottom: 1px solid var(--p31-ghost) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-right, #maincontent, main {
|
||||||
|
padding: 1rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-grid {
|
||||||
|
grid-template-columns: 1fr !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === Spinner/Loading === */
|
||||||
|
.spinning, .cbi-button-waiting {
|
||||||
|
animation: spin 1s linear infinite !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
from { transform: rotate(0deg); }
|
||||||
|
to { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === CRT Screen Curvature (subtle) === */
|
||||||
|
body {
|
||||||
|
border-radius: 8px !important;
|
||||||
|
box-shadow: inset 0 0 60px rgba(0, 0, 0, 0.5), inset 0 0 10px rgba(51, 255, 102, 0.1) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === LuCI Specific Overrides === */
|
||||||
|
.cbi-value-title {
|
||||||
|
color: var(--p31-dim) !important;
|
||||||
|
font-weight: normal !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cbi-value-description {
|
||||||
|
color: var(--p31-ghost) !important;
|
||||||
|
font-size: 0.8rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cbi-section-descr {
|
||||||
|
color: var(--p31-dim) !important;
|
||||||
|
font-size: 0.85rem !important;
|
||||||
|
margin-bottom: 1rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Interface badges */
|
||||||
|
.ifacebadge {
|
||||||
|
background: var(--tube-black) !important;
|
||||||
|
border: 1px solid var(--p31-ghost) !important;
|
||||||
|
color: var(--p31-mid) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Zone badges */
|
||||||
|
.zonebadge {
|
||||||
|
background: rgba(51, 255, 102, 0.1) !important;
|
||||||
|
border-color: var(--p31-dim) !important;
|
||||||
|
color: var(--p31-mid) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.zonebadge.zone-lan {
|
||||||
|
border-color: var(--p31-mid) !important;
|
||||||
|
background: rgba(51, 255, 102, 0.15) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.zonebadge.zone-wan {
|
||||||
|
border-color: var(--p31-decay-dim) !important;
|
||||||
|
background: rgba(255, 179, 71, 0.1) !important;
|
||||||
|
color: var(--p31-decay) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hide default theme backgrounds */
|
||||||
|
.main, .main-left, .main-right, .container,
|
||||||
|
#maincontent, #maincontainer, header, footer {
|
||||||
|
background-image: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Force dark background everywhere */
|
||||||
|
div, section, article, aside, main, nav, header, footer {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Override any white/light backgrounds */
|
||||||
|
.white, .bg-white, .bg-light {
|
||||||
|
background: var(--tube-deep) !important;
|
||||||
|
color: var(--p31-mid) !important;
|
||||||
|
}
|
||||||
@ -0,0 +1,433 @@
|
|||||||
|
/**
|
||||||
|
* SecuBox CRT P31 Reusable UI Components
|
||||||
|
* CyberMind — SecuBox — 2026
|
||||||
|
*
|
||||||
|
* Provides CRT-styled UI components for SecuBox dashboards
|
||||||
|
*/
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var CRTComponents = {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a status widget with glowing value
|
||||||
|
*/
|
||||||
|
createWidget: function(options) {
|
||||||
|
var defaults = {
|
||||||
|
title: 'Widget',
|
||||||
|
value: '0',
|
||||||
|
unit: '',
|
||||||
|
status: 'normal', // normal, warning, danger
|
||||||
|
icon: null
|
||||||
|
};
|
||||||
|
|
||||||
|
var opts = Object.assign({}, defaults, options);
|
||||||
|
var statusClass = opts.status === 'warning' ? 'warning' :
|
||||||
|
opts.status === 'danger' ? 'danger' : '';
|
||||||
|
|
||||||
|
var widget = document.createElement('div');
|
||||||
|
widget.className = 'crt-widget';
|
||||||
|
widget.innerHTML = [
|
||||||
|
'<div class="crt-widget-header">' + this.escapeHtml(opts.title) + '</div>',
|
||||||
|
'<div class="crt-widget-value ' + statusClass + '">' +
|
||||||
|
this.escapeHtml(opts.value) +
|
||||||
|
(opts.unit ? '<span class="unit">' + this.escapeHtml(opts.unit) + '</span>' : '') +
|
||||||
|
'</div>'
|
||||||
|
].join('');
|
||||||
|
|
||||||
|
return widget;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a status badge
|
||||||
|
*/
|
||||||
|
createBadge: function(text, type) {
|
||||||
|
type = type || 'info';
|
||||||
|
var badge = document.createElement('span');
|
||||||
|
badge.className = 'crt-badge crt-badge-' + type;
|
||||||
|
badge.textContent = text;
|
||||||
|
return badge;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a status dot indicator
|
||||||
|
*/
|
||||||
|
createStatusDot: function(status) {
|
||||||
|
var dot = document.createElement('span');
|
||||||
|
dot.className = 'crt-status-dot ' + (status || 'offline');
|
||||||
|
return dot;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a progress bar
|
||||||
|
*/
|
||||||
|
createProgressBar: function(value, max, label) {
|
||||||
|
max = max || 100;
|
||||||
|
var percent = Math.min(100, Math.max(0, (value / max) * 100));
|
||||||
|
|
||||||
|
var container = document.createElement('div');
|
||||||
|
container.className = 'crt-progress';
|
||||||
|
|
||||||
|
var bar = document.createElement('div');
|
||||||
|
bar.className = 'crt-progress-bar';
|
||||||
|
bar.style.width = percent + '%';
|
||||||
|
|
||||||
|
if (label) {
|
||||||
|
var labelEl = document.createElement('span');
|
||||||
|
labelEl.className = 'crt-progress-label';
|
||||||
|
labelEl.textContent = label;
|
||||||
|
container.appendChild(labelEl);
|
||||||
|
}
|
||||||
|
|
||||||
|
container.appendChild(bar);
|
||||||
|
return container;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a terminal-style log viewer
|
||||||
|
*/
|
||||||
|
createLogViewer: function(lines, maxLines) {
|
||||||
|
maxLines = maxLines || 20;
|
||||||
|
lines = lines || [];
|
||||||
|
|
||||||
|
var container = document.createElement('div');
|
||||||
|
container.className = 'crt-log-viewer';
|
||||||
|
|
||||||
|
var displayLines = lines.slice(-maxLines);
|
||||||
|
displayLines.forEach(function(line) {
|
||||||
|
var lineEl = document.createElement('div');
|
||||||
|
lineEl.className = 'crt-log-line';
|
||||||
|
lineEl.textContent = line;
|
||||||
|
container.appendChild(lineEl);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Auto-scroll to bottom
|
||||||
|
container.scrollTop = container.scrollHeight;
|
||||||
|
|
||||||
|
return container;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a topology node for SVG visualization
|
||||||
|
*/
|
||||||
|
createTopologyNode: function(svg, x, y, label, status, nodeType) {
|
||||||
|
var ns = 'http://www.w3.org/2000/svg';
|
||||||
|
var group = document.createElementNS(ns, 'g');
|
||||||
|
group.setAttribute('class', 'topology-node ' + (status || '') + ' ' + (nodeType || ''));
|
||||||
|
group.setAttribute('transform', 'translate(' + x + ',' + y + ')');
|
||||||
|
|
||||||
|
var circle = document.createElementNS(ns, 'circle');
|
||||||
|
circle.setAttribute('r', '20');
|
||||||
|
circle.setAttribute('cx', '0');
|
||||||
|
circle.setAttribute('cy', '0');
|
||||||
|
|
||||||
|
var text = document.createElementNS(ns, 'text');
|
||||||
|
text.setAttribute('y', '35');
|
||||||
|
text.setAttribute('text-anchor', 'middle');
|
||||||
|
text.setAttribute('class', 'topology-label');
|
||||||
|
text.textContent = label || '';
|
||||||
|
|
||||||
|
group.appendChild(circle);
|
||||||
|
group.appendChild(text);
|
||||||
|
|
||||||
|
return group;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a topology edge for SVG visualization
|
||||||
|
*/
|
||||||
|
createTopologyEdge: function(svg, x1, y1, x2, y2, active) {
|
||||||
|
var ns = 'http://www.w3.org/2000/svg';
|
||||||
|
var line = document.createElementNS(ns, 'line');
|
||||||
|
line.setAttribute('class', 'topology-edge ' + (active ? 'active' : ''));
|
||||||
|
line.setAttribute('x1', x1);
|
||||||
|
line.setAttribute('y1', y1);
|
||||||
|
line.setAttribute('x2', x2);
|
||||||
|
line.setAttribute('y2', y2);
|
||||||
|
return line;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an alert/notification box
|
||||||
|
*/
|
||||||
|
createAlert: function(message, type) {
|
||||||
|
type = type || 'info';
|
||||||
|
var alert = document.createElement('div');
|
||||||
|
alert.className = 'crt-alert crt-alert-' + type;
|
||||||
|
alert.textContent = message;
|
||||||
|
|
||||||
|
var close = document.createElement('button');
|
||||||
|
close.className = 'crt-alert-close';
|
||||||
|
close.innerHTML = '×';
|
||||||
|
close.onclick = function() {
|
||||||
|
alert.remove();
|
||||||
|
};
|
||||||
|
|
||||||
|
alert.appendChild(close);
|
||||||
|
return alert;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show a toast notification
|
||||||
|
*/
|
||||||
|
toast: function(message, type, duration) {
|
||||||
|
type = type || 'info';
|
||||||
|
duration = duration || 3000;
|
||||||
|
|
||||||
|
var toast = document.createElement('div');
|
||||||
|
toast.className = 'crt-toast crt-toast-' + type;
|
||||||
|
toast.textContent = message;
|
||||||
|
|
||||||
|
// Position in top-right
|
||||||
|
toast.style.cssText = [
|
||||||
|
'position: fixed',
|
||||||
|
'top: 1rem',
|
||||||
|
'right: 1rem',
|
||||||
|
'padding: 0.75rem 1.5rem',
|
||||||
|
'background: #080d05',
|
||||||
|
'border: 1px solid',
|
||||||
|
'border-radius: 4px',
|
||||||
|
'z-index: 10001',
|
||||||
|
'animation: toastIn 0.3s ease'
|
||||||
|
].join(';');
|
||||||
|
|
||||||
|
if (type === 'success') {
|
||||||
|
toast.style.borderColor = '#22cc44';
|
||||||
|
toast.style.color = '#33ff66';
|
||||||
|
} else if (type === 'error') {
|
||||||
|
toast.style.borderColor = '#ff4466';
|
||||||
|
toast.style.color = '#ff6688';
|
||||||
|
} else if (type === 'warning') {
|
||||||
|
toast.style.borderColor = '#cc7722';
|
||||||
|
toast.style.color = '#ffb347';
|
||||||
|
} else {
|
||||||
|
toast.style.borderColor = '#0f8822';
|
||||||
|
toast.style.color = '#22cc44';
|
||||||
|
}
|
||||||
|
|
||||||
|
document.body.appendChild(toast);
|
||||||
|
|
||||||
|
setTimeout(function() {
|
||||||
|
toast.style.animation = 'toastOut 0.3s ease';
|
||||||
|
setTimeout(function() {
|
||||||
|
toast.remove();
|
||||||
|
}, 300);
|
||||||
|
}, duration);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a data table with CRT styling
|
||||||
|
*/
|
||||||
|
createTable: function(headers, rows) {
|
||||||
|
var table = document.createElement('table');
|
||||||
|
table.className = 'crt-table';
|
||||||
|
|
||||||
|
// Header
|
||||||
|
var thead = document.createElement('thead');
|
||||||
|
var headerRow = document.createElement('tr');
|
||||||
|
headers.forEach(function(header) {
|
||||||
|
var th = document.createElement('th');
|
||||||
|
th.textContent = header;
|
||||||
|
headerRow.appendChild(th);
|
||||||
|
});
|
||||||
|
thead.appendChild(headerRow);
|
||||||
|
table.appendChild(thead);
|
||||||
|
|
||||||
|
// Body
|
||||||
|
var tbody = document.createElement('tbody');
|
||||||
|
rows.forEach(function(row) {
|
||||||
|
var tr = document.createElement('tr');
|
||||||
|
row.forEach(function(cell) {
|
||||||
|
var td = document.createElement('td');
|
||||||
|
if (typeof cell === 'object' && cell.html) {
|
||||||
|
td.innerHTML = cell.html;
|
||||||
|
} else {
|
||||||
|
td.textContent = cell;
|
||||||
|
}
|
||||||
|
tr.appendChild(td);
|
||||||
|
});
|
||||||
|
tbody.appendChild(tr);
|
||||||
|
});
|
||||||
|
table.appendChild(tbody);
|
||||||
|
|
||||||
|
return table;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Escape HTML entities
|
||||||
|
*/
|
||||||
|
escapeHtml: function(text) {
|
||||||
|
var div = document.createElement('div');
|
||||||
|
div.textContent = text;
|
||||||
|
return div.innerHTML;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add component styles
|
||||||
|
var styles = document.createElement('style');
|
||||||
|
styles.textContent = [
|
||||||
|
'.crt-widget {',
|
||||||
|
' background: #080d05;',
|
||||||
|
' border: 1px solid #052210;',
|
||||||
|
' border-radius: 4px;',
|
||||||
|
' padding: 1rem;',
|
||||||
|
' text-align: center;',
|
||||||
|
'}',
|
||||||
|
'.crt-widget-header {',
|
||||||
|
' color: #0f8822;',
|
||||||
|
' font-size: 0.75rem;',
|
||||||
|
' text-transform: uppercase;',
|
||||||
|
' letter-spacing: 1px;',
|
||||||
|
' margin-bottom: 0.5rem;',
|
||||||
|
'}',
|
||||||
|
'.crt-widget-value {',
|
||||||
|
' color: #33ff66;',
|
||||||
|
' font-size: 2rem;',
|
||||||
|
' font-weight: bold;',
|
||||||
|
' text-shadow: 0 0 6px #33ff66;',
|
||||||
|
'}',
|
||||||
|
'.crt-widget-value.warning {',
|
||||||
|
' color: #ffb347;',
|
||||||
|
' text-shadow: 0 0 6px #ffb347;',
|
||||||
|
'}',
|
||||||
|
'.crt-widget-value.danger {',
|
||||||
|
' color: #ff6688;',
|
||||||
|
' text-shadow: 0 0 6px #ff4466;',
|
||||||
|
'}',
|
||||||
|
'.crt-widget-value .unit {',
|
||||||
|
' font-size: 0.9rem;',
|
||||||
|
' margin-left: 0.25rem;',
|
||||||
|
'}',
|
||||||
|
'',
|
||||||
|
'.crt-badge {',
|
||||||
|
' display: inline-block;',
|
||||||
|
' padding: 0.15rem 0.5rem;',
|
||||||
|
' font-size: 0.7rem;',
|
||||||
|
' text-transform: uppercase;',
|
||||||
|
' letter-spacing: 1px;',
|
||||||
|
' border-radius: 2px;',
|
||||||
|
' border: 1px solid;',
|
||||||
|
'}',
|
||||||
|
'.crt-badge-success { border-color: #22cc44; color: #33ff66; background: rgba(51,255,102,0.1); }',
|
||||||
|
'.crt-badge-warning { border-color: #cc7722; color: #ffb347; background: rgba(255,179,71,0.1); }',
|
||||||
|
'.crt-badge-danger { border-color: #ff4466; color: #ff6688; background: rgba(255,68,102,0.1); }',
|
||||||
|
'.crt-badge-info { border-color: #0f8822; color: #22cc44; background: rgba(51,255,102,0.05); }',
|
||||||
|
'',
|
||||||
|
'.crt-status-dot {',
|
||||||
|
' display: inline-block;',
|
||||||
|
' width: 8px;',
|
||||||
|
' height: 8px;',
|
||||||
|
' border-radius: 50%;',
|
||||||
|
' margin-right: 0.5rem;',
|
||||||
|
'}',
|
||||||
|
'.crt-status-dot.online { background: #33ff66; box-shadow: 0 0 6px #33ff66; }',
|
||||||
|
'.crt-status-dot.offline { background: #ff4466; box-shadow: 0 0 6px #ff4466; }',
|
||||||
|
'.crt-status-dot.warning { background: #ffb347; box-shadow: 0 0 6px #ffb347; }',
|
||||||
|
'',
|
||||||
|
'.crt-progress {',
|
||||||
|
' background: #050803;',
|
||||||
|
' border: 1px solid #052210;',
|
||||||
|
' border-radius: 2px;',
|
||||||
|
' height: 20px;',
|
||||||
|
' position: relative;',
|
||||||
|
' overflow: hidden;',
|
||||||
|
'}',
|
||||||
|
'.crt-progress-bar {',
|
||||||
|
' background: linear-gradient(90deg, #0f8822, #22cc44);',
|
||||||
|
' height: 100%;',
|
||||||
|
' box-shadow: 0 0 10px #22cc44;',
|
||||||
|
' transition: width 0.3s ease;',
|
||||||
|
'}',
|
||||||
|
'.crt-progress-label {',
|
||||||
|
' position: absolute;',
|
||||||
|
' top: 50%;',
|
||||||
|
' left: 50%;',
|
||||||
|
' transform: translate(-50%, -50%);',
|
||||||
|
' font-size: 0.7rem;',
|
||||||
|
' color: #22cc44;',
|
||||||
|
'}',
|
||||||
|
'',
|
||||||
|
'.crt-log-viewer {',
|
||||||
|
' background: #050803;',
|
||||||
|
' border: 1px solid #052210;',
|
||||||
|
' border-radius: 4px;',
|
||||||
|
' padding: 0.5rem;',
|
||||||
|
' font-size: 0.8rem;',
|
||||||
|
' max-height: 300px;',
|
||||||
|
' overflow-y: auto;',
|
||||||
|
'}',
|
||||||
|
'.crt-log-line {',
|
||||||
|
' color: #22cc44;',
|
||||||
|
' padding: 0.1rem 0;',
|
||||||
|
' border-bottom: 1px solid #052210;',
|
||||||
|
'}',
|
||||||
|
'.crt-log-line:last-child { border-bottom: none; }',
|
||||||
|
'',
|
||||||
|
'.crt-alert {',
|
||||||
|
' padding: 1rem;',
|
||||||
|
' border-radius: 4px;',
|
||||||
|
' margin-bottom: 1rem;',
|
||||||
|
' border: 1px solid;',
|
||||||
|
' position: relative;',
|
||||||
|
'}',
|
||||||
|
'.crt-alert-success { background: rgba(51,255,102,0.1); border-color: #0f8822; color: #22cc44; }',
|
||||||
|
'.crt-alert-warning { background: rgba(255,179,71,0.1); border-color: #cc7722; color: #ffb347; }',
|
||||||
|
'.crt-alert-error { background: rgba(255,68,102,0.1); border-color: #ff4466; color: #ff6688; }',
|
||||||
|
'.crt-alert-info { background: rgba(51,255,102,0.05); border-color: #052210; color: #22cc44; }',
|
||||||
|
'.crt-alert-close {',
|
||||||
|
' position: absolute;',
|
||||||
|
' top: 0.5rem;',
|
||||||
|
' right: 0.5rem;',
|
||||||
|
' background: none;',
|
||||||
|
' border: none;',
|
||||||
|
' color: inherit;',
|
||||||
|
' cursor: pointer;',
|
||||||
|
' font-size: 1.2rem;',
|
||||||
|
' opacity: 0.6;',
|
||||||
|
'}',
|
||||||
|
'.crt-alert-close:hover { opacity: 1; }',
|
||||||
|
'',
|
||||||
|
'.crt-table {',
|
||||||
|
' width: 100%;',
|
||||||
|
' border-collapse: collapse;',
|
||||||
|
' font-size: 0.85rem;',
|
||||||
|
'}',
|
||||||
|
'.crt-table th {',
|
||||||
|
' color: #0f8822;',
|
||||||
|
' font-weight: normal;',
|
||||||
|
' text-transform: uppercase;',
|
||||||
|
' font-size: 0.75rem;',
|
||||||
|
' letter-spacing: 1px;',
|
||||||
|
' padding: 0.75rem;',
|
||||||
|
' text-align: left;',
|
||||||
|
' border-bottom: 1px solid #052210;',
|
||||||
|
' background: #050803;',
|
||||||
|
'}',
|
||||||
|
'.crt-table td {',
|
||||||
|
' padding: 0.75rem;',
|
||||||
|
' border-bottom: 1px solid #052210;',
|
||||||
|
' color: #22cc44;',
|
||||||
|
'}',
|
||||||
|
'.crt-table tr:hover td { background: rgba(51,255,102,0.03); }',
|
||||||
|
'',
|
||||||
|
'.topology-label {',
|
||||||
|
' fill: #22cc44;',
|
||||||
|
' font-size: 0.75rem;',
|
||||||
|
'}',
|
||||||
|
'',
|
||||||
|
'@keyframes toastIn {',
|
||||||
|
' from { opacity: 0; transform: translateX(100%); }',
|
||||||
|
' to { opacity: 1; transform: translateX(0); }',
|
||||||
|
'}',
|
||||||
|
'@keyframes toastOut {',
|
||||||
|
' from { opacity: 1; transform: translateX(0); }',
|
||||||
|
' to { opacity: 0; transform: translateX(100%); }',
|
||||||
|
'}'
|
||||||
|
].join('\n');
|
||||||
|
document.head.appendChild(styles);
|
||||||
|
|
||||||
|
// Export to global scope
|
||||||
|
window.CRTComponents = CRTComponents;
|
||||||
|
|
||||||
|
})();
|
||||||
@ -0,0 +1,264 @@
|
|||||||
|
/**
|
||||||
|
* SecuBox CRT P31 Effects Engine
|
||||||
|
* CyberMind — SecuBox — 2026
|
||||||
|
*
|
||||||
|
* Provides CRT terminal effects for the SecuBox LuCI theme
|
||||||
|
*/
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var SecuBoxCRT = {
|
||||||
|
config: {
|
||||||
|
enableScanlines: true,
|
||||||
|
enableFlicker: false, // Disabled by default (can be distracting)
|
||||||
|
enableGlow: true,
|
||||||
|
enableBootSequence: true,
|
||||||
|
bootSequenceOnce: true
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add scanlines overlay to the page
|
||||||
|
*/
|
||||||
|
addScanlines: function() {
|
||||||
|
if (!this.config.enableScanlines) return;
|
||||||
|
if (document.querySelector('.crt-scanlines')) return;
|
||||||
|
|
||||||
|
var scanlines = document.createElement('div');
|
||||||
|
scanlines.className = 'crt-scanlines';
|
||||||
|
scanlines.style.cssText = [
|
||||||
|
'position: fixed',
|
||||||
|
'top: 0',
|
||||||
|
'left: 0',
|
||||||
|
'width: 100%',
|
||||||
|
'height: 100%',
|
||||||
|
'pointer-events: none',
|
||||||
|
'background: repeating-linear-gradient(0deg, rgba(0,0,0,0.15), rgba(0,0,0,0.15) 1px, transparent 1px, transparent 2px)',
|
||||||
|
'z-index: 9999'
|
||||||
|
].join(';');
|
||||||
|
document.body.appendChild(scanlines);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable subtle screen flicker effect
|
||||||
|
*/
|
||||||
|
enableFlicker: function() {
|
||||||
|
if (!this.config.enableFlicker) return;
|
||||||
|
document.body.classList.add('crt-flicker');
|
||||||
|
|
||||||
|
// Add flicker keyframes if not present
|
||||||
|
if (!document.querySelector('#crt-flicker-style')) {
|
||||||
|
var style = document.createElement('style');
|
||||||
|
style.id = 'crt-flicker-style';
|
||||||
|
style.textContent = [
|
||||||
|
'@keyframes crtFlicker {',
|
||||||
|
' 0%, 100% { opacity: 1; }',
|
||||||
|
' 92% { opacity: 0.98; }',
|
||||||
|
' 93% { opacity: 1; }',
|
||||||
|
' 94% { opacity: 0.97; }',
|
||||||
|
'}',
|
||||||
|
'.crt-flicker { animation: crtFlicker 0.15s infinite; }'
|
||||||
|
].join('\n');
|
||||||
|
document.head.appendChild(style);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add phosphor glow effect on hover for interactive elements
|
||||||
|
*/
|
||||||
|
addPhosphorGlow: function() {
|
||||||
|
if (!this.config.enableGlow) return;
|
||||||
|
|
||||||
|
var glowElements = document.querySelectorAll('button, .cbi-button, a, input[type="submit"]');
|
||||||
|
var bloomSoft = '0 0 6px #33ff66, 0 0 14px rgba(51,255,102,0.5)';
|
||||||
|
|
||||||
|
glowElements.forEach(function(el) {
|
||||||
|
el.addEventListener('mouseenter', function() {
|
||||||
|
this.style.textShadow = bloomSoft;
|
||||||
|
});
|
||||||
|
el.addEventListener('mouseleave', function() {
|
||||||
|
this.style.textShadow = '';
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add terminal cursor effect to text inputs
|
||||||
|
*/
|
||||||
|
addTerminalCursor: function() {
|
||||||
|
var inputs = document.querySelectorAll('input[type="text"], input[type="password"], input[type="email"], input[type="url"], textarea');
|
||||||
|
inputs.forEach(function(input) {
|
||||||
|
input.addEventListener('focus', function() {
|
||||||
|
this.style.caretColor = '#33ff66';
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Typewriter effect for an element
|
||||||
|
*/
|
||||||
|
typewriterEffect: function(element, speed) {
|
||||||
|
speed = speed || 50;
|
||||||
|
var text = element.textContent;
|
||||||
|
element.textContent = '';
|
||||||
|
element.style.visibility = 'visible';
|
||||||
|
|
||||||
|
var i = 0;
|
||||||
|
function type() {
|
||||||
|
if (i < text.length) {
|
||||||
|
element.textContent += text.charAt(i);
|
||||||
|
i++;
|
||||||
|
setTimeout(type, speed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
type();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Boot sequence animation
|
||||||
|
*/
|
||||||
|
bootSequence: function() {
|
||||||
|
if (!this.config.enableBootSequence) return;
|
||||||
|
if (this.config.bootSequenceOnce && sessionStorage.getItem('crt-booted')) return;
|
||||||
|
|
||||||
|
var bootText = [
|
||||||
|
'[ SECUBOX MESH DAEMON ]',
|
||||||
|
'Initializing P31 phosphor display...',
|
||||||
|
'Loading security modules...',
|
||||||
|
'mDNS discovery: ACTIVE',
|
||||||
|
'Mesh topology: ONLINE',
|
||||||
|
'System ready.'
|
||||||
|
];
|
||||||
|
|
||||||
|
var container = document.createElement('div');
|
||||||
|
container.id = 'crt-boot-sequence';
|
||||||
|
container.style.cssText = [
|
||||||
|
'position: fixed',
|
||||||
|
'top: 0',
|
||||||
|
'left: 0',
|
||||||
|
'width: 100%',
|
||||||
|
'height: 100%',
|
||||||
|
'background: #050803',
|
||||||
|
'z-index: 10000',
|
||||||
|
'display: flex',
|
||||||
|
'flex-direction: column',
|
||||||
|
'justify-content: center',
|
||||||
|
'align-items: center',
|
||||||
|
'font-family: "Courier Prime", "Courier New", monospace',
|
||||||
|
'color: #22cc44'
|
||||||
|
].join(';');
|
||||||
|
|
||||||
|
document.body.appendChild(container);
|
||||||
|
|
||||||
|
var lineIndex = 0;
|
||||||
|
function showLine() {
|
||||||
|
if (lineIndex < bootText.length) {
|
||||||
|
var line = document.createElement('div');
|
||||||
|
var isHeader = lineIndex === 0;
|
||||||
|
line.style.cssText = [
|
||||||
|
'opacity: 0',
|
||||||
|
'margin: 0.3rem 0',
|
||||||
|
isHeader ? 'font-size: 1.2rem; color: #33ff66; text-shadow: 0 0 6px #33ff66;' : ''
|
||||||
|
].join(';');
|
||||||
|
line.textContent = bootText[lineIndex];
|
||||||
|
container.appendChild(line);
|
||||||
|
|
||||||
|
setTimeout(function() {
|
||||||
|
line.style.transition = 'opacity 0.3s';
|
||||||
|
line.style.opacity = '1';
|
||||||
|
}, 50);
|
||||||
|
|
||||||
|
lineIndex++;
|
||||||
|
setTimeout(showLine, 300);
|
||||||
|
} else {
|
||||||
|
setTimeout(function() {
|
||||||
|
container.style.transition = 'opacity 0.5s';
|
||||||
|
container.style.opacity = '0';
|
||||||
|
setTimeout(function() {
|
||||||
|
container.remove();
|
||||||
|
}, 500);
|
||||||
|
}, 800);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
showLine();
|
||||||
|
sessionStorage.setItem('crt-booted', 'true');
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply status dot glow effects
|
||||||
|
*/
|
||||||
|
applyStatusGlow: function() {
|
||||||
|
// Find status indicators and add appropriate glow
|
||||||
|
var onlineElements = document.querySelectorAll('.online, .running, .status-online');
|
||||||
|
var offlineElements = document.querySelectorAll('.offline, .stopped, .status-offline');
|
||||||
|
var warningElements = document.querySelectorAll('.warning, .status-warning');
|
||||||
|
|
||||||
|
onlineElements.forEach(function(el) {
|
||||||
|
el.style.textShadow = '0 0 6px #33ff66';
|
||||||
|
});
|
||||||
|
|
||||||
|
offlineElements.forEach(function(el) {
|
||||||
|
el.style.textShadow = '0 0 6px #ff4466';
|
||||||
|
});
|
||||||
|
|
||||||
|
warningElements.forEach(function(el) {
|
||||||
|
el.style.textShadow = '0 0 6px #ffb347';
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize all CRT effects
|
||||||
|
*/
|
||||||
|
init: function() {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
this.addScanlines();
|
||||||
|
this.enableFlicker();
|
||||||
|
this.addPhosphorGlow();
|
||||||
|
this.addTerminalCursor();
|
||||||
|
this.applyStatusGlow();
|
||||||
|
|
||||||
|
// Boot sequence on first visit
|
||||||
|
if (this.config.enableBootSequence) {
|
||||||
|
this.bootSequence();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-apply effects on dynamic content changes
|
||||||
|
var observer = new MutationObserver(function(mutations) {
|
||||||
|
self.addPhosphorGlow();
|
||||||
|
self.addTerminalCursor();
|
||||||
|
self.applyStatusGlow();
|
||||||
|
});
|
||||||
|
|
||||||
|
observer.observe(document.body, {
|
||||||
|
childList: true,
|
||||||
|
subtree: true
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configure CRT effects
|
||||||
|
*/
|
||||||
|
configure: function(options) {
|
||||||
|
for (var key in options) {
|
||||||
|
if (this.config.hasOwnProperty(key)) {
|
||||||
|
this.config[key] = options[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Initialize on DOM ready
|
||||||
|
if (document.readyState === 'loading') {
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
SecuBoxCRT.init();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
SecuBoxCRT.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Export to global scope
|
||||||
|
window.SecuBoxCRT = SecuBoxCRT;
|
||||||
|
|
||||||
|
})();
|
||||||
@ -0,0 +1,37 @@
|
|||||||
|
<%#
|
||||||
|
SecuBox CRT P31 Theme - Footer Template
|
||||||
|
CyberMind — SecuBox — 2026
|
||||||
|
-%>
|
||||||
|
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<footer class="main-foot" id="mainfooter">
|
||||||
|
<span>SecuBox Mesh v<%=luci.version.distversion%></span>
|
||||||
|
<span class="separator">|</span>
|
||||||
|
<span>Powered by <a href="https://openwrt.org" target="_blank">OpenWrt</a></span>
|
||||||
|
<span class="separator">|</span>
|
||||||
|
<span>© 2026 CyberMind</span>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<%
|
||||||
|
local webroot = luci.http.getenv("DOCUMENT_ROOT") or ""
|
||||||
|
local resource = luci.dispatcher.build_url("admin/translations", luci.i18n.context.lang)
|
||||||
|
-%>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
(function() {
|
||||||
|
// Initialize SecuBox CRT effects
|
||||||
|
if (window.SecuBoxCRT) {
|
||||||
|
window.SecuBoxCRT.configure({
|
||||||
|
enableScanlines: true,
|
||||||
|
enableFlicker: false,
|
||||||
|
enableGlow: true,
|
||||||
|
enableBootSequence: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@ -0,0 +1,106 @@
|
|||||||
|
<%#
|
||||||
|
SecuBox CRT P31 Theme - Header Template
|
||||||
|
CyberMind — SecuBox — 2026
|
||||||
|
-%>
|
||||||
|
|
||||||
|
<%
|
||||||
|
local sys = require "luci.sys"
|
||||||
|
local http = require "luci.http"
|
||||||
|
local disp = require "luci.dispatcher"
|
||||||
|
local util = require "luci.util"
|
||||||
|
local fs = require "nixio.fs"
|
||||||
|
|
||||||
|
local request = disp.context.path
|
||||||
|
local request2 = disp.context.request
|
||||||
|
local category = request[1]
|
||||||
|
local cattree = category and disp.node(category)
|
||||||
|
|
||||||
|
local node = token and disp.node(unpack(request)) or nil
|
||||||
|
local title = node and node.title or "SecuBox"
|
||||||
|
|
||||||
|
local hostname = fs.readfile("/proc/sys/kernel/hostname") or ""
|
||||||
|
hostname = util.trim(hostname)
|
||||||
|
-%>
|
||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="<%=luci.i18n.context.lang%>">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<meta name="robots" content="noindex, nofollow">
|
||||||
|
<title><%=pcdata(hostname)%> - <%=striptags(title)%> - SecuBox</title>
|
||||||
|
<link rel="stylesheet" href="<%=media%>/cascade.css?v=<%=luci.version.revision%>">
|
||||||
|
<script src="<%=media%>/crt-engine.js?v=<%=luci.version.revision%>"></script>
|
||||||
|
<% if node and node.css then %>
|
||||||
|
<link rel="stylesheet" href="<%=resource%>/<%=node.css%>">
|
||||||
|
<% end -%>
|
||||||
|
<% if css then %>
|
||||||
|
<style title="text/css"><%= css %></style>
|
||||||
|
<% end -%>
|
||||||
|
</head>
|
||||||
|
<body class="lang_<%=luci.i18n.context.lang%>">
|
||||||
|
<header class="main-head">
|
||||||
|
<div class="brand">SECUBOX</div>
|
||||||
|
<div class="status">
|
||||||
|
<span class="hostname"><%=pcdata(hostname)%></span>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div id="maincontainer" class="main-container">
|
||||||
|
<nav class="main-left" id="mainmenu">
|
||||||
|
<ul class="mainmenu">
|
||||||
|
<%
|
||||||
|
local function submenu(prefix, node, level)
|
||||||
|
local childs = disp.node_childs(node)
|
||||||
|
if #childs > 0 then
|
||||||
|
for i, r in ipairs(childs) do
|
||||||
|
local nnode = node.nodes[r]
|
||||||
|
local href = controller .. prefix .. r
|
||||||
|
local title = pcdata(striptags(nnode.title))
|
||||||
|
local active = (request[level] == r)
|
||||||
|
|
||||||
|
if nnode.hidden then
|
||||||
|
-- skip hidden nodes
|
||||||
|
else
|
||||||
|
%>
|
||||||
|
<li class="<%=active and "active " or ""%><%=nnode.nodes and "slide" or ""%>">
|
||||||
|
<a href="<%=href%>"><%=title%></a>
|
||||||
|
<%
|
||||||
|
if active and nnode.nodes then
|
||||||
|
submenu(prefix .. r .. "/", nnode, level + 1)
|
||||||
|
end
|
||||||
|
%>
|
||||||
|
</li>
|
||||||
|
<%
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for i, r in ipairs(disp.node_childs(cattree)) do
|
||||||
|
local nnode = cattree.nodes[r]
|
||||||
|
local href = controller .. "/" .. category .. "/" .. r
|
||||||
|
local title = pcdata(striptags(nnode.title))
|
||||||
|
local active = (request[2] == r)
|
||||||
|
|
||||||
|
if not nnode.hidden then
|
||||||
|
%>
|
||||||
|
<li class="<%=active and "active " or ""%><%=nnode.nodes and "slide" or ""%>">
|
||||||
|
<a href="<%=href%>"><%=title%></a>
|
||||||
|
<%
|
||||||
|
if active and nnode.nodes then
|
||||||
|
submenu("/" .. category .. "/" .. r .. "/", nnode, 3)
|
||||||
|
end
|
||||||
|
%>
|
||||||
|
</li>
|
||||||
|
<%
|
||||||
|
end
|
||||||
|
end
|
||||||
|
%>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<main class="main-right" id="maincontent">
|
||||||
|
<% if title then %>
|
||||||
|
<h2><%=pcdata(striptags(title))%></h2>
|
||||||
|
<% end %>
|
||||||
@ -0,0 +1,215 @@
|
|||||||
|
<%#
|
||||||
|
SecuBox CRT P31 Theme - Login Page Template
|
||||||
|
CyberMind — SecuBox — 2026
|
||||||
|
-%>
|
||||||
|
|
||||||
|
<%
|
||||||
|
local sys = require "luci.sys"
|
||||||
|
local fs = require "nixio.fs"
|
||||||
|
local util = require "luci.util"
|
||||||
|
|
||||||
|
local hostname = fs.readfile("/proc/sys/kernel/hostname") or "SecuBox"
|
||||||
|
hostname = util.trim(hostname)
|
||||||
|
-%>
|
||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="<%=luci.i18n.context.lang%>">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<meta name="robots" content="noindex, nofollow">
|
||||||
|
<title><%=pcdata(hostname)%> - Login - SecuBox</title>
|
||||||
|
<link rel="stylesheet" href="<%=media%>/cascade.css?v=<%=luci.version.revision%>">
|
||||||
|
<script src="<%=media%>/crt-engine.js?v=<%=luci.version.revision%>"></script>
|
||||||
|
<style>
|
||||||
|
body.login {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-height: 100vh;
|
||||||
|
margin: 0;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-container {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 400px;
|
||||||
|
background: #080d05;
|
||||||
|
border: 1px solid #052210;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 2rem;
|
||||||
|
box-shadow: 0 0 30px rgba(0, 0, 0, 0.8), 0 0 8px rgba(51,255,102,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-logo {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-logo h1 {
|
||||||
|
color: #33ff66;
|
||||||
|
font-size: 2rem;
|
||||||
|
letter-spacing: 4px;
|
||||||
|
text-shadow: 0 0 6px #33ff66, 0 0 14px rgba(51,255,102,0.5);
|
||||||
|
margin: 0;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-logo h1::before {
|
||||||
|
content: '[ ';
|
||||||
|
color: #0f8822;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-logo h1::after {
|
||||||
|
content: ' ]';
|
||||||
|
color: #0f8822;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-logo .tagline {
|
||||||
|
color: #0f8822;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-hostname {
|
||||||
|
color: #22cc44;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
padding: 0.5rem;
|
||||||
|
border: 1px solid #052210;
|
||||||
|
border-radius: 3px;
|
||||||
|
background: #050803;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-form label {
|
||||||
|
display: block;
|
||||||
|
color: #0f8822;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
margin-bottom: 0.3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-form input[type="text"],
|
||||||
|
.login-form input[type="password"] {
|
||||||
|
width: 100%;
|
||||||
|
background: #050803;
|
||||||
|
border: 1px solid #052210;
|
||||||
|
color: #22cc44;
|
||||||
|
padding: 0.75rem;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: 1rem;
|
||||||
|
border-radius: 3px;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-form input:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #22cc44;
|
||||||
|
box-shadow: 0 0 8px rgba(51,255,102,0.3), inset 0 0 4px rgba(51,255,102,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-form button {
|
||||||
|
width: 100%;
|
||||||
|
background: transparent;
|
||||||
|
border: 1px solid #22cc44;
|
||||||
|
color: #33ff66;
|
||||||
|
padding: 0.75rem;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 3px;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-form button:hover {
|
||||||
|
background: #22cc44;
|
||||||
|
color: #050803;
|
||||||
|
text-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-error {
|
||||||
|
background: rgba(255, 68, 102, 0.1);
|
||||||
|
border: 1px solid #ff4466;
|
||||||
|
color: #ff6688;
|
||||||
|
padding: 0.75rem;
|
||||||
|
border-radius: 3px;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-footer {
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 2rem;
|
||||||
|
padding-top: 1rem;
|
||||||
|
border-top: 1px solid #052210;
|
||||||
|
color: #052210;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-footer a {
|
||||||
|
color: #0f8822;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body class="login">
|
||||||
|
|
||||||
|
<div class="login-container">
|
||||||
|
<div class="login-logo">
|
||||||
|
<h1>SECUBOX</h1>
|
||||||
|
<div class="tagline">Mesh Security Appliance</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="login-hostname">
|
||||||
|
<%=pcdata(hostname)%>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<% if fuser then %>
|
||||||
|
<div class="login-error">
|
||||||
|
Invalid username or password
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<form class="login-form" method="post" action="<%=pcdata(duser and url("admin/uci/changes") or REQUEST_URI)%>">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="luci_username"><%:Username%></label>
|
||||||
|
<input type="text" id="luci_username" name="luci_username"
|
||||||
|
value="<%=pcdata(duser)%>" placeholder="root"
|
||||||
|
autocomplete="username" autofocus>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="luci_password"><%:Password%></label>
|
||||||
|
<input type="password" id="luci_password" name="luci_password"
|
||||||
|
placeholder="********" autocomplete="current-password">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit"><%:Login%></button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div class="login-footer">
|
||||||
|
<span>SecuBox v<%=luci.version.distversion%></span>
|
||||||
|
<span>|</span>
|
||||||
|
<a href="https://openwrt.org" target="_blank">OpenWrt</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Focus password field if username is pre-filled
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
var username = document.getElementById('luci_username');
|
||||||
|
var password = document.getElementById('luci_password');
|
||||||
|
if (username && username.value && password) {
|
||||||
|
password.focus();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# SecuBox CRT P31 Theme - UCI Defaults
|
||||||
|
# Set SecuBox theme as default LuCI theme
|
||||||
|
|
||||||
|
uci -q batch <<-EOF
|
||||||
|
set luci.main.mediaurlbase='/luci-static/secubox'
|
||||||
|
commit luci
|
||||||
|
EOF
|
||||||
|
|
||||||
|
exit 0
|
||||||
Loading…
Reference in New Issue
Block a user