gwoe-antragspruefer/app/templates/v2/screens/scorecard.html
Dotty Dotter 099fbd0fb0 fix: Scorecard-Matrix — Neutral-Cells sichtbar, weisser Frame raus
Im rebalanced Render war die Matrix immer noch loechrig: r-0 (#d8d8d2)
hatte zu wenig Kontrast gegen den weissen Matrix-Frame, sodass die
neutralen Cells visuell wie Luecken wirkten.

Zwei Fixes:
- r-0 dunkler: #b8b8b2 statt #d8d8d2 (deutliche Grau-Praesenz)
- Matrix-Frame raus: kein weisser Hintergrund mehr, der Karten-Gradient
  wird durch die 4px-Gaps sichtbar — Cells stehen als klare Farbflaechen
  heraus, kein konkurrierender weisser Untergrund.

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

433 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: kraeftiger Grauton — gegen den weissen Matrix-Frame muss er
deutlich abstehen, sonst sieht das Grid loechrig aus. */
.cell.r-0 { background: #b8b8b2; color: #4a4a44; }
.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: 4px;
width: 480px;
height: 480px;
flex-shrink: 0;
/* Kein weisser Frame — der Karten-Hintergrund (Gradient) wird
durch die gap-Zwischenraeume sichtbar, die Cells stehen klar
als Farbflaechen heraus. */
}
.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>