UI/UX improvements: gallery autoplay, clickable cards, footer legal section
Property details page: - Move address to header above gallery - Add property type badges (blue residential, red commercial) - Gallery autoplay with play/pause button, 5-second interval - Fade transitions for autoplay, slide transitions for swipe - Thumbnail navigation with sync - Swipe support in gallery and lightbox - Widget titles: 18px Times New Roman bold - Remove breadcrumbs Layout and styling: - Container width: 1400px - Contact page map 50% taller (375px) - Contact info labels: Times New Roman 16px - Agent photo backgrounds solid black - CTA accent button hover: black text Clickable components: - Service cards fully clickable with stretched links - Resource cards fully clickable with stretched links - Addresses link to Google Maps (contact page, footer) Footer updates: - Add Send Us a Message link with paper airplane icon - Replace credentials with legal section - Privacy Policy, Fair Housing, MLS Disclaimer, Brokerage Disclosure links - Credits: Web Design by HansonXyz Other: - Install Classic Editor plugin 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -44,13 +44,13 @@ $agent_count = count($agents_data);
|
||||
?>
|
||||
|
||||
<main id="primary" class="site-main Agents_Archive">
|
||||
<?php
|
||||
// Hero Section - title only, subtitle below in content area
|
||||
get_template_part('template-parts/components/hero-section', null, array(
|
||||
'title' => 'Our Team',
|
||||
'size' => 'small',
|
||||
));
|
||||
?>
|
||||
<!-- Archive Hero -->
|
||||
<section class="archive-hero">
|
||||
<div class="container">
|
||||
<h1 class="archive-hero-title">Our Team</h1>
|
||||
<p class="archive-hero-subtitle">Meet the dedicated professionals ready to help you with all your real estate needs.</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="agents-section">
|
||||
<div class="container">
|
||||
@@ -122,7 +122,7 @@ $agent_count = count($agents_data);
|
||||
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/>
|
||||
<circle cx="12" cy="7" r="4"/>
|
||||
</svg>
|
||||
<span>View Profile</span>
|
||||
<span>Profile</span>
|
||||
</a>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
@@ -14,11 +14,12 @@ if (!defined('ABSPATH')) {
|
||||
|
||||
get_header();
|
||||
|
||||
// Check if map view is enabled
|
||||
$show_map = isset($_GET['view']) && $_GET['view'] === 'map';
|
||||
// Map view is default on desktop, grid view requires ?view=grid
|
||||
$show_map = !isset($_GET['view']) || $_GET['view'] !== 'grid';
|
||||
$view_class = $show_map ? 'is-map-view' : 'is-grid-view';
|
||||
?>
|
||||
|
||||
<main id="primary" class="site-main property-archive-main">
|
||||
<main id="primary" class="site-main property-archive-main <?php echo esc_attr($view_class); ?>">
|
||||
<?php
|
||||
// Get hero content from Theme Options
|
||||
$hero_title = get_field('properties_hero_title', 'option') ?: 'Find Your Perfect Property';
|
||||
@@ -42,166 +43,128 @@ $show_map = isset($_GET['view']) && $_GET['view'] === 'map';
|
||||
<!-- Filters -->
|
||||
<?php get_template_part('template-parts/property/property-filters'); ?>
|
||||
|
||||
<!-- View Toggle -->
|
||||
<div class="view-toggle">
|
||||
<a href="<?php echo esc_url(remove_query_arg('view')); ?>" class="view-toggle-btn <?php echo !$show_map ? 'active' : ''; ?>">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
|
||||
<rect x="3" y="3" width="7" height="7"/>
|
||||
<rect x="14" y="3" width="7" height="7"/>
|
||||
<rect x="3" y="14" width="7" height="7"/>
|
||||
<rect x="14" y="14" width="7" height="7"/>
|
||||
</svg>
|
||||
<span>Grid</span>
|
||||
</a>
|
||||
<a href="<?php echo esc_url(add_query_arg('view', 'map')); ?>" class="view-toggle-btn <?php echo $show_map ? 'active' : ''; ?>">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
|
||||
<polygon points="1 6 1 22 8 18 16 22 23 18 23 2 16 6 8 2 1 6"/>
|
||||
<line x1="8" y1="2" x2="8" y2="18"/>
|
||||
<line x1="16" y1="6" x2="16" y2="22"/>
|
||||
</svg>
|
||||
<span>Map</span>
|
||||
</a>
|
||||
<!-- Map + List View (hidden below 1024px via CSS) -->
|
||||
<div class="property-map-layout">
|
||||
<div class="property-map-container">
|
||||
<!-- View Toggle (inside map column) -->
|
||||
<div class="view-toggle">
|
||||
<a href="<?php echo esc_url(add_query_arg('view', 'grid')); ?>" class="view-toggle-btn view-toggle-grid">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
|
||||
<rect x="3" y="3" width="7" height="7"/>
|
||||
<rect x="14" y="3" width="7" height="7"/>
|
||||
<rect x="3" y="14" width="7" height="7"/>
|
||||
<rect x="14" y="14" width="7" height="7"/>
|
||||
</svg>
|
||||
<span>Grid</span>
|
||||
</a>
|
||||
<a href="<?php echo esc_url(remove_query_arg('view')); ?>" class="view-toggle-btn view-toggle-map active">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
|
||||
<polygon points="1 6 1 22 8 18 16 22 23 18 23 2 16 6 8 2 1 6"/>
|
||||
<line x1="8" y1="2" x2="8" y2="18"/>
|
||||
<line x1="16" y1="6" x2="16" y2="22"/>
|
||||
</svg>
|
||||
<span>Map</span>
|
||||
</a>
|
||||
</div>
|
||||
<div id="property-map" class="property-map">
|
||||
<!-- Leaflet map will be initialized here -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="property-list-container">
|
||||
<div id="property-results" class="property-results-map">
|
||||
<?php get_template_part('template-parts/property/property-results'); ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if ($show_map) : ?>
|
||||
<!-- Map + List View -->
|
||||
<div class="property-map-layout">
|
||||
<div class="property-map-container">
|
||||
<div id="property-map" class="property-map">
|
||||
<!-- Leaflet map will be initialized here -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="property-list-container">
|
||||
<div id="property-results">
|
||||
<?php get_template_part('template-parts/property/property-results'); ?>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Grid View (shown below 1024px, or when grid selected above 1024px) -->
|
||||
<div class="grid-view-container">
|
||||
<div class="view-toggle">
|
||||
<a href="<?php echo esc_url(add_query_arg('view', 'grid')); ?>" class="view-toggle-btn view-toggle-grid active">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
|
||||
<rect x="3" y="3" width="7" height="7"/>
|
||||
<rect x="14" y="3" width="7" height="7"/>
|
||||
<rect x="3" y="14" width="7" height="7"/>
|
||||
<rect x="14" y="14" width="7" height="7"/>
|
||||
</svg>
|
||||
<span>Grid</span>
|
||||
</a>
|
||||
<a href="<?php echo esc_url(remove_query_arg('view')); ?>" class="view-toggle-btn view-toggle-map">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
|
||||
<polygon points="1 6 1 22 8 18 16 22 23 18 23 2 16 6 8 2 1 6"/>
|
||||
<line x1="8" y1="2" x2="8" y2="18"/>
|
||||
<line x1="16" y1="6" x2="16" y2="22"/>
|
||||
</svg>
|
||||
<span>Map</span>
|
||||
</a>
|
||||
</div>
|
||||
<?php else : ?>
|
||||
<!-- Grid View -->
|
||||
<div id="property-results">
|
||||
<div id="property-results-grid" class="property-results-grid">
|
||||
<?php get_template_part('template-parts/property/property-results'); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<?php if ($show_map) : ?>
|
||||
<?php
|
||||
// Always load map data for responsive switching
|
||||
$map_properties = new WP_Query(array(
|
||||
'post_type' => 'property',
|
||||
'posts_per_page' => -1,
|
||||
));
|
||||
|
||||
$markers = array();
|
||||
$city_coords = array(
|
||||
'Albert Lea' => array(43.6480, -93.3685),
|
||||
'Austin' => array(43.6666, -92.9746),
|
||||
'Glenville' => array(43.5733, -93.2779),
|
||||
'Emmons' => array(43.5013, -93.4896),
|
||||
'Clarks Grove' => array(43.7627, -93.3196),
|
||||
'Alden' => array(43.6719, -93.5768),
|
||||
'Hartland' => array(43.8030, -93.4846),
|
||||
'Geneva' => array(43.8255, -93.2682),
|
||||
'Owatonna' => array(44.0838, -93.2260),
|
||||
'Faribault' => array(44.2949, -93.2688),
|
||||
'Rochester' => array(44.0234, -92.4699),
|
||||
'Mankato' => array(44.1636, -93.9994),
|
||||
);
|
||||
|
||||
if ($map_properties->have_posts()) :
|
||||
while ($map_properties->have_posts()) :
|
||||
$map_properties->the_post();
|
||||
$city = get_field('city');
|
||||
$price = get_field('property_price');
|
||||
$address = get_field('street_address');
|
||||
|
||||
// Get coords for city, default to Albert Lea
|
||||
$coords = isset($city_coords[$city]) ? $city_coords[$city] : $city_coords['Albert Lea'];
|
||||
// Add small random offset (seeded by post ID for consistency)
|
||||
srand(get_the_ID());
|
||||
$lat = $coords[0] + (rand(-50, 50) / 10000);
|
||||
$lng = $coords[1] + (rand(-50, 50) / 10000);
|
||||
|
||||
$markers[] = array(
|
||||
'id' => get_the_ID(),
|
||||
'lat' => $lat,
|
||||
'lng' => $lng,
|
||||
'title' => get_the_title(),
|
||||
'price' => '$' . number_format($price),
|
||||
'address' => $address . ', ' . $city,
|
||||
'url' => get_permalink(),
|
||||
);
|
||||
endwhile;
|
||||
wp_reset_postdata();
|
||||
endif;
|
||||
?>
|
||||
<!-- Leaflet CSS -->
|
||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=" crossorigin=""/>
|
||||
<!-- Leaflet JS -->
|
||||
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js" integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin=""></script>
|
||||
<script>
|
||||
(function($) {
|
||||
// Wait for DOM
|
||||
$(document).ready(function() {
|
||||
// Initialize map centered on Albert Lea area
|
||||
var map = L.map('property-map').setView([43.6480, -93.3685], 10);
|
||||
|
||||
// Add OpenStreetMap tiles
|
||||
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>'
|
||||
}).addTo(map);
|
||||
|
||||
// Property data from PHP
|
||||
var properties = [
|
||||
<?php
|
||||
// Get all properties for map markers
|
||||
$map_properties = new WP_Query(array(
|
||||
'post_type' => 'property',
|
||||
'posts_per_page' => -1,
|
||||
'tax_query' => array(
|
||||
array(
|
||||
'taxonomy' => 'property_status',
|
||||
'field' => 'slug',
|
||||
'terms' => array('active', 'pending'),
|
||||
),
|
||||
),
|
||||
));
|
||||
|
||||
$markers = array();
|
||||
if ($map_properties->have_posts()) :
|
||||
while ($map_properties->have_posts()) :
|
||||
$map_properties->the_post();
|
||||
$city = get_field('city');
|
||||
$price = get_field('property_price');
|
||||
$address = get_field('street_address');
|
||||
|
||||
// Approximate coordinates based on city (can be enhanced with geocoding)
|
||||
$city_coords = array(
|
||||
'Albert Lea' => array(43.6480, -93.3685),
|
||||
'Austin' => array(43.6666, -92.9746),
|
||||
'Glenville' => array(43.5733, -93.2779),
|
||||
'Emmons' => array(43.5013, -93.4896),
|
||||
'Clarks Grove' => array(43.7627, -93.3196),
|
||||
'Alden' => array(43.6719, -93.5768),
|
||||
'Hartland' => array(43.8030, -93.4846),
|
||||
'Geneva' => array(43.8255, -93.2682),
|
||||
'Owatonna' => array(44.0838, -93.2260),
|
||||
'Faribault' => array(44.2949, -93.2688),
|
||||
'Rochester' => array(44.0234, -92.4699),
|
||||
'Mankato' => array(44.1636, -93.9994),
|
||||
);
|
||||
|
||||
// Get coords for city, default to Albert Lea
|
||||
$coords = isset($city_coords[$city]) ? $city_coords[$city] : $city_coords['Albert Lea'];
|
||||
// Add small random offset to prevent overlapping markers
|
||||
$lat = $coords[0] + (rand(-50, 50) / 10000);
|
||||
$lng = $coords[1] + (rand(-50, 50) / 10000);
|
||||
|
||||
$markers[] = array(
|
||||
'id' => get_the_ID(),
|
||||
'lat' => $lat,
|
||||
'lng' => $lng,
|
||||
'title' => get_the_title(),
|
||||
'price' => '$' . number_format($price),
|
||||
'address' => $address . ', ' . $city,
|
||||
'url' => get_permalink(),
|
||||
);
|
||||
endwhile;
|
||||
wp_reset_postdata();
|
||||
endif;
|
||||
|
||||
// Output JSON
|
||||
foreach ($markers as $i => $marker) {
|
||||
echo json_encode($marker);
|
||||
if ($i < count($markers) - 1) echo ',';
|
||||
}
|
||||
?>
|
||||
];
|
||||
|
||||
// Custom marker icon
|
||||
var propertyIcon = L.divIcon({
|
||||
className: 'property-marker',
|
||||
html: '<div class="marker-pin"></div>',
|
||||
iconSize: [30, 40],
|
||||
iconAnchor: [15, 40],
|
||||
popupAnchor: [0, -40]
|
||||
});
|
||||
|
||||
// Add markers for each property
|
||||
properties.forEach(function(prop) {
|
||||
if (prop.lat && prop.lng) {
|
||||
var marker = L.marker([prop.lat, prop.lng], {icon: propertyIcon}).addTo(map);
|
||||
marker.bindPopup(
|
||||
'<div class="map-popup">' +
|
||||
'<strong>' + prop.price + '</strong><br>' +
|
||||
'<span>' + prop.address + '</span><br>' +
|
||||
'<a href="' + prop.url + '">View Details</a>' +
|
||||
'</div>'
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// Fit bounds if we have properties
|
||||
if (properties.length > 0) {
|
||||
var bounds = L.latLngBounds(properties.map(function(p) { return [p.lat, p.lng]; }));
|
||||
map.fitBounds(bounds, { padding: [50, 50] });
|
||||
}
|
||||
});
|
||||
})(jQuery);
|
||||
var homeprozMapData = {
|
||||
properties: <?php echo json_encode($markers); ?>,
|
||||
isMapView: <?php echo $show_map ? 'true' : 'false'; ?>
|
||||
};
|
||||
</script>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php
|
||||
get_footer();
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 52 KiB |
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
@@ -94,6 +94,7 @@ $featured_commercial = new WP_Query(array(
|
||||
'show_location_search' => true,
|
||||
'primary_cta_text' => 'View All Properties',
|
||||
'primary_cta_url' => home_url('/properties/'),
|
||||
'primary_cta_icon' => 'map',
|
||||
'secondary_cta_text' => 'Contact Us',
|
||||
'secondary_cta_url' => home_url('/contact/'),
|
||||
'background_image' => $hero_image_split ?: $fallback_image,
|
||||
@@ -115,6 +116,7 @@ $featured_commercial = new WP_Query(array(
|
||||
'show_location_search' => true,
|
||||
'primary_cta_text' => 'View All Properties',
|
||||
'primary_cta_url' => home_url('/properties/'),
|
||||
'primary_cta_icon' => 'map',
|
||||
'secondary_cta_text' => 'Contact Us',
|
||||
'secondary_cta_url' => home_url('/contact/'),
|
||||
'background_image' => $hero_image_card ?: $fallback_image,
|
||||
|
||||
@@ -197,10 +197,51 @@ function homeproz_ajax_filter_properties() {
|
||||
<?php
|
||||
$html = ob_get_clean();
|
||||
|
||||
// Build markers data for map view
|
||||
$markers = array();
|
||||
$city_coords = array(
|
||||
'Albert Lea' => array(43.6480, -93.3685),
|
||||
'Austin' => array(43.6666, -92.9746),
|
||||
'Glenville' => array(43.5733, -93.2779),
|
||||
'Emmons' => array(43.5013, -93.4896),
|
||||
'Clarks Grove' => array(43.7627, -93.3196),
|
||||
'Alden' => array(43.6719, -93.5768),
|
||||
'Hartland' => array(43.8030, -93.4846),
|
||||
'Geneva' => array(43.8255, -93.2682),
|
||||
'Owatonna' => array(44.0838, -93.2260),
|
||||
'Faribault' => array(44.2949, -93.2688),
|
||||
'Rochester' => array(44.0234, -92.4699),
|
||||
'Mankato' => array(44.1636, -93.9994),
|
||||
);
|
||||
|
||||
foreach ($sorted_properties as $prop) {
|
||||
$city = get_field('city', $prop->ID);
|
||||
$price = get_field('property_price', $prop->ID);
|
||||
$address = get_field('street_address', $prop->ID);
|
||||
|
||||
// Get coords for city, default to Albert Lea
|
||||
$coords = isset($city_coords[$city]) ? $city_coords[$city] : $city_coords['Albert Lea'];
|
||||
// Add small random offset to prevent overlapping markers (seeded by post ID for consistency)
|
||||
srand($prop->ID);
|
||||
$lat = $coords[0] + (rand(-50, 50) / 10000);
|
||||
$lng = $coords[1] + (rand(-50, 50) / 10000);
|
||||
|
||||
$markers[] = array(
|
||||
'id' => $prop->ID,
|
||||
'lat' => $lat,
|
||||
'lng' => $lng,
|
||||
'title' => $prop->post_title,
|
||||
'price' => '$' . number_format($price),
|
||||
'address' => $address . ', ' . $city,
|
||||
'url' => get_permalink($prop->ID),
|
||||
);
|
||||
}
|
||||
|
||||
wp_send_json_success(array(
|
||||
'html' => $html,
|
||||
'found_posts' => $total,
|
||||
'max_pages' => $max_pages,
|
||||
'markers' => $markers,
|
||||
));
|
||||
}
|
||||
add_action('wp_ajax_homeproz_filter_properties', 'homeproz_ajax_filter_properties');
|
||||
|
||||
@@ -153,8 +153,6 @@ function homeproz_footer_fallback_menu() {
|
||||
echo '<li class="menu-item"><a href="' . esc_url(home_url('/properties/')) . '">Properties</a></li>';
|
||||
echo '<li class="menu-item"><a href="' . esc_url(get_post_type_archive_link('agent')) . '">Agents</a></li>';
|
||||
echo '<li class="menu-item"><a href="' . esc_url(home_url('/about/')) . '">About</a></li>';
|
||||
echo '<li class="menu-item"><a href="' . esc_url(home_url('/blog/')) . '">Blog</a></li>';
|
||||
echo '<li class="menu-item"><a href="' . esc_url(home_url('/contact/')) . '">Contact</a></li>';
|
||||
echo '</ul>';
|
||||
}
|
||||
|
||||
|
||||
@@ -40,95 +40,139 @@ get_header();
|
||||
<!-- Company Story Section -->
|
||||
<section class="about-story-section">
|
||||
<div class="container">
|
||||
<div class="about-story-content">
|
||||
<?php
|
||||
while (have_posts()) :
|
||||
the_post();
|
||||
the_content();
|
||||
endwhile;
|
||||
?>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Our Values Section -->
|
||||
<section class="about-values-section">
|
||||
<div class="container">
|
||||
<header class="about-values-header">
|
||||
<h2 class="about-values-title">Our Values</h2>
|
||||
</header>
|
||||
|
||||
<div class="features-grid">
|
||||
<?php
|
||||
get_template_part('template-parts/components/feature-block', null, array(
|
||||
'icon' => 'handshake',
|
||||
'title' => 'Integrity',
|
||||
'text' => 'We believe in honest, transparent communication throughout every transaction. Your trust is our priority.',
|
||||
));
|
||||
|
||||
get_template_part('template-parts/components/feature-block', null, array(
|
||||
'icon' => 'personalized',
|
||||
'title' => 'Client-Focused',
|
||||
'text' => 'Your goals are our goals. We listen carefully and work tirelessly to exceed your expectations.',
|
||||
));
|
||||
|
||||
get_template_part('template-parts/components/feature-block', null, array(
|
||||
'icon' => 'local-expertise',
|
||||
'title' => 'Community',
|
||||
'text' => 'We live and work in the communities we serve. Local knowledge means better results for you.',
|
||||
));
|
||||
?>
|
||||
<div class="about-story-layout">
|
||||
<div class="about-story-image">
|
||||
<img src="<?php echo esc_url(get_template_directory_uri() . '/assets/images/about-us.webp'); ?>" alt="HomeProz Real Estate Team">
|
||||
</div>
|
||||
<div class="about-story-content">
|
||||
<?php
|
||||
while (have_posts()) :
|
||||
the_post();
|
||||
the_content();
|
||||
endwhile;
|
||||
?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Meet the Team Section -->
|
||||
<section class="about-team-section">
|
||||
<section class="about-team-section Agents_Archive">
|
||||
<div class="container">
|
||||
<header class="about-team-header">
|
||||
<h2 class="about-team-title">Meet Our Team</h2>
|
||||
<p class="about-team-subtitle">A dedicated group of real estate professionals committed to your success</p>
|
||||
</header>
|
||||
|
||||
<div class="about-team-grid">
|
||||
<?php
|
||||
// Team member data with bios
|
||||
$team_members = array(
|
||||
array(
|
||||
'name' => 'Heidi Johnson',
|
||||
'title' => 'REALTOR',
|
||||
'phone' => '507-402-2310',
|
||||
'email' => 'heidi@homeprozrealestate.com',
|
||||
'bio' => 'With deep roots in the Albert Lea community, Heidi brings years of local knowledge and dedication to every client relationship.',
|
||||
),
|
||||
array(
|
||||
'name' => 'Kyra Johnson',
|
||||
'title' => 'REALTOR',
|
||||
'phone' => '507-516-4870',
|
||||
'email' => 'kyra@homeprozrealestate.com',
|
||||
'bio' => 'Kyra combines her passion for real estate with a commitment to exceptional service, guiding buyers and sellers to successful outcomes.',
|
||||
),
|
||||
array(
|
||||
'name' => 'John Hill',
|
||||
'title' => 'REALTOR',
|
||||
'phone' => '507-383-1738',
|
||||
'email' => 'john@homeprozrealestate.com',
|
||||
'bio' => 'John\'s expertise in the local market and attention to detail ensure a smooth experience whether you\'re buying or selling.',
|
||||
),
|
||||
array(
|
||||
'name' => 'Mindy Moreno',
|
||||
'title' => 'REALTOR',
|
||||
'phone' => '507-438-5900',
|
||||
'email' => 'mindy@homeprozrealestate.com',
|
||||
'bio' => 'Mindy\'s bilingual skills and warm approach help her connect with a diverse range of clients in our community.',
|
||||
),
|
||||
);
|
||||
<?php
|
||||
// Query all published agents, then filter disabled and sort by order
|
||||
$all_agents = get_posts([
|
||||
'post_type' => 'agent',
|
||||
'posts_per_page' => -1,
|
||||
'post_status' => 'publish',
|
||||
]);
|
||||
|
||||
foreach ($team_members as $member) :
|
||||
get_template_part('template-parts/components/agent-card', null, $member);
|
||||
endforeach;
|
||||
?>
|
||||
</div>
|
||||
// Filter out disabled agents and add sorting data
|
||||
$agents_data = [];
|
||||
foreach ($all_agents as $agent) {
|
||||
$disabled = get_field('agent_disabled', $agent->ID);
|
||||
if ($disabled) continue;
|
||||
|
||||
$order = get_field('agent_order', $agent->ID);
|
||||
$agents_data[] = [
|
||||
'post' => $agent,
|
||||
'order' => $order ? (int) $order : 10,
|
||||
];
|
||||
}
|
||||
|
||||
// Sort by order, then by title
|
||||
usort($agents_data, function($a, $b) {
|
||||
if ($a['order'] !== $b['order']) {
|
||||
return $a['order'] - $b['order'];
|
||||
}
|
||||
return strcmp($a['post']->post_title, $b['post']->post_title);
|
||||
});
|
||||
?>
|
||||
|
||||
<?php if (!empty($agents_data)) : ?>
|
||||
<div class="agents-grid">
|
||||
<?php foreach ($agents_data as $agent_item) :
|
||||
$agent = $agent_item['post'];
|
||||
$agent_id = $agent->ID;
|
||||
$agent_phone = get_field('agent_phone', $agent_id);
|
||||
$agent_email = get_field('agent_email', $agent_id);
|
||||
$agent_title = get_field('agent_title', $agent_id);
|
||||
$agent_short_bio = get_field('agent_short_bio', $agent_id);
|
||||
$agent_permalink = get_permalink($agent_id);
|
||||
|
||||
// Get featured image
|
||||
$agent_photo_id = get_post_thumbnail_id($agent_id);
|
||||
$agent_photo_url = $agent_photo_id ? wp_get_attachment_image_url($agent_photo_id, 'medium_large') : '';
|
||||
?>
|
||||
<article class="agent-card-item">
|
||||
<a href="<?php echo esc_url($agent_permalink); ?>" class="agent-card-link">
|
||||
<div class="agent-card-image">
|
||||
<?php if ($agent_photo_url) : ?>
|
||||
<img src="<?php echo esc_url($agent_photo_url); ?>" alt="<?php echo esc_attr($agent->post_title); ?>">
|
||||
<?php else : ?>
|
||||
<div class="agent-card-placeholder">
|
||||
<svg width="64" height="64" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1" aria-hidden="true">
|
||||
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/>
|
||||
<circle cx="12" cy="7" r="4"/>
|
||||
</svg>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div class="agent-card-content">
|
||||
<?php if ($agent_title) : ?>
|
||||
<p class="agent-card-title-label"><?php echo esc_html($agent_title); ?></p>
|
||||
<?php endif; ?>
|
||||
|
||||
<h3 class="agent-card-name"><?php echo esc_html($agent->post_title); ?></h3>
|
||||
|
||||
<?php if ($agent_short_bio) : ?>
|
||||
<p class="agent-card-bio"><?php echo esc_html($agent_short_bio); ?></p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<div class="agent-card-actions">
|
||||
<?php if ($agent_phone) : ?>
|
||||
<a href="tel:<?php echo esc_attr(preg_replace('/[^0-9]/', '', $agent_phone)); ?>" class="agent-action-btn">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
|
||||
<path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z"/>
|
||||
</svg>
|
||||
<span class="sr-only">Call</span>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($agent_email) : ?>
|
||||
<a href="mailto:<?php echo esc_attr($agent_email); ?>" class="agent-action-btn">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
|
||||
<path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/>
|
||||
<polyline points="22,6 12,13 2,6"/>
|
||||
</svg>
|
||||
<span class="sr-only">Email</span>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
|
||||
<a href="<?php echo esc_url($agent_permalink); ?>" class="agent-action-btn agent-action-profile">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
|
||||
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/>
|
||||
<circle cx="12" cy="7" r="4"/>
|
||||
</svg>
|
||||
<span>Profile</span>
|
||||
</a>
|
||||
</div>
|
||||
</article>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php else : ?>
|
||||
<div class="no-agents-message">
|
||||
<p>No team members to display at this time.</p>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -132,7 +132,9 @@ if ($property_inquiry) {
|
||||
</div>
|
||||
<div class="contact-info-content">
|
||||
<h4 class="contact-info-label">Office Address</h4>
|
||||
<p class="contact-info-value"><?php echo esc_html($address); ?></p>
|
||||
<p class="contact-info-value">
|
||||
<a href="https://www.google.com/maps/search/?api=1&query=<?php echo urlencode($address); ?>" target="_blank" rel="noopener"><?php echo esc_html($address); ?></a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
@@ -196,7 +198,7 @@ if ($property_inquiry) {
|
||||
<iframe
|
||||
src="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d2871.8244599999997!2d-93.36827!3d43.64829!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x87f5024a6c0d9e7d%3A0x0!2s111%20E%20Clark%20St%2C%20Albert%20Lea%2C%20MN%2056007!5e0!3m2!1sen!2sus!4v1234567890"
|
||||
width="100%"
|
||||
height="250"
|
||||
height="375"
|
||||
style="border:0;"
|
||||
allowfullscreen=""
|
||||
loading="lazy"
|
||||
|
||||
@@ -49,6 +49,7 @@ $resource_pages = get_pages(array(
|
||||
<div class="resources-featured-grid">
|
||||
<!-- Buyer's Guide -->
|
||||
<div class="resource-featured-card">
|
||||
<a href="<?php echo esc_url(home_url('/resources/buyers-guide/')); ?>" class="resource-card-link-overlay" aria-hidden="true" tabindex="-1"></a>
|
||||
<div class="resource-featured-icon">
|
||||
<svg width="64" height="64" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" aria-hidden="true">
|
||||
<path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/>
|
||||
@@ -59,13 +60,14 @@ $resource_pages = get_pages(array(
|
||||
<p class="resource-featured-description">
|
||||
Ready to buy your first home or next property? Our comprehensive guide walks you through every step of the home buying process, from getting pre-approved to closing day.
|
||||
</p>
|
||||
<a href="<?php echo esc_url(home_url('/resources/buyers-guide/')); ?>" class="btn btn-primary">
|
||||
<span class="btn btn-primary">
|
||||
Read the Buyer's Guide
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Seller's Guide -->
|
||||
<div class="resource-featured-card">
|
||||
<a href="<?php echo esc_url(home_url('/resources/sellers-guide/')); ?>" class="resource-card-link-overlay" aria-hidden="true" tabindex="-1"></a>
|
||||
<div class="resource-featured-icon">
|
||||
<svg width="64" height="64" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" aria-hidden="true">
|
||||
<rect x="2" y="7" width="20" height="14" rx="2" ry="2"/>
|
||||
@@ -78,9 +80,9 @@ $resource_pages = get_pages(array(
|
||||
<p class="resource-featured-description">
|
||||
Thinking about selling? Learn how to prepare your home for the market, price it right, and navigate the selling process to get the best possible return on your investment.
|
||||
</p>
|
||||
<a href="<?php echo esc_url(home_url('/resources/sellers-guide/')); ?>" class="btn btn-primary">
|
||||
<span class="btn btn-primary">
|
||||
Read the Seller's Guide
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -96,6 +98,7 @@ $resource_pages = get_pages(array(
|
||||
|
||||
<div class="resources-additional-grid">
|
||||
<div class="resource-card">
|
||||
<a href="<?php echo esc_url(home_url('/resources/moving-checklist/')); ?>" class="resource-card-link-overlay" aria-hidden="true" tabindex="-1"></a>
|
||||
<div class="resource-card-icon">
|
||||
<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" aria-hidden="true">
|
||||
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
|
||||
@@ -110,6 +113,7 @@ $resource_pages = get_pages(array(
|
||||
</div>
|
||||
|
||||
<div class="resource-card">
|
||||
<a href="<?php echo esc_url(home_url('/resources/mortgage-calculator/')); ?>" class="resource-card-link-overlay" aria-hidden="true" tabindex="-1"></a>
|
||||
<div class="resource-card-icon">
|
||||
<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" aria-hidden="true">
|
||||
<rect x="1" y="4" width="22" height="16" rx="2" ry="2"/>
|
||||
@@ -121,6 +125,7 @@ $resource_pages = get_pages(array(
|
||||
</div>
|
||||
|
||||
<div class="resource-card">
|
||||
<a href="<?php echo esc_url(home_url('/resources/faq/')); ?>" class="resource-card-link-overlay" aria-hidden="true" tabindex="-1"></a>
|
||||
<div class="resource-card-icon">
|
||||
<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" aria-hidden="true">
|
||||
<circle cx="12" cy="12" r="10"/>
|
||||
|
||||
@@ -52,18 +52,12 @@ while (have_posts()) :
|
||||
?>
|
||||
|
||||
<main id="primary" class="site-main single-property-main">
|
||||
<!-- Breadcrumbs -->
|
||||
<nav class="breadcrumbs" aria-label="Breadcrumb">
|
||||
<div class="container">
|
||||
<ol class="breadcrumb-list">
|
||||
<li><a href="<?php echo esc_url(home_url('/')); ?>">Home</a></li>
|
||||
<li><a href="<?php echo esc_url(get_post_type_archive_link('property')); ?>">Properties</a></li>
|
||||
<li aria-current="page"><?php echo esc_html($street_address ?: get_the_title()); ?></li>
|
||||
</ol>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="container">
|
||||
<!-- Property Address Header -->
|
||||
<header class="property-address-header">
|
||||
<h1 class="property-address-title"><?php echo esc_html($full_address ?: get_the_title()); ?></h1>
|
||||
</header>
|
||||
|
||||
<div class="single-property-layout">
|
||||
<!-- Main Content -->
|
||||
<div class="single-property-content">
|
||||
@@ -199,23 +193,19 @@ while (have_posts()) :
|
||||
<!-- Property Header -->
|
||||
<div class="sidebar-widget property-header-widget">
|
||||
<div class="property-header-top">
|
||||
<?php if ($type) : ?>
|
||||
<?php
|
||||
$type_slug = sanitize_title($type);
|
||||
$type_class = 'badge-type-' . $type_slug;
|
||||
?>
|
||||
<span class="badge <?php echo esc_attr($type_class); ?>"><?php echo esc_html($type); ?></span>
|
||||
<?php endif; ?>
|
||||
<?php if ($status) : ?>
|
||||
<span class="badge <?php echo esc_attr($status_class); ?>"><?php echo esc_html($status); ?></span>
|
||||
<?php endif; ?>
|
||||
<?php if ($type) : ?>
|
||||
<span class="property-type"><?php echo esc_html($type); ?></span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<h1 class="property-title"><?php echo esc_html(homeproz_format_price($price)); ?></h1>
|
||||
|
||||
<p class="property-address">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
|
||||
<path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"/>
|
||||
<circle cx="12" cy="10" r="3"/>
|
||||
</svg>
|
||||
<?php echo esc_html($full_address); ?>
|
||||
</p>
|
||||
<div class="property-title"><?php echo esc_html(homeproz_format_price($price)); ?></div>
|
||||
|
||||
<?php if ($mls_number) : ?>
|
||||
<p class="property-mls">MLS# <?php echo esc_html($mls_number); ?></p>
|
||||
@@ -224,7 +214,7 @@ while (have_posts()) :
|
||||
|
||||
<!-- Property Documents -->
|
||||
<div class="sidebar-widget property-documents-widget">
|
||||
<h3 class="widget-title">Property Documents</h3>
|
||||
<h3 class="widget-title">Property Documents (SAMPLE)</h3>
|
||||
<?php if ($property_documents && is_array($property_documents) && !empty($property_documents)) : ?>
|
||||
<div class="sidebar-documents-list">
|
||||
<?php foreach ($property_documents as $doc) :
|
||||
|
||||
@@ -34,7 +34,6 @@
|
||||
@import '../template-parts/components/hero-section-card.scss';
|
||||
@import '../template-parts/components/cta-section.scss';
|
||||
@import '../template-parts/components/testimonial.scss';
|
||||
@import '../template-parts/components/agent-card.scss';
|
||||
@import '../template-parts/components/feature-block.scss';
|
||||
@import '../template-parts/components/service-cards.scss';
|
||||
|
||||
@@ -61,7 +60,7 @@
|
||||
--font-body: 'Inter', 'Droid Sans', Arial, sans-serif;
|
||||
|
||||
// Spacing
|
||||
--container-max: 1200px;
|
||||
--container-max: 1400px;
|
||||
--container-padding: 1.5rem;
|
||||
|
||||
// Transitions (minimal, only functional)
|
||||
@@ -144,6 +143,7 @@ a {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.75rem 1.5rem;
|
||||
font-family: var(--font-body);
|
||||
font-weight: 600;
|
||||
@@ -154,6 +154,10 @@ a {
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
|
||||
svg {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
&-primary {
|
||||
background-color: var(--color-accent);
|
||||
color: white;
|
||||
|
||||
@@ -121,25 +121,31 @@
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.375rem;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
background-color: var(--color-bg-dark);
|
||||
padding: 0 0.75rem;
|
||||
background-color: transparent;
|
||||
border: 2px solid var(--color-accent);
|
||||
border-radius: 0.25rem;
|
||||
color: var(--color-text-muted);
|
||||
color: var(--color-accent);
|
||||
text-decoration: none;
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-accent);
|
||||
color: white;
|
||||
}
|
||||
|
||||
// Icon-only buttons (phone, email)
|
||||
&:not(.agent-action-profile) {
|
||||
width: 40px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
&.agent-action-profile {
|
||||
flex: 1;
|
||||
width: auto;
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
// Agent Photo
|
||||
.agent-photo-wrapper {
|
||||
flex-shrink: 0;
|
||||
background-color: #000;
|
||||
}
|
||||
|
||||
.agent-photo {
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Agent Card Component
|
||||
*
|
||||
* @package HomeProz
|
||||
*
|
||||
* Args:
|
||||
* - name (string): Agent name
|
||||
* - title (string): Agent title (e.g., "REALTOR")
|
||||
* - phone (string): Agent phone number
|
||||
* - email (string): Agent email
|
||||
* - photo (string): Photo URL or attachment ID
|
||||
* - bio (string): Short bio text
|
||||
*/
|
||||
|
||||
// Prevent direct access
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
// Get args with defaults
|
||||
$name = isset($args['name']) ? $args['name'] : '';
|
||||
$title = isset($args['title']) ? $args['title'] : 'REALTOR';
|
||||
$phone = isset($args['phone']) ? $args['phone'] : '';
|
||||
$email = isset($args['email']) ? $args['email'] : '';
|
||||
$photo = isset($args['photo']) ? $args['photo'] : '';
|
||||
$bio = isset($args['bio']) ? $args['bio'] : '';
|
||||
|
||||
if (!$name) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If photo is an ID, get the URL
|
||||
if (is_numeric($photo)) {
|
||||
$photo = wp_get_attachment_image_url($photo, 'medium');
|
||||
}
|
||||
?>
|
||||
|
||||
<div class="agent-card-component card">
|
||||
<div class="agent-card-photo">
|
||||
<?php if ($photo) : ?>
|
||||
<img src="<?php echo esc_url($photo); ?>" alt="<?php echo esc_attr($name); ?>" loading="lazy">
|
||||
<?php else : ?>
|
||||
<div class="agent-card-photo-placeholder">
|
||||
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" aria-hidden="true">
|
||||
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/>
|
||||
<circle cx="12" cy="7" r="4"/>
|
||||
</svg>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div class="agent-card-body">
|
||||
<h3 class="agent-card-name"><?php echo esc_html($name); ?></h3>
|
||||
<p class="agent-card-title"><?php echo esc_html($title); ?></p>
|
||||
|
||||
<?php if ($bio) : ?>
|
||||
<p class="agent-card-bio"><?php echo esc_html($bio); ?></p>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="agent-card-contact">
|
||||
<?php if ($phone) : ?>
|
||||
<a href="tel:<?php echo esc_attr(preg_replace('/[^0-9]/', '', $phone)); ?>" class="agent-card-phone">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
|
||||
<path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z"/>
|
||||
</svg>
|
||||
<?php echo esc_html($phone); ?>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($email) : ?>
|
||||
<a href="mailto:<?php echo esc_attr($email); ?>" class="agent-card-email">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
|
||||
<path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/>
|
||||
<polyline points="22,6 12,13 2,6"/>
|
||||
</svg>
|
||||
Email
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,133 +0,0 @@
|
||||
/**
|
||||
* Agent Card Component Styles
|
||||
*
|
||||
* @package HomeProz
|
||||
*/
|
||||
|
||||
.agent-card-component {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.agent-card-photo {
|
||||
margin-bottom: 1rem;
|
||||
|
||||
img {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
margin: 0 auto;
|
||||
}
|
||||
}
|
||||
|
||||
.agent-card-photo-placeholder {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
border-radius: 50%;
|
||||
background-color: var(--color-bg-dark);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 0 auto;
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
.agent-card-body {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.agent-card-name {
|
||||
font-family: var(--font-display);
|
||||
font-size: 1.25rem;
|
||||
color: var(--color-text);
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.agent-card-title {
|
||||
font-size: 0.75rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.1em;
|
||||
color: var(--color-accent-light);
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.agent-card-bio {
|
||||
font-size: 0.875rem;
|
||||
color: var(--color-text-muted);
|
||||
line-height: 1.6;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.agent-card-contact {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.agent-card-phone,
|
||||
.agent-card-email {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
font-size: 0.875rem;
|
||||
color: var(--color-text-muted);
|
||||
|
||||
&:hover {
|
||||
color: var(--color-accent-light);
|
||||
}
|
||||
|
||||
svg {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Team Section (for homepage/about page)
|
||||
.team-section {
|
||||
padding: 4rem 0;
|
||||
background-color: var(--color-bg-card);
|
||||
|
||||
@media (max-width: 768px) {
|
||||
padding: 3rem 0;
|
||||
}
|
||||
}
|
||||
|
||||
.team-section-header {
|
||||
text-align: center;
|
||||
margin-bottom: 3rem;
|
||||
}
|
||||
|
||||
.team-section-title {
|
||||
font-family: var(--font-display);
|
||||
font-size: 2.25rem;
|
||||
color: var(--color-text);
|
||||
margin-bottom: 0.75rem;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
font-size: 1.875rem;
|
||||
}
|
||||
}
|
||||
|
||||
.team-section-subtitle {
|
||||
font-size: 1.125rem;
|
||||
color: var(--color-text-muted);
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.team-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 1.5rem;
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
@@ -31,8 +31,10 @@
|
||||
background-color: #fff;
|
||||
color: var(--color-accent);
|
||||
|
||||
&:hover {
|
||||
&:hover,
|
||||
&:active {
|
||||
background-color: var(--color-text);
|
||||
color: #000;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ $title = isset($args['title']) ? $args['title'] : '';
|
||||
$subtitle = isset($args['subtitle']) ? $args['subtitle'] : '';
|
||||
$primary_cta_text = isset($args['primary_cta_text']) ? $args['primary_cta_text'] : '';
|
||||
$primary_cta_url = isset($args['primary_cta_url']) ? $args['primary_cta_url'] : '';
|
||||
$primary_cta_icon = isset($args['primary_cta_icon']) ? $args['primary_cta_icon'] : '';
|
||||
$secondary_cta_text = isset($args['secondary_cta_text']) ? $args['secondary_cta_text'] : '';
|
||||
$secondary_cta_url = isset($args['secondary_cta_url']) ? $args['secondary_cta_url'] : '';
|
||||
$background_image = isset($args['background_image']) ? $args['background_image'] : '';
|
||||
@@ -78,6 +79,13 @@ if ($show_location_search) {
|
||||
<div class="hero-section-actions">
|
||||
<?php if ($primary_cta_text && $primary_cta_url) : ?>
|
||||
<a href="<?php echo esc_url($primary_cta_url); ?>" class="btn btn-primary">
|
||||
<?php if ($primary_cta_icon === 'map') : ?>
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
|
||||
<polygon points="1 6 1 22 8 18 16 22 23 18 23 2 16 6 8 2 1 6"/>
|
||||
<line x1="8" y1="2" x2="8" y2="18"/>
|
||||
<line x1="16" y1="6" x2="16" y2="22"/>
|
||||
</svg>
|
||||
<?php endif; ?>
|
||||
<?php echo esc_html($primary_cta_text); ?>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
|
||||
@@ -70,13 +70,13 @@
|
||||
left: 32vw;
|
||||
}
|
||||
|
||||
@media (min-width: 1400px) and (max-width: 1649px) {
|
||||
// 1400-1650px: left edge 150px from left of page
|
||||
@media (min-width: 1500px) and (max-width: 1649px) {
|
||||
// 1500-1650px: left edge 150px from left of page
|
||||
// Left edge = 150px, center = 150px + 260px = 410px
|
||||
left: 410px;
|
||||
}
|
||||
|
||||
// Below 1400px: stays centered at 50vw (default)
|
||||
// Below 1500px: stays centered at 50vw (default)
|
||||
|
||||
@media (max-width: 768px) {
|
||||
position: relative;
|
||||
@@ -111,7 +111,7 @@
|
||||
line-height: 1.1;
|
||||
|
||||
@media (max-width: 1450px) {
|
||||
font-size: 32px;
|
||||
font-size: 42px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
* - subtitle (string): Hero subheadline
|
||||
* - primary_cta_text (string): Primary button text
|
||||
* - primary_cta_url (string): Primary button URL
|
||||
* - primary_cta_icon (string): Primary button icon ('map', 'search', 'arrow', or custom SVG)
|
||||
* - secondary_cta_text (string): Secondary button text
|
||||
* - secondary_cta_url (string): Secondary button URL
|
||||
* - background_image (string): Background image URL
|
||||
@@ -29,6 +30,7 @@ $title = isset($args['title']) ? $args['title'] : '';
|
||||
$subtitle = isset($args['subtitle']) ? $args['subtitle'] : '';
|
||||
$primary_cta_text = isset($args['primary_cta_text']) ? $args['primary_cta_text'] : '';
|
||||
$primary_cta_url = isset($args['primary_cta_url']) ? $args['primary_cta_url'] : '';
|
||||
$primary_cta_icon = isset($args['primary_cta_icon']) ? $args['primary_cta_icon'] : '';
|
||||
$secondary_cta_text = isset($args['secondary_cta_text']) ? $args['secondary_cta_text'] : '';
|
||||
$secondary_cta_url = isset($args['secondary_cta_url']) ? $args['secondary_cta_url'] : '';
|
||||
$background_image = isset($args['background_image']) ? $args['background_image'] : '';
|
||||
@@ -95,6 +97,13 @@ if ($show_location_search) {
|
||||
<div class="hero-section-actions">
|
||||
<?php if ($primary_cta_text && $primary_cta_url) : ?>
|
||||
<a href="<?php echo esc_url($primary_cta_url); ?>" class="btn btn-primary">
|
||||
<?php if ($primary_cta_icon === 'map') : ?>
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
|
||||
<polygon points="1 6 1 22 8 18 16 22 23 18 23 2 16 6 8 2 1 6"/>
|
||||
<line x1="8" y1="2" x2="8" y2="18"/>
|
||||
<line x1="16" y1="6" x2="16" y2="22"/>
|
||||
</svg>
|
||||
<?php endif; ?>
|
||||
<?php echo esc_html($primary_cta_text); ?>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
|
||||
@@ -41,13 +41,14 @@ $services = array(
|
||||
<section class="service-cards-section">
|
||||
<div class="container">
|
||||
<header class="service-cards-header">
|
||||
<h2 class="service-cards-title">How Can We Help?</h2>
|
||||
<h2 class="service-cards-title">Go With The Proz</h2>
|
||||
<p class="service-cards-subtitle">Whether you're buying, renting, or selling, we're here to guide you every step of the way.</p>
|
||||
</header>
|
||||
|
||||
<div class="service-cards-grid">
|
||||
<?php foreach ($services as $service) : ?>
|
||||
<div class="service-card">
|
||||
<a href="<?php echo esc_url($service['button_url']); ?>" class="service-card-link-overlay" aria-hidden="true" tabindex="-1"></a>
|
||||
<div class="service-card-icon">
|
||||
<?php if ($service['icon'] === 'home') : ?>
|
||||
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" aria-hidden="true">
|
||||
@@ -69,12 +70,12 @@ $services = array(
|
||||
</div>
|
||||
<h3 class="service-card-title"><?php echo esc_html($service['title']); ?></h3>
|
||||
<p class="service-card-description"><?php echo esc_html($service['description']); ?></p>
|
||||
<a href="<?php echo esc_url($service['button_url']); ?>" class="btn btn-outline service-card-btn">
|
||||
<span class="btn btn-outline service-card-btn">
|
||||
<?php echo esc_html($service['button_text']); ?>
|
||||
<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>
|
||||
</span>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
@@ -48,19 +48,33 @@
|
||||
}
|
||||
|
||||
.service-card {
|
||||
position: relative;
|
||||
background-color: var(--color-bg-dark);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 0.5rem;
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
transition: border-color 0.2s ease, transform 0.2s ease;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
border-color: var(--color-accent);
|
||||
transform: translateY(-4px);
|
||||
|
||||
.service-card-btn {
|
||||
background-color: var(--color-accent);
|
||||
color: var(--color-text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.service-card-link-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.service-card-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -19,14 +19,41 @@
|
||||
}
|
||||
}
|
||||
|
||||
.about-story-content {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
.about-story-layout {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 2fr;
|
||||
gap: 3rem;
|
||||
align-items: center;
|
||||
|
||||
@media (max-width: 1300px) {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
.about-story-image {
|
||||
img {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
border-radius: 0.5rem;
|
||||
display: block;
|
||||
}
|
||||
|
||||
@media (max-width: 1300px) {
|
||||
max-width: 500px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
}
|
||||
|
||||
.about-story-content {
|
||||
h2, h3 {
|
||||
font-family: var(--font-display);
|
||||
color: var(--color-text);
|
||||
margin-top: 2rem;
|
||||
|
||||
&:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
p {
|
||||
@@ -95,20 +122,6 @@
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.about-team-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 1.5rem;
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
// Broker Section
|
||||
.about-broker-section {
|
||||
padding: 3rem 0;
|
||||
|
||||
@@ -181,7 +181,8 @@
|
||||
}
|
||||
|
||||
.contact-info-label {
|
||||
font-size: 0.875rem;
|
||||
font-family: 'Times New Roman', Times, serif;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
color: var(--color-text);
|
||||
margin-bottom: 0.25rem;
|
||||
|
||||
@@ -25,18 +25,32 @@
|
||||
}
|
||||
|
||||
.resource-featured-card {
|
||||
position: relative;
|
||||
background-color: var(--color-bg-dark);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 0.5rem;
|
||||
padding: 2.5rem;
|
||||
text-align: center;
|
||||
transition: border-color 0.2s ease;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
border-color: var(--color-accent);
|
||||
|
||||
.btn {
|
||||
background-color: var(--color-accent-hover);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.resource-card-link-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.resource-featured-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -100,11 +114,12 @@
|
||||
}
|
||||
|
||||
.resource-card {
|
||||
position: relative;
|
||||
background-color: var(--color-bg-card);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 0.5rem;
|
||||
padding: 1.5rem;
|
||||
transition: border-color 0.2s ease;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
border-color: var(--color-accent);
|
||||
|
||||
@@ -79,6 +79,13 @@ $license_info = 'MN License #40229984';
|
||||
<div class="footer-column footer-contact">
|
||||
<h4 class="footer-heading">Contact Us</h4>
|
||||
<ul class="contact-list">
|
||||
<li class="contact-item">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
|
||||
<path d="M22 2L11 13"/>
|
||||
<path d="M22 2L15 22L11 13L2 9L22 2Z"/>
|
||||
</svg>
|
||||
<a href="<?php echo esc_url(home_url('/contact/')); ?>">Send Us a Message</a>
|
||||
</li>
|
||||
<?php if ($phone) : ?>
|
||||
<li class="contact-item">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
|
||||
@@ -102,7 +109,7 @@ $license_info = 'MN License #40229984';
|
||||
<path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"/>
|
||||
<circle cx="12" cy="10" r="3"/>
|
||||
</svg>
|
||||
<span><?php echo esc_html($address); ?></span>
|
||||
<a href="https://www.google.com/maps/search/?api=1&query=<?php echo urlencode($address); ?>" target="_blank" rel="noopener"><?php echo esc_html($address); ?></a>
|
||||
</li>
|
||||
<?php endif; ?>
|
||||
</ul>
|
||||
@@ -122,31 +129,51 @@ $license_info = 'MN License #40229984';
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Professional Associations & License Info -->
|
||||
<div class="footer-credentials">
|
||||
<div class="footer-logos">
|
||||
<!-- REALTOR Logo -->
|
||||
<div class="credential-logo" title="REALTOR">
|
||||
<svg width="40" height="40" viewBox="0 0 24 24" fill="currentColor" aria-label="REALTOR">
|
||||
<text x="50%" y="50%" dominant-baseline="middle" text-anchor="middle" font-size="6" font-weight="bold">R</text>
|
||||
<circle cx="12" cy="12" r="11" fill="none" stroke="currentColor" stroke-width="1"/>
|
||||
<!-- Legal & Compliance Section -->
|
||||
<div class="footer-legal">
|
||||
<div class="footer-legal-inner">
|
||||
<!-- Privacy Policy -->
|
||||
<a href="<?php echo esc_url(home_url('/privacy-policy/')); ?>" class="legal-item">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
|
||||
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/>
|
||||
</svg>
|
||||
</div>
|
||||
<!-- Equal Housing Logo -->
|
||||
<div class="credential-logo" title="Equal Housing Opportunity">
|
||||
<svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" aria-label="Equal Housing Opportunity">
|
||||
<span>Privacy Policy</span>
|
||||
</a>
|
||||
|
||||
<!-- Fair Housing -->
|
||||
<a href="<?php echo esc_url(home_url('/fair-housing-statement/')); ?>" class="legal-item">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
|
||||
<path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/>
|
||||
<line x1="8" y1="14" x2="16" y2="14"/>
|
||||
<line x1="8" y1="17" x2="16" y2="17"/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<p class="footer-license"><?php echo esc_html($license_info); ?></p>
|
||||
</div>
|
||||
<span>Fair Housing</span>
|
||||
</a>
|
||||
|
||||
<!-- Temporary Alt Page Link -->
|
||||
<div class="footer-temp-link">
|
||||
<a href="<?php echo esc_url(home_url('/home-page-alt/')); ?>">HOME PAGE ALT</a>
|
||||
<!-- MLS Disclaimer -->
|
||||
<a href="<?php echo esc_url(home_url('/mls-disclaimer/')); ?>" class="legal-item">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
|
||||
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
|
||||
<polyline points="14 2 14 8 20 8"/>
|
||||
<line x1="16" y1="13" x2="8" y2="13"/>
|
||||
<line x1="16" y1="17" x2="8" y2="17"/>
|
||||
</svg>
|
||||
<span>MLS Disclaimer</span>
|
||||
</a>
|
||||
|
||||
<!-- Brokerage Disclosure -->
|
||||
<a href="<?php echo esc_url(home_url('/brokerage-disclosure/')); ?>" class="legal-item">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
|
||||
<path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"/>
|
||||
<rect x="8" y="2" width="8" height="4" rx="1" ry="1"/>
|
||||
<line x1="12" y1="11" x2="12" y2="17"/>
|
||||
<line x1="9" y1="14" x2="15" y2="14"/>
|
||||
</svg>
|
||||
<span>Brokerage Disclosure</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<p class="footer-license"><?php echo esc_html($license_info); ?></p>
|
||||
</div>
|
||||
|
||||
<!-- Footer Bottom -->
|
||||
@@ -155,7 +182,7 @@ $license_info = 'MN License #40229984';
|
||||
© <?php echo date('Y'); ?> <?php bloginfo('name'); ?>. All rights reserved.
|
||||
</p>
|
||||
<p class="footer-credits">
|
||||
Powered by <a href="https://developer.wordpress.org/" target="_blank" rel="noopener noreferrer">WordPress</a>
|
||||
Web Design by <a href="https://hanson.xyz" target="_blank" rel="noopener">HansonXyz</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -192,33 +192,59 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Credentials Section
|
||||
.footer-credentials {
|
||||
// Legal Section
|
||||
.footer-legal {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
gap: 1.25rem;
|
||||
padding-top: 2rem;
|
||||
margin-top: 2rem;
|
||||
border-top: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.footer-logos {
|
||||
.footer-legal-inner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: 1.5rem;
|
||||
|
||||
@media (max-width: 640px) {
|
||||
gap: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.credential-logo {
|
||||
.legal-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.5rem 1rem;
|
||||
background-color: var(--color-bg-dark);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 0.25rem;
|
||||
color: var(--color-text-muted);
|
||||
opacity: 0.7;
|
||||
font-size: 0.8125rem;
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
border-color: var(--color-accent);
|
||||
color: var(--color-accent-light);
|
||||
|
||||
svg {
|
||||
color: var(--color-accent);
|
||||
}
|
||||
}
|
||||
|
||||
svg {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
flex-shrink: 0;
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
width: calc(50% - 0.5rem);
|
||||
justify-content: center;
|
||||
padding: 0.625rem 0.75rem;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -38,8 +38,9 @@ if ($state) {
|
||||
}
|
||||
?>
|
||||
|
||||
<article id="property-<?php echo esc_attr($property_id); ?>" <?php post_class('property-card card'); ?>>
|
||||
<a href="<?php the_permalink(); ?>" class="property-card-image">
|
||||
<article id="property-<?php echo esc_attr($property_id); ?>" data-property-id="<?php echo esc_attr($property_id); ?>" <?php post_class('property-card card'); ?>>
|
||||
<a href="<?php the_permalink(); ?>" class="property-card-link-overlay" aria-hidden="true" tabindex="-1"></a>
|
||||
<div class="property-card-image">
|
||||
<?php if (has_post_thumbnail()) : ?>
|
||||
<?php the_post_thumbnail('property-card', array('loading' => 'lazy')); ?>
|
||||
<?php else : ?>
|
||||
@@ -56,7 +57,7 @@ if ($state) {
|
||||
<?php echo esc_html($status); ?>
|
||||
</span>
|
||||
<?php endif; ?>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="property-card-content">
|
||||
<div class="property-card-price">
|
||||
@@ -64,9 +65,7 @@ if ($state) {
|
||||
</div>
|
||||
|
||||
<h3 class="property-card-title">
|
||||
<a href="<?php the_permalink(); ?>">
|
||||
<?php echo esc_html($full_address ?: get_the_title()); ?>
|
||||
</a>
|
||||
<?php echo esc_html($full_address ?: get_the_title()); ?>
|
||||
</h3>
|
||||
|
||||
<?php if ($bedrooms || $bathrooms || $square_feet) : ?>
|
||||
@@ -107,11 +106,11 @@ if ($state) {
|
||||
</p>
|
||||
<?php endif; ?>
|
||||
|
||||
<a href="<?php the_permalink(); ?>" class="property-card-link">
|
||||
<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>
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
@@ -12,6 +12,26 @@
|
||||
border: 1px solid var(--color-accent);
|
||||
border-radius: 0.5rem;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
border-color: var(--color-accent-hover);
|
||||
|
||||
.property-card-link {
|
||||
color: var(--color-accent-hover);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Stretched link overlay - makes entire card clickable
|
||||
.property-card-link-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.property-card-image {
|
||||
@@ -64,15 +84,7 @@
|
||||
font-weight: 500;
|
||||
line-height: 1.4;
|
||||
margin-bottom: 0.75rem;
|
||||
|
||||
a {
|
||||
color: var(--color-text-muted);
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
color: var(--color-accent-light);
|
||||
}
|
||||
}
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
// Property specs (beds, baths, sqft)
|
||||
|
||||
@@ -9,6 +9,266 @@
|
||||
(function($) {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Property Map Manager
|
||||
*/
|
||||
var PropertyMap = {
|
||||
map: null,
|
||||
markers: {}, // Object keyed by property ID
|
||||
markerLayer: null,
|
||||
selectedPropertyId: null,
|
||||
hoveredPropertyId: null,
|
||||
baseZIndex: 400,
|
||||
|
||||
/**
|
||||
* Initialize the map
|
||||
*/
|
||||
init: function(initialProperties) {
|
||||
var $mapContainer = $('#property-map');
|
||||
if (!$mapContainer.length || typeof L === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialize map centered on Albert Lea area
|
||||
this.map = L.map('property-map').setView([43.6480, -93.3685], 10);
|
||||
|
||||
// Add OpenStreetMap tiles
|
||||
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>'
|
||||
}).addTo(this.map);
|
||||
|
||||
// Create a layer group for markers
|
||||
this.markerLayer = L.layerGroup().addTo(this.map);
|
||||
|
||||
// Add initial markers
|
||||
if (initialProperties && initialProperties.length > 0) {
|
||||
this.updateMarkers(initialProperties);
|
||||
}
|
||||
|
||||
// Bind card hover events
|
||||
this.bindCardHoverEvents();
|
||||
},
|
||||
|
||||
/**
|
||||
* Create marker icon with specified color
|
||||
*/
|
||||
createIcon: function(color) {
|
||||
color = color || 'red';
|
||||
return L.divIcon({
|
||||
className: 'property-marker property-marker-' + color,
|
||||
html: '<div class="marker-pin"></div>',
|
||||
iconSize: [30, 40],
|
||||
iconAnchor: [15, 40],
|
||||
popupAnchor: [0, -40]
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Update map markers with new property data
|
||||
*/
|
||||
updateMarkers: function(properties) {
|
||||
if (!this.map || !this.markerLayer) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear existing markers and reset state
|
||||
this.markerLayer.clearLayers();
|
||||
this.markers = {};
|
||||
this.selectedPropertyId = null;
|
||||
this.hoveredPropertyId = null;
|
||||
|
||||
// Reset any highlighted cards
|
||||
$('.property-card').removeClass('property-card-highlighted');
|
||||
|
||||
var self = this;
|
||||
properties.forEach(function(prop, index) {
|
||||
if (prop.lat && prop.lng) {
|
||||
var marker = L.marker([prop.lat, prop.lng], {
|
||||
icon: self.createIcon('red'),
|
||||
zIndexOffset: self.baseZIndex + index
|
||||
});
|
||||
|
||||
// Store property ID on marker
|
||||
marker.propertyId = prop.id;
|
||||
marker.defaultZIndex = self.baseZIndex + index;
|
||||
|
||||
// Bind popup
|
||||
marker.bindPopup(
|
||||
'<div class="map-popup">' +
|
||||
'<strong>' + prop.price + '</strong><br>' +
|
||||
'<span>' + prop.address + '</span><br>' +
|
||||
'<a href="' + prop.url + '">View Details</a>' +
|
||||
'</div>'
|
||||
);
|
||||
|
||||
// Handle marker click
|
||||
marker.on('click', function(e) {
|
||||
self.onMarkerClick(prop.id);
|
||||
});
|
||||
|
||||
self.markerLayer.addLayer(marker);
|
||||
self.markers[prop.id] = marker;
|
||||
}
|
||||
});
|
||||
|
||||
// Fit bounds to show all markers
|
||||
this.fitBounds(properties);
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle marker click - scroll to card and highlight
|
||||
*/
|
||||
onMarkerClick: function(propertyId) {
|
||||
var self = this;
|
||||
|
||||
// If clicking the same pin, do nothing
|
||||
if (this.selectedPropertyId === propertyId) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Reset previous selection
|
||||
if (this.selectedPropertyId) {
|
||||
this.setMarkerColor(this.selectedPropertyId, 'red');
|
||||
this.resetMarkerZIndex(this.selectedPropertyId);
|
||||
$('#property-' + this.selectedPropertyId).removeClass('property-card-highlighted');
|
||||
}
|
||||
|
||||
// Set new selection
|
||||
this.selectedPropertyId = propertyId;
|
||||
this.setMarkerColor(propertyId, 'amber');
|
||||
this.setMarkerZIndex(propertyId, 10000); // Amber on top
|
||||
|
||||
// Find the card
|
||||
var $card = $('#property-' + propertyId);
|
||||
if (!$card.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if card is in view
|
||||
var cardTop = $card.offset().top;
|
||||
var cardBottom = cardTop + $card.outerHeight();
|
||||
var viewportTop = $(window).scrollTop();
|
||||
var viewportBottom = viewportTop + $(window).height();
|
||||
var isInView = cardTop >= viewportTop && cardBottom <= viewportBottom;
|
||||
|
||||
if (isInView) {
|
||||
// Already in view, flash immediately
|
||||
self.flashCard($card);
|
||||
} else {
|
||||
// Scroll then flash
|
||||
$('html, body').animate({
|
||||
scrollTop: cardTop - 120
|
||||
}, 400, function() {
|
||||
self.flashCard($card);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Flash card border amber animation
|
||||
*/
|
||||
flashCard: function($card) {
|
||||
var self = this;
|
||||
$card.removeClass('property-card-highlighted');
|
||||
|
||||
// Flash sequence: amber -> red -> amber -> stay amber
|
||||
setTimeout(function() {
|
||||
$card.addClass('property-card-highlighted');
|
||||
setTimeout(function() {
|
||||
$card.removeClass('property-card-highlighted');
|
||||
setTimeout(function() {
|
||||
$card.addClass('property-card-highlighted');
|
||||
// Leave it highlighted
|
||||
}, 150);
|
||||
}, 150);
|
||||
}, 50);
|
||||
},
|
||||
|
||||
/**
|
||||
* Set marker color
|
||||
*/
|
||||
setMarkerColor: function(propertyId, color) {
|
||||
var marker = this.markers[propertyId];
|
||||
if (marker) {
|
||||
marker.setIcon(this.createIcon(color));
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Set marker z-index
|
||||
*/
|
||||
setMarkerZIndex: function(propertyId, zIndex) {
|
||||
var marker = this.markers[propertyId];
|
||||
if (marker) {
|
||||
marker.setZIndexOffset(zIndex);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Reset marker to default z-index
|
||||
*/
|
||||
resetMarkerZIndex: function(propertyId) {
|
||||
var marker = this.markers[propertyId];
|
||||
if (marker) {
|
||||
marker.setZIndexOffset(marker.defaultZIndex);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Bind card hover events (delegated for AJAX-loaded content)
|
||||
*/
|
||||
bindCardHoverEvents: function() {
|
||||
var self = this;
|
||||
|
||||
$(document).on('mouseenter', '.property-card[data-property-id]', function() {
|
||||
var propertyId = $(this).data('property-id');
|
||||
|
||||
// Don't change if this is the selected (amber) card
|
||||
if (propertyId === self.selectedPropertyId) {
|
||||
return;
|
||||
}
|
||||
|
||||
self.hoveredPropertyId = propertyId;
|
||||
self.setMarkerColor(propertyId, 'blue');
|
||||
self.setMarkerZIndex(propertyId, 9000); // Blue below amber but above red
|
||||
});
|
||||
|
||||
$(document).on('mouseleave', '.property-card[data-property-id]', function() {
|
||||
var propertyId = $(this).data('property-id');
|
||||
|
||||
// Don't change if this is the selected (amber) card
|
||||
if (propertyId === self.selectedPropertyId) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (self.hoveredPropertyId === propertyId) {
|
||||
self.hoveredPropertyId = null;
|
||||
}
|
||||
|
||||
self.setMarkerColor(propertyId, 'red');
|
||||
self.resetMarkerZIndex(propertyId);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Fit map bounds to show all properties
|
||||
*/
|
||||
fitBounds: function(properties) {
|
||||
if (!this.map || !properties || properties.length === 0) {
|
||||
// Reset to default view if no properties
|
||||
if (this.map) {
|
||||
this.map.setView([43.6480, -93.3685], 10);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var bounds = L.latLngBounds(properties.map(function(p) {
|
||||
return [p.lat, p.lng];
|
||||
}));
|
||||
this.map.fitBounds(bounds, { padding: [50, 50] });
|
||||
}
|
||||
};
|
||||
|
||||
var PropertyFilters = {
|
||||
// Elements
|
||||
$form: null,
|
||||
@@ -140,6 +400,11 @@
|
||||
self.$results.html(response.data.html);
|
||||
self.isFirstLoad = false;
|
||||
|
||||
// Update map markers if map is active
|
||||
if (response.data.markers) {
|
||||
PropertyMap.updateMarkers(response.data.markers);
|
||||
}
|
||||
|
||||
// Update URL
|
||||
if (updateHistory) {
|
||||
self.updateUrl(formData, page);
|
||||
@@ -231,9 +496,80 @@
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Responsive View Manager
|
||||
* Handles switching between map and grid views based on viewport width
|
||||
*/
|
||||
var ResponsiveView = {
|
||||
breakpoint: 1024,
|
||||
isMapView: true, // Track if user selected map view
|
||||
isAboveBreakpoint: true,
|
||||
mapInitialized: false,
|
||||
|
||||
init: function() {
|
||||
var self = this;
|
||||
|
||||
// Get initial view state from PHP
|
||||
if (typeof homeprozMapData !== 'undefined') {
|
||||
this.isMapView = homeprozMapData.isMapView !== false;
|
||||
}
|
||||
|
||||
// Check initial viewport state
|
||||
this.isAboveBreakpoint = window.innerWidth >= this.breakpoint;
|
||||
|
||||
// Initialize map if above breakpoint, map view selected, and we have data
|
||||
if (this.isAboveBreakpoint && this.isMapView && typeof homeprozMapData !== 'undefined' && homeprozMapData.properties) {
|
||||
PropertyMap.init(homeprozMapData.properties);
|
||||
this.mapInitialized = true;
|
||||
}
|
||||
|
||||
// Handle resize
|
||||
var resizeTimeout;
|
||||
$(window).on('resize', function() {
|
||||
clearTimeout(resizeTimeout);
|
||||
resizeTimeout = setTimeout(function() {
|
||||
self.handleResize();
|
||||
}, 150);
|
||||
});
|
||||
},
|
||||
|
||||
handleResize: function() {
|
||||
var wasAbove = this.isAboveBreakpoint;
|
||||
this.isAboveBreakpoint = window.innerWidth >= this.breakpoint;
|
||||
var $main = $('.property-archive-main');
|
||||
|
||||
// Crossing from below to above breakpoint
|
||||
if (!wasAbove && this.isAboveBreakpoint) {
|
||||
// Restore the user's view preference
|
||||
if (this.isMapView) {
|
||||
$main.removeClass('is-grid-view').addClass('is-map-view');
|
||||
|
||||
// Initialize map if not already done
|
||||
if (!this.mapInitialized && typeof homeprozMapData !== 'undefined') {
|
||||
PropertyMap.init(homeprozMapData.properties);
|
||||
this.mapInitialized = true;
|
||||
} else if (PropertyMap.map) {
|
||||
// Invalidate size to fix map rendering after show
|
||||
setTimeout(function() {
|
||||
PropertyMap.map.invalidateSize();
|
||||
}, 100);
|
||||
}
|
||||
} else {
|
||||
$main.removeClass('is-map-view').addClass('is-grid-view');
|
||||
}
|
||||
}
|
||||
// No need to do anything when crossing below - CSS handles hiding map
|
||||
},
|
||||
|
||||
setMapView: function(isMap) {
|
||||
this.isMapView = isMap;
|
||||
}
|
||||
};
|
||||
|
||||
// Initialize on document ready
|
||||
$(function() {
|
||||
PropertyFilters.init();
|
||||
ResponsiveView.init();
|
||||
});
|
||||
|
||||
})(jQuery);
|
||||
|
||||
@@ -102,15 +102,42 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Map + List Layout
|
||||
// Responsive View Containers
|
||||
// Below 1024px: always show grid, hide map layout and view toggle
|
||||
// Above 1024px: show based on view selection
|
||||
|
||||
.property-map-layout {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 1.5rem;
|
||||
display: none; // Hidden by default (mobile-first)
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 2rem;
|
||||
// Show map layout when in map view above breakpoint
|
||||
.is-map-view & {
|
||||
display: grid;
|
||||
grid-template-columns: 38% 1fr;
|
||||
gap: 2rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.grid-view-container {
|
||||
display: block; // Visible by default (mobile)
|
||||
|
||||
// Hide view toggle below 1024px
|
||||
.view-toggle {
|
||||
display: none;
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
// Above breakpoint, only show if grid view selected
|
||||
display: none;
|
||||
|
||||
.is-grid-view & {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,6 +149,11 @@
|
||||
top: 100px;
|
||||
height: calc(50vh - 75px);
|
||||
}
|
||||
|
||||
// View toggle inside map container
|
||||
.view-toggle {
|
||||
margin-bottom: 1.4rem;
|
||||
}
|
||||
}
|
||||
|
||||
.property-map {
|
||||
@@ -133,17 +165,26 @@
|
||||
border: 1px solid var(--color-border);
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
height: 100%;
|
||||
height: calc(100% - 44px); // Account for view toggle height + margin
|
||||
}
|
||||
}
|
||||
|
||||
.property-list-container {
|
||||
.property-grid {
|
||||
.properties-meta {
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.properties-grid {
|
||||
grid-template-columns: 1fr;
|
||||
|
||||
@media (min-width: 640px) {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,6 +214,22 @@
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
// Amber marker (selected)
|
||||
&.property-marker-amber .marker-pin {
|
||||
background: #F59E0B;
|
||||
}
|
||||
|
||||
// Blue marker (hovered)
|
||||
&.property-marker-blue .marker-pin {
|
||||
background: #3B82F6;
|
||||
}
|
||||
}
|
||||
|
||||
// Highlighted property card (amber border)
|
||||
.property-card-highlighted {
|
||||
border-color: #F59E0B !important;
|
||||
box-shadow: 0 0 0 2px rgba(245, 158, 11, 0.3);
|
||||
}
|
||||
|
||||
// Map Popup
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* Property Gallery JavaScript
|
||||
*
|
||||
* Lightbox and thumbnail navigation
|
||||
* Lightbox, thumbnail navigation, autoplay with fade transitions, and swipe support
|
||||
*
|
||||
* @package HomeProz
|
||||
*/
|
||||
@@ -14,13 +14,34 @@
|
||||
$gallery: null,
|
||||
$lightbox: null,
|
||||
$mainImage: null,
|
||||
$mainImageContainer: null,
|
||||
$thumbnails: null,
|
||||
$thumbnailsContainer: null,
|
||||
$thumbnailsViewport: null,
|
||||
$playbackBtn: null,
|
||||
$prevBtn: null,
|
||||
$nextBtn: null,
|
||||
$lightboxImage: null,
|
||||
$lightboxImageContainer: null,
|
||||
$lightboxCounter: null,
|
||||
|
||||
// State
|
||||
images: [],
|
||||
currentIndex: 0,
|
||||
isPlaying: true,
|
||||
isTransitioning: false,
|
||||
autoplayInterval: null,
|
||||
autoplayDelay: 5000,
|
||||
fadeDuration: 1000,
|
||||
slideDuration: 300,
|
||||
thumbnailsPerPage: 5,
|
||||
thumbnailPage: 0,
|
||||
|
||||
// Swipe state
|
||||
swipeStartX: 0,
|
||||
swipeStartY: 0,
|
||||
swipeThreshold: 50,
|
||||
isSwiping: false,
|
||||
|
||||
/**
|
||||
* Initialize
|
||||
@@ -33,9 +54,16 @@
|
||||
return;
|
||||
}
|
||||
|
||||
this.$mainImage = this.$gallery.find('.gallery-main-image img');
|
||||
this.$mainImageContainer = this.$gallery.find('.gallery-main-image');
|
||||
this.$mainImage = this.$mainImageContainer.find('img');
|
||||
this.$thumbnailsContainer = this.$gallery.find('.gallery-thumbnails-container');
|
||||
this.$thumbnailsViewport = this.$gallery.find('.gallery-thumbnails-viewport');
|
||||
this.$thumbnails = this.$gallery.find('.gallery-thumbnail');
|
||||
this.$playbackBtn = this.$gallery.find('.gallery-playback-btn');
|
||||
this.$prevBtn = this.$gallery.find('.gallery-thumbnails-prev');
|
||||
this.$nextBtn = this.$gallery.find('.gallery-thumbnails-next');
|
||||
this.$lightboxImage = this.$lightbox.find('.lightbox-image');
|
||||
this.$lightboxImageContainer = this.$lightbox.find('.lightbox-image-container');
|
||||
this.$lightboxCounter = this.$lightbox.find('.lightbox-current');
|
||||
|
||||
// Load images data
|
||||
@@ -48,7 +76,28 @@
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate thumbnails per page based on viewport
|
||||
this.calculateThumbnailsPerPage();
|
||||
|
||||
this.bindEvents();
|
||||
this.bindSwipeEvents();
|
||||
this.updateThumbnailNavigation();
|
||||
|
||||
// Start autoplay only if more than 1 image
|
||||
if (this.images.length > 1) {
|
||||
this.startAutoplay();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Calculate thumbnails per page based on viewport width
|
||||
*/
|
||||
calculateThumbnailsPerPage: function() {
|
||||
if ($(window).width() <= 640) {
|
||||
this.thumbnailsPerPage = 4;
|
||||
} else {
|
||||
this.thumbnailsPerPage = 5;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -57,14 +106,42 @@
|
||||
bindEvents: function() {
|
||||
var self = this;
|
||||
|
||||
// Thumbnail clicks
|
||||
this.$thumbnails.on('click', function() {
|
||||
// Thumbnail clicks - stop autoplay, instant transition
|
||||
this.$thumbnails.on('click', function(e) {
|
||||
e.stopPropagation();
|
||||
var index = parseInt($(this).data('index'));
|
||||
self.setMainImage(index);
|
||||
self.stopAutoplay();
|
||||
self.setMainImage(index, false); // false = no fade
|
||||
});
|
||||
|
||||
// Open lightbox
|
||||
this.$gallery.find('[data-lightbox-trigger]').on('click', function() {
|
||||
// Play/pause button
|
||||
this.$playbackBtn.on('click', function(e) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
if (self.isPlaying) {
|
||||
self.stopAutoplay();
|
||||
} else {
|
||||
self.startAutoplay();
|
||||
}
|
||||
});
|
||||
|
||||
// Thumbnail navigation buttons
|
||||
this.$prevBtn.on('click', function() {
|
||||
self.prevThumbnailPage();
|
||||
});
|
||||
|
||||
this.$nextBtn.on('click', function() {
|
||||
self.nextThumbnailPage();
|
||||
});
|
||||
|
||||
// Open lightbox - stop autoplay
|
||||
this.$gallery.find('[data-lightbox-trigger]').on('click', function(e) {
|
||||
// Don't open if we just finished a swipe
|
||||
if (self.isSwiping) {
|
||||
self.isSwiping = false;
|
||||
return;
|
||||
}
|
||||
self.stopAutoplay();
|
||||
self.openLightbox(self.currentIndex);
|
||||
});
|
||||
|
||||
@@ -73,13 +150,13 @@
|
||||
self.closeLightbox();
|
||||
});
|
||||
|
||||
// Navigation
|
||||
// Lightbox navigation
|
||||
this.$lightbox.find('.lightbox-prev').on('click', function() {
|
||||
self.prevImage();
|
||||
self.slideLightboxImage('prev');
|
||||
});
|
||||
|
||||
this.$lightbox.find('.lightbox-next').on('click', function() {
|
||||
self.nextImage();
|
||||
self.slideLightboxImage('next');
|
||||
});
|
||||
|
||||
// Keyboard navigation
|
||||
@@ -93,33 +170,376 @@
|
||||
self.closeLightbox();
|
||||
break;
|
||||
case 'ArrowLeft':
|
||||
self.prevImage();
|
||||
self.slideLightboxImage('prev');
|
||||
break;
|
||||
case 'ArrowRight':
|
||||
self.nextImage();
|
||||
self.slideLightboxImage('next');
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
// Recalculate on resize
|
||||
$(window).on('resize', function() {
|
||||
self.calculateThumbnailsPerPage();
|
||||
self.updateThumbnailNavigation();
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Set main gallery image
|
||||
* Bind swipe events for touch devices
|
||||
*/
|
||||
setMainImage: function(index) {
|
||||
bindSwipeEvents: function() {
|
||||
var self = this;
|
||||
|
||||
// Main gallery swipe
|
||||
this.$mainImageContainer[0].addEventListener('touchstart', function(e) {
|
||||
self.handleSwipeStart(e);
|
||||
}, { passive: true });
|
||||
|
||||
this.$mainImageContainer[0].addEventListener('touchend', function(e) {
|
||||
self.handleMainGallerySwipeEnd(e);
|
||||
}, { passive: true });
|
||||
|
||||
// Lightbox swipe
|
||||
this.$lightboxImageContainer[0].addEventListener('touchstart', function(e) {
|
||||
self.handleSwipeStart(e);
|
||||
}, { passive: true });
|
||||
|
||||
this.$lightboxImageContainer[0].addEventListener('touchend', function(e) {
|
||||
self.handleLightboxSwipeEnd(e);
|
||||
}, { passive: true });
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle swipe start
|
||||
*/
|
||||
handleSwipeStart: function(e) {
|
||||
if (e.touches.length === 1) {
|
||||
this.swipeStartX = e.touches[0].clientX;
|
||||
this.swipeStartY = e.touches[0].clientY;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle main gallery swipe end
|
||||
*/
|
||||
handleMainGallerySwipeEnd: function(e) {
|
||||
if (e.changedTouches.length !== 1) return;
|
||||
|
||||
var deltaX = e.changedTouches[0].clientX - this.swipeStartX;
|
||||
var deltaY = e.changedTouches[0].clientY - this.swipeStartY;
|
||||
|
||||
// Only handle horizontal swipes (not vertical scrolling)
|
||||
if (Math.abs(deltaX) > Math.abs(deltaY) && Math.abs(deltaX) > this.swipeThreshold) {
|
||||
this.isSwiping = true;
|
||||
this.stopAutoplay();
|
||||
|
||||
if (deltaX > 0) {
|
||||
// Swipe right - previous image
|
||||
this.slideMainImage('prev');
|
||||
} else {
|
||||
// Swipe left - next image
|
||||
this.slideMainImage('next');
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle lightbox swipe end
|
||||
*/
|
||||
handleLightboxSwipeEnd: function(e) {
|
||||
if (e.changedTouches.length !== 1) return;
|
||||
|
||||
var deltaX = e.changedTouches[0].clientX - this.swipeStartX;
|
||||
var deltaY = e.changedTouches[0].clientY - this.swipeStartY;
|
||||
|
||||
// Only handle horizontal swipes
|
||||
if (Math.abs(deltaX) > Math.abs(deltaY) && Math.abs(deltaX) > this.swipeThreshold) {
|
||||
if (deltaX > 0) {
|
||||
// Swipe right - previous image
|
||||
this.slideLightboxImage('prev');
|
||||
} else {
|
||||
// Swipe left - next image
|
||||
this.slideLightboxImage('next');
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Start autoplay
|
||||
*/
|
||||
startAutoplay: function() {
|
||||
var self = this;
|
||||
|
||||
if (this.images.length <= 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.isPlaying = true;
|
||||
this.$playbackBtn.addClass('is-playing');
|
||||
this.$playbackBtn.attr('aria-label', 'Pause slideshow');
|
||||
|
||||
this.autoplayInterval = setInterval(function() {
|
||||
self.advanceImage();
|
||||
}, this.autoplayDelay);
|
||||
},
|
||||
|
||||
/**
|
||||
* Stop autoplay
|
||||
*/
|
||||
stopAutoplay: function() {
|
||||
this.isPlaying = false;
|
||||
this.$playbackBtn.removeClass('is-playing');
|
||||
this.$playbackBtn.attr('aria-label', 'Play slideshow');
|
||||
|
||||
if (this.autoplayInterval) {
|
||||
clearInterval(this.autoplayInterval);
|
||||
this.autoplayInterval = null;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Advance to next image (for autoplay) - with fade
|
||||
*/
|
||||
advanceImage: function() {
|
||||
if (this.isTransitioning) {
|
||||
return;
|
||||
}
|
||||
|
||||
var newIndex = this.currentIndex + 1;
|
||||
if (newIndex >= this.images.length) {
|
||||
newIndex = 0;
|
||||
}
|
||||
this.setMainImage(newIndex, true); // true = use fade
|
||||
},
|
||||
|
||||
/**
|
||||
* Slide main image with slide transition
|
||||
* @param {string} direction - 'prev' or 'next'
|
||||
*/
|
||||
slideMainImage: function(direction) {
|
||||
var self = this;
|
||||
|
||||
if (this.isTransitioning || this.images.length <= 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
var newIndex;
|
||||
if (direction === 'prev') {
|
||||
newIndex = this.currentIndex - 1;
|
||||
if (newIndex < 0) newIndex = this.images.length - 1;
|
||||
} else {
|
||||
newIndex = this.currentIndex + 1;
|
||||
if (newIndex >= this.images.length) newIndex = 0;
|
||||
}
|
||||
|
||||
this.isTransitioning = true;
|
||||
|
||||
var image = this.images[newIndex];
|
||||
var slideFrom = direction === 'next' ? '100%' : '-100%';
|
||||
var slideTo = direction === 'next' ? '-100%' : '100%';
|
||||
|
||||
// Create new image for slide in
|
||||
var $newImage = $('<img class="gallery-slide-image" />');
|
||||
$newImage.attr('src', image.url);
|
||||
$newImage.attr('alt', image.alt || 'Property photo');
|
||||
$newImage.css({
|
||||
'position': 'absolute',
|
||||
'top': 0,
|
||||
'left': 0,
|
||||
'width': '100%',
|
||||
'height': '100%',
|
||||
'object-fit': 'cover',
|
||||
'transform': 'translateX(' + slideFrom + ')',
|
||||
'z-index': 2,
|
||||
'border-radius': '0.5rem'
|
||||
});
|
||||
|
||||
// Ensure container has relative positioning and overflow hidden
|
||||
this.$mainImageContainer.css({
|
||||
'position': 'relative',
|
||||
'overflow': 'hidden'
|
||||
});
|
||||
|
||||
// Append new image
|
||||
this.$mainImageContainer.append($newImage);
|
||||
|
||||
// Animate current image out and new image in
|
||||
this.$mainImage.css({
|
||||
'transition': 'transform ' + this.slideDuration + 'ms ease-out'
|
||||
});
|
||||
$newImage.css({
|
||||
'transition': 'transform ' + this.slideDuration + 'ms ease-out'
|
||||
});
|
||||
|
||||
// Trigger reflow
|
||||
$newImage[0].offsetHeight;
|
||||
|
||||
// Animate
|
||||
this.$mainImage.css('transform', 'translateX(' + slideTo + ')');
|
||||
$newImage.css('transform', 'translateX(0)');
|
||||
|
||||
// After transition
|
||||
setTimeout(function() {
|
||||
self.$mainImage.attr('src', image.url);
|
||||
self.$mainImage.attr('alt', image.alt || 'Property photo');
|
||||
self.$mainImage.css({
|
||||
'transition': '',
|
||||
'transform': ''
|
||||
});
|
||||
$newImage.remove();
|
||||
self.isTransitioning = false;
|
||||
}, this.slideDuration);
|
||||
|
||||
this.currentIndex = newIndex;
|
||||
|
||||
// Update active thumbnail
|
||||
this.$thumbnails.removeClass('is-active');
|
||||
this.$thumbnails.filter('[data-index="' + newIndex + '"]').addClass('is-active');
|
||||
|
||||
// Ensure the current thumbnail is visible
|
||||
this.scrollToThumbnail(newIndex);
|
||||
},
|
||||
|
||||
/**
|
||||
* Set main gallery image
|
||||
* @param {number} index - Image index
|
||||
* @param {boolean} useFade - Whether to use fade transition
|
||||
*/
|
||||
setMainImage: function(index, useFade) {
|
||||
var self = this;
|
||||
|
||||
if (index < 0 || index >= this.images.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.currentIndex = index;
|
||||
if (this.isTransitioning) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update main image
|
||||
var image = this.images[index];
|
||||
this.$mainImage.attr('src', image.url);
|
||||
this.$mainImage.attr('alt', image.alt || 'Property photo');
|
||||
|
||||
if (useFade) {
|
||||
// Fade transition for autoplay
|
||||
this.isTransitioning = true;
|
||||
|
||||
// Create overlay image for crossfade
|
||||
var $overlay = $('<img class="gallery-fade-overlay" />');
|
||||
$overlay.attr('src', image.url);
|
||||
$overlay.attr('alt', image.alt || 'Property photo');
|
||||
$overlay.css({
|
||||
'position': 'absolute',
|
||||
'top': 0,
|
||||
'left': 0,
|
||||
'width': '100%',
|
||||
'height': '100%',
|
||||
'object-fit': 'cover',
|
||||
'opacity': 0,
|
||||
'transform': 'scale(1.02)',
|
||||
'transition': 'opacity ' + this.fadeDuration + 'ms ease-in-out, transform ' + this.fadeDuration + 'ms ease-in-out',
|
||||
'z-index': 2,
|
||||
'border-radius': '0.5rem'
|
||||
});
|
||||
|
||||
// Ensure container has relative positioning
|
||||
this.$mainImageContainer.css('position', 'relative');
|
||||
|
||||
// Append overlay
|
||||
this.$mainImageContainer.append($overlay);
|
||||
|
||||
// Trigger reflow then animate
|
||||
$overlay[0].offsetHeight; // Force reflow
|
||||
$overlay.css({
|
||||
'opacity': 1,
|
||||
'transform': 'scale(1)'
|
||||
});
|
||||
|
||||
// After transition, update main image and remove overlay
|
||||
setTimeout(function() {
|
||||
self.$mainImage.attr('src', image.url);
|
||||
self.$mainImage.attr('alt', image.alt || 'Property photo');
|
||||
$overlay.remove();
|
||||
self.isTransitioning = false;
|
||||
}, this.fadeDuration);
|
||||
} else {
|
||||
// Instant transition for manual clicks
|
||||
this.$mainImage.attr('src', image.url);
|
||||
this.$mainImage.attr('alt', image.alt || 'Property photo');
|
||||
}
|
||||
|
||||
this.currentIndex = index;
|
||||
|
||||
// Update active thumbnail
|
||||
this.$thumbnails.removeClass('is-active');
|
||||
this.$thumbnails.filter('[data-index="' + index + '"]').addClass('is-active');
|
||||
|
||||
// Ensure the current thumbnail is visible
|
||||
this.scrollToThumbnail(index);
|
||||
},
|
||||
|
||||
/**
|
||||
* Scroll to make thumbnail visible
|
||||
*/
|
||||
scrollToThumbnail: function(index) {
|
||||
var pageForIndex = Math.floor(index / this.thumbnailsPerPage);
|
||||
|
||||
if (pageForIndex !== this.thumbnailPage) {
|
||||
this.thumbnailPage = pageForIndex;
|
||||
this.scrollThumbnails();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Scroll thumbnails to current page
|
||||
*/
|
||||
scrollThumbnails: function() {
|
||||
var $thumbnailsInner = this.$gallery.find('.gallery-thumbnails');
|
||||
var thumbnailWidth = this.$thumbnails.first().outerWidth(true);
|
||||
var scrollAmount = this.thumbnailPage * this.thumbnailsPerPage * thumbnailWidth;
|
||||
|
||||
$thumbnailsInner.css('transform', 'translateX(-' + scrollAmount + 'px)');
|
||||
|
||||
this.updateThumbnailNavigation();
|
||||
},
|
||||
|
||||
/**
|
||||
* Update thumbnail navigation button states
|
||||
*/
|
||||
updateThumbnailNavigation: function() {
|
||||
var totalPages = Math.ceil(this.images.length / this.thumbnailsPerPage);
|
||||
|
||||
this.$prevBtn.prop('disabled', this.thumbnailPage === 0);
|
||||
this.$nextBtn.prop('disabled', this.thumbnailPage >= totalPages - 1);
|
||||
|
||||
// Hide nav buttons if all thumbnails fit
|
||||
if (totalPages <= 1) {
|
||||
this.$prevBtn.hide();
|
||||
this.$nextBtn.hide();
|
||||
} else {
|
||||
this.$prevBtn.show();
|
||||
this.$nextBtn.show();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Previous thumbnail page
|
||||
*/
|
||||
prevThumbnailPage: function() {
|
||||
if (this.thumbnailPage > 0) {
|
||||
this.thumbnailPage--;
|
||||
this.scrollThumbnails();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Next thumbnail page
|
||||
*/
|
||||
nextThumbnailPage: function() {
|
||||
var totalPages = Math.ceil(this.images.length / this.thumbnailsPerPage);
|
||||
if (this.thumbnailPage < totalPages - 1) {
|
||||
this.thumbnailPage++;
|
||||
this.scrollThumbnails();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -142,31 +562,103 @@
|
||||
},
|
||||
|
||||
/**
|
||||
* Previous image
|
||||
* Slide lightbox image with animation
|
||||
* @param {string} direction - 'prev' or 'next'
|
||||
*/
|
||||
slideLightboxImage: function(direction) {
|
||||
var self = this;
|
||||
|
||||
if (this.isTransitioning || this.images.length <= 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
var newIndex;
|
||||
if (direction === 'prev') {
|
||||
newIndex = this.currentIndex - 1;
|
||||
if (newIndex < 0) newIndex = this.images.length - 1;
|
||||
} else {
|
||||
newIndex = this.currentIndex + 1;
|
||||
if (newIndex >= this.images.length) newIndex = 0;
|
||||
}
|
||||
|
||||
this.isTransitioning = true;
|
||||
|
||||
var image = this.images[newIndex];
|
||||
var slideFrom = direction === 'next' ? '100%' : '-100%';
|
||||
var slideTo = direction === 'next' ? '-100%' : '100%';
|
||||
|
||||
// Create new image for slide in
|
||||
var $newImage = $('<img class="lightbox-slide-image" />');
|
||||
$newImage.attr('src', image.url);
|
||||
$newImage.attr('alt', image.alt || 'Property photo');
|
||||
$newImage.css({
|
||||
'position': 'absolute',
|
||||
'max-width': '100%',
|
||||
'max-height': 'calc(100vh - 8rem)',
|
||||
'object-fit': 'contain',
|
||||
'transform': 'translateX(' + slideFrom + ')',
|
||||
'left': '50%',
|
||||
'top': '50%',
|
||||
'margin-left': '-45vw',
|
||||
'margin-top': 'calc(-50vh + 4rem)'
|
||||
});
|
||||
|
||||
// Ensure container has relative positioning
|
||||
this.$lightboxImageContainer.css({
|
||||
'position': 'relative',
|
||||
'overflow': 'hidden'
|
||||
});
|
||||
|
||||
// Append new image
|
||||
this.$lightboxImageContainer.append($newImage);
|
||||
|
||||
// Animate current image out and new image in
|
||||
this.$lightboxImage.css({
|
||||
'transition': 'transform ' + this.slideDuration + 'ms ease-out'
|
||||
});
|
||||
$newImage.css({
|
||||
'transition': 'transform ' + this.slideDuration + 'ms ease-out'
|
||||
});
|
||||
|
||||
// Trigger reflow
|
||||
$newImage[0].offsetHeight;
|
||||
|
||||
// Animate
|
||||
this.$lightboxImage.css('transform', 'translateX(' + slideTo + ')');
|
||||
$newImage.css('transform', 'translateX(0)');
|
||||
|
||||
// After transition
|
||||
setTimeout(function() {
|
||||
self.$lightboxImage.attr('src', image.url);
|
||||
self.$lightboxImage.attr('alt', image.alt || 'Property photo');
|
||||
self.$lightboxImage.css({
|
||||
'transition': '',
|
||||
'transform': ''
|
||||
});
|
||||
$newImage.remove();
|
||||
self.isTransitioning = false;
|
||||
self.$lightboxCounter.text(newIndex + 1);
|
||||
}, this.slideDuration);
|
||||
|
||||
this.currentIndex = newIndex;
|
||||
},
|
||||
|
||||
/**
|
||||
* Previous image (legacy - now uses slide)
|
||||
*/
|
||||
prevImage: function() {
|
||||
var newIndex = this.currentIndex - 1;
|
||||
if (newIndex < 0) {
|
||||
newIndex = this.images.length - 1;
|
||||
}
|
||||
this.currentIndex = newIndex;
|
||||
this.updateLightboxImage();
|
||||
this.slideLightboxImage('prev');
|
||||
},
|
||||
|
||||
/**
|
||||
* Next image
|
||||
* Next image (legacy - now uses slide)
|
||||
*/
|
||||
nextImage: function() {
|
||||
var newIndex = this.currentIndex + 1;
|
||||
if (newIndex >= this.images.length) {
|
||||
newIndex = 0;
|
||||
}
|
||||
this.currentIndex = newIndex;
|
||||
this.updateLightboxImage();
|
||||
this.slideLightboxImage('next');
|
||||
},
|
||||
|
||||
/**
|
||||
* Update lightbox image
|
||||
* Update lightbox image (instant, no animation)
|
||||
*/
|
||||
updateLightboxImage: function() {
|
||||
var image = this.images[this.currentIndex];
|
||||
|
||||
@@ -53,6 +53,16 @@ $image_count = count($images);
|
||||
<button class="gallery-main-image" type="button" data-lightbox-trigger aria-label="Open gallery">
|
||||
<img src="<?php echo esc_url($images[0]['url']); ?>" alt="<?php echo esc_attr($images[0]['alt'] ?: 'Property photo'); ?>" />
|
||||
<?php if ($image_count > 1) : ?>
|
||||
<!-- Play/Pause Button -->
|
||||
<button class="gallery-playback-btn is-playing" type="button" aria-label="Pause slideshow">
|
||||
<svg class="icon-pause" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
||||
<rect x="6" y="4" width="4" height="16"/>
|
||||
<rect x="14" y="4" width="4" height="16"/>
|
||||
</svg>
|
||||
<svg class="icon-play" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
||||
<polygon points="5,3 19,12 5,21"/>
|
||||
</svg>
|
||||
</button>
|
||||
<span class="gallery-count">
|
||||
<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" ry="2"/>
|
||||
@@ -67,20 +77,31 @@ $image_count = count($images);
|
||||
|
||||
<?php if ($image_count > 1) : ?>
|
||||
<!-- Thumbnails -->
|
||||
<div class="gallery-thumbnails">
|
||||
<?php foreach (array_slice($images, 0, 5) as $index => $image) : ?>
|
||||
<button
|
||||
class="gallery-thumbnail <?php echo $index === 0 ? 'is-active' : ''; ?>"
|
||||
type="button"
|
||||
data-index="<?php echo esc_attr($index); ?>"
|
||||
aria-label="View image <?php echo esc_attr($index + 1); ?>"
|
||||
>
|
||||
<img src="<?php echo esc_url(wp_get_attachment_image_url($image['id'], 'thumbnail')); ?>" alt="" loading="lazy" />
|
||||
<?php if ($index === 4 && $image_count > 5) : ?>
|
||||
<span class="thumbnail-more">+<?php echo esc_html($image_count - 5); ?></span>
|
||||
<?php endif; ?>
|
||||
</button>
|
||||
<?php endforeach; ?>
|
||||
<div class="gallery-thumbnails-container">
|
||||
<button class="gallery-thumbnails-nav gallery-thumbnails-prev" type="button" aria-label="Previous thumbnails" disabled>
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<polyline points="15 18 9 12 15 6"/>
|
||||
</svg>
|
||||
</button>
|
||||
<div class="gallery-thumbnails-viewport">
|
||||
<div class="gallery-thumbnails">
|
||||
<?php foreach ($images as $index => $image) : ?>
|
||||
<button
|
||||
class="gallery-thumbnail <?php echo $index === 0 ? 'is-active' : ''; ?>"
|
||||
type="button"
|
||||
data-index="<?php echo esc_attr($index); ?>"
|
||||
aria-label="View image <?php echo esc_attr($index + 1); ?>"
|
||||
>
|
||||
<img src="<?php echo esc_url(wp_get_attachment_image_url($image['id'], 'thumbnail')); ?>" alt="" loading="lazy" />
|
||||
</button>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
<button class="gallery-thumbnails-nav gallery-thumbnails-next" type="button" aria-label="Next thumbnails">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<polyline points="9 18 15 12 9 6"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
|
||||
@@ -48,19 +48,68 @@
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
// Thumbnails
|
||||
.gallery-thumbnails {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(5, 1fr);
|
||||
gap: 0.5rem;
|
||||
// Play/Pause Button
|
||||
.gallery-playback-btn {
|
||||
position: absolute;
|
||||
bottom: 1rem;
|
||||
left: 1rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
padding: 0;
|
||||
background-color: rgba(0, 0, 0, 0.75);
|
||||
border: none;
|
||||
border-radius: 0.25rem;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
z-index: 2;
|
||||
|
||||
@media (max-width: 640px) {
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
.icon-play {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.icon-pause {
|
||||
display: block;
|
||||
}
|
||||
|
||||
&:not(.is-playing) {
|
||||
.icon-play {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.icon-pause {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(0, 0, 0, 0.9);
|
||||
}
|
||||
}
|
||||
|
||||
// Thumbnails Container
|
||||
.gallery-thumbnails-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.gallery-thumbnails-viewport {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.gallery-thumbnails {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.gallery-thumbnail {
|
||||
position: relative;
|
||||
flex-shrink: 0;
|
||||
width: calc((100% - 2rem) / 5);
|
||||
padding: 0;
|
||||
border: 2px solid transparent;
|
||||
background: none;
|
||||
@@ -68,6 +117,10 @@
|
||||
border-radius: 0.25rem;
|
||||
overflow: hidden;
|
||||
|
||||
@media (max-width: 640px) {
|
||||
width: calc((100% - 1.5rem) / 4);
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
border-color: var(--color-accent);
|
||||
}
|
||||
@@ -80,16 +133,31 @@
|
||||
}
|
||||
}
|
||||
|
||||
.thumbnail-more {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
// Thumbnail Navigation Arrows
|
||||
.gallery-thumbnails-nav {
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
color: white;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
padding: 0;
|
||||
background-color: var(--color-bg-card);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 0.25rem;
|
||||
color: var(--color-text);
|
||||
cursor: pointer;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
background-color: var(--color-accent);
|
||||
border-color: var(--color-accent);
|
||||
color: white;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.3;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
// Gallery Placeholder
|
||||
|
||||
@@ -51,11 +51,26 @@
|
||||
padding-bottom: 4rem;
|
||||
}
|
||||
|
||||
// Property Address Header
|
||||
.property-address-header {
|
||||
padding-top: 2rem;
|
||||
padding-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.property-address-title {
|
||||
font-size: 2rem;
|
||||
margin-bottom: 0;
|
||||
color: var(--color-text);
|
||||
|
||||
@media (max-width: 768px) {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.single-property-layout {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 2rem;
|
||||
padding-top: 2rem;
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
grid-template-columns: 1fr 350px;
|
||||
@@ -213,8 +228,9 @@
|
||||
}
|
||||
|
||||
.widget-title {
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
font-family: 'Times New Roman', Times, serif;
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 1rem;
|
||||
padding-bottom: 0.75rem;
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
@@ -230,33 +246,12 @@
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.property-type {
|
||||
font-size: 0.75rem;
|
||||
color: var(--color-text-muted);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
.property-title {
|
||||
font-family: var(--font-display);
|
||||
font-size: 1.75rem;
|
||||
margin-bottom: 0.75rem;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.property-address {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 0.5rem;
|
||||
font-size: 0.9375rem;
|
||||
color: var(--color-text-muted);
|
||||
color: var(--color-text);
|
||||
margin-bottom: 0.5rem;
|
||||
line-height: 1.4;
|
||||
|
||||
svg {
|
||||
flex-shrink: 0;
|
||||
margin-top: 0.125rem;
|
||||
color: var(--color-accent);
|
||||
}
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.property-mls {
|
||||
@@ -266,6 +261,27 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Property type badges
|
||||
.badge-type-residential {
|
||||
background-color: #93C5FD;
|
||||
color: #1E3A5F;
|
||||
}
|
||||
|
||||
.badge-type-commercial {
|
||||
background-color: var(--color-accent);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.badge-type-land {
|
||||
background-color: #86EFAC;
|
||||
color: #14532D;
|
||||
}
|
||||
|
||||
.badge-type-multi-family {
|
||||
background-color: #C4B5FD;
|
||||
color: #3B1D66;
|
||||
}
|
||||
|
||||
// Property Documents Widget
|
||||
.property-documents-widget {
|
||||
.sidebar-documents-list {
|
||||
@@ -364,6 +380,8 @@
|
||||
|
||||
.agent-avatar {
|
||||
flex-shrink: 0;
|
||||
background-color: #000;
|
||||
border-radius: 50%;
|
||||
|
||||
img {
|
||||
width: 64px;
|
||||
@@ -379,7 +397,7 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: var(--color-bg-dark);
|
||||
background-color: #000;
|
||||
border-radius: 50%;
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user