{# Property report rendered by minijinja into Typst markup, then compiled to
   PDF by src/pdf.rs::PdfRenderer. Mirrors analytics/templates/properties/property_report.typ
   in look-and-feel: monochrome, hairline rules, tracking-letterspaced uppercase
   section headers, mono for technical strings. #}

#let dim = rgb("#555")
#let muted = rgb("#888")
#let mono = ("JetBrains Mono", "DejaVu Sans Mono", "Liberation Mono")

#set page(
  paper: "a4",
  margin: (top: 14mm, bottom: 18mm, left: 14mm, right: 14mm),
  footer: context {
    set text(size: 7.5pt, fill: dim)
    grid(
      columns: (1fr, auto),
      align: (left + horizon, right + horizon),
      [Status · self-hosted{% if base_url %} · {{ base_url | typst_md }}{% endif %} · {{ property.name | typst_md }}],
      [Page #counter(page).display() of #counter(page).final().first()],
    )
  },
)

#set text(
  font: ("DejaVu Sans", "Liberation Sans", "Arial"),
  size: 9.5pt,
  fill: black,
)
#set par(leading: 0.5em, justify: false)

#show heading.where(level: 1): set text(size: 22pt, weight: "bold")
#show heading.where(level: 1): set block(above: 8pt, below: 4pt)

#show heading.where(level: 2): it => block(
  above: 16pt,
  below: 6pt,
  width: 100%,
  stroke: (bottom: 0.6pt + black),
  inset: (bottom: 3pt),
)[#text(size: 11pt, weight: "bold", tracking: 0.6pt, upper(it.body))]

#show heading.where(level: 3): it => block(
  above: 8pt,
  below: 3pt,
)[#text(size: 8.5pt, weight: "bold", tracking: 0.5pt, fill: rgb("#333"), upper(it.body))]

// Header strip
#grid(
  columns: (1fr, auto),
  align: (left + top, right + top),
  text(size: 8.5pt, tracking: 0.9pt, fill: dim, upper("// Status · property report")),
  text(size: 8pt, fill: dim)[Generated {{ generated_at | typst_md }}],
)

= {{ property.name | typst_md }}

// Meta dl: 2-col with thin rules above and below.
#block(
  above: 8pt,
  below: 0pt,
  width: 100%,
  stroke: (top: 0.6pt + black, bottom: 0.6pt + black),
  inset: (top: 6pt, bottom: 6pt),
)[
  #grid(
    columns: (1fr, 1fr),
    column-gutter: 16pt,
    row-gutter: 4pt,
    [
      #text(size: 7.5pt, tracking: 0.4pt, fill: dim, upper("Property ID")) \
      #text(font: mono, size: 8.5pt)[{{ property.id | typst_md }}]
    ],
    [
      #text(size: 7.5pt, tracking: 0.4pt, fill: dim, upper("URL")) \
      #text(font: mono, size: 8.5pt)[{{ property.url | typst_md }}]
    ],
    [
      #text(size: 7.5pt, tracking: 0.4pt, fill: dim, upper("Alert state")) \
      #text(weight: "semibold")[{{ property.alert_state | typst_md }}]
    ],
    [
      #text(size: 7.5pt, tracking: 0.4pt, fill: dim, upper("Visibility")) \
      #text(weight: "semibold")[{% if property.is_public %}public{% else %}private{% endif %}]
    ],
  )
]

== Live signals

#grid(
  columns: (1fr, 1fr, 1fr, 1fr),
  gutter: 4pt,
  rect(width: 100%, stroke: 0.6pt + black, inset: 6pt)[
    #text(size: 7.5pt, tracking: 0.4pt, fill: dim)[STATUS]
    #v(2pt)
    #text(size: 14pt, weight: "bold")[{{ property.current_status }}]
  ],
  rect(width: 100%, stroke: 0.6pt + black, inset: 6pt)[
    #text(size: 7.5pt, tracking: 0.4pt, fill: dim)[AVG RESPONSE]
    #v(2pt)
    #text(size: 14pt, weight: "bold")[{{ property.avg_response_time }} ms]
  ],
  rect(width: 100%, stroke: 0.6pt + black, inset: 6pt)[
    #text(size: 7.5pt, tracking: 0.4pt, fill: dim)[UPTIME]
    #v(2pt)
    #text(size: 14pt, weight: "bold")[{% if property.recent_uptime_pct is none %}—{% else %}{{ property.recent_uptime_pct }}%{% endif %}]
  ],
  rect(width: 100%, stroke: 0.6pt + black, inset: 6pt)[
    #text(size: 7.5pt, tracking: 0.4pt, fill: dim)[CHECKS]
    #v(2pt)
    #text(size: 14pt, weight: "bold")[{{ property.total_checks }}]
  ],
)

== Lighthouse

{% if property.lighthouse_scores %}
#grid(
  columns: (1fr, 1fr, 1fr, 1fr),
  gutter: 4pt,
  {% for k, v in property.lighthouse_scores | items %}
  rect(width: 100%, stroke: 0.6pt + black, inset: 6pt)[
    #text(size: 7.5pt, tracking: 0.4pt, fill: dim)[{{ k | upper | typst_md }}]
    #v(2pt)
    #text(size: 14pt, weight: "bold")[{{ v }}]
  ],
  {% endfor %}
)

{% if property.lighthouse_details and property.lighthouse_details.metrics %}
=== Performance metrics

#block(breakable: false, table(
  columns: (auto, 1fr, auto),
  align: (left + top, left + top, right + top),
  inset: (x: 3pt, y: 2pt),
  stroke: (x, y) => if y == 0 { (bottom: 0.8pt + black) } else { (bottom: 0.3pt + rgb("#ddd")) },
  table.header(
    text(size: 6.5pt, tracking: 0.3pt, fill: dim, weight: "bold", upper("Metric")),
    text(size: 6.5pt, tracking: 0.3pt, fill: dim, weight: "bold", upper("Title")),
    text(size: 6.5pt, tracking: 0.3pt, fill: dim, weight: "bold", upper("Value")),
  ),
  {% for m in property.lighthouse_details.metrics %}
  text(font: mono, size: 7pt, weight: "bold")[{{ m.acronym | typst_md }}], text(size: 7.5pt)[{{ m.title | typst_md }}], text(size: 7.5pt)[{{ m.display_value | typst_md }}],
  {% endfor %}
))
{% endif %}

{% if property.lighthouse_details and property.lighthouse_details.opportunities %}
=== Top opportunities

#block(breakable: false, table(
  columns: (1fr, auto),
  align: (left + top, right + top),
  inset: (x: 3pt, y: 2pt),
  stroke: (x, y) => if y == 0 { (bottom: 0.8pt + black) } else { (bottom: 0.3pt + rgb("#ddd")) },
  table.header(
    text(size: 6.5pt, tracking: 0.3pt, fill: dim, weight: "bold", upper("Opportunity")),
    text(size: 6.5pt, tracking: 0.3pt, fill: dim, weight: "bold", upper("Savings")),
  ),
  {% for o in property.lighthouse_details.opportunities %}
  text(size: 7.5pt)[{{ o.title | typst_md }}], text(size: 7.5pt)[{{ o.savings_ms | format_ms_savings | typst_md }}],
  {% endfor %}
))
{% endif %}
{% else %}
#text(size: 8pt, fill: muted, style: "italic")[No lighthouse data yet.]
{% endif %}

== Security

#block(breakable: false, table(
  columns: (1fr, auto),
  align: (left + top, right + top),
  inset: (x: 3pt, y: 2pt),
  stroke: (x, y) => if y == 0 { (bottom: 0.8pt + black) } else { (bottom: 0.3pt + rgb("#ddd")) },
  table.header(
    text(size: 6.5pt, tracking: 0.3pt, fill: dim, weight: "bold", upper("Check")),
    text(size: 6.5pt, tracking: 0.3pt, fill: dim, weight: "bold", upper("Result")),
  ),
  text(size: 7.5pt)[HTTPS], text(size: 7.5pt)[{% if property.is_https %}OK{% else %}Issue{% endif %}],
  text(size: 7.5pt)[TLS certificate valid], text(size: 7.5pt)[{% if property.invalid_cert %}Issue{% else %}OK{% endif %}],
  text(size: 7.5pt)[HSTS (≥1y max-age)], text(size: 7.5pt)[{% if property.has_hsts %}OK{% else %}Issue{% endif %}],
  text(size: 7.5pt)[HSTS preload], text(size: 7.5pt)[{% if property.has_hsts_preload %}OK{% else %}Issue{% endif %}],
  text(size: 7.5pt)[X-Frame-Options], text(size: 7.5pt)[{% if property.has_clickjack_protection %}OK{% else %}Issue{% endif %}],
  text(size: 7.5pt)[X-Content-Type-Options], text(size: 7.5pt)[{% if property.has_content_sniffing_protection %}OK{% else %}Issue{% endif %}],
  text(size: 7.5pt)[Server header hidden], text(size: 7.5pt)[{% if property.hides_server_version %}OK{% else %}Issue{% endif %}],
))

== SEO insights

{% if insights_groups and insights_groups | length > 0 %}
{% for group in insights_groups %}
=== {{ group.type | typst_md }}

#block(breakable: false, table(
  columns: (auto, 1fr, 1fr),
  align: (left + top, left + top, left + top),
  inset: (x: 3pt, y: 2pt),
  stroke: (x, y) => if y == 0 { (bottom: 0.8pt + black) } else { (bottom: 0.3pt + rgb("#ddd")) },
  table.header(
    text(size: 6.5pt, tracking: 0.3pt, fill: dim, weight: "bold", upper("Severity")),
    text(size: 6.5pt, tracking: 0.3pt, fill: dim, weight: "bold", upper("Issue")),
    text(size: 6.5pt, tracking: 0.3pt, fill: dim, weight: "bold", upper("URL")),
  ),
  {% for i in group.items %}
  text(size: 7.5pt, weight: "bold")[{{ i.severity | typst_md }}], text(size: 7.5pt)[{{ i.issue | typst_md }}], text(font: mono, size: 7pt, fill: dim)[{{ i.url | typst_md }}],
  {% endfor %}
))

{% endfor %}
{% else %}
#text(size: 8pt, fill: muted, style: "italic")[No crawl results yet.]
{% endif %}
