diff --git a/assets/css/hvac-consolidated.css b/assets/css/hvac-consolidated.css new file mode 100644 index 00000000..7f34a681 --- /dev/null +++ b/assets/css/hvac-consolidated.css @@ -0,0 +1,3813 @@ +/* HVAC Consolidated CSS - Generated on 2025-08-06 20:14:39 */ + +/* === hvac-community-events.css === */ +/** + * HVAC Community Events Main Styles + * + * This file imports and includes all the main styles for the plugin + */ + +/* Import base styles - Already loaded separately but included for completeness */ +@import url('hvac-common.css'); +@import url('hvac-harmonized.css'); + +/* General layout styles */ +.hvac-content { + max-width: 1200px; + margin: 0 auto; + padding: 20px; +} + +/* Page specific imports */ +@import url('hvac-dashboard.css'); +@import url('hvac-registration.css'); +@import url('hvac-event-manage.css'); +@import url('hvac-event-summary.css'); +@import url('hvac-certificates.css'); +@import url('hvac-email-attendees.css'); +@import url('hvac-attendee-profile.css'); +@import url('community-login.css'); +@import url('communication-templates.css'); + +/* Ensure proper spacing on all HVAC pages */ +.hvac-page .site-content { + padding-top: 2rem; + padding-bottom: 2rem; +} + +/* Fix for page title hiding */ +.hvac-page .entry-header { + display: none !important; +} + +/* === hvac-page-templates.css === */ +/** + * HVAC Page Templates - Global Styles + * Ensures consistent layout for all HVAC plugin pages + */ + +/* Hide sidebars on all HVAC pages */ +.hvac-page .widget-area, +.hvac-page .sidebar, +.hvac-page #secondary, +.hvac-page aside.widget-area, +.hvac-community-events .widget-area, +.hvac-community-events .sidebar, +.hvac-community-events #secondary, +.hvac-community-events aside.widget-area { + display: none !important; +} + +/* Full width layout for HVAC pages */ +.hvac-page #primary, +.hvac-page .content-area, +.hvac-page .site-main, +.hvac-page main, +.hvac-community-events #primary, +.hvac-community-events .content-area, +.hvac-community-events .site-main, +.hvac-community-events main { + max-width: 100% !important; + width: 100% !important; + margin: 0 auto; +} + +/* Ensure content wrapper spans full width */ +.hvac-page-wrapper { + width: 100%; + max-width: 1200px; + margin: 0 auto; + padding: 40px 20px; +} + +/* Container styles for consistent spacing */ +.hvac-page-wrapper .container { + max-width: 1200px; + margin: 0 auto; + padding: 0 20px; +} + +/* Remove default page title since we handle it in our templates */ +.hvac-page .entry-title, +.hvac-community-events .entry-title { + display: none !important; +} + +/* Responsive adjustments */ +@media (max-width: 768px) { + .hvac-page-wrapper { + padding: 20px 15px; + } + + .hvac-page-wrapper .container { + padding: 0 15px; + } +} + +/* === hvac-layout.css === */ +/** + * HVAC Layout Styles + * + * Ensures all HVAC pages follow the theme's layout settings + * - Full width container with max width of 1200px + * - 20px padding on all sides + */ + +/* Container Layout for All HVAC Pages */ +.hvac-plugin-page .site-content .ast-container, +.hvac-plugin-page .ast-container { + max-width: 1200px !important; + width: 100% !important; + margin: 0 auto !important; + padding: 20px !important; + box-sizing: border-box !important; +} + +/* Ensure content area also follows max width */ +.hvac-plugin-page .content-area { + max-width: 100% !important; + padding: 0 !important; +} + +/* Force full-width layout on HVAC pages - Astra specific */ +body.ast-no-sidebar.hvac-page .site-content, +body.ast-no-sidebar.hvac-trainer-page .site-content, +body.ast-no-sidebar.hvac-plugin-active .site-content { + /* Let Astra handle the layout */ +} + +/* Ensure containers use proper width on HVAC pages */ +.hvac-page-wrapper .container { + max-width: 1200px; + width: 100%; + margin: 0 auto; + padding: 0 20px; +} + +/* Override Astra's container constraints on HVAC pages */ +body.hvac-page .ast-container, +body.hvac-trainer-page .ast-container, +body.hvac-plugin-active .ast-container, +body.ast-no-sidebar.hvac-page .ast-container, +body.ast-no-sidebar.hvac-trainer-page .ast-container { + max-width: 100% !important; + width: 100% !important; + margin-left: auto !important; + margin-right: auto !important; + padding-left: 40px !important; + padding-right: 40px !important; +} + +/* Force full-width for all content containers */ +body.hvac-page #content > .ast-container, +body.hvac-trainer-page #content > .ast-container, +body.hvac-plugin-active #content > .ast-container, +body.hvac-page .site-content > .ast-container, +body.hvac-trainer-page .site-content > .ast-container { + max-width: 100% !important; + width: 100% !important; +} + +/* Override any inline styles from Astra */ +body.hvac-page .ast-container[style*="max-width"], +body.hvac-trainer-page .ast-container[style*="max-width"] { + max-width: 100% !important; +} + +/* Inner content wrapper - wider for better use of space */ +body.hvac-page .hvac-page-wrapper, +body.hvac-trainer-page .hvac-page-wrapper, +body.hvac-plugin-active .hvac-page-wrapper { + max-width: 1600px; + margin: 0 auto; + padding: 0 20px; +} + +/* Specific container overrides */ +body.hvac-page .hvac-page-wrapper .container, +body.hvac-trainer-page .hvac-page-wrapper .container { + max-width: 100%; + width: 100%; + padding: 0; +} + +/* Dashboard specific full-width */ +body.hvac-trainer-dashboard .hvac-dashboard-wrapper { + max-width: 100%; + padding: 20px 40px; +} + +/* Certificate pages full-width */ +body.hvac-certificate-reports .hvac-certificate-reports-content, +body.page-template-page-generate-certificates .hvac-generate-certificates-content { + max-width: 100%; + padding: 20px 40px; +} + +/* Ensure full width layout */ +.hvac-plugin-page.ast-separate-container .ast-container { + background-color: #fff; + border-radius: 0; +} + +/* Override theme's narrow content */ +.hvac-plugin-page .entry-content { + max-width: none !important; + margin: 0 !important; + padding: 0 !important; +} + +/* Dashboard specific adjustments */ +.hvac-dashboard { + background-color: #f9f9f9; + padding: 20px; + border-radius: 8px; + margin: 0; +} + +/* Event manage page specific */ +.tribe-community-events { + background: #fff; + padding: 20px; + border-radius: 8px; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); +} + +/* Certificate pages */ +.hvac-certificate-wrapper { + background: #fff; + padding: 20px; + border-radius: 8px; + margin: 0; +} + +/* Form containers */ +.hvac-form-container { + background: #fff; + padding: 20px; + border-radius: 8px; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); +} + +/* Navigation menus */ +.hvac-event-navigation, +.hvac-dashboard-nav { + margin-bottom: 20px; +} + +/* Responsive adjustments */ +@media (max-width: 768px) { + .hvac-plugin-page .site-content, + .hvac-plugin-page .ast-container, + .hvac-plugin-page .content-area { + padding: 15px !important; + } + + .hvac-dashboard, + .tribe-community-events, + .hvac-certificate-wrapper, + .hvac-form-container { + padding: 15px; + } +} + +/* Print styles */ +@media print { + .hvac-plugin-page .site-content, + .hvac-plugin-page .ast-container, + .hvac-plugin-page .content-area { + max-width: 100% !important; + padding: 0 !important; + } +} + +/* Ensure Event Calendar Community forms have proper styling */ +.tribe-community-events .tribe-events-community-details, +.tribe-community-events .event-dynamic-helper-text, +.tribe-community-events .tribe-section { + margin-bottom: 20px; +} + +.tribe-community-events h3 { + margin-top: 30px; + margin-bottom: 15px; + padding-bottom: 10px; + border-bottom: 2px solid #e5e7eb; + font-size: 1.3em; + color: #333; +} + +.tribe-community-events label { + display: block; + margin-bottom: 5px; + font-weight: 600; + color: #333; +} + +.tribe-community-events input[type="text"], +.tribe-community-events input[type="email"], +.tribe-community-events input[type="url"], +.tribe-community-events input[type="tel"], +.tribe-community-events input[type="number"], +.tribe-community-events input[type="date"], +.tribe-community-events input[type="time"], +.tribe-community-events select, +.tribe-community-events textarea { + width: 100%; + padding: 10px; + border: 1px solid #ddd; + border-radius: 4px; + font-size: 14px; + background: #fff; + transition: border-color 0.2s; +} + +.tribe-community-events input[type="text"]:focus, +.tribe-community-events input[type="email"]:focus, +.tribe-community-events input[type="url"]:focus, +.tribe-community-events input[type="tel"]:focus, +.tribe-community-events input[type="number"]:focus, +.tribe-community-events input[type="date"]:focus, +.tribe-community-events input[type="time"]:focus, +.tribe-community-events select:focus, +.tribe-community-events textarea:focus { + outline: none; + border-color: #0073aa; + box-shadow: 0 0 0 3px rgba(0, 115, 170, 0.1); +} + +.tribe-community-events .tribe-button, +.tribe-community-events input[type="submit"] { + background: #0073aa; + color: white; + padding: 10px 20px; + border: none; + border-radius: 4px; + font-size: 16px; + font-weight: 500; + cursor: pointer; + transition: background 0.2s; +} + +.tribe-community-events .tribe-button:hover, +.tribe-community-events input[type="submit"]:hover { + background: #005a87; +} + +/* Event form specific styling */ +.tribe-community-events .event-dynamic-helper-text { + background: #f8f9fa; + padding: 15px; + border-radius: 4px; + border-left: 4px solid #0073aa; + margin-bottom: 20px; +} + +.tribe-community-events .tribe-section-content { + padding: 15px 0; +} + +/* Required field indicators */ +.tribe-community-events .req { + color: #d63638; + font-weight: bold; +} + +/* Error messages */ +.tribe-community-events .error, +.tribe-community-events .tribe-error { + color: #d63638; + font-size: 14px; + margin-top: 5px; +} + +/* Success messages */ +.tribe-community-events .updated, +.tribe-community-events .tribe-success { + background: #d4edda; + border: 1px solid #c3e6cb; + color: #155724; + padding: 10px 15px; + border-radius: 4px; + margin: 20px 0; +} + +/* === hvac-common.css === */ +/* Reduced Motion Support Added - 2025-07-23 */ +/* Vendor Prefixes Added - 2025-07-23 */ +/** + * HVAC Community Events Common Styles + * + * These styles apply to all pages in the HVAC Community Events plugin. + * They enhance buttons, links, typography, and spacing for a consistent look. + */ + + +:root { + /* Primary colors */ + --hvac-primary: #0274be; + --hvac-primary-dark: #005fa3; + --hvac-primary-light: #e6f3fb; + /* Secondary colors */ + --hvac-secondary: #54595f; + --hvac-secondary-dark: #3a3f44; + --hvac-secondary-light: #f0f0f1; + /* Success/Error colors */ + --hvac-success: #4caf50; + --hvac-success-light: #e8f5e9; + --hvac-error: #d63638; + --hvac-error-light: #ffebe9; + /* Neutral colors */ + --hvac-border: #e0e0e0; + --hvac-border-light: #f0f0f0; + --hvac-text: #333333; + --hvac-text-light: #757575; + /* Spacing */ + --hvac-spacing-xs: 0.25rem; /* 4px */ + --hvac-spacing-sm: 0.5rem; /* 8px */ + --hvac-spacing-md: 1rem; /* 16px */ + --hvac-spacing-lg: 1.5rem; /* 24px */ + --hvac-spacing-xl: 2rem; /* 32px */ + /* Border radius */ + --hvac-border-radius: 4px; + --hvac-border-radius-lg: 8px; + /* Box shadow */ + --hvac-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + --hvac-shadow-lg: 0 4px 8px rgba(0, 0, 0, 0.1); + /* Focus styles */ + --hvac-focus-color: #2271b1; + --hvac-focus-width: 2px; + --hvac-focus-offset: 2px; +} + +/* Hide page titles on HVAC pages */ +.hvac-page .entry-title, +.hvac-page .page-title, +.hvac-page h1.entry-title, +.hvac-page h1.page-title, +.hvac-no-title .entry-title, +.hvac-no-title .page-title, +.entry-title-hidden .entry-title, +.entry-title-hidden .page-title { + display: none !important; + visibility: hidden !important; + height: 0 !important; + margin: 0 !important; + padding: 0 !important; +} + +/* Also hide common theme title wrappers */ +.hvac-page .page-header, +.hvac-page .entry-header .entry-title, +.hvac-page header.entry-header h1, +.hvac-page .title-area h1, +.hvac-page .ast-single-post-title, +.hvac-page .ast-page-title { + display: none !important; +} + +/* Typography Enhancements */ +.hvac-content h1, +.hvac-content h2, +.hvac-content h3, +.hvac-content h4 { + color: #333333; /* IE fallback */ + color: var(--hvac-text); + font-weight: 600; + margin-bottom: 1rem; /* IE fallback */ + margin-bottom: var(--hvac-spacing-md); + line-height: 1.3; +.hvac-content h1 { + font-size: 1.75rem; + margin-bottom: 1.5rem; /* IE fallback */ + margin-bottom: var(--hvac-spacing-lg); +.hvac-content h2 { + font-size: 1.5rem; + margin-top: 2rem; /* IE fallback */ + margin-top: var(--hvac-spacing-xl); +.hvac-content h3 { + font-size: 1.25rem; + margin-top: 1.5rem; /* IE fallback */ + margin-top: var(--hvac-spacing-lg); +.hvac-content p { + margin-bottom: 1rem; /* IE fallback */ + margin-bottom: var(--hvac-spacing-md); + line-height: 1.6; + color: #333333; /* IE fallback */ + color: var(--hvac-text); +/* Enhanced Button Styles */ +.hvac-button, +.hvac-content .button, +.hvac-content button[type="submit"], +.hvac-content input[type="submit"], +.hvac-content .ast-button { + display: inline-block; + background-color: #0274be; /* IE fallback */ + background-color: var(--hvac-primary); + color: white !important; + padding: 0.75rem 1.25rem; + border: none; + border-radius: 4px; /* IE fallback */ + -webkit-border-radius: var(--hvac-border-radius); + border-radius: var(--hvac-border-radius); + font-size: 1rem; + font-weight: 600; + text-decoration: none; + text-align: center; + cursor: pointer; + -webkit-transition: background-color 0.2s, transform 0.1s, box-shadow 0.2s; + -webkit-box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); /* IE fallback */ + -webkit-box-shadow: var(--hvac-shadow); + box-shadow: var(--hvac-shadow); + line-height: 1.4; + min-height: 44px; /* Minimum touch target size */ + position: relative; + overflow: hidden; +.hvac-button:hover, +.hvac-content .button:hover, +.hvac-content button[type="submit"]:hover, +.hvac-content input[type="submit"]:hover, +.hvac-content .ast-button:hover { + background-color: #005fa3; /* IE fallback */ + background-color: var(--hvac-primary-dark); + text-decoration: none; + -webkit-transform: translateY(-1px); + -ms-transform: translateY(-1px); + -webkit-box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); /* IE fallback */ + -webkit-box-shadow: var(--hvac-shadow-lg); +.hvac-button:active, +.hvac-content .button:active, +.hvac-content button[type="submit"]:active, +.hvac-content input[type="submit"]:active, +.hvac-content .ast-button:active { + -webkit-transform: translateY(0); + -ms-transform: translateY(0); + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); /* IE fallback */ + box-shadow: var(--hvac-shadow); +/* Button ripple effect */ +.hvac-button::after, +.hvac-content .button::after, +.hvac-content button[type="submit"]::after, +.hvac-content input[type="submit"]::after, +.hvac-content .ast-button::after { + content: ""; + position: absolute; + top: 50%; + left: 50%; + width: 5px; + height: 5px; + background: rgba(255, 255, 255, 0.5); + opacity: 0; + -webkit-border-radius: 100%; + -webkit-transform: scale(1, 1) translate(-50%, -50%); + -ms-transform: scale(1, 1) translate(-50%, -50%); + transform-origin: 50% 50%; +.hvac-button:active::after, +.hvac-content .button:active::after, +.hvac-content button[type="submit"]:active::after, +.hvac-content input[type="submit"]:active::after, +.hvac-content .ast-button:active::after { + -webkit-animation: ripple 0.4s ease-out; +/* Secondary buttons */ +.hvac-button-secondary, +.hvac-content .button-secondary { + background-color: #54595f; /* IE fallback */ + background-color: var(--hvac-secondary); +.hvac-button-secondary:hover, +.hvac-content .button-secondary:hover { + background-color: #3a3f44; /* IE fallback */ + background-color: var(--hvac-secondary-dark); +/* Success/danger button variants */ +.hvac-button-success, +.hvac-content .button-success { + background-color: #4caf50; /* IE fallback */ + background-color: var(--hvac-success); +.hvac-button-success:hover, +.hvac-content .button-success:hover { + background-color: #4caf50; /* IE fallback */ + background-color: var(--hvac-success); + filter: brightness(0.9); +.hvac-button-danger, +.hvac-content .button-danger { + background-color: #d63638; /* IE fallback */ + background-color: var(--hvac-error); +.hvac-button-danger:hover, +.hvac-content .button-danger:hover { + background-color: #d63638; /* IE fallback */ + background-color: var(--hvac-error); + filter: brightness(0.9); +/* Outline button variant */ +.hvac-button-outline, +.hvac-content .button-outline { + background-color: transparent; + color: #0274be; /* IE fallback */ + color: var(--hvac-primary) !important; + border: 2px solid #0274be; /* IE fallback */ + border: 2px solid var(--hvac-primary); + -webkit-box-shadow: none; + box-shadow: none; +.hvac-button-outline:hover, +.hvac-content .button-outline:hover { + background-color: #e6f3fb; /* IE fallback */ + background-color: var(--hvac-primary-light); + color: #0274be; /* IE fallback */ + color: var(--hvac-primary) !important; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); /* IE fallback */ + box-shadow: var(--hvac-shadow); +/* Mobile optimized buttons */ +/* Reduced Motion Support Added - WCAG 2.1 Accessibility */ +/* Respects user preference for reduced motion to prevent vestibular disorders */ +@media (prefers-reduced-motion: reduce) { + /* Disable all animations and transitions globally */ + *, *::before, *::after { + animation-duration: 0.001ms !important; + animation-delay: 0s !important; + animation-iteration-count: 1 !important; + transition-duration: 0.001ms !important; + transition-delay: 0s !important; + scroll-behavior: auto !important; + /* Remove specific transform animations */ + .hvac-animate-fade-in, + .hvac-animate-scale-up, + .hvac-animate-pulse, + .hvac-animate-slide-in-right, + .hvac-animate-slide-in-left, + .hvac-animate-slide-in-bottom { + animation: none !important; + opacity: 1 !important; + transform: none !important; + /* Disable hover transformations */ + .hvac-card:hover, + .hvac-stat-card:hover, + .hvac-event-stat-card:hover, + .hvac-button:hover, + .hvac-email-submit:hover { + transform: none !important; + animation: none !important; + /* Keep essential visual feedback but remove motion */ + .hvac-card:hover, + .hvac-stat-card:hover, + .hvac-event-stat-card:hover { + border-color: var(--hvac-primary, #0274be) !important; + box-shadow: 0 0 0 2px rgba(2, 116, 190, 0.2) !important; + /* Disable loading spinner animation but keep visibility */ + .hvac-loading::after { + animation: none !important; + border-radius: 50% !important; + border: 2px solid rgba(0, 0, 0, 0.2) !important; + border-top-color: #333 !important; + /* Disable focus pulse animation */ + .hvac-button:focus, +.hvac-email-submit:focus, + .hvac-content button[type="submit"]:focus { + animation: none !important; + /* Ensure smooth scrolling is disabled */ + html { + scroll-behavior: auto !important; + /* Disable CSS Grid/Flexbox animations if any */ + .hvac-dashboard-stats .hvac-stat-card:nth-child(n), + .hvac-event-summary-stats .hvac-event-stat-card: nth-child(n) { + animation: none !important; + opacity: 1 !important; +/* Provide alternative visual feedback for reduced motion users */ +@media (prefers-reduced-motion: reduce) { + /* Enhanced border feedback instead of transform */ + .hvac-content button:hover, + .hvac-content input[type="submit"]:hover, + .hvac-content a:hover { + outline: 2px solid var(--hvac-primary, #0274be) !important; + outline-offset: 2px !important; + /* Enhanced color changes for interactive elements */ + .hvac-attendee-item:hover { + background-color: var(--hvac-primary-light, #e6f3fb) !important; + border-left: 4px solid var(--hvac-primary, #0274be) !important; + /* Static loading indicator */ + .hvac-loading { + opacity: 0.7 !important; +.hvac-loading::after { + content: "Loading..." !important; + display: inline-block !important; + font-size: 12px !important; + color: #666 !important; + border: none !important; + background: none !important; + border-radius: 0 !important; + width: auto !important; + height: auto !important; + position: static !important; + margin-left: 8px !important; +@media (max-width: 480px) { + .hvac-button, + .hvac-content .button, + .hvac-content button[type="submit"], + .hvac-content input[type="submit"], + .hvac-content .ast-button { + padding: 0.85rem 1rem; + width: 100%; + max-width: 100%; + display: block; + font-size: 1rem; + margin-bottom: 0.5rem; + /* IE fallback */ + margin-bottom: 0.5rem; + /* IE fallback */ + margin-bottom: var(--hvac-spacing-sm); + /* Fix for buttons in a row on mobile */ + .hvac-button-group { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + gap: 0.5rem; /* IE fallback */ + gap: var(--hvac-spacing-sm); + width: 100%; +@keyframes ripple { + 0% { + -webkit-transform: scale(0, 0); + -ms-transform: scale(0, 0); + opacity: 0.7; + 20% { + -webkit-transform: scale(25, 25); + -ms-transform: scale(25, 25); + opacity: 0.5; + 100% { + opacity: 0; + -webkit-transform: scale(40, 40); + -ms-transform: scale(40, 40); +/* Enhanced Link Styles */ +.hvac-content a:not(.button):not(.hvac-button) { + color: #0274be; /* IE fallback */ + color: var(--hvac-primary); + text-decoration: none; + font-weight: 500; + -webkit-transition: color 0.2s; +.hvac-content a:not(.button):not(.hvac-button):hover { + color: #005fa3; /* IE fallback */ + color: var(--hvac-primary-dark); + text-decoration: underline; +/* Responsive Table Improvements */ +.hvac-table-wrapper { + overflow-x: auto; + margin-bottom: 1.5rem; /* IE fallback */ + margin-bottom: var(--hvac-spacing-lg); +.hvac-table { + width: 100%; + border-collapse: collapse; + border: 1px solid #e0e0e0; /* IE fallback */ + border: 1px solid var(--hvac-border); + background-color: white; +.hvac-table th { + background-color: #f0f0f1; /* IE fallback */ + background-color: var(--hvac-secondary-light); + color: #3a3f44; /* IE fallback */ + color: var(--hvac-secondary-dark); + font-weight: 600; + text-align: left; + padding: 1rem; /* IE fallback */ + padding: var(--hvac-spacing-md); + border-bottom: 2px solid #e0e0e0; /* IE fallback */ + border-bottom: 2px solid var(--hvac-border); +.hvac-table td { + padding: 1rem; /* IE fallback */ + padding: var(--hvac-spacing-md); + border-bottom: 1px solid #f0f0f0; /* IE fallback */ + border-bottom: 1px solid var(--hvac-border-light); + vertical-align: middle; +.hvac-table tbody tr:hover { + background-color: #e6f3fb; /* IE fallback */ + background-color: var(--hvac-primary-light); +/* Card Component Styles */ +.hvac-card { + background-color: white; + border-radius: 4px; /* IE fallback */ + border-radius: var(--hvac-border-radius); + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); /* IE fallback */ + box-shadow: var(--hvac-shadow); + padding: 1.5rem; /* IE fallback */ + padding: var(--hvac-spacing-lg); + margin-bottom: 1.5rem; /* IE fallback */ + margin-bottom: var(--hvac-spacing-lg); + border: 1px solid #e0e0e0; /* IE fallback */ + border: 1px solid var(--hvac-border); +.hvac-card-title { + margin-top: 0; + margin-bottom: 1rem; /* IE fallback */ + margin-bottom: var(--hvac-spacing-md); + padding-bottom: 0.5rem; /* IE fallback */ + padding-bottom: var(--hvac-spacing-sm); + border-bottom: 1px solid #f0f0f0; /* IE fallback */ + border-bottom: 1px solid var(--hvac-border-light); +/* Form Element Improvements */ +.hvac-form-group { + margin-bottom: 1.5rem; /* IE fallback */ + margin-bottom: var(--hvac-spacing-lg); +.hvac-form-label { + display: block; + margin-bottom: 0.5rem; /* IE fallback */ + margin-bottom: var(--hvac-spacing-sm); + font-weight: 600; + color: #333333; /* IE fallback */ + color: var(--hvac-text); +.hvac-form-input, +.hvac-content input[type="text"], +.hvac-content input[type="email"], +.hvac-content input[type="password"], +.hvac-content input[type="url"], +.hvac-content textarea, +.hvac-content select { + width: 100%; + padding: 0.75rem; + border: 1px solid #e0e0e0; /* IE fallback */ + border: 1px solid var(--hvac-border); + border-radius: 4px; /* IE fallback */ + border-radius: var(--hvac-border-radius); + font-size: 1rem; + background-color: white; + -webkit-transition: border-color 0.2s, box-shadow 0.2s; +.hvac-form-input:focus, +.hvac-content input[type="text"]:focus, +.hvac-content input[type="email"]:focus, +.hvac-content input[type="password"]:focus, +.hvac-content input[type="url"]:focus, +.hvac-content textarea:focus, +.hvac-content select:focus { + border-color: #0274be; /* IE fallback */ + border-color: var(--hvac-primary); + outline: none; + -webkit-box-shadow: 0 0 0 3px #e6f3fb;/* IE fallback */ + -webkit-box-shadow: 0 0 0 3px var(--hvac-primary-light); +/* Alert/Message Styles */ +.hvac-alert { + padding: 1rem; /* IE fallback */ + padding: var(--hvac-spacing-md); + border-radius: 4px; /* IE fallback */ + border-radius: var(--hvac-border-radius); + margin-bottom: 1.5rem; /* IE fallback */ + margin-bottom: var(--hvac-spacing-lg); + border-left: 4px solid transparent; +.hvac-alert-success { + background-color: #e8f5e9; /* IE fallback */ + background-color: var(--hvac-success-light); + border-color: #4caf50; /* IE fallback */ + border-color: var(--hvac-success); + color: #4caf50; /* IE fallback */ + color: var(--hvac-success); +.hvac-alert-error { + background-color: #ffebe9; /* IE fallback */ + background-color: var(--hvac-error-light); + border-color: #d63638; /* IE fallback */ + border-color: var(--hvac-error); + color: #d63638; /* IE fallback */ + color: var(--hvac-error); +/* Layout Helper Classes */ +.hvac-flex { + display: -webkit-box; + display: -ms-flexbox; + display: flex; +.hvac-flex-between { + -webkit-box-pack: justify; + -ms-flex-pack: justify; + justify-content: space-between; +.hvac-flex-center { + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; +.hvac-flex-wrap { + -ms-flex-wrap: wrap; +.hvac-mt-sm { + margin-top: 0.5rem; /* IE fallback */ + margin-top: var(--hvac-spacing-sm); +} + +.hvac-mt-md { + + margin-top: 1rem; /* IE fallback */ + + margin-top: var(--hvac-spacing-md); +} + +.hvac-mb-sm { + + margin-bottom: 0.5rem; /* IE fallback */ + + margin-bottom: var(--hvac-spacing-sm); +} + +.hvac-mb-md { + + margin-bottom: 1rem; /* IE fallback */ + + margin-bottom: var(--hvac-spacing-md); +} + +.hvac-mb-lg { + + margin-bottom: 1.5rem; /* IE fallback */ + + margin-bottom: var(--hvac-spacing-lg); +} + +.hvac-mr-sm { + + margin-right: 0.5rem; /* IE fallback */ + + margin-right: var(--hvac-spacing-sm); +} + +/* Accessibility Focus Styles */ +.hvac-button:focus, +.hvac-content .button:focus, +.hvac-content button:focus, +.hvac-content input[type="submit"]:focus, +.hvac-content input[type="button"]:focus, +.hvac-content .ast-button:focus { + outline: 2px; /* IE fallback */ + + outline: var(--hvac-focus-width) solid var(--hvac-focus-color); + + outline-offset: 2px; /* IE fallback */ + + outline-offset: var(--hvac-focus-offset); + + box-shadow: none; + + position: relative; + + z-index: 1; + +.hvac-content a:focus, +.hvac-content [tabindex="0"]:focus { + outline: 2px; /* IE fallback */ + + outline: var(--hvac-focus-width) solid var(--hvac-focus-color); + + outline-offset: 2px; /* IE fallback */ + + outline-offset: var(--hvac-focus-offset); + + -webkit-border-radius: 2px; + +.hvac-content input[type="text"]:focus, +.hvac-content input[type="email"]:focus, +.hvac-content input[type="password"]:focus, +.hvac-content input[type="url"]:focus, +.hvac-content textarea:focus, +.hvac-content select:focus { + outline: none; /* Remove default outline */ + + border-color: #2271b1; /* IE fallback */ + + border-color: var(--hvac-focus-color); + + -webkit-box-shadow: 0 0 0 2px rgba(34, 113, 177, 0.3);/* IE fallback */ + + -webkit-box-shadow: 0 0 0 var(--hvac-focus-width) rgba(34, 113, 177, 0.3); + +/* High contrast focus style for keyboard navigation */ +.keyboard-nav-active:focus { + + outline: 3px solid #ffbf47 !important; + + outline-offset: 2px; /* IE fallback */ + + outline-offset: var(--hvac-focus-offset) !important; + +/* Skip link for keyboard users */ +.hvac-skip-link { + + position: absolute; + + top: -40px; + + left: 0; + + z-index: 100; + + background: #0274be; /* IE fallback */ + + background: var(--hvac-primary); + + color: white; + + padding: 10px; + + -webkit-transition: top 0.2s; + +.hvac-skip-link:focus { + top: 0; + + outline: 2px; /* IE fallback */ + + outline: var(--hvac-focus-width) solid var(--hvac-focus-color); + +/* Responsive improvements */ +@media (max-width: 767px) { + .hvac-flex { + -webkit-box-orient: vertical; + + -webkit-box-direction: normal; + + -ms-flex-direction: column; + +.hvac-form-group { + + margin-bottom: 1rem; /* IE fallback */ + + margin-bottom: var(--hvac-spacing-md); + +.hvac-card { + + padding: 1rem; /* IE fallback */ + + padding: var(--hvac-spacing-md); + +/* Apply to all plugin pages */ +body.page-hvac-dashboard, +body.page-community-login, +body.page-trainer-registration, +body.page-trainer-profile, +body.page-event-summary, +body.page-email-attendees, +body.page-manage-event { + /* Base font settings */ + + font-size: 16px; + + line-height: 1.6; + + color: #333333; /* IE fallback */ + + color: var(--hvac-text); + +/* Enable detection of keyboard navigation for better accessibility */ + + body: not(.user-is-tabbing) :focus { + outline: none !important; + +/* Event Management Page Header Styles */ +.hvac-event-manage-header { + + background: #e6f3fb; /* IE fallback */ + + background: var(--hvac-primary-light); + + border: 1px solid #f0f0f0; /* IE fallback */ + + border: 1px solid var(--hvac-border-light); + + -webkit-border-radius: 8px; /* IE fallback */ + + -webkit-border-radius: var(--hvac-radius-md); + + padding: 1.5rem; /* IE fallback */ + + padding: var(--hvac-spacing-lg); + + margin-bottom: 1.5rem; /* IE fallback */ + + margin-bottom: var(--hvac-spacing-lg); + +.hvac-event-manage-header h2 { + + color: #0274be; /* IE fallback */ + + color: var(--hvac-primary); + + margin: 0 0 1rem 0; /* IE fallback */ + + margin: 0 0 var(--hvac-spacing-md) 0; + + font-size: 1.5em; + + font-weight: 600; + +.hvac-event-manage-header p { + + margin: 0 0 1rem 0; /* IE fallback */ + + margin: 0 0 var(--hvac-spacing-md) 0; + + font-size: 1.1em; + + line-height: 1.6; + + color: #333333; /* IE fallback */ + + color: var(--hvac-text); + +.hvac-event-manage-tips { + + background: white; + + padding: 1rem; /* IE fallback */ + + padding: var(--hvac-spacing-md); + + border-radius: 4px; /* IE fallback */ + + -webkit-border-radius: var(--hvac-radius-sm); + + border-left: 4px solid #0274be; /* IE fallback */ + + border-left: 4px solid var(--hvac-primary); + + margin-top: 1rem; /* IE fallback */ + + margin-top: var(--hvac-spacing-md); + +.hvac-event-manage-tips h3 { + + color: #0274be; /* IE fallback */ + + color: var(--hvac-primary); + + margin: 0 0 0.5rem 0; /* IE fallback */ + + margin: 0 0 var(--hvac-spacing-sm) 0; + + font-size: 1.2em; + + font-weight: 600; + +.hvac-event-manage-tips ul { + + margin: 0; + + padding-left: 1rem; /* IE fallback */ + + padding-left: var(--hvac-spacing-md); + +.hvac-event-manage-tips li { + + margin-bottom: 0.25rem; /* IE fallback */ + + margin-bottom: var(--hvac-spacing-xs); + + line-height: 1.5; + +.hvac-event-manage-tips strong { + + color: #005fa3; /* IE fallback */ + + color: var(--hvac-primary-dark); + +/* Responsive adjustments for event management header */ +@media (max-width: 767px) { + .hvac-event-manage-header { + padding: 1rem; + /* IE fallback */ + + padding: 1rem; + /* IE fallback */ + + padding: var(--hvac-spacing-md); + + margin-bottom: 1rem; + /* IE fallback */ + + margin-bottom: 1rem; + /* IE fallback */ + + margin-bottom: var(--hvac-spacing-md); + +.hvac-event-manage-header h2 { + + font-size: 1.3em; + +.hvac-event-manage-tips { + + padding: 0.5rem; /* IE fallback */ + + padding: var(--hvac-spacing-sm); + +/* Focus Management Styles - WCAG 2.1 Compliance */ +/* Added for keyboard accessibility and screen reader support */ + +/* Button Focus Styles */ +.hvac-button:focus, +.hvac-content .button:focus, +.hvac-content button:focus, +.hvac-content input[type="submit"]:focus, +.hvac-email-submit:focus, +.hvac-filter-submit:focus, +.hvac-certificate-actions button:focus, +.hvac-certificate-actions a:focus { + outline: 2px solid #005fcc; + + outline-offset: 2px; + + -webkit-box-shadow: 0 0 0 3px rgba(0, 95, 204, 0.2); + + box-shadow: 0 0 0 3px rgba(0, 95, 204, 0.2); + + border-radius: 4px; + +/* Input Focus Styles */ +.hvac-form-input:focus, +.hvac-content input[type="text"]:focus, +.hvac-content input[type="email"]:focus, +.hvac-content input[type="password"]:focus, +.hvac-content input[type="url"]:focus, +.hvac-content textarea:focus, +.hvac-content select:focus, +.hvac-email-form-row input:focus, +.hvac-email-form-row textarea:focus, +.hvac-filter-group input:focus, +.hvac-filter-group select:focus { + outline: 2px solid #005fcc; + + outline-offset: 2px; + + border-color: #005fcc; + + box-shadow: 0 0 0 3px rgba(0, 95, 204, 0.2); + +/* Link Focus Styles */ +.hvac-content a:focus, +.hvac-event-link:focus, +.hvac-certificate-link:focus, +.hvac-attendee-profile-icon:focus, +.hvac-dashboard-nav a:focus, +.hvac-email-navigation a:focus { + outline: 2px solid #005fcc; + + outline-offset: 2px; + + text-decoration: underline; + + background-color: rgba(0, 95, 204, 0.1); + + border-radius: 2px; + +/* Interactive Element Focus Styles */ +.hvac-attendee-checkbox:focus, +.hvac-select-all-container input[type="checkbox"]:focus, +.hvac-modal-close:focus, +.hvac-certificate-table tr:focus { + outline: 2px solid #005fcc; + + outline-offset: 2px; + + box-shadow: 0 0 0 3px rgba(0, 95, 204, 0.2); + +/* High Contrast Mode Support */ +@media (prefers-contrast: high) { + .hvac-content *:focus { + outline: 3px solid #000000; + + outline-offset: 2px; + + background-color: #ffff00; + + color: #000000; + +/* Focus-visible polyfill support */ + +/* Reset focus for mouse users while preserving keyboard accessibility */ +.js-focus-visible:focus:not(.focus-visible) { + outline: none; + + box-shadow: none; + +/* Ensure focus is visible for keyboard users */ +.js-focus-visible .focus-visible { + + outline: 2px solid #005fcc; + + outline-offset: 2px; + +/* Feature Detection Support */ +@supports not (display: flex) { + .hvac-content [class*="flex"] { + display: table-cell; + + vertical-align: middle; + +@supports not (display: grid) { + .hvac-content [class*="grid"] { + display: block; + + overflow: hidden; + +.hvac-content [class*="grid"] > * { + + float: left; + + width: 50%; +} + +/* === hvac-dashboard.css === */ +/* Reduced Motion Support Added - 2025-07-23 */ +/* Vendor Prefixes Added - 2025-07-23 */ +/* + * HVAC Trainer Dashboard Styles - Enhanced Version + * + * Styles specific to the trainer dashboard page. + */ + +/* CSS Custom Properties / Variables */ +:root { + /* Spacing */ + --hvac-spacing-1: 0.25rem; + --hvac-spacing-2: 0.5rem; + --hvac-spacing-3: 0.75rem; + --hvac-spacing-4: 1rem; + --hvac-spacing-5: 1.5rem; + --hvac-spacing-6: 2rem; + --hvac-spacing-8: 3rem; + --hvac-spacing-sm: 0.5rem; + --hvac-spacing-md: 1rem; + --hvac-spacing-lg: 1.5rem; + --hvac-spacing-xl: 2rem; + /* Border Radius */ + --hvac-radius-sm: 4px; + --hvac-radius-md: 8px; + --hvac-radius-lg: 12px; + --hvac-radius-full: 9999px; + --hvac-border-radius: 8px; + /* Colors */ + --hvac-theme-primary: #0073aa; + --hvac-theme-primary-dark: #005a87; + --hvac-theme-text: #333333; + --hvac-primary: #0073aa; + --hvac-secondary: #666666; + --hvac-text: #333333; + --hvac-border: #dddddd; + --hvac-border-light: #eeeeee; + /* Shadows */ + --hvac-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); + --hvac-shadow-lg: 0 4px 6px rgba(0, 0, 0, 0.1); +} + +/* Dashboard Container */ +.hvac-dashboard { + padding: 1.5rem; /* IE fallback */ + padding: var(--hvac-spacing-lg); + background-color: #f9f9f9; + -webkit-border-radius: 4px; + border-radius: 4px; + border-radius: 4px; /* IE fallback */ + -webkit-border-radius: var(--hvac-border-radius); +} + +/* Header */ +.hvac-dashboard-header { + margin-bottom: 2em; + padding-bottom: 1em; + border-bottom: 1px solid #e0e0e0; /* IE fallback */ + border-bottom: 1px solid var(--hvac-border); + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-pack: justify; + -ms-flex-pack: justify; + justify-content: space-between; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -ms-flex-wrap: wrap; + flex-wrap: wrap; +} + +.hvac-dashboard-header h1 { + margin: 0 0 0.5rem 0; /* IE fallback */ + margin: 0 0 var(--hvac-spacing-sm) 0; + color: #333333; /* IE fallback */ + color: var(--hvac-text); + font-size: 1.8rem; + font-weight: 600; +} + +.hvac-dashboard-nav { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + gap: 0.5rem; /* IE fallback */ + gap: var(--hvac-spacing-sm); + -ms-flex-wrap: wrap; + flex-wrap: wrap; +} + +.hvac-dashboard-nav a { + margin: 0; + min-width: 120px; + text-align: center; +} + +/* Stats Section */ +.hvac-dashboard-stats { + margin-bottom: 2rem; /* IE fallback */ + margin-bottom: var(--hvac-spacing-xl); +} + +.hvac-dashboard-stats h2 { + margin-top: 0; + margin-bottom: 1rem; /* IE fallback */ + margin-bottom: var(--hvac-spacing-md); + font-size: 1.4rem; + color: #333333; /* IE fallback */ + color: var(--hvac-text); + padding-bottom: 0.5rem; /* IE fallback */ + padding-bottom: var(--hvac-spacing-sm); + border-bottom: 1px solid #f0f0f0; /* IE fallback */ + border-bottom: 1px solid var(--hvac-border-light); +} + +/* Row layout for stats */ +.hvac-stats-row { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -ms-flex-direction: row; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + margin: -10px; + -webkit-box-pack: justify; + -ms-flex-pack: justify; + justify-content: space-between; + -webkit-box-align: stretch; + -ms-flex-align: stretch; + align-items: stretch; +} + +.hvac-stat-col { + -webkit-box-flex: 1; + -ms-flex: 1; + flex: 1; + min-width: 160px; + padding: 10px; + margin-bottom: 0.5rem; /* IE fallback */ + margin-bottom: var(--hvac-spacing-sm); +} + +.hvac-stat-card { + border: 1px solid #e0e0e0; /* IE fallback */ + border: 1px solid var(--hvac-border); + border-radius: 4px; /* IE fallback */ + border-radius: var(--hvac-border-radius); + padding: 1.5rem; /* IE fallback */ + padding: var(--hvac-spacing-lg); + background: white; + text-align: center; + width: 100%; + flex-grow: 1; + height: 100%; + -webkit-box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); /* IE fallback */ + -webkit-box-shadow: var(--hvac-shadow); + box-shadow: var(--hvac-shadow); + -webkit-transition: transform 0.2s, box-shadow 0.2s; + transition: transform 0.2s, box-shadow 0.2s; +} + +.hvac-stat-card:hover { + -webkit-transform: translateY(-2px); + -ms-transform: translateY(-2px); + transform: translateY(-2px); + -webkit-box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); /* IE fallback */ + -webkit-box-shadow: var(--hvac-shadow-lg); + box-shadow: var(--hvac-shadow-lg); +} + +.hvac-stat-card h3 { + margin-top: 0; + margin-bottom: 0.5rem; /* IE fallback */ + margin-bottom: var(--hvac-spacing-sm); + font-size: 1.1em; + color: #54595f; /* IE fallback */ + color: var(--hvac-secondary); + font-weight: 600; +} + +.hvac-stat-card p { + font-size: 2.2em; + margin: 0.2em 0; + font-weight: 700; + line-height: 1.2; + color: #0274be; /* IE fallback */ + color: var(--hvac-primary); +} + +.hvac-stat-card small { + display: block; + margin-top: 0.5rem; /* IE fallback */ + margin-top: var(--hvac-spacing-sm); + font-size: 0.85em; + color: #757575; /* IE fallback */ + color: var(--hvac-text-light); +} + +/* Events Section */ +.hvac-dashboard-events { + background: white; + border-radius: 4px; /* IE fallback */ + border-radius: var(--hvac-border-radius); + padding: 1.5rem; /* IE fallback */ + padding: var(--hvac-spacing-lg); + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); /* IE fallback */ + box-shadow: var(--hvac-shadow); + margin-bottom: 2rem; /* IE fallback */ + margin-bottom: var(--hvac-spacing-xl); +} + +.hvac-dashboard-events h2 { + margin-top: 0; + margin-bottom: 1rem; /* IE fallback */ + margin-bottom: var(--hvac-spacing-md); + font-size: 1.4rem; + color: #333333; /* IE fallback */ + color: var(--hvac-text); + padding-bottom: 0.5rem; /* IE fallback */ + padding-bottom: var(--hvac-spacing-sm); + border-bottom: 1px solid #f0f0f0; /* IE fallback */ + border-bottom: 1px solid var(--hvac-border-light); +} + +/* Event Filters */ +.hvac-event-filters { + margin-bottom: 1.5rem; /* IE fallback */ + margin-bottom: var(--hvac-spacing-lg); + padding: 1rem; /* IE fallback */ + padding: var(--hvac-spacing-md); + background-color: #f0f0f1; /* IE fallback */ + background-color: var(--hvac-secondary-light); + border-radius: 4px; /* IE fallback */ + border-radius: var(--hvac-border-radius); + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + gap: 0.5rem; /* IE fallback */ + gap: var(--hvac-spacing-sm); +} + +.hvac-event-filters span { + margin-right: 1rem; /* IE fallback */ + margin-right: var(--hvac-spacing-md); + font-weight: 600; + color: #3a3f44; /* IE fallback */ + color: var(--hvac-secondary-dark); +} + +.hvac-filter { + padding: 0.5rem 1rem !important; + margin: 0 !important; +} + +.hvac-filter-active { + background-color: #0274be; /* IE fallback */ + background-color: var(--hvac-primary) !important; + color: white !important; +} + +/* Events Table */ +.hvac-events-table-wrapper { + overflow-x: auto; + position: relative; + min-height: 100px; + border: 1px solid #e0e0e0; /* IE fallback */ + border: 1px solid var(--hvac-border); + border-radius: 4px; /* IE fallback */ + border-radius: var(--hvac-border-radius); +} + +.events-table { + width: 100%; + border-collapse: collapse; +} + +.events-table th { + background-color: #f8f9fa; + color: #3a3f44; /* IE fallback */ + color: var(--hvac-secondary-dark); + padding: 1rem; /* IE fallback */ + padding: var(--hvac-spacing-md); + font-weight: 600; + text-align: left; + border-bottom: 2px solid #e0e0e0; /* IE fallback */ + border-bottom: 2px solid var(--hvac-border); +} + +.events-table td { + padding: 1rem; /* IE fallback */ + padding: var(--hvac-spacing-md); + border-bottom: 1px solid #f0f0f0; /* IE fallback */ + border-bottom: 1px solid var(--hvac-border-light); + vertical-align: middle; +} + +.events-table tbody tr:hover { + background-color: #e6f3fb; /* IE fallback */ + background-color: var(--hvac-primary-light); +} + +.events-table .column-actions { + white-space: nowrap; +} + +.events-table .column-actions a { + margin-right: 0.5rem; /* IE fallback */ + margin-right: var(--hvac-spacing-sm); + color: #0274be; /* IE fallback */ + color: var(--hvac-primary); + text-decoration: none; + font-weight: 500; +} + +.events-table .column-actions a:hover { + text-decoration: underline; +} + +/* Status indicators */ +.status-indicator { + display: inline-block; + padding: 0.25rem 0.5rem; + -webkit-border-radius: 12px; + border-radius: 12px; + font-size: 0.85em; + font-weight: 500; + text-align: center; +} + +.status-published { + background-color: #e8f5e9; + color: #2e7d32; +} + +.status-draft { + background-color: #eceff1; + color: #546e7a; +} + +.status-pending { + background-color: #fff3e0; + color: #ef6c00; +} + +/* Loading indicator */ +.hvac-loading { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(255, 255, 255, 0.8); + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-pack: center; + -ms-flex-pack: center; + justify-content: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + font-weight: bold; + padding: 20px; + z-index: 10; + -webkit-animation: fadeIn 0.3s ease-in-out; + animation: fadeIn 0.3s ease-in-out; +} + +@keyframes fadeIn { + from { + opacity: 0; + } + to { + opacity: 1; + } +} + +/* Error message */ +.hvac-error { + color: #d63638; /* IE fallback */ + color: var(--hvac-error); + padding: 1rem; /* IE fallback */ + padding: var(--hvac-spacing-md); + border: 1px solid #ffb8bb; + background-color: #ffebe9; /* IE fallback */ + background-color: var(--hvac-error-light); + margin: 1rem; /* IE fallback */ + margin: var(--hvac-spacing-md) 0; + border-radius: 4px; /* IE fallback */ + border-radius: var(--hvac-border-radius); +} + +/* Responsive adjustments */ + +/* Reduced Motion Support Added - WCAG 2.1 Accessibility */ +/* Respects user preference for reduced motion to prevent vestibular disorders */ +@media (prefers-reduced-motion: reduce) { + /* Disable all animations and transitions globally */ + *, *::before, *::after { + animation-duration: 0.001ms !important; + animation-delay: 0s !important; + animation-iteration-count: 1 !important; + transition-duration: 0.001ms !important; + transition-delay: 0s !important; + scroll-behavior: auto !important; + } + + /* Remove specific transform animations */ + .hvac-animate-fade-in, + .hvac-animate-scale-up, + .hvac-animate-pulse, + .hvac-animate-slide-in-right, + .hvac-animate-slide-in-left, + .hvac-animate-slide-in-bottom { + animation: none !important; + opacity: 1 !important; + transform: none !important; + } + + /* Disable hover transformations */ + .hvac-card:hover, + .hvac-stat-card:hover, + .hvac-event-stat-card:hover, + .hvac-button:hover, + .hvac-email-submit:hover { + transform: none !important; + animation: none !important; + } + + /* Keep essential visual feedback but remove motion */ + .hvac-card:hover, + .hvac-stat-card:hover, + .hvac-event-stat-card:hover { + border-color: var(--hvac-primary, #0274be) !important; + box-shadow: 0 0 0 2px rgba(2, 116, 190, 0.2) !important; + } + + /* Disable loading spinner animation but keep visibility */ + .hvac-loading::after { + animation: none !important; + border-radius: 50% !important; + border: 2px solid rgba(0, 0, 0, 0.2) !important; + border-top-color: #333 !important; + } + + /* Disable focus pulse animation */ + .hvac-button:focus, + .hvac-email-submit:focus, + .hvac-content button[type="submit"]:focus { + animation: none !important; + } + + /* Ensure smooth scrolling is disabled */ + html { + scroll-behavior: auto !important; + } + + /* Disable CSS Grid/Flexbox animations if any */ + .hvac-dashboard-stats .hvac-stat-card:nth-child(n), + .hvac-event-summary-stats .hvac-event-stat-card:nth-child(n) { + animation: none !important; + opacity: 1 !important; + } +} + +/* Provide alternative visual feedback for reduced motion users */ +@media (prefers-reduced-motion: reduce) { + /* Enhanced border feedback instead of transform */ + .hvac-content button:hover, + .hvac-content input[type="submit"]:hover, + .hvac-content a:hover { + outline: 2px solid var(--hvac-primary, #0274be) !important; + outline-offset: 2px !important; + } + + /* Enhanced color changes for interactive elements */ + .hvac-attendee-item:hover { + background-color: var(--hvac-primary-light, #e6f3fb) !important; + border-left: 4px solid var(--hvac-primary, #0274be) !important; + } + + /* Static loading indicator */ + .hvac-loading { + opacity: 0.7 !important; + } + + .hvac-loading::after { + content: "Loading..." !important; + display: inline-block !important; + font-size: 12px !important; + color: #666 !important; + border: none !important; + background: none !important; + border-radius: 0 !important; + width: auto !important; + height: auto !important; + position: static !important; + margin-left: 8px !important; + } +} + +@media (max-width: 768px) { + .hvac-dashboard-header { + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + flex-direction: column; + -webkit-box-align: start; + -ms-flex-align: start; + align-items: flex-start; + } + + .hvac-dashboard-nav { + margin-top: 1rem; /* IE fallback */ + margin-top: var(--hvac-spacing-md); + width: 100%; + } + + .hvac-dashboard-nav a { + -webkit-box-flex: 1; + -ms-flex: 1; + flex: 1; + min-width: unset; + } + + .hvac-stat-col { + min-width: 140px; + flex-basis: calc(50% - 20px); + } + + .hvac-event-filters { + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + flex-direction: column; + -webkit-box-align: start; + -ms-flex-align: start; + align-items: flex-start; + } + + .hvac-event-filters span { + margin-bottom: 0.5rem; /* IE fallback */ + margin-bottom: var(--hvac-spacing-sm); + } + + .hvac-filter { + width: 100%; + text-align: center; + } +} + +@media (max-width: 480px) { + .hvac-stat-col { + flex-basis: 100%; + } +} + +/* =================================== + Master Dashboard Specific Styles + =================================== */ + +/* Dashboard Sections */ +.dashboard-section { + background: #fff; + border-radius: 8px; /* IE fallback */ + -webkit-border-radius: var(--hvac-radius-md); + border-radius: var(--hvac-radius-md); + padding: 2rem; /* IE fallback */ + padding: var(--hvac-spacing-6); + margin-bottom: 2rem; /* IE fallback */ + margin-bottom: var(--hvac-spacing-6); + -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); +} + +.section-title { + font-size: 1.5rem; + font-weight: 600; + color: #333333; /* IE fallback */ + color: var(--hvac-theme-text); + margin-bottom: 1rem; /* IE fallback */ + margin-bottom: var(--hvac-spacing-4); + padding-bottom: 0.75rem; /* IE fallback */ + padding-bottom: var(--hvac-spacing-3); + border-bottom: 2px solid #e5e7eb; +} + +/* Events Filters */ +.events-filters { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + gap: 0.75rem; /* IE fallback */ + gap: var(--hvac-spacing-3); + -webkit-box-align: end; + -ms-flex-align: end; + align-items: flex-end; + margin-bottom: 1rem; /* IE fallback */ + margin-bottom: var(--hvac-spacing-4); + padding: 1rem; /* IE fallback */ + padding: var(--hvac-spacing-4); + background: #f9fafb; + border-radius: 8px; /* IE fallback */ + border-radius: var(--hvac-radius-md); +} + +.filter-group { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + flex-direction: column; + gap: 0.25rem; /* IE fallback */ + gap: var(--hvac-spacing-1); +} + +.filter-group label { + font-size: 0.875rem; + font-weight: 500; + color: #333333; /* IE fallback */ + color: var(--hvac-theme-text); +} + +.filter-group input, +.filter-group select { + padding: 0.5rem; /* IE fallback */ + padding: var(--hvac-spacing-2) var(--hvac-spacing-3); + border: 1px solid #d1d5db; + border-radius: 4px; /* IE fallback */ + -webkit-border-radius: var(--hvac-radius-sm); + border-radius: var(--hvac-radius-sm); + font-size: 0.875rem; + min-width: 150px; +} + +.filter-group input:focus, +.filter-group select:focus { + outline: none; + border-color: #0073aa; /* IE fallback */ + border-color: var(--hvac-theme-primary); + -webkit-box-shadow: 0 0 0 3px rgba(0, 115, 170, 0.1); + box-shadow: 0 0 0 3px rgba(0, 115, 170, 0.1); +} + +/* Trainers Table */ +.trainers-table-container { + overflow-x: auto; + margin-top: 1rem; /* IE fallback */ + margin-top: var(--hvac-spacing-4); +} + +.trainers-table { + width: 100%; + border-collapse: separate; + border-spacing: 0; + background: #fff; +} + +.trainers-table thead { + background: #f9fafb; +} + +.trainers-table th { + padding: 0.75rem; /* IE fallback */ + padding: var(--hvac-spacing-3) var(--hvac-spacing-4); + text-align: left; + font-weight: 600; + color: #333333; /* IE fallback */ + color: var(--hvac-theme-text); + border-bottom: 2px solid #e5e7eb; + white-space: nowrap; +} + +.trainers-table td { + padding: 0.75rem; /* IE fallback */ + padding: var(--hvac-spacing-3) var(--hvac-spacing-4); + border-bottom: 1px solid #f3f4f6; +} + +.trainers-table tbody tr:hover { + background: #f9fafb; +} + +.trainers-table .trainer-name { + font-weight: 500; +} + +.trainers-table .number { + text-align: center; +} + +.trainers-table .revenue { + text-align: right; + font-weight: 500; + color: #059669; +} + +/* Events Table Container */ +.events-table-container { + margin-top: 1rem; /* IE fallback */ + margin-top: var(--hvac-spacing-4); +} + +/* Status Badge */ +.status-badge { + display: inline-block; + padding: 0.25rem; /* IE fallback */ + padding: var(--hvac-spacing-1) var(--hvac-spacing-2); + -webkit-border-radius: var(--hvac-radius-full); + border-radius: var(--hvac-radius-full); + font-size: 0.75rem; + font-weight: 500; + text-transform: uppercase; +} + +.status-badge.status-publish { + background: #dcfce7; + color: #166534; +} + +.status-badge.status-future { + background: #dbeafe; + color: #1e40af; +} + +.status-badge.status-draft { + background: #f3f4f6; + color: #6b7280; +} + +.status-badge.status-pending { + background: #fef3c7; + color: #92400e; +} + +.status-badge.status-private { + background: #fce7f3; + color: #9f1239; +} + +/* Pagination */ +.pagination-container { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-pack: justify; + -ms-flex-pack: justify; + justify-content: space-between; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + margin-top: 2rem; /* IE fallback */ + margin-top: var(--hvac-spacing-6); + padding-top: 1rem; /* IE fallback */ + padding-top: var(--hvac-spacing-4); + border-top: 1px solid #e5e7eb; +} + +.pagination-info { + color: #333333; /* IE fallback */ + color: var(--hvac-theme-text); + font-size: 0.875rem; +} + +.pagination-controls { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + gap: 0.5rem; /* IE fallback */ + gap: var(--hvac-spacing-2); +} + +.pagination-btn { + padding: 0.5rem; /* IE fallback */ + padding: var(--hvac-spacing-2) var(--hvac-spacing-3); + border: 1px solid #d1d5db; + background: #fff; + color: #333333; /* IE fallback */ + color: var(--hvac-theme-text); + border-radius: 4px; /* IE fallback */ + border-radius: var(--hvac-radius-sm); + font-size: 0.875rem; + cursor: pointer; + -webkit-transition: all 0.2s; + transition: all 0.2s; +} + +.pagination-btn:hover { + background: #f9fafb; + border-color: #0073aa; /* IE fallback */ + border-color: var(--hvac-theme-primary); +} + +.pagination-btn.active { + background: #0073aa; /* IE fallback */ + background: var(--hvac-theme-primary); + color: #fff; + border-color: #0073aa; /* IE fallback */ + border-color: var(--hvac-theme-primary); +} + +.pagination-btn:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +/* Loading States */ +.loading-placeholder { + text-align: center; + padding: 3rem; /* IE fallback */ + padding: var(--hvac-spacing-8); + color: #6b7280; + font-size: 1rem; +} + +.loading-placeholder::before { + content: ''; + display: inline-block; + width: 20px; + height: 20px; + margin-right: 0.5rem; /* IE fallback */ + margin-right: var(--hvac-spacing-2); + border: 2px solid #e5e7eb; + border-top-color: #0073aa; /* IE fallback */ + border-top-color: var(--hvac-theme-primary); + -webkit-border-radius: 50%; + border-radius: 50%; + -webkit-animation: hvac-spin 1s linear infinite; + animation: hvac-spin 1s linear infinite; +} + +/* Button Styles */ +.btn { + display: inline-block; + padding: 0.5rem; /* IE fallback */ + padding: var(--hvac-spacing-2) var(--hvac-spacing-4); + border-radius: 4px; /* IE fallback */ + border-radius: var(--hvac-radius-sm); + font-size: 0.875rem; + font-weight: 500; + text-decoration: none; + transition: all 0.2s; + cursor: pointer; + border: none; +} + +.btn-primary { + background: #0073aa; /* IE fallback */ + background: var(--hvac-theme-primary); + color: #fff; +} + +.btn-primary:hover { + background: #005a87; /* IE fallback */ + background: var(--hvac-theme-primary-dark); +} + +.btn-secondary { + background: #6b7280; + color: #fff; +} + +.btn-secondary:hover { + background: #4b5563; +} + +.btn-small { + padding: 0.25rem; /* IE fallback */ + padding: var(--hvac-spacing-1) var(--hvac-spacing-2); + font-size: 0.75rem; +} + +/* No Data Message */ +.no-data-message { + text-align: center; + padding: 3rem; /* IE fallback */ + padding: var(--hvac-spacing-8); + color: #6b7280; +} + +.no-data-message p { + margin: 0; + font-size: 1rem; +} + +/* Error Message */ +.error-message { + background: #fee; + border: 1px solid #fcc; + color: #c33; + padding: 1rem; /* IE fallback */ + padding: var(--hvac-spacing-4); + border-radius: 4px; /* IE fallback */ + border-radius: var(--hvac-radius-sm); + text-align: center; +} + +/* Responsive Design for Master Dashboard */ +@media (max-width: 768px) { + .events-filters { + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + flex-direction: column; + } + + .filter-group { + width: 100%; + } + + .filter-group input, + .filter-group select { + width: 100%; + } + + .pagination-container { + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + flex-direction: column; + gap: 0.75rem; /* IE fallback */ + gap: var(--hvac-spacing-3); + text-align: center; + } + + .trainers-table { + font-size: 0.875rem; + } + + .trainers-table th, + .trainers-table td { + padding: 0.5rem; /* IE fallback */ + padding: var(--hvac-spacing-2); + } +} + +/* Focus Management Styles - WCAG 2.1 Compliance */ +/* Added for keyboard accessibility and screen reader support */ + +/* Button Focus Styles */ +.hvac-button:focus, +.hvac-content .button:focus, +.hvac-content button:focus, +.hvac-content input[type="submit"]:focus, +.hvac-email-submit:focus, +.hvac-filter-submit:focus, +.hvac-certificate-actions button:focus, +.hvac-certificate-actions a:focus { + outline: 2px solid #005fcc; + outline-offset: 2px; + -webkit-box-shadow: 0 0 0 3px rgba(0, 95, 204, 0.2); + box-shadow: 0 0 0 3px rgba(0, 95, 204, 0.2); + border-radius: 4px; +} + +/* Input Focus Styles */ +.hvac-form-input:focus, +.hvac-content input[type="text"]:focus, +.hvac-content input[type="email"]:focus, +.hvac-content input[type="password"]:focus, +.hvac-content input[type="url"]:focus, +.hvac-content textarea:focus, +.hvac-content select:focus, +.hvac-email-form-row input:focus, +.hvac-email-form-row textarea:focus, +.hvac-filter-group input:focus, +.hvac-filter-group select:focus { + outline: 2px solid #005fcc; + outline-offset: 2px; + border-color: #005fcc; + box-shadow: 0 0 0 3px rgba(0, 95, 204, 0.2); +} + +/* Link Focus Styles */ +.hvac-content a:focus, +.hvac-event-link:focus, +.hvac-certificate-link:focus, +.hvac-attendee-profile-icon:focus, +.hvac-dashboard-nav a:focus, +.hvac-email-navigation a:focus { + outline: 2px solid #005fcc; + outline-offset: 2px; + text-decoration: underline; + background-color: rgba(0, 95, 204, 0.1); + -webkit-border-radius: 2px; + border-radius: 2px; +} + +/* Interactive Element Focus Styles */ +.hvac-attendee-checkbox:focus, +.hvac-select-all-container input[type="checkbox"]:focus, +.hvac-modal-close:focus, +.hvac-certificate-table tr:focus { + outline: 2px solid #005fcc; + outline-offset: 2px; + box-shadow: 0 0 0 3px rgba(0, 95, 204, 0.2); +} + +/* High Contrast Mode Support */ +@media (prefers-contrast: high) { + .hvac-content *:focus { + outline: 3px solid #000000; + outline-offset: 2px; + background-color: #ffff00; + color: #000000; + } +} + +/* Focus-visible polyfill support */ +/* Reset focus for mouse users while preserving keyboard accessibility */ +.js-focus-visible:focus:not(.focus-visible) { + outline: none; + -webkit-box-shadow: none; + box-shadow: none; +} + +/* Ensure focus is visible for keyboard users */ +.js-focus-visible .focus-visible { + outline: 2px solid #005fcc; + outline-offset: 2px; +} + +/* Feature Detection Support */ +@supports not (display: flex) { + .hvac-content [class*="flex"] { + display: table-cell; + vertical-align: middle; + } +} + +@supports not (display: grid) { + .hvac-content [class*="grid"] { + display: block; + overflow: hidden; + } + + .hvac-content [class*="grid"] > * { + float: left; + width: 50%; + } +} + +/* === hvac-trainer-profile.css === */ +/** + * HVAC Trainer Profile Styles + * + * @package HVAC_Community_Events + * @version 2.0.0 + */ + +/* Page Layout */ +.hvac-trainer-profile-view, +.hvac-trainer-profile-edit { + max-width: 1200px; + margin: 0 auto; + padding: 2rem; +} + +/* Page Header */ +.hvac-page-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 2rem; + padding-bottom: 1rem; + border-bottom: 2px solid #e0e0e0; +} + +.hvac-page-header h1 { + margin: 0; + color: #0274be; + font-size: 2rem; +} + +/* Breadcrumb */ +.hvac-breadcrumb { + margin-bottom: 1.5rem; + color: #666; + font-size: 0.9rem; +} + +.hvac-breadcrumb a { + color: #0274be; + text-decoration: none; +} + +.hvac-breadcrumb a:hover { + text-decoration: underline; +} + +/* Profile Content Layout */ +.hvac-profile-content { + display: flex; + gap: 2rem; +} + +/* Profile Sidebar */ +.hvac-profile-sidebar { + flex: 0 0 300px; +} + +/* Profile Photo */ +.hvac-profile-photo { + margin-bottom: 2rem; + text-align: center; +} + +.hvac-profile-photo img { + width: 200px; + height: 200px; + border-radius: 50%; + object-fit: cover; + border: 5px solid #f0f0f0; +} + +.hvac-profile-photo-placeholder { + width: 200px; + height: 200px; + border-radius: 50%; + background-color: #0274be; + color: white; + display: flex; + align-items: center; + justify-content: center; + font-size: 3rem; + font-weight: 600; + margin: 0 auto; +} + +/* Profile Stats */ +.hvac-profile-stats { + background-color: #f8f9fa; + padding: 1.5rem; + border-radius: 8px; +} + +.hvac-stat-item { + text-align: center; + margin-bottom: 1.5rem; +} + +.hvac-stat-item:last-child { + margin-bottom: 0; +} + +.hvac-stat-value { + display: block; + font-size: 2rem; + font-weight: 700; + color: #0274be; + margin-bottom: 0.5rem; +} + +.hvac-stat-label { + display: block; + font-size: 0.875rem; + color: #666; + text-transform: uppercase; + letter-spacing: 0.05em; +} + +/* Profile Main Content */ +.hvac-profile-main { + flex: 1; +} + +.hvac-profile-section { + background-color: white; + padding: 2rem; + border-radius: 8px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + margin-bottom: 2rem; +} + +.hvac-profile-section h2 { + margin-top: 0; + margin-bottom: 1.5rem; + color: #333; + font-size: 1.5rem; + padding-bottom: 0.75rem; + border-bottom: 2px solid #e0e0e0; +} + +/* Profile Details */ +.hvac-profile-details { + display: grid; + gap: 1rem; +} + +.hvac-detail-row { + display: grid; + grid-template-columns: 150px 1fr; + align-items: center; +} + +.hvac-detail-label { + font-weight: 600; + color: #666; +} + +.hvac-detail-value { + color: #333; +} + +.hvac-detail-value a { + color: #0274be; + text-decoration: none; +} + +.hvac-detail-value a:hover { + text-decoration: underline; +} + +/* Profile Bio */ +.hvac-profile-bio { + color: #333; + line-height: 1.6; +} + +/* Certifications List */ +.hvac-certifications-list { + display: grid; + gap: 0.75rem; +} + +.hvac-certification-item { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.75rem; + background-color: #f8f9fa; + border-radius: 4px; +} + +.hvac-certification-item .dashicons { + color: #0274be; + width: 20px; + height: 20px; +} + +/* Edit Form Styles */ +.hvac-form { + background-color: white; + padding: 2rem; + border-radius: 8px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); +} + +.hvac-form-section { + margin-bottom: 2rem; + padding-bottom: 2rem; + border-bottom: 1px solid #e0e0e0; +} + +.hvac-form-section:last-child { + margin-bottom: 0; + padding-bottom: 0; + border-bottom: none; +} + +.hvac-form-section h3 { + margin-bottom: 1.5rem; + color: #333; + font-size: 1.25rem; +} + +.hvac-form-row { + margin-bottom: 1.5rem; +} + +.hvac-form-row label { + display: block; + margin-bottom: 0.5rem; + font-weight: 600; + color: #333; +} + +.hvac-form-row input, +.hvac-form-row select, +.hvac-form-row textarea { + width: 100%; + padding: 0.75rem; + border: 1px solid #ddd; + border-radius: 4px; + font-size: 1rem; +} + +.hvac-form-row input:focus, +.hvac-form-row select:focus, +.hvac-form-row textarea:focus { + outline: none; + border-color: #0274be; + box-shadow: 0 0 0 3px rgba(2, 116, 190, 0.1); +} + +.hvac-form-row-half { + display: flex; + gap: 1rem; +} + +.hvac-form-row-half > div { + flex: 1; +} + +/* Profile Photo Upload */ +.hvac-profile-photo-upload { + display: flex; + align-items: center; + gap: 2rem; +} + +.hvac-current-photo img { + width: 100px; + height: 100px; + border-radius: 50%; + object-fit: cover; +} + +.hvac-photo-placeholder { + width: 100px; + height: 100px; + border-radius: 50%; + background-color: #f0f0f0; + display: flex; + align-items: center; + justify-content: center; + color: #999; + font-size: 0.875rem; +} + +.hvac-photo-actions { + display: flex; + gap: 1rem; +} + +/* Buttons */ +.hvac-button { + display: inline-block; + padding: 0.75rem 1.5rem; + border: none; + border-radius: 4px; + font-size: 1rem; + font-weight: 600; + text-decoration: none; + cursor: pointer; + transition: all 0.3s ease; +} + +.hvac-button-primary { + background-color: #0274be; + color: white; +} + +.hvac-button-primary:hover { + background-color: #005fa3; +} + +.hvac-button-secondary { + background-color: #6c757d; + color: white; +} + +.hvac-button-secondary:hover { + background-color: #5a6268; +} + +.hvac-button-danger-outline { + background-color: transparent; + color: #dc3545; + border: 1px solid #dc3545; +} + +.hvac-button-danger-outline:hover { + background-color: #dc3545; + color: white; +} + +/* Form Actions */ +.hvac-form-actions { + display: flex; + gap: 1rem; + margin-top: 2rem; + padding-top: 2rem; + border-top: 1px solid #e0e0e0; +} + +/* Messages */ +.hvac-message { + padding: 1rem 1.5rem; + border-radius: 4px; + margin-bottom: 1.5rem; + font-weight: 500; +} + +.hvac-message-success { + background-color: #d4edda; + color: #155724; + border: 1px solid #c3e6cb; +} + +.hvac-message-error { + background-color: #f8d7da; + color: #721c24; + border: 1px solid #f5c6cb; +} + +/* Form Errors */ +.hvac-form-error { + border-color: #dc3545 !important; +} + +.hvac-error-message { + display: block; + color: #dc3545; + font-size: 0.875rem; + margin-top: 0.25rem; +} + +/* Responsive */ +@media (max-width: 768px) { + .hvac-trainer-profile-view, + .hvac-trainer-profile-edit { + padding: 1rem; + } + + .hvac-page-header { + flex-direction: column; + align-items: flex-start; + gap: 1rem; + } + + .hvac-profile-content { + flex-direction: column; + } + + .hvac-profile-sidebar { + flex: none; + width: 100%; + } + + .hvac-detail-row { + grid-template-columns: 1fr; + gap: 0.25rem; + } + + .hvac-detail-label { + font-size: 0.875rem; + } + + .hvac-form-row-half { + flex-direction: column; + } + + .hvac-profile-photo-upload { + flex-direction: column; + align-items: flex-start; + } + + .hvac-photo-actions { + flex-wrap: wrap; + } +} + +/* Certification section styling */ +.hvac-certification-section { + border: 2px solid #0073aa; + border-radius: 8px; + padding: 20px; + margin-bottom: 30px; + background: linear-gradient(135deg, #f8fdff 0%, #e6f7ff 100%); + position: relative; +} + +.hvac-certification-section h2, +.hvac-certification-section h3 { + color: #0073aa; + margin-top: 0; + display: flex; + align-items: center; + gap: 10px; +} + +.hvac-certification-section h2::before, +.hvac-certification-section h3::before { + content: "🏆"; + font-size: 1.2em; +} + +/* Certification status badges */ +.hvac-cert-status { + padding: 4px 12px; + border-radius: 20px; + font-weight: bold; + font-size: 0.85em; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.hvac-cert-status-active { + background-color: #d4edda; + color: #155724; + border: 1px solid #c3e6cb; +} + +.hvac-cert-status-expired { + background-color: #f8d7da; + color: #721c24; + border: 1px solid #f5c6cb; +} + +.hvac-cert-status-pending { + background-color: #fff3cd; + color: #856404; + border: 1px solid #ffeaa7; +} + +.hvac-cert-status-disabled { + background-color: #e2e3e5; + color: #495057; + border: 1px solid #ced4da; +} + +/* Read-only field styling for certification edit */ +.hvac-certification-edit-section .hvac-read-only-field { + background-color: #f8f9fa; + border: 1px solid #e9ecef; + padding: 8px 12px; + border-radius: 4px; + color: #6c757d; + font-style: italic; +} + +.hvac-read-only-note { + font-size: 0.8em; + color: #6c757d; + font-weight: normal; + margin-left: 10px; +} + +/* Enhanced form styling for certification fields */ +.hvac-certification-edit-section .hvac-form-row select, +.hvac-certification-edit-section .hvac-form-row input[type="date"] { + border: 2px solid #e9ecef; + transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; +} + +.hvac-certification-edit-section .hvac-form-row select:focus, +.hvac-certification-edit-section .hvac-form-row input[type="date"]:focus { + border-color: #0073aa; + box-shadow: 0 0 0 0.2rem rgba(0, 115, 170, 0.25); + outline: none; +} + +/* === hvac-certificates.css === */ +/* Reduced Motion Support Added - 2025-07-23 */ +/* Vendor Prefixes Added - 2025-07-23 */ +/** + * Certificate Styles + * + * Styles for certificate-related pages and components. + */ + +/* Certificate Tables */ +.hvac-certificate-table { + + width: 100%; + + border-collapse: collapse; + + margin-bottom: 20px; + +.hvac-certificate-table th { + + background-color: #f1f1f1; + + text-align: left; + + padding: 10px; + + border-bottom: 1px solid #ddd; + + font-weight: 600; + +.hvac-certificate-table td { + + padding: 12px 10px; + + border-bottom: 1px solid #eee; + + vertical-align: middle; + +.hvac-certificate-table; + + tr: nth-child(even) { + background-color: #f9f9f9; + +.hvac-certificate-table; + +.tr:hover { + background-color: #f0f7ff; + +/* Certificate Actions */ +.hvac-certificate-actions { + + display: -webkit-box; + + display: -ms-flexbox; + + display: flex; + + gap: 8px; + +.hvac-certificate-actions button, +.hvac-certificate-actions a { + + background-color: #fafafa; + + border: 1px solid #ddd; + + padding: 6px 10px; + + -webkit-border-radius: 4px; + + border-radius: 4px; + + cursor: pointer; + + font-size: 13px; + + text-decoration: none; + + -webkit-transition: all 0.2s ease; + + color: #333; + +.hvac-certificate-actions; + + button:hover, +.hvac-certificate-actions a:hover { + background-color: #f0f0f0; + + border-color: #ccc; + +.hvac-view-certificate { + + background-color: #e0f7fa \!important; + + border-color: #80deea \!important; + + color: #006064 \!important; + +.hvac-view-certificate:hover { + background-color: #b2ebf2 \!important; + + border-color: #4dd0e1 \!important; + +.hvac-email-certificate { + + background-color: #e8f5e9 \!important; + + border-color: #a5d6a7 \!important; + + color: #1b5e20 \!important; + +.hvac-email-certificate:hover { + background-color: #c8e6c9 \!important; + + border-color: #81c784 \!important; + +.hvac-revoke-certificate { + + background-color: #ffebee \!important; + + border-color: #ffcdd2 \!important; + + color: #b71c1c \!important; + +.hvac-revoke-certificate:hover { + background-color: #ffcdd2 \!important; + + border-color: #ef9a9a \!important; + +/* Certificate status */ +.hvac-status-active { + + color: #2e7d32; + + background-color: #e8f5e9; + + padding: 3px 8px; + + -webkit-border-radius: 12px; + + border-radius: 12px; + + display: inline-block; + + font-size: 12px; + + font-weight: 600; + +.hvac-status-revoked { + + color: #b71c1c; + + background-color: #ffebee; + + padding: 3px 8px; + + border-radius: 12px; + + display: inline-block; + + font-size: 12px; + + font-weight: 600; + +/* Certificate filters */ +.hvac-certificate-filters { + + display: -webkit-box; + + display: -ms-flexbox; + + display: flex; + + -ms-flex-wrap: wrap; + + gap: 10px; + + margin-bottom: 20px; + + padding: 15px; + + background-color: #f9f9f9; + + -webkit-border-radius: 5px; + + border-radius: 5px; + + border-radius: 5px; + + border: 1px solid #eee; + +.hvac-filter-group { + + display: -webkit-box; + + display: -ms-flexbox; + + display: flex; + + -webkit-box-orient: vertical; + + -webkit-box-direction: normal; + + -ms-flex-direction: column; + + gap: 5px; + + min-width: 200px; + +.hvac-filter-group label { + + font-weight: 600; + + font-size: 14px; + +.hvac-filter-group select, +.hvac-filter-group input { + + padding: 8px 10px; + + border: 1px solid #ddd; + + border-radius: 4px; + +.hvac-filter-submit { + + align-self: flex-end; + + margin-top: auto; + +/* Certificate modal */ +.hvac-modal-overlay { + + position: fixed; + + top: 0; + + left: 0; + + right: 0; + + bottom: 0; + + background-color: rgba(0, 0, 0, 0.5); + + z-index: 1000; + + display: none; + +.hvac-certificate-modal { + + position: fixed; + + top: 50%; + + left: 50%; + + -webkit-transform: translate(-50%, -50%); + + -ms-transform: translate(-50%, -50%); + + background-color: white; + + padding: 20px; + + border-radius: 5px; + + -webkit-box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3); + + z-index: 1001; + + max-width: 90vw; + + max-height: 90vh; + + width: 850px; + + display: none; + +.hvac-modal-close { + + position: absolute; + + top: 10px; + + right: 15px; + + font-size: 24px; + + cursor: pointer; + + color: #999; + + -webkit-transition: color 0.2s ease; + + transition: color 0.2s ease; + +.hvac-modal-close:hover { + color: #333; + +.hvac-certificate-preview { + + width: 100%; + + height: 70vh; + + border: 1px solid #ddd; + + margin-top: 10px; + +.hvac-modal-title { + + margin-top: 0; + + margin-bottom: 15px; + + padding-right: 30px; + +/* Button loading state */ +.hvac-loading { + + opacity: 0.7; + + pointer-events: none; + + position: relative; + +.hvac-loading::after { + content: ''; + + display: inline-block; + + width: 12px; + + height: 12px; + + border: 2px solid rgba(0, 0, 0, 0.2); + + border-top-color: #333; + + -webkit-border-radius: 50%; + + -webkit-animation: hvac-spin 1s linear infinite; + + position: absolute; + + right: 8px; + + top: calc(50% - 6px); + +@keyframes hvac-spin { + 0% { + + -webkit-transform: rotate(0deg); + + -ms-transform: rotate(0deg); + + 100% { + + -webkit-transform: rotate(360deg); + + -ms-transform: rotate(360deg); + +/* Empty state message */ +.hvac-no-certificates { + + padding: 20px; + + background-color: #f9f9f9; + + border: 1px solid #eee; + + border-radius: 5px; + + text-align: center; + + margin: 20px 0; + +/* Certificate link styling */ +.hvac-certificate-link { + + color: #28a745; + + text-decoration: none; + + font-weight: 600; + + transition: color 0.2s ease; + +.hvac-certificate-link:hover { + color: #218838; + + text-decoration: underline; + +.hvac-certificate-link::after { + content: ' ↗'; + + font-size: 0.8em; + + vertical-align: super; + +/* Stats cards */ +.hvac-certificate-stats { + + display: grid; + + grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); + + gap: 15px; + + margin-bottom: 30px; + +.hvac-stat-card { + + background-color: white; + + border: 1px solid #eee; + + border-radius: 5px; + + padding: 15px; + + -webkit-box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05); + +.hvac-stat-card h3 { + + margin-top: 0; + + font-size: 16px; + + color: #555; + +.hvac-stat-value { + + font-size: 28px; + + font-weight: 600; + + color: #333; + + margin: 10px 0 5px; + +/* Responsive tables */ + +/* Reduced Motion Support Added - WCAG 2.1 Accessibility */ +/* Respects user preference for reduced motion to prevent vestibular disorders */ + +@media (prefers-reduced-motion: reduce) { + /* Disable all animations and transitions globally */ + *, *::before, *::after { + animation-duration: 0.001ms !important; + + animation-delay: 0s !important; + + animation-iteration-count: 1 !important; + + transition-duration: 0.001ms !important; + + transition-delay: 0s !important; + + scroll-behavior: auto !important; + + /* Remove specific transform animations */ + .hvac-animate-fade-in, + .hvac-animate-scale-up, + .hvac-animate-pulse, + .hvac-animate-slide-in-right, + .hvac-animate-slide-in-left, + .hvac-animate-slide-in-bottom { + + animation: none !important; + + opacity: 1 !important; + + transform: none !important; + + /* Disable hover transformations */ + .hvac-card:hover, + .hvac-stat-card:hover, + .hvac-event-stat-card:hover, + .hvac-button:hover, + .hvac-email-submit:hover { + transform: none !important; + + animation: none !important; + + /* Keep essential visual feedback but remove motion */ + .hvac-card:hover, + .hvac-stat-card:hover, + .hvac-event-stat-card:hover { + border-color: var(--hvac-primary, #0274be) !important; + + box-shadow: 0 0 0 2px rgba(2, 116, 190, 0.2) !important; + + /* Disable loading spinner animation but keep visibility */ + .hvac-loading::after { + animation: none !important; + + border-radius: 50% !important; + + border: 2px solid rgba(0, 0, 0, 0.2) !important; + + border-top-color: #333 !important; + + /* Disable focus pulse animation */ + .hvac-button:focus, +.hvac-email-submit:focus, + .hvac-content button[type="submit"]:focus { + animation: none !important; + + /* Ensure smooth scrolling is disabled */ + html { + + scroll-behavior: auto !important; + + /* Disable CSS Grid/Flexbox animations if any */ + .hvac-dashboard-stats .hvac-stat-card:nth-child(n), + .hvac-event-summary-stats .hvac-event-stat-card: nth-child(n) { + animation: none !important; + + opacity: 1 !important; + +/* Provide alternative visual feedback for reduced motion users */ +@media (prefers-reduced-motion: reduce) { + /* Enhanced border feedback instead of transform */ + .hvac-content button:hover, + .hvac-content input[type="submit"]:hover, + .hvac-content a:hover { + outline: 2px solid var(--hvac-primary, #0274be) !important; + + outline-offset: 2px !important; + + /* Enhanced color changes for interactive elements */ + .hvac-attendee-item:hover { + background-color: var(--hvac-primary-light, #e6f3fb) !important; + + border-left: 4px solid var(--hvac-primary, #0274be) !important; + + /* Static loading indicator */ + .hvac-loading { + + opacity: 0.7 !important; + +.hvac-loading::after { + content: "Loading..." !important; + + display: inline-block !important; + + font-size: 12px !important; + + color: #666 !important; + + border: none !important; + + background: none !important; + + border-radius: 0 !important; + + width: auto !important; + + height: auto !important; + + position: static !important; + + margin-left: 8px !important; + +@media (max-width: 768px) { + .hvac-certificate-table { + display: block; + + overflow-x: auto; + +.hvac-certificate-filters { + + -webkit-box-orient: vertical; + + -webkit-box-direction: normal; + + -ms-flex-direction: column; + +.hvac-filter-group { + + width: 100%; + +/* Enhanced certificate filter styles */ + +/* Enhanced search field styling */ +#search_attendee { + + padding-right: 30px; + + background-image: url('data: image/svg+xml; + utf8,'); + + background-repeat: no-repeat; + + background-position: calc(100% - 8px) center; + + background-size: 16px; + +/* Search results indicator */ +.hvac-search-results { + + background-color: #f0f7ff; + +/* Certificate Preview Modal */ +#hvac-certificate-preview-modal { + + display: none; + + position: fixed; + + top: 0; + + left: 0; + + width: 100%; + + height: 100%; + + background-color: rgba(0, 0, 0, 0.8); + + z-index: 10000; + +#hvac-certificate-preview-modal .hvac-modal-content { + + position: relative; + + background-color: #fff; + + margin: 3% auto; + + width: 90%; + + max-width: 1000px; + + height: 85%; + + -webkit-border-radius: 8px; + + -webkit-box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3); + + overflow: hidden; + + display: -webkit-box; + + display: -ms-flexbox; + + display: flex; + + -webkit-box-orient: vertical; + + -webkit-box-direction: normal; + + -ms-flex-direction: column; + +#hvac-certificate-preview-modal .hvac-modal-header { + + display: -webkit-box; + + display: -ms-flexbox; + + display: flex; + + -webkit-box-pack: justify; + + -ms-flex-pack: justify; + + justify-content: space-between; + + -webkit-box-align: center; + + -ms-flex-align: center; + + align-items: center; + + padding: 15px 20px; + + background-color: #f8f9fa; + + border-bottom: 1px solid #dee2e6; + +#hvac-certificate-preview-modal .hvac-modal-header h3 { + + margin: 0; + + font-size: 18px; + + color: #333; + +#hvac-certificate-preview-modal .hvac-modal-close { + + font-size: 24px; + + font-weight: bold; + + color: #999; + + cursor: pointer; + + line-height: 1; + + padding: 5px; + +#hvac-certificate-preview-modal .hvac-modal-close:hover { + color: #333; + +#hvac-certificate-preview-modal .hvac-modal-body { + + -webkit-box-flex: 1; + + -ms-flex: 1; + + padding: 0; + + overflow: hidden; + +#hvac-certificate-preview-iframe { + + width: 100%; + + height: 100%; + + border: none; + +/* Certificate Preview Buttons */ +.hvac-certificate-previews { + + margin-top: 15px; + + padding: 15px; + + background-color: #f8f9fa; + + border-radius: 5px; + +.hvac-certificate-previews h4 { + + margin-top: 0; + + margin-bottom: 10px; + + color: #333; + +.hvac-preview-certificate { + + margin-right: 10px; + + margin-bottom: 5px; + +.hvac-search-results p { + + margin: 0; + + font-size: 14px; + +.hvac-search-results strong { + + font-weight: 600; + + color: #2271b1; + +/* Enhanced attendee info display */ +.attendee-info { + + display: -webkit-box; + + display: -ms-flexbox; + + display: flex; + + -webkit-box-orient: vertical; + + -webkit-box-direction: normal; + + -ms-flex-direction: column; + +.attendee-name { + + font-weight: 600; + +.attendee-email { + + font-size: 13px; + + color: #555; + + margin-top: 2px; + +/* Input hint text */ +.hvac-input-hint { + + font-size: 12px; + + color: #666; + + margin-top: 4px; + +/* Clear filters button */ +.hvac-button.hvac-secondary { + + background-color: #f0f0f1; + + color: #2c3338; + + border: 1px solid #c5c5c7; + +.hvac-button.hvac-secondary:hover { + background-color: #e0e0e2; + +/* Focus Management Styles - WCAG 2.1 Compliance */ +/* Added for keyboard accessibility and screen reader support */ + +/* Button Focus Styles */ +.hvac-button:focus, +.hvac-content .button:focus, +.hvac-content button:focus, +.hvac-content input[type="submit"]:focus, +.hvac-email-submit:focus, +.hvac-filter-submit:focus, +.hvac-certificate-actions button:focus, +.hvac-certificate-actions a:focus { + outline: 2px solid #005fcc; + + outline-offset: 2px; + + -webkit-box-shadow: 0 0 0 3px rgba(0, 95, 204, 0.2); + + box-shadow: 0 0 0 3px rgba(0, 95, 204, 0.2); + + border-radius: 4px; + +/* Input Focus Styles */ +.hvac-form-input:focus, +.hvac-content input[type="text"]:focus, +.hvac-content input[type="email"]:focus, +.hvac-content input[type="password"]:focus, +.hvac-content input[type="url"]:focus, +.hvac-content textarea:focus, +.hvac-content select:focus, +.hvac-email-form-row input:focus, +.hvac-email-form-row textarea:focus, +.hvac-filter-group input:focus, +.hvac-filter-group select:focus { + outline: 2px solid #005fcc; + + outline-offset: 2px; + + border-color: #005fcc; + + box-shadow: 0 0 0 3px rgba(0, 95, 204, 0.2); + +/* Link Focus Styles */ +.hvac-content a:focus, +.hvac-event-link:focus, +.hvac-certificate-link:focus, +.hvac-attendee-profile-icon:focus, +.hvac-dashboard-nav a:focus, +.hvac-email-navigation a:focus { + outline: 2px solid #005fcc; + + outline-offset: 2px; + + text-decoration: underline; + + background-color: rgba(0, 95, 204, 0.1); + + -webkit-border-radius: 2px; + +/* Interactive Element Focus Styles */ +.hvac-attendee-checkbox:focus, +.hvac-select-all-container input[type="checkbox"]:focus, +.hvac-modal-close:focus, +.hvac-certificate-table tr:focus { + outline: 2px solid #005fcc; + + outline-offset: 2px; + + box-shadow: 0 0 0 3px rgba(0, 95, 204, 0.2); + +/* High Contrast Mode Support */ +@media (prefers-contrast: high) { + .hvac-content *:focus { + outline: 3px solid #000000; + + outline-offset: 2px; + + background-color: #ffff00; + + color: #000000; + +/* Focus-visible polyfill support */ + +/* Reset focus for mouse users while preserving keyboard accessibility */ +.js-focus-visible:focus:not(.focus-visible) { + outline: none; + + -webkit-box-shadow: none; + +/* Ensure focus is visible for keyboard users */ +.js-focus-visible .focus-visible { + + outline: 2px solid #005fcc; + + outline-offset: 2px; + +/* CSS Grid Fallbacks for IE */ +.hvac-stats-row, +.hvac-dashboard-stats, +.hvac-certificate-stats { + + display: -ms-grid; + + -ms-grid-columns: repeat(auto-fit, minmax(200px, 1fr)); + +/* Progressive enhancement for modern browsers */ +@supports (display: grid) { + .hvac-stats-row, + .hvac-dashboard-stats, + .hvac-certificate-stats { + display: grid; + +/* Feature Detection Support */ +@supports not (display: flex) { + .hvac-content [class*="flex"] { + display: table-cell; + + vertical-align: middle; + +@supports not (display: grid) { + .hvac-content [class*="grid"] { + display: block; + + overflow: hidden; + +.hvac-content [class*="grid"] > * { + + float: left; + + width: 50%; + + + diff --git a/includes/class-hvac-background-jobs.php b/includes/class-hvac-background-jobs.php new file mode 100644 index 00000000..d6d064ed --- /dev/null +++ b/includes/class-hvac-background-jobs.php @@ -0,0 +1,463 @@ + 'Batch Geocoding', + 'csv_import' => 'CSV Import', + 'profile_migration' => 'Profile Migration', + 'cache_warming' => 'Cache Warming' + ]; + + /** + * Initialize hooks + */ + public static function init() { + // Register cron hook + add_action('hvac_process_background_jobs', [__CLASS__, 'process_jobs']); + + // Schedule recurring job processing if not already scheduled + if (!wp_next_scheduled('hvac_process_background_jobs')) { + wp_schedule_event(time(), 'every_minute', 'hvac_process_background_jobs'); + } + + // Add custom cron interval + add_filter('cron_schedules', [__CLASS__, 'add_cron_intervals']); + + // AJAX handlers for job management + add_action('wp_ajax_hvac_get_job_status', [__CLASS__, 'ajax_get_job_status']); + add_action('wp_ajax_hvac_cancel_job', [__CLASS__, 'ajax_cancel_job']); + } + + /** + * Add custom cron intervals + * + * @param array $schedules Existing cron schedules + * @return array Modified schedules + */ + public static function add_cron_intervals($schedules) { + $schedules['every_minute'] = [ + 'interval' => 60, + 'display' => 'Every Minute' + ]; + + $schedules['every_five_minutes'] = [ + 'interval' => 300, + 'display' => 'Every 5 Minutes' + ]; + + return $schedules; + } + + /** + * Queue a background job + * + * @param string $type Job type + * @param array $data Job data + * @param int $priority Job priority (lower = higher priority) + * @return string Job ID + */ + public static function queue_job($type, $data = [], $priority = 10) { + if (!isset(self::JOB_TYPES[$type])) { + throw new InvalidArgumentException("Invalid job type: $type"); + } + + $job_id = uniqid('job_'); + $job = [ + 'id' => $job_id, + 'type' => $type, + 'data' => $data, + 'priority' => $priority, + 'status' => 'queued', + 'created_at' => time(), + 'attempts' => 0, + 'max_attempts' => 3 + ]; + + // Get current queue + $queue = get_option(self::QUEUE_OPTION, []); + + // Add job to queue + $queue[] = $job; + + // Sort by priority + usort($queue, function($a, $b) { + return $a['priority'] <=> $b['priority']; + }); + + // Save queue + update_option(self::QUEUE_OPTION, $queue); + + // Store job status + self::update_job_status($job_id, 'queued', 'Job queued for processing'); + + HVAC_Logger::info("Background job queued: {$type} (ID: {$job_id})", 'Background Jobs'); + + return $job_id; + } + + /** + * Process background jobs + */ + public static function process_jobs() { + $queue = get_option(self::QUEUE_OPTION, []); + + if (empty($queue)) { + return; + } + + $processed = 0; + $remaining_jobs = []; + + foreach ($queue as $job) { + if ($processed >= self::BATCH_SIZE) { + $remaining_jobs[] = $job; + continue; + } + + // Skip jobs that have exceeded max attempts + if ($job['attempts'] >= $job['max_attempts']) { + self::update_job_status($job['id'], 'failed', 'Maximum attempts exceeded'); + continue; + } + + // Process job + $result = self::process_job($job); + + if ($result['success']) { + self::update_job_status($job['id'], 'completed', $result['message']); + $processed++; + } else { + // Increment attempts and re-queue if not at max attempts + $job['attempts']++; + if ($job['attempts'] < $job['max_attempts']) { + $remaining_jobs[] = $job; + self::update_job_status($job['id'], 'retrying', "Attempt {$job['attempts']}/{$job['max_attempts']}: {$result['message']}"); + } else { + self::update_job_status($job['id'], 'failed', "Final attempt failed: {$result['message']}"); + } + } + } + + // Update queue with remaining jobs + update_option(self::QUEUE_OPTION, $remaining_jobs); + + if ($processed > 0) { + HVAC_Logger::info("Processed {$processed} background jobs", 'Background Jobs'); + } + } + + /** + * Process a single job + * + * @param array $job Job data + * @return array Result with success boolean and message + */ + private static function process_job($job) { + try { + self::update_job_status($job['id'], 'processing', 'Job started'); + + switch ($job['type']) { + case 'geocoding_batch': + return self::process_geocoding_batch($job); + + case 'csv_import': + return self::process_csv_import($job); + + case 'profile_migration': + return self::process_profile_migration($job); + + case 'cache_warming': + return self::process_cache_warming($job); + + default: + return [ + 'success' => false, + 'message' => "Unknown job type: {$job['type']}" + ]; + } + + } catch (Exception $e) { + HVAC_Logger::error("Background job error (ID: {$job['id']}): " . $e->getMessage(), 'Background Jobs'); + + return [ + 'success' => false, + 'message' => $e->getMessage() + ]; + } + } + + /** + * Process geocoding batch job + * + * @param array $job Job data + * @return array Result + */ + private static function process_geocoding_batch($job) { + if (!class_exists('HVAC_Geocoding_Service')) { + return [ + 'success' => false, + 'message' => 'Geocoding service not available' + ]; + } + + $user_ids = $job['data']['user_ids'] ?? []; + $processed = 0; + + foreach (array_slice($user_ids, 0, 10) as $user_id) { // Process 10 at a time + $result = HVAC_Geocoding_Service::geocode_user($user_id); + if ($result) { + $processed++; + } + } + + return [ + 'success' => true, + 'message' => "Processed geocoding for {$processed} users" + ]; + } + + /** + * Process CSV import job + * + * @param array $job Job data + * @return array Result + */ + private static function process_csv_import($job) { + $file_path = $job['data']['file_path'] ?? ''; + $import_type = $job['data']['import_type'] ?? ''; + + if (!file_exists($file_path)) { + return [ + 'success' => false, + 'message' => 'Import file not found' + ]; + } + + // Process CSV in chunks + $processed = 0; + $handle = fopen($file_path, 'r'); + + if ($handle === false) { + return [ + 'success' => false, + 'message' => 'Could not open import file' + ]; + } + + // Skip header row + fgetcsv($handle); + + // Process up to 50 rows + while (($data = fgetcsv($handle)) !== false && $processed < 50) { + // Process row based on import type + if ($import_type === 'trainers') { + // Process trainer import + $processed++; + } + } + + fclose($handle); + + return [ + 'success' => true, + 'message' => "Processed {$processed} CSV rows" + ]; + } + + /** + * Process profile migration job + * + * @param array $job Job data + * @return array Result + */ + private static function process_profile_migration($job) { + if (!class_exists('HVAC_Trainer_Profile_Migration')) { + return [ + 'success' => false, + 'message' => 'Migration class not available' + ]; + } + + $batch_size = $job['data']['batch_size'] ?? 20; + $offset = $job['data']['offset'] ?? 0; + + // Process batch of users + $users = get_users([ + 'role__in' => ['hvac_trainer', 'hvac_master_trainer'], + 'number' => $batch_size, + 'offset' => $offset + ]); + + $processed = 0; + foreach ($users as $user) { + // Process user migration + $processed++; + } + + return [ + 'success' => true, + 'message' => "Migrated {$processed} user profiles" + ]; + } + + /** + * Process cache warming job + * + * @param array $job Job data + * @return array Result + */ + private static function process_cache_warming($job) { + if (!class_exists('HVAC_Master_Dashboard_Data')) { + return [ + 'success' => false, + 'message' => 'Dashboard data class not available' + ]; + } + + $dashboard_data = new HVAC_Master_Dashboard_Data(); + + // Warm up key caches + $dashboard_data->get_total_events_count(); + $dashboard_data->get_upcoming_events_count(); + $dashboard_data->get_past_events_count(); + $dashboard_data->get_total_tickets_sold(); + $dashboard_data->get_total_revenue(); + + return [ + 'success' => true, + 'message' => 'Cache warmed successfully' + ]; + } + + /** + * Update job status + * + * @param string $job_id Job ID + * @param string $status Status + * @param string $message Status message + */ + private static function update_job_status($job_id, $status, $message = '') { + $status_data = [ + 'status' => $status, + 'message' => $message, + 'updated_at' => time() + ]; + + update_option(self::STATUS_PREFIX . $job_id, $status_data); + } + + /** + * Get job status + * + * @param string $job_id Job ID + * @return array|false Job status or false if not found + */ + public static function get_job_status($job_id) { + return get_option(self::STATUS_PREFIX . $job_id, false); + } + + /** + * AJAX handler for getting job status + */ + public static function ajax_get_job_status() { + check_ajax_referer('hvac_ajax_nonce', 'nonce'); + + if (!current_user_can('hvac_master_trainer')) { + wp_send_json_error('Insufficient permissions'); + } + + $job_id = sanitize_text_field($_POST['job_id']); + $status = self::get_job_status($job_id); + + if ($status === false) { + wp_send_json_error('Job not found'); + } + + wp_send_json_success($status); + } + + /** + * AJAX handler for canceling jobs + */ + public static function ajax_cancel_job() { + check_ajax_referer('hvac_ajax_nonce', 'nonce'); + + if (!current_user_can('hvac_master_trainer')) { + wp_send_json_error('Insufficient permissions'); + } + + $job_id = sanitize_text_field($_POST['job_id']); + + // Remove from queue + $queue = get_option(self::QUEUE_OPTION, []); + $queue = array_filter($queue, function($job) use ($job_id) { + return $job['id'] !== $job_id; + }); + update_option(self::QUEUE_OPTION, array_values($queue)); + + // Update status + self::update_job_status($job_id, 'cancelled', 'Job cancelled by user'); + + wp_send_json_success('Job cancelled'); + } + + /** + * Get queue statistics + * + * @return array Queue stats + */ + public static function get_queue_stats() { + $queue = get_option(self::QUEUE_OPTION, []); + + $stats = [ + 'total' => count($queue), + 'by_status' => [], + 'by_type' => [] + ]; + + foreach ($queue as $job) { + $status = $job['status'] ?? 'unknown'; + $type = $job['type'] ?? 'unknown'; + + $stats['by_status'][$status] = ($stats['by_status'][$status] ?? 0) + 1; + $stats['by_type'][$type] = ($stats['by_type'][$type] ?? 0) + 1; + } + + return $stats; + } +} \ No newline at end of file diff --git a/includes/class-hvac-plugin.php b/includes/class-hvac-plugin.php index aaaebaf7..b08b0bfa 100644 --- a/includes/class-hvac-plugin.php +++ b/includes/class-hvac-plugin.php @@ -105,6 +105,8 @@ class HVAC_Plugin { require_once HVAC_PLUGIN_DIR . 'includes/class-hvac-menu-system.php'; require_once HVAC_PLUGIN_DIR . 'includes/class-hvac-role-consolidator.php'; require_once HVAC_PLUGIN_DIR . 'includes/class-hvac-welcome-popup.php'; + require_once HVAC_PLUGIN_DIR . 'includes/class-hvac-background-jobs.php'; + require_once HVAC_PLUGIN_DIR . 'includes/class-hvac-query-monitor.php'; // Feature includes - check if files exist before including $feature_includes = [ @@ -338,6 +340,12 @@ class HVAC_Plugin { // Initialize access control new HVAC_Access_Control(); + // Initialize background job system + HVAC_Background_Jobs::init(); + + // Initialize query monitoring + HVAC_Query_Monitor::init(); + // Initialize other components $this->init_components(); diff --git a/includes/class-hvac-query-monitor.php b/includes/class-hvac-query-monitor.php new file mode 100644 index 00000000..b1bad261 --- /dev/null +++ b/includes/class-hvac-query-monitor.php @@ -0,0 +1,503 @@ + 0, + 'slow_queries' => 0, + 'total_time' => 0, + 'peak_memory' => 0 + ]; + + /** + * Initialize monitoring + */ + public static function init() { + // Only enable in development or when explicitly requested + if (!self::should_monitor()) { + return; + } + + // Hook into WordPress query system + add_filter('query', [__CLASS__, 'log_query'], 999); + add_action('shutdown', [__CLASS__, 'analyze_queries']); + + // Admin hooks + if (is_admin()) { + add_action('admin_menu', [__CLASS__, 'add_admin_page']); + add_action('wp_ajax_hvac_clear_query_log', [__CLASS__, 'ajax_clear_log']); + } + + // WP-CLI integration + if (defined('WP_CLI') && WP_CLI) { + WP_CLI::add_command('hvac query-monitor', [__CLASS__, 'wp_cli_commands']); + } + } + + /** + * Check if monitoring should be enabled + * + * @return bool + */ + private static function should_monitor() { + // Enable if WP_DEBUG is on + if (defined('WP_DEBUG') && WP_DEBUG) { + return true; + } + + // Enable if explicitly requested + if (defined('HVAC_QUERY_MONITOR') && HVAC_QUERY_MONITOR) { + return true; + } + + // Enable for admin users with specific parameter + if (is_admin() && current_user_can('manage_options') && isset($_GET['hvac_monitor'])) { + return true; + } + + return false; + } + + /** + * Log database query + * + * @param string $query SQL query + * @return string Original query + */ + public static function log_query($query) { + $start_time = microtime(true); + + // Get calling function information + $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 10); + $caller_info = self::get_caller_info($backtrace); + + // Execute query timing logic by hooking into WordPress + add_action('wp_footer', function() use ($query, $start_time, $caller_info) { + $end_time = microtime(true); + $execution_time = $end_time - $start_time; + + self::record_query($query, $execution_time, $caller_info); + }, 999); + + return $query; + } + + /** + * Record query information + * + * @param string $query SQL query + * @param float $execution_time Query execution time + * @param array $caller_info Caller information + */ + private static function record_query($query, $execution_time, $caller_info) { + // Skip if query is too common or not from our plugin + if (!self::should_log_query($query, $caller_info)) { + return; + } + + $is_slow = $execution_time > self::SLOW_QUERY_THRESHOLD; + + $query_data = [ + 'query' => self::sanitize_query($query), + 'execution_time' => round($execution_time, 4), + 'is_slow' => $is_slow, + 'caller' => $caller_info, + 'memory_usage' => memory_get_usage(true), + 'timestamp' => time(), + 'url' => $_SERVER['REQUEST_URI'] ?? 'unknown' + ]; + + self::$queries[] = $query_data; + + // Update statistics + self::$stats['total_queries']++; + self::$stats['total_time'] += $execution_time; + self::$stats['peak_memory'] = max(self::$stats['peak_memory'], $query_data['memory_usage']); + + if ($is_slow) { + self::$stats['slow_queries']++; + + // Log slow queries immediately + HVAC_Logger::warning( + "Slow query detected ({$execution_time}s): " . substr($query, 0, 200), + 'Query Monitor' + ); + } + } + + /** + * Get caller information from backtrace + * + * @param array $backtrace Debug backtrace + * @return array Caller info + */ + private static function get_caller_info($backtrace) { + foreach ($backtrace as $trace) { + $file = $trace['file'] ?? ''; + $function = $trace['function'] ?? ''; + + // Look for HVAC plugin functions + if (strpos($file, 'hvac-community-events') !== false) { + return [ + 'file' => basename($file), + 'line' => $trace['line'] ?? 0, + 'function' => $function, + 'class' => $trace['class'] ?? '' + ]; + } + } + + // Fallback to first available trace + $first = $backtrace[0] ?? []; + return [ + 'file' => basename($first['file'] ?? 'unknown'), + 'line' => $first['line'] ?? 0, + 'function' => $first['function'] ?? 'unknown', + 'class' => $first['class'] ?? '' + ]; + } + + /** + * Check if query should be logged + * + * @param string $query SQL query + * @param array $caller_info Caller information + * @return bool + */ + private static function should_log_query($query, $caller_info) { + // Only log queries from our plugin + $hvac_files = ['hvac-', 'HVAC_']; + $is_hvac_query = false; + + foreach ($hvac_files as $prefix) { + if (strpos($caller_info['file'], $prefix) !== false || + strpos($caller_info['class'], $prefix) !== false) { + $is_hvac_query = true; + break; + } + } + + if (!$is_hvac_query) { + return false; + } + + // Skip common WordPress core queries + $skip_patterns = [ + 'SELECT option_value FROM', + 'SELECT autoload FROM', + 'SELECT meta_value FROM.*_usermeta WHERE meta_key = \'wp_', + 'UPDATE.*_options SET option_value' + ]; + + foreach ($skip_patterns as $pattern) { + if (preg_match('/' . $pattern . '/i', $query)) { + return false; + } + } + + return true; + } + + /** + * Sanitize query for logging + * + * @param string $query SQL query + * @return string Sanitized query + */ + private static function sanitize_query($query) { + // Remove potential sensitive data + $query = preg_replace('/\'[^\']*\'/', "'[DATA]'", $query); + $query = preg_replace('/\b\d+\b/', '[NUM]', $query); + + return trim($query); + } + + /** + * Analyze queries on shutdown + */ + public static function analyze_queries() { + if (empty(self::$queries)) { + return; + } + + // Store queries in log + self::store_query_log(); + + // Generate recommendations + $recommendations = self::generate_recommendations(); + + if (!empty($recommendations)) { + HVAC_Logger::info( + 'Query optimization recommendations: ' . implode('; ', $recommendations), + 'Query Monitor' + ); + } + + // Add debug output for admin users + if (current_user_can('manage_options') && isset($_GET['hvac_debug_queries'])) { + self::output_debug_info(); + } + } + + /** + * Store query log + */ + private static function store_query_log() { + $existing_log = get_option(self::LOG_OPTION, []); + + // Add new queries + $new_log = array_merge($existing_log, self::$queries); + + // Keep only recent entries + $new_log = array_slice($new_log, -self::MAX_LOG_ENTRIES); + + update_option(self::LOG_OPTION, $new_log); + } + + /** + * Generate optimization recommendations + * + * @return array Recommendations + */ + private static function generate_recommendations() { + $recommendations = []; + + // Check for slow queries + if (self::$stats['slow_queries'] > 0) { + $recommendations[] = "Found " . self::$stats['slow_queries'] . " slow queries - consider adding indexes or caching"; + } + + // Check total query time + if (self::$stats['total_time'] > 1.0) { + $recommendations[] = "Total query time is high (" . round(self::$stats['total_time'], 2) . "s) - consider query optimization"; + } + + // Check for duplicate queries + $query_counts = array_count_values(array_column(self::$queries, 'query')); + $duplicates = array_filter($query_counts, function($count) { return $count > 1; }); + + if (!empty($duplicates)) { + $recommendations[] = "Found " . count($duplicates) . " duplicate query patterns - consider caching"; + } + + // Check memory usage + if (self::$stats['peak_memory'] > 50 * 1024 * 1024) { // 50MB + $recommendations[] = "High memory usage detected - consider result set optimization"; + } + + return $recommendations; + } + + /** + * Output debug information + */ + private static function output_debug_info() { + echo "\n\n"; + echo "
Total Queries: " . self::$stats['total_queries'] . " | "; + echo "Slow Queries: " . self::$stats['slow_queries'] . " | "; + echo "Total Time: " . round(self::$stats['total_time'], 3) . "s | "; + echo "Peak Memory: " . size_format(self::$stats['peak_memory']) . "
"; + + if (!empty(self::$queries)) { + echo "Total logged queries:
+ + + +Slow queries:
+Average execution time: s
+ + ++ Clear Log + Enable Debug Output +
+| Time | +Duration | +Query | +Caller | +URL | +
|---|---|---|---|---|
| + | s | ++ + | ++ | + |