Improve public SEO and align chrome with EXIT Realty teal.
Add meta/canonical/OG, robots/sitemap, and contact-form admin email; restyle consent, icons, and scroll-top away from theme blue.
This commit is contained in:
@@ -27,19 +27,83 @@
|
|||||||
|
|
||||||
.page .button-primary,
|
.page .button-primary,
|
||||||
.page .button-primary:focus,
|
.page .button-primary:focus,
|
||||||
.page .button-primary:active {
|
.page .button-primary:active,
|
||||||
background-color: #00626c !important;
|
.cookie-consent-banner .button-primary,
|
||||||
border-color: #00626c !important;
|
.cookie-consent-banner .button-primary:focus,
|
||||||
|
.cookie-consent-banner .button-primary:active {
|
||||||
|
background-color: var(--monica-primary) !important;
|
||||||
|
border-color: var(--monica-primary) !important;
|
||||||
color: #fff !important;
|
color: #fff !important;
|
||||||
}
|
}
|
||||||
.page .button-primary:hover {
|
.page .button-primary:hover,
|
||||||
background-color: #008898 !important;
|
.cookie-consent-banner .button-primary:hover {
|
||||||
border-color: #008898 !important;
|
background-color: var(--monica-primary-light) !important;
|
||||||
|
border-color: var(--monica-primary-light) !important;
|
||||||
color: #fff !important;
|
color: #fff !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Theme default blue (#0045b6) → EXIT Realty teal on public chrome */
|
||||||
|
html .page .text-primary {
|
||||||
|
color: var(--monica-primary) !important;
|
||||||
|
}
|
||||||
.page .breadcrumbs-custom-path a,
|
.page .breadcrumbs-custom-path a,
|
||||||
.page a.link-default {
|
.page a.link-default {
|
||||||
color: #00626c;
|
color: var(--monica-primary);
|
||||||
|
}
|
||||||
|
.page a.link-default:hover {
|
||||||
|
color: var(--monica-primary-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
.page .button-gray-bordered:hover,
|
||||||
|
.page .button-gray-bordered:active,
|
||||||
|
.page .button-default:hover,
|
||||||
|
.page .button-default:active,
|
||||||
|
.page .button-accent-outline:hover,
|
||||||
|
.page .button-accent-outline:active,
|
||||||
|
.page .button-primary-outline:hover,
|
||||||
|
.page .button-primary-outline:active,
|
||||||
|
.page .button-primary-white:hover,
|
||||||
|
.page .button-primary-white:active,
|
||||||
|
.page .button.button-primary-lighten,
|
||||||
|
.page .button.button-primary-lighten:focus {
|
||||||
|
background-color: var(--monica-primary) !important;
|
||||||
|
border-color: var(--monica-primary) !important;
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
.page .button.button-primary-lighten:hover,
|
||||||
|
.page .button.button-primary-lighten:active {
|
||||||
|
background-color: var(--monica-ink) !important;
|
||||||
|
border-color: var(--monica-ink) !important;
|
||||||
|
}
|
||||||
|
.page .button-accent-outline,
|
||||||
|
.page .button-accent-outline:focus {
|
||||||
|
border-color: var(--monica-primary) !important;
|
||||||
|
}
|
||||||
|
.page .button-video:hover {
|
||||||
|
color: var(--monica-primary) !important;
|
||||||
|
}
|
||||||
|
.page .btn-primary,
|
||||||
|
.page .btn-primary:active,
|
||||||
|
.page .btn-primary:focus {
|
||||||
|
background: var(--monica-primary) !important;
|
||||||
|
border-color: var(--monica-primary) !important;
|
||||||
|
}
|
||||||
|
.page .box-chloe__icon,
|
||||||
|
.page .box-light-icon,
|
||||||
|
.page .box-minimal-icon .box-chloe__icon {
|
||||||
|
color: var(--monica-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Scroll-to-top (injected outside .page) */
|
||||||
|
.ui-to-top {
|
||||||
|
background: var(--monica-primary) !important;
|
||||||
|
color: #fff !important;
|
||||||
|
box-shadow: 0 6px 16px rgba(0, 98, 108, 0.35);
|
||||||
|
}
|
||||||
|
.ui-to-top:hover,
|
||||||
|
.ui-to-top:focus {
|
||||||
|
background: var(--monica-primary-light) !important;
|
||||||
|
color: #fff !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Django flash messages */
|
/* Django flash messages */
|
||||||
@@ -73,19 +137,21 @@
|
|||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Cookie consent — light theme fit */
|
/* Cookie consent — match public EXIT Realty teal brand */
|
||||||
.cookie-consent-banner {
|
.cookie-consent-banner {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
z-index: 11000;
|
z-index: 11000;
|
||||||
padding: 1rem;
|
padding: 1rem 1.25rem;
|
||||||
background: rgba(8, 8, 8, 0.96);
|
background: rgba(17, 24, 28, 0.96);
|
||||||
border-top: 1px solid rgba(0, 98, 108, 0.45);
|
border-top: 2px solid var(--monica-primary);
|
||||||
|
box-shadow: 0 -8px 24px rgba(0, 0, 0, 0.28);
|
||||||
|
font-family: "Work Sans", sans-serif;
|
||||||
}
|
}
|
||||||
.cookie-consent-content {
|
.cookie-consent-content {
|
||||||
max-width: 960px;
|
max-width: 1200px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
@@ -95,10 +161,12 @@
|
|||||||
}
|
}
|
||||||
.cookie-consent-text {
|
.cookie-consent-text {
|
||||||
flex: 1 1 420px;
|
flex: 1 1 420px;
|
||||||
color: #cbd5e1;
|
color: #d7dee3;
|
||||||
font-size: 0.95rem;
|
font-size: 0.9375rem;
|
||||||
|
line-height: 1.45;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-family: "Work Sans", sans-serif;
|
font-family: "Work Sans", sans-serif;
|
||||||
|
font-weight: 400;
|
||||||
}
|
}
|
||||||
.cookie-consent-text-short {
|
.cookie-consent-text-short {
|
||||||
display: none;
|
display: none;
|
||||||
@@ -108,6 +176,15 @@
|
|||||||
gap: 0.75rem;
|
gap: 0.75rem;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
.cookie-consent-banner .button {
|
||||||
|
min-width: 10rem;
|
||||||
|
padding-left: 1.5rem;
|
||||||
|
padding-right: 1.5rem;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-family: "Work Sans", sans-serif;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
.visually-hidden {
|
.visually-hidden {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 1px;
|
width: 1px;
|
||||||
@@ -197,3 +274,24 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Keep hero slide-2 heading visually aligned with slide-1 h1 (SEO: one h1). */
|
||||||
|
.swiper-slider-minimal .jumbotron-classic-content h1.font-weight-bold,
|
||||||
|
.swiper-slider-minimal .jumbotron-classic-content h2.font-weight-bold {
|
||||||
|
font-size: 2.5rem;
|
||||||
|
line-height: 1.15;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.swiper-slider-minimal .jumbotron-classic-content h1.font-weight-bold,
|
||||||
|
.swiper-slider-minimal .jumbotron-classic-content h2.font-weight-bold {
|
||||||
|
font-size: 3.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (min-width: 1200px) {
|
||||||
|
.swiper-slider-minimal .jumbotron-classic-content h1.font-weight-bold,
|
||||||
|
.swiper-slider-minimal .jumbotron-classic-content h2.font-weight-bold {
|
||||||
|
font-size: 4.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,24 +5,41 @@
|
|||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>{% block title %}{{ SITE_NAME }}{% endblock %}</title>
|
<title>{% block title %}{{ SITE_NAME }}{% endblock %}</title>
|
||||||
|
<meta name="description" content="{% block meta_description %}{{ SITE_NAME }} — {{ SITE_TAGLINE }}. {{ CONTACT_SERVICE_AREA }}.{% endblock %}">
|
||||||
|
<meta name="robots" content="{% block meta_robots %}index, follow{% endblock %}">
|
||||||
|
<link rel="canonical" href="{% block canonical_url %}{{ PUBLIC_SITE_URL }}{{ request.path }}{% endblock %}">
|
||||||
|
<meta property="og:type" content="website">
|
||||||
|
<meta property="og:site_name" content="{{ SITE_NAME }}">
|
||||||
|
<meta property="og:locale" content="en_US">
|
||||||
|
<meta property="og:title" content="{% block og_title %}{{ SITE_NAME }}{% endblock %}">
|
||||||
|
<meta property="og:description" content="{% block og_description %}{{ SITE_NAME }} — {{ SITE_TAGLINE }}. {{ CONTACT_SERVICE_AREA }}.{% endblock %}">
|
||||||
|
<meta property="og:url" content="{% block og_url %}{{ PUBLIC_SITE_URL }}{{ request.path }}{% endblock %}">
|
||||||
|
{% block og_image %}
|
||||||
|
<meta property="og:image" content="{{ PUBLIC_SITE_URL }}{% static 'images/slider-minimal-slide-1-1920x968.jpg' %}">
|
||||||
|
<meta property="og:image:alt" content="{{ SITE_NAME }} · EXIT Realty">
|
||||||
|
{% endblock %}
|
||||||
|
<meta name="twitter:card" content="summary_large_image">
|
||||||
|
<meta name="twitter:title" content="{% block twitter_title %}{{ SITE_NAME }}{% endblock %}">
|
||||||
|
<meta name="twitter:description" content="{% block twitter_description %}{{ SITE_NAME }} — {{ SITE_TAGLINE }}. {{ CONTACT_SERVICE_AREA }}.{% endblock %}">
|
||||||
<link rel="icon" href="{% static 'brand/favicon-32.png' %}" type="image/png">
|
<link rel="icon" href="{% static 'brand/favicon-32.png' %}" type="image/png">
|
||||||
<link rel="apple-touch-icon" href="{% static 'brand/apple-touch-icon.png' %}">
|
<link rel="apple-touch-icon" href="{% static 'brand/apple-touch-icon.png' %}">
|
||||||
<link rel="stylesheet" href="//fonts.googleapis.com/css?family=Work+Sans:300,400,500,700,800%7CPoppins:300,400,700">
|
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Work+Sans:300,400,500,700,800%7CPoppins:300,400,700">
|
||||||
<link rel="stylesheet" href="{% static 'css/bootstrap.css' %}">
|
<link rel="stylesheet" href="{% static 'css/bootstrap.css' %}">
|
||||||
<link rel="stylesheet" href="{% static 'css/fonts.css' %}">
|
<link rel="stylesheet" href="{% static 'css/fonts.css' %}">
|
||||||
<link rel="stylesheet" href="{% static 'css/style.css' %}" id="main-styles-link">
|
<link rel="stylesheet" href="{% static 'css/style.css' %}" id="main-styles-link">
|
||||||
<link rel="stylesheet" href="{% static 'css/theme-overrides.css' %}">
|
<link rel="stylesheet" href="{% static 'css/theme-overrides.css' %}">
|
||||||
{% if tianji_enabled %}
|
{% block structured_data %}{% endblock %}
|
||||||
<div id="tianji-config"
|
|
||||||
data-tracker-url="{{ tianji_tracker_url }}"
|
|
||||||
data-website-id="{{ tianji_website_id }}"
|
|
||||||
data-page-name="{{ page_name }}"
|
|
||||||
{% if user.is_authenticated %}data-user-id="{{ user.pk }}"{% endif %}
|
|
||||||
hidden></div>
|
|
||||||
{% endif %}
|
|
||||||
{% block extra_head %}{% endblock %}
|
{% block extra_head %}{% endblock %}
|
||||||
</head>
|
</head>
|
||||||
<body class="{% block body_class %}{% endblock %}">
|
<body class="{% block body_class %}{% endblock %}">
|
||||||
|
{% if tianji_enabled %}
|
||||||
|
<div id="tianji-config"
|
||||||
|
data-tracker-url="{{ tianji_tracker_url }}"
|
||||||
|
data-website-id="{{ tianji_website_id }}"
|
||||||
|
data-page-name="{{ page_name }}"
|
||||||
|
{% if user.is_authenticated %}data-user-id="{{ user.pk }}"{% endif %}
|
||||||
|
hidden></div>
|
||||||
|
{% endif %}
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<div class="page">
|
<div class="page">
|
||||||
{% block header %}
|
{% block header %}
|
||||||
|
|||||||
@@ -5,20 +5,21 @@
|
|||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>{% block title %}Portal · {{ SITE_NAME }}{% endblock %}</title>
|
<title>{% block title %}Portal · {{ SITE_NAME }}{% endblock %}</title>
|
||||||
|
<meta name="robots" content="noindex, nofollow">
|
||||||
<link rel="icon" href="{% static 'brand/favicon-32.png' %}" type="image/png">
|
<link rel="icon" href="{% static 'brand/favicon-32.png' %}" type="image/png">
|
||||||
<link rel="stylesheet" href="//fonts.googleapis.com/css?family=Work+Sans:300,400,500,700%7CPoppins:400,600,700">
|
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Work+Sans:300,400,500,700%7CPoppins:400,600,700">
|
||||||
<link rel="stylesheet" href="{% static 'css/portal.css' %}">
|
<link rel="stylesheet" href="{% static 'css/portal.css' %}">
|
||||||
{% if tianji_enabled %}
|
|
||||||
<div id="tianji-config"
|
|
||||||
data-tracker-url="{{ tianji_tracker_url }}"
|
|
||||||
data-website-id="{{ tianji_website_id }}"
|
|
||||||
data-page-name="{{ page_name }}"
|
|
||||||
{% if user.is_authenticated %}data-user-id="{{ user.pk }}"{% endif %}
|
|
||||||
hidden></div>
|
|
||||||
{% endif %}
|
|
||||||
{% block extra_head %}{% endblock %}
|
{% block extra_head %}{% endblock %}
|
||||||
</head>
|
</head>
|
||||||
<body class="portal {% block body_class %}{% endblock %}">
|
<body class="portal {% block body_class %}{% endblock %}">
|
||||||
|
{% if tianji_enabled %}
|
||||||
|
<div id="tianji-config"
|
||||||
|
data-tracker-url="{{ tianji_tracker_url }}"
|
||||||
|
data-website-id="{{ tianji_website_id }}"
|
||||||
|
data-page-name="{{ page_name }}"
|
||||||
|
{% if user.is_authenticated %}data-user-id="{{ user.pk }}"{% endif %}
|
||||||
|
hidden></div>
|
||||||
|
{% endif %}
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<div class="portal-shell">
|
<div class="portal-shell">
|
||||||
<aside class="portal-sidebar">
|
<aside class="portal-sidebar">
|
||||||
|
|||||||
@@ -8,11 +8,15 @@ def _phone_tel(phone: str) -> str:
|
|||||||
return digits
|
return digits
|
||||||
|
|
||||||
|
|
||||||
def site_branding(_request):
|
def site_branding(request):
|
||||||
phone = settings.CONTACT_PHONE or ""
|
phone = settings.CONTACT_PHONE or ""
|
||||||
|
public_site_url = (getattr(settings, "PUBLIC_SITE_URL", None) or "").rstrip("/")
|
||||||
|
if not public_site_url and getattr(request, "build_absolute_uri", None):
|
||||||
|
public_site_url = request.build_absolute_uri("/").rstrip("/")
|
||||||
return {
|
return {
|
||||||
"SITE_NAME": settings.SITE_NAME,
|
"SITE_NAME": settings.SITE_NAME,
|
||||||
"SITE_TAGLINE": settings.SITE_TAGLINE,
|
"SITE_TAGLINE": settings.SITE_TAGLINE,
|
||||||
|
"PUBLIC_SITE_URL": public_site_url,
|
||||||
"CONTACT_PHONE": phone,
|
"CONTACT_PHONE": phone,
|
||||||
"CONTACT_PHONE_TEL": _phone_tel(phone),
|
"CONTACT_PHONE_TEL": _phone_tel(phone),
|
||||||
"CONTACT_EMAIL": settings.CONTACT_EMAIL or "",
|
"CONTACT_EMAIL": settings.CONTACT_EMAIL or "",
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ class UnderConstructionMiddleware:
|
|||||||
"/portal/messaging/webhooks/",
|
"/portal/messaging/webhooks/",
|
||||||
"/unsubscribe/",
|
"/unsubscribe/",
|
||||||
"/api/address-suggest",
|
"/api/address-suggest",
|
||||||
|
"/robots.txt",
|
||||||
|
"/sitemap.xml",
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self, get_response):
|
def __init__(self, get_response):
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
"""Outbound notifications for public-site events."""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.core.mail import EmailMessage
|
||||||
|
from django.urls import reverse
|
||||||
|
|
||||||
|
from leads.models import Lead
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def notify_admins_of_contact_form(lead: Lead) -> bool:
|
||||||
|
"""
|
||||||
|
Email CONTACT_EMAIL when someone submits the public contact form.
|
||||||
|
|
||||||
|
Returns True if a message was sent. Failures are logged; callers should
|
||||||
|
not block the visitor success path on delivery errors.
|
||||||
|
"""
|
||||||
|
to_email = (settings.CONTACT_EMAIL or "").strip()
|
||||||
|
if not to_email:
|
||||||
|
logger.warning("CONTACT_EMAIL unset; skipping contact-form notification")
|
||||||
|
return False
|
||||||
|
|
||||||
|
contact = lead.contact
|
||||||
|
name = contact.full_name or "(no name)"
|
||||||
|
phone = contact.phone or "(none)"
|
||||||
|
email = contact.email or "(none)"
|
||||||
|
message = (lead.message or "").strip() or "(no message)"
|
||||||
|
|
||||||
|
site = (settings.PUBLIC_SITE_URL or "").rstrip("/")
|
||||||
|
portal_path = reverse("leads:detail", kwargs={"pk": lead.pk})
|
||||||
|
portal_url = f"{site}{portal_path}" if site else portal_path
|
||||||
|
|
||||||
|
postal = contact.postal_address or {}
|
||||||
|
address_bits = [
|
||||||
|
postal.get("line1") or "",
|
||||||
|
postal.get("line2") or "",
|
||||||
|
", ".join(
|
||||||
|
part
|
||||||
|
for part in [
|
||||||
|
postal.get("city") or "",
|
||||||
|
postal.get("state") or "",
|
||||||
|
postal.get("zip") or "",
|
||||||
|
]
|
||||||
|
if part
|
||||||
|
),
|
||||||
|
]
|
||||||
|
address = "\n".join(bit for bit in address_bits if bit) or "(none)"
|
||||||
|
|
||||||
|
body = (
|
||||||
|
f"New contact form inquiry from {name}.\n\n"
|
||||||
|
f"Name: {name}\n"
|
||||||
|
f"Email: {email}\n"
|
||||||
|
f"Phone: {phone}\n"
|
||||||
|
f"Address:\n{address}\n\n"
|
||||||
|
f"Message:\n{message}\n\n"
|
||||||
|
f"View in portal: {portal_url}\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
mail = EmailMessage(
|
||||||
|
subject=f"New contact form inquiry from {name}",
|
||||||
|
body=body,
|
||||||
|
from_email=settings.DEFAULT_FROM_EMAIL,
|
||||||
|
to=[to_email],
|
||||||
|
reply_to=[contact.email] if contact.email else None,
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
mail.send(fail_silently=False)
|
||||||
|
except Exception:
|
||||||
|
logger.exception("Failed to send contact-form notification to %s", to_email)
|
||||||
|
return False
|
||||||
|
return True
|
||||||
@@ -1,13 +1,45 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
{% block title %}About · {{ SITE_NAME }}{% endblock %}
|
{% block title %}About {{ SITE_NAME }} · EXIT Realty Realtor{% endblock %}
|
||||||
|
{% block og_title %}About {{ SITE_NAME }} · EXIT Realty Realtor{% endblock %}
|
||||||
|
{% block twitter_title %}About {{ SITE_NAME }} · EXIT Realty Realtor{% endblock %}
|
||||||
|
{% block meta_description %}Meet {{ SITE_NAME }}, a local EXIT Realty realtor helping families buy and sell homes with clear communication and marketing that reaches people. {{ CONTACT_SERVICE_AREA }}.{% endblock %}
|
||||||
|
{% block og_description %}Meet {{ SITE_NAME }}, a local EXIT Realty realtor helping families buy and sell homes with clear communication and marketing that reaches people. {{ CONTACT_SERVICE_AREA }}.{% endblock %}
|
||||||
|
{% block twitter_description %}Meet {{ SITE_NAME }}, a local EXIT Realty realtor helping families buy and sell homes with clear communication and marketing that reaches people. {{ CONTACT_SERVICE_AREA }}.{% endblock %}
|
||||||
|
{% block og_image %}
|
||||||
|
<meta property="og:image" content="{{ PUBLIC_SITE_URL }}{% static 'images/careers-1-570x388.jpg' %}">
|
||||||
|
<meta property="og:image:alt" content="{{ SITE_NAME }}">
|
||||||
|
{% endblock %}
|
||||||
|
{% block structured_data %}
|
||||||
|
<script type="application/ld+json">
|
||||||
|
{
|
||||||
|
"@context": "https://schema.org",
|
||||||
|
"@type": "AboutPage",
|
||||||
|
"name": "About {{ SITE_NAME|escapejs }}",
|
||||||
|
"url": "{{ PUBLIC_SITE_URL }}{% url 'public:about' %}",
|
||||||
|
"description": "Meet {{ SITE_NAME|escapejs }}, a local EXIT Realty realtor helping families buy and sell homes with clear communication and marketing that reaches people.",
|
||||||
|
"mainEntity": {
|
||||||
|
"@type": "RealEstateAgent",
|
||||||
|
"name": "{{ SITE_NAME|escapejs }}",
|
||||||
|
"url": "{{ PUBLIC_SITE_URL }}/",
|
||||||
|
"telephone": "{{ CONTACT_PHONE|escapejs }}",
|
||||||
|
"email": "{{ CONTACT_EMAIL|escapejs }}",
|
||||||
|
"areaServed": "{{ CONTACT_SERVICE_AREA|escapejs }}",
|
||||||
|
"worksFor": {
|
||||||
|
"@type": "Organization",
|
||||||
|
"name": "EXIT Realty Redefined"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<section class="breadcrumbs-custom bg-image context-dark" style="background-image: url({% static 'images/breadcrumbs-image-1.jpg' %});">
|
<section class="breadcrumbs-custom bg-image context-dark" style="background-image: url({% static 'images/breadcrumbs-image-1.jpg' %});">
|
||||||
<div class="breadcrumbs-custom-inner">
|
<div class="breadcrumbs-custom-inner">
|
||||||
<div class="container breadcrumbs-custom-container">
|
<div class="container breadcrumbs-custom-container">
|
||||||
<div class="breadcrumbs-custom-main">
|
<div class="breadcrumbs-custom-main">
|
||||||
<h6 class="breadcrumbs-custom-subtitle title-decorated">About</h6>
|
<p class="breadcrumbs-custom-subtitle title-decorated">About</p>
|
||||||
<h2 class="text-uppercase breadcrumbs-custom-title">Meet Monica</h2>
|
<h1 class="text-uppercase breadcrumbs-custom-title">Meet Monica</h1>
|
||||||
</div>
|
</div>
|
||||||
<ul class="breadcrumbs-custom-path">
|
<ul class="breadcrumbs-custom-path">
|
||||||
<li><a href="{% url 'public:home' %}">Home</a></li>
|
<li><a href="{% url 'public:home' %}">Home</a></li>
|
||||||
@@ -20,13 +52,13 @@
|
|||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row row-50 justify-content-center justify-content-lg-between flex-lg-row-reverse">
|
<div class="row row-50 justify-content-center justify-content-lg-between flex-lg-row-reverse">
|
||||||
<div class="col-md-10 col-lg-6 col-xl-5">
|
<div class="col-md-10 col-lg-6 col-xl-5">
|
||||||
<h3 class="text-uppercase">Local realtor,<br>personal service</h3>
|
<h2 class="text-uppercase">Local realtor,<br>personal service</h2>
|
||||||
<p class="about-subtitle">I help families buy and sell homes with clear communication and marketing that actually reaches people — not just another postcard blast.</p>
|
<p class="about-subtitle">I help families buy and sell homes with clear communication and marketing that actually reaches people — not just another postcard blast.</p>
|
||||||
<p>This site replaces a costly third-party campaign platform. Visitors reach me through a protected contact form; every inquiry lands in my portal with source tracking so I know which campaigns work.</p>
|
<p>This site replaces a costly third-party campaign platform. Visitors reach me through a protected contact form; every inquiry lands in my portal with source tracking so I know which campaigns work.</p>
|
||||||
<a class="button button-lg button-primary button-winona" href="{% url 'public:contact' %}" data-tianji-event="about_cta" data-tianji-event-destination="contact">Work with me</a>
|
<a class="button button-lg button-primary button-winona" href="{% url 'public:contact' %}" data-tianji-event="about_cta" data-tianji-event-destination="contact">Work with me</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-10 col-lg-6 col-xl-6">
|
<div class="col-md-10 col-lg-6 col-xl-6">
|
||||||
<img class="img-responsive" src="{% static 'images/careers-1-570x388.jpg' %}" alt="{{ SITE_NAME }}" width="570" height="388"/>
|
<img class="img-responsive" src="{% static 'images/careers-1-570x388.jpg' %}" alt="{{ SITE_NAME }}, EXIT Realty realtor" width="570" height="388"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,13 +1,45 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
{% block title %}Contact · {{ SITE_NAME }}{% endblock %}
|
{% block title %}Contact {{ SITE_NAME }} · Get in Touch{% endblock %}
|
||||||
|
{% block og_title %}Contact {{ SITE_NAME }} · Get in Touch{% endblock %}
|
||||||
|
{% block twitter_title %}Contact {{ SITE_NAME }} · Get in Touch{% endblock %}
|
||||||
|
{% block meta_description %}Contact {{ SITE_NAME }} at EXIT Realty about buying or selling a home. {{ CONTACT_SERVICE_AREA }}. Call{% if CONTACT_PHONE %} {{ CONTACT_PHONE }}{% endif %}{% if CONTACT_EMAIL %} or email {{ CONTACT_EMAIL }}{% endif %}.{% endblock %}
|
||||||
|
{% block og_description %}Contact {{ SITE_NAME }} at EXIT Realty about buying or selling a home. {{ CONTACT_SERVICE_AREA }}. Call{% if CONTACT_PHONE %} {{ CONTACT_PHONE }}{% endif %}{% if CONTACT_EMAIL %} or email {{ CONTACT_EMAIL }}{% endif %}.{% endblock %}
|
||||||
|
{% block twitter_description %}Contact {{ SITE_NAME }} at EXIT Realty about buying or selling a home. {{ CONTACT_SERVICE_AREA }}. Call{% if CONTACT_PHONE %} {{ CONTACT_PHONE }}{% endif %}{% if CONTACT_EMAIL %} or email {{ CONTACT_EMAIL }}{% endif %}.{% endblock %}
|
||||||
|
{% block structured_data %}
|
||||||
|
<script type="application/ld+json">
|
||||||
|
{
|
||||||
|
"@context": "https://schema.org",
|
||||||
|
"@type": "ContactPage",
|
||||||
|
"name": "Contact {{ SITE_NAME|escapejs }}",
|
||||||
|
"url": "{{ PUBLIC_SITE_URL }}{% url 'public:contact' %}",
|
||||||
|
"description": "Contact {{ SITE_NAME|escapejs }} at EXIT Realty about buying or selling a home. {{ CONTACT_SERVICE_AREA|escapejs }}.",
|
||||||
|
"mainEntity": {
|
||||||
|
"@type": "RealEstateAgent",
|
||||||
|
"name": "{{ SITE_NAME|escapejs }}",
|
||||||
|
"url": "{{ PUBLIC_SITE_URL }}/",
|
||||||
|
"telephone": "{{ CONTACT_PHONE|escapejs }}",
|
||||||
|
"email": "{{ CONTACT_EMAIL|escapejs }}",
|
||||||
|
"areaServed": "{{ CONTACT_SERVICE_AREA|escapejs }}",
|
||||||
|
"contactPoint": {
|
||||||
|
"@type": "ContactPoint",
|
||||||
|
"contactType": "customer service",
|
||||||
|
"telephone": "{{ CONTACT_PHONE|escapejs }}",
|
||||||
|
"email": "{{ CONTACT_EMAIL|escapejs }}",
|
||||||
|
"areaServed": "{{ CONTACT_SERVICE_AREA|escapejs }}",
|
||||||
|
"availableLanguage": "English"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<section class="breadcrumbs-custom bg-image context-dark" style="background-image: url({% static 'images/breadcrumbs-image-1.jpg' %});">
|
<section class="breadcrumbs-custom bg-image context-dark" style="background-image: url({% static 'images/breadcrumbs-image-1.jpg' %});">
|
||||||
<div class="breadcrumbs-custom-inner">
|
<div class="breadcrumbs-custom-inner">
|
||||||
<div class="container breadcrumbs-custom-container">
|
<div class="container breadcrumbs-custom-container">
|
||||||
<div class="breadcrumbs-custom-main">
|
<div class="breadcrumbs-custom-main">
|
||||||
<h6 class="breadcrumbs-custom-subtitle title-decorated">Contact</h6>
|
<p class="breadcrumbs-custom-subtitle title-decorated">Contact</p>
|
||||||
<h2 class="text-uppercase breadcrumbs-custom-title">Get in touch</h2>
|
<h1 class="text-uppercase breadcrumbs-custom-title">Get in touch</h1>
|
||||||
</div>
|
</div>
|
||||||
<ul class="breadcrumbs-custom-path">
|
<ul class="breadcrumbs-custom-path">
|
||||||
<li><a href="{% url 'public:home' %}">Home</a></li>
|
<li><a href="{% url 'public:home' %}">Home</a></li>
|
||||||
|
|||||||
@@ -1,6 +1,30 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
{% block title %}Home · {{ SITE_NAME }}{% endblock %}
|
{% block title %}{{ SITE_NAME }} · EXIT Realty | {{ CONTACT_SERVICE_AREA }}{% endblock %}
|
||||||
|
{% block og_title %}{{ SITE_NAME }} · EXIT Realty | {{ CONTACT_SERVICE_AREA }}{% endblock %}
|
||||||
|
{% block twitter_title %}{{ SITE_NAME }} · EXIT Realty | {{ CONTACT_SERVICE_AREA }}{% endblock %}
|
||||||
|
{% block meta_description %}{{ SITE_NAME }}, EXIT Realty — buying and selling homes with clear advice and multi-channel marketing. {{ CONTACT_SERVICE_AREA }}.{% endblock %}
|
||||||
|
{% block og_description %}{{ SITE_NAME }}, EXIT Realty — buying and selling homes with clear advice and multi-channel marketing. {{ CONTACT_SERVICE_AREA }}.{% endblock %}
|
||||||
|
{% block twitter_description %}{{ SITE_NAME }}, EXIT Realty — buying and selling homes with clear advice and multi-channel marketing. {{ CONTACT_SERVICE_AREA }}.{% endblock %}
|
||||||
|
{% block structured_data %}
|
||||||
|
<script type="application/ld+json">
|
||||||
|
{
|
||||||
|
"@context": "https://schema.org",
|
||||||
|
"@type": "RealEstateAgent",
|
||||||
|
"name": "{{ SITE_NAME|escapejs }}",
|
||||||
|
"url": "{{ PUBLIC_SITE_URL }}/",
|
||||||
|
"image": "{{ PUBLIC_SITE_URL }}{% static 'images/slider-minimal-slide-1-1920x968.jpg' %}",
|
||||||
|
"telephone": "{{ CONTACT_PHONE|escapejs }}",
|
||||||
|
"email": "{{ CONTACT_EMAIL|escapejs }}",
|
||||||
|
"description": "{{ SITE_NAME|escapejs }}, EXIT Realty — buying and selling homes with clear advice and multi-channel marketing. {{ CONTACT_SERVICE_AREA|escapejs }}.",
|
||||||
|
"areaServed": "{{ CONTACT_SERVICE_AREA|escapejs }}",
|
||||||
|
"worksFor": {
|
||||||
|
"@type": "Organization",
|
||||||
|
"name": "EXIT Realty Redefined"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="swiper-container swiper-slider swiper-slider-minimal" data-loop="true" data-slide-effect="fade" data-autoplay="false" data-simulate-touch="true">
|
<div class="swiper-container swiper-slider swiper-slider-minimal" data-loop="true" data-slide-effect="fade" data-autoplay="false" data-simulate-touch="true">
|
||||||
<div class="swiper-wrapper">
|
<div class="swiper-wrapper">
|
||||||
@@ -24,7 +48,7 @@
|
|||||||
<div class="wow-outer">
|
<div class="wow-outer">
|
||||||
<div class="title-docor-text font-weight-bold title-decorated text-uppercase wow slideInLeft text-white">Local Market Insight</div>
|
<div class="title-docor-text font-weight-bold title-decorated text-uppercase wow slideInLeft text-white">Local Market Insight</div>
|
||||||
</div>
|
</div>
|
||||||
<h1 class="text-uppercase text-white font-weight-bold wow-outer"><span class="wow slideInDown" data-wow-delay=".2s">Sell With<br>Confidence</span></h1>
|
<h2 class="text-uppercase text-white font-weight-bold wow-outer"><span class="wow slideInDown" data-wow-delay=".2s">Sell With<br>Confidence</span></h2>
|
||||||
<p class="text-white wow-outer"><span class="wow slideInDown" data-wow-delay=".35s">Pricing strategy, staging guidance, and multi-channel outreach so the right buyers see your listing.</span></p>
|
<p class="text-white wow-outer"><span class="wow slideInDown" data-wow-delay=".35s">Pricing strategy, staging guidance, and multi-channel outreach so the right buyers see your listing.</span></p>
|
||||||
<div class="wow-outer button-outer">
|
<div class="wow-outer button-outer">
|
||||||
<a class="button button-md button-primary button-winona wow slideInDown" href="{% url 'public:about' %}" data-wow-delay=".4s" data-tianji-event="hero_about">Meet Monica</a>
|
<a class="button button-md button-primary button-winona wow slideInDown" href="{% url 'public:about' %}" data-wow-delay=".4s" data-tianji-event="hero_about">Meet Monica</a>
|
||||||
@@ -39,13 +63,13 @@
|
|||||||
</div>
|
</div>
|
||||||
<section class="section section-lg">
|
<section class="section section-lg">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h3 class="text-uppercase text-center wow-outer"><span class="wow slideInDown">How I can help</span></h3>
|
<h2 class="text-uppercase text-center wow-outer"><span class="wow slideInDown">How I can help</span></h2>
|
||||||
<div class="row row-50">
|
<div class="row row-50">
|
||||||
<div class="col-md-4 wow-outer">
|
<div class="col-md-4 wow-outer">
|
||||||
<article class="box-chloe wow slideInUp">
|
<article class="box-chloe wow slideInUp">
|
||||||
<div class="box-chloe__icon linearicons-home-icon3"></div>
|
<div class="box-chloe__icon linearicons-home-icon3"></div>
|
||||||
<div class="box-chloe__main">
|
<div class="box-chloe__main">
|
||||||
<h4 class="box-chloe__title">Buyers</h4>
|
<h3 class="box-chloe__title">Buyers</h3>
|
||||||
<p>Neighborhood tours, offer strategy, and negotiation that protects your timeline and budget.</p>
|
<p>Neighborhood tours, offer strategy, and negotiation that protects your timeline and budget.</p>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
@@ -54,7 +78,7 @@
|
|||||||
<article class="box-chloe wow slideInUp" data-wow-delay=".05s">
|
<article class="box-chloe wow slideInUp" data-wow-delay=".05s">
|
||||||
<div class="box-chloe__icon linearicons-apartment"></div>
|
<div class="box-chloe__icon linearicons-apartment"></div>
|
||||||
<div class="box-chloe__main">
|
<div class="box-chloe__main">
|
||||||
<h4 class="box-chloe__title">Sellers</h4>
|
<h3 class="box-chloe__title">Sellers</h3>
|
||||||
<p>Listing prep, professional marketing, and outreach across email, SMS, postcard, and social.</p>
|
<p>Listing prep, professional marketing, and outreach across email, SMS, postcard, and social.</p>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
@@ -63,7 +87,7 @@
|
|||||||
<article class="box-chloe wow slideInUp" data-wow-delay=".1s">
|
<article class="box-chloe wow slideInUp" data-wow-delay=".1s">
|
||||||
<div class="box-chloe__icon linearicons-chart-growth"></div>
|
<div class="box-chloe__icon linearicons-chart-growth"></div>
|
||||||
<div class="box-chloe__main">
|
<div class="box-chloe__main">
|
||||||
<h4 class="box-chloe__title">Market guidance</h4>
|
<h3 class="box-chloe__title">Market guidance</h3>
|
||||||
<p>Honest comps and timing advice — whether you move this season or plan ahead.</p>
|
<p>Honest comps and timing advice — whether you move this season or plan ahead.</p>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
@@ -73,7 +97,7 @@
|
|||||||
</section>
|
</section>
|
||||||
<section class="section section-lg bg-gray-100 text-center">
|
<section class="section section-lg bg-gray-100 text-center">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h3 class="text-uppercase wow-outer"><span class="wow slideInUp">Ready when you are</span></h3>
|
<h2 class="text-uppercase wow-outer"><span class="wow slideInUp">Ready when you are</span></h2>
|
||||||
<p class="wow-outer"><span class="text-width-1 wow slideInDown">Tell me what you are looking for. I will follow up personally — your message becomes a lead in my private portal.</span></p>
|
<p class="wow-outer"><span class="text-width-1 wow slideInDown">Tell me what you are looking for. I will follow up personally — your message becomes a lead in my private portal.</span></p>
|
||||||
<a class="button button-lg button-primary button-winona" href="{% url 'public:contact' %}" data-tianji-event="home_cta_contact">Contact Monica</a>
|
<a class="button button-lg button-primary button-winona" href="{% url 'public:contact' %}" data-tianji-event="home_cta_contact">Contact Monica</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ urlpatterns = [
|
|||||||
path("", views.home, name="home"),
|
path("", views.home, name="home"),
|
||||||
path("about/", views.about, name="about"),
|
path("about/", views.about, name="about"),
|
||||||
path("contact/", views.contact, name="contact"),
|
path("contact/", views.contact, name="contact"),
|
||||||
|
path("robots.txt", views.robots_txt, name="robots_txt"),
|
||||||
|
path("sitemap.xml", views.sitemap_xml, name="sitemap_xml"),
|
||||||
path("under-construction/", views.under_construction, name="under_construction"),
|
path("under-construction/", views.under_construction, name="under_construction"),
|
||||||
path("unsubscribe/<str:token>/", views.unsubscribe, name="unsubscribe"),
|
path("unsubscribe/<str:token>/", views.unsubscribe, name="unsubscribe"),
|
||||||
path(
|
path(
|
||||||
|
|||||||
+57
-1
@@ -1,8 +1,10 @@
|
|||||||
|
from django.conf import settings
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
|
from django.http import HttpResponse
|
||||||
from django.shortcuts import redirect, render
|
from django.shortcuts import redirect, render
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.views.decorators.csrf import csrf_exempt
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
from django.views.decorators.http import require_http_methods
|
from django.views.decorators.http import require_GET, require_http_methods
|
||||||
|
|
||||||
from analytics.services import attribute_lead_from_request
|
from analytics.services import attribute_lead_from_request
|
||||||
from contacts.models import Channel, ConsentRecord, Contact
|
from contacts.models import Channel, ConsentRecord, Contact
|
||||||
@@ -15,6 +17,14 @@ from messaging.services import (
|
|||||||
unsubscribe_all,
|
unsubscribe_all,
|
||||||
)
|
)
|
||||||
from public.forms import ContactForm, NotifyForm
|
from public.forms import ContactForm, NotifyForm
|
||||||
|
from public.notifications import notify_admins_of_contact_form
|
||||||
|
|
||||||
|
|
||||||
|
def _public_site_base(request) -> str:
|
||||||
|
base = (settings.PUBLIC_SITE_URL or "").rstrip("/")
|
||||||
|
if base:
|
||||||
|
return base
|
||||||
|
return request.build_absolute_uri("/").rstrip("/")
|
||||||
|
|
||||||
|
|
||||||
def home(request):
|
def home(request):
|
||||||
@@ -25,6 +35,51 @@ def about(request):
|
|||||||
return render(request, "public/about.html")
|
return render(request, "public/about.html")
|
||||||
|
|
||||||
|
|
||||||
|
@require_GET
|
||||||
|
def robots_txt(request):
|
||||||
|
site = _public_site_base(request)
|
||||||
|
body = "\n".join(
|
||||||
|
[
|
||||||
|
"User-agent: *",
|
||||||
|
"Allow: /",
|
||||||
|
"Disallow: /portal/",
|
||||||
|
"Disallow: /accounts/",
|
||||||
|
"Disallow: /admin/",
|
||||||
|
"Disallow: /api/",
|
||||||
|
f"Sitemap: {site}/sitemap.xml",
|
||||||
|
"",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
return HttpResponse(body, content_type="text/plain; charset=utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
@require_GET
|
||||||
|
def sitemap_xml(request):
|
||||||
|
site = _public_site_base(request)
|
||||||
|
paths = [
|
||||||
|
("public:home", "1.0", "weekly"),
|
||||||
|
("public:about", "0.8", "monthly"),
|
||||||
|
("public:contact", "0.9", "monthly"),
|
||||||
|
]
|
||||||
|
urls = []
|
||||||
|
for name, priority, changefreq in paths:
|
||||||
|
path = reverse(name)
|
||||||
|
urls.append(
|
||||||
|
" <url>\n"
|
||||||
|
f" <loc>{site}{path}</loc>\n"
|
||||||
|
f" <changefreq>{changefreq}</changefreq>\n"
|
||||||
|
f" <priority>{priority}</priority>\n"
|
||||||
|
" </url>"
|
||||||
|
)
|
||||||
|
body = (
|
||||||
|
'<?xml version="1.0" encoding="UTF-8"?>\n'
|
||||||
|
'<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n'
|
||||||
|
+ "\n".join(urls)
|
||||||
|
+ "\n</urlset>\n"
|
||||||
|
)
|
||||||
|
return HttpResponse(body, content_type="application/xml; charset=utf-8")
|
||||||
|
|
||||||
|
|
||||||
@require_http_methods(["GET", "POST"])
|
@require_http_methods(["GET", "POST"])
|
||||||
def contact(request):
|
def contact(request):
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
@@ -72,6 +127,7 @@ def contact(request):
|
|||||||
status=Lead.Status.NEW,
|
status=Lead.Status.NEW,
|
||||||
)
|
)
|
||||||
attribute_lead_from_request(request, lead)
|
attribute_lead_from_request(request, lead)
|
||||||
|
notify_admins_of_contact_form(lead)
|
||||||
messages.success(request, "Thanks — Monica will be in touch soon.")
|
messages.success(request, "Thanks — Monica will be in touch soon.")
|
||||||
return redirect(f"{reverse('public:contact')}?sent=1")
|
return redirect(f"{reverse('public:contact')}?sent=1")
|
||||||
else:
|
else:
|
||||||
|
|||||||
Reference in New Issue
Block a user