clqms-be/app/Views/layouts/v2_auth.php
2025-12-22 16:54:19 +07:00

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>