diff --git a/app/main.py b/app/main.py index 3efe55d..2b86e84 100644 --- a/app/main.py +++ b/app/main.py @@ -3480,8 +3480,9 @@ async def scorecard_template( ): """Internes Render-Template für Scorecards (#179). - `format=og` → 1200×630 (LinkedIn/Twitter-OG) - `format=square` → 1080×1080 (Instagram) + `format=og` → 1200×630 (LinkedIn/Twitter-OG) + `format=square` → 1080×1080 (Instagram quadratisch) + `format=portrait` → 1080×1350 (Instagram 4:5 Hochformat — Default fuer Feed) """ from .config import settings as _settings drucksache = validate_drucksache(drucksache) @@ -3507,7 +3508,11 @@ async def scorecard_template( elif score >= 3: score_color = "#9a2a2a" else: score_color = "#9a2a2a" - dimensions = {"og": (1200, 630), "square": (1080, 1080)} + dimensions = { + "og": (1200, 630), + "square": (1080, 1080), + "portrait": (1080, 1350), + } width, height = dimensions.get(format, dimensions["og"]) return templates.TemplateResponse("v2/screens/scorecard.html", { @@ -3533,9 +3538,10 @@ async def _render_scorecard_pdf( from weasyprint import HTML, CSS from .models import Assessment - if format not in ("og", "square"): - raise HTTPException(status_code=400, detail="format muss 'og' oder 'square' sein") - width, height = ((1200, 630) if format == "og" else (1080, 1080)) + if format not in ("og", "square", "portrait"): + raise HTTPException(status_code=400, detail="format muss 'og', 'square' oder 'portrait' sein") + dimensions = {"og": (1200, 630), "square": (1080, 1080), "portrait": (1080, 1350)} + width, height = dimensions[format] drucksache = validate_drucksache(drucksache) row = await get_assessment(drucksache) diff --git a/app/templates/v2/screens/antrag_detail.html b/app/templates/v2/screens/antrag_detail.html index 0964ada..1c70003 100644 --- a/app/templates/v2/screens/antrag_detail.html +++ b/app/templates/v2/screens/antrag_detail.html @@ -942,21 +942,22 @@ window.v2ShowMatrixFieldInfo = function(field) { window.open(url, '_blank', 'noopener'); }; - /* Instagram-Sharing: oeffnet das Square-PNG (1080×1080) in neuem Tab, - legt den Begleittext in die Zwischenablage. Instagram hat keinen - Web-Share-Endpoint — User muss das Bild lokal speichern und in der - Instagram-App posten, der Text liegt dann zum Einfuegen bereit. */ + /* Instagram-Sharing: oeffnet das Hochkant-PNG (1080×1350, 4:5 — das von + Instagram empfohlene Feed-Format) in neuem Tab, legt den Begleittext + in die Zwischenablage. Instagram hat keinen Web-Share-Endpoint — + User muss das Bild lokal speichern und in der Instagram-App posten, + der Text liegt dann zum Einfuegen bereit. */ window.v2DetailShareInstagram = function() { var url = '/api/assessment/scorecard.png?drucksache=' + encodeURIComponent(DRS) + '&bundesland=' + encodeURIComponent(BL || 'NRW') - + '&format=square&scale=2'; + + '&format=portrait&scale=2'; var win = window.open(url, '_blank', 'noopener'); var body = buildLongShareText(); if (navigator.clipboard && navigator.clipboard.writeText) { navigator.clipboard.writeText(body).then(function() { - v2ShareToast('Square-Bild öffnet — Bild speichern, in Instagram posten. Text liegt in der Zwischenablage.'); + v2ShareToast('Hochkant-Bild öffnet — Bild speichern, in Instagram posten. Text liegt in der Zwischenablage.'); }, function() { - v2ShareToast('Square-Bild öffnet. Text-Kopieren manuell.'); + v2ShareToast('Hochkant-Bild öffnet. Text-Kopieren manuell.'); }); } if (!win) v2ShareToast('Bitte Pop-up-Blocker prüfen.'); diff --git a/app/templates/v2/screens/scorecard.html b/app/templates/v2/screens/scorecard.html index 3d0e08e..673d7cc 100644 --- a/app/templates/v2/screens/scorecard.html +++ b/app/templates/v2/screens/scorecard.html @@ -1,3 +1,16 @@ +{# 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) %} @@ -17,7 +30,7 @@ .card { width: 100%; height: 100%; - padding: {{ '38px 48px' if width >= 1200 else '32px 36px' }}; + 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; @@ -32,7 +45,7 @@ } .kicker { font-family: 'Source Code Pro', monospace; - font-size: 12pt; + font-size: {% if is_portrait %}13pt{% else %}12pt{% endif %}; color: #009DA5; text-transform: uppercase; letter-spacing: 0.1em; @@ -42,15 +55,17 @@ font-size: 11pt; color: #555; } + + /* ── og + square: zweispaltig (og) bzw. einspaltig (square) ── */ .body-grid { flex: 1; display: grid; - grid-template-columns: {{ '1.4fr 1fr' if width >= 1200 else '1fr' }}; + grid-template-columns: {% if is_og %}1.4fr 1fr{% else %}1fr{% endif %}; gap: 28px; margin-top: 22px; } .left-col h1 { - font-size: {{ '28pt' if width >= 1200 else '22pt' }}; + font-size: {% if is_og %}28pt{% else %}22pt{% endif %}; line-height: 1.18; margin-bottom: 14px; color: #1f1f1f; @@ -80,9 +95,8 @@ font-size: 12pt; line-height: 1.55; color: #333; - /* Clamp fuer Lang-Texte */ display: -webkit-box; - -webkit-line-clamp: {{ 6 if width >= 1200 else 4 }}; + -webkit-line-clamp: {% if is_og %}6{% else %}4{% endif %}; -webkit-box-orient: vertical; overflow: hidden; } @@ -95,7 +109,7 @@ } .score-big { font-family: 'Source Code Pro', monospace; - font-size: {{ '88pt' if width >= 1200 else '72pt' }}; + font-size: {% if is_og %}88pt{% else %}72pt{% endif %}; font-weight: 700; line-height: 1; color: {{ score_color }}; @@ -109,22 +123,15 @@ color: #555; margin-bottom: 16px; } - /* Mini-Matrix 5x5 */ - .matrix { - display: grid; - grid-template-columns: repeat(5, 1fr); - grid-template-rows: repeat(5, 1fr); - gap: 2px; - width: {{ '220px' if width >= 1200 else '160px' }}; - aspect-ratio: 1 / 1; - } + + /* ── 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: 10pt; + font-size: {% if is_portrait %}19pt{% elif is_og %}10pt{% else %}10pt{% endif %}; font-weight: 700; } .cell.r-pp { background: #889E33; color: #fff; } @@ -132,11 +139,137 @@ .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: {{ '16px' if width >= 1200 else '12px' }}; - left: {{ '48px' if width >= 1200 else '36px' }}; - right: {{ '48px' if width >= 1200 else '36px' }}; + 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; @@ -155,6 +288,47 @@
GWÖ-Bewertung · {{ bundesland }} · {{ assessment.drucksache }}
{{ datum }}
+ + {% if is_portrait %} +
+
{{ assessment.title|truncate(150, end="…") }}
+ {% if fraktionen %} +
+ Eingebracht von: + {% for f in fraktionen %}{{ f }}{% endfor %} +
+ {% endif %} +
+
{{ "%.1f"|format(assessment.gwoe_score) }}/10
+
+
Gemeinwohl-Score
+
{{ assessment.empfehlung.value }}
+
+
+
+
+ {% 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) %} +
{% if rt >= 4 %}++{% elif rt >= 1 %}+{% elif rt == 0 %}·{% elif rt <= -4 %}−−{% else %}−{% endif %}
+ {% endfor %} + {% endfor %} +
+
+
Matrix 5×5
+
    +
  • ++ stark fördernd
  • +
  • + fördernd
  • +
  • ○ neutral
  • +
  • − widersprechend
  • +
  • −− stark widersprechend
  • +
+
+
+
{{ assessment.gwoe_begruendung|truncate(360, end="…") }}
+
+ {% else %}

{{ assessment.title|truncate(100, end="…") }}

@@ -169,19 +343,19 @@
{{ "%.1f"|format(assessment.gwoe_score) }}
GWÖ-Score · 0–10
-
+
{% 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) %} -
- {% if rt >= 4 %}++{% elif rt >= 1 %}+{% elif rt == 0 %}·{% elif rt <= -4 %}−−{% else %}−{% endif %} -
+
{% if rt >= 4 %}++{% elif rt >= 1 %}+{% elif rt == 0 %}·{% elif rt <= -4 %}−−{% else %}−{% endif %}
{% endfor %} {% endfor %}
+ {% endif %} +