<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Permit Analysis — sfpermits.ai</title>
<meta name="description" content="AI-powered permit analysis for {{ session.project_description[:100] }}">
<meta property="og:title" content="Project Analysis: {{ (session.project_description or 'SF Permit Analysis')[:60] }}">
<meta property="og:description" content="Permits needed, timeline estimate, fee breakdown. Powered by sfpermits.ai">
<meta property="og:type" content="article">
<meta property="og:url" content="https://sfpermits.ai/analysis/{{ session.id }}">
<meta property="og:image" content="https://sfpermits.ai/static/og-card.png">
<meta property="og:site_name" content="sfpermits.ai">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="Project Analysis: {{ (session.project_description or 'SF Permit Analysis')[:60] }}">
<meta name="twitter:description" content="Permits needed, timeline estimate, fee breakdown. Powered by sfpermits.ai">
<meta name="twitter:image" content="https://sfpermits.ai/static/og-card.png">
<meta name="csrf-token" content="{{ csrf_token }}">
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
<style nonce="{{ csp_nonce }}">
.shared-banner {
background: #eff6ff;
border: 1px solid #bfdbfe;
border-radius: 8px;
padding: 12px 16px;
margin-bottom: 24px;
font-size: 0.9rem;
color: #1e40af;
}
.shared-meta {
background: #f9fafb;
border: 1px solid #e5e7eb;
border-radius: 8px;
padding: 16px 20px;
margin-bottom: 24px;
}
.shared-meta h2 {
font-size: 1.1rem;
margin: 0 0 8px 0;
color: #111827;
}
.shared-meta .meta-row {
display: flex;
gap: 24px;
flex-wrap: wrap;
font-size: 0.85rem;
color: #6b7280;
}
.shared-meta .meta-row span strong {
color: #374151;
}
.shared-cta {
background: linear-gradient(135deg, #1e40af 0%, #2563eb 100%);
border-radius: 12px;
padding: 28px 32px;
margin: 32px 0;
text-align: center;
color: white;
}
.shared-cta h3 {
margin: 0 0 8px 0;
font-size: 1.25rem;
}
.shared-cta p {
margin: 0 0 20px 0;
opacity: 0.9;
font-size: 0.95rem;
}
.shared-cta .cta-btn {
display: inline-block;
background: white;
color: #1e40af;
padding: 12px 28px;
border-radius: 8px;
text-decoration: none;
font-weight: 700;
font-size: 1rem;
transition: transform 0.1s;
}
.shared-cta .cta-btn:hover {
transform: translateY(-1px);
}
.shared-role-text {
font-size: 0.85rem;
opacity: 0.85;
margin-top: 12px;
}
.tab-panel { display: none; }
.tab-panel.active { display: block; }
.results-tabs { margin-bottom: 16px; }
.tab { cursor: pointer; }
/* === Sprint 58C: Methodology Cards (shared page — no toggle) === */
.methodology-card { margin-top: 8px; }
.methodology-card summary { cursor: pointer; color: #6b7280; font-size: 0.85em; font-weight: 500; user-select: none; list-style: none; display: flex; align-items: center; gap: 4px; }
.methodology-card summary:hover { color: #374151; }
.methodology-card summary::before { content: "ⓘ"; }
.methodology-body { background: #f8f9fa; border-left: 3px solid #2563eb; padding: 12px 16px; margin-top: 6px; font-size: 0.9em; line-height: 1.6; border-radius: 0 4px 4px 0; }
.methodology-model { font-weight: 600; color: #1e40af; margin-bottom: 6px; font-size: 0.95em; }
.formula-steps { margin: 8px 0; }
.formula-steps .step { padding: 2px 0; color: #374151; font-family: monospace; font-size: 0.85em; }
.station-breakdown { width: 100%; margin: 8px 0; font-size: 0.9em; border-collapse: collapse; }
.station-breakdown th { text-align: left; color: #6b7280; padding: 4px 8px; border-bottom: 1px solid #e5e7eb; }
.station-breakdown td { padding: 4px 8px; border-bottom: 1px solid #f3f4f6; }
.triggers-matched { margin-top: 8px; }
.triggers-matched div { font-size: 0.85em; color: #374151; padding: 2px 0; }
.correction-cats { margin-top: 8px; }
.correction-cats div { font-size: 0.85em; color: #374151; padding: 2px 0; }
.coverage-gaps { color: #b45309; font-size: 0.85em; font-style: italic; margin-top: 8px; }
.fallback-note { color: #6b7280; font-style: italic; font-size: 0.85em; margin-top: 6px; }
.revision-context { margin-top: 8px; padding: 8px; background: rgba(180, 83, 9, 0.06); border-radius: 4px; font-size: 0.85em; }
.methodology-footer { color: #9ca3af; font-size: 0.8em; margin-top: 10px; border-top: 1px solid #e5e7eb; padding-top: 6px; }
@media print {
.methodology-card .methodology-body { display: block !important; }
.methodology-card:not([open]) .methodology-body { display: block !important; }
}
</style>
</head>
<body>
<div class="container">
<header style="display:flex; align-items:center; justify-content:space-between; padding:16px 0; border-bottom:2px solid #2563eb; margin-bottom:24px;">
<a href="/" style="text-decoration:none;">
<span style="font-size:1.4rem; font-weight:800; color:#2563eb; letter-spacing:-0.5px;">sfpermits.ai</span>
</a>
<a href="/auth/login?referral_source=shared_link&analysis_id={{ session_id }}" class="btn btn-primary" style="font-size:0.9rem;">Sign up free</a>
</header>
<div class="shared-banner">
This analysis was shared with you from sfpermits.ai — AI-powered permit guidance for San Francisco.
</div>
<div class="shared-meta">
<h2>{{ session.project_description[:120] }}{% if session.project_description|length > 120 %}...{% endif %}</h2>
<div class="meta-row">
{% if session.address %}
<span><strong>Address:</strong> {{ session.address }}</span>
{% endif %}
{% if session.neighborhood %}
<span><strong>Neighborhood:</strong> {{ session.neighborhood }}</span>
{% endif %}
{% if session.estimated_cost %}
<span><strong>Est. Cost:</strong> ${{ "{:,.0f}".format(session.estimated_cost) }}</span>
{% endif %}
<span><strong>Analyzed:</strong> {{ session.created_at.strftime('%B %d, %Y') if session.created_at else 'recently' }}</span>
</div>
</div>
{# Sprint 61B: join banner #}{% if project_id and g.user and not is_project_member %}<div id="join-banner" style="background:var(--surface);border:1px solid var(--border);border-radius:10px;padding:12px 18px;margin-bottom:16px;display:flex;align-items:center;justify-content:space-between;"><span style="font-size:0.9rem;color:var(--text);">This analysis is linked to a project.</span><button id="join-btn" onclick="joinProject()" style="padding:6px 16px;background:var(--accent);color:#fff;border:none;border-radius:6px;font-size:0.85rem;font-weight:600;cursor:pointer;">Join Project</button></div>{% elif project_id and g.user and is_project_member %}<div style="background:var(--surface);border:1px solid var(--border);border-radius:10px;padding:10px 18px;margin-bottom:16px;font-size:0.85rem;color:var(--text-muted);">Part of a project — <a href="/project/{{ project_id }}" style="color:var(--accent);text-decoration:none;">View project</a></div>{% endif %}
<!-- Analysis results tabs (reuse results.html tab logic) -->
{% set results = session.results %}
{% set TAB_META = {
"predict": {"label": "Permits", "panel": "panel-predict"},
"fees": {"label": "Fees", "panel": "panel-fees"},
"timeline": {"label": "Timeline", "panel": "panel-timeline"},
"docs": {"label": "Documents", "panel": "panel-docs"},
"risk": {"label": "Revision Risk", "panel": "panel-risk"},
"team": {"label": "Your Team", "panel": "panel-team"},
} %}
{% set order = ["predict", "timeline", "fees", "docs", "risk"] %}
<div class="results-tabs">
{% for key in order %}
{% if results.get(key) %}
<button class="tab{% if loop.first %} active{% endif %}" data-panel="{{ TAB_META[key].panel }}">{{ TAB_META[key].label }}</button>
{% endif %}
{% endfor %}
</div>
{# Map tab keys to anchor IDs for deep-linking from email #}
{% set SHARED_ANCHOR_MAP = {
"predict": "method-permits",
"fees": "method-fees",
"timeline": "method-timeline",
"docs": "method-documents",
"risk": "method-risk",
} %}
{% for key in order %}
{% if results.get(key) %}
<div id="{{ TAB_META[key].panel }}" class="tab-panel{% if loop.first %} active{% endif %}">
<div class="result-card">
{{ results[key] | safe }}
</div>
{# === Sprint 58C: Methodology Card (no toggle on shared page) === #}
{% set m = (methodology.get(key) if methodology is defined and methodology else None) %}
{% if m %}
<details class="methodology-card" id="{{ SHARED_ANCHOR_MAP.get(key, 'method-' ~ key) }}">
<summary>How we calculated this</summary>
<div class="methodology-body">
{# model name #}
{% set model_name = (m.methodology.model if m.methodology is defined and m.methodology is mapping else None) or m.get('tool', '') %}
{% if model_name %}
<p class="methodology-model">{{ model_name }}</p>
{% endif %}
{# formula steps #}
{% if m.formula_steps %}
<div class="formula-steps">
{% for step in m.formula_steps %}
<div class="step">{{ step }}</div>
{% endfor %}
</div>
{% endif %}
{# station breakdown — timeline only #}
{% if m.stations %}
<table class="station-breakdown">
<tr><th>Station</th><th>Median</th><th>Reviews</th><th>Trend</th></tr>
{% for s in m.stations %}
<tr>
<td>{{ s.name if s.name is defined else s.get('station_code', '—') }}</td>
<td>~{{ (s.p50_days | round(0) | int) if s.p50_days is defined else '—' }} days</td>
<td>{{ s.sample_count if s.sample_count is defined else s.get('sample_count', '—') }}</td>
<td>{{ s.trend if s.trend is defined else s.get('trend', '—') }}</td>
</tr>
{% endfor %}
</table>
{% endif %}
{# triggers matched — predict_permits only #}
{% if m.triggers_matched %}
<div class="triggers-matched">
<strong>Project types identified:</strong>
{% for t in m.triggers_matched %}
<div>{{ t.trigger if t.trigger is defined else t.get('trigger', '—') }}
{% if t.form is defined and t.form %} → {{ t.form }}{% endif %}
{% if t.path is defined and t.path %} ({{ t.path }}){% endif %}
</div>
{% endfor %}
</div>
{% endif %}
{# correction categories — revision_risk only #}
{% if m.correction_categories %}
<div class="correction-cats">
<strong>Common correction types:</strong>
{% for c in m.correction_categories %}
<div>{{ c.category if c.category is defined else c.get('category', '—') }}
{% if c.rate is defined and c.rate %}: {{ (c.rate | float * 100) | round(0) | int }}%{% endif %}
{% if c.detail is defined and c.detail %} — {{ c.detail }}{% endif %}
</div>
{% endfor %}
</div>
{% endif %}
{# revision context — estimate_fees only #}
{% if m.revision_context and m.revision_context.revision_rate is defined %}
<div class="revision-context">
<strong>Budget risk:</strong>
{{ (m.revision_context.revision_rate | float * 100) | round(0) | int }}% of similar projects see cost revisions.
{% if m.revision_context.budget_ceiling is defined and m.revision_context.budget_ceiling %}
Budget ceiling: ${{ "{:,.0f}".format(m.revision_context.budget_ceiling | round(0) | int) }}.
{% endif %}
</div>
{% endif %}
{# coverage gaps (only when non-empty) #}
{% if m.coverage_gaps %}
<div class="coverage-gaps">
<strong>Note:</strong>
{% for gap in m.coverage_gaps %}
<span>{{ gap }}</span>{% if not loop.last %} · {% endif %}
{% endfor %}
</div>
{% endif %}
{# fallback note — timeline only #}
{% if m.fallback_note %}
<div class="fallback-note">{{ m.fallback_note }}</div>
{% endif %}
{# footer #}
<div class="methodology-footer">
{% if m.methodology is defined and m.methodology is mapping and m.methodology.data_source is defined %}
Source: {{ m.methodology.data_source }}
{% elif m.data_sources %}
Source: {{ m.data_sources | join(', ') }}
{% endif %}
{% if m.sample_size %} · {{ "{:,}".format(m.sample_size) }} records{% endif %}
{% if m.data_freshness %} · Last updated: {{ m.data_freshness }}{% endif %}
{% if m.confidence %} · Confidence: {{ m.confidence }}{% endif %}
</div>
</div>
</details>
{% endif %}
</div>
{% endif %}
{% endfor %}
<!-- CTA -->
<div class="shared-cta">
<h3>Run your own permit analysis</h3>
{% if referrer_role %}
<p>{{ referrer_role }} used sfpermits.ai to research this project. See what it can do for yours.</p>
{% else %}
<p>Get instant permit guidance, fee estimates, and timeline predictions for any San Francisco project.</p>
{% endif %}
<a href="/auth/login?referral_source=shared_link&analysis_id={{ session_id }}" class="cta-btn">
Try sfpermits.ai free
</a>
<div class="shared-role-text">No invite code needed — shared analysis grants immediate access.</div>
</div>
</div>
<script nonce="{{ csp_nonce }}">
{% if project_id %}
function joinProject() {
const btn = document.getElementById('join-btn');
if (btn) btn.disabled = true;
fetch('/project/{{ project_id }}/join', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': document.querySelector('meta[name="csrf-token"]')?.content || ''
}
})
.then(r => r.json())
.then(d => {
const banner = document.getElementById('join-banner');
if (d.ok && banner) {
banner.innerHTML = '<span style="font-size:0.85rem;color:var(--text-muted);">Joined! — <a href="/project/{{ project_id }}" style="color:var(--accent);text-decoration:none;">View project</a></span>';
} else if (btn) { btn.disabled = false; }
})
.catch(() => { if (btn) btn.disabled = false; });
}
{% endif %}
// Tab switching
document.querySelectorAll('.tab').forEach(btn => {
btn.addEventListener('click', () => {
const panel = btn.dataset.panel;
document.querySelectorAll('.tab').forEach(b => b.classList.remove('active'));
document.querySelectorAll('.tab-panel').forEach(p => p.classList.remove('active'));
btn.classList.add('active');
document.getElementById(panel)?.classList.add('active');
});
});
</script>
</body>
</html>