From 48a51557722e80e0dd767dceb03631c0d2361674 Mon Sep 17 00:00:00 2001
From: Zan <62830223+Zan1456@users.noreply.github.com>
Date: Fri, 17 Oct 2025 21:10:23 +0200
Subject: [PATCH] w
---
firka/LICENSE | 661 ++++++
firka/README.md | 96 +
firka/absences/absences.css | 485 ++++
firka/absences/absences.js | 638 ++++++
firka/dashboard/dashboard.css | 449 ++++
firka/dashboard/dashboard.js | 588 +++++
firka/fonts/Figtree-Regular.woff2 | Bin 0 -> 23324 bytes
firka/fonts/Icons.woff2 | Bin 0 -> 173620 bytes
firka/fonts/Montserrat-Medium.woff2 | Bin 0 -> 127352 bytes
firka/fonts/Montserrat-Regular.woff2 | Bin 0 -> 126576 bytes
firka/fonts/Montserrat-SemiBold.woff2 | Bin 0 -> 128544 bytes
firka/forgotpassword/forgotpassword.css | 57 +
firka/forgotpassword/forgotpassword.js | 290 +++
firka/global/language.js | 189 ++
firka/global/maintenance.css | 57 +
firka/global/maintenance.js | 106 +
firka/global/navigation.css | 332 +++
firka/global/navigation.js | 177 ++
firka/global/theme.css | 129 ++
firka/global/theme.js | 197 ++
firka/grades/chart.js | 14 +
firka/grades/grades.css | 827 +++++++
firka/grades/grades.js | 790 +++++++
firka/i18n/en.json | 463 ++++
firka/i18n/hu.json | 465 ++++
firka/icons/ArrowsExpandFull.svg | 1 +
firka/icons/BadgeCheck.svg | 1 +
firka/icons/Calendar.svg | 1 +
firka/icons/ChevronLeftCircle.svg | 1 +
firka/icons/ChevronRightCircle.svg | 1 +
firka/icons/CloseCircle.svg | 1 +
firka/icons/Subject.svg | 1 +
firka/icons/absences-active.svg | 1 +
firka/icons/absences-inactive.svg | 5 +
firka/icons/assigment.svg | 1 +
firka/icons/close.svg | 1 +
firka/icons/dashboard-active.svg | 3 +
firka/icons/dashboard-inactive.svg | 5 +
firka/icons/delete.svg | 1 +
firka/icons/dkt.svg | 5 +
firka/icons/eye-off.svg | 1 +
firka/icons/eye-on.svg | 1 +
firka/icons/grades-active.svg | 3 +
firka/icons/grades-inactive.svg | 5 +
firka/icons/homework.svg | 1 +
firka/icons/logout.svg | 4 +
firka/icons/messages-active.svg | 1 +
firka/icons/messages-inactive.svg | 1 +
firka/icons/naplo.svg | 3 +
firka/icons/open-link.svg | 1 +
firka/icons/others.svg | 5 +
firka/icons/pipa.svg | 1 +
firka/icons/plus.svg | 1 +
firka/icons/profile.svg | 1 +
firka/icons/settings.svg | 1 +
firka/icons/timetable-active.svg | 4 +
firka/icons/timetable-inactive.svg | 5 +
firka/images/cactus.png | Bin 0 -> 43397 bytes
firka/images/chrome.png | Bin 0 -> 21845 bytes
firka/images/firefox.png | Bin 0 -> 54652 bytes
firka/images/firefoxact.png | Bin 0 -> 46372 bytes
firka/images/firka_logo.png | Bin 0 -> 1427 bytes
firka/images/firka_logo_128.png | Bin 0 -> 10004 bytes
firka/images/loading.gif | Bin 0 -> 16742 bytes
firka/login/login.css | 306 +++
firka/login/login.js | 201 ++
firka/login/twofactor.css | 254 +++
firka/login/twofactor.js | 186 ++
firka/logout/logout.css | 117 +
firka/logout/logout.js | 82 +
firka/manifest.json | 230 ++
firka/manifest_fox.json | 232 ++
firka/messages/messages.css | 547 +++++
firka/messages/messages.js | 340 +++
firka/profile/profile.css | 485 ++++
firka/profile/profile.js | 228 ++
firka/roleselect/roleselect.css | 323 +++
firka/roleselect/roleselect.js | 179 ++
firka/search/search.css | 196 ++
firka/search/search.js | 144 ++
firka/settings/index.css | 606 +++++
firka/settings/index.html | 236 ++
firka/settings/index.js | 570 +++++
firka/timetable/timetable.css | 1534 +++++++++++++
firka/timetable/timetable.js | 2751 +++++++++++++++++++++++
firka/tools/background.js | 106 +
firka/tools/createTemplate.js | 119 +
firka/tools/helper.js | 40 +
firka/tools/loadingScreen.css | 53 +
firka/tools/loadingScreen.js | 77 +
firka/tools/storageManager.js | 144 ++
91 files changed, 17364 insertions(+)
create mode 100644 firka/LICENSE
create mode 100644 firka/README.md
create mode 100644 firka/absences/absences.css
create mode 100644 firka/absences/absences.js
create mode 100644 firka/dashboard/dashboard.css
create mode 100644 firka/dashboard/dashboard.js
create mode 100644 firka/fonts/Figtree-Regular.woff2
create mode 100644 firka/fonts/Icons.woff2
create mode 100644 firka/fonts/Montserrat-Medium.woff2
create mode 100644 firka/fonts/Montserrat-Regular.woff2
create mode 100644 firka/fonts/Montserrat-SemiBold.woff2
create mode 100644 firka/forgotpassword/forgotpassword.css
create mode 100644 firka/forgotpassword/forgotpassword.js
create mode 100644 firka/global/language.js
create mode 100644 firka/global/maintenance.css
create mode 100644 firka/global/maintenance.js
create mode 100644 firka/global/navigation.css
create mode 100644 firka/global/navigation.js
create mode 100644 firka/global/theme.css
create mode 100644 firka/global/theme.js
create mode 100644 firka/grades/chart.js
create mode 100644 firka/grades/grades.css
create mode 100644 firka/grades/grades.js
create mode 100644 firka/i18n/en.json
create mode 100644 firka/i18n/hu.json
create mode 100644 firka/icons/ArrowsExpandFull.svg
create mode 100644 firka/icons/BadgeCheck.svg
create mode 100644 firka/icons/Calendar.svg
create mode 100644 firka/icons/ChevronLeftCircle.svg
create mode 100644 firka/icons/ChevronRightCircle.svg
create mode 100644 firka/icons/CloseCircle.svg
create mode 100644 firka/icons/Subject.svg
create mode 100644 firka/icons/absences-active.svg
create mode 100644 firka/icons/absences-inactive.svg
create mode 100644 firka/icons/assigment.svg
create mode 100644 firka/icons/close.svg
create mode 100644 firka/icons/dashboard-active.svg
create mode 100644 firka/icons/dashboard-inactive.svg
create mode 100644 firka/icons/delete.svg
create mode 100644 firka/icons/dkt.svg
create mode 100644 firka/icons/eye-off.svg
create mode 100644 firka/icons/eye-on.svg
create mode 100644 firka/icons/grades-active.svg
create mode 100644 firka/icons/grades-inactive.svg
create mode 100644 firka/icons/homework.svg
create mode 100644 firka/icons/logout.svg
create mode 100644 firka/icons/messages-active.svg
create mode 100644 firka/icons/messages-inactive.svg
create mode 100644 firka/icons/naplo.svg
create mode 100644 firka/icons/open-link.svg
create mode 100644 firka/icons/others.svg
create mode 100644 firka/icons/pipa.svg
create mode 100644 firka/icons/plus.svg
create mode 100644 firka/icons/profile.svg
create mode 100644 firka/icons/settings.svg
create mode 100644 firka/icons/timetable-active.svg
create mode 100644 firka/icons/timetable-inactive.svg
create mode 100644 firka/images/cactus.png
create mode 100644 firka/images/chrome.png
create mode 100644 firka/images/firefox.png
create mode 100644 firka/images/firefoxact.png
create mode 100644 firka/images/firka_logo.png
create mode 100644 firka/images/firka_logo_128.png
create mode 100644 firka/images/loading.gif
create mode 100644 firka/login/login.css
create mode 100644 firka/login/login.js
create mode 100644 firka/login/twofactor.css
create mode 100644 firka/login/twofactor.js
create mode 100644 firka/logout/logout.css
create mode 100644 firka/logout/logout.js
create mode 100644 firka/manifest.json
create mode 100644 firka/manifest_fox.json
create mode 100644 firka/messages/messages.css
create mode 100644 firka/messages/messages.js
create mode 100644 firka/profile/profile.css
create mode 100644 firka/profile/profile.js
create mode 100644 firka/roleselect/roleselect.css
create mode 100644 firka/roleselect/roleselect.js
create mode 100644 firka/search/search.css
create mode 100644 firka/search/search.js
create mode 100644 firka/settings/index.css
create mode 100644 firka/settings/index.html
create mode 100644 firka/settings/index.js
create mode 100644 firka/timetable/timetable.css
create mode 100644 firka/timetable/timetable.js
create mode 100644 firka/tools/background.js
create mode 100644 firka/tools/createTemplate.js
create mode 100644 firka/tools/helper.js
create mode 100644 firka/tools/loadingScreen.css
create mode 100644 firka/tools/loadingScreen.js
create mode 100644 firka/tools/storageManager.js
diff --git a/firka/LICENSE b/firka/LICENSE
new file mode 100644
index 0000000..0ad25db
--- /dev/null
+++ b/firka/LICENSE
@@ -0,0 +1,661 @@
+ GNU AFFERO GENERAL PUBLIC LICENSE
+ Version 3, 19 November 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+
+ Firka extension
+
+ Modern, testre szabható felhasználói felület az e-KRÉTA rendszerhez +
+ + + + + +## 📱 Funkciók + +- **Modern Dizájn**: Teljesen újratervezett, modern felhasználói felület +- **Személyre Szabható Témák**: Világos és sötét mód vagy akár egyedi témáddal +- **Továbbfejlesztett Felületek**: + - Átdolgozott bejelentkezési képernyő + - Átláthatóbb jegynapló + - Modernizált órarend + - Fejlett hiányzás kezelés + - Egyszerűsített szerepkör választó + - Új kezdőlap elrendezés + +## 🚀 Telepítés + +1. Töltsd le a legfrissebb verziót a fenti gombok segítségével a böngésződnek megfelelően. + +## ⚙️ Beállítások + +A bővítmény beállításait a böngésző eszköztárán található Firka ikonra kattintva érheted el. Itt módosíthatod: + +- A felület témáját, legyen az világos, sötét vagy akár egyedi témáddal. + +## 💡 Támogatott Oldalak + +A bővítmény jelenleg az alábbi e-KRÉTA oldalakat támogatja: + +- Bejelentkezés +- Kijelentkezés +- Szerepkörválasztó +- Órarend (Házi feladatok, Számonkérések) +- Faliújság +- Hiányzások +- Jegyek +- Intézménykereső +- Üzenetek +- Profil (Béta) + +## 👥 Csapat + +- **[Zan1456](https://github.com/Zan1456)** - Vezető Fejlesztő +- **[Xou](https://yoursit.ee/xou)** - Designer + +## 🤝 Közreműködés + +Örömmel fogadunk minden fejlesztési javaslatot és hibajelentést! Ha szeretnél hozzájárulni a projekthez: + +1. Fork-old a repository-t +2. Hozz létre egy új branch-et a fejlesztésednek +3. Commit-old a változtatásaidat +4. Push-old a branch-et +5. Nyiss egy Pull Request-et + +## 📝 Licensz + +A projekt [GNU Affero General Public License v3.0](LICENSE) alatt jelent meg. További információért lásd a LICENSE fájlt. + +## 💬 Kapcsolat + +- Discord: [discord.gg/6DvjyPAw2T](https://discord.gg/6DvjyPAw2T) +- GitHub: [QwIT-Development](https://github.com/QwIT-Development/) + +--- + ++ Készült ❤️-vel diákoktól diákoknak +
diff --git a/firka/absences/absences.css b/firka/absences/absences.css new file mode 100644 index 0000000..747bd52 --- /dev/null +++ b/firka/absences/absences.css @@ -0,0 +1,485 @@ +* { + box-sizing:border-box; + margin:0; + padding:0; +} +body { + margin:0; + padding:0; + color:var(--text-primary); + background-color:var(--background) !important; + font-family:"Montserrat",serif !important; + min-height:100vh; + font-size:16px; +} +@media (max-width:768px) { + body { + font-size:14px; +} +}.kreta-container { + min-height:100vh; + display:flex; + flex-direction:column; +} +.kreta-header { + padding:clamp(1rem,3vw,2rem); + display:grid; + grid-template-columns:minmax(300px,400px) 1fr minmax(200px,300px); + align-items:center; + gap:1rem; +} +@media (max-width:1200px) { + .kreta-header { + grid-template-columns:minmax(250px,350px) 1fr minmax(180px,250px); +} +}@media (max-width:768px) { + .kreta-header { + grid-template-columns:1fr auto auto; + grid-template-areas:"school toggle user" + "nav nav nav"; + padding:1rem; + gap:0.5rem; +} +}.school-info { + margin:0; +} +@media (max-width:768px) { + .school-info { + grid-area:school; + max-width:none; + display:flex; + align-items:center; + gap:0.5rem; +} +}.logo-text { + color:var(--text-primary); + font-size:24px; + font-weight:600; + margin:0 0 0.5rem; + display:flex; + align-items:center; +} +@media (max-width:768px) { + .logo-text { + margin:0; + font-size:20px; +} +}.logo { + width:24px; + border-radius:8px; + margin-right:0.5rem; +} +.school-details { + color:var(--text-secondary); + font-size:14px; +} +.school-details span { + display:block; + white-space:nowrap; + overflow:hidden; + text-overflow:ellipsis; + max-width:300px; +} +@media (max-width:768px) { + .school-details span { + max-width:200px; +} +.school-details { + font-size:12px; +} +}.user-profile { + position:relative; + justify-self:flex-end; +} +@media (max-width:768px) { + .user-profile { + grid-area:user; +} +}.user-dropdown-btn { + display:flex; + align-items:center; + gap:1rem; + background:none; + border:none; + cursor:pointer; + padding:0.5rem; + border-radius:8px; + transition:background-color 0.2s; +} +.user-dropdown-btn:hover { + background:var(--card-card); +} +.user-info { + text-align:right; +} +.user-dropdown { + position:absolute; + top:100%; + right:0; + margin-top:0.5rem; + background:var(--card-card); + border-radius:12px; + box-shadow:0 4px 6px -1px rgba(0,0,0,0.1); + width:200px; + display:none; + z-index:1000; +} +.user-dropdown.show { + display:block; + animation:dropdownShow 0.2s ease; +} +.dropdown-item { + display:flex; + align-items:center; + gap:0.75rem; + padding:0.75rem 1rem; + color:var(--text-primary); + text-decoration:none; + transition:background-color 0.2s; +} +.dropdown-item:hover { + background:var(--button-secondaryFill); + text-decoration:none; +} +.kreta-main { + flex:1; + padding:clamp(1rem,3vw,2rem); + max-width:1200px; + margin:0 auto; + width:100%; +} +.filter-card { + background:var(--card-card); + border-radius:24px; + padding:20px; + margin-bottom:24px; + box-shadow:0px 1px var(--shadow-blur) 0px var(--accent-shadow); +} +.filter-header { + margin-bottom:16px; +} +.filter-header h2 { + font-size:18px; + font-weight:600; + color:var(--text-primary); + background-color:var(--card-card); +} +.filter-content { + display:grid; + grid-template-columns:repeat(auto-fit,minmax(200px,1fr)); + gap:16px; +} +.filter-group { + display:flex; + flex-direction:column; + gap:8px; +} +.filter-group label { + display:flex; + align-items:center; + gap:8px; + color:var(--text-secondary); + font-size:14px; +} +.filter-input { + padding:10px; + border:none; + border-radius:8px; + background:var(--button-secondaryFill); + color:var(--text-primary); + font-family:inherit; + font-size:14px; + transition:all 0.2s ease; +} +.filter-input:focus { + outline:none; + box-shadow:0 0 0 2px var(--accent-accent); +} +.stats-overview { + display:grid; + grid-template-columns:repeat(auto-fit,minmax(200px,1fr)); + gap:16px; + margin-bottom:24px; +} +.stat-card { + background:var(--card-card); + border-radius:16px; + padding:20px; + text-align:center; + box-shadow:0px 1px var(--shadow-blur) 0px var(--accent-shadow); + transition:transform 0.2s ease; +} +.stat-card:hover { + transform:translateY(-2px); +} +.stat-number { + font-size:32px; + font-weight:700; + color:var(--accent-accent); + margin-bottom:8px; +} +.stat-label { + color:var(--text-secondary); + font-size:14px; + font-weight:500; +} +.absences-container { + background:var(--card-card); + border-radius:16px; + overflow:hidden; + box-shadow:0px 1px var(--shadow-blur) 0px var(--accent-shadow); +} +.absences-table { + width:100%; + border-collapse:collapse; +} +.table-header { + background:var(--accent-15); + border-bottom:1px solid var(--accent-30); +} +.table-header th { + padding:16px; + text-align:left; + font-weight:600; + color:var(--text-primary); + font-size:14px; + text-transform:uppercase; + letter-spacing:0.5px; +} +.table-row { + border-bottom:1px solid var(--accent-15); + transition:background-color 0.2s ease; +} +.table-row:hover { + background:var(--accent-10); +} +.table-row:last-child { + border-bottom:none; +} +.table-cell { + padding:16px; + color:var(--text-primary); + vertical-align:middle; +} +.date-cell { + font-weight:600; + color:var(--accent-accent); +} +.lesson-cell { + text-align:center; + font-weight:500; +} +.subject-cell { + font-weight:600; +} +.topic-cell { + color:var(--text-secondary); + font-size:14px; + max-width:200px; + overflow:hidden; + text-overflow:ellipsis; + white-space:nowrap; +} +.status-cell { + text-align:center; +} +.status-badge { + display:inline-flex; + align-items:center; + gap:4px; + padding:6px 12px; + border-radius:20px; + font-size:12px; + font-weight:600; + text-transform:uppercase; + letter-spacing:0.5px; +} +.status-badge.justified { + background:var(--grades-4-bg); + color:var(--grades-4); +} +.status-badge.unjustified { + background:var(--grades-1-bg); + color:var(--grades-1); +} +.status-badge.pending { + background:var(--grades-3-bg); + color:var(--grades-3); +} +@media (max-width:768px) { + .absences-table { + font-size:14px; +} +.table-header th,.table-cell { + padding:12px 8px; +} +.topic-cell { + max-width:120px; +} +.stats-overview { + grid-template-columns:repeat(2,1fr); +} +}@media (max-width:480px) { + .absences-table,.table-header,.table-row { + display:block; +} +.table-header { + display:none; +} +.date-group { + margin-bottom:24px; +} +.date-group-header { + background:var(--accent-accent); + color:white; + padding:12px 16px; + border-radius:12px 12px 0 0; + font-weight:600; + font-size:16px; + margin-bottom:0; +} +.date-group-content { + background:var(--card-card); + border:1px solid var(--accent-15); + border-radius:0 0 12px 12px; + overflow:hidden; +} +.table-row { + width:100%; + margin-bottom:0; + border:none; + border-bottom:1px solid var(--accent-15); + border-radius:0; + padding:16px; + background:transparent; +} +.table-row:last-child { + border-bottom:none; +} +.table-cell { + display:flex; + justify-content:space-between; + align-items:flex-start; + padding:6px 0; + border-bottom:1px solid var(--accent-15); +} +.table-cell:last-child { + border-bottom:none; +} +.table-cell::before { + content:attr(data-label); + font-weight:600; + color:var(--text-secondary); + font-size:12px; + text-transform:uppercase; + flex-shrink:0; +} +.topic-cell { + max-width:none; + white-space:normal; + text-overflow:initial; + overflow:visible; + text-align:right; + flex:1; +} +.stats-overview { + grid-template-columns:1fr; +} +}.absence-details { + display:flex; + flex-direction:column; + gap:4px; +} +.absence-subject { + font-weight:600; + color:var(--text-primary); +} +.absence-topic { + color:var(--text-secondary); + font-size:14px; +} +.absence-status { + display:flex; + align-items:center; + gap:4px; + font-size:14px; + font-weight:500; +} +.absence-status.justified { + color:var(--grades-4); +} +.absence-status.unjustified { + color:var(--grades-1); +} +.absence-status.pending { + color:var(--grades-3); +} +.loading-overlay { + position:fixed; + top:0; + left:0; + width:100%; + height:100%; + background:var(--background); + display:flex; + flex-direction:column; + align-items:center; + justify-content:center; + z-index:9999; +} +.loading-container { + display:flex; + flex-direction:column; + align-items:center; + gap:1rem; + padding:2rem; + border-radius:24px; +} +.loading-text { + color:var(--Text-Primary); + text-align:center; + font-family:Montserrat; + font-size:20px; + font-style:normal; + font-weight:700; + line-height:normal; +} +.loading-text2 { + align-self:stretch; + color:var(--Text-Secondary); + text-align:center; + font-family:Figtree; + font-size:16px; + font-style:normal; + font-weight:500; + line-height:130%; +} +.loading-logo { + width:48px; + height:48px; + margin-bottom:1rem; + border-radius:8px; +} +@keyframes fadeIn { + from { + opacity:0; + transform:translateY(-10px); +} +to { + opacity:1; + transform:translateY(0); +} +}@keyframes dropdownShow { + from { + opacity:0; + transform:translateY(-10px); +} +to { + opacity:1; + transform:translateY(0); +} +}@keyframes spin { + to { + transform:rotate(360deg); +} +} \ No newline at end of file diff --git a/firka/absences/absences.js b/firka/absences/absences.js new file mode 100644 index 0000000..0729a96 --- /dev/null +++ b/firka/absences/absences.js @@ -0,0 +1,638 @@ +async function collectAbsencesData() { + const basicData = { + schoolInfo: { + name: await storageManager.get("schoolName", "OM azonosító - Iskola neve"), + id: await storageManager.get("schoolCode", ""), + }, + userData: { + name: await storageManager.get("userName", "Felhasználónév"), + time: + document.querySelector(".usermenu_timer")?.textContent?.trim() || + "45:00", + }, + }; + + try { + const currentDomain = window.location.hostname; + const response = await fetch( + `https://${currentDomain}/api/HianyzasokApi/GetHianyzasGrid?sort=MulasztasDatum-desc&page=1&pageSize=100&group=&filter=&data=%7B%7D&_=${Date.now()}`, + { + method: "GET", + credentials: "include", + headers: { + Accept: "application/json, text/javascript, */*; q=0.01", + "X-Requested-With": "XMLHttpRequest", + }, + }, + ); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const apiData = await response.json(); + const absences = []; + + if (apiData.Data && Array.isArray(apiData.Data)) { + apiData.Data.forEach((item) => { + const date = new Date(item.MulasztasDatum); + const formattedDate = `${date.getFullYear()}.${(date.getMonth() + 1).toString().padStart(2, "0")}.${date.getDate().toString().padStart(2, "0")}.`; + + let justificationStatus = "pending"; + if (item.Igazolt_BOOL === true) { + justificationStatus = "justified"; + } else if (item.Igazolt_BOOL === false && item.IgazolasTipus !== null) { + justificationStatus = "unjustified"; + } + + absences.push({ + date: formattedDate, + lesson: item.Oraszam?.toString() || "", + subject: item.Targy || "", + topic: item.Tema || "", + type: item.MulasztasTipus_DNAME || "", + justified: item.Igazolt_BOOL === true, + justificationStatus: justificationStatus, + purposeful: item.TanoraiCeluMulasztas_BNAME || "", + justificationType: item.IgazolasTipus_DNAME || "", + }); + }); + } + + const groupedAbsences = {}; + absences.forEach((absence) => { + if (!groupedAbsences[absence.date]) { + groupedAbsences[absence.date] = []; + } + groupedAbsences[absence.date].push(absence); + }); + + return { basicData, absences, groupedAbsences }; + } catch (error) { + console.error("Hiba az API hívás során:", error); + return { basicData, absences: [], groupedAbsences: {} }; + } +} + +function createFilterCard(absences) { + const filterCard = document.createElement('div'); + filterCard.className = 'filter-card'; + + const filterHeader = document.createElement('div'); + filterHeader.className = 'filter-header'; + const h2 = document.createElement('h2'); + h2.textContent = LanguageManager.t('absences.filter_title'); + filterHeader.appendChild(h2); + + const filterContent = document.createElement('div'); + filterContent.className = 'filter-content'; + + const dateGroup = createFilterGroup( + 'Calendar.svg', + 'Dátum', + LanguageManager.t('absences.date'), + 'input', + { type: 'date', id: 'dateFilter', className: 'filter-input' } + ); + filterContent.appendChild(dateGroup); + + const subjectGroup = createSubjectFilterGroup(absences); + filterContent.appendChild(subjectGroup); + + const justificationGroup = createJustificationFilterGroup(); + filterContent.appendChild(justificationGroup); + + filterCard.appendChild(filterHeader); + filterCard.appendChild(filterContent); + + return filterCard; +} + +function createFilterGroup(iconName, altText, labelText, elementType, attributes) { + const group = document.createElement('div'); + group.className = 'filter-group'; + + const label = document.createElement('label'); + const img = document.createElement('img'); + img.src = chrome.runtime.getURL(`icons/${iconName}`); + img.alt = altText; + img.style.width = '24px'; + img.style.height = '24px'; + + label.appendChild(img); + label.appendChild(document.createTextNode(' ' + labelText)); + + const element = document.createElement(elementType); + Object.assign(element, attributes); + + group.appendChild(label); + group.appendChild(element); + + return group; +} + +function createSubjectFilterGroup(absences) { + const group = document.createElement('div'); + group.className = 'filter-group'; + + const label = document.createElement('label'); + const img = document.createElement('img'); + img.src = chrome.runtime.getURL('icons/Subject.svg'); + img.alt = 'Tantárgy'; + img.style.width = '24px'; + img.style.height = '24px'; + + label.appendChild(img); + label.appendChild(document.createTextNode(' ' + LanguageManager.t('absences.subject'))); + + const select = document.createElement('select'); + select.id = 'subjectFilter'; + select.className = 'filter-input'; + + const defaultOption = document.createElement('option'); + defaultOption.value = ''; + defaultOption.textContent = LanguageManager.t('absences.all_subjects'); + select.appendChild(defaultOption); + + const subjects = [...new Set(absences.map(a => a.subject))].sort(); + subjects.forEach(subject => { + const option = document.createElement('option'); + option.value = subject; + option.textContent = subject; + select.appendChild(option); + }); + + group.appendChild(label); + group.appendChild(select); + + return group; +} + +function createJustificationFilterGroup() { + const group = document.createElement('div'); + group.className = 'filter-group'; + + const label = document.createElement('label'); + const img = document.createElement('img'); + img.src = chrome.runtime.getURL('icons/BadgeCheck.svg'); + img.alt = 'Igazolás'; + img.style.width = '24px'; + img.style.height = '24px'; + + label.appendChild(img); + label.appendChild(document.createTextNode(' ' + LanguageManager.t('absences.justification'))); + + const select = document.createElement('select'); + select.id = 'justificationFilter'; + select.className = 'filter-input'; + + const options = [ + { value: '', text: LanguageManager.t('absences.all_types') }, + { value: 'justified', text: LanguageManager.t('absences.justified') }, + { value: 'unjustified', text: LanguageManager.t('absences.unjustified') }, + { value: 'pending', text: LanguageManager.t('absences.pending') } + ]; + + options.forEach(optionData => { + const option = document.createElement('option'); + option.value = optionData.value; + option.textContent = optionData.text; + select.appendChild(option); + }); + + group.appendChild(label); + group.appendChild(select); + + return group; +} + +function createStatsOverview(absences) { + const statsOverview = document.createElement('div'); + statsOverview.className = 'stats-overview'; + + const stats = [ + { number: absences.length, label: LanguageManager.t('absences.total_absences') }, + { number: absences.filter(a => a.justificationStatus === 'justified').length, label: LanguageManager.t('absences.justified') }, + { number: absences.filter(a => a.justificationStatus === 'unjustified').length, label: LanguageManager.t('absences.unjustified') }, + { number: absences.filter(a => a.justificationStatus === 'pending').length, label: LanguageManager.t('absences.pending') } + ]; + + stats.forEach(stat => { + const statCard = document.createElement('div'); + statCard.className = 'stat-card'; + + const statNumber = document.createElement('div'); + statNumber.className = 'stat-number'; + statNumber.textContent = stat.number; + + const statLabel = document.createElement('div'); + statLabel.className = 'stat-label'; + statLabel.textContent = stat.label; + + statCard.appendChild(statNumber); + statCard.appendChild(statLabel); + statsOverview.appendChild(statCard); + }); + + return statsOverview; +} + +function createAbsencesContainer(absences) { + const container = document.createElement('div'); + container.className = 'absences-container'; + + const table = document.createElement('table'); + table.className = 'absences-table'; + + const thead = document.createElement('thead'); + thead.className = 'table-header'; + + const headerRow = document.createElement('tr'); + const headers = [ + LanguageManager.t('absences.date'), + LanguageManager.t('absences.lesson'), + LanguageManager.t('absences.subject'), + LanguageManager.t('absences.topic'), + LanguageManager.t('absences.status') + ]; + + headers.forEach(headerText => { + const th = document.createElement('th'); + th.textContent = headerText; + headerRow.appendChild(th); + }); + + thead.appendChild(headerRow); + + const tbody = document.createElement('tbody'); + generateAbsencesRows(absences, tbody); + + table.appendChild(thead); + table.appendChild(tbody); + container.appendChild(table); + + return container; +} + +async function transformAbsencesPage() { + const { basicData, absences, groupedAbsences } = await collectAbsencesData(); + document.body.textContent = ''; + const container = document.createElement('div'); + container.className = 'kreta-container'; + const headerDiv = document.createElement('div'); + const parser = new DOMParser(); + const doc = parser.parseFromString(await createTemplate.header(), 'text/html'); + const tempDiv = doc.body; + while (tempDiv.firstChild) { + headerDiv.appendChild(tempDiv.firstChild); + } + container.appendChild(headerDiv); + const main = document.createElement('main'); + main.className = 'kreta-main'; + const filterCard = createFilterCard(absences); + main.appendChild(filterCard); + const statsOverview = createStatsOverview(absences); + main.appendChild(statsOverview); + const absencesContainer = createAbsencesContainer(absences); + main.appendChild(absencesContainer); + + container.appendChild(main); + document.body.appendChild(container); + + + setupUserDropdown(); + setupMobileNavigation(); + + setupEventListeners(); + setupFilters(); + + loadingScreen.hide(); +} + +function generateAbsencesRows(absences, tbody) { + const groupedByDate = absences.reduce((groups, absence) => { + const date = absence.date; + if (!groups[date]) { + groups[date] = []; + } + groups[date].push(absence); + return groups; + }, {}); + + const sortedDates = Object.keys(groupedByDate).sort( + (a, b) => new Date(b) - new Date(a), + ); + + sortedDates.forEach((date) => { + const dateAbsences = groupedByDate[date]; + const divider = document.createElement('tr'); + divider.className = 'date-group-divider'; + divider.style.display = 'none'; + tbody.appendChild(divider); + + dateAbsences.forEach((absence) => { + const row = document.createElement('tr'); + row.className = 'table-row'; + row.dataset.subject = absence.subject; + row.dataset.justified = absence.justified; + row.dataset.date = absence.date; + row.dataset.dateGroup = date; + + const dateCell = document.createElement('td'); + dateCell.className = 'table-cell date-cell'; + dateCell.dataset.label = LanguageManager.t('absences.date'); + dateCell.textContent = absence.date; + row.appendChild(dateCell); + + const lessonCell = document.createElement('td'); + lessonCell.className = 'table-cell lesson-cell'; + lessonCell.dataset.label = LanguageManager.t('absences.lesson'); + lessonCell.textContent = absence.lesson + '.'; + row.appendChild(lessonCell); + + const subjectCell = document.createElement('td'); + subjectCell.className = 'table-cell subject-cell'; + subjectCell.dataset.label = LanguageManager.t('absences.subject'); + subjectCell.textContent = absence.subject; + row.appendChild(subjectCell); + + const topicCell = document.createElement('td'); + topicCell.className = 'table-cell topic-cell'; + topicCell.dataset.label = LanguageManager.t('absences.topic'); + topicCell.title = absence.topic; + topicCell.textContent = absence.topic; + row.appendChild(topicCell); + + const statusCell = document.createElement('td'); + statusCell.className = 'table-cell status-cell'; + statusCell.dataset.label = LanguageManager.t('absences.status'); + + const statusBadge = document.createElement('span'); + statusBadge.className = `status-badge ${absence.justificationStatus}`; + + if (absence.justificationStatus === 'justified') { + const img = document.createElement('img'); + img.src = chrome.runtime.getURL('icons/BadgeCheck.svg'); + img.alt = 'Igazolt'; + img.style.width = '16px'; + img.style.height = '16px'; + statusBadge.appendChild(img); + statusBadge.appendChild(document.createTextNode(' ' + LanguageManager.t('absences.justified'))); + } else if (absence.justificationStatus === 'unjustified') { + const span = document.createElement('span'); + span.className = 'material-icons-round'; + span.textContent = 'cancel'; + statusBadge.appendChild(span); + statusBadge.appendChild(document.createTextNode(' ' + LanguageManager.t('absences.unjustified'))); + } else { + const span = document.createElement('span'); + span.className = 'material-icons-round'; + span.textContent = 'pending'; + statusBadge.appendChild(span); + statusBadge.appendChild(document.createTextNode(' ' + LanguageManager.t('absences.pending'))); + } + + statusCell.appendChild(statusBadge); + row.appendChild(statusCell); + + tbody.appendChild(row); + }); + }); +} + +function setupEventListeners() { + function setupMobileGrouping() { + if (window.innerWidth <= 480) { + createMobileGroups(); + } else { + removeMobileGroups(); + } + } + + window.addEventListener("resize", setupMobileGrouping); + + setupMobileGrouping(); +} + +function createMobileGroups() { + const tbody = document.querySelector(".absences-table tbody"); + if (!tbody) return; + + removeMobileGroups(); + + const rows = Array.from(tbody.querySelectorAll(".table-row")); + const groupedRows = {}; + + rows.forEach((row) => { + const date = row.dataset.date; + if (!groupedRows[date]) { + groupedRows[date] = []; + } + groupedRows[date].push(row); + }); + + const sortedDates = Object.keys(groupedRows).sort( + (a, b) => new Date(b) - new Date(a), + ); + + while (tbody.firstChild) { + tbody.removeChild(tbody.firstChild); + } + + sortedDates.forEach((date) => { + const dateRows = groupedRows[date]; + + const dateGroup = document.createElement("div"); + dateGroup.className = "date-group"; + + const dateHeader = document.createElement("div"); + dateHeader.className = "date-group-header"; + dateHeader.textContent = date; + + const dateContent = document.createElement("div"); + dateContent.className = "date-group-content"; + + dateRows.forEach((row) => { + dateContent.appendChild(row); + }); + + dateGroup.appendChild(dateHeader); + dateGroup.appendChild(dateContent); + tbody.appendChild(dateGroup); + }); +} + +function removeMobileGroups() { + const tbody = document.querySelector(".absences-table tbody"); + if (!tbody) return; + + const dateGroups = tbody.querySelectorAll(".date-group"); + if (dateGroups.length === 0) return; + + const allRows = []; + dateGroups.forEach((group) => { + const rows = group.querySelectorAll(".table-row"); + rows.forEach((row) => allRows.push(row)); + }); + + while (tbody.firstChild) { + tbody.removeChild(tbody.firstChild); + } + allRows.forEach((row) => tbody.appendChild(row)); +} + +function updateDateGroupsVisibility() { + if (window.innerWidth > 480) return; + + const dateGroups = document.querySelectorAll(".date-group"); + + dateGroups.forEach((group) => { + const visibleRows = group.querySelectorAll( + '.table-row[style=""], .table-row:not([style])', + ); + + if (visibleRows.length > 0) { + group.style.display = ""; + } else { + group.style.display = "none"; + } + }); +} + +function setupFilters() { + try { + const filters = { + dateFilter: document.getElementById("dateFilter"), + subject: document.getElementById("subjectFilter"), + justified: document.getElementById("justificationFilter"), + }; + + if (!filters.dateFilter || !filters.subject || !filters.justified) { + console.warn("Some filter elements were not found in the DOM"); + return; + } + + const filterAbsences = () => { + try { + const dateFilterValue = filters.dateFilter.value; + const subject = filters.subject.value; + const justified = filters.justified.value; + const selectedDate = dateFilterValue ? new Date(dateFilterValue) : null; + + document.querySelectorAll(".table-row").forEach((row) => { + const dateStr = row.dataset.date; + const dateParts = dateStr.split("."); + + if (dateParts.length < 3) { + console.error(`Invalid date format: ${dateStr}`); + return; + } + + const parsedYear = parseInt(dateParts[0].trim(), 10); + const parsedMonth = parseInt(dateParts[1].trim(), 10) - 1; + const parsedDay = parseInt(dateParts[2].trim(), 10); + + if (isNaN(parsedDay) || isNaN(parsedMonth) || isNaN(parsedYear)) { + console.error(`Invalid date components: ${dateStr}`); + return; + } + + const rowDate = new Date(parsedYear, parsedMonth, parsedDay); + + let showRow = true; + + if (selectedDate) { + if ( + rowDate.getFullYear() !== selectedDate.getFullYear() || + rowDate.getMonth() !== selectedDate.getMonth() || + rowDate.getDate() !== selectedDate.getDate() + ) { + showRow = false; + } + } + + if (subject && row.dataset.subject !== subject) { + showRow = false; + } + + if (justified) { + const statusElement = row.querySelector(".status-badge"); + const hasStatus = statusElement.classList.contains(justified); + if (!hasStatus) showRow = false; + } + + row.style.display = showRow ? "" : "none"; + }); + + updateDateGroupsVisibility(); + updateStatistics(); + } catch (err) { + console.error("Error during filtering absences:", err); + } + }; + + Object.values(filters).forEach((filter) => { + try { + if (filter) { + filter.addEventListener("change", filterAbsences); + } + } catch (err) { + if ( + err.message && + err.message.includes("Extension context invalidated") + ) { + console.warn( + "Extension context invalidated during event listener setup", + ); + } else { + console.error("Error setting up filter event listener:", err); + } + } + }); + + filterAbsences(); + } catch (err) { + if (err.message && err.message.includes("Extension context invalidated")) { + console.warn("Extension context invalidated during filter setup"); + } else { + console.error("Error setting up filters:", err); + } + } +} + +function updateStatistics() { + try { + const visibleRows = document.querySelectorAll( + '.table-row:not([style*="display: none"])', + ); + const totalVisible = visibleRows.length; + const justifiedVisible = Array.from(visibleRows).filter((row) => + row.querySelector(".status-badge.justified"), + ).length; + const unjustifiedVisible = Array.from(visibleRows).filter((row) => + row.querySelector(".status-badge.unjustified"), + ).length; + const pendingVisible = Array.from(visibleRows).filter((row) => + row.querySelector(".status-badge.pending"), + ).length; + + const statCards = document.querySelectorAll(".stat-card"); + if (statCards[0]) + statCards[0].querySelector(".stat-number").textContent = totalVisible; + if (statCards[1]) + statCards[1].querySelector(".stat-number").textContent = justifiedVisible; + if (statCards[2]) + statCards[2].querySelector(".stat-number").textContent = + unjustifiedVisible; + if (statCards[3]) + statCards[3].querySelector(".stat-number").textContent = pendingVisible; + } catch (err) { + console.error("Error updating statistics:", err); + } +} + +if (window.location.href.includes("/Hianyzas/Hianyzasok")) { + transformAbsencesPage().catch((error) => { + console.error(LanguageManager.t("absences.page_transform_error"), error); + }); +} \ No newline at end of file diff --git a/firka/dashboard/dashboard.css b/firka/dashboard/dashboard.css new file mode 100644 index 0000000..ed105e7 --- /dev/null +++ b/firka/dashboard/dashboard.css @@ -0,0 +1,449 @@ +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + margin: 0; + padding: 0; + color: var(--text-primary); + background-color: var(--background) !important; + font-family: "Montserrat", serif !important; + min-height: 100vh; + font-size: 16px; +} + +h2 { + background-color: #00000000 !important; +} + +.kreta-container { + min-height: 100vh; + display: flex; + flex-direction: column; +} + + +.kreta-main { + flex: 1; + padding: clamp(1rem, 3vw, 2rem); + max-width: 1400px; + margin: 0 auto; + width: 100%; +} + +.grid-container { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(min(100%, 300px), 1fr)); + gap: 20px; +} + +.widget-card { + background: var(--card-card); + border-radius: 24px; + overflow: hidden; + animation: fadeIn 0.5s ease forwards; + box-shadow: 0px 1px var(--shadow-blur, 2px) 0px var(--accent-shadow); + display: flex; + flex-direction: column; + height: 100%; + border: none; + min-height: 400px; +} + +.widget-header { + padding: 20px 20px 0 20px; + background: var(--card-card) !important; +} + +.widget-card-title { + font-size: 18px; + font-weight: 600; + color: var(--text-primary); + margin: 0; + padding-bottom: 16px; +} + +.widget-content { + flex: 1; + padding: 0 20px; + background: var(--card-card); + display: flex; + flex-direction: column; + justify-content: flex-start; +} + +.widget-footer { + padding: 0px 20px 20px 20px; + background: var(--card-card); +} + +.widget-link { + margin: 0; + padding: 0; +} + +.card h2 { + font-size: 18px; + font-weight: 600; + color: var(--text-primary); + margin: 0; + padding: 20px 0 16px 0; +} + +.card:last-child { + grid-column: 1 / -1; +} + +.widget-item { + border-radius: 12px; + transition: transform 0.2s ease, box-shadow 0.2s ease; + border: none; + background: var(--card-card); + display: flex; + flex-direction: column; + padding: 8px; + position: relative; + min-height: 80px; +} + +.widget-row { + display: flex; + align-items: flex-start; + justify-content: space-between; + width: 100%; + gap: 12px; + min-height: 40px; +} + +.widget-row.grade-row { + align-items: center; + justify-content: flex-start; +} + +.widget-details { + flex: 1; + display: flex; + flex-direction: column; + gap: 6px; + min-width: 0; +} + +.widget-details.grade-details { + flex: 1; + margin-right: auto; +} + +.widget-title { + color: var(--text-primary); + font-weight: 600; + font-size: 16px; + line-height: 1.3; + word-wrap: break-word; + overflow-wrap: break-word; + hyphens: auto; + max-width: 100%; +} + +.widget-subtitle { + color: var(--text-secondary); + font-weight: 500; + font-size: 14px; + line-height: 1.2; + word-wrap: break-word; + overflow-wrap: break-word; +} + +.widget-content { + color: var(--text-secondary); + font-size: 14px; + line-height: 1.4; + margin-top: 4px; +} + +.widget-date { + color: var(--text-secondary); + font-size: 14px; + font-weight: 500; + white-space: nowrap; + text-align: right; + align-self: center; +} + +.grade-type-with-date { + display: flex; + justify-content: space-between; + align-items: center; + width: 100%; + gap: 8px; +} + +.grade-type-with-date .grade-type { + flex: 1; + min-width: 0; +} + +.note-author { + color: var(--text-tertiary); + font-size: 12px; + font-weight: 400; + margin-top: 4px; + word-wrap: break-word; + overflow-wrap: break-word; +} + +.grade-date { + color: var(--text-secondary); + font-size: 13px; + font-weight: 400; + white-space: nowrap; + text-align: right; +} + +.widget-author { + color: var(--text-tertiary); + font-size: 12px; + font-weight: 400; + word-wrap: break-word; + overflow-wrap: break-word; + max-width: 100%; +} + +.news-author { + display: flex; + flex-direction: column; + align-items: flex-end; + gap: 4px; + text-align: right; + word-wrap: break-word; + overflow-wrap: break-word; + hyphens: auto; + max-width: 200px; +} + +/* Mobile view: author below content */ +@media (max-width: 768px) { + .news-item .widget-row { + flex-direction: column; + align-items: stretch; + } + + .news-item .widget-meta { + order: 2; + display: flex; + flex-direction: column; + align-items: center; + margin-top: 8px; + } + + .news-item .widget-details { + order: 1; + } + + .news-author { + align-items: center; + text-align: center; + max-width: 100%; + margin-top: 4px; + } + + .widget-date { + order: 1; + } + + .news-author { + order: 2; + } +} + +/* Desktop view: limit author width and wrap text */ +@media (min-width: 769px) { + .news-author { + max-width: 180px; + white-space: normal; + line-height: 1.3; + } +} + +.widget-empty { + color: var(--text-secondary); + font-style: italic; + text-align: center; + padding: 20px; +} + +.grade { + width: 32px; + height: 32px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 16px; + font-weight: 700; + margin-right: 1rem; + color: var(--text-primary); + font-size: 22px; +} + +.news-title { + font-size: 16px; + font-weight: 600; + margin-bottom: 8px; + word-wrap: break-word; + overflow-wrap: break-word; + hyphens: auto; + line-height: 1.4; +} + +.news-content { + font-size: 14px; + line-height: 1.4; + color: var(--text-secondary); + word-wrap: break-word; + overflow-wrap: break-word; + hyphens: auto; +} + +.exam-type { + font-size: 12px; + font-weight: 500; + letter-spacing: 0.5px; +} + +.more-link { + margin-top: auto; + display: inline-flex; + align-items: center; + gap: 0.5rem; + color: var(--accent-accent); + text-decoration: none; + font-weight: 500; + padding-top: 16px; + transition: gap 0.2s ease; + font-size: clamp(0.875rem, 1.5vw, 1rem); +} +@media (hover: hover) { + .more-link:hover { + color: var(--accent-secondary); + gap: 0.75rem; + } +} +.more-link i { + font-size: 0.875rem; +} +.grade-1 {color: var(--grades-1); background-color: var(--grades-background-1);} +.grade-2 {color: var(--grades-2); background-color: var(--grades-background-2);} +.grade-3 {color: var(--grades-3); background-color: var(--grades-background-3);} +.grade-4 {color: var(--grades-4); background-color: var(--grades-background-4);} +.grade-5 {color: var(--grades-5); background-color: var(--grades-background-5);} +.grade-Sz {color: var(--grades-3); background-color: var(--grades-background-3);} + +@keyframes fadeIn { + from { + opacity: 0; + transform: translateY(-10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} +.user-profile { + position: relative; +} + +.user-dropdown-btn { + background: none; + border: none; + cursor: pointer; + padding: 8px; + display: flex; + align-items: center; + color: var(--text-primary); +} + +.user-dropdown { + position: absolute; + top: 100%; + right: 0; + background: var(--card-card); + border-radius: 12px; + box-shadow: 0 4px 12px var(--accent-shadow); + min-width: 200px; + display: none; + z-index: 1000; +} + +.user-dropdown.active { + display: block; +} + +.dropdown-item { + display: flex; + align-items: center; + gap: 8px; + padding: 12px 16px; + color: var(--text-primary); + text-decoration: none; + transition: background-color 0.2s ease; +} + +.dropdown-item:hover { + background-color: var(--accent-15); + text-decoration: none; +} + +.dropdown-item svg { + width: 20px; + height: 20px; +} + +.news-title { + font-size: 16px; + font-weight: 600; + margin-bottom: 8px; +} + +.news-content { + font-size: 14px; + line-height: 1.4; + color: var(--text-secondary); +} +.more-link { + margin-top: auto; + display: inline-flex; + align-items: center; + gap: 0.5rem; + color: var(--accent-accent); + text-decoration: none; + font-weight: 500; + padding-top: 16px; + transition: gap 0.2s ease; + font-size: clamp(0.875rem, 1.5vw, 1rem); +} +@media (hover: hover) { + .more-link:hover { + color: var(--accent-secondary); + gap: 0.75rem; + } +} +.more-link i { + font-size: 0.875rem; +} + +.exam-info { + display: flex; + gap: 8px; + margin-top: 4px; + color: var(--text-secondary); + font-size: 14px; +} + +.exam-info { + display: flex; + gap: 8px; + margin-top: 4px; + color: var(--text-secondary); + font-size: 14px; +} \ No newline at end of file diff --git a/firka/dashboard/dashboard.js b/firka/dashboard/dashboard.js new file mode 100644 index 0000000..fdedd2f --- /dev/null +++ b/firka/dashboard/dashboard.js @@ -0,0 +1,588 @@ +const DashboardUtils = { + formatGradeValue(value) { + const trimmedValue = value?.trim() || ""; + if (trimmedValue.toLowerCase() === "szöveges") { + return "Sz"; + } + return trimmedValue; + }, + + parseDate(dateStr) { + return dateStr?.trim() || ""; + }, + + formatHungarianDate(dateStr) { + if (!dateStr) return ""; + + const dateParts = dateStr.trim().split("."); + if (dateParts.length < 3) return dateStr; + + const month = parseInt(dateParts[1], 10); + const day = parseInt(dateParts[2], 10); + + if (isNaN(month) || month < 1 || month > 12) return dateStr; + + if (typeof window.LanguageManager !== "undefined") { + const monthKeys = [ + "months.january", + "months.february", + "months.march", + "months.april", + "months.may", + "months.june", + "months.july", + "months.august", + "months.september", + "months.october", + "months.november", + "months.december", + ]; + const monthName = window.LanguageManager.t(monthKeys[month - 1]); + return `${monthName} ${day}.`; + } + + const monthKeys = [ + "months.january", + "months.february", + "months.march", + "months.april", + "months.may", + "months.june", + "months.july", + "months.august", + "months.september", + "months.october", + "months.november", + "months.december", + ]; + + return `${LanguageManager.t(monthKeys[month - 1])} ${day}.`; + }, +}; + +class DashboardDataManager { + constructor() { + this.dashboardData = { + grades: [], + absences: [], + notes: [], + upcomingExams: [], + news: [], + }; + } + + extractGradeData() { + const gradeRows = document.querySelectorAll( + "#legutobbiErtekelesek tr:not(:first-child)", + ); + + this.dashboardData.grades = Array.from(gradeRows) + .map((row) => { + const gradeValue = row.querySelector( + 'span[style*="font-size: 200%"]', + )?.textContent; + const gradeInfo = row.querySelector( + 'span[style*="float: right"]', + )?.textContent; + + if (!gradeValue || !gradeInfo) return null; + + const [fullSubject, date] = gradeInfo + .split("\n") + .map((str) => str.trim()); + const { subject, type, dateInSubject } = + this.parseSubjectInformation(fullSubject); + + return { + value: DashboardUtils.formatGradeValue(gradeValue), + subject, + date: DashboardUtils.parseDate(date), + type: type || LanguageManager.t("dashboard.evaluation"), + dateInSubject: dateInSubject || null, + }; + }) + .filter(Boolean); + } + + parseSubjectInformation(fullSubject) { + const hungarianMonths = [ + "január", + "február", + "március", + "április", + "május", + "június", + "július", + "augusztus", + "szeptember", + "október", + "november", + "december", + ]; + const monthPattern = hungarianMonths.join("|"); + const datePattern = new RegExp(`(${monthPattern})\\s+(\\d{1,2})\\.?$`, "i"); + const dateMatch = fullSubject.match(datePattern); + + if (dateMatch) { + const subjectPart = fullSubject.substring(0, dateMatch.index).trim(); + const datePart = dateMatch[0].trim(); + return { + subject: subjectPart, + type: "", + dateInSubject: datePart, + }; + } + + const months = [ + LanguageManager.t("months.january"), + LanguageManager.t("months.february"), + LanguageManager.t("months.march"), + LanguageManager.t("months.april"), + LanguageManager.t("months.may"), + LanguageManager.t("months.june"), + LanguageManager.t("months.july"), + LanguageManager.t("months.august"), + LanguageManager.t("months.september"), + LanguageManager.t("months.october"), + LanguageManager.t("months.november"), + LanguageManager.t("months.december"), + ]; + const fallbackMonthPattern = new RegExp(months.join("|"), "i"); + const monthMatch = fullSubject.match(fallbackMonthPattern); + + if (monthMatch) { + const monthIndex = fullSubject.lastIndexOf(monthMatch[0]); + return { + subject: fullSubject.substring(0, monthIndex).trim(), + type: fullSubject.substring(monthIndex).trim(), + }; + } + + return { subject: fullSubject, type: "" }; + } + + extractAbsenceData() { + const absenceRows = document.querySelectorAll( + "#legutobbiMulasztasok tr:not(:first-child)", + ); + + this.dashboardData.absences = Array.from(absenceRows) + .map((row) => { + const spans = row.querySelectorAll("span"); + if (spans.length < 4) return null; + + return { + date: spans[0]?.textContent?.trim() || "", + day: spans[2]?.textContent?.trim() || "", + type: spans[1]?.textContent?.trim() || "", + count: spans[3]?.textContent?.trim() || "", + }; + }) + .filter(Boolean); + } + + extractNoteData() { + const noteRows = document.querySelectorAll( + "#legutobbiFeljegyzesek tr:not(:first-child)", + ); + + this.dashboardData.notes = Array.from(noteRows) + .map((row) => { + const spans = row.querySelectorAll("span"); + if (spans.length < 3) return null; + + return { + title: spans[0]?.textContent?.trim() || "", + author: spans[1]?.textContent?.trim() || "", + date: spans[2]?.textContent?.trim() || "", + }; + }) + .filter(Boolean); + } + + extractExamData() { + const examRows = document.querySelectorAll( + "#legutobbiBejelentettSzamonkeres tr:not(:first-child)", + ); + + this.dashboardData.upcomingExams = Array.from(examRows) + .map((row) => { + const spans = row.querySelectorAll("span"); + if (spans.length < 4) return null; + + return { + date: spans[0]?.textContent?.trim() || "", + subject: spans[1]?.textContent?.trim() || "", + day: spans[2]?.textContent?.trim() || "", + type: spans[3]?.textContent?.trim() || "", + }; + }) + .filter(Boolean); + } + + async extractNewsData() { + try { + const timestamp = Date.now(); + const apiUrl = `https://${window.location.hostname}/Intezmeny/Faliujsag/GetMoreEntries?startindex=0&range=10&_=${timestamp}`; + + const response = await fetch(apiUrl, { + method: "GET", + credentials: "include", + headers: { + Accept: "application/json, text/javascript, */*; q=0.01", + "X-Requested-With": "XMLHttpRequest", + }, + }); + + if (!response.ok) { + throw new Error(`API request failed with status: ${response.status}`); + } + + const data = await response.json(); + + if (!data.FaliujsagElemek || !Array.isArray(data.FaliujsagElemek)) { + return; + } + + data.FaliujsagElemek.forEach((item, index) => { + let formattedDate = ""; + if (item.DatumNap && item.DatumHonap && item.DatumEv) { + formattedDate = `${item.DatumEv}. ${item.DatumHonap} ${item.DatumNap}.`; + } else if (item.Idopont) { + const match = item.Idopont.match(/\/Date\((\d+)\)\//); + if (match) { + const timestamp = parseInt(match[1]); + const date = new Date(timestamp); + formattedDate = date.toLocaleDateString("hu-HU"); + } + } + + if (!formattedDate) { + formattedDate = new Date().toLocaleDateString("hu-HU"); + } + + let cleanContent = item.EsemenySzovege || ""; + cleanContent = cleanContent.replace(/<[^>]*>/g, ""); + cleanContent = cleanContent.replace(/\r\n/g, " "); + cleanContent = cleanContent.replace(/\s+/g, " ").trim(); + + const newsItem = { + title: item.EsemenyCime || `Hír ${index + 1}`, + content: cleanContent || "Nincs elérhető tartalom", + date: formattedDate, + author: `${item.Nev || "Ismeretlen"} (${item.Munkakor || "Ismeretlen"})`, + }; + + this.dashboardData.news.push(newsItem); + }); + } catch (error) { + console.error("❌ Error fetching news from API:", error); + } + } + + async extractAllData() { + this.extractGradeData(); + this.extractAbsenceData(); + this.extractNoteData(); + this.extractExamData(); + await this.extractNewsData(); + return this.dashboardData; + } +} + +class DashboardRenderer { + constructor(data) { + this.baseData = data; + } + + async init() { + this.data = { + ...this.baseData, + schoolInfo: { + name: + await storageManager.get("schoolName", "OM azonosító - Iskola neve"), + id: await storageManager.get("schoolCode", ""), + }, + userData: { + name: await storageManager.get("userName", "Felhasználónév"), + time: + document.querySelector(".usermenu_timer")?.textContent?.trim() || + "45:00", + }, + }; + this.schoolNameFull = `${this.data.schoolInfo.id} - ${this.data.schoolInfo.name}`; + this.shortenedSchoolName = helper.shortenSchoolName(this.schoolNameFull); + } + + generateMainContent() { + return ` +{qGKrIVl`&yia4Z&SF7O8$nZ@4r=#9e#L1(rDFE zk*il-pUSd_E}f^gVPaYMF%^#MQ(dD9pFJ$NrK%8Qv`q3#r%&yy&41?HPCkGB%fY3e z-pOZkh|aHGv8Ta0C{emBK7##d}lZ# CPR(Fx#UpyCaI~bj|8TPE {<_d2r_P8R;(u=C9ssoYaad^N|Vwm&Fzx+mwJpvG_~Ve3r$ zNjvXxa@B0)oyK*A$M3wqWWe5v_DM2_7CoCQB5vt$;(WUNJ~0hhE4_=$9v00_%G!M_ z_lA )_I4;F|$`}FLv)vDO`nWk!d!bRuWWn!L6 z=eI4JGP%7bq)&8rpkU#(L+f0`6WyM_yfrl?+tlbwPj|)tGSj~=9! 1Sl@%B|wx#`y*zxw*!Gr4=GaDU%o z?jmWCJ W7Z=$*aXe&eG4;YU=YHbki!U&$r+HD=Bu} z_dBZJH*P-kOy;cY?i>7>a{8ZH)kP;(zDajH=3|?aT;#Jv&}h+*zamq6cJ4cINO6%{ zh}WbK`+ClYuWhj1Jgdv_<}>bD 5|ONhc{-mdLFjFF#R MN^IjP63ww~5MNta5ocu96sCI8lIL2% zU77t^p}Fn(L1GsByA}mmJ}OZ@pyqJSf8yjDsT;j`)1`HKI`3C5unJu39r#0AebV!M z3EtBwFY?-s&sck_Ce3l7^^=!g#}^$o^t{{YB-j-4$&0@*oUtr%>5&I(Ig`EoWqngB zgu}lWI^R3PzV)MN;+5>u)jW}sf2N8^6 Q^(;NQqTL3gUkLY3dP(kCRU(v9QJRlj+;#=BIu>(xx{hpr`6 z96_AwbrZV+H!L``AS=Qm?9{hyd0Kl8eZHioUt#v$AkY6Y%l{c0XZ&BZDXM|BI>f&3 zaHX-n(ccBHxbxbAoK;_5GyfX?Rr~KQNv`WJ8)o+fd`YQTEuZv}IdG-P{g?GDm+zkK zER5AVQe-WAv9b5j-?_`5SlC>MxjFL^_j)zE_cy~2v&yNyx>a;U-{8yRygs9SQKFZ1 zkMHF?lc5`U#>XNxC%7uDcYW6!F%_$jkBWL~Rc9M7?DG(K^e{<0)nnoZ24`2llW}LQ zCzqUJk#Kp=QLy9LYK`?ejydtiq$e;qh_0!;b&6MOc1pr;i=Y`6tjGAG6ok7XW!J^> ztZDz7!y+EE@z3W^3Le52@~3~-{b(2Fn0xKk{*U*+X{23my1Hf6%BxHvyS8LHu6nU4 z_>88Y`W}sdch@2s `C?bV91qwPe}L5LCJMdmBL-ZyXRbgZ?xlUaQorv?5I+^>c|M!sn4Un zh)+)3zKVN+)7@Pqc{as=ip3RPXL&Y#zm~!N$z;cYUlwJ8rv=JPL|F@--TL$-#FI}p zFNd|)Z+ElJ>;^HbPi6`1?+)eNO8b3%k>>Na9LtwEUA&qjt1h F1neVQoURu7_a)H|Q%136i4}MdgHThE) zm+11_FN^s8tENP3t`4;R-I|+vv`;r+*XziOuXe=whc39`m3TQ$;Lzg-N;+TdEDPk* z!i;b4de?aSQETkHdoEKty~I5x-|3yGv?lF#{Qbu=MXG;RSDsnckXhrhI^`kz<7+qf z9M*{vi_NPn_?DW#q6Ee1ct5#AKs)zKxNa=-%`-XBRCl zz7@$kmrayiV#T2!yh{%VA7OTW8s3%iywAe<+WPxz3`1u>H<-LgpepocwWtx>Zo_or z)9Z4|L)9O*OPtyBy!{z()HNI3ICX``?{+%BTeU9JiLd?igauVSQ;%(YpIouw>CEW+ zrW2c$-c34J{Gwv6;@OtM7vE3!D)vv0eR2H>msH%JN{M#!Ck9v6em}*1j_38XkjK2Y zJokN?GTYAm^4tY!>W269W7 rb~maJ;`d zgoF1`=q#QdMHRhJjvreOly5rHo#u8{Q@h-hQ>nI}Z*m0x^B$|Eg$~zh9u_{^yXX4b zAIsY|%)R@0XB%VWmU*vzTRU|xcl~qCuR9c+VLbcu9 ^D91CCW4`CZqeb@ K?M*sAXXj*lk8_$)#dDhXeDN3Hig>-h>%-e$yW5wm z6jaY?J{;`mwq@Rov phk^?ojV$w&2?z zDp01Qwch=m{1?mFGF&nf{2#2IdHP0R`0Je5qrdOZVz6@maDi S?r*@s$T(6(drMcjdoPvr}qbXDL pr^~YJ?tPQ@eolOM+~GF=2cf6@0`DzCPyJR{>char>GIb{V|wlb;~>{RSIUp7 zA5jV`E 2 zTVsv<*KZ3Vw(H&uTBjQFbfJp%{%YULGv)~$lDznk;YEI;e3s{7?`N|ArpVq3ja5Gu z!6q=(i>rKR0QZB%ugjBvG&TO*G2I}jv~)|rfuyg-7R#o5S#^A4O{4SC&g4&5Pj?7x zd~3FO-K-6{x6bUhv6>ZLKEZ9-ZSlu;Dtl*{PP5 tBAa{O7$0|C0F1 z`MPGPcuHG;o|xIw*1cC3=}ek2v&bcm=TV;IqD2$5`#MkLAE@HBeaJ7gJA~;>+dYrx z-!xt%OZCiJbKz=nVHxW}srts0C_CR{8|rjF9=mxkZl-&q%tw=-#yW*1%5jg51~cc+ z`*!O};P#jwM}F$+FZE2i?Y3R_tkAZKnkQB+aklsF$DW%l{kDAj>3|$o?QIiNX55)C z-qW}`^U
-CXe8k26zA4{5QF7XqqA!AbYIf|-H9MOYv3^?Z?;ty${dZ0) zi5?3zomyECEI8}qr^RP7BBkHXy~(lICjW3wTI rTwKIiY9{%vBCd$ec) z$Ap|;skIhWEBu0!vpLxl9+poMn{xa2UK=mXd7<5Bf(0z*Brkk)c7~&8u*jm|i@%-- zEsgPFt2sHBMLfAkPB4Ju9OIfphknoe{QW_aTE)q2H-%SkQ$E`4EmB``L;O$Y(tYQW z4QD9XOi{K--)^@<=9;tKJaxq!?i0qGf(upK9+ZFU&OAK-TB>UWSKWHC*uyg0HtAZ6 z8#c|)I=~#_z3$;$i@%$Mo%b}(xNfxWtcOvUtO%3d!c=L#hqlG$XD^pk{Pf%M^6(A5 zsqXJDG$k-@-`MQ5CMj&`(YvhuX@`#pPptaVs?x9_vn62OOUK_IGK_cU?g&YHS7Tq? z#hw>^s_#hq=X*MHJyr8ADBd<==@ybO{8-ApOIxKxLE`Ym(kq<%XZP@ZNNBknbj+K7 zkFaS*_c1RKt50(bqnJOQ{t _X@u)OB { z!>N~+{{QGC6HoPSgZ2C?x jD>e**+JL@A$wi zH1m{utQlkDo@8%+j(n{(npez@1+nk>yqv?UbPp4c+Kx$%!Fl$vIoXe*PPTp55UyXH zVA9+9`0j-t4x3d9BYG#edfC}Iu6i8v@n47F$CkWMh0v?Yv+hjyj^ JwWrQ(A&c&pFKImCw2e8|Ne{F8#l048*XXxWSuxyW8d3g- CtiFIn0TAXRbdkGh8s^x>UklnHKSboL~A}WWPU^_0Q#FnO9w)d~k==gfI>3MF9&t z5A|PlkSsm-<(PrgLGOizi`A}5uP{6-J3DJ(+>G9kux)W4<}!KzwdiMET5esncHz~Z zHuvS4vi(eqSY&VB@MoCP$;93{wb|UYUDjNFF>`FizWE)EH{A~ReNtMvUB TFMs!LTq$KIzh-ixgW0Ph$=+>WHgA}4wOn+M{M%=p`KPmFjxx%a za@R2H@~i7Tx7aUsMWO$Z$gU)X1vwM7t_g%0>a6%IQS<(CT!)#$KjuFbSAO0; bIe_~{)X> yALIsErHi)(?I<_+$*mwQ?Zb0+yGu~%Pu zQD=Us>)J^nJ4U$=mw(wz3cnay8+vlrd*_eulWkOQD}B|` 8sJVdiMDVFBZF D`NZhR^Lv)<)TKBDVH^vPWsEw^lOwn$`q@@ebK$C?)SypOt(8UXRk@q zyXq?aFg#r9-Tu!0d~MdN2d4V(*j^i6P}`H(TB)~N{#gEXiTL{u{2O9V%`%@UzS2hd z>->cl6MScADJI7pt2 `o5oy-5_?j1?rO)G z@(?qfRmV<6Xx!tyR5C~D+SYSf7FVtK`*)eG;@DanGC|^6 _x z{pt0GTsoxW%9bek8(nqbowvH@Gwa`?ghT)A(;Q=YIdpy%JN13fq2rT*Wqczxy> zkFWbaWz1bR?cx3C>q0aACfIyof43 ztTIKe94ahs1tOf$H4NcQE4y5_2_0l#y(D1yY7zVWt7M)o4d3~vYTc>F7avyDg*se5 zT9tU!ZqmFMEup%g^{ot# ^`2K z7Qsa`=Sth%us&TGe@S6ox!l~fPK_b| w6ZQn*lyU>W%f?_Ns7`{ho1NH7pA^l zE@qfG$L4b4Jk_n`;ak$58EKYJJ-n_-FZjh& ~h2KqrE^bPd{2$#VqG8<9<)p&e z%W>>R;q@bVr#xilfA3SfeJ0Lr_H!-k&TgxMTB%c?Lg%T8uP$M|x_rBH E`1YG01nBm7h{A?UgCgqGq C?IF(hExo%b7Z5 z^TQ_3KK#`5#QDp6#I|hs$&^^VWUt4&-z9bvBPPYKKl#ttG{f}ng%z8`q|KL1nLT6H z%<#C@)}>QRr?UROwrulXr?T3__YYn?dGknX>PgXhZw3LDHizictrzpJ^z5E^v*=0N z!QQvKo`R1`gPan!*m#DHS^Me7+$~BQU1lB4IN4;h(`jYU?B7#wyyQKmbiS?rZ-4dX z&E*9pMP;2OrQF3v+VMa87T$75S|7Y@Q~&H4J9oSH#chgD=j8MbI?PgZRg(RR#fu5K z+g2CLsPDR#mST34=ki&Tz)kwT1(#H|zOmUmE2r?iOYOI%vd66capi2dz$Cr2>GzS( ztaD(;n1uv|1%?Kz3xJOWTe$M7{;F~b-$iDvZc5o1SJjUBSw50HzB0Y+a#}=*V`mM2 z_r7=4clIrhKXdYD>Y?55SG-bJtTCR%k;K&^TkgU%L+oU|`pcgiuEeROuFOmM9W!~e z#MSDJ*CN_p)b;rGo)q!F^{8{@ezR*gb>l6U&DpklTZs{S#KZ!V`9UggqcbA+?@7)L zKY3X`yv=gS;Y(La6>c0{?P#5GdrjT$MWvfI_m}Q}ZGW2crs3Y_4?4dUs+?1|TJ!#5 zfBU_z_O>m>-nZJ9&n>yIa{8{z!TJ8HPVQGqI9%E%=NMM*dC_&sqU^uQ@7C>}?8CB| zr`T%drj>IvxJp}omnk=~rWs98Id$%x&MT)jp(9!DO6%q}KhR(bf8oQ)?7TN df0%U!GO6k{7(L=Qx35hG)p YSu1$tRfbaQ z%#F+&FFx{Lym5Y$fO0+W*=-fmR4iZS%gN>c5!*MhXT!>wFTWm1Q#4<1xY1_Ou_G(f zw~5%7{66rnr*Z1LXX!e}m$3cQn|kH`G9gc`UuvQrS(TG+NvVn6SlGikjqQ-3K!k_Z z1P=v+2`8L_xEl|+IooyGn95mg3lX|sE^*L)i|Em3UF%-C_vomfi`h~1_}qaA8O0;q zy9;@D$d{fzaKJ$!p=a05qi^C~e0wcvxv{8T;b(1yNXVW4-DiInZi_lUcUx}K+E>r7 z9J?~%vqI6+N~s9#OAL nO2$J@4+8*2A7Dg>3onPVV{3+537)>{<&`nRBu{=?hGnkNEor|Cg@({r2l&`(L-Z zO4Q!AUP}sgc(gvD)~-PR(VOSrAMF1!>(l1F3EMyLGdz}U>nT4!KUL(l(oYfh_V8eV zJ16&9S?>KHU-Eb5zeRGM0aI$3WWAZbubv?!_E(R=eV+7=<%=s7LfjN99wy4|xP4&J z`#s;k{R?xie4L=YPhs!+Ce1z5or{`mSB4iV-PB)_`YGkw_Bprz9XPoo=|w&h00?b4 AYybcN literal 0 HcmV?d00001 diff --git a/firka/fonts/Icons.woff2 b/firka/fonts/Icons.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..e9e305f27d5e78ec2d5047f9660e01b0de6a1ac8 GIT binary patch literal 173620 zcmXT-cQf)23GruOT4ut)#lXP8CUu8_Y3VZtMh2{acS?)DO!IXWwgiR^0gPr$D$I+d zSlC#bbJ!K4HKevN?dwT@cb2ik!E<$C+1$U=vx2QveKu` O@&G-q%drsXpJnzUN&j_bJ%J`*?Z zT<^+q=V5`krIX~uskY50Ti?njES`~DZXxn6YhjknrEk8mGW)FT?#V8WPvTy0Vfi-4 zZ>mrI+fDBG*%lp_JZBvpICs Q=(t)^;)&$ z)R)~eubs*ID%aC7qdPUGW2ebx?Z~}<7e*f1yUYFUrE8`84xH%dsDA0I8fx^@(Y`t) zj=xf(?Qe9Bm!|8 m!nS%V-|2 VN z5qbA6`^$Bcv~J#f>$f^>YoC&mQjLh9*2+nD?=G#%-@0p(`t#;rfoENg_;hxDZh7um z>9b?;AC_tD=e^8kB(Y64IV55{ V!R#f3I-(x%W)N&5|eXBt-P+7F<%ECRmYkLh12c zFT+)m@<& z^Z)k%W89y%j+#?e6NE-%r;2TvGf(qJC xl#8NpLFS_*>0OXv9nuyYRJun z3lpv{{v~&RiSEDP-~U6)stjE_w60%X6901R?&*<-`;~6_KXNepyU@5SL~YU5%D)?K zhG+l!>#`^JeEHh7S8n; ugz!R5uCdunO#qk|$xcI@td^<(a>)21x~ht6}@+9#h;tSi@h`C25^ z=bBIPF`nRQwuhr;uaD+%Nqt>6Pr-mK_0GSYHOANGyVWbMt_f{j8L)NNuB^AewV$v( z b1>@V=2aJ%nMPt$GXQ{lx||Ig9-#9(kDmGy$%z20Sg?21P#|EGJMyYN4f!{m7D z?u?1Gduq}d7?>o?Zm!EZ)+n?ol;x9TbFu0niM2DrE^C;qQr8F$@|Y}icjN#6^DTeR zf4A!Ato@ggKB|RSf1aiAAw+PJ#N_qO2ZDrKe5VR66zm9CI>E}zV}YPhkoK*R=7|kz z9T%Q5Y*S@V+$|$yBxf07`9Q(i|NF(L-OK-*{r>+yYKeW_imZsz>$?`6f3oA;H^a_8 zSDuY|85vs|#afT4cnEO_&i$B@zyHy^{*VR+3Bzr-y;^cqU1!a(O%ph>W7geid^zG= zb3P;`hfEf_)4`&) !HL zq5(5ACr?j-%TBdlKVGy~ |6DAcQwoP@L$dbSs#t7a^AGeiCQLc zMMaCDXMU3U3!8gS+D@^k>6tn-ygD-H seb^)>79>4gu9TehWWp>yhFu}OZ+ B^In=MbmGbz2{&@iir7A=mp^5FV5_@F;OrS^EUfOyT5RR}$uKP_Y R9@CgWG;Uq5^x~3>+iemR_5Py zw?;T <|G)k7|Mh40em{O*CVB4O`!{~CxV~V~7YAvV;Kk3E*hUq4 Mk3UkV5 z%iIh4j`{~9)b|{zd(HXi_BZLINoSPZ8(Skyw@u5~rY|-zZDK{!3=_8d^OHiiNvqG? z`*ziThLq@Z2Pw}Rh3o#E{rP|M^4aUZo3GN{&DA1YcFZwbhC#43f_0M0rZQ0huf*_M zOL#qnyre}htrPNaYGB!&(8zf+uIaF ?1ngcV%lzU0-~CU2{kN>vz5Dyl zb&Eo|s>Yz)n1`;aTbd8uICA6A4xKm6hul~1iazvH#7`(}hE9+68ojAPaRrV-=7wQ6 z#3FC7*-BkrJI6XHbkF~vSAPEgzVG{qlgInxPiw1vRar7aWgDkJR3}p#!?N2&3nk2V zdaU>oxUnV0(^zY<-6qwYCzg8Z?YOLaAVhlClm^cvuUT97UQ7G8f91db{>GR4<-gwh zz2I%4hRlOa!71z~=18!JojG%Fv*+wK(@8ovjgFMfQS-}EXSF$EP`>u%mH&2I|L+Q_ z|Fa^?uRQd7vQL?O(1*Phn-iw!-rgv3bDD1Ei+j}zehZk$?s8yWak0lO qbNZ*=)y##d{P zkgdkSS7o1^68#@C(Xh>i{R78@JB4@u-nuz6wt$go0qa>y$=627%`OZ7o&A1)zLAgO z={e`(j!&>WJSA0Q@BQmfLB=W-S#-p^==}?NHN}qY>S^D~<%{R9p6~m+JfdoTCHp?_ zrVbWPB_$yj7Z()|4xu-NGQsa(#vJpTd+OfC!?*T+UcMnW^-NQrSY6R3)+rw@JAeJh zc8!6ViR1tOeIcvg{%&O8oDibraMA4PtQ9hTmrPIXj0l`^>gAcpmocH=i)6y=-|c?C z|3Lr$gAe+DFi0?Suqt_IF lvuI3>m1KXxp~RW+Elb4=$y{Yb&luOBuaUt@~?|onYHv)Xy{t4 z@~v+!ub#CmR^L2w_q{Ux>f5XD@pfM|cqg_ua_j&9+dkcww|nqHT9J# aTHfa+ z_gB4|8GhAvQk2xoY8SSK>eal;k0lI#30M^?GWwc6IPh10?ULBl%XyzKiL*Q-6&ru1 z|F^Eu9O>DNrKMB7_OGsAz5my*x8JsH*{qnIroSvRchXap=chTPChRQbV3c%V&PrBh zPJ45vLvcrXN>*8eqH)G6`F9@!!wVSvH|FtoA7o%_zW*_4!@vKl!)6M_+FpJ$HQUS1 zM32*bDX) 0<=%OBF)e(~P>!!t%yMK-) z{g=DNC7P1MnzCo&o83?JD)Tw#M*g2%^5;d={hCYX8yGkRPOvn+-CXNjuKsdKnc&=y z|L X93lE1H~Lb8`9Vxcl{g z_Iu5*zW;e|e0tBdTbH6V13~#>(UhB?|5yL>x0>_4W`42!?TBMW)0S#!ty-m}rKFTr z%w!_P(G*aVDzHcN YYL zbCt=hohMQj{`)^w$vp3*(O1C$7pqMZ+m(I)!1siu)xk1=_X -LHe$p$ntthM^Y09FSsjGr(rLHduD_OOFlHo=n zuf*ttwQKtVmd=v0nyfdm=GDt5F)o1@)wV9#`u0`A_Lr=wrhk7qdaT^M >c z=kE9Ze^B=SGv)mMRrx>n&ENI+Q?Q}Z`s8Ep%D&~<{{EQ$+F?Q06t?RX3}L@!zk0na zC(P<~xa!%`YwZDro1Uz?;@XvSRO{--QwJ*)n3r`J|KD=s!x=ug?v6>*1ld?UPG0)@ zdfM}Qzuzv}9rxoAfAX@NTZQT8rX_EW?EJiRw(mR7rE`7ln%n&+Svp<$aWW|V;@Wwx z(XnB7zekzg%R0XI%l@3BvwClRiQ2v7?X|KuamANY=ck6 !%^y7>( z ns*FJR-RhI9{EJ)`_Gepe$Tw#J@u_n*tTJfp{A{b z7bAOtPDDYQ`X2@ZhYroY3T_q&j)Dynl3IDB8NHH}WM@|IdBSWT@wB0MUgd%V6LiC~ zT-HRYh-_Wy>i;V^dSBWvE^$rQiC(&kQnarnT)D7m?d@%{(Kol+MqfOjBYo@GySCd0 zMZ9y(4%(c~eRk@mxy|1*@BbdoURV7hd;iaKmHc}?U92>ht$5KH{!jm+N^;wsC)#sE z!Zy#Iy@y-W)brJy$7-hovhG*D&Ao4ZDQxl0t#V)XReW8x_xcOpbsFaj`RgU*O*S6m zXK+!8IN;RSER&p9uqX2A4C{R}Qs=wZ1*RToV`j~2smTh@P-IZBn(??ne3rwF841nm z85so=9`{e7JboIB-tkNnZIin|+&|{dPW>Tg{Z!V;QjI=p0+upYf_sk2tl*{qi_@ zDI|RE5zlFh >a0^Zo_F#D4Y!0Rch9!yHmf3_>L$S>NVdviUSo)a%=e zf Hlm#@RYjwbIjDz7;$lg|Bd zV_SUuyuoRi&zczyKP*mea^X59oyOdkaOj{X@1evKoIG5T)8|ZE{~>}w$?M;i8`Xt1 zUk~SB*L_>@YO&zsw458|@2;7zX7jV{Q-3xodYXS$ -@rM`yc-=HCYHLU*nOf zd@$K;v5&!Vt>`%gCzUyPxXd_Jg-xvNW^|QLGc;@DP;OS5m66;d8YPj`&%EyjLqmI~ zlyHOu!wSPTZ_V8W&mSB$-yf2>^VvRgrFSZ!XV&DzPPxh|^t_;;TlOU5a~ ua!YgM7-Rj(Q=9AFmQ<_;Tc5d5salg#juOf=>%{pJ#RXuIKzvkwr z6Wa2;lBUnE%*`ym#_OvyZPV(jvejSieXskv{A$+L*X37o>s41@&AYWO#?kY(-unx# zKUZyyU%vH9s_oVE(5_Q!*Gwzb+p?u=)mnSjReASzn*UxifA^JDU;U~se?K-WYwhQK zCin9mzjODScXp-a{Nq2REcbp?oA+|fa|3y{nSTS6`;8ASF+TS;W3rEJgwCA{QhTdD zKb$RJSM^K%)eGzL_n|Ry_xhX? z)4jI0b@lxUjZ>B_x!7*@C`I$4ieI16)mc^Nr@x-}>&fiHein~s{f?`6&M&`q*9Y_L zZL&K*9yvWdX_Da?rQb166|b&%(K#t?R`3Obi;tF_m9n&m-Ma9yUiGW=>Qd`>%dRWA zo(@@ie_zPeMb}y_&Sgz8fBmM|+vH}+rc;XN8;g3TsiZ#JuyJzA$A_Ua*F-Fhn)O2M zde^xxdVZ^%Orun}S2jn8vH4gg^i0&*bdEE8PcvgefWe28GZz-FnDagBwEy{^7w)T_ zjoSYH%+~eWV%(I4gO|*7oObisoU&z`=FLvHIC%-Tu(p9p>A$MOnK@c}%m0eLD!t|Y zeX7)!m*-Dy<&MtDPJI*cUG_}OuCIEBkDrg+T6*Wq!rpVX>1qDo((Sr$OjvuP`NJn^ z-RMB0=zAp$N89;Y^q*WjD3kuj$*GG|$>)cM!J(#X5q+^6&*waA%vZbltW*BZ#3iR1 zG}F1ZME+ICj$giGL7&^EYrC#5p7(WW{9Dx%VM|v_yzB~HJ$=gZThmHcE^pp9B{ #2CZ{kq|@pZvhs7=$Md26HQ%;t&-?KtT>fmt_Sa{cBTtw0#5jE2b^LWh zcV6_pUr*TWf1dq+Z^rhwC${RR dL6u z@w0!y0vG;ZosCCXZFwd{9N_c&z`-HGAd#`+qp)I>V`Pegu%)MamC*#(o70?kPU|lc z31-_V7+B*t=|sp}y<68? CyA^US9acK#G6)9a)NdG z-R(Wx1MrQhd~{CsheY`UIp i~}xR`jRb@%1C>igU4zW4e6SoQwb(*3(1OuZjd`eLX5 z65&HN-Btl>_f1*IchK y93N`fYm1wasOrS4|5i$t{^7d(Ph@ zq_a=k>AL@qE&C1f7oDAPKD79CwbkmQohLplUeFvTz3b^$+r5obcORNF(R8< 7)d8acc&y;$IjO#JIWL=C<90s7>oOrElK- zV0TnL)8mzLj~CqgWyP~A;)vRegIj0%%yzt(`%?7DBjqke^G}_Bnb%%qQrx|4*Ej3< z@|o{n|JD?G;Wpz=P3wI(u?U`d%?Fy9exK-mJkPC~Q}XNrgNw=cgbZIC^W+0XLX+Pw1|1GZ7|oMdDqe?)!d{B)8aoaYdZS$wAKQbrCU57 z>D^WKyDTO7{$SPY@7b~^zR&d(nERy1eZxWa3yYpS^4@M$acA1Hy}mhf{xuYHUATQa zw%jAxTkr0Iw*Rt@zrQ=5>ot?BU=gpe6fmv6Y1(;bx9U3A*Uzu<-!XEGKcDxw)#hi< z``^5>?)glKcgp{B%l)f4Jlmf6e!9zImCoa~aa{Youa(N^Sh+S=C(Kiy@bdQG18(`% zPgXu<^R{bT_x?;?-`vQodzUxq*cfp+|MypU<54(4=+q`Q0SC3qf?xi VuY}xMGyaWE3bP)@{cV)~ zrEm7=i|^0oSLIdzMU8hJcx8UkoUyxnfkAM8F^fXOy0wch%(P; S<^jzb8>_+vbzTmw=LVF}CpZK5avv|>)AX-0RasQ0g z+k6TOu8G>+`gVWe$5g)hBOWW9-u-{1FnNK|o+ggTclBPZc3!zr?BIl!f7?a=o?b9j zV=cq1Hv)f5I#xDDe~-;jo^^>Q 9klV| zbz875Y5uj|fVZ!ur~ZFAOZ#c!&wWad>+Y!hNSV3ZXSaZ{gU A i;+7L*JV1w7iq)QTgt&w!$&(;`~KbZM7m@>Q^GV?p`-P zEi7{(z-9VFu0TGa>U-xcnkKY1N_^j0pgyy|>&He9m!n~gJF@fUS8m(<)OMNqPHRVz zR6Y++Q6X)G7j}AaZy$C%x$%OtbN0K2nJ-tAP1ttOz%Q56T|4Un!}2yRf5S3o{ryIp zwlFNJkcjEwyCP+#wb}2ka{QvKT}>+&`rAD+4a%PArG4K(oagJ^OG`9A-kJJ8!bObR z@#u~dbz9bQR4xs8s(Gqt!;Z(gzRHS%Y{|-;^As!Gw>?-CFrCLz?$puc`uG3+2#<5P zm6sQhvE^BPevzfnO5tkz)*h>bdg1k|0&A|{mTY;E-tm0XzX^#R4A Cg@%| zwA7WYac`sX1;@N5)fO(1zDLF-{R%gh{p8zyLgPkU#Rknw8|Q?j<{Z0L?v>pn-W2-c z-$};1`yw?t{JZaW9A-_xLvp;!M0}Mtwar$_C?(Ojvp3SKiuTM+Eag$`$O4Db2E<^ z)F0(~85NcOy_n(bq+_N!Cxw{iGwv>yZTC-)e#EONa(SEZOvl$8Ju?~fKK>H!Uo2X+ zRhHM-s>WHM^CElX4Q{o>7lAIKhh{g5zn6HX#pqQ&v*%aN4x=fzUbF`sw_h{A+)iOr zBj-v11^(BT8!v6!6!+7%OO5MA;l!u1;fxh>C5Dq{7+e+qvcsR7W$QIYMlmNv7DE}v z)MaNF?L#hvHVQlyJ7oOn!)-~HqDWamY5BXX56fpu#ozf@+WqeMcT0|ECpSzj5^>S| z8D{n1Zg~2IfXd#xN`BJn=TBZLpVR;8NVvn{L(LT>Hx4X0q^758+stjvn0qsclbt0b z;+DB_NI>%YppW$llQ;DR#D~67U19n-#<*p5Y|Hklw_FkPBh1(Ep1&0@I(eeOVU-JE zYmP5-Q@XT7(ukF%=cOFy;UMP|zE`A;r$lwn)=__EHOI1uZNBI$(ceo~xRpISb+qDF z%IezVv;S`Y|Fq%2G@}ZWRmr stC^`)eZ| =U4;t2wqD(HHAL}ITfkSw @Bp=Wt&?u`lzwj&uj>)w2_1j}>q1lAJyFtc_LuXX!aBzsp_gkCd}l z-(56y^ZVCHQD%aY*)ccr=kAei@)WKPI{R|d^4Z&_|9`izH=!l|(9XHuQfxZ6#0sA& z<)3O)b+6l5k#JXeZ}#Qi+H+)g^sM0C#ZmA +$Nf)TLbiVWRW0=N+ottSe(vYIcI>~;e#D?Yer}Ne;f>lG-f#H2 zH~*IUr3v;0)~oH$oVYSm!;kUF+ my*Z4D0ZU`;w*F3J;g;{QI~rm&;iHhzpJ`iuPHJBELwQk1 z M0L@ zbeQ|^Pl@{XCE2 gs_6nJonEn5u^fLaSQG*Fv$ICtk(V~ mUlmSwD ))o9~uqn=KR#NO@Pb(t9tv zWO+YNy{h%^2)7v;GcD)aJTa_z-Sc9Z*~Z>^_TJ*W^<5Hc50`iBGrRen=kEQj_pe@> z(0Fm_QPHq{+9i{#4p^qVIK1!l-wWI4cz($_v3Eb;v(u0Nt#NbncydSU5a&tR<1KD3 zXHK?kn!aP7hudYrCUHTtuQG?1cYcqJC^oN1FWJ0J=S|a+>l36}I{$49pS7@P4r|=f z=cjwTxu?E5c`Lhg?S4U@rI$Cy vbU {hj%KuFroBjs=%JVg-~+ zLJq%|oW=hw{e%4G!;TpRMj}#Ers=WoHCA>hP z&5}Pc*J8=bKieABw)`+&n0WW#`Me*qpZEVybGBakQ){!UVblG;-=n{=e=GF1b_gls z5w@THhHu{E#?}7wKUX(7tyq2`Lh{g@!d5*gHI--cZrKFbYH#7q-_)t)_Qd4>>yYhT zxoqrC4i6)P|L!ern67#F$yNgv-E(Iji9D9})$?|JX?Q~9ip0zLIeYdkWOv( - a)w%WfNs4vMqK-v2AiHurk<(u-V|X6(EzeLHf$x0s9N3!`NZ&2;9=2cKly z(B#aZxL}jApZaaViqh922eV7`_~x&l@of6?i5oThyEZj%-MPIvNBGfC!wan8I(hrj zH?LOJtMQ+>`$f_luB?sM(=YD- ?{rO|&!NarCCkeT7E1N!z@?Bb1u+EIR zZlC9F=Cw8q!jhZsn$*gQKUDX1&N|&~JX3n-Pv^%^_NaVUijAnxeCu7`YQDyEfuPpy z3ei;cTQ)xo4&T0g|CG$n`5$VZ{q=EOy!BV%4e`T9b#LDD|1Dn-v^YdRd8&G&S*O~Y zYue>EbK4E-%WlmV)c2j4-f<>E&g|REb#tdI_+R4sLvn85hc#|;))Ki3QupggCb)09 z`HrVN&*I{iPhb7d9*Ui~>~HYl2Um`IG@Oc }fJQtg6JNW6Mxs2`kY2PzmrE2}X zv3SkeU9YD0T;#mx9v4}4$yzh#l d(xVcW!876t8SGJ> cwd zJ((iTAvbGJ@0Y}fFJkUZk2@#N!*^%GYD3SxCI+fc%ryjOPc7QId7{Tk&TP~D9K1~# z3KBf+qQ4sty_EmB=f{h$7k@97zru5P{V`RG)1N~o`7`f3dcyYU-NJbvb5=V=Z^*g5 z_RcF$PNuYX_9+bKq|(Bkh=!)Um|pxseo1A8e#E}|4>kW=FB%Gj`Rtyjc0GG7Pj&76 z;>#>*^Ukhq*yv%JKjT$HLxIcHsdg9d8eE!`d*QBc*GI{Bt1Ir_dBFLte$G?lJHH;r zTggw0I<)dmvz5}T ^O8dWCnk$bMp^_6OmsM zPrmJ1l>19`hxg)rbF9V7of{4msQyloHGZ*r!v)@M&n
Pfvww;$B9?h#gV(Yfx zp@G)KsXJ#VOt~=W{aIh;9zKVw=bQKcHY#27^7TTQe|93~s~p_yGp(3@IgRgUWTA8K> z)5{-Ab)N?AR}J3%acYTcpQ_f`=d*Oy6-=Ad*0%qk%_GBKS2Rx;NuQtpOgczda7ygA zSJO7dzu6IyvD=^~$aS|_VC`YK)4R2!SeV;owceMy?0QuDNj#|A?lai? z&-2^YO{>Hj@6Og+IWKC%M$f!^>TagKjD}*}C${x#9j?2sxwX0E>8%IbPJ}f_%!!w` zjhfKRxnk}5Jt^u8?V4r0^YhhZYL_$Zv2ZHBxV~Vi)~`p4*co5j*}QI#udC UAIVOiSL8SSj8~<8@>|X5 DHZ&5nUcytE#tbyO{6YKe=heiY_ZBUwx=* zCf4#UEJ{RRI;YX`Ykm$#CA<2p*=kLzs*-YN&HL&$?fT0Y>7C~f-b;O1A0E7FW$8En zuDItLzaHg$#@;)>E8q7bN7tv$ypvVGIcIrZd?dP5j@dXueM1wUWbW?RwOx^o>)g9a z4ScTDX#9Kfotdre>H1gqXU*FXQ}gG~gQd+oJGcCaeZ(jyUU}%EeZam&HEVqq1e+!2 zR~hC>d=L50vr4Po^$1JO@g+N-_f@3?ue>o))+CF~Q~!NQ`t*<;o-f)({6c~!o8DNl ziD~VG&YFcqa+hZ8n0?sf2E!%wsylWI`<5%MvN^@bdMdHm`f!&+W3|iU)h~-;mve6} zHCD4y`|f{J=W;^m#%+81KB&y_i84D8eANBO+33mw TkJ|7(LZ{j ze$F*>+7*=-ewwpIZHJ_6yhq{jIXi`>CFWJS@;8;ujF^*oC9>x0*Q!17w@yWz5&2e9 z`2C}Dw4B}Y-ukc!w?y+ecS}_6K5M}})n{q<@;h-GSh*67`<3hUa%+VDKM7o`VrefP zRQE@(c=FNxlRh<;UQau}h4H5N+X4~izKf!37aAxSuAE>dm|c3|>pB}T`&%bNn6##< za@<$f))RlH+Mdq-S1>8_=nc-(A*M$@%$R>vBFU-$m1M%kOY4gwdH%_iC@};iHTzvC z6}xEFw6D1?@09>kK<}A|h3f^^+*o9kVd%fgQQVfHp_EDI)dbgyz0F-6+ju8r6}O6M z2n6{So@(Rt(+IxCy5~;!>TQmTgj{C2_E-Ef{b6BYk+_rNtE 9UF?) zh&*Fdnc41;x?%CB85^fK%&hud<=ve)wX?BR!1~~VKHe3(lo^ULH ~`Du=W6{JOa#IkR?Md=WV7{cNSfpQIK}pHn2bT6=*rf08Zlm03 Idb_8Qd-}`; zd|wRZ7ACFUy)QSi{%J^e!AYg<+wu$puNrx5I;C(h?ti59#%`J5)CX2;d|Sl#8gDIP z7PI8%b4XicnBMWsIe@Jw$7HqL?ChgwyXH?(-=QNdw#q>9ZP^X8Z)SZ>Z^TTt2(D;M zobLa#>uAOkwe;_P(*=(&zvk DFPwi<={*hz5YuO98#&}ud*`M<+hFIV9i59+Q@n~H-uk59Q{mdory)LI-nPVm3 zcIQCI3&)jST4j?KJ82|4$Cloy`;>oi+T_kzwimcpE%ul@=kHvu687g3-g^^2J@eMN zKkLjSOTYgewPn?otop~kPFU0bWrxSi%MUxA9}(cut25G`%{MbkXW!-KN=?lN!3(6l z+f0|Sp49oezIV#xfOq2h9NYdqemVQ?R%7cbZO#7)GS22cZEM?nHXNGPx8d#QGY7s` zs+}pV$QO>Y|6K9v?;XxX*KVzvBsKL(!Rr(AFY2?ETPzhy`uFi%NmFnEbH&DvXWY{? zb?hVdJoswoy6;4$@>%|87v$}${Z;QOeV%(V __F?dX|6~}h!fY0 zBkPVg^JnZ9HA?-;eqg)QMk$t68)tr;ymyCpe8SPly)%AJ*6S5~`B{ v!1t+{m*XJvgyIQeoy_Uty^ne|Ju{k;=tZLAN5oJ zZuavZlFuYsigIaq*24r`yb(<8sPd((1 }xz zZk5Q^u19w}YhKQi-+1fB3yH?7bKg7V+e}_!YbIl`#`&cKuTEUSe!eFkk{&yVAC0^t z*cHY+ahFW> &z_abh(_>wHJ|!VD51NL~C|lkf{rE*o@=MJ;>9nK=oZR&? zJ^{TZt{2PSzqpsQ#qOf=kM_68FP+v} DE6{LHtX-_{Tp{aWe-$6 z`=ob@lfJ;?TaEd#{)V$HT#d@EP0@d{)%yCO@=le|O)@WLE_;2}_8rgZnP*eEo@`Qo z`Kal2=3nnBX32^OPUr16EpE-=e<~u$cVfB4U5N=xR%Vx3zPwm!ul_g5(yv`X?- zrFz$#pR8MJ{4Q(uDjlN>mZm=I3{LP|7P*yJ`r_}_BR?m)_3KE@Xw6;bXZwduuQn*& zpg*ndLYVuS)35&RT0ZUFZ|M@-oo{L{EU@A$J#a$hn*CR~M@Ad+CGXDrShOiuw!it~ zvW_|KNkX -R59EeK3J>Ndr$==p~{gOF%UTJ$;K3THouzy3j#_AIv6sKFdhs{4) z;g+TIV5XUWpYi!@jmKu+(_ZOZ{yB%Klau?yn%OaD7Bcwl+?hA!#fP*VPF-KakGM~; zFYMoP(o9p5y-1+h_0yFNOD`Kv%X_)|-S(Sdf#;s8d8fta?R+ucsETWeP3M&Rsun$p zO$#QcRNntoAi7-Rpy!SEA`9 ?1wC$US8|r%j1y{Ty_vOds>pR`l30}vTu(>LJQ|YWtwmTBP&VPEu_PAh% zl2%#41ua|0z8&w37_UvxoHHvucGv#O_m}#w)l{#sPqh8PVflpX#Cn+`?&$SVLHyxz zlP~Jcf6RD1R%Frh^b4sEc2vknY-i42e!kgv>O9svmnSq8US09r-Gr}N*k(hn__Q#F z2CwG1V)<*6DmiZo_rGXbaLmiIqxbH1vD6zEmInDUT=e2wV&L$Puj!8ScEk2dNfvn% z${LLvZnLsnUUIW%OFPd=!-`3TMVr-H1$Go%Ffc#1L+#%!;|BrXgZAt`yj;G*&{brk z`raJAJr;hKcORS5XY=jVvNKC|R|b8UFx|6~(|@KS+m+WAo!Nh1s!EvUDQ=#8YG3cJ zl$|nNU)f*Y^pZZ)`m8G;%HP|v?B b;!^nXzovmS?)%W=T8bK8(RhM(OrI} zX31Vfk4-}J)b17s8@Go(INUkqipCk&DMlw!E^REo6MV$i!QIQH>#nA!mi4SBD-Wnw z#s$q^_|Kv$UwQkd_{Bfk`8h3@GX`%IOX0mM(bpgTYU;Vi|F2!IZn@XV`91UIM6>!| zdUn^@+l~uWsr6mxbuTVI{(8pc-~$`o!~Z_`_~~2ur{rNVr!{cc@Bbo|DbH zNg%6@KSycbzeuY)?_ATpo*fhmU{hH)yXE;gr$dH|(oa6jeQG!3S0L+J(SMhIue`>@ zx8uQ*L${uqgmhdu_K`pINJf6!tn$)>2b)q>GRDlQ<+!ANoHs@vox=f7q+@W|%)jrB*Ar-$sX zncDp+`gVBR3bAcHa@G )7vv}3^-=|wUXFi?b@KG!^S*1OOr(2!h@zg%? zmCKfRNNiJbxN)H6qQ*PJUnL(6rpQKq-1EVpr$pdPuj>6}54IMFby+Scy5O}^#lW`h zgRz{Q@Fee?C0`qk2Oed8q|YCJ?T3cuvie?EQ8C-ZM>|{+o43kVW+WsxH)k$ulQ(-L zz9ar@O4|S0)h8sMG|V|U^UtkP>({$%A5EEm=wn*i+; a#4S5$dIxoS&tBWKL*&3p;hiqC6go&L&R&+yA6VH;DK*rG=Q zUm5?TD!gG?6nFmx{}Ppm=ETj;J<3VBeZ{_hci$VVJ2 DVn#rGtw4sz1#od*`o5_zq%e; z9qX4@3$B=H*>iON;$Mlc-tw5$pWl#P*>-pX+yBC2-xAalw(^%RNUtcldn|jQeA&Nq zKMsYqW@$>-eBOS$eDdBFd%igZ_CgVE1_u fOyc=o>kl~pA{ zr@qWT# (_k$b$VHm{$|?~=Nz?vzj(~uZXlgk zyy;Tl+&Qmf9wzO`-;wwHn9a9>^;=W5{dKj&;`4X!GPAom?;~$f$NU8S&-{v7yLAlC zx;ie~#~t@%r9fQb+RuS2Vha7Zg`ZrOv~O8|(@-Z$$> JI%KQ{L)omG23e(Rn8=4NsFr+9R4 uGxmm3dil76^{!a_U`%dFxRTQ$8*ai_Q!qa ze;5b0mme;))aqSU`kpoI*^Ip})9uz}C>>jUr8YNFB%d#9Tgv^)-f5{^Zx+by`CfKs z)%vpRdsp8Dz7EfA$=Dcq!ax{_jJ2;*2~KZ*gq^jyVSk9OkjW9&T}`|^Kw4wx9)wlPVWE3 zD^C>D<16R%#k=mSI9GCY{Vmx82eQ^*ioI!nFR$`?>E-snabGLX&;RxJ{rjN*J6d-= zvDf;>`DN{@2doQXYYxXTPH41d=$t<@m!a!hq+|Sr?+j-&7*?|!kX$C(@Z6A5!C)nW zz{bs)8UL9j-5FAtGa0yA4H>2e*)=R%D`&M{)nLk#gC)0(H@rJ=u7JC5M{Pmr9)=3b zYX{QdTUXGGc(7hdR`>Zxj`H1mdM?4gC**E1}a(A%|}!`w{HXwSqS zM_ztpwG{5IaXlHZJ3mD7t;YSwAG!0p g04il@~;f$6GOP04P-~HQM#wYvmZ*QKMty}kt%i4(w@7SD6KTCYfOi=o? z@9>v?`Ly5}+c!N(kUYt?Y0K%)JzMY0OPYLW+GW$SW!2raTI^oV7GBl|J=a^YTO>bO zGnv~tSFbSF?jLjej_;ojD0ZrE;frH>pIKA=^Ws+jAJ4f2gcK&+X;@%l%`*A=t7!tE z^_*8j6?-hVKURNesr1QZ)+24Ni}{OEzjipDIbgG~h_560ZtA1CssVP-?{{9D#G-O{ z!W=zUS1lJmkHcqt)|MDcr@WZ*|NGQ49+3i{% >R &o@ZzC@??0^ix?u=_%eXwg=y8m#@6D z{DZ*JGjbCz8EjYQT3EfzGVYsqxjs*Is&ZETp343I-|ao${=x3#sT)`7PQUy2@5ej+ z$-Dkew%hj2Ud1Io;LN>-36=^|oL-) a1nZk8>4i&ZQ6`I-w1tz-N zb+kO;aG7>9OW>%4%h3l6ivnME{99|1p0q+j?DGA_2(z>!-}3J4wU|>mLp=QSq6#y< zqsymGVQ{Q+GIQ({;Idn%^3G3b+LX3yDKj)Brp^gZ DlYC^i6 FDai`v_Mt6)A6R-AI6)NTpRV-qnB3erAMxd z`G4JLqtf~Cz{-ggPY>UH5bshqb$#yjKXw!15ACs!`EIVE5Eig8r(V?PmU3&p=*DYd zQ-0qI)7iWujr04Y6HlfmZC<@)Gk5Bte%;iWryou`9(W*bbwZq=XRzwDOLtf0 2;y&5|tMSnJ25%%(Kp!ayDbjT=Qe8I(f^M z^$MHi?r9MeHu0U=5%6-}j1>Es_3M2<&N%h>=oW$4%UgA3JWM(4!`Pj6@#PHNcL(Pb z{4HL2^qjNqtsOsQW=^{}<-V9i_4W-1_PQ>4rjw?7X_=~vrNqW5XSbwEbxnUPxzXv; zhN%J{SH`-g9?a6tzSdH;{aQu(+U~gXTl&qG !rE3j?Qea-ff$EBRT6}9Mj$31;yX<>RZ*>dA^@>EN(9;yZg}A-NXAvs{2-1=C77| zA*be9EquRZf4twx=z>|!Q;l1y=7^d;^WV~wYQa9g#q{~vH9H$ZH)V=<_eW2ciIFQ< z+GQNhnzD;6cuVbd*DbpzKDl TBkKZ*^@2LF!cwcC3oXv!5&lR_Z z^eap*eztLvn}2}O#h02zfxoXC=bpd%tzWz}Zc@tT*f`cIUa_o$(vs`%aYcMOcV^~y zEx($8o6d5v6