gwoe-antragspruefer/app/templates/v2/screens/scorecard.html
Dotty Dotter c6278d8453 feat: Scorecard Portrait neu komponiert — Score-Hero-Band, sichtbare Neutral-Cells, Title gekuerzt
Visuelle Probleme im vorigen Render: Title dominierte oben (5+ Zeilen,
36pt), Matrix wirkte loechrig (Neutral-Cells #f0f0f0 verschwanden im
Karten-Hintergrund), Score-Row war optisch zu zart fuer den Anker.

Neue Komposition:
- Title kompakt: 26pt, line-height 1.18, line-clamp 3 — beschreibt aber
  dominiert nicht mehr.
- Score-Hero-Block: tonierter Hintergrund passend zur Score-Farbe
  (gruen/orange/rot) plus dicker Border-left, full-width — wird zum
  visuellen Anker statt nur zwischen Trennlinien zu sitzen. Score 132pt,
  Verdict 30pt.
- Matrix: 480x480 mit weissem Frame + zarter Border, 6px gap. Neutral-
  Cells (r-0) jetzt #d8d8d2 statt #f0f0f0 → klar sichtbar im Grid,
  Loch-Look weg.
- Legende: Swatches 22px statt 18px, gleiche Sichtbarkeit wie Cells.
- Begruendung: line-clamp 4 (statt 9), eigene 'Begruendung'-Sublabel,
  obere Trennlinie — bewusst schmal als Ausklang.

Hierarchie: Score-Hero ist optisch dominant (Anker), Matrix das datenreiche
Zentrum, Title deskriptiv aber zurueckhaltend, Begruendung ergaenzt ohne
zu konkurrieren.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 13:21:03 +02:00

434 lines
13 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.

{# scorecard.html — Social-Card-Render-Template
Drei Formate, ueber `width`/`height` parametrisiert:
- 1200×630 (og) — LinkedIn/Twitter-OG; horizontal mit Matrix rechts
- 1080×1080 (square) — Instagram klassisch; vertikal gestackt
- 1080×1350 (portrait) — Instagram 4:5 Hochformat (Default Feed);
Title + grosser Score + grosse Matrix + Begruendung
`is_portrait` und `is_og` schalten Layout-Varianten in den CSS-Switches.
#}
{% set is_og = (width >= 1200) %}
{% set is_portrait = (height > width) %}
{% set is_square = (not is_og and not is_portrait) %}
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title>Scorecard — {{ assessment.title }}</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
html, body { font-family: 'Source Sans Pro', 'Helvetica Neue', sans-serif; }
body {
width: {{ width }}px;
height: {{ height }}px;
background: linear-gradient(135deg, #f8faf2 0%, #e9ede0 100%);
color: #1f1f1f;
overflow: hidden;
position: relative;
}
.card {
width: 100%;
height: 100%;
padding: {% if is_og %}28px 38px{% elif is_portrait %}34px 38px 24px{% else %}26px 30px{% endif %};
display: flex;
flex-direction: column;
justify-content: space-between;
position: relative;
}
.header {
display: flex;
justify-content: space-between;
align-items: baseline;
border-bottom: 2px solid #009DA5;
padding-bottom: 10px;
}
.kicker {
font-family: 'Source Code Pro', monospace;
font-size: {% if is_portrait %}13pt{% else %}12pt{% endif %};
color: #009DA5;
text-transform: uppercase;
letter-spacing: 0.1em;
}
.meta {
font-family: 'Source Code Pro', monospace;
font-size: 11pt;
color: #555;
}
/* ── og + square: zweispaltig (og) bzw. einspaltig (square) ── */
.body-grid {
flex: 1;
display: grid;
grid-template-columns: {% if is_og %}1.4fr 1fr{% else %}1fr{% endif %};
gap: 28px;
margin-top: 22px;
}
.left-col h1 {
font-size: {% if is_og %}28pt{% else %}22pt{% endif %};
line-height: 1.18;
margin-bottom: 14px;
color: #1f1f1f;
font-weight: 700;
}
.left-col .fraktionen {
font-family: 'Source Code Pro', monospace;
font-size: 11pt;
color: #555;
margin-bottom: 18px;
}
.left-col .fraktionen .pill {
display: inline-block;
padding: 2px 10px;
background: rgba(136, 158, 51, 0.15);
color: #44570a;
border-radius: 3px;
margin-right: 5px;
}
.left-col .verdict {
font-size: 14pt;
color: #1a7f37;
font-weight: 600;
margin-bottom: 12px;
}
.left-col .summary {
font-size: 12pt;
line-height: 1.55;
color: #333;
display: -webkit-box;
-webkit-line-clamp: {% if is_og %}6{% else %}4{% endif %};
-webkit-box-orient: vertical;
overflow: hidden;
}
.right-col {
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-start;
padding-top: 8px;
}
.score-big {
font-family: 'Source Code Pro', monospace;
font-size: {% if is_og %}88pt{% else %}72pt{% endif %};
font-weight: 700;
line-height: 1;
color: {{ score_color }};
margin-bottom: 4px;
}
.score-label {
font-family: 'Source Code Pro', monospace;
font-size: 11pt;
text-transform: uppercase;
letter-spacing: 0.1em;
color: #555;
margin-bottom: 16px;
}
/* ── Matrix-Zellen (alle Formate gleiche Klassen) ── */
.matrix .cell {
border-radius: 2px;
display: flex;
align-items: center;
justify-content: center;
font-family: 'Source Code Pro', monospace;
font-size: {% if is_portrait %}19pt{% elif is_og %}10pt{% else %}10pt{% endif %};
font-weight: 700;
}
.cell.r-pp { background: #889E33; color: #fff; }
.cell.r-p { background: #cddaa1; color: #44570a; }
/* r-0: deutlich sichtbarer Grauton, sonst verschwindet Neutral-Zelle
im hellen Karten-Hintergrund und das Grid sieht loechrig aus. */
.cell.r-0 { background: #d8d8d2; color: #6a6a64; }
.cell.r-n { background: #efc9c3; color: #931515; }
.cell.r-nn { background: #9A2A2A; color: #fff; }
/* ── og/square Matrix-Container ── */
.matrix-compact {
display: grid;
grid-template-columns: repeat(5, 1fr);
grid-template-rows: repeat(5, 1fr);
gap: 2px;
width: {% if is_og %}220px{% else %}180px{% endif %};
aspect-ratio: 1 / 1;
}
/* ── portrait Layout — ausbalancierte vertikale Komposition.
Sektions-Hierarchie (top → bottom):
1. Header-Bar (kicker + meta) ~50 px
2. Title (kompakt, 3 Zeilen max) ~150 px
3. Fraktionen-Pills ~40 px
4. Score-Hero (full-width Farbband) ~280 px
5. Matrix-Block (Grid + Legende) ~480 px
6. Begründung (4 Zeilen) ~140 px
7. Footer (absolut, ~50 px vom unteren Rand)
Summe ~1190 + Padding 58 + Gaps ~30 = 1280 / 1350 → ~70 px Slack,
gleichmaessig per justify-content space-between auf die fuenf
inneren Sektionen verteilt.
Visuelle Hierarchie: Score-Hero ist optisch prominent (groesster
Farbblock + groesste Zahl), Matrix als datenreiches Zentrum, Title
deskriptiv aber zurueckhaltend, Begruendung als Ausklang. */
.portrait-body {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
gap: 18px;
margin-top: 18px;
padding-bottom: 28px;
}
.portrait-title {
font-size: 26pt;
line-height: 1.18;
color: #1f1f1f;
font-weight: 700;
/* Begrenze Titel auf 3 Zeilen — sonst dominiert er die Karte. */
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
}
.portrait-fraktionen {
font-family: 'Source Code Pro', monospace;
font-size: 13pt;
color: #555;
}
.portrait-fraktionen .pill {
display: inline-block;
padding: 4px 14px;
background: rgba(136, 158, 51, 0.18);
color: #44570a;
border-radius: 3px;
margin-right: 6px;
font-weight: 700;
}
/* ── Score-Hero: full-width Farbband, dominanter visueller Anker ── */
.portrait-score-hero {
display: flex;
align-items: center;
gap: 32px;
padding: 22px 30px;
background:
{% if score_color == '#1a7f37' %}#e8eed1
{% elif score_color == '#bf6c10' %}#f4e6cf
{% else %}#f1dcda{% endif %};
border-left: 8px solid {{ score_color }};
border-radius: 4px;
}
.portrait-score-num {
font-family: 'Source Code Pro', monospace;
font-size: 132pt;
font-weight: 700;
line-height: 0.85;
color: {{ score_color }};
flex-shrink: 0;
letter-spacing: -0.04em;
}
.portrait-score-num .slash {
font-size: 60pt;
opacity: 0.5;
letter-spacing: 0;
}
.portrait-score-side {
display: flex;
flex-direction: column;
gap: 10px;
flex: 1;
}
.portrait-score-label {
font-family: 'Source Code Pro', monospace;
font-size: 12pt;
text-transform: uppercase;
letter-spacing: 0.14em;
color: #555;
}
.portrait-verdict {
font-size: 30pt;
color: {{ score_color }};
font-weight: 700;
line-height: 1.08;
}
/* ── Matrix-Block: Grid links, Legende rechts, vertikal zentriert ── */
.portrait-matrix-block {
display: flex;
align-items: center;
gap: 28px;
}
.portrait-matrix {
display: grid;
grid-template-columns: repeat(5, 1fr);
grid-template-rows: repeat(5, 1fr);
gap: 6px;
width: 480px;
height: 480px;
flex-shrink: 0;
padding: 8px;
background: #fff;
border: 1px solid #e0e0db;
border-radius: 6px;
}
.portrait-matrix-legend {
flex: 1;
font-family: 'Source Code Pro', monospace;
font-size: 12pt;
color: #444;
line-height: 1.55;
}
.portrait-matrix-legend .l-title {
font-weight: 700;
text-transform: uppercase;
color: #009DA5;
margin-bottom: 14px;
letter-spacing: 0.08em;
font-size: 12pt;
}
.portrait-matrix-legend ul {
list-style: none;
padding: 0;
margin: 0;
}
.portrait-matrix-legend li {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 7px;
}
.portrait-matrix-legend .swatch {
width: 22px; height: 22px; border-radius: 2px; flex-shrink: 0;
border: 1px solid rgba(0,0,0,0.08);
}
.portrait-summary {
font-size: 13pt;
line-height: 1.55;
color: #333;
display: -webkit-box;
-webkit-line-clamp: 4;
-webkit-box-orient: vertical;
overflow: hidden;
border-top: 1px solid rgba(0,0,0,0.08);
padding-top: 14px;
}
.portrait-summary-label {
display: block;
font-family: 'Source Code Pro', monospace;
font-size: 10pt;
text-transform: uppercase;
letter-spacing: 0.12em;
color: #009DA5;
font-weight: 700;
margin-bottom: 6px;
}
/* ── Footer ── */
.footer {
position: absolute;
bottom: {% if is_og %}12px{% elif is_portrait %}16px{% else %}10px{% endif %};
left: {% if is_og %}38px{% elif is_portrait %}38px{% else %}30px{% endif %};
right: {% if is_og %}38px{% elif is_portrait %}38px{% else %}30px{% endif %};
display: flex;
justify-content: space-between;
align-items: center;
font-family: 'Source Code Pro', monospace;
font-size: 9pt;
color: #888;
border-top: 1px solid rgba(0,0,0,0.1);
padding-top: 6px;
}
.footer .brand { color: #009DA5; font-weight: 700; letter-spacing: 0.06em; }
</style>
</head>
<body>
<div class="card">
<div class="header">
<div class="kicker">GWÖ-Bewertung · {{ bundesland }} · {{ assessment.drucksache }}</div>
<div class="meta">{{ datum }}</div>
</div>
{% if is_portrait %}
<div class="portrait-body">
<div class="portrait-title">{{ assessment.title|truncate(160, end="…") }}</div>
{% if fraktionen %}
<div class="portrait-fraktionen">
Eingebracht von
{% for f in fraktionen %}<span class="pill">{{ f }}</span>{% endfor %}
</div>
{% endif %}
<div class="portrait-score-hero">
<div class="portrait-score-num">{{ "%.1f"|format(assessment.gwoe_score) }}<span class="slash">/10</span></div>
<div class="portrait-score-side">
<div class="portrait-score-label">Gemeinwohl-Score</div>
<div class="portrait-verdict">{{ assessment.empfehlung.value }}</div>
</div>
</div>
<div class="portrait-matrix-block">
<div class="portrait-matrix matrix">
{% for r in ['A','B','C','D','E'] %}
{% for c in ['1','2','3','4','5'] %}
{% set cell = matrix_lookup.get(r ~ c, {}) %}
{% set rt = cell.get('rating', 0) %}
<div class="cell {% if rt >= 4 %}r-pp{% elif rt >= 1 %}r-p{% elif rt == 0 %}r-0{% elif rt <= -4 %}r-nn{% else %}r-n{% endif %}">{% if rt >= 4 %}++{% elif rt >= 1 %}+{% elif rt == 0 %}·{% elif rt <= -4 %}{% else %}{% endif %}</div>
{% endfor %}
{% endfor %}
</div>
<div class="portrait-matrix-legend">
<div class="l-title">GWÖ-Matrix 5×5</div>
<ul>
<li><span class="swatch" style="background:#889E33;"></span>++ stark fördernd</li>
<li><span class="swatch" style="background:#cddaa1;"></span>+ fördernd</li>
<li><span class="swatch" style="background:#d8d8d2;"></span>○ neutral</li>
<li><span class="swatch" style="background:#efc9c3;"></span> widersprechend</li>
<li><span class="swatch" style="background:#9A2A2A;"></span> stark widersprechend</li>
</ul>
</div>
</div>
<div class="portrait-summary">
<span class="portrait-summary-label">Begründung</span>
{{ assessment.gwoe_begruendung|truncate(440, end="…") }}
</div>
</div>
{% else %}
<div class="body-grid">
<div class="left-col">
<h1>{{ assessment.title|truncate(100, end="…") }}</h1>
{% if fraktionen %}
<div class="fraktionen">
{% for f in fraktionen %}<span class="pill">{{ f }}</span>{% endfor %}
</div>
{% endif %}
<div class="verdict">{{ assessment.empfehlung.value }}</div>
<div class="summary">{{ assessment.gwoe_begruendung|truncate(420, end="…") }}</div>
</div>
<div class="right-col">
<div class="score-big">{{ "%.1f"|format(assessment.gwoe_score) }}</div>
<div class="score-label">GWÖ-Score · 010</div>
<div class="matrix-compact matrix">
{% for r in ['A','B','C','D','E'] %}
{% for c in ['1','2','3','4','5'] %}
{% set cell = matrix_lookup.get(r ~ c, {}) %}
{% set rt = cell.get('rating', 0) %}
<div class="cell {% if rt >= 4 %}r-pp{% elif rt >= 1 %}r-p{% elif rt == 0 %}r-0{% elif rt <= -4 %}r-nn{% else %}r-n{% endif %}">{% if rt >= 4 %}++{% elif rt >= 1 %}+{% elif rt == 0 %}·{% elif rt <= -4 %}{% else %}{% endif %}</div>
{% endfor %}
{% endfor %}
</div>
</div>
</div>
{% endif %}
<div class="footer">
<span><span class="brand">gwoe.toppyr.de</span> · automatische Gemeinwohl-Bilanzierung</span>
<span>{{ assessment.drucksache }}</span>
</div>
</div>
</body>
</html>