Compare commits

..

5 commits

Author SHA1 Message Date
ben
0b033f7f4f docs: condense Status.md from 1348 to 131 lines
Some checks are pending
HVAC Plugin CI/CD Pipeline / Security Analysis (push) Waiting to run
HVAC Plugin CI/CD Pipeline / Code Quality & Standards (push) Waiting to run
HVAC Plugin CI/CD Pipeline / Unit Tests (push) Waiting to run
HVAC Plugin CI/CD Pipeline / Integration Tests (push) Waiting to run
HVAC Plugin CI/CD Pipeline / Deploy to Staging (push) Blocked by required conditions
HVAC Plugin CI/CD Pipeline / Deploy to Production (push) Blocked by required conditions
HVAC Plugin CI/CD Pipeline / Notification (push) Blocked by required conditions
Security Monitoring & Compliance / Secrets & Credential Scan (push) Waiting to run
Security Monitoring & Compliance / WordPress Security Analysis (push) Waiting to run
Security Monitoring & Compliance / Dependency Vulnerability Scan (push) Waiting to run
Security Monitoring & Compliance / Static Code Security Analysis (push) Waiting to run
Security Monitoring & Compliance / Security Compliance Validation (push) Waiting to run
Security Monitoring & Compliance / Security Summary Report (push) Blocked by required conditions
Security Monitoring & Compliance / Security Team Notification (push) Blocked by required conditions
Compress previous session entries to one-line summaries, remove
duplicate info already in CLAUDE.md (guidelines, architecture, testing).
Keep current session detail and previous session git hashes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 19:27:47 -04:00
ben
95382ac3a3 feat(find-training): Refactor marker visibility dots into inline tab checkboxes
Replace standalone colored dot toggles with custom checkboxes inline
beside each category tab heading (Events, Trainers, Venues). Version
bump 2.2.17 → 2.2.18 for CDN cache busting.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 19:19:58 -04:00
ben
9dbe472c45 feat(find-training): Reorder tabs (Events first), add marker highlighting and map reset
- Move Events tab to first/default position, update search placeholder and
  skip link to match
- Add highlighted icon variants for all marker types (larger scale, brighter
  stroke, higher zIndex) that activate when their category tab is selected
- Add reset map button (bottom-right) to restore original zoom/bounds
- Fix mobile sidebar overflow caused by long event titles pushing beyond
  viewport (overflow-x: hidden, overflow-y: visible)
- Bump version to 2.2.17

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 18:53:44 -04:00
ben
4104c80669 fix(find-training): Fix mobile scrolling on Find Training page
On mobile, the page used overflow:hidden + height:100vh with nested
scrolling in a 158px sidebar area, making it impossible to scroll
through trainer/venue/event cards. Switched mobile layout to natural
page scrolling with sticky filter bar. Desktop layout unchanged.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 18:01:03 -04:00
ben
f123c7a513 fix(find-training): Fix map tile drift at low zoom and event cost HTML entities
Add minZoom: 3 to prevent Google Maps WebGL tile/marker drift caused by
viewport width mismatch in CSS Grid layouts. Fix event cost displaying
&#x24; instead of $ by decoding HTML entities before passing to JS.
Bump version to 2.2.13 for CDN cache busting.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 17:32:17 -04:00
6 changed files with 390 additions and 1184 deletions

1162
Status.md

File diff suppressed because it is too large Load diff

View file

@ -576,6 +576,15 @@ body .hvac-find-training-page {
background: #f0f0f0; background: #f0f0f0;
} }
/* Override global img { max-width: 100% } from theme/WP that shrinks
Google Maps tile images, causing markers to drift from tiles on zoom.
See: https://developers.google.com/maps/documentation/javascript/best-practices */
#hvac-training-map img,
#hvac-training-map .gm-style img {
max-width: none !important;
max-height: none !important;
}
/* Map Loading State */ /* Map Loading State */
.hvac-map-loading { .hvac-map-loading {
display: flex; display: flex;
@ -659,6 +668,37 @@ body .hvac-find-training-page {
background: var(--hvac-event-color); background: var(--hvac-event-color);
} }
/* Reset Map View Button */
.hvac-reset-map-btn {
position: absolute;
bottom: 24px;
right: 12px;
width: 36px;
height: 36px;
background: #fff;
border: none;
border-radius: 6px;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
cursor: pointer;
z-index: 10;
display: flex;
align-items: center;
justify-content: center;
transition: background 0.2s, box-shadow 0.2s;
}
.hvac-reset-map-btn:hover {
background: #f0f0f0;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.25);
}
.hvac-reset-map-btn .dashicons {
font-size: 18px;
width: 18px;
height: 18px;
color: var(--hvac-text-muted);
}
/* Map Toggles Overlay */ /* Map Toggles Overlay */
.hvac-map-toggles { .hvac-map-toggles {
position: absolute; position: absolute;
@ -1220,19 +1260,49 @@ body .hvac-find-training-page {
--hvac-sidebar-width: 320px; --hvac-sidebar-width: 320px;
} }
/* On mobile/tablet, allow natural page scrolling instead of fixed viewport */
.hvac-find-training-page {
height: auto !important;
overflow: visible !important;
}
/* Keep filter bar visible while scrolling */
.hvac-filter-bar {
position: sticky;
top: 0;
z-index: 100;
}
.admin-bar .hvac-filter-bar {
top: 46px;
}
/* Switch to stacked layout - map on top, sidebar below */ /* Switch to stacked layout - map on top, sidebar below */
.hvac-map-layout { .hvac-map-layout {
grid-template-areas: grid-template-areas:
"map" "map"
"sidebar" !important; "sidebar" !important;
grid-template-columns: 1fr !important; grid-template-columns: 1fr !important;
grid-template-rows: 55vh 1fr !important; grid-template-rows: 55vh auto !important;
overflow: visible !important;
}
/* Map needs explicit height since page no longer constrains it */
.hvac-map-container {
min-height: 300px;
} }
.hvac-sidebar { .hvac-sidebar {
border-right: none; border-right: none;
border-top: 1px solid var(--hvac-border); border-top: 1px solid var(--hvac-border);
max-height: 45vh; overflow-x: hidden !important;
overflow-y: visible !important;
max-height: none !important;
}
/* Sidebar content scrolls with page, not independently */
.hvac-sidebar-content {
overflow-y: visible !important;
} }
/* Show sidebar toggle on tablet/mobile */ /* Show sidebar toggle on tablet/mobile */
@ -1243,7 +1313,7 @@ body .hvac-find-training-page {
/* Collapsible sidebar */ /* Collapsible sidebar */
.hvac-sidebar.collapsed { .hvac-sidebar.collapsed {
max-height: 80px; max-height: 80px;
overflow: hidden; overflow: hidden !important;
} }
.hvac-sidebar.collapsed .hvac-sidebar-content { .hvac-sidebar.collapsed .hvac-sidebar-content {
@ -1292,14 +1362,20 @@ body .hvac-find-training-page {
font-size: 12px; font-size: 12px;
} }
.hvac-visibility-toggles { .hvac-marker-checkbox {
gap: 8px; width: 12px;
padding: 4px 0; height: 12px;
} }
.hvac-toggle-dot { .hvac-marker-checkbox::after {
width: 14px; left: 2px;
height: 14px; top: 0px;
width: 3px;
height: 6px;
}
.hvac-marker-toggle {
margin-right: 2px;
} }
} }
@ -1309,11 +1385,7 @@ body .hvac-find-training-page {
@media (max-width: 767px) { @media (max-width: 767px) {
.hvac-map-layout { .hvac-map-layout {
grid-template-rows: 50vh 1fr; grid-template-rows: 50vh auto;
}
.hvac-sidebar {
max-height: 50vh;
} }
/* Filter bar adjustments */ /* Filter bar adjustments */
@ -1382,7 +1454,7 @@ body .hvac-find-training-page {
font-size: 11px; font-size: 11px;
} }
.hvac-visibility-toggles { .hvac-marker-toggle {
display: none; display: none;
} }
@ -1443,11 +1515,7 @@ body .hvac-find-training-page {
@media (max-width: 480px) { @media (max-width: 480px) {
.hvac-map-layout { .hvac-map-layout {
grid-template-rows: 45vh 1fr; grid-template-rows: 45vh auto;
}
.hvac-sidebar {
max-height: 55vh;
} }
.hvac-map-legend { .hvac-map-legend {
@ -1702,10 +1770,12 @@ body .hvac-find-training-page {
border-bottom: 2px solid var(--hvac-border); border-bottom: 2px solid var(--hvac-border);
margin: 0 -16px; margin: 0 -16px;
padding: 0 16px; padding: 0 16px;
overflow: hidden;
} }
.hvac-tab { .hvac-tab {
flex: 1; flex: 1;
min-width: 0;
padding: 10px 8px; padding: 10px 8px;
background: transparent; background: transparent;
border: none; border: none;
@ -1717,6 +1787,8 @@ body .hvac-find-training-page {
cursor: pointer; cursor: pointer;
transition: all 0.2s; transition: all 0.2s;
white-space: nowrap; white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
} }
.hvac-tab:hover { .hvac-tab:hover {
@ -1738,57 +1810,71 @@ body .hvac-find-training-page {
font-weight: 600; font-weight: 600;
} }
/* Visibility Toggles */ /* Marker Toggle Checkboxes (inline in tab buttons) */
.hvac-visibility-toggles { .hvac-marker-toggle {
display: flex; display: inline-flex;
align-items: center;
gap: 12px;
padding: 8px 0;
}
.hvac-visibility-toggle {
display: flex;
align-items: center; align-items: center;
cursor: pointer; cursor: pointer;
margin-right: 4px;
vertical-align: middle;
position: relative;
} }
.hvac-visibility-toggle input { .hvac-marker-toggle input {
position: absolute; position: absolute;
opacity: 0; opacity: 0;
width: 0; width: 0;
height: 0; height: 0;
} }
.hvac-toggle-dot { .hvac-marker-checkbox {
width: 18px; width: 14px;
height: 18px; height: 14px;
border-radius: 50%; border: 2px solid #999;
border: 2px solid; border-radius: 3px;
transition: all 0.2s; display: inline-block;
position: relative;
transition: all 0.15s ease;
background: #fff;
flex-shrink: 0;
} }
.hvac-toggle-trainer { .hvac-marker-checkbox::after {
background: var(--hvac-trainer-color); content: '';
border-color: #5a8a1a; position: absolute;
left: 3px;
top: 0px;
width: 4px;
height: 8px;
border: solid #fff;
border-width: 0 2px 2px 0;
transform: rotate(45deg);
opacity: 0;
transition: opacity 0.15s ease;
} }
.hvac-toggle-venue { .hvac-marker-toggle input:checked + .hvac-marker-checkbox::after {
opacity: 1;
}
/* Category-specific checkbox colors */
.hvac-marker-toggle-trainer input:checked + .hvac-marker-checkbox {
background: #6aad1e;
border-color: #6aad1e;
}
.hvac-marker-toggle-venue input:checked + .hvac-marker-checkbox {
background: var(--hvac-venue-color); background: var(--hvac-venue-color);
border-color: #6fa024; border-color: var(--hvac-venue-color);
} }
.hvac-toggle-event { .hvac-marker-toggle-event input:checked + .hvac-marker-checkbox {
background: var(--hvac-event-color); background: var(--hvac-event-color);
border-color: #0a9a8a; border-color: var(--hvac-event-color);
} }
.hvac-visibility-toggle input:not(:checked) + .hvac-toggle-dot { .hvac-marker-toggle:hover .hvac-marker-checkbox {
background: #f5f5f5; border-color: #666;
border-color: #ccc;
}
.hvac-visibility-toggle:hover .hvac-toggle-dot {
transform: scale(1.1);
} }
/* Tab Panels */ /* Tab Panels */

View file

@ -39,7 +39,7 @@
visibleEvents: [], visibleEvents: [],
// Active tab // Active tab
activeTab: 'trainers', activeTab: 'events',
// Items per page for load more // Items per page for load more
itemsPerPage: 6, itemsPerPage: 6,
@ -110,6 +110,7 @@
// Initialize responsive features // Initialize responsive features
this.handleWindowResize(); this.handleWindowResize();
this.initSidebarToggle(); this.initSidebarToggle();
}, },
/** /**
@ -169,9 +170,13 @@
mapElement.innerHTML = ''; mapElement.innerHTML = '';
// Create map with options // Create map with options
// minZoom: 3 prevents tile/marker drift at low zoom levels caused by
// Google Maps WebGL renderer reading viewport width instead of container width
// in CSS Grid layouts. Drift is invisible at zoom 4+ but severe at zoom 1-2.
this.map = new google.maps.Map(mapElement, { this.map = new google.maps.Map(mapElement, {
center: this.config.defaultCenter, center: this.config.defaultCenter,
zoom: this.config.defaultZoom, zoom: this.config.defaultZoom,
minZoom: 3,
mapTypeControl: true, mapTypeControl: true,
mapTypeControlOptions: { mapTypeControlOptions: {
position: google.maps.ControlPosition.TOP_RIGHT position: google.maps.ControlPosition.TOP_RIGHT
@ -322,6 +327,9 @@
// Fit bounds if we have markers // Fit bounds if we have markers
this.fitBounds(); this.fitBounds();
// Highlight markers for active tab
this.highlightMarkersForTab(this.activeTab);
}, },
/** /**
@ -505,6 +513,122 @@
}; };
}, },
/**
* Get highlighted trainer marker icon (larger, brighter stroke)
*/
getTrainerIconHighlighted: function() {
if (hvacFindTraining.marker_icons?.trainer) {
return {
url: hvacFindTraining.marker_icons.trainer,
scaledSize: new google.maps.Size(40, 40),
anchor: new google.maps.Point(20, 20)
};
}
return {
path: google.maps.SymbolPath.CIRCLE,
fillColor: '#f0f7e8',
fillOpacity: 1,
strokeColor: '#3d6e00',
strokeWeight: 3,
scale: 14
};
},
/**
* Get highlighted champion marker icon (larger, brighter stroke)
*/
getChampionIconHighlighted: function() {
return {
path: google.maps.SymbolPath.CIRCLE,
fillColor: '#f0f7e8',
fillOpacity: 1,
strokeColor: '#e0e0e0',
strokeWeight: 3.5,
scale: 14
};
},
/**
* Get highlighted venue marker icon (larger, brighter stroke)
*/
getVenueIconHighlighted: function() {
if (hvacFindTraining.marker_icons?.venue) {
return {
url: hvacFindTraining.marker_icons.venue,
scaledSize: new google.maps.Size(40, 40),
anchor: new google.maps.Point(20, 20)
};
}
return {
path: google.maps.SymbolPath.BACKWARD_CLOSED_ARROW,
fillColor: '#89c92e',
fillOpacity: 1,
strokeColor: '#e0e0e0',
strokeWeight: 3,
scale: 8
};
},
/**
* Get highlighted event marker icon (larger, brighter stroke)
*/
getEventIconHighlighted: function() {
if (hvacFindTraining.marker_icons?.event) {
return {
url: hvacFindTraining.marker_icons.event,
scaledSize: new google.maps.Size(40, 40),
anchor: new google.maps.Point(20, 20)
};
}
return {
path: google.maps.SymbolPath.CIRCLE,
fillColor: '#0ebaa6',
fillOpacity: 1,
strokeColor: '#e0e0e0',
strokeWeight: 3,
scale: 11
};
},
/**
* Highlight markers for the active tab category.
* Active tab markers get larger icons + higher zIndex.
* Inactive tab markers revert to normal icons + default zIndex.
*/
highlightMarkersForTab: function(tab) {
if (!this.map) return;
const DEFAULT_Z = 1;
const HIGHLIGHT_Z = 100;
// Trainer markers
const trainerHighlighted = (tab === 'trainers');
this.trainerMarkers.forEach(function(marker) {
const isChampion = marker.trainerData && marker.trainerData.is_champion;
if (trainerHighlighted) {
marker.setIcon(isChampion ? HVACTrainingMap.getChampionIconHighlighted() : HVACTrainingMap.getTrainerIconHighlighted());
marker.setZIndex(HIGHLIGHT_Z);
} else {
marker.setIcon(isChampion ? HVACTrainingMap.getChampionIcon() : HVACTrainingMap.getTrainerIcon());
marker.setZIndex(DEFAULT_Z);
}
});
// Venue markers
const venueHighlighted = (tab === 'venues');
this.venueMarkers.forEach(function(marker) {
marker.setIcon(venueHighlighted ? HVACTrainingMap.getVenueIconHighlighted() : HVACTrainingMap.getVenueIcon());
marker.setZIndex(venueHighlighted ? HIGHLIGHT_Z : DEFAULT_Z);
});
// Event markers
const eventHighlighted = (tab === 'events');
this.eventMarkers.forEach(function(marker) {
marker.setIcon(eventHighlighted ? HVACTrainingMap.getEventIconHighlighted() : HVACTrainingMap.getEventIcon());
marker.setZIndex(eventHighlighted ? HIGHLIGHT_Z : DEFAULT_Z);
});
},
/** /**
* Initialize marker clustering * Initialize marker clustering
*/ */
@ -1152,6 +1276,11 @@
$('#hvac-load-more').on('click', function() { $('#hvac-load-more').on('click', function() {
self.loadMoreTrainers(); self.loadMoreTrainers();
}); });
// Reset map view button
$('#hvac-reset-map').on('click', function() {
self.resetMapView();
});
}, },
/** /**
@ -1357,6 +1486,14 @@
this.map.setZoom(zoom || 10); this.map.setZoom(zoom || 10);
}, },
/**
* Reset map to fit all markers (or default view if none)
*/
resetMapView: function() {
if (!this.map) return;
this.fitBounds();
},
/** /**
* Sync sidebar lists with visible map viewport * Sync sidebar lists with visible map viewport
*/ */
@ -1412,14 +1549,16 @@
* Show loading state * Show loading state
*/ */
showLoading: function() { showLoading: function() {
$('#hvac-trainer-grid').html('<div class="hvac-grid-loading"><span class="dashicons dashicons-update-alt hvac-spin"></span> Loading trainers...</div>'); const gridId = this.activeTab === 'events' ? '#hvac-event-grid' : this.activeTab === 'venues' ? '#hvac-venue-grid' : '#hvac-trainer-grid';
const label = this.activeTab === 'events' ? 'events' : this.activeTab === 'venues' ? 'venues' : 'trainers';
$(gridId).html('<div class="hvac-grid-loading"><span class="dashicons dashicons-update-alt hvac-spin"></span> Loading ' + label + '...</div>');
}, },
/** /**
* Hide loading state * Hide loading state
*/ */
hideLoading: function() { hideLoading: function() {
$('#hvac-trainer-grid .hvac-grid-loading').remove(); $('.hvac-grid-loading').remove();
}, },
/** /**
@ -1502,6 +1641,9 @@
// Update search placeholder // Update search placeholder
this.updateSearchPlaceholder(tab); this.updateSearchPlaceholder(tab);
// Highlight markers for active tab
this.highlightMarkersForTab(tab);
// Reset displayed count for current tab // Reset displayed count for current tab
this.displayedCounts[tab] = 0; this.displayedCounts[tab] = 0;

View file

@ -112,10 +112,10 @@ final class HVAC_Plugin {
*/ */
private function defineConstants(): void { private function defineConstants(): void {
if (!defined('HVAC_PLUGIN_VERSION')) { if (!defined('HVAC_PLUGIN_VERSION')) {
define('HVAC_PLUGIN_VERSION', '2.2.11'); define('HVAC_PLUGIN_VERSION', '2.2.18');
} }
if (!defined('HVAC_VERSION')) { if (!defined('HVAC_VERSION')) {
define('HVAC_VERSION', '2.2.11'); define('HVAC_VERSION', '2.2.18');
} }
if (!defined('HVAC_PLUGIN_FILE')) { if (!defined('HVAC_PLUGIN_FILE')) {
define('HVAC_PLUGIN_FILE', dirname(__DIR__) . '/hvac-community-events.php'); define('HVAC_PLUGIN_FILE', dirname(__DIR__) . '/hvac-community-events.php');

View file

@ -956,8 +956,8 @@ class HVAC_Training_Map_Data {
$event_end = tribe_get_end_date($event->ID, false, 'U'); $event_end = tribe_get_end_date($event->ID, false, 'U');
$is_past = $event_end < time(); $is_past = $event_end < time();
// Get cost // Get cost (decode HTML entities so JS doesn't double-escape &#x24; → $)
$cost = tribe_get_formatted_cost($event->ID); $cost = html_entity_decode(tribe_get_formatted_cost($event->ID), ENT_QUOTES | ENT_HTML5, 'UTF-8');
if (empty($cost)) { if (empty($cost)) {
$cost = 'Free'; $cost = 'Free';
} }

View file

@ -22,15 +22,15 @@ $api_key_configured = $find_training->is_api_key_configured();
<div class="hvac-find-training-page"> <div class="hvac-find-training-page">
<!-- Skip link for accessibility --> <!-- Skip link for accessibility -->
<a href="#hvac-trainer-grid" class="hvac-skip-link">Skip to trainer results</a> <a href="#hvac-event-grid" class="hvac-skip-link">Skip to event results</a>
<!-- Compact Filter Bar --> <!-- Compact Filter Bar -->
<div class="hvac-filter-bar" role="search" aria-label="Filter trainers and venues"> <div class="hvac-filter-bar" role="search" aria-label="Filter events, trainers, and venues">
<div class="hvac-filter-bar-inner"> <div class="hvac-filter-bar-inner">
<!-- Search --> <!-- Search -->
<div class="hvac-filter-item hvac-filter-search"> <div class="hvac-filter-item hvac-filter-search">
<span class="dashicons dashicons-search" aria-hidden="true"></span> <span class="dashicons dashicons-search" aria-hidden="true"></span>
<input type="text" id="hvac-training-search" placeholder="Search trainers..." aria-label="Search trainers"> <input type="text" id="hvac-training-search" placeholder="Search events..." aria-label="Search events">
</div> </div>
<!-- Info Button --> <!-- Info Button -->
@ -137,31 +137,27 @@ $api_key_configured = $find_training->is_api_key_configured();
<div class="hvac-sidebar-header"> <div class="hvac-sidebar-header">
<!-- Tab Navigation --> <!-- Tab Navigation -->
<div class="hvac-sidebar-tabs" role="tablist" aria-label="Browse by category"> <div class="hvac-sidebar-tabs" role="tablist" aria-label="Browse by category">
<button role="tab" class="hvac-tab active" data-tab="trainers" aria-selected="true" aria-controls="hvac-panel-trainers"> <button role="tab" class="hvac-tab active" data-tab="events" aria-selected="true" aria-controls="hvac-panel-events">
<label class="hvac-marker-toggle hvac-marker-toggle-event" title="Show events on map" onclick="event.stopPropagation()">
<input type="checkbox" id="hvac-show-events" checked>
<span class="hvac-marker-checkbox"></span>
</label>
Events (<span data-count="events">0</span>)
</button>
<button role="tab" class="hvac-tab" data-tab="trainers" aria-selected="false" aria-controls="hvac-panel-trainers">
<label class="hvac-marker-toggle hvac-marker-toggle-trainer" title="Show trainers on map" onclick="event.stopPropagation()">
<input type="checkbox" id="hvac-show-trainers" checked>
<span class="hvac-marker-checkbox"></span>
</label>
Trainers (<span data-count="trainers">0</span>) Trainers (<span data-count="trainers">0</span>)
</button> </button>
<button role="tab" class="hvac-tab" data-tab="venues" aria-selected="false" aria-controls="hvac-panel-venues"> <button role="tab" class="hvac-tab" data-tab="venues" aria-selected="false" aria-controls="hvac-panel-venues">
<label class="hvac-marker-toggle hvac-marker-toggle-venue" title="Show venues on map" onclick="event.stopPropagation()">
<input type="checkbox" id="hvac-show-venues" checked>
<span class="hvac-marker-checkbox"></span>
</label>
Venues (<span data-count="venues">0</span>) Venues (<span data-count="venues">0</span>)
</button> </button>
<button role="tab" class="hvac-tab" data-tab="events" aria-selected="false" aria-controls="hvac-panel-events">
Events (<span data-count="events">0</span>)
</button>
</div>
<!-- Visibility Toggles (moved from map overlay) -->
<div class="hvac-visibility-toggles">
<label class="hvac-visibility-toggle" title="Show trainers on map">
<input type="checkbox" id="hvac-show-trainers" checked>
<span class="hvac-toggle-dot hvac-toggle-trainer"></span>
</label>
<label class="hvac-visibility-toggle" title="Show venues on map">
<input type="checkbox" id="hvac-show-venues" checked>
<span class="hvac-toggle-dot hvac-toggle-venue"></span>
</label>
<label class="hvac-visibility-toggle" title="Show events on map">
<input type="checkbox" id="hvac-show-events" checked>
<span class="hvac-toggle-dot hvac-toggle-event"></span>
</label>
</div> </div>
<!-- Mobile collapse toggle --> <!-- Mobile collapse toggle -->
@ -175,26 +171,26 @@ $api_key_configured = $find_training->is_api_key_configured();
</div> </div>
<div id="hvac-sidebar-content" class="hvac-sidebar-content"> <div id="hvac-sidebar-content" class="hvac-sidebar-content">
<!-- Trainers Panel --> <!-- Events Panel -->
<div role="tabpanel" id="hvac-panel-trainers" class="hvac-tab-panel active" aria-labelledby="tab-trainers"> <div role="tabpanel" id="hvac-panel-events" class="hvac-tab-panel active" aria-labelledby="tab-events">
<div id="hvac-trainer-grid" class="hvac-item-list"> <div id="hvac-event-grid" class="hvac-item-list">
<div class="hvac-grid-loading"> <div class="hvac-grid-loading">
<span class="dashicons dashicons-update-alt hvac-spin" aria-hidden="true"></span> <span class="dashicons dashicons-update-alt hvac-spin" aria-hidden="true"></span>
Loading trainers... Loading events...
</div> </div>
</div> </div>
</div> </div>
<!-- Trainers Panel -->
<div role="tabpanel" id="hvac-panel-trainers" class="hvac-tab-panel" aria-labelledby="tab-trainers" hidden>
<div id="hvac-trainer-grid" class="hvac-item-list"></div>
</div>
<!-- Venues Panel --> <!-- Venues Panel -->
<div role="tabpanel" id="hvac-panel-venues" class="hvac-tab-panel" aria-labelledby="tab-venues" hidden> <div role="tabpanel" id="hvac-panel-venues" class="hvac-tab-panel" aria-labelledby="tab-venues" hidden>
<div id="hvac-venue-grid" class="hvac-item-list"></div> <div id="hvac-venue-grid" class="hvac-item-list"></div>
</div> </div>
<!-- Events Panel -->
<div role="tabpanel" id="hvac-panel-events" class="hvac-tab-panel" aria-labelledby="tab-events" hidden>
<div id="hvac-event-grid" class="hvac-item-list"></div>
</div>
<!-- Load More Button --> <!-- Load More Button -->
<div class="hvac-load-more-wrapper" style="display: none;"> <div class="hvac-load-more-wrapper" style="display: none;">
<button type="button" id="hvac-load-more" class="hvac-btn-secondary"> <button type="button" id="hvac-load-more" class="hvac-btn-secondary">
@ -229,6 +225,11 @@ $api_key_configured = $find_training->is_api_key_configured();
<?php endif; ?> <?php endif; ?>
</div> </div>
<!-- Reset Map View Button -->
<button type="button" id="hvac-reset-map" class="hvac-reset-map-btn" aria-label="Reset map view" title="Reset map view">
<span class="dashicons dashicons-image-rotate" aria-hidden="true"></span>
</button>
<!-- Map Legend Overlay --> <!-- Map Legend Overlay -->
<div class="hvac-map-legend"> <div class="hvac-map-legend">
<div class="hvac-legend-item"> <div class="hvac-legend-item">
@ -335,6 +336,9 @@ $api_key_configured = $find_training->is_api_key_configured();
<label for="venue-contact-message">Message</label> <label for="venue-contact-message">Message</label>
<textarea id="venue-contact-message" name="message" rows="4" placeholder="Tell us about your training needs..."></textarea> <textarea id="venue-contact-message" name="message" rows="4" placeholder="Tell us about your training needs..."></textarea>
</div> </div>
<div class="hvac-form-group hvac-recaptcha-wrapper">
<div class="g-recaptcha" data-sitekey="<?php echo esc_attr(class_exists('HVAC_Recaptcha') ? HVAC_Recaptcha::SITE_KEY : ''); ?>"></div>
</div>
<button type="submit" class="hvac-btn-primary">Send Message</button> <button type="submit" class="hvac-btn-primary">Send Message</button>
</form> </form>
<div class="hvac-form-success" style="display: none;"> <div class="hvac-form-success" style="display: none;">