Temporarily disable anti-forgery validation for HTTPS proxy compatibility
415
publish/littleshop-hotfix/wwwroot/css/corporate-steel-theme.css
Normal 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;
|
||||
}
|
||||
444
publish/littleshop-hotfix/wwwroot/css/modern-admin.css
Normal 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;
|
||||
}
|
||||
}
|
||||
BIN
publish/littleshop-hotfix/wwwroot/css/modern-admin.css.br
Normal file
BIN
publish/littleshop-hotfix/wwwroot/css/modern-admin.css.gz
Normal file
534
publish/littleshop-hotfix/wwwroot/css/radzen-tech-theme.css
Normal 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;
|
||||
}
|
||||
BIN
publish/littleshop-hotfix/wwwroot/css/radzen-tech-theme.css.br
Normal file
BIN
publish/littleshop-hotfix/wwwroot/css/radzen-tech-theme.css.gz
Normal file
1
publish/littleshop-hotfix/wwwroot/favicon.ico
Normal file
@@ -0,0 +1 @@
|
||||
AAABAAEAEBAQAAEABAAoAQAAFgAAACgAAAAQAAAAIAAAAAEABAAAAAAAgAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
BIN
publish/littleshop-hotfix/wwwroot/favicon.ico.br
Normal file
BIN
publish/littleshop-hotfix/wwwroot/favicon.ico.gz
Normal file
BIN
publish/littleshop-hotfix/wwwroot/icons/icon-128x128.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
publish/littleshop-hotfix/wwwroot/icons/icon-144x144.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
publish/littleshop-hotfix/wwwroot/icons/icon-152x152.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
publish/littleshop-hotfix/wwwroot/icons/icon-192x192.png
Normal file
|
After Width: | Height: | Size: 4.0 KiB |
BIN
publish/littleshop-hotfix/wwwroot/icons/icon-384x384.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
publish/littleshop-hotfix/wwwroot/icons/icon-512x512.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
publish/littleshop-hotfix/wwwroot/icons/icon-72x72.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
publish/littleshop-hotfix/wwwroot/icons/icon-96x96.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
16
publish/littleshop-hotfix/wwwroot/icons/icon-placeholder.svg
Normal 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 |
@@ -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>pͼ<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
|
||||
BIN
publish/littleshop-hotfix/wwwroot/icons/icon-placeholder.svg.gz
Normal file
537
publish/littleshop-hotfix/wwwroot/js/holographic-effects.js
Normal 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.');
|
||||
BIN
publish/littleshop-hotfix/wwwroot/js/holographic-effects.js.br
Normal file
BIN
publish/littleshop-hotfix/wwwroot/js/holographic-effects.js.gz
Normal file
221
publish/littleshop-hotfix/wwwroot/js/modern-mobile.js
Normal 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;
|
||||
BIN
publish/littleshop-hotfix/wwwroot/js/modern-mobile.js.br
Normal file
BIN
publish/littleshop-hotfix/wwwroot/js/modern-mobile.js.gz
Normal file
317
publish/littleshop-hotfix/wwwroot/js/notifications.js
Normal 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;
|
||||
BIN
publish/littleshop-hotfix/wwwroot/js/notifications.js.br
Normal file
BIN
publish/littleshop-hotfix/wwwroot/js/notifications.js.gz
Normal file
537
publish/littleshop-hotfix/wwwroot/js/pwa.js
Normal 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();
|
||||
BIN
publish/littleshop-hotfix/wwwroot/js/pwa.js.br
Normal file
BIN
publish/littleshop-hotfix/wwwroot/js/pwa.js.gz
Normal file
2018
publish/littleshop-hotfix/wwwroot/lib/bootstrap-icons/bootstrap-icons.css
vendored
Normal file
7
publish/littleshop-hotfix/wwwroot/lib/bootstrap/css/bootstrap.min.css
vendored
Normal file
7
publish/littleshop-hotfix/wwwroot/lib/bootstrap/js/bootstrap.bundle.min.js
vendored
Normal file
6
publish/littleshop-hotfix/wwwroot/lib/fontawesome/css/all.min.css
vendored
Normal file
2
publish/littleshop-hotfix/wwwroot/lib/jquery/jquery.min.js
vendored
Normal file
BIN
publish/littleshop-hotfix/wwwroot/lib/jquery/jquery.min.js.br
Normal file
BIN
publish/littleshop-hotfix/wwwroot/lib/jquery/jquery.min.js.gz
Normal file
2580
publish/littleshop-hotfix/wwwroot/lib/radzen/Radzen.Blazor.js
Normal file
BIN
publish/littleshop-hotfix/wwwroot/lib/radzen/Radzen.Blazor.js.br
Normal file
BIN
publish/littleshop-hotfix/wwwroot/lib/radzen/Radzen.Blazor.js.gz
Normal file
24431
publish/littleshop-hotfix/wwwroot/lib/radzen/dark-base.css
Normal file
BIN
publish/littleshop-hotfix/wwwroot/lib/radzen/dark-base.css.br
Normal file
BIN
publish/littleshop-hotfix/wwwroot/lib/radzen/dark-base.css.gz
Normal file
24578
publish/littleshop-hotfix/wwwroot/lib/radzen/dark.css
Normal file
BIN
publish/littleshop-hotfix/wwwroot/lib/radzen/dark.css.br
Normal file
BIN
publish/littleshop-hotfix/wwwroot/lib/radzen/dark.css.gz
Normal file
90
publish/littleshop-hotfix/wwwroot/manifest.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
BIN
publish/littleshop-hotfix/wwwroot/manifest.json.br
Normal file
BIN
publish/littleshop-hotfix/wwwroot/manifest.json.gz
Normal file
192
publish/littleshop-hotfix/wwwroot/sw.js
Normal 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' }
|
||||
});
|
||||
}
|
||||
});
|
||||
})
|
||||
);
|
||||
});
|
||||
BIN
publish/littleshop-hotfix/wwwroot/sw.js.br
Normal file
BIN
publish/littleshop-hotfix/wwwroot/sw.js.gz
Normal file
|
After Width: | Height: | Size: 165 KiB |
|
After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 48 KiB |
@@ -0,0 +1 @@
|
||||
test image content
|
||||
@@ -0,0 +1 @@
|
||||
test image content
|
||||