Implement launch blockers and MLS state filter

- Add MLS state filter for MN/IA only queries
- Add property inquiry form auto-population with read-only display
- Update broker info and office hours in footer
- Remove Bridge Realty text from about page
- Update service area to Minnesota and Iowa
- Add HomeProz listing identification (is_homeproz column)
- Add dynamic featured listings on front page
- Add gallery thumbnail preloading and loading spinners
- Update FEATURES_PENDING with completion status

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Hanson.xyz Dev
2025-12-16 13:07:12 -06:00
parent 15449b9131
commit 07a8d1756e
21 changed files with 680 additions and 91 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+24 -43
View File
@@ -12,28 +12,10 @@ if (!defined('ABSPATH')) {
get_header();
// Get featured residential properties (3 most recent active residential listings)
$featured_residential = new WP_Query(array(
'post_type' => 'property',
'posts_per_page' => 3,
'tax_query' => array(
'relation' => 'AND',
array(
'taxonomy' => 'property_status',
'field' => 'slug',
'terms' => 'active',
),
array(
'taxonomy' => 'property_type',
'field' => 'slug',
'terms' => 'residential',
),
),
'orderby' => 'date',
'order' => 'DESC',
));
// Get featured MLS listings for JSON data
$featured_mls_listings = homeproz_get_featured_mls_listings(10); // Get more than needed for random selection
// Get featured commercial/land properties (3 most recent active commercial or land listings)
// Get featured commercial/land properties from WordPress (3 most recent active commercial or land listings)
$featured_commercial = new WP_Query(array(
'post_type' => 'property',
'posts_per_page' => 3,
@@ -130,7 +112,7 @@ $featured_commercial = new WP_Query(array(
get_template_part('template-parts/components/service-cards');
?>
<!-- Featured Residential Properties Section -->
<!-- Featured Homes Section (MLS Listings) -->
<section class="featured-properties-section">
<div class="container">
<header class="section-header">
@@ -138,29 +120,28 @@ $featured_commercial = new WP_Query(array(
<p class="section-subtitle">Browse our residential properties for sale</p>
</header>
<?php if ($featured_residential->have_posts()) : ?>
<div class="property-grid property-grid--3col">
<?php
while ($featured_residential->have_posts()) :
$featured_residential->the_post();
get_template_part('template-parts/property/property-card');
endwhile;
wp_reset_postdata();
?>
</div>
<div id="featured-listings-grid" class="property-grid property-grid--3col">
<!-- Populated by JavaScript -->
</div>
<div class="section-footer">
<a href="<?php echo esc_url(home_url('/properties/?type=residential')); ?>" class="btn btn-secondary">
View All Residential
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
<path d="M5 12h14M12 5l7 7-7 7"/>
</svg>
</a>
</div>
<?php else : ?>
<p class="no-properties-message">No residential properties currently available. Please check back soon.</p>
<?php endif; ?>
<p id="featured-listings-empty" class="no-properties-message" style="display: none;">
No residential properties currently available. Please check back soon.
</p>
<div class="section-footer">
<a href="<?php echo esc_url(home_url('/properties/')); ?>" class="btn btn-secondary">
View All Properties
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
<path d="M5 12h14M12 5l7 7-7 7"/>
</svg>
</a>
</div>
</div>
<!-- MLS Listings Data for JavaScript -->
<script type="application/json" id="featured-mls-data">
<?php echo wp_json_encode($featured_mls_listings); ?>
</script>
</section>
<!-- Featured Commercial & Land Properties Section -->
@@ -528,6 +528,13 @@ function homeproz_register_acf_fields() {
'type' => 'text',
'instructions' => 'Real estate license number',
),
array(
'key' => 'field_agent_mls_id',
'label' => 'MLS Agent ID',
'name' => 'agent_mls_id',
'type' => 'text',
'instructions' => 'NorthstarMLS agent ID (e.g., NST503517068)',
),
// Bio Tab
array(
@@ -229,6 +229,165 @@ function homeproz_get_option($key, $default = '') {
return isset($options[$key]) ? $options[$key] : $default;
}
/**
* Get featured MLS listings for homepage
*
* Returns HomeProz listings first, padded with nearby average-priced listings if needed.
* Data is returned as an array ready for JSON encoding.
*
* @param int $count Number of listings to return (default 3)
* @return array Array of listing data for JSON
*/
function homeproz_get_featured_mls_listings($count = 3) {
global $wpdb;
if (!function_exists('mls_get_image_url')) {
return array();
}
$table = $wpdb->prefix . 'mls_properties';
// Albert Lea coordinates and 30-mile bounding box
// 30 miles ~ 0.43 deg latitude, ~0.6 deg longitude at this latitude
$albert_lea_lat = 43.679;
$albert_lea_lon = -93.360;
$lat_range = 0.43;
$lon_range = 0.60;
$min_lat = $albert_lea_lat - $lat_range;
$max_lat = $albert_lea_lat + $lat_range;
$min_lon = $albert_lea_lon - $lon_range;
$max_lon = $albert_lea_lon + $lon_range;
// Get all HomeProz listings (active only, exclude TBD addresses)
// Uses \b word boundary for whole-word match, case-insensitive
$homeproz_listings = $wpdb->get_results(
"SELECT listing_key, list_price, street_number, street_name, street_suffix,
city, state_or_province, postal_code, bedrooms_total, bathrooms_total,
living_area, standard_status, property_type, photos_count
FROM {$table}
WHERE is_homeproz = 1
AND standard_status = 'Active'
AND mlg_can_view = 1
AND COALESCE(street_name, '') NOT REGEXP '\\\\bTBD\\\\b'
AND COALESCE(street_number, '') NOT REGEXP '\\\\bTBD\\\\b'
AND photos_count > 0
ORDER BY modification_timestamp DESC"
);
$listings = array();
// Add HomeProz listings first
foreach ($homeproz_listings as $listing) {
$listings[] = homeproz_format_mls_listing_for_json($listing, true);
}
// If we need padding, get nearby average-priced listings
if (count($listings) < $count) {
$needed = $count - count($listings);
$homeproz_keys = array_column($homeproz_listings, 'listing_key');
// Get average price for residential properties near Albert Lea
$avg_price = $wpdb->get_var($wpdb->prepare(
"SELECT AVG(list_price)
FROM {$table}
WHERE standard_status = 'Active'
AND mlg_can_view = 1
AND property_type = 'Residential'
AND latitude BETWEEN %f AND %f
AND longitude BETWEEN %f AND %f
AND list_price > 0",
$min_lat, $max_lat, $min_lon, $max_lon
));
if ($avg_price > 0) {
// +/- 10% of average price
$min_price = $avg_price * 0.9;
$max_price = $avg_price * 1.1;
// Build exclusion clause for HomeProz listings
$exclude_clause = '';
if (!empty($homeproz_keys)) {
$placeholders = implode(',', array_fill(0, count($homeproz_keys), '%s'));
$exclude_clause = $wpdb->prepare(
" AND listing_key NOT IN ({$placeholders})",
...$homeproz_keys
);
}
// Get random padding listings within price range and distance
$padding_listings = $wpdb->get_results($wpdb->prepare(
"SELECT listing_key, list_price, street_number, street_name, street_suffix,
city, state_or_province, postal_code, bedrooms_total, bathrooms_total,
living_area, standard_status, property_type, photos_count
FROM {$table}
WHERE standard_status = 'Active'
AND mlg_can_view = 1
AND property_type = 'Residential'
AND latitude BETWEEN %f AND %f
AND longitude BETWEEN %f AND %f
AND list_price BETWEEN %f AND %f
AND photos_count > 0
{$exclude_clause}
ORDER BY RAND()
LIMIT %d",
$min_lat, $max_lat, $min_lon, $max_lon,
$min_price, $max_price,
$needed
));
foreach ($padding_listings as $listing) {
$listings[] = homeproz_format_mls_listing_for_json($listing, false);
}
}
}
return $listings;
}
/**
* Format MLS listing data for JSON output
*
* @param object $listing Database row object
* @param bool $is_homeproz Whether this is a HomeProz listing
* @return array Formatted listing data
*/
function homeproz_format_mls_listing_for_json($listing, $is_homeproz = false) {
// Build address
$address_parts = array_filter(array(
$listing->street_number,
$listing->street_name,
$listing->street_suffix
));
$street_address = implode(' ', $address_parts);
$full_address = $street_address;
if ($listing->city) {
$full_address .= ', ' . $listing->city;
}
if ($listing->state_or_province) {
$full_address .= ', ' . $listing->state_or_province;
}
return array(
'listing_key' => $listing->listing_key,
'url' => home_url('/properties/?listing=' . $listing->listing_key),
'image_url' => mls_get_image_url($listing->listing_key, 1, 'thumb'),
'price' => (float) $listing->list_price,
'price_formatted' => homeproz_format_price($listing->list_price),
'address' => $full_address,
'street_address' => $street_address,
'city' => $listing->city,
'state' => $listing->state_or_province,
'bedrooms' => (int) $listing->bedrooms_total,
'bathrooms' => (float) $listing->bathrooms_total,
'sqft' => (int) $listing->living_area,
'status' => $listing->standard_status,
'property_type' => $listing->property_type,
'is_homeproz' => $is_homeproz,
);
}
/**
* Get property locations that have active or pending properties
*
+3 -2
View File
@@ -182,8 +182,9 @@ get_header();
<div class="about-broker-content">
<h3 class="about-broker-title">Broker Information</h3>
<p class="about-broker-text">
HomeProz Real Estate operates as a DBA of Bridge Realty, MN.<br>
Licensed in the State of Minnesota.
HomeProz Real Estate LLC DBA LandProz Real Estate, LLC<br>
111 East Clark Street, Albert Lea, MN 56007<br>
Broker Brian Haugen - MN | Broker/Auctioneer Greg Jensen - MN, IA - 24-21
</p>
</div>
</div>
+29 -14
View File
@@ -18,12 +18,9 @@ $phone = homeproz_get_option('phone', '507-516-4870');
$email = homeproz_get_option('email', 'info@homeprozrealestate.com');
$address = homeproz_get_option('address', '111 E Clark St, Albert Lea, MN 56007');
// Check for property inquiry parameter
// Check for property inquiry parameter (passed via URL from single property page)
$property_inquiry = isset($_GET['property']) ? sanitize_text_field(urldecode($_GET['property'])) : '';
$prefilled_message = '';
if ($property_inquiry) {
$prefilled_message = 'I would like to get more information on property: ' . $property_inquiry;
}
$property_link = isset($_GET['property_url']) ? esc_url(urldecode($_GET['property_url'])) : '';
?>
<main id="primary" class="site-main contact-page-main">
@@ -57,6 +54,19 @@ if ($property_inquiry) {
<div class="contact-form-wrapper">
<h2 class="contact-form-title">Send Us a Message</h2>
<?php if ($property_inquiry) : ?>
<div class="property-inquiry-display">
<div class="property-inquiry-label">Property Inquiry</div>
<div class="property-inquiry-value">
<?php if ($property_link) : ?>
<a href="<?php echo esc_url($property_link); ?>" target="_blank"><?php echo esc_html($property_inquiry); ?></a>
<?php else : ?>
<?php echo esc_html($property_inquiry); ?>
<?php endif; ?>
</div>
</div>
<?php endif; ?>
<?php
// Check for Contact Form 7
if (function_exists('wpcf7_contact_form')) {
@@ -70,6 +80,9 @@ if ($property_inquiry) {
// Show default form markup as fallback
?>
<form class="contact-form" action="" method="post">
<?php if ($property_inquiry) : ?>
<input type="hidden" name="property-inquiry" value="<?php echo esc_attr($property_inquiry); ?>">
<?php endif; ?>
<div class="form-group">
<label for="contact-name">Name <span class="required">*</span></label>
<input type="text" id="contact-name" name="name" required>
@@ -84,7 +97,7 @@ if ($property_inquiry) {
</div>
<div class="form-group">
<label for="contact-message">Message <span class="required">*</span></label>
<textarea id="contact-message" name="message" rows="5" required><?php echo esc_textarea($prefilled_message); ?></textarea>
<textarea id="contact-message" name="message" rows="5" required></textarea>
</div>
<button type="submit" class="btn btn-primary">Send Message</button>
</form>
@@ -94,6 +107,9 @@ if ($property_inquiry) {
// Contact Form 7 not installed - show placeholder form
?>
<form class="contact-form" action="" method="post">
<?php if ($property_inquiry) : ?>
<input type="hidden" name="property-inquiry" value="<?php echo esc_attr($property_inquiry); ?>">
<?php endif; ?>
<div class="form-group">
<label for="contact-name">Name <span class="required">*</span></label>
<input type="text" id="contact-name" name="name" required>
@@ -108,7 +124,7 @@ if ($property_inquiry) {
</div>
<div class="form-group">
<label for="contact-message">Message <span class="required">*</span></label>
<textarea id="contact-message" name="message" rows="5" required><?php echo esc_textarea($prefilled_message); ?></textarea>
<textarea id="contact-message" name="message" rows="5" required></textarea>
</div>
<button type="submit" class="btn btn-primary">Send Message</button>
</form>
@@ -228,19 +244,18 @@ if ($property_inquiry) {
</main>
<?php if ($prefilled_message) : ?>
<?php if ($property_inquiry) : ?>
<script>
(function($) {
if (!$('.Contact_Page').length) return;
$(document).ready(function() {
var prefilledMessage = <?php echo json_encode($prefilled_message); ?>;
var propertyInquiry = <?php echo json_encode($property_inquiry); ?>;
// Try to find textarea in Contact Form 7 or fallback form
var $textarea = $('textarea[name="your-message"], textarea[name="message"], textarea#contact-message').first();
if ($textarea.length && !$textarea.val()) {
$textarea.val(prefilledMessage);
// Populate CF7 hidden field for property inquiry
var $hiddenField = $('input[name="property-inquiry"]');
if ($hiddenField.length) {
$hiddenField.val(propertyInquiry);
}
});
})(jQuery);
+1
View File
@@ -9,6 +9,7 @@ import './main.scss';
// Import component JS
import '../template-parts/header/navigation.js';
import '../template-parts/components/hero-section.js';
import '../template-parts/home/featured-listings.js';
import '../template-parts/property/property-filters.js';
import '../template-parts/property/property-gallery.js';
import '../template-parts/content/content-mortgage-calculator.js';
@@ -50,6 +50,40 @@
margin-bottom: 1.5rem;
}
// Property Inquiry Display (read-only)
.property-inquiry-display {
background-color: var(--color-bg-dark);
border: 1px solid var(--color-border);
border-left: 3px solid var(--color-accent);
padding: 1rem 1.25rem;
margin-bottom: 1.5rem;
border-radius: 0.25rem;
}
.property-inquiry-label {
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--color-text-muted);
margin-bottom: 0.25rem;
}
.property-inquiry-value {
font-size: 0.9375rem;
color: var(--color-text);
line-height: 1.4;
a {
color: var(--color-accent-light);
text-decoration: none;
&:hover {
text-decoration: underline;
}
}
}
.contact-form {
.form-group {
margin-bottom: 1.25rem;
@@ -18,13 +18,13 @@ $tiktok = homeproz_get_option('tiktok');
// Office hours - can be moved to theme options later
$office_hours = array(
'Mon-Fri' => '9:00am - 5:00pm',
'Mon-Fri' => '9:00am - 4:00pm',
'Saturday' => 'By Appointment',
'Sunday' => 'Closed',
'Sunday' => 'By Appointment',
);
// License info - can be moved to theme options later
$license_info = 'MN License #40229984';
// Broker info
$broker_info = 'HomeProz Real Estate LLC DBA LandProz Real Estate, LLC';
?>
<footer id="colophon" class="site-footer">
@@ -39,7 +39,7 @@ $license_info = 'MN License #40229984';
<span class="site-title"><?php bloginfo('name'); ?></span>
<?php endif; ?>
</div>
<p class="footer-tagline">Your trusted partner in Minnesota real estate. Finding homes, building futures.</p>
<p class="footer-tagline">Your trusted partner in Minnesota and Iowa real estate. Finding homes, building futures.</p>
<?php if ($facebook || $tiktok) : ?>
<div class="footer-social">
@@ -173,7 +173,7 @@ $license_info = 'MN License #40229984';
</a>
</div>
<p class="footer-license"><?php echo esc_html($license_info); ?></p>
<p class="footer-license"><?php echo esc_html($broker_info); ?></p>
</div>
<!-- Footer Bottom -->
@@ -0,0 +1,156 @@
/**
* Featured Listings - Home Page
*
* Randomly selects and displays MLS listings on the home page.
*/
(function($) {
'use strict';
// Early return if not on home page
if (!$('body').hasClass('Home_Page')) {
return;
}
var FeaturedListings = {
grid: null,
emptyMessage: null,
listings: [],
init: function() {
this.grid = $('#featured-listings-grid');
this.emptyMessage = $('#featured-listings-empty');
if (!this.grid.length) {
return;
}
// Load listings data from JSON
this.loadListingsData();
// Render random selection
this.renderListings();
},
loadListingsData: function() {
var dataElement = document.getElementById('featured-mls-data');
if (!dataElement) {
this.listings = [];
return;
}
try {
this.listings = JSON.parse(dataElement.textContent);
} catch (e) {
console.error('Failed to parse featured listings data:', e);
this.listings = [];
}
},
/**
* Shuffle array using Fisher-Yates algorithm
*/
shuffleArray: function(array) {
var shuffled = array.slice();
for (var i = shuffled.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var temp = shuffled[i];
shuffled[i] = shuffled[j];
shuffled[j] = temp;
}
return shuffled;
},
renderListings: function() {
if (!this.listings || this.listings.length === 0) {
this.grid.hide();
this.emptyMessage.show();
return;
}
// Shuffle and take up to 3 listings
var shuffled = this.shuffleArray(this.listings);
var selected = shuffled.slice(0, 3);
// Build HTML for each listing
var html = '';
for (var i = 0; i < selected.length; i++) {
html += this.buildPropertyCard(selected[i]);
}
this.grid.html(html);
this.grid.show();
this.emptyMessage.hide();
},
buildPropertyCard: function(listing) {
var bedsLabel = listing.bedrooms === 1 ? 'Bed' : 'Beds';
var bathsLabel = listing.bathrooms === 1 ? 'Bath' : 'Baths';
var specsHtml = '';
if (listing.bedrooms || listing.bathrooms || listing.sqft) {
specsHtml = '<ul class="property-card-specs">';
if (listing.bedrooms) {
specsHtml += '<li class="spec-item">' +
'<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">' +
'<path d="M3 7v11h18V7M3 7V4a1 1 0 0 1 1-1h16a1 1 0 0 1 1 1v3M3 7h18M7 11h4v4H7zM14 11h3"/>' +
'</svg>' +
'<span>' + listing.bedrooms + ' ' + bedsLabel + '</span>' +
'</li>';
}
if (listing.bathrooms) {
specsHtml += '<li class="spec-item">' +
'<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">' +
'<path d="M4 12h16M4 12v7a1 1 0 0 0 1 1h14a1 1 0 0 0 1-1v-7M4 12V6a2 2 0 0 1 2-2h3v2a1 1 0 0 0 1 1h1a1 1 0 0 0 1-1V4"/>' +
'</svg>' +
'<span>' + listing.bathrooms + ' ' + bathsLabel + '</span>' +
'</li>';
}
if (listing.sqft) {
specsHtml += '<li class="spec-item">' +
'<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">' +
'<rect x="3" y="3" width="18" height="18" rx="2"/>' +
'<path d="M3 9h18M9 3v18"/>' +
'</svg>' +
'<span>' + listing.sqft.toLocaleString() + ' sqft</span>' +
'</li>';
}
specsHtml += '</ul>';
}
return '<article class="property-card card mls-property" data-listing-key="' + this.escapeHtml(listing.listing_key) + '">' +
'<a href="' + this.escapeHtml(listing.url) + '" class="property-card-link-overlay" aria-hidden="true" tabindex="-1"></a>' +
'<div class="property-card-image has-image" style="background-image: url(' + this.escapeHtml(listing.image_url) + ')">' +
'<span class="property-card-badge badge badge-active">Active</span>' +
'</div>' +
'<div class="property-card-content">' +
'<div class="property-card-price">' + this.escapeHtml(listing.price_formatted) + '</div>' +
'<h3 class="property-card-title">' + this.escapeHtml(listing.address) + '</h3>' +
specsHtml +
'<span class="property-card-link">' +
'View Details' +
'<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">' +
'<path d="M5 12h14M12 5l7 7-7 7"/>' +
'</svg>' +
'</span>' +
'</div>' +
'</article>';
},
escapeHtml: function(text) {
if (!text) return '';
var div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
};
// Initialize on DOM ready
$(document).ready(function() {
FeaturedListings.init();
});
})(jQuery);
@@ -83,6 +83,10 @@
this.bindSwipeEvents();
this.updateThumbnailNavigation();
// Setup thumbnail loading states and preload first two pages
this.setupThumbnailLoading();
this.preloadThumbnailPages(0, 2);
// Start autoplay only if more than 1 image
if (this.images.length > 1) {
this.startAutoplay();
@@ -125,12 +129,14 @@
}
});
// Thumbnail navigation buttons
// Thumbnail navigation buttons - stop autoplay when paginating
this.$prevBtn.on('click', function() {
self.stopAutoplay();
self.prevThumbnailPage();
});
this.$nextBtn.on('click', function() {
self.stopAutoplay();
self.nextThumbnailPage();
});
@@ -528,6 +534,7 @@
if (this.thumbnailPage > 0) {
this.thumbnailPage--;
this.scrollThumbnails();
this.preloadPrevThumbnailPage();
}
},
@@ -539,6 +546,7 @@
if (this.thumbnailPage < totalPages - 1) {
this.thumbnailPage++;
this.scrollThumbnails();
this.preloadNextThumbnailPage();
}
},
@@ -665,6 +673,87 @@
this.$lightboxImage.attr('src', image.url);
this.$lightboxImage.attr('alt', image.alt || 'Property photo');
this.$lightboxCounter.text(this.currentIndex + 1);
},
/**
* Setup thumbnail loading states
* Adds loading class and spinner to each thumbnail
*/
setupThumbnailLoading: function() {
this.$thumbnails.each(function() {
var $thumb = $(this);
var $img = $thumb.find('img');
// Add loading state
$thumb.addClass('is-loading');
// Add spinner element
if (!$thumb.find('.thumbnail-spinner').length) {
$thumb.append('<div class="thumbnail-spinner"><div class="spinner"></div></div>');
}
// Handle image load
if ($img[0].complete) {
$thumb.removeClass('is-loading');
} else {
$img.on('load', function() {
$thumb.removeClass('is-loading');
});
$img.on('error', function() {
$thumb.removeClass('is-loading');
});
}
});
},
/**
* Preload thumbnail pages
* @param {number} startPage - First page to preload (0-indexed)
* @param {number} numPages - Number of pages to preload
*/
preloadThumbnailPages: function(startPage, numPages) {
var self = this;
var startIndex = startPage * this.thumbnailsPerPage;
var endIndex = Math.min((startPage + numPages) * this.thumbnailsPerPage, this.images.length);
for (var i = startIndex; i < endIndex; i++) {
(function(index) {
var $thumb = self.$thumbnails.filter('[data-index="' + index + '"]');
var $img = $thumb.find('img');
// Remove lazy loading to force immediate load
$img.removeAttr('loading');
// If not already loaded, preload
if (!$img[0].complete) {
var preloader = new Image();
preloader.src = $img.attr('src');
}
})(i);
}
},
/**
* Preload next page of thumbnails when navigating
*/
preloadNextThumbnailPage: function() {
var totalPages = Math.ceil(this.images.length / this.thumbnailsPerPage);
var nextPage = this.thumbnailPage + 1;
if (nextPage < totalPages) {
this.preloadThumbnailPages(nextPage, 1);
}
},
/**
* Preload previous page of thumbnails when navigating
*/
preloadPrevThumbnailPage: function() {
var prevPage = this.thumbnailPage - 1;
if (prevPage >= 0) {
this.preloadThumbnailPages(prevPage, 1);
}
}
};
@@ -117,7 +117,7 @@
width: calc((100% - 2rem) / 5);
padding: 0;
border: 2px solid transparent;
background: none;
background: var(--color-bg-card);
cursor: pointer;
border-radius: 0.25rem;
overflow: hidden;
@@ -135,6 +135,44 @@
aspect-ratio: 1;
object-fit: cover;
display: block;
opacity: 1;
transition: opacity 0.2s ease;
}
// Loading state
&.is-loading {
img {
opacity: 0;
}
.thumbnail-spinner {
display: flex;
}
}
}
// Thumbnail spinner
.thumbnail-spinner {
position: absolute;
inset: 0;
display: none;
align-items: center;
justify-content: center;
background: var(--color-bg-card);
.spinner {
width: 20px;
height: 20px;
border: 2px solid var(--color-border);
border-top-color: var(--color-accent);
border-radius: 50%;
animation: thumbnail-spin 0.8s linear infinite;
}
}
@keyframes thumbnail-spin {
to {
transform: rotate(360deg);
}
}
@@ -372,7 +372,14 @@ get_header();
<p class="office-name"><?php echo esc_html($office_name); ?></p>
<?php endif; ?>
<a href="<?php echo esc_url(home_url('/contact/')); ?>" class="btn btn-primary btn-block">
<?php
$contact_url = add_query_arg(array(
'property' => urlencode($full_address),
'property_url' => urlencode(home_url('/properties/?listing=' . $listing_key)),
'property-inquiry' => urlencode($full_address),
), home_url('/contact/'));
?>
<a href="<?php echo esc_url($contact_url); ?>" class="btn btn-primary btn-block">
Contact About This Property
</a>
</div>