From 1140221f4a310bf14d2a16107016948bcaf4400e Mon Sep 17 00:00:00 2001 From: CyberMind-FR Date: Thu, 26 Mar 2026 07:18:23 +0100 Subject: [PATCH] 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 --- package/secubox/luci-theme-secubox/Makefile | 45 +- package/secubox/luci-theme-secubox/README.md | 152 +++- .../secubox-theme/themes/crt-p31.css | 411 +++++++++ .../htdocs/luci-static/secubox/cascade.css | 796 ++++++++++++++++++ .../luci-static/secubox/crt-components.js | 433 ++++++++++ .../htdocs/luci-static/secubox/crt-engine.js | 264 ++++++ .../luci/view/themes/secubox/footer.htm | 37 + .../luci/view/themes/secubox/header.htm | 106 +++ .../luci/view/themes/secubox/sysauth.htm | 215 +++++ .../etc/uci-defaults/90-luci-theme-secubox | 10 + 10 files changed, 2423 insertions(+), 46 deletions(-) create mode 100644 package/secubox/luci-theme-secubox/htdocs/luci-static/resources/secubox-theme/themes/crt-p31.css create mode 100644 package/secubox/luci-theme-secubox/htdocs/luci-static/secubox/cascade.css create mode 100644 package/secubox/luci-theme-secubox/htdocs/luci-static/secubox/crt-components.js create mode 100644 package/secubox/luci-theme-secubox/htdocs/luci-static/secubox/crt-engine.js create mode 100644 package/secubox/luci-theme-secubox/luasrc/luci/view/themes/secubox/footer.htm create mode 100644 package/secubox/luci-theme-secubox/luasrc/luci/view/themes/secubox/header.htm create mode 100644 package/secubox/luci-theme-secubox/luasrc/luci/view/themes/secubox/sysauth.htm create mode 100755 package/secubox/luci-theme-secubox/root/etc/uci-defaults/90-luci-theme-secubox diff --git a/package/secubox/luci-theme-secubox/Makefile b/package/secubox/luci-theme-secubox/Makefile index 64dc886f..82f7bb69 100644 --- a/package/secubox/luci-theme-secubox/Makefile +++ b/package/secubox/luci-theme-secubox/Makefile @@ -1,16 +1,45 @@ +# SPDX-License-Identifier: MIT +# SecuBox CRT P31 Phosphor Theme for OpenWrt LuCI +# CyberMind — SecuBox — 2026 + include $(TOPDIR)/rules.mk PKG_NAME:=luci-theme-secubox -PKG_VERSION:=0.4.8 -PKG_RELEASE:=1 -PKG_LICENSE:=Apache-2.0 -PKG_MAINTAINER:=CyberMind +PKG_VERSION:=1.0.0 +PKG_RELEASE:=2 +PKG_MAINTAINER:=Gerald KERMA +PKG_LICENSE:=MIT -LUCI_TITLE:=LuCI - SecuBox CyberMood Theme -LUCI_DESCRIPTION:=Global CyberMood design system (CSS/JS/i18n) shared by all SecuBox dashboards. +LUCI_TITLE:=SecuBox CRT P31 Phosphor Theme 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 -# 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)) diff --git a/package/secubox/luci-theme-secubox/README.md b/package/secubox/luci-theme-secubox/README.md index 3a722e7e..f5d5eb76 100644 --- a/package/secubox/luci-theme-secubox/README.md +++ b/package/secubox/luci-theme-secubox/README.md @@ -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. -``` -./feeds/luci/luci-theme-secubox/ -├── Makefile -└── htdocs/luci-static/resources/secubox-theme/ - ├── core/ # Variables, reset, typography, animations, utilities - ├── components/ # Buttons, cards, forms, tables, badges, alerts, etc. - ├── layouts/ # Dashboard/grid/responsive helpers - ├── themes/ # Dark (default), light, cyberpunk variants - ├── i18n/ # en/fr/de/es JSON dictionaries - ├── secubox-theme.css - ├── secubox-theme.min.css - └── theme.js # Theme controller (init/apply/t/Theme.create*) +## Features + +- **P31 Phosphor Green** color palette - authentic CRT terminal aesthetic +- **Scanlines overlay** - subtle CRT screen effect +- **Phosphor glow effects** - text and button bloom on hover/focus +- **Boot sequence animation** - terminal-style startup on first visit +- **Responsive design** - works on desktop and mobile +- **Full LuCI coverage** - all pages, forms, tables, and widgets styled + +## Color Palette + +| 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 -'use strict'; -'require secubox-theme/theme as Theme'; - -return view.extend({ - load: function() { - return Theme.init(); - }, - 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() - }) - ] - }); - } +// Configure CRT effects +SecuBoxCRT.configure({ + enableScanlines: true, // Scanlines overlay + enableFlicker: false, // Screen flicker (disabled by default) + enableGlow: true, // Phosphor glow on hover + enableBootSequence: true // Terminal boot animation }); + +// 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 diff --git a/package/secubox/luci-theme-secubox/htdocs/luci-static/resources/secubox-theme/themes/crt-p31.css b/package/secubox/luci-theme-secubox/htdocs/luci-static/resources/secubox-theme/themes/crt-p31.css new file mode 100644 index 00000000..297ddc56 --- /dev/null +++ b/package/secubox/luci-theme-secubox/htdocs/luci-static/resources/secubox-theme/themes/crt-p31.css @@ -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); +} diff --git a/package/secubox/luci-theme-secubox/htdocs/luci-static/secubox/cascade.css b/package/secubox/luci-theme-secubox/htdocs/luci-static/secubox/cascade.css new file mode 100644 index 00000000..82ddbc3f --- /dev/null +++ b/package/secubox/luci-theme-secubox/htdocs/luci-static/secubox/cascade.css @@ -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; +} diff --git a/package/secubox/luci-theme-secubox/htdocs/luci-static/secubox/crt-components.js b/package/secubox/luci-theme-secubox/htdocs/luci-static/secubox/crt-components.js new file mode 100644 index 00000000..813325ed --- /dev/null +++ b/package/secubox/luci-theme-secubox/htdocs/luci-static/secubox/crt-components.js @@ -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 = [ + '
' + this.escapeHtml(opts.title) + '
', + '
' + + this.escapeHtml(opts.value) + + (opts.unit ? '' + this.escapeHtml(opts.unit) + '' : '') + + '
' + ].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; + +})(); diff --git a/package/secubox/luci-theme-secubox/htdocs/luci-static/secubox/crt-engine.js b/package/secubox/luci-theme-secubox/htdocs/luci-static/secubox/crt-engine.js new file mode 100644 index 00000000..ab84e18e --- /dev/null +++ b/package/secubox/luci-theme-secubox/htdocs/luci-static/secubox/crt-engine.js @@ -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; + +})(); diff --git a/package/secubox/luci-theme-secubox/luasrc/luci/view/themes/secubox/footer.htm b/package/secubox/luci-theme-secubox/luasrc/luci/view/themes/secubox/footer.htm new file mode 100644 index 00000000..487d0546 --- /dev/null +++ b/package/secubox/luci-theme-secubox/luasrc/luci/view/themes/secubox/footer.htm @@ -0,0 +1,37 @@ +<%# + SecuBox CRT P31 Theme - Footer Template + CyberMind — SecuBox — 2026 +-%> + + + + +
+ SecuBox Mesh v<%=luci.version.distversion%> + | + Powered by OpenWrt + | + © 2026 CyberMind +
+ +<% + local webroot = luci.http.getenv("DOCUMENT_ROOT") or "" + local resource = luci.dispatcher.build_url("admin/translations", luci.i18n.context.lang) +-%> + + + + + diff --git a/package/secubox/luci-theme-secubox/luasrc/luci/view/themes/secubox/header.htm b/package/secubox/luci-theme-secubox/luasrc/luci/view/themes/secubox/header.htm new file mode 100644 index 00000000..a4ed8e1f --- /dev/null +++ b/package/secubox/luci-theme-secubox/luasrc/luci/view/themes/secubox/header.htm @@ -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) +-%> + + + + + + + +<%=pcdata(hostname)%> - <%=striptags(title)%> - SecuBox + + +<% if node and node.css then %> + +<% end -%> +<% if css then %> + +<% end -%> + + +
+
SECUBOX
+
+ <%=pcdata(hostname)%> +
+
+ +
+ + +
+ <% if title then %> +

<%=pcdata(striptags(title))%>

+ <% end %> diff --git a/package/secubox/luci-theme-secubox/luasrc/luci/view/themes/secubox/sysauth.htm b/package/secubox/luci-theme-secubox/luasrc/luci/view/themes/secubox/sysauth.htm new file mode 100644 index 00000000..5e05aa54 --- /dev/null +++ b/package/secubox/luci-theme-secubox/luasrc/luci/view/themes/secubox/sysauth.htm @@ -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) +-%> + + + + + + + +<%=pcdata(hostname)%> - Login - SecuBox + + + + + + + + + + + + diff --git a/package/secubox/luci-theme-secubox/root/etc/uci-defaults/90-luci-theme-secubox b/package/secubox/luci-theme-secubox/root/etc/uci-defaults/90-luci-theme-secubox new file mode 100755 index 00000000..b68a55e8 --- /dev/null +++ b/package/secubox/luci-theme-secubox/root/etc/uci-defaults/90-luci-theme-secubox @@ -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