@using System.Security.Claims @using Alica_Technol...
생성일: 2025년 9월 14일
답변에 사용된 모델 GPT-5 Thinking by Chat01
생성일: 2025년 9월 14일
답변에 사용된 모델 GPT-5 Thinking by Chat01
@using System.Security.Claims
@using Alica_Technologies_LLP.Models
@functions {
public string GetUserInitials(string userName)
{
if (string.IsNullOrEmpty(userName))
return "U";
textvar parts = userName.Split(' ', StringSplitOptions.RemoveEmptyEntries); if (parts.Length == 0) return "U"; if (parts.Length == 1) return parts[0].Substring(0, Math.Min(2, parts[0].Length)).ToUpper(); return string.Concat(parts.Select(p => p.FirstOrDefault())).ToUpper(); } public string GetUserRole() { return User.FindFirstValue(ClaimTypes.Role) ?? "User"; } public bool HasRole(string roleName) { return User.IsInRole(roleName); } public bool HasAnyRole(params string[] roleNames) { return roleNames.Any(role => User.IsInRole(role)); }
}
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>@ViewData["Title"] - Alica Technologies LLP</title> <link rel="stylesheet" href="~/css/site.css" asp-append-version="true" /> </head> <body> <!-- Top Navigation Bar --> <nav class="navbar"> <div class="navbar-container"> <!-- Logo/Brand --> <a class="navbar-brand" href="@Url.Action("Index", "Home")"> Alica Technologies LLP </a></body> </html>text<!-- Main Navigation --> <div class="navbar-nav"> @if (User.Identity.IsAuthenticated) { <!-- User Profile Dropdown --> <div class="user-dropdown"> <button class="user-profile-btn" type="button"> <div class="user-avatar"> <span class="user-initials">@GetUserInitials(User.Identity?.Name ?? "")</span> </div> <span class="username-text">@(User.Identity?.Name ?? "User")</span> <svg class="dropdown-arrow" width="12" height="12" viewBox="0 0 12 12" fill="none"> <path d="M2.5 4.5L6 8L9.5 4.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/> </svg> </button> <!-- Dropdown Menu --> <div id="userDropdown" class="user-dropdown-menu"> <div class="dropdown-header"> <div class="dropdown-user-info"> <div class="dropdown-avatar"> <span class="dropdown-user-initials">@GetUserInitials(User.Identity?.Name ?? "")</span> </div> <div> <div class="dropdown-username">@(User.Identity?.Name ?? "User")</div> <div class="dropdown-user-role">@GetUserRole()</div> </div> </div> </div> <div class="dropdown-divider"></div> <!-- Profile Link --> <a href="@Url.Action("MyProfile", "Account")" class="dropdown-item"> <svg class="dropdown-item-icon" width="16" height="16" viewBox="0 0 16 16" fill="none"> <path d="M8 8C9.65685 8 11 6.65685 11 5C11 3.34315 9.65685 2 8 2C6.34315 2 5 3.34315 5 5C5 6.65685 6.34315 8 8 8Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/> <path d="M13 14C13 11.7909 10.7614 10 8 10C5.23858 10 3 11.7909 3 14" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/> </svg> My Profile </a> <!-- Role-based menu items --> @if (HasRole("Administrator")) { <a href="@Url.Action("Index", "Dashboard")" class="dropdown-item"> <svg class="dropdown-item-icon" width="16" height="16" viewBox="0 0 16 16" fill="none"> <path d="M2 6H14M2 10H14M4 14H12C13.1046 14 14 13.1046 14 12V4C14 2.89543 13.1046 2 12 2H4C2.89543 2 2 2.89543 2 4V12C2 13.1046 2.89543 14 4 14Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/> </svg> Dashboard </a> <div class="dropdown-submenu"> <button class="dropdown-item dropdown-submenu-btn" type="button" onclick="toggleUserManagementSubmenu(event)"> <svg class="dropdown-item-icon" width="16" height="16" viewBox="0 0 16 16" fill="none"> <path d="M8 2V14M2 8H14" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/> <circle cx="8" cy="8" r="7" stroke="currentColor" stroke-width="1.5"/> </svg> User Management <svg class="dropdown-submenu-arrow" width="12" height="12" viewBox="0 0 12 12" fill="none"> <path d="M3.5 4.5L6 7L8.5 4.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/> </svg> </button> <div class="dropdown-submenu-content" id="userManagementSubmenu"> <a href="@Url.Action("Index", "UserManagement")" class="dropdown-submenu-item"> <svg class="dropdown-item-icon" width="14" height="14" viewBox="0 0 14 14" fill="none"> <path d="M2 5.5H12M2 8.5H8M3 11H11C11.5304 11 12.0391 10.7893 12.4142 10.4142C12.7893 10.0391 13 9.53043 13 9V3C13 2.46957 12.7893 1.96086 12.4142 1.58579C12.0391 1.21071 11.5304 1 11 1H3C2.46957 1 1.96086 1.21071 1.58579 1.58579C1.21071 1.96086 1 2.46957 1 3V9C1 9.53043 1.21071 10.0391 1.58579 10.4142C1.96086 10.7893 2.46957 11 3 11Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/> </svg> Manage Users </a> <a href="@Url.Action("CreateUser", "UserManagement")" class="dropdown-submenu-item"> <svg class="dropdown-item-icon" width="14" height="14" viewBox="0 0 14 14" fill="none"> <path d="M7 1V13M1 7H13" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/> <circle cx="7" cy="7" r="6.5" stroke="currentColor" stroke-width="1.5"/> </svg> Create User </a> </div> </div> } @if (HasAnyRole("Manager", "Administrator")) { <a href="#" class="dropdown-item"> <svg class="dropdown-item-icon" width="16" height="16" viewBox="0 0 16 16" fill="none"> <path d="M9 14H7C4.23858 14 2 11.7614 2 9V7C2 4.23858 4.23858 2 7 2H9C11.7614 2 14 4.23858 14 7V9C14 11.7614 11.7614 14 9 14Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/> <path d="M5 6H11M5 10H9" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/> </svg> Reports </a> } <div class="dropdown-divider"></div> <!-- Logout --> <form asp-action="Logout" asp-controller="Account" method="post" style="display: contents;"> <button type="submit" class="dropdown-item dropdown-item-logout"> <svg class="dropdown-item-icon" width="16" height="16" viewBox="0 0 16 16" fill="none"> <path d="M6 14H4C2.89543 14 2 13.1046 2 12V4C2 2.89543 2.89543 2 4 2H6M10 11L14 8M14 8L10 5M14 8H6" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/> </svg> Logout </button> </form> </div> </div> } else { <a class="nav-link" href="@Url.Action("Login", "Account")">Login</a> <a class="nav-link" href="@Url.Action("Register", "Account")">Register</a> } </div> </div> </nav> <!-- Sidebar for Role-based Navigation (Hidden by default, can be enabled later) --> <aside class="sidebar" id="sidebar"> <div class="sidebar-header"> <h3 class="sidebar-title">Navigation</h3> <button class="sidebar-close" onclick="toggleSidebar()"> <svg width="20" height="20" viewBox="0 0 20 20" fill="none"> <path d="M5 5L15 15M15 5L5 15" stroke="currentColor" stroke-width="2" stroke-linecap="round"/> </svg> </button> </div> <nav class="sidebar-nav"> <!-- Role-based sidebar items --> @if (User.Identity.IsAuthenticated) { @if (HasRole("Administrator")) { <div class="sidebar-section"> <h4 class="sidebar-section-title">Administration</h4> <a href="#" class="sidebar-item">User Management</a> <a href="#" class="sidebar-item">System Settings</a> <a href="#" class="sidebar-item">Audit Logs</a> </div> } @if (HasAnyRole("Manager", "Administrator")) { <div class="sidebar-section"> <h4 class="sidebar-section-title">Management</h4> <a href="#" class="sidebar-item">Reports</a> <a href="#" class="sidebar-item">Analytics</a> <a href="#" class="sidebar-item">Team Overview</a> </div> } @if (HasAnyRole("Operations", "Administrator")) { <div class="sidebar-section"> <h4 class="sidebar-section-title">Operations</h4> <a href="#" class="sidebar-item">Process Management</a> <a href="#" class="sidebar-item">Workflow Status</a> <a href="#" class="sidebar-item">Task Queue</a> </div> } @if (HasAnyRole("Accounts", "Administrator")) { <div class="sidebar-section"> <h4 class="sidebar-section-title">Accounts</h4> <a href="#" class="sidebar-item">Financial Reports</a> <a href="#" class="sidebar-item">Invoice Management</a> <a href="#" class="sidebar-item">Tax Compliance</a> </div> } @if (HasAnyRole("Store", "Administrator")) { <div class="sidebar-section"> <h4 class="sidebar-section-title">Store</h4> <a href="#" class="sidebar-item">Inventory</a> <a href="#" class="sidebar-item">Purchase Orders</a> <a href="#" class="sidebar-item">Supplier Management</a> </div> } } </nav> </aside> <!-- Main Content Area --> <main class="flex-grow-1"> <div class="container py-4"> @RenderBody() </div> </main> <!-- Footer --> <footer class="border-t mt-auto py-6 bg-white"> <div class="container"> <div class="text-center"> <p class="mb-0 text-gray-500 font-size-sm"> © 2024 Alica Technologies LLP. All rights reserved. </p> </div> </div> </footer> <!-- JavaScript for dropdown and sidebar functionality --> <script> // Initialize dropdown functionality document.addEventListener('DOMContentLoaded', function() { initializeDropdown(); }); function initializeDropdown() { const userProfileBtn = document.querySelector('.user-profile-btn'); const userDropdown = document.getElementById('userDropdown'); if (!userProfileBtn || !userDropdown) { console.warn('Dropdown elements not found'); return; } console.log('Dropdown initialized successfully'); userProfileBtn.addEventListener('click', function(event) { event.preventDefault(); event.stopPropagation(); console.log('Dropdown button clicked'); console.log('Before toggle - has show class:', userDropdown.classList.contains('show')); console.log('Dropdown element:', userDropdown); // Toggle dropdown const isOpen = userDropdown.classList.contains('show'); if (isOpen) { userDropdown.classList.remove('show'); userDropdown.style.display = 'none'; console.log('Dropdown closed'); } else { // Close any other open dropdowns first document.querySelectorAll('.user-dropdown.show').forEach(dropdown => { dropdown.classList.remove('show'); dropdown.style.display = 'none'; }); userDropdown.classList.add('show'); // Force inline style as backup userDropdown.style.display = 'block'; userDropdown.style.opacity = '1'; userDropdown.style.visibility = 'visible'; userDropdown.style.transform = 'translateY(0)'; userDropdown.style.pointerEvents = 'auto'; console.log('Dropdown opened'); console.log('After adding - has show class:', userDropdown.classList.contains('show')); console.log('Inline styles applied:', userDropdown.style.cssText); // Force a style recalculation userDropdown.offsetHeight; } // Setup outside click listener setupOutsideClickListener(); // Debug: Log the computed style setTimeout(() => { const computedStyle = window.getComputedStyle(userDropdown); console.log('Computed display:', computedStyle.display); console.log('Computed visibility:', computedStyle.visibility); console.log('Computed opacity:', computedStyle.opacity); console.log('Current classes:', userDropdown.className); }, 100); }); // Prevent dropdown from closing when clicking inside it userDropdown.addEventListener('click', function(event) { event.stopPropagation(); }); } function setupOutsideClickListener() { // Remove existing listener to prevent duplicates document.removeEventListener('click', closeDropdownOutside); // Add new listener with small delay setTimeout(() => { document.addEventListener('click', closeDropdownOutside); }, 100); } function closeDropdownOutside(event) { const userProfileBtn = document.querySelector('.user-profile-btn'); const userDropdown = document.getElementById('userDropdown'); if (userProfileBtn && userDropdown && !userProfileBtn.contains(event.target) && !userDropdown.contains(event.target)) { userDropdown.classList.remove('show'); userDropdown.style.display = 'none'; userDropdown.style.opacity = ''; userDropdown.style.visibility = ''; userDropdown.style.transform = ''; userDropdown.style.pointerEvents = ''; document.removeEventListener('click', closeDropdownOutside); console.log('Dropdown closed by outside click'); } } // Toggle User Management submenu function toggleUserManagementSubmenu(event) { event.preventDefault(); event.stopPropagation(); const submenu = document.getElementById('userManagementSubmenu'); const arrow = event.currentTarget.querySelector('.dropdown-submenu-arrow'); if (submenu && arrow) { const isOpen = submenu.classList.contains('show'); if (isOpen) { submenu.classList.remove('show'); arrow.style.transform = 'rotate(0deg)'; } else { submenu.classList.add('show'); arrow.style.transform = 'rotate(180deg)'; } } } // Also handle keyboard accessibility document.addEventListener('keydown', function(event) { if (event.key === 'Escape') { const userDropdown = document.getElementById('userDropdown'); if (userDropdown && userDropdown.classList.contains('show')) { userDropdown.classList.remove('show'); userDropdown.style.display = 'none'; userDropdown.style.opacity = ''; userDropdown.style.visibility = ''; userDropdown.style.transform = ''; userDropdown.style.pointerEvents = ''; document.removeEventListener('click', closeDropdownOutside); console.log('Dropdown closed by Escape key'); } } }); </script> @RenderSection("Scripts", required: false)
Create menu items in the layout such that, it would appear based on the roles. In desktop mode, it should be visible as is. In the mobile view, it would collapse with the buttons allowing us to expand the menu. Make sure you think and reason about its positioning extremely precisely before forming a plan.
/* Swiss Design System - Monochrome & Minimal */
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@100;200;300;400;500;600;700;800;900&family=Geist:wght@100;200;300;400;500;600;700;800;900&display=swap');
:root {
/* Swiss Monochrome Palette */
--color-black: #000000;
--color-gray-900: #0a0a0a;
--color-gray-800: #1a1a1a;
--color-gray-700: #2a2a2a;
--color-gray-600: #3a3a3a;
--color-gray-500: #4a4a4a;
--color-gray-400: #6a6a6a;
--color-gray-300: #8a8a8a;
--color-gray-200: #aaaaaa;
--color-gray-100: #cacaca;
--color-gray-50: #eaeaea;
--color-white: #ffffff;
--color-off-white: #fafafa;
text/* Swiss Typography - Geist as primary, Inter as fallback */ --font-primary: 'Geist', 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; --font-mono: 'Geist Mono', 'JetBrains Mono', 'Menlo', 'Monaco', 'Courier New', monospace; /* Swiss Typography Scale */ --font-size-xs: 0.75rem; --font-size-sm: 0.875rem; --font-size-base: 1rem; --font-size-lg: 1.125rem; --font-size-xl: 1.25rem; --font-size-2xl: 1.5rem; --font-size-3xl: 1.875rem; --font-size-4xl: 2.25rem; --font-size-5xl: 3rem; --font-size-6xl: 3.75rem; /* Swiss 8-Point Grid System */ --spacing-px: 1px; --spacing-1: 0.125rem; --spacing-2: 0.25rem; --spacing-3: 0.375rem; --spacing-4: 0.5rem; --spacing-5: 0.625rem; --spacing-6: 0.75rem; --spacing-8: 1rem; --spacing-10: 1.25rem; --spacing-12: 1.5rem; --spacing-16: 2rem; --spacing-20: 2.5rem; --spacing-24: 3rem; --spacing-32: 4rem; --spacing-40: 5rem; --spacing-48: 6rem; --spacing-64: 8rem; /* Swiss Minimal Borders */ --border-sm: 1px solid var(--color-gray-200); --border-md: 1px solid var(--color-gray-300); --border-lg: 2px solid var(--color-gray-400); /* Swiss Minimal Shadows */ --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05); --shadow-md: 0 2px 4px rgba(0, 0, 0, 0.1); --shadow-lg: 0 4px 8px rgba(0, 0, 0, 0.15); --shadow-xl: 0 8px 16px rgba(0, 0, 0, 0.2); /* Swiss Transitions */ --transition-fast: 150ms cubic-bezier(0.4, 0, 0.2, 1); --transition-normal: 300ms cubic-bezier(0.4, 0, 0.2, 1); --transition-slow: 500ms cubic-bezier(0.4, 0, 0.2, 1); /* Swiss Layout */ --max-width-sm: 640px; --max-width-md: 768px; --max-width-lg: 1024px; --max-width-xl: 1280px; --max-width-2xl: 1536px; --container-padding: var(--spacing-8);
}
html {
scroll-behavior: smooth;
}
body {
font-family: var(--font-primary);
font-size: var(--font-size-base);
line-height: 1.5;
color: var(--color-black);
background: var(--color-white);
min-height: 100vh;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
overflow-x: hidden;
font-weight: 400;
}
/* Swiss Typography */
h1, h2, h3, h4, h5, h6 {
font-family: var(--font-primary);
font-weight: 600;
line-height: 1.2;
margin-bottom: var(--spacing-8);
color: var(--color-black);
letter-spacing: -0.02em;
}
h1 {
font-size: var(--font-size-5xl);
font-weight: 700;
letter-spacing: -0.04em;
margin-bottom: var(--spacing-12);
}
h2 {
font-size: var(--font-size-4xl);
font-weight: 600;
letter-spacing: -0.03em;
margin-bottom: var(--spacing-10);
}
h3 {
font-size: var(--font-size-3xl);
font-weight: 600;
letter-spacing: -0.02em;
margin-bottom: var(--spacing-8);
}
h4 {
font-size: var(--font-size-2xl);
font-weight: 600;
margin-bottom: var(--spacing-6);
}
h5 {
font-size: var(--font-size-xl);
font-weight: 500;
margin-bottom: var(--spacing-4);
}
h6 {
font-size: var(--font-size-lg);
font-weight: 500;
margin-bottom: var(--spacing-4);
}
p {
margin-bottom: var(--spacing-6);
color: var(--color-gray-700);
line-height: 1.6;
}
a {
color: var(--color-black);
text-decoration: none;
transition: var(--transition-fast);
font-weight: 500;
position: relative;
}
a:hover {
color: var(--color-gray-700);
}
a::after {
content: '';
position: absolute;
width: 0;
height: 1px;
bottom: -2px;
left: 0;
background: var(--color-black);
transition: var(--transition-normal);
}
a:hover::after {
width: 100%;
}
/* Swiss Buttons */
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
padding: var(--spacing-4) var(--spacing-8);
border: var(--border-sm);
border-radius: 0;
font-family: var(--font-primary);
font-size: var(--font-size-base);
font-weight: 500;
text-decoration: none;
cursor: pointer;
transition: var(--transition-fast);
position: relative;
overflow: hidden;
white-space: nowrap;
background: var(--color-white);
color: var(--color-black);
letter-spacing: -0.01em;
}
.btn:hover {
background: var(--color-gray-50);
border-color: var(--color-gray-300);
}
.btn:active {
background: var(--color-gray-100);
transform: translateY(1px);
}
.btn-primary {
background: var(--color-black);
color: var(--color-white);
border-color: var(--color-black);
}
.btn-primary:hover {
background: var(--color-gray-900);
border-color: var(--color-gray-900);
}
.btn-outline {
background: transparent;
border: var(--border-sm);
color: var(--color-black);
}
.btn-outline:hover {
background: var(--color-black);
color: var(--color-white);
}
.btn-block {
display: flex;
width: 100%;
}
.btn-lg {
padding: var(--spacing-6) var(--spacing-12);
font-size: var(--font-size-lg);
}
.btn-sm {
padding: var(--spacing-2) var(--spacing-4);
font-size: var(--font-size-sm);
}
/* Swiss Forms */
.form-group {
margin-bottom: var(--spacing-8);
position: relative;
}
.form-label {
display: block;
font-family: var(--font-primary);
font-weight: 500;
margin-bottom: var(--spacing-2);
color: var(--color-black);
font-size: var(--font-size-sm);
letter-spacing: -0.01em;
}
.form-control {
width: 100%;
padding: var(--spacing-4) var(--spacing-4);
border: var(--border-sm);
border-radius: 0;
font-family: var(--font-primary);
font-size: var(--font-size-base);
transition: var(--transition-fast);
background: var(--color-white);
color: var(--color-black);
box-shadow: var(--shadow-sm);
}
.form-control:focus {
outline: none;
border-color: var(--color-gray-600);
box-shadow: var(--shadow-md);
background: var(--color-off-white);
}
.form-control::placeholder {
color: var(--color-gray-400);
}
.form-select {
width: 100%;
padding: var(--spacing-4) var(--spacing-4);
border: var(--border-sm);
border-radius: 0;
font-family: var(--font-primary);
font-size: var(--font-size-base);
transition: var(--transition-fast);
background: var(--color-white);
color: var(--color-black);
box-shadow: var(--shadow-sm);
cursor: pointer;
}
.form-select:focus {
outline: none;
border-color: var(--color-gray-600);
box-shadow: var(--shadow-md);
}
.form-check {
display: flex;
align-items: center;
margin-bottom: var(--spacing-4);
}
.form-check-input {
margin-right: var(--spacing-2);
cursor: pointer;
width: 16px;
height: 16px;
border: var(--border-sm);
border-radius: 2px;
}
/* Swiss Cards */
.card {
background: var(--color-white);
border: var(--border-sm);
border-radius: 0;
box-shadow: var(--shadow-sm);
overflow: hidden;
transition: var(--transition-fast);
position: relative;
}
.card:hover {
box-shadow: var(--shadow-md);
}
.card-body {
padding: var(--spacing-12);
position: relative;
z-index: 1;
}
.card-header {
padding: var(--spacing-8) var(--spacing-12);
border-bottom: var(--border-sm);
background: var(--color-off-white);
position: relative;
z-index: 1;
}
.card-title {
margin: 0;
font-size: var(--font-size-xl);
font-weight: 600;
color: var(--color-black);
letter-spacing: -0.02em;
}
/* Swiss Navigation */
.navbar {
background: var(--color-white);
border-bottom: var(--border-sm);
box-shadow: var(--shadow-sm);
position: sticky;
top: 0;
z-index: 1000;
padding: var(--spacing-4) 0;
}
.navbar-container {
max-width: var(--max-width-xl);
margin: 0 auto;
padding: 0 var(--container-padding);
display: flex;
justify-content: space-between;
align-items: center;
height: 64px;
}
.navbar-brand {
font-family: var(--font-primary);
font-size: var(--font-size-lg);
font-weight: 600;
color: var(--color-black);
text-decoration: none;
transition: var(--transition-fast);
letter-spacing: -0.02em;
}
.navbar-brand:hover {
color: var(--color-gray-700);
}
.navbar-nav {
display: flex;
align-items: center;
gap: var(--spacing-6);
}
/* User Dropdown Styles */
.user-dropdown {
position: relative;
}
.user-profile-btn {
display: flex;
align-items: center;
gap: var(--spacing-3);
padding: var(--spacing-2) var(--spacing-4);
border: var(--border-sm);
background-color: var(--color-white);
color: var(--color-black);
border-radius: 0;
cursor: pointer;
transition: all 0.2s ease;
font-family: var(--font-primary);
font-size: var(--font-size-sm);
font-weight: 500;
}
.user-profile-btn:hover {
background-color: var(--color-gray-50);
border-color: var(--color-gray-800);
cursor: pointer;
}
.user-profile-btn:focus {
outline: 2px solid var(--color-gray-300);
outline-offset: -2px;
}
.user-avatar {
width: 32px;
height: 32px;
border: var(--border-sm);
background-color: var(--color-gray-100);
display: flex;
align-items: center;
justify-content: center;
font-weight: 600;
font-size: var(--font-size-xs);
color: var(--color-gray-700);
}
.user-initials {
text-transform: uppercase;
line-height: 1;
}
.username-text {
font-weight: 500;
color: var(--color-black);
}
.dropdown-arrow {
transition: transform 0.2s ease;
}
.user-dropdown.show .dropdown-arrow {
transform: rotate(180deg);
}
.user-dropdown-menu {
position: absolute;
top: calc(100% + var(--spacing-2));
right: 0;
min-width: 280px;
border: var(--border-sm);
background-color: var(--color-white);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
z-index: 9999;
display: none;
transform: translateY(-var(--spacing-2));
transition: all 0.2s ease;
}
.user-dropdown.show .user-dropdown-menu {
display: block;
transform: translateY(0);
opacity: 1;
visibility: visible;
}
/* Debug: Add a visual indicator when dropdown is shown */
.user-dropdown.show .user-profile-btn {
border-color: var(--color-gray-800);
background-color: var(--color-gray-50);
}
.dropdown-header {
padding: var(--spacing-6);
border-bottom: var(--border-sm);
}
.dropdown-user-info {
display: flex;
align-items: center;
gap: var(--spacing-4);
}
.dropdown-avatar {
width: 48px;
height: 48px;
border: var(--border-sm);
background-color: var(--color-gray-100);
display: flex;
align-items: center;
justify-content: center;
font-weight: 600;
font-size: var(--font-size-sm);
color: var(--color-gray-700);
}
.dropdown-user-initials {
text-transform: uppercase;
line-height: 1;
}
.dropdown-username {
font-weight: 600;
font-size: var(--font-size-base);
color: var(--color-black);
margin-bottom: var(--spacing-1);
}
.dropdown-user-role {
font-size: var(--font-size-xs);
color: var(--color-gray-500);
text-transform: capitalize;
}
.dropdown-divider {
height: 1px;
background-color: var(--color-gray-200);
margin: var(--spacing-4) 0;
}
.dropdown-item {
display: flex;
align-items: center;
gap: var(--spacing-3);
padding: var(--spacing-3) var(--spacing-6);
color: var(--color-black);
text-decoration: none;
font-size: var(--font-size-sm);
font-weight: 400;
transition: all 0.2s ease;
border: none;
background: none;
width: 100%;
text-align: left;
cursor: pointer;
}
.dropdown-item:hover {
background-color: var(--color-gray-50);
color: var(--color-black);
}
.dropdown-item-icon {
width: 16px;
height: 16px;
color: var(--color-gray-500);
flex-shrink: 0;
}
.dropdown-item:hover .dropdown-item-icon {
color: var(--color-gray-700);
}
.dropdown-item-logout {
color: var(--color-gray-700);
}
.dropdown-item-logout:hover {
background-color: var(--color-gray-100);
color: var(--color-black);
}
/* Dropdown Submenu Styles */
.dropdown-submenu {
position: relative;
}
.dropdown-submenu-btn {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
padding-right: var(--spacing-8);
}
.dropdown-submenu-arrow {
transition: transform 0.2s ease;
margin-left: auto;
}
.dropdown-submenu-content {
position: absolute;
left: 100%;
top: 0;
min-width: 200px;
border: var(--border-sm);
background-color: var(--color-white);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
z-index: 10000;
display: none;
transform: translateX(var(--spacing-2));
transition: all 0.2s ease;
}
.dropdown-submenu-content.show {
display: block;
transform: translateX(0);
opacity: 1;
visibility: visible;
}
.dropdown-submenu-item {
display: flex;
align-items: center;
gap: var(--spacing-3);
padding: var(--spacing-3) var(--spacing-6);
color: var(--color-black);
text-decoration: none;
font-size: var(--font-size-sm);
font-weight: 400;
transition: all 0.2s ease;
border: none;
background: none;
width: 100%;
text-align: left;
cursor: pointer;
}
.dropdown-submenu-item:hover {
background-color: var(--color-gray-50);
color: var(--color-black);
}
.dropdown-submenu-item .dropdown-item-icon {
width: 14px;
height: 14px;
}
/* Sidebar Styles */
.sidebar {
position: fixed;
left: 0;
top: 0;
height: 100vh;
width: 280px;
border-right: var(--border-sm);
background-color: var(--color-white);
z-index: 999;
transform: translateX(-100%);
transition: transform 0.3s ease;
overflow-y: auto;
}
.sidebar.show {
transform: translateX(0);
}
.sidebar-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: var(--spacing-6);
border-bottom: var(--border-sm);
}
.sidebar-title {
font-size: var(--font-size-lg);
font-weight: 600;
color: var(--color-black);
margin: 0;
}
.sidebar-close {
background: none;
border: none;
color: var(--color-gray-500);
cursor: pointer;
padding: var(--spacing-2);
border-radius: 0;
transition: color 0.2s ease;
}
.sidebar-close:hover {
color: var(--color-black);
}
.sidebar-nav {
padding: var(--spacing-6);
}
.sidebar-section {
margin-bottom: var(--spacing-8);
}
.sidebar-section-title {
font-size: var(--font-size-xs);
font-weight: 600;
color: var(--color-gray-500);
text-transform: uppercase;
letter-spacing: 0.05em;
margin: 0 0 var(--spacing-4) 0;
}
.sidebar-item {
display: block;
padding: var(--spacing-3) var(--spacing-4);
color: var(--color-black);
text-decoration: none;
font-size: var(--font-size-sm);
font-weight: 400;
transition: all 0.2s ease;
border: none;
background: none;
width: 100%;
text-align: left;
cursor: pointer;
}
.sidebar-item:hover {
background-color: var(--color-gray-50);
color: var(--color-black);
}
.nav-link {
color: var(--color-black);
font-family: var(--font-primary);
font-weight: 500;
text-decoration: none;
transition: var(--transition-fast);
padding: var(--spacing-2) var(--spacing-4);
font-size: var(--font-size-sm);
}
.nav-link:hover {
color: var(--color-gray-700);
background: var(--color-gray-50);
}
/* Swiss Alerts */
.alert {
padding: var(--spacing-4) var(--spacing-6);
border: var(--border-sm);
margin-bottom: var(--spacing-6);
background: var(--color-white);
box-shadow: var(--shadow-sm);
position: relative;
font-size: var(--font-size-sm);
font-weight: 500;
}
.alert::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 4px;
height: 100%;
}
.alert-success {
border-left: 4px solid var(--color-gray-700);
background: var(--color-off-white);
color: var(--color-gray-800);
}
.alert-success::before {
background: var(--color-gray-700);
}
.alert-danger {
border-left: 4px solid var(--color-gray-700);
background: var(--color-off-white);
color: var(--color-gray-800);
}
.alert-danger::before {
background: var(--color-gray-700);
}
.alert-warning {
border-left: 4px solid var(--color-gray-700);
background: var(--color-off-white);
color: var(--color-gray-800);
}
.alert-warning::before {
background: var(--color-gray-700);
}
.alert-info {
border-left: 4px solid var(--color-gray-700);
background: var(--color-off-white);
color: var(--color-gray-800);
}
.alert-info::before {
background: var(--color-gray-700);
}
/* Swiss Layout */
.container {
max-width: var(--max-width-xl);
margin: 0 auto;
padding: 0 var(--container-padding);
}
.row {
display: flex;
flex-wrap: wrap;
margin: 0 calc(-1 * var(--spacing-4));
}
.col {
flex: 1;
padding: 0 var(--spacing-4);
}
.col-6 {
flex: 0 0 50%;
padding: 0 var(--spacing-4);
}
.col-12 {
flex: 0 0 100%;
padding: 0 var(--spacing-4);
}
/* Swiss Spacing */
.mt-1 { margin-top: var(--spacing-1); }
.mt-2 { margin-top: var(--spacing-2); }
.mt-3 { margin-top: var(--spacing-3); }
.mt-4 { margin-top: var(--spacing-4); }
.mt-5 { margin-top: var(--spacing-5); }
.mt-6 { margin-top: var(--spacing-6); }
.mt-8 { margin-top: var(--spacing-8); }
.mt-10 { margin-top: var(--spacing-10); }
.mt-12 { margin-top: var(--spacing-12); }
.mt-16 { margin-top: var(--spacing-16); }
.mt-20 { margin-top: var(--spacing-20); }
.mt-24 { margin-top: var(--spacing-24); }
.mt-32 { margin-top: var(--spacing-32); }
.mb-1 { margin-bottom: var(--spacing-1); }
.mb-2 { margin-bottom: var(--spacing-2); }
.mb-3 { margin-bottom: var(--spacing-3); }
.mb-4 { margin-bottom: var(--spacing-4); }
.mb-5 { margin-bottom: var(--spacing-5); }
.mb-6 { margin-bottom: var(--spacing-6); }
.mb-8 { margin-bottom: var(--spacing-8); }
.mb-10 { margin-bottom: var(--spacing-10); }
.mb-12 { margin-bottom: var(--spacing-12); }
.mb-16 { margin-bottom: var(--spacing-16); }
.mb-20 { margin-bottom: var(--spacing-20); }
.mb-24 { margin-bottom: var(--spacing-24); }
.mb-32 { margin-bottom: var(--spacing-32); }
.pt-1 { padding-top: var(--spacing-1); }
.pt-2 { padding-top: var(--spacing-2); }
.pt-3 { padding-top: var(--spacing-3); }
.pt-4 { padding-top: var(--spacing-4); }
.pt-5 { padding-top: var(--spacing-5); }
.pt-6 { padding-top: var(--spacing-6); }
.pt-8 { padding-top: var(--spacing-8); }
.pt-10 { padding-top: var(--spacing-10); }
.pt-12 { padding-top: var(--spacing-12); }
.pt-16 { padding-top: var(--spacing-16); }
.pt-20 { padding-top: var(--spacing-20); }
.pt-24 { padding-top: var(--spacing-24); }
.pt-32 { padding-top: var(--spacing-32); }
.pb-1 { padding-bottom: var(--spacing-1); }
.pb-2 { padding-bottom: var(--spacing-2); }
.pb-3 { padding-bottom: var(--spacing-3); }
.pb-4 { padding-bottom: var(--spacing-4); }
.pb-5 { padding-bottom: var(--spacing-5); }
.pb-6 { padding-bottom: var(--spacing-6); }
.pb-8 { padding-bottom: var(--spacing-8); }
.pb-10 { padding-bottom: var(--spacing-10); }
.pb-12 { padding-bottom: var(--spacing-12); }
.pb-16 { padding-bottom: var(--spacing-16); }
.pb-20 { padding-bottom: var(--spacing-20); }
.pb-24 { padding-bottom: var(--spacing-24); }
.pb-32 { padding-bottom: var(--spacing-32); }
/* Swiss Text Utilities */
.text-center { text-align: center; }
.text-left { text-align: left; }
.text-right { text-align: right; }
.text-black { color: var(--color-black); }
.text-gray-900 { color: var(--color-gray-900); }
.text-gray-700 { color: var(--color-gray-700); }
.text-gray-500 { color: var(--color-gray-500); }
.text-gray-300 { color: var(--color-gray-300); }
.text-white { color: var(--color-white); }
.font-weight-light { font-weight: 300; }
.font-weight-normal { font-weight: 400; }
.font-weight-medium { font-weight: 500; }
.font-weight-semibold { font-weight: 600; }
.font-weight-bold { font-weight: 700; }
.font-size-xs { font-size: var(--font-size-xs); }
.font-size-sm { font-size: var(--font-size-sm); }
.font-size-base { font-size: var(--font-size-base); }
.font-size-lg { font-size: var(--font-size-lg); }
.font-size-xl { font-size: var(--font-size-xl); }
.font-size-2xl { font-size: var(--font-size-2xl); }
.font-size-3xl { font-size: var(--font-size-3xl); }
.font-size-4xl { font-size: var(--font-size-4xl); }
.font-size-5xl { font-size: var(--font-size-5xl); }
.font-size-6xl { font-size: var(--font-size-6xl); }
/* Swiss Validation */
.field-validation-error {
color: var(--color-gray-700);
font-size: var(--font-size-sm);
margin-top: var(--spacing-1);
display: block;
font-family: var(--font-mono);
font-weight: 500;
}
.input-validation-error {
border-color: var(--color-gray-700) !important;
box-shadow: 0 0 0 1px var(--color-gray-700) !important;
}
.input-validation-error:focus {
outline: none;
border-color: var(--color-gray-800) !important;
box-shadow: 0 0 0 2px var(--color-gray-700) !important;
}
/* Swiss Tables */
.table {
width: 100%;
border-collapse: collapse;
background: var(--color-white);
border: var(--border-sm);
box-shadow: var(--shadow-sm);
}
.table th,
.table td {
padding: var(--spacing-4) var(--spacing-6);
text-align: left;
border-bottom: var(--border-sm);
color: var(--color-black);
font-family: var(--font-primary);
font-size: var(--font-size-sm);
}
.table th {
font-weight: 600;
background: var(--color-off-white);
color: var(--color-black);
letter-spacing: -0.01em;
}
.table tbody tr:hover {
background: var(--color-off-white);
}
/* Swiss Display Utilities */
.d-none { display: none !important; }
.d-block { display: block !important; }
.d-flex { display: flex !important; }
.flex-column { flex-direction: column !important; }
.justify-content-center { justify-content: center !important; }
.justify-content-between { justify-content: space-between !important; }
.align-items-center { align-items: center !important; }
.align-items-start { align-items: flex-start !important; }
.gap-1 { gap: var(--spacing-1) !important; }
.gap-2 { gap: var(--spacing-2) !important; }
.gap-3 { gap: var(--spacing-3) !important; }
.gap-4 { gap: var(--spacing-4) !important; }
.gap-6 { gap: var(--spacing-6) !important; }
.gap-8 { gap: var(--spacing-8) !important; }
.position-relative { position: relative !important; }
.position-absolute { position: absolute !important; }
.w-100 { width: 100% !important; }
.w-auto { width: auto !important; }
.h-100 { height: 100% !important; }
.h-auto { height: auto !important; }
.min-vh-100 { min-height: 100vh !important; }
/* Swiss Minimal Animations */
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes slideUp {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.animate-fadeIn {
animation: fadeIn 0.3s ease-out;
}
.animate-slideUp {
animation: slideUp 0.4s ease-out;
}
/* Swiss Borders */
.border-none { border: none !important; }
.border-sm { border: var(--border-sm) !important; }
.border-md { border: var(--border-md) !important; }
.border-lg { border: var(--border-lg) !important; }
.border-t { border-top: var(--border-sm) !important; }
.border-b { border-bottom: var(--border-sm) !important; }
.border-l { border-left: var(--border-sm) !important; }
.border-r { border-right: var(--border-sm) !important; }
/* Swiss Backgrounds */
.bg-white { background: var(--color-white) !important; }
.bg-black { background: var(--color-black) !important; }
.bg-gray-50 { background: var(--color-gray-50) !important; }
.bg-gray-100 { background: var(--color-gray-100) !important; }
.bg-off-white { background: var(--color-off-white) !important; }
/* Swiss Sizing */
.w-8 { width: 2rem !important; }
.w-12 { width: 3rem !important; }
.w-16 { width: 4rem !important; }
.w-20 { width: 5rem !important; }
.w-24 { width: 6rem !important; }
.w-32 { width: 8rem !important; }
.w-48 { width: 12rem !important; }
.w-64 { width: 16rem !important; }
.h-8 { height: 2rem !important; }
.h-12 { height: 3rem !important; }
.h-16 { height: 4rem !important; }
.h-20 { height: 5rem !important; }
.h-24 { height: 6rem !important; }
.h-32 { height: 8rem !important; }
.h-48 { height: 12rem !important; }
.h-64 { height: 16rem !important; }
.rounded-none { border-radius: 0 !important; }
.rounded-sm { border-radius: 2px !important; }
/* Swiss Responsive Design */
@media (max-width: 768px) {
.navbar-container {
flex-direction: column;
gap: var(--spacing-4);
height: auto;
padding: var(--spacing-4);
}
text.navbar-nav { gap: var(--spacing-4); flex-wrap: wrap; justify-content: center; } .container { padding: 0 var(--spacing-4); } h1 { font-size: var(--font-size-4xl); } h2 { font-size: var(--font-size-3xl); } h3 { font-size: var(--font-size-2xl); } h4 { font-size: var(--font-size-xl); } h5 { font-size: var(--font-size-lg); } .card-body { padding: var(--spacing-8); } .card-header { padding: var(--spacing-6) var(--spacing-8); } .btn { padding: var(--spacing-3) var(--spacing-6); font-size: var(--font-size-sm); } .form-control, .form-select { padding: var(--spacing-3) var(--spacing-3); } .row { margin: 0 calc(-1 * var(--spacing-2)); } .col, .col-6, .col-12 { padding: 0 var(--spacing-2); }
}
@media (max-width: 480px) {
.navbar-brand {
font-size: var(--font-size-base);
}
text.nav-link { font-size: var(--font-size-xs); padding: var(--spacing-1) var(--spacing-2); } .btn { width: 100%; justify-content: center; }
}
/* Swiss Utility Classes */
.text-uppercase { text-transform: uppercase; }
.text-lowercase { text-transform: lowercase; }
.text-capitalize { text-transform: capitalize; }
.text-nowrap { white-space: nowrap; }
.text-break { word-break: break-word; }
.overflow-hidden { overflow: hidden; }
.overflow-auto { overflow: auto; }
.overflow-scroll { overflow: scroll; }
.cursor-pointer { cursor: pointer; }
.cursor-default { cursor: default; }
.transition-none { transition: none !important; }
/* Form Validation Styles */
.field-error {
color: var(--color-gray-700);
font-size: var(--font-size-xs);
margin-top: var(--spacing-1);
display: block;
}
.input-validation-error {
border-color: var(--color-gray-700) !important;
background-color: var(--color-gray-50) !important;
}
/* Alert Styles */
.alert {
padding: var(--spacing-4);
margin-bottom: var(--spacing-4);
border: var(--border-sm);
border-radius: 0;
display: flex;
align-items: center;
gap: var(--spacing-3);
}
.alert-success {
background-color: var(--color-gray-50);
border-color: var(--color-gray-300);
color: var(--color-gray-700);
}
.alert-error {
background-color: var(--color-gray-50);
border-color: var(--color-gray-400);
color: var(--color-gray-800);
}
.alert-icon {
width: 16px;
height: 16px;
flex-shrink: 0;
}
/* Enhanced Button Styles */
.btn-icon {
width: 16px;
height: 16px;
flex-shrink: 0;
}
.btn-warning {
background-color: var(--color-gray-600);
color: var(--color-white);
border: 1px solid var(--color-gray-600);
}
.btn-warning:hover {
background-color: var(--color-gray-700);
border-color: var(--color-gray-700);
}
.btn-success {
background-color: var(--color-gray-800);
color: var(--color-white);
border: 1px solid var(--color-gray-800);
}
.btn-success:hover {
background-color: var(--color-black);
border-color: var(--color-black);
}
.btn-error {
background-color: var(--color-gray-800);
color: var(--color-white);
border: 1px solid var(--color-gray-800);
}
.btn-error:hover {
background-color: var(--color-black);
border-color: var(--color-black);
}
/* Table Styles */
.table {
width: 100%;
border-collapse: collapse;
border: var(--border-sm);
}
.table th,
.table td {
padding: var(--spacing-4);
text-align: left;
border-bottom: var(--border-sm);
}
.table th {
background-color: var(--color-gray-50);
font-weight: 600;
color: var(--color-gray-700);
text-transform: uppercase;
font-size: var(--font-size-xs);
}
.table tbody tr:hover {
background-color: var(--color-gray-50);
}
.table-responsive {
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}
/* Additional Form Styles */
.form-input {
width: 100%;
padding: var(--spacing-3);
border: var(--border-sm);
background-color: var(--color-white);
color: var(--color-black);
font-family: var(--font-primary);
font-size: var(--font-size-base);
transition: all 0.2s ease;
}
.form-input:focus {
outline: 2px solid var(--color-gray-300);
outline-offset: -2px;
border-color: var(--color-gray-800);
}
.form-input::placeholder {
color: var(--color-gray-400);
}
.form-input[readonly] {
background-color: var(--color-gray-100);
cursor: not-allowed;
}
/* Layout System */
.row {
display: flex;
flex-wrap: wrap;
margin: 0 calc(-1 * var(--spacing-4));
}
.col {
flex: 1;
padding: 0 var(--spacing-4);
}
.col-md-8 {
flex: 0 0 66.666667%;
max-width: 66.666667%;
padding: 0 var(--spacing-4);
}
.col-md-4 {
flex: 0 0 33.333333%;
max-width: 33.333333%;
padding: 0 var(--spacing-4);
}
@media (max-width: 768px) {
.col-md-8,
.col-md-4 {
flex: 0 0 100%;
max-width: 100%;
}
}
.transition-fast { transition: var(--transition-fast) !important; }
.transition-normal { transition: var(--transition-normal) !important; }
.shadow-none { box-shadow: none !important; }
.shadow-sm { box-shadow: var(--shadow-sm) !important; }
.shadow-md { box-shadow: var(--shadow-md) !important; }
.shadow-lg { box-shadow: var(--shadow-lg) !important; }
.opacity-50 { opacity: 0.5 !important; }
.opacity-75 { opacity: 0.75 !important; }
.opacity-100 { opacity: 1 !important; }
.z-0 { z-index: 0 !important; }
.z-10 { z-index: 10 !important; }
.z-20 { z-index: 20 !important; }
.z-30 { z-index: 30 !important; }
.z-50 { z-index: 50 !important; }
/* Swiss Loading State */
.loading {
display: inline-block;
width: 16px;
height: 16px;
border: 2px solid var(--color-gray-300);
border-top-color: var(--color-black);
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
/* Swiss Focus Styles */
:focus-visible {
outline: 2px solid var(--color-gray-700);
outline-offset: 2px;
}
/* Swiss Print Styles */
@media print {
body {
background: var(--color-white);
color: var(--color-black);
}
text.navbar, .btn { display: none; } .card { border: var(--border-sm); box-shadow: none; }
}
/* Swiss Link Hover Effects */
.hover-underline {
position: relative;
transition: var(--transition-fast);
}
.hover-underline:hover {
color: var(--color-gray-700);
}
.hover-underline::after {
content: '';
position: absolute;
width: 0;
height: 1px;
bottom: -1px;
left: 0;
background: var(--color-black);
transition: var(--transition-normal);
}
.hover-underline:hover::after {
width: 100%;
}
sweet — you already have the role helpers and most of the styling. Here’s a tight plan and the exact changes to make it work cleanly on both desktop and mobile.
aria-expanded
, ESC closes the drawer, clicking a link in the drawer closes it. Focus isn’t hard-trapped (keeps code light) but the drawer has clear close affordances.Drop this inside .navbar-container
, between the brand and the existing .navbar-nav
:
cshtml<!-- Desktop role-based links (hidden on mobile) --> <div class="navbar-links desktop-only"> @if (User.Identity.IsAuthenticated) { @* Administrator *@ @if (HasRole("Administrator")) { <a class="nav-link" href="@Url.Action("Index","Dashboard")">Dashboard</a> <div class="nav-divider" aria-hidden="true"></div> <div class="nav-group"> <a class="nav-link" href="@Url.Action("Index","UserManagement")">Users</a> <a class="nav-link" href="@Url.Action("CreateUser","UserManagement")">Create</a> </div> } @* Manager or Admin *@ @if (HasAnyRole("Manager","Administrator")) { <a class="nav-link" href="@Url.Action("Index","Reports")">Reports</a> } @* Operations or Admin *@ @if (HasAnyRole("Operations","Administrator")) { <a class="nav-link" href="@Url.Action("Index","Process")">Processes</a> } @* Accounts or Admin *@ @if (HasAnyRole("Accounts","Administrator")) { <a class="nav-link" href="@Url.Action("Index","Finance")">Finance</a> } @* Store or Admin *@ @if (HasAnyRole("Store","Administrator")) { <a class="nav-link" href="@Url.Action("Index","Inventory")">Inventory</a> } } </div>
Then add a hamburger just before the existing user dropdown inside .navbar-nav
:
cshtml<!-- Mobile hamburger (hidden on desktop) --> <button class="nav-toggle mobile-only" type="button" aria-label="Open menu" aria-controls="sidebar" aria-expanded="false" onclick="toggleSidebar(event)"> <svg width="20" height="20" viewBox="0 0 20 20" fill="none" aria-hidden="true"> <path d="M3 6H17M3 10H17M3 14H17" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/> </svg> </button>
The existing sidebar you already have becomes the mobile menu; no duplicate markup needed.
Add below your CSS (keeps your Swiss system intact):
css/* Desktop/Mobile visibility toggles */ .desktop-only { display: flex; align-items: center; gap: var(--spacing-4); } .mobile-only { display: none; } /* tiny divider for Desktop menu grouping */ .nav-divider { width: 1px; height: 20px; background: var(--color-gray-200); margin: 0 var(--spacing-2); } .nav-group { display: inline-flex; gap: var(--spacing-2); } /* Hamburger button */ .nav-toggle { display: inline-flex; align-items: center; justify-content: center; padding: var(--spacing-2) var(--spacing-4); border: var(--border-sm); background: var(--color-white); cursor: pointer; transition: var(--transition-fast); } .nav-toggle:hover { background: var(--color-gray-50); } /* Responsive switch */ @media (max-width: 768px) { .desktop-only { display: none; } .mobile-only { display: inline-flex; } }
(No conflict with your existing queries; .navbar-container
already changes layout under 768px.)
You referenced toggleSidebar()
but didn’t define it. Add this below your existing scripts:
html<script> // Sidebar (mobile menu) toggle + a11y function toggleSidebar(event) { if (event) { event.preventDefault(); event.stopPropagation(); } const sidebar = document.getElementById('sidebar'); const toggler = document.querySelector('.nav-toggle'); if (!sidebar) return; const isOpen = sidebar.classList.contains('show'); if (isOpen) { sidebar.classList.remove('show'); document.body.style.overflow = ''; if (toggler) toggler.setAttribute('aria-expanded', 'false'); } else { sidebar.classList.add('show'); document.body.style.overflow = 'hidden'; // prevent background scroll if (toggler) toggler.setAttribute('aria-expanded', 'true'); // close when clicking outside setTimeout(() => { document.addEventListener('click', closeSidebarOutside, { once: true }); }, 0); } } function closeSidebarOutside(e) { const sidebar = document.getElementById('sidebar'); const toggler = document.querySelector('.nav-toggle'); if (!sidebar) return; const clickInsideSidebar = sidebar.contains(e.target); const clickOnToggler = toggler && toggler.contains(e.target); if (!clickInsideSidebar && !clickOnToggler) { sidebar.classList.remove('show'); document.body.style.overflow = ''; if (toggler) toggler.setAttribute('aria-expanded', 'false'); } } // Close on ESC document.addEventListener('keydown', function (event) { if (event.key === 'Escape') { const sidebar = document.getElementById('sidebar'); if (sidebar && sidebar.classList.contains('show')) { sidebar.classList.remove('show'); document.body.style.overflow = ''; const toggler = document.querySelector('.nav-toggle'); if (toggler) toggler.setAttribute('aria-expanded', 'false'); } } }); // Close the drawer after tapping a link (mobile) document.addEventListener('click', function (e) { const sidebar = document.getElementById('sidebar'); if (!sidebar || !sidebar.classList.contains('show')) return; const link = e.target.closest('a.sidebar-item, a.sidebar-link, .sidebar a'); if (link) { sidebar.classList.remove('show'); document.body.style.overflow = ''; const toggler = document.querySelector('.nav-toggle'); if (toggler) toggler.setAttribute('aria-expanded', 'false'); } }); </script>
If you want a tap-to-dismiss overlay on mobile, add:
css/* Overlay shown when sidebar is open (mobile) */ .sidebar-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.1); opacity: 0; pointer-events: none; transition: var(--transition-normal); z-index: 998; } .sidebar.show + .sidebar-overlay { opacity: 1; pointer-events: auto; } @media (min-width: 769px) { .sidebar-overlay { display: none; } }
And right after <aside id="sidebar" ...></aside>
place:
html<div class="sidebar-overlay" onclick="toggleSidebar(event)"></div>
HasRole/HasAnyRole
helpers; links are canonical MVC routes.If you want, I can also convert the desktop links into a compact “More ▾” megamenu for Admins with many items—but the above keeps it minimal and Swiss-clean.