nordabiz/templates/admin/seo_detail.html
Maciej Pienczyn 04bbf5db18
Some checks are pending
NordaBiz Tests / Unit & Integration Tests (push) Waiting to run
NordaBiz Tests / E2E Tests (Playwright) (push) Blocked by required conditions
NordaBiz Tests / Smoke Tests (Production) (push) Blocked by required conditions
NordaBiz Tests / Send Failure Notification (push) Blocked by required conditions
fix: show OG image link on load failure + add no-referrer policy
When OG image fails to load (hotlink protection, CORS), shows
clickable URL instead of just "Obrazek niedostepny". Adds
referrerpolicy=no-referrer to bypass referer-based hotlink blocking.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 06:02:24 +01:00

1297 lines
67 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{% extends "base.html" %}
{% block title %}Audyt SEO: {{ company.name }} - Panel Admina{% endblock %}
{% block extra_css %}
<style>
.page-header {
display: flex; justify-content: space-between; align-items: flex-start;
margin-bottom: var(--spacing-xl); flex-wrap: wrap; gap: var(--spacing-md);
}
.page-header h1 { font-size: var(--font-size-2xl); margin-bottom: var(--spacing-xs); }
.page-header .subtitle { color: var(--text-secondary); font-size: var(--font-size-sm); }
.page-header .subtitle a { color: var(--primary); text-decoration: none; }
.back-link {
display: inline-flex; align-items: center; gap: var(--spacing-xs);
color: var(--text-secondary); text-decoration: none; font-size: var(--font-size-sm);
margin-bottom: var(--spacing-md);
}
.back-link:hover { color: var(--primary); }
/* Score cards */
.scores-grid {
display: grid; grid-template-columns: repeat(4, 1fr);
gap: var(--spacing-lg); margin-bottom: var(--spacing-2xl);
}
.score-card {
background: var(--surface); padding: var(--spacing-xl);
border-radius: var(--radius-lg); box-shadow: var(--shadow);
text-align: center;
}
.score-card .score-value {
font-size: 48px; font-weight: 700; line-height: 1;
margin-bottom: var(--spacing-xs);
}
.score-card .score-value.good { color: #16a34a; }
.score-card .score-value.medium { color: #d97706; }
.score-card .score-value.poor { color: #dc2626; }
.score-card .score-value.na { color: var(--text-muted); font-size: var(--font-size-2xl); }
.score-card .score-name { font-weight: 600; color: var(--text-primary); margin-bottom: 4px; }
.score-card .score-desc { font-size: var(--font-size-xs); color: var(--text-secondary); line-height: 1.4; }
.score-ring {
width: 100px; height: 100px; margin: 0 auto var(--spacing-md);
border-radius: 50%; display: flex; align-items: center; justify-content: center;
font-size: 32px; font-weight: 700;
}
.score-ring.good { background: #dcfce7; color: #16a34a; }
.score-ring.medium { background: #fef3c7; color: #d97706; }
.score-ring.poor { background: #fee2e2; color: #dc2626; }
.score-ring.na { background: var(--border); color: var(--text-muted); }
/* Sections */
.section {
background: var(--surface); padding: var(--spacing-xl);
border-radius: var(--radius-lg); box-shadow: var(--shadow);
margin-bottom: var(--spacing-xl);
}
.section h2 {
font-size: var(--font-size-lg); margin-bottom: var(--spacing-lg);
color: var(--text-primary); border-bottom: 2px solid var(--border);
padding-bottom: var(--spacing-sm);
}
/* Recommendations */
.reco-list { list-style: none; }
.reco-item {
padding: var(--spacing-md) var(--spacing-lg);
border-left: 4px solid var(--border);
margin-bottom: var(--spacing-sm);
border-radius: 0 var(--radius) var(--radius) 0;
background: var(--background);
font-size: var(--font-size-sm);
line-height: 1.5;
}
.reco-item.critical { border-left-color: #dc2626; background: #fef2f2; }
.reco-item.warning { border-left-color: #d97706; background: #fffbeb; }
.reco-item.info { border-left-color: #2563eb; background: #eff6ff; }
.reco-item.success { border-left-color: #16a34a; background: #f0fdf4; }
.reco-severity {
display: inline-block; padding: 1px 6px; border-radius: var(--radius-sm);
font-size: 10px; font-weight: 600; text-transform: uppercase;
margin-right: var(--spacing-sm); vertical-align: middle;
}
.reco-severity.critical { background: #dc2626; color: white; }
.reco-severity.warning { background: #d97706; color: white; }
.reco-severity.info { background: #2563eb; color: white; }
.reco-severity.success { background: #16a34a; color: white; }
/* Detail grid */
.detail-grid {
display: grid; grid-template-columns: 1fr 1fr;
gap: var(--spacing-md);
}
.detail-item {
display: flex; justify-content: space-between; align-items: center;
padding: var(--spacing-sm) var(--spacing-md);
background: var(--background); border-radius: var(--radius);
}
.detail-label { color: var(--text-secondary); font-size: var(--font-size-sm); }
.detail-value { font-weight: 600; font-size: var(--font-size-sm); }
.detail-value.yes { color: #16a34a; }
.detail-value.no { color: #dc2626; }
/* Two columns */
.two-col { display: grid; grid-template-columns: 1fr 1fr; gap: var(--spacing-xl); }
/* Audit info bar */
.audit-info {
display: flex; gap: var(--spacing-lg); align-items: center;
padding: var(--spacing-md) var(--spacing-lg);
background: var(--background); border-radius: var(--radius);
margin-bottom: var(--spacing-xl); font-size: var(--font-size-sm);
color: var(--text-secondary); flex-wrap: wrap;
}
.audit-info strong { color: var(--text-primary); }
@media (max-width: 768px) {
.scores-grid { grid-template-columns: repeat(2, 1fr); }
.detail-grid { grid-template-columns: 1fr; }
.two-col { grid-template-columns: 1fr; }
}
</style>
{% endblock %}
{% block content %}
<a href="{{ url_for('admin.admin_seo') }}" class="back-link">
<svg width="16" height="16" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/>
</svg>
Powrot do listy firm
</a>
<div class="page-header">
<div>
<h1>{{ company.name }}</h1>
<div class="subtitle">
{% if company.website %}
<a href="{{ company.website }}" target="_blank" rel="noopener">{{ company.website }}</a>
{% else %}
Brak strony WWW
{% endif %}
</div>
</div>
{% if analysis and current_user.can_access_admin_panel() %}
<button class="btn btn-primary btn-sm" id="auditBtn" onclick="runAudit()">Uruchom audyt ponownie</button>
{% endif %}
</div>
<!-- Stepper postepu audytu -->
<div id="auditProgress" style="display: none; margin-bottom: var(--spacing-lg); padding: var(--spacing-lg); background: var(--surface); border-radius: var(--radius); border: 1px solid var(--border);">
<strong style="display: block; margin-bottom: var(--spacing-md);">Audyt SEO — {{ company.name }}</strong>
<div id="auditSteps" style="display: flex; flex-direction: column; gap: 2px;">
<div class="audit-step" id="step-connect">
<span class="step-icon"></span>
<span>Laczenie z Google PageSpeed Insights...</span>
</div>
<div class="audit-step" id="step-pagespeed">
<span class="step-icon"></span>
<span>Analiza szybkosci i wydajnosci strony (PageSpeed)...</span>
</div>
<div class="audit-step" id="step-onpage">
<span class="step-icon"></span>
<span>Sprawdzanie tresci, tagow i struktury HTML...</span>
</div>
<div class="audit-step" id="step-technical">
<span class="step-icon"></span>
<span>Testy techniczne (SSL, robots.txt, sitemap)...</span>
</div>
<div class="audit-step" id="step-gsc">
<span class="step-icon"></span>
<span>Pobieranie danych z Google Search Console...</span>
</div>
<div class="audit-step" id="step-save">
<span class="step-icon"></span>
<span>Zapisywanie wynikow do bazy...</span>
</div>
<div class="audit-step" id="step-done">
<span class="step-icon"></span>
<span>Gotowe</span>
</div>
</div>
<div id="auditResult" style="margin-top: var(--spacing-md); display: none;"></div>
</div>
<style>
.audit-step { display: flex; align-items: center; gap: var(--spacing-sm); padding: 6px 0; font-size: var(--font-size-sm); color: var(--text-secondary); }
.audit-step.active { color: var(--text-primary); font-weight: 600; }
.audit-step.active .step-icon { color: var(--primary); }
.audit-step.done { color: var(--success); }
.audit-step.done .step-icon { color: var(--success); }
.audit-step.error { color: var(--danger); }
.audit-step.error .step-icon { color: var(--danger); }
.step-icon { font-size: 16px; width: 20px; text-align: center; }
@keyframes spin { to { transform: rotate(360deg); } }
</style>
<!-- Modal potwierdzenia -->
<div class="modal" id="confirmModal">
<div class="modal-content">
<div class="modal-header">
<div class="modal-icon warning">
<svg width="24" height="24" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/>
</svg>
</div>
<div class="modal-title">Uruchom audyt SEO</div>
</div>
<div class="modal-body">
Czy na pewno chcesz uruchomic audyt SEO dla {{ company.name }}? Jesli firma juz poprawila strone, wyniki zostana nadpisane nowymi danymi.
</div>
<div class="modal-footer">
<button class="btn btn-outline" onclick="closeModal()">Anuluj</button>
<button class="btn btn-primary" onclick="confirmAudit()">Uruchom audyt</button>
</div>
</div>
</div>
<!-- Modal informacyjny -->
<div class="modal" id="infoModal">
<div class="modal-content">
<div class="modal-header">
<div class="modal-icon info" id="infoModalIcon">
<svg width="24" height="24" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
</div>
<div class="modal-title" id="infoModalTitle">Informacja</div>
</div>
<div class="modal-body" id="infoModalBody"></div>
<div class="modal-footer">
<button class="btn btn-primary" onclick="closeInfoModal()">OK</button>
</div>
</div>
</div>
<style>
.modal { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); z-index: 1000; align-items: center; justify-content: center; }
.modal.active { display: flex; }
.modal-content { background: var(--surface); padding: var(--spacing-xl); border-radius: var(--radius-lg, 12px); max-width: 480px; width: 90%; box-shadow: 0 20px 60px rgba(0,0,0,0.3); animation: modalSlideIn 0.2s ease-out; }
@keyframes modalSlideIn { from { opacity: 0; transform: translateY(-20px); } to { opacity: 1; transform: translateY(0); } }
.modal-header { display: flex; align-items: center; gap: var(--spacing-md); margin-bottom: var(--spacing-md); }
.modal-icon { width: 48px; height: 48px; border-radius: 50%; display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
.modal-icon.warning { background: #fef3c7; color: #d97706; }
.modal-icon.success { background: #dcfce7; color: #16a34a; }
.modal-icon.info { background: #dbeafe; color: #2563eb; }
.modal-title { font-size: var(--font-size-lg, 18px); font-weight: 600; }
.modal-body { color: var(--text-secondary); line-height: 1.6; margin-bottom: var(--spacing-lg); }
.modal-footer { display: flex; justify-content: flex-end; gap: var(--spacing-sm); }
</style>
{% if analysis and analysis.seo_audited_at %}
<div class="audit-info">
<span>Ostatni audyt: <strong>{{ analysis.seo_audited_at.strftime('%d.%m.%Y %H:%M') }}</strong></span>
{% if analysis.cms_detected %}<span>CMS: <strong>{{ analysis.cms_detected }}</strong></span>{% endif %}
{% if analysis.hosting_provider %}<span>Hosting: <strong>{{ analysis.hosting_provider }}</strong></span>{% endif %}
{% if analysis.load_time_ms %}<span>Czas ladowania: <strong>{{ analysis.load_time_ms }}ms</strong></span>{% endif %}
</div>
{% endif %}
<!-- 4 Score Cards -->
{% macro score_class(val) %}{% if val is none %}na{% elif val >= 90 %}good{% elif val >= 50 %}medium{% else %}poor{% endif %}{% endmacro %}
<div class="scores-grid">
<div class="score-card">
<div class="score-ring {{ score_class(analysis.pagespeed_seo_score if analysis else none) }}">
{{ analysis.pagespeed_seo_score if analysis and analysis.pagespeed_seo_score is not none else '-' }}
</div>
<div class="score-name">SEO</div>
<div class="score-desc">Optymalizacja pod wyszukiwarki: meta tagi, struktura, indeksowalnosc</div>
</div>
<div class="score-card">
<div class="score-ring {{ score_class(analysis.pagespeed_performance_score if analysis else none) }}">
{{ analysis.pagespeed_performance_score if analysis and analysis.pagespeed_performance_score is not none else '-' }}
</div>
<div class="score-name">Performance</div>
<div class="score-desc">Szybkosc ladowania strony, czas do interakcji, plynnosc dzialania</div>
</div>
<div class="score-card">
<div class="score-ring {{ score_class(analysis.pagespeed_accessibility_score if analysis else none) }}">
{{ analysis.pagespeed_accessibility_score if analysis and analysis.pagespeed_accessibility_score is not none else '-' }}
</div>
<div class="score-name">Accessibility</div>
<div class="score-desc">Dostepnosc dla osob z niepelnosprawnosciami: kontrast, opisy, nawigacja klawiatura</div>
</div>
<div class="score-card">
<div class="score-ring {{ score_class(analysis.pagespeed_best_practices_score if analysis else none) }}">
{{ analysis.pagespeed_best_practices_score if analysis and analysis.pagespeed_best_practices_score is not none else '-' }}
</div>
<div class="score-name">Best Practices</div>
<div class="score-desc">Bezpieczenstwo (HTTPS), nowoczesne standardy, brak bledow technicznych</div>
</div>
</div>
<!-- Zalecenia -->
<div class="section">
<h2>Zalecenia — co poprawic</h2>
{% if recommendations %}
<ul class="reco-list">
{% for r in recommendations %}
<li class="reco-item {{ r.severity }}">
<span class="reco-severity {{ r.severity }}">
{% if r.severity == 'critical' %}KRYTYCZNE{% elif r.severity == 'warning' %}UWAGA{% elif r.severity == 'success' %}OK{% else %}INFO{% endif %}
</span>
{{ r.text }}
</li>
{% endfor %}
</ul>
{% else %}
<p class="text-muted">Brak danych do analizy. Uruchom audyt, zeby zobaczyc zalecenia.</p>
{% endif %}
</div>
{% if analysis %}
<div class="two-col">
<!-- SEO & Tresc -->
<div class="section">
<h2>SEO i tresc strony</h2>
<div class="detail-grid">
<div class="detail-item">
<span class="detail-label">Tytul strony (meta title)</span>
<span class="detail-value {% if analysis.meta_title or analysis.seo_title %}yes{% else %}no{% endif %}">
{% if analysis.meta_title or analysis.seo_title %}Jest{% else %}Brak{% endif %}
</span>
</div>
<div class="detail-item">
<span class="detail-label">Opis strony (meta description)</span>
<span class="detail-value {% if analysis.meta_description or analysis.seo_description %}yes{% else %}no{% endif %}">
{% if analysis.meta_description or analysis.seo_description %}Jest{% else %}Brak{% endif %}
</span>
</div>
<div class="detail-item">
<span class="detail-label">Sitemap.xml</span>
<span class="detail-value {% if analysis.has_sitemap %}yes{% else %}no{% endif %}">
{% if analysis.has_sitemap %}Jest{% else %}Brak{% endif %}
</span>
</div>
<div class="detail-item">
<span class="detail-label">Robots.txt</span>
<span class="detail-value {% if analysis.has_robots_txt %}yes{% else %}no{% endif %}">
{% if analysis.has_robots_txt %}Jest{% else %}Brak{% endif %}
</span>
</div>
<div class="detail-item">
<span class="detail-label">Dane strukturalne (Schema.org)</span>
<span class="detail-value {% if analysis.has_structured_data %}yes{% else %}no{% endif %}">
{% if analysis.has_structured_data %}Jest{% else %}Brak{% endif %}
</span>
</div>
<div class="detail-item">
<span class="detail-label">Canonical URL</span>
<span class="detail-value {% if analysis.has_canonical %}yes{% else %}no{% endif %}">
{% if analysis.has_canonical %}Jest{% else %}Brak{% endif %}
</span>
</div>
<div class="detail-item">
<span class="detail-label">Open Graph (Facebook)</span>
<span class="detail-value {% if analysis.has_og_tags %}yes{% else %}no{% endif %}">
{% if analysis.has_og_tags %}Jest{% else %}Brak{% endif %}
</span>
</div>
<div class="detail-item">
<span class="detail-label">Twitter Cards</span>
<span class="detail-value {% if analysis.has_twitter_cards %}yes{% else %}no{% endif %}">
{% if analysis.has_twitter_cards %}Jest{% else %}Brak{% endif %}
</span>
</div>
<div class="detail-item">
<span class="detail-label">Naglowki H1</span>
<span class="detail-value {% if analysis.h1_count == 1 %}yes{% elif analysis.h1_count is not none %}no{% endif %}">
{{ analysis.h1_count if analysis.h1_count is not none else '-' }}
</span>
</div>
<div class="detail-item">
<span class="detail-label">Naglowki H2</span>
<span class="detail-value">{{ analysis.h2_count if analysis.h2_count is not none else '-' }}</span>
</div>
<div class="detail-item">
<span class="detail-label">Naglowki H3</span>
<span class="detail-value">{{ analysis.h3_count if analysis.h3_count is not none else '-' }}</span>
</div>
{% if analysis.has_hreflang is not none %}
<div class="detail-item">
<span class="detail-label">Hreflang — wersje jezykowe</span>
<span class="detail-value {% if analysis.has_hreflang %}yes{% else %}no{% endif %}">
{% if analysis.has_hreflang %}Jest{% else %}Brak{% endif %}
</span>
</div>
{% endif %}
<div class="detail-item">
<span class="detail-label">Obrazki ogolnie</span>
<span class="detail-value">{{ analysis.total_images if analysis.total_images is not none else '-' }}</span>
</div>
<div class="detail-item">
<span class="detail-label">Obrazki bez opisu (alt)</span>
<span class="detail-value {% if analysis.images_without_alt and analysis.images_without_alt > 0 %}no{% else %}yes{% endif %}">
{{ analysis.images_without_alt if analysis.images_without_alt is not none else '-' }}
</span>
</div>
<div class="detail-item">
<span class="detail-label">Linki wewnetrzne</span>
<span class="detail-value">{{ analysis.internal_links_count if analysis.internal_links_count is not none else '-' }}</span>
</div>
<div class="detail-item">
<span class="detail-label">Linki zewnetrzne</span>
<span class="detail-value">{{ analysis.external_links_count if analysis.external_links_count is not none else '-' }}</span>
</div>
</div>
{% if analysis.meta_title or analysis.seo_title %}
<div style="margin-top: var(--spacing-lg); padding: var(--spacing-md); background: var(--background); border-radius: var(--radius);">
<div class="detail-label" style="margin-bottom: 4px;">Tytul strony:</div>
<div style="font-size: var(--font-size-sm); color: var(--text-primary);">{{ analysis.meta_title or analysis.seo_title }}</div>
</div>
{% endif %}
{% if analysis.meta_description or analysis.seo_description %}
<div style="margin-top: var(--spacing-sm); padding: var(--spacing-md); background: var(--background); border-radius: var(--radius);">
<div class="detail-label" style="margin-bottom: 4px;">Opis strony:</div>
<div style="font-size: var(--font-size-sm); color: var(--text-primary);">{{ analysis.meta_description or analysis.seo_description }}</div>
</div>
{% endif %}
{% if analysis.meta_keywords %}
<div style="margin-top: var(--spacing-sm); padding: var(--spacing-md); background: var(--background); border-radius: var(--radius);">
<div class="detail-label" style="margin-bottom: 4px;">Meta keywords (slowa kluczowe w kodzie strony):</div>
<div style="font-size: var(--font-size-sm); color: var(--text-secondary);">{{ analysis.meta_keywords }}</div>
</div>
{% endif %}
{% if analysis.canonical_url %}
<div style="margin-top: var(--spacing-sm); padding: var(--spacing-md); background: var(--background); border-radius: var(--radius);">
<div class="detail-label" style="margin-bottom: 4px;">Canonical URL — adres kanoniczny strony:</div>
<div style="font-size: var(--font-size-sm); color: var(--text-primary); word-break: break-all;">{{ analysis.canonical_url }}</div>
</div>
{% endif %}
{% if analysis.noindex_reason %}
<div style="margin-top: var(--spacing-sm); padding: var(--spacing-md); background: #fef2f2; border-radius: var(--radius); border-left: 3px solid var(--danger);">
<div class="detail-label" style="margin-bottom: 4px; color: var(--danger);">Powod blokady indeksowania:</div>
<div style="font-size: var(--font-size-sm); color: var(--text-primary);">{{ analysis.noindex_reason }}</div>
</div>
{% endif %}
</div>
<!-- Podglad Open Graph — jak strona wyglada na Facebooku -->
{% if analysis.og_title or analysis.og_description or analysis.og_image %}
<div class="section">
<h2>Open Graph — podglad udostepnienia na Facebooku</h2>
<p style="font-size: var(--font-size-xs); color: var(--text-secondary); margin-bottom: var(--spacing-md);">
Tak wyglada link do tej strony udostepniony na Facebooku, LinkedIn czy Messengerze.
</p>
<div style="max-width: 500px; border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; background: #f0f2f5;">
{% if analysis.og_image %}
<div style="width: 100%; height: 260px; background: #e4e6eb; display: flex; align-items: center; justify-content: center; overflow: hidden;">
<img src="{{ analysis.og_image }}" alt="OG Image" referrerpolicy="no-referrer" style="width: 100%; height: 100%; object-fit: cover;" onerror="this.parentElement.innerHTML='<div style=\'text-align:center; padding: 20px;\'><div style=\'color: var(--text-secondary); font-size: var(--font-size-sm); margin-bottom: 8px;\'>Obrazek niedostepny z poziomu naszego serwera</div><a href=\'{{ analysis.og_image }}\' target=\'_blank\' rel=\'noopener\' style=\'color: var(--primary); font-size: 11px; word-break: break-all;\'>{{ analysis.og_image }}</a></div>'">
</div>
{% else %}
<div style="width: 100%; height: 80px; background: #e4e6eb; display: flex; align-items: center; justify-content: center;">
<span style="color: var(--text-secondary); font-size: var(--font-size-sm);">Brak obrazka Open Graph</span>
</div>
{% endif %}
<div style="padding: var(--spacing-md);">
<div style="font-size: 11px; color: #65676b; text-transform: uppercase;">{{ analysis.final_url or analysis.website_url or '' }}</div>
<div style="font-weight: 600; font-size: var(--font-size-base); color: #050505; margin-top: 4px;">{{ analysis.og_title or analysis.meta_title or analysis.seo_title or 'Brak tytulu' }}</div>
{% if analysis.og_description %}
<div style="font-size: var(--font-size-sm); color: #65676b; margin-top: 4px; line-height: 1.3;">{{ analysis.og_description[:150] }}{% if analysis.og_description|length > 150 %}...{% endif %}</div>
{% endif %}
</div>
</div>
</div>
{% endif %}
<!-- Techniczne -->
<div class="section">
<h2>Dane techniczne</h2>
<div class="detail-grid">
<div class="detail-item">
<span class="detail-label">SSL (HTTPS)</span>
<span class="detail-value {% if analysis.has_ssl %}yes{% else %}no{% endif %}">
{% if analysis.has_ssl %}Tak{% else %}Nie{% endif %}
</span>
</div>
<div class="detail-item">
<span class="detail-label">Responsywnosc (mobile)</span>
<span class="detail-value {% if analysis.is_responsive or analysis.is_mobile_friendly %}yes{% else %}no{% endif %}">
{% if analysis.is_responsive or analysis.is_mobile_friendly %}Tak{% else %}Nie{% endif %}
</span>
</div>
<div class="detail-item">
<span class="detail-label">Indeksowalnosc</span>
<span class="detail-value {% if analysis.is_indexable %}yes{% else %}no{% endif %}">
{% if analysis.is_indexable %}Tak{% else %}Nie{% endif %}
</span>
</div>
<div class="detail-item">
<span class="detail-label">Viewport (meta tag)</span>
<span class="detail-value {% if analysis.viewport_configured or analysis.has_viewport_meta %}yes{% else %}no{% endif %}">
{% if analysis.viewport_configured or analysis.has_viewport_meta %}Tak{% else %}Nie{% endif %}
</span>
</div>
<div class="detail-item">
<span class="detail-label">Jezyk strony (html lang)</span>
<span class="detail-value">{{ analysis.html_lang or '-' }}</span>
</div>
<div class="detail-item">
<span class="detail-label">CMS / System</span>
<span class="detail-value">{{ analysis.cms_detected or '-' }}</span>
</div>
{% if analysis.largest_contentful_paint_ms %}
<div class="detail-item">
<span class="detail-label">LCP — czas ladowania glownej tresci</span>
<span class="detail-value {% if analysis.largest_contentful_paint_ms <= 2500 %}yes{% elif analysis.largest_contentful_paint_ms <= 4000 %}{% else %}no{% endif %}">
{{ (analysis.largest_contentful_paint_ms / 1000)|round(1) }}s
</span>
</div>
{% endif %}
{% if analysis.cumulative_layout_shift is not none %}
<div class="detail-item">
<span class="detail-label">CLS — stabilnosc wizualna strony</span>
<span class="detail-value {% if analysis.cumulative_layout_shift|float <= 0.1 %}yes{% elif analysis.cumulative_layout_shift|float <= 0.25 %}{% else %}no{% endif %}">
{{ analysis.cumulative_layout_shift }}
</span>
</div>
{% endif %}
{% if analysis.interaction_to_next_paint_ms %}
<div class="detail-item">
<span class="detail-label">INP — szybkosc reakcji na klikniecia</span>
<span class="detail-value {% if analysis.interaction_to_next_paint_ms <= 200 %}yes{% elif analysis.interaction_to_next_paint_ms <= 500 %}{% else %}no{% endif %}">
{{ analysis.interaction_to_next_paint_ms }}ms
</span>
</div>
{% endif %}
</div>
{% if analysis.security_headers_count is not none %}
<h3 style="margin-top: var(--spacing-lg); font-size: var(--font-size-base); color: var(--text-secondary);">Zabezpieczenia</h3>
<div class="detail-grid" style="margin-top: var(--spacing-sm);">
<div class="detail-item">
<span class="detail-label">HSTS — wymuszenie HTTPS</span>
<span class="detail-value {% if analysis.has_hsts %}yes{% else %}no{% endif %}">
{% if analysis.has_hsts %}Tak{% else %}Nie{% endif %}
</span>
</div>
<div class="detail-item">
<span class="detail-label">CSP — ochrona przed wstrzykiwaniem skryptow</span>
<span class="detail-value {% if analysis.has_csp %}yes{% else %}no{% endif %}">
{% if analysis.has_csp %}Tak{% else %}Nie{% endif %}
</span>
</div>
<div class="detail-item">
<span class="detail-label">X-Frame-Options — ochrona przed osadzaniem</span>
<span class="detail-value {% if analysis.has_x_frame_options %}yes{% else %}no{% endif %}">
{% if analysis.has_x_frame_options %}Tak{% else %}Nie{% endif %}
</span>
</div>
<div class="detail-item">
<span class="detail-label">X-Content-Type — ochrona typu plikow</span>
<span class="detail-value {% if analysis.has_x_content_type_options %}yes{% else %}no{% endif %}">
{% if analysis.has_x_content_type_options %}Tak{% else %}Nie{% endif %}
</span>
</div>
</div>
{% endif %}
</div>
</div>
<!-- Tresc i slowa kluczowe -->
{% if analysis.content_summary or analysis.services_extracted or analysis.main_keywords or analysis.word_count_homepage %}
<div class="section">
<h2>Tresc strony i slowa kluczowe</h2>
<div class="detail-grid">
{% if analysis.word_count_homepage %}
<div class="detail-item">
<span class="detail-label">Liczba slow na stronie glownej</span>
<span class="detail-value">{{ analysis.word_count_homepage }}</span>
</div>
{% endif %}
{% if analysis.content_richness_score %}
<div class="detail-item">
<span class="detail-label">Bogactwo tresci</span>
<span class="detail-value {% if analysis.content_richness_score >= 7 %}yes{% elif analysis.content_richness_score >= 4 %}{% else %}no{% endif %}">{{ analysis.content_richness_score }}/10</span>
</div>
{% endif %}
{% if analysis.page_count_estimate %}
<div class="detail-item">
<span class="detail-label">Szacowana liczba podstron</span>
<span class="detail-value">{{ analysis.page_count_estimate }}</span>
</div>
{% endif %}
{% if analysis.google_indexed_pages %}
<div class="detail-item">
<span class="detail-label">Stron zaindeksowanych w Google</span>
<span class="detail-value">{{ analysis.google_indexed_pages }}</span>
</div>
{% endif %}
<div class="detail-item">
<span class="detail-label">Blog firmowy</span>
<span class="detail-value {% if analysis.has_blog %}yes{% else %}no{% endif %}">{% if analysis.has_blog %}Jest{% else %}Brak{% endif %}</span>
</div>
<div class="detail-item">
<span class="detail-label">Formularz kontaktowy</span>
<span class="detail-value {% if analysis.has_contact_form %}yes{% else %}no{% endif %}">{% if analysis.has_contact_form %}Jest{% else %}Brak{% endif %}</span>
</div>
{% if analysis.has_portfolio is not none %}
<div class="detail-item">
<span class="detail-label">Portfolio / Realizacje</span>
<span class="detail-value {% if analysis.has_portfolio %}yes{% else %}no{% endif %}">{% if analysis.has_portfolio %}Jest{% else %}Brak{% endif %}</span>
</div>
{% endif %}
{% if analysis.has_live_chat is not none %}
<div class="detail-item">
<span class="detail-label">Live chat na stronie</span>
<span class="detail-value {% if analysis.has_live_chat %}yes{% else %}no{% endif %}">{% if analysis.has_live_chat %}Jest{% else %}Brak{% endif %}</span>
</div>
{% endif %}
{% if analysis.broken_links_count is not none %}
<div class="detail-item">
<span class="detail-label">Zlamane linki</span>
<span class="detail-value {% if analysis.broken_links_count == 0 %}yes{% else %}no{% endif %}">{{ analysis.broken_links_count }}</span>
</div>
{% endif %}
{% if analysis.modern_image_ratio is not none %}
<div class="detail-item">
<span class="detail-label">Nowoczesne formaty obrazkow (WebP/AVIF)</span>
<span class="detail-value {% if analysis.modern_image_ratio|float >= 50 %}yes{% else %}no{% endif %}">{{ analysis.modern_image_ratio|round(0)|int }}%</span>
</div>
{% endif %}
</div>
{% if analysis.h1_text %}
<div style="margin-top: var(--spacing-lg); padding: var(--spacing-md); background: var(--background); border-radius: var(--radius);">
<div class="detail-label" style="margin-bottom: 4px;">Naglowek H1 strony:</div>
<div style="font-size: var(--font-size-sm); color: var(--text-primary); font-weight: 600;">{{ analysis.h1_text }}</div>
</div>
{% endif %}
{% if analysis.content_summary %}
<div style="margin-top: var(--spacing-sm); padding: var(--spacing-md); background: var(--background); border-radius: var(--radius);">
<div class="detail-label" style="margin-bottom: 4px;">Podsumowanie tresci (AI):</div>
<div style="font-size: var(--font-size-sm); color: var(--text-primary); line-height: 1.5;">{{ analysis.content_summary }}</div>
</div>
{% endif %}
{% if analysis.services_extracted %}
<div style="margin-top: var(--spacing-sm); padding: var(--spacing-md); background: var(--background); border-radius: var(--radius);">
<div class="detail-label" style="margin-bottom: 4px;">Wykryte uslugi:</div>
<div style="display: flex; flex-wrap: wrap; gap: 4px; margin-top: 4px;">
{% for service in analysis.services_extracted %}
<span style="padding: 2px 8px; background: #dbeafe; color: #1e40af; border-radius: var(--radius-sm); font-size: 11px;">{{ service }}</span>
{% endfor %}
</div>
</div>
{% endif %}
{% if analysis.main_keywords %}
<div style="margin-top: var(--spacing-sm); padding: var(--spacing-md); background: var(--background); border-radius: var(--radius);">
<div class="detail-label" style="margin-bottom: 4px;">Glowne slowa kluczowe:</div>
<div style="display: flex; flex-wrap: wrap; gap: 4px; margin-top: 4px;">
{% for kw in analysis.main_keywords %}
<span style="padding: 2px 8px; background: #f0fdf4; color: #166534; border-radius: var(--radius-sm); font-size: 11px;">{{ kw }}</span>
{% endfor %}
</div>
</div>
{% endif %}
{% if analysis.structured_data_types %}
<div style="margin-top: var(--spacing-sm); padding: var(--spacing-md); background: var(--background); border-radius: var(--radius);">
<div class="detail-label" style="margin-bottom: 4px;">Typy danych strukturalnych (Schema.org):</div>
<div style="display: flex; flex-wrap: wrap; gap: 4px; margin-top: 4px;">
{% for sdt in analysis.structured_data_types %}
<span style="padding: 2px 8px; background: #fef3c7; color: #92400e; border-radius: var(--radius-sm); font-size: 11px;">{{ sdt }}</span>
{% endfor %}
</div>
</div>
{% endif %}
</div>
{% endif %}
<!-- CrUX — dane od prawdziwych uzytkownikow -->
{% if analysis.crux_lcp_ms or analysis.crux_inp_ms or analysis.crux_cls is not none %}
<div class="section">
<h2>Core Web Vitals — dane od prawdziwych uzytkownikow (CrUX)</h2>
<p style="font-size: var(--font-size-xs); color: var(--text-secondary); margin-bottom: var(--spacing-md);">
Te metryki pochodza z przegladarek Chrome prawdziwych odwiedzajacych, nie z testow laboratoryjnych. Wyniki moga sie roznic od Lighthouse.
</p>
<div class="detail-grid">
{% if analysis.crux_lcp_ms %}
<div class="detail-item">
<span class="detail-label">LCP — czas glownej tresci (p75)</span>
<span class="detail-value {% if analysis.crux_lcp_ms <= 2500 %}yes{% elif analysis.crux_lcp_ms <= 4000 %}{% else %}no{% endif %}">{{ (analysis.crux_lcp_ms / 1000)|round(1) }}s</span>
</div>
{% endif %}
{% if analysis.crux_fcp_ms %}
<div class="detail-item">
<span class="detail-label">FCP — czas pierwszej tresci (p75)</span>
<span class="detail-value {% if analysis.crux_fcp_ms <= 1800 %}yes{% elif analysis.crux_fcp_ms <= 3000 %}{% else %}no{% endif %}">{{ (analysis.crux_fcp_ms / 1000)|round(1) }}s</span>
</div>
{% endif %}
{% if analysis.crux_inp_ms %}
<div class="detail-item">
<span class="detail-label">INP — szybkosc reakcji (p75)</span>
<span class="detail-value {% if analysis.crux_inp_ms <= 200 %}yes{% elif analysis.crux_inp_ms <= 500 %}{% else %}no{% endif %}">{{ analysis.crux_inp_ms }}ms</span>
</div>
{% endif %}
{% if analysis.crux_cls is not none %}
<div class="detail-item">
<span class="detail-label">CLS — stabilnosc wizualna (p75)</span>
<span class="detail-value {% if analysis.crux_cls|float <= 0.1 %}yes{% elif analysis.crux_cls|float <= 0.25 %}{% else %}no{% endif %}">{{ analysis.crux_cls }}</span>
</div>
{% endif %}
{% if analysis.crux_ttfb_ms %}
<div class="detail-item">
<span class="detail-label">TTFB — czas odpowiedzi serwera (p75)</span>
<span class="detail-value {% if analysis.crux_ttfb_ms <= 800 %}yes{% elif analysis.crux_ttfb_ms <= 1800 %}{% else %}no{% endif %}">{{ analysis.crux_ttfb_ms }}ms</span>
</div>
{% endif %}
{% if analysis.crux_lcp_good_pct is not none %}
<div class="detail-item">
<span class="detail-label">Uzytkownicy z dobrym LCP</span>
<span class="detail-value {% if analysis.crux_lcp_good_pct|float >= 75 %}yes{% elif analysis.crux_lcp_good_pct|float >= 50 %}{% else %}no{% endif %}">{{ analysis.crux_lcp_good_pct }}%</span>
</div>
{% endif %}
{% if analysis.crux_inp_good_pct is not none %}
<div class="detail-item">
<span class="detail-label">Uzytkownicy z dobrym INP</span>
<span class="detail-value {% if analysis.crux_inp_good_pct|float >= 75 %}yes{% elif analysis.crux_inp_good_pct|float >= 50 %}{% else %}no{% endif %}">{{ analysis.crux_inp_good_pct }}%</span>
</div>
{% endif %}
</div>
</div>
{% endif %}
<!-- Google Search Console -->
{% if analysis.gsc_clicks or analysis.gsc_impressions %}
<div class="section">
<h2>Google Search Console — widocznosc w wyszukiwarce</h2>
<p style="font-size: var(--font-size-xs); color: var(--text-secondary); margin-bottom: var(--spacing-md);">
Dane z ostatnich {{ analysis.gsc_period_days or 28 }} dni. Pokazuja, jak strona wypada w wynikach wyszukiwania Google.
</p>
<div class="detail-grid">
<div class="detail-item">
<span class="detail-label">Klikniecia z Google</span>
<span class="detail-value">{{ analysis.gsc_clicks or 0 }}</span>
</div>
<div class="detail-item">
<span class="detail-label">Wyswietlenia w Google</span>
<span class="detail-value">{{ analysis.gsc_impressions or 0 }}</span>
</div>
{% if analysis.gsc_ctr %}
<div class="detail-item">
<span class="detail-label">CTR — procent klikniec</span>
<span class="detail-value">{{ analysis.gsc_ctr }}%</span>
</div>
{% endif %}
{% if analysis.gsc_avg_position %}
<div class="detail-item">
<span class="detail-label">Srednia pozycja w Google</span>
<span class="detail-value {% if analysis.gsc_avg_position|float <= 10 %}yes{% elif analysis.gsc_avg_position|float <= 30 %}{% else %}no{% endif %}">{{ analysis.gsc_avg_position }}</span>
</div>
{% endif %}
{% if analysis.gsc_index_status %}
<div class="detail-item">
<span class="detail-label">Status indeksowania</span>
<span class="detail-value {% if analysis.gsc_index_status == 'PASS' %}yes{% else %}no{% endif %}">{{ analysis.gsc_index_status }}</span>
</div>
{% endif %}
</div>
{% if analysis.gsc_top_queries and analysis.gsc_top_queries is not mapping %}
<div style="margin-top: var(--spacing-lg); padding: var(--spacing-md); background: var(--background); border-radius: var(--radius);">
<div class="detail-label" style="margin-bottom: var(--spacing-sm);">Top zapytania, po ktorych ludzie trafiaja na strone:</div>
<table style="width: 100%; font-size: var(--font-size-sm); border-collapse: collapse;">
<thead>
<tr style="border-bottom: 1px solid var(--border);">
<th style="text-align: left; padding: 4px 8px; color: var(--text-secondary);">Zapytanie</th>
<th style="text-align: right; padding: 4px 8px; color: var(--text-secondary);">Klikniecia</th>
<th style="text-align: right; padding: 4px 8px; color: var(--text-secondary);">Wyswietlenia</th>
<th style="text-align: right; padding: 4px 8px; color: var(--text-secondary);">Pozycja</th>
</tr>
</thead>
<tbody>
{% for q in analysis.gsc_top_queries[:10] %}
<tr style="border-bottom: 1px solid var(--border);">
<td style="padding: 4px 8px;">{{ q.query if q.query is defined else q.get('keys', [''])[0] }}</td>
<td style="text-align: right; padding: 4px 8px; font-weight: 600;">{{ q.clicks if q.clicks is defined else q.get('clicks', 0) }}</td>
<td style="text-align: right; padding: 4px 8px;">{{ q.impressions if q.impressions is defined else q.get('impressions', 0) }}</td>
<td style="text-align: right; padding: 4px 8px;">{{ "%.1f"|format(q.position if q.position is defined else q.get('position', 0)) }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
{% if analysis.gsc_top_pages and analysis.gsc_top_pages is not mapping %}
<div style="margin-top: var(--spacing-sm); padding: var(--spacing-md); background: var(--background); border-radius: var(--radius);">
<div class="detail-label" style="margin-bottom: var(--spacing-sm);">Najpopularniejsze podstrony w Google:</div>
<table style="width: 100%; font-size: var(--font-size-sm); border-collapse: collapse;">
<thead>
<tr style="border-bottom: 1px solid var(--border);">
<th style="text-align: left; padding: 4px 8px; color: var(--text-secondary);">Strona</th>
<th style="text-align: right; padding: 4px 8px; color: var(--text-secondary);">Klikniecia</th>
<th style="text-align: right; padding: 4px 8px; color: var(--text-secondary);">Wyswietlenia</th>
</tr>
</thead>
<tbody>
{% for p in analysis.gsc_top_pages[:10] %}
<tr style="border-bottom: 1px solid var(--border);">
<td style="padding: 4px 8px; word-break: break-all; max-width: 300px;">{{ p.get('page', p.get('keys', [''])[0]) }}</td>
<td style="text-align: right; padding: 4px 8px; font-weight: 600;">{{ p.get('clicks', 0) }}</td>
<td style="text-align: right; padding: 4px 8px;">{{ p.get('impressions', 0) }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
</div>
{% endif %}
<!-- Google Business Profile -->
{% if analysis.google_rating or analysis.google_place_id %}
<div class="section">
<h2>Google Business Profile</h2>
<div class="detail-grid">
{% if analysis.google_rating %}
<div class="detail-item">
<span class="detail-label">Ocena Google</span>
<span class="detail-value">{{ analysis.google_rating }}/5 ({{ analysis.google_reviews_count or 0 }} opinii)</span>
</div>
{% endif %}
{% if analysis.google_name %}
<div class="detail-item">
<span class="detail-label">Nazwa w Google</span>
<span class="detail-value">{{ analysis.google_name }}</span>
</div>
{% endif %}
{% if analysis.google_address %}
<div class="detail-item">
<span class="detail-label">Adres w Google</span>
<span class="detail-value">{{ analysis.google_address }}</span>
</div>
{% endif %}
{% if analysis.google_phone %}
<div class="detail-item">
<span class="detail-label">Telefon z Google</span>
<span class="detail-value">{{ analysis.google_phone }}</span>
</div>
{% endif %}
{% if analysis.google_website %}
<div class="detail-item">
<span class="detail-label">Strona WWW w Google</span>
<span class="detail-value"><a href="{{ analysis.google_website }}" target="_blank" rel="noopener" style="color: var(--primary); font-size: 11px; word-break: break-all;">{{ analysis.google_website }}</a></span>
</div>
{% endif %}
{% if analysis.google_business_status %}
<div class="detail-item">
<span class="detail-label">Status</span>
<span class="detail-value {% if analysis.google_business_status == 'OPERATIONAL' %}yes{% endif %}">{{ analysis.google_business_status }}</span>
</div>
{% endif %}
{% if analysis.google_types %}
<div class="detail-item">
<span class="detail-label">Kategorie w Google</span>
<span class="detail-value" style="font-size: 11px;">{{ analysis.google_types|join(', ') }}</span>
</div>
{% endif %}
{% if analysis.google_photos_count %}
<div class="detail-item">
<span class="detail-label">Zdjecia w Google</span>
<span class="detail-value">{{ analysis.google_photos_count }}</span>
</div>
{% endif %}
{% if analysis.google_posts_count %}
<div class="detail-item">
<span class="detail-label">Posty w Google (Google Posts)</span>
<span class="detail-value">{{ analysis.google_posts_count }}</span>
</div>
{% endif %}
{% if analysis.google_review_response_rate is not none %}
<div class="detail-item">
<span class="detail-label">Odpowiedzi na opinie</span>
<span class="detail-value {% if analysis.google_review_response_rate|float >= 80 %}yes{% elif analysis.google_review_response_rate|float >= 50 %}{% else %}no{% endif %}">{{ analysis.google_review_response_rate }}%</span>
</div>
{% endif %}
{% if analysis.google_maps_url %}
<div class="detail-item">
<span class="detail-label">Link do Google Maps</span>
<span class="detail-value"><a href="{{ analysis.google_maps_url }}" target="_blank" rel="noopener" style="color: var(--primary);">Otworz</a></span>
</div>
{% endif %}
</div>
{% if analysis.google_editorial_summary %}
<div style="margin-top: var(--spacing-lg); padding: var(--spacing-md); background: var(--background); border-radius: var(--radius);">
<div class="detail-label" style="margin-bottom: 4px;">Opis firmy w Google:</div>
<div style="font-size: var(--font-size-sm); color: var(--text-primary); line-height: 1.5;">{{ analysis.google_editorial_summary }}</div>
</div>
{% endif %}
{% if analysis.google_opening_hours %}
<div style="margin-top: var(--spacing-sm); padding: var(--spacing-md); background: var(--background); border-radius: var(--radius);">
<div class="detail-label" style="margin-bottom: var(--spacing-sm);">Godziny otwarcia z Google:</div>
{% set hours_data = analysis.google_opening_hours %}
{% if hours_data is mapping and hours_data.get('weekday_text') %}
{% for entry in hours_data.weekday_text %}
<div style="font-size: var(--font-size-sm); padding: 3px 0; border-bottom: 1px solid var(--border);">{{ entry }}</div>
{% endfor %}
{% elif hours_data is mapping and hours_data.get('periods') %}
{% set day_names = {0: 'Niedziela', 1: 'Poniedzialek', 2: 'Wtorek', 3: 'Sroda', 4: 'Czwartek', 5: 'Piatek', 6: 'Sobota'} %}
{% for period in hours_data.periods %}
<div style="font-size: var(--font-size-sm); padding: 3px 0; border-bottom: 1px solid var(--border);">
{{ day_names.get(period.open.day, period.open.day) }}: {{ period.open.time[:2] }}:{{ period.open.time[2:] }}{{ period.close.time[:2] }}:{{ period.close.time[2:] }}
</div>
{% endfor %}
{% elif hours_data is iterable and hours_data is not string and hours_data is not mapping %}
{% for entry in hours_data %}
<div style="font-size: var(--font-size-sm); padding: 3px 0; border-bottom: 1px solid var(--border);">{{ entry }}</div>
{% endfor %}
{% else %}
<div style="font-size: var(--font-size-sm);">{{ hours_data }}</div>
{% endif %}
</div>
{% endif %}
{% if analysis.google_reviews_data and analysis.google_reviews_data is iterable and analysis.google_reviews_data is not mapping %}
<div style="margin-top: var(--spacing-sm); padding: var(--spacing-md); background: var(--background); border-radius: var(--radius);">
<div class="detail-label" style="margin-bottom: var(--spacing-sm);">Ostatnie opinie Google:</div>
{% for review in analysis.google_reviews_data[:5] %}
<div style="padding: var(--spacing-sm) 0; border-bottom: 1px solid var(--border); font-size: var(--font-size-sm);">
<div style="display: flex; justify-content: space-between; margin-bottom: 4px;">
<span style="font-weight: 600;">{{ review.get('author', review.get('authorAttribution', {}).get('displayName', 'Anonim')) }}</span>
<span style="color: var(--warning);">
{% set stars = review.get('rating', review.get('star_rating', 0))|int %}
{% for i in range(stars) %}★{% endfor %}{% for i in range(5 - stars) %}☆{% endfor %}
</span>
</div>
{% set review_text = review.get('text', review.get('comment', review.get('originalText', {}).get('text', ''))) %}
{% if review_text %}
<div style="color: var(--text-secondary); line-height: 1.4;">{{ review_text[:200] }}{% if review_text|length > 200 %}...{% endif %}</div>
{% endif %}
</div>
{% endfor %}
</div>
{% endif %}
</div>
{% endif %}
<!-- GBP statystyki -->
{% if analysis.gbp_impressions_maps or analysis.gbp_impressions_search or analysis.gbp_call_clicks %}
<div class="section">
<h2>Statystyki Google Business Profile</h2>
<p style="font-size: var(--font-size-xs); color: var(--text-secondary); margin-bottom: var(--spacing-md);">
Dane z ostatnich {{ analysis.gbp_performance_period_days or 30 }} dni.
</p>
<div class="detail-grid">
{% if analysis.gbp_impressions_search %}
<div class="detail-item">
<span class="detail-label">Wyswietlenia w wyszukiwarce Google</span>
<span class="detail-value">{{ analysis.gbp_impressions_search }}</span>
</div>
{% endif %}
{% if analysis.gbp_impressions_maps %}
<div class="detail-item">
<span class="detail-label">Wyswietlenia w Google Maps</span>
<span class="detail-value">{{ analysis.gbp_impressions_maps }}</span>
</div>
{% endif %}
{% if analysis.gbp_website_clicks %}
<div class="detail-item">
<span class="detail-label">Klikniecia w strone WWW</span>
<span class="detail-value">{{ analysis.gbp_website_clicks }}</span>
</div>
{% endif %}
{% if analysis.gbp_call_clicks %}
<div class="detail-item">
<span class="detail-label">Klikniecia w telefon</span>
<span class="detail-value">{{ analysis.gbp_call_clicks }}</span>
</div>
{% endif %}
{% if analysis.gbp_direction_requests %}
<div class="detail-item">
<span class="detail-label">Zapytania o dojazd</span>
<span class="detail-value">{{ analysis.gbp_direction_requests }}</span>
</div>
{% endif %}
{% if analysis.gbp_conversations %}
<div class="detail-item">
<span class="detail-label">Wiadomosci (conversations)</span>
<span class="detail-value">{{ analysis.gbp_conversations }}</span>
</div>
{% endif %}
</div>
{% if analysis.gbp_search_keywords and analysis.gbp_search_keywords is not mapping %}
<div style="margin-top: var(--spacing-lg); padding: var(--spacing-md); background: var(--background); border-radius: var(--radius);">
<div class="detail-label" style="margin-bottom: var(--spacing-sm);">Po jakich slowach ludzie szukaja tej firmy w Google Maps:</div>
<table style="width: 100%; font-size: var(--font-size-sm); border-collapse: collapse;">
<thead>
<tr style="border-bottom: 1px solid var(--border);">
<th style="text-align: left; padding: 4px 8px; color: var(--text-secondary);">Slowo kluczowe</th>
<th style="text-align: right; padding: 4px 8px; color: var(--text-secondary);">Wyswietlenia</th>
</tr>
</thead>
<tbody>
{% for kw in analysis.gbp_search_keywords[:15] %}
<tr style="border-bottom: 1px solid var(--border);">
<td style="padding: 4px 8px;">{{ kw.get('keyword', kw.get('searchKeyword', kw)) if kw is mapping else kw }}</td>
<td style="text-align: right; padding: 4px 8px; font-weight: 600;">{{ kw.get('impressions', kw.get('insightsValue', {}).get('value', '-')) if kw is mapping else '' }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
</div>
{% endif %}
<!-- Infrastruktura i szczegoly techniczne -->
{% if analysis.ssl_expires_at or analysis.final_url or analysis.hosting_provider or analysis.server_software or analysis.hosting_ip %}
<div class="section">
<h2>Infrastruktura i szczegoly techniczne</h2>
<div class="detail-grid">
{% if analysis.final_url and analysis.final_url != analysis.website_url %}
<div class="detail-item">
<span class="detail-label">URL po przekierowaniu</span>
<span class="detail-value" style="font-size: 11px; word-break: break-all;">{{ analysis.final_url }}</span>
</div>
{% endif %}
{% if analysis.http_status_code %}
<div class="detail-item">
<span class="detail-label">Kod odpowiedzi HTTP</span>
<span class="detail-value {% if analysis.http_status_code == 200 %}yes{% else %}no{% endif %}">{{ analysis.http_status_code }}</span>
</div>
{% endif %}
{% if analysis.ssl_expires_at %}
<div class="detail-item">
<span class="detail-label">Certyfikat SSL wygasa</span>
<span class="detail-value">{{ analysis.ssl_expires_at.strftime('%d.%m.%Y') }}</span>
</div>
{% endif %}
{% if analysis.ssl_issuer %}
<div class="detail-item">
<span class="detail-label">Wystawca SSL</span>
<span class="detail-value">{{ analysis.ssl_issuer }}</span>
</div>
{% endif %}
{% if analysis.hosting_provider or ip_info.get('isp') %}
<div class="detail-item">
<span class="detail-label">Hosting</span>
<span class="detail-value">{{ analysis.hosting_provider or ip_info.get('isp', '') }}</span>
</div>
{% endif %}
{% if ip_info.get('org') and ip_info.get('org') != (analysis.hosting_provider or '') %}
<div class="detail-item">
<span class="detail-label">Firma hostingowa</span>
<span class="detail-value">{{ ip_info.org }}</span>
</div>
{% endif %}
{% if analysis.hosting_ip %}
<div class="detail-item">
<span class="detail-label">Adres IP serwera</span>
<span class="detail-value" style="font-family: monospace;">{{ analysis.hosting_ip }}</span>
</div>
{% endif %}
{% if ip_info.get('city') %}
<div class="detail-item">
<span class="detail-label">Lokalizacja serwera</span>
<span class="detail-value">{{ ip_info.city }}{% if ip_info.get('region') %}, {{ ip_info.region }}{% endif %}{% if ip_info.get('country') %}, {{ ip_info.country }}{% endif %}</span>
</div>
{% endif %}
{% if ip_info.get('as_number') %}
<div class="detail-item">
<span class="detail-label">Siec (AS)</span>
<span class="detail-value" style="font-size: 11px;">{{ ip_info.as_number }}</span>
</div>
{% endif %}
{% if analysis.server_software %}
<div class="detail-item">
<span class="detail-label">Oprogramowanie serwera</span>
<span class="detail-value">{{ analysis.server_software }}</span>
</div>
{% endif %}
{% if analysis.site_author %}
<div class="detail-item">
<span class="detail-label">Tworca strony</span>
<span class="detail-value">{{ analysis.site_author }}</span>
</div>
{% endif %}
{% if analysis.site_generator %}
<div class="detail-item">
<span class="detail-label">Generator strony</span>
<span class="detail-value">{{ analysis.site_generator }}</span>
</div>
{% endif %}
{% if analysis.domain_registrar %}
<div class="detail-item">
<span class="detail-label">Rejestrator domeny</span>
<span class="detail-value">{{ analysis.domain_registrar }}</span>
</div>
{% endif %}
{% if analysis.frameworks_detected %}
<div class="detail-item">
<span class="detail-label">Wykryte technologie</span>
<span class="detail-value">{{ analysis.frameworks_detected|join(', ') }}</span>
</div>
{% endif %}
{% if analysis.last_modified_at %}
<div class="detail-item">
<span class="detail-label">Ostatnia modyfikacja strony</span>
<span class="detail-value">{{ analysis.last_modified_at.strftime('%d.%m.%Y %H:%M') }}</span>
</div>
{% endif %}
</div>
</div>
{% endif %}
<!-- Diagnostyka audytu -->
{% if analysis and (analysis.audit_source or analysis.audit_errors or analysis.analyzed_at) %}
<div class="section" style="opacity: 0.7;">
<h2>Diagnostyka audytu</h2>
<p style="font-size: var(--font-size-xs); color: var(--text-secondary); margin-bottom: var(--spacing-md);">
Informacje techniczne o ostatnim uruchomieniu audytu.
</p>
<div class="detail-grid">
{% if analysis.analyzed_at %}
<div class="detail-item">
<span class="detail-label">Data audytu</span>
<span class="detail-value">{{ analysis.analyzed_at.strftime('%d.%m.%Y %H:%M') }}</span>
</div>
{% endif %}
{% if analysis.audit_source %}
<div class="detail-item">
<span class="detail-label">Zrodlo danych</span>
<span class="detail-value">{{ analysis.audit_source }}</span>
</div>
{% endif %}
{% if analysis.audit_version %}
<div class="detail-item">
<span class="detail-label">Wersja audytu</span>
<span class="detail-value">{{ analysis.audit_version }}</span>
</div>
{% endif %}
</div>
{% if analysis.audit_errors %}
<div style="margin-top: var(--spacing-md); padding: var(--spacing-md); background: #fef2f2; border-radius: var(--radius); border-left: 3px solid var(--danger);">
<div class="detail-label" style="margin-bottom: 4px; color: var(--danger);">Bledy podczas audytu:</div>
<div style="font-size: var(--font-size-sm); color: var(--text-primary); white-space: pre-wrap;">{{ analysis.audit_errors }}</div>
</div>
{% endif %}
</div>
{% endif %}
{% endif %}
{% endblock %}
{% block extra_js %}
var csrfToken = '{{ csrf_token() }}';
// Modal functions
function closeModal() {
document.getElementById('confirmModal').classList.remove('active');
}
function showInfoModal(title, body) {
document.getElementById('infoModalTitle').textContent = title;
document.getElementById('infoModalBody').textContent = body;
document.getElementById('infoModal').classList.add('active');
}
function closeInfoModal() {
document.getElementById('infoModal').classList.remove('active');
}
document.getElementById('confirmModal').addEventListener('click', function(e) {
if (e.target.id === 'confirmModal') closeModal();
});
document.getElementById('infoModal').addEventListener('click', function(e) {
if (e.target.id === 'infoModal') closeInfoModal();
});
// Stepper helpers
function setStep(stepId, state) {
var el = document.getElementById(stepId);
var icon = el.querySelector('.step-icon');
el.className = 'audit-step ' + state;
if (state === 'active') icon.textContent = '◉';
else if (state === 'done') icon.textContent = '✓';
else if (state === 'error') icon.textContent = '✗';
else icon.textContent = '○';
}
function runAudit() {
document.getElementById('confirmModal').classList.add('active');
}
var stepTimers = [];
var allSteps = ['step-connect', 'step-pagespeed', 'step-onpage', 'step-technical', 'step-gsc', 'step-save', 'step-done'];
function clearStepTimers() {
stepTimers.forEach(function(t) { clearTimeout(t); });
stepTimers = [];
}
function confirmAudit() {
closeModal();
var btn = document.getElementById('auditBtn');
var progress = document.getElementById('auditProgress');
var result = document.getElementById('auditResult');
btn.disabled = true;
btn.textContent = 'Audyt w toku...';
progress.style.display = 'block';
progress.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
result.style.display = 'none';
// Reset all steps
allSteps.forEach(function(id) { setStep(id, ''); });
clearStepTimers();
// Simulate steps progressing while API works
// API typically takes 10-25 seconds total
var apiDone = false;
var currentStepIdx = 0;
function advanceStep() {
if (apiDone || currentStepIdx >= allSteps.length - 1) return; // don't touch 'done' step
if (currentStepIdx > 0) setStep(allSteps[currentStepIdx - 1], 'done');
setStep(allSteps[currentStepIdx], 'active');
currentStepIdx++;
}
// Start step progression
advanceStep(); // step-connect immediately
stepTimers.push(setTimeout(function() { advanceStep(); }, 3000)); // step-pagespeed at 3s
stepTimers.push(setTimeout(function() { advanceStep(); }, 8000)); // step-onpage at 8s
stepTimers.push(setTimeout(function() { advanceStep(); }, 13000)); // step-technical at 13s
stepTimers.push(setTimeout(function() { advanceStep(); }, 18000)); // step-gsc at 18s
stepTimers.push(setTimeout(function() { advanceStep(); }, 23000)); // step-save at 23s
fetch('/api/seo/audit', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-CSRFToken': csrfToken },
body: JSON.stringify({ slug: '{{ company.slug }}' })
}).then(function(r) {
return r.json();
}).then(function(data) {
apiDone = true;
clearStepTimers();
if (data.success) {
// Mark all steps as done
allSteps.forEach(function(id) { setStep(id, 'done'); });
// Show scores in result area — stays visible until user hides
var scores = data.seo_audit ? data.seo_audit.pagespeed : null;
result.style.display = 'block';
var html = '<div style="padding: var(--spacing-md); background: #f0fdf4; border-radius: var(--radius); border-left: 3px solid var(--success);">' +
'<strong style="color: var(--success);">Audyt zakonczony pomyslnie</strong>';
if (scores) {
html += '<div style="margin-top: var(--spacing-sm); font-size: var(--font-size-sm); display: flex; gap: var(--spacing-lg); flex-wrap: wrap;">' +
'<span>SEO: <strong>' + (scores.seo_score || '-') + '</strong></span>' +
'<span>Performance: <strong>' + (scores.performance_score || '-') + '</strong></span>' +
'<span>Dostepnosc: <strong>' + (scores.accessibility_score || '-') + '</strong></span>' +
'<span>Best Practices: <strong>' + (scores.best_practices_score || '-') + '</strong></span>' +
'</div>';
}
html += '<div style="margin-top: var(--spacing-md); display: flex; gap: var(--spacing-sm);">' +
'<button class="btn btn-primary btn-sm" onclick="location.reload()">Odswiez strone z nowymi danymi</button>' +
'<button class="btn btn-outline btn-sm" onclick="hideAuditProgress()">Ukryj</button>' +
'</div></div>';
result.innerHTML = html;
btn.disabled = false;
btn.textContent = 'Uruchom audyt ponownie';
} else {
// Mark failed step
for (var i = 0; i < allSteps.length; i++) {
var stepEl = document.getElementById(allSteps[i]);
if (stepEl.classList.contains('active')) {
setStep(allSteps[i], 'error');
break;
}
}
result.style.display = 'block';
result.innerHTML = '<div style="padding: var(--spacing-md); background: #fef2f2; border-radius: var(--radius); border-left: 3px solid var(--danger);">' +
'<strong style="color: var(--danger);">Blad audytu</strong>' +
'<div style="margin-top: var(--spacing-sm); font-size: var(--font-size-sm);">' + (data.error || 'Nieznany blad') + '</div>' +
'<div style="margin-top: var(--spacing-md);">' +
'<button class="btn btn-outline btn-sm" onclick="hideAuditProgress()">Ukryj</button>' +
'</div></div>';
btn.disabled = false;
btn.textContent = 'Uruchom audyt ponownie';
}
}).catch(function(e) {
apiDone = true;
clearStepTimers();
setStep('step-connect', 'error');
result.style.display = 'block';
result.innerHTML = '<div style="padding: var(--spacing-md); background: #fef2f2; border-radius: var(--radius); border-left: 3px solid var(--danger);">' +
'<strong style="color: var(--danger);">Blad polaczenia</strong>' +
'<div style="margin-top: var(--spacing-sm); font-size: var(--font-size-sm);">' + e.message + '</div>' +
'<div style="margin-top: var(--spacing-md);">' +
'<button class="btn btn-outline btn-sm" onclick="hideAuditProgress()">Ukryj</button>' +
'</div></div>';
btn.disabled = false;
btn.textContent = 'Uruchom audyt ponownie';
});
}
function hideAuditProgress() {
document.getElementById('auditProgress').style.display = 'none';
}
{% endblock %}