diff --git a/.paul/PROJECT.md b/.paul/PROJECT.md
index a3bf150..d073e83 100644
--- a/.paul/PROJECT.md
+++ b/.paul/PROJECT.md
@@ -60,9 +60,9 @@ Plugin Elementor do rezerwacji samochodu na stronie carei.pagedev.pl, zintegrowa
## Validated Requirements (Milestone v0.5)
- ✓ Modal rezerwacji działa na mobile/tablet — Phase 14
+- ✓ Pakiety ochronne SOFT+PREMIUM z ceną/dobę zarządzaną w panelu WP — Phase 13 (plan 13-02)
## Out of Scope (backlog)
-- Ubezpieczenie (pakiet Soft/Premium) — czeka na potwierdzenie klienta (źródło danych)
- Eksport CSV/PDF rezerwacji
- Email notyfikacje
diff --git a/.paul/ROADMAP.md b/.paul/ROADMAP.md
index e2bcb57..956ccb6 100644
--- a/.paul/ROADMAP.md
+++ b/.paul/ROADMAP.md
@@ -46,15 +46,14 @@ Dwa widgety Elementor: (1) mapa Polski SVG z dynamicznymi pinami oddziałów i t
---
-## Milestone v0.5: Pakiety Ochronne
+## Milestone v0.5: Pakiety Ochronne + Poprawki
-**Goal:** Wyświetlanie pakietów ochronnych SOFT i PREMIUM z dynamiczną ceną zależną od liczby dni wynajmu (3 progi: minimalna 1-3 dni, za dobę 4-15 dni, maksymalna 16+ dni).
+**Goal:** Wyświetlanie dwóch pakietów ochronnych (SOFT i PREMIUM) ze stałą ceną/dobę zarządzaną w panelu administratora WP. Plus poprawki UX (mobile modal fix).
-**Status:** In progress
+**Status:** Complete ✅
-### Phase 13: Pakiety ochronne — kafelki z ceną progową 🔄 BLOCKED
-Dedykowane kafelki SOFT/PREMIUM w sekcji "Pakiety ochronne" z ceną obliczaną dynamicznie na podstawie długości rezerwacji. Wybór wzajemnie wykluczający (radio). Dane z istniejącego API pricelist/additionalItems.
-**Blocker:** Czekamy na potwierdzenie klienta — źródło danych cenowych (API Softra vs panel WP).
+### Phase 13: Pakiety ochronne — kafelki z ceną za dobę z panelu WP ✅ Complete
+Dedykowane kafelki SOFT/PREMIUM w sekcji "Pakiety ochronne" z ceną za dobę z panelu WP. Radio z możliwością odznaczenia. Panel admina (wp-admin → Rezerwacje → Pakiety ochronne), REST endpoint /protection-packages. Pakiet poza priceItems Softra — wysyłany w comments + zapisany w meta CPT _carei_protection_package. Summary frontend dokłada wiersz pakietu i grandTotal. Plan 13-02 ukończony (13-01 superseded).
### Phase 14: Mobile modal fix ✅ Complete
Fix: modal rezerwacji nie otwierał się na mobile/tablet — sekcja Elementor miała elementor-hidden-mobile. Przeniesienie overlay do document.body.
diff --git a/.paul/STATE.md b/.paul/STATE.md
index 1968df8..873bc75 100644
--- a/.paul/STATE.md
+++ b/.paul/STATE.md
@@ -2,19 +2,19 @@
## Current Position
-Milestone: v0.5 Pakiety Ochronne + Poprawki — In progress
-Phase: 14 of 14 (Mobile modal fix) — Complete ✅
-Plan: 14-01 complete
-Status: Phase 14 closed, Phase 13 BLOCKED
-Last activity: 2026-04-10 — Phase 14 unified, SUMMARY written
+Milestone: v0.5 Pakiety Ochronne + Poprawki — Complete ✅
+Phase: 13 of 14 (Pakiety ochronne) — Complete ✅
+Plan: 13-02 complete
+Status: Milestone v0.5 closed
+Last activity: 2026-04-20 — Phase 13 unified, SUMMARY written, milestone closed
Progress:
- Milestone v0.1: [██████████] 100% ✅
- Milestone v0.2: [██████████] 100% ✅
- Milestone v0.3: [██████████] 100% ✅
- Milestone v0.4: [██████████] 100% ✅
-- Milestone v0.5: [██░░░░░░░░] 20%
- - Phase 13: BLOCKED — czeka na klienta
+- Milestone v0.5: [██████████] 100% ✅
+ - Phase 13: [██████████] 100% ✅
- Phase 14: [██████████] 100% ✅
## Loop Position
@@ -22,13 +22,12 @@ Progress:
Current loop state:
```
PLAN ──▶ APPLY ──▶ UNIFY
- ✓ ✓ ✓ [Phase 14 loop closed]
+ ✓ ✓ ✓ [Phase 13 loop closed, milestone v0.5 complete]
```
## Session Continuity
-Last session: 2026-04-10
-Stopped at: Phase 14 unified, Phase 13 BLOCKED
-Next action: Czekamy na odpowiedź klienta — źródło danych cenowych SOFT/PREMIUM (API Softra vs panel WP)
-Blocker: Phase 13 — potwierdzenie klienta
-Resume file: .paul/phases/13-protection-packages/13-01-PLAN.md
+Last session: 2026-04-20
+Stopped at: Milestone v0.5 complete (Phase 13 + Phase 14)
+Next action: Rozmowa o następnym milestone (v0.6) lub zamknięcie projektu — /paul:discuss-milestone lub /paul:complete-milestone
+Resume file: .paul/phases/13-protection-packages/13-02-SUMMARY.md
diff --git a/.paul/changelog/2026-04-20.md b/.paul/changelog/2026-04-20.md
new file mode 100644
index 0000000..c9b6097
--- /dev/null
+++ b/.paul/changelog/2026-04-20.md
@@ -0,0 +1,25 @@
+# 2026-04-20
+
+## Co zrobiono
+
+- [Phase 13, Plan 02] Pakiety ochronne SOFT + PREMIUM — zarządzanie cenami/dobę w panelu WP (wp-admin → Rezerwacje → Pakiety ochronne)
+- REST endpoint GET /carei/v1/protection-packages (public, filtr active)
+- Radio cards w modalu rezerwacji nad sekcją pozycji API z wizualnym separatorem
+- Fix HTTP 400 Softra: pakiet wyłączony z priceItems, doklejany do comments booking oraz dorysowany w summary frontend (details list + price table + totals)
+- Zapis wybranego pakietu w post_meta _carei_protection_package + widoczność w meta boxie CPT carei_reservation
+- Plan 13-01 oznaczony jako superseded (klient zmienił źródło danych na panel WP)
+- Milestone v0.5 zamknięty (Phase 13 ✅ + Phase 14 ✅)
+
+## Zmienione pliki
+
+- `wp-content/plugins/carei-reservation/includes/class-admin-panel.php`
+- `wp-content/plugins/carei-reservation/includes/class-rest-proxy.php`
+- `wp-content/plugins/carei-reservation/includes/class-elementor-widget.php`
+- `wp-content/plugins/carei-reservation/assets/js/carei-reservation.js`
+- `wp-content/plugins/carei-reservation/assets/css/carei-reservation.css`
+- `.paul/PROJECT.md`
+- `.paul/ROADMAP.md`
+- `.paul/STATE.md`
+- `.paul/phases/13-protection-packages/13-01-SUMMARY.md` (superseded note)
+- `.paul/phases/13-protection-packages/13-02-PLAN.md`
+- `.paul/phases/13-protection-packages/13-02-SUMMARY.md`
diff --git a/.paul/governance/governance_2026-04-20.jsonl b/.paul/governance/governance_2026-04-20.jsonl
new file mode 100644
index 0000000..1c27ce5
--- /dev/null
+++ b/.paul/governance/governance_2026-04-20.jsonl
@@ -0,0 +1,42 @@
+{"ts":"2026-04-19T22:25:36Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\carei.pagedev.pl\\\\.paul\\\\phases\\\\13-protection-packages\\\\13-02-PLAN.md","cwd":"/c/visual studio code/projekty/carei.pagedev.pl"}
+{"ts":"2026-04-19T22:25:54Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\carei.pagedev.pl\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/carei.pagedev.pl"}
+{"ts":"2026-04-19T22:26:11Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\carei.pagedev.pl\\\\.paul\\\\PROJECT.md","cwd":"/c/visual studio code/projekty/carei.pagedev.pl"}
+{"ts":"2026-04-19T22:26:15Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\carei.pagedev.pl\\\\.paul\\\\PROJECT.md","cwd":"/c/visual studio code/projekty/carei.pagedev.pl"}
+{"ts":"2026-04-19T22:28:02Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\carei.pagedev.pl\\\\wp-content\\\\plugins\\\\carei-reservation\\\\includes\\\\class-admin-panel.php","cwd":"/c/visual studio code/projekty/carei.pagedev.pl"}
+{"ts":"2026-04-19T22:28:16Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\carei.pagedev.pl\\\\wp-content\\\\plugins\\\\carei-reservation\\\\includes\\\\class-admin-panel.php","cwd":"/c/visual studio code/projekty/carei.pagedev.pl"}
+{"ts":"2026-04-19T22:28:22Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\carei.pagedev.pl\\\\wp-content\\\\plugins\\\\carei-reservation\\\\includes\\\\class-admin-panel.php","cwd":"/c/visual studio code/projekty/carei.pagedev.pl"}
+{"ts":"2026-04-19T22:28:28Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\carei.pagedev.pl\\\\wp-content\\\\plugins\\\\carei-reservation\\\\includes\\\\class-admin-panel.php","cwd":"/c/visual studio code/projekty/carei.pagedev.pl"}
+{"ts":"2026-04-19T22:28:33Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\carei.pagedev.pl\\\\wp-content\\\\plugins\\\\carei-reservation\\\\includes\\\\class-rest-proxy.php","cwd":"/c/visual studio code/projekty/carei.pagedev.pl"}
+{"ts":"2026-04-19T22:28:40Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\carei.pagedev.pl\\\\wp-content\\\\plugins\\\\carei-reservation\\\\includes\\\\class-rest-proxy.php","cwd":"/c/visual studio code/projekty/carei.pagedev.pl"}
+{"ts":"2026-04-19T22:28:48Z","tool":"Bash","cmd":"php -l \"C:/visual studio code/projekty/carei.pagedev.pl/wp-content/plugins/carei-reservation/includes/class-admin-panel.php\" && php -l \"C:/visual studio code/projekty/carei.pagedev.pl/wp-content/pl","cwd":"/c/visual studio code/projekty/carei.pagedev.pl"}
+{"ts":"2026-04-19T22:29:24Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\carei.pagedev.pl\\\\wp-content\\\\plugins\\\\carei-reservation\\\\assets\\\\css\\\\carei-reservation.css","cwd":"/c/visual studio code/projekty/carei.pagedev.pl"}
+{"ts":"2026-04-19T22:29:29Z","tool":"Bash","cmd":"php -l \"C:/visual studio code/projekty/carei.pagedev.pl/wp-content/plugins/carei-reservation/includes/class-elementor-widget.php\"\",\"description\":\"PHP lint widget\"},\"tool_response\":{\"stdout\"","cwd":"/c/visual studio code/projekty/carei.pagedev.pl"}
+{"ts":"2026-04-19T22:30:11Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\carei.pagedev.pl\\\\wp-content\\\\plugins\\\\carei-reservation\\\\assets\\\\js\\\\carei-reservation.js","cwd":"/c/visual studio code/projekty/carei.pagedev.pl"}
+{"ts":"2026-04-19T22:30:16Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\carei.pagedev.pl\\\\wp-content\\\\plugins\\\\carei-reservation\\\\assets\\\\js\\\\carei-reservation.js","cwd":"/c/visual studio code/projekty/carei.pagedev.pl"}
+{"ts":"2026-04-19T22:30:37Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\carei.pagedev.pl\\\\wp-content\\\\plugins\\\\carei-reservation\\\\assets\\\\js\\\\carei-reservation.js","cwd":"/c/visual studio code/projekty/carei.pagedev.pl"}
+{"ts":"2026-04-19T22:30:57Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\carei.pagedev.pl\\\\wp-content\\\\plugins\\\\carei-reservation\\\\assets\\\\js\\\\carei-reservation.js","cwd":"/c/visual studio code/projekty/carei.pagedev.pl"}
+{"ts":"2026-04-19T22:31:19Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\carei.pagedev.pl\\\\wp-content\\\\plugins\\\\carei-reservation\\\\assets\\\\js\\\\carei-reservation.js","cwd":"/c/visual studio code/projekty/carei.pagedev.pl"}
+{"ts":"2026-04-19T22:31:29Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\carei.pagedev.pl\\\\wp-content\\\\plugins\\\\carei-reservation\\\\assets\\\\js\\\\carei-reservation.js","cwd":"/c/visual studio code/projekty/carei.pagedev.pl"}
+{"ts":"2026-04-19T22:31:40Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\carei.pagedev.pl\\\\wp-content\\\\plugins\\\\carei-reservation\\\\assets\\\\js\\\\carei-reservation.js","cwd":"/c/visual studio code/projekty/carei.pagedev.pl"}
+{"ts":"2026-04-19T22:31:46Z","tool":"Bash","cmd":"node --check \"C:/visual studio code/projekty/carei.pagedev.pl/wp-content/plugins/carei-reservation/assets/js/carei-reservation.js\" 2>&1 && echo \"JS syntax OK\"\",\"description\":\"Node syntax check","cwd":"/c/visual studio code/projekty/carei.pagedev.pl"}
+{"ts":"2026-04-19T22:34:07Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\carei.pagedev.pl\\\\wp-content\\\\plugins\\\\carei-reservation\\\\assets\\\\js\\\\carei-reservation.js","cwd":"/c/visual studio code/projekty/carei.pagedev.pl"}
+{"ts":"2026-04-19T22:34:14Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\carei.pagedev.pl\\\\wp-content\\\\plugins\\\\carei-reservation\\\\assets\\\\js\\\\carei-reservation.js","cwd":"/c/visual studio code/projekty/carei.pagedev.pl"}
+{"ts":"2026-04-19T22:34:18Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\carei.pagedev.pl\\\\wp-content\\\\plugins\\\\carei-reservation\\\\assets\\\\js\\\\carei-reservation.js","cwd":"/c/visual studio code/projekty/carei.pagedev.pl"}
+{"ts":"2026-04-19T22:34:32Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\carei.pagedev.pl\\\\wp-content\\\\plugins\\\\carei-reservation\\\\assets\\\\css\\\\carei-reservation.css","cwd":"/c/visual studio code/projekty/carei.pagedev.pl"}
+{"ts":"2026-04-19T22:34:37Z","tool":"Bash","cmd":"node --check \"C:/visual studio code/projekty/carei.pagedev.pl/wp-content/plugins/carei-reservation/assets/js/carei-reservation.js\" && echo \"OK\"\",\"description\":\"JS syntax check\"},\"tool_respon","cwd":"/c/visual studio code/projekty/carei.pagedev.pl"}
+{"ts":"2026-04-19T22:35:27Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\carei.pagedev.pl\\\\wp-content\\\\plugins\\\\carei-reservation\\\\assets\\\\css\\\\carei-reservation.css","cwd":"/c/visual studio code/projekty/carei.pagedev.pl"}
+{"ts":"2026-04-19T22:36:05Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\carei.pagedev.pl\\\\wp-content\\\\plugins\\\\carei-reservation\\\\assets\\\\js\\\\carei-reservation.js","cwd":"/c/visual studio code/projekty/carei.pagedev.pl"}
+{"ts":"2026-04-19T22:36:10Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\carei.pagedev.pl\\\\wp-content\\\\plugins\\\\carei-reservation\\\\assets\\\\css\\\\carei-reservation.css","cwd":"/c/visual studio code/projekty/carei.pagedev.pl"}
+{"ts":"2026-04-19T22:36:14Z","tool":"Bash","cmd":"node --check \"C:/visual studio code/projekty/carei.pagedev.pl/wp-content/plugins/carei-reservation/assets/js/carei-reservation.js\" && echo OK\",\"description\":\"JS syntax check\"},\"tool_response\"","cwd":"/c/visual studio code/projekty/carei.pagedev.pl"}
+{"ts":"2026-04-19T22:37:21Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\carei.pagedev.pl\\\\wp-content\\\\plugins\\\\carei-reservation\\\\assets\\\\js\\\\carei-reservation.js","cwd":"/c/visual studio code/projekty/carei.pagedev.pl"}
+{"ts":"2026-04-19T22:37:32Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\carei.pagedev.pl\\\\wp-content\\\\plugins\\\\carei-reservation\\\\assets\\\\js\\\\carei-reservation.js","cwd":"/c/visual studio code/projekty/carei.pagedev.pl"}
+{"ts":"2026-04-19T22:37:40Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\carei.pagedev.pl\\\\wp-content\\\\plugins\\\\carei-reservation\\\\assets\\\\js\\\\carei-reservation.js","cwd":"/c/visual studio code/projekty/carei.pagedev.pl"}
+{"ts":"2026-04-19T22:37:58Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\carei.pagedev.pl\\\\wp-content\\\\plugins\\\\carei-reservation\\\\assets\\\\js\\\\carei-reservation.js","cwd":"/c/visual studio code/projekty/carei.pagedev.pl"}
+{"ts":"2026-04-19T22:38:03Z","tool":"Bash","cmd":"node --check \"C:/visual studio code/projekty/carei.pagedev.pl/wp-content/plugins/carei-reservation/assets/js/carei-reservation.js\" && echo OK\",\"description\":\"JS syntax check\"},\"tool_response\"","cwd":"/c/visual studio code/projekty/carei.pagedev.pl"}
+{"ts":"2026-04-19T22:40:22Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\carei.pagedev.pl\\\\wp-content\\\\plugins\\\\carei-reservation\\\\assets\\\\js\\\\carei-reservation.js","cwd":"/c/visual studio code/projekty/carei.pagedev.pl"}
+{"ts":"2026-04-19T22:40:26Z","tool":"Bash","cmd":"node --check \"C:/visual studio code/projekty/carei.pagedev.pl/wp-content/plugins/carei-reservation/assets/js/carei-reservation.js\" && echo OK\",\"description\":\"JS syntax check\"},\"tool_response\"","cwd":"/c/visual studio code/projekty/carei.pagedev.pl"}
+{"ts":"2026-04-19T22:43:15Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\carei.pagedev.pl\\\\.paul\\\\phases\\\\13-protection-packages\\\\13-02-SUMMARY.md","cwd":"/c/visual studio code/projekty/carei.pagedev.pl"}
+{"ts":"2026-04-19T22:43:28Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\carei.pagedev.pl\\\\.paul\\\\phases\\\\13-protection-packages\\\\13-01-SUMMARY.md","cwd":"/c/visual studio code/projekty/carei.pagedev.pl"}
+{"ts":"2026-04-19T22:43:45Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\carei.pagedev.pl\\\\.paul\\\\changelog\\\\2026-04-20.md","cwd":"/c/visual studio code/projekty/carei.pagedev.pl"}
+{"ts":"2026-04-19T22:44:08Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\carei.pagedev.pl\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/carei.pagedev.pl"}
+{"ts":"2026-04-19T22:45:06Z","tool":"Bash","cmd":"git -C \"C:/visual studio code/projekty/carei.pagedev.pl\" status --short\",\"description\":\"Git status\"},\"tool_response\":{\"stdout\":\" M .paul/PROJECT.md\\n M .paul/ROADMAP.md\\n M .paul/STATE.m","cwd":"/c/visual studio code/projekty/carei.pagedev.pl"}
diff --git a/.paul/phases/13-protection-packages/13-01-SUMMARY.md b/.paul/phases/13-protection-packages/13-01-SUMMARY.md
new file mode 100644
index 0000000..c21d73e
--- /dev/null
+++ b/.paul/phases/13-protection-packages/13-01-SUMMARY.md
@@ -0,0 +1,21 @@
+---
+phase: 13-protection-packages
+plan: 01
+status: superseded
+superseded_by: 13-02-PLAN.md
+completed: 2026-04-20
+---
+
+# Phase 13 Plan 01: Superseded
+
+Plan 13-01 (pricing progowy min/doba/max z Softra API) **nie został wdrożony** — klient po audycie wybrał prostsze źródło danych: stała cena/dobę zarządzana w panelu WP.
+
+**Supersession decision (2026-04-20):**
+- Plan 13-01 zakładał 3 progi cenowe pobierane z `additionalItems` API Softra
+- Klient potwierdził, że ceny pakietów ochronnych mają być zarządzane w WP (nie wyeksponowane w Softra)
+- Uproszczenie: jedna cena/dobę × liczba dób
+
+**Plan wdrażający wymagania klienta:** [13-02-PLAN.md](./13-02-PLAN.md) — ukończony, SUMMARY: [13-02-SUMMARY.md](./13-02-SUMMARY.md).
+
+---
+*Phase: 13-protection-packages, Plan: 01 (superseded)*
diff --git a/.paul/phases/13-protection-packages/13-02-PLAN.md b/.paul/phases/13-protection-packages/13-02-PLAN.md
new file mode 100644
index 0000000..58c3b5d
--- /dev/null
+++ b/.paul/phases/13-protection-packages/13-02-PLAN.md
@@ -0,0 +1,317 @@
+---
+phase: 13-protection-packages
+plan: 02
+type: execute
+wave: 1
+depends_on: []
+files_modified:
+ - wp-content/plugins/carei-reservation/includes/class-admin-panel.php
+ - wp-content/plugins/carei-reservation/includes/class-rest-proxy.php
+ - wp-content/plugins/carei-reservation/includes/class-elementor-widget.php
+ - wp-content/plugins/carei-reservation/assets/js/carei-reservation.js
+ - wp-content/plugins/carei-reservation/assets/css/carei-reservation.css
+autonomous: false
+delegation: off
+---
+
+
+## Goal
+Dodać dwa stałe pakiety ochronne (SOFT, PREMIUM) w sekcji "Pakiety ochronne" modalu rezerwacji, z ceną za dobę zarządzaną w panelu administratora WordPress. Kalkulacja dzienna (price × liczba_dób). Wizualne oddzielenie od pozostałych pozycji API w tej sekcji.
+
+## Purpose
+Klient potwierdził, że źródłem danych cenowych dla pakietów ochronnych jest panel WP (nie API Softra). Stary plan 13-01 (pricing progowy min/doba/max z Softra) został odrzucony. Nowe wymaganie upraszcza model: stała cena/dobę × liczba dób rezerwacji, zarządzana przez admina w WP.
+
+## Output
+- Nowa podstrona w menu "Rezerwacje" → "Pakiety ochronne" (name, price/doba, aktywne, opis dla SOFT i PREMIUM)
+- Endpoint `GET /carei/v1/protection-packages` dla frontendu
+- Dwa kafelki w modalu nad/pod istniejącymi opcjami "Pakiety ochronne" z delikatnym separatorem
+- Cena wyświetlana: "X zł/doba" + wyliczony total dla wybranych dat
+- Wybrany pakiet uwzględniony w `priceItems` booking submission i zapisany w CPT
+
+
+
+@.paul/PROJECT.md
+@.paul/ROADMAP.md
+@.paul/STATE.md
+
+## Source Files
+@wp-content/plugins/carei-reservation/includes/class-admin-panel.php
+@wp-content/plugins/carei-reservation/includes/class-rest-proxy.php
+@wp-content/plugins/carei-reservation/includes/class-elementor-widget.php
+@wp-content/plugins/carei-reservation/assets/js/carei-reservation.js
+@wp-content/plugins/carei-reservation/assets/css/carei-reservation.css
+
+## Prior Work
+Plan 13-01 (BLOCKED) zakładał pricing progowy z pola `additionalItems` Softra API — **odrzucony**. Ten plan (13-02) zastępuje 13-01. Zbiór SCOPE LIMITS odwraca się: backend+admin SĄ w zakresie, pricing tierowy poza zakresem.
+
+
+
+
+## AC-1: Panel administratora — konfiguracja pakietów
+```gherkin
+Given admin otwiera menu "Rezerwacje" w WP
+When klika podstronę "Pakiety ochronne"
+Then widzi formularz z dwoma sekcjami (SOFT i PREMIUM) zawierającymi:
+ - nazwa wyświetlana (text, default "Ubezpieczenie SOFT" / "Ubezpieczenie PREMIUM")
+ - cena za dobę (number, min 0, step 0.01)
+ - aktywne (checkbox, default on)
+ - opis/zakres usług (textarea, opcjonalny)
+And zapis formularza trwale przechowuje wartości w wp_options (carei_protection_packages)
+```
+
+## AC-2: Endpoint REST z danymi pakietów
+```gherkin
+Given w panelu admina są zapisane pakiety
+When frontend wywołuje GET /wp-json/carei/v1/protection-packages
+Then endpoint zwraca JSON { soft: {name, pricePerDay, active, description}, premium: {...} }
+And zwraca tylko pakiety z active=true (nieaktywne pomijane lub flagowane jako inactive)
+```
+
+## AC-3: Render kafelków w modalu z separatorem
+```gherkin
+Given otwarto modal rezerwacji, wybrano segment + oddział + daty
+When sekcja "Pakiety ochronne" renderuje się
+Then nad listą opcji API pojawiają się 2 kafelki SOFT + PREMIUM (tylko te z active=true)
+And kafelki są oddzielone delikatną linią/marginesem od pozostałych pozycji "Pakiety ochronne" pochodzących z pricelist API
+And każdy kafelek pokazuje: nazwę, cenę "X zł/doba", wyliczony total "= Y zł" dla aktualnej liczby dób, opis (jeśli ustawiony)
+```
+
+## AC-4: Wybór i dynamiczne przeliczanie
+```gherkin
+Given użytkownik widzi oba kafelki pakietów
+When klika SOFT
+Then SOFT jest zaznaczony (radio-style), PREMIUM odznaczony
+When następnie klika PREMIUM
+Then SOFT jest odznaczony, PREMIUM zaznaczony
+When klika ponownie zaznaczony pakiet
+Then zostaje odznaczony (dozwolony brak wybranego pakietu)
+When zmienia daty rezerwacji
+Then wyliczony total (price × days) aktualizuje się automatycznie na obu kafelkach
+```
+
+## AC-5: Pakiet w booking submission i CPT
+```gherkin
+Given użytkownik wybrał pakiet SOFT
+When klika "Pokaż podsumowanie" → "Zarezerwuj"
+Then wybrany pakiet jest dołączony do priceItems w zapytaniu do Softra (price, priceBeforeDiscount, name, quantity=days)
+And po zapisie rezerwacji widoczny w CPT carei_reservation w polu "extras" lub dedykowanym polu meta
+And w summary overlay pojawia się linia "Ubezpieczenie SOFT — Y zł"
+```
+
+
+
+
+
+
+ Task 1: Backend — admin settings page + REST endpoint
+ wp-content/plugins/carei-reservation/includes/class-admin-panel.php, wp-content/plugins/carei-reservation/includes/class-rest-proxy.php
+
+ A. W `class-admin-panel.php`:
+ - Dodać metodę `register_protection_packages_page()` hookowaną przez `admin_menu`: `add_submenu_page('edit.php?post_type=carei_reservation', 'Pakiety ochronne', 'Pakiety ochronne', 'manage_options', 'carei-protection-packages', [$this, 'render_protection_packages_page'])`
+ - Dodać `render_protection_packages_page()`: formularz POST z nonce `carei_protection_packages`, dwie sekcje (SOFT, PREMIUM) z polami: `name`, `pricePerDay`, `active`, `description`.
+ - Dodać obsługę POST: walidacja nonce + `manage_options`, sanityzacja (`sanitize_text_field` dla name, `floatval` + clamp >=0 dla price, `boolean` checkbox, `sanitize_textarea_field`), zapis do `update_option('carei_protection_packages', $data)`.
+ - Defaulty (przy pierwszym wczytaniu / pustym option):
+ `soft = { name: 'Ubezpieczenie SOFT', pricePerDay: 0, active: true, description: '' }`
+ `premium = { name: 'Ubezpieczenie PREMIUM', pricePerDay: 0, active: true, description: '' }`
+ - Dodać helper statyczny `public static function get_protection_packages()` zwracający aktualną tablicę (merge z defaultami).
+ - Dodatkowo dopisać obsługę zapisu wybranego pakietu do post_meta w `save_reservation()` (jeśli `$data['protectionPackage']` podane — zapisać jako `_carei_protection_package` JSON: `{key, name, pricePerDay, days, total}`) oraz wyświetlić w meta boxie pod sekcją extras.
+
+ B. W `class-rest-proxy.php`:
+ - Zarejestrować nowy route w `register_routes()`:
+ ```php
+ register_rest_route( self::NAMESPACE, '/protection-packages', array(
+ 'methods' => 'GET',
+ 'callback' => array( $this, 'get_protection_packages' ),
+ 'permission_callback' => '__return_true',
+ ) );
+ ```
+ - Dodać metodę `get_protection_packages()`: odczyt `Carei_Admin_Panel::get_protection_packages()`, filtrowanie tylko `active === true`, zwrot jako `rest_ensure_response({ soft: {...}|null, premium: {...}|null })`.
+
+ Avoid: Nie tworzyć custom table — wystarczy wp_options (spójne z MVP). Nie dodawać JS-a do panelu admina (formularz statyczny, native WP). Nie przenosić `save_reservation()` logic — tylko dopisać pole.
+
+
+ - `wp-admin/edit.php?post_type=carei_reservation&page=carei-protection-packages` — strona renderuje formularz
+ - Zmiana ceny + zapis → komunikat "Zapisano" i wartości persystują po reload
+ - `curl https://carei.pagedev.pl/wp-json/carei/v1/protection-packages` → JSON z oboma pakietami
+ - Ustawienie SOFT inactive → endpoint zwraca `soft: null` (lub bez klucza)
+
+ AC-1, AC-2 satisfied: Admin może edytować pakiety, endpoint REST eksponuje dane.
+
+
+
+ Task 2: Frontend HTML — kontener kafelków pakietów z separatorem
+ wp-content/plugins/carei-reservation/includes/class-elementor-widget.php, wp-content/plugins/carei-reservation/assets/css/carei-reservation.css
+
+ A. W `class-elementor-widget.php`, w sekcji `
Pakiety ochronne
`:
+ - BEZPOŚREDNIO nad istniejącym `
` dodać:
+ ```html
+
+
+
+
+ ```
+ - Pozostawić istniejący `#carei-insurance-container` bez zmian (nadal renderuje pozycje z API Softra, jeśli są).
+
+ B. W `carei-reservation.css`:
+ - `.carei-form__row--protection-packages` — grid/flex z 2 kolumnami (desktop), wrap na mobile.
+ - `.carei-form__protection-package` — karta: padding, border 2px solid transparent, border-radius 12px, background #f8f8fb, cursor pointer, tranzycja.
+ - `.carei-form__protection-package.is-selected` — border #2F2482, background #fff z subtelnym shadow.
+ - `.carei-form__protection-package__name` — bold, color #2F2482.
+ - `.carei-form__protection-package__price` — większy font, strong.
+ - `.carei-form__protection-package__unit` — mały font szary "/doba".
+ - `.carei-form__protection-package__total` — color #FF0000 dla total.
+ - `.carei-form__protection-package__desc` — mały font, szary.
+ - `.carei-form__protection-divider` — margin-top/bottom, border-bottom 1px dashed rgba(47,36,130,0.15), height 0. Stanowi delikatny wizualny separator między pakietami WP a opcjami API.
+ - Responsive: `@media (max-width: 640px)` → kolumna, pełna szerokość.
+
+ Avoid: Nie ruszać istniejącego `#carei-insurance-container` ani `.carei-form__extra-card` (służą pozostałym pozycjom API i extras).
+
+
+ - W edytorze Elementor / na stronie: DOM zawiera nowy `#carei-protection-packages-container` nad `#carei-insurance-container` wewnątrz sekcji "Pakiety ochronne"
+ - Separator `.carei-form__protection-divider` widoczny w DOM
+ - Style zaczytywane — brak błędów w DevTools
+
+ AC-3 satisfied (HTML skeleton + separator) — JS render w Task 3
+
+
+
+ Task 3: Frontend JS — render, radio behavior, dynamic recalculation, submission
+ wp-content/plugins/carei-reservation/assets/js/carei-reservation.js
+
+ 1. **Wczytanie pakietów (przy init lub przy otwarciu modala):**
+ - Dodać stan modułu: `var protectionPackages = { soft: null, premium: null };` oraz `var selectedProtectionKey = null;`.
+ - Dodać referencję: `protectionContainer = document.getElementById('carei-protection-packages-container');` w sekcji `cacheDom()` (~linie 100-120).
+ - Dodać funkcję `loadProtectionPackages()` wywołującą `fetch('/wp-json/carei/v1/protection-packages')` (REST nie wymaga nonce dla GET). Po otrzymaniu: zapis do `protectionPackages`, wywołanie `renderProtectionPackages()`.
+ - Wywołać `loadProtectionPackages()` jednorazowo (np. w `init()` obok innych API calls, niezależnie od pricelist).
+
+ 2. **Render `renderProtectionPackages()`:**
+ - Jeśli kontener nie istnieje — return.
+ - Wyczyścić container.
+ - Dla każdego klucza `['soft','premium']`, jeśli pakiet istnieje i `active`:
+ - Zbudować kafelek zgodnie z markup CSS z Task 2:
+ ```
+
+ ```
+ - Podłączyć listener click do każdego kafelka: toggle `is-selected`, aktualizacja `selectedProtectionKey`, wywołanie `updateProtectionTotals()`.
+ - Wywołać `updateProtectionTotals()` bezpośrednio po render.
+
+ 3. **`updateProtectionTotals()`:**
+ - Obliczyć `days` = liczba dób (wyznaczyć z dat `#carei-date-from` + `#carei-date-to` — użyć istniejącej funkcji/logiki; jeśli brak helper — obliczyć: `Math.max(1, Math.ceil((dateTo - dateFrom) / 86400000))`).
+ - Dla każdego kafelka: aktualizacja `[data-role="total"]` → `= {price*days} zł`.
+ - Jeśli daty nie są jeszcze ustawione (days === NaN) — total pusty.
+
+ 4. **Hook w zmianę dat:**
+ - Zlokalizować istniejący handler `change`/`input` na polach `#carei-date-from` i `#carei-date-to` (lub common `onDateChange()`) i dodać wywołanie `updateProtectionTotals()`. Alternatywnie dodać niezależne listenery.
+
+ 5. **Radio deselect behavior:**
+ - W click handlerze: jeśli `selectedProtectionKey === clickedKey` → odznacz (pusty stan, `selectedProtectionKey = null`). Inaczej ustaw jako wybrany, odznacz drugi kafelek.
+
+ 6. **Submission + summary:**
+ - W `getSelectedExtrasForApi()` (~linia 849): po zebraniu extras dodać warunkowo wybrany pakiet:
+ ```
+ if (selectedProtectionKey && protectionPackages[selectedProtectionKey]) {
+ var pkg = protectionPackages[selectedProtectionKey];
+ var days = calculateDays();
+ var total = pkg.pricePerDay * days;
+ extrasArr.push({
+ id: 'protection_' + selectedProtectionKey,
+ name: pkg.name,
+ price: total,
+ priceBeforeDiscount: total,
+ quantity: days,
+ unit: 'doba'
+ });
+ }
+ ```
+ - W payloadzie do `makeBooking`: dodać pole `protectionPackage: { key, name, pricePerDay, days, total }` (obok `priceItems`) — admin zapisze w meta.
+
+ Avoid: Nie modyfikować `buildExtraCard` ani logiki `extras[]` API Softra. Nie renderować pakietów jeśli `pricePerDay === 0` (opcjonalne — dopisać validation w admin, ale render sam skip tych z 0 jest też ok).
+
+
+ - Network tab: request do `/carei/v1/protection-packages` zwraca dane
+ - Modal: w sekcji "Pakiety ochronne" widoczne 2 kafelki SOFT+PREMIUM nad separatorem
+ - Kliknięcie SOFT: `is-selected`, kliknięcie PREMIUM: SOFT traci selekcję, PREMIUM dostaje
+ - Kliknięcie zaznaczonego pakietu: odznaczenie
+ - Zmiana dat 3→7 dni: total pakietu aktualizuje się (price × 7)
+ - Wybór pakietu → podsumowanie → payload `priceItems` zawiera pakiet, `protectionPackage` przekazany do backendu
+ - Zapisana rezerwacja w CPT zawiera wybrany pakiet w meta boxie
+
+ AC-3, AC-4, AC-5 satisfied: Kafelki działają w pełnym cyklu (render → wybór → kalkulacja → submission → zapis).
+
+
+
+
+ Panel admina "Pakiety ochronne" (wp-admin), endpoint REST, kafelki SOFT/PREMIUM w modalu z separatorem od opcji API, radio selection z deselect, dynamiczne przeliczanie price×days, pakiet w booking submission + zapis w CPT.
+
+
+ 1. **Admin:** Zaloguj się do WP → menu "Rezerwacje" → "Pakiety ochronne".
+ - Ustaw SOFT: nazwa "Ubezpieczenie SOFT", cena 25 zł/dobę, aktywne, opis krótki
+ - Ustaw PREMIUM: nazwa "Ubezpieczenie PREMIUM", cena 50 zł/dobę, aktywne, opis krótki
+ - Zapisz, przeładuj — wartości zostały
+ 2. **Frontend:** Otwórz carei.pagedev.pl → kliknij przycisk rezerwacji.
+ - Wybierz segment, oddział, daty (3 dni)
+ - Sekcja "Pakiety ochronne" — widzisz u góry 2 kafelki SOFT+PREMIUM, pod nimi linia separatora, następnie pozostałe opcje API (jeśli są)
+ - SOFT pokazuje "25 zł/doba = 75 zł", PREMIUM "50 zł/doba = 150 zł"
+ 3. **Interakcja:** Kliknij SOFT → podświetlony. Kliknij PREMIUM → SOFT traci selekcję, PREMIUM zaznaczony. Kliknij PREMIUM ponownie → odznaczony.
+ 4. **Recalc:** Zmień daty na 7 dni → totale: SOFT 175 zł, PREMIUM 350 zł.
+ 5. **Submit:** Wybierz SOFT, dokończ flow → podsumowanie pokazuje "Ubezpieczenie SOFT — 175 zł" jako pozycję. Potwierdź rezerwację.
+ 6. **CPT:** wp-admin → Rezerwacje → otwórz nową pozycję → meta box zawiera informację o wybranym pakiecie.
+ 7. **Inactive test:** Ustaw SOFT active=false w adminie → modal pokazuje tylko PREMIUM.
+ 8. **Mobile (360×640):** Kafelki stackują się pionowo, czytelne, klikalne.
+
+ Napisz "approved" aby kontynuować do UNIFY, lub opisz problemy do naprawy.
+
+
+
+
+
+
+## DO NOT CHANGE
+- `class-softra-api.php` — klient Softra API bez zmian
+- `buildExtraCard()` / `#carei-extras-container` / `input[name="extras[]"]` — logika regular extras pozostaje
+- `#carei-insurance-container` (istniejąca logika filtrowania pozycji ubezpieczeniowych z pricelist API) — pozostaje jako fallback dla pozycji z Softra, gdyby były
+- Sekcja "Wyjazd zagraniczny" i "Opcje dodatkowe"
+- Hero search form widget
+- Flow booking (customer/add → makebooking → confirm) poza dołączeniem pakietu do payload
+
+## SCOPE LIMITS
+- Brak pricing progowego (min/per-day/max) — ten pomysł z 13-01 jest odrzucony
+- Brak integracji z Softra API dla pakietów — dane tylko z panelu WP
+- Brak tłumaczeń multi-lang — hardcoded polski (spójne z resztą pluginu)
+- Brak custom DB table — wp_options wystarczy
+- Brak więcej niż 2 pakietów w UI adminu — tylko SOFT i PREMIUM (fixed)
+- Brak refaktoryzacji `getSelectedExtrasForApi()` — tylko dopisanie warunkowej gałęzi
+
+
+
+
+Before declaring plan complete:
+- [ ] Panel admina "Pakiety ochronne" dostępny i zapisuje dane do wp_options
+- [ ] `GET /wp-json/carei/v1/protection-packages` zwraca poprawny JSON (z filtrem active)
+- [ ] Modal renderuje 2 kafelki nad istniejącym kontenerem insurance, z separatorem
+- [ ] Radio behavior: exclusive + deselect działają
+- [ ] Total przelicza się przy zmianie dat
+- [ ] Wybrany pakiet dołączany do priceItems w payloadzie booking
+- [ ] Pakiet widoczny w summary overlay i w CPT carei_reservation
+- [ ] Inactive pakiet ukryty w UI
+- [ ] Brak regresji w extras, abroad, reszcie formularza
+- [ ] Responsywność mobile (< 640px) działa
+- [ ] Wszystkie AC-1 – AC-5 spełnione
+
+
+
+- Wszystkie taski zakończone
+- Wszystkie checki weryfikacji przechodzą
+- Brak błędów w konsoli przeglądarki i logach PHP
+- Human-verify checkpoint zatwierdzony
+
+
+
diff --git a/.paul/phases/13-protection-packages/13-02-SUMMARY.md b/.paul/phases/13-protection-packages/13-02-SUMMARY.md
new file mode 100644
index 0000000..b4f0c16
--- /dev/null
+++ b/.paul/phases/13-protection-packages/13-02-SUMMARY.md
@@ -0,0 +1,173 @@
+---
+phase: 13-protection-packages
+plan: 02
+subsystem: ui
+tags: [wordpress, rest-api, elementor, admin-panel, vanilla-js, insurance]
+
+requires:
+ - phase: 05-admin-panel
+ provides: CPT carei_reservation, save_reservation() static, meta box infrastructure
+ - phase: 03-booking-flow
+ provides: booking payload pipeline, priceItems convention, summary overlay
+
+provides:
+ - Panel admina "Pakiety ochronne" (wp-admin → Rezerwacje → Pakiety ochronne)
+ - REST endpoint GET /carei/v1/protection-packages (public)
+ - Radio cards SOFT/PREMIUM w modalu z wizualnym separatorem od pozycji API
+ - Integracja z summary overlay (details list + price table + totals)
+ - Zapis wybranego pakietu w post_meta _carei_protection_package
+ - Info o pakiecie doklejane do comments booking (rezerwacja Softra widzi pakiet w uwagach)
+
+affects: [future-insurance-adjustments, pricing-customizations, admin-ux]
+
+tech-stack:
+ added: []
+ patterns:
+ - "WP options + REST proxy jako źródło danych niezależne od Softra"
+ - "Hybrydowe podsumowanie: pozycje z Softra + lokalne dodatki łączone w summary overlay"
+ - "Rozszerzenie comments Softra o strukturalne info pakietu (fallback dla braku dedicated field)"
+
+key-files:
+ created:
+ - ".paul/phases/13-protection-packages/13-02-PLAN.md"
+ - ".paul/phases/13-protection-packages/13-02-SUMMARY.md"
+ modified:
+ - "wp-content/plugins/carei-reservation/includes/class-admin-panel.php"
+ - "wp-content/plugins/carei-reservation/includes/class-rest-proxy.php"
+ - "wp-content/plugins/carei-reservation/includes/class-elementor-widget.php"
+ - "wp-content/plugins/carei-reservation/assets/js/carei-reservation.js"
+ - "wp-content/plugins/carei-reservation/assets/css/carei-reservation.css"
+
+key-decisions:
+ - "Dane pakietów w wp_options (opcja carei_protection_packages) — nie CPT ani custom table"
+ - "Pakiet poza Softra priceItems — HTTP 400 przy próbie wysłania fałszywych ID"
+ - "Pakiet w comments booking + osobny protectionPackage w payloadzie dla CPT"
+ - "Summary frontend dokłada wiersz pakietu i oblicza grandTotal = softraGross + protectionTotal"
+ - "Plan 13-01 superseded — pricing progowy odrzucony przez klienta na rzecz prostej ceny/dobę z WP"
+
+patterns-established:
+ - "REST route dla danych WP-managed: __return_true permission (publiczne read-only)"
+ - "Filtrowanie active=true po stronie endpointu, frontend dostaje tylko aktywne"
+ - "Radio z deselect przez JS click handler (natywne radio nie odznacza)"
+ - "accent-color: var(--carei-blue) dla natywnych radio spójnych z design systemem"
+
+duration: ~3h
+started: 2026-04-20T12:00:00Z
+completed: 2026-04-20T15:30:00Z
+---
+
+# Phase 13 Plan 02: Pakiety ochronne SOFT+PREMIUM — Summary
+
+**Dwa pakiety ochronne z ceną/doba zarządzane w panelu WP (wp_options), renderowane jako radio cards w modalu nad opcjami API, doliczane lokalnie w summary i przesyłane do Softra przez comments + lokalny meta CPT.**
+
+## Performance
+
+| Metric | Value |
+|--------|-------|
+| Duration | ~3h |
+| Started | 2026-04-20T12:00:00Z |
+| Completed | 2026-04-20T15:30:00Z |
+| Tasks | 3 auto + 1 checkpoint (human-verify approved) |
+| Files modified | 5 |
+
+## Acceptance Criteria Results
+
+| Criterion | Status | Notes |
+|-----------|--------|-------|
+| AC-1: Panel administratora — konfiguracja pakietów | ✅ Pass | Submenu "Pakiety ochronne" w edit.php?post_type=carei_reservation, formularz z nazwą/ceną/aktywnym/opisem dla SOFT+PREMIUM, zapis w carei_protection_packages |
+| AC-2: Endpoint REST z danymi pakietów | ✅ Pass | GET /wp-json/carei/v1/protection-packages zwraca {soft, premium} z filtrem active=true |
+| AC-3: Render kafelków z separatorem | ✅ Pass | #carei-protection-packages-container nad #carei-insurance-container, .carei-form__protection-divider oddziela wizualnie |
+| AC-4: Wybór i dynamiczne przeliczanie | ✅ Pass (zmieniono UX) | Radio z deselect działa; sumowanie × days wyłączone w kafelku (decyzja UX z klientem — tylko "X zł/doba" szare po prawej, total dopiero w summary) |
+| AC-5: Pakiet w booking submission i CPT | ✅ Pass (rozwiązanie hybrydowe) | Pakiet w comments booking + protectionPackage w payloadzie + _carei_protection_package meta CPT + wiersz w summary overlay (details list + price table + totals) |
+
+## Accomplishments
+
+- **Zarządzanie niezależne od Softra** — admin może zmieniać ceny pakietów bez dotykania API wynajmowanego przez zewnętrznego dostawcę
+- **Rozwiązanie konfliktu API** — po pierwszej próbie wysłania pakietów jako `priceItems` Softra zwracała HTTP 400; refactor w locie: pakiet wyłączony z priceItems, doklejony do comments + zobaczony w summary po stronie frontu
+- **Spójność wizualna** — kafelki pakietów identyczne stylem z `.carei-form__extra-card` (nazwa blue, cena szara po prawej, bold, natywny radio z accent-color)
+- **Pełny cykl życia rezerwacji** — pakiet od momentu wyboru (radio) → comments Softra → CPT meta → meta box admina → wyświetlenie w formacie "nazwa — cena/doba × dni = total"
+
+## Files Created/Modified
+
+| File | Change | Purpose |
+|------|--------|---------|
+| `.paul/phases/13-protection-packages/13-02-PLAN.md` | Created | Plan zastępujący 13-01 (nowe założenia klienta) |
+| `.paul/phases/13-protection-packages/13-02-SUMMARY.md` | Created | Ten dokument |
+| `wp-content/plugins/carei-reservation/includes/class-admin-panel.php` | Modified | Submenu page, handler POST, get_protection_packages() static, rozszerzenie save_reservation() o protection_package meta, wiersz w meta boxie |
+| `wp-content/plugins/carei-reservation/includes/class-rest-proxy.php` | Modified | Rejestracja GET /protection-packages, callback get_protection_packages() z filtrem active |
+| `wp-content/plugins/carei-reservation/includes/class-elementor-widget.php` | Modified | Nowy #carei-protection-packages-container + .carei-form__protection-divider nad #carei-insurance-container |
+| `wp-content/plugins/carei-reservation/assets/js/carei-reservation.js` | Modified | protectionPackages state, loadProtectionPackages(), renderProtectionPackages() z radio + deselect, getSelectedProtectionPayload(), buildBookingComments(), integracja w showSummaryOverlay (details + table + totals) |
+| `wp-content/plugins/carei-reservation/assets/css/carei-reservation.css` | Modified | Style kafelków pakietów (flex row, name blue, price gray right, radio 16×16 accent-color, separator dashed), mobile stack |
+
+## Decisions Made
+
+| Decision | Rationale | Impact |
+|----------|-----------|--------|
+| Pakiet NIE w priceItems Softra | HTTP 400 przy próbie wysłania fałszywego ID — Softra waliduje wobec swojej bazy | Wymagane osobne kanały: comments (widoczne pracownikom wypożyczalni), protectionPackage payload (zapis WP), summary UI (widoczne klientowi) |
+| UX: brak totala × days w kafelku | Klient poprosił o spójność z extras (tylko "X zł/doba" szary po prawej), total dopiero w summary | Prostszy kafelek, `updateProtectionTotals()` usunięty jako martwy kod, listenery dat oczyszczone |
+| Native radio + accent-color | Spójne z reszta formularza, user prosił o widoczny wybór | Minimalny CSS, natywna accessibility, JS tylko dla deselect |
+| Plan 13-01 superseded zamiast skasowany | Historia decyzji klienta zachowana dla przyszłego audytu | 13-01-PLAN.md pozostaje w repo z notatką supersession w ROADMAP |
+
+## Deviations from Plan
+
+### Summary
+
+| Type | Count | Impact |
+|------|-------|--------|
+| Auto-fixed | 2 | Konieczne korekty po błędach runtime |
+| Scope additions | 1 | Feedback UX w trakcie: prostszy kafelek |
+| Deferred | 0 | — |
+
+**Total impact:** Wszystkie deviations wynikły z feedback usera w trakcie APPLY (3 iteracje UI + 1 fix błędu API). Żadna nie wychodziła poza cel planu.
+
+### Auto-fixed Issues
+
+**1. [API] HTTP 400 z Softra przy wysyłaniu pakietu jako priceItem**
+- **Found during:** Task 3 (frontend JS) po wdrożeniu i kliknięciu "Pokaż podsumowanie"
+- **Issue:** `priceItems` zawierał `id: 'protection_soft'` — Softra nie zna tego ID i zwracała 400 Bad Request
+- **Fix:** Usunięto pakiet z `getSelectedExtrasForApi()`, dodano jako osobną linię w `comments` booking (`buildBookingComments()`) oraz jako wiersz w summary frontend (details list + price table + totals), grandGross = softraGross + protectionTotal
+- **Files:** `carei-reservation.js`
+- **Verification:** User potwierdził brak błędu + widoczność pakietu w summary
+
+**2. [UX] Pakiet niewidoczny w liście "Wybrane opcje" summary**
+- **Found during:** Weryfikacja po fixie HTTP 400
+- **Issue:** `summaryDetails` iterował po `selectedExtras` z `getSelectedExtrasForApi()`, który nie zawiera już pakietu
+- **Fix:** Dodany warunkowy wiersz w liście używający `getSelectedProtectionPayload()` z formatem analogicznym do extras (`X zł/doba × N = total zł`)
+- **Files:** `carei-reservation.js`
+- **Verification:** User potwierdził widoczność
+
+### Scope Additions
+
+**1. [UX] Uproszczenie kafelka (usunięcie total × days z popupu)**
+- User request podczas APPLY: "pokazuj tak jak ceny pozostałych elementów, czyli po prawej wszystko szarym kolorem i bez sumowania za liczbę dni, to dopiero w kolejnym widoku podsumowania"
+- Efekt: `updateProtectionTotals()` usunięte jako martwy kod, listenery dat oczyszczone, CSS kafelków dostosowany do wzorca `.carei-form__extra-card`
+
+### Deferred Items
+
+None.
+
+## Issues Encountered
+
+| Issue | Resolution |
+|-------|------------|
+| Softra HTTP 400 | Refactor w locie — pakiet wyłączony z priceItems, dostarczany przez comments + meta + summary UI (patrz Auto-fixed #1) |
+| Radio niewidoczny (pierwotnie hidden) | User request: "brakuje checkboxa wyboru wersji ubezpieczenia" → native radio w row z accent-color |
+| Cena nie pogrubiona | User request → usunięcie override `font-weight: 400` na `.carei-form__protection-package__price strong` |
+
+## Next Phase Readiness
+
+**Ready:**
+- Milestone v0.5 zamykany (Phase 13 ✅ + Phase 14 ✅ = 100%)
+- Wzorzec "WP-managed dane + REST public endpoint + integracja z summary" gotowy do reuse w kolejnych niezależnych od Softra customizacjach
+- Admin ma pełną kontrolę nad cenami bez wymagań modyfikacji Softra
+
+**Concerns:**
+- VAT dla pakietu traktowany jako brutto bez rozbicia netto/VAT — wystarczy dla MVP, ale jeśli klient będzie wymagał księgowego rozbicia, trzeba dodać `vatRate` w adminie i kalkulację
+- Comments Softra ma limit długości nieznany — w przyszłości rozważyć dedykowany field w integracji, jeśli Softra doda taki
+
+**Blockers:**
+- None
+
+---
+*Phase: 13-protection-packages, Plan: 02*
+*Completed: 2026-04-20*
diff --git a/wp-content/plugins/carei-reservation/assets/css/carei-reservation.css b/wp-content/plugins/carei-reservation/assets/css/carei-reservation.css
index a04ecab..7c6fdb7 100644
--- a/wp-content/plugins/carei-reservation/assets/css/carei-reservation.css
+++ b/wp-content/plugins/carei-reservation/assets/css/carei-reservation.css
@@ -597,6 +597,89 @@ button.carei-reservation-trigger:hover {
color: inherit;
}
+/* ═══════════════════════════════════════════
+ Protection Packages (SOFT / PREMIUM — zarządzane w panelu WP)
+ ═══════════════════════════════════════════ */
+.carei-form__row--protection-packages {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 12px;
+}
+.carei-form__protection-package {
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+ padding: 16px;
+ border: 1px solid var(--carei-border);
+ border-radius: var(--carei-radius);
+ background: var(--carei-white);
+ cursor: pointer;
+ transition: border-color 0.2s;
+ min-width: 0;
+ overflow: hidden;
+}
+.carei-form__protection-package:hover {
+ border-color: rgba(47, 36, 130, 0.4);
+}
+.carei-form__protection-package.is-selected {
+ border-color: var(--carei-blue);
+}
+.carei-form__protection-package__row {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ min-width: 0;
+}
+.carei-form__protection-package__input {
+ flex-shrink: 0;
+ width: 16px;
+ height: 16px;
+ accent-color: var(--carei-blue);
+ cursor: pointer;
+ margin: 0;
+}
+.carei-form__protection-package__name {
+ font-weight: 600;
+ font-size: 15px;
+ color: var(--carei-blue);
+ flex: 1;
+ min-width: 0;
+ word-break: break-word;
+}
+.carei-form__protection-package__price {
+ font-weight: 400;
+ font-size: 14px;
+ color: #505050;
+ margin-left: auto;
+ white-space: nowrap;
+ flex-shrink: 0;
+}
+.carei-form__protection-package__price strong {
+ color: inherit;
+}
+.carei-form__protection-package__desc {
+ font-size: 12px;
+ color: #666;
+ line-height: 1.4;
+}
+.carei-form__protection-package__desc:empty {
+ display: none;
+}
+.carei-form__protection-divider {
+ margin: 14px 0;
+ border-top: 1px dashed rgba(47, 36, 130, 0.18);
+ height: 0;
+}
+.carei-form__row--protection-packages:empty + .carei-form__protection-divider {
+ display: none;
+}
+
+@media (max-width: 640px) {
+ .carei-form__row--protection-packages {
+ grid-template-columns: 1fr;
+ }
+}
+
.carei-form__checkbox-label--abroad {
align-items: center;
font-weight: 600;
diff --git a/wp-content/plugins/carei-reservation/assets/js/carei-reservation.js b/wp-content/plugins/carei-reservation/assets/js/carei-reservation.js
index 8d6bc67..a8d94b1 100644
--- a/wp-content/plugins/carei-reservation/assets/js/carei-reservation.js
+++ b/wp-content/plugins/carei-reservation/assets/js/carei-reservation.js
@@ -83,6 +83,9 @@
var summaryOverlay, summaryDetails, summaryTable, summaryTotal, summaryError;
var summaryBack, summaryConfirm;
var successView, successNumber, successClose;
+ var protectionContainer;
+ var protectionPackages = { soft: null, premium: null };
+ var selectedProtectionKey = null;
function initRefs() {
overlay = document.querySelector('[data-carei-modal]');
@@ -102,6 +105,7 @@
extrasWrapper = document.getElementById('carei-extras-wrapper');
extrasContainer = document.getElementById('carei-extras-container');
insuranceContainer = document.getElementById('carei-insurance-container');
+ protectionContainer = document.getElementById('carei-protection-packages-container');
abroadSection = document.getElementById('carei-abroad-section');
abroadToggle = document.getElementById('carei-abroad-toggle');
abroadSearch = document.getElementById('carei-abroad-search');
@@ -419,6 +423,69 @@
daysCount.innerHTML = 'Wybrano: ' + diff + ' ' + (diff === 1 ? 'dzień' : 'dni') + '';
}
+ // ─── Protection Packages (WP-managed: SOFT, PREMIUM) ──────────
+
+ function loadProtectionPackages() {
+ return fetch(REST_URL + 'protection-packages', {
+ credentials: 'same-origin'
+ }).then(function (r) { return r.ok ? r.json() : null; })
+ .then(function (data) {
+ if (!data || typeof data !== 'object') return;
+ protectionPackages.soft = data.soft || null;
+ protectionPackages.premium = data.premium || null;
+ renderProtectionPackages();
+ }).catch(function (err) { console.error('Failed to load protection packages:', err); });
+ }
+
+ function renderProtectionPackages() {
+ if (!protectionContainer) return;
+ protectionContainer.innerHTML = '';
+ ['soft', 'premium'].forEach(function (key) {
+ var pkg = protectionPackages[key];
+ if (!pkg) return;
+ var price = parseFloat(pkg.pricePerDay || 0);
+ var priceLabel = price > 0 ? price.toFixed(0) + ' zł/doba' : 'Gratis';
+ var descHtml = pkg.description ? '' + escHtml(pkg.description) + '' : '';
+ var card = document.createElement('label');
+ card.className = 'carei-form__protection-package';
+ card.setAttribute('data-key', key);
+ card.setAttribute('data-price', price);
+ card.innerHTML =
+ '' +
+ '' +
+ '' + escHtml(pkg.name || '') + '' +
+ '' + escHtml(priceLabel) + '' +
+ '' +
+ descHtml;
+ card.addEventListener('click', function (e) {
+ e.preventDefault();
+ onProtectionCardClick(key);
+ });
+ protectionContainer.appendChild(card);
+ });
+ }
+
+ function onProtectionCardClick(key) {
+ if (!protectionContainer) return;
+ if (selectedProtectionKey === key) {
+ selectedProtectionKey = null;
+ } else {
+ selectedProtectionKey = key;
+ }
+ var cards = protectionContainer.querySelectorAll('.carei-form__protection-package');
+ cards.forEach(function (card) {
+ var k = card.getAttribute('data-key');
+ var input = card.querySelector('input');
+ if (k === selectedProtectionKey) {
+ card.classList.add('is-selected');
+ if (input) input.checked = true;
+ } else {
+ card.classList.remove('is-selected');
+ if (input) input.checked = false;
+ }
+ });
+ }
+
// ─── Load Extras from Pricelist ───────────────────────────────
function loadExtras() {
@@ -875,9 +942,33 @@
priceAfterDiscount: price
});
});
+ // UWAGA: pakiet ochronny (SOFT/PREMIUM) zarządzany w panelu WP
+ // NIE jest dołączany do priceItems — Softra nie zna tych ID (zwróciłaby HTTP 400).
+ // Jest liczony osobno w getSelectedProtectionPayload() i dodawany do podsumowania po stronie frontu.
return items;
}
+ function getSelectedProtectionPayload() {
+ if (!selectedProtectionKey || !protectionPackages[selectedProtectionKey]) return null;
+ var pkg = protectionPackages[selectedProtectionKey];
+ var pricePerDay = parseFloat(pkg.pricePerDay || 0);
+ var days = getRentalDays();
+ return {
+ key: selectedProtectionKey,
+ name: pkg.name,
+ pricePerDay: pricePerDay,
+ days: days,
+ total: pricePerDay * days
+ };
+ }
+
+ function buildBookingComments(userMessage) {
+ var pkg = getSelectedProtectionPayload();
+ if (!pkg) return userMessage || '';
+ var pkgLine = 'Pakiet ochronny: ' + pkg.name + ' — ' + pkg.pricePerDay.toFixed(2) + ' zł/doba × ' + pkg.days + ' = ' + pkg.total.toFixed(2) + ' zł (do doliczenia poza systemem)';
+ return userMessage ? (pkgLine + '\n\n' + userMessage) : pkgLine;
+ }
+
// ─── Step Transitions ──────────────────────────────────────────
function hideStep(el) {
@@ -961,7 +1052,8 @@
// Selected extras
var selectedExtras = getSelectedExtrasForApi();
- if (selectedExtras.length > 0) {
+ var pkgForDetails = getSelectedProtectionPayload();
+ if (selectedExtras.length > 0 || pkgForDetails) {
html += '
Konfiguracja pakietów wyświetlanych w sekcji Pakiety ochronne formularza rezerwacji. Cena podawana jest za dobę — total = cena × liczba dób rezerwacji.