feat: Scorecard Portrait redesign — Matrix mit Achsen-Labels, flex-grow gegen Slack

User-Feedback nach Browser-Inspektion: 'Bei mir sieht das immer noch
nicht so aus' — und tatsächlich, das vorherige Layout hatte zwei
sichtbare Probleme:

1. justify-content: space-between auf portrait-body verteilte den
   Slack-Raum nicht symmetrisch, sondern haeufte ihn unten zwischen
   Matrix-Block und Begruendung an. Folge: ~270 px Luecke zwischen
   diesen Sektionen.

2. Die Matrix war 'stilisiert' nur in Form (5×5 Farb-Grid) — aber
   ohne Achsen-Beschriftungen muessten Buerger:innen wissen was A1,
   B2 etc. bedeuten. Kommt nicht an.

Redesign:
- Layout-Strategie: portrait-matrix-block bekommt flex-grow:1 und
  absorbiert allen verbleibenden vertikalen Platz; Matrix bleibt
  zentriert. Andere Sektionen sitzen in natuerlicher Hoehe oben/
  unten. Kein space-between.

- Matrix stilisiert mit Achsen:
  · Spalten-Header: Wuerde / Solidaritaet / Nachhaltigkeit /
    Gerechtigkeit / Transparenz (Brand-Color, Mono-Caps)
  · Zeilen-Header: A·Lieferant:innen, B·Finanzen, C·Verwaltung,
    D·Buerger:innen, E·Gesellschaft & Natur
  · Cells in 88×88 Quadraten, gap 4 px
  · Legende horizontal unter der Matrix statt seitlich

- Begruendung: line-clamp 5, sitzt am Boden, mit Trennlinie und Sublabel.

- Cache-Control: no-store auf /v2/scorecard, damit Browser nach
  Layout-Aenderungen nicht die alte HTML-Variante zeigt.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dotty Dotter 2026-05-07 13:32:44 +02:00
parent 099fbd0fb0
commit d470e03caf
2 changed files with 90 additions and 66 deletions

View File

@ -3515,7 +3515,7 @@ async def scorecard_template(
}
width, height = dimensions.get(format, dimensions["og"])
return templates.TemplateResponse("v2/screens/scorecard.html", {
response = templates.TemplateResponse("v2/screens/scorecard.html", {
"request": request,
"assessment": assessment,
"bundesland": bundesland,
@ -3526,6 +3526,10 @@ async def scorecard_template(
"width": width,
"height": height,
})
# No-cache fuer die Live-Preview, sonst zeigt der Browser nach Layout-
# Aenderungen die alte HTML-Variante an.
response.headers["Cache-Control"] = "no-store, no-cache, must-revalidate"
return response
async def _render_scorecard_pdf(

View File

@ -154,29 +154,17 @@
/* ── 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. */
Statt justify-content: space-between (verteilt Slack-Raum unkontrol-
liert) absorbiert .portrait-matrix-block per flex-grow:1 die ueber-
schuessige Hoehe. Matrix bleibt visuell zentriert, andere Sektionen
in natuerlicher Groesse oben/unten. */
.portrait-body {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
gap: 18px;
margin-top: 18px;
padding-bottom: 28px;
gap: 14px;
margin-top: 16px;
padding-bottom: 32px;
}
.portrait-title {
@ -253,65 +241,83 @@
line-height: 1.08;
}
/* ── Matrix-Block: Grid links, Legende rechts, vertikal zentriert ── */
/* ── Matrix-Block: Grid mit beschrifteten Achsen, vertikal zentriert.
flex-grow:1 absorbiert allen verbleibenden Slack der Karte. */
.portrait-matrix-block {
flex-grow: 1;
display: flex;
flex-direction: column;
align-items: center;
gap: 28px;
justify-content: center;
gap: 12px;
}
.portrait-matrix {
.portrait-matrix-grid {
/* 5 Werte als Spalten + 1 Spalte fuer die Zeilen-Labels */
display: grid;
grid-template-columns: repeat(5, 1fr);
grid-template-rows: repeat(5, 1fr);
grid-template-columns: 130px repeat(5, 88px);
grid-template-rows: 36px repeat(5, 88px);
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. */
align-items: stretch;
}
.portrait-matrix-legend {
flex: 1;
.portrait-matrix-grid .col-label {
font-family: 'Source Code Pro', monospace;
font-size: 12pt;
color: #444;
line-height: 1.55;
}
.portrait-matrix-legend .l-title {
font-size: 9.5pt;
font-weight: 700;
text-transform: uppercase;
color: #009DA5;
margin-bottom: 14px;
letter-spacing: 0.08em;
font-size: 12pt;
text-transform: uppercase;
letter-spacing: 0.04em;
display: flex;
align-items: end;
justify-content: center;
padding-bottom: 4px;
text-align: center;
line-height: 1.1;
}
.portrait-matrix-legend ul {
list-style: none;
padding: 0;
margin: 0;
}
.portrait-matrix-legend li {
.portrait-matrix-grid .row-label {
font-family: 'Source Code Pro', monospace;
font-size: 10pt;
font-weight: 700;
color: #009DA5;
text-transform: uppercase;
letter-spacing: 0.04em;
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 7px;
padding-right: 8px;
line-height: 1.15;
}
.portrait-matrix-legend .swatch {
width: 22px; height: 22px; border-radius: 2px; flex-shrink: 0;
border: 1px solid rgba(0,0,0,0.08);
.portrait-matrix-grid .corner {
/* leere Ecke oben links */
}
.portrait-legend-row {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 8px 18px;
font-family: 'Source Code Pro', monospace;
font-size: 10.5pt;
color: #444;
margin-top: 4px;
}
.portrait-legend-row .legend-item {
display: inline-flex;
align-items: center;
gap: 6px;
}
.portrait-legend-row .swatch {
width: 18px; height: 18px; border-radius: 2px; flex-shrink: 0;
}
.portrait-summary {
font-size: 13pt;
line-height: 1.55;
line-height: 1.5;
color: #333;
display: -webkit-box;
-webkit-line-clamp: 4;
-webkit-line-clamp: 5;
-webkit-box-orient: vertical;
overflow: hidden;
border-top: 1px solid rgba(0,0,0,0.08);
padding-top: 14px;
flex-shrink: 0;
}
.portrait-summary-label {
display: block;
@ -369,8 +375,25 @@
</div>
<div class="portrait-matrix-block">
<div class="portrait-matrix matrix">
{% for r in ['A','B','C','D','E'] %}
<div class="portrait-matrix-grid matrix">
{# Top row: leere Ecke + 5 Werte-Spalten-Labels #}
<div class="corner"></div>
<div class="col-label">Würde</div>
<div class="col-label">Solidari­tät</div>
<div class="col-label">Nach­haltig­keit</div>
<div class="col-label">Gerech­tigkeit</div>
<div class="col-label">Trans­parenz</div>
{# Folgende 5 Zeilen: Beruehrungsgruppen-Label + 5 Cells #}
{% set rows = [
('A', 'Lieferant:­innen'),
('B', 'Finanzen'),
('C', 'Verwal­tung'),
('D', 'Bürger:­innen'),
('E', 'Gesell­schaft & Natur'),
] %}
{% for r, r_label in rows %}
<div class="row-label">{{ r }} · {{ r_label }}</div>
{% for c in ['1','2','3','4','5'] %}
{% set cell = matrix_lookup.get(r ~ c, {}) %}
{% set rt = cell.get('rating', 0) %}
@ -378,15 +401,12 @@
{% 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 class="portrait-legend-row">
<span class="legend-item"><span class="swatch" style="background:#889E33;"></span>++ stark fördernd</span>
<span class="legend-item"><span class="swatch" style="background:#cddaa1;"></span>+ fördernd</span>
<span class="legend-item"><span class="swatch" style="background:#d8d8d2;"></span>○ neutral</span>
<span class="legend-item"><span class="swatch" style="background:#efc9c3;"></span> widersprechend</span>
<span class="legend-item"><span class="swatch" style="background:#9A2A2A;"></span> stark widerspr.</span>
</div>
</div>