PWA-implementation

This commit is contained in:
sysadmin 2025-09-01 04:49:05 +01:00
parent cccb4e4d03
commit 5eb7647faf
34 changed files with 56372 additions and 13 deletions

View File

@ -59,6 +59,9 @@
<div class="card"> <div class="card">
<div class="card-header"> <div class="card-header">
<h5><i class="fas fa-chart-line"></i> Quick Actions</h5> <h5><i class="fas fa-chart-line"></i> Quick Actions</h5>
<button id="pwa-install-dashboard" class="btn btn-sm btn-outline-primary" style="float: right;">
<i class="fas fa-mobile-alt"></i> Install App
</button>
</div> </div>
<div class="card-body"> <div class="card-body">
<div class="list-group list-group-flush"> <div class="list-group list-group-flush">
@ -95,4 +98,34 @@
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const installBtn = document.getElementById('pwa-install-dashboard');
if (installBtn) {
installBtn.addEventListener('click', function() {
// Check if app is in standalone mode
if (window.matchMedia('(display-mode: standalone)').matches) {
alert('App is already installed!');
return;
}
// Show manual install instructions
alert(`To install LittleShop Admin as an app:
🌐 Chrome/Edge:
1. Click the install icon (⊞) in the address bar, OR
2. Menu (⋮) → "Install LittleShop Admin"
🍎 Safari (iOS):
1. Share button → "Add to Home Screen"
📱 Mobile browsers:
1. Browser menu → "Add to Home Screen"
The app will then work offline and appear in your apps list!`);
});
}
});
</script>

View File

@ -2,15 +2,42 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no, viewport-fit=cover" />
<title>@ViewData["Title"] - LittleShop Admin</title> <title>@ViewData["Title"] - LittleShop Admin</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet"> <!-- PWA Meta Tags -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css" rel="stylesheet"> <meta name="application-name" content="LittleShop Admin" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
<meta name="apple-mobile-web-app-title" content="LittleShop" />
<meta name="description" content="Modern e-commerce admin panel" />
<meta name="mobile-web-app-capable" content="yes" />
<meta name="theme-color" content="#2563eb" />
<meta name="msapplication-TileColor" content="#2563eb" />
<meta name="msapplication-tap-highlight" content="no" />
<!-- PWA Manifest -->
<link rel="manifest" href="/manifest.json" />
<!-- Icons -->
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
<link rel="apple-touch-icon" href="/icons/icon-152x152.png" />
<link rel="apple-touch-icon" sizes="72x72" href="/icons/icon-72x72.png" />
<link rel="apple-touch-icon" sizes="96x96" href="/icons/icon-96x96.png" />
<link rel="apple-touch-icon" sizes="128x128" href="/icons/icon-128x128.png" />
<link rel="apple-touch-icon" sizes="144x144" href="/icons/icon-144x144.png" />
<link rel="apple-touch-icon" sizes="152x152" href="/icons/icon-152x152.png" />
<link rel="apple-touch-icon" sizes="192x192" href="/icons/icon-192x192.png" />
<link rel="apple-touch-icon" sizes="384x384" href="/icons/icon-384x384.png" />
<link rel="apple-touch-icon" sizes="512x512" href="/icons/icon-512x512.png" />
<link href="/lib/bootstrap/css/bootstrap.min.css" rel="stylesheet">
<link href="/lib/fontawesome/css/all.min.css" rel="stylesheet">
<link href="/lib/bootstrap-icons/bootstrap-icons.css" rel="stylesheet">
<link href="/css/modern-admin.css" rel="stylesheet">
</head> </head>
<body> <body>
<header> <header>
<nav class="navbar navbar-expand-sm navbar-dark bg-dark"> <nav class="navbar navbar-expand-sm navbar-light bg-white">
<div class="container-fluid"> <div class="container-fluid">
<a class="navbar-brand" href="@Url.Action("Index", "Dashboard", new { area = "Admin" })"> <a class="navbar-brand" href="@Url.Action("Index", "Dashboard", new { area = "Admin" })">
<i class="fas fa-store"></i> LittleShop Admin <i class="fas fa-store"></i> LittleShop Admin
@ -87,8 +114,10 @@
</main> </main>
</div> </div>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script> <script src="/lib/jquery/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script> <script src="/lib/bootstrap/js/bootstrap.bundle.min.js"></script>
<script src="/js/pwa.js"></script>
<script src="/js/modern-mobile.js"></script>
@await RenderSectionAsync("Scripts", required: false) @await RenderSectionAsync("Scripts", required: false)
</body> </body>
</html> </html>

View File

@ -7,5 +7,8 @@ public enum PaymentStatus
Paid = 2, Paid = 2,
Overpaid = 3, Overpaid = 3,
Expired = 4, Expired = 4,
Cancelled = 5 Cancelled = 5,
Processing = 6,
Completed = 7,
Failed = 8
} }

View File

@ -83,9 +83,28 @@ public class BTCPayServerService : IBTCPayServerService
public Task<bool> ValidateWebhookAsync(string payload, string signature) public Task<bool> ValidateWebhookAsync(string payload, string signature)
{ {
// Implement webhook signature validation try
// This is a simplified version - in production, implement proper HMAC validation {
return Task.FromResult(true); // BTCPay Server uses HMAC-SHA256 with format "sha256=<hex>"
if (!signature.StartsWith("sha256="))
{
return Task.FromResult(false);
}
var expectedHash = signature.Substring(7); // Remove "sha256=" prefix
var secretBytes = System.Text.Encoding.UTF8.GetBytes(_webhookSecret);
var payloadBytes = System.Text.Encoding.UTF8.GetBytes(payload);
using var hmac = new System.Security.Cryptography.HMACSHA256(secretBytes);
var computedHash = hmac.ComputeHash(payloadBytes);
var computedHashHex = Convert.ToHexString(computedHash).ToLowerInvariant();
return Task.FromResult(expectedHash.Equals(computedHashHex, StringComparison.OrdinalIgnoreCase));
}
catch
{
return Task.FromResult(false);
}
} }
private static string GetCurrencyCode(CryptoCurrency currency) private static string GetCurrencyCode(CryptoCurrency currency)

View File

@ -9,7 +9,7 @@
"ExpiryInHours": 24 "ExpiryInHours": 24
}, },
"BTCPayServer": { "BTCPayServer": {
"BaseUrl": "https://your-btcpay-server.com", "BaseUrl": "https://pay.silverlabs.uk",
"ApiKey": "your-api-key", "ApiKey": "your-api-key",
"StoreId": "your-store-id", "StoreId": "your-store-id",
"WebhookSecret": "your-webhook-secret" "WebhookSecret": "your-webhook-secret"

View File

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

View File

@ -0,0 +1,442 @@
/*
* 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 */
.table-responsive {
border: none;
}
.table {
font-size: 0.875rem;
}
.table thead {
display: none;
}
.table tbody,
.table tbody tr,
.table tbody td {
display: block;
width: 100%;
}
.table tbody tr {
background: white;
border: 1px solid var(--grey-200);
border-radius: var(--radius-md);
margin-bottom: var(--spacing-md);
padding: var(--spacing-md);
box-shadow: var(--shadow-sm);
}
.table tbody td {
border: none;
padding: var(--spacing-sm) 0;
position: relative;
padding-left: 40%;
}
.table tbody td:before {
content: attr(data-label) ": ";
position: absolute;
left: 0;
width: 35%;
font-weight: 600;
color: var(--grey-600);
}
/* Mobile Navigation */
.navbar-toggler {
border: none;
padding: 0.25rem 0.5rem;
}
.navbar-collapse {
margin-top: var(--spacing-md);
}
.navbar-nav .nav-link {
padding: var(--spacing-md);
margin: var(--spacing-xs) 0;
background: var(--grey-50);
border-radius: var(--radius-md);
}
}
/* Modern Form Layout */
.row .col-md-6 .form-group,
.row .col-md-6 .mb-3 {
margin-bottom: var(--spacing-lg);
}
/* Action Buttons */
.d-flex.justify-content-between .btn {
min-width: 120px;
}
/* Loading States */
.btn:disabled {
opacity: 0.6;
cursor: not-allowed;
}
/* Focus States for Accessibility */
.btn:focus,
.form-control:focus,
.form-select:focus {
outline: 2px solid var(--primary-blue);
outline-offset: 2px;
}
/* Toast/Alert Improvements */
.alert {
border: none;
border-radius: var(--radius-md);
border-left: 4px solid;
}
.alert-success {
background: #ecfdf5;
color: #065f46;
border-left-color: var(--success-green);
}
.alert-danger {
background: #fef2f2;
color: #991b1b;
border-left-color: var(--danger-red);
}
.alert-warning {
background: #fffbeb;
color: #92400e;
border-left-color: var(--warning-orange);
}
/* Clean List Styling */
.list-group-item {
border: 1px solid var(--grey-200);
background: white;
transition: all 0.2s ease;
}
.list-group-item:hover {
background: var(--grey-50);
transform: translateX(4px);
}
.list-group-item-action {
color: var(--grey-700);
}
.list-group-item-action:hover {
color: var(--primary-blue);
}
/* Typography Improvements */
.text-muted {
color: var(--grey-500) !important;
}
code {
background: var(--grey-100);
color: var(--grey-800);
padding: 0.125rem 0.375rem;
border-radius: var(--radius-sm);
font-family: var(--font-mono);
font-size: 0.875em;
}
/* Utility Classes */
.rounded-modern {
border-radius: var(--radius-lg) !important;
}
.shadow-modern {
box-shadow: var(--shadow-md) !important;
}
.bg-gradient-primary {
background: linear-gradient(135deg, var(--primary-blue) 0%, var(--primary-purple) 100%) !important;
}
/* Print Styles */
@media print {
.navbar,
.btn,
.card-header {
display: none !important;
}
.card {
border: 1px solid #ccc !important;
box-shadow: none !important;
}
}

View File

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

View File

@ -0,0 +1 @@
AAABAAEAEBAQAAEABAAoAQAAFgAAACgAAAAQAAAAIAAAAAEABAAAAAAAgAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

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

After

Width:  |  Height:  |  Size: 879 B

View File

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

View File

@ -0,0 +1,214 @@
// 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) => {
const invalidInputs = form.querySelectorAll(':invalid');
if (invalidInputs.length > 0) {
e.preventDefault();
invalidInputs[0].focus();
invalidInputs[0].scrollIntoView({ behavior: 'smooth', block: 'center' });
}
});
});
// Floating labels effect
const inputs = document.querySelectorAll('.form-control, .form-select');
inputs.forEach(input => {
if (input.value) {
input.parentElement.classList.add('has-value');
}
input.addEventListener('blur', () => {
if (input.value) {
input.parentElement.classList.add('has-value');
} else {
input.parentElement.classList.remove('has-value');
}
});
});
}
// Smooth interactions and feedback
setupSmoothInteractions() {
// Button click feedback
const buttons = document.querySelectorAll('.btn');
buttons.forEach(button => {
button.addEventListener('click', (e) => {
if (!button.disabled) {
button.style.transform = 'scale(0.95)';
setTimeout(() => {
button.style.transform = '';
}, 100);
}
});
});
// Card hover enhancement
const cards = document.querySelectorAll('.card');
cards.forEach(card => {
card.addEventListener('mouseenter', () => {
card.style.transform = 'translateY(-2px)';
});
card.addEventListener('mouseleave', () => {
card.style.transform = '';
});
});
// Smooth scroll for anchor links
const anchorLinks = document.querySelectorAll('a[href^="#"]');
anchorLinks.forEach(link => {
link.addEventListener('click', (e) => {
e.preventDefault();
const target = document.querySelector(link.getAttribute('href'));
if (target) {
target.scrollIntoView({ behavior: 'smooth' });
}
});
});
}
// Show toast notification
showToast(message, type = 'info') {
const toastContainer = document.getElementById('toast-container') || this.createToastContainer();
const toast = document.createElement('div');
toast.className = `alert alert-${type} alert-dismissible fade show`;
toast.style.cssText = `
margin-bottom: 0.5rem;
animation: slideInRight 0.3s ease;
`;
toast.innerHTML = `
${message}
<button type="button" class="btn-close" aria-label="Close"></button>
`;
toastContainer.appendChild(toast);
// Auto-remove after 4 seconds
setTimeout(() => {
toast.classList.add('fade');
setTimeout(() => {
if (toast.parentNode) {
toast.parentNode.removeChild(toast);
}
}, 150);
}, 4000);
// Manual close
const closeBtn = toast.querySelector('.btn-close');
closeBtn.addEventListener('click', () => {
toast.classList.add('fade');
setTimeout(() => {
if (toast.parentNode) {
toast.parentNode.removeChild(toast);
}
}, 150);
});
}
createToastContainer() {
const container = document.createElement('div');
container.id = 'toast-container';
container.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
z-index: 1050;
max-width: 300px;
`;
document.body.appendChild(container);
return container;
}
}
// CSS for toast animations
const style = document.createElement('style');
style.textContent = `
@keyframes slideInRight {
from {
transform: translateX(100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
.fade {
opacity: 0 !important;
transition: opacity 0.15s linear !important;
}
`;
document.head.appendChild(style);
// Initialize
const modernMobile = new ModernMobile();
window.modernMobile = modernMobile;

View File

@ -0,0 +1,205 @@
// Progressive Web App functionality
// Handles service worker registration and PWA features
class PWAManager {
constructor() {
this.swRegistration = 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();
// Show manual install option after 3 seconds if no prompt appeared
setTimeout(() => {
if (!document.getElementById('pwa-install-btn')) {
this.showManualInstallButton();
}
}, 3000);
}
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)');
} else {
console.log('PWA: App is not installed, waiting for install prompt...');
}
}
showInstallButton(deferredPrompt) {
// 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() {
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', () => {
alert('To install this app:\\n\\n1. Click the browser menu (⋮)\\n2. Select "Install LittleShop Admin"\\n\\nOr look for the install icon in the address bar!');
});
document.body.appendChild(installBtn);
}
// Check if app is installed
isInstalled() {
return window.matchMedia('(display-mode: standalone)').matches ||
window.navigator.standalone === true;
}
}
// Initialize PWA Manager
const pwaManager = new PWAManager();
window.pwaManager = pwaManager;
// Expose notification function globally
window.showNotification = (title, options) => pwaManager.showNotification(title, options);

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

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

106
LittleShop/wwwroot/sw.js Normal file
View File

@ -0,0 +1,106 @@
// LittleShop Admin - Service Worker
// Provides offline functionality and app-like experience
const CACHE_NAME = 'littleshop-admin-v1';
const urlsToCache = [
'/Admin/Dashboard',
'/Admin/Orders',
'/Admin/Products',
'/Admin/Categories',
'/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',
'/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);
}
})
);
})
);
});
// Fetch event - serve from cache, fallback to network
self.addEventListener('fetch', (event) => {
// Only handle GET requests
if (event.request.method !== 'GET') {
return;
}
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' }
});
}
});
})
);
});

86
create_pwa_icons.ps1 Normal file
View File

@ -0,0 +1,86 @@
# PowerShell script to generate PWA icons
# Creates PNG icons for LittleShop Admin PWA
Add-Type -AssemblyName System.Drawing
function Create-Icon {
param(
[int]$Size,
[string]$OutputPath
)
# Create bitmap
$bitmap = New-Object System.Drawing.Bitmap($Size, $Size)
$graphics = [System.Drawing.Graphics]::FromImage($bitmap)
$graphics.SmoothingMode = [System.Drawing.Drawing2D.SmoothingMode]::AntiAlias
$graphics.TextRenderingHint = [System.Drawing.Text.TextRenderingHint]::AntiAlias
# Create gradient brush
$startColor = [System.Drawing.Color]::FromArgb(37, 99, 235) # #2563eb
$endColor = [System.Drawing.Color]::FromArgb(124, 58, 237) # #7c3aed
$gradientBrush = New-Object System.Drawing.Drawing2D.LinearGradientBrush(
[System.Drawing.Point]::new(0, 0),
[System.Drawing.Point]::new($Size, $Size),
$startColor,
$endColor
)
# Draw rounded rectangle background
$cornerRadius = [int]($Size * 0.15)
$rect = New-Object System.Drawing.Rectangle(0, 0, $Size, $Size)
# Create rounded rectangle path
$path = New-Object System.Drawing.Drawing2D.GraphicsPath
$path.AddArc(0, 0, $cornerRadius * 2, $cornerRadius * 2, 180, 90)
$path.AddArc($Size - $cornerRadius * 2, 0, $cornerRadius * 2, $cornerRadius * 2, 270, 90)
$path.AddArc($Size - $cornerRadius * 2, $Size - $cornerRadius * 2, $cornerRadius * 2, $cornerRadius * 2, 0, 90)
$path.AddArc(0, $Size - $cornerRadius * 2, $cornerRadius * 2, $cornerRadius * 2, 90, 90)
$path.CloseFigure()
# Fill background
$graphics.FillPath($gradientBrush, $path)
# Draw text "LS"
$font = New-Object System.Drawing.Font("Segoe UI", ($Size * 0.3), [System.Drawing.FontStyle]::Bold)
$brush = New-Object System.Drawing.SolidBrush([System.Drawing.Color]::White)
$format = New-Object System.Drawing.StringFormat
$format.Alignment = [System.Drawing.StringAlignment]::Center
$format.LineAlignment = [System.Drawing.StringAlignment]::Center
$textRect = New-Object System.Drawing.RectangleF(0, 0, $Size, $Size)
$graphics.DrawString("LS", $font, $brush, $textRect, $format)
# Save image
$bitmap.Save($OutputPath, [System.Drawing.Imaging.ImageFormat]::Png)
# Cleanup
$graphics.Dispose()
$bitmap.Dispose()
$gradientBrush.Dispose()
$font.Dispose()
$brush.Dispose()
$path.Dispose()
Write-Host "Created icon: $OutputPath ($Size x $Size)"
}
# Create icons directory
$iconsDir = "C:\Production\Source\LittleShop\LittleShop\wwwroot\icons"
if (!(Test-Path $iconsDir)) {
New-Item -ItemType Directory -Path $iconsDir -Force
}
# Generate all required icon sizes
$sizes = @(72, 96, 128, 144, 152, 192, 384, 512)
foreach ($size in $sizes) {
$outputPath = Join-Path $iconsDir "icon-${size}x${size}.png"
Create-Icon -Size $size -OutputPath $outputPath
}
Write-Host ""
Write-Host "🎉 All PWA icons created successfully!"
Write-Host "Icons saved to: $iconsDir"
Write-Host ""
Write-Host "Generated icons:"
Get-ChildItem $iconsDir -Name "icon-*.png" | ForEach-Object { Write-Host "$_" }