219 lines
7.4 KiB
PHP
219 lines
7.4 KiB
PHP
|
|
<!DOCTYPE html>
|
||
|
|
<html lang="en" data-theme="clqms">
|
||
|
|
<head>
|
||
|
|
<meta charset="UTF-8">
|
||
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
|
|
<title><?= $title ?? 'Login' ?> - CLQMS</title>
|
||
|
|
<meta name="description" content="Sign in to CLQMS - Clinical Laboratory Quality Management System">
|
||
|
|
|
||
|
|
<!-- Tailwind 4 + DaisyUI 5 CDN -->
|
||
|
|
<link href="https://cdn.jsdelivr.net/npm/daisyui@5" rel="stylesheet" type="text/css" />
|
||
|
|
<link href="https://cdn.jsdelivr.net/npm/daisyui@5/themes.css" rel="stylesheet" type="text/css" />
|
||
|
|
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
|
||
|
|
|
||
|
|
<!-- Google Fonts - Inter -->
|
||
|
|
<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:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
|
||
|
|
|
||
|
|
<!-- Alpine.js -->
|
||
|
|
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
|
||
|
|
|
||
|
|
<!-- Lucide Icons (local) -->
|
||
|
|
<script src="<?= base_url('assets/js/lucide.min.js') ?>"></script>
|
||
|
|
|
||
|
|
<style type="text/tailwindcss">
|
||
|
|
/* Custom CLQMS Medical Theme */
|
||
|
|
[data-theme="clqms"] {
|
||
|
|
--color-base-100: oklch(98% 0.005 240);
|
||
|
|
--color-base-200: oklch(95% 0.01 240);
|
||
|
|
--color-base-300: oklch(90% 0.015 240);
|
||
|
|
--color-base-content: oklch(25% 0.02 240);
|
||
|
|
|
||
|
|
--color-primary: oklch(55% 0.2 175);
|
||
|
|
--color-primary-content: oklch(100% 0 0);
|
||
|
|
|
||
|
|
--color-secondary: oklch(60% 0.15 250);
|
||
|
|
--color-secondary-content: oklch(100% 0 0);
|
||
|
|
|
||
|
|
--color-accent: oklch(70% 0.2 140);
|
||
|
|
--color-accent-content: oklch(20% 0.05 140);
|
||
|
|
|
||
|
|
--color-neutral: oklch(30% 0.02 250);
|
||
|
|
--color-neutral-content: oklch(95% 0.01 250);
|
||
|
|
|
||
|
|
--color-info: oklch(65% 0.2 230);
|
||
|
|
--color-info-content: oklch(100% 0 0);
|
||
|
|
|
||
|
|
--color-success: oklch(65% 0.2 145);
|
||
|
|
--color-success-content: oklch(100% 0 0);
|
||
|
|
|
||
|
|
--color-warning: oklch(80% 0.18 85);
|
||
|
|
--color-warning-content: oklch(25% 0.05 85);
|
||
|
|
|
||
|
|
--color-error: oklch(60% 0.25 25);
|
||
|
|
--color-error-content: oklch(100% 0 0);
|
||
|
|
}
|
||
|
|
|
||
|
|
@keyframes float {
|
||
|
|
0%, 100% { transform: translateY(0) rotate(0deg); }
|
||
|
|
50% { transform: translateY(-20px) rotate(5deg); }
|
||
|
|
}
|
||
|
|
|
||
|
|
@keyframes pulse-glow {
|
||
|
|
0%, 100% { box-shadow: 0 0 20px rgba(45, 212, 191, 0.3); }
|
||
|
|
50% { box-shadow: 0 0 40px rgba(45, 212, 191, 0.5); }
|
||
|
|
}
|
||
|
|
|
||
|
|
@keyframes gradient-shift {
|
||
|
|
0% { background-position: 0% 50%; }
|
||
|
|
50% { background-position: 100% 50%; }
|
||
|
|
100% { background-position: 0% 50%; }
|
||
|
|
}
|
||
|
|
</style>
|
||
|
|
|
||
|
|
<style>
|
||
|
|
body {
|
||
|
|
font-family: 'Inter', system-ui, sans-serif;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Animated gradient background */
|
||
|
|
.auth-gradient-bg {
|
||
|
|
background: linear-gradient(-45deg,
|
||
|
|
oklch(95% 0.03 175),
|
||
|
|
oklch(97% 0.02 200),
|
||
|
|
oklch(96% 0.03 250),
|
||
|
|
oklch(98% 0.01 180)
|
||
|
|
);
|
||
|
|
background-size: 400% 400%;
|
||
|
|
animation: gradient-shift 15s ease infinite;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Floating decoration animation */
|
||
|
|
.float-animation {
|
||
|
|
animation: float 6s ease-in-out infinite;
|
||
|
|
}
|
||
|
|
|
||
|
|
.float-animation-delayed {
|
||
|
|
animation: float 8s ease-in-out infinite;
|
||
|
|
animation-delay: -3s;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Card glow effect */
|
||
|
|
.card-glow {
|
||
|
|
animation: pulse-glow 3s ease-in-out infinite;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Glass morphism */
|
||
|
|
.glass-card {
|
||
|
|
backdrop-filter: blur(20px) saturate(180%);
|
||
|
|
-webkit-backdrop-filter: blur(20px) saturate(180%);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Medical pattern overlay */
|
||
|
|
.medical-pattern {
|
||
|
|
background-image:
|
||
|
|
radial-gradient(circle at 1px 1px, rgba(45, 212, 191, 0.08) 1px, transparent 1px);
|
||
|
|
background-size: 24px 24px;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* DNA helix pattern */
|
||
|
|
.dna-pattern {
|
||
|
|
background: repeating-linear-gradient(
|
||
|
|
45deg,
|
||
|
|
transparent,
|
||
|
|
transparent 10px,
|
||
|
|
rgba(45, 212, 191, 0.03) 10px,
|
||
|
|
rgba(45, 212, 191, 0.03) 20px
|
||
|
|
);
|
||
|
|
}
|
||
|
|
</style>
|
||
|
|
|
||
|
|
<?= $this->renderSection('styles') ?>
|
||
|
|
</head>
|
||
|
|
<body class="min-h-screen font-sans antialiased auth-gradient-bg">
|
||
|
|
|
||
|
|
<main class="min-h-screen flex items-center justify-center relative overflow-hidden">
|
||
|
|
|
||
|
|
<!-- Background Decorations -->
|
||
|
|
<div class="absolute inset-0 z-0 medical-pattern dna-pattern"></div>
|
||
|
|
|
||
|
|
<!-- Floating orbs -->
|
||
|
|
<div class="absolute top-20 left-20 w-64 h-64 bg-primary/20 rounded-full blur-3xl float-animation"></div>
|
||
|
|
<div class="absolute bottom-20 right-20 w-80 h-80 bg-secondary/20 rounded-full blur-3xl float-animation-delayed"></div>
|
||
|
|
<div class="absolute top-1/2 left-1/4 w-40 h-40 bg-accent/15 rounded-full blur-2xl float-animation" style="animation-delay: -2s;"></div>
|
||
|
|
|
||
|
|
<!-- Medical icons decorations (hidden on mobile) -->
|
||
|
|
<div class="hidden lg:block absolute top-32 right-32 text-primary/10 float-animation-delayed">
|
||
|
|
<i data-lucide="test-tube-2" class="w-16 h-16"></i>
|
||
|
|
</div>
|
||
|
|
<div class="hidden lg:block absolute bottom-32 left-32 text-secondary/10 float-animation">
|
||
|
|
<i data-lucide="microscope" class="w-20 h-20"></i>
|
||
|
|
</div>
|
||
|
|
<div class="hidden lg:block absolute top-1/4 left-16 text-accent/10 float-animation-delayed" style="animation-delay: -1s;">
|
||
|
|
<i data-lucide="dna" class="w-12 h-12"></i>
|
||
|
|
</div>
|
||
|
|
<div class="hidden lg:block absolute bottom-1/4 right-16 text-primary/10 float-animation" style="animation-delay: -4s;">
|
||
|
|
<i data-lucide="heart-pulse" class="w-14 h-14"></i>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- Content -->
|
||
|
|
<div class="relative z-10 w-full max-w-6xl p-4">
|
||
|
|
<?= $this->renderSection('content') ?>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
</main>
|
||
|
|
|
||
|
|
<!-- Toast Container -->
|
||
|
|
<div class="toast toast-top toast-center z-50" x-data>
|
||
|
|
<template x-for="toast in $store.toast?.messages || []" :key="toast.id">
|
||
|
|
<div
|
||
|
|
class="alert shadow-xl backdrop-blur-sm"
|
||
|
|
:class="{
|
||
|
|
'alert-success bg-success/90': toast.type === 'success',
|
||
|
|
'alert-error bg-error/90': toast.type === 'error',
|
||
|
|
'alert-info bg-info/90': toast.type === 'info',
|
||
|
|
'alert-warning bg-warning/90': toast.type === 'warning'
|
||
|
|
}"
|
||
|
|
x-transition:enter="transition ease-out duration-300"
|
||
|
|
x-transition:enter-start="opacity-0 -translate-y-8 scale-95"
|
||
|
|
x-transition:enter-end="opacity-100 translate-y-0 scale-100"
|
||
|
|
x-transition:leave="transition ease-in duration-200"
|
||
|
|
x-transition:leave-start="opacity-100 translate-y-0 scale-100"
|
||
|
|
x-transition:leave-end="opacity-0 -translate-y-8 scale-95"
|
||
|
|
>
|
||
|
|
<span x-text="toast.message" class="font-medium"></span>
|
||
|
|
</div>
|
||
|
|
</template>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<script>
|
||
|
|
document.addEventListener('DOMContentLoaded', () => {
|
||
|
|
if(window.lucide) lucide.createIcons();
|
||
|
|
});
|
||
|
|
// Set Base URL for JS
|
||
|
|
window.BASEURL = '<?= base_url() ?>';
|
||
|
|
|
||
|
|
// Toast Store for Alpine
|
||
|
|
document.addEventListener('alpine:init', () => {
|
||
|
|
Alpine.store('toast', {
|
||
|
|
messages: [],
|
||
|
|
show(message, type = 'info') {
|
||
|
|
const id = Date.now();
|
||
|
|
this.messages.push({ id, message, type });
|
||
|
|
setTimeout(() => {
|
||
|
|
this.dismiss(id);
|
||
|
|
}, 4000);
|
||
|
|
},
|
||
|
|
dismiss(id) {
|
||
|
|
this.messages = this.messages.filter(m => m.id !== id);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
});
|
||
|
|
</script>
|
||
|
|
|
||
|
|
<?= $this->renderSection('script') ?>
|
||
|
|
<?= $this->renderSection('scripts') ?>
|
||
|
|
</body>
|
||
|
|
</html>
|