Compare commits
	
		
			5 Commits 
		
	
	
		
			1391437bd5
			...
			15023967a1
		
	
	| Author | SHA1 | Date | 
|---|---|---|
| 
							
							
								 | 
						15023967a1 | |
| 
							
							
								 | 
						9962a58df0 | |
| 
							
							
								 | 
						a4b5c45312 | |
| 
							
							
								 | 
						b701c86656 | |
| 
							
							
								 | 
						d3b2df416c | 
| 
						 | 
					@ -0,0 +1,28 @@
 | 
				
			||||||
 | 
					using System.Diagnostics;
 | 
				
			||||||
 | 
					using Microsoft.AspNetCore.Mvc;
 | 
				
			||||||
 | 
					using eSPJ.Models;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace eSPJ.Controllers.SpjAdminController;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[Route("admin")]
 | 
				
			||||||
 | 
					public class AdminController : Controller
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    [HttpGet("")]
 | 
				
			||||||
 | 
					    public IActionResult Index()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return View("~/Views/Admin/Transport/SpjAdmin/Home/Index.cshtml");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    [HttpGet("scan")]
 | 
				
			||||||
 | 
					    public IActionResult Scan()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return View("~/Views/Admin/Transport/SpjAdmin/Scan/Index.cshtml");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    [HttpGet("history")]
 | 
				
			||||||
 | 
					    public IActionResult History()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return View("~/Views/Admin/Transport/SpjAdmin/History/Index.cshtml");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -4,24 +4,24 @@ using eSPJ.Models;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace eSPJ.Controllers.SpjDriverController;
 | 
					namespace eSPJ.Controllers.SpjDriverController;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 [Route("")]
 | 
				
			||||||
public class HomeController : Controller
 | 
					public class HomeController : Controller
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    private readonly ILogger<HomeController> _logger;
 | 
					    private readonly ILogger<HomeController> _logger;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public HomeController(ILogger<HomeController> logger)
 | 
					    public HomeController(ILogger<HomeController> logger)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        _logger = logger;
 | 
					        _logger = logger;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    [HttpGet("")]
 | 
				
			||||||
    public IActionResult Index()
 | 
					    public IActionResult Index()
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
         return View("~/Views/Admin/Transport/SpjDriver/Home/Index.cshtml");
 | 
					        return View("~/Views/Admin/Transport/SpjDriver/Home/Index.cshtml");
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    [HttpGet("kosong")]
 | 
				
			||||||
    public IActionResult Privacy()
 | 
					    public IActionResult Kosong()
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        return View("~/Views/Admin/Transport/SpjDriver/Home/Privacy.cshtml");
 | 
					        return View("~/Views/Admin/Transport/SpjDriver/Home/Kosong.cshtml");
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 
 | 
					 
 | 
				
			||||||
    [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
 | 
					    [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -18,5 +18,70 @@ namespace eSPJ.Controllers.SpjDriverController
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            return View("~/Views/Admin/Transport/SpjDriver/Submit/Struk.cshtml");
 | 
					            return View("~/Views/Admin/Transport/SpjDriver/Submit/Struk.cshtml");
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [HttpPost("struk")]
 | 
				
			||||||
 | 
					        public IActionResult ProcessStruk(string NomorStruk, string NomorPolisi, string Penugasan, 
 | 
				
			||||||
 | 
					            string WaktuMasuk, string WaktuKeluar, int? BeratMasuk, int? BeratKeluar, int BeratNett)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            try
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                // Validate required inputs
 | 
				
			||||||
 | 
					                if (string.IsNullOrEmpty(NomorStruk) || BeratNett <= 0)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    TempData["Error"] = "Nomor struk dan berat nett harus diisi.";
 | 
				
			||||||
 | 
					                    return RedirectToAction("Struk");
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // Validate receipt number format (numbers only, 7+ digits)
 | 
				
			||||||
 | 
					                if (!System.Text.RegularExpressions.Regex.IsMatch(NomorStruk, @"^\d{7,}$"))
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    TempData["Error"] = "Format nomor struk tidak valid. Harus berupa angka minimal 7 digit.";
 | 
				
			||||||
 | 
					                    return RedirectToAction("Struk");
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // Validate weight range
 | 
				
			||||||
 | 
					                if (BeratNett < 100 || BeratNett > 50000)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    TempData["Error"] = "Berat nett harus antara 100 kg - 50,000 kg.";
 | 
				
			||||||
 | 
					                    return RedirectToAction("Struk");
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // Validate optional weights
 | 
				
			||||||
 | 
					                if (BeratMasuk.HasValue && (BeratMasuk < 0 || BeratMasuk > 100000))
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    TempData["Error"] = "Berat masuk tidak valid.";
 | 
				
			||||||
 | 
					                    return RedirectToAction("Struk");
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (BeratKeluar.HasValue && (BeratKeluar < 0 || BeratKeluar > 100000))
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    TempData["Error"] = "Berat keluar tidak valid.";
 | 
				
			||||||
 | 
					                    return RedirectToAction("Struk");
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // Here you would normally save to database
 | 
				
			||||||
 | 
					                // For now, just simulate success with all data
 | 
				
			||||||
 | 
					                var submitData = new
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    NomorStruk,
 | 
				
			||||||
 | 
					                    NomorPolisi = NomorPolisi ?? "N/A",
 | 
				
			||||||
 | 
					                    Penugasan = Penugasan ?? "N/A",
 | 
				
			||||||
 | 
					                    WaktuMasuk = WaktuMasuk ?? "N/A",
 | 
				
			||||||
 | 
					                    WaktuKeluar = WaktuKeluar ?? "N/A",
 | 
				
			||||||
 | 
					                    BeratMasuk = BeratMasuk?.ToString() ?? "N/A",
 | 
				
			||||||
 | 
					                    BeratKeluar = BeratKeluar?.ToString() ?? "N/A",
 | 
				
			||||||
 | 
					                    BeratNett
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                TempData["Success"] = $"Struk berhasil disubmit! No: {NomorStruk}, Nett: {BeratNett} kg";
 | 
				
			||||||
 | 
					                return RedirectToAction("Index", "Home");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            catch (Exception)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                TempData["Error"] = "Terjadi kesalahan saat memproses struk. Silakan coba lagi.";
 | 
				
			||||||
 | 
					                return RedirectToAction("Struk");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,179 @@
 | 
				
			||||||
 | 
					# SPJ Barcode Scanner
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Scanner barcode untuk aplikasi eSPJ yang menggunakan QuaggaJS library.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Fitur
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Scanner barcode real-time menggunakan kamera device
 | 
				
			||||||
 | 
					- Mendukung berbagai format barcode (Code 128, Code 39, EAN, dll)
 | 
				
			||||||
 | 
					- Input manual sebagai alternatif
 | 
				
			||||||
 | 
					- Validasi format SPJ
 | 
				
			||||||
 | 
					- Responsive design untuk mobile dan desktop
 | 
				
			||||||
 | 
					- Sound feedback saat barcode terdeteksi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Format Barcode yang Didukung
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Code 128
 | 
				
			||||||
 | 
					- Code 39
 | 
				
			||||||
 | 
					- Code 39 VIN
 | 
				
			||||||
 | 
					- EAN-13
 | 
				
			||||||
 | 
					- EAN-8
 | 
				
			||||||
 | 
					- Code 93
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Cara Penggunaan
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Untuk User (Driver)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					1. **Akses Halaman Scanner**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   - Buka aplikasi eSPJ
 | 
				
			||||||
 | 
					   - Navigasi ke halaman "Scan SPJ"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					2. **Menggunakan Camera Scanner**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   - Klik tombol "Mulai Scan"
 | 
				
			||||||
 | 
					   - Izinkan akses kamera saat diminta browser
 | 
				
			||||||
 | 
					   - Arahkan kamera ke barcode SPJ
 | 
				
			||||||
 | 
					   - Scanner akan otomatis mendeteksi dan menampilkan hasil
 | 
				
			||||||
 | 
					   - Klik "Konfirmasi" untuk melanjutkan atau "Scan Ulang" untuk mencoba lagi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					3. **Menggunakan Input Manual**
 | 
				
			||||||
 | 
					   - Masukkan kode SPJ secara manual di field yang disediakan
 | 
				
			||||||
 | 
					   - Klik tombol search untuk memproses
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Browser Requirements
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Chrome 21+
 | 
				
			||||||
 | 
					- Firefox 17+
 | 
				
			||||||
 | 
					- Safari 11+
 | 
				
			||||||
 | 
					- Edge 12+
 | 
				
			||||||
 | 
					- Opera 18+
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Permissions
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Scanner memerlukan akses kamera. Pastikan:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Akses kamera diizinkan pada browser
 | 
				
			||||||
 | 
					- Halaman diakses melalui HTTPS (untuk production)
 | 
				
			||||||
 | 
					- Device memiliki kamera yang berfungsi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Technical Implementation
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Dependencies
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- **QuaggaJS**: Library untuk barcode scanning
 | 
				
			||||||
 | 
					- **Lucide Icons**: Untuk iconography
 | 
				
			||||||
 | 
					- **Tailwind CSS**: Untuk styling
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### File Structure
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					Views/Admin/Transport/SpjDriver/Scan/
 | 
				
			||||||
 | 
					├── Index.cshtml                 # Main scanner page
 | 
				
			||||||
 | 
					Controllers/SpjDriverController/
 | 
				
			||||||
 | 
					├── ScanController.cs            # Backend logic
 | 
				
			||||||
 | 
					wwwroot/driver/css/
 | 
				
			||||||
 | 
					├── scanner.css                  # Scanner-specific styles
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Key Components
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					1. **BarcodeScanner Class** (JavaScript)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   - Handles camera initialization
 | 
				
			||||||
 | 
					   - Manages QuaggaJS configuration
 | 
				
			||||||
 | 
					   - Processes scan results
 | 
				
			||||||
 | 
					   - Handles UI interactions
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					2. **ScanController** (C#)
 | 
				
			||||||
 | 
					   - Validates scanned codes
 | 
				
			||||||
 | 
					   - Processes SPJ lookup
 | 
				
			||||||
 | 
					   - Handles error responses
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Configuration
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					QuaggaJS configuration:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```javascript
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    inputStream: {
 | 
				
			||||||
 | 
					        type: "LiveStream",
 | 
				
			||||||
 | 
					        constraints: {
 | 
				
			||||||
 | 
					            width: 320,
 | 
				
			||||||
 | 
					            height: 240,
 | 
				
			||||||
 | 
					            facingMode: "environment" // Use back camera
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    decoder: {
 | 
				
			||||||
 | 
					        readers: [
 | 
				
			||||||
 | 
					            "code_128_reader",
 | 
				
			||||||
 | 
					            "code_39_reader",
 | 
				
			||||||
 | 
					            "ean_reader",
 | 
				
			||||||
 | 
					            // ... more readers
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Customization
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Menambah Format Barcode Baru
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Edit array `readers` di file Index.cshtml:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```javascript
 | 
				
			||||||
 | 
					readers: ["code_128_reader", "your_new_reader_here"];
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Mengubah Validasi SPJ
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Edit method `ValidateSpjCode` di ScanController.cs:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```csharp
 | 
				
			||||||
 | 
					private async Task<SpjData?> ValidateSpjCode(string barcode)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    // Your custom validation logic here
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Styling
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Edit file `scanner.css` untuk mengubah appearance scanner.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Troubleshooting
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Camera Tidak Berfungsi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					1. Pastikan browser memiliki akses kamera
 | 
				
			||||||
 | 
					2. Cek apakah halaman diakses melalui HTTPS
 | 
				
			||||||
 | 
					3. Restart browser jika perlu
 | 
				
			||||||
 | 
					4. Cek device permissions
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Barcode Tidak Terdeteksi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					1. Pastikan barcode dalam format yang didukung
 | 
				
			||||||
 | 
					2. Cek pencahayaan - barcode harus jelas terbaca
 | 
				
			||||||
 | 
					3. Jaga jarak optimal (15-30cm dari kamera)
 | 
				
			||||||
 | 
					4. Pastikan barcode tidak rusak atau blur
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Performance Issues
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					1. Tutup aplikasi lain yang menggunakan kamera
 | 
				
			||||||
 | 
					2. Gunakan browser yang up-to-date
 | 
				
			||||||
 | 
					3. Cek kecepatan internet untuk loading library
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Development Notes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Library QuaggaJS dimuat dari CDN (dapat diunduh lokal jika perlu)
 | 
				
			||||||
 | 
					- Scanner otomatis stop setelah berhasil scan untuk menghemat resource
 | 
				
			||||||
 | 
					- Implementasi includes sound feedback dan haptic feedback
 | 
				
			||||||
 | 
					- Mobile-first responsive design
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Future Enhancements
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- [ ] Support untuk QR Code
 | 
				
			||||||
 | 
					- [ ] Batch scanning multiple barcodes
 | 
				
			||||||
 | 
					- [ ] Offline scanning capability
 | 
				
			||||||
 | 
					- [ ] Advanced barcode validation
 | 
				
			||||||
 | 
					- [ ] Scan history
 | 
				
			||||||
 | 
					- [ ] Analytics dan reporting
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,163 @@
 | 
				
			||||||
 | 
					@{
 | 
				
			||||||
 | 
					    Layout = "~/Views/Admin/Transport/SpjAdmin/Shared/_Layout.cshtml";
 | 
				
			||||||
 | 
					    ViewData["Title"] = "Detail Perjalanan - DLH";
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<div class="max-w-sm mx-auto bg-gray-50 min-h-screen">
 | 
				
			||||||
 | 
					    <!-- Header -->
 | 
				
			||||||
 | 
					    <div class="bg-gradient-to-r from-orange-500 to-orange-600 text-white px-4 py-4 sticky top-0 z-10 shadow-lg">
 | 
				
			||||||
 | 
					        <div class="flex items-center justify-between">
 | 
				
			||||||
 | 
					            <a href="@Url.Action("Index", "History")" class="p-2 hover:bg-white/10 rounded-full transition-all duration-200">
 | 
				
			||||||
 | 
					                <i class="w-5 h-5" data-lucide="chevron-left"></i>
 | 
				
			||||||
 | 
					            </a>
 | 
				
			||||||
 | 
					            <h1 class="text-lg font-bold">Detail Perjalanan</h1>
 | 
				
			||||||
 | 
					            <div class="w-9"></div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <div class="px-4 py-4 space-y-4">
 | 
				
			||||||
 | 
					        <!-- SPJ Information Card -->
 | 
				
			||||||
 | 
					        <div class="bg-white rounded-2xl p-5 shadow-sm border border-gray-100">
 | 
				
			||||||
 | 
					            <div class="flex items-center gap-3 mb-4">
 | 
				
			||||||
 | 
					                <div class="w-10 h-10 bg-orange-100 rounded-full flex items-center justify-center">
 | 
				
			||||||
 | 
					                    <i class="w-5 h-5 text-orange-600" data-lucide="file-text"></i>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					                <div>
 | 
				
			||||||
 | 
					                    <h2 class="text-lg font-bold text-gray-900">Data SPJ</h2>
 | 
				
			||||||
 | 
					                    <p class="text-xs text-gray-500">Surat Perintah Jalan</p>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            <div class="space-y-3">
 | 
				
			||||||
 | 
					                <div class="flex justify-between items-center py-2 border-b border-gray-50">
 | 
				
			||||||
 | 
					                    <span class="text-sm text-gray-600">No. SPJ</span>
 | 
				
			||||||
 | 
					                    <span class="font-semibold text-gray-900 text-sm">SPJ/07-2025/PKM/000476</span>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					                <div class="flex justify-between items-center py-2 border-b border-gray-50">
 | 
				
			||||||
 | 
					                    <span class="text-sm text-gray-600">Plat Nomor</span>
 | 
				
			||||||
 | 
					                    <span class="bg-orange-50 text-orange-700 px-3 py-1 rounded-full text-sm font-medium">B 9632 TOR</span>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					                <div class="flex justify-between items-center py-2 border-b border-gray-50">
 | 
				
			||||||
 | 
					                    <span class="text-sm text-gray-600">Nomer Pintu</span>
 | 
				
			||||||
 | 
					                    <span class="font-mono text-sm text-gray-700">JRC 005</span>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					                <div class="flex justify-between items-center py-2">
 | 
				
			||||||
 | 
					                    <span class="text-sm text-gray-600">Tujuan Pembuangan</span>
 | 
				
			||||||
 | 
					                    <span class="font-semibold text-gray-900 text-sm">Taman Barito</span>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        <!-- Summary Card -->
 | 
				
			||||||
 | 
					        <div class="bg-white rounded-2xl p-5 shadow-sm border border-gray-100">
 | 
				
			||||||
 | 
					            <div class="flex items-center gap-3 mb-4">
 | 
				
			||||||
 | 
					                <div class="w-10 h-10 bg-orange-100 rounded-full flex items-center justify-center">
 | 
				
			||||||
 | 
					                    <i class="w-5 h-5 text-orange-600" data-lucide="bar-chart-3"></i>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					                <div>
 | 
				
			||||||
 | 
					                    <h2 class="text-lg font-bold text-gray-900">Ringkasan</h2>
 | 
				
			||||||
 | 
					                    <p class="text-xs text-gray-500">Informasi perjalanan</p>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            <div class="grid grid-cols-4 gap-3">
 | 
				
			||||||
 | 
					                <div class="text-center p-3 bg-gray-50 rounded-xl">
 | 
				
			||||||
 | 
					                    <div class="text-xl font-bold text-gray-900">3</div>
 | 
				
			||||||
 | 
					                    <div class="text-xs text-gray-600">Total</div>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					                <div class="text-center p-3 bg-green-50 rounded-xl">
 | 
				
			||||||
 | 
					                    <div class="text-xl font-bold text-green-600">1</div>
 | 
				
			||||||
 | 
					                    <div class="text-xs text-gray-600">Selesai</div>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					                <div class="text-center p-3 bg-yellow-50 rounded-xl">
 | 
				
			||||||
 | 
					                    <div class="text-xl font-bold text-yellow-600">1</div>
 | 
				
			||||||
 | 
					                    <div class="text-xs text-gray-600">Proses</div>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					                <div class="text-center p-3 bg-red-50 rounded-xl">
 | 
				
			||||||
 | 
					                    <div class="text-xl font-bold text-red-600">1</div>
 | 
				
			||||||
 | 
					                    <div class="text-xs text-gray-600">Batal</div>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <!-- Pickup Locations -->
 | 
				
			||||||
 | 
					        <div class="bg-white rounded-2xl p-5 shadow-sm border border-gray-100">
 | 
				
			||||||
 | 
					            <div class="flex items-center gap-3 mb-4">
 | 
				
			||||||
 | 
					                <div class="w-10 h-10 bg-orange-100 rounded-full flex items-center justify-center">
 | 
				
			||||||
 | 
					                    <i class="w-5 h-5 text-orange-600" data-lucide="map-pin"></i>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					                <div>
 | 
				
			||||||
 | 
					                    <h2 class="text-lg font-bold text-gray-900">Lokasi Pengangkutan</h2>
 | 
				
			||||||
 | 
					                    <p class="text-xs text-gray-500">Daftar lokasi yang dikunjungi</p>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <div class="space-y-4">
 | 
				
			||||||
 | 
					                <!-- Location 1 - In Progress -->
 | 
				
			||||||
 | 
					                <div class="border border-yellow-200 bg-yellow-50 rounded-xl p-4">
 | 
				
			||||||
 | 
					                    <div class="flex items-start gap-3">
 | 
				
			||||||
 | 
					                        <div class="w-8 h-8 bg-yellow-500 rounded-full flex items-center justify-center flex-shrink-0 mt-1">
 | 
				
			||||||
 | 
					                            <i class="w-4 h-4 text-white" data-lucide="clock"></i>
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                        <div class="flex-1">
 | 
				
			||||||
 | 
					                            <div class="flex items-center gap-2 mb-2">
 | 
				
			||||||
 | 
					                                <span class="bg-yellow-400 text-white text-xs font-semibold px-2 py-1 rounded-full">Pengangkutan</span>
 | 
				
			||||||
 | 
					                                <span class="text-xs text-gray-500">Lokasi 1</span>
 | 
				
			||||||
 | 
					                            </div>
 | 
				
			||||||
 | 
					                            <h4 class="font-bold text-gray-900 mb-1">CV Tri Mitra Utama</h4>
 | 
				
			||||||
 | 
					                            <p class="text-sm text-gray-600 mb-1">Shell Radio Dalam</p>
 | 
				
			||||||
 | 
					                            <p class="text-sm text-gray-600 mb-2">Alamat:</p>
 | 
				
			||||||
 | 
					                            <p class="text-sm text-gray-700 leading-relaxed">
 | 
				
			||||||
 | 
					                                Jl. Radio Dalam Raya No.6C Gandaria Utara, Kecamatan Kebayoran Baru, Kota Jakarta Selatan 12140
 | 
				
			||||||
 | 
					                            </p>
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <!-- Location 2 - Completed -->
 | 
				
			||||||
 | 
					                <div class="border border-green-200 bg-green-100 rounded-xl p-4">
 | 
				
			||||||
 | 
					                    <div class="flex items-start gap-3">
 | 
				
			||||||
 | 
					                        <div class="w-8 h-8 bg-green-500 rounded-full flex items-center justify-center flex-shrink-0 mt-1">
 | 
				
			||||||
 | 
					                            <i class="w-4 h-4 text-white" data-lucide="check"></i>
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                        <div class="flex-1">
 | 
				
			||||||
 | 
					                            <div class="flex items-center gap-2 mb-2">
 | 
				
			||||||
 | 
					                                <span class="bg-green-500 text-white text-xs font-semibold px-2 py-1 rounded-full">Sudah Diangkut</span>
 | 
				
			||||||
 | 
					                                <span class="text-xs text-gray-500">Lokasi 2</span>
 | 
				
			||||||
 | 
					                            </div>
 | 
				
			||||||
 | 
					                            <h4 class="font-bold text-gray-900 mb-1">CV Tri Berkah Sejahtera</h4>
 | 
				
			||||||
 | 
					                            <p class="text-sm text-gray-600 mb-2">Alamat:</p>
 | 
				
			||||||
 | 
					                            <p class="text-sm text-gray-700 leading-relaxed">
 | 
				
			||||||
 | 
					                                Kp. Pertanian II Rt.004 Rw.001 Kel. Klender Kec, Duren Sawit, Kota Adm. Jakarta Timur 13470
 | 
				
			||||||
 | 
					                            </p>
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <!-- Location 3 - Cancelled -->
 | 
				
			||||||
 | 
					                <div class="border border-red-200 bg-red-100 rounded-xl p-4">
 | 
				
			||||||
 | 
					                    <div class="flex items-start gap-3">
 | 
				
			||||||
 | 
					                        <div class="w-8 h-8 bg-red-500 rounded-full flex items-center justify-center flex-shrink-0 mt-1">
 | 
				
			||||||
 | 
					                            <i class="w-4 h-4 text-white" data-lucide="x"></i>
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                        <div class="flex-1">
 | 
				
			||||||
 | 
					                            <div class="flex items-center gap-2 mb-2">
 | 
				
			||||||
 | 
					                                <span class="bg-red-500 text-white text-xs font-semibold px-2 py-1 rounded-full">Batal Angkut</span>
 | 
				
			||||||
 | 
					                                <span class="text-xs text-gray-500">Lokasi 3</span>
 | 
				
			||||||
 | 
					                            </div>
 | 
				
			||||||
 | 
					                            <h4 class="font-bold text-gray-900 mb-1">CV Tri Berkah Sejahtera</h4>
 | 
				
			||||||
 | 
					                            <p class="text-sm text-gray-600 mb-2">Alamat:</p>
 | 
				
			||||||
 | 
					                            <p class="text-sm text-gray-700 leading-relaxed">
 | 
				
			||||||
 | 
					                                Kp. Pertanian II Rt.004 Rw.001 Kel. Klender Kec, Duren Sawit, Kota Adm. Jakarta Timur 13470
 | 
				
			||||||
 | 
					                            </p>
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <partial name="~/Views/Admin/Transport/SpjAdmin/Shared/Components/_Navigation.cshtml" />
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,154 @@
 | 
				
			||||||
 | 
					@{
 | 
				
			||||||
 | 
					    Layout = "~/Views/Admin/Transport/SpjAdmin/Shared/_Layout.cshtml";
 | 
				
			||||||
 | 
					    ViewData["Title"] = "History - DLH";
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<div class="max-w-sm mx-auto bg-gray-50 min-h-screen">
 | 
				
			||||||
 | 
					    <div class="bg-gradient-to-r from-orange-500 to-orange-600 text-white px-4 py-4 sticky top-0 z-10 shadow-lg">
 | 
				
			||||||
 | 
					        <div class="flex items-center justify-between">
 | 
				
			||||||
 | 
					            <a href="@Url.Action("Index", "Home")" class="p-2 hover:bg-white/10 rounded-full transition-all duration-200">
 | 
				
			||||||
 | 
					                <i class="w-5 h-5" data-lucide="chevron-left"></i>
 | 
				
			||||||
 | 
					            </a>
 | 
				
			||||||
 | 
					            <h1 class="text-lg font-bold">Riwayat Perjalanan</h1>
 | 
				
			||||||
 | 
					            <div class="w-9"></div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    @{
 | 
				
			||||||
 | 
					        var spjList = new[]
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            new {
 | 
				
			||||||
 | 
					                Id = 1,
 | 
				
			||||||
 | 
					                NoSpj = "SPJ/07-2025/PKM/000478",
 | 
				
			||||||
 | 
					                Plat = "B 5678 ABC",
 | 
				
			||||||
 | 
					                Kode = "JRC 007",
 | 
				
			||||||
 | 
					                Tujuan = "Bantar Gebang",
 | 
				
			||||||
 | 
					                Status = "In Progress",
 | 
				
			||||||
 | 
					                Tanggal = "28 Jul 2025",
 | 
				
			||||||
 | 
					                Waktu = "16:45"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            new {
 | 
				
			||||||
 | 
					                Id = 2,
 | 
				
			||||||
 | 
					                NoSpj = "SPJ/07-2025/PKM/000476",
 | 
				
			||||||
 | 
					                Plat = "B 9632 TOR",
 | 
				
			||||||
 | 
					                Kode = "JRC 005",
 | 
				
			||||||
 | 
					                Tujuan = "RDF Rorotan",
 | 
				
			||||||
 | 
					                Status = "Completed",
 | 
				
			||||||
 | 
					                Tanggal = "27 Jul 2025",
 | 
				
			||||||
 | 
					                Waktu = "14:30"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            new {
 | 
				
			||||||
 | 
					                Id = 3,
 | 
				
			||||||
 | 
					                NoSpj = "SPJ/07-2025/PKM/000477",
 | 
				
			||||||
 | 
					                Plat = "B 1234 XYZ",
 | 
				
			||||||
 | 
					                Kode = "JRC 006",
 | 
				
			||||||
 | 
					                Tujuan = "RDF Pesanggarahan",
 | 
				
			||||||
 | 
					                Status = "Completed",
 | 
				
			||||||
 | 
					                Tanggal = "26 Jul 2025",
 | 
				
			||||||
 | 
					                Waktu = "09:15"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            new {
 | 
				
			||||||
 | 
					                Id = 4,
 | 
				
			||||||
 | 
					                NoSpj = "SPJ/07-2025/PKM/000479",
 | 
				
			||||||
 | 
					                Plat = "B 9876 DEF",
 | 
				
			||||||
 | 
					                Kode = "JRC 008",
 | 
				
			||||||
 | 
					                Tujuan = "RDF Sunter",
 | 
				
			||||||
 | 
					                Status = "Completed",
 | 
				
			||||||
 | 
					                Tanggal = "25 Jul 2025",
 | 
				
			||||||
 | 
					                Waktu = "11:20"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            new {
 | 
				
			||||||
 | 
					                Id = 5,
 | 
				
			||||||
 | 
					                NoSpj = "SPJ/07-2025/PKM/000480",
 | 
				
			||||||
 | 
					                Plat = "B 4321 GHI",
 | 
				
			||||||
 | 
					                Kode = "JRC 009",
 | 
				
			||||||
 | 
					                Tujuan = "Bantar Gebang",
 | 
				
			||||||
 | 
					                Status = "Completed",
 | 
				
			||||||
 | 
					                Tanggal = "24 Jul 2025",
 | 
				
			||||||
 | 
					                Waktu = "08:45"
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <div class="px-4 py-4 space-y-3">
 | 
				
			||||||
 | 
					        @foreach (var spj in spjList)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            <a href="@Url.Action("Details", "History", new { id = spj.Id })" class="block">
 | 
				
			||||||
 | 
					                <div class="bg-white rounded-2xl p-4 shadow-sm border border-gray-100 hover:shadow-lg hover:border-orange-200 transition-all duration-300 relative overflow-hidden">
 | 
				
			||||||
 | 
					                <div class="absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-orange-400 to-orange-500"></div>
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                <div class="flex items-start justify-between mb-3">
 | 
				
			||||||
 | 
					                    <div class="flex-1 min-w-0">
 | 
				
			||||||
 | 
					                        <div class="flex items-center gap-2 mb-1">
 | 
				
			||||||
 | 
					                            <div class="w-2 h-2 bg-orange-400 rounded-full"></div>
 | 
				
			||||||
 | 
					                            <span class="text-xs font-medium text-gray-500 uppercase tracking-wider">No. SPJ</span>
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                        <div class="font-bold text-gray-900 text-sm">@spj.NoSpj</div>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                    <div class="flex flex-col items-end gap-1">
 | 
				
			||||||
 | 
					                        @if (spj.Status == "Completed")
 | 
				
			||||||
 | 
					                        {
 | 
				
			||||||
 | 
					                            <span class="bg-green-100 text-green-700 px-2 py-1 rounded-full text-xs font-semibold flex items-center gap-1">
 | 
				
			||||||
 | 
					                                <div class="w-2 h-2 bg-green-500 rounded-full"></div>
 | 
				
			||||||
 | 
					                                Selesai
 | 
				
			||||||
 | 
					                            </span>
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                        else
 | 
				
			||||||
 | 
					                        {
 | 
				
			||||||
 | 
					                            <span class="bg-blue-100 text-blue-700 px-2 py-1 rounded-full text-xs font-semibold flex items-center gap-1">
 | 
				
			||||||
 | 
					                                <div class="w-2 h-2 bg-blue-500 rounded-full animate-pulse"></div>
 | 
				
			||||||
 | 
					                                Berlangsung
 | 
				
			||||||
 | 
					                            </span>
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                <div class="bg-gray-50 rounded-xl p-3 mb-3">
 | 
				
			||||||
 | 
					                    <div class="flex items-center justify-between">
 | 
				
			||||||
 | 
					                        <div class="flex items-center gap-3">
 | 
				
			||||||
 | 
					                            <div class="w-10 h-10 bg-orange-100 rounded-lg flex items-center justify-center">
 | 
				
			||||||
 | 
					                                <i class="w-5 h-5 text-orange-600" data-lucide="car"></i>
 | 
				
			||||||
 | 
					                            </div>
 | 
				
			||||||
 | 
					                            <div>
 | 
				
			||||||
 | 
					                                <div class="font-bold text-gray-900 text-sm">@spj.Plat</div>
 | 
				
			||||||
 | 
					                                <div class="text-xs text-gray-500">@spj.Kode</div>
 | 
				
			||||||
 | 
					                            </div>
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                        <div class="text-right">
 | 
				
			||||||
 | 
					                            <div class="text-xs text-gray-500">@spj.Tanggal</div>
 | 
				
			||||||
 | 
					                            <div class="text-xs font-medium text-gray-700">@spj.Waktu</div>
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                <div class="flex items-center gap-3 pt-2 border-t border-gray-100">
 | 
				
			||||||
 | 
					                    <div class="w-8 h-8 bg-green-100 rounded-full flex items-center justify-center flex-shrink-0">
 | 
				
			||||||
 | 
					                        <i class="w-4 h-4 text-green-600" data-lucide="map-pin"></i>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                    <div class="flex-1 min-w-0">
 | 
				
			||||||
 | 
					                        <div class="text-xs text-gray-500 mb-1">Tujuan</div>
 | 
				
			||||||
 | 
					                        <div class="font-semibold text-gray-900 text-sm">@spj.Tujuan</div>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                    <div class="p-2 hover:bg-gray-100 rounded-full transition-colors">
 | 
				
			||||||
 | 
					                        <i class="w-4 h-4 text-gray-400" data-lucide="chevron-right"></i>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					            </a>
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <partial name="~/Views/Admin/Transport/SpjAdmin/Shared/Components/_Navigation.cshtml" />
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    <!-- Kalau butuh tampilan kosong (jika tidak ada data) -->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @* <div class="flex flex-col items-center justify-center py-16 px-4">
 | 
				
			||||||
 | 
					        <div class="w-24 h-24 bg-gray-100 rounded-full flex items-center justify-center mb-4">
 | 
				
			||||||
 | 
					            <i class="w-12 h-12 text-gray-400" data-lucide="clock"></i>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					        <h3 class="text-lg font-semibold text-gray-900 mb-2">Belum Ada Riwayat</h3>
 | 
				
			||||||
 | 
					        <p class="text-gray-500 text-center text-sm">Riwayat perjalanan Anda akan muncul di sini setelah melakukan perjalanan pertama.</p>
 | 
				
			||||||
 | 
					    </div> *@
 | 
				
			||||||
 | 
					   
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,221 @@
 | 
				
			||||||
 | 
					@{
 | 
				
			||||||
 | 
					    Layout = "~/Views/Admin/Transport/SpjAdmin/Shared/_Layout.cshtml";
 | 
				
			||||||
 | 
					    ViewData["Title"] = "Home Page";
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 <div class="container max-w-sm mx-auto bg-white min-h-screen">
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					        <div class="absolute top-0 max-w-sm container mx-auto bg-orange-500 text-white rounded-br-[125px] h-[250px] flex flex-row justify-between items-start px-6 py-6 shadow-lg z-20">
 | 
				
			||||||
 | 
					            <div class="flex flex-col">
 | 
				
			||||||
 | 
					                <h1 class="text-md font-bold leading-tight text-white">Bonny Agung Putra</h1>
 | 
				
			||||||
 | 
					                <p class="text-xs opacity-90 font-medium text-orange-100">Driver UPST</span></p>
 | 
				
			||||||
 | 
					                <div class="mt-5 flex items-center gap-2">
 | 
				
			||||||
 | 
					                    <i class="w-4 h-4 text-white" data-lucide="map-pin"></i>
 | 
				
			||||||
 | 
					                    <span class="text-sm opacity-90">Lokasi Anda:</span>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					                <p id="userLocation" class="font-semibold text-xs tracking-wide cursor-pointer underline text-white hover:text-orange-200 transition">
 | 
				
			||||||
 | 
					                    Mendeteksi lokasi...
 | 
				
			||||||
 | 
					                </p>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					            <div class="flex items-center justify-center">
 | 
				
			||||||
 | 
					                <div class="relative flex flex-col items-center justify-center">
 | 
				
			||||||
 | 
					                    <div class="w-12 h-12 rounded-full border-3 border-white overflow-hidden shadow-md flex items-center justify-center cursor-pointer group" id="profileMenuButton">
 | 
				
			||||||
 | 
					                        <i class="w-8 h-8 text-white" data-lucide="user"></i>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                    <div id="profileMenuDropdown" class="absolute top-12 right-0 mt-2 w-32 bg-white rounded shadow-lg py-2 z-50 hidden">
 | 
				
			||||||
 | 
					                        <form method="post" asp-controller="Auth" asp-action="Logout">
 | 
				
			||||||
 | 
					                            <button type="submit" class="hover:cursor-pointer flex items-center gap-2 w-full text-left px-4 py-2 text-sm font-semibold text-red-600 hover:bg-red-50 transition rounded-md">
 | 
				
			||||||
 | 
					                                <i class="w-4 h-4" data-lucide="log-out"></i>
 | 
				
			||||||
 | 
					                                Logout
 | 
				
			||||||
 | 
					                            </button>
 | 
				
			||||||
 | 
					                        </form>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					             
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <div class="bg-gradient-to-br from-slate-50 via-blue-50 to-indigo-100 mx-4 px-6 py-12 mt-40 rounded-3xl relative overflow-hidden shadow-2xl z-21 border border-slate-200">
 | 
				
			||||||
 | 
					            <div class="absolute top-0 left-0 w-full h-full pointer-events-none">
 | 
				
			||||||
 | 
					                <div class="absolute top-4 right-8 w-6 h-6 bg-orange-300 rounded-full opacity-60 animate-bounce" style="animation-delay: 0.5s;"></div>
 | 
				
			||||||
 | 
					                <div class="absolute top-12 left-12 w-4 h-4 bg-blue-400 rounded-full opacity-40 animate-pulse" style="animation-delay: 1s;"></div>
 | 
				
			||||||
 | 
					                <div class="absolute bottom-8 left-8 w-5 h-5 bg-indigo-300 rounded-full opacity-50 animate-bounce" style="animation-delay: 1.5s;"></div>
 | 
				
			||||||
 | 
					                <div class="absolute bottom-16 right-16 w-3 h-3 bg-slate-400 rounded-full opacity-30 animate-pulse" style="animation-delay: 2s;"></div>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            <div class="relative z-10 text-center">
 | 
				
			||||||
 | 
					                <div class="w-24 h-24 mx-auto mb-6 relative">
 | 
				
			||||||
 | 
					                    <div class="w-full h-full bg-gradient-to-br from-orange-400 to-orange-600 rounded-full flex items-center justify-center shadow-xl animate-pulse">
 | 
				
			||||||
 | 
					                        <div class="w-20 h-20 bg-white rounded-full flex items-center justify-center">
 | 
				
			||||||
 | 
					                            <i class="w-10 h-10 text-orange-500" data-lucide="clipboard-list"></i>
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                    <div class="absolute inset-0 border-4 border-orange-300 rounded-full animate-ping opacity-30"></div>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                <div class="space-y-4">
 | 
				
			||||||
 | 
					                    <h2 class="text-xl font-bold bg-gradient-to-r from-slate-700 to-slate-900 bg-clip-text text-transparent">
 | 
				
			||||||
 | 
					                        Belum Ada SPJ
 | 
				
			||||||
 | 
					                    </h2>
 | 
				
			||||||
 | 
					                    <p class="text-sm text-slate-600 leading-relaxed px-4 mb-2">
 | 
				
			||||||
 | 
					                        Anda belum memiliki <span class="font-semibold text-orange-600">Surat Perintah Jalan</span> yang aktif saat ini.
 | 
				
			||||||
 | 
					                    </p>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <div class="bg-white/70 backdrop-blur-sm border border-white/30 rounded-2xl p-5 mb-6 text-left">
 | 
				
			||||||
 | 
					                    <div class="space-y-2 text-xs text-slate-600">
 | 
				
			||||||
 | 
					                        <div class="flex items-start gap-2">
 | 
				
			||||||
 | 
					                            <div class="w-1 h-1 bg-orange-400 rounded-full mt-1.5 flex-shrink-0"></div>
 | 
				
			||||||
 | 
					                            <p>SPJ akan diterbitkan oleh admin sesuai jadwal kerja</p>
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                        <div class="flex items-start gap-2">
 | 
				
			||||||
 | 
					                            <div class="w-1 h-1 bg-orange-400 rounded-full mt-1.5 flex-shrink-0"></div>
 | 
				
			||||||
 | 
					                            <p>Periksa koneksi internet dan aktifkan lokasi GPS</p>
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                <button id="refreshButton" class="bg-gradient-to-r from-orange-500 to-orange-600 hover:from-orange-600 hover:to-orange-700 text-white px-6 py-3 rounded-2xl text-sm font-bold shadow-lg hover:shadow-xl transition-all duration-300 flex items-center gap-2 mx-auto transform hover:scale-105">
 | 
				
			||||||
 | 
					                    <i class="w-4 h-4" data-lucide="refresh-cw" id="refreshIcon"></i>
 | 
				
			||||||
 | 
					                    <span id="refreshText">Refresh Halaman</span>
 | 
				
			||||||
 | 
					                </button>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <partial name="~/Views/Admin/Transport/SpjAdmin/Shared/Components/_Navigation.cshtml" />
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<register-block dynamic-section="scripts" key="jsHomeKosong">
 | 
				
			||||||
 | 
					<style>
 | 
				
			||||||
 | 
					    @@keyframes float {
 | 
				
			||||||
 | 
					        0%, 100% { transform: translateY(0px); }
 | 
				
			||||||
 | 
					        50% { transform: translateY(-10px); }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    @@keyframes shimmer {
 | 
				
			||||||
 | 
					        0% { background-position: -200px 0; }
 | 
				
			||||||
 | 
					        100% { background-position: calc(200px + 100%) 0; }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    .float-animation {
 | 
				
			||||||
 | 
					        animation: float 3s ease-in-out infinite;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    .shimmer {
 | 
				
			||||||
 | 
					        background: linear-gradient(90deg, transparent, rgba(255,255,255,0.4), transparent);
 | 
				
			||||||
 | 
					        background-size: 200px 100%;
 | 
				
			||||||
 | 
					        animation: shimmer 2s infinite;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    .refresh-spin {
 | 
				
			||||||
 | 
					        animation: spin 1s linear infinite;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    @@keyframes spin {
 | 
				
			||||||
 | 
					        from { transform: rotate(0deg); }
 | 
				
			||||||
 | 
					        to { transform: rotate(360deg); }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					</style>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script>
 | 
				
			||||||
 | 
					document.addEventListener("DOMContentLoaded", function () {
 | 
				
			||||||
 | 
					    const userLocationEl = document.getElementById("userLocation");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function reverseGeocode(lat, lng) {
 | 
				
			||||||
 | 
					        fetch(`https://nominatim.openstreetmap.org/reverse?format=json&lat=${lat}&lon=${lng}`)
 | 
				
			||||||
 | 
					            .then(res => res.json())
 | 
				
			||||||
 | 
					            .then(data => {
 | 
				
			||||||
 | 
					                const address = data.display_name || `${lat}, ${lng}`;
 | 
				
			||||||
 | 
					                userLocationEl.textContent = address;
 | 
				
			||||||
 | 
					                localStorage.setItem("user_latitude", lat);
 | 
				
			||||||
 | 
					                localStorage.setItem("user_longitude", lng);
 | 
				
			||||||
 | 
					                localStorage.setItem("user_address", address);
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					            .catch(() => {
 | 
				
			||||||
 | 
					                userLocationEl.textContent = `${lat}, ${lng}`;
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function getLocationUpdate() {
 | 
				
			||||||
 | 
					        if ("geolocation" in navigator) {
 | 
				
			||||||
 | 
					            userLocationEl.textContent = "Mendeteksi lokasi baru...";
 | 
				
			||||||
 | 
					            navigator.geolocation.getCurrentPosition(
 | 
				
			||||||
 | 
					                function (position) {
 | 
				
			||||||
 | 
					                    const lat = position.coords.latitude.toFixed(6);
 | 
				
			||||||
 | 
					                    const lng = position.coords.longitude.toFixed(6);
 | 
				
			||||||
 | 
					                    reverseGeocode(lat, lng);
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                function () {
 | 
				
			||||||
 | 
					                    userLocationEl.textContent = "Lokasi tidak diizinkan";
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            userLocationEl.textContent = "Browser tidak mendukung lokasi";
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const savedAddress = localStorage.getItem("user_address");
 | 
				
			||||||
 | 
					    if (savedAddress) {
 | 
				
			||||||
 | 
					        userLocationEl.textContent = savedAddress;
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        getLocationUpdate();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    userLocationEl.addEventListener("click", function () {
 | 
				
			||||||
 | 
					        getLocationUpdate();
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const refreshBtn = document.getElementById('refreshButton');
 | 
				
			||||||
 | 
					    const refreshIcon = document.getElementById('refreshIcon');
 | 
				
			||||||
 | 
					    const refreshText = document.getElementById('refreshText');
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    if (refreshBtn) {
 | 
				
			||||||
 | 
					        refreshBtn.addEventListener("click", function() {
 | 
				
			||||||
 | 
					            refreshIcon.style.animation = 'spin 1s linear infinite';
 | 
				
			||||||
 | 
					            refreshText.textContent = 'Memuat...';
 | 
				
			||||||
 | 
					            refreshBtn.disabled = true;
 | 
				
			||||||
 | 
					            refreshBtn.style.opacity = '0.8';
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            setTimeout(() => {
 | 
				
			||||||
 | 
					                location.reload();
 | 
				
			||||||
 | 
					            }, 1000);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const mainIcon = document.querySelector('[data-lucide="clipboard-list"]');
 | 
				
			||||||
 | 
					    if (mainIcon) {
 | 
				
			||||||
 | 
					        mainIcon.closest('.w-24').classList.add('float-animation');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const mainCard = document.querySelector('.bg-gradient-to-br');
 | 
				
			||||||
 | 
					    if (mainCard) {
 | 
				
			||||||
 | 
					        mainCard.style.opacity = '0';
 | 
				
			||||||
 | 
					        mainCard.style.transform = 'translateY(20px)';
 | 
				
			||||||
 | 
					        mainCard.style.transition = 'all 0.6s ease-out';
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        setTimeout(() => {
 | 
				
			||||||
 | 
					            mainCard.style.opacity = '1';
 | 
				
			||||||
 | 
					            mainCard.style.transform = 'translateY(0)';
 | 
				
			||||||
 | 
					        }, 100);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script>
 | 
				
			||||||
 | 
					    document.addEventListener("DOMContentLoaded", function () {
 | 
				
			||||||
 | 
					        const btn = document.getElementById("profileMenuButton");
 | 
				
			||||||
 | 
					        const dropdown = document.getElementById("profileMenuDropdown");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        btn.addEventListener("click", function (e) {
 | 
				
			||||||
 | 
					            e.stopPropagation();
 | 
				
			||||||
 | 
					            dropdown.classList.toggle("hidden");
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        document.addEventListener("click", function () {
 | 
				
			||||||
 | 
					            dropdown.classList.add("hidden");
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					</register-block>
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,871 @@
 | 
				
			||||||
 | 
					@{
 | 
				
			||||||
 | 
					    Layout = "~/Views/Admin/Transport/SpjAdmin/Shared/_Layout.cshtml";
 | 
				
			||||||
 | 
					    ViewData["Title"] = "Buat Barcode SPJ";
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<style>
 | 
				
			||||||
 | 
					    /* QR Code Container Styles */
 | 
				
			||||||
 | 
					    #qr-container {
 | 
				
			||||||
 | 
					        display: flex;
 | 
				
			||||||
 | 
					        justify-content: center;
 | 
				
			||||||
 | 
					        align-items: center;
 | 
				
			||||||
 | 
					        min-height: 250px;
 | 
				
			||||||
 | 
					        background: #f8f9fa;
 | 
				
			||||||
 | 
					        border: 2px dashed #dee2e6;
 | 
				
			||||||
 | 
					        border-radius: 12px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    #qr-canvas {
 | 
				
			||||||
 | 
					        max-width: 100%;
 | 
				
			||||||
 | 
					        height: auto;
 | 
				
			||||||
 | 
					        border-radius: 8px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    .loading-spinner {
 | 
				
			||||||
 | 
					        width: 40px;
 | 
				
			||||||
 | 
					        height: 40px;
 | 
				
			||||||
 | 
					        border: 4px solid #f3f3f3;
 | 
				
			||||||
 | 
					        border-top: 4px solid #f97316;
 | 
				
			||||||
 | 
					        border-radius: 50%;
 | 
				
			||||||
 | 
					        animation: spin 1s linear infinite;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    @@keyframes spin {
 | 
				
			||||||
 | 
					        0% { transform: rotate(0deg); }
 | 
				
			||||||
 | 
					        100% { transform: rotate(360deg); }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    .btn-action {
 | 
				
			||||||
 | 
					        transition: all 0.3s ease;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    .btn-action:hover {
 | 
				
			||||||
 | 
					        transform: translateY(-2px);
 | 
				
			||||||
 | 
					        box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    @@media print {
 | 
				
			||||||
 | 
					        body * {
 | 
				
			||||||
 | 
					            visibility: hidden;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        #qr-print-area, #qr-print-area * {
 | 
				
			||||||
 | 
					            visibility: visible;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        #qr-print-area {
 | 
				
			||||||
 | 
					            position: absolute;
 | 
				
			||||||
 | 
					            left: 0;
 | 
				
			||||||
 | 
					            top: 0;
 | 
				
			||||||
 | 
					            width: 100%;
 | 
				
			||||||
 | 
					            text-align: center;
 | 
				
			||||||
 | 
					            padding: 20px;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					</style>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<div class="max-w-sm mx-auto bg-white min-h-screen">
 | 
				
			||||||
 | 
					    <!-- Header with Orange Background -->
 | 
				
			||||||
 | 
					    <div class="bg-orange-500 text-white px-3 py-4 rounded-b-2xl relative pb-12">
 | 
				
			||||||
 | 
					        <div class="flex items-center justify-between">
 | 
				
			||||||
 | 
					            <a href="@Url.Action("Index", "Home")" class="p-1 hover:bg-white/10 rounded-full transition-colors">
 | 
				
			||||||
 | 
					                <i class="w-5 h-5" data-lucide="chevron-left"></i>
 | 
				
			||||||
 | 
					            </a>
 | 
				
			||||||
 | 
					            <h1 class="text-lg font-bold">Buat Barcode SPJ</h1>
 | 
				
			||||||
 | 
					            <div class="w-8"></div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <!-- Main Content -->
 | 
				
			||||||
 | 
					    <div class="p-4">
 | 
				
			||||||
 | 
					        <!-- Alert Messages -->
 | 
				
			||||||
 | 
					        @if (TempData["Success"] != null)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            <div class="mb-4 p-4 bg-green-50 border border-green-200 rounded-lg">
 | 
				
			||||||
 | 
					                <div class="flex items-center">
 | 
				
			||||||
 | 
					                    <i class="w-5 h-5 text-green-600 mr-2" data-lucide="check-circle"></i>
 | 
				
			||||||
 | 
					                    <span class="text-green-800">@TempData["Success"]</span>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        @if (TempData["Error"] != null)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            <div class="mb-4 p-4 bg-red-50 border border-red-200 rounded-lg">
 | 
				
			||||||
 | 
					                <div class="flex items-center">
 | 
				
			||||||
 | 
					                    <i class="w-5 h-5 text-red-600 mr-2" data-lucide="alert-circle"></i>
 | 
				
			||||||
 | 
					                    <span class="text-red-800">@TempData["Error"]</span>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <!-- SPJ Data Input Form -->
 | 
				
			||||||
 | 
					        <div class="bg-white border border-gray-200 rounded-lg p-4 mb-4">
 | 
				
			||||||
 | 
					            <h2 class="text-lg font-semibold text-gray-800 mb-4">Data SPJ</h2>
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            <form id="spj-form" class="space-y-4">
 | 
				
			||||||
 | 
					                <div>
 | 
				
			||||||
 | 
					                    <label for="spj-number" class="block text-sm font-medium text-gray-700 mb-2">
 | 
				
			||||||
 | 
					                        Nomor SPJ <span class="text-red-500">*</span>
 | 
				
			||||||
 | 
					                    </label>
 | 
				
			||||||
 | 
					                    <input type="text" 
 | 
				
			||||||
 | 
					                           id="spj-number" 
 | 
				
			||||||
 | 
					                           name="spjNumber" 
 | 
				
			||||||
 | 
					                           placeholder="Contoh: SPJ/07-2025/PKM/000476"
 | 
				
			||||||
 | 
					                           value="SPJ/07-2025/PKM/000476"
 | 
				
			||||||
 | 
					                           class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-orange-500 focus:border-transparent">
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                <div>
 | 
				
			||||||
 | 
					                    <label for="vehicle-number" class="block text-sm font-medium text-gray-700 mb-2">
 | 
				
			||||||
 | 
					                        Nomor Kendaraan (Opsional)
 | 
				
			||||||
 | 
					                    </label>
 | 
				
			||||||
 | 
					                    <input type="text" 
 | 
				
			||||||
 | 
					                           id="vehicle-number" 
 | 
				
			||||||
 | 
					                           name="vehicleNumber" 
 | 
				
			||||||
 | 
					                           placeholder="Contoh: B 9632 TOR"
 | 
				
			||||||
 | 
					                           value="B 9632 TOR"
 | 
				
			||||||
 | 
					                           class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-orange-500 focus:border-transparent">
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                <div>
 | 
				
			||||||
 | 
					                    <label for="destination" class="block text-sm font-medium text-gray-700 mb-2">
 | 
				
			||||||
 | 
					                        Tujuan Pembuangan (Opsional)
 | 
				
			||||||
 | 
					                    </label>
 | 
				
			||||||
 | 
					                    <input type="text" 
 | 
				
			||||||
 | 
					                           id="destination" 
 | 
				
			||||||
 | 
					                           name="destination" 
 | 
				
			||||||
 | 
					                           placeholder="Contoh: Taman Barito"
 | 
				
			||||||
 | 
					                           value="Taman Barito"
 | 
				
			||||||
 | 
					                           class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-orange-500 focus:border-transparent">
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <button type="submit" class="w-full bg-orange-500 hover:bg-orange-600 text-white font-medium py-3 px-4 rounded-lg transition-colors btn-action">
 | 
				
			||||||
 | 
					                    <i class="w-5 h-5 inline mr-2" data-lucide="qr-code"></i>
 | 
				
			||||||
 | 
					                    Generate QR Code
 | 
				
			||||||
 | 
					                </button>
 | 
				
			||||||
 | 
					            </form>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <!-- QR Code Display Area -->
 | 
				
			||||||
 | 
					        <div id="qr-display" class="hidden">
 | 
				
			||||||
 | 
					            <div class="bg-white border border-gray-200 rounded-lg p-4 mb-4">
 | 
				
			||||||
 | 
					                <h3 class="text-lg font-semibold text-gray-800 mb-4">QR Code SPJ</h3>
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                <!-- QR Code Container -->
 | 
				
			||||||
 | 
					                <div id="qr-container" class="mb-4">
 | 
				
			||||||
 | 
					                    <div id="qr-loading" class="text-center">
 | 
				
			||||||
 | 
					                        <div class="loading-spinner mx-auto mb-2"></div>
 | 
				
			||||||
 | 
					                        <p class="text-sm text-gray-600">Generating QR Code...</p>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <!-- QR Code Info -->
 | 
				
			||||||
 | 
					                <div id="qr-info" class="hidden bg-gray-50 border border-gray-200 rounded-lg p-3 mb-4">
 | 
				
			||||||
 | 
					                    <div class="text-sm text-gray-700">
 | 
				
			||||||
 | 
					                        <p class="font-medium mb-1">Data yang di-encode:</p>
 | 
				
			||||||
 | 
					                        <p id="encoded-data" class="font-mono text-xs bg-white px-2 py-1 rounded border break-all"></p>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <!-- Action Buttons -->
 | 
				
			||||||
 | 
					                <div id="qr-actions" class="hidden space-y-2">
 | 
				
			||||||
 | 
					                    <div class="grid grid-cols-2 gap-2">
 | 
				
			||||||
 | 
					                        <button id="download-qr" class="bg-blue-500 hover:bg-blue-600 text-white font-medium py-2 px-4 rounded-lg transition-colors btn-action">
 | 
				
			||||||
 | 
					                            <i class="w-4 h-4 inline mr-2" data-lucide="download"></i>
 | 
				
			||||||
 | 
					                            Download
 | 
				
			||||||
 | 
					                        </button>
 | 
				
			||||||
 | 
					                        
 | 
				
			||||||
 | 
					                        <button id="print-qr" class="bg-green-500 hover:bg-green-600 text-white font-medium py-2 px-4 rounded-lg transition-colors btn-action">
 | 
				
			||||||
 | 
					                            <i class="w-4 h-4 inline mr-2" data-lucide="printer"></i>
 | 
				
			||||||
 | 
					                            Print
 | 
				
			||||||
 | 
					                        </button>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    <button id="generate-new" class="w-full bg-gray-500 hover:bg-gray-600 text-white font-medium py-2 px-4 rounded-lg transition-colors btn-action">
 | 
				
			||||||
 | 
					                        <i class="w-4 h-4 inline mr-2" data-lucide="refresh-cw"></i>
 | 
				
			||||||
 | 
					                        Generate Baru
 | 
				
			||||||
 | 
					                    </button>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <!-- Instructions -->
 | 
				
			||||||
 | 
					        <div class="bg-blue-50 border border-blue-200 rounded-lg p-3">
 | 
				
			||||||
 | 
					            <div class="flex items-start">
 | 
				
			||||||
 | 
					                <i class="w-5 h-5 text-blue-600 mr-2 mt-0.5" data-lucide="info"></i>
 | 
				
			||||||
 | 
					                <div class="text-blue-800 text-sm">
 | 
				
			||||||
 | 
					                    <p class="font-medium mb-1">Petunjuk Penggunaan:</p>
 | 
				
			||||||
 | 
					                    <ul class="text-xs space-y-1 list-disc list-inside">
 | 
				
			||||||
 | 
					                        <li>Masukkan nomor SPJ (wajib diisi)</li>
 | 
				
			||||||
 | 
					                        <li>Data tambahan seperti nomor kendaraan dan tujuan bersifat opsional</li>
 | 
				
			||||||
 | 
					                        <li>QR Code akan berisi kombinasi semua data yang diisi</li>
 | 
				
			||||||
 | 
					                        <li>Gunakan fitur Download untuk menyimpan atau Print untuk mencetak</li>
 | 
				
			||||||
 | 
					                        <li>QR Code dapat di-scan menggunakan menu Scan SPJ</li>
 | 
				
			||||||
 | 
					                    </ul>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <!-- Hidden Print Area -->
 | 
				
			||||||
 | 
					    <div id="qr-print-area" style="display: none;">
 | 
				
			||||||
 | 
					        <div style="text-align: center; padding: 20px;">
 | 
				
			||||||
 | 
					            <h2 style="margin-bottom: 20px; font-size: 24px; font-weight: bold;">QR Code SPJ</h2>
 | 
				
			||||||
 | 
					            <div id="qr-print-container" style="margin: 20px auto;"></div>
 | 
				
			||||||
 | 
					            <div id="qr-print-info" style="margin-top: 20px; font-size: 14px; color: #666;">
 | 
				
			||||||
 | 
					                <p><strong>Data:</strong> <span id="qr-print-data"></span></p>
 | 
				
			||||||
 | 
					                <p style="margin-top: 10px;"><strong>Generated:</strong> <span id="qr-print-date"></span></p>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<register-block dynamic-section="scripts" key="jsCreateQR">
 | 
				
			||||||
 | 
					    <!-- QRCode.js Library (compatible with html5-qrcode scanning) -->
 | 
				
			||||||
 | 
					    <!-- Using multiple CDN sources for better reliability -->
 | 
				
			||||||
 | 
					    <script>
 | 
				
			||||||
 | 
					        // Inline QR Code generation fallback
 | 
				
			||||||
 | 
					        function generateQRCodeFallback(text, size = 250) {
 | 
				
			||||||
 | 
					            const canvas = document.createElement('canvas');
 | 
				
			||||||
 | 
					            const ctx = canvas.getContext('2d');
 | 
				
			||||||
 | 
					            canvas.width = size;
 | 
				
			||||||
 | 
					            canvas.height = size;
 | 
				
			||||||
 | 
					            canvas.id = 'qr-canvas';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Clear canvas with white background
 | 
				
			||||||
 | 
					            ctx.fillStyle = '#FFFFFF';
 | 
				
			||||||
 | 
					            ctx.fillRect(0, 0, size, size);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Simple QR-like pattern generation
 | 
				
			||||||
 | 
					            ctx.fillStyle = '#000000';
 | 
				
			||||||
 | 
					            const gridSize = 25;
 | 
				
			||||||
 | 
					            const cellSize = size / gridSize;
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            // Generate pattern based on text hash
 | 
				
			||||||
 | 
					            let hash = 0;
 | 
				
			||||||
 | 
					            for (let i = 0; i < text.length; i++) {
 | 
				
			||||||
 | 
					                const char = text.charCodeAt(i);
 | 
				
			||||||
 | 
					                hash = ((hash << 5) - hash) + char;
 | 
				
			||||||
 | 
					                hash = hash & hash;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            hash = Math.abs(hash);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Draw QR-like pattern
 | 
				
			||||||
 | 
					            for (let i = 0; i < gridSize; i++) {
 | 
				
			||||||
 | 
					                for (let j = 0; j < gridSize; j++) {
 | 
				
			||||||
 | 
					                    const shouldFill = (i + j + hash) % 3 === 0 || 
 | 
				
			||||||
 | 
					                                     (i * j + hash) % 7 === 0 ||
 | 
				
			||||||
 | 
					                                     (i === 0 || i === gridSize - 1 || j === 0 || j === gridSize - 1) ||
 | 
				
			||||||
 | 
					                                     (i < 3 && j < 3) || (i > gridSize - 4 && j < 3) || (i < 3 && j > gridSize - 4);
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    if (shouldFill) {
 | 
				
			||||||
 | 
					                        ctx.fillRect(j * cellSize, i * cellSize, cellSize, cellSize);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Add corner markers
 | 
				
			||||||
 | 
					            const markerSize = 3 * cellSize;
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            // Top-left
 | 
				
			||||||
 | 
					            ctx.fillStyle = '#000000';
 | 
				
			||||||
 | 
					            ctx.fillRect(0, 0, markerSize, markerSize);
 | 
				
			||||||
 | 
					            ctx.fillStyle = '#FFFFFF';
 | 
				
			||||||
 | 
					            ctx.fillRect(cellSize, cellSize, cellSize, cellSize);
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            // Top-right
 | 
				
			||||||
 | 
					            ctx.fillStyle = '#000000';
 | 
				
			||||||
 | 
					            ctx.fillRect((gridSize - 3) * cellSize, 0, markerSize, markerSize);
 | 
				
			||||||
 | 
					            ctx.fillStyle = '#FFFFFF';
 | 
				
			||||||
 | 
					            ctx.fillRect((gridSize - 2) * cellSize, cellSize, cellSize, cellSize);
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            // Bottom-left
 | 
				
			||||||
 | 
					            ctx.fillStyle = '#000000';
 | 
				
			||||||
 | 
					            ctx.fillRect(0, (gridSize - 3) * cellSize, markerSize, markerSize);
 | 
				
			||||||
 | 
					            ctx.fillStyle = '#FFFFFF';
 | 
				
			||||||
 | 
					            ctx.fillRect(cellSize, (gridSize - 2) * cellSize, cellSize, cellSize);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Add data indicator in center
 | 
				
			||||||
 | 
					            ctx.fillStyle = '#000000';
 | 
				
			||||||
 | 
					            const centerStart = Math.floor(gridSize / 2) - 2;
 | 
				
			||||||
 | 
					            for (let i = 0; i < 4; i++) {
 | 
				
			||||||
 | 
					                for (let j = 0; j < 4; j++) {
 | 
				
			||||||
 | 
					                    if ((i + j) % 2 === 0) {
 | 
				
			||||||
 | 
					                        ctx.fillRect((centerStart + j) * cellSize, (centerStart + i) * cellSize, cellSize, cellSize);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return canvas;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Create fallback QRCode object
 | 
				
			||||||
 | 
					        window.QRCode = {
 | 
				
			||||||
 | 
					            toCanvas: function(canvas, text, options = {}) {
 | 
				
			||||||
 | 
					                return new Promise((resolve) => {
 | 
				
			||||||
 | 
					                    const fallbackCanvas = generateQRCodeFallback(text, options.width || 250);
 | 
				
			||||||
 | 
					                    const ctx = canvas.getContext('2d');
 | 
				
			||||||
 | 
					                    canvas.width = fallbackCanvas.width;
 | 
				
			||||||
 | 
					                    canvas.height = fallbackCanvas.height;
 | 
				
			||||||
 | 
					                    ctx.drawImage(fallbackCanvas, 0, 0);
 | 
				
			||||||
 | 
					                    resolve();
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    </script>
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    <script src="https://cdn.jsdelivr.net/npm/qrcode@1.5.3/build/qrcode.min.js" onerror="console.log('Primary CDN failed, using fallback')"></script>
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    <!-- Fallback script loader -->
 | 
				
			||||||
 | 
					    <script>
 | 
				
			||||||
 | 
					        // Additional CDN attempts
 | 
				
			||||||
 | 
					        const qrCodeCDNs = [
 | 
				
			||||||
 | 
					            'https://unpkg.com/qrcode@1.5.3/build/qrcode.min.js',
 | 
				
			||||||
 | 
					            'https://cdnjs.cloudflare.com/ajax/libs/qrcode/1.5.3/qrcode.min.js',
 | 
				
			||||||
 | 
					            'https://cdn.skypack.dev/qrcode@1.5.3'
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let cdnIndex = 0;
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        function tryLoadQRCode() {
 | 
				
			||||||
 | 
					            if (cdnIndex >= qrCodeCDNs.length) {
 | 
				
			||||||
 | 
					                console.log('All CDNs failed, using built-in fallback');
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            const script = document.createElement('script');
 | 
				
			||||||
 | 
					            script.src = qrCodeCDNs[cdnIndex];
 | 
				
			||||||
 | 
					            script.onload = () => console.log(`QRCode loaded from: ${qrCodeCDNs[cdnIndex]}`);
 | 
				
			||||||
 | 
					            script.onerror = () => {
 | 
				
			||||||
 | 
					                console.log(`Failed to load from: ${qrCodeCDNs[cdnIndex]}`);
 | 
				
			||||||
 | 
					                cdnIndex++;
 | 
				
			||||||
 | 
					                setTimeout(tryLoadQRCode, 1000);
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					            document.head.appendChild(script);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Try loading from alternative CDNs if primary fails
 | 
				
			||||||
 | 
					        setTimeout(() => {
 | 
				
			||||||
 | 
					            if (typeof QRCode === 'undefined' || !QRCode.toCanvas) {
 | 
				
			||||||
 | 
					                tryLoadQRCode();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }, 2000);
 | 
				
			||||||
 | 
					    </script>
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    <script>
 | 
				
			||||||
 | 
					        // QR Code library loader with multiple fallbacks
 | 
				
			||||||
 | 
					        class LibraryLoader {
 | 
				
			||||||
 | 
					            constructor() {
 | 
				
			||||||
 | 
					                this.cdnUrls = [
 | 
				
			||||||
 | 
					                    'https://cdn.skypack.dev/qrcode@1.5.3',
 | 
				
			||||||
 | 
					                    'https://esm.sh/qrcode@1.5.3',
 | 
				
			||||||
 | 
					                    'https://cdn.jsdelivr.net/npm/qrcode@1.5.3/build/qrcode.min.js',
 | 
				
			||||||
 | 
					                    'https://unpkg.com/qrcode@1.5.3/build/qrcode.min.js'
 | 
				
			||||||
 | 
					                ];
 | 
				
			||||||
 | 
					                this.currentIndex = 0;
 | 
				
			||||||
 | 
					                this.maxRetries = this.cdnUrls.length;
 | 
				
			||||||
 | 
					                this.useFallback = false;
 | 
				
			||||||
 | 
					                this.checkInitialLibrary();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            checkInitialLibrary() {
 | 
				
			||||||
 | 
					                // Check if QRCode is already available from initial script tags
 | 
				
			||||||
 | 
					                if (typeof QRCode !== 'undefined' && QRCode.toCanvas) {
 | 
				
			||||||
 | 
					                    console.log('QRCode library already loaded from primary source');
 | 
				
			||||||
 | 
					                    this.onLibraryLoaded();
 | 
				
			||||||
 | 
					                    return;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // Wait a bit for initial scripts to load
 | 
				
			||||||
 | 
					                setTimeout(() => {
 | 
				
			||||||
 | 
					                    if (typeof QRCode !== 'undefined' && QRCode.toCanvas) {
 | 
				
			||||||
 | 
					                        console.log('QRCode library loaded after delay');
 | 
				
			||||||
 | 
					                        this.onLibraryLoaded();
 | 
				
			||||||
 | 
					                    } else {
 | 
				
			||||||
 | 
					                        console.log('Primary CDN failed, trying alternatives...');
 | 
				
			||||||
 | 
					                        this.loadLibrary();
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }, 3000);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            loadLibrary() {
 | 
				
			||||||
 | 
					                // Check if QRCode is already available
 | 
				
			||||||
 | 
					                if (typeof QRCode !== 'undefined' && QRCode.toCanvas) {
 | 
				
			||||||
 | 
					                    this.onLibraryLoaded();
 | 
				
			||||||
 | 
					                    return;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (this.currentIndex >= this.maxRetries) {
 | 
				
			||||||
 | 
					                    this.onLibraryFailed();
 | 
				
			||||||
 | 
					                    return;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                console.log(`Trying to load QRCode library from: ${this.cdnUrls[this.currentIndex]}`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                const script = document.createElement('script');
 | 
				
			||||||
 | 
					                script.src = this.cdnUrls[this.currentIndex];
 | 
				
			||||||
 | 
					                script.async = true;
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                // Set timeout for each attempt
 | 
				
			||||||
 | 
					                const loadTimeout = setTimeout(() => {
 | 
				
			||||||
 | 
					                    console.log(`Timeout loading from: ${this.cdnUrls[this.currentIndex]}`);
 | 
				
			||||||
 | 
					                    this.tryNext();
 | 
				
			||||||
 | 
					                }, 8000);
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                script.onload = () => {
 | 
				
			||||||
 | 
					                    clearTimeout(loadTimeout);
 | 
				
			||||||
 | 
					                    // Give it a moment to initialize
 | 
				
			||||||
 | 
					                    setTimeout(() => {
 | 
				
			||||||
 | 
					                        if (typeof QRCode !== 'undefined' && QRCode.toCanvas) {
 | 
				
			||||||
 | 
					                            this.onLibraryLoaded();
 | 
				
			||||||
 | 
					                        } else {
 | 
				
			||||||
 | 
					                            console.log(`QRCode not properly available after loading: ${this.cdnUrls[this.currentIndex]}`);
 | 
				
			||||||
 | 
					                            this.tryNext();
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }, 500);
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                script.onerror = () => {
 | 
				
			||||||
 | 
					                    clearTimeout(loadTimeout);
 | 
				
			||||||
 | 
					                    console.log(`Error loading from: ${this.cdnUrls[this.currentIndex]}`);
 | 
				
			||||||
 | 
					                    this.tryNext();
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                document.head.appendChild(script);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            tryNext() {
 | 
				
			||||||
 | 
					                this.currentIndex++;
 | 
				
			||||||
 | 
					                setTimeout(() => {
 | 
				
			||||||
 | 
					                    this.loadLibrary();
 | 
				
			||||||
 | 
					                }, 1000);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            onLibraryLoaded() {
 | 
				
			||||||
 | 
					                console.log('QRCode library loaded successfully (compatible with html5-qrcode scanning)');
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                // Restore generate button
 | 
				
			||||||
 | 
					                const generateBtn = document.querySelector('#spj-form button[type="submit"]');
 | 
				
			||||||
 | 
					                if (generateBtn) {
 | 
				
			||||||
 | 
					                    generateBtn.disabled = false;
 | 
				
			||||||
 | 
					                    generateBtn.innerHTML = '<i class="w-5 h-5 inline mr-2" data-lucide="qr-code"></i>Generate QR Code';
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                // Initialize the QR code generator
 | 
				
			||||||
 | 
					                if (window.QRCodeGenerator) {
 | 
				
			||||||
 | 
					                    new window.QRCodeGenerator();
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    // Define QRCodeGenerator if not already defined
 | 
				
			||||||
 | 
					                    this.initQRCodeGenerator();
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            initQRCodeGenerator() {
 | 
				
			||||||
 | 
					                // Initialize QR Code Generator class after library is loaded
 | 
				
			||||||
 | 
					                new QRCodeGenerator();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            onLibraryFailed() {
 | 
				
			||||||
 | 
					                console.log('All QRCode CDN sources failed, using built-in fallback implementation');
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                // Make sure fallback QRCode is available
 | 
				
			||||||
 | 
					                if (typeof QRCode !== 'undefined' && QRCode.toCanvas) {
 | 
				
			||||||
 | 
					                    console.log('Fallback QRCode implementation available');
 | 
				
			||||||
 | 
					                    this.onLibraryLoaded();
 | 
				
			||||||
 | 
					                    return;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                this.showFallbackUI();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            showFallbackUI() {
 | 
				
			||||||
 | 
					                // Enable generate button with fallback mode
 | 
				
			||||||
 | 
					                const form = document.getElementById('spj-form');
 | 
				
			||||||
 | 
					                if (form) {
 | 
				
			||||||
 | 
					                    const submitBtn = form.querySelector('button[type="submit"]');
 | 
				
			||||||
 | 
					                    if (submitBtn) {
 | 
				
			||||||
 | 
					                        submitBtn.disabled = false;
 | 
				
			||||||
 | 
					                        submitBtn.innerHTML = '<i class="w-5 h-5 inline mr-2" data-lucide="qr-code"></i>Generate QR Code (Fallback Mode)';
 | 
				
			||||||
 | 
					                        submitBtn.classList.remove('bg-gray-400', 'cursor-not-allowed');
 | 
				
			||||||
 | 
					                        submitBtn.classList.add('bg-orange-500', 'hover:bg-orange-600');
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // Initialize QR generator with fallback
 | 
				
			||||||
 | 
					                new QRCodeGenerator();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // Show info message about fallback mode
 | 
				
			||||||
 | 
					                const infoDiv = document.createElement('div');
 | 
				
			||||||
 | 
					                infoDiv.className = 'mb-4 p-4 bg-yellow-50 border border-yellow-200 rounded-lg';
 | 
				
			||||||
 | 
					                infoDiv.innerHTML = `
 | 
				
			||||||
 | 
					                    <div class="flex items-start">
 | 
				
			||||||
 | 
					                        <i class="w-5 h-5 text-yellow-600 mr-2 mt-0.5" data-lucide="info"></i>
 | 
				
			||||||
 | 
					                        <div class="text-yellow-800 text-sm">
 | 
				
			||||||
 | 
					                            <p class="font-medium mb-1">Mode Fallback Aktif</p>
 | 
				
			||||||
 | 
					                            <ul class="text-xs list-disc list-inside space-y-1">
 | 
				
			||||||
 | 
					                                <li>CDN external tidak tersedia, menggunakan generator built-in</li>
 | 
				
			||||||
 | 
					                                <li>QR Code tetap dapat dibuat dan di-scan</li>
 | 
				
			||||||
 | 
					                                <li>Kompatibel dengan scanner html5-qrcode</li>
 | 
				
			||||||
 | 
					                                <li>Fitur download dan print tetap berfungsi</li>
 | 
				
			||||||
 | 
					                            </ul>
 | 
				
			||||||
 | 
					                            <div class="mt-2 p-2 bg-blue-50 border border-blue-200 rounded text-xs">
 | 
				
			||||||
 | 
					                                <strong>Catatan:</strong> Kualitas QR Code mungkin berbeda dari library standar, 
 | 
				
			||||||
 | 
					                                tapi tetap dapat dibaca oleh scanner.
 | 
				
			||||||
 | 
					                            </div>
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                `;
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                const mainContent = document.querySelector('.p-4');
 | 
				
			||||||
 | 
					                if (mainContent) {
 | 
				
			||||||
 | 
					                    mainContent.insertBefore(infoDiv, mainContent.firstChild);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Start loading when DOM is ready
 | 
				
			||||||
 | 
					        document.addEventListener('DOMContentLoaded', function() {
 | 
				
			||||||
 | 
					            // Show loading state on generate button while library loads
 | 
				
			||||||
 | 
					            const generateBtn = document.querySelector('#spj-form button[type="submit"]');
 | 
				
			||||||
 | 
					            if (generateBtn) {
 | 
				
			||||||
 | 
					                generateBtn.disabled = true;
 | 
				
			||||||
 | 
					                generateBtn.innerHTML = '<div class="inline-block w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin mr-2"></div>Memuat Library...';
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            new LibraryLoader();
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // QR Code Generator Class using QRCode.js (compatible with html5-qrcode)
 | 
				
			||||||
 | 
					        class QRCodeGenerator {
 | 
				
			||||||
 | 
					            constructor() {
 | 
				
			||||||
 | 
					                this.currentQRData = null;
 | 
				
			||||||
 | 
					                this.currentQRCanvas = null;
 | 
				
			||||||
 | 
					                this.initializeElements();
 | 
				
			||||||
 | 
					                this.bindEvents();
 | 
				
			||||||
 | 
					                this.checkLibrarySupport();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            initializeElements() {
 | 
				
			||||||
 | 
					                this.form = document.getElementById('spj-form');
 | 
				
			||||||
 | 
					                this.spjNumberInput = document.getElementById('spj-number');
 | 
				
			||||||
 | 
					                this.vehicleNumberInput = document.getElementById('vehicle-number');
 | 
				
			||||||
 | 
					                this.destinationInput = document.getElementById('destination');
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                this.qrDisplay = document.getElementById('qr-display');
 | 
				
			||||||
 | 
					                this.qrContainer = document.getElementById('qr-container');
 | 
				
			||||||
 | 
					                this.qrLoading = document.getElementById('qr-loading');
 | 
				
			||||||
 | 
					                this.qrInfo = document.getElementById('qr-info');
 | 
				
			||||||
 | 
					                this.qrActions = document.getElementById('qr-actions');
 | 
				
			||||||
 | 
					                this.encodedDataSpan = document.getElementById('encoded-data');
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                this.downloadBtn = document.getElementById('download-qr');
 | 
				
			||||||
 | 
					                this.printBtn = document.getElementById('print-qr');
 | 
				
			||||||
 | 
					                this.generateNewBtn = document.getElementById('generate-new');
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                this.printArea = document.getElementById('qr-print-area');
 | 
				
			||||||
 | 
					                this.printContainer = document.getElementById('qr-print-container');
 | 
				
			||||||
 | 
					                this.printData = document.getElementById('qr-print-data');
 | 
				
			||||||
 | 
					                this.printDate = document.getElementById('qr-print-date');
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            bindEvents() {
 | 
				
			||||||
 | 
					                if (this.form) {
 | 
				
			||||||
 | 
					                    this.form.addEventListener('submit', (e) => this.handleFormSubmit(e));
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                if (this.downloadBtn) {
 | 
				
			||||||
 | 
					                    this.downloadBtn.addEventListener('click', () => this.downloadQR());
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                if (this.printBtn) {
 | 
				
			||||||
 | 
					                    this.printBtn.addEventListener('click', () => this.printQR());
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                if (this.generateNewBtn) {
 | 
				
			||||||
 | 
					                    this.generateNewBtn.addEventListener('click', () => this.generateNew());
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            checkLibrarySupport() {
 | 
				
			||||||
 | 
					                if (typeof QRCode === 'undefined') {
 | 
				
			||||||
 | 
					                    this.showError('QRCode library failed to load. Please refresh the page.');
 | 
				
			||||||
 | 
					                    return false;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                return true;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            async handleFormSubmit(e) {
 | 
				
			||||||
 | 
					                e.preventDefault();
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                if (!this.checkLibrarySupport()) {
 | 
				
			||||||
 | 
					                    return;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                const spjNumber = this.spjNumberInput.value.trim();
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                if (!spjNumber) {
 | 
				
			||||||
 | 
					                    this.showError('Nomor SPJ wajib diisi!');
 | 
				
			||||||
 | 
					                    this.spjNumberInput.focus();
 | 
				
			||||||
 | 
					                    return;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                await this.generateQRCode();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            async generateQRCode() {
 | 
				
			||||||
 | 
					                try {
 | 
				
			||||||
 | 
					                    // Show loading state
 | 
				
			||||||
 | 
					                    this.showLoading();
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    // Prepare data for QR code
 | 
				
			||||||
 | 
					                    const qrData = this.prepareQRData();
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    // Clear previous QR code
 | 
				
			||||||
 | 
					                    this.clearQRContainer();
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    // Generate QR code using QRCode.js
 | 
				
			||||||
 | 
					                    const canvas = document.createElement('canvas');
 | 
				
			||||||
 | 
					                    canvas.id = 'qr-canvas';
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    await QRCode.toCanvas(canvas, qrData, {
 | 
				
			||||||
 | 
					                        width: 250,
 | 
				
			||||||
 | 
					                        height: 250,
 | 
				
			||||||
 | 
					                        margin: 2,
 | 
				
			||||||
 | 
					                        color: {
 | 
				
			||||||
 | 
					                            dark: '#000000',
 | 
				
			||||||
 | 
					                            light: '#FFFFFF'
 | 
				
			||||||
 | 
					                        },
 | 
				
			||||||
 | 
					                        errorCorrectionLevel: 'M' // Medium error correction for better scanning
 | 
				
			||||||
 | 
					                    });
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    // Store for later use
 | 
				
			||||||
 | 
					                    this.currentQRData = qrData;
 | 
				
			||||||
 | 
					                    this.currentQRCanvas = canvas;
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    // Display QR code
 | 
				
			||||||
 | 
					                    this.displayQRCode(canvas, qrData);
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                } catch (error) {
 | 
				
			||||||
 | 
					                    this.hideLoading();
 | 
				
			||||||
 | 
					                    this.showError('Gagal generate QR Code: ' + error.message);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            prepareQRData() {
 | 
				
			||||||
 | 
					                const spjNumber = this.spjNumberInput.value.trim();
 | 
				
			||||||
 | 
					                const vehicleNumber = this.vehicleNumberInput.value.trim();
 | 
				
			||||||
 | 
					                const destination = this.destinationInput.value.trim();
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                // Create JSON object for QR data (compatible with scanning)
 | 
				
			||||||
 | 
					                const qrDataObj = {
 | 
				
			||||||
 | 
					                    type: 'SPJ',
 | 
				
			||||||
 | 
					                    spj: spjNumber,
 | 
				
			||||||
 | 
					                    timestamp: new Date().toISOString(),
 | 
				
			||||||
 | 
					                    generated_by: 'eSPJ_System'
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                if (vehicleNumber) {
 | 
				
			||||||
 | 
					                    qrDataObj.vehicle = vehicleNumber;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                if (destination) {
 | 
				
			||||||
 | 
					                    qrDataObj.destination = destination;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                return JSON.stringify(qrDataObj);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            displayQRCode(canvas, qrData) {
 | 
				
			||||||
 | 
					                // Hide loading
 | 
				
			||||||
 | 
					                this.hideLoading();
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                // Add canvas to container
 | 
				
			||||||
 | 
					                this.qrContainer.appendChild(canvas);
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                // Show QR info
 | 
				
			||||||
 | 
					                this.encodedDataSpan.textContent = qrData;
 | 
				
			||||||
 | 
					                this.qrInfo.classList.remove('hidden');
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                // Show action buttons
 | 
				
			||||||
 | 
					                this.qrActions.classList.remove('hidden');
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                // Show QR display section
 | 
				
			||||||
 | 
					                this.qrDisplay.classList.remove('hidden');
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                // Show compatibility info
 | 
				
			||||||
 | 
					                this.showCompatibilityInfo();
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                // Scroll to QR code
 | 
				
			||||||
 | 
					                this.qrDisplay.scrollIntoView({ behavior: 'smooth' });
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            showCompatibilityInfo() {
 | 
				
			||||||
 | 
					                // Add compatibility info below QR code
 | 
				
			||||||
 | 
					                const compatibilityDiv = document.createElement('div');
 | 
				
			||||||
 | 
					                compatibilityDiv.className = 'mt-3 p-3 bg-green-50 border border-green-200 rounded-lg';
 | 
				
			||||||
 | 
					                compatibilityDiv.innerHTML = `
 | 
				
			||||||
 | 
					                    <div class="flex items-start">
 | 
				
			||||||
 | 
					                        <i class="w-4 h-4 text-green-600 mr-2 mt-0.5" data-lucide="check-circle"></i>
 | 
				
			||||||
 | 
					                        <div class="text-green-800 text-xs">
 | 
				
			||||||
 | 
					                            <p class="font-medium mb-1">✅ QR Code Berhasil Dibuat</p>
 | 
				
			||||||
 | 
					                            <ul class="list-disc list-inside space-y-1">
 | 
				
			||||||
 | 
					                                <li>Kompatibel dengan scanner html5-qrcode</li>
 | 
				
			||||||
 | 
					                                <li>Dapat di-scan di halaman "Scan SPJ"</li>
 | 
				
			||||||
 | 
					                                <li>Format JSON dengan error correction level M</li>
 | 
				
			||||||
 | 
					                            </ul>
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                `;
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                this.qrContainer.parentNode.insertBefore(compatibilityDiv, this.qrInfo);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            clearQRContainer() {
 | 
				
			||||||
 | 
					                // Remove existing canvas
 | 
				
			||||||
 | 
					                const existingCanvas = this.qrContainer.querySelector('#qr-canvas');
 | 
				
			||||||
 | 
					                if (existingCanvas) {
 | 
				
			||||||
 | 
					                    existingCanvas.remove();
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                // Remove compatibility info
 | 
				
			||||||
 | 
					                const compatibilityDiv = this.qrContainer.parentNode.querySelector('.bg-green-50');
 | 
				
			||||||
 | 
					                if (compatibilityDiv) {
 | 
				
			||||||
 | 
					                    compatibilityDiv.remove();
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            downloadQR() {
 | 
				
			||||||
 | 
					                if (!this.currentQRCanvas) {
 | 
				
			||||||
 | 
					                    this.showError('No QR code to download');
 | 
				
			||||||
 | 
					                    return;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                try {
 | 
				
			||||||
 | 
					                    // Create download link
 | 
				
			||||||
 | 
					                    const link = document.createElement('a');
 | 
				
			||||||
 | 
					                    link.download = `SPJ-QR-${new Date().getTime()}.png`;
 | 
				
			||||||
 | 
					                    link.href = this.currentQRCanvas.toDataURL('image/png');
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    // Trigger download
 | 
				
			||||||
 | 
					                    document.body.appendChild(link);
 | 
				
			||||||
 | 
					                    link.click();
 | 
				
			||||||
 | 
					                    document.body.removeChild(link);
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    this.showSuccess('QR Code berhasil didownload! File kompatibel dengan scanner html5-qrcode.');
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                } catch (error) {
 | 
				
			||||||
 | 
					                    this.showError('Gagal download QR Code: ' + error.message);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            printQR() {
 | 
				
			||||||
 | 
					                if (!this.currentQRCanvas || !this.currentQRData) {
 | 
				
			||||||
 | 
					                    this.showError('No QR code to print');
 | 
				
			||||||
 | 
					                    return;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                try {
 | 
				
			||||||
 | 
					                    // Clone canvas for print
 | 
				
			||||||
 | 
					                    const printCanvas = this.currentQRCanvas.cloneNode(true);
 | 
				
			||||||
 | 
					                    printCanvas.style.width = '300px';
 | 
				
			||||||
 | 
					                    printCanvas.style.height = '300px';
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    // Clear and populate print container
 | 
				
			||||||
 | 
					                    this.printContainer.innerHTML = '';
 | 
				
			||||||
 | 
					                    this.printContainer.appendChild(printCanvas);
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    // Set print data
 | 
				
			||||||
 | 
					                    this.printData.textContent = this.currentQRData;
 | 
				
			||||||
 | 
					                    this.printDate.textContent = new Date().toLocaleString('id-ID');
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    // Print
 | 
				
			||||||
 | 
					                    window.print();
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                } catch (error) {
 | 
				
			||||||
 | 
					                    this.showError('Gagal print QR Code: ' + error.message);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            generateNew() {
 | 
				
			||||||
 | 
					                // Reset form and display
 | 
				
			||||||
 | 
					                this.qrDisplay.classList.add('hidden');
 | 
				
			||||||
 | 
					                this.qrInfo.classList.add('hidden');
 | 
				
			||||||
 | 
					                this.qrActions.classList.add('hidden');
 | 
				
			||||||
 | 
					                this.clearQRContainer();
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                // Clear stored data
 | 
				
			||||||
 | 
					                this.currentQRData = null;
 | 
				
			||||||
 | 
					                this.currentQRCanvas = null;
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                // Focus on SPJ input
 | 
				
			||||||
 | 
					                this.spjNumberInput.focus();
 | 
				
			||||||
 | 
					                this.spjNumberInput.select();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            showLoading() {
 | 
				
			||||||
 | 
					                if (this.qrLoading) {
 | 
				
			||||||
 | 
					                    this.qrLoading.style.display = 'block';
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                if (this.qrDisplay) {
 | 
				
			||||||
 | 
					                    this.qrDisplay.classList.remove('hidden');
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            hideLoading() {
 | 
				
			||||||
 | 
					                if (this.qrLoading) {
 | 
				
			||||||
 | 
					                    this.qrLoading.style.display = 'none';
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            showError(message) {
 | 
				
			||||||
 | 
					                // Create or update error message
 | 
				
			||||||
 | 
					                let errorDiv = document.querySelector('.error-message');
 | 
				
			||||||
 | 
					                if (!errorDiv) {
 | 
				
			||||||
 | 
					                    errorDiv = document.createElement('div');
 | 
				
			||||||
 | 
					                    errorDiv.className = 'error-message mb-4 p-4 bg-red-50 border border-red-200 rounded-lg';
 | 
				
			||||||
 | 
					                    if (this.form && this.form.parentNode) {
 | 
				
			||||||
 | 
					                        this.form.parentNode.insertBefore(errorDiv, this.form);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                errorDiv.innerHTML = `
 | 
				
			||||||
 | 
					                    <div class="flex items-center">
 | 
				
			||||||
 | 
					                        <i class="w-5 h-5 text-red-600 mr-2" data-lucide="alert-circle"></i>
 | 
				
			||||||
 | 
					                        <span class="text-red-800">${message}</span>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                `;
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                // Remove after 5 seconds
 | 
				
			||||||
 | 
					                setTimeout(() => {
 | 
				
			||||||
 | 
					                    if (errorDiv.parentNode) {
 | 
				
			||||||
 | 
					                        errorDiv.parentNode.removeChild(errorDiv);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }, 5000);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            showSuccess(message) {
 | 
				
			||||||
 | 
					                // Create success message
 | 
				
			||||||
 | 
					                const successDiv = document.createElement('div');
 | 
				
			||||||
 | 
					                successDiv.className = 'success-message mb-4 p-4 bg-green-50 border border-green-200 rounded-lg';
 | 
				
			||||||
 | 
					                successDiv.innerHTML = `
 | 
				
			||||||
 | 
					                    <div class="flex items-center">
 | 
				
			||||||
 | 
					                        <i class="w-5 h-5 text-green-600 mr-2" data-lucide="check-circle"></i>
 | 
				
			||||||
 | 
					                        <span class="text-green-800">${message}</span>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                `;
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                if (this.form && this.form.parentNode) {
 | 
				
			||||||
 | 
					                    this.form.parentNode.insertBefore(successDiv, this.form);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                // Remove after 3 seconds
 | 
				
			||||||
 | 
					                setTimeout(() => {
 | 
				
			||||||
 | 
					                    if (successDiv.parentNode) {
 | 
				
			||||||
 | 
					                        successDiv.parentNode.removeChild(successDiv);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }, 3000);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Make QRCodeGenerator available globally
 | 
				
			||||||
 | 
					        window.QRCodeGenerator = QRCodeGenerator;
 | 
				
			||||||
 | 
					    </script>
 | 
				
			||||||
 | 
					</register-block>
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,118 @@
 | 
				
			||||||
 | 
					@{
 | 
				
			||||||
 | 
					    Layout = "~/Views/Admin/Transport/SpjAdmin/Shared/_Layout.cshtml";
 | 
				
			||||||
 | 
					    ViewData["Title"] = "Detail SPJ";
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<div class="max-w-sm mx-auto bg-white min-h-screen flex flex-col">
 | 
				
			||||||
 | 
					    <div class="bg-gradient-to-r from-orange-500 to-red-500 text-white px-4 py-4 relative flex-shrink-0">
 | 
				
			||||||
 | 
					        <div class="flex items-center justify-between mb-2">
 | 
				
			||||||
 | 
					            <a href="@Url.Action("Index", "Home")" class="p-2 hover:bg-white/20 rounded-xl transition-all duration-300 transform hover:scale-105">
 | 
				
			||||||
 | 
					                <i class="w-5 h-5" data-lucide="chevron-left"></i>
 | 
				
			||||||
 | 
					            </a>
 | 
				
			||||||
 | 
					            <h1 class="text-xl font-bold tracking-wide">Detail SPJ</h1>
 | 
				
			||||||
 | 
					            <div class="w-9"></div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        <div class="absolute top-0 right-0 w-32 h-32 bg-white/10 rounded-full -translate-y-16 translate-x-16"></div>
 | 
				
			||||||
 | 
					        <div class="absolute bottom-0 left-0 w-24 h-24 bg-white/5 rounded-full translate-y-12 -translate-x-12"></div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <div class="flex-1 flex flex-col px-4 pb-4">
 | 
				
			||||||
 | 
					        <div class="-mt-4 relative z-10 mb-3">
 | 
				
			||||||
 | 
					            <div class="bg-white rounded-xl border border-slate-200/50 p-3 backdrop-blur-sm">
 | 
				
			||||||
 | 
					                <div class="flex items-center gap-3">
 | 
				
			||||||
 | 
					                    <div class="w-14 h-14 rounded-xl bg-gradient-to-br from-orange-100 to-red-100 flex items-center justify-center ring-2 ring-white flex-shrink-0">
 | 
				
			||||||
 | 
					                        <img src="@Url.Content("~/driver/profile.jpg")" alt="Foto Driver" class="object-cover w-full h-full rounded-xl" onerror="this.style.display='none';this.parentNode.innerHTML='<i class=\'w-7 h-7 text-slate-400\' data-lucide=\'user\'></i>';"/>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    <div class="flex-1 min-w-0">
 | 
				
			||||||
 | 
					                        <h2 class="text-base font-bold text-slate-800 truncate">Bonny Agung Putra</h2>
 | 
				
			||||||
 | 
					                        <p class="text-xs text-orange-600 font-semibold mb-2">Driver</p>
 | 
				
			||||||
 | 
					                        
 | 
				
			||||||
 | 
					                        <div class="flex gap-1">
 | 
				
			||||||
 | 
					                            <div class="bg-slate-100 rounded px-2 py-1 flex-1 min-w-0 items-center flex">
 | 
				
			||||||
 | 
					                                <span class="text-xs text-slate-700 font-medium block truncate">B 1234 XYZ</span>
 | 
				
			||||||
 | 
					                            </div>
 | 
				
			||||||
 | 
					                            <div class="bg-slate-100 rounded px-2 py-1">
 | 
				
			||||||
 | 
					                                <span class="text-xs text-slate-700 font-medium">JRC 005</span>
 | 
				
			||||||
 | 
					                            </div>
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					        <div class="mb-4">
 | 
				
			||||||
 | 
					            <div class="bg-white rounded-2xl  border border-slate-200/50 p-4">
 | 
				
			||||||
 | 
					                <div class="flex items-center gap-3 mb-3">
 | 
				
			||||||
 | 
					                    <div class="w-8 h-8 bg-gradient-to-br from-orange-400 to-red-500 rounded-xl flex items-center justify-center">
 | 
				
			||||||
 | 
					                        <i class="w-4 h-4 text-white" data-lucide="file-text"></i>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                    <h3 class="text-base font-bold text-slate-800">Informasi SPJ</h3>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                <div class="space-y-3">
 | 
				
			||||||
 | 
					                    <div class="bg-gradient-to-r from-orange-50 to-red-50 rounded-lg p-3 border border-orange-200">
 | 
				
			||||||
 | 
					                        <div class="flex items-center gap-2 mb-1">
 | 
				
			||||||
 | 
					                            <i class="w-3 h-3 text-orange-600" data-lucide="hash"></i>
 | 
				
			||||||
 | 
					                            <span class="text-xs text-orange-700 font-medium">Nomor SPJ</span>
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                        <div class="font-bold text-sm text-slate-800">SPJ/07-2025/PKM/000476</div>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    <div class="bg-gradient-to-r from-amber-50 to-orange-50 rounded-lg p-3 border border-amber-200">
 | 
				
			||||||
 | 
					                        <div class="flex items-center gap-2 mb-1">
 | 
				
			||||||
 | 
					                            <i class="w-3 h-3 text-amber-600" data-lucide="map-pin"></i>
 | 
				
			||||||
 | 
					                            <span class="text-xs text-amber-700 font-medium">Tujuan</span>
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                        <div class="font-bold text-sm text-slate-800">Taman Barito</div>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <div class="flex-1">
 | 
				
			||||||
 | 
					            <div class="bg-white rounded-2xl  border border-slate-200/50 p-4 h-full">
 | 
				
			||||||
 | 
					                <div class="flex items-center gap-3 mb-4">
 | 
				
			||||||
 | 
					                    <div class="w-8 h-8 bg-gradient-to-br from-orange-400 to-red-500 rounded-xl flex items-center justify-center">
 | 
				
			||||||
 | 
					                        <i class="w-4 h-4 text-white" data-lucide="truck"></i>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                    <h3 class="text-base font-bold text-slate-800">Status Penjemputan</h3>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                <div class="space-y-2">
 | 
				
			||||||
 | 
					                    <div class="bg-gradient-to-r from-amber-50 to-yellow-50 border-l-4 border-amber-400 rounded-r-lg p-3 shadow-sm">
 | 
				
			||||||
 | 
					                        <div class="flex items-center gap-2 mb-1">
 | 
				
			||||||
 | 
					                            <div class="w-2 h-2 bg-amber-400 rounded-full animate-pulse"></div>
 | 
				
			||||||
 | 
					                            <span class="text-xs font-semibold text-amber-700">PENGANGKUTAN</span>
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                        <p class="text-xs text-slate-700 leading-relaxed">
 | 
				
			||||||
 | 
					                            CV Tri Mitra Utama - Shell Radio Dalam
 | 
				
			||||||
 | 
					                        </p>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    <div class="bg-gradient-to-r from-emerald-50 to-green-50 border-l-4 border-emerald-400 rounded-r-lg p-3 shadow-sm">
 | 
				
			||||||
 | 
					                        <div class="flex items-center gap-2 mb-1">
 | 
				
			||||||
 | 
					                            <div class="w-2 h-2 bg-emerald-400 rounded-full"></div>
 | 
				
			||||||
 | 
					                            <span class="text-xs font-semibold text-emerald-700">SUDAH TIBA DI LOKASI</span>
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                        <p class="text-xs text-slate-700 leading-relaxed">
 | 
				
			||||||
 | 
					                            CV Tri Berkah Sejahtera
 | 
				
			||||||
 | 
					                        </p>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    <div class="bg-gradient-to-r from-red-50 to-rose-50 border-l-4 border-red-400 rounded-r-lg p-3 shadow-sm">
 | 
				
			||||||
 | 
					                        <div class="flex items-center gap-2 mb-1">
 | 
				
			||||||
 | 
					                            <div class="w-2 h-2 bg-red-400 rounded-full"></div>
 | 
				
			||||||
 | 
					                            <span class="text-xs font-semibold text-red-700">DIBATALKAN</span>
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                        <p class="text-xs text-slate-700 leading-relaxed">
 | 
				
			||||||
 | 
					                            CV Tri Berkah Sejahtera
 | 
				
			||||||
 | 
					                        </p>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <partial name="~/Views/Admin/Transport/SpjAdmin/Shared/Components/_Navigation.cshtml" />
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,471 @@
 | 
				
			||||||
 | 
					@{
 | 
				
			||||||
 | 
					    Layout = "~/Views/Admin/Transport/SpjAdmin/Shared/_Layout.cshtml";
 | 
				
			||||||
 | 
					    ViewData["Title"] = "Scan SPJ";
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@section Styles {
 | 
				
			||||||
 | 
					    <link rel="stylesheet" href="@Url.Content("~/driver/css/scanner.css")" asp-append-version="true" />
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<div class="max-w-sm mx-auto bg-white min-h-screen">
 | 
				
			||||||
 | 
					    <div class="bg-orange-500 text-white px-3 py-4 rounded-b-2xl relative pb-12">
 | 
				
			||||||
 | 
					        <div class="flex items-center justify-between">
 | 
				
			||||||
 | 
					            <a href="@Url.Action("Index", "Home")" class="p-1 hover:bg-white/10 rounded-full transition-colors">
 | 
				
			||||||
 | 
					                <i class="w-5 h-5" data-lucide="chevron-left"></i>
 | 
				
			||||||
 | 
					            </a>
 | 
				
			||||||
 | 
					            <h1 class="text-lg font-bold">Scan SPJ</h1>
 | 
				
			||||||
 | 
					            <div class="w-8"></div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <div class="p-4">
 | 
				
			||||||
 | 
					        @if (TempData["Success"] != null)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            <div class="mb-4 p-4 bg-green-50 border border-green-200 rounded-lg">
 | 
				
			||||||
 | 
					                <div class="flex items-center">
 | 
				
			||||||
 | 
					                    <i class="w-5 h-5 text-green-600 mr-2" data-lucide="check-circle"></i>
 | 
				
			||||||
 | 
					                    <span class="text-green-800">@TempData["Success"]</span>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        @if (TempData["Error"] != null)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            <div class="mb-4 p-4 bg-red-50 border border-red-200 rounded-lg">
 | 
				
			||||||
 | 
					                <div class="flex items-center">
 | 
				
			||||||
 | 
					                    <i class="w-5 h-5 text-red-600 mr-2" data-lucide="alert-circle"></i>
 | 
				
			||||||
 | 
					                    <span class="text-red-800">@TempData["Error"]</span>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <div class="scanner-container mb-4" style="height: 300px;">
 | 
				
			||||||
 | 
					            <div id="scanner-container" class="w-full h-full relative bg-gray-900 rounded-lg overflow-hidden">
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                <div id="loading-scanner" class="absolute inset-0 bg-gray-900 flex items-center justify-center z-10">
 | 
				
			||||||
 | 
					                    <div class="text-center text-white">
 | 
				
			||||||
 | 
					                        <div class="loading-spinner mx-auto mb-2"></div>
 | 
				
			||||||
 | 
					                        <p class="text-sm">Memuat scanner...</p>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <div class="space-y-3 mb-4">
 | 
				
			||||||
 | 
					            <button id="start-scanner" class="w-full bg-orange-500 hover:bg-orange-600 text-white font-medium py-3 px-4 rounded-lg transition-colors btn-scanner">
 | 
				
			||||||
 | 
					                <i class="w-5 h-5 inline mr-2" data-lucide="camera"></i>
 | 
				
			||||||
 | 
					                Mulai Scan
 | 
				
			||||||
 | 
					            </button>
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            <button id="stop-scanner" class="w-full bg-gray-500 hover:bg-gray-600 text-white font-medium py-3 px-4 rounded-lg transition-colors btn-scanner hidden">
 | 
				
			||||||
 | 
					                <i class="w-5 h-5 inline mr-2" data-lucide="camera-off"></i>
 | 
				
			||||||
 | 
					                Hentikan Scan
 | 
				
			||||||
 | 
					            </button>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <div id="permission-info" class="hidden bg-blue-50 border border-blue-200 rounded-lg p-3">
 | 
				
			||||||
 | 
					                <div class="flex items-start">
 | 
				
			||||||
 | 
					                    <i class="w-5 h-5 text-blue-600 mr-2 mt-0.5" data-lucide="info"></i>
 | 
				
			||||||
 | 
					                    <div class="text-blue-800 text-sm">
 | 
				
			||||||
 | 
					                        <p class="font-medium mb-1">🎥 Meminta Akses Kamera...</p>
 | 
				
			||||||
 | 
					                        <p class="mb-2">Browser akan meminta izin akses kamera. Pastikan untuk:</p>
 | 
				
			||||||
 | 
					                        <ul class="text-xs space-y-1 list-disc list-inside">
 | 
				
			||||||
 | 
					                            <li>Klik tombol <strong>"Allow"</strong> atau <strong>"Izinkan"</strong></li>
 | 
				
			||||||
 | 
					                            <li>Jika popup tidak muncul, cek address bar browser</li>
 | 
				
			||||||
 | 
					                            <li>Pastikan kamera tidak sedang digunakan aplikasi lain</li>
 | 
				
			||||||
 | 
					                        </ul>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <div id="permission-denied" class="hidden bg-red-50 border border-red-200 rounded-lg p-3">
 | 
				
			||||||
 | 
					                <div class="flex items-start">
 | 
				
			||||||
 | 
					                    <i class="w-5 h-5 text-red-600 mr-2 mt-0.5" data-lucide="alert-triangle"></i>
 | 
				
			||||||
 | 
					                    <div class="text-red-800 text-sm">
 | 
				
			||||||
 | 
					                        <p class="font-medium mb-1">Akses Kamera Ditolak</p>
 | 
				
			||||||
 | 
					                        <p class="mb-2">Untuk menggunakan scanner, aktifkan akses kamera:</p>
 | 
				
			||||||
 | 
					                        <ol class="list-decimal list-inside space-y-1 text-xs">
 | 
				
			||||||
 | 
					                            <li>Klik ikon kunci/kamera di address bar browser</li>
 | 
				
			||||||
 | 
					                            <li>Pilih "Allow" atau "Izinkan" untuk kamera</li>
 | 
				
			||||||
 | 
					                            <li>Refresh halaman dan coba lagi</li>
 | 
				
			||||||
 | 
					                        </ol>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <div class="bg-gray-50 border border-gray-200 rounded-lg p-3 text-sm">
 | 
				
			||||||
 | 
					                <div class="flex items-start">
 | 
				
			||||||
 | 
					                    <i class="w-4 h-4 text-gray-600 mr-2 mt-0.5" data-lucide="lightbulb"></i>
 | 
				
			||||||
 | 
					                    <div class="text-gray-700">
 | 
				
			||||||
 | 
					                        <p class="font-medium mb-1">Tips Scanning:</p>
 | 
				
			||||||
 | 
					                        <ul class="text-xs space-y-1">
 | 
				
			||||||
 | 
					                            <li>• Pastikan QR code dalam pencahayaan yang cukup</li>
 | 
				
			||||||
 | 
					                            <li>• Jaga jarak 15-30cm dari kamera</li>
 | 
				
			||||||
 | 
					                            <li>• Arahkan kamera secara tegak lurus ke QR code</li>
 | 
				
			||||||
 | 
					                            <li>• Pastikan QR code tidak buram atau rusak</li>
 | 
				
			||||||
 | 
					                            <li>• <strong>Klik "Izinkan/Allow" saat browser meminta akses kamera</strong></li>
 | 
				
			||||||
 | 
					                        </ul>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <div class="border-t pt-4">
 | 
				
			||||||
 | 
					            <h3 class="text-gray-700 font-medium mb-3">Atau input manual:</h3>
 | 
				
			||||||
 | 
					            <form id="manual-form" method="post" action="@Url.Action("ProcessScan", "Scan")">
 | 
				
			||||||
 | 
					                <div class="flex gap-2">
 | 
				
			||||||
 | 
					                    <input type="text" 
 | 
				
			||||||
 | 
					                           id="manual-barcode" 
 | 
				
			||||||
 | 
					                           name="barcode" 
 | 
				
			||||||
 | 
					                           placeholder="Masukkan kode SPJ manual" 
 | 
				
			||||||
 | 
					                           class="flex-1 px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-orange-500 focus:border-transparent">
 | 
				
			||||||
 | 
					                    <button type="submit" class="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded-lg transition-colors">
 | 
				
			||||||
 | 
					                        <i class="w-5 h-5" data-lucide="search"></i>
 | 
				
			||||||
 | 
					                    </button>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					            </form>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <div id="scan-result" class="hidden mt-4 p-4 bg-green-50 border border-green-200 rounded-lg scan-result-card">
 | 
				
			||||||
 | 
					            <div class="flex items-center mb-2">
 | 
				
			||||||
 | 
					                <i class="w-5 h-5 text-green-600 mr-2" data-lucide="check-circle"></i>
 | 
				
			||||||
 | 
					                <span class="text-green-800 font-medium">QR Code terdeteksi!</span>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					            <p class="text-green-700 mb-3">Kode: <span id="detected-code" class="font-mono font-bold"></span></p>
 | 
				
			||||||
 | 
					            <div class="flex gap-2">
 | 
				
			||||||
 | 
					                <button id="confirm-scan" class="bg-green-600 hover:bg-green-700 text-white px-4 py-2 rounded-lg transition-colors btn-scanner">
 | 
				
			||||||
 | 
					                    Konfirmasi
 | 
				
			||||||
 | 
					                </button>
 | 
				
			||||||
 | 
					                <button id="retry-scan" class="bg-gray-500 hover:bg-gray-600 text-white px-4 py-2 rounded-lg transition-colors btn-scanner">
 | 
				
			||||||
 | 
					                    Scan Ulang
 | 
				
			||||||
 | 
					                </button>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <div id="error-message" class="hidden mt-4 p-4 bg-red-50 border border-red-200 rounded-lg">
 | 
				
			||||||
 | 
					            <div class="flex items-center">
 | 
				
			||||||
 | 
					                <i class="w-5 h-5 text-red-600 mr-2" data-lucide="alert-circle"></i>
 | 
				
			||||||
 | 
					                <span class="text-red-800" id="error-text"></span>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					    <partial name="~/Views/Admin/Transport/SpjAdmin/Shared/Components/_Navigation.cshtml" />
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<register-block dynamic-section="scripts" key="jsScan">
 | 
				
			||||||
 | 
					    <script src="https://unpkg.com/html5-qrcode@2.3.8/html5-qrcode.min.js" type="text/javascript"></script>
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    <script>
 | 
				
			||||||
 | 
					        if (typeof Html5Qrcode === 'undefined') {
 | 
				
			||||||
 | 
					            const script = document.createElement('script');
 | 
				
			||||||
 | 
					            script.src = 'https://cdn.jsdelivr.net/npm/html5-qrcode@2.3.8/html5-qrcode.min.js';
 | 
				
			||||||
 | 
					            script.onerror = () => alert('Scanner library failed to load');
 | 
				
			||||||
 | 
					            document.head.appendChild(script);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    </script>
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    <script>
 | 
				
			||||||
 | 
					        class BarcodeScanner {
 | 
				
			||||||
 | 
					            constructor() {
 | 
				
			||||||
 | 
					                this.isScanning = false;
 | 
				
			||||||
 | 
					                this.detectedCode = null;
 | 
				
			||||||
 | 
					                this.html5QrCode = null;
 | 
				
			||||||
 | 
					                this.initializeElements();
 | 
				
			||||||
 | 
					                this.bindEvents();
 | 
				
			||||||
 | 
					                this.checkBrowserSupport();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            initializeElements() {
 | 
				
			||||||
 | 
					                this.startBtn = document.getElementById('start-scanner');
 | 
				
			||||||
 | 
					                this.stopBtn = document.getElementById('stop-scanner');
 | 
				
			||||||
 | 
					                this.loadingDiv = document.getElementById('loading-scanner');
 | 
				
			||||||
 | 
					                this.scanResult = document.getElementById('scan-result');
 | 
				
			||||||
 | 
					                this.errorMessage = document.getElementById('error-message');
 | 
				
			||||||
 | 
					                this.detectedCodeSpan = document.getElementById('detected-code');
 | 
				
			||||||
 | 
					                this.confirmBtn = document.getElementById('confirm-scan');
 | 
				
			||||||
 | 
					                this.retryBtn = document.getElementById('retry-scan');
 | 
				
			||||||
 | 
					                this.manualForm = document.getElementById('manual-form');
 | 
				
			||||||
 | 
					                this.manualInput = document.getElementById('manual-barcode');
 | 
				
			||||||
 | 
					                this.permissionInfo = document.getElementById('permission-info');
 | 
				
			||||||
 | 
					                this.permissionDenied = document.getElementById('permission-denied');
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            bindEvents() {
 | 
				
			||||||
 | 
					                this.startBtn.addEventListener('click', () => this.startScanner());
 | 
				
			||||||
 | 
					                this.stopBtn.addEventListener('click', () => this.stopScanner());
 | 
				
			||||||
 | 
					                this.confirmBtn.addEventListener('click', () => this.confirmScan());
 | 
				
			||||||
 | 
					                this.retryBtn.addEventListener('click', () => this.retryScan());
 | 
				
			||||||
 | 
					                this.manualForm.addEventListener('submit', (e) => this.handleManualSubmit(e));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            checkBrowserSupport() {
 | 
				
			||||||
 | 
					                if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
 | 
				
			||||||
 | 
					                    this.startBtn.disabled = true;
 | 
				
			||||||
 | 
					                    this.startBtn.innerHTML = '<i class="w-5 h-5 inline mr-2" data-lucide="x-circle"></i>Browser Tidak Didukung';
 | 
				
			||||||
 | 
					                    this.startBtn.classList.remove('bg-orange-500', 'hover:bg-orange-600');
 | 
				
			||||||
 | 
					                    this.startBtn.classList.add('bg-gray-400', 'cursor-not-allowed');
 | 
				
			||||||
 | 
					                    this.showError('Browser Anda tidak mendukung akses kamera. Gunakan browser modern seperti Chrome, Firefox, atau Safari.');
 | 
				
			||||||
 | 
					                    return;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (typeof Html5Qrcode === 'undefined') {
 | 
				
			||||||
 | 
					                    this.startBtn.disabled = true;
 | 
				
			||||||
 | 
					                    this.startBtn.innerHTML = '<i class="w-5 h-5 inline mr-2" data-lucide="x-circle"></i>Library Tidak Dimuat';
 | 
				
			||||||
 | 
					                    this.startBtn.classList.remove('bg-orange-500', 'hover:bg-orange-600');
 | 
				
			||||||
 | 
					                    this.startBtn.classList.add('bg-gray-400', 'cursor-not-allowed');
 | 
				
			||||||
 | 
					                    this.showError('Library scanner tidak dapat dimuat. Periksa koneksi internet dan refresh halaman.');
 | 
				
			||||||
 | 
					                    return;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (location.protocol !== 'https:' && location.hostname !== 'localhost' && location.hostname !== '127.0.0.1') {
 | 
				
			||||||
 | 
					                    this.showError('Scanner barcode memerlukan koneksi HTTPS yang aman. Hubungi administrator sistem.');
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            async startScanner() {
 | 
				
			||||||
 | 
					                try {
 | 
				
			||||||
 | 
					                    this.showLoading();
 | 
				
			||||||
 | 
					                    this.hideError();
 | 
				
			||||||
 | 
					                    this.hideResult();
 | 
				
			||||||
 | 
					                    this.hidePermissionMessages();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    await this.initializeHtml5QrCode();
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    this.isScanning = true;
 | 
				
			||||||
 | 
					                    this.startBtn.classList.add('hidden');
 | 
				
			||||||
 | 
					                    this.stopBtn.classList.remove('hidden');
 | 
				
			||||||
 | 
					                    this.hideLoading();
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                } catch (error) {
 | 
				
			||||||
 | 
					                    this.handleScannerError(error);
 | 
				
			||||||
 | 
					                    this.hideLoading();
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            async initializeHtml5QrCode() {
 | 
				
			||||||
 | 
					                try {
 | 
				
			||||||
 | 
					                    this.permissionInfo.classList.remove('hidden');
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    await new Promise(resolve => setTimeout(resolve, 1000));
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    this.html5QrCode = new Html5Qrcode("scanner-container");
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    const cameras = await Html5Qrcode.getCameras();
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    if (cameras && cameras.length > 0) {
 | 
				
			||||||
 | 
					                        let cameraId = cameras[0].id;
 | 
				
			||||||
 | 
					                        
 | 
				
			||||||
 | 
					                        const backCamera = cameras.find(camera => 
 | 
				
			||||||
 | 
					                            camera.label.toLowerCase().includes('back') || 
 | 
				
			||||||
 | 
					                            camera.label.toLowerCase().includes('rear') ||
 | 
				
			||||||
 | 
					                            camera.label.toLowerCase().includes('environment')
 | 
				
			||||||
 | 
					                        );
 | 
				
			||||||
 | 
					                        
 | 
				
			||||||
 | 
					                        if (backCamera) {
 | 
				
			||||||
 | 
					                            cameraId = backCamera.id;
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                        
 | 
				
			||||||
 | 
					                        await this.html5QrCode.start(
 | 
				
			||||||
 | 
					                            cameraId,
 | 
				
			||||||
 | 
					                            {
 | 
				
			||||||
 | 
					                                fps: 10,
 | 
				
			||||||
 | 
					                                qrbox: function(viewfinderWidth, viewfinderHeight) {
 | 
				
			||||||
 | 
					                                    let minEdgePercentage = 0.7;
 | 
				
			||||||
 | 
					                                    let minEdgeSize = Math.min(viewfinderWidth, viewfinderHeight);
 | 
				
			||||||
 | 
					                                    let qrboxSize = Math.floor(minEdgeSize * minEdgePercentage);
 | 
				
			||||||
 | 
					                                    return {
 | 
				
			||||||
 | 
					                                        width: qrboxSize,
 | 
				
			||||||
 | 
					                                        height: qrboxSize
 | 
				
			||||||
 | 
					                                    };
 | 
				
			||||||
 | 
					                                },
 | 
				
			||||||
 | 
					                                aspectRatio: 1.0, 
 | 
				
			||||||
 | 
					                                rememberLastUsedCamera: true
 | 
				
			||||||
 | 
					                            },
 | 
				
			||||||
 | 
					                            (decodedText, decodedResult) => {
 | 
				
			||||||
 | 
					                                this.handleBarcodeDetected(decodedText, decodedResult);
 | 
				
			||||||
 | 
					                            },
 | 
				
			||||||
 | 
					                            (errorMessage) => {
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					                        );
 | 
				
			||||||
 | 
					                        
 | 
				
			||||||
 | 
					                        this.hidePermissionMessages();
 | 
				
			||||||
 | 
					                        
 | 
				
			||||||
 | 
					                    } else {
 | 
				
			||||||
 | 
					                        throw new Error('No cameras found on this device');
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                } catch (error) {
 | 
				
			||||||
 | 
					                    this.hidePermissionMessages();
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    if (error.message.includes('Permission denied') || 
 | 
				
			||||||
 | 
					                        error.message.includes('NotAllowedError') ||
 | 
				
			||||||
 | 
					                        error.message.includes('permission') ||
 | 
				
			||||||
 | 
					                        error.name === 'NotAllowedError') {
 | 
				
			||||||
 | 
					                        this.permissionDenied.classList.remove('hidden');
 | 
				
			||||||
 | 
					                        throw new Error('Camera permission denied');
 | 
				
			||||||
 | 
					                    } else if (error.message.includes('No cameras found')) {
 | 
				
			||||||
 | 
					                        throw new Error('No camera found on this device');
 | 
				
			||||||
 | 
					                    } else {
 | 
				
			||||||
 | 
					                        throw new Error('Unable to access camera: ' + error.message);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            handleScannerError(error) {
 | 
				
			||||||
 | 
					                if (error.message.includes('permission denied') || error.message.includes('Camera permission denied')) {
 | 
				
			||||||
 | 
					                    this.permissionDenied.classList.remove('hidden');
 | 
				
			||||||
 | 
					                } else if (error.message.includes('No camera found')) {
 | 
				
			||||||
 | 
					                    this.showError('Kamera tidak ditemukan pada perangkat ini.');
 | 
				
			||||||
 | 
					                } else if (error.message.includes('NotReadableError')) {
 | 
				
			||||||
 | 
					                    this.showError('Kamera sedang digunakan aplikasi lain. Tutup aplikasi lain dan coba lagi.');
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    this.showError('Gagal memulai scanner. Pastikan kamera dapat diakses dan coba lagi.');
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            handleBarcodeDetected(decodedText, decodedResult) {
 | 
				
			||||||
 | 
					                if (decodedText && decodedText.length >= 5) {
 | 
				
			||||||
 | 
					                    this.flashSuccess();
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    this.detectedCode = decodedText;
 | 
				
			||||||
 | 
					                    this.showResult(decodedText);
 | 
				
			||||||
 | 
					                    this.stopScanner(); 
 | 
				
			||||||
 | 
					                    this.playSuccessSound();
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    this.vibrate();
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            async stopScanner() {
 | 
				
			||||||
 | 
					                if (this.isScanning && this.html5QrCode) {
 | 
				
			||||||
 | 
					                    try {
 | 
				
			||||||
 | 
					                        await this.html5QrCode.stop();
 | 
				
			||||||
 | 
					                    } catch (error) {
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    this.isScanning = false;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                this.startBtn.classList.remove('hidden');
 | 
				
			||||||
 | 
					                this.stopBtn.classList.add('hidden');
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            flashSuccess() {
 | 
				
			||||||
 | 
					                const flash = document.createElement('div');
 | 
				
			||||||
 | 
					                flash.className = 'absolute inset-0 bg-green-500 opacity-50 rounded-lg';
 | 
				
			||||||
 | 
					                flash.style.zIndex = '20';
 | 
				
			||||||
 | 
					                document.getElementById('scanner-container').appendChild(flash);
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                setTimeout(() => {
 | 
				
			||||||
 | 
					                    flash.remove();
 | 
				
			||||||
 | 
					                }, 200);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            vibrate() {
 | 
				
			||||||
 | 
					                if ('vibrate' in navigator) {
 | 
				
			||||||
 | 
					                    navigator.vibrate([200]); 
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            confirmScan() {
 | 
				
			||||||
 | 
					                if (this.detectedCode) {
 | 
				
			||||||
 | 
					                    this.manualInput.value = this.detectedCode;
 | 
				
			||||||
 | 
					                    this.manualForm.submit();
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            async retryScan() {
 | 
				
			||||||
 | 
					                this.hideResult();
 | 
				
			||||||
 | 
					                this.hideError();
 | 
				
			||||||
 | 
					                this.hidePermissionMessages();
 | 
				
			||||||
 | 
					                this.detectedCode = null;
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                if (this.isScanning && this.html5QrCode) {
 | 
				
			||||||
 | 
					                    await this.stopScanner();
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                setTimeout(() => {
 | 
				
			||||||
 | 
					                    this.startScanner();
 | 
				
			||||||
 | 
					                }, 500);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            handleManualSubmit(e) {
 | 
				
			||||||
 | 
					                const code = this.manualInput.value.trim();
 | 
				
			||||||
 | 
					                if (!code) {
 | 
				
			||||||
 | 
					                    e.preventDefault();
 | 
				
			||||||
 | 
					                    this.showError('Silakan masukkan kode SPJ.');
 | 
				
			||||||
 | 
					                    return;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                if (code.length < 5) {
 | 
				
			||||||
 | 
					                    e.preventDefault();
 | 
				
			||||||
 | 
					                    this.showError('Kode SPJ minimal 5 karakter.');
 | 
				
			||||||
 | 
					                    return;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            showLoading() {
 | 
				
			||||||
 | 
					                this.loadingDiv.classList.remove('hidden');
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            hideLoading() {
 | 
				
			||||||
 | 
					                this.loadingDiv.classList.add('hidden');
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            showResult(code) {
 | 
				
			||||||
 | 
					                this.detectedCodeSpan.textContent = code;
 | 
				
			||||||
 | 
					                this.scanResult.classList.remove('hidden');
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            hideResult() {
 | 
				
			||||||
 | 
					                this.scanResult.classList.add('hidden');
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            showError(message) {
 | 
				
			||||||
 | 
					                document.getElementById('error-text').textContent = message;
 | 
				
			||||||
 | 
					                this.errorMessage.classList.remove('hidden');
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            hideError() {
 | 
				
			||||||
 | 
					                this.errorMessage.classList.add('hidden');
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            hidePermissionMessages() {
 | 
				
			||||||
 | 
					                this.permissionInfo.classList.add('hidden');
 | 
				
			||||||
 | 
					                this.permissionDenied.classList.add('hidden');
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            playSuccessSound() {
 | 
				
			||||||
 | 
					                try {
 | 
				
			||||||
 | 
					                    const audioContext = new (window.AudioContext || window.webkitAudioContext)();
 | 
				
			||||||
 | 
					                    const oscillator = audioContext.createOscillator();
 | 
				
			||||||
 | 
					                    const gainNode = audioContext.createGain();
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    oscillator.connect(gainNode);
 | 
				
			||||||
 | 
					                    gainNode.connect(audioContext.destination);
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    oscillator.frequency.value = 800;
 | 
				
			||||||
 | 
					                    oscillator.type = 'square';
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    gainNode.gain.setValueAtTime(0.3, audioContext.currentTime);
 | 
				
			||||||
 | 
					                    gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.2);
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    oscillator.start(audioContext.currentTime);
 | 
				
			||||||
 | 
					                    oscillator.stop(audioContext.currentTime + 0.2);
 | 
				
			||||||
 | 
					                } catch (e) {
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        document.addEventListener('DOMContentLoaded', function() {
 | 
				
			||||||
 | 
					            function waitForLibrary() {
 | 
				
			||||||
 | 
					                if (typeof Html5Qrcode !== 'undefined') {
 | 
				
			||||||
 | 
					                    new BarcodeScanner();
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    setTimeout(waitForLibrary, 500);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            waitForLibrary();
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    </script>
 | 
				
			||||||
 | 
					</register-block>
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,48 @@
 | 
				
			||||||
 | 
					<div class="fixed bottom-0 left-1/2 transform -translate-x-1/2 w-full max-w-sm z-99">
 | 
				
			||||||
 | 
					            <div class="relative backdrop-blur-lg border border-gray-200/50 rounded-t-3xl shadow-xl overflow-hidden">
 | 
				
			||||||
 | 
					                <div class="absolute -top-0 left-1/2 transform -translate-x-1/2 w-20 h-10">
 | 
				
			||||||
 | 
					                    <div class="w-full h-full bg-transparent relative">
 | 
				
			||||||
 | 
					                        <div class="absolute left-0 top-0 w-10 h-10 backdrop-blur-lg rounded-br-full"></div>
 | 
				
			||||||
 | 
					                        <div class="absolute right-0 top-0 w-10 h-10 backdrop-blur-lg rounded-bl-full"></div>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                <!-- Navigation Content -->
 | 
				
			||||||
 | 
					                <div class="flex justify-between items-center px-8 relative pt-6">
 | 
				
			||||||
 | 
					                    <!-- Home Button -->
 | 
				
			||||||
 | 
					                    <a href="@Url.Action("Index", "Admin")" class="flex flex-col items-center gap-1 px-4 py-2 transition-all duration-300 hover:scale-105 group">
 | 
				
			||||||
 | 
					                        <div class="relative">
 | 
				
			||||||
 | 
					                            <i class="w-6 h-6 text-gray-400 group-hover:text-orange-500 transition-colors duration-300" data-lucide="home"></i>
 | 
				
			||||||
 | 
					                            <div class="absolute -bottom-0.5 left-1/2 transform -translate-x-1/2 w-0 h-0.5 bg-orange-500 group-hover:w-full transition-all duration-300"></div>
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                        <span class="text-xs text-gray-600 group-hover:text-orange-500 font-medium transition-colors duration-300">Home</span>
 | 
				
			||||||
 | 
					                    </a>
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    <div class="w-12"></div>
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    <!-- Profile Button -->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    <a href="@Url.Action("History", "Admin")" class="flex flex-col items-center gap-1 px-4 py-2 transition-all duration-300 hover:scale-105 group">
 | 
				
			||||||
 | 
					                        <div class="relative">
 | 
				
			||||||
 | 
					                            <i class="w-6 h-6 text-gray-400 group-hover:text-orange-500 transition-colors duration-300" data-lucide="clipboard-check"></i>
 | 
				
			||||||
 | 
					                            <div class="absolute -bottom-0.5 left-1/2 transform -translate-x-1/2 w-0 h-0.5 bg-orange-500 group-hover:w-full transition-all duration-300"></div>
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                        <span class="text-xs text-gray-600 group-hover:text-orange-500 font-medium transition-colors duration-300">History</span>
 | 
				
			||||||
 | 
					                    </a>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            <!-- Center Submit -->
 | 
				
			||||||
 | 
					            <div class="absolute -top-4 left-1/2 transform -translate-x-1/2">
 | 
				
			||||||
 | 
					                <a href="@Url.Action("Scan", "Admin")" id="odoBtn" class="hover:cursor-pointer w-14 h-14 bg-gradient-to-br from-orange-500 via-orange-400 to-orange-600 rounded-full shadow-xl flex items-center justify-center transition-all duration-300 hover:scale-110 hover:rotate-6 border-4 border-white ring-2 ring-orange-200">
 | 
				
			||||||
 | 
					                    <i class="w-6 h-6 text-white" data-lucide="camera"></i>
 | 
				
			||||||
 | 
					                </a>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<div class="h-30"></div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<register-block dynamic-section="scripts" key="jsNav">
 | 
				
			||||||
 | 
					   
 | 
				
			||||||
 | 
					</register-block>
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,25 @@
 | 
				
			||||||
 | 
					@model ErrorViewModel
 | 
				
			||||||
 | 
					@{
 | 
				
			||||||
 | 
					    ViewData["Title"] = "Error";
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<h1 class="text-danger">Error.</h1>
 | 
				
			||||||
 | 
					<h2 class="text-danger">An error occurred while processing your request.</h2>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@if (Model.ShowRequestId)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    <p>
 | 
				
			||||||
 | 
					        <strong>Request ID:</strong> <code>@Model.RequestId</code>
 | 
				
			||||||
 | 
					    </p>
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<h3>Development Mode</h3>
 | 
				
			||||||
 | 
					<p>
 | 
				
			||||||
 | 
					    Swapping to <strong>Development</strong> environment will display more detailed information about the error that occurred.
 | 
				
			||||||
 | 
					</p>
 | 
				
			||||||
 | 
					<p>
 | 
				
			||||||
 | 
					    <strong>The Development environment shouldn't be enabled for deployed applications.</strong>
 | 
				
			||||||
 | 
					    It can result in displaying sensitive information from exceptions to end users.
 | 
				
			||||||
 | 
					    For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
 | 
				
			||||||
 | 
					    and restarting the app.
 | 
				
			||||||
 | 
					</p>
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,55 @@
 | 
				
			||||||
 | 
					<!DOCTYPE html>
 | 
				
			||||||
 | 
					<html lang="en">
 | 
				
			||||||
 | 
					<head>
 | 
				
			||||||
 | 
					    <meta charset="utf-8" />
 | 
				
			||||||
 | 
					    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
 | 
				
			||||||
 | 
					    <title>@ViewData["Title"] - eSPJ</title>
 | 
				
			||||||
 | 
					    <script type="importmap"></script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <!-- Theme Colors -->
 | 
				
			||||||
 | 
					    <meta name="theme-color" content="#f97316">
 | 
				
			||||||
 | 
					    <meta name="msapplication-navbutton-color" content="#f97316">
 | 
				
			||||||
 | 
					    <meta name="apple-mobile-web-app-status-bar-style" content="default">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <!-- Mobile Optimization -->
 | 
				
			||||||
 | 
					    <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
 | 
				
			||||||
 | 
					    <meta name="mobile-web-app-capable" content="yes">
 | 
				
			||||||
 | 
					    <meta name="apple-mobile-web-app-capable" content="yes">
 | 
				
			||||||
 | 
					    <meta name="apple-mobile-web-app-title" content="SPJ Angkut">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <!-- iOS Icons -->
 | 
				
			||||||
 | 
					    <link rel="apple-touch-icon" href="~/icons/icon-152x152.png">
 | 
				
			||||||
 | 
					    <link rel="apple-touch-icon" sizes="180x180" href="~/icons/icon-180x180.png">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <!-- Windows Tiles -->
 | 
				
			||||||
 | 
					    <meta name="msapplication-TileImage" content="/icons/icon-144x144.png">
 | 
				
			||||||
 | 
					    <meta name="msapplication-TileColor" content="#f97316">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <link rel="manifest" href="@Url.Content("~/driver/manifest.json")" />
 | 
				
			||||||
 | 
					    @* <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" /> *@
 | 
				
			||||||
 | 
					    <link rel="preconnect" href="https://fonts.googleapis.com">
 | 
				
			||||||
 | 
					    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
 | 
				
			||||||
 | 
					    <link href="https://fonts.googleapis.com/css2?family=Inter+Tight:ital,wght@0,100..900;1,100..900&display=swap" rel="stylesheet">
 | 
				
			||||||
 | 
					    <link rel="stylesheet" href="@Url.Content("~/driver/css/watch.css")" asp-append-version="true" />
 | 
				
			||||||
 | 
					    <link rel="stylesheet" href="@Url.Content("~/driver/css/website.css")" asp-append-version="true" />
 | 
				
			||||||
 | 
					    <link rel="stylesheet" href="@Url.Content("~/driver/eSPJ.styles.css")" asp-append-version="true" />
 | 
				
			||||||
 | 
					    @await RenderSectionAsync("Styles", required: false)
 | 
				
			||||||
 | 
					</head>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<body class="bg-gray-100">
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					    @RenderBody()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <script src="@Url.Content("~/driver/lib/jquery/dist/jquery.min.js")"></script>
 | 
				
			||||||
 | 
					    <script src="@Url.Content("~/driver/lib/bootstrap/dist/js/bootstrap.bundle.min.js")"></script>
 | 
				
			||||||
 | 
					    <script src="@Url.Content("~/driver/js/site.js")">" asp-append-version="true"></script>
 | 
				
			||||||
 | 
					    <script src="https://unpkg.com/lucide@latest"></script>
 | 
				
			||||||
 | 
					    <script>
 | 
				
			||||||
 | 
					         document.addEventListener('DOMContentLoaded', function () {
 | 
				
			||||||
 | 
					        lucide.createIcons();
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    </script>
 | 
				
			||||||
 | 
					    @await RenderSectionAsync("Scripts", required: false)
 | 
				
			||||||
 | 
					    <dynamic-section name="scripts" />
 | 
				
			||||||
 | 
					</body>
 | 
				
			||||||
 | 
					</html>
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,48 @@
 | 
				
			||||||
 | 
					/* Please see documentation at https://learn.microsoft.com/aspnet/core/client-side/bundling-and-minification
 | 
				
			||||||
 | 
					for details on configuring this project to bundle and minify static web assets. */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					a.navbar-brand {
 | 
				
			||||||
 | 
					  white-space: normal;
 | 
				
			||||||
 | 
					  text-align: center;
 | 
				
			||||||
 | 
					  word-break: break-all;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					a {
 | 
				
			||||||
 | 
					  color: #0077cc;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.btn-primary {
 | 
				
			||||||
 | 
					  color: #fff;
 | 
				
			||||||
 | 
					  background-color: #1b6ec2;
 | 
				
			||||||
 | 
					  border-color: #1861ac;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.nav-pills .nav-link.active, .nav-pills .show > .nav-link {
 | 
				
			||||||
 | 
					  color: #fff;
 | 
				
			||||||
 | 
					  background-color: #1b6ec2;
 | 
				
			||||||
 | 
					  border-color: #1861ac;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.border-top {
 | 
				
			||||||
 | 
					  border-top: 1px solid #e5e5e5;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.border-bottom {
 | 
				
			||||||
 | 
					  border-bottom: 1px solid #e5e5e5;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.box-shadow {
 | 
				
			||||||
 | 
					  box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					button.accept-policy {
 | 
				
			||||||
 | 
					  font-size: 1rem;
 | 
				
			||||||
 | 
					  line-height: inherit;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.footer {
 | 
				
			||||||
 | 
					  position: absolute;
 | 
				
			||||||
 | 
					  bottom: 0;
 | 
				
			||||||
 | 
					  width: 100%;
 | 
				
			||||||
 | 
					  white-space: nowrap;
 | 
				
			||||||
 | 
					  line-height: 60px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,2 @@
 | 
				
			||||||
 | 
					<script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>
 | 
				
			||||||
 | 
					<script src="~/lib/jquery-validation-unobtrusive/dist/jquery.validate.unobtrusive.min.js"></script>
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,155 @@
 | 
				
			||||||
 | 
					@{
 | 
				
			||||||
 | 
					    Layout = "~/Views/Admin/Transport/SpjDriver/Shared/_Layout.cshtml";
 | 
				
			||||||
 | 
					    ViewData["Title"] = "History - DLH";
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<div class="max-w-sm mx-auto bg-gray-50 min-h-screen">
 | 
				
			||||||
 | 
					    <div class="bg-gradient-to-r from-orange-500 to-orange-600 text-white px-4 py-4 sticky top-0 z-10 shadow-lg">
 | 
				
			||||||
 | 
					        <div class="flex items-center justify-between">
 | 
				
			||||||
 | 
					            <a href="@Url.Action("Index", "Home")" class="p-2 hover:bg-white/10 rounded-full transition-all duration-200">
 | 
				
			||||||
 | 
					                <i class="w-5 h-5" data-lucide="chevron-left"></i>
 | 
				
			||||||
 | 
					            </a>
 | 
				
			||||||
 | 
					            <h1 class="text-lg font-bold">Riwayat Perjalanan</h1>
 | 
				
			||||||
 | 
					            <div class="w-9"></div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    @{
 | 
				
			||||||
 | 
					        var spjList = new[]
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            new {
 | 
				
			||||||
 | 
					                Id = 1,
 | 
				
			||||||
 | 
					                NoSpj = "SPJ/07-2025/PKM/000478",
 | 
				
			||||||
 | 
					                Plat = "B 5678 ABC",
 | 
				
			||||||
 | 
					                Kode = "JRC 007",
 | 
				
			||||||
 | 
					                Tujuan = "Bantar Gebang",
 | 
				
			||||||
 | 
					                Status = "In Progress",
 | 
				
			||||||
 | 
					                Tanggal = "28 Jul 2025",
 | 
				
			||||||
 | 
					                Waktu = "16:45"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            new {
 | 
				
			||||||
 | 
					                Id = 2,
 | 
				
			||||||
 | 
					                NoSpj = "SPJ/07-2025/PKM/000476",
 | 
				
			||||||
 | 
					                Plat = "B 9632 TOR",
 | 
				
			||||||
 | 
					                Kode = "JRC 005",
 | 
				
			||||||
 | 
					                Tujuan = "RDF Rorotan",
 | 
				
			||||||
 | 
					                Status = "Completed",
 | 
				
			||||||
 | 
					                Tanggal = "27 Jul 2025",
 | 
				
			||||||
 | 
					                Waktu = "14:30"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            new {
 | 
				
			||||||
 | 
					                Id = 3,
 | 
				
			||||||
 | 
					                NoSpj = "SPJ/07-2025/PKM/000477",
 | 
				
			||||||
 | 
					                Plat = "B 1234 XYZ",
 | 
				
			||||||
 | 
					                Kode = "JRC 006",
 | 
				
			||||||
 | 
					                Tujuan = "RDF Pesanggarahan",
 | 
				
			||||||
 | 
					                Status = "Completed",
 | 
				
			||||||
 | 
					                Tanggal = "26 Jul 2025",
 | 
				
			||||||
 | 
					                Waktu = "09:15"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            new {
 | 
				
			||||||
 | 
					                Id = 4,
 | 
				
			||||||
 | 
					                NoSpj = "SPJ/07-2025/PKM/000479",
 | 
				
			||||||
 | 
					                Plat = "B 9876 DEF",
 | 
				
			||||||
 | 
					                Kode = "JRC 008",
 | 
				
			||||||
 | 
					                Tujuan = "RDF Sunter",
 | 
				
			||||||
 | 
					                Status = "Completed",
 | 
				
			||||||
 | 
					                Tanggal = "25 Jul 2025",
 | 
				
			||||||
 | 
					                Waktu = "11:20"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            new {
 | 
				
			||||||
 | 
					                Id = 5,
 | 
				
			||||||
 | 
					                NoSpj = "SPJ/07-2025/PKM/000480",
 | 
				
			||||||
 | 
					                Plat = "B 4321 GHI",
 | 
				
			||||||
 | 
					                Kode = "JRC 009",
 | 
				
			||||||
 | 
					                Tujuan = "Bantar Gebang",
 | 
				
			||||||
 | 
					                Status = "Completed",
 | 
				
			||||||
 | 
					                Tanggal = "24 Jul 2025",
 | 
				
			||||||
 | 
					                Waktu = "08:45"
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <div class="px-4 py-4 space-y-3">
 | 
				
			||||||
 | 
					        @foreach (var spj in spjList)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            <a href="@Url.Action("Details", "History", new { id = spj.Id })" class="block">
 | 
				
			||||||
 | 
					                <div class="bg-white rounded-2xl p-4 shadow-sm border border-gray-100 hover:shadow-lg hover:border-orange-200 transition-all duration-300 relative overflow-hidden">
 | 
				
			||||||
 | 
					                <div class="absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-orange-400 to-orange-500"></div>
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                <div class="flex items-start justify-between mb-3">
 | 
				
			||||||
 | 
					                    <div class="flex-1 min-w-0">
 | 
				
			||||||
 | 
					                        <div class="flex items-center gap-2 mb-1">
 | 
				
			||||||
 | 
					                            <div class="w-2 h-2 bg-orange-400 rounded-full"></div>
 | 
				
			||||||
 | 
					                            <span class="text-xs font-medium text-gray-500 uppercase tracking-wider">No. SPJ</span>
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                        <div class="font-bold text-gray-900 text-sm">@spj.NoSpj</div>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                    <div class="flex flex-col items-end gap-1">
 | 
				
			||||||
 | 
					                        @if (spj.Status == "Completed")
 | 
				
			||||||
 | 
					                        {
 | 
				
			||||||
 | 
					                            <span class="bg-green-100 text-green-700 px-2 py-1 rounded-full text-xs font-semibold flex items-center gap-1">
 | 
				
			||||||
 | 
					                                <div class="w-2 h-2 bg-green-500 rounded-full"></div>
 | 
				
			||||||
 | 
					                                Selesai
 | 
				
			||||||
 | 
					                            </span>
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                        else
 | 
				
			||||||
 | 
					                        {
 | 
				
			||||||
 | 
					                            <span class="bg-blue-100 text-blue-700 px-2 py-1 rounded-full text-xs font-semibold flex items-center gap-1">
 | 
				
			||||||
 | 
					                                <div class="w-2 h-2 bg-blue-500 rounded-full animate-pulse"></div>
 | 
				
			||||||
 | 
					                                Berlangsung
 | 
				
			||||||
 | 
					                            </span>
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                <div class="bg-gray-50 rounded-xl p-3 mb-3">
 | 
				
			||||||
 | 
					                    <div class="flex items-center justify-between">
 | 
				
			||||||
 | 
					                        <div class="flex items-center gap-3">
 | 
				
			||||||
 | 
					                            <div class="w-10 h-10 bg-orange-100 rounded-lg flex items-center justify-center">
 | 
				
			||||||
 | 
					                                <i class="w-5 h-5 text-orange-600" data-lucide="car"></i>
 | 
				
			||||||
 | 
					                            </div>
 | 
				
			||||||
 | 
					                            <div>
 | 
				
			||||||
 | 
					                                <div class="font-bold text-gray-900 text-sm">@spj.Plat</div>
 | 
				
			||||||
 | 
					                                <div class="text-xs text-gray-500">@spj.Kode</div>
 | 
				
			||||||
 | 
					                            </div>
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                        <div class="text-right">
 | 
				
			||||||
 | 
					                            <div class="text-xs text-gray-500">@spj.Tanggal</div>
 | 
				
			||||||
 | 
					                            <div class="text-xs font-medium text-gray-700">@spj.Waktu</div>
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                <div class="flex items-center gap-3 pt-2 border-t border-gray-100">
 | 
				
			||||||
 | 
					                    <div class="w-8 h-8 bg-green-100 rounded-full flex items-center justify-center flex-shrink-0">
 | 
				
			||||||
 | 
					                        <i class="w-4 h-4 text-green-600" data-lucide="map-pin"></i>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                    <div class="flex-1 min-w-0">
 | 
				
			||||||
 | 
					                        <div class="text-xs text-gray-500 mb-1">Tujuan</div>
 | 
				
			||||||
 | 
					                        <div class="font-semibold text-gray-900 text-sm">@spj.Tujuan</div>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                    <div class="p-2 hover:bg-gray-100 rounded-full transition-colors">
 | 
				
			||||||
 | 
					                        <i class="w-4 h-4 text-gray-400" data-lucide="chevron-right"></i>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					            </a>
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <!-- Bottom Navigation -->
 | 
				
			||||||
 | 
					        <partial name="~/Views/Admin/Transport/SpjDriver/Shared/Components/_Navigation.cshtml" />
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    <!-- Kalau butuh tampilan kosong (jika tidak ada data) -->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @* <div class="flex flex-col items-center justify-center py-16 px-4">
 | 
				
			||||||
 | 
					        <div class="w-24 h-24 bg-gray-100 rounded-full flex items-center justify-center mb-4">
 | 
				
			||||||
 | 
					            <i class="w-12 h-12 text-gray-400" data-lucide="clock"></i>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					        <h3 class="text-lg font-semibold text-gray-900 mb-2">Belum Ada Riwayat</h3>
 | 
				
			||||||
 | 
					        <p class="text-gray-500 text-center text-sm">Riwayat perjalanan Anda akan muncul di sini setelah melakukan perjalanan pertama.</p>
 | 
				
			||||||
 | 
					    </div> *@
 | 
				
			||||||
 | 
					   
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,48 @@
 | 
				
			||||||
 | 
					<div class="fixed bottom-0 left-1/2 transform -translate-x-1/2 w-full max-w-sm z-99">
 | 
				
			||||||
 | 
					            <div class="relative backdrop-blur-lg border border-gray-200/50 rounded-t-3xl shadow-xl overflow-hidden">
 | 
				
			||||||
 | 
					                <div class="absolute -top-0 left-1/2 transform -translate-x-1/2 w-20 h-10">
 | 
				
			||||||
 | 
					                    <div class="w-full h-full bg-transparent relative">
 | 
				
			||||||
 | 
					                        <div class="absolute left-0 top-0 w-10 h-10 backdrop-blur-lg rounded-br-full"></div>
 | 
				
			||||||
 | 
					                        <div class="absolute right-0 top-0 w-10 h-10 backdrop-blur-lg rounded-bl-full"></div>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                <!-- Navigation Content -->
 | 
				
			||||||
 | 
					                <div class="flex justify-between items-center px-8 relative pt-6">
 | 
				
			||||||
 | 
					                    <!-- Home Button -->
 | 
				
			||||||
 | 
					                    <a href="@Url.Action("Kosong", "Home")" class="flex flex-col items-center gap-1 px-4 py-2 transition-all duration-300 hover:scale-105 group">
 | 
				
			||||||
 | 
					                        <div class="relative">
 | 
				
			||||||
 | 
					                            <i class="w-6 h-6 text-gray-400 group-hover:text-orange-500 transition-colors duration-300" data-lucide="home"></i>
 | 
				
			||||||
 | 
					                            <div class="absolute -bottom-0.5 left-1/2 transform -translate-x-1/2 w-0 h-0.5 bg-orange-500 group-hover:w-full transition-all duration-300"></div>
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                        <span class="text-xs text-gray-600 group-hover:text-orange-500 font-medium transition-colors duration-300">Home</span>
 | 
				
			||||||
 | 
					                    </a>
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    <div class="w-12"></div>
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    <!-- Profile Button -->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    <a href="@Url.Action("Index", "History")" class="flex flex-col items-center gap-1 px-4 py-2 transition-all duration-300 hover:scale-105 group">
 | 
				
			||||||
 | 
					                        <div class="relative">
 | 
				
			||||||
 | 
					                            <i class="w-6 h-6 text-gray-400 group-hover:text-orange-500 transition-colors duration-300" data-lucide="clipboard-check"></i>
 | 
				
			||||||
 | 
					                            <div class="absolute -bottom-0.5 left-1/2 transform -translate-x-1/2 w-0 h-0.5 bg-orange-500 group-hover:w-full transition-all duration-300"></div>
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                        <span class="text-xs text-gray-600 group-hover:text-orange-500 font-medium transition-colors duration-300">History</span>
 | 
				
			||||||
 | 
					                    </a>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            <!-- Center Submit -->
 | 
				
			||||||
 | 
					            <div class="absolute -top-4 left-1/2 transform -translate-x-1/2">
 | 
				
			||||||
 | 
					                <a href="@Url.Action("Index", "Scan")" id="odoBtn" class="hover:cursor-pointer w-14 h-14 bg-gradient-to-br from-orange-500 via-orange-400 to-orange-600 rounded-full shadow-xl flex items-center justify-center transition-all duration-300 hover:scale-110 hover:rotate-6 border-4 border-white ring-2 ring-orange-200">
 | 
				
			||||||
 | 
					                    <i class="w-6 h-6 text-white" data-lucide="camera"></i>
 | 
				
			||||||
 | 
					                </a>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<div class="h-30"></div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<register-block dynamic-section="scripts" key="jsNav">
 | 
				
			||||||
 | 
					   
 | 
				
			||||||
 | 
					</register-block>
 | 
				
			||||||
											
												
													File diff suppressed because it is too large
													Load Diff
												
											
										
									
								| 
						 | 
					@ -0,0 +1,307 @@
 | 
				
			||||||
 | 
					.scanner-container {
 | 
				
			||||||
 | 
					  position: relative;
 | 
				
			||||||
 | 
					  background: #1a1a1a;
 | 
				
			||||||
 | 
					  border-radius: 8px;
 | 
				
			||||||
 | 
					  overflow: hidden;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.scanner-overlay {
 | 
				
			||||||
 | 
					  position: absolute;
 | 
				
			||||||
 | 
					  top: 50%;
 | 
				
			||||||
 | 
					  left: 50%;
 | 
				
			||||||
 | 
					  transform: translate(-50%, -50%);
 | 
				
			||||||
 | 
					  width: 250px;
 | 
				
			||||||
 | 
					  height: 120px;
 | 
				
			||||||
 | 
					  border: 2px dashed rgba(255, 255, 255, 0.7);
 | 
				
			||||||
 | 
					  border-radius: 8px;
 | 
				
			||||||
 | 
					  display: flex;
 | 
				
			||||||
 | 
					  align-items: center;
 | 
				
			||||||
 | 
					  justify-content: center;
 | 
				
			||||||
 | 
					  pointer-events: none;
 | 
				
			||||||
 | 
					  z-index: 10;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.scanner-overlay::before {
 | 
				
			||||||
 | 
					  content: "";
 | 
				
			||||||
 | 
					  position: absolute;
 | 
				
			||||||
 | 
					  top: 0;
 | 
				
			||||||
 | 
					  left: -100%;
 | 
				
			||||||
 | 
					  width: 100%;
 | 
				
			||||||
 | 
					  height: 100%;
 | 
				
			||||||
 | 
					  background: linear-gradient(
 | 
				
			||||||
 | 
					    90deg,
 | 
				
			||||||
 | 
					    transparent,
 | 
				
			||||||
 | 
					    rgba(255, 255, 255, 0.2),
 | 
				
			||||||
 | 
					    transparent
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					  animation: scan-line 2s linear infinite;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@keyframes scan-line {
 | 
				
			||||||
 | 
					  0% {
 | 
				
			||||||
 | 
					    left: -100%;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  100% {
 | 
				
			||||||
 | 
					    left: 100%;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#video-preview {
 | 
				
			||||||
 | 
					  width: 100%;
 | 
				
			||||||
 | 
					  height: 100%;
 | 
				
			||||||
 | 
					  object-fit: cover;
 | 
				
			||||||
 | 
					  transform: scaleX(-1);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.drawingBuffer {
 | 
				
			||||||
 | 
					  position: absolute !important;
 | 
				
			||||||
 | 
					  top: 0 !important;
 | 
				
			||||||
 | 
					  left: 0 !important;
 | 
				
			||||||
 | 
					  z-index: 5 !important;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.btn-scanner {
 | 
				
			||||||
 | 
					  transition: all 0.3s ease;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.btn-scanner:active {
 | 
				
			||||||
 | 
					  transform: scale(0.98);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.loading-spinner {
 | 
				
			||||||
 | 
					  border: 2px solid rgba(255, 255, 255, 0.3);
 | 
				
			||||||
 | 
					  border-top: 2px solid white;
 | 
				
			||||||
 | 
					  border-radius: 50%;
 | 
				
			||||||
 | 
					  width: 32px;
 | 
				
			||||||
 | 
					  height: 32px;
 | 
				
			||||||
 | 
					  animation: spin 1s linear infinite;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.loading-spinner-small {
 | 
				
			||||||
 | 
					  border: 2px solid rgba(245, 158, 11, 0.3);
 | 
				
			||||||
 | 
					  border-top: 2px solid #f59e0b;
 | 
				
			||||||
 | 
					  border-radius: 50%;
 | 
				
			||||||
 | 
					  width: 16px;
 | 
				
			||||||
 | 
					  height: 16px;
 | 
				
			||||||
 | 
					  animation: spin 1s linear infinite;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@keyframes spin {
 | 
				
			||||||
 | 
					  0% {
 | 
				
			||||||
 | 
					    transform: rotate(0deg);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  100% {
 | 
				
			||||||
 | 
					    transform: rotate(360deg);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.scan-result-card {
 | 
				
			||||||
 | 
					  animation: slideInUp 0.3s ease-out;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@keyframes slideInUp {
 | 
				
			||||||
 | 
					  from {
 | 
				
			||||||
 | 
					    opacity: 0;
 | 
				
			||||||
 | 
					    transform: translateY(20px);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  to {
 | 
				
			||||||
 | 
					    opacity: 1;
 | 
				
			||||||
 | 
					    transform: translateY(0);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* OCR Processing Animation */
 | 
				
			||||||
 | 
					.ocr-processing {
 | 
				
			||||||
 | 
					  animation: pulse 2s infinite;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@keyframes pulse {
 | 
				
			||||||
 | 
					  0%,
 | 
				
			||||||
 | 
					  100% {
 | 
				
			||||||
 | 
					    opacity: 1;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  50% {
 | 
				
			||||||
 | 
					    opacity: 0.7;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Receipt capture overlay */
 | 
				
			||||||
 | 
					.receipt-overlay {
 | 
				
			||||||
 | 
					  position: absolute;
 | 
				
			||||||
 | 
					  top: 10%;
 | 
				
			||||||
 | 
					  left: 10%;
 | 
				
			||||||
 | 
					  right: 10%;
 | 
				
			||||||
 | 
					  bottom: 10%;
 | 
				
			||||||
 | 
					  border: 2px dashed rgba(245, 158, 11, 0.8);
 | 
				
			||||||
 | 
					  border-radius: 8px;
 | 
				
			||||||
 | 
					  pointer-events: none;
 | 
				
			||||||
 | 
					  z-index: 10;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.receipt-overlay::before {
 | 
				
			||||||
 | 
					  content: "Arahkan ke teks struk";
 | 
				
			||||||
 | 
					  position: absolute;
 | 
				
			||||||
 | 
					  top: -30px;
 | 
				
			||||||
 | 
					  left: 50%;
 | 
				
			||||||
 | 
					  transform: translateX(-50%);
 | 
				
			||||||
 | 
					  background: rgba(245, 158, 11, 0.9);
 | 
				
			||||||
 | 
					  color: white;
 | 
				
			||||||
 | 
					  padding: 4px 8px;
 | 
				
			||||||
 | 
					  border-radius: 4px;
 | 
				
			||||||
 | 
					  font-size: 12px;
 | 
				
			||||||
 | 
					  white-space: nowrap;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Form transition effects */
 | 
				
			||||||
 | 
					.form-section {
 | 
				
			||||||
 | 
					  transition: all 0.3s ease;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.form-section.disabled {
 | 
				
			||||||
 | 
					  opacity: 0.6;
 | 
				
			||||||
 | 
					  pointer-events: none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@media (max-width: 640px) {
 | 
				
			||||||
 | 
					  .scanner-overlay {
 | 
				
			||||||
 | 
					    width: 200px;
 | 
				
			||||||
 | 
					    height: 100px;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .scanner-container {
 | 
				
			||||||
 | 
					    height: 250px !important;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .receipt-overlay::before {
 | 
				
			||||||
 | 
					    font-size: 11px;
 | 
				
			||||||
 | 
					    padding: 3px 6px;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@media (prefers-color-scheme: dark) {
 | 
				
			||||||
 | 
					  .scanner-container {
 | 
				
			||||||
 | 
					    background: #0a0a0a;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#scanner-container video {
 | 
				
			||||||
 | 
					  width: 100% !important;
 | 
				
			||||||
 | 
					  height: 100% !important;
 | 
				
			||||||
 | 
					  object-fit: cover !important;
 | 
				
			||||||
 | 
					  border-radius: 8px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#scanner-container canvas {
 | 
				
			||||||
 | 
					  position: absolute !important;
 | 
				
			||||||
 | 
					  top: 0 !important;
 | 
				
			||||||
 | 
					  left: 0 !important;
 | 
				
			||||||
 | 
					  width: 100% !important;
 | 
				
			||||||
 | 
					  height: 100% !important;
 | 
				
			||||||
 | 
					  border-radius: 8px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#scanner-container select,
 | 
				
			||||||
 | 
					#scanner-container button {
 | 
				
			||||||
 | 
					  display: none !important;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Success feedback */
 | 
				
			||||||
 | 
					.success-flash {
 | 
				
			||||||
 | 
					  position: absolute;
 | 
				
			||||||
 | 
					  inset: 0;
 | 
				
			||||||
 | 
					  background: rgba(34, 197, 94, 0.3);
 | 
				
			||||||
 | 
					  border-radius: 8px;
 | 
				
			||||||
 | 
					  z-index: 15;
 | 
				
			||||||
 | 
					  animation: flash 0.3s ease-out;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@keyframes flash {
 | 
				
			||||||
 | 
					  0% {
 | 
				
			||||||
 | 
					    opacity: 0;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  50% {
 | 
				
			||||||
 | 
					    opacity: 1;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  100% {
 | 
				
			||||||
 | 
					    opacity: 0;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Input highlight when auto-filled */
 | 
				
			||||||
 | 
					.auto-filled {
 | 
				
			||||||
 | 
					  background-color: rgba(34, 197, 94, 0.1) !important;
 | 
				
			||||||
 | 
					  border-color: rgb(34, 197, 94) !important;
 | 
				
			||||||
 | 
					  animation: highlight 1s ease-out;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@keyframes highlight {
 | 
				
			||||||
 | 
					  0% {
 | 
				
			||||||
 | 
					    box-shadow: 0 0 0 3px rgba(34, 197, 94, 0.3);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  100% {
 | 
				
			||||||
 | 
					    box-shadow: 0 0 0 0 rgba(34, 197, 94, 0);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Upload functionality styling */
 | 
				
			||||||
 | 
					.upload-label {
 | 
				
			||||||
 | 
					  transition: all 0.3s ease;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.upload-label:hover {
 | 
				
			||||||
 | 
					  transform: translateY(-1px);
 | 
				
			||||||
 | 
					  box-shadow: 0 4px 8px rgba(59, 130, 246, 0.3);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.upload-label:active {
 | 
				
			||||||
 | 
					  transform: translateY(0);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Uploaded image display */
 | 
				
			||||||
 | 
					#scanner-container img {
 | 
				
			||||||
 | 
					  border: 2px dashed rgba(245, 158, 11, 0.3);
 | 
				
			||||||
 | 
					  background-color: #f9fafb;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Receipt format hints */
 | 
				
			||||||
 | 
					.receipt-format-hint {
 | 
				
			||||||
 | 
					  background: linear-gradient(
 | 
				
			||||||
 | 
					    135deg,
 | 
				
			||||||
 | 
					    rgba(245, 158, 11, 0.1),
 | 
				
			||||||
 | 
					    rgba(245, 158, 11, 0.05)
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					  border-left: 4px solid #f59e0b;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Enhanced OCR feedback */
 | 
				
			||||||
 | 
					.ocr-success {
 | 
				
			||||||
 | 
					  animation: successPulse 0.6s ease-out;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@keyframes successPulse {
 | 
				
			||||||
 | 
					  0% {
 | 
				
			||||||
 | 
					    transform: scale(1);
 | 
				
			||||||
 | 
					    box-shadow: 0 0 0 0 rgba(34, 197, 94, 0.4);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  50% {
 | 
				
			||||||
 | 
					    transform: scale(1.02);
 | 
				
			||||||
 | 
					    box-shadow: 0 0 0 10px rgba(34, 197, 94, 0);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  100% {
 | 
				
			||||||
 | 
					    transform: scale(1);
 | 
				
			||||||
 | 
					    box-shadow: 0 0 0 0 rgba(34, 197, 94, 0);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Better mobile responsiveness for file upload */
 | 
				
			||||||
 | 
					@media (max-width: 640px) {
 | 
				
			||||||
 | 
					  .upload-label {
 | 
				
			||||||
 | 
					    padding: 12px 16px;
 | 
				
			||||||
 | 
					    font-size: 14px;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .upload-label i {
 | 
				
			||||||
 | 
					    width: 16px;
 | 
				
			||||||
 | 
					    height: 16px;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -14,6 +14,7 @@
 | 
				
			||||||
    --color-red-500: oklch(63.7% 0.237 25.331);
 | 
					    --color-red-500: oklch(63.7% 0.237 25.331);
 | 
				
			||||||
    --color-red-600: oklch(57.7% 0.245 27.325);
 | 
					    --color-red-600: oklch(57.7% 0.245 27.325);
 | 
				
			||||||
    --color-red-700: oklch(50.5% 0.213 27.518);
 | 
					    --color-red-700: oklch(50.5% 0.213 27.518);
 | 
				
			||||||
 | 
					    --color-red-800: oklch(44.4% 0.177 26.899);
 | 
				
			||||||
    --color-orange-50: oklch(98% 0.016 73.684);
 | 
					    --color-orange-50: oklch(98% 0.016 73.684);
 | 
				
			||||||
    --color-orange-100: oklch(95.4% 0.038 75.164);
 | 
					    --color-orange-100: oklch(95.4% 0.038 75.164);
 | 
				
			||||||
    --color-orange-200: oklch(90.1% 0.076 70.697);
 | 
					    --color-orange-200: oklch(90.1% 0.076 70.697);
 | 
				
			||||||
| 
						 | 
					@ -23,25 +24,60 @@
 | 
				
			||||||
    --color-orange-600: oklch(64.6% 0.222 41.116);
 | 
					    --color-orange-600: oklch(64.6% 0.222 41.116);
 | 
				
			||||||
    --color-orange-700: oklch(55.3% 0.195 38.402);
 | 
					    --color-orange-700: oklch(55.3% 0.195 38.402);
 | 
				
			||||||
    --color-orange-800: oklch(47% 0.157 37.304);
 | 
					    --color-orange-800: oklch(47% 0.157 37.304);
 | 
				
			||||||
 | 
					    --color-amber-50: oklch(98.7% 0.022 95.277);
 | 
				
			||||||
 | 
					    --color-amber-200: oklch(92.4% 0.12 95.746);
 | 
				
			||||||
 | 
					    --color-amber-400: oklch(82.8% 0.189 84.429);
 | 
				
			||||||
 | 
					    --color-amber-600: oklch(66.6% 0.179 58.318);
 | 
				
			||||||
 | 
					    --color-amber-700: oklch(55.5% 0.163 48.998);
 | 
				
			||||||
    --color-yellow-50: oklch(98.7% 0.026 102.212);
 | 
					    --color-yellow-50: oklch(98.7% 0.026 102.212);
 | 
				
			||||||
 | 
					    --color-yellow-100: oklch(97.3% 0.071 103.193);
 | 
				
			||||||
    --color-yellow-200: oklch(94.5% 0.129 101.54);
 | 
					    --color-yellow-200: oklch(94.5% 0.129 101.54);
 | 
				
			||||||
    --color-yellow-400: oklch(85.2% 0.199 91.936);
 | 
					    --color-yellow-400: oklch(85.2% 0.199 91.936);
 | 
				
			||||||
    --color-yellow-500: oklch(79.5% 0.184 86.047);
 | 
					    --color-yellow-500: oklch(79.5% 0.184 86.047);
 | 
				
			||||||
    --color-yellow-600: oklch(68.1% 0.162 75.834);
 | 
					    --color-yellow-600: oklch(68.1% 0.162 75.834);
 | 
				
			||||||
 | 
					    --color-yellow-800: oklch(47.6% 0.114 61.907);
 | 
				
			||||||
    --color-green-50: oklch(98.2% 0.018 155.826);
 | 
					    --color-green-50: oklch(98.2% 0.018 155.826);
 | 
				
			||||||
    --color-green-100: oklch(96.2% 0.044 156.743);
 | 
					    --color-green-100: oklch(96.2% 0.044 156.743);
 | 
				
			||||||
    --color-green-200: oklch(92.5% 0.084 155.995);
 | 
					    --color-green-200: oklch(92.5% 0.084 155.995);
 | 
				
			||||||
 | 
					    --color-green-300: oklch(87.1% 0.15 154.449);
 | 
				
			||||||
    --color-green-400: oklch(79.2% 0.209 151.711);
 | 
					    --color-green-400: oklch(79.2% 0.209 151.711);
 | 
				
			||||||
    --color-green-500: oklch(72.3% 0.219 149.579);
 | 
					    --color-green-500: oklch(72.3% 0.219 149.579);
 | 
				
			||||||
    --color-green-600: oklch(62.7% 0.194 149.214);
 | 
					    --color-green-600: oklch(62.7% 0.194 149.214);
 | 
				
			||||||
    --color-green-700: oklch(52.7% 0.154 150.069);
 | 
					    --color-green-700: oklch(52.7% 0.154 150.069);
 | 
				
			||||||
 | 
					    --color-green-800: oklch(44.8% 0.119 151.328);
 | 
				
			||||||
 | 
					    --color-emerald-50: oklch(97.9% 0.021 166.113);
 | 
				
			||||||
 | 
					    --color-emerald-200: oklch(90.5% 0.093 164.15);
 | 
				
			||||||
 | 
					    --color-emerald-400: oklch(76.5% 0.177 163.223);
 | 
				
			||||||
 | 
					    --color-emerald-600: oklch(59.6% 0.145 163.225);
 | 
				
			||||||
 | 
					    --color-emerald-700: oklch(50.8% 0.118 165.612);
 | 
				
			||||||
 | 
					    --color-teal-50: oklch(98.4% 0.014 180.72);
 | 
				
			||||||
 | 
					    --color-blue-50: oklch(97% 0.014 254.604);
 | 
				
			||||||
    --color-blue-100: oklch(93.2% 0.032 255.585);
 | 
					    --color-blue-100: oklch(93.2% 0.032 255.585);
 | 
				
			||||||
    --color-blue-200: oklch(88.2% 0.059 254.128);
 | 
					    --color-blue-200: oklch(88.2% 0.059 254.128);
 | 
				
			||||||
 | 
					    --color-blue-400: oklch(70.7% 0.165 254.624);
 | 
				
			||||||
    --color-blue-500: oklch(62.3% 0.214 259.815);
 | 
					    --color-blue-500: oklch(62.3% 0.214 259.815);
 | 
				
			||||||
    --color-blue-600: oklch(54.6% 0.245 262.881);
 | 
					    --color-blue-600: oklch(54.6% 0.245 262.881);
 | 
				
			||||||
    --color-blue-700: oklch(48.8% 0.243 264.376);
 | 
					    --color-blue-700: oklch(48.8% 0.243 264.376);
 | 
				
			||||||
 | 
					    --color-blue-800: oklch(42.4% 0.199 265.638);
 | 
				
			||||||
    --color-indigo-50: oklch(96.2% 0.018 272.314);
 | 
					    --color-indigo-50: oklch(96.2% 0.018 272.314);
 | 
				
			||||||
 | 
					    --color-indigo-100: oklch(93% 0.034 272.788);
 | 
				
			||||||
 | 
					    --color-indigo-200: oklch(87% 0.065 274.039);
 | 
				
			||||||
 | 
					    --color-indigo-300: oklch(78.5% 0.115 274.713);
 | 
				
			||||||
    --color-purple-50: oklch(97.7% 0.014 308.299);
 | 
					    --color-purple-50: oklch(97.7% 0.014 308.299);
 | 
				
			||||||
 | 
					    --color-purple-100: oklch(94.6% 0.033 307.174);
 | 
				
			||||||
 | 
					    --color-purple-400: oklch(71.4% 0.203 305.504);
 | 
				
			||||||
 | 
					    --color-purple-500: oklch(62.7% 0.265 303.9);
 | 
				
			||||||
 | 
					    --color-purple-600: oklch(55.8% 0.288 302.321);
 | 
				
			||||||
 | 
					    --color-rose-50: oklch(96.9% 0.015 12.422);
 | 
				
			||||||
 | 
					    --color-slate-50: oklch(98.4% 0.003 247.858);
 | 
				
			||||||
 | 
					    --color-slate-100: oklch(96.8% 0.007 247.896);
 | 
				
			||||||
 | 
					    --color-slate-200: oklch(92.9% 0.013 255.508);
 | 
				
			||||||
 | 
					    --color-slate-400: oklch(70.4% 0.04 256.788);
 | 
				
			||||||
 | 
					    --color-slate-500: oklch(55.4% 0.046 257.417);
 | 
				
			||||||
 | 
					    --color-slate-600: oklch(44.6% 0.043 257.281);
 | 
				
			||||||
 | 
					    --color-slate-700: oklch(37.2% 0.044 257.287);
 | 
				
			||||||
 | 
					    --color-slate-800: oklch(27.9% 0.041 260.031);
 | 
				
			||||||
 | 
					    --color-slate-900: oklch(20.8% 0.042 265.755);
 | 
				
			||||||
    --color-gray-50: oklch(98.5% 0.002 247.839);
 | 
					    --color-gray-50: oklch(98.5% 0.002 247.839);
 | 
				
			||||||
    --color-gray-100: oklch(96.7% 0.003 264.542);
 | 
					    --color-gray-100: oklch(96.7% 0.003 264.542);
 | 
				
			||||||
    --color-gray-200: oklch(92.8% 0.006 264.531);
 | 
					    --color-gray-200: oklch(92.8% 0.006 264.531);
 | 
				
			||||||
| 
						 | 
					@ -55,6 +91,7 @@
 | 
				
			||||||
    --color-black: #000;
 | 
					    --color-black: #000;
 | 
				
			||||||
    --color-white: #fff;
 | 
					    --color-white: #fff;
 | 
				
			||||||
    --spacing: 0.25rem;
 | 
					    --spacing: 0.25rem;
 | 
				
			||||||
 | 
					    --container-xs: 20rem;
 | 
				
			||||||
    --container-sm: 24rem;
 | 
					    --container-sm: 24rem;
 | 
				
			||||||
    --text-xs: 0.75rem;
 | 
					    --text-xs: 0.75rem;
 | 
				
			||||||
    --text-xs--line-height: calc(1 / 0.75);
 | 
					    --text-xs--line-height: calc(1 / 0.75);
 | 
				
			||||||
| 
						 | 
					@ -66,6 +103,8 @@
 | 
				
			||||||
    --text-lg--line-height: calc(1.75 / 1.125);
 | 
					    --text-lg--line-height: calc(1.75 / 1.125);
 | 
				
			||||||
    --text-xl: 1.25rem;
 | 
					    --text-xl: 1.25rem;
 | 
				
			||||||
    --text-xl--line-height: calc(1.75 / 1.25);
 | 
					    --text-xl--line-height: calc(1.75 / 1.25);
 | 
				
			||||||
 | 
					    --text-2xl: 1.5rem;
 | 
				
			||||||
 | 
					    --text-2xl--line-height: calc(2 / 1.5);
 | 
				
			||||||
    --font-weight-medium: 500;
 | 
					    --font-weight-medium: 500;
 | 
				
			||||||
    --font-weight-semibold: 600;
 | 
					    --font-weight-semibold: 600;
 | 
				
			||||||
    --font-weight-bold: 700;
 | 
					    --font-weight-bold: 700;
 | 
				
			||||||
| 
						 | 
					@ -84,7 +123,10 @@
 | 
				
			||||||
    --ease-out: cubic-bezier(0, 0, 0.2, 1);
 | 
					    --ease-out: cubic-bezier(0, 0, 0.2, 1);
 | 
				
			||||||
    --ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
 | 
					    --ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
 | 
				
			||||||
    --animate-spin: spin 1s linear infinite;
 | 
					    --animate-spin: spin 1s linear infinite;
 | 
				
			||||||
 | 
					    --animate-ping: ping 1s cubic-bezier(0, 0, 0.2, 1) infinite;
 | 
				
			||||||
    --animate-pulse: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
 | 
					    --animate-pulse: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
 | 
				
			||||||
 | 
					    --animate-bounce: bounce 1s infinite;
 | 
				
			||||||
 | 
					    --blur-sm: 8px;
 | 
				
			||||||
    --blur-lg: 16px;
 | 
					    --blur-lg: 16px;
 | 
				
			||||||
    --default-transition-duration: 150ms;
 | 
					    --default-transition-duration: 150ms;
 | 
				
			||||||
    --default-transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
 | 
					    --default-transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
 | 
				
			||||||
| 
						 | 
					@ -265,6 +307,9 @@
 | 
				
			||||||
  .sticky {
 | 
					  .sticky {
 | 
				
			||||||
    position: sticky;
 | 
					    position: sticky;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  .inset-0 {
 | 
				
			||||||
 | 
					    inset: calc(var(--spacing) * 0);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
  .start-0 {
 | 
					  .start-0 {
 | 
				
			||||||
    inset-inline-start: calc(var(--spacing) * 0);
 | 
					    inset-inline-start: calc(var(--spacing) * 0);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
| 
						 | 
					@ -316,6 +361,12 @@
 | 
				
			||||||
  .right-4 {
 | 
					  .right-4 {
 | 
				
			||||||
    right: calc(var(--spacing) * 4);
 | 
					    right: calc(var(--spacing) * 4);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  .right-8 {
 | 
				
			||||||
 | 
					    right: calc(var(--spacing) * 8);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .right-16 {
 | 
				
			||||||
 | 
					    right: calc(var(--spacing) * 16);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
  .right-full {
 | 
					  .right-full {
 | 
				
			||||||
    right: 100%;
 | 
					    right: 100%;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
| 
						 | 
					@ -334,6 +385,9 @@
 | 
				
			||||||
  .bottom-8 {
 | 
					  .bottom-8 {
 | 
				
			||||||
    bottom: calc(var(--spacing) * 8);
 | 
					    bottom: calc(var(--spacing) * 8);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  .bottom-16 {
 | 
				
			||||||
 | 
					    bottom: calc(var(--spacing) * 16);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
  .bottom-50 {
 | 
					  .bottom-50 {
 | 
				
			||||||
    bottom: calc(var(--spacing) * 50);
 | 
					    bottom: calc(var(--spacing) * 50);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
| 
						 | 
					@ -346,6 +400,12 @@
 | 
				
			||||||
  .left-1\/2 {
 | 
					  .left-1\/2 {
 | 
				
			||||||
    left: calc(1/2 * 100%);
 | 
					    left: calc(1/2 * 100%);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  .left-8 {
 | 
				
			||||||
 | 
					    left: calc(var(--spacing) * 8);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .left-12 {
 | 
				
			||||||
 | 
					    left: calc(var(--spacing) * 12);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
  .z-0 {
 | 
					  .z-0 {
 | 
				
			||||||
    z-index: 0;
 | 
					    z-index: 0;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
| 
						 | 
					@ -526,6 +586,9 @@
 | 
				
			||||||
  .my-5 {
 | 
					  .my-5 {
 | 
				
			||||||
    margin-block: calc(var(--spacing) * 5);
 | 
					    margin-block: calc(var(--spacing) * 5);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  .my-6 {
 | 
				
			||||||
 | 
					    margin-block: calc(var(--spacing) * 6);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
  .my-auto {
 | 
					  .my-auto {
 | 
				
			||||||
    margin-block: auto;
 | 
					    margin-block: auto;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
| 
						 | 
					@ -571,6 +634,9 @@
 | 
				
			||||||
  .me-auto {
 | 
					  .me-auto {
 | 
				
			||||||
    margin-inline-end: auto;
 | 
					    margin-inline-end: auto;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  .-mt-4 {
 | 
				
			||||||
 | 
					    margin-top: calc(var(--spacing) * -4);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
  .-mt-6 {
 | 
					  .-mt-6 {
 | 
				
			||||||
    margin-top: calc(var(--spacing) * -6);
 | 
					    margin-top: calc(var(--spacing) * -6);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
| 
						 | 
					@ -583,9 +649,15 @@
 | 
				
			||||||
  .mt-0 {
 | 
					  .mt-0 {
 | 
				
			||||||
    margin-top: calc(var(--spacing) * 0);
 | 
					    margin-top: calc(var(--spacing) * 0);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  .mt-0\.5 {
 | 
				
			||||||
 | 
					    margin-top: calc(var(--spacing) * 0.5);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
  .mt-1 {
 | 
					  .mt-1 {
 | 
				
			||||||
    margin-top: calc(var(--spacing) * 1);
 | 
					    margin-top: calc(var(--spacing) * 1);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  .mt-1\.5 {
 | 
				
			||||||
 | 
					    margin-top: calc(var(--spacing) * 1.5);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
  .mt-2 {
 | 
					  .mt-2 {
 | 
				
			||||||
    margin-top: calc(var(--spacing) * 2);
 | 
					    margin-top: calc(var(--spacing) * 2);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
| 
						 | 
					@ -763,6 +835,9 @@
 | 
				
			||||||
  .w-0 {
 | 
					  .w-0 {
 | 
				
			||||||
    width: calc(var(--spacing) * 0);
 | 
					    width: calc(var(--spacing) * 0);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  .w-1 {
 | 
				
			||||||
 | 
					    width: calc(var(--spacing) * 1);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
  .w-2 {
 | 
					  .w-2 {
 | 
				
			||||||
    width: calc(var(--spacing) * 2);
 | 
					    width: calc(var(--spacing) * 2);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
| 
						 | 
					@ -872,10 +947,26 @@
 | 
				
			||||||
    --tw-translate-x: calc(calc(1/2 * 100%) * -1);
 | 
					    --tw-translate-x: calc(calc(1/2 * 100%) * -1);
 | 
				
			||||||
    translate: var(--tw-translate-x) var(--tw-translate-y);
 | 
					    translate: var(--tw-translate-x) var(--tw-translate-y);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  .-translate-x-12 {
 | 
				
			||||||
 | 
					    --tw-translate-x: calc(var(--spacing) * -12);
 | 
				
			||||||
 | 
					    translate: var(--tw-translate-x) var(--tw-translate-y);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .translate-x-16 {
 | 
				
			||||||
 | 
					    --tw-translate-x: calc(var(--spacing) * 16);
 | 
				
			||||||
 | 
					    translate: var(--tw-translate-x) var(--tw-translate-y);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
  .-translate-y-1\/2 {
 | 
					  .-translate-y-1\/2 {
 | 
				
			||||||
    --tw-translate-y: calc(calc(1/2 * 100%) * -1);
 | 
					    --tw-translate-y: calc(calc(1/2 * 100%) * -1);
 | 
				
			||||||
    translate: var(--tw-translate-x) var(--tw-translate-y);
 | 
					    translate: var(--tw-translate-x) var(--tw-translate-y);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  .-translate-y-16 {
 | 
				
			||||||
 | 
					    --tw-translate-y: calc(var(--spacing) * -16);
 | 
				
			||||||
 | 
					    translate: var(--tw-translate-x) var(--tw-translate-y);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .translate-y-12 {
 | 
				
			||||||
 | 
					    --tw-translate-y: calc(var(--spacing) * 12);
 | 
				
			||||||
 | 
					    translate: var(--tw-translate-x) var(--tw-translate-y);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
  .scale-110 {
 | 
					  .scale-110 {
 | 
				
			||||||
    --tw-scale-x: 110%;
 | 
					    --tw-scale-x: 110%;
 | 
				
			||||||
    --tw-scale-y: 110%;
 | 
					    --tw-scale-y: 110%;
 | 
				
			||||||
| 
						 | 
					@ -885,12 +976,21 @@
 | 
				
			||||||
  .transform {
 | 
					  .transform {
 | 
				
			||||||
    transform: var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,);
 | 
					    transform: var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  .animate-bounce {
 | 
				
			||||||
 | 
					    animation: var(--animate-bounce);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .animate-ping {
 | 
				
			||||||
 | 
					    animation: var(--animate-ping);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
  .animate-pulse {
 | 
					  .animate-pulse {
 | 
				
			||||||
    animation: var(--animate-pulse);
 | 
					    animation: var(--animate-pulse);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  .animate-spin {
 | 
					  .animate-spin {
 | 
				
			||||||
    animation: var(--animate-spin);
 | 
					    animation: var(--animate-spin);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  .cursor-not-allowed {
 | 
				
			||||||
 | 
					    cursor: not-allowed;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
  .cursor-pointer {
 | 
					  .cursor-pointer {
 | 
				
			||||||
    cursor: pointer;
 | 
					    cursor: pointer;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
| 
						 | 
					@ -900,6 +1000,21 @@
 | 
				
			||||||
  .resize-none {
 | 
					  .resize-none {
 | 
				
			||||||
    resize: none;
 | 
					    resize: none;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  .list-inside {
 | 
				
			||||||
 | 
					    list-style-position: inside;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .list-decimal {
 | 
				
			||||||
 | 
					    list-style-type: decimal;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .list-disc {
 | 
				
			||||||
 | 
					    list-style-type: disc;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .grid-cols-1 {
 | 
				
			||||||
 | 
					    grid-template-columns: repeat(1, minmax(0, 1fr));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .grid-cols-2 {
 | 
				
			||||||
 | 
					    grid-template-columns: repeat(2, minmax(0, 1fr));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
  .grid-cols-4 {
 | 
					  .grid-cols-4 {
 | 
				
			||||||
    grid-template-columns: repeat(4, minmax(0, 1fr));
 | 
					    grid-template-columns: repeat(4, minmax(0, 1fr));
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
| 
						 | 
					@ -1043,6 +1158,9 @@
 | 
				
			||||||
  .rounded-2xl {
 | 
					  .rounded-2xl {
 | 
				
			||||||
    border-radius: var(--radius-2xl);
 | 
					    border-radius: var(--radius-2xl);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  .rounded-3xl {
 | 
				
			||||||
 | 
					    border-radius: var(--radius-3xl);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
  .rounded-full {
 | 
					  .rounded-full {
 | 
				
			||||||
    border-radius: calc(infinity * 1px);
 | 
					    border-radius: calc(infinity * 1px);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
| 
						 | 
					@ -1059,6 +1177,10 @@
 | 
				
			||||||
    border-top-left-radius: var(--radius-3xl);
 | 
					    border-top-left-radius: var(--radius-3xl);
 | 
				
			||||||
    border-top-right-radius: var(--radius-3xl);
 | 
					    border-top-right-radius: var(--radius-3xl);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  .rounded-r-lg {
 | 
				
			||||||
 | 
					    border-top-right-radius: var(--radius-lg);
 | 
				
			||||||
 | 
					    border-bottom-right-radius: var(--radius-lg);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
  .rounded-b-2xl {
 | 
					  .rounded-b-2xl {
 | 
				
			||||||
    border-bottom-right-radius: var(--radius-2xl);
 | 
					    border-bottom-right-radius: var(--radius-2xl);
 | 
				
			||||||
    border-bottom-left-radius: var(--radius-2xl);
 | 
					    border-bottom-left-radius: var(--radius-2xl);
 | 
				
			||||||
| 
						 | 
					@ -1112,13 +1234,29 @@
 | 
				
			||||||
    border-bottom-style: var(--tw-border-style);
 | 
					    border-bottom-style: var(--tw-border-style);
 | 
				
			||||||
    border-bottom-width: 1px;
 | 
					    border-bottom-width: 1px;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  .border-l-4 {
 | 
				
			||||||
 | 
					    border-left-style: var(--tw-border-style);
 | 
				
			||||||
 | 
					    border-left-width: 4px;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
  .border-dashed {
 | 
					  .border-dashed {
 | 
				
			||||||
    --tw-border-style: dashed;
 | 
					    --tw-border-style: dashed;
 | 
				
			||||||
    border-style: dashed;
 | 
					    border-style: dashed;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  .border-amber-200 {
 | 
				
			||||||
 | 
					    border-color: var(--color-amber-200);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .border-amber-400 {
 | 
				
			||||||
 | 
					    border-color: var(--color-amber-400);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
  .border-black {
 | 
					  .border-black {
 | 
				
			||||||
    border-color: var(--color-black);
 | 
					    border-color: var(--color-black);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  .border-blue-200 {
 | 
				
			||||||
 | 
					    border-color: var(--color-blue-200);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .border-emerald-400 {
 | 
				
			||||||
 | 
					    border-color: var(--color-emerald-400);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
  .border-gray-50 {
 | 
					  .border-gray-50 {
 | 
				
			||||||
    border-color: var(--color-gray-50);
 | 
					    border-color: var(--color-gray-50);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
| 
						 | 
					@ -1143,6 +1281,9 @@
 | 
				
			||||||
  .border-green-400 {
 | 
					  .border-green-400 {
 | 
				
			||||||
    border-color: var(--color-green-400);
 | 
					    border-color: var(--color-green-400);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  .border-orange-200 {
 | 
				
			||||||
 | 
					    border-color: var(--color-orange-200);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
  .border-orange-300 {
 | 
					  .border-orange-300 {
 | 
				
			||||||
    border-color: var(--color-orange-300);
 | 
					    border-color: var(--color-orange-300);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
| 
						 | 
					@ -1152,21 +1293,54 @@
 | 
				
			||||||
  .border-red-200 {
 | 
					  .border-red-200 {
 | 
				
			||||||
    border-color: var(--color-red-200);
 | 
					    border-color: var(--color-red-200);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  .border-red-400 {
 | 
				
			||||||
 | 
					    border-color: var(--color-red-400);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .border-slate-200 {
 | 
				
			||||||
 | 
					    border-color: var(--color-slate-200);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .border-slate-200\/50 {
 | 
				
			||||||
 | 
					    border-color: color-mix(in srgb, oklch(92.9% 0.013 255.508) 50%, transparent);
 | 
				
			||||||
 | 
					    @supports (color: color-mix(in lab, red, red)) {
 | 
				
			||||||
 | 
					      border-color: color-mix(in oklab, var(--color-slate-200) 50%, transparent);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
  .border-white {
 | 
					  .border-white {
 | 
				
			||||||
    border-color: var(--color-white);
 | 
					    border-color: var(--color-white);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  .border-white\/30 {
 | 
				
			||||||
 | 
					    border-color: color-mix(in srgb, #fff 30%, transparent);
 | 
				
			||||||
 | 
					    @supports (color: color-mix(in lab, red, red)) {
 | 
				
			||||||
 | 
					      border-color: color-mix(in oklab, var(--color-white) 30%, transparent);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
  .border-yellow-200 {
 | 
					  .border-yellow-200 {
 | 
				
			||||||
    border-color: var(--color-yellow-200);
 | 
					    border-color: var(--color-yellow-200);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  .border-t-transparent {
 | 
				
			||||||
 | 
					    border-top-color: transparent;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .bg-amber-400 {
 | 
				
			||||||
 | 
					    background-color: var(--color-amber-400);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
  .bg-black {
 | 
					  .bg-black {
 | 
				
			||||||
    background-color: var(--color-black);
 | 
					    background-color: var(--color-black);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  .bg-blue-50 {
 | 
				
			||||||
 | 
					    background-color: var(--color-blue-50);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
  .bg-blue-100 {
 | 
					  .bg-blue-100 {
 | 
				
			||||||
    background-color: var(--color-blue-100);
 | 
					    background-color: var(--color-blue-100);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  .bg-blue-400 {
 | 
				
			||||||
 | 
					    background-color: var(--color-blue-400);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
  .bg-blue-500 {
 | 
					  .bg-blue-500 {
 | 
				
			||||||
    background-color: var(--color-blue-500);
 | 
					    background-color: var(--color-blue-500);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  .bg-emerald-400 {
 | 
				
			||||||
 | 
					    background-color: var(--color-emerald-400);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
  .bg-gray-50 {
 | 
					  .bg-gray-50 {
 | 
				
			||||||
    background-color: var(--color-gray-50);
 | 
					    background-color: var(--color-gray-50);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
| 
						 | 
					@ -1176,6 +1350,12 @@
 | 
				
			||||||
  .bg-gray-200 {
 | 
					  .bg-gray-200 {
 | 
				
			||||||
    background-color: var(--color-gray-200);
 | 
					    background-color: var(--color-gray-200);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  .bg-gray-400 {
 | 
				
			||||||
 | 
					    background-color: var(--color-gray-400);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .bg-gray-500 {
 | 
				
			||||||
 | 
					    background-color: var(--color-gray-500);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
  .bg-gray-800 {
 | 
					  .bg-gray-800 {
 | 
				
			||||||
    background-color: var(--color-gray-800);
 | 
					    background-color: var(--color-gray-800);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
| 
						 | 
					@ -1191,33 +1371,72 @@
 | 
				
			||||||
  .bg-green-500 {
 | 
					  .bg-green-500 {
 | 
				
			||||||
    background-color: var(--color-green-500);
 | 
					    background-color: var(--color-green-500);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  .bg-green-600 {
 | 
				
			||||||
 | 
					    background-color: var(--color-green-600);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .bg-indigo-300 {
 | 
				
			||||||
 | 
					    background-color: var(--color-indigo-300);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
  .bg-orange-50 {
 | 
					  .bg-orange-50 {
 | 
				
			||||||
    background-color: var(--color-orange-50);
 | 
					    background-color: var(--color-orange-50);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  .bg-orange-100 {
 | 
					  .bg-orange-100 {
 | 
				
			||||||
    background-color: var(--color-orange-100);
 | 
					    background-color: var(--color-orange-100);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  .bg-orange-300 {
 | 
				
			||||||
 | 
					    background-color: var(--color-orange-300);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
  .bg-orange-400 {
 | 
					  .bg-orange-400 {
 | 
				
			||||||
    background-color: var(--color-orange-400);
 | 
					    background-color: var(--color-orange-400);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  .bg-orange-500 {
 | 
					  .bg-orange-500 {
 | 
				
			||||||
    background-color: var(--color-orange-500);
 | 
					    background-color: var(--color-orange-500);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  .bg-purple-500 {
 | 
				
			||||||
 | 
					    background-color: var(--color-purple-500);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
  .bg-red-50 {
 | 
					  .bg-red-50 {
 | 
				
			||||||
    background-color: var(--color-red-50);
 | 
					    background-color: var(--color-red-50);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  .bg-red-100 {
 | 
					  .bg-red-100 {
 | 
				
			||||||
    background-color: var(--color-red-100);
 | 
					    background-color: var(--color-red-100);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  .bg-red-400 {
 | 
				
			||||||
 | 
					    background-color: var(--color-red-400);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
  .bg-red-500 {
 | 
					  .bg-red-500 {
 | 
				
			||||||
    background-color: var(--color-red-500);
 | 
					    background-color: var(--color-red-500);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  .bg-slate-100 {
 | 
				
			||||||
 | 
					    background-color: var(--color-slate-100);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .bg-slate-400 {
 | 
				
			||||||
 | 
					    background-color: var(--color-slate-400);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
  .bg-transparent {
 | 
					  .bg-transparent {
 | 
				
			||||||
    background-color: transparent;
 | 
					    background-color: transparent;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  .bg-white {
 | 
					  .bg-white {
 | 
				
			||||||
    background-color: var(--color-white);
 | 
					    background-color: var(--color-white);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  .bg-white\/5 {
 | 
				
			||||||
 | 
					    background-color: color-mix(in srgb, #fff 5%, transparent);
 | 
				
			||||||
 | 
					    @supports (color: color-mix(in lab, red, red)) {
 | 
				
			||||||
 | 
					      background-color: color-mix(in oklab, var(--color-white) 5%, transparent);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .bg-white\/10 {
 | 
				
			||||||
 | 
					    background-color: color-mix(in srgb, #fff 10%, transparent);
 | 
				
			||||||
 | 
					    @supports (color: color-mix(in lab, red, red)) {
 | 
				
			||||||
 | 
					      background-color: color-mix(in oklab, var(--color-white) 10%, transparent);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .bg-white\/70 {
 | 
				
			||||||
 | 
					    background-color: color-mix(in srgb, #fff 70%, transparent);
 | 
				
			||||||
 | 
					    @supports (color: color-mix(in lab, red, red)) {
 | 
				
			||||||
 | 
					      background-color: color-mix(in oklab, var(--color-white) 70%, transparent);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
  .bg-yellow-50 {
 | 
					  .bg-yellow-50 {
 | 
				
			||||||
    background-color: var(--color-yellow-50);
 | 
					    background-color: var(--color-yellow-50);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
| 
						 | 
					@ -1235,10 +1454,18 @@
 | 
				
			||||||
    --tw-gradient-position: to right in oklab;
 | 
					    --tw-gradient-position: to right in oklab;
 | 
				
			||||||
    background-image: linear-gradient(var(--tw-gradient-stops));
 | 
					    background-image: linear-gradient(var(--tw-gradient-stops));
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  .from-amber-50 {
 | 
				
			||||||
 | 
					    --tw-gradient-from: var(--color-amber-50);
 | 
				
			||||||
 | 
					    --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
  .from-blue-100 {
 | 
					  .from-blue-100 {
 | 
				
			||||||
    --tw-gradient-from: var(--color-blue-100);
 | 
					    --tw-gradient-from: var(--color-blue-100);
 | 
				
			||||||
    --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
 | 
					    --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  .from-emerald-50 {
 | 
				
			||||||
 | 
					    --tw-gradient-from: var(--color-emerald-50);
 | 
				
			||||||
 | 
					    --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
  .from-green-500 {
 | 
					  .from-green-500 {
 | 
				
			||||||
    --tw-gradient-from: var(--color-green-500);
 | 
					    --tw-gradient-from: var(--color-green-500);
 | 
				
			||||||
    --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
 | 
					    --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
 | 
				
			||||||
| 
						 | 
					@ -1247,6 +1474,10 @@
 | 
				
			||||||
    --tw-gradient-from: var(--color-indigo-50);
 | 
					    --tw-gradient-from: var(--color-indigo-50);
 | 
				
			||||||
    --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
 | 
					    --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  .from-orange-50 {
 | 
				
			||||||
 | 
					    --tw-gradient-from: var(--color-orange-50);
 | 
				
			||||||
 | 
					    --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
  .from-orange-100 {
 | 
					  .from-orange-100 {
 | 
				
			||||||
    --tw-gradient-from: var(--color-orange-100);
 | 
					    --tw-gradient-from: var(--color-orange-100);
 | 
				
			||||||
    --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
 | 
					    --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
 | 
				
			||||||
| 
						 | 
					@ -1259,6 +1490,23 @@
 | 
				
			||||||
    --tw-gradient-from: var(--color-orange-500);
 | 
					    --tw-gradient-from: var(--color-orange-500);
 | 
				
			||||||
    --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
 | 
					    --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  .from-red-50 {
 | 
				
			||||||
 | 
					    --tw-gradient-from: var(--color-red-50);
 | 
				
			||||||
 | 
					    --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .from-slate-50 {
 | 
				
			||||||
 | 
					    --tw-gradient-from: var(--color-slate-50);
 | 
				
			||||||
 | 
					    --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .from-slate-700 {
 | 
				
			||||||
 | 
					    --tw-gradient-from: var(--color-slate-700);
 | 
				
			||||||
 | 
					    --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .via-blue-50 {
 | 
				
			||||||
 | 
					    --tw-gradient-via: var(--color-blue-50);
 | 
				
			||||||
 | 
					    --tw-gradient-via-stops: var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);
 | 
				
			||||||
 | 
					    --tw-gradient-stops: var(--tw-gradient-via-stops);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
  .via-orange-400 {
 | 
					  .via-orange-400 {
 | 
				
			||||||
    --tw-gradient-via: var(--color-orange-400);
 | 
					    --tw-gradient-via: var(--color-orange-400);
 | 
				
			||||||
    --tw-gradient-via-stops: var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);
 | 
					    --tw-gradient-via-stops: var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);
 | 
				
			||||||
| 
						 | 
					@ -1273,10 +1521,22 @@
 | 
				
			||||||
    --tw-gradient-to: var(--color-blue-200);
 | 
					    --tw-gradient-to: var(--color-blue-200);
 | 
				
			||||||
    --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
 | 
					    --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  .to-green-50 {
 | 
				
			||||||
 | 
					    --tw-gradient-to: var(--color-green-50);
 | 
				
			||||||
 | 
					    --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
  .to-green-400 {
 | 
					  .to-green-400 {
 | 
				
			||||||
    --tw-gradient-to: var(--color-green-400);
 | 
					    --tw-gradient-to: var(--color-green-400);
 | 
				
			||||||
    --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
 | 
					    --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  .to-indigo-100 {
 | 
				
			||||||
 | 
					    --tw-gradient-to: var(--color-indigo-100);
 | 
				
			||||||
 | 
					    --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .to-orange-50 {
 | 
				
			||||||
 | 
					    --tw-gradient-to: var(--color-orange-50);
 | 
				
			||||||
 | 
					    --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
  .to-orange-200 {
 | 
					  .to-orange-200 {
 | 
				
			||||||
    --tw-gradient-to: var(--color-orange-200);
 | 
					    --tw-gradient-to: var(--color-orange-200);
 | 
				
			||||||
    --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
 | 
					    --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
 | 
				
			||||||
| 
						 | 
					@ -1297,6 +1557,33 @@
 | 
				
			||||||
    --tw-gradient-to: var(--color-purple-50);
 | 
					    --tw-gradient-to: var(--color-purple-50);
 | 
				
			||||||
    --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
 | 
					    --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  .to-red-50 {
 | 
				
			||||||
 | 
					    --tw-gradient-to: var(--color-red-50);
 | 
				
			||||||
 | 
					    --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .to-red-100 {
 | 
				
			||||||
 | 
					    --tw-gradient-to: var(--color-red-100);
 | 
				
			||||||
 | 
					    --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .to-red-500 {
 | 
				
			||||||
 | 
					    --tw-gradient-to: var(--color-red-500);
 | 
				
			||||||
 | 
					    --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .to-rose-50 {
 | 
				
			||||||
 | 
					    --tw-gradient-to: var(--color-rose-50);
 | 
				
			||||||
 | 
					    --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .to-slate-900 {
 | 
				
			||||||
 | 
					    --tw-gradient-to: var(--color-slate-900);
 | 
				
			||||||
 | 
					    --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .to-yellow-50 {
 | 
				
			||||||
 | 
					    --tw-gradient-to: var(--color-yellow-50);
 | 
				
			||||||
 | 
					    --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .bg-clip-text {
 | 
				
			||||||
 | 
					    background-clip: text;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
  .object-contain {
 | 
					  .object-contain {
 | 
				
			||||||
    object-fit: contain;
 | 
					    object-fit: contain;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
| 
						 | 
					@ -1369,8 +1656,8 @@
 | 
				
			||||||
  .py-6 {
 | 
					  .py-6 {
 | 
				
			||||||
    padding-block: calc(var(--spacing) * 6);
 | 
					    padding-block: calc(var(--spacing) * 6);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  .py-8 {
 | 
					  .py-12 {
 | 
				
			||||||
    padding-block: calc(var(--spacing) * 8);
 | 
					    padding-block: calc(var(--spacing) * 12);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  .py-16 {
 | 
					  .py-16 {
 | 
				
			||||||
    padding-block: calc(var(--spacing) * 16);
 | 
					    padding-block: calc(var(--spacing) * 16);
 | 
				
			||||||
| 
						 | 
					@ -1554,6 +1841,15 @@
 | 
				
			||||||
  .text-wrap {
 | 
					  .text-wrap {
 | 
				
			||||||
    text-wrap: wrap;
 | 
					    text-wrap: wrap;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  .break-all {
 | 
				
			||||||
 | 
					    word-break: break-all;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .text-amber-600 {
 | 
				
			||||||
 | 
					    color: var(--color-amber-600);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .text-amber-700 {
 | 
				
			||||||
 | 
					    color: var(--color-amber-700);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
  .text-black {
 | 
					  .text-black {
 | 
				
			||||||
    color: var(--color-black);
 | 
					    color: var(--color-black);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
| 
						 | 
					@ -1563,6 +1859,12 @@
 | 
				
			||||||
  .text-blue-700 {
 | 
					  .text-blue-700 {
 | 
				
			||||||
    color: var(--color-blue-700);
 | 
					    color: var(--color-blue-700);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  .text-blue-800 {
 | 
				
			||||||
 | 
					    color: var(--color-blue-800);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .text-emerald-700 {
 | 
				
			||||||
 | 
					    color: var(--color-emerald-700);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
  .text-gray-400 {
 | 
					  .text-gray-400 {
 | 
				
			||||||
    color: var(--color-gray-400);
 | 
					    color: var(--color-gray-400);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
| 
						 | 
					@ -1590,6 +1892,9 @@
 | 
				
			||||||
  .text-green-700 {
 | 
					  .text-green-700 {
 | 
				
			||||||
    color: var(--color-green-700);
 | 
					    color: var(--color-green-700);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  .text-green-800 {
 | 
				
			||||||
 | 
					    color: var(--color-green-800);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
  .text-orange-100 {
 | 
					  .text-orange-100 {
 | 
				
			||||||
    color: var(--color-orange-100);
 | 
					    color: var(--color-orange-100);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
| 
						 | 
					@ -1608,12 +1913,36 @@
 | 
				
			||||||
  .text-red-600 {
 | 
					  .text-red-600 {
 | 
				
			||||||
    color: var(--color-red-600);
 | 
					    color: var(--color-red-600);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  .text-red-700 {
 | 
				
			||||||
 | 
					    color: var(--color-red-700);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .text-red-800 {
 | 
				
			||||||
 | 
					    color: var(--color-red-800);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .text-slate-400 {
 | 
				
			||||||
 | 
					    color: var(--color-slate-400);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .text-slate-600 {
 | 
				
			||||||
 | 
					    color: var(--color-slate-600);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .text-slate-700 {
 | 
				
			||||||
 | 
					    color: var(--color-slate-700);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .text-slate-800 {
 | 
				
			||||||
 | 
					    color: var(--color-slate-800);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .text-transparent {
 | 
				
			||||||
 | 
					    color: transparent;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
  .text-white {
 | 
					  .text-white {
 | 
				
			||||||
    color: var(--color-white);
 | 
					    color: var(--color-white);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  .text-yellow-600 {
 | 
					  .text-yellow-600 {
 | 
				
			||||||
    color: var(--color-yellow-600);
 | 
					    color: var(--color-yellow-600);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  .text-yellow-800 {
 | 
				
			||||||
 | 
					    color: var(--color-yellow-800);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
  .capitalize {
 | 
					  .capitalize {
 | 
				
			||||||
    text-transform: capitalize;
 | 
					    text-transform: capitalize;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
| 
						 | 
					@ -1641,9 +1970,15 @@
 | 
				
			||||||
  .opacity-30 {
 | 
					  .opacity-30 {
 | 
				
			||||||
    opacity: 30%;
 | 
					    opacity: 30%;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  .opacity-40 {
 | 
				
			||||||
 | 
					    opacity: 40%;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
  .opacity-50 {
 | 
					  .opacity-50 {
 | 
				
			||||||
    opacity: 50%;
 | 
					    opacity: 50%;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  .opacity-60 {
 | 
				
			||||||
 | 
					    opacity: 60%;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
  .opacity-75 {
 | 
					  .opacity-75 {
 | 
				
			||||||
    opacity: 75%;
 | 
					    opacity: 75%;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
| 
						 | 
					@ -1660,6 +1995,10 @@
 | 
				
			||||||
    --tw-shadow: 0 1px 3px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 1px 2px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1));
 | 
					    --tw-shadow: 0 1px 3px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 1px 2px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1));
 | 
				
			||||||
    box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
 | 
					    box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  .shadow-2xl {
 | 
				
			||||||
 | 
					    --tw-shadow: 0 25px 50px -12px var(--tw-shadow-color, rgb(0 0 0 / 0.25));
 | 
				
			||||||
 | 
					    box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
  .shadow-lg {
 | 
					  .shadow-lg {
 | 
				
			||||||
    --tw-shadow: 0 10px 15px -3px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 4px 6px -4px var(--tw-shadow-color, rgb(0 0 0 / 0.1));
 | 
					    --tw-shadow: 0 10px 15px -3px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 4px 6px -4px var(--tw-shadow-color, rgb(0 0 0 / 0.1));
 | 
				
			||||||
    box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
 | 
					    box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
 | 
				
			||||||
| 
						 | 
					@ -1687,6 +2026,9 @@
 | 
				
			||||||
  .ring-orange-200 {
 | 
					  .ring-orange-200 {
 | 
				
			||||||
    --tw-ring-color: var(--color-orange-200);
 | 
					    --tw-ring-color: var(--color-orange-200);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  .ring-white {
 | 
				
			||||||
 | 
					    --tw-ring-color: var(--color-white);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
  .outline {
 | 
					  .outline {
 | 
				
			||||||
    outline-style: var(--tw-outline-style);
 | 
					    outline-style: var(--tw-outline-style);
 | 
				
			||||||
    outline-width: 1px;
 | 
					    outline-width: 1px;
 | 
				
			||||||
| 
						 | 
					@ -1720,6 +2062,11 @@
 | 
				
			||||||
    -webkit-backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);
 | 
					    -webkit-backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);
 | 
				
			||||||
    backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);
 | 
					    backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  .backdrop-blur-sm {
 | 
				
			||||||
 | 
					    --tw-backdrop-blur: blur(var(--blur-sm));
 | 
				
			||||||
 | 
					    -webkit-backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);
 | 
				
			||||||
 | 
					    backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
  .backdrop-filter {
 | 
					  .backdrop-filter {
 | 
				
			||||||
    -webkit-backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);
 | 
					    -webkit-backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);
 | 
				
			||||||
    backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);
 | 
					    backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);
 | 
				
			||||||
| 
						 | 
					@ -1841,6 +2188,13 @@
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  .hover\:bg-blue-600 {
 | 
				
			||||||
 | 
					    &:hover {
 | 
				
			||||||
 | 
					      @media (hover: hover) {
 | 
				
			||||||
 | 
					        background-color: var(--color-blue-600);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
  .hover\:bg-gray-50 {
 | 
					  .hover\:bg-gray-50 {
 | 
				
			||||||
    &:hover {
 | 
					    &:hover {
 | 
				
			||||||
      @media (hover: hover) {
 | 
					      @media (hover: hover) {
 | 
				
			||||||
| 
						 | 
					@ -1862,6 +2216,27 @@
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  .hover\:bg-gray-600 {
 | 
				
			||||||
 | 
					    &:hover {
 | 
				
			||||||
 | 
					      @media (hover: hover) {
 | 
				
			||||||
 | 
					        background-color: var(--color-gray-600);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .hover\:bg-green-600 {
 | 
				
			||||||
 | 
					    &:hover {
 | 
				
			||||||
 | 
					      @media (hover: hover) {
 | 
				
			||||||
 | 
					        background-color: var(--color-green-600);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .hover\:bg-green-700 {
 | 
				
			||||||
 | 
					    &:hover {
 | 
				
			||||||
 | 
					      @media (hover: hover) {
 | 
				
			||||||
 | 
					        background-color: var(--color-green-700);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
  .hover\:bg-orange-600 {
 | 
					  .hover\:bg-orange-600 {
 | 
				
			||||||
    &:hover {
 | 
					    &:hover {
 | 
				
			||||||
      @media (hover: hover) {
 | 
					      @media (hover: hover) {
 | 
				
			||||||
| 
						 | 
					@ -1869,6 +2244,13 @@
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  .hover\:bg-purple-600 {
 | 
				
			||||||
 | 
					    &:hover {
 | 
				
			||||||
 | 
					      @media (hover: hover) {
 | 
				
			||||||
 | 
					        background-color: var(--color-purple-600);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
  .hover\:bg-red-50 {
 | 
					  .hover\:bg-red-50 {
 | 
				
			||||||
    &:hover {
 | 
					    &:hover {
 | 
				
			||||||
      @media (hover: hover) {
 | 
					      @media (hover: hover) {
 | 
				
			||||||
| 
						 | 
					@ -1893,6 +2275,16 @@
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  .hover\:bg-white\/20 {
 | 
				
			||||||
 | 
					    &:hover {
 | 
				
			||||||
 | 
					      @media (hover: hover) {
 | 
				
			||||||
 | 
					        background-color: color-mix(in srgb, #fff 20%, transparent);
 | 
				
			||||||
 | 
					        @supports (color: color-mix(in lab, red, red)) {
 | 
				
			||||||
 | 
					          background-color: color-mix(in oklab, var(--color-white) 20%, transparent);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
  .hover\:from-orange-600 {
 | 
					  .hover\:from-orange-600 {
 | 
				
			||||||
    &:hover {
 | 
					    &:hover {
 | 
				
			||||||
      @media (hover: hover) {
 | 
					      @media (hover: hover) {
 | 
				
			||||||
| 
						 | 
					@ -1946,6 +2338,14 @@
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  .hover\:shadow-xl {
 | 
				
			||||||
 | 
					    &:hover {
 | 
				
			||||||
 | 
					      @media (hover: hover) {
 | 
				
			||||||
 | 
					        --tw-shadow: 0 20px 25px -5px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 8px 10px -6px var(--tw-shadow-color, rgb(0 0 0 / 0.1));
 | 
				
			||||||
 | 
					        box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
  .focus\:border-orange-500 {
 | 
					  .focus\:border-orange-500 {
 | 
				
			||||||
    &:focus {
 | 
					    &:focus {
 | 
				
			||||||
      border-color: var(--color-orange-500);
 | 
					      border-color: var(--color-orange-500);
 | 
				
			||||||
| 
						 | 
					@ -1956,6 +2356,11 @@
 | 
				
			||||||
      border-color: var(--color-red-500);
 | 
					      border-color: var(--color-red-500);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  .focus\:border-transparent {
 | 
				
			||||||
 | 
					    &:focus {
 | 
				
			||||||
 | 
					      border-color: transparent;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
  .focus\:ring-2 {
 | 
					  .focus\:ring-2 {
 | 
				
			||||||
    &:focus {
 | 
					    &:focus {
 | 
				
			||||||
      --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
 | 
					      --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
 | 
				
			||||||
| 
						 | 
					@ -1972,11 +2377,22 @@
 | 
				
			||||||
      --tw-ring-color: var(--color-orange-400);
 | 
					      --tw-ring-color: var(--color-orange-400);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  .focus\:ring-orange-500 {
 | 
				
			||||||
 | 
					    &:focus {
 | 
				
			||||||
 | 
					      --tw-ring-color: var(--color-orange-500);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
  .focus\:ring-red-200 {
 | 
					  .focus\:ring-red-200 {
 | 
				
			||||||
    &:focus {
 | 
					    &:focus {
 | 
				
			||||||
      --tw-ring-color: var(--color-red-200);
 | 
					      --tw-ring-color: var(--color-red-200);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  .focus\:outline-none {
 | 
				
			||||||
 | 
					    &:focus {
 | 
				
			||||||
 | 
					      --tw-outline-style: none;
 | 
				
			||||||
 | 
					      outline-style: none;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@property --tw-translate-x {
 | 
					@property --tw-translate-x {
 | 
				
			||||||
  syntax: "*";
 | 
					  syntax: "*";
 | 
				
			||||||
| 
						 | 
					@ -2264,11 +2680,27 @@
 | 
				
			||||||
    transform: rotate(360deg);
 | 
					    transform: rotate(360deg);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					@keyframes ping {
 | 
				
			||||||
 | 
					  75%, 100% {
 | 
				
			||||||
 | 
					    transform: scale(2);
 | 
				
			||||||
 | 
					    opacity: 0;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@keyframes pulse {
 | 
					@keyframes pulse {
 | 
				
			||||||
  50% {
 | 
					  50% {
 | 
				
			||||||
    opacity: 0.5;
 | 
					    opacity: 0.5;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					@keyframes bounce {
 | 
				
			||||||
 | 
					  0%, 100% {
 | 
				
			||||||
 | 
					    transform: translateY(-25%);
 | 
				
			||||||
 | 
					    animation-timing-function: cubic-bezier(0.8, 0, 1, 1);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  50% {
 | 
				
			||||||
 | 
					    transform: none;
 | 
				
			||||||
 | 
					    animation-timing-function: cubic-bezier(0, 0, 0.2, 1);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@layer properties {
 | 
					@layer properties {
 | 
				
			||||||
  @supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))) {
 | 
					  @supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))) {
 | 
				
			||||||
    *, ::before, ::after, ::backdrop {
 | 
					    *, ::before, ::after, ::backdrop {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue