{"id":49229,"date":"2026-02-04T13:49:16","date_gmt":"2026-02-04T12:49:16","guid":{"rendered":"https:\/\/cmm.imgw.pl\/?page_id=49229"},"modified":"2026-05-14T10:33:42","modified_gmt":"2026-05-14T08:33:42","slug":"nowcasting-imgw","status":"publish","type":"page","link":"https:\/\/cmm.imgw.pl\/?page_id=49229","title":{"rendered":"Nowcasting"},"content":{"rendered":"\t\t<div data-elementor-type=\"wp-page\" data-elementor-id=\"49229\" class=\"elementor elementor-49229\">\n\t\t\t\t\t\t\t\t\t<section class=\"elementor-section elementor-top-section elementor-element elementor-element-27444d1 elementor-section-boxed elementor-section-height-default elementor-section-height-default\" data-id=\"27444d1\" data-element_type=\"section\">\n\t\t\t\t\t\t<div class=\"elementor-container elementor-column-gap-default\">\n\t\t\t\t\t<div class=\"elementor-column elementor-col-100 elementor-top-column elementor-element elementor-element-7f7183b\" data-id=\"7f7183b\" data-element_type=\"column\">\n\t\t\t<div class=\"elementor-widget-wrap elementor-element-populated\">\n\t\t\t\t\t\t\t\t<div class=\"elementor-element elementor-element-6cb3e0f elementor-widget elementor-widget-html\" data-id=\"6cb3e0f\" data-element_type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t<div id=\"forecast-app\" class=\"forecast-app ultra-app\">\n\t<div class=\"layout-container\">\n\t\t<div class=\"left-column\">\n\t\t\t<div class=\"section\">\n\t\t\t\t<h3>Model:<\/h3>\n\t\t\t\t<div id=\"ultraModels\" class=\"buttons models-buttons\"><\/div>\n\t\t\t<\/div>\n\n\t\t\t<div class=\"section\">\n\t\t\t\t<h3>Parametr:<\/h3>\n\t\t\t\t<div id=\"ultraParamButtons\" class=\"buttons params-buttons\"><\/div>\n\t\t\t<\/div>\n\n\t\t\t<div class=\"section\">\n\t\t\t\t<h3 id=\"ultraFramesTitle\">Godzina (UTC):<\/h3>\n\t\t\t\t<div id=\"ultraFrames\" class=\"buttons frames-buttons\"><\/div>\n\t\t\t<\/div>\n\n\t\t\t\n\n\t\t\t<div class=\"keyboard-info\">\n\t\t\t\t<strong>\ud83d\udca1 Skr\u00f3ty klawiszowe:<\/strong><br>\n\t\t\t\t1-4 | Wyb\u00f3r modelu<br>\n\t\t\t\t\u2190 \u2192 | Zmiana Godziny<br>\n\t\t\t\t\u2191 \u2193 | Zmiana parametru<br>\n\t\t\t\tA | Animacja<br>\n\t\t\t\tN | Powr\u00f3t do aktualnej sytuacji<br>\n\t\t\t\tR | Od\u015bwie\u017c<br>\n\t\t\t<\/div>\n\t\t<\/div>\n\n\t\t<div class=\"right-column\">\n\t\t\t<div class=\"section images\">\n\t\t\t\t<h3 id=\"ultraImageTitle\" class=\"visually-hidden\" aria-hidden=\"true\">Prognoza ultrakr\u00f3tkoterminowa<\/h3>\n\n\t\t\t\t<div class=\"horizontal-slider-container\" id=\"ultraSliderContainer\">\n\t\t\t\t\t<div class=\"slider-header\">\n\t\t\t\t\t\t<span class=\"slider-title\" id=\"ultraSliderTitle\">Godzina (UTC):<\/span>\n\t\t\t\t\t\t<span class=\"hour-value\" id=\"ultraFrameValue\">--<\/span>\n\t\t\t\t\t\t<div class=\"slider-right\">\n\t\t\t\t\t\t\t<div id=\"ultraRefreshInfo\" class=\"refresh-info header-refresh\">Ostatnie sprawdzenie: jeszcze nie wykonano<\/div>\n\t\t\t\t\t\t\t<div class=\"slider-controls\">\n\t\t\t\t\t\t\t\t<button id=\"ultraAnimateBtn\" class=\"control-btn\" title=\"Animuj godziny\" type=\"button\">&#9654;<\/button>\n\t\t\t\t\t\t\t\t<button id=\"ultraLatestBtn\" class=\"control-btn\" title=\"Powr\u00f3t do aktualnej sytuacji\" type=\"button\">N<\/button>\n\t\t\t\t\t\t\t<\/div>\n\t\t\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t\t<input type=\"range\" id=\"ultraFrameSlider\" class=\"horizontal-slider\" min=\"0\" max=\"0\" step=\"1\" value=\"0\" aria-label=\"Wybierz godzin\u0119\">\n\t\t\t\t\t<div class=\"hour-labels\" id=\"ultraFrameLabels\"><\/div>\n\t\t\t\t\t<div class=\"date-cutoffs\" id=\"ultraDateCutoffs\"><\/div>\n\t\t\t\t<\/div>\n\n\t\t\t\t<div id=\"ultraStatus\" class=\"date-warning\"><\/div>\n\t\t\t\t<div id=\"ultraLoader\" class=\"loader-container\" style=\"display: none;\">\n\t\t\t\t\t<div class=\"loader\"><\/div>\n\t\t\t\t\t<p>\u0141adowanie mapy...<\/p>\n\t\t\t\t<\/div>\n\t\t\t\t<div id=\"ultraImages\" class=\"images\"><\/div>\n\t\t\t<\/div>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n\n<div class=\"lightbox\" id=\"ultraLightbox\">\n\t<span class=\"lightbox-close\" id=\"ultraLightboxClose\">&times;<\/span>\n\t<img id=\"ultraLightboxImg\" src=\"\" alt=\"Powi\u0119kszony obraz prognozy\">\n<\/div>\n\n<style>\n\t.forecast-app {\n\t\tfont-family: Arial, sans-serif;\n\t\tmax-width: 1200px;\n\t\tmargin: 0 auto;\n\t}\n\n\t.layout-container {\n\t\tdisplay: flex;\n\t\tflex-wrap: wrap;\n\t\tgap: 10px;\n\t}\n\n\t.left-column {\n\t\tflex: 1;\n\t\tmin-width: 260px;\n\t\tmax-width: 300px;\n\t}\n\n\t.right-column {\n\t\tflex: 2;\n\t\tmin-width: 400px;\n\t}\n\n\t.section {\n\t\tmargin-bottom: 12px;\n\t\tbackground: #f9f9f9;\n\t\tpadding: 10px;\n\t\tborder-radius: 8px;\n\t\tbox-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);\n\t}\n\n\t.section h3 {\n\t\tmargin-top: 0;\n\t\tmargin-bottom: 6px;\n\t\tborder-bottom: 1px solid #ddd;\n\t\tpadding-bottom: 5px;\n\t\tcolor: #333;\n\t}\n\n\t.buttons {\n\t\tdisplay: flex;\n\t\tflex-wrap: wrap;\n\t\tgap: 5px;\n\t\tmargin-top: 3px;\n\t}\n\n\t.buttons button {\n\t\tpadding: 4px 10px;\n\t\tmin-width: 45px;\n\t\tborder: 1px solid #666;\n\t\tborder-radius: 6px;\n\t\tbackground: #f5f5f5;\n\t\tcursor: pointer;\n\t\ttransition: all 0.2s;\n\t\tfont-size: 0.9em;\n\t\tline-height: 1.2;\n\t}\n\n\t.buttons button:hover {\n\t\tbackground: #ddd;\n\t}\n\n\t.buttons button.active {\n\t\tbackground-color: rgba(86, 221, 208, 1);\n\t\tcolor: #fff;\n\t\tborder-color: #2e7d32;\n\t}\n\n\t.models-buttons {\n\t\tflex-direction: column;\n\t\talign-items: stretch;\n\t\twidth: 100%;\n\t\tgap: 4px;\n\t}\n\n\t.models-buttons button {\n\t\twidth: 100%;\n\t\ttext-align: center;\n\t}\n\n\t.frames-buttons {\n\t\tdisplay: grid;\n\t\tgrid-template-columns: repeat(auto-fit, minmax(68px, 1fr));\n\t\tmax-height: none;\n\t\toverflow-y: visible;\n\t\tpadding-right: 2px;\n\t}\n\n\t.frames-buttons button {\n\t\twidth: 100%;\n\t\tmin-width: 0;\n\t\ttext-align: center;\n\t\tfont-variant-numeric: tabular-nums;\n\t\tpadding: 2px 8px;\n\t\tfont-size: 0.82em;\n\t\tline-height: 1.15;\n\t}\n\n\t.frames-buttons button.preload-pending:not(.active) {\n\t\tbackground: #ececec;\n\t\tborder-color: #b8b8b8;\n\t\tcolor: #7a7a7a;\n\t}\n\n\t.frames-buttons button.preload-loaded:not(.active) {\n\t\tbackground: linear-gradient(135deg, #d8d8d8 0%, #c9c9c9 100%);\n\t\tborder-color: #b1b1b1;\n\t\tcolor: #4f4f4f;\n\t}\n\n\t.frames-buttons button.preload-loading:not(.active) {\n\t\tbackground-image: repeating-linear-gradient(\n\t\t\t-45deg,\n\t\t\t#e8e8e8 0,\n\t\t\t#e8e8e8 8px,\n\t\t\t#dfdfdf 8px,\n\t\t\t#dfdfdf 16px\n\t\t);\n\t}\n\n\t.frames-buttons button.merge-grs-range:not(.active) {\n\t\tbackground: #e4eff1;\n\t\tborder-color: #95b1b7;\n\t\tcolor: #436168;\n\t}\n\n\t.frames-buttons button.merge-loaded:not(.active) {\n\t\tbackground: linear-gradient(135deg, #d8d8d8 0%, #c9c9c9 100%);\n\t\tborder-color: #b1b1b1;\n\t\tcolor: #4f4f4f;\n\t}\n\n\t.params-buttons {\n\t\twidth: 100%;\n\t\tflex-direction: column;\n\t\talign-items: stretch;\n\t\tgap: 4px;\n\t}\n\n\t.params-buttons button {\n\t\twidth: 100%;\n\t\ttext-align: center;\n\t\tpadding: 6px 10px;\n\t\tfont-size: 0.9em;\n\t}\n\n\t.visually-hidden {\n\t\tposition: absolute !important;\n\t\twidth: 1px;\n\t\theight: 1px;\n\t\tpadding: 0;\n\t\tmargin: -1px;\n\t\toverflow: hidden;\n\t\tclip: rect(0, 0, 0, 0);\n\t\twhite-space: nowrap;\n\t\tborder: 0;\n\t}\n\n\t.refresh-info {\n\t\tmargin-top: 8px;\n\t\tfont-size: 12px;\n\t\tcolor: #555;\n\t\tline-height: 1.35;\n\t}\n\n\t.header-refresh {\n\t\tmargin-top: 0;\n\t\ttext-align: right;\n\t\tfont-size: 11px;\n\t\tmax-width: 240px;\n\t}\n\n\t.time-note {\n\t\tfont-size: 10px;\n\t\tcolor: #666;\n\t\tmargin-top: 15px;\n\t\tpadding: 0 10px;\n\t\tline-height: 1.4;\n\t\tfont-style: italic;\n\t\ttext-align: justify;\n\t}\n\n\t.keyboard-info {\n\t\tfont-size: 11px;\n\t\tcolor: #555;\n\t\tmargin-top: 20px;\n\t\tpadding: 10px;\n\t\tline-height: 1.6;\n\t\tbackground-color: #f0f8ff;\n\t\tborder-left: 3px solid rgba(86, 221, 208, 1);\n\t\tborder-radius: 4px;\n\t}\n\n\t.keyboard-info strong {\n\t\tcolor: rgba(86, 221, 208, 1);\n\t\tfont-size: 12px;\n\t}\n\n\t.section.images {\n\t\tmin-height: 620px;\n\t}\n\n\t.horizontal-slider-container {\n\t\twidth: 100%;\n\t\tposition: relative;\n\t\tmargin: 0 0 20px 0;\n\t\tpadding: 10px 0;\n\t\tbackground-color: #f5f5f5;\n\t\tborder-radius: 5px;\n\t\tbox-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);\n\t\tdisplay: block;\n\t}\n\n\t.slider-header {\n\t\tdisplay: flex;\n\t\talign-items: center;\n\t\ttext-align: left;\n\t\tmargin-bottom: 0;\n\t\tgap: 10px;\n\t}\n\n\t.slider-right {\n\t\tmargin-left: auto;\n\t\tdisplay: flex;\n\t\talign-items: center;\n\t\tgap: 12px;\n\t}\n\n\t.slider-controls {\n\t\tdisplay: flex;\n\t\tgap: 8px;\n\t}\n\n\t.control-btn {\n\t\tbackground: rgba(86, 221, 208, 0.1);\n\t\tborder: 2px solid rgba(86, 221, 208, 1);\n\t\tcolor: rgba(86, 221, 208, 1);\n\t\tfont-size: 1.1em;\n\t\twidth: 40px;\n\t\theight: 40px;\n\t\tborder-radius: 8px;\n\t\tcursor: pointer !important;\n\t\ttransition: all 0.2s;\n\t\tdisplay: flex;\n\t\talign-items: center;\n\t\tjustify-content: center;\n\t\tpadding: 0;\n\t}\n\n\t.control-btn * {\n\t\tcursor: pointer !important;\n\t}\n\n\t.control-btn:hover {\n\t\tbackground: rgba(86, 221, 208, 0.2);\n\t\ttransform: scale(1.05);\n\t}\n\n\t.control-btn.active {\n\t\tbackground: rgba(86, 221, 208, 1);\n\t\tcolor: #fff;\n\t}\n\n\t.slider-title {\n\t\tfont-size: 1.1em;\n\t\tfont-weight: bold;\n\t\tcolor: #333;\n\t}\n\n\t.hour-value {\n\t\tfont-size: 1.15em;\n\t\tfont-weight: bold;\n\t\tcolor: rgba(86, 221, 208, 1);\n\t}\n\n\t.horizontal-slider {\n\t\t-webkit-appearance: none;\n\t\tappearance: none;\n\t\twidth: 100%;\n\t\theight: 10px;\n\t\tborder-radius: 5px;\n\t\tbackground: linear-gradient(to right, #b0b0b0 0%, #b0b0b0 0%, #e0e0e0 0%, #e0e0e0 100%);\n\t\toutline: none;\n\t\tmargin: 0 0 10px 0;\n\t\tposition: relative;\n\t\tz-index: 2;\n\t}\n\n\t.horizontal-slider::-webkit-slider-thumb {\n\t\t-webkit-appearance: none;\n\t\tappearance: none;\n\t\twidth: 24px;\n\t\theight: 24px;\n\t\tborder-radius: 50%;\n\t\tbackground: rgba(86, 221, 208, 1);\n\t\tcursor: pointer;\n\t\tborder: 2px solid #fff;\n\t\tbox-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);\n\t}\n\n\t.horizontal-slider::-moz-range-thumb {\n\t\twidth: 24px;\n\t\theight: 24px;\n\t\tborder-radius: 50%;\n\t\tbackground: rgba(86, 221, 208, 1);\n\t\tcursor: pointer;\n\t\tborder: 2px solid #fff;\n\t\tbox-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);\n\t}\n\n\t.hour-labels {\n\t\tposition: relative;\n\t\theight: 20px;\n\t\tmargin-top: 0;\n\t\tcolor: #666;\n\t\tfont-size: 0.82em;\n\t\tpadding: 0;\n\t\tfont-variant-numeric: tabular-nums;\n\t}\n\n\t.hour-label-marker {\n\t\tposition: absolute;\n\t\ttop: 0;\n\t\ttransform: translateX(-50%);\n\t\twhite-space: nowrap;\n\t}\n\n\t.hour-label-marker.preload-pending {\n\t\tcolor: #8f8f8f;\n\t}\n\n\t.hour-label-marker.preload-loading {\n\t\tcolor: #777;\n\t}\n\n\t.hour-label-marker.preload-loaded {\n\t\tcolor: #7a7a7a;\n\t}\n\n\t.hour-label-marker.merge-grs-range {\n\t\tcolor: #45656b;\n\t}\n\n\t.hour-label-marker.merge-loaded {\n\t\tcolor: #7a7a7a;\n\t}\n\n\t.hour-label-marker.first {\n\t\tleft: 0 !important;\n\t\ttransform: none;\n\t}\n\n\t.hour-label-marker.last {\n\t\ttransform: translateX(-100%);\n\t}\n\n\t.date-cutoffs {\n\t\tposition: relative;\n\t\theight: 22px;\n\t\tmargin-top: 4px;\n\t}\n\n\t.date-cutoff-marker {\n\t\tposition: absolute;\n\t\tbottom: 0;\n\t\ttransform: translateX(-50%);\n\t\tfont-size: 10px;\n\t\tcolor: #5f5f5f;\n\t\twhite-space: nowrap;\n\t\tline-height: 1;\n\t}\n\n\t.date-cutoff-marker::before {\n\t\tcontent: \"\";\n\t\tposition: absolute;\n\t\tleft: 50%;\n\t\ttransform: translateX(-50%);\n\t\ttop: -11px;\n\t\theight: 8px;\n\t\tborder-left: 1px solid #909090;\n\t}\n\n\t.date-cutoff-marker.first {\n\t\tleft: 0 !important;\n\t\ttransform: none;\n\t}\n\n\t.date-cutoff-marker.first::before {\n\t\tleft: 0;\n\t\ttransform: none;\n\t}\n\n\t.frame-date-divider {\n\t\tgrid-column: 1 \/ -1;\n\t\twidth: 100%;\n\t\tmargin: 4px 0 0;\n\t\tpadding-top: 4px;\n\t\tborder-top: 1px dashed #cfcfcf;\n\t\tfont-size: 11px;\n\t\tcolor: #666;\n\t\tfont-weight: 600;\n\t}\n\n\t.date-warning {\n\t\tdisplay: none;\n\t\ttext-align: center;\n\t\tpadding: 8px 15px;\n\t\tbackground: rgba(255, 152, 0, 0.1);\n\t\tborder: 1px solid rgba(255, 152, 0, 0.3);\n\t\tborder-radius: 6px;\n\t\tfont-size: 13px;\n\t\tcolor: #e65100;\n\t\tmargin: 10px 0;\n\t\tfont-weight: 500;\n\t}\n\n\t.loader-container {\n\t\tdisplay: flex;\n\t\tjustify-content: center;\n\t\talign-items: center;\n\t\tflex-direction: column;\n\t\twidth: 100%;\n\t\theight: 280px;\n\t\tz-index: 100;\n\t\tposition: relative;\n\t\tbackground-color: rgba(245, 245, 245, 0.7);\n\t}\n\n\t.loader {\n\t\tborder: 12px solid #f3f3f3;\n\t\tborder-radius: 50%;\n\t\tborder-top: 12px solid rgba(86, 221, 208, 1);\n\t\twidth: 90px;\n\t\theight: 90px;\n\t\tanimation: spin 0.8s linear infinite;\n\t\tbox-shadow: 0 0 20px rgba(86, 221, 208, 0.7);\n\t}\n\n\t@keyframes spin {\n\t\t0% {\n\t\t\ttransform: rotate(0deg);\n\t\t}\n\t\t100% {\n\t\t\ttransform: rotate(360deg);\n\t\t}\n\t}\n\n\t.images {\n\t\tposition: relative;\n\t\twidth: 100%;\n\t\tmin-height: 540px;\n\t}\n\n\t.images img {\n\t\tmax-width: 100%;\n\t\tmargin: 5px auto;\n\t\tborder: 1px solid #ccc;\n\t\tdisplay: block;\n\t\tbox-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);\n\t\tcursor: zoom-in;\n\t}\n\n\t.lightbox {\n\t\tdisplay: none;\n\t\tposition: fixed;\n\t\ttop: 0;\n\t\tleft: 0;\n\t\twidth: 100%;\n\t\theight: 100%;\n\t\tbackground: rgba(0, 0, 0, 0.9);\n\t\tz-index: 1000;\n\t\tjustify-content: center;\n\t\talign-items: center;\n\t\tcursor: pointer;\n\t}\n\n\t.lightbox.active {\n\t\tdisplay: flex;\n\t}\n\n\t.lightbox img {\n\t\tmax-width: 95%;\n\t\tmax-height: 95%;\n\t\tobject-fit: contain;\n\t}\n\n\t.lightbox-close {\n\t\tposition: absolute;\n\t\ttop: 20px;\n\t\tright: 30px;\n\t\tcolor: #fff;\n\t\tfont-size: 40px;\n\t\tcursor: pointer;\n\t\tline-height: 1;\n\t}\n\n\t@media (max-width: 768px) {\n\t\t.layout-container {\n\t\t\tflex-direction: column;\n\t\t}\n\n\t\t.left-column,\n\t\t.right-column {\n\t\t\tmax-width: 100%;\n\t\t\twidth: 100%;\n\t\t}\n\n\t\t.frames-buttons {\n\t\t\tmax-height: 200px;\n\t\t}\n\n\t\t.frames-buttons button {\n\t\t\twidth: 100%;\n\t\t}\n\n\t\t.slider-header {\n\t\t\tflex-direction: column;\n\t\t\talign-items: flex-start;\n\t\t}\n\n\t\t.slider-right {\n\t\t\twidth: 100%;\n\t\t\tmargin-left: 0;\n\t\t\tjustify-content: space-between;\n\t\t}\n\n\t\t.header-refresh {\n\t\t\ttext-align: left;\n\t\t\tmax-width: 100%;\n\t\t}\n\n\t\t.keyboard-info {\n\t\t\tdisplay: none;\n\t\t}\n\n\t}\n<\/style>\n\n<script>\n\t(function() {\n\t\tvar BASE_PATH = \"\/wp-content\/uploads\/production\";\n\t\tvar GRS_FOLDER = \"GRS\";\n\t\tvar GRS_RAIN_PREFIX = \"RAIN_GRS_GRS_10_\";\n\n\t\tvar modelsConfig = {\n\t\t\tINCA: {\n\t\t\t\tdisplayName: \"INCA (8h)\",\n\t\t\t\tfolder: \"INCA\",\n\t\t\t\thorizonHours: 8,\n\t\t\t\tlookBackHours: 2,\n\t\t\t\tstepMinutes: 10,\n\t\t\t\tparameters: [\n\t\t\t\t\t{ id: \"TEMP2M\", label: \"Temperatura powietrza 2\u00a0m\", filePrefix: \"INCA_PL_TEMP2M_\" },\n\t\t\t\t\t{ id: \"TEMPCH\", label: \"Temperatura odczuwalna\", filePrefix: \"INCA_PL_TEMPCH_\" },\n\t\t\t\t\t{ id: \"TSURF\", label: \"Temperatura powierzchni\", filePrefix: \"INCA_PL_TSURF_\" },\n\t\t\t\t\t{ id: \"TEMPD\", label: \"Temperatura punktu rosy\", filePrefix: \"INCA_PL_TEMPD_\" },\n\t\t\t\t\t{ id: \"TEMP2M_SW\", label: \"Temperatura pow. (color friendly)\", filePrefix: \"INCA_PL_TEMP2M_SW_\" },\n\t\t\t\t\t{ id: \"WIND10M\", label: \"Wiatr 10\u00a0m\", filePrefix: \"INCA_PL_WIND10M_\" },\n\t\t\t\t\t{ id: \"PRES\", label: \"Ci\u015bnienie n.p.m.\", filePrefix: \"INCA_PL_PRES_\" },\n\t\t\t\t\t{ id: \"RHUM2M\", label: \"Wilgotno\u015b\u0107 wzgl\u0119dna 2\u00a0m\", filePrefix: \"INCA_PL_RHUM2M_\" },\n\t\t\t\t\t{ id: \"RHUM2M_SW\", label: \"Wilgotno\u015b\u0107 wzgl. (color friendly)\", filePrefix: \"INCA_PL_RHUM2M_SW_\" }\n\t\t\t\t]\n\t\t\t},\n\t\t\tMERGE: {\n\t\t\t\tdisplayName: \"RainGRS + MERGE (8h)\",\n\t\t\t\tfolder: \"MERGE\",\n\t\t\t\thorizonHours: 8,\n\t\t\t\tlookBackHours: 2,\n\t\t\t\tstepMinutes: 10,\n\t\t\t\tparameters: [\n\t\t\t\t\t{ id: \"MERGE\", label: \"Opad ca\u0142kowity\", filePrefix: \"MERGE_MERGE_10_\" },\n\t\t\t\t\t{ id: \"SNOW\", label: \"Opad \u015bniegu\", filePrefix: \"MERGE_SNOW_\" }\n\t\t\t\t]\n\t\t\t},\n\t\t\tTSP: {\n\t\t\t\tdisplayName: \"TSP (1h)\",\n\t\t\t\tfolder: \"TSP\",\n\t\t\t\thorizonHours: 1,\n\t\t\t\tlookBackHours: 1,\n\t\t\t\tstepMinutes: 10,\n\t\t\t\tparameters: [\n\t\t\t\t\t{\n\t\t\t\t\t\tid: \"INTENSITY\",\n\t\t\t\t\t\tlabel: \"Intensywno\u015b\u0107 burz (aktualna + prognoza 1h)\",\n\t\t\t\t\t\tnowPrefix: \"TSP_TSP_10_ANA_intensity_\",\n\t\t\t\t\t\tfuturePrefix: \"TSP_TSP_60_PROG_intensity_\",\n\t\t\t\t\t\tstaticNowFile: \"TSP_TSP_10_ANA_intensity.jpg\",\n\t\t\t\t\t\tstaticFutureFile: \"TSP_TSP_60_PROG_intensity.jpg\"\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tid: \"PROBABILITY\",\n\t\t\t\t\t\tlabel: \"Prawdopodobie\u0144stwo burz (aktualne + prognoza 1h)\",\n\t\t\t\t\t\tnowPrefix: \"TSP_TSP_10_ANA_probability_\",\n\t\t\t\t\t\tfuturePrefix: \"TSP_TSP_60_PROG_probability_\",\n\t\t\t\t\t\tstaticNowFile: \"TSP_TSP_10_ANA_probability.jpg\",\n\t\t\t\t\t\tstaticFutureFile: \"TSP_TSP_60_PROG_probability.jpg\"\n\t\t\t\t\t}\n\t\t\t\t]\n\t\t\t},\n\t\t\tSPT: {\n\t\t\t\tdisplayName: \"SPT (2h)\",\n\t\t\t\tfolder: \"SPT\",\n\t\t\t\thorizonHours: 2,\n\t\t\t\tlookBackHours: 1,\n\t\t\t\tstepMinutes: 10,\n\t\t\t\tparameters: [\n\t\t\t\t\t{ id: \"SPT\", label: \"Typ opadu\", filePrefix: \"SPT_SPT_10_\" }\n\t\t\t\t]\n\t\t\t}\n\t\t};\n\n\t\tvar modelOrder = Object.keys(modelsConfig);\n\n\t\tvar state = {\n\t\t\tselectedModel: modelOrder[0],\n\t\t\tselectedParam: modelsConfig[modelOrder[0]].parameters[0].id,\n\t\t\tframes: [],\n\t\t\tselectedFrameIndex: 0,\n\t\t\tisAnimating: false,\n\t\t\tanimationTimer: null,\n\t\t\tautoRefreshTimer: null,\n\t\t\tlastRefreshAt: null,\n\t\t\tisRefreshing: false,\n\t\t\tpendingRefresh: false,\n\t\t\trefreshToken: 0,\n\t\t\timageToken: 0,\n\t\t\tcacheVersion: 0,\n\t\t\timageCache: {},\n\t\t\timageLoadPromises: {},\n\t\t\tpreloadQueue: [],\n\t\t\tisPreloading: false,\n\t\t\tpreloadGeneration: 0\n\t\t};\n\n\t\tvar PRELOAD_BATCH_SIZE = 4;\n\t\tvar AUTO_REFRESH_INTERVAL_MS = 5 * 60 * 1000;\n\t\tvar AUTO_REFRESH_MODELS = {\n\t\t\tMERGE: true,\n\t\t\tSPT: true,\n\t\t\tTSP: true\n\t\t};\n\n\t\tfunction escapeRegExp(value) {\n\t\t\treturn value.replace(\/[.*+?^${}()|[\\]\\\\]\/g, \"\\\\$&\");\n\t\t}\n\n\t\tfunction floorToStepUTC(dateObj, stepMinutes) {\n\t\t\tvar rounded = new Date(dateObj.getTime());\n\t\t\trounded.setUTCSeconds(0, 0);\n\t\t\tvar minutes = rounded.getUTCMinutes();\n\t\t\trounded.setUTCMinutes(minutes - (minutes % stepMinutes));\n\t\t\treturn rounded;\n\t\t}\n\n\t\tfunction formatTimestampUTC(dateObj) {\n\t\t\tvar y = String(dateObj.getUTCFullYear());\n\t\t\tvar m = String(dateObj.getUTCMonth() + 1).padStart(2, \"0\");\n\t\t\tvar d = String(dateObj.getUTCDate()).padStart(2, \"0\");\n\t\t\tvar hh = String(dateObj.getUTCHours()).padStart(2, \"0\");\n\t\t\tvar mm = String(dateObj.getUTCMinutes()).padStart(2, \"0\");\n\t\t\treturn y + \"-\" + m + \"-\" + d + \"_\" + hh + \"_\" + mm + \"_00\";\n\t\t}\n\n\t\tfunction parseTimestampUTC(timestamp) {\n\t\t\tif (!timestamp) {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\tvar match = timestamp.match(\/^(\\d{4})-(\\d{2})-(\\d{2})_(\\d{2})_(\\d{2})_(\\d{2})$\/);\n\t\t\tif (!match) {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\treturn new Date(Date.UTC(\n\t\t\t\tparseInt(match[1], 10),\n\t\t\t\tparseInt(match[2], 10) - 1,\n\t\t\t\tparseInt(match[3], 10),\n\t\t\t\tparseInt(match[4], 10),\n\t\t\t\tparseInt(match[5], 10),\n\t\t\t\tparseInt(match[6], 10)\n\t\t\t));\n\t\t}\n\n\t\tfunction formatFrameButtonLabel(timestamp) {\n\t\t\tvar dateObj = parseTimestampUTC(timestamp);\n\t\t\tif (!dateObj) {\n\t\t\t\treturn \"Najnowsza\";\n\t\t\t}\n\t\t\tvar hour = String(dateObj.getUTCHours()).padStart(2, \"0\");\n\t\t\tvar minute = String(dateObj.getUTCMinutes()).padStart(2, \"0\");\n\t\t\treturn hour + \":\" + minute;\n\t\t}\n\n\t\tfunction formatFrameDateLabel(timestamp) {\n\t\t\tvar dateObj = parseTimestampUTC(timestamp);\n\t\t\tif (!dateObj) {\n\t\t\t\treturn \"\";\n\t\t\t}\n\t\t\tvar day = String(dateObj.getUTCDate()).padStart(2, \"0\");\n\t\t\tvar month = String(dateObj.getUTCMonth() + 1).padStart(2, \"0\");\n\t\t\tvar year = String(dateObj.getUTCFullYear());\n\t\t\treturn day + \".\" + month + \".\" + year;\n\t\t}\n\n\t\tfunction formatFrameVerboseLabel(timestamp) {\n\t\t\tvar dateObj = parseTimestampUTC(timestamp);\n\t\t\tif (!dateObj) {\n\t\t\t\treturn \"Najnowsza dost\u0119pna mapa\";\n\t\t\t}\n\t\t\tvar day = String(dateObj.getUTCDate()).padStart(2, \"0\");\n\t\t\tvar month = String(dateObj.getUTCMonth() + 1).padStart(2, \"0\");\n\t\t\tvar year = String(dateObj.getUTCFullYear());\n\t\t\tvar hour = String(dateObj.getUTCHours()).padStart(2, \"0\");\n\t\t\tvar minute = String(dateObj.getUTCMinutes()).padStart(2, \"0\");\n\t\t\treturn day + \".\" + month + \".\" + year + \" \" + hour + \":\" + minute + \" UTC\";\n\t\t}\n\n\t\tfunction formatLocalDateTime(dateObj) {\n\t\t\tif (!dateObj) {\n\t\t\t\treturn \"jeszcze nie wykonano\";\n\t\t\t}\n\t\t\tvar dd = String(dateObj.getDate()).padStart(2, \"0\");\n\t\t\tvar mm = String(dateObj.getMonth() + 1).padStart(2, \"0\");\n\t\t\tvar yyyy = String(dateObj.getFullYear());\n\t\t\tvar hh = String(dateObj.getHours()).padStart(2, \"0\");\n\t\t\tvar min = String(dateObj.getMinutes()).padStart(2, \"0\");\n\t\t\tvar ss = String(dateObj.getSeconds()).padStart(2, \"0\");\n\t\t\treturn dd + \".\" + mm + \".\" + yyyy + \" \" + hh + \":\" + min + \":\" + ss;\n\t\t}\n\n\t\tfunction getModelConfig(modelId) {\n\t\t\treturn modelsConfig[modelId];\n\t\t}\n\n\t\tfunction getCurrentModelConfig() {\n\t\t\treturn getModelConfig(state.selectedModel);\n\t\t}\n\n\t\tfunction getParamConfig(modelConfig, paramId) {\n\t\t\tvar i;\n\t\t\tfor (i = 0; i < modelConfig.parameters.length; i += 1) {\n\t\t\t\tif (modelConfig.parameters[i].id === paramId) {\n\t\t\t\t\treturn modelConfig.parameters[i];\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn null;\n\t\t}\n\n\t\tfunction getCurrentParamConfig() {\n\t\t\treturn getParamConfig(getCurrentModelConfig(), state.selectedParam);\n\t\t}\n\n\t\tfunction isMergedTspParam(paramConfig) {\n\t\t\treturn !!(paramConfig && paramConfig.nowPrefix && paramConfig.futurePrefix);\n\t\t}\n\n\t\tfunction isAutoRefreshModel(modelId) {\n\t\t\treturn !!AUTO_REFRESH_MODELS[modelId];\n\t\t}\n\n\t\tfunction getTimestampMs(timestamp) {\n\t\t\tvar dateObj = parseTimestampUTC(timestamp);\n\t\t\treturn dateObj ? dateObj.getTime() : null;\n\t\t}\n\n\t\tfunction isPastOrNowTimestamp(timestamp) {\n\t\t\tvar tsMs = getTimestampMs(timestamp);\n\t\t\tif (tsMs === null) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\treturn tsMs <= Date.now();\n\t\t}\n\n\t\tfunction getTimestampPrefixes(paramConfig, timestamp) {\n\t\t\tif (!isMergedTspParam(paramConfig)) {\n\t\t\t\treturn [paramConfig.filePrefix];\n\t\t\t}\n\n\t\t\tif (isPastOrNowTimestamp(timestamp)) {\n\t\t\t\treturn [paramConfig.nowPrefix, paramConfig.futurePrefix];\n\t\t\t}\n\n\t\t\treturn [paramConfig.futurePrefix, paramConfig.nowPrefix];\n\t\t}\n\n\t\tfunction getPreferredPrefixForTimestamp(paramConfig, timestamp) {\n\t\t\treturn getTimestampPrefixes(paramConfig, timestamp)[0];\n\t\t}\n\n\t\tfunction buildTimestampFileCandidates(paramConfig, timestamp) {\n\t\t\treturn getTimestampPrefixes(paramConfig, timestamp).map(function(prefix) {\n\t\t\t\treturn prefix + timestamp + \".jpg\";\n\t\t\t});\n\t\t}\n\n\t\tfunction getStaticFallbackFiles(paramConfig) {\n\t\t\tif (isMergedTspParam(paramConfig)) {\n\t\t\t\tvar files = [];\n\t\t\t\tif (paramConfig.staticNowFile) {\n\t\t\t\t\tfiles.push(paramConfig.staticNowFile);\n\t\t\t\t}\n\t\t\t\tif (paramConfig.staticFutureFile) {\n\t\t\t\t\tfiles.push(paramConfig.staticFutureFile);\n\t\t\t\t}\n\t\t\t\treturn files;\n\t\t\t}\n\n\t\t\treturn paramConfig.staticFile ? [paramConfig.staticFile] : [];\n\t\t}\n\n\t\tfunction getFrameCacheKey(frame) {\n\t\t\treturn frame.url + \"::\" + state.cacheVersion;\n\t\t}\n\n\t\tfunction getFrameRequestUrl(frame) {\n\t\t\tvar separator = frame.url.indexOf(\"?\") === -1 ? \"?\" : \"&\";\n\t\t\treturn frame.url + separator + \"v=\" + state.cacheVersion;\n\t\t}\n\n\t\tfunction buildFrameUrl(modelConfig, fileName) {\n\t\t\treturn BASE_PATH + \"\/\" + modelConfig.folder + \"\/\" + fileName;\n\t\t}\n\n\t\tfunction buildFrameUrlForFolder(folder, fileName) {\n\t\t\treturn BASE_PATH + \"\/\" + folder + \"\/\" + fileName;\n\t\t}\n\n\t\tfunction extractTimestampByPrefix(fileName, prefix) {\n\t\t\tvar regex = new RegExp(\"^\" + escapeRegExp(prefix) + \"(\\\\d{4}-\\\\d{2}-\\\\d{2}_\\\\d{2}_\\\\d{2}_\\\\d{2})\\\\.jpg$\", \"i\");\n\t\t\tvar match = fileName.match(regex);\n\t\t\treturn match ? match[1] : null;\n\t\t}\n\n\t\tfunction getFrameDisplayLabel(frame) {\n\t\t\tif (frame && frame.displayLabel) {\n\t\t\t\treturn frame.displayLabel;\n\t\t\t}\n\t\t\tif (!frame || !frame.timestamp) {\n\t\t\t\treturn \"Najnowsza\";\n\t\t\t}\n\t\t\treturn formatFrameButtonLabel(frame.timestamp);\n\t\t}\n\n\t\tfunction getFrameLoadState(frame) {\n\t\t\tvar key = getFrameCacheKey(frame);\n\t\t\tif (state.imageCache[key]) {\n\t\t\t\treturn \"loaded\";\n\t\t\t}\n\t\t\tif (state.imageLoadPromises[key]) {\n\t\t\t\treturn \"loading\";\n\t\t\t}\n\t\t\treturn \"pending\";\n\t\t}\n\n\t\tfunction isMergeGrsFrame(frame) {\n\t\t\treturn state.selectedModel === \"MERGE\" && state.selectedParam === \"MERGE\" && frame && frame.source === \"GRS\";\n\t\t}\n\n\t\tfunction isMergeForecastFrame(frame) {\n\t\t\treturn state.selectedModel === \"MERGE\" && state.selectedParam === \"MERGE\" && frame && frame.source === \"MERGE\";\n\t\t}\n\n\t\tfunction updateTimeLabelsVisibility() {\n\t\t\tvar isTsp = state.selectedModel === \"TSP\";\n\t\t\tvar framesTitle = document.getElementById(\"ultraFramesTitle\");\n\t\t\tvar sliderTitle = document.getElementById(\"ultraSliderTitle\");\n\n\t\t\tif (framesTitle) {\n\t\t\t\tframesTitle.style.display = isTsp ? \"none\" : \"block\";\n\t\t\t}\n\n\t\t\tif (sliderTitle) {\n\t\t\t\tsliderTitle.style.display = isTsp ? \"none\" : \"inline\";\n\t\t\t}\n\t\t}\n\n\t\tfunction buildTimestampCandidates(modelConfig) {\n\t\t\tvar step = modelConfig.stepMinutes;\n\t\t\tvar nowRounded = floorToStepUTC(new Date(), step);\n\t\t\tvar start = new Date(nowRounded.getTime() - modelConfig.lookBackHours * 3600000);\n\t\t\tvar end = new Date(nowRounded.getTime() + modelConfig.horizonHours * 3600000);\n\t\t\tvar values = [];\n\t\t\tvar pointer = floorToStepUTC(start, step);\n\n\t\t\twhile (pointer.getTime() <= end.getTime()) {\n\t\t\t\tvalues.push(formatTimestampUTC(pointer));\n\t\t\t\tpointer = new Date(pointer.getTime() + step * 60000);\n\t\t\t}\n\n\t\t\treturn values;\n\t\t}\n\n\t\tasync function checkUrlExists(url) {\n\t\t\ttry {\n\t\t\t\tvar headResponse = await fetch(url, { method: \"HEAD\", cache: \"no-store\" });\n\t\t\t\tif (headResponse.ok) {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t\tif (headResponse.status !== 405 && headResponse.status !== 501) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tvar getResponse = await fetch(url, {\n\t\t\t\t\tmethod: \"GET\",\n\t\t\t\t\tcache: \"no-store\",\n\t\t\t\t\theaders: {\n\t\t\t\t\t\t\"Range\": \"bytes=0-0\"\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\treturn getResponse.ok;\n\t\t\t} catch (errorGet) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\n\t\tasync function fetchDirectoryFiles(folder) {\n\t\t\tvar directoryUrl = BASE_PATH + \"\/\" + folder + \"\/\";\n\t\t\ttry {\n\t\t\t\tvar response = await fetch(directoryUrl, { method: \"GET\", cache: \"no-store\" });\n\t\t\t\tif (!response.ok) {\n\t\t\t\t\treturn [];\n\t\t\t\t}\n\n\t\t\t\tvar html = await response.text();\n\t\t\t\tvar files = {};\n\t\t\t\tvar hrefRegex = \/href=[\"']([^\"']+\\.jpg(?:\\?[^\"']*)?)[\"']\/gi;\n\t\t\t\tvar anyMatch = false;\n\t\t\t\tvar match;\n\n\t\t\t\twhile ((match = hrefRegex.exec(html)) !== null) {\n\t\t\t\t\tanyMatch = true;\n\t\t\t\t\tvar raw = match[1].split(\"?\")[0];\n\t\t\t\t\tvar fileName = decodeURIComponent(raw.split(\"\/\").pop());\n\t\t\t\t\tif (fileName && fileName.toLowerCase().endsWith(\".jpg\")) {\n\t\t\t\t\t\tfiles[fileName] = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (!anyMatch) {\n\t\t\t\t\tvar plainRegex = \/([A-Za-z0-9._-]+\\.jpg)\/g;\n\t\t\t\t\twhile ((match = plainRegex.exec(html)) !== null) {\n\t\t\t\t\t\tfiles[match[1]] = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn Object.keys(files);\n\t\t\t} catch (error) {\n\t\t\t\treturn [];\n\t\t\t}\n\t\t}\n\n\t\tfunction extractTimestampFromFileName(fileName, paramConfig) {\n\t\t\tvar prefixes = getTimestampPrefixes(paramConfig);\n\n\t\t\tfor (var i = 0; i < prefixes.length; i += 1) {\n\t\t\t\tvar prefix = prefixes[i];\n\t\t\t\tvar regex = new RegExp(\"^\" + escapeRegExp(prefix) + \"(\\\\d{4}-\\\\d{2}-\\\\d{2}_\\\\d{2}_\\\\d{2}_\\\\d{2})\\\\.jpg$\", \"i\");\n\t\t\t\tvar match = fileName.match(regex);\n\t\t\t\tif (match) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\ttimestamp: match[1],\n\t\t\t\t\t\tprefix: prefix\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn null;\n\t\t}\n\n\t\tfunction isTimestampInWindow(timestamp, modelConfig) {\n\t\t\tvar dateObj = parseTimestampUTC(timestamp);\n\t\t\tif (!dateObj) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tvar now = new Date();\n\t\t\tvar diffHours = (dateObj.getTime() - now.getTime()) \/ 3600000;\n\t\t\treturn diffHours >= (-modelConfig.lookBackHours - 0.2) && diffHours <= (modelConfig.horizonHours + 0.2);\n\t\t}\n\n\t\tfunction sortFramesAscending(frames) {\n\t\t\tframes.sort(function(a, b) {\n\t\t\t\tvar aDate = parseTimestampUTC(a.timestamp);\n\t\t\t\tvar bDate = parseTimestampUTC(b.timestamp);\n\t\t\t\tif (!aDate && !bDate) {\n\t\t\t\t\treturn 0;\n\t\t\t\t}\n\t\t\t\tif (!aDate) {\n\t\t\t\t\treturn -1;\n\t\t\t\t}\n\t\t\t\tif (!bDate) {\n\t\t\t\t\treturn 1;\n\t\t\t\t}\n\t\t\t\treturn aDate.getTime() - bDate.getTime();\n\t\t\t});\n\t\t\treturn frames;\n\t\t}\n\n\t\tasync function appendStaticFallbackFrame(frames, modelConfig, paramConfig) {\n\t\t\tif (frames.length) {\n\t\t\t\treturn frames;\n\t\t\t}\n\n\t\t\tvar fallbackFiles = getStaticFallbackFiles(paramConfig);\n\t\t\tfor (var i = 0; i < fallbackFiles.length; i += 1) {\n\t\t\t\tvar staticUrl = buildFrameUrl(modelConfig, fallbackFiles[i]);\n\t\t\t\tif (await checkUrlExists(staticUrl)) {\n\t\t\t\t\tframes.push({\n\t\t\t\t\t\ttimestamp: null,\n\t\t\t\t\t\turl: staticUrl,\n\t\t\t\t\t\tstatic: true\n\t\t\t\t\t});\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn frames;\n\t\t}\n\n\t\tasync function loadFramesFromDirectory(modelConfig, paramConfig) {\n\t\t\tvar files = await fetchDirectoryFiles(modelConfig.folder);\n\t\t\tif (!files.length) {\n\t\t\t\treturn [];\n\t\t\t}\n\n\t\t\tvar byTimestamp = {};\n\t\t\tvar i;\n\t\t\tfor (i = 0; i < files.length; i += 1) {\n\t\t\t\tvar fileName = files[i];\n\t\t\t\tvar extracted = extractTimestampFromFileName(fileName, paramConfig);\n\t\t\t\tif (extracted && isTimestampInWindow(extracted.timestamp, modelConfig)) {\n\t\t\t\t\tvar existing = byTimestamp[extracted.timestamp];\n\t\t\t\t\tif (!existing) {\n\t\t\t\t\t\tbyTimestamp[extracted.timestamp] = {\n\t\t\t\t\t\t\ttimestamp: extracted.timestamp,\n\t\t\t\t\t\t\turl: buildFrameUrl(modelConfig, fileName),\n\t\t\t\t\t\t\tstatic: false,\n\t\t\t\t\t\t\tprefix: extracted.prefix\n\t\t\t\t\t\t};\n\t\t\t\t\t} else {\n\t\t\t\t\t\tvar preferredPrefix = getPreferredPrefixForTimestamp(paramConfig, extracted.timestamp);\n\t\t\t\t\t\tvar currentScore = existing.prefix === preferredPrefix ? 1 : 0;\n\t\t\t\t\t\tvar incomingScore = extracted.prefix === preferredPrefix ? 1 : 0;\n\t\t\t\t\t\tif (incomingScore > currentScore) {\n\t\t\t\t\t\t\tbyTimestamp[extracted.timestamp] = {\n\t\t\t\t\t\t\t\ttimestamp: extracted.timestamp,\n\t\t\t\t\t\t\t\turl: buildFrameUrl(modelConfig, fileName),\n\t\t\t\t\t\t\t\tstatic: false,\n\t\t\t\t\t\t\t\tprefix: extracted.prefix\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tvar frames = Object.keys(byTimestamp).map(function(ts) {\n\t\t\t\treturn {\n\t\t\t\t\ttimestamp: byTimestamp[ts].timestamp,\n\t\t\t\t\turl: byTimestamp[ts].url,\n\t\t\t\t\tstatic: false\n\t\t\t\t};\n\t\t\t});\n\n\t\t\tframes = sortFramesAscending(frames);\n\t\t\treturn appendStaticFallbackFrame(frames, modelConfig, paramConfig);\n\t\t}\n\n\t\tasync function loadFramesByProbing(modelConfig, paramConfig) {\n\t\t\tvar timestamps = buildTimestampCandidates(modelConfig);\n\t\t\tvar checks = [];\n\t\t\tvar i;\n\n\t\t\tfor (i = 0; i < timestamps.length; i += 1) {\n\t\t\t\tchecks.push((async function(ts) {\n\t\t\t\t\tvar fileCandidates = buildTimestampFileCandidates(paramConfig, ts);\n\n\t\t\t\t\tfor (var idx = 0; idx < fileCandidates.length; idx += 1) {\n\t\t\t\t\t\tvar url = buildFrameUrl(modelConfig, fileCandidates[idx]);\n\t\t\t\t\t\tif (await checkUrlExists(url)) {\n\t\t\t\t\t\t\treturn { timestamp: ts, url: url, static: false };\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\treturn null;\n\t\t\t\t}(timestamps[i])));\n\t\t\t}\n\n\t\t\tvar checked = await Promise.all(checks);\n\t\t\tvar frames = checked.filter(function(item) {\n\t\t\t\treturn item !== null;\n\t\t\t});\n\n\t\t\tframes = sortFramesAscending(frames);\n\t\t\treturn appendStaticFallbackFrame(frames, modelConfig, paramConfig);\n\t\t}\n\n\t\tasync function loadFramesForSelection(modelConfig, paramConfig) {\n\t\t\tif (modelConfig.folder === \"TSP\" && isMergedTspParam(paramConfig)) {\n\t\t\t\treturn loadLatestTspFrames(modelConfig, paramConfig);\n\t\t\t}\n\n\t\t\tif (modelConfig.folder === \"MERGE\" && paramConfig.id === \"MERGE\") {\n\t\t\t\treturn loadMergeWithGrsFrames(modelConfig, paramConfig);\n\t\t\t}\n\n\t\t\tvar fromDirectory = await loadFramesFromDirectory(modelConfig, paramConfig);\n\t\t\tif (fromDirectory.length) {\n\t\t\t\treturn fromDirectory;\n\t\t\t}\n\t\t\treturn loadFramesByProbing(modelConfig, paramConfig);\n\t\t}\n\n\t\tasync function findLatestFrameByPrefixProbing(modelConfig, prefix) {\n\t\t\tvar timestamps = buildTimestampCandidates(modelConfig).reverse();\n\n\t\t\tfor (var i = 0; i < timestamps.length; i += 1) {\n\t\t\t\tvar ts = timestamps[i];\n\t\t\t\tvar url = buildFrameUrl(modelConfig, prefix + ts + \".jpg\");\n\t\t\t\tif (await checkUrlExists(url)) {\n\t\t\t\t\treturn { timestamp: ts, url: url };\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn null;\n\t\t}\n\n\t\tasync function loadLatestTspFrames(modelConfig, paramConfig) {\n\t\t\tvar frames = [];\n\t\t\tvar files = await fetchDirectoryFiles(modelConfig.folder);\n\t\t\tvar latestNow = null;\n\t\t\tvar latestFuture = null;\n\n\t\t\tfiles.forEach(function(fileName) {\n\t\t\t\tvar nowTs = extractTimestampByPrefix(fileName, paramConfig.nowPrefix);\n\t\t\t\tif (nowTs && isTimestampInWindow(nowTs, modelConfig)) {\n\t\t\t\t\tif (!latestNow || nowTs > latestNow.timestamp) {\n\t\t\t\t\t\tlatestNow = {\n\t\t\t\t\t\t\ttimestamp: nowTs,\n\t\t\t\t\t\t\turl: buildFrameUrl(modelConfig, fileName)\n\t\t\t\t\t\t};\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tvar futureTs = extractTimestampByPrefix(fileName, paramConfig.futurePrefix);\n\t\t\t\tif (futureTs && isTimestampInWindow(futureTs, modelConfig)) {\n\t\t\t\t\tif (!latestFuture || futureTs > latestFuture.timestamp) {\n\t\t\t\t\t\tlatestFuture = {\n\t\t\t\t\t\t\ttimestamp: futureTs,\n\t\t\t\t\t\t\turl: buildFrameUrl(modelConfig, fileName)\n\t\t\t\t\t\t};\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\n\t\t\tif (!latestNow) {\n\t\t\t\tlatestNow = await findLatestFrameByPrefixProbing(modelConfig, paramConfig.nowPrefix);\n\t\t\t}\n\t\t\tif (!latestFuture) {\n\t\t\t\tlatestFuture = await findLatestFrameByPrefixProbing(modelConfig, paramConfig.futurePrefix);\n\t\t\t}\n\n\t\t\tif (latestNow) {\n\t\t\t\tframes.push({\n\t\t\t\t\ttimestamp: latestNow.timestamp,\n\t\t\t\t\turl: latestNow.url,\n\t\t\t\t\tstatic: false,\n\t\t\t\t\tdisplayLabel: \"Aktualny stan\"\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tif (latestFuture) {\n\t\t\t\tframes.push({\n\t\t\t\t\ttimestamp: latestFuture.timestamp,\n\t\t\t\t\turl: latestFuture.url,\n\t\t\t\t\tstatic: false,\n\t\t\t\t\tdisplayLabel: \"Prognoza\"\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tif (!frames.length) {\n\t\t\t\tif (paramConfig.staticNowFile) {\n\t\t\t\t\tvar nowStaticUrl = buildFrameUrl(modelConfig, paramConfig.staticNowFile);\n\t\t\t\t\tif (await checkUrlExists(nowStaticUrl)) {\n\t\t\t\t\t\tframes.push({\n\t\t\t\t\t\t\ttimestamp: null,\n\t\t\t\t\t\t\turl: nowStaticUrl,\n\t\t\t\t\t\t\tstatic: true,\n\t\t\t\t\t\t\tdisplayLabel: \"Aktualny stan\"\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (paramConfig.staticFutureFile) {\n\t\t\t\t\tvar futureStaticUrl = buildFrameUrl(modelConfig, paramConfig.staticFutureFile);\n\t\t\t\t\tif (await checkUrlExists(futureStaticUrl)) {\n\t\t\t\t\t\tframes.push({\n\t\t\t\t\t\t\ttimestamp: null,\n\t\t\t\t\t\t\turl: futureStaticUrl,\n\t\t\t\t\t\t\tstatic: true,\n\t\t\t\t\t\t\tdisplayLabel: \"Prognoza\"\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn frames;\n\t\t}\n\n\t\tasync function loadMergeWithGrsFrames(modelConfig, paramConfig) {\n\t\t\tvar nowMs = Date.now();\n\t\t\tvar byTimestamp = {};\n\t\t\tvar grsFiles = await fetchDirectoryFiles(GRS_FOLDER);\n\t\t\tvar mergeFiles = await fetchDirectoryFiles(modelConfig.folder);\n\t\t\tvar i;\n\n\t\t\tfor (i = 0; i < grsFiles.length; i += 1) {\n\t\t\t\tvar grsTs = extractTimestampByPrefix(grsFiles[i], GRS_RAIN_PREFIX);\n\t\t\t\tif (!grsTs || !isTimestampInWindow(grsTs, modelConfig)) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tvar grsMs = getTimestampMs(grsTs);\n\t\t\t\tif (grsMs !== null && grsMs <= nowMs) {\n\t\t\t\t\tbyTimestamp[grsTs] = {\n\t\t\t\t\t\ttimestamp: grsTs,\n\t\t\t\t\t\turl: buildFrameUrlForFolder(GRS_FOLDER, grsFiles[i]),\n\t\t\t\t\t\tstatic: false,\n\t\t\t\t\t\tsource: \"GRS\"\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfor (i = 0; i < mergeFiles.length; i += 1) {\n\t\t\t\tvar mergeTs = extractTimestampByPrefix(mergeFiles[i], paramConfig.filePrefix);\n\t\t\t\tif (!mergeTs || !isTimestampInWindow(mergeTs, modelConfig)) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tvar mergeMs = getTimestampMs(mergeTs);\n\t\t\t\tif (mergeMs === null) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tif (mergeMs > nowMs) {\n\t\t\t\t\tbyTimestamp[mergeTs] = {\n\t\t\t\t\t\ttimestamp: mergeTs,\n\t\t\t\t\t\turl: buildFrameUrl(modelConfig, mergeFiles[i]),\n\t\t\t\t\t\tstatic: false,\n\t\t\t\t\t\tsource: \"MERGE\"\n\t\t\t\t\t};\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tif (!byTimestamp[mergeTs]) {\n\t\t\t\t\tbyTimestamp[mergeTs] = {\n\t\t\t\t\t\ttimestamp: mergeTs,\n\t\t\t\t\t\turl: buildFrameUrl(modelConfig, mergeFiles[i]),\n\t\t\t\t\t\tstatic: false,\n\t\t\t\t\t\tsource: \"MERGE\"\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (!Object.keys(byTimestamp).length) {\n\t\t\t\tvar timestamps = buildTimestampCandidates(modelConfig);\n\t\t\t\tvar checks = timestamps.map(async function(ts) {\n\t\t\t\t\tvar tsMs = getTimestampMs(ts);\n\t\t\t\t\tif (tsMs === null) {\n\t\t\t\t\t\treturn null;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (tsMs <= nowMs) {\n\t\t\t\t\t\tvar grsUrl = buildFrameUrlForFolder(GRS_FOLDER, GRS_RAIN_PREFIX + ts + \".jpg\");\n\t\t\t\t\t\tif (await checkUrlExists(grsUrl)) {\n\t\t\t\t\t\t\treturn { timestamp: ts, url: grsUrl, static: false, source: \"GRS\" };\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tvar mergeBackfillUrl = buildFrameUrl(modelConfig, paramConfig.filePrefix + ts + \".jpg\");\n\t\t\t\t\t\tif (await checkUrlExists(mergeBackfillUrl)) {\n\t\t\t\t\t\t\treturn { timestamp: ts, url: mergeBackfillUrl, static: false, source: \"MERGE\" };\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\treturn null;\n\t\t\t\t\t}\n\n\t\t\t\t\tvar mergeUrl = buildFrameUrl(modelConfig, paramConfig.filePrefix + ts + \".jpg\");\n\t\t\t\t\tif (await checkUrlExists(mergeUrl)) {\n\t\t\t\t\t\treturn { timestamp: ts, url: mergeUrl, static: false, source: \"MERGE\" };\n\t\t\t\t\t}\n\t\t\t\t\treturn null;\n\t\t\t\t});\n\n\t\t\t\tvar found = await Promise.all(checks);\n\t\t\t\tfound.forEach(function(item) {\n\t\t\t\t\tif (item) {\n\t\t\t\t\t\tbyTimestamp[item.timestamp] = item;\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tvar frames = Object.keys(byTimestamp).map(function(ts) {\n\t\t\t\treturn byTimestamp[ts];\n\t\t\t});\n\n\t\t\treturn sortFramesAscending(frames);\n\t\t}\n\n\t\tfunction updateRefreshInfo() {\n\t\t\tvar infoEl = document.getElementById(\"ultraRefreshInfo\");\n\t\t\tvar autoText = isAutoRefreshModel(state.selectedModel) ? \"w\u0142\u0105czone\" : \"wy\u0142\u0105czone\";\n\t\t\tinfoEl.textContent = \"Ostatnie sprawdzenie: \" + formatLocalDateTime(state.lastRefreshAt) + \" | Auto-od\u015bwie\u017canie: \" + autoText;\n\t\t}\n\n\t\tfunction showLoader(show) {\n\t\t\tdocument.getElementById(\"ultraLoader\").style.display = show ? \"flex\" : \"none\";\n\t\t}\n\n\t\tfunction showStatus(message) {\n\t\t\tvar status = document.getElementById(\"ultraStatus\");\n\t\t\tif (!message) {\n\t\t\t\tstatus.style.display = \"none\";\n\t\t\t\tstatus.textContent = \"\";\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tstatus.style.display = \"block\";\n\t\t\tstatus.textContent = message;\n\t\t}\n\n\t\tfunction updateImageTitle() {\n\t\t\tvar titleEl = document.getElementById(\"ultraImageTitle\");\n\t\t\tvar modelConfig = getCurrentModelConfig();\n\t\t\tvar paramConfig = getCurrentParamConfig();\n\n\t\t\tif (!paramConfig) {\n\t\t\t\ttitleEl.textContent = \"Prognoza ultrakr\u00f3tkoterminowa\";\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (!state.frames.length) {\n\t\t\t\ttitleEl.textContent = modelConfig.displayName + \" | \" + paramConfig.label;\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tvar frame = state.frames[state.selectedFrameIndex];\n\t\t\tvar timeText = frame && frame.timestamp ? formatFrameVerboseLabel(frame.timestamp) : \"Najnowsza dost\u0119pna mapa\";\n\t\t\ttitleEl.textContent = modelConfig.displayName + \" | \" + paramConfig.label + \" | \" + timeText;\n\t\t}\n\n\t\tfunction renderModelButtons() {\n\t\t\tvar container = document.getElementById(\"ultraModels\");\n\t\t\tcontainer.innerHTML = \"\";\n\n\t\t\tmodelOrder.forEach(function(modelId) {\n\t\t\t\tvar btn = document.createElement(\"button\");\n\t\t\t\tbtn.type = \"button\";\n\t\t\t\tbtn.textContent = modelsConfig[modelId].displayName;\n\t\t\t\tbtn.dataset.value = modelId;\n\t\t\t\tif (modelId === state.selectedModel) {\n\t\t\t\t\tbtn.classList.add(\"active\");\n\t\t\t\t}\n\t\t\t\tbtn.addEventListener(\"click\", function() {\n\t\t\t\t\tif (state.selectedModel === modelId) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tstopAnimation();\n\t\t\t\t\tstate.selectedModel = modelId;\n\t\t\t\t\tupdateTimeLabelsVisibility();\n\t\t\t\t\tstate.selectedParam = modelsConfig[modelId].parameters[0].id;\n\t\t\t\t\tstate.selectedFrameIndex = 0;\n\t\t\t\t\trenderModelButtons();\n\t\t\t\t\trenderParamButtons();\n\t\t\t\t\tstartAutoRefreshTimer();\n\t\t\t\t\tupdateRefreshInfo();\n\t\t\t\t\trefreshFrames(\"model\");\n\t\t\t\t});\n\t\t\t\tcontainer.appendChild(btn);\n\t\t\t});\n\t\t}\n\n\t\tfunction renderParamButtons() {\n\t\t\tvar container = document.getElementById(\"ultraParamButtons\");\n\t\t\tvar modelConfig = getCurrentModelConfig();\n\t\t\tvar currentParam = getParamConfig(modelConfig, state.selectedParam);\n\n\t\t\tif (!currentParam && modelConfig.parameters.length) {\n\t\t\t\tstate.selectedParam = modelConfig.parameters[0].id;\n\t\t\t}\n\n\t\t\tcontainer.innerHTML = \"\";\n\t\t\tmodelConfig.parameters.forEach(function(param) {\n\t\t\t\tvar btn = document.createElement(\"button\");\n\t\t\t\tbtn.type = \"button\";\n\t\t\t\tbtn.dataset.value = param.id;\n\t\t\t\tbtn.textContent = param.label;\n\t\t\t\tif (param.id === state.selectedParam) {\n\t\t\t\t\tbtn.classList.add(\"active\");\n\t\t\t\t}\n\t\t\t\tbtn.addEventListener(\"click\", function() {\n\t\t\t\t\thandleParamChange(param.id);\n\t\t\t\t});\n\t\t\t\tcontainer.appendChild(btn);\n\t\t\t});\n\t\t}\n\n\t\tfunction renderFrameButtons() {\n\t\t\tupdateTimeLabelsVisibility();\n\t\t\tvar container = document.getElementById(\"ultraFrames\");\n\t\t\tcontainer.innerHTML = \"\";\n\n\t\t\tif (!state.frames.length) {\n\t\t\t\tvar noData = document.createElement(\"div\");\n\t\t\t\tnoData.style.fontSize = \"12px\";\n\t\t\t\tnoData.style.color = \"#777\";\n\t\t\t\tnoData.textContent = \"Brak dost\u0119pnych ramek czasowych.\";\n\t\t\t\tcontainer.appendChild(noData);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tvar lastDateLabel = \"\";\n\t\t\tvar showDateDivider = state.selectedModel !== \"TSP\";\n\n\t\t\tstate.frames.forEach(function(frame, index) {\n\t\t\t\tif (showDateDivider && frame.timestamp) {\n\t\t\t\t\tvar dateLabel = formatFrameDateLabel(frame.timestamp);\n\t\t\t\t\tif (index === 0 || dateLabel !== lastDateLabel) {\n\t\t\t\t\t\tvar divider = document.createElement(\"div\");\n\t\t\t\t\t\tdivider.className = \"frame-date-divider\";\n\t\t\t\t\t\tdivider.textContent = dateLabel;\n\t\t\t\t\t\tcontainer.appendChild(divider);\n\t\t\t\t\t\tlastDateLabel = dateLabel;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tvar btn = document.createElement(\"button\");\n\t\t\t\tbtn.type = \"button\";\n\t\t\t\tbtn.dataset.index = String(index);\n\t\t\t\tbtn.textContent = getFrameDisplayLabel(frame);\n\t\t\t\tbtn.title = frame.timestamp\n\t\t\t\t\t? (getFrameDisplayLabel(frame) + \" | \" + formatFrameVerboseLabel(frame.timestamp))\n\t\t\t\t\t: \"Najnowsza dost\u0119pna mapa\";\n\n\t\t\t\tvar loadState = getFrameLoadState(frame);\n\t\t\t\tif (loadState === \"pending\") {\n\t\t\t\t\tbtn.classList.add(\"preload-pending\");\n\t\t\t\t}\n\t\t\t\tif (loadState === \"loading\") {\n\t\t\t\t\tbtn.classList.add(\"preload-pending\");\n\t\t\t\t\tbtn.classList.add(\"preload-loading\");\n\t\t\t\t}\n\t\t\t\tif (loadState === \"loaded\") {\n\t\t\t\t\tbtn.classList.add(\"preload-loaded\");\n\t\t\t\t}\n\t\t\t\tif (isMergeGrsFrame(frame)) {\n\t\t\t\t\tbtn.classList.add(\"merge-grs-range\");\n\t\t\t\t}\n\t\t\t\tif (isMergeForecastFrame(frame) && loadState === \"loaded\") {\n\t\t\t\t\tbtn.classList.add(\"merge-loaded\");\n\t\t\t\t}\n\n\t\t\t\tif (index === state.selectedFrameIndex) {\n\t\t\t\t\tbtn.classList.add(\"active\");\n\t\t\t\t}\n\t\t\t\tbtn.addEventListener(\"click\", function() {\n\t\t\t\t\tstopAnimation();\n\t\t\t\t\tselectFrameByIndex(index, true);\n\t\t\t\t});\n\t\t\t\tcontainer.appendChild(btn);\n\t\t\t});\n\t\t}\n\n\t\tfunction renderFrameSlider() {\n\t\t\tupdateTimeLabelsVisibility();\n\t\t\tvar sliderContainer = document.getElementById(\"ultraSliderContainer\");\n\t\t\tvar slider = document.getElementById(\"ultraFrameSlider\");\n\t\t\tvar valueEl = document.getElementById(\"ultraFrameValue\");\n\t\t\tvar labels = document.getElementById(\"ultraFrameLabels\");\n\t\t\tvar dateCutoffs = document.getElementById(\"ultraDateCutoffs\");\n\n\t\t\tif (!state.frames.length) {\n\t\t\t\tsliderContainer.style.display = \"none\";\n\t\t\t\tvalueEl.textContent = \"--\";\n\t\t\t\tlabels.innerHTML = \"\";\n\t\t\t\tdateCutoffs.innerHTML = \"\";\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tsliderContainer.style.display = \"block\";\n\t\t\tslider.min = \"0\";\n\t\t\tslider.max = String(state.frames.length - 1);\n\t\t\tslider.value = String(state.selectedFrameIndex);\n\n\t\t\tvar activeFrame = state.frames[state.selectedFrameIndex];\n\t\t\tvalueEl.textContent = getFrameDisplayLabel(activeFrame);\n\n\t\t\tlabels.innerHTML = \"\";\n\t\t\tvar i;\n\t\t\tvar labelIndexes = [];\n\t\t\tvar lastFullHourIndex = -1;\n\n\t\t\tif (state.selectedModel === \"TSP\") {\n\t\t\t\tfor (i = 0; i < state.frames.length; i += 1) {\n\t\t\t\t\tlabelIndexes.push(i);\n\t\t\t\t}\n\t\t\t} else {\n\n\t\t\t\tfor (i = 0; i < state.frames.length; i += 1) {\n\t\t\t\t\tif (!state.frames[i].timestamp) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\tvar dateObj = parseTimestampUTC(state.frames[i].timestamp);\n\t\t\t\t\tif (!dateObj) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\tvar minute = dateObj.getUTCMinutes();\n\t\t\t\t\tif (minute === 0 || minute === 30) {\n\t\t\t\t\t\tlabelIndexes.push(i);\n\t\t\t\t\t\tif (minute === 0) {\n\t\t\t\t\t\t\tlastFullHourIndex = i;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (state.selectedModel !== \"SPT\" && state.selectedModel !== \"TSP\" && lastFullHourIndex !== -1) {\n\t\t\t\tlabelIndexes = labelIndexes.filter(function(idx) {\n\t\t\t\t\treturn idx <= lastFullHourIndex;\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tif (!labelIndexes.length) {\n\t\t\t\tfor (i = 0; i < state.frames.length; i += 1) {\n\t\t\t\t\tif (state.frames[i].timestamp) {\n\t\t\t\t\t\tlabelIndexes.push(i);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tvar sliderWidth = Math.max(1, slider.clientWidth || slider.offsetWidth || 1);\n\t\t\tvar minSpacingPx = state.selectedModel === \"TSP\" ? 120 : 70;\n\t\t\tvar filteredIndexes = [];\n\n\t\t\tlabelIndexes.forEach(function(idx) {\n\t\t\t\tif (!filteredIndexes.length) {\n\t\t\t\t\tfilteredIndexes.push(idx);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tvar prevIdx = filteredIndexes[filteredIndexes.length - 1];\n\t\t\t\tvar prevPx = (prevIdx \/ Math.max(1, state.frames.length - 1)) * sliderWidth;\n\t\t\t\tvar currentPx = (idx \/ Math.max(1, state.frames.length - 1)) * sliderWidth;\n\n\t\t\t\tif (currentPx - prevPx >= minSpacingPx) {\n\t\t\t\t\tfilteredIndexes.push(idx);\n\t\t\t\t}\n\t\t\t});\n\n\t\t\tif (labelIndexes.length) {\n\t\t\t\tvar lastCandidate = labelIndexes[labelIndexes.length - 1];\n\t\t\t\tvar lastFiltered = filteredIndexes[filteredIndexes.length - 1];\n\t\t\t\tif (lastFiltered !== lastCandidate) {\n\t\t\t\t\tif (filteredIndexes.length > 1) {\n\t\t\t\t\t\tvar beforeLast = filteredIndexes[filteredIndexes.length - 2];\n\t\t\t\t\t\tvar beforeLastPx = (beforeLast \/ Math.max(1, state.frames.length - 1)) * sliderWidth;\n\t\t\t\t\t\tvar lastCandidatePx = (lastCandidate \/ Math.max(1, state.frames.length - 1)) * sliderWidth;\n\t\t\t\t\t\tif (lastCandidatePx - beforeLastPx < minSpacingPx) {\n\t\t\t\t\t\t\tfilteredIndexes.pop();\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tfilteredIndexes.push(lastCandidate);\n\t\t\t\t}\n\t\t\t}\n\n\n\t\t\tfilteredIndexes.forEach(function(idx) {\n\t\t\t\tvar label = document.createElement(\"div\");\n\t\t\t\tlabel.className = \"hour-label-marker\";\n\t\t\t\tvar frame = state.frames[idx];\n\t\t\t\tlabel.textContent = getFrameDisplayLabel(frame);\n\n\t\t\t\tvar loadState = getFrameLoadState(frame);\n\t\t\t\tif (loadState === \"pending\") {\n\t\t\t\t\tlabel.classList.add(\"preload-pending\");\n\t\t\t\t}\n\t\t\t\tif (loadState === \"loading\") {\n\t\t\t\t\tlabel.classList.add(\"preload-pending\");\n\t\t\t\t\tlabel.classList.add(\"preload-loading\");\n\t\t\t\t}\n\t\t\t\tif (loadState === \"loaded\") {\n\t\t\t\t\tlabel.classList.add(\"preload-loaded\");\n\t\t\t\t}\n\t\t\t\tif (isMergeGrsFrame(frame)) {\n\t\t\t\t\tlabel.classList.add(\"merge-grs-range\");\n\t\t\t\t}\n\t\t\t\tif (isMergeForecastFrame(frame) && loadState === \"loaded\") {\n\t\t\t\t\tlabel.classList.add(\"merge-loaded\");\n\t\t\t\t}\n\n\t\t\t\tif (idx === 0) {\n\t\t\t\t\tlabel.classList.add(\"first\");\n\t\t\t\t} else if (idx === state.frames.length - 1) {\n\t\t\t\t\tlabel.classList.add(\"last\");\n\t\t\t\t\tlabel.style.left = ((idx \/ (state.frames.length - 1)) * 100) + \"%\";\n\t\t\t\t} else {\n\t\t\t\t\tlabel.style.left = ((idx \/ (state.frames.length - 1)) * 100) + \"%\";\n\t\t\t\t}\n\n\t\t\t\tlabels.appendChild(label);\n\t\t\t});\n\n\t\t\tdateCutoffs.innerHTML = \"\";\n\t\t\tif (state.selectedModel === \"TSP\") {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tvar lastDateLabel = \"\";\n\t\t\tfor (i = 0; i < state.frames.length; i += 1) {\n\t\t\t\tif (!state.frames[i].timestamp) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tvar dateLabel = formatFrameDateLabel(state.frames[i].timestamp);\n\t\t\t\tif (i === 0 || dateLabel !== lastDateLabel) {\n\t\t\t\t\tvar marker = document.createElement(\"div\");\n\t\t\t\t\tmarker.className = \"date-cutoff-marker\" + (i === 0 ? \" first\" : \"\");\n\t\t\t\t\tmarker.textContent = dateLabel;\n\t\t\t\t\tif (state.frames.length === 1) {\n\t\t\t\t\t\tmarker.style.left = \"0%\";\n\t\t\t\t\t} else if (i > 0) {\n\t\t\t\t\t\tmarker.style.left = ((i \/ (state.frames.length - 1)) * 100) + \"%\";\n\t\t\t\t\t}\n\t\t\t\t\tdateCutoffs.appendChild(marker);\n\t\t\t\t\tlastDateLabel = dateLabel;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tfunction renderFrameGradient() {\n\t\t\tvar slider = document.getElementById(\"ultraFrameSlider\");\n\t\t\tif (!state.frames.length) {\n\t\t\t\tslider.style.background = \"linear-gradient(to right, #e0e0e0 0%, #e0e0e0 100%)\";\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tvar denominator = Math.max(1, state.frames.length - 1);\n\t\t\tvar percent = (state.selectedFrameIndex \/ denominator) * 100;\n\n\t\t\tif (state.selectedModel === \"MERGE\" && state.selectedParam === \"MERGE\") {\n\t\t\t\tvar lastGrsIndex = -1;\n\t\t\t\tvar firstMergeIndex = -1;\n\t\t\t\tfor (var i = 0; i < state.frames.length; i += 1) {\n\t\t\t\t\tif (state.frames[i].source === \"GRS\") {\n\t\t\t\t\t\tlastGrsIndex = i;\n\t\t\t\t\t}\n\t\t\t\t\tif (firstMergeIndex === -1 && state.frames[i].source === \"MERGE\") {\n\t\t\t\t\t\tfirstMergeIndex = i;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tvar mergeLoadedEnd = -1;\n\t\t\t\tif (firstMergeIndex !== -1) {\n\t\t\t\t\tfor (i = firstMergeIndex; i < state.frames.length; i += 1) {\n\t\t\t\t\t\tif (state.frames[i].source !== \"MERGE\") {\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (getFrameLoadState(state.frames[i]) === \"loaded\") {\n\t\t\t\t\t\t\tmergeLoadedEnd = i;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tvar grsEndPercent = lastGrsIndex >= 0 ? (lastGrsIndex \/ denominator) * 100 : 0;\n\t\t\t\tvar mergeStartPercent = firstMergeIndex >= 0 ? (firstMergeIndex \/ denominator) * 100 : grsEndPercent;\n\t\t\t\tvar mergeLoadedPercent = mergeLoadedEnd >= 0 ? (mergeLoadedEnd \/ denominator) * 100 : mergeStartPercent;\n\n\t\t\t\tif (firstMergeIndex === -1) {\n\t\t\t\t\tslider.style.background = \"linear-gradient(to right, #d4e7ea 0%, #d4e7ea \" + grsEndPercent + \"%, #e0e0e0 \" + grsEndPercent + \"%, #e0e0e0 100%)\";\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tslider.style.background = \"linear-gradient(to right, #d4e7ea 0%, #d4e7ea \" + grsEndPercent + \"%, #e0e0e0 \" + grsEndPercent + \"%, #e0e0e0 \" + mergeStartPercent + \"%, #c7c7c7 \" + mergeStartPercent + \"%, #c7c7c7 \" + mergeLoadedPercent + \"%, #e0e0e0 \" + mergeLoadedPercent + \"%, #e0e0e0 100%)\";\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tvar loadedEndIndex = -1;\n\t\t\tfor (var j = 0; j < state.frames.length; j += 1) {\n\t\t\t\tif (getFrameLoadState(state.frames[j]) === \"loaded\") {\n\t\t\t\t\tloadedEndIndex = j;\n\t\t\t\t} else {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (loadedEndIndex < 0) {\n\t\t\t\tslider.style.background = \"linear-gradient(to right, #e0e0e0 0%, #e0e0e0 100%)\";\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tvar loadedPercent = (loadedEndIndex \/ denominator) * 100;\n\t\t\tslider.style.background = \"linear-gradient(to right, #c7c7c7 0%, #c7c7c7 \" + loadedPercent + \"%, #e0e0e0 \" + loadedPercent + \"%, #e0e0e0 100%)\";\n\t\t}\n\n\t\tfunction attachImage(frame) {\n\t\t\treturn new Promise(function(resolve, reject) {\n\t\t\t\tvar img = new Image();\n\t\t\t\tvar requestUrl = getFrameRequestUrl(frame);\n\t\t\t\timg.onload = function() {\n\t\t\t\t\tresolve({\n\t\t\t\t\t\timg: img,\n\t\t\t\t\t\tsrc: requestUrl\n\t\t\t\t\t});\n\t\t\t\t};\n\t\t\t\timg.onerror = function() {\n\t\t\t\t\treject(new Error(\"Nie uda\u0142o si\u0119 wczyta\u0107 obrazu\"));\n\t\t\t\t};\n\t\t\t\timg.src = requestUrl;\n\t\t\t});\n\t\t}\n\n\t\tfunction ensureFrameImage(frame) {\n\t\t\tvar key = getFrameCacheKey(frame);\n\n\t\t\tif (state.imageCache[key]) {\n\t\t\t\treturn Promise.resolve(state.imageCache[key]);\n\t\t\t}\n\n\t\t\tif (state.imageLoadPromises[key]) {\n\t\t\t\treturn state.imageLoadPromises[key];\n\t\t\t}\n\n\t\t\tstate.imageLoadPromises[key] = attachImage(frame)\n\t\t\t\t.then(function(loaded) {\n\t\t\t\t\tstate.imageCache[key] = loaded.img;\n\t\t\t\t\tdelete state.imageLoadPromises[key];\n\t\t\t\t\trenderFrameButtons();\n\t\t\t\t\trenderFrameSlider();\n\t\t\t\t\trenderFrameGradient();\n\t\t\t\t\treturn loaded.img;\n\t\t\t\t})\n\t\t\t\t.catch(function(error) {\n\t\t\t\t\tdelete state.imageLoadPromises[key];\n\t\t\t\t\trenderFrameButtons();\n\t\t\t\t\trenderFrameSlider();\n\t\t\t\t\trenderFrameGradient();\n\t\t\t\t\tthrow error;\n\t\t\t\t});\n\n\t\t\treturn state.imageLoadPromises[key];\n\t\t}\n\n\t\tfunction buildPreloadOrder(frames, centerIndex) {\n\t\t\tvar order = [];\n\t\t\tvar added = {};\n\n\t\t\tfunction addFrame(index) {\n\t\t\t\tif (index < 0 || index >= frames.length || added[index]) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tadded[index] = true;\n\t\t\t\tvar frame = frames[index];\n\t\t\t\tvar key = getFrameCacheKey(frame);\n\n\t\t\t\tif (!state.imageCache[key] && !state.imageLoadPromises[key]) {\n\t\t\t\t\torder.push(frame);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\taddFrame(centerIndex);\n\n\t\t\tfor (var offset = 1; offset < frames.length; offset += 1) {\n\t\t\t\taddFrame(centerIndex + offset);\n\t\t\t\taddFrame(centerIndex - offset);\n\t\t\t}\n\n\t\t\treturn order;\n\t\t}\n\n\t\tasync function processPreloadQueue(generation) {\n\t\t\tif (state.isPreloading) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tstate.isPreloading = true;\n\n\t\t\ttry {\n\t\t\t\twhile (generation === state.preloadGeneration && state.preloadQueue.length > 0) {\n\t\t\t\t\tvar batch = state.preloadQueue.splice(0, PRELOAD_BATCH_SIZE);\n\n\t\t\t\t\tawait Promise.all(batch.map(function(frame) {\n\t\t\t\t\t\treturn ensureFrameImage(frame).catch(function() {\n\t\t\t\t\t\t\treturn null;\n\t\t\t\t\t\t});\n\t\t\t\t\t}));\n\t\t\t\t}\n\t\t\t} finally {\n\t\t\t\tstate.isPreloading = false;\n\n\t\t\t\tif (generation !== state.preloadGeneration && state.preloadQueue.length > 0) {\n\t\t\t\t\tprocessPreloadQueue(state.preloadGeneration);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tfunction scheduleFramePreload() {\n\t\t\tif (!state.frames.length) {\n\t\t\t\tstate.preloadQueue = [];\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tstate.preloadGeneration += 1;\n\t\t\tvar generation = state.preloadGeneration;\n\t\t\tstate.preloadQueue = buildPreloadOrder(state.frames, state.selectedFrameIndex);\n\n\t\t\tif (!state.isPreloading) {\n\t\t\t\tprocessPreloadQueue(generation);\n\t\t\t}\n\t\t}\n\n\t\tfunction openLightbox(src) {\n\t\t\tvar lightbox = document.getElementById(\"ultraLightbox\");\n\t\t\tvar lightboxImg = document.getElementById(\"ultraLightboxImg\");\n\t\t\tlightboxImg.src = src;\n\t\t\tlightbox.classList.add(\"active\");\n\t\t}\n\n\t\tfunction closeLightbox() {\n\t\t\tvar lightbox = document.getElementById(\"ultraLightbox\");\n\t\t\tvar lightboxImg = document.getElementById(\"ultraLightboxImg\");\n\t\t\tlightbox.classList.remove(\"active\");\n\t\t\tlightboxImg.src = \"\";\n\t\t}\n\n\t\tasync function showSelectedFrame() {\n\t\t\tvar container = document.getElementById(\"ultraImages\");\n\t\t\tcontainer.innerHTML = \"\";\n\n\t\t\tif (!state.frames.length) {\n\t\t\t\tupdateImageTitle();\n\t\t\t\tshowStatus(\"Brak map dla wybranego modelu i parametru.\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tvar frame = state.frames[state.selectedFrameIndex];\n\t\t\tvar imageToken = ++state.imageToken;\n\t\t\tvar cacheKey = getFrameCacheKey(frame);\n\t\t\tvar cachedImage = state.imageCache[cacheKey];\n\n\t\t\tif (cachedImage) {\n\t\t\t\tvar cachedDisplay = new Image();\n\t\t\t\tcachedDisplay.src = cachedImage.currentSrc || cachedImage.src;\n\t\t\t\tcachedDisplay.alt = \"Prognoza ultrakr\u00f3tkoterminowa\";\n\t\t\t\tcachedDisplay.addEventListener(\"click\", function() {\n\t\t\t\t\topenLightbox(cachedDisplay.currentSrc || cachedDisplay.src);\n\t\t\t\t});\n\t\t\t\tcontainer.appendChild(cachedDisplay);\n\t\t\t\tshowLoader(false);\n\t\t\t\tshowStatus(\"\");\n\t\t\t\tupdateImageTitle();\n\t\t\t\trenderFrameGradient();\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tshowLoader(true);\n\t\t\tshowStatus(\"\");\n\n\t\t\ttry {\n\t\t\t\tvar loaded = await ensureFrameImage(frame);\n\t\t\t\tif (imageToken !== state.imageToken) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tvar displayImg = new Image();\n\t\t\t\tdisplayImg.src = loaded.currentSrc || loaded.src;\n\t\t\t\tdisplayImg.alt = \"Prognoza ultrakr\u00f3tkoterminowa\";\n\t\t\t\tdisplayImg.addEventListener(\"click\", function() {\n\t\t\t\t\topenLightbox(displayImg.currentSrc || displayImg.src);\n\t\t\t\t});\n\t\t\t\tcontainer.innerHTML = \"\";\n\t\t\t\tcontainer.appendChild(displayImg);\n\t\t\t} catch (error) {\n\t\t\t\tif (imageToken !== state.imageToken) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tcontainer.innerHTML = \"\";\n\t\t\t\tshowStatus(\"Nie uda\u0142o si\u0119 wczyta\u0107 wybranej mapy.\");\n\t\t\t} finally {\n\t\t\t\tif (imageToken === state.imageToken) {\n\t\t\t\t\tshowLoader(false);\n\t\t\t\t\tupdateImageTitle();\n\t\t\t\t\trenderFrameGradient();\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tfunction findCurrentFrameIndex(frames) {\n\t\t\tif (!frames.length) {\n\t\t\t\treturn 0;\n\t\t\t}\n\n\t\t\tvar nowMs = Date.now();\n\t\t\tvar bestPastIndex = -1;\n\t\t\tvar bestPastTs = Number.NEGATIVE_INFINITY;\n\t\t\tvar nearestFutureIndex = -1;\n\t\t\tvar nearestFutureTs = Number.POSITIVE_INFINITY;\n\t\t\tvar i;\n\n\t\t\tfor (i = 0; i < frames.length; i += 1) {\n\t\t\t\tif (!frames[i].timestamp) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tvar frameTs = getTimestampMs(frames[i].timestamp);\n\t\t\t\tif (frameTs === null) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tif (frameTs <= nowMs && frameTs > bestPastTs) {\n\t\t\t\t\tbestPastTs = frameTs;\n\t\t\t\t\tbestPastIndex = i;\n\t\t\t\t}\n\n\t\t\t\tif (frameTs > nowMs && frameTs < nearestFutureTs) {\n\t\t\t\t\tnearestFutureTs = frameTs;\n\t\t\t\t\tnearestFutureIndex = i;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (bestPastIndex !== -1) {\n\t\t\t\treturn bestPastIndex;\n\t\t\t}\n\n\t\t\tif (nearestFutureIndex !== -1) {\n\t\t\t\treturn nearestFutureIndex;\n\t\t\t}\n\n\t\t\treturn 0;\n\t\t}\n\n\t\tfunction selectFrameByIndex(index, shouldDisplay) {\n\t\t\tif (!state.frames.length) {\n\t\t\t\tstate.selectedFrameIndex = 0;\n\t\t\t\trenderFrameButtons();\n\t\t\t\trenderFrameSlider();\n\t\t\t\trenderFrameGradient();\n\t\t\t\tstate.preloadQueue = [];\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tvar safeIndex = Math.max(0, Math.min(index, state.frames.length - 1));\n\t\t\tstate.selectedFrameIndex = safeIndex;\n\t\t\trenderFrameButtons();\n\t\t\trenderFrameSlider();\n\t\t\trenderFrameGradient();\n\t\t\tscheduleFramePreload();\n\n\t\t\tif (shouldDisplay) {\n\t\t\t\tshowSelectedFrame();\n\t\t\t}\n\t\t}\n\n\t\tfunction startAutoRefreshTimer() {\n\t\t\tif (state.autoRefreshTimer) {\n\t\t\t\tclearInterval(state.autoRefreshTimer);\n\t\t\t\tstate.autoRefreshTimer = null;\n\t\t\t}\n\n\t\t\tif (!isAutoRefreshModel(state.selectedModel)) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tstate.autoRefreshTimer = setInterval(function() {\n\t\t\t\trefreshFrames(\"auto\");\n\t\t\t}, AUTO_REFRESH_INTERVAL_MS);\n\t\t}\n\n\t\tfunction stopAnimation() {\n\t\t\tif (state.animationTimer) {\n\t\t\t\tclearInterval(state.animationTimer);\n\t\t\t\tstate.animationTimer = null;\n\t\t\t}\n\t\t\tstate.isAnimating = false;\n\t\t\tvar animateBtn = document.getElementById(\"ultraAnimateBtn\");\n\t\t\tanimateBtn.classList.remove(\"active\");\n\t\t\tanimateBtn.textContent = \"\u25b6\";\n\t\t\tanimateBtn.title = \"Animuj godziny\";\n\t\t}\n\n\t\tfunction toggleAnimation() {\n\t\t\tif (state.isAnimating) {\n\t\t\t\tstopAnimation();\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (state.frames.length < 2) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tstate.isAnimating = true;\n\t\t\tvar animateBtn = document.getElementById(\"ultraAnimateBtn\");\n\t\t\tanimateBtn.classList.add(\"active\");\n\t\t\tanimateBtn.textContent = \"\u23f8\";\n\t\t\tanimateBtn.title = \"Zatrzymaj animacj\u0119\";\n\n\t\t\tstate.animationTimer = setInterval(function() {\n\t\t\t\tif (!state.frames.length) {\n\t\t\t\t\tstopAnimation();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tvar nextIndex = state.selectedFrameIndex + 1;\n\t\t\t\tif (nextIndex >= state.frames.length) {\n\t\t\t\t\tstopAnimation();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tselectFrameByIndex(nextIndex, true);\n\t\t\t}, 800);\n\t\t}\n\n\t\tasync function refreshFrames(reason) {\n\t\t\tif (state.isRefreshing) {\n\t\t\t\tstate.pendingRefresh = true;\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tstate.cacheVersion += 1;\n\t\t\tstate.imageCache = {};\n\t\t\tstate.imageLoadPromises = {};\n\t\t\tstate.preloadQueue = [];\n\t\t\tstate.preloadGeneration += 1;\n\t\t\tstate.imageToken += 1;\n\n\t\t\tstate.isRefreshing = true;\n\t\t\tdocument.getElementById(\"ultraImages\").innerHTML = \"\";\n\t\t\tshowLoader(true);\n\t\t\tshowStatus(\"\");\n\n\t\t\tvar modelConfig = getCurrentModelConfig();\n\t\t\tvar paramConfig = getCurrentParamConfig();\n\n\t\t\tif (!paramConfig) {\n\t\t\t\tstate.isRefreshing = false;\n\t\t\t\tshowLoader(false);\n\t\t\t\tshowStatus(\"Brak parametr\u00f3w dla wybranego modelu.\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tvar selectedFrameBefore = state.frames[state.selectedFrameIndex] || null;\n\t\t\tvar previousTimestamp = selectedFrameBefore ? selectedFrameBefore.timestamp : null;\n\t\t\tvar refreshToken = ++state.refreshToken;\n\t\t\tvar forceCurrentSelection = reason === \"init\" || reason === \"model\" || reason === \"keyboard-model\" || reason === \"param\";\n\n\t\t\ttry {\n\t\t\t\tvar frames = await loadFramesForSelection(modelConfig, paramConfig);\n\n\t\t\t\tif (refreshToken !== state.refreshToken) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tstate.frames = frames;\n\n\t\t\t\tif (!frames.length) {\n\t\t\t\t\tstate.selectedFrameIndex = 0;\n\t\t\t\t\trenderFrameButtons();\n\t\t\t\t\trenderFrameSlider();\n\t\t\t\t\tshowLoader(false);\n\t\t\t\t\tshowStatus(\"Brak dost\u0119pnych map dla wybranego zestawu.\");\n\t\t\t\t\tupdateImageTitle();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (previousTimestamp && !forceCurrentSelection) {\n\t\t\t\t\tvar matchedIndex = -1;\n\t\t\t\t\tvar i;\n\t\t\t\t\tfor (i = 0; i < frames.length; i += 1) {\n\t\t\t\t\t\tif (frames[i].timestamp === previousTimestamp) {\n\t\t\t\t\t\t\tmatchedIndex = i;\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif (matchedIndex !== -1) {\n\t\t\t\t\t\tstate.selectedFrameIndex = matchedIndex;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tstate.selectedFrameIndex = findCurrentFrameIndex(frames);\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tstate.selectedFrameIndex = findCurrentFrameIndex(frames);\n\t\t\t\t}\n\n\t\t\t\trenderFrameButtons();\n\t\t\t\trenderFrameSlider();\n\t\t\t\trenderFrameGradient();\n\t\t\t\tscheduleFramePreload();\n\t\t\t\tawait showSelectedFrame();\n\t\t\t} catch (error) {\n\t\t\t\tshowStatus(\"Wyst\u0105pi\u0142 b\u0142\u0105d podczas od\u015bwie\u017cania danych.\");\n\t\t\t} finally {\n\t\t\t\tstate.lastRefreshAt = new Date();\n\t\t\t\tupdateRefreshInfo();\n\t\t\t\tshowLoader(false);\n\t\t\t\tstate.isRefreshing = false;\n\n\t\t\t\tif (state.pendingRefresh) {\n\t\t\t\t\tstate.pendingRefresh = false;\n\t\t\t\t\trefreshFrames(reason || \"pending\");\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tfunction handleParamChange(paramId) {\n\t\t\tif (state.selectedParam === paramId) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tstopAnimation();\n\t\t\tstate.selectedParam = paramId;\n\t\t\tstate.selectedFrameIndex = 0;\n\t\t\trenderParamButtons();\n\t\t\trefreshFrames(\"param\");\n\t\t}\n\n\t\tfunction setupEvents() {\n\t\t\tvar slider = document.getElementById(\"ultraFrameSlider\");\n\t\t\tvar animateBtn = document.getElementById(\"ultraAnimateBtn\");\n\t\t\tvar latestBtn = document.getElementById(\"ultraLatestBtn\");\n\n\t\t\tslider.addEventListener(\"input\", function(event) {\n\t\t\t\tstopAnimation();\n\t\t\t\tvar index = parseInt(event.target.value, 10) || 0;\n\t\t\t\tselectFrameByIndex(index, true);\n\t\t\t});\n\n\t\t\tanimateBtn.addEventListener(\"click\", function() {\n\t\t\t\ttoggleAnimation();\n\t\t\t});\n\n\t\t\tlatestBtn.addEventListener(\"click\", function() {\n\t\t\t\tstopAnimation();\n\t\t\t\tif (!state.frames.length) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tvar latestIndex = findCurrentFrameIndex(state.frames);\n\t\t\t\tselectFrameByIndex(latestIndex, true);\n\t\t\t});\n\n\t\t\tdocument.getElementById(\"ultraLightbox\").addEventListener(\"click\", function(event) {\n\t\t\t\tif (event.target.id === \"ultraLightbox\" || event.target.id === \"ultraLightboxClose\") {\n\t\t\t\t\tcloseLightbox();\n\t\t\t\t}\n\t\t\t});\n\n\t\t\tdocument.addEventListener(\"keydown\", function(event) {\n\t\t\t\tvar tag = event.target && event.target.tagName ? event.target.tagName.toUpperCase() : \"\";\n\t\t\t\tif (tag === \"INPUT\" || tag === \"TEXTAREA\" || tag === \"SELECT\") {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (event.key === \"Escape\") {\n\t\t\t\t\tcloseLightbox();\n\t\t\t\t\tstopAnimation();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (event.key >= \"1\" && event.key <= \"4\") {\n\t\t\t\t\tvar index = parseInt(event.key, 10) - 1;\n\t\t\t\t\tif (index < modelOrder.length) {\n\t\t\t\t\t\tvar targetModel = modelOrder[index];\n\t\t\t\t\t\tif (targetModel !== state.selectedModel) {\n\t\t\t\t\t\t\tstopAnimation();\n\t\t\t\t\t\t\tstate.selectedModel = targetModel;\n\t\t\t\t\t\t\tupdateTimeLabelsVisibility();\n\t\t\t\t\t\t\tstate.selectedParam = modelsConfig[targetModel].parameters[0].id;\n\t\t\t\t\t\t\tstate.selectedFrameIndex = 0;\n\t\t\t\t\t\t\trenderModelButtons();\n\t\t\t\t\t\t\trenderParamButtons();\n\t\t\t\t\t\t\tstartAutoRefreshTimer();\n\t\t\t\t\t\t\tupdateRefreshInfo();\n\t\t\t\t\t\t\trefreshFrames(\"keyboard-model\");\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (event.key === \"ArrowLeft\" || event.key === \"ArrowRight\") {\n\t\t\t\t\tif (!state.frames.length) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\tstopAnimation();\n\t\t\t\t\tvar shift = event.key === \"ArrowLeft\" ? -1 : 1;\n\t\t\t\t\tvar next = state.selectedFrameIndex + shift;\n\t\t\t\t\tif (next < 0 || next >= state.frames.length) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tselectFrameByIndex(next, true);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (event.key === \"ArrowUp\" || event.key === \"ArrowDown\") {\n\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\tvar currentModel = getCurrentModelConfig();\n\t\t\t\t\tvar params = currentModel.parameters;\n\t\t\t\t\tvar currentIdx = 0;\n\t\t\t\t\tvar i;\n\n\t\t\t\t\tfor (i = 0; i < params.length; i += 1) {\n\t\t\t\t\t\tif (params[i].id === state.selectedParam) {\n\t\t\t\t\t\t\tcurrentIdx = i;\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tvar direction = event.key === \"ArrowUp\" ? -1 : 1;\n\t\t\t\t\tvar nextIdx = currentIdx + direction;\n\t\t\t\t\tif (nextIdx < 0) {\n\t\t\t\t\t\tnextIdx = params.length - 1;\n\t\t\t\t\t}\n\t\t\t\t\tif (nextIdx >= params.length) {\n\t\t\t\t\t\tnextIdx = 0;\n\t\t\t\t\t}\n\n\t\t\t\t\thandleParamChange(params[nextIdx].id);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (event.key === \"a\" || event.key === \"A\") {\n\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\ttoggleAnimation();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (event.key === \"r\" || event.key === \"R\") {\n\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\trefreshFrames(\"keyboard-refresh\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (event.key === \"n\" || event.key === \"N\") {\n\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\tif (!state.frames.length) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tstopAnimation();\n\t\t\t\t\tselectFrameByIndex(findCurrentFrameIndex(state.frames), true);\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\n\t\tfunction init() {\n\t\t\trenderModelButtons();\n\t\t\trenderParamButtons();\n\t\t\tsetupEvents();\n\t\t\tupdateRefreshInfo();\n\t\t\tstartAutoRefreshTimer();\n\t\t\trefreshFrames(\"init\");\n\t\t}\n\n\t\tif (document.readyState === \"loading\") {\n\t\t\tdocument.addEventListener(\"DOMContentLoaded\", init);\n\t\t} else {\n\t\t\tinit();\n\t\t}\n\t}());\nconsole.log(\"Autor Bart\u0142omiej Sobczyk 2026\");\n<\/script>\n\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/div>\n\t\t\t\t\t\t\t<\/div>\n\t\t<\/section>\n\t\t\t\t\t\t\t<\/div>\n\t\t","protected":false},"excerpt":{"rendered":"<p>Model: Parametr: Godzina (UTC): \ud83d\udca1 Skr\u00f3ty klawiszowe: 1-4 | Wyb\u00f3r modelu \u2190 \u2192 | Zmiana Godziny \u2191 \u2193 | Zmiana parametru A | Animacja N | Powr\u00f3t do aktualnej sytuacji R | Od\u015bwie\u017c Prognoza ultrakr\u00f3tkoterminowa Godzina (UTC): &#8212; Ostatnie sprawdzenie: jeszcze nie wykonano \u25b6 N \u0141adowanie mapy&#8230; \u00d7<\/p>\n","protected":false},"author":11,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"ocean_post_layout":"","ocean_both_sidebars_style":"","ocean_both_sidebars_content_width":0,"ocean_both_sidebars_sidebars_width":0,"ocean_sidebar":"","ocean_second_sidebar":"","ocean_disable_margins":"enable","ocean_add_body_class":"","ocean_shortcode_before_top_bar":"","ocean_shortcode_after_top_bar":"","ocean_shortcode_before_header":"","ocean_shortcode_after_header":"","ocean_has_shortcode":"","ocean_shortcode_after_title":"","ocean_shortcode_before_footer_widgets":"","ocean_shortcode_after_footer_widgets":"","ocean_shortcode_before_footer_bottom":"","ocean_shortcode_after_footer_bottom":"","ocean_display_top_bar":"default","ocean_display_header":"default","ocean_header_style":"","ocean_center_header_left_menu":"","ocean_custom_header_template":"","ocean_custom_logo":0,"ocean_custom_retina_logo":0,"ocean_custom_logo_max_width":0,"ocean_custom_logo_tablet_max_width":0,"ocean_custom_logo_mobile_max_width":0,"ocean_custom_logo_max_height":0,"ocean_custom_logo_tablet_max_height":0,"ocean_custom_logo_mobile_max_height":0,"ocean_header_custom_menu":"","ocean_menu_typo_font_family":"","ocean_menu_typo_font_subset":"","ocean_menu_typo_font_size":0,"ocean_menu_typo_font_size_tablet":0,"ocean_menu_typo_font_size_mobile":0,"ocean_menu_typo_font_size_unit":"px","ocean_menu_typo_font_weight":"","ocean_menu_typo_font_weight_tablet":"","ocean_menu_typo_font_weight_mobile":"","ocean_menu_typo_transform":"","ocean_menu_typo_transform_tablet":"","ocean_menu_typo_transform_mobile":"","ocean_menu_typo_line_height":0,"ocean_menu_typo_line_height_tablet":0,"ocean_menu_typo_line_height_mobile":0,"ocean_menu_typo_line_height_unit":"","ocean_menu_typo_spacing":0,"ocean_menu_typo_spacing_tablet":0,"ocean_menu_typo_spacing_mobile":0,"ocean_menu_typo_spacing_unit":"","ocean_menu_link_color":"","ocean_menu_link_color_hover":"","ocean_menu_link_color_active":"","ocean_menu_link_background":"","ocean_menu_link_hover_background":"","ocean_menu_link_active_background":"","ocean_menu_social_links_bg":"","ocean_menu_social_hover_links_bg":"","ocean_menu_social_links_color":"","ocean_menu_social_hover_links_color":"","ocean_disable_title":"default","ocean_disable_heading":"default","ocean_post_title":"","ocean_post_subheading":"","ocean_post_title_style":"","ocean_post_title_background_color":"","ocean_post_title_background":0,"ocean_post_title_bg_image_position":"","ocean_post_title_bg_image_attachment":"","ocean_post_title_bg_image_repeat":"","ocean_post_title_bg_image_size":"","ocean_post_title_height":0,"ocean_post_title_bg_overlay":0.5,"ocean_post_title_bg_overlay_color":"","ocean_disable_breadcrumbs":"default","ocean_breadcrumbs_color":"","ocean_breadcrumbs_separator_color":"","ocean_breadcrumbs_links_color":"","ocean_breadcrumbs_links_hover_color":"","ocean_display_footer_widgets":"default","ocean_display_footer_bottom":"default","ocean_custom_footer_template":""},"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v19.5.1 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Nowcasting - Laboratorium Modelowania Meteorologicznego CMOK IMGW-PIB<\/title>\n<meta name=\"description\" content=\"Prognozy ultra-kr\u00f3tkoterminowe IMGW-PIB\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/cmm.imgw.pl\/?page_id=49229\" \/>\n<meta property=\"og:locale\" content=\"pl_PL\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Nowcasting - Laboratorium Modelowania Meteorologicznego CMOK IMGW-PIB\" \/>\n<meta property=\"og:description\" content=\"Prognozy ultra-kr\u00f3tkoterminowe IMGW-PIB\" \/>\n<meta property=\"og:url\" content=\"https:\/\/cmm.imgw.pl\/?page_id=49229\" \/>\n<meta property=\"og:site_name\" content=\"Laboratorium Modelowania Meteorologicznego CMOK IMGW-PIB\" \/>\n<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/Meteoimgw\/\" \/>\n<meta property=\"article:modified_time\" content=\"2026-05-14T08:33:42+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/cmm.imgw.pl\/wp-content\/uploads\/2025\/10\/MODELE_LOGO_UNIFIKACJA_v2.png\" \/>\n\t<meta property=\"og:image:width\" content=\"1356\" \/>\n\t<meta property=\"og:image:height\" content=\"365\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:site\" content=\"@IMGW_CMM\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\/\/cmm.imgw.pl\/?page_id=49229\",\"url\":\"https:\/\/cmm.imgw.pl\/?page_id=49229\",\"name\":\"Nowcasting - Laboratorium Modelowania Meteorologicznego CMOK IMGW-PIB\",\"isPartOf\":{\"@id\":\"https:\/\/cmm.imgw.pl\/#website\"},\"datePublished\":\"2026-02-04T12:49:16+00:00\",\"dateModified\":\"2026-05-14T08:33:42+00:00\",\"description\":\"Prognozy ultra-kr\u00f3tkoterminowe IMGW-PIB\",\"breadcrumb\":{\"@id\":\"https:\/\/cmm.imgw.pl\/?page_id=49229#breadcrumb\"},\"inLanguage\":\"pl-PL\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/cmm.imgw.pl\/?page_id=49229\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/cmm.imgw.pl\/?page_id=49229#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/cmm.imgw.pl\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Nowcasting\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/cmm.imgw.pl\/#website\",\"url\":\"https:\/\/cmm.imgw.pl\/\",\"name\":\"Laboratorium Modelowania Meteorologicznego CMOK IMGW-PIB\",\"description\":\"CMOK-LMM Laboratorium pe\u0142ni pa\u0144stwow\u0105 s\u0142u\u017cb\u0119 hydrologiczno-meteorologiczn\u0105 w zakresie numerycznych prognoz pogody, kt\u00f3rego zadaniem jest konsolidacja kompetencji w obszarze modelowania zjawisk pogodowych oraz dalszego rozwoju numerycznych modeli pogody (NMP).\",\"publisher\":{\"@id\":\"https:\/\/cmm.imgw.pl\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/cmm.imgw.pl\/?s={search_term_string}\"},\"query-input\":\"required name=search_term_string\"}],\"inLanguage\":\"pl-PL\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/cmm.imgw.pl\/#organization\",\"name\":\"Laboratorium Modelowania Meteorologicznego CMOK IMGW-PIB\",\"url\":\"https:\/\/cmm.imgw.pl\/\",\"sameAs\":[\"https:\/\/www.facebook.com\/Meteoimgw\/\",\"https:\/\/twitter.com\/IMGW_CMM\"],\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"pl-PL\",\"@id\":\"https:\/\/cmm.imgw.pl\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/cmm.imgw.pl\/wp-content\/uploads\/2025\/10\/MODELE_LOGO_UNIFIKACJA_v2.png\",\"contentUrl\":\"https:\/\/cmm.imgw.pl\/wp-content\/uploads\/2025\/10\/MODELE_LOGO_UNIFIKACJA_v2.png\",\"width\":1356,\"height\":365,\"caption\":\"Laboratorium Modelowania Meteorologicznego CMOK IMGW-PIB\"},\"image\":{\"@id\":\"https:\/\/cmm.imgw.pl\/#\/schema\/logo\/image\/\"}}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Nowcasting - Laboratorium Modelowania Meteorologicznego CMOK IMGW-PIB","description":"Prognozy ultra-kr\u00f3tkoterminowe IMGW-PIB","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/cmm.imgw.pl\/?page_id=49229","og_locale":"pl_PL","og_type":"article","og_title":"Nowcasting - Laboratorium Modelowania Meteorologicznego CMOK IMGW-PIB","og_description":"Prognozy ultra-kr\u00f3tkoterminowe IMGW-PIB","og_url":"https:\/\/cmm.imgw.pl\/?page_id=49229","og_site_name":"Laboratorium Modelowania Meteorologicznego CMOK IMGW-PIB","article_publisher":"https:\/\/www.facebook.com\/Meteoimgw\/","article_modified_time":"2026-05-14T08:33:42+00:00","og_image":[{"width":1356,"height":365,"url":"https:\/\/cmm.imgw.pl\/wp-content\/uploads\/2025\/10\/MODELE_LOGO_UNIFIKACJA_v2.png","type":"image\/png"}],"twitter_card":"summary_large_image","twitter_site":"@IMGW_CMM","schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/cmm.imgw.pl\/?page_id=49229","url":"https:\/\/cmm.imgw.pl\/?page_id=49229","name":"Nowcasting - Laboratorium Modelowania Meteorologicznego CMOK IMGW-PIB","isPartOf":{"@id":"https:\/\/cmm.imgw.pl\/#website"},"datePublished":"2026-02-04T12:49:16+00:00","dateModified":"2026-05-14T08:33:42+00:00","description":"Prognozy ultra-kr\u00f3tkoterminowe IMGW-PIB","breadcrumb":{"@id":"https:\/\/cmm.imgw.pl\/?page_id=49229#breadcrumb"},"inLanguage":"pl-PL","potentialAction":[{"@type":"ReadAction","target":["https:\/\/cmm.imgw.pl\/?page_id=49229"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/cmm.imgw.pl\/?page_id=49229#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/cmm.imgw.pl\/"},{"@type":"ListItem","position":2,"name":"Nowcasting"}]},{"@type":"WebSite","@id":"https:\/\/cmm.imgw.pl\/#website","url":"https:\/\/cmm.imgw.pl\/","name":"Laboratorium Modelowania Meteorologicznego CMOK IMGW-PIB","description":"CMOK-LMM Laboratorium pe\u0142ni pa\u0144stwow\u0105 s\u0142u\u017cb\u0119 hydrologiczno-meteorologiczn\u0105 w zakresie numerycznych prognoz pogody, kt\u00f3rego zadaniem jest konsolidacja kompetencji w obszarze modelowania zjawisk pogodowych oraz dalszego rozwoju numerycznych modeli pogody (NMP).","publisher":{"@id":"https:\/\/cmm.imgw.pl\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/cmm.imgw.pl\/?s={search_term_string}"},"query-input":"required name=search_term_string"}],"inLanguage":"pl-PL"},{"@type":"Organization","@id":"https:\/\/cmm.imgw.pl\/#organization","name":"Laboratorium Modelowania Meteorologicznego CMOK IMGW-PIB","url":"https:\/\/cmm.imgw.pl\/","sameAs":["https:\/\/www.facebook.com\/Meteoimgw\/","https:\/\/twitter.com\/IMGW_CMM"],"logo":{"@type":"ImageObject","inLanguage":"pl-PL","@id":"https:\/\/cmm.imgw.pl\/#\/schema\/logo\/image\/","url":"https:\/\/cmm.imgw.pl\/wp-content\/uploads\/2025\/10\/MODELE_LOGO_UNIFIKACJA_v2.png","contentUrl":"https:\/\/cmm.imgw.pl\/wp-content\/uploads\/2025\/10\/MODELE_LOGO_UNIFIKACJA_v2.png","width":1356,"height":365,"caption":"Laboratorium Modelowania Meteorologicznego CMOK IMGW-PIB"},"image":{"@id":"https:\/\/cmm.imgw.pl\/#\/schema\/logo\/image\/"}}]}},"_links":{"self":[{"href":"https:\/\/cmm.imgw.pl\/index.php?rest_route=\/wp\/v2\/pages\/49229"}],"collection":[{"href":"https:\/\/cmm.imgw.pl\/index.php?rest_route=\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/cmm.imgw.pl\/index.php?rest_route=\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/cmm.imgw.pl\/index.php?rest_route=\/wp\/v2\/users\/11"}],"replies":[{"embeddable":true,"href":"https:\/\/cmm.imgw.pl\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=49229"}],"version-history":[{"count":103,"href":"https:\/\/cmm.imgw.pl\/index.php?rest_route=\/wp\/v2\/pages\/49229\/revisions"}],"predecessor-version":[{"id":50157,"href":"https:\/\/cmm.imgw.pl\/index.php?rest_route=\/wp\/v2\/pages\/49229\/revisions\/50157"}],"wp:attachment":[{"href":"https:\/\/cmm.imgw.pl\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=49229"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}