We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/tbrennem-source/sf-permits-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ query }} — sfpermits.ai</title>
<meta name="description" content="SF building permit search results for {{ query }}. Free permit lookup powered by 1.1M+ records.">
<link rel="manifest" href="/static/manifest.json">
<meta name="theme-color" content="#5eead4">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@300;400;500&family=IBM+Plex+Sans:wght@300;400;500;600&display=swap" rel="stylesheet">
<style nonce="{{ csp_nonce }}">
*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }
:root {
--obsidian: #0a0a0f;
--obsidian-mid: #12121a;
--obsidian-light: #1a1a26;
--glass: rgba(255, 255, 255, 0.04);
--glass-border: rgba(255, 255, 255, 0.06);
--glass-hover: rgba(255, 255, 255, 0.10);
--text-primary: rgba(255, 255, 255, 0.92);
--text-secondary: rgba(255, 255, 255, 0.55);
--text-tertiary: rgba(255, 255, 255, 0.30);
--text-ghost: rgba(255, 255, 255, 0.15);
--accent: #5eead4;
--accent-glow: rgba(94, 234, 212, 0.08);
--accent-ring: rgba(94, 234, 212, 0.30);
--signal-green: #34d399;
--signal-amber: #fbbf24;
--signal-red: #f87171;
--signal-blue: #60a5fa;
--dot-green: #22c55e;
--dot-amber: #f59e0b;
--dot-red: #ef4444;
--mono: 'JetBrains Mono', ui-monospace, 'Cascadia Code', monospace;
--sans: 'IBM Plex Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
--text-xs: clamp(0.65rem, 0.6rem + 0.2vw, 0.75rem);
--text-sm: clamp(0.75rem, 0.7rem + 0.25vw, 0.875rem);
--text-base: clamp(0.8125rem, 0.75rem + 0.3vw, 1rem);
--text-lg: clamp(0.875rem, 0.8rem + 0.4vw, 1.125rem);
--radius-sm: 6px;
--radius-md: 12px;
--radius-full: 9999px;
--space-1: 4px; --space-2: 8px; --space-3: 12px;
--space-4: 16px; --space-5: 20px; --space-6: 24px;
--space-8: 32px; --space-10: 40px;
}
html { scroll-behavior: smooth; scrollbar-width: thin; scrollbar-color: var(--glass-border) transparent; }
body {
font-family: var(--sans); background: var(--obsidian); color: var(--text-primary);
overflow-x: hidden; -webkit-font-smoothing: antialiased; line-height: 1.5;
}
/* ═══ NAV ═══ */
.nav-float {
position: fixed; top: 0; left: 0; right: 0; z-index: 50;
padding: 12px var(--space-6);
display: flex; align-items: center; justify-content: space-between;
background: rgba(0, 0, 0, 0.85);
backdrop-filter: blur(12px);
border-bottom: 1px solid var(--glass-border);
}
.nav-float__wordmark {
font-family: var(--mono); font-size: var(--text-xs); font-weight: 300;
letter-spacing: 0.35em; text-transform: uppercase; color: var(--text-tertiary);
text-decoration: none;
}
.nav-float__wordmark:hover { color: var(--text-secondary); }
.nav-float__links { display: flex; align-items: center; gap: var(--space-6); }
.nav-float__link {
font-family: var(--sans); font-size: var(--text-sm); font-weight: 400;
color: var(--text-secondary); text-decoration: none; transition: color 0.3s;
}
.nav-float__link:hover { color: var(--accent); }
.nav-float__cta {
font-family: var(--mono); font-size: var(--text-xs); font-weight: 400;
color: var(--text-tertiary); text-decoration: none;
padding: 5px 12px; border: 1px solid var(--glass-border); border-radius: var(--radius-sm);
transition: color 0.3s, border-color 0.3s;
}
.nav-float__cta:hover { color: var(--accent); border-color: var(--accent-ring); }
.page-body { padding-top: 56px; }
.obs-container { max-width: 1000px; margin: 0 auto; padding: 0 var(--space-6); }
/* ═══ SEARCH HEADER ═══ */
.search-header { padding: var(--space-6) 0 var(--space-4); }
.search-bar { position: relative; max-width: 520px; }
.search-input {
width: 100%; padding: 14px 20px; padding-right: 44px;
font-family: var(--mono); font-size: 14px; font-weight: 300;
color: var(--text-primary); background: var(--glass);
border: 1px solid var(--glass-border); border-radius: var(--radius-md); outline: none;
transition: border-color 0.4s, background 0.4s, box-shadow 0.4s;
}
.search-input::placeholder { color: var(--text-tertiary); font-weight: 300; }
.search-input:focus {
border-color: var(--accent-ring); background: rgba(255, 255, 255, 0.06);
box-shadow: 0 0 40px var(--accent-glow);
}
.search-bar .search-icon {
position: absolute; right: 14px; top: 50%; transform: translateY(-50%);
color: var(--text-tertiary); transition: color 0.3s;
}
.search-input:focus ~ .search-icon { color: var(--accent); }
.search-meta {
font-family: var(--mono); font-size: var(--text-xs); color: var(--text-tertiary);
margin-top: var(--space-3);
display: flex; align-items: center; gap: var(--space-4);
}
.search-meta__count { color: var(--text-secondary); }
/* ═══ FILTER CHIPS ═══ */
.filter-row {
display: flex; gap: var(--space-2); flex-wrap: wrap;
margin-bottom: var(--space-4);
}
.filter-chip {
font-family: var(--mono); font-size: var(--text-xs); font-weight: 400;
color: var(--text-tertiary); background: var(--glass);
border: 1px solid var(--glass-border);
padding: 4px 10px; border-radius: var(--radius-full);
cursor: pointer; transition: border-color 0.2s, color 0.2s;
text-decoration: none;
}
.filter-chip:hover { border-color: var(--glass-hover); color: var(--text-secondary); }
.filter-chip--active {
border-color: var(--accent-ring); color: var(--accent);
background: var(--accent-glow);
}
/* ═══ AI SUGGESTION ═══ */
.ai-suggest {
background: var(--accent-glow);
border: 1px solid var(--accent-ring);
border-radius: var(--radius-md);
padding: var(--space-4) var(--space-5);
margin-bottom: var(--space-6);
display: flex; align-items: center; gap: var(--space-4);
cursor: pointer; text-decoration: none;
transition: background 0.2s, border-color 0.2s;
}
.ai-suggest:hover {
background: rgba(94, 234, 212, 0.12);
border-color: rgba(94, 234, 212, 0.4);
}
.ai-suggest__icon { font-size: 16px; flex-shrink: 0; }
.ai-suggest__text {
font-family: var(--sans); font-size: var(--text-base); font-weight: 300;
color: var(--text-primary); flex: 1;
}
.ai-suggest__text em { font-style: normal; color: var(--accent); }
.ai-suggest__arrow { font-family: var(--mono); font-size: 12px; color: var(--accent); }
/* ═══ RESULTS ═══ */
.results-list { padding-bottom: var(--space-8); }
.results-content {
background: var(--obsidian-mid);
border: 1px solid var(--glass-border);
border-radius: var(--radius-md);
padding: var(--space-5);
transition: border-color 0.3s;
}
/* Markdown result HTML styling */
.results-content h1 {
font-family: var(--mono); font-size: var(--text-lg); font-weight: 300;
color: var(--text-primary); margin-bottom: var(--space-3);
}
.results-content h2 {
font-family: var(--sans); font-size: var(--text-base); font-weight: 400;
color: var(--text-secondary); margin: var(--space-5) 0 var(--space-2);
}
.results-content h3 {
font-family: var(--sans); font-size: var(--text-sm); font-weight: 500;
color: var(--text-secondary); margin: var(--space-4) 0 var(--space-2);
}
.results-content table {
width: 100%; border-collapse: collapse; font-size: var(--text-sm);
margin-bottom: var(--space-3);
}
.results-content th {
text-align: left; padding: 6px 10px;
font-family: var(--mono); font-size: var(--text-xs); font-weight: 400;
color: var(--text-tertiary); text-transform: uppercase; letter-spacing: 0.04em;
border-bottom: 1px solid var(--glass-border);
}
.results-content td {
padding: 8px 10px; vertical-align: top;
font-family: var(--sans); font-size: var(--text-sm); font-weight: 300;
color: var(--text-secondary);
border-bottom: 1px solid rgba(255, 255, 255, 0.03);
}
.results-content tr:last-child td { border-bottom: none; }
.results-content a { color: var(--accent); text-decoration: none; }
.results-content a:hover { text-decoration: underline; }
.results-content a.dbi-link {
color: var(--text-tertiary); font-size: 0.75rem;
margin-left: 0.25rem;
}
.results-content a.dbi-link:hover { color: var(--text-secondary); text-decoration: none; }
.results-content strong { color: var(--text-primary); font-weight: 400; }
.results-content hr {
border: none; height: 1px; margin: var(--space-4) 0;
background: linear-gradient(90deg, transparent, var(--glass-border), transparent);
}
.results-content p {
font-family: var(--sans); font-size: var(--text-sm); font-weight: 300;
color: var(--text-secondary); margin-bottom: var(--space-2); line-height: 1.6;
}
.results-content ul, .results-content ol {
margin-left: 20px; margin-bottom: var(--space-2);
font-size: var(--text-sm); color: var(--text-secondary);
}
.results-content li { margin-bottom: 4px; font-weight: 300; }
/* ═══ INTEL PANEL (two-column on desktop) ═══ */
.results-layout {
display: grid; grid-template-columns: 1fr;
gap: var(--space-6); padding-bottom: var(--space-8);
}
@media (min-width: 900px) {
.results-layout { grid-template-columns: 3fr 2fr; }
.intel-sidebar { position: sticky; top: 72px; align-self: start; }
}
.intel-panel {
background: var(--obsidian-mid);
border: 1px solid var(--glass-border);
border-radius: var(--radius-md);
padding: var(--space-5);
}
.intel-section {
padding: var(--space-4) 0;
border-bottom: 1px solid rgba(255, 255, 255, 0.03);
}
.intel-section:first-child { padding-top: 0; }
.intel-section:last-of-type { border-bottom: none; }
.intel-heading {
font-family: var(--mono); font-size: var(--text-xs); font-weight: 400;
color: var(--text-secondary); text-transform: uppercase;
letter-spacing: 0.08em; margin-bottom: var(--space-3);
display: flex; align-items: center; gap: 6px;
}
.intel-icon { font-size: 14px; }
.intel-value {
font-family: var(--sans); font-size: var(--text-sm); font-weight: 300;
color: var(--text-secondary);
}
.intel-link {
font-family: var(--mono); font-size: var(--text-xs); font-weight: 300;
color: var(--accent); text-decoration: none;
display: inline-block; margin-top: var(--space-2);
padding-bottom: 1px; border-bottom: 1px solid transparent;
transition: border-color 0.3s;
}
.intel-link:hover { border-bottom-color: var(--accent); }
.intel-muted { color: var(--text-tertiary); font-size: var(--text-sm); font-weight: 300; }
/* Routing progress */
.routing-item { margin-bottom: var(--space-3); }
.routing-item:last-child { margin-bottom: 0; }
.routing-header { display: flex; align-items: center; gap: var(--space-2); margin-bottom: 4px; }
.routing-permit {
font-family: var(--mono); font-size: var(--text-xs); color: var(--text-secondary);
}
.routing-status {
font-family: var(--mono); font-size: 10px; font-weight: 400;
text-transform: uppercase; letter-spacing: 0.05em;
padding: 1px 6px; border-radius: 3px;
}
.routing-status.status-issued { background: rgba(52,211,153,0.12); color: var(--signal-green); }
.routing-status.status-filed { background: rgba(251,191,36,0.12); color: var(--signal-amber); }
.routing-status.status-approved { background: rgba(96,165,250,0.12); color: var(--signal-blue); }
.routing-status.status-plancheck { background: rgba(251,191,36,0.12); color: var(--signal-amber); }
.routing-desc {
font-family: var(--sans); font-size: var(--text-xs); font-weight: 300;
color: var(--text-tertiary); margin-bottom: 6px;
display: -webkit-box; -webkit-line-clamp: 2;
-webkit-box-orient: vertical; overflow: hidden;
}
.progress-bar-container {
height: 2px; background: var(--glass); border-radius: 1px;
overflow: hidden; margin-bottom: 4px;
}
.progress-bar {
height: 100%; border-radius: 1px;
background: linear-gradient(90deg, var(--accent), rgba(94, 234, 212, 0.4));
transition: width 1.6s cubic-bezier(0.16, 1, 0.3, 1);
}
.routing-meta {
font-family: var(--mono); font-size: 11px; color: var(--text-tertiary);
}
/* Entities */
.entity-row {
display: flex; align-items: center; gap: var(--space-2);
margin-bottom: 6px; font-size: var(--text-sm);
}
.entity-role {
font-family: var(--mono); font-size: 10px; font-weight: 400;
color: var(--text-tertiary); text-transform: uppercase;
min-width: 72px; letter-spacing: 0.04em;
}
.entity-name { color: var(--text-primary); font-weight: 400; }
.entity-count { color: var(--text-tertiary); font-size: var(--text-xs); }
/* Enforcement */
.enforcement-stats { display: flex; flex-direction: column; gap: 6px; }
.enforcement-stat { display: flex; align-items: center; gap: var(--space-2); font-size: var(--text-sm); }
.stat-dot {
width: 6px; height: 6px; border-radius: var(--radius-full); flex-shrink: 0;
}
.stat-dot.dot-amber { background: var(--signal-amber); }
.stat-dot.dot-red { background: var(--signal-red); }
/* Intel CTA */
.intel-cta { padding-top: var(--space-4); text-align: center; }
.intel-cta-btn {
font-family: var(--mono); font-size: var(--text-xs); font-weight: 400;
color: var(--text-tertiary); text-decoration: none;
padding: 8px 16px; border: 1px solid var(--glass-border); border-radius: var(--radius-sm);
transition: color 0.3s, border-color 0.3s;
display: inline-block;
}
.intel-cta-btn:hover { color: var(--accent); border-color: var(--accent-ring); }
.intel-cta-sub {
font-family: var(--mono); font-size: 11px; color: var(--text-tertiary);
margin-top: var(--space-2);
}
/* Intel loading */
.intel-loading {
text-align: center; padding: var(--space-6);
color: var(--text-tertiary); font-size: var(--text-sm);
}
.intel-spinner {
width: 20px; height: 20px;
border: 1.5px solid rgba(255,255,255,0.08);
border-top-color: var(--accent);
border-radius: 50%;
animation: spin 0.8s linear infinite;
margin: 0 auto var(--space-2);
}
@keyframes spin { to { transform: rotate(360deg); } }
.intel-empty-state { padding: var(--space-4); }
/* ═══ VIOLATION BANNER ═══ */
.violation-banner {
background: rgba(248,113,113,0.06);
border: 1px solid rgba(248,113,113,0.20);
border-radius: var(--radius-md);
padding: var(--space-4) var(--space-5);
margin-bottom: var(--space-5);
display: flex; align-items: center; gap: var(--space-3);
}
.violation-banner__icon { font-size: 14px; }
.violation-banner strong {
font-family: var(--mono); font-size: var(--text-sm); font-weight: 400;
color: var(--signal-red);
}
.violation-banner span {
font-family: var(--sans); font-size: var(--text-sm); font-weight: 300;
color: var(--text-secondary);
}
.violation-banner a { color: var(--accent); text-decoration: none; }
.violation-banner a:hover { text-decoration: underline; }
/* ═══ NO RESULTS ═══ */
.no-results {
text-align: center; padding: var(--space-10) var(--space-5);
}
.no-results h3 {
font-family: var(--mono); font-size: var(--text-lg); font-weight: 300;
color: var(--text-primary); margin-bottom: var(--space-2);
}
.no-results p {
font-family: var(--sans); font-size: var(--text-sm); font-weight: 300;
color: var(--text-secondary);
}
.guidance-card {
background: var(--obsidian-mid);
border: 1px solid var(--glass-border);
border-radius: var(--radius-md);
padding: var(--space-6);
margin-bottom: var(--space-4);
}
.guidance-card h3 {
font-family: var(--mono); font-size: var(--text-sm); font-weight: 400;
color: var(--text-secondary); margin-bottom: var(--space-4);
}
.guidance-grid {
display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
gap: var(--space-4);
}
.guidance-label {
font-family: var(--mono); font-size: 10px; font-weight: 400;
color: var(--text-tertiary); text-transform: uppercase;
letter-spacing: 0.08em; margin-bottom: var(--space-2);
}
.guidance-link {
font-family: var(--mono); font-size: var(--text-sm); font-weight: 300;
color: var(--accent); text-decoration: none;
display: block; margin-bottom: 4px;
padding-bottom: 1px; border-bottom: 1px solid transparent;
transition: border-color 0.3s;
}
.guidance-link:hover { border-bottom-color: var(--accent); }
.guidance-note {
margin-top: var(--space-5); padding-top: var(--space-4);
border-top: 1px solid rgba(255, 255, 255, 0.03);
}
.guidance-note p {
font-family: var(--sans); font-size: var(--text-sm); font-weight: 300;
color: var(--text-secondary);
}
.guidance-cta {
margin-top: var(--space-4); text-align: center;
}
/* ═══ FRESHNESS ═══ */
.freshness {
font-family: var(--mono); font-size: 11px; color: var(--text-ghost);
text-align: center; padding: var(--space-10) 0;
display: flex; align-items: center; justify-content: center; gap: var(--space-2);
}
.freshness-dot {
width: 6px; height: 6px; border-radius: var(--radius-full);
background: var(--signal-green); display: inline-block;
}
/* ═══ MOBILE INTEL TOGGLE ═══ */
.mobile-intel-toggle {
display: none; width: 100%;
font-family: var(--mono); font-size: var(--text-xs); font-weight: 400;
color: var(--text-secondary); background: var(--glass);
border: 1px solid var(--glass-border); border-radius: var(--radius-sm);
padding: var(--space-3) var(--space-4); cursor: pointer;
text-align: left; margin-bottom: var(--space-4);
transition: border-color 0.2s, color 0.2s;
}
.mobile-intel-toggle:hover { border-color: var(--glass-hover); color: var(--text-primary); }
.mobile-intel-toggle::after {
content: '\25BC'; float: right; font-size: 9px; transition: transform 0.2s;
}
.mobile-intel-toggle.open::after { transform: rotate(180deg); }
@media (max-width: 899px) {
.mobile-intel-toggle { display: block; }
.intel-sidebar { display: none; }
.mobile-intel { display: none; }
.mobile-intel.open { display: block; }
}
/* ═══ REVEAL ═══ */
.reveal {
opacity: 0; transform: translateY(24px);
transition: opacity 0.9s cubic-bezier(0.16, 1, 0.3, 1),
transform 0.9s cubic-bezier(0.16, 1, 0.3, 1);
}
.reveal.visible { opacity: 1; transform: translateY(0); }
.reveal-delay-1 { transition-delay: 0.1s; }
.reveal-delay-2 { transition-delay: 0.2s; }
.reveal-delay-3 { transition-delay: 0.3s; }
/* ═══ RESPONSIVE ═══ */
@media (max-width: 768px) {
.obs-container { padding: 0 var(--space-4); }
.nav-float__links .nav-float__link { display: none; }
.entity-row { flex-wrap: wrap; }
.entity-role { min-width: auto; }
}
@media (max-width: 480px) {
.obs-container { padding: 0 var(--space-4); }
}
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
}
.reveal { opacity: 1; transform: none; }
}
/* ═══ TRIAGE INTELLIGENCE SIGNALS ═══ */
.triage-panel {
margin-bottom: var(--space-5);
}
.triage-heading {
font-family: var(--mono); font-size: var(--text-xs); font-weight: 400;
color: var(--text-tertiary); text-transform: uppercase; letter-spacing: 0.08em;
margin-bottom: var(--space-3);
display: flex; align-items: center; gap: 6px;
}
.triage-cards {
display: flex; flex-direction: column; gap: var(--space-3);
}
.triage-card {
background: var(--obsidian-mid);
border: 1px solid var(--glass-border);
border-radius: var(--radius-md);
padding: var(--space-4) var(--space-5);
transition: border-color 0.3s;
}
.triage-card:hover { border-color: var(--glass-hover); }
.triage-card__header {
display: flex; align-items: center; gap: var(--space-3);
flex-wrap: wrap;
}
.triage-card__permit {
font-family: var(--mono); font-size: var(--text-xs); font-weight: 400;
color: var(--text-secondary);
}
.triage-card__status {
font-family: var(--mono); font-size: 10px; font-weight: 400;
color: var(--text-tertiary); text-transform: uppercase; letter-spacing: 0.04em;
}
.triage-card__desc {
font-family: var(--sans); font-size: var(--text-xs); font-weight: 300;
color: var(--text-tertiary); margin-top: 4px;
display: -webkit-box; -webkit-line-clamp: 1;
-webkit-box-orient: vertical; overflow: hidden;
}
.triage-card__signals {
display: flex; align-items: center; gap: var(--space-3);
flex-wrap: wrap; margin-top: var(--space-3);
}
/* Station timing badge */
.station-badge {
display: inline-flex; align-items: center; gap: 5px;
font-family: var(--mono); font-size: var(--text-xs); font-weight: 400;
padding: 3px 8px; border-radius: var(--radius-full);
border: 1px solid;
}
.station-badge--green {
color: var(--dot-green);
background: rgba(34, 197, 94, 0.08);
border-color: rgba(34, 197, 94, 0.20);
}
.station-badge--amber {
color: var(--dot-amber);
background: rgba(245, 158, 11, 0.08);
border-color: rgba(245, 158, 11, 0.20);
}
.station-badge--red {
color: var(--dot-red);
background: rgba(239, 68, 68, 0.08);
border-color: rgba(239, 68, 68, 0.20);
}
.station-badge__dot {
width: 6px; height: 6px; border-radius: var(--radius-full); flex-shrink: 0;
}
.station-badge--green .station-badge__dot { background: var(--dot-green); }
.station-badge--amber .station-badge__dot { background: var(--dot-amber); }
.station-badge--red .station-badge__dot { background: var(--dot-red); }
/* Stuck indicator */
.stuck-indicator {
font-family: var(--mono); font-size: var(--text-xs); font-weight: 400;
color: var(--dot-red);
display: inline-flex; align-items: center; gap: 4px;
}
/* Reviewer name */
.triage-reviewer {
font-family: var(--mono); font-size: var(--text-xs); font-weight: 300;
color: var(--text-secondary);
}
</style>
<meta name="csrf-token" content="{{ csrf_token }}">
<script nonce="{{ csp_nonce }}" src="https://unpkg.com/htmx.org@2.0.4" crossorigin="anonymous"></script>
<script nonce="{{ csp_nonce }}">
document.addEventListener('htmx:configRequest', function(e) {
var token = document.querySelector('meta[name="csrf-token"]');
if (token) e.detail.headers['X-CSRFToken'] = token.getAttribute('content');
});
</script>
</head>
<body>
<!-- NAV -->
<nav class="nav-float">
<a href="/" class="nav-float__wordmark">sfpermits.ai</a>
<div class="nav-float__links">
<a href="/methodology" class="nav-float__link">Methodology</a>
<a href="/auth/login" class="nav-float__cta">Sign in</a>
</div>
</nav>
<div class="page-body">
<div class="obs-container">
<!-- SEARCH -->
<form action="/search" method="GET" class="search-header reveal">
<div class="search-bar">
<input type="text" name="q" class="search-input" value="{{ query }}" placeholder="Address, permit #, or block/lot..." autocomplete="off">
<svg class="search-icon" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
</div>
</form>
{% if violation_context %}
<div class="violation-banner reveal reveal-delay-1">
<span class="violation-banner__icon">⚠️</span>
<div>
<strong>Violation Lookup Mode</strong>
<span>Showing enforcement data first. <a href="/auth/login">Sign up free</a> for full history.</span>
</div>
</div>
{% endif %}
{% if error %}
<!-- ERROR STATE -->
<div class="no-results reveal reveal-delay-1">
<h3>Something went wrong</h3>
<p>{{ error }}</p>
</div>
{% elif no_results %}
<!-- NO RESULTS -->
<div class="no-results reveal reveal-delay-1">
<h3>No permits found</h3>
<p>No permit records matched "{{ query }}"</p>
</div>
{% if empty_guidance and empty_guidance.did_you_mean %}
<div class="guidance-card reveal reveal-delay-2" style="margin-bottom: var(--space-4);">
<p style="font-family: var(--sans); font-size: var(--text-sm); font-weight: 300; color: var(--accent);">
{{ empty_guidance.did_you_mean }}
</p>
</div>
{% endif %}
<div class="guidance-card reveal reveal-delay-2">
<h3>{% if nl_query %}How to use sfpermits.ai{% else %}Try searching by{% endif %}</h3>
{% if empty_guidance and empty_guidance.suggestions %}
<div class="guidance-grid" style="margin-bottom: var(--space-4);">
{% for s in empty_guidance.suggestions %}
<div>
<div class="guidance-label">Suggested search</div>
<a href="{{ s.url }}" class="guidance-link">{{ s.label }}</a>
{% if s.hint %}<p style="font-family: var(--sans); font-size: var(--text-xs); font-weight: 300; color: var(--text-secondary); margin-top: 4px;">{{ s.hint }}</p>{% endif %}
</div>
{% endfor %}
</div>
{% endif %}
<div class="guidance-grid">
<div>
<div class="guidance-label">Street Address</div>
<a href="/search?q=614+6th+Ave" class="guidance-link">614 6th Ave</a>
<a href="/search?q=75+Robin+Hood+Dr" class="guidance-link">75 Robin Hood Dr</a>
</div>
<div>
<div class="guidance-label">Permit Number</div>
<a href="/search?q=202401015555" class="guidance-link">202401015555</a>
</div>
<div>
<div class="guidance-label">Block / Lot</div>
<a href="/search?q=3512/001" class="guidance-link">3512/001</a>
</div>
</div>
{% if nl_query %}
<div class="guidance-note">
<p>sfpermits.ai searches SF building permit records. Sign up free to access AI-powered project analysis.</p>
</div>
{% endif %}
{% if empty_guidance and empty_guidance.show_demo_link %}
<div class="guidance-note">
<p>Not sure what to search? <a href="/demo" style="color: var(--accent); text-decoration: none;">Try our interactive demo</a> to see what sfpermits.ai can find.</p>
</div>
{% endif %}
<div class="guidance-cta">
<a href="/auth/login" class="intel-cta-btn">Sign up free for full access</a>
</div>
</div>
{% else %}
<!-- RESULTS -->
<!-- AI Suggestion -->
<a href="/search?q=what+permits+do+I+need+at+{{ query | urlencode }}" class="ai-suggest reveal reveal-delay-1">
<span class="ai-suggest__icon">✨</span>
<span class="ai-suggest__text">Planning a project at <em>{{ query }}</em>? Ask AI what permits you'll need</span>
<span class="ai-suggest__arrow">→</span>
</a>
{% if triage_signals %}
<!-- TRIAGE INTELLIGENCE SIGNALS -->
<div class="triage-panel reveal reveal-delay-1">
<div class="triage-heading">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>
Triage signals
</div>
<div class="triage-cards">
{% for sig in triage_signals %}
<div class="triage-card">
<div class="triage-card__header">
<span class="triage-card__permit">{{ sig.permit_number }}</span>
{% if sig.status %}
<span class="triage-card__status">{{ sig.status }}</span>
{% endif %}
{% if sig.is_stuck %}
<span class="stuck-indicator" aria-label="Permit may be stuck">
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>
Stuck
</span>
{% endif %}
</div>
{% if sig.description %}
<div class="triage-card__desc">{{ sig.description }}</div>
{% endif %}
<div class="triage-card__signals">
{% if sig.current_station and sig.days_at_station is not none %}
<span class="station-badge station-badge--{{ sig.threshold_class or 'green' }}" title="{{ sig.days_at_station }} days at {{ sig.current_station }}{% if sig.station_median %} (median: {{ sig.station_median | int }}d){% endif %}">
<span class="station-badge__dot"></span>
{{ sig.days_at_station }}d at {{ sig.current_station }}
</span>
{% elif sig.current_station %}
<span class="triage-reviewer">At {{ sig.current_station }}</span>
{% endif %}
{% if sig.reviewer %}
<span class="triage-reviewer">Reviewer: {{ sig.reviewer }}</span>
{% endif %}
</div>
</div>
{% endfor %}
</div>
</div>
{% endif %}
{% if block and lot %}
<!-- Two-column layout with intel sidebar -->
<div class="results-layout">
<div>
<div class="results-content reveal reveal-delay-2">
{{ result_html | safe }}
</div>
<!-- Mobile: expandable intel -->
<button class="mobile-intel-toggle" onclick="this.classList.toggle('open'); document.getElementById('mobile-intel').classList.toggle('open');">
Property intelligence
</button>
<div id="mobile-intel" class="mobile-intel">
<div class="intel-loading"
hx-post="/lookup/intel-preview"
hx-trigger="intersect once"
hx-vals='{"block": "{{ block }}", "lot": "{{ lot }}"}'
hx-swap="outerHTML">
<div class="intel-spinner"></div>
<p>Loading intelligence...</p>
</div>
</div>
</div>
<!-- Desktop: sticky intel sidebar -->
<div class="intel-sidebar">
<div class="intel-loading"
hx-post="/lookup/intel-preview"
hx-trigger="load"
hx-vals='{"block": "{{ block }}", "lot": "{{ lot }}"}'
hx-swap="outerHTML">
<div class="intel-spinner"></div>
<p>Loading property intelligence...</p>
</div>
</div>
</div>
{% else %}
<!-- Single column (no intel) -->
<div class="results-content reveal reveal-delay-2">
{{ result_html | safe }}
</div>
{% endif %}
<!-- FRESHNESS -->
<div class="freshness reveal reveal-delay-3">
<span class="freshness-dot"></span>
Updated nightly from SF open data
</div>
{% endif %}
</div>
</div>
<script nonce="{{ csp_nonce }}" src="/static/admin-feedback.js" defer></script>
<script nonce="{{ csp_nonce }}" src="/static/admin-tour.js" defer></script>
<script nonce="{{ csp_nonce }}" src="/static/activity-tracker.js" defer></script>
<script nonce="{{ csp_nonce }}">
// Scroll reveal
var observer = new IntersectionObserver(function(entries) {
entries.forEach(function(entry) {
if (entry.isIntersecting) {
entry.target.classList.add('visible');
observer.unobserve(entry.target);
}
});
}, { threshold: 0.15, rootMargin: '0px 0px -40px 0px' });
document.querySelectorAll('.reveal').forEach(function(el) { observer.observe(el); });
// Mobile intel toggle — hide desktop on mobile
(function() {
var mq = window.matchMedia('(min-width: 900px)');
function handleResize(e) {
var sidebar = document.querySelector('.intel-sidebar');
var toggle = document.querySelector('.mobile-intel-toggle');
if (!sidebar) return;
sidebar.style.display = e.matches ? '' : 'none';
if (toggle) toggle.style.display = e.matches ? 'none' : '';
}
mq.addEventListener('change', handleResize);
handleResize(mq);
})();
</script>
</body>
</html>