ATS/templates/portal_base.html
2026-01-29 14:19:03 +03:00

630 lines
31 KiB
HTML

{% load static i18n %}
{% get_current_language as LANGUAGE_CODE %}
<!DOCTYPE html>
<html lang="{{ LANGUAGE_CODE }}" dir="{% if LANGUAGE_CODE == 'ar' %}rtl{% else %}ltr{% endif %}">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0, user-scalable=yes, viewport-fit=cover">
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="description" content="{% trans 'King Abdullah Academic University Hospital - Portal' %}">
<title>{% block title %}{% trans 'KAAUH Portal' %}{% endblock %}</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://unpkg.com/lucide@latest"></script>
<script>
tailwind.config = {
theme: {
extend: {
colors: {
'temple-red': '#9d2235',
'temple-dark': '#1a1a1a',
'temple-cream': '#f8f7f2',
'dashboard-blue': '#4e73df',
}
}
}
}
</script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600;700&display=swap');
body {
font-family: 'Inter', sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
/* Smooth scrolling */
html {
scroll-behavior: smooth;
}
/* Touch-friendly tap highlight */
* {
-webkit-tap-highlight-color: rgba(157, 34, 53, 0.1);
}
/* Smooth transitions */
.smooth-transition {
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
/* CRITICAL: Improved touch targets - minimum 48x48px for better mobile UX */
button,
a:not(.no-touch-target),
.touch-target {
min-height: 48px;
display: inline-flex;
align-items: center;
justify-content: center;
}
/* Prevent text selection on buttons */
button {
-webkit-user-select: none;
user-select: none;
}
/* Custom scrollbar - desktop only */
@media (min-width: 1024px) {
.sidebar-scroll::-webkit-scrollbar { width: 4px; }
.sidebar-scroll::-webkit-scrollbar-thumb {
background: #333;
border-radius: 10px;
}
}
/* Backdrop blur support */
@supports ((-webkit-backdrop-filter: blur(10px)) or (backdrop-filter: blur(10px))) {
.backdrop-blur-custom {
-webkit-backdrop-filter: blur(10px);
backdrop-filter: blur(10px);
background-color: rgba(0, 0, 0, 0.5);
}
}
/* Safe area padding for notched devices */
@supports (padding: env(safe-area-inset-bottom)) {
.safe-area-padding-bottom {
padding-bottom: calc(1rem + env(safe-area-inset-bottom));
}
.safe-area-padding-top {
padding-top: calc(0.75rem + env(safe-area-inset-top));
}
}
/* Sticky header optimization */
.sticky-header {
position: sticky;
top: 0;
z-index: 40;
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
}
/* Alert animation */
.alert {
animation: slideInDown 0.3s ease-out;
}
@keyframes slideInDown {
from {
transform: translateY(-100%);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}
/* Mobile-optimized spacing */
.mobile-spaced {
padding-left: 0.75rem;
padding-right: 0.75rem;
}
@media (min-width: 640px) {
.mobile-spaced {
padding-left: 1rem;
padding-right: 1rem;
}
}
/* Optimized badge positioning */
.nav-badge {
font-size: 0.625rem;
line-height: 1;
padding: 0.25rem 0.375rem;
min-width: 1.25rem;
text-align: center;
}
/* Better visibility for active states */
.nav-link.active {
font-weight: 600;
}
/* Mobile-friendly footer */
@media (max-width: 640px) {
footer {
font-size: 0.75rem;
padding: 1rem;
}
}
/* Better button sizing on mobile */
@media (max-width: 640px) {
.mobile-btn {
padding: 0.875rem 1rem;
font-size: 0.9375rem;
}
}
/* Header actions responsive sizing */
.header-action {
min-width: 44px;
min-height: 44px;
padding: 0.625rem;
}
@media (min-width: 640px) {
.header-action {
min-width: 48px;
min-height: 48px;
}
}
/* User avatar responsive sizing */
.user-avatar {
width: 2.25rem;
height: 2.25rem;
}
@media (min-width: 640px) {
.user-avatar {
width: 2.5rem;
height: 2.5rem;
}
}
/* Sidebar header responsive */
.sidebar-header {
padding: 1rem;
}
@media (min-width: 640px) {
.sidebar-header {
padding: 1.25rem;
}
}
/* Language button responsive */
.lang-btn {
padding: 0.625rem 0.75rem;
}
@media (min-width: 640px) {
.lang-btn {
padding: 0.625rem 1rem;
}
}
</style>
<link rel="apple-touch-icon" sizes="180x180" href="{% static 'image/favicon/apple-touch-icon.png'%}">
<link rel="icon" type="image/png" sizes="32x32" href="{% static 'image/favicon/favicon-32x32.png'%}">
<link rel="icon" type="image/png" sizes="16x16" href="{% static 'image/favicon/favicon-16x16.png'%}">
<link rel="manifest" href="{% static 'image/favicon/site.webmanifest'%}">
{% block customCSS %}{% endblock %}
</head>
<body class="bg-temple-cream text-gray-800 flex min-h-screen overflow-x-hidden">
<!-- Mobile Sidebar Backdrop -->
<div id="sidebar-backdrop"
class="fixed inset-0 bg-black/50 backdrop-blur-sm z-40 hidden lg:hidden backdrop-blur-custom smooth-transition"
onclick="closeMobileSidebar()"
role="button"
aria-label="{% trans 'Close sidebar' %}">
</div>
<!-- Sidebar -->
<aside id="sidebar"
class="fixed inset-y-0 left-0 z-50 bg-temple-dark text-gray-400 transform -translate-x-full lg:translate-x-0 lg:w-64 smooth-transition border-r border-gray-800"
role="navigation"
aria-label="{% trans 'Main navigation' %}">
<div class="flex flex-col h-full">
<!-- Sidebar Header -->
<div class="sidebar-header flex items-center gap-3 text-white safe-area-padding-top border-b border-gray-800">
<div class="bg-temple-red p-2.5 rounded-lg shrink-0">
<i data-lucide="shield-check" class="w-5 h-5 sm:w-6 sm:h-6 text-white"></i>
</div>
<div class="min-w-0 flex-1">
<span class="text-lg sm:text-xl font-bold tracking-tight block">KAAUH</span>
<div class="text-[10px] sm:text-xs text-gray-400 mt-0.5 truncate leading-tight">
{% if request.user.user_type == 'agency' %}{% trans "Agency Portal" %}{% else %}{% trans "Applicant Portal" %}{% endif %}
</div>
</div>
<button onclick="closeMobileSidebar()"
class="lg:hidden ml-2 text-gray-400 hover:text-white transition touch-target"
aria-label="{% trans 'Close menu' %}">
<i data-lucide="x" class="w-5 h-5"></i>
</button>
</div>
<!-- Navigation Menu -->
<nav class="flex-1 overflow-y-auto sidebar-scroll momentum-scroll px-4 pb-4">
<div class="section-header text-[10px] uppercase tracking-widest text-gray-600 font-bold mb-3 px-2">
{% trans "Main" %}
</div>
<ul class="space-y-1.5">
{% if request.user.user_type == 'agency' %}
<li>
<a href="{% url 'agency_portal_dashboard' %}"
class="nav-link flex items-center rounded-lg transition touch-target {% if request.resolver_match.url_name == 'agency_portal_dashboard' %}text-white bg-temple-red/10 border-l-4 border-temple-red rounded-r-none{% else %}hover:bg-gray-800 hover:text-white{% endif %}"
aria-current="{% if request.resolver_match.url_name == 'agency_portal_dashboard' %}page{% endif %}">
<i data-lucide="layout-grid" class="nav-icon w-4 h-4 sm:w-5 sm:h-5 mr-3 shrink-0 {% if request.resolver_match.url_name == 'agency_portal_dashboard' %}text-temple-red{% endif %}"></i>
<span class="text-[14px] sm:text-[15px] font-medium block">{% trans "Dashboard" %}</span>
</a>
</li>
<li>
<a href="{% url 'agency_portal_persons_list' %}"
class="nav-link flex items-center rounded-lg transition touch-target {% if request.resolver_match.url_name == 'agency_portal_persons_list' %}text-white bg-temple-red/10 border-l-4 border-temple-red rounded-r-none{% else %}hover:bg-gray-800 hover:text-white{% endif %}"
aria-current="{% if request.resolver_match.url_name == 'agency_portal_persons_list' %}page{% endif %}">
<i data-lucide="users" class="nav-icon w-4 h-4 sm:w-5 sm:h-5 mr-3 shrink-0 {% if request.resolver_match.url_name == 'agency_portal_persons_list' %}text-temple-red{% endif %}"></i>
<span class="text-[14px] sm:text-[15px] font-medium block">{% trans "Applicants" %}</span>
</a>
</li>
<li>
<a href="{% url 'kaauh_career' %}"
class="nav-link flex items-center rounded-lg transition touch-target {% if request.resolver_match.url_name == 'kaauh_career' %}text-white bg-temple-red/10 border-l-4 border-temple-red rounded-r-none{% else %}hover:bg-gray-800 hover:text-white{% endif %}"
aria-current="{% if request.resolver_match.url_name == 'kaauh_career' %}page{% endif %}">
<i data-lucide="briefcase" class="nav-icon w-4 h-4 sm:w-5 sm:h-5 mr-3 shrink-0 {% if request.resolver_match.url_name == 'kaauh_career' %}text-temple-red{% endif %}"></i>
<span class="text-[14px] sm:text-[15px] font-medium block">{% trans "Careers" %}</span>
</a>
</li>
<li>
<a href="{% url 'message_list' %}"
class="nav-link flex items-center rounded-lg transition touch-target relative {% if request.resolver_match.url_name == 'message_list' or request.resolver_match.url_name|slice:':8' == 'message_' %}text-white bg-temple-red/10 border-l-4 border-temple-red rounded-r-none{% else %}hover:bg-gray-800 hover:text-white{% endif %}"
aria-current="{% if 'message_' in request.resolver_match.url_name %}page{% endif %}">
<i data-lucide="mail" class="nav-icon w-4 h-4 sm:w-5 sm:h-5 mr-3 shrink-0 {% if 'message_' in request.resolver_match.url_name %}text-temple-red{% endif %}"></i>
<span class="text-[14px] sm:text-[15px] font-medium block">{% trans "Messages" %}</span>
{% if request.user.get_unread_message_count > 0 %}
<span class="ml-auto bg-temple-red text-[10px] sm:text-xs text-white rounded-full font-medium nav-badge px-2 py-0.5">{{ request.user.get_unread_message_count }}</span>
{% endif %}
</a>
</li>
{% else %}
<li>
<a href="{% url 'applicant_portal_dashboard' %}"
class="nav-link flex items-center rounded-lg transition touch-target {% if request.resolver_match.url_name == 'applicant_portal_dashboard' %}text-white bg-temple-red/10 border-l-4 border-temple-red rounded-r-none{% else %}hover:bg-gray-800 hover:text-white{% endif %}"
aria-current="{% if request.resolver_match.url_name == 'applicant_portal_dashboard' %}page{% endif %}">
<i data-lucide="layout-grid" class="nav-icon w-4 h-4 sm:w-5 sm:h-5 mr-3 shrink-0 {% if request.resolver_match.url_name == 'applicant_portal_dashboard' %}text-temple-red{% endif %}"></i>
<span class="text-[14px] sm:text-[15px] font-medium block">{% trans "Dashboard" %}</span>
</a>
</li>
<li>
<a href="{% url 'kaauh_career' %}"
class="nav-link flex items-center rounded-lg transition touch-target {% if request.resolver_match.url_name == 'kaauh_career' %}text-white bg-temple-red/10 border-l-4 border-temple-red rounded-r-none{% else %}hover:bg-gray-800 hover:text-white{% endif %}"
aria-current="{% if request.resolver_match.url_name == 'kaauh_career' %}page{% endif %}">
<i data-lucide="briefcase" class="nav-icon w-4 h-4 sm:w-5 sm:h-5 mr-3 shrink-0 {% if request.resolver_match.url_name == 'kaauh_career' %}text-temple-red{% endif %}"></i>
<span class="text-[14px] sm:text-[15px] font-medium block">{% trans "Careers" %}</span>
</a>
</li>
<li>
<a href="{% url 'message_list' %}"
class="nav-link flex items-center rounded-lg transition touch-target {% if request.resolver_match.url_name == 'message_list' or request.resolver_match.url_name|slice:':8' == 'message_' %}text-white bg-temple-red/10 border-l-4 border-temple-red rounded-r-none{% else %}hover:bg-gray-800 hover:text-white{% endif %}"
aria-current="{% if 'message_' in request.resolver_match.url_name %}page{% endif %}">
<i data-lucide="mail" class="nav-icon w-4 h-4 sm:w-5 sm:h-5 mr-3 shrink-0 {% if 'message_' in request.resolver_match.url_name %}text-temple-red{% endif %}"></i>
<span class="text-[14px] sm:text-[15px] font-medium block">{% trans "Messages" %}</span>
{% if request.user.get_unread_message_count > 0 %}
<span class="ml-auto bg-temple-red text-[10px] sm:text-xs text-white rounded-full font-medium nav-badge px-2 py-0.5">{{ request.user.get_unread_message_count }}</span>
{% endif %}
</a>
</li>
{% endif %}
</ul>
<!-- Account Section -->
<div class="section-header text-[10px] uppercase tracking-widest text-gray-600 font-bold mt-6 mb-3 px-2">
{% trans "Account" %}
</div>
<ul class="space-y-1">
<li>
<a href="{% url 'user_detail' request.user.pk %}"
class="nav-link flex items-center rounded-lg transition touch-target {% if request.resolver_match.url_name == 'user_detail' %}text-white bg-temple-red/10 border-l-4 border-temple-red rounded-r-none{% else %}hover:bg-gray-800 hover:text-white{% endif %}"
aria-current="{% if request.resolver_match.url_name == 'user_detail' %}page{% endif %}">
<i data-lucide="user-circle" class="nav-icon w-4 h-4 sm:w-5 sm:h-5 mr-3 shrink-0 {% if request.resolver_match.url_name == 'user_detail' %}text-temple-red{% endif %}"></i>
<span class="text-[14px] sm:text-[15px] font-medium block">{% trans "My Profile" %}</span>
</a>
</li>
</ul>
</nav>
<!-- Logout Button -->
{% if request.user.is_authenticated %}
<div class="p-4 border-t border-gray-800 mt-auto">
<form method="post" action="{% url 'account_logout'%}">
{% csrf_token %}
<button type="submit"
class="mobile-btn w-full flex items-center justify-center gap-2 bg-gray-800 hover:bg-gray-700 text-white rounded-lg smooth-transition touch-target"
title="{% trans 'Sign Out' %}">
<i data-lucide="log-out" class="w-4 h-4 shrink-0"></i>
<span class="font-medium">{% trans "Sign Out" %}</span>
</button>
</form>
</div>
{% endif %}
<!-- Powered By Footer -->
<div class="p-4 border-t border-gray-800 text-center safe-area-padding-bottom">
<a href="https://tenhal.sa/" class="text-decoration-none block no-touch-target" target="_blank" rel="noopener noreferrer">
<div class="text-[10px] text-gray-600 uppercase tracking-widest leading-relaxed">
{% trans "POWERED BY" %} <span class="text-white font-bold">TENHAL</span>
</div>
</a>
</div>
</div>
</aside>
<!-- Main Content -->
<main class="flex-1 lg:ml-64 flex flex-col min-h-screen smooth-transition">
<!-- Header -->
<header class="sticky-header bg-white border-b px-3 sm:px-4 lg:px-6 py-2.5 sm:py-3 flex justify-between items-center shadow-sm safe-area-padding-top">
<div class="flex items-center flex-1 min-w-0">
<!-- Mobile Menu Toggle -->
<button id="menu-toggle"
class="lg:hidden mr-2 sm:mr-3 p-2.5 text-gray-600 hover:bg-gray-100 active:bg-gray-200 rounded-lg smooth-transition touch-target shrink-0"
aria-label="{% trans 'Open menu' %}"
aria-expanded="false"
aria-controls="sidebar">
<i data-lucide="menu" class="w-5 h-5"></i>
</button>
<!-- Logo/Title -->
<div class="hospital-text min-w-0 flex flex-wrap items-baseline gap-x-2 mobile-spaced">
<span class="text-temple-red font-bold text-sm sm:text-base whitespace-nowrap">
{% if request.user.user_type == 'agency' %}{% trans "Agency Portal" %}{% else %}{% trans "Applicant Portal" %}{% endif %}
</span>
<span class="text-gray-400 text-xs sm:text-sm hidden sm:inline">|</span>
<span class="text-gray-600 text-xs sm:text-sm whitespace-nowrap">KAAUH</span>
</div>
</div>
<!-- Header Actions -->
<div class="flex items-center gap-1 sm:gap-2 ml-1 sm:ml-2 shrink-0">
<!-- Messages Icon -->
<a href="{% url 'message_list' %}"
class="header-action relative text-gray-500 hover:text-temple-red smooth-transition rounded-lg hover:bg-gray-50"
aria-label="{% trans 'Messages' %}{% if request.user.get_unread_message_count > 0 %} ({{ request.user.get_unread_message_count }} {% trans 'unread' %}){% endif %}">
<i data-lucide="mail" class="w-5 h-5"></i>
{% if request.user.get_unread_message_count > 0 %}
<span class="absolute top-1 right-1 bg-temple-red border-2 border-white w-2.5 h-2.5 rounded-full" aria-hidden="true"></span>
{% endif %}
</a>
<!-- Language Switcher -->
{% if LANGUAGE_CODE == 'en' %}
<form action="{% url 'set_language' %}" method="post" class="relative">
{% csrf_token %}
<input name="language" type="hidden" value="ar">
<input name="next" type="hidden" value="{{ request.get_full_path }}">
<button class="lang-btn flex items-center gap-1.5 sm:gap-2 bg-gray-100 hover:bg-gray-200 active:bg-gray-300 rounded-lg smooth-transition touch-target"
aria-label="{% trans 'Switch to Arabic' %}">
<span class="text-base" aria-hidden="true">🇸🇦</span>
<span class="hidden sm:inline text-sm">{% trans "العربية" %}</span>
</button>
</form>
{% else %}
<form action="{% url 'set_language' %}" method="post" class="relative">
{% csrf_token %}
<input name="language" type="hidden" value="en">
<input name="next" type="hidden" value="{{ request.get_full_path }}">
<button class="lang-btn flex items-center gap-1.5 sm:gap-2 bg-gray-100 hover:bg-gray-200 active:bg-gray-300 rounded-lg smooth-transition touch-target"
aria-label="{% trans 'Switch to English' %}">
<span class="text-base" aria-hidden="true">🇺🇸</span>
<span class="hidden sm:inline text-sm">English</span>
</button>
</form>
{% endif %}
<!-- User Menu Dropdown -->
<div class="dropdown relative">
<button class="header-action flex items-center gap-2 rounded-lg hover:bg-gray-50"
id="user-menu-button"
aria-haspopup="true"
aria-expanded="false"
aria-label="{% trans 'User menu' %}">
{% if user.profile_image %}
<img src="{{ user.profile_image.url }}"
alt="{{ user.get_full_name|default:user.username }}"
class="user-avatar rounded-full border-2 border-gray-200 shadow-sm object-cover">
{% else %}
<div class="user-avatar rounded-full bg-temple-red flex items-center justify-center text-white font-bold text-[12px] sm:text-sm border-2 border-gray-200">
{{ user.username|first|upper }}
</div>
{% endif %}
</button>
<div class="hidden absolute right-0 mt-2 w-56 sm:w-64 bg-white rounded-xl shadow-lg border border-gray-100 py-2 z-50 dropdown-menu"
id="user-menu-dropdown"
role="menu"
aria-labelledby="user-menu-button">
<div class="px-4 py-3 border-b border-gray-100">
<p class="text-sm font-bold text-gray-800 truncate">{{ user.get_full_name|default:user.username }}</p>
<p class="text-xs text-gray-500 truncate mt-1">{{ user.email }}</p>
</div>
<a href="{% url 'user_detail' request.user.pk %}"
class="block px-4 py-2.5 text-sm text-gray-700 hover:bg-gray-50 active:bg-gray-100 transition touch-target"
role="menuitem">
<div class="flex items-center gap-2">
<i data-lucide="user-circle" class="w-4 h-4 inline-block mr-2 align-text-bottom"></i>
{% trans "Profile" %}
</div>
</a>
<hr class="my-2 border-gray-100">
<form method="post" action="{% url 'account_logout'%}">
{% csrf_token %}
<button type="submit"
class="no-touch-target w-full text-left px-4 py-2.5 text-sm text-red-600 hover:bg-red-50 active:bg-red-100 transition"
role="menuitem">
<i data-lucide="log-out" class="w-4 h-4 inline-block mr-2 align-text-bottom"></i>
{% trans "Sign Out" %}
</button>
</form>
</div>
</div>
</div>
</header>
<!-- Messages/Alerts -->
<div class="p-4 sm:p-6 lg:p-8 space-y-4 flex-1 overflow-x-hidden">
{% if messages %}
<div class="space-y-2.5" role="alert" aria-live="polite">
{% for message in messages %}
<div class="alert p-4 rounded-lg shadow-sm border flex items-start gap-3 smooth-transition {% if message.tags == 'error' %}bg-red-50 border-red-200 text-red-800{% elif message.tags == 'success' %}bg-green-50 border-green-200 text-green-800{% elif message.tags == 'warning' %}bg-yellow-50 border-yellow-200 text-yellow-800{% else %}bg-blue-50 border-blue-200 text-blue-800{% endif %}">
<span class="flex-1 text-sm leading-relaxed">{{ message }}</span>
<button type="button"
class="text-gray-400 hover:text-gray-600 p-1.5 rounded hover:bg-gray-100 transition touch-target shrink-0"
onclick="this.parentElement.parentElement.remove()"
aria-label="{% trans 'Dismiss' %}">
<i data-lucide="x" class="w-4 h-4"></i>
</button>
</div>
{% endfor %}
</div>
{% endif %}
<!-- Page Content -->
{% block content %}{% endblock %}
</div>
<!-- Footer -->
<footer class="mt-auto py-4 border-t bg-white safe-area-padding-bottom">
<div class="px-4 text-center">
<div class="text-xs sm:text-sm text-gray-400">
&copy; {% now "Y" %} KAAUH. {% trans "All rights reserved." %}
</div>
</div>
</footer>
</main>
<!-- Delete Modal -->
{% include 'includes/delete_modal.html' %}
<script>
// Initialize Lucide icons
lucide.createIcons();
// Mobile Sidebar Toggle
const menuToggle = document.getElementById('menu-toggle');
const sidebar = document.getElementById('sidebar');
const sidebarBackdrop = document.getElementById('sidebar-backdrop');
function openMobileSidebar() {
sidebar.classList.remove('-translate-x-full');
sidebar.classList.add('translate-x-0');
sidebarBackdrop.classList.remove('hidden');
document.body.style.overflow = 'hidden';
menuToggle?.setAttribute('aria-expanded', 'true');
}
function closeMobileSidebar() {
sidebar.classList.add('-translate-x-full');
sidebar.classList.remove('translate-x-0');
sidebarBackdrop.classList.add('hidden');
document.body.style.overflow = '';
menuToggle?.setAttribute('aria-expanded', 'false');
}
// Make functions available globally
window.openMobileSidebar = openMobileSidebar;
window.closeMobileSidebar = closeMobileSidebar;
if (menuToggle) {
menuToggle.addEventListener('click', () => {
if (sidebar.classList.contains('-translate-x-full')) {
openMobileSidebar();
} else {
closeMobileSidebar();
}
});
}
// Close sidebar on escape key
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && !sidebar.classList.contains('-translate-x-full')) {
closeMobileSidebar();
}
});
// User Dropdown Toggle
const userMenuButton = document.getElementById('user-menu-button');
const userMenuDropdown = document.getElementById('user-menu-dropdown');
if (userMenuButton && userMenuDropdown) {
userMenuButton.addEventListener('click', (e) => {
e.stopPropagation();
const isHidden = userMenuDropdown.classList.contains('hidden');
userMenuDropdown.classList.toggle('hidden');
userMenuButton.setAttribute('aria-expanded', isHidden ? 'true' : 'false');
// Reinitialize icons when dropdown opens
if (!isHidden) {
setTimeout(() => lucide.createIcons(), 10);
}
});
// Close dropdown when clicking outside
document.addEventListener('click', (e) => {
if (!userMenuButton.contains(e.target) && !userMenuDropdown.contains(e.target)) {
userMenuDropdown.classList.add('hidden');
userMenuButton.setAttribute('aria-expanded', 'false');
}
});
}
// Close dropdown on escape key
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && !userMenuDropdown.classList.contains('hidden')) {
userMenuDropdown.classList.add('hidden');
userMenuButton.setAttribute('aria-expanded', 'false');
userMenuButton.focus();
}
});
// Performance: Debounce scroll events
let ticking = false;
function onScroll() {
if (!ticking) {
window.requestAnimationFrame(() => {
ticking = false;
});
ticking = true;
}
}
window.addEventListener('scroll', onScroll, { passive: true });
// Handle window resize for mobile
let resizeTimer;
window.addEventListener('resize', () => {
clearTimeout(resizeTimer);
resizeTimer = setTimeout(() => {
if (window.innerWidth >= 1024 && !sidebar.classList.contains('-translate-x-full')) {
closeMobileSidebar();
}
}, 250);
});
</script>
<script src="https://cdn.jsdelivr.net/npm/htmx.org@2.0.7/dist/htmx.min.js"></script>
{% block customJS %}{% endblock %}
</body>
</html>