Temporarily disable anti-forgery validation for HTTPS proxy compatibility

This commit is contained in:
2025-09-19 16:44:37 +01:00
parent 8b0e3e0611
commit bcefd2c6fc
180 changed files with 85605 additions and 2 deletions

View File

@@ -0,0 +1,415 @@
/*
* 🏢 CORPORATE STEEL THEME 🏢
* Professional dark theme with subtle metallic accents
* Clean, corporate aesthetic with steel/metal textures
*/
:root {
/* Corporate Color Palette */
--corp-primary: #6A4C93; /* Muted Purple */
--corp-secondary: #8B5A8C; /* Soft Purple */
--corp-accent: #9B69B0; /* Light Purple */
/* Steel/Metal Colors */
--steel-dark: #1C1C1E; /* Dark Steel */
--steel-medium: #2C2C2E; /* Medium Steel */
--steel-light: #3A3A3C; /* Light Steel */
--steel-accent: #48484A; /* Steel Accent */
/* Text Colors */
--text-primary: #FFFFFF; /* White */
--text-secondary: #E5E5E7; /* Light Grey */
--text-muted: #AEAEB2; /* Muted Grey */
/* Subtle Gradients */
--steel-gradient: linear-gradient(135deg,
var(--steel-dark) 0%,
var(--steel-medium) 50%,
var(--steel-light) 100%);
--purple-gradient: linear-gradient(135deg,
var(--corp-primary) 0%,
var(--corp-secondary) 100%);
/* Shadows */
--shadow-subtle: 0 2px 8px rgba(0, 0, 0, 0.3);
--shadow-medium: 0 4px 16px rgba(0, 0, 0, 0.4);
--shadow-strong: 0 8px 32px rgba(0, 0, 0, 0.5);
/* Transitions */
--transition-smooth: 0.2s ease;
}
/* Global Corporate Base */
body {
background: var(--steel-dark) !important;
background-image:
linear-gradient(45deg, transparent 49%, rgba(106, 76, 147, 0.03) 50%, transparent 51%),
linear-gradient(-45deg, transparent 49%, rgba(139, 90, 140, 0.02) 50%, transparent 51%);
background-size: 20px 20px, 24px 24px;
color: var(--text-primary) !important;
font-family: 'Segoe UI', 'Roboto', 'Arial', sans-serif !important;
font-weight: 400;
line-height: 1.6;
}
/* Card Styling */
.card,
.rz-card {
background: var(--steel-gradient) !important;
border: 1px solid var(--steel-accent) !important;
border-radius: 8px !important;
box-shadow: var(--shadow-medium) !important;
color: var(--text-primary) !important;
transition: all var(--transition-smooth) !important;
}
.card:hover,
.rz-card:hover {
transform: translateY(-2px) !important;
box-shadow: var(--shadow-strong) !important;
border-color: var(--corp-accent) !important;
}
.card-header,
.rz-card-header {
background: linear-gradient(90deg,
var(--steel-medium) 0%,
var(--steel-light) 100%) !important;
border-bottom: 1px solid var(--steel-accent) !important;
color: var(--text-primary) !important;
font-weight: 600 !important;
}
.card-body,
.rz-card-body {
background: var(--steel-medium) !important;
color: var(--text-primary) !important;
}
/* Button Styling */
.btn,
.rz-button {
background: var(--purple-gradient) !important;
border: 1px solid var(--corp-accent) !important;
border-radius: 6px !important;
color: var(--text-primary) !important;
font-weight: 500 !important;
transition: all var(--transition-smooth) !important;
position: relative !important;
}
.btn:hover,
.rz-button:hover {
background: linear-gradient(135deg,
var(--corp-secondary) 0%,
var(--corp-accent) 100%) !important;
border-color: var(--corp-accent) !important;
transform: translateY(-1px) !important;
color: var(--text-primary) !important;
}
.btn:active,
.rz-button:active {
transform: scale(0.98) !important;
}
/* Navigation */
.navbar-dark {
background: var(--steel-gradient) !important;
border-bottom: 1px solid var(--steel-accent) !important;
}
.navbar-brand {
color: var(--text-primary) !important;
font-weight: 600 !important;
}
.nav-link {
color: var(--text-secondary) !important;
transition: all var(--transition-smooth) !important;
}
.nav-link:hover {
color: var(--corp-accent) !important;
}
/* Form Controls */
.form-control,
.form-select,
.rz-textbox,
.rz-dropdown {
background: var(--steel-medium) !important;
border: 2px solid var(--steel-accent) !important;
border-radius: 6px !important;
color: var(--text-primary) !important;
transition: all var(--transition-smooth) !important;
}
.form-control:focus,
.form-select:focus,
.rz-textbox:focus,
.rz-dropdown:focus {
background: var(--steel-light) !important;
border-color: var(--corp-primary) !important;
box-shadow: 0 0 0 0.25rem rgba(106, 76, 147, 0.25) !important;
outline: none !important;
}
.form-label {
color: var(--text-secondary) !important;
font-weight: 500 !important;
}
/* Tables */
.table {
background: var(--steel-medium) !important;
color: var(--text-primary) !important;
border-radius: 8px !important;
overflow: hidden !important;
}
.table th {
background: var(--steel-light) !important;
color: var(--text-primary) !important;
border-bottom: 2px solid var(--steel-accent) !important;
font-weight: 600 !important;
}
.table td {
border-bottom: 1px solid var(--steel-accent) !important;
color: var(--text-primary) !important;
}
.table tbody tr:hover {
background: rgba(106, 76, 147, 0.1) !important;
}
/* Mobile Navigation */
.mobile-header {
background: var(--steel-gradient) !important;
border-bottom: 1px solid var(--steel-accent) !important;
}
.mobile-bottom-nav {
background: var(--steel-medium) !important;
border-top: 1px solid var(--steel-accent) !important;
}
.mobile-nav-item {
color: var(--text-secondary) !important;
transition: all var(--transition-smooth) !important;
}
.mobile-nav-item:hover {
color: var(--corp-accent) !important;
background: rgba(106, 76, 147, 0.1) !important;
}
.mobile-nav-item.active {
color: var(--text-primary) !important;
background: var(--purple-gradient) !important;
}
.mobile-sidebar {
background: var(--steel-gradient) !important;
border-right: 1px solid var(--steel-accent) !important;
}
.mobile-sidebar-header {
background: var(--purple-gradient) !important;
}
.mobile-sidebar-link {
color: var(--text-secondary) !important;
transition: all var(--transition-smooth) !important;
}
.mobile-sidebar-link:hover {
background: rgba(106, 76, 147, 0.1) !important;
color: var(--corp-accent) !important;
}
.mobile-sidebar-link.active {
background: var(--purple-gradient) !important;
color: var(--text-primary) !important;
}
/* Alerts and Notifications */
.alert {
background: var(--steel-medium) !important;
border: 1px solid var(--steel-accent) !important;
color: var(--text-primary) !important;
}
.alert-success {
border-color: #28a745 !important;
background: linear-gradient(135deg, var(--steel-medium), rgba(40, 167, 69, 0.1)) !important;
}
.alert-danger {
border-color: #dc3545 !important;
background: linear-gradient(135deg, var(--steel-medium), rgba(220, 53, 69, 0.1)) !important;
}
.alert-warning {
border-color: #ffc107 !important;
background: linear-gradient(135deg, var(--steel-medium), rgba(255, 193, 7, 0.1)) !important;
}
/* Mobile Cards */
.mobile-card,
.order-card {
background: var(--steel-gradient) !important;
border: 1px solid var(--steel-accent) !important;
color: var(--text-primary) !important;
}
.mobile-card-header,
.order-header {
background: var(--steel-light) !important;
color: var(--text-primary) !important;
border-bottom: 1px solid var(--steel-accent) !important;
}
.mobile-card-body,
.order-body {
background: var(--steel-medium) !important;
color: var(--text-primary) !important;
}
.mobile-card-footer {
background: var(--steel-light) !important;
color: var(--text-primary) !important;
border-top: 1px solid var(--steel-accent) !important;
}
/* Status Badges */
.status-badge {
background: var(--steel-light) !important;
color: var(--text-primary) !important;
border: 1px solid var(--steel-accent) !important;
}
.status-badge.pending {
background: linear-gradient(135deg, var(--steel-light), rgba(255, 193, 7, 0.2)) !important;
color: #ffc107 !important;
}
.status-badge.processing {
background: linear-gradient(135deg, var(--steel-light), rgba(23, 162, 184, 0.2)) !important;
color: #17a2b8 !important;
}
.status-badge.shipped {
background: linear-gradient(135deg, var(--steel-light), rgba(40, 167, 69, 0.2)) !important;
color: #28a745 !important;
}
/* Breadcrumbs and Links */
a {
color: var(--corp-accent) !important;
transition: color var(--transition-smooth) !important;
}
a:hover {
color: var(--text-primary) !important;
}
/* Dropdowns */
.dropdown-menu {
background: var(--steel-medium) !important;
border: 1px solid var(--steel-accent) !important;
box-shadow: var(--shadow-medium) !important;
}
.dropdown-item {
color: var(--text-secondary) !important;
transition: all var(--transition-smooth) !important;
}
.dropdown-item:hover {
background: rgba(106, 76, 147, 0.2) !important;
color: var(--text-primary) !important;
}
/* List Groups */
.list-group-item {
background: var(--steel-medium) !important;
border: 1px solid var(--steel-accent) !important;
color: var(--text-primary) !important;
}
.list-group-item:hover {
background: var(--steel-light) !important;
}
/* Override any remaining Bootstrap defaults */
.container,
.container-fluid {
color: var(--text-primary) !important;
}
h1, h2, h3, h4, h5, h6 {
color: var(--text-primary) !important;
font-weight: 600 !important;
}
p {
color: var(--text-secondary) !important;
}
/* Subtle Steel Texture */
.steel-texture {
background-image:
linear-gradient(45deg, rgba(255,255,255,0.02) 25%, transparent 25%),
linear-gradient(-45deg, rgba(255,255,255,0.02) 25%, transparent 25%),
linear-gradient(45deg, transparent 75%, rgba(255,255,255,0.02) 75%),
linear-gradient(-45deg, transparent 75%, rgba(255,255,255,0.02) 75%);
background-size: 4px 4px;
background-position: 0 0, 0 2px, 2px -2px, -2px 0px;
}
/* Corporate Professional Styling */
.corporate-card {
background: var(--steel-gradient) !important;
border: 1px solid var(--steel-accent) !important;
border-radius: 8px !important;
box-shadow: var(--shadow-subtle) !important;
}
.corporate-card:hover {
box-shadow: var(--shadow-medium) !important;
transform: translateY(-1px) !important;
}
/* Remove any epileptic-inducing effects */
* {
animation: none !important;
}
/* Clean scrollbars */
::-webkit-scrollbar {
width: 8px;
background: var(--steel-dark);
}
::-webkit-scrollbar-track {
background: var(--steel-medium);
}
::-webkit-scrollbar-thumb {
background: var(--steel-light);
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: var(--corp-primary);
}
/* Professional Focus States */
.form-control:focus,
.form-select:focus {
border-color: var(--corp-primary) !important;
box-shadow: 0 0 0 0.2rem rgba(106, 76, 147, 0.25) !important;
}

View File

@@ -0,0 +1,444 @@
/*
* Modern Clean Admin Theme
* Mobile-first, professional, and user-friendly
*/
:root {
/* Modern Color Palette */
--primary-blue: #2563eb;
--primary-purple: #7c3aed;
--success-green: #059669;
--warning-orange: #d97706;
--danger-red: #dc2626;
/* Neutral Greys */
--grey-50: #f9fafb;
--grey-100: #f3f4f6;
--grey-200: #e5e7eb;
--grey-300: #d1d5db;
--grey-400: #9ca3af;
--grey-500: #6b7280;
--grey-600: #4b5563;
--grey-700: #374151;
--grey-800: #1f2937;
--grey-900: #111827;
/* Spacing */
--spacing-xs: 0.25rem;
--spacing-sm: 0.5rem;
--spacing-md: 1rem;
--spacing-lg: 1.5rem;
--spacing-xl: 2rem;
/* Border Radius */
--radius-sm: 0.375rem;
--radius-md: 0.5rem;
--radius-lg: 0.75rem;
--radius-xl: 1rem;
/* Shadows */
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
/* Typography */
--font-sans: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
--font-mono: ui-monospace, SFMono-Regular, "SF Mono", Consolas, "Liberation Mono", Menlo, monospace;
}
/* Global Styles */
body {
font-family: var(--font-sans);
background-color: var(--grey-50);
color: var(--grey-900);
line-height: 1.6;
}
/* Modern Card Styling */
.card {
border: 1px solid var(--grey-200);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-sm);
background: white;
transition: all 0.2s ease;
}
.card:hover {
box-shadow: var(--shadow-md);
transform: translateY(-1px);
}
.card-header {
background: var(--grey-50);
border-bottom: 1px solid var(--grey-200);
padding: var(--spacing-lg);
font-weight: 600;
color: var(--grey-700);
}
.card-body {
padding: var(--spacing-lg);
}
/* Modern Button Styling */
.btn {
border-radius: var(--radius-md);
font-weight: 500;
padding: 0.625rem 1.25rem;
transition: all 0.2s ease;
border: none;
}
.btn-primary {
background: linear-gradient(135deg, var(--primary-blue) 0%, var(--primary-purple) 100%);
color: white;
}
.btn-primary:hover {
background: linear-gradient(135deg, #1d4ed8 0%, #6d28d9 100%);
transform: translateY(-1px);
box-shadow: var(--shadow-md);
}
.btn-success {
background: var(--success-green);
color: white;
}
.btn-success:hover {
background: #047857;
transform: translateY(-1px);
}
.btn-outline-primary {
border: 2px solid var(--primary-blue);
color: var(--primary-blue);
background: white;
}
.btn-outline-primary:hover {
background: var(--primary-blue);
color: white;
transform: translateY(-1px);
}
.btn-sm {
padding: 0.5rem 1rem;
font-size: 0.875rem;
}
/* Modern Navigation */
.navbar {
background: white !important;
border-bottom: 1px solid var(--grey-200);
box-shadow: var(--shadow-sm);
padding: var(--spacing-md) 0;
}
.navbar-brand {
color: var(--grey-900) !important;
font-weight: 700;
font-size: 1.25rem;
}
.navbar-nav .nav-link {
color: var(--grey-600) !important;
font-weight: 500;
padding: 0.75rem 1rem;
border-radius: var(--radius-md);
margin: 0 0.25rem;
transition: all 0.2s ease;
}
.navbar-nav .nav-link:hover {
color: var(--primary-blue) !important;
background: var(--grey-50);
}
/* Form Controls */
.form-control,
.form-select {
border: 2px solid var(--grey-200);
border-radius: var(--radius-md);
padding: 0.75rem 1rem;
transition: all 0.2s ease;
background: white;
}
.form-control:focus,
.form-select:focus {
border-color: var(--primary-blue);
box-shadow: 0 0 0 0.25rem rgba(37, 99, 235, 0.1);
outline: none;
}
.form-label {
font-weight: 600;
color: var(--grey-700);
margin-bottom: var(--spacing-sm);
}
/* Table Styling */
.table {
background: white;
border-radius: var(--radius-lg);
overflow: hidden;
box-shadow: var(--shadow-sm);
}
.table thead th {
background: var(--grey-50);
border-bottom: 2px solid var(--grey-200);
font-weight: 600;
color: var(--grey-700);
padding: 1rem;
}
.table tbody td {
padding: 1rem;
border-bottom: 1px solid var(--grey-100);
vertical-align: middle;
}
.table tbody tr:hover {
background: var(--grey-50);
}
/* Status Badges */
.badge {
font-weight: 500;
padding: 0.5rem 0.75rem;
border-radius: var(--radius-sm);
}
.bg-success {
background-color: var(--success-green) !important;
}
.bg-warning {
background-color: var(--warning-orange) !important;
}
.bg-danger {
background-color: var(--danger-red) !important;
}
.bg-info {
background-color: var(--primary-blue) !important;
}
/* Page Headers */
h1, h2, h3, h4, h5, h6 {
color: var(--grey-900);
font-weight: 700;
line-height: 1.25;
}
h1 {
font-size: 2rem;
margin-bottom: var(--spacing-lg);
}
/* Mobile Responsive Design */
@media (max-width: 768px) {
.container-fluid {
padding: var(--spacing-md);
}
.card {
margin-bottom: var(--spacing-lg);
border-radius: var(--radius-md);
}
.card-header {
padding: var(--spacing-md);
}
.card-body {
padding: var(--spacing-md);
}
.btn {
width: 100%;
margin-bottom: var(--spacing-sm);
}
.btn:last-child {
margin-bottom: 0;
}
/* Mobile Table - Stack on Small Screens */
@media (max-width: 768px) {
.table-responsive {
border: none;
}
.table {
font-size: 0.875rem;
}
.table thead {
display: none;
}
.table tbody,
.table tbody tr,
.table tbody td {
display: block;
width: 100%;
}
.table tbody tr {
background: white;
border: 1px solid var(--grey-200);
border-radius: var(--radius-md);
margin-bottom: var(--spacing-md);
padding: var(--spacing-md);
box-shadow: var(--shadow-sm);
}
.table tbody td {
border: none;
padding: var(--spacing-sm) 0;
position: relative;
padding-left: 40%;
}
.table tbody td:before {
content: attr(data-label) ": ";
position: absolute;
left: 0;
width: 35%;
font-weight: 600;
color: var(--grey-600);
}
}
/* Mobile Navigation */
.navbar-toggler {
border: none;
padding: 0.25rem 0.5rem;
}
.navbar-collapse {
margin-top: var(--spacing-md);
}
.navbar-nav .nav-link {
padding: var(--spacing-md);
margin: var(--spacing-xs) 0;
background: var(--grey-50);
border-radius: var(--radius-md);
}
}
/* Modern Form Layout */
.row .col-md-6 .form-group,
.row .col-md-6 .mb-3 {
margin-bottom: var(--spacing-lg);
}
/* Action Buttons */
.d-flex.justify-content-between .btn {
min-width: 120px;
}
/* Loading States */
.btn:disabled {
opacity: 0.6;
cursor: not-allowed;
}
/* Focus States for Accessibility */
.btn:focus,
.form-control:focus,
.form-select:focus {
outline: 2px solid var(--primary-blue);
outline-offset: 2px;
}
/* Toast/Alert Improvements */
.alert {
border: none;
border-radius: var(--radius-md);
border-left: 4px solid;
}
.alert-success {
background: #ecfdf5;
color: #065f46;
border-left-color: var(--success-green);
}
.alert-danger {
background: #fef2f2;
color: #991b1b;
border-left-color: var(--danger-red);
}
.alert-warning {
background: #fffbeb;
color: #92400e;
border-left-color: var(--warning-orange);
}
/* Clean List Styling */
.list-group-item {
border: 1px solid var(--grey-200);
background: white;
transition: all 0.2s ease;
}
.list-group-item:hover {
background: var(--grey-50);
transform: translateX(4px);
}
.list-group-item-action {
color: var(--grey-700);
}
.list-group-item-action:hover {
color: var(--primary-blue);
}
/* Typography Improvements */
.text-muted {
color: var(--grey-500) !important;
}
code {
background: var(--grey-100);
color: var(--grey-800);
padding: 0.125rem 0.375rem;
border-radius: var(--radius-sm);
font-family: var(--font-mono);
font-size: 0.875em;
}
/* Utility Classes */
.rounded-modern {
border-radius: var(--radius-lg) !important;
}
.shadow-modern {
box-shadow: var(--shadow-md) !important;
}
.bg-gradient-primary {
background: linear-gradient(135deg, var(--primary-blue) 0%, var(--primary-purple) 100%) !important;
}
/* Print Styles */
@media print {
.navbar,
.btn,
.card-header {
display: none !important;
}
.card {
border: 1px solid #ccc !important;
box-shadow: none !important;
}
}

View File

@@ -0,0 +1,534 @@
/*
* 🤖 RADZEN TECH HOLOGRAPHIC THEME 🤖
* Dark Purple Metallic with Holographic Effects
* Pixel Perfect Robot/Tech Aesthetic
*/
:root {
/* Core Theme Colors */
--tech-primary: #8A2BE2; /* Blue Violet */
--tech-secondary: #9932CC; /* Dark Orchid */
--tech-accent: #DA70D6; /* Orchid */
--tech-highlight: #FF00FF; /* Magenta */
--tech-neon: #00FFFF; /* Cyan */
/* Dark Base Colors */
--tech-dark-base: #0D0014; /* Ultra Dark Purple */
--tech-dark-surface: #1A0A2E; /* Dark Purple */
--tech-dark-card: #16213E; /* Dark Blue Purple */
--tech-dark-accent: #0F3460; /* Deep Blue */
/* Metallic Colors */
--tech-metallic-silver: #C0C0C0;
--tech-metallic-gold: #FFD700;
--tech-metallic-copper: #B87333;
--tech-metallic-chrome: #E5E5E5;
/* Holographic Gradient */
--holographic-gradient: linear-gradient(45deg,
#FF0080 0%,
#7928CA 25%,
#0070F3 50%,
#00DFD8 75%,
#FF0080 100%);
/* Glow Effects */
--tech-glow-primary: 0 0 20px rgba(138, 43, 226, 0.8);
--tech-glow-secondary: 0 0 30px rgba(153, 50, 204, 0.6);
--tech-glow-accent: 0 0 15px rgba(218, 112, 214, 0.9);
--tech-glow-neon: 0 0 25px rgba(0, 255, 255, 0.7);
/* Animation Timings */
--tech-transition-fast: 0.15s cubic-bezier(0.4, 0, 0.2, 1);
--tech-transition-smooth: 0.3s cubic-bezier(0.4, 0, 0.2, 1);
--tech-transition-glow: 0.5s cubic-bezier(0.4, 0, 0.2, 1);
}
/* Holographic Animation Keyframes */
@keyframes holographic-shift {
0% { background-position: 0% 50%; }
25% { background-position: 100% 50%; }
50% { background-position: 100% 0%; }
75% { background-position: 0% 0%; }
100% { background-position: 0% 50%; }
}
@keyframes tech-pulse {
0%, 100% {
transform: scale(1);
box-shadow: var(--tech-glow-primary);
}
50% {
transform: scale(1.05);
box-shadow: var(--tech-glow-secondary);
}
}
@keyframes neon-flicker {
0%, 100% { opacity: 1; }
2% { opacity: 0.8; }
4% { opacity: 1; }
8% { opacity: 0.9; }
10% { opacity: 1; }
}
@keyframes data-stream {
0% { transform: translateY(-100%); opacity: 0; }
10% { opacity: 1; }
90% { opacity: 1; }
100% { transform: translateY(100vh); opacity: 0; }
}
/* Global Dark Theme Base */
body {
background: var(--tech-dark-base) !important;
background-image:
radial-gradient(circle at 25% 25%, rgba(138, 43, 226, 0.1) 0%, transparent 50%),
radial-gradient(circle at 75% 75%, rgba(153, 50, 204, 0.1) 0%, transparent 50%),
linear-gradient(45deg, transparent 45%, rgba(0, 255, 255, 0.03) 50%, transparent 55%);
color: var(--tech-metallic-chrome) !important;
font-family: 'Segoe UI', 'Roboto Mono', 'Courier New', monospace !important;
font-weight: 400;
line-height: 1.5;
min-height: 100vh;
}
/* Radzen Component Overrides */
.rz-card {
background: linear-gradient(135deg,
var(--tech-dark-surface) 0%,
var(--tech-dark-card) 100%) !important;
border: 1px solid rgba(138, 43, 226, 0.3) !important;
border-radius: 12px !important;
box-shadow:
0 8px 32px rgba(13, 0, 20, 0.8),
inset 0 1px 0 rgba(218, 112, 214, 0.2),
0 0 0 1px rgba(138, 43, 226, 0.1) !important;
color: var(--tech-metallic-chrome) !important;
transition: all var(--tech-transition-smooth) !important;
backdrop-filter: blur(20px) !important;
}
.rz-card:hover {
transform: translateY(-2px) !important;
box-shadow:
0 16px 64px rgba(13, 0, 20, 0.9),
inset 0 1px 0 rgba(218, 112, 214, 0.4),
var(--tech-glow-primary) !important;
border-color: var(--tech-primary) !important;
}
.rz-card-header {
background: linear-gradient(90deg,
rgba(138, 43, 226, 0.2) 0%,
rgba(153, 50, 204, 0.1) 100%) !important;
border-bottom: 1px solid rgba(218, 112, 214, 0.3) !important;
color: var(--tech-metallic-chrome) !important;
font-weight: 600 !important;
padding: 1rem 1.5rem !important;
position: relative !important;
}
.rz-card-header::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 2px;
background: var(--holographic-gradient);
background-size: 200% 100%;
animation: holographic-shift 3s ease-in-out infinite;
}
.rz-card-body {
padding: 1.5rem !important;
background: rgba(22, 33, 62, 0.3) !important;
}
/* Radzen Buttons */
.rz-button {
background: linear-gradient(135deg,
var(--tech-primary) 0%,
var(--tech-secondary) 100%) !important;
border: 1px solid var(--tech-accent) !important;
border-radius: 8px !important;
color: white !important;
font-weight: 600 !important;
text-transform: uppercase !important;
letter-spacing: 0.5px !important;
transition: all var(--tech-transition-smooth) !important;
position: relative !important;
overflow: hidden !important;
}
.rz-button::before {
content: '';
position: absolute;
top: -2px;
left: -2px;
right: -2px;
bottom: -2px;
background: var(--holographic-gradient);
background-size: 200% 200%;
border-radius: inherit;
z-index: -1;
opacity: 0;
transition: opacity var(--tech-transition-smooth);
animation: holographic-shift 2s ease-in-out infinite;
}
.rz-button:hover::before {
opacity: 1;
}
.rz-button:hover {
transform: translateY(-1px) !important;
/* box-shadow: var(--tech-glow-primary) !important; */
/* text-shadow: 0 0 10px rgba(255, 255, 255, 0.8) !important; */
}
.rz-button:active {
transform: scale(0.98) !important;
}
.rz-button-primary {
background: linear-gradient(135deg,
var(--tech-primary) 0%,
var(--tech-highlight) 100%) !important;
}
.rz-button-secondary {
background: linear-gradient(135deg,
var(--tech-dark-accent) 0%,
var(--tech-secondary) 100%) !important;
}
/* Radzen Data Grid */
.rz-datatable,
.rz-grid {
background: var(--tech-dark-surface) !important;
border: 1px solid rgba(138, 43, 226, 0.3) !important;
border-radius: 12px !important;
overflow: hidden !important;
}
.rz-datatable-header,
.rz-grid-header {
background: linear-gradient(90deg,
var(--tech-dark-card) 0%,
var(--tech-dark-accent) 100%) !important;
color: var(--tech-metallic-chrome) !important;
font-weight: 600 !important;
border-bottom: 2px solid var(--tech-primary) !important;
}
.rz-datatable-header th,
.rz-grid-header th {
border-right: 1px solid rgba(138, 43, 226, 0.2) !important;
padding: 1rem !important;
text-transform: uppercase !important;
letter-spacing: 0.5px !important;
font-size: 0.85rem !important;
}
.rz-datatable-data tr,
.rz-grid-table tr {
background: rgba(26, 10, 46, 0.5) !important;
border-bottom: 1px solid rgba(218, 112, 214, 0.1) !important;
transition: all var(--tech-transition-fast) !important;
}
.rz-datatable-data tr:hover,
.rz-grid-table tr:hover {
background: rgba(138, 43, 226, 0.1) !important;
box-shadow: inset 0 0 20px rgba(218, 112, 214, 0.1) !important;
}
.rz-datatable-data td,
.rz-grid-table td {
border-right: 1px solid rgba(138, 43, 226, 0.1) !important;
padding: 1rem !important;
color: var(--tech-metallic-chrome) !important;
}
/* Radzen Form Controls */
.rz-textbox,
.rz-dropdown,
.rz-multiselect,
.rz-textarea {
background: rgba(22, 33, 62, 0.8) !important;
border: 2px solid rgba(138, 43, 226, 0.3) !important;
border-radius: 8px !important;
color: var(--tech-metallic-chrome) !important;
padding: 0.75rem 1rem !important;
transition: all var(--tech-transition-smooth) !important;
backdrop-filter: blur(10px) !important;
}
.rz-textbox:focus,
.rz-dropdown:focus,
.rz-multiselect:focus,
.rz-textarea:focus {
border-color: var(--tech-primary) !important;
box-shadow:
var(--tech-glow-primary),
inset 0 0 20px rgba(138, 43, 226, 0.1) !important;
outline: none !important;
background: rgba(22, 33, 62, 1) !important;
}
.rz-textbox::placeholder,
.rz-dropdown::placeholder,
.rz-textarea::placeholder {
color: rgba(192, 192, 192, 0.6) !important;
font-style: italic !important;
}
/* Radzen Navigation */
.rz-navigation {
background: linear-gradient(180deg,
var(--tech-dark-surface) 0%,
var(--tech-dark-base) 100%) !important;
border-right: 1px solid rgba(138, 43, 226, 0.3) !important;
backdrop-filter: blur(20px) !important;
}
.rz-navigation-item {
color: var(--tech-metallic-chrome) !important;
padding: 0.75rem 1.5rem !important;
transition: all var(--tech-transition-fast) !important;
border-radius: 0 25px 25px 0 !important;
margin: 0.25rem 0 !important;
position: relative !important;
}
.rz-navigation-item:hover {
background: linear-gradient(90deg,
rgba(138, 43, 226, 0.2) 0%,
rgba(153, 50, 204, 0.1) 100%) !important;
color: var(--tech-accent) !important;
transform: translateX(5px) !important;
}
.rz-navigation-item.rz-state-active {
background: linear-gradient(90deg,
var(--tech-primary) 0%,
var(--tech-secondary) 100%) !important;
color: white !important;
box-shadow: var(--tech-glow-primary) !important;
}
.rz-navigation-item.rz-state-active::after {
content: '';
position: absolute;
right: -1px;
top: 0;
bottom: 0;
width: 3px;
background: var(--tech-neon);
box-shadow: var(--tech-glow-neon);
animation: neon-flicker 2s infinite;
}
/* Radzen Panels */
.rz-panel {
background: var(--tech-dark-surface) !important;
border: 1px solid rgba(138, 43, 226, 0.3) !important;
border-radius: 12px !important;
box-shadow: 0 8px 32px rgba(13, 0, 20, 0.6) !important;
backdrop-filter: blur(20px) !important;
}
.rz-panel-header {
background: linear-gradient(90deg,
var(--tech-dark-card) 0%,
var(--tech-dark-accent) 100%) !important;
border-bottom: 1px solid rgba(218, 112, 214, 0.3) !important;
color: var(--tech-metallic-chrome) !important;
font-weight: 600 !important;
padding: 1rem 1.5rem !important;
position: relative !important;
}
/* Radzen Dialogs */
.rz-dialog {
background: linear-gradient(135deg,
var(--tech-dark-surface) 0%,
var(--tech-dark-card) 100%) !important;
border: 2px solid var(--tech-primary) !important;
border-radius: 16px !important;
box-shadow:
0 20px 60px rgba(13, 0, 20, 0.9),
var(--tech-glow-primary) !important;
backdrop-filter: blur(30px) !important;
}
.rz-dialog-header {
background: linear-gradient(90deg,
rgba(138, 43, 226, 0.3) 0%,
rgba(153, 50, 204, 0.2) 100%) !important;
border-bottom: 1px solid rgba(218, 112, 214, 0.4) !important;
color: var(--tech-metallic-chrome) !important;
font-weight: 700 !important;
text-transform: uppercase !important;
letter-spacing: 1px !important;
}
/* Radzen Notifications */
.rz-notification {
background: linear-gradient(135deg,
var(--tech-dark-surface) 0%,
var(--tech-dark-card) 100%) !important;
border: 1px solid var(--tech-primary) !important;
border-radius: 12px !important;
color: var(--tech-metallic-chrome) !important;
box-shadow:
0 8px 32px rgba(13, 0, 20, 0.8),
var(--tech-glow-primary) !important;
backdrop-filter: blur(20px) !important;
}
.rz-notification-success {
border-color: var(--tech-neon) !important;
box-shadow: var(--tech-glow-neon) !important;
}
.rz-notification-error {
border-color: var(--tech-highlight) !important;
box-shadow: 0 0 25px rgba(255, 0, 255, 0.7) !important;
}
/* Holographic Border Effect */
.tech-holographic-border {
position: relative;
border: 2px solid transparent !important;
background: linear-gradient(var(--tech-dark-surface), var(--tech-dark-surface)) padding-box,
var(--holographic-gradient) border-box !important;
background-size: 200% 200%;
animation: holographic-shift 3s ease-in-out infinite;
}
/* Tech Grid Pattern Overlay */
.tech-grid-overlay {
background-image:
linear-gradient(rgba(138, 43, 226, 0.1) 1px, transparent 1px),
linear-gradient(90deg, rgba(138, 43, 226, 0.1) 1px, transparent 1px);
background-size: 20px 20px;
}
/* Scrollbar Styling */
::-webkit-scrollbar {
width: 12px;
background: var(--tech-dark-base);
}
::-webkit-scrollbar-track {
background: rgba(26, 10, 46, 0.5);
border-radius: 6px;
}
::-webkit-scrollbar-thumb {
background: linear-gradient(180deg,
var(--tech-primary) 0%,
var(--tech-secondary) 100%);
border-radius: 6px;
border: 1px solid rgba(218, 112, 214, 0.3);
}
::-webkit-scrollbar-thumb:hover {
background: linear-gradient(180deg,
var(--tech-accent) 0%,
var(--tech-highlight) 100%);
box-shadow: var(--tech-glow-accent);
}
/* Loading Spinner Override */
.rz-spinner {
border: 3px solid rgba(138, 43, 226, 0.3);
border-top: 3px solid var(--tech-primary);
box-shadow: var(--tech-glow-primary);
}
/* Mobile Responsive Adjustments */
@media (max-width: 768px) {
.rz-card {
margin: 0.5rem !important;
border-radius: 8px !important;
}
.rz-button {
padding: 0.75rem 1.5rem !important;
font-size: 0.9rem !important;
}
.rz-navigation-item {
padding: 1rem !important;
border-radius: 0 20px 20px 0 !important;
}
}
/* Print Styles */
@media print {
body {
background: white !important;
color: black !important;
}
.rz-card {
background: white !important;
border: 1px solid #ccc !important;
box-shadow: none !important;
}
}
/* High Contrast Mode */
@media (prefers-contrast: high) {
:root {
--tech-primary: #FF00FF;
--tech-secondary: #00FFFF;
--tech-accent: #FFFF00;
}
}
/* Reduced Motion */
@media (prefers-reduced-motion: reduce) {
* {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
/* Dark Theme Class for Radzen */
.dark-theme {
--rz-primary: var(--tech-primary);
--rz-secondary: var(--tech-secondary);
--rz-success: var(--tech-neon);
--rz-info: var(--tech-accent);
--rz-warning: var(--tech-metallic-gold);
--rz-danger: var(--tech-highlight);
--rz-dark: var(--tech-dark-base);
--rz-light: var(--tech-metallic-chrome);
}
/* Tech Enhancement Classes */
.tech-glow {
box-shadow: var(--tech-glow-primary) !important;
animation: tech-pulse 2s infinite !important;
}
.tech-holographic {
background: var(--holographic-gradient) !important;
background-size: 200% 200% !important;
animation: holographic-shift 3s ease-in-out infinite !important;
-webkit-background-clip: text !important;
background-clip: text !important;
color: transparent !important;
}
.tech-neon {
color: var(--tech-neon) !important;
text-shadow: var(--tech-glow-neon) !important;
animation: neon-flicker 2s infinite !important;
}

View File

@@ -0,0 +1 @@
AAABAAEAEBAQAAEABAAoAQAAFgAAACgAAAAQAAAAIAAAAAEABAAAAAAAgAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -0,0 +1,16 @@
<svg width="512" height="512" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="gradient" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#2563eb;stop-opacity:1" />
<stop offset="100%" style="stop-color:#7c3aed;stop-opacity:1" />
</linearGradient>
</defs>
<rect width="512" height="512" fill="url(#gradient)" rx="80"/>
<g fill="white">
<path d="M256 120c75.111 0 136 60.889 136 136s-60.889 136-136 136-136-60.889-136-136 60.889-136 136-136zm0 32c-57.438 0-104 46.562-104 104s46.562 104 104 104 104-46.562 104-104-46.562-104-104-104z"/>
<circle cx="256" cy="256" r="48"/>
<path d="M160 320h192v32H160z"/>
<path d="M192 360h128v24H192z"/>
</g>
<text x="256" y="420" text-anchor="middle" fill="white" font-family="system-ui" font-size="32" font-weight="600">LS</text>
</svg>

After

Width:  |  Height:  |  Size: 879 B

View File

@@ -0,0 +1,2 @@
n <07><>3!<21><>qS<71><53><EFBFBD>l<EFBFBD>rZuz<75>*<2A>d<EFBFBD>s=<3D><>S<EFBFBD><53>0E<14><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><04> <20><>қ<EFBFBD>c<EFBFBD>bu<62>b<EFBFBD>`@<40>0<EFBFBD><30><EFBFBD><70><03>Ӹ<EFBFBD><D3B8><EFBFBD><EFBFBD>ɢA<>><3E>/ <03>!<10>ϔ_H<5F>qT[<5B>l+<03><><EFBFBD>ٟ<EFBFBD>xe0<1C><>d\<5C>$<24>q<EFBFBD>Bw<42>:@<40>%<25><>v<><76>kT<6B><54><17>
<EFBFBD><EFBFBD>!<21><1D><>?<3F>p기JPߛT<1F><>wo_<5F><7F><04><>s<EFBFBD><73>o<EEA3BC><6F><EFBFBD>&[4<>I<EFBFBD><49>g7<67>J<16><><11>(Y)<29>q <0B>.k-2<>Q0vG+*<2A><>bp;H<><48><10>~<7E>3<06>g1:"m<>Ɉ<EFBFBD><C988>2`#S<>$<24><><EFBFBD>m<EFBFBD>`<60>O>Ϝ<><CF9C>$<24><><EFBFBD><EFBFBD><07>$<24>%R<>lBoX<6F><58>˔,C<><43><EFBFBD><0F><><EFBFBD><EFBFBD><EFBFBD><17><>ڑ<EFBFBD>`<60>6<EFBFBD>BOV?%<25>͝Z(<1C>'uV<75>5f<35><66><18><>,^<5E> <0C>8<EFBFBD>4<><34>i<EFBFBD>6:T&<26>t<EFBFBD><1D><>?l<15><>$Ε<>>0c<30><63>s<EFBFBD>W<EFBFBD><57><EFBFBD>h

View File

@@ -0,0 +1,537 @@
// 🤖 Holographic Tech Robot Effects System 🤖
// Advanced pixel-perfect holographic animations and tech aesthetics
class HolographicEffectsSystem {
constructor() {
this.isInitialized = false;
this.effectsActive = true;
this.dataStreams = [];
this.glitchElements = [];
this.init();
}
init() {
if (this.isInitialized) return;
console.log('🤖 Initializing Holographic Effects System...');
// Initialize effects when DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => this.setupEffects());
} else {
this.setupEffects();
}
this.isInitialized = true;
}
setupEffects() {
// this.createDataStreamBackground(); // Removed
// this.setupHolographicBorders(); // Removed
this.initializeGlitchEffects();
this.createParticleSystem();
this.setupTechScanlines();
this.initializeRobotAnimations();
// this.setupInteractiveHovers(); // Removed
}
// Create animated data streams in background
createDataStreamBackground() {
const dataStreamContainer = document.createElement('div');
dataStreamContainer.className = 'data-stream-container';
dataStreamContainer.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
pointer-events: none;
z-index: -1;
overflow: hidden;
`;
// Create multiple data streams
for (let i = 0; i < 8; i++) {
setTimeout(() => {
this.createDataStream(dataStreamContainer, i);
}, i * 500);
}
document.body.appendChild(dataStreamContainer);
}
createDataStream(container, index) {
const stream = document.createElement('div');
stream.className = 'data-stream';
const left = Math.random() * 100;
const animationDuration = 3 + Math.random() * 4;
const delay = Math.random() * 2;
stream.style.cssText = `
position: absolute;
left: ${left}%;
width: 2px;
height: 100px;
background: linear-gradient(to bottom,
transparent 0%,
rgba(0, 255, 255, 0.8) 20%,
rgba(138, 43, 226, 0.6) 80%,
transparent 100%);
animation: dataStreamFlow ${animationDuration}s linear infinite;
animation-delay: ${delay}s;
box-shadow: 0 0 10px rgba(0, 255, 255, 0.5);
`;
// Add random tech symbols
const symbols = ['01', '10', '11', '00', 'Ω', '∆', '∇', '◊', '◈'];
const symbol = document.createElement('span');
symbol.textContent = symbols[Math.floor(Math.random() * symbols.length)];
symbol.style.cssText = `
position: absolute;
top: 10px;
left: -5px;
color: rgba(0, 255, 255, 0.8);
font-size: 8px;
font-family: 'Courier New', monospace;
text-shadow: 0 0 5px rgba(0, 255, 255, 0.8);
`;
stream.appendChild(symbol);
container.appendChild(stream);
// Remove and recreate after animation
setTimeout(() => {
if (stream.parentNode) {
stream.parentNode.removeChild(stream);
}
if (this.effectsActive) {
this.createDataStream(container, index);
}
}, (animationDuration + delay) * 1000);
}
// Setup holographic borders for cards
setupHolographicBorders() {
const style = document.createElement('style');
style.textContent = `
@keyframes dataStreamFlow {
0% {
transform: translateY(-100px);
opacity: 0;
}
10% { opacity: 1; }
90% { opacity: 1; }
100% {
transform: translateY(calc(100vh + 100px));
opacity: 0;
}
}
@keyframes holographicBorderShift {
0% { border-image-source: linear-gradient(45deg, #8A2BE2, #9932CC, #DA70D6, #FF00FF); }
25% { border-image-source: linear-gradient(90deg, #00FFFF, #8A2BE2, #9932CC, #DA70D6); }
50% { border-image-source: linear-gradient(135deg, #FF00FF, #00FFFF, #8A2BE2, #9932CC); }
75% { border-image-source: linear-gradient(180deg, #DA70D6, #FF00FF, #00FFFF, #8A2BE2); }
100% { border-image-source: linear-gradient(45deg, #8A2BE2, #9932CC, #DA70D6, #FF00FF); }
}
`;
document.head.appendChild(style);
// Apply to cards
setTimeout(() => {
const cards = document.querySelectorAll('.rz-card, .card, .mobile-card');
cards.forEach(card => {
card.style.borderImage = 'linear-gradient(45deg, #8A2BE2, #9932CC, #DA70D6, #FF00FF) 1';
card.style.animation = 'holographicBorderShift 4s ease-in-out infinite';
});
}, 100);
}
// Initialize glitch effects on hover
initializeGlitchEffects() {
const glitchStyle = document.createElement('style');
glitchStyle.textContent = `
.tech-glitch {
position: relative;
}
.tech-glitch::before,
.tech-glitch::after {
content: attr(data-text);
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
opacity: 0;
transition: opacity 0.3s;
}
.tech-glitch::before {
color: #FF00FF;
animation: glitch1 0.5s infinite;
clip: rect(0, 900px, 0, 0);
}
.tech-glitch::after {
color: #00FFFF;
animation: glitch2 0.5s infinite;
clip: rect(0, 900px, 0, 0);
}
.tech-glitch:hover::before,
.tech-glitch:hover::after {
opacity: 0.8;
}
@keyframes glitch1 {
0% { clip: rect(42px, 9999px, 44px, 0); }
20% { clip: rect(12px, 9999px, 59px, 0); }
40% { clip: rect(63px, 9999px, 34px, 0); }
60% { clip: rect(18px, 9999px, 76px, 0); }
80% { clip: rect(54px, 9999px, 91px, 0); }
100% { clip: rect(25px, 9999px, 38px, 0); }
}
@keyframes glitch2 {
0% { clip: rect(65px, 9999px, 23px, 0); }
20% { clip: rect(87px, 9999px, 45px, 0); }
40% { clip: rect(29px, 9999px, 78px, 0); }
60% { clip: rect(52px, 9999px, 31px, 0); }
80% { clip: rect(15px, 9999px, 89px, 0); }
100% { clip: rect(73px, 9999px, 16px, 0); }
}
`;
document.head.appendChild(glitchStyle);
// Apply to headings
setTimeout(() => {
const headings = document.querySelectorAll('h1, h2, h3, h4, h5, h6');
headings.forEach(heading => {
heading.classList.add('tech-glitch');
heading.setAttribute('data-text', heading.textContent);
});
}, 200);
}
// Create particle system
createParticleSystem() {
const particleContainer = document.createElement('div');
particleContainer.className = 'particle-system';
particleContainer.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
pointer-events: none;
z-index: -2;
overflow: hidden;
`;
// Create floating particles
for (let i = 0; i < 20; i++) {
setTimeout(() => {
this.createParticle(particleContainer);
}, i * 100);
}
document.body.appendChild(particleContainer);
}
createParticle(container) {
const particle = document.createElement('div');
const size = Math.random() * 3 + 1;
const x = Math.random() * window.innerWidth;
const y = Math.random() * window.innerHeight;
const duration = Math.random() * 20 + 10;
particle.style.cssText = `
position: absolute;
width: ${size}px;
height: ${size}px;
background: radial-gradient(circle, rgba(138, 43, 226, 0.8) 0%, transparent 70%);
left: ${x}px;
top: ${y}px;
border-radius: 50%;
animation: particleFloat ${duration}s linear infinite;
box-shadow: 0 0 ${size * 3}px rgba(138, 43, 226, 0.6);
`;
const floatDistance = Math.random() * 100 + 50;
const angle = Math.random() * 360;
particle.style.setProperty('--float-x', `${Math.cos(angle * Math.PI / 180) * floatDistance}px`);
particle.style.setProperty('--float-y', `${Math.sin(angle * Math.PI / 180) * floatDistance}px`);
container.appendChild(particle);
// Remove after animation
setTimeout(() => {
if (particle.parentNode) {
particle.parentNode.removeChild(particle);
}
if (this.effectsActive) {
this.createParticle(container);
}
}, duration * 1000);
}
// Setup tech scanlines
setupTechScanlines() {
const scanlineStyle = document.createElement('style');
scanlineStyle.textContent = `
@keyframes particleFloat {
0%, 100% {
transform: translate(0, 0) scale(1);
opacity: 0.3;
}
50% {
transform: translate(var(--float-x), var(--float-y)) scale(1.5);
opacity: 0.8;
}
}
.tech-scanlines::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: repeating-linear-gradient(
0deg,
transparent,
transparent 2px,
rgba(0, 255, 255, 0.03) 2px,
rgba(0, 255, 255, 0.03) 4px
);
pointer-events: none;
animation: scanlineMove 2s linear infinite;
}
@keyframes scanlineMove {
0% { transform: translateY(0); }
100% { transform: translateY(4px); }
}
`;
document.head.appendChild(scanlineStyle);
// Apply to main containers
setTimeout(() => {
const containers = document.querySelectorAll('.container, .container-fluid, .main-content');
containers.forEach(container => {
container.classList.add('tech-scanlines');
container.style.position = 'relative';
});
}, 300);
}
// Initialize robot-style animations
initializeRobotAnimations() {
const robotStyle = document.createElement('style');
robotStyle.textContent = `
.robot-pulse {
animation: robotPulse 3s ease-in-out infinite;
}
.robot-scan {
position: relative;
overflow: hidden;
}
.robot-scan::after {
content: '';
position: absolute;
top: -50%;
left: -50%;
width: 200%;
height: 200%;
background: linear-gradient(45deg,
transparent 40%,
rgba(0, 255, 255, 0.1) 50%,
transparent 60%);
animation: robotScan 4s ease-in-out infinite;
}
@keyframes robotPulse {
0%, 100% {
transform: scale(1);
box-shadow: 0 0 20px rgba(138, 43, 226, 0.3);
}
50% {
transform: scale(1.02);
box-shadow: 0 0 40px rgba(138, 43, 226, 0.8);
}
}
@keyframes robotScan {
0% { transform: translate(-100%, -100%) rotate(0deg); }
100% { transform: translate(100%, 100%) rotate(360deg); }
}
.tech-loading {
position: relative;
}
.tech-loading::before {
content: '';
position: absolute;
top: -2px;
left: -2px;
right: -2px;
bottom: -2px;
background: linear-gradient(45deg, #8A2BE2, #00FFFF, #8A2BE2);
background-size: 200% 200%;
border-radius: inherit;
z-index: -1;
animation: techLoadingPulse 2s ease-in-out infinite;
}
@keyframes techLoadingPulse {
0%, 100% {
opacity: 0.3;
background-position: 0% 50%;
}
50% {
opacity: 0.8;
background-position: 100% 50%;
}
}
`;
document.head.appendChild(robotStyle);
// Apply to cards only (removed button glow effects)
setTimeout(() => {
// const buttons = document.querySelectorAll('.rz-button, .btn');
// buttons.forEach(button => {
// button.classList.add('robot-pulse');
// });
const cards = document.querySelectorAll('.rz-card, .card');
cards.forEach(card => {
card.classList.add('robot-scan');
});
}, 400);
}
// Setup interactive hover effects
setupInteractiveHovers() {
setTimeout(() => {
// Add hover effects to interactive elements
const interactiveElements = document.querySelectorAll('button, .rz-button, .btn, a, .card');
interactiveElements.forEach(element => {
element.addEventListener('mouseenter', (e) => {
this.createHoverEffect(e.target);
});
element.addEventListener('mouseleave', (e) => {
this.removeHoverEffect(e.target);
});
});
}, 500);
}
createHoverEffect(element) {
// Create ripple effect
const ripple = document.createElement('div');
ripple.style.cssText = `
position: absolute;
border-radius: 50%;
background: radial-gradient(circle, rgba(0, 255, 255, 0.6) 0%, transparent 70%);
transform: scale(0);
animation: rippleEffect 0.6s linear;
pointer-events: none;
z-index: 1000;
`;
const rect = element.getBoundingClientRect();
const size = Math.max(rect.width, rect.height);
ripple.style.width = ripple.style.height = size + 'px';
ripple.style.left = (rect.left + rect.width / 2 - size / 2) + 'px';
ripple.style.top = (rect.top + rect.height / 2 - size / 2) + 'px';
document.body.appendChild(ripple);
// Remove after animation
setTimeout(() => {
if (ripple.parentNode) {
ripple.parentNode.removeChild(ripple);
}
}, 600);
// Add tech glow to element
element.style.transition = 'all 0.3s ease';
element.style.boxShadow = '0 0 30px rgba(0, 255, 255, 0.8), inset 0 0 20px rgba(138, 43, 226, 0.4)';
element.style.transform = 'scale(1.05)';
if (!document.getElementById('ripple-style')) {
const rippleStyle = document.createElement('style');
rippleStyle.id = 'ripple-style';
rippleStyle.textContent = `
@keyframes rippleEffect {
to {
transform: scale(4);
opacity: 0;
}
}
`;
document.head.appendChild(rippleStyle);
}
}
removeHoverEffect(element) {
element.style.boxShadow = '';
element.style.transform = '';
}
// Toggle effects on/off
toggleEffects() {
this.effectsActive = !this.effectsActive;
if (!this.effectsActive) {
// Remove all effect containers
const effectContainers = document.querySelectorAll('.data-stream-container, .particle-system');
effectContainers.forEach(container => {
if (container.parentNode) {
container.parentNode.removeChild(container);
}
});
} else {
// Restart effects
this.setupEffects();
}
console.log(`🤖 Holographic effects ${this.effectsActive ? 'enabled' : 'disabled'}`);
}
// Performance monitoring
checkPerformance() {
const start = performance.now();
setTimeout(() => {
const delta = performance.now() - start;
if (delta > 50) { // If frame time is too long
console.warn('🤖 Performance warning: Consider reducing effects');
}
}, 0);
}
}
// Initialize the system
const holographicSystem = new HolographicEffectsSystem();
// Expose control functions globally
window.toggleHolographicEffects = () => holographicSystem.toggleEffects();
window.holographicSystem = holographicSystem;
// Add keyboard shortcut (Ctrl+H) to toggle effects
document.addEventListener('keydown', (e) => {
if (e.ctrlKey && e.key === 'h') {
e.preventDefault();
holographicSystem.toggleEffects();
}
});
console.log('🤖 Holographic Tech System loaded! Press Ctrl+H to toggle effects.');

View File

@@ -0,0 +1,221 @@
// Modern Mobile Enhancements
// Clean, simple mobile-friendly functionality
class ModernMobile {
constructor() {
this.init();
}
init() {
this.setupMobileTableLabels();
this.setupResponsiveNavigation();
this.setupFormEnhancements();
this.setupSmoothInteractions();
}
// Add data labels for mobile table stacking
setupMobileTableLabels() {
const tables = document.querySelectorAll('.table');
tables.forEach(table => {
const headers = Array.from(table.querySelectorAll('thead th')).map(th => th.textContent.trim());
const rows = table.querySelectorAll('tbody tr');
rows.forEach(row => {
const cells = row.querySelectorAll('td');
cells.forEach((cell, index) => {
if (headers[index]) {
cell.setAttribute('data-label', headers[index]);
}
});
});
});
}
// Enhanced mobile navigation
setupResponsiveNavigation() {
const navbar = document.querySelector('.navbar');
const toggler = document.querySelector('.navbar-toggler');
const collapse = document.querySelector('.navbar-collapse');
if (toggler && collapse) {
// Smooth collapse animation
toggler.addEventListener('click', () => {
collapse.classList.toggle('show');
});
// Close menu when clicking outside
document.addEventListener('click', (e) => {
if (!navbar.contains(e.target) && collapse.classList.contains('show')) {
collapse.classList.remove('show');
}
});
}
}
// Form enhancements for better mobile UX
setupFormEnhancements() {
// Auto-focus first input on desktop
if (window.innerWidth > 768) {
const firstInput = document.querySelector('.form-control:not([readonly]):not([disabled])');
if (firstInput) {
firstInput.focus();
}
}
// Enhanced form validation feedback
const forms = document.querySelectorAll('form');
forms.forEach(form => {
form.addEventListener('submit', (e) => {
// Skip validation for file upload forms
if (form.enctype === 'multipart/form-data') {
console.log('Mobile: Skipping validation for file upload form');
return;
}
const invalidInputs = form.querySelectorAll(':invalid');
if (invalidInputs.length > 0) {
e.preventDefault();
console.log('Mobile: Form validation failed, focusing on first invalid input');
invalidInputs[0].focus();
invalidInputs[0].scrollIntoView({ behavior: 'smooth', block: 'center' });
}
});
});
// Floating labels effect
const inputs = document.querySelectorAll('.form-control, .form-select');
inputs.forEach(input => {
if (input.value) {
input.parentElement.classList.add('has-value');
}
input.addEventListener('blur', () => {
if (input.value) {
input.parentElement.classList.add('has-value');
} else {
input.parentElement.classList.remove('has-value');
}
});
});
}
// Smooth interactions and feedback
setupSmoothInteractions() {
// Button click feedback
const buttons = document.querySelectorAll('.btn');
buttons.forEach(button => {
button.addEventListener('click', (e) => {
if (!button.disabled) {
button.style.transform = 'scale(0.95)';
setTimeout(() => {
button.style.transform = '';
}, 100);
}
});
});
// Card hover enhancement
const cards = document.querySelectorAll('.card');
cards.forEach(card => {
card.addEventListener('mouseenter', () => {
card.style.transform = 'translateY(-2px)';
});
card.addEventListener('mouseleave', () => {
card.style.transform = '';
});
});
// Smooth scroll for anchor links
const anchorLinks = document.querySelectorAll('a[href^="#"]');
anchorLinks.forEach(link => {
link.addEventListener('click', (e) => {
e.preventDefault();
const target = document.querySelector(link.getAttribute('href'));
if (target) {
target.scrollIntoView({ behavior: 'smooth' });
}
});
});
}
// Show toast notification
showToast(message, type = 'info') {
const toastContainer = document.getElementById('toast-container') || this.createToastContainer();
const toast = document.createElement('div');
toast.className = `alert alert-${type} alert-dismissible fade show`;
toast.style.cssText = `
margin-bottom: 0.5rem;
animation: slideInRight 0.3s ease;
`;
toast.innerHTML = `
${message}
<button type="button" class="btn-close" aria-label="Close"></button>
`;
toastContainer.appendChild(toast);
// Auto-remove after 4 seconds
setTimeout(() => {
toast.classList.add('fade');
setTimeout(() => {
if (toast.parentNode) {
toast.parentNode.removeChild(toast);
}
}, 150);
}, 4000);
// Manual close
const closeBtn = toast.querySelector('.btn-close');
closeBtn.addEventListener('click', () => {
toast.classList.add('fade');
setTimeout(() => {
if (toast.parentNode) {
toast.parentNode.removeChild(toast);
}
}, 150);
});
}
createToastContainer() {
const container = document.createElement('div');
container.id = 'toast-container';
container.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
z-index: 1050;
max-width: 300px;
`;
document.body.appendChild(container);
return container;
}
}
// CSS for toast animations
const style = document.createElement('style');
style.textContent = `
@keyframes slideInRight {
from {
transform: translateX(100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
.fade {
opacity: 0 !important;
transition: opacity 0.15s linear !important;
}
`;
document.head.appendChild(style);
// Initialize
const modernMobile = new ModernMobile();
window.modernMobile = modernMobile;

View File

@@ -0,0 +1,317 @@
// Enhanced notification management for LittleShop Admin
// Handles real-time order notifications and admin alerts
class AdminNotificationManager {
constructor() {
this.isSetupComplete = false;
this.notificationQueue = [];
this.init();
}
async init() {
console.log('Admin Notifications: Initializing...');
// Wait for PWA manager to be ready
if (window.pwaManager) {
await this.setupOrderNotifications();
} else {
// Wait for PWA manager to load
setTimeout(() => this.init(), 1000);
}
}
async setupOrderNotifications() {
try {
// Ensure push notifications are enabled
if (!window.pwaManager.pushSubscription) {
console.log('Admin Notifications: Setting up push notifications...');
// Show admin-specific notification prompt
this.showAdminNotificationPrompt();
return;
}
this.isSetupComplete = true;
this.addNotificationStatusIndicator();
this.setupTestNotificationButton();
console.log('Admin Notifications: Setup complete');
} catch (error) {
console.error('Admin Notifications: Setup failed:', error);
}
}
showAdminNotificationPrompt() {
// Check if prompt already exists
if (document.getElementById('admin-notification-prompt')) {
return;
}
const promptDiv = document.createElement('div');
promptDiv.id = 'admin-notification-prompt';
promptDiv.className = 'alert alert-warning alert-dismissible position-fixed';
promptDiv.style.cssText = `
top: 80px;
right: 20px;
z-index: 1055;
max-width: 400px;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
`;
promptDiv.innerHTML = `
<div class="d-flex align-items-center">
<i class="fas fa-bell-slash text-warning me-3 fa-2x"></i>
<div class="flex-grow-1">
<h6 class="alert-heading mb-1">Enable Order Notifications</h6>
<p class="mb-2">Get instant alerts for new orders, payments, and status changes.</p>
<div class="d-flex gap-2">
<button type="button" class="btn btn-warning btn-sm" id="enable-admin-notifications">
<i class="fas fa-bell me-1"></i>Enable Now
</button>
<button type="button" class="btn btn-outline-secondary btn-sm" id="remind-later">
Later
</button>
</div>
</div>
</div>
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
`;
document.body.appendChild(promptDiv);
// Add event listeners
document.getElementById('enable-admin-notifications').addEventListener('click', async () => {
try {
await this.enableNotifications();
promptDiv.remove();
} catch (error) {
console.error('Failed to enable notifications:', error);
this.showNotificationError('Failed to enable notifications. Please try again.');
}
});
document.getElementById('remind-later').addEventListener('click', () => {
promptDiv.remove();
// Set reminder for 1 hour
setTimeout(() => this.showAdminNotificationPrompt(), 60 * 60 * 1000);
});
}
async enableNotifications() {
const button = document.getElementById('enable-admin-notifications');
const originalText = button.innerHTML;
button.disabled = true;
button.innerHTML = '<i class="fas fa-spinner fa-spin me-1"></i>Enabling...';
try {
await window.pwaManager.subscribeToPushNotifications();
// Show success message
this.showNotificationSuccess('✅ Order notifications enabled successfully!');
// Complete setup
await this.setupOrderNotifications();
} finally {
button.disabled = false;
button.innerHTML = originalText;
}
}
addNotificationStatusIndicator() {
// Add status indicator to admin header/navbar
const navbar = document.querySelector('.navbar-nav');
if (!navbar || document.getElementById('notification-status')) {
return;
}
const statusItem = document.createElement('li');
statusItem.className = 'nav-item dropdown';
statusItem.innerHTML = `
<a class="nav-link dropdown-toggle" href="#" id="notification-status" role="button" data-bs-toggle="dropdown">
<i class="fas fa-bell text-success"></i>
<span class="d-none d-md-inline ms-1">Notifications</span>
<span id="notification-badge" class="badge bg-danger ms-1" style="display: none;">0</span>
</a>
<ul class="dropdown-menu dropdown-menu-end">
<li><h6 class="dropdown-header">Notification Settings</h6></li>
<li><a class="dropdown-item" href="#" id="test-notification">
<i class="fas fa-vial me-2"></i>Send Test Notification
</a></li>
<li><a class="dropdown-item" href="#" id="notification-history">
<i class="fas fa-history me-2"></i>Recent Notifications
</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item text-danger" href="#" id="disable-notifications">
<i class="fas fa-bell-slash me-2"></i>Disable Notifications
</a></li>
</ul>
`;
navbar.appendChild(statusItem);
// Add event listeners
document.getElementById('test-notification').addEventListener('click', (e) => {
e.preventDefault();
this.sendTestNotification();
});
document.getElementById('disable-notifications').addEventListener('click', (e) => {
e.preventDefault();
this.disableNotifications();
});
}
setupTestNotificationButton() {
// Add test button to dashboard if we're on the dashboard page
const dashboardContent = document.querySelector('.dashboard-content, .admin-dashboard');
if (!dashboardContent) {
return;
}
const testButton = document.createElement('button');
testButton.className = 'btn btn-outline-primary btn-sm me-2';
testButton.innerHTML = '<i class="fas fa-bell me-1"></i>Test Notification';
testButton.onclick = () => this.sendTestNotification();
// Find a good place to add it (e.g., near page title)
const pageTitle = document.querySelector('h1, .page-title');
if (pageTitle) {
pageTitle.parentNode.insertBefore(testButton, pageTitle.nextSibling);
}
}
async sendTestNotification() {
try {
const response = await fetch('/api/push/test', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
title: '🧪 Test Notification',
body: 'LittleShop admin notifications are working perfectly!'
}),
credentials: 'same-origin'
});
if (response.ok) {
this.showNotificationSuccess('Test notification sent!');
} else {
throw new Error('Failed to send test notification');
}
} catch (error) {
console.error('Test notification failed:', error);
this.showNotificationError('Failed to send test notification');
}
}
async disableNotifications() {
if (confirm('Are you sure you want to disable order notifications?')) {
try {
await window.pwaManager.unsubscribeFromPushNotifications();
// Remove status indicator
const statusElement = document.getElementById('notification-status');
if (statusElement) {
statusElement.closest('.nav-item').remove();
}
this.showNotificationSuccess('Notifications disabled');
// Reset setup status
this.isSetupComplete = false;
} catch (error) {
console.error('Failed to disable notifications:', error);
this.showNotificationError('Failed to disable notifications');
}
}
}
showNotificationSuccess(message) {
this.showToast(message, 'success');
}
showNotificationError(message) {
this.showToast(message, 'danger');
}
showToast(message, type = 'info') {
const toast = document.createElement('div');
toast.className = `alert alert-${type} alert-dismissible position-fixed`;
toast.style.cssText = `
top: 20px;
right: 20px;
z-index: 1060;
min-width: 300px;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
`;
toast.innerHTML = `
${message}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
`;
document.body.appendChild(toast);
// Auto-remove after 5 seconds
setTimeout(() => {
if (toast.parentNode) {
toast.remove();
}
}, 5000);
}
// Handle incoming notifications (if using WebSocket/SignalR in future)
handleOrderNotification(data) {
if (!this.isSetupComplete) {
this.notificationQueue.push(data);
return;
}
// Update notification badge
this.updateNotificationBadge();
// Show browser notification if page is not visible
if (document.hidden && window.pwaManager) {
window.pwaManager.showNotification(data.title, {
body: data.body,
icon: '/icons/icon-192x192.png',
badge: '/icons/icon-72x72.png',
tag: 'order-notification',
requireInteraction: true,
actions: [
{ action: 'view', title: 'View Order' },
{ action: 'dismiss', title: 'Dismiss' }
]
});
}
}
updateNotificationBadge(count = null) {
const badge = document.getElementById('notification-badge');
if (!badge) return;
if (count === null) {
// Get current count and increment
const currentCount = parseInt(badge.textContent) || 0;
count = currentCount + 1;
}
if (count > 0) {
badge.textContent = count;
badge.style.display = 'inline';
} else {
badge.style.display = 'none';
}
}
}
// Initialize admin notification manager
document.addEventListener('DOMContentLoaded', () => {
window.adminNotificationManager = new AdminNotificationManager();
});
// Export for global access
window.AdminNotificationManager = AdminNotificationManager;

View File

@@ -0,0 +1,537 @@
// Progressive Web App functionality
// Handles service worker registration and PWA features
class PWAManager {
constructor() {
this.swRegistration = null;
this.vapidPublicKey = null;
this.pushSubscription = null;
this.init();
}
async init() {
console.log('PWA: Initializing PWA Manager...');
if ('serviceWorker' in navigator) {
try {
this.swRegistration = await navigator.serviceWorker.register('/sw.js');
console.log('SW: Service Worker registered successfully');
// Listen for updates
this.swRegistration.addEventListener('updatefound', () => {
console.log('SW: New version available');
this.showUpdateNotification();
});
} catch (error) {
console.log('SW: Service Worker registration failed:', error);
}
}
// Setup PWA install prompt
this.setupInstallPrompt();
// Setup notifications (if enabled)
this.setupNotifications();
// Setup push notifications
this.setupPushNotifications();
// Show manual install option after 5 seconds if no prompt appeared and app not installed
setTimeout(() => {
if (!document.getElementById('pwa-install-btn') && !this.isInstalled()) {
console.log('PWA: No install prompt appeared, showing manual install guide');
this.showManualInstallButton();
}
}, 5000);
}
setupInstallPrompt() {
let deferredPrompt;
window.addEventListener('beforeinstallprompt', (e) => {
console.log('PWA: beforeinstallprompt event fired');
// Prevent Chrome 67 and earlier from automatically showing the prompt
e.preventDefault();
deferredPrompt = e;
// Show custom install button
this.showInstallButton(deferredPrompt);
});
window.addEventListener('appinstalled', () => {
console.log('PWA: App was installed');
this.hideInstallButton();
});
// Debug: Check if app is already installed
if (this.isInstalled()) {
console.log('PWA: App is already installed (standalone mode)');
// Hide any existing install buttons
this.hideInstallButton();
} else {
console.log('PWA: App is not installed, waiting for install prompt...');
console.log('PWA: Current URL:', window.location.href);
console.log('PWA: Display mode:', window.matchMedia('(display-mode: standalone)').matches ? 'standalone' : 'browser');
console.log('PWA: User agent:', navigator.userAgent);
}
// Periodically check if app becomes installed (for cases where user installs via browser menu)
setInterval(() => {
if (this.isInstalled()) {
this.hideInstallButton();
}
}, 2000);
}
showInstallButton(deferredPrompt) {
// Don't show install button if app is already installed
if (this.isInstalled()) {
console.log('PWA: App already installed, skipping install button');
return;
}
// Create install button
const installBtn = document.createElement('button');
installBtn.id = 'pwa-install-btn';
installBtn.className = 'btn btn-primary btn-sm';
installBtn.innerHTML = '<i class="fas fa-download"></i> Install App';
installBtn.style.cssText = `
position: fixed;
bottom: 20px;
right: 20px;
z-index: 1000;
box-shadow: 0 4px 12px rgba(37, 99, 235, 0.3);
`;
installBtn.addEventListener('click', async () => {
if (deferredPrompt) {
deferredPrompt.prompt();
const { outcome } = await deferredPrompt.userChoice;
console.log('PWA: User response to install prompt:', outcome);
deferredPrompt = null;
this.hideInstallButton();
}
});
document.body.appendChild(installBtn);
}
hideInstallButton() {
const installBtn = document.getElementById('pwa-install-btn');
if (installBtn) {
installBtn.remove();
}
}
showUpdateNotification() {
// Show update available notification
const notification = document.createElement('div');
notification.className = 'alert alert-info alert-dismissible';
notification.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
z-index: 1050;
max-width: 300px;
`;
notification.innerHTML = `
<strong>Update Available!</strong><br>
A new version of the app is ready.
<button type="button" class="btn btn-sm btn-outline-info ms-2" id="update-btn">
Update Now
</button>
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
`;
document.body.appendChild(notification);
// Handle update
document.getElementById('update-btn').addEventListener('click', () => {
if (this.swRegistration && this.swRegistration.waiting) {
this.swRegistration.waiting.postMessage({ type: 'SKIP_WAITING' });
window.location.reload();
}
});
}
async setupNotifications() {
// Check if notifications are supported and get permission
if ('Notification' in window) {
const permission = await this.requestNotificationPermission();
console.log('Notifications permission:', permission);
}
}
async requestNotificationPermission() {
if (Notification.permission === 'default') {
// Only request permission when user interacts with a relevant feature
// For now, just return the current status
return Notification.permission;
}
return Notification.permission;
}
// Show notification (if permission granted)
showNotification(title, options = {}) {
if (Notification.permission === 'granted') {
const notification = new Notification(title, {
icon: '/icons/icon-192x192.png',
badge: '/icons/icon-72x72.png',
tag: 'littleshop-admin',
...options
});
// Auto-close after 5 seconds
setTimeout(() => {
notification.close();
}, 5000);
return notification;
}
}
// Show manual install button for browsers that don't auto-prompt
showManualInstallButton() {
// Don't show install button if app is already installed
if (this.isInstalled()) {
console.log('PWA: App already installed, skipping manual install button');
return;
}
console.log('PWA: Showing manual install button');
const installBtn = document.createElement('button');
installBtn.id = 'pwa-install-btn';
installBtn.className = 'btn btn-primary btn-sm';
installBtn.innerHTML = '<i class="fas fa-mobile-alt"></i> Install as App';
installBtn.style.cssText = `
position: fixed;
bottom: 20px;
right: 20px;
z-index: 1000;
box-shadow: 0 4px 12px rgba(37, 99, 235, 0.3);
`;
installBtn.addEventListener('click', () => {
const isChrome = navigator.userAgent.includes('Chrome');
const isEdge = navigator.userAgent.includes('Edge');
const isFirefox = navigator.userAgent.includes('Firefox');
let instructions = 'To install this app:\\n\\n';
if (isChrome || isEdge) {
instructions += '1. Look for the install icon (⬇️) in the address bar\\n';
instructions += '2. Or click the browser menu (⋮) → "Install LittleShop Admin"\\n';
instructions += '3. Or check if there\'s an "Install app" option in the browser menu';
} else if (isFirefox) {
instructions += '1. Firefox doesn\'t support PWA installation yet\\n';
instructions += '2. You can bookmark this page for easy access\\n';
instructions += '3. Or use Chrome/Edge for the full PWA experience';
} else {
instructions += '1. Look for an install or "Add to Home Screen" option\\n';
instructions += '2. Check your browser menu for app installation\\n';
instructions += '3. Or bookmark this page for quick access';
}
alert(instructions);
});
document.body.appendChild(installBtn);
}
// Check if app is installed
isInstalled() {
return window.matchMedia('(display-mode: standalone)').matches ||
window.navigator.standalone === true;
}
// Setup push notifications
async setupPushNotifications() {
if (!('serviceWorker' in navigator) || !('PushManager' in window)) {
console.log('PWA: Push notifications not supported');
return;
}
try {
// Get VAPID public key from server
await this.getVapidPublicKey();
// Check if user is already subscribed
await this.checkPushSubscription();
// Simple logic: only show prompt if user is not subscribed
if (!this.pushSubscription) {
// Check if we've already asked this session
if (!sessionStorage.getItem('pushNotificationPromptShown')) {
this.showPushNotificationSetup();
sessionStorage.setItem('pushNotificationPromptShown', 'true');
}
} else {
console.log('PWA: User already subscribed to push notifications');
}
} catch (error) {
console.error('PWA: Failed to setup push notifications:', error);
}
}
async getVapidPublicKey() {
try {
const response = await fetch('/api/push/vapid-key');
if (response.ok) {
const data = await response.json();
this.vapidPublicKey = data.publicKey;
console.log('PWA: VAPID public key retrieved');
} else {
throw new Error('Failed to get VAPID public key');
}
} catch (error) {
console.error('PWA: Error getting VAPID public key:', error);
throw error;
}
}
async checkPushSubscription() {
if (!this.swRegistration) {
return;
}
try {
this.pushSubscription = await this.swRegistration.pushManager.getSubscription();
if (this.pushSubscription) {
console.log('PWA: User has active push subscription');
} else {
console.log('PWA: User is not subscribed to push notifications');
}
} catch (error) {
console.error('PWA: Error checking push subscription:', error);
}
}
async subscribeToPushNotifications() {
if (!this.swRegistration || !this.vapidPublicKey) {
throw new Error('Service worker or VAPID key not available');
}
try {
// Check current permission status
if (Notification.permission === 'denied') {
throw new Error('Notification permission was denied. Please enable notifications in your browser settings.');
}
// Request notification permission if not already granted
let permission = Notification.permission;
if (permission === 'default') {
permission = await Notification.requestPermission();
}
if (permission !== 'granted') {
throw new Error('Notification permission is required for push notifications. Please allow notifications and try again.');
}
// Subscribe to push notifications
const subscription = await this.swRegistration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: this.urlBase64ToUint8Array(this.vapidPublicKey)
});
// Send subscription to server with timeout
console.log('PWA: Sending subscription to server...');
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 10000); // 10 second timeout
const response = await fetch('/api/push/subscribe', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
endpoint: subscription.endpoint,
p256dh: btoa(String.fromCharCode(...new Uint8Array(subscription.getKey('p256dh')))),
auth: btoa(String.fromCharCode(...new Uint8Array(subscription.getKey('auth'))))
}),
credentials: 'same-origin',
signal: controller.signal
});
clearTimeout(timeoutId);
console.log('PWA: Server response received:', response.status, response.statusText);
if (response.ok) {
this.pushSubscription = subscription;
console.log('PWA: Successfully subscribed to push notifications');
this.hidePushNotificationSetup();
return true;
} else {
throw new Error('Failed to save push subscription to server');
}
} catch (error) {
console.error('PWA: Failed to subscribe to push notifications:', error);
throw error;
}
}
async unsubscribeFromPushNotifications() {
if (!this.pushSubscription) {
return true;
}
try {
// Unsubscribe from push manager
await this.pushSubscription.unsubscribe();
// Notify server
await fetch('/api/push/unsubscribe', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
endpoint: this.pushSubscription.endpoint
}),
credentials: 'same-origin'
});
this.pushSubscription = null;
console.log('PWA: Successfully unsubscribed from push notifications');
return true;
} catch (error) {
console.error('PWA: Failed to unsubscribe from push notifications:', error);
throw error;
}
}
showPushNotificationSetup() {
// Check if setup UI already exists
if (document.getElementById('push-notification-setup')) {
return;
}
const setupDiv = document.createElement('div');
setupDiv.id = 'push-notification-setup';
setupDiv.className = 'alert alert-info alert-dismissible';
setupDiv.style.cssText = `
position: fixed;
top: 80px;
right: 20px;
z-index: 1050;
max-width: 350px;
`;
setupDiv.innerHTML = `
<div class="d-flex align-items-center">
<i class="fas fa-bell me-2"></i>
<div class="flex-grow-1">
<strong>Push Notifications</strong><br>
<small>Get notified of new orders and updates</small>
</div>
<div class="ms-2">
<button type="button" class="btn btn-sm btn-primary" id="subscribe-push-btn">Enable</button>
</div>
</div>
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
`;
document.body.appendChild(setupDiv);
// Add event listener for subscribe button
const subscribeBtn = document.getElementById('subscribe-push-btn');
if (subscribeBtn) {
subscribeBtn.addEventListener('click', async () => {
subscribeBtn.disabled = true;
subscribeBtn.innerHTML = '<i class="fas fa-spinner fa-spin me-1"></i>Subscribing...';
try {
// Add timeout to prevent infinite hanging
const subscriptionPromise = this.subscribeToPushNotifications();
const timeoutPromise = new Promise((_, reject) =>
setTimeout(() => reject(new Error('Push subscription timed out after 15 seconds. This may be due to network connectivity or browser push service issues.')), 15000)
);
await Promise.race([subscriptionPromise, timeoutPromise]);
this.showNotification('Push notifications enabled!', {
body: 'You will now receive notifications for new orders and updates.'
});
} catch (error) {
console.error('PWA: Push subscription failed:', error);
// Provide user-friendly error messages
let userMessage = error.message;
if (error.message.includes('permission')) {
userMessage = 'Please allow notifications when your browser asks, then try again.';
} else if (error.message.includes('timeout')) {
userMessage = 'Push notification setup timed out. This may be due to network or browser issues. Please try again or check your internet connection.';
}
alert('Failed to enable push notifications: ' + userMessage);
subscribeBtn.disabled = false;
subscribeBtn.innerHTML = 'Enable';
}
});
}
}
hidePushNotificationSetup() {
const setupDiv = document.getElementById('push-notification-setup');
if (setupDiv) {
setupDiv.remove();
console.log('PWA: Push notification setup hidden');
}
}
async sendTestNotification() {
try {
const response = await fetch('/api/push/test', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
title: 'Test Notification',
body: 'This is a test push notification from LittleShop Admin!'
}),
credentials: 'same-origin'
});
const result = await response.json();
if (response.ok) {
console.log('PWA: Test notification sent successfully');
return true;
} else {
throw new Error(result.error || 'Failed to send test notification');
}
} catch (error) {
console.error('PWA: Failed to send test notification:', error);
throw error;
}
}
// Helper function to convert VAPID key
urlBase64ToUint8Array(base64String) {
const padding = '='.repeat((4 - base64String.length % 4) % 4);
const base64 = (base64String + padding)
.replace(/\-/g, '+')
.replace(/_/g, '/');
const rawData = window.atob(base64);
const outputArray = new Uint8Array(rawData.length);
for (let i = 0; i < rawData.length; ++i) {
outputArray[i] = rawData.charCodeAt(i);
}
return outputArray;
}
}
// Initialize PWA Manager
const pwaManager = new PWAManager();
window.pwaManager = pwaManager;
// Expose notification functions globally
window.showNotification = (title, options) => pwaManager.showNotification(title, options);
window.sendTestPushNotification = () => pwaManager.sendTestNotification();
window.subscribeToPushNotifications = () => pwaManager.subscribeToPushNotifications();
window.unsubscribeFromPushNotifications = () => pwaManager.unsubscribeFromPushNotifications();

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,90 @@
{
"name": "LittleShop Admin",
"short_name": "LittleShop",
"description": "Modern e-commerce admin panel for managing orders, products, and customers",
"start_url": "/Admin/Dashboard",
"display": "standalone",
"orientation": "any",
"theme_color": "#2563eb",
"background_color": "#f9fafb",
"scope": "/Admin/",
"categories": ["business", "productivity", "shopping"],
"icons": [
{
"src": "/icons/icon-72x72.png",
"sizes": "72x72",
"type": "image/png",
"purpose": "any"
},
{
"src": "/icons/icon-96x96.png",
"sizes": "96x96",
"type": "image/png",
"purpose": "any"
},
{
"src": "/icons/icon-128x128.png",
"sizes": "128x128",
"type": "image/png",
"purpose": "any"
},
{
"src": "/icons/icon-144x144.png",
"sizes": "144x144",
"type": "image/png",
"purpose": "any"
},
{
"src": "/icons/icon-152x152.png",
"sizes": "152x152",
"type": "image/png",
"purpose": "any"
},
{
"src": "/icons/icon-192x192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any maskable"
},
{
"src": "/icons/icon-384x384.png",
"sizes": "384x384",
"type": "image/png",
"purpose": "any"
},
{
"src": "/icons/icon-512x512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "any maskable"
}
],
"shortcuts": [
{
"name": "Orders",
"short_name": "Orders",
"description": "View and manage orders",
"url": "/Admin/Orders",
"icons": [
{
"src": "/icons/icon-192x192.png",
"sizes": "192x192",
"type": "image/png"
}
]
},
{
"name": "Products",
"short_name": "Products",
"description": "Manage product catalog",
"url": "/Admin/Products",
"icons": [
{
"src": "/icons/icon-192x192.png",
"sizes": "192x192",
"type": "image/png"
}
]
}
]
}

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,192 @@
// LittleShop Admin - Service Worker
// Provides offline functionality and app-like experience
const CACHE_NAME = 'littleshop-admin-v2';
const urlsToCache = [
// Only cache static assets, not dynamic admin pages
'/lib/bootstrap/css/bootstrap.min.css',
'/lib/fontawesome/css/all.min.css',
'/lib/bootstrap-icons/bootstrap-icons.css',
'/css/modern-admin.css',
'/lib/jquery/jquery.min.js',
'/lib/bootstrap/js/bootstrap.bundle.min.js',
'/js/modern-mobile.js',
'/js/pwa.js',
'/lib/fontawesome/webfonts/fa-solid-900.woff2',
'/lib/fontawesome/webfonts/fa-brands-400.woff2'
];
// Install event - cache resources
self.addEventListener('install', (event) => {
console.log('SW: Installing service worker');
event.waitUntil(
caches.open(CACHE_NAME)
.then((cache) => {
console.log('SW: Caching app shell');
return cache.addAll(urlsToCache);
})
);
});
// Activate event - clean up old caches
self.addEventListener('activate', (event) => {
console.log('SW: Activating service worker');
event.waitUntil(
caches.keys().then((cacheNames) => {
return Promise.all(
cacheNames.map((cacheName) => {
if (cacheName !== CACHE_NAME) {
console.log('SW: Deleting old cache:', cacheName);
return caches.delete(cacheName);
}
})
);
})
);
});
// Push event - handle incoming push notifications
self.addEventListener('push', (event) => {
console.log('SW: Push notification received');
if (!event.data) {
console.log('SW: Push notification received without data');
return;
}
try {
const data = event.data.json();
console.log('SW: Push notification data:', data);
const notificationOptions = {
body: data.body || 'New notification',
icon: data.icon || '/icons/icon-192x192.png',
badge: data.badge || '/icons/icon-72x72.png',
tag: 'littleshop-notification',
requireInteraction: false,
silent: false,
data: data.data || {}
};
event.waitUntil(
self.registration.showNotification(data.title || 'LittleShop Admin', notificationOptions)
);
} catch (error) {
console.error('SW: Error parsing push notification data:', error);
// Show generic notification
event.waitUntil(
self.registration.showNotification('LittleShop Admin', {
body: 'New notification received',
icon: '/icons/icon-192x192.png',
badge: '/icons/icon-72x72.png',
tag: 'littleshop-notification'
})
);
}
});
// Notification click event - handle user interaction with notifications
self.addEventListener('notificationclick', (event) => {
console.log('SW: Notification clicked');
event.notification.close();
const notificationData = event.notification.data || {};
// Determine URL to open
let urlToOpen = '/Admin/Dashboard';
if (notificationData.url) {
urlToOpen = notificationData.url;
} else if (notificationData.orderId) {
urlToOpen = `/Admin/Orders/Details/${notificationData.orderId}`;
}
event.waitUntil(
clients.matchAll({ type: 'window', includeUncontrolled: true })
.then((clientList) => {
// Check if there's already a window/tab open with the target URL
for (const client of clientList) {
if (client.url.includes(urlToOpen) && 'focus' in client) {
return client.focus();
}
}
// If no window/tab is already open, open a new one
if (clients.openWindow) {
return clients.openWindow(urlToOpen);
}
})
);
});
// Fetch event - network-first for admin pages, cache-first for assets
self.addEventListener('fetch', (event) => {
// Only handle GET requests
if (event.request.method !== 'GET') {
return;
}
const url = new URL(event.request.url);
// Network-first strategy for admin pages (always fresh data)
if (url.pathname.startsWith('/Admin/')) {
event.respondWith(
fetch(event.request).catch(() => {
// If network fails, try cache as fallback
return caches.match(event.request);
})
);
return;
}
// Cache-first strategy for static assets
event.respondWith(
caches.match(event.request)
.then((response) => {
// Return cached version or fetch from network
return response || fetch(event.request).catch(() => {
// If both cache and network fail, show offline page for HTML requests
if (event.request.headers.get('accept').includes('text/html')) {
return new Response(`
<!DOCTYPE html>
<html>
<head>
<title>LittleShop Admin - Offline</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
body {
font-family: system-ui, sans-serif;
text-align: center;
padding: 2rem;
background: #f9fafb;
color: #374151;
}
.offline-icon { font-size: 4rem; margin-bottom: 1rem; color: #6b7280; }
h1 { color: #1f2937; margin-bottom: 0.5rem; }
p { color: #6b7280; margin-bottom: 2rem; }
.btn {
background: #2563eb;
color: white;
padding: 0.75rem 1.5rem;
border: none;
border-radius: 0.5rem;
text-decoration: none;
font-weight: 500;
}
</style>
</head>
<body>
<div class="offline-icon">📱</div>
<h1>You're offline</h1>
<p>Please check your internet connection and try again.</p>
<a href="/Admin/Dashboard" class="btn">Try Again</a>
</body>
</html>
`, {
headers: { 'Content-Type': 'text/html' }
});
}
});
})
);
});

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB