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:focus,
.page .button-primary:active {
background-color: #00626c !important;
border-color: #00626c !important;
.page .button-primary:active,
.cookie-consent-banner .button-primary,
.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;
}
.page .button-primary:hover {
background-color: #008898 !important;
border-color: #008898 !important;
.page .button-primary:hover,
.cookie-consent-banner .button-primary:hover {
background-color: var(--monica-primary-light) !important;
border-color: var(--monica-primary-light) !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 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 */
@@ -73,19 +137,21 @@
font-size: 13px;
}
/* Cookie consent — light theme fit */
/* Cookie consent — match public EXIT Realty teal brand */
.cookie-consent-banner {
position: fixed;
bottom: 0;
left: 0;
right: 0;
z-index: 11000;
padding: 1rem;
background: rgba(8, 8, 8, 0.96);
border-top: 1px solid rgba(0, 98, 108, 0.45);
padding: 1rem 1.25rem;
background: rgba(17, 24, 28, 0.96);
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 {
max-width: 960px;
max-width: 1200px;
margin: 0 auto;
display: flex;
flex-wrap: wrap;
@@ -95,10 +161,12 @@
}
.cookie-consent-text {
flex: 1 1 420px;
color: #cbd5e1;
font-size: 0.95rem;
color: #d7dee3;
font-size: 0.9375rem;
line-height: 1.45;
margin: 0;
font-family: "Work Sans", sans-serif;
font-weight: 400;
}
.cookie-consent-text-short {
display: none;
@@ -108,6 +176,15 @@
gap: 0.75rem;
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 {
position: absolute;
width: 1px;
@@ -197,3 +274,24 @@
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 name="viewport" content="width=device-width, initial-scale=1.0">
<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="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/fonts.css' %}">
<link rel="stylesheet" href="{% static 'css/style.css' %}" id="main-styles-link">
<link rel="stylesheet" href="{% static 'css/theme-overrides.css' %}">
{% if tianji_enabled %}
<div id="tianji-config"
{% block structured_data %}{% endblock %}
{% 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-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 %}
</head>
<body class="{% block body_class %}{% endblock %}">
{% endif %}
{% block body %}
<div class="page">
{% block header %}
+8 -7
View File
@@ -5,20 +5,21 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<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="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' %}">
{% if tianji_enabled %}
<div id="tianji-config"
{% block extra_head %}{% endblock %}
</head>
<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 extra_head %}{% endblock %}
</head>
<body class="portal {% block body_class %}{% endblock %}">
{% endif %}
{% block body %}
<div class="portal-shell">
<aside class="portal-sidebar">
+5 -1
View File
@@ -8,11 +8,15 @@ def _phone_tel(phone: str) -> str:
return digits
def site_branding(_request):
def site_branding(request):
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 {
"SITE_NAME": settings.SITE_NAME,
"SITE_TAGLINE": settings.SITE_TAGLINE,
"PUBLIC_SITE_URL": public_site_url,
"CONTACT_PHONE": phone,
"CONTACT_PHONE_TEL": _phone_tel(phone),
"CONTACT_EMAIL": settings.CONTACT_EMAIL or "",
+2
View File
@@ -21,6 +21,8 @@ class UnderConstructionMiddleware:
"/portal/messaging/webhooks/",
"/unsubscribe/",
"/api/address-suggest",
"/robots.txt",
"/sitemap.xml",
)
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" %}
{% 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 %}
<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="container breadcrumbs-custom-container">
<div class="breadcrumbs-custom-main">
<h6 class="breadcrumbs-custom-subtitle title-decorated">About</h6>
<h2 class="text-uppercase breadcrumbs-custom-title">Meet Monica</h2>
<p class="breadcrumbs-custom-subtitle title-decorated">About</p>
<h1 class="text-uppercase breadcrumbs-custom-title">Meet Monica</h1>
</div>
<ul class="breadcrumbs-custom-path">
<li><a href="{% url 'public:home' %}">Home</a></li>
@@ -20,13 +52,13 @@
<div class="container">
<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">
<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>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>
</div>
<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>
+35 -3
View File
@@ -1,13 +1,45 @@
{% extends "base.html" %}
{% 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 %}
<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="container breadcrumbs-custom-container">
<div class="breadcrumbs-custom-main">
<h6 class="breadcrumbs-custom-subtitle title-decorated">Contact</h6>
<h2 class="text-uppercase breadcrumbs-custom-title">Get in touch</h2>
<p class="breadcrumbs-custom-subtitle title-decorated">Contact</p>
<h1 class="text-uppercase breadcrumbs-custom-title">Get in touch</h1>
</div>
<ul class="breadcrumbs-custom-path">
<li><a href="{% url 'public:home' %}">Home</a></li>
+31 -7
View File
@@ -1,6 +1,30 @@
{% extends "base.html" %}
{% 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 %}
<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">
@@ -24,7 +48,7 @@
<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>
<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>
<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>
@@ -39,13 +63,13 @@
</div>
<section class="section section-lg">
<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="col-md-4 wow-outer">
<article class="box-chloe wow slideInUp">
<div class="box-chloe__icon linearicons-home-icon3"></div>
<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>
</div>
</article>
@@ -54,7 +78,7 @@
<article class="box-chloe wow slideInUp" data-wow-delay=".05s">
<div class="box-chloe__icon linearicons-apartment"></div>
<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>
</div>
</article>
@@ -63,7 +87,7 @@
<article class="box-chloe wow slideInUp" data-wow-delay=".1s">
<div class="box-chloe__icon linearicons-chart-growth"></div>
<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>
</div>
</article>
@@ -73,7 +97,7 @@
</section>
<section class="section section-lg bg-gray-100 text-center">
<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>
<a class="button button-lg button-primary button-winona" href="{% url 'public:contact' %}" data-tianji-event="home_cta_contact">Contact Monica</a>
</div>
+2
View File
@@ -8,6 +8,8 @@ urlpatterns = [
path("", views.home, name="home"),
path("about/", views.about, name="about"),
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("unsubscribe/<str:token>/", views.unsubscribe, name="unsubscribe"),
path(
+57 -1
View File
@@ -1,8 +1,10 @@
from django.conf import settings
from django.contrib import messages
from django.http import HttpResponse
from django.shortcuts import redirect, render
from django.urls import reverse
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 contacts.models import Channel, ConsentRecord, Contact
@@ -15,6 +17,14 @@ from messaging.services import (
unsubscribe_all,
)
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):
@@ -25,6 +35,51 @@ def about(request):
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"])
def contact(request):
if request.method == "POST":
@@ -72,6 +127,7 @@ def contact(request):
status=Lead.Status.NEW,
)
attribute_lead_from_request(request, lead)
notify_admins_of_contact_form(lead)
messages.success(request, "Thanks — Monica will be in touch soon.")
return redirect(f"{reverse('public:contact')}?sent=1")
else: