{"id":4461,"date":"2025-12-25T18:32:47","date_gmt":"2025-12-25T16:32:47","guid":{"rendered":"https:\/\/science.knu.ua\/new\/?page_id=4461"},"modified":"2026-01-21T11:09:42","modified_gmt":"2026-01-21T09:09:42","slug":"naukovi-shkoly","status":"publish","type":"page","link":"https:\/\/science.knu.ua\/new\/?page_id=4461","title":{"rendered":"\u041d\u0430\u0443\u043a\u043e\u0432\u0456 \u0448\u043a\u043e\u043b\u0438"},"content":{"rendered":"<!-- START OF FILE Paste February 26, 2026 -->\r\n<!-- FONTS & LIBRARIES -->\r\n<link href=\"https:\/\/fonts.googleapis.com\/css2?family=Montserrat:ital,wght@0,400;0,600;0,700;1,400&display=swap\" rel=\"stylesheet\">\r\n<!-- Lucide Icons -->\r\n<script src=\"https:\/\/unpkg.com\/lucide@latest\"><\/script>\r\n<!-- QRCode.js -->\r\n<script src=\"https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/qrcodejs\/1.0.0\/qrcode.min.js\"><\/script>\r\n<!-- DOCX & FileSaver (For Word Export) -->\r\n<script src=\"https:\/\/unpkg.com\/docx@7.1.0\/build\/index.js\"><\/script>\r\n<script src=\"https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/FileSaver.js\/2.0.5\/FileSaver.min.js\"><\/script>\r\n<!-- JSZip (For Package Download) -->\r\n<script src=\"https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/jszip\/3.10.1\/jszip.min.js\"><\/script>\r\n<style>\r\n  \/* MAIN CONTAINER & RESET *\/\r\n  #knu-projects-app {\r\n    font-family: 'Montserrat', sans-serif;\r\n    color: #333;\r\n    max-width: 1200px;\r\n    margin: 0 auto;\r\n    padding: 20px 10px;\r\n    box-sizing: border-box;\r\n    font-size: 16px; \r\n    line-height: 1.5;\r\n    position: relative;\r\n  }\r\n  #knu-projects-app *, \r\n  #knu-projects-app *::before, \r\n  #knu-projects-app *::after {\r\n    box-sizing: border-box;\r\n  }\r\n  \/* VARIABLES *\/\r\n  :root {\r\n    --knu-blue: #0b5394;\r\n    --knu-red: #b80000;\r\n    --knu-green: #2e7d32;\r\n    --knu-bg: #f9f9f9;\r\n    --knu-card-bg: #ffffff;\r\n    --knu-border: #e0e0e0;\r\n  }\r\n  \/* --- ENHANCED REGISTRATION HEADER --- *\/\r\n  .knu-registration-header {\r\n    display: flex;\r\n    flex-wrap: wrap;\r\n    align-items: flex-start;\r\n    justify-content: space-between;\r\n    background: #f4f8fb;\r\n    border: 1px solid #c9def1;\r\n    border-left: 4px solid var(--knu-blue);\r\n    padding: 20px 40px 20px 25px; \r\n    border-radius: 8px;\r\n    margin-bottom: 25px;\r\n    gap: 30px;\r\n    box-shadow: 0 4px 12px rgba(0,0,0,0.03);\r\n    position: relative; \r\n    \/* Toggle dropdown properties *\/\r\n    max-height: 1000px; \r\n    overflow: hidden;\r\n    transition: max-height 0.5s ease-in-out;\r\n  }\r\n  .knu-registration-header.collapsed {\r\n    max-height: 64px; \/* Exactly fits the padding + title line *\/\r\n  }\r\n  .knu-reg-toggle-btn {\r\n    position: absolute;\r\n    top: 10px;\r\n    right: 10px;\r\n    background: rgba(0, 0, 0, 0.05);\r\n    border: none;\r\n    color: #666;\r\n    cursor: pointer;\r\n    padding: 4px;\r\n    border-radius: 50%;\r\n    display: flex;\r\n    align-items: center;\r\n    justify-content: center;\r\n    transition: all 0.2s;\r\n  }\r\n  .knu-reg-toggle-btn:hover {\r\n    background: rgba(11, 83, 148, 0.1);\r\n    color: var(--knu-blue);\r\n  }\r\n  .knu-reg-main {\r\n    display: flex;\r\n    flex-direction: column;\r\n    gap: 15px;\r\n    flex: 1;\r\n    min-width: 320px;\r\n  }\r\n  .knu-reg-title-wrap {\r\n    display: flex;\r\n    align-items: flex-start;\r\n    gap: 15px;\r\n  }\r\n  .knu-reg-icon {\r\n    color: var(--knu-blue);\r\n    width: 28px;\r\n    height: 28px;\r\n    flex-shrink: 0;\r\n    margin-top: 2px;\r\n  }\r\n  .knu-reg-text-content {\r\n    display: flex;\r\n    flex-direction: column;\r\n  }\r\n  .knu-reg-title {\r\n    font-size: 1.05em;\r\n    font-weight: 700;\r\n    color: var(--knu-blue);\r\n    margin-bottom: 5px;\r\n    line-height: 1.3;\r\n  }\r\n  .knu-reg-desc {\r\n    font-size: 0.85em;\r\n    color: #444;\r\n    line-height: 1.5;\r\n  }\r\n  .knu-reg-actions {\r\n    display: flex;\r\n    gap: 12px;\r\n    flex-wrap: wrap;\r\n    margin-left: 43px; \r\n  }\r\n  \/* Buttons Base *\/\r\n  .knu-btn-reg {\r\n    padding: 8px 16px;\r\n    font-size: 0.85em;\r\n    border-radius: 6px;\r\n    font-weight: 600;\r\n    display: inline-flex;\r\n    align-items: center;\r\n    justify-content: center;\r\n    gap: 6px;\r\n    cursor: pointer;\r\n    transition: all 0.2s ease;\r\n    border: none;\r\n    text-decoration: none !important;\r\n    text-transform: none !important; \r\n  }\r\n  .knu-btn-reg:hover:not(:disabled) {\r\n    transform: translateY(-2px);\r\n  }\r\n  .knu-btn-reg.primary {\r\n    background-color: var(--knu-blue);\r\n    color: #fff !important;\r\n    box-shadow: 0 3px 8px rgba(11, 83, 148, 0.2);\r\n  }\r\n  .knu-btn-reg.primary:hover { background-color: #084175; }\r\n  .knu-btn-reg.secondary {\r\n    background-color: #6f42c1;\r\n    color: #fff !important;\r\n    position: relative;\r\n    box-shadow: 0 3px 8px rgba(111, 66, 193, 0.2);\r\n  }\r\n  .knu-btn-reg.secondary:hover { background-color: #5a32a3; }\r\n  .knu-btn-reg.secondary:disabled { background-color: #aaa; cursor: wait; transform: none; box-shadow: none; }\r\n  .knu-reg-badge {\r\n    position: absolute;\r\n    top: -10px;\r\n    right: -8px;\r\n    background: #f57c00;\r\n    color: #fff;\r\n    font-size: 0.7em;\r\n    padding: 2px 6px;\r\n    border-radius: 12px;\r\n    font-weight: 700;\r\n    border: 2px solid #fff;\r\n    box-shadow: 0 2px 5px rgba(0,0,0,0.15);\r\n    pointer-events: none;\r\n    letter-spacing: 0.5px;\r\n    text-transform: none !important;\r\n  }\r\n  \/* --- DYNAMIC CONTACT CARD WRAPPER SMOOTH HIDE --- *\/\r\n  #knu-dynamic-contact {\r\n    transition: opacity 0.3s ease, transform 0.3s ease;\r\n    opacity: 1;\r\n    transform: translateY(0);\r\n  }\r\n  .knu-registration-header.collapsed #knu-dynamic-contact {\r\n    opacity: 0;\r\n    transform: translateY(-10px) scale(0.98);\r\n    pointer-events: none;\r\n  }\r\n  \/* --- BOT CONTACT CARD --- *\/\r\n  .bot-contact-card {\r\n    display: flex;\r\n    flex-direction: column;\r\n    background: #fff;\r\n    padding: 15px 18px;\r\n    border-left: 4px solid var(--knu-red);\r\n    box-shadow: 0 4px 15px rgba(0,0,0,0.06);\r\n    border-radius: 0 8px 8px 0;\r\n    width: 320px;\r\n    flex-shrink: 0;\r\n    font-size: 13px;\r\n    color: #333;\r\n    border-top: 1px solid #eee;\r\n    border-right: 1px solid #eee;\r\n    border-bottom: 1px solid #eee;\r\n    position: relative;\r\n  }\r\n  .bot-contact-header {\r\n    display: flex;\r\n    align-items: center;\r\n    gap: 12px;\r\n    margin-bottom: 12px;\r\n    border-bottom: 1px solid #f0f0f0;\r\n    padding-bottom: 10px;\r\n  }\r\n  .bot-contact-photo {\r\n    width: 50px;\r\n    height: 50px;\r\n    min-width: 50px; \r\n    min-height: 50px;\r\n    aspect-ratio: 1\/1; \r\n    border-radius: 50%;\r\n    object-fit: cover; \r\n    border: 2px solid #e0e0e0;\r\n    flex-shrink: 0;\r\n    display: block;\r\n  }\r\n  .bot-contact-info {\r\n    display: flex;\r\n    flex-direction: column;\r\n    justify-content: center;\r\n  }\r\n  .bot-contact-role {\r\n    color: var(--knu-red);\r\n    font-weight: 700;\r\n    font-size: 10.5px;\r\n    letter-spacing: 0.5px;\r\n    line-height: 1.2;\r\n    margin-bottom: 3px;\r\n  }\r\n  .bot-contact-name {\r\n    font-weight: 800;\r\n    font-size: 14.5px;\r\n    color: #222;\r\n    line-height: 1.2;\r\n  }\r\n  .bot-contact-detail {\r\n    display: flex;\r\n    align-items: flex-start;\r\n    gap: 10px;\r\n    margin-top: 8px;\r\n    color: #444;\r\n  }\r\n  .bot-contact-detail .lucide {\r\n    width: 14px;\r\n    height: 14px;\r\n    color: var(--knu-blue);\r\n    flex-shrink: 0;\r\n    margin-top: 2px;\r\n  }\r\n  .bot-contact-links {\r\n    display: flex;\r\n    flex-direction: column;\r\n    gap: 3px;\r\n    width: 100%;\r\n  }\r\n  .bot-contact-links a {\r\n    color: var(--knu-blue);\r\n    text-decoration: none;\r\n    border-bottom: 1px dotted #ccc;\r\n    transition: all 0.2s;\r\n  }\r\n  .bot-contact-links a:hover {\r\n    color: #003d82;\r\n    border-bottom-color: #003d82;\r\n  }\r\n  \/* --- BOT QR BUTTONS & OVERLAY --- *\/\r\n  .bot-qr-btn {\r\n    background: none;\r\n    border: none;\r\n    cursor: pointer;\r\n    padding: 3px;\r\n    margin-left: 5px;\r\n    color: #aaa;\r\n    display: inline-flex;\r\n    align-items: center;\r\n    transition: all 0.2s;\r\n    border-radius: 4px;\r\n  }\r\n  .bot-qr-btn:hover { color: var(--knu-blue); background: #f0f7fd; }\r\n  .bot-qr-overlay {\r\n    position: absolute;\r\n    top: 60px; \r\n    right: 10px;\r\n    width: 160px;\r\n    background: #fff;\r\n    border: 1px solid #ccc;\r\n    box-shadow: 0 5px 20px rgba(0,0,0,0.2);\r\n    display: none;\r\n    flex-direction: column;\r\n    align-items: center;\r\n    border-radius: 8px;\r\n    z-index: 100;\r\n    padding: 10px;\r\n  }\r\n  .bot-qr-overlay.active { display: flex; animation: fadeIn 0.2s ease; }\r\n  .bot-qr-overlay img { width: 100px; height: 100px; margin-top: 5px; }\r\n  .bot-overlay-header { \r\n    display: flex; \r\n    justify-content: space-between; \r\n    align-items: center; \r\n    width: 100%; \r\n    border-bottom: 1px solid #eee; \r\n    padding-bottom: 5px; \r\n    margin-bottom: 8px;\r\n  }\r\n  .bot-overlay-title { font-weight: 700; color: var(--knu-blue); font-size: 11px; }\r\n  .bot-overlay-close { background: none; border: none; cursor: pointer; color: #999; padding: 2px; transition: color 0.2s; display: flex;}\r\n  .bot-overlay-close:hover { color: var(--knu-red); }\r\n  \/* FILTERS SECTION *\/\r\n  .knu-filters {\r\n    display: flex;\r\n    flex-wrap: wrap;\r\n    align-items: flex-end;\r\n    gap: 8px; \r\n    margin-bottom: 20px;\r\n    background: var(--knu-card-bg);\r\n    padding: 15px;\r\n    border-radius: 8px;\r\n    box-shadow: 0 4px 15px rgba(0,0,0,0.05);\r\n    border: 1px solid var(--knu-border);\r\n  }\r\n  .knu-input-group {\r\n    flex: 1;\r\n    min-width: 110px; \r\n    display: flex;\r\n    flex-direction: column;\r\n    gap: 4px;\r\n  }\r\n  .knu-input-group.search-group {\r\n    flex: 1.3; \r\n    min-width: 140px; \r\n  }\r\n  .knu-input-group.btn-group {\r\n    flex: 0 0 auto; \r\n    min-width: auto;\r\n    align-self: flex-end;\r\n  }\r\n  .knu-input-group label {\r\n    font-size: 0.75em;\r\n    font-weight: 700;\r\n    color: var(--knu-blue);\r\n    text-transform: uppercase;\r\n    letter-spacing: 0.5px;\r\n    display: flex;\r\n    align-items: center;\r\n    gap: 5px;\r\n    white-space: nowrap;\r\n  }\r\n  .knu-select, .knu-input {\r\n    padding: 5px 10px;\r\n    border: 1px solid #ccc;\r\n    border-radius: 4px;\r\n    font-family: 'Montserrat', sans-serif;\r\n    font-size: 0.9em;\r\n    width: 100%;\r\n    transition: border-color 0.3s;\r\n    height: 38px;\r\n    background-color: #fff;\r\n  }\r\n  .knu-select:focus, .knu-input:focus {\r\n    border-color: var(--knu-blue);\r\n    outline: none;\r\n  }\r\n  \/* Buttons Style *\/\r\n  .knu-btn-icon {\r\n    height: 38px;\r\n    width: 38px;\r\n    display: flex;\r\n    align-items: center;\r\n    justify-content: center;\r\n    border: 1px solid #ccc;\r\n    background: #fff;\r\n    border-radius: 4px;\r\n    cursor: pointer;\r\n    color: var(--knu-blue);\r\n    transition: all 0.2s;\r\n    padding: 0;\r\n  }\r\n  .knu-btn-icon:hover {\r\n    background: #f0f8ff;\r\n    border-color: var(--knu-blue);\r\n    transform: scale(1.05);\r\n  }\r\n  .knu-btn-icon.active {\r\n    background-color: var(--knu-blue);\r\n    color: #fff;\r\n    border-color: var(--knu-blue);\r\n  }\r\n  \/* COUNTER STYLES *\/\r\n  .knu-counter-bar {\r\n    display: flex;\r\n    justify-content: space-between;\r\n    align-items: center;\r\n    padding: 0 5px 15px 5px;\r\n    font-size: 0.9em;\r\n    color: #555;\r\n    font-weight: 600;\r\n  }\r\n  .knu-counter-number {\r\n    color: var(--knu-blue);\r\n    font-weight: 700;\r\n  }\r\n  \/* Cache Status Indicator *\/\r\n  #knu-cache-status {\r\n    font-size: 0.85em;\r\n    color: #e65100;\r\n    background: #fff3e0;\r\n    padding: 2px 8px;\r\n    border-radius: 4px;\r\n    display: none;\r\n    animation: fadeIn 0.5s;\r\n  }\r\n  \/* GRID LAYOUT (Cards) *\/\r\n  .knu-grid-view {\r\n    display: grid;\r\n    gap: 20px;\r\n    align-items: stretch;\r\n    grid-template-columns: 1fr;\r\n  }\r\n  @media (min-width: 700px) {\r\n    .knu-grid-view { grid-template-columns: repeat(2, 1fr); }\r\n  }\r\n  @media (min-width: 1100px) {\r\n    .knu-grid-view { grid-template-columns: repeat(3, 1fr); }\r\n  }\r\n  \/* TABLE LAYOUT *\/\r\n  .knu-table-wrapper {\r\n    overflow-x: auto;\r\n    background: #fff;\r\n    border-radius: 8px;\r\n    border: 1px solid var(--knu-border);\r\n    box-shadow: 0 4px 15px rgba(0,0,0,0.05);\r\n  }\r\n  .knu-table {\r\n    width: 100%;\r\n    border-collapse: collapse;\r\n    font-size: 0.9em;\r\n    min-width: 900px; \r\n  }\r\n  .knu-table th, .knu-table td {\r\n    padding: 12px 15px;\r\n    text-align: left;\r\n    border-bottom: 1px solid #eee;\r\n    vertical-align: middle;\r\n  }\r\n  .knu-table th {\r\n    background-color: #f4f6f8;\r\n    color: var(--knu-blue);\r\n    font-weight: 700;\r\n    text-transform: uppercase;\r\n    font-size: 0.8em;\r\n    position: sticky;\r\n    top: 0;\r\n  }\r\n  .knu-table tr:hover {\r\n    background-color: #fafafa;\r\n  }\r\n  .knu-table-title {\r\n    font-weight: 600;\r\n    color: #222;\r\n    margin-bottom: 4px;\r\n    display: block;\r\n  }\r\n  .knu-table-id {\r\n    font-size: 0.8em;\r\n    color: #888;\r\n    display: block;\r\n  }\r\n  .knu-table-sup {\r\n    font-style: italic;\r\n    color: #444;\r\n  }\r\n  \/* CARD STYLES *\/\r\n  .knu-card {\r\n    background: var(--knu-card-bg);\r\n    border: 1px solid var(--knu-border);\r\n    border-radius: 8px;\r\n    display: flex;\r\n    flex-direction: column;\r\n    transition: transform 0.3s, box-shadow 0.3s;\r\n    overflow: hidden;\r\n    position: relative;\r\n    height: 100%;\r\n  }\r\n  .knu-card:hover {\r\n    transform: translateY(-4px);\r\n    box-shadow: 0 8px 20px rgba(0,0,0,0.08);\r\n    border-color: var(--knu-blue);\r\n  }\r\n  \/* --- COMPACT HEADER --- *\/\r\n  .knu-card-header {\r\n    padding: 8px 15px;\r\n    display: flex;\r\n    justify-content: space-between;\r\n    align-items: center;\r\n    border-bottom: 1px solid #f0f0f0;\r\n    background: #fafafa;\r\n    flex-shrink: 0;\r\n    height: 44px;\r\n  }\r\n  .knu-type-box {\r\n    display: flex;\r\n    align-items: center;\r\n    justify-content: center;\r\n    width: 32px;\r\n    height: 32px;\r\n    border-radius: 6px;\r\n    background-color: #e3f2fd; \r\n    color: var(--knu-blue);\r\n  }\r\n  .knu-header-year {\r\n    font-weight: 700;\r\n    font-size: 1.1em;\r\n    color: #444;\r\n    display: flex;\r\n    align-items: center;\r\n    gap: 6px;\r\n  }\r\n  .knu-status-icon {\r\n    display: flex;\r\n    align-items: center;\r\n    justify-content: center;\r\n  }\r\n  \/* --- CARD BODY --- *\/\r\n  .knu-card-body {\r\n    padding: 15px;\r\n    flex-grow: 1;\r\n    display: flex;\r\n    flex-direction: column;\r\n    gap: 6px;\r\n    position: relative;\r\n  }\r\n  .knu-meta-row {\r\n    font-size: 0.8em;\r\n    color: #777;\r\n    display: flex;\r\n    align-items: center;\r\n    gap: 5px;\r\n    margin-bottom: 2px;\r\n  }\r\n  .knu-faculty {\r\n    font-size: 0.8em;\r\n    color: var(--knu-blue);\r\n    font-weight: 600;\r\n    text-transform: uppercase;\r\n    line-height: 1.2;\r\n    display: flex;\r\n    align-items: center;\r\n    gap: 6px;\r\n  }\r\n  .knu-title {\r\n    font-size: 1em;\r\n    font-weight: 700;\r\n    line-height: 1.3;\r\n    color: #222;\r\n    margin: 4px 0 8px 0;\r\n  }\r\n  \/* Persons Layout (Head & Founder) *\/\r\n  .knu-person-block {\r\n    margin-top: 6px;\r\n    display: flex;\r\n    flex-direction: column;\r\n    gap: 2px;\r\n  }\r\n  .knu-person-label {\r\n    font-size: 0.75em;\r\n    font-weight: 700;\r\n    color: #777;\r\n    text-transform: uppercase;\r\n    display: flex;\r\n    align-items: center;\r\n    gap: 5px;\r\n  }\r\n  .knu-person-value {\r\n    font-size: 0.95em;\r\n    color: #333;\r\n    padding-left: 2px;\r\n    line-height: 1.3;\r\n    font-style: italic;\r\n  }\r\n  \/* Foundation Date Layout *\/\r\n  .knu-foundation-row {\r\n    font-size: 0.85em;\r\n    color: #555;\r\n    align-self: flex-start;\r\n    margin-top: auto;\r\n    padding-top: 15px;\r\n    display: flex;\r\n    align-items: center;\r\n    gap: 6px;\r\n  }\r\n  \/* --- CARD FOOTER --- *\/\r\n  .knu-card-footer {\r\n    padding: 10px 15px;\r\n    background: #fff;\r\n    border-top: 1px solid #f0f0f0;\r\n    display: flex;\r\n    justify-content: space-between;\r\n    gap: 6px;\r\n    flex-wrap: wrap;\r\n    flex-shrink: 0;\r\n    min-height: 55px;\r\n  }\r\n  .knu-btn {\r\n    flex: 1;\r\n    display: inline-flex;\r\n    align-items: center;\r\n    justify-content: center;\r\n    gap: 6px;\r\n    padding: 6px 8px;\r\n    border-radius: 4px;\r\n    font-size: 0.8em;\r\n    font-weight: 600;\r\n    text-decoration: none !important;\r\n    transition: all 0.2s;\r\n    cursor: pointer;\r\n    border: none;\r\n    white-space: nowrap;\r\n    height: 34px;\r\n  }\r\n  .knu-btn-pres {\r\n    background-color: var(--knu-blue);\r\n    color: #fff !important;\r\n  }\r\n  .knu-btn-pres:hover { background-color: #084175; }\r\n  .knu-btn-web {\r\n    background-color: #fff;\r\n    border: 1px solid var(--knu-blue);\r\n    color: var(--knu-blue) !important;\r\n  }\r\n  .knu-btn-web:hover { background-color: #f0f8ff; }\r\n  \/* Utility Buttons (QR, Info, View) *\/\r\n  .knu-btn-util {\r\n    background-color: #fff;\r\n    border: 1px solid #ccc;\r\n    color: #555 !important;\r\n    width: 34px;\r\n    max-width: 34px;\r\n    padding: 0;\r\n    flex: 0 0 34px;\r\n  }\r\n  .knu-btn-util:hover { border-color: #333; color: #333 !important; }\r\n  \/* Small Link Button for Table *\/\r\n  .knu-link-sm {\r\n    display: inline-flex;\r\n    align-items: center;\r\n    justify-content: center;\r\n    width: 30px;\r\n    height: 30px;\r\n    border-radius: 4px;\r\n    color: var(--knu-blue);\r\n    border: 1px solid #ddd;\r\n    background: #fff;\r\n    margin-right: 4px;\r\n    transition: 0.2s;\r\n    cursor: pointer;\r\n  }\r\n  .knu-link-sm:hover {\r\n    background-color: var(--knu-blue);\r\n    color: #fff;\r\n    border-color: var(--knu-blue);\r\n  }\r\n  \/* OVERLAY (QR & DESCRIPTION) *\/\r\n  .knu-overlay-container {\r\n    display: none;\r\n    position: absolute;\r\n    top: 0;\r\n    left: 0;\r\n    width: 100%;\r\n    height: 100%;\r\n    background: rgba(255, 255, 255, 0.98);\r\n    backdrop-filter: blur(4px);\r\n    z-index: 10;\r\n    flex-direction: column;\r\n    justify-content: center;\r\n    align-items: center;\r\n    text-align: center;\r\n    animation: fadeIn 0.2s ease;\r\n    padding: 20px;\r\n    overflow-y: auto;\r\n  }\r\n  .knu-desc-content {\r\n    font-size: 0.9em;\r\n    color: #333;\r\n    line-height: 1.6;\r\n    text-align: left;\r\n    width: 100%;\r\n  }\r\n  .knu-desc-title {\r\n    font-weight: 700;\r\n    color: var(--knu-blue);\r\n    margin-bottom: 10px;\r\n    font-size: 1.1em;\r\n    border-bottom: 1px solid #eee;\r\n    padding-bottom: 5px;\r\n  }\r\n  .knu-qr-canvas img { \r\n    margin: 0 auto 15px auto; \r\n    display: block; \r\n    cursor: pointer; \r\n    padding: 10px;\r\n    background: #fff;\r\n    border: 1px solid #eee;\r\n    border-radius: 8px;\r\n    box-shadow: 0 4px 10px rgba(0,0,0,0.1);\r\n  }\r\n  .knu-overlay-hint { \r\n    font-size: 0.8em; \r\n    color: #666;\r\n    margin-top: 10px; \r\n  }\r\n  .knu-close-overlay {\r\n    position: absolute;\r\n    top: 10px;\r\n    right: 10px;\r\n    cursor: pointer;\r\n    color: #999;\r\n  }\r\n  .knu-close-overlay:hover { color: #333; }\r\n  \/* FILE VIEWER MODAL (Full Screen) *\/\r\n  #knu-viewer-modal {\r\n    display: none;\r\n    position: fixed;\r\n    top: 0;\r\n    left: 0;\r\n    width: 100vw;\r\n    height: 100vh;\r\n    background: rgba(0, 0, 0, 0.85);\r\n    z-index: 9999;\r\n    align-items: center;\r\n    justify-content: center;\r\n    flex-direction: column;\r\n    animation: fadeIn 0.3s;\r\n  }\r\n  .knu-viewer-content {\r\n    width: 90vw;\r\n    height: 90vh;\r\n    background: #fff;\r\n    border-radius: 6px;\r\n    display: flex;\r\n    flex-direction: column;\r\n    position: relative;\r\n    box-shadow: 0 0 20px rgba(0,0,0,0.5);\r\n  }\r\n  .knu-viewer-header {\r\n    height: 50px;\r\n    background: #f1f1f1;\r\n    border-bottom: 1px solid #ccc;\r\n    display: flex;\r\n    align-items: center;\r\n    justify-content: space-between;\r\n    padding: 0 15px;\r\n    font-weight: 600;\r\n    color: #333;\r\n    border-radius: 6px 6px 0 0;\r\n  }\r\n  .knu-viewer-title-group {\r\n    display: flex;\r\n    align-items: center;\r\n    gap: 15px;\r\n    flex: 1;\r\n    overflow: hidden;\r\n  }\r\n  .knu-viewer-title {\r\n    white-space: nowrap;\r\n    overflow: hidden;\r\n    text-overflow: ellipsis;\r\n    font-size: 0.9em;\r\n    max-width: 60%;\r\n  }\r\n  .knu-viewer-external-link {\r\n    font-size: 0.8em;\r\n    color: var(--knu-blue);\r\n    text-decoration: none;\r\n    border: 1px solid var(--knu-blue);\r\n    padding: 3px 10px;\r\n    border-radius: 4px;\r\n    display: inline-flex;\r\n    align-items: center;\r\n    gap: 5px;\r\n    transition: 0.2s;\r\n  }\r\n  .knu-viewer-external-link:hover {\r\n    background: var(--knu-blue);\r\n    color: #fff;\r\n  }\r\n  .knu-viewer-close {\r\n    cursor: pointer;\r\n    padding: 5px 10px;\r\n    background: #e0e0e0;\r\n    border-radius: 4px;\r\n    font-size: 0.8em;\r\n    color: #333;\r\n    transition: 0.2s;\r\n    border: none;\r\n    margin-left: 10px;\r\n  }\r\n  .knu-viewer-close:hover {\r\n    background: #d32f2f;\r\n    color: #fff;\r\n  }\r\n  .knu-viewer-body {\r\n    flex: 1;\r\n    position: relative;\r\n    background: #eee;\r\n    display: flex;\r\n    justify-content: center;\r\n    align-items: center;\r\n  }\r\n  .knu-viewer-frame {\r\n    width: 100%;\r\n    height: 100%;\r\n    border: none;\r\n    position: relative;\r\n    z-index: 1;\r\n  }\r\n  \/* LOADER FOR VIEWER *\/\r\n  #knu-viewer-loader {\r\n    position: absolute;\r\n    z-index: 2;\r\n    display: flex;\r\n    flex-direction: column;\r\n    align-items: center;\r\n    justify-content: center;\r\n    background: rgba(255,255,255,0.9);\r\n    padding: 20px;\r\n    border-radius: 8px;\r\n    box-shadow: 0 4px 15px rgba(0,0,0,0.1);\r\n  }\r\n  .knu-viewer-loading-text {\r\n    margin-top: 15px;\r\n    font-size: 0.9em;\r\n    color: #555;\r\n    font-weight: 600;\r\n  }\r\n  .knu-viewer-timeout {\r\n    font-size: 0.8em;\r\n    color: #b80000;\r\n    margin-top: 5px;\r\n    display: none;\r\n    text-align: center;\r\n    max-width: 250px;\r\n  }\r\n  .knu-no-results {\r\n    width: 100%;\r\n    text-align: center;\r\n    padding: 40px;\r\n    color: #777;\r\n    font-size: 1.2em;\r\n    grid-column: 1 \/ -1;\r\n  }\r\n  .knu-loading {\r\n    display: flex;\r\n    flex-direction: column;\r\n    align-items: center;\r\n    justify-content: center;\r\n    gap: 15px;\r\n    padding: 40px;\r\n    grid-column: 1 \/ -1;\r\n    color: var(--knu-blue);\r\n    font-weight: 600;\r\n  }\r\n  #knu-loading-details {\r\n    font-size: 0.85em;\r\n    color: #666;\r\n    background: #f0f0f0;\r\n    padding: 4px 12px;\r\n    border-radius: 20px;\r\n  }\r\n  @keyframes fadeIn {\r\n    from { opacity: 0; }\r\n    to { opacity: 1; }\r\n  }\r\n  @media (max-width: 800px) {\r\n    .knu-registration-header { flex-direction: column; align-items: stretch; padding: 25px 15px 15px; }\r\n    .bot-contact-card { width: 100%; border-radius: 8px; border: 1px solid #eee; border-left: 4px solid var(--knu-red); }\r\n    .knu-reg-actions { margin-left: 0; justify-content: center; }\r\n    .knu-filters { flex-direction: column; gap: 10px; }\r\n    .knu-input-group { width: 100%; min-width: 100%; }\r\n    .knu-input-group.btn-group { align-self: flex-end; width: auto; flex: 0; min-width: auto; }\r\n    .knu-counter-bar { justify-content: flex-start; flex-wrap: wrap; gap:10px; }\r\n    .knu-viewer-content { width: 100vw; height: 100vh; border-radius: 0; }\r\n    .knu-viewer-title { display: none; }\r\n  }\r\n  .animate-spin {\r\n    animation: spin 1s linear infinite;\r\n  }\r\n  @keyframes spin {\r\n    from { transform: rotate(0deg); }\r\n    to { transform: rotate(360deg); }\r\n  }\r\n  \/* DECEASED MEMORIAL FRAME (BLACK BORDER) *\/\r\n  .knu-deceased-frame {\r\n    border: 1px solid #000000;\r\n    padding: 1px 5px;\r\n    display: inline-block;\r\n    font-style: normal !important;\r\n    font-weight: 600;\r\n    color: #000000;\r\n    background-color: #fdfdfd;\r\n    box-shadow: 1px 1px 0px #000000;\r\n  }\r\n<\/style>\r\n<div id=\"knu-projects-app\">\r\n  <!-- AESTHETIC REGISTRATION HEADER INFO BAR -->\r\n  <div class=\"knu-registration-header\" id=\"knu-reg-header\">\r\n    <button class=\"knu-reg-toggle-btn\" id=\"knu-reg-header-toggle\" title=\"\u0417\u0433\u043e\u0440\u043d\u0443\u0442\u0438\/\u0420\u043e\u0437\u0433\u043e\u0440\u043d\u0443\u0442\u0438 \u0456\u043d\u0444\u043e-\u043f\u0430\u043d\u0435\u043b\u044c\">\r\n      <i data-lucide=\"chevron-up\" size=\"20\"><\/i>\r\n    <\/button>\r\n    <div class=\"knu-reg-main\">\r\n        <div class=\"knu-reg-title-wrap\">\r\n          <i data-lucide=\"info\" class=\"knu-reg-icon\"><\/i>\r\n          <div class=\"knu-reg-text-content\">\r\n            <div class=\"knu-reg-title\">\u0411\u0430\u0436\u0430\u0454\u0442\u0435 \u0437\u0430\u0440\u0435\u0454\u0441\u0442\u0440\u0443\u0432\u0430\u0442\u0438 \u0430\u0431\u043e \u0430\u0442\u0435\u0441\u0442\u0443\u0432\u0430\u0442\u0438 \u043d\u0430\u0443\u043a\u043e\u0432\u0443 \u0448\u043a\u043e\u043b\u0443?<\/div>\r\n            <div class=\"knu-reg-desc\"><br>\u041e\u0437\u043d\u0430\u0439\u043e\u043c\u0442\u0435\u0441\u044c \u0437 \u0434\u0435\u0442\u0430\u043b\u044c\u043d\u043e\u044e \u0456\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0456\u0454\u044e \u0442\u0430 \u0437\u0430\u0432\u0430\u043d\u0442\u0430\u0436\u0442\u0435 \u043f\u0430\u043a\u0435\u0442 \u043d\u0435\u043e\u0431\u0445\u0456\u0434\u043d\u0438\u0445 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0456\u0432. \u041a\u043e\u043d\u0441\u0443\u043b\u044c\u0442\u0430\u0446\u0456\u0457 \u0449\u043e\u0434\u043e \u043e\u0444\u043e\u0440\u043c\u043b\u0435\u043d\u043d\u044f \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0456\u0432 \u043c\u043e\u0436\u043d\u0430 \u043e\u0442\u0440\u0438\u043c\u0430\u0442\u0438 \u0437\u0430 \u043a\u043e\u043d\u0442\u0430\u043a\u0442\u0430\u043c\u0438 \u0432 \u0456\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0456\u0439\u043d\u0456\u0439 \"\u043a\u0430\u0440\u0442\u0446\u0456\", \u0430 \u0442\u0430\u043a\u043e\u0436 \u0437\u0430 \u043f\u043e\u0441\u0438\u043b\u0430\u043d\u043d\u044f\u043c \u043d\u0438\u0436\u0447\u0435.\r\n<br><br>\u0417\u0432\u0435\u0440\u0442\u0430\u0454\u043c\u043e \u0412\u0430\u0448\u0443 \u0443\u0432\u0430\u0433\u0443 \u043d\u0430 \u0442\u0435, \u0449\u043e \u0430\u0440\u0445\u0456\u0432 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0456\u0432 \u043c\u043e\u0436\u043d\u0430 \u0437\u0430\u0432\u0430\u043d\u0442\u0430\u0436\u0438\u0442\u0438 \u0442\u0443\u0442 \u0436\u0435, \u043f\u0440\u043e\u0442\u0435 \u0434\u0430\u043d\u0438\u0439 \u0444\u0443\u043d\u043a\u0446\u0456\u043e\u043d\u0430\u043b \u0454 <b><u>\u0435\u043a\u0441\u043f\u0435\u0440\u0438\u043c\u0435\u043d\u0442\u0430\u043b\u044c\u043d\u0438\u043c<\/u><\/b> \u0456 \u043c\u043e\u0436\u0435 \u043d\u0435 \u0434\u043e\u0434\u0430\u0432\u0430\u043d\u0442\u0430\u0436\u0443\u0432\u0430\u0442\u0438 \u0432\u0441\u0456\u0445 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0456\u0432. \u0422\u0430\u043a\u043e\u0436 \u0443\u0441\u0435 \u0436 <b><u>\u043d\u0430\u043f\u043e\u043b\u0435\u0433\u043b\u0438\u0432\u043e \u0440\u0430\u0434\u0438\u043c\u043e \u043e\u0437\u043d\u0430\u0439\u043e\u043c\u0438\u0442\u0438\u0441\u044f \u0437 \u0434\u0435\u0442\u0430\u043b\u044c\u043d\u043e\u044e \u0456\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0456\u0454\u044e<\/u><\/b>.<\/div>\r\n          <\/div>\r\n        <\/div>\r\n        <div class=\"knu-reg-actions\">\r\n            <a href=\"https:\/\/science.knu.ua\/new\/?page_id=4422&pkg=%D0%90%D1%82%D0%B5%D1%81%D1%82%D0%B0%D1%86%D1%96%D1%8F+%2F+%D0%A0%D0%B5%D1%94%D1%81%D1%82%D1%80%D0%B0%D1%86%D1%96%D1%8F+%D0%9D%D0%A8\" target=\"_blank\" class=\"knu-btn-reg primary\">\r\n              <i data-lucide=\"file-text\" size=\"16\"><\/i> \u0414\u0435\u0442\u0430\u043b\u0456 \u0442\u0430 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u044b\r\n            <\/a>\r\n            <button class=\"knu-btn-reg secondary\" onclick=\"downloadRegistrationZip(this)\" title=\"\u0410\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u043d\u0435 \u0437\u0430\u0432\u0430\u043d\u0442\u0430\u0436\u0435\u043d\u043d\u044f \u0432\u0441\u0456\u0445 \u0448\u0430\u0431\u043b\u043e\u043d\u0456\u0432\">\r\n              <i data-lucide=\"archive\" size=\"16\"><\/i> \u0417\u0430\u0432\u0430\u043d\u0442\u0430\u0436\u0438\u0442\u0438 \u043f\u0430\u043a\u0435\u0442 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0456\u0432\r\n              <span class=\"knu-reg-badge\">\u0415\u043a\u0441\u043f\u0435\u0440\u0438\u043c\u0435\u043d\u0442\u0430\u043b\u044c\u043d\u043e<\/span>\r\n            <\/button>\r\n        <\/div>\r\n    <\/div>\r\n    <!-- Dynamic Contact Card Container -->\r\n    <div id=\"knu-dynamic-contact\">\r\n       <div style=\"font-size: 0.85em; color:#0b5394; display:flex; align-items:center; gap:8px;\">\r\n         <i data-lucide=\"loader-2\" class=\"animate-spin\" size=\"18\"><\/i> \u0417\u0430\u0432\u0430\u043d\u0442\u0430\u0436\u0435\u043d\u043d\u044f \u043a\u043e\u043d\u0442\u0430\u043a\u0442\u0456\u0432...\r\n       <\/div>\r\n    <\/div>\r\n  <\/div>\r\n  <!-- Search & Filters -->\r\n  <div class=\"knu-filters\">\r\n    <div class=\"knu-input-group search-group\">\r\n      <label for=\"knu-search\">\r\n        <i data-lucide=\"search\" size=\"14\"><\/i> \u041f\u043e\u0448\u0443\u043a\r\n      <\/label>\r\n      <div style=\"position:relative;\">\r\n        <input type=\"text\" id=\"knu-search\" class=\"knu-input\" placeholder=\"\u041d\u0430\u0437\u0432\u0430, \u041a\u0435\u0440\u0456\u0432\u043d\u0438\u043a, \u0417\u0430\u0441\u043d\u043e\u0432\u043d\u0438\u043a...\">\r\n      <\/div>\r\n    <\/div>\r\n    <div class=\"knu-input-group\">\r\n      <label for=\"knu-faculty-filter\">\r\n        <i data-lucide=\"building-2\" size=\"14\"><\/i> \u041f\u0456\u0434\u0440\u043e\u0437\u0434\u0456\u043b\r\n      <\/label>\r\n      <select id=\"knu-faculty-filter\" class=\"knu-select\">\r\n        <option value=\"\">\u0412\u0441\u0456<\/option>\r\n      <\/select>\r\n    <\/div>\r\n    <div class=\"knu-input-group\">\r\n      <label for=\"knu-est-filter\">\r\n        <i data-lucide=\"history\" size=\"14\"><\/i> \u0417\u0430\u0441\u043d\u043e\u0432\u0430\u043d\u043e\r\n      <\/label>\r\n      <select id=\"knu-est-filter\" class=\"knu-select\">\r\n        <option value=\"\">\u0412\u0441\u0456 \u0440\u043e\u043a\u0438<\/option>\r\n      <\/select>\r\n    <\/div>\r\n    <div class=\"knu-input-group\">\r\n      <label for=\"knu-status-filter\">\r\n        <i data-lucide=\"activity\" size=\"14\"><\/i> \u0421\u0442\u0430\u0442\u0443\u0441\r\n      <\/label>\r\n      <select id=\"knu-status-filter\" class=\"knu-select\">\r\n        <option value=\"\">\u0412\u0441\u0456<\/option>\r\n        <option value=\"active\" selected>\u0410\u0442\u0435\u0441\u0442\u043e\u0432\u0430\u043d\u0456 \/ \u0417\u0430\u0440\u0435\u0454\u0441\u0442\u0440\u043e\u0432\u0430\u043d\u0456<\/option>\r\n        <option value=\"expired\">\u0423 \u043f\u043b\u0430\u043d\u0430\u0445 \u0430\u0442\u0435\u0441\u0442\u0430\u0446\u0456\u0457 \/ \u0410\u0440\u0445\u0456\u0432\u043d\u0456<\/option>\r\n      <\/select>\r\n    <\/div>\r\n    <div class=\"knu-input-group\">\r\n      <label for=\"knu-sort-select\">\r\n        <i data-lucide=\"arrow-up-down\" size=\"14\"><\/i> \u0421\u043e\u0440\u0442\u0443\u0432\u0430\u0442\u0438\r\n      <\/label>\r\n      <select id=\"knu-sort-select\" class=\"knu-select\">\r\n        <option value=\"default\">\u0411\u0430\u0437\u043e\u0432\u043e<\/option>\r\n        <option value=\"title_asc\">\u041d\u0430\u0437\u0432\u0430 &#9650;<\/option>\r\n        <option value=\"title_desc\">\u041d\u0430\u0437\u0432\u0430 &#9660;<\/option>\r\n        <option value=\"head_asc\">\u041a\u0435\u0440\u0456\u0432\u043d\u0438\u043a &#9650;<\/option>\r\n        <option value=\"est_desc\">\u0420\u0456\u043a (\u041d\u043e\u0432\u0456) &#9660;<\/option>\r\n        <option value=\"est_asc\">\u0420\u0456\u043a (\u0421\u0442\u0430\u0440\u0456) &#9650;<\/option>\r\n      <\/select>\r\n    <\/div>\r\n    <!-- Buttons Row -->\r\n    <div class=\"knu-input-group btn-group\">\r\n       <div style=\"display:flex; gap:5px;\">\r\n          <button id=\"knu-download-btn\" class=\"knu-btn-icon\" title=\"\u0417\u0430\u0432\u0430\u043d\u0442\u0430\u0436\u0438\u0442\u0438 \u0441\u043f\u0438\u0441\u043e\u043a (DOCX)\">\r\n             <i data-lucide=\"download\" size=\"18\"><\/i>\r\n          <\/button>\r\n          <button id=\"knu-view-toggle\" class=\"knu-btn-icon\" title=\"\u041f\u0435\u0440\u0435\u043c\u0438\u043a\u0430\u0447 \u0432\u0438\u0434\u0443 (\u0422\u0430\u0431\u043b\u0438\u0446\u044f\/\u041a\u0430\u0440\u0442\u043a\u0438)\">\r\n             <i data-lucide=\"table\" size=\"18\"><\/i>\r\n          <\/button>\r\n          <button id=\"knu-reload-btn\" class=\"knu-btn-icon\" title=\"\u041e\u043d\u043e\u0432\u0438\u0442\u0438 \u0434\u0430\u043d\u0456\">\r\n            <i data-lucide=\"rotate-cw\" size=\"18\"><\/i>\r\n          <\/button>\r\n       <\/div>\r\n    <\/div>\r\n  <\/div>\r\n  <!-- Counter Section -->\r\n  <div class=\"knu-counter-bar\">\r\n    <span>\u041f\u043e\u043a\u0430\u0437\u0430\u043d\u043e <span id=\"knu-shown-count\" class=\"knu-counter-number\">0<\/span> \u0456\u0437 <span id=\"knu-total-count\" class=\"knu-counter-number\">0<\/span> \u0448\u043a\u0456\u043b<\/span>\r\n    <span id=\"knu-cache-status\"><i data-lucide=\"cloud-download\" size=\"12\"><\/i> \u041e\u043d\u043e\u0432\u043b\u0435\u043d\u043d\u044f...<\/span>\r\n  <\/div>\r\n  <!-- Content Container (Grid or Table) -->\r\n  <div id=\"knu-content-area\">\r\n    <div class=\"knu-loading\">\r\n      <i data-lucide=\"loader-2\" class=\"animate-spin\" size=\"40\"><\/i>\r\n      <span id=\"knu-loading-status\">\u041f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0434\u043e \u0431\u0430\u0437\u0438 \u0434\u0430\u043d\u0438\u0445...<\/span>\r\n      <span id=\"knu-loading-details\">\u0406\u043d\u0456\u0446\u0456\u0430\u043b\u0456\u0437\u0430\u0446\u0456\u044f...<\/span>\r\n      <!-- HARDCODED SOURCE BUTTON -->\r\n      <a href=\"https:\/\/science.knu.ua\/new\/?p=5046\" target=\"_blank\" class=\"knu-btn knu-btn-web\" style=\"margin-top:15px; border-color:#ccc; color:#555!important; width:auto; padding: 0 15px;\">\r\n         <i data-lucide=\"external-link\" size=\"16\"><\/i> \u0412\u0456\u0434\u043a\u0440\u0438\u0442\u0438 \u043e\u0440\u0438\u0433\u0456\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u0442\u0456\r\n      <\/a>\r\n    <\/div>\r\n  <\/div>\r\n  <!-- FILE VIEWER MODAL -->\r\n  <div id=\"knu-viewer-modal\">\r\n    <div class=\"knu-viewer-content\">\r\n      <div class=\"knu-viewer-header\">\r\n        <div class=\"knu-viewer-title-group\">\r\n            <span class=\"knu-viewer-title\" id=\"knu-viewer-filename\">\u0414\u043e\u043a\u0443\u043c\u0435\u043d\u0442<\/span>\r\n            <a href=\"#\" id=\"knu-viewer-dl-btn\" target=\"_blank\" class=\"knu-viewer-external-link\">\r\n                <i data-lucide=\"external-link\" size=\"14\"><\/i> \u0421\u043a\u0430\u0447\u0430\u0442\u0438\/\u0412\u0456\u0434\u043a\u0440\u0438\u0442\u0438 \u043e\u0440\u0438\u0433\u0456\u043d\u0430\u043b\r\n            <\/a>\r\n        <\/div>\r\n        <button class=\"knu-viewer-close\" onclick=\"closeFileViewer()\">\u0417\u0430\u043a\u0440\u0438\u0442\u0438 <i data-lucide=\"x\" size=\"14\" style=\"display:inline; vertical-align:middle;\"><\/i><\/button>\r\n      <\/div>\r\n      <div class=\"knu-viewer-body\">\r\n         <div id=\"knu-viewer-loader\">\r\n            <i data-lucide=\"loader\" class=\"animate-spin\" size=\"32\" color=\"#0b5394\"><\/i>\r\n            <div class=\"knu-viewer-loading-text\">\u0417\u0430\u0432\u0430\u043d\u0442\u0430\u0436\u0435\u043d\u043d\u044f \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430...<\/div>\r\n            <div class=\"knu-viewer-timeout\" id=\"knu-viewer-timeout-msg\">\r\n                \u0417\u0430\u0432\u0430\u043d\u0442\u0430\u0436\u0435\u043d\u043d\u044f \u0442\u0440\u0438\u0432\u0430\u043e\u0433\u043e \u0434\u043e\u0432\u0448\u0435, \u043d\u0456\u0436 \u0437\u0430\u0437\u0432\u0438\u0447\u0430\u0439. <br>\u0421\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u043d\u0430\u0442\u0438\u0441\u043d\u0443\u0442\u0438 \"\u0412\u0456\u0434\u043a\u0440\u0438\u0442\u0438 \u043e\u0440\u0438\u0433\u0456\u043d\u0430\u043b\".\r\n            <\/div>\r\n         <\/div>\r\n         <iframe id=\"knu-viewer-frame\" class=\"knu-viewer-frame\" src=\"\" allowfullscreen><\/iframe>\r\n      <\/div>\r\n    <\/div>\r\n  <\/div>\r\n<\/div>\r\n<script>\r\n\/**\r\n * DATA STORAGE\r\n *\/\r\nlet schoolsData =[];\r\nlet currentFilteredData =[];\r\nlet currentViewMode = 'grid'; \/\/ 'grid' or 'table'\r\n\/\/ CONSTANTS\r\nconst INVALID_LINK = \"https:\/\/google.com\"; \r\nconst SOURCE_URL = \"https:\/\/science.knu.ua\/new\/?p=5046\";\r\nconst CACHE_KEY = \"knu_schools_data_v2\"; \/\/ LocalStorage key\r\ndocument.addEventListener('DOMContentLoaded', () => {\r\n  if (window.lucide) { lucide.createIcons(); }\r\n  \/\/ Header Toggle Logic\r\n  const regHeader = document.getElementById('knu-reg-header');\r\n  const regHeaderToggle = document.getElementById('knu-reg-header-toggle');\r\n  if (regHeaderToggle && regHeader) {\r\n      regHeaderToggle.addEventListener('click', () => {\r\n          regHeader.classList.toggle('collapsed');\r\n          const isCollapsed = regHeader.classList.contains('collapsed');\r\n          regHeaderToggle.innerHTML = isCollapsed\r\n              ? '<i data-lucide=\"chevron-down\" size=\"20\"><\/i>'\r\n              : '<i data-lucide=\"chevron-up\" size=\"20\"><\/i>';\r\n          if(window.lucide) lucide.createIcons();\r\n      });\r\n  }\r\n  const container = document.getElementById('knu-content-area');\r\n  const searchInput = document.getElementById('knu-search');\r\n  const facultyFilter = document.getElementById('knu-faculty-filter');\r\n  const estFilter = document.getElementById('knu-est-filter');\r\n  const statusFilter = document.getElementById('knu-status-filter');\r\n  const sortSelect = document.getElementById('knu-sort-select');\r\n  const reloadBtn = document.getElementById('knu-reload-btn');\r\n  const viewToggleBtn = document.getElementById('knu-view-toggle');\r\n  const downloadBtn = document.getElementById('knu-download-btn');\r\n  const shownCountEl = document.getElementById('knu-shown-count');\r\n  const totalCountEl = document.getElementById('knu-total-count');\r\n  const cacheStatusEl = document.getElementById('knu-cache-status');\r\n  \/\/ Update loading text helper\r\n  function updateLoadingUI(text) {\r\n    const statusEl = document.getElementById('knu-loading-status');\r\n    const detailsEl = document.getElementById('knu-loading-details');\r\n    if(statusEl) statusEl.textContent = text || \"\u0417\u0430\u0432\u0430\u043d\u0442\u0430\u0436\u0435\u043d\u043d\u044f \u0434\u0430\u043d\u0438\u0445...\";\r\n    if(detailsEl) detailsEl.textContent = \"\u041e\u0431\u0440\u043e\u0431\u043a\u0430...\";\r\n  }\r\n  \/\/ --- LOCAL STORAGE HELPERS ---\r\n  function saveToCache(data) {\r\n    try {\r\n        const payload = {\r\n            timestamp: new Date().getTime(),\r\n            data: data\r\n        };\r\n        localStorage.setItem(CACHE_KEY, JSON.stringify(payload));\r\n    } catch (e) {\r\n        console.warn(\"Local storage limit or error\", e);\r\n    }\r\n  }\r\n  function loadFromCache() {\r\n    try {\r\n        const raw = localStorage.getItem(CACHE_KEY);\r\n        if (!raw) return null;\r\n        return JSON.parse(raw);\r\n    } catch (e) {\r\n        return null;\r\n    }\r\n  }\r\n  \/\/ --- URL STATE MANAGEMENT ---\r\n  function applyUrlState() {\r\n     const params = new URLSearchParams(window.location.search);\r\n     if (params.has('q')) searchInput.value = params.get('q');\r\n     if (params.has('fac')) facultyFilter.value = params.get('fac');\r\n     if (params.has('est')) estFilter.value = params.get('est');\r\n     if (params.has('stat')) statusFilter.value = params.get('stat');\r\n     if (params.has('sort')) sortSelect.value = params.get('sort');\r\n     if (params.has('view')) {\r\n         const mode = params.get('view');\r\n         if (mode === 'table') {\r\n             currentViewMode = 'table';\r\n             viewToggleBtn.classList.add('active');\r\n             viewToggleBtn.innerHTML = '<i data-lucide=\"layout-grid\" size=\"18\"><\/i>';\r\n         } else {\r\n             currentViewMode = 'grid';\r\n             viewToggleBtn.classList.remove('active');\r\n             viewToggleBtn.innerHTML = '<i data-lucide=\"table\" size=\"18\"><\/i>';\r\n         }\r\n     }\r\n  }\r\n  function updateUrlState() {\r\n     const params = new URLSearchParams(window.location.search);\r\n     const setOrDelete = (key, value) => {\r\n         if (value) params.set(key, value);\r\n         else params.delete(key);\r\n     };\r\n     setOrDelete('q', searchInput.value);\r\n     setOrDelete('fac', facultyFilter.value);\r\n     setOrDelete('est', estFilter.value);\r\n     setOrDelete('stat', statusFilter.value === 'active' ? null : statusFilter.value); \/\/ Default active, don't spam URL unless changed\r\n     if (sortSelect.value && sortSelect.value !== 'default') {\r\n         params.set('sort', sortSelect.value);\r\n     } else {\r\n         params.delete('sort');\r\n     }\r\n     if (currentViewMode === 'table') {\r\n         params.set('view', 'table');\r\n     } else {\r\n         params.delete('view');\r\n     }\r\n     const newUrl = window.location.pathname + (params.toString() ? '?' + params.toString() : '');\r\n     window.history.replaceState({}, '', newUrl);\r\n  }\r\n  \/\/ --- FETCHING LOGIC (FROM DOCUMENTS CODE) ---\r\n  \/\/ Timeout wrapper for fetch\r\n  async function fetchWithTimeout(url, options = {}, timeout = 3000) {\r\n      const controller = new AbortController();\r\n      const id = setTimeout(() => controller.abort(), timeout);\r\n      try {\r\n          const response = await fetch(url, { ...options, signal: controller.signal });\r\n          clearTimeout(id);\r\n          return response;\r\n      } catch (error) {\r\n          clearTimeout(id);\r\n          throw error;\r\n      }\r\n  }\r\n  \/\/ IMPROVED: Prioritizes Direct Fetch, then falls back to reliable JSON proxy\r\n  async function fetchWithFallback(targetUrl = SOURCE_URL) {\r\n    const t = new Date().getTime();\r\n    const urlWithTime = `${targetUrl}&_t=${t}`;\r\n    \/\/ 1. Try Direct Fetch first (Best for same-origin or allowed CORS)\r\n    try {\r\n        const response = await fetchWithTimeout(urlWithTime, { \r\n            headers: { 'Cache-Control': 'no-cache' } \r\n        }, 2000);\r\n        if (response.ok) {\r\n            const text = await response.text();\r\n            if (text && text.length > 50) return text;\r\n        }\r\n    } catch (e) {\r\n        console.warn(\"Direct fetch failed, trying proxy...\", e);\r\n    }\r\n    \/\/ 2. Fallback: AllOrigins JSON Proxy (Reliable backup)\r\n    try {\r\n        const proxyUrl = `https:\/\/api.allorigins.win\/get?url=${encodeURIComponent(urlWithTime)}`;\r\n        const response = await fetchWithTimeout(proxyUrl, { cache: \"no-store\" }, 4000);\r\n        if (response.ok) {\r\n            const data = await response.json();\r\n            if (data.contents && data.contents.length > 50) return data.contents;\r\n        }\r\n    } catch (e) {\r\n        console.error(\"Proxy fetch failed\", e);\r\n    }\r\n    throw new Error(\"Unable to fetch data from source.\");\r\n  }\r\n  function parseHtmlToSchools(htmlContent) {\r\n      const parser = new DOMParser();\r\n      const doc = parser.parseFromString(htmlContent, \"text\/html\");\r\n      const contentArea = doc.querySelector('.entry-content') || doc.body;\r\n      const cleanText = contentArea.innerText;\r\n      \/\/ Extract markers\r\n      const startMarker = \"=\/=\/=\/=\/=\/=\/=\/=\/=\/=\/=\/=\/=\/=\/=\/=\/=\/=\/=\/=\/=\/=\/=\/=\/=\/=\/=\/=\/=\/=\/=[1]\";\r\n      const endMarker = \"=\/=\/=\/=\/=\/=\/=\/=\/=\/=\/=\/=\/=\/=\/=\/=\/=\/=\/=\/=\/=\/=\/=\/=\/=\/=\/=\/=\/=\/=\/=[2]\";\r\n      let targetText = cleanText;\r\n      if (cleanText.includes(startMarker) && cleanText.includes(endMarker)) {\r\n        targetText = cleanText.split(startMarker)[1].split(endMarker)[0];\r\n      }\r\n      const rawBlocks = targetText.split(\/(?:-\\\/)+\/);\r\n      return rawBlocks.map(block => {\r\n        if (!block.trim()) return null;\r\n        const school = {\r\n            attestDateStr: \"\",\r\n            durationStr: \"\",\r\n            faculty: \"\",\r\n            title: \"\",\r\n            head: \"\",\r\n            founder: \"\",\r\n            foundYear: \"\",\r\n            description: \"\",\r\n            presUrl: \"\",\r\n            webUrl: \"\",\r\n            isActive: false,\r\n            expireYear: 0,\r\n            isHeadDeceased: false,\r\n            isFounderDeceased: false,\r\n            headHtml: \"\",\r\n            founderHtml: \"\"\r\n        };\r\n        \/\/ Extract tagged text inside [\u041d\u0415\u041c\u0410\u0404_1]...[\u041d\u0415\u041c\u0410\u0404_2] in the raw block\r\n        const deceasedTaggedTexts = [];\r\n        const tagRegex = \/\\[\u041d\u0415\u041c\u0410\u0404_1\\](.*?)\\[\u041d\u0415\u041c\u0410\u0404_2\\]\/gi;\r\n        let tagMatch;\r\n        while ((tagMatch = tagRegex.exec(block)) !== null) {\r\n            deceasedTaggedTexts.push(tagMatch[1].trim());\r\n        }\r\n        const cleanTags = (str) => {\r\n            if (!str) return \"\";\r\n            return str.replace(\/\\[\u041d\u0415\u041c\u0410\u0404_1\\]\/gi, \"\").replace(\/\\[\u041d\u0415\u041c\u0410\u0404_2\\]\/gi, \"\");\r\n        };\r\n        let rawHead = \"\";\r\n        let rawFounder = \"\";\r\n        const lines = block.split(\/\\r?\\n\/);\r\n        lines.forEach(line => {\r\n            const separatorIndex = line.indexOf(':');\r\n            if (separatorIndex === -1) return;\r\n            let keyPart = line.substring(0, separatorIndex).trim().toLowerCase();\r\n            keyPart = keyPart.replace('.', '');\r\n            const value = line.substring(separatorIndex + 1).trim();\r\n            if (keyPart.includes('1a')) school.attestDateStr = cleanTags(value);\r\n            else if (keyPart.includes('2a')) school.durationStr = cleanTags(value);\r\n            else if (keyPart.includes('3a')) school.faculty = cleanTags(value);\r\n            else if (keyPart.includes('4a')) school.title = cleanTags(value);\r\n            else if (keyPart.includes('5a')) {\r\n                school.head = cleanTags(value);\r\n                rawHead = value;\r\n            }\r\n            else if (keyPart.includes('6a')) {\r\n                school.founder = cleanTags(value);\r\n                rawFounder = value;\r\n            }\r\n            else if (keyPart.includes('7a')) school.foundYear = cleanTags(value);\r\n            else if (keyPart.includes('8a')) school.description = cleanTags(value);\r\n            else if (keyPart.includes('9a')) school.presUrl = cleanTags(value);\r\n            else if (keyPart.includes('10a')) school.webUrl = cleanTags(value);\r\n        });\r\n        if (!school.title) return null;\r\n        \/\/ Dynamic deceased logic (tagged extraction + sentence structure heuristics)\r\n        const checkNameDeceased = (name, desc, tagged) => {\r\n            if (!name) return false;\r\n            \/\/ Heuristic 1: Explicit tags matched to the name or parts of it\r\n            for (const tagText of tagged) {\r\n                const cleanName = name.toLowerCase().replace(\/[^a-z\u0430-\u044f\u0454\u0456\u0457\u0491\\s]\/g, '').trim();\r\n                const cleanTag = tagText.toLowerCase().replace(\/[^a-z\u0430-\u044f\u0454\u0456\u0457\u0491\\s]\/g, '').trim();\r\n                if (cleanTag.includes(cleanName) || cleanName.includes(cleanTag)) {\r\n                    return true;\r\n                }\r\n                const nameWords = cleanName.split(\/\\s+\/).filter(w => w.length > 3);\r\n                if (nameWords.length > 0 && nameWords.every(w => cleanTag.includes(w))) {\r\n                    return true;\r\n                }\r\n            }\r\n            \/\/ Heuristic 2: Check by sentence structure \/ years \/ indicators\r\n            const nameParts = name.split(\/[\\s\/.,]+\/).map(p => p.trim().replace(\/[^a-zA-Z\u0430-\u044f\u0410-\u042f\u0451\u0401\u0456\u0406\u0457\u0407\u0454\u0404\u0491\u0490]\/g, '')).filter(p => p.length > 2);\r\n            if (nameParts.length === 0) return false;\r\n            \/\/ Lifespans in parentheses, e.g. (1923-2015)\r\n            const lifeSpanRegex = \/\\b(18|19)\\d{2}\\s*[\\-\u2013\u2014\\s]+\\s*(19|20)\\d{2}\\b\/;\r\n            if (lifeSpanRegex.test(desc)) {\r\n                if (nameParts.some(part => new RegExp(part, 'i').test(desc))) {\r\n                    return true;\r\n                }\r\n            }\r\n            \/\/ Academic and linguistic past-tense\/memorial markers in Ukrainian\r\n            const deceasedKeywords = [\r\n                \"\u043f\u043e\u043c\u0435\u0440\", \"\u0437\u0430\u0433\u0438\u043d\u0443\u0432\", \"\u043f\u0430\u043c'\u044f\u0442\u0456\", \"\u043f\u0430\u043c\u2019\u044f\u0442\u0456\", \"\u043f\u0456\u0448\u043e\u0432 \u0437 \u0436\u0438\u0442\u0442\u044f\", \"\u043f\u0456\u0448\u043b\u0430 \u0437 \u0436\u0438\u0442\u0442\u044f\", \r\n                \"\u043f\u043e\u0441\u043c\u0435\u0440\u0442\u043d\u043e\", \"\u0441\u0432\u0456\u0442\u043b\u043e\u0457 \u043f\u0430\u043c\", \"\u0443\u043f\u043e\u043a\u043e\u0457\u0432\u0441\u044f\", \"\u0432\u0448\u0430\u043d\u0443\u0432\u0430\u043d\", \"\u0432\u0448\u0430\u043d\u0443\u0432\u0430\u0442\u0438\", \"\u0431\u0443\u0432 \u0437\u0430\u0441\u043d\u043e\u0432\u043d\u0438\u043a\u043e\u043c\",\r\n                \"\u0431\u0443\u0432 \u0444\u0443\u043d\u0434\u0430\u0442\u043e\u0440\u043e\u043c\", \"\u0431\u0443\u043b\u0430 \u0437\u0430\u0441\u043d\u043e\u0432\u043d\u0438\u0446\u0435\u044e\", \"\u0432\u0438\u0434\u0430\u0442\u043d\u043e\u044e \u043f\u043e\u0441\u0442\u0430\u0442\u0442\u044e \u0432 \u0456\u0441\u0442\u043e\u0440\u0456\u0457\", \"\u043f\u043e\u0441\u0442\u0430\u0442\u0435\u0439 \u0432 \u0456\u0441\u0442\u043e\u0440\u0456\u0457\",\r\n                \"\u0444\u0443\u043d\u0434\u0430\u043c\u0435\u043d\u0442\u0430\u043b\u044c\u043d\u043e\u044e \u043f\u043e\u0441\u0442\u0430\u0442\u0442\u044e\", \"\u0456\u0441\u0442\u043e\u0440\u0456\u0457 \u0443\u043a\u0440\u0430\u0457\u043d\u0441\u044c\u043a\u043e\u0457\", \"\u0456\u0441\u0442\u043e\u0440\u0456\u0457 \u0441\u0432\u0456\u0442\u043e\u0432\u043e\u0457\", \"\u0456\u0441\u0442\u043e\u0440\u0456\u0457 \u043d\u0430\u0443\u043a\u0438\", \r\n                \"\u043d\u0430\u043b\u0435\u0436\u0430\u0432 \u0434\u043e\", \"\u043e\u0447\u043e\u043b\u044e\u0432\u0430\u0432 \u0443 \u043f\u0435\u0440\u0456\u043e\u0434\", \"\u0456\u0441\u0442\u043e\u0440\u0438\u0447\u043d\", \"\u0441\u0442\u0432\u043e\u0440\u0438\u0432 \u0441\u0432\u043e\u0433\u043e \u0447\u0430\u0441\u0443\", \"\u0432\u0438\u0434\u0430\u0442\u043d\u0438\u0439 \u0443\u043a\u0440\u0430\u0457\u043d\u0441\u044c\u043a\u0438\u0439 \u043f\u0440\u0430\u0432\u043e\u0437\u043d\u0430\u0432\u0435\u0446\u044c\",\r\n                \"\u0432\u0438\u0434\u0430\u0442\u043d\u0438\u0439 \u0432\u0447\u0435\u043d\u0438\u0439\", \"\u0440\u0430\u0434\u044f\u043d\u0441\u044c\u043a\", \"\u0443 \u043f\u0435\u0440\u0456\u043e\u0434 \u0437\", \"\u0437\u0430\u0441\u043d\u0443\u0432\u0430\u0432 \u0443 19\", \"\u043a\u0435\u0440\u0443\u0432\u0430\u0432 \u0434\u043e 20\"\r\n            ];\r\n            const sentences = desc.split(\/[.!?]\\s+\/);\r\n            for (const sentence of sentences) {\r\n                const hasName = nameParts.some(part => new RegExp(part, 'i').test(sentence));\r\n                if (hasName) {\r\n                    const hasKeyword = deceasedKeywords.some(kw => new RegExp(kw, 'i').test(sentence));\r\n                    if (hasKeyword) return true;\r\n                }\r\n            }\r\n            return false;\r\n        };\r\n        \/\/ Strict tag-based formatter\r\n        const formatDeceased = (rawValue) => {\r\n            if (!rawValue) return \"\";\r\n            return rawValue.replace(\/\\[\u041d\u0415\u041c\u0410\u0404_1\\]\/gi, '<span class=\"knu-deceased-frame\">').replace(\/\\[\u041d\u0415\u041c\u0410\u0404_2\\]\/gi, '<\/span>');\r\n        };\r\n        school.headHtml = formatDeceased(rawHead || school.head);\r\n        school.founderHtml = formatDeceased(rawFounder || school.founder);\r\n        school.isHeadDeceased = false;\r\n        school.isFounderDeceased = false;\r\n        \/\/ Calculate Status\r\n        let attestYear = 0;\r\n        if (school.attestDateStr.includes('.')) {\r\n            const parts = school.attestDateStr.split('.');\r\n            if (parts.length === 3) {\r\n               let y = parseInt(parts[2], 10);\r\n               attestYear = y < 100 ? 2000 + y : y;\r\n            }\r\n        } else {\r\n            attestYear = parseInt(school.attestDateStr, 10);\r\n        }\r\n        let dur = parseInt(school.durationStr, 10) || 0;\r\n        school.expireYear = attestYear + dur;\r\n        const currentYear = new Date().getFullYear();\r\n        school.isActive = currentYear <= school.expireYear;\r\n        return school;\r\n      }).filter(item => item !== null);\r\n  }\r\n  \/\/ --- MAIN ORCHESTRATOR ---\r\n  async function fetchSchools(forceReload = false) {\r\n    let isDisplayedFromCache = false;\r\n    \/\/ 1. Try Cache First (Sync)\r\n    if (!forceReload) {\r\n        const cached = loadFromCache();\r\n        if (cached && cached.data && cached.data.length > 0) {\r\n            schoolsData = cached.data;\r\n            populateFilters();\r\n            applyUrlState();\r\n            filterData();\r\n            isDisplayedFromCache = true;\r\n            \/\/ Update UI to show we are checking in background\r\n            if(cacheStatusEl) {\r\n                cacheStatusEl.style.display = \"block\";\r\n                cacheStatusEl.innerHTML = '<i data-lucide=\"cloud\" size=\"12\"><\/i> \u041f\u0435\u0440\u0435\u0432\u0456\u0440\u043a\u0430 \u043e\u043d\u043e\u0432\u043b\u0435\u043d\u044c...';\r\n                if(window.lucide) lucide.createIcons();\r\n            }\r\n        }\r\n    }\r\n    \/\/ 2. Network Fetch (Async Background)\r\n    (async () => {\r\n        try {\r\n            if (!isDisplayedFromCache) {\r\n                updateLoadingUI(\"\u0417\u0430\u0432\u0430\u043d\u0442\u0430\u0436\u0435\u043d\u043d\u044f \u0434\u0430\u043d\u0438\u0445...\");\r\n            }\r\n            let htmlContent = await fetchWithFallback();\r\n            const freshData = parseHtmlToSchools(htmlContent);\r\n            if (freshData.length > 0) {\r\n                const jsonFresh = JSON.stringify(freshData);\r\n                const jsonOld = isDisplayedFromCache ? JSON.stringify(schoolsData) : \"\";\r\n                if (jsonFresh !== jsonOld) {\r\n                    schoolsData = freshData;\r\n                    saveToCache(schoolsData);\r\n                    populateFilters();\r\n                    applyUrlState();\r\n                    filterData();\r\n                    if (cacheStatusEl) {\r\n                        cacheStatusEl.innerHTML = '<i data-lucide=\"check\" size=\"12\"><\/i> \u0414\u0430\u043d\u0456 \u043e\u043d\u043e\u0432\u043b\u0435\u043d\u043e';\r\n                        cacheStatusEl.style.display = \"block\";\r\n                        setTimeout(() => { cacheStatusEl.style.display = \"none\"; }, 3000);\r\n                        if(window.lucide) lucide.createIcons();\r\n                    }\r\n                } else {\r\n                    \/\/ Data matches cache\r\n                    if (cacheStatusEl) {\r\n                        cacheStatusEl.innerHTML = '<i data-lucide=\"check\" size=\"12\"><\/i> \u0410\u043a\u0442\u0443\u0430\u043b\u044c\u043d\u043e';\r\n                        setTimeout(() => { cacheStatusEl.style.display = \"none\"; }, 2000);\r\n                        if(window.lucide) lucide.createIcons();\r\n                    }\r\n                }\r\n            } else {\r\n                 \/\/ Ensure we don't get stuck in loading loop if parsing fails but no error was thrown\r\n                 throw new Error(\"\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u043e\u0442\u0440\u0438\u043c\u0430\u0442\u0438 \u0434\u0430\u043d\u0456 (\u043f\u043e\u0440\u043e\u0436\u043d\u0456\u0439 \u0441\u043f\u0438\u0441\u043e\u043a).\");\r\n            }\r\n        } catch (error) {\r\n            console.error(\"Fetch Error:\", error);\r\n            if (!isDisplayedFromCache) {\r\n                container.innerHTML = `\r\n                    <div class=\"knu-no-results\">\r\n                    <i data-lucide=\"wifi-off\" size=\"40\" style=\"margin-bottom:10px; color:#b80000;\"><\/i><br>\r\n                    \u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u0437\u0430\u0432\u0430\u043d\u0442\u0430\u0436\u0438\u0442\u0438 \u0434\u0430\u043d\u0456.<br>\r\n                    <small style=\"font-size:0.7em; color:#999;\">Error: ${error.message}<\/small><br>\r\n                    <div style=\"display:flex; flex-direction:column; gap:10px; align-items:center; margin-top:15px;\">\r\n                        <button onclick=\"location.reload()\" class=\"knu-btn\" style=\"max-width:200px; background:#eee;\">\u0421\u043f\u0440\u043e\u0431\u0443\u0432\u0430\u0442\u0438 \u0449\u0435 \u0440\u0430\u0437<\/button>\r\n                        <a href=\"https:\/\/science.knu.ua\/new\/?p=5046\" target=\"_blank\" class=\"knu-btn knu-btn-web\" style=\"max-width:200px; border-color:#ccc; color:#555!important;\">\r\n                            <i data-lucide=\"external-link\" size=\"16\"><\/i> \u0412\u0456\u0434\u043a\u0440\u0438\u0442\u0438 \u0441\u0442\u0430\u0442\u0442\u044e\r\n                        <\/a>\r\n                    <\/div>\r\n                    <\/div>\r\n                `;\r\n                if (window.lucide) lucide.createIcons();\r\n            } else {\r\n                if (cacheStatusEl) {\r\n                    cacheStatusEl.innerHTML = '<i data-lucide=\"wifi-off\" size=\"12\"><\/i> \u041e\u0444\u043b\u0430\u0439\u043d \u0440\u0435\u0436\u0438\u043c';\r\n                    if(window.lucide) lucide.createIcons();\r\n                }\r\n            }\r\n        }\r\n    })();\r\n  }\r\n  function populateFilters() {\r\n    while (facultyFilter.options.length > 1) { facultyFilter.remove(1); }\r\n    while (estFilter.options.length > 1) { estFilter.remove(1); }\r\n    const uniqueFaculties =[...new Set(schoolsData.map(p => p.faculty))].filter(f => f).sort();\r\n    uniqueFaculties.forEach(faculty => {\r\n      const option = document.createElement('option');\r\n      option.value = faculty;\r\n      option.textContent = faculty;\r\n      facultyFilter.appendChild(option);\r\n    });\r\n    const uniqueEst =[...new Set(schoolsData.map(p => p.foundYear))].filter(y => y).sort((a,b) => b-a);\r\n    uniqueEst.forEach(year => {\r\n      const option = document.createElement('option');\r\n      option.value = year;\r\n      option.textContent = year;\r\n      estFilter.appendChild(option);\r\n    });\r\n  }\r\n  function renderSchools(schools) {\r\n    shownCountEl.textContent = schools.length;\r\n    totalCountEl.textContent = schoolsData.length;\r\n    container.innerHTML = '';\r\n    if (schools.length === 0) {\r\n      container.innerHTML = '<div class=\"knu-no-results\">\u041d\u0430 \u0436\u0430\u043b\u044c, \u0437\u0430 \u0432\u0430\u0448\u0438\u043c \u0437\u0430\u043f\u0438\u0442\u043e\u043c \u043d\u0456\u0447\u043e\u0433\u043e \u043d\u0435 \u0437\u043d\u0430\u0439\u0434\u0435\u043d\u043e.<\/div>';\r\n      return;\r\n    }\r\n    if (currentViewMode === 'table') {\r\n      renderTable(schools);\r\n    } else {\r\n      renderGrid(schools);\r\n    }\r\n    if (window.lucide) { lucide.createIcons(); }\r\n  }\r\n  function renderGrid(schools) {\r\n    container.className = 'knu-grid-view';\r\n    schools.forEach((item, index) => {\r\n      const uniqueQrId = `qr-${index}`;\r\n      const uniqueDescId = `desc-${index}`;\r\n      const statusIconHtml = item.isActive \r\n        ? `<div title=\"\u0414\u0456\u044e\u0447\u0430 \u0448\u043a\u043e\u043b\u0430 (\u0434\u043e ${item.expireYear})\"><i data-lucide=\"circle-check\" color=\"var(--knu-green)\" fill=\"#e8f5e9\" size=\"22\"><\/i><\/div>`\r\n        : `<div title=\"\u0410\u0442\u0435\u0441\u0442\u0430\u0446\u0456\u044e \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u043e\"><i data-lucide=\"circle-x\" color=\"var(--knu-red)\" fill=\"#ffebee\" size=\"22\"><\/i><\/div>`;\r\n      \/\/ Check Links\r\n      const hasPres = item.presUrl && item.presUrl !== \"\u0412\u0456\u0434\u0441\u0443\u0442\u043d\u0454\" && item.presUrl !== INVALID_LINK;\r\n      const hasWeb = item.webUrl && item.webUrl !== INVALID_LINK;\r\n      const hasDesc = item.description && item.description.length > 5;\r\n      const qrTarget = hasWeb ? item.webUrl : (hasPres ? item.presUrl : null);\r\n      const qrLabel = (qrTarget === item.webUrl) ? \"\u0412\u0435\u0431-\u0441\u0430\u0439\u0442\" : \"\u041f\u0440\u0435\u0437\u0435\u043d\u0442\u0430\u0446\u0456\u044f\";\r\n      let buttonsHtml = '';\r\n      \/\/ Presentation\r\n      if (hasPres) {\r\n        buttonsHtml += `\r\n          <a href=\"${item.presUrl}\" target=\"_blank\" class=\"knu-btn knu-btn-pres\">\r\n            <i data-lucide=\"presentation\" size=\"16\"><\/i> \u041f\u0440\u0435\u0437\u0435\u043d\u0442\u0430\u0446\u0456\u044f\r\n          <\/a>`;\r\n      }\r\n      \/\/ Web\r\n      if (hasWeb) {\r\n        buttonsHtml += `\r\n          <a href=\"${item.webUrl}\" target=\"_blank\" class=\"knu-btn knu-btn-web\">\r\n            <i data-lucide=\"globe\" size=\"16\"><\/i> \u0421\u0430\u0439\u0442\r\n          <\/a>`;\r\n      }\r\n      \/\/ Description\r\n      if (hasDesc) {\r\n        buttonsHtml += `\r\n          <button class=\"knu-btn knu-btn-util\" onclick=\"toggleOverlay('${uniqueDescId}')\" title=\"\u0406\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0456\u044f \u043f\u0440\u043e \u0448\u043a\u043e\u043b\u0443\">\r\n            <i data-lucide=\"info\" size=\"18\"><\/i>\r\n          <\/button>`;\r\n      }\r\n      \/\/ File Viewer\r\n      if (hasPres) {\r\n          buttonsHtml += `\r\n          <button class=\"knu-btn knu-btn-util\" onclick=\"openFileViewer('${item.presUrl}', '${item.title.replace(\/'\/g, \"\\\\'\")}')\" title=\"\u041f\u0435\u0440\u0435\u0433\u043b\u044f\u043d\u0443\u0442\u0438 \u0444\u0430\u0439\u043b (\u041e\u043d\u043b\u0430\u0439\u043d)\">\r\n            <i data-lucide=\"eye\" size=\"18\"><\/i>\r\n          <\/button>`;\r\n      }\r\n      \/\/ QR\r\n      if (qrTarget) {\r\n        buttonsHtml += `\r\n          <button class=\"knu-btn knu-btn-util\" onclick=\"toggleQR('${uniqueQrId}', '${qrTarget}')\" title=\"QR \u043f\u043e\u0441\u0438\u043b\u0430\u043d\u043d\u044f\">\r\n            <i data-lucide=\"qr-code\" size=\"18\"><\/i>\r\n          <\/button>`;\r\n      }\r\n      \/\/ Deceased frame check for Grid render\r\n      const headHtml = item.headHtml || item.head;\r\n      const founderHtml = item.founderHtml || item.founder;\r\n      const card = document.createElement('div');\r\n      card.className = 'knu-card';\r\n      card.innerHTML = `\r\n        <div class=\"knu-card-header\">\r\n          <div class=\"knu-type-box\" title=\"\u041d\u0430\u0443\u043a\u043e\u0432\u0430 \u0448\u043a\u043e\u043b\u0430\">\r\n            <i data-lucide=\"graduation-cap\" size=\"18\"><\/i>\r\n          <\/div>\r\n          <div class=\"knu-header-year\" title=\"\u0420\u0456\u043a\/\u0414\u0430\u0442\u0430 \u0430\u0442\u0435\u0441\u0442\u0430\u0446\u0456\u0457\">\r\n            <i data-lucide=\"calendar-check\" size=\"18\"><\/i> ${item.attestDateStr}\r\n          <\/div>\r\n          <div class=\"knu-status-icon\">${statusIconHtml}<\/div>\r\n        <\/div>\r\n        <div class=\"knu-card-body\">\r\n          <div class=\"knu-meta-row\">\r\n             <i data-lucide=\"hourglass\" size=\"14\"><\/i> <b>\u0422\u0440\u0438\u0432\u0430\u043b\u0456\u0441\u0442\u044c:<\/b> ${item.durationStr}\r\n          <\/div>\r\n          <div class=\"knu-faculty\">\r\n            <i data-lucide=\"building-2\" size=\"16\"><\/i>${item.faculty}\r\n          <\/div>\r\n          <h3 class=\"knu-title\">${item.title}<\/h3>\r\n          <div class=\"knu-person-block\">\r\n             <div class=\"knu-person-label\">\r\n                <i data-lucide=\"user\" size=\"14\"><\/i> \u041a\u0435\u0440\u0456\u0432\u043d\u0438\u043a:\r\n             <\/div>\r\n             <div class=\"knu-person-value\">${headHtml}<\/div>\r\n          <\/div>\r\n          <div class=\"knu-person-block\">\r\n             <div class=\"knu-person-label\">\r\n                <i data-lucide=\"flag\" size=\"14\"><\/i> \u0417\u0430\u0441\u043d\u043e\u0432\u043d\u0438\u043a:\r\n             <\/div>\r\n             <div class=\"knu-person-value\">${founderHtml}<\/div>\r\n          <\/div>\r\n          <div class=\"knu-foundation-row\">\r\n             <i data-lucide=\"landmark\" size=\"16\"><\/i>\r\n             <span><b>\u0417\u0430\u0441\u043d\u043e\u0432\u0430\u043d\u043e:<\/b> ${item.foundYear}<\/span>\r\n          <\/div>\r\n          <!-- Description Overlay -->\r\n          <div class=\"knu-overlay-container\" id=\"${uniqueDescId}\">\r\n             <div class=\"knu-close-overlay\" onclick=\"toggleOverlay('${uniqueDescId}')\"><i data-lucide=\"x\" size=\"24\"><\/i><\/div>\r\n             <div class=\"knu-desc-title\">\u041f\u0440\u043e \u043d\u0430\u0443\u043a\u043e\u0432\u0443 \u0448\u043a\u043e\u043b\u0443<\/div>\r\n             <div class=\"knu-desc-content\">${item.description}<\/div>\r\n          <\/div>\r\n          <!-- QR Overlay -->\r\n          <div class=\"knu-overlay-container\" id=\"${uniqueQrId}\">\r\n             <div class=\"knu-close-overlay\" onclick=\"toggleOverlay('${uniqueQrId}')\"><i data-lucide=\"x\" size=\"24\"><\/i><\/div>\r\n             <!-- QR Title -->\r\n             <div class=\"knu-desc-title\" style=\"text-align: center; border-bottom: none; margin-bottom: 5px; font-size:1em;\">\r\n                 ${item.title}\r\n             <\/div>\r\n             <div class=\"knu-qr-canvas\"><\/div>\r\n             <!-- QR Label -->\r\n             <div class=\"knu-overlay-hint\">\r\n                 <i data-lucide=\"${qrLabel === '\u0412\u0435\u0431-\u0441\u0430\u0439\u0442' ? 'globe' : 'file-text'}\" size=\"14\" style=\"vertical-align:middle;\"><\/i> \r\n                ${qrLabel}\r\n             <\/div>\r\n          <\/div>\r\n        <\/div>\r\n        <div class=\"knu-card-footer\">${buttonsHtml}<\/div>\r\n      `;\r\n      container.appendChild(card);\r\n    });\r\n  }\r\n  function renderTable(schools) {\r\n    container.className = ''; \r\n    let rowsHtml = '';\r\n    schools.forEach((item, index) => {\r\n      const statusIcon = item.isActive \r\n        ? `<i data-lucide=\"circle-check\" color=\"var(--knu-green)\" fill=\"#e8f5e9\" size=\"20\"><\/i>`\r\n        : `<i data-lucide=\"circle-x\" color=\"var(--knu-red)\" fill=\"#ffebee\" size=\"20\"><\/i>`;\r\n      const hasPres = item.presUrl && item.presUrl !== \"\u0412\u0456\u0434\u0441\u0443\u0442\u043d\u0454\" && item.presUrl !== INVALID_LINK;\r\n      const hasWeb = item.webUrl && item.webUrl !== INVALID_LINK;\r\n      let linksHtml = '';\r\n      if (hasPres) {\r\n          linksHtml += `<a href=\"${item.presUrl}\" target=\"_blank\" class=\"knu-link-sm\" title=\"\u0417\u0430\u0432\u0430\u043d\u0442\u0430\u0436\u0438\u0442\u0438\"><i data-lucide=\"download\" size=\"14\"><\/i><\/a>`;\r\n          linksHtml += `<button onclick=\"openFileViewer('${item.presUrl}', '${item.title.replace(\/'\/g, \"\\\\'\")}')\" class=\"knu-link-sm\" title=\"\u041f\u0435\u0440\u0435\u0433\u043b\u044f\u043d\u0443\u0442\u0438\"><i data-lucide=\"eye\" size=\"14\"><\/i><\/button>`;\r\n      }\r\n      if (hasWeb) linksHtml += `<a href=\"${item.webUrl}\" target=\"_blank\" class=\"knu-link-sm\" title=\"\u0421\u0430\u0439\u0442\"><i data-lucide=\"globe\" size=\"14\"><\/i><\/a>`;\r\n      \/\/ Deceased frame check for Table render\r\n      const headHtml = item.headHtml || item.head;\r\n      const founderHtml = item.founderHtml || item.founder;\r\n      rowsHtml += `\r\n        <tr>\r\n          <td style=\"text-align:center;\">${statusIcon}<\/td>\r\n          <td>\r\n            <span class=\"knu-table-title\">${item.title}<\/span>\r\n            <span class=\"knu-table-id\">\u0410\u0442\u0435\u0441\u0442\u0430\u0446\u0456\u044f: ${item.attestDateStr}<\/span>\r\n          <\/td>\r\n          <td class=\"knu-table-sup\">${headHtml}<\/td>\r\n          <td class=\"knu-table-sup\">${founderHtml}<\/td>\r\n          <td style=\"font-size:0.85em;\">${item.faculty}<\/td>\r\n          <td style=\"white-space:nowrap; font-weight:600;\">${item.foundYear}<\/td>\r\n          <td style=\"white-space:nowrap;\">${linksHtml}<\/td>\r\n        <\/tr>\r\n      `;\r\n    });\r\n    const tableHtml = `\r\n      <div class=\"knu-table-wrapper\">\r\n        <table class=\"knu-table\">\r\n          <thead>\r\n            <tr>\r\n              <th width=\"40\" style=\"text-align:center;\">\u0421\u0442.<\/th>\r\n              <th>\u041d\u0430\u0443\u043a\u043e\u0432\u0430 \u0428\u043a\u043e\u043b\u0430<\/th>\r\n              <th>\u041a\u0435\u0440\u0456\u0432\u043d\u0438\u043a<\/th>\r\n              <th>\u0417\u0430\u0441\u043d\u043e\u0432\u043d\u0438\u043a<\/th>\r\n              <th>\u041f\u0456\u0434\u0440\u043e\u0437\u0434\u0456\u043b<\/th>\r\n              <th>\u0417\u0430\u0441\u043d\u043e\u0432\u0430\u043d\u043e<\/th>\r\n              <th>\u041f\u043e\u0441\u0438\u043b\u0430\u043d\u043d\u044f<\/th>\r\n            <\/tr>\r\n          <\/thead>\r\n          <tbody>\r\n            ${rowsHtml}\r\n          <\/tbody>\r\n        <\/table>\r\n      <\/div>\r\n    `;\r\n    container.innerHTML = tableHtml;\r\n  }\r\n  function filterData() {\r\n    const query = searchInput.value.toLowerCase();\r\n    const selectedFaculty = facultyFilter.value;\r\n    const selectedEst = estFilter.value;\r\n    const selectedStatus = statusFilter.value;\r\n    const sortValue = sortSelect.value;\r\n    \/\/ UPDATE URL\r\n    updateUrlState();\r\n    let filtered = schoolsData.filter(item => {\r\n      const matchSearch = item.title.toLowerCase().includes(query) || \r\n                          item.head.toLowerCase().includes(query) || \r\n                          item.founder.toLowerCase().includes(query);\r\n      const matchFaculty = selectedFaculty ? item.faculty === selectedFaculty : true;\r\n      const matchEst = selectedEst ? item.foundYear === selectedEst : true;\r\n      let matchStatus = true;\r\n      if (selectedStatus === 'active') matchStatus = item.isActive;\r\n      if (selectedStatus === 'expired') matchStatus = !item.isActive;\r\n      return matchSearch && matchFaculty && matchEst && matchStatus;\r\n    });\r\n    \/\/ Sorting\r\n    if (sortValue === 'title_asc') {\r\n      filtered.sort((a, b) => a.title.localeCompare(b.title, 'uk'));\r\n    } else if (sortValue === 'title_desc') {\r\n      filtered.sort((a, b) => b.title.localeCompare(a.title, 'uk'));\r\n    } else if (sortValue === 'head_asc') {\r\n      filtered.sort((a, b) => a.head.localeCompare(b.head, 'uk'));\r\n    } else if (sortValue === 'est_desc') {\r\n      filtered.sort((a, b) => b.foundYear - a.foundYear);\r\n    } else if (sortValue === 'est_asc') {\r\n      filtered.sort((a, b) => a.foundYear - b.foundYear);\r\n    }\r\n    currentFilteredData = filtered;\r\n    renderSchools(filtered);\r\n  }\r\n  \/\/ --- DOCX GENERATOR ---\r\n  downloadBtn.addEventListener('click', () => {\r\n    if (!currentFilteredData || currentFilteredData.length === 0) {\r\n      alert(\"\u041d\u0435\u043c\u0430\u0454 \u0434\u0430\u043d\u0438\u0445 \u0434\u043b\u044f \u0437\u0430\u0432\u0430\u043d\u0442\u0430\u0436\u0435\u043d\u043d\u044f\");\r\n      return;\r\n    }\r\n    if (!window.docx) {\r\n      alert(\"\u0411\u0456\u0431\u043b\u0456\u043e\u0442\u0435\u043a\u0430 \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0456\u0457 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0456\u0432 \u0449\u0435 \u0437\u0430\u0432\u0430\u043d\u0442\u0430\u0436\u0443\u0454\u0442\u044c\u0441\u044f.\");\r\n      return;\r\n    }\r\n    const { Document, Packer, Paragraph, TextRun, HeadingLevel, AlignmentType } = window.docx;\r\n    const children =[];\r\n    \/\/ Title\r\n    children.push(new Paragraph({\r\n        text: \"\u041f\u0435\u0440\u0435\u043b\u0456\u043a \u043d\u0430\u0443\u043a\u043e\u0432\u0438\u0445 \u0448\u043a\u0456\u043b\",\r\n        heading: HeadingLevel.HEADING_1,\r\n        alignment: AlignmentType.CENTER,\r\n        spacing: { after: 200 }\r\n    }));\r\n    \/\/ Summary\r\n    children.push(new Paragraph({\r\n        text: `\u0417\u0433\u0435\u043d\u0435\u0440\u043e\u0432\u0430\u043d\u043e: ${new Date().toLocaleDateString()} | \u041a\u0456\u043b\u044c\u043a\u0456\u0441\u0442\u044c: ${currentFilteredData.length}`,\r\n        alignment: AlignmentType.CENTER,\r\n        spacing: { after: 400 }\r\n    }));\r\n    \/\/ Data Loop\r\n    currentFilteredData.forEach(p => {\r\n        const addField = (label, val) => {\r\n            children.push(new Paragraph({\r\n                children:[\r\n                    new TextRun({ text: label + \": \", bold: true }),\r\n                    new TextRun({ text: val || \"\" })\r\n                ],\r\n                spacing: { after: 50 }\r\n            }));\r\n        };\r\n        addField(\"\u041d\u0430\u0437\u0432\u0430 \u043d\u0430\u0443\u043a\u043e\u0432\u043e\u0457 \u0448\u043a\u043e\u043b\u0438\", p.title);\r\n        addField(\"\u0421\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u043d\u0438\u0439 \u043f\u0456\u0434\u0440\u043e\u0437\u0434\u0456\u043b\", p.faculty);\r\n        addField(\"\u041a\u0435\u0440\u0456\u0432\u043d\u0438\u043a\", p.head);\r\n        addField(\"\u0417\u0430\u0441\u043d\u043e\u0432\u043d\u0438\u043a\", p.founder);\r\n        addField(\"\u0420\u0456\u043a \u0437\u0430\u0441\u043d\u0443\u0432\u0430\u043d\u043d\u044f\", p.foundYear);\r\n        addField(\"\u0414\u0430\u0442\u0430 \u0430\u0442\u0435\u0441\u0442\u0430\u0446\u0456\u0457\", p.attestDateStr);\r\n        addField(\"\u0422\u0440\u0438\u0432\u0430\u043b\u0456\u0441\u0442\u044c \u0430\u0442\u0435\u0441\u0442\u0430\u0446\u0456\u0457\", p.durationStr);\r\n        addField(\"\u041a\u043e\u0440\u043e\u0442\u043a\u0438\u0439 \u043e\u043f\u0438\u0441\", p.description);\r\n        addField(\"\u041f\u043e\u0441\u0438\u043b\u0430\u043d\u043d\u044f \u043d\u0430 \u043f\u0440\u0435\u0437\u0435\u043d\u0442\u0430\u0446\u0456\u044e\", p.presUrl);\r\n        addField(\"\u041f\u043e\u0441\u0438\u043b\u0430\u043d\u043d\u044f \u043d\u0430 \u0441\u0430\u0439\u0442\", p.webUrl);\r\n        \/\/ Separator\r\n        children.push(new Paragraph({\r\n            text: \"--------------------------------------------------------------------------------------------\",\r\n            spacing: { before: 100, after: 100 },\r\n            color: \"999999\"\r\n        }));\r\n    });\r\n    const doc = new Document({\r\n        sections: [{ children: children }]\r\n    });\r\n    Packer.toBlob(doc).then(blob => {\r\n        saveAs(blob, \"scientific_schools_export.docx\");\r\n    });\r\n  });\r\n  searchInput.addEventListener('input', filterData);\r\n  facultyFilter.addEventListener('change', filterData);\r\n  estFilter.addEventListener('change', filterData);\r\n  statusFilter.addEventListener('change', filterData);\r\n  sortSelect.addEventListener('change', filterData);\r\n  reloadBtn.addEventListener('click', () => {\r\n    container.innerHTML = `\r\n      <div class=\"knu-loading\">\r\n        <i data-lucide=\"loader-2\" class=\"animate-spin\" size=\"40\"><\/i>\r\n        <span id=\"knu-loading-status\">\u041f\u0435\u0440\u0435\u0437\u0430\u0432\u0430\u043d\u0442\u0430\u0436\u0435\u043d\u043d\u044f...<\/span>\r\n        <span id=\"knu-loading-details\">\u041e\u0447\u0438\u0449\u0435\u043d\u043d\u044f \u043a\u0435\u0448\u0443...<\/span>\r\n        <a href=\"https:\/\/science.knu.ua\/new\/?p=5046\" target=\"_blank\" class=\"knu-btn knu-btn-web\" style=\"margin-top:15px; border-color:#ccc; color:#555!important; width:auto; padding: 0 15px;\">\r\n           <i data-lucide=\"external-link\" size=\"16\"><\/i> \u0412\u0456\u0434\u043a\u0440\u0438\u0442\u0438 \u043e\u0440\u0438\u0433\u0456\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u0442\u0456\r\n        <\/a>\r\n      <\/div>\r\n    `;\r\n    if (window.lucide) lucide.createIcons();\r\n    schoolsData =[];\r\n    currentFilteredData =[];\r\n    localStorage.removeItem(CACHE_KEY); \r\n    \/\/ Clear only APP params on hard reload\r\n    const params = new URLSearchParams(window.location.search);['q', 'fac', 'est', 'stat', 'sort', 'view'].forEach(k => params.delete(k));\r\n    const newUrl = window.location.pathname + (params.toString() ? '?' + params.toString() : '');\r\n    window.history.replaceState({}, '', newUrl);\r\n    searchInput.value = '';\r\n    fetchSchools(true); \r\n  });\r\n  viewToggleBtn.addEventListener('click', () => {\r\n    currentViewMode = currentViewMode === 'grid' ? 'table' : 'grid';\r\n    if(currentViewMode === 'table') {\r\n        viewToggleBtn.classList.add('active');\r\n        viewToggleBtn.innerHTML = '<i data-lucide=\"layout-grid\" size=\"18\"><\/i>';\r\n    } else {\r\n        viewToggleBtn.classList.remove('active');\r\n        viewToggleBtn.innerHTML = '<i data-lucide=\"table\" size=\"18\"><\/i>';\r\n    }\r\n    filterData(); \r\n  });\r\n  \/\/ Init Main Logic\r\n  fetchSchools();\r\n  \/\/ Init Dynamic Contact Parsing\r\n  fetchUmanskaContact();\r\n});\r\n\/\/ Generic Overlay Toggle (Used for QR and Description)\r\nwindow.toggleOverlay = function(elementId) {\r\n  const container = document.getElementById(elementId);\r\n  if (!container) return;\r\n  const isHidden = getComputedStyle(container).display === 'none';\r\n  document.querySelectorAll('.knu-overlay-container').forEach(el => el.style.display = 'none');\r\n  if (isHidden) {\r\n    container.style.display = 'flex';\r\n  } else {\r\n    container.style.display = 'none';\r\n  }\r\n};\r\n\/\/ Specific wrapper for QR\r\nwindow.toggleQR = function(elementId, url) {\r\n  const container = document.getElementById(elementId);\r\n  if (!container) return;\r\n  const canvasDiv = container.querySelector('.knu-qr-canvas');\r\n  if (canvasDiv && canvasDiv.innerHTML.trim() === '') {\r\n      try {\r\n        new QRCode(canvasDiv, {\r\n          text: url,\r\n          width: 150,\r\n          height: 150,\r\n          colorDark : \"#0b5394\",\r\n          colorLight : \"#ffffff\",\r\n          correctLevel : QRCode.CorrectLevel.H\r\n        });\r\n        canvasDiv.addEventListener('click', () => { window.open(url, '_blank'); });\r\n      } catch (e) { console.error(\"QR Error\", e); }\r\n  }\r\n  toggleOverlay(elementId);\r\n};\r\n\/\/ Toggle logic for Contact Card QR\r\nwindow.toggleContactQR = function(btn, data) {\r\n    const card = btn.closest('.bot-contact-card');\r\n    const overlay = card.querySelector('.bot-qr-overlay');\r\n    const currentData = overlay.getAttribute('data-current');\r\n    if (overlay.classList.contains('active') && currentData === data) {\r\n        overlay.classList.remove('active');\r\n        overlay.setAttribute('data-current', '');\r\n    } else {\r\n        overlay.innerHTML = `\r\n            <div class=\"bot-overlay-header\">\r\n                <span class=\"bot-overlay-title\">QR-\u043a\u043e\u0434<\/span>\r\n                <button class=\"bot-overlay-close\" onclick=\"this.closest('.bot-qr-overlay').classList.remove('active')\" title=\"\u0417\u0430\u043a\u0440\u0438\u0442\u0438\">\r\n                    <i data-lucide=\"x\" size=\"16\"><\/i>\r\n                <\/button>\r\n            <\/div>\r\n            <div id=\"bot-qr-target-${Date.now()}\"><\/div>\r\n        `;\r\n        const qrTarget = overlay.querySelector('div[id^=\"bot-qr-target-\"]');\r\n        new QRCode(qrTarget, {\r\n            text: data,\r\n            width: 110,\r\n            height: 110,\r\n            colorDark : \"#0b5394\",\r\n            colorLight : \"#ffffff\",\r\n            correctLevel : QRCode.CorrectLevel.H\r\n        });\r\n        overlay.classList.add('active');\r\n        overlay.setAttribute('data-current', data);\r\n        if(window.lucide) lucide.createIcons();\r\n    }\r\n};\r\n\/\/ --- FILE VIEWER FUNCTIONS ---\r\nlet loaderTimeout = null;\r\nwindow.openFileViewer = function(url, title) {\r\n    if(!url || url === 'undefined') return;\r\n    const modal = document.getElementById('knu-viewer-modal');\r\n    const iframe = document.getElementById('knu-viewer-frame');\r\n    const titleEl = document.getElementById('knu-viewer-filename');\r\n    const dlBtn = document.getElementById('knu-viewer-dl-btn');\r\n    const loader = document.getElementById('knu-viewer-loader');\r\n    const timeoutMsg = document.getElementById('knu-viewer-timeout-msg');\r\n    if(modal && iframe) {\r\n        titleEl.textContent = title || \"\u0414\u043e\u043a\u0443\u043c\u0435\u043d\u0442\";\r\n        dlBtn.href = url;\r\n        iframe.src = \"\";\r\n        modal.style.display = \"flex\";\r\n        if(loader) {\r\n            loader.style.display = \"flex\";\r\n            if(timeoutMsg) timeoutMsg.style.display = \"none\";\r\n        }\r\n        const extension = url.split('.').pop().toLowerCase();\r\n        let viewerUrl = \"\";\r\n        if (['ppt', 'pptx', 'doc', 'docx'].includes(extension)) {\r\n            viewerUrl = `https:\/\/view.officeapps.live.com\/op\/embed.aspx?src=${encodeURIComponent(url)}`;\r\n        } else {\r\n            viewerUrl = `https:\/\/docs.google.com\/gview?url=${encodeURIComponent(url)}&embedded=true`;\r\n        }\r\n        iframe.onload = function() {\r\n            if(loader) loader.style.display = \"none\";\r\n            if(loaderTimeout) clearTimeout(loaderTimeout);\r\n        };\r\n        iframe.src = viewerUrl;\r\n        if(loaderTimeout) clearTimeout(loaderTimeout);\r\n        loaderTimeout = setTimeout(() => {\r\n            if(timeoutMsg) timeoutMsg.style.display = \"block\";\r\n        }, 8000);\r\n    }\r\n};\r\nwindow.closeFileViewer = function() {\r\n    const modal = document.getElementById('knu-viewer-modal');\r\n    const iframe = document.getElementById('knu-viewer-frame');\r\n    if(modal) {\r\n        modal.style.display = \"none\";\r\n        if(iframe) iframe.src = \"\";\r\n        if(loaderTimeout) clearTimeout(loaderTimeout);\r\n    }\r\n};\r\n\/\/ --- DYNAMIC CONTACT PARSING ---\r\nasync function fetchUmanskaContact() {\r\n    const contactContainer = document.getElementById('knu-dynamic-contact');\r\n    try {\r\n        const targetUrl = 'https:\/\/science.knu.ua\/new\/?page_id=4533';\r\n        let htmlText = '';\r\n        const t = new Date().getTime();\r\n        const urlWithTime = `${targetUrl}&_t=${t}`;\r\n        try {\r\n            const controller = new AbortController();\r\n            const id = setTimeout(() => controller.abort(), 3000);\r\n            const response = await fetch(urlWithTime, { headers: { 'Cache-Control': 'no-cache' }, signal: controller.signal });\r\n            clearTimeout(id);\r\n            if (response.ok) htmlText = await response.text();\r\n        } catch(e) {}\r\n        if (!htmlText || htmlText.length < 50) {\r\n            const proxyUrl = `https:\/\/api.allorigins.win\/get?url=${encodeURIComponent(urlWithTime)}`;\r\n            const response = await fetch(proxyUrl, { cache: \"no-store\" });\r\n            const data = await response.json();\r\n            htmlText = data.contents;\r\n        }\r\n        const parser = new DOMParser();\r\n        const doc = parser.parseFromString(htmlText, 'text\/html');\r\n        const dataDiv = doc.getElementById('human-readable-data');\r\n        if(!dataDiv) throw new Error(\"Contacts Data Block not found\");\r\n        const lines = dataDiv.innerText.split('\\n');\r\n        let person = null;\r\n        let currentPerson = {};\r\n        const getVal = (s) => {\r\n            const colIdx = s.indexOf(':');\r\n            return colIdx > -1 ? s.substring(colIdx + 1).trim() : s;\r\n        };\r\n        for (let line of lines) {\r\n            const trimmed = line.trim();\r\n            if(trimmed.startsWith('1a.')) {\r\n                if (currentPerson.name && currentPerson.name.includes('\u0423\u043c\u0430\u043d\u0441\u044c\u043a\u0430 \u041b\u044e\u0434\u043c\u0438\u043b\u0430')) {\r\n                    person = currentPerson;\r\n                    break;\r\n                }\r\n                currentPerson = {};\r\n            }\r\n            else if (trimmed.startsWith('2a.')) currentPerson.role = getVal(trimmed);\r\n            else if (trimmed.startsWith('3a.')) currentPerson.name = getVal(trimmed);\r\n            else if (trimmed.startsWith('4a.')) currentPerson.address = getVal(trimmed);\r\n            else if (trimmed.startsWith('5a.')) currentPerson.phone = getVal(trimmed);\r\n            else if (trimmed.startsWith('6a.')) currentPerson.email = getVal(trimmed);\r\n            else if (trimmed.startsWith('7a.')) currentPerson.photo = getVal(trimmed);\r\n        }\r\n        \/\/ Final check if she was the last item\r\n        if (!person && currentPerson.name && currentPerson.name.includes('\u0423\u043c\u0430\u043d\u0441\u044c\u043a\u0430 \u041b\u044e\u0434\u043c\u0438\u043b\u0430')) {\r\n            person = currentPerson;\r\n        }\r\n        if(person) {\r\n            const photoSrc = (person.photo && person.photo.length > 5 && !person.photo.includes('\u0432\u0456\u0434\u0441\u0443\u0442\u043d\u0454')) \r\n                             ? person.photo \r\n                             : 'https:\/\/science.knu.ua\/upload\/persons\/goch.jpeg'; \/\/ fallback just in case\r\n            const phones = person.phone.split(',').map(p => p.trim());\r\n            let mainPhone = phones.find(p => p.length > 5) || phones[0];\r\n            let extPhone = phones.find(p => p.length <= 5 && p !== mainPhone);\r\n            let phoneDisplay = mainPhone + (extPhone ? ` (\u0432\u043d. ${extPhone})` : '');\r\n            const phoneClean = mainPhone.replace(\/[^\\d+]\/g, '');\r\n            const phoneQrBtn = `<button class=\"bot-qr-btn\" onclick=\"toggleContactQR(this, 'tel:${phoneClean}')\" title=\"QR \u0434\u043b\u044f \u0434\u0437\u0432\u0456\u043d\u043a\u0430\"><i data-lucide=\"qr-code\" size=\"14\"><\/i><\/button>`;\r\n            const emails = person.email.split(',').map(e => e.trim());\r\n            let emailLinks = emails.map(e => {\r\n                const qrBtn = `<button class=\"bot-qr-btn\" onclick=\"toggleContactQR(this, 'mailto:${e}')\" title=\"QR \u0434\u043b\u044f \u043f\u043e\u0448\u0442\u0438\"><i data-lucide=\"qr-code\" size=\"14\"><\/i><\/button>`;\r\n                return `<div style=\"display:flex; align-items:center; width:100%;\"><a href=\"mailto:${e}\">${e}<\/a>${qrBtn}<\/div>`;\r\n            }).join('');\r\n            let addressHtml = '';\r\n            if (person.address && person.address !== '\u0412\u0456\u0434\u0441\u0443\u0442\u043d\u044f' && person.address !== '-') {\r\n                addressHtml = `\r\n                <div class=\"bot-contact-detail\" style=\"margin-top:4px;\">\r\n                    <i data-lucide=\"map-pin\"><\/i>\r\n                    <span>${person.address}<\/span>\r\n                <\/div>`;\r\n            }\r\n            contactContainer.innerHTML = `\r\n                <div class=\"bot-contact-card\">\r\n                    <div class=\"bot-qr-overlay\"><\/div>\r\n                    <div class=\"bot-contact-header\">\r\n                        <img decoding=\"async\" src=\"${photoSrc}\" class=\"bot-contact-photo\" alt=\"${person.name}\">\r\n                        <div class=\"bot-contact-info\">\r\n                            <div class=\"bot-contact-role\">${person.role}<\/div>\r\n                            <div class=\"bot-contact-name\">${person.name}<\/div>\r\n                        <\/div>\r\n                    <\/div>\r\n                    ${addressHtml}\r\n                    <div class=\"bot-contact-detail\">\r\n                        <i data-lucide=\"phone\"><\/i>\r\n                        <div style=\"display:flex; align-items:center;\">\r\n                            <span>${phoneDisplay}<\/span>\r\n                            ${phoneQrBtn}\r\n                        <\/div>\r\n                    <\/div>\r\n                    <div class=\"bot-contact-detail\">\r\n                        <i data-lucide=\"mail\"><\/i>\r\n                        <div class=\"bot-contact-links\">${emailLinks}<\/div>\r\n                    <\/div>\r\n                <\/div>\r\n            `;\r\n            if(window.lucide) lucide.createIcons();\r\n        } else {\r\n            contactContainer.style.display = 'none';\r\n        }\r\n    } catch (e) {\r\n        console.error(\"Failed to load Umanska contact dynamically\", e);\r\n        contactContainer.style.display = 'none';\r\n    }\r\n}\r\n\/\/ --- JS BOT ZIP DOWNLOAD LOGIC (ROBUST FETCH WITH RETRY) ---\r\nasync function fetchBinaryWithRetry(url) {\r\n    const FETCH_TIMEOUT_MS = 5000;\r\n    const fetchWithTimeout = async (u, opts, time) => {\r\n        const controller = new AbortController();\r\n        const id = setTimeout(() => controller.abort(), time);\r\n        try {\r\n            const response = await fetch(u, { ...opts, signal: controller.signal });\r\n            clearTimeout(id);\r\n            return response;\r\n        } catch (error) {\r\n            clearTimeout(id);\r\n            throw error;\r\n        }\r\n    };\r\n    try {\r\n        const res = await fetchWithTimeout(url, {}, 3000);\r\n        if (res.ok) {\r\n            const blob = await res.blob();\r\n            if (blob.size > 500 && !blob.type.includes(\"text\/html\")) return blob;\r\n        }\r\n    } catch (e) { }\r\n    \/\/ Fallback Proxies\r\n    const proxies =[\r\n        `https:\/\/api.allorigins.win\/raw?url=${encodeURIComponent(url)}`,\r\n        `https:\/\/corsproxy.io\/?${encodeURIComponent(url)}`,\r\n        `https:\/\/api.codetabs.com\/v1\/proxy?quest=${encodeURIComponent(url)}`\r\n    ];\r\n    for (const proxyUrl of proxies) {\r\n        try {\r\n            const res = await fetchWithTimeout(proxyUrl, {}, FETCH_TIMEOUT_MS);\r\n            if (!res.ok) throw new Error(`HTTP ${res.status}`);\r\n            const blob = await res.blob();\r\n            if (blob.type.includes(\"text\/html\") || blob.type.includes(\"application\/json\")) {\r\n                throw new Error(\"Proxy returned text\/html instead of binary file\");\r\n            }\r\n            if (blob.size < 500) throw new Error(\"File too small\");\r\n            return blob;\r\n        } catch (e) { }\r\n    }\r\n    return null;\r\n}\r\nwindow.downloadRegistrationZip = async function(btn) {\r\n    const originalContent = btn.innerHTML;\r\n    btn.disabled = true;\r\n    btn.innerHTML = `<i data-lucide=\"loader-2\" class=\"animate-spin\" size=\"16\"><\/i> \u0424\u043e\u0440\u043c\u0443\u0432\u0430\u043d\u043d\u044f...`;\r\n    if(window.lucide) lucide.createIcons();\r\n    try {\r\n        if (!window.JSZip) throw new Error(\"\u0411\u0456\u0431\u043b\u0456\u043e\u0442\u0435\u043a\u0430 JSZip \u043d\u0435 \u0437\u0430\u0432\u0430\u043d\u0442\u0430\u0436\u0435\u043d\u0430.\");\r\n        \/\/ 1. Fetch File DB exactly like bot\r\n        const targetUrl = 'https:\/\/science.knu.ua\/new\/?p=5274';\r\n        let htmlText = '';\r\n        const t = new Date().getTime();\r\n        const urlWithTime = `${targetUrl}&_t=${t}`;\r\n        try {\r\n            const controller = new AbortController();\r\n            const id = setTimeout(() => controller.abort(), 3000);\r\n            const response = await fetch(urlWithTime, { headers: { 'Cache-Control': 'no-cache' }, signal: controller.signal });\r\n            clearTimeout(id);\r\n            if (response.ok) htmlText = await response.text();\r\n        } catch(e) {}\r\n        if (!htmlText || htmlText.length < 50) {\r\n            const proxyUrl = `https:\/\/api.allorigins.win\/get?url=${encodeURIComponent(urlWithTime)}`;\r\n            const response = await fetch(proxyUrl, { cache: \"no-store\" });\r\n            const data = await response.json();\r\n            htmlText = data.contents;\r\n        }\r\n        const parser = new DOMParser();\r\n        const doc = parser.parseFromString(htmlText, 'text\/html');\r\n        const dataDiv = doc.getElementById('human-readable-data');\r\n        const rawText = dataDiv ? dataDiv.textContent : doc.body.textContent;\r\n        \/\/ Exact regex from JS bot\r\n        const regex = \/1a\\.\\s*\u041f\u043e\u0441\u0438\u043b\u0430\u043d\u043d\u044f:\\s*(https?:\\\/\\\/[^\\s]+)[\\s\\S]*?2a\\.\\s*\u041f\u043e\u0432\u043d\u0430 \u043d\u0430\u0437\u0432\u0430:\\s*([\\s\\S]*?)\\s*3a\\.\\s*\u0414\u0430\u0442\u0430 \u0432\u043d\u0435\u0441\u0435\u043d\u043d\u044f:\\s*([\\s\\S]*?)\\s*4a\\.\\s*\u0422\u0438\u043f \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0443:\\s*([\\s\\S]*?)\\s*5a\\.\\s*\u041f\u0430\u043a\u0435\u0442 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0456\u0432:\\s*([\\s\\S]*?)(?=\\s*(?:-\\\/-\\\/-|1a\\.|=\\\/=|$))\/gi;\r\n        let match;\r\n        const filesToZip =[];\r\n        while ((match = regex.exec(rawText)) !== null) {\r\n            const url = match[1].trim();\r\n            const name = match[2].trim();\r\n            const pkg = match[5].trim();\r\n            \/\/ Match target package\r\n            if (pkg.includes(\"\u0410\u0442\u0435\u0441\u0442\u0430\u0446\u0456\u044f\") || pkg.includes(\"\u0420\u0435\u0454\u0441\u0442\u0440\u0430\u0446\u0456\u044f \u041d\u0428\")) {\r\n                filesToZip.push({ url, name });\r\n            }\r\n        }\r\n        if (filesToZip.length === 0) {\r\n            throw new Error(\"\u0414\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0438 \u043f\u0430\u043a\u0435\u0442\u0443 \u043d\u0435 \u0437\u043d\u0430\u0439\u0434\u0435\u043d\u0456 \u0432 \u0431\u0430\u0437\u0456.\");\r\n        }\r\n        const zip = new JSZip();\r\n        let count = 0;\r\n        for (const file of filesToZip) {\r\n            const blob = await fetchBinaryWithRetry(file.url);\r\n            if (blob) {\r\n                let filename = file.name.replace(\/[\/\\\\?%*:|\"<>]\/g, '-');\r\n                if (!filename.includes('.')) {\r\n                     const mime = blob.type;\r\n                     if(mime.includes('pdf')) filename += '.pdf';\r\n                     else if(mime.includes('word')) filename += '.doc';\r\n                     else if(mime.includes('sheet')) filename += '.xls';\r\n                }\r\n                zip.file(filename, blob);\r\n                count++;\r\n            }\r\n        }\r\n        if (count > 0) {\r\n            const zipContent = await zip.generateAsync({type:\"blob\"});\r\n            saveAs(zipContent, \"Registration_Science_Schools_KNU.zip\");\r\n            btn.innerHTML = `<i data-lucide=\"check\" size=\"16\"><\/i> \u0413\u043e\u0442\u043e\u0432\u043e`;\r\n        } else {\r\n            throw new Error(\"\u041d\u0435 \u0432\u0434\u0430\u043b\u043e\u0441\u044f \u0437\u0430\u0432\u0430\u043d\u0442\u0430\u0436\u0438\u0442\u0438 \u0444\u0430\u0439\u043b\u0438 (\u043c\u043e\u0436\u043b\u0438\u0432\u043e \u0431\u043b\u043e\u043a\u0443\u0454 CORS).\");\r\n        }\r\n    } catch (e) {\r\n        console.error(e);\r\n        btn.innerHTML = `<i data-lucide=\"alert-triangle\" size=\"16\"><\/i> \u041f\u043e\u043c\u0438\u043b\u043a\u0430`;\r\n        alert(\"\u041f\u043e\u043c\u0438\u043b\u043a\u0430 \u0437\u0430\u0432\u0430\u043d\u0442\u0430\u0436\u0435\u043d\u043d\u044f ZIP: \" + e.message + \"\\n\\n\u0411\u0443\u0434\u044c \u043b\u0430\u0441\u043a\u0430, \u043d\u0430\u0442\u0438\u0441\u043d\u0456\u0442\u044c '\u0414\u0435\u0442\u0430\u043b\u0456 \u0442\u0430 \u0414\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0438' \u0434\u043b\u044f \u043f\u0435\u0440\u0435\u0433\u043b\u044f\u0434\u0443 \u043d\u0430 \u0441\u0442\u043e\u0440\u0456\u043d\u0446\u0456.\");\r\n    } finally {\r\n        if(window.lucide) lucide.createIcons();\r\n        setTimeout(() => {\r\n            btn.innerHTML = originalContent;\r\n            btn.disabled = false;\r\n            if(window.lucide) lucide.createIcons();\r\n        }, 4000);\r\n    }\r\n};\r\n<\/script>\n","protected":false},"excerpt":{"rendered":"","protected":false},"author":4,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-4461","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/science.knu.ua\/new\/index.php?rest_route=\/wp\/v2\/pages\/4461","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/science.knu.ua\/new\/index.php?rest_route=\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/science.knu.ua\/new\/index.php?rest_route=\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/science.knu.ua\/new\/index.php?rest_route=\/wp\/v2\/users\/4"}],"replies":[{"embeddable":true,"href":"https:\/\/science.knu.ua\/new\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=4461"}],"version-history":[{"count":2,"href":"https:\/\/science.knu.ua\/new\/index.php?rest_route=\/wp\/v2\/pages\/4461\/revisions"}],"predecessor-version":[{"id":5054,"href":"https:\/\/science.knu.ua\/new\/index.php?rest_route=\/wp\/v2\/pages\/4461\/revisions\/5054"}],"wp:attachment":[{"href":"https:\/\/science.knu.ua\/new\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=4461"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}