Add DOT/FMCSA compliance checker frontend page

Static page at /tools/dot-compliance-check with:
- DOT number lookup with live FMCSA API checks
- Name search against local census database
- Color-coded check cards (green/yellow/red) for each filing
- Auto-fill from ?dot= URL param
- CTA to email for help when issues found
- Mobile responsive, orange/trucking theme

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
justin 2026-05-28 21:30:23 -05:00
parent b1d21c2ba7
commit 6e0a84418f

View file

@ -0,0 +1,278 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Free DOT/FMCSA motor carrier compliance check. Verify MCS-150 status, insurance, operating authority, and safety rating instantly.">
<title>DOT Compliance Check — Free Motor Carrier Filing Status | Performance West Inc.</title>
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<script defer src="https://analytics.performancewest.net/script.js" data-website-id="55250014-ee15-44ac-a1f6-81dabad3fe0f"></script>
<style>
*{margin:0;padding:0;box-sizing:border-box}
body{font-family:'Inter',system-ui,sans-serif;background:#f8fafc;color:#1f2937;min-height:100vh}
.container{max-width:800px;margin:0 auto;padding:24px 16px}
.header{background:#1a2744;color:white;padding:32px 0;text-align:center}
.header h1{font-size:28px;font-weight:700;margin-bottom:8px}
.header p{color:#94a3b8;font-size:14px}
.search-box{background:white;border-radius:12px;box-shadow:0 2px 12px rgba(0,0,0,0.08);padding:24px;margin:-32px auto 24px;max-width:600px;position:relative;z-index:10}
.search-row{display:flex;gap:8px}
.search-row input{flex:1;border:1px solid #cbd5e1;border-radius:8px;padding:12px 16px;font-size:15px;outline:none;font-family:monospace}
.search-row input:focus{border-color:#f97316;box-shadow:0 0 0 3px rgba(249,115,22,0.15)}
.search-row button{background:#f97316;color:white;border:none;border-radius:8px;padding:12px 24px;font-weight:700;font-size:14px;cursor:pointer;white-space:nowrap}
.search-row button:hover{background:#ea580c}
.search-row button:disabled{background:#9ca3af;cursor:not-allowed}
.tabs{display:flex;gap:0;margin-bottom:16px;border-bottom:2px solid #e5e7eb}
.tab{padding:8px 16px;font-size:13px;font-weight:600;color:#6b7280;cursor:pointer;border-bottom:2px solid transparent;margin-bottom:-2px}
.tab.active{color:#f97316;border-bottom-color:#f97316}
.name-results{margin-top:12px;max-height:300px;overflow-y:auto}
.name-result{padding:12px;border:1px solid #e5e7eb;border-radius:8px;margin-bottom:8px;cursor:pointer;transition:all 0.15s}
.name-result:hover{border-color:#f97316;background:#fff7ed}
.hidden{display:none!important}
.loading{text-align:center;padding:40px}
.loading .spinner{width:40px;height:40px;border:3px solid #e5e7eb;border-top-color:#f97316;border-radius:50%;animation:spin 0.8s linear infinite;margin:0 auto 16px}
@keyframes spin{to{transform:rotate(360deg)}}
.error-box{background:#fef2f2;border:1px solid #fca5a5;border-radius:8px;padding:16px;color:#991b1b;margin-top:16px}
.results{margin-top:24px}
.entity-card{background:white;border-radius:12px;box-shadow:0 2px 8px rgba(0,0,0,0.06);padding:24px;margin-bottom:16px}
.entity-name{font-size:22px;font-weight:700;color:#1a2744}
.entity-meta{font-size:13px;color:#6b7280;margin-top:4px}
.entity-meta span{margin-right:16px}
.check-card{border-radius:10px;padding:16px;margin-bottom:10px;display:flex;gap:12px;align-items:flex-start}
.check-card.green{background:#f0fdf4;border:1px solid #bbf7d0}
.check-card.yellow{background:#fefce8;border:1px solid #fde68a}
.check-card.red{background:#fef2f2;border:1px solid #fecaca}
.check-card.unknown{background:#f8fafc;border:1px solid #e2e8f0}
.check-icon{width:24px;height:24px;flex-shrink:0;margin-top:2px}
.check-label{font-weight:600;font-size:14px;margin-bottom:2px}
.check-detail{font-size:13px;color:#374151;line-height:1.5}
.green .check-label{color:#166534}
.yellow .check-label{color:#854d0e}
.red .check-label{color:#991b1b}
.summary-bar{display:flex;gap:12px;padding:16px;background:white;border-radius:10px;box-shadow:0 1px 4px rgba(0,0,0,0.06);margin-bottom:16px;justify-content:center}
.summary-item{font-size:13px;font-weight:600;display:flex;align-items:center;gap:4px}
.cta-box{background:linear-gradient(135deg,#fff7ed,#ffedd5);border:2px solid #f97316;border-radius:12px;padding:24px;text-align:center;margin-top:24px}
.cta-box h3{font-size:18px;font-weight:700;color:#1a2744;margin-bottom:8px}
.cta-box p{font-size:13px;color:#6b7280;margin-bottom:16px}
.cta-btn{display:inline-block;background:#f97316;color:white;font-weight:700;padding:14px 36px;border-radius:8px;text-decoration:none;font-size:15px;box-shadow:0 4px 12px rgba(249,115,22,0.3)}
.cta-btn:hover{background:#ea580c}
.footer{text-align:center;padding:24px;font-size:11px;color:#9ca3af}
.footer a{color:#6b7280}
</style>
</head>
<body>
<div class="header">
<div class="container">
<a href="/" style="display:inline-block;margin-bottom:12px"><img src="/images/logo.png" alt="Performance West" style="height:44px"></a>
<h1>DOT / FMCSA Compliance Check</h1>
<p>Free motor carrier filing status check — results in seconds</p>
</div>
</div>
<div class="container">
<div class="search-box">
<div class="tabs">
<div class="tab active" data-tab="dot">Search by DOT #</div>
<div class="tab" data-tab="name">Search by Name</div>
</div>
<div id="tab-dot">
<div class="search-row">
<input type="text" id="dot-input" placeholder="Enter USDOT number (e.g. 1234567)" maxlength="10">
<button type="button" id="dot-btn">Check Compliance</button>
</div>
</div>
<div id="tab-name" class="hidden">
<div class="search-row">
<input type="text" id="name-input" placeholder="Enter carrier name (e.g. Swift Transportation)">
<button type="button" id="name-btn">Search</button>
</div>
<div id="name-results" class="name-results hidden"></div>
</div>
</div>
<div id="loading" class="loading hidden">
<div class="spinner"></div>
<p style="color:#6b7280">Checking compliance status...</p>
</div>
<div id="error-box" class="error-box hidden"></div>
<div id="results" class="results hidden"></div>
</div>
<div class="footer">
<p>Performance West Inc. &middot; 525 Randall Ave Ste 100-1195, Cheyenne, WY 82001 &middot; <a href="https://performancewest.net">performancewest.net</a> &middot; (888) 411-0383</p>
<p style="margin-top:4px">Performance West is a regulatory compliance consulting firm, not a law firm. This tool does not constitute legal advice.</p>
</div>
<script>
var API = window.__PW_API || "https://api.performancewest.net";
// Tab switching
document.querySelectorAll(".tab").forEach(function(tab) {
tab.addEventListener("click", function() {
document.querySelectorAll(".tab").forEach(function(t) { t.classList.remove("active"); });
tab.classList.add("active");
document.getElementById("tab-dot").classList.toggle("hidden", tab.dataset.tab !== "dot");
document.getElementById("tab-name").classList.toggle("hidden", tab.dataset.tab !== "name");
});
});
// DOT lookup
var dotInput = document.getElementById("dot-input");
var dotBtn = document.getElementById("dot-btn");
var loadingEl = document.getElementById("loading");
var errorBox = document.getElementById("error-box");
var resultsEl = document.getElementById("results");
dotBtn.addEventListener("click", runDotCheck);
dotInput.addEventListener("keydown", function(e) { if (e.key === "Enter") runDotCheck(); });
async function runDotCheck() {
var raw = dotInput.value.trim().replace(/\D/g, "");
if (!raw) { showError("Please enter a DOT number."); return; }
loadingEl.classList.remove("hidden");
resultsEl.classList.add("hidden");
errorBox.classList.add("hidden");
try {
var res = await fetch(API + "/api/v1/dot/lookup?dot=" + raw);
var data = await res.json();
if (!res.ok) throw new Error(data.error || "Lookup failed");
renderResults(data);
} catch (e) {
showError(e.message || "Something went wrong. Try again.");
} finally {
loadingEl.classList.add("hidden");
}
}
// Name search
var nameInput = document.getElementById("name-input");
var nameBtn = document.getElementById("name-btn");
var nameResults = document.getElementById("name-results");
nameBtn.addEventListener("click", runNameSearch);
nameInput.addEventListener("keydown", function(e) { if (e.key === "Enter") runNameSearch(); });
async function runNameSearch() {
var name = nameInput.value.trim();
if (!name || name.length < 2) return;
nameResults.classList.remove("hidden");
nameResults.innerHTML = '<p style="color:#6b7280;font-size:13px">Searching...</p>';
try {
var res = await fetch(API + "/api/v1/dot/search?name=" + encodeURIComponent(name));
var data = await res.json();
if (!data.results || data.results.length === 0) {
nameResults.innerHTML = '<p style="color:#6b7280;font-size:13px">No carriers found matching "' + name + '"</p>';
return;
}
var html = "";
data.results.forEach(function(r) {
var overdue = r.mcs150_parsed && new Date(r.mcs150_parsed) < new Date(Date.now() - 2 * 365.25 * 86400000);
html += '<div class="name-result" data-dot="' + r.dot_number + '">';
html += '<div style="font-weight:600;color:#1a2744">' + r.legal_name + '</div>';
html += '<div style="font-size:12px;color:#6b7280;margin-top:2px">';
html += 'DOT# ' + r.dot_number;
if (r.phy_city) html += ' &middot; ' + r.phy_city + ', ' + r.phy_state;
if (r.nbr_power_unit) html += ' &middot; ' + r.nbr_power_unit + ' trucks';
if (overdue) html += ' &middot; <span style="color:#dc2626;font-weight:600">MCS-150 OVERDUE</span>';
html += '</div></div>';
});
nameResults.innerHTML = html;
nameResults.querySelectorAll(".name-result").forEach(function(el) {
el.addEventListener("click", function() {
dotInput.value = el.dataset.dot;
document.querySelector('.tab[data-tab="dot"]').click();
runDotCheck();
});
});
} catch (e) {
nameResults.innerHTML = '<p style="color:#dc2626;font-size:13px">Search failed. Try again.</p>';
}
}
function showError(msg) {
errorBox.textContent = msg;
errorBox.classList.remove("hidden");
resultsEl.classList.add("hidden");
}
var statusIcons = {
green: '<svg class="check-icon" fill="none" stroke="#16a34a" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M9 12.75L11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>',
yellow: '<svg class="check-icon" fill="none" stroke="#ca8a04" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m9-.75a9 9 0 11-18 0 9 9 0 0118 0zm-9 3.75h.008v.008H12v-.008z"/></svg>',
red: '<svg class="check-icon" fill="none" stroke="#dc2626" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z"/></svg>',
unknown: '<svg class="check-icon" fill="none" stroke="#6b7280" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M9.879 7.519c1.171-1.025 3.071-1.025 4.242 0 1.172 1.025 1.172 2.687 0 3.712-.203.179-.43.326-.67.442-.745.361-1.45.999-1.45 1.827v.75M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9 5.25h.008v.008H12v-.008z"/></svg>'
};
function renderResults(data) {
var html = '';
// Entity card
html += '<div class="entity-card">';
html += '<div class="entity-name">' + data.legal_name + '</div>';
if (data.dba_name) html += '<div style="font-size:14px;color:#6b7280;margin-top:2px">d/b/a ' + data.dba_name + '</div>';
html += '<div class="entity-meta">';
html += '<span>DOT# ' + data.dot_number + '</span>';
if (data.phy_city) html += '<span>' + data.phy_city + ', ' + data.phy_state + '</span>';
if (data.fleet && data.fleet.power_units) html += '<span>' + data.fleet.power_units + ' power units</span>';
if (data.fleet && data.fleet.drivers) html += '<span>' + data.fleet.drivers + ' drivers</span>';
html += '</div></div>';
// Summary bar
var s = data.summary || {};
html += '<div class="summary-bar">';
if (s.red) html += '<div class="summary-item" style="color:#dc2626">' + statusIcons.red + ' ' + s.red + ' issue' + (s.red > 1 ? 's' : '') + '</div>';
if (s.yellow) html += '<div class="summary-item" style="color:#ca8a04">' + statusIcons.yellow + ' ' + s.yellow + ' warning' + (s.yellow > 1 ? 's' : '') + '</div>';
if (s.green) html += '<div class="summary-item" style="color:#16a34a">' + statusIcons.green + ' ' + s.green + ' passed</div>';
html += '</div>';
// Check cards
(data.checks || []).forEach(function(c) {
html += '<div class="check-card ' + c.status + '">';
html += statusIcons[c.status] || statusIcons.unknown;
html += '<div><div class="check-label">' + c.label + '</div>';
html += '<div class="check-detail">' + c.detail + '</div></div></div>';
});
// CTA
var redCount = (s.red || 0);
if (redCount > 0) {
html += '<div class="cta-box">';
html += '<h3>' + redCount + ' compliance issue' + (redCount > 1 ? 's' : '') + ' found</h3>';
html += '<p>Performance West can help you resolve these issues. We handle the paperwork so you can focus on driving.</p>';
html += '<a class="cta-btn" href="mailto:info@performancewest.net?subject=DOT Compliance Help — ' + encodeURIComponent(data.legal_name) + ' (DOT ' + data.dot_number + ')">Get Help With My Filings &rarr;</a>';
html += '<p style="font-size:11px;color:#9ca3af;margin-top:10px">Or call (888) 411-0383</p>';
html += '</div>';
} else {
html += '<div style="background:#f0fdf4;border:2px solid #86efac;border-radius:12px;padding:24px;text-align:center;margin-top:24px">';
html += '<h3 style="font-size:18px;font-weight:700;color:#166534;margin-bottom:4px">Looking good!</h3>';
html += '<p style="font-size:13px;color:#374151">Your DOT filings appear current. No action needed at this time.</p>';
html += '</div>';
}
resultsEl.innerHTML = html;
resultsEl.classList.remove("hidden");
}
// Auto-fill from URL
(function() {
var params = new URLSearchParams(window.location.search);
var dot = params.get("dot");
if (dot) {
dotInput.value = dot;
runDotCheck();
}
})();
</script>
</body>
</html>