{"id":4491,"date":"2025-12-25T18:52:28","date_gmt":"2025-12-25T16:52:28","guid":{"rendered":"https:\/\/science.knu.ua\/new\/?page_id=4491"},"modified":"2026-01-09T14:23:16","modified_gmt":"2026-01-09T12:23:16","slug":"konkurs-proyektiv","status":"publish","type":"page","link":"https:\/\/science.knu.ua\/new\/?page_id=4491","title":{"rendered":"\u041a\u043e\u043d\u043a\u0443\u0440\u0441 \u043f\u0440\u043e\u0454\u043a\u0442\u0456\u0432"},"content":{"rendered":"<!-- START OF FILE Paste June 23, 2026 - 10:51AM -->\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<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  }\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  \/* 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: 10px;\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  \/* FILTERS HINT TEXT *\/\r\n  .knu-filters-hint {\r\n      text-align: right;\r\n      font-size: 0.8em;\r\n      color: #777;\r\n      font-style: italic;\r\n      margin-bottom: 15px;\r\n      padding-right: 5px;\r\n      line-height: 1.4;\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: 800px;\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-org {\r\n    font-size: 0.8em;\r\n    color: #555;\r\n    display: block;\r\n    font-style: italic;\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  }\r\n  .knu-type-box.domestic { background-color: #e3f2fd; color: var(--knu-blue); }\r\n  .knu-type-box.foreign { background-color: #ffebee; color: var(--knu-red); }\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 IMAGE --- *\/\r\n  .knu-card-image-wrapper {\r\n    width: 100%;\r\n    height: 190px; \/* Increased from 160px to display larger images *\/\r\n    background: #ffffff; \r\n    display: flex;\r\n    align-items: center;\r\n    justify-content: center;\r\n    overflow: hidden;\r\n    border-bottom: 1px solid #f0f0f0;\r\n    padding: 6px; \/* Reduced from 10px to let image content occupy maximum size *\/\r\n  }\r\n  .knu-card-img {\r\n    width: 100%;\r\n    height: 100%;\r\n    object-fit: contain !important; \/* Fits without cropping *\/\r\n    object-position: center !important;\r\n    transition: transform 0.3s ease;\r\n  }\r\n  .knu-card:hover .knu-card-img {\r\n    transform: scale(1.02); \/* Minimal hover effect *\/\r\n  }\r\n  .knu-card-placeholder {\r\n    color: #ccc;\r\n    display: flex;\r\n    flex-direction: column;\r\n    align-items: center;\r\n    justify-content: center;\r\n    gap: 5px;\r\n    width: 100%;\r\n    height: 100%;\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  \/* UNIFIED META ROW STYLES *\/\r\n  .knu-meta-row {\r\n    display: flex;\r\n    justify-content: space-between;\r\n    align-items: center;\r\n    width: 100%;\r\n    margin-bottom: 2px;\r\n    font-size: 0.85em;\r\n    color: #555;\r\n  }\r\n  .knu-meta-item {\r\n      display: flex;\r\n      align-items: center;\r\n      gap: 5px;\r\n  }\r\n  .knu-meta-item.left {\r\n      justify-content: flex-start;\r\n      text-align: left;\r\n  }\r\n  .knu-meta-item.right {\r\n      justify-content: flex-end;\r\n      text-align: right;\r\n  }\r\n  .knu-organizer {\r\n    font-size: 0.85em;\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    margin-top: 5px;\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  \/* --- 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: 8px;\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 10px;\r\n    border-radius: 4px;\r\n    font-size: 0.85em;\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-news {\r\n    background-color: var(--knu-blue);\r\n    color: #fff !important;\r\n  }\r\n  .knu-btn-news: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 Button (QR, Eye) *\/\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  \/* QR OVERLAY *\/\r\n  .knu-qr-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  }\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-qr-title {\r\n    font-size: 0.9rem;\r\n    font-weight: 700;\r\n    color: var(--knu-blue);\r\n    margin-bottom: 5px;\r\n    line-height: 1.3;\r\n    max-height: 60px;\r\n    overflow: hidden;\r\n  }\r\n  .knu-qr-hint { \r\n    font-size: 0.8em; \r\n    color: #666; \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  \/* 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: 768px) {\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    .knu-btn-row {\r\n      display: flex;\r\n      width: 100%;\r\n      justify-content: flex-end;\r\n      gap: 10px;\r\n    }\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<\/style>\r\n<div id=\"knu-projects-app\">\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, \u041e\u0440\u0433\u0430\u043d\u0456\u0437\u0430\u0442\u043e\u0440...\">\r\n      <\/div>\r\n    <\/div>\r\n    <div class=\"knu-input-group\">\r\n      <label for=\"knu-organizer-filter\">\r\n        <i data-lucide=\"building\" size=\"14\"><\/i> \u041e\u0440\u0433\u0430\u043d\u0456\u0437\u0430\u0442\u043e\u0440\r\n      <\/label>\r\n      <select id=\"knu-organizer-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-year-filter\">\r\n        <i data-lucide=\"calendar\" size=\"14\"><\/i> \u041f\u0435\u0440\u0456\u043e\u0434 \/ \u0420\u0456\u043a\r\n      <\/label>\r\n      <select id=\"knu-year-filter\" class=\"knu-select\">\r\n\t\t<option value=\"active\">\u0410\u043a\u0442\u0438\u0432\u043d\u0456<\/option>\r\n        <option value=\"done\">\u0417\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0456<\/option>\r\n        <option value=\"\">\u0412\u0441\u0456 \u043a\u043e\u043d\u043a\u0443\u0440\u0441\u0438<\/option>\r\n        <option disabled>\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500<\/option>\r\n      <\/select>\r\n    <\/div>\r\n    <div class=\"knu-input-group\">\r\n      <label for=\"knu-type-filter\">\r\n        <i data-lucide=\"filter\" size=\"14\"><\/i> \u0422\u0438\u043f\r\n      <\/label>\r\n      <select id=\"knu-type-filter\" class=\"knu-select\">\r\n        <option value=\"\">\u0412\u0441\u0456<\/option>\r\n        <option value=\"\u0432\u0456\u0442\u0447\u0438\u0437\u043d\u044f\u043d\u0438\u0439\">\u0412\u0456\u0442\u0447\u0438\u0437\u043d\u044f\u043d\u0456<\/option>\r\n        <option value=\"\u0437\u0430\u043a\u043e\u0440\u0434\u043e\u043d\u043d\u0438\u0439\">\u0417\u0430\u043a\u043e\u0440\u0434\u043e\u043d\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=\"organizer_asc\">\u041e\u0440\u0433\u0430\u043d\u0456\u0437\u0430\u0442\u043e\u0440 &#9650;<\/option>\r\n        <option value=\"organizer_desc\">\u041e\u0440\u0433\u0430\u043d\u0456\u0437\u0430\u0442\u043e\u0440 &#9660;<\/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  <!-- Hint Text -->\r\n  <div class=\"knu-filters-hint\">\r\n     * \u043d\u0430 \u043a\u0430\u0440\u0442\u043a\u0430\u0445 \u043f\u0440\u043e\u0454\u043a\u0442\u0456\u0432 \u0432\u043a\u0430\u0437\u0430\u043d\u043e \u043f\u0435\u0440\u0448\u0438\u0439 \u0442\u0430 \u043e\u0441\u0442\u0430\u043d\u043d\u0456\u0439 \u0434\u0435\u0434\u043b\u0430\u0439\u043d\u0438 \u043f\u043e\u0434\u0430\u0447\u0456,<br>\r\n\t  \u0434\u0456\u0437\u043d\u0430\u0442\u0438\u0441\u044f \u0431\u0456\u043b\u044c\u0448\u0435 \u043f\u0440\u043e \u0434\u0430\u0442\u0438 \u043c\u043e\u0436\u043d\u0430 \u0432 \u0443\u043c\u043e\u0432\u0430\u0445 \u043a\u043e\u0436\u043d\u043e\u0433\u043e \u043a\u043e\u043d\u043a\u0443\u0440\u0441\u0443 \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> \u043a\u043e\u043d\u043a\u0443\u0440\u0441\u0456\u0432<\/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      <a href=\"https:\/\/science.knu.ua\/new\/?p=4899\" 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\u0454 \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 projectsData = [];\r\nlet currentFilteredData = [];\r\nlet currentViewMode = 'grid'; \/\/ 'grid' or 'table'\r\n\/\/ CONSTANTS\r\nconst SOURCE_URL = \"https:\/\/science.knu.ua\/new\/?p=4899\";\r\nconst CACHE_KEY = \"knu_projects_data_v6\"; \/\/ \u041e\u041d\u041e\u0412\u041b\u0415\u041d\u041e \u0414\u041b\u042f \u0410\u0412\u0422\u041e\u041c\u0410\u0422\u0418\u0427\u041d\u041e\u0413\u041e \u0421\u041a\u0418\u0414\u0410\u041d\u041d\u042f \u0421\u0422\u0410\u0420\u041e\u0413\u041e \u041a\u0415\u0428\u0423\r\ndocument.addEventListener('DOMContentLoaded', () => {\r\n  const container = document.getElementById('knu-content-area');\r\n  const searchInput = document.getElementById('knu-search');\r\n  const organizerFilter = document.getElementById('knu-organizer-filter');\r\n  const typeFilter = document.getElementById('knu-type-filter');\r\n  const yearFilter = document.getElementById('knu-year-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  const currentYear = new Date().getFullYear(); \r\n  if (window.lucide) { lucide.createIcons(); }\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('org')) organizerFilter.value = params.get('org');\r\n     if (params.has('type')) typeFilter.value = params.get('type');\r\n     if (params.has('year')) yearFilter.value = params.get('year');\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('org', organizerFilter.value);\r\n     setOrDelete('type', typeFilter.value);\r\n     if(yearFilter.value) params.set('year', yearFilter.value); else params.delete('year');\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 ---\r\n  \/\/ Simple timeout wrapper\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  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        \/\/ We give it 3 seconds to connect directly\r\n        const response = await fetchWithTimeout(urlWithTime, { \r\n            headers: { 'Cache-Control': 'no-cache' } \r\n        }, 3000);\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        \/\/ Direct fetch failed (likely CORS if running locally, or network issue)\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 parseHtmlToProjects(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      \/\/ Split blocks by separator\r\n      const rawBlocks = targetText.split(\/(?:-\\\/){5,}\/);\r\n      return rawBlocks.map(block => {\r\n        if (!block.trim()) return null;\r\n        const proj = {\r\n            type: \"\",\r\n            startYear: 0,\r\n            endYear: 0,\r\n            years: \"\",\r\n            organizer: \"\",\r\n            title: \"\",\r\n            newsUrl: null,\r\n            webUrl: null,\r\n            imageUrl: null,\r\n            firstDeadline: null,\r\n            lastDeadline: null\r\n        };\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 rawKey = line.substring(0, separatorIndex).trim().toLowerCase();\r\n            let value = line.substring(separatorIndex + 1).trim();\r\n            if (value === '(URL)' || value.toLowerCase().includes('\u0432\u0456\u0434\u0441\u0443\u0442\u043d') || value.toLowerCase().includes('\u043d\u0435\u043c\u0430\u0454') || value === \"\") {\r\n                value = null;\r\n            }\r\n            \/\/ CLEAN LINKS FROM INVISIBLE CARACTERS \/ WRAPPING TAGS\r\n            let extractedUrl = null;\r\n            if (value) {\r\n                let cleanVal = value.replace(\/<\\\/?[^>]+(>|$)\/g, \"\").trim();\r\n                const urlMatch = cleanVal.match(\/(https?:\\\/\\\/[^\\s\"'<>)]+)\/i);\r\n                if (urlMatch) {\r\n                    extractedUrl = urlMatch[1].trim();\r\n                    extractedUrl = extractedUrl.replace(\/[.,;:]+$\/, ''); \/\/ Strip trailing punctuation\r\n                }\r\n            }\r\n            if (rawKey.includes('\u0442\u0438\u043f')) proj.type = value || \"\";\r\n            else if (rawKey.includes('\u043f\u043e\u0447\u0430\u0442\u043e\u043a')) proj.startYear = parseInt(value, 10);\r\n            else if (rawKey.includes('\u043a\u0456\u043d\u0435\u0446\u044c')) proj.endYear = parseInt(value, 10);\r\n            else if (rawKey.includes('\u043e\u0440\u0433\u0430\u043d\u0456\u0437\u0430\u0442\u043e\u0440')) proj.organizer = value || \"\";\r\n            else if (rawKey.includes('\u043d\u0430\u0437\u0432\u0430')) proj.title = value || \"\";\r\n            else if (rawKey.includes('\u043f\u0435\u0440\u0448\u0438\u0439') || rawKey.includes('first deadline')) proj.firstDeadline = value;\r\n            else if (rawKey.includes('\u043a\u0440\u0430\u0439\u043d\u0456\u0439') || rawKey.includes('last deadline')) proj.lastDeadline = value;\r\n            else if (rawKey.includes('\u043d\u043e\u0432\u0438\u043d\u0430') && extractedUrl) proj.newsUrl = extractedUrl;\r\n            else if ((rawKey.includes('\u0441\u0430\u0439\u0442') || rawKey.includes('web')) && extractedUrl) proj.webUrl = extractedUrl;\r\n            else if ((rawKey.includes('\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u043d\u044f') || rawKey.includes('img')) && extractedUrl) proj.imageUrl = extractedUrl;\r\n        });\r\n        if (!proj.title) return null;\r\n        if (proj.startYear && proj.endYear) {\r\n            proj.years = `${proj.startYear}-${proj.endYear}`;\r\n        } else {\r\n            proj.years = proj.startYear ? `${proj.startYear}` : \"\";\r\n        }\r\n        return proj;\r\n      }).filter(item => item !== null);\r\n  }\r\n  \/\/ --- MAIN ORCHESTRATOR ---\r\n  async function fetchProjects(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            projectsData = cached.data;\r\n            populateFilters();\r\n            applyUrlState();\r\n            filterData();\r\n            isDisplayedFromCache = true;\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            let freshData = parseHtmlToProjects(htmlContent);\r\n            if (freshData.length > 0) {\r\n                const jsonFresh = JSON.stringify(freshData);\r\n                const jsonOld = isDisplayedFromCache ? JSON.stringify(projectsData) : \"\";\r\n                if (jsonFresh !== jsonOld) {\r\n                    projectsData = freshData;\r\n                    saveToCache(projectsData);\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                    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            }\r\n        } catch (error) {\r\n            console.error(\"Critical 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;\">\u041f\u0435\u0440\u0435\u0432\u0456\u0440\u0442\u0435 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0434\u043e \u0406\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u0443<\/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=4899\" 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 (organizerFilter.options.length > 1) { organizerFilter.remove(1); }\r\n    while (yearFilter.options.length > 4) { yearFilter.remove(4); } \r\n    const uniqueOrg = [...new Set(projectsData.map(p => p.organizer))].filter(f => f).sort();\r\n    uniqueOrg.forEach(org => {\r\n      const option = document.createElement('option');\r\n      option.value = org;\r\n      option.textContent = org;\r\n      organizerFilter.appendChild(option);\r\n    });\r\n    const uniqueYears = [...new Set(projectsData.map(p => p.startYear))].filter(y => y).sort((a,b) => b-a);\r\n    uniqueYears.forEach(year => {\r\n      const option = document.createElement('option');\r\n      option.value = year;\r\n      option.textContent = year;\r\n      yearFilter.appendChild(option);\r\n    });\r\n  }\r\n  function renderProjects(projects) {\r\n    shownCountEl.textContent = projects.length;\r\n    totalCountEl.textContent = projectsData.length;\r\n    container.innerHTML = '';\r\n    if (projects.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(projects);\r\n    } else {\r\n      renderGrid(projects);\r\n    }\r\n    if (window.lucide) { lucide.createIcons(); }\r\n  }\r\n  function isFileUrl(url) {\r\n      if (!url) return false;\r\n      const lower = url.toLowerCase();\r\n      return lower.endsWith('.pdf') || lower.endsWith('.doc') || lower.endsWith('.docx') || lower.endsWith('.ppt') || lower.endsWith('.pptx');\r\n  }\r\n  function renderGrid(projects) {\r\n    container.className = 'knu-grid-view';\r\n    projects.forEach((proj, index) => {\r\n      const isDomestic = proj.type.toLowerCase().includes('\u0432\u0456\u0442\u0447\u0438\u0437\u043d\u044f\u043d\u0438\u0439');\r\n      const typeClass = isDomestic ? 'domestic' : 'foreign';\r\n      const typeIconName = isDomestic ? 'map-pin' : 'globe';\r\n      const uniqueQrId = `qr-${index}`;\r\n      const isActive = currentYear <= proj.endYear;\r\n      const statusIconHtml = isActive \r\n        ? `<div title=\"\u041a\u043e\u043d\u043a\u0443\u0440\u0441 \u0442\u0440\u0438\u0432\u0430\u0454\"><i data-lucide=\"circle-check\" color=\"var(--knu-green)\" fill=\"#e8f5e9\" size=\"22\"><\/i><\/div>`\r\n        : `<div title=\"\u041a\u043e\u043d\u043a\u0443\u0440\u0441 \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      const isNewsValid = proj.newsUrl && !proj.newsUrl.includes(\"google.com\");\r\n      const isWebValid = proj.webUrl && !proj.webUrl.includes(\"google.com\");\r\n      const qrTargetUrl = isNewsValid ? proj.newsUrl : (isWebValid ? proj.webUrl : null);\r\n      let metaHtml = '';\r\n      metaHtml += `\r\n      <div class=\"knu-meta-row\">\r\n         <div class=\"knu-meta-item left\" title=\"\u0421\u0442\u0430\u0440\u0442\">\r\n            <i data-lucide=\"calendar\" size=\"14\"><\/i> <span>${proj.startYear}<\/span>\r\n         <\/div>\r\n         <div class=\"knu-meta-item right\" title=\"\u041f\u0435\u0440\u0456\u043e\u0434 \u0440\u0435\u0430\u043b\u0456\u0437\u0430\u0446\u0456\u0457\">\r\n            <span>${proj.years}<\/span> <i data-lucide=\"hourglass\" size=\"14\"><\/i>\r\n         <\/div>\r\n      <\/div>`;\r\n      if (proj.firstDeadline || proj.lastDeadline) {\r\n          metaHtml += '<div class=\"knu-meta-row\">';\r\n          if (proj.firstDeadline) {\r\n             metaHtml += `<div class=\"knu-meta-item left\" title=\"\u041f\u0435\u0440\u0448\u0438\u0439 \u0434\u0435\u0434\u043b\u0430\u0439\u043d\"><i data-lucide=\"bell\" size=\"14\"><\/i> <span>${proj.firstDeadline}<\/span><\/div>`;\r\n          } else {\r\n             metaHtml += `<div><\/div>`;\r\n          }\r\n          if (proj.lastDeadline) {\r\n             metaHtml += `<div class=\"knu-meta-item right\" title=\"\u041a\u0440\u0430\u0439\u043d\u0456\u0439 \u0434\u0435\u0434\u043b\u0430\u0439\u043d\"><span>${proj.lastDeadline}<\/span> <i data-lucide=\"flag\" size=\"14\"><\/i><\/div>`;\r\n          }\r\n          metaHtml += '<\/div>';\r\n      }\r\n      let buttonsHtml = '';\r\n      if (isNewsValid) {\r\n        buttonsHtml += `\r\n          <a href=\"${proj.newsUrl}\" target=\"_blank\" class=\"knu-btn knu-btn-news\">\r\n            <i data-lucide=\"file-text\" size=\"16\"><\/i> \u041d\u043e\u0432\u0438\u043d\u0430\r\n          <\/a>`;\r\n      }\r\n      if (isWebValid) {\r\n        buttonsHtml += `\r\n          <a href=\"${proj.webUrl}\" target=\"_blank\" class=\"knu-btn knu-btn-web\">\r\n            <i data-lucide=\"globe\" size=\"16\"><\/i> \u0423\u043c\u043e\u0432\u0438\r\n          <\/a>`;\r\n      }\r\n      if (isWebValid && isFileUrl(proj.webUrl)) {\r\n          const safeTitle = proj.title.replace(\/'\/g, \"\\\\'\");\r\n          buttonsHtml += `\r\n          <button class=\"knu-btn knu-btn-util\" onclick=\"openFileViewer('${proj.webUrl}', '${safeTitle}')\" title=\"\u041f\u0435\u0440\u0435\u0433\u043b\u044f\u043d\u0443\u0442\u0438 \u0443\u043c\u043e\u0432\u0438 (\u0444\u0430\u0439\u043b)\">\r\n            <i data-lucide=\"eye\" size=\"18\"><\/i>\r\n          <\/button>`;\r\n      }\r\n      if (qrTargetUrl) {\r\n        buttonsHtml += `\r\n          <button class=\"knu-btn knu-btn-util\" onclick=\"toggleQR('${uniqueQrId}', '${qrTargetUrl}')\" title=\"\u041f\u043e\u043a\u0430\u0437\u0430\u0442\u0438\/\u041f\u0440\u0438\u0445\u043e\u0432\u0430\u0442\u0438 QR\">\r\n            <i data-lucide=\"qr-code\" size=\"18\"><\/i>\r\n          <\/button>`;\r\n      }\r\n      let imageHtml = '';\r\n      if(proj.imageUrl) {\r\n        const safeImageUrl = proj.imageUrl.replace(\/'\/g, \"\\\\'\");\r\n        \/\/ Implemented bypass for hotlinking and security referrer policies, plus a proxy fallback\r\n        imageHtml = `<img decoding=\"async\" src=\"${proj.imageUrl}\" referrerpolicy=\"no-referrer\" class=\"knu-card-img\" alt=\"Logo\" onerror=\"handleImageError(this, '${safeImageUrl}')\">`;\r\n      } else {\r\n        imageHtml = `<div class=\"knu-card-placeholder\"><i data-lucide=\"trophy\" size=\"48\"><\/i><\/div>`;\r\n      }\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 ${typeClass}\" title=\"${proj.type}\">\r\n            <i data-lucide=\"${typeIconName}\" size=\"18\"><\/i>\r\n          <\/div>\r\n          <div class=\"knu-header-year\" title=\"\u0420\u0456\u043a \u043f\u043e\u0447\u0430\u0442\u043a\u0443\">\r\n            <i data-lucide=\"calendar\" size=\"18\"><\/i> ${proj.startYear}\r\n          <\/div>\r\n          <div class=\"knu-status-icon\">${statusIconHtml}<\/div>\r\n        <\/div>\r\n        <div class=\"knu-card-image-wrapper\">\r\n            ${imageHtml}\r\n        <\/div>\r\n        <div class=\"knu-card-body\">\r\n          ${metaHtml}\r\n          <div class=\"knu-organizer\">\r\n            <i data-lucide=\"building\" size=\"16\"><\/i>${proj.organizer}\r\n          <\/div>\r\n          <h3 class=\"knu-title\">${proj.title}<\/h3>\r\n          <div class=\"knu-qr-container\" id=\"${uniqueQrId}\">\r\n             <div class=\"knu-close-overlay\" onclick=\"toggleQR('${uniqueQrId}')\"><i data-lucide=\"x\" size=\"24\"><\/i><\/div>\r\n             <div class=\"knu-qr-canvas\"><\/div>\r\n             <div class=\"knu-qr-title\">${proj.title}<\/div>\r\n             <div class=\"knu-qr-hint\">\u041d\u0430\u0442\u0438\u0441\u043d\u0456\u0442\u044c \u043d\u0430 QR \u043a\u043e\u0434 \u0434\u043b\u044f \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u0443<\/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(projects) {\r\n    container.className = ''; \r\n    let rowsHtml = '';\r\n    projects.forEach((proj) => {\r\n      const isDomestic = proj.type.toLowerCase().includes('\u0432\u0456\u0442\u0447\u0438\u0437\u043d\u044f\u043d\u0438\u0439');\r\n      const isActive = currentYear <= proj.endYear;\r\n      const statusIcon = 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 typeIcon = isDomestic \r\n        ? `<i data-lucide=\"map-pin\" color=\"var(--knu-blue)\" size=\"20\" title=\"${proj.type}\"><\/i>` \r\n        : `<i data-lucide=\"globe\" color=\"var(--knu-red)\" size=\"20\" title=\"${proj.type}\"><\/i>`;\r\n      const isNewsValid = proj.newsUrl && !proj.newsUrl.includes(\"google.com\");\r\n      const isWebValid = proj.webUrl && !proj.webUrl.includes(\"google.com\");\r\n      let linksHtml = '';\r\n      if (isNewsValid) {\r\n        linksHtml += `<a href=\"${proj.newsUrl}\" target=\"_blank\" class=\"knu-link-sm\" title=\"\u041d\u043e\u0432\u0438\u043d\u0430\"><i data-lucide=\"file-text\" size=\"14\"><\/i><\/a>`;\r\n      }\r\n      if (isWebValid) {\r\n        linksHtml += `<a href=\"${proj.webUrl}\" target=\"_blank\" class=\"knu-link-sm\" title=\"\u0421\u0430\u0439\u0442\"><i data-lucide=\"globe\" size=\"14\"><\/i><\/a>`;\r\n        if (isFileUrl(proj.webUrl)) {\r\n             const safeTitle = proj.title.replace(\/'\/g, \"\\\\'\");\r\n             linksHtml += `<button onclick=\"openFileViewer('${proj.webUrl}', '${safeTitle}')\" class=\"knu-link-sm\" title=\"\u041f\u0435\u0440\u0435\u0433\u043b\u044f\u043d\u0443\u0442\u0438 \u0443\u043c\u043e\u0432\u0438\"><i data-lucide=\"eye\" size=\"14\"><\/i><\/button>`;\r\n        }\r\n      }\r\n      rowsHtml += `\r\n        <tr>\r\n          <td style=\"text-align:center;\">${statusIcon}<\/td>\r\n          <td style=\"text-align:center;\">${typeIcon}<\/td>\r\n          <td>\r\n            <span class=\"knu-table-title\">${proj.title}<\/span>\r\n            <span class=\"knu-table-org\">${proj.organizer}<\/span>\r\n          <\/td>\r\n          <td style=\"white-space:nowrap; font-weight:600;\">${proj.years}<\/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=\"50\" style=\"text-align:center;\">\u0421\u0442.<\/th>\r\n              <th width=\"50\" style=\"text-align:center;\">\u0422\u0438\u043f<\/th>\r\n              <th>\u041d\u0430\u0437\u0432\u0430 \/ \u041e\u0440\u0433\u0430\u043d\u0456\u0437\u0430\u0442\u043e\u0440<\/th>\r\n              <th>\u0420\u043e\u043a\u0438<\/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 selectedOrg = organizerFilter.value;\r\n    const selectedType = typeFilter.value;\r\n    const selectedYear = yearFilter.value;\r\n    const sortValue = sortSelect.value;\r\n    updateUrlState();\r\n    let filtered = projectsData.filter(item => {\r\n      const matchSearch = item.title.toLowerCase().includes(query) || \r\n                          item.organizer.toLowerCase().includes(query);\r\n      const matchOrg = selectedOrg ? item.organizer === selectedOrg : true;\r\n      const matchType = selectedType ? item.type.toLowerCase().includes(selectedType.toLowerCase()) : true;\r\n      let matchYear = true;\r\n      if (selectedYear === 'active') {\r\n        matchYear = currentYear <= item.endYear;\r\n      } else if (selectedYear === 'done') {\r\n        matchYear = currentYear > item.endYear;\r\n      } else if (selectedYear) {\r\n        matchYear = item.startYear.toString() === selectedYear;\r\n      }\r\n      return matchSearch && matchOrg && matchType && matchYear;\r\n    });\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 === 'organizer_asc') {\r\n      filtered.sort((a, b) => a.organizer.localeCompare(b.organizer, 'uk'));\r\n    } else if (sortValue === 'organizer_desc') {\r\n      filtered.sort((a, b) => b.organizer.localeCompare(a.organizer, 'uk'));\r\n    }\r\n    currentFilteredData = filtered;\r\n    renderProjects(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, \u0441\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0447\u0435\u0440\u0435\u0437 \u043a\u0456\u043b\u044c\u043a\u0430 \u0441\u0435\u043a\u0443\u043d\u0434.\");\r\n      return;\r\n    }\r\n    const { Document, Packer, Paragraph, TextRun, HeadingLevel, AlignmentType } = window.docx;\r\n    const children = [];\r\n    children.push(new Paragraph({\r\n        text: \"\u041f\u0435\u0440\u0435\u043b\u0456\u043a \u043a\u043e\u043d\u043a\u0443\u0440\u0441\u0456\u0432 \u043f\u0440\u043e\u0454\u043a\u0442\u0456\u0432\",\r\n        heading: HeadingLevel.HEADING_1,\r\n        alignment: AlignmentType.CENTER,\r\n        spacing: { after: 200 }\r\n    }));\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    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(\"\u0422\u0438\u043f\", p.type);\r\n        addField(\"\u041f\u0435\u0440\u0448\u0438\u0439 \u0434\u0435\u0434\u043b\u0430\u0439\u043d\", p.firstDeadline);\r\n        addField(\"\u041a\u0440\u0430\u0439\u043d\u0456\u0439 \u0434\u0435\u0434\u043b\u0430\u0439\u043d\", p.lastDeadline);\r\n        addField(\"\u041f\u043e\u0447\u0430\u0442\u043e\u043a\", p.startYear.toString());\r\n        addField(\"\u041a\u0456\u043d\u0435\u0446\u044c\", p.endYear.toString());\r\n        addField(\"\u041e\u0440\u0433\u0430\u043d\u0456\u0437\u0430\u0442\u043e\u0440\", p.organizer);\r\n        addField(\"\u041d\u0430\u0437\u0432\u0430\", p.title);\r\n        addField(\"\u041d\u043e\u0432\u0438\u043d\u0430 \u041d\u0414\u0427\", p.newsUrl);\r\n        addField(\"\u0412\u0435\u0431-\u0441\u0430\u0439\u0442\", p.webUrl);\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({ sections: [{ children: children }] });\r\n    Packer.toBlob(doc).then(blob => {\r\n        saveAs(blob, \"contests_export.docx\");\r\n    });\r\n  });\r\n  searchInput.addEventListener('input', filterData);\r\n  organizerFilter.addEventListener('change', filterData);\r\n  typeFilter.addEventListener('change', filterData);\r\n  yearFilter.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=4899\" 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    projectsData = [];\r\n    currentFilteredData = [];\r\n    localStorage.removeItem(CACHE_KEY); \r\n    const params = new URLSearchParams(window.location.search);\r\n    ['q', 'org', 'type', 'year', '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    fetchProjects(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\r\n  fetchProjects();\r\n});\r\nwindow.toggleQR = function(elementId, url) {\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-qr-container').forEach(el => el.style.display = 'none');\r\n  if (isHidden) {\r\n    container.style.display = 'flex';\r\n    const canvasDiv = container.querySelector('.knu-qr-canvas');\r\n    if (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) {\r\n        console.error(\"QR Error\", e);\r\n      }\r\n    }\r\n  } else {\r\n    container.style.display = 'none';\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 lowerUrl = url.toLowerCase();\r\n        let viewerUrl = \"\";\r\n        if (lowerUrl.endsWith('.pdf')) {\r\n             viewerUrl = `https:\/\/docs.google.com\/gview?url=${encodeURIComponent(url)}&embedded=true`;\r\n        } \r\n        else if (lowerUrl.endsWith('.ppt') || lowerUrl.endsWith('.pptx') || lowerUrl.endsWith('.doc') || lowerUrl.endsWith('.docx')) {\r\n             viewerUrl = `https:\/\/view.officeapps.live.com\/op\/embed.aspx?src=${encodeURIComponent(url)}`;\r\n        } \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) {\r\n                timeoutMsg.style.display = \"block\";\r\n            }\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\/\/ --- IMAGE ERROR RESOLVER & PROXY FALLBACK ---\r\nwindow.handleImageError = function(img, originalUrl) {\r\n  if (!img) return;\r\n  if (!img.dataset.proxied) {\r\n    \/\/ If the image fails directly, attempt loading it via an SSL image proxy (weserv)\r\n    \/\/ This successfully bypasses referrer validation and protocol mismatch checks.\r\n    img.dataset.proxied = \"true\";\r\n    img.src = `https:\/\/images.weserv.nl\/?url=${encodeURIComponent(originalUrl)}`;\r\n  } else {\r\n    \/\/ If the proxy fails as well, replace with the standard placeholder icon safely.\r\n    img.onerror = null;\r\n    const parent = img.parentNode;\r\n    if (parent) {\r\n      parent.innerHTML = `<div class=\"knu-card-placeholder\"><i data-lucide=\"image-off\" size=\"48\" color=\"#ccc\"><\/i><\/div>`;\r\n      if (window.lucide) {\r\n        lucide.createIcons();\r\n      }\r\n    }\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-4491","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/science.knu.ua\/new\/index.php?rest_route=\/wp\/v2\/pages\/4491","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=4491"}],"version-history":[{"count":2,"href":"https:\/\/science.knu.ua\/new\/index.php?rest_route=\/wp\/v2\/pages\/4491\/revisions"}],"predecessor-version":[{"id":4902,"href":"https:\/\/science.knu.ua\/new\/index.php?rest_route=\/wp\/v2\/pages\/4491\/revisions\/4902"}],"wp:attachment":[{"href":"https:\/\/science.knu.ua\/new\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=4491"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}