renderreport 0.2.21

Data-driven report generation with Typst as embedded render engine — no CLI dependency
Documentation
// PhaseBlock Component
// Colored phase header, bullet list, optional "+ N weitere" footer

#let phase-block(data) = {
  // Default colors per phase number (1=bad, 2=warn, 3=info, 4+ = primary)
  let default-color = if data.phase_number == 1 { color-bad }
    else if data.phase_number == 2 { color-warn }
    else if data.phase_number == 3 { color-info }
    else { color-primary }

  let accent = if data.accent_color != none { rgb(data.accent_color) } else { default-color }

  // Header stays with at least the start of the items list.
  // Note: inside content blocks (`[...]`) every function call must be `#`-prefixed,
  // otherwise it is rendered as literal text. See issue auditmysite#239.
  block(width: 100%, breakable: false, below: 0pt)[
    #block(
      width: 100%,
      fill: accent,
      radius: 8pt,
      inset: (x: spacing-4, y: spacing-3),
    )[
      #set text(fill: white)
      #grid(
        columns: (auto, 1fr),
        column-gutter: spacing-3,
        align: horizon,

        text(size: font-size-3xl, weight: "bold")[#data.phase_number],

        stack(
          spacing: spacing-2,
          text(size: font-size-lg, weight: "bold")[#data.phase_label],
          text(size: font-size-xs, fill: white.transparentize(20%))[#data.description],
        ),
      )
    ]
    #v(spacing-2)
    #box(height: 3em, width: 0pt)[]
  ]
  v(spacing-2 - 3em)

  // Items list (breakable — can span pages for long lists)
  block(
      width: 100%,
      fill: color-surface,
      stroke: (paint: color-border, thickness: component-card-border-width),
      radius: 8pt,
      inset: (x: spacing-4, y: spacing-3),
    )[
      #for (i, item) in data.items.enumerate() [
        #grid(
          columns: (10pt, 1fr),
          gutter: 4pt,
          align: (top, top),
          text(size: font-size-sm, fill: accent)[•],
          text(size: font-size-sm, fill: color-text)[#item],
        )
        #if i < data.items.len() - 1 [ #v(3pt) ]
      ]
    ]
}