Improve Lighthouse Agentic Browsing readiness (#8)
Deploy Company Site / test (push) Successful in 10s
Deploy Company Site / deploy (push) Successful in 5s

## Summary

- Add `robots.txt`, `sitemap.xml`, and `llms.txt` endpoints so crawlers and AI agents can discover public pages
- Fix accessibility tree issues: contact form labels, semantic nav/profile dropdown buttons, cookie consent dialog ARIA, and dead `href="#"` links
- Reduce layout shift on homepage/contact via hero min-heights, trusted-by marquee sizing, and fixed cookie banner
- Add regression tests and `docs/agentic-browsing.md` for Lighthouse agentic-browsing audits

Closes #5. Also addresses overlap with #3 (sitemap + robots.txt).

## Test plan

- [x] `python manage.py test public.tests` (14 tests pass)
- [ ] Verify `GET /robots.txt`, `/sitemap.xml`, `/llms.txt` return 200 in staging/prod
- [ ] Confirm contact form labels visible and submittable on `/contact`
- [ ] Run Lighthouse `--only-categories=agentic-browsing` on homepage and contact page
- [ ] Smoke test mobile nav dropdown and cookie consent banner

Reviewed-on: #8
This commit was merged in pull request #8.
This commit is contained in:
2026-07-02 11:36:54 +00:00
parent c8574d76d4
commit 846f1b8a37
14 changed files with 436 additions and 44 deletions
+49 -15
View File
@@ -49,18 +49,18 @@
<body>
<!-- Navigation Bar -->
<nav>
<nav aria-label="Main navigation">
<a href="{% url 'public_index' %}" class="brand-logo">
<img src="{% static 'public/img/logo.png' %}" alt="AI ML Operations" class="brand-logo-img" width="243" height="28">
</a>
<button class="mobile-menu-btn" aria-label="Menu"></button>
<ul class="nav-links">
<button type="button" class="mobile-menu-btn" aria-label="Open menu" aria-expanded="false" aria-controls="main-nav-links"></button>
<ul class="nav-links" id="main-nav-links">
<li><a href="{% url 'public_index' %}"
class="{% if request.resolver_match.url_name == 'public_index' %}active{% endif %}">Home</a></li>
<li class="dropdown">
<a href="#"
class="{% if request.resolver_match.url_name in 'forward_deployed,ai_education,ai_sensor,bot,chat,computers,file_hosting,ml_model,web_design' %}active{% endif %}">Services</a>
<ul class="dropdown-content">
<button type="button" class="nav-dropdown-trigger{% if request.resolver_match.url_name in 'forward_deployed,ai_education,ai_sensor,bot,chat,computers,file_hosting,ml_model,web_design' %} active{% endif %}"
id="services-menu-button" aria-expanded="false" aria-haspopup="true" aria-controls="services-menu">Services</button>
<ul class="dropdown-content" id="services-menu" role="menu" aria-labelledby="services-menu-button">
<li><a href="{% url 'forward_deployed' %}">Forward-Deployed AI</a></li>
<li><a href="{% url 'bot' %}">AI Agents</a></li>
<li><a href="{% url 'ml_model' %}">ML Models</a></li>
@@ -83,14 +83,15 @@
class="{% if 'financial' in request.path %}active{% endif %}"
data-tianji-event="nav_financials">Financials</a></li>
<li class="dropdown" id="user-profile-dropdown">
<a href="#" class="profile-icon-link" title="{{ user.get_full_name|default:user.username }}">
<button type="button" class="profile-icon-link" aria-label="Account menu for {{ user.get_full_name|default:user.username }}"
aria-expanded="false" aria-haspopup="true" aria-controls="profile-menu">
<svg class="profile-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<circle cx="12" cy="8" r="4" />
<path d="M4 21v-1a6 6 0 0 1 12 0v1" />
</svg>
</a>
<ul class="dropdown-content profile-dropdown-content">
</button>
<ul class="dropdown-content profile-dropdown-content" id="profile-menu" role="menu">
<li class="profile-name-item">{{ user.get_full_name|default:user.username }}</li>
<li><a href="{% url 'change_password' %}">Change Password</a></li>
<li>
@@ -123,7 +124,7 @@
<a href="{% url 'terms_of_service' %}" data-tianji-event="footer_terms">Terms &amp; Privacy</a>
{% if tianji_enabled %}
<span class="footer-separator">|</span>
<a href="#" data-open-cookie-preferences data-tianji-event="footer_cookie_preferences">Cookie Preferences</a>
<button type="button" class="footer-link-btn" data-open-cookie-preferences data-tianji-event="footer_cookie_preferences">Cookie Preferences</button>
{% endif %}
</p>
<p class="footer-text footer-text--muted">&copy; 2023 -
@@ -133,10 +134,11 @@
</footer>
{% if tianji_enabled %}
<div id="cookie-consent-banner" class="cookie-consent-banner" hidden role="dialog" aria-live="polite"
aria-label="Cookie consent">
<div id="cookie-consent-banner" class="cookie-consent-banner" hidden role="dialog" aria-modal="true"
aria-labelledby="cookie-consent-title" aria-describedby="cookie-consent-description">
<div class="cookie-consent-content">
<p class="cookie-consent-text">
<h2 id="cookie-consent-title" class="visually-hidden">Cookie consent</h2>
<p class="cookie-consent-text" id="cookie-consent-description">
<span class="cookie-consent-text-full">We use analytics tracking to understand how visitors use our site. Tracking runs only if you accept.
See our <a href="{% url 'terms_of_service' %}">Terms of Service &amp; Privacy Policy</a> for details.</span>
<span class="cookie-consent-text-short">We use analytics if you accept. <a href="{% url 'terms_of_service' %}">Privacy Policy</a></span>
@@ -157,9 +159,41 @@
if (mobileBtn && navLinks) {
mobileBtn.addEventListener('click', function () {
navLinks.classList.toggle('active');
const isOpen = navLinks.classList.toggle('active');
mobileBtn.setAttribute('aria-expanded', isOpen ? 'true' : 'false');
mobileBtn.setAttribute('aria-label', isOpen ? 'Close menu' : 'Open menu');
});
}
function closeDropdown(dropdown) {
dropdown.classList.remove('dropdown-open');
const trigger = dropdown.querySelector('.nav-dropdown-trigger, .profile-icon-link');
if (trigger) {
trigger.setAttribute('aria-expanded', 'false');
}
}
document.querySelectorAll('.dropdown').forEach(function (dropdown) {
const trigger = dropdown.querySelector('.nav-dropdown-trigger, .profile-icon-link');
if (!trigger) {
return;
}
trigger.addEventListener('click', function (event) {
if (window.matchMedia('(min-width: 769px)').matches && dropdown.querySelector('.nav-dropdown-trigger')) {
return;
}
event.preventDefault();
const isOpen = dropdown.classList.toggle('dropdown-open');
trigger.setAttribute('aria-expanded', isOpen ? 'true' : 'false');
});
});
document.addEventListener('click', function (event) {
if (!event.target.closest('.dropdown')) {
document.querySelectorAll('.dropdown.dropdown-open').forEach(closeDropdown);
}
});
});
</script>
{% block tracking_events %}{% endblock %}
@@ -6,7 +6,7 @@
{% block content %}
<!-- Hero Section -->
<div class="hero-section" style="height: 40vh; min-height: 300px;">
<div class="hero-section hero-section--compact">
<div class="hero-content">
<h1 class="hero-title">Get in Touch</h1>
<p class="hero-subtitle hero-subtitle--wide">Describe the workflow or bottleneck you want to automate—we will help you scope a forward-deployed solution.</p>
@@ -32,24 +32,28 @@
{% endif %}
<div class="card">
<h4 class="card-title" style="margin-bottom: 2rem;">Send Us a Message</h4>
<form action="{% url 'contact' %}" method="POST">
<h2 class="card-title contact-form-heading" id="contact-form-heading" style="margin-bottom: 2rem;">Send Us a Message</h2>
<form action="{% url 'contact' %}" method="POST" aria-labelledby="contact-form-heading">
{% csrf_token %}
<div class="form-row">
<div class="form-group">
<input type="text" class="form-control" name="name" placeholder="Your Name">
<label class="form-label" for="contact-name">Your Name</label>
<input type="text" class="form-control" name="name" id="contact-name" autocomplete="name" required>
</div>
<div class="form-group">
<input type="email" class="form-control" name="email" placeholder="Your Email">
<label class="form-label" for="contact-email">Your Email</label>
<input type="email" class="form-control" name="email" id="contact-email" autocomplete="email" required>
</div>
</div>
<div class="form-group">
<input type="text" class="form-control" name="subject" id="contact-subject" placeholder="Subject" value="{{ request.GET.subject|default:'' }}">
<label class="form-label" for="contact-subject">Subject</label>
<input type="text" class="form-control" name="subject" id="contact-subject" value="{{ request.GET.subject|default:'' }}" required>
</div>
<div class="form-group">
<textarea name="message" class="form-control" rows="5" placeholder="What workflow is costing you the most time? What systems does it touch?"></textarea>
<label class="form-label" for="contact-message">Message</label>
<textarea name="message" class="form-control" id="contact-message" rows="5" placeholder="What workflow is costing you the most time? What systems does it touch?"></textarea>
</div>
<div class="form-group">
@@ -69,15 +73,15 @@
<!-- Info Column -->
<div>
<div class="card">
<h5 class="card-title">Contact Information</h5>
<ul style="list-style: none;">
<li style="margin-bottom: 1rem; display: flex; align-items: center; gap: 1rem;">
<span style="color: var(--primary-color);">📞</span>
<p style="color: var(--text-muted);">+1 (330) 402-2675</p>
<h3 class="card-title">Contact Information</h3>
<ul class="contact-info-list">
<li class="contact-info-item">
<span class="contact-info-icon" aria-hidden="true">📞</span>
<a href="tel:+13304022675">+1 (330) 402-2675</a>
</li>
<li style="margin-bottom: 1rem; display: flex; align-items: center; gap: 1rem;">
<span style="color: var(--primary-color);">✉️</span>
<p style="color: var(--text-muted);">ryan@aimloperations.com</p>
<li class="contact-info-item">
<span class="contact-info-icon" aria-hidden="true">✉️</span>
<a href="mailto:ryan@aimloperations.com">ryan@aimloperations.com</a>
</li>
</ul>
</div>
@@ -96,4 +100,4 @@
}
</script>
{% endif %}
{% endblock %}
{% endblock %}
@@ -106,13 +106,16 @@
-->
<div class="form-group col-6" style="padding-left:1rem">
<input type="email" class="form-control" name="email" placeholder="Your Email">
<label for="legacy-contact-email">Your Email</label>
<input type="email" class="form-control" name="email" id="legacy-contact-email" placeholder="Your Email">
</div>
<div class="form-group col-6" style="padding-right:1rem">
<input type="text" class="form-control" name="name" placeholder="Your Name">
<label for="legacy-contact-name">Your Name</label>
<input type="text" class="form-control" name="name" id="legacy-contact-name" placeholder="Your Name">
</div>
<div class="form-group col-12" style="padding:1rem">
<textarea name="message" type="text" class="form-control" rows="5" placeholder="Your message"></textarea>
<label for="legacy-contact-message">Your Message</label>
<textarea name="message" id="legacy-contact-message" class="form-control" rows="5" placeholder="Your message"></textarea>
</div>
<div class="form-group col-12">
{% if capchaForm %}
@@ -0,0 +1,19 @@
# AI ML Operations, LLC
> Forward-deployed AI engineering. We embed with your team to architect, build, and deploy custom agentic workflows, integrations, and production AI systems.
AI ML Operations helps organizations move from AI pilots to production systems. Core services include forward-deployed AI engineering, custom AI agents, ML model development, secure hosted chat, sensor algorithms, education, hardware builds, and web hosting.
## Key pages
{% for page in pages %}- [{{ page.title }}]({{ page.url }})
{% endfor %}
## Contact
- [Contact form]({{ contact_url }})
- Phone: +1 (330) 402-2675
- Email: ryan@aimloperations.com
## Site
{{ site_url }}
@@ -11,7 +11,7 @@
{% block content %}
<!-- Hero Section -->
<div class="hero-section">
<canvas id="hero-canvas"></canvas>
<canvas id="hero-canvas" aria-hidden="true"></canvas>
<div class="hero-content">
<h1 class="hero-title">AI ML Operations</h1>
<p class="hero-subtitle hero-subtitle--wide">
@@ -0,0 +1,20 @@
User-agent: *
Allow: /
Disallow: /admin/
Disallow: /accounts/
Disallow: /financial/
Disallow: /planning/
User-agent: GPTBot
Allow: /
User-agent: ClaudeBot
Allow: /
User-agent: Google-Extended
Allow: /
User-agent: PerplexityBot
Allow: /
Sitemap: {{ sitemap_url }}
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
{% for page in pages %}
<url>
<loc>{{ page.loc }}</loc>
<changefreq>{{ page.changefreq }}</changefreq>
<priority>{{ page.priority }}</priority>
</url>
{% endfor %}
</urlset>
@@ -97,7 +97,7 @@ visually stunning, functional websites tailored to your business.{% endblock %}
<div class="pricing-toggle" role="group" aria-label="Billing period">
<span class="pricing-toggle-label active" id="monthlyLabel">Monthly</span>
<label class="pricing-switch" for="pricingToggle">
<input type="checkbox" id="pricingToggle" role="switch" aria-labelledby="monthlyLabel yearlyLabel">
<input type="checkbox" id="pricingToggle" aria-label="Toggle between monthly and yearly billing">
<span class="pricing-switch-slider"></span>
</label>
<span class="pricing-toggle-label" id="yearlyLabel">Yearly</span>
@@ -119,7 +119,7 @@ visually stunning, functional websites tailored to your business.{% endblock %}
<li style="margin-bottom: 0.5rem;">✓ Email Notifications</li>
<li style="margin-bottom: 0.5rem;">✓ Backend Admin Access</li>
</ul>
<a href="#" class="btn">Get Started</a>
<a href="{% url 'contact' %}?subject=Web%20Hosting%20Standard%20Plan" class="btn">Get Started</a>
</div>
<!-- Yearly Card -->
@@ -135,7 +135,7 @@ visually stunning, functional websites tailored to your business.{% endblock %}
<li style="margin-bottom: 0.5rem;">✓ 2 Months Free (Yearly)</li>
<li style="margin-bottom: 0.5rem;">✓ Priority Support</li>
</ul>
<a href="#" class="btn" style="background: var(--secondary-color); color: white;">Save 20%</a>
<a href="{% url 'contact' %}?subject=Web%20Hosting%20Premium%20Plan" class="btn" style="background: var(--secondary-color); color: white;">Save 20%</a>
</div>
</div>
</div>