{"id":18885,"date":"2024-05-20T17:48:30","date_gmt":"2024-05-20T15:48:30","guid":{"rendered":"https:\/\/johanneum-lueneburg.de\/wordpress\/?page_id=18885"},"modified":"2026-04-03T13:18:30","modified_gmt":"2026-04-03T11:18:30","slug":"abiturrechner","status":"publish","type":"page","link":"https:\/\/johanneum-lueneburg.de\/wordpress\/abiturrechner\/","title":{"rendered":"Abiturrechner"},"content":{"rendered":"<section class=\"l-section wpb_row height_auto\"><div class=\"l-section-h i-cf\"><div class=\"g-cols vc_row via_flex valign_top type_default stacking_default\"><div class=\"vc_col-sm-12 wpb_column vc_column_container\"><div class=\"vc_column-inner\"><div class=\"wpb_wrapper\"><div class=\"w-html\"><!doctype html>\n<html lang=\"de\">\n<head>\n  <meta charset=\"utf-8\" \/>\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" \/>\n  <title>Abiturrechner \u2013 Gymnasium Johanneum L\u00fcneburg<\/title>\n  <meta name=\"theme-color\" content=\"#ffffff\" \/>\n  <style>\n    :root{\n      --bg: #ffffff;\n      --fg: #111827;\n      --muted: #6b7280;\n      --line: #e5e7eb;\n      --card: #ffffff;\n      --shadow: 0 8px 24px rgba(0,0,0,.08);\n      --good: #4CAF50;\n      --bad: #F44336;\n      --warn: #b45309;\n      --neutral: #374151;\n      --info: #60a5fa;\n      --chip: #111827;\n      --chipText: #ffffff;\n      --focus: #2563eb;\n      --radius: 12px;\n      --font: system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,Cantarell,Noto Sans,Arial,\"Apple Color Emoji\",\"Segoe UI Emoji\";\n    }\n\n    *{ box-sizing: border-box; }\n\n    body{\n      margin: 0;\n      font-family: var(--font);\n      color: var(--fg);\n      background: var(--bg);\n    }\n\n    .wrap{\n      max-width: 640px;\n      margin: 28px auto 60px;\n      padding: 0 16px;\n    }\n\n    header{\n      text-align: center;\n      margin-bottom: 16px;\n    }\n\n    h1{\n      margin: 0;\n      font-size: clamp(26px, 3.2vw, 40px);\n      letter-spacing: .2px;\n    }\n\n    .subtitle{\n      margin-top: 4px;\n      color: var(--muted);\n      font-size: 16px;\n    }\n\n    .meta{\n      margin-top: 10px;\n      color: var(--muted);\n      font-size: 13px;\n    }\n\n    .toolbar{\n      display: flex;\n      gap: 10px;\n      justify-content: center;\n      flex-wrap: wrap;\n      margin: 18px 0 18px;\n    }\n\n    button{\n      border: 1px solid var(--line);\n      background: #f9fafb;\n      padding: 10px 14px;\n      border-radius: 10px;\n      cursor: pointer;\n      font-weight: 600;\n      font-size: 14px;\n    }\n\n    button:hover{ background: #f3f4f6; }\n    button:active{ transform: translateY(1px); }\n\n    \/* --- Save-Panel (inline, unterhalb Toolbar) --- *\/\n    .save-panel{\n      display: none;\n      margin: 0 0 14px;\n      padding: 14px 16px;\n      border: 1px solid var(--line);\n      border-radius: var(--radius);\n      background: #f9fafb;\n    }\n\n    .save-panel.open{ display: block; }\n\n    .save-panel-row{\n      display: flex;\n      gap: 8px;\n      align-items: center;\n    }\n\n    .save-panel-row input[type=\"text\"]{\n      flex: 1;\n      border: 1px solid var(--line);\n      border-radius: 10px;\n      padding: 8px 12px;\n      font-size: 14px;\n      outline: none;\n      height: 38px;\n    }\n\n    .save-panel-row input[type=\"text\"]:focus{\n      border-color: var(--focus);\n      box-shadow: 0 0 0 3px rgba(37,99,235,.15);\n    }\n\n    .save-panel-row button{\n      white-space: nowrap;\n      height: 38px;\n      padding: 0 16px;\n    }\n\n    .save-panel .save-hint{\n      margin-top: 6px;\n      font-size: 12px;\n      color: var(--muted);\n    }\n\n    \/* --- Manage-Panel (inline, unterhalb Toolbar) --- *\/\n    .manage-panel{\n      display: none;\n      margin: 0 0 14px;\n    }\n\n    .manage-panel.open{ display: block; }\n\n    .manage-list{\n      border: 1px solid var(--line);\n      border-radius: var(--radius);\n      overflow: hidden;\n      background: var(--card);\n      box-shadow: var(--shadow);\n    }\n\n    .manage-empty{\n      padding: 18px 16px;\n      text-align: center;\n      color: var(--muted);\n      font-size: 14px;\n    }\n\n    .manage-item{\n      display: flex;\n      align-items: center;\n      gap: 10px;\n      padding: 12px 14px;\n      border-bottom: 1px solid var(--line);\n    }\n\n    .manage-item:last-child{ border-bottom: 0; }\n\n    .manage-item-info{\n      flex: 1;\n      min-width: 0;\n    }\n\n    .manage-item-name{\n      font-weight: 700;\n      font-size: 15px;\n      white-space: nowrap;\n      overflow: hidden;\n      text-overflow: ellipsis;\n    }\n\n    .manage-item-date{\n      font-size: 12px;\n      color: var(--muted);\n      margin-top: 2px;\n    }\n\n    .manage-item-actions{\n      display: flex;\n      gap: 6px;\n      flex-shrink: 0;\n    }\n\n    .manage-item-actions button{\n      padding: 6px 12px;\n      font-size: 13px;\n      border-radius: 8px;\n    }\n\n    .btn-load{\n      background: #e0f2fe;\n      border-color: #bae6fd;\n      color: #0c4a6e;\n    }\n\n    .btn-load:hover{ background: #bae6fd; }\n\n    .btn-rename{\n      background: #ecfdf5;\n      border-color: #a7f3d0;\n      color: #065f46;\n    }\n\n    .btn-rename:hover{ background: #a7f3d0; }\n\n\n    .btn-delete{\n      background: #fef2f2;\n      border-color: #fecaca;\n      color: #991b1b;\n    }\n\n    .btn-delete:hover{ background: #fecaca; }\n\n    .manage-footer{\n      text-align: center;\n      padding: 10px 0 4px;\n    }\n\n    .manage-footer button{\n      border: none;\n      background: transparent;\n      color: var(--muted);\n      font-size: 12px;\n      text-decoration: underline;\n      padding: 4px 8px;\n      cursor: pointer;\n    }\n\n    .manage-footer button:hover{ color: var(--bad); background: transparent; }\n\n    \/* Overwrite-Confirm *\/\n    .overwrite-confirm{\n      display: none;\n      margin-top: 8px;\n      padding: 10px 14px;\n      border-radius: 10px;\n      background: #fffbeb;\n      border: 1px solid #fde68a;\n      font-size: 13px;\n    }\n\n    .overwrite-confirm.show{ display: block; }\n\n    .overwrite-confirm-actions{\n      display: flex;\n      gap: 8px;\n      margin-top: 8px;\n    }\n\n    .overwrite-confirm-actions button{\n      font-size: 13px;\n      padding: 5px 14px;\n    }\n\n    .card{\n      background: var(--card);\n      border: 1px solid var(--line);\n      border-radius: var(--radius);\n      box-shadow: var(--shadow);\n      overflow: hidden;\n    }\n\n    .grid{\n      display: grid;\n      grid-template-columns: minmax(200px, 2fr) minmax(90px, 1fr) minmax(90px, 1fr) minmax(90px, 1fr) minmax(65px, 1fr);\n      align-items: stretch;\n    }\n\n    .grid .head{\n      background: #f9fafb;\n      font-weight: 700;\n      border-bottom: 1px solid var(--line);\n    }\n\n    .cell{\n      padding: 8px 10px;\n      border-bottom: 1px solid var(--line);\n      min-height: 48px;\n      display: flex;\n      align-items: center;\n      gap: 8px;\n    }\n\n    .grid .cell:nth-child(5n){ justify-content: center; }\n\n    .grid .cell:nth-child(5n-1){ justify-content: center; }\n\n    .grid .cell:nth-child(5n-2),\n    .grid .cell:nth-child(5n-3){ justify-content: center; }\n\n    .grid .cell:nth-child(5n-4){ justify-content: flex-start; }\n\n    .grid .row:last-child .cell{ border-bottom: 0; }\n\n    .head .cell{ min-height: 44px; }\n\n    .field-label{\n      font-weight: 700;\n      font-size: 16px;\n      margin: 0;\n      white-space: nowrap;\n    }\n\n    .p-tag{\n      display: inline-flex;\n      align-items: center;\n      justify-content: center;\n      min-width: 34px;\n      padding: 6px 8px;\n      border-radius: 999px;\n      background: #e0f2fe;\n      border: 1px solid #bae6fd;\n      color: #0f172a;\n      font-weight: 800;\n      font-size: 13px;\n      line-height: 1;\n      flex: 0 0 auto;\n    }\n\n    label.visually-hidden{\n      position: absolute;\n      width: 1px;\n      height: 1px;\n      padding: 0;\n      margin: -1px;\n      overflow: hidden;\n      clip: rect(0, 0, 0, 0);\n      white-space: nowrap;\n      border: 0;\n    }\n\n    input[type=\"text\"], input[type=\"number\"]{\n      width: 100%;\n      border: 1px solid var(--line);\n      border-radius: 10px;\n      padding: 6px 10px;\n      height: 34px;\n      line-height: 1.1;\n      font-size: 14px;\n      outline: none;\n      background: #ffffff;\n      -webkit-appearance: none;\n      appearance: none;\n    }\n\n    input[type=\"number\"]{\n      text-align: center;\n      max-width: 110px;\n    }\n\n    #PB1{\n      max-width: 105px;\n      height: 30px;\n      padding: 4px 10px;\n      line-height: 1.05;\n    }\n\n    .pb1-inline{\n      display: flex;\n      align-items: center;\n      justify-content: center;\n      gap: 10px;\n      flex-wrap: wrap;\n    }\n\n    .pb1-meta{\n      color: var(--muted);\n      font-size: 13px;\n      font-weight: 700;\n      white-space: nowrap;\n    }\n\n    input:focus{\n      border-color: var(--focus);\n      box-shadow: 0 0 0 3px rgba(37,99,235,.15);\n    }\n\n    input:invalid{\n      border-color: var(--bad);\n    }\n\n    .hint{\n      margin-top: 6px;\n      font-size: 12px;\n      color: var(--muted);\n    }\n\n    .error{\n      margin-top: 6px;\n      font-size: 12px;\n      color: var(--bad);\n      display: none;\n    }\n\n    .error.show{ display: block; }\n\n    .points{\n      display: inline-flex;\n      align-items: center;\n      justify-content: center;\n      min-width: 56px;\n      padding: 6px 10px;\n      border-radius: 10px;\n      color: var(--chipText);\n      background: var(--neutral);\n      font-weight: 800;\n      font-variant-numeric: tabular-nums;\n    }\n\n    .points.good{ background: var(--good); }\n    .points.bad{ background: var(--bad); }\n\n    .points.blank{\n      background: transparent;\n      color: transparent;\n      padding: 0;\n      min-width: 0;\n    }\n\n    .diff{\n      font-weight: 700;\n      font-variant-numeric: tabular-nums;\n    }\n\n    .diff.good{ color: var(--good); }\n    .diff.bad{ color: var(--bad); }\n\n    .summary{\n      display: flex;\n      gap: 16px;\n      justify-content: center;\n      flex-wrap: wrap;\n      margin: 18px 0 14px;\n    }\n\n    .chip{\n      display: inline-flex;\n      align-items: center;\n      gap: 8px;\n      padding: 10px 12px;\n      border: 1px solid var(--line);\n      border-radius: 999px;\n      background: #f9fafb;\n      font-weight: 700;\n    }\n\n    .chip .value{\n      display: inline-flex;\n      align-items: center;\n      justify-content: center;\n      min-width: 34px;\n      padding: 6px 10px;\n      border-radius: 999px;\n      color: var(--chipText);\n      background: var(--neutral);\n      font-variant-numeric: tabular-nums;\n    }\n\n    .value.good{ background: var(--good); }\n    .value.bad{ background: var(--bad); }\n\n    .divider{\n      height: 1px;\n      background: var(--line);\n      margin: 18px 0;\n    }\n\n    .final{\n      text-align: center;\n      padding: 18px 16px;\n    }\n\n    .status{\n      font-size: 22px;\n      font-weight: 900;\n      margin-bottom: 10px;\n    }\n\n    .status .badge{\n      display: inline-flex;\n      align-items: center;\n      justify-content: center;\n      width: 34px;\n      height: 34px;\n      border-radius: 10px;\n      margin-left: 10px;\n      color: #ffffff;\n      font-weight: 900;\n    }\n\n    .badge.good{ background: var(--good); }\n    .badge.bad{ background: var(--bad); }\n    .badge.neutral{ background: var(--info); }\n\n    .status.fhrline{\n      margin: 0;\n    }\n\n    .final-grid{\n      display: grid;\n      gap: 12px;\n      justify-content: center;\n      margin-top: 10px;\n    }\n\n    .final-row{\n      font-size: 18px;\n      font-weight: 700;\n    }\n\n    .final-row strong{\n      font-weight: 800;\n    }\n\n    .note{\n      font-weight: 700;\n    }\n\n    #abiturNote{\n      font-weight: 800;\n    }\n\n    #fhrNoteValue{\n      font-weight: 800;\n    }\n\n    .fhr{ display: none; }\n\n    .toast{\n      position: fixed;\n      left: 50%;\n      bottom: 18px;\n      transform: translateX(-50%);\n      background: rgba(17,24,39,.92);\n      color: #ffffff;\n      padding: 10px 14px;\n      border-radius: 999px;\n      font-weight: 700;\n      opacity: 0;\n      pointer-events: none;\n      transition: opacity .18s ease-in-out;\n    }\n\n    .toast.show{ opacity: 1; }\n\n    @media (max-width: 900px){\n      .grid{\n        grid-template-columns: minmax(120px, 2fr) minmax(74px, 1fr) minmax(74px, 1fr) minmax(82px, 1fr) minmax(58px, 1fr);\n        column-gap: 0;\n      }\n      input[type=\"text\"], input[type=\"number\"]{ padding: 5px 8px; height: 32px; line-height: 1.1; }\n      input[type=\"number\"]{ max-width: 84px; }\n      .cell{ padding: 8px 8px; min-height: 44px; }\n      .pb1-meta{ font-size: 13px; }\n    }\n\n    @media (max-width: 560px){\n      #PB1{ height: 28px; padding: 3px 8px; }\n      .cell{ min-height: 38px; }\n      .head .cell{ min-height: 36px; }\n      input[type=\"text\"], input[type=\"number\"]{ padding: 4px 8px; height: 30px; line-height: 1.05; }\n\n      .grid{\n        grid-template-columns: minmax(86px, 2fr) minmax(62px, 1fr) minmax(62px, 1fr) minmax(68px, 1fr) minmax(46px, 1fr);\n      }\n      input[type=\"number\"]{ max-width: 72px; }\n      .cell{ padding: 6px 6px; }\n      .subtitle{ font-size: 14px; }\n      .meta{ font-size: 12px; }\n      .pb1-meta{ font-size: 12px; }\n      .field-label{ font-size: 15px; }\n\n      .manage-item{ padding: 10px 10px; gap: 8px; }\n      .manage-item-name{ font-size: 14px; }\n      .manage-item-actions button{ padding: 5px 10px; font-size: 12px; }\n    }\n\n    @media print{\n      .toolbar, .toast, .save-panel, .manage-panel{ display: none !important; }\n      .card{ box-shadow: none; }\n    }\n  <\/style>\n<\/head>\n<body>\n  <div class=\"wrap\">\n    <header>\n      <h1>Abiturrechner<\/h1>\n      <div class=\"subtitle\">Gymnasium Johanneum L\u00fcneburg<\/div>\n      <div class=\"meta\">Version 4.12 (03.04.2026, F\u00fc)<\/div>\n    <\/header>\n\n    <div class=\"toolbar\">\n      <button type=\"button\" id=\"btnReset\">Reset<\/button>\n      <button type=\"button\" id=\"btnSave\">Speichern<\/button>\n      <button type=\"button\" id=\"btnManage\">Verwalten (Laden \/ L\u00f6schen)<\/button>\n    <\/div>\n\n    <!-- Inline Save-Panel -->\n    <div id=\"savePanel\" class=\"save-panel\">\n      <div class=\"save-panel-row\">\n        <input type=\"text\" id=\"saveName\" placeholder=\"Name f\u00fcr diesen Zustand\u2026\" maxlength=\"60\" autocomplete=\"off\" \/>\n        <button type=\"button\" id=\"btnSaveConfirm\">OK<\/button>\n        <button type=\"button\" id=\"btnSaveCancel\">Abbrechen<\/button>\n      <\/div>\n      <div id=\"overwriteConfirm\" class=\"overwrite-confirm\">\n        <span>Ein Zustand mit diesem Namen existiert bereits. \u00dcberschreiben?<\/span>\n        <div class=\"overwrite-confirm-actions\">\n          <button type=\"button\" id=\"btnOverwriteYes\">Ja, \u00fcberschreiben<\/button>\n          <button type=\"button\" id=\"btnOverwriteNo\">Nein<\/button>\n        <\/div>\n      <\/div>\n    <\/div>\n\n    <!-- Inline Manage-Panel -->\n    <div id=\"managePanel\" class=\"manage-panel\">\n      <div id=\"manageList\" class=\"manage-list\"><\/div>\n      <div id=\"manageFooter\" class=\"manage-footer\" style=\"display:none;\">\n        <button type=\"button\" id=\"btnClearAll\">Alle Zust\u00e4nde l\u00f6schen<\/button>\n      <\/div>\n    <\/div>\n\n    <div class=\"card\" aria-label=\"Eingabe der P-Ergebnisse\">\n      <div class=\"grid head\" role=\"rowgroup\">\n        <div class=\"cell\" role=\"columnheader\">P-Fach<\/div>\n        <div class=\"cell\" role=\"columnheader\" style=\"justify-content:center;\">schr.<\/div>\n        <div class=\"cell\" role=\"columnheader\" style=\"justify-content:center;\">m\u00fcndl.<\/div>\n        <div class=\"cell\" role=\"columnheader\" style=\"justify-content:center;\">Punkte<\/div>\n        <div class=\"cell\" role=\"columnheader\" style=\"justify-content:center;\">Diff.<\/div>\n      <\/div>\n\n      <div id=\"rows\" role=\"rowgroup\"><\/div>\n    <\/div>\n\n    <div class=\"summary\" aria-label=\"Zwischenauswertung\">\n      <div class=\"chip\">Anz. Unterkurse = <span id=\"underCourses\" class=\"value\">0<\/span><\/div>\n      <div class=\"chip\">Abitur-Punkte = <span id=\"totalPoints\" class=\"value\">0<\/span><\/div>\n    <\/div>\n\n    <div class=\"divider\" role=\"separator\"><\/div>\n\n    <div class=\"card final\" aria-label=\"Gesamtauswertung\">\n      <div id=\"abiturStatus\" class=\"status\">Abitur-Pr\u00fcfung <span class=\"badge neutral\" aria-hidden=\"true\">?<\/span><\/div>\n\n      <div style=\"max-width: 640px; margin: 0 auto; text-align: left;\">\n        <div class=\"pb1-inline\">\n          <label for=\"PB1\" class=\"field-label\">Punkte aus Block I =<\/label>\n          <input type=\"number\" id=\"PB1\" min=\"200\" max=\"600\" step=\"1\" inputmode=\"numeric\" placeholder=\"200\u2013600\" \/>\n          <span id=\"PB1Meta\" class=\"pb1-meta\" aria-live=\"polite\" style=\"display:none;\"><\/span>\n        <\/div>\n\n        <div id=\"PB1Error\" class=\"error\">Bitte eine Zahl zwischen 200 und 600 eingeben.<\/div>\n      <\/div>\n\n      <div class=\"final-grid\">\n        <div class=\"final-row\">Gesamtpunkte = <strong id=\"finalPoints\">Block-I-Punkte fehlen<\/strong><\/div>\n        <div class=\"final-row\"><span class=\"note\">Abitur-Note = <span id=\"abiturNote\">Block-I-Punkte fehlen<\/span><\/span><\/div>\n        <div id=\"fhrStatusDisplay\" class=\"status fhr fhrline\"><\/div>\n        <div id=\"fhrNoteDisplay\" class=\"final-row fhr\">\n          <span class=\"note\">FHR-Note = <span id=\"fhrNoteValue\"><\/span><\/span>\n        <\/div>\n      <\/div>\n    <\/div>\n  <\/div>\n\n  <div id=\"toast\" class=\"toast\" role=\"status\" aria-live=\"polite\"><\/div>\n\n  <script>\n    'use strict';\n\n    \/\/ =========================================================\n    \/\/ Abiturrechner \u2013 Version 4.00\n    \/\/ Regeln: \u00fcbernommen aus V1.0; robuste Validierung erg\u00e4nzt.\n    \/\/\n    \/\/ Changelog (bitte fortschreiben, bestehende Eintr\u00e4ge bleiben erhalten):\n    \/\/ - 4.12 (03.04.2026): schr.\/m\u00fcndl.-Felder: importierte Dezimalwerte (z.B. \"8.0\") werden\n    \/\/       beim Laden\/URL-Bef\u00fcllen auf ganze Zahlen normalisiert.\n    \/\/ - 4.11 (28.02.2026): Manage-Panel: Zust\u00e4nde k\u00f6nnen umbenannt werden.\n    \/\/ - 4.10 (15.02.2026): FHR-Anzeige: wird jetzt auch bei offenem Abitur-Status eingeblendet,\n    \/\/       sofern FHRStatus per URL (1=erreicht, 2=nicht erreicht) mitgegeben wurde.\n    \/\/       Bei bestandenem Abitur wird die FHR ausgeblendet (verf\u00e4llt).\n    \/\/ - 4.01 (15.02.2026): Einzell\u00f6schung mit Best\u00e4tigungsdialog;\n    \/\/       Hinweis \"(P-Ergebnisse fehlen)\" neben dem blauen Fragezeichen bei neutralem Status.\n    \/\/ - 4.00 (15.02.2026): Mehrfachspeicher:\n    \/\/       Toolbar vereinfacht auf \"Reset\", \"Speichern\", \"Verwalten (Laden \/ L\u00f6schen)\".\n    \/\/       Zust\u00e4nde werden mit frei w\u00e4hlbarem Namen gespeichert.\n    \/\/       Inline-Panel zum Speichern (mit \u00dcberschreib-R\u00fcckfrage) und\n    \/\/       Inline-Panel zum Verwalten (Laden \/ einzeln L\u00f6schen \/ alle L\u00f6schen).\n    \/\/       Datenformat: Array von benannten Zust\u00e4nden in localStorage.\n    \/\/       Migration: vorhandener V3-Einzelzustand wird automatisch \u00fcbernommen.\n    \/\/ - 3.09 (18.01.2026): Notenanzeige: Hinweis \"+\u2026 Punkte f\u00fcr bessere Note\" wieder aktiv (au\u00dfer bei Note 1,0 und besser).\n    \/\/ - 3.08 (18.01.2026): Block I:\n    \/\/       (1) \"Punkte aus Block I =\" + Eingabefeld + Zusatzanzeige in einer Zeile (vertikal zentriert).\n    \/\/       (2) PB1-Feld nochmals ca. 20% schmaler.\n    \/\/       (3) Zusatzanzeige: \u00d8 = B\/40 mit 2 Nachkommastellen; Notenanzeige:\n    \/\/           - wenn Note >= 1,0: \"(\u00d8 ... | Note x,x)\"\n    \/\/           - wenn Note < 1,0:  \"(\u00d8 ... | rechn. Note x,x)\" (ohne Deckelung).\n    \/\/       Abitur-Note:\n    \/\/       (4) Noten < 1,0 werden auf 1,0 gedeckelt und als \"1,0 (rechnerisch 0,8)\" angezeigt.\n    \/\/       (5) Hinweis \"+... Punkte f\u00fcr bessere Note\" entf\u00e4llt vollst\u00e4ndig.\n    \/\/ - 3.07 (18.01.2026): Neben \"Punkte aus Block I\" zus\u00e4tzliche Anzeige (Variante A):\n    \/\/       (\u00d8 <B\/40, 2 Nachkommastellen> | Note <Block-I-Note, 1 Nachkommastelle>).\n    \/\/ - 3.06 (17.01.2026): Ausgangsversion (Stand vor Erg\u00e4nzung der Block-I-Zusatzanzeige).\n    \/\/ - 3.00: Grundaufbau, Berechnungslogik, Validierung, lokales Speichern\/Laden.\n    \/\/ =========================================================\n\n    const CONFIG = Object.freeze({\n      rows: 5,\n      minGradePoints: 0,\n      maxGradePoints: 15,\n      blockI: { min: 200, max: 600 },\n      pass: { minTotalExamPoints: 100, minResultsAtLeast20: 3 },\n    });\n\n    let FHRNote = 0;\n    let FHRStatus = 0;\n\n    const elRows = document.getElementById('rows');\n    const elUnder = document.getElementById('underCourses');\n    const elTotal = document.getElementById('totalPoints');\n    const elStatus = document.getElementById('abiturStatus');\n    const elPB1 = document.getElementById('PB1');\n    const elPB1Error = document.getElementById('PB1Error');\n    const elPB1Meta = document.getElementById('PB1Meta');\n    const elFinalPoints = document.getElementById('finalPoints');\n    const elAbiturNote = document.getElementById('abiturNote');\n    const elFhrStatus = document.getElementById('fhrStatusDisplay');\n    const elFhrWrap = document.getElementById('fhrNoteDisplay');\n    const elFhrVal = document.getElementById('fhrNoteValue');\n    const elToast = document.getElementById('toast');\n\n    \/\/ Save\/Manage UI\n    const elSavePanel = document.getElementById('savePanel');\n    const elSaveName = document.getElementById('saveName');\n    const elOverwriteConfirm = document.getElementById('overwriteConfirm');\n    const elManagePanel = document.getElementById('managePanel');\n    const elManageList = document.getElementById('manageList');\n    const elManageFooter = document.getElementById('manageFooter');\n\n    \/\/ =========================================================\n    \/\/ Multi-State Storage (V4)\n    \/\/ =========================================================\n    const STORAGE_KEY = 'abiturrechner_states_v4';\n    const LEGACY_KEY = 'abiturrechner_state_v3';\n\n    function storageAvailable(){\n      try{\n        const k = '__abiturrechner_test__';\n        window.localStorage.setItem(k, '1');\n        window.localStorage.removeItem(k);\n        return true;\n      } catch {\n        return false;\n      }\n    }\n\n    function loadAllStates(){\n      if(!storageAvailable()) return [];\n      try{\n        const raw = window.localStorage.getItem(STORAGE_KEY);\n        if(!raw) return [];\n        const arr = JSON.parse(raw);\n        if(!Array.isArray(arr)) return [];\n        return arr.filter(s => s && typeof s.name === 'string' && typeof s.savedAt === 'string' && typeof s.values === 'object');\n      } catch {\n        return [];\n      }\n    }\n\n    function saveAllStates(states){\n      if(!storageAvailable()) return false;\n      try{\n        window.localStorage.setItem(STORAGE_KEY, JSON.stringify(states));\n        return true;\n      } catch {\n        return false;\n      }\n    }\n\n    function migrateLegacy(){\n      if(!storageAvailable()) return;\n      \/\/ Nur migrieren, wenn V4-Speicher noch leer ist und V3-Daten existieren\n      const existing = window.localStorage.getItem(STORAGE_KEY);\n      if(existing) return;\n\n      try{\n        const raw = window.localStorage.getItem(LEGACY_KEY);\n        if(!raw) return;\n        const obj = JSON.parse(raw);\n        if(!obj || obj.schema !== 1 || typeof obj.values !== 'object') return;\n\n        const migrated = [{\n          name: 'Importiert (V3)',\n          savedAt: obj.savedAt || new Date().toISOString(),\n          values: obj.values\n        }];\n        saveAllStates(migrated);\n      } catch { \/* ignore *\/ }\n    }\n\n    function findStateByName(states, name){\n      const lower = name.trim().toLowerCase();\n      return states.findIndex(s => s.name.trim().toLowerCase() === lower);\n    }\n\n    \/\/ =========================================================\n    \/\/ Build UI rows (unchanged from V3)\n    \/\/ =========================================================\n    function buildRows(){\n      const frag = document.createDocumentFragment();\n\n      for(let i=1;i<=CONFIG.rows;i++){\n        const row = document.createElement('div');\n        row.className = 'grid row';\n        row.setAttribute('role','row');\n        row.dataset.row = String(i);\n\n        const cF = document.createElement('div');\n        cF.className = 'cell';\n\n        const tag = document.createElement('span');\n        tag.className = 'p-tag';\n        tag.textContent = `P${i}`;\n        tag.setAttribute('aria-hidden','true');\n\n        const inF = document.createElement('input');\n        inF.type = 'text';\n        inF.id = `P${i}f`;\n        inF.placeholder = `Fach`;\n        inF.autocomplete = 'off';\n\n        cF.append(tag, inF);\n\n        const cS = document.createElement('div');\n        cS.className = 'cell';\n        if(i < 5){\n          const inS = document.createElement('input');\n          inS.type = 'number';\n          inS.id = `P${i}s`;\n          inS.min = String(CONFIG.minGradePoints);\n          inS.max = String(CONFIG.maxGradePoints);\n          inS.step = '1';\n          inS.inputMode = 'numeric';\n          inS.placeholder = '0\u201315';\n          cS.appendChild(inS);\n        } else {\n          const dummy = document.createElement('div');\n          dummy.style.color = 'var(--muted)';\n          dummy.style.fontWeight = '700';\n          dummy.textContent = '\u2013';\n          cS.style.justifyContent = 'center';\n          cS.appendChild(dummy);\n        }\n\n        const cM = document.createElement('div');\n        cM.className = 'cell';\n        const inM = document.createElement('input');\n        inM.type = 'number';\n        inM.id = `P${i}m`;\n        inM.min = String(CONFIG.minGradePoints);\n        inM.max = String(CONFIG.maxGradePoints);\n        inM.step = '1';\n        inM.inputMode = 'numeric';\n        inM.placeholder = (i === 5) ? '0\u201315' : '';\n        cM.appendChild(inM);\n\n        const cP = document.createElement('div');\n        cP.className = 'cell';\n        const spP = document.createElement('span');\n        spP.id = `P${i}Punkte`;\n        spP.className = 'points blank';\n        spP.textContent = '';\n        spP.setAttribute('aria-label','Punkte');\n        cP.appendChild(spP);\n\n        const cD = document.createElement('div');\n        cD.className = 'cell';\n        const spD = document.createElement('span');\n        spD.id = `P${i}Diff`;\n        spD.className = 'diff';\n        spD.textContent = '';\n        cD.appendChild(spD);\n\n        row.append(cF,cS,cM,cP,cD);\n        frag.appendChild(row);\n      }\n\n      elRows.innerHTML = '';\n      elRows.appendChild(frag);\n    }\n\n    \/\/ =========================================================\n    \/\/ Calculation logic (unchanged from V3.09)\n    \/\/ =========================================================\n    \/\/ Normalisiert einen importierten\/URL-Wert auf ganze Zahl (z.B. \"8.0\" \u2192 \"8\").\n    \/\/ Leere Strings und nicht-numerische Werte bleiben unver\u00e4ndert.\n    function toIntVal(raw){\n      if(raw === null || raw === undefined) return '';\n      const s = String(raw).trim();\n      if(s === '') return '';\n      const n = Number(s);\n      if(!Number.isFinite(n)) return s;\n      return String(Math.trunc(n));\n    }\n\n    function inRangeInt(x, min, max){\n      return Number.isInteger(x) && x >= min && x <= max;\n    }\n\n    function readGradeInput(id){\n      const el = document.getElementById(id);\n      if(!el) return null;\n      const raw = el.value.trim();\n      if(raw === '') return null;\n      const x = Number(raw);\n      if(!Number.isFinite(x)) return null;\n      const xi = Math.trunc(x);\n      if(String(xi) !== String(x)) return null;\n      if(!inRangeInt(xi, CONFIG.minGradePoints, CONFIG.maxGradePoints)) return null;\n      return xi;\n    }\n\n    function computeRowPoints(rowIndex, s, m){\n      if(rowIndex < 5){\n        if(s === null) return { points: null, diff: null };\n        if(m === null) return { points: 4 * s, diff: null };\n        const points = Math.round((8 * s + 4 * m) \/ 3);\n        const diff = points - 4 * s;\n        return { points, diff };\n      }\n      if(m === null) return { points: null, diff: null };\n      return { points: 4 * m, diff: null };\n    }\n\n    function renderRow(rowIndex){\n      const elPoints = document.getElementById(`P${rowIndex}Punkte`);\n      const elDiff = document.getElementById(`P${rowIndex}Diff`);\n\n      const s = rowIndex < 5 ? readGradeInput(`P${rowIndex}s`) : null;\n      const m = readGradeInput(`P${rowIndex}m`);\n\n      const res = computeRowPoints(rowIndex, s, m);\n\n      if(res.points === null){\n        elPoints.textContent = '';\n        elPoints.classList.remove('good','bad');\n        elPoints.classList.add('blank');\n        elDiff.textContent = '';\n        elDiff.classList.remove('good','bad');\n        return { hasResult: false, points: null };\n      }\n\n      elPoints.textContent = String(res.points);\n      elPoints.classList.remove('blank');\n      if(res.points < 20){\n        elPoints.classList.add('bad');\n        elPoints.classList.remove('good');\n      } else {\n        elPoints.classList.add('good');\n        elPoints.classList.remove('bad');\n      }\n\n      if(res.diff !== null){\n        const sign = res.diff >= 0 ? '+' : '';\n        elDiff.textContent = `${sign}${res.diff}`;\n        elDiff.classList.remove('good','bad');\n        if(res.diff < 0) elDiff.classList.add('bad');\n        if(res.diff > 0) elDiff.classList.add('good');\n      } else {\n        elDiff.textContent = '';\n        elDiff.classList.remove('good','bad');\n      }\n\n      return { hasResult: true, points: res.points };\n    }\n\n    function renderSummary(){\n      let under = 0;\n      let total = 0;\n      let atLeast20 = 0;\n      let count = 0;\n\n      for(let i=1;i<=CONFIG.rows;i++){\n        const out = renderRow(i);\n        if(!out.hasResult) continue;\n        count++;\n        total += out.points;\n        if(out.points < 20) under++;\n        if(out.points >= 20) atLeast20++;\n      }\n\n      elUnder.textContent = String(under);\n      elUnder.classList.remove('good','bad');\n      if(under < 3) elUnder.classList.add('good');\n      else elUnder.classList.add('bad');\n\n      elTotal.textContent = String(total);\n      elTotal.classList.remove('good','bad');\n      if(total >= CONFIG.pass.minTotalExamPoints) elTotal.classList.add('good');\n      else elTotal.classList.add('bad');\n\n      return { under, total, atLeast20, count };\n    }\n\n    function setStatus(kind, text){\n      const badge = kind === 'good' ? '\u2713' : (kind === 'bad' ? '\u2717' : '?');\n      let hint = '';\n      if(kind === 'neutral'){\n        hint = ' <span style=\"font-size:13px;font-weight:600;color:var(--muted);\">(P-Ergebnisse fehlen)<\/span>';\n      }\n      elStatus.innerHTML = `${text} <span class=\"badge ${kind}\" aria-hidden=\"true\">${badge}<\/span>${hint}`;\n    }\n\n    function setFhrStatus(kind, text){\n      const badge = kind === 'good' ? '\u2713' : (kind === 'bad' ? '\u2717' : '?');\n      elFhrStatus.innerHTML = `${text} <span class=\"badge ${kind}\" aria-hidden=\"true\">${badge}<\/span>`;\n    }\n\n    function renderFhr(){\n      elFhrStatus.style.display = 'block';\n\n      if(FHRStatus === 1){\n        setFhrStatus('good', 'Fachhochschulreife erreicht');\n        elFhrVal.textContent = (FHRNote > 0) ? FHRNote.toFixed(1).replace('.', ',') : '\u2014';\n        elFhrWrap.style.display = 'block';\n      } else if(FHRStatus === 2){\n        setFhrStatus('bad', 'Fachhochschulreife nicht erreicht');\n        elFhrWrap.style.display = 'none';\n      } else {\n        setFhrStatus('neutral', 'Fachhochschulreife unbekannt');\n        elFhrWrap.style.display = 'none';\n      }\n    }\n\n    function parseBlockI(){\n      const raw = elPB1.value.trim();\n      if(raw === '') return null;\n      const x = Number(raw);\n      if(!Number.isFinite(x)) return null;\n      const xi = Math.trunc(x);\n      if(String(xi) !== String(x)) return null;\n      if(!inRangeInt(xi, CONFIG.blockI.min, CONFIG.blockI.max)) return null;\n      return xi;\n    }\n\n    function computeAbiturNote(finalPoints){\n      let note = Math.floor(40 - (finalPoints - 300) \/ 18) \/ 10;\n      note = Number(note.toFixed(1));\n\n      const z = (note - 0.1);\n      const y = (301 + (40 - note * 10) * 18 - finalPoints);\n\n      return {\n        note,\n        nextNote: Number(z.toFixed(1)),\n        neededPoints: Math.trunc(y)\n      };\n    }\n\n    function formatOneDecimalDE(x){\n      return Number(x).toFixed(1).replace('.', ',');\n    }\n\n    function formatAbiturNoteCapped(noteValue){\n      const raw = Number(noteValue);\n      const rawText = formatOneDecimalDE(raw);\n      if(raw < 1.0){\n        return `1,0 (rechnerisch ${rawText})`;\n      }\n      return formatOneDecimalDE(raw);\n    }\n\n    function updateBlockIMeta(pb1){\n      if(pb1 === null){\n        if(elPB1Meta){\n          elPB1Meta.textContent = '';\n          elPB1Meta.style.display = 'none';\n        }\n        return;\n      }\n\n      const avgText = (pb1 \/ 40).toFixed(2).replace('.', ',');\n      const scaledPoints = 1.5 * pb1;\n      const blockINote = computeAbiturNote(scaledPoints).note;\n      const noteText = formatOneDecimalDE(blockINote);\n\n      const right = (blockINote < 1.0) ? `rechn. Note ${noteText}` : `Note ${noteText}`;\n\n      if(elPB1Meta){\n        elPB1Meta.textContent = `(\u00d8 ${avgText} | ${right})`;\n        elPB1Meta.style.display = 'inline';\n      }\n    }\n\n    function updateTotals(){\n      const s = renderSummary();\n\n      const pb1 = parseBlockI();\n      const pb1Raw = elPB1.value.trim();\n      const pb1Invalid = (pb1Raw !== '' && pb1 === null);\n      elPB1Error.classList.toggle('show', pb1Invalid);\n\n      updateBlockIMeta(pb1);\n\n      if(s.count < CONFIG.rows){\n        setStatus('neutral', 'Abitur-Pr\u00fcfung');\n        \/\/ FHR anzeigen, falls Daten vorhanden (Status 1 oder 2), auch wenn P-Ergebnisse noch fehlen\n        if(FHRStatus === 1 || FHRStatus === 2){\n          renderFhr();\n        } else {\n          elFhrStatus.style.display = 'none';\n          elFhrWrap.style.display = 'none';\n        }\n      } else {\n        const passed = (s.total >= CONFIG.pass.minTotalExamPoints) && (s.atLeast20 >= CONFIG.pass.minResultsAtLeast20);\n        if(passed){\n          setStatus('good', 'Abitur bestanden');\n          \/\/ Bei bestandenem Abitur: FHR ausblenden (verf\u00e4llt)\n          elFhrStatus.style.display = 'none';\n          elFhrWrap.style.display = 'none';\n        } else {\n          setStatus('bad', 'Abitur nicht bestanden');\n          renderFhr();\n        }\n      }\n\n      if(s.count < CONFIG.rows){\n        elFinalPoints.textContent = 'P-Ergebnisse fehlen';\n        elAbiturNote.textContent = 'P-Ergebnisse fehlen';\n        return;\n      }\n\n      if(pb1 === null){\n        elFinalPoints.textContent = 'Block-I-Punkte fehlen';\n        elAbiturNote.textContent = 'Block-I-Punkte fehlen';\n        return;\n      }\n\n      const finalPoints = pb1 + s.total;\n      elFinalPoints.textContent = String(finalPoints);\n\n      const passed = (s.total >= CONFIG.pass.minTotalExamPoints) && (s.atLeast20 >= CONFIG.pass.minResultsAtLeast20);\n      const noteObj = computeAbiturNote(finalPoints);\n      const noteText = formatAbiturNoteCapped(noteObj.note);\n\n      if(!passed){\n        if(noteObj.note < 1.0){\n          elAbiturNote.textContent = `--- (ca. 1,0; rechnerisch ${formatOneDecimalDE(noteObj.note)})`;\n        } else {\n          elAbiturNote.textContent = `--- (ca. ${formatOneDecimalDE(noteObj.note)})`;\n        }\n        return;\n      }\n\n      if(noteObj.note <= 1.0){\n        elAbiturNote.textContent = noteText;\n      } else {\n        const zText = formatOneDecimalDE(noteObj.nextNote);\n        const yVal = Math.max(0, noteObj.neededPoints);\n        elAbiturNote.textContent = `${noteText} (+${yVal} Punkte f\u00fcr ${zText})`;\n      }\n    }\n\n    function resetAll(){\n      document.querySelectorAll('#rows input, #PB1').forEach(inp => inp.value = '');\n\n      FHRNote = 0;\n      FHRStatus = 0;\n\n      for(let i=1;i<=CONFIG.rows;i++){\n        const elP = document.getElementById(`P${i}Punkte`);\n        const elD = document.getElementById(`P${i}Diff`);\n        elP.textContent = '';\n        elP.classList.remove('good','bad');\n        elP.classList.add('blank');\n        elD.textContent = '';\n        elD.classList.remove('good','bad');\n      }\n\n      elUnder.textContent = '0';\n      elUnder.classList.remove('good','bad');\n\n      elTotal.textContent = '0';\n      elTotal.classList.remove('good','bad');\n\n      setStatus('neutral','Abitur-Pr\u00fcfung');\n      elPB1Error.classList.remove('show');\n\n      elFinalPoints.textContent = 'Block-I-Punkte fehlen';\n      elAbiturNote.textContent = 'Block-I-Punkte fehlen';\n\n      if(elPB1Meta){\n        elPB1Meta.textContent = '';\n        elPB1Meta.style.display = 'none';\n      }\n\n      elFhrStatus.style.display = 'none';\n      elFhrWrap.style.display = 'none';\n    }\n\n    function showToast(msg){\n      elToast.textContent = msg;\n      elToast.classList.add('show');\n      window.setTimeout(() => elToast.classList.remove('show'), 1300);\n    }\n\n    \/\/ =========================================================\n    \/\/ Collect & apply state values\n    \/\/ =========================================================\n    function collectStateValues(){\n      const values = {};\n      for(let i=1;i<=CONFIG.rows;i++){\n        values[`P${i}f`] = document.getElementById(`P${i}f`)?.value ?? '';\n        if(i < 5) values[`P${i}s`] = document.getElementById(`P${i}s`)?.value ?? '';\n        values[`P${i}m`] = document.getElementById(`P${i}m`)?.value ?? '';\n      }\n      values['PB1'] = elPB1.value ?? '';\n      values['FHRStatus'] = String(FHRStatus ?? '0');\n      values['FHRNote'] = String(FHRNote ?? '0');\n      return values;\n    }\n\n    function applyStateValues(v){\n      resetAll();\n\n      for(let i=1;i<=CONFIG.rows;i++){\n        const elF = document.getElementById(`P${i}f`);\n        if(elF) elF.value = v[`P${i}f`] ?? '';\n        if(i < 5){\n          const elS = document.getElementById(`P${i}s`);\n          if(elS) elS.value = toIntVal(v[`P${i}s`]);\n        }\n        const elM = document.getElementById(`P${i}m`);\n        if(elM) elM.value = toIntVal(v[`P${i}m`]);\n      }\n      elPB1.value = v['PB1'] ?? '';\n\n      const s = Number(v['FHRStatus']);\n      FHRStatus = Number.isInteger(s) ? s : 0;\n      const n = Number(String(v['FHRNote']).replace(',', '.'));\n      FHRNote = Number.isFinite(n) ? n : 0;\n\n      updateTotals();\n    }\n\n    \/\/ =========================================================\n    \/\/ Save Panel logic\n    \/\/ =========================================================\n    let pendingSaveName = '';\n\n    function openSavePanel(){\n      \/\/ Schlie\u00dfe Manage-Panel falls offen\n      elManagePanel.classList.remove('open');\n      elSavePanel.classList.add('open');\n      elOverwriteConfirm.classList.remove('show');\n      elSaveName.value = '';\n      elSaveName.focus();\n    }\n\n    function closeSavePanel(){\n      elSavePanel.classList.remove('open');\n      elOverwriteConfirm.classList.remove('show');\n    }\n\n    function doSave(name, overwrite){\n      const states = loadAllStates();\n      const idx = findStateByName(states, name);\n\n      if(idx >= 0 && !overwrite){\n        \/\/ R\u00fcckfrage\n        pendingSaveName = name;\n        elOverwriteConfirm.classList.add('show');\n        return;\n      }\n\n      const entry = {\n        name: name.trim(),\n        savedAt: new Date().toISOString(),\n        values: collectStateValues()\n      };\n\n      if(idx >= 0){\n        states[idx] = entry;\n      } else {\n        states.push(entry);\n      }\n\n      if(saveAllStates(states)){\n        showToast(`Gespeichert: \"${entry.name}\"`);\n        closeSavePanel();\n      } else {\n        showToast('Speichern fehlgeschlagen');\n      }\n    }\n\n    function handleSaveConfirm(){\n      const name = elSaveName.value.trim();\n      if(!name){\n        elSaveName.focus();\n        return;\n      }\n      doSave(name, false);\n    }\n\n    \/\/ =========================================================\n    \/\/ Manage Panel logic\n    \/\/ =========================================================\n    function openManagePanel(){\n      closeSavePanel();\n      elManagePanel.classList.toggle('open');\n      if(elManagePanel.classList.contains('open')){\n        renderManageList();\n      }\n    }\n\n    function formatDateDE(isoStr){\n      try{\n        const dt = new Date(isoStr);\n        if(!Number.isFinite(dt.getTime())) return isoStr;\n        return dt.toLocaleString('de-DE', { year:'numeric', month:'2-digit', day:'2-digit', hour:'2-digit', minute:'2-digit' });\n      } catch {\n        return isoStr;\n      }\n    }\n\n    function renderManageList(){\n      const states = loadAllStates();\n\n      if(states.length === 0){\n        elManageList.innerHTML = '<div class=\"manage-empty\">Keine gespeicherten Zust\u00e4nde vorhanden.<\/div>';\n        elManageFooter.style.display = 'none';\n        return;\n      }\n\n      let html = '';\n      states.forEach((s, idx) => {\n        const nameEsc = s.name.replace(\/&\/g,'&amp;').replace(\/<\/g,'&lt;').replace(\/>\/g,'&gt;').replace(\/\"\/g,'&quot;');\n        const dateStr = formatDateDE(s.savedAt);\n        html += `<div class=\"manage-item\" data-idx=\"${idx}\">\n          <div class=\"manage-item-info\">\n            <div class=\"manage-item-name\" title=\"${nameEsc}\">${nameEsc}<\/div>\n            <div class=\"manage-item-date\">${dateStr}<\/div>\n          <\/div>\n          <div class=\"manage-item-actions\">\n            <button type=\"button\" class=\"btn-load\" data-action=\"load\" data-idx=\"${idx}\">Laden<\/button>\n            <button type=\"button\" class=\"btn-rename\" data-action=\"rename\" data-idx=\"${idx}\">Umbenennen<\/button>\n            <button type=\"button\" class=\"btn-delete\" data-action=\"delete\" data-idx=\"${idx}\">\u2717<\/button>\n          <\/div>\n        <\/div>`;\n      });\n\n      elManageList.innerHTML = html;\n      elManageFooter.style.display = (states.length > 1) ? 'block' : 'none';\n    }\n\n    function handleManageClick(e){\n      const btn = e.target.closest('button[data-action]');\n      if(!btn) return;\n\n      const action = btn.dataset.action;\n      const idx = Number(btn.dataset.idx);\n      const states = loadAllStates();\n\n      if(idx < 0 || idx >= states.length) return;\n\n      if(action === 'load'){\n        applyStateValues(states[idx].values);\n        showToast(`Geladen: \"${states[idx].name}\"`);\n        elManagePanel.classList.remove('open');\n      } else if(action === 'rename'){\n        const oldName = states[idx].name;\n        let newName = prompt('Neuer Name (max. 60 Zeichen):', oldName);\n        if(newName === null) return; \/\/ Abbruch\n        newName = newName.trim();\n        if(!newName){ showToast('\u26a0 Name darf nicht leer sein.'); return; }\n        if(newName.length > 60){ showToast('\u26a0 Name zu lang (max. 60 Zeichen).'); return; }\n        if(newName === oldName) return; \/\/ Keine \u00c4nderung\n\n        \/\/ Doublette? -> optional \u00fcberschreiben (Doublette l\u00f6schen)\n        const existIdx = states.findIndex((s, i) => i !== idx && s.name === newName);\n        if(existIdx >= 0){\n          if(!confirm(`Ein Zustand mit dem Namen \u201e${newName}\" existiert bereits.\\nDiesen \u00fcberschreiben (der alte Eintrag wird gel\u00f6scht)?`)) return;\n          states.splice(existIdx, 1);\n          \/\/ Wenn wir einen fr\u00fcheren Eintrag gel\u00f6scht haben, verschiebt sich unser Index\n          const idx2 = (existIdx < idx) ? (idx - 1) : idx;\n          if(idx2 < 0 || idx2 >= states.length) return;\n          states[idx2].name = newName;\n        } else {\n          states[idx].name = newName;\n        }\n\n        saveAllStates(states);\n        renderManageList();\n        showToast(`Umbenannt in \u201e${newName}\".`);\n      } else if(action === 'delete'){\n        const name = states[idx].name;\n        if(!confirm(`\u201e${name}\" wirklich l\u00f6schen?`)) return;\n        states.splice(idx, 1);\n        saveAllStates(states);\n        showToast(`Gel\u00f6scht: \"${name}\"`);\n        renderManageList();\n      }\n    }\n\n    function handleClearAll(){\n      const states = loadAllStates();\n      if(states.length === 0) return;\n      if(!confirm('Alle gespeicherten Zust\u00e4nde wirklich l\u00f6schen?')) return;\n      saveAllStates([]);\n      showToast('Alle Zust\u00e4nde gel\u00f6scht');\n      renderManageList();\n    }\n\n    \/\/ =========================================================\n    \/\/ URL pre-fill (unchanged from V3)\n    \/\/ =========================================================\n    function fillFromUrl(){\n      const sp = new URLSearchParams(window.location.search);\n\n      const fhrN = sp.get('FHRNote');\n      const fhrS = sp.get('FHRStatus');\n      if(fhrN !== null){\n        const x = Number(String(fhrN).replace(',', '.'));\n        if(Number.isFinite(x)) FHRNote = x;\n      }\n      if(fhrS !== null){\n        const x = Number(fhrS);\n        if(Number.isInteger(x)) FHRStatus = x;\n      }\n\n      for(let i=1;i<=CONFIG.rows;i++){\n        const f = sp.get(`P${i}f`);\n        const s = sp.get(`P${i}s`);\n        const m = sp.get(`P${i}m`);\n\n        const elF = document.getElementById(`P${i}f`);\n        if(elF && f !== null) elF.value = f;\n\n        if(i < 5){\n          const elS = document.getElementById(`P${i}s`);\n          if(elS && s !== null) elS.value = toIntVal(s);\n        }\n\n        const elM = document.getElementById(`P${i}m`);\n        if(elM && m !== null) elM.value = toIntVal(m);\n      }\n\n      const pb1 = sp.get('PB1');\n      if(pb1 !== null) elPB1.value = pb1;\n    }\n\n    \/\/ =========================================================\n    \/\/ Wire events\n    \/\/ =========================================================\n    function wireEvents(){\n      \/\/ Recalc on any relevant input\n      document.querySelectorAll('#rows input, #PB1').forEach(inp => {\n        inp.addEventListener('input', () => updateTotals());\n        inp.addEventListener('change', () => updateTotals());\n      });\n\n      \/\/ Reset\n      document.getElementById('btnReset').addEventListener('click', () => {\n        resetAll();\n        updateTotals();\n      });\n\n      \/\/ Save panel\n      document.getElementById('btnSave').addEventListener('click', openSavePanel);\n      document.getElementById('btnSaveConfirm').addEventListener('click', handleSaveConfirm);\n      document.getElementById('btnSaveCancel').addEventListener('click', closeSavePanel);\n\n      \/\/ Enter-Taste im Namensfeld\n      elSaveName.addEventListener('keydown', (e) => {\n        if(e.key === 'Enter'){\n          e.preventDefault();\n          handleSaveConfirm();\n        }\n      });\n\n      \/\/ Overwrite confirm\n      document.getElementById('btnOverwriteYes').addEventListener('click', () => {\n        doSave(pendingSaveName, true);\n      });\n      document.getElementById('btnOverwriteNo').addEventListener('click', () => {\n        elOverwriteConfirm.classList.remove('show');\n        elSaveName.focus();\n      });\n\n      \/\/ Manage panel\n      document.getElementById('btnManage').addEventListener('click', openManagePanel);\n      elManageList.addEventListener('click', handleManageClick);\n      document.getElementById('btnClearAll').addEventListener('click', handleClearAll);\n    }\n\n    \/\/ =========================================================\n    \/\/ Init\n    \/\/ =========================================================\n    buildRows();\n    migrateLegacy();\n    fillFromUrl();\n    wireEvents();\n    updateTotals();\n  <\/script>\n<\/body>\n<\/html>\n<\/div><\/div><\/div><\/div><\/div><\/div><\/section>\n","protected":false},"excerpt":{"rendered":"Abiturrechner \u2013 Gymnasium Johanneum L\u00fcneburg Abiturrechner Gymnasium Johanneum L\u00fcneburg Version 4.12 (03.04.2026, F\u00fc) Reset Speichern Verwalten (Laden \/ L\u00f6schen) OK Abbrechen Ein Zustand mit diesem Namen existiert bereits. \u00dcberschreiben? Ja, \u00fcberschreiben Nein Alle Zust\u00e4nde l\u00f6schen P-Fach schr. m\u00fcndl. Punkte Diff. Anz. Unterkurse = 0 Abitur-Punkte = 0 Abitur-Pr\u00fcfung ? Punkte aus Block I = Bitte...","protected":false},"author":4,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-18885","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/johanneum-lueneburg.de\/wordpress\/wp-json\/wp\/v2\/pages\/18885","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/johanneum-lueneburg.de\/wordpress\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/johanneum-lueneburg.de\/wordpress\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/johanneum-lueneburg.de\/wordpress\/wp-json\/wp\/v2\/users\/4"}],"replies":[{"embeddable":true,"href":"https:\/\/johanneum-lueneburg.de\/wordpress\/wp-json\/wp\/v2\/comments?post=18885"}],"version-history":[{"count":36,"href":"https:\/\/johanneum-lueneburg.de\/wordpress\/wp-json\/wp\/v2\/pages\/18885\/revisions"}],"predecessor-version":[{"id":25858,"href":"https:\/\/johanneum-lueneburg.de\/wordpress\/wp-json\/wp\/v2\/pages\/18885\/revisions\/25858"}],"wp:attachment":[{"href":"https:\/\/johanneum-lueneburg.de\/wordpress\/wp-json\/wp\/v2\/media?parent=18885"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}