gwoe-antragspruefer/app/templates/v2/screens/scorecard.html
Dotty Dotter 8c8dfbe625 feat: Scorecard im Instagram 4:5 Hochformat (1080×1350) — Title + Score + Matrix prominenter
User-Frage: 'Mach die Scorecard im Instagram-Format hochkant. Was genau
ist das nochmal? Vier zu fünf?' — Ja, 1080×1350 ist das Instagram-Feed-
Format mit dem groessten vertikalen Real-Estate. Stories sind 9:16,
quadratisch ist 1:1.

Neuer format=portrait (1080×1350) in /v2/scorecard und
/api/assessment/scorecard.png. Layout speziell vertikal:

- Title gross (32 pt) mit reichlich Leading
- Fraktions-Pills in eigener Zeile
- Score-Row: HUGE Zahl (110 pt) links + Empfehlung-Wort rechts,
  oben/unten zarte Trennlinien — klare Bewertungs-Anker
- Matrix 5×5 gross (380×380) mit Legende daneben — User sieht
  sofort welche Felder + - + - sind
- gwoe_begruendung als 5-zeilige Zusammenfassung darunter
- Footer am Boden

Instagram-Button im Share-Block stellt von square auf portrait um.

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

366 lines
11 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 %}38px 48px{% elif is_portrait %}54px 56px 32px{% else %}32px 36px{% 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; }
.cell.r-0 { background: #f0f0f0; color: #888; }
.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 ── */
.portrait-body {
flex: 1;
display: flex;
flex-direction: column;
gap: 22px;
margin-top: 26px;
}
.portrait-title {
font-size: 32pt;
line-height: 1.15;
color: #1f1f1f;
font-weight: 700;
}
.portrait-fraktionen {
font-family: 'Source Code Pro', monospace;
font-size: 13pt;
color: #555;
}
.portrait-fraktionen .pill {
display: inline-block;
padding: 3px 12px;
background: rgba(136, 158, 51, 0.18);
color: #44570a;
border-radius: 3px;
margin-right: 6px;
}
.portrait-score-row {
display: flex;
align-items: center;
gap: 28px;
padding: 16px 0;
border-top: 1px solid rgba(0,0,0,0.08);
border-bottom: 1px solid rgba(0,0,0,0.08);
}
.portrait-score-num {
font-family: 'Source Code Pro', monospace;
font-size: 110pt;
font-weight: 700;
line-height: 0.95;
color: {{ score_color }};
flex-shrink: 0;
}
.portrait-score-num .slash { font-size: 60pt; opacity: 0.45; }
.portrait-score-side {
display: flex;
flex-direction: column;
gap: 8px;
}
.portrait-score-label {
font-family: 'Source Code Pro', monospace;
font-size: 12pt;
text-transform: uppercase;
letter-spacing: 0.12em;
color: #555;
}
.portrait-verdict {
font-size: 24pt;
color: {{ score_color }};
font-weight: 700;
line-height: 1.15;
}
.portrait-matrix-block {
display: flex;
align-items: center;
gap: 24px;
}
.portrait-matrix {
display: grid;
grid-template-columns: repeat(5, 1fr);
grid-template-rows: repeat(5, 1fr);
gap: 4px;
width: 380px;
height: 380px;
flex-shrink: 0;
}
.portrait-matrix-legend {
flex: 1;
font-family: 'Source Code Pro', monospace;
font-size: 11pt;
color: #555;
line-height: 1.5;
}
.portrait-matrix-legend .l-title {
font-weight: 700;
text-transform: uppercase;
color: #009DA5;
margin-bottom: 8px;
letter-spacing: 0.06em;
}
.portrait-matrix-legend ul {
list-style: none;
padding: 0;
margin: 0;
}
.portrait-matrix-legend li {
display: flex;
align-items: center;
gap: 6px;
margin-bottom: 4px;
}
.portrait-matrix-legend .swatch {
width: 14px; height: 14px; border-radius: 2px; flex-shrink: 0;
}
.portrait-summary {
font-size: 14pt;
line-height: 1.55;
color: #333;
display: -webkit-box;
-webkit-line-clamp: 5;
-webkit-box-orient: vertical;
overflow: hidden;
}
/* ── Footer ── */
.footer {
position: absolute;
bottom: {% if is_og %}16px{% elif is_portrait %}24px{% else %}12px{% endif %};
left: {% if is_og %}48px{% elif is_portrait %}56px{% else %}36px{% endif %};
right: {% if is_og %}48px{% elif is_portrait %}56px{% else %}36px{% 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: 8px;
}
.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(150, 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-row">
<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">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:#f0f0f0;border:1px solid #ccc;"></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">{{ assessment.gwoe_begruendung|truncate(360, 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>