Improve public SEO and align chrome with EXIT Realty teal.
Deploy Beta / unit-tests (push) Successful in 8s
Deploy Beta / docker (push) Successful in 13s
Deploy Beta / deploy-beta (push) Successful in 1m0s

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:
2026-08-08 08:50:59 -05:00
parent 40f2555ee9
commit 7f3bac7da3
11 changed files with 391 additions and 49 deletions
+112 -14
View File
@@ -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;
}
}
+24 -7
View File
@@ -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" {% block extra_head %}{% endblock %}
</head>
<body class="{% block body_class %}{% endblock %}">
{% if tianji_enabled %}
<div id="tianji-config"
data-tracker-url="{{ tianji_tracker_url }}" data-tracker-url="{{ tianji_tracker_url }}"
data-website-id="{{ tianji_website_id }}" data-website-id="{{ tianji_website_id }}"
data-page-name="{{ page_name }}" data-page-name="{{ page_name }}"
{% if user.is_authenticated %}data-user-id="{{ user.pk }}"{% endif %} {% if user.is_authenticated %}data-user-id="{{ user.pk }}"{% endif %}
hidden></div> hidden></div>
{% endif %} {% endif %}
{% block extra_head %}{% endblock %}
</head>
<body class="{% block body_class %}{% endblock %}">
{% block body %} {% block body %}
<div class="page"> <div class="page">
{% block header %} {% block header %}
+8 -7
View File
@@ -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 %} {% block extra_head %}{% endblock %}
<div id="tianji-config" </head>
<body class="portal {% block body_class %}{% endblock %}">
{% if tianji_enabled %}
<div id="tianji-config"
data-tracker-url="{{ tianji_tracker_url }}" data-tracker-url="{{ tianji_tracker_url }}"
data-website-id="{{ tianji_website_id }}" data-website-id="{{ tianji_website_id }}"
data-page-name="{{ page_name }}" data-page-name="{{ page_name }}"
{% if user.is_authenticated %}data-user-id="{{ user.pk }}"{% endif %} {% if user.is_authenticated %}data-user-id="{{ user.pk }}"{% endif %}
hidden></div> hidden></div>
{% endif %} {% endif %}
{% block extra_head %}{% endblock %}
</head>
<body class="portal {% block body_class %}{% endblock %}">
{% block body %} {% block body %}
<div class="portal-shell"> <div class="portal-shell">
<aside class="portal-sidebar"> <aside class="portal-sidebar">
+5 -1
View File
@@ -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 "",
+2
View File
@@ -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):
+74
View File
@@ -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
+37 -5
View File
@@ -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>
+35 -3
View File
@@ -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>
+31 -7
View File
@@ -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>
+2
View File
@@ -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
View File
@@ -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: