gwoe-antragspruefer/app/templates/v3/screens/antrag_detail.html
Dotty Dotter 895187ac36 feat(v3): Bürger:innen-Modus visuell ausgebaut — Wort-Etikett, 5-Werte-Bars, Glossar, Collapsibles
v2 unangetastet — v3 überschreibt nur Sub-Blocks. Neue v2-Blocks
für Override-Punkte: antrag_id_section, score_hero_section,
matrix_section, verbesserungen_section, aktions_section,
comments_section.

v3-Anpassungen:
1. Drucksache-ID: "Antrag im Landtag NRW · Drucksache 18/18246" —
   "Drucksache" als Glossar-Hinweis klickbar.
2. Score-Hero: großes Wort-Etikett ("Stark gemeinwohlfördernd" /
   "Gemischt" / "Widerspricht dem Gemeinwohl") aus verdict_title oder
   abgeleitet. Score-Zahl als kleiner Untertitel mit Glossar-Link.
   Akzentfarbe links (grün/blau/rot je nach Score).
3. Matrix: 5 Werte als Diverging-Bars (-5..+5) sofort sichtbar;
   volle 5×5-Matrix in <details>. Aggregation = Spalten-Mittel über
   die 5 Berührungsgruppen.
4. Verbesserungsvorschläge: <details> default zu, Hint-Text mit Anzahl.
5. Aktions-Links: JSON-Export raus — nur PDF + Permalink.
6. Kommentare: <details> default zu (für Erst-Leser:innen unwichtig).

Glossar-System: 6 Begriffe (Drucksache, Fraktion, GWÖ-Score,
GWÖ-Matrix, Heuchelei-Marker, Opportunismus-Marker) mit Klick/Focus-
Tooltip via Modal. Trigger: Element mit class="v3-glossar"
data-glossar="<key>".

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

303 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.

{# ─────────────────────────────────────────────────────────────────────
v3/screens/antrag_detail.html — Bürger:innen-Modus
Override-Strategie: extendet v2-Screen, ersetzt nur die Sub-Blocks,
die in v3 vereinfacht werden. Block-Liste:
- antrag_id_section — entschärfte Drucksache-Sprache mit Glossar
- score_hero_section — Wort-Etikett groß, Zahl klein darunter
- matrix_section — 5 Werte default + <details> für 5×5
- verbesserungen_section — default kollabiert
- aktions_section — JSON-Export raus, nur PDF + Permalink
- comments_section — default kollabiert
───────────────────────────────────────────────────────────────────── #}
{% extends "v2/screens/antrag_detail.html" %}
{% block head_extra %}
{{ super() }}
<link rel="stylesheet" href="/static/v3/v3.css?v={{ app_version|default('1') }}">
{% endblock %}
{# ─── 1. Drucksache-ID: weniger Behörden-Sprache, Glossar-Hinweise ─── #}
{% block antrag_id_section %}
<div class="v2-antrag-id v3-antrag-id">
Antrag im
{% if antrag.bundesland == "BU" %}Bundestag
{% elif antrag.bundesland %}Landtag {{ antrag.bundesland }}
{% else %}Parlament{% endif %}
{% if antrag.drucksache %}
· <span class="v3-glossar" data-glossar="drucksache" tabindex="0" role="button"
aria-label="Glossar: Drucksache">Drucksache</span>
{{ antrag.drucksache }}
{% endif %}
{% if antrag.datum %} · eingebracht {{ antrag.datum }}{% endif %}
</div>
{% endblock %}
{# ─── 2. Score-Hero: Wort-Etikett groß, Zahl klein ──────────────────── #}
{% block score_hero_section %}
{% set s = (antrag.score | default(0)) | float %}
{% if s >= 7 %}
{% set v3_label = antrag.verdict_title or "Stark gemeinwohlfördernd" %}
{% set v3_class = "good" %}
{% elif s >= 4 %}
{% set v3_label = antrag.verdict_title or "Gemischt" %}
{% set v3_class = "mid" %}
{% else %}
{% set v3_label = antrag.verdict_title or "Widerspricht dem Gemeinwohl" %}
{% set v3_class = "low" %}
{% endif %}
<div class="v3-score-hero {{ v3_class }}"
role="region" aria-label="Bewertung: {{ v3_label }} ({{ '%.1f'|format(s) }} von 10)">
<div class="v3-score-label">{{ v3_label }}</div>
{% if antrag.verdict_body %}
<div class="v3-score-body">{{ antrag.verdict_body }}</div>
{% endif %}
<div class="v3-score-meta">
<span class="v3-glossar" data-glossar="gwoe-score" tabindex="0" role="button"
aria-label="Glossar: GWÖ-Score">Gemeinwohl-Score</span>:
<strong>{{ "%.1f"|format(s) }} von 10</strong>
</div>
</div>
{% endblock %}
{# ─── 3. Matrix: 5 Werte default, volle 5×5 in <details> ─────────────── #}
{% block matrix_section %}
{% if antrag.matrix %}
<h3 class="v2-h3">Beitrag zu den 5 Gemeinwohl-Werten</h3>
{# Pro Wert (Spalte 1..5) Durchschnitt aus den 5 Berührungsgruppen-Zellen.
Skala bleibt -5..+5 wie in der DB. #}
{% set v3_werte = [
("1", "Menschenwürde"),
("2", "Solidarität"),
("3", "Ökologische Nachhaltigkeit"),
("4", "Soziale Gerechtigkeit"),
("5", "Transparenz & Demokratie"),
] %}
<div class="v3-werte-list">
{% for col, label in v3_werte %}
{% set ns = namespace(sum=0, cnt=0) %}
{% for row in ["A","B","C","D","E"] %}
{% set cell = antrag.matrix[row ~ col] | default(none) %}
{% if cell %}
{% set ns.sum = ns.sum + (cell.rating | int) %}
{% set ns.cnt = ns.cnt + 1 %}
{% endif %}
{% endfor %}
{% set avg = (ns.sum / ns.cnt) if ns.cnt else 0 %}
{% set pct = ((avg + 5) / 10 * 100) %}
<div class="v3-wert-row">
<div class="v3-wert-label">{{ label }}</div>
<div class="v3-wert-bar" aria-hidden="true">
<div class="v3-wert-track">
<div class="v3-wert-mid"></div>
<div class="v3-wert-fill {% if avg >= 1 %}pos{% elif avg <= -1 %}neg{% else %}neu{% endif %}"
style="left: {{ 'calc(50% - ' ~ ((-avg / 5 * 50)) ~ '%)' if avg < 0 else '50%' }};
width: {{ ((avg if avg > 0 else -avg) / 5 * 50) }}%;"></div>
</div>
</div>
<div class="v3-wert-num"
title="Durchschnitt der 5 Berührungsgruppen — Skala 5 bis +5">
{% if avg > 0 %}+{% endif %}{{ "%.1f"|format(avg) }}
</div>
</div>
{% endfor %}
</div>
<details class="v3-matrix-details">
<summary>
Volle <span class="v3-glossar" data-glossar="gwoe-matrix" tabindex="0" role="button"
aria-label="Glossar: GWÖ-Matrix">GWÖ-Matrix 2.0</span>
· 25 Felder (5 Werte × 5 Berührungsgruppen)
</summary>
<div style="margin-top:8px;">
{% from "v2/components/matrix_mini.html" import matrix_mini %}
{{ matrix_mini(antrag.matrix) }}
</div>
</details>
{% endif %}
{% endblock %}
{# ─── 4. Verbesserungsvorschläge: default kollabiert ──────────────────── #}
{% block verbesserungen_section %}
{% if antrag.verbesserungen %}
<details class="v3-collapsible">
<summary>
<h3 class="v2-h3">Verbesserungsvorschläge</h3>
<span class="v3-collapsible-hint">{{ antrag.verbesserungen | length }} Vorschlag{{ "" if antrag.verbesserungen | length == 1 else "e" }} · klicken zum Aufklappen</span>
</summary>
<div style="margin-top:12px;">
{% for v in antrag.verbesserungen %}
<div style="margin-bottom:16px;">
{% if antrag.verbesserungen | length > 1 %}
<div style="font-family:var(--font-mono);font-size:11px;color:var(--ecg-dark);opacity:0.65;margin-bottom:4px;">
Vorschlag {{ loop.index }} von {{ antrag.verbesserungen | length }}
</div>
{% endif %}
{% from "v2/components/redline.html" import redline %}
{% if v.segments %}
{{ redline(original=v.original | default(""), segments=v.segments) }}
{% else %}
{{ redline(original=v.original | default(""), vorschlag=v.vorschlag | default("")) }}
{% endif %}
{% if v.begruendung %}
<p style="font-size:12px;color:var(--ecg-dark);opacity:0.75;margin:4px 0 0;font-family:var(--font-mono);">
{{ v.begruendung }}
</p>
{% endif %}
</div>
{% endfor %}
</div>
</details>
{% endif %}
{% endblock %}
{# ─── 5. Aktions-Links: nur PDF + Permalink ──────────────────────────── #}
{% block aktions_section %}
<div class="v3-aktions">
<a href="/api/assessment/pdf?drucksache={{ antrag.drucksache | urlencode }}">
PDF-Bericht
</a>
<a href="/antrag/{{ antrag.drucksache }}">
Permalink
</a>
</div>
{% endblock %}
{# ─── 6. Kommentare: default kollabiert ──────────────────────────────── #}
{% block comments_section %}
<details class="v3-collapsible v3-comments">
<summary>
<h3 class="v2-h3">Kommentare</h3>
<span class="v3-collapsible-hint">klicken zum Aufklappen · Anmeldung erforderlich</span>
</summary>
<div style="margin-top:16px;">
<div id="v2-comments-list" style="margin-bottom:20px;">
<span style="font-family:var(--font-mono);font-size:12px;color:var(--ecg-dark);opacity:0.5;">Lade…</span>
</div>
<div id="v2-comment-form" style="display:none;">
<div style="font-family:var(--font-mono);font-size:10px;text-transform:uppercase;letter-spacing:0.07em;color:var(--ecg-dark);opacity:0.6;margin-bottom:8px;">Kommentar hinzufügen</div>
<textarea id="v2-comment-input"
rows="3"
placeholder="Kommentar…"
style="width:100%;box-sizing:border-box;padding:8px 10px;border:1px solid var(--hairline);border-radius:4px;font-family:var(--font-sans);font-size:13px;background:var(--surface);color:var(--ecg-dark);resize:vertical;margin-bottom:8px;outline:none;"></textarea>
<div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap;">
<select id="v2-comment-visibility"
style="padding:5px 8px;border:1px solid var(--hairline);border-radius:4px;font-family:var(--font-mono);font-size:11px;background:var(--surface);color:var(--ecg-dark);">
<option value="all">Öffentlich</option>
<option value="authenticated">Nur Angemeldete</option>
<option value="private">Nur ich</option>
</select>
<button onclick="v2DetailAddComment('{{ antrag.drucksache | e }}')"
style="padding:5px 14px;border:none;border-radius:4px;background:var(--ecg-blue);color:#fff;cursor:pointer;font-family:var(--font-mono);font-size:11px;font-weight:700;">
Absenden
</button>
</div>
</div>
<div id="v2-comment-login-hint" style="display:none;">
<button onclick="v2AuthModalOpen()"
style="padding:5px 12px;border:1px solid var(--hairline);border-radius:4px;background:none;cursor:pointer;font-family:var(--font-mono);font-size:11px;color:var(--ecg-blue);">
Anmelden um zu kommentieren
</button>
</div>
</div>
</details>
{% endblock %}
{# ─── Topbar-Pill + Toggle + Glossar-Tooltips (JS) ───────────────────── #}
{% block body_scripts %}
{{ super() }}
<script>
/* Topbar: Beta-Pill + Toggle zurueck zu Profi-Modus */
(function () {
var bar = document.querySelector('.v2-topbar');
if (!bar) return;
var pill = document.createElement('span');
pill.className = 'v3-beta-badge';
pill.textContent = 'Bürger:innen-Modus · Beta';
pill.title = 'Vereinfachte Ansicht für Erst-Leser:innen.';
var toggle = document.createElement('a');
toggle.className = 'v3-modus-toggle';
toggle.href = '/antrag/' + encodeURIComponent({{ antrag.drucksache | tojson }});
toggle.textContent = '→ Profi-Modus';
toggle.title = 'Volle GWÖ-Detailansicht (v2) öffnen';
bar.appendChild(pill);
bar.appendChild(toggle);
})();
/* Glossar-Tooltips: Klick/Focus öffnet Pop-up mit Alltags-Definition. */
window._v3Glossar = {
"drucksache": {
titel: "Drucksache",
text: "Eine Drucksache ist die offizielle Nummer eines Antrags im Parlament. Format: <Wahlperiode>/<laufende Nummer>. Beispiel: '18/18246' = 18. Wahlperiode, Nummer 18246. Über diese Nummer findest du den Antrag in den Parlamentsakten."
},
"fraktion": {
titel: "Fraktion",
text: "Eine Fraktion ist die Gruppe der Abgeordneten einer Partei im Parlament. Sie ist nicht dasselbe wie die Partei selbst — die Partei ist die Organisation außerhalb, die Fraktion sind die gewählten Abgeordneten innerhalb."
},
"gwoe-score": {
titel: "Gemeinwohl-Score (010)",
text: "Der GWÖ-Score bewertet, wie stark der Antrag dem Gemeinwohl dient — auf einer Skala von 0 bis 10. Höher = besser für die Allgemeinheit. Der Wert ist ein gewichteter Durchschnitt aus 25 Bewertungs-Feldern (5 Werte × 5 Berührungsgruppen). Mehr unter /methodik."
},
"gwoe-matrix": {
titel: "GWÖ-Matrix 2.0",
text: "Die Gemeinwohl-Matrix ist ein Bewertungsraster mit 25 Feldern: 5 Werte (Menschenwürde, Solidarität, Nachhaltigkeit, Gerechtigkeit, Transparenz) × 5 Berührungsgruppen (Lieferant:innen, Finanzen, Verwaltung, Bürger:innen, Gesellschaft & Natur). Jedes Feld bekommt eine Bewertung von 5 (stark widersprechend) bis +5 (stark fördernd)."
},
"heuchelei": {
titel: "Heuchelei-Marker (⚠)",
text: "Wird angezeigt, wenn eine Fraktion mit Nein gestimmt hat, obwohl der Antrag inhaltlich gut zu ihrem eigenen Wahlprogramm passt (Wahlprogramm-Score ≥ 7 von 10). Der Marker macht die Lücke zwischen Wahlversprechen und Abstimmungsverhalten sichtbar."
},
"opportunismus": {
titel: "Opportunismus-Marker (!)",
text: "Wird angezeigt, wenn eine Fraktion mit Ja gestimmt hat, obwohl der Antrag schlecht zum eigenen Wahlprogramm passt (Wahlprogramm-Score < 3 von 10). Mögliches Anzeichen für taktisches Stimmverhalten."
}
};
window.v3OpenGlossar = function (key) {
var data = window._v3Glossar[key];
if (!data) return;
var modal = document.getElementById('v3-glossar-modal');
if (!modal) return;
document.getElementById('v3-glossar-title').textContent = data.titel;
document.getElementById('v3-glossar-body').textContent = data.text;
modal.style.display = 'flex';
};
document.addEventListener('click', function (e) {
var t = e.target.closest('.v3-glossar');
if (t) {
e.preventDefault();
window.v3OpenGlossar(t.dataset.glossar);
}
});
document.addEventListener('keydown', function (e) {
if ((e.key === 'Enter' || e.key === ' ') && document.activeElement && document.activeElement.classList.contains('v3-glossar')) {
e.preventDefault();
window.v3OpenGlossar(document.activeElement.dataset.glossar);
}
if (e.key === 'Escape') {
var m = document.getElementById('v3-glossar-modal');
if (m && m.style.display === 'flex') { m.style.display = 'none'; }
}
});
</script>
{# Glossar-Modal #}
<div id="v3-glossar-modal" class="v3-glossar-modal" role="dialog" aria-modal="true"
aria-labelledby="v3-glossar-title"
onclick="if(event.target===this)this.style.display='none'">
<div class="v3-glossar-card">
<div class="v3-glossar-head">
<strong id="v3-glossar-title"></strong>
<button class="v3-glossar-close"
onclick="document.getElementById('v3-glossar-modal').style.display='none'"
aria-label="Schließen">×</button>
</div>
<p id="v3-glossar-body"></p>
</div>
</div>
{% endblock %}